From 70e2c052056e31f06ac67d69fda503f07cae46d0 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 7 May 2025 08:33:03 -0700 Subject: Rename rtc.rs to time_driver.rs We will add another time driver for user selection. --- embassy-imxrt/src/lib.rs | 4 +- embassy-imxrt/src/rtc.rs | 254 --------------------------------------- embassy-imxrt/src/time_driver.rs | 254 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 256 deletions(-) delete mode 100644 embassy-imxrt/src/rtc.rs create mode 100644 embassy-imxrt/src/time_driver.rs diff --git a/embassy-imxrt/src/lib.rs b/embassy-imxrt/src/lib.rs index 5fbf3244b..fdf07b433 100644 --- a/embassy-imxrt/src/lib.rs +++ b/embassy-imxrt/src/lib.rs @@ -22,7 +22,7 @@ pub mod gpio; pub mod iopctl; #[cfg(feature = "_time-driver")] -pub mod rtc; +pub mod time_driver; // This mod MUST go last, so that it sees all the `impl_foo!' macros #[cfg_attr(feature = "mimxrt633s", path = "chips/mimxrt633s.rs")] @@ -137,7 +137,7 @@ pub fn init(config: config::Config) -> Peripherals { // init RTC time driver #[cfg(feature = "_time-driver")] - rtc::init(config.time_interrupt_priority); + time_driver::init(config.time_interrupt_priority); peripherals } diff --git a/embassy-imxrt/src/rtc.rs b/embassy-imxrt/src/rtc.rs deleted file mode 100644 index 56a8f7397..000000000 --- a/embassy-imxrt/src/rtc.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! RTC Time Driver. -use core::cell::{Cell, RefCell}; -use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; - -use critical_section::CriticalSection; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::blocking_mutex::Mutex; -use embassy_time_driver::Driver; -use embassy_time_queue_utils::Queue; - -use crate::interrupt::InterruptExt; -use crate::{interrupt, pac}; - -fn rtc() -> &'static pac::rtc::RegisterBlock { - unsafe { &*pac::Rtc::ptr() } -} - -/// Calculate the timestamp from the period count and the tick count. -/// -/// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches -/// the expected range for the `period` parity, we're done. If it doesn't, this means that -/// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value -/// corresponds to the next period. -/// -/// the 1kHz RTC counter is 16 bits and RTC doesn't have separate compare channels, -/// so using a 32 bit GPREG0-2 as counter, compare, and int_en -/// `period` is a 32bit integer, gpreg 'counter' is 31 bits plus the parity bit for overflow detection -fn calc_now(period: u32, counter: u32) -> u64 { - ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) as u64) -} - -struct AlarmState { - timestamp: Cell, -} - -unsafe impl Send for AlarmState {} - -impl AlarmState { - const fn new() -> Self { - Self { - timestamp: Cell::new(u64::MAX), - } - } -} - -struct Rtc { - /// Number of 2^31 periods elapsed since boot. - period: AtomicU32, - /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. - alarms: Mutex, - queue: Mutex>, -} - -embassy_time_driver::time_driver_impl!(static DRIVER: Rtc = Rtc { - period: AtomicU32::new(0), - alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), - queue: Mutex::new(RefCell::new(Queue::new())), -}); - -impl Rtc { - /// Access the GPREG0 register to use it as a 31-bit counter. - #[inline] - fn counter_reg(&self) -> &pac::rtc::Gpreg { - rtc().gpreg(0) - } - - /// Access the GPREG1 register to use it as a compare register for triggering alarms. - #[inline] - fn compare_reg(&self) -> &pac::rtc::Gpreg { - rtc().gpreg(1) - } - - /// Access the GPREG2 register to use it to enable or disable interrupts (int_en). - #[inline] - fn int_en_reg(&self) -> &pac::rtc::Gpreg { - rtc().gpreg(2) - } - - fn init(&'static self, irq_prio: crate::interrupt::Priority) { - let r = rtc(); - // enable RTC int (1kHz since subsecond doesn't generate an int) - r.ctrl().modify(|_r, w| w.rtc1khz_en().set_bit()); - // TODO: low power support. line above is leaving out write to .wakedpd_en().set_bit()) - // which enables wake from deep power down - - // safety: Writing to the gregs is always considered unsafe, gpreg1 is used - // as a compare register for triggering an alarm so to avoid unnecessary triggers - // after initialization, this is set to 0x:FFFF_FFFF - self.compare_reg().write(|w| unsafe { w.gpdata().bits(u32::MAX) }); - // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe. - // The following loads 10 into the count-down timer. - r.wake().write(|w| unsafe { w.bits(0xA) }); - interrupt::RTC.set_priority(irq_prio); - unsafe { interrupt::RTC.enable() }; - } - - #[cfg(feature = "rt")] - fn on_interrupt(&self) { - let r = rtc(); - // This interrupt fires every 10 ticks of the 1kHz RTC high res clk and adds - // 10 to the 31 bit counter gpreg0. The 32nd bit is used for parity detection - // This is done to avoid needing to calculate # of ticks spent on interrupt - // handlers to recalibrate the clock between interrupts - // - // TODO: this is admittedly not great for power that we're generating this - // many interrupts, will probably get updated in future iterations. - if r.ctrl().read().wake1khz().bit_is_set() { - r.ctrl().modify(|_r, w| w.wake1khz().set_bit()); - // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe. - // The following reloads 10 into the count-down timer after it triggers an int. - // The countdown begins anew after the write so time can continue to be measured. - r.wake().write(|w| unsafe { w.bits(0xA) }); - if (self.counter_reg().read().bits() + 0xA) > 0x8000_0000 { - // if we're going to "overflow", increase the period - self.next_period(); - let rollover_diff = 0x8000_0000 - (self.counter_reg().read().bits() + 0xA); - // safety: writing to gpregs is always considered unsafe. In order to - // not "lose" time when incrementing the period, gpreg0, the extended - // counter, is restarted at the # of ticks it would overflow by - self.counter_reg().write(|w| unsafe { w.bits(rollover_diff) }); - } else { - self.counter_reg().modify(|r, w| unsafe { w.bits(r.bits() + 0xA) }); - } - } - - critical_section::with(|cs| { - // gpreg2 as an "int_en" set by next_period(). This is - // 1 when the timestamp for the alarm deadline expires - // before the counter register overflows again. - if self.int_en_reg().read().gpdata().bits() == 1 { - // gpreg0 is our extended counter register, check if - // our counter is larger than the compare value - if self.counter_reg().read().bits() > self.compare_reg().read().bits() { - self.trigger_alarm(cs); - } - } - }) - } - - #[cfg(feature = "rt")] - fn next_period(&self) { - critical_section::with(|cs| { - let period = self - .period - .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |p| Some(p + 1)) - .unwrap_or_else(|p| { - trace!("Unable to increment period. Time is now inaccurate"); - // TODO: additional error handling beyond logging - - p - }); - let t = (period as u64) << 31; - - let alarm = &self.alarms.borrow(cs); - let at = alarm.timestamp.get(); - if at < t + 0xc000_0000 { - // safety: writing to gpregs is always unsafe, gpreg2 is an alarm - // enable. If the alarm must trigger within the next period, then - // just enable it. `set_alarm` has already set the correct CC val. - self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) }); - } - }) - } - - #[must_use] - fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { - let alarm = self.alarms.borrow(cs); - alarm.timestamp.set(timestamp); - - let t = self.now(); - if timestamp <= t { - // safety: Writing to the gpregs is always unsafe, gpreg2 is - // always just used as the alarm enable for the timer driver. - // If alarm timestamp has passed the alarm will not fire. - // Disarm the alarm and return `false` to indicate that. - self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) }); - - alarm.timestamp.set(u64::MAX); - - return false; - } - - // If it hasn't triggered yet, setup it by writing to the compare field - // An alarm can be delayed, but this is allowed by the Alarm trait contract. - // What's not allowed is triggering alarms *before* their scheduled time, - let safe_timestamp = timestamp.max(t + 10); //t+3 was done for nrf chip, choosing 10 - - // safety: writing to the gregs is always unsafe. When a new alarm is set, - // the compare register, gpreg1, is set to the last 31 bits of the timestamp - // as the 32nd and final bit is used for the parity check in `next_period` - // `period` will be used for the upper bits in a timestamp comparison. - self.compare_reg() - .modify(|_r, w| unsafe { w.bits(safe_timestamp as u32 & 0x7FFF_FFFF) }); - - // The following checks that the difference in timestamp is less than the overflow period - let diff = timestamp - t; - if diff < 0xc000_0000 { - // this is 0b11 << (30). NRF chip used 23 bit periods and checked against 0b11<<22 - - // safety: writing to the gpregs is always unsafe. If the alarm - // must trigger within the next period, set the "int enable" - self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) }); - } else { - // safety: writing to the gpregs is always unsafe. If alarm must trigger - // some time after the current period, too far in the future, don't setup - // the alarm enable, gpreg2, yet. It will be setup later by `next_period`. - self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) }); - } - - true - } - - #[cfg(feature = "rt")] - fn trigger_alarm(&self, cs: CriticalSection) { - let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); - while !self.set_alarm(cs, next) { - next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); - } - } -} - -impl Driver for Rtc { - fn now(&self) -> u64 { - // `period` MUST be read before `counter`, see comment at the top for details. - let period = self.period.load(Ordering::Acquire); - compiler_fence(Ordering::Acquire); - let counter = self.counter_reg().read().bits(); - calc_now(period, counter) - } - - 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) { - next = queue.next_expiration(self.now()); - } - } - }) - } -} - -#[cfg(feature = "rt")] -#[allow(non_snake_case)] -#[interrupt] -fn RTC() { - DRIVER.on_interrupt() -} - -pub(crate) fn init(irq_prio: crate::interrupt::Priority) { - DRIVER.init(irq_prio) -} diff --git a/embassy-imxrt/src/time_driver.rs b/embassy-imxrt/src/time_driver.rs new file mode 100644 index 000000000..56a8f7397 --- /dev/null +++ b/embassy-imxrt/src/time_driver.rs @@ -0,0 +1,254 @@ +//! RTC Time Driver. +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; + +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; + +use crate::interrupt::InterruptExt; +use crate::{interrupt, pac}; + +fn rtc() -> &'static pac::rtc::RegisterBlock { + unsafe { &*pac::Rtc::ptr() } +} + +/// Calculate the timestamp from the period count and the tick count. +/// +/// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches +/// the expected range for the `period` parity, we're done. If it doesn't, this means that +/// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value +/// corresponds to the next period. +/// +/// the 1kHz RTC counter is 16 bits and RTC doesn't have separate compare channels, +/// so using a 32 bit GPREG0-2 as counter, compare, and int_en +/// `period` is a 32bit integer, gpreg 'counter' is 31 bits plus the parity bit for overflow detection +fn calc_now(period: u32, counter: u32) -> u64 { + ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) as u64) +} + +struct AlarmState { + timestamp: Cell, +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + } + } +} + +struct Rtc { + /// Number of 2^31 periods elapsed since boot. + period: AtomicU32, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, + queue: Mutex>, +} + +embassy_time_driver::time_driver_impl!(static DRIVER: Rtc = Rtc { + period: AtomicU32::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), + queue: Mutex::new(RefCell::new(Queue::new())), +}); + +impl Rtc { + /// Access the GPREG0 register to use it as a 31-bit counter. + #[inline] + fn counter_reg(&self) -> &pac::rtc::Gpreg { + rtc().gpreg(0) + } + + /// Access the GPREG1 register to use it as a compare register for triggering alarms. + #[inline] + fn compare_reg(&self) -> &pac::rtc::Gpreg { + rtc().gpreg(1) + } + + /// Access the GPREG2 register to use it to enable or disable interrupts (int_en). + #[inline] + fn int_en_reg(&self) -> &pac::rtc::Gpreg { + rtc().gpreg(2) + } + + fn init(&'static self, irq_prio: crate::interrupt::Priority) { + let r = rtc(); + // enable RTC int (1kHz since subsecond doesn't generate an int) + r.ctrl().modify(|_r, w| w.rtc1khz_en().set_bit()); + // TODO: low power support. line above is leaving out write to .wakedpd_en().set_bit()) + // which enables wake from deep power down + + // safety: Writing to the gregs is always considered unsafe, gpreg1 is used + // as a compare register for triggering an alarm so to avoid unnecessary triggers + // after initialization, this is set to 0x:FFFF_FFFF + self.compare_reg().write(|w| unsafe { w.gpdata().bits(u32::MAX) }); + // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe. + // The following loads 10 into the count-down timer. + r.wake().write(|w| unsafe { w.bits(0xA) }); + interrupt::RTC.set_priority(irq_prio); + unsafe { interrupt::RTC.enable() }; + } + + #[cfg(feature = "rt")] + fn on_interrupt(&self) { + let r = rtc(); + // This interrupt fires every 10 ticks of the 1kHz RTC high res clk and adds + // 10 to the 31 bit counter gpreg0. The 32nd bit is used for parity detection + // This is done to avoid needing to calculate # of ticks spent on interrupt + // handlers to recalibrate the clock between interrupts + // + // TODO: this is admittedly not great for power that we're generating this + // many interrupts, will probably get updated in future iterations. + if r.ctrl().read().wake1khz().bit_is_set() { + r.ctrl().modify(|_r, w| w.wake1khz().set_bit()); + // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe. + // The following reloads 10 into the count-down timer after it triggers an int. + // The countdown begins anew after the write so time can continue to be measured. + r.wake().write(|w| unsafe { w.bits(0xA) }); + if (self.counter_reg().read().bits() + 0xA) > 0x8000_0000 { + // if we're going to "overflow", increase the period + self.next_period(); + let rollover_diff = 0x8000_0000 - (self.counter_reg().read().bits() + 0xA); + // safety: writing to gpregs is always considered unsafe. In order to + // not "lose" time when incrementing the period, gpreg0, the extended + // counter, is restarted at the # of ticks it would overflow by + self.counter_reg().write(|w| unsafe { w.bits(rollover_diff) }); + } else { + self.counter_reg().modify(|r, w| unsafe { w.bits(r.bits() + 0xA) }); + } + } + + critical_section::with(|cs| { + // gpreg2 as an "int_en" set by next_period(). This is + // 1 when the timestamp for the alarm deadline expires + // before the counter register overflows again. + if self.int_en_reg().read().gpdata().bits() == 1 { + // gpreg0 is our extended counter register, check if + // our counter is larger than the compare value + if self.counter_reg().read().bits() > self.compare_reg().read().bits() { + self.trigger_alarm(cs); + } + } + }) + } + + #[cfg(feature = "rt")] + fn next_period(&self) { + critical_section::with(|cs| { + let period = self + .period + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |p| Some(p + 1)) + .unwrap_or_else(|p| { + trace!("Unable to increment period. Time is now inaccurate"); + // TODO: additional error handling beyond logging + + p + }); + let t = (period as u64) << 31; + + let alarm = &self.alarms.borrow(cs); + let at = alarm.timestamp.get(); + if at < t + 0xc000_0000 { + // safety: writing to gpregs is always unsafe, gpreg2 is an alarm + // enable. If the alarm must trigger within the next period, then + // just enable it. `set_alarm` has already set the correct CC val. + self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) }); + } + }) + } + + #[must_use] + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let alarm = self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + // safety: Writing to the gpregs is always unsafe, gpreg2 is + // always just used as the alarm enable for the timer driver. + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) }); + + alarm.timestamp.set(u64::MAX); + + return false; + } + + // If it hasn't triggered yet, setup it by writing to the compare field + // An alarm can be delayed, but this is allowed by the Alarm trait contract. + // What's not allowed is triggering alarms *before* their scheduled time, + let safe_timestamp = timestamp.max(t + 10); //t+3 was done for nrf chip, choosing 10 + + // safety: writing to the gregs is always unsafe. When a new alarm is set, + // the compare register, gpreg1, is set to the last 31 bits of the timestamp + // as the 32nd and final bit is used for the parity check in `next_period` + // `period` will be used for the upper bits in a timestamp comparison. + self.compare_reg() + .modify(|_r, w| unsafe { w.bits(safe_timestamp as u32 & 0x7FFF_FFFF) }); + + // The following checks that the difference in timestamp is less than the overflow period + let diff = timestamp - t; + if diff < 0xc000_0000 { + // this is 0b11 << (30). NRF chip used 23 bit periods and checked against 0b11<<22 + + // safety: writing to the gpregs is always unsafe. If the alarm + // must trigger within the next period, set the "int enable" + self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) }); + } else { + // safety: writing to the gpregs is always unsafe. If alarm must trigger + // some time after the current period, too far in the future, don't setup + // the alarm enable, gpreg2, yet. It will be setup later by `next_period`. + self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) }); + } + + true + } + + #[cfg(feature = "rt")] + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } + } +} + +impl Driver for Rtc { + fn now(&self) -> u64 { + // `period` MUST be read before `counter`, see comment at the top for details. + let period = self.period.load(Ordering::Acquire); + compiler_fence(Ordering::Acquire); + let counter = self.counter_reg().read().bits(); + calc_now(period, counter) + } + + 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) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +#[cfg(feature = "rt")] +#[allow(non_snake_case)] +#[interrupt] +fn RTC() { + DRIVER.on_interrupt() +} + +pub(crate) fn init(irq_prio: crate::interrupt::Priority) { + DRIVER.init(irq_prio) +} -- cgit From 64ce271af526a9311b0c1c251b19b5ce79032551 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 7 May 2025 10:39:25 -0700 Subject: clocks: split clock and reset operations Some peripherals need to control clock without touching the reset. Signed-off-by: Felipe Balbi --- embassy-imxrt/src/clocks.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/embassy-imxrt/src/clocks.rs b/embassy-imxrt/src/clocks.rs index 1d36fb142..5be5f3925 100644 --- a/embassy-imxrt/src/clocks.rs +++ b/embassy-imxrt/src/clocks.rs @@ -1561,7 +1561,8 @@ pub(crate) unsafe fn init(config: ClockConfig) -> Result<(), ClockError> { ///Trait to expose perph clocks trait SealedSysconPeripheral { - fn enable_and_reset_perph_clock(); + fn enable_perph_clock(); + fn reset_perph(); fn disable_perph_clock(); } @@ -1574,7 +1575,18 @@ pub trait SysconPeripheral: SealedSysconPeripheral + 'static {} /// /// Peripheral must not be in use. pub fn enable_and_reset() { - T::enable_and_reset_perph_clock(); + T::enable_perph_clock(); + T::reset_perph(); +} + +/// Enables peripheral `T`. +pub fn enable() { + T::enable_perph_clock(); +} + +/// Reset peripheral `T`. +pub fn reset() { + T::reset_perph(); } /// Disables peripheral `T`. @@ -1588,15 +1600,21 @@ pub fn disable() { macro_rules! impl_perph_clk { ($peripheral:ident, $clkctl:ident, $clkreg:ident, $rstctl:ident, $rstreg:ident, $bit:expr) => { impl SealedSysconPeripheral for crate::peripherals::$peripheral { - fn enable_and_reset_perph_clock() { + fn enable_perph_clock() { // SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1 let cc1 = unsafe { pac::$clkctl::steal() }; - let rc1 = unsafe { pac::$rstctl::steal() }; paste! { // SAFETY: unsafe due to the use of bits() cc1.[<$clkreg _set>]().write(|w| unsafe { w.bits(1 << $bit) }); + } + } + fn reset_perph() { + // SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1 + let rc1 = unsafe { pac::$rstctl::steal() }; + + paste! { // SAFETY: unsafe due to the use of bits() rc1.[<$rstreg _clr>]().write(|w| unsafe { w.bits(1 << $bit) }); } @@ -1605,12 +1623,8 @@ macro_rules! impl_perph_clk { fn disable_perph_clock() { // SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1 let cc1 = unsafe { pac::$clkctl::steal() }; - let rc1 = unsafe { pac::$rstctl::steal() }; paste! { - // SAFETY: unsafe due to the use of bits() - rc1.[<$rstreg _set>]().write(|w| unsafe { w.bits(1 << $bit) }); - // SAFETY: unsafe due to the use of bits() cc1.[<$clkreg _clr>]().write(|w| unsafe { w.bits(1 << $bit) }); } -- cgit From 297ff3d03229bedb2582c171be23b488ecc4e520 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 7 May 2025 12:56:58 -0700 Subject: clocks: remove defmt messages Whenever any of the defmt-timestamp-uptime* features is enabled, defmt will insert code that reads the timestamp in order to embed it into the format string. This means that we *must* have a functional time driver by the time the very first defmt message is printed. Because clocks.rs is the part of the code setting up clocks that may, indeed, be required by the chosen clock driver, it cannot contain any defmt messages, otherwise it will trigger a read to a function that does not yet exist. Signed-off-by: Felipe Balbi --- embassy-imxrt/src/clocks.rs | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/embassy-imxrt/src/clocks.rs b/embassy-imxrt/src/clocks.rs index 5be5f3925..39c3e6238 100644 --- a/embassy-imxrt/src/clocks.rs +++ b/embassy-imxrt/src/clocks.rs @@ -1,8 +1,6 @@ //! Clock configuration for the `RT6xx` use core::sync::atomic::{AtomicU32, AtomicU8, Ordering}; -#[cfg(feature = "defmt")] -use defmt; use paste::paste; use crate::pac; @@ -503,7 +501,6 @@ impl ConfigurableClock for LposcConfig { } } } else { - error!("failed to convert desired clock rate, {:#}, to LPOSC Freq", freq); Err(ClockError::InvalidFrequency) } } @@ -549,7 +546,6 @@ impl ConfigurableClock for FfroConfig { Ok(()) } fn get_clock_rate(&self) -> Result { - trace!("getting ffro clock rate"); Ok(self.freq.load(Ordering::Relaxed)) } fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { @@ -616,7 +612,6 @@ impl ConfigurableClock for SfroConfig { fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { if self.state == State::Enabled { if freq == SFRO_FREQ { - trace!("Sfro frequency is already set at 16MHz"); Ok(()) } else { Err(ClockError::InvalidFrequency) @@ -677,7 +672,6 @@ impl MultiSourceClock for MainPllClkConfig { } MainPllClkSrc::SFRO => { if !clock_src_config.is_enabled() { - error!("Can't set SFRO as source for MainPll as it's not enabled"); return Err(ClockError::ClockNotEnabled); } // check if desired frequency is a valid multiple of 16m SFRO clock @@ -703,7 +697,6 @@ impl ConfigurableClock for MainPllClkConfig { } fn disable(&self) -> Result<(), ClockError> { if self.is_enabled() { - error!("Attempting to reset the Main Pll Clock, should be resetting its source"); Err(ClockError::ClockNotSupported) } else { Err(ClockError::ClockNotEnabled) @@ -719,7 +712,6 @@ impl ConfigurableClock for MainPllClkConfig { } fn set_clock_rate(&mut self, div: u8, mult: u8, freq: u32) -> Result<(), ClockError> { if self.is_enabled() { - trace!("attempting to set main pll clock rate"); // SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0 let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; @@ -741,15 +733,12 @@ impl ConfigurableClock for MainPllClkConfig { base_rate = r; } MainPllClkSrc::FFRO => { - trace!("found FFRO as source, wait a bit"); delay_loop_clocks(1000, desired_freq); match clkctl0.ffroctl0().read().trim_range().is_ffro_48mhz() { true => base_rate = Into::into(FfroFreq::Ffro48m), false => base_rate = Into::into(FfroFreq::Ffro60m), } - trace!("found ffro rate to be: {:#}", base_rate); if div == 2 { - trace!("dividing FFRO rate by 2"); clkctl0.syspll0clksel().write(|w| w.sel().ffro_div_2()); delay_loop_clocks(150, desired_freq); base_rate /= 2; @@ -763,10 +752,8 @@ impl ConfigurableClock for MainPllClkConfig { } }; base_rate *= u32::from(mult); - trace!("calculated base rate at: {:#}", base_rate); if base_rate != freq { // make sure to power syspll back up before returning the error - error!("invalid frequency found, powering syspll back up before returning error. Check div and mult"); // Clear System PLL reset clkctl0.syspll0ctl0().write(|w| w.reset().normal()); // Power up SYSPLL @@ -775,13 +762,11 @@ impl ConfigurableClock for MainPllClkConfig { .write(|w| w.syspllana_pd().clr_pdruncfg0().syspllldo_pd().clr_pdruncfg0()); return Err(ClockError::InvalidFrequency); } - trace!("setting default num and denom"); // SAFETY: unsafe needed to write the bits for the num and demon fields clkctl0.syspll0num().write(|w| unsafe { w.num().bits(0b0) }); clkctl0.syspll0denom().write(|w| unsafe { w.denom().bits(0b1) }); delay_loop_clocks(30, desired_freq); self.mult.store(mult, Ordering::Relaxed); - trace!("setting self.mult as: {:#}", mult); match mult { 16 => { clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_16()); @@ -803,7 +788,6 @@ impl ConfigurableClock for MainPllClkConfig { } _ => return Err(ClockError::InvalidMult), } - trace!("clear syspll reset"); // Clear System PLL reset clkctl0.syspll0ctl0().modify(|_r, w| w.reset().normal()); // Power up SYSPLL @@ -819,7 +803,6 @@ impl ConfigurableClock for MainPllClkConfig { clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().dsiable()); delay_loop_clocks(15, desired_freq); - trace!("setting new PFD0 bits"); // gate the output and clear bits. // SAFETY: unsafe needed to write the bits for pfd0 clkctl0 @@ -833,7 +816,6 @@ impl ConfigurableClock for MainPllClkConfig { .modify(|_r, w| unsafe { w.pfd0_clkgate().not_gated().pfd0().bits(0x12) }); // wait for ready bit to be set delay_loop_clocks(50, desired_freq); - trace!("waiting for mainpll clock to be ready"); while clkctl0.syspll0pfd().read().pfd0_clkrdy().bit_is_clear() {} // clear by writing a 1 clkctl0.syspll0pfd().modify(|_, w| w.pfd0_clkrdy().set_bit()); @@ -854,11 +836,9 @@ impl ConfigurableClock for MainPllClkConfig { impl MainPllClkConfig { /// Calculate the mult value of a desired frequency, return error if invalid pub(self) fn calc_mult(rate: u32, base_freq: u32) -> Result { - trace!("calculating mult for {:#} / {:#}", rate, base_freq); const VALIDMULTS: [u8; 6] = [16, 17, 20, 22, 27, 33]; if rate > base_freq && rate % base_freq == 0 { let mult = (rate / base_freq) as u8; - trace!("verifying that calculated mult {:#} is a valid one", mult); if VALIDMULTS.into_iter().any(|i| i == mult) { Ok(mult) } else { @@ -1112,7 +1092,6 @@ impl ConfigurableClock for MainClkConfig { Ok(()) } fn disable(&self) -> Result<(), ClockError> { - error!("Attempting to reset the main clock, should NOT happen during runtime"); Err(ClockError::ClockNotSupported) } fn get_clock_rate(&self) -> Result { @@ -1120,7 +1099,6 @@ impl ConfigurableClock for MainClkConfig { Ok(rate) } fn set_clock_rate(&mut self, _div: u8, _mult: u8, _freq: u32) -> Result<(), ClockError> { - error!("The multi-source set_clock_rate_and_source method should be used instead of set_clock_rate"); Err(ClockError::ClockNotSupported) } fn is_enabled(&self) -> bool { @@ -1145,7 +1123,6 @@ impl ConfigurableClock for ClkInConfig { } } fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { - trace!("Setting value of clk in config, this won't change the clock itself"); self.freq.as_ref().unwrap().store(freq, Ordering::Relaxed); Ok(()) } @@ -1188,7 +1165,6 @@ impl ConfigurableClock for RtcClkConfig { Ok(()) } fn disable(&self) -> Result<(), ClockError> { - error!("Resetting the RTC clock, this should NOT happen during runtime"); Err(ClockError::ClockNotSupported) } fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { @@ -1199,7 +1175,6 @@ impl ConfigurableClock for RtcClkConfig { match r { RtcFreq::Default1Hz => { if rtc.ctrl().read().rtc_en().is_enable() { - trace!("Attempting to enable an already enabled clock, RTC 1Hz"); } else { rtc.ctrl().modify(|_r, w| w.rtc_en().enable()); } @@ -1207,7 +1182,6 @@ impl ConfigurableClock for RtcClkConfig { } RtcFreq::HighResolution1khz => { if rtc.ctrl().read().rtc1khz_en().is_enable() { - trace!("Attempting to enable an already enabled clock, RTC 1Hz"); } else { rtc.ctrl().modify(|_r, w| w.rtc1khz_en().enable()); } @@ -1215,7 +1189,6 @@ impl ConfigurableClock for RtcClkConfig { } RtcFreq::SubSecond32kHz => { if rtc.ctrl().read().rtc_subsec_ena().is_enable() { - trace!("Attempting to enable an already enabled clock, RTC 1Hz"); } else { rtc.ctrl().modify(|_r, w| w.rtc_subsec_ena().enable()); } @@ -1245,18 +1218,12 @@ impl ConfigurableClock for RtcClkConfig { impl SysClkConfig { /// Updates the system core clock frequency, SW concept used for systick - fn update_sys_core_clock(&self) { - trace!( - "System core clock has been updated to {:?}, this involves no HW reg writes", - self.sysclkfreq.load(Ordering::Relaxed) - ); - } + fn update_sys_core_clock(&self) {} } impl ConfigurableClock for SysOscConfig { fn enable_and_reset(&self) -> Result<(), ClockError> { if self.state == State::Enabled { - trace!("SysOsc was already enabled"); return Ok(()); } @@ -1498,32 +1465,26 @@ impl ClockOutConfig { /// Using the config, enables all desired clocks to desired clock rates fn init_clock_hw(config: ClockConfig) -> Result<(), ClockError> { if let Err(e) = config.rtc.enable_and_reset() { - error!("couldn't Power on OSC for RTC, result: {:?}", e); return Err(e); } if let Err(e) = config.lposc.enable_and_reset() { - error!("couldn't Power on LPOSC, result: {:?}", e); return Err(e); } if let Err(e) = config.ffro.enable_and_reset() { - error!("couldn't Power on FFRO, result: {:?}", e); return Err(e); } if let Err(e) = config.sfro.enable_and_reset() { - error!("couldn't Power on SFRO, result: {:?}", e); return Err(e); } if let Err(e) = config.sys_osc.enable_and_reset() { - error!("Couldn't enable sys oscillator {:?}", e); return Err(e); } if let Err(e) = config.main_pll_clk.enable_and_reset() { - error!("Couldn't enable main pll clock {:?}", e); return Err(e); } @@ -1542,7 +1503,6 @@ fn init_clock_hw(config: ClockConfig) -> Result<(), ClockError> { init_syscpuahb_clk(); if let Err(e) = config.main_clk.enable_and_reset() { - error!("Couldn't enable main clock {:?}", e); return Err(e); } -- cgit From 42c62ba8999df08ad34d566f30f0a7199dbae083 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 7 May 2025 10:40:43 -0700 Subject: Add OS Event timer support Allow for the use of the OS Event timer as a time source. Signed-off-by: Felipe Balbi --- embassy-imxrt/Cargo.toml | 9 +- embassy-imxrt/src/lib.rs | 4 +- embassy-imxrt/src/time_driver.rs | 196 ++++++++++++++++++++++++++++++++++----- examples/mimxrt6/Cargo.toml | 4 +- 4 files changed, 184 insertions(+), 29 deletions(-) diff --git a/embassy-imxrt/Cargo.toml b/embassy-imxrt/Cargo.toml index d58de6353..f16002a8d 100644 --- a/embassy-imxrt/Cargo.toml +++ b/embassy-imxrt/Cargo.toml @@ -12,7 +12,7 @@ documentation = "https://docs.embassy.dev/embassy-imxrt" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-imxrt-v$VERSION/embassy-imxrt/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-imxrt/src/" -features = ["defmt", "unstable-pac", "time", "time-driver"] +features = ["defmt", "unstable-pac", "time", "time-driver-os-timer"] flavors = [ { regex_feature = "mimxrt6.*", target = "thumbv8m.main-none-eabihf" } ] @@ -37,9 +37,12 @@ defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "mimxr time = ["dep:embassy-time", "embassy-embedded-hal/time"] ## Enable custom embassy time-driver implementation, using 32KHz RTC -time-driver-rtc = ["_time-driver"] +time-driver-rtc = ["_time-driver", "embassy-time-driver?/tick-hz-1_000"] -_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] +## Enable custom embassy time-driver implementation, using 1MHz OS Timer +time-driver-os-timer = ["_time-driver", "embassy-time-driver?/tick-hz-1_000_000"] + +_time-driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] ## Reexport the PAC for the currently enabled chip at `embassy_imxrt::pac` (unstable) unstable-pac = [] diff --git a/embassy-imxrt/src/lib.rs b/embassy-imxrt/src/lib.rs index fdf07b433..ad9f81e88 100644 --- a/embassy-imxrt/src/lib.rs +++ b/embassy-imxrt/src/lib.rs @@ -132,12 +132,10 @@ pub fn init(config: config::Config) -> Peripherals { error!("unable to initialize Clocks for reason: {:?}", e); // Panic here? } - gpio::init(); } - - // init RTC time driver #[cfg(feature = "_time-driver")] time_driver::init(config.time_interrupt_priority); + gpio::init(); peripherals } diff --git a/embassy-imxrt/src/time_driver.rs b/embassy-imxrt/src/time_driver.rs index 56a8f7397..c68679d3e 100644 --- a/embassy-imxrt/src/time_driver.rs +++ b/embassy-imxrt/src/time_driver.rs @@ -1,5 +1,6 @@ -//! RTC Time Driver. +//! Time Driver. use core::cell::{Cell, RefCell}; +#[cfg(feature = "time-driver-rtc")] use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; use critical_section::CriticalSection; @@ -8,9 +9,26 @@ use embassy_sync::blocking_mutex::Mutex; use embassy_time_driver::Driver; use embassy_time_queue_utils::Queue; +#[cfg(feature = "time-driver-os-timer")] +use crate::clocks::enable; use crate::interrupt::InterruptExt; use crate::{interrupt, pac}; +struct AlarmState { + timestamp: Cell, +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + } + } +} + +#[cfg(feature = "time-driver-rtc")] fn rtc() -> &'static pac::rtc::RegisterBlock { unsafe { &*pac::Rtc::ptr() } } @@ -25,24 +43,19 @@ fn rtc() -> &'static pac::rtc::RegisterBlock { /// the 1kHz RTC counter is 16 bits and RTC doesn't have separate compare channels, /// so using a 32 bit GPREG0-2 as counter, compare, and int_en /// `period` is a 32bit integer, gpreg 'counter' is 31 bits plus the parity bit for overflow detection +#[cfg(feature = "time-driver-rtc")] fn calc_now(period: u32, counter: u32) -> u64 { ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) as u64) } -struct AlarmState { - timestamp: Cell, -} - -unsafe impl Send for AlarmState {} - -impl AlarmState { - const fn new() -> Self { - Self { - timestamp: Cell::new(u64::MAX), - } - } -} +#[cfg(feature = "time-driver-rtc")] +embassy_time_driver::time_driver_impl!(static DRIVER: Rtc = Rtc { + period: AtomicU32::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), + queue: Mutex::new(RefCell::new(Queue::new())), +}); +#[cfg(feature = "time-driver-rtc")] struct Rtc { /// Number of 2^31 periods elapsed since boot. period: AtomicU32, @@ -51,12 +64,7 @@ struct Rtc { queue: Mutex>, } -embassy_time_driver::time_driver_impl!(static DRIVER: Rtc = Rtc { - period: AtomicU32::new(0), - alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), - queue: Mutex::new(RefCell::new(Queue::new())), -}); - +#[cfg(feature = "time-driver-rtc")] impl Rtc { /// Access the GPREG0 register to use it as a 31-bit counter. #[inline] @@ -219,6 +227,7 @@ impl Rtc { } } +#[cfg(feature = "time-driver-rtc")] impl Driver for Rtc { fn now(&self) -> u64 { // `period` MUST be read before `counter`, see comment at the top for details. @@ -242,13 +251,158 @@ impl Driver for Rtc { } } -#[cfg(feature = "rt")] +#[cfg(all(feature = "rt", feature = "time-driver-rtc"))] #[allow(non_snake_case)] #[interrupt] fn RTC() { DRIVER.on_interrupt() } +#[cfg(feature = "time-driver-os-timer")] +fn os() -> &'static pac::ostimer0::RegisterBlock { + unsafe { &*pac::Ostimer0::ptr() } +} + +/// Convert gray to decimal +/// +/// Os Event provides a 64-bit timestamp gray-encoded. All we have to +/// do here is read both 32-bit halves of the register and convert +/// from gray to regular binary. +#[cfg(feature = "time-driver-os-timer")] +fn gray_to_dec(gray: u64) -> u64 { + let mut dec = gray; + + dec ^= dec >> 1; + dec ^= dec >> 2; + dec ^= dec >> 4; + dec ^= dec >> 8; + dec ^= dec >> 16; + dec ^= dec >> 32; + + dec +} + +/// Convert decimal to gray +/// +/// Before writing match value to the target register, we must convert +/// it back into gray code. +#[cfg(feature = "time-driver-os-timer")] +fn dec_to_gray(dec: u64) -> u64 { + let gray = dec; + gray ^ (gray >> 1) +} + +#[cfg(feature = "time-driver-os-timer")] +embassy_time_driver::time_driver_impl!(static DRIVER: OsTimer = OsTimer { + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), + queue: Mutex::new(RefCell::new(Queue::new())), +}); + +#[cfg(feature = "time-driver-os-timer")] +struct OsTimer { + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, + queue: Mutex>, +} + +#[cfg(feature = "time-driver-os-timer")] +impl OsTimer { + fn init(&'static self, irq_prio: crate::interrupt::Priority) { + // init alarms + critical_section::with(|cs| { + let alarm = DRIVER.alarms.borrow(cs); + alarm.timestamp.set(u64::MAX); + }); + + // Enable clocks. Documentation advises AGAINST resetting this + // peripheral. + enable::(); + + interrupt::OS_EVENT.disable(); + + // Make sure interrupt is masked + os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); + + // Default to the end of time + os().match_l().write(|w| unsafe { w.bits(0xffff_ffff) }); + os().match_h().write(|w| unsafe { w.bits(0xffff_ffff) }); + + interrupt::OS_EVENT.unpend(); + interrupt::OS_EVENT.set_priority(irq_prio); + unsafe { interrupt::OS_EVENT.enable() }; + } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let alarm = self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); + alarm.timestamp.set(u64::MAX); + return false; + } + + let gray_timestamp = dec_to_gray(timestamp); + + os().match_l() + .write(|w| unsafe { w.bits(gray_timestamp as u32 & 0xffff_ffff) }); + os().match_h() + .write(|w| unsafe { w.bits((gray_timestamp >> 32) as u32) }); + os().osevent_ctrl().modify(|_, w| w.ostimer_intena().set_bit()); + + true + } + + #[cfg(feature = "rt")] + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } + } + + #[cfg(feature = "rt")] + fn on_interrupt(&self) { + critical_section::with(|cs| { + if os().osevent_ctrl().read().ostimer_intrflag().bit_is_set() { + os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); + self.trigger_alarm(cs); + } + }); + } +} + +#[cfg(feature = "time-driver-os-timer")] +impl Driver for OsTimer { + fn now(&self) -> u64 { + let mut t = os().evtimerh().read().bits() as u64; + t <<= 32; + t |= os().evtimerl().read().bits() as u64; + gray_to_dec(t) + } + + 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) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +#[cfg(all(feature = "rt", feature = "time-driver-os-timer"))] +#[allow(non_snake_case)] +#[interrupt] +fn OS_EVENT() { + DRIVER.on_interrupt() +} + pub(crate) fn init(irq_prio: crate::interrupt::Priority) { DRIVER.init(irq_prio) } diff --git a/examples/mimxrt6/Cargo.toml b/examples/mimxrt6/Cargo.toml index 8fc510c47..b0c56f003 100644 --- a/examples/mimxrt6/Cargo.toml +++ b/examples/mimxrt6/Cargo.toml @@ -12,8 +12,8 @@ defmt-rtt = "1.0" embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } embassy-futures = { version = "0.1.1", path = "../../embassy-futures" } -embassy-imxrt = { version = "0.1.0", path = "../../embassy-imxrt", features = ["defmt", "mimxrt685s", "unstable-pac", "time", "time-driver-rtc"] } -embassy-time = { version = "0.4", path = "../../embassy-time" } +embassy-imxrt = { version = "0.1.0", path = "../../embassy-imxrt", features = ["defmt", "mimxrt685s", "unstable-pac", "time", "time-driver-os-timer"] } +embassy-time = { version = "0.4", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-sync = { version = "0.6.2", path = "../../embassy-sync", features = ["defmt"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = "1.0.0" -- cgit