| /* |
| * 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 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| * This driver is based on max17040_battery.c |
| */ |
| |
| #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/of_gpio.h> |
| #include <linux/types.h> |
| #include <linux/fs.h> |
| #include <linux/debugfs.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 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 MODEL_UNLOCK1 0X0059 |
| #define MODEL_UNLOCK2 0X00C4 |
| #define MODEL_LOCK1 0X0000 |
| #define MODEL_LOCK2 0X0000 |
| |
| #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); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static int max17042_read_reg(struct i2c_client *client, u8 reg) |
| { |
| int ret = i2c_smbus_read_word_data(client, reg); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static void max17042_set_reg(struct i2c_client *client, |
| struct max17042_reg_data *data, int size) |
| { |
| int i; |
| |
| for (i = 0; i < size; i++) |
| max17042_write_reg(client, data[i].addr, data[i].data); |
| } |
| |
| static enum power_supply_property max17042_battery_props[] = { |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_CYCLE_COUNT, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX, |
| 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, |
| POWER_SUPPLY_PROP_CHARGE_COUNTER, |
| 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 = 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) |
| goto err; |
| |
| if (ret & MAX17042_STATUS_BattAbsent) |
| val->intval = 0; |
| else |
| val->intval = 1; |
| break; |
| case POWER_SUPPLY_PROP_CYCLE_COUNT: |
| ret = max17042_read_reg(chip->client, MAX17042_Cycles); |
| if (ret < 0) |
| goto err; |
| |
| val->intval = ret; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| ret = max17042_read_reg(chip->client, MAX17042_MinMaxVolt); |
| if (ret < 0) |
| goto err; |
| |
| val->intval = ret >> 8; |
| val->intval *= 20000; /* Units of LSB = 20mV */ |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
| if (chip->chip_type == MAX17042) |
| ret = max17042_read_reg(chip->client, MAX17042_V_empty); |
| else |
| ret = max17042_read_reg(chip->client, MAX17047_V_empty); |
| if (ret < 0) |
| goto err; |
| |
| val->intval = ret >> 7; |
| val->intval *= 10000; /* Units of LSB = 10mV */ |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| ret = max17042_read_reg(chip->client, MAX17042_VCELL); |
| if (ret < 0) |
| 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) |
| 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) |
| 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) |
| goto err; |
| |
| 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) |
| 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) |
| goto err; |
| |
| val->intval = ret * 1000 / 2; |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| ret = max17042_read_reg(chip->client, MAX17042_TEMP); |
| if (ret < 0) |
| goto err; |
| |
| val->intval = ret; |
| /* The value is signed. */ |
| if (val->intval & 0x8000) { |
| 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) |
| goto err; |
| |
| val->intval = ret; |
| if (val->intval & 0x8000) { |
| /* Negative */ |
| val->intval = ~val->intval & 0x7fff; |
| val->intval++; |
| val->intval *= -1; |
| } |
| val->intval *= 1562500 / chip->pdata->r_sns; |
| } else { |
| ret = -EINVAL; |
| } |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_AVG: |
| if (chip->pdata->enable_current_sense) { |
| ret = max17042_read_reg(chip->client, |
| MAX17042_AvgCurrent); |
| if (ret < 0) |
| goto err; |
| |
| val->intval = ret; |
| if (val->intval & 0x8000) { |
| /* Negative */ |
| val->intval = ~val->intval & 0x7fff; |
| val->intval++; |
| val->intval *= -1; |
| } |
| val->intval *= 1562500 / chip->pdata->r_sns; |
| } else { |
| 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: |
| ret = -EINVAL; |
| } |
| |
| if (ret > 0) |
| ret = 0; |
| err: |
| mutex_unlock(&chip->lock); |
| return ret; |
| } |
| |
| static int max17042_write_verify_reg(struct i2c_client *client, |
| u8 reg, u16 value) |
| { |
| int retries = 8; |
| int ret; |
| u16 read_value; |
| |
| do { |
| ret = i2c_smbus_write_word_data(client, reg, value); |
| read_value = max17042_read_reg(client, reg); |
| if (read_value != value) { |
| ret = -EIO; |
| retries--; |
| } |
| } while (retries && read_value != value); |
| |
| if (ret < 0) |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| |
| return ret; |
| } |
| |
| static inline void max17042_override_por( |
| struct i2c_client *client, u8 reg, u16 value) |
| { |
| if (value) |
| max17042_write_reg(client, reg, value); |
| } |
| |
| static inline void max10742_unlock_model(struct max17042_chip *chip) |
| { |
| struct i2c_client *client = chip->client; |
| max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_UNLOCK1); |
| max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_UNLOCK2); |
| } |
| |
| static inline void max10742_lock_model(struct max17042_chip *chip) |
| { |
| struct i2c_client *client = chip->client; |
| max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_LOCK1); |
| max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_LOCK2); |
| } |
| |
| static inline void max17042_write_model_data(struct max17042_chip *chip, |
| u8 addr, int size) |
| { |
| struct i2c_client *client = chip->client; |
| int i; |
| for (i = 0; i < size; i++) |
| max17042_write_reg(client, 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 i2c_client *client = chip->client; |
| int i; |
| |
| for (i = 0; i < size; i++) |
| data[i] = max17042_read_reg(client, addr + i); |
| } |
| |
| 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; |
| |
| max10742_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); |
| |
| max10742_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 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; |
| |
| max17042_write_reg(chip->client, MAX17042_CONFIG, config->config); |
| max17042_write_reg(chip->client, MAX17042_LearnCFG, config->learn_cfg); |
| max17042_write_reg(chip->client, MAX17042_FilterCFG, |
| config->filter_cfg); |
| max17042_write_reg(chip->client, MAX17042_RelaxCFG, config->relax_cfg); |
| if (chip->chip_type == MAX17047) |
| max17042_write_reg(chip->client, 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; |
| |
| max17042_write_verify_reg(chip->client, MAX17042_RCOMP0, |
| config->rcomp0); |
| max17042_write_verify_reg(chip->client, MAX17042_TempCo, |
| config->tcompc0); |
| max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm, |
| config->ichgt_term); |
| if (chip->chip_type == MAX17042) { |
| max17042_write_reg(chip->client, MAX17042_EmptyTempCo, |
| config->empty_tempco); |
| max17042_write_verify_reg(chip->client, MAX17042_K_empty0, |
| config->kempty0); |
| } else { |
| max17042_write_verify_reg(chip->client, MAX17047_QRTbl00, |
| config->qrtbl00); |
| max17042_write_verify_reg(chip->client, MAX17047_QRTbl10, |
| config->qrtbl10); |
| max17042_write_verify_reg(chip->client, MAX17047_QRTbl20, |
| config->qrtbl20); |
| max17042_write_verify_reg(chip->client, MAX17047_QRTbl30, |
| config->qrtbl30); |
| } |
| } |
| |
| static void max17042_update_capacity_regs(struct max17042_chip *chip) |
| { |
| struct max17042_config_data *config = chip->pdata->config_data; |
| |
| max17042_write_verify_reg(chip->client, MAX17042_FullCAP, |
| config->fullcap); |
| /* Set DesignCap to fullcapnom here */ |
| max17042_write_reg(chip->client, MAX17042_DesignCap, |
| config->fullcapnom); |
| max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, |
| config->fullcapnom); |
| } |
| |
| static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip) |
| { |
| u16 vfSoc; |
| |
| vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC); |
| max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK); |
| max17042_write_verify_reg(chip->client, MAX17042_VFSOC0, vfSoc); |
| 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 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; |
| |
| vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC); |
| |
| /* vfSoc needs to shifted by 8 bits to get the |
| * perc in 1% accuracy, to get the right rem_cap multiply |
| * fullcapnom by vfSoc and devide by 100 |
| */ |
| rem_cap = ((vfSoc >> 8) * config->fullcapnom) / 100; |
| max17042_write_verify_reg(chip->client, MAX17042_RemCap, (u16)rem_cap); |
| |
| rep_cap = (u16)rem_cap; |
| max17042_write_verify_reg(chip->client, 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(chip->client, MAX17042_dQacc, dq_acc); |
| max17042_write_verify_reg(chip->client, MAX17042_dPacc, dP_ACC_200); |
| |
| max17042_write_verify_reg(chip->client, MAX17042_FullCAP, |
| config->fullcap); |
| max17042_write_reg(chip->client, MAX17042_DesignCap, |
| config->design_cap); |
| max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, |
| config->fullcapnom); |
| /* Update SOC register with new SOC */ |
| max17042_write_reg(chip->client, MAX17042_RepSOC, vfSoc); |
| } |
| |
| /* |
| * Block write all the override values coming from platform data. |
| * This function MUST be called before the POR initialization proceedure |
| * specified by maxim. |
| */ |
| static inline void max17042_override_por_values(struct max17042_chip *chip) |
| { |
| struct i2c_client *client = chip->client; |
| struct max17042_config_data *config = chip->pdata->config_data; |
| |
| max17042_override_por(client, MAX17042_TGAIN, config->tgain); |
| max17042_override_por(client, MAx17042_TOFF, config->toff); |
| max17042_override_por(client, MAX17042_CGAIN, config->cgain); |
| max17042_override_por(client, MAX17042_COFF, config->coff); |
| |
| max17042_override_por(client, MAX17042_VALRT_Th, config->valrt_thresh); |
| max17042_override_por(client, MAX17042_TALRT_Th, config->talrt_thresh); |
| max17042_override_por(client, MAX17042_SALRT_Th, |
| 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_AtRate, config->at_rate); |
| max17042_override_por(client, MAX17042_MiscCFG, config->misc_cfg); |
| max17042_override_por(client, MAX17042_MaskSOC, config->masksoc); |
| |
| if (chip->chip_type == MAX17042) |
| max17042_override_por(client, MAX17042_SOC_empty, |
| config->socempty); |
| max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty); |
| |
| if (chip->chip_type == MAX17042) |
| max17042_override_por(client, MAX17042_V_empty, config->vempty); |
| else |
| max17042_override_por(client, MAX17047_V_empty, config->vempty); |
| 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); |
| if (chip->chip_type == MAX17042) { |
| max17042_override_por(client, MAX17042_EmptyTempCo, |
| config->empty_tempco); |
| max17042_override_por(client, MAX17042_K_empty0, |
| config->kempty0); |
| } |
| } |
| |
| static int max17042_init_chip(struct max17042_chip *chip) |
| { |
| int ret; |
| int val; |
| |
| 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 configaration */ |
| 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); |
| |
| /* advance to coulomb-counter mode */ |
| max17042_advance_to_coulomb_counter_mode(chip); |
| |
| /* load new capacity params */ |
| max17042_load_new_capacity_params(chip); |
| |
| /* Init complete, Clear the POR bit */ |
| val = max17042_read_reg(chip->client, MAX17042_STATUS); |
| max17042_write_reg(chip->client, MAX17042_STATUS, |
| val & (~STATUS_POR_BIT)); |
| return 0; |
| } |
| |
| static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) |
| { |
| u16 soc, soc_tr; |
| /* |
| * 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_max) << 8; |
| if (soc >= off_min) |
| soc_tr |= (soc - off_min); |
| max17042_write_reg(chip->client, MAX17042_SALRT_Th, soc_tr); |
| } |
| |
| static irqreturn_t max17042_thread_handler(int id, void *dev) |
| { |
| struct max17042_chip *chip = dev; |
| u16 val; |
| |
| val = max17042_read_reg(chip->client, MAX17042_STATUS); |
| |
| 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; |
| } |
| |
| static void max17042_init_worker(struct work_struct *work) |
| { |
| struct max17042_chip *chip = container_of(work, |
| struct max17042_chip, work); |
| 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) { |
| 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) |
| { |
| struct device_node *np = dev->of_node; |
| u32 prop; |
| struct max17042_platform_data *pdata; |
| |
| if (!np) |
| return dev->platform_data; |
| |
| pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); |
| 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. |
| */ |
| if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) { |
| pdata->r_sns = prop; |
| 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 |
| static struct max17042_platform_data * |
| max17042_get_pdata(struct device *dev) |
| { |
| return dev->platform_data; |
| } |
| #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) |
| { |
| struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); |
| 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; |
| |
| chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); |
| if (!chip) |
| return -ENOMEM; |
| |
| chip->client = client; |
| chip->pdata = max17042_get_pdata(&client->dev); |
| if (!chip->pdata) { |
| dev_err(&client->dev, "no platform data provided\n"); |
| return -EINVAL; |
| } |
| |
| 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"); |
| chip->chip_type = MAX17042; |
| } else if (ret == MAX17047_IC_VERSION) { |
| dev_dbg(&client->dev, "chip type max17047/50 detected\n"); |
| chip->chip_type = MAX17047; |
| } else { |
| dev_err(&client->dev, "device version mismatch: %x\n", ret); |
| return -EIO; |
| } |
| |
| chip->battery.name = "max170xx_battery"; |
| chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; |
| 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. */ |
| if (!chip->pdata->enable_current_sense) |
| chip->battery.num_properties -= 2; |
| |
| if (chip->pdata->r_sns == 0) |
| chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR; |
| |
| if (chip->pdata->init_data) |
| max17042_set_reg(client, chip->pdata->init_data, |
| chip->pdata->num_init_data); |
| |
| if (!chip->pdata->enable_current_sense) { |
| max17042_write_reg(client, MAX17042_CGAIN, 0x0000); |
| max17042_write_reg(client, MAX17042_MiscCFG, 0x0003); |
| max17042_write_reg(client, MAX17042_LearnCFG, 0x0007); |
| } |
| |
| ret = power_supply_register(&client->dev, &chip->battery); |
| if (ret) { |
| dev_err(&client->dev, "failed: power supply register\n"); |
| 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_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, chip->alert_threshold); |
| } else { |
| client->irq = 0; |
| dev_err(&client->dev, "%s(): cannot get IRQ\n", |
| __func__); |
| } |
| } |
| |
| reg = max17042_read_reg(chip->client, MAX17042_STATUS); |
| 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; |
| } |
| |
| 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; |
| } |
| |
| #ifdef CONFIG_PM |
| 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, chip->alert_threshold); |
| } |
| |
| return 0; |
| } |
| |
| static const struct dev_pm_ops max17042_pm_ops = { |
| .suspend = max17042_suspend, |
| .resume = max17042_resume, |
| }; |
| |
| #define MAX17042_PM_OPS (&max17042_pm_ops) |
| #else |
| #define MAX17042_PM_OPS NULL |
| #endif |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id max17042_dt_match[] = { |
| { .compatible = "maxim,max17042" }, |
| { .compatible = "maxim,max17047" }, |
| { .compatible = "maxim,max17050" }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, max17042_dt_match); |
| #endif |
| |
| static const struct i2c_device_id max17042_id[] = { |
| { "max17042", 0 }, |
| { "max17047", 1 }, |
| { "max17050", 2 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, max17042_id); |
| |
| static struct i2c_driver max17042_i2c_driver = { |
| .driver = { |
| .name = "max17042", |
| .of_match_table = of_match_ptr(max17042_dt_match), |
| .pm = MAX17042_PM_OPS, |
| }, |
| .probe = max17042_probe, |
| .remove = max17042_remove, |
| .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"); |