diff options
Diffstat (limited to 'src/timer.c')
| -rw-r--r-- | src/timer.c | 82 |
1 files changed, 82 insertions, 0 deletions
diff --git a/src/timer.c b/src/timer.c new file mode 100644 index 0000000..d4ee69f --- /dev/null +++ b/src/timer.c @@ -0,0 +1,82 @@ +#include "timer.h" + +// Which DIV bit feeds the TIMA increment for each TAC clock-select. +static const int tac_bit[4] = { 9, 3, 5, 7 }; + +static bool timer_signal(Timer *t) { + if (!(t->tac & 0x04)) return false; + return (t->div >> tac_bit[t->tac & 3]) & 1; +} + +static void set_div(GB *gb, u16 newdiv) { + Timer *t = &gb->timer; + bool old = timer_signal(t); + t->div = newdiv; + bool now = timer_signal(t); + // falling edge -> increment TIMA + if (old && !now) { + if (t->tima == 0xFF) { + t->tima = 0; + t->overflow_pending = true; + t->reload_delay = 4; // reload TMA after 4 T-cycles + } else { + t->tima++; + } + } +} + +void timer_tick(GB *gb, int tcycles) { + Timer *t = &gb->timer; + for (int i = 0; i < tcycles; i++) { + if (t->overflow_pending) { + t->reload_delay--; + if (t->reload_delay == 0) { + t->tima = t->tma; + t->overflow_pending = false; + gb_request_interrupt(gb, INT_TIMER); + } + } + set_div(gb, t->div + 1); + } +} + +u8 timer_read(GB *gb, u16 addr) { + Timer *t = &gb->timer; + switch (addr) { + case 0xFF04: return t->div >> 8; + case 0xFF05: return t->tima; + case 0xFF06: return t->tma; + case 0xFF07: return t->tac | 0xF8; + } + return 0xFF; +} + +void timer_write(GB *gb, u16 addr, u8 val) { + Timer *t = &gb->timer; + switch (addr) { + case 0xFF04: + set_div(gb, 0); // writing DIV resets it (may cause edge) + break; + case 0xFF05: + // writing during reload delay cancels the reload + if (t->reload_delay != 0) t->overflow_pending = false; + if (!(t->overflow_pending && t->reload_delay == 0)) + t->tima = val; + break; + case 0xFF06: + t->tma = val; + break; + case 0xFF07: { + bool old = timer_signal(t); + t->tac = val & 0x07; + bool now = timer_signal(t); + if (old && !now) { + // disabling can also cause an increment (glitch) + if (t->tima == 0xFF) { + t->tima = 0; t->overflow_pending = true; t->reload_delay = 4; + } else t->tima++; + } + break; + } + } +} |
