From dc467e89a0f093c1656eaf63955c28dd3b08be6c Mon Sep 17 00:00:00 2001
From: xoviat <xoviat@users.noreply.github.com>
Date: Sat, 4 Nov 2023 13:49:54 -0500
Subject: [PATCH] stm32: compute stop mode and workaround rtt test bug

---
 ci.sh                          |  2 --
 embassy-stm32/Cargo.toml       |  1 +
 embassy-stm32/build.rs         | 32 ++++++++++++++++++++++++++++----
 embassy-stm32/src/lib.rs       |  5 +++--
 embassy-stm32/src/low_power.rs | 34 ++++++++++++++++++++++++++++------
 embassy-stm32/src/rcc/mod.rs   |  9 +++++++++
 embassy-stm32/src/rtc/mod.rs   |  8 +-------
 tests/stm32/Cargo.toml         |  2 +-
 8 files changed, 71 insertions(+), 22 deletions(-)

diff --git a/ci.sh b/ci.sh
index 5c69d3a29..2e6bbae8e 100755
--- a/ci.sh
+++ b/ci.sh
@@ -218,8 +218,6 @@ cargo batch  \
 rm out/tests/stm32wb55rg/wpan_mac
 rm out/tests/stm32wb55rg/wpan_ble
 
-# unstable
-rm out/tests/stm32f429zi/stop
 
 # unstable, I think it's running out of RAM?
 rm out/tests/stm32f207zg/eth
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index bc79ab7a2..2a2df5c56 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -90,6 +90,7 @@ defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-emb
 
 exti = []
 low-power = [ "dep:embassy-executor", "embassy-executor/arch-cortex-m" ]
+low-power-debug-with-sleep = []
 embassy-executor = []
 
 ## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/)
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index 3400529c9..276c1d637 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -556,6 +556,32 @@ fn main() {
                 },
             };
 
