From 660fc4f7602c0be689e2c6cb4b20e55a26127636 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 26 Nov 2025 14:35:18 +0100 Subject: feat: support nrf54 GRTC as time-driver * Refactor GRTC peripheral splitting it into multiple channels * Reserve channel 1 for time-driver if enabled * Implement time-driver using GRTC (RTC peripheral is now removed). * Add timer example to nrf54l15 --- embassy-nrf/src/chips/nrf54l15_app.rs | 28 +++--- embassy-nrf/src/lib.rs | 1 + embassy-nrf/src/time_driver.rs | 157 ++++++++++++++++++++++++++-------- 3 files changed, 137 insertions(+), 49 deletions(-) (limited to 'embassy-nrf/src') diff --git a/embassy-nrf/src/chips/nrf54l15_app.rs b/embassy-nrf/src/chips/nrf54l15_app.rs index 8846717db..b6c9fd110 100644 --- a/embassy-nrf/src/chips/nrf54l15_app.rs +++ b/embassy-nrf/src/chips/nrf54l15_app.rs @@ -26,7 +26,6 @@ pub mod pac { PPIB10_NS as PPIB10, PPIB11_NS as PPIB11, TIMER10_NS as TIMER10, - RTC10_NS as RTC10, EGU10_NS as EGU10, RADIO_NS as RADIO, DPPIC20_NS as DPPIC20, @@ -76,7 +75,6 @@ pub mod pac { TWIM30_NS as TWIM30, TWIS30_NS as TWIS30, UARTE30_NS as UARTE30, - RTC30_NS as RTC30, COMP_NS as COMP, LPCOMP_NS as LPCOMP, WDT31_NS as WDT31, @@ -127,7 +125,6 @@ pub mod pac { PPIB10_S as PPIB10, PPIB11_S as PPIB11, TIMER10_S as TIMER10, - RTC10_S as RTC10, EGU10_S as EGU10, RADIO_S as RADIO, SPU20_S as SPU20, @@ -180,7 +177,6 @@ pub mod pac { TWIM30_S as TWIM30, TWIS30_S as TWIS30, UARTE30_S as UARTE30, - RTC30_S as RTC30, COMP_S as COMP, LPCOMP_S as LPCOMP, WDT30_S as WDT30, @@ -417,11 +413,19 @@ embassy_hal_internal::peripherals! { P2_10, // GRTC - GRTC, - - // RTC - RTC10, - RTC30, + GRTC_CH0, + #[cfg(not(feature = "time-driver-grtc"))] + GRTC_CH1, + GRTC_CH2, + GRTC_CH3, + GRTC_CH4, + GRTC_CH5, + GRTC_CH6, + GRTC_CH7, + GRTC_CH8, + GRTC_CH9, + GRTC_CH10, + GRTC_CH11, // PWM PWM20, @@ -548,9 +552,6 @@ cfg_if::cfg_if! { } } -impl_rtc!(RTC10, RTC10, RTC10); -impl_rtc!(RTC30, RTC30, RTC30); - #[cfg(feature = "_ns")] impl_wdt!(WDT, WDT31, WDT31, 0); #[cfg(feature = "_s")] @@ -703,7 +704,6 @@ embassy_hal_internal::interrupt_mod!( TIMER00, SPU10, TIMER10, - RTC10, EGU10, RADIO_0, RADIO_1, @@ -737,11 +737,9 @@ embassy_hal_internal::interrupt_mod!( GRTC_3, SPU30, SERIAL30, - RTC30, COMP_LPCOMP, WDT30, WDT31, GPIOTE30_0, GPIOTE30_1, - CLOCK_POWER, ); diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 28d2119ae..e8762b00d 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -153,6 +153,7 @@ pub mod reset; #[cfg(not(feature = "_nrf54l"))] #[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf91")))] pub mod rng; +#[cfg(not(feature = "_nrf54l"))] pub mod rtc; #[cfg(not(any(feature = "_nrf51", feature = "nrf52820", feature = "_nrf5340-net")))] pub mod saadc; diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index b723e2334..de1974fb7 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -1,4 +1,5 @@ use core::cell::{Cell, RefCell}; +#[cfg(not(feature = "_grtc"))] use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; use critical_section::CriticalSection; @@ -8,21 +9,27 @@ use embassy_time_driver::Driver; use embassy_time_queue_utils::Queue; use crate::interrupt::InterruptExt; +#[cfg(feature = "_grtc")] +use crate::pac::grtc::vals::Busy; use crate::{interrupt, pac}; -#[cfg(feature = "_nrf54l")] -fn rtc() -> pac::rtc::Rtc { - pac::RTC30 +#[cfg(feature = "_grtc")] +fn rtc() -> pac::grtc::Grtc { + pac::GRTC } -#[cfg(not(feature = "_nrf54l"))] + +#[cfg(not(feature = "_grtc"))] fn rtc() -> pac::rtc::Rtc { pac::RTC1 } /// Calculate the timestamp from the period count and the tick count. /// -/// The RTC counter is 24 bit. Ticking at 32768hz, it overflows every ~8 minutes. This is -/// too short. We must make it "never" overflow. +/// For nRF54 devices and newer, the GRTC counter is 52 bits, so the time driver uses the +/// syscounter and ignores the periods handling, since it overflows every 142 years. +/// +/// For most other devices, the RTC counter is 24 bit. Ticking at 32768hz, it overflows every ~8 minutes. +/// This is too short. We must make it "never" overflow. /// /// The obvious way would be to count overflow periods. Every time the counter overflows, /// increase a `periods` variable. `now()` simply does `periods << 24 + counter`. So, the logic @@ -64,14 +71,39 @@ fn rtc() -> pac::rtc::Rtc { /// `period` is a 32bit integer, so It overflows on 2^32 * 2^23 / 32768 seconds of uptime, which is 34865 /// years. For comparison, flash memory like the one containing your firmware is usually rated to retain /// data for only 10-20 years. 34865 years is long enough! +#[cfg(not(feature = "_grtc"))] fn calc_now(period: u32, counter: u32) -> u64 { ((period as u64) << 23) + ((counter ^ ((period & 1) << 23)) as u64) } +#[cfg(feature = "_grtc")] +fn syscounter() -> u64 { + let r = rtc(); + r.syscounter(0).active().write(|w| w.set_active(true)); + loop { + let countl: u32 = r.syscounter(0).syscounterl().read(); + let counth = r.syscounter(0).syscounterh().read(); + + if counth.busy() == Busy::READY && !counth.overflow() { + let counth: u32 = counth.value(); + let count = countl as u64 | ((counth as u64) << 32); + r.syscounter(0).active().write(|w| w.set_active(false)); + return count; + } + // If overflow or not ready, loop will re-read both registers + } +} + +#[cfg(not(feature = "_grtc"))] fn compare_n(n: usize) -> u32 { 1 << (n + 16) } +#[cfg(feature = "_grtc")] +fn compare_n(n: usize) -> u32 { + 1 << n // GRTC uses bits 0-11 for COMPARE[0-11] +} + #[cfg(test)] mod test { use super::*; @@ -108,6 +140,7 @@ impl AlarmState { struct RtcDriver { /// Number of 2^23 periods elapsed since boot. + #[cfg(not(feature = "_grtc"))] period: AtomicU32, /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. alarms: Mutex, @@ -115,6 +148,7 @@ struct RtcDriver { } embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { + #[cfg(not(feature = "_grtc"))] period: AtomicU32::new(0), alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), queue: Mutex::new(RefCell::new(Queue::new())), @@ -123,25 +157,48 @@ embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { impl RtcDriver { fn init(&'static self, irq_prio: crate::interrupt::Priority) { let r = rtc(); - r.cc(3).write(|w| w.set_compare(0x800000)); + // Chips without GRTC needs to deal with overflow + #[cfg(not(feature = "_grtc"))] + { + r.cc(3).write(|w| w.set_compare(0x800000)); - r.intenset().write(|w| { - w.set_ovrflw(true); - w.set_compare(3, true); - }); + r.intenset().write(|w| { + w.set_ovrflw(true); + w.set_compare(3, true); + }); + } + #[cfg(feature = "_grtc")] + { + r.mode().write(|w| { + w.set_syscounteren(true); + }); + } r.tasks_clear().write_value(1); r.tasks_start().write_value(1); // Wait for clear + #[cfg(not(feature = "_grtc"))] while r.counter().read().0 != 0 {} - #[cfg(feature = "_nrf54l")] + #[cfg(feature = "_grtc")] + { + // According to datasheet, we need to wait for STATUS.LFTIMER.READY + // before the timer is actually started + loop { + if r.status().lftimer().read().ready() { + break; + } + } + // Keep SYSCOUNTER[0] always active so we can read it anytime without BUSY waits + } + + #[cfg(feature = "_grtc")] { - interrupt::RTC30.set_priority(irq_prio); - unsafe { interrupt::RTC30.enable() }; + interrupt::GRTC_1.set_priority(irq_prio); + unsafe { interrupt::GRTC_1.enable() }; } - #[cfg(not(feature = "_nrf54l"))] + #[cfg(not(feature = "_grtc"))] { interrupt::RTC1.set_priority(irq_prio); unsafe { interrupt::RTC1.enable() }; @@ -150,11 +207,14 @@ impl RtcDriver { fn on_interrupt(&self) { let r = rtc(); + + #[cfg(not(feature = "_grtc"))] if r.events_ovrflw().read() == 1 { r.events_ovrflw().write_value(0); self.next_period(); } + #[cfg(not(feature = "_grtc"))] if r.events_compare(3).read() == 1 { r.events_compare(3).write_value(0); self.next_period(); @@ -169,6 +229,7 @@ impl RtcDriver { } } + #[cfg(not(feature = "_grtc"))] fn next_period(&self) { critical_section::with(|cs| { let r = rtc(); @@ -190,7 +251,10 @@ impl RtcDriver { fn trigger_alarm(&self, cs: CriticalSection) { let n = 0; let r = rtc(); + #[cfg(not(feature = "_grtc"))] r.intenclr().write(|w| w.0 = compare_n(n)); + #[cfg(feature = "_grtc")] + r.intenclr(1).write(|w| w.0 = compare_n(n)); let alarm = &self.alarms.borrow(cs); alarm.timestamp.set(u64::MAX); @@ -214,7 +278,10 @@ impl RtcDriver { if timestamp <= t { // If alarm timestamp has passed the alarm will not fire. // Disarm the alarm and return `false` to indicate that. + #[cfg(not(feature = "_grtc"))] r.intenclr().write(|w| w.0 = compare_n(n)); + #[cfg(feature = "_grtc")] + r.intenclr(1).write(|w| w.0 = compare_n(n)); alarm.timestamp.set(u64::MAX); @@ -226,7 +293,7 @@ impl RtcDriver { // Write the CC value regardless of whether we're going to enable it now or not. // This way, when we enable it later, the right value is already set. - // nrf52 docs say: + // nrf52 docs say : // If the COUNTER is N, writing N or N+1 to a CC register may not trigger a COMPARE event. // To workaround this, we never write a timestamp smaller than N+3. // N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc. @@ -238,22 +305,39 @@ impl RtcDriver { // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time, // and we don't do that here. - let safe_timestamp = timestamp.max(t + 3); - r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF)); - - let diff = timestamp - t; - if diff < 0xc00000 { - r.intenset().write(|w| w.0 = compare_n(n)); - - // If we have not passed the timestamp, we can be sure the alarm will be invoked. Otherwise, - // we need to retry setting the alarm. - if self.now() + 2 <= timestamp { + #[cfg(not(feature = "_grtc"))] + { + let safe_timestamp = timestamp.max(t + 3); + r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF)); + let diff = timestamp - t; + if diff < 0xc00000 { + r.intenset().write(|w| w.0 = compare_n(n)); + + // If we have not passed the timestamp, we can be sure the alarm will be invoked. Otherwise, + // we need to retry setting the alarm. + if self.now() + 2 <= timestamp { + return true; + } + } else { + // If it's too far in the future, don't setup the compare channel yet. + // It will be setup later by `next_period`. + r.intenclr().write(|w| w.0 = compare_n(n)); return true; } - } else { - // If it's too far in the future, don't setup the compare channel yet. - // It will be setup later by `next_period`. - r.intenclr().write(|w| w.0 = compare_n(n)); + } + + // The nRF54 datasheet states that 'The EVENTS_COMPARE[n] event is generated immediately if the + // configured compare value at CC[n] is less than the current SYSCOUNTER value.'. This means we + // can write the expected timestamp and be sure the alarm is triggered. + #[cfg(feature = "_grtc")] + { + let ccl = timestamp as u32; + let cch = (timestamp >> 32) as u32 & 0xFFFFF; // 20 bits for CCH + + r.cc(n).ccl().write_value(ccl); + r.cc(n).cch().write(|w| w.set_cch(cch)); + r.intenset(1).write(|w| w.0 = compare_n(n)); + return true; } } @@ -261,6 +345,7 @@ impl RtcDriver { } impl Driver for RtcDriver { + #[cfg(not(feature = "_grtc"))] fn now(&self) -> u64 { // `period` MUST be read before `counter`, see comment at the top for details. let period = self.period.load(Ordering::Relaxed); @@ -269,10 +354,14 @@ impl Driver for RtcDriver { calc_now(period, counter) } + #[cfg(feature = "_grtc")] + fn now(&self) -> u64 { + syscounter() + } + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { critical_section::with(|cs| { let mut queue = self.queue.borrow(cs).borrow_mut(); - if queue.schedule_wake(at, waker) { let mut next = queue.next_expiration(self.now()); while !self.set_alarm(cs, next) { @@ -283,14 +372,14 @@ impl Driver for RtcDriver { } } -#[cfg(feature = "_nrf54l")] +#[cfg(feature = "_grtc")] #[cfg(feature = "rt")] #[interrupt] -fn RTC30() { +fn GRTC_1() { DRIVER.on_interrupt() } -#[cfg(not(feature = "_nrf54l"))] +#[cfg(not(feature = "_grtc"))] #[cfg(feature = "rt")] #[interrupt] fn RTC1() { -- cgit From 9842b8b8927ff3de0bde2bf78a49b3d52bd20e5a Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 26 Nov 2025 14:36:57 +0100 Subject: fix: update saadc after API changes --- embassy-nrf/src/saadc.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'embassy-nrf/src') diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index ca8cbd73e..ab0e2b86a 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -771,7 +771,7 @@ pub(crate) trait SealedInput { fn port(&self) -> u8; #[cfg(feature = "_nrf54l")] - fn internal(&self) -> vals::Internal; + fn internal(&self) -> vals::PselpInternal; #[cfg(feature = "_nrf54l")] fn connect(&self) -> vals::PselpConnect; @@ -832,7 +832,7 @@ pub struct AnyInput<'a> { pub struct AnyInput<'a> { pin: u8, port: u8, - internal: vals::Internal, + internal: vals::PselpInternal, connect: vals::PselpConnect, _phantom: PhantomData<&'a ()>, } @@ -881,7 +881,7 @@ impl SealedInput for AnyInput<'_> { } #[cfg(feature = "_nrf54l")] - fn internal(&self) -> vals::Internal { + fn internal(&self) -> vals::PselpInternal { self.internal } @@ -923,8 +923,8 @@ macro_rules! impl_saadc_input { $port } - fn internal(&self) -> crate::pac::saadc::vals::Internal { - crate::pac::saadc::vals::Internal::$internal + fn internal(&self) -> crate::pac::saadc::vals::PselpInternal { + crate::pac::saadc::vals::PselpInternal::$internal } fn connect(&self) -> crate::pac::saadc::vals::PselpConnect { -- cgit From da96ef9d51730975b52fa2c92ec27236b68f1daf Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 26 Nov 2025 14:42:46 +0100 Subject: chore: cleanup --- embassy-nrf/src/time_driver.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'embassy-nrf/src') diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index de1974fb7..35f65bd64 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -182,15 +182,10 @@ impl RtcDriver { while r.counter().read().0 != 0 {} #[cfg(feature = "_grtc")] - { - // According to datasheet, we need to wait for STATUS.LFTIMER.READY - // before the timer is actually started - loop { - if r.status().lftimer().read().ready() { - break; - } + loop { + if r.status().lftimer().read().ready() { + break; } - // Keep SYSCOUNTER[0] always active so we can read it anytime without BUSY waits } #[cfg(feature = "_grtc")] -- cgit From ec93a56a2e5ba4403a603b9015daa2e94712917b Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 27 Nov 2025 09:22:24 +0100 Subject: fix: patch back clock power irq --- embassy-nrf/src/chips/nrf54l15_app.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'embassy-nrf/src') diff --git a/embassy-nrf/src/chips/nrf54l15_app.rs b/embassy-nrf/src/chips/nrf54l15_app.rs index b6c9fd110..8e6595248 100644 --- a/embassy-nrf/src/chips/nrf54l15_app.rs +++ b/embassy-nrf/src/chips/nrf54l15_app.rs @@ -742,4 +742,5 @@ embassy_hal_internal::interrupt_mod!( WDT31, GPIOTE30_0, GPIOTE30_1, + CLOCK_POWER, ); -- cgit