// SPDX-License-Identifier: GPL-2.0-or-later /* ROHM BD71815, BD71828 and BD71878 Charger driver */ #include #include #include #include #include #include #include #include #include #include /* 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 "); MODULE_DESCRIPTION("ROHM BD718(15/28/78) PMIC Battery Charger driver"); MODULE_LICENSE("GPL");