- 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
838 lines
27 KiB
C
838 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2023, Nikita Travkin <nikita@trvn.ru>
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/property.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/extcon-provider.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/devm-helpers.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
/* 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 <nikita@trvn.ru>");
|
|
MODULE_LICENSE("GPL");
|