uzbek-plus/b10/b10b3/lbc-v2-to-v3.diff
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

434 lines
16 KiB
Diff

--- v2
+++ v3
@@ -61,9 +61,14 @@
/* Aurora B10B-3 recharge supervisor */
#define PM8916_LBC_SUPV_PERIOD_MS 30000
-#define PM8916_LBC_SUPV_DONE_POLLS 2
-#define PM8916_LBC_SUPV_LOW_POLLS 3
+#define PM8916_LBC_SUPV_LATCH_POLLS 2
+#define PM8916_LBC_SUPV_NOCV_POLLS 2
#define PM8916_LBC_SUPV_OVP_UV 4250000
+#define PM8916_LBC_CV_HOLD_DEFAULT_MIN 60
+#define PM8916_LBC_CHGR_CV_LOOP BIT(1)
+
+#define PM8916_LBC_MISC_BOOT_DONE 0x42
+#define PM8916_LBC_MISC_BOOT_DONE_BIT BIT(7)
#define PM8916_LBC_TIMEOUT_DEFAULT_MIN 512
#define PM8916_LBC_TIMEOUT_STEP_MIN 4
#define PM8916_LBC_TIMEOUT_MAX_MIN 512
@@ -92,19 +97,25 @@
unsigned int charge_current_max;
unsigned int charge_current_safe;
- /* recharge supervisor */
+ /* charger supervisor (Aurora B10B-3 v3) */
struct iio_channel *vbat_chan;
struct delayed_work supv_work;
struct mutex supv_lock;
int state;
int fault_reason;
- unsigned int recharge_uv; /* production threshold from monitored-battery */
+ unsigned int hold_uv; /* aurora,hold-voltage-microvolt, 0 = HOLD disabled */
+ unsigned int full_min_uv; /* aurora,full-min-voltage-microvolt */
+ unsigned int cv_hold_min; /* aurora,cv-hold-minutes */
+ unsigned int ibat_code; /* IBAT_MAX code programmed at probe */
unsigned int timeout_min;
int vbat_uv;
- int done_polls;
- int low_polls;
- unsigned int n_terminated;
- unsigned int n_recharge;
+ int latch_polls;
+ int nocv_polls;
+ time64_t cv_since; /* start of the current CV run, 0 = none */
+ bool supv_ready; /* set at the end of probe; IRQ kicks only after that */
+ unsigned int n_insert;
+ unsigned int n_hold;
+ unsigned int n_latch;
struct {
time64_t t;
int from, to, vbat;
@@ -118,7 +129,7 @@
LBC_ST_INIT = 0,
LBC_ST_NO_USB,
LBC_ST_CHARGING,
- LBC_ST_TERMINATED,
+ LBC_ST_HOLD,
LBC_ST_FAULT,
};
@@ -126,7 +137,7 @@
[LBC_ST_INIT] = "INIT",
[LBC_ST_NO_USB] = "NO_USB",
[LBC_ST_CHARGING] = "CHARGING",
- [LBC_ST_TERMINATED] = "TERMINATED",
+ [LBC_ST_HOLD] = "HOLD",
[LBC_ST_FAULT] = "FAULT",
};
@@ -154,7 +165,7 @@
LBC_MISC,
};
-static int pm8916_lbc_charger_configure(struct pm8916_lbc_charger *chg, bool set_ctrl)
+static int pm8916_lbc_charger_configure(struct pm8916_lbc_charger *chg, bool probe)
{
int ret = 0;
unsigned int tmp;
@@ -166,9 +177,12 @@
tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP;
chg->charge_voltage_max = PM8916_LBC_CHGR_MIN_VOLTAGE + tmp * PM8916_LBC_CHGR_VOLTAGE_STEP;
- ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, tmp);
- if (ret)
- goto error;
+ /* after probe the supervisor owns VDD_MAX (charge level / hold level) */
+ if (probe) {
+ ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, tmp);
+ if (ret)
+ goto error;
+ }
chg->charge_current_max = min(chg->charge_current_max, chg->charge_current_safe);
@@ -177,16 +191,17 @@
tmp = chg->charge_current_max / PM8916_LBC_CHGR_MIN_CURRENT - 1;
chg->charge_current_max = (tmp + 1) * PM8916_LBC_CHGR_MIN_CURRENT;
+ chg->ibat_code = tmp;
ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_MAX, tmp);
if (ret)
goto error;
/*
- * CHG_CTRL is written only at probe. Afterwards the recharge supervisor owns CHG_EN, so a
- * constant_charge_current update must not re-enable charging in TERMINATED/FAULT.
+ * CHG_CTRL is written only at probe. Afterwards the supervisor owns CHG_EN, so a
+ * constant_charge_current update must not restart charging in HOLD/FAULT.
*/
- if (set_ctrl) {
+ if (probe) {
ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_CTRL,
PM8916_LBC_CHGR_CHG_EN | PM8916_LBC_CHGR_PSTG_EN);
if (ret)
@@ -212,7 +227,7 @@
case LBC_ST_CHARGING:
val->intval = POWER_SUPPLY_STATUS_CHARGING;
break;
- case LBC_ST_TERMINATED:
+ case LBC_ST_HOLD:
val->intval = POWER_SUPPLY_STATUS_FULL;
break;
case LBC_ST_NO_USB:
@@ -249,8 +264,15 @@
switch (prop) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ {
+ int ret;
+
+ mutex_lock(&chg->supv_lock);
chg->charge_current_max = val->intval;
- return pm8916_lbc_charger_configure(chg, false);
+ ret = pm8916_lbc_charger_configure(chg, false);
+ mutex_unlock(&chg->supv_lock);
+ return ret;
+ }
default:
return -EINVAL;
}
@@ -276,14 +298,15 @@
/*
- * Aurora B10B-3 recharge supervisor.
- * The LBC ends a charge cycle in hardware (end-of-charge latch: CHG_STATUS 00, USB path 01, USB input ~0) and then
- * stays off until software gives CHG_EN a 0 -> 1 edge (downstream qpnp-linear-charger did this from BMS SOC /
- * VBAT_DET IRQ). Verified on Aurora (B10B-3 ram1): CHG_CTRL 0x21 -> 0xa0 releases the latch.
- * Also verified: writing CHG_EN = 0 does NOT stop the charger (USB input stays ~0.36 A), so the supervisor never
- * uses CHG_CTRL to "turn charging off"; TERMINATED is only the hardware latch, observed after CHARGING.
- * The supervisor polls the VADC VBAT every 30 s and restarts charging when VBAT stays below the monitored-battery
- * re-charge-voltage-microvolt for 3 consecutive polls.
+ * Aurora B10B-3 v3 charger supervisor: CHARGE -> HOLD -> (battery) -> CHARGE.
+ * Facts it relies on (B10A/B10B-2/B10B-3 tests): CHG_STATUS 00 + USB path 01 is a latch, not an end-of-charge;
+ * no hardware EOC was seen with BOOT_DONE set; CHG_EN = 0 does not stop the charger; verified levers are VDD_MAX,
+ * IBAT_MAX and the CHG_CTRL 0x21 -> 0xa0 edge (releases the latch). All decisions use the calibrated VADC VBAT.
+ * NO_USB: nothing is written.
+ * CHARGING: entered on USB insert/probe: BOOT_DONE, VDD_MAX = charge level, IBAT_MAX, restart edge.
+ * -> HOLD after the CV loop with VBAT >= full-min lasted cv-hold-minutes; 00/01 latch -> restart, stay.
+ * HOLD: only VDD_MAX = hold level; 00/01 latch -> restart edge, VDD_MAX stays at the hold level.
+ * FAULT: logical only (VBAT > 4.25 V or CHG_FAILED): no restarts. There is no verified software stop.
*/
static void lbc_supv_log(struct pm8916_lbc_charger *chg, int to, const char *why)
{
@@ -294,14 +317,17 @@
chg->log[i].to = to;
chg->log[i].vbat = chg->vbat_uv;
chg->log[i].why = why;
- dev_info(chg->dev, "supervisor %s -> %s (%s), vbat %d uV\n", lbc_state_name[chg->state],
- lbc_state_name[to], why, chg->vbat_uv);
+ if (to == LBC_ST_FAULT)
+ dev_crit(chg->dev, "supervisor %s -> FAULT (%s), vbat %d uV: restarts blocked, NO software stop available\n",
+ lbc_state_name[chg->state], why, chg->vbat_uv);
+ else
+ dev_info(chg->dev, "supervisor %s -> %s (%s), vbat %d uV\n", lbc_state_name[chg->state],
+ lbc_state_name[to], why, chg->vbat_uv);
chg->state = to;
}
static int lbc_charge_enable(struct pm8916_lbc_charger *chg, bool en)
{
- /* stock-style: enabled = CHG_EN, disabled = FORCE_BATT_ON */
return regmap_update_bits(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_CTRL,
PM8916_LBC_CHGR_EN_MASK,
en ? PM8916_LBC_CHGR_CHG_EN : PM8916_LBC_CHGR_FORCE_BATT_ON);
@@ -325,10 +351,69 @@
return lbc_charge_enable(chg, true);
}
+static int lbc_set_vdd_max(struct pm8916_lbc_charger *chg, unsigned int uv)
+{
+ uv = clamp_t(u32, uv, PM8916_LBC_CHGR_MIN_VOLTAGE, chg->charge_voltage_safe);
+ return regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX,
+ (uv - PM8916_LBC_CHGR_MIN_VOLTAGE) / PM8916_LBC_CHGR_VOLTAGE_STEP);
+}
+
+/* USB insert / probe with USB: start a full cycle at the charge level */
+static int lbc_enter_charging(struct pm8916_lbc_charger *chg)
+{
+ unsigned int v;
+ int ret;
+
+ ret = regmap_update_bits(chg->regmap, chg->reg[LBC_MISC] + PM8916_LBC_MISC_BOOT_DONE,
+ PM8916_LBC_MISC_BOOT_DONE_BIT, PM8916_LBC_MISC_BOOT_DONE_BIT);
+ ret = ret ?: lbc_set_vdd_max(chg, chg->charge_voltage_max);
+ ret = ret ?: regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_MAX, &v);
+ if (!ret && v != chg->ibat_code)
+ ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_MAX, chg->ibat_code);
+ ret = ret ?: lbc_charge_restart(chg, true);
+ chg->latch_polls = 0;
+ chg->nocv_polls = 0;
+ chg->cv_since = 0;
+ return ret;
+}
+
+/* 00/01 latch with USB valid: count polls; restart with the verified edge (VDD_MAX untouched) */
+static bool lbc_handle_latch(struct pm8916_lbc_charger *chg, unsigned int sts, unsigned int path)
+{
+ if (sts == 0 && !(path & PM8916_LBC_USB_PATH_ON))
+ chg->latch_polls++;
+ else
+ chg->latch_polls = 0;
+ if (chg->latch_polls < PM8916_LBC_SUPV_LATCH_POLLS)
+ return false;
+ chg->latch_polls = 0;
+ chg->n_latch++;
+ if (!lbc_charge_restart(chg, false))
+ dev_warn(chg->dev, "supervisor: 00/01 latch in %s (vbat %d uV) -> restart edge #%u\n",
+ lbc_state_name[chg->state], chg->vbat_uv, chg->n_latch);
+ return true;
+}
+
+static bool lbc_check_fault(struct pm8916_lbc_charger *chg, unsigned int failed)
+{
+ if (failed & PM8916_LBC_CHGR_CHG_FAILED_BIT) {
+ chg->fault_reason = LBC_FAULT_TIMER;
+ lbc_supv_log(chg, LBC_ST_FAULT, "CHG_FAILED (safety timer)");
+ return true;
+ }
+ if (chg->vbat_uv > PM8916_LBC_SUPV_OVP_UV) {
+ chg->fault_reason = LBC_FAULT_OVP;
+ lbc_supv_log(chg, LBC_ST_FAULT, "vbat > 4.25 V");
+ return true;
+ }
+ return false;
+}
+
static void lbc_supv_work(struct work_struct *work)
{
struct pm8916_lbc_charger *chg = container_of(work, struct pm8916_lbc_charger, supv_work.work);
unsigned int usb_rt, sts, path, failed;
+ time64_t now = ktime_get_seconds();
int ret, uv;
mutex_lock(&chg->supv_lock);
@@ -347,7 +432,6 @@
chg->vbat_uv = -1;
if (!(usb_rt & PM8916_LBC_USB_USBIN_VALID)) {
- /* CHG_CTRL untouched; the next insert restarts with the verified edge */
if (chg->state != LBC_ST_NO_USB) {
chg->fault_reason = LBC_FAULT_NONE;
lbc_supv_log(chg, LBC_ST_NO_USB, "usb removed");
@@ -358,58 +442,50 @@
switch (chg->state) {
case LBC_ST_INIT:
case LBC_ST_NO_USB:
- /* probe or USB insert: always start a cycle, like stock's usbin path */
- chg->done_polls = 0;
- chg->low_polls = 0;
chg->fault_reason = LBC_FAULT_NONE;
- if (!lbc_charge_restart(chg, true))
+ chg->n_insert++;
+ if (!lbc_enter_charging(chg))
lbc_supv_log(chg, LBC_ST_CHARGING,
chg->state == LBC_ST_INIT ? "probe, usb present" : "usb inserted");
break;
case LBC_ST_CHARGING:
- /*
- * Safety timer expiry: the hardware stops and sets CHG_FAILED. Latch FAULT so the supervisor never
- * restarts; no register write (CHG_EN = 0 is not a verified way to stop the charger).
- */
- if (failed & PM8916_LBC_CHGR_CHG_FAILED_BIT) {
- chg->fault_reason = LBC_FAULT_TIMER;
- lbc_supv_log(chg, LBC_ST_FAULT, "CHG_FAILED (safety timer)");
+ if (lbc_check_fault(chg, failed))
+ break;
+ if (lbc_handle_latch(chg, sts, path)) {
+ chg->cv_since = 0;
+ chg->nocv_polls = 0;
break;
}
- if (chg->vbat_uv > PM8916_LBC_SUPV_OVP_UV) {
- /* defence only: VDD_MAX regulates in hardware; latch FAULT = no further restarts */
- chg->fault_reason = LBC_FAULT_OVP;
- lbc_supv_log(chg, LBC_ST_FAULT, "vbat > 4.25 V");
+ if (!chg->hold_uv || chg->vbat_uv < 0)
break;
+ /* CV run: CV loop bit with VBAT >= full-min; tolerate one poll without the bit (05/07 interleave) */
+ if ((unsigned int)chg->vbat_uv < chg->full_min_uv) {
+ chg->cv_since = 0;
+ chg->nocv_polls = 0;
+ break;
+ }
+ if (sts & PM8916_LBC_CHGR_CV_LOOP) {
+ chg->nocv_polls = 0;
+ if (!chg->cv_since)
+ chg->cv_since = now;
+ } else if (++chg->nocv_polls >= PM8916_LBC_SUPV_NOCV_POLLS) {
+ chg->cv_since = 0;
}
- /* TERMINATED = hardware end-of-charge latch only; CHG_CTRL is not changed */
- if (sts == 0 && !(path & PM8916_LBC_USB_PATH_ON))
- chg->done_polls++;
- else
- chg->done_polls = 0;
- if (chg->done_polls >= PM8916_LBC_SUPV_DONE_POLLS) {
- chg->n_terminated++;
- chg->low_polls = 0;
- lbc_supv_log(chg, LBC_ST_TERMINATED, "hardware end of charge");
+ if (chg->cv_since && now - chg->cv_since >= (time64_t)chg->cv_hold_min * 60) {
+ if (!lbc_set_vdd_max(chg, chg->hold_uv)) {
+ chg->n_hold++;
+ chg->latch_polls = 0;
+ lbc_supv_log(chg, LBC_ST_HOLD, "CV time reached, VDD_MAX -> hold level");
+ }
}
break;
- case LBC_ST_TERMINATED:
- if (!chg->recharge_uv || chg->vbat_uv < 0)
+ case LBC_ST_HOLD:
+ if (lbc_check_fault(chg, failed))
break;
- if ((unsigned int)chg->vbat_uv < chg->recharge_uv)
- chg->low_polls++;
- else
- chg->low_polls = 0;
- if (chg->low_polls >= PM8916_LBC_SUPV_LOW_POLLS) {
- chg->done_polls = 0;
- chg->low_polls = 0;
- if (!lbc_charge_restart(chg, false)) {
- chg->n_recharge++;
- lbc_supv_log(chg, LBC_ST_CHARGING, "recharge");
- }
- }
+ /* keep the board on USB: a 00/01 latch gets the restart edge, VDD_MAX stays at the hold level */
+ lbc_handle_latch(chg, sts, path);
break;
case LBC_ST_FAULT:
@@ -439,15 +515,18 @@
{ "USB_PATH", LBC_USB, PM8916_LBC_USB_PATH_STS },
{ "USB_RT", LBC_USB, PM8916_INT_RT_STS },
{ "BAT_RT", LBC_BAT_IF, PM8916_INT_RT_STS },
+ { "BOOT_DONE", LBC_MISC, PM8916_LBC_MISC_BOOT_DONE },
};
+ time64_t now = ktime_get_seconds();
unsigned int i, v;
mutex_lock(&chg->supv_lock);
seq_printf(s, "state %s fault %s vbat_uv %d\n", lbc_state_name[chg->state],
lbc_fault_name[chg->fault_reason], chg->vbat_uv);
- seq_printf(s, "recharge_uv %u done_polls %d low_polls %d timeout_min %u\n",
- chg->recharge_uv, chg->done_polls, chg->low_polls, chg->timeout_min);
- seq_printf(s, "n_terminated %u n_recharge %u\n", chg->n_terminated, chg->n_recharge);
+ seq_printf(s, "charge_uv %u hold_uv %u full_min_uv %u cv_hold_min %u cv_s %lld nocv %d timeout_min %u\n",
+ chg->charge_voltage_max, chg->hold_uv, chg->full_min_uv, chg->cv_hold_min,
+ chg->cv_since ? now - chg->cv_since : -1LL, chg->nocv_polls, chg->timeout_min);
+ seq_printf(s, "n_insert %u n_hold %u n_latch %u\n", chg->n_insert, chg->n_hold, chg->n_latch);
for (i = 0; i < ARRAY_SIZE(regs); i++) {
if (regmap_read(chg->regmap, chg->reg[regs[i].blk] + regs[i].off, &v))
v = 0xffff;
@@ -485,7 +564,7 @@
power_supply_changed(chg->charger);
- if (chg->vbat_chan)
+ if (READ_ONCE(chg->supv_ready))
mod_delayed_work(system_freezable_wq, &chg->supv_work, 0);
return IRQ_HANDLED;
@@ -584,6 +663,7 @@
return -ENOMEM;
chg->dev = dev;
+ mutex_init(&chg->supv_lock);
chg->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!chg->regmap)
@@ -649,14 +729,13 @@
if (IS_ERR(chg->vbat_chan))
return dev_err_probe(dev, PTR_ERR(chg->vbat_chan), "no VADC vbat channel\n");
- {
- struct device_node *bat = of_parse_phandle(dev->of_node, "monitored-battery", 0);
-
- if (!bat || of_property_read_u32(bat, "re-charge-voltage-microvolt", &chg->recharge_uv))
- dev_warn(dev, "no re-charge-voltage-microvolt: automatic recharge disabled\n");
- of_node_put(bat);
+ chg->cv_hold_min = PM8916_LBC_CV_HOLD_DEFAULT_MIN;
+ device_property_read_u32(dev, "aurora,cv-hold-minutes", &chg->cv_hold_min);
+ if (device_property_read_u32(dev, "aurora,hold-voltage-microvolt", &chg->hold_uv) ||
+ device_property_read_u32(dev, "aurora,full-min-voltage-microvolt", &chg->full_min_uv)) {
+ chg->hold_uv = 0;
+ dev_warn(dev, "no aurora,hold-voltage/full-min-voltage: HOLD disabled\n");
}
- mutex_init(&chg->supv_lock);
ret = devm_delayed_work_autocancel(dev, &chg->supv_work, lbc_supv_work);
if (ret)
return ret;
@@ -696,9 +775,17 @@
if (ret)
return ret;
- dev_info(dev, "recharge supervisor: threshold %u uV, %d x %d ms below, ovp %d uV\n",
- chg->recharge_uv, PM8916_LBC_SUPV_LOW_POLLS, PM8916_LBC_SUPV_PERIOD_MS,
- PM8916_LBC_SUPV_OVP_UV);
+ if (chg->hold_uv && (chg->hold_uv < PM8916_LBC_CHGR_MIN_VOLTAGE ||
+ chg->hold_uv >= chg->charge_voltage_max ||
+ chg->full_min_uv > chg->charge_voltage_max)) {
+ dev_warn(dev, "invalid hold %u / full-min %u for charge level %u: HOLD disabled\n",
+ chg->hold_uv, chg->full_min_uv, chg->charge_voltage_max);
+ chg->hold_uv = 0;
+ }
+ dev_info(dev, "supervisor v3: charge %u uV, hold %u uV, full-min %u uV, cv %u min, IBAT_MAX code %u, ovp %d uV\n",
+ chg->charge_voltage_max, chg->hold_uv, chg->full_min_uv, chg->cv_hold_min,
+ chg->ibat_code, PM8916_LBC_SUPV_OVP_UV);
+ WRITE_ONCE(chg->supv_ready, true);
queue_delayed_work(system_freezable_wq, &chg->supv_work, 0);
return 0;