uzbek-plus/logs/b10/b10b3/B10B3-TIMER-SEMANTICS.md
q 58dcb3d121 B10: battery charging — PM8916 LBC supervisor v4 (CHARGE/HOLD, FAULT safe clamp to 4.00 V) and persistent B10B-5 cache
- 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
2026-10-04 19:00:27 +03:00

8.5 KiB
Raw Blame History

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 @7f1748ce drivers/power/qpnp-linear-charger.c (PM8916 linear battery charger, our PMIC)
  • LBC-DT = Documentation/devicetree/bindings/power/qpnp-linear-charger.txt
  • SMBB = same tree drivers/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, &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:

  1. 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).
  2. The binding calls it "maximum total software initialized charge time".
  3. 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).
  4. 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.