Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8995a700 authored by Nicholas Troast's avatar Nicholas Troast Committed by Harry Yang
Browse files

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: default avatarNicholas Troast <ntroast@codeaurora.org>
parent 48798441
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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>
+4 −1
Original line number Diff line number Diff line
@@ -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",
+266 −65
Original line number Diff line number Diff line
@@ -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);
	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;
}

	smblib_otg_cl_config(chg, MICRO_250MA);
static int _smblib_vbus_regulator_disable(struct regulator_dev *rdev)
{
	struct smb_charger *chg = rdev_get_drvdata(rdev);
	int rc;
	u8 stat;

	rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG,
				 ENG_BUCKBOOST_HALT1_8_MODE_BIT, 0);
	if (!chg->external_vconn) {
		rc = smblib_read(chg, RID_CC_CONTROL_7_0_REG, &stat);
		if (rc < 0) {
		smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n",
			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;
		}
	}

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, 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;
	int ret;

	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;
	}

	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);
+10 −4
Original line number Diff line number Diff line
@@ -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);
+1 −0
Original line number Diff line number Diff line
@@ -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)