summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Troast <ntroast@codeaurora.org>2016-12-05 10:22:22 -0800
committerNicholas Troast <ntroast@codeaurora.org>2017-01-10 11:38:45 -0800
commit9645aa1f612dec51ac8d31c663af195c04d65cee (patch)
tree6ea46e3a9a930c20c9db8949f155481e9058c5e1
parenta6d83d2e8ed916c5a097c7c047dc39cb9183b0e9 (diff)
smb-lib: fix Type-C removal detection with OTG
When VCONN is enabled while OTG is disabled the CC line which is not configured for VCONN can be internally pulled down. If the Type-C plug were removed then Type-C detection would still see that Rd is applied and not detect the removal. Fix this by ensuring that OTG is enabled while VCONN is enabled. If OTG were disabled due to an over-current event then VCONN must also be disabled. Implement a retry mechanism if over-current is detected on either VCONN or VBUS. Change-Id: Iccfb923bce8f06c7c1270943211ce134ea9ef616 Signed-off-by: Nicholas Troast <ntroast@codeaurora.org>
-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)