kea-hal 0.5.0

HAL for NXP KEA devices.
Documentation
//! # WDOG - Watchdog Timer
//!
//! This peripheral runs from an independent timer, and resets the MCU when
//! that timer overflows. Clearing the count value of this timer ensures that
//! software does not leave the cpu stuck in an infinite loop or execute from
//! unknown data.
//!
//! ** NOTE: ** The watchdog does not function well with the debugger active.
//! writing to it is only successful in odd cases, with little effect.
//! Attempting to unlock will trigger a reset. I pulled my hair out over a long
//! weekend before I realized this. Yes, there is a DEBUG mode to set according
//! the KEA64RM. It never works as expected.
//!
//! ## Usage
//!
//! ```rust
//! #![no_main]
//! #![no_std]
//!
//! use kea_hal as hal;
//!
//! use cortex_m_rt::entry;
//! use hal::{pac, prelude::*, system};
//! use panic_halt as _;
//!
//! #[entry]
//! fn main() -> ! {
//!     //println!("Hello, world!");
//!     let _cp = cortex_m::Peripherals::take().unwrap();
//!     let dp = pac::Peripherals::take().unwrap();
//!
//!     let watchdog = dp.WDOG.split();
//!     let mut config = watchdog.configuration();
//!     // Reset every 0xBEEF/32kHz seconds.
//!     config.period = 0xBEEF;
//!     config.clock = system::watchdog::WDogClock::IntRefClock;
//!
//!     // Trigger an interrupt before reset to log the error (or something).
//!     config.interrupt = true;
//!
//!     // load new settings (watchdog will determine if needs to unlock or
//!     // not)
//!     let watchdog = watchdog.configure(config);
//!
//!     // Seal the watchdog so that it cannot be modified until reset
//!     let watchdog.into_sealed();
//! }
//! ```
//!
//!
//! ## Clock Sources
//!
//! * Bus Clock
//! * 1kHz Clock
//! * 32kHz Clock
//! * Ext Clock
//!
//! ## Programmable Timeout Perioid
//!
//! 16 bit timeout value, with optional, fixed, 1/256 prescaler for longer
//!    timeout periods.
//!
//! ## Servicing the Watchdog.
//!
//! Call the [Watchdog.service] method to reset the countdown. This is often
//! known as petting the watchdog.
//!
//! Refresh write sequence: 0x2A6 and then 0x80B4 within 16 bus clocks.
//!
//! ## Windowed refresh
//!
//! Triggers a reset if refresh comes before expected. 16 bit programmable
//! window value. "Provides robust check that program flow is faster than
//! expected".
//!
//! *Implementer's Note*
//!
//! This seems useful for asm sequences, but it seems like writing in a high
//! level language would make determining "how soon is too soon" rather
//! difficult.
//!
//! ## Watchdog Interrupt
//!
//! Allows some post-processing to be done after the watchdog triggers, but
//! before the reset. Reset happens 128 bus clocks after the interrupt vector
//! is fetched.
//!
//! ## Configuration
//!
//! Configuration register fields are write-once after reset to prevent
//! accidental modification. These fields can be unlocked for updates by
//! writing 0x20C5 and then 0x28D9 (within 16 bus clocks of each other).
//! Updates must be written within 128 bus clocks after unlocking.

use crate::cortex_m::interrupt;
use crate::{pac::WDOG, HALExt};
use core::marker::PhantomData;

#[inline(always)]
fn unlock(_cs: &interrupt::CriticalSection) {
    let peripheral = unsafe { &(*WDOG::ptr()) };
    peripheral
        .wdog_cnt()
        .write(|w| unsafe { w.bits(0x20C5).bits(0x28D9) });
}

impl HALExt for WDOG {
    type T = WatchDog<Enabled, Unlocked>;
    fn split(self) -> WatchDog<Enabled, Unlocked> {
        WatchDog {
            _enable: PhantomData,
            _update: PhantomData,
            peripheral: self,
        }
    }
}

