diff options
Diffstat (limited to 'drivers/power/supply/bd71828-power.c')
| -rw-r--r-- | drivers/power/supply/bd71828-power.c | 1049 |
1 files changed, 1049 insertions, 0 deletions
diff --git a/drivers/power/supply/bd71828-power.c b/drivers/power/supply/bd71828-power.c new file mode 100644 index 000000000000..f667baedeb77 --- /dev/null +++ b/drivers/power/supply/bd71828-power.c @@ -0,0 +1,1049 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* ROHM BD71815, BD71828 and BD71878 Charger driver */ + +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mfd/rohm-bd71815.h> +#include <linux/mfd/rohm-bd71828.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +/* common defines */ +#define BD7182x_MASK_VBAT_U 0x1f +#define BD7182x_MASK_VDCIN_U 0x0f +#define BD7182x_MASK_IBAT_U 0x3f +#define BD7182x_MASK_CURDIR_DISCHG 0x80 +#define BD7182x_MASK_CHG_STATE 0x7f +#define BD7182x_MASK_BAT_TEMP 0x07 +#define BD7182x_MASK_DCIN_DET BIT(0) +#define BD7182x_MASK_CONF_PON BIT(0) +#define BD71815_MASK_CONF_XSTB BIT(1) +#define BD7182x_MASK_BAT_STAT 0x3f +#define BD7182x_MASK_DCIN_STAT 0x07 + +#define BD7182x_MASK_WDT_AUTO 0x40 +#define BD7182x_MASK_VBAT_ALM_LIMIT_U 0x01 +#define BD7182x_MASK_CHG_EN 0x01 + +#define BD7182x_DCIN_COLLAPSE_DEFAULT 0x36 + +#define MAX_CURRENT_DEFAULT 890000 /* uA */ +#define AC_NAME "bd71828_ac" +#define BAT_NAME "bd71828_bat" + +#define BAT_OPEN 0x7 + +/* + * VBAT Low voltage detection Threshold + * 0x00D4*16mV = 212*0.016 = 3.392v + */ +#define VBAT_LOW_TH 0x00D4 + +struct pwr_regs { + u8 vbat_avg; + u8 ibat; + u8 ibat_avg; + u8 btemp_vth; + u8 chg_state; + u8 bat_temp; + u8 dcin_stat; + u8 dcin_collapse_limit; + u8 chg_set1; + u8 chg_en; + u8 vbat_alm_limit_u; + u8 conf; + u8 vdcin; +}; + +static const struct pwr_regs pwr_regs_bd71828 = { + .vbat_avg = BD71828_REG_VBAT_U, + .ibat = BD71828_REG_IBAT_U, + .ibat_avg = BD71828_REG_IBAT_AVG_U, + .btemp_vth = BD71828_REG_VM_BTMP_U, + .chg_state = BD71828_REG_CHG_STATE, + .bat_temp = BD71828_REG_BAT_TEMP, + .dcin_stat = BD71828_REG_DCIN_STAT, + .dcin_collapse_limit = BD71828_REG_DCIN_CLPS, + .chg_set1 = BD71828_REG_CHG_SET1, + .chg_en = BD71828_REG_CHG_EN, + .vbat_alm_limit_u = BD71828_REG_ALM_VBAT_LIMIT_U, + .conf = BD71828_REG_CONF, + .vdcin = BD71828_REG_VDCIN_U, +}; + +static const struct pwr_regs pwr_regs_bd71815 = { + .vbat_avg = BD71815_REG_VM_SA_VBAT_U, + /* BD71815 does not have separate current and current avg */ + .ibat = BD71815_REG_CC_CURCD_U, + .ibat_avg = BD71815_REG_CC_CURCD_U, + + .btemp_vth = BD71815_REG_VM_BTMP, + .chg_state = BD71815_REG_CHG_STATE, + .bat_temp = BD71815_REG_BAT_TEMP, + .dcin_stat = BD71815_REG_DCIN_STAT, + .dcin_collapse_limit = BD71815_REG_DCIN_CLPS, + .chg_set1 = BD71815_REG_CHG_SET1, + .chg_en = BD71815_REG_CHG_SET1, + .vbat_alm_limit_u = BD71815_REG_ALM_VBAT_TH_U, + .conf = BD71815_REG_CONF, + + .vdcin = BD71815_REG_VM_DCIN_U, +}; + +struct bd71828_power { + struct regmap *regmap; + enum rohm_chip_type chip_type; + struct device *dev; + struct power_supply *ac; + struct power_supply *bat; + + const struct pwr_regs *regs; + /* Reg val to uA */ + int curr_factor; + int rsens; + int (*get_temp)(struct bd71828_power *pwr, int *temp); + int (*bat_inserted)(struct bd71828_power *pwr); +}; + +static int bd7182x_write16(struct bd71828_power *pwr, int reg, u16 val) +{ + __be16 tmp; + + tmp = cpu_to_be16(val); + + return regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp)); +} + +static int bd7182x_read16_himask(struct bd71828_power *pwr, int reg, int himask, + u16 *val) +{ + struct regmap *regmap = pwr->regmap; + int ret; + __be16 rvals; + u8 *tmp = (u8 *)&rvals; + + ret = regmap_bulk_read(regmap, reg, &rvals, sizeof(*val)); + if (!ret) { + *tmp &= himask; + *val = be16_to_cpu(rvals); + } + + return ret; +} + +static int bd71828_get_vbat(struct bd71828_power *pwr, int *vcell) +{ + u16 tmp_vcell; + int ret; + + ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_avg, + BD7182x_MASK_VBAT_U, &tmp_vcell); + if (ret) + dev_err(pwr->dev, "Failed to read battery average voltage\n"); + else + *vcell = ((int)tmp_vcell) * 1000; + + return ret; +} + +static int bd71828_get_current_ds_adc(struct bd71828_power *pwr, int *curr, int *curr_avg) +{ + __be16 tmp_curr; + char *tmp = (char *)&tmp_curr; + int dir = 1; + int regs[] = { pwr->regs->ibat, pwr->regs->ibat_avg }; + int *vals[] = { curr, curr_avg }; + int ret, i; + + for (dir = 1, i = 0; i < ARRAY_SIZE(regs); i++) { + ret = regmap_bulk_read(pwr->regmap, regs[i], &tmp_curr, + sizeof(tmp_curr)); + if (ret) + break; + + if (*tmp & BD7182x_MASK_CURDIR_DISCHG) + dir = -1; + + *tmp &= BD7182x_MASK_IBAT_U; + + *vals[i] = dir * ((int)be16_to_cpu(tmp_curr)) * pwr->curr_factor; + } + + return ret; +} + +/* Unit is tenths of degree C */ +static int bd71815_get_temp(struct bd71828_power *pwr, int *temp) +{ + struct regmap *regmap = pwr->regmap; + int ret; + int t; + + ret = regmap_read(regmap, pwr->regs->btemp_vth, &t); + if (ret) + return ret; + + t = 200 - t; + + if (t > 200) { + dev_err(pwr->dev, "Failed to read battery temperature\n"); + return -ENODATA; + } + + return 0; +} + +/* Unit is tenths of degree C */ +static int bd71828_get_temp(struct bd71828_power *pwr, int *temp) +{ + u16 t; + int ret; + int tmp = 200 * 10000; + + ret = bd7182x_read16_himask(pwr, pwr->regs->btemp_vth, + BD71828_MASK_VM_BTMP_U, &t); + if (ret) + return ret; + + if (t > 3200) { + dev_err(pwr->dev, + "Failed to read battery temperature\n"); + return -ENODATA; + } + + tmp -= 625ULL * (unsigned int)t; + *temp = tmp / 1000; + + return ret; +} + +static int bd71828_charge_status(struct bd71828_power *pwr, + int *s, int *h) +{ + unsigned int state; + int status, health; + int ret = 1; + + ret = regmap_read(pwr->regmap, pwr->regs->chg_state, &state); + if (ret) { + dev_err(pwr->dev, "charger status reading failed (%d)\n", ret); + return ret; + } + + state &= BD7182x_MASK_CHG_STATE; + + dev_dbg(pwr->dev, "CHG_STATE %d\n", state); + + switch (state) { + case 0x00: + status = POWER_SUPPLY_STATUS_DISCHARGING; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x01: + case 0x02: + case 0x03: + case 0x0E: + status = POWER_SUPPLY_STATUS_CHARGING; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x0F: + status = POWER_SUPPLY_STATUS_FULL; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case 0x30: + case 0x31: + case 0x32: + case 0x40: + status = POWER_SUPPLY_STATUS_DISCHARGING; + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x7f: + default: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + health = POWER_SUPPLY_HEALTH_DEAD; + break; + } + + if (s) + *s = status; + if (h) + *h = health; + + return ret; +} + +static int get_chg_online(struct bd71828_power *pwr, int *chg_online) +{ + int r, ret; + + ret = regmap_read(pwr->regmap, pwr->regs->dcin_stat, &r); + if (ret) { + dev_err(pwr->dev, "Failed to read DCIN status\n"); + return ret; + } + *chg_online = ((r & BD7182x_MASK_DCIN_DET) != 0); + + return 0; +} + +static int get_bat_online(struct bd71828_power *pwr, int *bat_online) +{ + int r, ret; + + ret = regmap_read(pwr->regmap, pwr->regs->bat_temp, &r); + if (ret) { + dev_err(pwr->dev, "Failed to read battery temperature\n"); + return ret; + } + *bat_online = ((r & BD7182x_MASK_BAT_TEMP) != BAT_OPEN); + + return 0; +} + +static int bd71828_bat_inserted(struct bd71828_power *pwr) +{ + int ret, val; + + ret = regmap_read(pwr->regmap, pwr->regs->conf, &val); + if (ret) { + dev_err(pwr->dev, "Failed to read CONF register\n"); + return 0; + } + ret = val & BD7182x_MASK_CONF_PON; + + if (ret) + regmap_update_bits(pwr->regmap, pwr->regs->conf, + BD7182x_MASK_CONF_PON, 0); + + return ret; +} + +static int bd71815_bat_inserted(struct bd71828_power *pwr) +{ + int ret, val; + + ret = regmap_read(pwr->regmap, pwr->regs->conf, &val); + if (ret) { + dev_err(pwr->dev, "Failed to read CONF register\n"); + return ret; + } + + ret = !(val & BD71815_MASK_CONF_XSTB); + if (ret) + regmap_write(pwr->regmap, pwr->regs->conf, val | + BD71815_MASK_CONF_XSTB); + + return ret; +} + +static int bd71828_init_hardware(struct bd71828_power *pwr) +{ + int ret; + + /* TODO: Collapse limit should come from device-tree ? */ + ret = regmap_write(pwr->regmap, pwr->regs->dcin_collapse_limit, + BD7182x_DCIN_COLLAPSE_DEFAULT); + if (ret) { + dev_err(pwr->dev, "Failed to write DCIN collapse limit\n"); + return ret; + } + + ret = pwr->bat_inserted(pwr); + if (ret < 0) + return ret; + + if (ret) { + /* WDT_FST auto set */ + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_set1, + BD7182x_MASK_WDT_AUTO, + BD7182x_MASK_WDT_AUTO); + if (ret) + return ret; + + ret = bd7182x_write16(pwr, pwr->regs->vbat_alm_limit_u, + VBAT_LOW_TH); + if (ret) + return ret; + + /* + * On BD71815 "we mask the power-state" from relax detection. + * I am unsure what the impact of the power-state would be if + * we didn't - but this is what the vendor driver did - and + * that driver has been used in few projects so I just assume + * this is needed. + */ + if (pwr->chip_type == ROHM_CHIP_TYPE_BD71815) { + ret = regmap_set_bits(pwr->regmap, + BD71815_REG_REX_CTRL_1, + REX_PMU_STATE_MASK); + if (ret) + return ret; + } + } + + return 0; +} + +static int bd71828_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); + u32 vot; + u16 tmp; + int online; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = get_chg_online(pwr, &online); + if (!ret) + val->intval = online; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bd7182x_read16_himask(pwr, pwr->regs->vdcin, + BD7182x_MASK_VDCIN_U, &tmp); + if (ret) + return ret; + + vot = tmp; + /* 5 milli volt steps */ + val->intval = 5000 * vot; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bd71828_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); + int ret = 0; + int status, health, tmp, curr, curr_avg, chg_en; + + if (psp == POWER_SUPPLY_PROP_STATUS || + psp == POWER_SUPPLY_PROP_HEALTH || + psp == POWER_SUPPLY_PROP_CHARGE_TYPE) + ret = bd71828_charge_status(pwr, &status, &health); + else if (psp == POWER_SUPPLY_PROP_CURRENT_AVG || + psp == POWER_SUPPLY_PROP_CURRENT_NOW) + ret = bd71828_get_current_ds_adc(pwr, &curr, &curr_avg); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = health; + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = get_bat_online(pwr, &tmp); + if (!ret) + val->intval = tmp; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bd71828_get_vbat(pwr, &tmp); + val->intval = tmp; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = curr_avg; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = curr; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = MAX_CURRENT_DEFAULT; + break; + case POWER_SUPPLY_PROP_TEMP: + ret = pwr->get_temp(pwr, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + ret = regmap_read(pwr->regmap, pwr->regs->chg_en, &chg_en); + if (ret) + return ret; + + val->intval = (chg_en & BD7182x_MASK_CHG_EN) ? + POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO : + POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int bd71828_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + if (val->intval == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en, + BD7182x_MASK_CHG_EN, + BD7182x_MASK_CHG_EN); + else + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en, + BD7182x_MASK_CHG_EN, + 0); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int bd71828_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return true; + default: + return false; + } +} + +/** @brief ac properties */ +static const enum power_supply_property bd71828_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static const enum power_supply_property bd71828_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, +}; + +/** @brief powers supplied by bd71828_ac */ +static char *bd71828_ac_supplied_to[] = { + BAT_NAME, +}; + +static const struct power_supply_desc bd71828_ac_desc = { + .name = AC_NAME, + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = bd71828_charger_props, + .num_properties = ARRAY_SIZE(bd71828_charger_props), + .get_property = bd71828_charger_get_property, +}; + +static const struct power_supply_desc bd71828_bat_desc = { + .name = BAT_NAME, + .type = POWER_SUPPLY_TYPE_BATTERY, + .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE), + .properties = bd71828_battery_props, + .num_properties = ARRAY_SIZE(bd71828_battery_props), + .get_property = bd71828_battery_get_property, + .set_property = bd71828_battery_set_property, + .property_is_writeable = bd71828_battery_property_is_writeable, +}; + +#define RSENS_CURR 10000000LLU + +#define BD_ISR_NAME(name) \ +bd7181x_##name##_isr + +#define BD_ISR_BAT(name, print, run_gauge) \ +static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ +{ \ + struct bd71828_power *pwr = (struct bd71828_power *)data; \ + \ + dev_dbg(pwr->dev, "%s\n", print); \ + power_supply_changed(pwr->bat); \ + \ + return IRQ_HANDLED; \ +} + +#define BD_ISR_AC(name, print, run_gauge) \ +static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ +{ \ + struct bd71828_power *pwr = (struct bd71828_power *)data; \ + \ + power_supply_changed(pwr->ac); \ + dev_dbg(pwr->dev, "%s\n", print); \ + power_supply_changed(pwr->bat); \ + \ + return IRQ_HANDLED; \ +} + +#define BD_ISR_DUMMY(name, print) \ +static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ +{ \ + struct bd71828_power *pwr = (struct bd71828_power *)data; \ + \ + dev_dbg(pwr->dev, "%s\n", print); \ + \ + return IRQ_HANDLED; \ +} + +BD_ISR_BAT(chg_state_changed, "CHG state changed", true) +/* DCIN voltage changes */ +BD_ISR_AC(dcin_removed, "DCIN removed", true) +BD_ISR_AC(clps_out, "DCIN voltage back to normal", true) +BD_ISR_AC(clps_in, "DCIN voltage collapsed", false) +BD_ISR_AC(dcin_ovp_res, "DCIN voltage normal", true) +BD_ISR_AC(dcin_ovp_det, "DCIN OVER VOLTAGE", true) + +BD_ISR_DUMMY(dcin_mon_det, "DCIN voltage below threshold") +BD_ISR_DUMMY(dcin_mon_res, "DCIN voltage above threshold") + +BD_ISR_DUMMY(vsys_uv_res, "VSYS under-voltage cleared") +BD_ISR_DUMMY(vsys_uv_det, "VSYS under-voltage") +BD_ISR_DUMMY(vsys_low_res, "'VSYS low' cleared") +BD_ISR_DUMMY(vsys_low_det, "VSYS low") +BD_ISR_DUMMY(vsys_mon_res, "VSYS mon - resumed") +BD_ISR_DUMMY(vsys_mon_det, "VSYS mon - detected") +BD_ISR_BAT(chg_wdg_temp, "charger temperature watchdog triggered", true) +BD_ISR_BAT(chg_wdg, "charging watchdog triggered", true) +BD_ISR_BAT(bat_removed, "Battery removed", true) +BD_ISR_BAT(bat_det, "Battery detected", true) +/* TODO: Verify the meaning of these interrupts */ +BD_ISR_BAT(rechg_det, "Recharging", true) +BD_ISR_BAT(rechg_res, "Recharge ending", true) +BD_ISR_DUMMY(temp_transit, "Temperature transition") +BD_ISR_BAT(therm_rmv, "bd71815-therm-rmv", false) +BD_ISR_BAT(therm_det, "bd71815-therm-det", true) +BD_ISR_BAT(bat_dead, "bd71815-bat-dead", false) +BD_ISR_BAT(bat_short_res, "bd71815-bat-short-res", true) +BD_ISR_BAT(bat_short, "bd71815-bat-short-det", false) +BD_ISR_BAT(bat_low_res, "bd71815-bat-low-res", true) +BD_ISR_BAT(bat_low, "bd71815-bat-low-det", true) +BD_ISR_BAT(bat_ov_res, "bd71815-bat-over-res", true) +/* What should we do here? */ +BD_ISR_BAT(bat_ov, "bd71815-bat-over-det", false) +BD_ISR_BAT(bat_mon_res, "bd71815-bat-mon-res", true) +BD_ISR_BAT(bat_mon, "bd71815-bat-mon-det", true) +BD_ISR_BAT(bat_cc_mon, "bd71815-bat-cc-mon2", false) +BD_ISR_BAT(bat_oc1_res, "bd71815-bat-oc1-res", true) +BD_ISR_BAT(bat_oc1, "bd71815-bat-oc1-det", false) +BD_ISR_BAT(bat_oc2_res, "bd71815-bat-oc2-res", true) +BD_ISR_BAT(bat_oc2, "bd71815-bat-oc2-det", false) +BD_ISR_BAT(bat_oc3_res, "bd71815-bat-oc3-res", true) +BD_ISR_BAT(bat_oc3, "bd71815-bat-oc3-det", false) +BD_ISR_BAT(temp_bat_low_res, "bd71815-temp-bat-low-res", true) +BD_ISR_BAT(temp_bat_low, "bd71815-temp-bat-low-det", true) +BD_ISR_BAT(temp_bat_hi_res, "bd71815-temp-bat-hi-res", true) +BD_ISR_BAT(temp_bat_hi, "bd71815-temp-bat-hi-det", true) + +static irqreturn_t bd7182x_dcin_removed(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + power_supply_changed(pwr->ac); + dev_dbg(pwr->dev, "DCIN removed\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd718x7_chg_done(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd7182x_dcin_detected(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "DCIN inserted\n"); + power_supply_changed(pwr->ac); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_vbat_low_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VBAT LOW Resumed\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_vbat_low_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VBAT LOW Detected\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_bat_hi_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_warn(pwr->dev, "Overtemp Detected\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_bat_hi_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "Overtemp Resumed\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_bat_low_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "Lowtemp Detected\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_bat_low_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "Lowtemp Resumed\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_vf_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VF Detected\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_vf_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VF Resumed\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_vf125_det(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VF125 Detected\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +static irqreturn_t bd71828_temp_vf125_res(int irq, void *data) +{ + struct bd71828_power *pwr = (struct bd71828_power *)data; + + dev_dbg(pwr->dev, "VF125 Resumed\n"); + power_supply_changed(pwr->bat); + + return IRQ_HANDLED; +} + +struct bd7182x_irq_res { + const char *name; + irq_handler_t handler; +}; + +#define BDIRQ(na, hn) { .name = (na), .handler = (hn) } + +static int bd7182x_get_irqs(struct platform_device *pdev, + struct bd71828_power *pwr) +{ + int i, irq, ret; + static const struct bd7182x_irq_res bd71815_irqs[] = { + BDIRQ("bd71815-dcin-rmv", BD_ISR_NAME(dcin_removed)), + BDIRQ("bd71815-dcin-clps-out", BD_ISR_NAME(clps_out)), + BDIRQ("bd71815-dcin-clps-in", BD_ISR_NAME(clps_in)), + BDIRQ("bd71815-dcin-ovp-res", BD_ISR_NAME(dcin_ovp_res)), + BDIRQ("bd71815-dcin-ovp-det", BD_ISR_NAME(dcin_ovp_det)), + BDIRQ("bd71815-dcin-mon-res", BD_ISR_NAME(dcin_mon_res)), + BDIRQ("bd71815-dcin-mon-det", BD_ISR_NAME(dcin_mon_det)), + + BDIRQ("bd71815-vsys-uv-res", BD_ISR_NAME(vsys_uv_res)), + BDIRQ("bd71815-vsys-uv-det", BD_ISR_NAME(vsys_uv_det)), + BDIRQ("bd71815-vsys-low-res", BD_ISR_NAME(vsys_low_res)), + BDIRQ("bd71815-vsys-low-det", BD_ISR_NAME(vsys_low_det)), + BDIRQ("bd71815-vsys-mon-res", BD_ISR_NAME(vsys_mon_res)), + BDIRQ("bd71815-vsys-mon-det", BD_ISR_NAME(vsys_mon_det)), + BDIRQ("bd71815-chg-wdg-temp", BD_ISR_NAME(chg_wdg_temp)), + BDIRQ("bd71815-chg-wdg", BD_ISR_NAME(chg_wdg)), + BDIRQ("bd71815-rechg-det", BD_ISR_NAME(rechg_det)), + BDIRQ("bd71815-rechg-res", BD_ISR_NAME(rechg_res)), + BDIRQ("bd71815-ranged-temp-transit", BD_ISR_NAME(temp_transit)), + BDIRQ("bd71815-chg-state-change", BD_ISR_NAME(chg_state_changed)), + BDIRQ("bd71815-bat-temp-normal", bd71828_temp_bat_hi_res), + BDIRQ("bd71815-bat-temp-erange", bd71828_temp_bat_hi_det), + BDIRQ("bd71815-bat-rmv", BD_ISR_NAME(bat_removed)), + BDIRQ("bd71815-bat-det", BD_ISR_NAME(bat_det)), + + /* Add ISRs for these */ + BDIRQ("bd71815-therm-rmv", BD_ISR_NAME(therm_rmv)), + BDIRQ("bd71815-therm-det", BD_ISR_NAME(therm_det)), + BDIRQ("bd71815-bat-dead", BD_ISR_NAME(bat_dead)), + BDIRQ("bd71815-bat-short-res", BD_ISR_NAME(bat_short_res)), + BDIRQ("bd71815-bat-short-det", BD_ISR_NAME(bat_short)), + BDIRQ("bd71815-bat-low-res", BD_ISR_NAME(bat_low_res)), + BDIRQ("bd71815-bat-low-det", BD_ISR_NAME(bat_low)), + BDIRQ("bd71815-bat-over-res", BD_ISR_NAME(bat_ov_res)), + BDIRQ("bd71815-bat-over-det", BD_ISR_NAME(bat_ov)), + BDIRQ("bd71815-bat-mon-res", BD_ISR_NAME(bat_mon_res)), + BDIRQ("bd71815-bat-mon-det", BD_ISR_NAME(bat_mon)), + /* cc-mon 1 & 3 ? */ + BDIRQ("bd71815-bat-cc-mon2", BD_ISR_NAME(bat_cc_mon)), + BDIRQ("bd71815-bat-oc1-res", BD_ISR_NAME(bat_oc1_res)), + BDIRQ("bd71815-bat-oc1-det", BD_ISR_NAME(bat_oc1)), + BDIRQ("bd71815-bat-oc2-res", BD_ISR_NAME(bat_oc2_res)), + BDIRQ("bd71815-bat-oc2-det", BD_ISR_NAME(bat_oc2)), + BDIRQ("bd71815-bat-oc3-res", BD_ISR_NAME(bat_oc3_res)), + BDIRQ("bd71815-bat-oc3-det", BD_ISR_NAME(bat_oc3)), + BDIRQ("bd71815-temp-bat-low-res", BD_ISR_NAME(temp_bat_low_res)), + BDIRQ("bd71815-temp-bat-low-det", BD_ISR_NAME(temp_bat_low)), + BDIRQ("bd71815-temp-bat-hi-res", BD_ISR_NAME(temp_bat_hi_res)), + BDIRQ("bd71815-temp-bat-hi-det", BD_ISR_NAME(temp_bat_hi)), + /* + * TODO: add rest of the IRQs and re-check the handling. + * Check the bd71815-bat-cc-mon1, bd71815-bat-cc-mon3, + * bd71815-bat-low-res, bd71815-bat-low-det, + * bd71815-bat-hi-res, bd71815-bat-hi-det. + */ + }; + static const struct bd7182x_irq_res bd71828_irqs[] = { + BDIRQ("bd71828-chg-done", bd718x7_chg_done), + BDIRQ("bd71828-pwr-dcin-in", bd7182x_dcin_detected), + BDIRQ("bd71828-pwr-dcin-out", bd7182x_dcin_removed), + BDIRQ("bd71828-vbat-normal", bd71828_vbat_low_res), + BDIRQ("bd71828-vbat-low", bd71828_vbat_low_det), + BDIRQ("bd71828-btemp-hi", bd71828_temp_bat_hi_det), + BDIRQ("bd71828-btemp-cool", bd71828_temp_bat_hi_res), + BDIRQ("bd71828-btemp-lo", bd71828_temp_bat_low_det), + BDIRQ("bd71828-btemp-warm", bd71828_temp_bat_low_res), + BDIRQ("bd71828-temp-hi", bd71828_temp_vf_det), + BDIRQ("bd71828-temp-norm", bd71828_temp_vf_res), + BDIRQ("bd71828-temp-125-over", bd71828_temp_vf125_det), + BDIRQ("bd71828-temp-125-under", bd71828_temp_vf125_res), + }; + int num_irqs; + const struct bd7182x_irq_res *irqs; + + switch (pwr->chip_type) { + case ROHM_CHIP_TYPE_BD71828: + irqs = &bd71828_irqs[0]; + num_irqs = ARRAY_SIZE(bd71828_irqs); + break; + case ROHM_CHIP_TYPE_BD71815: + irqs = &bd71815_irqs[0]; + num_irqs = ARRAY_SIZE(bd71815_irqs); + break; + default: + return -EINVAL; + } + + for (i = 0; i < num_irqs; i++) { + irq = platform_get_irq_byname(pdev, irqs[i].name); + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + irqs[i].handler, 0, + irqs[i].name, pwr); + if (ret) + break; + } + + return ret; +} + +#define RSENS_DEFAULT_30MOHM 30000 /* 30 mOhm in uOhms*/ + +static int bd7182x_get_rsens(struct bd71828_power *pwr) +{ + u64 tmp = RSENS_CURR; + int rsens_ohm = RSENS_DEFAULT_30MOHM; + struct fwnode_handle *node = NULL; + + if (pwr->dev->parent) + node = dev_fwnode(pwr->dev->parent); + + if (node) { + int ret; + u32 rs; + + ret = fwnode_property_read_u32(node, + "rohm,charger-sense-resistor-micro-ohms", + &rs); + if (ret) { + if (ret == -EINVAL) { + rs = RSENS_DEFAULT_30MOHM; + } else { + dev_err(pwr->dev, "Bad RSENS dt property\n"); + return ret; + } + } + if (!rs) { + dev_err(pwr->dev, "Bad RSENS value\n"); + return -EINVAL; + } + + rsens_ohm = (int)rs; + } + + /* Reg val to uA */ + do_div(tmp, rsens_ohm); + + pwr->curr_factor = tmp; + pwr->rsens = rsens_ohm; + dev_dbg(pwr->dev, "Setting rsens to %u micro ohm\n", pwr->rsens); + dev_dbg(pwr->dev, "Setting curr-factor to %u\n", pwr->curr_factor); + + return 0; +} + +static int bd71828_power_probe(struct platform_device *pdev) +{ + struct bd71828_power *pwr; + struct power_supply_config ac_cfg = {}; + struct power_supply_config bat_cfg = {}; + int ret; + struct regmap *regmap; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) { + dev_err(&pdev->dev, "No parent regmap\n"); + return -EINVAL; + } + + pwr = devm_kzalloc(&pdev->dev, sizeof(*pwr), GFP_KERNEL); + if (!pwr) + return -ENOMEM; + + pwr->regmap = regmap; + pwr->dev = &pdev->dev; + pwr->chip_type = platform_get_device_id(pdev)->driver_data; + + switch (pwr->chip_type) { + case ROHM_CHIP_TYPE_BD71828: + pwr->bat_inserted = bd71828_bat_inserted; + pwr->get_temp = bd71828_get_temp; + pwr->regs = &pwr_regs_bd71828; + break; + case ROHM_CHIP_TYPE_BD71815: + pwr->bat_inserted = bd71815_bat_inserted; + pwr->get_temp = bd71815_get_temp; + pwr->regs = &pwr_regs_bd71815; + break; + default: + dev_err(pwr->dev, "Unknown PMIC\n"); + return -EINVAL; + } + + ret = bd7182x_get_rsens(pwr); + if (ret) + return dev_err_probe(&pdev->dev, ret, "sense resistor missing\n"); + + dev_set_drvdata(&pdev->dev, pwr); + bd71828_init_hardware(pwr); + + bat_cfg.drv_data = pwr; + bat_cfg.fwnode = dev_fwnode(&pdev->dev); + + ac_cfg.supplied_to = bd71828_ac_supplied_to; + ac_cfg.num_supplicants = ARRAY_SIZE(bd71828_ac_supplied_to); + ac_cfg.drv_data = pwr; + + pwr->ac = devm_power_supply_register(&pdev->dev, &bd71828_ac_desc, + &ac_cfg); + if (IS_ERR(pwr->ac)) + return dev_err_probe(&pdev->dev, PTR_ERR(pwr->ac), + "failed to register ac\n"); + + pwr->bat = devm_power_supply_register(&pdev->dev, &bd71828_bat_desc, + &bat_cfg); + if (IS_ERR(pwr->bat)) + return dev_err_probe(&pdev->dev, PTR_ERR(pwr->bat), + "failed to register bat\n"); + + ret = bd7182x_get_irqs(pdev, pwr); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to request IRQs"); + + /* Configure wakeup capable */ + device_set_wakeup_capable(pwr->dev, 1); + device_set_wakeup_enable(pwr->dev, 1); + + return 0; +} + +static const struct platform_device_id bd71828_charger_id[] = { + { "bd71815-power", ROHM_CHIP_TYPE_BD71815 }, + { "bd71828-power", ROHM_CHIP_TYPE_BD71828 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, bd71828_charger_id); + +static struct platform_driver bd71828_power_driver = { + .driver = { + .name = "bd718xx-power", + }, + .probe = bd71828_power_probe, + .id_table = bd71828_charger_id, +}; + +module_platform_driver(bd71828_power_driver); + +MODULE_AUTHOR("Cong Pham <cpham2403@gmail.com>"); +MODULE_DESCRIPTION("ROHM BD718(15/28/78) PMIC Battery Charger driver"); +MODULE_LICENSE("GPL"); |
