Add charger driver for ROHM BD718(15/28/78) PMIC charger block.
It is a stripped down version of the driver here:
https://lore.kernel.org/lkml/dbd97c1b0d715aa35a8b4d79741e433d97c562aa.1637061794.git.matti.vaittinen@fi.rohmeurope.com/
For the ease of review and to do a step-by-step approach remove all the
coloumb counter related stuff and do not sneak in BD71827 support. That
also avoids non-trivial rebasing of the above series.
Changes besides that:
Replace the custom property by a standard one and do not use megaohms
for the current sense resistor.
Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
Reviewed-by: Matti Vaittinen <mazziesaccount@gmail.com>
---
drivers/power/supply/Kconfig | 9 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/bd71828-power.c | 1051 ++++++++++++++++++++++++++++++++++
3 files changed, 1061 insertions(+)
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 79ddb006e2dad..16dddb2355d9b 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -974,6 +974,15 @@ config CHARGER_UCS1002
Say Y to enable support for Microchip UCS1002 Programmable
USB Port Power Controller with Charger Emulation.
+config CHARGER_BD71828
+ tristate "Power-supply driver for ROHM BD71828 and BD71815 PMIC"
+ depends on MFD_ROHM_BD71828
+ help
+ Say Y here to enable support for charger and battery
+ in ROHM BD71815, BD71817, ROHM BD71828 power management
+ ICs. This driver gets various bits of information about battery
+ and charger states.
+
config CHARGER_BD99954
tristate "ROHM bd99954 charger driver"
depends on I2C
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index f943c9150b326..c6520a11f021c 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -113,6 +113,7 @@ obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
obj-$(CONFIG_FUEL_GAUGE_STC3117) += stc3117_fuel_gauge.o
obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
+obj-$(CONFIG_CHARGER_BD71828) += bd71828-power.o
obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
diff --git a/drivers/power/supply/bd71828-power.c b/drivers/power/supply/bd71828-power.c
new file mode 100644
index 0000000000000..fdd03159dc7bc
--- /dev/null
+++ b/drivers/power/supply/bd71828-power.c
@@ -0,0 +1,1051 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* ROHM BD71815, BD71828 and BD71878 Charger driver */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#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/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/uaccess.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");
--
2.47.3
Hi, On Wed, Sep 17, 2025 at 07:48:03PM +0200, Andreas Kemnade wrote: > Add charger driver for ROHM BD718(15/28/78) PMIC charger block. > It is a stripped down version of the driver here: > https://lore.kernel.org/lkml/dbd97c1b0d715aa35a8b4d79741e433d97c562aa.1637061794.git.matti.vaittinen@fi.rohmeurope.com/ > > For the ease of review and to do a step-by-step approach remove all the > coloumb counter related stuff and do not sneak in BD71827 support. That > also avoids non-trivial rebasing of the above series. > > Changes besides that: > Replace the custom property by a standard one and do not use megaohms > for the current sense resistor. > > Signed-off-by: Andreas Kemnade <andreas@kemnade.info> > Reviewed-by: Matti Vaittinen <mazziesaccount@gmail.com> > --- > drivers/power/supply/Kconfig | 9 + > drivers/power/supply/Makefile | 1 + > drivers/power/supply/bd71828-power.c | 1051 ++++++++++++++++++++++++++++++++++ > 3 files changed, 1061 insertions(+) > > diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig > index 79ddb006e2dad..16dddb2355d9b 100644 > --- a/drivers/power/supply/Kconfig > +++ b/drivers/power/supply/Kconfig > @@ -974,6 +974,15 @@ config CHARGER_UCS1002 > Say Y to enable support for Microchip UCS1002 Programmable > USB Port Power Controller with Charger Emulation. > > +config CHARGER_BD71828 > + tristate "Power-supply driver for ROHM BD71828 and BD71815 PMIC" > + depends on MFD_ROHM_BD71828 > + help > + Say Y here to enable support for charger and battery > + in ROHM BD71815, BD71817, ROHM BD71828 power management > + ICs. This driver gets various bits of information about battery > + and charger states. > + > config CHARGER_BD99954 > tristate "ROHM bd99954 charger driver" > depends on I2C > diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile > index f943c9150b326..c6520a11f021c 100644 > --- a/drivers/power/supply/Makefile > +++ b/drivers/power/supply/Makefile > @@ -113,6 +113,7 @@ obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o > obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o > obj-$(CONFIG_FUEL_GAUGE_STC3117) += stc3117_fuel_gauge.o > obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o > +obj-$(CONFIG_CHARGER_BD71828) += bd71828-power.o > obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o > obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o > obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o > diff --git a/drivers/power/supply/bd71828-power.c b/drivers/power/supply/bd71828-power.c > new file mode 100644 > index 0000000000000..fdd03159dc7bc > --- /dev/null > +++ b/drivers/power/supply/bd71828-power.c > @@ -0,0 +1,1051 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* ROHM BD71815, BD71828 and BD71878 Charger driver */ > + > +#include <linux/delay.h> > +#include <linux/init.h> Not needed. > +#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/of.h> No of_ is being used in this, so please don't include of.h. You need linux/mod_devicetable.h instead, which you currently get via of.h. > +#include <linux/platform_device.h> > +#include <linux/power_supply.h> > +#include <linux/slab.h> > +#include <linux/uaccess.h> I don't think uaccess.h is needed either. Otherwise: Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com> Greetings, -- Sebastian > + > +/* 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"); > > -- > 2.47.3 > >
© 2016 - 2025 Red Hat, Inc.