use esp_sync::NonReentrantMutex;
use crate::rtc_cntl::{WakeupSource, sleep::RtcSleepConfig};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SleepKind {
Light,
Deep,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code, reason = "the names are the same for all chips")]
pub(crate) enum SleepResource {
LpPeripherals,
LpMemory,
HpPeripherals,
Xtal,
RcFast,
Xtal32k,
Rc32k,
}
pub(crate) struct WrappedSleepConfig<'a> {
config: &'a mut RtcSleepConfig,
}
impl<'a> WrappedSleepConfig<'a> {
pub(crate) fn new(config: &'a mut RtcSleepConfig) -> Self {
Self { config }
}
pub(crate) fn is_deep_sleep(&self) -> bool {
self.config.is_deep_sleep()
}
pub(crate) fn keep_alive(&mut self, resource: SleepResource) {
let _config = &mut self.config;
match resource {
SleepResource::LpPeripherals => {
cfg_select! {
esp32h2 => {}
soc_has_pmu => _config.pd_flags.set_pd_lp_periph(false),
_ => _config.set_rtc_peri_pd_en(false),
}
}
SleepResource::LpMemory => {
cfg_select! {
any(esp32, esp32s2, esp32s3) => _config.set_rtc_slowmem_pd_en(false),
_ => {}
}
}
SleepResource::HpPeripherals => {
cfg_select! {
any(esp32, esp32s2, esp32h2) => {}
soc_has_pmu => _config.pd_flags.set_pd_hp_periph(false),
_ => _config.set_dig_peri_pd_en(false),
}
}
SleepResource::Xtal => {
cfg_select! {
soc_has_pmu => _config.pd_flags.set_pd_xtal(false),
_ => _config.set_xtal_fpu(true),
}
}
SleepResource::RcFast => {
cfg_select! {
soc_has_pmu => _config.pd_flags.set_pd_rc_fast(false),
_ => _config.set_int_8m_pd_en(false),
}
}
SleepResource::Xtal32k => {
cfg_select! {
soc_has_pmu => _config.pd_flags.set_pd_xtal32k(false),
_ => {}
}
}
SleepResource::Rc32k => {
cfg_select! {
esp32h2 => {}
soc_has_pmu => _config.pd_flags.set_pd_rc32k(false),
_ => {}
}
}
}
}
}
pub(crate) type SleepEntryHook = fn(&mut WrappedSleepConfig<'_>);
pub(crate) type SleepExitHook = fn();
for_each_wakeup_source! {
(all $( ($variant:ident, $bit:literal) ),*) => {
const HOOK_SLOTS: usize = {
let mut highest = 0;
$(
if $bit > highest {
highest = $bit;
}
)*
highest + 1
};
};
}
struct Hooks {
entry: [Option<SleepEntryHook>; HOOK_SLOTS],
exit: [Option<SleepExitHook>; HOOK_SLOTS],
}
static HOOKS: NonReentrantMutex<Hooks> = NonReentrantMutex::new(Hooks {
entry: [None; HOOK_SLOTS],
exit: [None; HOOK_SLOTS],
});
impl WakeupSource {
#[allow(dead_code, reason = "not every chip has such a source yet")]
pub(crate) fn enable(self) {
self.enable_with_hooks(None, None)
}
pub(crate) fn enable_with_hooks(
self,
entry: Option<SleepEntryHook>,
exit: Option<SleepExitHook>,
) {
HOOKS.with(|hooks| {
hooks.entry[self as usize] = entry;
hooks.exit[self as usize] = exit;
set_mask_bit(self, true);
})
}
pub(crate) fn disable(self) {
HOOKS.with(|hooks| {
hooks.entry[self as usize] = None;
hooks.exit[self as usize] = None;
set_mask_bit(self, false);
})
}
}
pub(crate) fn enabled_sources() -> enumset::EnumSet<WakeupSource> {
enumset::EnumSet::from_u32_truncated(mask())
}
pub(crate) fn reject_mask() -> u32 {
mask() & property!("sleep.rejectable_mask")
}
pub(crate) fn run_entry_hooks(config: &mut RtcSleepConfig) {
let mut wrapped = WrappedSleepConfig::new(config);
for source in enabled_sources() {
let hook = HOOKS.with(|hooks| hooks.entry[source as usize]);
if let Some(hook) = hook {
hook(&mut wrapped);
}
}
}
pub(crate) fn run_exit_hooks() {
for source in enabled_sources() {
let hook = HOOKS.with(|hooks| hooks.exit[source as usize]);
if let Some(hook) = hook {
hook();
}
}
}
pub(crate) fn mask() -> u32 {
let reg = cfg_select! {
soc_has_pmu => crate::peripherals::PMU::regs().slp_wakeup_cntl2(),
_ => crate::peripherals::LPWR::regs().wakeup_state(),
};
reg.read().wakeup_ena().bits() as _
}
pub(crate) fn set_mask(mask: u32) {
let reg = cfg_select! {
soc_has_pmu => crate::peripherals::PMU::regs().slp_wakeup_cntl2(),
_ => crate::peripherals::LPWR::regs().wakeup_state(),
};
reg.modify(|_, w| unsafe { w.wakeup_ena().bits(mask as _) });
}
fn set_mask_bit(source: WakeupSource, enable: bool) {
let bit = 1 << source as u32;
let current = mask();
set_mask(if enable {
current | bit
} else {
current & !bit
});
}