From 81aa304c8dcb3e45f3e2304223cbf215013adb2e Mon Sep 17 00:00:00 2001 From: Harry Yang Date: Mon, 29 Aug 2016 16:06:50 -0700 Subject: [PATCH 1/4] qcom-charger: Fix a possible null ptr access of pl psy The issue results from a earlier wrong assumption that parallel psy is available when master side is ready. The happens on boot with a strong charger attached, when FCC callback is about to redistribute charging currents while parallel charger has not yet probed, hence pl psy is invalid when accessed. This issue is fixed by introducing parallel charger as a parallel charging voter, which is also convenient for debugging purpose. CRs-Fixed: 1059499 Change-Id: Ic96d30a02c966704d98e047602e4292f576fe448 Signed-off-by: Harry Yang --- drivers/power/qcom-charger/qpnp-smb2.c | 2 ++ drivers/power/qcom-charger/smb-lib.c | 16 +++++++++------- drivers/power/qcom-charger/smb-lib.h | 21 +++++++++++---------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index 8fa4fe301676..2c228cc6f4b2 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -986,6 +986,8 @@ static int smb2_init_hw(struct smb2 *chip) USBIN_ICL_VOTER, true, 0); vote(chg->pl_disable_votable, CHG_STATE_VOTER, true, 0); + vote(chg->pl_disable_votable, + PARALLEL_PSY_VOTER, true, 0); vote(chg->usb_suspend_votable, DEFAULT_VOTER, chip->dt.no_battery, 0); vote(chg->dc_suspend_votable, diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index a869fc592474..5551c5d9df18 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -2178,8 +2178,7 @@ static void smblib_pl_detect_work(struct work_struct *work) struct smb_charger *chg = container_of(work, struct smb_charger, pl_detect_work); - if (!get_effective_result_locked(chg->pl_disable_votable)) - rerun_election(chg->pl_disable_votable); + vote(chg->pl_disable_votable, PARALLEL_PSY_VOTER, false, 0); } #define MINIMUM_PARALLEL_FCC_UA 500000 @@ -2204,7 +2203,7 @@ static void smblib_pl_taper_work(struct work_struct *work) } if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) { - vote(chg->awake_votable, PL_VOTER, true, 0); + vote(chg->awake_votable, PL_TAPER_WORK_RUNNING_VOTER, true, 0); /* Reduce the taper percent by 25 percent */ chg->pl.taper_percent = chg->pl.taper_percent * TAPER_RESIDUAL_PERCENT / 100; @@ -2218,7 +2217,7 @@ static void smblib_pl_taper_work(struct work_struct *work) * Master back to Fast Charge, get out of this round of taper reduction */ done: - vote(chg->awake_votable, PL_VOTER, false, 0); + vote(chg->awake_votable, PL_TAPER_WORK_RUNNING_VOTER, false, 0); } static void clear_hdc_work(struct work_struct *work) @@ -2381,9 +2380,6 @@ int smblib_init(struct smb_charger *chg) return rc; } - chg->bms_psy = power_supply_get_by_name("bms"); - chg->pl.psy = power_supply_get_by_name("parallel"); - rc = smblib_register_notifier(chg); if (rc < 0) { dev_err(chg->dev, @@ -2391,6 +2387,12 @@ int smblib_init(struct smb_charger *chg) return rc; } + chg->bms_psy = power_supply_get_by_name("bms"); + chg->pl.psy = power_supply_get_by_name("parallel"); + if (chg->pl.psy) + vote(chg->pl_disable_votable, PARALLEL_PSY_VOTER, + false, 0); + break; case PARALLEL_SLAVE: break; diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index 1f2457e04ed6..1d34e156792f 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -24,16 +24,17 @@ enum print_reason { PR_MISC = BIT(2), }; -#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" -#define FCC_MAX_RESULT "FCC_MAX_RESULT" -#define THERMAL_DAEMON "THERMAL_DAEMON" +#define DEFAULT_VOTER "DEFAULT_VOTER" +#define USER_VOTER "USER_VOTER" +#define PD_VOTER "PD_VOTER" +#define PL_TAPER_WORK_RUNNING_VOTER "PL_TAPER_WORK_RUNNING_VOTER" +#define PARALLEL_PSY_VOTER "PARALLEL_PSY_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" +#define FCC_MAX_RESULT "FCC_MAX_RESULT" +#define THERMAL_DAEMON "THERMAL_DAEMON" enum smb_mode { PARALLEL_MASTER = 0, From 6e3a5f53c77d5c74008a9edf1a778574616255da Mon Sep 17 00:00:00 2001 From: Harry Yang Date: Fri, 23 Sep 2016 10:52:05 -0700 Subject: [PATCH 2/4] qcom-charger: Fix voting issues on parallel charging The current driver simply skips checking for other events in the typec change interrupt if an vbus error is seen. This could cause parallel charging to never get enabled among other issues. Fix is by simply printing a debug messages for vbus-error and handling other events. CRs-Fixed: 1069575 Change-Id: Ic2df178430b80ade1dea9aff7dbf8e08bb9c2310 Signed-off-by: Harry Yang --- drivers/power/qcom-charger/smb-lib.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index 5551c5d9df18..1d4a89b3cebb 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -1876,7 +1876,7 @@ skip_dpdm_float: } #define MICRO_5P5V 5500000 -#define USB_WEAK_INPUT_MA 1500000 +#define USB_WEAK_INPUT_MA 1400000 static bool is_icl_pl_ready(struct smb_charger *chg) { union power_supply_propval pval = {0, }; @@ -1886,7 +1886,8 @@ static bool is_icl_pl_ready(struct smb_charger *chg) 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; + /* proceed with a convervative value */ + pval.intval = MICRO_5P5V; } if (pval.intval <= MICRO_5P5V) { @@ -2105,11 +2106,6 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) } smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat); - if (stat & TYPEC_VBUS_ERROR_STATUS_BIT) { - dev_err(chg->dev, "IRQ: vbus-error rising\n"); - return IRQ_HANDLED; - } - smblib_handle_typec_cc(chg, (bool)(stat & CC_ATTACHED_BIT)); smblib_handle_typec_debounce_done(chg, @@ -2118,6 +2114,10 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) power_supply_changed(chg->usb_psy); + if (stat & TYPEC_VBUS_ERROR_STATUS_BIT) + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s vbus-error\n", + irq_data->name); + return IRQ_HANDLED; } From 7cb260336d2ece5a200d4dc8eed1b7ad14b68e21 Mon Sep 17 00:00:00 2001 From: Harry Yang Date: Wed, 28 Sep 2016 10:47:29 -0700 Subject: [PATCH 3/4] qcom-charger: Add USBIN proxy votable for parallel charging USB charger voltage votes indenpently, along with ICL through intermediate USBIN votable. This change can correctly capture strong chargers by monitoring input voltage or charger type. CRs-Fixed: 1069575 Change-Id: I36843279fdac909966b3f01b5a6716d191fab903 Signed-off-by: Harry Yang --- drivers/power/qcom-charger/qpnp-smb2.c | 2 +- drivers/power/qcom-charger/smb-lib.c | 100 +++++++++++++------------ drivers/power/qcom-charger/smb-lib.h | 9 ++- 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index 2c228cc6f4b2..4f0e6c676a98 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -983,7 +983,7 @@ static int smb2_init_hw(struct smb2 *chip) /* votes must be cast before configuring software control */ vote(chg->pl_disable_votable, - USBIN_ICL_VOTER, true, 0); + PL_INDIRECT_VOTER, true, 0); vote(chg->pl_disable_votable, CHG_STATE_VOTER, true, 0); vote(chg->pl_disable_votable, diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index 1d4a89b3cebb..836af1541919 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -521,7 +521,7 @@ static int smblib_fcc_max_vote_callback(struct votable *votable, void *data, { struct smb_charger *chg = data; - return vote(chg->fcc_votable, FCC_MAX_RESULT, true, fcc_ua); + return vote(chg->fcc_votable, FCC_MAX_RESULT_VOTER, true, fcc_ua); } static int smblib_fcc_vote_callback(struct votable *votable, void *data, @@ -731,6 +731,17 @@ static int smblib_chg_disable_vote_callback(struct votable *votable, void *data, return 0; } + +static int smblib_pl_enable_indirect_vote_callback(struct votable *votable, + void *data, int chg_enable, const char *client) +{ + struct smb_charger *chg = data; + + vote(chg->pl_disable_votable, PL_INDIRECT_VOTER, !chg_enable, 0); + + return 0; +} + /***************** * OTG REGULATOR * *****************/ @@ -1144,13 +1155,14 @@ int smblib_set_prop_system_temp_level(struct smb_charger *chg, chg->system_temp_level = val->intval; if (chg->system_temp_level == chg->thermal_levels) - return vote(chg->chg_disable_votable, THERMAL_DAEMON, true, 0); + return vote(chg->chg_disable_votable, + THERMAL_DAEMON_VOTER, true, 0); - vote(chg->chg_disable_votable, THERMAL_DAEMON, false, 0); + vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0); if (chg->system_temp_level == 0) - return vote(chg->fcc_votable, THERMAL_DAEMON, false, 0); + return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0); - vote(chg->fcc_votable, THERMAL_DAEMON, true, + vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, true, chg->thermal_mitigation[chg->system_temp_level]); return 0; } @@ -1589,7 +1601,11 @@ int smblib_set_prop_usb_voltage_min(struct smb_charger *chg, return rc; } - chg->voltage_min_uv = val->intval; + if (chg->mode == PARALLEL_MASTER) + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, + min_uv > MICRO_5V, 0); + + chg->voltage_min_uv = min_uv; return rc; } @@ -1607,7 +1623,7 @@ int smblib_set_prop_usb_voltage_max(struct smb_charger *chg, return rc; } - chg->voltage_max_uv = val->intval; + chg->voltage_max_uv = max_uv; return rc; } @@ -1875,50 +1891,23 @@ skip_dpdm_float: return IRQ_HANDLED; } -#define MICRO_5P5V 5500000 -#define USB_WEAK_INPUT_MA 1400000 -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); - /* proceed with a convervative value */ - pval.intval = MICRO_5P5V; - } - - 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; -} - +#define USB_WEAK_INPUT_MA 1400000 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; + int icl_ma; + int rc; + + 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 IRQ_HANDLED; + } if (chg->mode == PARALLEL_MASTER) - vote(chg->pl_disable_votable, USBIN_ICL_VOTER, - !is_icl_pl_ready(chg), 0); + vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, + icl_ma >= USB_WEAK_INPUT_MA, 0); smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); @@ -1955,6 +1944,9 @@ static void smblib_handle_hvdcp_3p0_auth_done(struct smb_charger *chg, if (!rising) return; + if (chg->mode == PARALLEL_MASTER) + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, true, 0); + /* the APSD done handler will set the USB supply type */ apsd_result = smblib_get_apsd_result(chg); smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-3p0-auth-done rising; %s detected\n", @@ -2080,8 +2072,9 @@ static void smblib_handle_typec_debounce_done(struct smb_charger *chg, !rising || sink_attached, 0); if (!rising || sink_attached) { - /* icl votes to disable parallel charging */ - vote(chg->pl_disable_votable, USBIN_ICL_VOTER, true, 0); + /* reset both usbin current and voltage votes */ + vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0); /* reset taper_end voter here */ vote(chg->pl_disable_votable, TAPER_END_VOTER, false, 0); } @@ -2319,6 +2312,15 @@ static int smblib_create_votables(struct smb_charger *chg) return rc; } + chg->pl_enable_votable_indirect = create_votable("PL_ENABLE_INDIRECT", + VOTE_SET_ANY, + smblib_pl_enable_indirect_vote_callback, + chg); + if (IS_ERR(chg->pl_enable_votable_indirect)) { + rc = PTR_ERR(chg->pl_enable_votable_indirect); + return rc; + } + return rc; } @@ -2344,6 +2346,10 @@ static void smblib_destroy_votables(struct smb_charger *chg) destroy_votable(chg->awake_votable); if (chg->pl_disable_votable) destroy_votable(chg->pl_disable_votable); + if (chg->chg_disable_votable) + destroy_votable(chg->chg_disable_votable); + if (chg->pl_enable_votable_indirect) + destroy_votable(chg->pl_enable_votable_indirect); } static void smblib_iio_deinit(struct smb_charger *chg) diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index 1d34e156792f..7a113bbaf8cc 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -29,12 +29,14 @@ enum print_reason { #define PD_VOTER "PD_VOTER" #define PL_TAPER_WORK_RUNNING_VOTER "PL_TAPER_WORK_RUNNING_VOTER" #define PARALLEL_PSY_VOTER "PARALLEL_PSY_VOTER" -#define USBIN_ICL_VOTER "USBIN_ICL_VOTER" +#define PL_INDIRECT_VOTER "PL_INDIRECT_VOTER" +#define USBIN_I_VOTER "USBIN_I_VOTER" +#define USBIN_V_VOTER "USBIN_V_VOTER" #define CHG_STATE_VOTER "CHG_STATE_VOTER" #define TYPEC_SRC_VOTER "TYPEC_SRC_VOTER" #define TAPER_END_VOTER "TAPER_END_VOTER" -#define FCC_MAX_RESULT "FCC_MAX_RESULT" -#define THERMAL_DAEMON "THERMAL_DAEMON" +#define FCC_MAX_RESULT_VOTER "FCC_MAX_RESULT_VOTER" +#define THERMAL_DAEMON_VOTER "THERMAL_DAEMON_VOTER" enum smb_mode { PARALLEL_MASTER = 0, @@ -140,6 +142,7 @@ struct smb_charger { struct votable *awake_votable; struct votable *pl_disable_votable; struct votable *chg_disable_votable; + struct votable *pl_enable_votable_indirect; /* work */ struct work_struct bms_update_work; From 4ed92c2c0074a305b6f44776d71905ace7a2b8e1 Mon Sep 17 00:00:00 2001 From: Harry Yang Date: Tue, 27 Sep 2016 15:59:50 -0700 Subject: [PATCH 4/4] qcom-charger: update qc charger detection for PMICv2 PMICv2 added irq support to tell if DCP is not QC charger. Update charger driver for this HW change. Change-Id: Ic75e2f1d528e6bbb3cf14c842b803bf4ebca8ad8 Signed-off-by: Harry Yang --- drivers/power/qcom-charger/qpnp-smb2.c | 3 +++ drivers/power/qcom-charger/smb-lib.c | 21 +++++++++++++++++++-- drivers/power/qcom-charger/smb-lib.h | 4 ++++ drivers/power/qcom-charger/smb-reg.h | 2 +- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index 4f0e6c676a98..1b63f51088ee 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -1102,6 +1102,7 @@ static int smb2_init_hw(struct smb2 *chip) static int smb2_setup_wa_flags(struct smb2 *chip) { + struct smb_charger *chg = &chip->chg; struct pmic_revid_data *pmic_rev_id; struct device_node *revid_dev_node; @@ -1124,6 +1125,8 @@ static int smb2_setup_wa_flags(struct smb2 *chip) switch (pmic_rev_id->pmic_subtype) { case PMICOBALT_SUBTYPE: + if (pmic_rev_id->rev4 == PMICOBALT_V1P1_REV4) /* PMI rev 1.1 */ + chg->wa_flags |= QC_CHARGER_DETECTION_WA_BIT; break; default: pr_err("PMIC subtype %d not supported\n", diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index 836af1541919..e9c189ae17e7 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -1953,6 +1953,18 @@ static void smblib_handle_hvdcp_3p0_auth_done(struct smb_charger *chg, apsd_result->name); } +static void smblib_handle_hvdcp_check_timeout(struct smb_charger *chg, + bool rising, bool qc_charger) +{ + if (rising && !qc_charger) { + vote(chg->pd_allowed_votable, DEFAULT_VOTER, true, 0); + power_supply_changed(chg->usb_psy); + } + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: smblib_handle_hvdcp_check_timeout %s\n", + rising ? "rising" : "falling"); +} + /* triggers when HVDCP is detected */ static void smblib_handle_hvdcp_detect_done(struct smb_charger *chg, bool rising) @@ -1984,8 +1996,9 @@ static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising) vote(chg->pd_allowed_votable, DEFAULT_VOTER, true, 0); break; case DCP_CHARGER_BIT: - schedule_delayed_work(&chg->hvdcp_detect_work, - msecs_to_jiffies(HVDCP_DET_MS)); + if (chg->wa_flags & QC_CHARGER_DETECTION_WA_BIT) + schedule_delayed_work(&chg->hvdcp_detect_work, + msecs_to_jiffies(HVDCP_DET_MS)); break; default: break; @@ -2019,6 +2032,10 @@ irqreturn_t smblib_handle_usb_source_change(int irq, void *data) smblib_handle_hvdcp_detect_done(chg, (bool)(stat & QC_CHARGER_BIT)); + smblib_handle_hvdcp_check_timeout(chg, + (bool)(stat & HVDCP_CHECK_TIMEOUT_BIT), + (bool)(stat & QC_CHARGER_BIT)); + smblib_handle_hvdcp_3p0_auth_done(chg, (bool)(stat & QC_AUTH_DONE_STATUS_BIT)); diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index 7a113bbaf8cc..c9732c25dfcd 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -44,6 +44,10 @@ enum smb_mode { NUM_MODES, }; +enum { + QC_CHARGER_DETECTION_WA_BIT = BIT(0), +}; + struct smb_regulator { struct regulator_dev *rdev; struct regulator_desc rdesc; diff --git a/drivers/power/qcom-charger/smb-reg.h b/drivers/power/qcom-charger/smb-reg.h index 4a50d2fcbf97..c4ad72e254f9 100644 --- a/drivers/power/qcom-charger/smb-reg.h +++ b/drivers/power/qcom-charger/smb-reg.h @@ -427,7 +427,7 @@ enum { #define APSD_STATUS_REG (USBIN_BASE + 0x07) #define APSD_STATUS_7_BIT BIT(7) -#define APSD_STATUS_6_BIT BIT(6) +#define HVDCP_CHECK_TIMEOUT_BIT BIT(6) #define SLOW_PLUGIN_TIMEOUT_BIT BIT(5) #define ENUMERATION_DONE_BIT BIT(4) #define VADP_CHANGE_DONE_AFTER_AUTH_BIT BIT(3)