Commit 35192007 authored by Dmitry Osipenko's avatar Dmitry Osipenko Committed by Greg Kroah-Hartman
Browse files

usb: phy: tegra: Support waking up from a low power mode



Support programming of waking up from a low power mode by implementing the
generic set_wakeup() callback of the USB PHY API.

Tested-by: default avatarMatt Merhar <mattmerhar@protonmail.com>
Tested-by: default avatarNicolas Chauvet <kwizart@gmail.com>
Tested-by: default avatarPeter Geis <pgwipeout@gmail.com>
Tested-by: default avatarIon Agorria <ion@agorria.com>
Acked-by: default avatarThierry Reding <treding@nvidia.com>
Acked-by: default avatarPeter Chen <peter.chen@kernel.org>
Signed-off-by: default avatarDmitry Osipenko <digetx@gmail.com>
Link: https://lore.kernel.org/r/20201218120246.7759-3-digetx@gmail.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent b100402e
Loading
Loading
Loading
Loading
+89 −11
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@
#define TEGRA_PORTSC1_RWC_BITS	(PORT_CSC | PORT_PEC | PORT_OCC)

#define USB_SUSP_CTRL				0x400
#define   USB_WAKE_ON_RESUME_EN			BIT(2)
#define   USB_WAKE_ON_CNNT_EN_DEV		BIT(3)
#define   USB_WAKE_ON_DISCON_EN_DEV		BIT(4)
#define   USB_SUSP_CLR				BIT(5)
@@ -56,6 +57,15 @@
#define   USB_SUSP_SET				BIT(14)
#define   USB_WAKEUP_DEBOUNCE_COUNT(x)		(((x) & 0x7) << 16)

#define USB_PHY_VBUS_SENSORS			0x404
#define   B_SESS_VLD_WAKEUP_EN			BIT(6)
#define   B_VBUS_VLD_WAKEUP_EN			BIT(14)
#define   A_SESS_VLD_WAKEUP_EN			BIT(22)
#define   A_VBUS_VLD_WAKEUP_EN			BIT(30)

#define USB_PHY_VBUS_WAKEUP_ID			0x408
#define   VBUS_WAKEUP_WAKEUP_EN			BIT(30)

#define USB1_LEGACY_CTRL			0x410
#define   USB1_NO_LEGACY_MODE			BIT(0)
#define   USB1_VBUS_SENSE_CTL_MASK		(3 << 1)
@@ -334,6 +344,11 @@ static int utmip_pad_power_on(struct tegra_usb_phy *phy)
		writel_relaxed(val, base + UTMIP_BIAS_CFG0);
	}

	if (phy->pad_wakeup) {
		phy->pad_wakeup = false;
		utmip_pad_count--;
	}

	spin_unlock(&utmip_pad_lock);

	clk_disable_unprepare(phy->pad_clk);
@@ -359,6 +374,17 @@ static int utmip_pad_power_off(struct tegra_usb_phy *phy)
		goto ulock;
	}

	/*
	 * In accordance to TRM, OTG and Bias pad circuits could be turned off
	 * to save power if wake is enabled, but the VBUS-change detection
	 * method is board-specific and these circuits may need to be enabled
	 * to generate wakeup event, hence we will just keep them both enabled.
	 */
	if (phy->wakeup_enabled) {
		phy->pad_wakeup = true;
		utmip_pad_count++;
	}

	if (--utmip_pad_count == 0) {
		val = readl_relaxed(base + UTMIP_BIAS_CFG0);
		val |= UTMIP_OTGPD | UTMIP_BIASPD;
@@ -503,11 +529,24 @@ static int utmi_phy_power_on(struct tegra_usb_phy *phy)
		writel_relaxed(val, base + UTMIP_PLL_CFG1);
	}

	val = readl_relaxed(base + USB_SUSP_CTRL);
	val &= ~USB_WAKE_ON_RESUME_EN;
	writel_relaxed(val, base + USB_SUSP_CTRL);

	if (phy->mode == USB_DR_MODE_PERIPHERAL) {
		val = readl_relaxed(base + USB_SUSP_CTRL);
		val &= ~(USB_WAKE_ON_CNNT_EN_DEV | USB_WAKE_ON_DISCON_EN_DEV);
		writel_relaxed(val, base + USB_SUSP_CTRL);

		val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID);
		val &= ~VBUS_WAKEUP_WAKEUP_EN;
		writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID);

		val = readl_relaxed(base + USB_PHY_VBUS_SENSORS);
		val &= ~(A_VBUS_VLD_WAKEUP_EN | A_SESS_VLD_WAKEUP_EN);
		val &= ~(B_VBUS_VLD_WAKEUP_EN | B_SESS_VLD_WAKEUP_EN);
		writel_relaxed(val, base + USB_PHY_VBUS_SENSORS);

		val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0);
		val &= ~UTMIP_PD_CHRG;
		writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0);
