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}