wbec: suspend-to-off support (suspend mode 0xA4, 2.4.0 series)#92
Draft
evgeny-boger wants to merge 12 commits into
Draft
wbec: suspend-to-off support (suspend mode 0xA4, 2.4.0 series)#92evgeny-boger wants to merge 12 commits into
evgeny-boger wants to merge 12 commits into
Conversation
Warm reset pulses the PMIC RESET (PWROK) line for 100 ms instead of cycling the 5V rail. On WB 8.5 the line is wired to both the AXP15060 PWROK pin and the T507 RESET pin; with PMIC restart-on-PWROK-low disabled (AXP REG32[4]=0, the power-on default) the pulse resets only the SoC while all PMIC rails, including DRAM power, stay up. DRAM contents survive, so ramoops panic logs can be read back after reboot. Validated on WB 8.5.1: 16 MB test pattern survived EC-driven PWROK reset bit-perfect. The feature is gated by WBEC_HAS_WARM_RESET, defined only in config_wb85.h: the pulse behavior is hardware-validated on WB 8.5 only, so WB74 keeps its exact previous behavior (immediate hard power cycle on watchdog timeout, 2 s PMIC RESET hold on the reset_pmic request). On WB85 the watchdog escalates in two stages: the first timeout triggers a warm reset (new appended poweron reason 8, REASON_WATCHDOG_WARM); if Linux does not feed the watchdog via regmap before the next timeout, the EC falls back to the original hard power cycle (REASON_WATCHDOG). Feeding the watchdog re-arms the warm stage; a timeout-only regmap write restarts the timer but deliberately does not re-arm it. On WB85 the reset_pmic request from Linux (POWER_CTRL bit 2) now performs the short-pulse warm reset instead of the 2 s hold. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RiQPqJiM3ftEk3SDda31Kf
…light A watchdog timeout racing a 3.3V loss (or a reset_pmic request) in the same main loop pass started a second power sequence on top of the warm reset pulse already in flight. The pulse never completed, the PMIC RESET (PWROK) line stayed latched and the SoC was held in reset forever: 5V rail on, no SPL/U-Boot output ever, and no further watchdog recovery (the escalation was latched to hard resets, which did not release the line). Matches the failure observed on a WB 8.5.1 bench unit after a rapid kernel-hang reboot loop. The same race latched the line on WB74 via the reset_pmic request path. - wbec: allow only one power action per pass in WORKING; once a power sequence is started, defer the watchdog and 3.3V checks to the next passes (pending watchdog flags are not lost) - wbec: restart the escalation after a stage-2 hard reset so unfed timeouts alternate warm/hard; the first hang of every fresh boot gets a DRAM-preserving warm reset and ramoops stays readable - linux-power-control: hard_reset/hard_off/power-on always release the PMIC RESET (PWROK) line so no aborted sequence can leave it latched; a completed warm pulse turns 5V on before the power-on sequence - add wbec + linux-power-control + wdt integration test suite with a board model (PMIC/SoC/5V rail): reproduces the wedge, sweeps PMIC failure timing across all reset phases, asserts bounded recovery Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVPkc2g2yir6SGyrED6uNT
Linux writes the expected sleep duration in seconds to a new regmap region SUSPEND_CTRL (0xA4) before suspending. While the mode is active: - the 3.3V-loss monitor is skipped (the firmware suspend path powers DCDC1 down deliberately, which is otherwise indistinguishable from the 'PMIC unexpectedly off' failure); - watchdog timeouts are ignored, replaced by a deadline of the requested duration plus 10 s: a system that never wakes gets the usual warm-reset (WB85) / power-cycle (WB74) recovery. The mode ends on the first watchdog feed after resume (no extra kernel work needed), an explicit write of 0, or the deadline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The watchdog daemon keeps feeding for another second or two between the SUSPEND_CTRL write (systemd-sleep pre hook) and the actual freeze, so exiting the mode on any feed cancelled it before the sleep even began — and the 3.3V monitor then killed the suspended board as before. The feed-exit is now armed only once the EC has actually seen 3.3V drop during the announced window; aborted suspends are covered by the explicit zero-write from the post hook and by the deadline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SUSPEND_CTRL gains an off_mode flag (0xA5 bit0). In this mode the firmware puts the whole PMIC to sleep (AXP853T sleep state, only the DRAM rails stay up) after Linux freezes — nothing on the SoC can wake it, so the EC becomes the wake authority: on its RTC alarm, the suspend deadline, or a power-button press it restarts the power-on sequence, whose PWRON escalation is exactly the AXP sleep wake source (POK negative edge). The PMIC then restores the recorded rail configuration and the SoC boots through its power-on reset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… sleep An announced window whose suspend never actually happened (canceled or failed before the power cut) used to warm-reset a perfectly alive system at the deadline. If 3.3V never dropped, just leave the mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The AXP853T sleep-exit restores the recorded rail configuration but produces no reset edge, and the SoC does not start on its own when its rails return — the board sat with all rails up and the SoC dead until the watchdog recovery pulsed PWROK two minutes later. Do that pulse as part of the wake: once 3.3V is back, route the power-on sequence through the existing warm-reset pulse state instead of completing directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment out the power-on buzzer_beep in the WBEC_STATE_WORKING entry so the suspend-to-off bench soak (many recovery/reboot cycles) is not accompanied by a beep on every boot. Temporary, requested for the soak only: revert this commit and re-flash the EC to restore the beep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rtcwake -m mem -s 60 woke the board ~220 ms after it entered off-mode instead of after 60 s, even on the first cycle after boot. The off-mode wake evaluates rtc_alarm_take_fired(), which reads the software alarm_fired_latch (set from the STM32G0 RTC ALRAF flag). Both live in the backup-power domain / a firmware static and survive resets and power loss, and nothing consumes them outside off-mode. The alarm that last woke the board therefore leaves them latched, so the very first off-mode poll reads a stale match as a fresh wake and restarts the PMIC at once. The kernel-side ack of IRQ_FLAGS.RTC_ALARM (wbec.c commit 0d510d76b) clears a different latch (the host-visible irq-subsystem flags word), not alarm_fired_latch, so it never prevented this. Drain both representations when off-mode is entered: consume the software fired latch and clear the hardware ALRAF. After this the off-mode wake starts from a clean event state, so only a freshly armed alarm (or the deadline / power button) wakes the board -- rtcwake -m mem -s 60 now sleeps the full 60 s and resumes the same kernel. The wake path itself (linux_cpu_pwr_seq_wakeup) is unchanged. Add two wbec integration regression tests: a stale alarm latch must not wake the board immediately, and a fresh off-mode alarm must still wake it. Both fail without this change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The "sleep started" flag (suspend_started) is armed once the EC sees 3.3V drop during an announced window, and from then on a watchdog feed exits suspend mode (that gate stops the daemon's 1-2 s post-announce feed from cancelling the sleep before it begins). But the flag was only ever reset on the *entry* of the next announce, never on exit -- and the off-mode wake is a same-kernel resume that does not restart the EC, so the flag survived from one cycle into the next. On the bench the first rtcwake -m mem slept, but every later one exited immediately (RF2+: [EC] Suspend mode: off (request)): entering cycle 2 with the arm already set, the watchdog daemon's feed ended suspend mode during entry. Reset suspend_started in every suspend-mode exit path -- off-mode alarm/ deadline/button wake, the watchdog-fed exit, the deadline backstop and the host de-announce (both the WARM_RESET and legacy builds) -- so the arm returns to its power-on (disarmed) state after every exit and each cycle starts clean like a fresh boot. The entry reset is kept. Add wbec integration regression tests: a fresh boot arms the flag on the 3.3V drop and a suspend-mode exit must leave it disarmed (fails without this change), and a second off-mode cycle with the watchdog feeding must still sleep and wake on its alarm rather than exit on the feed. A new __unittest_env__ accessor exposes the flag. Keeps the ALRAF-drain (89947fd) and everything else intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This reverts commit 6fdf799. The working-state power-on beep was commented out only to keep the suspend-to-off bench soak quiet across its many recovery/reboot cycles; that commit was marked revert-before-merge. The soak work is done, so restore the stock beep on WBEC_STATE_WORKING entry for production. The buzzer_beep() line is byte-identical to its pre-6fdf799 form; the suspend-to-off fixes (89947fd ALRAF drain, 2ac7e28 exit-arm reset) are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The working-state beep (buzzer_beep in new_state(WBEC_STATE_WORKING)) fired on every entry to WORKING, including the suspend-to-off resume. On resume the SoC re-initializes DRAM over self-refresh (content- preserving) — a path sensitive to power/timing perturbation. The buzzer firing at that moment corrupts the resume and the board cold- boots instead of waking (hw-proven 2026-07-07: beep-on-resume -> cold boot; gated -> clean resume). Gate the beep with a suspend_resume_no_beep flag set on the suspend-to- off wake path. The board still beeps on a genuine power-up (cold start / reboot — which init DRAM from scratch and are insensitive) but stays silent on resume. This supersedes the naive f3face8 "restore beep" which reintroduced the cold-boot; the earlier "TEMP silence" was masking this timing bug. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qj3VnuyyfUAWs7qFKvVZX9
This was referenced Jul 8, 2026
Member
Author
|
Heads-up: the button double-delivery bug fixed in #93 (4421ec8) is LATENT IN THIS BASELINE too (the wbec.c running-state IRQ_PWR_OFF_REQ level-check predates the Stop branch; the D4 button matrix had simply never run). If this baseline ever ships standalone as the fallback ladder, cherry-pick that fix. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
EC side of WB8.5 suspend-to-off (2.4.0 series, validated baseline
a655128): suspend mode armed via register 0xA4 with a deadline backstop; on RTC alarm / PWRON / deadline the EC restarts the PMIC (PWRON press → V33 poll → PWROK pulse) and the SoC resumes from self-refresh DRAM.Includes: suspend mode 0xA4 + deadline; PWROK pulse on wake; RTC ALRAF drain; exit-arm reset (regression-tested); resume-beep gate.
The sleep window in this baseline is the 64 MHz busy-poll (0.212 W system floor). Two competing low-power window implementations are stacked on this branch as separate PRs: Stop 1 (feature/ec-stop-mode) and Standby heartbeat (feature/ec-suspend-standby) — bench A/B pending (gate G2), both target the measured 0.165 W floor.
Validated on the bench across 40+ suspend cycles (alarm wake exact, deadline recovery clean). The tip deliberately excludes the abandoned
ec0bef4Standby experiment (root cause of its failure — un-fed IWDG reset misclassified as resume — documented in notes/ec-stop-mode-brief.md §3).🤖 Generated with Claude Code