From 9ed929f6d2ef2eeeeeaf77ada73f78008dc9f8ea Mon Sep 17 00:00:00 2001 From: ragarnoy Date: Mon, 24 Nov 2025 20:11:42 +0100 Subject: [PATCH 1/8] Add AON Timer driver for RP2350 and integrate with library --- embassy-rp/src/aon_timer/mod.rs | 427 ++++++++++++++++++++++++++++++++ embassy-rp/src/lib.rs | 5 + 2 files changed, 432 insertions(+) create mode 100644 embassy-rp/src/aon_timer/mod.rs diff --git a/embassy-rp/src/aon_timer/mod.rs b/embassy-rp/src/aon_timer/mod.rs new file mode 100644 index 0000000000..f1876106f9 --- /dev/null +++ b/embassy-rp/src/aon_timer/mod.rs @@ -0,0 +1,427 @@ +//! AON (Always-On) Timer driver for RP2350 +//! +//! The AON Timer is a 64-bit counter that runs at 1 kHz (1ms resolution) and can operate during +//! low-power modes. It's part of the POWMAN peripheral and provides: +//! +//! - Millisecond resolution counter +//! - Alarm support for wakeup from low-power modes (WFI/WFE and DORMANT) +//! - Async alarm waiting with interrupt support (POWMAN_IRQ_TIMER) +//! - Choice of XOSC or LPOSC clock sources +//! +//! # Wake from Low Power +//! +//! The AON Timer supports two wake scenarios: +//! +//! - **WFI/WFE (light sleep)**: Alarm triggers `POWMAN_IRQ_TIMER` interrupt to wake. +//! Use `wait_for_alarm().await` for async waiting with CPU in low-power mode. +//! - **DORMANT mode (deep sleep)**: Hardware alarm event wakes directly without interrupt. +//! No CPU clock running, so interrupts cannot fire. This requires using LPOSC as the clock source. +//! +//! # Important Notes +//! +//! - All POWMAN registers require password `0x5AFE` in upper 16 bits for writes +//! - Timer must be stopped before setting the counter value +//! - Resolution is 1ms (1 kHz tick rate) +//! +//! # Example +//! +//! ```no_run +//! use embassy_rp::aon_timer::{AonTimer, Config, ClockSource}; +//! use embassy_rp::bind_interrupts; +//! use embassy_time::Duration; +//! +//! // Bind the interrupt handler +//! bind_interrupts!(struct Irqs { +//! POWMAN_IRQ_TIMER => embassy_rp::aon_timer::InterruptHandler; +//! }); +//! +//! let config = Config { +//! clock_source: ClockSource::Xosc, +//! clock_freq_khz: 12000, // 12 MHz +//! }; +//! +//! let mut timer = AonTimer::new(p.POWMAN, Irqs, config); +//! +//! // Set counter to 0 (or any starting value in milliseconds) +//! timer.set_counter(0); +//! +//! // Start the timer +//! timer.start(); +//! +//! // Read current value in milliseconds +//! let ms = timer.now(); +//! +//! // Set an alarm and wait asynchronously +//! timer.set_alarm_after(Duration::from_secs(5)).unwrap(); +//! timer.wait_for_alarm().await; // CPU enters low-power mode +//! ``` + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, Ordering, compiler_fence}; +use core::task::Poll; + +use embassy_hal_internal::Peri; +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_time::Duration; + +use crate::{interrupt, pac}; + +const POWMAN_PASSWORD: u32 = 0x5AFE << 16; + +static WAKER: AtomicWaker = AtomicWaker::new(); +static ALARM_OCCURRED: AtomicBool = AtomicBool::new(false); + +/// AON Timer configuration +#[derive(Clone, Copy)] +pub struct Config { + /// Clock source for the timer + pub clock_source: ClockSource, + /// Clock frequency in kHz + pub clock_freq_khz: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { + clock_source: ClockSource::Xosc, + clock_freq_khz: 12000, // 12 MHz XOSC + } + } +} + +/// Clock source for the AON Timer +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ClockSource { + /// Crystal oscillator (more accurate, requires external crystal) + Xosc, + /// Low-power oscillator (less accurate, ~32 kHz, available in all power modes) + Lposc, +} + +/// AON Timer errors +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The alarm time is in the past + AlarmInPast, +} + +/// AON Timer driver +pub struct AonTimer<'d> { + _phantom: PhantomData<&'d ()>, +} + +impl<'d> AonTimer<'d> { + /// Create a new AON Timer instance + /// + /// This configures the clock source and frequency but does not start the timer. + /// Call `start()` to begin counting. + /// + /// For async alarm support, you must bind the `POWMAN_IRQ_TIMER` interrupt: + /// ```rust,ignore + /// bind_interrupts!(struct Irqs { + /// POWMAN_IRQ_TIMER => aon_timer::InterruptHandler; + /// }); + /// let timer = AonTimer::new(p.POWMAN, Irqs, config); + /// ``` + pub fn new( + _inner: Peri<'d, crate::peripherals::POWMAN>, + _irq: impl interrupt::typelevel::Binding + 'd, + config: Config, + ) -> Self { + let powman = pac::POWMAN; + + // Configure clock source and frequency + match config.clock_source { + ClockSource::Xosc => { + powman.xosc_freq_khz_int().write(|w| { + w.0 = config.clock_freq_khz | POWMAN_PASSWORD; + *w + }); + powman.xosc_freq_khz_frac().write(|w| { + w.0 = POWMAN_PASSWORD; + *w + }); + } + ClockSource::Lposc => { + powman.lposc_freq_khz_int().write(|w| { + w.0 = config.clock_freq_khz | POWMAN_PASSWORD; + *w + }); + powman.lposc_freq_khz_frac().write(|w| { + w.0 = POWMAN_PASSWORD; + *w + }); + } + } + + // Enable the POWMAN_IRQ_TIMER interrupt + interrupt::POWMAN_IRQ_TIMER.unpend(); + unsafe { interrupt::POWMAN_IRQ_TIMER.enable() }; + + Self { _phantom: PhantomData } + } + + /// Start the timer + /// + /// The timer will begin counting from its current value. + pub fn start(&mut self) { + let powman = pac::POWMAN; + powman.timer().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | POWMAN_PASSWORD; + w.set_use_xosc(true); // TODO: Use configured clock source + w.set_run(true); + *w + }); + } + + /// Stop the timer + /// + /// The timer will stop counting but retain its current value. + pub fn stop(&mut self) { + let powman = pac::POWMAN; + powman.timer().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | POWMAN_PASSWORD; + w.set_run(false); + *w + }); + } + + /// Check if the timer is currently running + pub fn is_running(&self) -> bool { + let powman = pac::POWMAN; + powman.timer().read().run() + } + + /// Read the current counter value in milliseconds + /// + /// This reads the 64-bit counter value with rollover protection. + /// The value represents milliseconds since the counter was last set. + pub fn now(&self) -> u64 { + let powman = pac::POWMAN; + // Read with rollover protection: read upper, lower, upper again + loop { + let upper1 = powman.read_time_upper().read(); + let lower = powman.read_time_lower().read(); + let upper2 = powman.read_time_upper().read(); + + // If upper didn't change, we got a consistent read + if upper1 == upper2 { + return ((upper1 as u64) << 32) | (lower as u64); + } + // Otherwise retry (rollover occurred) + } + } + + /// Set the counter value in milliseconds + /// + /// This allows you to initialize the counter to any value (e.g., Unix timestamp in ms, + /// or 0 to start counting from boot). + /// + /// Note: Timer must be stopped before calling this function. + pub fn set_counter(&mut self, value_ms: u64) { + let powman = pac::POWMAN; + // Write the 64-bit value in 4x 16-bit chunks + powman.set_time_15to0().write(|w| { + w.0 = ((value_ms & 0xFFFF) as u32) | POWMAN_PASSWORD; + *w + }); + powman.set_time_31to16().write(|w| { + w.0 = (((value_ms >> 16) & 0xFFFF) as u32) | POWMAN_PASSWORD; + *w + }); + powman.set_time_47to32().write(|w| { + w.0 = (((value_ms >> 32) & 0xFFFF) as u32) | POWMAN_PASSWORD; + *w + }); + powman.set_time_63to48().write(|w| { + w.0 = (((value_ms >> 48) & 0xFFFF) as u32) | POWMAN_PASSWORD; + *w + }); + } + + /// Set an alarm for a specific counter value (in milliseconds) + /// + /// The alarm will fire when the counter reaches this value. + /// Returns an error if the alarm time is in the past. + pub fn set_alarm(&mut self, alarm_ms: u64) -> Result<(), Error> { + // Check if alarm is in the past + let current_ms = self.now(); + if alarm_ms <= current_ms { + return Err(Error::AlarmInPast); + } + + // Disable alarm before setting new time + self.disable_alarm(); + + // Set alarm value + self.set_alarm_value(alarm_ms); + + // Clear any pending alarm flag + self.clear_alarm(); + + // Enable the alarm and interrupt + self.enable_alarm(); + self.enable_alarm_interrupt(); + + Ok(()) + } + + /// Enable the alarm interrupt (INTE.TIMER) + /// + /// This allows the alarm to trigger POWMAN_IRQ_TIMER and wake from WFI/WFE. + fn enable_alarm_interrupt(&mut self) { + let powman = pac::POWMAN; + powman.inte().modify(|w| w.set_timer(true)); + } + + /// Disable the alarm interrupt (INTE.TIMER) + fn disable_alarm_interrupt(&mut self) { + let powman = pac::POWMAN; + powman.inte().modify(|w| w.set_timer(false)); + } + + /// Set the internal alarm value in milliseconds + /// + /// Note: Use `set_alarm()` instead for the public API. This is a low-level helper. + fn set_alarm_value(&mut self, value: u64) { + let powman = pac::POWMAN; + powman.alarm_time_15to0().write(|w| { + w.0 = ((value & 0xFFFF) as u32) | POWMAN_PASSWORD; + *w + }); + powman.alarm_time_31to16().write(|w| { + w.0 = (((value >> 16) & 0xFFFF) as u32) | POWMAN_PASSWORD; + *w + }); + powman.alarm_time_47to32().write(|w| { + w.0 = (((value >> 32) & 0xFFFF) as u32) | POWMAN_PASSWORD; + *w + }); + powman.alarm_time_63to48().write(|w| { + w.0 = (((value >> 48) & 0xFFFF) as u32) | POWMAN_PASSWORD; + *w + }); + } + + /// Check if the alarm has fired + pub fn alarm_fired(&self) -> bool { + let powman = pac::POWMAN; + powman.timer().read().alarm() + } + + /// Clear the alarm flag + pub fn clear_alarm(&mut self) { + let powman = pac::POWMAN; + powman.timer().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | POWMAN_PASSWORD; + w.set_alarm(true); // Write 1 to clear + *w + }); + } + + /// Disable the alarm + pub fn disable_alarm(&mut self) { + let powman = pac::POWMAN; + powman.timer().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | POWMAN_PASSWORD; + w.set_alarm_enab(false); + *w + }); + } + + /// Enable the alarm + pub fn enable_alarm(&mut self) { + let powman = pac::POWMAN; + powman.timer().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | POWMAN_PASSWORD; + w.set_alarm_enab(true); + *w + }); + } + + /// Set an alarm to fire after a duration from now + /// + /// This is a convenience method that sets the alarm to `now() + duration`. + pub fn set_alarm_after(&mut self, duration: Duration) -> Result<(), Error> { + let current_ms = self.now(); + let alarm_ms = current_ms + duration.as_millis(); + self.set_alarm(alarm_ms) + } + + /// Get the current counter value as a Duration + /// + /// This is useful for measuring time spans. The duration represents + /// the time since the counter was last set to 0. + pub fn elapsed(&self) -> Duration { + Duration::from_millis(self.now()) + } + + /// Wait asynchronously for the alarm to fire + /// + /// This function will wait until the AON Timer alarm is triggered. + /// If the alarm is already triggered, it will return immediately. + /// The CPU will enter WFI (Wait For Interrupt) low-power mode while waiting. + /// + /// # Example + /// ```rust,ignore + /// // Set alarm for 5 seconds from now + /// timer.set_alarm_after(Duration::from_secs(5)).unwrap(); + /// + /// // Wait for the alarm (CPU enters low power mode) + /// timer.wait_for_alarm().await; + /// ``` + pub async fn wait_for_alarm(&mut self) { + poll_fn(|cx| { + WAKER.register(cx.waker()); + + // Atomically check and clear the alarm occurred flag + if critical_section::with(|_| { + let occurred = ALARM_OCCURRED.load(Ordering::SeqCst); + if occurred { + ALARM_OCCURRED.store(false, Ordering::SeqCst); + } + occurred + }) { + // Clear the interrupt flag in hardware + self.clear_alarm(); + + compiler_fence(Ordering::SeqCst); + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + } +} + +/// Interrupt handler for AON Timer alarms +pub struct InterruptHandler { + _empty: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let powman = crate::pac::POWMAN; + + // Disable the alarm interrupt to prevent re-entry + powman.inte().modify(|w| w.set_timer(false)); + + // Disable the alarm + powman.timer().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | POWMAN_PASSWORD; + w.set_alarm_enab(false); + *w + }); + + // Clear the interrupt flag in INTR + powman.intr().modify(|w| w.set_timer(true)); + + // Set the alarm occurred flag and wake the waker + ALARM_OCCURRED.store(true, Ordering::SeqCst); + WAKER.wake(); + } +} diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 4cb1a0912d..8a4ee4ea43 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -22,6 +22,8 @@ mod intrinsics; pub mod adc; #[cfg(feature = "_rp235x")] +pub mod aon_timer; +#[cfg(feature = "_rp235x")] pub mod block; #[cfg(feature = "rp2040")] pub mod bootsel; @@ -147,6 +149,8 @@ embassy_hal_internal::interrupt_mod!( TRNG_IRQ, PLL_SYS_IRQ, PLL_USB_IRQ, + POWMAN_IRQ_POW, + POWMAN_IRQ_TIMER, SWI_IRQ_0, SWI_IRQ_1, SWI_IRQ_2, @@ -440,6 +444,7 @@ embassy_hal_internal::peripherals! { WATCHDOG, BOOTSEL, + POWMAN, TRNG } From 2082634a81de43409b440a2bbb5d984a6886bd6a Mon Sep 17 00:00:00 2001 From: ragarnoy Date: Mon, 24 Nov 2025 20:11:58 +0100 Subject: [PATCH 2/8] Add AON Timer async example for RP235x --- examples/rp235x/src/bin/aon_timer_async.rs | 102 +++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 examples/rp235x/src/bin/aon_timer_async.rs diff --git a/examples/rp235x/src/bin/aon_timer_async.rs b/examples/rp235x/src/bin/aon_timer_async.rs new file mode 100644 index 0000000000..7ffbbad011 --- /dev/null +++ b/examples/rp235x/src/bin/aon_timer_async.rs @@ -0,0 +1,102 @@ +//! AON (Always-On) Timer Example using Embassy Async API +//! +//! This example demonstrates async alarm support with the AON Timer. +//! The alarm triggers an interrupt (POWMAN_IRQ_TIMER) which wakes the CPU +//! from WFI (Wait For Interrupt) low-power mode. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::aon_timer::{AonTimer, ClockSource, Config}; +use embassy_rp::{bind_interrupts, gpio}; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + POWMAN_IRQ_TIMER => embassy_rp::aon_timer::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + info!("AON Timer Async example starting"); + + // Small delay for debug probe + Timer::after_millis(10).await; + + // Configure the AON Timer with XOSC at 12 MHz + let config = Config { + clock_source: ClockSource::Xosc, + clock_freq_khz: 12000, + }; + + let mut aon = AonTimer::new(p.POWMAN, Irqs, config); + + // Set counter to 0 (start counting from boot) + info!("Setting counter to 0"); + aon.set_counter(0); + + // Start the timer + aon.start(); + info!("AON Timer started"); + + // Verify timer is running + Timer::after_millis(100).await; + let initial_ms = aon.now(); + info!("Counter value after 100ms: {} ms", initial_ms); + + // Main loop: set alarms and wait asynchronously + for i in 1..=5 { + info!("=== Round {} ===", i); + + // Set an alarm for 2 seconds from now + let alarm_duration = Duration::from_secs(2); + let current = aon.now(); + info!("Current time: {} ms", current); + info!("Setting alarm for {} seconds from now", alarm_duration.as_secs()); + + aon.set_alarm_after(alarm_duration).unwrap(); + + // Blink LED while waiting + led.set_high(); + + // Wait asynchronously for the alarm + // The CPU will enter WFI low-power mode during this time + info!("Waiting for alarm (CPU will enter low-power mode)..."); + aon.wait_for_alarm().await; + + led.set_low(); + + // Alarm fired! + let elapsed = aon.elapsed(); + info!( + "ALARM FIRED! Counter: {} ms ({}.{:03} seconds)", + aon.now(), + elapsed.as_secs(), + elapsed.as_millis() % 1000 + ); + + // Wait a bit before next round + Timer::after_secs(1).await; + } + + info!("Demo complete! Looping with longer alarms..."); + + // Continue with longer alarms + loop { + info!("Setting alarm for 5 seconds"); + aon.set_alarm_after(Duration::from_secs(5)).unwrap(); + + led.toggle(); + aon.wait_for_alarm().await; + led.toggle(); + + info!("Alarm fired at {} ms", aon.now()); + Timer::after_secs(1).await; + } +} From cde9bd21a1d66735a5bb7b4523a0a4335f309974 Mon Sep 17 00:00:00 2001 From: ragarnoy Date: Tue, 25 Nov 2025 22:11:37 +0100 Subject: [PATCH 3/8] Update AON Timer to support configurable clock sources and add minor refinements --- embassy-rp/src/aon_timer/mod.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/embassy-rp/src/aon_timer/mod.rs b/embassy-rp/src/aon_timer/mod.rs index f1876106f9..c14476974e 100644 --- a/embassy-rp/src/aon_timer/mod.rs +++ b/embassy-rp/src/aon_timer/mod.rs @@ -111,6 +111,7 @@ pub enum Error { /// AON Timer driver pub struct AonTimer<'d> { _phantom: PhantomData<&'d ()>, + config: Config, } impl<'d> AonTimer<'d> { @@ -161,7 +162,10 @@ impl<'d> AonTimer<'d> { interrupt::POWMAN_IRQ_TIMER.unpend(); unsafe { interrupt::POWMAN_IRQ_TIMER.enable() }; - Self { _phantom: PhantomData } + Self { + _phantom: PhantomData, + config, + } } /// Start the timer @@ -171,7 +175,10 @@ impl<'d> AonTimer<'d> { let powman = pac::POWMAN; powman.timer().modify(|w| { w.0 = (w.0 & 0x0000FFFF) | POWMAN_PASSWORD; - w.set_use_xosc(true); // TODO: Use configured clock source + match self.config.clock_source { + ClockSource::Lposc => w.set_use_lposc(true), + ClockSource::Xosc => w.set_use_xosc(true), + } w.set_run(true); *w }); @@ -278,14 +285,13 @@ impl<'d> AonTimer<'d> { } /// Disable the alarm interrupt (INTE.TIMER) - fn disable_alarm_interrupt(&mut self) { + pub fn disable_alarm_interrupt(&mut self) { let powman = pac::POWMAN; powman.inte().modify(|w| w.set_timer(false)); } /// Set the internal alarm value in milliseconds - /// - /// Note: Use `set_alarm()` instead for the public API. This is a low-level helper. + #[inline(always)] fn set_alarm_value(&mut self, value: u64) { let powman = pac::POWMAN; powman.alarm_time_15to0().write(|w| { @@ -404,6 +410,7 @@ pub struct InterruptHandler { } impl interrupt::typelevel::Handler for InterruptHandler { + #[inline(always)] unsafe fn on_interrupt() { let powman = crate::pac::POWMAN; From 0b1e36a73a1fffb3e289535f91becd25742bf7da Mon Sep 17 00:00:00 2001 From: ragarnoy Date: Tue, 25 Nov 2025 22:17:16 +0100 Subject: [PATCH 4/8] Mask clock frequency with `0xFFFF` before applying `POWMAN_PASSWORD` in AON Timer config. --- embassy-rp/src/aon_timer/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-rp/src/aon_timer/mod.rs b/embassy-rp/src/aon_timer/mod.rs index c14476974e..2e837eedf9 100644 --- a/embassy-rp/src/aon_timer/mod.rs +++ b/embassy-rp/src/aon_timer/mod.rs @@ -138,7 +138,7 @@ impl<'d> AonTimer<'d> { match config.clock_source { ClockSource::Xosc => { powman.xosc_freq_khz_int().write(|w| { - w.0 = config.clock_freq_khz | POWMAN_PASSWORD; + w.0 = (config.clock_freq_khz & 0xFFFF) | POWMAN_PASSWORD; *w }); powman.xosc_freq_khz_frac().write(|w| { @@ -148,7 +148,7 @@ impl<'d> AonTimer<'d> { } ClockSource::Lposc => { powman.lposc_freq_khz_int().write(|w| { - w.0 = config.clock_freq_khz | POWMAN_PASSWORD; + w.0 = (config.clock_freq_khz & 0xFFFF) | POWMAN_PASSWORD; *w }); powman.lposc_freq_khz_frac().write(|w| { From 225963eb297d7603234c91642c559641c7d2b899 Mon Sep 17 00:00:00 2001 From: ragarnoy Date: Tue, 25 Nov 2025 22:47:15 +0100 Subject: [PATCH 5/8] Enhance AON Timer with configurable alarm wake modes and update documentation. --- embassy-rp/src/aon_timer/mod.rs | 267 +++++++++++++++++++-- examples/rp235x/src/bin/aon_timer_async.rs | 3 +- 2 files changed, 247 insertions(+), 23 deletions(-) diff --git a/embassy-rp/src/aon_timer/mod.rs b/embassy-rp/src/aon_timer/mod.rs index 2e837eedf9..966c982a43 100644 --- a/embassy-rp/src/aon_timer/mod.rs +++ b/embassy-rp/src/aon_timer/mod.rs @@ -8,14 +8,36 @@ //! - Async alarm waiting with interrupt support (POWMAN_IRQ_TIMER) //! - Choice of XOSC or LPOSC clock sources //! -//! # Wake from Low Power +//! # Alarm Wake Modes //! -//! The AON Timer supports two wake scenarios: +//! The AON Timer supports multiple wake modes via the [`AlarmWakeMode`] enum: //! -//! - **WFI/WFE (light sleep)**: Alarm triggers `POWMAN_IRQ_TIMER` interrupt to wake. -//! Use `wait_for_alarm().await` for async waiting with CPU in low-power mode. -//! - **DORMANT mode (deep sleep)**: Hardware alarm event wakes directly without interrupt. -//! No CPU clock running, so interrupts cannot fire. This requires using LPOSC as the clock source. +//! - **`WfiOnly` (default)**: Alarm triggers `POWMAN_IRQ_TIMER` interrupt to wake from +//! WFI/WFE (light sleep). Use `wait_for_alarm().await` for async waiting. Works with +//! both XOSC and LPOSC clock sources. +//! +//! - **`DormantOnly`**: Hardware power-up wake from DORMANT (deep sleep). Sets the +//! `PWRUP_ON_ALARM` bit to trigger hardware power-up event (no interrupt, since CPU +//! clock is stopped). **Requirements:** +//! - Must use LPOSC clock source (XOSC is powered down in DORMANT) +//! - Requires Secure privilege level (TIMER register is Secure-only) +//! +//! - **`Both`**: Enables both interrupt wake (WFI/WFE) and hardware power-up wake (DORMANT). +//! Subject to the same requirements as `DormantOnly` for DORMANT support. +//! +//! - **`Disabled`**: Alarm flag is set but no wake mechanisms are enabled. Use `alarm_fired()` +//! to manually poll the alarm status. +//! +//! You can set the wake mode either in [`Config`] at initialization, or at runtime via +//! [`AonTimer::set_wake_mode()`]. +//! +//! # Security Considerations +//! +//! The TIMER register (including the `PWRUP_ON_ALARM` bit) is **Secure-only** per the +//! RP2350 datasheet. Setting wake modes that involve DORMANT wake (`DormantOnly` or `Both`) +//! may fail silently or have no effect when running in Non-secure contexts. Methods +//! `enable_dormant_wake()`, `disable_dormant_wake()`, and `set_wake_mode()` that configure +//! DORMANT wake are subject to this restriction. //! //! # Important Notes //! @@ -23,10 +45,10 @@ //! - Timer must be stopped before setting the counter value //! - Resolution is 1ms (1 kHz tick rate) //! -//! # Example +//! # Example - WFI/WFE Wake (Default) //! //! ```no_run -//! use embassy_rp::aon_timer::{AonTimer, Config, ClockSource}; +//! use embassy_rp::aon_timer::{AonTimer, Config, ClockSource, AlarmWakeMode}; //! use embassy_rp::bind_interrupts; //! use embassy_time::Duration; //! @@ -38,22 +60,67 @@ //! let config = Config { //! clock_source: ClockSource::Xosc, //! clock_freq_khz: 12000, // 12 MHz +//! alarm_wake_mode: AlarmWakeMode::WfiOnly, // Default //! }; //! //! let mut timer = AonTimer::new(p.POWMAN, Irqs, config); -//! -//! // Set counter to 0 (or any starting value in milliseconds) //! timer.set_counter(0); +//! timer.start(); +//! +//! // Set an alarm and wait asynchronously (interrupt-based wake) +//! timer.set_alarm_after(Duration::from_secs(5)).unwrap(); +//! timer.wait_for_alarm().await; // CPU enters WFI low-power mode +//! ``` +//! +//! # Example - DORMANT Wake +//! +//! ```no_run +//! use embassy_rp::aon_timer::{AonTimer, Config, ClockSource, AlarmWakeMode}; +//! use embassy_rp::bind_interrupts; +//! use embassy_time::Duration; +//! +//! bind_interrupts!(struct Irqs { +//! POWMAN_IRQ_TIMER => embassy_rp::aon_timer::InterruptHandler; +//! }); +//! +//! let config = Config { +//! clock_source: ClockSource::Lposc, // Required for DORMANT +//! clock_freq_khz: 32, // ~32 kHz LPOSC +//! alarm_wake_mode: AlarmWakeMode::DormantOnly, +//! }; //! -//! // Start the timer +//! let mut timer = AonTimer::new(p.POWMAN, Irqs, config); +//! timer.set_counter(0); //! timer.start(); //! -//! // Read current value in milliseconds -//! let ms = timer.now(); +//! // Set alarm for DORMANT wake (hardware power-up) +//! timer.set_alarm_after(Duration::from_secs(10)).unwrap(); +//! // Enter DORMANT mode here - alarm will wake via power-up event +//! ``` +//! +//! # Example - Runtime Wake Mode Change +//! +//! ```no_run +//! use embassy_rp::aon_timer::{AonTimer, Config, ClockSource, AlarmWakeMode}; +//! use embassy_rp::bind_interrupts; +//! use embassy_time::Duration; +//! +//! bind_interrupts!(struct Irqs { +//! POWMAN_IRQ_TIMER => embassy_rp::aon_timer::InterruptHandler; +//! }); //! -//! // Set an alarm and wait asynchronously +//! let mut timer = AonTimer::new(p.POWMAN, Irqs, Config::default()); +//! timer.set_counter(0); +//! timer.start(); +//! +//! // Use WFI wake initially //! timer.set_alarm_after(Duration::from_secs(5)).unwrap(); -//! timer.wait_for_alarm().await; // CPU enters low-power mode +//! timer.wait_for_alarm().await; +//! +//! // Switch to both wake modes at runtime +//! timer.set_wake_mode(AlarmWakeMode::Both); +//! timer.set_alarm_after(Duration::from_secs(10)).unwrap(); +//! // Now supports both WFI and DORMANT wake //! ``` use core::future::poll_fn; @@ -73,6 +140,42 @@ const POWMAN_PASSWORD: u32 = 0x5AFE << 16; static WAKER: AtomicWaker = AtomicWaker::new(); static ALARM_OCCURRED: AtomicBool = AtomicBool::new(false); +/// Alarm wake mode configuration +/// +/// Controls which low-power wake mechanisms are enabled for the alarm. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AlarmWakeMode { + /// Wake from WFI/WFE only (interrupt-based via POWMAN_IRQ_TIMER) + /// + /// This is the default and most common mode. The alarm triggers an interrupt + /// that wakes the CPU from light sleep (WFI/WFE). Works with both XOSC and LPOSC. + WfiOnly, + + /// Wake from DORMANT mode only (hardware power-up via PWRUP_ON_ALARM) + /// + /// The alarm wakes the chip from deep sleep (DORMANT) by triggering a hardware + /// power-up event. No interrupt is used since the CPU clock is stopped in DORMANT. + /// + /// **Requirements:** + /// - Must use LPOSC clock source (XOSC is powered down in DORMANT) + /// - Requires Secure privilege level (TIMER register is Secure-only) + /// - May fail silently in Non-secure contexts + DormantOnly, + + /// Wake from both WFI/WFE and DORMANT modes + /// + /// Enables both interrupt-based wake (WFI/WFE) and hardware power-up wake (DORMANT). + /// Subject to the same requirements as DormantOnly for DORMANT wake support. + Both, + + /// Alarm fires but doesn't wake (manual polling only) + /// + /// The alarm flag is set in hardware but no wake mechanisms are enabled. + /// Use `alarm_fired()` to manually poll the alarm status. + Disabled, +} + /// AON Timer configuration #[derive(Clone, Copy)] pub struct Config { @@ -80,6 +183,11 @@ pub struct Config { pub clock_source: ClockSource, /// Clock frequency in kHz pub clock_freq_khz: u32, + /// Alarm wake mode + /// + /// Controls which low-power wake mechanisms are enabled for alarms. + /// See [`AlarmWakeMode`] for details on each mode. + pub alarm_wake_mode: AlarmWakeMode, } impl Default for Config { @@ -87,6 +195,7 @@ impl Default for Config { Self { clock_source: ClockSource::Xosc, clock_freq_khz: 12000, // 12 MHz XOSC + alarm_wake_mode: AlarmWakeMode::WfiOnly, } } } @@ -117,10 +226,17 @@ pub struct AonTimer<'d> { impl<'d> AonTimer<'d> { /// Create a new AON Timer instance /// - /// This configures the clock source and frequency but does not start the timer. - /// Call `start()` to begin counting. + /// This configures the clock source, frequency, and alarm wake mode but does not + /// start the timer. Call `start()` to begin counting. /// - /// For async alarm support, you must bind the `POWMAN_IRQ_TIMER` interrupt: + /// The wake mode in `config.alarm_wake_mode` determines how alarms wake the CPU: + /// - `WfiOnly` (default): Interrupt-based wake from WFI/WFE + /// - `DormantOnly`: Hardware power-up wake from DORMANT (requires LPOSC + Secure mode) + /// - `Both`: Both interrupt and hardware power-up wake + /// - `Disabled`: No automatic wake (manual polling only) + /// + /// For interrupt-based wake modes (`WfiOnly` or `Both`), you must bind the + /// `POWMAN_IRQ_TIMER` interrupt: /// ```rust,ignore /// bind_interrupts!(struct Irqs { /// POWMAN_IRQ_TIMER => aon_timer::InterruptHandler; @@ -229,6 +345,9 @@ impl<'d> AonTimer<'d> { /// /// Note: Timer must be stopped before calling this function. pub fn set_counter(&mut self, value_ms: u64) { + if self.is_running() { + panic!("timer must be stopped before setting counter"); + } let powman = pac::POWMAN; // Write the 64-bit value in 4x 16-bit chunks powman.set_time_15to0().write(|w| { @@ -253,6 +372,12 @@ impl<'d> AonTimer<'d> { /// /// The alarm will fire when the counter reaches this value. /// Returns an error if the alarm time is in the past. + /// + /// The wake behavior depends on the configured `alarm_wake_mode`: + /// - `WfiOnly`: Alarm triggers interrupt wake from WFI/WFE + /// - `DormantOnly`: Alarm triggers power-up from DORMANT mode + /// - `Both`: Alarm triggers both interrupt and power-up wake + /// - `Disabled`: Alarm flag is set but no wake occurs pub fn set_alarm(&mut self, alarm_ms: u64) -> Result<(), Error> { // Check if alarm is in the past let current_ms = self.now(); @@ -269,9 +394,28 @@ impl<'d> AonTimer<'d> { // Clear any pending alarm flag self.clear_alarm(); - // Enable the alarm and interrupt + // Configure wake mode based on configuration + match self.config.alarm_wake_mode { + AlarmWakeMode::WfiOnly => { + self.disable_dormant_wake(); + self.enable_alarm_interrupt(); + } + AlarmWakeMode::DormantOnly => { + self.enable_dormant_wake(); + self.disable_alarm_interrupt(); + } + AlarmWakeMode::Both => { + self.enable_dormant_wake(); + self.enable_alarm_interrupt(); + } + AlarmWakeMode::Disabled => { + self.disable_dormant_wake(); + self.disable_alarm_interrupt(); + } + } + + // Enable the alarm self.enable_alarm(); - self.enable_alarm_interrupt(); Ok(()) } @@ -348,6 +492,76 @@ impl<'d> AonTimer<'d> { }); } + /// Enable DORMANT mode wake on alarm + /// + /// Sets the TIMER.PWRUP_ON_ALARM bit to allow the alarm to wake the chip from + /// DORMANT (deep sleep) mode. This is a hardware power-up event, distinct from + /// interrupt-based WFI/WFE wake. + /// + /// **Security Note**: The TIMER register is Secure-only per the RP2350 datasheet. + /// This method may fail silently or have no effect when called from Non-secure contexts. + /// + /// **Clock Source**: DORMANT wake requires LPOSC as the clock source, since XOSC + /// is powered down in DORMANT mode. + pub fn enable_dormant_wake(&mut self) { + let powman = pac::POWMAN; + powman.timer().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | POWMAN_PASSWORD; + w.set_pwrup_on_alarm(true); + *w + }); + } + + /// Disable DORMANT mode wake on alarm + /// + /// Clears the TIMER.PWRUP_ON_ALARM bit. The alarm will no longer wake the chip + /// from DORMANT mode, but can still wake from WFI/WFE via interrupts. + /// + /// **Security Note**: The TIMER register is Secure-only per the RP2350 datasheet. + /// This method may fail silently or have no effect when called from Non-secure contexts. + pub fn disable_dormant_wake(&mut self) { + let powman = pac::POWMAN; + powman.timer().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | POWMAN_PASSWORD; + w.set_pwrup_on_alarm(false); + *w + }); + } + + /// Set the alarm wake mode + /// + /// Configures which low-power wake mechanisms are enabled for the alarm. + /// This immediately updates the hardware configuration. + /// + /// # Arguments + /// * `mode` - The desired wake mode (see [`AlarmWakeMode`]) + /// + /// # Security Note + /// Setting modes that involve DORMANT wake (DormantOnly or Both) requires + /// Secure privilege level. These modes may fail silently in Non-secure contexts. + pub fn set_wake_mode(&mut self, mode: AlarmWakeMode) { + match mode { + AlarmWakeMode::WfiOnly => { + self.enable_alarm_interrupt(); + self.disable_dormant_wake(); + } + AlarmWakeMode::DormantOnly => { + self.disable_alarm_interrupt(); + self.enable_dormant_wake(); + } + AlarmWakeMode::Both => { + self.enable_alarm_interrupt(); + self.enable_dormant_wake(); + } + AlarmWakeMode::Disabled => { + self.disable_alarm_interrupt(); + self.disable_dormant_wake(); + } + } + // Update stored config + self.config.alarm_wake_mode = mode; + } + /// Set an alarm to fire after a duration from now /// /// This is a convenience method that sets the alarm to `now() + duration`. @@ -369,14 +583,23 @@ impl<'d> AonTimer<'d> { /// /// This function will wait until the AON Timer alarm is triggered. /// If the alarm is already triggered, it will return immediately. - /// The CPU will enter WFI (Wait For Interrupt) low-power mode while waiting. + /// + /// **Wake Mode Behavior:** + /// - `WfiOnly` or `Both`: CPU enters WFI (Wait For Interrupt) low-power mode while + /// waiting. The alarm interrupt will wake the CPU and this function will return. + /// - `DormantOnly` or `Disabled`: This function will NOT automatically wake from + /// DORMANT mode. For DORMANT wake, the hardware power-up event will restart the + /// chip, not resume this async function. + /// + /// This method is primarily intended for `WfiOnly` and `Both` wake modes where + /// interrupt-based wake is available. /// /// # Example /// ```rust,ignore /// // Set alarm for 5 seconds from now /// timer.set_alarm_after(Duration::from_secs(5)).unwrap(); /// - /// // Wait for the alarm (CPU enters low power mode) + /// // Wait for the alarm (CPU enters WFI low-power mode) /// timer.wait_for_alarm().await; /// ``` pub async fn wait_for_alarm(&mut self) { diff --git a/examples/rp235x/src/bin/aon_timer_async.rs b/examples/rp235x/src/bin/aon_timer_async.rs index 7ffbbad011..3f3be846df 100644 --- a/examples/rp235x/src/bin/aon_timer_async.rs +++ b/examples/rp235x/src/bin/aon_timer_async.rs @@ -9,7 +9,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::aon_timer::{AonTimer, ClockSource, Config}; +use embassy_rp::aon_timer::{AlarmWakeMode, AonTimer, ClockSource, Config}; use embassy_rp::{bind_interrupts, gpio}; use embassy_time::{Duration, Timer}; use gpio::{Level, Output}; @@ -33,6 +33,7 @@ async fn main(_spawner: Spawner) { let config = Config { clock_source: ClockSource::Xosc, clock_freq_khz: 12000, + alarm_wake_mode: AlarmWakeMode::WfiOnly, }; let mut aon = AonTimer::new(p.POWMAN, Irqs, config); From ff7e9c2da1d7c40b3be3f1a9bd24875004e81edb Mon Sep 17 00:00:00 2001 From: ragarnoy Date: Tue, 25 Nov 2025 22:52:36 +0100 Subject: [PATCH 6/8] change misleading log --- examples/rp235x/src/bin/aon_timer_async.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rp235x/src/bin/aon_timer_async.rs b/examples/rp235x/src/bin/aon_timer_async.rs index 3f3be846df..b2da401843 100644 --- a/examples/rp235x/src/bin/aon_timer_async.rs +++ b/examples/rp235x/src/bin/aon_timer_async.rs @@ -68,7 +68,7 @@ async fn main(_spawner: Spawner) { // Wait asynchronously for the alarm // The CPU will enter WFI low-power mode during this time - info!("Waiting for alarm (CPU will enter low-power mode)..."); + info!("Waiting for alarm..."); aon.wait_for_alarm().await; led.set_low(); From eda25998dd91859c28867ef7172f4567cc34d2d7 Mon Sep 17 00:00:00 2001 From: ragarnoy Date: Tue, 25 Nov 2025 23:05:06 +0100 Subject: [PATCH 7/8] Update CHANGELOG to include AON Timer enhancements for RP2350. --- embassy-rp/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index 3b3cb5351b..3d48dca144 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- Add AON Timer driver for RP2350 with configurable clock sources and alarm wake modes - Fix several minor typos in documentation - Add PIO SPI - Add PIO I2S input From 798bba29a5076620a8770af6185367a804171b02 Mon Sep 17 00:00:00 2001 From: ragarnoy Date: Tue, 25 Nov 2025 23:53:27 +0100 Subject: [PATCH 8/8] Update AON Timer documentation code blocks to use `rust,ignore` --- embassy-rp/src/aon_timer/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-rp/src/aon_timer/mod.rs b/embassy-rp/src/aon_timer/mod.rs index 966c982a43..d8f9d2836f 100644 --- a/embassy-rp/src/aon_timer/mod.rs +++ b/embassy-rp/src/aon_timer/mod.rs @@ -47,7 +47,7 @@ //! //! # Example - WFI/WFE Wake (Default) //! -//! ```no_run +//! ```rust,ignore //! use embassy_rp::aon_timer::{AonTimer, Config, ClockSource, AlarmWakeMode}; //! use embassy_rp::bind_interrupts; //! use embassy_time::Duration; @@ -74,7 +74,7 @@ //! //! # Example - DORMANT Wake //! -//! ```no_run +//! ```rust,ignore //! use embassy_rp::aon_timer::{AonTimer, Config, ClockSource, AlarmWakeMode}; //! use embassy_rp::bind_interrupts; //! use embassy_time::Duration; @@ -100,7 +100,7 @@ //! //! # Example - Runtime Wake Mode Change //! -//! ```no_run +//! ```rust,ignore //! use embassy_rp::aon_timer::{AonTimer, Config, ClockSource, AlarmWakeMode}; //! use embassy_rp::bind_interrupts; //! use embassy_time::Duration;