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}