/// Enumeration of watchdog clocks
#[derive(Clone, Debug)]
#[repr(u8)]
pub enum WDogClock {
    /// Bus Clock.
    ///
    /// Note that using this clock disables the watchdog's backup reset
    /// functionality. The Watchdog periphal uses the bus block internally to
    /// operate. If the bus clock is lost and the WDogClock continues to
    /// increment the counter (i.e. it's not also set to bus clock), after the
    /// counter overflows twice the backup reset functionality kicks in to
    /// reset the MCU.
    BusClock = 0,
    /// Internal 1kHz Low Power Oscillator
    LpoClock = 1,
    /// 32kHz Internal Reference Clock
    IntRefClock = 2,
    /// External Reference Clock
    ExtRefClock = 3,
}

/// The Watchdog interface presented to the user.
pub struct WatchDog<State, UpdateState> {
    _enable: PhantomData<State>,
    _update: PhantomData<UpdateState>,
    peripheral: WDOG,
}
/// Holds watchdog configuration.
///
/// Generated by [WatchDog::configuration] and consumed by
/// [WatchDog::configure].
#[derive(Debug)]
pub struct WDogConfig {
    /// Watchdog Generates Intterupts
    pub interrupt: bool,
    /// Watchdog Operates in Debug mode
    pub debug_mode: bool,
    /// Watchdog Operates in Wait mode
    pub wait_mode: bool,
    /// Watchdog Operates in Stop mode
    pub stop_mode: bool,
    /// Windowed Watchdog Mode
    pub windowed: bool,
    /// Use watchdog clock prescaler (fixed 1:256)
    pub prescale: bool,
    /// Set the clock used by the watchdog
    pub clock: WDogClock,
    /// When counter >= period, reset.
    pub period: u16,
    /// Refresh window
    ///
    /// If windowed is set and the watchdog is serviced while counter <= window
    /// reset. In other words, in windowed mode, the watchdog will trigger a
    /// reset unless serviced when window < counter < period.
    pub window: u16,
}

// This state is the on-reset state
impl WatchDog<Enabled, Unlocked> {
    /// Load a configuration and start the watchdog
    ///
    /// per KEA64RM 16.3.2 pg193-194, all registers except count must be
    /// written to for configuration to take effect. The Window register may be
    /// omited if not in windowed mode.
    ///
    /// Note: Configuring in an unlocked state with the debugger is attached
    /// will have little useful effect.
    pub fn configure(self, config: WDogConfig) -> WatchDog<Enabled, Locked> {
        // The 16bit watchdog registers are in big-endian format
        self.peripheral
            .wdog_toval()
            .write(|w| unsafe { w.bits(config.period.swap_bytes()) });
        if config.windowed {
            self.peripheral
                .wdog_win()
                .write(|w| unsafe { w.bits(config.window.swap_bytes()) });
        }
        self.peripheral.cs2.modify(|_, w| {
            w.win()
                .bit(config.windowed)
                .pres()
                .bit(config.prescale)
                .clk()
                .bits(config.clock.clone() as u8) // why does only this one move from config?
        });
        self.peripheral.cs1.modify(|_, w| {
            w.int()
                .bit(config.interrupt)
                .dbg()
                .bit(config.debug_mode)
                .wait()
                .bit(config.wait_mode)
                .stop()
                .bit(config.stop_mode)
                .en() // Enable the Watchdog
                ._1()
                .update() // Allow to be updateable (locked, not sealed)
                ._1()
        });

        WatchDog {
            _enable: PhantomData,
            _update: PhantomData,
            peripheral: self.peripheral,
        }
    }

    /// Disable the WatchDog
    pub fn into_disabled(self) -> WatchDog<Disabled, Locked> {
        // Write everything. CS1 last
        self.peripheral
            .wdog_toval()
            .modify(|r, w| unsafe { w.bits(r.bits()) });

        // update window register if window is set
        if self.peripheral.cs2.read().win().bit() {
            self.peripheral
                .wdog_win()
                .modify(|r, w| unsafe { w.bits(r.bits()) });
        }
        self.peripheral
            .cs2
            .modify(|r, w| unsafe { w.bits(r.bits()) });
        self.peripheral
            .cs1
            .modify(|r, w| unsafe { w.bits(r.bits()).en()._0() });

        WatchDog {
            _enable: PhantomData,
            _update: PhantomData,
            peripheral: self.peripheral,
        }
    }
}

impl WatchDog<Enabled, Locked> {
    /// Unlock and disable the WatchDog
    pub fn into_disabled(self) -> WatchDog<Disabled, Locked> {
        interrupt::free(|cs| {
            unlock(cs);
            WatchDog::<Enabled, Unlocked> {
                _enable: PhantomData,
                _update: PhantomData,
                peripheral: self.peripheral,
            }
            .into_disabled()
        })
    }
}

