summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/supply/sc27xx_fuel_gauge.c127
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);