all repos — mgba @ beba0cb2c59505d0905db8b0efe4a407b54b201c

mGBA Game Boy Advance Emulator

src/gba/timer.c (view raw)

  1/* Copyright (c) 2013-2018 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include <mgba/internal/gba/timer.h>
  7
  8#include <mgba/internal/gba/gba.h>
  9#include <mgba/internal/gba/io.h>
 10
 11#define REG_TMCNT_LO(X) (REG_TM0CNT_LO + ((X) << 2))
 12
 13static void GBATimerUpdate(struct GBA* gba, int timerId, uint32_t cyclesLate) {
 14	struct GBATimer* timer = &gba->timers[timerId];
 15	if (GBATimerFlagsIsCountUp(timer->flags)) {
 16		gba->memory.io[REG_TMCNT_LO(timerId) >> 1] = timer->reload;
 17	} else {
 18		GBATimerUpdateRegister(gba, timerId, cyclesLate);
 19	}
 20
 21	if (GBATimerFlagsIsDoIrq(timer->flags)) {
 22		GBARaiseIRQ(gba, IRQ_TIMER0 + timerId, cyclesLate);
 23	}
 24
 25	if (gba->audio.enable && timerId < 2) {
 26		if ((gba->audio.chALeft || gba->audio.chARight) && gba->audio.chATimer == timerId) {
 27			GBAAudioSampleFIFO(&gba->audio, 0, cyclesLate);
 28		}
 29
 30		if ((gba->audio.chBLeft || gba->audio.chBRight) && gba->audio.chBTimer == timerId) {
 31			GBAAudioSampleFIFO(&gba->audio, 1, cyclesLate);
 32		}
 33	}
 34
 35	if (timerId < 3) {
 36		struct GBATimer* nextTimer = &gba->timers[timerId + 1];
 37		if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled?
 38			++gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1];
 39			if (!gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1] && GBATimerFlagsIsEnable(nextTimer->flags)) {
 40				GBATimerUpdate(gba, timerId + 1, cyclesLate);
 41			}
 42		}
 43	}
 44}
 45
 46static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 47	UNUSED(timing);
 48	GBATimerUpdate(context, 0, cyclesLate);
 49}
 50
 51static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 52	UNUSED(timing);
 53	GBATimerUpdate(context, 1, cyclesLate);
 54}
 55
 56static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 57	UNUSED(timing);
 58	GBATimerUpdate(context, 2, cyclesLate);
 59}
 60
 61static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 62	UNUSED(timing);
 63	GBATimerUpdate(context, 3, cyclesLate);
 64}
 65
 66void GBATimerInit(struct GBA* gba) {
 67	memset(gba->timers, 0, sizeof(gba->timers));
 68	gba->timers[0].event.name = "GBA Timer 0";
 69	gba->timers[0].event.callback = GBATimerUpdate0;
 70	gba->timers[0].event.context = gba;
 71	gba->timers[0].event.priority = 0x20;
 72	gba->timers[1].event.name = "GBA Timer 1";
 73	gba->timers[1].event.callback = GBATimerUpdate1;
 74	gba->timers[1].event.context = gba;
 75	gba->timers[1].event.priority = 0x21;
 76	gba->timers[2].event.name = "GBA Timer 2";
 77	gba->timers[2].event.callback = GBATimerUpdate2;
 78	gba->timers[2].event.context = gba;
 79	gba->timers[2].event.priority = 0x22;
 80	gba->timers[3].event.name = "GBA Timer 3";
 81	gba->timers[3].event.callback = GBATimerUpdate3;
 82	gba->timers[3].event.context = gba;
 83	gba->timers[3].event.priority = 0x23;
 84}
 85
 86void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate) {
 87	struct GBATimer* currentTimer = &gba->timers[timer];
 88	if (!GBATimerFlagsIsEnable(currentTimer->flags) || GBATimerFlagsIsCountUp(currentTimer->flags)) {
 89		return;
 90	}
 91
 92	// Align timer
 93	int prescaleBits = GBATimerFlagsGetPrescaleBits(currentTimer->flags);
 94	int32_t currentTime = mTimingCurrentTime(&gba->timing) - cyclesLate;
 95	int32_t tickMask = (1 << prescaleBits) - 1;
 96	currentTime &= ~tickMask;
 97
 98	// Update register
 99	int32_t tickIncrement = currentTime - currentTimer->lastEvent;
100	currentTimer->lastEvent = currentTime;
101	tickIncrement >>= prescaleBits;
102	tickIncrement += gba->memory.io[REG_TMCNT_LO(timer) >> 1];
103	while (tickIncrement >= 0x10000) {
104		tickIncrement -= 0x10000 - currentTimer->reload;
105	}
106	gba->memory.io[REG_TMCNT_LO(timer) >> 1] = tickIncrement;
107
108	// Schedule next update
109	tickIncrement = (0x10000 - tickIncrement) << prescaleBits;
110	currentTime += tickIncrement;
111	currentTime &= ~tickMask;
112	mTimingDeschedule(&gba->timing, &currentTimer->event);
113	mTimingScheduleAbsolute(&gba->timing, &currentTimer->event, currentTime);
114}
115
116void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) {
117	gba->timers[timer].reload = reload;
118}
119
120void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
121	struct GBATimer* currentTimer = &gba->timers[timer];
122	GBATimerUpdateRegister(gba, timer, 0);
123
124	unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags);
125	unsigned prescaleBits;
126	switch (control & 0x0003) {
127	case 0x0000:
128		prescaleBits = 0;
129		break;
130	case 0x0001:
131		prescaleBits = 6;
132		break;
133	case 0x0002:
134		prescaleBits = 8;
135		break;
136	case 0x0003:
137		prescaleBits = 10;
138		break;
139	}
140	currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, prescaleBits);
141	currentTimer->flags = GBATimerFlagsTestFillCountUp(currentTimer->flags, timer > 0 && (control & 0x0004));
142	currentTimer->flags = GBATimerFlagsTestFillDoIrq(currentTimer->flags, control & 0x0040);
143	bool wasEnabled = GBATimerFlagsIsEnable(currentTimer->flags);
144	currentTimer->flags = GBATimerFlagsTestFillEnable(currentTimer->flags, control & 0x0080);
145	if (!wasEnabled && GBATimerFlagsIsEnable(currentTimer->flags)) {
146		mTimingDeschedule(&gba->timing, &currentTimer->event);
147		gba->memory.io[REG_TMCNT_LO(timer) >> 1] = currentTimer->reload;
148		int32_t tickMask = (1 << prescaleBits) - 1;
149		currentTimer->lastEvent = mTimingCurrentTime(&gba->timing) & ~tickMask;
150		GBATimerUpdateRegister(gba, timer, 0);
151	} else if (wasEnabled && !GBATimerFlagsIsEnable(currentTimer->flags)) {
152		mTimingDeschedule(&gba->timing, &currentTimer->event);
153	} else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
154		mTimingDeschedule(&gba->timing, &currentTimer->event);
155		int32_t tickMask = (1 << prescaleBits) - 1;
156		currentTimer->lastEvent = mTimingCurrentTime(&gba->timing) & ~tickMask;
157		GBATimerUpdateRegister(gba, timer, 0);
158	}
159}