2020-05-23 00:45:22 -04:00
|
|
|
#include "stdafx.h"
|
|
|
|
#include "GbDmaController.h"
|
|
|
|
#include "GbMemoryManager.h"
|
2020-05-28 00:15:37 -04:00
|
|
|
#include "GbPpu.h"
|
2020-06-09 22:40:35 -04:00
|
|
|
#include "GbCpu.h"
|
2020-05-23 00:45:22 -04:00
|
|
|
|
2020-06-09 22:40:35 -04:00
|
|
|
void GbDmaController::Init(GbMemoryManager* memoryManager, GbPpu* ppu, GbCpu* cpu)
|
2020-05-23 00:45:22 -04:00
|
|
|
{
|
|
|
|
_memoryManager = memoryManager;
|
2020-05-28 00:15:37 -04:00
|
|
|
_ppu = ppu;
|
2020-06-09 22:40:35 -04:00
|
|
|
_cpu = cpu;
|
2020-05-23 00:45:22 -04:00
|
|
|
_state = {};
|
|
|
|
}
|
|
|
|
|
2020-06-07 13:09:43 -04:00
|
|
|
GbDmaControllerState GbDmaController::GetState()
|
|
|
|
{
|
|
|
|
return _state;
|
|
|
|
}
|
|
|
|
|
2020-05-23 00:45:22 -04:00
|
|
|
void GbDmaController::Exec()
|
|
|
|
{
|
2020-12-19 23:30:09 +03:00
|
|
|
if (_cpu->IsHalted())
|
|
|
|
{
|
2020-06-09 22:40:35 -04:00
|
|
|
//OAM DMA is halted while the CPU is in halt mode, and resumes when the CPU resumes
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-19 23:30:09 +03:00
|
|
|
if (_state.DmaCounter > 0)
|
|
|
|
{
|
|
|
|
if (_state.DmaCounter <= 160)
|
|
|
|
{
|
2020-05-23 11:19:22 -04:00
|
|
|
_memoryManager->WriteDma(0xFE00 + (160 - _state.DmaCounter), _state.DmaReadBuffer);
|
2020-05-23 00:45:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
_state.DmaCounter--;
|
2020-06-07 13:09:43 -04:00
|
|
|
_state.DmaReadBuffer = _memoryManager->ReadDma((_state.OamDmaSource << 8) + (160 - _state.DmaCounter));
|
2020-05-23 00:45:22 -04:00
|
|
|
}
|
|
|
|
|
2020-12-19 23:30:09 +03:00
|
|
|
if (_state.DmaStartDelay > 0)
|
|
|
|
{
|
|
|
|
if (--_state.DmaStartDelay == 0)
|
|
|
|
{
|
2020-06-07 13:09:43 -04:00
|
|
|
_state.InternalDest = _state.OamDmaSource;
|
2020-05-23 00:45:22 -04:00
|
|
|
_state.DmaCounter = 161;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GbDmaController::IsOamDmaRunning()
|
|
|
|
{
|
|
|
|
return _state.DmaCounter > 0 && _state.DmaCounter < 161;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t GbDmaController::Read()
|
|
|
|
{
|
2020-06-07 13:09:43 -04:00
|
|
|
return _state.OamDmaSource;
|
2020-05-23 00:45:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void GbDmaController::Write(uint8_t value)
|
|
|
|
{
|
|
|
|
_state.DmaStartDelay = 1;
|
2020-06-07 13:09:43 -04:00
|
|
|
_state.OamDmaSource = value;
|
2020-05-23 00:45:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t GbDmaController::ReadCgb(uint16_t addr)
|
|
|
|
{
|
2020-12-19 23:30:09 +03:00
|
|
|
switch (addr)
|
|
|
|
{
|
|
|
|
case 0xFF55: return _state.CgbDmaLength | (_state.CgbHdmaDone ? 0x80 : 0);
|
2020-05-23 00:45:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbDmaController::WriteCgb(uint16_t addr, uint8_t value)
|
|
|
|
{
|
2020-12-19 23:30:09 +03:00
|
|
|
switch (addr)
|
|
|
|
{
|
|
|
|
case 0xFF51: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF) | (value << 8);
|
|
|
|
break;
|
|
|
|
case 0xFF52: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF00) | (value & 0xF0);
|
|
|
|
break;
|
|
|
|
case 0xFF53: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF) | (value << 8);
|
|
|
|
break;
|
|
|
|
case 0xFF54: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF00) | (value & 0xF0);
|
|
|
|
break;
|
|
|
|
case 0xFF55:
|
|
|
|
{
|
2020-05-28 00:15:37 -04:00
|
|
|
bool hdmaMode = (value & 0x80) != 0;
|
2020-05-23 00:45:22 -04:00
|
|
|
_state.CgbDmaLength = value & 0x7F;
|
2020-05-27 22:50:27 -04:00
|
|
|
|
2020-12-19 23:30:09 +03:00
|
|
|
if (!hdmaMode)
|
|
|
|
{
|
|
|
|
if (_state.CgbHdmaRunning)
|
|
|
|
{
|
2020-05-28 00:15:37 -04:00
|
|
|
//"If HDMA5 is written during a HDMA copy, the behaviour depends on the written bit 7.
|
|
|
|
// - New bit 7 is 0: Stop copy. HDMA5 new value is ( 80h OR written_value ).
|
|
|
|
// - New bit 7 is 1: Restart copy. New size is the value of written_value bits 0-6.
|
|
|
|
//This means that HDMA can't switch to GDMA with only one write. It must be stopped first."
|
|
|
|
_state.CgbHdmaRunning = false;
|
|
|
|
_state.CgbHdmaDone = true;
|
2020-12-19 23:30:09 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-07-03 11:08:31 -04:00
|
|
|
//4 cycles for setup
|
|
|
|
_memoryManager->Exec();
|
|
|
|
_memoryManager->Exec();
|
|
|
|
|
2020-12-19 23:30:09 +03:00
|
|
|
do
|
|
|
|
{
|
2020-05-28 00:15:37 -04:00
|
|
|
ProcessDmaBlock();
|
2020-12-19 23:30:09 +03:00
|
|
|
}
|
|
|
|
while (_state.CgbDmaLength != 0x7F);
|
2020-05-28 00:15:37 -04:00
|
|
|
}
|
2020-12-19 23:30:09 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-05-28 00:15:37 -04:00
|
|
|
//"After writing a value to HDMA5 that starts the HDMA copy, the upper bit (that indicates HDMA mode when set to '1') will be cleared"
|
|
|
|
_state.CgbHdmaDone = false;
|
|
|
|
_state.CgbHdmaRunning = true;
|
|
|
|
|
|
|
|
//"If a HDMA transfer is started when the screen is off, one block is copied. "
|
|
|
|
//" When a HDMA transfer is started during HBL it will start right away."
|
2020-12-19 23:30:09 +03:00
|
|
|
if (!_ppu->IsLcdEnabled() || _ppu->GetMode() == PpuMode::HBlank)
|
|
|
|
{
|
2020-05-28 00:15:37 -04:00
|
|
|
ProcessHdma();
|
|
|
|
}
|
2020-05-23 00:45:22 -04:00
|
|
|
}
|
|
|
|
break;
|
2020-05-28 00:15:37 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbDmaController::ProcessHdma()
|
|
|
|
{
|
2020-12-19 23:30:09 +03:00
|
|
|
if (_state.CgbHdmaRunning)
|
|
|
|
{
|
2020-07-03 11:08:31 -04:00
|
|
|
//4 cycles for setup
|
|
|
|
_memoryManager->Exec();
|
|
|
|
_memoryManager->Exec();
|
|
|
|
|
2020-05-28 00:15:37 -04:00
|
|
|
ProcessDmaBlock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbDmaController::ProcessDmaBlock()
|
|
|
|
{
|
|
|
|
//TODO check invalid dma sources/etc.
|
2020-12-19 23:30:09 +03:00
|
|
|
for (int i = 0; i < 16; i++)
|
|
|
|
{
|
2020-05-28 00:15:37 -04:00
|
|
|
uint16_t dst = 0x8000 | ((_state.CgbDmaDest + i) & 0x1FFF);
|
|
|
|
|
|
|
|
//2 or 4 cycles per byte transfered (2x more cycles in high speed mode - effective speed is the same in both modes
|
2020-05-30 23:23:35 -04:00
|
|
|
_memoryManager->Exec();
|
2020-06-02 20:42:58 -04:00
|
|
|
uint8_t value = _memoryManager->Read<MemoryOperationType::DmaRead>(_state.CgbDmaSource + i);
|
2020-12-19 23:30:09 +03:00
|
|
|
if (_memoryManager->IsHighSpeed())
|
|
|
|
{
|
2020-05-28 00:15:37 -04:00
|
|
|
_memoryManager->Exec();
|
|
|
|
}
|
2020-06-02 20:42:58 -04:00
|
|
|
_memoryManager->Write<MemoryOperationType::DmaWrite>(dst, value);
|
2020-05-28 00:15:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
//Source/Dest/Length are all modified by the DMA process and keep their last value after DMA completes
|
|
|
|
_state.CgbDmaSource += 16;
|
|
|
|
_state.CgbDmaDest += 16;
|
|
|
|
_state.CgbDmaLength = (_state.CgbDmaLength - 1) & 0x7F;
|
|
|
|
|
2020-12-19 23:30:09 +03:00
|
|
|
if (_state.CgbHdmaRunning && _state.CgbDmaLength == 0x7F)
|
|
|
|
{
|
2020-05-28 00:15:37 -04:00
|
|
|
_state.CgbHdmaRunning = false;
|
|
|
|
_state.CgbHdmaDone = true;
|
2020-05-23 00:45:22 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbDmaController::Serialize(Serializer& s)
|
|
|
|
{
|
|
|
|
s.Stream(
|
2020-06-07 13:09:43 -04:00
|
|
|
_state.OamDmaSource, _state.DmaStartDelay, _state.InternalDest, _state.DmaCounter, _state.DmaReadBuffer,
|
2020-05-28 00:15:37 -04:00
|
|
|
_state.CgbDmaDest, _state.CgbDmaLength, _state.CgbDmaSource, _state.CgbHdmaDone, _state.CgbHdmaRunning
|
2020-05-23 00:45:22 -04:00
|
|
|
);
|
|
|
|
}
|