- LBC supervisor v1..v4 (linux/patches/b10b3-lbc-supervisor-v3.patch + b10/b10b3/lbc-v3-to-v4-fault-clamp.diff): CHARGING 4.20 V/450 mA -> HOLD 4.05 V after 60 min CV -> battery -> new cycle on USB insert; FAULT = VDD_MAX 4.00 V clamp - RAM tests C/B/D/D2/D3/D4 (safety timer only ends fast charge), B10B-4 actuator audit (VDD_MAX works, USB_SUSP does not), B10B-5 FAULT clamp regression, production run, warm reboot via PON reboot reason - cache = B10B-5 c3732515 (HW EDL write, readback + full partition verify), class-A persistent validation - test-only hooks/images are not part of the production kernel; images with the AP PSK are not published
12 KiB
B10A-DRIVER-AUDIT — charge termination and recharge: stock vs upstream (code-only, read-only)
Date: 2026-10-03. Board untouched (no PMIC writes, no build). Sources:
- downstream: android.googlesource kernel/msm @7f1748ce
drivers/power/qpnp-linear-charger.c,qpnp-vm-bms.c(copies:b10/b10a/lbc-7f1748.c,vmbms-7f1748.c). The stock Aurora kernel (3.10.28,partitions/boot.bin) is a slightly different revision: its strings match this LBC source ("Failed to override VBAT_DET", "vbatdet-lo triggered", "Failed to disable EOC comp", "resume-soc") and its BMS additionally has "soc dropped below resume_soc soc=%d resume_soc=%d, restart charging". Same design. - upstream: Linux 7.2.7
drivers/power/supply/pm8916_lbc.c,pm8916_bms_vm.c,arch/arm64/boot/dts/qcom/pm8916.dtsi, reference boardmsm8916-longcheer-l8150.dts. - live registers:
b10/b10a/b10a-live1.txt,b10a-live2.txt(B10A).
Register names (downstream defines, offsets from CHGR 0x1000 / BAT_IF 0x1200 / USB 0x1300 / MISC 0x1600)
| Reg | Name | Live (SBL1 state) | Meaning |
|---|---|---|---|
| 0x1009 | CHG_STATUS, bit1 = VDD_LOOP (CV) | 05 / 03 / 07 / 00 | 03 = CV loop, 05 = other loop (CC/input), 00 = not charging (bits 0/2 not named in source) |
| 0x1010 | CHGR INT_RT_STS (bit5 FAST_CHG_ON) | 01 | fast_chg RT was never seen set |
| 0x1040/41 | VDD_MAX / VDD_SAFE | 08 / 08 | 4.200 V |
| 0x1044/45 | IBAT_MAX / IBAT_SAFE (90 mA steps) | 00 / 0a | 90 mA / 990 mA |
| 0x1047 | VIN_MIN (4200 + 27 mV·n) | 04 | 4308 mV = stock vinmin-mv (B10A table had this wrong: 0x1043 is not VIN_MIN) |
| 0x1049 | CHG_CTRL: bit7 CHG_ENABLE, bit0 CHG_FORCE_BATT_ON | 90 | enabled; bit4 unknown |
| 0x104A | CHG_FAILED (bit7) | 00 | |
| 0x1052 | VBAT_WEAK | 0b | |
| 0x105B | CHG_IBATTERM_EN, bit3 = HW end-of-charge (iterm) comparator | 09 → bit3 = 1 | HW termination ENABLED |
| 0x1060/61 | TCHG_MAX_EN / TCHG_MAX (4 min steps) | 80 / 1d | timer on, (0x1d+1)·4 = 120 min |
| 0x1065 | CHG_WDOG_EN | 00 | off |
| 0x10DA | PERPH_RESET_CTRL3 ("follow PMIC reset") | 0b | |
| 0x10EE | CHG_COMP_OVR1, bits1:0 = VBAT_DET override | 00 | no override → VBAT_DET comparator active |
| 0x1309 | USB_PTH_STS (bits7:6 = USB_IN_VALID) | 90 | valid |
| 0x1308 | (not in the downstream source) | 02 charging / 01 after EOC | empirical |
| 0x1642 | MISC BOOT_DONE (bit7) | 00 | never set (downstream sets it at probe) |
| 0x16F3/F4 | MISC TRIM3/TRIM4 (VDD trim) | 58 / 49 |
A. Stock (downstream) recharge logic
Termination is a software decision, recharge is software-triggered:
- Probe (
qpnp_lbc_probe): misc_init (reads VDD trim, writes BOOT_DONE), chg_init (VBAT_WEAK, VIN_MIN, VDD_SAFE, VDD_MAX + VDD trim, IBAT_SAFE, TCHG_MAX iftchg-mins, VBAT_DET override = 0 (CHG_COMP_OVR1) "charge irrespective of VBAT above VBAT_DET", HW iterm comparator OFF (0x105B bit3 = 0) unlessqcom,charger-detect-eocor float-charge, charger WDOG off), bat_if_init (BPD source, force VREF_BAT_THM), usb_path_init (USB enum timer stop = 0, CHG_CTRL CHG_ENABLE), battery psy, initial status, IRQs, VDD-trim alarm. Stock DT has neithercharger-detect-eocnorfloat-chargenordisable-vbatdet-based-recharge, so all three SW paths below are active. - IRQs: chg_failed, fast_chg (on: clear chg_done, start 50 s VDD-trim alarm), chg_done (only sets a flag), vbatdet_lo (falling edge), batt_pres, batt_temp, usbin_valid, usb_overtemp.
- EOC: VM-BMS (
qcom,report-charger-eoc) decides "full" (SOC 100 / OCV at 100) and calls batteryset_property(STATUS, FULL). The LBC driver then disables charging (CHG_CTRL: CHG_EN=0, FORCE_BATT_ON=1), sets chg_done, removes the VBAT_DET override and enables the vbatdet_lo IRQ. - Recharge, three independent paths, all in software:
- vbatdet_lo IRQ (VBAT fell below VBAT_DET): disable the IRQ, override VBAT_DET to 0 again, CHG_ENABLE.
- resume-soc (DT 99 %):
get_prop_capacity(polled by healthd/BMS) re-enables charging when SOC ≤ resume_soc and USB is in. - usbin_valid insert: override VBAT_DET to 0, CHG_ENABLE (and on removal: disable + 90 mA).
- Also
external_power_changed: IBAT_MAX = min(USB current_max (500 mA), thermal, JEITA) and enable/disable on suspend (≤ 2 mA).
- Other runtime: VDD_MAX trim alarm every 50 s while fast charging (compensates VDD_MAX against measured VBAT), JEITA via ADC_TM batt_therm, thermal mitigation levels 1440/720/630/0 mA.
B. Upstream pm8916_lbc
Only programs the LBC and leaves the state machine to the hardware. No termination/recharge logic at all.
- Probe writes: VDD_SAFE (
qcom,fast-charge-safe-voltage), IBAT_SAFE (qcom,fast-charge-safe-current), TCHG_MAX_EN = 0x00 (safety timer OFF), VDD_MAX (frommonitored-batteryvoltage-max-design, clamped to safe), IBAT_MAX (= safe current, settable later via sysfsconstant_charge_current), CHG_CTRL = 0xA0 (CHG_EN | PSTG_EN) (overwrites the live 0x90 → clears bit4, sets bit5). - Does NOT touch: IBATTERM_EN (HW termination stays as SBL1 left it = ON), CHG_COMP_OVR1/VBAT_DET, BOOT_DONE, WDOG, VIN_MIN, VBAT_WEAK, BAT_IF/BPD/BTC, VDD trim, USB enum timer.
- IRQs: only usb_vbus (0x13 bit1) →
online+ its own extcon (EXTCON_USB) + power_supply_changed. chg_done/vbat_det/fast_chg/ chg_fail/bat/temp interrupts are listed in the DT binding but never requested. - Properties: ONLINE, CONSTANT_CHARGE_VOLTAGE_MAX, CONSTANT_CHARGE_CURRENT (writable → re-runs configure → rewrites VDD_MAX, IBAT_MAX and CHG_CTRL with the same value; no 0→1 edge on CHG_EN).
- Requires
monitored-battery(probe fails without battery info). Minor bug: the clamp result in configure() is discarded (line 100–103). - Upstream BMS-VM: probe writes S1/S2 sample interval (10/10), FIFO length (2/2), EN_CTL; suspend/resume force S3/normal via SEC_ACCESS. Reports VOLTAGE_NOW = FIFO·300 µV, OCV for 180 s after boot, STATUS = charging if supplied; no SOC, no EOC, no recharge. Downstream converts FIFO raw with VADC calibration: raw·97.656 µV·3, then gain-corrected with the 625 mV/1.25 V references (uncalibrated: 14682 → 4.301 V; our CV level means the gain correction is ≈ −2.4 %). The ×300 µV constant is ≈ +5 % high here.
C. Difference
| Stock | Upstream | Our board now (no driver) | |
|---|---|---|---|
| Who ends the charge | SW (BMS SOC=100 → disable) | HW iterm comparator (whatever SBL1 left) | HW iterm comparator (0x105B bit3 = 1) |
| HW iterm | disabled at probe | untouched | enabled |
| VBAT_DET override | 0 while charging; removed at EOC, re-armed on recharge | untouched | none (0x10EE = 0) |
| Recharge | vbatdet_lo IRQ / resume-soc / USB insert → CHG_EN | none | none |
| BOOT_DONE | set | not set | not set |
| Safety timer | tchg-mins 232 |
disabled | 120 min, on |
| IBAT_MAX | min(USB 500 mA, thermal, JEITA) | fixed (safe current) | 90 mA field |
| VDD_MAX trim | 50 s alarm | none | SBL1 trim |
| USB extcon | msm_otg | LBC's own extcon (conflicts with our pm8916_usbin on the same IRQ) |
pm8916_usbin |
D. Why the hardware does not start a new cycle, and what our board needs
Explanation (consistent with all B10A observations; the exact FSM rule is undocumented → hypothesis, high confidence):
- SBL1 leaves the LBC in pure hardware mode: HW iterm termination ON, no VBAT_DET override, BOOT_DONE not set.
- After the CV taper the iterm comparator ends the cycle: CHG_STATUS 03 → 00, USB path 02 → 01. VPH = VBAT (no power path), so the whole load moves to the battery.
- The FSM stays latched in that state. Nothing in this design restarts it automatically: on stock, restart is always a software CHG_EN edge (vbatdet_lo IRQ / resume-soc / USB insert). A warm reset does not reset the LBC peripheral (class B kept the state, PERPH_RESET_CTRL3 = 0x0b); only a full PMIC POR (class A) does.
- Not proven: whether the LBC would resume by itself at a much lower VBAT (VBAT_DET comparator with no override). In 30 min it fell ≈ 70–80 mV below the CV level without resuming. The VBAT_DET threshold register is not named in the source.
Is enabling upstream pm8916_lbc enough? No. It programs the limits once and adds the usb_vbus extcon, but it has no chg_done/vbat_det handling and no recharge. After the first termination the board would be exactly in today's state. At best, a USB re-plug would not help either (upstream does not touch CHG_EN on insert).
What our board needs (beyond upstream):
- A recharge mechanism that produces a CHG_EN 0→1 edge when VBAT drops (and on USB insert), e.g.:
- kernel patch to pm8916_lbc: request
chg_done+vbat_detIRQs, emulate the stock flow (EOC → disable + arm VBAT_DET → re-enable), or simpler: a periodic check (VBAT/BMS below threshold ∧ USB valid ∧ CHG_STATUS == 00 → CHG_EN toggle); - or a userspace supervisor (shell + regmap/debugfs or a small sysfs knob) doing the same. Debugfs writes are a debug path only.
- kernel patch to pm8916_lbc: request
- A decision on termination: either keep HW iterm (then recharge logic is mandatory) or disable it (0x105B bit3 = 0, like stock) and accept float charging at a reduced VDD_MAX — float at 4.2 V forever is bad for the cell; not recommended without a lower float voltage.
- A battery description (
simple-battery, voltage-max 4.20 V for now, voltage-min ≈3.4 V, capacity from the label) — mandatory for both drivers. - USB extcon: move
usb/usb_hs_phyfrompm8916_usbintopm8916_charger(like l8150) and disablepm8916_usbin, otherwise both drivers request the same usb_vbus IRQ. - IIO +
QCOM_SPMI_VADC(+ add channel 6 VBAT_SNS with pre-scaling 1/3, maybe 0x30 batt_therm / 0x31 batt_id) for a calibrated VBAT, and to calibrate the BMS FIFO scale. - Keep the safety timer in mind: upstream turns it off. With HW iterm + recharge it is acceptable; otherwise keep it.
- USB gadget MaxPower 2 mA → 500 mA (separate, unrelated to the PMIC).
E. Minimal B10B plan (RAM-only, each step its own GO)
Common rules: RAM boot via RESET-held power-on + fastboot boot (cache/aboot untouched); class-A power-on before each step so the LBC starts
from SBL1 defaults; USB meter in line; results streamed to 480s; full PMIC dump (b10a-probe.sh) before boot actions and after each probe → diff.
- B10B-0 (no new PMIC writes by drivers): kernel b9c +
IIO,QCOM_SPMI_VADC,QCOM_SPMI_TEMP_ALARM; DT adds VADC channel 6 (VBAT_SNS 1/3). Note: VADC conversions themselves write VADC control registers (0x31xx) — that is the only write class. Check: VBAT via IIO vs BMS FIFO (calibrate the scale), USBIN, VPH, die temp, PMIC thermal zone. Baseline/diff: CHGR/BAT_IF/USB/MISC/BMS unchanged. - B10B-1 BMS: +
BATTERY_PM8916_BMS_VM,simple-batterynode (4.20 V max, 3.4 V min). Expected writes: BMS 0x4055/56/47/46 only. Check: power_supplypm8916-bms-vmVOLTAGE_NOW/OCV vs IIO. Charger untouched. - B10B-2 charger (upstream as-is, observation): +
CHARGER_PM8916_LBC,qcom,fast-charge-safe-voltage = 4200000,qcom,fast-charge-safe-current = 500000, battery voltage-max 4.20 V; extcon moved topm8916_charger,pm8916_usbindisabled. Expected writes: 0x1041, 0x1045, 0x1060 (timer off!), 0x1040, 0x1044 (=4 → 450 mA: 500000/90000−1 = 4, i.e. 450 mA), 0x1049 = 0xA0. Check: USB enumeration still works (NCM), meter current, CHG_STATUS/0x1308 trend through CC → CV → termination, and confirm that no recharge happens (expected). Decide whether to keep the safety timer (would need a patch). - B10B-3 recharge logic (needs design GO): minimal patch or userspace supervisor: on (USB valid ∧ CHG_STATUS == 00 ∧ VBAT < 4.10 V [threshold TBD]) → CHG_CTRL 0x80 → 0x81/0x00 → 0x80 (or stock-style VBAT_DET override + vbat_det IRQ). Test: full cycle, termination, discharge to the threshold, automatic recharge, repeat ≥2 cycles, USB unplug/replug, class-B reboot during done-state.
- Only after B10B-3 PASS: persistent integration (cache), separate GO.