@@ -791,6 +791,17 @@ 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
+ select POWER_SIMPLE_GAUGE
+ help
+ Say Y here to enable support for charger, battery and fuel gauge
+ in ROHM BD71815, BD71817, BD71827, ROHM BD71828 power management
+ ICs. This driver gets various bits of information about battery
+ and charger states and also implements fuel gauge based on
+ coulomb counters on PMIC.
+
config CHARGER_BD99954
tristate "ROHM bd99954 charger driver"
depends on I2C
@@ -99,6 +99,7 @@ obj-$(CONFIG_CHARGER_CROS_PCHG) += cros_peripheral_charger.o
obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
+obj-$(CONFIG_CHARGER_BD71828) += bd71827-power.o
obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
new file mode 100644
@@ -0,0 +1,2473 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * bd71827-power.c
+ * @file ROHM BD71815, BD71827, BD71828 and BD71878 Charger driver
+ *
+ * Copyright 2021.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/linear_range.h>
+#include <linux/mfd/rohm-bd71815.h>
+#include <linux/mfd/rohm-bd71827.h>
+#include <linux/mfd/rohm-bd71828.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power/simple_gauge.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#define MAX(X, Y) ((X) >= (Y) ? (X) : (Y))
+#define uAMP_TO_mAMP(ma) ((ma) / 1000)
+
+#define LINEAR_INTERPOLATE(y_hi, y_lo, x_hi, x_lo, x) \
+ ((y_lo) + ((x) - (x_lo)) * ((y_hi) - (y_lo)) / ((x_hi) - (x_lo)))
+
+#define CAP2DSOC(cap, full_cap) ((cap) * 1000 / (full_cap))
+
+/* 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_CC_CCNTD_HI 0x0FFF
+#define BD7182x_MASK_CC_CCNTD 0x0FFFFFFF
+#define BD7182x_MASK_CHG_STATE 0x7f
+#define BD7182x_MASK_CC_FULL_CLR 0x10
+#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_CCNTRST 0x80
+#define BD7182x_MASK_CCNTENB 0x40
+#define BD7182x_MASK_CCCALIB 0x20
+#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
+
+static const struct linear_range dcin_collapse = {
+ .min = 0,
+ .min_sel = 0,
+ .max_sel = 0xff,
+ /* Anti-collapse voltage threshold 0.0V to 20.4V range, 80 mV steps */
+ .step = 80000,
+};
+
+/* Measured min and max value clear bits */
+#define BD718XX_MASK_VSYS_MIN_AVG_CLR 0x10
+
+#define JITTER_DEFAULT 3000
+#define MAX_CURRENT_DEFAULT 890000 /* uA */
+#define AC_NAME "bd71827_ac"
+#define BAT_NAME "bd71827_bat"
+
+/*
+ * VBAT Low voltage detection Threshold
+ * 0x00D4*16mV = 212*0.016 = 3.392v
+ */
+#define VBAT_LOW_TH 0x00D4
+
+#define THR_RELAX_CURRENT_DEFAULT 5 /* mA */
+#define THR_RELAX_TIME_DEFAULT (60 * 60) /* sec. */
+
+#define DGRD_CYC_CAP_DEFAULT 88 /* 1 micro Ah */
+
+#define DGRD_TEMP_H_DEFAULT 450 /* 0.1 degrees C */
+#define DGRD_TEMP_M_DEFAULT 250 /* 0.1 degrees C */
+#define DGRD_TEMP_L_DEFAULT 50 /* 0.1 degrees C */
+#define DGRD_TEMP_VL_DEFAULT 0 /* 0.1 degrees C */
+
+#define SOC_EST_MAX_NUM_DEFAULT 5
+#define PWRCTRL_NORMAL 0x22
+#define PWRCTRL_RESET 0x23
+
+/*
+ * Originally we relied upon a fixed size table of OCV and VDR params.
+ * However the exising linux power-supply batinfo interface for getting the OCV
+ * values from DT does not have fixed amount of OCV values. Thus we use fixed
+ * parameter amount only for values provided as module params - and use this
+ * only as maximum number of parameters when values come from DT.
+ */
+#define NUM_BAT_PARAMS 23
+#define MAX_NUM_VDR_VALUES NUM_BAT_PARAMS
+
+struct pwr_regs {
+ int used_init_regs;
+ u8 vbat_init;
+ u8 vbat_init2;
+ u8 vbat_init3;
+ u8 vbat_avg;
+ u8 ibat;
+ u8 ibat_avg;
+ u8 meas_clear;
+ u8 vsys_min_avg;
+ u8 btemp_vth;
+ u8 chg_state;
+ u8 coulomb3;
+ u8 coulomb2;
+ u8 coulomb1;
+ u8 coulomb0;
+ u8 coulomb_ctrl;
+ u8 vbat_rex_avg;
+ u8 coulomb_full3;
+ u8 cc_full_clr;
+ u8 coulomb_chg3;
+ u8 bat_temp;
+ u8 dcin_stat;
+ u8 dcin_collapse_limit;
+ u8 chg_set1;
+ u8 chg_en;
+ u8 vbat_alm_limit_u;
+ u8 batcap_mon_limit_u;
+ u8 conf;
+ u8 vdcin;
+#ifdef PWRCTRL_HACK
+ u8 pwrctrl;
+#endif
+};
+
+/* Regions for High, Medium, Low, Very Low temperature */
+enum {
+ VDR_TEMP_HIGH,
+ VDR_TEMP_NORMAL,
+ VDR_TEMP_LOW,
+ VDR_TEMP_VERY_LOW,
+ NUM_VDR_TEMPS
+};
+
+/*
+ * This works as long as we have only one instance of this driver (which is
+ * likely to be the case even with DT originated battery info). Anyways,
+ * consider moving these in allocated data just to pretend to know what I am
+ * doing XD
+ */
+static int vdr_temps[NUM_VDR_TEMPS] = { -EINVAL, -EINVAL, -EINVAL, -EINVAL};
+static int g_num_vdr_params;
+
+static struct pwr_regs pwr_regs_bd71827 = {
+ .vbat_init = BD71827_REG_VM_OCV_PRE_U,
+ .vbat_init2 = BD71827_REG_VM_OCV_PST_U,
+ .vbat_init3 = BD71827_REG_VM_OCV_PWRON_U,
+ .used_init_regs = 3,
+ .vbat_avg = BD71827_REG_VM_SA_VBAT_U,
+ .ibat = BD71827_REG_CC_CURCD_U,
+ .ibat_avg = BD71827_REG_CC_SA_CURCD_U,
+ .meas_clear = BD71827_REG_VM_SA_MINMAX_CLR,
+ .vsys_min_avg = BD71827_REG_VM_SA_VSYS_MIN_U,
+ .btemp_vth = BD71827_REG_VM_BTMP,
+ .chg_state = BD71827_REG_CHG_STATE,
+ .coulomb3 = BD71827_REG_CC_CCNTD_3,
+ .coulomb2 = BD71827_REG_CC_CCNTD_2,
+ .coulomb1 = BD71827_REG_CC_CCNTD_1,
+ .coulomb0 = BD71827_REG_CC_CCNTD_0,
+ .coulomb_ctrl = BD71827_REG_CC_CTRL,
+ .coulomb_full3 = BD71827_REG_FULL_CCNTD_3,
+ .cc_full_clr = BD71827_REG_FULL_CTRL,
+ .coulomb_chg3 = BD71827_REG_CCNTD_CHG_3,
+ .bat_temp = BD71827_REG_BAT_TEMP,
+ .dcin_stat = BD71827_REG_DCIN_STAT,
+ .dcin_collapse_limit = BD71827_REG_DCIN_CLPS,
+ .chg_set1 = BD71827_REG_CHG_SET1,
+ .chg_en = BD71827_REG_CHG_SET1,
+ .vbat_alm_limit_u = BD71827_REG_ALM_VBAT_TH_U,
+ .batcap_mon_limit_u = BD71827_REG_CC_BATCAP1_TH_U,
+ .conf = BD71827_REG_CONF,
+ .vdcin = BD71827_REG_VM_DCIN_U,
+#ifdef PWRCTRL_HACK
+ .pwrctrl = BD71827_REG_PWRCTRL,
+ .hibernate_mask = 0x1,
+#endif
+};
+
+static struct pwr_regs pwr_regs_bd71828 = {
+ .vbat_init = BD71828_REG_VBAT_INITIAL1_U,
+ .vbat_init2 = BD71828_REG_VBAT_INITIAL2_U,
+ .vbat_init3 = BD71828_REG_OCV_PWRON_U,
+ .used_init_regs = 3,
+ .vbat_avg = BD71828_REG_VBAT_U,
+ .ibat = BD71828_REG_IBAT_U,
+ .ibat_avg = BD71828_REG_IBAT_AVG_U,
+ .meas_clear = BD71828_REG_MEAS_CLEAR,
+ .vsys_min_avg = BD71828_REG_VSYS_MIN_AVG_U,
+ .btemp_vth = BD71828_REG_VM_BTMP_U,
+ .chg_state = BD71828_REG_CHG_STATE,
+ .coulomb3 = BD71828_REG_CC_CNT3,
+ .coulomb2 = BD71828_REG_CC_CNT2,
+ .coulomb1 = BD71828_REG_CC_CNT1,
+ .coulomb0 = BD71828_REG_CC_CNT0,
+ .coulomb_ctrl = BD71828_REG_COULOMB_CTRL,
+ .vbat_rex_avg = BD71828_REG_VBAT_REX_AVG_U,
+ .coulomb_full3 = BD71828_REG_CC_CNT_FULL3,
+ .cc_full_clr = BD71828_REG_COULOMB_CTRL2,
+ .coulomb_chg3 = BD71828_REG_CC_CNT_CHG3,
+ .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,
+ .batcap_mon_limit_u = BD71828_REG_BATCAP_MON_LIMIT_U,
+ .conf = BD71828_REG_CONF,
+ .vdcin = BD71828_REG_VDCIN_U,
+#ifdef PWRCTRL_HACK
+ .pwrctrl = BD71828_REG_PS_CTRL_1,
+ .hibernate_mask = 0x2,
+#endif
+};
+
+static struct pwr_regs pwr_regs_bd71815 = {
+ .vbat_init = BD71815_REG_VM_OCV_PRE_U,
+ .vbat_init2 = BD71815_REG_VM_OCV_PST_U,
+ .used_init_regs = 2,
+ .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,
+
+ .meas_clear = BD71815_REG_VM_SA_MINMAX_CLR,
+ .vsys_min_avg = BD71815_REG_VM_SA_VSYS_MIN_U,
+ .btemp_vth = BD71815_REG_VM_BTMP,
+ .chg_state = BD71815_REG_CHG_STATE,
+ .coulomb3 = BD71815_REG_CC_CCNTD_3,
+ .coulomb2 = BD71815_REG_CC_CCNTD_2,
+ .coulomb1 = BD71815_REG_CC_CCNTD_1,
+ .coulomb0 = BD71815_REG_CC_CCNTD_0,
+ .coulomb_ctrl = BD71815_REG_CC_CTRL,
+ .vbat_rex_avg = BD71815_REG_REX_SA_VBAT_U,
+ .coulomb_full3 = BD71815_REG_FULL_CCNTD_3,
+ .cc_full_clr = BD71815_REG_FULL_CTRL,
+ .coulomb_chg3 = BD71815_REG_CCNTD_CHG_3,
+ .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,
+ .batcap_mon_limit_u = BD71815_REG_CC_BATCAP1_TH_U,
+ .conf = BD71815_REG_CONF,
+
+ .vdcin = BD71815_REG_VM_DCIN_U,
+#ifdef PWRCTRL_HACK
+ #error "Not implemented for BD71815"
+#endif
+};
+
+/*
+ * unit 0.1%
+ *
+ * These are the SOCs we use at zero-correction. If OCV is given via DT
+ * we interpolate the OCV tables to get the OCV corresponding these SOCs.
+ *
+ * If VDR tables are given we will ovrride these SOCs by SOCs corresponding
+ * the VDR values.
+ */
+static int soc_table_default[NUM_BAT_PARAMS] = {
+ 1000,
+ 1000,
+ 950,
+ 900,
+ 850,
+ 800,
+ 750,
+ 700,
+ 650,
+ 600,
+ 550,
+ 500,
+ 450,
+ 400,
+ 350,
+ 300,
+ 250,
+ 200,
+ 150,
+ 100,
+ 50,
+ 0,
+ -50
+};
+
+/* Module parameters */
+static int use_load_bat_params;
+static int param_thr_voltage;
+static int param_max_voltage;
+static int param_min_voltage;
+
+static int battery_cap_mah;
+
+static int dgrd_cyc_cap = DGRD_CYC_CAP_DEFAULT;
+
+static int soc_est_max_num;
+static int ocv_table[NUM_BAT_PARAMS];
+static int soc_table[NUM_BAT_PARAMS];
+static int vdr_table_h[NUM_BAT_PARAMS];
+static int vdr_table_m[NUM_BAT_PARAMS];
+static int vdr_table_l[NUM_BAT_PARAMS];
+static int vdr_table_vl[NUM_BAT_PARAMS];
+
+struct bd71827_power {
+ struct simple_gauge *sw;
+ struct simple_gauge_desc gdesc;
+ struct simple_gauge_ops ops;
+ struct regmap *regmap;
+ enum rohm_chip_type chip_type;
+ struct device *dev;
+ struct power_supply *ac; /**< alternating current power */
+ int gauge_delay; /**< Schedule to call gauge algorithm */
+ int relax_time; /**< Relax Time */
+
+ struct pwr_regs *regs;
+ /* Reg val to uA */
+ int curr_factor;
+ int rsens;
+ int min_voltage;
+ int max_voltage;
+ int low_thr_voltage;
+ int (*get_temp)(struct bd71827_power *pwr, int *temp);
+ int (*bat_inserted)(struct bd71827_power *pwr);
+ int battery_cap;
+ struct power_supply_battery_info batinfo;
+};
+
+#define __CC_to_UAH(pwr, cc) \
+({ \
+ u64 __tmp = ((u64)(cc)) * 1000000000LLU; \
+ \
+ do_div(__tmp, (pwr)->rsens * 36 / 1000); \
+ __tmp; \
+})
+
+#define CC16_to_UAH(pwe, cc) ((int)__CC_to_UAH((pwr), (cc)))
+#define CC32_to_UAH(pwe, cc) ((int)(__CC_to_UAH((pwr), (cc)) >> 16))
+
+/*
+ * rsens is typically tens of Mohms so dividing by 1000 should be ok. (usual
+ * values are 10 and 30 Mohms so division is likely to go even). We do this
+ * to avoid doing two do_divs which would be unnecessary performance hit
+ * even if this should not be time critical.
+ */
+#define UAH_to_CC(pwr, uah) ({ \
+ u64 __tmp = (uah); \
+ u32 __rs = (pwr)->rsens / 1000; \
+ __tmp *= ((u64)__rs) * 36LLU; \
+ \
+ do_div(__tmp, 1000000000); \
+ (int)__tmp; \
+})
+
+#define CALIB_NORM 0
+#define CALIB_START 1
+#define CALIB_GO 2
+
+enum {
+ STAT_POWER_ON,
+ STAT_INITIALIZED,
+};
+
+static int bd7182x_write16(struct bd71827_power *pwr, int reg, uint16_t val)
+{
+ __be16 tmp;
+
+ tmp = cpu_to_be16(val);
+
+ return regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp));
+}
+
+static int bd7182x_read16_himask(struct bd71827_power *pwr, int reg, int himask,
+ uint16_t *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;
+}
+
+#define MAX_INITIAL_OCV_REGS 3
+/* get initial battery voltage and current */
+static int bd71827_get_init_voltage(struct bd71827_power *pwr,
+ int *ocv)
+{
+ int ret;
+ int i;
+ u8 regs[MAX_INITIAL_OCV_REGS] = {
+ pwr->regs->vbat_init,
+ pwr->regs->vbat_init2,
+ pwr->regs->vbat_init3
+ };
+ uint16_t vals[MAX_INITIAL_OCV_REGS];
+
+ *ocv = 0;
+
+ for (i = 0; i < pwr->regs->used_init_regs; i++) {
+
+ ret = bd7182x_read16_himask(pwr, regs[i], BD7182x_MASK_VBAT_U,
+ &vals[i]);
+ if (ret) {
+ dev_err(pwr->dev,
+ "Failed to read initial battery voltage\n");
+ return ret;
+ }
+ *ocv = MAX(vals[i], *ocv);
+
+ dev_dbg(pwr->dev, "VM_OCV_%d = %d\n", i, ((int)vals[i]) * 1000);
+ }
+
+ *ocv *= 1000;
+
+ return ret;
+}
+
+static int bd71827_get_vbat(struct bd71827_power *pwr, int *vcell)
+{
+ uint16_t 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 bd71827_get_current_ds_adc(struct bd71827_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;
+ tmp_curr = be16_to_cpu(tmp_curr);
+
+ *vals[i] = dir * ((int)tmp_curr) * pwr->curr_factor;
+ }
+
+ return ret;
+}
+
+static int bd71827_voltage_to_capacity(struct simple_gauge *sw, int ocv,
+ int temp __always_unused,
+ int *dsoc);
+
+static int bd71827_voltage_to_capacity(struct simple_gauge *sw, int ocv, int temp,
+ int *dsoc)
+{
+ int i = 0;
+ struct bd71827_power *pwr;
+
+ /* If ocv_table is not given try luck with batinfo */
+ if (!use_load_bat_params || !ocv_table[0]) {
+ if (!sw)
+ return -EINVAL;
+
+ pwr = simple_gauge_get_drvdata(sw);
+ *dsoc = power_supply_batinfo_ocv2dcap(&pwr->batinfo, ocv, 0);
+ if (*dsoc < 0)
+ return *dsoc;
+
+ return 0;
+ }
+
+ /* Default or module param OCV table. We have NUM_BAT_PARAMS */
+ if (ocv > ocv_table[0]) {
+ *dsoc = soc_table[0];
+ } else {
+ for (i = 0; i < NUM_BAT_PARAMS; i++) {
+ if ((ocv <= ocv_table[i]) && (ocv > ocv_table[i+1])) {
+ *dsoc = (soc_table[i] - soc_table[i+1]) *
+ (ocv - ocv_table[i+1]) /
+ (ocv_table[i] - ocv_table[i+1]);
+ *dsoc += soc_table[i+1];
+ break;
+ }
+ }
+ if (i == NUM_BAT_PARAMS)
+ *dsoc = soc_table[i - 1];
+ }
+
+ return 0;
+}
+
+/* Unit is tenths of degree C */
+static int bd71827_get_temp(struct simple_gauge *sw, int *temp)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ struct regmap *regmap = pwr->regmap;
+ int ret;
+ int t;
+
+ ret = regmap_read(regmap, pwr->regs->btemp_vth, &t);
+ t = 200 - t;
+
+ if (ret || t > 200) {
+ dev_err(pwr->dev, "Failed to read battery temperature\n");
+ *temp = 2000;
+ } else {
+ *temp = t * 10;
+ }
+
+ return ret;
+}
+
+/* Unit is tenths of degree C */
+static int bd71828_get_temp(struct simple_gauge *sw, int *temp)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ uint16_t t;
+ int ret;
+ int tmp = 200 * 10000;
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->btemp_vth,
+ BD71828_MASK_VM_BTMP_U, &t);
+ if (ret || t > 3200)
+ dev_err(pwr->dev,
+ "Failed to read system min average voltage\n");
+
+ tmp -= 625ULL * (unsigned int)t;
+ *temp = tmp / 1000;
+
+ return ret;
+}
+
+static int bd71827_charge_status(struct bd71827_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);
+
+ state &= BD7182x_MASK_CHG_STATE;
+
+ dev_dbg(pwr->dev, "CHG_STATE %d\n", state);
+
+ switch (state) {
+ case 0x00:
+ ret = 0;
+ 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:
+ ret = 0;
+ 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:
+ ret = 0;
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x40:
+ ret = 0;
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case 0x7f:
+ default:
+ ret = 0;
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ health = POWER_SUPPLY_HEALTH_DEAD;
+ break;
+ }
+
+ if (s)
+ *s = status;
+ if (h)
+ *h = health;
+
+ return ret;
+}
+
+static int __write_cc(struct bd71827_power *pwr, uint16_t bcap,
+ unsigned int reg, uint32_t *new)
+{
+ int ret;
+ __be32 tmp;
+ __be16 *swap_hi = (__be16 *)&tmp;
+ uint16_t *swap_lo = swap_hi + 1;
+
+ *swap_hi = cpu_to_be16(bcap & BD7182x_MASK_CC_CCNTD_HI);
+ *swap_lo = 0;
+
+ ret = regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp));
+ if (ret) {
+ dev_err(pwr->dev, "Failed to write coulomb counter\n");
+ return ret;
+ }
+ if (new)
+ *new = be32_to_cpu(tmp);
+
+ return ret;
+}
+
+static int write_cc(struct bd71827_power *pwr, uint16_t bcap)
+{
+ int ret;
+ uint32_t new;
+
+ ret = __write_cc(pwr, bcap, pwr->regs->coulomb3, &new);
+ if (!ret)
+ dev_dbg(pwr->dev, "CC set to 0x%x\n", (int)new);
+
+ return ret;
+}
+
+static int stop_cc(struct bd71827_power *pwr)
+{
+ struct regmap *r = pwr->regmap;
+
+ return regmap_update_bits(r, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCNTENB, 0);
+}
+
+static int start_cc(struct bd71827_power *pwr)
+{
+ struct regmap *r = pwr->regmap;
+
+ return regmap_update_bits(r, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCNTENB, BD7182x_MASK_CCNTENB);
+}
+
+static int update_cc(struct bd71827_power *pwr, uint16_t bcap)
+{
+ int ret;
+
+ ret = stop_cc(pwr);
+ if (ret)
+ goto err_out;
+
+ ret = write_cc(pwr, bcap);
+ if (ret)
+ goto enable_out;
+
+ ret = start_cc(pwr);
+ if (ret)
+ goto enable_out;
+
+ return 0;
+
+enable_out:
+ start_cc(pwr);
+err_out:
+ dev_err(pwr->dev, "Coulomb counter write failed (%d)\n", ret);
+ return ret;
+}
+
+static int bd71828_set_uah(struct simple_gauge *sw, int bcap)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ u16 cc_val = UAH_to_CC(pwr, bcap);
+
+ return update_cc(pwr, cc_val);
+}
+
+static int __read_cc(struct bd71827_power *pwr, u32 *cc, unsigned int reg)
+{
+ int ret;
+ __be32 tmp_cc;
+
+ ret = regmap_bulk_read(pwr->regmap, reg, &tmp_cc, sizeof(tmp_cc));
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read coulomb counter\n");
+ return ret;
+ }
+ *cc = be32_to_cpu(tmp_cc) & BD7182x_MASK_CC_CCNTD;
+
+ return 0;
+}
+
+static int read_cc_full(struct bd71827_power *pwr, u32 *cc)
+{
+ return __read_cc(pwr, cc, pwr->regs->coulomb_full3);
+}
+
+static int read_cc(struct bd71827_power *pwr, u32 *cc)
+{
+ return __read_cc(pwr, cc, pwr->regs->coulomb3);
+}
+
+/** @brief set initial coulomb counter value from battery voltage
+ * @param pwr power device
+ * @return 0
+ */
+static int calibration_coulomb_counter(struct bd71827_power *pwr)
+{
+ struct regmap *regmap = pwr->regmap;
+ u32 bcap;
+ int soc, ocv, ret = 0, tmpret = 0;
+
+ /* Get initial OCV */
+ bd71827_get_init_voltage(pwr, &ocv);
+ dev_dbg(pwr->dev, "ocv %d\n", ocv);
+
+ if (pwr->ops.get_soc_by_ocv) {
+ ret = pwr->ops.get_soc_by_ocv(NULL, ocv, 0, &soc);
+ } else {
+ soc = power_supply_batinfo_ocv2dcap(&pwr->batinfo, ocv, 0);
+ if (soc < 0)
+ return soc;
+ }
+ /* Get init soc from ocv/soc table */
+
+ dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc);
+ if (soc < 0)
+ soc = 0;
+
+ bcap = UAH_to_CC(pwr, pwr->battery_cap) * soc / 1000;
+ write_cc(pwr, bcap + UAH_to_CC(pwr, pwr->battery_cap) / 200);
+
+ msleep(5000);
+
+ tmpret = write_cc(pwr, bcap);
+ if (tmpret)
+ goto enable_cc_out;
+
+enable_cc_out:
+ /* Start canceling offset of the DS ADC. This needs 1 second at least */
+ ret = regmap_update_bits(regmap, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCCALIB, BD7182x_MASK_CCCALIB);
+
+ return (tmpret) ? tmpret : ret;
+}
+
+/* This should be used to get VSYS for low limit calculations */
+static int bd71827_get_vsys_min(struct simple_gauge *sw, int *uv)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ uint16_t tmp_vcell;
+ int ret;
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->vsys_min_avg,
+ BD7182x_MASK_VBAT_U, &tmp_vcell);
+ if (ret) {
+ dev_err(pwr->dev,
+ "Failed to read system min average voltage\n");
+ return ret;
+ }
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->meas_clear,
+ BD718XX_MASK_VSYS_MIN_AVG_CLR,
+ BD718XX_MASK_VSYS_MIN_AVG_CLR);
+ if (ret)
+ dev_warn(pwr->dev, "failed to clear cached Vsys\n");
+
+ *uv = ((int)tmp_vcell) * 1000;
+
+ return 0;
+}
+
+/* This should be used for relax Vbat with BD71827 */
+static int bd71827_get_voltage(struct simple_gauge *sg, int *vbat)
+{
+ int voltage, ret;
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sg);
+
+ ret = bd71827_get_vbat(pwr, &voltage);
+ if (ret)
+ return ret;
+
+ *vbat = voltage;
+
+ return 0;
+}
+
+static int bd71828_get_uah_from_full(struct simple_gauge *sw, int *from_full_uah)
+{
+ int ret;
+ struct bd71827_power *pwr;
+ struct regmap *regmap;
+ u32 full_charged_coulomb_cnt;
+ u32 cc;
+ int diff_coulomb_cnt;
+
+ pwr = simple_gauge_get_drvdata(sw);
+ regmap = pwr->regmap;
+
+ /*
+ * Read and clear the stored CC value from moment when battery was
+ * last charged to full.
+ */
+ ret = read_cc_full(pwr, &full_charged_coulomb_cnt);
+ if (ret) {
+ dev_err(pwr->dev, "failed to read full coulomb counter\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(regmap, pwr->regs->cc_full_clr,
+ BD7182x_MASK_CC_FULL_CLR,
+ BD7182x_MASK_CC_FULL_CLR);
+ /* Get current CC value to estimate change of charge since full */
+ ret = read_cc(pwr, &cc);
+ if (ret)
+ return ret;
+
+ diff_coulomb_cnt = full_charged_coulomb_cnt - cc;
+
+ diff_coulomb_cnt >>= 16;
+
+ /*
+ * Ignore possible increase in CC which can be caused by ADC offset or
+ * temperature change
+ */
+ if (diff_coulomb_cnt > 0)
+ diff_coulomb_cnt = 0;
+
+ *from_full_uah = CC16_to_UAH(pwr, diff_coulomb_cnt);
+
+ return 0;
+}
+
+static int bd71828_get_uah(struct simple_gauge *sw, int *uah)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ u32 cc;
+ int ret;
+
+ ret = read_cc(pwr, &cc);
+ if (!ret)
+ *uah = CC32_to_UAH(pwr, cc);
+
+ return ret;
+}
+
+/*
+ * Standard batinfo supports only accuracy of 1% for SOC - which
+ * may not be sufficient for us. SWGAUGE provides soc in unts of 0.1% here
+ * to allow more accurate computation.
+ */
+static int bd71827_get_ocv(struct simple_gauge *sw, int dsoc, int temp, int *ocv)
+{
+ int i = 0;
+ struct bd71827_power *pwr;
+
+ /* If soc_table is not given try luck with batinfo */
+ if (!use_load_bat_params || !ocv_table[0]) {
+ if (!sw)
+ return -EINVAL;
+
+ pwr = simple_gauge_get_drvdata(sw);
+ *ocv = power_supply_batinfo_dcap2ocv(&pwr->batinfo, dsoc, temp);
+ if (*ocv < 0)
+ return *ocv;
+
+ return 0;
+ }
+
+ /* Default or module param OCV table. We have NUM_BAT_PARAMS */
+
+ if (dsoc > soc_table[0]) {
+ *ocv = pwr->max_voltage;
+ return 0;
+ }
+ if (dsoc == 0) {
+ *ocv = ocv_table[NUM_BAT_PARAMS - 2];
+ return 0;
+ }
+
+ i = 0;
+ while (i < NUM_BAT_PARAMS - 1) {
+ if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) {
+ *ocv = (ocv_table[i] - ocv_table[i+1]) *
+ (dsoc - soc_table[i+1]) / (soc_table[i] -
+ soc_table[i+1]) + ocv_table[i+1];
+ return 0;
+ }
+ i++;
+ }
+
+ *ocv = ocv_table[NUM_BAT_PARAMS - 1];
+
+ return 0;
+}
+
+static void calc_vdr(int *res, int *vdr, int temp, int dgrd_temp,
+ int *vdr_hi, int dgrd_temp_hi, int items)
+{
+ int i;
+
+ /* Get VDR weighed by temperature */
+ for (i = 0; i < items; i++)
+ res[i] = LINEAR_INTERPOLATE(vdr_hi[i], vdr[i], dgrd_temp_hi,
+ dgrd_temp, temp);
+}
+
+/* get VDR(Voltage Drop Rate) value by SOC */
+static int bd71827_get_vdr(struct bd71827_power *pwr, int dsoc, int temp)
+{
+ int i = 0;
+ int vdr = 100;
+ int vdr_table[NUM_BAT_PARAMS];
+
+ /* Calculate VDR by temperature */
+ if (temp >= vdr_temps[VDR_TEMP_HIGH])
+ for (i = 0; i < g_num_vdr_params; i++)
+ vdr_table[i] = vdr_table_h[i];
+ else if (temp >= vdr_temps[VDR_TEMP_NORMAL])
+ calc_vdr(vdr_table, vdr_table_m, temp, vdr_temps[VDR_TEMP_NORMAL],
+ vdr_table_h, vdr_temps[VDR_TEMP_HIGH],
+ g_num_vdr_params);
+ else if (temp >= vdr_temps[VDR_TEMP_LOW])
+ calc_vdr(vdr_table, vdr_table_l, temp, vdr_temps[VDR_TEMP_LOW],
+ vdr_table_m, vdr_temps[VDR_TEMP_NORMAL],
+ g_num_vdr_params);
+ else if (temp >= vdr_temps[VDR_TEMP_VERY_LOW])
+ calc_vdr(vdr_table, vdr_table_vl, temp,
+ vdr_temps[VDR_TEMP_VERY_LOW], vdr_table_l,
+ vdr_temps[VDR_TEMP_LOW], g_num_vdr_params);
+ else
+ for (i = 0; i < g_num_vdr_params; i++)
+ vdr_table[i] = vdr_table_vl[i];
+
+ if (dsoc > soc_table[0]) {
+ vdr = 100;
+ } else if (dsoc == 0) {
+ vdr = vdr_table[g_num_vdr_params - 1];
+ } else {
+ for (i = 0; i < g_num_vdr_params - 1; i++)
+ if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) {
+ vdr = LINEAR_INTERPOLATE(vdr_table[i],
+ vdr_table[i+1],
+ soc_table[i],
+ soc_table[i+1], dsoc);
+
+ break;
+ }
+ if (i == g_num_vdr_params - 1)
+ vdr = vdr_table[i];
+ }
+ dev_dbg(pwr->dev, "vdr = %d\n", vdr);
+ return vdr;
+}
+
+static int bd71828_zero_correct(struct simple_gauge *sw, int *effective_cap,
+ int cc_uah, int vbat, int temp)
+{
+ int ocv_table_load[NUM_BAT_PARAMS];
+ int i, ret;
+ /* Assume fixed-size module param table */
+ static int params = NUM_BAT_PARAMS;
+ int ocv;
+ int dsoc;
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+
+ /*
+ * Calculate SOC from CC and effective battery cap.
+ * Use unit of 0.1% for dsoc to improve accuracy
+ */
+ dsoc = CAP2DSOC(cc_uah, *effective_cap);
+ dev_dbg(pwr->dev, "dsoc = %d\n", dsoc);
+
+ ret = bd71827_get_ocv(sw, dsoc, 0, &ocv);
+ if (ret)
+ return ret;
+
+ if (!ocv_table[0]) {
+ for (i = 0; i < g_num_vdr_params; i++)
+ ocv_table[i] = power_supply_batinfo_dcap2ocv(&pwr->batinfo,
+ soc_table[i], temp);
+ /*
+ * Update amount of OCV values id we didn't have the fixed size
+ * module param table
+ */
+ params = g_num_vdr_params;
+ }
+ for (i = 1; i < params; i++) {
+ ocv_table_load[i] = ocv_table[i] - (ocv - vbat);
+ if (ocv_table_load[i] <= pwr->min_voltage) {
+ dev_dbg(pwr->dev, "ocv_table_load[%d] = %d\n", i,
+ ocv_table_load[i]);
+ break;
+ }
+ }
+
+ /*
+ * For improved accuracy ROHM helps customers to measure some
+ * battery voltage drop curves to do further SOC estimation improvement.
+ * If VDR tables are available we perform these corrections.
+ */
+ if (i < params) {
+ int zero_soc_pos;
+ int j, k, m;
+ int dv;
+ int lost_cap, new_lost_cap;
+ int dsoc0;
+ int vdr, vdr0;
+ int soc_range;
+
+ /*
+ * The original ROHM algorithm had fixed amount of OCV and VDR
+ * values. The quiet expectation of the algorithm was that the
+ * second last value in these tables correspond zero SOC. In
+ * order to relax this assumption when values come from DT we
+ * try to scan the SOC table for zero SOC.
+ */
+ for (zero_soc_pos = params - 1; zero_soc_pos >= 0;
+ zero_soc_pos--)
+ if (soc_table[zero_soc_pos] >= 0)
+ break;
+
+ if (soc_table[zero_soc_pos])
+ dev_warn_once(pwr->dev,
+ "VDR/OCV: zero SOC not found\n");
+
+ /*
+ * We want to know the zero soc position from the last entry
+ * in SOC table so that we know where the fully depleted cap
+ * is met.
+ */
+ zero_soc_pos = params - zero_soc_pos;
+
+ soc_range = (soc_table[i - 1] - soc_table[i]) / 10;
+ if (soc_range < 1) {
+ dev_err_once(pwr->dev, "Bad SOC table values %u, %u\n",
+ soc_table[i - 1], soc_table[i]);
+ return -EINVAL;
+ }
+ dv = (ocv_table_load[i - 1] - ocv_table_load[i]) / soc_range; /* was hard coded 5 */
+ for (j = 1; j < soc_range/* was 5 */; j++) {
+ if ((ocv_table_load[i] + dv * j) >
+ pwr->min_voltage) {
+ break;
+ }
+ }
+
+ lost_cap = ((params - zero_soc_pos - i) * soc_range /* was 5 */ +
+ (j - 1)) * *effective_cap / 100;
+ dev_dbg(pwr->dev, "lost_cap-1 = %d\n", lost_cap);
+
+ for (m = 0; m < soc_est_max_num; m++) {
+ new_lost_cap = lost_cap;
+ dsoc0 = CAP2DSOC(lost_cap, *effective_cap);
+ if ((dsoc >= 0 && dsoc0 > dsoc) ||
+ (dsoc < 0 && dsoc0 < dsoc))
+ dsoc0 = dsoc;
+
+ dev_dbg(pwr->dev, "dsoc0(%d) = %d\n", m, dsoc0);
+
+ vdr = bd71827_get_vdr(pwr, dsoc, temp);
+ vdr0 = bd71827_get_vdr(pwr, dsoc0, temp);
+
+ for (k = 1; k < params; k++) {
+ if (!vdr) {
+ dev_err(pwr->dev,
+ "VDR zero-correction failed\n");
+ break;
+ }
+ ocv_table_load[k] = ocv_table[k] -
+ (ocv - vbat) * vdr0 / vdr;
+ if (ocv_table_load[k] <= pwr->min_voltage) {
+ dev_dbg(pwr->dev,
+ "ocv_table_load[%d] = %d\n", k,
+ ocv_table_load[k]);
+ break;
+ }
+ }
+ if (k < params) {
+ dv = (ocv_table_load[k-1] -
+ ocv_table_load[k]) / 5;
+ for (j = 1; j < 5; j++)
+ if ((ocv_table_load[k] + dv * j) >
+ pwr->min_voltage)
+ break;
+
+ new_lost_cap = ((params - zero_soc_pos - k) *
+ 5 + (j - 1)) *
+ *effective_cap / 100;
+ if (soc_est_max_num == 1)
+ lost_cap = new_lost_cap;
+ else
+ lost_cap += (new_lost_cap - lost_cap) /
+ (2 * (soc_est_max_num - m));
+ dev_dbg(pwr->dev, "lost_cap-2(%d) = %d\n", m,
+ lost_cap);
+ }
+ if (new_lost_cap == lost_cap)
+ break;
+ }
+
+ *effective_cap -= lost_cap;
+ }
+
+ return 0;
+}
+
+static int get_chg_online(struct bd71827_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 bd71827_power *pwr, int *bat_online)
+{
+ int r, ret;
+
+#define BAT_OPEN 0x7
+ 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 bd71827_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 bd71827_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 bd71827_init_hardware(struct bd71827_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) {
+ int cc_val;
+
+ /* Ensure Coulomb Counter is stopped */
+ ret = stop_cc(pwr);
+ if (ret)
+ return ret;
+
+ /* Set Coulomb Counter Reset bit*/
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCNTRST,
+ BD7182x_MASK_CCNTRST);
+ if (ret)
+ return ret;
+
+ /* Clear Coulomb Counter Reset bit*/
+ ret = regmap_update_bits(pwr->regmap, pwr->regs->coulomb_ctrl,
+ BD7182x_MASK_CCNTRST, 0);
+ if (ret)
+ return ret;
+
+ /* Set initial Coulomb Counter by HW OCV */
+ calibration_coulomb_counter(pwr);
+
+ /* 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;
+
+ /* Set monitor threshold to 9/10 of battery uAh capacity */
+ cc_val = UAH_to_CC(pwr, pwr->battery_cap);
+
+ /*
+ * 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;
+ }
+
+ ret = bd7182x_write16(pwr, pwr->regs->batcap_mon_limit_u,
+ cc_val * 9 / 10);
+ if (ret)
+ return ret;
+
+ dev_dbg(pwr->dev, "REG_CC_BATCAP1_TH = %d\n",
+ (cc_val * 9 / 10));
+ }
+
+ return start_cc(pwr);
+}
+
+#define MK_2_100MCELSIUS(m_kevl_in) (((int)(m_kevl_in) - 273150) / 100)
+static int get_vdr_from_dt(struct bd71827_power *pwr,
+ struct fwnode_handle *node, int temp_values)
+{
+ int i, ret, num_values, *tmp_table;
+ u32 vdr_kelvin[NUM_VDR_TEMPS];
+
+ if (temp_values != NUM_VDR_TEMPS) {
+ dev_err(pwr->dev, "Bad VDR temperature table size (%d). Expected %d",
+ temp_values, NUM_VDR_TEMPS);
+ return -EINVAL;
+ }
+ ret = fwnode_property_read_u32_array(node,
+ "rohm,volt-drop-temp-millikelvin",
+ &vdr_kelvin[0], NUM_VDR_TEMPS);
+ if (ret) {
+ dev_err(pwr->dev, "Invalid VDR temperatures in device-tree");
+ return ret;
+ }
+ /* Convert millikelvin to .1 celsius as is expected by VDR algo */
+ for (i = 0; i < NUM_VDR_TEMPS; i++)
+ vdr_temps[i] = MK_2_100MCELSIUS(vdr_kelvin[i]);
+
+ num_values = fwnode_property_count_u32(node, "rohm,volt-drop-soc");
+ if (num_values <= 0 || num_values > MAX_NUM_VDR_VALUES) {
+ dev_err(pwr->dev, "malformed voltage drop parameters\n");
+ return -EINVAL;
+ }
+ g_num_vdr_params = num_values;
+
+ tmp_table = kcalloc(num_values, sizeof(int), GFP_KERNEL);
+ if (!tmp_table)
+ return -ENOMEM;
+ /*
+ * We collect NUM_VDR_TEMPS + 1 tables, all temperature tables +
+ * the SOC table
+ */
+ for (i = 0; i < NUM_VDR_TEMPS + 1; i++) {
+ int index;
+ static const char * const prop[] = {
+ /*
+ * SOC in units of 0.1 percent. TODO: Check if we have
+ * standard DT unit for percentage with higher accuracy
+ */
+ "rohm,volt-drop-soc",
+ "rohm,volt-drop-high-temp-microvolt",
+ "rohm,volt-drop-normal-temp-microvolt",
+ "rohm,volt-drop-low-temp-microvolt",
+ "rohm,volt-drop-very-low-temp-microvolt",
+ };
+ int *tables[5] = {
+ &soc_table[0], &vdr_table_h[0], &vdr_table_m[0],
+ &vdr_table_l[0], &vdr_table_vl[0]
+ };
+
+ if (num_values != fwnode_property_count_u32(node, prop[i])) {
+ dev_err(pwr->dev,
+ "%s: Bad size. Expected %d parameters",
+ prop[i], num_values);
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = fwnode_property_read_u32_array(node, prop[i], tmp_table,
+ num_values);
+ if (ret) {
+ dev_err(pwr->dev,
+ "Invalid VDR temperatures in device-tree");
+ goto out;
+ }
+ for (index = 0; index < num_values; index++)
+ tables[i][index] = tmp_table[index];
+ }
+out:
+ kfree(tmp_table);
+
+ return ret;
+}
+
+ /* Set default parameters if no module parameters were given */
+static int bd71827_set_battery_parameters(struct bd71827_power *pwr)
+{
+ int i;
+
+ /*
+ * We support getting battery parameters from static battery node
+ * or as module parameters.
+ */
+ if (!use_load_bat_params) {
+ int ret, num_vdr;
+ struct fwnode_handle *node;
+
+ /*
+ * power_supply_dev_get_battery_info uses devm internally
+ * so we do not need explicit remove()
+ */
+ ret = power_supply_dev_get_battery_info(pwr->dev->parent, NULL,
+ &pwr->batinfo);
+ if (ret) {
+ dev_err(pwr->dev, "No battery information (%d)\n", ret);
+ return ret;
+ }
+
+ if (!pwr->batinfo.ocv_table[0]) {
+ dev_err(pwr->dev, "No Open Circuit Voltages for battery\n");
+ return -EINVAL;
+ }
+
+ if (pwr->batinfo.charge_full_design_uah == -EINVAL) {
+ dev_err(pwr->dev, "Unknown battery capacity\n");
+ return -EINVAL;
+ }
+
+ if (pwr->batinfo.voltage_max_design_uv == -EINVAL) {
+ /*
+ * We could try digging this from OCV table but
+ * lets just bail-out for now
+ */
+ dev_err(pwr->dev, "Unknown max voltage\n");
+ return -EINVAL;
+ }
+ pwr->max_voltage = pwr->batinfo.voltage_max_design_uv;
+
+ if (pwr->batinfo.voltage_min_design_uv == -EINVAL) {
+ /* We could try digging this from OCV table....? */
+ dev_err(pwr->dev, "Unknown max voltage\n");
+ return -EINVAL;
+ }
+ pwr->min_voltage = pwr->batinfo.voltage_min_design_uv;
+ /*
+ * Let's default the zero-correction limit to 10%
+ * voltage limit. TODO: See what would be the correct value
+ */
+ pwr->battery_cap = pwr->batinfo.charge_full_design_uah;
+ pwr->gdesc.degrade_cycle_uah = pwr->batinfo.degrade_cycle_uah;
+
+ soc_est_max_num = SOC_EST_MAX_NUM_DEFAULT;
+
+ node = dev_fwnode(pwr->dev->parent);
+ if (!node) {
+ dev_err(pwr->dev, "no charger node\n");
+ return -ENODEV;
+ }
+ node = fwnode_find_reference(node, "monitored-battery", 0);
+ if (IS_ERR(node)) {
+ dev_err(pwr->dev, "No battery node found\n");
+ return PTR_ERR(node);
+ }
+
+ num_vdr = fwnode_property_count_u32(node, "rohm,volt-drop-temp-millikelvin");
+ if (num_vdr > 0) {
+ ret = get_vdr_from_dt(pwr, node, num_vdr);
+ if (ret)
+ return ret;
+ } else {
+ vdr_temps[VDR_TEMP_HIGH] = DGRD_TEMP_H_DEFAULT;
+ vdr_temps[VDR_TEMP_NORMAL] = DGRD_TEMP_M_DEFAULT;
+ vdr_temps[VDR_TEMP_LOW] = DGRD_TEMP_L_DEFAULT;
+ vdr_temps[VDR_TEMP_VERY_LOW] = DGRD_TEMP_VL_DEFAULT;
+ }
+ } else {
+ if (vdr_temps[VDR_TEMP_HIGH] == -EINVAL ||
+ vdr_temps[VDR_TEMP_NORMAL] == -EINVAL ||
+ vdr_temps[VDR_TEMP_LOW] == -EINVAL ||
+ vdr_temps[VDR_TEMP_VERY_LOW] == -EINVAL) {
+ vdr_temps[VDR_TEMP_HIGH] = DGRD_TEMP_H_DEFAULT;
+ vdr_temps[VDR_TEMP_NORMAL] = DGRD_TEMP_M_DEFAULT;
+ vdr_temps[VDR_TEMP_LOW] = DGRD_TEMP_L_DEFAULT;
+ vdr_temps[VDR_TEMP_VERY_LOW] = DGRD_TEMP_VL_DEFAULT;
+ }
+
+ pwr->min_voltage = param_max_voltage;
+ pwr->max_voltage = param_min_voltage;
+ pwr->low_thr_voltage = param_thr_voltage;
+ pwr->battery_cap = battery_cap_mah * 1000;
+ pwr->gdesc.degrade_cycle_uah = dgrd_cyc_cap;
+ }
+ /* SOC table is expected to be in descending order. */
+ if (!soc_table[0])
+ for (i = 0; i < NUM_BAT_PARAMS; i++)
+ soc_table[i] = soc_table_default[i];
+
+ if (!pwr->min_voltage || !pwr->max_voltage || !pwr->battery_cap) {
+ dev_err(pwr->dev, "Battery parameters missing\n");
+
+ return -EINVAL;
+ }
+ if (!pwr->low_thr_voltage)
+ pwr->low_thr_voltage = pwr->min_voltage +
+ (pwr->max_voltage - pwr->min_voltage) / 10;
+ return 0;
+}
+
+static int bd71827_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bd71827_power *pwr = dev_get_drvdata(psy->dev.parent);
+ u32 vot;
+ uint16_t 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 bd71827_battery_get_property(struct simple_gauge *gauge,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(gauge);
+ int ret = 0;
+ int status, health, tmp, curr, curr_avg;
+
+ if (psp == POWER_SUPPLY_PROP_STATUS || psp == POWER_SUPPLY_PROP_HEALTH
+ || psp == POWER_SUPPLY_PROP_CHARGE_TYPE)
+ ret = bd71827_charge_status(pwr, &status, &health);
+ else if (psp == POWER_SUPPLY_PROP_CURRENT_AVG ||
+ psp == POWER_SUPPLY_PROP_CURRENT_NOW)
+ ret = bd71827_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_CHARGE_TYPE:
+ if (status == POWER_SUPPLY_STATUS_CHARGING)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = get_bat_online(pwr, &tmp);
+ if (!ret)
+ val->intval = tmp;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = bd71827_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_VOLTAGE_MAX:
+ val->intval = pwr->max_voltage;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = pwr->min_voltage;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = MAX_CURRENT_DEFAULT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/** @brief ac properties */
+static enum power_supply_property bd71827_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static enum power_supply_property bd71827_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static ssize_t charging_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct bd71827_power *pwr = dev_get_drvdata(dev->parent);
+ ssize_t ret = 0;
+ unsigned long val;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val != 0 && val != 1) {
+ dev_warn(dev, "use 0/1 to disable/enable charging\n");
+ return -EINVAL;
+ }
+
+ if (val)
+ 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);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t charging_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct bd71827_power *pwr = dev_get_drvdata(dev->parent);
+ int chg_en, chg_online, ret;
+
+ ret = regmap_read(pwr->regmap, pwr->regs->chg_en, &chg_en);
+ if (ret)
+ return ret;
+
+ ret = get_chg_online(pwr, &chg_online);
+ if (ret)
+ return ret;
+
+ chg_en &= BD7182x_MASK_CHG_EN;
+ return sprintf(buf, "%x\n", chg_online && chg_en);
+}
+
+static DEVICE_ATTR_RW(charging);
+
+static struct attribute *bd71827_sysfs_attributes[] = {
+ &dev_attr_charging.attr, NULL,
+};
+
+static const struct attribute_group bd71827_sysfs_attr_group = {
+ .attrs = bd71827_sysfs_attributes,
+};
+
+static const struct attribute_group *bd71827_sysfs_attr_groups[] = {
+ &bd71827_sysfs_attr_group, NULL,
+};
+
+/** @brief powers supplied by bd71827_ac */
+static char *bd71827_ac_supplied_to[] = {
+ BAT_NAME,
+};
+
+static const struct power_supply_desc bd71827_ac_desc = {
+ .name = AC_NAME,
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = bd71827_charger_props,
+ .num_properties = ARRAY_SIZE(bd71827_charger_props),
+ .get_property = bd71827_charger_get_property,
+};
+
+static const struct simple_gauge_psy gauge_psy_config = {
+ .psy_name = BAT_NAME,
+ .additional_props = bd71827_battery_props,
+ .num_additional_props = ARRAY_SIZE(bd71827_battery_props),
+ .get_custom_property = bd71827_battery_get_property,
+};
+#ifdef PWRCTRL_HACK
+/*
+ * This is not-so-pretty hack for allowing external code to call
+ * bd71827_chip_hibernate() without this power device-data
+ */
+static struct bd71827_power *hack;
+static DEFINE_SPINLOCK(pwrlock);
+
+static struct bd71827_power *get_power(void)
+{
+ mutex_lock(&pwrlock);
+ if (!hack) {
+ mutex_unlock(&pwrlock);
+ return -ENOENT;
+ }
+ return hack;
+}
+
+static void put_power(void)
+{
+ mutex_unlock(&pwrlock);
+}
+
+static int set_power(struct bd71827_power *pwr)
+{
+ mutex_lock(&pwrlock);
+ hack = pwr;
+ mutex_unlock(&pwrlock);
+}
+
+static void free_power(void)
+{
+ mutex_lock(&pwrlock);
+ hack = NULL;
+ mutex_unlock(&pwrlock);
+}
+
+/*
+ * TODO: Find the corret way to do this
+ */
+void bd71827_chip_hibernate(void)
+{
+ struct bd71827_power *pwr = get_power();
+
+ if (IS_ERR(pwr)) {
+ pr_err("%s called before probe finished\n", __func__);
+ return PTR_ERR(pwr);
+ }
+
+ /* programming sequence in EANAB-151 */
+ regmap_update_bits(pwr->regmap, pwr->regs->pwrctrl,
+ pwr->regs->hibernate_mask, 0);
+ regmap_update_bits(pwr->regmap, pwr->regs->pwrctrl,
+ pwr->regs->hibernate_mask,
+ pwr->regs->hibernate_mask);
+ put_power();
+}
+#endif
+
+#define RSENS_CURR 10000000000LLU
+
+#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 bd71827_power *pwr = (struct bd71827_power *)data; \
+ \
+ if (run_gauge) \
+ simple_gauge_run(pwr->sw); \
+ dev_dbg(pwr->dev, "%s\n", print); \
+ power_supply_changed(pwr->sw->psy); \
+ \
+ return IRQ_HANDLED; \
+}
+
+#define BD_ISR_AC(name, print, run_gauge) \
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \
+{ \
+ struct bd71827_power *pwr = (struct bd71827_power *)data; \
+ \
+ if (run_gauge) \
+ simple_gauge_run(pwr->sw); \
+ power_supply_changed(pwr->ac); \
+ dev_dbg(pwr->dev, "%s\n", print); \
+ power_supply_changed(pwr->sw->psy); \
+ \
+ return IRQ_HANDLED; \
+}
+
+#define BD_ISR_DUMMY(name, print) \
+static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \
+{ \
+ struct bd71827_power *pwr = (struct bd71827_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 bd71827_power *pwr = (struct bd71827_power *)data;
+
+ simple_gauge_run(pwr->sw);
+ 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 bd71827_power *pwr = (struct bd71827_power *)data;
+
+ /*
+ * Battery is likely to be FULL => run simple_gauge to initiate
+ * CC setting
+ */
+ simple_gauge_run(pwr->sw);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd7182x_dcin_detected(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "DCIN inserted\n");
+ power_supply_changed(pwr->ac);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_vbat_low_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VBAT LOW Resumed\n");
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_vbat_low_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VBAT LOW Detected\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_hi_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_warn(pwr->dev, "Overtemp Detected\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_hi_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "Overtemp Resumed\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_low_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "Lowtemp Detected\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_bat_low_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "Lowtemp Resumed\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VF Detected\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VF Resumed\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf125_det(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VF125 Detected\n");
+ power_supply_changed(pwr->sw->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bd71827_temp_vf125_res(int irq, void *data)
+{
+ struct bd71827_power *pwr = (struct bd71827_power *)data;
+
+ dev_dbg(pwr->dev, "VF125 Resumed\n");
+ power_supply_changed(pwr->sw->psy);
+
+ 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 bd71827_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", bd71827_temp_bat_hi_res),
+ BDIRQ("bd71815-bat-temp-erange", bd71827_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", bd71827_vbat_low_res),
+ BDIRQ("bd71828-vbat-low", bd71827_vbat_low_det),
+ BDIRQ("bd71828-btemp-hi", bd71827_temp_bat_hi_det),
+ BDIRQ("bd71828-btemp-cool", bd71827_temp_bat_hi_res),
+ BDIRQ("bd71828-btemp-lo", bd71827_temp_bat_low_det),
+ BDIRQ("bd71828-btemp-warm", bd71827_temp_bat_low_res),
+ BDIRQ("bd71828-temp-hi", bd71827_temp_vf_det),
+ BDIRQ("bd71828-temp-norm", bd71827_temp_vf_res),
+ BDIRQ("bd71828-temp-125-over", bd71827_temp_vf125_det),
+ BDIRQ("bd71828-temp-125-under", bd71827_temp_vf125_res),
+ };
+ int num_irqs;
+ const struct bd7182x_irq_res *irqs;
+
+
+ switch (pwr->chip_type) {
+ case ROHM_CHIP_TYPE_BD71827:
+ 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 30000000
+
+static int bd7182x_get_rsens(struct bd71827_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;
+ uint32_t rs;
+
+ ret = fwnode_property_read_u32(node,
+ "rohm,charger-sense-resistor-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 ohm\n", pwr->rsens);
+ dev_dbg(pwr->dev, "Setting curr-factor to %u\n", pwr->curr_factor);
+ return 0;
+}
+
+/*
+ * BD71827 has no proper support for detecting relaxed battery.
+ * Driver has implemented current polling and logic has been that:
+ * if for the specified time current consumption has always been below
+ * threshold value when polled - then battery is assumed to be relaxed. This
+ * for sure leads to a problem when current cunsumption has had short
+ * 'spikes' - but this is what the logic has been - and it has probably been
+ * working as the driver is left as is? So let's just keep this logic here.
+ */
+static bool bd71827_is_relaxed(struct simple_gauge *sw, int *rex_volt)
+{
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+ int tmp_curr_mA, ret, curr, curr_avg;
+
+ ret = bd71827_get_current_ds_adc(pwr, &curr, &curr_avg);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to get current\n");
+ return false;
+ }
+
+ tmp_curr_mA = uAMP_TO_mAMP(curr);
+ if ((tmp_curr_mA * tmp_curr_mA) <=
+ (THR_RELAX_CURRENT_DEFAULT * THR_RELAX_CURRENT_DEFAULT))
+ /* No load */
+ pwr->relax_time = pwr->relax_time + (JITTER_DEFAULT / 1000);
+ else
+ pwr->relax_time = 0;
+ if (!(pwr->relax_time >= THR_RELAX_TIME_DEFAULT))
+ return false;
+
+ ret = bd71827_get_voltage(sw, rex_volt);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to get Vbat\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool bd71828_is_relaxed(struct simple_gauge *sw, int *rex_volt)
+{
+ int ret;
+ u16 tmp;
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_rex_avg,
+ BD7182x_MASK_VBAT_U, &tmp);
+ if (ret) {
+ dev_err(pwr->dev,
+ "Failed to read battery relax voltage\n");
+ return 0;
+ }
+ *rex_volt = tmp * 1000;
+
+ return !!tmp;
+}
+
+static int bd71828_get_cycle(struct simple_gauge *sw, int *cycle)
+{
+ int tmpret, ret, update = 0;
+ uint16_t charged_coulomb_cnt;
+ int cc_designed_cap;
+ struct bd71827_power *pwr = simple_gauge_get_drvdata(sw);
+
+ ret = bd7182x_read16_himask(pwr, pwr->regs->coulomb_chg3, 0xff,
+ &charged_coulomb_cnt);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to read charging CC (%d)\n", ret);
+ return ret;
+ }
+ dev_dbg(pwr->dev, "charged_coulomb_cnt = 0x%x\n",
+ (int)charged_coulomb_cnt);
+
+ cc_designed_cap = UAH_to_CC(pwr, sw->designed_cap);
+
+ while (charged_coulomb_cnt >= cc_designed_cap) {
+ update = 1;
+ /*
+ * sw-gauge caches old cycle value so we do not need to care
+ * about it. We just add new cycles
+ */
+ *cycle = *cycle + 1;
+ dev_dbg(pwr->dev, "Update cycle = %d\n", *cycle);
+ charged_coulomb_cnt -= cc_designed_cap;
+ }
+ if (update) {
+ ret = stop_cc(pwr);
+ if (ret)
+ return ret;
+
+ ret = bd7182x_write16(pwr, pwr->regs->coulomb_chg3,
+ charged_coulomb_cnt);
+ if (ret) {
+ dev_err(pwr->dev, "Failed to update charging CC (%d)\n",
+ ret);
+ }
+
+ tmpret = start_cc(pwr);
+ if (tmpret)
+ return tmpret;
+ }
+ return ret;
+}
+
+static void fgauge_initial_values(struct bd71827_power *pwr)
+{
+ struct simple_gauge_desc *d = &pwr->gdesc;
+ struct simple_gauge_ops *o = &pwr->ops;
+ bool use_vdr = false;
+
+ /* TODO: See if these could be get from DT? */
+ d->poll_interval = JITTER_DEFAULT; /* 3 seconds */
+ d->allow_set_cycle = true;
+ d->cap_adjust_volt_threshold = pwr->low_thr_voltage;
+ d->designed_cap = pwr->battery_cap;
+ d->clamp_soc = true;
+
+ o->get_uah_from_full = bd71828_get_uah_from_full; /* Ok */
+ o->get_uah = bd71828_get_uah; /* Ok */
+ o->update_cc_uah = bd71828_set_uah; /* Ok */
+ o->get_cycle = bd71828_get_cycle; /* Ok */
+ o->get_vsys = bd71827_get_vsys_min;
+
+ /*
+ * We have custom OCV table => provide our own volt_to_cap and
+ * ocv_by_soc which utilize the custom tables.
+ */
+ if (ocv_table[0]) {
+ dev_dbg(pwr->dev, "OCV values given as parameters\n");
+ o->get_soc_by_ocv = &bd71827_voltage_to_capacity;
+ o->get_ocv_by_soc = &bd71827_get_ocv;
+ }
+
+ if (vdr_table_h[0] && vdr_table_m[0] && vdr_table_l[0] &&
+ vdr_table_vl[0])
+ use_vdr = true;
+
+ /* TODO:
+ * o->suspend_calibrate = bd71828_suspend_calibrate;
+ */
+ switch (pwr->chip_type) {
+ case ROHM_CHIP_TYPE_BD71828:
+ o->get_temp = bd71828_get_temp;
+ o->is_relaxed = bd71828_is_relaxed;
+ if (use_vdr)
+ o->zero_cap_adjust = bd71828_zero_correct;
+ break;
+ case ROHM_CHIP_TYPE_BD71827:
+ o->get_temp = bd71827_get_temp;
+ o->is_relaxed = bd71827_is_relaxed;
+ if (use_vdr)
+ o->zero_cap_adjust = bd71828_zero_correct;
+ break;
+ case ROHM_CHIP_TYPE_BD71815:
+ o->get_temp = bd71827_get_temp;
+ o->is_relaxed = bd71828_is_relaxed;
+ /*
+ * TODO: BD71815 has not been used with VDR. This is untested
+ * but I don't see why it wouldn't work by setting thresholds
+ * and by populating correct SOC-OCV tables.
+ */
+ if (use_vdr)
+ o->zero_cap_adjust = bd71828_zero_correct;
+ break;
+ /*
+ * No need to handle default here as this is done already in probe.
+ * But this keeps gcc shut-up.
+ */
+ default:
+ break;
+ }
+}
+
+static int bd71827_power_probe(struct platform_device *pdev)
+{
+ struct bd71827_power *pwr;
+ struct power_supply_config ac_cfg = {};
+ struct simple_gauge_psy psycfg;
+ int ret;
+ struct regmap *regmap;
+
+ psycfg = gauge_psy_config;
+
+ 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->regs = &pwr_regs_bd71828;
+ dev_dbg(pwr->dev, "Found ROHM BD71828\n");
+ psycfg.psy_name = "bd71828-charger";
+ break;
+ case ROHM_CHIP_TYPE_BD71827:
+ pwr->bat_inserted = bd71828_bat_inserted;
+ pwr->regs = &pwr_regs_bd71827;
+ dev_dbg(pwr->dev, "Found ROHM BD71817\n");
+ psycfg.psy_name = "bd71827-charger";
+ break;
+ case ROHM_CHIP_TYPE_BD71815:
+ pwr->bat_inserted = bd71815_bat_inserted;
+ pwr->regs = &pwr_regs_bd71815;
+ psycfg.psy_name = "bd71815-charger";
+ dev_dbg(pwr->dev, "Found ROHM BD71815\n");
+ break;
+ default:
+ dev_err(pwr->dev, "Unknown PMIC\n");
+ return -EINVAL;
+ }
+
+ /* We need to set batcap etc before we do set fgauge initial values */
+ ret = bd71827_set_battery_parameters(pwr);
+ if (ret) {
+ dev_err(pwr->dev, "Missing battery parameters\n");
+
+ return ret;
+ }
+ fgauge_initial_values(pwr);
+
+ pwr->gdesc.drv_data = pwr;
+
+ ret = bd7182x_get_rsens(pwr);
+ if (ret) {
+ dev_err(&pdev->dev, "sense resistor missing\n");
+ return ret;
+ }
+
+ dev_set_drvdata(&pdev->dev, pwr);
+ bd71827_init_hardware(pwr);
+
+ psycfg.attr_grp = &bd71827_sysfs_attr_groups[0];
+ psycfg.of_node = pdev->dev.parent->of_node;
+
+ ac_cfg.supplied_to = bd71827_ac_supplied_to;
+ ac_cfg.num_supplicants = ARRAY_SIZE(bd71827_ac_supplied_to);
+ ac_cfg.drv_data = pwr;
+
+ pwr->ac = devm_power_supply_register(&pdev->dev, &bd71827_ac_desc,
+ &ac_cfg);
+ if (IS_ERR(pwr->ac)) {
+ ret = PTR_ERR(pwr->ac);
+ dev_err(&pdev->dev, "failed to register ac: %d\n", ret);
+ return ret;
+ }
+
+ pwr->sw = devm_psy_register_simple_gauge(pwr->dev, &psycfg, &pwr->ops,
+ &pwr->gdesc);
+ if (IS_ERR(pwr->sw)) {
+ dev_err(&pdev->dev, "SW-gauge registration failed\n");
+ return PTR_ERR(pwr->sw);
+ }
+
+ ret = bd7182x_get_irqs(pdev, pwr);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request IRQs: %d\n", ret);
+ return ret;
+ };
+
+ /* 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 bd71827_charger_id[] = {
+ { "bd71815-power", ROHM_CHIP_TYPE_BD71815 },
+ { "bd71827-power", ROHM_CHIP_TYPE_BD71827 },
+ { "bd71828-power", ROHM_CHIP_TYPE_BD71828 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, bd71827_charger_id);
+
+static struct platform_driver bd71827_power_driver = {
+ .driver = {
+ .name = "bd718xx-power",
+ },
+ .probe = bd71827_power_probe,
+ .id_table = bd71827_charger_id,
+};
+
+module_platform_driver(bd71827_power_driver);
+MODULE_ALIAS("platform:bd718xx-power");
+
+module_param(use_load_bat_params, int, 0444);
+MODULE_PARM_DESC(use_load_bat_params, "use_load_bat_params:Use loading battery parameters");
+
+module_param(param_max_voltage, int, 0444);
+MODULE_PARM_DESC(param_max_voltage,
+ "Maximum voltage of fully charged battery, uV");
+
+module_param(param_min_voltage, int, 0444);
+MODULE_PARM_DESC(param_min_voltage,
+ "Minimum voltage of fully drained battery, uV");
+
+module_param(param_thr_voltage, int, 0444);
+MODULE_PARM_DESC(param_thr_voltage,
+ "Threshold voltage for applying zero correction, uV");
+
+module_param(battery_cap_mah, int, 0444);
+MODULE_PARM_DESC(battery_cap_mah, "battery_cap_mah:Battery capacity (mAh)");
+
+module_param(dgrd_cyc_cap, int, 0444);
+MODULE_PARM_DESC(dgrd_cyc_cap, "dgrd_cyc_cap:Degraded capacity per cycle (uAh)");
+
+module_param(soc_est_max_num, int, 0444);
+MODULE_PARM_DESC(soc_est_max_num, "soc_est_max_num:SOC estimation max repeat number");
+
+module_param_array(ocv_table, int, NULL, 0444);
+MODULE_PARM_DESC(ocv_table, "ocv_table:Open Circuit Voltage table (uV)");
+
+module_param_array(vdr_temps, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_temps, "vdr_temps:temperatures for VDR tables. (0.1C)");
+
+module_param_array(vdr_table_h, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_h, "vdr_table_h:Voltage Drop Ratio temperature high area table");
+
+module_param_array(vdr_table_m, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_m, "vdr_table_m:Voltage Drop Ratio temperature middle area table");
+
+module_param_array(vdr_table_l, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_l, "vdr_table_l:Voltage Drop Ratio temperature low area table");
+
+module_param_array(vdr_table_vl, int, NULL, 0444);
+MODULE_PARM_DESC(vdr_table_vl, "vdr_table_vl:Voltage Drop Ratio temperature very low area table");
+
+MODULE_AUTHOR("Cong Pham <cpham2403@gmail.com>");
+MODULE_DESCRIPTION("ROHM BD718(15/17/27/28/78) PMIC Battery Charger driver");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2016
+ *
+ * @author cpham2403@gmail.com
+ */
+
+#ifndef __LINUX_MFD_BD71827_H
+#define __LINUX_MFD_BD71827_H
+
+#include <linux/regmap.h>
+
+#define LDO5VSEL_EQ_H 0
+
+#ifndef LDO5VSEL_EQ_H
+ #error define LDO5VSEL_EQ_H to 1 when connect to High, to 0 when connect to Low
+#else
+ #if LDO5VSEL_EQ_H == 1
+ #define BD71827_REG_LDO5_VOLT BD71827_REG_LDO5_VOLT_H
+ #define LDO5_MASK LDO5_H_MASK
+ #elif LDO5VSEL_EQ_H == 0
+ #define BD71827_REG_LDO5_VOLT BD71827_REG_LDO5_VOLT_L
+ #define LDO5_MASK LDO5_L_MASK
+ #else
+ #error Define LDO5VSEL_EQ_H only to 0 or 1
+ #endif
+#endif
+
+enum {
+ BD71827_BUCK1 = 0,
+ BD71827_BUCK2,
+ BD71827_BUCK3,
+ BD71827_BUCK4,
+ BD71827_BUCK5,
+ // General Purpose
+ BD71827_LDO1,
+ BD71827_LDO2,
+ BD71827_LDO3,
+ BD71827_LDO4,
+ BD71827_LDO5,
+ BD71827_LDO6,
+ // LDO for Secure Non-Volatile Storage
+ BD71827_LDOSNVS,
+ BD71827_REGULATOR_CNT,
+};
+
+#define BD71827_SUPPLY_STATE_ENABLED 0x1
+
+#define BD71827_BUCK1_VOLTAGE_NUM 0x3F
+#define BD71827_BUCK2_VOLTAGE_NUM 0x3F
+#define BD71827_BUCK3_VOLTAGE_NUM 0x3F
+#define BD71827_BUCK4_VOLTAGE_NUM 0x1F
+#define BD71827_BUCK5_VOLTAGE_NUM 0x1F
+#define BD71827_LDO1_VOLTAGE_NUM 0x3F
+#define BD71827_LDO2_VOLTAGE_NUM 0x3F
+#define BD71827_LDO3_VOLTAGE_NUM 0x3F
+#define BD71827_LDO4_VOLTAGE_NUM 0x3F
+#define BD71827_LDO5_VOLTAGE_NUM 0x3F
+#define BD71827_LDO6_VOLTAGE_NUM 0x1
+#define BD71827_LDOSNVS_VOLTAGE_NUM 0x1
+
+#define BD71827_GPIO_NUM 2 /* BD71827 have 2 GPO */
+
+enum {
+ BD71827_REG_DEVICE = 0x00,
+ BD71827_REG_PWRCTRL = 0x01,
+ BD71827_REG_BUCK1_MODE = 0x02,
+ BD71827_REG_BUCK2_MODE = 0x03,
+ BD71827_REG_BUCK3_MODE = 0x04,
+ BD71827_REG_BUCK4_MODE = 0x05,
+ BD71827_REG_BUCK5_MODE = 0x06,
+ BD71827_REG_BUCK1_VOLT_RUN = 0x07,
+ BD71827_REG_BUCK1_VOLT_SUSP = 0x08,
+ BD71827_REG_BUCK2_VOLT_RUN = 0x09,
+ BD71827_REG_BUCK2_VOLT_SUSP = 0x0A,
+ BD71827_REG_BUCK3_VOLT = 0x0B,
+ BD71827_REG_BUCK4_VOLT = 0x0C,
+ BD71827_REG_BUCK5_VOLT = 0x0D,
+ BD71827_REG_LED_CTRL = 0x0E,
+ BD71827_REG_reserved_0F = 0x0F,
+ BD71827_REG_LDO_MODE1 = 0x10,
+ BD71827_REG_LDO_MODE2 = 0x11,
+ BD71827_REG_LDO_MODE3 = 0x12,
+ BD71827_REG_LDO_MODE4 = 0x13,
+ BD71827_REG_LDO1_VOLT = 0x14,
+ BD71827_REG_LDO2_VOLT = 0x15,
+ BD71827_REG_LDO3_VOLT = 0x16,
+ BD71827_REG_LDO4_VOLT = 0x17,
+ BD71827_REG_LDO5_VOLT_H = 0x18,
+ BD71827_REG_LDO5_VOLT_L = 0x19,
+ BD71827_REG_BUCK_PD_DIS = 0x1A,
+ BD71827_REG_LDO_PD_DIS = 0x1B,
+ BD71827_REG_GPIO = 0x1C,
+ BD71827_REG_OUT32K = 0x1D,
+ BD71827_REG_SEC = 0x1E,
+ BD71827_REG_MIN = 0x1F,
+ BD71827_REG_HOUR = 0x20,
+ BD71827_REG_WEEK = 0x21,
+ BD71827_REG_DAY = 0x22,
+ BD71827_REG_MONTH = 0x23,
+ BD71827_REG_YEAR = 0x24,
+ BD71827_REG_ALM0_SEC = 0x25,
+ BD71827_REG_ALM0_MIN = 0x26,
+ BD71827_REG_ALM0_HOUR = 0x27,
+ BD71827_REG_ALM0_WEEK = 0x28,
+ BD71827_REG_ALM0_DAY = 0x29,
+ BD71827_REG_ALM0_MONTH = 0x2A,
+ BD71827_REG_ALM0_YEAR = 0x2B,
+ BD71827_REG_ALM1_SEC = 0x2C,
+ BD71827_REG_ALM1_MIN = 0x2D,
+ BD71827_REG_ALM1_HOUR = 0x2E,
+ BD71827_REG_ALM1_WEEK = 0x2F,
+ BD71827_REG_ALM1_DAY = 0x30,
+ BD71827_REG_ALM1_MONTH = 0x31,
+ BD71827_REG_ALM1_YEAR = 0x32,
+ BD71827_REG_ALM0_MASK = 0x33,
+ BD71827_REG_ALM1_MASK = 0x34,
+ BD71827_REG_ALM2 = 0x35,
+ BD71827_REG_TRIM = 0x36,
+ BD71827_REG_CONF = 0x37,
+ BD71827_REG_SYS_INIT = 0x38,
+ BD71827_REG_CHG_STATE = 0x39,
+ BD71827_REG_CHG_LAST_STATE = 0x3A,
+ BD71827_REG_BAT_STAT = 0x3B,
+ BD71827_REG_DCIN_STAT = 0x3C,
+ BD71827_REG_VSYS_STAT = 0x3D,
+ BD71827_REG_CHG_STAT = 0x3E,
+ BD71827_REG_CHG_WDT_STAT = 0x3F,
+ BD71827_REG_BAT_TEMP = 0x40,
+ BD71827_REG_ILIM_STAT = 0x41,
+ BD71827_REG_DCIN_SET = 0x42,
+ BD71827_REG_DCIN_CLPS = 0x43,
+ BD71827_REG_VSYS_REG = 0x44,
+ BD71827_REG_VSYS_MAX = 0x45,
+ BD71827_REG_VSYS_MIN = 0x46,
+ BD71827_REG_CHG_SET1 = 0x47,
+ BD71827_REG_CHG_SET2 = 0x48,
+ BD71827_REG_CHG_WDT_PRE = 0x49,
+ BD71827_REG_CHG_WDT_FST = 0x4A,
+ BD71827_REG_CHG_IPRE = 0x4B,
+ BD71827_REG_CHG_IFST = 0x4C,
+ BD71827_REG_CHG_IFST_TERM = 0x4D,
+ BD71827_REG_CHG_VPRE = 0x4E,
+ BD71827_REG_CHG_VBAT_1 = 0x4F,
+ BD71827_REG_CHG_VBAT_2 = 0x50,
+ BD71827_REG_CHG_VBAT_3 = 0x51,
+ BD71827_REG_CHG_LED_1 = 0x52,
+ BD71827_REG_VF_TH = 0x53,
+ BD71827_REG_BAT_SET_1 = 0x54,
+ BD71827_REG_BAT_SET_2 = 0x55,
+ BD71827_REG_BAT_SET_3 = 0x56,
+ BD71827_REG_ALM_VBAT_TH_U = 0x57,
+ BD71827_REG_ALM_VBAT_TH_L = 0x58,
+ BD71827_REG_ALM_DCIN_TH = 0x59,
+ BD71827_REG_ALM_VSYS_TH = 0x5A,
+ BD71827_REG_reserved_5B = 0x5B,
+ BD71827_REG_reserved_5C = 0x5C,
+ BD71827_REG_VM_VBAT_U = 0x5D,
+ BD71827_REG_VM_VBAT_L = 0x5E,
+ BD71827_REG_VM_BTMP = 0x5F,
+ BD71827_REG_VM_VTH = 0x60,
+ BD71827_REG_VM_DCIN_U = 0x61,
+ BD71827_REG_VM_DCIN_L = 0x62,
+ BD71827_REG_reserved_63 = 0x63,
+ BD71827_REG_VM_VF = 0x64,
+ BD71827_REG_reserved_65 = 0x65,
+ BD71827_REG_reserved_66 = 0x66,
+ BD71827_REG_VM_OCV_PRE_U = 0x67,
+ BD71827_REG_VM_OCV_PRE_L = 0x68,
+ BD71827_REG_reserved_69 = 0x69,
+ BD71827_REG_reserved_6A = 0x6A,
+ BD71827_REG_VM_OCV_PST_U = 0x6B,
+ BD71827_REG_VM_OCV_PST_L = 0x6C,
+ BD71827_REG_VM_SA_VBAT_U = 0x6D,
+ BD71827_REG_VM_SA_VBAT_L = 0x6E,
+ BD71827_REG_reserved_6F = 0x6F,
+ BD71827_REG_reserved_70 = 0x70,
+ BD71827_REG_CC_CTRL = 0x71,
+ BD71827_REG_CC_BATCAP1_TH_U = 0x72,
+ BD71827_REG_CC_BATCAP1_TH_L = 0x73,
+ BD71827_REG_CC_BATCAP2_TH_U = 0x74,
+ BD71827_REG_CC_BATCAP2_TH_L = 0x75,
+ BD71827_REG_CC_BATCAP3_TH_U = 0x76,
+ BD71827_REG_CC_BATCAP3_TH_L = 0x77,
+ BD71827_REG_CC_STAT = 0x78,
+ BD71827_REG_CC_CCNTD_3 = 0x79,
+ BD71827_REG_CC_CCNTD_2 = 0x7A,
+ BD71827_REG_CC_CCNTD_1 = 0x7B,
+ BD71827_REG_CC_CCNTD_0 = 0x7C,
+ BD71827_REG_CC_CURCD_U = 0x7D,
+ BD71827_REG_CC_CURCD_L = 0x7E,
+ BD71827_REG_CC_OCUR_THR_1 = 0x7F,
+ BD71827_REG_CC_OCUR_DUR_1 = 0x80,
+ BD71827_REG_CC_OCUR_THR_2 = 0x81,
+ BD71827_REG_CC_OCUR_DUR_2 = 0x82,
+ BD71827_REG_CC_OCUR_THR_3 = 0x83,
+ BD71827_REG_CC_OCUR_DUR_3 = 0x84,
+ BD71827_REG_CC_OCUR_MON = 0x85,
+ BD71827_REG_VM_BTMP_OV_THR = 0x86,
+ BD71827_REG_VM_BTMP_OV_DUR = 0x87,
+ BD71827_REG_VM_BTMP_LO_THR = 0x88,
+ BD71827_REG_VM_BTMP_LO_DUR = 0x89,
+ BD71827_REG_VM_BTMP_MON = 0x8A,
+ BD71827_REG_INT_EN_01 = 0x8B,
+ BD71827_REG_INT_EN_02 = 0x8C,
+ BD71827_REG_INT_EN_03 = 0x8D,
+ BD71827_REG_INT_EN_04 = 0x8E,
+ BD71827_REG_INT_EN_05 = 0x8F,
+ BD71827_REG_INT_EN_06 = 0x90,
+ BD71827_REG_INT_EN_07 = 0x91,
+ BD71827_REG_INT_EN_08 = 0x92,
+ BD71827_REG_INT_EN_09 = 0x93,
+ BD71827_REG_INT_EN_10 = 0x94,
+ BD71827_REG_INT_EN_11 = 0x95,
+ BD71827_REG_INT_EN_12 = 0x96,
+ BD71827_REG_INT_STAT = 0x97,
+ BD71827_REG_INT_STAT_01 = 0x98,
+ BD71827_REG_INT_STAT_02 = 0x99,
+ BD71827_REG_INT_STAT_03 = 0x9A,
+ BD71827_REG_INT_STAT_04 = 0x9B,
+ BD71827_REG_INT_STAT_05 = 0x9C,
+ BD71827_REG_INT_STAT_06 = 0x9D,
+ BD71827_REG_INT_STAT_07 = 0x9E,
+ BD71827_REG_INT_STAT_08 = 0x9F,
+ BD71827_REG_INT_STAT_09 = 0xA0,
+ BD71827_REG_INT_STAT_10 = 0xA1,
+ BD71827_REG_INT_STAT_11 = 0xA2,
+ BD71827_REG_INT_STAT_12 = 0xA3,
+ BD71827_REG_INT_UPDATE = 0xA4,
+ BD71827_REG_PWRCTRL2 = 0xA8,
+ BD71827_REG_PWRCTRL3 = 0xA9,
+ BD71827_REG_SWRESET = 0xAA,
+ BD71827_REG_BUCK1_VOLT_IDLE = 0xAB,
+ BD71827_REG_BUCK2_VOLT_IDLE = 0xAC,
+ BD71827_REG_ONEVNT_MODE_1 = 0xAD,
+ BD71827_REG_ONEVNT_MODE_2 = 0xAE,
+ BD71827_REG_RESERVE_0 = 0xB0,
+ BD71827_REG_RESERVE_1 = 0xB1,
+ BD71827_REG_RESERVE_2 = 0xB2,
+ BD71827_REG_RESERVE_3 = 0xB3,
+ BD71827_REG_RESERVE_4 = 0xB4,
+ BD71827_REG_RESERVE_5 = 0xB5,
+ BD71827_REG_RESERVE_6 = 0xB6,
+ BD71827_REG_RESERVE_7 = 0xB7,
+ BD71827_REG_RESERVE_8 = 0xB8,
+ BD71827_REG_RESERVE_9 = 0xB9,
+ BD71827_REG_RESERVE_A = 0xBA,
+ BD71827_REG_RESERVE_B = 0xBB,
+ BD71827_REG_RESERVE_C = 0xBC,
+ BD71827_REG_RESERVE_D = 0xBD,
+ BD71827_REG_RESERVE_E = 0xBE,
+ BD71827_REG_RESERVE_F = 0xBF,
+ BD71827_REG_VM_VSYS_U = 0xC0,
+ BD71827_REG_VM_VSYS_L = 0xC1,
+ BD71827_REG_VM_SA_VSYS_U = 0xC2,
+ BD71827_REG_VM_SA_VSYS_L = 0xC3,
+ BD71827_REG_CC_SA_CURCD_U = 0xC4,
+ BD71827_REG_CC_SA_CURCD_L = 0xC5,
+ BD71827_REG_BATID = 0xC6,
+ BD71827_REG_VM_SA_VBAT_MIN_U = 0xD4,
+ BD71827_REG_VM_SA_VBAT_MIN_L = 0xD5,
+ BD71827_REG_VM_SA_VBAT_MAX_U = 0xD6,
+ BD71827_REG_VM_SA_VBAT_MAX_L = 0xD7,
+ BD71827_REG_VM_SA_VSYS_MIN_U = 0xD8,
+ BD71827_REG_VM_SA_VSYS_MIN_L = 0xD9,
+ BD71827_REG_VM_SA_VSYS_MAX_U = 0xDA,
+ BD71827_REG_VM_SA_VSYS_MAX_L = 0xDB,
+ BD71827_REG_VM_SA_MINMAX_CLR = 0xDC,
+ BD71827_REG_VM_OCV_PWRON_U = 0xDD,
+ BD71827_REG_VM_OCV_PWRON_L = 0xDE,
+ BD71827_REG_REX_CCNTD_3 = 0xE0,
+ BD71827_REG_REX_CCNTD_2 = 0xE1,
+ BD71827_REG_REX_CCNTD_1 = 0xE2,
+ BD71827_REG_REX_CCNTD_0 = 0xE3,
+ BD71827_REG_REX_SA_VBAT_U = 0xE4,
+ BD71827_REG_REX_SA_VBAT_L = 0xE5,
+ BD71827_REG_REX_CTRL_1 = 0xE6,
+ BD71827_REG_REX_CTRL_2 = 0xE7,
+ BD71827_REG_FULL_CCNTD_3 = 0xE8,
+ BD71827_REG_FULL_CCNTD_2 = 0xE9,
+ BD71827_REG_FULL_CCNTD_1 = 0xEA,
+ BD71827_REG_FULL_CCNTD_0 = 0xEB,
+ BD71827_REG_FULL_CTRL = 0xEC,
+ BD71827_REG_CCNTD_CHG_3 = 0xF0,
+ BD71827_REG_CCNTD_CHG_2 = 0xF1,
+ BD71827_REG_INT_EN_13 = 0xF8,
+ BD71827_REG_INT_STAT_13 = 0xF9,
+ BD71827_REG_I2C_MAGIC = 0xFE,
+ BD71827_REG_PRODUCT = 0xFF,
+ BD71827_MAX_REGISTER = 0x100,
+};
+
+#define BD71827_REX_CLR_MASK 0x10
+
+#endif /* __LINUX_MFD_BD71827_H */
Add charger driver for ROHM BD718(15/27/28/78) PMIC charger block. Driver utilizes the simple-gauge for battery status / current polling, CC correction and SOC estimation. A version of driver which does not utilize simple-gauge can be seen in ROHM venor specific Linux tree - comparison can visualize how simple-gauge can simplify IC specific drivers. https://github.com/RohmSemiconductor/Linux-Kernel-PMIC-Drivers/blob/stable-v5.4.6/drivers/power/supply/bd71827-power.c Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com> --- Changes for RFCv3: - adapt to changed swgauge name (simple_gauge) - adapt to simple_gauge parameter changes - Initial BD71815 support - Use drvdata properly. - Sort includes - Prepare to provide dcin_collapse voltage from DT - clean unused defines - use OCV tables from batinfo if module params not given - do not directly call bd71827_voltage_to_capacity from calibration but use provided operation. - Mask the power-state from relax-condition on BD71815 as is done by the ROHM driver. REX state is used to do OCV => SOC conversion when battery is relaxed even if REX_CC was not used. - Clarify that we require either the module params or DT values for battery. Fail probe if parameters are not given. - Utilize degrade_cycle_uah aging degradation. - Get battery max and min values either as module parameters or from static battery node at DT. - Allow giving the zero correction threshold as a module param or compute it as 10% of "remaining battery voltage" based on max and min voltages given via DT. - Add proper MODULE_ALIAS - Implement VDR table reading from DT - Do not require fixed amount of battery parameters - Fix Coulomb Counter to uAh conversion - Fix endianess related warnings - clean-up comment - Avoid dividing by zero at VDR computation - Use the fwnode API instead of of_* API . don't assume 32bit int - Fix IC type prints - Fix the current sense resistor DT property *-ohm => *-ohms --- drivers/power/supply/Kconfig | 11 + drivers/power/supply/Makefile | 1 + drivers/power/supply/bd71827-power.c | 2473 ++++++++++++++++++++++++++ include/linux/mfd/rohm-bd71827.h | 295 +++ 4 files changed, 2780 insertions(+) create mode 100644 drivers/power/supply/bd71827-power.c create mode 100644 include/linux/mfd/rohm-bd71827.h