@@ -605,31 +644,51 @@ static int utmi_phy_power_off(struct tegra_usb_phy *phy)

	utmi_phy_clk_disable(phy);

	if (phy->mode == USB_DR_MODE_PERIPHERAL) {
		val = readl_relaxed(base + USB_SUSP_CTRL);
		val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0);
		val |= USB_WAKE_ON_CNNT_EN_DEV | USB_WAKEUP_DEBOUNCE_COUNT(5);
		writel_relaxed(val, base + USB_SUSP_CTRL);
	}

	/* PHY won't resume if reset is asserted */
	if (!phy->wakeup_enabled) {
		val = readl_relaxed(base + USB_SUSP_CTRL);
		val |= UTMIP_RESET;
		writel_relaxed(val, base + USB_SUSP_CTRL);
	}

	val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0);
	val |= UTMIP_PD_CHRG;
	writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0);

	if (!phy->wakeup_enabled) {
		val = readl_relaxed(base + UTMIP_XCVR_CFG0);
		val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN |
		       UTMIP_FORCE_PDZI_POWERDOWN;
		writel_relaxed(val, base + UTMIP_XCVR_CFG0);
	}

	val = readl_relaxed(base + UTMIP_XCVR_CFG1);
	val |= UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN |
	       UTMIP_FORCE_PDDR_POWERDOWN;
	writel_relaxed(val, base + UTMIP_XCVR_CFG1);

	if (phy->wakeup_enabled) {
		val = readl_relaxed(base + USB_SUSP_CTRL);
		val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0);
		val |= USB_WAKEUP_DEBOUNCE_COUNT(5);
		val |= USB_WAKE_ON_RESUME_EN;
		writel_relaxed(val, base + USB_SUSP_CTRL);

		/*
		 * Ask VBUS sensor to generate wake event once cable is
		 * connected.
		 */
		if (phy->mode == USB_DR_MODE_PERIPHERAL) {
			val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID);
			val |= VBUS_WAKEUP_WAKEUP_EN;
			writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID);

			val = readl_relaxed(base + USB_PHY_VBUS_SENSORS);
			val |= A_VBUS_VLD_WAKEUP_EN;
			writel_relaxed(val, base + USB_PHY_VBUS_SENSORS);
		}
	}

	return utmip_pad_power_off(phy);
}

@@ -765,6 +824,15 @@ static int ulpi_phy_power_off(struct tegra_usb_phy *phy)
	usleep_range(5000, 6000);
	clk_disable_unprepare(phy->clk);

	/*
	 * Wakeup currently unimplemented for ULPI, thus PHY needs to be
	 * force-resumed.
	 */
	if (WARN_ON_ONCE(phy->wakeup_enabled)) {
		ulpi_phy_power_on(phy);
		return -EOPNOTSUPP;
	}

	return 0;
}

@@ -827,6 +895,15 @@ static void tegra_usb_phy_shutdown(struct usb_phy *u_phy)
	phy->freq = NULL;
}

static int tegra_usb_phy_set_wakeup(struct usb_phy *u_phy, bool enable)
{
	struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);

	phy->wakeup_enabled = enable;

	return 0;
}

static int tegra_usb_phy_set_suspend(struct usb_phy *u_phy, int suspend)
{
	struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
@@ -1198,6 +1275,7 @@ static int tegra_usb_phy_probe(struct platform_device *pdev)
	tegra_phy->u_phy.dev = &pdev->dev;
	tegra_phy->u_phy.init = tegra_usb_phy_init;
	tegra_phy->u_phy.shutdown = tegra_usb_phy_shutdown;
	tegra_phy->u_phy.set_wakeup = tegra_usb_phy_set_wakeup;
	tegra_phy->u_phy.set_suspend = tegra_usb_phy_set_suspend;

	platform_set_drvdata(pdev, tegra_phy);
+2 −0
Original line number Diff line number Diff line
@@ -79,6 +79,8 @@ struct tegra_usb_phy {
	bool is_ulpi_phy;
	struct gpio_desc *reset_gpio;
	struct reset_control *pad_rst;
	bool wakeup_enabled;
	bool pad_wakeup;
	bool powered_on;
};