Mesen-S/Core/GbTimer.cpp
Sour d204485153 Added Super Game Boy support
Missing support for SGB2's timings and still needs a few tweaks and more testing
2020-06-18 00:58:39 -04:00

145 lines
3.3 KiB
C++

#include "stdafx.h"
#include "GbTimer.h"
#include "GbTypes.h"
#include "GbMemoryManager.h"
#include "GbApu.h"
void GbTimer::Init(GbMemoryManager* memoryManager, GbApu* apu)
{
_apu = apu;
_memoryManager = memoryManager;
_state = {};
_state.TimerDivider = 1024;
//Passes boot_div-dmgABCmgb
//But that test depends on LCD power on timings, so may be wrong.
_state.Divider = 0x06;
}
GbTimer::~GbTimer()
{
}
GbTimerState GbTimer::GetState()
{
return _state;
}
void GbTimer::Exec()
{
if((_state.Divider & 0x03) == 2) {
_state.Reloaded = false;
if(_state.NeedReload) {
ReloadCounter();
}
}
SetDivider(_state.Divider + 2);
}
void GbTimer::ReloadCounter()
{
_state.Counter = _state.Modulo;
_memoryManager->RequestIrq(GbIrqSource::Timer);
_state.NeedReload = false;
_state.Reloaded = true;
}
void GbTimer::SetDivider(uint16_t newValue)
{
if(_state.TimerEnabled && !(newValue & _state.TimerDivider) && (_state.Divider & _state.TimerDivider)) {
_state.Counter++;
if(_state.Counter == 0) {
_state.NeedReload = true;
}
}
uint16_t frameSeqBit = _memoryManager->IsHighSpeed() ? 0x2000 : 0x1000;
if(!(newValue & frameSeqBit) && (_state.Divider & frameSeqBit)) {
_apu->ClockFrameSequencer();
}
_state.Divider = newValue;
}
uint8_t GbTimer::Read(uint16_t addr)
{
switch(addr) {
case 0xFF04: return _state.Divider >> 8;
case 0xFF05: return _state.Counter; //FF05 - TIMA - Timer counter (R/W)
case 0xFF06: return _state.Modulo; //FF06 - TMA - Timer Modulo (R/W)
case 0xFF07: return _state.Control | 0xF8; //FF07 - TAC - Timer Control (R/W)
}
return 0;
}
void GbTimer::Write(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0xFF04:
SetDivider(0);
break;
case 0xFF05:
//FF05 - TIMA - Timer counter (R/W)
if(_state.NeedReload) {
//Writing to TIMA when a reload is pending will cancel the reload and IRQ request
_state.NeedReload = false;
}
if(!_state.Reloaded) {
//Writes to TIMA on the cycle TIMA was reloaded with TMA are ignored
_state.Counter = value;
}
break;
case 0xFF06:
//FF06 - TMA - Timer Modulo (R/W)
_state.Modulo = value;
if(_state.Reloaded) {
//Writing to TMA on the same cycle it was reloaded into TIMA will also update TIMA
_state.Counter = value;
}
break;
case 0xFF07: {
//FF07 - TAC - Timer Control (R/W)
_state.Control = value;
bool enabled = (value & 0x04) != 0;
uint16_t newDivider = 0;
switch(value & 0x03) {
case 0: newDivider = 1 << 9; break;
case 1: newDivider = 1 << 3; break;
case 2: newDivider = 1 << 5; break;
case 3: newDivider = 1 << 7; break;
}
if(_state.TimerEnabled) {
//When changing the value of TAC, the TIMA register can get incremented due to a glitch
bool incrementCounter;
if(enabled) {
incrementCounter = (_state.Divider & _state.TimerDivider) != 0 && (_state.Divider & newDivider) == 0;
} else {
incrementCounter = (_state.Divider & _state.TimerDivider) != 0;
}
if(incrementCounter) {
_state.Counter++;
if(_state.Counter == 0) {
ReloadCounter();
}
}
}
_state.TimerEnabled = enabled;
_state.TimerDivider = newDivider;
break;
}
}
}
void GbTimer::Serialize(Serializer& s)
{
s.Stream(_state.Divider, _state.Counter, _state.Modulo, _state.Control, _state.TimerEnabled, _state.TimerDivider, _state.NeedReload, _state.Reloaded);
}