summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt5
-rw-r--r--drivers/power/qcom-charger/qpnp-smb2.c5
-rw-r--r--drivers/power/qcom-charger/smb-lib.c331
-rw-r--r--drivers/power/qcom-charger/smb-lib.h14
-rw-r--r--drivers/power/qcom-charger/smb-reg.h1
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)