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