Skip to content

Correct AXP2101 register addresses, fields, and shutdown accesses - #171

Merged
jonasniesner merged 1 commit into
OpenDisplay:mainfrom
EdenNelson:fix/axp2101-register-access
Oct 11, 2026
Merged

jonasniesner merged 1 commit into
OpenDisplay:mainfrom
EdenNelson:fix/axp2101-register-access

Conversation

@EdenNelson

Copy link
Copy Markdown
Contributor

Fixes #170

Summary

Follow-up to #155. I checked every AXP2101 register access in the firmware against the AXP2101 datasheet. Several used addresses or bits the AXP2101 does not have. This corrects them in place and removes the accesses that have no AXP2101 register to land on. It adds no new functions.

The observable runtime changes are concentrated in powerDownAXP2101(), which runs at every panel shutdown. The live #155 battery helper also sanitizes REG30 reserved bits when it enables the VBAT channel. readAXP2101Data() remains uncalled, and the duplicate main.h constants remain unused.

This patch is for register correctness only. It intentionally does not change the ALDO3 shutdown policy, so the post-shutdown I²C failure seen with #155's reader remains; that is a separate follow-up.

What changed

Datasheet references are PDF page numbers.

powerDownAXP2101()

  • IRQ status was cleared at 0x44–0x46. The AXP2101's status registers are 0x48–0x4A (p.43–44), and they are now the ones written. 0x4A gets 0xDF because its bit 5 is reserved.
  • The writes to a fourth IRQ-enable register (0x43) and a fourth IRQ-status register (0x47) are removed, along with their constants. The AXP2101 has three of each, and 0x43–0x47 are not in its register map. I traced where the four-register layout came from: its names and count (IRQ_ENABLE1–4, IRQ_STATUS1–4) match Allwinner's AXP8191 PMU definitions, and its status addresses match the AXP192, whose REG43 enables N_OE, VBUS-session and APS low-voltage IRQs that the AXP2101 does not have. On the AXP2101 these writes reach no register, so they cannot do what they were written to do. They also never fail: the bus acknowledges them, so nothing reported an error.
  • REG26 reserved bit 7 is no longer set (p.37).
  • REG90 is now written 0x00. This is a deliberate runtime behavior change. REG91 holds only the DLDO2 enable (p.53), but its write was logged as disabling "BLDO1-2, CPUSLDO, DLDO1-2". The BLDO1, BLDO2, CPUSLDO and DLDO1 enables are REG90 bits 4–7 (p.53), which the shutdown preserved even though its log claimed those rails were disabled. Writing 0x00 now disables BLDO1, BLDO2, CPUSLDO and DLDO1 as well as ALDO1–4 (ALDO1–4 were already cleared). The datasheet establishes where those enables live; turning them off follows the intent stated by the existing shutdown code. Those outputs are unconnected on the tested PhotoPainter, so no board-level effect was observed. Other AXP2101 hardware could observe the difference.
  • Each rail log line now states what its own write did: DLDO2 disabled (REG91) and ALDO1-4, BLDO1-2, CPUSLDO, DLDO1 disabled (REG90).

