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 <ashayj@codeaurora.org> Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
This commit is contained in:
parent
a37c10a168
commit
95e05ba592
6 changed files with 282 additions and 54 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
return -ENODATA;
|
||||
/* 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;
|
||||
|
||||
/*
|
||||
* 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");
|
||||
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 */
|
||||
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;
|
||||
|
||||
fcc_ua = get_fcc(pval.intval);
|
||||
if (fcc_ua < 0) {
|
||||
/* remove the vote if no step-based fcc is found */
|
||||
vote(chip->fcc_votable, STEP_CHG_VOTER, false, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -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__ */
|
||||
|
|
Loading…
Add table
Reference in a new issue