src/gba/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/gba/timer.h>
7
8#include <mgba/internal/gba/gba.h>
9#include <mgba/internal/gba/io.h>
10
11static void GBATimerUpdate(struct mTiming* timing, struct GBA* gba, int timerId, uint32_t cyclesLate) {
12 struct GBATimer* timer = &gba->timers[timerId];
13 gba->memory.io[(REG_TM0CNT_LO >> 1) + (timerId << 1)] = timer->reload;
14 timer->oldReload = timer->reload;
15 timer->lastEvent = timing->masterCycles - cyclesLate;
16
17 if (GBATimerFlagsIsDoIrq(timer->flags)) {
18 GBARaiseIRQ(gba, IRQ_TIMER0 + timerId);
19 }
20
21 if (gba->audio.enable && timerId < 2) {
22 if ((gba->audio.chALeft || gba->audio.chARight) && gba->audio.chATimer == timerId) {
23 GBAAudioSampleFIFO(&gba->audio, 0, cyclesLate);
24 }
25
26 if ((gba->audio.chBLeft || gba->audio.chBRight) && gba->audio.chBTimer == timerId) {
27 GBAAudioSampleFIFO(&gba->audio, 1, cyclesLate);
28 }
29 }
30
31 if (timerId < 4) {
32 struct GBATimer* nextTimer = &gba->timers[timerId + 1];
33 if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled?
34 ++gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)];
35 if (!gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)] && GBATimerFlagsIsEnable(nextTimer->flags)) {
36 mTimingSchedule(timing, &nextTimer->event, -cyclesLate);
37 }
38 }
39 }
40
41 if (!GBATimerFlagsIsCountUp(timer->flags)) {
42 uint32_t nextEvent = timer->overflowInterval - cyclesLate;
43 mTimingSchedule(timing, &timer->event, nextEvent);
44 }
45}
46
47static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
48 GBATimerUpdate(timing, context, 0, cyclesLate);
49}
50
51static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
52 GBATimerUpdate(timing, context, 1, cyclesLate);
53}
54
55static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
56 GBATimerUpdate(timing, context, 2, cyclesLate);
57}
58
59static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
60 GBATimerUpdate(timing, context, 3, cyclesLate);
61}
62
63void GBATimerInit(struct GBA* gba) {
64 memset(gba->timers, 0, sizeof(gba->timers));
65 gba->timers[0].event.name = "GBA Timer 0";
66 gba->timers[0].event.callback = GBATimerUpdate0;
67 gba->timers[0].event.context = gba;
68 gba->timers[0].event.priority = 0x20;
69 gba->timers[1].event.name = "GBA Timer 1";
70 gba->timers[1].event.callback = GBATimerUpdate1;
71 gba->timers[1].event.context = gba;
72 gba->timers[1].event.priority = 0x21;
73 gba->timers[2].event.name = "GBA Timer 2";
74 gba->timers[2].event.callback = GBATimerUpdate2;
75 gba->timers[2].event.context = gba;
76 gba->timers[2].event.priority = 0x22;
77 gba->timers[3].event.name = "GBA Timer 3";
78 gba->timers[3].event.callback = GBATimerUpdate3;
79 gba->timers[3].event.context = gba;
80 gba->timers[3].event.priority = 0x23;
81}
82
83void GBATimerUpdateRegister(struct GBA* gba, int timer) {
84 struct GBATimer* currentTimer = &gba->timers[timer];
85 if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
86 int32_t prefetchSkew = -2;
87 if (gba->memory.lastPrefetchedPc > (uint32_t) gba->cpu->gprs[ARM_PC]) {
88 prefetchSkew += ((gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * gba->cpu->memory.activeSeqCycles16) / WORD_SIZE_THUMB;
89 }
90 // Reading this takes two cycles (1N+1I), so let's remove them preemptively
91 int32_t diff = gba->cpu->cycles - (currentTimer->lastEvent - gba->timing.masterCycles);
92 gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((diff + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags));
93 }
94}
95
96void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) {
97 gba->timers[timer].reload = reload;
98 gba->timers[timer].overflowInterval = (0x10000 - gba->timers[timer].reload) << GBATimerFlagsGetPrescaleBits(gba->timers[timer].flags);
99}
100
101void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
102 struct GBATimer* currentTimer = &gba->timers[timer];
103 GBATimerUpdateRegister(gba, timer);
104
105 unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags);
106 switch (control & 0x0003) {
107 case 0x0000:
108 currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 0);
109 break;
110 case 0x0001:
111 currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 6);
112 break;
113 case 0x0002:
114 currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 8);
115 break;
116 case 0x0003:
117 currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 10);
118 break;
119 }
120 currentTimer->flags = GBATimerFlagsTestFillCountUp(currentTimer->flags, timer > 0 && (control & 0x0004));
121 currentTimer->flags = GBATimerFlagsTestFillDoIrq(currentTimer->flags, control & 0x0040);
122 currentTimer->overflowInterval = (0x10000 - currentTimer->reload) << GBATimerFlagsGetPrescaleBits(currentTimer->flags);
123 bool wasEnabled = GBATimerFlagsIsEnable(currentTimer->flags);
124 currentTimer->flags = GBATimerFlagsTestFillEnable(currentTimer->flags, control & 0x0080);
125 if (!wasEnabled && GBATimerFlagsIsEnable(currentTimer->flags)) {
126 mTimingDeschedule(&gba->timing, ¤tTimer->event);
127 if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
128 mTimingSchedule(&gba->timing, ¤tTimer->event, currentTimer->overflowInterval);
129 }
130 gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload;
131 currentTimer->oldReload = currentTimer->reload;
132 currentTimer->lastEvent = gba->timing.masterCycles + gba->cpu->cycles;
133 } else if (wasEnabled && !GBATimerFlagsIsEnable(currentTimer->flags)) {
134 mTimingDeschedule(&gba->timing, ¤tTimer->event);
135 } else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
136 mTimingDeschedule(&gba->timing, ¤tTimer->event);
137 mTimingSchedule(&gba->timing, ¤tTimer->event, currentTimer->overflowInterval - currentTimer->lastEvent);
138 }
139}