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 }