aboutsummaryrefslogtreecommitdiffstats
path: root/src/timer.c
blob: d4ee69f9ac26a136901d2b8322e366d3317b0b63 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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;
        }
    }
}