[PATCH AUTOSEL 6.18-6.6] mfd: axp20x: Preserve other control bits when powering off

From: Sasha Levin

Date: Mon Oct 19 2026 - 14:20:16 EST


From: Oleg Proshkin <oleg.pro171@xxxxxxxxx>

[ Upstream commit 3756bf05c963563f1325af105356541882dd5899 ]

axp20x_power_off() triggers shutdown by writing AXP20X_OFF (BIT(7)) to
the power-off control register with regmap_write(), which rewrites the
whole register and clears other control bits in it.

On the AXP221/AXP223 (and the register-compatible AXP228) - that
register also holds the CHGLED auto-control bit.
Clearing it during an orderly shutdown disables the hardware charge
indicator, so the charge LED stays dark while the board is powered off
and charging. Other variants keep unrelated configuration in the same
register too.

Set only the power-off bit with regmap_set_bits() and leave the rest of
the register untouched. The shutdown register is readable on every
variant, so the read-modify-write should be safe.

Tested on a ClockworkPi uConsole (Raspberry Pi Compute Module 4, AXP228,
which enumerates as AXP221): register AXP20X_OFF_CTRL reads 0x08 at
runtime, so the old code left it 0x80 whereas setting only BIT(7)
leaves 0x88. Writing 0x88 at power-off enables the charge LED
while still powering off the PMIC.

Signed-off-by: Oleg Proshkin <oleg.pro171@xxxxxxxxx>
Reviewed-by: Chen-Yu Tsai <wens@xxxxxxxxxx>
Link: https://patch.msgid.link/20260611154629.76607-1-oleg.pro171@xxxxxxxxx
Signed-off-by: Lee Jones <lee@xxxxxxxxxx>
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
---

LLM Generated explanations, may be completely bogus:

Connection lost, reconnecting to https://agentn.us.api5.cursor.sh
(attempt 1)...
Retry attempt 1...
Connection lost, reconnecting to https://agentn.us.api5.cursor.sh
(attempt 2)...
Retry attempt 2...
Connection lost, reconnecting to https://agentn.us.api5.cursor.sh
(attempt 3)...
Retry attempt 3...
Connection lost, reconnecting to https://agentn.us.api5.cursor.sh
(attempt 4)...
Retry attempt 4...
Connection lost, reconnecting to https://agentn.us.api5.cursor.sh
(attempt 5)...
Retry attempt 5...
This is a 6.1.y tree (Makefile 6.1.188, `pending-6.1`, `git describe`
`p-6.1-9-g27ef8181b8a64`). The candidate is not an ancestor of HEAD. The
buggy full-register write is present in this tree’s
`axp20x_power_off()`.

## Phase 1: Commit message forensics

**Step 1.1 — Subject**
Record: **[mfd/axp20x] [Preserve] Keep other bits in the PMIC shutdown
register when setting the power-off bit**

**Step 1.2 — Tags**
Record:
- Reviewed-by: Chen-Yu Tsai `<wens@xxxxxxxxxx>` (Allwinner/sunXi
maintainer)
- Link: `https://patch.msgid.link/20260611154629.76607-1-
oleg.pro171@xxxxxxxxx`
- Signed-off-by: Oleg Proshkin (author); Lee Jones (MFD maintainer)
- No Fixes:, Reported-by, Tested-by, Cc: stable, or syzbot tags (absence
is expected)

**Step 1.3 — Body**
Record:
- Bug: `axp20x_power_off()` uses `regmap_write(..., AXP20X_OFF)` (BIT(7)
only), which zeros every other bit in the shutdown/CHGLED control
register.
- Symptom: On AXP221/AXP223/AXP228, CHGLED auto-control is cleared, so
the charge LED stays dark while the board is powered off and charging.
Author notes other variants store unrelated config in the same
register.
- Root cause: whole-register write instead of set-bit RMW.
- Evidence: ClockworkPi uConsole (CM4 + AXP228 as AXP221); runtime
`AXP20X_OFF_CTRL` is 0x08; old write produces 0x80, bit-set produces
0x88 and the LED works.
- Author states the register is readable on every variant, so RMW is
safe.

**Step 1.4 — Hidden bug fix?**
Record: Not hidden. Explicit hardware-correctness fix (register clobber
at shutdown).

## Phase 2: Diff analysis

**Step 2.1 — Inventory**
Record: `drivers/mfd/axp20x.c` only; 1 insertion, 1 deletion. Function:
`axp20x_power_off`. Surgical single-file change.

**Step 2.2 — Code flow**
Record: Before: write 0x80 to the shutdown register, clearing bits 6–0.
After: `regmap_set_bits()` ORs BIT(7) and leaves the rest. Shutdown path
only (orderly power-off).

**Step 2.3 — Bug mechanism**
Record: **Logic/correctness + hardware control-register clobber.**
`regmap_write` of a single bit value wipes CHGLED/battery-detect/N_OE-
related fields in REG 0x32. Fix is RMW via `regmap_set_bits`.

