esp32s3/esp32s3_idle.c: unwedge the PM state machine after the first wakeup

up_idlepm() put the domain back in PM_NORMAL with pm_changestate() but
left its local oldstate holding whatever it was before sleeping, usually
PM_STANDBY.  The pm_checkstate() below then returned PM_STANDBY again,
the "newstate != oldstate" test compared PM_STANDBY against a stale
PM_STANDBY, and the whole block was skipped -- including the
esp_pmstandby() call that is the only thing in here that ever sleeps.

So after the very first wakeup the board reported PM_NORMAL essentially
forever, and light-slept only when something else happened to perturb
oldstate, such as an application taking and releasing a PM_IDLE wakelock
around a transmission window.

Measured on the esp32s3-xiao collar before this fix: 4.1 s of actual
light sleep in 2 h of near-total idleness, a 1780:1 awake-to-asleep
ratio.  After it: ~13.5% of wall time asleep, thousands of sleeps, no
storms.

The dead "newstate = PM_NORMAL" assignment that used to sit here was
presumably meant to be this; it is overwritten by pm_checkstate() a few
lines below and never had any effect.

Note that fixing this is what exposed two further bugs that had been
dormant behind a board that never slept: the systimer double-count in
esp_pmstandby(), and I2C transfers being cut in half by sleep.  Both are
fixed in their own commits.

Signed-off-by: Felipe Moura <moura.fmo@gmail.com>
Assisted-by: Claude:claude-opus-5
This commit is contained in:
Felipe Moura 2026-09-19 17:56:39 -03:00 • committed by Xiang Xiao
parent 9c84989322
commit d31609d8dd

View file

@ -104,7 +104,12 @@ static void up_idlepm(void)
/* Release the stay above: it only forces this one state change. */
pm_relax(PM_IDLE_DOMAIN, PM_NORMAL);
newstate = PM_NORMAL;
/* Without this, oldstate goes stale after the first wakeup and the
* state machine wedges in PM_NORMAL forever -- see commit message.
*/
oldstate = PM_NORMAL;
}
/* Decide, which power saving level can be obtained */