1use crate::apu::{self, ApuContext};
2use crate::bits::BitGroup;
3use crate::interrupts::{InterruptContext, InterruptFlags, Interrupts};
4use crate::memdev::{MemDevice, RelativeAddr};
5
6#[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 #[inline]
24 pub fn period_idx(&self) -> u8 {
25 Self::TIMER_PERIOD.extract(self.0)
26 }
27
28 #[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 #[inline]
43 pub fn set_period_idx(&mut self, val: u8) {
44 Self::TIMER_PERIOD.apply(&mut self.0, val);
45 }
46
47 #[inline]
49 pub fn timer_enable(&self) -> bool {
50 Self::TIMER_ENABLE.extract_bool(self.0)
51 }
52
53 #[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#[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
97pub trait TimerContext: InterruptContext + ApuContext {
99 fn timer(&self) -> &TimerState;
101
102 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 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 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 let period = 0x2000;
177 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}