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

Commit ff1d25fc authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "usb-phy-qusb: powerdown PHY during disconnect to avoid leakage"

parents 38fab880 62caca35
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -160,6 +160,9 @@ Optional properties:
   "efuse_addr": EFUSE address to read and update analog tune parameter.
   "emu_phy_base" : phy base address used for programming emulation target phy.
   "ref_clk_addr" : ref_clk bcr address used for on/off ref_clk before reset.
   "tcsr_clamp_dig_n" : To enable/disable digital clamp to the phy. When
   de-asserted, it will prevent random leakage from qusb2 phy resulting from
   out of sequence turn on/off of 1p8, 3p3 and DVDD regulators.
   "refgen_north_bg_reg" : address used to read REFGEN status for overriding QUSB PHY register.
 - clocks: a list of phandles to the PHY clocks. Use as per
   Documentation/devicetree/bindings/clock/clock-bindings.txt
@@ -179,6 +182,8 @@ Optional properties:
 - qcom,major-rev: provide major revision number to differentiate power up sequence. default is 2.0
 - pinctrl-names/pinctrl-0/1: The GPIOs configured as output function. Names represents "active"
   state when attached in host mode and "suspend" state when detached.
 - qcom,tune2-efuse-correction: The value to be adjusted from fused value for
   improved rise/fall times.

