#[cfg_attr(sleep_ext1_version = "1", path = "ext1_v1.rs")]
#[cfg_attr(sleep_ext1_version = "2", path = "ext1_v2.rs")]
#[cfg_attr(sleep_ext1_version = "3", path = "ext1_v3.rs")]
#[cfg_attr(not(sleep_ext1_version_is_set), path = "per_pin.rs")]
mod path;
use portable_atomic::Ordering;
#[cfg(sleep_ext1_version_is_set)]
use crate::gpio::lp_io::LpFunction;
use crate::{
gpio::{
AnyPin,
Event,
GpioBank,
Level,
Pin,
WakeConfigError,
low_level::PadMask,
lp_io::low_level,
},
peripherals::GPIO,
rtc_cntl::{
WakeupSource,
sleep::{SleepResource, WrappedSleepConfig},
},
};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, procmacros::BuilderLite)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
#[instability::unstable]
pub struct WakeupConfig {
low_power_path: bool,
}
static LOW_POWER_PADS: PadMask = PadMask::new();
static PREPARED_PADS: PadMask = PadMask::new();
static WOKEN_PADS: PadMask = PadMask::new();
for_each_gpio! {
(all $( ($n:literal, $gpio:ident $_ins:tt $_outs:tt $_attrs:tt) ),*) => {
const PAD_COUNT: usize = {
let mut highest = 0;
$( if $n > highest { highest = $n; } )*
highest + 1
};
};
}
const NO_LP_NUMBER: u8 = u8::MAX;
for_each_lp_function! {
(LP_GPIOn $( (($_sig:ident, LP_GPIOn, $lp:literal), $gpio:ident, $_af:ident, $_in:tt $_out:tt) ),*) => {
const LP_NUMBERS: [u8; PAD_COUNT] = {
let mut numbers = [NO_LP_NUMBER; PAD_COUNT];
$( numbers[crate::peripherals::$gpio::NUMBER as usize] = $lp; )*
numbers
};
};
}
const MAX_ARMED: usize = {
let mut count = 0;
let mut pad = 0;
while pad < PAD_COUNT {
if LP_NUMBERS[pad] != NO_LP_NUMBER {
count += 1;
}
pad += 1;
}
count
};
#[derive(Debug, Clone, Copy)]
struct Armed {
gpio: u8,
lp: u8,
level: Level,
}
impl Armed {
const NONE: Self = Self {
gpio: 0,
lp: 0,
level: Level::Low,
};
}
pub(crate) fn apply_config(pin: &AnyPin<'_>, config: &WakeupConfig) -> Result<(), WakeConfigError> {
if config.low_power_path {
if lp_number(pin.number()).is_none() {
return Err(WakeConfigError::NoLowPowerPath);
}
LOW_POWER_PADS.set(pin.number(), true);
enable();
} else {
LOW_POWER_PADS.set(pin.number(), false);
}
Ok(())
}
pub(crate) fn caused_wakeup(pin: &AnyPin<'_>) -> bool {
WOKEN_PADS.contains(pin.number())
}
#[crate::ram]
pub(crate) fn record_wakeup() {
let cause = crate::rtc_cntl::wakeup_cause();
for bank in GpioBank::ALL {
let digital = if cause.contains(WakeupSource::Gpio) {
bank.read_interrupt_status() & bank.listening().load(Ordering::Relaxed)
} else {
0
};
WOKEN_PADS.word(bank).store(digital, Ordering::Relaxed);
}
for (gpio, &lp) in LP_NUMBERS.iter().enumerate() {
if lp != NO_LP_NUMBER && path::caused_wakeup(gpio as u8, cause) {
WOKEN_PADS.set(gpio as u8, true);
}
}
}
pub(crate) fn enable() {
WakeupSource::Gpio.enable_with_hooks(Some(entry_hook), Some(exit_hook));
}
fn disable() {
WakeupSource::Gpio.disable();
path::disable();
}
fn lp_number(gpio: u8) -> Option<u8> {
match LP_NUMBERS[gpio as usize] {
NO_LP_NUMBER => None,
lp => Some(lp),
}
}
fn low_power_pads() -> impl Iterator<Item = (u8, u8)> {
LP_NUMBERS
.iter()
.copied()
.enumerate()
.filter(|&(_, lp)| lp != NO_LP_NUMBER)
.map(|(gpio, lp)| (gpio as u8, lp))
}
#[cfg(not(sleep_ext1_version = "3"))]
fn low_power_numbers() -> impl Iterator<Item = u8> {
low_power_pads().map(|(_, lp)| lp)
}
pub(crate) fn wake_enabled() -> bool {
let sources = crate::rtc_cntl::sleep::enabled_sources();
#[allow(unused_mut)]
let mut enabled = sources.contains(WakeupSource::Gpio);
#[cfg(sleep_has_wakeup_source_ext0)]
{
enabled |= sources.contains(WakeupSource::Ext0);
}
#[cfg(sleep_has_wakeup_source_ext1)]
{
enabled |= sources.contains(WakeupSource::Ext1);
}
enabled
}
pub(crate) fn wake_io_reset() {
path::wake_io_reset();
}
#[cfg(not(sleep_ext1_version_is_set))]
fn hold_taken(gpio: u8) {
PREPARED_PADS.set(gpio, true);
}
#[crate::ram]
fn entry_hook(config: &mut WrappedSleepConfig<'_>) {
let mut buffer = [Armed::NONE; MAX_ARMED];
let (armed, mut digital) = collect(&mut buffer);
digital &= !config.is_deep_sleep();
if armed.is_empty() && !digital {
disable();
return;
}
if digital {
config.keep_alive(SleepResource::HpPeripherals);
}
path::allocate(armed, config);
}
#[crate::ram]
fn exit_hook() {
for bank in GpioBank::ALL {
let mut prepared = PREPARED_PADS.word(bank).swap(0, Ordering::Relaxed);
while prepared != 0 {
let pin = prepared.trailing_zeros();
prepared &= !(1 << pin);
let Some(lp) = lp_number(bank.offset() + pin as u8) else {
continue;
};
low_level::pad_hold(lp, false);
#[cfg(sleep_ext1_version_is_set)]
low_level::set_config(lp, true, false, LpFunction::LP_GPIO);
}
}
}
fn collect(buffer: &mut [Armed; MAX_ARMED]) -> (&[Armed], bool) {
let mut count = 0;
let mut digital = false;
for bank in GpioBank::ALL {
let listening = bank.listening().load(Ordering::Relaxed);
digital |= listening != 0;
let mut participants = listening & LOW_POWER_PADS.word(bank).load(Ordering::Relaxed);
while participants != 0 {
let pin = participants.trailing_zeros();
participants &= !(1 << pin);
let gpio = bank.offset() + pin as u8;
let Some(lp) = lp_number(gpio) else { continue };
if let Some(level) = armed_level(gpio) {
buffer[count] = Armed { gpio, lp, level };
count += 1;
}
}
}
(&buffer[..count], digital)
}
fn armed_level(gpio: u8) -> Option<Level> {
let trigger = GPIO::regs().pin(gpio as usize).read().int_type().bits();
Some(
if trigger == Event::HighLevel as u8 || trigger == Event::RisingEdge as u8 {
Level::High
} else if trigger == Event::LowLevel as u8 || trigger == Event::FallingEdge as u8 {
Level::Low
} else if trigger == Event::AnyEdge as u8 {
!Level::from(unsafe { AnyPin::steal(gpio) }.is_input_high())
} else {
return None;
},
)
}
#[cfg(sleep_ext1_version_is_set)]
fn prepare_pad(pin: &Armed, deep: bool) {
#[cfg(not(esp32h2))]
{
let digital = crate::gpio::io_mux_reg(pin.gpio).read();
low_level::pullup_enable(pin.lp, digital.fun_wpu().bit());
low_level::pulldown_enable(pin.lp, digital.fun_wpd().bit());
}
low_level::set_config(pin.lp, true, !cfg!(esp32h2), LpFunction::LP_GPIO);
low_level::pad_hold(pin.lp, deep);
PREPARED_PADS.set(pin.gpio, true);
}
#[cfg(sleep_deep_sleep_needs_gpio_isolation)]
pub(crate) fn isolate_pads_for_deep_sleep() {
use crate::{
gpio::{AlternateFunction, OutputSignal, io_mux_reg},
peripherals::LPWR,
};
let dig_iso = LPWR::regs().dig_iso().read();
let hold_enabled = !dig_iso.dg_pad_force_unhold().bit() && dig_iso.dg_pad_autohold_en().bit();
if !hold_enabled {
return;
}
for gpio in 0..PAD_COUNT as u8 {
if lp_number(gpio).is_some() {
continue;
}
if crate::gpio::lp_io::low_level::is_digital_pad_held(gpio) {
continue;
}
GPIO::regs()
.func_out_sel_cfg(gpio as usize)
.modify(|_, w| unsafe { w.bits(OutputSignal::GPIO as u32) });
io_mux_reg(gpio).modify(|_, w| unsafe {
w.fun_wpu().clear_bit();
w.fun_wpd().clear_bit();
w.fun_ie().clear_bit();
w.mcu_sel().bits(AlternateFunction::GPIO as u8)
});
}
}