1
//! The sleep alarm.
2
//!
3
//! The alarm is the first comparator of the LP timer. ESP-IDF also reserves that comparator for the
4
//! sleep wakeup. An armed alarm is a standing request, like every other wakeup source. It stays
5
//! armed through the wake that it causes, and it ends only when the caller clears it.
6

            
7
use crate::{
8
    peripherals::RTC_TIMER,
9
    rtc_cntl::{WakeupSource, sleep::WrappedSleepConfig},
10
    time::{Duration, Instant},
11
};
12

            
13
/// The time that ESP-IDF reserves between the arming of the alarm and the start of the sleep, in
14
/// slow-clock ticks.
15
///
16
/// This is `SLEEP_TIMER_ALARM_TO_SLEEP_TICKS`, and its comment gives 80 µs for it. The sleep
17
/// transition cannot catch a nearer deadline, and the 48-bit counter needs decades to reach the
18
/// deadline again.
19
const ALARM_TO_SLEEP_TICKS: u64 = 16;
20

            
21
/// Arms the alarm for `deadline`, and enables the timer wakeup source.
22
pub(crate) fn set_deadline(deadline: Instant) {
23
    let now = Instant::now();
24

            
25
    // A deadline in the past must stay in the past. That is how the caller learns about it, as a
26
    // rejected light sleep or as a panic in `sleep_deep`.
27
    let ticks = if deadline >= now {
28
        let ahead = crate::clock::us_to_rtc_ticks((deadline - now).as_micros());
29
        crate::rtc_cntl::time_since_boot_raw().saturating_add(ahead)
30
    } else {
31
        let behind = crate::clock::us_to_rtc_ticks((now - deadline).as_micros());
32
        crate::rtc_cntl::time_since_boot_raw().saturating_sub(behind)
33
    };
34

            
35
    arm(ticks);
36

            
37
    WakeupSource::Timer.enable_with_hooks(Some(entry_hook), None);
38
}
39

            
40
/// Disarms the alarm, and disables the timer wakeup source.
41
pub(crate) fn clear_deadline() {
42
    WakeupSource::Timer.disable();
43
    disarm();
44
}
45

            
46
/// Returns whether the armed deadline is too near for the sleep transition to catch it.
47
///
48
/// A deep sleep that misses the alarm does not wake. The transition is not rejected, and the
49
/// counter needs decades to reach the deadline again. A light sleep on most chips rejects, because
50
/// the timer is a reject source. esp32 cannot reject on the timer, so a missed alarm is not safe
51
/// there either.
52
///
53
/// The comparator holds the deadline, so esp-hal keeps no copy of it. The target of the comparator
54
/// is readable on every chip, also where the alarm enable beside it is not readable. Only a caller
55
/// that finds the timer source enabled calls this function, so the target always comes from this
56
/// run.
57
pub(crate) fn deadline_missed() -> bool {
58
    target_ticks() < crate::rtc_cntl::time_since_boot_raw() + ALARM_TO_SLEEP_TICKS
59
}
60

            
61
/// How [`clamp_to_limit`] changed the wake timer.
62
///
63
/// A limit applies to one sleep. It must not replace a deadline the caller armed. Restore it with
64
/// [`restore_after_limit`] on every path that returns to the caller.
65
#[must_use = "restore the wake timer after the sleep"]
66
pub(crate) enum LimitClamp {
67
    /// The armed deadline is at or before the limit, and the transition can catch it.
68
    Unchanged,
69
    /// The limit replaced the armed deadline. `previous` is that deadline, in RTC ticks.
70
    Shortened {
71
        /// The comparator value from before the clamp.
72
        previous: u64,
73
    },
74
    /// The timer source was off, so the limit enabled it.
75
    Enabled,
76
    /// The sleep does not wake at or before the limit.
77
    ///
78
    /// The limit is already due, the transition cannot catch the limit, or the armed deadline is
79
    /// sooner and the transition cannot catch that deadline. The comparator is unchanged. The
80
    /// sleep must not start.
81
    TooShort,
82
}
83

            
84
impl LimitClamp {
85
    /// Runs the timer entry hook when this limit enabled the source.
86
    ///
87
    /// The hook keeps the low-power peripherals powered on chips whose comparator is not in the
88
    /// always-on domain. The hook of a source that was already enabled has run. Hooks do not run
89
    /// again, so a source that this limit enabled would otherwise sleep without that request.
90
    pub(crate) fn apply_entry_hook(&self, config: &mut WrappedSleepConfig<'_>) {
91
        if matches!(self, Self::Enabled) {
92
            entry_hook(config);
93
        }
94
    }
95
}
96

            
97
/// Shortens the wake timer so the sleep lasts at most `limit`.
98
///
99
/// The duration is measured from this call, on the RTC counter the comparator uses. An armed
100
/// deadline that is sooner than the limit stays, when the transition can catch that deadline.
101
///
102
/// When the timer wakeup source is off, the limit enables it. Nothing else wakes the chip at the
103
/// limit.
104
///
105
/// Returns [`LimitClamp::TooShort`] when the sleep does not wake at or before the limit. The
106
/// timer is unchanged. The caller must not start the sleep. esp32 cannot reject on the timer, so a
107
/// missed alarm does not wake the chip.
108
pub(crate) fn clamp_to_limit(limit: Duration) -> LimitClamp {
109
    let now = crate::rtc_cntl::time_since_boot_raw();
110
    let ahead = crate::clock::us_to_rtc_ticks(limit.as_micros());
111
    let limit_ticks = now.saturating_add(ahead);
112

            
113
    // Same window as `deadline_missed`. Check it before any write. A later armed deadline must
114
    // not hide a limit the transition cannot catch, and arming that limit clears the status
115
    // of the caller's deadline.
116
    if limit_ticks < now + ALARM_TO_SLEEP_TICKS {
117
        return LimitClamp::TooShort;
118
    }
119

            
120
    if super::enabled_sources().contains(WakeupSource::Timer) {
121
        let current = target_ticks();
122
        if current <= limit_ticks {
123
            // The armed deadline is sooner. When the transition cannot catch it, the chip does
124
            // not wake at the limit either. esp32 cannot reject on the timer.
125
            if current < now + ALARM_TO_SLEEP_TICKS {
126
                return LimitClamp::TooShort;
127
            }
128
            LimitClamp::Unchanged
129
        } else {
130
            arm(limit_ticks);
131
            LimitClamp::Shortened { previous: current }
132
        }
133
    } else {
134
        arm(limit_ticks);
135
        WakeupSource::Timer.enable_with_hooks(Some(entry_hook), None);
136
        LimitClamp::Enabled
137
    }
138
}
139

            
140
/// Puts the wake timer back to the state from before [`clamp_to_limit`].
141
pub(crate) fn restore_after_limit(clamp: LimitClamp) {
142
    match clamp {
143
        LimitClamp::Unchanged | LimitClamp::TooShort => {}
144
        LimitClamp::Shortened { previous } => arm(previous),
145
        LimitClamp::Enabled => clear_deadline(),
146
    }
147
}
148

            
149
/// Reads the comparator target, in RTC counter ticks.
150
fn target_ticks() -> u64 {
151
    let regs = RTC_TIMER::regs();
152

            
153
    let (low, high) = cfg_select! {
154
        any(esp32c5, esp32c6, esp32c61, esp32h2, esp32p4, esp32s31) => (
155
            regs.tar0_low().read().main_timer_tar_low0().bits(),
156
            regs.tar0_high().read().main_timer_tar_high0().bits(),
157
        ),
158
        _ => (
159
            regs.slp_timer0().read().slp_val_lo().bits(),
160
            regs.slp_timer1().read().slp_val_hi().bits(),
161
        ),
162
    };
163

            
164
    (u64::from(high) << 32) | u64::from(low)
165
}
166

            
167
#[crate::ram]
168
fn entry_hook(config: &mut WrappedSleepConfig<'_>) {
169
    // The PMU chips run the comparator from the always-on domain. ESP-IDF also powers their
170
    // low-power peripherals down while a timer wake is armed.
171
    if !cfg!(soc_has_pmu) {
172
        config.keep_alive(super::SleepResource::LpPeripherals);
173
    }
174
}
175

            
176
/// Writes the comparator, and clears the status that the previous deadline can leave set.
177
///
178
/// Only this function clears the status. Sleep entry must not clear it on its own, because the
179
/// status is the only report of an expired deadline. Arming a new deadline clears it here.
180
fn arm(ticks: u64) {
181
    let low = (ticks & 0xffff_ffff) as u32;
182
    let high = ((ticks >> 32) & 0xffff) as u16;
183
    let regs = RTC_TIMER::regs();
184

            
185
    cfg_select! {
186
        any(esp32c5, esp32c6, esp32c61, esp32h2, esp32p4, esp32s31) => {
187
            regs.int_clr().write(|w| w.soc_wakeup().clear_bit_by_one());
188
            regs.tar0_low()
189
                .write(|w| unsafe { w.main_timer_tar_low0().bits(low) });
190
            regs.tar0_high()
191
                .write(|w| unsafe { w.main_timer_tar_high0().bits(high) });
192
            regs.tar0_high()
193
                .modify(|_, w| w.main_timer_tar_en0().set_bit());
194
        }
195
        _ => {
196
            regs.int_clr().write(|w| w.main_timer().clear_bit_by_one());
197
            regs.slp_timer0()
198
                .write(|w| unsafe { w.slp_val_lo().bits(low) });
199
            regs.slp_timer1().write(|w| unsafe {
200
                w.slp_val_hi().bits(high);
201
                w.main_timer_alarm_en().set_bit()
202
            });
203
        }
204
    }
205
}
206

            
207
fn disarm() {
208
    let regs = RTC_TIMER::regs();
209

            
210
    cfg_select! {
211
        any(esp32c5, esp32c6, esp32c61, esp32h2, esp32p4, esp32s31) => {
212
            regs.tar0_high()
213
                .modify(|_, w| w.main_timer_tar_en0().clear_bit());
214
            regs.int_clr().write(|w| w.soc_wakeup().clear_bit_by_one());
215
        }
216
        _ => {
217
            // The alarm enable is in the same register as the high half of the target. A write of
218
            // the target therefore arms the alarm again.
219
            regs.slp_timer1()
220
                .write(|w| unsafe { w.slp_val_hi().bits(0) });
221
            regs.int_clr().write(|w| w.main_timer().clear_bit_by_one());
222
        }
223
    }
224
}