From aa97fe7cbd9e6b7dbb83b61133a38d436fb3efc0 Mon Sep 17 00:00:00 2001
From: Ben Gamari <ben@smart-cactus.org>
Date: Mon, 30 Oct 2023 13:36:52 -0400
Subject: [PATCH] stm32: Add some documentation to `low_power`

This begins to explain the operation of the low-power executor.
---
 embassy-stm32/src/low_power.rs   | 47 ++++++++++++++++++++++++++++++++
 embassy-stm32/src/time_driver.rs |  6 +++-
 2 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs
index d5846f530..385795bce 100644
--- a/embassy-stm32/src/low_power.rs
+++ b/embassy-stm32/src/low_power.rs
@@ -1,3 +1,50 @@
+/// The STM32 line of microcontrollers support various deep-sleep modes which exploit clock-gating
+/// to reduce power consumption. `embassy-stm32` provides a low-power executor, [`Executor`] which
+/// can use knowledge of which peripherals are currently blocked upon to transparently and safely
+/// enter such low-power modes (currently, only `STOP2`) when idle.
+///
+/// The executor determines which peripherals are active by their RCC state; consequently,
+/// low-power states can only be entered if all peripherals have been `drop`'d. There are a few
+/// exceptions to this rule:
+///
+///  * `GPIO`
+///  * `RCC`
+///
+/// Since entering and leaving low-power modes typically incurs a significant latency, the
+/// low-power executor will only attempt to enter when the next timer event is at least
+/// [`time_driver::MIN_STOP_PAUSE`] in the future.
+///
+/// Currently there is no macro analogous to `embassy_executor::main` for this executor;
+/// consequently one must define their entrypoint manually. Moveover, you must relinquish control
+/// of the `RTC` peripheral to the executor. This will typically look like
+///
+/// ```rust,no_run
+/// use embassy_executor::Spawner;
+/// use embassy_stm32::low_power::Executor;
+/// use embassy_stm32::rtc::{Rtc, RtcConfig};
+/// use static_cell::make_static;
+///
+/// #[cortex_m_rt::entry]
+/// fn main() -> ! {
+///     Executor::take().run(|spawner| {
+///         unwrap!(spawner.spawn(async_main(spawner)));
+///     });
+/// }
+///
+/// #[embassy_executor::task]
+/// async fn async_main(spawner: Spawner) {
+///     // initialize the platform...
+///     let mut config = embassy_stm32::Config::default();
+///     let p = embassy_stm32::init(config);
+///
+///     // give the RTC to the executor...
+///     let mut rtc = Rtc::new(p.RTC, RtcConfig::default());
+///     let rtc = make_static!(rtc);
+///     embassy_stm32::low_power::stop_with_rtc(rtc);
+///
+///     // your application here...
+/// }
+/// ```
 use core::arch::asm;
 use core::marker::PhantomData;
 use core::sync::atomic::{compiler_fence, Ordering};
diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs
index add8be831..564c9d086 100644
--- a/embassy-stm32/src/time_driver.rs
+++ b/embassy-stm32/src/time_driver.rs
@@ -345,6 +345,10 @@ impl RtcDriver {
         });
     }
 
+    #[cfg(feature = "low-power")]
+    /// The minimum pause time beyond which the executor will enter a low-power state.
+    pub(crate) const MIN_STOP_PAUSE: embassy_time::Duration = embassy_time::Duration::from_millis(250);
+
     #[cfg(feature = "low-power")]
     /// Pause the timer if ready; return err if not
     pub(crate) fn pause_time(&self) -> Result<(), ()> {
@@ -357,7 +361,7 @@ impl RtcDriver {
             self.stop_wakeup_alarm(cs);
 
             let time_until_next_alarm = self.time_until_next_alarm(cs);
-            if time_until_next_alarm < embassy_time::Duration::from_millis(250) {
+            if time_until_next_alarm < Self::MIN_STOP_PAUSE {
                 Err(())
             } else {
                 self.rtc