pureikyubu/SRC/Hardware/DI.cpp
2020-07-21 23:28:29 +03:00

391 lines
10 KiB
C++

// DI - Flipper Disk Interface
#include "pch.h"
// DI state (registers and other data)
DIControl di;
static uint8_t DIHostToDduCallbackCommand();
static uint8_t DIHostToDduCallbackData();
static void DIDduToHostCallback(uint8_t data);
// ---------------------------------------------------------------------------
// cover control. Callbacks are issued from DDU
static void DIOpenCover()
{
// cover interrupt
DICVR |= DI_CVR_CVRINT;
if(DICVR & DI_CVR_CVRINTMSK)
{
PIAssertInt(PI_INTERRUPT_DI);
}
}
static void DICloseCover()
{
// cover interrupt
DICVR |= DI_CVR_CVRINT;
if(DICVR & DI_CVR_CVRINTMSK)
{
PIAssertInt(PI_INTERRUPT_DI);
}
}
// ---------------------------------------------------------------------------
// Device Error
static void DIErrorCallback()
{
DICR &= ~DI_CR_TSTART;
DISR |= DI_SR_DEINT;
if (DISR & DI_SR_DEINTMSK)
{
PIAssertInt(PI_INTERRUPT_DI);
}
DVD::DDU->SetTransferCallbacks(DIHostToDduCallbackCommand, DIDduToHostCallback);
}
// ---------------------------------------------------------------------------
// Transfer
// DI breaks itself only after finishing next 32 Byte chunk of data
static void DIBreak()
{
DICR &= ~DI_CR_TSTART;
DISR &= ~DI_SR_BRK;
DISR |= DI_SR_BRKINT;
if (DISR & DI_SR_BRKINTMSK)
{
PIAssertInt(PI_INTERRUPT_DI);
}
DVD::DDU->SetTransferCallbacks(DIHostToDduCallbackCommand, DIDduToHostCallback);
}
// DDU transfer complete interrupt
static void DITransferComplete()
{
DICR &= ~DI_CR_TSTART;
DISR |= DI_SR_TCINT;
if (DISR & DI_SR_TCINTMSK)
{
PIAssertInt(PI_INTERRUPT_DI);
}
DVD::DDU->SetTransferCallbacks(DIHostToDduCallbackCommand, DIDduToHostCallback);
}
static uint8_t DIHostToDduCallbackCommand()
{
uint8_t data = 0;
// DI Imm Write Command (DILEN ignored)
if (di.hostToDduByteCounter < sizeof(di.cmdbuf))
{
data = di.cmdbuf[di.hostToDduByteCounter++];
}
if (di.hostToDduByteCounter >= sizeof(di.cmdbuf))
{
// Dont stop DDU Bus clock
// Issue transfer data
DVD::DDU->SetTransferCallbacks(DIHostToDduCallbackData, DIDduToHostCallback);
DVD::DDU->StartTransfer(DICR & DI_CR_RW ? DVD::DduBusDirection::HostToDdu : DVD::DduBusDirection::DduToHost);
if (DICR & DI_CR_RW)
{
di.hostToDduByteCounter = 32; // A special value that overloads the FIFO before reading the first byte from the DDU side.
}
else
{
di.dduToHostByteCounter = 0;
}
}
return data;
}
static uint8_t DIHostToDduCallbackData()
{
uint8_t data = 0;
if (DICR & DI_CR_DMA)
{
// DI Dma Write
if (di.hostToDduByteCounter >= 32)
{
di.hostToDduByteCounter = 0;
if (DILEN)
{
uint32_t dimar = DIMAR & DI_DIMAR_MASK;
MIReadBurst(dimar, di.dmaFifo);
DIMAR += 32;
DILEN -= 32;
}
if (DILEN == 0)
{
DVD::DDU->TransferComplete(); // Stop DDU Bus clock
DITransferComplete();
return 0;
}
if (DISR & DI_SR_BRK)
{
// Can break only after writing next chunk
DIBreak();
return 0;
}
}
data = di.dmaFifo[di.hostToDduByteCounter];
di.hostToDduByteCounter++;
}
else
{
if (di.hostToDduByteCounter < sizeof(di.immbuf))
{
data = di.immbuf[di.hostToDduByteCounter++];
}
if (di.hostToDduByteCounter >= sizeof(di.immbuf))
{
DVD::DDU->TransferComplete(); // Stop DDU Bus clock
DITransferComplete();
}
}
return data;
}
static void DIDduToHostCallback(uint8_t data)
{
if (DICR & DI_CR_DMA)
{
// DI Dma Read
di.dmaFifo[di.dduToHostByteCounter] = data;
di.dduToHostByteCounter++;
if (di.dduToHostByteCounter >= 32)
{
di.dduToHostByteCounter = 0;
if (DISR & DI_SR_BRK)
{
// Can break only after reading next chunk
DIBreak();
return;
}
if (DILEN)
{
uint32_t dimar = DIMAR & DI_DIMAR_MASK;
MIWriteBurst(dimar, di.dmaFifo);
DIMAR += 32;
DILEN -= 32;
}
if (DILEN == 0)
{
DVD::DDU->TransferComplete(); // Stop DDU Bus clock
DITransferComplete();
}
}
}
else
{
// DI Imm Read (DILEN ignored)
if (di.dduToHostByteCounter < sizeof(di.immbuf))
{
di.immbuf[di.dduToHostByteCounter] = data;
di.dduToHostByteCounter++;
}
if (di.dduToHostByteCounter >= sizeof(di.immbuf))
{
di.dduToHostByteCounter = 0;
DVD::DDU->TransferComplete(); // Stop DDU Bus clock
DITransferComplete();
}
}
}
// ---------------------------------------------------------------------------
// DI register traps
// status register
static void write_sr(uint32_t addr, uint32_t data)
{
// set masks
if(data & DI_SR_BRKINTMSK) DISR |= DI_SR_BRKINTMSK;
else DISR &= ~DI_SR_BRKINTMSK;
if(data & DI_SR_TCINTMSK) DISR |= DI_SR_TCINTMSK;
else DISR &= ~DI_SR_TCINTMSK;
// clear interrupts
if(data & DI_SR_BRKINT)
{
DISR &= ~DI_SR_BRKINT;
}
if(data & DI_SR_TCINT)
{
DISR &= ~DI_SR_TCINT;
}
if (data & DI_SR_DEINT)
{
DISR &= ~DI_SR_DEINT;
}
if ((DISR & DI_SR_BRKINT) == 0 && (DISR & DI_SR_TCINT) == 0 && (DISR & DI_SR_DEINT) == 0)
{
PIClearInt(PI_INTERRUPT_DI);
}
// Issue break
if(data & DI_SR_BRK)
{
// Send break to DDU immediately (DIBRK signal)
DVD::DDU->Break();
}
}
static void read_sr(uint32_t addr, uint32_t *reg)
{
*reg = (uint16_t)DISR;
}
// control register
static void write_cr(uint32_t addr, uint32_t data)
{
DICR = (uint16_t)data;
// start command
if(DICR & DI_CR_TSTART)
{
// Issue command
di.hostToDduByteCounter = 0;
DVD::DDU->SetTransferCallbacks(DIHostToDduCallbackCommand, DIDduToHostCallback);
DVD::DDU->StartTransfer(DVD::DduBusDirection::HostToDdu);
}
}
static void read_cr(uint32_t addr, uint32_t *reg)
{
*reg = (uint16_t)DICR;
}
// cover register
static void write_cvr(uint32_t addr, uint32_t data)
{
// clear cover interrupt
if(data & DI_CVR_CVRINT)
{
DICVR &= ~DI_CVR_CVRINT;
PIClearInt(PI_INTERRUPT_DI);
}
// set mask
if(data & DI_CVR_CVRINTMSK) DICVR |= DI_CVR_CVRINTMSK;
else DICVR &= ~DI_CVR_CVRINTMSK;
}
static void read_cvr(uint32_t addr, uint32_t *reg)
{
uint32_t value = DICVR & ~DI_CVR_CVR;
if (DVD::DDU->GetCoverStatus() == DVD::CoverStatus::Open)
{
value |= DI_CVR_CVR;
}
*reg = value;
}
// dma registers
static void read_mar(uint32_t addr, uint32_t *reg) { *reg = DIMAR & DI_DIMAR_MASK; }
static void write_mar(uint32_t addr, uint32_t data) { DIMAR = data; }
static void read_len(uint32_t addr, uint32_t *reg) { *reg = DILEN & ~0x1f; }
static void write_len(uint32_t addr, uint32_t data) { DILEN = data; }
static void DISetCommandBuffer(int n, uint32_t value)
{
volatile uint8_t* ptr = &di.cmdbuf[n * 4];
*(uint32_t*)ptr = _byteswap_ulong(value);
}
static uint32_t DIGetCommandBuffer(int n)
{
volatile uint8_t* ptr = &di.cmdbuf[n * 4];
return _byteswap_ulong(*(uint32_t*)ptr);
}
// di buffers
static void read_cmdbuf0(uint32_t addr, uint32_t *reg) { *reg = DIGetCommandBuffer(0); }
static void write_cmdbuf0(uint32_t addr, uint32_t data) { DISetCommandBuffer(0, data); }
static void read_cmdbuf1(uint32_t addr, uint32_t *reg) { *reg = DIGetCommandBuffer(1); }
static void write_cmdbuf1(uint32_t addr, uint32_t data) { DISetCommandBuffer(1, data); }
static void read_cmdbuf2(uint32_t addr, uint32_t *reg) { *reg = DIGetCommandBuffer(2); }
static void write_cmdbuf2(uint32_t addr, uint32_t data) { DISetCommandBuffer(2, data); }
static void read_immbuf(uint32_t addr, uint32_t *reg) { *reg = _byteswap_ulong(*(uint32_t *)di.immbuf); }
static void write_immbuf(uint32_t addr, uint32_t data) { *(uint32_t*)di.immbuf = _byteswap_ulong(data); }
// register is read only.
// currently, bit 0 is used for ROM scramble disable (which ROM?), bits 1-7 are reserved
// used in EXISync->__OSGetDIConfig call, return 0 and forget.
static void read_cfg(uint32_t addr, uint32_t *reg) { *reg = 0; }
// ---------------------------------------------------------------------------
// init
void DIOpen()
{
Debug::Report(Debug::Channel::DI, "DVD interface hardware\n");
// Current DVD is set by Loader, or when disk is swapped by UI.
// clear registers
memset(&di, 0, sizeof(DIControl));
di.log = true;
// Register DDU callbacks
DVD::DDU->SetCoverOpenCallback(DIOpenCover);
DVD::DDU->SetCoverCloseCallback(DICloseCover);
DVD::DDU->SetErrorCallback(DIErrorCallback);
DVD::DDU->SetTransferCallbacks(DIHostToDduCallbackCommand, DIDduToHostCallback);
// set 32-bit register traps
MISetTrap(32, DI_SR , read_sr , write_sr);
MISetTrap(32, DI_CVR , read_cvr , write_cvr);
MISetTrap(32, DI_CMDBUF0, read_cmdbuf0 , write_cmdbuf0);
MISetTrap(32, DI_CMDBUF1, read_cmdbuf1 , write_cmdbuf1);
MISetTrap(32, DI_CMDBUF2, read_cmdbuf2 , write_cmdbuf2);
MISetTrap(32, DI_MAR , read_mar , write_mar);
MISetTrap(32, DI_LEN , read_len , write_len);
MISetTrap(32, DI_CR , read_cr , write_cr);
MISetTrap(32, DI_IMMBUF , read_immbuf , write_immbuf);
MISetTrap(32, DI_CFG , read_cfg , NULL);
}
void DIClose()
{
DVD::DDU->TransferComplete();
DVD::DDU->SetCoverOpenCallback(nullptr);
DVD::DDU->SetCoverCloseCallback(nullptr);
DVD::DDU->SetErrorCallback(nullptr);
DVD::DDU->SetTransferCallbacks(nullptr, nullptr);
di.dduToHostByteCounter = 0;
di.hostToDduByteCounter = 32;
}