diff --git a/embassy-mspm0/Cargo.toml b/embassy-mspm0/Cargo.toml index d591f6514d..e23582c7f9 100644 --- a/embassy-mspm0/Cargo.toml +++ b/embassy-mspm0/Cargo.toml @@ -27,6 +27,9 @@ build = [ {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1345dgs28", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1106dgs28", "time-driver-any"]}, {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1228pt", "time-driver-any"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1306rhb", "time-driver-any", "executor-thread"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1306rhb", "time-driver-any", "executor-interrupt"]}, + {target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1306rhb", "time-driver-any", "executor-thread", "executor-interrupt"]}, ] [package.metadata.embassy_docs] @@ -47,6 +50,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] embassy-sync = { version = "0.8.0", path = "../embassy-sync" } +embassy-executor = { version = "0.10.0", path = "../embassy-executor", optional = true } # TODO: Support other tick rates embassy-time-driver = { version = "0.2.2", path = "../embassy-time-driver", optional = true, features = ["tick-hz-32_768"] } embassy-time-queue-utils = { version = "0.3.2", path = "../embassy-time-queue-utils", optional = true } @@ -116,6 +120,14 @@ nrst-pin-as-gpio = [] ## Allow using the SWD pins as regular GPIO pins. swd-pins-as-gpio = [] +low-power = [] + +executor-thread = ["_executor"] + +executor-interrupt = ["_executor"] + +_executor = ["dep:embassy-executor", "low-power"] + #! ## Time # Features starting with `_` are for internal use only. They're not intended diff --git a/embassy-mspm0/src/executor.rs b/embassy-mspm0/src/executor.rs new file mode 100644 index 0000000000..d269ebc638 --- /dev/null +++ b/embassy-mspm0/src/executor.rs @@ -0,0 +1,253 @@ +//! MSPM0-specific `embassy-executor` platform. +//! +//! This module provides an `embassy-executor` platform specific for MSPM0 chips that integrates [`low_power::sleep()`](crate::low_power::sleep) in the main loop. +//! Read the `embassy-executor` README for information about what "executor platforms" are and how they work. +//! +//! To use it: +//! - Enable the `executor-thread` and/or `executor-interrupt` feature on this crate. +//! - **Do not** enable features `platform-cortex-m`, `executor-thread` or `executor-interrupt` in the `embassy-executor` crate. +//! - Tell the `main` macro to use this executor like this: +//! +//! ```rust,no_run +//! #[embassy_executor::main(executor = "embassy_mspm0::executor::Executor", entry = "cortex_m_rt::entry")] +//! async fn main(spawner: Spawner) { +//! let p = embassy_mspm0::init(Config::default()); +//! // ... +//! } +//! ``` + +#[unsafe(export_name = "__pender")] +#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] +fn __pender(context: *mut ()) { + // `context` is either `THREAD_PENDER`, or an interrupt number passed to `InterruptExecutor::start`. + let context = context as usize; + + #[cfg(feature = "executor-thread")] + // Try to optimize away the branch when only thread mode is enabled. + if !cfg!(feature = "executor-interrupt") || context == thread::THREAD_PENDER { + thread::SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst); + return; + } + + #[cfg(feature = "executor-interrupt")] + { + use cortex_m::interrupt::InterruptNumber; + use cortex_m::peripheral::NVIC; + + #[derive(Clone, Copy)] + struct Irq(u16); + + // SAFETY: `context` was an `InterruptNumber` when passed to `InterruptExecutor::start`. + unsafe impl InterruptNumber for Irq { + fn number(self) -> u16 { + self.0 + } + } + + // MSPM0 is Cortex-M0+, which has no STIR. + NVIC::pend(Irq(context as u16)); + } +} + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + use core::sync::atomic::{AtomicBool, Ordering}; + + use embassy_executor::{Spawner, raw}; + + pub(super) const THREAD_PENDER: usize = usize::MAX; + + /// Set by the pender to signal pending work; checked before sleeping since `WFI` ignores `SEV`. + pub(crate) static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + + /// Thread-mode executor that deep-sleeps on idle via [`low_power::sleep`](crate::low_power::sleep). + /// + /// This is the simplest and most common kind of executor. It runs on + /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction + /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction + /// is executed, to make the `WFE` exit from sleep and poll the task. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(THREAD_PENDER as *mut ()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + + critical_section::with(|cs| { + if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { + SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + } else { + crate::low_power::sleep(cs); + } + }); + } + } + } + } + + impl Default for Executor { + fn default() -> Self { + Self::new() + } + } +} + +#[cfg(feature = "executor-interrupt")] +pub use interrupt::*; +#[cfg(feature = "executor-interrupt")] +mod interrupt { + use core::cell::{Cell, UnsafeCell}; + use core::mem::MaybeUninit; + + use cortex_m::interrupt::InterruptNumber; + use cortex_m::peripheral::NVIC; + use critical_section::Mutex; + use embassy_executor::raw; + + /// Interrupt-mode executor. + /// + /// This executor runs tasks in interrupt mode. The interrupt handler is set up + /// to poll tasks, and when a task is woken the interrupt is pended from software. + /// + /// This allows running async tasks at a priority higher than thread mode. One + /// use case is to leave thread mode free for non-async tasks. Another use case is + /// to run multiple executors: one in thread mode for low priority tasks and another in + /// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower + /// priority ones. + /// + /// It is even possible to run multiple interrupt mode executors at different priorities, + /// by assigning different priorities to the interrupts. + /// + /// To use it, you have to pick an interrupt that won't be used by the hardware. + /// MSPM0 has no dedicated software interrupt, so use the interrupt of a peripheral the + /// application leaves unused. + /// + /// It is somewhat more complex to use, it's recommended to use the thread-mode + /// `Executor` instead, if it works for your use case. + pub struct InterruptExecutor { + started: Mutex>, + executor: UnsafeCell>, + } + + unsafe impl Send for InterruptExecutor {} + unsafe impl Sync for InterruptExecutor {} + + impl InterruptExecutor { + /// Create a new, not started `InterruptExecutor`. + #[inline] + pub const fn new() -> Self { + Self { + started: Mutex::new(Cell::new(false)), + executor: UnsafeCell::new(MaybeUninit::uninit()), + } + } + + /// Executor interrupt callback. + /// + /// # Safety + /// + /// - You MUST call this from the interrupt handler, and from nowhere else. + /// - You must not call this before calling `start()`. + pub unsafe fn on_interrupt(&'static self) { + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.poll(); + } + + /// Start the executor. + /// + /// This initializes the executor, enables the interrupt, and returns. + /// The executor keeps running in the background through the interrupt. + /// + /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`] + /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a + /// different "thread" (the interrupt), so spawning tasks on it is effectively + /// sending them. + /// + /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from + /// a task running in it. + /// + /// # Interrupt requirements + /// + /// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt). + /// + /// This method already enables (unmasks) the interrupt, you must NOT do it yourself. + /// + /// You must set the interrupt priority before calling this method. You MUST NOT + /// do it after. + /// + /// [`SendSpawner`]: embassy_executor::SendSpawner + pub fn start(&'static self, irq: impl InterruptNumber) -> embassy_executor::SendSpawner { + if critical_section::with(|cs| self.started.borrow(cs).replace(true)) { + panic!("InterruptExecutor::start() called multiple times on the same executor."); + } + + unsafe { + (&mut *self.executor.get()) + .as_mut_ptr() + .write(raw::Executor::new(irq.number() as *mut ())) + } + + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + + unsafe { NVIC::unmask(irq) } + + executor.spawner().make_send() + } + + /// Get a SendSpawner for this executor + /// + /// This returns a [`SendSpawner`](embassy_executor::SendSpawner) you can use to spawn tasks on this + /// executor. + /// + /// This MUST only be called on an executor that has already been started. + /// The function will panic otherwise. + pub fn spawner(&'static self) -> embassy_executor::SendSpawner { + if !critical_section::with(|cs| self.started.borrow(cs).get()) { + panic!("InterruptExecutor::spawner() called on uninitialized executor."); + } + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.spawner().make_send() + } + } + + impl Default for InterruptExecutor { + fn default() -> Self { + Self::new() + } + } +} diff --git a/embassy-mspm0/src/lib.rs b/embassy-mspm0/src/lib.rs index 76bcee6fa1..1a8574d7f1 100644 --- a/embassy-mspm0/src/lib.rs +++ b/embassy-mspm0/src/lib.rs @@ -14,12 +14,16 @@ mod macros; pub mod adc; pub mod dma; +#[cfg(feature = "_executor")] +pub mod executor; pub mod gpio; // TODO: I2C unicomm #[cfg(not(unicomm))] pub mod i2c; #[cfg(not(unicomm))] pub mod i2c_target; +#[cfg(feature = "low-power")] +pub mod low_power; #[cfg(any(mspm0g150x, mspm0g151x, mspm0g350x, mspm0g351x))] pub mod mathacl; pub mod sysctl; @@ -194,6 +198,7 @@ pub fn init(config: Config) -> Peripherals { w.set_mfpclken(true); }); + // TODO: Errata PCMU_ERR_03 states that BOR thresholds other than 0 don't work in STANDBY. pac::SYSCTL.borthreshold().modify(|w| { w.set_level(0); }); diff --git a/embassy-mspm0/src/low_power/c110x.rs b/embassy-mspm0/src/low_power/c110x.rs new file mode 100644 index 0000000000..af1988c879 --- /dev/null +++ b/embassy-mspm0/src/low_power/c110x.rs @@ -0,0 +1,70 @@ +//! C-series deep sleep: STOP0/2 + STANDBY0/1 (no STOP1). +//! +//! Covers mspm0c110x and mspm0c1105/c1106. These families lack the STOP1 (4 MHz SYSOSC) sub-mode, +//! and their STOP0 additionally clears `USELFCLK`. +//! +//! The entry sequence from the TRM is: +//! `PMODECFG.DSLEEP` selects STOP vs STANDBY, +//! `SYSOSCCFG.DISABLESTOP` selects STOP0 vs STOP2 (this family has no 4 MHz STOP1) with STOP0 also clearing `MCLKCFG.USELFCLK`, +//! `MCLKCFG.STOPCLKSTBY` selects STANDBY0 vs STANDBY1. + +use critical_section::CriticalSection; +use pac::sysctl::vals::Dsleep; + +use crate::pac; + +/// Deep-sleep idle modes, ordered by increasing power saving. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SleepMode { + /// SYSOSC available. Fastest wake, highest STOP current. + Stop0, + /// SYSOSC disabled; ULPCLK runs from LFCLK. Lowest STOP current. + Stop2, + /// low-speed peripherals retained. + Standby0, + /// only TIMG0/TIMG1 remain clocked. Lowest wake-capable current. + Standby1, +} + +/// Enter a deep-sleep `mode` and block until an interrupt wakes the core. +/// +/// This runs with interrupts masked, but `WFI` still wakes on enabled interrupts with PRIMASK set. +/// They will run once the `CriticalSection` exits. +/// +/// # Safety +/// The caller is responsible for ensuring deep sleep is safe right now: no transaction that must survive is in +/// flight (PD1 powers down and its peripherals lose state unless retained by the mode), and a wake source is armed. +pub unsafe fn enter_sleep(_cs: CriticalSection, mode: SleepMode) { + let sysctl = pac::SYSCTL; + + let dsleep = match mode { + SleepMode::Stop0 | SleepMode::Stop2 => Dsleep::STOP, + SleepMode::Standby0 | SleepMode::Standby1 => Dsleep::STANDBY, + }; + sysctl.pmodecfg().modify(|w| w.set_dsleep(dsleep)); + + match mode { + SleepMode::Stop0 => { + sysctl.sysosccfg().modify(|w| w.set_disablestop(false)); + sysctl.mclkcfg().modify(|w| w.set_uselfclk(false)); + } + SleepMode::Stop2 => sysctl.sysosccfg().modify(|w| w.set_disablestop(true)), + SleepMode::Standby0 => sysctl.mclkcfg().modify(|w| w.set_stopclkstby(false)), + SleepMode::Standby1 => sysctl.mclkcfg().modify(|w| w.set_stopclkstby(true)), + } + + super::arm_and_wait(); +} + +/// Map the family-independent [`SleepLevel`](super::SleepLevel) to this family's [`SleepMode`]. +pub(super) fn level_to_mode(level: super::SleepLevel) -> SleepMode { + use super::SleepLevel; + + match level { + SleepLevel::Stop0 | SleepLevel::Stop1 => SleepMode::Stop0, + SleepLevel::Stop2 => SleepMode::Stop2, + SleepLevel::Standby0 => SleepMode::Standby0, + SleepLevel::Standby1 => SleepMode::Standby1, + } +} diff --git a/embassy-mspm0/src/low_power/full.rs b/embassy-mspm0/src/low_power/full.rs new file mode 100644 index 0000000000..37deeaf810 --- /dev/null +++ b/embassy-mspm0/src/low_power/full.rs @@ -0,0 +1,80 @@ +//! Full-capability deep sleep: STOP0/1/2 + STANDBY0/1. +//! +//! Covers every family whose SYSCTL exposes the full STOP policy: all G families and the supported +//! L families. +//! +//! The entry sequence from the TRM is: +//! `PMODECFG.DSLEEP` selects STOP vs STANDBY, +//! `SYSOSCCFG.{USE4MHZSTOP, DISABLESTOP}` combination selects the STOP sub-mode, +//! `MCLKCFG.STOPCLKSTBY` selects the STANDBY sub-mode. + +use critical_section::CriticalSection; +use pac::sysctl::vals::Dsleep; + +use crate::pac; + +/// Deep-sleep idle modes, ordered by increasing power saving. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SleepMode { + /// SYSOSC stays at full speed. Fastest wake, highest STOP current. + Stop0, + /// SYSOSC limited to 4 MHz. + Stop1, + /// SYSOSC disabled; ULPCLK runs from LFCLK. Lowest STOP current. + Stop2, + /// low-speed peripherals retained. + Standby0, + /// only TIMG0/TIMG1 remain clocked. Lowest wake-capable current. + Standby1, +} + +/// Enter a deep-sleep `mode` and block until an interrupt wakes the core. +/// +/// This runs with interrupts masked, but `WFI` still wakes on enabled interrupts with PRIMASK set. +/// They will run once the `CriticalSection` exits. +/// +/// # Safety +/// The caller is responsible for ensuring deep sleep is safe right now: no transaction that must survive is in +/// flight (PD1 powers down and its peripherals lose state unless retained by the mode), and a wake source is armed. +pub unsafe fn enter_sleep(_cs: CriticalSection, mode: SleepMode) { + let sysctl = pac::SYSCTL; + + let dsleep = match mode { + SleepMode::Stop0 | SleepMode::Stop1 | SleepMode::Stop2 => Dsleep::STOP, + SleepMode::Standby0 | SleepMode::Standby1 => Dsleep::STANDBY, + }; + sysctl.pmodecfg().modify(|w| w.set_dsleep(dsleep)); + + match mode { + SleepMode::Stop0 => sysctl.sysosccfg().modify(|w| { + w.set_use4mhzstop(false); + w.set_disablestop(false); + }), + SleepMode::Stop1 => sysctl.sysosccfg().modify(|w| { + w.set_use4mhzstop(true); + w.set_disablestop(false); + }), + SleepMode::Stop2 => sysctl.sysosccfg().modify(|w| { + w.set_use4mhzstop(false); + w.set_disablestop(true); + }), + SleepMode::Standby0 => sysctl.mclkcfg().modify(|w| w.set_stopclkstby(false)), + SleepMode::Standby1 => sysctl.mclkcfg().modify(|w| w.set_stopclkstby(true)), + } + + super::arm_and_wait(); +} + +/// Map the family-independent [`SleepLevel`](super::SleepLevel) to this family's [`SleepMode`]. +pub(super) fn level_to_mode(level: super::SleepLevel) -> SleepMode { + use super::SleepLevel; + + match level { + SleepLevel::Stop0 => SleepMode::Stop0, + SleepLevel::Stop1 => SleepMode::Stop1, + SleepLevel::Stop2 => SleepMode::Stop2, + SleepLevel::Standby0 => SleepMode::Standby0, + SleepLevel::Standby1 => SleepMode::Standby1, + } +} diff --git a/embassy-mspm0/src/low_power/h321x.rs b/embassy-mspm0/src/low_power/h321x.rs new file mode 100644 index 0000000000..a7b9fb9c49 --- /dev/null +++ b/embassy-mspm0/src/low_power/h321x.rs @@ -0,0 +1,66 @@ +//! H321x deep sleep: STOP0/2 + STANDBY0/1 (no STOP1). +//! +//! Covers mspm0h321x. It lacks the STOP1 (4 MHz SYSOSC) sub-mode, and its STOP0 clears `DISABLESTOP` only. +//! +//! The entry sequence from the TRM is: +//! `PMODECFG.DSLEEP` selects STOP vs STANDBY, +//! `SYSOSCCFG.DISABLESTOP` selects STOP0 vs STOP2 (no 4 MHz STOP1, and STOP0 clears DISABLESTOP only), +//! `MCLKCFG.STOPCLKSTBY` selects STANDBY0 vs STANDBY1. + +use critical_section::CriticalSection; +use pac::sysctl::vals::Dsleep; + +use crate::pac; + +/// Deep-sleep idle modes, ordered by increasing power saving. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SleepMode { + /// SYSOSC available. Fastest wake, highest STOP current. + Stop0, + /// SYSOSC disabled; ULPCLK runs from LFCLK. Lowest STOP current. + Stop2, + /// Low-speed peripherals retained. + Standby0, + /// Only TIMG0/TIMG1 remain clocked. Lowest wake-capable current. + Standby1, +} + +/// Enter a deep-sleep `mode` and block until an interrupt wakes the core. +/// +/// This runs with interrupts masked, but `WFI` still wakes on enabled interrupts with PRIMASK set. +/// They will run once the `CriticalSection` exits. +/// +/// # Safety +/// The caller is responsible for ensuring deep sleep is safe right now: no transaction that must survive is in +/// flight (PD1 powers down and its peripherals lose state unless retained by the mode), and a wake source is armed. +pub unsafe fn enter_sleep(_cs: CriticalSection, mode: SleepMode) { + let sysctl = pac::SYSCTL; + + let dsleep = match mode { + SleepMode::Stop0 | SleepMode::Stop2 => Dsleep::STOP, + SleepMode::Standby0 | SleepMode::Standby1 => Dsleep::STANDBY, + }; + sysctl.pmodecfg().modify(|w| w.set_dsleep(dsleep)); + + match mode { + SleepMode::Stop0 => sysctl.sysosccfg().modify(|w| w.set_disablestop(false)), + SleepMode::Stop2 => sysctl.sysosccfg().modify(|w| w.set_disablestop(true)), + SleepMode::Standby0 => sysctl.mclkcfg().modify(|w| w.set_stopclkstby(false)), + SleepMode::Standby1 => sysctl.mclkcfg().modify(|w| w.set_stopclkstby(true)), + } + + super::arm_and_wait(); +} + +/// Map the family-independent [`SleepLevel`](super::SleepLevel) to this family's [`SleepMode`]. +pub(super) fn level_to_mode(level: super::SleepLevel) -> SleepMode { + use super::SleepLevel; + + match level { + SleepLevel::Stop0 | SleepLevel::Stop1 => SleepMode::Stop0, + SleepLevel::Stop2 => SleepMode::Stop2, + SleepLevel::Standby0 => SleepMode::Standby0, + SleepLevel::Standby1 => SleepMode::Standby1, + } +} diff --git a/embassy-mspm0/src/low_power/mod.rs b/embassy-mspm0/src/low_power/mod.rs new file mode 100644 index 0000000000..f3d2199443 --- /dev/null +++ b/embassy-mspm0/src/low_power/mod.rs @@ -0,0 +1,207 @@ +//! Low-power (deep-sleep) support. +//! +//! Deep-sleep depth is gated by [`WakeGuard`](crate::sysctl::WakeGuard), which drivers hold to keep +//! the chip shallower than a given [`SleepLevel`]; the low-power executor then idles into the deepest +//! mode no guard blocks. +//! +//! # Wake source caveats +//! - `GPIO_ERR_01` (L, G) — a wake edge can be missed. Only the STANDBY1 half is handled: if the pin +//! is still asserted when the chip goes back to sleep no further edge is detected +//! - `GPIO_ERR_08` (H) — in low-power mode a GPIO can trigger a fast wake regardless of the `FASTWAKE` +//! register and the pin configuration. +//! - `UART_ERR_01` (L, G, H) — a start bit arriving while the chip is on its way back into STANDBY1 +//! is not received. +use core::sync::atomic::Ordering; + +use critical_section::CriticalSection; +use pac::cpuss::vals::Prefetch; +use pac::sysctl::vals::Dsleep; +use portable_atomic::AtomicU8; + +use crate::pac; + +#[cfg(any( + mspm0l110x, mspm0l130x, mspm0l134x, mspm0l122x, mspm0l222x, mspm0g110x, mspm0g150x, mspm0g310x, mspm0g350x, + mspm0g151x, mspm0g351x, mspm0g518x +))] +#[path = "full.rs"] +mod inner; + +#[cfg(any(mspm0c110x, mspm0c1105_c1106))] +#[path = "c110x.rs"] +mod inner; + +#[cfg(mspm0h321x)] +#[path = "h321x.rs"] +mod inner; + +#[cfg(any( + mspm0l110x, + mspm0l130x, + mspm0l134x, + mspm0l122x, + mspm0l222x, + mspm0g110x, + mspm0g150x, + mspm0g310x, + mspm0g350x, + mspm0g151x, + mspm0g351x, + mspm0g518x, + mspm0c110x, + mspm0c1105_c1106, + mspm0h321x +))] +pub use inner::{SleepMode, enter_sleep}; + +#[cfg(not(any( + mspm0l110x, + mspm0l130x, + mspm0l134x, + mspm0l122x, + mspm0l222x, + mspm0g110x, + mspm0g150x, + mspm0g310x, + mspm0g350x, + mspm0g151x, + mspm0g351x, + mspm0g518x, + mspm0c110x, + mspm0c1105_c1106, + mspm0h321x +)))] +compile_error!("the `low-power` feature is not implemented for this chip family"); + +pub use crate::sysctl::SleepLevel; + +static SLEEP_BLOCKS: [AtomicU8; 5] = [const { AtomicU8::new(0) }; 5]; + +/// Block sleep at `level` and every deeper mode. Paired with [`unblock`] by +/// [`WakeGuard`](crate::sysctl::WakeGuard). +pub(crate) fn block(level: SleepLevel) { + trace!("Blocking sleep at level {:?}", level); + if SLEEP_BLOCKS[level as usize].fetch_add(1, Ordering::Relaxed) == (u8::MAX - 1) { + panic!("Blocking at SleepLevel {:?} would overflow", level) + }; +} + +/// Remove a block previously added at `level`. +pub(crate) fn unblock(level: SleepLevel) { + trace!("Unblocking sleep at level {:?}", level); + SLEEP_BLOCKS[level as usize].fetch_sub(1, Ordering::Relaxed); +} + +/// Deepest mode currently permitted, or `None` if all deep sleep is blocked. +fn deepest_allowed() -> Option { + for (i, blocks) in SLEEP_BLOCKS.iter().enumerate() { + if blocks.load(Ordering::Relaxed) > 0 { + return i.checked_sub(1).map(|j| SleepLevel::LEVELS[j]); + } + } + Some(SleepLevel::Standby1) +} + +/// Enter the deepest sleep permitted by the active [`WakeGuard`](crate::sysctl::WakeGuard)s, waiting +/// for an interrupt. +/// +/// Called by the low-power executor on idle. With no guards held it enters the deepest mode the +/// chip supports; a held guard caps the depth, and a guard on [`SleepLevel::Stop0`] keeps it a +/// plain `WFI`. +/// +/// # Safety +/// Must be called from thread mode. `WFI` in a handler is only woken by an interrupt of *higher* +/// priority than the one running, so sleeping inside the lowest-priority handler never returns. +/// Deep sleep powers down PD1 (and, in STANDBY, most of PD0). Any peripheral transaction that must +/// survive has to be protected by a [`WakeGuard`](crate::sysctl::WakeGuard) shallow enough to keep it +/// clocked. Until the drivers hold their own guards, the caller is responsible for this. +pub unsafe fn sleep(cs: CriticalSection) { + trace!("Attempting to enter low-power sleep"); + + // Some of the prefetcher errata applies even for a plain WFI + // FIXME: This could be a problem for embassy-executor's default executor. + let _prefetch = PrefetchSuspend::new(); + + match deepest_allowed() { + None => { + trace!("Low-power sleep blocked"); + cortex_m::asm::dsb(); + cortex_m::asm::wfi(); + cortex_m::asm::isb(); + } + Some(level) => { + trace!("Low-power sleep allowed, mode: {:?}", level); + enter_sleep(cs, inner::level_to_mode(level)); + } + } +} + +/// Enter SHUTDOWN, the lowest-power state. Does not return. +/// +/// SHUTDOWN powers down VCORE: all SRAM is lost except the `SHUTDNSTORE` bytes, and the only wake +/// sources are a wake-capable IO event, NRST, or SWD activity. Does not return as the wake results +/// in a reset. +/// You can respond to the reset on boot using [`ResetCause::BorWakeFromShutdown`](crate::ResetCause). +// +// From the TRM: SYSCTL "Operating Modes": set `PMODECFG.DSLEEP = SHUTDOWN`, arm `SLEEPDEEP`, +// then `WFI`. This is identical across every MSPM0 family. +pub fn shutdown(_cs: CriticalSection) -> ! { + let sysctl = pac::SYSCTL; + sysctl.pmodecfg().modify(|w| w.set_dsleep(Dsleep::SHUTDOWN)); + + let _prefetch = PrefetchSuspend::new(); + + let mut scb = unsafe { cortex_m::Peripherals::steal() }.SCB; + scb.set_sleepdeep(); + cortex_m::asm::dsb(); + + cortex_m::asm::wfi(); + + // SAFETY: Setting DSLEEP to SHUTDOWN means WFI will never return. + unsafe { core::hint::unreachable_unchecked() } +} + +/// Workaround for CPU_ERR_02, CPU_ERR_03, PMCU_ERR_13 - the prefetcher has at least one errata in +/// sleep for every currently supported MCU. +struct PrefetchSuspend(pac::cpuss::regs::Ctl); + +impl PrefetchSuspend { + fn new() -> Self { + let saved = pac::CPUSS.ctl().read(); + let mut disabled = saved; + disabled.set_prefetch(Prefetch::DISABLE); + pac::CPUSS.ctl().write_value(disabled); + + // CPU_ERR_02 means the prefetcher will not be disabled until pending flash access is finished. + // Reading any SYSCTL register after disabling prefetch will complete the pending flash access. + #[cfg(not(mspm0h321x))] + let _ = pac::SYSCTL.shutdnstore(0).read(); + #[cfg(mspm0h321x)] + let _ = pac::SYSCTL.clkstatus().read(); + + cortex_m::asm::dsb(); + cortex_m::asm::isb(); + + Self(saved) + } +} + +impl Drop for PrefetchSuspend { + fn drop(&mut self) { + pac::CPUSS.ctl().write_value(self.0); + } +} + +/// Arm ARM deep-sleep (`SLEEPDEEP`), wait for an interrupt, then clear it. +/// +/// The mode-specific SYSCTL programming must already be done by the caller, and the prefetcher must +/// already be suspended by [`PrefetchSuspend`]. `WFI` wakes on a pending enabled interrupt even with +/// PRIMASK set; `SLEEPDEEP` is cleared on wake so a later plain executor idle does not deep-sleep. +pub(crate) unsafe fn arm_and_wait() { + let mut scb = unsafe { cortex_m::Peripherals::steal() }.SCB; + scb.set_sleepdeep(); + cortex_m::asm::dsb(); + cortex_m::asm::wfi(); + cortex_m::asm::isb(); + scb.clear_sleepdeep(); +} diff --git a/embassy-mspm0/src/sysctl/mod.rs b/embassy-mspm0/src/sysctl/mod.rs index c8d4588ba0..d0a7085fdb 100644 --- a/embassy-mspm0/src/sysctl/mod.rs +++ b/embassy-mspm0/src/sysctl/mod.rs @@ -23,6 +23,144 @@ mod inner; pub use inner::ClkOutSource; +/// Deep-sleep idle modes, ordered by increasing power saving. +/// +/// Has no effect when the `low-power` feature is disabled. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SleepLevel { + Stop0, + Stop1, + Stop2, + Standby0, + Standby1, +} + +impl SleepLevel { + #[allow(unused)] + pub(crate) const LEVELS: [SleepLevel; 5] = [ + SleepLevel::Stop0, + SleepLevel::Stop1, + SleepLevel::Stop2, + SleepLevel::Standby0, + SleepLevel::Standby1, + ]; + + /// Shallowest level to block so a PD0 peripheral clocked at `clock_hz` keeps running, or `None` + /// to block nothing (any sleep depth is fine). + /// + /// `clock_hz` is the frequency of the clock that the peripheral depends on. + /// ULPCLK for bus-clocked peripherals, or the LFCLK/MFCLK source rate for those clocked directly. + /// The per-mode ceiling is the same across every MSPM0 family: STOP0/STOP1 cap at 4 MHz, STOP2 + /// and STANDBY0 at 32 kHz (LFCLK), and only STANDBY1 unclocks PD0 (there just TIMG0/1 stay clocked). + /// + /// NOTE: Assumes the RUN0 run mode, the only one the HAL configures today + /// (STOP0 reaches 4 MHz only when entered from RUN0). + pub const fn floor_for_clock_hz(clock_hz: u32) -> Option { + // Per-mode clock ceilings, from the family TRMs' "DMA Operating Mode Support" and "Operating + // Modes" sections. + // STANDBY0 clocks all PD0 peripherals from LFCLK; STANDBY1 does not. + const STOP_HZ: u32 = 4_000_000; + const LFCLK_HZ: u32 = 32_768; + + if clock_hz > STOP_HZ { + // Needs MCLK + Some(Self::Stop0) + } else if clock_hz > LFCLK_HZ { + // Reqires MFCLK + Some(Self::Stop2) + } else if clock_hz > 0 { + // LFCLK is enough, keep PD0 alive + Some(Self::Standby1) + } else { + // No clock + None + } + } +} + +// Boundary checks for `floor_for_clock_hz`. `crate::fmt` cannot be used in const. +const _: () = { + core::assert!(matches!( + SleepLevel::floor_for_clock_hz(4_000_001), + Some(SleepLevel::Stop0) + )); + core::assert!(matches!( + SleepLevel::floor_for_clock_hz(4_000_000), + Some(SleepLevel::Stop2) + )); + core::assert!(matches!( + SleepLevel::floor_for_clock_hz(32_769), + Some(SleepLevel::Stop2) + )); + core::assert!(matches!( + SleepLevel::floor_for_clock_hz(32_768), + Some(SleepLevel::Standby1) + )); + core::assert!(matches!(SleepLevel::floor_for_clock_hz(1), Some(SleepLevel::Standby1))); + core::assert!(matches!(SleepLevel::floor_for_clock_hz(0), None)); +}; + +/// A token forbidding a deep-sleep mode (and anything deeper) while held. +/// +/// A guard at `level` blocks that [`SleepLevel`] and every deeper mode; the low-power executor then +/// idles into the deepest mode still permitted, or a plain `WFI` if even [`SleepLevel::Stop0`] is +/// blocked. Guards are refcounted per level. +/// +/// Always available so drivers can hold one unconditionally. +/// Without the `low-power` feature it is a no-op. +#[must_use] +pub struct WakeGuard { + #[cfg(feature = "low-power")] + level: SleepLevel, + _unit: (), +} + +impl WakeGuard { + /// Forbid entering `level` or any deeper mode until dropped. + /// + /// [`SleepLevel::Stop0`] blocks all deep sleep, leaving only `WFI`. Without the `low-power` + /// feature `level` is ignored and this does nothing. + #[inline] + pub fn new(level: SleepLevel) -> Self { + #[cfg(not(feature = "low-power"))] + let _ = level; + #[cfg(feature = "low-power")] + crate::low_power::block(level); + + Self { + #[cfg(feature = "low-power")] + level, + _unit: (), + } + } +} + +impl Drop for WakeGuard { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "low-power")] + crate::low_power::unblock(self.level); + } +} + +/// Frequency of MCLK, which is also the rate of the ULPCLK ("bus clock") driving PD0 peripherals. +// TODO: Compute this once the MCLK rate can be adjusted. +#[cfg(any(mspm0c110x, mspm0c1105_c1106))] +#[allow(dead_code)] +pub(crate) fn mclk_frequency() -> u32 { + 24_000_000 +} + +#[cfg(any( + mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0h321x, mspm0l110x, mspm0l122x, + mspm0l130x, mspm0l134x, mspm0l222x +))] +#[allow(dead_code)] +pub(crate) fn mclk_frequency() -> u32 { + 32_000_000 +} + /// Divider applied to the clock source of the CLK_OUT pin. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/embassy-mspm0/src/time_driver/mod.rs b/embassy-mspm0/src/time_driver/mod.rs index 4ea02a3b21..022b58b837 100644 --- a/embassy-mspm0/src/time_driver/mod.rs +++ b/embassy-mspm0/src/time_driver/mod.rs @@ -1,4 +1,4 @@ // TODO: Alternative TIMB implementation #[path = "tim.rs"] mod driver; -pub use driver::*; +pub(crate) use driver::*; diff --git a/embassy-mspm0/src/time_driver/tim.rs b/embassy-mspm0/src/time_driver/tim.rs index 6bddb98f6b..b006366f4a 100644 --- a/embassy-mspm0/src/time_driver/tim.rs +++ b/embassy-mspm0/src/time_driver/tim.rs @@ -17,6 +17,18 @@ use crate::{peripherals, tim}; #[cfg(any(time_driver_timg12, time_driver_timg13))] compile_error!("TIMG12 and TIMG13 are not supported by the time driver yet"); +// Only TIMG0 and TIMG1 remain clocked in STANDBY, so they are the only timers that can wake the +// core from deep sleep via the time driver. Reject a `low-power` build on any other timer. +// TODO: Or maybe allow using them, but disable STANDBY? STOP0 will still work. +// Another option is to leak a wake guard when one of those timers is used. + +#[cfg(all(feature = "low-power", not(any(time_driver_timg0, time_driver_timg1))))] +compile_error!( + "the `low-power` feature requires the time driver to run on TIMG0 or TIMG1, as they are the \ + only timers clocked in STANDBY. Enable `time-driver-timg0`, `time-driver-timg1`, or \ + `time-driver-any` (which selects TIMG0 when available)." +); + // Currently TIMG12 and TIMG13 are excluded because those are 32-bit timers. #[cfg(time_driver_timg0)] type T = peripherals::TIMG0; @@ -61,23 +73,9 @@ fn regs() -> Tim { // - `period` is incremented on overflow (at counter value 0) // - `period` is incremented "midway" between overflows (at counter value 0x8000) // -// Therefore, when `period` is even, counter is in 0..0x7FFF. When odd, counter is in 0x8000..0xFFFF +// When `period` is even, counter is in 0..0x7FFF. When odd, counter is in 0x8000..0xFFFF // This allows for now() to return the correct value even if it races an overflow. // -// The overflow half must be counted on the *zero* event, not the load event, or that invariant does -// not hold. SLAU847F Figure 28-10 shows that in up counting mode the load event is asserted during -// the `CTR == LOAD` TIMCLK cycle, i.e. one full tick *before* the counter wraps, while the zero -// event is asserted during the `CTR == 0` cycle. Counting on the load event advances `period` while -// `counter` still reads 0xFFFF, which is the one pairing the parity repair below cannot fix: it -// leaves a stale counter against a fresh period for a whole tick on every wrap, worth 2^16 ticks of -// error. The zero event lands exactly on the parity boundary, same as CCU0 lands exactly on -// `CTR == 0x8000`. (LOAD is 2^16 - 1, not 2^16: up counting mode has a period of `LOAD + 1`.) -// -// 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. -// // `period` is a 32bit integer, so It overflows on 2^32 * 2^15 / 32768 seconds of uptime, which is 136 years. fn calc_now(period: u32, counter: u16) -> u64 { ((period as u64) << 15) + ((counter as u32 ^ ((period & 1) << 15)) as u64) @@ -116,11 +114,13 @@ impl TimxDriver { // 1. Select TIMCLK source regs.clksel().modify(|w| { + // Use LFCLK at 32.768 kHz, as it's available all the way down to STANDBY w.set_lfclk_sel(true); }); - // 2. Divide by TIMCLK, we don't need to divide further for the 32kHz tick rate + // 2. Divide by TIMCLK regs.clkdiv().modify(|w| { + // 32.768 kHz on the LFCLK requires no division. w.set_ratio(0); // + 1 }); @@ -140,7 +140,7 @@ impl TimxDriver { regs.counterregs(0).ctrctl().modify(|w| { w.set_repeat(Repeat::REPEAT_1); - w.set_cvae(Cvae::ZEROVAL); + w.set_cvae(Cvae::NOCHANGE); w.set_cm(Cm::UP); // Must explicitly set CZC, CAC and CLC to 0 in order for all the timers to count. @@ -158,6 +158,8 @@ impl TimxDriver { // Middle regs.counterregs(0).cc(0).write_value(0x8000 as u32); regs.counterregs(0).load().write_value(u16::MAX as u32); + // Start with the counter at 1 to avoid immediately incrementing period. + regs.counterregs(0).ctr().write_value(1); // Enable the period interrupts // @@ -176,15 +178,6 @@ impl TimxDriver { w.set_en(true); }); - // Enabling the counter with CVAE = ZEROVAL zeroes it, which can latch a zero event. Discard - // anything latched up to here before arming the interrupt, or that event would be counted as - // a wrap that never happened and put the whole timebase a one period off. - regs.cpu_int(0).iclr().write(|w| { - w.set_z(true); - w.set_ccu0(true); - w.set_ccu1(true); - }); - ::Interrupt::IRQ.unpend(); unsafe { ::Interrupt::IRQ.enable() }; } @@ -295,15 +288,6 @@ impl Driver for TimxDriver { // On MSPM0 this sequence reread and comparison must be done or else time may // appear to go backwards. - // - // The timer counter and the software period counter are not updated as one - // atomic operation. It is possible for the timer interrupt to increment the - // period counter while reading the counter. - // - // For example, `period` may be read as X while the timer is near the end of - // that period. The timer then wraps and the interrupt increments `period` to - // X + 1 before the counter is read. If the counter read returns 0x0000, using - // the stale period X would produce a timestamp 32768 ticks in the past. loop { let period = self.period.load(Ordering::Relaxed); // Ensure the compiler does not read the counter before the period. diff --git a/embassy-mspm0/src/trng.rs b/embassy-mspm0/src/trng.rs index 61dc1437b7..3b520976ba 100644 --- a/embassy-mspm0/src/trng.rs +++ b/embassy-mspm0/src/trng.rs @@ -280,21 +280,6 @@ impl TryRngCore for Trng<'_, D> { impl TryCryptoRng for Trng<'_, Crypto> {} -// TODO: Replace this when the MCLK rate can be adjusted. -#[cfg(any(mspm0c110x, mspm0c1105_c1106))] -fn get_mclk_frequency() -> u32 { - 24_000_000 -} - -// TODO: Replace this when the MCLK rate can be adjusted. -#[cfg(any( - mspm0g110x, mspm0g150x, mspm0g151x, mspm0g310x, mspm0g350x, mspm0g351x, mspm0h321x, mspm0l110x, mspm0l122x, - mspm0l130x, mspm0l134x, mspm0l222x -))] -fn get_mclk_frequency() -> u32 { - 32_000_000 -} - // Inner TRNG driver implementation. Used to reduce monomorphization bloat. struct TrngInner<'d> { decim_rate: vals::DecimRate, @@ -356,7 +341,7 @@ impl TrngInner<'_> { fn set_div(&mut self) { // L-series TRM 13.2.2: The TRNG is derived from MCLK. Datasheets specify 9.5-20 MHz range. - let freq = get_mclk_frequency(); + let freq = crate::sysctl::mclk_frequency(); let ratio = if freq > 160_000_000 { panic!("MCLK frequency {} > 160 MHz is not compatible with the TRNG", freq) } else if freq >= 80_000_000 {