1 module gbaid.gba.timer;
2 
3 import std.meta : AliasSeq;
4 
5 import gbaid.util;
6 
7 import gbaid.gba.io;
8 import gbaid.gba.interrupt;
9 import gbaid.gba.sound;
10 
11 public class Timers {
12     private InterruptHandler interruptHandler;
13     private SoundChip soundChip;
14     mixin declareFields!(int, true, "reloadValue", 0, 4);
15     mixin declareFields!(int, true, "control", 0, 4);
16     mixin declareFields!(int, true, "subTicks", 0, 4);
17     mixin declareFields!(int, true, "ticks", 0, 4);
18 
19     public this(IoRegisters* ioRegisters, InterruptHandler interruptHandler, SoundChip soundChip) {
20         this.interruptHandler = interruptHandler;
21         this.soundChip = soundChip;
22 
23         foreach (timer; AliasSeq!(0, 1, 2, 3)) {
24             enum address = 0x100 + timer * 4;
25             ioRegisters.mapAddress(address, &ticks!timer, 0xFFFF, 0).preWriteMonitor(&onCountPreWrite!timer);
26             ioRegisters.mapAddress(address, &control!timer, 0xC7, 16).postWriteMonitor(&onControlPostWrite!timer);
27         }
28     }
29 
30     public size_t emulate(size_t cycles) {
31         auto shortCycles = cast(ushort) cycles;
32         auto previousOverflows = updateTimer!0(shortCycles, 0);
33         previousOverflows = updateTimer!1(shortCycles, previousOverflows);
34         previousOverflows = updateTimer!2(shortCycles, previousOverflows);
35         updateTimer!3(shortCycles, previousOverflows);
36         return 0;
37     }
38 
39     private int updateTimer(int timer)(ushort cycles, int previousOverflows) {
40         // Check that the timer is enabled
41         if (!control!timer.checkBit(7)) {
42             return 0;
43         }
44         // Check the ticking condition
45         int newTicks = void;
46         if (control!timer.checkBit(2)) {
47             // Count-up timing: increment if the previous timer overflowed
48             newTicks = previousOverflows;
49         } else {
50             // Update the sub-ticks according to the pre-scaler
51             subTicks!timer += cycles;
52             auto preScalerBase2Power = control!timer.getPreScalerBase2Power();
53             // We tick for each completed sub-tick
54             newTicks = subTicks!timer >> preScalerBase2Power;
55             subTicks!timer &= (1 << preScalerBase2Power) - 1;
56         }
57         // Only tick if we need to
58         if (newTicks <= 0) {
59             return 0;
60         }
61         // Check for an overflow
62         auto ticksUntilOverflow = (ushort.max + 1) - ticks!timer;
63         if (newTicks < ticksUntilOverflow) {
64             // No overflow, just increment the tick counter
65             ticks!timer += newTicks;
66             return 0;
67         }
68         // If we overflow, start by consuming the new ticks to that overflow
69         newTicks -= ticksUntilOverflow;
70         // Reload the value and add any extra ticks past the overflows
71         ticksUntilOverflow = (ushort.max + 1) - reloadValue!timer;
72         ticks!timer = reloadValue!timer + newTicks % ticksUntilOverflow;
73         // Trigger an IRQ on overflow if requested
74         if (control!timer.checkBit(6)) {
75             interruptHandler.requestInterrupt(InterruptSource.TIMER_0_OVERFLOW + timer);
76         }
77         // The count is the first overflow plus any extra
78         auto overflowCount = 1 + newTicks / ticksUntilOverflow;
79         // Pass the overflow count to the sound chip for the direct sound system
80         static if (timer == 0 || timer == 1) {
81             soundChip.addTimerOverflows!timer(overflowCount);
82         }
83         return overflowCount;
84     }
85 
86     private bool onCountPreWrite(int timer)(int mask, ref int reload) {
87         // Update the reload value and cancel the write
88         reloadValue!timer = reloadValue!timer & ~mask | reload & mask;
89         return false;
90     }
91 
92     private void onControlPostWrite(int timer)(int mask, int oldControl, int newControl) {
93         // Reset the timer if the enable bit goes from 0 to 1
94         if (mask.checkBit(7) && !oldControl.checkBit(7) && newControl.checkBit(7)) {
95             subTicks!timer = 0;
96             ticks!timer = reloadValue!timer;
97         }
98     }
99 }
100 
101 private int getPreScalerBase2Power(int control) {
102     final switch (control & 0b11) {
103         case 0:
104             return 0;
105         case 1:
106             return 6;
107         case 2:
108             return 8;
109         case 3:
110             return 10;
111     }
112 }