//! # OSTIMER Driver with Robustness Features //! //! This module provides an async timer driver for the NXP MCXA276 OSTIMER peripheral //! with protection against race conditions and timer rollover issues. //! //! ## Features //! //! - Async timing with embassy-time integration //! - Gray code counter handling (42-bit counter) //! - Interrupt-driven wakeups //! - Configurable interrupt priority //! - **Race condition protection**: Critical sections and atomic operations //! - **Timer rollover handling**: Bounds checking and rollover prevention //! //! ## Clock Frequency Configuration //! //! The OSTIMER frequency depends on your system's clock configuration. You must provide //! the actual frequency when calling `time_driver::init()`. //! //! ## Race Condition Protection //! - Critical sections in interrupt handlers prevent concurrent access //! - Atomic register operations with memory barriers //! - Proper interrupt flag clearing and validation //! //! ## Timer Rollover Handling //! - Bounds checking prevents scheduling beyond timer capacity //! - Immediate wake for timestamps that would cause rollover issues #![allow(dead_code)] use core::sync::atomic::{AtomicBool, Ordering}; use embassy_hal_internal::{Peri, PeripheralType}; use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; use crate::clocks::{Gate, PoweredClock, assert_reset, enable_and_reset, is_reset_released, release_reset}; use crate::interrupt::InterruptExt; use crate::pac; // PAC defines the shared RegisterBlock under `ostimer0`. type Regs = pac::ostimer0::RegisterBlock; // OSTIMER EVTIMER register layout constants /// Total width of the EVTIMER counter in bits (42 bits total) const EVTIMER_TOTAL_BITS: u32 = 42; /// Width of the low part of EVTIMER (bits 31:0) const EVTIMER_LO_BITS: u32 = 32; /// Width of the high part of EVTIMER (bits 41:32) const EVTIMER_HI_BITS: u32 = 10; /// Bit position where high part starts in the combined 64-bit value const EVTIMER_HI_SHIFT: u32 = 32; /// Bit mask for the high part of EVTIMER const EVTIMER_HI_MASK: u16 = (1 << EVTIMER_HI_BITS) - 1; /// Maximum value for MATCH_L register (32-bit) const MATCH_L_MAX: u32 = u32::MAX; /// Maximum value for MATCH_H register (10-bit) const MATCH_H_MAX: u16 = EVTIMER_HI_MASK; /// Bit mask for extracting the low 32 bits from a 64-bit value const LOW_32_BIT_MASK: u64 = u32::MAX as u64; /// Gray code conversion bit shifts (most significant to least) const GRAY_CONVERSION_SHIFTS: [u32; 6] = [32, 16, 8, 4, 2, 1]; /// Maximum timer value before rollover (2^42 - 1 ticks) /// Actual rollover time depends on the configured clock frequency const TIMER_MAX_VALUE: u64 = (1u64 << EVTIMER_TOTAL_BITS) - 1; /// Threshold for detecting timer rollover in comparisons (1 second at 1MHz) const TIMER_ROLLOVER_THRESHOLD: u64 = 1_000_000; /// Common default interrupt priority for OSTIMER const DEFAULT_INTERRUPT_PRIORITY: u8 = 3; // Global alarm state for interrupt handling static ALARM_ACTIVE: AtomicBool = AtomicBool::new(false); static mut ALARM_CALLBACK: Option = None; static mut ALARM_FLAG: Option<*const AtomicBool> = None; static mut ALARM_TARGET_TIME: u64 = 0; /// Number of tight spin iterations between elapsed time checks while waiting for MATCH writes to return to the idle (0) state. const MATCH_WRITE_READY_SPINS: usize = 512; /// Maximum time (in OSTIMER ticks) to wait for MATCH registers to become writable (~5 ms at 1 MHz). const MATCH_WRITE_READY_TIMEOUT_TICKS: u64 = 5_000; /// Short stabilization delay executed after toggling the MRCC reset line to let the OSTIMER bus interface settle. const RESET_STABILIZE_SPINS: usize = 512; pub(super) fn wait_for_match_write_ready(r: &Regs) -> bool { let start = now_ticks_read(); let mut spin_budget = 0usize; loop { if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { return true; } cortex_m::asm::nop(); spin_budget += 1; if spin_budget >= MATCH_WRITE_READY_SPINS { spin_budget = 0; let elapsed = now_ticks_read().wrapping_sub(start); if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { return false; } } } } pub(super) fn wait_for_match_write_complete(r: &Regs) -> bool { let start = now_ticks_read(); let mut spin_budget = 0usize; loop { if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { return true; } cortex_m::asm::nop(); spin_budget += 1; if spin_budget >= MATCH_WRITE_READY_SPINS { spin_budget = 0; let elapsed = now_ticks_read().wrapping_sub(start); if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { return false; } } } } fn prime_match_registers(r: &Regs) { // Disable the interrupt, clear any pending flag, then wait until the MATCH registers are writable. r.osevent_ctrl() .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); if wait_for_match_write_ready(r) { r.match_l().write(|w| unsafe { w.match_value().bits(MATCH_L_MAX) }); r.match_h().write(|w| unsafe { w.match_value().bits(MATCH_H_MAX) }); let _ = wait_for_match_write_complete(r); } } /// Single-shot alarm functionality for OSTIMER pub struct Alarm<'d> { /// Whether the alarm is currently active active: AtomicBool, /// Callback to execute when alarm expires (optional) callback: Option, /// Flag that gets set when alarm expires (optional) flag: Option<&'d AtomicBool>, _phantom: core::marker::PhantomData<&'d mut ()>, } impl<'d> Default for Alarm<'d> { fn default() -> Self { Self::new() } } impl<'d> Alarm<'d> { /// Create a new alarm instance pub fn new() -> Self { Self { active: AtomicBool::new(false), callback: None, flag: None, _phantom: core::marker::PhantomData, } } /// Set a callback that will be executed when the alarm expires /// Note: Due to interrupt handler constraints, callbacks must be static function pointers pub fn with_callback(mut self, callback: fn()) -> Self { self.callback = Some(callback); self } /// Set a flag that will be set to true when the alarm expires pub fn with_flag(mut self, flag: &'d AtomicBool) -> Self { self.flag = Some(flag); self } /// Check if the alarm is currently active pub fn is_active(&self) -> bool { self.active.load(Ordering::Acquire) } /// Cancel the alarm if it's active pub fn cancel(&self) { self.active.store(false, Ordering::Release); } } /// Configuration for Ostimer::new() #[derive(Copy, Clone)] pub struct Config { /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. pub init_match_max: bool, pub power: PoweredClock, pub source: OstimerClockSel, } impl Default for Config { fn default() -> Self { Self { init_match_max: true, power: PoweredClock::NormalEnabledDeepSleepDisabled, source: OstimerClockSel::Clk1M, } } } /// OSTIMER peripheral instance pub struct Ostimer<'d, I: Instance> { _inst: core::marker::PhantomData, clock_frequency_hz: u64, _phantom: core::marker::PhantomData<&'d mut ()>, } impl<'d, I: Instance> Ostimer<'d, I> { /// Construct OSTIMER handle. /// Requires clocks for the instance to be enabled by the board before calling. /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. pub fn new(_inst: Peri<'d, I>, cfg: Config) -> Self { let clock_freq = unsafe { enable_and_reset::(&OsTimerConfig { power: cfg.power, source: cfg.source, }) .expect("Enabling OsTimer clock should not fail") }; assert!(clock_freq > 0, "OSTIMER frequency must be greater than 0"); if cfg.init_match_max { let r: &Regs = unsafe { &*I::ptr() }; // Mask INTENA, clear pending flag, and set MATCH to max so no spurious IRQ fires. prime_match_registers(r); } Self { _inst: core::marker::PhantomData, clock_frequency_hz: clock_freq as u64, _phantom: core::marker::PhantomData, } } /// Get the configured clock frequency in Hz pub fn clock_frequency_hz(&self) -> u64 { self.clock_frequency_hz } /// Read the current timer counter value in timer ticks /// /// # Returns /// Current timer counter value as a 64-bit unsigned integer pub fn now(&self) -> u64 { now_ticks_read() } /// Reset the timer counter to zero /// /// This performs a hardware reset of the OSTIMER peripheral, which will reset /// the counter to zero and clear any pending interrupts. Note that this will /// affect all timer operations including embassy-time. /// /// # Safety /// This operation will reset the entire OSTIMER peripheral. Any active alarms /// or time_driver operations will be disrupted. Use with caution. pub fn reset(&self, _peripherals: &crate::pac::Peripherals) { critical_section::with(|_| { let r: &Regs = unsafe { &*I::ptr() }; // Mask the peripheral interrupt flag before we toggle the reset line so that // no new NVIC activity races with the reset sequence. r.osevent_ctrl() .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); unsafe { assert_reset::(); for _ in 0..RESET_STABILIZE_SPINS { cortex_m::asm::nop(); } release_reset::(); while !is_reset_released::() { cortex_m::asm::nop(); } } for _ in 0..RESET_STABILIZE_SPINS { cortex_m::asm::nop(); } // Clear alarm bookkeeping before re-arming MATCH registers. ALARM_ACTIVE.store(false, Ordering::Release); unsafe { ALARM_TARGET_TIME = 0; ALARM_CALLBACK = None; ALARM_FLAG = None; } prime_match_registers(r); }); // Ensure no stale OS_EVENT request remains pending after the reset sequence. crate::interrupt::OS_EVENT.unpend(); } /// Schedule a single-shot alarm to expire after the specified delay in microseconds /// /// # Parameters /// * `alarm` - The alarm instance to schedule /// * `delay_us` - Delay in microseconds from now /// /// # Returns /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity pub fn schedule_alarm_delay(&self, alarm: &Alarm, delay_us: u64) -> bool { let delay_ticks = (delay_us * self.clock_frequency_hz) / 1_000_000; let target_time = now_ticks_read() + delay_ticks; self.schedule_alarm_at(alarm, target_time) } /// Schedule a single-shot alarm to expire at the specified absolute time in timer ticks /// /// # Parameters /// * `alarm` - The alarm instance to schedule /// * `target_ticks` - Absolute time in timer ticks when the alarm should expire /// /// # Returns /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity pub fn schedule_alarm_at(&self, alarm: &Alarm, target_ticks: u64) -> bool { let now = now_ticks_read(); // Check if target time is in the past if target_ticks <= now { // Execute callback immediately if alarm was supposed to be active if alarm.active.load(Ordering::Acquire) { alarm.active.store(false, Ordering::Release); if let Some(callback) = alarm.callback { callback(); } if let Some(flag) = &alarm.flag { flag.store(true, Ordering::Release); } } return true; } // Check for timer rollover let max_future = now + TIMER_MAX_VALUE; if target_ticks > max_future { return false; // Would exceed timer capacity } // Program the timer let r: &Regs = unsafe { &*I::ptr() }; critical_section::with(|_| { // Disable interrupt and clear flag r.osevent_ctrl() .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); if !wait_for_match_write_ready(r) { prime_match_registers(r); if !wait_for_match_write_ready(r) { alarm.active.store(false, Ordering::Release); ALARM_ACTIVE.store(false, Ordering::Release); unsafe { ALARM_TARGET_TIME = 0; ALARM_CALLBACK = None; ALARM_FLAG = None; } return false; } } // Mark alarm as active now that we know the MATCH registers are writable alarm.active.store(true, Ordering::Release); // Set global alarm state for interrupt handler ALARM_ACTIVE.store(true, Ordering::Release); unsafe { ALARM_TARGET_TIME = target_ticks; ALARM_CALLBACK = alarm.callback; ALARM_FLAG = alarm.flag.map(|f| f as *const AtomicBool); } // Program MATCH registers (Gray-coded) let gray = bin_to_gray(target_ticks); let l = (gray & LOW_32_BIT_MASK) as u32; let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; r.match_l().write(|w| unsafe { w.match_value().bits(l) }); r.match_h().write(|w| unsafe { w.match_value().bits(h) }); if !wait_for_match_write_complete(r) { alarm.active.store(false, Ordering::Release); ALARM_ACTIVE.store(false, Ordering::Release); unsafe { ALARM_TARGET_TIME = 0; ALARM_CALLBACK = None; ALARM_FLAG = None; } return false; } let now_after_program = now_ticks_read(); let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); if now_after_program >= target_ticks && !intrflag_set { alarm.active.store(false, Ordering::Release); ALARM_ACTIVE.store(false, Ordering::Release); unsafe { ALARM_TARGET_TIME = 0; ALARM_CALLBACK = None; ALARM_FLAG = None; } return false; } // Enable interrupt r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); true }) } /// Cancel any active alarm pub fn cancel_alarm(&self, alarm: &Alarm) { critical_section::with(|_| { alarm.cancel(); // Clear global alarm state ALARM_ACTIVE.store(false, Ordering::Release); unsafe { ALARM_TARGET_TIME = 0 }; // Reset MATCH registers to maximum values to prevent spurious interrupts let r: &Regs = unsafe { &*I::ptr() }; prime_match_registers(r); }); } /// Check if an alarm has expired (call this from your interrupt handler) /// Returns true if the alarm was active and has now expired pub fn check_alarm_expired(&self, alarm: &Alarm) -> bool { if alarm.active.load(Ordering::Acquire) { alarm.active.store(false, Ordering::Release); // Execute callback if let Some(callback) = alarm.callback { callback(); } // Set flag if let Some(flag) = &alarm.flag { flag.store(true, Ordering::Release); } true } else { false } } } /// Read current EVTIMER (Gray-coded) and convert to binary ticks. #[inline(always)] fn now_ticks_read() -> u64 { let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; // Read high then low to minimize incoherent snapshots let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; // Combine and convert from Gray code to binary let gray = lo | (hi << EVTIMER_HI_SHIFT); gray_to_bin(gray) } // Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance pub trait Instance: Gate + PeripheralType { fn ptr() -> *const Regs; } #[cfg(not(feature = "time"))] impl Instance for crate::peripherals::OSTIMER0 { #[inline(always)] fn ptr() -> *const Regs { pac::Ostimer0::ptr() } } #[inline(always)] fn bin_to_gray(x: u64) -> u64 { x ^ (x >> 1) } #[inline(always)] fn gray_to_bin(gray: u64) -> u64 { // More efficient iterative conversion using predefined shifts let mut bin = gray; for &shift in &GRAY_CONVERSION_SHIFTS { bin ^= bin >> shift; } bin } #[cfg(feature = "time")] pub mod time_driver { use core::sync::atomic::Ordering; use core::task::Waker; use embassy_sync::waitqueue::AtomicWaker; use embassy_time_driver as etd; use super::{ ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, Regs, bin_to_gray, now_ticks_read, }; use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; use crate::clocks::{PoweredClock, enable_and_reset}; use crate::pac; #[allow(non_camel_case_types)] pub(crate) struct _OSTIMER0_TIME_DRIVER { _x: (), } // #[cfg(feature = "time")] // impl_cc_gate!(_OSTIMER0_TIME_DRIVER, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); impl crate::clocks::Gate for _OSTIMER0_TIME_DRIVER { type MrccPeriphConfig = crate::clocks::periph_helpers::OsTimerConfig; #[inline] unsafe fn enable_clock() { let mrcc = unsafe { pac::Mrcc0::steal() }; mrcc.mrcc_glb_cc1().modify(|_, w| w.ostimer0().enabled()); } #[inline] unsafe fn disable_clock() { let mrcc = unsafe { pac::Mrcc0::steal() }; mrcc.mrcc_glb_cc1().modify(|_r, w| w.ostimer0().disabled()); } #[inline] fn is_clock_enabled() -> bool { let mrcc = unsafe { pac::Mrcc0::steal() }; mrcc.mrcc_glb_cc1().read().ostimer0().is_enabled() } #[inline] unsafe fn release_reset() { let mrcc = unsafe { pac::Mrcc0::steal() }; mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().enabled()); } #[inline] unsafe fn assert_reset() { let mrcc = unsafe { pac::Mrcc0::steal() }; mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().disabled()); } #[inline] fn is_reset_released() -> bool { let mrcc = unsafe { pac::Mrcc0::steal() }; mrcc.mrcc_glb_rst1().read().ostimer0().is_enabled() } } pub struct Driver; static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); impl etd::Driver for Driver { fn now(&self) -> u64 { // Use the hardware counter (frequency configured in init) super::now_ticks_read() } fn schedule_wake(&self, timestamp: u64, waker: &Waker) { let now = self.now(); // If timestamp is in the past or very close to now, wake immediately if timestamp <= now { waker.wake_by_ref(); return; } // Prevent scheduling too far in the future (beyond timer rollover) // This prevents wraparound issues let max_future = now + super::TIMER_MAX_VALUE; if timestamp > max_future { // For very long timeouts, wake immediately to avoid rollover issues waker.wake_by_ref(); return; } // Register the waker first so any immediate wake below is observed by the executor. TIMER_WAKER.register(waker); let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; critical_section::with(|_| { // Mask INTENA and clear flag r.osevent_ctrl() .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); // Read back to ensure W1C took effect on hardware let _ = r.osevent_ctrl().read().ostimer_intrflag().bit(); if !super::wait_for_match_write_ready(r) { super::prime_match_registers(r); if !super::wait_for_match_write_ready(r) { // If we can't safely program MATCH, wake immediately and leave INTENA masked. waker.wake_by_ref(); return; } } // Program MATCH (Gray-coded). Write low then high, then fence. let gray = bin_to_gray(timestamp); let l = (gray & LOW_32_BIT_MASK) as u32; let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; r.match_l().write(|w| unsafe { w.match_value().bits(l) }); r.match_h().write(|w| unsafe { w.match_value().bits(h) }); if !super::wait_for_match_write_complete(r) { waker.wake_by_ref(); return; } let now_after_program = super::now_ticks_read(); let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); if now_after_program >= timestamp && !intrflag_set { waker.wake_by_ref(); return; } // Enable peripheral interrupt r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); }); } } /// Install the global embassy-time driver and configure NVIC priority for OS_EVENT. /// /// # Parameters /// * `priority` - Interrupt priority for the OSTIMER interrupt /// * `frequency_hz` - Actual OSTIMER clock frequency in Hz (stored for future use) /// /// Note: The frequency parameter is currently accepted for API compatibility. /// The embassy_time_driver macro handles driver registration automatically. pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { let _clock_freq = unsafe { enable_and_reset::<_OSTIMER0_TIME_DRIVER>(&OsTimerConfig { power: PoweredClock::AlwaysEnabled, source: OstimerClockSel::Clk1M, }) .expect("Enabling OsTimer clock should not fail") }; // Mask/clear at peripheral and set default MATCH let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; super::prime_match_registers(r); // Configure NVIC for timer operation crate::interrupt::OS_EVENT.configure_for_timer(priority); // Note: The embassy_time_driver macro automatically registers the driver // The frequency parameter is accepted for future compatibility let _ = frequency_hz; // Suppress unused parameter warning } // Export the global time driver expected by embassy-time embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver); /// To be called from the OS_EVENT IRQ. pub fn on_interrupt() { let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; // Critical section to prevent races with schedule_wake critical_section::with(|_| { // Check if interrupt is actually pending and handle it atomically if r.osevent_ctrl().read().ostimer_intrflag().bit_is_set() { // Clear flag and disable interrupt atomically r.osevent_ctrl().write(|w| { w.ostimer_intrflag() .clear_bit_by_one() // Write-1-to-clear using safe helper .ostimer_intena() .clear_bit() }); // Wake the waiting task TIMER_WAKER.wake(); // Handle alarm callback if active and this interrupt is for the alarm if ALARM_ACTIVE.load(Ordering::SeqCst) { let current_time = now_ticks_read(); let target_time = unsafe { ALARM_TARGET_TIME }; // Check if current time is close to alarm target time (within 1000 ticks for timing variations) if current_time >= target_time && current_time <= target_time + 1000 { ALARM_ACTIVE.store(false, Ordering::SeqCst); unsafe { ALARM_TARGET_TIME = 0 }; // Execute callback if set unsafe { if let Some(callback) = ALARM_CALLBACK { callback(); } } // Set flag if provided unsafe { if let Some(flag) = ALARM_FLAG { (*flag).store(true, Ordering::SeqCst); } } } } } }); } } #[cfg(feature = "time")] use crate::pac::interrupt; #[cfg(feature = "time")] #[allow(non_snake_case)] #[interrupt] fn OS_EVENT() { time_driver::on_interrupt() }