2020-05-18 16:10:53 -04:00
|
|
|
#include "stdafx.h"
|
|
|
|
#include "GbTimer.h"
|
|
|
|
#include "GbTypes.h"
|
|
|
|
#include "GbMemoryManager.h"
|
|
|
|
#include "GbApu.h"
|
|
|
|
|
|
|
|
GbTimer::GbTimer(GbMemoryManager* memoryManager, GbApu* apu)
|
|
|
|
{
|
|
|
|
_apu = apu;
|
|
|
|
_memoryManager = memoryManager;
|
2020-05-23 00:45:22 -04:00
|
|
|
|
|
|
|
//Passes boot_div-dmgABCmgb
|
|
|
|
//But that test depends on LCD power on timings, so may be wrong.
|
|
|
|
_divider = 0x06;
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
GbTimer::~GbTimer()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbTimer::Exec()
|
|
|
|
{
|
2020-05-23 15:41:30 -04:00
|
|
|
_reloaded = false;
|
|
|
|
if(_needReload) {
|
|
|
|
ReloadCounter();
|
|
|
|
}
|
|
|
|
SetDivider(_divider + 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbTimer::ReloadCounter()
|
|
|
|
{
|
|
|
|
_counter = _modulo;
|
|
|
|
_memoryManager->RequestIrq(GbIrqSource::Timer);
|
|
|
|
_needReload = false;
|
|
|
|
_reloaded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbTimer::SetDivider(uint16_t newValue)
|
|
|
|
{
|
2020-05-18 16:10:53 -04:00
|
|
|
if(_timerEnabled && !(newValue & _timerDivider) && (_divider & _timerDivider)) {
|
|
|
|
_counter++;
|
|
|
|
if(_counter == 0) {
|
2020-05-23 15:41:30 -04:00
|
|
|
_needReload = true;
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 21:31:33 -04:00
|
|
|
uint16_t frameSeqBit = _memoryManager->IsHighSpeed() ? 0x2000 : 0x1000;
|
|
|
|
if(!(newValue & frameSeqBit) && (_divider & frameSeqBit)) {
|
2020-05-18 16:10:53 -04:00
|
|
|
_apu->ClockFrameSequencer();
|
|
|
|
}
|
|
|
|
|
|
|
|
_divider = newValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t GbTimer::Read(uint16_t addr)
|
|
|
|
{
|
|
|
|
switch(addr) {
|
|
|
|
case 0xFF04: return _divider >> 8;
|
|
|
|
case 0xFF05: return _counter; //FF05 - TIMA - Timer counter (R/W)
|
|
|
|
case 0xFF06: return _modulo; //FF06 - TMA - Timer Modulo (R/W)
|
2020-05-23 11:19:49 -04:00
|
|
|
case 0xFF07: return _control | 0xF8; //FF07 - TAC - Timer Control (R/W)
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbTimer::Write(uint16_t addr, uint8_t value)
|
|
|
|
{
|
|
|
|
//TODO properly detect edges when setting new values to registers or disabling timer, etc.
|
|
|
|
switch(addr) {
|
|
|
|
case 0xFF04:
|
2020-05-23 15:41:30 -04:00
|
|
|
SetDivider(0);
|
2020-05-18 16:10:53 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xFF05:
|
|
|
|
//FF05 - TIMA - Timer counter (R/W)
|
2020-05-23 15:41:30 -04:00
|
|
|
if(_needReload) {
|
|
|
|
//Writing to TIMA when a reload is pending will cancel the reload and IRQ request
|
|
|
|
_needReload = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!_reloaded) {
|
|
|
|
//Writes to TIMA on the cycle TIMA was reloaded with TMA are ignored
|
|
|
|
_counter = value;
|
|
|
|
}
|
2020-05-18 16:10:53 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xFF06:
|
|
|
|
//FF06 - TMA - Timer Modulo (R/W)
|
|
|
|
_modulo = value;
|
2020-05-23 15:41:30 -04:00
|
|
|
if(_reloaded) {
|
|
|
|
//Writing to TMA on the same cycle it was reloaded into TIMA will also update TIMA
|
|
|
|
_counter = value;
|
|
|
|
}
|
2020-05-18 16:10:53 -04:00
|
|
|
break;
|
|
|
|
|
2020-05-23 15:41:30 -04:00
|
|
|
case 0xFF07: {
|
2020-05-18 16:10:53 -04:00
|
|
|
//FF07 - TAC - Timer Control (R/W)
|
|
|
|
_control = value;
|
2020-05-23 15:41:30 -04:00
|
|
|
bool enabled = (value & 0x04) != 0;
|
|
|
|
uint16_t newDivider = 0;
|
2020-05-18 16:10:53 -04:00
|
|
|
switch(value & 0x03) {
|
2020-05-23 15:41:30 -04:00
|
|
|
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(_timerEnabled) {
|
|
|
|
//When changing the value of TAC, the TIMA register can get incremented due to a glitch
|
|
|
|
bool incrementCounter;
|
|
|
|
if(enabled) {
|
|
|
|
incrementCounter = (_divider & _timerDivider) != 0 && (_divider & newDivider) == 0;
|
|
|
|
} else {
|
|
|
|
incrementCounter = (_divider & _timerDivider) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(incrementCounter) {
|
|
|
|
_counter++;
|
|
|
|
if(_counter == 0) {
|
|
|
|
ReloadCounter();
|
|
|
|
}
|
|
|
|
}
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|
2020-05-23 15:41:30 -04:00
|
|
|
|
|
|
|
_timerEnabled = enabled;
|
|
|
|
_timerDivider = newDivider;
|
2020-05-18 16:10:53 -04:00
|
|
|
break;
|
2020-05-23 15:41:30 -04:00
|
|
|
}
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbTimer::Serialize(Serializer& s)
|
|
|
|
{
|
2020-05-24 22:04:47 -04:00
|
|
|
s.Stream(_divider, _counter, _modulo, _control, _timerEnabled, _timerDivider, _needReload, _reloaded);
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|