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;