+            /*
+                If LP and non-LP peripherals share the same RCC enable bit, then a refcount leak will result.
+
+                This should be checked in stm32-data-gen.
+            */
+            let stop_refcount = if p.name.starts_with("LP") {
+                quote! { REFCOUNT_STOP2 }
+            } else {
+                quote! { REFCOUNT_STOP1 }
+            };
+
+            let (incr_stop_refcount, decr_stop_refcount) = if p.name != "RTC" {
+                (
+                    quote! {
+                        #[cfg(feature = "low-power")]
+                        unsafe { crate::rcc::#stop_refcount += 1 };
+                    },
+                    quote! {
+                        #[cfg(feature = "low-power")]
+                        unsafe { crate::rcc::#stop_refcount -= 1 };
+                    },
+                )
+            } else {
+                (quote! {}, quote! {})
+            };
+
             g.extend(quote! {
                 impl crate::rcc::sealed::RccPeripheral for peripherals::#pname {
                     fn frequency() -> crate::time::Hertz {
@@ -563,8 +589,7 @@ fn main() {
                     }
                     fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) {
                         #before_enable
-                        #[cfg(feature = "low-power")]
-                        unsafe { crate::rcc::REFCOUNT_STOP2 += 1 };
+                        #incr_stop_refcount
                         crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(true));
                         #after_enable
                         #rst
@@ -572,8 +597,7 @@ fn main() {
                     fn disable_with_cs(_cs: critical_section::CriticalSection) {
                         #before_disable
                         crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(false));
-                        #[cfg(feature = "low-power")]
-                        unsafe { crate::rcc::REFCOUNT_STOP2 -= 1 };
+                        #decr_stop_refcount
                     }
                 }
 
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 23d42bae8..511da9179 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -228,8 +228,9 @@ pub fn init(config: Config) -> Peripherals {
 
             #[cfg(feature = "low-power")]
             {
-                crate::rcc::REFCOUNT_STOP2 = 0
-            };
+                crate::rcc::REFCOUNT_STOP2 = 0;
+                crate::rcc::REFCOUNT_STOP1 = 0;
+            }
         }
 
         p
diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs
index d5846f530..8e75f641a 100644
--- a/embassy-stm32/src/low_power.rs
+++ b/embassy-stm32/src/low_power.rs
@@ -33,11 +33,17 @@ pub fn stop_with_rtc(rtc: &'static Rtc) {
 }
 
 pub fn stop_ready(stop_mode: StopMode) -> bool {
-    unsafe { EXECUTOR.as_mut().unwrap() }.stop_ready(stop_mode)
+    match unsafe { EXECUTOR.as_mut().unwrap() }.stop_mode() {
+        Some(StopMode::Stop2) => true,
+        Some(StopMode::Stop1) => stop_mode == StopMode::Stop1,
+        None => false,
+    }
 }
 
 #[non_exhaustive]
+#[derive(PartialEq)]
 pub enum StopMode {
+    Stop1,
     Stop2,
 }
 
@@ -88,23 +94,39 @@ impl Executor {
         trace!("low power: stop with rtc configured");
     }
 
-    fn stop_ready(&self, stop_mode: StopMode) -> bool {
-        match stop_mode {
-            StopMode::Stop2 => unsafe { crate::rcc::REFCOUNT_STOP2 == 0 },
+    fn stop_mode(&self) -> Option<StopMode> {
+        if unsafe { crate::rcc::REFCOUNT_STOP2 == 0 } && unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } {
+            Some(StopMode::Stop2)
+        } else if unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } {
+            Some(StopMode::Stop1)
+        } else {
+            None
         }
     }
 
+    fn configure_stop(&mut self, _stop_mode: StopMode) {
+        // TODO: configure chip-specific settings for stop
+    }
+
     fn configure_pwr(&mut self) {
         self.scb.clear_sleepdeep();
 
         compiler_fence(Ordering::SeqCst);
 
-        if !self.stop_ready(StopMode::Stop2) {
+        let stop_mode = self.stop_mode();
+        if stop_mode.is_none() {
             trace!("low power: not ready to stop");
         } else if self.time_driver.pause_time().is_err() {
             trace!("low power: failed to pause time");
         } else {
-            trace!("low power: stop");
+            let stop_mode = stop_mode.unwrap();
+            match stop_mode {
+                StopMode::Stop1 => trace!("low power: stop 1"),
+                StopMode::Stop2 => trace!("low power: stop 2"),
+            }
+            self.configure_stop(stop_mode);
+
+            #[cfg(not(feature = "low-power-debug-with-sleep"))]
             self.scb.set_sleepdeep();
         }
     }
diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs
index 3b19e4b95..c11a9cc6b 100644
--- a/embassy-stm32/src/rcc/mod.rs
+++ b/embassy-stm32/src/rcc/mod.rs
@@ -181,6 +181,15 @@ pub struct Clocks {
 }
 
 #[cfg(feature = "low-power")]
+/// Must be written within a critical section
+///
+/// May be read without a critical section
+pub(crate) static mut REFCOUNT_STOP1: u32 = 0;
+
+#[cfg(feature = "low-power")]
+/// Must be written within a critical section
+///
+/// May be read without a critical section
 pub(crate) static mut REFCOUNT_STOP2: u32 = 0;
 
 /// Frozen clock frequencies
diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs
index c8990c30a..68836be07 100644
--- a/embassy-stm32/src/rtc/mod.rs
+++ b/embassy-stm32/src/rtc/mod.rs
@@ -143,13 +143,7 @@ impl Default for RtcCalibrationCyclePeriod {
 impl Rtc {
     pub fn new(_rtc: impl Peripheral<P = RTC>, rtc_config: RtcConfig) -> Self {
         #[cfg(not(any(stm32l0, stm32f3, stm32l1, stm32f0, stm32f2)))]
-        critical_section::with(|cs| {
-            <RTC as crate::rcc::sealed::RccPeripheral>::enable_and_reset_with_cs(cs);
-            #[cfg(feature = "low-power")]
-            unsafe {
-                crate::rcc::REFCOUNT_STOP2 -= 1
-            };
-        });
+        <RTC as crate::rcc::sealed::RccPeripheral>::enable_and_reset();
 
         let mut this = Self {
             #[cfg(feature = "low-power")]
diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml
index 1ca14ee87..db265daae 100644
--- a/tests/stm32/Cargo.toml
+++ b/tests/stm32/Cargo.toml
@@ -33,7 +33,7 @@ stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"]
 eth = []
 rng = []
 sdmmc = []
-stop = ["embassy-stm32/low-power"]
+stop = ["embassy-stm32/low-power", "embassy-stm32/low-power-debug-with-sleep"]
 chrono = ["embassy-stm32/chrono", "dep:chrono"]
 can = []
 ble = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/ble"]