impl<UpdateState> WatchDog<Enabled, UpdateState> {
    /// Service the Watchdog
    ///
    /// Restart the countdown for MCU reset. This is often called petting,
    /// feeding, or kicking the watchdog.
    pub fn service(&self) {
        interrupt::free(|_| {
            self.peripheral
                .wdog_cnt()
                .write(|w| unsafe { w.cnt().bits(0x02A6) });
            self.peripheral
                .wdog_cnt()
                .write(|w| unsafe { w.cnt().bits(0x80B4) });
        });
    }

    /// Return the current value of the watchdog's counter
    ///
    /// This function swaps the bytes from the big endian registers to little
    /// endian representation used.
    pub fn counts(&self) -> u16 {
        self.peripheral.wdog_cnt().read().bits().swap_bytes()
    }
}

impl<State> WatchDog<State, Locked> {
    /// Unlock, enable, and reconfigure
    ///
    /// Note: Configuring in a locked state with the debugger attached will
    /// trigger an immediate reset.
    pub fn configure(self, config: WDogConfig) -> WatchDog<Enabled, Locked> {
        interrupt::free(|cs| {
            unlock(cs);
            WatchDog::<Enabled, Unlocked> {
                _enable: PhantomData,
                _update: PhantomData,
                peripheral: self.peripheral,
            }
            .configure(config)
        })
    }

    /// Seals the WatchDog peripheral.
    ///
    /// This prevents any further modifications to the watchdog, aside from
    /// servicing as needed.
    pub fn into_sealed(self) -> WatchDog<State, Sealed> {
        interrupt::free(|cs| {
            unlock(cs);

            // Relock everything with current value, except unset update
            self.peripheral
                .wdog_toval()
                .modify(|r, w| unsafe { w.bits(r.bits()) });
            if self.peripheral.cs2.read().win().bit() {
                self.peripheral
                    .wdog_win()
                    .modify(|r, w| unsafe { w.bits(r.bits()) });
            }
            self.peripheral
                .cs2
                .modify(|r, w| unsafe { w.bits(r.bits()) });

            // update_A::_0 is what seals.
            self.peripheral
                .cs1
                .modify(|r, w| unsafe { w.bits(r.bits()).update()._0() });
        });
        WatchDog {
            _enable: PhantomData,
            _update: PhantomData,
            peripheral: self.peripheral,
        }
    }
}

impl<State, UpdateState> WatchDog<State, UpdateState> {
    /// Returns a [WDogConfig] containing the current state of the peripheral
    pub fn configuration(&self) -> WDogConfig {
        WDogConfig {
            interrupt: self.peripheral.cs1.read().int().bit(),
            debug_mode: self.peripheral.cs1.read().dbg().bit(),
            wait_mode: self.peripheral.cs1.read().wait().bit(),
            stop_mode: self.peripheral.cs1.read().stop().bit(),
            windowed: self.peripheral.cs2.read().win().bit(),
            prescale: self.peripheral.cs2.read().pres().bit(),
            clock: match self.peripheral.cs2.read().clk().bits() {
                0 => WDogClock::BusClock,
                1 => WDogClock::LpoClock,
                2 => WDogClock::IntRefClock,
                _ => WDogClock::ExtRefClock,
            },
            period: self.peripheral.wdog_toval().read().bits(),
            window: self.peripheral.wdog_win().read().bits(),
        }
    }

    /// Checks if the interrupt_ran.
    ///
    /// If acknowledge argument is true, then clear the interrupt flag if it is
    /// set.
    pub fn interrupt_ran(&self, acknowledge: bool) -> bool {
        let ret_val: bool = self.peripheral.cs2.read().flg().bit();
        if acknowledge && ret_val {
            self.peripheral.cs2.modify(|_, w| w.flg()._1());
        }
        ret_val
    }
}

/// Unlocked state, modifiable.
pub struct Unlocked;
/// Locked state, must unlock before modifying
pub struct Locked;
/// Locked and sealed state; Device must be reset to unlock.
pub struct Sealed;

/// Enabled state
pub struct Enabled;
/// Disabled state
pub struct Disabled;