Mesen-SX/Core/GbDmaController.cpp

106 lines
3 KiB
C++
Raw Normal View History

#include "stdafx.h"
#include "GbDmaController.h"
#include "MessageManager.h"
#include "CpuTypes.h"
#include "GbMemoryManager.h"
GbDmaController::GbDmaController(GbMemoryManager* memoryManager)
{
_memoryManager = memoryManager;
_state = {};
}
void GbDmaController::Exec()
{
if(_state.DmaCounter > 0) {
if(_state.DmaCounter <= 160) {
_memoryManager->WriteDma(0xFE00 + (160 - _state.DmaCounter), _state.DmaReadBuffer);
}
_state.DmaCounter--;
_state.DmaReadBuffer = _memoryManager->ReadDma((_state.OamDmaDest << 8) + (160 - _state.DmaCounter));
}
if(_state.DmaStartDelay > 0) {
if(--_state.DmaStartDelay == 0) {
_state.InternalDest = _state.OamDmaDest;
_state.DmaCounter = 161;
}
}
}
bool GbDmaController::IsOamDmaRunning()
{
return _state.DmaCounter > 0 && _state.DmaCounter < 161;
}
uint8_t GbDmaController::Read()
{
return _state.OamDmaDest;
}
void GbDmaController::Write(uint8_t value)
{
_state.DmaStartDelay = 1;
_state.OamDmaDest = value;
}
uint8_t GbDmaController::ReadCgb(uint16_t addr)
{
switch(addr) {
case 0xFF51: return _state.CgbDmaSource >> 8;
case 0xFF52: return _state.CgbDmaSource & 0xFF;
case 0xFF53: return _state.CgbDmaDest >> 8;
case 0xFF54: return _state.CgbDmaDest & 0xFF;
case 0xFF55: return _state.CgbDmaLength | (_state.CgbHdmaMode ? 0x80 : 0);
}
return 0;
}
void GbDmaController::WriteCgb(uint16_t addr, uint8_t value)
{
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:
_state.CgbDmaLength = value & 0x7F;
_state.CgbHdmaMode = (value & 0x80) != 0;
if(!_state.CgbHdmaMode) {
//TODO check invalid dma sources/etc.
//4 cycles for setup
_memoryManager->Exec();
int count = (_state.CgbDmaLength + 1) * 16;
for(int i = 0; i < count; i++) {
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
if(_memoryManager->IsHighSpeed() || (i & 0x01)) {
//TODO: Not perfectly accurate (in "slow" mode mode both occur on the same cycle)
_memoryManager->Exec();
}
_memoryManager->Write(dst, _memoryManager->Read(_state.CgbDmaSource + i, MemoryOperationType::DmaRead));
}
//Source/Dest/Length are all modified by the DMA process and keep their last value after DMA completes
_state.CgbDmaSource += count;
_state.CgbDmaDest += count;
_state.CgbDmaLength = 0x7F;
} else {
MessageManager::Log("TODO HDMA");
}
break;
}
}
void GbDmaController::Serialize(Serializer& s)
{
s.Stream(
_state.OamDmaDest, _state.DmaStartDelay, _state.InternalDest, _state.DmaCounter, _state.DmaReadBuffer,
_state.CgbDmaDest, _state.CgbDmaLength, _state.CgbDmaSource, _state.CgbHdmaMode
);
}