Skip to main content

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