esp_hal/rtc_cntl/sleep/mod.rs
1//! # RTC Control Sleep Module
2//!
3//! ## Overview
4//! The `sleep` module allows configuring various wakeup sources and setting up
5//! the sleep behavior based on those sources. The supported wakeup sources
6//! include:
7//! * `GPIO` pins - light sleep only
8//! * timers
9//! * `SDIO (Secure Digital Input/Output) - light sleep only`
10//! * `MAC (Media Access Control)` wake - light sleep only
11//! * `UART0` - light sleep only
12//! * `UART1` - light sleep only
13//! * `touch`
14//! * `ULP (Ultra-Low Power)` wake
15//! * `BT (Bluetooth) wake` - light sleep only
16
17use crate::{
18 gpio,
19 peripherals::LPWR,
20 rtc_cntl::{Rtc, WakeupSource},
21};
22
23#[cfg(soc_has_pmu)]
24mod pmu_common;
25
26#[cfg_attr(esp32, path = "esp32.rs")]
27#[cfg_attr(esp32s2, path = "esp32s2.rs")]
28#[cfg_attr(esp32s3, path = "esp32s3.rs")]
29#[cfg_attr(esp32c3, path = "esp32c3.rs")]
30#[cfg_attr(esp32c5, path = "esp32c5.rs")]
31#[cfg_attr(esp32c61, path = "esp32c61.rs")]
32#[cfg_attr(esp32c6, path = "esp32c6.rs")]
33#[cfg_attr(esp32c2, path = "esp32c2.rs")]
34#[cfg_attr(esp32h2, path = "esp32h2.rs")]
35#[cfg_attr(esp32p4, path = "esp32p4.rs")]
36mod sleep_impl;
37pub use sleep_impl::*;
38
39#[cfg(sleep_has_wakeup_source_timer)]
40mod timer;
41
42mod wakeup;
43pub(crate) use wakeup::*;
44
45/// Prepares the sleep hardware, and clears the wakeup sources of the previous run.
46///
47/// The wakeup-enable mask survives a deep-sleep wake, so here it still holds the request of the run
48/// that went to sleep. Two steps need the mask, in this order. First, the code releases the pads
49/// that the previous run armed. Then it clears the mask, because a program starts with no wakeup
50/// sources, and the drivers of the new run build the mask again.
51pub(crate) fn init(rtc: &Rtc<'_>) {
52 // First, because the pads that ended a deep sleep are readable only until the code below
53 // changes a path.
54 gpio::wakeup::record_wakeup();
55
56 // Release the pads after a deep-sleep wake only, and only if the previous
57 // run armed an IO wake source.
58 if super::reset_reason(crate::system::Cpu::ProCpu) == Some(super::SocResetReason::CoreDeepSleep)
59 && gpio::wakeup::wake_enabled()
60 {
61 gpio::wakeup::wake_io_reset();
62 }
63
64 RtcSleepConfig::base_settings(rtc);
65
66 set_mask(0);
67}
68
69/// Low-power management.
70///
71/// The sleep calls do not take the wakeup sources that end the sleep. Each driver enables the
72/// source that it owns, and the hardware wakeup-enable mask keeps that request until the driver
73/// clears it. The mask keeps it through a light sleep, and through a deep-sleep wake. A sleep call
74/// reads the mask back, and calculates the rest of the configuration from it.
75#[instability::unstable]
76pub struct LowPower<'d> {
77 _inner: LPWR<'d>,
78}
79
80impl<'d> LowPower<'d> {
81 /// Creates a new `LowPower` driver.
82 pub fn new(lpwr: LPWR<'d>) -> Self {
83 Self { _inner: lpwr }
84 }
85
86 /// Arms the sleep alarm for `deadline`, and enables the timer wakeup source.
87 ///
88 /// The deadline is absolute, so the time between this call and the sleep does not make the
89 /// sleep shorter. The deadline is a standing request. The wake that it causes does not
90 /// disarm it, a later call replaces it, and [`Self::clear_wakeup_deadline`] removes it.
91 ///
92 /// A deadline in the past ends a light sleep immediately, and makes [`Self::sleep_deep`] panic.
93 #[cfg(sleep_has_wakeup_source_timer)]
94 pub fn set_wakeup_deadline(&mut self, deadline: crate::time::Instant) {
95 timer::set_deadline(deadline);
96 }
97
98 /// Disarms the sleep alarm, and disables the timer wakeup source.
99 #[cfg(sleep_has_wakeup_source_timer)]
100 pub fn clear_wakeup_deadline(&mut self) {
101 timer::clear_deadline();
102 }
103
104 /// Enters deep sleep, and does not return.
105 ///
106 /// In deep sleep the CPUs, most of the RAM, and all digital peripherals that are clocked from
107 /// APB_CLK are powered off. The wake resets the chip, so use the
108 /// [`#[esp_hal::ram(persistent)]`][procmacros::ram] attribute to keep a variable through the
109 /// sleep.
110 ///
111 /// The hardware cannot reject this sleep, because the function cannot return to report the
112 /// rejection. Use [`Self::sleep_deep_with_rejection`] for that.
113 ///
114 /// # Panics
115 ///
116 /// Panics if no wakeup source is enabled, because then nothing can end the sleep. Panics also
117 /// if the armed wakeup deadline is too near for the sleep transition to catch it. In both
118 /// cases the chip never wakes again, and it gives no report of the cause.
119 #[cfg(sleep_deep_sleep)]
120 pub fn sleep_deep(&mut self, config: RtcSleepConfig) -> ! {
121 #[cfg(sleep_has_wakeup_source_timer)]
122 if enabled_sources().contains(WakeupSource::Timer) {
123 assert!(
124 !timer::deadline_missed(),
125 "the wakeup deadline is too near to be caught by the sleep transition"
126 );
127 }
128
129 self.sleep(config, SleepKind::Deep, false);
130
131 unreachable!("deep sleep without rejection cannot return")
132 }
133
134 /// Enters deep sleep, and returns only if the hardware rejects the request.
135 ///
136 /// The hardware rejects a sleep if one of its wakeup sources is already asserted. Without the
137 /// rejection, the chip sleeps through the event that the caller wants to wake on. The return of
138 /// this function is the complete report, so it gives no other result.
139 ///
140 /// A rejected request returns the wake pads to their drivers, but it cannot return every pad.
141 /// Sleep entry disconnects the pads that no hold keeps, on the chips that need that step to
142 /// reach the deep-sleep current, and it cannot know their earlier configuration. Configure
143 /// those pads again if this function returns. ESP-IDF has the same limit in
144 /// `esp_deep_sleep_try_to_start`.
145 ///
146 /// # Panics
147 ///
148 /// Panics if no wakeup source is enabled.
149 #[cfg(sleep_deep_sleep)]
150 pub fn sleep_deep_with_rejection(&mut self, config: RtcSleepConfig) {
151 self.sleep(config, SleepKind::Deep, true);
152 }
153
154 /// Enters light sleep, and returns when a wakeup source ends it.
155 ///
156 /// Light sleep keeps the state of the digital domain, so the program continues at the same
157 /// place.
158 ///
159 /// The function also returns immediately, without a sleep, if no wakeup source is enabled, or
160 /// if the hardware rejects the request because a wakeup source is already asserted. It
161 /// reports neither case. For the caller, a refused sleep, a rejected sleep and a very short
162 /// sleep have the same result.
163 #[cfg(sleep_light_sleep)]
164 pub fn sleep_light(&mut self, config: RtcSleepConfig) {
165 self.sleep(config, SleepKind::Light, true);
166 }
167
168 /// Calculates the sleep configuration from the wakeup-enable mask, and enters the sleep.
169 #[cfg(sleep_driver_supported)]
170 #[crate::ram]
171 fn sleep(&mut self, config: RtcSleepConfig, kind: SleepKind, allow_reject: bool) {
172 let rtc = Rtc::new(unsafe { crate::peripherals::RTC_TIMER::steal() });
173
174 let mut config = config;
175 config.set_sleep_kind(kind);
176
177 // The hooks run before `apply`, so that a request to keep a power domain powered reaches
178 // the hardware. They also run before the last read of the mask, because a hook can
179 // enable another source. The GPIO hook does this while it allocates its pins to the
180 // paths.
181 run_entry_hooks(&mut config);
182
183 config.apply();
184
185 // A sleep with no wakeup source never ends. No counter overflow ends it either.
186 let wakeup_mask = mask();
187 if wakeup_mask == 0 {
188 match kind {
189 // A refused sleep gives the same result as a rejected sleep, and light sleep does
190 // not report that case either.
191 SleepKind::Light => return,
192 SleepKind::Deep => {
193 panic!("no wakeup source is enabled, so nothing could end the sleep")
194 }
195 }
196 }
197
198 let reject_mask = if allow_reject { reject_mask() } else { 0 };
199
200 sleep_uart_prepare();
201
202 // Last, because this step takes the pads away from the peripherals that drove them. The
203 // wakeup sources have their holds now, and no later step needs a pad.
204 #[cfg(sleep_deep_sleep_needs_gpio_isolation)]
205 if kind == SleepKind::Deep {
206 gpio::wakeup::isolate_pads_for_deep_sleep();
207 }
208
209 // Latch the systimer value *before* sleeping. The systimer keeps running during
210 // the sleep enter/exit sequences, so we must not advance from the post-wake
211 // value (that would count the enter/exit time twice). Instead we set an absolute
212 // target of `before + slept`, measured by the always-running LP timer.
213 let before_ticks = crate::time::implem::raw_counter();
214 let before = rtc.time_since_boot_raw();
215
216 let _uart0_sclk_guard = crate::system::ensure_uart0_sclk_enabled();
217 let rejected = {
218 // A chip can keep a guard for the length of the sleep, to restore what sleep entry
219 // changed for the sleep only. The guard must therefore outlive the wait below.
220 #[allow(clippy::let_unit_value)]
221 let _sleep_guard = config.start_sleep(wakeup_mask, reject_mask);
222 let rejected = wait_for_sleep_result();
223
224 if config.is_deep_sleep() && !rejected {
225 // The chip is entering deep sleep, and the wake resets it. Because RTC is in a
226 // slower clock domain than the CPU, the power-down can take several CPU cycles.
227 loop {
228 core::hint::spin_loop();
229 }
230 }
231
232 rejected
233 };
234
235 config.finish_sleep();
236
237 let after = rtc.time_since_boot_raw();
238
239 let slept_us = crate::clock::rtc_ticks_to_us(after.wrapping_sub(before));
240 let slept_ticks = crate::time::implem::us_to_ticks(slept_us);
241
242 unsafe { crate::time::implem::update_counter(before_ticks + slept_ticks) };
243 sleep_uart_resume();
244
245 run_exit_hooks();
246
247 // Unlike deep sleep, light sleep does not reset the chip, so `wakeup_cause` cannot rely on
248 // the reset reason to tell whether a wakeup occurred. A rejected request is not a wakeup,
249 // and it must not name a wakeup source.
250 // https://github.com/espressif/esp-idf/blob/a45d713b03fd96d8805d1cc116f02a4415b360c7/components/esp_hw_support/sleep_modes.c#L2158
251 if !config.is_deep_sleep() && !rejected {
252 super::LIGHT_SLEEP_WAKEUP.store(true, portable_atomic::Ordering::Relaxed);
253 }
254
255 // Last, because this call reads the wakeup cause, and after a light sleep the cause is
256 // available only after the line above.
257 gpio::wakeup::record_wakeup();
258 }
259}
260
261/// Waits for the hardware to report the result of the sleep request, and returns whether the
262/// hardware rejected the request.
263///
264/// A deep sleep powers the CPU down inside this loop, and a light sleep stops the CPU here until a
265/// wakeup source ends the sleep. A rejected request does neither, so the reject interrupt is the
266/// only report of that case. ESP-IDF waits in the same place, in `rtc_sleep_start` and in
267/// `pmu_sleep_start`.
268#[cfg(sleep_driver_supported)]
269fn wait_for_sleep_result() -> bool {
270 loop {
271 cfg_select! {
272 soc_has_pmu => {
273 let int_raw = crate::peripherals::PMU::regs().int_raw().read();
274 if int_raw.soc_wakeup().bit_is_set() || int_raw.soc_sleep_reject().bit_is_set() {
275 return int_raw.soc_sleep_reject().bit_is_set();
276 }
277 }
278 _ => {
279 let int_raw = LPWR::regs().int_raw().read();
280 if int_raw.slp_wakeup().bit_is_set() || int_raw.slp_reject().bit_is_set() {
281 return int_raw.slp_reject().bit_is_set();
282 }
283 }
284 }
285 }
286}
287
288#[cfg(sleep_driver_supported)]
289fn sleep_uart_prepare() {
290 use crate::uart::Instance;
291 for_each_uart! {
292 ($id:literal, $inst:ident, $peri:ident, $rxd:ident, $txd:ident, $cts:ident, $rts:ident, wakeup_source = $_:literal) => {
293 unsafe {
294 crate::peripherals::$inst::steal().info().suspend_for_sleep();
295 }
296 };
297 }
298}
299
300#[cfg(sleep_driver_supported)]
301fn sleep_uart_resume() {
302 use crate::uart::Instance;
303 for_each_uart! {
304 ($id:literal, $inst:ident, $peri:ident, $rxd:ident, $txd:ident, $cts:ident, $rts:ident, wakeup_source = $_:literal) => {
305 unsafe {
306 crate::peripherals::$inst::steal().info().resume_from_sleep();
307 }
308 };
309 }
310}