axp2101_adc_enable_bit0() (#155 battery reader, live): REG30 reserved bits 7:6 are written as 0 instead of echoed back (p.39). On conforming silicon they read as 0, so this is defensive. One named constant is added, AXP2101_ADC_CTRL_RESERVED_MASK.

readAXP2101Data() (uncalled diagnostic), corrected line by line:

Item Before After
VBUS 0x36, which is the TS pin 0x38/0x39 (p.40–41)
VSYS 0x38, which is VBUS 0x3A/0x3B (p.41)
Voltage decode 12-bit, 0.5 / 1.7 / 1.4 mV per count (AXP192 conventions) 14-bit (H & 0x3F) << 8 | L, 1 mV per count (p.40–41)
REG30 0xFF: all channels plus reserved bits 7:6 0x3F: all channels, reserved bits 0 (p.39)
Battery / VBUS present REG00 bits swapped bit 3 / bit 5 (p.31)
"Charging" REG00 bit 2 ("Battery in Active Mode") REG01 bits 6:5 (p.31)

src/main.h: the unused duplicate AXP2101 constant block gets the same corrections: VBUS/SYS ADC addresses, POWER_ON_STATUS/POWER_OFF_STATUS at 0x20/0x21, IRQ status at 0x48–0x4A, the fourth IRQ registers removed, and the REG91 comment.

The diagnostic and main.h changes do not affect the production binary's control flow. They keep retained AXP2101 code and duplicate definitions from preserving a second, incorrect register map that could be reused later.

Comments and tests: #155's comments describing the diagnostic's now-fixed defects are updated, and REG34 bits 7:6 are described as ch_dbg_en rather than "unused". In tools/test_sensor_axp2101.cpp, only the two expectations affected by the reserved-bit mask change (0xFE -> 0x3F, 0xA5 -> 0x25).

Why these changes are correct. Each register address and field follows the AXP2101 datasheet. ESPHome test-bed work on the PhotoPainter reads REG00 bits 3 and 5, REG01 bits 6:5, VBAT at 0x34/0x35 and VBUS at 0x38/0x39 with the 14-bit decode, REG30 bit 2, and battery percentage at 0xA4, and the datasheet matched the hardware on every one of them. VSYS, the IRQ status registers and REG26 could not be exercised on hardware here; those corrections follow the same datasheet, which has proven reliable on every register we could test.

Unchanged: the REG26 sleep/wake sequence apart from bit 7 (it matches the datasheet's Figure 6-2, with GPIO21 driving the IRQ-pin wake; checked against the datasheet, not hardware-tested), DCDC1 and ALDO3/ALDO4 setup, the IRQ enables 0x40–0x42, the REG80 rail disable, REG30 = 0x00 at shutdown, battery percentage (0xA4), power-on status (0x20), and #155's battery-present and VBAT reads.

There is no new extern, no heap allocation, no new caller, and no new periodic work.

Performance

Shutdown does two fewer I²C writes (the removed IRQ registers) and one fewer read (REG90 is written directly). RAM is unchanged on every target. Flash is smaller on every target, for example esp32-s3-N16R8 1,529,338 bytes vs 1,529,418 (-80).

Testing

Each layer proves something different:

  • Host tests: Report battery voltage from a configured AXP2101 #155's 49 checks pass under -fsanitize=undefined,address with -Wall -Wextra -Werror, with the two expectations above updated. They exercise the AXP2101 battery helper only, not powerDownAXP2101() or the diagnostic's Wire transaction sequence.
  • Builds: locally built all 12 environments from .github/firmware-targets.json, including esp32-wrover-e-N4R8, in production and diagnostic profiles. All succeeded with no new warnings.
  • Hardware, diagnostic build: validates the corrected decoded values and successful execution of the same patch when readAXP2101Data() is temporarily called by a diagnostic-only commit.
  • Hardware, production build: validates the normal boot-to-shutdown path.

The hardware cycle uses the same four-state pattern as #155. The diagnostic-only commit, not part of this PR, calls readAXP2101Data() once at the end of initAXP2101(), its original position from b53c778. The fix has the same patch ID applied alone and on top of it.

State Contents Profile
00-main Upstream 3716925 Production
01-main-diag Main + diagnostic call Diagnostic
02-fix-diag Main + diagnostic call + this fix Diagnostic
03-fix-main Main + this fix Production

Observed during boot-to-shutdown validation on the Waveshare ESP32-S3-PhotoPainter (esp32-s3-N16R8, the only AXP2101 board in the catalog), on USB-C with the battery attached, after a full power drain:

  • The corrected shutdown sequence completed without an I²C error and emitted the expected logs: All IRQs disabled and status cleared, DLDO2 disabled, and ALDO1-4, BLDO1-2, CPUSLDO, DLDO1 disabled. No other shutdown line changed against 01. IRQ status registers were not read back after clearing.
  • Diagnostic (02): battery present and VBUS present Yes, charge state standby (REG01 0x14, battery full), VBAT 4171 mV against Report battery voltage from a configured AXP2101 #155's reader 4169 mV in the same boot. Report battery voltage from a configured AXP2101 #155's reader logged adc_ctrl=0x3F -> 0x3F, so the reserved bits read back as 0.
  • After a power-on reset, the diagnostic read VBUS and VSYS as 0 mV despite its existing 10 ms post-enable delay. The cause was not investigated because the diagnostic is uncalled and its conversion timing is unchanged by this PR. In an earlier run where the channels were already enabled, the corrected addresses reported VBUS 5191 mV and VSYS 5170 mV.
  • Production (00 vs 03): baseline and fixed builds followed the same successful boot, refresh, shutdown, and BLE-startup control flow. Refresh duration and other live values varied between captures.

Negative devices: Seeed XIAO ESP32-C3 7.5" kit (esp32-c3-N4) and Seeed EE04 ESP32-S3 (esp32-s3-N16R8). Neither has an AXP2101 configured, so these runs show configuration isolation and no regression on those boards; they do not exercise AXP2101 registers. Both followed the same control flow in every state; only live values such as the MSD sensor byte varied.

Not tested: deep-sleep wake, BLE transfers, register readback after the shutdown writes, and long-term behavior beyond the boot-to-shutdown window.

Scope boundaries

Each of these will follow in its own issue and pull request:

  • Post-shutdown I²C lifecycle. powerDownAXP2101() still disables ALDO3. On the PhotoPainter this makes the shared I²C bus unavailable, so the battery reader added by Report battery voltage from a configured AXP2101 #155 fails after panel shutdown. A separate follow-up will keep ALDO3 enabled. This PR intentionally leaves that policy unchanged, including ALDO2.
  • Fail-closed read/modify/write. Several AXP2101 updates continue to a write after a failed or short read. Fixing that needs new error-handling code, so it will follow separately from this register-correctness change.
  • Configured address and bus. initAXP2101(), powerDownAXP2101() and readAXP2101Data() hardcode 0x34 and use ambient Wire state, unlike Report battery voltage from a configured AXP2101 #155's reader.
  • State commit. pwrmgm() commits the power state before the PMIC transition, and both helpers return void.

AI disclosure: developed with AI assistance (GitHub Copilot and other AI harnesses), with a human reviewing every stage. All hardware observations were made on physical devices by the contributor, and the raw serial, build, and flash logs are retained.

🤖 Generated with Claude Code

…t register

Checked every AXP2101 register access in the firmware against the AXP2101
datasheet. Several used addresses or bits the AXP2101 does not have. This
corrects them in place and removes the accesses that have nothing to land
on. Where ESPHome test-bed work on the PhotoPainter reads the same
registers (REG00, REG01, VBAT, VBUS, REG30 bit 2, battery percentage), the
datasheet matched the hardware. The other registers follow the same
datasheet.

powerDownAXP2101() (runs at every panel shutdown):
  - IRQ status was cleared at 0x44-0x46. The AXP2101's IRQ status
    registers are 0x48-0x4A. 0x4A is written 0xDF because its bit 5 is
    reserved.
  - It also wrote a fourth IRQ-enable register (0x43) and a fourth
    IRQ-status register (0x47). The AXP2101 has three of each; these
    appear to be vestigial from older PMIC support. The writes were
    non-functional and non-erroring: the bus acknowledged them, so
    nothing reported a failure. Removed, with their constants.
  - REG26 reserved bit 7 was set. No longer set.
  - REG91 holds only the DLDO2 enable, but its write was logged as
    disabling BLDO1-2, CPUSLDO and DLDO1-2. Those other enables are REG90
    bits 4-7, which the shutdown preserved. REG90 is now written 0x00 so
    the stated disables happen, and each log line states what its own
    write did.

readAXP2101Data() (uncalled diagnostic, flagged in OpenDisplay#155):
  - "VBUS" was read from 0x36 (the TS pin); VBUS is 0x38/0x39.
  - "System" was read from 0x38 (VBUS); VSYS is 0x3A/0x3B.
  - All three voltages used a 12-bit decode with 0.5/1.7/1.4 mV steps.
    The AXP2101 is 14-bit, (hi & 0x3F) << 8 | lo, at 1 mV per count.
  - REG30 was written 0xFF to enable all ADC channels, which also set
    reserved bits 7:6. Now 0x3F.
  - Battery presence and VBUS presence read swapped REG00 bits.
  - "Charging" read REG00 bit 2 (Battery in Active Mode). Charge
    direction is REG01 bits 6:5.
  The function remains uncalled.

axp2101_adc_enable_bit0() (OpenDisplay#155 battery reader): REG30 reserved bits 7:6
are written as 0 instead of echoed back. Two host-test expectations
follow. OpenDisplay#155's comments describing the diagnostic's now-fixed defects,
and calling REG34 bits 7:6 "unused" (they are ch_dbg_en), are corrected.

src/main.h: the unused duplicate AXP2101 block, left behind when fd0d73a
moved these functions to display_service.cpp, gets the same corrections:
VBUS/SYS ADC addresses, PWRON/PWROFF status at 0x20/0x21, IRQ status at
0x48-0x4A, the fourth IRQ registers removed, and the REG91 comment.

The REG26 and REG80/90/91 handling follows the datasheet's sleep and wake
sequence (Figure 6-2) and is otherwise unchanged. Not changed: rail policy
beyond what the code already states (including ALDO3), fail-closed
read/modify/write, the hardcoded 0x34 address and ambient Wire use, and
when power state is committed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 7, 2026 16:48
@jonasniesner
jonasniesner merged commit 3b6de4e into OpenDisplay:main Oct 11, 2026
13 checks passed
@jonasniesner

Copy link
Copy Markdown
Member

Thank you for your contribution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Correct invalid AXP2101 register addresses and fields (follow-up to #155)

2 participants