diff options
Diffstat (limited to 'embassy-mcxa/src/ostimer.rs')
| -rw-r--r-- | embassy-mcxa/src/ostimer.rs | 745 |
1 files changed, 745 insertions, 0 deletions
diff --git a/embassy-mcxa/src/ostimer.rs b/embassy-mcxa/src/ostimer.rs new file mode 100644 index 000000000..9e66e82d8 --- /dev/null +++ b/embassy-mcxa/src/ostimer.rs | |||
| @@ -0,0 +1,745 @@ | |||
| 1 | //! # OSTIMER Driver with Robustness Features | ||
| 2 | //! | ||
| 3 | //! This module provides an async timer driver for the NXP MCXA276 OSTIMER peripheral | ||
| 4 | //! with protection against race conditions and timer rollover issues. | ||
| 5 | //! | ||
| 6 | //! ## Features | ||
| 7 | //! | ||
| 8 | //! - Async timing with embassy-time integration | ||
| 9 | //! - Gray code counter handling (42-bit counter) | ||
| 10 | //! - Interrupt-driven wakeups | ||
| 11 | //! - Configurable interrupt priority | ||
| 12 | //! - **Race condition protection**: Critical sections and atomic operations | ||
| 13 | //! - **Timer rollover handling**: Bounds checking and rollover prevention | ||
| 14 | //! | ||
| 15 | //! ## Clock Frequency Configuration | ||
| 16 | //! | ||
| 17 | //! The OSTIMER frequency depends on your system's clock configuration. You must provide | ||
| 18 | //! the actual frequency when calling `time_driver::init()`. | ||
| 19 | //! | ||
| 20 | //! ## Race Condition Protection | ||
| 21 | //! - Critical sections in interrupt handlers prevent concurrent access | ||
| 22 | //! - Atomic register operations with memory barriers | ||
| 23 | //! - Proper interrupt flag clearing and validation | ||
| 24 | //! | ||
| 25 | //! ## Timer Rollover Handling | ||
| 26 | //! - Bounds checking prevents scheduling beyond timer capacity | ||
| 27 | //! - Immediate wake for timestamps that would cause rollover issues | ||
| 28 | #![allow(dead_code)] | ||
| 29 | |||
| 30 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 31 | |||
| 32 | use embassy_hal_internal::{Peri, PeripheralType}; | ||
| 33 | |||
| 34 | use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; | ||
| 35 | use crate::clocks::{Gate, PoweredClock, assert_reset, enable_and_reset, is_reset_released, release_reset}; | ||
| 36 | use crate::interrupt::InterruptExt; | ||
| 37 | use crate::pac; | ||
| 38 | |||
| 39 | // PAC defines the shared RegisterBlock under `ostimer0`. | ||
| 40 | type Regs = pac::ostimer0::RegisterBlock; | ||
| 41 | |||
| 42 | // OSTIMER EVTIMER register layout constants | ||
| 43 | /// Total width of the EVTIMER counter in bits (42 bits total) | ||
| 44 | const EVTIMER_TOTAL_BITS: u32 = 42; | ||
| 45 | /// Width of the low part of EVTIMER (bits 31:0) | ||
| 46 | const EVTIMER_LO_BITS: u32 = 32; | ||
| 47 | /// Width of the high part of EVTIMER (bits 41:32) | ||
| 48 | const EVTIMER_HI_BITS: u32 = 10; | ||
| 49 | /// Bit position where high part starts in the combined 64-bit value | ||
| 50 | const EVTIMER_HI_SHIFT: u32 = 32; | ||
| 51 | |||
| 52 | /// Bit mask for the high part of EVTIMER | ||
| 53 | const EVTIMER_HI_MASK: u16 = (1 << EVTIMER_HI_BITS) - 1; | ||
| 54 | |||
| 55 | /// Maximum value for MATCH_L register (32-bit) | ||
| 56 | const MATCH_L_MAX: u32 = u32::MAX; | ||
| 57 | /// Maximum value for MATCH_H register (10-bit) | ||
| 58 | const MATCH_H_MAX: u16 = EVTIMER_HI_MASK; | ||
| 59 | |||
| 60 | /// Bit mask for extracting the low 32 bits from a 64-bit value | ||
| 61 | const LOW_32_BIT_MASK: u64 = u32::MAX as u64; | ||
| 62 | |||
| 63 | /// Gray code conversion bit shifts (most significant to least) | ||
| 64 | const GRAY_CONVERSION_SHIFTS: [u32; 6] = [32, 16, 8, 4, 2, 1]; | ||
| 65 | |||
| 66 | /// Maximum timer value before rollover (2^42 - 1 ticks) | ||
| 67 | /// Actual rollover time depends on the configured clock frequency | ||
| 68 | const TIMER_MAX_VALUE: u64 = (1u64 << EVTIMER_TOTAL_BITS) - 1; | ||
| 69 | |||
| 70 | /// Threshold for detecting timer rollover in comparisons (1 second at 1MHz) | ||
| 71 | const TIMER_ROLLOVER_THRESHOLD: u64 = 1_000_000; | ||
| 72 | |||
| 73 | /// Common default interrupt priority for OSTIMER | ||
| 74 | const DEFAULT_INTERRUPT_PRIORITY: u8 = 3; | ||
| 75 | |||
| 76 | // Global alarm state for interrupt handling | ||
| 77 | static ALARM_ACTIVE: AtomicBool = AtomicBool::new(false); | ||
| 78 | static mut ALARM_CALLBACK: Option<fn()> = None; | ||
| 79 | static mut ALARM_FLAG: Option<*const AtomicBool> = None; | ||
| 80 | static mut ALARM_TARGET_TIME: u64 = 0; | ||
| 81 | |||
| 82 | /// Number of tight spin iterations between elapsed time checks while waiting for MATCH writes to return to the idle (0) state. | ||
| 83 | const MATCH_WRITE_READY_SPINS: usize = 512; | ||
| 84 | /// Maximum time (in OSTIMER ticks) to wait for MATCH registers to become writable (~5 ms at 1 MHz). | ||
| 85 | const MATCH_WRITE_READY_TIMEOUT_TICKS: u64 = 5_000; | ||
| 86 | /// Short stabilization delay executed after toggling the MRCC reset line to let the OSTIMER bus interface settle. | ||
| 87 | const RESET_STABILIZE_SPINS: usize = 512; | ||
| 88 | |||
| 89 | pub(super) fn wait_for_match_write_ready(r: &Regs) -> bool { | ||
| 90 | let start = now_ticks_read(); | ||
| 91 | let mut spin_budget = 0usize; | ||
| 92 | |||
| 93 | loop { | ||
| 94 | if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { | ||
| 95 | return true; | ||
| 96 | } | ||
| 97 | |||
| 98 | cortex_m::asm::nop(); | ||
| 99 | spin_budget += 1; | ||
| 100 | |||
| 101 | if spin_budget >= MATCH_WRITE_READY_SPINS { | ||
| 102 | spin_budget = 0; | ||
| 103 | |||
| 104 | let elapsed = now_ticks_read().wrapping_sub(start); | ||
| 105 | if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { | ||
| 106 | return false; | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | pub(super) fn wait_for_match_write_complete(r: &Regs) -> bool { | ||
| 113 | let start = now_ticks_read(); | ||
| 114 | let mut spin_budget = 0usize; | ||
| 115 | |||
| 116 | loop { | ||
| 117 | if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { | ||
| 118 | return true; | ||
| 119 | } | ||
| 120 | |||
| 121 | cortex_m::asm::nop(); | ||
| 122 | spin_budget += 1; | ||
| 123 | |||
| 124 | if spin_budget >= MATCH_WRITE_READY_SPINS { | ||
| 125 | spin_budget = 0; | ||
| 126 | |||
| 127 | let elapsed = now_ticks_read().wrapping_sub(start); | ||
| 128 | if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { | ||
| 129 | return false; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | fn prime_match_registers(r: &Regs) { | ||
| 136 | // Disable the interrupt, clear any pending flag, then wait until the MATCH registers are writable. | ||
| 137 | r.osevent_ctrl() | ||
| 138 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); | ||
| 139 | |||
| 140 | if wait_for_match_write_ready(r) { | ||
| 141 | r.match_l().write(|w| unsafe { w.match_value().bits(MATCH_L_MAX) }); | ||
| 142 | r.match_h().write(|w| unsafe { w.match_value().bits(MATCH_H_MAX) }); | ||
| 143 | let _ = wait_for_match_write_complete(r); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | /// Single-shot alarm functionality for OSTIMER | ||
| 148 | pub struct Alarm<'d> { | ||
| 149 | /// Whether the alarm is currently active | ||
| 150 | active: AtomicBool, | ||
| 151 | /// Callback to execute when alarm expires (optional) | ||
| 152 | callback: Option<fn()>, | ||
| 153 | /// Flag that gets set when alarm expires (optional) | ||
| 154 | flag: Option<&'d AtomicBool>, | ||
| 155 | _phantom: core::marker::PhantomData<&'d mut ()>, | ||
| 156 | } | ||
| 157 | |||
| 158 | impl<'d> Default for Alarm<'d> { | ||
| 159 | fn default() -> Self { | ||
| 160 | Self::new() | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | impl<'d> Alarm<'d> { | ||
| 165 | /// Create a new alarm instance | ||
| 166 | pub fn new() -> Self { | ||
| 167 | Self { | ||
| 168 | active: AtomicBool::new(false), | ||
| 169 | callback: None, | ||
| 170 | flag: None, | ||
| 171 | _phantom: core::marker::PhantomData, | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | /// Set a callback that will be executed when the alarm expires | ||
| 176 | /// Note: Due to interrupt handler constraints, callbacks must be static function pointers | ||
| 177 | pub fn with_callback(mut self, callback: fn()) -> Self { | ||
| 178 | self.callback = Some(callback); | ||
| 179 | self | ||
| 180 | } | ||
| 181 | |||
| 182 | /// Set a flag that will be set to true when the alarm expires | ||
| 183 | pub fn with_flag(mut self, flag: &'d AtomicBool) -> Self { | ||
| 184 | self.flag = Some(flag); | ||
| 185 | self | ||
| 186 | } | ||
| 187 | |||
| 188 | /// Check if the alarm is currently active | ||
| 189 | pub fn is_active(&self) -> bool { | ||
| 190 | self.active.load(Ordering::Acquire) | ||
| 191 | } | ||
| 192 | |||
| 193 | /// Cancel the alarm if it's active | ||
| 194 | pub fn cancel(&self) { | ||
| 195 | self.active.store(false, Ordering::Release); | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | /// Configuration for Ostimer::new() | ||
| 200 | #[derive(Copy, Clone)] | ||
| 201 | pub struct Config { | ||
| 202 | /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. | ||
| 203 | pub init_match_max: bool, | ||
| 204 | pub power: PoweredClock, | ||
| 205 | pub source: OstimerClockSel, | ||
| 206 | } | ||
| 207 | |||
| 208 | impl Default for Config { | ||
| 209 | fn default() -> Self { | ||
| 210 | Self { | ||
| 211 | init_match_max: true, | ||
| 212 | power: PoweredClock::NormalEnabledDeepSleepDisabled, | ||
| 213 | source: OstimerClockSel::Clk1M, | ||
| 214 | } | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | /// OSTIMER peripheral instance | ||
| 219 | pub struct Ostimer<'d, I: Instance> { | ||
| 220 | _inst: core::marker::PhantomData<I>, | ||
| 221 | clock_frequency_hz: u64, | ||
| 222 | _phantom: core::marker::PhantomData<&'d mut ()>, | ||
| 223 | } | ||
| 224 | |||
| 225 | impl<'d, I: Instance> Ostimer<'d, I> { | ||
| 226 | /// Construct OSTIMER handle. | ||
| 227 | /// Requires clocks for the instance to be enabled by the board before calling. | ||
| 228 | /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. | ||
| 229 | pub fn new(_inst: Peri<'d, I>, cfg: Config) -> Self { | ||
| 230 | let clock_freq = unsafe { | ||
| 231 | enable_and_reset::<I>(&OsTimerConfig { | ||
| 232 | power: cfg.power, | ||
| 233 | source: cfg.source, | ||
| 234 | }) | ||
| 235 | .expect("Enabling OsTimer clock should not fail") | ||
| 236 | }; | ||
| 237 | |||
| 238 | assert!(clock_freq > 0, "OSTIMER frequency must be greater than 0"); | ||
| 239 | |||
| 240 | if cfg.init_match_max { | ||
| 241 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 242 | // Mask INTENA, clear pending flag, and set MATCH to max so no spurious IRQ fires. | ||
| 243 | prime_match_registers(r); | ||
| 244 | } | ||
| 245 | |||
| 246 | Self { | ||
| 247 | _inst: core::marker::PhantomData, | ||
| 248 | clock_frequency_hz: clock_freq as u64, | ||
| 249 | _phantom: core::marker::PhantomData, | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | /// Get the configured clock frequency in Hz | ||
| 254 | pub fn clock_frequency_hz(&self) -> u64 { | ||
| 255 | self.clock_frequency_hz | ||
| 256 | } | ||
| 257 | |||
| 258 | /// Read the current timer counter value in timer ticks | ||
| 259 | /// | ||
| 260 | /// # Returns | ||
| 261 | /// Current timer counter value as a 64-bit unsigned integer | ||
| 262 | pub fn now(&self) -> u64 { | ||
| 263 | now_ticks_read() | ||
| 264 | } | ||
| 265 | |||
| 266 | /// Reset the timer counter to zero | ||
| 267 | /// | ||
| 268 | /// This performs a hardware reset of the OSTIMER peripheral, which will reset | ||
| 269 | /// the counter to zero and clear any pending interrupts. Note that this will | ||
| 270 | /// affect all timer operations including embassy-time. | ||
| 271 | /// | ||
| 272 | /// # Safety | ||
| 273 | /// This operation will reset the entire OSTIMER peripheral. Any active alarms | ||
| 274 | /// or time_driver operations will be disrupted. Use with caution. | ||
| 275 | pub fn reset(&self, _peripherals: &crate::pac::Peripherals) { | ||
| 276 | critical_section::with(|_| { | ||
| 277 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 278 | |||
| 279 | // Mask the peripheral interrupt flag before we toggle the reset line so that | ||
| 280 | // no new NVIC activity races with the reset sequence. | ||
| 281 | r.osevent_ctrl() | ||
| 282 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); | ||
| 283 | |||
| 284 | unsafe { | ||
| 285 | assert_reset::<I>(); | ||
| 286 | |||
| 287 | for _ in 0..RESET_STABILIZE_SPINS { | ||
| 288 | cortex_m::asm::nop(); | ||
| 289 | } | ||
| 290 | |||
| 291 | release_reset::<I>(); | ||
| 292 | |||
| 293 | while !is_reset_released::<I>() { | ||
| 294 | cortex_m::asm::nop(); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | for _ in 0..RESET_STABILIZE_SPINS { | ||
| 299 | cortex_m::asm::nop(); | ||
| 300 | } | ||
| 301 | |||
| 302 | // Clear alarm bookkeeping before re-arming MATCH registers. | ||
| 303 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 304 | unsafe { | ||
| 305 | ALARM_TARGET_TIME = 0; | ||
| 306 | ALARM_CALLBACK = None; | ||
| 307 | ALARM_FLAG = None; | ||
| 308 | } | ||
| 309 | |||
| 310 | prime_match_registers(r); | ||
| 311 | }); | ||
| 312 | |||
| 313 | // Ensure no stale OS_EVENT request remains pending after the reset sequence. | ||
| 314 | crate::interrupt::OS_EVENT.unpend(); | ||
| 315 | } | ||
| 316 | |||
| 317 | /// Schedule a single-shot alarm to expire after the specified delay in microseconds | ||
| 318 | /// | ||
| 319 | /// # Parameters | ||
| 320 | /// * `alarm` - The alarm instance to schedule | ||
| 321 | /// * `delay_us` - Delay in microseconds from now | ||
| 322 | /// | ||
| 323 | /// # Returns | ||
| 324 | /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity | ||
| 325 | pub fn schedule_alarm_delay(&self, alarm: &Alarm, delay_us: u64) -> bool { | ||
| 326 | let delay_ticks = (delay_us * self.clock_frequency_hz) / 1_000_000; | ||
| 327 | let target_time = now_ticks_read() + delay_ticks; | ||
| 328 | self.schedule_alarm_at(alarm, target_time) | ||
| 329 | } | ||
| 330 | |||
| 331 | /// Schedule a single-shot alarm to expire at the specified absolute time in timer ticks | ||
| 332 | /// | ||
| 333 | /// # Parameters | ||
| 334 | /// * `alarm` - The alarm instance to schedule | ||
| 335 | /// * `target_ticks` - Absolute time in timer ticks when the alarm should expire | ||
| 336 | /// | ||
| 337 | /// # Returns | ||
| 338 | /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity | ||
| 339 | pub fn schedule_alarm_at(&self, alarm: &Alarm, target_ticks: u64) -> bool { | ||
| 340 | let now = now_ticks_read(); | ||
| 341 | |||
| 342 | // Check if target time is in the past | ||
| 343 | if target_ticks <= now { | ||
| 344 | // Execute callback immediately if alarm was supposed to be active | ||
| 345 | if alarm.active.load(Ordering::Acquire) { | ||
| 346 | alarm.active.store(false, Ordering::Release); | ||
| 347 | if let Some(callback) = alarm.callback { | ||
| 348 | callback(); | ||
| 349 | } | ||
| 350 | if let Some(flag) = &alarm.flag { | ||
| 351 | flag.store(true, Ordering::Release); | ||
| 352 | } | ||
| 353 | } | ||
| 354 | return true; | ||
| 355 | } | ||
| 356 | |||
| 357 | // Check for timer rollover | ||
| 358 | let max_future = now + TIMER_MAX_VALUE; | ||
| 359 | if target_ticks > max_future { | ||
| 360 | return false; // Would exceed timer capacity | ||
| 361 | } | ||
| 362 | |||
| 363 | // Program the timer | ||
| 364 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 365 | |||
| 366 | critical_section::with(|_| { | ||
| 367 | // Disable interrupt and clear flag | ||
| 368 | r.osevent_ctrl() | ||
| 369 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); | ||
| 370 | |||
| 371 | if !wait_for_match_write_ready(r) { | ||
| 372 | prime_match_registers(r); | ||
| 373 | |||
| 374 | if !wait_for_match_write_ready(r) { | ||
| 375 | alarm.active.store(false, Ordering::Release); | ||
| 376 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 377 | unsafe { | ||
| 378 | ALARM_TARGET_TIME = 0; | ||
| 379 | ALARM_CALLBACK = None; | ||
| 380 | ALARM_FLAG = None; | ||
| 381 | } | ||
| 382 | return false; | ||
| 383 | } | ||
| 384 | } | ||
| 385 | |||
| 386 | // Mark alarm as active now that we know the MATCH registers are writable | ||
| 387 | alarm.active.store(true, Ordering::Release); | ||
| 388 | |||
| 389 | // Set global alarm state for interrupt handler | ||
| 390 | ALARM_ACTIVE.store(true, Ordering::Release); | ||
| 391 | unsafe { | ||
| 392 | ALARM_TARGET_TIME = target_ticks; | ||
| 393 | ALARM_CALLBACK = alarm.callback; | ||
| 394 | ALARM_FLAG = alarm.flag.map(|f| f as *const AtomicBool); | ||
| 395 | } | ||
| 396 | |||
| 397 | // Program MATCH registers (Gray-coded) | ||
| 398 | let gray = bin_to_gray(target_ticks); | ||
| 399 | let l = (gray & LOW_32_BIT_MASK) as u32; | ||
| 400 | let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; | ||
| 401 | |||
| 402 | r.match_l().write(|w| unsafe { w.match_value().bits(l) }); | ||
| 403 | r.match_h().write(|w| unsafe { w.match_value().bits(h) }); | ||
| 404 | |||
| 405 | if !wait_for_match_write_complete(r) { | ||
| 406 | alarm.active.store(false, Ordering::Release); | ||
| 407 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 408 | unsafe { | ||
| 409 | ALARM_TARGET_TIME = 0; | ||
| 410 | ALARM_CALLBACK = None; | ||
| 411 | ALARM_FLAG = None; | ||
| 412 | } | ||
| 413 | return false; | ||
| 414 | } | ||
| 415 | |||
| 416 | let now_after_program = now_ticks_read(); | ||
| 417 | let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); | ||
| 418 | if now_after_program >= target_ticks && !intrflag_set { | ||
| 419 | alarm.active.store(false, Ordering::Release); | ||
| 420 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 421 | unsafe { | ||
| 422 | ALARM_TARGET_TIME = 0; | ||
| 423 | ALARM_CALLBACK = None; | ||
| 424 | ALARM_FLAG = None; | ||
| 425 | } | ||
| 426 | return false; | ||
| 427 | } | ||
| 428 | |||
| 429 | // Enable interrupt | ||
| 430 | r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); | ||
| 431 | |||
| 432 | true | ||
| 433 | }) | ||
| 434 | } | ||
| 435 | |||
| 436 | /// Cancel any active alarm | ||
| 437 | pub fn cancel_alarm(&self, alarm: &Alarm) { | ||
| 438 | critical_section::with(|_| { | ||
| 439 | alarm.cancel(); | ||
| 440 | |||
| 441 | // Clear global alarm state | ||
| 442 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 443 | unsafe { ALARM_TARGET_TIME = 0 }; | ||
| 444 | |||
| 445 | // Reset MATCH registers to maximum values to prevent spurious interrupts | ||
| 446 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 447 | prime_match_registers(r); | ||
| 448 | }); | ||
| 449 | } | ||
| 450 | |||
| 451 | /// Check if an alarm has expired (call this from your interrupt handler) | ||
| 452 | /// Returns true if the alarm was active and has now expired | ||
| 453 | pub fn check_alarm_expired(&self, alarm: &Alarm) -> bool { | ||
| 454 | if alarm.active.load(Ordering::Acquire) { | ||
| 455 | alarm.active.store(false, Ordering::Release); | ||
| 456 | |||
| 457 | // Execute callback | ||
| 458 | if let Some(callback) = alarm.callback { | ||
| 459 | callback(); | ||
| 460 | } | ||
| 461 | |||
| 462 | // Set flag | ||
| 463 | if let Some(flag) = &alarm.flag { | ||
| 464 | flag.store(true, Ordering::Release); | ||
| 465 | } | ||
| 466 | |||
| 467 | true | ||
| 468 | } else { | ||
| 469 | false | ||
| 470 | } | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | /// Read current EVTIMER (Gray-coded) and convert to binary ticks. | ||
| 475 | #[inline(always)] | ||
| 476 | fn now_ticks_read() -> u64 { | ||
| 477 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 478 | |||
| 479 | // Read high then low to minimize incoherent snapshots | ||
| 480 | let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); | ||
| 481 | let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; | ||
| 482 | // Combine and convert from Gray code to binary | ||
| 483 | let gray = lo | (hi << EVTIMER_HI_SHIFT); | ||
| 484 | gray_to_bin(gray) | ||
| 485 | } | ||
| 486 | |||
| 487 | // Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance | ||
| 488 | pub trait Instance: Gate<MrccPeriphConfig = OsTimerConfig> + PeripheralType { | ||
| 489 | fn ptr() -> *const Regs; | ||
| 490 | } | ||
| 491 | |||
| 492 | #[cfg(not(feature = "time"))] | ||
| 493 | impl Instance for crate::peripherals::OSTIMER0 { | ||
| 494 | #[inline(always)] | ||
| 495 | fn ptr() -> *const Regs { | ||
| 496 | pac::Ostimer0::ptr() | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | #[inline(always)] | ||
| 501 | fn bin_to_gray(x: u64) -> u64 { | ||
| 502 | x ^ (x >> 1) | ||
| 503 | } | ||
| 504 | |||
| 505 | #[inline(always)] | ||
| 506 | fn gray_to_bin(gray: u64) -> u64 { | ||
| 507 | // More efficient iterative conversion using predefined shifts | ||
| 508 | let mut bin = gray; | ||
| 509 | for &shift in &GRAY_CONVERSION_SHIFTS { | ||
| 510 | bin ^= bin >> shift; | ||
| 511 | } | ||
| 512 | bin | ||
| 513 | } | ||
| 514 | |||
| 515 | #[cfg(feature = "time")] | ||
| 516 | pub mod time_driver { | ||
| 517 | use core::sync::atomic::Ordering; | ||
| 518 | use core::task::Waker; | ||
| 519 | |||
| 520 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 521 | use embassy_time_driver as etd; | ||
| 522 | |||
| 523 | use super::{ | ||
| 524 | ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, | ||
| 525 | LOW_32_BIT_MASK, Regs, bin_to_gray, now_ticks_read, | ||
| 526 | }; | ||
| 527 | use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; | ||
| 528 | use crate::clocks::{PoweredClock, enable_and_reset}; | ||
| 529 | use crate::pac; | ||
| 530 | |||
| 531 | #[allow(non_camel_case_types)] | ||
| 532 | pub(crate) struct _OSTIMER0_TIME_DRIVER { | ||
| 533 | _x: (), | ||
| 534 | } | ||
| 535 | |||
| 536 | // #[cfg(feature = "time")] | ||
| 537 | // impl_cc_gate!(_OSTIMER0_TIME_DRIVER, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); | ||
| 538 | |||
| 539 | impl crate::clocks::Gate for _OSTIMER0_TIME_DRIVER { | ||
| 540 | type MrccPeriphConfig = crate::clocks::periph_helpers::OsTimerConfig; | ||
| 541 | |||
| 542 | #[inline] | ||
| 543 | unsafe fn enable_clock() { | ||
| 544 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 545 | mrcc.mrcc_glb_cc1().modify(|_, w| w.ostimer0().enabled()); | ||
| 546 | } | ||
| 547 | |||
| 548 | #[inline] | ||
| 549 | unsafe fn disable_clock() { | ||
| 550 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 551 | mrcc.mrcc_glb_cc1().modify(|_r, w| w.ostimer0().disabled()); | ||
| 552 | } | ||
| 553 | |||
| 554 | #[inline] | ||
| 555 | fn is_clock_enabled() -> bool { | ||
| 556 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 557 | mrcc.mrcc_glb_cc1().read().ostimer0().is_enabled() | ||
| 558 | } | ||
| 559 | |||
| 560 | #[inline] | ||
| 561 | unsafe fn release_reset() { | ||
| 562 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 563 | mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().enabled()); | ||
| 564 | } | ||
| 565 | |||
| 566 | #[inline] | ||
| 567 | unsafe fn assert_reset() { | ||
| 568 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 569 | mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().disabled()); | ||
| 570 | } | ||
| 571 | |||
| 572 | #[inline] | ||
| 573 | fn is_reset_released() -> bool { | ||
| 574 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 575 | mrcc.mrcc_glb_rst1().read().ostimer0().is_enabled() | ||
| 576 | } | ||
| 577 | } | ||
| 578 | |||
| 579 | pub struct Driver; | ||
| 580 | static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); | ||
| 581 | |||
| 582 | impl etd::Driver for Driver { | ||
| 583 | fn now(&self) -> u64 { | ||
| 584 | // Use the hardware counter (frequency configured in init) | ||
| 585 | super::now_ticks_read() | ||
| 586 | } | ||
| 587 | |||
| 588 | fn schedule_wake(&self, timestamp: u64, waker: &Waker) { | ||
| 589 | let now = self.now(); | ||
| 590 | |||
| 591 | // If timestamp is in the past or very close to now, wake immediately | ||
| 592 | if timestamp <= now { | ||
| 593 | waker.wake_by_ref(); | ||
| 594 | return; | ||
| 595 | } | ||
| 596 | |||
| 597 | // Prevent scheduling too far in the future (beyond timer rollover) | ||
| 598 | // This prevents wraparound issues | ||
| 599 | let max_future = now + super::TIMER_MAX_VALUE; | ||
| 600 | if timestamp > max_future { | ||
| 601 | // For very long timeouts, wake immediately to avoid rollover issues | ||
| 602 | waker.wake_by_ref(); | ||
| 603 | return; | ||
| 604 | } | ||
| 605 | |||
| 606 | // Register the waker first so any immediate wake below is observed by the executor. | ||
| 607 | TIMER_WAKER.register(waker); | ||
| 608 | |||
| 609 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 610 | |||
| 611 | critical_section::with(|_| { | ||
| 612 | // Mask INTENA and clear flag | ||
| 613 | r.osevent_ctrl() | ||
| 614 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); | ||
| 615 | |||
| 616 | // Read back to ensure W1C took effect on hardware | ||
| 617 | let _ = r.osevent_ctrl().read().ostimer_intrflag().bit(); | ||
| 618 | |||
| 619 | if !super::wait_for_match_write_ready(r) { | ||
| 620 | super::prime_match_registers(r); | ||
| 621 | |||
| 622 | if !super::wait_for_match_write_ready(r) { | ||
| 623 | // If we can't safely program MATCH, wake immediately and leave INTENA masked. | ||
| 624 | waker.wake_by_ref(); | ||
| 625 | return; | ||
| 626 | } | ||
| 627 | } | ||
| 628 | |||
| 629 | // Program MATCH (Gray-coded). Write low then high, then fence. | ||
| 630 | let gray = bin_to_gray(timestamp); | ||
| 631 | let l = (gray & LOW_32_BIT_MASK) as u32; | ||
| 632 | |||
| 633 | let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; | ||
| 634 | |||
| 635 | r.match_l().write(|w| unsafe { w.match_value().bits(l) }); | ||
| 636 | r.match_h().write(|w| unsafe { w.match_value().bits(h) }); | ||
| 637 | |||
| 638 | if !super::wait_for_match_write_complete(r) { | ||
| 639 | waker.wake_by_ref(); | ||
| 640 | return; | ||
| 641 | } | ||
| 642 | |||
| 643 | let now_after_program = super::now_ticks_read(); | ||
| 644 | let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); | ||
| 645 | if now_after_program >= timestamp && !intrflag_set { | ||
| 646 | waker.wake_by_ref(); | ||
| 647 | return; | ||
| 648 | } | ||
| 649 | |||
| 650 | // Enable peripheral interrupt | ||
| 651 | r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); | ||
| 652 | }); | ||
| 653 | } | ||
| 654 | } | ||
| 655 | |||
| 656 | /// Install the global embassy-time driver and configure NVIC priority for OS_EVENT. | ||
| 657 | /// | ||
| 658 | /// # Parameters | ||
| 659 | /// * `priority` - Interrupt priority for the OSTIMER interrupt | ||
| 660 | /// * `frequency_hz` - Actual OSTIMER clock frequency in Hz (stored for future use) | ||
| 661 | /// | ||
| 662 | /// Note: The frequency parameter is currently accepted for API compatibility. | ||
| 663 | /// The embassy_time_driver macro handles driver registration automatically. | ||
| 664 | pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { | ||
| 665 | let _clock_freq = unsafe { | ||
| 666 | enable_and_reset::<_OSTIMER0_TIME_DRIVER>(&OsTimerConfig { | ||
| 667 | power: PoweredClock::AlwaysEnabled, | ||
| 668 | source: OstimerClockSel::Clk1M, | ||
| 669 | }) | ||
| 670 | .expect("Enabling OsTimer clock should not fail") | ||
| 671 | }; | ||
| 672 | |||
| 673 | // Mask/clear at peripheral and set default MATCH | ||
| 674 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 675 | super::prime_match_registers(r); | ||
| 676 | |||
| 677 | // Configure NVIC for timer operation | ||
| 678 | crate::interrupt::OS_EVENT.configure_for_timer(priority); | ||
| 679 | |||
| 680 | // Note: The embassy_time_driver macro automatically registers the driver | ||
| 681 | // The frequency parameter is accepted for future compatibility | ||
| 682 | let _ = frequency_hz; // Suppress unused parameter warning | ||
| 683 | } | ||
| 684 | |||
| 685 | // Export the global time driver expected by embassy-time | ||
| 686 | embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver); | ||
| 687 | |||
| 688 | /// To be called from the OS_EVENT IRQ. | ||
| 689 | pub fn on_interrupt() { | ||
| 690 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 691 | |||
| 692 | // Critical section to prevent races with schedule_wake | ||
| 693 | critical_section::with(|_| { | ||
| 694 | // Check if interrupt is actually pending and handle it atomically | ||
| 695 | if r.osevent_ctrl().read().ostimer_intrflag().bit_is_set() { | ||
| 696 | // Clear flag and disable interrupt atomically | ||
| 697 | r.osevent_ctrl().write(|w| { | ||
| 698 | w.ostimer_intrflag() | ||
| 699 | .clear_bit_by_one() // Write-1-to-clear using safe helper | ||
| 700 | .ostimer_intena() | ||
| 701 | .clear_bit() | ||
| 702 | }); | ||
| 703 | |||
| 704 | // Wake the waiting task | ||
| 705 | TIMER_WAKER.wake(); | ||
| 706 | |||
| 707 | // Handle alarm callback if active and this interrupt is for the alarm | ||
| 708 | if ALARM_ACTIVE.load(Ordering::SeqCst) { | ||
| 709 | let current_time = now_ticks_read(); | ||
| 710 | let target_time = unsafe { ALARM_TARGET_TIME }; | ||
| 711 | |||
| 712 | // Check if current time is close to alarm target time (within 1000 ticks for timing variations) | ||
| 713 | if current_time >= target_time && current_time <= target_time + 1000 { | ||
| 714 | ALARM_ACTIVE.store(false, Ordering::SeqCst); | ||
| 715 | unsafe { ALARM_TARGET_TIME = 0 }; | ||
| 716 | |||
| 717 | // Execute callback if set | ||
| 718 | unsafe { | ||
| 719 | if let Some(callback) = ALARM_CALLBACK { | ||
| 720 | callback(); | ||
| 721 | } | ||
| 722 | } | ||
| 723 | |||
| 724 | // Set flag if provided | ||
| 725 | unsafe { | ||
| 726 | if let Some(flag) = ALARM_FLAG { | ||
| 727 | (*flag).store(true, Ordering::SeqCst); | ||
| 728 | } | ||
| 729 | } | ||
| 730 | } | ||
| 731 | } | ||
| 732 | } | ||
| 733 | }); | ||
| 734 | } | ||
| 735 | } | ||
| 736 | |||
| 737 | #[cfg(feature = "time")] | ||
| 738 | use crate::pac::interrupt; | ||
| 739 | |||
| 740 | #[cfg(feature = "time")] | ||
| 741 | #[allow(non_snake_case)] | ||
| 742 | #[interrupt] | ||
| 743 | fn OS_EVENT() { | ||
| 744 | time_driver::on_interrupt() | ||
| 745 | } | ||
