diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 1cc1a1c828..e04e0c55ff 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -359,6 +359,26 @@ void cpuDeepSleep(uint32_t msecToWake) Note: we don't isolate pins that are used for the LORA, LED, i2c, or ST7735 Display for the Chatter2, spi or the wake button(s), maybe we should not include any other GPIOs... */ + // The pad deep sleep wakes on. A variant whose button is not an RTC IO can nominate a different one + // with DEEP_SLEEP_WAKE_PIN; that replaces the button rather than joining it, because + // esp_sleep_enable_ext1_wakeup() validates the whole mask and arms nothing if any pin in it lacks + // RTC function, so one bad pin would veto the good one. +#if defined(DEEP_SLEEP_WAKE_PIN) + const int wakeButton = DEEP_SLEEP_WAKE_PIN; +#elif defined(BUTTON_PIN) + const int wakeButton = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; +#else + const int wakeButton = -1; +#endif +#if defined(BUTTON_PIN) || defined(DEEP_SLEEP_WAKE_PIN) + // Which level ext1 is armed for below, so the pull we hold the pad at is the idle one. +#if defined(ESP32S3_WAKE_TYPE) + const bool wakeOnHigh = (ESP32S3_WAKE_TYPE == ESP_EXT1_WAKEUP_ANY_HIGH); +#else + const bool wakeOnHigh = false; +#endif +#endif + #if SOC_RTCIO_HOLD_SUPPORTED static const uint8_t rtcGpios[] = { #ifndef HELTEC_VISION_MASTER_E213 @@ -371,16 +391,6 @@ void cpuDeepSleep(uint32_t msecToWake) #endif 34, 35, 37}; - // A variant whose button is not an RTC IO can nominate a different pad here. This replaces the - // button rather than joining it: esp_sleep_enable_ext1_wakeup() validates the whole mask and - // arms nothing if any pin in it lacks RTC function, so one bad pin would veto the good one. -#if defined(DEEP_SLEEP_WAKE_PIN) - const int wakeButton = DEEP_SLEEP_WAKE_PIN; -#elif defined(BUTTON_PIN) - const int wakeButton = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; -#else - const int wakeButton = -1; -#endif // Isolating a pad holds it, and ext1 cannot re-arm a held pad - skip the pin we wake on. for (int i = 0; i < sizeof(rtcGpios); i++) { if (rtcGpios[i] != wakeButton) @@ -404,13 +414,37 @@ void cpuDeepSleep(uint32_t msecToWake) #ifdef ALT_BUTTON_WAKE gpioMask |= (1ULL << BUTTON_PIN_ALT); #endif -#ifdef BUTTON_NEED_PULLUP - gpio_pullup_en((gpio_num_t)BUTTON_PIN); + // Hold the wake pad at its idle level for the whole sleep. Two reasons, and the second is the one + // that bites: ext1 samples the line, and on a strapping pin the ROM reads it again at the reset that + // ends the sleep - GPIO0 is the ESP32 download-mode strap, so a pad left floating can bring the chip + // up in ROM download mode instead of booting the firmware, which needs power physically removed to + // clear. ~40 variants use GPIO0 as BUTTON_PIN. This was previously gated on BUTTON_NEED_PULLUP and + // applied to BUTTON_PIN rather than to the pin actually armed; an internal pull is a few hundred nA, + // less than the leakage the gate was there to avoid. + // + // Both register sets are written on purpose: the digital pull covers the window before the sleep, + // the RTC pull is the one that holds once the RTC domain owns the pad. + if (wakeButton >= 0) { + const gpio_num_t wakePad = (gpio_num_t)wakeButton; + if (wakeOnHigh) { + gpio_pullup_dis(wakePad); + gpio_pulldown_en(wakePad); + } else { + gpio_pulldown_dis(wakePad); + gpio_pullup_en(wakePad); + } +#if SOC_RTCIO_HOLD_SUPPORTED + if (rtc_gpio_is_valid_gpio(wakePad)) { + if (wakeOnHigh) { + rtc_gpio_pullup_dis(wakePad); + rtc_gpio_pulldown_en(wakePad); + } else { + rtc_gpio_pulldown_dis(wakePad); + rtc_gpio_pullup_en(wakePad); + } + } #endif - - // Not needed because both of the current boards have external pullups - // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead - // of just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); + } #if SOC_PM_SUPPORT_EXT1_WAKEUP gpioMask = filterExt1WakeMask(gpioMask); diff --git a/src/sleep.cpp b/src/sleep.cpp index 8bad92c78d..dfae191463 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -311,13 +311,28 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN #ifdef BUTTON_PIN // Avoid leakage through button pin if (GPIO_IS_VALID_OUTPUT_GPIO(BUTTON_PIN)) { +#if defined(DEEP_SLEEP_WAKE_PIN) + const int deepSleepWakePin = DEEP_SLEEP_WAKE_PIN; +#else + const int deepSleepWakePin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; +#endif #ifdef BUTTON_NEED_PULLUP pinMode(BUTTON_PIN, INPUT_PULLUP); #else - pinMode(BUTTON_PIN, INPUT); +#if defined(ESP32S3_WAKE_TYPE) + const bool wakeOnHigh = (ESP32S3_WAKE_TYPE == ESP_EXT1_WAKEUP_ANY_HIGH); +#else + const bool wakeOnHigh = false; +#endif + // Leaving the pad with no pull is only safe for a pin we are not waking on, which gpio_hold_en() + // latches below. The pin ext1 does wake on has to hold its idle level instead: the ROM reads a + // strapping pin again at the reset that ends the sleep, and GPIO0 - BUTTON_PIN on ~40 variants - + // is the download-mode strap, so a floating pad can come back up in ROM download mode. + // cpuDeepSleep() asserts the same pull in the RTC registers, which are what hold through the sleep. + pinMode(BUTTON_PIN, BUTTON_PIN != deepSleepWakePin ? INPUT : (wakeOnHigh ? INPUT_PULLDOWN : INPUT_PULLUP)); #endif // A held pad ignores ext1_wakeup_prepare()'s re-route to RTC, so never hold the pin we wake on. - if (config.device.button_gpio && config.device.button_gpio != BUTTON_PIN) + if (BUTTON_PIN != deepSleepWakePin) gpio_hold_en((gpio_num_t)BUTTON_PIN); } #endif