// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2023, Nikita Travkin */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Two bytes: type + subtype */ #define PM8916_PERPH_TYPE 0x04 #define PM8916_LBC_CHGR_TYPE 0x1502 #define PM8916_LBC_BAT_IF_TYPE 0x1602 #define PM8916_LBC_USB_TYPE 0x1702 #define PM8916_LBC_MISC_TYPE 0x1802 #define PM8916_LBC_CHGR_CHG_OPTION 0x08 #define PM8916_LBC_CHGR_PMIC_CHARGER BIT(7) #define PM8916_LBC_CHGR_CHG_STATUS 0x09 #define PM8916_INT_RT_STS 0x10 #define PM8916_LBC_USB_USBIN_VALID BIT(1) #define PM8916_LBC_CHGR_VDD_MAX 0x40 #define PM8916_LBC_CHGR_VDD_SAFE 0x41 #define PM8916_LBC_CHGR_IBAT_MAX 0x44 #define PM8916_LBC_CHGR_IBAT_SAFE 0x45 #define PM8916_LBC_CHGR_TCHG_MAX_EN 0x60 #define PM8916_LBC_CHGR_TCHG_MAX_ENABLED BIT(7) #define PM8916_LBC_CHGR_TCHG_MAX 0x61 #define PM8916_LBC_CHGR_CHG_CTRL 0x49 #define PM8916_LBC_CHGR_CHG_EN BIT(7) #define PM8916_LBC_CHGR_PSTG_EN BIT(5) #define PM8916_LBC_CHGR_FORCE_BATT_ON BIT(0) #define PM8916_LBC_CHGR_EN_MASK (PM8916_LBC_CHGR_CHG_EN | PM8916_LBC_CHGR_FORCE_BATT_ON) #define PM8916_LBC_CHGR_CHG_FAILED 0x4a #define PM8916_LBC_CHGR_CHG_FAILED_BIT BIT(7) /* USB_CHGPTH 0x08: 0x02 while the charge path is on, 0x01 after end of charge (observed, not documented) */ #define PM8916_LBC_USB_PATH_STS 0x08 #define PM8916_LBC_USB_PATH_ON BIT(1) /* Aurora B10B-3 recharge supervisor */ #define PM8916_LBC_SUPV_PERIOD_MS 30000 #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 #define PM8916_LBC_SUPV_LOG 16 #define PM8916_LBC_CHGR_MIN_CURRENT 90000 #define PM8916_LBC_CHGR_MAX_CURRENT 1440000 #define PM8916_LBC_CHGR_MIN_VOLTAGE 4000000 #define PM8916_LBC_CHGR_MAX_VOLTAGE 4775000 #define PM8916_LBC_CHGR_VOLTAGE_STEP 25000 #define PM8916_LBC_CHGR_MIN_TIME 4 #define PM8916_LBC_CHGR_MAX_TIME 256 struct pm8916_lbc_charger { struct device *dev; struct extcon_dev *edev; struct power_supply *charger; struct power_supply_battery_info *info; struct regmap *regmap; unsigned int reg[4]; bool online; unsigned int charge_voltage_max; unsigned int charge_voltage_safe; unsigned int charge_current_max; unsigned int charge_current_safe; /* 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 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 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; const char *why; } log[PM8916_LBC_SUPV_LOG]; unsigned int log_n; struct dentry *debugfs; }; enum { LBC_ST_INIT = 0, LBC_ST_NO_USB, LBC_ST_CHARGING, LBC_ST_HOLD, LBC_ST_FAULT, }; static const char * const lbc_state_name[] = { [LBC_ST_INIT] = "INIT", [LBC_ST_NO_USB] = "NO_USB", [LBC_ST_CHARGING] = "CHARGING", [LBC_ST_HOLD] = "HOLD", [LBC_ST_FAULT] = "FAULT", }; enum { LBC_FAULT_NONE = 0, LBC_FAULT_TIMER, LBC_FAULT_OVP, }; static const char * const lbc_fault_name[] = { [LBC_FAULT_NONE] = "none", [LBC_FAULT_TIMER] = "safety-timer (CHG_FAILED)", [LBC_FAULT_OVP] = "vbat-overvoltage", }; static const unsigned int pm8916_lbc_charger_cable[] = { EXTCON_USB, EXTCON_NONE, }; enum { LBC_CHGR = 0, LBC_BAT_IF, LBC_USB, LBC_MISC, }; static int pm8916_lbc_charger_configure(struct pm8916_lbc_charger *chg, bool probe) { int ret = 0; unsigned int tmp; chg->charge_voltage_max = clamp_t(u32, chg->charge_voltage_max, PM8916_LBC_CHGR_MIN_VOLTAGE, chg->charge_voltage_safe); tmp = chg->charge_voltage_max - PM8916_LBC_CHGR_MIN_VOLTAGE; tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP; chg->charge_voltage_max = PM8916_LBC_CHGR_MIN_VOLTAGE + tmp * PM8916_LBC_CHGR_VOLTAGE_STEP; /* 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); tmp = clamp_t(u32, chg->charge_current_max, PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT); 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 supervisor owns CHG_EN, so a * constant_charge_current update must not restart charging in HOLD/FAULT. */ 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) goto error; } return ret; error: dev_err(chg->dev, "Failed to configure charging: %pe\n", ERR_PTR(ret)); return ret; } static int pm8916_lbc_charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: switch (chg->state) { case LBC_ST_CHARGING: val->intval = POWER_SUPPLY_STATUS_CHARGING; break; case LBC_ST_HOLD: val->intval = POWER_SUPPLY_STATUS_FULL; break; case LBC_ST_NO_USB: val->intval = POWER_SUPPLY_STATUS_DISCHARGING; break; default: val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; } return 0; case POWER_SUPPLY_PROP_ONLINE: val->intval = chg->online; return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: val->intval = chg->charge_voltage_max; return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: val->intval = chg->charge_current_max; return 0; default: return -EINVAL; }; } static int pm8916_lbc_charger_set_property(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) { struct pm8916_lbc_charger *chg = power_supply_get_drvdata(psy); switch (prop) { case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: { int ret; mutex_lock(&chg->supv_lock); chg->charge_current_max = val->intval; ret = pm8916_lbc_charger_configure(chg, false); mutex_unlock(&chg->supv_lock); return ret; } default: return -EINVAL; } } static int pm8916_lbc_charger_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { switch (psp) { case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: return true; default: return false; } } static enum power_supply_property pm8916_lbc_charger_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, }; /* * Aurora B10B-3 v4 charger supervisor: CHARGE -> HOLD -> (battery) -> CHARGE (v4 = v3 + FAULT safe clamp, B10B-5). * 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: entered on VBAT > 4.25 V or CHG_FAILED; on entry only VDD_MAX = 0x00 (FAULT safe clamp to 4.00 V, B10B-4) + * readback; no further charger writes, no restarts; left only by USB removal (-> NO_USB) or reboot. * The clamp is NOT a guaranteed charge disconnect: with VBAT > 4.00 V charging stops and the board runs * from the battery; with VBAT < 4.00 V the charger may still charge up to 4.00 V. */ static void lbc_supv_log(struct pm8916_lbc_charger *chg, int to, const char *why) { unsigned int i = chg->log_n++ % PM8916_LBC_SUPV_LOG; chg->log[i].t = ktime_get_seconds(); chg->log[i].from = chg->state; chg->log[i].to = to; chg->log[i].vbat = chg->vbat_uv; chg->log[i].why = why; if (to == LBC_ST_FAULT) dev_crit(chg->dev, "supervisor %s -> FAULT (%s), vbat %d uV: restarts blocked, FAULT safe clamp to 4.00 V\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) { 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); } /* the verified restart: CHG_CTRL 0x21 (CHG_EN 0) -> 20 ms -> 0xa0 (CHG_EN 1); on (re)insert also clear CHG_FAILED */ static int lbc_charge_restart(struct pm8916_lbc_charger *chg, bool clear_failed) { int ret; if (clear_failed) { ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_FAILED, PM8916_LBC_CHGR_CHG_FAILED_BIT); if (ret) return ret; } ret = lbc_charge_enable(chg, false); if (ret) return ret; msleep(20); 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; } /* * FAULT safe clamp to 4.00 V (B10B-4): the only charger write on FAULT entry is VDD_MAX = 0x00 (lowest LBC step). * Not a guaranteed charge disconnect - below 4.00 V the charger may still charge up to 4.00 V. */ static void lbc_fault_clamp(struct pm8916_lbc_charger *chg, const char *why) { unsigned int old = 0xff, rb = 0xff; int ret; ret = regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, &old); ret = ret ?: regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, 0x00); ret = ret ?: regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_MAX, &rb); dev_crit(chg->dev, "FAULT safe clamp to 4.00 V: cause %s, vbat %d uV, VDD_MAX old 0x%02x -> readback 0x%02x (ret %d)\n", why, chg->vbat_uv, old, rb, ret); } static bool lbc_enter_fault(struct pm8916_lbc_charger *chg, int reason, const char *why) { chg->fault_reason = reason; lbc_fault_clamp(chg, why); lbc_supv_log(chg, LBC_ST_FAULT, why); return true; } static bool lbc_check_fault(struct pm8916_lbc_charger *chg, unsigned int failed) { if (failed & PM8916_LBC_CHGR_CHG_FAILED_BIT) return lbc_enter_fault(chg, LBC_FAULT_TIMER, "CHG_FAILED (safety timer)"); if (chg->vbat_uv > PM8916_LBC_SUPV_OVP_UV) return lbc_enter_fault(chg, LBC_FAULT_OVP, "vbat > 4.25 V"); 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); ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &usb_rt); ret = ret ?: regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_STATUS, &sts); ret = ret ?: regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_LBC_USB_PATH_STS, &path); ret = ret ?: regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_FAILED, &failed); if (ret) { dev_err(chg->dev, "supervisor: register read failed: %d\n", ret); goto out; } if (!iio_read_channel_processed(chg->vbat_chan, &uv)) chg->vbat_uv = uv; else chg->vbat_uv = -1; if (!(usb_rt & PM8916_LBC_USB_USBIN_VALID)) { if (chg->state != LBC_ST_NO_USB) { chg->fault_reason = LBC_FAULT_NONE; lbc_supv_log(chg, LBC_ST_NO_USB, "usb removed"); } goto out; } switch (chg->state) { case LBC_ST_INIT: case LBC_ST_NO_USB: chg->fault_reason = LBC_FAULT_NONE; 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: 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->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; } 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_HOLD: if (lbc_check_fault(chg, failed)) break; /* 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: /* latched: left only by USB removal/insert or reboot */ break; } out: mutex_unlock(&chg->supv_lock); power_supply_changed(chg->charger); queue_delayed_work(system_freezable_wq, &chg->supv_work, msecs_to_jiffies(PM8916_LBC_SUPV_PERIOD_MS)); } static int lbc_state_show(struct seq_file *s, void *unused) { struct pm8916_lbc_charger *chg = s->private; static const struct { const char *n; int blk; unsigned int off; } regs[] = { { "CHG_STATUS", LBC_CHGR, PM8916_LBC_CHGR_CHG_STATUS }, { "CHGR_RT", LBC_CHGR, PM8916_INT_RT_STS }, { "CHG_CTRL", LBC_CHGR, PM8916_LBC_CHGR_CHG_CTRL }, { "CHG_FAILED", LBC_CHGR, PM8916_LBC_CHGR_CHG_FAILED }, { "VDD_MAX", LBC_CHGR, PM8916_LBC_CHGR_VDD_MAX }, { "IBAT_MAX", LBC_CHGR, PM8916_LBC_CHGR_IBAT_MAX }, { "TCHG_MAX_EN", LBC_CHGR, PM8916_LBC_CHGR_TCHG_MAX_EN }, { "TCHG_MAX", LBC_CHGR, PM8916_LBC_CHGR_TCHG_MAX }, { "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, "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; seq_printf(s, "%s %02x\n", regs[i].n, v); } for (i = chg->log_n > PM8916_LBC_SUPV_LOG ? chg->log_n - PM8916_LBC_SUPV_LOG : 0; i < chg->log_n; i++) { unsigned int j = i % PM8916_LBC_SUPV_LOG; seq_printf(s, "log %u t=%lld %s->%s vbat=%d %s\n", i, chg->log[j].t, lbc_state_name[chg->log[j].from], lbc_state_name[chg->log[j].to], chg->log[j].vbat, chg->log[j].why); } mutex_unlock(&chg->supv_lock); return 0; } DEFINE_SHOW_ATTRIBUTE(lbc_state); static void lbc_debugfs_remove(void *data) { debugfs_remove_recursive(data); } static irqreturn_t pm8916_lbc_charger_state_changed_irq(int irq, void *data) { struct pm8916_lbc_charger *chg = data; unsigned int tmp; int ret; ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp); if (ret) return IRQ_HANDLED; chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID); extcon_set_state_sync(chg->edev, EXTCON_USB, chg->online); power_supply_changed(chg->charger); if (READ_ONCE(chg->supv_ready)) mod_delayed_work(system_freezable_wq, &chg->supv_work, 0); return IRQ_HANDLED; } static int pm8916_lbc_charger_probe_dt(struct pm8916_lbc_charger *chg) { struct device *dev = chg->dev; int ret = 0; unsigned int tmp; ret = device_property_read_u32(dev, "qcom,fast-charge-safe-voltage", &chg->charge_voltage_safe); if (ret) return ret; if (chg->charge_voltage_safe < PM8916_LBC_CHGR_MIN_VOLTAGE) return -EINVAL; chg->charge_voltage_safe = clamp_t(u32, chg->charge_voltage_safe, PM8916_LBC_CHGR_MIN_VOLTAGE, PM8916_LBC_CHGR_MAX_VOLTAGE); tmp = chg->charge_voltage_safe - PM8916_LBC_CHGR_MIN_VOLTAGE; tmp /= PM8916_LBC_CHGR_VOLTAGE_STEP; ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_VDD_SAFE, tmp); if (ret) return ret; ret = device_property_read_u32(dev, "qcom,fast-charge-safe-current", &chg->charge_current_safe); if (ret) return ret; if (chg->charge_current_safe < PM8916_LBC_CHGR_MIN_CURRENT) return -EINVAL; chg->charge_current_safe = clamp_t(u32, chg->charge_current_safe, PM8916_LBC_CHGR_MIN_CURRENT, PM8916_LBC_CHGR_MAX_CURRENT); chg->charge_current_max = chg->charge_current_safe; tmp = chg->charge_current_safe / PM8916_LBC_CHGR_MIN_CURRENT - 1; ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_IBAT_SAFE, tmp); if (ret) return ret; /* * Keep the charger safety timer running (upstream disabled it). Same sequence as the * downstream qpnp-linear-charger: timer off, TCHG_MAX = minutes / 4 - 1 (4..512 min), timer on. */ chg->timeout_min = PM8916_LBC_TIMEOUT_DEFAULT_MIN; device_property_read_u32(dev, "qcom,charge-timeout-minutes", &chg->timeout_min); chg->timeout_min = clamp_t(u32, chg->timeout_min, PM8916_LBC_TIMEOUT_STEP_MIN, PM8916_LBC_TIMEOUT_MAX_MIN); chg->timeout_min -= chg->timeout_min % PM8916_LBC_TIMEOUT_STEP_MIN; ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00); if (ret) return ret; tmp = chg->timeout_min / PM8916_LBC_TIMEOUT_STEP_MIN - 1; ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX, tmp); if (ret) return ret; ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, PM8916_LBC_CHGR_TCHG_MAX_ENABLED); if (ret) return ret; ret = regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX, &tmp); if (ret) return ret; if (tmp != chg->timeout_min / PM8916_LBC_TIMEOUT_STEP_MIN - 1) return dev_err_probe(dev, -EIO, "TCHG_MAX readback 0x%x != 0x%x\n", tmp, chg->timeout_min / PM8916_LBC_TIMEOUT_STEP_MIN - 1); dev_info(dev, "safety timer %u min (TCHG_MAX 0x%02x), enabled\n", chg->timeout_min, tmp); return ret; } static const struct power_supply_desc pm8916_lbc_charger_psy_desc = { .name = "pm8916-lbc-chgr", .type = POWER_SUPPLY_TYPE_USB, .properties = pm8916_lbc_charger_properties, .num_properties = ARRAY_SIZE(pm8916_lbc_charger_properties), .get_property = pm8916_lbc_charger_get_property, .set_property = pm8916_lbc_charger_set_property, .property_is_writeable = pm8916_lbc_charger_property_is_writeable, }; static int pm8916_lbc_charger_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct pm8916_lbc_charger *chg; struct power_supply_config psy_cfg = {}; int ret, len, irq; unsigned int tmp; chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); if (!chg) return -ENOMEM; chg->dev = dev; mutex_init(&chg->supv_lock); chg->regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!chg->regmap) return -ENODEV; len = device_property_count_u32(dev, "reg"); if (len < 0) return len; if (len != 4) return dev_err_probe(dev, -EINVAL, "Wrong amount of reg values: %d (4 expected)\n", len); ret = device_property_read_u32_array(dev, "reg", chg->reg, len); if (ret) return ret; ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_PERPH_TYPE, &tmp, 2); if (ret) goto comm_error; if (tmp != PM8916_LBC_CHGR_TYPE) goto type_error; ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_BAT_IF] + PM8916_PERPH_TYPE, &tmp, 2); if (ret) goto comm_error; if (tmp != PM8916_LBC_BAT_IF_TYPE) goto type_error; ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_USB] + PM8916_PERPH_TYPE, &tmp, 2); if (ret) goto comm_error; if (tmp != PM8916_LBC_USB_TYPE) goto type_error; ret = regmap_bulk_read(chg->regmap, chg->reg[LBC_MISC] + PM8916_PERPH_TYPE, &tmp, 2); if (ret) goto comm_error; if (tmp != PM8916_LBC_MISC_TYPE) goto type_error; ret = regmap_read(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_CHG_OPTION, &tmp); if (ret) goto comm_error; if (tmp != PM8916_LBC_CHGR_PMIC_CHARGER) dev_err_probe(dev, -ENODEV, "The system is using an external charger\n"); ret = pm8916_lbc_charger_probe_dt(chg); if (ret) return dev_err_probe(dev, ret, "Error while parsing device tree\n"); psy_cfg.drv_data = chg; psy_cfg.fwnode = dev_fwnode(dev); chg->charger = devm_power_supply_register(dev, &pm8916_lbc_charger_psy_desc, &psy_cfg); if (IS_ERR(chg->charger)) return dev_err_probe(dev, PTR_ERR(chg->charger), "Unable to register charger\n"); ret = power_supply_get_battery_info(chg->charger, &chg->info); if (ret) return dev_err_probe(dev, ret, "Unable to get battery info\n"); chg->vbat_chan = devm_iio_channel_get(dev, "vbat"); if (IS_ERR(chg->vbat_chan)) return dev_err_probe(dev, PTR_ERR(chg->vbat_chan), "no VADC vbat channel\n"); 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"); } ret = devm_delayed_work_autocancel(dev, &chg->supv_work, lbc_supv_work); if (ret) return ret; irq = platform_get_irq_byname(pdev, "usb_vbus"); if (irq < 0) return irq; chg->edev = devm_extcon_dev_allocate(dev, pm8916_lbc_charger_cable); if (IS_ERR(chg->edev)) return PTR_ERR(chg->edev); ret = devm_extcon_dev_register(dev, chg->edev); if (ret < 0) return dev_err_probe(dev, ret, "failed to register extcon device\n"); ret = devm_request_threaded_irq(dev, irq, NULL, pm8916_lbc_charger_state_changed_irq, IRQF_ONESHOT, "pm8916_lbc", chg); if (ret) return ret; ret = regmap_read(chg->regmap, chg->reg[LBC_USB] + PM8916_INT_RT_STS, &tmp); if (ret) goto comm_error; chg->online = !!(tmp & PM8916_LBC_USB_USBIN_VALID); extcon_set_state_sync(chg->edev, EXTCON_USB, chg->online); chg->charge_voltage_max = chg->info->voltage_max_design_uv; ret = pm8916_lbc_charger_configure(chg, true); if (ret) return ret; chg->debugfs = debugfs_create_dir("pm8916_lbc", NULL); debugfs_create_file("state", 0444, chg->debugfs, chg, &lbc_state_fops); ret = devm_add_action_or_reset(dev, lbc_debugfs_remove, chg->debugfs); if (ret) return ret; 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; comm_error: return dev_err_probe(dev, ret, "Unable to communicate with device\n"); type_error: return dev_err_probe(dev, -ENODEV, "Device reported wrong type: 0x%X\n", tmp); } static const struct of_device_id pm8916_lbc_charger_of_match[] = { { .compatible = "qcom,pm8916-lbc", }, {} }; MODULE_DEVICE_TABLE(of, pm8916_lbc_charger_of_match); static struct platform_driver pm8916_lbc_charger_driver = { .driver = { .name = "pm8916-lbc", .of_match_table = pm8916_lbc_charger_of_match, }, .probe = pm8916_lbc_charger_probe, }; module_platform_driver(pm8916_lbc_charger_driver); MODULE_DESCRIPTION("pm8916 LBC driver"); MODULE_AUTHOR("Nikita Travkin "); MODULE_LICENSE("GPL");