--- 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;