**Step 2.4 — Fix quality**
Record: Obviously correct one-line change. `regmap_set_bits` in this
tree is `regmap_update_bits_base(map, reg, bits, bits, ...)`. Regression
risk is low: one extra cached RMW at shutdown. 0x32 is not in axp22x
volatile ranges (0x04–0x4c is writeable; 0x32 is not volatile), so RMW
can use the cache. Mainline context (`sys_off_data`, AXP313 switch) is
not in 6.1, so the raw patch does not apply; the equivalent change is
still one write→set_bits.

## Phase 3: Git history

**Step 3.1 — Blame**
Record: The full-register write is from **cfb61a419630a8** (“mfd:
AXP20x: Add mfd driver for AXP20x PMIC”, Carlo Caione, 2014-05-01, first
appeared **v3.16-rc1**). Present for the entire 6.1 lifetime. AXP288
skip is af7e9069543aab (2014). Delay is 179dc63d06c51 (2016);
`msleep`→`mdelay` is 3bcc06c5fd484 (already in this tree).

**Step 3.2 — Fixes: tag**
Record: None. Introducer is cfb61a419630a8, which **is** an ancestor of
HEAD.

**Step 3.3 — File history**
Record: Recent 6.1 file history is unrelated (GPIO cells, AXP288 ranges,
`mdelay` in power-off). No prior CHGLED/bit-preserve fix. Standalone,
not patch x/y.

**Step 3.4 — Author**
Record: Oleg Proshkin has no other `drivers/mfd/` commits in this tree.
Reviewer Chen-Yu Tsai is ARM/Allwinner sunXi maintainer; Lee Jones is
MFD maintainer.

**Step 3.5 — Dependencies**
Record: Mainline uses `sys_off_data` (v6.3) and AXP313a register select
(v6.5). Those are **not** required for the fix. 6.1 already has
`regmap_set_bits` and `AXP20X_OFF_CTRL`. Standalone with a 1-line
context adaptation.

## Phase 4: Mailing list / external

**Step 4.1 — Original discussion**
Record: `b4 dig -c 3756bf05c96356` →
https://patch.msgid.link/20260611154629.76607-1-oleg.pro171@xxxxxxxxx
`b4 dig -a`: single **v1**, no later revisions.
Chen-Yu Tsai: “Makes sense.” + Reviewed-by (openwall 2026-06-12).
Lee Jones applied 2026-06-18 (lkml.iu.edu); no NAK, no stable-risk
comments. lore.kernel.org fetches returned 403; discussion recovered via
b4 and mirrors.

**Step 4.2 — Recipients (`b4 dig -w`)**
Record: Oleg Proshkin, Chen-Yu Tsai, Lee Jones, linux-kernel@. Right
maintainers were on the patch.

**Step 4.3 — Bug report**
Record: No syzbot/bugzilla. Hardware test is in the commit (ClockworkPi
uConsole, measured 0x08 vs 0x80 vs 0x88).

**Step 4.4 — Series**
Record: Standalone 1/1. No dependents.

**Step 4.5 — Stable list**
Record: lore.kernel.org/stable returned 403. No evidence this was
rejected for stable. UNVERIFIED: no stable-ML thread found.

## Phase 5: Semantic / call graph

**Step 5.1 — Functions**
Record: `axp20x_power_off` only.

**Step 5.2 — Callers (this tree)**
Record: Probe sets `pm_power_off = axp20x_power_off` when unset. Invoked
from the kernel power-off path (`kernel_power_off` /
`do_kernel_power_off`). Not IRQ, not a hot path. AXP288 returns
immediately (same as mainline skipping that variant).

**Step 5.3 — Callees**
Record: `regmap_write` today; `regmap_set_bits` after the fix;
`mdelay(500)`.

**Step 5.4 — Reachability**
Record: Yes — `shutdown`/`poweroff` from userspace. Affects
AXP221/223/202/209/803/809/813/806/152 boards that register this
handler. AXP221 is a first-class compatible in `axp20x-i2c.c`.

**Step 5.5 — Similar patterns**
Record: Power-supply code in this tree does not program
`AXP20X_OFF_CTRL`; CHGLED bits come from hardware/boot default (0x08
matches bit 3). No sibling fix already present.

## Phase 6: This tree

**Step 6.1 — Buggy code present?**
Record: **Yes.** HEAD:

```
regmap_write(axp20x_pm_power_off->regmap, AXP20X_OFF_CTRL, AXP20X_OFF);
```

AXP221/AXP223 match and `x-powers,axp221` compatible exist. Bug since
v3.16.

**Step 6.2 — Backport difficulty**
Record: **Minor conflicts / trivial rework.** `git apply --check` of the
mainline hunk fails (`shutdown_reg` / `sys_off_data` not in 6.1).
Equivalent backport:

```
- regmap_write(axp20x_pm_power_off->regmap, AXP20X_OFF_CTRL,
- AXP20X_OFF);
+ regmap_set_bits(axp20x_pm_power_off->regmap, AXP20X_OFF_CTRL,
+ AXP20X_OFF);
```

**Step 6.3 — Related fixes already here?**
Record: No. Only power-off history is the 500ms delay and `mdelay`
conversion.

