From 3c3afd04bdcb044acafc45c20f2c71863daafb44 Mon Sep 17 00:00:00 2001 From: Felipe Moura Date: Sat, 19 Sep 2026 17:56:15 -0300 Subject: [PATCH] espressif/esp_pm.c: restore GPIO config after using it as a wake source Arming a pin as a light-sleep wake source destroyed whatever it was configured as, permanently. esp_pm_gpio_wakeup_prepare() has to reconfigure each masked pin to plain INPUT and hand it to gpio_wakeup_enable(), because the wakeup path only supports level triggering. It then never put anything back. A pin that was also a normal peripheral interrupt -- a sensor's data-ready line, say -- came out of the first light sleep with its trigger mode gone and never interrupted again. Nothing failed loudly; the device just went silent. Fixed generically rather than per-board: - esp_configgpio() now remembers the last attr applied to each pin, and a new esp_getconfiggpio() hands it back. This is what lets the PM code restore a pin without having to know what the pin is for. - esp_pm_gpio_wakeup_prepare() saves each masked pin's attr before overwriting it, and a new esp_pm_gpio_wakeup_restore() puts it back as soon as esp_pm_light_sleep_start() returns. Tied to the physical sleep/wake cycle deliberately, not to PM state transitions. An earlier attempt used a board-level pm_register()/notify() callback and never fired at all, because the board sits in PM_STANDBY without transitioning back to PM_NORMAL -- there is no state change to hang the restore on. The return from esp_pm_light_sleep_start() is the one event that always happens exactly once per sleep. Assisted-by: Claude:claude-opus-5 Signed-off-by: Felipe Moura --- arch/xtensa/src/common/espressif/esp_gpio.c | 32 +++++++++++++++++ arch/xtensa/src/common/espressif/esp_gpio.h | 19 ++++++++++ arch/xtensa/src/common/espressif/esp_pm.c | 39 +++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/arch/xtensa/src/common/espressif/esp_gpio.c b/arch/xtensa/src/common/espressif/esp_gpio.c index d2593ca0cdd..2c08597e819 100644 --- a/arch/xtensa/src/common/espressif/esp_gpio.c +++ b/arch/xtensa/src/common/espressif/esp_gpio.c @@ -72,6 +72,13 @@ static gpio_hal_context_t g_gpio_hal = .dev = GPIO_HAL_GET_HW(GPIO_PORT_0) }; +/* Mirrors the attr last applied to each pin via esp_configgpio(), so a + * caller that has to temporarily reconfigure a pin (e.g. PM wake-source + * setup) can read it back and restore it afterward. + */ + +static gpio_pinattr_t g_gpio_lastattr[SOC_GPIO_PIN_COUNT]; + /**************************************************************************** * Private Functions ****************************************************************************/ @@ -138,6 +145,8 @@ int esp_configgpio(int pin, gpio_pinattr_t attr) { DEBUGASSERT(pin >= 0 && pin < SOC_GPIO_PIN_COUNT); + g_gpio_lastattr[pin] = attr; + /* Handle input pins */ if ((attr & INPUT) != 0) @@ -191,6 +200,7 @@ int esp_configgpio(int pin, gpio_pinattr_t attr) if ((attr & DRIVE_MASK) != 0) { uint32_t val = ((attr & DRIVE_MASK) >> DRIVE_SHIFT) - 1; + gpio_hal_set_drive_capability(&g_gpio_hal, pin, val); } else @@ -202,6 +212,7 @@ int esp_configgpio(int pin, gpio_pinattr_t attr) if ((attr & FUNCTION_MASK) != 0) { uint32_t val = ((attr & FUNCTION_MASK) >> FUNCTION_SHIFT) - 1; + gpio_hal_func_sel(&g_gpio_hal, pin, val); } else @@ -212,6 +223,27 @@ int esp_configgpio(int pin, gpio_pinattr_t attr) return OK; } +/**************************************************************************** + * Name: esp_getconfiggpio + * + * Description: + * Return the attr last passed to esp_configgpio() for this pin. + * + * Input Parameters: + * pin - GPIO pin to query. + * + * Returned Value: + * The attr last passed to esp_configgpio() for this pin. + * + ****************************************************************************/ + +gpio_pinattr_t esp_getconfiggpio(int pin) +{ + DEBUGASSERT(pin >= 0 && pin < SOC_GPIO_PIN_COUNT); + + return g_gpio_lastattr[pin]; +} + /**************************************************************************** * Name: esp_gpio_matrix_in * diff --git a/arch/xtensa/src/common/espressif/esp_gpio.h b/arch/xtensa/src/common/espressif/esp_gpio.h index a046eb72583..52b3eb21783 100644 --- a/arch/xtensa/src/common/espressif/esp_gpio.h +++ b/arch/xtensa/src/common/espressif/esp_gpio.h @@ -157,6 +157,25 @@ extern "C" int esp_configgpio(int pin, gpio_pinattr_t attr); +/**************************************************************************** + * Name: esp_getconfiggpio + * + * Description: + * Return the attr last passed to esp_configgpio() for this pin, so a + * caller that has to temporarily reconfigure a pin (e.g. PM wake-source + * setup) can restore it afterward without needing its own knowledge of + * what the pin is normally used for. + * + * Input Parameters: + * pin - GPIO pin to query. + * + * Returned Value: + * The attr last passed to esp_configgpio() for this pin. + * + ****************************************************************************/ + +gpio_pinattr_t esp_getconfiggpio(int pin); + /**************************************************************************** * Name: esp_gpio_matrix_in * diff --git a/arch/xtensa/src/common/espressif/esp_pm.c b/arch/xtensa/src/common/espressif/esp_pm.c index 7001e23c71a..f35ee2dfae9 100644 --- a/arch/xtensa/src/common/espressif/esp_pm.c +++ b/arch/xtensa/src/common/espressif/esp_pm.c @@ -446,6 +446,8 @@ static uint64_t IRAM_ATTR esp_pm_get_gpio_mask(void) * ****************************************************************************/ +static gpio_pinattr_t g_gpio_wakeup_saved[CONFIG_SOC_GPIO_PIN_COUNT]; + static void IRAM_ATTR esp_pm_gpio_wakeup_prepare(void) { uint64_t mask_value = esp_pm_get_gpio_mask(); @@ -461,6 +463,14 @@ static void IRAM_ATTR esp_pm_gpio_wakeup_prepare(void) pin_mask = BIT(i); if ((mask_value & pin_mask) != 0) { + /* This pin may also be a normal edge-triggered peripheral IRQ + * (e.g. a sensor's data-ready line) -- gpio_wakeup_enable() + * below only supports level triggering, so remember whatever + * esp_configgpio() had last set here and put it back in + * esp_pm_gpio_wakeup_restore() once this sleep is over. + */ + + g_gpio_wakeup_saved[i] = esp_getconfiggpio(i); esp_configgpio(i, INPUT); gpio_wakeup_enable(i, level_mode); } @@ -468,6 +478,31 @@ static void IRAM_ATTR esp_pm_gpio_wakeup_prepare(void) esp_sleep_enable_gpio_wakeup(); } + +/**************************************************************************** + * Name: esp_pm_gpio_wakeup_restore + * + * Description: + * Undo esp_pm_gpio_wakeup_prepare(): restore each wake-source gpio to + * whatever esp_configgpio() had it set to before this sleep. + * + ****************************************************************************/ + +static void IRAM_ATTR esp_pm_gpio_wakeup_restore(void) +{ + uint64_t mask_value = esp_pm_get_gpio_mask(); + int pin_mask = 0; + + for (int i = 0; i < CONFIG_SOC_GPIO_PIN_COUNT; i++) + { + pin_mask = BIT(i); + if ((mask_value & pin_mask) != 0) + { + gpio_wakeup_disable(i); + esp_configgpio(i, g_gpio_wakeup_saved[i]); + } + } +} #endif /* CONFIG_PM_GPIO_WAKEUP */ #ifdef CONFIG_PM_UART_WAKEUP @@ -777,6 +812,10 @@ void esp_pmstandby(uint64_t time_in_us) esp_pm_light_sleep_start(&rtc_diff_us); +#ifdef CONFIG_PM_GPIO_WAKEUP + esp_pm_gpio_wakeup_restore(); +#endif + /* Only step the clock where the systimer actually stalls during sleep * (SOC_SLEEP_SYSTIMER_STALL_WORKAROUND); elsewhere it keeps counting * through light sleep, so stepping it here would double-count the time.