pcsx2/pcsx2/ps2/pgif.cpp

741 lines
20 KiB
C++

// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "ps2/Iop/IopHw_Internal.h"
#include "ps2/HwInternal.h"
#include "ps2/pgif.h"
#include "IopHw.h"
#include "IopDma.h"
#include "Common.h"
//NOTES (TODO):
/*
- 8 and 16 bit access to the PGPU regs is not emulated... is it ever used? Emulating it would be tricky.
------
Much of the code is ("very") unoptimized, because it is a bit cleaner and more complete this way.
All the PS1 GPU info comes from psx-spx: http://problemkaputt.de/psx-spx.htm
*/
//Debug printf:
// Set to 1 to log PGIF HW IO
#define LOG_REG 0
#if LOG_REG
#define REG_LOG pgifConLog
#else
#define REG_LOG(...) do {} while(0)
#endif
// Set to 1 to log PGPU DMA
#define LOG_PGPU_DMA 1
#if LOG_PGPU_DMA
#define PGPU_DMA_LOG pgifConLog
#else
#define PGPU_DMA_LOG(...) do {} while(0)
#endif
u32 old_gp0_value = 0;
void fillFifoOnDrain(void);
void drainPgpuDmaLl(void);
void drainPgpuDmaNrToGpu(void);
void drainPgpuDmaNrToIop(void);
void ringBufPut(struct ringBuf_t* rb, u32* data)
{
if (rb->count < rb->size)
{
//there is available space
*(rb->buf + rb->head) = *data;
if ((++(rb->head)) >= rb->size)
rb->head = 0; //wrap back when the end is reached
rb->count++;
}
else
{
// This should never happen. If it does, the code is bad somewhere.
Console.Error("PGIF FIFO overflow! sz= %X", rb->size);
}
}
void ringBufGet(struct ringBuf_t* rb, u32* data)
{
if (rb->count > 0)
{
//there is available data
*data = *(rb->buf + rb->tail);
if ((++(rb->tail)) >= rb->size)
rb->tail = 0; //wrap back when the end is reached
rb->count--;
}
else
{
// This should never happen. If it does, the code is bad somewhere.
Console.Error("PGIF FIFO underflow! sz= %X", rb->size);
}
}
void ringBufferClear(struct ringBuf_t* rb)
{
rb->head = 0;
rb->tail = 0;
rb->count = 0;
// wisi comment:
// Yes, the memset should be commented-out. The reason is that it shouldn't really be necessary (but I am not sure).
// It is better not to be enabled, because clearing the new huge PGIF FIFO, can waste significant time.
//memset(rb->buf, 0, rb->size * sizeof(u32));
return;
}
//Ring buffers definition and initialization:
//Command (GP1) FIFO, size= 0x8 words:
#define PGIF_CMD_RB_SIZE 0x8
struct ringBuf_t rb_gp1; //Ring buffer control variables.
u32 pgif_gp1_buffer[PGIF_CMD_RB_SIZE] = {0}; //Ring buffer data.
//Data (GP0) FIFO - the so-called (in PS1DRV) "GFIFO", (real) size= 0x20 words:
//Using small (the real) FIFO size, disturbs MDEC video (and other stuff),
//because the MDEC does DMA instantly, while this emulation drains the FIFO,
//only when the PS1DRV gets data from it, which depends on IOP-EE sync, among other things.
//The reson it works in the real hardware, is that the MDEC DMA would run in the pauses of GPU DMA,
//thus the GPU DMA would never get data, MDEC hasn't yet written to RAM yet (too early).
#define PGIF_DAT_RB_SIZE 0x20000
struct ringBuf_t rb_gp0; //Ring buffer control variables.
u32 pgif_gp0_buffer[PGIF_DAT_RB_SIZE] = {0}; //Ring buffer data.
dma_t dma;
//TODO: Make this be called by IopHw.cpp / psxHwReset()... but maybe it should be called by the EE reset func,
//given that the PGIF is in the EE ASIC, on the other side of the SBUS.
void pgifInit()
{
rb_gp1.buf = pgif_gp1_buffer;
rb_gp1.size = PGIF_CMD_RB_SIZE;
ringBufferClear(&rb_gp1);
rb_gp0.buf = pgif_gp0_buffer;
rb_gp0.size = PGIF_DAT_RB_SIZE;
ringBufferClear(&rb_gp0);
pgpu.stat.write(0);
pgif.ctrl.write(0);
old_gp0_value = 0;
dmaRegs.madr.address = 0;
dmaRegs.bcr.write(0);
dmaRegs.chcr.write(0);
//pgpuDmaTadr = 0;
dma.state.ll_active = 0;
dma.state.to_gpu_active = 0;
dma.state.to_iop_active = 0;
dma.ll_dma.data_read_address = 0;
dma.ll_dma.current_word = 0;
dma.ll_dma.total_words = 0;
dma.ll_dma.next_address = 0;
dma.normal.total_words = 0;
dma.normal.current_word = 0;
dma.normal.address = 0;
}
//Interrupt-related (IOP, EE and DMA):
void triggerPgifInt(int subCause)
{
// Probably we should try to mess with delta here.
hwIntcIrq(15);
cpuSetEvent();
}
void getIrqCmd(u32 data)
{
//For the IOP - GPU. This is triggered by the GP0(1Fh) - Interrupt Request (IRQ1) command.
//This may break stuff, because it doesn't detect whether this is really a GP0() command or data...
//Since PS1 HW also didn't recognize that is data or not, we should left it enabled.
{
if ((data & 0xFF000000) == 0x1F000000)
{
pgpu.stat.bits.IRQ1 = 1;
iopIntcIrq(1);
}
}
}
void ackGpuIrq1()
{
//Acknowledge for the IOP GPU interrupt.
pgpu.stat.bits.IRQ1 = 0;
}
void pgpuDmaIntr(int trigDma)
{
//For the IOP GPU DMA channel.
//trigDma: 1=normal,ToGPU; 2=normal,FromGPU, 3=LinkedList
// psxmode: 25.09.2016 at this point, the emulator works even when removing this interrupt call. how? why?
#if PREVENT_IRQ_ON_NORM_DMA_TO_GPU == 1
if (trigDma != 1) //Interrupt on ToGPU DMA breaks some games. TODO: Why?
#endif
psxDmaInterrupt(2);
}
//Pass-through & intercepting functions:
u32 immRespHndl(u32 cmd, u32 data)
{
//Handles the GP1(10h) command, that requires immediate response.
//The data argument is the old data of the register (shouldn't be critical what it contains).
switch ((cmd & 0x7))
{
case 0:
case 1:
case 6:
case 7:
break; //Returns Nothing (old value in GPUREAD remains unchanged)
case 2:
data = pgif.imm_response.reg.e2 & 0x000FFFFF;
break; //Read Texture Window setting ;GP0(E2h) ;20bit/MSBs=Nothing
case 3:
data = pgif.imm_response.reg.e3 & 0x0007FFFF;
break; //Read Draw area top left ;GP0(E3h) ;19bit/MSBs=Nothing
case 4:
data = pgif.imm_response.reg.e4 & 0x0007FFFF;
break; //Read Draw area bottom right ;GP0(E4h) ;19bit/MSBs=Nothing
case 5:
data = pgif.imm_response.reg.e5 & 0x003FFFFF;
break; //Read Draw offset ;GP0(E5h) ;22bit
}
return data;
}
void handleGp1Command(u32 cmd)
{
//Check GP1() command and configure PGIF accordingly.
//Commands 0x00 - 0x01, 0x03, 0x05 - 0x08 are fully handled in ps1drv.
const u32 cmdNr = ((cmd >> 24) & 0xFF) & 0x3F;
switch (cmdNr)
{
case 2: //Acknowledge GPU IRQ
ackGpuIrq1();
break;
case 4: //DMA Direction / Data Request. The PS1DRV doesn't care of this... Should we do something on pgif ctrl?
pgpu.stat.bits.DDIR = cmd & 0x3;
//Since DREQ bit is dependent on DDIR bits, we should set it as soon as command is processed.
switch (pgpu.stat.bits.DDIR) //29-30 bit (0=Off, 1=FIFO, 2=CPUtoGP0, 3=GPUREADtoCPU) ;GP1(04h).0-1
{
case 0x00: // When GP1(04h)=0 ---> Always zero (0)
pgpu.stat.bits.DREQ = 0;
break;
case 0x01: // When GP1(04h)=1 ---> FIFO State (0=Full, 1=Not Full)
if (rb_gp0.count < (rb_gp0.size - PGIF_DAT_RB_LEAVE_FREE))
{
pgpu.stat.bits.DREQ = 1;
}
else
{
pgpu.stat.bits.DREQ = 0;
}
break;
case 0x02: // When GP1(04h)=2 ---> Same as GPUSTAT.28
pgpu.stat.bits.DREQ = pgpu.stat.bits.RDMA;
drainPgpuDmaLl(); //See comment in this function.
break;
case 0x03: //When GP1(04h)=3 ---> Same as GPUSTAT.27
pgpu.stat.bits.DREQ = pgpu.stat.bits.RSEND;
break;
}
break;
default:
break;
}
}
u32 getUpdPgpuStatReg()
{
//PS1DRV does set bit RSEND on (probably - took from command print) GP0(C0h), should we do something?
//The PS1 program pools this bit to determine if there is data in the FIFO, it can get. Then starts DMA to get it.
//The PS1 program will not send the DMA direction command (GP1(04h)) and will not start DMA until this bit (27) becomes set.
pgpu.stat.bits.RSEND = pgif.ctrl.bits.data_from_gpu_ready;
return pgpu.stat.get();
}
u8 getGP0RbC_Count()
{
//Returns "correct" element-in-FIFO count, even if extremely large buffer is used.
return std::min(rb_gp0.count, 0x1F);
}
u32 getUpdPgifCtrlReg()
{
//Update fifo counts before returning register value
pgif.ctrl.bits.GP0_fifo_count = getGP0RbC_Count();
pgif.ctrl.bits.GP1_fifo_count = rb_gp1.count;
return pgif.ctrl.get();
}
void rb_gp1_Get(u32* data)
{
ringBufGet(&rb_gp1, data);
handleGp1Command(*data); //Setup GP1 right after reading command from FIFO.
}
void rb_gp0_Get(u32* data)
{
if (rb_gp0.count > 0)
{
ringBufGet(&rb_gp0, data);
getIrqCmd(*data); //Checks if an IRQ CMD passes through here and triggers IRQ when it does.
}
else
{
*data = old_gp0_value;
}
}
//PS1 GPU registers I/O handlers:
void psxGPUw(int addr, u32 data)
{
REG_LOG("PGPU write 0x%08X = 0x%08X", addr, data);
if (addr == HW_PS1_GPU_DATA)
{
ringBufPut(&rb_gp0, &data);
}
else if (addr == HW_PS1_GPU_STATUS)
{
// Check for Cmd 0x10-0x1F
u8 imm_check = (data >> 28);
imm_check &= 0x3;
if (imm_check == 1)
{
//Handle immediate-response command. Commands are NOT sent to the Fifo apparently (PS1DRV).
old_gp0_value = immRespHndl(data, old_gp0_value);
}
else
{
triggerPgifInt(0);
ringBufPut(&rb_gp1, &data);
}
}
}
u32 psxGPUr(int addr)
{
u32 data = 0;
if (addr == HW_PS1_GPU_DATA)
{
rb_gp0_Get(&data);
}
else if (addr == HW_PS1_GPU_STATUS)
{
data = getUpdPgpuStatReg();
}
if (addr != HW_PS1_GPU_STATUS)
REG_LOG("PGPU read 0x%08X = 0x%08X", addr, data);
return data;
}
// PGIF registers I/O handlers:
void PGIFw(int addr, u32 data)
{
//if (((addr != PGIF_CTRL) || (addr != PGPU_STAT) || ((addr == PGIF_CTRL) && (getUpdPgifCtrlReg() != data))) && (addr != PGPU_STAT))
REG_LOG("PGIF write 0x%08X = 0x%08X 0x%08X EEpc= %08X IOPpc= %08X ", addr, data, getUpdPgifCtrlReg(), cpuRegs.pc, psxRegs.pc);
switch (addr)
{
case PGPU_STAT:
pgpu.stat.write(data); //Should all bits be writable?
break;
case PGIF_CTRL:
pgif.ctrl.write(data);
fillFifoOnDrain(); //Now this checks the 0x8 bit of the PGIF_CTRL reg, so it here too,
break; //so that it gets updated immediately once it is set.
case IMM_E2:
pgif.imm_response.reg.e2 = data;
break;
case IMM_E3:
pgif.imm_response.reg.e3 = data;
break;
case IMM_E4:
pgif.imm_response.reg.e4 = data;
break;
case IMM_E5:
pgif.imm_response.reg.e5 = data;
break;
case PGPU_CMD_FIFO:
Console.Error("PGIF CMD FIFO write by EE (SHOULDN'T HAPPEN) 0x%08X = 0x%08X", addr, data);
break;
case PGPU_DAT_FIFO:
ringBufPut(&rb_gp0, &data);
// Console.WriteLn( "\n\r PGIF REVERSE !!! DATA write 0x%08X = 0x%08X IF_CTRL= %08X PGPU_STAT= %08X CmdCnt 0x%X \n\r", addr, data, getUpdPgifCtrlReg(), getUpdPgpuStatReg(), rb_gp1.count);
drainPgpuDmaNrToIop();
break;
default:
DevCon.Error("PGIF write to unknown location 0xx% , data: %x", addr, data);
break;
}
}
// Read PGIF Hardware Registers.
u32 PGIFr(int addr)
{
u32 data = 0;
switch (addr)
{
case PGPU_STAT:
data = pgpu.stat.get();
break;
case PGIF_CTRL:
data = getUpdPgifCtrlReg();
break;
case IMM_E2:
data = pgif.imm_response.reg.e2;
break;
case IMM_E3:
data = pgif.imm_response.reg.e3;
break;
case IMM_E4:
data = pgif.imm_response.reg.e4;
break;
case IMM_E5:
data = pgif.imm_response.reg.e5;
break;
case PGPU_CMD_FIFO:
rb_gp1_Get(&data);
break;
case PGPU_DAT_FIFO:
fillFifoOnDrain();
rb_gp0_Get(&data);
break;
default:
DevCon.Error("PGIF read from unknown location 0xx%", addr);
break;
}
//if (addr != PGPU_DAT_FIFO)
//if ((addr != PGIF_CTRL) || (getUpdPgifCtrlReg() != data))
// if (addr != PGPU_STAT)
// REG_LOG("PGIF read %08X = %08X GPU_ST %08X IF_STAT %08X IOPpc %08X EEpc= %08X ", addr, data, getUpdPgpuStatReg(), getUpdPgifCtrlReg(), psxRegs.pc, cpuRegs.pc);
return data;
}
void PGIFrQword(u32 addr, void* dat)
{
u32* data = (u32*)dat;
if (addr == PGPU_CMD_FIFO)
{
//shouldn't happen
Console.Error("PGIF QW CMD read =ERR!");
}
else if (addr == PGPU_DAT_FIFO)
{
fillFifoOnDrain();
rb_gp0_Get(data + 0);
rb_gp0_Get(data + 1);
rb_gp0_Get(data + 2);
rb_gp0_Get(data + 3);
fillFifoOnDrain();
}
else
{
Console.WriteLn("PGIF QWord Read from address %08X ERR - shouldnt happen!", addr);
Console.WriteLn("Data = %08X %08X %08X %08X ", *(u32*)(data + 0), *(u32*)(data + 1), *(u32*)(data + 2), *(u32*)(data + 3));
}
}
void PGIFwQword(u32 addr, void* dat)
{
u32* data = (u32*)dat;
DevCon.Warning("WARNING PGIF WRITE BY PS1DRV ! - NOT KNOWN TO EVER BE DONE!");
Console.WriteLn("PGIF QW write 0x%08X = 0x%08X %08X %08X %08X ", addr, *(u32*)(data + 0), *(u32*)(data + 1), *(u32*)(data + 2), *(u32*)(data + 3));
if (addr == PGPU_CMD_FIFO)
{
Console.Error("PGIF QW CMD write!");
}
else if (addr == PGPU_DAT_FIFO)
{
ringBufPut(&rb_gp0, (u32*)(data + 0));
ringBufPut(&rb_gp0, (u32*)(data + 1));
ringBufPut(&rb_gp0, (u32*)(data + 2));
ringBufPut(&rb_gp0, (u32*)(data + 3));
drainPgpuDmaNrToIop();
}
}
//DMA-emulating functions:
//This function is used as a global FIFO-DMA-fill function and both Linked-list normal DMA call it,
void fillFifoOnDrain()
{
//Skip filing FIFO with elements, if PS1DRV hasn't set this bit.
//Maybe it could be cleared once FIFO has data?
if (!pgif.ctrl.bits.fifo_GP0_ready_for_data)
return;
//This is done here in a loop, rather than recursively in each function, because a very large buffer causes stack oveflow.
while ((rb_gp0.count < ((rb_gp0.size) - PGIF_DAT_RB_LEAVE_FREE)) && ((dma.state.to_gpu_active) || (dma.state.ll_active)))
{
drainPgpuDmaLl();
drainPgpuDmaNrToGpu();
}
//Clear bit as DMA will be run - normally it should be cleared only once the current request finishes, but the IOP won't notice anything anyway.
//WARNING: Current implementation assume that GPU->IOP DMA uses this flag, so we only clear it here if the mode is not GPU->IOP.
if (((dma.state.ll_active) || (dma.state.to_gpu_active)) && (!dma.state.to_iop_active))
{
pgif.ctrl.bits.fifo_GP0_ready_for_data = 0;
}
}
void drainPgpuDmaLl()
{
if (!dma.state.ll_active)
return;
//Some games (Breath of Fire 3 US) set-up linked-list DMA, but don't immediatelly have the list correctly set-up,
//so the result is that this function loops indefinitely, because of some links pointing back to themselves, forming a loop.
//The solution is to only start DMA once the GP1(04h) - DMA Direction / Data Request command has been set to the value 0x2 (CPU->GPU DMA)
//Buffer full - needs to be drained first.
if (rb_gp0.count >= ((rb_gp0.size) - PGIF_DAT_RB_LEAVE_FREE))
return;
if (dmaRegs.chcr.bits.MAS)
DevCon.Error("Unimplemented backward memory step on PGPU DMA Linked List");
if (dma.ll_dma.current_word >= dma.ll_dma.total_words)
{
if (dma.ll_dma.next_address == DMA_LL_END_CODE)
{
//Reached end of linked list
dma.state.ll_active = 0;
dmaRegs.madr.address = 0x00FFFFFF;
dmaRegs.chcr.bits.BUSY = 0; //Transfer completed => clear busy flag
pgpuDmaIntr(3);
PGPU_DMA_LOG("PGPU DMA Linked List Finished");
}
else
{
//Or the beginning of a new one
u32 data = iopMemRead32(dma.ll_dma.next_address);
PGPU_DMA_LOG( "Next PGPU LL DMA header= %08X ", data);
dmaRegs.madr.address = data & 0x00FFFFFF; //Copy the address in MADR.
dma.ll_dma.data_read_address = dma.ll_dma.next_address + 4; //start of data section of packet
dma.ll_dma.current_word = 0;
dma.ll_dma.total_words = (data >> 24) & 0xFF; // Current length of packet and future address of header word.
dma.ll_dma.next_address = dmaRegs.madr.address;
}
}
else
{
//We are in the middle of linked list transfer
u32 data = iopMemRead32(dma.ll_dma.data_read_address);
PGPU_DMA_LOG( "PGPU LL DMA data= %08X addr %08X ", data, dma.ll_dma.data_read_address);
ringBufPut(&rb_gp0, &data);
dma.ll_dma.data_read_address += 4;
dma.ll_dma.current_word++;
}
}
void drainPgpuDmaNrToGpu()
{
if (!dma.state.to_gpu_active)
return;
//Buffer full - needs to be drained first.
if (rb_gp0.count >= ((rb_gp0.size) - PGIF_DAT_RB_LEAVE_FREE))
return;
if (dma.normal.current_word < dma.normal.total_words)
{
u32 data = iopMemRead32(dma.normal.address);
PGPU_DMA_LOG( "To GPU Normal DMA data= %08X addr %08X ", data, dma.ll_dma.data_read_address);
ringBufPut(&rb_gp0, &data);
if (dmaRegs.chcr.bits.MAS)
{
DevCon.Error("Unimplemented backward memory step on TO GPU DMA");
}
dmaRegs.madr.address += 4;
dma.normal.address += 4;
dma.normal.current_word++;
// decrease block amount only if full block size were drained.
if ((dma.normal.current_word % dmaRegs.bcr.bit.block_size) == 0)
dmaRegs.bcr.bit.block_amount -= 1;
}
if (dma.normal.current_word >= dma.normal.total_words)
{
//Reached end of sequence = complete
dma.state.to_gpu_active = 0;
dmaRegs.chcr.bits.BUSY = 0;
pgpuDmaIntr(1);
PGPU_DMA_LOG("To GPU DMA Normal FINISHED");
}
}
void drainPgpuDmaNrToIop()
{
if (!dma.state.to_iop_active || rb_gp0.count <= 0)
return;
if (dma.normal.current_word < dma.normal.total_words)
{
u32 data = 0;
//This is not the best way, but... is there another?
ringBufGet(&rb_gp0, &data);
iopMemWrite32(dma.normal.address, data);
if (dmaRegs.chcr.bits.MAS)
{
DevCon.Error("Unimplemented backward memory step on FROM GPU DMA");
}
dmaRegs.madr.address += 4;
dma.normal.address += 4;
dma.normal.current_word++;
//dmaRegs.madr.address += 4; //It is unclear if this should be done exactly so... // kozarovv: WHY EVEN DO THAT AGAIN?
if ((dma.normal.current_word % dmaRegs.bcr.bit.block_size) == 0)
{
// decrease block amount only if full block size were drained.
dmaRegs.bcr.bit.block_amount -= 1;
}
PGPU_DMA_LOG("GPU->IOP ba: %x , cw: %x , tw: %x" ,dmaRegs.bcr.bit.block_amount, dma.normal.current_word, dma.normal.total_words);
}
if (dma.normal.current_word >= dma.normal.total_words)
{
dma.state.to_iop_active = 0;
dmaRegs.chcr.bits.BUSY = 0;
pgpuDmaIntr(2);
}
if (rb_gp0.count > 0)
drainPgpuDmaNrToIop();
}
void processPgpuDma()
{
if (!dmaRegs.chcr.bits.TSM)
{
Console.Error("SyncMode 0 on GPU DMA!");
}
if (dmaRegs.chcr.bits.TSM == 3)
{
Console.Warning("SyncMode 3! Assuming SyncMode 1");
dmaRegs.chcr.bits.TSM = 1;
}
PGPU_DMA_LOG("Starting GPU DMA! CHCR %08X BCR %08X MADR %08X ", dmaRegs.chcr.get(), dmaRegs.bcr.get(), dmaRegs.madr.address);
//Linked List Mode
if (dmaRegs.chcr.bits.TSM == 2)
{
//To GPU
if (dmaRegs.chcr.bits.DIR)
{
dma.state.ll_active = 1;
dma.ll_dma.next_address = (dmaRegs.madr.address & 0x00FFFFFF); //The address in IOP RAM where to load the first header word from
dma.ll_dma.current_word = 0;
dma.ll_dma.total_words = 0;
PGPU_DMA_LOG("LL DMA FILL");
//fill a single word in fifo now, because otherwise PS1DRV won't know that a transfer is pending.
fillFifoOnDrain();
return;
}
else
{
Console.Error("Error: Linked list from GPU DMA!");
return;
}
}
dma.normal.current_word = 0;
dma.normal.address = dmaRegs.madr.address & 0x1FFFFFFF; // Sould we allow whole range? Maybe for psx SPR?
dma.normal.total_words = (dmaRegs.bcr.bit.block_size * dmaRegs.bcr.get_block_amount());
if (dmaRegs.chcr.bits.DIR) // to gpu
{
PGPU_DMA_LOG("NORMAL DMA TO GPU");
dma.state.to_gpu_active = 1;
fillFifoOnDrain();
}
else
{
PGPU_DMA_LOG("NORMAL DMA FROM GPU");
dma.state.to_iop_active = 1;
drainPgpuDmaNrToIop();
}
}
u32 psxDma2GpuR(u32 addr)
{
u32 data = 0;
addr &= 0x1FFFFFFF;
switch (addr)
{
case PGPU_DMA_MADR:
data = dmaRegs.madr.address;
break;
case PGPU_DMA_BCR:
data = dmaRegs.bcr.get();
break;
case PGPU_DMA_CHCR:
data = dmaRegs.chcr.get();
break;
case PGPU_DMA_TADR:
data = pgpuDmaTadr;
Console.Error("PGPU DMA read TADR!");
break;
default:
Console.Error("Unknown PGPU DMA read 0x%08X", addr);
break;
}
if (addr != PGPU_DMA_CHCR)
PGPU_DMA_LOG("PGPU DMA read 0x%08X = 0x%08X", addr, data);
return data;
}
void psxDma2GpuW(u32 addr, u32 data)
{
PGPU_DMA_LOG("PGPU DMA write 0x%08X = 0x%08X", addr, data);
addr &= 0x1FFFFFFF;
switch (addr)
{
case PGPU_DMA_MADR:
dmaRegs.madr.address = (data & 0x00FFFFFF);
break;
case PGPU_DMA_BCR:
dmaRegs.bcr.write(data);
break;
case PGPU_DMA_CHCR:
dmaRegs.chcr.write(data);
if (dmaRegs.chcr.bits.BUSY)
{
processPgpuDma();
}
break;
case PGPU_DMA_TADR:
pgpuDmaTadr = data;
Console.Error("PGPU DMA write TADR! ");
break;
default:
Console.Error("Unknown PGPU DMA write 0x%08X = 0x%08X", addr, data);
break;
}
}