use crate::peripherals::Wdt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResetPulseLength {
Cycles2 = 0,
Cycles4 = 1,
Cycles8 = 2,
Cycles16 = 3,
Cycles32 = 4,
Cycles64 = 5,
Cycles128 = 6,
Cycles256 = 7,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WdtMode {
SingleInterrupt,
DoubleInterrupt,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum WdtError {
Busy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct WdtTimeout(u32);
impl WdtTimeout {
pub const MAX_MS: u32 = {
let max_cycles = (WDT_MAX_LOAD as u64) << WDT_LOAD_RESEV;
(max_cycles * 1000 / (WDT_CLOCK_HZ as u64)) as u32
};
pub const fn from_ms(ms: u32) -> Option<Self> {
if ms == 0 {
return None;
}
let cycles = (ms as u64) * (WDT_CLOCK_HZ as u64) / 1000;
if (cycles >> WDT_LOAD_RESEV) > WDT_MAX_LOAD as u64 {
return None;
}
Some(WdtTimeout(ms))
}
pub const fn as_ms(self) -> u32 {
self.0
}
const fn load_field(self) -> u32 {
let cycles = (self.0 as u64) * (WDT_CLOCK_HZ as u64) / 1000;
(cycles >> WDT_LOAD_RESEV) as u32
}
}
const WDT_CCVR_POLL_LIMIT: u32 = 100_000;
pub struct Watchdog<'d> {
_wdt: Wdt<'d>,
}
pub const WDT_CLOCK_HZ: u32 = crate::soc::chip::TCXO_HZ;
pub const WDT_MAX_LOAD: u32 = 0x00FF_FFFF;
const WDT_LOAD_RESEV: u32 = 8;
impl<'d> Watchdog<'d> {
pub fn new(wdt: Wdt<'d>) -> Self {
let wd = Self { _wdt: wdt };
wd.unlock();
wd
}
fn regs(&self) -> &'static crate::soc::pac::wdt::RegisterBlock {
unsafe { &*Wdt::ptr() }
}
fn unlock(&self) {
unsafe {
self.regs().wdt_lock().write(|w| w.bits(0x5A5A5A5A));
}
}
fn lock(&self) {
unsafe {
self.regs().wdt_lock().write(|w| w.bits(0x0000_0000));
}
}
pub fn configure(
&mut self,
timeout: WdtTimeout,
mode: WdtMode,
reset_enable: bool,
reset_pulse: ResetPulseLength,
) -> Result<(), WdtError> {
let load = timeout.load_field();
self.unlock();
unsafe {
self.regs().wdt_load().write(|w| w.bits(load << WDT_LOAD_RESEV));
}
let mut cr: u32 = 0;
cr |= 0x01; if reset_enable {
cr |= 1 << 2; }
cr |= (reset_pulse as u32) << 3; cr |= 1 << 6; if matches!(mode, WdtMode::DoubleInterrupt) {
cr |= 1 << 7; }
unsafe {
self.regs().wdt_cr().write(|w| w.bits(cr));
}
let res = self.request_counter();
self.lock();
res
}
pub fn enable(&mut self) {
self.unlock();
let cr = self.regs().wdt_cr().read().bits();
unsafe {
self.regs().wdt_cr().write(|w| w.bits(cr | 0x01));
}
self.lock();
}
pub fn disable(&mut self) {
self.unlock();
let cr = self.regs().wdt_cr().read().bits();
unsafe {
self.regs().wdt_cr().write(|w| w.bits(cr & !0x01));
}
self.lock();
}
pub fn feed(&mut self) {
self.unlock();
unsafe {
self.regs().wdt_restart().write(|w| w.bits(0x0000_0001));
}
self.lock();
}
pub fn counter_value(&self) -> Result<u32, WdtError> {
self.request_counter()?;
Ok(self.regs().wdt_cnt().read().bits())
}
fn request_counter(&self) -> Result<(), WdtError> {
unsafe {
self.regs().wdt_ccvr_en().write(|w| w.bits(0x01));
}
for _ in 0..WDT_CCVR_POLL_LIMIT {
if self.regs().wdt_ccvr_en().read().bits() & 0x02 != 0 {
return Ok(());
}
core::hint::spin_loop();
}
Err(WdtError::Busy)
}
#[instability::unstable]
pub fn interrupt_pending(&self) -> bool {
self.regs().wdt_raw_intr().read().bits() & 0x01 != 0
}
#[instability::unstable]
pub fn interrupt_masked(&self) -> bool {
self.regs().wdt_intr().read().bits() & 0x01 != 0
}
#[instability::unstable]
pub fn clear_interrupt(&self) {
let _ = self.regs().wdt_eoi().read().bits();
}
#[instability::unstable]
pub fn enable_interrupt(&mut self) {
self.unlock();
let cr = self.regs().wdt_cr().read().bits();
unsafe {
self.regs().wdt_cr().write(|w| w.bits(cr & !(1 << 6)));
}
self.lock();
}
#[instability::unstable]
pub fn disable_interrupt(&mut self) {
self.unlock();
let cr = self.regs().wdt_cr().read().bits();
unsafe {
self.regs().wdt_cr().write(|w| w.bits(cr | (1 << 6)));
}
self.lock();
}
pub fn is_busy(&self) -> bool {
self.regs().wdt_status().read().bits() & 0x01 == 0
}
#[must_use = "the watchdog is now armed forever; bind the marker to make that explicit"]
pub fn into_armed(self) -> WatchdogArmed {
core::mem::forget(self); WatchdogArmed(())
}
#[must_use = "the watchdog is now armed forever; bind the marker to make that explicit"]
pub fn leak(self) -> WatchdogArmed {
self.into_armed()
}
}
#[derive(Debug)]
#[must_use]
pub struct WatchdogArmed(());
impl Drop for Watchdog<'_> {
fn drop(&mut self) {
self.disable();
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod tests {
use super::{ResetPulseLength, WDT_CLOCK_HZ, WDT_LOAD_RESEV, WDT_MAX_LOAD, WatchdogArmed, WdtMode, WdtTimeout};
#[test]
fn armed_marker_is_zero_sized() {
assert_eq!(core::mem::size_of::<WatchdogArmed>(), 0);
}
fn load_field(timeout_ms: u32) -> u32 {
WdtTimeout::from_ms(timeout_ms).expect("ms in range").load_field()
}
fn control_bits(mode: WdtMode, reset_enable: bool, reset_pulse: ResetPulseLength) -> u32 {
let mut cr: u32 = 0;
cr |= 0x01; if reset_enable {
cr |= 1 << 2; }
cr |= (reset_pulse as u32) << 3; cr |= 1 << 6; if matches!(mode, WdtMode::DoubleInterrupt) {
cr |= 1 << 7; }
cr
}
#[test]
fn reset_pulse_discriminants_are_the_3bit_field() {
assert_eq!(ResetPulseLength::Cycles2 as u32, 0);
assert_eq!(ResetPulseLength::Cycles4 as u32, 1);
assert_eq!(ResetPulseLength::Cycles8 as u32, 2);
assert_eq!(ResetPulseLength::Cycles16 as u32, 3);
assert_eq!(ResetPulseLength::Cycles32 as u32, 4);
assert_eq!(ResetPulseLength::Cycles64 as u32, 5);
assert_eq!(ResetPulseLength::Cycles128 as u32, 6);
assert_eq!(ResetPulseLength::Cycles256 as u32, 7);
}
#[test]
fn zero_timeout_rejected() {
assert!(WdtTimeout::from_ms(0).is_none());
}
#[test]
fn known_timeout_matches_hand_computed_field() {
let cycles = WDT_CLOCK_HZ as u64; let expected = (cycles >> WDT_LOAD_RESEV) as u32;
assert_eq!(load_field(1000), expected);
assert!(expected <= WDT_MAX_LOAD);
}
#[test]
fn over_range_timeout_rejected() {
assert!(WdtTimeout::from_ms(u32::MAX).is_none());
assert!(WdtTimeout::from_ms(WdtTimeout::MAX_MS).is_some());
assert!(WdtTimeout::from_ms(WdtTimeout::MAX_MS + 1).is_none());
assert!(WdtTimeout::from_ms(WdtTimeout::MAX_MS).unwrap().load_field() <= WDT_MAX_LOAD);
}
#[test]
fn load_field_is_monotonic_in_range() {
let a = load_field(1000);
let b = load_field(2000);
assert!(b >= a);
assert!(b <= WDT_MAX_LOAD);
}
#[test]
fn first_timeout_past_boundary_is_rejected() {
let threshold_cycles = (WDT_MAX_LOAD as u64 + 1) << WDT_LOAD_RESEV;
let big_ms = (threshold_cycles * 1000).div_ceil(WDT_CLOCK_HZ as u64) + 1;
let ms = big_ms.min(u32::MAX as u64) as u32;
assert!(WdtTimeout::from_ms(ms).is_none());
}
#[test]
fn control_bits_minimal_config() {
let cr = control_bits(WdtMode::SingleInterrupt, false, ResetPulseLength::Cycles2);
assert_eq!(cr, 0x01 | (1 << 6));
}
#[test]
fn control_bits_full_config() {
let cr = control_bits(WdtMode::DoubleInterrupt, true, ResetPulseLength::Cycles256);
let expected = 0x01 | (1 << 2) | (7 << 3) | (1 << 6) | (1 << 7); assert_eq!(cr, expected);
}
#[test]
fn control_bits_reset_pulse_occupies_field() {
for (variant, val) in
[(ResetPulseLength::Cycles2, 0u32), (ResetPulseLength::Cycles16, 3), (ResetPulseLength::Cycles256, 7)]
{
let cr = control_bits(WdtMode::SingleInterrupt, false, variant);
assert_eq!((cr >> 3) & 0x7, val);
}
}
#[test]
fn mode_only_affects_bit7() {
let single = control_bits(WdtMode::SingleInterrupt, true, ResetPulseLength::Cycles32);
let double = control_bits(WdtMode::DoubleInterrupt, true, ResetPulseLength::Cycles32);
assert_eq!(single ^ double, 1 << 7);
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod proptests {
use super::{WDT_MAX_LOAD, WdtTimeout};
use proptest::prelude::*;
proptest! {
#[test]
fn from_ms_never_panics(ms in any::<u32>()) {
let _ = WdtTimeout::from_ms(ms);
}
#[test]
fn accepted_timeout_fits_field(ms in any::<u32>()) {
if let Some(t) = WdtTimeout::from_ms(ms) {
prop_assert!(t.load_field() <= WDT_MAX_LOAD);
}
}
#[test]
fn acceptance_is_monotone(ms in 1u32..=WdtTimeout::MAX_MS) {
prop_assert!(WdtTimeout::from_ms(ms).is_some());
}
}
}