diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd888d8d4d5..076fd6bb54c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,21 +47,21 @@ jobs: # Check all RISC-V targets: - name: check (esp32c3) - run: cd esp-hal-smartled/ && cargo +nightly check --features=esp32c3 + run: cd esp-hal-smartled/ && cargo +nightly check --features=esp32c3 --target=riscv32imc-unknown-none-elf - name: check (esp32c6) - run: cd esp-hal-smartled/ && cargo +nightly check --features=esp32c6 + run: cd esp-hal-smartled/ && cargo +nightly check --features=esp32c6 --target=riscv32imac-unknown-none-elf - name: check (esp32h2) - run: cd esp-hal-smartled/ && cargo +nightly check --features=esp32h2 + run: cd esp-hal-smartled/ && cargo +nightly check --features=esp32h2 --target=riscv32imac-unknown-none-elf # Check all Xtensa targets: - name: check (esp32) - run: cd esp-hal-smartled/ && cargo +esp check --features=esp32,xtal-40mhz + run: cd esp-hal-smartled/ && cargo +esp check --features=esp32,xtal-40mhz --target=xtensa-esp32-none-elf - name: check (esp32s2) - run: cd esp-hal-smartled/ && cargo +esp check --features=esp32s2 + run: cd esp-hal-smartled/ && cargo +esp check --features=esp32s2 --target=xtensa-esp32s2-none-elf - name: check (esp32s3) - run: cd esp-hal-smartled/ && cargo +esp check --features=esp32s3 + run: cd esp-hal-smartled/ && cargo +esp check --features=esp32s3 --target=xtensa-esp32s3-none-elf # Ensure documentation can be built (requires a chip feature!) - name: rustdoc - run: cd esp-hal-smartled/ && cargo doc --features=esp32c3,esp-hal-common/eh1 + run: cd esp-hal-smartled/ && cargo doc --features=esp32c3,esp-hal-common/eh1 --target=riscv32imac-unknown-none-elf esp32-hal: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d74d37734..e970b6486de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added Rx Timeout functionality to async Uart (#911) - RISC-V: Thread-mode and interrupt-mode executors, `#[main]` macro (#947) - ESP32-C6: Properly initialize PMU (#974) +- ESP32-C6: Implement light/deep sleep (#918) ### Changed diff --git a/esp-hal-common/src/gpio.rs b/esp-hal-common/src/gpio.rs index 243a97e1a34..9da38c64cca 100644 --- a/esp-hal-common/src/gpio.rs +++ b/esp-hal-common/src/gpio.rs @@ -158,14 +158,14 @@ pub enum RtcFunction { pub trait RTCPin: Pin { #[cfg(xtensa)] fn rtc_number(&self) -> u8; - #[cfg(xtensa)] + #[cfg(any(xtensa, esp32c6))] fn rtc_set_config(&mut self, input_enable: bool, mux: bool, func: RtcFunction); fn rtcio_pad_hold(&mut self, enable: bool); // Unsafe because `level` needs to be a valid setting for the // rtc_cntl.gpio_wakeup.gpio_pinX_int_type - #[cfg(esp32c3)] + #[cfg(any(esp32c3, esp32c6))] unsafe fn apply_wakeup(&mut self, wakeup: bool, level: u8); } @@ -2528,6 +2528,72 @@ pub mod lp_gpio { } } } + + impl crate::gpio::RTCPin for GpioPin { + unsafe fn apply_wakeup(&mut self, wakeup: bool, level: u8) { + let lp_io = &*crate::peripherals::LP_IO::ptr(); + lp_io.[< pin $gpionum >].modify(|_, w| { + w + .[< lp_gpio $gpionum _wakeup_enable >]().bit(wakeup) + .[< lp_gpio $gpionum _int_type >]().bits(level) + }); + } + + fn rtcio_pad_hold(&mut self, enable: bool) { + let mask = 1 << $gpionum; + unsafe { + let lp_aon = &*crate::peripherals::LP_AON::ptr(); + + lp_aon.gpio_hold0.modify(|r, w| { + if enable { + w.gpio_hold0().bits(r.gpio_hold0().bits() | mask) + } else { + w.gpio_hold0().bits(r.gpio_hold0().bits() & !mask) + } + }); + } + } + + /// Set the LP properties of the pin. If `mux` is true then then pin is + /// routed to LP_IO, when false it is routed to IO_MUX. + fn rtc_set_config(&mut self, input_enable: bool, mux: bool, func: crate::gpio::RtcFunction) { + let mask = 1 << $gpionum; + unsafe { + // Select LP_IO + let lp_aon = &*crate::peripherals::LP_AON::ptr(); + lp_aon + .gpio_mux + .modify(|r, w| { + if mux { + w.sel().bits(r.sel().bits() | mask) + } else { + w.sel().bits(r.sel().bits() & !mask) + } + }); + + // Configure input, function and select normal operation registers + let lp_io = &*crate::peripherals::LP_IO::ptr(); + lp_io.[< gpio $gpionum >].modify(|_, w| { + w + .[< lp_gpio $gpionum _slp_sel >]().bit(false) + .[< lp_gpio $gpionum _fun_ie >]().bit(input_enable) + .[< lp_gpio $gpionum _mcu_sel >]().bits(func as u8) + }); + } + } + } + + impl crate::gpio::RTCPinWithResistors for GpioPin { + fn rtcio_pullup(&mut self, enable: bool) { + let lp_io = unsafe { &*crate::peripherals::LP_IO::ptr() }; + lp_io.[< gpio $gpionum >].modify(|_, w| w.[< lp_gpio $gpionum _fun_wpu >]().bit(enable)); + } + + fn rtcio_pulldown(&mut self, enable: bool) { + let lp_io = unsafe { &*crate::peripherals::LP_IO::ptr() }; + lp_io.[< gpio $gpionum >].modify(|_, w| w.[< lp_gpio $gpionum _fun_wpd >]().bit(enable)); + } + } )+ } } diff --git a/esp-hal-common/src/rtc_cntl/mod.rs b/esp-hal-common/src/rtc_cntl/mod.rs index ce27ce9c2b0..db05914cde6 100644 --- a/esp-hal-common/src/rtc_cntl/mod.rs +++ b/esp-hal-common/src/rtc_cntl/mod.rs @@ -83,7 +83,7 @@ use crate::efuse::Efuse; use crate::peripherals::{LP_TIMER, LP_WDT}; #[cfg(not(any(esp32c6, esp32h2)))] use crate::peripherals::{RTC_CNTL, TIMG0}; -#[cfg(any(esp32, esp32s3, esp32c3))] +#[cfg(any(esp32, esp32s3, esp32c3, esp32c6))] use crate::rtc_cntl::sleep::{RtcSleepConfig, WakeSource, WakeTriggers}; use crate::{ clock::Clock, @@ -92,7 +92,7 @@ use crate::{ Cpu, }; // only include sleep where its been implemented -#[cfg(any(esp32, esp32s3, esp32c3))] +#[cfg(any(esp32, esp32s3, esp32c3, esp32c6))] pub mod sleep; #[cfg(any(esp32c6, esp32h2))] @@ -212,7 +212,7 @@ impl<'d> Rtc<'d> { swd: Swd::new(), }; - #[cfg(any(esp32, esp32s3, esp32c3))] + #[cfg(any(esp32, esp32s3, esp32c3, esp32c6))] RtcSleepConfig::base_settings(&this); this @@ -272,7 +272,7 @@ impl<'d> Rtc<'d> { } /// enter deep sleep and wake with the provided `wake_sources` - #[cfg(any(esp32, esp32s3, esp32c3))] + #[cfg(any(esp32, esp32s3, esp32c3, esp32c6))] pub fn sleep_deep<'a>( &mut self, wake_sources: &[&'a dyn WakeSource], @@ -284,7 +284,7 @@ impl<'d> Rtc<'d> { } /// enter light sleep and wake with the provided `wake_sources` - #[cfg(any(esp32, esp32s3, esp32c3))] + #[cfg(any(esp32, esp32s3, esp32c3, esp32c6))] pub fn sleep_light<'a>( &mut self, wake_sources: &[&'a dyn WakeSource], @@ -296,7 +296,7 @@ impl<'d> Rtc<'d> { /// enter sleep with the provided `config` and wake with the provided /// `wake_sources` - #[cfg(any(esp32, esp32s3, esp32c3))] + #[cfg(any(esp32, esp32s3, esp32c3, esp32c6))] pub fn sleep<'a>( &mut self, config: &RtcSleepConfig, diff --git a/esp-hal-common/src/rtc_cntl/rtc/esp32c6.rs b/esp-hal-common/src/rtc_cntl/rtc/esp32c6.rs index c2566be0f89..a075e8ac6e1 100644 --- a/esp-hal-common/src/rtc_cntl/rtc/esp32c6.rs +++ b/esp-hal-common/src/rtc_cntl/rtc/esp32c6.rs @@ -943,6 +943,16 @@ bitfield::bitfield! { pub bool, xpd_xtal , set_xpd_xtal : 31; } +#[derive(Clone, Copy, Default)] +// pmu_sleep_power_config_t.1 +pub struct LpSysPower { + // This is a best-guess assignment of the variants in the union `pmu_lp_power_t` union + // In esp-idf, all three fields are `pmu_lp_power_t` + pub dig_power: LpDigPower, + pub clk_power: LpClkPower, + pub xtal: LpXtalPower, +} + bitfield::bitfield! { #[derive(Clone, Copy, Default)] // pmu_lp_analog_t.0 @@ -1475,7 +1485,7 @@ impl RtcClock { /// Calibration of RTC_SLOW_CLK is performed using a special feature of /// TIMG0. This feature counts the number of XTAL clock cycles within a /// given number of RTC_SLOW_CLK cycles. - fn calibrate_internal(mut cal_clk: RtcCalSel, slowclk_cycles: u32) -> u32 { + pub(crate) fn calibrate_internal(mut cal_clk: RtcCalSel, slowclk_cycles: u32) -> u32 { const SOC_CLK_RC_FAST_FREQ_APPROX: u32 = 17_500_000; const SOC_CLK_RC_SLOW_FREQ_APPROX: u32 = 136_000; const SOC_CLK_XTAL32K_FREQ_APPROX: u32 = 32768; @@ -1790,3 +1800,27 @@ impl RtcClock { (100_000_000 * 1000 / period) as u16 } } + +pub(crate) fn rtc_clk_cpu_freq_set_xtal() { + let freq_mhz = RtcClock::get_xtal_freq(); + + unsafe { + // clk_ll_ahb_set_ls_divider(1); + pcr() + .ahb_freq_conf + .modify(|_, w| w.ahb_ls_div_num().bits(0)); + + // clk_ll_cpu_set_ls_divider(1); + pcr() + .cpu_freq_conf + .modify(|_, w| w.cpu_ls_div_num().bits(0)); + + // clk_ll_cpu_set_src(SOC_CPU_CLK_SRC_XTAL); + pcr().sysclk_conf.modify(|_, w| w.soc_clk_sel().bits(0)); + } + + let esp_rom_set_cpu_ticks_per_us: fn(u32) = unsafe { core::mem::transmute(0x4000_0048) }; + esp_rom_set_cpu_ticks_per_us(freq_mhz.mhz()); + + // TODO: don't turn off the bbpll if some consumers depend on bbpll +} diff --git a/esp-hal-common/src/rtc_cntl/sleep/esp32c3.rs b/esp-hal-common/src/rtc_cntl/sleep/esp32c3.rs index 24f16d643d6..9d771690517 100644 --- a/esp-hal-common/src/rtc_cntl/sleep/esp32c3.rs +++ b/esp-hal-common/src/rtc_cntl/sleep/esp32c3.rs @@ -1,6 +1,6 @@ use super::{TimerWakeupSource, WakeSource, WakeTriggers, WakeupLevel}; use crate::{ - gpio::RTCPinWithResistors, + gpio::{RTCPinWithResistors, RtcFunction}, regi2c_write_mask, rtc_cntl::{sleep::RtcioWakeupSource, Clock, RtcClock}, Rtc, @@ -170,7 +170,7 @@ fn isolate_digital_gpio() { io_mux.gpio[pin_num].modify(|_, w| w.fun_wpd().clear_bit()); // make pad work as gpio (otherwise, deep_sleep bottom current will rise) - io_mux.gpio[pin_num].modify(|_, w| w.mcu_sel().variant(PIN_FUNC_GPIO)); + io_mux.gpio[pin_num].modify(|_, w| w.mcu_sel().variant(RtcFunction::Digital as u8)); } } } diff --git a/esp-hal-common/src/rtc_cntl/sleep/esp32c6.rs b/esp-hal-common/src/rtc_cntl/sleep/esp32c6.rs new file mode 100644 index 00000000000..f9e2352f1f0 --- /dev/null +++ b/esp-hal-common/src/rtc_cntl/sleep/esp32c6.rs @@ -0,0 +1,964 @@ +use core::ops::Not; + +use crate::{ + efuse::Efuse, + gpio::{Pins, RtcFunction}, + peripherals::Peripherals, + rtc_cntl::{ + rtc::{ + rtc_clk_cpu_freq_set_xtal, + HpAnalog, + HpSysCntlReg, + HpSysPower, + LpAnalog, + LpSysPower, + RtcCalSel, + }, + sleep::{Ext1WakeupSource, WakeSource, WakeTriggers, WakeupLevel}, + RtcClock, + }, + Rtc, +}; + +impl Ext1WakeupSource<'_, '_> { + /// Returns the currently configured wakeup pins. + fn wakeup_pins() -> u8 { + unsafe { lp_aon().ext_wakeup_cntl.read().ext_wakeup_sel().bits() } + } + + fn wake_io_reset(gpio: &mut Pins) { + use crate::gpio::RTCPin; + + fn uninit_pin(pin: &mut impl RTCPin, wakeup_pins: u8) { + if wakeup_pins & (1 << pin.number()) != 0 { + pin.rtcio_pad_hold(false); + pin.rtc_set_config(false, false, RtcFunction::Rtc); + } + } + + let wakeup_pins = Self::wakeup_pins(); + uninit_pin(&mut gpio.gpio0, wakeup_pins); + uninit_pin(&mut gpio.gpio1, wakeup_pins); + uninit_pin(&mut gpio.gpio2, wakeup_pins); + uninit_pin(&mut gpio.gpio3, wakeup_pins); + uninit_pin(&mut gpio.gpio4, wakeup_pins); + uninit_pin(&mut gpio.gpio5, wakeup_pins); + uninit_pin(&mut gpio.gpio6, wakeup_pins); + uninit_pin(&mut gpio.gpio7, wakeup_pins); + } +} + +impl WakeSource for Ext1WakeupSource<'_, '_> { + fn apply(&self, _rtc: &Rtc, triggers: &mut WakeTriggers, sleep_config: &mut RtcSleepConfig) { + // We don't have to keep the LP domain powered if we hold the wakeup pin states. + triggers.set_ext1(true); + + // set pins to RTC function + let mut pins = self.pins.borrow_mut(); + let mut pin_mask = 0u8; + let mut level_mask = 0u8; + for (pin, level) in pins.iter_mut() { + pin_mask |= 1 << pin.number(); + level_mask |= match level { + WakeupLevel::High => 1 << pin.number(), + WakeupLevel::Low => 0, + }; + + pin.rtc_set_config(true, true, RtcFunction::Rtc); + pin.rtcio_pad_hold(true); + } + + unsafe { + // clear previous wakeup status + lp_aon() + .ext_wakeup_cntl + .modify(|_, w| w.ext_wakeup_status_clr().set_bit()); + + // set pin + level register fields + lp_aon().ext_wakeup_cntl.modify(|r, w| { + w.ext_wakeup_sel() + .bits(r.ext_wakeup_sel().bits() | pin_mask) + .ext_wakeup_lv() + .bits(r.ext_wakeup_lv().bits() & !pin_mask | level_mask) + }); + } + } +} + +impl Drop for Ext1WakeupSource<'_, '_> { + fn drop(&mut self) { + // should we have saved the pin configuration first? + // set pin back to IO_MUX (input_enable and func have no effect when pin is sent + // to IO_MUX) + let mut pins = self.pins.borrow_mut(); + for (pin, _level) in pins.iter_mut() { + pin.rtc_set_config(true, false, RtcFunction::Rtc); + } + } +} + +#[derive(Clone, Copy)] +// pmu_sleep_analog_config_t +pub struct AnalogSleepConfig { + pub hp_sys: HpAnalog, + // pub lp_sys_active: LpAnalog, // unused + pub lp_sys_sleep: LpAnalog, +} + +impl AnalogSleepConfig { + fn defaults_deep_sleep() -> Self { + Self { + // PMU_SLEEP_ANALOG_DSLP_CONFIG_DEFAULT + hp_sys: { + let mut cfg = HpAnalog::default(); + + cfg.bias.set_pd_cur(false); + cfg.bias.set_bias_sleep(false); + cfg.regulator0.set_xpd(false); + cfg.bias.set_dbg_atten(0); + + cfg + }, + // lp_sys_active: LpAnalog::default(), + lp_sys_sleep: { + let mut cfg = LpAnalog::default(); + + cfg.regulator1.set_drv_b(0); + cfg.bias.set_pd_cur(true); + cfg.bias.set_bias_sleep(true); + cfg.regulator0.set_slp_xpd(false); + cfg.regulator0.set_slp_dbias(0); + cfg.regulator0.set_xpd(true); + cfg.bias.set_dbg_atten(12); + cfg.regulator0.set_dbias(23); // 0.7V + + cfg + }, + } + } + + fn defaults_light_sleep(pd_flags: PowerDownFlags) -> Self { + let mut this = Self { + // PMU_SLEEP_ANALOG_LSLP_CONFIG_DEFAULT + hp_sys: { + let mut cfg = HpAnalog::default(); + + cfg.regulator1.set_drv_b(0); + cfg.bias.set_pd_cur(true); + cfg.bias.set_bias_sleep(true); + cfg.regulator0.set_xpd(true); + cfg.bias.set_dbg_atten(0); + cfg.regulator0.set_dbias(1); // 0.6V + + cfg + }, + // lp_sys_active: LpAnalog::default(), + lp_sys_sleep: { + let mut cfg = LpAnalog::default(); + + cfg.regulator1.set_drv_b(0); + cfg.bias.set_pd_cur(true); + cfg.bias.set_bias_sleep(true); + cfg.regulator0.set_slp_xpd(false); + cfg.regulator0.set_slp_dbias(0); + cfg.regulator0.set_xpd(true); + cfg.bias.set_dbg_atten(0); + cfg.regulator0.set_dbias(12); // 0.7V + + cfg + }, + }; + + if !pd_flags.pd_xtal() { + this.hp_sys.bias.set_pd_cur(false); + this.hp_sys.bias.set_bias_sleep(false); + this.hp_sys.regulator0.set_dbias(25); + + this.lp_sys_sleep.bias.set_pd_cur(false); + this.lp_sys_sleep.bias.set_bias_sleep(false); + this.lp_sys_sleep.regulator0.set_dbias(26); + } + + this + } + + fn apply(&self) { + // pmu_sleep_analog_init + + unsafe { + // HP SLEEP (hp_sleep_*) + pmu().hp_sleep_bias.modify(|_, w| { + w.hp_sleep_dbg_atten() // pmu_ll_hp_set_dbg_atten + .bits(self.hp_sys.bias.dbg_atten() as u8) + .hp_sleep_pd_cur() // pmu_ll_hp_set_current_power_off + .bit(self.hp_sys.bias.pd_cur()) + .sleep() // pmu_ll_hp_set_bias_sleep_enable + .bit(self.hp_sys.bias.bias_sleep()) + }); + pmu().hp_sleep_hp_regulator0.modify(|_, w| { + w.hp_sleep_hp_regulator_xpd() // pmu_ll_hp_set_regulator_xpd + .bit(self.hp_sys.regulator0.xpd()) + .hp_sleep_hp_regulator_dbias() // pmu_ll_hp_set_regulator_dbias + .bits(self.hp_sys.regulator0.dbias() as u8) + }); + pmu().hp_sleep_hp_regulator1.modify(|_, w| { + w.hp_sleep_hp_regulator_drv_b() // pmu_ll_hp_set_regulator_driver_bar + .bits(self.hp_sys.regulator1.drv_b()) + }); + + // LP SLEEP (lp_sleep_*) + pmu().lp_sleep_bias.modify(|_, w| { + w.lp_sleep_dbg_atten() // pmu_ll_lp_set_dbg_atten + .bits(self.lp_sys_sleep.bias.dbg_atten() as u8) + .lp_sleep_pd_cur() // pmu_ll_lp_set_current_power_off + .bit(self.lp_sys_sleep.bias.pd_cur()) + .sleep() // pmu_ll_lp_set_bias_sleep_enable + .bit(self.lp_sys_sleep.bias.bias_sleep()) + }); + + pmu().lp_sleep_lp_regulator0.modify(|_, w| { + w.lp_sleep_lp_regulator_slp_xpd() // pmu_ll_lp_set_regulator_slp_xpd + .bit(self.lp_sys_sleep.regulator0.slp_xpd()) + .lp_sleep_lp_regulator_xpd() // pmu_ll_lp_set_regulator_xpd + .bit(self.lp_sys_sleep.regulator0.xpd()) + .lp_sleep_lp_regulator_slp_dbias() // pmu_ll_lp_set_regulator_sleep_dbias + .bits(self.lp_sys_sleep.regulator0.slp_dbias() as u8) + .lp_sleep_lp_regulator_dbias() // pmu_ll_lp_set_regulator_dbias + .bits(self.lp_sys_sleep.regulator0.dbias() as u8) + }); + + pmu().lp_sleep_lp_regulator1.modify(|_, w| { + w.lp_sleep_lp_regulator_drv_b() // pmu_ll_lp_set_regulator_driver_bar + .bits(self.lp_sys_sleep.regulator1.drv_b() as u8) + }); + } + } +} + +#[derive(Clone, Copy)] +// pmu_sleep_digital_config_t +pub struct DigitalSleepConfig { + pub syscntl: HpSysCntlReg, +} + +impl DigitalSleepConfig { + fn defaults_light_sleep(pd_flags: PowerDownFlags) -> Self { + Self { + // PMU_SLEEP_DIGITAL_LSLP_CONFIG_DEFAULT + syscntl: { + let mut cfg = HpSysCntlReg::default(); + + cfg.set_dig_pad_slp_sel(pd_flags.pd_top().not()); + + cfg + }, + } + } + + fn apply(&self) { + // pmu_sleep_digital_init + unsafe { + pmu().hp_sleep_hp_sys_cntl.modify(|_, w| { + w.hp_sleep_dig_pad_slp_sel() + .bit(self.syscntl.dig_pad_slp_sel()) + }) + }; + } +} + +#[derive(Clone, Copy)] +// pmu_sleep_power_config_t +pub struct PowerSleepConfig { + pub hp_sys: HpSysPower, + pub lp_sys_active: LpSysPower, + pub lp_sys_sleep: LpSysPower, +} + +impl PowerSleepConfig { + fn defaults(pd_flags: PowerDownFlags) -> Self { + let mut this = Self { + hp_sys: HpSysPower::default(), + lp_sys_active: LpSysPower::default(), + lp_sys_sleep: LpSysPower::default(), + }; + this.apply_flags(pd_flags); + this + } + + fn apply_flags(&mut self, pd_flags: PowerDownFlags) { + // PMU_SLEEP_POWER_CONFIG_DEFAULT + self.hp_sys + .dig_power + .set_vdd_spi_pd_en(pd_flags.pd_vddsdio()); + self.hp_sys.dig_power.set_wifi_pd_en(pd_flags.pd_modem()); + self.hp_sys.dig_power.set_cpu_pd_en(pd_flags.pd_cpu()); + self.hp_sys.dig_power.set_aon_pd_en(pd_flags.pd_hp_aon()); + self.hp_sys.dig_power.set_top_pd_en(pd_flags.pd_top()); + + self.hp_sys.clk.set_i2c_iso_en(true); + self.hp_sys.clk.set_i2c_retention(true); + + self.hp_sys.xtal.set_xpd_xtal(pd_flags.pd_xtal().not()); + + self.lp_sys_active.clk_power.set_xpd_xtal32k(true); + self.lp_sys_active.clk_power.set_xpd_rc32k(true); + self.lp_sys_active.clk_power.set_xpd_fosc(true); + + self.lp_sys_sleep + .dig_power + .set_peri_pd_en(pd_flags.pd_lp_periph()); + self.lp_sys_sleep.dig_power.set_mem_dslp(true); + + self.lp_sys_sleep + .clk_power + .set_xpd_xtal32k(pd_flags.pd_xtal32k().not()); + self.lp_sys_sleep + .clk_power + .set_xpd_rc32k(pd_flags.pd_rc32k().not()); + self.lp_sys_sleep + .clk_power + .set_xpd_fosc(pd_flags.pd_rc_fast().not()); + + self.lp_sys_sleep + .xtal + .set_xpd_xtal(pd_flags.pd_xtal().not()); + } + + fn apply(&self) { + // pmu_sleep_power_init + + unsafe { + // HP SLEEP (hp_sleep_*) + pmu() + .hp_sleep_dig_power + .modify(|_, w| w.bits(self.hp_sys.dig_power.0)); + pmu() + .hp_sleep_hp_ck_power + .modify(|_, w| w.bits(self.hp_sys.clk.0)); + pmu() + .hp_sleep_xtal + .modify(|_, w| w.hp_sleep_xpd_xtal().bit(self.hp_sys.xtal.xpd_xtal())); + + // LP ACTIVE (hp_sleep_lp_*) + pmu() + .hp_sleep_lp_dig_power + .modify(|_, w| w.bits(self.lp_sys_active.dig_power.0)); + pmu() + .hp_sleep_lp_ck_power + .modify(|_, w| w.bits(self.lp_sys_active.clk_power.0)); + + // LP SLEEP (lp_sleep_*) + pmu() + .lp_sleep_lp_dig_power + .modify(|_, w| w.bits(self.lp_sys_sleep.dig_power.0)); + pmu() + .lp_sleep_lp_ck_power + .modify(|_, w| w.bits(self.lp_sys_sleep.clk_power.0)); + pmu() + .lp_sleep_xtal + .modify(|_, w| w.lp_sleep_xpd_xtal().bit(self.lp_sys_sleep.xtal.xpd_xtal())); + } + } +} + +#[derive(Clone, Copy)] +// pmu_hp_param_t +pub struct HpParam { + pub modem_wakeup_wait_cycle: u32, + pub analog_wait_target_cycle: u16, + pub digital_power_down_wait_cycle: u16, + pub digital_power_supply_wait_cycle: u16, + pub digital_power_up_wait_cycle: u16, + pub pll_stable_wait_cycle: u16, + pub modify_icg_cntl_wait_cycle: u8, + pub switch_icg_cntl_wait_cycle: u8, + pub min_slp_slow_clk_cycle: u8, +} + +#[derive(Clone, Copy)] +// pmu_lp_param_t +pub struct LpParam { + pub digital_power_supply_wait_cycle: u16, + pub min_slp_slow_clk_cycle: u8, + pub analog_wait_target_cycle: u8, + pub digital_power_down_wait_cycle: u8, + pub digital_power_up_wait_cycle: u8, +} + +#[derive(Clone, Copy)] +// pmu_hp_lp_param_t +pub struct HpLpParam { + // union of two u16 variants, I've elected to not complicate things... + pub xtal_stable_wait_cycle: u16, +} + +#[derive(Clone, Copy)] +// pmu_sleep_param_config_t +pub struct ParamSleepConfig { + pub hp_sys: HpParam, + pub lp_sys: LpParam, + pub hp_lp: HpLpParam, +} +impl ParamSleepConfig { + const PMU_SLEEP_PARAM_CONFIG_DEFAULT: Self = Self { + hp_sys: HpParam { + min_slp_slow_clk_cycle: 10, + analog_wait_target_cycle: 2419, + digital_power_supply_wait_cycle: 32, + digital_power_up_wait_cycle: 32, + modem_wakeup_wait_cycle: 20700, + pll_stable_wait_cycle: 2, + + digital_power_down_wait_cycle: 0, + modify_icg_cntl_wait_cycle: 0, + switch_icg_cntl_wait_cycle: 0, + }, + lp_sys: LpParam { + min_slp_slow_clk_cycle: 10, + analog_wait_target_cycle: 23, + digital_power_supply_wait_cycle: 32, + digital_power_up_wait_cycle: 32, + + digital_power_down_wait_cycle: 0, + }, + hp_lp: HpLpParam { + xtal_stable_wait_cycle: 30, + }, + }; + + fn apply(&self) { + // pmu_sleep_param_init + + unsafe { + pmu().slp_wakeup_cntl3.modify(|_, w| { + w.hp_min_slp_val() // pmu_ll_hp_set_min_sleep_cycle + .bits(self.hp_sys.min_slp_slow_clk_cycle) + .lp_min_slp_val() // pmu_ll_lp_set_min_sleep_cycle + .bits(self.lp_sys.min_slp_slow_clk_cycle) + }); + + pmu().slp_wakeup_cntl7.modify(|_, w| { + w.ana_wait_target() // pmu_ll_hp_set_analog_wait_target_cycle + .bits(self.hp_sys.analog_wait_target_cycle) + }); + + pmu().power_wait_timer0.modify(|_, w| { + w.dg_hp_wait_timer() // pmu_ll_hp_set_digital_power_supply_wait_cycle + .bits(self.hp_sys.digital_power_supply_wait_cycle) + .dg_hp_powerup_timer() // pmu_ll_hp_set_digital_power_up_wait_cycle + .bits(self.hp_sys.digital_power_up_wait_cycle) + }); + + pmu().power_wait_timer1.modify(|_, w| { + w.dg_lp_wait_timer() // pmu_ll_lp_set_digital_power_supply_wait_cycle + .bits(self.lp_sys.digital_power_supply_wait_cycle) + .dg_lp_powerup_timer() // pmu_ll_lp_set_digital_power_up_wait_cycle + .bits(self.lp_sys.digital_power_up_wait_cycle) + }); + + pmu().slp_wakeup_cntl5.modify(|_, w| { + w.lp_ana_wait_target() // pmu_ll_lp_set_analog_wait_target_cycle + .bits(self.lp_sys.analog_wait_target_cycle) + .modem_wait_target() // pmu_ll_hp_set_modem_wakeup_wait_cycle + .bits(self.hp_sys.modem_wakeup_wait_cycle) + }); + pmu().power_ck_wait_cntl.modify(|_, w| { + w.wait_xtl_stable() // pmu_ll_hp_set_xtal_stable_wait_cycle + .bits(self.hp_lp.xtal_stable_wait_cycle) + .wait_pll_stable() // pmu_ll_hp_set_pll_stable_wait_cycle + .bits(self.hp_sys.pll_stable_wait_cycle) + }); + } + } + + fn defaults(config: SleepTimeConfig, pd_flags: PowerDownFlags, pd_xtal: bool) -> Self { + let mut param = Self::PMU_SLEEP_PARAM_CONFIG_DEFAULT; + + // pmu_sleep_param_config_default + param.hp_sys.min_slp_slow_clk_cycle = + config.us_to_slowclk(MachineConstants::HP_MIN_SLP_TIME_US) as u8; + param.hp_sys.analog_wait_target_cycle = + config.us_to_fastclk(MachineConstants::HP_ANALOG_WAIT_TIME_US) as u16; + param.hp_sys.digital_power_supply_wait_cycle = + config.us_to_fastclk(MachineConstants::HP_POWER_SUPPLY_WAIT_TIME_US) as u16; + param.hp_sys.digital_power_up_wait_cycle = + config.us_to_fastclk(MachineConstants::HP_POWER_UP_WAIT_TIME_US) as u16; + param.hp_sys.pll_stable_wait_cycle = + config.us_to_fastclk(MachineConstants::HP_PLL_WAIT_STABLE_TIME_US) as u16; + + let hw_wait_time_us = config.pmu_sleep_calculate_hw_wait_time(pd_flags); + + let modem_wakeup_wait_time_us = (config.sleep_time_adjustment + + MachineConstants::MODEM_STATE_SKIP_TIME_US + + MachineConstants::HP_REGDMA_RF_ON_WORK_TIME_US) + .saturating_sub(hw_wait_time_us); + param.hp_sys.modem_wakeup_wait_cycle = config.us_to_fastclk(modem_wakeup_wait_time_us); + + param.lp_sys.min_slp_slow_clk_cycle = + config.us_to_slowclk(MachineConstants::LP_MIN_SLP_TIME_US) as u8; + param.lp_sys.analog_wait_target_cycle = + config.us_to_slowclk(MachineConstants::LP_ANALOG_WAIT_TIME_US) as u8; + param.lp_sys.digital_power_supply_wait_cycle = + config.us_to_fastclk(MachineConstants::LP_POWER_SUPPLY_WAIT_TIME_US) as u16; + param.lp_sys.digital_power_up_wait_cycle = + config.us_to_fastclk(MachineConstants::LP_POWER_UP_WAIT_TIME_US) as u8; + + // This looks different from esp-idf but it is the same: + // Both `xtal_stable_wait_cycle` and `xtal_stable_wait_slow_clk_cycle` are + // u16 variants of the same union + param.hp_lp.xtal_stable_wait_cycle = if pd_xtal { + config.us_to_slowclk(MachineConstants::LP_XTAL_WAIT_STABLE_TIME_US) as u16 + } else { + config.us_to_fastclk(MachineConstants::HP_XTAL_WAIT_STABLE_TIME_US) as u16 + }; + + param + } +} + +#[derive(Clone, Copy)] +struct SleepTimeConfig { + sleep_time_adjustment: u32, + // TODO: esp-idf does some calibration here to determine slowclk_period + slowclk_period: u32, + fastclk_period: u32, +} + +const CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ: u32 = 160; + +impl SleepTimeConfig { + const RTC_CLK_CAL_FRACT: u32 = 19; + + fn rtc_clk_cal_fast(mut slowclk_cycles: u32) -> u32 { + let xtal_freq = 40; // esp-idf has a very complicated way of determining this + + // The Fosc CLK of calibration circuit is divided by 32 for ECO1. + // So we need to divide the calibrate cycles of the FOSC for ECO1 and above + // chips by 32 to avoid excessive calibration time. + if Efuse::chip_revision() >= 1 { + slowclk_cycles = slowclk_cycles / 32; + } + + let xtal_cycles = + RtcClock::calibrate_internal(RtcCalSel::RtcCalRcFast, slowclk_cycles) as u64; + + let divider: u64 = xtal_freq as u64 * slowclk_cycles as u64; + let period_64: u64 = ((xtal_cycles << Self::RTC_CLK_CAL_FRACT) + divider / 2 - 1) / divider; + (period_64 & (u32::MAX as u64)) as u32 + } + + fn light_sleep(pd_flags: PowerDownFlags) -> Self { + const LIGHT_SLEEP_TIME_OVERHEAD_US: u32 = 56; + + let mut this = Self { + sleep_time_adjustment: 0, + slowclk_period: unsafe { lp_aon().store1.read().lp_aon_store1().bits() }, + fastclk_period: Self::rtc_clk_cal_fast(2048), + }; + + let sw = LIGHT_SLEEP_TIME_OVERHEAD_US; // TODO + let hw = this.pmu_sleep_calculate_hw_wait_time(pd_flags); + + this.sleep_time_adjustment = sw + hw; + + this + } + + fn deep_sleep() -> Self { + Self { + sleep_time_adjustment: 250 + 100 * 240 / CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, + slowclk_period: unsafe { lp_aon().store1.read().lp_aon_store1().bits() }, + fastclk_period: 0, // esp-idf doesn't set this anywhere for deep sleep + } + } + + fn us_to_slowclk(&self, us: u32) -> u32 { + if self.slowclk_period == 0 { + 0 + } else { + (us << Self::RTC_CLK_CAL_FRACT) / self.slowclk_period + } + } + + fn slowclk_to_us(&self, rtc_cycles: u32) -> u32 { + (rtc_cycles * self.slowclk_period) >> Self::RTC_CLK_CAL_FRACT + } + + fn us_to_fastclk(&self, us: u32) -> u32 { + if self.fastclk_period == 0 { + 0 + } else { + (us << Self::RTC_CLK_CAL_FRACT) / self.fastclk_period + } + } + + fn pmu_sleep_calculate_hw_wait_time(&self, pd_flags: PowerDownFlags) -> u32 { + // LP core hardware wait time, microsecond + let lp_wakeup_wait_time_us = self.slowclk_to_us(MachineConstants::LP_WAKEUP_WAIT_CYCLE); + let lp_clk_switch_time_us = self.slowclk_to_us(MachineConstants::LP_CLK_SWITCH_CYCLE); + let lp_clk_power_on_wait_time_us = if pd_flags.pd_xtal() { + MachineConstants::LP_XTAL_WAIT_STABLE_TIME_US + } else { + self.slowclk_to_us(MachineConstants::LP_CLK_POWER_ON_WAIT_CYCLE) + }; + + let lp_hw_wait_time_us = MachineConstants::LP_MIN_SLP_TIME_US + + MachineConstants::LP_ANALOG_WAIT_TIME_US + + lp_clk_power_on_wait_time_us + + lp_wakeup_wait_time_us + + lp_clk_switch_time_us + + MachineConstants::LP_POWER_SUPPLY_WAIT_TIME_US + + MachineConstants::LP_POWER_UP_WAIT_TIME_US; + + // HP core hardware wait time, microsecond + let hp_digital_power_up_wait_time_us = MachineConstants::HP_POWER_SUPPLY_WAIT_TIME_US + + MachineConstants::HP_POWER_UP_WAIT_TIME_US; + let hp_regdma_wait_time_us = u32::max( + MachineConstants::HP_REGDMA_S2M_WORK_TIME_US + + MachineConstants::HP_REGDMA_M2A_WORK_TIME_US, + MachineConstants::HP_REGDMA_S2A_WORK_TIME_US, + ); + let hp_clock_wait_time_us = MachineConstants::HP_XTAL_WAIT_STABLE_TIME_US + + MachineConstants::HP_PLL_WAIT_STABLE_TIME_US; + + let hp_hw_wait_time_us = MachineConstants::HP_ANALOG_WAIT_TIME_US + + u32::max( + hp_digital_power_up_wait_time_us + hp_regdma_wait_time_us, + hp_clock_wait_time_us, + ); + + #[rustfmt::skip] // ASCII art + /* When the SOC wakeup (lp timer or GPIO wakeup) and Modem wakeup (Beacon wakeup) complete, + * the soc wakeup will be delayed until the RF is turned on in Modem state. + * + * modem wakeup TBTT, RF on by HW + * | | + * \|/ \|/ + * PMU_HP_ACTIVE /------ + * PMU_HP_MODEM /------------////////////////// + * PMU_HP_SLEEP ----------------------////////////////// + * /|\ /|\ /|\ /|\ /|\ /|\ + * |<- some hw wait ->| | | |<- M2A switch ->| + * | slow cycles & | soc wakeup | | + * | FOSC cycles |<- S2M switch ->| | + * | | + * |<-- PMU guard time, also the maximum time for the SOC -->| + * | wake-up delay | + */ + const CONFIG_ESP_WIFI_ENHANCED_LIGHT_SLEEP: bool = true; + + let (rf_on_protect_time_us, sync_time_us) = if CONFIG_ESP_WIFI_ENHANCED_LIGHT_SLEEP { + ( + MachineConstants::HP_REGDMA_RF_ON_WORK_TIME_US, + MachineConstants::HP_CLOCK_DOMAIN_SYNC_TIME_US, + ) + } else { + (0, 0) + }; + + lp_hw_wait_time_us + hp_hw_wait_time_us + sync_time_us + rf_on_protect_time_us + } +} + +#[derive(Clone, Copy)] +// pmu_sleep_config_t + deep sleep flag + pd flags +pub struct RtcSleepConfig { + pub deep: bool, + pub pd_flags: PowerDownFlags, +} + +impl Default for RtcSleepConfig { + fn default() -> Self { + // from pmu_sleep_param_config_default + // sleep flags will be applied by wakeup methods and apply + + Self { + deep: false, + pd_flags: { + let mut pd_flags = PowerDownFlags(0); + + pd_flags.set_pd_lp_periph(true); + pd_flags.set_pd_cpu(true); + pd_flags.set_pd_xtal32k(true); + pd_flags.set_pd_rc32k(true); + pd_flags.set_pd_rc_fast(true); + pd_flags.set_pd_xtal(true); + pd_flags.set_pd_top(true); + pd_flags.set_pd_modem(true); + pd_flags.set_pd_vddsdio(true); + + pd_flags + }, + } + } +} + +unsafe fn pmu<'a>() -> &'a esp32c6::pmu::RegisterBlock { + &*esp32c6::PMU::ptr() +} + +unsafe fn lp_aon<'a>() -> &'a esp32c6::lp_aon::RegisterBlock { + &*esp32c6::LP_AON::ptr() +} + +bitfield::bitfield! { + #[derive(Clone, Copy)] + /// Power domains to be powered down during sleep + pub struct PowerDownFlags(u32); + + pub u32, pd_top , set_pd_top : 0; + pub u32, pd_vddsdio , set_pd_vddsdio : 1; + pub u32, pd_modem , set_pd_modem : 2; + pub u32, pd_hp_periph, set_pd_hp_periph: 3; + pub u32, pd_cpu , set_pd_cpu : 4; + pub u32, pd_hp_aon , set_pd_hp_aon : 5; + pub u32, pd_mem_g0 , set_pd_mem_g0 : 6; + pub u32, pd_mem_g1 , set_pd_mem_g1 : 7; + pub u32, pd_mem_g2 , set_pd_mem_g2 : 8; + pub u32, pd_mem_g3 , set_pd_mem_g3 : 9; + pub u32, pd_xtal , set_pd_xtal : 10; + pub u32, pd_rc_fast , set_pd_rc_fast : 11; + pub u32, pd_xtal32k , set_pd_xtal32k : 12; + pub u32, pd_rc32k , set_pd_rc32k : 13; + pub u32, pd_lp_periph, set_pd_lp_periph: 14; +} + +impl PowerDownFlags { + pub fn pd_mem(self) -> bool { + self.pd_mem_g0() && self.pd_mem_g1() && self.pd_mem_g2() && self.pd_mem_g3() + } + + pub fn set_pd_mem(&mut self, value: bool) { + self.set_pd_mem_g0(value); + self.set_pd_mem_g1(value); + self.set_pd_mem_g2(value); + self.set_pd_mem_g3(value); + } +} + +struct MachineConstants; +impl MachineConstants { + const LP_MIN_SLP_TIME_US: u32 = 450; + const LP_WAKEUP_WAIT_CYCLE: u32 = 4; + const LP_ANALOG_WAIT_TIME_US: u32 = 154; + const LP_XTAL_WAIT_STABLE_TIME_US: u32 = 250; + const LP_CLK_SWITCH_CYCLE: u32 = 1; + const LP_CLK_POWER_ON_WAIT_CYCLE: u32 = 1; + const LP_POWER_SUPPLY_WAIT_TIME_US: u32 = 2; + const LP_POWER_UP_WAIT_TIME_US: u32 = 2; + + const HP_MIN_SLP_TIME_US: u32 = 450; + const HP_CLOCK_DOMAIN_SYNC_TIME_US: u32 = 150; + const HP_SYSTEM_DFS_UP_WORK_TIME_US: u32 = 124; + const HP_ANALOG_WAIT_TIME_US: u32 = 154; + const HP_POWER_SUPPLY_WAIT_TIME_US: u32 = 2; + const HP_POWER_UP_WAIT_TIME_US: u32 = 2; + const HP_REGDMA_S2M_WORK_TIME_US: u32 = 172; + const HP_REGDMA_S2A_WORK_TIME_US: u32 = 480; + const HP_REGDMA_M2A_WORK_TIME_US: u32 = 278; + // const HP_REGDMA_A2S_WORK_TIME_US: u32 = 382; + const HP_REGDMA_RF_ON_WORK_TIME_US: u32 = 70; + // const HP_REGDMA_RF_OFF_WORK_TIME_US: u32 = 23; + const HP_XTAL_WAIT_STABLE_TIME_US: u32 = 250; + const HP_PLL_WAIT_STABLE_TIME_US: u32 = 1; + + const MODEM_STATE_SKIP_TIME_US: u32 = Self::HP_REGDMA_M2A_WORK_TIME_US + + Self::HP_SYSTEM_DFS_UP_WORK_TIME_US + + Self::LP_MIN_SLP_TIME_US; +} + +impl RtcSleepConfig { + pub fn deep_slp(&self) -> bool { + self.deep + } + + pub fn deep() -> Self { + // Set up for ultra-low power sleep. Wakeup sources may modify these settings. + let mut cfg = Self::default(); + + cfg.deep = true; + + cfg + } + + pub(crate) fn base_settings(_rtc: &Rtc) { + Self::wake_io_reset(); + } + + fn wake_io_reset() { + // loosely based on esp_deep_sleep_wakeup_io_reset + + use crate::gpio::GpioExt; + + let peripherals = unsafe { + // We're stealing peripherals to do some uninitialization after waking up from + // deep sleep. We have to be careful to only touch settings that were enabled + // by deep sleep setup. + Peripherals::steal() + }; + let mut gpio = peripherals.GPIO.split(); + + Ext1WakeupSource::wake_io_reset(&mut gpio); + } + + /// Finalize power-down flags, apply configuration based on the flags. + pub(crate) fn apply(&mut self) { + // TODO: save clock settings + rtc_clk_cpu_freq_set_xtal(); + + // pmu_sleep_config_default + pmu_sleep_init + if self.deep { + // force-disable certain power domains + self.pd_flags.set_pd_top(true); + self.pd_flags.set_pd_vddsdio(true); + self.pd_flags.set_pd_modem(true); + self.pd_flags.set_pd_hp_periph(true); + self.pd_flags.set_pd_cpu(true); + self.pd_flags.set_pd_mem(true); + self.pd_flags.set_pd_xtal(true); + self.pd_flags.set_pd_hp_aon(true); + } + + let power = PowerSleepConfig::defaults(self.pd_flags); + power.apply(); + + let config = if self.deep { + SleepTimeConfig::deep_sleep() + } else { + SleepTimeConfig::light_sleep(self.pd_flags) + }; + + let mut param = + ParamSleepConfig::defaults(config, self.pd_flags, power.hp_sys.xtal.xpd_xtal()); + + if self.deep { + const PMU_LP_ANALOG_WAIT_TARGET_TIME_DSLP_US: u32 = 500; + param.lp_sys.analog_wait_target_cycle = + config.us_to_slowclk(PMU_LP_ANALOG_WAIT_TARGET_TIME_DSLP_US) as u8; + + AnalogSleepConfig::defaults_deep_sleep().apply(); + } else { + AnalogSleepConfig::defaults_light_sleep(self.pd_flags).apply(); + DigitalSleepConfig::defaults_light_sleep(self.pd_flags).apply(); + } + + param.apply(); + + if !self.deep { + // TODO: CPU retention + } + // esp_set_deep_sleep_wake_stub_default_entry(); + } + + /// Configures wakeup options and enters sleep. + /// + /// This function does not return if deep sleep is requested. + pub(crate) fn start_sleep(&self, wakeup_triggers: WakeTriggers) { + const PMU_EXT0_WAKEUP_EN: u32 = 1 << 0; + const PMU_EXT1_WAKEUP_EN: u32 = 1 << 1; + const PMU_GPIO_WAKEUP_EN: u32 = 1 << 2; + const PMU_LP_TIMER_WAKEUP_EN: u32 = 1 << 4; + const PMU_WIFI_SOC_WAKEUP_EN: u32 = 1 << 5; + const PMU_UART0_WAKEUP_EN: u32 = 1 << 6; + const PMU_UART1_WAKEUP_EN: u32 = 1 << 7; + const PMU_SDIO_WAKEUP_EN: u32 = 1 << 8; + const PMU_BLE_SOC_WAKEUP_EN: u32 = 1 << 10; + const PMU_LP_CORE_WAKEUP_EN: u32 = 1 << 11; + const PMU_USB_WAKEUP_EN: u32 = 1 << 14; + const MODEM_REJECT: u32 = 1 << 16; + + const RTC_SLEEP_REJECT_MASK: u32 = PMU_EXT0_WAKEUP_EN + | PMU_EXT1_WAKEUP_EN + | PMU_GPIO_WAKEUP_EN + | PMU_LP_TIMER_WAKEUP_EN + | PMU_WIFI_SOC_WAKEUP_EN + | PMU_UART0_WAKEUP_EN + | PMU_UART1_WAKEUP_EN + | PMU_SDIO_WAKEUP_EN + | PMU_BLE_SOC_WAKEUP_EN + | PMU_LP_CORE_WAKEUP_EN + | PMU_USB_WAKEUP_EN; + + let wakeup_mask = wakeup_triggers.0 as u32; + let reject_mask = if self.deep { + 0 + } else { + // TODO: MODEM_REJECT if s_sleep_modem.wifi.phy_link != NULL + let reject_mask = RTC_SLEEP_REJECT_MASK | MODEM_REJECT; + wakeup_mask & reject_mask + }; + + // like esp-idf pmu_sleep_start() + + unsafe { + // lp_aon_hal_inform_wakeup_type - tells ROM which wakeup stub to run + lp_aon() + .store9 + .modify(|r, w| w.bits(r.bits() & !0x01 | self.deep as u32)); + + // pmu_ll_hp_set_wakeup_enable + pmu().slp_wakeup_cntl2.write(|w| w.bits(wakeup_mask)); + + // pmu_ll_hp_set_reject_enable + pmu().slp_wakeup_cntl1.modify(|_, w| { + w.slp_reject_en() + .bit(true) + .sleep_reject_ena() + .bits(reject_mask) + }); + + // pmu_ll_hp_clear_reject_cause + pmu() + .slp_wakeup_cntl4 + .write(|w| w.slp_reject_cause_clr().bit(true)); + + pmu().hp_int_clr.write(|w| { + w.sw_int_clr() // pmu_ll_hp_clear_sw_intr_status + .bit(true) + .soc_sleep_reject_int_clr() // pmu_ll_hp_clear_reject_intr_status + .bit(true) + .soc_wakeup_int_clr() // pmu_ll_hp_clear_wakeup_intr_status + .bit(true) + }); + + // misc_modules_sleep_prepare + + // TODO: IDF-7370 + #[cfg(not(pmu))] + if !(self.deep && wakeup_triggers.touch) { + let saradc = &*esp32c6::APB_SARADC::ptr(); + saradc.ctrl.modify(|_, w| w.saradc2_pwdet_drv().bit(false)); + } + + // Start entry into sleep mode + + // pmu_ll_hp_set_sleep_enable + pmu().slp_wakeup_cntl0.write(|w| w.sleep_req().bit(true)); + + // In pd_cpu lightsleep and deepsleep mode, we never get here + loop { + let int_raw = pmu().int_raw.read(); + if int_raw.soc_wakeup_int_raw().bit_is_set() + || int_raw.soc_sleep_reject_int_raw().bit_is_set() + { + break; + } + } + } + + // esp-idf returns if the sleep was rejected, we don't return anything + + // TODO: restore clock settings + } + + /// Cleans up after sleep + pub(crate) fn finish_sleep(&self) { + // like esp-idf pmu_sleep_finish() + // In "pd_cpu lightsleep" and "deepsleep" modes we never get here + + // esp-idf returns if the sleep was rejected, we do nothing + // pmu_ll_hp_is_sleep_reject(PMU_instance()->hal->dev) + + Self::wake_io_reset(); + } +} diff --git a/esp-hal-common/src/rtc_cntl/sleep/mod.rs b/esp-hal-common/src/rtc_cntl/sleep/mod.rs index 97090fe9dfb..2b5fcaafa0c 100644 --- a/esp-hal-common/src/rtc_cntl/sleep/mod.rs +++ b/esp-hal-common/src/rtc_cntl/sleep/mod.rs @@ -1,7 +1,8 @@ //! # RTC Control Sleep Module //! //! ## Overview -//! The `sleep` module in the `RTC CNTL (Real-Time Clock Control)` driver +//! +//! The `sleep` module in the `RTC CNTL (Real-Time Control)` driver //! provides functionality to manage sleep and wakeup sources for `ESP` chips. //! The `RTC_CNTL` is responsible for controlling the power and sleep behavior //! of the chip. @@ -19,15 +20,20 @@ //! * `ULP (Ultra-Low Power)` wake //! * `BT (Bluetooth) wake` - light sleep only -use core::{cell::RefCell, time::Duration}; +use core::cell::RefCell; +#[cfg(any(esp32, esp32c3, esp32s3))] +use core::time::Duration; -#[cfg(esp32c3)] -use crate::gpio::RTCPinWithResistors; -use crate::{gpio::RTCPin, Rtc}; +#[cfg(any(esp32, esp32s3))] +use crate::gpio::RTCPin as RtcIoWakeupPinType; +#[cfg(any(esp32c3, esp32c6))] +use crate::gpio::RTCPinWithResistors as RtcIoWakeupPinType; +use crate::Rtc; #[cfg_attr(esp32, path = "esp32.rs")] #[cfg_attr(esp32s3, path = "esp32s3.rs")] #[cfg_attr(esp32c3, path = "esp32c3.rs")] +#[cfg_attr(esp32c6, path = "esp32c6.rs")] mod sleep_impl; pub use sleep_impl::*; @@ -40,10 +46,12 @@ pub enum WakeupLevel { } #[derive(Debug, Default, Clone, Copy)] +#[cfg(any(esp32, esp32c3, esp32s3))] pub struct TimerWakeupSource { duration: Duration, } +#[cfg(any(esp32, esp32c3, esp32s3))] impl TimerWakeupSource { pub fn new(duration: Duration) -> Self { Self { duration } @@ -57,14 +65,15 @@ pub enum Error { TooManyWakeupSources, } -#[allow(unused)] #[derive(Debug)] -pub struct Ext0WakeupSource<'a, P: RTCPin> { +#[cfg(any(esp32, esp32s3))] +pub struct Ext0WakeupSource<'a, P: RtcIoWakeupPinType> { pin: RefCell<&'a mut P>, level: WakeupLevel, } -impl<'a, P: RTCPin> Ext0WakeupSource<'a, P> { +#[cfg(any(esp32, esp32s3))] +impl<'a, P: RtcIoWakeupPinType> Ext0WakeupSource<'a, P> { pub fn new(pin: &'a mut P, level: WakeupLevel) -> Self { Self { pin: RefCell::new(pin), @@ -73,14 +82,15 @@ impl<'a, P: RTCPin> Ext0WakeupSource<'a, P> { } } -#[allow(unused)] +#[cfg(any(esp32, esp32s3))] pub struct Ext1WakeupSource<'a, 'b> { - pins: RefCell<&'a mut [&'b mut dyn RTCPin]>, + pins: RefCell<&'a mut [&'b mut dyn RtcIoWakeupPinType]>, level: WakeupLevel, } +#[cfg(any(esp32, esp32s3))] impl<'a, 'b> Ext1WakeupSource<'a, 'b> { - pub fn new(pins: &'a mut [&'b mut dyn RTCPin], level: WakeupLevel) -> Self { + pub fn new(pins: &'a mut [&'b mut dyn RtcIoWakeupPinType], level: WakeupLevel) -> Self { Self { pins: RefCell::new(pins), level, @@ -88,35 +98,40 @@ impl<'a, 'b> Ext1WakeupSource<'a, 'b> { } } +#[cfg(esp32c6)] +pub struct Ext1WakeupSource<'a, 'b> { + pins: RefCell<&'a mut [(&'b mut dyn RtcIoWakeupPinType, WakeupLevel)]>, +} + +#[cfg(esp32c6)] +impl<'a, 'b> Ext1WakeupSource<'a, 'b> { + pub fn new(pins: &'a mut [(&'b mut dyn RtcIoWakeupPinType, WakeupLevel)]) -> Self { + Self { + pins: RefCell::new(pins), + } + } +} + /// RTC_IO wakeup source /// /// RTC_IO wakeup allows configuring any combination of RTC_IO pins with /// arbitrary wakeup levels to wake up the chip from sleep. This wakeup source /// can be used to wake up from both light and deep sleep. -#[allow(unused)] +#[cfg(any(esp32, esp32c3, esp32s3))] pub struct RtcioWakeupSource<'a, 'b> { - #[cfg(xtensa)] - pins: RefCell<&'a mut [(&'b mut dyn RTCPin, WakeupLevel)]>, - #[cfg(esp32c3)] - pins: RefCell<&'a mut [(&'b mut dyn RTCPinWithResistors, WakeupLevel)]>, + pins: RefCell<&'a mut [(&'b mut dyn RtcIoWakeupPinType, WakeupLevel)]>, } +#[cfg(any(esp32, esp32c3, esp32s3))] impl<'a, 'b> RtcioWakeupSource<'a, 'b> { - #[cfg(xtensa)] - pub fn new(pins: &'a mut [(&'b mut dyn RTCPin, WakeupLevel)]) -> Self { - Self { - pins: RefCell::new(pins), - } - } - - #[cfg(esp32c3)] - pub fn new(pins: &'a mut [(&'b mut dyn RTCPinWithResistors, WakeupLevel)]) -> Self { + pub fn new(pins: &'a mut [(&'b mut dyn RtcIoWakeupPinType, WakeupLevel)]) -> Self { Self { pins: RefCell::new(pins), } } } +#[cfg(not(pmu))] bitfield::bitfield! { #[derive(Default, Clone, Copy)] pub struct WakeTriggers(u16); @@ -145,6 +160,39 @@ bitfield::bitfield! { pub bt, set_bt: 10; } +#[cfg(pmu)] +bitfield::bitfield! { + #[derive(Default, Clone, Copy)] + pub struct WakeTriggers(u16); + impl Debug; + + /// EXT0 GPIO wakeup + pub ext0, set_ext0: 0; + /// EXT1 GPIO wakeup + pub ext1, set_ext1: 1; + /// GPIO wakeup + pub gpio, set_gpio: 2; + /// WiFi beacon wakeup + pub wifi_beacon, set_wifi_beacon: 3; + /// Timer wakeup + pub timer, set_timer: 4; + /// WiFi SoC wakeup + pub wifi_soc, set_wifi_soc: 5; + /// UART0 wakeup + pub uart0, set_uart0: 6; + /// UART1 wakeup + pub uart1, set_uart1: 7; + /// SDIO wakeup + pub sdio, set_sdio: 8; + /// BT wakeup + pub bt, set_bt: 10; + + /// LP core wakeup + pub lp_core, set_lp_core: 11; + /// USB wakeup + pub usb, set_usb: 14; +} + pub trait WakeSource { fn apply(&self, rtc: &Rtc, triggers: &mut WakeTriggers, sleep_config: &mut RtcSleepConfig); } diff --git a/esp-hal-smartled/.cargo/config.toml b/esp-hal-smartled/.cargo/config.toml new file mode 100644 index 00000000000..25b35e967ab --- /dev/null +++ b/esp-hal-smartled/.cargo/config.toml @@ -0,0 +1,21 @@ +[target.xtensa-esp32s2-none-elf] +rustflags = [ + # enable the atomic codegen option for Xtensa + "-C", "target-feature=+s32c1i", + + # tell the core library have atomics even though it's not specified in the target definition + "--cfg", "target_has_atomic_load_store", + "--cfg", 'target_has_atomic_load_store="8"', + "--cfg", 'target_has_atomic_load_store="16"', + "--cfg", 'target_has_atomic_load_store="32"', + "--cfg", 'target_has_atomic_load_store="ptr"', + # enable cas + "--cfg", "target_has_atomic", + "--cfg", 'target_has_atomic="8"', + "--cfg", 'target_has_atomic="16"', + "--cfg", 'target_has_atomic="32"', + "--cfg", 'target_has_atomic="ptr"', +] + +[unstable] +build-std = [ "core" ]