| /* |
| * max17042_battery.c - 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/module.h> |
| #include <linux/init.h> |
| #include <linux/err.h> |
| #include <linux/interrupt.h> |
| #include <linux/workqueue.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/power_supply.h> |
| #include <linux/power/max17042_battery.h> |
| #include <linux/reboot.h> |
| #include <linux/delay.h> |
| #include <linux/notifier.h> |
| #include <linux/miscdevice.h> |
| #include <linux/atomic.h> |
| #include <linux/acpi.h> |
| #include <linux/acpi_gpio.h> |
| |
| /* Status register bits */ |
| #define STATUS_MASK 0xFF0A |
| #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) |
| |
| #define MAX17042_IC_VERSION 0x0092 |
| #define MAX17050_IC_VERSION 0x00AC |
| |
| /* Vmax disabled, Vmin disabled */ |
| #define VOLT_DEF_MAX_MIN_THRLD 0xFF00 |
| |
| /* Vmax disabled, Vmin set to 3300mV */ |
| #define VOLT_MIN_THRLD_ENBL 0xFFA5 |
| |
| /* Tmax disabled, Tmin disabled */ |
| #define TEMP_DEF_MAX_MIN_THRLD 0x7F80 |
| |
| /* SoCmax disabled, SoCmin can be set to 15%, 10%, 5% and 1%. |
| * INT will trigger when the thresholds are voilated. |
| */ |
| #define SOC_DEF_MAX_MIN1_THRLD 0xFF0F |
| #define SOC_DEF_MAX_MIN2_THRLD 0xFF0A |
| #define SOC_DEF_MAX_MIN3_THRLD 0xFF05 |
| #define SOC_DEF_MAX_MIN4_THRLD 0xFF01 |
| |
| /* SOC threshold for 1% interrupt */ |
| #define SOC_INTR_S0_THR 1 |
| |
| #define MISCCFG_CONFIG_REPSOC 0x0000 |
| #define MISCCFG_CONFIG_VFSOC 0x0003 |
| |
| /* low battery notification warning level */ |
| #define SOC_WARNING_LEVEL1 15 |
| #define SOC_WARNING_LEVEL2 10 |
| #define SOC_WARNING_LEVEL3 5 |
| #define SOC_SHUTDOWN_LEVEL 1 |
| |
| #define CONFIG_BER_BIT_ENBL (1 << 0) |
| #define CONFIG_BEI_BIT_ENBL (1 << 1) |
| #define CONFIG_ALRT_BIT_ENBL (1 << 2) |
| #define CONFIG_I2CSH_BIT_ENBL (1 << 6) |
| #define CONFIG_VSTICKY_BIT_SET (1 << 12) |
| #define CONFIG_TSTICKY_BIT_SET (1 << 13) |
| #define CONFIG_SSTICKY_BIT_SET (1 << 14) |
| #define CONFIG_ALP_BIT_ENBL (1 << 11) |
| #define CONFIG_TEX_BIT_ENBL (1 << 8) |
| |
| #define VFSOC0_LOCK 0x0000 |
| #define VFSOC0_UNLOCK 0x0080 |
| #define FG_MODEL_UNLOCK1 0X0059 |
| #define FG_MODEL_UNLOCK2 0X00C4 |
| #define FG_MODEL_LOCK1 0X0000 |
| #define FG_MODEL_LOCK2 0X0000 |
| |
| #define dQ_ACC_DIV_MAX17042 0x4 |
| #define dQ_ACC_DIV_MAX17050 0x10 |
| #define dP_ACC_100 0x1900 |
| #define dP_ACC_200 0x3200 |
| #define dP_ACC_MAX17050 0x0c80 |
| |
| #define NTC_47K_TGAIN 0xE4E4 |
| #define NTC_47K_TOFF 0x2F1D |
| |
| #define BATT_CHRG_FULL_DES 1550000 |
| #define MAX17042_VOLT_CONV_FCTR 625 |
| #define MAX17042_CURR_CONV_FCTR 156 |
| #define MAX17042_CHRG_CONV_FCTR 500 |
| |
| #define MAX17042_TEMP_SIGN_MASK 0x8000 |
| #define MAX17042_TEMP_CONV_FACTOR(a) ((a * 39) / 1000) |
| |
| #define MAX17042_MAX_MEM (0xFF + 1) |
| |
| #define MAX17042_MODEL_MUL_FACTOR(a, b) ((a * 100) / b) |
| #define MAX17042_MODEL_DIV_FACTOR(a, b) ((a * b) / 100) |
| |
| #define MAX17042_SHUTDOWN_TIMEOUT_45S 0x0 |
| #define MAX17042_SHUTDOWN_TIMEOUT_DEF 0xE000 |
| |
| #define CONSTANT_TEMP_IN_POWER_SUPPLY 350 |
| #define POWER_SUPPLY_VOLT_MIN_THRESHOLD 3500000 |
| #define BATTERY_VOLT_MIN_THRESHOLD 3400000 |
| |
| #define CYCLES_ROLLOVER_CUTOFF 0x00FF |
| #define MAX17042_DEF_RO_LRNCFG 0x0076 |
| |
| #define MAX17042_CGAIN_DEFAULT 0x4000 |
| #define MAX17042_CGAIN_DISABLE 0x0000 |
| #define MAX17042_COFF_DEFAULT 0x0000 |
| #define MAX17042_EN_VOLT_FG 0x0007 |
| #define MAX17042_CFG_INTR_SOCVF 0x0003 |
| #define MAX17042_AtRate_DEFAULT 0x0 |
| #define MAX17042_MinMaxTemp_DEFAULT 0x807F |
| #define MAX17042_MinMaxVolt_DEFAULT 0x00FF |
| #define MAX17042_MinMaxCurr_DEFAULT 0x807F |
| |
| /* Vempty value set to 2500mV */ |
| #define MAX17042_DEF_VEMPTY_VAL 0x7D5A |
| |
| #define MAX17042_SIGN_INDICATOR 0x8000 |
| |
| #define SHUTDOWN_DEF_FG_MASK_BIT (1 << 0) |
| #define SHUTDOWN_OCV_MASK_BIT (1 << 1) |
| #define SHUTDOWN_LOWBATT_MASK_BIT (1 << 2) |
| |
| #define BYTE_VALUE 1 |
| #define WORD_VALUE 0 |
| |
| /* Time interval to write temperature values from host, if needed (in milliseconds) */ |
| #define TEMP_WRITE_INTERVAL 120000 |
| enum max17042_register { |
| MAX17042_STATUS = 0x00, |
| MAX17042_VALRT_Th = 0x01, |
| MAX17042_TALRT_Th = 0x02, |
| MAX17042_SALRT_Th = 0x03, |
| MAX17042_AtRate = 0x04, |
| MAX17042_RepCap = 0x05, |
| MAX17042_RepSOC = 0x06, |
| MAX17042_Age = 0x07, |
| MAX17042_TEMP = 0x08, |
| MAX17042_VCELL = 0x09, |
| MAX17042_Current = 0x0A, |
| MAX17042_AvgCurrent = 0x0B, |
| MAX17042_Qresidual = 0x0C, |
| MAX17042_SOC = 0x0D, |
| MAX17042_AvSOC = 0x0E, |
| MAX17042_RemCap = 0x0F, |
| MAX17042_FullCAP = 0x10, |
| MAX17042_TTE = 0x11, |
| MAX17042_V_empty = 0x12, |
| |
| MAX17042_RSLOW = 0x14, |
| |
| MAX17042_AvgTA = 0x16, |
| MAX17042_Cycles = 0x17, |
| MAX17042_DesignCap = 0x18, |
| MAX17042_AvgVCELL = 0x19, |
| MAX17042_MinMaxTemp = 0x1A, |
| MAX17042_MinMaxVolt = 0x1B, |
| MAX17042_MinMaxCurr = 0x1C, |
| MAX17042_CONFIG = 0x1D, |
| MAX17042_ICHGTerm = 0x1E, |
| MAX17042_AvCap = 0x1F, |
| MAX17042_ManName = 0x20, |
| MAX17042_DevName = 0x21, |
| MAX17042_DevChem = 0x22, |
| MAX17042_FullCAPNom = 0x23, |
| |
| MAX17042_TempNom = 0x24, |
| MAX17042_TempCold = 0x25, |
| MAX17042_TempHot = 0x26, |
| MAX17042_AIN = 0x27, |
| MAX17042_LearnCFG = 0x28, |
| MAX17042_SHFTCFG = 0x29, |
| MAX17042_RelaxCFG = 0x2A, |
| MAX17042_MiscCFG = 0x2B, |
| MAX17042_TGAIN = 0x2C, |
| MAx17042_TOFF = 0x2D, |
| MAX17042_CGAIN = 0x2E, |
| MAX17042_COFF = 0x2F, |
| |
| MAX17042_SOCempty = 0x33, |
| MAX17042_T_empty = 0x34, |
| MAX17042_FullCAP0 = 0x35, |
| |
| MAX17042_LAvg_empty = 0x36, |
| MAX17042_FCTC = 0x37, |
| MAX17042_RCOMP0 = 0x38, |
| MAX17042_TempCo = 0x39, |
| MAX17042_ETC = 0x3A, |
| MAX17042_K_empty0 = 0x3B, |
| MAX17042_TaskPeriod = 0x3C, |
| MAX17042_FSTAT = 0x3D, |
| |
| MAX17042_SHDNTIMER = 0x3F, |
| |
| MAX17042_dQacc = 0x45, |
| MAX17042_dPacc = 0x46, |
| MAX17042_VFSOC0 = 0x48, |
| MAX17042_VFRemCap = 0x4A, |
| |
| MAX17042_QH = 0x4D, |
| MAX17042_QL = 0x4E, |
| |
| MAX17042_VFSOC0Enable = 0x60, |
| MAX17042_MLOCKReg1 = 0x62, |
| MAX17042_MLOCKReg2 = 0x63, |
| MAX17042_MODELChrTbl = 0x80, |
| MAX17042_OCV = 0xEE, |
| MAX17042_OCVInternal = 0xFB, |
| MAX17042_VFSOC = 0xFF, |
| |
| }; |
| |
| /* Registers specific to max17047/50 */ |
| enum max17050_register { |
| MAX17050_QRTbl00 = 0x12, |
| MAX17050_FullSOCThr = 0x13, |
| MAX17050_QRTbl10 = 0x22, |
| MAX17050_QRTbl20 = 0x32, |
| MAX17050_V_empty = 0x3A, |
| MAX17050_QRTbl30 = 0x42, |
| }; |
| |
| #define DRV_NAME "max170xx_battery" |
| |
| enum max170xx_chip_type {MAX17042, MAX17050}; |
| |
| /* No of times we should retry on -EAGAIN error */ |
| #define NR_RETRY_CNT 3 |
| |
| /* No of times we should process interrupt reasons @irq handler */ |
| /* Probably all values >1 are ok, Normally It just goes once thought |
| * all bits and everything is handled. Also chips seems to limit |
| * interrupts to ~3/s, so we have ~300ms to process, until we will |
| * miss interrupt. What ever value it's, it doesn't have any |
| * performance impact. */ |
| #define NR_RETRY_INT 3 |
| |
| /* No of times we should reset I2C lines */ |
| #define NR_I2C_RESET_CNT 8 |
| |
| #define VBATT_MAX 4200000 /* 4200mV */ |
| #define VBATT_MIN 3400000 /* 3400mV */ |
| |
| #define VBATT_MIN_OFFSET 100 /* 100mV from VMMIN */ |
| #define VBATT_MAX_OFFSET 50 /* 50mV from VMAX */ |
| #define VALERT_VOLT_OFFSET 20 /* each bit corresponds to 20mV */ |
| |
| /* default fuel gauge cell data for debug purpose only */ |
| static uint16_t cell_char_tbl[] = { |
| /* Data to be written from 0x80h */ |
| 0xA250, 0xB720, 0xB800, 0xB880, 0xB920, 0xBA00, 0xBA60, 0xBBF0, |
| 0xBCF0, 0xBE50, 0xC060, 0xC2D0, 0xC520, 0xC750, 0xCA00, 0xD090, |
| /* Data to be written from 0x90h */ |
| 0x0120, 0x1C80, 0x0470, 0x0440, 0x0100, 0x5500, 0x0960, 0x2410, |
| 0x2250, 0x15F0, 0x0BD0, 0x0D00, 0x0B00, 0x0BB0, 0x08A0, 0x08A0, |
| /* Data to be written from 0xA0h */ |
| 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, |
| 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, |
| }; |
| |
| struct max17042_chip { |
| struct i2c_client *client; |
| enum max170xx_chip_type chip_type; |
| struct power_supply battery; |
| struct max17042_platform_data *pdata; |
| struct mutex batt_lock; |
| struct mutex init_lock; |
| |
| int present; |
| int status; |
| int health; |
| int technology; |
| int charge_full_des; |
| int ext_set_cap; |
| |
| struct work_struct init_worker; |
| struct work_struct evt_worker; |
| struct delayed_work temp_worker; |
| |
| bool plat_rebooting; |
| /* |
| * user space can disable default shutdown |
| * methods set by platform. |
| */ |
| int disable_shdwn_methods; |
| |
| /* |
| * user space can set this variable to report constant |
| * batery temperature for conformence testing. |
| */ |
| bool enable_fake_temp; |
| int extra_resv_cap; |
| int voltage_max; |
| int model_algo_factor; |
| }; |
| |
| /* Sysfs entry for disable shutdown methods from user space */ |
| static ssize_t override_shutdown_methods(struct device *device, |
| struct device_attribute *attr, const char *buf, |
| size_t count); |
| static ssize_t get_shutdown_methods(struct device *device, |
| struct device_attribute *attr, char *buf); |
| static DEVICE_ATTR(disable_shutdown_methods, S_IRUGO | S_IWUSR, |
| get_shutdown_methods, override_shutdown_methods); |
| |
| /* Sysfs entry to enter shutdown voltage from user space */ |
| static int shutdown_volt; |
| static ssize_t set_shutdown_voltage(struct device *device, |
| struct device_attribute *attr, const char *buf, |
| size_t count); |
| static ssize_t get_shutdown_voltage_set_by_user(struct device *device, |
| struct device_attribute *attr, char *buf); |
| static DEVICE_ATTR(shutdown_voltage, S_IRUGO | S_IWUSR, |
| get_shutdown_voltage_set_by_user, set_shutdown_voltage); |
| |
| /* Sysfs entry to enable from user space */ |
| static bool shutdown_mode_status = false; |
| static ssize_t get_shutdown_mode_status(struct device *dev, |
| struct device_attribute *attr, char *buf); |
| static ssize_t set_shutdown_mode_status(struct device *dev, |
| struct device_attribute *attr, const char *buf, |
| size_t count); |
| static DEVICE_ATTR(shutdown_mode, S_IRUGO | S_IWUSR, |
| get_shutdown_mode_status, set_shutdown_mode_status); |
| |
| /* |
| * Sysfs entry to report fake battery temperature. This |
| * interface is needed to support conformence testing |
| */ |
| static ssize_t set_fake_temp_enable(struct device *device, |
| struct device_attribute *attr, const char *buf, |
| size_t count); |
| static ssize_t get_fake_temp_enable(struct device *device, |
| struct device_attribute *attr, char *buf); |
| static DEVICE_ATTR(enable_fake_temp, S_IRUGO | S_IWUSR, |
| get_fake_temp_enable, set_fake_temp_enable); |
| |
| #ifdef CONFIG_DEBUG_FS |
| static struct dentry *max17042_dbgfs_root; |
| static char max17042_dbg_regs[MAX17042_MAX_MEM][4]; |
| #endif |
| |
| static int max17042_reboot_callback(struct notifier_block *nfb, |
| unsigned long event, void *data); |
| static void enable_shutdown_mode(void); |
| |
| static struct notifier_block max17042_reboot_notifier_block = { |
| .notifier_call = max17042_reboot_callback, |
| .priority = 0, |
| }; |
| |
| static bool is_battery_online(struct max17042_chip *chip); |
| static void configure_interrupts(struct max17042_chip *chip); |
| /* Set SOC threshold in S3 state */ |
| static void set_soc_intr_thresholds_s3(struct max17042_chip *chip); |
| /* Set SOC threshold to offset percentage in S0 state */ |
| static void set_soc_intr_thresholds_s0(struct max17042_chip *chip, int offset); |
| static void save_runtime_params(struct max17042_chip *chip); |
| static void set_chip_config(struct max17042_chip *chip); |
| static u16 fg_vfSoc; |
| static bool fake_batt_full; |
| static struct max17042_config_data *fg_conf_data; |
| static struct i2c_client *max17042_client; |
| |
| |
| atomic_t fopen_count; |
| |
| static void update_runtime_params(struct max17042_chip *chip); |
| static int read_batt_pack_temp(struct max17042_chip *chip, int *temp, int is_byte); |
| |
| /* Voltage-Capacity lookup function to get |
| * capacity value against a given voltage */ |
| static unsigned int voltage_capacity_lookup(unsigned int val) |
| { |
| unsigned int max = VBATT_MAX / 1000; |
| unsigned int min = VBATT_MIN / 1000; |
| unsigned int capacity; |
| unsigned int total_diff; |
| unsigned int val_diff; |
| |
| if (val > max) |
| return 100; |
| |
| if (val < min) |
| return 0; |
| |
| total_diff = max - min; |
| val_diff = max - val; |
| |
| capacity = (total_diff - val_diff) * 100 / total_diff; |
| |
| return capacity; |
| } |
| |
| static int max17042_property_is_privileged_read(struct power_supply *psy, |
| enum power_supply_property psp) |
| { |
| switch (psp) { |
| case POWER_SUPPLY_PROP_MODEL_NAME: |
| case POWER_SUPPLY_PROP_SERIAL_NUMBER: |
| return 1; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static int dev_file_open(struct inode *i, struct file *f) |
| { |
| if (atomic_read(&fopen_count)) |
| return -EBUSY; |
| atomic_inc(&fopen_count); |
| return 0; |
| } |
| |
| static int dev_file_close(struct inode *i, struct file *f) |
| { |
| atomic_dec(&fopen_count); |
| return 0; |
| } |
| |
| static ssize_t dev_file_read(struct file *f, char __user *buf, |
| size_t len, loff_t *off) |
| { |
| struct max17042_chip *chip = i2c_get_clientdata(max17042_client); |
| int ret; |
| |
| if (!chip->pdata->is_init_done) { |
| dev_err(&max17042_client->dev, |
| "MAX17042 is not initialized.\n"); |
| return -ECANCELED; |
| } |
| |
| update_runtime_params(chip); |
| |
| if (sizeof(*fg_conf_data) > len) |
| { |
| dev_err(&max17042_client->dev, |
| "MAX17042 FG Config data file: %d is bigger than expected length: %d.\n", |
| sizeof(*fg_conf_data), |
| len); |
| return -EINVAL; |
| } |
| |
| ret = copy_to_user(buf, fg_conf_data, sizeof(*fg_conf_data)); |
| if (!ret) |
| return sizeof(*fg_conf_data); |
| |
| return -EINVAL; |
| } |
| |
| static ssize_t dev_file_write(struct file *f, const char __user *buf, |
| size_t len, loff_t *off) |
| { |
| struct max17042_chip *chip = i2c_get_clientdata(max17042_client); |
| |
| if (chip->pdata->is_init_done) { |
| dev_err(&max17042_client->dev, |
| "Already initialized.So ignoring new set of data\n"); |
| return -ECANCELED; |
| } |
| |
| if (len > sizeof(*fg_conf_data)) |
| return -EINVAL; |
| |
| if (copy_from_user(fg_conf_data, buf, len)) |
| return -EINVAL; |
| |
| set_chip_config(chip); |
| |
| if (chip->pdata->is_init_done) { |
| dev_info(&max17042_client->dev, |
| "MAX17042 initialized successfully\n"); |
| fg_conf_data->config_init = 0x1; |
| } |
| |
| /* Return no. of bytes written */ |
| return len; |
| } |
| |
| static const struct file_operations helper_fops = { |
| .owner = THIS_MODULE, |
| .open = &dev_file_open, |
| .release = &dev_file_close, |
| .read = &dev_file_read, |
| .write = &dev_file_write, |
| }; |
| |
| static struct miscdevice fg_helper = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "max170xx", |
| .fops = &helper_fops, |
| }; |
| |
| static enum power_supply_property max17042_battery_props[] = { |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_TECHNOLOGY, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| POWER_SUPPLY_PROP_VOLTAGE_AVG, |
| POWER_SUPPLY_PROP_VOLTAGE_OCV, |
| POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| POWER_SUPPLY_PROP_CURRENT_AVG, |
| POWER_SUPPLY_PROP_CAPACITY, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_TEMP_ALERT_MIN, |
| POWER_SUPPLY_PROP_TEMP_ALERT_MAX, |
| POWER_SUPPLY_PROP_CHARGE_NOW, |
| POWER_SUPPLY_PROP_CHARGE_FULL, |
| POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, |
| POWER_SUPPLY_PROP_CHARGE_COUNTER, |
| POWER_SUPPLY_PROP_MODEL_NAME, |
| POWER_SUPPLY_PROP_SERIAL_NUMBER, |
| }; |
| |
| static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value) |
| { |
| int ret, i; |
| struct max17042_chip *chip = i2c_get_clientdata(client); |
| |
| /* if the shutdown or reboot sequence started |
| * then block the access to maxim registers as chip |
| * cannot be recovered from broken i2c transactions |
| */ |
| if (chip->plat_rebooting) { |
| dev_warn(&client->dev, "rebooting is in progress\n"); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < NR_RETRY_CNT; i++) { |
| ret = i2c_smbus_write_word_data(client, reg, value); |
| if (ret == -EAGAIN || ret == -ETIMEDOUT) |
| continue; |
| else |
| break; |
| } |
| |
| if (ret < 0) |
| dev_err(&client->dev, "I2C SMbus Write error:%d\n", ret); |
| |
| return ret; |
| } |
| |
| static int max17042_read_reg(struct i2c_client *client, u8 reg) |
| { |
| int ret, i; |
| struct max17042_chip *chip = i2c_get_clientdata(client); |
| |
| /* if the shutdown or reboot sequence started |
| * then block the access to maxim registers as chip |
| * cannot be recovered from broken i2c transactions |
| */ |
| if (chip->plat_rebooting) { |
| dev_warn(&client->dev, "rebooting is in progress\n"); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < NR_RETRY_CNT; i++) { |
| ret = i2c_smbus_read_word_data(client, reg); |
| if (ret == -EAGAIN || ret == -ETIMEDOUT) |
| continue; |
| else |
| break; |
| } |
| |
| if (ret < 0) |
| dev_err(&client->dev, "I2C SMbus Read error:%d\n", ret); |
| |
| return ret; |
| } |
| |
| /* |
| * max17042 chip has few registers which could get modified by the |
| * chip as well during its fuel gauge learning process. So we need |
| * to do a write verify on those registers and if the write fails |
| * then we have to retry. |
| */ |
| static int max17042_write_verify_reg(struct i2c_client *client, |
| u8 reg, u16 value) |
| { |
| int ret, i; |
| |
| for (i = 0; i < NR_RETRY_CNT; i++) { |
| /* Write the value to register */ |
| ret = max17042_write_reg(client, reg, value); |
| if (ret < 0) |
| continue; |
| /* Read the value from register */ |
| ret = max17042_read_reg(client, reg); |
| if (ret < 0) |
| continue; |
| /* compare the both the values */ |
| if (value != ret) |
| dev_err(&client->dev, |
| "write verify failed on Register:0x%x\n", reg); |
| else |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int max17042_reg_read_modify(struct i2c_client *client, u8 reg, |
| u16 val, int bit_set) |
| { |
| int ret; |
| |
| ret = max17042_read_reg(client, reg); |
| if (ret < 0) |
| return ret; |
| |
| if (bit_set) |
| ret |= val; |
| else |
| ret &= (~val); |
| |
| ret = max17042_write_reg(client, reg, ret); |
| return ret; |
| } |
| |
| static irqreturn_t max17042_intr_handler(int id, void *dev) |
| { |
| return IRQ_WAKE_THREAD; |
| } |
| |
| static irqreturn_t max17042_thread_handler(int id, void *dev) |
| { |
| struct max17042_chip *chip = dev; |
| struct device *device = &chip->client->dev; |
| int stat, temp, val, count = 0; |
| u16 processed, ignored, config; |
| |
| pm_runtime_get_sync(device); |
| |
| /* read current configuration */ |
| val = max17042_read_reg(chip->client, MAX17042_CONFIG); |
| if (val < 0) |
| config = fg_conf_data->cfg; |
| else |
| config = val; |
| |
| stat = max17042_read_reg(chip->client, MAX17042_STATUS); |
| do { |
| dev_dbg(device, "%s: Status-val: 0x%x\n", __func__, stat); |
| if (stat < 0) { |
| dev_err(device, |
| "max17042-INTR: status read failed:%d\n", stat); |
| pm_runtime_put_sync(device); |
| return IRQ_HANDLED; |
| } |
| |
| processed = 0; |
| ignored = 0; |
| |
| if ((stat & STATUS_VMN_BIT) || (stat & STATUS_VMX_BIT)) { |
| dev_info(device, "VOLT threshold INTR\n"); |
| /* nothing yet */ |
| if (stat & STATUS_VMN_BIT) { |
| if (config & CONFIG_VSTICKY_BIT_SET) |
| processed |= STATUS_VMN_BIT; |
| else |
| ignored |= STATUS_VMN_BIT; |
| } |
| if (stat & STATUS_VMX_BIT) { |
| if (config & CONFIG_VSTICKY_BIT_SET) |
| processed |= STATUS_VMX_BIT; |
| else |
| ignored |= STATUS_VMX_BIT; |
| } |
| } |
| |
| if ((stat & STATUS_SMN_BIT) || (stat & STATUS_SMX_BIT)) { |
| dev_info(device, "SOC threshold INTR\n"); |
| /* Actual processing is done in evt_worker */ |
| /* so we might get interrupt again or miss */ |
| if (stat & STATUS_SMN_BIT) { |
| if (config & CONFIG_SSTICKY_BIT_SET) |
| processed |= STATUS_SMN_BIT; |
| else |
| ignored |= STATUS_SMN_BIT; |
| } |
| if (stat & STATUS_SMX_BIT) { |
| if (config & CONFIG_SSTICKY_BIT_SET) |
| processed |= STATUS_SMX_BIT; |
| else |
| ignored |= STATUS_SMX_BIT; |
| } |
| } |
| |
| if (stat & STATUS_BR_BIT) { |
| dev_info(device, "Battery removed INTR\n"); |
| if ((config & CONFIG_BER_BIT_ENBL) && |
| (stat & STATUS_BST_BIT)) { |
| dev_warn(device, "battery unplugged\n"); |
| mutex_lock(&chip->batt_lock); |
| chip->present = 0; |
| mutex_unlock(&chip->batt_lock); |
| kernel_power_off(); |
| } |
| processed |= STATUS_BR_BIT; |
| } |
| |
| if ((stat & STATUS_TMN_BIT) || (stat & STATUS_TMX_BIT)) { |
| val = read_batt_pack_temp(chip, &temp, BYTE_VALUE); |
| if (val) { |
| dev_warn(device, "Can't read temp: %d\n", val); |
| } else { |
| val = max17042_read_reg(chip->client, |
| MAX17042_TALRT_Th); |
| dev_info(device, |
| "Thermal threshold INTR: %d (%d, %d)\n", |
| temp, (int8_t)(val & 0xff), |
| (int8_t)(val >> 8)); |
| } |
| if (stat & STATUS_TMN_BIT) { |
| if (config & CONFIG_TSTICKY_BIT_SET) |
| processed |= STATUS_TMN_BIT; |
| else |
| ignored |= STATUS_TMN_BIT; |
| } |
| if (stat & STATUS_TMX_BIT) { |
| if (config & CONFIG_TSTICKY_BIT_SET) |
| processed |= STATUS_TMX_BIT; |
| else |
| ignored |= STATUS_TMX_BIT; |
| } |
| } |
| |
| if (stat & STATUS_POR_BIT) { |
| dev_info(device, "Power On Reset event\n"); |
| ignored |= STATUS_POR_BIT; |
| } |
| |
| if (stat & STATUS_BST_BIT) |
| ignored |= STATUS_BST_BIT; |
| |
| if (stat & STATUS_BI_BIT) { |
| dev_info(device, "Battery Insert INTR\n"); |
| /* nothing yet */ |
| processed |= STATUS_BI_BIT; |
| } |
| |
| /* clear int */ |
| max17042_reg_read_modify(chip->client, MAX17042_STATUS, |
| processed, 0); |
| |
| stat = max17042_read_reg(chip->client, MAX17042_STATUS); |
| } while ((stat & STATUS_MASK & ~ignored) && (count++ < NR_RETRY_INT)); |
| |
| /* update battery status and health */ |
| schedule_work(&chip->evt_worker); |
| pm_runtime_put_sync(device); |
| if (count >= NR_RETRY_INT) { |
| dev_err(device, "%s: can't process all IRQ reasons: 0x%x\n", |
| __func__, stat); |
| /* desperate */ |
| max17042_write_reg(max17042_client, MAX17042_STATUS, 0x0000); |
| } |
| return IRQ_HANDLED; |
| } |
| |
| static short adjust_sign_value(int value, int is_byte) |
| { |
| short result, temp = (short)value; |
| if (temp & MAX17042_SIGN_INDICATOR) { |
| |
| if (is_byte) { |
| result = (~temp) >> 8; |
| result &= 0xff; |
| } else { |
| result = ~temp; |
| } |
| |
| result++; |
| result *= -1; |
| } else { |
| if (is_byte) |
| result = temp >> 8; |
| else |
| result = temp; |
| } |
| |
| return result; |
| } |
| |
| static int read_batt_pack_temp(struct max17042_chip *chip, int *temp, int is_byte) |
| { |
| int ret; |
| u16 val; |
| |
| /* Read battery pack temperature */ |
| if (chip->pdata->battery_pack_temp) { |
| ret = chip->pdata->battery_pack_temp(temp); |
| if (ret < 0) |
| goto temp_read_err; |
| |
| /* Convert the temperature to 2's complement form. |
| * Most significant byte contains the decimal |
| * equivalent of the data */ |
| if (fg_conf_data->cfg & CONFIG_TEX_BIT_ENBL) { |
| if (*temp < 0) { |
| val = (*temp + 0xff + 1); |
| val <<= 8; |
| } else { |
| val = *temp; |
| val <<= 8; |
| } |
| ret = max17042_write_reg(chip->client, |
| MAX17042_TEMP, val); |
| if (ret < 0) |
| dev_err(&chip->client->dev, |
| "Temp write to maxim failed:%d", ret); |
| } |
| } else { |
| ret = max17042_read_reg(chip->client, MAX17042_TEMP); |
| if (ret < 0) |
| goto temp_read_err; |
| |
| *temp = adjust_sign_value(ret, is_byte); |
| } |
| return 0; |
| |
| temp_read_err: |
| dev_err(&chip->client->dev, "BP Temp read error:%d", ret); |
| return ret; |
| } |
| |
| static int max17042_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct max17042_chip *chip = container_of(psy, |
| struct max17042_chip, battery); |
| int ret = 0; |
| int8_t temp; |
| |
| mutex_lock(&chip->batt_lock); |
| switch (psp) { |
| case POWER_SUPPLY_PROP_STATUS: |
| chip->status = val->intval; |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| if ((val->intval >= 0) && (val->intval <= 100)) |
| chip->ext_set_cap = val->intval; |
| break; |
| case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: |
| ret = max17042_read_reg(chip->client, MAX17042_TALRT_Th); |
| if (ret < 0) |
| break; |
| temp = val->intval / 10; /* 0.1C prop to 1C reg */ |
| /* Force that min is under max */ |
| if (temp >= (int8_t)(ret >> 8)) |
| temp = (int8_t)(ret >> 8) - 1; |
| ret = (ret & 0xff00) + (uint8_t)temp; |
| ret = max17042_write_reg(chip->client, MAX17042_TALRT_Th, ret); |
| break; |
| case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: |
| ret = max17042_read_reg(chip->client, MAX17042_TALRT_Th); |
| if (ret < 0) |
| break; |
| temp = val->intval / 10; /* 0.1C prop to 1C reg */ |
| /* Force that max is over min */ |
| if (temp <= (int8_t)(ret & 0xff)) |
| temp = (int8_t)(ret & 0xff) + 1; |
| ret = (temp << 8) + (ret & 0xff); |
| ret = max17042_write_reg(chip->client, MAX17042_TALRT_Th, ret); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| mutex_unlock(&chip->batt_lock); |
| |
| 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 = container_of(psy, |
| struct max17042_chip, battery); |
| short int cur; |
| int volt_ocv, ret, batt_temp, batt_vmin; |
| |
| mutex_lock(&chip->batt_lock); |
| switch (psp) { |
| case POWER_SUPPLY_PROP_STATUS: |
| /* |
| * status is being read from external |
| * module so check for error case before |
| * assigning to intval. |
| */ |
| if (chip->status < 0) { |
| ret = chip->status; |
| goto ps_prop_read_err; |
| } else { |
| val->intval = chip->status; |
| } |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| /* |
| * health is being read from external |
| * module so check for error case before |
| * assigning to intval. |
| */ |
| if (chip->health < 0) { |
| ret = chip->health; |
| goto ps_prop_read_err; |
| } else { |
| val->intval = chip->health; |
| } |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = chip->present; |
| break; |
| case POWER_SUPPLY_PROP_TECHNOLOGY: |
| val->intval = chip->technology; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: |
| val->intval = chip->charge_full_des; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_NOW: |
| ret = max17042_read_reg(chip->client, MAX17042_RepCap); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| |
| if (fg_conf_data->rsense) |
| val->intval = (ret * MAX17042_CHRG_CONV_FCTR) |
| / fg_conf_data->rsense; |
| else |
| val->intval = ret * MAX17042_CHRG_CONV_FCTR; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_FULL: |
| ret = max17042_read_reg(chip->client, MAX17042_FullCAP); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| |
| if (fg_conf_data->rsense) |
| val->intval = (ret * MAX17042_CHRG_CONV_FCTR) |
| / fg_conf_data->rsense; |
| else |
| val->intval = ret * MAX17042_CHRG_CONV_FCTR; |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_COUNTER: |
| ret = max17042_read_reg(chip->client, MAX17042_QH); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| |
| if (fg_conf_data->rsense) |
| val->intval = (ret * MAX17042_CHRG_CONV_FCTR) |
| / fg_conf_data->rsense; |
| else |
| val->intval = ret * MAX17042_CHRG_CONV_FCTR; |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| ret = max17042_read_reg(chip->client, MAX17042_Current); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| |
| cur = adjust_sign_value(ret, WORD_VALUE); |
| |
| if (fg_conf_data->rsense) |
| val->intval = (cur * MAX17042_CURR_CONV_FCTR) |
| / fg_conf_data->rsense; |
| else |
| val->intval = cur * MAX17042_CURR_CONV_FCTR; |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_AVG: |
| ret = max17042_read_reg(chip->client, MAX17042_AvgCurrent); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| |
| cur = adjust_sign_value(ret, WORD_VALUE); |
| |
| if (fg_conf_data->rsense) |
| val->intval = (cur * MAX17042_CURR_CONV_FCTR) |
| / fg_conf_data->rsense; |
| else |
| val->intval = cur * MAX17042_CURR_CONV_FCTR; |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| if (!chip->pdata->enable_current_sense || |
| chip->enable_fake_temp) { |
| val->intval = CONSTANT_TEMP_IN_POWER_SUPPLY; |
| break; |
| } |
| ret = read_batt_pack_temp(chip, &batt_temp, WORD_VALUE); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| /* |
| * Temperature is measured in tenths of degrees celsius. |
| */ |
| val->intval = MAX17042_TEMP_CONV_FACTOR(batt_temp); |
| break; |
| case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: |
| ret = max17042_read_reg(chip->client, MAX17042_TALRT_Th); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| val->intval = ((int8_t)(ret & 0xff)) * 10; /* 0.1C */ |
| break; |
| case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: |
| ret = max17042_read_reg(chip->client, MAX17042_TALRT_Th); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| val->intval = ((int8_t)(ret >> 8)) * 10; /* 0.1C */ |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
| ret = max17042_read_reg(chip->client, MAX17042_VCELL); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| val->intval = (ret >> 3) * MAX17042_VOLT_CONV_FCTR; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_AVG: |
| ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| val->intval = (ret >> 3) * MAX17042_VOLT_CONV_FCTR; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_OCV: |
| ret = max17042_read_reg(chip->client, MAX17042_OCVInternal); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| val->intval = (ret >> 3) * MAX17042_VOLT_CONV_FCTR; |
| 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, MAX17050_V_empty); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| val->intval = (ret >> 7) * 10000; /* Units of LSB = 10mV */ |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
| val->intval = chip->voltage_max; |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| /* |
| * Check whether the capacity is set externally or not. If the |
| * capacity value is set externally, use same as the SOC value |
| * for the battery level usage. |
| */ |
| if ((chip->ext_set_cap) >= 0 && (chip->ext_set_cap <= 100)) { |
| val->intval = chip->ext_set_cap; |
| break; |
| } |
| |
| /* |
| * WA added to support power supply voltage |
| * variations b/w supply and FG readings. |
| */ |
| if (fake_batt_full) { |
| val->intval = 100; |
| break; |
| } |
| |
| /* Voltage Based shutdown method to avoid modem crash */ |
| if (chip->pdata->is_volt_shutdown) { |
| ret = max17042_read_reg(chip->client, |
| MAX17042_OCVInternal); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| volt_ocv = (ret >> 3) * MAX17042_VOLT_CONV_FCTR; |
| |
| /* Get the minimum voltage thereshold */ |
| if (shutdown_volt) |
| batt_vmin = shutdown_volt; |
| else if (chip->pdata->get_vmin_threshold) |
| batt_vmin = chip->pdata->get_vmin_threshold(); |
| else |
| batt_vmin = BATTERY_VOLT_MIN_THRESHOLD; |
| |
| if (volt_ocv <= batt_vmin) { |
| /* if user disables OCV shutdown method |
| * report 1% capcity so that platform |
| * will not get shutdown. |
| */ |
| if (chip->disable_shdwn_methods & |
| SHUTDOWN_OCV_MASK_BIT) |
| val->intval = 1; |
| else |
| val->intval = 0; |
| break; |
| } |
| |
| } |
| |
| /* Check for LOW Battery Shutdown mechanism is enabled */ |
| if (chip->pdata->is_lowbatt_shutdown && |
| (chip->health == POWER_SUPPLY_HEALTH_DEAD)) { |
| /* if user disables LOWBATT INT shutdown method |
| * report 1% capcity so that platform |
| * will not get shutdown. |
| */ |
| if (chip->disable_shdwn_methods & |
| SHUTDOWN_LOWBATT_MASK_BIT) |
| val->intval = 1; |
| else |
| val->intval = 0; |
| break; |
| } |
| |
| /* If current sensing is not enabled then read the |
| * voltage based fuel gauge register for SOC */ |
| if (chip->pdata->enable_current_sense) { |
| /* If LOW Battery and not charging then |
| * report 0% for immediate graceful shutdown */ |
| if ((chip->health == POWER_SUPPLY_HEALTH_DEAD) && |
| (chip->status != POWER_SUPPLY_STATUS_CHARGING)) { |
| val->intval = 0; |
| break; |
| } |
| ret = max17042_read_reg(chip->client, MAX17042_RepSOC); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| val->intval = ret >> 8; |
| /* Check if MSB of lower byte is set |
| * then round off the SOC to higher digit |
| */ |
| if ((ret & 0x80) && val->intval) |
| val->intval += 1; |
| } else { |
| ret = max17042_read_reg(chip->client, MAX17042_VCELL); |
| if (ret < 0) |
| goto ps_prop_read_err; |
| |
| ret = (ret >> 3) * MAX17042_VOLT_CONV_FCTR / 1000; |
| val->intval = voltage_capacity_lookup(ret); |
| } |
| |
| if (val->intval > 100) |
| val->intval = 100; |
| |
| /* if user disables default FG shutdown method |
| * report 1% capcity so that platform |
| * will not get shutdown. |
| */ |
| if ((val->intval == 0) && (chip->disable_shdwn_methods & |
| SHUTDOWN_DEF_FG_MASK_BIT)) |
| val->intval = 1; |
| break; |
| case POWER_SUPPLY_PROP_MODEL_NAME: |
| if (!strncmp(chip->pdata->battid, "UNKNOWNB", 8)) |
| val->strval = chip->pdata->battid; |
| else |
| val->strval = chip->pdata->model_name; |
| break; |
| case POWER_SUPPLY_PROP_SERIAL_NUMBER: |
| val->strval = chip->pdata->serial_num; |
| break; |
| default: |
| mutex_unlock(&chip->batt_lock); |
| return -EINVAL; |
| } |
| |
| mutex_unlock(&chip->batt_lock); |
| return 0; |
| |
| ps_prop_read_err: |
| mutex_unlock(&chip->batt_lock); |
| return ret; |
| } |
| |
| static void dump_fg_conf_data(struct max17042_chip *chip) |
| { |
| int i; |
| |
| dev_info(&chip->client->dev, "size:%x\n", fg_conf_data->size); |
| dev_info(&chip->client->dev, "table_type:%x\n", |
| fg_conf_data->table_type); |
| dev_info(&chip->client->dev, "config_init:%x\n", |
| fg_conf_data->config_init); |
| dev_info(&chip->client->dev, "rcomp0:%x\n", fg_conf_data->rcomp0); |
| dev_info(&chip->client->dev, "tempCo:%x\n", fg_conf_data->tempCo); |
| dev_info(&chip->client->dev, "kempty0:%x\n", fg_conf_data->kempty0); |
| dev_info(&chip->client->dev, "full_cap:%x\n", fg_conf_data->full_cap); |
| dev_info(&chip->client->dev, "cycles:%x\n", fg_conf_data->cycles); |
| dev_info(&chip->client->dev, "full_capnom:%x\n", |
| fg_conf_data->full_capnom); |
| dev_info(&chip->client->dev, "qrtbl00:%x\n", fg_conf_data->qrtbl00); |
| dev_info(&chip->client->dev, "qrtbl10:%x\n", fg_conf_data->qrtbl10); |
| dev_info(&chip->client->dev, "qrtbl20:%x\n", fg_conf_data->qrtbl20); |
| dev_info(&chip->client->dev, "qrtbl30:%x\n", fg_conf_data->qrtbl30); |
| dev_info(&chip->client->dev, "full_soc_thr:%x\n", |
| fg_conf_data->full_soc_thr); |
| dev_info(&chip->client->dev, "vempty:%x\n", fg_conf_data->vempty); |
| |
| dev_info(&chip->client->dev, "soc_empty:%x\n", |
| fg_conf_data->soc_empty); |
| dev_info(&chip->client->dev, "ichgt_term:%x\n", |
| fg_conf_data->ichgt_term); |
| dev_info(&chip->client->dev, "design_cap:%x\n", |
| fg_conf_data->design_cap); |
| dev_info(&chip->client->dev, "etc:%x\n", fg_conf_data->etc); |
| dev_info(&chip->client->dev, "rsense:%x\n", fg_conf_data->rsense); |
| dev_info(&chip->client->dev, "cfg:%x\n", fg_conf_data->cfg); |
| dev_info(&chip->client->dev, "learn_cfg:%x\n", |
| fg_conf_data->learn_cfg); |
| dev_info(&chip->client->dev, "filter_cfg:%x\n", |
| fg_conf_data->filter_cfg); |
| dev_info(&chip->client->dev, "relax_cfg:%x\n", fg_conf_data->relax_cfg); |
| |
| for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++) |
| dev_info(&chip->client->dev, "%x, ", |
| fg_conf_data->cell_char_tbl[i]); |
| dev_info(&chip->client->dev, "\n"); |
| } |
| |
| static void enable_soft_POR(struct max17042_chip *chip) |
| { |
| u16 val = 0x0000; |
| |
| max17042_write_reg(chip->client, MAX17042_MLOCKReg1, val); |
| max17042_write_reg(chip->client, MAX17042_MLOCKReg2, val); |
| max17042_write_reg(chip->client, MAX17042_STATUS, val); |
| |
| val = max17042_read_reg(chip->client, MAX17042_MLOCKReg1); |
| if (val) |
| dev_err(&chip->client->dev, "MLOCKReg1 read failed\n"); |
| |
| val = max17042_read_reg(chip->client, MAX17042_MLOCKReg2); |
| if (val) |
| dev_err(&chip->client->dev, "MLOCKReg2 read failed\n"); |
| |
| val = max17042_read_reg(chip->client, MAX17042_STATUS); |
| if (val) |
| dev_err(&chip->client->dev, "STATUS read failed\n"); |
| |
| /* send POR command */ |
| max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, 0x000F); |
| mdelay(2); |
| |
| val = max17042_read_reg(chip->client, MAX17042_STATUS); |
| if (val & STATUS_POR_BIT) |
| dev_info(&chip->client->dev, "SoftPOR done!\n"); |
| else |
| dev_err(&chip->client->dev, "SoftPOR failed\n"); |
| } |
| |
| static int write_characterization_data(struct max17042_chip *chip) |
| { |
| uint16_t cell_data[CELL_CHAR_TBL_SAMPLES]; |
| uint16_t temp_data[CELL_CHAR_TBL_SAMPLES]; |
| int i; |
| u8 addr; |
| |
| memset(cell_data, 0x0, sizeof(cell_data)); |
| /* Unlock model access */ |
| max17042_write_reg(chip->client, MAX17042_MLOCKReg1, FG_MODEL_UNLOCK1); |
| max17042_write_reg(chip->client, MAX17042_MLOCKReg2, FG_MODEL_UNLOCK2); |
| addr = MAX17042_MODELChrTbl; |
| |
| /* write the 48 words */ |
| for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++) |
| max17042_write_reg(chip->client, addr + i, |
| fg_conf_data->cell_char_tbl[i]); |
| |
| /* read the 48 words */ |
| for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++) |
| cell_data[i] = max17042_read_reg(chip->client, addr + i); |
| |
| /* compare the data */ |
| if (memcmp(cell_data, fg_conf_data->cell_char_tbl, sizeof(cell_data))) { |
| dev_err(&chip->client->dev, "%s write failed\n", __func__); |
| for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++) |
| dev_err(&chip->client->dev, "0x%x,0x%x\n", cell_data[i], |
| fg_conf_data->cell_char_tbl[i]); |
| /* Lock Model access regs */ |
| max17042_write_reg(chip->client, MAX17042_MLOCKReg1, |
| FG_MODEL_LOCK1); |
| max17042_write_reg(chip->client, MAX17042_MLOCKReg2, |
| FG_MODEL_LOCK2); |
| return -EIO; |
| } |
| |
| memset(temp_data, 0x0, sizeof(temp_data)); |
| /* Lock Model access regs */ |
| max17042_write_reg(chip->client, MAX17042_MLOCKReg1, FG_MODEL_LOCK1); |
| max17042_write_reg(chip->client, MAX17042_MLOCKReg2, FG_MODEL_LOCK2); |
| |
| /* read the 48 words */ |
| for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++) |
| cell_data[i] = max17042_read_reg(chip->client, addr + i); |
| |
| /* compare the data */ |
| if (memcmp(cell_data, temp_data, sizeof(temp_data))) { |
| dev_err(&chip->client->dev, "%s verify failed\n", __func__); |
| for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++) |
| dev_err(&chip->client->dev, "0x%x, ", cell_data[i]); |
| dev_err(&chip->client->dev, "\n"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static void configure_learncfg(struct max17042_chip *chip) |
| { |
| |
| u16 cycles; |
| |
| /*assigning cycles value from restored data*/ |
| cycles = fg_conf_data->cycles; |
| if ((cycles >= CYCLES_ROLLOVER_CUTOFF) && |
| (chip->chip_type == MAX17042)) |
| max17042_write_verify_reg(chip->client, MAX17042_LearnCFG, |
| MAX17042_DEF_RO_LRNCFG); |
| else |
| max17042_write_reg(chip->client, MAX17042_LearnCFG, |
| fg_conf_data->learn_cfg); |
| } |
| |
| static void write_config_regs(struct max17042_chip *chip) |
| { |
| max17042_write_reg(chip->client, MAX17042_CONFIG, fg_conf_data->cfg); |
| configure_learncfg(chip); |
| |
| max17042_write_reg(chip->client, MAX17042_SHFTCFG, |
| fg_conf_data->filter_cfg); |
| max17042_write_reg(chip->client, MAX17042_RelaxCFG, |
| fg_conf_data->relax_cfg); |
| if (chip->chip_type == MAX17050) |
| max17042_write_reg(chip->client, MAX17050_FullSOCThr, |
| fg_conf_data->full_soc_thr); |
| } |
| |
| static void write_custom_regs(struct max17042_chip *chip) |
| { |
| max17042_write_verify_reg(chip->client, MAX17042_RCOMP0, |
| fg_conf_data->rcomp0); |
| max17042_write_verify_reg(chip->client, MAX17042_TempCo, |
| fg_conf_data->tempCo); |
| max17042_write_verify_reg(chip->client, MAX17042_LAvg_empty, |
| fg_conf_data->lavg_empty); |
| max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm, |
| fg_conf_data->ichgt_term); |
| /* adjust Temperature gain and offset */ |
| max17042_write_reg(chip->client, |
| MAX17042_TGAIN, chip->pdata->tgain); |
| max17042_write_reg(chip->client, |
| MAx17042_TOFF, chip->pdata->toff); |
| /* Disable I2C shutdown mode */ |
| max17042_reg_read_modify(chip->client, MAX17042_CONFIG, |
| CONFIG_I2CSH_BIT_ENBL, 0); |
| /* Reset SHDNTIMER Threshold to default value */ |
| max17042_write_reg(chip->client, MAX17042_SHDNTIMER, |
| MAX17042_SHUTDOWN_TIMEOUT_DEF); |
| |
| if (chip->chip_type == MAX17042) { |
| max17042_write_reg(chip->client, MAX17042_ETC, |
| fg_conf_data->etc); |
| max17042_write_verify_reg(chip->client, MAX17042_K_empty0, |
| fg_conf_data->kempty0); |
| max17042_write_verify_reg(chip->client, MAX17042_SOCempty, |
| fg_conf_data->soc_empty); |
| max17042_write_verify_reg(chip->client, MAX17042_V_empty, |
| MAX17042_DEF_VEMPTY_VAL); |
| |
| } else { /* chip type max17050 */ |
| max17042_write_verify_reg(chip->client, MAX17050_V_empty, |
| fg_conf_data->vempty); |
| max17042_write_verify_reg(chip->client, MAX17050_QRTbl00, |
| fg_conf_data->qrtbl00 + chip->extra_resv_cap); |
| max17042_write_verify_reg(chip->client, MAX17050_QRTbl10, |
| fg_conf_data->qrtbl10 + chip->extra_resv_cap); |
| max17042_write_verify_reg(chip->client, MAX17050_QRTbl20, |
| fg_conf_data->qrtbl20 + chip->extra_resv_cap); |
| max17042_write_verify_reg(chip->client, MAX17050_QRTbl30, |
| fg_conf_data->qrtbl30 + chip->extra_resv_cap); |
| } |
| } |
| |
| static void update_capacity_regs(struct max17042_chip *chip) |
| { |
| max17042_write_verify_reg(chip->client, MAX17042_FullCAP, |
| MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap, |
| chip->model_algo_factor) |
| * fg_conf_data->rsense); |
| max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, |
| MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap, |
| chip->model_algo_factor) |
| * fg_conf_data->rsense); |
| max17042_write_reg(chip->client, MAX17042_DesignCap, |
| MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap, |
| chip->model_algo_factor) |
| * fg_conf_data->rsense); |
| } |
| |
| static void reset_vfsoc0_reg(struct max17042_chip *chip) |
| { |
| fg_vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC); |
| if (chip->chip_type == MAX17042) |
| { |
| max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK); |
| max17042_write_verify_reg(chip->client, MAX17042_VFSOC0, fg_vfSoc); |
| max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK); |
| } |
| } |
| |
| static void load_new_capacity_params(struct max17042_chip *chip, bool is_por) |
| { |
| u16 rem_cap, rep_cap, dq_acc, dq_acc_div, dp_acc; |
| |
| if (is_por) { |
| /* fg_vfSoc needs to shifted by 8 bits to get the |
| * perc in 1% accuracy, to get the right rem_cap multiply |
| * full_cap by model multiplication factor,fg_vfSoc |
| * and divide by 100 |
| */ |
| rem_cap = ((fg_vfSoc >> 8) * |
| (u32)(MAX17042_MODEL_MUL_FACTOR |
| (fg_conf_data->full_cap, |
| chip->model_algo_factor))) / 100; |
| |
| max17042_write_verify_reg(chip->client, MAX17042_RemCap, |
| rem_cap * fg_conf_data->rsense); |
| |
| rep_cap = rem_cap; |
| |
| max17042_write_verify_reg(chip->client, MAX17042_RepCap, |
| rep_cap * fg_conf_data->rsense); |
| } |
| |
| if (chip->chip_type == MAX17050) |
| { |
| /* Write dQ_acc to Capacity / 0x16 and dP_acc to 0xc80 */ |
| dq_acc_div = dQ_ACC_DIV_MAX17050; |
| dp_acc = dP_ACC_MAX17050; |
| } |
| else |
| { |
| /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */ |
| dq_acc_div = dQ_ACC_DIV_MAX17042; |
| dp_acc = dP_ACC_200; |
| } |
| |
| dq_acc = MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap * fg_conf_data->rsense, |
| chip->model_algo_factor) / dq_acc_div; |
| max17042_write_verify_reg(chip->client, MAX17042_dQacc, dq_acc); |
| max17042_write_verify_reg(chip->client, MAX17042_dPacc, dp_acc); |
| |
| max17042_write_verify_reg(chip->client, MAX17042_FullCAP, |
| fg_conf_data->full_cap |
| * fg_conf_data->rsense); |
| max17042_write_reg(chip->client, MAX17042_DesignCap, |
| MAX17042_MODEL_MUL_FACTOR(fg_conf_data->design_cap, |
| chip->model_algo_factor) |
| * fg_conf_data->rsense); |
| max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, |
| MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_capnom, |
| chip->model_algo_factor) |
| * fg_conf_data->rsense); |
| /* Update SOC register with new SOC */ |
| max17042_write_reg(chip->client, MAX17042_RepSOC, fg_vfSoc); |
| } |
| |
| static void update_runtime_params(struct max17042_chip *chip) |
| { |
| |
| fg_conf_data->rcomp0 = max17042_read_reg(chip->client, |
| MAX17042_RCOMP0); |
| fg_conf_data->tempCo = max17042_read_reg(chip->client, |
| MAX17042_TempCo); |
| /* |
| * Save only the original qrtbl register values ignoring the |
| * additionally reserved capacity. We deal with reserved |
| * capacity while restoring. |
| */ |
| if (chip->chip_type == MAX17050) { |
| fg_conf_data->qrtbl00 = max17042_read_reg(chip->client, |
| MAX17050_QRTbl00) - chip->extra_resv_cap; |
| fg_conf_data->qrtbl10 = max17042_read_reg(chip->client, |
| MAX17050_QRTbl10) - chip->extra_resv_cap; |
| fg_conf_data->qrtbl20 = max17042_read_reg(chip->client, |
| MAX17050_QRTbl20) - chip->extra_resv_cap; |
| fg_conf_data->qrtbl30 = max17042_read_reg(chip->client, |
| MAX17050_QRTbl30) - chip->extra_resv_cap; |
| } |
| |
| fg_conf_data->full_capnom = max17042_read_reg(chip->client, |
| MAX17042_FullCAPNom); |
| fg_conf_data->full_cap = max17042_read_reg(chip->client, |
| MAX17042_FullCAP); |
| if (fg_conf_data->rsense) { |
| fg_conf_data->full_capnom = MAX17042_MODEL_DIV_FACTOR( |
| fg_conf_data->full_capnom, chip->model_algo_factor) |
| / fg_conf_data->rsense; |
| |
| fg_conf_data->full_cap /= fg_conf_data->rsense; |
| } |
| fg_conf_data->cycles = max17042_read_reg(chip->client, |
| MAX17042_Cycles); |
| |
| /* Dump data before saving */ |
| dump_fg_conf_data(chip); |
| } |
| |
| static void save_runtime_params(struct max17042_chip *chip) |
| { |
| int size, retval; |
| |
| dev_dbg(&chip->client->dev, "%s\n", __func__); |
| |
| if (!chip->pdata->save_config_data || !chip->pdata->is_init_done) |
| return ; |
| |
| update_runtime_params(chip); |
| |
| size = sizeof(*fg_conf_data) - sizeof(fg_conf_data->cell_char_tbl); |
| retval = chip->pdata->save_config_data(DRV_NAME, fg_conf_data, size); |
| if (retval < 0) { |
| dev_err(&chip->client->dev, "%s failed\n", __func__); |
| return ; |
| } |
| |
| } |
| |
| static void enable_shutdown_mode(void) |
| { |
| int val, retval; |
| struct max17042_chip *chip = i2c_get_clientdata(max17042_client); |
| |
| /* Set shutdown timeout reg to minimum value of 45s */ |
| retval = max17042_write_reg(max17042_client, MAX17042_SHDNTIMER, MAX17042_SHUTDOWN_TIMEOUT_45S); |
| if (retval < 0) |
| { |
| dev_err(&chip->client->dev, |
| "shutdown timeout write to maxim failed: %d", retval); |
| return; |
| } |
| |
| /* Enable I2C Shutdown */ |
| max17042_reg_read_modify(chip->client, MAX17042_CONFIG, |
| CONFIG_I2CSH_BIT_ENBL, 1); |
| if (retval < 0) |
| { |
| dev_err(&chip->client->dev, |
| "I2C shutdown write to maxim failed: %d", retval); |
| return; |
| } |
| |
| dev_info(&chip->client->dev, "Shutdown mode enabled."); |
| |
| return; |
| } |
| |
| static int init_max17042_chip(struct max17042_chip *chip) |
| { |
| int ret = 0, val; |
| bool is_por; |
| |
| val = max17042_read_reg(chip->client, MAX17042_STATUS); |
| dev_info(&chip->client->dev, "Status reg: %x\n", val); |
| |
| if (val & STATUS_POR_BIT) |
| is_por = true; |
| else |
| is_por = false; |
| |
| /* Initialize configuration */ |
| write_config_regs(chip); |
| |
| /* write cell characterization data */ |
| ret = write_characterization_data(chip); |
| if (ret < 0) |
| return ret; |
| |
| /* write custom parameters */ |
| write_custom_regs(chip); |
| |
| /* update capacity params */ |
| update_capacity_regs(chip); |
| |
| /* delay must be atleast 350mS to allow VFSOC |
| * to be calculated from the new configuration |
| */ |
| msleep(350); |
| |
| /* reset vfsoc0 reg */ |
| reset_vfsoc0_reg(chip); |
| |
| /* advance to coulomb counter mode */ |
| max17042_write_verify_reg(chip->client, |
| MAX17042_Cycles, fg_conf_data->cycles); |
| |
| /* load new capacity params */ |
| load_new_capacity_params(chip, is_por); |
| |
| if (is_por) { |
| /* 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)); |
| } |
| |
| /* reset FullCap to non inflated value */ |
| max17042_write_verify_reg(chip->client, MAX17042_FullCAP, |
| fg_conf_data->full_cap * fg_conf_data->rsense); |
| |
| return ret; |
| } |
| |
| static void reset_max17042(struct max17042_chip *chip) |
| { |
| /* do soft power reset */ |
| enable_soft_POR(chip); |
| |
| /* After Power up, the MAX17042 requires 500mS in order |
| * to perform signal debouncing and initial SOC reporting |
| */ |
| msleep(500); |
| |
| max17042_write_reg(chip->client, MAX17042_CONFIG, 0x2210); |
| |
| /* adjust Temperature gain and offset */ |
| max17042_write_reg(chip->client, MAX17042_TGAIN, NTC_47K_TGAIN); |
| max17042_write_reg(chip->client, MAx17042_TOFF, NTC_47K_TOFF); |
| /* Reset Gain of Coulomb counter */ |
| max17042_write_reg(chip->client, MAX17042_CGAIN, |
| MAX17042_CGAIN_DEFAULT); |
| /* Reset Offset of Coulomb counter */ |
| max17042_write_reg(chip->client, MAX17042_COFF, |
| MAX17042_COFF_DEFAULT * fg_conf_data->rsense); |
| /* Reset AtRate register */ |
| max17042_write_reg(chip->client, MAX17042_AtRate, |
| MAX17042_AtRate_DEFAULT * fg_conf_data->rsense); |
| /* Reset MinMaxTemp register */ |
| max17042_write_reg(chip->client, MAX17042_MinMaxTemp, |
| MAX17042_MinMaxTemp_DEFAULT); |
| /* Reset MinMaxVolt register */ |
| max17042_write_reg(chip->client, MAX17042_MinMaxVolt, |
| MAX17042_MinMaxVolt_DEFAULT); |
| /* Reset MinMaxCurr register */ |
| max17042_write_reg(chip->client, MAX17042_MinMaxCurr, |
| MAX17042_MinMaxCurr_DEFAULT); |
| } |
| |
| static void max17042_restore_conf_data(struct max17042_chip *chip) |
| { |
| int retval = 0, size; |
| |
| /* return if lock already acquired */ |
| if (!mutex_trylock(&chip->init_lock)) |
| return; |
| |
| if (!chip->pdata->is_init_done && chip->pdata->restore_config_data) { |
| retval = chip->pdata->restore_config_data(DRV_NAME, |
| fg_conf_data, sizeof(*fg_conf_data)); |
| |
| if (retval == -ENXIO) { /* no device found */ |
| dev_err(&chip->client->dev, "device not found\n"); |
| chip->pdata->is_init_done = 1; |
| chip->pdata->save_config_data = NULL; |
| } else if (retval < 0) { /* device not ready */ |
| dev_warn(&chip->client->dev, "device not ready\n"); |
| } else { /* device ready */ |
| set_chip_config(chip); |
| /* mark the dirty byte in non-volatile memory */ |
| if (!fg_conf_data->config_init && retval >= 0) { |
| fg_conf_data->config_init = 0x1; |
| size = sizeof(*fg_conf_data) - |
| sizeof(fg_conf_data->cell_char_tbl); |
| retval = chip->pdata->save_config_data( |
| DRV_NAME, fg_conf_data, size); |
| if (retval < 0) |
| dev_err(&chip->client->dev, |
| "%s failed\n", __func__); |
| } |
| } |
| } |
| |
| if (chip->pdata->is_volt_shutdown_enabled) |
| chip->pdata->is_volt_shutdown = |
| chip->pdata->is_volt_shutdown_enabled(); |
| |
| if (chip->pdata->is_lowbatt_shutdown_enabled) |
| chip->pdata->is_lowbatt_shutdown = |
| chip->pdata->is_lowbatt_shutdown_enabled(); |
| |
| mutex_unlock(&chip->init_lock); |
| } |
| |
| static void set_chip_config(struct max17042_chip *chip) |
| { |
| int val, retval; |
| |
| /* Dump data after restoring */ |
| dump_fg_conf_data(chip); |
| |
| val = max17042_read_reg(chip->client, MAX17042_STATUS); |
| dev_info(&chip->client->dev, "Status reg: %x\n", val); |
| if (!fg_conf_data->config_init || (val & STATUS_POR_BIT)) { |
| dev_info(&chip->client->dev, "Config data should be loaded\n"); |
| if (chip->pdata->reset_chip) |
| reset_max17042(chip); |
| retval = init_max17042_chip(chip); |
| if (retval < 0) { |
| dev_err(&chip->client->dev, "maxim chip init failed\n"); |
| reset_max17042(chip); |
| chip->pdata->save_config_data = NULL; |
| } |
| } |
| if (fg_conf_data->cfg & CONFIG_TEX_BIT_ENBL) |
| schedule_delayed_work(&chip->temp_worker, 0); |
| chip->pdata->is_init_done = 1; |
| configure_interrupts(chip); |
| |
| /* multiply with 1000 to align with linux power supply sub system */ |
| chip->charge_full_des = (fg_conf_data->design_cap / 2) * 1000; |
| } |
| |
| static void max17042_init_worker(struct work_struct *work) |
| { |
| struct max17042_chip *chip = container_of(work, |
| struct max17042_chip, init_worker); |
| |
| dev_info(&chip->client->dev, "%s\n", __func__); |
| max17042_restore_conf_data(chip); |
| } |
| |
| static void max17042_temp_worker(struct work_struct *w) |
| { |
| struct delayed_work *work = to_delayed_work(w); |
| struct max17042_chip *chip = container_of(work, |
| struct max17042_chip, temp_worker); |
| int temp; |
| read_batt_pack_temp(chip, &temp, BYTE_VALUE); |
| schedule_delayed_work(&chip->temp_worker, TEMP_WRITE_INTERVAL); |
| } |
| |
| /* Set the SOC threshold interrupt to offset percentage in S0 state */ |
| static void set_soc_intr_thresholds_s0(struct max17042_chip *chip, int offset) |
| { |
| u16 soc_tr; |
| int soc, ret; |
| |
| /* program interrupt thesholds such that we should |
| * get interrupt for every 'offset' perc change in the soc |
| */ |
| ret = max17042_read_reg(chip->client, MAX17042_RepSOC); |
| if (ret < 0) { |
| dev_err(&chip->client->dev, |
| "maxim RepSOC read failed:%d\n", ret); |
| return ; |
| } |
| soc = ret >> 8; |
| |
| /* if upper threshold exceeds 100% then stop |
| * the interrupt for upper thresholds */ |
| if ((soc + offset) > 100) |
| soc_tr = 0xff << 8; |
| else |
| soc_tr = (soc + offset) << 8; |
| |
| /* if lower threshold falls |
| * below 1% limit it to 1% */ |
| if ((soc - offset) < 1) |
| soc_tr |= 1; |
| else |
| soc_tr |= soc; |
| |
| dev_info(&chip->client->dev, |
| "soc perc: soc: %d, offset: %d\n", soc, offset); |
| ret = max17042_write_reg(chip->client, MAX17042_SALRT_Th, soc_tr); |
| if (ret < 0) |
| dev_err(&chip->client->dev, |
| "SOC threshold write to maxim fail:%d", ret); |
| } |
| |
| static void set_soc_intr_thresholds_s3(struct max17042_chip *chip) |
| { |
| int ret, val, soc; |
| |
| if (chip->pdata->enable_current_sense) |
| ret = max17042_read_reg(chip->client, MAX17042_RepSOC); |
| else |
| ret = max17042_read_reg(chip->client, MAX17042_VFSOC); |
| if (ret < 0) { |
| dev_err(&chip->client->dev, |
| "maxim RepSOC read failed:%d\n", ret); |
| return ; |
| } |
| val = ret; |
| soc = val >> 8; |
| /* Check if MSB of lower byte is set |
| * then round off the SOC to higher digit |
| */ |
| if (val & 0x80) |
| soc += 1; |
| |
| /* If soc > 15% set the alert threshold to 15% |
| * else if soc > 4% set the threshold to 4% |
| * else set it to 1% |
| */ |
| if (soc > SOC_WARNING_LEVEL1) |
| val = SOC_DEF_MAX_MIN1_THRLD; |
| else if (soc > SOC_WARNING_LEVEL2) |
| val = SOC_DEF_MAX_MIN2_THRLD; |
| else if (soc > SOC_WARNING_LEVEL3) |
| val = SOC_DEF_MAX_MIN3_THRLD; |
| else |
| val = SOC_DEF_MAX_MIN4_THRLD; |
| |
| max17042_write_reg(chip->client, MAX17042_SALRT_Th, val); |
| } |
| |
| static int max17042_get_batt_health(void) |
| { |
| struct max17042_chip *chip = i2c_get_clientdata(max17042_client); |
| int vavg, temp, ret; |
| int stat; |
| |
| if (!chip->pdata->valid_battery) { |
| dev_err(&chip->client->dev, "Invalid battery detected"); |
| return POWER_SUPPLY_HEALTH_UNKNOWN; |
| } |
| |
| ret = read_batt_pack_temp(chip, &temp, BYTE_VALUE); |
| if (ret < 0) { |
| dev_err(&chip->client->dev, |
| "battery pack temp read fail:%d", ret); |
| return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
| } |
| if (temp >= chip->pdata->temp_max_lim) { |
| dev_info(&chip->client->dev, |
| "Battery Over Temp condition Detected:%d\n", temp); |
| return POWER_SUPPLY_HEALTH_OVERHEAT; |
| } |
| if (temp <= chip->pdata->temp_min_lim) { |
| dev_info(&chip->client->dev, |
| "Battery Under Temp condition Detected:%d\n", temp); |
| return POWER_SUPPLY_HEALTH_COLD; |
| } |
| |
| stat = max17042_read_reg(chip->client, MAX17042_STATUS); |
| if (stat < 0) { |
| dev_err(&chip->client->dev, "error reading status register"); |
| return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
| } |
| |
| ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL); |
| if (ret < 0) { |
| dev_err(&chip->client->dev, "Vavg read fail:%d", ret); |
| return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; |
| } |
| /* get the voltage to milli volts */ |
| vavg = ((ret >> 3) * MAX17042_VOLT_CONV_FCTR) / 1000; |
| if (vavg < chip->pdata->volt_min_lim) { |
| dev_info(&chip->client->dev, |
| "Low Battery condition Detected:%d\n", vavg); |
| return POWER_SUPPLY_HEALTH_DEAD; |
| } |
| if (vavg > chip->pdata->volt_max_lim + VBATT_MAX_OFFSET) { |
| dev_info(&chip->client->dev, |
| "Battery Over Voltage condition Detected:%d\n", vavg); |
| return POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| } |
| if (stat & STATUS_VMX_BIT) { |
| dev_info(&chip->client->dev, |
| "Battery Over Voltage condition Detected:%d\n", vavg); |
| return POWER_SUPPLY_HEALTH_OVERVOLTAGE; |
| } |
| |
| return POWER_SUPPLY_HEALTH_GOOD; |
| } |
| |
| static void max17042_evt_worker(struct work_struct *work) |
| { |
| struct max17042_chip *chip = container_of(work, |
| struct max17042_chip, evt_worker); |
| int status = 0, health; |
| |
| pm_runtime_get_sync(&chip->client->dev); |
| |
| /* get the battery status */ |
| if (chip->pdata->battery_status) |
| status = chip->pdata->battery_status(); |
| |
| /* get the battery health */ |
| if (chip->pdata->battery_health) |
| health = chip->pdata->battery_health(); |
| else |
| health = max17042_get_batt_health(); |
| |
| mutex_lock(&chip->batt_lock); |
| if (chip->pdata->battery_status) |
| chip->status = status; |
| chip->health = health; |
| mutex_unlock(&chip->batt_lock); |
| |
| /* Init maxim chip if it is not already initialized */ |
| if (!chip->pdata->is_init_done && |
| !chip->pdata->file_sys_storage_enabled) |
| schedule_work(&chip->init_worker); |
| |
| power_supply_changed(&chip->battery); |
| /* If charging is stopped and there is a sudden drop in SOC below |
| * minimum threshold currently set, we'll not get further interrupts. |
| * This call to set thresholds, will take care of this scenario. |
| */ |
| if (chip->pdata->soc_intr_mode_enabled) |
| set_soc_intr_thresholds_s0(chip, SOC_INTR_S0_THR); |
| pm_runtime_put_sync(&chip->client->dev); |
| } |
| |
| static void max17042_external_power_changed(struct power_supply *psy) |
| { |
| struct max17042_chip *chip = container_of(psy, |
| struct max17042_chip, battery); |
| schedule_work(&chip->evt_worker); |
| } |
| |
| static bool is_battery_online(struct max17042_chip *chip) |
| { |
| int val; |
| bool online = false; |
| |
| val = max17042_read_reg(chip->client, MAX17042_STATUS); |
| if (val < 0) { |
| dev_info(&chip->client->dev, "i2c read error\n"); |
| return online; |
| } |
| |
| /* check battery present bit */ |
| if (val & STATUS_BST_BIT) |
| online = false; |
| else |
| online = true; |
| |
| return online; |
| } |
| |
| static void init_battery_props(struct max17042_chip *chip) |
| { |
| chip->present = 1; |
| chip->ext_set_cap = -EINVAL; |
| chip->status = POWER_SUPPLY_STATUS_UNKNOWN; |
| chip->health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| chip->technology = chip->pdata->technology; |
| chip->charge_full_des = BATT_CHRG_FULL_DES; |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| /** |
| * max17042_show - debugfs: show the state of an endpoint. |
| * @seq: The seq_file to write data to. |
| * @unused: not used |
| * |
| * This debugfs entry shows the content of the register |
| * given in the data parameter. |
| */ |
| static int max17042_show(struct seq_file *seq, void *unused) |
| { |
| u16 val; |
| long addr; |
| |
| if (kstrtol((char *)seq->private, 16, &addr)) |
| return -EINVAL; |
| |
| val = max17042_read_reg(max17042_client, addr); |
| seq_printf(seq, "%x\n", val); |
| |
| return 0; |
| } |
| |
| static int max17042_show_all(struct seq_file *seq, void *unused) |
| { |
| u16 val; |
| long addr; |
| |
| for (addr = 0; addr < MAX17042_MAX_MEM; addr++) { |
| val = max17042_read_reg(max17042_client, addr); |
| seq_printf(seq, "%x:%x\n", addr, val); |
| } |
| |
| return 0; |
| } |
| |
| static int max17042_dbgfs_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, max17042_show, inode->i_private); |
| } |
| |
| static int max17042_dbgfs_open_all(struct inode *inode, struct file *file) |
| { |
| return single_open(file, max17042_show_all, inode->i_private); |
| } |
| |
| static const struct file_operations max17042_dbgfs_fops = { |
| .owner = THIS_MODULE, |
| .open = max17042_dbgfs_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static const struct file_operations max17042_dbgfs_all_fops = { |
| .owner = THIS_MODULE, |
| .open = max17042_dbgfs_open_all, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static void max17042_create_debugfs(struct max17042_chip *chip) |
| { |
| int i; |
| struct dentry *entry; |
| |
| max17042_dbgfs_root = debugfs_create_dir(DRV_NAME, NULL); |
| if (IS_ERR(max17042_dbgfs_root)) { |
| dev_warn(&chip->client->dev, "DEBUGFS DIR create failed\n"); |
| return ; |
| } |
| |
| for (i = 0; i < MAX17042_MAX_MEM; i++) { |
| sprintf((char *)&max17042_dbg_regs[i], "%x", i); |
| entry = debugfs_create_file( |
| (const char *)&max17042_dbg_regs[i], |
| S_IRUGO, |
| max17042_dbgfs_root, |
| &max17042_dbg_regs[i], |
| &max17042_dbgfs_fops); |
| if (IS_ERR(entry)) { |
| debugfs_remove_recursive(max17042_dbgfs_root); |
| max17042_dbgfs_root = NULL; |
| dev_warn(&chip->client->dev, |
| "DEBUGFS entry Create failed\n"); |
| return ; |
| } |
| } |
| entry = debugfs_create_file( |
| "show_all", |
| S_IRUGO, |
| max17042_dbgfs_root, |
| NULL, |
| &max17042_dbgfs_all_fops); |
| } |
| static inline void max17042_remove_debugfs(struct max17042_chip *chip) |
| { |
| if (max17042_dbgfs_root) |
| debugfs_remove_recursive(max17042_dbgfs_root); |
| } |
| #else |
| static inline void max17042_create_debugfs(struct max17042_chip *chip) |
| { |
| } |
| static inline void max17042_remove_debugfs(struct max17042_chip *chip) |
| { |
| } |
| #endif |
| /** |
| * override_shutdown_methods - sysfs to set disable_shdwn_methods |
| * Parameter as define by sysfs interface |
| * Context: can sleep |
| * |
| */ |
| static ssize_t override_shutdown_methods(struct device *dev, |
| struct device_attribute *attr, const char *buf, |
| size_t count) |
| { |
| struct max17042_chip *chip = dev_get_drvdata(dev); |
| unsigned long value; |
| |
| if (kstrtoul(buf, 10, &value)) |
| return -EINVAL; |
| |
| if (value > (SHUTDOWN_DEF_FG_MASK_BIT | |
| SHUTDOWN_OCV_MASK_BIT | |
| SHUTDOWN_LOWBATT_MASK_BIT)) |
| return -EINVAL; |
| |
| chip->disable_shdwn_methods = value; |
| return count; |
| } |
| |
| /** |
| * get_shutdown_methods - sysfs get disable_shdwn_methods |
| * Parameter as define by sysfs interface |
| * Context: can sleep |
| * |
| */ |
| static ssize_t get_shutdown_methods(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct max17042_chip *chip = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%d\n", chip->disable_shdwn_methods); |
| } |
| |
| /** |
| * get_shutdown_voltage_set_by_user - get function for sysfs shutdown_voltage |
| * Parameters as defined by sysfs interface |
| */ |
| static ssize_t get_shutdown_voltage_set_by_user(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%d\n", shutdown_volt); |
| } |
| |
| /** |
| * set_shutdown_voltage - set function for sysfs shutdown_voltage |
| * Parameters as defined by sysfs interface |
| * shutdown_volt can take the values between 3.4V to 4.2V |
| */ |
| static ssize_t set_shutdown_voltage(struct device *dev, |
| struct device_attribute *attr, const char *buf, |
| size_t count) |
| { |
| unsigned long value; |
| if (kstrtoul(buf, 10, &value)) |
| return -EINVAL; |
| if ((value < VBATT_MIN) || (value > VBATT_MAX)) |
| return -EINVAL; |
| shutdown_volt = value; |
| return count; |
| } |
| |
| /** |
| * get_shutdown_mode_status - get function for sysfs shutdown_voltage |
| * Parameters as defined by sysfs interface |
| */ |
| static ssize_t get_shutdown_mode_status(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%d\n", shutdown_mode_status); |
| } |
| |
| /** |
| * set_shutdown_mode_status - set function for sysfs shutdown_voltage |
| * Parameters as defined by sysfs interface |
| * shutdown_mode_status can take the values 0 and 1 |
| */ |
| static ssize_t set_shutdown_mode_status(struct device *dev, |
| struct device_attribute *attr, const char *buf, |
| size_t count) |
| { |
| unsigned long value; |
| if (kstrtoul(buf, 10, &value)) |
| return -EINVAL; |
| |
| /* allow only 0 or 1 */ |
| if (value != 1 && value !=0) |
| return -EINVAL; |
| |
| if (value) |
| { |
| shutdown_mode_status = true; |
| enable_shutdown_mode(); |
| } |
| else |
| shutdown_mode_status = false; |
| |
| return count; |
| } |
| |
| /** |
| * set_fake_temp_enable - sysfs to set enable_fake_temp |
| * Parameter as define by sysfs interface |
| */ |
| static ssize_t set_fake_temp_enable(struct device *dev, |
| struct device_attribute *attr, const char *buf, |
| size_t count) |
| { |
| struct max17042_chip *chip = dev_get_drvdata(dev); |
| unsigned long value; |
| |
| if (kstrtoul(buf, 10, &value)) |
| return -EINVAL; |
| |
| /* allow only 0 or 1 */ |
| if (value > 1) |
| return -EINVAL; |
| |
| if (value) |
| chip->enable_fake_temp = true; |
| else |
| chip->enable_fake_temp = false; |
| |
| return count; |
| } |
| |
| /** |
| * get_fake_temp_enable - sysfs get enable_fake_temp |
| * Parameter as define by sysfs interface |
| * Context: can sleep |
| */ |
| static ssize_t get_fake_temp_enable(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct max17042_chip *chip = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%d\n", chip->enable_fake_temp); |
| } |
| |
| static void configure_interrupts(struct max17042_chip *chip) |
| { |
| int ret; |
| unsigned int edge_type; |
| int vmax, vmin, reg_val; |
| |
| /* set SOC-alert threshold sholds to lowest value */ |
| max17042_write_reg(chip->client, MAX17042_SALRT_Th, |
| SOC_DEF_MAX_MIN4_THRLD); |
| |
| /* enable Alerts for SOCRep */ |
| if (chip->pdata->enable_current_sense) |
| max17042_write_reg(chip->client, MAX17042_MiscCFG, |
| MISCCFG_CONFIG_REPSOC); |
| else |
| max17042_write_reg(chip->client, MAX17042_MiscCFG, |
| MISCCFG_CONFIG_VFSOC); |
| |
| /* disable the T-alert sticky bit */ |
| max17042_reg_read_modify(chip->client, MAX17042_CONFIG, |
| CONFIG_TSTICKY_BIT_SET, 0); |
| |
| /* Setting V-alrt threshold register to default values */ |
| if (chip->pdata->en_vmax_intr) { |
| vmax = chip->pdata->volt_max_lim + VBATT_MAX_OFFSET; |
| vmin = chip->pdata->volt_min_lim - VBATT_MIN_OFFSET; |
| reg_val = ((vmax / VALERT_VOLT_OFFSET) << 8) | |
| (vmin / VALERT_VOLT_OFFSET); |
| max17042_write_reg(chip->client, MAX17042_VALRT_Th, reg_val); |
| } else { |
| max17042_write_reg(chip->client, MAX17042_VALRT_Th, |
| VOLT_DEF_MAX_MIN_THRLD); |
| } |
| |
| /* Setting T-alrt threshold register to default values */ |
| max17042_write_reg(chip->client, MAX17042_TALRT_Th, |
| TEMP_DEF_MAX_MIN_THRLD); |
| |
| /* clear BI bit */ |
| max17042_reg_read_modify(chip->client, MAX17042_STATUS, |
| STATUS_BI_BIT, 0); |
| /* clear BR bit */ |
| max17042_reg_read_modify(chip->client, MAX17042_STATUS, |
| STATUS_BR_BIT, 0); |
| |
| /* get interrupt edge type from ALP pin */ |
| if (fg_conf_data->cfg & CONFIG_ALP_BIT_ENBL) |
| edge_type = IRQF_TRIGGER_RISING; |
| else |
| edge_type = IRQF_TRIGGER_FALLING; |
| |
| /* register interrupt */ |
| ret = request_threaded_irq(chip->client->irq, |
| max17042_intr_handler, |
| max17042_thread_handler, |
| edge_type, |
| DRV_NAME, chip); |
| if (ret) { |
| dev_warn(&chip->client->dev, |
| "cannot get IRQ:%d\n", chip->client->irq); |
| chip->client->irq = -1; |
| } else { |
| dev_info(&chip->client->dev, "IRQ No:%d\n", chip->client->irq); |
| } |
| |
| /* enable interrupts */ |
| max17042_reg_read_modify(chip->client, MAX17042_CONFIG, |
| CONFIG_ALRT_BIT_ENBL, 1); |
| |
| /* set the Interrupt threshold register for soc */ |
| if (chip->pdata->soc_intr_mode_enabled) |
| set_soc_intr_thresholds_s0(chip, SOC_INTR_S0_THR); |
| |
| /* |
| * recheckthe battery present status to |
| * make sure we didn't miss any battery |
| * removal event and power off if battery |
| * is removed/unplugged. |
| */ |
| if ((fg_conf_data->cfg & CONFIG_BER_BIT_ENBL) && |
| !is_battery_online(chip)) { |
| dev_warn(&chip->client->dev, "battery NOT present\n"); |
| mutex_lock(&chip->batt_lock); |
| chip->present = 0; |
| mutex_unlock(&chip->batt_lock); |
| kernel_power_off(); |
| } |
| } |
| |
| #ifdef CONFIG_ACPI |
| extern void *max17042_platform_data(void *info); |
| #endif |
| 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, i, gpio; |
| struct acpi_gpio_info gpio_info; |
| |
| #ifdef CONFIG_XEN |
| return -ENODEV; |
| #endif |
| |
| #ifdef CONFIG_ACPI |
| client->dev.platform_data = max17042_platform_data(NULL); |
| gpio = acpi_get_gpio_by_index(&client->dev, 0, &gpio_info); |
| client->irq = gpio_to_irq(gpio); |
| ret = gpio_request_one(gpio, GPIOF_IN, client->name); |
| if (ret < 0) { |
| dev_warn(&client->dev, "gpio request failed."); |
| return -EIO; |
| } |
| #endif |
| if (!client->dev.platform_data) { |
| dev_err(&client->dev, "Platform Data is NULL"); |
| return -EFAULT; |
| } |
| |
| if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { |
| dev_err(&client->dev, |
| "SM bus doesn't support DWORD transactions\n"); |
| return -EIO; |
| } |
| |
| chip = kzalloc(sizeof(*chip), GFP_KERNEL); |
| if (!chip) { |
| dev_err(&client->dev, "mem alloc failed\n"); |
| return -ENOMEM; |
| } |
| |
| fg_conf_data = kzalloc(sizeof(*fg_conf_data), GFP_KERNEL); |
| if (!fg_conf_data) { |
| dev_err(&client->dev, "mem alloc failed\n"); |
| kfree(chip); |
| return -ENOMEM; |
| } |
| chip->client = client; |
| chip->pdata = client->dev.platform_data; |
| /* LSB offset for qrtbl registers is 0.25% |
| * ie, 0x04 = 1% reserved capacity |
| */ |
| chip->extra_resv_cap = 4 * chip->pdata->resv_cap; |
| |
| if (chip->pdata->get_vmax_threshold) |
| chip->voltage_max = chip->pdata->get_vmax_threshold(); |
| else |
| chip->voltage_max = VBATT_MAX; |
| |
| if (chip->pdata->fg_algo_model) |
| chip->model_algo_factor = chip->pdata->fg_algo_model; |
| else |
| chip->model_algo_factor = 100; |
| |
| i2c_set_clientdata(client, chip); |
| max17042_client = client; |
| |
| ret = max17042_read_reg(chip->client, MAX17042_DevName); |
| if (ret < 0 && chip->pdata->reset_i2c_lines) { |
| dev_warn(&client->dev, "reset i2c device:%d\n", ret); |
| for (i = 0; i < NR_I2C_RESET_CNT; i++) { |
| chip->pdata->reset_i2c_lines(); |
| ret = max17042_read_reg(chip->client, MAX17042_DevName); |
| if (ret < 0) |
| dev_warn(&client->dev, |
| "reset i2c device:%d\n", ret); |
| else |
| break; |
| } |
| } |
| |
| if (ret == MAX17042_IC_VERSION) { |
| dev_info(&client->dev, "chip type max17042 detected\n"); |
| chip->chip_type = MAX17042; |
| } else if (ret == MAX17050_IC_VERSION) { |
| dev_info(&client->dev, "chip type max17047/50 detected\n"); |
| chip->chip_type = MAX17050; |
| } else { |
| dev_err(&client->dev, "device version mismatch: %x\n", ret); |
| kfree(chip); |
| kfree(fg_conf_data); |
| return -EIO; |
| } |
| |
| /* init battery properties */ |
| init_battery_props(chip); |
| INIT_WORK(&chip->init_worker, max17042_init_worker); |
| INIT_WORK(&chip->evt_worker, max17042_evt_worker); |
| INIT_DEFERRABLE_WORK(&chip->temp_worker, max17042_temp_worker); |
| |
| mutex_init(&chip->batt_lock); |
| mutex_init(&chip->init_lock); |
| |
| /* disable the Alert pin before setting thresholds */ |
| max17042_reg_read_modify(client, MAX17042_CONFIG, |
| CONFIG_ALRT_BIT_ENBL, 0); |
| |
| if (chip->pdata->enable_current_sense) { |
| dev_info(&chip->client->dev, "current sensing enabled\n"); |
| /* Initialize the chip with battery config data */ |
| max17042_restore_conf_data(chip); |
| } else { |
| |
| dev_info(&chip->client->dev, "current sensing NOT enabled\n"); |
| /* incase of invalid battery no need to init the FG chip */ |
| chip->pdata->is_init_done = 1; |
| /* disable coulomb counter based fuel gauging */ |
| max17042_write_reg(chip->client, MAX17042_CGAIN, |
| MAX17042_CGAIN_DISABLE); |
| /* Enable voltage based Fuel Gauging */ |
| max17042_write_reg(chip->client, MAX17042_LearnCFG, |
| MAX17042_EN_VOLT_FG); |
| /* configure interrupts for SOCvf */ |
| max17042_write_reg(chip->client, MAX17042_MiscCFG, |
| MAX17042_CFG_INTR_SOCVF); |
| } |
| |
| chip->technology = chip->pdata->technology; |
| |
| if (chip->chip_type == MAX17042) |
| chip->battery.name = "max17042_battery"; |
| else |
| chip->battery.name = "max17047_battery"; |
| chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; |
| chip->battery.get_property = max17042_get_property; |
| chip->battery.set_property = max17042_set_property; |
| chip->battery.property_is_privileged_read = |
| max17042_property_is_privileged_read; |
| chip->battery.external_power_changed = max17042_external_power_changed; |
| chip->battery.properties = max17042_battery_props; |
| chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props); |
| |
| chip->status = POWER_SUPPLY_STATUS_DISCHARGING; |
| |
| ret = power_supply_register(&client->dev, &chip->battery); |
| if (ret) { |
| dev_err(&client->dev, "failed: power supply register\n"); |
| kfree(chip); |
| kfree(fg_conf_data); |
| return ret; |
| } |
| |
| /* Init Runtime PM State */ |
| pm_runtime_put_noidle(&chip->client->dev); |
| pm_schedule_suspend(&chip->client->dev, MSEC_PER_SEC); |
| |
| /* In case of power supply register INT now |
| * else the INT will registered after chip init. |
| */ |
| if (!chip->pdata->enable_current_sense) |
| configure_interrupts(chip); |
| |
| if (chip->pdata->file_sys_storage_enabled) |
| misc_register(&fg_helper); |
| |
| /* Create debugfs for maxim registers */ |
| max17042_create_debugfs(chip); |
| |
| /* create sysfs file to disable shutdown methods */ |
| ret = device_create_file(&client->dev, |
| &dev_attr_disable_shutdown_methods); |
| if (ret) |
| dev_warn(&client->dev, "cannot create sysfs entry\n"); |
| |
| /* create sysfs file to enter shutdown voltage */ |
| ret = device_create_file(&client->dev, |
| &dev_attr_shutdown_voltage); |
| if (ret) |
| dev_warn(&client->dev, "cannot create sysfs entry\n"); |
| |
| /* create sysfs file to enable fake battery temperature */ |
| ret = device_create_file(&client->dev, |
| &dev_attr_enable_fake_temp); |
| if (ret) |
| dev_warn(&client->dev, "cannot create sysfs entry\n"); |
| |
| /* create sysfs file to enable shutdown mode */ |
| ret = device_create_file(&client->dev, |
| &dev_attr_shutdown_mode); |
| if (ret) |
| dev_warn(&client->dev, "cannot create sysfs entry\n"); |
| |
| /* Register reboot notifier callback */ |
| if (!chip->pdata->file_sys_storage_enabled) |
| register_reboot_notifier(&max17042_reboot_notifier_block); |
| schedule_work(&chip->evt_worker); |
| |
| pm_runtime_enable(&chip->client->dev); |
| |
| return 0; |
| } |
| |
| static int max17042_remove(struct i2c_client *client) |
| { |
| struct max17042_chip *chip = i2c_get_clientdata(client); |
| |
| pm_runtime_disable(&chip->client->dev); |
| |
| if (chip->pdata->file_sys_storage_enabled) |
| misc_deregister(&fg_helper); |
| else |
| unregister_reboot_notifier(&max17042_reboot_notifier_block); |
| |
| device_remove_file(&client->dev, &dev_attr_disable_shutdown_methods); |
| device_remove_file(&client->dev, &dev_attr_shutdown_voltage); |
| device_remove_file(&client->dev, &dev_attr_shutdown_mode); |
| device_remove_file(&client->dev, &dev_attr_enable_fake_temp); |
| max17042_remove_debugfs(chip); |
| if (client->irq > 0) |
| free_irq(client->irq, chip); |
| power_supply_unregister(&chip->battery); |
| pm_runtime_get_noresume(&chip->client->dev); |
| |
| kfree(chip); |
| kfree(fg_conf_data); |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int max17042_suspend(struct device *dev) |
| { |
| struct max17042_chip *chip = dev_get_drvdata(dev); |
| |
| /* |
| * disable irq here doesn't mean max17042 interrupt |
| * can't wake up system. max17042 interrupt is triggered |
| * by GPIO pin, which is always active. |
| * When resume callback calls enable_irq, kernel |
| * would deliver the buffered interrupt (if it has) to |
| * driver. |
| */ |
| if (chip->client->irq > 0) { |
| /* set SOC alert thresholds */ |
| set_soc_intr_thresholds_s3(chip); |
| /* setting Vmin(3300mV) threshold to wake the |
| * platfrom in under low battery conditions */ |
| max17042_write_reg(chip->client, MAX17042_VALRT_Th, |
| VOLT_MIN_THRLD_ENBL); |
| disable_irq(chip->client->irq); |
| enable_irq_wake(chip->client->irq); |
| } |
| if (fg_conf_data->cfg & CONFIG_TEX_BIT_ENBL) |
| cancel_delayed_work_sync(&chip->temp_worker); |
| |
| /* max17042 IC automatically goes into shutdown mode |
| * if the SCL and SDA were held low for more than |
| * timeout of SHDNTIMER register value |
| */ |
| dev_dbg(&chip->client->dev, "max17042 suspend\n"); |
| |
| return 0; |
| } |
| |
| static int max17042_resume(struct device *dev) |
| { |
| struct max17042_chip *chip = dev_get_drvdata(dev); |
| int vmax, vmin, reg_val; |
| |
| if (chip->client->irq > 0) { |
| /* Setting V-alrt threshold register to default values */ |
| if (chip->pdata->en_vmax_intr) { |
| vmax = chip->pdata->volt_max_lim + |
| VBATT_MAX_OFFSET; |
| vmin = chip->pdata->volt_min_lim - |
| VBATT_MIN_OFFSET; |
| reg_val = ((vmax / VALERT_VOLT_OFFSET) << 8) | |
| (vmin / VALERT_VOLT_OFFSET); |
| max17042_write_reg(chip->client, MAX17042_VALRT_Th, |
| reg_val); |
| } else { |
| max17042_write_reg(chip->client, MAX17042_VALRT_Th, |
| VOLT_DEF_MAX_MIN_THRLD); |
| } |
| /* set SOC-alert threshold sholds to lowest value */ |
| max17042_write_reg(chip->client, MAX17042_SALRT_Th, |
| SOC_DEF_MAX_MIN4_THRLD); |
| enable_irq(chip->client->irq); |
| disable_irq_wake(chip->client->irq); |
| } |
| /* update battery status and health */ |
| schedule_work(&chip->evt_worker); |
| if (fg_conf_data->cfg & CONFIG_TEX_BIT_ENBL) |
| schedule_delayed_work(&chip->temp_worker, 0); |
| |
| /* max17042 IC automatically wakes up if any edge |
| * on SDCl or SDA if we set I2CSH of CONFG reg |
| */ |
| dev_dbg(&chip->client->dev, "max17042 resume\n"); |
| |
| return 0; |
| } |
| #else |
| #define max17042_suspend NULL |
| #define max17042_resume NULL |
| #endif |
| |
| #ifdef CONFIG_PM_RUNTIME |
| static int max17042_runtime_suspend(struct device *dev) |
| { |
| |
| dev_dbg(dev, "%s called\n", __func__); |
| return 0; |
| } |
| |
| static int max17042_runtime_resume(struct device *dev) |
| { |
| dev_dbg(dev, "%s called\n", __func__); |
| return 0; |
| } |
| |
| static int max17042_runtime_idle(struct device *dev) |
| { |
| |
| dev_dbg(dev, "%s called\n", __func__); |
| return 0; |
| } |
| #else |
| #define max17042_runtime_suspend NULL |
| #define max17042_runtime_resume NULL |
| #define max17042_runtime_idle NULL |
| #endif |
| |
| static const struct i2c_device_id max17042_id[] = { |
| { "max17042", 0 }, |
| { "max17047", 1 }, |
| { "max17050", 2 }, |
| { "MAX17042", 0 }, |
| { "MAX17047", 1 }, |
| { "MAX17050", 2 }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(i2c, max17042_id); |
| |
| static const struct dev_pm_ops max17042_pm_ops = { |
| .suspend = max17042_suspend, |
| .resume = max17042_resume, |
| .runtime_suspend = max17042_runtime_suspend, |
| .runtime_resume = max17042_runtime_resume, |
| .runtime_idle = max17042_runtime_idle, |
| }; |
| |
| #ifdef CONFIG_ACPI |
| static struct acpi_device_id max17042_acpi_match[] = { |
| {"MAX17047", 0}, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(acpi, max17042_acpi_match); |
| |
| #endif |
| |
| static struct i2c_driver max17042_i2c_driver = { |
| .driver = { |
| .name = DRV_NAME, |
| .owner = THIS_MODULE, |
| .pm = &max17042_pm_ops, |
| #ifdef CONFIG_ACPI |
| .acpi_match_table = ACPI_PTR(max17042_acpi_match), |
| #endif |
| }, |
| .probe = max17042_probe, |
| .remove = max17042_remove, |
| .id_table = max17042_id, |
| }; |
| |
| static int max17042_reboot_callback(struct notifier_block *nfb, |
| unsigned long event, void *data) |
| { |
| struct max17042_chip *chip = i2c_get_clientdata(max17042_client); |
| |
| if (chip->pdata->enable_current_sense) |
| save_runtime_params(chip); |
| |
| /* if the shutdown or reboot sequence started |
| * then block the access to maxim registers as chip |
| * cannot be recovered from broken i2c transactions |
| */ |
| mutex_lock(&chip->batt_lock); |
| chip->plat_rebooting = true; |
| mutex_unlock(&chip->batt_lock); |
| |
| return NOTIFY_OK; |
| } |
| |
| static int __init max17042_init(void) |
| { |
| return i2c_add_driver(&max17042_i2c_driver); |
| } |
| #ifdef CONFIG_ACPI |
| late_initcall(max17042_init); |
| #else |
| module_init(max17042_init); |
| #endif |
| |
| static void __exit max17042_exit(void) |
| { |
| i2c_del_driver(&max17042_i2c_driver); |
| } |
| module_exit(max17042_exit); |
| |
| int __init set_fake_batt_full(char *p) |
| { |
| fake_batt_full = true; |
| return 0; |
| } |
| |
| early_param("fake_batt_full", set_fake_batt_full); |
| |
| MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); |
| MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); |
| MODULE_LICENSE("GPL"); |