Skip to main content

arm_sp805/
lib.rs

1// SPDX-FileCopyrightText: Copyright 2023-2025 Arm Limited and/or its affiliates <open-source-office@arm.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4#![no_std]
5#![doc = include_str!("../README.md")]
6#![deny(clippy::undocumented_unsafe_blocks)]
7
8//! ## Example
9//! ```rust
10//! use arm_sp805::{SP805Registers, Watchdog, UniqueMmioPointer};
11//! use core::ptr::NonNull;
12//! # use zerocopy::transmute_mut;
13//! # let mut fake_registers = [0u32; 1024];
14//! # let WATCHDOG_ADDRESS : *mut SP805Registers = transmute_mut!(&mut fake_registers);
15//! # fn handler() {}
16//!
17//! // SAFETY: `WATCHDOG_ADDRESS` is the base address of a SP805 watchdog register block. It remains
18//! // valid for the lifetime of the application and nothing else references this address range.
19//! let watchdog_pointer = unsafe { UniqueMmioPointer::new(NonNull::new(WATCHDOG_ADDRESS).unwrap()) };
20//!
21//! let mut watchdog = Watchdog::new(watchdog_pointer, 0x0001_0000);
22//! watchdog.enable();
23//!
24//! loop {
25//!   handler();
26//!   watchdog.update();
27//!   # break
28//! }
29//! ```
30
31use bitflags::bitflags;
32pub use safe_mmio::UniqueMmioPointer;
33use safe_mmio::{
34    field,
35    fields::{ReadPure, ReadPureWrite, WriteOnly},
36};
37use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
38
39/// Control Register
40#[repr(transparent)]
41#[derive(Copy, Clone, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
42struct ControlRegister(u32);
43
44/// Interrupt register.
45#[repr(transparent)]
46#[derive(Copy, Clone, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
47struct Interrupts(u32);
48
49bitflags! {
50    /// Control register
51    impl ControlRegister : u32 {
52        /// Enable Watchdog module reset output
53        const RESEN = 1 << 1;
54        /// Break error
55        const INTEN = 1 << 0;
56    }
57
58    /// Raw Interrupt Status Register
59    impl Interrupts : u32 {
60        /// Raw interrupt status from the counter
61        const WDOGRIS = 1 << 0;
62    }
63}
64
65/// SP805 Watchdog register map.
66#[derive(Clone, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
67#[repr(C, align(4))]
68pub struct SP805Registers {
69    /// 0x000 Load Register
70    wdog_load: ReadPureWrite<u32>,
71    /// 0x004 Value Register
72    wdog_value: ReadPure<u32>,
73    /// 0x008 Control register
74    wdog_control: ReadPureWrite<ControlRegister>,
75    /// 0x00c Interrupt Clear Register
76    wdog_intclr: WriteOnly<u32>,
77    /// 0x010 Raw Interrupt Status Register
78    wdog_ris: ReadPure<Interrupts>,
79    /// 0x014 Masked Interrupt Status Register
80    wdog_mis: ReadPure<Interrupts>,
81    /// 0x018 - 0xbfc
82    reserved_18: [u32; 762],
83    /// 0xc00 Lock Register
84    wdog_lock: ReadPureWrite<u32>,
85    /// 0xc04 - 0xefc
86    reserved_c04: [u32; 191],
87    /// 0xf00 Integration Test Control Register,
88    wdog_itcr: ReadPureWrite<u32>,
89    /// 0xf04 Integration Test Output Set
90    wdog_itop: WriteOnly<u32>,
91    /// 0xf08 - 0xfdc
92    reserved_f08: [u32; 54],
93    /// 0xfe0 Peripheral Identification Register 0
94    wdog_periph_id0: ReadPure<u32>,
95    /// 0xfe4 Peripheral Identification Register 1
96    wdog_periph_id1: ReadPure<u32>,
97    /// 0xfe8 Peripheral Identification Register 2
98    wdog_periph_id2: ReadPure<u32>,
99    /// 0xfec Peripheral Identification Register 3
100    wdog_periph_id3: ReadPure<u32>,
101    /// 0xff0 PrimeCell Identification Register 0
102    wdog_pcell_id0: ReadPure<u32>,
103    /// 0xff4 PrimeCell Identification Register 1
104    wdog_pcell_id1: ReadPure<u32>,
105    /// 0xff8 PrimeCell Identification Register 2
106    wdog_pcell_id2: ReadPure<u32>,
107    /// 0xffc PrimeCell Identification Register 3
108    wdog_pcell_id3: ReadPure<u32>,
109}
110
111/// SP805 Watchdog driver implementation.
112pub struct Watchdog<'a> {
113    regs: UniqueMmioPointer<'a, SP805Registers>,
114    load_value: u32,
115}
116
117impl<'a> Watchdog<'a> {
118    const LOCK: u32 = 0x00000001;
119    const UNLOCK: u32 = 0x1ACCE551;
120
121    /// Create new watchdog instance
122    pub fn new(regs: UniqueMmioPointer<'a, SP805Registers>, load_value: u32) -> Self {
123        Self { regs, load_value }
124    }
125
126    /// Enable watchdog
127    pub fn enable(&mut self) {
128        let load_value = self.load_value;
129
130        self.with_unlock(|mut regs| {
131            field!(regs, wdog_load).write(load_value);
132            field!(regs, wdog_intclr).write(1);
133            field!(regs, wdog_control).write(ControlRegister::INTEN | ControlRegister::RESEN);
134        });
135    }
136
137    /// Disable watchdog
138    pub fn disable(&mut self) {
139        self.with_unlock(|mut regs| field!(regs, wdog_control).write(ControlRegister::empty()));
140    }
141
142    /// Update watchdog
143    pub fn update(&mut self) {
144        let load_value = self.load_value;
145
146        self.with_unlock(|mut regs| field!(regs, wdog_load).write(load_value));
147    }
148
149    fn with_unlock<F>(&mut self, f: F)
150    where
151        F: FnOnce(&mut UniqueMmioPointer<SP805Registers>),
152    {
153        field!(self.regs, wdog_lock).write(Self::UNLOCK);
154        f(&mut self.regs);
155        field!(self.regs, wdog_lock).write(Self::LOCK);
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use zerocopy::transmute_mut;
163
164    const LOAD_VALUE: u32 = 0xabcd_ef01;
165
166    #[repr(align(4096))]
167    pub struct FakeSp805Registers {
168        regs: [u32; 1024],
169    }
170
171    impl FakeSp805Registers {
172        pub fn new() -> Self {
173            Self { regs: [0u32; 1024] }
174        }
175
176        pub fn clear(&mut self) {
177            self.regs.fill(0);
178        }
179
180        pub fn reg_write(&mut self, offset: usize, value: u32) {
181            self.regs[offset / 4] = value;
182        }
183
184        pub fn reg_read(&self, offset: usize) -> u32 {
185            self.regs[offset / 4]
186        }
187
188        fn get(&mut self) -> UniqueMmioPointer<'_, SP805Registers> {
189            UniqueMmioPointer::from(transmute_mut!(&mut self.regs))
190        }
191
192        pub fn system_for_test(&mut self) -> Watchdog<'_> {
193            Watchdog::new(self.get(), LOAD_VALUE)
194        }
195    }
196
197    #[test]
198    fn register_block_size() {
199        assert_eq!(0x1000, core::mem::size_of::<SP805Registers>());
200    }
201
202    #[test]
203    fn enable() {
204        let mut regs = FakeSp805Registers::new();
205
206        {
207            // Enable
208            regs.reg_write(0x0c, 0xffff_ffff);
209            let mut wdt = regs.system_for_test();
210            wdt.enable();
211        }
212
213        assert_eq!(LOAD_VALUE, regs.reg_read(0x00));
214        assert_eq!(0x0000_0003, regs.reg_read(0x08));
215        assert!(regs.reg_read(0x0c) != 0);
216        assert_eq!(Watchdog::LOCK, regs.reg_read(0xc00));
217
218        regs.clear();
219
220        {
221            // Disable
222            regs.reg_write(0x08, 0x0000_0003);
223            let mut wdt = regs.system_for_test();
224            wdt.disable();
225        }
226
227        assert_eq!(0x0000_0000, regs.reg_read(0x08));
228        assert_eq!(Watchdog::LOCK, regs.reg_read(0xc00));
229
230        regs.clear();
231
232        {
233            // Update
234            let mut wdt = regs.system_for_test();
235            wdt.update();
236        }
237
238        assert_eq!(LOAD_VALUE, regs.reg_read(0x00));
239    }
240}