// Meteor - A Nintendo Gameboy Advance emulator1// Copyright (C) 2009-2011 Philippe Daouadi2//3// This program is free software: you can redistribute it and/or modify4// it under the terms of the GNU General Public License as published by5// the Free Software Foundation, either version 3 of the License, or6// (at your option) any later version.7//8// This program is distributed in the hope that it will be useful,9// but WITHOUT ANY WARRANTY; without even the implied warranty of10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11// GNU General Public License for more details.12//13// You should have received a copy of the GNU General Public License14// along with this program. If not, see <http://www.gnu.org/licenses/>.1516#include "ameteor/timer.hpp"17#include "globals.hpp"18#include "ameteor.hpp"1920#include "debug.hpp"2122namespace AMeteor23{24static const uint16_t Prescalers[] = {1, 64, 256, 1024};2526void Timer::Reset ()27{28m_reload = 0;29m_count = 0;30m_control.w = 0;31}3233void Timer::Reload ()34{35// FIXME to test on hardware, vba update the prescaler even if the timer36// didn't restart37// if the timer is at 33, 63 cycles have passed since the last timer38// increment and prescaler is 1/64, setting TM0CNT even to the same value39// it had will reset the prescaler part, so the timer would be at 33 and we40// will have to wait another 64 cycles before having it go to 3441//42// the current behaviour for the above example is that it will reach 34 in43// 1 cycle instead of 6444if (!m_control.b.start &&45(IO.DRead16(Io::TM0CNT_H + m_num * Io::TIMER_SIZE)46& (0x1 << 7)))47// if start has changed from 0 to 148{49m_control.w = IO.DRead16(Io::TM0CNT_H + m_num * Io::TIMER_SIZE);50m_count = 65536 - m_reload;51if (!m_control.b.countup)52{53m_count *= Prescalers[m_control.b.prescaler];5455// here, the str instruction which have triggered this function56// will be taken in account by this timer57// in other words, if the str instruction takes 3 cycles, the58// timer will be incremented by 3 cycles just after its start59CLOCK.SetTimer(m_num, m_count);60}61}62else63{64uint16_t cnt = IO.DRead16(Io::TM0CNT_H + m_num * Io::TIMER_SIZE);65if (m_control.b.start && (cnt & (0x1 << 7))66&& m_control.b.prescaler != (cnt & 0x3))67met_abort("Prescaler changed while timer " << (int)m_num << " was up");6869m_control.w = IO.DRead16(Io::TM0CNT_H + m_num * Io::TIMER_SIZE);7071if (!m_control.b.start)72CLOCK.DisableTimer(m_num);73}7475if (m_num == 0 && m_control.b.countup)76met_abort("Count-up on first timer !");77}7879uint16_t Timer::GetCount () const80{81if (m_control.b.countup)82return 65536 - m_count;83else84return 65536 -85CLOCK.GetTimer(m_num) / Prescalers[m_control.b.prescaler];86}8788void Timer::TimeEvent ()89{90//debug("Timer" << (int)m_num << " overflow");91SOUND.TimerOverflow(m_num);9293m_count = 65536 - m_reload;94if (!m_control.b.countup)95{96m_count *= Prescalers[m_control.b.prescaler];9798// GetTimer should be zero or less since this function was called99if (m_count >= (unsigned short)-CLOCK.GetTimer(m_num))100{101m_count += CLOCK.GetTimer(m_num);102103CLOCK.SetTimer(m_num, m_count);104}105else106{107CLOCK.AddTimer(m_num, m_count);108}109}110111if (m_control.b.irq)112CPU.SendInterrupt(0x1 << (3 + m_num));113114if (m_num != 3)115m_next->Countup();116}117118void Timer::Countup ()119{120if (m_control.b.countup)121{122--m_count;123if (m_count == 0)124TimeEvent();125}126}127128bool Timer::SaveState (std::ostream& stream)129{130SS_WRITE_VAR(m_reload);131SS_WRITE_VAR(m_count);132SS_WRITE_VAR(m_control);133134return true;135}136137bool Timer::LoadState (std::istream& stream)138{139SS_READ_VAR(m_reload);140SS_READ_VAR(m_count);141SS_READ_VAR(m_control);142143return true;144}145}146147148