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, ¤tTimer->event);
113 mTimingScheduleAbsolute(&gba->timing, ¤tTimer->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, ¤tTimer->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, ¤tTimer->event);
153 } else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
154 mTimingDeschedule(&gba->timing, ¤tTimer->event);
155 int32_t tickMask = (1 << prescaleBits) - 1;
156 currentTimer->lastEvent = mTimingCurrentTime(&gba->timing) & ~tickMask;
157 GBATimerUpdateRegister(gba, timer, 0);
158 }
159}