xemu/hw/xbox/mcpx/dsp/dsp.c

243 lines
6.0 KiB
C

/*
* MCPX DSP emulator
*
* Copyright (c) 2015 espes
* Copyright (c) 2020-2025 Matt Borgerson
*
* Adapted from Hatari DSP M56001 emulation
* (C) 2001-2008 ARAnyM developer team
* Adaption to Hatari (C) 2008 by Thomas Huth
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "qemu/osdep.h"
#include "dsp_cpu.h"
#include "dsp_dma.h"
#include "dsp_state.h"
#include "dsp.h"
#include "debug.h"
#include "trace.h"
/* Defines */
#define BITMASK(x) ((1<<(x))-1)
#define INTERRUPT_ABORT_FRAME (1 << 0)
#define INTERRUPT_START_FRAME (1 << 1)
#define INTERRUPT_DMA_EOL (1 << 7)
static uint32_t read_peripheral(dsp_core_t* core, uint32_t address);
static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value);
DSPState *dsp_init(void *rw_opaque,
dsp_scratch_rw_func scratch_rw,
dsp_fifo_rw_func fifo_rw)
{
DPRINTF("dsp_init\n");
DSPState* dsp = (DSPState*)malloc(sizeof(DSPState));
memset(dsp, 0, sizeof(*dsp));
dsp->core.read_peripheral = read_peripheral;
dsp->core.write_peripheral = write_peripheral;
dsp->dma.core = &dsp->core;
dsp->dma.rw_opaque = rw_opaque;
dsp->dma.scratch_rw = scratch_rw;
dsp->dma.fifo_rw = fifo_rw;
dsp_reset(dsp);
return dsp;
}
void dsp_reset(DSPState* dsp)
{
dsp56k_reset_cpu(&dsp->core);
dsp->save_cycles = 0;
}
void dsp_destroy(DSPState* dsp)
{
free(dsp);
}
static uint32_t read_peripheral(dsp_core_t* core, uint32_t address) {
DSPState* dsp = container_of(core, DSPState, core);
uint32_t v = 0xababa;
switch(address) {
case 0xFFFFB3:
v = 0; // core->num_inst; // ??
break;
case 0xFFFFC5:
v = dsp->interrupts;
if (dsp->dma.eol) {
v |= INTERRUPT_DMA_EOL;
}
break;
case 0xFFFFD4:
v = dsp_dma_read(&dsp->dma, DMA_NEXT_BLOCK);
break;
case 0xFFFFD5:
v = dsp_dma_read(&dsp->dma, DMA_START_BLOCK);
break;
case 0xFFFFD6:
v = dsp_dma_read(&dsp->dma, DMA_CONTROL);
break;
case 0xFFFFD7:
v = dsp_dma_read(&dsp->dma, DMA_CONFIGURATION);
break;
}
trace_dsp_read_peripheral(address, v);
return v;
}
static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value) {
DSPState* dsp = container_of(core, DSPState, core);
switch(address) {
case 0xFFFFC4:
if (value & 1) {
core->is_idle = true;
}
break;
case 0xFFFFC5:
dsp->interrupts &= ~value;
if (value & INTERRUPT_DMA_EOL) {
dsp->dma.eol = false;
}
break;
case 0xFFFFD4:
dsp_dma_write(&dsp->dma, DMA_NEXT_BLOCK, value);
break;
case 0xFFFFD5:
dsp_dma_write(&dsp->dma, DMA_START_BLOCK, value);
break;
case 0xFFFFD6:
dsp_dma_write(&dsp->dma, DMA_CONTROL, value);
break;
case 0xFFFFD7:
dsp_dma_write(&dsp->dma, DMA_CONFIGURATION, value);
break;
}
trace_dsp_write_peripheral(address, value);
}
void dsp_step(DSPState* dsp)
{
dsp56k_execute_instruction(&dsp->core);
}
void dsp_run(DSPState* dsp, int cycles)
{
dsp->save_cycles += cycles;
if (dsp->save_cycles <= 0) return;
int dma_timer = 0;
while (dsp->save_cycles > 0)
{
dsp56k_execute_instruction(&dsp->core);
dsp->save_cycles -= dsp->core.instr_cycle;
dsp->core.cycle_count++;
if (dsp->dma.control & DMA_CONTROL_RUNNING) {
dma_timer++;
}
if (dma_timer > 2) {
dma_timer = 0;
dsp->dma.control &= ~DMA_CONTROL_RUNNING;
dsp->dma.control |= DMA_CONTROL_STOPPED;
}
if (dsp->core.is_idle) break;
}
/* FIXME: DMA timing be done cleaner. Xbox enables running
* then polls to make sure its running. But we complete DMA instantaneously,
* so when is it supposed to be signaled that it stopped? Maybe just wait at
* least one cycle? How long does hardware wait?
*/
}
void dsp_bootstrap(DSPState* dsp)
{
// scratch memory is dma'd in to pram by the bootrom
dsp->dma.scratch_rw(dsp->dma.rw_opaque,
(uint8_t*)dsp->core.pram, 0, 0x800*4, false);
for (int i = 0; i < 0x800; i++) {
if (dsp->core.pram[i] & 0xff000000) {
DPRINTF("Bootstrap %04x: %08x\n", i, dsp->core.pram[i]);
dsp->core.pram[i] &= 0x00ffffff;
}
}
memset(dsp->core.pram_opcache, 0, sizeof(dsp->core.pram_opcache));
}
void dsp_start_frame(DSPState* dsp)
{
dsp->interrupts |= INTERRUPT_START_FRAME;
}
uint32_t dsp_read_memory(DSPState* dsp, char space, uint32_t address)
{
int space_id;
switch (space) {
case 'X':
space_id = DSP_SPACE_X;
break;
case 'Y':
space_id = DSP_SPACE_Y;
break;
case 'P':
space_id = DSP_SPACE_P;
break;
default:
assert(false);
return 0;
}
return dsp56k_read_memory(&dsp->core, space_id, address);
}
void dsp_write_memory(DSPState* dsp, char space, uint32_t address, uint32_t value)
{
int space_id;
switch (space) {
case 'X':
space_id = DSP_SPACE_X;
break;
case 'Y':
space_id = DSP_SPACE_Y;
break;
case 'P':
space_id = DSP_SPACE_P;
break;
default:
assert(false);
return;
}
dsp56k_write_memory(&dsp->core, space_id, address, value);
}