## Phase 7: Subsystem context

**Step 7.1**
Record: **MFD / X-Powers AXP20x PMIC.** Criticality **IMPORTANT** for
Allwinner/ARM boards (Pine64, many SBCs, ClockworkPi). Not core mm/fs,
not staging.

**Step 7.2**
Record: Mature driver (2014). 6.1 file log is slow-moving; this is an
old bug, not churn from new code.

## Phase 8: Impact and risk

**Step 8.1 — Who**
Record: Driver-specific: AXP20x/22x users on orderly shutdown,
especially AXP221/223/228 with a charge LED. Config:
`CONFIG_MFD_AXP20X`.

**Step 8.2 — Trigger**
Record: Every orderly power-off on those PMICs. Unprivileged users can
typically run shutdown if policy allows; not a remote exploit path.

**Step 8.3 — Failure mode**
Record: PMIC REG 0x32 clobbered at power-off. User-visible: charge LED
off while charging with SoC down. Other bits in the same register
(battery detect / CHGLED mode / related config) are also cleared.
**Severity: MEDIUM** (hardware misprogramming, not
oops/corruption/deadlock).

**Step 8.4 — Risk/benefit**
Record: Benefit medium for battery AXP boards (indicator and leftover
PMIC config). Risk **very low** (1-line RMW, cached register,
maintainer-reviewed, hardware-tested). Ratio favors backport.

## Phase 9: Synthesis

**For:** Real, tested hardware bug; 1-line obviously correct RMW;
AXP221/223 exist in 6.1; bug since 2014; Chen-Yu Tsai + Lee Jones;
`regmap_set_bits` already here; shutdown path only.

**Against:** Not a crash/security/corruption bug; mainline hunk needs a
trivial context adapt; no Fixes:/Cc: stable (not a negative).

**Unresolved:** lore.kernel.org 403; no stable-ML thread retrieved.

**Stable rules**
1. Obviously correct and tested? **PASS** — RMW vs full write; tested
0x08→0x88; Reviewed-by maintainer.
2. Real user-facing bug? **PASS** — charge LED dead after shutdown while
charging.
3. Important (crash/security/corruption/deadlock)? **PASS** (borderline)
— PMIC control register clobber at power-off; LED is the reported
symptom, not a panic. Small enough that this still belongs in LTS.
4. Small and contained? **PASS** — one call site.
5. No new features/APIs? **PASS**
6. Can apply to this tree? **PASS** with a one-line backport (raw
mainline patch fails `git apply --check`).

**Exception:** Not a new ID/DT/build fix. Closest category is hardware-
correctness/quirk-class behavior.

This tree should get the fix: the buggy write is here, AXP221 is
supported, and the adapted change is a single `regmap_set_bits` on
`AXP20X_OFF_CTRL`.

Verification:
- [Phase 1] Tags: Reviewed-by Chen-Yu Tsai, Link msgid, SOBs Oleg/Lee;
no Fixes/syzbot
- [Phase 2] Diff: `regmap_write` → `regmap_set_bits` in
`axp20x_power_off`
- [Phase 3] `git blame HEAD -L 835,848`: write from cfb61a419630a8
(v3.16-rc1); `git merge-base --is-ancestor cfb61a419630a HEAD`
succeeded
- [Phase 3] `git merge-base --is-ancestor 3756bf05c96356 HEAD` failed
(candidate not in 6.1)
- [Phase 4] `b4 dig -c 3756bf05c96356`: lore msgid; `-a` v1 only; `-w`
wens+lee+lkml
- [Phase 4] Chen-Yu “Makes sense.”; Lee Jones applied; lore.kernel.org
HTTP 403
- [Phase 5] HEAD probe assigns `pm_power_off = axp20x_power_off`; AXP221
compatible in `axp20x-i2c.c`
- [Phase 6] HEAD Makefile 6.1.188; describe `p-6.1-9-g27ef8181b8a64`;
branch `pending-6.1`
- [Phase 6] HEAD still has `regmap_write(..., AXP20X_OFF_CTRL,
AXP20X_OFF)`; `git apply --check` of the mainline hunk fails
- [Phase 6] `regmap_set_bits` exists; 0x32 in axp22x writeable 0x04–0x4c
and not volatile
- [Phase 8] Failure: CHGLED/other bits cleared at power-off; severity
MEDIUM
- UNVERIFIED: stable@ mailing list (lore/stable 403)

**YES**

drivers/mfd/axp20x.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
index c5f0ebae327f5..85506813f089c 100644
--- a/drivers/mfd/axp20x.c
+++ b/drivers/mfd/axp20x.c
@@ -1250,7 +1250,7 @@ static int axp20x_power_off(struct sys_off_data *data)
break;
}

- regmap_write(axp20x->regmap, shutdown_reg, AXP20X_OFF);
+ regmap_set_bits(axp20x->regmap, shutdown_reg, AXP20X_OFF);

/* Give capacitors etc. time to drain to avoid kernel panic msg. */
mdelay(500);
--
2.53.0