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