diff options
Diffstat (limited to 'drivers/power/supply/sc27xx_fuel_gauge.c')
-rw-r--r-- | drivers/power/supply/sc27xx_fuel_gauge.c | 127 |
1 files changed, 92 insertions, 35 deletions
diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c index 096da951f465..6071671caaa9 100644 --- a/drivers/power/supply/sc27xx_fuel_gauge.c +++ b/drivers/power/supply/sc27xx_fuel_gauge.c @@ -109,6 +109,8 @@ struct sc27xx_fgu_data { }; static int sc27xx_fgu_cap_to_clbcnt(struct sc27xx_fgu_data *data, int capacity); +static void sc27xx_fgu_capacity_calibration(struct sc27xx_fgu_data *data, + int cap, bool int_mode); static const char * const sc27xx_charger_supply_name[] = { "sc2731_charger", @@ -389,6 +391,9 @@ static int sc27xx_fgu_get_capacity(struct sc27xx_fgu_data *data, int *cap) delta_cap = DIV_ROUND_CLOSEST(temp * 100, data->total_cap); *cap = delta_cap + data->init_cap; + /* Calibrate the battery capacity in a normal range. */ + sc27xx_fgu_capacity_calibration(data, *cap, false); + return 0; } @@ -661,50 +666,62 @@ static const struct power_supply_desc sc27xx_fgu_desc = { static void sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap) { + int ret; + data->init_cap = cap; - data->init_clbcnt = sc27xx_fgu_cap_to_clbcnt(data, data->init_cap); + ret = sc27xx_fgu_get_clbcnt(data, &data->init_clbcnt); + if (ret) + dev_err(data->dev, "failed to get init coulomb counter\n"); } -static irqreturn_t sc27xx_fgu_interrupt(int irq, void *dev_id) +static void sc27xx_fgu_capacity_calibration(struct sc27xx_fgu_data *data, + int cap, bool int_mode) { - struct sc27xx_fgu_data *data = dev_id; - int ret, cap, ocv, adc; - u32 status; - - mutex_lock(&data->lock); + int ret, ocv, chg_sts, adc; - ret = regmap_read(data->regmap, data->base + SC27XX_FGU_INT_STS, - &status); - if (ret) - goto out; + ret = sc27xx_fgu_get_vbat_ocv(data, &ocv); + if (ret) { + dev_err(data->dev, "get battery ocv error.\n"); + return; + } - ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_CLR, - status, status); - if (ret) - goto out; + ret = sc27xx_fgu_get_status(data, &chg_sts); + if (ret) { + dev_err(data->dev, "get charger status error.\n"); + return; + } /* - * When low overload voltage interrupt happens, we should calibrate the - * battery capacity in lower voltage stage. + * If we are in charging mode, then we do not need to calibrate the + * lower capacity. */ - if (!(status & SC27XX_FGU_LOW_OVERLOAD_INT)) - goto out; + if (chg_sts == POWER_SUPPLY_STATUS_CHARGING) + return; - ret = sc27xx_fgu_get_capacity(data, &cap); - if (ret) - goto out; - - ret = sc27xx_fgu_get_vbat_ocv(data, &ocv); - if (ret) - goto out; - - /* - * If current OCV value is less than the minimum OCV value in OCV table, - * which means now battery capacity is 0%, and we should adjust the - * inititial capacity to 0. - */ - if (ocv <= data->cap_table[data->table_len - 1].ocv) { + if ((ocv > data->cap_table[0].ocv && cap < 100) || cap > 100) { + /* + * If current OCV value is larger than the max OCV value in + * OCV table, or the current capacity is larger than 100, + * we should force the inititial capacity to 100. + */ + sc27xx_fgu_adjust_cap(data, 100); + } else if (ocv <= data->cap_table[data->table_len - 1].ocv) { + /* + * If current OCV value is leass than the minimum OCV value in + * OCV table, we should force the inititial capacity to 0. + */ sc27xx_fgu_adjust_cap(data, 0); + } else if ((ocv > data->cap_table[data->table_len - 1].ocv && cap <= 0) || + (ocv > data->min_volt && cap <= data->alarm_cap)) { + /* + * If current OCV value is not matchable with current capacity, + * we should re-calculate current capacity by looking up the + * OCV table. + */ + int cur_cap = power_supply_ocv2cap_simple(data->cap_table, + data->table_len, ocv); + + sc27xx_fgu_adjust_cap(data, cur_cap); } else if (ocv <= data->min_volt) { /* * If current OCV value is less than the low alarm voltage, but @@ -713,7 +730,7 @@ static irqreturn_t sc27xx_fgu_interrupt(int irq, void *dev_id) */ if (cap > data->alarm_cap) { sc27xx_fgu_adjust_cap(data, data->alarm_cap); - } else if (cap <= 0) { + } else { int cur_cap; /* @@ -728,15 +745,55 @@ static irqreturn_t sc27xx_fgu_interrupt(int irq, void *dev_id) sc27xx_fgu_adjust_cap(data, cur_cap); } + if (!int_mode) + return; + /* * After adjusting the battery capacity, we should set the * lowest alarm voltage instead. */ data->min_volt = data->cap_table[data->table_len - 1].ocv; + data->alarm_cap = power_supply_ocv2cap_simple(data->cap_table, + data->table_len, + data->min_volt); + adc = sc27xx_fgu_voltage_to_adc(data, data->min_volt / 1000); - regmap_update_bits(data->regmap, data->base + SC27XX_FGU_LOW_OVERLOAD, + regmap_update_bits(data->regmap, + data->base + SC27XX_FGU_LOW_OVERLOAD, SC27XX_FGU_LOW_OVERLOAD_MASK, adc); } +} + +static irqreturn_t sc27xx_fgu_interrupt(int irq, void *dev_id) +{ + struct sc27xx_fgu_data *data = dev_id; + int ret, cap; + u32 status; + + mutex_lock(&data->lock); + + ret = regmap_read(data->regmap, data->base + SC27XX_FGU_INT_STS, + &status); + if (ret) + goto out; + + ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_CLR, + status, status); + if (ret) + goto out; + + /* + * When low overload voltage interrupt happens, we should calibrate the + * battery capacity in lower voltage stage. + */ + if (!(status & SC27XX_FGU_LOW_OVERLOAD_INT)) + goto out; + + ret = sc27xx_fgu_get_capacity(data, &cap); + if (ret) + goto out; + + sc27xx_fgu_capacity_calibration(data, cap, true); out: mutex_unlock(&data->lock); |