summaryrefslogtreecommitdiff
path: root/drivers/power/max17042_battery.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/max17042_battery.c')
-rw-r--r--drivers/power/max17042_battery.c864
1 files changed, 801 insertions, 63 deletions
diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c
index d664ef58afa..c6c70c4afeb 100644
--- a/drivers/power/max17042_battery.c
+++ b/drivers/power/max17042_battery.c
@@ -33,6 +33,10 @@
#include <linux/power_supply.h>
#include <linux/power/max17042_battery.h>
#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/debugfs.h>
/* Status register bits */
#define STATUS_POR_BIT (1 << 1)
@@ -48,32 +52,101 @@
/* Interrupt mask bits */
#define CONFIG_ALRT_BIT_ENBL (1 << 2)
-#define STATUS_INTR_SOCMIN_BIT (1 << 10)
-#define STATUS_INTR_SOCMAX_BIT (1 << 14)
-
+#define CONFIG_VS_BIT_ENBL (1 << 12)
+#define CONFIG_TS_BIT_ENBL (1 << 13)
+#define CONFIG_SS_BIT_ENBL (1 << 14)
+#define CONFIG_STICK_ALL_ENBL (CONFIG_VS_BIT_ENBL | \
+CONFIG_TS_BIT_ENBL | CONFIG_SS_BIT_ENBL)
#define VFSOC0_LOCK 0x0000
-#define VFSOC0_UNLOCK 0x0080
+#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_INIT_NUM_CYCLES 160
+#define MAX17047_INIT_NUM_CYCLES 96
+
+#define MAX17042_dQ_ACC_DIV 4
+#define MAX17047_dQ_ACC_DIV 16
+
+#define MAX17042_dP_ACC_200 0x3200
+#define MAX17047_dP_ACC_200 0x0C80
#define MAX17042_IC_VERSION 0x0092
#define MAX17047_IC_VERSION 0x00AC /* same for max17050 */
+#define MAX17042_AGE_DIV 256
+
+#define INIT_DATA_PROPERTY "maxim,regs-init-data"
+#define CONFIG_NODE "maxim,configuration"
+#define VERSION_PROPERTY "version"
+#define CONFIG_PROPERTY "config"
+#define FULL_SOC_THRESH_PROPERTY "full_soc_thresh"
+#define DESIGN_CAP_PROPERTY "design_cap"
+#define ICHGT_TERM_PROPERTY "ichgt_term"
+#define LEARN_CFG_PROPERTY "learn_cfg"
+#define FILTER_CFG_PROPERTY "filter_cfg"
+#define RELAX_CFG_PROPERTY "relax_cfg"
+#define FULLCAP_PROPERTY "fullcap"
+#define FULLCAPNOM_PROPERTY "fullcapnom"
+#define QRTBL00_PROPERTY "qrtbl00"
+#define QRTBL10_PROPERTY "qrtbl10"
+#define QRTBL20_PROPERTY "qrtbl20"
+#define QRTBL30_PROPERTY "qrtbl30"
+#define RCOMP0_PROPERTY "rcomp0"
+#define TCOMPC0_PROPERTY "tcompc0"
+#define CELL_CHAR_TBL_PROPERTY "maxim,cell-char-tbl"
+#define TGAIN_PROPERTY "tgain"
+#define TOFF_PROPERTY "toff"
+#define TEMP_CONV_NODE "maxim,temp-conv"
+#define RESULT_PROPERTY "result"
+#define START_PROPERTY "start"
+
+/* we need to set the alert threshold to a default value
+ before powerlib calls into the driver */
+#define DEFAULT_ALERT_THRESHOLD 1
+
struct max17042_chip {
struct i2c_client *client;
struct power_supply battery;
enum max170xx_chip_type chip_type;
struct max17042_platform_data *pdata;
struct work_struct work;
+ struct work_struct work_malicious_removed;
int init_complete;
+ bool batt_undervoltage;
+ u16 alert_threshold;
+#ifdef CONFIG_BATTERY_MAX17042_DEBUGFS
+ struct dentry *debugfs_root;
+ u8 debugfs_addr;
+ u8 debugfs_capacity;
+#endif
+ struct mutex lock; /* lock to control access during reset */
+ struct power_supply *psy_malicious;
+ int malicious_online;
};
+#ifdef CONFIG_OF
+const char *get_dts_batt_id(struct device *dev)
+{
+ int lenp;
+ const char *retval = NULL;
+ struct device_node *n = of_find_node_by_path("/chosen");
+
+ if (n) {
+ retval = of_get_property(n, "batt-id", &lenp);
+ if (!retval || !lenp) {
+ dev_err(dev, "%s: batt-id len %d\n", __func__, lenp);
+ retval = NULL;
+ }
+ of_node_put(n);
+ }
+
+ return retval;
+}
+#endif
+
static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value)
{
int ret = i2c_smbus_write_word_data(client, reg, value);
@@ -117,24 +190,54 @@ static enum power_supply_property max17042_battery_props[] = {
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_STATUS
};
+/* input and output temperature is in deci-centigrade */
+static int max17042_conv_temp(struct max17042_temp_conv *conv, int t)
+{
+ int i; /* conversion table index */
+ s16 *r = conv->result;
+ int dt;
+
+ /*
+ * conv->result[0] corresponds to conv->start temp, conv->result[1] to
+ * conv->start + 1 temp, etc. Find index to the conv->result table for
+ * t to be between index and index + 1 temperatures.
+ */
+ i = t / 10 - conv->start; /* t is in 1/10th C, conv->start is in C */
+ if (t < 0)
+ i -= 1;
+
+ /* Interpolate linearly if index and index + 1 are within the table */
+ if (i < 0) {
+ return r[0];
+ } else if (i >= conv->num_result - 1) {
+ return r[conv->num_result - 1];
+ } else {
+ dt = t - (conv->start + i) * 10; /* in 1/10th C */
+ return r[i] + (r[i + 1] - r[i]) * dt / 10;
+ }
+}
+
static int max17042_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct max17042_chip *chip = container_of(psy,
struct max17042_chip, battery);
- int ret;
+ int ret = 0;
if (!chip->init_complete)
return -EAGAIN;
+ mutex_lock(&chip->lock);
+
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
ret = max17042_read_reg(chip->client, MAX17042_STATUS);
if (ret < 0)
- return ret;
+ goto err;
if (ret & MAX17042_STATUS_BattAbsent)
val->intval = 0;
@@ -144,14 +247,14 @@ static int max17042_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CYCLE_COUNT:
ret = max17042_read_reg(chip->client, MAX17042_Cycles);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
ret = max17042_read_reg(chip->client, MAX17042_MinMaxVolt);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret >> 8;
val->intval *= 20000; /* Units of LSB = 20mV */
@@ -162,7 +265,7 @@ static int max17042_get_property(struct power_supply *psy,
else
ret = max17042_read_reg(chip->client, MAX17047_V_empty);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret >> 7;
val->intval *= 10000; /* Units of LSB = 10mV */
@@ -170,49 +273,67 @@ static int max17042_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = max17042_read_reg(chip->client, MAX17042_VCELL);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret * 625 / 8;
break;
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret * 625 / 8;
break;
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
ret = max17042_read_reg(chip->client, MAX17042_OCVInternal);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret * 625 / 8;
break;
case POWER_SUPPLY_PROP_CAPACITY:
+#ifdef CONFIG_BATTERY_MAX17042_DEBUGFS
+ if (chip->debugfs_capacity != 0xFF) {
+ val->intval = chip->debugfs_capacity;
+ break;
+ }
+#endif
+ if (chip->pdata->batt_undervoltage_zero_soc &&
+ chip->batt_undervoltage) {
+ val->intval = 0;
+ break;
+ }
+
ret = max17042_read_reg(chip->client, MAX17042_RepSOC);
if (ret < 0)
- return ret;
+ goto err;
- val->intval = ret >> 8;
+ ret >>= 8;
+ if (ret == 0 &&
+ chip->pdata->batt_undervoltage_zero_soc &&
+ !chip->batt_undervoltage)
+ val->intval = 1;
+ else
+ val->intval = ret;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = max17042_read_reg(chip->client, MAX17042_FullCAP);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret * 1000 / 2;
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
ret = max17042_read_reg(chip->client, MAX17042_QH);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret * 1000 / 2;
break;
case POWER_SUPPLY_PROP_TEMP:
ret = max17042_read_reg(chip->client, MAX17042_TEMP);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret;
/* The value is signed. */
@@ -220,15 +341,21 @@ static int max17042_get_property(struct power_supply *psy,
val->intval = (0x7fff & ~val->intval) + 1;
val->intval *= -1;
}
+
/* The value is converted into deci-centigrade scale */
/* Units of LSB = 1 / 256 degree Celsius */
val->intval = val->intval * 10 / 256;
+
+ /* Convert IC temp to "real" temp */
+ if (chip->pdata->tcnv)
+ val->intval = max17042_conv_temp(chip->pdata->tcnv,
+ val->intval);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (chip->pdata->enable_current_sense) {
ret = max17042_read_reg(chip->client, MAX17042_Current);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret;
if (val->intval & 0x8000) {
@@ -239,7 +366,7 @@ static int max17042_get_property(struct power_supply *psy,
}
val->intval *= 1562500 / chip->pdata->r_sns;
} else {
- return -EINVAL;
+ ret = -EINVAL;
}
break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
@@ -247,7 +374,7 @@ static int max17042_get_property(struct power_supply *psy,
ret = max17042_read_reg(chip->client,
MAX17042_AvgCurrent);
if (ret < 0)
- return ret;
+ goto err;
val->intval = ret;
if (val->intval & 0x8000) {
@@ -258,13 +385,24 @@ static int max17042_get_property(struct power_supply *psy,
}
val->intval *= 1562500 / chip->pdata->r_sns;
} else {
- return -EINVAL;
+ ret = -EINVAL;
}
break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (power_supply_am_i_supplied(psy))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
default:
- return -EINVAL;
+ ret = -EINVAL;
}
- return 0;
+
+ if (ret > 0)
+ ret = 0;
+err:
+ mutex_unlock(&chip->lock);
+ return ret;
}
static int max17042_write_verify_reg(struct i2c_client *client,
@@ -395,6 +533,52 @@ static int max17042_verify_model_lock(struct max17042_chip *chip)
return ret;
}
+static int max17042_perform_soft_POR(struct max17042_chip *chip)
+{
+ int val;
+ int retries = 8;
+
+ do {
+ /* 1. Lock the model and clear the POR bit */
+ max10742_lock_model(chip);
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ max17042_write_reg(chip->client, MAX17042_STATUS,
+ val & (~STATUS_POR_BIT));
+ /* 2. Verify model lock MLOCK1 = MLOCK2 = STATUS = 0 */
+ if (!max17042_read_reg(chip->client, MAX17042_MLOCKReg1) &&
+ !max17042_read_reg(chip->client, MAX17042_MLOCKReg2) &&
+ !max17042_read_reg(chip->client, MAX17042_MLOCKReg2))
+ break;
+ } while (--retries);
+
+ if (!retries) {
+ dev_err(&chip->client->dev, "Unable to lock model\n");
+ return -EINVAL;
+ }
+
+ retries = 8;
+
+ do {
+ /* 3. Send SoftPOR */
+ max17042_write_reg(chip->client, MAX17042_VFSOC0Enable,
+ 0x000F);
+ /* 4. Wait atleast 2ms */
+ usleep_range(2000, 4000);
+ /* 5. Verify POR bit is set */
+ if (max17042_read_reg(chip->client, MAX17042_STATUS) &
+ STATUS_POR_BIT)
+ break;
+ } while (--retries);
+
+ if (!retries) {
+ dev_err(&chip->client->dev, "Unable to set POR bit\n");
+ return -EINVAL;
+ }
+ dev_dbg(&chip->client->dev, "Soft POR success\n");
+
+ return 0;
+}
+
static void max17042_write_config_regs(struct max17042_chip *chip)
{
struct max17042_config_data *config = chip->pdata->config_data;
@@ -442,8 +626,9 @@ static void max17042_update_capacity_regs(struct max17042_chip *chip)
max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
config->fullcap);
+ /* Set DesignCap to fullcapnom here */
max17042_write_reg(chip->client, MAX17042_DesignCap,
- config->design_cap);
+ config->fullcapnom);
max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
config->fullcapnom);
}
@@ -458,21 +643,31 @@ static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip)
max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK);
}
+static void max17042_advance_to_coulomb_counter_mode(struct max17042_chip *chip)
+{
+ u16 value = (chip->chip_type == MAX17042 ?
+ MAX17042_INIT_NUM_CYCLES : MAX17047_INIT_NUM_CYCLES);
+ max17042_write_verify_reg(chip->client, MAX17042_Cycles, value);
+}
+
static void max17042_load_new_capacity_params(struct max17042_chip *chip)
{
- u16 full_cap0, rep_cap, dq_acc, vfSoc;
+ u16 rep_cap, dq_acc, vfSoc;
u32 rem_cap;
+ u16 dQ_ACC_DIV = (chip->chip_type == MAX17042 ?
+ MAX17042_dQ_ACC_DIV : MAX17047_dQ_ACC_DIV);
+ u16 dP_ACC_200 = (chip->chip_type == MAX17042 ?
+ MAX17042_dP_ACC_200 : MAX17047_dP_ACC_200);
struct max17042_config_data *config = chip->pdata->config_data;
- full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0);
vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC);
- /* fg_vfSoc needs to shifted by 8 bits to get the
+ /* 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
+ * fullcapnom by vfSoc and devide by 100
*/
- rem_cap = ((vfSoc >> 8) * full_cap0) / 100;
+ rem_cap = ((vfSoc >> 8) * config->fullcapnom) / 100;
max17042_write_verify_reg(chip->client, MAX17042_RemCap, (u16)rem_cap);
rep_cap = (u16)rem_cap;
@@ -514,25 +709,14 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
config->soc_alrt_thresh);
max17042_override_por(client, MAX17042_CONFIG, config->config);
max17042_override_por(client, MAX17042_SHDNTIMER, config->shdntimer);
-
- max17042_override_por(client, MAX17042_DesignCap, config->design_cap);
- max17042_override_por(client, MAX17042_ICHGTerm, config->ichgt_term);
-
max17042_override_por(client, MAX17042_AtRate, config->at_rate);
- max17042_override_por(client, MAX17042_LearnCFG, config->learn_cfg);
- max17042_override_por(client, MAX17042_FilterCFG, config->filter_cfg);
- max17042_override_por(client, MAX17042_RelaxCFG, config->relax_cfg);
max17042_override_por(client, MAX17042_MiscCFG, config->misc_cfg);
max17042_override_por(client, MAX17042_MaskSOC, config->masksoc);
- max17042_override_por(client, MAX17042_FullCAP, config->fullcap);
- max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom);
if (chip->chip_type == MAX17042)
max17042_override_por(client, MAX17042_SOC_empty,
config->socempty);
max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty);
- max17042_override_por(client, MAX17042_dQacc, config->dqacc);
- max17042_override_por(client, MAX17042_dPacc, config->dpacc);
if (chip->chip_type == MAX17042)
max17042_override_por(client, MAX17042_V_empty, config->vempty);
@@ -541,9 +725,7 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(client, MAX17042_TempNom, config->temp_nom);
max17042_override_por(client, MAX17042_TempLim, config->temp_lim);
max17042_override_por(client, MAX17042_FCTC, config->fctc);
- max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0);
- max17042_override_por(client, MAX17042_TempCo, config->tcompc0);
- if (chip->chip_type) {
+ if (chip->chip_type == MAX17042) {
max17042_override_por(client, MAX17042_EmptyTempCo,
config->empty_tempco);
max17042_override_por(client, MAX17042_K_empty0,
@@ -593,6 +775,9 @@ static int max17042_init_chip(struct max17042_chip *chip)
/* reset vfsoc0 reg */
max17042_reset_vfsoc0_reg(chip);
+ /* advance to coulomb-counter mode */
+ max17042_advance_to_coulomb_counter_mode(chip);
+
/* load new capacity params */
max17042_load_new_capacity_params(chip);
@@ -606,13 +791,20 @@ static int max17042_init_chip(struct max17042_chip *chip)
static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
{
u16 soc, soc_tr;
-
- /* program interrupt thesholds such that we should
- * get interrupt for every 'off' perc change in the soc
+ /*
+ * Program interrupt thresholds to get interrupt for every 'off'
+ * percent change in the soc. Since we truncate soc value when
+ * reporting it, the reported SOC is equal to (min Salrt - 1) when soc
+ * falls below the min Salrt threshold and equal to max Salrt when soc
+ * exceeds the max Salrt threshold.
*/
+ u16 off_max = off;
+ u16 off_min = off - 1;
+
soc = max17042_read_reg(chip->client, MAX17042_RepSOC) >> 8;
- soc_tr = (soc + off) << 8;
- soc_tr |= (soc - off);
+ soc_tr = (soc + off_max) << 8;
+ if (soc >= off_min)
+ soc_tr |= (soc - off_min);
max17042_write_reg(chip->client, MAX17042_SALRT_Th, soc_tr);
}
@@ -622,12 +814,26 @@ static irqreturn_t max17042_thread_handler(int id, void *dev)
u16 val;
val = max17042_read_reg(chip->client, MAX17042_STATUS);
- if ((val & STATUS_INTR_SOCMIN_BIT) ||
- (val & STATUS_INTR_SOCMAX_BIT)) {
- dev_info(&chip->client->dev, "SOC threshold INTR\n");
- max17042_set_soc_threshold(chip, 1);
+
+ dev_dbg(&chip->client->dev, "status:0x%x soc_tr:0x%x\n",
+ val, chip->alert_threshold);
+
+ if ((val & STATUS_SMN_BIT) || (val & STATUS_SMX_BIT)) {
+ max17042_set_soc_threshold(chip, chip->alert_threshold);
+ /* clear the Smin Smax bits if set */
+ if (chip->pdata->config_data->config & CONFIG_SS_BIT_ENBL)
+ val &= ~STATUS_SMN_BIT & ~STATUS_SMX_BIT;
+ }
+
+ if (val & STATUS_VMN_BIT) {
+ dev_dbg(&chip->client->dev, "Battery undervoltage INTR\n");
+ chip->batt_undervoltage = true;
}
+ /* if sticky bits are used, clear them */
+ if (chip->pdata->config_data->config & CONFIG_STICK_ALL_ENBL)
+ max17042_write_reg(chip->client, MAX17042_STATUS, val);
+
power_supply_changed(&chip->battery);
return IRQ_HANDLED;
}
@@ -636,19 +842,308 @@ static void max17042_init_worker(struct work_struct *work)
{
struct max17042_chip *chip = container_of(work,
struct max17042_chip, work);
- int ret;
+ int ret = 0;
+
+ mutex_lock(&chip->lock);
/* 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;
+ if (!ret) {
+ chip->init_complete = 1;
+ if (chip->chip_type == MAX17047) {
+ max17042_write_reg(chip->client, MAX17047_Config_Ver,
+ chip->pdata->config_data->version);
+ }
+ }
+
+ mutex_unlock(&chip->lock);
+}
+
+static void max17042_malicious_removed_worker(struct work_struct *work)
+{
+ struct max17042_chip *chip = container_of(work,
+ struct max17042_chip, work_malicious_removed);
+ int ret;
+
+ mutex_lock(&chip->lock);
+
+ max17042_perform_soft_POR(chip);
+ ret = max17042_init_chip(chip);
+ if (!ret && chip->chip_type == MAX17047)
+ max17042_write_reg(chip->client, MAX17047_Config_Ver,
+ chip->pdata->config_data->version);
+
+ dev_info(&chip->client->dev, "malicious ps removed, chip re-inited\n");
+
+ mutex_unlock(&chip->lock);
+
+ power_supply_changed(&chip->battery);
}
#ifdef CONFIG_OF
+static struct gpio *
+max17042_get_gpio_list(struct device *dev, int *num_gpio_list)
+{
+ struct device_node *np = dev->of_node;
+ struct gpio *gpio_list;
+ int i, num_gpios, gpio_list_size;
+ enum of_gpio_flags flags;
+
+ if (!np)
+ return NULL;
+
+ num_gpios = of_gpio_count(np);
+ if (num_gpios <= 0)
+ return NULL;
+
+ gpio_list_size = sizeof(struct gpio) * num_gpios;
+ gpio_list = devm_kzalloc(dev, gpio_list_size, GFP_KERNEL);
+
+ if (!gpio_list)
+ return NULL;
+
+ *num_gpio_list = num_gpios;
+ for (i = 0; i < num_gpios; i++) {
+ gpio_list[i].gpio = of_get_gpio_flags(np, i, &flags);
+ gpio_list[i].flags = flags;
+ of_property_read_string_index(np, "gpio-names", i,
+ &gpio_list[i].label);
+ }
+
+ return gpio_list;
+}
+
+static struct max17042_reg_data *
+max17042_get_init_data(struct device *dev, int *num_init_data)
+{
+ struct device_node *np = dev->of_node;
+ const __be32 *property;
+ static struct max17042_reg_data *init_data;
+ int i, lenp, num_cells, init_data_size;
+
+ if (!np)
+ return NULL;
+
+ property = of_get_property(np, INIT_DATA_PROPERTY, &lenp);
+
+ if (!property || lenp <= 0)
+ return NULL;
+
+ /*
+ * Check data validity and whether number of cells is even
+ */
+ if (lenp % sizeof(*property)) {
+ dev_err(dev, "%s has invalid data\n", INIT_DATA_PROPERTY);
+ return NULL;
+ }
+
+ num_cells = lenp / sizeof(*property);
+ if (num_cells % 2) {
+ dev_err(dev, "%s must have even number of cells\n",
+ INIT_DATA_PROPERTY);
+ return NULL;
+ }
+
+ *num_init_data = num_cells / 2;
+ init_data_size = sizeof(struct max17042_reg_data) * (num_cells / 2);
+ init_data = (struct max17042_reg_data *)
+ devm_kzalloc(dev, init_data_size, GFP_KERNEL);
+
+ if (init_data) {
+ for (i = 0; i < num_cells / 2; i++) {
+ init_data[i].addr = be32_to_cpu(property[2 * i]);
+ init_data[i].data = be32_to_cpu(property[2 * i + 1]);
+ }
+ }
+
+ return init_data;
+}
+
+static int max17042_get_cell_char_tbl(struct device *dev,
+ struct device_node *np,
+ struct max17042_config_data *config_data)
+{
+ const __be16 *property;
+ int i, lenp;
+
+ property = of_get_property(np, CELL_CHAR_TBL_PROPERTY, &lenp);
+ if (!property)
+ return -ENODEV ;
+
+ if (lenp != sizeof(*property) * MAX17042_CHARACTERIZATION_DATA_SIZE) {
+ dev_err(dev, "%s must have %d cells\n", CELL_CHAR_TBL_PROPERTY,
+ MAX17042_CHARACTERIZATION_DATA_SIZE);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < MAX17042_CHARACTERIZATION_DATA_SIZE; i++)
+ config_data->cell_char_tbl[i] = be16_to_cpu(property[i]);
+
+ return 0;
+}
+
+static int max17042_cfg_rqrd_prop(struct device *dev,
+ struct device_node *np,
+ struct max17042_config_data *config_data)
+{
+ if (of_property_read_u16(np, VERSION_PROPERTY,
+ &config_data->version))
+ return -EINVAL;
+
+ if (of_property_read_u16(np, CONFIG_PROPERTY,
+ &config_data->config))
+ return -EINVAL;
+ if (of_property_read_u16(np, FILTER_CFG_PROPERTY,
+ &config_data->filter_cfg))
+ return -EINVAL;
+ if (of_property_read_u16(np, RELAX_CFG_PROPERTY,
+ &config_data->relax_cfg))
+ return -EINVAL;
+ if (of_property_read_u16(np, LEARN_CFG_PROPERTY,
+ &config_data->learn_cfg))
+ return -EINVAL;
+ if (of_property_read_u16(np, FULL_SOC_THRESH_PROPERTY,
+ &config_data->full_soc_thresh))
+ return -EINVAL;
+ if (of_property_read_u16(np, RCOMP0_PROPERTY,
+ &config_data->rcomp0))
+ return -EINVAL;
+ if (of_property_read_u16(np, TCOMPC0_PROPERTY,
+ &config_data->tcompc0))
+ return -EINVAL;
+ if (of_property_read_u16(np, ICHGT_TERM_PROPERTY,
+ &config_data->ichgt_term))
+ return -EINVAL;
+ if (of_property_read_u16(np, QRTBL00_PROPERTY,
+ &config_data->qrtbl00))
+ return -EINVAL;
+ if (of_property_read_u16(np, QRTBL10_PROPERTY,
+ &config_data->qrtbl10))
+ return -EINVAL;
+ if (of_property_read_u16(np, QRTBL20_PROPERTY,
+ &config_data->qrtbl20))
+ return -EINVAL;
+ if (of_property_read_u16(np, QRTBL30_PROPERTY,
+ &config_data->qrtbl30))
+ return -EINVAL;
+ if (of_property_read_u16(np, FULLCAP_PROPERTY,
+ &config_data->fullcap))
+ return -EINVAL;
+ if (of_property_read_u16(np, DESIGN_CAP_PROPERTY,
+ &config_data->design_cap))
+ return -EINVAL;
+ if (of_property_read_u16(np, FULLCAPNOM_PROPERTY,
+ &config_data->fullcapnom))
+ return -EINVAL;
+
+ return max17042_get_cell_char_tbl(dev, np, config_data);
+}
+
+static void max17042_cfg_optnl_prop(struct device_node *np,
+ struct max17042_config_data *config_data)
+{
+ of_property_read_u16(np, TGAIN_PROPERTY, &config_data->tgain);
+ of_property_read_u16(np, TOFF_PROPERTY, &config_data->toff);
+}
+
+static struct max17042_config_data *
+max17042_get_config_data(struct device *dev)
+{
+ char *config_node = NULL;
+ char config_node_path[64];
+ struct max17042_config_data *config_data;
+ struct device_node *np = dev->of_node;
+
+ if (!np)
+ return NULL;
+
+ config_node = (char *)get_dts_batt_id(dev);
+ if (config_node) {
+ snprintf(config_node_path, sizeof(config_node_path),
+ "%s-%s", CONFIG_NODE, config_node);
+ config_node = config_node_path;
+ } else {
+ config_node = CONFIG_NODE;
+ }
+
+ dev_info(dev, "using %s profile\n", config_node);
+
+ np = of_get_child_by_name(np, config_node);
+ if (!np)
+ return NULL;
+
+ config_data = devm_kzalloc(dev, sizeof(*config_data), GFP_KERNEL);
+ if (!config_data)
+ return NULL;
+
+ if (max17042_cfg_rqrd_prop(dev, np, config_data)) {
+ devm_kfree(dev, config_data);
+ return NULL;
+ }
+
+ max17042_cfg_optnl_prop(np, config_data);
+
+ return config_data;
+}
+
+static struct max17042_temp_conv *
+max17042_get_conv_table(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct max17042_temp_conv *temp_conv;
+ const __be16 *property;
+ int i, lenp, num;
+ u16 temp;
+ s16 start;
+
+ if (!np)
+ return NULL;
+
+ np = of_get_child_by_name(np, TEMP_CONV_NODE);
+ if (!np)
+ return NULL;
+
+ property = of_get_property(np, RESULT_PROPERTY, &lenp);
+ if (!property || lenp <= 0) {
+ dev_err(dev, "%s must have %s property\n", TEMP_CONV_NODE,
+ RESULT_PROPERTY);
+ return NULL;
+ }
+
+ if (of_property_read_u16(np, START_PROPERTY, &temp)) {
+ dev_err(dev, "%s must have %s property\n", TEMP_CONV_NODE,
+ START_PROPERTY);
+ return NULL;
+ }
+
+ start = (s16) temp;
+
+ temp_conv = devm_kzalloc(dev, sizeof(*temp_conv), GFP_KERNEL);
+ if (!temp_conv)
+ return NULL;
+
+ num = lenp / sizeof(*property);
+ temp_conv->result = devm_kzalloc(dev, sizeof(s16) * num, GFP_KERNEL);
+ if (!temp_conv->result) {
+ devm_kfree(dev, temp_conv);
+ return NULL;
+ }
+
+ temp_conv->start = start;
+ temp_conv->num_result = num;
+
+ for (i = 0; i < num; i++) {
+ temp = be16_to_cpu(property[i]);
+ temp_conv->result[i] = (s16) temp;
+ }
+
+ return temp_conv;
+}
+
+
static struct max17042_platform_data *
max17042_get_pdata(struct device *dev)
{
@@ -663,6 +1158,9 @@ max17042_get_pdata(struct device *dev)
if (!pdata)
return NULL;
+ pdata->init_data = max17042_get_init_data(dev, &pdata->num_init_data);
+ pdata->gpio_list = max17042_get_gpio_list(dev, &pdata->num_gpio_list);
+
/*
* Require current sense resistor value to be specified for
* current-sense functionality to be enabled at all.
@@ -672,6 +1170,21 @@ max17042_get_pdata(struct device *dev)
pdata->enable_current_sense = true;
}
+ pdata->enable_por_init =
+ of_property_read_bool(np, "maxim,enable_por_init");
+
+ pdata->batt_undervoltage_zero_soc =
+ of_property_read_bool(np, "maxim,batt_undervoltage_zero_soc");
+
+ pdata->config_data = max17042_get_config_data(dev);
+ if (!pdata->config_data)
+ dev_warn(dev, "config data is missing\n");
+
+ pdata->tcnv = max17042_get_conv_table(dev);
+
+ of_property_read_string(np, "maxim,malicious_supply",
+ &pdata->malicious_supply);
+
return pdata;
}
#else
@@ -682,6 +1195,176 @@ max17042_get_pdata(struct device *dev)
}
#endif
+#ifdef CONFIG_BATTERY_MAX17042_DEBUGFS
+static int max17042_debugfs_read_addr(void *data, u64 *val)
+{
+ struct max17042_chip *chip = (struct max17042_chip *)data;
+ *val = chip->debugfs_addr;
+ return 0;
+}
+
+static int max17042_debugfs_write_addr(void *data, u64 val)
+{
+ struct max17042_chip *chip = (struct max17042_chip *)data;
+ chip->debugfs_addr = val;
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(addr_fops, max17042_debugfs_read_addr,
+ max17042_debugfs_write_addr, "0x%02llx\n");
+
+static int max17042_debugfs_read_data(void *data, u64 *val)
+{
+ struct max17042_chip *chip = (struct max17042_chip *)data;
+ int ret = max17042_read_reg(chip->client, chip->debugfs_addr);
+
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+}
+
+static int max17042_debugfs_write_data(void *data, u64 val)
+{
+ struct max17042_chip *chip = (struct max17042_chip *)data;
+ return max17042_write_reg(chip->client, chip->debugfs_addr, val);
+}
+DEFINE_SIMPLE_ATTRIBUTE(data_fops, max17042_debugfs_read_data,
+ max17042_debugfs_write_data, "0x%02llx\n");
+
+static int max17042_debugfs_read_capacity(void *data, u64 *val)
+{
+ struct max17042_chip *chip = (struct max17042_chip *)data;
+ *val = chip->debugfs_capacity;
+ return 0;
+}
+
+static int max17042_debugfs_write_capacity(void *data, u64 val)
+{
+ struct max17042_chip *chip = (struct max17042_chip *)data;
+ chip->debugfs_capacity = val;
+ power_supply_changed(&chip->battery);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(capacity_fops, max17042_debugfs_read_capacity,
+ max17042_debugfs_write_capacity, "%llu\n");
+
+static int max17042_debugfs_create(struct max17042_chip *chip)
+{
+ chip->debugfs_root = debugfs_create_dir(dev_name(&chip->client->dev),
+ NULL);
+ if (!chip->debugfs_root)
+ return -ENOMEM;
+
+ if (!debugfs_create_file("addr", S_IRUGO | S_IWUSR, chip->debugfs_root,
+ chip, &addr_fops))
+ goto err_debugfs;
+
+ if (!debugfs_create_file("data", S_IRUGO | S_IWUSR, chip->debugfs_root,
+ chip, &data_fops))
+ goto err_debugfs;
+
+ chip->debugfs_capacity = 0xFF;
+ if (!debugfs_create_file("capacity", S_IRUGO | S_IWUSR,
+ chip->debugfs_root, chip, &capacity_fops))
+ goto err_debugfs;
+
+ return 0;
+
+err_debugfs:
+ debugfs_remove_recursive(chip->debugfs_root);
+ chip->debugfs_root = NULL;
+ return -ENOMEM;
+}
+#endif
+
+static void max17042_external_power_changed(struct power_supply *psy)
+{
+ struct max17042_chip *chip;
+ union power_supply_propval val;
+
+ chip = container_of(psy, struct max17042_chip, battery);
+
+ if (!chip->psy_malicious)
+ return;
+
+ chip->psy_malicious->get_property(chip->psy_malicious,
+ POWER_SUPPLY_PROP_ONLINE, &val);
+
+ if (chip->malicious_online != val.intval) {
+ chip->malicious_online = val.intval;
+ if (!chip->malicious_online) {
+ dev_dbg(&chip->client->dev, "malicious ps removed\n");
+ schedule_work(&chip->work_malicious_removed);
+ }
+ }
+}
+
+static ssize_t max17042_show_alert_threshold(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct max17042_chip *chip = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", chip->alert_threshold);
+}
+
+static ssize_t max17042_store_alert_threshold(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct max17042_chip *chip = dev_get_drvdata(dev);
+ unsigned long t;
+ u16 r;
+
+ r = kstrtoul(buf, 10, &t);
+ if ((!r) && ( r < 100 )) {
+ chip->alert_threshold = (u16)t;
+ max17042_set_soc_threshold(chip, chip->alert_threshold);
+ }
+
+ return r ? r : count;
+}
+
+static DEVICE_ATTR(alert_threshold, S_IRUGO | S_IWUSR,
+ max17042_show_alert_threshold, max17042_store_alert_threshold);
+
+
+static ssize_t max17042_show_battery_age(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct max17042_chip *chip = dev_get_drvdata(dev);
+ int ret = max17042_read_reg(chip->client, MAX17042_Age);
+
+ return ret < 0 ? ret : sprintf(buf, "%u\n", ret / MAX17042_AGE_DIV);
+}
+static DEVICE_ATTR(battery_age, S_IRUGO, max17042_show_battery_age, NULL);
+
+static struct attribute *max17042_attrs[] = {
+ &dev_attr_alert_threshold.attr,
+ &dev_attr_battery_age.attr,
+ NULL,
+};
+
+static struct attribute_group max17042_attr_group = {
+ .attrs = max17042_attrs,
+};
+
+static bool max17042_new_config_data(struct max17042_chip *chip)
+{
+ int ret;
+
+ if (chip->chip_type == MAX17042)
+ return false;
+
+ ret = max17042_read_reg(chip->client, MAX17047_Config_Ver);
+ if (ret < 0)
+ return false;
+
+ return (chip->pdata->config_data->version != ret);
+}
+
static int max17042_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -689,6 +1372,7 @@ static int max17042_probe(struct i2c_client *client,
struct max17042_chip *chip;
int ret;
int reg;
+ union power_supply_propval val;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
return -EIO;
@@ -706,6 +1390,16 @@ static int max17042_probe(struct i2c_client *client,
i2c_set_clientdata(client, chip);
+ if (chip->pdata->malicious_supply) {
+ chip->psy_malicious = power_supply_get_by_name(
+ chip->pdata->malicious_supply);
+ if (!chip->psy_malicious) {
+ dev_warn(&client->dev,
+ "malicious ps not found, deferring.\n");
+ return -EPROBE_DEFER;
+ }
+ }
+
ret = max17042_read_reg(chip->client, MAX17042_DevName);
if (ret == MAX17042_IC_VERSION) {
dev_dbg(&client->dev, "chip type max17042 detected\n");
@@ -723,6 +1417,9 @@ static int max17042_probe(struct i2c_client *client,
chip->battery.get_property = max17042_get_property;
chip->battery.properties = max17042_battery_props;
chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props);
+ chip->battery.external_power_changed = max17042_external_power_changed;
+
+ chip->alert_threshold = DEFAULT_ALERT_THRESHOLD;
/* When current is not measured,
* CURRENT_NOW and CURRENT_AVG properties should be invisible. */
@@ -748,16 +1445,37 @@ static int max17042_probe(struct i2c_client *client,
return ret;
}
+ if (chip->psy_malicious) {
+ INIT_WORK(&chip->work_malicious_removed,
+ max17042_malicious_removed_worker);
+ chip->psy_malicious->get_property(chip->psy_malicious,
+ POWER_SUPPLY_PROP_ONLINE,
+ &val);
+ chip->malicious_online = val.intval;
+ dev_dbg(&client->dev, "malicious_online = %d\n",
+ chip->malicious_online);
+ }
+
+ ret = gpio_request_array(chip->pdata->gpio_list,
+ chip->pdata->num_gpio_list);
+ if (ret) {
+ dev_err(&client->dev, "cannot request GPIOs\n");
+ return ret;
+ }
+
+ mutex_init(&chip->lock);
+
if (client->irq) {
ret = request_threaded_irq(client->irq, NULL,
max17042_thread_handler,
- IRQF_TRIGGER_FALLING,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
chip->battery.name, chip);
if (!ret) {
reg = max17042_read_reg(client, MAX17042_CONFIG);
reg |= CONFIG_ALRT_BIT_ENBL;
max17042_write_reg(client, MAX17042_CONFIG, reg);
- max17042_set_soc_threshold(chip, 1);
+ max17042_set_soc_threshold(chip, chip->alert_threshold);
} else {
client->irq = 0;
dev_err(&client->dev, "%s(): cannot get IRQ\n",
@@ -766,13 +1484,25 @@ static int max17042_probe(struct i2c_client *client,
}
reg = max17042_read_reg(chip->client, MAX17042_STATUS);
- if (reg & STATUS_POR_BIT) {
+ if (reg & STATUS_POR_BIT || max17042_new_config_data(chip)) {
INIT_WORK(&chip->work, max17042_init_worker);
schedule_work(&chip->work);
} else {
chip->init_complete = 1;
}
+#ifdef CONFIG_BATTERY_MAX17042_DEBUGFS
+ ret = max17042_debugfs_create(chip);
+ if (ret) {
+ dev_err(&client->dev, "cannot create debugfs\n");
+ return ret;
+ }
+#endif
+
+ ret = sysfs_create_group(&client->dev.kobj, &max17042_attr_group);
+ if (ret)
+ dev_err(&client->dev, "failed to create sysfs files\n");
+
return 0;
}
@@ -780,8 +1510,16 @@ static int max17042_remove(struct i2c_client *client)
{
struct max17042_chip *chip = i2c_get_clientdata(client);
+#ifdef CONFIG_BATTERY_MAX17042_DEBUGFS
+ debugfs_remove_recursive(chip->debugfs_root);
+#endif
+
+ sysfs_remove_group(&client->dev.kobj, &max17042_attr_group);
+
if (client->irq)
free_irq(client->irq, chip);
+ mutex_destroy(&chip->lock);
+ gpio_free_array(chip->pdata->gpio_list, chip->pdata->num_gpio_list);
power_supply_unregister(&chip->battery);
return 0;
}
@@ -811,7 +1549,7 @@ static int max17042_resume(struct device *dev)
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);
+ max17042_set_soc_threshold(chip, chip->alert_threshold);
}
return 0;