Skip to main content

esp_hal/rtc_cntl/sleep/
wakeup.rs

1//! The hardware wakeup-enable mask, and the hooks that run around a sleep.
2//!
3//! The driver that owns the hardware enables a wakeup source. The caller of a sleep function does
4//! not. The hardware wakeup-enable mask is the record of the request. It is a register in the
5//! always-on domain, and it keeps its value while the chip is awake, through a light sleep, and
6//! through a deep-sleep wake. Sleep entry reads the mask back, and calculates everything else from
7//! it.
8//!
9//! A driver can also register hooks in the call that sets its mask bit. Use them to do work at
10//! sleep entry, or to restore state after a light sleep.
11
12use enumset::EnumSet;
13use esp_sync::NonReentrantMutex;
14
15use crate::{
16    rtc_cntl::{WakeupSource, sleep::RtcSleepConfig},
17    soc::clocks::ClockSource,
18    time::Duration,
19};
20
21/// Which sleep the chip is entering.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub(crate) enum SleepKind {
24    /// The digital domain keeps its state, and the sleep returns to the caller.
25    Light,
26    /// The chip resets when it wakes.
27    Deep,
28}
29
30/// A power domain that a wakeup source needs powered while the chip sleeps.
31///
32/// The names are the same for all chips. A request for a domain that the target chip does not
33/// have, or that it cannot power down, does nothing.
34///
35/// A clock is not a domain. Ask for one with [`WrappedSleepConfig::keep_clock_running`].
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37// The names are the same for all chips, and each chip uses a different subset.
38#[allow(dead_code, reason = "the names are the same for all chips")]
39pub(crate) enum SleepResource {
40    /// The low-power peripherals, including the RTC IO pads.
41    LpPeripherals,
42    /// The low-power memory, which holds the program and the data of a low-power core.
43    LpMemory,
44    /// The high-performance peripherals, including the digital GPIO pads.
45    HpPeripherals,
46}
47
48/// The sleep configuration, as the entry hook of a wakeup source can see it.
49///
50/// A hook can only relax the sleep. It can keep a power domain powered, keep a clock running,
51/// refuse a light sleep, or shorten the sleep. The caller of the sleep function selects how much
52/// of the chip to power down, and it arms the wake timer. A wakeup source only adds the resources
53/// that it needs, refuses the sleep, or sets a shorter limit. It cannot request a power-down, stop
54/// a clock, cancel a refusal, or lengthen a limit.
55///
56/// Each request is idempotent. The order of the hooks cannot change the result. The source with
57/// the strongest request wins. One refusal is enough. The shortest limit wins.
58///
59/// Keep this property. A method that requests a power-down, stops a clock, cancels a refusal, or
60/// lengthens a limit removes it.
61#[instability::unstable]
62pub struct WrappedSleepConfig<'a> {
63    config: &'a mut RtcSleepConfig,
64    clocks: EnumSet<ClockSource>,
65    refused: bool,
66    limit: Option<Duration>,
67}
68
69impl<'a> WrappedSleepConfig<'a> {
70    pub(crate) fn new(config: &'a mut RtcSleepConfig) -> Self {
71        Self {
72            config,
73            clocks: EnumSet::empty(),
74            refused: false,
75            limit: None,
76        }
77    }
78
79    /// Returns whether the chip is entering deep sleep, which resets it when it wakes.
80    #[instability::unstable]
81    pub fn is_deep_sleep(&self) -> bool {
82        self.config.is_deep_sleep()
83    }
84
85    /// Keeps `resource` powered during the sleep.
86    pub(crate) fn keep_alive(&mut self, resource: SleepResource) {
87        // One function for all chips. It keeps the code of one resource together for all chips, so
88        // that a reader can compare the chips, and a new resource needs one change only.
89        let _config = &mut self.config;
90        match resource {
91            SleepResource::LpPeripherals => {
92                cfg_select! {
93                    // esp32h2 powers its low-power peripherals from the top domain, so it has no
94                    // separate flag to clear.
95                    esp32h2 => {}
96                    soc_has_pmu => _config.pd_flags.set_pd_lp_periph(false),
97                    _ => _config.set_rtc_peri_pd_en(false),
98                }
99            }
100            SleepResource::LpMemory => {
101                cfg_select! {
102                    // The PMU chips keep the contents of the low-power memory through a sleep in
103                    // retention mode, so there is no power-down to prevent.
104                    any(esp32, esp32s2, esp32s3) => _config.set_rtc_slowmem_pd_en(false),
105                    _ => {}
106                }
107            }
108            SleepResource::HpPeripherals => {
109                cfg_select! {
110                    // esp32, esp32s2 and esp32h2 cannot power down the high-performance peripherals
111                    // separately, so there is nothing to prevent.
112                    any(esp32, esp32s2, esp32h2) => {}
113                    soc_has_pmu => _config.pd_flags.set_pd_hp_periph(false),
114                    _ => _config.set_dig_peri_pd_en(false),
115                }
116            }
117        }
118    }
119
120    /// Keeps `source` running during the sleep.
121    ///
122    /// Ask a clock tree node which source it runs on, and name the answer. A source that the chip
123    /// keeps running anyway, or that it cannot power down, needs nothing.
124    #[instability::unstable]
125    pub fn keep_clock_running(&mut self, source: ClockSource) {
126        self.clocks.insert(source);
127    }
128
129    /// Refuses a light sleep.
130    ///
131    /// Call this when software state makes the sleep unsafe and the hardware cannot see that
132    /// state. One call is enough. A later hook cannot cancel it.
133    ///
134    /// A deep sleep ignores the call. The wake resets the chip, so there is no caller to report
135    /// the refusal to. Read [`Self::is_deep_sleep`] when other work in the hook depends on the
136    /// kind of sleep.
137    ///
138    /// The hardware also rejects a sleep when an enabled source is already asserted at sleep
139    /// entry. This call is the software refusal.
140    #[instability::unstable]
141    pub fn reject_sleep(&mut self) {
142        self.refused = true;
143    }
144
145    /// Limits this sleep to `duration`.
146    ///
147    /// The duration starts when the driver programs the wake timer, after every hook has run. The
148    /// shortest request wins. A later hook cannot make it longer.
149    ///
150    /// The driver clamps the wake timer to the limit. When the timer wakeup source is not enabled,
151    /// the limit enables it for this sleep. Nothing else wakes the chip at the limit. After a
152    /// light sleep the driver restores the timer. A deadline the caller armed stays armed. A timer
153    /// that only the limit enabled is disabled again.
154    ///
155    /// The limit applies to deep sleep as well. The wake resets the chip. Read
156    /// [`Self::is_deep_sleep`] when the hook should bound only one kind of sleep.
157    ///
158    /// A duration that the sleep transition cannot catch does not start the sleep. A light sleep
159    /// then returns as if it had ended. A deep sleep without rejection panics, because it cannot
160    /// return and the chip does not wake. This is not a refusal. Use [`Self::reject_sleep`] to
161    /// refuse a light sleep. A duration of zero is too short to sleep.
162    #[instability::unstable]
163    pub fn limit_sleep(&mut self, duration: Duration) {
164        let already_shorter = match self.limit {
165            Some(current) => current.as_micros() <= duration.as_micros(),
166            None => false,
167        };
168        if !already_shorter {
169            self.limit = Some(duration);
170        }
171    }
172
173    /// Prevents the power-down of every clock source a hook asked for.
174    ///
175    /// The set is what makes the requests of the hooks independent of their order.
176    fn apply_clock_requests(&mut self) {
177        // One function for all chips, like `keep_alive`.
178        let _config = &mut self.config;
179        for source in self.clocks {
180            match source {
181                ClockSource::XtalClk => {
182                    cfg_select! {
183                        soc_has_pmu => _config.pd_flags.set_pd_xtal(false),
184                        // This flag has the opposite sense. It forces the crystal on.
185                        _ => _config.set_xtal_fpu(true),
186                    }
187                }
188                ClockSource::RcFastClk => {
189                    cfg_select! {
190                        soc_has_pmu => _config.pd_flags.set_pd_rc_fast(false),
191                        _ => _config.set_int_8m_pd_en(false),
192                    }
193                }
194                #[cfg(use_xtal32k)]
195                ClockSource::Xtal32kClk => {
196                    cfg_select! {
197                        soc_has_pmu => _config.pd_flags.set_pd_xtal32k(false),
198                        _ => {}
199                    }
200                }
201                #[cfg(soc_has_clock_node_rc32k_clk)]
202                ClockSource::Rc32kClk => {
203                    cfg_select! {
204                        // esp32h2 has no separate flag for the 32 kHz RC oscillator.
205                        esp32h2 => {}
206                        _ => _config.pd_flags.set_pd_rc32k(false),
207                    }
208                }
209                // The slow RC oscillator runs in the always-on domain, and the external 32 kHz
210                // oscillator arrives on a pad, so neither has a power-down to prevent.
211                #[cfg(soc_has_clock_node_osc_slow_clk)]
212                ClockSource::OscSlowClk => {}
213                ClockSource::RcSlowClk => {}
214            }
215        }
216    }
217}
218
219/// Runs at sleep entry, before the sleep configuration reaches hardware.
220///
221/// The configuration already holds the kind of the sleep, so a hook that needs it asks
222/// [`WrappedSleepConfig::is_deep_sleep`]. A hook keeps a clock running with
223/// [`WrappedSleepConfig::keep_clock_running`]. A hook refuses a light sleep with
224/// [`WrappedSleepConfig::reject_sleep`].
225///
226/// The hook can run with interrupts disabled, for example from automatic light sleep. Do not
227/// allocate, take a blocking lock, or log in it.
228#[instability::unstable]
229pub type SleepEntryHook = fn(&mut WrappedSleepConfig<'_>);
230
231/// Runs after a light sleep.
232///
233/// It also runs when a hook refuses a light sleep. An entry hook may already have changed a pad,
234/// and the exit hook puts that pad back. A deep sleep resets the chip, which runs the
235/// initialization again.
236#[instability::unstable]
237pub type SleepExitHook = fn();
238
239for_each_wakeup_source! {
240    (all $( ($variant:ident, $bit:literal) ),*) => {
241        /// One slot for each bit of the mask, so that a source can use its bit as the index.
242        const HOOK_SLOTS: usize = {
243            let mut highest = 0;
244            $(
245                if $bit > highest {
246                    highest = $bit;
247                }
248            )*
249            highest + 1
250        };
251    };
252}
253
254struct Hooks {
255    entry: [Option<SleepEntryHook>; HOOK_SLOTS],
256    exit: [Option<SleepExitHook>; HOOK_SLOTS],
257}
258
259/// The same lock protects the hooks and the mask bits, so the two always agree.
260static HOOKS: NonReentrantMutex<Hooks> = NonReentrantMutex::new(Hooks {
261    entry: [None; HOOK_SLOTS],
262    exit: [None; HOOK_SLOTS],
263});
264
265impl WakeupSource {
266    /// Enables this source, so that it can end a sleep.
267    ///
268    /// The source stays enabled until [`Self::disable`] is called. It also stays enabled through a
269    /// sleep and through a deep-sleep wake. While the chip is awake, an enabled source does
270    /// nothing.
271    // On a chip where all the implemented sources need hooks, nothing calls this function.
272    #[allow(dead_code, reason = "not every chip has such a source yet")]
273    pub(crate) fn enable(self) {
274        self.enable_with_hooks(None, None)
275    }
276
277    /// Enables this source and registers the hooks that run around a sleep.
278    ///
279    /// A source has one slot for each kind of hook, so a second call replaces the hooks of the
280    /// first call. One driver owns each source, so only that driver can replace its own hooks.
281    /// A call for a source that is already enabled does this.
282    ///
283    /// Both hooks run with the flash accessible, so they need no [`ram`][crate::ram] attribute. The
284    /// entry hook runs before esp-hal writes the sleep configuration to hardware, and the exit hook
285    /// runs after the wake sequence restores it. Both hooks are part of sleep entry, so keep them
286    /// short.
287    ///
288    /// Only the driver that owns the source calls this. esp-hal owns the sources of its own
289    /// drivers, for example the timer source. A call for such a source replaces the hooks of that
290    /// driver, and the driver then does not work through a sleep.
291    #[instability::unstable]
292    pub fn enable_with_hooks(self, entry: Option<SleepEntryHook>, exit: Option<SleepExitHook>) {
293        HOOKS.with(|hooks| {
294            hooks.entry[self as usize] = entry;
295            hooks.exit[self as usize] = exit;
296
297            set_mask_bit(self, true);
298        })
299    }
300
301    /// Disables this source, and removes its hooks.
302    ///
303    /// Only the driver that owns the source calls this, as for [`Self::enable_with_hooks`].
304    #[instability::unstable]
305    pub fn disable(self) {
306        HOOKS.with(|hooks| {
307            hooks.entry[self as usize] = None;
308            hooks.exit[self as usize] = None;
309
310            set_mask_bit(self, false);
311        })
312    }
313}
314
315/// Returns the enabled wakeup sources, as the hardware mask records them.
316pub(crate) fn enabled_sources() -> enumset::EnumSet<WakeupSource> {
317    enumset::EnumSet::from_u32_truncated(mask())
318}
319
320/// Returns the sources that reject the next sleep.
321///
322/// A source rejects a sleep if the source is already asserted when the sleep starts. Without the
323/// rejection, the chip sleeps through the event that the caller wants to wake on. The reject
324/// sources are the enabled wake sources that the chip can also reject on. A chip cannot reject on
325/// every source that it can wake from. esp32 rejects on GPIO and SDIO only, and esp32c2, esp32c3,
326/// esp32s2 and esp32s3 do not reject on a UART.
327pub(crate) fn reject_mask() -> u32 {
328    mask() & property!("sleep.rejectable_mask")
329}
330
331/// What the entry hooks asked for, other than power domains and clocks.
332///
333/// The clock requests reach the configuration before this value is returned, and so does the
334/// wake timer. The refusal does not, because only the caller knows what to do with it.
335pub(crate) struct SleepEntry {
336    /// A hook called [`WrappedSleepConfig::reject_sleep`].
337    pub(crate) refused: bool,
338
339    /// How [`WrappedSleepConfig::limit_sleep`] changed the wake timer.
340    ///
341    /// `None` when no hook set a limit. The caller restores the timer on every path that does not
342    /// sleep, and after a light sleep.
343    #[cfg(sleep_has_wakeup_source_timer)]
344    pub(crate) clamp: Option<super::timer::LimitClamp>,
345}
346
347/// Runs the sleep-entry hook of every enabled source.
348///
349/// One [`WrappedSleepConfig::reject_sleep`] is enough. The caller ignores the refusal for a deep
350/// sleep. The wake resets the chip, so there is no caller to report the refusal to. The shortest
351/// [`WrappedSleepConfig::limit_sleep`] wins.
352///
353/// The mask as read at sleep entry selects the hooks. A hook can enable another source. The GPIO
354/// hook does this, because it selects between the `ext0`, `ext1` and per-pin paths. The caller
355/// therefore reads the mask again after the hooks. It does not run the hooks again until the mask
356/// stops to change.
357///
358/// The caller writes the kind of the sleep to the configuration before this call, so that the hooks
359/// can read it.
360pub(crate) fn run_entry_hooks(config: &mut RtcSleepConfig) -> SleepEntry {
361    let mut wrapped = WrappedSleepConfig::new(config);
362
363    for source in enabled_sources() {
364        let hook = HOOKS.with(|hooks| hooks.entry[source as usize]);
365        if let Some(hook) = hook {
366            hook(&mut wrapped);
367        }
368    }
369
370    // The clamp writes the comparator, so it runs after every hook has asked for its limit. A
371    // source that the clamp enables has not run its entry hook. It runs here, where the requests
372    // of a hook still reach the configuration.
373    #[cfg(sleep_has_wakeup_source_timer)]
374    let clamp = wrapped.limit.map(|limit| {
375        let clamp = super::timer::clamp_to_limit(limit);
376        clamp.apply_entry_hook(&mut wrapped);
377        clamp
378    });
379
380    wrapped.apply_clock_requests();
381
382    SleepEntry {
383        refused: wrapped.refused,
384        #[cfg(sleep_has_wakeup_source_timer)]
385        clamp,
386    }
387}
388
389/// Runs the post-wake hook of every enabled source.
390///
391/// A light sleep calls this after the wake. A refused light sleep calls it too, so an entry hook
392/// can undo a pad change. A sleep that a limit ends before it starts calls it for the same reason.
393/// A deep sleep that resets the chip does not call this.
394pub(crate) fn run_exit_hooks() {
395    for source in enabled_sources() {
396        let hook = HOOKS.with(|hooks| hooks.exit[source as usize]);
397        if let Some(hook) = hook {
398            hook();
399        }
400    }
401}
402
403/// Reads the wakeup-enable mask back from hardware.
404pub(crate) fn mask() -> u32 {
405    let reg = cfg_select! {
406        soc_has_pmu => crate::peripherals::PMU::regs().slp_wakeup_cntl2(),
407        _ => crate::peripherals::LPWR::regs().wakeup_state(),
408    };
409    reg.read().wakeup_ena().bits() as _
410}
411
412/// Writes the wakeup-enable mask.
413///
414/// The function writes the field and not the register, because on esp32 the mask shares its
415/// register with `gpio_wakeup_filter` and with the read-only `wakeup_cause`.
416pub(crate) fn set_mask(mask: u32) {
417    let reg = cfg_select! {
418        soc_has_pmu => crate::peripherals::PMU::regs().slp_wakeup_cntl2(),
419        _ => crate::peripherals::LPWR::regs().wakeup_state(),
420    };
421    reg.modify(|_, w| unsafe { w.wakeup_ena().bits(mask as _) });
422}
423
424fn set_mask_bit(source: WakeupSource, enable: bool) {
425    let bit = 1 << source as u32;
426    let current = mask();
427
428    set_mask(if enable {
429        current | bit
430    } else {
431        current & !bit
432    });
433}