use crate::peripherals::Rtc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RtcMode {
FreeRunning,
Periodic,
}
pub struct RtcDriver<'d> {
_rtc: Rtc<'d>,
}
pub const RTC_CLOCK_HZ: u32 = 32_768;
const CTRL_ENABLE: u32 = 0x01;
const CTRL_PERIODIC: u32 = 1 << 1;
const CTRL_INT_MASK: u32 = 1 << 2;
const fn control_bits(mode: RtcMode) -> u32 {
let mut ctrl = CTRL_ENABLE;
if matches!(mode, RtcMode::Periodic) {
ctrl |= CTRL_PERIODIC;
}
ctrl |= CTRL_INT_MASK;
ctrl
}
impl<'d> RtcDriver<'d> {
pub fn new(rtc: Rtc<'d>) -> Self {
Self { _rtc: rtc }
}
fn regs(&self) -> &'static crate::soc::pac::rtc::RegisterBlock {
unsafe { &*Rtc::ptr() }
}
pub fn configure(&mut self, mode: RtcMode, load_value: u32) {
unsafe {
self.regs().rtc_load_count().write(|w| w.bits(load_value));
}
let ctrl = control_bits(mode);
unsafe {
self.regs().rtc_control().write(|w| w.bits(ctrl));
}
}
pub fn enable(&mut self) {
let ctrl = self.regs().rtc_control().read().bits();
unsafe {
self.regs().rtc_control().write(|w| w.bits(ctrl | 0x01));
}
}
pub fn disable(&mut self) {
let ctrl = self.regs().rtc_control().read().bits();
unsafe {
self.regs().rtc_control().write(|w| w.bits(ctrl & !0x01));
}
}
pub fn set_load(&mut self, load_value: u32) {
unsafe {
self.regs().rtc_load_count().write(|w| w.bits(load_value));
}
}
pub fn current_value(&self) -> u32 {
self.regs().rtc_current_value().read().bits()
}
pub fn enable_interrupt(&mut self) {
let ctrl = self.regs().rtc_control().read().bits();
unsafe {
self.regs().rtc_control().write(|w| w.bits(ctrl & !(1 << 2)));
}
}
pub fn disable_interrupt(&mut self) {
let ctrl = self.regs().rtc_control().read().bits();
unsafe {
self.regs().rtc_control().write(|w| w.bits(ctrl | (1 << 2)));
}
}
pub fn interrupt_pending(&self) -> bool {
self.regs().rtc_int_status().read().bits() & 0x01 != 0
}
pub fn clear_interrupt(&self) {
let _ = self.regs().rtc_eoi().read().bits();
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod tests {
use super::*;
#[test]
fn free_running_control_word() {
let ctrl = control_bits(RtcMode::FreeRunning);
assert_eq!(ctrl, CTRL_ENABLE | CTRL_INT_MASK);
assert_eq!(ctrl, 0b101);
}
#[test]
fn periodic_control_word() {
let ctrl = control_bits(RtcMode::Periodic);
assert_eq!(ctrl, CTRL_ENABLE | CTRL_PERIODIC | CTRL_INT_MASK);
assert_eq!(ctrl, 0b111);
}
#[test]
fn periodic_bit_is_the_only_difference() {
let free = control_bits(RtcMode::FreeRunning);
let periodic = control_bits(RtcMode::Periodic);
assert_eq!(free ^ periodic, CTRL_PERIODIC);
}
#[test]
fn enable_always_set_and_interrupt_starts_masked() {
for mode in [RtcMode::FreeRunning, RtcMode::Periodic] {
let ctrl = control_bits(mode);
assert_ne!(ctrl & CTRL_ENABLE, 0, "enable bit must be set");
assert_ne!(ctrl & CTRL_INT_MASK, 0, "interrupt must start masked");
}
}
#[test]
fn control_bits_use_distinct_nonoverlapping_flags() {
assert_eq!(CTRL_ENABLE & CTRL_PERIODIC, 0);
assert_eq!(CTRL_ENABLE & CTRL_INT_MASK, 0);
assert_eq!(CTRL_PERIODIC & CTRL_INT_MASK, 0);
assert_eq!((CTRL_ENABLE | CTRL_PERIODIC | CTRL_INT_MASK) & !0b111, 0);
}
#[test]
fn enable_disable_mask_round_trip() {
let base = control_bits(RtcMode::Periodic);
assert_eq!((base & !CTRL_ENABLE) | CTRL_ENABLE, base);
assert_eq!((base & !CTRL_INT_MASK) | CTRL_INT_MASK, base);
assert_eq!(base | CTRL_ENABLE, base);
}
#[test]
fn rtc_clock_is_32_768_khz() {
assert_eq!(RTC_CLOCK_HZ, 32_768);
assert_eq!(RTC_CLOCK_HZ, 1 << 15);
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod proptests {
use super::{CTRL_ENABLE, CTRL_INT_MASK, CTRL_PERIODIC, RtcMode, control_bits};
use proptest::prelude::*;
fn any_mode() -> impl Strategy<Value = RtcMode> {
prop_oneof![Just(RtcMode::FreeRunning), Just(RtcMode::Periodic)]
}
proptest! {
#[test]
fn control_word_has_no_stray_bits(mode in any_mode()) {
let ctrl = control_bits(mode);
prop_assert_eq!(ctrl & !(CTRL_ENABLE | CTRL_PERIODIC | CTRL_INT_MASK), 0);
}
#[test]
fn enable_and_mask_always_set(mode in any_mode()) {
let ctrl = control_bits(mode);
prop_assert_ne!(ctrl & CTRL_ENABLE, 0);
prop_assert_ne!(ctrl & CTRL_INT_MASK, 0);
}
#[test]
fn periodic_bit_tracks_mode(mode in any_mode()) {
let ctrl = control_bits(mode);
let want_periodic = matches!(mode, RtcMode::Periodic);
prop_assert_eq!(ctrl & CTRL_PERIODIC != 0, want_periodic);
prop_assert_eq!(ctrl & !CTRL_PERIODIC, CTRL_ENABLE | CTRL_INT_MASK);
}
#[test]
fn control_bits_is_deterministic(mode in any_mode()) {
prop_assert_eq!(control_bits(mode), control_bits(mode));
}
#[test]
fn flag_clear_then_set_round_trips(mode in any_mode(), bits in any::<u32>()) {
let base = control_bits(mode) | bits;
for flag in [CTRL_ENABLE, CTRL_PERIODIC, CTRL_INT_MASK] {
let had = base & flag != 0;
if had {
prop_assert_eq!((base & !flag) | flag, base);
} else {
prop_assert_eq!((base | flag) & !flag, base);
}
}
}
#[test]
fn flag_ops_are_idempotent(bits in any::<u32>()) {
for flag in [CTRL_ENABLE, CTRL_PERIODIC, CTRL_INT_MASK] {
let set = bits | flag;
prop_assert_eq!(set | flag, set);
let cleared = bits & !flag;
prop_assert_eq!(cleared & !flag, cleared);
}
}
}
}