sam4l/
wdt.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Implementation of the SAM4L hardware watchdog timer.
6
7use core::cell::Cell;
8
9use crate::pm::{self, Clock, PBDClock};
10
11use cortexm4::support;
12
13use kernel::utilities::math::log_base_two_u64;
14use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable};
15use kernel::utilities::registers::{
16    register_bitfields, FieldValue, ReadOnly, ReadWrite, WriteOnly,
17};
18use kernel::utilities::StaticRef;
19
20#[repr(C)]
21pub struct WdtRegisters {
22    cr: ReadWrite<u32, Control::Register>,
23    clr: WriteOnly<u32, Clear::Register>,
24    sr: ReadOnly<u32, Status::Register>,
25    ier: WriteOnly<u32, Interrupt::Register>,
26    idr: WriteOnly<u32, Interrupt::Register>,
27    imr: ReadOnly<u32, Interrupt::Register>,
28    isr: ReadOnly<u32, Interrupt::Register>,
29    icr: WriteOnly<u32, Interrupt::Register>,
30}
31
32register_bitfields![u32,
33    Control [
34        /// Write access key
35        KEY OFFSET(24) NUMBITS(8) [
36            KEY1 = 0x55,
37            KEY2 = 0xAA
38        ],
39        /// Time Ban Prescale Select
40        TBAN OFFSET(18) NUMBITS(5) [],
41        /// Clock Source Select
42        CSSEL OFFSET(17) NUMBITS(1) [
43            RCSYS = 0,
44            OSC32K = 1
45        ],
46        /// Clock Enable
47        CEN OFFSET(16) NUMBITS(1) [
48            ClockDisable = 0,
49            ClockEnable = 1
50        ],
51        /// Time Out Prescale Select
52        PSEL OFFSET(8) NUMBITS(5) [],
53        /// Flash Calibration Done
54        FCD OFFSET(7) NUMBITS(1) [
55            RedoCalibration = 0,
56            DoNotRedoCalibration = 1
57        ],
58        /// Interrupt Mode
59        IM OFFSET(4) NUMBITS(1) [
60            InterruptModeDisabled = 0,
61            InterruptModeEnabled = 1
62        ],
63        /// WDT Control Register Store Final Value
64        SFV OFFSET(3) NUMBITS(1) [
65            NotLocked = 0,
66            Locked = 1
67        ],
68        /// WDT Mode
69        MODE OFFSET(2) NUMBITS(1) [
70            Basic = 0,
71            Window = 1
72        ],
73        /// WDT Disable After Reset
74        DAR OFFSET(1) NUMBITS(1) [
75            EnableAfterReset = 0,
76            DisableAfterReset = 1
77        ],
78        /// WDT Enable
79        EN OFFSET(0) NUMBITS(1) [
80            Disable = 0,
81            Enable = 1
82        ]
83    ],
84
85    Clear [
86        /// Write access key
87        KEY OFFSET(24) NUMBITS(8) [
88            KEY1 = 0x55,
89            KEY2 = 0xAA
90        ],
91        /// Watchdog Clear
92        WDTCLR OFFSET(0) NUMBITS(1) []
93    ],
94
95    Status [
96        /// WDT Counter Cleared
97        CLEARED 1,
98        /// Within Window
99        WINDOW 0
100    ],
101
102    Interrupt [
103        WINT 2
104    ]
105];
106
107// Page 59 of SAM4L data sheet
108const WDT_BASE: *mut WdtRegisters = 0x400F0C00 as *mut WdtRegisters;
109const WDT_REGS: StaticRef<WdtRegisters> = unsafe { StaticRef::new(WDT_BASE.cast_const()) };
110
111pub struct Wdt {
112    enabled: Cell<bool>,
113}
114
115#[derive(Copy, Clone)]
116pub enum WdtClockSource {
117    ClockRCSys = 0,
118    ClockOsc32 = 1,
119}
120
121impl From<WdtClockSource> for FieldValue<u32, Control::Register> {
122    fn from(clock: WdtClockSource) -> Self {
123        match clock {
124            WdtClockSource::ClockRCSys => Control::CSSEL::RCSYS,
125            WdtClockSource::ClockOsc32 => Control::CSSEL::OSC32K,
126        }
127    }
128}
129
130impl Wdt {
131    pub const fn new() -> Wdt {
132        Wdt {
133            enabled: Cell::new(false),
134        }
135    }
136
137    /// WDT Errata: §45.1.3
138    ///
139    /// When writing any of the PSEL, TBAN, EN, or MODE fields, must insert a
140    /// delay for synchronization to complete.
141    ///
142    /// Also handle the KEY for the caller since we're special casing this.
143    fn write_cr(&self, control: FieldValue<u32, Control::Register>) {
144        WDT_REGS.cr.modify(Control::KEY::KEY1 + control);
145        WDT_REGS.cr.modify(Control::KEY::KEY2 + control);
146
147        // When writing to the affected fields, the user must ensure a wait
148        // corresponding to 2 clock cycles of both the WDT peripheral bus clock
149        // and the selected WDT clock source.
150        //
151        // TODO: Actual math based on chosen clock, ASF does:
152        //       delay = div_ceil(sysclk_hz(), OSC_[chosen]_NOMINAL_HZ)
153        for _ in 0..10000 {
154            support::nop();
155        }
156    }
157
158    fn select_clock(&self, clock: WdtClockSource) {
159        if !(WDT_REGS.cr.matches_all(From::from(clock))) {
160            let clock_enabled = WDT_REGS.cr.is_set(Control::CEN);
161
162            if clock_enabled {
163                // Disable WDT clock before modifying source
164                self.write_cr(Control::CEN::CLEAR);
165                while WDT_REGS.cr.is_set(Control::CEN) {}
166            }
167
168            // Select Clock
169            self.write_cr(From::from(clock));
170
171            if clock_enabled {
172                // Re-enable WDT clock after modifying source
173                self.write_cr(Control::CEN::SET);
174                while !WDT_REGS.cr.is_set(Control::CEN) {}
175            }
176        }
177    }
178
179    fn start(&self, period: usize) {
180        self.enabled.set(true);
181
182        pm::enable_clock(Clock::PBD(PBDClock::WDT));
183
184        // Note: Must use this clock to allow deep sleep. If you leave the
185        // default RCSYS, then the watchdog simply will not fire if you enter
186        // deep sleep (despite §20.4.1's protestations to the contrary).
187        // This is lower power anyway, so take the win.
188        self.select_clock(WdtClockSource::ClockOsc32);
189
190        // Choose the best period setting based on what was passed to `start()`
191        //
192        // §20.5.1.3 Configuring the WDT
193        //
194        // T_timeout = T_psel = 2^(PSEL + 1) / f_wdt_clk
195        //
196        // Period is in ms so use freq in khz for easy integer math
197        let f_clk_khz: u64 = if WDT_REGS.cr.matches_all(Control::CSSEL::RCSYS) {
198            115
199        } else {
200            // OSC32K
201            32
202        };
203        let mult: u64 = f_clk_khz * (period as u64);
204        let scaler = log_base_two_u64(mult); // prefer rounding for longer WD (thus no -1)
205
206        let control = Control::CEN::ClockEnable
207            + Control::PSEL.val(scaler)
208            + Control::FCD::DoNotRedoCalibration
209            + Control::DAR::DisableAfterReset
210            + Control::EN::Enable;
211        self.write_cr(control);
212    }
213
214    fn stop(&self) {
215        self.write_cr(Control::EN::CLEAR);
216
217        pm::disable_clock(Clock::PBD(PBDClock::WDT));
218
219        self.enabled.set(false);
220    }
221
222    fn tickle(&self) {
223        // Need to write the WDTCLR bit twice for it to work
224        WDT_REGS.clr.write(Clear::KEY::KEY1 + Clear::WDTCLR::SET);
225        WDT_REGS.clr.write(Clear::KEY::KEY2 + Clear::WDTCLR::SET);
226    }
227}
228
229impl kernel::platform::watchdog::WatchDog for Wdt {
230    fn setup(&self) {
231        // Setup the WatchDog with a 100ms period.
232        self.start(100);
233    }
234
235    fn tickle(&self) {
236        self.tickle();
237    }
238
239    fn suspend(&self) {
240        self.stop();
241    }
242}