- 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
8.5 KiB
B10B3-TIMER-SEMANTICS — PM8916 LBC safety timer: what the Qualcomm sources say (desk study, no board access)
Date: 2026-10-04. No code changed, no test run. Sources (copies + sha256 in downstream-7f1748ce/SOURCES.txt):
LBC= android.googlesource kernel/msm @7f1748cedrivers/power/qpnp-linear-charger.c(PM8916 linear battery charger, our PMIC)LBC-DT=Documentation/devicetree/bindings/power/qpnp-linear-charger.txtSMBB= same treedrivers/power/qpnp-charger.c(PM8941/PM8226 switch-mode charger, same QPNP generation and the same CHGR register map)- ours =
/home/q/aurora-kbuild/src-b10b3/drivers/power/supply/pm8916_lbc.c(v3)
1. Register meaning (only what the code states)
| reg | name in source | what the source does with it |
|---|---|---|
| 0x1060 | CHG_TCHG_MAX_EN_REG 0x60, CHG_TCHG_MAX_EN_BIT BIT(7) (LBC:64–65) |
bit7 = timer enable; written 0 before and 1 after programming TCHG_MAX |
| 0x1061 | CHG_TCHG_MAX_REG 0x61, CHG_TCHG_MAX_MASK LBC_MASK(6,0) (LBC:66–67) |
7-bit limit, minutes = (val + 1) × 4 |
| 0x104A | CHG_FAILED_REG 0x4A, CHG_FAILED_BIT BIT(7) (LBC:59–60) |
only written (bit7 = 1) in the chg-failed IRQ handler = "chg_fail clear bit". Never read in LBC or SMBB |
| 0x1010 bit6 | chg-failed IRQ (LBC-DT:173–180 <0x0 0x10 0x6>; SMBB:192 CHG_FAILED_IRQ BIT(6)) |
the actual failure status/interrupt (RISING edge) |
| 0x1010 bit5 | FAST_CHG_ON_IRQ BIT(5) (LBC:34), DT fast-chg-on <0x0 0x10 0x5> |
LBC: the only indicator of "charging": get_prop_batt_status → CHARGING iff bit5, get_prop_charge_type → FAST iff bit5 (LBC:1060–1097) |
| 0x1010 bit7 / bit0 | chg-done / vbat-det-lo (LBC-DT:173–181) |
|
| 0x105E/0x105F | not in LBC; SMBB:87–88 CHGR_TTRKL_MAX_EN / CHGR_TTRKL_MAX |
SMBB has a separate trickle-charge timer next to TCHG; SMBB RT bit4 = TRKL_CHG_ON_IRQ (SMBB:194) |
| 0x1009 bit1 | CHG_VDD_LOOP_BIT (LBC:47) |
CV loop; bits 0/2 not named |
Excerpts:
/* LBC:932-963 qpnp_lbc_tchg_max_set() */
#define QPNP_LBC_TCHG_MIN 4
#define QPNP_LBC_TCHG_MAX 512
#define QPNP_LBC_TCHG_STEP 4
minutes = clamp(minutes, QPNP_LBC_TCHG_MIN, QPNP_LBC_TCHG_MAX);
/* Disable timer */ ... CHG_TCHG_MAX_EN_BIT, 0
reg_val = (minutes / QPNP_LBC_TCHG_STEP) - 1;
... CHG_TCHG_MAX_REG, CHG_TCHG_MAX_MASK, reg_val
/* Enable timer */ ... CHG_TCHG_MAX_EN_BIT, CHG_TCHG_MAX_EN_BIT
/* LBC:1653 (init): only if the DT has the property */
if (of_property_read_bool(chip->spmi->dev.of_node, "qcom,tchg-mins")) {
rc = qpnp_lbc_tchg_max_set(chip, chip->cfg_tchg_mins);
/* LBC:2066-2083 qpnp_lbc_chg_failed_irq_handler() */
u8 reg_val = CHG_FAILED_BIT;
pr_debug("chg_failed triggered count=%u\n", ++chip->chg_failed_count);
rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_FAILED_REG, ®_val, 1);
if (rc) pr_err("Failed to write chg_fail clear bit rc=%d\n", rc);
... power_supply_changed(&chip->batt_psy);
/* LBC:2273 */ SPMI_REQUEST_IRQ(chip, CHG_FAILED, rc, chg_failed, 0, IRQF_TRIGGER_RISING, 1);
/* LBC-DT:47 */ - qcom,tchg-mins: Maximum total software initialized charge time.
/* SMBB:3298-3330 qpnp_chg_tchg_max_set(): identical (TCHG_MIN 4, MAX 512, STEP 4, EN 0x80, MASK 0x7F, minutes/4-1) */
2. Formula minutes / 4 − 1
Confirmed: identical in LBC (qpnp_lbc_tchg_max_set) and SMBB (qpnp_chg_tchg_max_set), 7-bit field. Our v3 uses the same formula and the
same disable → write → enable sequence. This is Qualcomm driver code, not a register specification; it is the best available evidence, not proof.
3. Encoding table (from the formula)
4 min = 0x00 · 8 min = 0x01 · 16 min = 0x03 · 120 min = 0x1D (SBL default) · 152 min = 0x25 (binding example) · 232 min = 0x39 (stock DT) · 512 min = 0x7F (maximum).
4. When does the hardware timer count?
No source states it. Neither LBC nor SMBB documents counting, pausing in CV, reset on state transitions, or reset by CHG_CTRL/VDD_MAX writes. What the code implies, in decreasing strength:
- Programming is done once at probe with EN 0 → value → EN 1; nothing else in either driver touches TCHG (no re-arm on USB insert, on CHG_EN toggles or on recharge). EN 0→1 is therefore the only "arming" action Qualcomm uses (implied, not stated).
- The binding calls it "maximum total software initialized charge time".
- SMBB has a separate trickle timer (TTRKL 0x5E/0x5F) next to TCHG and distinguishes TRKL_CHG_ON / FAST_CHG_ON states → TCHG is the charge-phase (fast-charge) timer of the hardware charge state machine, not a "time since CHG_EN" counter (inference).
- Both drivers equate "charging" with the FAST_CHG_ON RT bit (SMBB also TRKL_CHG_ON). → The best-supported model: TCHG counts while the charger FSM is in its fast-charge state (FAST_CHG_ON). "Counts whenever CHG_EN" — our Test B/D model — has no support in the sources. Stops in CV / resets by VDD_MAX writes: unknown.
5. How the stock driver uses it
Programs it (if qcom,tchg-mins) at init; stock DT tchg-mins = <232> (0x39). Expiry handling = chg-failed IRQ (RT bit6, rising):
write CHG_FAILED bit7 to clear, count, power_supply_changed — no FAULT state, no CHG_EN change, no restart, no reprogramming. Fault is never
read back; battery status/health never use it. Recovery = the clear itself (whether charging resumes after the clear is not stated).
6. Our data against this
FAST_CHG_ON (CHGR RT 0x1010 bit5) per run, from the monitor logs:
| run | driver | CHGR RT | USB path 0x1308 | CHG_CTRL | samples | note |
|---|---|---|---|---|---|---|
| B10B-3 ram1 | v1 | 20 | 02 | a0 | 2 | after probe (VBAT 3.90 V) |
| B10B-3 ram1 | v1 | 00 | 01 | 21 | 11 | after force_terminate (CHG_CTRL 0x21 held): FAST_CHG_ON off, path 02→01, meter stayed 0.358 A |
| B10B-3T t1 | v2 | 21 | 02 | a0 | 227 (≈114 min) | VBAT 3.85–3.99 V, VDD_MAX 4.00 V, timer 512 min |
| Test C c1 | v3 | 01 | 01 | a0 | 134 | 0.358 A CC |
| Test B b1 | v3 | 01 | 01 | a0 | 174 | |
| Test D d1 | v3 | 01 | 01 | a0 | 161 | 0.42 A CC |
| B9C/SBL (class A) | — | 01 | 02 | 90 | dumps | BOOT_DONE 0, SBL charge |
- FAST_CHG_ON appears exactly when path = 02 under the Linux driver (v1/v2); never in any v3 run (path 01), although current flows.
- t1 vs v3 configuration: of the 69 charger registers logged in
t1-final-state.txt, only 0x1010 (RT), 0x1308 (path) and 0x1061 (timer value) differ from the v3 end dumps → a state difference of the charger FSM, not a configuration difference. - v1/v2 and v3 use the same restart edge at CHARGING entry (
lbc_charge_restart: CHG_FAILED clear, CHG_CTRL 0x21 → 20 ms → 0xa0); v3 adds BOOT_DONE (already 1 from lk1st fastboot) and a VDD_MAX/IBAT_MAX write just before the edge. What puts the FSM into "path 01, no FAST_CHG_ON, still charging" in v3 is not established (candidates: entry ordering, the state before probe — B9C SBL path 02 vs latched path 01 before t1 —, VBAT relative to the VBAT_DET comparator without the stock COMP_OVR override). - chg-failed RT (bit6) never set in any run; 0x104A always 00 (weak evidence: write-to-clear in the sources).
- 0x105E/0x105F (TTRKL in the SMBB map) = 80/2b in every dump (SBL value); if 1-min steps, 44 min — coincides with the BD=0 00/01 stop 43.7–44.7 min after class A (EOC audit case A). Hypothesis only.
- Stock init differences: COMP_OVR1 VBAT_DET override = 0x2 (LBC:1660–1669, also on USB insert), ENUM_T_STOP = 0 (LBC:1741–1745); ours 0x10EE 00, 0x134E 00.
7. Why Test D gave no timeout
At t = 480 s: TCHG 80/01, CHG_STATUS 05, CHG_CTRL a0, path 01, 0.42 A — but CHGR RT = 01: FAST_CHG_ON was not set (as in every v3 run). Under the source-supported model the TCHG timer belongs to the FSM's fast-charge state; in v3 the LBC charges at full current in a state that does not report fast charge (path 01), so the timer most likely never counted — in Test B and in Test D alike. The CV argument (VBAT at 4.10 V from ≈ t 300 s) is secondary. No hidden expiry: chg-failed RT bit6 stayed 0. Not proven: (a) that TCHG counts only with FAST_CHG_ON, (b) which state "path 01 + current" is, (c) why v3 lands there and v1/v2 did not.
8. Consequences (no code change yet)
- The safety timer is not a protection in the v3 configuration — it very likely does not run. Do not count on it.
- The "path 01 + charging" state also matters beyond the timer (it is the state that the 00/01 latch is part of).
- Supervisor fault detection should also use CHGR INT_RT bit6 (chg-failed) — separate decision.
- A D2 with lower IBAT on v3 would very likely repeat B/D (no FAST_CHG_ON) and adds little. The informative test needs the FAST_CHG_ON state.