1//! SH7604 watchdog timer (WDT) 2//! 3//! Can also be used as an interval timer, and that is the only mode that is emulated. No 32X software 4//! uses the WDT as a watchdog timer 5 6use crate::debug::WatchdogTimerState; 7use bincode::{Decode, Encode}; 8use jgenesis_common::num::GetBit; 9 10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 11pub enum WatchdogTimerMode { 12 #[default] 13 Interval = 0, 14 Watchdog = 1, 15} 16 17impl WatchdogTimerMode { 18 fn from_bit(bit: bool) -> Self { 19 if bit { Self::Watchdog } else { Self::Interval } 20 } 21} 22 23fn clock_shift_select(byte: u8) -> u8 { 24 match byte & 7 { 25 // sysclk/2 26 0 => 1, 27 // sysclk/64 28 1 => 6, 29 // sysclk/128 30 2 => 7, 31 // sysclk/256 32 3 => 8, 33 // sysclk/512 34 4 => 9, 35 // sysclk/1024 36 5 => 10, 37 // sysclk/4096 38 6 => 12, 39 // sysclk/8192 40 7 => 13, 41 _ => unreachable!("value & 7 is always <= 7"), 42 } 43} 44 45#[derive(Debug, Clone, Encode, Decode)] 46pub struct WatchdogTimer { 47 timer_counter: u8, 48 mode: WatchdogTimerMode, 49 enabled: bool, 50 raw_clock_select: u8, 51 system_clock_shift: u8, 52 system_clock_counter: u64, 53 interval_overflow_flag: bool, 54} 55 56impl WatchdogTimer { 57 pub fn new() -> Self { 58 Self { 59 timer_counter: 0, 60 mode: WatchdogTimerMode::default(), 61 enabled: false, 62 raw_clock_select: 0, 63 system_clock_shift: clock_shift_select(0), 64 system_clock_counter: 0, 65 interval_overflow_flag: false, 66 } 67 } 68 69 pub fn tick(&mut self, system_cycles: u64) { 70 if !self.enabled { 71 return; 72 } 73 74 self.system_clock_counter += system_cycles; 75 let elapsed = self.system_clock_counter >> self.system_clock_shift; 76 self.system_clock_counter &= (1 << self.system_clock_shift) - 1; 77 78 let exceeds_byte = elapsed >= 256; 79 let (counter, overflowed) = self.timer_counter.overflowing_add(elapsed as u8); 80 self.timer_counter = counter; 81 82 let overflow_flag = exceeds_byte || overflowed; 83 self.interval_overflow_flag |= overflow_flag; 84 } 85 86 // $FFFFFE80: WTCSR (Watchdog timer control/status) 87 pub fn read_control(&self) -> u8 { 88 (u8::from(self.interval_overflow_flag) << 7) 89 | ((self.mode as u8) << 6) 90 | (u8::from(self.enabled) << 5) 91 } 92 93 // $FFFFFE81: WTCNT (Watchdog timer counter) 94 pub fn read_counter(&self) -> u8 { 95 self.timer_counter 96 } 97 98 // $FFFFFE80: WTCSR (Watchdog timer control/status) / WTCNT (Watchdog timer counter) 99 // Upper byte determines which register is written to 100 pub fn write_control(&mut self, value: u16) { 101 log::debug!("Watchdog timer control write: {value:04X}"); 102 103 let [msb, lsb] = value.to_be_bytes(); 104 match msb { 105 0x5A => self.write_wtcnt(lsb), 106 0xA5 => self.write_wtcsr(lsb), 107 _ => { 108 log::warn!("Invalid watchdog timer write to $FFFFFE80: {value:04X}"); 109 } 110 } 111 } 112 113 fn write_wtcnt(&mut self, value: u8) { 114 self.timer_counter = value; 115 116 log::debug!("WTCNT write: {value:02X}"); 117 } 118 119 fn write_wtcsr(&mut self, value: u8) { 120 self.interval_overflow_flag &= value.bit(7); 121 self.mode = WatchdogTimerMode::from_bit(value.bit(6)); 122 self.enabled = value.bit(5); 123 self.raw_clock_select = value & 7; 124 self.system_clock_shift = clock_shift_select(value); 125 126 if !self.enabled { 127 // Counter is reset when timer is disabled 128 self.timer_counter = 0; 129 } 130 131 log::debug!("WTCSR write: {value:02X}"); 132 log::debug!(" Clear overflow flag: {}", !value.bit(7)); 133 log::debug!(" Timer mode: {:?}", self.mode); 134 log::debug!(" Timer enabled: {}", self.enabled); 135 log::debug!(" System clock divider: {}", 1 << self.system_clock_shift); 136 137 if self.mode == WatchdogTimerMode::Watchdog { 138 log::error!("Watchdog timer mode not implemented"); 139 } 140 } 141 142 pub fn overflow_flag(&self) -> bool { 143 self.interval_overflow_flag 144 } 145 146 pub(crate) fn debug_state(&self) -> WatchdogTimerState { 147 WatchdogTimerState { 148 enabled: self.enabled, 149 counter: self.timer_counter, 150 system_clock_divider: 1 << self.system_clock_shift, 151 overflow_flag: self.interval_overflow_flag, 152 } 153 } 154}