kea_hal/system/
watchdog.rs

1//! # WDOG - Watchdog Timer
2//!
3//! This peripheral runs from an independent timer, and resets the MCU when
4//! that timer overflows. Clearing the count value of this timer ensures that
5//! software does not leave the cpu stuck in an infinite loop or execute from
6//! unknown data.
7//!
8//! ** NOTE: ** The watchdog does not function well with the debugger active.
9//! writing to it is only successful in odd cases, with little effect.
10//! Attempting to unlock will trigger a reset. I pulled my hair out over a long
11//! weekend before I realized this. Yes, there is a DEBUG mode to set according
12//! the KEA64RM. It never works as expected.
13//!
14//! ## Usage
15//!
16//! ```rust
17//! #![no_main]
18//! #![no_std]
19//!
20//! use kea_hal as hal;
21//!
22//! use cortex_m_rt::entry;
23//! use hal::{pac, prelude::*, system};
24//! use panic_halt as _;
25//!
26//! #[entry]
27//! fn main() -> ! {
28//!     //println!("Hello, world!");
29//!     let _cp = cortex_m::Peripherals::take().unwrap();
30//!     let dp = pac::Peripherals::take().unwrap();
31//!
32//!     let watchdog = dp.WDOG.split();
33//!     let mut config = watchdog.configuration();
34//!     // Reset every 0xBEEF/32kHz seconds.
35//!     config.period = 0xBEEF;
36//!     config.clock = system::watchdog::WDogClock::IntRefClock;
37//!
38//!     // Trigger an interrupt before reset to log the error (or something).
39//!     config.interrupt = true;
40//!
41//!     // load new settings (watchdog will determine if needs to unlock or
42//!     // not)
43//!     let watchdog = watchdog.configure(config);
44//!
45//!     // Seal the watchdog so that it cannot be modified until reset
46//!     let watchdog.into_sealed();
47//! }
48//! ```
49//!
50//!
51//! ## Clock Sources
52//!
53//! * Bus Clock
54//! * 1kHz Clock
55//! * 32kHz Clock
56//! * Ext Clock
57//!
58//! ## Programmable Timeout Perioid
59//!
60//! 16 bit timeout value, with optional, fixed, 1/256 prescaler for longer
61//!    timeout periods.
62//!
63//! ## Servicing the Watchdog.
64//!
65//! Call the [Watchdog.service] method to reset the countdown. This is often
66//! known as petting the watchdog.
67//!
68//! Refresh write sequence: 0x2A6 and then 0x80B4 within 16 bus clocks.
69//!
70//! ## Windowed refresh
71//!
72//! Triggers a reset if refresh comes before expected. 16 bit programmable
73//! window value. "Provides robust check that program flow is faster than
74//! expected".
75//!
76//! *Implementer's Note*
77//!
78//! This seems useful for asm sequences, but it seems like writing in a high
79//! level language would make determining "how soon is too soon" rather
80//! difficult.
81//!
82//! ## Watchdog Interrupt
83//!
84//! Allows some post-processing to be done after the watchdog triggers, but
85//! before the reset. Reset happens 128 bus clocks after the interrupt vector
86//! is fetched.
87//!
88//! ## Configuration
89//!
90//! Configuration register fields are write-once after reset to prevent
91//! accidental modification. These fields can be unlocked for updates by
92//! writing 0x20C5 and then 0x28D9 (within 16 bus clocks of each other).
93//! Updates must be written within 128 bus clocks after unlocking.
94
95use crate::cortex_m::interrupt;
96use crate::{pac::WDOG, HALExt};
97use core::marker::PhantomData;
98
99#[inline(always)]
100fn unlock(_cs: &interrupt::CriticalSection) {
101    let peripheral = unsafe { &(*WDOG::ptr()) };
102    peripheral
103        .wdog_cnt()
104        .write(|w| unsafe { w.bits(0x20C5).bits(0x28D9) });
105}
106
107impl HALExt for WDOG {
108    type T = WatchDog<Enabled, Unlocked>;
109    fn split(self) -> WatchDog<Enabled, Unlocked> {
110        WatchDog {
111            _enable: PhantomData,
112            _update: PhantomData,
113            peripheral: self,
114        }
115    }
116}
117
118/// Enumeration of watchdog clocks
119#[derive(Clone, Debug)]
120#[repr(u8)]
121pub enum WDogClock {
122    /// Bus Clock.
123    ///
124    /// Note that using this clock disables the watchdog's backup reset
125    /// functionality. The Watchdog periphal uses the bus block internally to
126    /// operate. If the bus clock is lost and the WDogClock continues to
127    /// increment the counter (i.e. it's not also set to bus clock), after the
128    /// counter overflows twice the backup reset functionality kicks in to
129    /// reset the MCU.
130    BusClock = 0,
131    /// Internal 1kHz Low Power Oscillator
132    LpoClock = 1,
133    /// 32kHz Internal Reference Clock
134    IntRefClock = 2,
135    /// External Reference Clock
136    ExtRefClock = 3,
137}
138
139/// The Watchdog interface presented to the user.
140pub struct WatchDog<State, UpdateState> {
141    _enable: PhantomData<State>,
142    _update: PhantomData<UpdateState>,
143    peripheral: WDOG,
144}
145/// Holds watchdog configuration.
146///
147/// Generated by [WatchDog::configuration] and consumed by
148/// [WatchDog::configure].
149#[derive(Debug)]
150pub struct WDogConfig {
151    /// Watchdog Generates Intterupts
152    pub interrupt: bool,
153    /// Watchdog Operates in Debug mode
154    pub debug_mode: bool,
155    /// Watchdog Operates in Wait mode
156    pub wait_mode: bool,
157    /// Watchdog Operates in Stop mode
158    pub stop_mode: bool,
159    /// Windowed Watchdog Mode
160    pub windowed: bool,
161    /// Use watchdog clock prescaler (fixed 1:256)
162    pub prescale: bool,
163    /// Set the clock used by the watchdog
164    pub clock: WDogClock,
165    /// When counter >= period, reset.
166    pub period: u16,
167    /// Refresh window
168    ///
169    /// If windowed is set and the watchdog is serviced while counter <= window
170    /// reset. In other words, in windowed mode, the watchdog will trigger a
171    /// reset unless serviced when window < counter < period.
172    pub window: u16,
173}
174
175// This state is the on-reset state
176impl WatchDog<Enabled, Unlocked> {
177    /// Load a configuration and start the watchdog
178    ///
179    /// per KEA64RM 16.3.2 pg193-194, all registers except count must be
180    /// written to for configuration to take effect. The Window register may be
181    /// omited if not in windowed mode.
182    ///
183    /// Note: Configuring in an unlocked state with the debugger is attached
184    /// will have little useful effect.
185    pub fn configure(self, config: WDogConfig) -> WatchDog<Enabled, Locked> {
186        // The 16bit watchdog registers are in big-endian format
187        self.peripheral
188            .wdog_toval()
189            .write(|w| unsafe { w.bits(config.period.swap_bytes()) });
190        if config.windowed {
191            self.peripheral
192                .wdog_win()
193                .write(|w| unsafe { w.bits(config.window.swap_bytes()) });
194        }
195        self.peripheral.cs2.modify(|_, w| {
196            w.win()
197                .bit(config.windowed)
198                .pres()
199                .bit(config.prescale)
200                .clk()
201                .bits(config.clock.clone() as u8) // why does only this one move from config?
202        });
203        self.peripheral.cs1.modify(|_, w| {
204            w.int()
205                .bit(config.interrupt)
206                .dbg()
207                .bit(config.debug_mode)
208                .wait()
209                .bit(config.wait_mode)
210                .stop()
211                .bit(config.stop_mode)
212                .en() // Enable the Watchdog
213                ._1()
214                .update() // Allow to be updateable (locked, not sealed)
215                ._1()
216        });
217
218        WatchDog {
219            _enable: PhantomData,
220            _update: PhantomData,
221            peripheral: self.peripheral,
222        }
223    }
224
225    /// Disable the WatchDog
226    pub fn into_disabled(self) -> WatchDog<Disabled, Locked> {
227        // Write everything. CS1 last
228        self.peripheral
229            .wdog_toval()
230            .modify(|r, w| unsafe { w.bits(r.bits()) });
231
232        // update window register if window is set
233        if self.peripheral.cs2.read().win().bit() {
234            self.peripheral
235                .wdog_win()
236                .modify(|r, w| unsafe { w.bits(r.bits()) });
237        }
238        self.peripheral
239            .cs2
240            .modify(|r, w| unsafe { w.bits(r.bits()) });
241        self.peripheral
242            .cs1
243            .modify(|r, w| unsafe { w.bits(r.bits()).en()._0() });
244
245        WatchDog {
246            _enable: PhantomData,
247            _update: PhantomData,
248            peripheral: self.peripheral,
249        }
250    }
251}
252
253impl WatchDog<Enabled, Locked> {
254    /// Unlock and disable the WatchDog
255    pub fn into_disabled(self) -> WatchDog<Disabled, Locked> {
256        interrupt::free(|cs| {
257            unlock(cs);
258            WatchDog::<Enabled, Unlocked> {
259                _enable: PhantomData,
260                _update: PhantomData,
261                peripheral: self.peripheral,
262            }
263            .into_disabled()
264        })
265    }
266}
267
268impl<UpdateState> WatchDog<Enabled, UpdateState> {
269    /// Service the Watchdog
270    ///
271    /// Restart the countdown for MCU reset. This is often called petting,
272    /// feeding, or kicking the watchdog.
273    pub fn service(&self) {
274        interrupt::free(|_| {
275            self.peripheral
276                .wdog_cnt()
277                .write(|w| unsafe { w.cnt().bits(0x02A6) });
278            self.peripheral
279                .wdog_cnt()
280                .write(|w| unsafe { w.cnt().bits(0x80B4) });
281        });
282    }
283
284    /// Return the current value of the watchdog's counter
285    ///
286    /// This function swaps the bytes from the big endian registers to little
287    /// endian representation used.
288    pub fn counts(&self) -> u16 {
289        self.peripheral.wdog_cnt().read().bits().swap_bytes()
290    }
291}
292
293impl<State> WatchDog<State, Locked> {
294    /// Unlock, enable, and reconfigure
295    ///
296    /// Note: Configuring in a locked state with the debugger attached will
297    /// trigger an immediate reset.
298    pub fn configure(self, config: WDogConfig) -> WatchDog<Enabled, Locked> {
299        interrupt::free(|cs| {
300            unlock(cs);
301            WatchDog::<Enabled, Unlocked> {
302                _enable: PhantomData,
303                _update: PhantomData,
304                peripheral: self.peripheral,
305            }
306            .configure(config)
307        })
308    }
309
310    /// Seals the WatchDog peripheral.
311    ///
312    /// This prevents any further modifications to the watchdog, aside from
313    /// servicing as needed.
314    pub fn into_sealed(self) -> WatchDog<State, Sealed> {
315        interrupt::free(|cs| {
316            unlock(cs);
317
318            // Relock everything with current value, except unset update
319            self.peripheral
320                .wdog_toval()
321                .modify(|r, w| unsafe { w.bits(r.bits()) });
322            if self.peripheral.cs2.read().win().bit() {
323                self.peripheral
324                    .wdog_win()
325                    .modify(|r, w| unsafe { w.bits(r.bits()) });
326            }
327            self.peripheral
328                .cs2
329                .modify(|r, w| unsafe { w.bits(r.bits()) });
330
331            // update_A::_0 is what seals.
332            self.peripheral
333                .cs1
334                .modify(|r, w| unsafe { w.bits(r.bits()).update()._0() });
335        });
336        WatchDog {
337            _enable: PhantomData,
338            _update: PhantomData,
339            peripheral: self.peripheral,
340        }
341    }
342}
343
344impl<State, UpdateState> WatchDog<State, UpdateState> {
345    /// Returns a [WDogConfig] containing the current state of the peripheral
346    pub fn configuration(&self) -> WDogConfig {
347        WDogConfig {
348            interrupt: self.peripheral.cs1.read().int().bit(),
349            debug_mode: self.peripheral.cs1.read().dbg().bit(),
350            wait_mode: self.peripheral.cs1.read().wait().bit(),
351            stop_mode: self.peripheral.cs1.read().stop().bit(),
352            windowed: self.peripheral.cs2.read().win().bit(),
353            prescale: self.peripheral.cs2.read().pres().bit(),
354            clock: match self.peripheral.cs2.read().clk().bits() {
355                0 => WDogClock::BusClock,
356                1 => WDogClock::LpoClock,
357                2 => WDogClock::IntRefClock,
358                _ => WDogClock::ExtRefClock,
359            },
360            period: self.peripheral.wdog_toval().read().bits(),
361            window: self.peripheral.wdog_win().read().bits(),
362        }
363    }
364
365    /// Checks if the interrupt_ran.
366    ///
367    /// If acknowledge argument is true, then clear the interrupt flag if it is
368    /// set.
369    pub fn interrupt_ran(&self, acknowledge: bool) -> bool {
370        let ret_val: bool = self.peripheral.cs2.read().flg().bit();
371        if acknowledge && ret_val {
372            self.peripheral.cs2.modify(|_, w| w.flg()._1());
373        }
374        ret_val
375    }
376}
377
378/// Unlocked state, modifiable.
379pub struct Unlocked;
380/// Locked state, must unlock before modifying
381pub struct Locked;
382/// Locked and sealed state; Device must be reset to unlock.
383pub struct Sealed;
384
385/// Enabled state
386pub struct Enabled;
387/// Disabled state
388pub struct Disabled;