frt.rsannotatedfrt.rssource199 lines · 6.8 KB · raw
1//! SH-2 free-running timer (FRT)
2//!
3//! FRT functionality is not emulated; this module exists only so that the registers are R/W
4
5use bincode::{Decode, Encode};
6use jgenesis_common::num::{GetBit, U16Ext};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
9enum CompareRegister {
10    #[default]
11    A = 0,
12    B = 1,
13}
14
15impl CompareRegister {
16    fn from_bit(bit: bool) -> Self {
17        if bit { Self::B } else { Self::A }
18    }
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
22enum ClockSource {
23    #[default]
24    InternalDiv8 = 0,
25    InternalDiv32 = 1,
26    InternalDiv128 = 2,
27    External = 3,
28}
29
30impl ClockSource {
31    fn from_byte(byte: u8) -> Self {
32        match byte & 3 {
33            0 => Self::InternalDiv8,
34            1 => Self::InternalDiv32,
35            2 => Self::InternalDiv128,
36            3 => Self::External,
37            _ => unreachable!("value & 3 is always <= 3"),
38        }
39    }
40}
41
42#[derive(Debug, Clone, Encode, Decode)]
43pub struct FreeRunTimer {
44    counter: u16,
45    clock_source: ClockSource,
46    compare_a: u16,
47    compare_b: u16,
48    compare_register: CompareRegister,
49    compare_a_flag: bool,
50    compare_b_flag: bool,
51    overflow_flag: bool,
52    clear_counter_on_a_match: bool,
53    compare_a_interrupt_enabled: bool,
54    compare_b_interrupt_enabled: bool,
55    overflow_interrupt_enabled: bool,
56    // Stored only to emulate these bits being R/W
57    compare_a_pin_output: bool,
58    compare_b_pin_output: bool,
59    // 16-bit temporary register used to prevent data races when the CPU accesses 16-bit registers,
60    // since the timer only supports 8-bit memory accesses
61    temp_register: u16,
62}
63
64impl FreeRunTimer {
65    pub fn new() -> Self {
66        Self {
67            counter: 0,
68            clock_source: ClockSource::default(),
69            compare_a: 0xFFFF,
70            compare_b: 0xFFFF,
71            compare_register: CompareRegister::default(),
72            compare_a_flag: false,
73            compare_b_flag: false,
74            overflow_flag: false,
75            clear_counter_on_a_match: false,
76            compare_a_interrupt_enabled: false,
77            compare_b_interrupt_enabled: false,
78            overflow_interrupt_enabled: false,
79            compare_a_pin_output: false,
80            compare_b_pin_output: false,
81            temp_register: 0,
82        }
83    }
84
85    pub fn read_register(&self, address: u32) -> u8 {
86        log::trace!("Timer register read: {address:08X}");
87
88        match address & 0xF {
89            0x7 => self.read_tocr(),
90            _ => {
91                log::warn!("FRT register read {address:08X}");
92                0
93            }
94        }
95    }
96
97    pub fn write_register(&mut self, address: u32, value: u8) {
98        match address & 0xF {
99            0x0 => self.write_tier(value),
100            0x1 => self.write_ftcsr(value),
101            0x2 | 0x4 => {
102                log::trace!("Temp register MSB write: {address:08X} {value:02X}");
103                self.temp_register.set_msb(value);
104            }
105            0x3 => self.write_frc(value),
106            0x5 => self.write_ocr(value),
107            0x6 => self.write_tcr(value),
108            0x7 => self.write_tocr(value),
109            _ => log::warn!("FRT register write {address:08X} {value:02X}"),
110        }
111    }
112
113    // $FFFFFE10: TIER (Timer interrupt enable register)
114    fn write_tier(&mut self, value: u8) {
115        if value.bit(7) {
116            log::warn!("Input capture interrupt enabled; not implemented");
117        }
118
119        self.compare_a_interrupt_enabled = value.bit(3);
120        self.compare_b_interrupt_enabled = value.bit(2);
121        self.overflow_interrupt_enabled = value.bit(1);
122        self.clear_counter_on_a_match = value.bit(0);
123
124        log::trace!("TIER write: {value:02X}");
125        log::trace!("  Compare match A interrupt enabled: {}", self.compare_a_interrupt_enabled);
126        log::trace!("  Compare match B interrupt enabled: {}", self.compare_b_interrupt_enabled);
127        log::trace!("  Overflow interrupt enabled: {}", self.overflow_interrupt_enabled);
128        log::trace!("  Clear counter on compare match A: {}", self.clear_counter_on_a_match);
129    }
130
131    // $FFFFFE11: FTCSR (Free-running timer control/status register)
132    fn write_ftcsr(&mut self, value: u8) {
133        self.compare_a_flag &= value.bit(3);
134        self.compare_b_flag &= value.bit(2);
135        self.overflow_flag &= value.bit(1);
136        self.clear_counter_on_a_match = value.bit(0);
137
138        log::trace!("FTCSR write: {value:02X}");
139        log::trace!("  Compare A flag clear: {}", !value.bit(3));
140        log::trace!("  Compare B flag clear: {}", !value.bit(2));
141        log::trace!("  Overflow flag clear: {}", !value.bit(1));
142        log::trace!("  Clear counter on compare match A: {}", self.clear_counter_on_a_match);
143    }
144
145    // $FFFFFE12-$FFFFFE13: FRC (Free-running counter)
146    fn write_frc(&mut self, value: u8) {
147        self.counter = (self.temp_register & 0xFF00) | u16::from(value);
148        log::trace!("FRC write: {value:02X}");
149        log::trace!("  Counter: {:04X}", self.counter);
150    }
151
152    // $FFFFFE14-$FFFFFE15: OCRA/B (Output compare register A/B)
153    fn write_ocr(&mut self, value: u8) {
154        let word = (self.temp_register & 0xFF00) | u16::from(value);
155
156        match self.compare_register {
157            CompareRegister::A => {
158                self.compare_a = word;
159                log::trace!("Compare match A value: {word:04X}");
160            }
161            CompareRegister::B => {
162                self.compare_b = word;
163                log::trace!("Compare match B value: {word:04X}");
164            }
165        }
166    }
167
168    // $FFFFFE16: TCR (Timer control register)
169    fn write_tcr(&mut self, value: u8) {
170        self.clock_source = ClockSource::from_byte(value);
171
172        if self.clock_source == ClockSource::External {
173            log::warn!("Timer clock source set to external clock; not implemented");
174        }
175
176        log::trace!("TCR write: {value:02X}");
177        log::trace!("  Clock source: {:?}", self.clock_source);
178        log::trace!("  Input edge select: {}", if value.bit(7) { "Rising" } else { "Falling" });
179    }
180
181    // $FFFFFE17: TOCR (Timer output compare control register)
182    fn read_tocr(&self) -> u8 {
183        0xE0 | ((self.compare_register as u8) << 4)
184            | (u8::from(self.compare_a_pin_output) << 1)
185            | u8::from(self.compare_b_pin_output)
186    }
187
188    // $FFFFFE17: TOCR (Timer output compare control register)
189    fn write_tocr(&mut self, value: u8) {
190        self.compare_register = CompareRegister::from_bit(value.bit(4));
191        self.compare_a_pin_output = value.bit(1);
192        self.compare_b_pin_output = value.bit(0);
193
194        log::trace!("TOCR write: {value:02X}");
195        log::trace!("  Compare match register: {:?}", self.compare_register);
196        log::trace!("  Compare A output pin level: {}", self.compare_a_pin_output);
197        log::trace!("  Compare B output pin level: {}", self.compare_b_pin_output);
198    }
199}