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 <moura.fmo@gmail.com>
This commit is contained in:
Felipe Moura 2026-09-19 17:56:15 -03:00 • committed by Alan C. Assis
parent 4818198a0d
commit 3c3afd04bd
3 changed files with 90 additions and 0 deletions

View file

@ -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
*

View file

@ -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
*

View file

@ -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.