diff options
-rw-r--r-- | drivers/power/qcom-charger/qpnp-smb2.c | 70 | ||||
-rw-r--r-- | drivers/power/qcom-charger/smb-lib.c | 213 | ||||
-rw-r--r-- | drivers/power/qcom-charger/smb-lib.h | 21 |
3 files changed, 272 insertions, 32 deletions
diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index dec35a053778..9e752799affe 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -25,38 +25,45 @@ #include "smb-lib.h" #include "pmic-voter.h" -#define SMB2_DEFAULT_FCC_UA 1000000 +#define SMB2_DEFAULT_FCC_UA 3000000 #define SMB2_DEFAULT_FV_UV 4350000 -#define SMB2_DEFAULT_ICL_UA 1500000 +#define SMB2_DEFAULT_ICL_UA 3000000 static struct smb_params v1_params = { - .fcc = { - .name = "fast charge current", - .reg = FAST_CHARGE_CURRENT_CFG_REG, - .min_u = 0, - .max_u = 4500000, - .step_u = 25000, + .fcc = { + .name = "fast charge current", + .reg = FAST_CHARGE_CURRENT_CFG_REG, + .min_u = 0, + .max_u = 4500000, + .step_u = 25000, }, - .fv = { - .name = "float voltage", - .reg = FLOAT_VOLTAGE_CFG_REG, - .min_u = 2500000, - .max_u = 5000000, - .step_u = 10000, + .fv = { + .name = "float voltage", + .reg = FLOAT_VOLTAGE_CFG_REG, + .min_u = 3487500, + .max_u = 4920000, + .step_u = 7500, }, - .usb_icl = { - .name = "usb input current limit", - .reg = USBIN_CURRENT_LIMIT_CFG_REG, - .min_u = 0, - .max_u = 6000000, - .step_u = 25000, + .usb_icl = { + .name = "usb input current limit", + .reg = USBIN_CURRENT_LIMIT_CFG_REG, + .min_u = 0, + .max_u = 4800000, + .step_u = 25000, }, - .dc_icl = { - .name = "dc input current limit", - .reg = DCIN_CURRENT_LIMIT_CFG_REG, - .min_u = 0, - .max_u = 6000000, - .step_u = 25000, + .icl_stat = { + .name = "input current limit status", + .reg = ICL_STATUS_REG, + .min_u = 0, + .max_u = 4800000, + .step_u = 25000, + }, + .dc_icl = { + .name = "dc input current limit", + .reg = DCIN_CURRENT_LIMIT_CFG_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 25000, }, }; @@ -79,6 +86,11 @@ module_param_named( debug_mask, __debug_mask, int, S_IRUSR | S_IWUSR ); +static int __pl_master_percent = 50; +module_param_named( + pl_master_percent, __pl_master_percent, int, S_IRUSR | S_IWUSR +); + static int smb2_parse_dt(struct smb2 *chip) { struct smb_charger *chg = &chip->chg; @@ -477,6 +489,10 @@ static int smb2_init_hw(struct smb2 *chip) int rc; /* votes must be cast before configuring software control */ + vote(chg->pl_disable_votable, + USBIN_ICL_VOTER, true, 0); + vote(chg->pl_disable_votable, + CHG_STATE_VOTER, true, 0); vote(chg->usb_suspend_votable, DEFAULT_VOTER, chip->dt.suspend_input, 0); vote(chg->dc_suspend_votable, @@ -725,6 +741,8 @@ static int smb2_probe(struct platform_device *pdev) chg->dev = &pdev->dev; chg->param = v1_params; chg->debug_mask = &__debug_mask; + chg->mode = PARALLEL_MASTER; + chg->pl.master_percent = &__pl_master_percent; chg->regmap = dev_get_regmap(chg->dev->parent, NULL); if (!chg->regmap) { diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index 4412918aa1bb..46b824376847 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -82,6 +82,15 @@ unlock: return rc; } +static void smblib_fcc_split_ua(struct smb_charger *chg, int total_fcc, + int *master_ua, int *slave_ua) +{ + int master_percent = min(max(*chg->pl.master_percent, 0), 100); + + *master_ua = (total_fcc * master_percent) / 100; + *slave_ua = (total_fcc - *master_ua) * chg->pl.taper_percent / 100; +} + /******************** * REGISTER GETTERS * ********************/ @@ -330,6 +339,18 @@ static int smblib_detach_usb(struct smb_charger *chg) return rc; } +static struct power_supply *get_parallel_psy(struct smb_charger *chg) +{ + if (chg->pl.psy) + return chg->pl.psy; + + chg->pl.psy = power_supply_get_by_name("parallel"); + if (!chg->pl.psy) + smblib_dbg(chg, PR_MISC, "parallel charger not found\n"); + + return chg->pl.psy; +} + /********************* * VOTABLE CALLBACKS * *********************/ @@ -358,20 +379,49 @@ static int smblib_fcc_vote_callback(struct votable *votable, void *data, { struct smb_charger *chg = data; int rc = 0; + union power_supply_propval pval = {0, }; + int master_ua = fcc_ua, slave_ua; if (fcc_ua < 0) { smblib_dbg(chg, PR_MISC, "No Voter\n"); return 0; } - rc = smblib_set_charge_param(chg, &chg->param.fcc, fcc_ua); - return rc; + if (chg->mode == PARALLEL_MASTER + && !get_effective_result_locked(chg->pl_disable_votable)) { + smblib_fcc_split_ua(chg, fcc_ua, &master_ua, &slave_ua); + + /* + * parallel charger is not disabled, implying that + * chg->pl.psy exists + */ + pval.intval = slave_ua; + rc = power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &pval); + if (rc < 0) { + dev_err(chg->dev, "Could not set parallel fcc, rc=%d\n", + rc); + return rc; + } + + chg->pl.slave_fcc = slave_ua; + } + + rc = smblib_set_charge_param(chg, &chg->param.fcc, master_ua); + if (rc < 0) { + dev_err(chg->dev, "Error in setting fcc, rc=%d\n", rc); + return rc; + } + + return 0; } +#define PARALLEL_FLOAT_VOLTAGE_DELTA_UV 50000 static int smblib_fv_vote_callback(struct votable *votable, void *data, int fv_uv, const char *client) { struct smb_charger *chg = data; + union power_supply_propval pval = {0, }; int rc = 0; if (fv_uv < 0) { @@ -380,7 +430,24 @@ static int smblib_fv_vote_callback(struct votable *votable, void *data, } rc = smblib_set_charge_param(chg, &chg->param.fv, fv_uv); - return rc; + if (rc < 0) { + dev_err(chg->dev, + "Couldn't set floating voltage rc=%d\n", rc); + return rc; + } + + if (chg->mode == PARALLEL_MASTER && get_parallel_psy(chg)) { + pval.intval = fv_uv + PARALLEL_FLOAT_VOLTAGE_DELTA_UV; + rc = power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't set float on parallel rc=%d\n", rc); + return rc; + } + } + + return 0; } #define USBIN_25MA 25000 @@ -474,6 +541,26 @@ static int smblib_awake_vote_callback(struct votable *votable, void *data, static int smblib_pl_disable_vote_callback(struct votable *votable, void *data, int pl_disable, const char *client) { + struct smb_charger *chg = data; + union power_supply_propval pval = {0, }; + int rc; + + if (chg->mode != PARALLEL_MASTER || !get_parallel_psy(chg)) + return 0; + + chg->pl.taper_percent = 100; + rerun_election(chg->fv_votable); + rerun_election(chg->fcc_votable); + + pval.intval = pl_disable; + rc = power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't change slave suspend state rc=%d\n", rc); + return rc; + } + return 0; } @@ -633,7 +720,7 @@ int smblib_get_prop_batt_status(struct smb_charger *chg, stat); stat = stat & BATTERY_CHARGER_STATUS_MASK; - if (stat >= COMPLETED_CHARGE) + if (stat == COMPLETED_CHARGE || stat == INHIBIT_CHARGE) val->intval = POWER_SUPPLY_STATUS_FULL; else val->intval = POWER_SUPPLY_STATUS_CHARGING; @@ -1107,9 +1194,37 @@ irqreturn_t smblib_handle_chg_state_change(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; + union power_supply_propval pval = {0, }; + int rc; smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + if (chg->mode != PARALLEL_MASTER || !get_parallel_psy(chg)) + return IRQ_HANDLED; + + rc = smblib_get_prop_batt_charge_type(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get batt charge type rc=%d\n", rc); + return IRQ_HANDLED; + } + + if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_FAST) + vote(chg->pl_disable_votable, CHG_STATE_VOTER, false, 0); + + if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER + && !get_effective_result_locked(chg->pl_disable_votable)) { + cancel_delayed_work_sync(&chg->pl_taper_work); + schedule_delayed_work(&chg->pl_taper_work, 0); + } + + rc = smblib_get_prop_batt_status(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get batt status type rc=%d\n", rc); + return IRQ_HANDLED; + } + if (pval.intval == POWER_SUPPLY_STATUS_FULL) + vote(chg->pl_disable_votable, TAPER_END_VOTER, false, 0); + return IRQ_HANDLED; } @@ -1183,11 +1298,50 @@ skip_dpdm_float: return IRQ_HANDLED; } +#define MICRO_5P5V 5500000 +#define USB_WEAK_INPUT_MA 1500000 +static bool is_icl_pl_ready(struct smb_charger *chg) +{ + union power_supply_propval pval = {0, }; + int icl_ma; + int rc; + + rc = smblib_get_prop_usb_voltage_now(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get prop usb voltage rc=%d\n", rc); + return false; + } + + if (pval.intval <= MICRO_5P5V) { + rc = smblib_get_charge_param(chg, + &chg->param.icl_stat, &icl_ma); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get ICL status rc=%d\n", + rc); + return false; + } + + if (icl_ma < USB_WEAK_INPUT_MA) + return false; + } + + /* + * Always enable parallel charging when USB INPUT is higher than 5V + * regardless of the AICL results. Assume chargers above 5V are strong + */ + + return true; +} + irqreturn_t smblib_handle_icl_change(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; + if (chg->mode == PARALLEL_MASTER) + vote(chg->pl_disable_votable, USBIN_ICL_VOTER, + !is_icl_pl_ready(chg), 0); + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); return IRQ_HANDLED; @@ -1340,6 +1494,17 @@ static void smblib_handle_typec_debounce_done(struct smb_charger *chg, if (rc < 0) dev_err(chg->dev, "Couldn't get prop typec mode rc=%d\n", rc); + /* + * vote to enable parallel charging if a source is attached, and disable + * otherwise + */ + vote(chg->pl_disable_votable, TYPEC_SRC_VOTER, + !rising || sink_attached, 0); + + /* reset taper_end voter here */ + if (!rising || sink_attached) + vote(chg->pl_disable_votable, TAPER_END_VOTER, false, 0); + smblib_dbg(chg, PR_INTERRUPT, "IRQ: debounce-done %s; Type-C %s detected\n", rising ? "rising" : "falling", smblib_typec_mode_name[pval.intval]); @@ -1394,6 +1559,45 @@ static void smblib_hvdcp_detect_work(struct work_struct *work) } } +#define MINIMUM_PARALLEL_FCC_UA 500000 +#define PL_TAPER_WORK_DELAY_MS 100 +#define TAPER_RESIDUAL_PERCENT 75 +static void smblib_pl_taper_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + pl_taper_work.work); + union power_supply_propval pval = {0, }; + int rc; + + if (chg->pl.slave_fcc < MINIMUM_PARALLEL_FCC_UA) { + vote(chg->pl_disable_votable, TAPER_END_VOTER, true, 0); + goto done; + } + + rc = smblib_get_prop_batt_charge_type(chg, &pval); + if (rc < 0) { + dev_err(chg->dev, "Couldn't get batt charge type rc=%d\n", rc); + goto done; + } + + if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) { + vote(chg->awake_votable, PL_VOTER, true, 0); + /* Reduce the taper percent by 25 percent */ + chg->pl.taper_percent = chg->pl.taper_percent + * TAPER_RESIDUAL_PERCENT / 100; + rerun_election(chg->fcc_votable); + schedule_delayed_work(&chg->pl_taper_work, + msecs_to_jiffies(PL_TAPER_WORK_DELAY_MS)); + return; + } + + /* + * Master back to Fast Charge, get out of this round of taper reduction + */ +done: + vote(chg->awake_votable, PL_VOTER, false, 0); +} + int smblib_create_votables(struct smb_charger *chg) { int rc = 0; @@ -1478,6 +1682,7 @@ int smblib_init(struct smb_charger *chg) mutex_init(&chg->write_lock); INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work); + INIT_DELAYED_WORK(&chg->pl_taper_work, smblib_pl_taper_work); switch (chg->mode) { case PARALLEL_MASTER: diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index 628b76252530..fbadf02d9958 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -26,6 +26,11 @@ enum print_reason { #define DEFAULT_VOTER "DEFAULT_VOTER" #define USER_VOTER "USER_VOTER" #define PD_VOTER "PD_VOTER" +#define PL_VOTER "PL_VOTER" +#define USBIN_ICL_VOTER "USBIN_ICL_VOTER" +#define CHG_STATE_VOTER "CHG_STATE_VOTER" +#define TYPEC_SRC_VOTER "TYPEC_SRC_VOTER" +#define TAPER_END_VOTER "TAPER_END_VOTER" enum smb_mode { PARALLEL_MASTER = 0, @@ -55,9 +60,17 @@ struct smb_params { struct smb_chg_param fcc; struct smb_chg_param fv; struct smb_chg_param usb_icl; + struct smb_chg_param icl_stat; struct smb_chg_param dc_icl; }; +struct parallel_params { + struct power_supply *psy; + int *master_percent; + int taper_percent; + int slave_fcc; +}; + struct smb_charger { struct device *dev; struct regmap *regmap; @@ -74,6 +87,9 @@ struct smb_charger { struct power_supply *usb_psy; struct power_supply_desc usb_psy_desc; + /* parallel charging */ + struct parallel_params pl; + /* regulators */ struct smb_regulator *vbus_vreg; struct smb_regulator *vconn_vreg; @@ -87,12 +103,13 @@ struct smb_charger { struct votable *usb_icl_votable; struct votable *dc_icl_votable; struct votable *pd_allowed_votable; - struct votable *awake_votable; - struct votable *pl_disable_votable; + struct votable *awake_votable; + struct votable *pl_disable_votable; /* work */ struct delayed_work hvdcp_detect_work; struct delayed_work ps_change_timeout_work; + struct delayed_work pl_taper_work; /* cached status */ int voltage_min_uv; |