feat(power): improve X4 Pro battery life - drop RC_FAST frontlight retention + add shutdown-reason RTC marker - #70
Conversation
…tention + add shutdown-reason RTC marker Prepare the FreeInk SDK for the crosspoint firmware changes that improve X4 Pro battery life. 1. Drop the RC_FAST frontlight retention path (FREEINK_FRONTLIGHT_LS). The fork only deep-sleeps (never light-sleeps), so RC_FAST retention bought nothing while it forced the X4 Pro frontlight down to 10 kHz. Removing the RC_FAST path lets the LEDC timer run on the default AUTO clock (XTAL/APB, >=40 MHz), so the OEM 25 kHz/10-bit profile is valid again. The frontlight is still cut at deep sleep by park()/releaseOnWake() pad-holds; clock-agnostic. 2. Add a stock-parity shutdown-reason RTC marker (ShutdownReason enum + setShutdownReason/takeShutdownReason). xteink_app v7.4.4 writes magic 0x58435253 (SRCX) + a u8 reason to RTC slow RAM before its power-off commit and reads/clears it at boot. RTC slow RAM survives deep sleep and most resets, so the consumer can record why the device powered off and report it at wake.
There was a problem hiding this comment.
Pull request overview
Prepares the FreeInk SDK for upcoming crosspoint firmware power-management changes by removing the RC_FAST/KEEP_ALIVE light-sleep frontlight retention approach (and its 10 kHz PWM limitation), restoring the OEM frontlight PWM frequency for X4 Pro, and introducing an RTC slow-memory shutdown-reason marker to support stock-parity power-off detection.
Changes:
- Added a shutdown-reason marker API in
PowerManagerbacked by RTC slow RAM (write + consume-once read). - Updated the
FREEINK_FRONTLIGHT_LSLEDC setup to drop RC_FAST/KEEP_ALIVE and return to auto-clock selection so 25 kHz / 10-bit works again. - Restored X4 Pro’s board-profile frontlight PWM setting to 25 kHz and updated documentation/comments around deep-sleep leakage mitigation (
park()).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| libs/hardware/PowerManager/src/PowerManager.cpp | Implements RTC slow-RAM shutdown marker read/write logic. |
| libs/hardware/PowerManager/include/PowerManager.h | Exposes new shutdown-reason API and enum. |
| libs/hardware/FrontlightManager/src/FrontlightManager.cpp | Removes RC_FAST/KEEP_ALIVE configuration; uses auto clocking and keeps deep-sleep park() behavior. |
| libs/hardware/FrontlightManager/include/FrontlightManager.h | Updates park() contract/comments and removes LS keep-alive bookkeeping fields. |
| libs/hardware/BoardConfig/include/BoardConfig.h | Restores X4 Pro frontlight PWM frequency to OEM 25 kHz / 10-bit. |
Suppressed comments (3)
libs/hardware/PowerManager/src/PowerManager.cpp:123
- The shutdown reason is documented as a single byte, but this writes a full 32-bit word to RTC slow RAM. That will zero/overwrite the other 3 bytes at 0x50000000..03, which could conflict with other RTC-slow users now or later. Use a volatile uint8_t* for the reason cell and store only the byte.
auto* magicCell = reinterpret_cast<volatile uint32_t*>(kShutdownMagicAddr);
auto* reasonCell = reinterpret_cast<volatile uint32_t*>(kShutdownReasonAddr);
// Reason first, magic last: a torn write (power cut mid-sequence) leaves NO
libs/hardware/PowerManager/src/PowerManager.cpp:134
- Same issue on read/clear: using a volatile uint32_t* for the reason cell reads and clears 4 bytes even though only the low byte is meaningful. Read/clear the reason as a uint8_t to avoid clobbering neighboring bytes in RTC slow RAM.
auto* magicCell = reinterpret_cast<volatile uint32_t*>(kShutdownMagicAddr);
auto* reasonCell = reinterpret_cast<volatile uint32_t*>(kShutdownReasonAddr);
if (*magicCell != kShutdownMagic) return 0;
const uint16_t result = static_cast<uint16_t>(((*reasonCell & 0xffu) << 8) | 1u);
// Clear both cells: the marker is consumed exactly once (stock behavior).
libs/hardware/FrontlightManager/src/FrontlightManager.cpp:106
- Similarly, the channel config currently relies on the zero-initialized sleep_mode being "not KEEP_ALIVE". Make this explicit with LEDC_SLEEP_MODE_NO_ALIVE so future enum/value changes can’t accidentally re-enable KEEP_ALIVE behavior.
ledc_channel_config_t chan = {};
chan.gpio_num = gpio;
chan.speed_mode = LEDC_LOW_SPEED_MODE;
chan.channel = static_cast<ledc_channel_t>(ch);
chan.intr_type = LEDC_INTR_DISABLE;
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| constexpr uintptr_t kShutdownMagicAddr = 0x50000004; // RTC slow RAM + 4 | ||
| constexpr uintptr_t kShutdownReasonAddr = 0x50000000; // RTC slow RAM + 0 | ||
| constexpr uint32_t kShutdownMagic = 0x58435253u; // "SRCX" little-endian |
| ledc_timer_config_t timer = {}; | ||
| timer.speed_mode = LEDC_LOW_SPEED_MODE; | ||
| timer.duty_resolution = static_cast<ledc_timer_bit_t>(bits); | ||
| timer.timer_num = LEDC_TIMER_0; | ||
| timer.freq_hz = freq; |
|
@itsthisjustin relates to what we talked about about disabling RC_FAST to keep the same refresh rate on the frontlight + other findings relating to how the stock firmware works. I plan to add to upstream an auto shutdown feature, different from sleep, akin to what the stock firmware has, and store in the slow mem the reason (as stock does it) for the sleep. |
Prepare the FreeInk SDK for the crosspoint firmware changes that improve X4 Pro battery life.
This PR includes the SDK-side changes needed for the crosspoint battery-drain improvement:
The light-sleep RC_FAST keep-alive is intentionally removed per discussion with the @itsthisjustin - upstream PR #39 compatibility is not preserved.
Crosspoint consumer-side changes (auto-shutdown timer, HalPowerManager, HalFrontlight) will follow in a separate draft PR after this merges.