diff options
Diffstat (limited to 'drivers/power/supply/sc27xx_fuel_gauge.c')
-rw-r--r-- | drivers/power/supply/sc27xx_fuel_gauge.c | 223 |
1 files changed, 148 insertions, 75 deletions
diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c index 24895cc3b41e..bc8f5bda5762 100644 --- a/drivers/power/supply/sc27xx_fuel_gauge.c +++ b/drivers/power/supply/sc27xx_fuel_gauge.c @@ -109,6 +109,9 @@ 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 void sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap); static const char * const sc27xx_charger_supply_name[] = { "sc2731_charger", @@ -326,8 +329,6 @@ static int sc27xx_fgu_set_clbcnt(struct sc27xx_fgu_data *data, int clbcnt) { int ret; - clbcnt *= SC27XX_FGU_SAMPLE_HZ; - ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_CLBCNT_SETL, SC27XX_FGU_CLBCNT_MASK, clbcnt); @@ -362,7 +363,6 @@ static int sc27xx_fgu_get_clbcnt(struct sc27xx_fgu_data *data, int *clb_cnt) *clb_cnt = ccl & SC27XX_FGU_CLBCNT_MASK; *clb_cnt |= (cch & SC27XX_FGU_CLBCNT_MASK) << SC27XX_FGU_CLBCNT_SHIFT; - *clb_cnt /= SC27XX_FGU_SAMPLE_HZ; return 0; } @@ -380,10 +380,10 @@ static int sc27xx_fgu_get_capacity(struct sc27xx_fgu_data *data, int *cap) /* * Convert coulomb counter to delta capacity (mAh), and set multiplier - * as 100 to improve the precision. + * as 10 to improve the precision. */ - temp = DIV_ROUND_CLOSEST(delta_clbcnt, 360); - temp = sc27xx_fgu_adc_to_current(data, temp); + temp = DIV_ROUND_CLOSEST(delta_clbcnt * 10, 36 * SC27XX_FGU_SAMPLE_HZ); + temp = sc27xx_fgu_adc_to_current(data, temp / 1000); /* * Convert to capacity percent of the battery total capacity, @@ -392,6 +392,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; } @@ -587,6 +590,10 @@ static int sc27xx_fgu_get_property(struct power_supply *psy, val->intval = value * 1000; break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = data->total_cap * 1000; + break; + default: ret = -EINVAL; break; @@ -604,17 +611,25 @@ static int sc27xx_fgu_set_property(struct power_supply *psy, struct sc27xx_fgu_data *data = power_supply_get_drvdata(psy); int ret; - if (psp != POWER_SUPPLY_PROP_CAPACITY) - return -EINVAL; - mutex_lock(&data->lock); - ret = sc27xx_fgu_save_last_cap(data, val->intval); + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + ret = sc27xx_fgu_save_last_cap(data, val->intval); + if (ret < 0) + dev_err(data->dev, "failed to save battery capacity\n"); + break; - mutex_unlock(&data->lock); + case POWER_SUPPLY_PROP_CALIBRATE: + sc27xx_fgu_adjust_cap(data, val->intval); + ret = 0; + break; - if (ret < 0) - dev_err(data->dev, "failed to save battery capacity\n"); + default: + ret = -EINVAL; + } + + mutex_unlock(&data->lock); return ret; } @@ -629,7 +644,8 @@ static void sc27xx_fgu_external_power_changed(struct power_supply *psy) static int sc27xx_fgu_property_is_writeable(struct power_supply *psy, enum power_supply_property psp) { - return psp == POWER_SUPPLY_PROP_CAPACITY; + return psp == POWER_SUPPLY_PROP_CAPACITY || + psp == POWER_SUPPLY_PROP_CALIBRATE; } static enum power_supply_property sc27xx_fgu_props[] = { @@ -644,6 +660,8 @@ static enum power_supply_property sc27xx_fgu_props[] = { POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_CALIBRATE, }; static const struct power_supply_desc sc27xx_fgu_desc = { @@ -659,50 +677,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; + int ret, ocv, chg_sts, adc; - mutex_lock(&data->lock); - - 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; - - ret = sc27xx_fgu_get_capacity(data, &cap); - if (ret) - goto out; + if (chg_sts == POWER_SUPPLY_STATUS_CHARGING) + return; - 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 @@ -711,7 +741,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; /* @@ -726,15 +756,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); @@ -785,7 +855,7 @@ static int sc27xx_fgu_cap_to_clbcnt(struct sc27xx_fgu_data *data, int capacity) * Convert current capacity (mAh) to coulomb counter according to the * formula: 1 mAh =3.6 coulomb. */ - return DIV_ROUND_CLOSEST(cur_cap * 36 * data->cur_1000ma_adc, 10); + return DIV_ROUND_CLOSEST(cur_cap * 36 * data->cur_1000ma_adc * SC27XX_FGU_SAMPLE_HZ, 10); } static int sc27xx_fgu_calibration(struct sc27xx_fgu_data *data) @@ -856,6 +926,8 @@ static int sc27xx_fgu_hw_init(struct sc27xx_fgu_data *data) data->alarm_cap = power_supply_ocv2cap_simple(data->cap_table, data->table_len, data->min_volt); + if (!data->alarm_cap) + data->alarm_cap += 1; power_supply_put_battery_info(data->battery, &info); @@ -957,81 +1029,81 @@ disable_fgu: static int sc27xx_fgu_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; struct power_supply_config fgu_cfg = { }; struct sc27xx_fgu_data *data; int ret, irq; - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->regmap = dev_get_regmap(pdev->dev.parent, NULL); + data->regmap = dev_get_regmap(dev->parent, NULL); if (!data->regmap) { - dev_err(&pdev->dev, "failed to get regmap\n"); + dev_err(dev, "failed to get regmap\n"); return -ENODEV; } - ret = device_property_read_u32(&pdev->dev, "reg", &data->base); + ret = device_property_read_u32(dev, "reg", &data->base); if (ret) { - dev_err(&pdev->dev, "failed to get fgu address\n"); + dev_err(dev, "failed to get fgu address\n"); return ret; } - data->channel = devm_iio_channel_get(&pdev->dev, "bat-temp"); + data->channel = devm_iio_channel_get(dev, "bat-temp"); if (IS_ERR(data->channel)) { - dev_err(&pdev->dev, "failed to get IIO channel\n"); + dev_err(dev, "failed to get IIO channel\n"); return PTR_ERR(data->channel); } - data->charge_chan = devm_iio_channel_get(&pdev->dev, "charge-vol"); + data->charge_chan = devm_iio_channel_get(dev, "charge-vol"); if (IS_ERR(data->charge_chan)) { - dev_err(&pdev->dev, "failed to get charge IIO channel\n"); + dev_err(dev, "failed to get charge IIO channel\n"); return PTR_ERR(data->charge_chan); } - data->gpiod = devm_gpiod_get(&pdev->dev, "bat-detect", GPIOD_IN); + data->gpiod = devm_gpiod_get(dev, "bat-detect", GPIOD_IN); if (IS_ERR(data->gpiod)) { - dev_err(&pdev->dev, "failed to get battery detection GPIO\n"); + dev_err(dev, "failed to get battery detection GPIO\n"); return PTR_ERR(data->gpiod); } ret = gpiod_get_value_cansleep(data->gpiod); if (ret < 0) { - dev_err(&pdev->dev, "failed to get gpio state\n"); + dev_err(dev, "failed to get gpio state\n"); return ret; } data->bat_present = !!ret; mutex_init(&data->lock); - data->dev = &pdev->dev; + data->dev = dev; platform_set_drvdata(pdev, data); fgu_cfg.drv_data = data; fgu_cfg.of_node = np; - data->battery = devm_power_supply_register(&pdev->dev, &sc27xx_fgu_desc, + data->battery = devm_power_supply_register(dev, &sc27xx_fgu_desc, &fgu_cfg); if (IS_ERR(data->battery)) { - dev_err(&pdev->dev, "failed to register power supply\n"); + dev_err(dev, "failed to register power supply\n"); return PTR_ERR(data->battery); } ret = sc27xx_fgu_hw_init(data); if (ret) { - dev_err(&pdev->dev, "failed to initialize fgu hardware\n"); + dev_err(dev, "failed to initialize fgu hardware\n"); return ret; } - ret = devm_add_action(&pdev->dev, sc27xx_fgu_disable, data); + ret = devm_add_action_or_reset(dev, sc27xx_fgu_disable, data); if (ret) { - sc27xx_fgu_disable(data); - dev_err(&pdev->dev, "failed to add fgu disable action\n"); + dev_err(dev, "failed to add fgu disable action\n"); return ret; } irq = platform_get_irq(pdev, 0); if (irq < 0) { - dev_err(&pdev->dev, "no irq resource specified\n"); + dev_err(dev, "no irq resource specified\n"); return irq; } @@ -1046,17 +1118,17 @@ static int sc27xx_fgu_probe(struct platform_device *pdev) irq = gpiod_to_irq(data->gpiod); if (irq < 0) { - dev_err(&pdev->dev, "failed to translate GPIO to IRQ\n"); + dev_err(dev, "failed to translate GPIO to IRQ\n"); return irq; } - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ret = devm_request_threaded_irq(dev, irq, NULL, sc27xx_fgu_bat_detection, IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, pdev->name, data); if (ret) { - dev_err(&pdev->dev, "failed to request IRQ\n"); + dev_err(dev, "failed to request IRQ\n"); return ret; } @@ -1093,7 +1165,8 @@ static int sc27xx_fgu_suspend(struct device *dev) * If we are charging, then no need to enable the FGU interrupts to * adjust the battery capacity. */ - if (status != POWER_SUPPLY_STATUS_NOT_CHARGING) + if (status != POWER_SUPPLY_STATUS_NOT_CHARGING && + status != POWER_SUPPLY_STATUS_DISCHARGING) return 0; ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_EN, |