From 95e05ba59279c065bef21554d5010c765d68b05a Mon Sep 17 00:00:00 2001 From: Ashay Jaiswal Date: Thu, 20 Jul 2017 17:41:36 +0530 Subject: power: qcom-step-chg: Add temperature based step-charging It supports for temperature (JEITA) based step charging. The solution samples the battery temperature periodically and applies the FCC from a static look-up table. There is a provision for timed hysteresis which is achieved using a 5 second delayed work. Change-Id: Ica18f2c812232d6938799dab16fb9f18bc14b48f Signed-off-by: Ashay Jaiswal Signed-off-by: Abhijeet Dharmapurikar --- .../bindings/power/supply/qcom/qpnp-smb2.txt | 4 + drivers/power/supply/qcom/qpnp-smb2.c | 3 + drivers/power/supply/qcom/smb-lib.c | 5 +- drivers/power/supply/qcom/smb-lib.h | 1 + drivers/power/supply/qcom/step-chg-jeita.c | 321 +++++++++++++++++---- drivers/power/supply/qcom/step-chg-jeita.h | 2 +- 6 files changed, 282 insertions(+), 54 deletions(-) diff --git a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt index 3527779ef93c..f01eae10bf4f 100644 --- a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt +++ b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt @@ -178,6 +178,10 @@ Charger specific properties: Definition: WD bark-timeout in seconds. The possible values are 16, 32, 64, 128. If not defined it defaults to 64. +- qcom,sw-jeita-enable + Usage: optional + Value type: bool + Definition: Boolean flag which when present enables sw compensation for jeita ============================================= Second Level Nodes - SMB2 Charger Peripherals diff --git a/drivers/power/supply/qcom/qpnp-smb2.c b/drivers/power/supply/qcom/qpnp-smb2.c index 7de9429cae8b..2cf5f0d4858b 100644 --- a/drivers/power/supply/qcom/qpnp-smb2.c +++ b/drivers/power/supply/qcom/qpnp-smb2.c @@ -210,6 +210,9 @@ static int smb2_parse_dt(struct smb2 *chip) chg->step_chg_enabled = of_property_read_bool(node, "qcom,step-charging-enable"); + chg->sw_jeita_enabled = of_property_read_bool(node, + "qcom,sw-jeita-enable"); + rc = of_property_read_u32(node, "qcom,wd-bark-time-secs", &chip->dt.wd_bark_time); if (rc < 0 || chip->dt.wd_bark_time < MIN_WD_BARK_TIME) diff --git a/drivers/power/supply/qcom/smb-lib.c b/drivers/power/supply/qcom/smb-lib.c index 52775a3bdf1b..3f1aa1ab4952 100644 --- a/drivers/power/supply/qcom/smb-lib.c +++ b/drivers/power/supply/qcom/smb-lib.c @@ -3965,7 +3965,7 @@ irqreturn_t smblib_handle_wdog_bark(int irq, void *data) if (rc < 0) smblib_err(chg, "Couldn't pet the dog rc=%d\n", rc); - if (chg->step_chg_enabled) + if (chg->step_chg_enabled || chg->sw_jeita_enabled) power_supply_changed(chg->batt_psy); return IRQ_HANDLED; @@ -4603,7 +4603,8 @@ int smblib_init(struct smb_charger *chg) return rc; } - rc = qcom_step_chg_init(chg->step_chg_enabled); + rc = qcom_step_chg_init(chg->step_chg_enabled, + chg->sw_jeita_enabled); if (rc < 0) { smblib_err(chg, "Couldn't init qcom_step_chg_init rc=%d\n", rc); diff --git a/drivers/power/supply/qcom/smb-lib.h b/drivers/power/supply/qcom/smb-lib.h index 43a795085755..5acc3cc66a98 100644 --- a/drivers/power/supply/qcom/smb-lib.h +++ b/drivers/power/supply/qcom/smb-lib.h @@ -306,6 +306,7 @@ struct smb_charger { int dcp_icl_ua; int fake_capacity; bool step_chg_enabled; + bool sw_jeita_enabled; bool is_hdc; bool chg_done; bool micro_usb_mode; diff --git a/drivers/power/supply/qcom/step-chg-jeita.c b/drivers/power/supply/qcom/step-chg-jeita.c index a2c08be960be..cba01608afb3 100644 --- a/drivers/power/supply/qcom/step-chg-jeita.c +++ b/drivers/power/supply/qcom/step-chg-jeita.c @@ -20,7 +20,7 @@ #define MAX_STEP_CHG_ENTRIES 8 #define STEP_CHG_VOTER "STEP_CHG_VOTER" -#define STATUS_CHANGE_VOTER "STATUS_CHANGE_VOTER" +#define JEITA_VOTER "JEITA_VOTER" #define is_between(left, right, value) \ (((left) >= (right) && (left) >= (value) \ @@ -28,23 +28,44 @@ || ((left) <= (right) && (left) <= (value) \ && (value) <= (right))) -struct step_chg_data { - u32 vbatt_soc_low; - u32 vbatt_soc_high; - u32 fcc_ua; +struct range_data { + u32 low_threshold; + u32 high_threshold; + u32 value; }; struct step_chg_cfg { - u32 psy_prop; - char *prop_name; - struct step_chg_data cfg[MAX_STEP_CHG_ENTRIES]; + u32 psy_prop; + char *prop_name; + int hysteresis; + struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES]; +}; + +struct jeita_fcc_cfg { + u32 psy_prop; + char *prop_name; + int hysteresis; + struct range_data fcc_cfg[MAX_STEP_CHG_ENTRIES]; +}; + +struct jeita_fv_cfg { + u32 psy_prop; + char *prop_name; + int hysteresis; + struct range_data fv_cfg[MAX_STEP_CHG_ENTRIES]; }; struct step_chg_info { - ktime_t last_update_time; + ktime_t step_last_update_time; + ktime_t jeita_last_update_time; bool step_chg_enable; + bool sw_jeita_enable; + int jeita_fcc_index; + int jeita_fv_index; + int step_index; struct votable *fcc_votable; + struct votable *fv_votable; struct wakeup_source *step_chg_ws; struct power_supply *batt_psy; struct delayed_work status_change_work; @@ -53,32 +74,70 @@ struct step_chg_info { static struct step_chg_info *the_chip; +#define STEP_CHG_HYSTERISIS_DELAY_US 5000000 /* 5 secs */ + /* * Step Charging Configuration * Update the table based on the battery profile * Supports VBATT and SOC based source + * range data must be in increasing ranges and shouldn't overlap */ static struct step_chg_cfg step_chg_config = { - .psy_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW, - .prop_name = "VBATT", - .cfg = { + .psy_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW, + .prop_name = "VBATT", + .hysteresis = 100000, /* 100mV */ + .fcc_cfg = { /* VBAT_LOW VBAT_HIGH FCC */ {3600000, 4000000, 3000000}, - {4000000, 4200000, 2800000}, - {4200000, 4400000, 2000000}, + {4001000, 4200000, 2800000}, + {4201000, 4400000, 2000000}, }, + /* + * SOC STEP-CHG configuration example. + * + * .psy_prop = POWER_SUPPLY_PROP_CAPACITY, + * .prop_name = "SOC", + * .fcc_cfg = { + * //SOC_LOW SOC_HIGH FCC + * {20, 70, 3000000}, + * {70, 90, 2750000}, + * {90, 100, 2500000}, + * }, + */ +}; + /* - * SOC STEP-CHG configuration example. - * - * .psy_prop = POWER_SUPPLY_PROP_CAPACITY, - * .prop_name = "SOC", - * .cfg = { - * //SOC_LOW SOC_HIGH FCC - * {20, 70, 3000000}, - * {70, 90, 2750000}, - * {90, 100, 2500000}, - * }, + * Jeita Charging Configuration + * Update the table based on the battery profile + * Please ensure that the TEMP ranges are programmed in the hw so that + * an interrupt is issued and a consequent psy changed will cause us to + * react immediately. + * range data must be in increasing ranges and shouldn't overlap. + * Gaps are okay */ +static struct jeita_fcc_cfg jeita_fcc_config = { + .psy_prop = POWER_SUPPLY_PROP_TEMP, + .prop_name = "BATT_TEMP", + .hysteresis = 10, /* 1degC hysteresis */ + .fcc_cfg = { + /* TEMP_LOW TEMP_HIGH FCC */ + {0, 100, 600000}, + {101, 200, 2000000}, + {201, 450, 3000000}, + {451, 550, 600000}, + }, +}; + +static struct jeita_fv_cfg jeita_fv_config = { + .psy_prop = POWER_SUPPLY_PROP_TEMP, + .prop_name = "BATT_TEMP", + .hysteresis = 10, /* 1degC hysteresis */ + .fv_cfg = { + /* TEMP_LOW TEMP_HIGH FCC */ + {0, 100, 4200000}, + {101, 450, 4400000}, + {451, 550, 4200000}, + }, }; static bool is_batt_available(struct step_chg_info *chip) @@ -92,22 +151,67 @@ static bool is_batt_available(struct step_chg_info *chip) return true; } -static int get_fcc(int threshold) +static int get_val(struct range_data *range, int hysteresis, int current_index, + int threshold, + int *new_index, int *val) { int i; + *new_index = -EINVAL; + /* first find the matching index without hysteresis */ for (i = 0; i < MAX_STEP_CHG_ENTRIES; i++) - if (is_between(step_chg_config.cfg[i].vbatt_soc_low, - step_chg_config.cfg[i].vbatt_soc_high, threshold)) - return step_chg_config.cfg[i].fcc_ua; + if (is_between(range[i].low_threshold, + range[i].high_threshold, threshold)) { + *new_index = i; + *val = range[i].value; + } + + /* if nothing was found, return -ENODATA */ + if (*new_index == -EINVAL) + return -ENODATA; + /* + * If we don't have a current_index return this + * newfound value. There is no hysterisis from out of range + * to in range transition + */ + if (current_index == -EINVAL) + return 0; - return -ENODATA; + /* + * Check for hysteresis if it in the neighbourhood + * of our current index. + */ + if (*new_index == current_index + 1) { + if (threshold < range[*new_index].low_threshold + hysteresis) { + /* + * Stay in the current index, threshold is not higher + * by hysteresis amount + */ + *new_index = current_index; + *val = range[current_index].value; + } + } else if (*new_index == current_index - 1) { + if (threshold > range[*new_index].high_threshold - hysteresis) { + /* + * stay in the current index, threshold is not lower + * by hysteresis amount + */ + *new_index = current_index; + *val = range[current_index].value; + } + } + return 0; } static int handle_step_chg_config(struct step_chg_info *chip) { union power_supply_propval pval = {0, }; int rc = 0, fcc_ua = 0; + u64 elapsed_us; + + elapsed_us = ktime_us_delta(ktime_get(), chip->step_last_update_time); + if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US) + goto reschedule; rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED, &pval); @@ -119,7 +223,7 @@ static int handle_step_chg_config(struct step_chg_info *chip) if (!chip->step_chg_enable) { if (chip->fcc_votable) vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0); - return 0; + goto update_time; } rc = power_supply_get_property(chip->batt_psy, @@ -130,48 +234,144 @@ static int handle_step_chg_config(struct step_chg_info *chip) return rc; } - chip->fcc_votable = find_votable("FCC"); - if (!chip->fcc_votable) - return -EINVAL; - - fcc_ua = get_fcc(pval.intval); - if (fcc_ua < 0) { + rc = get_val(step_chg_config.fcc_cfg, step_chg_config.hysteresis, + chip->step_index, + pval.intval, + &chip->step_index, + &fcc_ua); + if (rc < 0) { /* remove the vote if no step-based fcc is found */ - vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0); - return 0; + if (chip->fcc_votable) + vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0); + goto update_time; } + if (!chip->fcc_votable) + chip->fcc_votable = find_votable("FCC"); + if (!chip->fcc_votable) + return -EINVAL; + vote(chip->fcc_votable, STEP_CHG_VOTER, true, fcc_ua); pr_debug("%s = %d Step-FCC = %duA\n", step_chg_config.prop_name, pval.intval, fcc_ua); +update_time: + chip->step_last_update_time = ktime_get(); return 0; + +reschedule: + /* reschedule 1000uS after the remaining time */ + return (STEP_CHG_HYSTERISIS_DELAY_US - elapsed_us + 1000); +} + +static int handle_jeita(struct step_chg_info *chip) +{ + union power_supply_propval pval = {0, }; + int rc = 0, fcc_ua = 0, fv_uv = 0; + u64 elapsed_us; + + if (!chip->sw_jeita_enable) { + if (chip->fcc_votable) + vote(chip->fcc_votable, JEITA_VOTER, false, 0); + if (chip->fv_votable) + vote(chip->fv_votable, JEITA_VOTER, false, 0); + return 0; + } + + elapsed_us = ktime_us_delta(ktime_get(), chip->jeita_last_update_time); + if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US) + goto reschedule; + + rc = power_supply_get_property(chip->batt_psy, + jeita_fcc_config.psy_prop, &pval); + if (rc < 0) { + pr_err("Couldn't read %s property rc=%d\n", + step_chg_config.prop_name, rc); + return rc; + } + + rc = get_val(jeita_fcc_config.fcc_cfg, jeita_fcc_config.hysteresis, + chip->jeita_fcc_index, + pval.intval, + &chip->jeita_fcc_index, + &fcc_ua); + if (rc < 0) { + /* remove the vote if no step-based fcc is found */ + if (chip->fcc_votable) + vote(chip->fcc_votable, JEITA_VOTER, false, 0); + goto update_time; + } + + if (!chip->fcc_votable) + chip->fcc_votable = find_votable("FCC"); + if (!chip->fcc_votable) + /* changing FCC is a must */ + return -EINVAL; + + vote(chip->fcc_votable, JEITA_VOTER, true, fcc_ua); + + rc = get_val(jeita_fv_config.fv_cfg, jeita_fv_config.hysteresis, + chip->jeita_fv_index, + pval.intval, + &chip->jeita_fv_index, + &fv_uv); + if (rc < 0) { + /* remove the vote if no step-based fcc is found */ + if (chip->fv_votable) + vote(chip->fv_votable, JEITA_VOTER, false, 0); + goto update_time; + } + + chip->fv_votable = find_votable("FV"); + if (!chip->fv_votable) + goto update_time; + + vote(chip->fv_votable, JEITA_VOTER, true, fv_uv); + + pr_debug("%s = %d FCC = %duA FV = %duV\n", + step_chg_config.prop_name, pval.intval, fcc_ua, fv_uv); + +update_time: + chip->jeita_last_update_time = ktime_get(); + return 0; + +reschedule: + /* reschedule 1000uS after the remaining time */ + return (STEP_CHG_HYSTERISIS_DELAY_US - elapsed_us + 1000); } -#define STEP_CHG_HYSTERISIS_DELAY_US 5000000 /* 5 secs */ static void status_change_work(struct work_struct *work) { struct step_chg_info *chip = container_of(work, struct step_chg_info, status_change_work.work); int rc = 0; - u64 elapsed_us; - - elapsed_us = ktime_us_delta(ktime_get(), chip->last_update_time); - if (elapsed_us < STEP_CHG_HYSTERISIS_DELAY_US) - goto release_ws; + int reschedule_us; + int reschedule_jeita_work_us = 0; + int reschedule_step_work_us = 0; if (!is_batt_available(chip)) - goto release_ws; + return; + + /* skip elapsed_us debounce for handling battery temperature */ + rc = handle_jeita(chip); + if (rc > 0) + reschedule_jeita_work_us = rc; + else if (rc < 0) + pr_err("Couldn't handle sw jeita rc = %d\n", rc); rc = handle_step_chg_config(chip); + if (rc > 0) + reschedule_step_work_us = rc; if (rc < 0) - goto release_ws; + pr_err("Couldn't handle step rc = %d\n", rc); - chip->last_update_time = ktime_get(); - -release_ws: - __pm_relax(chip->step_chg_ws); + reschedule_us = min(reschedule_jeita_work_us, reschedule_step_work_us); + if (reschedule_us == 0) + __pm_relax(chip->step_chg_ws); + else + schedule_delayed_work(&chip->status_change_work, + usecs_to_jiffies(reschedule_us)); } static int step_chg_notifier_call(struct notifier_block *nb, @@ -205,7 +405,7 @@ static int step_chg_register_notifier(struct step_chg_info *chip) return 0; } -int qcom_step_chg_init(bool step_chg_enable) +int qcom_step_chg_init(bool step_chg_enable, bool sw_jeita_enable) { int rc; struct step_chg_info *chip; @@ -226,6 +426,11 @@ int qcom_step_chg_init(bool step_chg_enable) } chip->step_chg_enable = step_chg_enable; + chip->sw_jeita_enable = sw_jeita_enable; + + chip->step_index = -EINVAL; + chip->jeita_fcc_index = -EINVAL; + chip->jeita_fv_index = -EINVAL; if (step_chg_enable && (!step_chg_config.psy_prop || !step_chg_config.prop_name)) { @@ -234,6 +439,20 @@ int qcom_step_chg_init(bool step_chg_enable) return -ENODATA; } + if (sw_jeita_enable && (!jeita_fcc_config.psy_prop || + !jeita_fcc_config.prop_name)) { + /* fail if step-chg configuration is invalid */ + pr_err("Jeita TEMP configuration not defined - fail\n"); + return -ENODATA; + } + + if (sw_jeita_enable && (!jeita_fv_config.psy_prop || + !jeita_fv_config.prop_name)) { + /* fail if step-chg configuration is invalid */ + pr_err("Jeita TEMP configuration not defined - fail\n"); + return -ENODATA; + } + INIT_DELAYED_WORK(&chip->status_change_work, status_change_work); rc = step_chg_register_notifier(chip); diff --git a/drivers/power/supply/qcom/step-chg-jeita.h b/drivers/power/supply/qcom/step-chg-jeita.h index 928eeb7a670b..53335c3c2c70 100644 --- a/drivers/power/supply/qcom/step-chg-jeita.h +++ b/drivers/power/supply/qcom/step-chg-jeita.h @@ -12,6 +12,6 @@ #ifndef __STEP_CHG_H__ #define __STEP_CHG_H__ -int qcom_step_chg_init(bool); +int qcom_step_chg_init(bool, bool); void qcom_step_chg_deinit(void); #endif /* __STEP_CHG_H__ */ -- cgit v1.2.3