all repos — mgba @ 7a3f2b12b5045fa67354c657d9c11b46a55fe173

mGBA Game Boy Advance Emulator

src/gb/timer.c (view raw)

  1/* Copyright (c) 2013-2016 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/gb/timer.h>
  7
  8#include <mgba/internal/sm83/sm83.h>
  9#include <mgba/internal/gb/gb.h>
 10#include <mgba/internal/gb/io.h>
 11#include <mgba/internal/gb/serialize.h>
 12
 13void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 14	UNUSED(timing);
 15	UNUSED(cyclesLate);
 16	struct GBTimer* timer = context;
 17	timer->p->memory.io[GB_REG_TIMA] = timer->p->memory.io[GB_REG_TMA];
 18	timer->p->memory.io[GB_REG_IF] |= (1 << GB_IRQ_TIMER);
 19	GBUpdateIRQs(timer->p);
 20}
 21
 22static void _GBTimerDivIncrement(struct GBTimer* timer, uint32_t cyclesLate) {
 23	int tMultiplier = 2 - timer->p->doubleSpeed;
 24	while (timer->nextDiv >= GB_DMG_DIV_PERIOD * tMultiplier) {
 25		timer->nextDiv -= GB_DMG_DIV_PERIOD * tMultiplier;
 26
 27		// Make sure to trigger when the correct bit is a falling edge
 28		if (timer->timaPeriod > 0 && (timer->internalDiv & (timer->timaPeriod - 1)) == timer->timaPeriod - 1) {
 29			++timer->p->memory.io[GB_REG_TIMA];
 30			if (!timer->p->memory.io[GB_REG_TIMA]) {
 31				mTimingSchedule(&timer->p->timing, &timer->irq, 7 * tMultiplier - ((timer->p->cpu->executionState * tMultiplier - cyclesLate) & (3 * tMultiplier)));
 32			}
 33		}
 34		unsigned timingFactor = 0x1FF;
 35		if ((timer->internalDiv & timingFactor) == timingFactor) {
 36			GBAudioUpdateFrame(&timer->p->audio, &timer->p->timing);
 37		}
 38		++timer->internalDiv;
 39		timer->p->memory.io[GB_REG_DIV] = timer->internalDiv >> 4;
 40	}
 41}
 42
 43void _GBTimerUpdate(struct mTiming* timing, void* context, uint32_t cyclesLate) {
 44	struct GBTimer* timer = context;
 45	timer->nextDiv += cyclesLate;
 46	_GBTimerDivIncrement(timer, cyclesLate);
 47	// Batch div increments
 48	int divsToGo = 16 - (timer->internalDiv & 15);
 49	int timaToGo = INT_MAX;
 50	if (timer->timaPeriod) {
 51		timaToGo = timer->timaPeriod - (timer->internalDiv & (timer->timaPeriod - 1));
 52	}
 53	if (timaToGo < divsToGo) {
 54		divsToGo = timaToGo;
 55	}
 56	timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo * (2 - timer->p->doubleSpeed);
 57	mTimingSchedule(timing, &timer->event, timer->nextDiv - cyclesLate);
 58}
 59
 60void GBTimerReset(struct GBTimer* timer) {
 61	timer->event.context = timer;
 62	timer->event.name = "GB Timer";
 63	timer->event.callback = _GBTimerUpdate;
 64	timer->event.priority = 0x20;
 65	timer->irq.context = timer;
 66	timer->irq.name = "GB Timer IRQ";
 67	timer->irq.callback = _GBTimerIRQ;
 68	timer->event.priority = 0x21;
 69
 70	timer->nextDiv = GB_DMG_DIV_PERIOD * 2;
 71	timer->timaPeriod = 1024 >> 4;
 72}
 73
 74void GBTimerDivReset(struct GBTimer* timer) {
 75	timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event);
 76	mTimingDeschedule(&timer->p->timing, &timer->event);
 77	_GBTimerDivIncrement(timer, 0);
 78	int tMultiplier = 2 - timer->p->doubleSpeed;
 79	if (((timer->internalDiv << 1) | ((timer->nextDiv >> (4 - timer->p->doubleSpeed)) & 1)) & timer->timaPeriod) {
 80		++timer->p->memory.io[GB_REG_TIMA];
 81		if (!timer->p->memory.io[GB_REG_TIMA]) {
 82			mTimingSchedule(&timer->p->timing, &timer->irq, (7 - (timer->p->cpu->executionState & 3)) * tMultiplier);
 83		}
 84	}
 85	if (timer->internalDiv & 0x200) {
 86		GBAudioUpdateFrame(&timer->p->audio, &timer->p->timing);
 87	}
 88	timer->p->memory.io[GB_REG_DIV] = 0;
 89	timer->internalDiv = 0;
 90	timer->nextDiv = GB_DMG_DIV_PERIOD * (2 - timer->p->doubleSpeed);
 91	mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv - ((timer->p->cpu->executionState + 1) & 3) * tMultiplier);
 92}
 93
 94uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
 95	if (GBRegisterTACIsRun(tac)) {
 96		timer->nextDiv -= mTimingUntil(&timer->p->timing, &timer->event);
 97		mTimingDeschedule(&timer->p->timing, &timer->event);
 98		_GBTimerDivIncrement(timer, ((timer->p->cpu->executionState + 2) & 3) * (2 - timer->p->doubleSpeed));
 99
100		switch (GBRegisterTACGetClock(tac)) {
101		case 0:
102			timer->timaPeriod = 1024 >> 4;
103			break;
104		case 1:
105			timer->timaPeriod = 16 >> 4;
106			break;
107		case 2:
108			timer->timaPeriod = 64 >> 4;
109			break;
110		case 3:
111			timer->timaPeriod = 256 >> 4;
112			break;
113		}
114
115		timer->nextDiv += GB_DMG_DIV_PERIOD * (2 - timer->p->doubleSpeed);
116		mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv);
117	} else {
118		timer->timaPeriod = 0;
119	}
120	return tac;
121}
122
123void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state) {
124	STORE_32LE(timer->nextDiv, 0, &state->timer.nextDiv);
125	STORE_32LE(timer->internalDiv, 0, &state->timer.internalDiv);
126	STORE_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod);
127	STORE_32LE(timer->event.when - mTimingCurrentTime(&timer->p->timing), 0, &state->timer.nextEvent);
128	STORE_32LE(timer->irq.when - mTimingCurrentTime(&timer->p->timing), 0, &state->timer.nextIRQ);
129	GBSerializedTimerFlags flags = GBSerializedTimerFlagsSetIrqPending(0, mTimingIsScheduled(&timer->p->timing, &timer->irq));
130	state->timer.flags = flags;
131}
132
133void GBTimerDeserialize(struct GBTimer* timer, const struct GBSerializedState* state) {
134	LOAD_32LE(timer->nextDiv, 0, &state->timer.nextDiv);
135	LOAD_32LE(timer->internalDiv, 0, &state->timer.internalDiv);
136	LOAD_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod);
137
138	uint32_t when;
139	LOAD_32LE(when, 0, &state->timer.nextEvent);
140	mTimingSchedule(&timer->p->timing, &timer->event, when);
141
142	GBSerializedTimerFlags flags = state->timer.flags;
143
144	LOAD_32LE(when, 0, &state->timer.nextIRQ);
145	if (GBSerializedTimerFlagsIsIrqPending(flags)) {
146		mTimingSchedule(&timer->p->timing, &timer->irq, when);
147	} else {
148		timer->irq.when = when + mTimingCurrentTime(&timer->p->timing);
149	}
150}