lowrisc/
aon_timer.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//! AON/Watchdog Timer Driver
6
7use kernel::platform;
8use kernel::utilities::registers::interfaces::{Readable, Writeable};
9use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite, WriteOnly};
10use kernel::utilities::StaticRef;
11
12// Based on the latest commit of OpenTitan supported by tock:
13// Refer: https://github.com/lowRISC/opentitan/blob/217a0168ba118503c166a9587819e3811eeb0c0c/hw/ip/aon_timer/rtl/aon_timer_reg_pkg.sv#L136
14register_structs! {
15    pub AonTimerRegisters {
16        //AON_TIMER: Alert Test Register
17        (0x000 => alert_test: WriteOnly<u32, ALERT_TEST::Register>),
18        //AON_TIMER: Wakeup Timer Control Register
19        (0x004 => wkup_ctrl: ReadWrite<u32, WKUP_CTRL::Register>),
20        //AON_TIMER: Wakeup Timer Threshold Register
21        (0x008 => wkup_thold: ReadWrite<u32, THRESHOLD::Register>),
22        //AON_TIMER: Wakeup Timer Count Register
23        (0x00C => wkup_count: ReadWrite<u32, WKUP_COUNT::Register>),
24        //AON_TIMER:  Watchdog Timer Write Enable Register [rw0c]
25        (0x010 => wdog_regwen: ReadWrite<u32, WDOG_REGWEN::Register>),
26        //AON_TIMER: Watchdog Timer Control register
27        (0x014 => wdog_ctrl: ReadWrite<u32, WDOG_CTRL::Register>),
28        //AON_TIMER: Watchdog Timer Bark Threshold Register
29        (0x018 => wdog_bark_thold: ReadWrite<u32, THRESHOLD::Register>),
30        //AON_TIMER: Watchdog Timer Bite Threshold Register
31        (0x01C => wdog_bite_thold: ReadWrite<u32, THRESHOLD::Register>),
32        //AON_TIMER: Watchdog Timer Count Register
33        (0x020 => wdog_count: ReadWrite<u32, WDOG_COUNT::Register>),
34        //AON_TIMER: Interrupt State Register [rw1c]
35        (0x024 => intr_state: ReadWrite<u32, INTR::Register>),
36        //AON_TIMER: Interrupt Test Reigster
37        (0x028 => intr_test: WriteOnly<u32, INTR::Register>),
38        //AON_TIMER: Wakeup Request Status [rw0c]
39        (0x02C => wkup_cause: ReadWrite<u32, WKUP_CAUSE::Register>),
40        (0x030 => @END),
41    }
42}
43
44register_bitfields![u32,
45    ALERT_TEST[
46        FATAL_FAULT OFFSET(0) NUMBITS(1) []
47    ],
48    WKUP_CTRL[
49        ENABLE OFFSET(0) NUMBITS(1) [],
50        PRESCALER OFFSET(1) NUMBITS(12) []
51    ],
52    THRESHOLD[
53        THRESHOLD OFFSET(0) NUMBITS(32) []
54    ],
55    WKUP_COUNT[
56        COUNT OFFSET(0) NUMBITS(32) []
57    ],
58    WDOG_REGWEN[
59        REGWEN OFFSET(0) NUMBITS(1) []
60    ],
61    WDOG_CTRL[
62        ENABLE OFFSET(0) NUMBITS(1) [],
63        PAUSE_IN_SLEEP OFFSET(1) NUMBITS(1) []
64    ],
65    WDOG_COUNT[
66        COUNT OFFSET(0) NUMBITS(32) [],
67    ],
68    INTR[
69        WKUP_TIMER_EXPIRED OFFSET(0) NUMBITS(1) [],
70        WDOG_TIMER_BARK OFFSET(1) NUMBITS(1) []
71    ],
72    WKUP_CAUSE[
73        CAUSE OFFSET(0) NUMBITS(1) [],
74    ]
75];
76
77pub struct AonTimer {
78    registers: StaticRef<AonTimerRegisters>,
79    aon_clk_freq: u32, //Hz, this differs for FPGA/Verilator
80}
81
82impl AonTimer {
83    pub const fn new(base: StaticRef<AonTimerRegisters>, aon_clk_freq: u32) -> AonTimer {
84        AonTimer {
85            registers: base,
86            aon_clk_freq,
87        }
88    }
89
90    /// Reset both watch dog and wake up timer count values.
91    fn reset_timers(&self) {
92        let regs = self.registers;
93        regs.wkup_count.set(0x00);
94        regs.wdog_count.set(0x00);
95    }
96
97    /// Start the watchdog counter with pause in sleep
98    /// i.e wdog timer is paused when system is sleeping
99    fn wdog_start_count(&self) {
100        self.registers
101            .wdog_ctrl
102            .write(WDOG_CTRL::ENABLE::SET + WDOG_CTRL::PAUSE_IN_SLEEP::SET);
103    }
104
105    /// Program the desired thresholds in WKUP_THOLD, WDOG_BARK_THOLD and WDOG_BITE_THOLD
106    fn set_wdog_thresh(&self) {
107        let regs = self.registers;
108        // Watchdog period may need to be revised with kernel changes/updates
109        // since the watchdog is `tickled()` at the start of every kernel loop
110        // see: https://github.com/tock/tock/blob/eb3f7ce59434b7ac1b77ef1ab7dd2afad1a62ac5/kernel/src/kernel.rs#L448
111        let bark_cycles = self.ms_to_cycles(500);
112        // ~1000ms bite period
113        let bite_cycles = bark_cycles.saturating_mul(2);
114
115        regs.wdog_bark_thold
116            .write(THRESHOLD::THRESHOLD.val(bark_cycles));
117        regs.wdog_bite_thold
118            .write(THRESHOLD::THRESHOLD.val(bite_cycles));
119    }
120
121    // Reset watch dog timer
122    fn wdog_pet(&self) {
123        self.registers.wdog_count.set(0x00);
124    }
125
126    fn wdog_suspend(&self) {
127        self.registers.wdog_ctrl.write(WDOG_CTRL::ENABLE::CLEAR);
128    }
129
130    fn wdog_resume(&self) {
131        self.registers.wdog_ctrl.write(WDOG_CTRL::ENABLE::SET);
132    }
133
134    /// Locks further config to WDOG until next system reset
135    fn lock_wdog_conf(&self) {
136        self.registers.wdog_regwen.write(WDOG_REGWEN::REGWEN::SET)
137    }
138
139    /// Convert microseconds to cycles
140    fn ms_to_cycles(&self, ms: u32) -> u32 {
141        // 250kHZ CW130 or 125kHz Verilator (as specified in chip config)
142        ms.saturating_mul(self.aon_clk_freq).saturating_div(1000)
143    }
144
145    fn reset_wkup_count(&self) {
146        self.registers.wkup_count.set(0x00);
147    }
148
149    pub fn handle_interrupt(&self) {
150        let regs = self.registers;
151        let intr = self.registers.intr_state.extract();
152
153        if intr.is_set(INTR::WKUP_TIMER_EXPIRED) {
154            // Wake up timer has expired, sw must ack and clear
155            regs.wkup_cause.set(0x00);
156            regs.wkup_count.set(0x00); // To avoid re-triggers
157            self.reset_wkup_count();
158            // RW1C, clear the interrupt
159            regs.intr_state.write(INTR::WKUP_TIMER_EXPIRED::SET);
160        }
161
162        if intr.is_set(INTR::WDOG_TIMER_BARK) {
163            // Clear the bark (RW1C) and pet doggo
164            regs.intr_state.write(INTR::WDOG_TIMER_BARK::SET);
165            self.wdog_pet();
166        }
167    }
168}
169
170impl platform::watchdog::WatchDog for AonTimer {
171    /// The always-on timer will run on a ~125KHz (Verilator) or ~250kHz clock.
172    /// The timers themselves are 32b wide, giving a maximum timeout
173    /// window of roughly ~6 hours. For the wakeup timer, the pre-scaler
174    /// extends the maximum timeout to ~1000 days.
175    ///
176    /// The AON HW_IP has a watchdog and a wake-up timer (counts independantly of eachother),
177    /// although struct `AonTimer` implements the wakeup timer functionality,
178    /// we only start and use the watchdog in the code below.
179    fn setup(&self) {
180        // 1. Clear Timers
181        self.reset_timers();
182
183        // 2. Set thresholds.
184        self.set_wdog_thresh();
185
186        // 3. Commence gaurd duty...
187        self.wdog_start_count();
188
189        // 4. Lock watchdog config
190        // Preventing firmware from accidentally or maliciously disabling the watchdog,
191        // until next system reset.
192        self.lock_wdog_conf();
193    }
194
195    fn tickle(&self) {
196        // Nothing to worry about, good dog...
197        self.wdog_pet();
198    }
199
200    fn suspend(&self) {
201        self.wdog_suspend();
202    }
203
204    fn resume(&self) {
205        self.wdog_resume();
206    }
207}