Skip to main content

gd32e2_hal/watchdog/
fwdgt.rs

1//! Free watchdog timer.
2//!
3//! Clocked from IRC40K rather than a bus, so it keeps counting whatever the
4//! system clock does, and resets the chip unless it is fed before the counter
5//! reaches zero.
6//!
7//! Starting it is irreversible: the hardware has no way to stop it.
8
9use crate::pac;
10use crate::rcu::{IRC40K, Rcu};
11use crate::time::Duration;
12
13/// Largest reload value: `RLD` is 12 bits.
14const RLD_MAX: u16 = 0xFFF;
15/// Counter ticks one full reload spans, the reload value being counted from zero.
16const RLD_SPAN: u32 = RLD_MAX as u32 + 1;
17
18/// Divider on IRC40K ahead of the counter (Table 12-1).
19///
20/// Sets the timeout range together with the reload value: `/4` spans up to
21/// 409 ms, `/256` up to 26 s.
22#[derive(Clone, Copy, PartialEq, Eq)]
23#[cfg_attr(feature = "defmt", derive(defmt::Format))]
24#[allow(missing_docs)]
25pub enum FwdgtPsc {
26    Div4 = 0b000,
27    Div8 = 0b001,
28    Div16 = 0b010,
29    Div32 = 0b011,
30    Div64 = 0b100,
31    Div128 = 0b101,
32    Div256 = 0b110,
33}
34
35impl FwdgtPsc {
36    /// Every divider, smallest first — the order [`dividers`] searches in.
37    const ALL: [Self; 7] = [
38        Self::Div4,
39        Self::Div8,
40        Self::Div16,
41        Self::Div32,
42        Self::Div64,
43        Self::Div128,
44        Self::Div256,
45    ];
46
47    /// The divider itself, derived from the `PSC` code.
48    const fn divider(self) -> u32 {
49        4 << (self as u8)
50    }
51}
52
53/// Splits a timeout given in IRC40K ticks into the smallest divider that spans
54/// it and the reload value to go with it.
55///
56/// The smallest divider has the finest resolution, so the realised timeout sits
57/// as close under the requested one as the hardware allows. Past what the
58/// dividers reach (26 s) both saturate.
59fn dividers(ticks: u32) -> (FwdgtPsc, u16) {
60    let ticks = ticks.max(1);
61    for psc in FwdgtPsc::ALL {
62        let div = psc.divider();
63        if ticks <= RLD_SPAN * div {
64            return (psc, (ticks / div).saturating_sub(1) as u16);
65        }
66    }
67    (FwdgtPsc::Div256, RLD_MAX)
68}
69
70fn timeout_to_ticks<const NOM: u64, const DENOM: u64>(
71    timeout: Duration<u32, NOM, DENOM>,
72    clock: u32,
73) -> u32 {
74    let raw_time = u64::from(timeout.as_ticks());
75    let raw_freq = u64::from(clock);
76    (raw_time.saturating_mul(raw_freq).saturating_mul(NOM) / DENOM).min(u32::MAX.into()) as u32
77}
78
79/// The watchdog before it is started, the only state its period can be set in.
80pub struct Fwdgt {
81    fwdgt: pac::Fwdgt,
82}
83
84impl Fwdgt {
85    /// Sets the period and starts counting down; there is no way back.
86    ///
87    /// The timeout is `(rld + 1)` ticks of `IRC40K / psc`. Blocks while the
88    /// dividers reach the counter, which runs at 40 kHz.
89    ///
90    /// # Panics
91    ///
92    /// If `rld` exceeds 12 bits.
93    pub fn start(self, psc: FwdgtPsc, rld: u16) -> FwdgtRunning {
94        assert!(rld <= RLD_MAX, "FWDGT reload must fit in 12 bits");
95        self.fwdgt.ctl().write(|w| w.cmd().enable());
96        self.fwdgt.psc().write(|w| w.psc().bits(psc as u8));
97        self.fwdgt.rld().write(|w| w.rld().bits(rld));
98        while self.fwdgt.stat().read().pud().is_ongoing() {}
99        while self.fwdgt.stat().read().rud().is_ongoing() {}
100        self.fwdgt.ctl().write(|w| w.cmd().reset());
101        self.fwdgt.ctl().write(|w| w.cmd().start());
102        FwdgtRunning { fwdgt: self.fwdgt }
103    }
104
105    /// Same as [`start`](Self::start), but takes the period as a duration and
106    /// picks the dividers itself.
107    ///
108    /// Any scale works with no conversion at the call site — `2.secs()`,
109    /// `500.millis()`. Truncating, so the realised timeout is at most one tick
110    /// short of the request; anything past 26 s saturates there, and zero
111    /// becomes one tick.
112    pub fn start_timeout<const NOM: u64, const DENOM: u64>(
113        self,
114        timeout: Duration<u32, NOM, DENOM>,
115    ) -> FwdgtRunning {
116        let (psc, rld) = dividers(timeout_to_ticks(timeout, IRC40K));
117        self.start(psc, rld)
118    }
119}
120
121/// The watchdog once it is counting down.
122///
123/// No way out: the hardware cannot stop it, and neither the peripheral nor the
124/// period can be recovered.
125#[must_use = "the watchdog keeps running; without this value nothing can feed it"]
126pub struct FwdgtRunning {
127    fwdgt: pac::Fwdgt,
128}
129
130impl FwdgtRunning {
131    /// Reloads the counter, postponing the reset by one full period.
132    ///
133    /// The manual requires 7 or more IRC40K cycles (~175 µs) between two
134    /// reloads; nothing here enforces it.
135    pub fn feed(&mut self) {
136        self.fwdgt.ctl().write(|w| w.cmd().reset());
137    }
138}
139
140/// Entry point on the raw peripheral.
141pub trait FwdgtExt {
142    /// Starts IRC40K, which clocks the watchdog, and takes the peripheral.
143    ///
144    /// Nothing is written to the watchdog itself.
145    fn constrain(self, rcu: &mut Rcu) -> Fwdgt;
146}
147
148impl FwdgtExt for pac::Fwdgt {
149    fn constrain(self, rcu: &mut Rcu) -> Fwdgt {
150        rcu.enable_irc40k();
151        Fwdgt { fwdgt: self }
152    }
153}