diff options
-rw-r--r-- | Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt | 5 | ||||
-rw-r--r-- | drivers/power/qcom-charger/qpnp-smb2.c | 5 | ||||
-rw-r--r-- | drivers/power/qcom-charger/smb-lib.c | 331 | ||||
-rw-r--r-- | drivers/power/qcom-charger/smb-lib.h | 14 | ||||
-rw-r--r-- | drivers/power/qcom-charger/smb-reg.h | 1 |
5 files changed, 286 insertions, 70 deletions
diff --git a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt index cedb68820d26..eabdc6a75fbe 100644 --- a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt +++ b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt @@ -35,6 +35,11 @@ Charger specific properties: addition battery properties will be faked such that the device assumes normal operation. +- qcom,external-vconn + Usage: optional + Value type: <empty> + Definition: Boolean flag which indicates VCONN is sourced externally. + - qcom,fcc-max-ua Usage: optional Value type: <u32> diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index a07325102631..98a917273328 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -288,6 +288,9 @@ static int smb2_parse_dt(struct smb2 *chip) chip->dt.no_battery = of_property_read_bool(node, "qcom,batteryless-platform"); + chg->external_vconn = of_property_read_bool(node, + "qcom,external-vconn"); + rc = of_property_read_u32(node, "qcom,fcc-max-ua", &chip->dt.fcc_ua); if (rc < 0) @@ -1522,7 +1525,7 @@ static struct smb2_irq_info smb2_irqs[] = { }, { .name = "otg-overcurrent", - .handler = smblib_handle_debug, + .handler = smblib_handle_otg_overcurrent, }, { .name = "otg-oc-dis-sw-sts", diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index 6d010a11d034..eec96d30d4f9 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -1013,12 +1013,119 @@ static int smblib_apsd_disable_vote_callback(struct votable *votable, return 0; } + +/******************* + * VCONN REGULATOR * + * *****************/ + +static int _smblib_vconn_regulator_enable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + u8 otg_stat, stat4; + int rc = 0; + + if (!chg->external_vconn) { + rc = smblib_read(chg, OTG_STATUS_REG, &otg_stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read OTG status rc=%d\n", rc); + return rc; + } + + if ((otg_stat & OTG_STATE_MASK) != OTG_STATE_ENABLED) { + smblib_err(chg, "Couldn't enable VCONN; OTG is not ready otg_stat=0x%02x\n", + otg_stat); + return -EAGAIN; + } + } + + /* + * VCONN_EN_ORIENTATION is overloaded with overriding the CC pin used + * for Vconn, and it should be set with reverse polarity of CC_OUT. + */ + rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat4); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); + return rc; + } + + stat4 = stat4 & CC_ORIENTATION_BIT ? 0 : VCONN_EN_ORIENTATION_BIT; + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT, + VCONN_EN_VALUE_BIT | stat4); + if (rc < 0) { + smblib_err(chg, "Couldn't enable vconn setting rc=%d\n", rc); + return rc; + } + + return rc; +} + +int smblib_vconn_regulator_enable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + + mutex_lock(&chg->otg_overcurrent_lock); + if (chg->vconn_en) + goto unlock; + + rc = _smblib_vconn_regulator_enable(rdev); + if (rc >= 0) + chg->vconn_en = true; + +unlock: + mutex_unlock(&chg->otg_overcurrent_lock); + return rc; +} + +static int _smblib_vconn_regulator_disable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + VCONN_EN_VALUE_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", rc); + + return rc; +} + +int smblib_vconn_regulator_disable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + + mutex_lock(&chg->otg_overcurrent_lock); + if (!chg->vconn_en) + goto unlock; + + rc = _smblib_vconn_regulator_disable(rdev); + if (rc >= 0) + chg->vconn_en = false; + +unlock: + mutex_unlock(&chg->otg_overcurrent_lock); + return rc; +} + +int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int ret; + + mutex_lock(&chg->otg_overcurrent_lock); + ret = chg->vconn_en; + mutex_unlock(&chg->otg_overcurrent_lock); + return ret; +} + /***************** * OTG REGULATOR * *****************/ #define MAX_SOFTSTART_TRIES 2 -int smblib_vbus_regulator_enable(struct regulator_dev *rdev) +static int _smblib_vbus_regulator_enable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); u8 stat; @@ -1056,110 +1163,103 @@ int smblib_vbus_regulator_enable(struct regulator_dev *rdev) } } while (--tries); - if (tries == 0) + if (tries == 0) { smblib_err(chg, "Timeout waiting for boost softstart rc=%d\n", rc); + return -ETIMEDOUT; + } return rc; } -int smblib_vbus_regulator_disable(struct regulator_dev *rdev) +int smblib_vbus_regulator_enable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc = 0; - rc = smblib_write(chg, CMD_OTG_REG, 0); - if (rc < 0) { - smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc); - return rc; - } - - smblib_otg_cl_config(chg, MICRO_250MA); - - rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG, - ENG_BUCKBOOST_HALT1_8_MODE_BIT, 0); - if (rc < 0) { - smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n", - rc); - return rc; - } + mutex_lock(&chg->otg_overcurrent_lock); + if (chg->otg_en) + goto unlock; + rc = _smblib_vbus_regulator_enable(rdev); + if (rc >= 0) + chg->otg_en = true; +unlock: + mutex_unlock(&chg->otg_overcurrent_lock); return rc; } -int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev) +static int _smblib_vbus_regulator_disable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); - int rc = 0; - u8 cmd; + int rc; + u8 stat; + + if (!chg->external_vconn) { + rc = smblib_read(chg, RID_CC_CONTROL_7_0_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read RID_CC_CONTROL_7_0 rc=%d\n", + rc); + return rc; + } + + /* check if VCONN is enabled on either CC pin */ + if (stat & VCONN_EN_CC_MASK) { + smblib_dbg(chg, PR_MISC, "Killing VCONN before disabling OTG\n"); + rc = _smblib_vconn_regulator_disable(rdev); + if (rc < 0) + smblib_err(chg, "Couldn't disable VCONN rc=%d\n", + rc); + return rc; + } + } - rc = smblib_read(chg, CMD_OTG_REG, &cmd); + rc = smblib_write(chg, CMD_OTG_REG, 0); if (rc < 0) { - smblib_err(chg, "Couldn't read CMD_OTG rc=%d", rc); + smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc); return rc; } - return (cmd & OTG_EN_BIT) ? 1 : 0; -} - -/******************* - * VCONN REGULATOR * - * *****************/ - -int smblib_vconn_regulator_enable(struct regulator_dev *rdev) -{ - struct smb_charger *chg = rdev_get_drvdata(rdev); - u8 stat; - int rc = 0; + smblib_otg_cl_config(chg, MICRO_250MA); - /* - * VCONN_EN_ORIENTATION is overloaded with overriding the CC pin used - * for Vconn, and it should be set with reverse polarity of CC_OUT. - */ - rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); + rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG, + ENG_BUCKBOOST_HALT1_8_MODE_BIT, 0); if (rc < 0) { - smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); + smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n", rc); return rc; } - stat = stat & CC_ORIENTATION_BIT ? 0 : VCONN_EN_ORIENTATION_BIT; - rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, - VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT, - VCONN_EN_VALUE_BIT | stat); - if (rc < 0) - smblib_err(chg, "Couldn't enable vconn setting rc=%d\n", rc); - return rc; + return 0; } -int smblib_vconn_regulator_disable(struct regulator_dev *rdev) +int smblib_vbus_regulator_disable(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); int rc = 0; - rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, - VCONN_EN_VALUE_BIT, 0); - if (rc < 0) - smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", - rc); + mutex_lock(&chg->otg_overcurrent_lock); + if (!chg->otg_en) + goto unlock; + + rc = _smblib_vbus_regulator_disable(rdev); + if (rc >= 0) + chg->otg_en = false; +unlock: + mutex_unlock(&chg->otg_overcurrent_lock); return rc; } -int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev) +int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev) { struct smb_charger *chg = rdev_get_drvdata(rdev); - int rc = 0; - u8 cmd; - - rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, &cmd); - if (rc < 0) { - smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", - rc); - return rc; - } + int ret; - return (cmd & VCONN_EN_VALUE_BIT) ? 1 : 0; + mutex_lock(&chg->otg_overcurrent_lock); + ret = chg->otg_en; + mutex_unlock(&chg->otg_overcurrent_lock); + return ret; } /******************** @@ -2350,6 +2450,66 @@ irqreturn_t smblib_handle_debug(int irq, void *data) return IRQ_HANDLED; } +irqreturn_t smblib_handle_otg_overcurrent(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + u8 stat; + + rc = smblib_read(chg, OTG_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read OTG_INT_RT_STS rc=%d\n", rc); + return IRQ_HANDLED; + } + + if (!(stat & OTG_OVERCURRENT_RT_STS_BIT)) + return IRQ_HANDLED; + + smblib_err(chg, "over-current detected on VBUS\n"); + if (!chg->vbus_vreg || !chg->vbus_vreg->rdev) + return IRQ_HANDLED; + + mutex_lock(&chg->otg_overcurrent_lock); + if (!chg->external_vconn && chg->vconn_en) { + rc = _smblib_vconn_regulator_disable(chg->vconn_vreg->rdev); + if (rc < 0) + smblib_err(chg, "Couldn't disable VCONN rc=%d\n", rc); + } + + rc = _smblib_vbus_regulator_disable(chg->vbus_vreg->rdev); + if (rc < 0) + smblib_err(chg, "Couldn't disable VBUS rc=%d\n", rc); + + /* + * VBUS must be disabled after OC to be ready for the next insertion. + * If the maximum number of attempts have been reached then don't try + * to re-enable. + */ + if (++chg->otg_attempts > OTG_MAX_ATTEMPTS) { + smblib_err(chg, "OTG failed to enable after %d attempts\n", + chg->otg_attempts - 1); + goto unlock; + } + + /* allow the attached device to discharge */ + msleep(250); + + rc = _smblib_vbus_regulator_enable(chg->vbus_vreg->rdev); + if (rc < 0) + smblib_err(chg, "Couldn't enable VBUS rc=%d\n", rc); + + if (!chg->external_vconn && chg->vconn_en) { + rc = _smblib_vconn_regulator_enable(chg->vconn_vreg->rdev); + if (rc < 0) + smblib_err(chg, "Couldn't enable VCONN rc=%d\n", rc); + } + +unlock: + mutex_unlock(&chg->otg_overcurrent_lock); + return IRQ_HANDLED; +} + static void smblib_pl_handle_chg_state_change(struct smb_charger *chg, u8 stat) { bool pl_enabled; @@ -2864,6 +3024,8 @@ static void smblib_handle_typec_removal(struct smb_charger *chg) */ vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, false, 0); + chg->vconn_attempts = 0; + chg->otg_attempts = 0; typec_source_removal(chg); typec_sink_removal(chg); @@ -2968,6 +3130,41 @@ irqreturn_t smblib_handle_usb_typec_change_for_uusb(struct smb_charger *chg) return IRQ_HANDLED; } +static void smblib_handle_vconn_overcurrent(struct smb_charger *chg) +{ + int rc; + + smblib_err(chg, "over-current detected on VCONN\n"); + if (!chg->vconn_vreg || !chg->vconn_vreg->rdev) + return; + + mutex_lock(&chg->otg_overcurrent_lock); + rc = _smblib_vconn_regulator_disable(chg->vconn_vreg->rdev); + if (rc < 0) + smblib_err(chg, "Couldn't disable VCONN rc=%d\n", rc); + + /* + * VCONN must be disabled after OC to be ready for the next insertion. + * If the maximum number of attempts have been reached then don't try + * to re-enable. + */ + if (++chg->vconn_attempts > VCONN_MAX_ATTEMPTS) { + smblib_err(chg, "VCONN failed to enable after %d attempts\n", + chg->vconn_attempts - 1); + goto unlock; + } + + /* allow the attached device to discharge */ + msleep(250); + + rc = _smblib_vconn_regulator_enable(chg->vconn_vreg->rdev); + if (rc < 0) + smblib_err(chg, "Couldn't enable VCONN rc=%d\n", rc); + +unlock: + mutex_unlock(&chg->otg_overcurrent_lock); +} + irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) { struct smb_irq_data *irq_data = data; @@ -3006,6 +3203,9 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s vbus-error\n", irq_data->name); + if (stat4 & TYPEC_VCONN_OVERCURR_STATUS_BIT) + smblib_handle_vconn_overcurrent(chg); + power_supply_changed(chg->usb_psy); smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat4); smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_5 = 0x%02x\n", stat5); @@ -3436,6 +3636,7 @@ int smblib_init(struct smb_charger *chg) int rc = 0; mutex_init(&chg->write_lock); + mutex_init(&chg->otg_overcurrent_lock); INIT_WORK(&chg->bms_update_work, bms_update_work); INIT_WORK(&chg->pl_detect_work, smblib_pl_detect_work); INIT_WORK(&chg->rdstd_cc2_detach_work, rdstd_cc2_detach_work); diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index b65c0211405a..efce7eb987ab 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -52,6 +52,9 @@ enum print_reason { #define HVDCP_INDIRECT_VOTER "HVDCP_INDIRECT_VOTER" #define MICRO_USB_VOTER "MICRO_USB_VOTER" +#define VCONN_MAX_ATTEMPTS 3 +#define OTG_MAX_ATTEMPTS 3 + enum smb_mode { PARALLEL_MASTER = 0, PARALLEL_SLAVE, @@ -153,10 +156,12 @@ struct smb_charger { struct smb_iio iio; int *debug_mask; enum smb_mode mode; + bool external_vconn; /* locks */ struct mutex write_lock; struct mutex ps_change_lock; + struct mutex otg_overcurrent_lock; /* power supplies */ struct power_supply *batt_psy; @@ -210,21 +215,21 @@ struct smb_charger { int pd_active; bool system_suspend_supported; int boost_threshold_ua; - int system_temp_level; int thermal_levels; int *thermal_mitigation; - int otg_cl_ua; int dcp_icl_ua; - int fake_capacity; - bool step_chg_enabled; bool is_hdc; bool chg_done; bool micro_usb_mode; int input_limited_fcc_ua; + bool otg_en; + bool vconn_en; + int otg_attempts; + int vconn_attempts; /* workaround flag */ u32 wa_flags; @@ -266,6 +271,7 @@ int smblib_vconn_regulator_disable(struct regulator_dev *rdev); int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev); irqreturn_t smblib_handle_debug(int irq, void *data); +irqreturn_t smblib_handle_otg_overcurrent(int irq, void *data); irqreturn_t smblib_handle_chg_state_change(int irq, void *data); irqreturn_t smblib_handle_step_chg_state_change(int irq, void *data); irqreturn_t smblib_handle_step_chg_soc_update_fail(int irq, void *data); diff --git a/drivers/power/qcom-charger/smb-reg.h b/drivers/power/qcom-charger/smb-reg.h index a9606ab1944b..5f74e27c7978 100644 --- a/drivers/power/qcom-charger/smb-reg.h +++ b/drivers/power/qcom-charger/smb-reg.h @@ -348,6 +348,7 @@ enum { #define OTG_STATUS_REG (OTG_BASE + 0x09) #define BOOST_SOFTSTART_DONE_BIT BIT(3) #define OTG_STATE_MASK GENMASK(2, 0) +#define OTG_STATE_ENABLED 0x2 /* OTG Interrupt Bits */ #define TESTMODE_CHANGE_DETECT_RT_STS_BIT BIT(3) |