Example:
	qusb_phy: qusb@f9b39000 {
+249 −34
Original line number Diff line number Diff line
@@ -45,6 +45,8 @@
#define FREEZIO_N			BIT(1)
#define POWER_DOWN			BIT(0)

#define QUSB2PHY_PORT_TEST_CTRL		0xB8

#define QUSB2PHY_PWR_CTRL1		0x210
#define PWR_CTRL1_CLAMP_N_EN		BIT(1)
#define PWR_CTRL1_POWR_DOWN		BIT(0)
@@ -68,10 +70,7 @@
#define QUSB2PHY_PORT_TUNE2             0x84
#define QUSB2PHY_PORT_TUNE3             0x88
#define QUSB2PHY_PORT_TUNE4             0x8C

/* In case Efuse register shows zero, use this value */
#define TUNE2_DEFAULT_HIGH_NIBBLE	0xB
#define TUNE2_DEFAULT_LOW_NIBBLE	0x3
#define QUSB2PHY_PORT_TUNE5             0x90

/* Get TUNE2's high nibble value read from efuse */
#define TUNE2_HIGH_NIBBLE_VAL(val, pos, mask)	((val >> pos) & mask)
@@ -98,21 +97,42 @@

#define QUSB2PHY_REFCLK_ENABLE		BIT(0)

unsigned int tune2;
module_param(tune2, uint, S_IRUGO | S_IWUSR);
static unsigned int tune1;
module_param(tune1, uint, 0644);
MODULE_PARM_DESC(tune1, "QUSB PHY TUNE1");

static unsigned int tune2;
module_param(tune2, uint, 0644);
MODULE_PARM_DESC(tune2, "QUSB PHY TUNE2");

static unsigned int tune3;
module_param(tune3, uint, 0644);
MODULE_PARM_DESC(tune3, "QUSB PHY TUNE3");

static unsigned int tune4;
module_param(tune4, uint, 0644);
MODULE_PARM_DESC(tune4, "QUSB PHY TUNE4");

static unsigned int tune5;
module_param(tune5, uint, 0644);
MODULE_PARM_DESC(tune5, "QUSB PHY TUNE5");


struct qusb_phy {
	struct usb_phy		phy;
	void __iomem		*base;
	void __iomem		*tune2_efuse_reg;
	void __iomem		*ref_clk_base;
	void __iomem		*tcsr_clamp_dig_n;

	struct clk		*ref_clk_src;
	struct clk		*ref_clk;
	struct clk		*cfg_ahb_clk;
	struct reset_control	*phy_reset;
	struct clk		*iface_clk;
	struct clk		*core_clk;

	struct regulator	*gdsc;
	struct regulator	*vdd;
	struct regulator	*vdda33;
	struct regulator	*vdda18;
@@ -124,6 +144,7 @@ struct qusb_phy {
	u32			tune2_val;
	int			tune2_efuse_bit_pos;
	int			tune2_efuse_num_of_bits;
	int			tune2_efuse_correction;

	bool			power_enabled;
	bool			clocks_enabled;
@@ -145,6 +166,8 @@ struct qusb_phy {
	int			phy_pll_reset_seq_len;
	int			*emu_dcm_reset_seq;
	int			emu_dcm_reset_seq_len;
	bool			put_into_high_z_state;
	struct mutex		phy_lock;
};

static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
@@ -155,14 +178,22 @@ static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
	if (!qphy->clocks_enabled && on) {
		clk_prepare_enable(qphy->ref_clk_src);
		clk_prepare_enable(qphy->ref_clk);
		clk_prepare_enable(qphy->iface_clk);
		clk_prepare_enable(qphy->core_clk);
		clk_prepare_enable(qphy->cfg_ahb_clk);
		qphy->clocks_enabled = true;
	}

	if (qphy->clocks_enabled && !on) {
		clk_disable_unprepare(qphy->cfg_ahb_clk);
		/*
		 * FSM depedency beween iface_clk and core_clk.
		 * Hence turned off core_clk before iface_clk.
		 */
		clk_disable_unprepare(qphy->core_clk);
		clk_disable_unprepare(qphy->iface_clk);
		clk_disable_unprepare(qphy->ref_clk);
		clk_disable_unprepare(qphy->ref_clk_src);
		clk_disable_unprepare(qphy->cfg_ahb_clk);
		qphy->clocks_enabled = false;
	}

@@ -170,6 +201,32 @@ static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
						qphy->clocks_enabled);
}

static int qusb_phy_gdsc(struct qusb_phy *qphy, bool on)
{
	int ret;

	if (IS_ERR_OR_NULL(qphy->gdsc))
		return -EPERM;

	if (on) {
		dev_dbg(qphy->phy.dev, "TURNING ON GDSC\n");
		ret = regulator_enable(qphy->gdsc);
		if (ret) {
			dev_err(qphy->phy.dev, "unable to enable gdsc\n");
			return ret;
		}
	} else {
		dev_dbg(qphy->phy.dev, "TURNING OFF GDSC\n");
		ret = regulator_disable(qphy->gdsc);
		if (ret) {
			dev_err(qphy->phy.dev, "unable to disable gdsc\n");
			return ret;
		}
	}

	return ret;
}

static int qusb_phy_config_vdd(struct qusb_phy *qphy, int high)
{
	int min, ret;
@@ -313,6 +370,7 @@ static void qusb_phy_get_tune2_param(struct qusb_phy *qphy)
{
	u8 num_of_bits;
	u32 bit_mask = 1;
	u8 reg_val;

	pr_debug("%s(): num_of_bits:%d bit_pos:%d\n", __func__,
				qphy->tune2_efuse_num_of_bits,
@@ -326,9 +384,8 @@ static void qusb_phy_get_tune2_param(struct qusb_phy *qphy)

	/*
	 * Read EFUSE register having TUNE2 parameter's high nibble.
	 * If efuse register shows value as 0x0, then use default value
	 * as 0xB as high nibble. Otherwise use efuse register based
	 * value for this purpose.
	 * If efuse register shows value as 0x0, then use previous value
	 * as it is. Otherwise use efuse register based value for this purpose.
	 */
	qphy->tune2_val = readl_relaxed(qphy->tune2_efuse_reg);
	pr_debug("%s(): bit_mask:%d efuse based tune2 value:%d\n",
@@ -337,12 +394,24 @@ static void qusb_phy_get_tune2_param(struct qusb_phy *qphy)
	qphy->tune2_val = TUNE2_HIGH_NIBBLE_VAL(qphy->tune2_val,
				qphy->tune2_efuse_bit_pos, bit_mask);

	if (!qphy->tune2_val)
		qphy->tune2_val = TUNE2_DEFAULT_HIGH_NIBBLE;
	/* Update higher nibble of TUNE2 value for better rise/fall times */
	if (qphy->tune2_efuse_correction && qphy->tune2_val) {
		if (qphy->tune2_efuse_correction > 5 ||
				qphy->tune2_efuse_correction < -10)
			pr_warn("Correction value is out of range : %d\n",
					qphy->tune2_efuse_correction);
		else
			qphy->tune2_val = qphy->tune2_val +
						qphy->tune2_efuse_correction;
	}

	reg_val = readb_relaxed(qphy->base + QUSB2PHY_PORT_TUNE2);
	if (qphy->tune2_val) {
		reg_val  &= 0x0f;
		reg_val |= (qphy->tune2_val << 4);
	}

	/* Get TUNE2 byte value using high and low nibble value */
	qphy->tune2_val = ((qphy->tune2_val << 0x4) |
					TUNE2_DEFAULT_LOW_NIBBLE);
	qphy->tune2_val = reg_val;
}

static void qusb_phy_write_seq(void __iomem *base, u32 *seq, int cnt,
@@ -450,7 +519,7 @@ static int qusb_phy_init(struct usb_phy *phy)
	 * and try to read EFUSE value only once i.e. not every USB
	 * cable connect case.
	 */
	if (qphy->tune2_efuse_reg) {
	if (qphy->tune2_efuse_reg && !tune2) {
		if (!qphy->tune2_val)
			qusb_phy_get_tune2_param(qphy);

@@ -460,13 +529,29 @@ static int qusb_phy_init(struct usb_phy *phy)
				qphy->base + QUSB2PHY_PORT_TUNE2);
	}

	/* If tune2 modparam set, override tune2 value */
	if (tune2) {
		pr_debug("%s(): (modparam) TUNE2 val:0x%02x\n",
						__func__, tune2);
	/* If tune modparam set, override tune value */

	pr_debug("%s():userspecified modparams TUNEX val:0x%x %x %x %x %x\n",
				__func__, tune1, tune2, tune3, tune4, tune5);
	if (tune1)
		writel_relaxed(tune1,
				qphy->base + QUSB2PHY_PORT_TUNE1);

	if (tune2)
		writel_relaxed(tune2,
				qphy->base + QUSB2PHY_PORT_TUNE2);
	}

	if (tune3)
		writel_relaxed(tune3,
				qphy->base + QUSB2PHY_PORT_TUNE3);

	if (tune4)
		writel_relaxed(tune4,
				qphy->base + QUSB2PHY_PORT_TUNE4);

	if (tune5)
		writel_relaxed(tune5,
				qphy->base + QUSB2PHY_PORT_TUNE5);

	/* ensure above writes are completed before re-enabling PHY */
	wmb();
@@ -596,27 +681,55 @@ static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
			writel_relaxed(intr_mask,
				qphy->base + QUSB2PHY_PORT_INTR_CTRL);

			if (linestate & (LINESTATE_DP | LINESTATE_DM)) {
				/* enable phy auto-resume */
				writel_relaxed(0x0C,
					qphy->base + QUSB2PHY_PORT_TEST_CTRL);
				/* flush the previous write before next write */
				wmb();
				writel_relaxed(0x04,
					qphy->base + QUSB2PHY_PORT_TEST_CTRL);
			}


			dev_dbg(phy->dev, "%s: intr_mask = %x\n",
			__func__, intr_mask);

			/* Makes sure that above write goes through */
			wmb();

			qusb_phy_enable_clocks(qphy, false);
		} else { /* Disconnect case */
			mutex_lock(&qphy->phy_lock);
			/* Disable all interrupts */
			writel_relaxed(0x00,
				qphy->base + QUSB2PHY_PORT_INTR_CTRL);
			/*
			 * Phy in non-driving mode leaves Dp and Dm lines in
			 * high-Z state. Controller power collapse is not
			 * switching phy to non-driving mode causing charger
			 * detection failure. Bring phy to non-driving mode by
			 * overriding controller output via UTMI interface.
			 */
			writel_relaxed(TERM_SELECT | XCVR_SELECT_FS |
				OP_MODE_NON_DRIVE,
				qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
			writel_relaxed(UTMI_ULPI_SEL | UTMI_TEST_MUX_SEL,
				qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);

			/* Disable PHY */
			writel_relaxed(POWER_DOWN,
				qphy->base + QUSB2PHY_PORT_POWERDOWN);
			/* Make sure that above write is completed */
			wmb();

			qusb_phy_enable_clocks(qphy, false);
			if (qphy->tcsr_clamp_dig_n)
				writel_relaxed(0x0,
					qphy->tcsr_clamp_dig_n);
			/* Do not disable power rails if there is vote for it */
			if (!qphy->dpdm_enable)
				qusb_phy_enable_power(qphy, false);
			else
				dev_dbg(phy->dev, "race with rm_pulldown. Keep ldo ON\n");
			mutex_unlock(&qphy->phy_lock);

			/*
			 * Set put_into_high_z_state to true so next USB
			 * cable connect, DPF_DMF request performs PHY
			 * reset and put it into high-z state. For bootup
			 * with or without USB cable, it doesn't require
			 * to put QUSB PHY into high-z state.
			 */
			qphy->put_into_high_z_state = true;
		}
		qphy->suspended = true;
	} else {
@@ -629,6 +742,9 @@ static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
				qphy->base + QUSB2PHY_PORT_INTR_CTRL);
		} else {
			qusb_phy_enable_power(qphy, true);
			if (qphy->tcsr_clamp_dig_n)
				writel_relaxed(0x1,
					qphy->tcsr_clamp_dig_n);
			qusb_phy_enable_clocks(qphy, true);
		}
		qphy->suspended = false;
@@ -669,15 +785,61 @@ static int qusb_phy_dpdm_regulator_enable(struct regulator_dev *rdev)
	dev_dbg(qphy->phy.dev, "%s dpdm_enable:%d\n",
				__func__, qphy->dpdm_enable);

	mutex_lock(&qphy->phy_lock);
	if (!qphy->dpdm_enable) {
		ret = qusb_phy_enable_power(qphy, true);
		if (ret < 0) {
			dev_dbg(qphy->phy.dev,
				"dpdm regulator enable failed:%d\n", ret);
			mutex_unlock(&qphy->phy_lock);
			return ret;
		}
		qphy->dpdm_enable = true;
		if (qphy->put_into_high_z_state) {
			if (qphy->tcsr_clamp_dig_n)
				writel_relaxed(0x1,
				qphy->tcsr_clamp_dig_n);

			qusb_phy_gdsc(qphy, true);
			qusb_phy_enable_clocks(qphy, true);

			dev_dbg(qphy->phy.dev, "RESET QUSB PHY\n");
			ret = reset_control_assert(qphy->phy_reset);
			if (ret)
				dev_err(qphy->phy.dev, "phyassert failed\n");
			usleep_range(100, 150);
			ret = reset_control_deassert(qphy->phy_reset);
			if (ret)
				dev_err(qphy->phy.dev, "deassert failed\n");

			/*
			 * Phy in non-driving mode leaves Dp and Dm
			 * lines in high-Z state. Controller power
			 * collapse is not switching phy to non-driving
			 * mode causing charger detection failure. Bring
			 * phy to non-driving mode by overriding
			 * controller output via UTMI interface.
			 */
			writel_relaxed(TERM_SELECT | XCVR_SELECT_FS |
				OP_MODE_NON_DRIVE,
				qphy->base + QUSB2PHY_PORT_UTMI_CTRL1);
			writel_relaxed(UTMI_ULPI_SEL |
				UTMI_TEST_MUX_SEL,
				qphy->base + QUSB2PHY_PORT_UTMI_CTRL2);


			/* Disable PHY */
			writel_relaxed(CLAMP_N_EN | FREEZIO_N |
					POWER_DOWN,
					qphy->base + QUSB2PHY_PORT_POWERDOWN);
			/* Make sure that above write is completed */
			wmb();

			qusb_phy_enable_clocks(qphy, false);
			qusb_phy_gdsc(qphy, false);
		}
	}
	mutex_unlock(&qphy->phy_lock);

	return ret;
}
@@ -690,19 +852,25 @@ static int qusb_phy_dpdm_regulator_disable(struct regulator_dev *rdev)
	dev_dbg(qphy->phy.dev, "%s dpdm_enable:%d\n",
				__func__, qphy->dpdm_enable);

	mutex_lock(&qphy->phy_lock);
	if (qphy->dpdm_enable) {
		if (!qphy->cable_connected) {
			if (qphy->tcsr_clamp_dig_n)
				writel_relaxed(0x0,
					qphy->tcsr_clamp_dig_n);
			dev_dbg(qphy->phy.dev, "turn off for HVDCP case\n");
			ret = qusb_phy_enable_power(qphy, false);
			if (ret < 0) {
				dev_dbg(qphy->phy.dev,
					"dpdm regulator disable failed:%d\n",
					ret);
				mutex_unlock(&qphy->phy_lock);
				return ret;
			}
		}
		qphy->dpdm_enable = false;
	}
	mutex_unlock(&qphy->phy_lock);

	return ret;
}
@@ -794,6 +962,9 @@ static int qusb_phy_probe(struct platform_device *pdev)
						"qcom,tune2-efuse-num-bits",
						&qphy->tune2_efuse_num_of_bits);
			}
			of_property_read_u32(dev->of_node,
						"qcom,tune2-efuse-correction",
						&qphy->tune2_efuse_correction);

			if (ret) {
				dev_err(dev, "DT Value for tune2 efuse is invalid.\n");
@@ -829,6 +1000,17 @@ static int qusb_phy_probe(struct platform_device *pdev)
		}
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
						"tcsr_clamp_dig_n_1p8");
	if (res) {
		qphy->tcsr_clamp_dig_n = devm_ioremap_nocache(dev,
				res->start, resource_size(res));
		if (IS_ERR(qphy->tcsr_clamp_dig_n)) {
			dev_err(dev, "err reading tcsr_clamp_dig_n\n");
			qphy->tcsr_clamp_dig_n = NULL;
		}
	}

	qphy->ref_clk_src = devm_clk_get(dev, "ref_clk_src");
	if (IS_ERR(qphy->ref_clk_src))
		dev_dbg(dev, "clk get failed for ref_clk_src\n");
