PAF becomes saf/device (history kept), STATE.md dissolves into saf/state.md with the dated era archived, the substrate SAF moves up from souveraine, and every agreement points at saf/INDEX.md and nowhere else. one map, nothing to remember
158 lines
8 KiB
Markdown
158 lines
8 KiB
Markdown
# HANDOFF — pmi8998 charge control (kernel + userspace)
|
|
|
|
> Historical handoff. Current platform status belongs in `../../platform.md`.
|
|
|
|
Written 2026-07-24. For a fresh model/reviewer picking this up cold.
|
|
|
|
## What this is
|
|
|
|
Full read/write charge control for the Pixel 3 (blueline) charger, so the
|
|
phone can report its charge stage and cap the charge for battery longevity —
|
|
the capability LineageOS exposed downstream and mainline dropped.
|
|
|
|
Two layers, in two separate repos:
|
|
|
|
- **Kernel driver** — `drivers/power/supply/qcom_smbx.c` in the
|
|
`linux-blueline` tree. Adds charge_type reporting + charge-cap control.
|
|
- **Userspace** — `rootfs-overlay/…` in the `Pixel3Arch` repo. A CLI, a udev
|
|
rule, a persistence unit. **Currently stale — see "Known gaps" #1.**
|
|
|
|
Nothing is flashed. The phone is untouched. The driver builds clean but has
|
|
not been packaged since the last fix, and has NOT run on hardware.
|
|
|
|
## State of the tree — READ THIS FIRST
|
|
|
|
**The gitea branch is behind local by two commits.**
|
|
|
|
| commit | what | where |
|
|
|---|---|---|
|
|
| `f08dc70b3` | original: charge_type + charge control | **pushed to gitea `blueline`** |
|
|
| `6b733d3ea` | fix pass after review #1 | **LOCAL ONLY** |
|
|
| `588929024` | cell-aware current cap + probe guard | **LOCAL ONLY** |
|
|
|
|
`gitea.wiuf.net`/`10.20…:4455` `Fimeg/linux-blueline` branch `blueline` tip is
|
|
`f08dc70b369a` — the **flawed original**. A reviewer who clones from gitea gets
|
|
code with the defects listed under "Review #1 found" still present. To review
|
|
the real thing, use the local tree at
|
|
`~/Projects/Pixel3Arch/kernel/linux-blueline-current` (branch `mic-race-fix`,
|
|
tip `588929024`) or push the two commits first.
|
|
|
|
`build.sh` clones from gitea and hard-asserts the built package's hash matches
|
|
the branch tip, so **a package build will not include the fixes until they are
|
|
pushed.**
|
|
|
|
## Files
|
|
|
|
### Kernel (linux-blueline-current, branch mic-race-fix)
|
|
- `drivers/power/supply/qcom_smbx.c` — the only changed file. +241 lines.
|
|
- `smb_get_prop_charge_type` — read-only, maps BATTERY_CHARGER_STATUS_1
|
|
bits 0:2 (trickle/pre/fast/fullon/taper/…) to POWER_SUPPLY_CHARGE_TYPE_*.
|
|
- `smb_{get,set}_prop_voltage_max` — CV target, FLOAT_VOLTAGE_CFG (0x70).
|
|
- `smb_{get,set}_prop_constant_charge_current` — FAST_CHARGE_CURRENT_CFG (0x61).
|
|
- `smb_{get,set}_prop_charge_control_end` — percentage form of voltage_max,
|
|
on POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD.
|
|
- `smb_fast_charge_current_max` — 1C ceiling from charge_full_design_uah.
|
|
- `smbx_ov_status` — one-char fix, `reg & mask` → `val & mask`.
|
|
- probe: design-limit guard + constant substitution.
|
|
|
|
### Userspace (Pixel3Arch repo, committed at 8a35c45 / 194c422)
|
|
- `rootfs-overlay/usr/bin/blueline-charge` — CLI. **STALE.**
|
|
- `rootfs-overlay/etc/udev/rules.d/60-blueline-charge.rules` — grants the
|
|
writable attrs to group wheel. **STALE.**
|
|
- `rootfs-overlay/etc/systemd/system/blueline-charge-limit.service` —
|
|
reapplies the cap on boot (registers are volatile).
|
|
- `PAF/platform.md` — "Charging" section documents the register map.
|
|
|
|
## Property provenance (asked and answered)
|
|
|
|
Every property name is a **pre-existing mainline `power_supply` core constant**
|
|
(`include/linux/power_supply.h`), not invented here and not from any PR of
|
|
mine. `charge_control_end_threshold` has a documented ABI contract dated April
|
|
2019 (`Documentation/ABI/testing/sysfs-class-power`) — "battery percentage
|
|
level, above which charging will stop" — and is already implemented by seven
|
|
in-tree drivers, including the sibling `qcom_battmgr.c` on this same SoC family.
|
|
The one thing authored here is the charge_type register-state→enum mapping.
|
|
|
|
## Review history
|
|
|
|
Three independent cold-start adversarial reviews were run (subagents, each told
|
|
to refute safety). Summary of what each found:
|
|
|
|
**Review #1 (against f08dc70b3) — four real defects, all since fixed in 6b733d3ea:**
|
|
1. `smbx_ov_status` masked the register *address* not its *value*
|
|
(`reg & mask`), so the overvoltage backstop returned 0 on both SMB2 and
|
|
SMB5 and never fired — pre-existing, but the write path leaned on it.
|
|
2. `charge_control_limit` is microamps by ABI; it was implemented as a
|
|
percentage, inverting a thermal daemon's write. Moved to
|
|
`charge_control_end_threshold` (percent, ABI-correct).
|
|
3. `voltage_max` floored at the register base (3.4875V), not the cell design
|
|
minimum (3.6V). `echo 3487500 > voltage_max` set CV below terminal voltage;
|
|
with CHARGER_INHIBIT the charger stalls and the phone drains on a cable.
|
|
Now floored at voltage_min_design_uv.
|
|
4. Current setter clamped against the DCP *input* limit (a different register
|
|
and concept), so the getter returned 1.95A while the setter rejected it.
|
|
|
|
**Review #2 (against 6b733d3ea) — confirmed the four fixes; flagged the current
|
|
ceiling. Fixed in 588929024:**
|
|
- The current cap was bounded by register width = 6.375A = **2.15C** into a
|
|
2970mAh cell. Relying on "the adapter/AICL gate it" is the same
|
|
external-backstop reasoning that hid the dead OV check. Now bounded at **1C**
|
|
(2.97A from charge_full_design_uah), register width as fallback.
|
|
- Probe didn't validate voltage design limits (latent, not blueline). Guarded.
|
|
|
|
**Review #3 (against 588929024) — verdict FLASH for blueline.** No overcharge
|
|
(clamp-before-encode, encoder rounds down, 8-bit field can't truncate a legal
|
|
value unsafe), no stall (3.6V floor on every path, registers volatile →
|
|
reboot recovers), 1C cap confirmed, dispatch clean.
|
|
|
|
## Known gaps — WHAT STILL NEEDS DOING
|
|
|
|
1. **Userspace is stale.** `blueline-charge` and `60-blueline-charge.rules`
|
|
still reference `charge_control_limit`, which the driver no longer exposes.
|
|
On the fixed kernel the CLI fails its own `[ -e "$PSY/charge_control_limit" ]`
|
|
guard. They must be updated to `charge_control_end_threshold`. NOT done.
|
|
|
|
2. **Only `end_threshold`, not the `start`/`end` pair.** The ABI defines
|
|
`charge_control_start_threshold` and `_end_threshold` as a pair; some
|
|
userspace (GNOME/UPower, some tlp configs) expects both. Only `end` is
|
|
implemented. `drivers/power/supply/cros_charge-control.c` is the in-tree
|
|
reference for doing both.
|
|
|
|
3. **The SMB2 overvoltage bit is unverified.** No auditor could confirm from
|
|
code alone that BIT(5) of BATTERY_CHARGER_STATUS_2 is the pmi8998 OV bit —
|
|
there's no datasheet or downstream reference in-tree. It's pre-existing and
|
|
read-only (can't overcharge/stall), but worth confirming against a datasheet.
|
|
|
|
4. **Cosmetic:** the max voltage write (4.4V → reg 122) reads back as 4.395V
|
|
and 99% via the threshold getter, because the 7.5mV encoding step doesn't
|
|
divide the range evenly. Errs low (safe). Not fixed.
|
|
|
|
5. **Not packaged, not flashed.** Single-object compiles pass under clang/LLD/
|
|
CFI with W=1. Full `build.sh` not re-run since the fixes (and can't include
|
|
them until pushed — see "State of the tree"). No hardware test.
|
|
|
|
## What is NOT verified without hardware
|
|
|
|
- Whether lowering the float voltage actually *stops* the charge on the
|
|
pmi8998, versus the fuel gauge continuing to report rising capacity.
|
|
- Whether charge_type reports sane transitions (Fast under load → Standard as
|
|
it tapers near full).
|
|
|
|
## Build & deploy (per PAF/build.md)
|
|
|
|
- Commit + **push to gitea** (uncommitted/unpushed work does not build).
|
|
- `ssh casey@10.10.20.123 'cd ~/Projects/Pixel3Arch/kernel && ./build.sh blueline'`
|
|
— self-locks with flock, invoke directly; asserts pkg hash == gitea tip.
|
|
- Deploy: modules first, then flash boot_a, then refuse to bless unless
|
|
`uname -r` matches the installed tree (`7.1.1-sdm845-g<hash>`).
|
|
- Rollback: `staging/deploy-139aa/boot-139aa7748181-pmoschain.img`. Slot B
|
|
(LineageOS) is untouched and is the ultimate fallback.
|
|
|
|
## Single-object compile check (fast, no full build)
|
|
|
|
scp qcom_smbx.c casey@10.10.20.123:/tmp/
|
|
ssh casey@10.10.20.123 'cd ~/Projects/Pixel3Arch/kernel/src/linux-blueline-src && \
|
|
cp /tmp/qcom_smbx.c drivers/power/supply/ && \
|
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- W=1 \
|
|
drivers/power/supply/qcom_smbx.o'
|
|
# restore the tree afterward — this is makepkg's throwaway clone
|