Source

gb_emulator / gb_emulator / src / gb_timers.cpp

Full commit
/*  Copyright Š 2011 Chris Spencer <spencercw@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include <time.h>

#include <algorithm>

#ifdef _MSC_VER
#pragma warning(push, 0)
#endif

#include "gb.pb.h"

#ifdef _MSC_VER
#pragma warning(pop)
#endif

#include <gb_emulator/gb.hpp>
#include <gb_emulator/gb_memory.hpp>
#include <gb_emulator/gb_timers.hpp>

using std::min;

GbTimers::GbTimers(Gb &gb):
gb_(gb)
{
	reset();
}

void GbTimers::reset()
{
	rtc_ = 44236800;  // 512 days. This will set the overflow flag.
	dividerCountdown_ = 0;
	timerCountdown_ = 0;
	rtcCountdown_ = 0;
}

int GbTimers::poll()
{
	// Increment the divider register
	if (dividerCountdown_ <= 0)
	{
		++gb_.mem_->ioPorts()[DIV];
		dividerCountdown_ += DIVIDER_CYCLES;
	}

	// Increment the timer counter
	bool timerActive = (gb_.mem_->ioPorts()[TAC] & 0x04) != 0;
	if (timerActive && timerCountdown_ <= 0)
	{
		++gb_.mem_->ioPorts()[TIMA];
		if (!gb_.mem_->ioPorts()[TIMA])
		{
			gb_.mem_->ioPorts()[TIMA] = gb_.mem_->ioPorts()[TMA];
			gb_.mem_->ioPorts()[IF] |= TIMER_INTR;
		}

		switch (gb_.mem_->ioPorts()[TAC] & 0x03)
		{
		case 0:
			timerCountdown_ += TIMER0_CYCLES;
			break;
		case 1:
			timerCountdown_ += TIMER1_CYCLES;
			break;
		case 2:
			timerCountdown_ += TIMER2_CYCLES;
			break;
		case 3:
			timerCountdown_ += TIMER3_CYCLES;
			break;
		}
	}
	else if (!timerActive)
	{
		timerCountdown_ = 0;
	}

	// Increment the real-time clock
	bool rtcActive = gb_.mem_->cart() == MBC3_TIMER_BAT || gb_.mem_->cart() == MBC3_TIMER_RAM_BAT;
	if (rtcActive && rtcCountdown_ <= 0)
	{
		// Don't do anything if the clock is halted
		if (!(gb_.mem_->rtc()[RTC_DH] & 0x40))
		{
			++rtc_;
		}
		rtcCountdown_ += RTC_CYCLES;
	}

	// Get the number of cycles until the next tick
	int cycles = dividerCountdown_;
	if (timerActive)
	{
		cycles = (min)(cycles, timerCountdown_);
	}
	if (rtcActive)
	{
		cycles = (min)(cycles, rtcCountdown_);
	}

	dividerCountdown_ -= cycles;
	if (timerActive)
	{
		timerCountdown_ -= cycles;
	}
	if (rtcActive)
	{
		rtcCountdown_ -= cycles;
	}

	return cycles;
}

void GbTimers::rtcWrite(const uint8_t registers[5])
{
	uint16_t days = ((registers[RTC_DH] & 0x01) << 8) | registers[RTC_DL];
	rtc_ =
		days * 86400 +
		registers[RTC_H] * 3600 +
		registers[RTC_M] * 60 +
		registers[RTC_S];
}

void GbTimers::save(GbTimerData &data) const
{
	data.set_real_time_clock(rtc_);
	data.set_wall_clock(time(NULL));
	data.set_divider_countdown(dividerCountdown_);
	data.set_timer_countdown(timerCountdown_);
	data.set_rtc_countdown(rtcCountdown_);
}

void GbTimers::load(const GbTimerData &data)
{
	rtc_ = data.real_time_clock();

	// Update the real-time clock with the time that has elapsed since the data was saved
	int64_t oldWallClock = data.wall_clock();
	int64_t wallClock = time(NULL);
	if (wallClock > oldWallClock)
	{
		rtc_ += wallClock - oldWallClock;
	}

	dividerCountdown_ = data.divider_countdown();
	timerCountdown_ = data.timer_countdown();
	rtcCountdown_ = data.rtc_countdown();
}