@@ -847,6 +1029,34 @@ static int qusb_phy_probe(struct platform_device *pdev)
	if (IS_ERR(qphy->phy_reset))
		return PTR_ERR(qphy->phy_reset);

	if (of_property_match_string(dev->of_node,
		"clock-names", "iface_clk") >= 0) {
		qphy->iface_clk = devm_clk_get(dev, "iface_clk");
		if (IS_ERR(qphy->iface_clk)) {
			ret = PTR_ERR(qphy->iface_clk);
			qphy->iface_clk = NULL;
		if (ret == -EPROBE_DEFER)
			return ret;
			dev_err(dev, "couldn't get iface_clk(%d)\n", ret);
		}
	}

	if (of_property_match_string(dev->of_node,
		"clock-names", "core_clk") >= 0) {
		qphy->core_clk = devm_clk_get(dev, "core_clk");
		if (IS_ERR(qphy->core_clk)) {
			ret = PTR_ERR(qphy->core_clk);
			qphy->core_clk = NULL;
			if (ret == -EPROBE_DEFER)
				return ret;
			dev_err(dev, "couldn't get core_clk(%d)\n", ret);
		}
	}

	qphy->gdsc = devm_regulator_get(dev, "USB3_GDSC");
	if (IS_ERR(qphy->gdsc))
		qphy->gdsc = NULL;

	qphy->emulation = of_property_read_bool(dev->of_node,
					"qcom,emulation");

@@ -981,6 +1191,7 @@ static int qusb_phy_probe(struct platform_device *pdev)
		return PTR_ERR(qphy->vdda18);
	}

	mutex_init(&qphy->phy_lock);
	platform_set_drvdata(pdev, qphy);

	qphy->phy.label			= "msm-qusb-phy";
@@ -1010,6 +1221,10 @@ static int qusb_phy_probe(struct platform_device *pdev)
	if (ret)
		usb_remove_phy(&qphy->phy);

	/* de-assert clamp dig n to reduce leakage on 1p8 upon boot up */
	if (qphy->tcsr_clamp_dig_n)
		writel_relaxed(0x0, qphy->tcsr_clamp_dig_n);

	return ret;
}