#![no_std]
#![doc = include_str!("../README.md")]
#![deny(clippy::undocumented_unsafe_blocks)]
use bitflags::bitflags;
pub use safe_mmio::UniqueMmioPointer;
use safe_mmio::{
field,
fields::{ReadPure, ReadPureWrite, WriteOnly},
};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
struct ControlRegister(u32);
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
struct Interrupts(u32);
bitflags! {
impl ControlRegister : u32 {
const RESEN = 1 << 1;
const INTEN = 1 << 0;
}
impl Interrupts : u32 {
const WDOGRIS = 1 << 0;
}
}
#[derive(Clone, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
#[repr(C, align(4))]
pub struct SP805Registers {
wdog_load: ReadPureWrite<u32>,
wdog_value: ReadPure<u32>,
wdog_control: ReadPureWrite<ControlRegister>,
wdog_intclr: WriteOnly<u32>,
wdog_ris: ReadPure<Interrupts>,
wdog_mis: ReadPure<Interrupts>,
reserved_18: [u32; 762],
wdog_lock: ReadPureWrite<u32>,
reserved_c04: [u32; 191],
wdog_itcr: ReadPureWrite<u32>,
wdog_itop: WriteOnly<u32>,
reserved_f08: [u32; 54],
wdog_periph_id0: ReadPure<u32>,
wdog_periph_id1: ReadPure<u32>,
wdog_periph_id2: ReadPure<u32>,
wdog_periph_id3: ReadPure<u32>,
wdog_pcell_id0: ReadPure<u32>,
wdog_pcell_id1: ReadPure<u32>,
wdog_pcell_id2: ReadPure<u32>,
wdog_pcell_id3: ReadPure<u32>,
}
pub struct Watchdog<'a> {
regs: UniqueMmioPointer<'a, SP805Registers>,
load_value: u32,
}
impl<'a> Watchdog<'a> {
const LOCK: u32 = 0x00000001;
const UNLOCK: u32 = 0x1ACCE551;
pub fn new(regs: UniqueMmioPointer<'a, SP805Registers>, load_value: u32) -> Self {
Self { regs, load_value }
}
pub fn enable(&mut self) {
let load_value = self.load_value;
self.with_unlock(|mut regs| {
field!(regs, wdog_load).write(load_value);
field!(regs, wdog_intclr).write(1);
field!(regs, wdog_control).write(ControlRegister::INTEN | ControlRegister::RESEN);
});
}
pub fn disable(&mut self) {
self.with_unlock(|mut regs| field!(regs, wdog_control).write(ControlRegister::empty()));
}
pub fn update(&mut self) {
let load_value = self.load_value;
self.with_unlock(|mut regs| field!(regs, wdog_load).write(load_value));
}
fn with_unlock<F>(&mut self, f: F)
where
F: FnOnce(&mut UniqueMmioPointer<SP805Registers>),
{
field!(self.regs, wdog_lock).write(Self::UNLOCK);
f(&mut self.regs);
field!(self.regs, wdog_lock).write(Self::LOCK);
}
}
#[cfg(test)]
mod tests {
use super::*;
use zerocopy::transmute_mut;
const LOAD_VALUE: u32 = 0xabcd_ef01;
#[repr(align(4096))]
pub struct FakeSp805Registers {
regs: [u32; 1024],
}
impl FakeSp805Registers {
pub fn new() -> Self {
Self { regs: [0u32; 1024] }
}
pub fn clear(&mut self) {
self.regs.fill(0);
}
pub fn reg_write(&mut self, offset: usize, value: u32) {
self.regs[offset / 4] = value;
}
pub fn reg_read(&self, offset: usize) -> u32 {
self.regs[offset / 4]
}
fn get(&mut self) -> UniqueMmioPointer<SP805Registers> {
UniqueMmioPointer::from(transmute_mut!(&mut self.regs))
}
pub fn system_for_test(&mut self) -> Watchdog {
Watchdog::new(self.get(), LOAD_VALUE)
}
}
#[test]
fn register_block_size() {
assert_eq!(0x1000, core::mem::size_of::<SP805Registers>());
}
#[test]
fn enable() {
let mut regs = FakeSp805Registers::new();
{
regs.reg_write(0x0c, 0xffff_ffff);
let mut wdt = regs.system_for_test();
wdt.enable();
}
assert_eq!(LOAD_VALUE, regs.reg_read(0x00));
assert_eq!(0x0000_0003, regs.reg_read(0x08));
assert!(regs.reg_read(0x0c) != 0);
assert_eq!(Watchdog::LOCK, regs.reg_read(0xc00));
regs.clear();
{
regs.reg_write(0x08, 0x0000_0003);
let mut wdt = regs.system_for_test();
wdt.disable();
}
assert_eq!(0x0000_0000, regs.reg_read(0x08));
assert_eq!(Watchdog::LOCK, regs.reg_read(0xc00));
regs.clear();
{
let mut wdt = regs.system_for_test();
wdt.update();
}
assert_eq!(LOAD_VALUE, regs.reg_read(0x00));
}
}