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/gb/gb.h>
9#include <mgba/internal/gb/io.h>
10#include <mgba/internal/gb/serialize.h>
11
12void _GBTimerIRQ(struct mTiming* timing, void* context, uint32_t cyclesLate) {
13 UNUSED(timing);
14 UNUSED(cyclesLate);
15 struct GBTimer* timer = context;
16 timer->p->memory.io[REG_TIMA] = timer->p->memory.io[REG_TMA];
17 timer->p->memory.io[REG_IF] |= (1 << GB_IRQ_TIMER);
18 GBUpdateIRQs(timer->p);
19}
20
21void _GBTimerIncrement(struct mTiming* timing, void* context, uint32_t cyclesLate) {
22 struct GBTimer* timer = context;
23 timer->nextDiv += cyclesLate;
24 while (timer->nextDiv > 0) {
25 timer->nextDiv -= GB_DMG_DIV_PERIOD;
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[REG_TIMA];
30 if (!timer->p->memory.io[REG_TIMA]) {
31 mTimingSchedule(timing, &timer->irq, 4 - cyclesLate);
32 }
33 }
34 ++timer->internalDiv;
35 timer->p->memory.io[REG_DIV] = timer->internalDiv >> 4;
36 }
37 // Batch div increments
38 int divsToGo = 16 - (timer->internalDiv & 15);
39 int timaToGo = INT_MAX;
40 if (timer->timaPeriod) {
41 timaToGo = timer->timaPeriod - (timer->internalDiv & (timer->timaPeriod - 1));
42 }
43 if (timaToGo < divsToGo) {
44 divsToGo = timaToGo;
45 }
46 timer->nextDiv = GB_DMG_DIV_PERIOD * divsToGo;
47 mTimingSchedule(timing, &timer->event, timer->nextDiv - cyclesLate);
48}
49
50void GBTimerReset(struct GBTimer* timer) {
51 timer->event.context = timer;
52 timer->event.name = "GB Timer";
53 timer->event.callback = _GBTimerIncrement;
54 timer->event.priority = 0x20;
55 timer->irq.context = timer;
56 timer->irq.name = "GB Timer IRQ";
57 timer->irq.callback = _GBTimerIRQ;
58 timer->event.priority = 0x21;
59
60 timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences
61 timer->timaPeriod = 1024 >> 4;
62 timer->internalDiv = 0;
63}
64
65void GBTimerDivReset(struct GBTimer* timer) {
66 timer->p->memory.io[REG_DIV] = 0;
67 timer->internalDiv = 0;
68 timer->nextDiv = GB_DMG_DIV_PERIOD;
69 mTimingDeschedule(&timer->p->timing, &timer->event);
70 mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv);
71}
72
73uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) {
74 if (GBRegisterTACIsRun(tac)) {
75 switch (GBRegisterTACGetClock(tac)) {
76 case 0:
77 timer->timaPeriod = 1024 >> 4;
78 break;
79 case 1:
80 timer->timaPeriod = 16 >> 4;
81 break;
82 case 2:
83 timer->timaPeriod = 64 >> 4;
84 break;
85 case 3:
86 timer->timaPeriod = 256 >> 4;
87 break;
88 }
89 } else {
90 timer->timaPeriod = 0;
91 }
92 return tac;
93}
94
95void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state) {
96 STORE_32LE(timer->nextDiv, 0, &state->timer.nextDiv);
97 STORE_32LE(timer->internalDiv, 0, &state->timer.internalDiv);
98 STORE_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod);
99 STORE_32LE(timer->event.when - mTimingCurrentTime(&timer->p->timing), 0, &state->timer.nextEvent);
100 STORE_32LE(timer->irq.when - mTimingCurrentTime(&timer->p->timing), 0, &state->timer.nextIRQ);
101 GBSerializedTimerFlags flags = GBSerializedTimerFlagsSetIrqPending(0, mTimingIsScheduled(&timer->p->timing, &timer->irq));
102 STORE_32LE(flags, 0, &state->timer.flags);
103}
104
105void GBTimerDeserialize(struct GBTimer* timer, const struct GBSerializedState* state) {
106 LOAD_32LE(timer->nextDiv, 0, &state->timer.nextDiv);
107 LOAD_32LE(timer->internalDiv, 0, &state->timer.internalDiv);
108 LOAD_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod);
109
110 uint32_t when;
111 LOAD_32LE(when, 0, &state->timer.nextEvent);
112 mTimingDeschedule(&timer->p->timing, &timer->event);
113 mTimingSchedule(&timer->p->timing, &timer->event, when);
114
115 GBSerializedTimerFlags flags;
116 LOAD_32LE(flags, 0, &state->timer.flags);
117
118 mTimingDeschedule(&timer->p->timing, &timer->irq);
119 if (GBSerializedTimerFlagsIsIrqPending(flags)) {
120 LOAD_32LE(when, 0, &state->timer.nextIRQ);
121 mTimingSchedule(&timer->p->timing, &timer->irq, when);
122 }
123}