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

Commit e377a1de authored by Mayank Rana's avatar Mayank Rana
Browse files

usb: phy: qusb: Make sure QUSB PHY is into proper state



On some platforms, QUSB PHY's DVDD related power supply (LDO) is
not always ON. Hence when this power supply is switched off, QUSB
PHY's register configuration is not retained. QUSB PHY state is
unknown when required LDOs are turned ON with USB cable connect
case and may interfere charger detection. Hence use suggested sequence
which involves resetting QUSB PHY and performing few set of QUSB PHY
register configuration to bring QUSB PHY into non-driving mode.

CRs-Fixed: 1046610
Change-Id: Ifc910fcd7df66575be0e0d9d3fc1850bc8831b05
Signed-off-by: default avatarMayank Rana <mrana@codeaurora.org>
parent 6b24dd5a
Loading
Loading
Loading
Loading
+163 −38
Original line number Diff line number Diff line
@@ -129,7 +129,10 @@ struct qusb_phy {
	struct clk		*ref_clk;
	struct clk		*cfg_ahb_clk;
	struct clk		*phy_reset;
	struct clk		*iface_clk;
	struct clk		*core_clk;

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

	bool			vdd_enabled;
	bool			power_enabled;
	bool			clocks_enabled;
	bool			cable_connected;
@@ -159,6 +163,7 @@ struct qusb_phy {
	int			*emu_dcm_reset_seq;
	int			emu_dcm_reset_seq_len;
	spinlock_t		pulse_lock;
	bool			put_into_high_z_state;
};

static void qusb_phy_enable_clocks(struct qusb_phy *qphy, bool on)
@@ -169,14 +174,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;
	}

@@ -184,6 +197,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;
@@ -201,6 +240,47 @@ static int qusb_phy_config_vdd(struct qusb_phy *qphy, int high)
	return ret;
}

static int qusb_phy_vdd(struct qusb_phy *qphy, bool on)
{
	int ret = 0;

	if (!qphy->vdd_enabled && on) {
		dev_dbg(qphy->phy.dev, "TURNING ON VDD\n");
		ret = qusb_phy_config_vdd(qphy, true);
		if (ret) {
			dev_err(qphy->phy.dev, "Unable to config VDD:%d\n",
								ret);
			goto err;
		}

		ret = regulator_enable(qphy->vdd);
		if (ret) {
			dev_err(qphy->phy.dev, "Unable to enable VDD\n");
			goto err;
		}
		qphy->vdd_enabled = true;
	}

	if (qphy->vdd_enabled && !on) {
		dev_dbg(qphy->phy.dev, "TURNING OFF VDD\n");
		ret = regulator_disable(qphy->vdd);
		if (ret) {
			dev_err(qphy->phy.dev, "Unable to disable vdd:%d\n",
									ret);
			goto err;
		}

		ret = qusb_phy_config_vdd(qphy, false);
		if (ret) {
			dev_err(qphy->phy.dev, "Unable unconfig VDD:%d\n", ret);
			goto err;
		}
		qphy->vdd_enabled = false;
	}
err:
	return ret;
}

static int qusb_phy_enable_power(struct qusb_phy *qphy, bool on)
{
	int ret = 0;
@@ -216,18 +296,9 @@ static int qusb_phy_enable_power(struct qusb_phy *qphy, bool on)
	if (!on)
		goto disable_vdda33;

	ret = qusb_phy_config_vdd(qphy, true);
	if (ret) {
		dev_err(qphy->phy.dev, "Unable to config VDD:%d\n",
							ret);
	ret = qusb_phy_vdd(qphy, true);
	if (ret < 0)
		goto err_vdd;
	}

	ret = regulator_enable(qphy->vdd);
	if (ret) {
		dev_err(qphy->phy.dev, "Unable to enable VDD\n");
		goto unconfig_vdd;
	}

	ret = regulator_set_optimum_mode(qphy->vdda18, QUSB2PHY_1P8_HPM_LOAD);
	if (ret < 0) {
@@ -307,16 +378,7 @@ put_vdda18_lpm:
		dev_err(qphy->phy.dev, "Unable to set LPM of vdda18\n");

disable_vdd:
	ret = regulator_disable(qphy->vdd);
	if (ret)
		dev_err(qphy->phy.dev, "Unable to disable vdd:%d\n",
								ret);

unconfig_vdd:
	ret = qusb_phy_config_vdd(qphy, false);
	if (ret)
		dev_err(qphy->phy.dev, "Unable unconfig VDD:%d\n",
								ret);
	ret = qusb_phy_vdd(qphy, false);
err_vdd:
	qphy->power_enabled = false;
	dev_dbg(qphy->phy.dev, "QUSB PHY's regulators are turned OFF.\n");
@@ -339,12 +401,55 @@ static int qusb_phy_update_dpdm(struct usb_phy *phy, int value)
	case POWER_SUPPLY_DP_DM_DPF_DMF:
		dev_dbg(phy->dev, "POWER_SUPPLY_DP_DM_DPF_DMF\n");
		if (!qphy->rm_pulldown) {

			if (qphy->put_into_high_z_state) {

				/* Bring up DVDD */
				ret = qusb_phy_vdd(qphy, true);
				if (ret < 0)
					goto clk_error;
				qusb_phy_gdsc(qphy, true);
				qusb_phy_enable_clocks(qphy, true);

				dev_dbg(phy->dev, "RESET QUSB PHY\n");
				clk_reset(qphy->phy_reset, CLK_RESET_ASSERT);
				usleep_range(100, 150);
				clk_reset(qphy->phy_reset, CLK_RESET_DEASSERT);

				/*
				 * 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();
			}

			ret = qusb_phy_enable_power(qphy, true);
			if (ret >= 0) {
				qphy->rm_pulldown = true;
				dev_dbg(phy->dev, "DP_DM_F: rm_pulldown:%d\n",
						qphy->rm_pulldown);
			}

			if (qphy->put_into_high_z_state) {
				qusb_phy_enable_clocks(qphy, false);
				qusb_phy_gdsc(qphy, false);
			}
		}

		/* Clear QC1 and QC2 registers when rm_pulldown = 1 */
@@ -880,28 +985,20 @@ static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
			/* 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(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_enable_power(qphy, false);
			/*
			 * 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 {
@@ -1086,6 +1183,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");