power: battery: Add support for FCC stepping

On some PMIC designs a sudden increase or decrease in FCC
can impact the PMIC die thermally, causing the XO to drift.
Introduce a logic which gradually ramps up/down the FCC
whenever there is a change in its value, this spreads out
the thermals over-time. The default step rate is 100mA/sec
and the feature can be enabled using the DT property
"qcom,fcc-stepping-enable".

Change-Id: I59ee932b60766208912054f7031cd34151fb5deb
Signed-off-by: Umang Agrawal <uagrawal@codeaurora.org>
This commit is contained in:
Umang Agrawal 2018-05-29 17:24:51 +05:30 committed by Ashay Jaiswal
parent eafe2f3d08
commit 491b7aff28
5 changed files with 367 additions and 59 deletions

View file

@ -183,6 +183,12 @@ Charger specific properties:
Value type: bool
Definition: Boolean flag which when present enables sw compensation for jeita
- qcom,fcc-stepping-enable
Usage: optional
Value type: bool
Definition: Boolean flag which when present enables stepwise change in FCC.
The default stepping rate is 100mA/sec.
=============================================
Second Level Nodes - SMB2 Charger Peripherals
=============================================

View file

@ -40,12 +40,14 @@
#define ICL_CHANGE_VOTER "ICL_CHANGE_VOTER"
#define PL_INDIRECT_VOTER "PL_INDIRECT_VOTER"
#define USBIN_I_VOTER "USBIN_I_VOTER"
#define FCC_STEPPER_VOTER "FCC_STEPPER_VOTER"
struct pl_data {
int pl_mode;
int slave_pct;
int taper_pct;
int slave_fcc_ua;
int main_fcc_ua;
int restricted_current;
bool restricted_charging_enabled;
struct votable *fcc_votable;
@ -59,6 +61,7 @@ struct pl_data {
struct work_struct pl_disable_forever_work;
struct delayed_work pl_taper_work;
struct delayed_work pl_awake_work;
struct delayed_work fcc_step_update_work;
struct power_supply *main_psy;
struct power_supply *pl_psy;
struct power_supply *batt_psy;
@ -66,6 +69,13 @@ struct pl_data {
int charge_type;
int total_settled_ua;
int pl_settled_ua;
int fcc_step_update;
int main_step_fcc_dir;
int main_step_fcc_count;
int main_step_fcc_residual;
int parallel_step_fcc_dir;
int parallel_step_fcc_count;
int parallel_step_fcc_residual;
struct class qcom_batt_class;
struct wakeup_source *pl_ws;
struct notifier_block nb;
@ -380,6 +390,10 @@ done:
* FCC *
**********/
#define EFFICIENCY_PCT 80
#define FCC_STEP_SIZE_UA 100000
#define FCC_STEP_UPDATE_DELAY_MS 1000
#define STEP_UP 1
#define STEP_DOWN -1
static void get_fcc_split(struct pl_data *chip, int total_ua,
int *master_ua, int *slave_ua)
{
@ -432,6 +446,43 @@ static void get_fcc_split(struct pl_data *chip, int total_ua,
*slave_ua = (*slave_ua * chip->taper_pct) / 100;
}
static void get_fcc_step_update_params(struct pl_data *chip, int main_fcc_ua,
int parallel_fcc_ua)
{
union power_supply_propval pval = {0, };
int rc;
/* Read current FCC of main charger */
rc = power_supply_get_property(chip->main_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
if (rc < 0) {
pr_err("Couldn't get main charger current fcc, rc=%d\n", rc);
return;
}
chip->main_fcc_ua = pval.intval;
chip->main_step_fcc_dir = (main_fcc_ua > pval.intval) ?
STEP_UP : STEP_DOWN;
chip->main_step_fcc_count = abs((main_fcc_ua - pval.intval) /
FCC_STEP_SIZE_UA);
chip->main_step_fcc_residual = (main_fcc_ua - pval.intval) %
FCC_STEP_SIZE_UA;
chip->parallel_step_fcc_dir = (parallel_fcc_ua > chip->slave_fcc_ua) ?
STEP_UP : STEP_DOWN;
chip->parallel_step_fcc_count = abs((parallel_fcc_ua -
chip->slave_fcc_ua) / FCC_STEP_SIZE_UA);
chip->parallel_step_fcc_residual = (parallel_fcc_ua -
chip->slave_fcc_ua) % FCC_STEP_SIZE_UA;
pr_debug("Main FCC Stepper parameters: main_step_direction: %d, main_step_count: %d, main_residual_fcc: %d\n",
chip->main_step_fcc_dir, chip->main_step_fcc_count,
chip->main_step_fcc_residual);
pr_debug("Parallel FCC Stepper parameters: parallel_step_direction: %d, parallel_step_count: %d, parallel_residual_fcc: %d\n",
chip->parallel_step_fcc_dir, chip->parallel_step_fcc_count,
chip->parallel_step_fcc_residual);
}
static int pl_fcc_vote_callback(struct votable *votable, void *data,
int total_fcc_ua, const char *client)
{
@ -445,21 +496,57 @@ static int pl_fcc_vote_callback(struct votable *votable, void *data,
if (!chip->main_psy)
return 0;
if (!chip->batt_psy) {
chip->batt_psy = power_supply_get_by_name("battery");
if (!chip->batt_psy)
return 0;
rc = power_supply_get_property(chip->batt_psy,
POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE, &pval);
if (rc < 0) {
pr_err("Couldn't read FCC step update status, rc=%d\n",
rc);
return rc;
}
chip->fcc_step_update = pval.intval;
pr_debug("FCC Stepper %s\n",
pval.intval ? "enabled" : "disabled");
}
if (chip->fcc_step_update)
cancel_delayed_work_sync(&chip->fcc_step_update_work);
if (chip->pl_mode == POWER_SUPPLY_PL_NONE
|| get_effective_result_locked(chip->pl_disable_votable)) {
if (chip->fcc_step_update) {
vote(chip->pl_awake_votable, FCC_STEPPER_VOTER,
true, 0);
get_fcc_step_update_params(chip, total_fcc_ua, 0);
schedule_delayed_work(&chip->fcc_step_update_work, 0);
return 0;
}
pval.intval = total_fcc_ua;
rc = power_supply_set_property(chip->main_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
&pval);
if (rc < 0)
pr_err("Couldn't set main fcc, rc=%d\n", rc);
return rc;
}
if (chip->pl_mode != POWER_SUPPLY_PL_NONE) {
get_fcc_split(chip, total_fcc_ua,
&master_fcc_ua, &slave_fcc_ua);
if (chip->fcc_step_update) {
vote(chip->pl_awake_votable, FCC_STEPPER_VOTER,
true, 0);
get_fcc_step_update_params(chip, master_fcc_ua,
slave_fcc_ua);
schedule_delayed_work(&chip->fcc_step_update_work, 0);
} else {
/*
* If there is an increase in slave share
* (Also handles parallel enable case)
@ -474,7 +561,8 @@ static int pl_fcc_vote_callback(struct votable *votable, void *data,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
&pval);
if (rc < 0) {
pr_err("Could not set main fcc, rc=%d\n", rc);
pr_err("Could not set main fcc, rc=%d\n",
rc);
return rc;
}
@ -507,11 +595,13 @@ static int pl_fcc_vote_callback(struct votable *votable, void *data,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
&pval);
if (rc < 0) {
pr_err("Could not set main fcc, rc=%d\n", rc);
pr_err("Could not set main fcc, rc=%d\n",
rc);
return rc;
}
}
}
}
pl_dbg(chip, PR_PARALLEL, "master_fcc=%d slave_fcc=%d distribution=(%d/%d)\n",
master_fcc_ua, slave_fcc_ua,
@ -521,6 +611,192 @@ static int pl_fcc_vote_callback(struct votable *votable, void *data,
return 0;
}
static void fcc_step_update_work(struct work_struct *work)
{
struct pl_data *chip = container_of(work,
struct pl_data, fcc_step_update_work.work);
union power_supply_propval pval = {0, };
int reschedule_ms = 0, rc = 0;
int main_fcc = chip->main_fcc_ua;
int parallel_fcc = chip->slave_fcc_ua;
if (!chip->usb_psy) {
chip->usb_psy = power_supply_get_by_name("usb");
if (!chip->usb_psy) {
pr_err("Couldn't get usb psy\n");
return;
}
}
/* Check whether USB is present or not */
rc = power_supply_get_property(chip->usb_psy,
POWER_SUPPLY_PROP_PRESENT, &pval);
if (rc < 0) {
pr_err("Couldn't get USB Present status, rc=%d\n", rc);
return;
}
/*
* If USB is not present, then disable parallel and
* Main FCC to the effective value of FCC votable and exit.
*/
if (!pval.intval) {
/* Disable parallel */
parallel_fcc = 0;
pval.intval = 1;
rc = power_supply_set_property(chip->pl_psy,
POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
if (rc < 0)
pr_err("Couldn't change slave suspend state rc=%d\n",
rc);
main_fcc = get_effective_result_locked(chip->fcc_votable);
pval.intval = main_fcc;
rc = power_supply_set_property(chip->main_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
if (rc < 0) {
pr_err("Couldn't set main charger fcc, rc=%d\n", rc);
return;
}
goto stepper_exit;
}
if (chip->main_step_fcc_count) {
main_fcc += (FCC_STEP_SIZE_UA * chip->main_step_fcc_dir);
chip->main_step_fcc_count--;
reschedule_ms = FCC_STEP_UPDATE_DELAY_MS;
} else if (chip->main_step_fcc_residual) {
main_fcc += chip->main_step_fcc_residual;
chip->main_step_fcc_residual = 0;
}
if (chip->parallel_step_fcc_count) {
parallel_fcc += (FCC_STEP_SIZE_UA *
chip->parallel_step_fcc_dir);
chip->parallel_step_fcc_count--;
reschedule_ms = FCC_STEP_UPDATE_DELAY_MS;
} else if (chip->parallel_step_fcc_residual) {
parallel_fcc += chip->parallel_step_fcc_residual;
chip->parallel_step_fcc_residual = 0;
}
if (chip->pl_mode == POWER_SUPPLY_PL_NONE ||
get_effective_result_locked(chip->pl_disable_votable)) {
/* Set Parallel FCC */
pval.intval = parallel_fcc;
rc = power_supply_set_property(chip->pl_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
if (rc < 0) {
pr_err("Couldn't set parallel charger fcc, rc=%d\n",
rc);
return;
}
/* Set main FCC */
pval.intval = main_fcc;
rc = power_supply_set_property(chip->main_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
if (rc < 0) {
pr_err("Couldn't set main charger fcc, rc=%d\n", rc);
return;
}
if (parallel_fcc < MINIMUM_PARALLEL_FCC_UA) {
pval.intval = 1;
rc = power_supply_set_property(chip->pl_psy,
POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
if (rc < 0) {
pr_err("Couldn't change slave suspend state rc=%d\n",
rc);
return;
}
}
} else {
if (parallel_fcc < chip->slave_fcc_ua) {
pval.intval = parallel_fcc;
rc = power_supply_set_property(chip->pl_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
&pval);
if (rc < 0) {
pr_err("Couldn't set parallel charger fcc, rc=%d\n",
rc);
return;
}
pval.intval = main_fcc;
rc = power_supply_set_property(chip->main_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
&pval);
if (rc < 0) {
pr_err("Couldn't set main charger fcc, rc=%d\n",
rc);
return;
}
} else {
pval.intval = main_fcc;
rc = power_supply_set_property(chip->main_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
&pval);
if (rc < 0) {
pr_err("Couldn't set main charger fcc, rc=%d\n",
rc);
return;
}
pval.intval = parallel_fcc;
rc = power_supply_set_property(chip->pl_psy,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
&pval);
if (rc < 0) {
pr_err("Couldn't set parallel charger fcc, rc=%d\n",
rc);
return;
}
}
rc = power_supply_get_property(chip->pl_psy,
POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
if (rc < 0) {
pr_err("Couldn't get slave suspend status, rc=%d\n",
rc);
return;
}
/*
* Enable parallel charger only if it was disabled earlier and
* configured slave fcc is greater than or equal to 100mA.
*/
if (pval.intval == 1 && parallel_fcc >= 100000) {
pval.intval = 0;
rc = power_supply_set_property(chip->pl_psy,
POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
if (rc < 0) {
pr_err("Couldn't change slave suspend state rc=%d\n",
rc);
return;
}
if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN) ||
(chip->pl_mode ==
POWER_SUPPLY_PL_USBIN_USBIN_EXT))
split_settled(chip);
}
}
stepper_exit:
chip->main_fcc_ua = main_fcc;
chip->slave_fcc_ua = parallel_fcc;
if (reschedule_ms) {
schedule_delayed_work(&chip->fcc_step_update_work,
msecs_to_jiffies(reschedule_ms));
pr_debug("Rescheduling FCC_STEPPER work\n");
} else {
vote(chip->pl_awake_votable, FCC_STEPPER_VOTER, false, 0);
}
}
#define PARALLEL_FLOAT_VOLTAGE_DELTA_UV 50000
static int pl_fv_vote_callback(struct votable *votable, void *data,
int fv_uv, const char *client)
@ -660,6 +936,9 @@ static int pl_disable_vote_callback(struct votable *votable,
chip->total_settled_ua = 0;
chip->pl_settled_ua = 0;
/* Cancel FCC step change work */
cancel_delayed_work_sync(&chip->fcc_step_update_work);
if (!pl_disable) { /* enable */
/* keep system awake to talk to slave charger through i2c */
cancel_delayed_work_sync(&chip->pl_awake_work);
@ -685,6 +964,7 @@ static int pl_disable_vote_callback(struct votable *votable,
* PARALLEL_PSY_VOTER keeps it disabled unless a pl_psy
* is seen.
*/
if (!chip->fcc_step_update) {
pval.intval = 0;
rc = power_supply_set_property(chip->pl_psy,
POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
@ -692,9 +972,11 @@ static int pl_disable_vote_callback(struct votable *votable,
pr_err("Couldn't change slave suspend state rc=%d\n",
rc);
if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
|| (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT))
if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN) ||
(chip->pl_mode ==
POWER_SUPPLY_PL_USBIN_USBIN_EXT))
split_settled(chip);
}
/*
* we could have been enabled while in taper mode,
* start the taper work if so
@ -715,6 +997,7 @@ static int pl_disable_vote_callback(struct votable *votable,
|| (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT))
split_settled(chip);
if (!chip->fcc_step_update) {
/* pl_psy may be NULL while in the disable branch */
if (chip->pl_psy) {
pval.intval = 1;
@ -724,6 +1007,8 @@ static int pl_disable_vote_callback(struct votable *votable,
pr_err("Couldn't change slave suspend state rc=%d\n",
rc);
}
}
rerun_election(chip->fcc_votable);
rerun_election(chip->fv_votable);
@ -1118,6 +1403,7 @@ int qcom_batt_init(void)
INIT_DELAYED_WORK(&chip->pl_taper_work, pl_taper_work);
INIT_WORK(&chip->pl_disable_forever_work, pl_disable_forever_work);
INIT_DELAYED_WORK(&chip->pl_awake_work, pl_awake_work);
INIT_DELAYED_WORK(&chip->fcc_step_update_work, fcc_step_update_work);
rc = pl_register_notifier(chip);
if (rc < 0) {
@ -1172,6 +1458,7 @@ void qcom_batt_deinit(void)
cancel_delayed_work_sync(&chip->pl_taper_work);
cancel_work_sync(&chip->pl_disable_forever_work);
cancel_delayed_work_sync(&chip->pl_awake_work);
cancel_delayed_work_sync(&chip->fcc_step_update_work);
power_supply_unreg_notifier(&chip->nb);
destroy_votable(chip->pl_enable_votable_indirect);

View file

@ -325,6 +325,9 @@ static int smb2_parse_dt(struct smb2 *chip)
if (rc < 0)
chg->otg_delay_ms = OTG_DEFAULT_DEGLITCH_TIME_MS;
chg->fcc_stepper_mode = of_property_read_bool(node,
"qcom,fcc-stepping-enable");
return 0;
}
@ -941,6 +944,7 @@ static enum power_supply_property smb2_batt_props[] = {
POWER_SUPPLY_PROP_RERUN_AICL,
POWER_SUPPLY_PROP_DP_DM,
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE,
};
static int smb2_batt_get_prop(struct power_supply *psy,
@ -1049,6 +1053,9 @@ static int smb2_batt_get_prop(struct power_supply *psy,
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
rc = smblib_get_prop_batt_charge_counter(chg, val);
break;
case POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE:
val->intval = chg->fcc_stepper_mode;
break;
default:
pr_err("batt power supply prop %d not supported\n", psp);
return -EINVAL;

View file

@ -3360,7 +3360,8 @@ void smblib_usb_plugin_locked(struct smb_charger *chg)
rc = smblib_request_dpdm(chg, true);
if (rc < 0)
smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc);
if (chg->fcc_stepper_mode)
vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0);
/* Schedule work to enable parallel charger */
vote(chg->awake_votable, PL_DELAY_VOTER, true, 0);
schedule_delayed_work(&chg->pl_enable_work,
@ -3379,6 +3380,11 @@ void smblib_usb_plugin_locked(struct smb_charger *chg)
}
}
/* Force 1500mA FCC on removal */
if (chg->fcc_stepper_mode)
vote(chg->fcc_votable, FCC_STEPPER_VOTER,
true, 1500000);
rc = smblib_request_dpdm(chg, false);
if (rc < 0)
smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc);

View file

@ -67,6 +67,7 @@ enum print_reason {
#define WEAK_CHARGER_VOTER "WEAK_CHARGER_VOTER"
#define WBC_VOTER "WBC_VOTER"
#define OV_VOTER "OV_VOTER"
#define FCC_STEPPER_VOTER "FCC_STEPPER_VOTER"
#define VCONN_MAX_ATTEMPTS 3
#define OTG_MAX_ATTEMPTS 3
@ -346,6 +347,7 @@ struct smb_charger {
u8 float_cfg;
bool use_extcon;
bool otg_present;
bool fcc_stepper_mode;
/* workaround flag */
u32 wa_flags;