Skip to main content

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 esp_sync::raw::{RawLock, SingleCoreInterruptLock};
18
19#[cfg(sleep_deep_sleep)]
20use crate::rtc_cntl::WakeupSource;
21use crate::{gpio, peripherals::LPWR, rtc_cntl::Rtc};
22
23#[cfg(soc_has_pmu)]
24pub(crate) mod 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(esp32h4, path = "esp32h4.rs")]
36#[cfg_attr(esp32p4, path = "esp32p4.rs")]
37#[cfg_attr(esp32s31, path = "esp32s31.rs")]
38mod sleep_impl;
39pub use sleep_impl::*;
40
41#[cfg(sleep_has_wakeup_source_timer)]
42mod timer;
43
44mod wakeup;
45pub(crate) use wakeup::*;
46#[instability::unstable]
47pub use wakeup::{SleepEntryHook, SleepExitHook, WrappedSleepConfig};
48
49/// The result of a light sleep.
50#[cfg(sleep_light_sleep)]
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
53#[instability::unstable]
54pub enum LightSleep {
55    /// The call returned from the sleep.
56    ///
57    /// The sleep may also never have started, because no wakeup source was enabled, because the
58    /// hardware rejected the request, or because a sleep limit was too short for the transition to
59    /// catch. This result does not tell those apart from a sleep that a wakeup source ended.
60    Ended,
61
62    /// A wakeup source refused the sleep, so the chip stayed awake.
63    ///
64    /// The refusal holds until the state that caused it changes, so a second call refuses too.
65    /// Wait for an interrupt instead.
66    Refused,
67}
68
69#[cfg(supports_cpu_power_down)]
70use crate::rtc_cntl::cpu_retention;
71
72/// The handler that answers whether this core still agrees to a light sleep, or null.
73///
74/// Only the rendezvous of the software retention path reads this, which is why the read lives
75/// there.
76#[cfg(all(multi_core, sleep_driver_supported))]
77pub(crate) static CAN_SLEEP_HANDLER: portable_atomic::AtomicPtr<()> =
78    portable_atomic::AtomicPtr::new(core::ptr::null_mut());
79
80/// The parts of the sleep path that an RTOS fills in.
81#[cfg(all(multi_core, sleep_driver_supported))]
82#[doc(hidden)]
83#[instability::unstable]
84pub mod __rtos_implementation {
85    use portable_atomic::Ordering;
86
87    /// Registers the function that answers whether the core that runs it agrees to a light sleep.
88    ///
89    /// A light sleep that powers the CPU domain down needs every running core to save itself, so
90    /// the core that calls the sleep function asks the other core to join it. The core that calls
91    /// decides for itself. The other core answers this function, and a `false` answer stops the
92    /// sleep. Either core can be the one that answers.
93    ///
94    /// The answer holds for the whole sleep. The core that gives it has its interrupts disabled
95    /// already, so nothing can give that core work while the sleep runs.
96    ///
97    /// # Safety
98    ///
99    /// The handler runs in an interrupt handler, with interrupts disabled and with the caches of
100    /// the sleep path. It must return, it must take no lock that a core can hold across a sleep,
101    /// and it must stay callable for the life of the program.
102    pub unsafe fn set_can_sleep_handler(handler: fn() -> bool) {
103        super::CAN_SLEEP_HANDLER.store(handler as *mut (), Ordering::Release);
104    }
105}
106
107/// Prepares the sleep hardware, and clears the wakeup sources of the previous run.
108///
109/// The wakeup-enable mask survives a deep-sleep wake, so here it still holds the request of the run
110/// that went to sleep. Two steps need the mask, in this order. First, the code releases the pads
111/// that the previous run armed. Then it clears the mask, because a program starts with no wakeup
112/// sources, and the drivers of the new run build the mask again.
113pub(crate) fn init(rtc: &Rtc<'_>) {
114    // First, because the pads that ended a deep sleep are readable only until the code below
115    // changes a path.
116    gpio::wakeup::record_wakeup();
117
118    // Release the pads after a deep-sleep wake only, and only if the previous
119    // run armed an IO wake source.
120    if super::reset_reason(crate::system::Cpu::ProCpu) == Some(super::SocResetReason::CoreDeepSleep)
121        && gpio::wakeup::wake_enabled()
122    {
123        gpio::wakeup::wake_io_reset();
124    }
125
126    RtcSleepConfig::base_settings(rtc);
127
128    set_mask(0);
129}
130
131/// Low-power management.
132///
133/// The sleep calls do not take the wakeup sources that end the sleep. Each driver enables the
134/// source that it owns, and the hardware wakeup-enable mask keeps that request until the driver
135/// clears it. The mask keeps it through a light sleep, and through a deep-sleep wake. A sleep call
136/// reads the mask back, and calculates the rest of the configuration from it.
137#[instability::unstable]
138pub struct LowPower<'d> {
139    _inner: LPWR<'d>,
140}
141
142impl<'d> LowPower<'d> {
143    /// Creates a new `LowPower` driver.
144    pub fn new(lpwr: LPWR<'d>) -> Self {
145        Self { _inner: lpwr }
146    }
147
148    /// Arms the sleep alarm for `deadline`, and enables the timer wakeup source.
149    ///
150    /// The deadline is absolute, so the time between this call and the sleep does not make the
151    /// sleep shorter. The deadline is a standing request. The wake that it causes does not
152    /// disarm it, a later call replaces it, and [`Self::clear_wakeup_deadline`] removes it.
153    ///
154    /// A deadline in the past ends a light sleep immediately, and makes [`Self::sleep_deep`] panic.
155    ///
156    /// A wakeup source can limit one sleep to an earlier wake. The limit does not replace this
157    /// deadline. The deadline stays armed for the next sleep.
158    #[cfg(sleep_has_wakeup_source_timer)]
159    pub fn set_wakeup_deadline(&mut self, deadline: crate::time::Instant) {
160        timer::set_deadline(deadline);
161    }
162
163    /// Disarms the sleep alarm, and disables the timer wakeup source.
164    #[cfg(sleep_has_wakeup_source_timer)]
165    pub fn clear_wakeup_deadline(&mut self) {
166        timer::clear_deadline();
167    }
168
169    /// Enters deep sleep, and does not return.
170    ///
171    /// In deep sleep the CPUs, most of the RAM, and all digital peripherals that are clocked from
172    /// APB_CLK are powered off. The wake resets the chip, so use the
173    /// [`#[esp_hal::ram(persistent)]`][procmacros::ram] attribute to keep a variable through the
174    /// sleep.
175    ///
176    /// The hardware cannot reject this sleep, because the function cannot return to report the
177    /// rejection. Use [`Self::sleep_deep_with_rejection`] for that.
178    ///
179    /// A wakeup source cannot refuse this sleep. The wake resets the chip, so the call cannot
180    /// return a refusal. [`Self::sleep_light`] reports a refusal.
181    ///
182    /// # Panics
183    ///
184    /// Panics if no wakeup source is enabled, because then nothing can end the sleep. Panics also
185    /// if the armed wakeup deadline is too near for the sleep transition to catch it, or if a
186    /// wakeup source limits the sleep to less time than the transition can catch. In these cases
187    /// the chip never wakes again, and it gives no report of the cause.
188    #[cfg(sleep_deep_sleep)]
189    #[cfg(feature = "rt")]
190    pub fn sleep_deep(&mut self, config: RtcSleepConfig) -> ! {
191        #[cfg(sleep_has_wakeup_source_timer)]
192        if enabled_sources().contains(WakeupSource::Timer) {
193            assert!(
194                !timer::deadline_missed(),
195                "the wakeup deadline is too near to be caught by the sleep transition"
196            );
197        }
198
199        self.sleep(config, SleepKind::Deep, false);
200
201        unreachable!("deep sleep without rejection cannot return")
202    }
203
204    /// Enters deep sleep, and returns only if the hardware rejects the request.
205    ///
206    /// The hardware rejects a sleep if one of its wakeup sources is already asserted. Without the
207    /// rejection, the chip sleeps through the event that the caller wants to wake on. The return of
208    /// this function is the complete report, so it gives no other result.
209    ///
210    /// A wakeup source cannot refuse this sleep. A wakeup source can limit it. The function also
211    /// returns when that limit is too short for the sleep transition to catch. The chip stays
212    /// awake. The return does not say which case it was.
213    ///
214    /// A rejected request returns the wake pads to their drivers, but it cannot return every pad.
215    /// Sleep entry disconnects the pads that no hold keeps, on the chips that need that step to
216    /// reach the deep-sleep current, and it cannot know their earlier configuration. Configure
217    /// those pads again if this function returns. ESP-IDF has the same limit in
218    /// `esp_deep_sleep_try_to_start`.
219    ///
220    /// # Panics
221    ///
222    /// Panics if no wakeup source is enabled.
223    #[cfg(sleep_deep_sleep)]
224    #[cfg(feature = "rt")]
225    pub fn sleep_deep_with_rejection(&mut self, config: RtcSleepConfig) {
226        self.sleep(config, SleepKind::Deep, true);
227    }
228
229    /// Enters light sleep, and returns when a wakeup source ends it.
230    ///
231    /// Light sleep keeps the state of the digital domain, so the program continues at the same
232    /// place.
233    ///
234    /// A wakeup source can refuse the sleep, which keeps the chip awake. A direct call and
235    /// automatic light sleep both honor a refusal. The return reports it, because the caller then
236    /// has to wait for the event itself.
237    ///
238    /// A wakeup source can also limit the length of the sleep. The chip wakes at the sooner of that
239    /// limit and the armed wakeup deadline. When the timer source is not enabled, the limit enables
240    /// it for this sleep and disables it again afterwards. The sleep does not start when that wake
241    /// is already due, or when the transition cannot catch it. The result is [`LightSleep::Ended`],
242    /// not [`LightSleep::Refused`].
243    ///
244    /// The function also returns without a sleep if no wakeup source is enabled, or if the
245    /// hardware rejects the request because a wakeup source is already asserted. It reports
246    /// neither case. For the caller, a rejected sleep and a very short sleep have the same
247    /// result.
248    #[cfg(sleep_light_sleep)]
249    #[cfg(feature = "rt")]
250    pub fn sleep_light(&mut self, config: RtcSleepConfig) -> LightSleep {
251        if self.sleep(config, SleepKind::Light, true) {
252            LightSleep::Refused
253        } else {
254            LightSleep::Ended
255        }
256    }
257
258    /// Calculates the sleep configuration from the wakeup-enable mask, and enters the sleep.
259    ///
260    /// Returns whether a wakeup source refused a light sleep. A deep sleep ignores a refusal,
261    /// so this function does not return `true` for a deep sleep.
262    ///
263    /// A sleep limit can end the call before the sleep starts. That result is `false`. It is not a
264    /// refusal. A deep sleep whose limit the transition cannot catch panics when the caller cannot
265    /// accept a rejection.
266    #[cfg(sleep_driver_supported)]
267    #[crate::ram]
268    #[cfg(feature = "rt")]
269    pub(crate) fn sleep(
270        &mut self,
271        config: RtcSleepConfig,
272        kind: SleepKind,
273        allow_reject: bool,
274    ) -> bool {
275        // ESP-IDF runs `esp_light_sleep_start` inside `portENTER_CRITICAL(&light_sleep_lock)`.
276        // A sleep ends when `wait_for_sleep_result` sees a wakeup or reject bit, so nothing in
277        // this path needs interrupts.
278        let irq_token = unsafe { SingleCoreInterruptLock.enter() };
279        let refused = cfg_select! {
280            all(cpu_retention = "software", multi_core) => {
281                // The PRO CPU requests every light sleep, because the wake releases the APP CPU
282                // from the PRO CPU.
283                match (kind, crate::system::Cpu::current()) {
284                    (SleepKind::Light, crate::system::Cpu::ProCpu) => {
285                        cpu_retention::rendezvous::take_request();
286                        let refused =
287                            self.sleep_with_interrupts_disabled(config, kind, allow_reject);
288                        cpu_retention::rendezvous::release_app_cpu();
289                        refused
290                    }
291                    // The APP CPU does not learn whether a wakeup source refused the sleep.
292                    (SleepKind::Light, _) => {
293                        cpu_retention::rendezvous::sleep_from_app_cpu(config);
294                        false
295                    }
296                    _ => self.sleep_with_interrupts_disabled(config, kind, allow_reject),
297                }
298            }
299            _ => self.sleep_with_interrupts_disabled(config, kind, allow_reject),
300        };
301        unsafe { SingleCoreInterruptLock.exit(irq_token) };
302        refused
303    }
304
305    #[cfg(sleep_driver_supported)]
306    #[crate::ram]
307    #[cfg(feature = "rt")]
308    fn sleep_with_interrupts_disabled(
309        &mut self,
310        config: RtcSleepConfig,
311        kind: SleepKind,
312        allow_reject: bool,
313    ) -> bool {
314        let rtc = Rtc::new(unsafe { crate::peripherals::RTC_TIMER::steal() });
315
316        let mut config = config;
317        config.set_sleep_kind(kind);
318
319        // Retention serves light sleep only. Deep sleep resets the chip, so it has no CPU state to
320        // bring back, and an armed descriptor would outlive the sleep in the RTC domain. esp-idf
321        // arms retention from its light sleep path alone.
322        //
323        // The buffer is read once, because the rendezvous and the power-down must agree on it. A
324        // buffer that another core installs during this sleep serves the next one.
325        #[cfg(supports_cpu_power_down)]
326        let retention_buffer = match kind {
327            SleepKind::Light => crate::rtc_cntl::installed_buffer_ptr(),
328            SleepKind::Deep => None,
329        };
330
331        // A chip that retains the CPU in software needs every running core to save itself, so the
332        // rendezvous comes first, before any step that a refusal would have to undo. A return
333        // after it must call `rendezvous::finish`.
334        #[cfg(all(cpu_retention = "software", multi_core))]
335        if retention_buffer.is_some() && !cpu_retention::rendezvous::engage() {
336            return false;
337        }
338
339        // The hooks run before `apply`, so that a request to keep a power domain powered reaches
340        // the hardware. They also run before the last read of the mask, because a hook can
341        // enable another source. The GPIO hook does this while it allocates its pins to the
342        // paths.
343        let entry = run_entry_hooks(&mut config);
344
345        // Stop before `apply` when a hook refused a light sleep. `apply` writes the sleep
346        // configuration, and the chip must stay awake with that configuration unchanged.
347        // Entry hooks can already have moved a pad. The exit hooks put the pad back.
348        // A deep sleep ignores the refusal. The wake resets the chip, so there is no caller.
349        if kind == SleepKind::Light && entry.refused {
350            #[cfg(sleep_has_wakeup_source_timer)]
351            if let Some(clamp) = entry.clamp {
352                timer::restore_after_limit(clamp);
353            }
354            run_exit_hooks();
355            #[cfg(all(cpu_retention = "software", multi_core))]
356            cpu_retention::rendezvous::finish();
357            return true;
358        }
359
360        // A limit inside the transition window cannot wake the chip. esp32 cannot reject on the
361        // timer, so the hardware does not report the miss. Stay awake. The exit hooks undo pad
362        // changes from the entry hooks. The comparator was not written.
363        #[cfg(sleep_has_wakeup_source_timer)]
364        if matches!(&entry.clamp, Some(timer::LimitClamp::TooShort)) {
365            run_exit_hooks();
366            #[cfg(all(cpu_retention = "software", multi_core))]
367            cpu_retention::rendezvous::finish();
368            if kind == SleepKind::Deep && !allow_reject {
369                panic!("the wakeup deadline is too near to be caught by the sleep transition");
370            }
371            return false;
372        }
373
374        // A deep sleep keeps what `RtcSleepConfig::deep` asked for, because the wake resets the
375        // chip and keeps no CPU state to lose.
376        #[cfg(supports_cpu_power_down)]
377        if kind == SleepKind::Light {
378            cpu_retention::configure_cpu_retention(&mut config, retention_buffer);
379        }
380
381        // The PMU chips write the configuration when the sleep starts.
382        #[cfg(not(soc_has_pmu))]
383        config.apply();
384
385        // A sleep with no wakeup source never ends. No counter overflow ends it either.
386        let wakeup_mask = mask();
387        if wakeup_mask == 0 {
388            // No sleep starts, so a clamp must not keep the comparator.
389            #[cfg(sleep_has_wakeup_source_timer)]
390            if let Some(clamp) = entry.clamp {
391                timer::restore_after_limit(clamp);
392            }
393            #[cfg(all(cpu_retention = "software", multi_core))]
394            cpu_retention::rendezvous::finish();
395
396            match kind {
397                // Not a software refusal. Light sleep does not report a missing wakeup source.
398                SleepKind::Light => return false,
399                SleepKind::Deep => {
400                    panic!("no wakeup source is enabled, so nothing could end the sleep")
401                }
402            }
403        }
404
405        let reject_mask = if allow_reject { reject_mask() } else { 0 };
406
407        sleep_uart_prepare();
408
409        // Latch the systimer value *before* sleeping. The systimer keeps running during
410        // the sleep enter/exit sequences, so we must not advance from the post-wake
411        // value (that would count the enter/exit time twice). Instead we set an absolute
412        // target of `before + slept`, measured by the always-running LP timer.
413        let before_ticks = crate::time::implem::raw_counter();
414        let before = rtc.time_since_boot_raw();
415
416        let _uart0_sclk_guard = crate::system::ensure_uart0_sclk_enabled();
417
418        #[cfg(multi_core)]
419        let parked = match kind {
420            SleepKind::Light => park_other_cores(),
421            // A deep sleep resets the chip, so the other core needs no stall, and nothing thaws
422            // it afterwards.
423            SleepKind::Deep => 0,
424        };
425
426        let rejected = {
427            // A chip can keep a guard for the length of the sleep, to restore what sleep entry
428            // changed for the sleep only. The guard must therefore outlive the wait below.
429            // ESP-IDF arms RTC_CNTL retention in `misc_modules_sleep_prepare`, before it arms the
430            // wakeup sources.
431
432            #[cfg(cpu_retention = "rtc_cntl")]
433            cpu_retention::prepare_cpu_retention(retention_buffer);
434
435            #[allow(clippy::let_unit_value)]
436            let _sleep_guard = config.start_sleep(wakeup_mask, reject_mask);
437
438            // Last, because this step takes the pads away from the peripherals that drove them. The
439            // wakeup sources have their holds now, and no later step needs a pad.
440            #[cfg(any(
441                sleep_deep_sleep_needs_gpio_isolation,
442                gpio_need_soft_isolate_during_pd
443            ))]
444            if kind == SleepKind::Deep {
445                gpio::wakeup::isolate_pads_for_deep_sleep();
446            }
447
448            let rejected = cfg_select! {
449                cpu_retention = "software" => {
450                    // The software chips save the CPU inside the request, so the request belongs to
451                    // them. ESP-IDF wraps `pmu_sleep_start` the same way (`sleep_modes.c:963-964`).
452                    cpu_retention::enter_sleep_with_retention(&config, retention_buffer)
453                }
454                _ => config.enter_sleep(),
455            };
456
457            if config.is_deep_sleep() && !rejected {
458                // The chip is entering deep sleep, and the wake resets it. Because RTC is in a
459                // slower clock domain than the CPU, the power-down can take several CPU cycles.
460                loop {
461                    core::hint::spin_loop();
462                }
463            }
464
465            rejected
466        };
467
468        #[cfg(supports_cpu_power_down)]
469        if kind == SleepKind::Light && retention_buffer.is_some() {
470            // First, because the other core comes back through the wake stub that the next call
471            // disarms.
472            #[cfg(all(cpu_retention = "software", multi_core))]
473            cpu_retention::rendezvous::finish();
474
475            cpu_retention::finish_cpu_retention(rejected);
476        }
477
478        config.finish_sleep();
479
480        #[cfg(multi_core)]
481        unpark_cores(parked);
482
483        let after = rtc.time_since_boot_raw();
484
485        let slept_us = crate::clock::rtc_ticks_to_us(after.wrapping_sub(before));
486        let slept_ticks = crate::time::implem::us_to_ticks(slept_us);
487
488        unsafe { crate::time::implem::update_counter(before_ticks + slept_ticks) };
489        sleep_uart_resume();
490
491        run_exit_hooks();
492
493        // Unlike deep sleep, light sleep does not reset the chip, so `wakeup_cause` cannot rely on
494        // the reset reason to tell whether a wakeup occurred. A rejected request is not a wakeup,
495        // and it must not name a wakeup source.
496        // https://github.com/espressif/esp-idf/blob/a45d713b03fd96d8805d1cc116f02a4415b360c7/components/esp_hw_support/sleep_modes.c#L2158
497        if !config.is_deep_sleep() && !rejected {
498            super::LIGHT_SLEEP_WAKEUP.store(true, portable_atomic::Ordering::Relaxed);
499        }
500
501        // Last, because this call reads the wakeup cause, and after a light sleep the cause is
502        // available only after the line above.
503        gpio::wakeup::record_wakeup();
504
505        // After the cause is recorded. A limit must not keep the comparator, or the next sleep
506        // inherits a deadline that no hook requested.
507        #[cfg(sleep_has_wakeup_source_timer)]
508        if let Some(clamp) = entry.clamp {
509            timer::restore_after_limit(clamp);
510        }
511
512        false
513    }
514}
515
516/// Hardware-stalls the other running cores for the length of a light sleep, or thaws them.
517///
518/// A stall is enough where the retention DMA saves the CPU domain, because the domain holds every
519/// core and the hardware brings the stalled core back with this one. esp-idf stalls the other core
520/// the same way and adds nothing more (`sleep_modes.c`, where the SMP retention work is gated on
521/// `SOC_PM_CPU_RETENTION_BY_SW`).
522///
523/// FIXME: a stall freezes the other core wherever it stands, which can be inside an interrupt
524/// handler that holds a cross-core lock, such as the clock tree, the peripheral reference counts,
525/// or the UART locks that this path takes. This core then spins for that lock for ever, because the
526/// frozen core cannot release it.
527///
528/// A chip that retains the CPU in software needs more than a stall, because each core saves
529/// itself, and a frozen core saves nothing. The rendezvous of that path keeps the other core
530/// running, so this function stalls nothing while that core waits in the rendezvous.
531///
532/// Returns the cores that it stalled, as a bit for each [`Cpu`], for [`unpark_cores`]. A core that
533/// the program stalled before the sleep stays stalled after it.
534#[cfg(all(multi_core, sleep_driver_supported))]
535#[crate::ram]
536fn park_other_cores() -> u8 {
537    // A core that saves itself in the rendezvous must keep running, because a stalled core saves
538    // nothing.
539    #[cfg(all(cpu_retention = "software", multi_core, feature = "rt"))]
540    if cpu_retention::rendezvous::helper_saved() {
541        return 0;
542    }
543
544    let mut parked = 0;
545    for cpu in crate::system::Cpu::other() {
546        if crate::soc::cpu_control::is_running(cpu) {
547            // SAFETY: [`unpark_cores`] runs before this function returns to its caller.
548            unsafe { crate::soc::cpu_control::internal_park_core(cpu, true) };
549            parked |= 1 << cpu as u8;
550        }
551    }
552    parked
553}
554
555/// Thaws the cores that [`park_other_cores`] stalled.
556#[cfg(all(multi_core, sleep_driver_supported))]
557#[crate::ram]
558fn unpark_cores(parked: u8) {
559    for cpu in crate::system::Cpu::other() {
560        if parked & (1 << cpu as u8) != 0 {
561            // SAFETY: this core stalled that core for the sleep, and the sleep is over.
562            unsafe { crate::soc::cpu_control::internal_park_core(cpu, false) };
563        }
564    }
565}
566
567/// Waits for the hardware to report the result of the sleep request, and returns whether the
568/// hardware rejected the request.
569///
570/// A deep sleep powers the CPU down inside this loop, and a light sleep stops the CPU here until a
571/// wakeup source ends the sleep. A rejected request does neither, so the reject interrupt is the
572/// only report of that case. ESP-IDF waits in the same place, in `rtc_sleep_start` and in
573/// `pmu_sleep_start`.
574#[cfg(sleep_driver_supported)]
575#[inline(always)]
576pub(crate) fn wait_for_sleep_result() -> bool {
577    loop {
578        cfg_select! {
579            soc_has_pmu => {
580                let int_raw = crate::peripherals::PMU::regs().int_raw().read();
581                let rejected = int_raw.soc_sleep_reject().bit_is_set();
582                let wakeup = int_raw.soc_wakeup().bit_is_set();
583            }
584            _ => {
585                let int_raw = LPWR::regs().int_raw().read();
586                let rejected = int_raw.slp_reject().bit_is_set();
587                let wakeup = int_raw.slp_wakeup().bit_is_set();
588            }
589        }
590
591        if wakeup || rejected {
592            return rejected;
593        }
594    }
595}
596
597#[cfg(sleep_driver_supported)]
598fn sleep_uart_prepare() {
599    use crate::uart::Instance;
600    for_each_uart! {
601        ($id:literal, $inst:ident, $peri:ident, $rxd:ident, $txd:ident, $cts:ident, $rts:ident, wakeup_source = $_:literal) => {
602            unsafe {
603                crate::peripherals::$inst::steal().info().suspend_for_sleep();
604            }
605        };
606    }
607}
608
609#[cfg(sleep_driver_supported)]
610fn sleep_uart_resume() {
611    use crate::uart::Instance;
612    for_each_uart! {
613        ($id:literal, $inst:ident, $peri:ident, $rxd:ident, $txd:ident, $cts:ident, $rts:ident, wakeup_source = $_:literal) => {
614            unsafe {
615                crate::peripherals::$inst::steal().info().resume_from_sleep();
616            }
617        };
618    }
619}