all repos — mgba @ 6158a4fb8d6bc8e7df42504bad7e18d9b5c667a3

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