/* Copyright (c) 2013-2016 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #define TIMER_MASTER 1024 static void GBATimerUpdate(struct GBA* gba, int timerId, uint32_t cyclesLate) { struct GBATimer* timer = &gba->timers[timerId]; gba->memory.io[(REG_TM0CNT_LO >> 1) + (timerId << 1)] = timer->reload; GBATimerUpdateRegister(gba, timerId, cyclesLate); if (GBATimerFlagsIsDoIrq(timer->flags)) { GBARaiseIRQ(gba, IRQ_TIMER0 + timerId); } if (gba->audio.enable && timerId < 2) { if ((gba->audio.chALeft || gba->audio.chARight) && gba->audio.chATimer == timerId) { GBAAudioSampleFIFO(&gba->audio, 0, cyclesLate); } if ((gba->audio.chBLeft || gba->audio.chBRight) && gba->audio.chBTimer == timerId) { GBAAudioSampleFIFO(&gba->audio, 1, cyclesLate); } } if (timerId < 4) { struct GBATimer* nextTimer = &gba->timers[timerId + 1]; if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled? ++gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)]; if (!gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)] && GBATimerFlagsIsEnable(nextTimer->flags)) { GBATimerUpdate(gba, timerId + 1, cyclesLate); } } } } static void GBATimerMasterUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBA* gba = context; mTimingSchedule(timing, &gba->timerMaster, TIMER_MASTER - cyclesLate); GBATimerUpdateRegister(gba, 0, cyclesLate); GBATimerUpdateRegister(gba, 1, cyclesLate); GBATimerUpdateRegister(gba, 2, cyclesLate); GBATimerUpdateRegister(gba, 3, cyclesLate); } static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(timing); GBATimerUpdate(context, 0, cyclesLate); } static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(timing); GBATimerUpdate(context, 1, cyclesLate); } static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(timing); GBATimerUpdate(context, 2, cyclesLate); } static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(timing); GBATimerUpdate(context, 3, cyclesLate); } void GBATimerInit(struct GBA* gba) { gba->timerMaster.name = "GBA Timer Master"; gba->timerMaster.callback = GBATimerMasterUpdate; gba->timerMaster.context = gba; gba->timerMaster.priority = 0x20; mTimingSchedule(&gba->timing, &gba->timerMaster, TIMER_MASTER); memset(gba->timers, 0, sizeof(gba->timers)); gba->timers[0].event.name = "GBA Timer 0"; gba->timers[0].event.callback = GBATimerUpdate0; gba->timers[0].event.context = gba; gba->timers[0].event.priority = 0x21; gba->timers[1].event.name = "GBA Timer 1"; gba->timers[1].event.callback = GBATimerUpdate1; gba->timers[1].event.context = gba; gba->timers[1].event.priority = 0x22; gba->timers[2].event.name = "GBA Timer 2"; gba->timers[2].event.callback = GBATimerUpdate2; gba->timers[2].event.context = gba; gba->timers[2].event.priority = 0x23; gba->timers[3].event.name = "GBA Timer 3"; gba->timers[3].event.callback = GBATimerUpdate3; gba->timers[3].event.context = gba; gba->timers[3].event.priority = 0x24; } void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate) { struct GBATimer* currentTimer = &gba->timers[timer]; if (!GBATimerFlagsIsEnable(currentTimer->flags) || GBATimerFlagsIsCountUp(currentTimer->flags)) { return; } if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) { cyclesLate -= ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB; } int prescaleBits = GBATimerFlagsGetPrescaleBits(currentTimer->flags); int32_t currentTime = mTimingCurrentTime(&gba->timing) - cyclesLate; int32_t tickMask = (1 << prescaleBits) - 1; currentTime &= ~tickMask; int32_t tickIncrement = currentTime - currentTimer->lastEvent; currentTimer->lastEvent = currentTime; tickIncrement >>= prescaleBits; tickIncrement += gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1]; gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = tickIncrement; tickIncrement -= 0x10000; mTimingDeschedule(&gba->timing, ¤tTimer->event); if (tickIncrement >= 0) { mTimingSchedule(&gba->timing, ¤tTimer->event, 7 - (tickIncrement << prescaleBits)); } else { int32_t nextIncrement = mTimingUntil(&gba->timing, &gba->timerMaster); tickIncrement = -tickIncrement; tickIncrement <<= prescaleBits; if (nextIncrement - tickIncrement > 0) { mTimingSchedule(&gba->timing, ¤tTimer->event, 7 + tickIncrement); } } } void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) { gba->timers[timer].reload = reload; } void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) { struct GBATimer* currentTimer = &gba->timers[timer]; GBATimerUpdateRegister(gba, timer, 0); unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags); unsigned prescaleBits; switch (control & 0x0003) { case 0x0000: prescaleBits = 0; break; case 0x0001: prescaleBits = 6; break; case 0x0002: prescaleBits = 8; break; case 0x0003: prescaleBits = 10; break; } currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, prescaleBits); currentTimer->flags = GBATimerFlagsTestFillCountUp(currentTimer->flags, timer > 0 && (control & 0x0004)); currentTimer->flags = GBATimerFlagsTestFillDoIrq(currentTimer->flags, control & 0x0040); bool wasEnabled = GBATimerFlagsIsEnable(currentTimer->flags); currentTimer->flags = GBATimerFlagsTestFillEnable(currentTimer->flags, control & 0x0080); if (!wasEnabled && GBATimerFlagsIsEnable(currentTimer->flags)) { mTimingDeschedule(&gba->timing, ¤tTimer->event); gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload; int32_t tickMask = (1 << prescaleBits) - 1; currentTimer->lastEvent = (mTimingCurrentTime(&gba->timing)) & ~tickMask; GBATimerUpdateRegister(gba, timer, 0); } else if (wasEnabled && !GBATimerFlagsIsEnable(currentTimer->flags)) { mTimingDeschedule(&gba->timing, ¤tTimer->event); } else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) { mTimingDeschedule(&gba->timing, ¤tTimer->event); int32_t tickMask = (1 << prescaleBits) - 1; currentTimer->lastEvent = (mTimingCurrentTime(&gba->timing)) & ~tickMask; GBATimerUpdateRegister(gba, timer, 0); } }