Skip to main content

ra_hal/watchdog/
wdt.rs

1//! Watchdog Timer (`WDT`) §25.
2
3use core::marker::PhantomData;
4
5use cortex_m::asm;
6use embassy_hal_internal::{Peri, PeripheralType, interrupt::InterruptExt as _};
7
8use crate::{
9    event_link::{IcuInterrupt as _, InterruptEvent},
10    interrupt::{
11        self,
12        typelevel::{Handler as InterruptHandler, Interrupt as InterruptType},
13    },
14    pac::{
15        self,
16        dbg::vals::DbgstopWdt,
17        osm::vals::StartMode,
18        wdt::vals::{Cks, Rpes, Rpss, Tops},
19    },
20    watchdog::Action,
21};
22
23/// `PCLKB` divider used to decrement the watchdog timer.
24#[cfg_attr(feature = "defmt", derive(defmt::Format))]
25#[derive(Copy, Clone)]
26pub enum ClockDivider {
27    /// `PCLKB/4`
28    Div4,
29
30    /// `PCLKB/64`
31    Div64,
32
33    /// `PCLKB/128`
34    Div128,
35
36    /// `PCLKB/512`
37    Div512,
38
39    /// `PCLKB/2048`
40    Div2048,
41
42    /// `PCLKB/8192`
43    Div8192,
44}
45
46/// Watchdog configuration.
47///
48/// # TODO
49/// Handle the reset window settings.
50#[cfg_attr(feature = "defmt", derive(defmt::Format))]
51#[non_exhaustive]
52pub struct Config {
53    #[allow(missing_docs)]
54    pub action: Action,
55    #[allow(missing_docs)]
56    pub divider: ClockDivider,
57    #[allow(missing_docs)]
58    pub period: TimeoutPeriod,
59}
60
61/// Reset / refresh value for watchdog timer.
62#[cfg_attr(feature = "defmt", derive(defmt::Format))]
63#[derive(Copy, Clone)]
64pub enum TimeoutPeriod {
65    /// `CNTVAL` = 1024
66    _1024,
67
68    /// `CNTVAL` = 4096
69    _4096,
70
71    /// `CNTVAL` = 8192
72    _8192,
73
74    /// `CNTVAL` = 16,384
75    _16384,
76}
77
78/// Watchdog driver.
79pub struct Watchdog<'d, I: Instance> {
80    phantom: PhantomData<&'d I>,
81}
82
83/// Interrupt handler for the watchdog timer, used with [`Watchdog::new_handler`].
84pub struct WdtInterruptHandler<I: Instance> {
85    _phantom: PhantomData<I>,
86}
87
88/// Watchdog instance.
89#[allow(private_bounds)]
90pub trait Instance: SealedInstance + PeripheralType {}
91
92pub(crate) trait SealedInstance {
93    fn regs() -> pac::wdt::Wdt;
94}
95
96impl From<ClockDivider> for u16 {
97    #[inline]
98    fn from(value: ClockDivider) -> Self {
99        match value {
100            ClockDivider::Div4 => 4,
101            ClockDivider::Div64 => 64,
102            ClockDivider::Div128 => 128,
103            ClockDivider::Div512 => 512,
104            ClockDivider::Div2048 => 2048,
105            ClockDivider::Div8192 => 8192,
106        }
107    }
108}
109
110impl From<ClockDivider> for Cks {
111    #[inline]
112    fn from(value: ClockDivider) -> Self {
113        match value {
114            ClockDivider::Div4 => Cks::Pclkb4,
115            ClockDivider::Div64 => Cks::Pclkb64,
116            ClockDivider::Div128 => Cks::Pclkb128,
117            ClockDivider::Div512 => Cks::Pclkb512,
118            ClockDivider::Div2048 => Cks::Pclkb2048,
119            ClockDivider::Div8192 => Cks::Pclkb8192,
120        }
121    }
122}
123
124impl From<TimeoutPeriod> for Tops {
125    #[inline]
126    fn from(value: TimeoutPeriod) -> Self {
127        match value {
128            TimeoutPeriod::_1024 => Self::_1024Cycles,
129            TimeoutPeriod::_4096 => Self::_4096Cycles,
130            TimeoutPeriod::_8192 => Self::_8192Cycles,
131            TimeoutPeriod::_16384 => Self::_16384Cycles,
132        }
133    }
134}
135
136impl From<TimeoutPeriod> for u16 {
137    #[inline]
138    fn from(value: TimeoutPeriod) -> Self {
139        match value {
140            TimeoutPeriod::_1024 => 1024,
141            TimeoutPeriod::_4096 => 4096,
142            TimeoutPeriod::_8192 => 8192,
143            TimeoutPeriod::_16384 => 16384,
144        }
145    }
146}
147
148impl Default for Config {
149    /// ≈2.8 s, ƒPCLKB = 24 MHz
150    fn default() -> Self {
151        Self {
152            divider: ClockDivider::Div8192,
153            period: TimeoutPeriod::_8192,
154            action: Action::Interrupt,
155        }
156    }
157}
158
159impl<'d, I: Instance> Watchdog<'d, I> {
160    fn new_inner(peri: Peri<'d, I>, config: Config) -> Self {
161        let _ = peri;
162
163        {
164            let osm = pac::OSM;
165            let ofs0 = osm.ofs0().read();
166
167            if ofs0.wdtstrt() == StartMode::AutoStart {
168                debug!("WDT: Auto-start mode selected, timing changes will be ignored.");
169            }
170        }
171
172        let mut reset = false;
173        match config.action {
174            Action::Interrupt => {
175                debug!("WDT: Enabling NMI");
176
177                cfg_select! {
178                    not(ra8m1) => {
179                        let icu = pac::ICU;
180
181                        icu.nmicr().write(|r| r.set_nflten(false));
182                        icu.nmicr().write(|r| r.set_nmimd(true));
183                        icu.nmiclr().write(|r| r.set_nmiclr(true));
184                        icu.nmier().write(|r| r.set_wdten(true));
185                    },
186                    ra8m1 => {
187                        let icu = pac::ICU;
188                        let icu_common = pac::ICU_COMMON;
189
190                        icu_common.nmicr().write(|r| r.set_nflten(false));
191                        icu_common.nmicr().write(|r| r.set_nmimd(true));
192                        icu.nmiclr().write(|r| r.set_nmiclr(true));
193                        icu.nmier().write(|r| r.set_wdten(true));
194                    },
195                }
196            }
197            Action::Reset => {
198                reset = true;
199            }
200            Action::Handler => {}
201        }
202
203        // The timer will never run in debug mode while execution is stopped.
204        // This just enables the timer when running in debug mode.
205        let dbg = pac::DBG;
206        dbg.dbgstopcr()
207            .write(|r| r.set_dbgstop_wdt(DbgstopWdt::Enable));
208
209        let wdt = I::regs();
210
211        wdt.wdtcr().write(|r| {
212            r.set_tops(config.period.into());
213            r.set_cks(config.divider.into());
214            r.set_rpes(Rpes::None);
215            r.set_rpss(Rpss::None);
216        });
217
218        {
219            let config = wdt.wdtcr().read();
220            let clocks = crate::clock::clock_status();
221            let divider = match config.cks() {
222                Cks::Pclkb4 => 4.0,
223                Cks::Pclkb64 => 64.0,
224                Cks::Pclkb128 => 128.0,
225                Cks::Pclkb512 => 512.0,
226                Cks::Pclkb2048 => 2048.0,
227                Cks::Pclkb8192 => 8192.0,
228                _ => unreachable!(),
229            };
230            let period = match config.tops() {
231                Tops::_1024Cycles => 1024.0,
232                Tops::_4096Cycles => 4096.0,
233                Tops::_8192Cycles => 8192.0,
234                Tops::_16384Cycles => 16384.0,
235            };
236
237            let timeout = period / ((clocks.peripheral_b.to_Hz() as f32) / divider);
238            debug!("WDT: timeout: {} sec", timeout);
239        }
240
241        wdt.wdtrcr().write(|r| r.set_rstirqs(reset));
242        wdt.wdtcstpr().write(|r| r.set_slcstp(false));
243
244        let mut this = Self {
245            phantom: PhantomData,
246        };
247
248        this.refresh();
249
250        this
251    }
252
253    /// Configures and enables the watchdog timer.
254    ///
255    /// # Returns
256    /// - A new driver instance.
257    /// # Notes
258    /// - If configured to generate an NMI, by default `cortex-m-rt` provides a handler that silently halts execution.
259    /// - If `OFS0` is configured to auto-start `WDT` runtime changes to configuration will be silently ignored.
260    #[inline]
261    pub fn new(peri: Peri<'d, I>, config: Config) -> Self {
262        Self::new_inner(peri, config)
263    }
264
265    /// Configures the watchdog to trigger our handler. Utility of this TBD.
266    ///
267    /// - Use for performance counting metrics?
268    /// - Allow user defined watchdog handlers?
269    #[inline]
270    pub fn new_handler<WdtInt: InterruptType>(
271        peri: Peri<'d, I>,
272        config: Config,
273        irq: impl interrupt::typelevel::Binding<WdtInt, WdtInterruptHandler<I>>,
274    ) -> Self {
275        let _ = peri;
276        let _ = irq;
277
278        assert_eq!(config.action, Action::Handler);
279
280        // Safety: The interrupt handler is defined by the irq argument and thus the interrupt is safe to enable.
281        unsafe {
282            WdtInt::IRQ.enable();
283            WdtInt::IRQ.icu_enable(InterruptEvent::WdtUnderflow);
284        }
285
286        Self::new_inner(peri, config)
287    }
288
289    /// Refreshes the `WDT` counter to prevent a timeout.
290    #[inline]
291    pub fn refresh(&mut self) {
292        let wdt = I::regs();
293
294        asm::dsb();
295        wdt.wdtrr().write_value(0x00);
296        asm::dsb();
297        wdt.wdtrr().write_value(0xFF);
298    }
299
300    /// Returns the current value of the `WDT` counter.
301    #[inline]
302    pub fn value(&self) -> u16 {
303        let wdt = I::regs();
304        let status = wdt.wdtsr().read();
305
306        status.cntval()
307    }
308}
309
310impl Instance for crate::peripherals::WDT {}
311
312impl SealedInstance for crate::peripherals::WDT {
313    #[inline(always)]
314    fn regs() -> pac::wdt::Wdt {
315        pac::WDT
316    }
317}
318
319impl<I: Instance, WdtInt: InterruptType> InterruptHandler<WdtInt> for WdtInterruptHandler<I> {
320    unsafe fn on_interrupt() {
321        WdtInt::IRQ.icu_unpend();
322
323        panic!("Watchdog underflow");
324    }
325}