feo3boy/
timer.rs

1use crate::apu::{self, ApuContext};
2use crate::bits::BitGroup;
3use crate::interrupts::{InterruptContext, InterruptFlags, Interrupts};
4use crate::memdev::{MemDevice, RelativeAddr};
5
6/// Represends sound and volume settings.
7#[derive(Default, Debug, Clone, Eq, PartialEq, Hash, MemDevice)]
8#[memdev(bits, readable = TimerControl::RW_BITS, writable = TimerControl::RW_BITS)]
9#[repr(transparent)]
10pub struct TimerControl(u8);
11
12impl TimerControl {
13    const TIMER_PERIOD: BitGroup = BitGroup(0b0000_0011);
14    const TIMER_ENABLE: BitGroup = BitGroup(0b0000_0100);
15    const RW_BITS: u8 = 0b0000_0111;
16
17    pub fn bits(&self) -> u8 {
18        self.0
19    }
20
21    /// Get the timer period selecton number, this is *not* the actual period value
22    /// selected.
23    #[inline]
24    pub fn period_idx(&self) -> u8 {
25        Self::TIMER_PERIOD.extract(self.0)
26    }
27
28    /// Get the actual period selected.
29    #[inline]
30    pub fn period(&self) -> u16 {
31        match self.period_idx() {
32            0 => 1024,
33            1 => 16,
34            2 => 64,
35            3 => 256,
36            _ => panic!("Illegal timer period number encountered. This should be impossible."),
37        }
38    }
39
40    /// Set the timer period selecton number, this is *not* the actual period value
41    /// selected.
42    #[inline]
43    pub fn set_period_idx(&mut self, val: u8) {
44        Self::TIMER_PERIOD.apply(&mut self.0, val);
45    }
46
47    /// Get the timer enable
48    #[inline]
49    pub fn timer_enable(&self) -> bool {
50        Self::TIMER_ENABLE.extract_bool(self.0)
51    }
52
53    /// Set the timer enable
54    #[inline]
55    pub fn set_timer_enable(&mut self, val: bool) {
56        Self::TIMER_ENABLE.apply(&mut self.0, val as u8);
57    }
58}
59
60/// Memory-mapped IO registers used by the Timer.
61#[derive(Default, Debug, Clone, PartialEq, Eq)]
62pub struct TimerRegs {
63    pub divider: u16,
64    pub timer: u8,
65    pub timer_mod: u8,
66    pub timer_control: TimerControl,
67}
68
69impl MemDevice for TimerRegs {
70    const LEN: usize = 4;
71
72    fn read_byte_relative(&self, addr: RelativeAddr) -> u8 {
73        match addr.relative() {
74            0x00 => (self.divider / 0x100) as u8,
75            0x01 => self.timer,
76            0x02 => self.timer_mod,
77            0x03 => self.timer_control.read_byte_relative(addr.offset_by(0x03)),
78            _ => panic!("Address {} out of range for TimerRegs", addr),
79        }
80    }
81
82    fn write_byte_relative(&mut self, addr: RelativeAddr, val: u8) {
83        match addr.relative() {
84            0x00 => self.divider = 0,
85            0x01 => self.timer = val,
86            0x02 => self.timer_mod = val,
87            0x03 => self
88                .timer_control
89                .write_byte_relative(addr.offset_by(0x03), val),
90            _ => panic!("Address {} out of range for TimerRegs", addr),
91        }
92    }
93
94    memdev_bytes_from_byte!(TimerRegs);
95}
96
97/// Context trait providing access to fields needed to service graphics.
98pub trait TimerContext: InterruptContext + ApuContext {
99    /// Get the timer state.
100    fn timer(&self) -> &TimerState;
101
102    /// Get mutable access to the timer state.
103    fn timer_mut(&mut self) -> &mut TimerState;
104
105    fn timer_regs(&self) -> &TimerRegs;
106    fn timer_regs_mut(&mut self) -> &mut TimerRegs;
107}
108
109#[derive(Default, Debug, Clone, PartialEq, Eq)]
110pub struct TimerState {
111    timer_ticks: u64,
112    old_divider: u16,
113    interrupt_queued: bool,
114    interrupt_delay: u64,
115}
116
117impl TimerState {
118    pub fn new() -> TimerState {
119        Default::default()
120    }
121
122    fn queue_interrupt(&mut self) {
123        self.interrupt_queued = true;
124        self.interrupt_delay = 4;
125    }
126}
127
128fn increment_timer(ctx: &mut impl TimerContext) {
129    let (timer, overflow) = ctx.timer_regs().timer.overflowing_add(1);
130
131    if overflow {
132        ctx.timer_mut().queue_interrupt();
133    }
134
135    ctx.timer_regs_mut().timer = timer;
136}
137
138fn check_interrupt(ctx: &mut impl TimerContext, tcycles: u64) {
139    // an intervening write to the timer will discard the interrupt
140    if ctx.timer().interrupt_queued && ctx.timer_regs().timer == 0 {
141        if tcycles >= ctx.timer().interrupt_delay as u64 {
142            ctx.timer_mut().interrupt_queued = false;
143            let timer_mod = ctx.timer_regs().timer_mod;
144            ctx.timer_regs_mut().timer = timer_mod;
145            ctx.interrupts_mut().send(InterruptFlags::TIMER);
146        } else {
147            ctx.timer_mut().interrupt_delay -= tcycles;
148        }
149    } else {
150        ctx.timer_mut().interrupt_queued = false;
151    }
152}
153
154pub fn tick(ctx: &mut impl TimerContext, tcycles: u64) {
155    let mut divider = ctx.timer_regs().divider;
156
157    check_interrupt(ctx, tcycles);
158
159    if ctx.timer_regs().timer_control.timer_enable() {
160        let period = ctx.timer_regs().timer_control.period();
161
162        // falling edge case when divider has been reset
163        if (ctx.timer().old_divider & (period >> 1)) != 0 && (divider & (period >> 1)) == 0 {
164            increment_timer(ctx);
165        } else {
166            let mask = period - 1;
167
168            if divider & mask + tcycles as u16 & mask > period || tcycles > period as u64 {
169                increment_timer(ctx)
170            }
171        }
172    }
173
174    // Update APU at ~512 Hz
175    // period will have to be dynamic to support GBC double speed
176    let period = 0x2000;
177    // falling edge case when divider has been reset
178    if (ctx.timer().old_divider & (period >> 1)) != 0 && (divider & (period >> 1)) == 0 {
179        apu::apu_tick(ctx);
180    } else {
181        let mask = period - 1;
182
183        if divider & mask + tcycles as u16 & mask > period || tcycles > period as u64 {
184            apu::apu_tick(ctx);
185        }
186    }
187
188    divider = divider.wrapping_add(tcycles as u16);
189    ctx.timer_mut().old_divider = ctx.timer_regs().divider;
190    ctx.timer_regs_mut().divider = divider;
191}