summaryrefslogtreecommitdiffstats
path: root/drivers/power/supply/max17042_battery.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/power/supply/max17042_battery.c
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/power/supply/max17042_battery.c')
-rw-r--r--drivers/power/supply/max17042_battery.c1225
1 files changed, 1225 insertions, 0 deletions
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
new file mode 100644
index 0000000000..17ac2ab78c
--- /dev/null
+++ b/drivers/power/supply/max17042_battery.c
@@ -0,0 +1,1225 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Fuel gauge driver for Maxim 17042 / 8966 / 8997
+// Note that Maxim 8966 and 8997 are mfd and this is its subdevice.
+//
+// Copyright (C) 2011 Samsung Electronics
+// MyungJoo Ham <myungjoo.ham@samsung.com>
+//
+// This driver is based on max17040_battery.c
+
+#include <linux/acpi.h>
+#include <linux/devm-helpers.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/mod_devicetable.h>
+#include <linux/power_supply.h>
+#include <linux/power/max17042_battery.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+/* Status register bits */
+#define STATUS_POR_BIT (1 << 1)
+#define STATUS_BST_BIT (1 << 3)
+#define STATUS_VMN_BIT (1 << 8)
+#define STATUS_TMN_BIT (1 << 9)
+#define STATUS_SMN_BIT (1 << 10)
+#define STATUS_BI_BIT (1 << 11)
+#define STATUS_VMX_BIT (1 << 12)
+#define STATUS_TMX_BIT (1 << 13)
+#define STATUS_SMX_BIT (1 << 14)
+#define STATUS_BR_BIT (1 << 15)
+
+/* Interrupt mask bits */
+#define CONFIG_ALRT_BIT_ENBL (1 << 2)
+
+#define VFSOC0_LOCK 0x0000
+#define VFSOC0_UNLOCK 0x0080
+#define MODEL_UNLOCK1 0X0059
+#define MODEL_UNLOCK2 0X00C4
+#define MODEL_LOCK1 0X0000
+#define MODEL_LOCK2 0X0000
+
+#define dQ_ACC_DIV 0x4
+#define dP_ACC_100 0x1900
+#define dP_ACC_200 0x3200
+
+#define MAX17042_VMAX_TOLERANCE 50 /* 50 mV */
+
+struct max17042_chip {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct power_supply *battery;
+ enum max170xx_chip_type chip_type;
+ struct max17042_platform_data *pdata;
+ struct work_struct work;
+ int init_complete;
+};
+
+static enum power_supply_property max17042_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
+ POWER_SUPPLY_PROP_TEMP_MIN,
+ POWER_SUPPLY_PROP_TEMP_MAX,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ // these two have to be at the end on the list
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+};
+
+static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
+{
+ int ret;
+ u32 data;
+ struct regmap *map = chip->regmap;
+
+ ret = regmap_read(map, MAX17042_TEMP, &data);
+ if (ret < 0)
+ return ret;
+
+ *temp = sign_extend32(data, 15);
+ /* The value is converted into deci-centigrade scale */
+ /* Units of LSB = 1 / 256 degree Celsius */
+ *temp = *temp * 10 / 256;
+ return 0;
+}
+
+static int max17042_get_status(struct max17042_chip *chip, int *status)
+{
+ int ret, charge_full, charge_now;
+ int avg_current;
+ u32 data;
+
+ ret = power_supply_am_i_supplied(chip->battery);
+ if (ret < 0) {
+ *status = POWER_SUPPLY_STATUS_UNKNOWN;
+ return 0;
+ }
+ if (ret == 0) {
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ /*
+ * The MAX170xx has builtin end-of-charge detection and will update
+ * FullCAP to match RepCap when it detects end of charging.
+ *
+ * When this cycle the battery gets charged to a higher (calculated)
+ * capacity then the previous cycle then FullCAP will get updated
+ * continuously once end-of-charge detection kicks in, so allow the
+ * 2 to differ a bit.
+ */
+
+ ret = regmap_read(chip->regmap, MAX17042_FullCAP, &charge_full);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(chip->regmap, MAX17042_RepCap, &charge_now);
+ if (ret < 0)
+ return ret;
+
+ if ((charge_full - charge_now) <= MAX17042_FULL_THRESHOLD) {
+ *status = POWER_SUPPLY_STATUS_FULL;
+ return 0;
+ }
+
+ /*
+ * Even though we are supplied, we may still be discharging if the
+ * supply is e.g. only delivering 5V 0.5A. Check current if available.
+ */
+ if (!chip->pdata->enable_current_sense) {
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+ return 0;
+ }
+
+ ret = regmap_read(chip->regmap, MAX17042_AvgCurrent, &data);
+ if (ret < 0)
+ return ret;
+
+ avg_current = sign_extend32(data, 15);
+ avg_current *= 1562500 / chip->pdata->r_sns;
+
+ if (avg_current > 0)
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ return 0;
+}
+
+static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
+{
+ int temp, vavg, vbatt, ret;
+ u32 val;
+
+ ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val);
+ if (ret < 0)
+ goto health_error;
+
+ /* bits [0-3] unused */
+ vavg = val * 625 / 8;
+ /* Convert to millivolts */
+ vavg /= 1000;
+
+ ret = regmap_read(chip->regmap, MAX17042_VCELL, &val);
+ if (ret < 0)
+ goto health_error;
+
+ /* bits [0-3] unused */
+ vbatt = val * 625 / 8;
+ /* Convert to millivolts */
+ vbatt /= 1000;
+
+ if (vavg < chip->pdata->vmin) {
+ *health = POWER_SUPPLY_HEALTH_DEAD;
+ goto out;
+ }
+
+ if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) {
+ *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ goto out;
+ }
+
+ ret = max17042_get_temperature(chip, &temp);
+ if (ret < 0)
+ goto health_error;
+
+ if (temp < chip->pdata->temp_min) {
+ *health = POWER_SUPPLY_HEALTH_COLD;
+ goto out;
+ }
+
+ if (temp > chip->pdata->temp_max) {
+ *health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ goto out;
+ }
+
+ *health = POWER_SUPPLY_HEALTH_GOOD;
+
+out:
+ return 0;
+
+health_error:
+ return ret;
+}
+
+static int max17042_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max17042_chip *chip = power_supply_get_drvdata(psy);
+ struct regmap *map = chip->regmap;
+ int ret;
+ u32 data;
+ u64 data64;
+
+ if (!chip->init_complete)
+ return -EAGAIN;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = max17042_get_status(chip, &val->intval);
+ if (ret < 0)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = regmap_read(map, MAX17042_STATUS, &data);
+ if (ret < 0)
+ return ret;
+
+ if (data & MAX17042_STATUS_BattAbsent)
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ ret = regmap_read(map, MAX17042_Cycles, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ ret = regmap_read(map, MAX17042_MinMaxVolt, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data >> 8;
+ val->intval *= 20000; /* Units of LSB = 20mV */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ ret = regmap_read(map, MAX17042_MinMaxVolt, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = (data & 0xff) * 20000; /* Units of 20mV */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
+ ret = regmap_read(map, MAX17042_V_empty, &data);
+ else
+ ret = regmap_read(map, MAX17047_V_empty, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data >> 7;
+ val->intval *= 10000; /* Units of LSB = 10mV */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = regmap_read(map, MAX17042_VCELL, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 625 / 8;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ ret = regmap_read(map, MAX17042_AvgVCELL, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 625 / 8;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ ret = regmap_read(map, MAX17042_OCVInternal, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 625 / 8;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (chip->pdata->enable_current_sense)
+ ret = regmap_read(map, MAX17042_RepSOC, &data);
+ else
+ ret = regmap_read(map, MAX17042_VFSOC, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data >> 8;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ ret = regmap_read(map, MAX17042_DesignCap, &data);
+ if (ret < 0)
+ return ret;
+
+ data64 = data * 5000000ll;
+ do_div(data64, chip->pdata->r_sns);
+ val->intval = data64;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = regmap_read(map, MAX17042_FullCAP, &data);
+ if (ret < 0)
+ return ret;
+
+ data64 = data * 5000000ll;
+ do_div(data64, chip->pdata->r_sns);
+ val->intval = data64;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = regmap_read(map, MAX17042_RepCap, &data);
+ if (ret < 0)
+ return ret;
+
+ data64 = data * 5000000ll;
+ do_div(data64, chip->pdata->r_sns);
+ val->intval = data64;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = regmap_read(map, MAX17042_QH, &data);
+ if (ret < 0)
+ return ret;
+
+ data64 = sign_extend64(data, 15) * 5000000ll;
+ val->intval = div_s64(data64, chip->pdata->r_sns);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = max17042_get_temperature(chip, &val->intval);
+ if (ret < 0)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+ if (ret < 0)
+ return ret;
+ /* LSB is Alert Minimum. In deci-centigrade */
+ val->intval = sign_extend32(data & 0xff, 7) * 10;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+ if (ret < 0)
+ return ret;
+ /* MSB is Alert Maximum. In deci-centigrade */
+ val->intval = sign_extend32(data >> 8, 7) * 10;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MIN:
+ val->intval = chip->pdata->temp_min;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MAX:
+ val->intval = chip->pdata->temp_max;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = max17042_get_battery_health(chip, &val->intval);
+ if (ret < 0)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (chip->pdata->enable_current_sense) {
+ ret = regmap_read(map, MAX17042_Current, &data);
+ if (ret < 0)
+ return ret;
+
+ data64 = sign_extend64(data, 15) * 1562500ll;
+ val->intval = div_s64(data64, chip->pdata->r_sns);
+ } else {
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ if (chip->pdata->enable_current_sense) {
+ ret = regmap_read(map, MAX17042_AvgCurrent, &data);
+ if (ret < 0)
+ return ret;
+
+ data64 = sign_extend64(data, 15) * 1562500ll;
+ val->intval = div_s64(data64, chip->pdata->r_sns);
+ } else {
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ ret = regmap_read(map, MAX17042_ICHGTerm, &data);
+ if (ret < 0)
+ return ret;
+
+ data64 = data * 1562500ll;
+ val->intval = div_s64(data64, chip->pdata->r_sns);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ ret = regmap_read(map, MAX17042_TTE, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 5625 / 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max17042_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max17042_chip *chip = power_supply_get_drvdata(psy);
+ struct regmap *map = chip->regmap;
+ int ret = 0;
+ u32 data;
+ int8_t temp;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+ if (ret < 0)
+ return ret;
+
+ /* Input in deci-centigrade, convert to centigrade */
+ temp = val->intval / 10;
+ /* force min < max */
+ if (temp >= (int8_t)(data >> 8))
+ temp = (int8_t)(data >> 8) - 1;
+ /* Write both MAX and MIN ALERT */
+ data = (data & 0xff00) + temp;
+ ret = regmap_write(map, MAX17042_TALRT_Th, data);
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+ if (ret < 0)
+ return ret;
+
+ /* Input in Deci-Centigrade, convert to centigrade */
+ temp = val->intval / 10;
+ /* force max > min */
+ if (temp <= (int8_t)(data & 0xff))
+ temp = (int8_t)(data & 0xff) + 1;
+ /* Write both MAX and MIN ALERT */
+ data = (data & 0xff) + (temp << 8);
+ ret = regmap_write(map, MAX17042_TALRT_Th, data);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int max17042_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
+{
+ int retries = 8;
+ int ret;
+ u32 read_value;
+
+ do {
+ ret = regmap_write(map, reg, value);
+ regmap_read(map, reg, &read_value);
+ if (read_value != value) {
+ ret = -EIO;
+ retries--;
+ }
+ } while (retries && read_value != value);
+
+ if (ret < 0)
+ pr_err("%s: err %d\n", __func__, ret);
+
+ return ret;
+}
+
+static inline void max17042_override_por(struct regmap *map,
+ u8 reg, u16 value)
+{
+ if (value)
+ regmap_write(map, reg, value);
+}
+
+static inline void max17042_unlock_model(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+
+ regmap_write(map, MAX17042_MLOCKReg1, MODEL_UNLOCK1);
+ regmap_write(map, MAX17042_MLOCKReg2, MODEL_UNLOCK2);
+}
+
+static inline void max17042_lock_model(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+
+ regmap_write(map, MAX17042_MLOCKReg1, MODEL_LOCK1);
+ regmap_write(map, MAX17042_MLOCKReg2, MODEL_LOCK2);
+}
+
+static inline void max17042_write_model_data(struct max17042_chip *chip,
+ u8 addr, int size)
+{
+ struct regmap *map = chip->regmap;
+ int i;
+
+ for (i = 0; i < size; i++)
+ regmap_write(map, addr + i,
+ chip->pdata->config_data->cell_char_tbl[i]);
+}
+
+static inline void max17042_read_model_data(struct max17042_chip *chip,
+ u8 addr, u16 *data, int size)
+{
+ struct regmap *map = chip->regmap;
+ int i;
+ u32 tmp;
+
+ for (i = 0; i < size; i++) {
+ regmap_read(map, addr + i, &tmp);
+ data[i] = (u16)tmp;
+ }
+}
+
+static inline int max17042_model_data_compare(struct max17042_chip *chip,
+ u16 *data1, u16 *data2, int size)
+{
+ int i;
+
+ if (memcmp(data1, data2, size)) {
+ dev_err(&chip->client->dev, "%s compare failed\n", __func__);
+ for (i = 0; i < size; i++)
+ dev_info(&chip->client->dev, "0x%x, 0x%x",
+ data1[i], data2[i]);
+ dev_info(&chip->client->dev, "\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max17042_init_model(struct max17042_chip *chip)
+{
+ int ret;
+ int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
+ u16 *temp_data;
+
+ temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL);
+ if (!temp_data)
+ return -ENOMEM;
+
+ max17042_unlock_model(chip);
+ max17042_write_model_data(chip, MAX17042_MODELChrTbl,
+ table_size);
+ max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data,
+ table_size);
+
+ ret = max17042_model_data_compare(
+ chip,
+ chip->pdata->config_data->cell_char_tbl,
+ temp_data,
+ table_size);
+
+ max17042_lock_model(chip);
+ kfree(temp_data);
+
+ return ret;
+}
+
+static int max17042_verify_model_lock(struct max17042_chip *chip)
+{
+ int i;
+ int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
+ u16 *temp_data;
+ int ret = 0;
+
+ temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL);
+ if (!temp_data)
+ return -ENOMEM;
+
+ max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data,
+ table_size);
+ for (i = 0; i < table_size; i++)
+ if (temp_data[i])
+ ret = -EINVAL;
+
+ kfree(temp_data);
+ return ret;
+}
+
+static void max17042_write_config_regs(struct max17042_chip *chip)
+{
+ struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
+
+ regmap_write(map, MAX17042_CONFIG, config->config);
+ regmap_write(map, MAX17042_LearnCFG, config->learn_cfg);
+ regmap_write(map, MAX17042_FilterCFG,
+ config->filter_cfg);
+ regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg);
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 ||
+ chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050 ||
+ chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+ regmap_write(map, MAX17047_FullSOCThr,
+ config->full_soc_thresh);
+}
+
+static void max17042_write_custom_regs(struct max17042_chip *chip)
+{
+ struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
+
+ max17042_write_verify_reg(map, MAX17042_RCOMP0, config->rcomp0);
+ max17042_write_verify_reg(map, MAX17042_TempCo, config->tcompc0);
+ max17042_write_verify_reg(map, MAX17042_ICHGTerm, config->ichgt_term);
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) {
+ regmap_write(map, MAX17042_EmptyTempCo, config->empty_tempco);
+ max17042_write_verify_reg(map, MAX17042_K_empty0,
+ config->kempty0);
+ } else {
+ max17042_write_verify_reg(map, MAX17047_QRTbl00,
+ config->qrtbl00);
+ max17042_write_verify_reg(map, MAX17047_QRTbl10,
+ config->qrtbl10);
+ max17042_write_verify_reg(map, MAX17047_QRTbl20,
+ config->qrtbl20);
+ max17042_write_verify_reg(map, MAX17047_QRTbl30,
+ config->qrtbl30);
+ }
+}
+
+static void max17042_update_capacity_regs(struct max17042_chip *chip)
+{
+ struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
+
+ max17042_write_verify_reg(map, MAX17042_FullCAP,
+ config->fullcap);
+ regmap_write(map, MAX17042_DesignCap, config->design_cap);
+ max17042_write_verify_reg(map, MAX17042_FullCAPNom,
+ config->fullcapnom);
+}
+
+static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip)
+{
+ unsigned int vfSoc;
+ struct regmap *map = chip->regmap;
+
+ regmap_read(map, MAX17042_VFSOC, &vfSoc);
+ regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK);
+ max17042_write_verify_reg(map, MAX17042_VFSOC0, vfSoc);
+ regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_LOCK);
+}
+
+static void max17042_load_new_capacity_params(struct max17042_chip *chip)
+{
+ u32 full_cap0, rep_cap, dq_acc, vfSoc;
+ u32 rem_cap;
+
+ struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
+
+ regmap_read(map, MAX17042_FullCAP0, &full_cap0);
+ regmap_read(map, MAX17042_VFSOC, &vfSoc);
+
+ /* fg_vfSoc needs to shifted by 8 bits to get the
+ * perc in 1% accuracy, to get the right rem_cap multiply
+ * full_cap0, fg_vfSoc and devide by 100
+ */
+ rem_cap = ((vfSoc >> 8) * full_cap0) / 100;
+ max17042_write_verify_reg(map, MAX17042_RemCap, rem_cap);
+
+ rep_cap = rem_cap;
+ max17042_write_verify_reg(map, MAX17042_RepCap, rep_cap);
+
+ /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */
+ dq_acc = config->fullcap / dQ_ACC_DIV;
+ max17042_write_verify_reg(map, MAX17042_dQacc, dq_acc);
+ max17042_write_verify_reg(map, MAX17042_dPacc, dP_ACC_200);
+
+ max17042_write_verify_reg(map, MAX17042_FullCAP,
+ config->fullcap);
+ regmap_write(map, MAX17042_DesignCap,
+ config->design_cap);
+ max17042_write_verify_reg(map, MAX17042_FullCAPNom,
+ config->fullcapnom);
+ /* Update SOC register with new SOC */
+ regmap_write(map, MAX17042_RepSOC, vfSoc);
+}
+
+/*
+ * Block write all the override values coming from platform data.
+ * This function MUST be called before the POR initialization procedure
+ * specified by maxim.
+ */
+static inline void max17042_override_por_values(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+ struct max17042_config_data *config = chip->pdata->config_data;
+
+ max17042_override_por(map, MAX17042_TGAIN, config->tgain);
+ max17042_override_por(map, MAX17042_TOFF, config->toff);
+ max17042_override_por(map, MAX17042_CGAIN, config->cgain);
+ max17042_override_por(map, MAX17042_COFF, config->coff);
+
+ max17042_override_por(map, MAX17042_VALRT_Th, config->valrt_thresh);
+ max17042_override_por(map, MAX17042_TALRT_Th, config->talrt_thresh);
+ max17042_override_por(map, MAX17042_SALRT_Th,
+ config->soc_alrt_thresh);
+ max17042_override_por(map, MAX17042_CONFIG, config->config);
+ max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer);
+
+ max17042_override_por(map, MAX17042_DesignCap, config->design_cap);
+ max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term);
+
+ max17042_override_por(map, MAX17042_AtRate, config->at_rate);
+ max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg);
+ max17042_override_por(map, MAX17042_FilterCFG, config->filter_cfg);
+ max17042_override_por(map, MAX17042_RelaxCFG, config->relax_cfg);
+ max17042_override_por(map, MAX17042_MiscCFG, config->misc_cfg);
+
+ max17042_override_por(map, MAX17042_FullCAP, config->fullcap);
+ max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom);
+ max17042_override_por(map, MAX17042_dQacc, config->dqacc);
+ max17042_override_por(map, MAX17042_dPacc, config->dpacc);
+
+ max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
+ max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
+
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) {
+ max17042_override_por(map, MAX17042_MaskSOC, config->masksoc);
+ max17042_override_por(map, MAX17042_SOC_empty, config->socempty);
+ max17042_override_por(map, MAX17042_V_empty, config->vempty);
+ max17042_override_por(map, MAX17042_EmptyTempCo, config->empty_tempco);
+ max17042_override_por(map, MAX17042_K_empty0, config->kempty0);
+ }
+
+ if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) {
+ max17042_override_por(map, MAX17042_IAvg_empty, config->iavg_empty);
+ max17042_override_por(map, MAX17042_TempNom, config->temp_nom);
+ max17042_override_por(map, MAX17042_TempLim, config->temp_lim);
+ max17042_override_por(map, MAX17042_FCTC, config->fctc);
+ }
+
+ if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)) {
+ max17042_override_por(map, MAX17047_V_empty, config->vempty);
+ }
+}
+
+static int max17042_init_chip(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+ int ret;
+
+ max17042_override_por_values(chip);
+ /* After Power up, the MAX17042 requires 500mS in order
+ * to perform signal debouncing and initial SOC reporting
+ */
+ msleep(500);
+
+ /* Initialize configuration */
+ max17042_write_config_regs(chip);
+
+ /* write cell characterization data */
+ ret = max17042_init_model(chip);
+ if (ret) {
+ dev_err(&chip->client->dev, "%s init failed\n",
+ __func__);
+ return -EIO;
+ }
+
+ ret = max17042_verify_model_lock(chip);
+ if (ret) {
+ dev_err(&chip->client->dev, "%s lock verify failed\n",
+ __func__);
+ return -EIO;
+ }
+ /* write custom parameters */
+ max17042_write_custom_regs(chip);
+
+ /* update capacity params */
+ max17042_update_capacity_regs(chip);
+
+ /* delay must be atleast 350mS to allow VFSOC
+ * to be calculated from the new configuration
+ */
+ msleep(350);
+
+ /* reset vfsoc0 reg */
+ max17042_reset_vfsoc0_reg(chip);
+
+ /* load new capacity params */
+ max17042_load_new_capacity_params(chip);
+
+ /* Init complete, Clear the POR bit */
+ regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0);
+ return 0;
+}
+
+static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
+{
+ struct regmap *map = chip->regmap;
+ u32 soc, soc_tr;
+
+ /* program interrupt thresholds such that we should
+ * get interrupt for every 'off' perc change in the soc
+ */
+ regmap_read(map, MAX17042_RepSOC, &soc);
+ soc >>= 8;
+ soc_tr = (soc + off) << 8;
+ if (off < soc)
+ soc_tr |= soc - off;
+ regmap_write(map, MAX17042_SALRT_Th, soc_tr);
+}
+
+static irqreturn_t max17042_thread_handler(int id, void *dev)
+{
+ struct max17042_chip *chip = dev;
+ u32 val;
+ int ret;
+
+ ret = regmap_read(chip->regmap, MAX17042_STATUS, &val);
+ if (ret)
+ return IRQ_HANDLED;
+
+ if ((val & STATUS_SMN_BIT) || (val & STATUS_SMX_BIT)) {
+ dev_dbg(&chip->client->dev, "SOC threshold INTR\n");
+ max17042_set_soc_threshold(chip, 1);
+ }
+
+ /* we implicitly handle all alerts via power_supply_changed */
+ regmap_clear_bits(chip->regmap, MAX17042_STATUS,
+ 0xFFFF & ~(STATUS_POR_BIT | STATUS_BST_BIT));
+
+ power_supply_changed(chip->battery);
+ return IRQ_HANDLED;
+}
+
+static void max17042_init_worker(struct work_struct *work)
+{
+ struct max17042_chip *chip = container_of(work,
+ struct max17042_chip, work);
+ int ret;
+
+ /* Initialize registers according to values from the platform data */
+ if (chip->pdata->enable_por_init && chip->pdata->config_data) {
+ ret = max17042_init_chip(chip);
+ if (ret)
+ return;
+ }
+
+ chip->init_complete = 1;
+}
+
+#ifdef CONFIG_OF
+static struct max17042_platform_data *
+max17042_get_of_pdata(struct max17042_chip *chip)
+{
+ struct device *dev = &chip->client->dev;
+ struct device_node *np = dev->of_node;
+ u32 prop;
+ struct max17042_platform_data *pdata;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ /*
+ * Require current sense resistor value to be specified for
+ * current-sense functionality to be enabled at all.
+ */
+ if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) {
+ pdata->r_sns = prop;
+ pdata->enable_current_sense = true;
+ }
+
+ if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min))
+ pdata->temp_min = INT_MIN;
+ if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max))
+ pdata->temp_max = INT_MAX;
+ if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin))
+ pdata->vmin = INT_MIN;
+ if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax))
+ pdata->vmax = INT_MAX;
+
+ return pdata;
+}
+#endif
+
+static struct max17042_reg_data max17047_default_pdata_init_regs[] = {
+ /*
+ * Some firmwares do not set FullSOCThr, Enable End-of-Charge Detection
+ * when the voltage FG reports 95%, as recommended in the datasheet.
+ */
+ { MAX17047_FullSOCThr, MAX17042_BATTERY_FULL << 8 },
+};
+
+static struct max17042_platform_data *
+max17042_get_default_pdata(struct max17042_chip *chip)
+{
+ struct device *dev = &chip->client->dev;
+ struct max17042_platform_data *pdata;
+ int ret, misc_cfg;
+
+ /*
+ * The MAX17047 gets used on x86 where we might not have pdata, assume
+ * the firmware will already have initialized the fuel-gauge and provide
+ * default values for the non init bits to make things work.
+ */
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return pdata;
+
+ if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) {
+ pdata->init_data = max17047_default_pdata_init_regs;
+ pdata->num_init_data =
+ ARRAY_SIZE(max17047_default_pdata_init_regs);
+ }
+
+ ret = regmap_read(chip->regmap, MAX17042_MiscCFG, &misc_cfg);
+ if (ret < 0)
+ return NULL;
+
+ /* If bits 0-1 are set to 3 then only Voltage readings are used */
+ if ((misc_cfg & 0x3) == 0x3)
+ pdata->enable_current_sense = false;
+ else
+ pdata->enable_current_sense = true;
+
+ pdata->vmin = MAX17042_DEFAULT_VMIN;
+ pdata->vmax = MAX17042_DEFAULT_VMAX;
+ pdata->temp_min = MAX17042_DEFAULT_TEMP_MIN;
+ pdata->temp_max = MAX17042_DEFAULT_TEMP_MAX;
+
+ return pdata;
+}
+
+static struct max17042_platform_data *
+max17042_get_pdata(struct max17042_chip *chip)
+{
+ struct device *dev = &chip->client->dev;
+
+#ifdef CONFIG_OF
+ if (dev->of_node)
+ return max17042_get_of_pdata(chip);
+#endif
+ if (dev->platform_data)
+ return dev->platform_data;
+
+ return max17042_get_default_pdata(chip);
+}
+
+static const struct regmap_config max17042_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+};
+
+static const struct power_supply_desc max17042_psy_desc = {
+ .name = "max170xx_battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = max17042_get_property,
+ .set_property = max17042_set_property,
+ .property_is_writeable = max17042_property_is_writeable,
+ .external_power_changed = power_supply_changed,
+ .properties = max17042_battery_props,
+ .num_properties = ARRAY_SIZE(max17042_battery_props),
+};
+
+static const struct power_supply_desc max17042_no_current_sense_psy_desc = {
+ .name = "max170xx_battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = max17042_get_property,
+ .set_property = max17042_set_property,
+ .property_is_writeable = max17042_property_is_writeable,
+ .properties = max17042_battery_props,
+ .num_properties = ARRAY_SIZE(max17042_battery_props) - 2,
+};
+
+static int max17042_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct i2c_adapter *adapter = client->adapter;
+ const struct power_supply_desc *max17042_desc = &max17042_psy_desc;
+ struct power_supply_config psy_cfg = {};
+ const struct acpi_device_id *acpi_id = NULL;
+ struct device *dev = &client->dev;
+ struct max17042_chip *chip;
+ int ret;
+ int i;
+ u32 val;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return -EIO;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ if (id) {
+ chip->chip_type = id->driver_data;
+ } else {
+ acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!acpi_id)
+ return -ENODEV;
+
+ chip->chip_type = acpi_id->driver_data;
+ }
+ chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ dev_err(&client->dev, "Failed to initialize regmap\n");
+ return -EINVAL;
+ }
+
+ chip->pdata = max17042_get_pdata(chip);
+ if (!chip->pdata) {
+ dev_err(&client->dev, "no platform data provided\n");
+ return -EINVAL;
+ }
+
+ i2c_set_clientdata(client, chip);
+ psy_cfg.drv_data = chip;
+ psy_cfg.of_node = dev->of_node;
+
+ /* When current is not measured,
+ * CURRENT_NOW and CURRENT_AVG properties should be invisible. */
+ if (!chip->pdata->enable_current_sense)
+ max17042_desc = &max17042_no_current_sense_psy_desc;
+
+ if (chip->pdata->r_sns == 0)
+ chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
+
+ if (chip->pdata->init_data)
+ for (i = 0; i < chip->pdata->num_init_data; i++)
+ regmap_write(chip->regmap,
+ chip->pdata->init_data[i].addr,
+ chip->pdata->init_data[i].data);
+
+ if (!chip->pdata->enable_current_sense) {
+ regmap_write(chip->regmap, MAX17042_CGAIN, 0x0000);
+ regmap_write(chip->regmap, MAX17042_MiscCFG, 0x0003);
+ regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007);
+ }
+
+ chip->battery = devm_power_supply_register(&client->dev, max17042_desc,
+ &psy_cfg);
+ if (IS_ERR(chip->battery)) {
+ dev_err(&client->dev, "failed: power supply register\n");
+ return PTR_ERR(chip->battery);
+ }
+
+ if (client->irq) {
+ unsigned int flags = IRQF_ONESHOT;
+
+ /*
+ * On ACPI systems the IRQ may be handled by ACPI-event code,
+ * so we need to share (if the ACPI code is willing to share).
+ */
+ if (acpi_id)
+ flags |= IRQF_SHARED | IRQF_PROBE_SHARED;
+
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL,
+ max17042_thread_handler, flags,
+ chip->battery->desc->name,
+ chip);
+ if (!ret) {
+ regmap_update_bits(chip->regmap, MAX17042_CONFIG,
+ CONFIG_ALRT_BIT_ENBL,
+ CONFIG_ALRT_BIT_ENBL);
+ max17042_set_soc_threshold(chip, 1);
+ } else {
+ client->irq = 0;
+ if (ret != -EBUSY)
+ dev_err(&client->dev, "Failed to get IRQ\n");
+ }
+ }
+ /* Not able to update the charge threshold when exceeded? -> disable */
+ if (!client->irq)
+ regmap_write(chip->regmap, MAX17042_SALRT_Th, 0xff00);
+
+ regmap_read(chip->regmap, MAX17042_STATUS, &val);
+ if (val & STATUS_POR_BIT) {
+ ret = devm_work_autocancel(&client->dev, &chip->work,
+ max17042_init_worker);
+ if (ret)
+ return ret;
+ schedule_work(&chip->work);
+ } else {
+ chip->init_complete = 1;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int max17042_suspend(struct device *dev)
+{
+ struct max17042_chip *chip = dev_get_drvdata(dev);
+
+ /*
+ * disable the irq and enable irq_wake
+ * capability to the interrupt line.
+ */
+ if (chip->client->irq) {
+ disable_irq(chip->client->irq);
+ enable_irq_wake(chip->client->irq);
+ }
+
+ return 0;
+}
+
+static int max17042_resume(struct device *dev)
+{
+ struct max17042_chip *chip = dev_get_drvdata(dev);
+
+ if (chip->client->irq) {
+ disable_irq_wake(chip->client->irq);
+ enable_irq(chip->client->irq);
+ /* re-program the SOC thresholds to 1% change */
+ max17042_set_soc_threshold(chip, 1);
+ }
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(max17042_pm_ops, max17042_suspend,
+ max17042_resume);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id max17042_acpi_match[] = {
+ { "MAX17047", MAXIM_DEVICE_TYPE_MAX17047 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, max17042_acpi_match);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id max17042_dt_match[] = {
+ { .compatible = "maxim,max17042" },
+ { .compatible = "maxim,max17047" },
+ { .compatible = "maxim,max17050" },
+ { .compatible = "maxim,max17055" },
+ { .compatible = "maxim,max77849-battery" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, max17042_dt_match);
+#endif
+
+static const struct i2c_device_id max17042_id[] = {
+ { "max17042", MAXIM_DEVICE_TYPE_MAX17042 },
+ { "max17047", MAXIM_DEVICE_TYPE_MAX17047 },
+ { "max17050", MAXIM_DEVICE_TYPE_MAX17050 },
+ { "max17055", MAXIM_DEVICE_TYPE_MAX17055 },
+ { "max77849-battery", MAXIM_DEVICE_TYPE_MAX17047 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max17042_id);
+
+static struct i2c_driver max17042_i2c_driver = {
+ .driver = {
+ .name = "max17042",
+ .acpi_match_table = ACPI_PTR(max17042_acpi_match),
+ .of_match_table = of_match_ptr(max17042_dt_match),
+ .pm = &max17042_pm_ops,
+ },
+ .probe = max17042_probe,
+ .id_table = max17042_id,
+};
+module_i2c_driver(max17042_i2c_driver);
+
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_DESCRIPTION("MAX17042 Fuel Gauge");
+MODULE_LICENSE("GPL");