diff options
| author | Subbaraman Narayanamurthy <subbaram@codeaurora.org> | 2016-09-14 14:48:49 -0700 |
|---|---|---|
| committer | Subbaraman Narayanamurthy <subbaram@codeaurora.org> | 2016-10-10 09:44:05 -0700 |
| commit | cdb65a95a8d4ffbc11f890ecdeb7b3a55d47f000 (patch) | |
| tree | b169bbabd94a3d804c1566efdcf5e290b6098008 | |
| parent | 0edd25653c344358c440dc8a4f97134adfe65e88 (diff) | |
power: qpnp-fg-gen3: add capacity learning feature
Batteries age across the time. Capacity learning is a feature
which uses the hardware autonomous coulomb counter to determine
the actual battery capacity against the advertised full capacity.
This will be useful to determine how much degradation has
happened to the battery. Capacity learning algorithm will start
based on some pre-set conditions like start SOC, battery
temperature and ends when the charging cycle is complete.
Actual capacity will be exposed through charge_full property.
Change-Id: I89cc14d213b0de10bc8d052dd76e1468fe9c30e5
Signed-off-by: Subbaraman Narayanamurthy <subbaram@codeaurora.org>
| -rw-r--r-- | Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt | 49 | ||||
| -rw-r--r-- | drivers/power/qcom-charger/fg-core.h | 27 | ||||
| -rw-r--r-- | drivers/power/qcom-charger/qpnp-fg-gen3.c | 464 |
3 files changed, 514 insertions, 26 deletions
diff --git a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt index 894924c56947..a3b265cd688a 100644 --- a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt +++ b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt @@ -153,6 +153,55 @@ First Level Node - FG Gen3 device loaded earlier by bootloader doesn't match with the profile available in the device tree. +- qcom,cl-start-capacity + Usage: optional + Value type: <u32> + Definition: Battery SOC threshold to start the capacity learning. + If this is not specified, then the default value used + will be 15. + +- qcom,cl-min-temp + Usage: optional + Value type: <u32> + Definition: Lower limit of battery temperature to start the capacity + learning. If this is not specified, then the default value + used will be 150. Unit is in decidegC. + +- qcom,cl-max-temp + Usage: optional + Value type: <u32> + Definition: Upper limit of battery temperature to start the capacity + learning. If this is not specified, then the default value + used will be 450 (45C). Unit is in decidegC. + +- qcom,cl-max-increment + Usage: optional + Value type: <u32> + Definition: Maximum capacity increment allowed per capacity learning + cycle. If this is not specified, then the default value + used will be 5 (0.5%). Unit is in decipercentage. + +- qcom,cl-max-decrement + Usage: optional + Value type: <u32> + Definition: Maximum capacity decrement allowed per capacity learning + cycle. If this is not specified, then the default value + used will be 100 (10%). Unit is in decipercentage. + +- qcom,cl-min-limit + Usage: optional + Value type: <u32> + Definition: Minimum limit that the capacity cannot go below in a + capacity learning cycle. If this is not specified, then + the default value is 0. Unit is in decipercentage. + +- qcom,cl-max-limit + Usage: optional + Value type: <u32> + Definition: Maximum limit that the capacity cannot go above in a + capacity learning cycle. If this is not specified, then + the default value is 0. Unit is in decipercentage. + ========================================================== Second Level Nodes - Peripherals managed by FG Gen3 driver ========================================================== diff --git a/drivers/power/qcom-charger/fg-core.h b/drivers/power/qcom-charger/fg-core.h index 091b8f49ee09..b975c93c401b 100644 --- a/drivers/power/qcom-charger/fg-core.h +++ b/drivers/power/qcom-charger/fg-core.h @@ -68,6 +68,7 @@ enum fg_debug_flag { FG_SRAM_READ = BIT(4), /* Show SRAM reads */ FG_BUS_WRITE = BIT(5), /* Show REGMAP writes */ FG_BUS_READ = BIT(6), /* Show REGMAP reads */ + FG_CAP_LEARN = BIT(7), /* Show capacity learning */ }; /* SRAM access */ @@ -119,6 +120,9 @@ enum fg_sram_param_id { FG_SRAM_OCV, FG_SRAM_RSLOW, FG_SRAM_ALG_FLAGS, + FG_SRAM_CC_SOC, + FG_SRAM_CC_SOC_SW, + FG_SRAM_ACT_BATT_CAP, /* Entries below here are configurable during initialization */ FG_SRAM_CUTOFF_VOLT, FG_SRAM_EMPTY_VOLT, @@ -182,6 +186,13 @@ struct fg_dt_props { int esr_timer_awake; int esr_timer_asleep; bool force_load_profile; + int cl_start_soc; + int cl_max_temp; + int cl_min_temp; + int cl_max_cap_inc; + int cl_max_cap_dec; + int cl_max_cap_limit; + int cl_min_cap_limit; }; /* parameters from battery profile */ @@ -203,6 +214,16 @@ struct fg_cyc_ctr_data { struct mutex lock; }; +struct fg_cap_learning { + bool active; + int init_cc_soc_sw; + int64_t nom_cap_uah; + int64_t init_cc_uah; + int64_t final_cc_uah; + int64_t learned_cc_uah; + struct mutex lock; +}; + struct fg_irq_info { const char *name; const irq_handler_t handler; @@ -222,21 +243,22 @@ struct fg_chip { struct fg_irq_info *irqs; struct votable *awake_votable; struct fg_sram_param *sp; + struct fg_alg_flag *alg_flags; int *debug_mask; char batt_profile[PROFILE_LEN]; struct fg_dt_props dt; struct fg_batt_props bp; struct fg_cyc_ctr_data cyc_ctr; struct notifier_block nb; + struct fg_cap_learning cl; struct mutex bus_lock; struct mutex sram_rw_lock; u32 batt_soc_base; u32 batt_info_base; u32 mem_if_base; int batt_id; - int nom_cap_uah; int status; - int prev_status; + int charge_done; int last_soc; bool profile_available; bool profile_loaded; @@ -247,7 +269,6 @@ struct fg_chip { struct delayed_work profile_load_work; struct work_struct status_change_work; struct work_struct cycle_count_work; - struct fg_alg_flag *alg_flags; }; /* Debugfs data structures are below */ diff --git a/drivers/power/qcom-charger/qpnp-fg-gen3.c b/drivers/power/qcom-charger/qpnp-fg-gen3.c index eda0efa312e2..028be23c76c2 100644 --- a/drivers/power/qcom-charger/qpnp-fg-gen3.c +++ b/drivers/power/qcom-charger/qpnp-fg-gen3.c @@ -67,12 +67,18 @@ #define BATT_SOC_OFFSET 0 #define MONOTONIC_SOC_WORD 94 #define MONOTONIC_SOC_OFFSET 2 +#define CC_SOC_WORD 95 +#define CC_SOC_OFFSET 0 +#define CC_SOC_SW_WORD 96 +#define CC_SOC_SW_OFFSET 0 #define VOLTAGE_PRED_WORD 97 #define VOLTAGE_PRED_OFFSET 0 #define OCV_WORD 97 #define OCV_OFFSET 2 #define RSLOW_WORD 101 #define RSLOW_OFFSET 0 +#define ACT_BATT_CAP_WORD 117 +#define ACT_BATT_CAP_OFFSET 0 #define LAST_BATT_SOC_WORD 119 #define LAST_BATT_SOC_OFFSET 0 #define LAST_MONOTONIC_SOC_WORD 119 @@ -100,6 +106,8 @@ static int fg_decode_default(struct fg_sram_param *sp, enum fg_sram_param_id id, int val); static int fg_decode_batt_soc(struct fg_sram_param *sp, enum fg_sram_param_id id, int val); +static int fg_decode_cc_soc(struct fg_sram_param *sp, + enum fg_sram_param_id id, int value); static void fg_encode_voltage(struct fg_sram_param *sp, enum fg_sram_param_id id, int val_mv, u8 *buf); static void fg_encode_current(struct fg_sram_param *sp, @@ -131,6 +139,12 @@ static struct fg_sram_param pmicobalt_v1_sram_params[] = { fg_decode_value_16b), PARAM(ALG_FLAGS, ALG_FLAGS_WORD, ALG_FLAGS_OFFSET, 1, 1, 1, 0, NULL, fg_decode_default), + PARAM(CC_SOC, CC_SOC_WORD, CC_SOC_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_cc_soc), + PARAM(CC_SOC_SW, CC_SOC_SW_WORD, CC_SOC_SW_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_cc_soc), + PARAM(ACT_BATT_CAP, ACT_BATT_CAP_WORD, ACT_BATT_CAP_OFFSET, 2, 1, 1, 0, + NULL, fg_decode_default), /* Entries below here are configurable during initialization */ PARAM(CUTOFF_VOLT, CUTOFF_VOLT_WORD, CUTOFF_VOLT_OFFSET, 2, 1000000, 244141, 0, fg_encode_voltage, NULL), @@ -171,6 +185,12 @@ static struct fg_sram_param pmicobalt_v2_sram_params[] = { fg_decode_value_16b), PARAM(ALG_FLAGS, ALG_FLAGS_WORD, ALG_FLAGS_OFFSET, 1, 1, 1, 0, NULL, fg_decode_default), + PARAM(CC_SOC, CC_SOC_WORD, CC_SOC_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_cc_soc), + PARAM(CC_SOC_SW, CC_SOC_SW_WORD, CC_SOC_SW_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_cc_soc), + PARAM(ACT_BATT_CAP, ACT_BATT_CAP_WORD, ACT_BATT_CAP_OFFSET, 2, 1, 1, 0, + NULL, fg_decode_default), /* Entries below here are configurable during initialization */ PARAM(CUTOFF_VOLT, CUTOFF_VOLT_WORD, CUTOFF_VOLT_OFFSET, 2, 1000000, 244141, 0, fg_encode_voltage, NULL), @@ -283,6 +303,16 @@ static int fg_restart; /* All getters HERE */ +static int fg_decode_cc_soc(struct fg_sram_param *sp, + enum fg_sram_param_id id, int value) +{ + sp[id].value = div_s64((s64)value * sp[id].numrtr, sp[id].denmtr); + sp[id].value = sign_extend32(sp[id].value, 31); + pr_debug("id: %d raw value: %x decoded value: %x\n", id, value, + sp[id].value); + return sp[id].value; +} + static int fg_decode_value_16b(struct fg_sram_param *sp, enum fg_sram_param_id id, int value) { @@ -410,6 +440,35 @@ static int fg_get_sram_prop(struct fg_chip *chip, enum fg_sram_param_id id, return 0; } +#define CC_SOC_30BIT GENMASK(29, 0) +static int fg_get_cc_soc(struct fg_chip *chip, int *val) +{ + int rc, cc_soc; + + rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC, &cc_soc); + if (rc < 0) { + pr_err("Error in getting CC_SOC, rc=%d\n", rc); + return rc; + } + + *val = div_s64(cc_soc * chip->cl.nom_cap_uah, CC_SOC_30BIT); + return 0; +} + +static int fg_get_cc_soc_sw(struct fg_chip *chip, int *val) +{ + int rc, cc_soc; + + rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC_SW, &cc_soc); + if (rc < 0) { + pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc); + return rc; + } + + *val = div_s64(cc_soc * chip->cl.learned_cc_uah, CC_SOC_30BIT); + return 0; +} + #define BATT_TEMP_NUMR 1 #define BATT_TEMP_DENR 1 static int fg_get_battery_temp(struct fg_chip *chip, int *val) @@ -560,7 +619,6 @@ static int fg_get_msoc_raw(struct fg_chip *chip, int *val) } fg_dbg(chip, FG_POWER_SUPPLY, "raw: 0x%02x\n", cap[0]); - *val = cap[0]; return 0; } @@ -763,38 +821,313 @@ static bool is_charger_available(struct fg_chip *chip) return true; } +static int fg_save_learned_cap_to_sram(struct fg_chip *chip) +{ + int16_t cc_mah; + int rc; + + if (chip->battery_missing || !chip->cl.learned_cc_uah) + return -EPERM; + + cc_mah = div64_s64(chip->cl.learned_cc_uah, 1000); + rc = fg_sram_write(chip, chip->sp[FG_SRAM_ACT_BATT_CAP].addr_word, + chip->sp[FG_SRAM_ACT_BATT_CAP].addr_byte, (u8 *)&cc_mah, + chip->sp[FG_SRAM_ACT_BATT_CAP].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing act_batt_cap, rc=%d\n", rc); + return rc; + } + + fg_dbg(chip, FG_CAP_LEARN, "learned capacity %llduah/%dmah stored\n", + chip->cl.learned_cc_uah, cc_mah); + return 0; +} + +#define CAPACITY_DELTA_DECIPCT 500 +static int fg_load_learned_cap_from_sram(struct fg_chip *chip) +{ + int rc, act_cap_mah; + int64_t delta_cc_uah, pct_nom_cap_uah; + + rc = fg_get_sram_prop(chip, FG_SRAM_ACT_BATT_CAP, &act_cap_mah); + if (rc < 0) { + pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc); + return rc; + } + + chip->cl.learned_cc_uah = act_cap_mah * 1000; + if (chip->cl.learned_cc_uah == 0) + chip->cl.learned_cc_uah = chip->cl.nom_cap_uah; + + if (chip->cl.learned_cc_uah != chip->cl.nom_cap_uah) { + delta_cc_uah = abs(chip->cl.learned_cc_uah - + chip->cl.nom_cap_uah); + pct_nom_cap_uah = div64_s64((int64_t)chip->cl.nom_cap_uah * + CAPACITY_DELTA_DECIPCT, 1000); + /* + * If the learned capacity is out of range by 50% from the + * nominal capacity, then overwrite the learned capacity with + * the nominal capacity. + */ + if (chip->cl.nom_cap_uah && delta_cc_uah > pct_nom_cap_uah) { + fg_dbg(chip, FG_CAP_LEARN, "learned_cc_uah: %lld is higher than expected\n", + chip->cl.learned_cc_uah); + fg_dbg(chip, FG_CAP_LEARN, "Capping it to nominal:%lld\n", + chip->cl.nom_cap_uah); + chip->cl.learned_cc_uah = chip->cl.nom_cap_uah; + rc = fg_save_learned_cap_to_sram(chip); + if (rc < 0) + pr_err("Error in saving learned_cc_uah, rc=%d\n", + rc); + } + } + + fg_dbg(chip, FG_CAP_LEARN, "learned_cc_uah:%lld nom_cap_uah: %lld\n", + chip->cl.learned_cc_uah, chip->cl.nom_cap_uah); + return 0; +} + +static bool is_temp_valid_cap_learning(struct fg_chip *chip) +{ + int rc, batt_temp; + + rc = fg_get_battery_temp(chip, &batt_temp); + if (rc < 0) { + pr_err("Error in getting batt_temp\n"); + return false; + } + + if (batt_temp > chip->dt.cl_max_temp || + batt_temp < chip->dt.cl_min_temp) { + fg_dbg(chip, FG_CAP_LEARN, "batt temp %d out of range [%d %d]\n", + batt_temp, chip->dt.cl_min_temp, chip->dt.cl_max_temp); + return false; + } + + return true; +} + +static void fg_cap_learning_post_process(struct fg_chip *chip) +{ + int64_t max_inc_val, min_dec_val, old_cap; + int rc; + + max_inc_val = chip->cl.learned_cc_uah + * (1000 + chip->dt.cl_max_cap_inc); + do_div(max_inc_val, 1000); + + min_dec_val = chip->cl.learned_cc_uah + * (1000 - chip->dt.cl_max_cap_dec); + do_div(min_dec_val, 1000); + + old_cap = chip->cl.learned_cc_uah; + if (chip->cl.final_cc_uah > max_inc_val) + chip->cl.learned_cc_uah = max_inc_val; + else if (chip->cl.final_cc_uah < min_dec_val) + chip->cl.learned_cc_uah = min_dec_val; + else + chip->cl.learned_cc_uah = + chip->cl.final_cc_uah; + + if (chip->dt.cl_max_cap_limit) { + max_inc_val = (int64_t)chip->cl.nom_cap_uah * (1000 + + chip->dt.cl_max_cap_limit); + do_div(max_inc_val, 1000); + if (chip->cl.final_cc_uah > max_inc_val) { + fg_dbg(chip, FG_CAP_LEARN, "learning capacity %lld goes above max limit %lld\n", + chip->cl.final_cc_uah, max_inc_val); + chip->cl.learned_cc_uah = max_inc_val; + } + } + + if (chip->dt.cl_min_cap_limit) { + min_dec_val = (int64_t)chip->cl.nom_cap_uah * (1000 - + chip->dt.cl_min_cap_limit); + do_div(min_dec_val, 1000); + if (chip->cl.final_cc_uah < min_dec_val) { + fg_dbg(chip, FG_CAP_LEARN, "learning capacity %lld goes below min limit %lld\n", + chip->cl.final_cc_uah, min_dec_val); + chip->cl.learned_cc_uah = min_dec_val; + } + } + + rc = fg_save_learned_cap_to_sram(chip); + if (rc < 0) + pr_err("Error in saving learned_cc_uah, rc=%d\n", rc); + + fg_dbg(chip, FG_CAP_LEARN, "final cc_uah = %lld, learned capacity %lld -> %lld uah\n", + chip->cl.final_cc_uah, old_cap, chip->cl.learned_cc_uah); +} + +static int fg_cap_learning_process_full_data(struct fg_chip *chip) +{ + int rc, cc_soc_sw, cc_soc_delta_pct; + int64_t delta_cc_uah; + + rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC_SW, &cc_soc_sw); + if (rc < 0) { + pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc); + return rc; + } + + cc_soc_delta_pct = DIV_ROUND_CLOSEST( + abs(cc_soc_sw - chip->cl.init_cc_soc_sw) * 100, + CC_SOC_30BIT); + delta_cc_uah = div64_s64(chip->cl.learned_cc_uah * cc_soc_delta_pct, + 100); + chip->cl.final_cc_uah = chip->cl.init_cc_uah + delta_cc_uah; + fg_dbg(chip, FG_CAP_LEARN, "Current cc_soc=%d cc_soc_delta_pct=%d total_cc_uah=%lld\n", + cc_soc_sw, cc_soc_delta_pct, chip->cl.final_cc_uah); + return 0; +} + +static int fg_cap_learning_begin(struct fg_chip *chip, int batt_soc) +{ + int rc, cc_soc_sw; + + if (DIV_ROUND_CLOSEST(batt_soc * 100, FULL_SOC_RAW) > + chip->dt.cl_start_soc) { + fg_dbg(chip, FG_CAP_LEARN, "Battery SOC %d is high!, not starting\n", + batt_soc); + return -EINVAL; + } + + chip->cl.init_cc_uah = div64_s64(chip->cl.learned_cc_uah * batt_soc, + FULL_SOC_RAW); + rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC_SW, &cc_soc_sw); + if (rc < 0) { + pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc); + return rc; + } + + chip->cl.init_cc_soc_sw = cc_soc_sw; + chip->cl.active = true; + fg_dbg(chip, FG_CAP_LEARN, "Capacity learning started @ battery SOC %d init_cc_soc_sw:%d\n", + batt_soc, chip->cl.init_cc_soc_sw); + return 0; +} + +static int fg_cap_learning_done(struct fg_chip *chip) +{ + int rc, cc_soc_sw; + + rc = fg_cap_learning_process_full_data(chip); + if (rc < 0) { + pr_err("Error in processing cap learning full data, rc=%d\n", + rc); + goto out; + } + + /* Write a FULL value to cc_soc_sw */ + cc_soc_sw = CC_SOC_30BIT; + rc = fg_sram_write(chip, chip->sp[FG_SRAM_CC_SOC_SW].addr_word, + chip->sp[FG_SRAM_CC_SOC_SW].addr_byte, (u8 *)&cc_soc_sw, + chip->sp[FG_SRAM_CC_SOC_SW].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing cc_soc_sw, rc=%d\n", rc); + goto out; + } + + fg_cap_learning_post_process(chip); +out: + return rc; +} + +#define FULL_SOC_RAW 255 +static void fg_cap_learning_update(struct fg_chip *chip) +{ + int rc, batt_soc; + + mutex_lock(&chip->cl.lock); + + if (!is_temp_valid_cap_learning(chip) || !chip->cl.learned_cc_uah || + chip->battery_missing) { + fg_dbg(chip, FG_CAP_LEARN, "Aborting cap_learning %lld\n", + chip->cl.learned_cc_uah); + chip->cl.active = false; + chip->cl.init_cc_uah = 0; + goto out; + } + + rc = fg_get_sram_prop(chip, FG_SRAM_BATT_SOC, &batt_soc); + if (rc < 0) { + pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc); + goto out; + } + + fg_dbg(chip, FG_CAP_LEARN, "Chg_status: %d cl_active: %d batt_soc: %d\n", + chip->status, chip->cl.active, batt_soc); + + /* Initialize the starting point of learning capacity */ + if (!chip->cl.active) { + if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { + rc = fg_cap_learning_begin(chip, batt_soc); + chip->cl.active = (rc == 0); + } + + } else { + if (chip->status == POWER_SUPPLY_STATUS_FULL && + chip->charge_done) { + rc = fg_cap_learning_done(chip); + if (rc < 0) + pr_err("Error in completing capacity learning, rc=%d\n", + rc); + + chip->cl.active = false; + chip->cl.init_cc_uah = 0; + } + + if (chip->status == POWER_SUPPLY_STATUS_NOT_CHARGING) { + fg_dbg(chip, FG_CAP_LEARN, "Capacity learning aborted @ battery SOC %d\n", + batt_soc); + chip->cl.active = false; + chip->cl.init_cc_uah = 0; + } + } + +out: + mutex_unlock(&chip->cl.lock); +} + static void status_change_work(struct work_struct *work) { struct fg_chip *chip = container_of(work, struct fg_chip, status_change_work); union power_supply_propval prop = {0, }; + int prev_status, rc; if (!is_charger_available(chip)) { fg_dbg(chip, FG_STATUS, "Charger not available?!\n"); - return; + goto out; } - power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS, + prev_status = chip->status; + rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS, &prop); - chip->prev_status = chip->status; - chip->status = prop.intval; + if (rc < 0) { + pr_err("Error in getting charging status, rc=%d\n", rc); + goto out; + } - if (chip->cyc_ctr.en && chip->prev_status != chip->status) - schedule_work(&chip->cycle_count_work); + chip->status = prop.intval; + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_DONE, &prop); + if (rc < 0) { + pr_err("Error in getting charge_done, rc=%d\n", rc); + goto out; + } - switch (prop.intval) { - case POWER_SUPPLY_STATUS_CHARGING: - fg_dbg(chip, FG_POWER_SUPPLY, "Charging\n"); - break; - case POWER_SUPPLY_STATUS_DISCHARGING: - fg_dbg(chip, FG_POWER_SUPPLY, "Discharging\n"); - break; - case POWER_SUPPLY_STATUS_FULL: - fg_dbg(chip, FG_POWER_SUPPLY, "Full\n"); - break; - default: - break; + chip->charge_done = prop.intval; + fg_dbg(chip, FG_POWER_SUPPLY, "prev_status: %d curr_status:%d charge_done: %d\n", + prev_status, chip->status, chip->charge_done); + if (prev_status != chip->status) { + if (chip->cyc_ctr.en) + schedule_work(&chip->cycle_count_work); + fg_cap_learning_update(chip); } + +out: + pm_relax(chip->dev); } static void restore_cycle_counter(struct fg_chip *chip) @@ -1059,6 +1392,11 @@ static void profile_load_work(struct work_struct *work) goto done; clear_cycle_counter(chip); + mutex_lock(&chip->cl.lock); + chip->cl.learned_cc_uah = 0; + chip->cl.active = false; + mutex_unlock(&chip->cl.lock); + fg_dbg(chip, FG_STATUS, "profile loading started\n"); rc = fg_masked_write(chip, BATT_SOC_RESTART(chip), RESTART_GO_BIT, 0); if (rc < 0) { @@ -1098,10 +1436,14 @@ done: if (rc < 0) { pr_err("Error in reading %04x[%d] rc=%d\n", NOM_CAP_WORD, NOM_CAP_OFFSET, rc); - goto out; + } else { + chip->cl.nom_cap_uah = (int)(buf[0] | buf[1] << 8) * 1000; + rc = fg_load_learned_cap_from_sram(chip); + if (rc < 0) + pr_err("Error in loading capacity learning data, rc:%d\n", + rc); } - chip->nom_cap_uah = (int)(buf[0] | buf[1] << 8) * 1000; chip->profile_loaded = true; fg_dbg(chip, FG_STATUS, "profile loaded successfully"); out: @@ -1181,7 +1523,7 @@ static int fg_psy_get_property(struct power_supply *psy, rc = fg_get_sram_prop(chip, FG_SRAM_OCV, &pval->intval); break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - pval->intval = chip->nom_cap_uah; + pval->intval = chip->cl.nom_cap_uah; break; case POWER_SUPPLY_PROP_RESISTANCE_ID: rc = fg_get_batt_id(chip, &pval->intval); @@ -1197,6 +1539,18 @@ static int fg_psy_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: pval->intval = chip->cyc_ctr.id; break; + case POWER_SUPPLY_PROP_CHARGE_NOW_RAW: + rc = fg_get_cc_soc(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + pval->intval = chip->cl.init_cc_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + pval->intval = chip->cl.learned_cc_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + rc = fg_get_cc_soc_sw(chip, &pval->intval); + break; default: break; } @@ -1255,8 +1609,14 @@ static int fg_notifier_cb(struct notifier_block *nb, return NOTIFY_OK; if ((strcmp(psy->desc->name, "battery") == 0) - || (strcmp(psy->desc->name, "usb") == 0)) + || (strcmp(psy->desc->name, "usb") == 0)) { + /* + * We cannot vote for awake votable here as that takes + * a mutex lock and this is executed in an atomic context. + */ + pm_stay_awake(chip->dev); schedule_work(&chip->status_change_work); + } return NOTIFY_OK; } @@ -1274,6 +1634,10 @@ static enum power_supply_property fg_psy_props[] = { POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_CYCLE_COUNT, POWER_SUPPLY_PROP_CYCLE_COUNT_ID, + POWER_SUPPLY_PROP_CHARGE_NOW_RAW, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_COUNTER, }; static const struct power_supply_desc fg_psy_desc = { @@ -1546,6 +1910,10 @@ static irqreturn_t fg_delta_soc_irq_handler(int irq, void *data) power_supply_changed(chip->batt_psy); fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq); + + if (chip->cl.active) + fg_cap_learning_update(chip); + return IRQ_HANDLED; } @@ -1715,6 +2083,13 @@ static int fg_register_interrupts(struct fg_chip *chip) #define DEFAULT_BATT_TEMP_COOL 5 #define DEFAULT_BATT_TEMP_WARM 45 #define DEFAULT_BATT_TEMP_HOT 50 +#define DEFAULT_CL_START_SOC 15 +#define DEFAULT_CL_MIN_TEMP_DECIDEGC 150 +#define DEFAULT_CL_MAX_TEMP_DECIDEGC 450 +#define DEFAULT_CL_MAX_INC_DECIPERC 5 +#define DEFAULT_CL_MAX_DEC_DECIPERC 100 +#define DEFAULT_CL_MIN_LIM_DECIPERC 0 +#define DEFAULT_CL_MAX_LIM_DECIPERC 0 static int fg_parse_dt(struct fg_chip *chip) { struct device_node *child, *revid_node, *node = chip->dev->of_node; @@ -1905,6 +2280,48 @@ static int fg_parse_dt(struct fg_chip *chip) chip->dt.force_load_profile = of_property_read_bool(node, "qcom,fg-force-load-profile"); + rc = of_property_read_u32(node, "qcom,cl-start-capacity", &temp); + if (rc < 0) + chip->dt.cl_start_soc = DEFAULT_CL_START_SOC; + else + chip->dt.cl_start_soc = temp; + + rc = of_property_read_u32(node, "qcom,cl-min-temp", &temp); + if (rc < 0) + chip->dt.cl_min_temp = DEFAULT_CL_MIN_TEMP_DECIDEGC; + else + chip->dt.cl_min_temp = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-temp", &temp); + if (rc < 0) + chip->dt.cl_max_temp = DEFAULT_CL_MAX_TEMP_DECIDEGC; + else + chip->dt.cl_max_temp = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-increment", &temp); + if (rc < 0) + chip->dt.cl_max_cap_inc = DEFAULT_CL_MAX_INC_DECIPERC; + else + chip->dt.cl_max_cap_inc = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-decrement", &temp); + if (rc < 0) + chip->dt.cl_max_cap_dec = DEFAULT_CL_MAX_DEC_DECIPERC; + else + chip->dt.cl_max_cap_dec = temp; + + rc = of_property_read_u32(node, "qcom,cl-min-limit", &temp); + if (rc < 0) + chip->dt.cl_min_cap_limit = DEFAULT_CL_MIN_LIM_DECIPERC; + else + chip->dt.cl_min_cap_limit = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-limit", &temp); + if (rc < 0) + chip->dt.cl_max_cap_limit = DEFAULT_CL_MAX_LIM_DECIPERC; + else + chip->dt.cl_max_cap_limit = temp; + return 0; } @@ -1957,6 +2374,7 @@ static int fg_gen3_probe(struct platform_device *pdev) mutex_init(&chip->bus_lock); mutex_init(&chip->sram_rw_lock); mutex_init(&chip->cyc_ctr.lock); + mutex_init(&chip->cl.lock); init_completion(&chip->soc_update); init_completion(&chip->soc_ready); INIT_DELAYED_WORK(&chip->profile_load_work, profile_load_work); |
