From 5d3d485a73cd1b1cff4077914ca1103e0cf6b84b Mon Sep 17 00:00:00 2001 From: xoviat Date: Fri, 21 Nov 2025 18:55:27 -0600 Subject: low power: store stop mode for dma channels --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/build.rs | 15 ++++++- embassy-stm32/src/dma/dma_bdma.rs | 16 ++++--- embassy-stm32/src/dma/gpdma/mod.rs | 13 ++++-- embassy-stm32/src/dma/gpdma/ringbuffered.rs | 10 ++--- embassy-stm32/src/dma/mod.rs | 66 ++++++++++++++++++++++++++++- embassy-stm32/src/low_power.rs | 42 +++++------------- embassy-stm32/src/rcc/mod.rs | 59 +++++++++++++++++--------- 8 files changed, 150 insertions(+), 72 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 87a8ef7c9..6e1381925 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- change: low power: store stop mode for dma channels - fix: Fixed ADC4 enable() for WBA - feat: allow use of anyadcchannel for adc4 - fix: fix incorrect logic for buffered usart transmission complete. diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 8cbd38e10..3277ba440 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1947,6 +1947,19 @@ fn main() { continue; } + let stop_mode = METADATA + .peripherals + .iter() + .find(|p| p.name == ch.dma) + .map(|p| p.rcc.as_ref().map(|rcc| rcc.stop_mode.clone()).unwrap_or_default()) + .unwrap_or_default(); + + let stop_mode = match stop_mode { + StopMode::Standby => quote! { Standby }, + StopMode::Stop2 => quote! { Stop2 }, + StopMode::Stop1 => quote! { Stop1 }, + }; + let name = format_ident!("{}", ch.name); let idx = ch_idx as u8; #[cfg(feature = "_dual-core")] @@ -1959,7 +1972,7 @@ fn main() { quote!(crate::pac::Interrupt::#irq_name) }; - g.extend(quote!(dma_channel_impl!(#name, #idx);)); + g.extend(quote!(dma_channel_impl!(#name, #idx, #stop_mode);)); let dma = format_ident!("{}", ch.dma); let ch_num = ch.channel as usize; diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index 90dbf4f09..b46ae2813 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -8,7 +8,7 @@ use embassy_sync::waitqueue::AtomicWaker; use super::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer}; use super::word::{Word, WordSize}; -use super::{AnyChannel, Channel, Dir, Request, STATE}; +use super::{AnyChannel, BusyChannel, Channel, Dir, Request, STATE}; use crate::interrupt::typelevel::Interrupt; use crate::{interrupt, pac}; @@ -602,7 +602,7 @@ impl AnyChannel { /// DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a> { - channel: Peri<'a, AnyChannel>, + channel: BusyChannel<'a>, } impl<'a> Transfer<'a> { @@ -713,7 +713,9 @@ impl<'a> Transfer<'a> { _request, dir, peri_addr, mem_addr, mem_len, incr_mem, mem_size, peri_size, options, ); channel.start(); - Self { channel } + Self { + channel: BusyChannel::new(channel), + } } /// Request the transfer to pause, keeping the existing configuration for this channel. @@ -816,7 +818,7 @@ impl<'a> DmaCtrl for DmaCtrlImpl<'a> { /// Ringbuffer for receiving data using DMA circular mode. pub struct ReadableRingBuffer<'a, W: Word> { - channel: Peri<'a, AnyChannel>, + channel: BusyChannel<'a>, ringbuf: ReadableDmaRingBuffer<'a, W>, } @@ -853,7 +855,7 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { ); Self { - channel, + channel: BusyChannel::new(channel), ringbuf: ReadableDmaRingBuffer::new(buffer), } } @@ -972,7 +974,7 @@ impl<'a, W: Word> Drop for ReadableRingBuffer<'a, W> { /// Ringbuffer for writing data using DMA circular mode. pub struct WritableRingBuffer<'a, W: Word> { - channel: Peri<'a, AnyChannel>, + channel: BusyChannel<'a>, ringbuf: WritableDmaRingBuffer<'a, W>, } @@ -1009,7 +1011,7 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { ); Self { - channel, + channel: BusyChannel::new(channel), ringbuf: WritableDmaRingBuffer::new(buffer), } } diff --git a/embassy-stm32/src/dma/gpdma/mod.rs b/embassy-stm32/src/dma/gpdma/mod.rs index 106558d20..383c74a78 100644 --- a/embassy-stm32/src/dma/gpdma/mod.rs +++ b/embassy-stm32/src/dma/gpdma/mod.rs @@ -11,6 +11,7 @@ use linked_list::Table; use super::word::{Word, WordSize}; use super::{AnyChannel, Channel, Dir, Request, STATE}; +use crate::dma::BusyChannel; use crate::interrupt::typelevel::Interrupt; use crate::pac; use crate::pac::gpdma::vals; @@ -408,7 +409,7 @@ impl AnyChannel { /// Linked-list DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct LinkedListTransfer<'a, const ITEM_COUNT: usize> { - channel: Peri<'a, AnyChannel>, + channel: BusyChannel<'a>, } impl<'a, const ITEM_COUNT: usize> LinkedListTransfer<'a, ITEM_COUNT> { @@ -429,7 +430,9 @@ impl<'a, const ITEM_COUNT: usize> LinkedListTransfer<'a, ITEM_COUNT> { channel.configure_linked_list(&table, options); channel.start(); - Self { channel } + Self { + channel: BusyChannel::new(channel), + } } /// Request the transfer to pause, keeping the existing configuration for this channel. @@ -505,7 +508,7 @@ impl<'a, const ITEM_COUNT: usize> Future for LinkedListTransfer<'a, ITEM_COUNT> /// DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a> { - channel: Peri<'a, AnyChannel>, + channel: BusyChannel<'a>, } impl<'a> Transfer<'a> { @@ -625,7 +628,9 @@ impl<'a> Transfer<'a> { ); channel.start(); - Self { channel } + Self { + channel: BusyChannel::new(channel), + } } /// Request the transfer to pause, keeping the existing configuration for this channel. diff --git a/embassy-stm32/src/dma/gpdma/ringbuffered.rs b/embassy-stm32/src/dma/gpdma/ringbuffered.rs index 94c597e0d..54e4d5f71 100644 --- a/embassy-stm32/src/dma/gpdma/ringbuffered.rs +++ b/embassy-stm32/src/dma/gpdma/ringbuffered.rs @@ -12,7 +12,7 @@ use super::{AnyChannel, STATE, TransferOptions}; use crate::dma::gpdma::linked_list::{RunMode, Table}; use crate::dma::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer}; use crate::dma::word::Word; -use crate::dma::{Channel, Dir, Request}; +use crate::dma::{BusyChannel, Channel, Dir, Request}; struct DmaCtrlImpl<'a>(Peri<'a, AnyChannel>); @@ -49,7 +49,7 @@ impl<'a> DmaCtrl for DmaCtrlImpl<'a> { /// Ringbuffer for receiving data using GPDMA linked-list mode. pub struct ReadableRingBuffer<'a, W: Word> { - channel: Peri<'a, AnyChannel>, + channel: BusyChannel<'a>, ringbuf: ReadableDmaRingBuffer<'a, W>, table: Table<2>, options: TransferOptions, @@ -70,7 +70,7 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { let table = Table::<2>::new_ping_pong::(request, peri_addr, buffer, Dir::PeripheralToMemory); Self { - channel, + channel: BusyChannel::new(channel), ringbuf: ReadableDmaRingBuffer::new(buffer), table, options, @@ -189,7 +189,7 @@ impl<'a, W: Word> Drop for ReadableRingBuffer<'a, W> { /// Ringbuffer for writing data using GPDMA linked-list mode. pub struct WritableRingBuffer<'a, W: Word> { - channel: Peri<'a, AnyChannel>, + channel: BusyChannel<'a>, ringbuf: WritableDmaRingBuffer<'a, W>, table: Table<2>, options: TransferOptions, @@ -210,7 +210,7 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { let table = Table::<2>::new_ping_pong::(request, peri_addr, buffer, Dir::MemoryToPeripheral); Self { - channel, + channel: BusyChannel::new(channel), ringbuf: WritableDmaRingBuffer::new(buffer), table, options, diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index de7a2c175..4becc2d87 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -3,11 +3,14 @@ #[cfg(any(bdma, dma))] mod dma_bdma; +use core::ops; + #[cfg(any(bdma, dma))] pub use dma_bdma::*; #[cfg(gpdma)] pub(crate) mod gpdma; +use embassy_hal_internal::Peri; #[cfg(gpdma)] pub use gpdma::ringbuffered::*; #[cfg(gpdma)] @@ -48,6 +51,8 @@ pub type Request = (); pub(crate) trait SealedChannel { #[cfg(not(stm32n6))] fn id(&self) -> u8; + #[cfg(feature = "low-power")] + fn stop_mode(&self) -> crate::rcc::StopMode; } #[cfg(not(stm32n6))] @@ -62,15 +67,25 @@ pub trait Channel: SealedChannel + PeripheralType + Into + 'static { #[cfg(not(stm32n6))] macro_rules! dma_channel_impl { - ($channel_peri:ident, $index:expr) => { + ($channel_peri:ident, $index:expr, $stop_mode:ident) => { impl crate::dma::SealedChannel for crate::peripherals::$channel_peri { fn id(&self) -> u8 { $index } + + #[cfg(feature = "low-power")] + fn stop_mode(&self) -> crate::rcc::StopMode { + crate::rcc::StopMode::$stop_mode + } } impl crate::dma::ChannelInterrupt for crate::peripherals::$channel_peri { unsafe fn on_irq() { - crate::dma::AnyChannel { id: $index }.on_irq(); + crate::dma::AnyChannel { + id: $index, + #[cfg(feature = "low-power")] + stop_mode: crate::rcc::StopMode::$stop_mode, + } + .on_irq(); } } @@ -80,15 +95,57 @@ macro_rules! dma_channel_impl { fn from(val: crate::peripherals::$channel_peri) -> Self { Self { id: crate::dma::SealedChannel::id(&val), + #[cfg(feature = "low-power")] + stop_mode: crate::dma::SealedChannel::stop_mode(&val), } } } }; } +pub(crate) struct BusyChannel<'a> { + channel: Peri<'a, AnyChannel>, +} + +impl<'a> BusyChannel<'a> { + pub fn new(channel: Peri<'a, AnyChannel>) -> Self { + #[cfg(feature = "low-power")] + critical_section::with(|cs| { + crate::rcc::increment_stop_refcount(cs, channel.stop_mode); + }); + + Self { channel } + } +} + +impl<'a> Drop for BusyChannel<'a> { + fn drop(&mut self) { + #[cfg(feature = "low-power")] + critical_section::with(|cs| { + crate::rcc::decrement_stop_refcount(cs, self.stop_mode); + }); + } +} + +impl<'a> ops::Deref for BusyChannel<'a> { + type Target = Peri<'a, AnyChannel>; + + fn deref(&self) -> &Self::Target { + &self.channel + } +} + +impl<'a> ops::DerefMut for BusyChannel<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.channel + } +} + /// Type-erased DMA channel. pub struct AnyChannel { pub(crate) id: u8, + #[cfg(feature = "low-power")] + pub(crate) stop_mode: crate::rcc::StopMode, } impl_peripheral!(AnyChannel); @@ -103,6 +160,11 @@ impl SealedChannel for AnyChannel { fn id(&self) -> u8 { self.id } + + #[cfg(feature = "low-power")] + fn stop_mode(&self) -> crate::rcc::StopMode { + self.stop_mode + } } impl Channel for AnyChannel {} diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 9de49c867..bd8290da0 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -50,7 +50,8 @@ use critical_section::CriticalSection; use embassy_executor::*; use crate::interrupt; -use crate::rcc::{RCC_CONFIG, REFCOUNT_STOP1, REFCOUNT_STOP2}; +pub use crate::rcc::StopMode; +use crate::rcc::{RCC_CONFIG, REFCOUNT_STOP1, REFCOUNT_STOP2, decrement_stop_refcount, increment_stop_refcount}; use crate::time_driver::get_driver; const THREAD_PENDER: usize = usize::MAX; @@ -73,15 +74,8 @@ impl DeviceBusy { /// Create a new DeviceBusy. pub fn new(stop_mode: StopMode) -> Self { - critical_section::with(|_| unsafe { - match stop_mode { - StopMode::Stop1 => { - crate::rcc::REFCOUNT_STOP1 += 1; - } - StopMode::Stop2 => { - crate::rcc::REFCOUNT_STOP2 += 1; - } - } + critical_section::with(|cs| { + increment_stop_refcount(cs, stop_mode); }); Self(stop_mode) @@ -90,15 +84,8 @@ impl DeviceBusy { impl Drop for DeviceBusy { fn drop(&mut self) { - critical_section::with(|_| unsafe { - match self.0 { - StopMode::Stop1 => { - crate::rcc::REFCOUNT_STOP1 -= 1; - } - StopMode::Stop2 => { - crate::rcc::REFCOUNT_STOP2 -= 1; - } - } + critical_section::with(|cs| { + decrement_stop_refcount(cs, self.0); }); } } @@ -131,22 +118,12 @@ foreach_interrupt! { /// prevents entering the given stop mode. pub fn stop_ready(stop_mode: StopMode) -> bool { critical_section::with(|cs| match Executor::stop_mode(cs) { - Some(StopMode::Stop2) => true, + Some(StopMode::Standby | StopMode::Stop2) => true, Some(StopMode::Stop1) => stop_mode == StopMode::Stop1, None => false, }) } -/// Available Stop modes. -#[non_exhaustive] -#[derive(PartialEq)] -pub enum StopMode { - /// STOP 1 - Stop1, - /// STOP 2 - Stop2, -} - #[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wb, stm32wlex, stm32u0))] use crate::pac::pwr::vals::Lpms; @@ -156,9 +133,9 @@ impl Into for StopMode { match self { StopMode::Stop1 => Lpms::STOP1, #[cfg(not(any(stm32wb, stm32wba)))] - StopMode::Stop2 => Lpms::STOP2, + StopMode::Standby | StopMode::Stop2 => Lpms::STOP2, #[cfg(any(stm32wb, stm32wba))] - StopMode::Stop2 => Lpms::STOP1, // TODO: WBA has no STOP2? + StopMode::Standby | StopMode::Stop2 => Lpms::STOP1, // TODO: WBA has no STOP2? } } } @@ -230,6 +207,7 @@ impl Executor { } fn stop_mode(_cs: CriticalSection) -> Option { + // We cannot enter standby because we will lose program state. if unsafe { REFCOUNT_STOP2 == 0 && REFCOUNT_STOP1 == 0 } { trace!("low power: stop 2"); Some(StopMode::Stop2) diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 66ee06e17..85434fa83 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -111,6 +111,32 @@ pub fn clocks<'a>(_rcc: &'a crate::Peri<'a, crate::peripherals::RCC>) -> &'a Clo unsafe { get_freqs() } } +#[cfg(feature = "low-power")] +pub(crate) fn increment_stop_refcount(_cs: CriticalSection, stop_mode: StopMode) { + match stop_mode { + StopMode::Standby => {} + StopMode::Stop2 => unsafe { + REFCOUNT_STOP2 += 1; + }, + StopMode::Stop1 => unsafe { + REFCOUNT_STOP1 += 1; + }, + } +} + +#[cfg(feature = "low-power")] +pub(crate) fn decrement_stop_refcount(_cs: CriticalSection, stop_mode: StopMode) { + match stop_mode { + StopMode::Standby => {} + StopMode::Stop2 => unsafe { + REFCOUNT_STOP2 -= 1; + }, + StopMode::Stop1 => unsafe { + REFCOUNT_STOP1 -= 1; + }, + } +} + pub(crate) trait SealedRccPeripheral { fn frequency() -> Hertz; #[allow(dead_code)] @@ -141,12 +167,19 @@ pub(crate) struct RccInfo { stop_mode: StopMode, } +/// Specifies a limit for the stop mode of the peripheral or the stop mode to be entered. +/// E.g. if `StopMode::Stop1` is selected, the peripheral prevents the chip from entering Stop1 mode. #[cfg(feature = "low-power")] #[allow(dead_code)] -pub(crate) enum StopMode { - Standby, - Stop2, +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum StopMode { + #[default] + /// Peripheral prevents chip from entering Stop1 or executor will enter Stop1 Stop1, + /// Peripheral prevents chip from entering Stop2 or executor will enter Stop2 + Stop2, + /// Peripheral does not prevent chip from entering Stop + Standby, } impl RccInfo { @@ -202,15 +235,7 @@ impl RccInfo { } #[cfg(feature = "low-power")] - match self.stop_mode { - StopMode::Standby => {} - StopMode::Stop2 => unsafe { - REFCOUNT_STOP2 += 1; - }, - StopMode::Stop1 => unsafe { - REFCOUNT_STOP1 += 1; - }, - } + increment_stop_refcount(_cs, self.stop_mode); // set the xxxRST bit let reset_ptr = self.reset_ptr(); @@ -268,15 +293,7 @@ impl RccInfo { } #[cfg(feature = "low-power")] - match self.stop_mode { - StopMode::Standby => {} - StopMode::Stop2 => unsafe { - REFCOUNT_STOP2 -= 1; - }, - StopMode::Stop1 => unsafe { - REFCOUNT_STOP1 -= 1; - }, - } + decrement_stop_refcount(_cs, self.stop_mode); // clear the xxxEN bit let enable_ptr = self.enable_ptr(); -- cgit