use crate::pac::peripherals::wdog::{WDOG, WDOG_CNT, WDOG_CS, WDOG_TOVAL, WDOG_WIN};
use embassy_hal_internal::{Peri, PeripheralType};
const UNLOCK: u32 = 0xd928_c520;
const REFRESH: u32 = 0xb480_a602;
const SYNC_POLLS: usize = 1_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ClockSource {
Source0 = 0,
Source1 = 1,
Source2 = 2,
Source3 = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub timeout_ticks: u16,
pub clock_source: ClockSource,
pub divide_by_256: bool,
pub run_in_wait: bool,
pub run_in_stop: bool,
pub run_in_debug: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
timeout_ticks: u16::MAX,
clock_source: ClockSource::Source1,
divide_by_256: false,
run_in_wait: true,
run_in_stop: false,
run_in_debug: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
InvalidTimeout,
Locked,
SyncTimeout,
}
pub(crate) trait SealedInstance {
fn regs() -> WDOG;
const LPCG: Option<u32>;
}
#[allow(private_bounds)]
pub trait Instance: SealedInstance + PeripheralType {}
macro_rules! impl_watchdog {
($instance:ident, $base:expr, $irq:expr) => {
impl crate::watchdog::SealedInstance for crate::peripherals::$instance {
const LPCG: ::core::option::Option<u32> = crate::clock::lpcg(stringify!($instance));
fn regs() -> crate::pac::peripherals::wdog::WDOG {
unsafe { crate::pac::peripherals::wdog::WDOG::from_ptr($base as *mut ()) }
}
}
impl crate::watchdog::Instance for crate::peripherals::$instance {}
};
}
pub(crate) use impl_watchdog;
pub struct Watchdog {
regs: WDOG,
}
impl Watchdog {
pub fn new<T: Instance>(peri: Peri<'static, T>) -> Self {
let _ = peri;
crate::clock::ungate(T::LPCG);
Self { regs: T::regs() }
}
pub fn start(&mut self, config: Config) -> Result<(), Error> {
if config.timeout_ticks < 3 {
return Err(Error::InvalidTimeout);
}
let configured = control(config);
critical_section::with(|_| {
let cs = self.regs.CS().read();
if !cs.UPDATE() {
return Err(Error::Locked);
}
command(
|word| self.regs.CNT().write_value(WDOG_CNT(word)),
UNLOCK,
cs.CMD32EN(),
);
wait_until(|| self.regs.CS().read().ULK())?;
self.regs.WIN().write_value(WDOG_WIN(0));
self.regs
.TOVAL()
.write_value(WDOG_TOVAL(u32::from(config.timeout_ticks)));
self.regs.CS().write_value(configured);
wait_until(|| self.regs.CS().read().RCS())
})
}
pub fn feed(&mut self) {
critical_section::with(|_| {
command(
|word| self.regs.CNT().write_value(WDOG_CNT(word)),
REFRESH,
self.regs.CS().read().CMD32EN(),
);
});
}
pub fn is_running(&self) -> bool {
self.regs.CS().read().EN()
}
}
fn control(config: Config) -> WDOG_CS {
let mut cs = WDOG_CS::default();
cs.set_EN(true);
cs.set_UPDATE(true);
cs.set_CMD32EN(true);
cs.set_CLK(config.clock_source as u8);
cs.set_PRES(config.divide_by_256);
cs.set_WAIT(config.run_in_wait);
cs.set_STOP(config.run_in_stop);
cs.set_DBG(config.run_in_debug);
cs
}
fn command(mut write: impl FnMut(u32), key: u32, command32: bool) {
if command32 {
write(key);
} else {
write(key & 0xffff);
write(key >> 16);
}
}
fn wait_until(mut ready: impl FnMut() -> bool) -> Result<(), Error> {
for _ in 0..SYNC_POLLS {
if ready() {
return Ok(());
}
core::hint::spin_loop();
}
Err(Error::SyncTimeout)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn command_modes_preserve_vendor_key_order() {
for (key, words) in [(UNLOCK, [0xc520, 0xd928]), (REFRESH, [0xa602, 0xb480])] {
let mut actual = std::vec::Vec::new();
command(|word| actual.push(word), key, false);
assert_eq!(actual, words);
actual.clear();
command(|word| actual.push(word), key, true);
assert_eq!(actual, [key]);
}
}
#[test]
fn control_enables_reset_without_window_interrupt_or_test_mode() {
let cs = control(Config {
clock_source: ClockSource::Source3,
divide_by_256: true,
run_in_wait: false,
run_in_stop: true,
run_in_debug: true,
..Config::default()
});
assert_eq!(cs.0, 0x33a5);
assert!(!cs.FLG()); }
#[test]
fn invalid_or_locked_start_leaves_registers_untouched() {
let mut words = [0u32; 4];
let regs = unsafe { WDOG::from_ptr(words.as_mut_ptr().cast()) };
let mut watchdog = Watchdog { regs };
assert_eq!(
watchdog.start(Config {
timeout_ticks: 2,
..Config::default()
}),
Err(Error::InvalidTimeout)
);
assert_eq!(watchdog.start(Config::default()), Err(Error::Locked));
assert_eq!(words, [0; 4]);
}
#[test]
fn synchronization_has_a_bounded_failure_path() {
let mut reads = 0;
assert_eq!(
wait_until(|| {
reads += 1;
false
}),
Err(Error::SyncTimeout)
);
assert_eq!(reads, SYNC_POLLS);
assert_eq!(wait_until(|| true), Ok(()));
}
}