linux-usb.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk
@ 2019-06-14  7:48 JC Kuo
  2019-06-14  7:48 ` [PATCH 6/8] phy: tegra: xusb: t210: support wake and sleepwalk JC Kuo
                   ` (3 more replies)
  0 siblings, 4 replies; 9+ messages in thread
From: JC Kuo @ 2019-06-14  7:48 UTC (permalink / raw)
  To: gregkh, thierry.reding, jonathanh, pdeschrijver, afrid
  Cc: linux-tegra, linux-usb, devicetree, nkristam, skomatineni, JC Kuo

This commit implements Tegra210 PMC USB 2.0 (UTMI and HSIC) Sleepwalk
programming sequence. With Sleepwalk enabled, XUSB host controller
can be put into ELPG (Engine Level PowerGate) state when controller
is idle to save power. The Sleepwalk logic is in charge of wake event
detection and maintain resume signal accordingly till XUSB host
controller is bring out of ELPG.

Signed-off-by: JC Kuo <jckuo@nvidia.com>
---
 drivers/soc/tegra/pmc.c | 462 ++++++++++++++++++++++++++++++++++++++++
 include/soc/tegra/pmc.h |  13 ++
 2 files changed, 475 insertions(+)

diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 8878720dd779..8a143cfc83b3 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -142,6 +142,142 @@
 #define  TEGRA_SMC_PMC_READ	0xaa
 #define  TEGRA_SMC_PMC_WRITE	0xbb
 
+/* USB2 SLEEPWALK registers */
+#define UTMIP(_port, _offset1, _offset2) \
+		(((_port) <= 2) ? (_offset1) : (_offset2))
+
+#define PMC_UTMIP_UHSIC_SLEEP_CFG(x)	UTMIP(x, 0x1fc, 0x4d0)
+#define   UTMIP_MASTER_ENABLE(x)		UTMIP(x, BIT(8 * (x)), BIT(0))
+#define   UTMIP_FSLS_USE_PMC(x)			UTMIP(x, BIT(8 * (x) + 1), \
+							BIT(1))
+#define   UTMIP_PCTRL_USE_PMC(x)		UTMIP(x, BIT(8 * (x) + 2), \
+							BIT(2))
+#define   UTMIP_TCTRL_USE_PMC(x)		UTMIP(x, BIT(8 * (x) + 3), \
+							BIT(3))
+#define   UTMIP_WAKE_VAL(_port, _value)		(((_value) & 0xf) << \
+					(UTMIP(_port, 8 * (_port) + 4, 4)))
+#define   UTMIP_WAKE_VAL_NONE(_port)		UTMIP_WAKE_VAL(_port, 12)
+#define   UTMIP_WAKE_VAL_ANY(_port)		UTMIP_WAKE_VAL(_port, 15)
+
+#define PMC_UTMIP_UHSIC_SLEEP_CFG1	(0x4d0)
+#define   UTMIP_RPU_SWITC_LOW_USE_PMC_PX(x)	BIT((x) + 8)
+#define   UTMIP_RPD_CTRL_USE_PMC_PX(x)		BIT((x) + 16)
+
+#define PMC_UTMIP_MASTER_CONFIG		(0x274)
+#define   UTMIP_PWR(x)				UTMIP(x, BIT(x), BIT(4))
+#define   UHSIC_PWR(x)				BIT(3)
+
+#define PMC_USB_DEBOUNCE_DEL		(0xec)
+#define   DEBOUNCE_VAL(x)			(((x) & 0xffff) << 0)
+#define   UTMIP_LINE_DEB_CNT(x)			(((x) & 0xf) << 16)
+#define   UHSIC_LINE_DEB_CNT(x)			(((x) & 0xf) << 20)
+
+#define PMC_UTMIP_UHSIC_FAKE(x)		UTMIP(x, 0x218, 0x294)
+#define   UTMIP_FAKE_USBOP_VAL(x)		UTMIP(x, BIT(4 * (x)), BIT(8))
+#define   UTMIP_FAKE_USBON_VAL(x)		UTMIP(x, BIT(4 * (x) + 1), \
+							BIT(9))
+#define   UTMIP_FAKE_USBOP_EN(x)		UTMIP(x, BIT(4 * (x) + 2), \
+							BIT(10))
+#define   UTMIP_FAKE_USBON_EN(x)		UTMIP(x, BIT(4 * (x) + 3), \
+							BIT(11))
+
+#define PMC_UTMIP_UHSIC_SLEEPWALK_CFG(x)	UTMIP(x, 0x200, 0x288)
+#define   UTMIP_LINEVAL_WALK_EN(x)		UTMIP(x, BIT(8 * (x) + 7), \
+							BIT(15))
+
+#define PMC_USB_AO			(0xf0)
+#define   USBOP_VAL_PD(x)			UTMIP(x, BIT(4 * (x)), BIT(20))
+#define   USBON_VAL_PD(x)			UTMIP(x, BIT(4 * (x) + 1), \
+							BIT(21))
+#define   STROBE_VAL_PD(x)			BIT(12)
+#define   DATA0_VAL_PD(x)			BIT(13)
+#define   DATA1_VAL_PD				BIT(24)
+
+#define PMC_UTMIP_UHSIC_SAVED_STATE(x)	UTMIP(x, 0x1f0, 0x280)
+#define   SPEED(_port, _value)			(((_value) & 0x3) << \
+						(UTMIP(_port, 8 * (_port), 8)))
+#define   UTMI_HS(_port)			SPEED(_port, 0)
+#define   UTMI_FS(_port)			SPEED(_port, 1)
+#define   UTMI_LS(_port)			SPEED(_port, 2)
+#define   UTMI_RST(_port)			SPEED(_port, 3)
+
+#define PMC_UTMIP_UHSIC_TRIGGERS		(0x1ec)
+#define   UTMIP_CLR_WALK_PTR(x)			UTMIP(x, BIT(x), BIT(16))
+#define   UTMIP_CAP_CFG(x)			UTMIP(x, BIT((x) + 4), BIT(17))
+#define   UTMIP_CLR_WAKE_ALARM(x)		UTMIP(x, BIT((x) + 12), \
+							BIT(19))
+#define   UHSIC_CLR_WALK_PTR			BIT(3)
+#define   UHSIC_CLR_WAKE_ALARM			BIT(15)
+
+#define PMC_UTMIP_SLEEPWALK_PX(x)	UTMIP(x, 0x204 + (4 * (x)), \
+							0x4e0)
+/* phase A */
+#define   UTMIP_USBOP_RPD_A			BIT(0)
+#define   UTMIP_USBON_RPD_A			BIT(1)
+#define   UTMIP_AP_A				BIT(4)
+#define   UTMIP_AN_A				BIT(5)
+#define   UTMIP_HIGHZ_A				BIT(6)
+/* phase B */
+#define   UTMIP_USBOP_RPD_B			BIT(8)
+#define   UTMIP_USBON_RPD_B			BIT(9)
+#define   UTMIP_AP_B				BIT(12)
+#define   UTMIP_AN_B				BIT(13)
+#define   UTMIP_HIGHZ_B				BIT(14)
+/* phase C */
+#define   UTMIP_USBOP_RPD_C			BIT(16)
+#define   UTMIP_USBON_RPD_C			BIT(17)
+#define   UTMIP_AP_C				BIT(20)
+#define   UTMIP_AN_C				BIT(21)
+#define   UTMIP_HIGHZ_C				BIT(22)
+/* phase D */
+#define   UTMIP_USBOP_RPD_D			BIT(24)
+#define   UTMIP_USBON_RPD_D			BIT(25)
+#define   UTMIP_AP_D				BIT(28)
+#define   UTMIP_AN_D				BIT(29)
+#define   UTMIP_HIGHZ_D				BIT(30)
+
+#define PMC_UTMIP_UHSIC_LINE_WAKEUP	(0x26c)
+#define   UTMIP_LINE_WAKEUP_EN(x)		UTMIP(x, BIT(x), BIT(4))
+#define   UHSIC_LINE_WAKEUP_EN			BIT(3)
+
+#define PMC_UTMIP_TERM_PAD_CFG		(0x1f8)
+#define   PCTRL_VAL(x)				(((x) & 0x3f) << 1)
+#define   TCTRL_VAL(x)				(((x) & 0x3f) << 7)
+
+#define PMC_UTMIP_PAD_CFGX(x)		(0x4c0 + (4 * (x)))
+#define   RPD_CTRL_PX(x)			(((x) & 0x1f) << 22)
+
+#define PMC_UHSIC_SLEEP_CFG	PMC_UTMIP_UHSIC_SLEEP_CFG(0)
+#define   UHSIC_MASTER_ENABLE			BIT(24)
+#define   UHSIC_WAKE_VAL(_value)		(((_value) & 0xf) << 28)
+#define   UHSIC_WAKE_VAL_SD10			UHSIC_WAKE_VAL(2)
+#define   UHSIC_WAKE_VAL_NONE			UHSIC_WAKE_VAL(12)
+
+#define PMC_UHSIC_FAKE			PMC_UTMIP_UHSIC_FAKE(0)
+#define   UHSIC_FAKE_STROBE_VAL			BIT(12)
+#define   UHSIC_FAKE_DATA_VAL			BIT(13)
+#define   UHSIC_FAKE_STROBE_EN			BIT(14)
+#define   UHSIC_FAKE_DATA_EN			BIT(15)
+
+#define PMC_UHSIC_SAVED_STATE		PMC_UTMIP_UHSIC_SAVED_STATE(0)
+#define   UHSIC_MODE(_value)			(((_value) & 0x1) << 24)
+#define   UHSIC_HS				UHSIC_MODE(0)
+#define   UHSIC_RST				UHSIC_MODE(1)
+
+#define PMC_UHSIC_SLEEPWALK_CFG		PMC_UTMIP_UHSIC_SLEEPWALK_CFG(0)
+#define   UHSIC_WAKE_WALK_EN			BIT(30)
+#define   UHSIC_LINEVAL_WALK_EN			BIT(31)
+
+#define PMC_UHSIC_SLEEPWALK_P0		(0x210)
+#define   UHSIC_DATA0_RPD_A			BIT(1)
+#define   UHSIC_DATA0_RPU_B			BIT(11)
+#define   UHSIC_DATA0_RPU_C			BIT(19)
+#define   UHSIC_DATA0_RPU_D			BIT(27)
+#define   UHSIC_STROBE_RPU_A			BIT(2)
+#define   UHSIC_STROBE_RPD_B			BIT(8)
+#define   UHSIC_STROBE_RPD_C			BIT(16)
+#define   UHSIC_STROBE_RPD_D			BIT(24)
+
 struct tegra_powergate {
 	struct generic_pm_domain genpd;
 	struct tegra_pmc *pmc;
@@ -689,6 +825,332 @@ static int tegra_genpd_power_off(struct generic_pm_domain *domain)
 	return err;
 }
 
+/* T210 USB2 SLEEPWALK APIs */
+int tegra_pmc_utmi_phy_enable_sleepwalk(int port, enum usb_device_speed speed,
+					struct tegra_utmi_pad_config *config)
+{
+	u32 reg;
+
+	pr_debug("PMC %s : port %d, speed %d\n", __func__, port, speed);
+
+	/* ensure sleepwalk logic is disabled */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+	reg &= ~UTMIP_MASTER_ENABLE(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+
+	/* ensure sleepwalk logics are in low power mode */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_MASTER_CONFIG);
+	reg |= UTMIP_PWR(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_MASTER_CONFIG);
+
+	/* set debounce time */
+	reg = tegra_pmc_readl(pmc, PMC_USB_DEBOUNCE_DEL);
+	reg &= ~UTMIP_LINE_DEB_CNT(~0);
+	reg |= UTMIP_LINE_DEB_CNT(0x1);
+	tegra_pmc_writel(pmc, reg, PMC_USB_DEBOUNCE_DEL);
+
+	/* ensure fake events of sleepwalk logic are desiabled */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_FAKE(port));
+	reg &= ~(UTMIP_FAKE_USBOP_VAL(port) | UTMIP_FAKE_USBON_VAL(port) |
+			UTMIP_FAKE_USBOP_EN(port) | UTMIP_FAKE_USBON_EN(port));
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_FAKE(port));
+
+	/* ensure wake events of sleepwalk logic are not latched */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+	reg &= ~UTMIP_LINE_WAKEUP_EN(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+
+	/* disable wake event triggers of sleepwalk logic */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+	reg &= ~UTMIP_WAKE_VAL(port, ~0);
+	reg |= UTMIP_WAKE_VAL_NONE(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+
+	/* power down the line state detectors of the pad */
+	reg = tegra_pmc_readl(pmc, PMC_USB_AO);
+	reg |= (USBOP_VAL_PD(port) | USBON_VAL_PD(port));
+	tegra_pmc_writel(pmc, reg, PMC_USB_AO);
+
+	/* save state per speed */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SAVED_STATE(port));
+	reg &= ~SPEED(port, ~0);
+	if (speed == USB_SPEED_HIGH)
+		reg |= UTMI_HS(port);
+	else if (speed == USB_SPEED_FULL)
+		reg |= UTMI_FS(port);
+	else if (speed == USB_SPEED_LOW)
+		reg |= UTMI_LS(port);
+	else
+		reg |= UTMI_RST(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SAVED_STATE(port));
+
+	/* enable the trigger of the sleepwalk logic */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEPWALK_CFG(port));
+	reg |= UTMIP_LINEVAL_WALK_EN(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEPWALK_CFG(port));
+
+	/* reset the walk pointer and clear the alarm of the sleepwalk logic,
+	 * as well as capture the configuration of the USB2.0 pad
+	 */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_TRIGGERS);
+	reg |= (UTMIP_CLR_WALK_PTR(port) | UTMIP_CLR_WAKE_ALARM(port) |
+		UTMIP_CAP_CFG(port));
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_TRIGGERS);
+
+	/* program electrical parameters read from XUSB PADCTL */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_TERM_PAD_CFG);
+	reg &= ~(TCTRL_VAL(~0) | PCTRL_VAL(~0));
+	reg |= (TCTRL_VAL(config->tctrl) | PCTRL_VAL(config->pctrl));
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_TERM_PAD_CFG);
+
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_PAD_CFGX(port));
+	reg &= ~RPD_CTRL_PX(~0);
+	reg |= RPD_CTRL_PX(config->rpd_ctrl);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_PAD_CFGX(port));
+
+	/* setup the pull-ups and pull-downs of the signals during the four
+	 * stages of sleepwalk.
+	 * if device is connected, program sleepwalk logic to maintain a J and
+	 * keep driving K upon seeing remote wake.
+	 */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_SLEEPWALK_PX(port));
+	reg = (UTMIP_USBOP_RPD_A | UTMIP_USBOP_RPD_B | UTMIP_USBOP_RPD_C |
+		UTMIP_USBOP_RPD_D);
+	reg |= (UTMIP_USBON_RPD_A | UTMIP_USBON_RPD_B | UTMIP_USBON_RPD_C |
+		UTMIP_USBON_RPD_D);
+	if (speed == USB_SPEED_UNKNOWN) {
+		reg |= (UTMIP_HIGHZ_A | UTMIP_HIGHZ_B | UTMIP_HIGHZ_C |
+			UTMIP_HIGHZ_D);
+	} else if ((speed == USB_SPEED_HIGH) || (speed == USB_SPEED_FULL)) {
+		/* J state: D+/D- = high/low, K state: D+/D- = low/high */
+		reg |= UTMIP_HIGHZ_A;
+		reg |= UTMIP_AP_A;
+		reg |= (UTMIP_AN_B | UTMIP_AN_C | UTMIP_AN_D);
+	} else if (speed == USB_SPEED_LOW) {
+		/* J state: D+/D- = low/high, K state: D+/D- = high/low */
+		reg |= UTMIP_HIGHZ_A;
+		reg |= UTMIP_AN_A;
+		reg |= (UTMIP_AP_B | UTMIP_AP_C | UTMIP_AP_D);
+	}
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_SLEEPWALK_PX(port));
+
+	/* power up the line state detectors of the pad */
+	reg = tegra_pmc_readl(pmc, PMC_USB_AO);
+	reg &= ~(USBOP_VAL_PD(port) | USBON_VAL_PD(port));
+	tegra_pmc_writel(pmc, reg, PMC_USB_AO);
+
+	usleep_range(50, 100);
+
+	/* switch the electric control of the USB2.0 pad to PMC */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+	reg |= (UTMIP_FSLS_USE_PMC(port) | UTMIP_PCTRL_USE_PMC(port) |
+			UTMIP_TCTRL_USE_PMC(port));
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG1);
+	reg |= (UTMIP_RPD_CTRL_USE_PMC_PX(port) |
+			UTMIP_RPU_SWITC_LOW_USE_PMC_PX(port));
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG1);
+
+	/* set the wake signaling trigger events */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+	reg &= ~UTMIP_WAKE_VAL(port, ~0);
+	reg |= UTMIP_WAKE_VAL_ANY(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+
+	/* enable the wake detection */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+	reg |= UTMIP_MASTER_ENABLE(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+	reg |= UTMIP_LINE_WAKEUP_EN(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+
+	return 0;
+}
+EXPORT_SYMBOL(tegra_pmc_utmi_phy_enable_sleepwalk);
+
+int tegra_pmc_utmi_phy_disable_sleepwalk(int port)
+{
+	u32 reg;
+
+	pr_debug("PMC %s : port %d\n", __func__, port);
+
+	/* disable the wake detection */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+	reg &= ~UTMIP_MASTER_ENABLE(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+	reg &= ~UTMIP_LINE_WAKEUP_EN(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+
+	/* switch the electric control of the USB2.0 pad to XUSB or USB2 */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+	reg &= ~(UTMIP_FSLS_USE_PMC(port) | UTMIP_PCTRL_USE_PMC(port) |
+			UTMIP_TCTRL_USE_PMC(port));
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG1);
+	reg &= ~(UTMIP_RPD_CTRL_USE_PMC_PX(port) |
+			UTMIP_RPU_SWITC_LOW_USE_PMC_PX(port));
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG1);
+
+	/* disable wake event triggers of sleepwalk logic */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+	reg &= ~UTMIP_WAKE_VAL(port, ~0);
+	reg |= UTMIP_WAKE_VAL_NONE(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port));
+
+	/* power down the line state detectors of the port */
+	reg = tegra_pmc_readl(pmc, PMC_USB_AO);
+	reg |= (USBOP_VAL_PD(port) | USBON_VAL_PD(port));
+	tegra_pmc_writel(pmc, reg, PMC_USB_AO);
+
+	/* clear alarm of the sleepwalk logic */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_TRIGGERS);
+	reg |= UTMIP_CLR_WAKE_ALARM(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_TRIGGERS);
+
+	return 0;
+}
+EXPORT_SYMBOL(tegra_pmc_utmi_phy_disable_sleepwalk);
+
+int tegra_pmc_hsic_phy_enable_sleepwalk(int port)
+{
+	u32 reg;
+
+	pr_debug("PMC %s : port %dn", __func__, port);
+
+	/* ensure sleepwalk logic is disabled */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG);
+	reg &= ~UHSIC_MASTER_ENABLE;
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG);
+
+	/* ensure sleepwalk logics are in low power mode */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_MASTER_CONFIG);
+	reg |= UHSIC_PWR(port);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_MASTER_CONFIG);
+
+	/* set debounce time */
+	reg = tegra_pmc_readl(pmc, PMC_USB_DEBOUNCE_DEL);
+	reg &= ~UHSIC_LINE_DEB_CNT(~0);
+	reg |= UHSIC_LINE_DEB_CNT(0x1);
+	tegra_pmc_writel(pmc, reg, PMC_USB_DEBOUNCE_DEL);
+
+	/* ensure fake events of sleepwalk logic are desiabled */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_FAKE);
+	reg &= ~(UHSIC_FAKE_STROBE_VAL | UHSIC_FAKE_DATA_VAL |
+			UHSIC_FAKE_STROBE_EN | UHSIC_FAKE_DATA_EN);
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_FAKE);
+
+	/* ensure wake events of sleepwalk logic are not latched */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+	reg &= ~UHSIC_LINE_WAKEUP_EN;
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+
+	/* disable wake event triggers of sleepwalk logic */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG);
+	reg &= ~UHSIC_WAKE_VAL(~0);
+	reg |= UHSIC_WAKE_VAL_NONE;
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG);
+
+	/* power down the line state detectors of the port */
+	reg = tegra_pmc_readl(pmc, PMC_USB_AO);
+	reg |= (STROBE_VAL_PD(port) | DATA0_VAL_PD(port) | DATA1_VAL_PD);
+	tegra_pmc_writel(pmc, reg, PMC_USB_AO);
+
+	/* save state, HSIC always comes up as HS */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_SAVED_STATE);
+	reg &= ~UHSIC_MODE(~0);
+	reg |= UHSIC_HS;
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_SAVED_STATE);
+
+	/* enable the trigger of the sleepwalk logic */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEPWALK_CFG);
+	reg |= (UHSIC_WAKE_WALK_EN | UHSIC_LINEVAL_WALK_EN);
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEPWALK_CFG);
+
+	/* reset the walk pointer and clear the alarm of the sleepwalk logic,
+	 * as well as capture the configuration of the USB2.0 port
+	 */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_TRIGGERS);
+	reg |= (UHSIC_CLR_WALK_PTR | UHSIC_CLR_WAKE_ALARM);
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_TRIGGERS);
+
+	/* setup the pull-ups and pull-downs of the signals during the four
+	 * stages of sleepwalk.
+	 * maintain a HSIC IDLE and keep driving HSIC RESUME upon remote wake
+	 */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEPWALK_P0);
+	reg = (UHSIC_DATA0_RPD_A | UHSIC_DATA0_RPU_B | UHSIC_DATA0_RPU_C |
+		UHSIC_DATA0_RPU_D);
+	reg |= (UHSIC_STROBE_RPU_A | UHSIC_STROBE_RPD_B | UHSIC_STROBE_RPD_C |
+		UHSIC_STROBE_RPD_D);
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEPWALK_P0);
+
+	/* power up the line state detectors of the port */
+	reg = tegra_pmc_readl(pmc, PMC_USB_AO);
+	reg &= ~(STROBE_VAL_PD(port) | DATA0_VAL_PD(port) | DATA1_VAL_PD);
+	tegra_pmc_writel(pmc, reg, PMC_USB_AO);
+
+	usleep_range(50, 100);
+
+	/* set the wake signaling trigger events */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG);
+	reg &= ~UHSIC_WAKE_VAL(~0);
+	reg |= UHSIC_WAKE_VAL_SD10;
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG);
+
+	/* enable the wake detection */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG);
+	reg |= UHSIC_MASTER_ENABLE;
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG);
+
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+	reg |= UHSIC_LINE_WAKEUP_EN;
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+
+	return 0;
+}
+EXPORT_SYMBOL(tegra_pmc_hsic_phy_enable_sleepwalk);
+
+int tegra_pmc_hsic_phy_disable_sleepwalk(int port)
+{
+	u32 reg;
+
+	pr_debug("PMC %s : port %dn", __func__, port);
+
+	/* disable the wake detection */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG);
+	reg &= ~UHSIC_MASTER_ENABLE;
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG);
+
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+	reg &= ~UHSIC_LINE_WAKEUP_EN;
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP);
+
+	/* disable wake event triggers of sleepwalk logic */
+	reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG);
+	reg &= ~UHSIC_WAKE_VAL(~0);
+	reg |= UHSIC_WAKE_VAL_NONE;
+	tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG);
+
+	/* power down the line state detectors of the port */
+	reg = tegra_pmc_readl(pmc, PMC_USB_AO);
+	reg |= (STROBE_VAL_PD(port) | DATA0_VAL_PD(port) | DATA1_VAL_PD);
+	tegra_pmc_writel(pmc, reg, PMC_USB_AO);
+
+	/* clear alarm of the sleepwalk logic */
+	reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_TRIGGERS);
+	reg |= UHSIC_CLR_WAKE_ALARM;
+	tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_TRIGGERS);
+
+	return 0;
+}
+EXPORT_SYMBOL(tegra_pmc_hsic_phy_disable_sleepwalk);
+
 /**
  * tegra_powergate_power_on() - power on partition
  * @id: partition ID
diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h
index 57e58faf660b..987109e79da9 100644
--- a/include/soc/tegra/pmc.h
+++ b/include/soc/tegra/pmc.h
@@ -11,6 +11,7 @@
 #define __SOC_TEGRA_PMC_H__
 
 #include <linux/reboot.h>
+#include <linux/usb/ch9.h>
 
 #include <soc/tegra/pm.h>
 
@@ -171,6 +172,18 @@ enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void);
 void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode);
 void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode);
 
+/* T210 USB2 SLEEPWALK APIs */
+struct tegra_utmi_pad_config {
+	u32 tctrl;
+	u32 pctrl;
+	u32 rpd_ctrl;
+};
+int tegra_pmc_utmi_phy_enable_sleepwalk(int port, enum usb_device_speed speed,
+					struct tegra_utmi_pad_config *config);
+int tegra_pmc_utmi_phy_disable_sleepwalk(int port);
+int tegra_pmc_hsic_phy_enable_sleepwalk(int port);
+int tegra_pmc_hsic_phy_disable_sleepwalk(int port);
+
 #else
 static inline int tegra_powergate_power_on(unsigned int id)
 {
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 6/8] phy: tegra: xusb: t210: support wake and sleepwalk
  2019-06-14  7:48 [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk JC Kuo
@ 2019-06-14  7:48 ` JC Kuo
  2019-07-04 13:53   ` Jon Hunter
  2019-06-14  7:48 ` [PATCH 7/8] arm64: tegra: add Tegra210 XUSB PADCTL irq JC Kuo
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 9+ messages in thread
From: JC Kuo @ 2019-06-14  7:48 UTC (permalink / raw)
  To: gregkh, thierry.reding, jonathanh, pdeschrijver, afrid
  Cc: linux-tegra, linux-usb, devicetree, nkristam, skomatineni, JC Kuo

This commit implements Tegra210 XUSB PADCTL wake and sleepwalk
routines.

Signed-off-by: JC Kuo <jckuo@nvidia.com>
---
 drivers/phy/tegra/xusb-tegra210.c | 574 ++++++++++++++++++++++++++++--
 1 file changed, 548 insertions(+), 26 deletions(-)

diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c
index 007bf352b45e..afa4bfa23be8 100644
--- a/drivers/phy/tegra/xusb-tegra210.c
+++ b/drivers/phy/tegra/xusb-tegra210.c
@@ -16,6 +16,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/reset.h>
 #include <linux/slab.h>
+#include <soc/tegra/pmc.h>
 
 #include <soc/tegra/fuse.h>
 
@@ -48,6 +49,20 @@
 #define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 5))
 #define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 5))
 
+#define XUSB_PADCTL_ELPG_PROGRAM_0 0x20
+#define   USB2_PORT_WAKE_INTERRUPT_ENABLE(x)      BIT((x))
+#define   USB2_PORT_WAKEUP_EVENT(x)               BIT((x) + 7)
+#define   SS_PORT_WAKE_INTERRUPT_ENABLE(x)        BIT((x) + 14)
+#define   SS_PORT_WAKEUP_EVENT(x)                 BIT((x) + 21)
+#define   USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x) + 28)
+#define   USB2_HSIC_PORT_WAKEUP_EVENT(x)          BIT((x) + 30)
+#define   ALL_WAKE_EVENTS ( \
+		USB2_PORT_WAKEUP_EVENT(0) | USB2_PORT_WAKEUP_EVENT(1) | \
+		USB2_PORT_WAKEUP_EVENT(2) | USB2_PORT_WAKEUP_EVENT(3) | \
+		SS_PORT_WAKEUP_EVENT(0) | SS_PORT_WAKEUP_EVENT(1) | \
+		SS_PORT_WAKEUP_EVENT(2) | SS_PORT_WAKEUP_EVENT(3) | \
+		USB2_HSIC_PORT_WAKEUP_EVENT(0))
+
 #define XUSB_PADCTL_ELPG_PROGRAM1 0x024
 #define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN (1 << 31)
 #define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 30)
@@ -81,6 +96,8 @@
 #define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2)
 #define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD (1 << 1)
 #define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD (1 << 0)
+#define   RPD_CTRL(x)                      (((x) & 0x1f) << 26)
+#define   RPD_CTRL_VALUE(x)                (((x) >> 26) & 0x1f)
 
 #define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284
 #define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 11)
@@ -99,6 +116,8 @@
 #define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT 12
 #define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK 0x7f
 #define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL 0x1e
+#define   TCTRL_VALUE(x)                (((x) & 0x3f) >> 0)
+#define   PCTRL_VALUE(x)                (((x) >> 6) & 0x3f)
 
 #define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20)
 #define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE (1 << 18)
@@ -228,10 +247,18 @@ struct tegra210_xusb_fuse_calibration {
 	u32 rpd_ctrl;
 };
 
+struct tegra210_xusb_padctl_context {
+	u32 usb2_pad_mux;
+	u32 usb2_port_cap;
+	u32 ss_port_map;
+	u32 usb3_pad_mux;
+};
+
 struct tegra210_xusb_padctl {
 	struct tegra_xusb_padctl base;
-
+	struct tegra_utmi_pad_config utmi_pad_cfg;
 	struct tegra210_xusb_fuse_calibration fuse;
+	struct tegra210_xusb_padctl_context context;
 };
 
 static inline struct tegra210_xusb_padctl *
@@ -868,6 +895,317 @@ static int tegra210_hsic_set_idle(struct tegra_xusb_padctl *padctl,
 	return 0;
 }
 
+static int tegra210_usb3_phy_enable_sleepwalk(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	int index = tegra210_usb3_lane_map(lane);
+	struct device *dev = padctl->dev;
+	u32 reg;
+
+	if (index < 0) {
+		dev_err(dev, "invalid usb3 port number %d\n", index);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "phy enable sleepwalk on usb3-%d\n", index);
+
+	mutex_lock(&padctl->lock);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	reg |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(100, 200);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	reg |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(250, 350);
+
+	mutex_unlock(&padctl->lock);
+
+	return 0;
+}
+
+static void tegra210_utmi_phy_get_pad_config(
+				struct tegra_xusb_padctl *padctl,
+				int port, struct tegra_utmi_pad_config *config)
+{
+	u32 reg;
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
+	config->tctrl = TCTRL_VALUE(reg);
+	config->pctrl = PCTRL_VALUE(reg);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(port));
+	config->rpd_ctrl = RPD_CTRL_VALUE(reg);
+}
+
+static int tegra210_usb3_phy_disable_sleepwalk(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	int index = tegra210_usb3_lane_map(lane);
+	struct device *dev = padctl->dev;
+	u32 reg;
+
+	if (index < 0) {
+		dev_err(dev, "invalid usb3 port number %d\n", index);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "phy disable sleepwalk on usb3-%d\n", index);
+
+	mutex_lock(&padctl->lock);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	reg &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	usleep_range(100, 200);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1);
+	reg &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM1);
+
+	mutex_unlock(&padctl->lock);
+
+	return 0;
+}
+
+static int tegra210_usb3_phy_enable_wake(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	int index = tegra210_usb3_lane_map(lane);
+	struct device *dev = padctl->dev;
+	u32 reg;
+
+	if (index < 0) {
+		dev_err(dev, "invalid usb3 port number %d\n", index);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "phy enable wake on usb3-%d\n", index);
+
+	mutex_lock(&padctl->lock);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg |= SS_PORT_WAKEUP_EVENT(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	usleep_range(10, 20);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg |= SS_PORT_WAKE_INTERRUPT_ENABLE(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	mutex_unlock(&padctl->lock);
+
+	return 0;
+}
+
+static int tegra210_usb3_phy_disable_wake(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	int index = tegra210_usb3_lane_map(lane);
+	struct device *dev = padctl->dev;
+	u32 reg;
+
+	if (index < 0) {
+		dev_err(dev, "invalid usb3 port number %d\n", index);
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "phy disable wake on usb3-%d\n", index);
+
+	mutex_lock(&padctl->lock);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg &= ~SS_PORT_WAKE_INTERRUPT_ENABLE(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	usleep_range(10, 20);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg |= SS_PORT_WAKEUP_EVENT(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	mutex_unlock(&padctl->lock);
+
+	return 0;
+}
+
+static int tegra210_utmi_phy_enable_wake(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned int index = lane->index;
+	struct device *dev = padctl->dev;
+	u32 reg;
+
+	dev_dbg(dev, "phy enable wake on usb2-%d\n", index);
+
+	mutex_lock(&padctl->lock);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg |= USB2_PORT_WAKEUP_EVENT(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	usleep_range(10, 20);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg |= USB2_PORT_WAKE_INTERRUPT_ENABLE(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	mutex_unlock(&padctl->lock);
+
+	return 0;
+}
+
+static int tegra210_utmi_phy_disable_wake(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned int index = lane->index;
+	struct device *dev = padctl->dev;
+	u32 reg;
+
+	dev_dbg(dev, "phy disable wake on usb2-%d\n", index);
+
+	mutex_lock(&padctl->lock);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg &= ~USB2_PORT_WAKE_INTERRUPT_ENABLE(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	usleep_range(10, 20);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg |= USB2_PORT_WAKEUP_EVENT(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	mutex_unlock(&padctl->lock);
+
+	return 0;
+}
+
+static int tegra210_hsic_phy_enable_wake(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned int index = lane->index;
+	struct device *dev = padctl->dev;
+	u32 reg;
+
+	dev_dbg(dev, "phy enable wake on hsic-%d\n", index);
+
+	mutex_lock(&padctl->lock);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg |= USB2_HSIC_PORT_WAKEUP_EVENT(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	usleep_range(10, 20);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg |= USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	mutex_unlock(&padctl->lock);
+
+	return 0;
+}
+
+static int tegra210_hsic_phy_disable_wake(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned int index = lane->index;
+	struct device *dev = padctl->dev;
+	u32 reg;
+
+	dev_dbg(dev, "phy disable wake on hsic-%d\n", index);
+
+	mutex_lock(&padctl->lock);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg &= ~USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	usleep_range(10, 20);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	reg &= ~ALL_WAKE_EVENTS;
+	reg |= USB2_HSIC_PORT_WAKEUP_EVENT(index);
+	padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0);
+
+	mutex_unlock(&padctl->lock);
+
+	return 0;
+}
+
+static int tegra210_usb3_phy_remote_wake_detected(
+			struct tegra_xusb_padctl *padctl, int port)
+{
+	u32 reg;
+
+	if (port < 0) {
+		dev_err(padctl->dev, "invalid usb3 port number %d\n",
+					port);
+		return false;
+	}
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	if ((reg & SS_PORT_WAKE_INTERRUPT_ENABLE(port)) &&
+	    (reg & SS_PORT_WAKEUP_EVENT(port)))
+		return true;
+	else
+		return false;
+}
+
+static int tegra210_utmi_phy_remote_wake_detected(
+			struct tegra_xusb_padctl *padctl, int port)
+{
+	u32 reg;
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	if ((reg & USB2_PORT_WAKE_INTERRUPT_ENABLE(port)) &&
+	    (reg & USB2_PORT_WAKEUP_EVENT(port)))
+		return true;
+	else
+		return false;
+}
+
+static int tegra210_hsic_phy_remote_wake_detected(
+			struct tegra_xusb_padctl *padctl, int port)
+{
+	u32 reg;
+
+	dev_dbg(padctl->dev, "hsic-%d remote wake detected\n", port);
+
+	reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0);
+	if ((reg & USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(port)) &&
+	    (reg & USB2_HSIC_PORT_WAKEUP_EVENT(port)))
+		return true;
+	else
+		return false;
+}
+
 static int tegra210_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl,
 					 unsigned int index, bool enable)
 {
@@ -970,8 +1308,23 @@ static int tegra210_usb2_phy_init(struct phy *phy)
 {
 	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
 	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned int index = lane->index;
+	struct tegra_xusb_usb2_port *port;
+	int err;
 	u32 value;
 
+	port = tegra_xusb_find_usb2_port(padctl, index);
+	if (!port) {
+		dev_err(&phy->dev, "no port found for USB2 lane %u\n", index);
+		return -ENODEV;
+	}
+
+	err = regulator_enable(port->supply);
+	if (err)
+		return err;
+
+	mutex_lock(&padctl->lock);
+
 	value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX);
 	value &= ~(XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK <<
 		   XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT);
@@ -979,12 +1332,28 @@ static int tegra210_usb2_phy_init(struct phy *phy)
 		 XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT;
 	padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX);
 
+	mutex_unlock(&padctl->lock);
+
 	return tegra210_xusb_padctl_enable(padctl);
 }
 
 static int tegra210_usb2_phy_exit(struct phy *phy)
 {
 	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
+	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
+	unsigned int index = lane->index;
+	struct tegra_xusb_usb2_port *port;
+	int err;
+
+	port = tegra_xusb_find_usb2_port(padctl, index);
+	if (!port) {
+		dev_err(&phy->dev, "no port found for USB2 lane %u\n", index);
+		return -ENODEV;
+	}
+
+	err = regulator_disable(port->supply);
+	if (err)
+		return err;
 
 	return tegra210_xusb_padctl_disable(lane->pad->padctl);
 }
@@ -996,19 +1365,14 @@ static int tegra210_usb2_phy_power_on(struct phy *phy)
 	struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
 	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
 	struct tegra210_xusb_padctl *priv;
-	struct tegra_xusb_usb2_port *port;
 	unsigned int index = lane->index;
 	u32 value;
 	int err;
 
-	port = tegra_xusb_find_usb2_port(padctl, index);
-	if (!port) {
-		dev_err(&phy->dev, "no port found for USB2 lane %u\n", index);
-		return -ENODEV;
-	}
-
 	priv = to_tegra210_xusb_padctl(padctl);
 
+	mutex_lock(&padctl->lock);
+
 	value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
 	value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK <<
 		    XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) |
@@ -1062,12 +1426,6 @@ static int tegra210_usb2_phy_power_on(struct phy *phy)
 	padctl_writel(padctl, value,
 		      XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index));
 
-	err = regulator_enable(port->supply);
-	if (err)
-		return err;
-
-	mutex_lock(&padctl->lock);
-
 	if (pad->enable > 0) {
 		pad->enable++;
 		mutex_unlock(&padctl->lock);
@@ -1076,7 +1434,7 @@ static int tegra210_usb2_phy_power_on(struct phy *phy)
 
 	err = clk_prepare_enable(pad->clk);
 	if (err)
-		goto disable_regulator;
+		goto out;
 
 	value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1);
 	value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK <<
@@ -1108,8 +1466,7 @@ static int tegra210_usb2_phy_power_on(struct phy *phy)
 
 	return 0;
 
-disable_regulator:
-	regulator_disable(port->supply);
+out:
 	mutex_unlock(&padctl->lock);
 	return err;
 }
@@ -1119,16 +1476,9 @@ static int tegra210_usb2_phy_power_off(struct phy *phy)
 	struct tegra_xusb_lane *lane = phy_get_drvdata(phy);
 	struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad);
 	struct tegra_xusb_padctl *padctl = lane->pad->padctl;
-	struct tegra_xusb_usb2_port *port;
+	unsigned int index = lane->index;
 	u32 value;
 
-	port = tegra_xusb_find_usb2_port(padctl, lane->index);
-	if (!port) {
-		dev_err(&phy->dev, "no port found for USB2 lane %u\n",
-			lane->index);
-		return -ENODEV;
-	}
-
 	mutex_lock(&padctl->lock);
 
 	if (WARN_ON(pad->enable == 0))
@@ -1137,12 +1487,19 @@ static int tegra210_usb2_phy_power_off(struct phy *phy)
 	if (--pad->enable > 0)
 		goto out;
 
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+	value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index));
+
+	value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+	value |= XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR;
+	padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
+
 	value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
 	value |= XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD;
 	padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0);
 
 out:
-	regulator_disable(port->supply);
 	mutex_unlock(&padctl->lock);
 	return 0;
 }
@@ -1863,6 +2220,114 @@ static const struct phy_ops tegra210_sata_phy_ops = {
 	.owner = THIS_MODULE,
 };
 
+static inline bool is_usb3_phy(struct phy *phy)
+{
+	return (phy->ops == &tegra210_pcie_phy_ops ||
+		phy->ops == &tegra210_sata_phy_ops);
+}
+
+static inline bool is_hsic_phy(struct phy *phy)
+{
+	return phy->ops == &tegra210_hsic_phy_ops;
+}
+
+static inline bool is_utmi_phy(struct phy *phy)
+{
+	return phy->ops == &tegra210_usb2_phy_ops;
+}
+
+static int tegra210_xusb_padctl_phy_wake(struct tegra_xusb_padctl *padctl,
+					 struct phy *phy, bool enable)
+{
+	if (!phy)
+		return 0;
+
+	if (is_usb3_phy(phy)) {
+		if (enable)
+			return tegra210_usb3_phy_enable_wake(phy);
+		else
+			return tegra210_usb3_phy_disable_wake(phy);
+	} else if (is_utmi_phy(phy)) {
+		if (enable)
+			return tegra210_utmi_phy_enable_wake(phy);
+		else
+			return tegra210_utmi_phy_disable_wake(phy);
+	} else if (is_hsic_phy(phy)) {
+		if (enable)
+			return tegra210_hsic_phy_enable_wake(phy);
+		else
+			return tegra210_hsic_phy_disable_wake(phy);
+	} else
+		return -EINVAL;
+
+	return 0;
+}
+
+int tegra210_xusb_padctl_remote_wake_detected(struct phy *phy)
+{
+	struct tegra_xusb_lane *lane;
+	struct tegra_xusb_padctl *padctl;
+
+	if (!phy)
+		return 0;
+
+	lane = phy_get_drvdata(phy);
+	padctl = lane->pad->padctl;
+
+	if (is_utmi_phy(phy))
+		return tegra210_utmi_phy_remote_wake_detected(padctl,
+					lane->index);
+	else if (is_hsic_phy(phy))
+		return tegra210_hsic_phy_remote_wake_detected(padctl,
+					lane->index);
+	else if (is_usb3_phy(phy))
+		return tegra210_usb3_phy_remote_wake_detected(padctl,
+					tegra210_usb3_lane_map(lane));
+
+	return -EINVAL;
+}
+
+static int tegra210_xusb_padctl_phy_sleepwalk(struct tegra_xusb_padctl *padctl,
+					      struct phy *phy, bool enable,
+					      enum usb_device_speed speed)
+{
+	struct tegra210_xusb_padctl *priv;
+	struct tegra_xusb_lane *lane;
+
+	if (!phy)
+		return 0;
+
+	priv = to_tegra210_xusb_padctl(padctl);
+	lane = phy_get_drvdata(phy);
+
+	if (is_usb3_phy(phy)) {
+		if (enable)
+			return tegra210_usb3_phy_enable_sleepwalk(phy);
+		else
+			return tegra210_usb3_phy_disable_sleepwalk(phy);
+	} else if (is_utmi_phy(phy)) {
+		tegra210_utmi_phy_get_pad_config(padctl, lane->index,
+				&priv->utmi_pad_cfg);
+		if (enable)
+			return tegra_pmc_utmi_phy_enable_sleepwalk(
+					lane->index, speed,
+					&priv->utmi_pad_cfg);
+		else
+			return tegra_pmc_utmi_phy_disable_sleepwalk(
+					lane->index);
+	} else if (is_hsic_phy(phy)) {
+		if (enable)
+			return tegra_pmc_hsic_phy_enable_sleepwalk(
+					lane->index);
+		else
+			return tegra_pmc_hsic_phy_disable_sleepwalk(
+					lane->index);
+	} else
+		return -EINVAL;
+
+	return 0;
+}
+
 static struct tegra_xusb_pad *
 tegra210_sata_pad_probe(struct tegra_xusb_padctl *padctl,
 			const struct tegra_xusb_pad_soc *soc,
@@ -2081,11 +2546,68 @@ static void tegra210_xusb_padctl_remove(struct tegra_xusb_padctl *padctl)
 {
 }
 
+static void tegra210_xusb_padctl_save(struct tegra_xusb_padctl *padctl)
+{
+	struct tegra210_xusb_padctl *priv = to_tegra210_xusb_padctl(padctl);
+
+	priv->context.usb2_pad_mux =
+				padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX);
+	priv->context.usb2_port_cap =
+				padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP);
+	priv->context.ss_port_map =
+				padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP);
+	priv->context.usb3_pad_mux =
+			padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX);
+}
+
+static void tegra210_xusb_padctl_restore(struct tegra_xusb_padctl *padctl)
+{
+	struct tegra210_xusb_padctl *priv = to_tegra210_xusb_padctl(padctl);
+
+	padctl_writel(padctl, priv->context.usb2_pad_mux,
+			XUSB_PADCTL_USB2_PAD_MUX);
+	padctl_writel(padctl, priv->context.usb2_port_cap,
+			XUSB_PADCTL_USB2_PORT_CAP);
+	padctl_writel(padctl, priv->context.ss_port_map,
+			XUSB_PADCTL_SS_PORT_MAP);
+	padctl_writel(padctl, priv->context.usb3_pad_mux,
+			XUSB_PADCTL_USB3_PAD_MUX);
+}
+
+static int tegra210_xusb_padctl_suspend_noirq(struct tegra_xusb_padctl *padctl)
+{
+	mutex_lock(&padctl->lock);
+
+	tegra210_uphy_deinit(padctl);
+
+	tegra210_xusb_padctl_save(padctl);
+
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
+static int tegra210_xusb_padctl_resume_noirq(struct tegra_xusb_padctl *padctl)
+{
+	mutex_lock(&padctl->lock);
+
+	tegra210_xusb_padctl_restore(padctl);
+
+	tegra210_uphy_init(padctl);
+
+	mutex_unlock(&padctl->lock);
+	return 0;
+}
+
 static const struct tegra_xusb_padctl_ops tegra210_xusb_padctl_ops = {
 	.probe = tegra210_xusb_padctl_probe,
 	.remove = tegra210_xusb_padctl_remove,
+	.suspend_noirq = tegra210_xusb_padctl_suspend_noirq,
+	.resume_noirq = tegra210_xusb_padctl_resume_noirq,
 	.usb3_set_lfps_detect = tegra210_usb3_set_lfps_detect,
 	.hsic_set_idle = tegra210_hsic_set_idle,
+	.phy_sleepwalk = tegra210_xusb_padctl_phy_sleepwalk,
+	.phy_wake = tegra210_xusb_padctl_phy_wake,
+	.remote_wake_detected = tegra210_xusb_padctl_remote_wake_detected,
 };
 
 const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc = {
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 7/8] arm64: tegra: add Tegra210 XUSB PADCTL irq
  2019-06-14  7:48 [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk JC Kuo
  2019-06-14  7:48 ` [PATCH 6/8] phy: tegra: xusb: t210: support wake and sleepwalk JC Kuo
@ 2019-06-14  7:48 ` JC Kuo
  2019-06-14  7:48 ` [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM JC Kuo
  2019-07-04 13:42 ` [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk Jon Hunter
  3 siblings, 0 replies; 9+ messages in thread
From: JC Kuo @ 2019-06-14  7:48 UTC (permalink / raw)
  To: gregkh, thierry.reding, jonathanh, pdeschrijver, afrid
  Cc: linux-tegra, linux-usb, devicetree, nkristam, skomatineni, JC Kuo

XUSB PADCTL interrupt will be raised when USB wake event happens.
This is required for supporting XUSB host controller ELPG.

Signed-off-by: JC Kuo <jckuo@nvidia.com>
---
 arch/arm64/boot/dts/nvidia/tegra210.dtsi | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/nvidia/tegra210.dtsi b/arch/arm64/boot/dts/nvidia/tegra210.dtsi
index a550c0a4d572..7496302a1a8a 100644
--- a/arch/arm64/boot/dts/nvidia/tegra210.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra210.dtsi
@@ -909,7 +909,8 @@
 		reg-names = "hcd", "fpci", "ipfs";
 
 		interrupts = <GIC_SPI 39 IRQ_TYPE_LEVEL_HIGH>,
-			     <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
+			     <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 49 IRQ_TYPE_LEVEL_HIGH>;
 
 		clocks = <&tegra_car TEGRA210_CLK_XUSB_HOST>,
 			 <&tegra_car TEGRA210_CLK_XUSB_HOST_SRC>,
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM
  2019-06-14  7:48 [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk JC Kuo
  2019-06-14  7:48 ` [PATCH 6/8] phy: tegra: xusb: t210: support wake and sleepwalk JC Kuo
  2019-06-14  7:48 ` [PATCH 7/8] arm64: tegra: add Tegra210 XUSB PADCTL irq JC Kuo
@ 2019-06-14  7:48 ` JC Kuo
  2019-06-18  6:33   ` Greg KH
  2019-07-04 14:05   ` Jon Hunter
  2019-07-04 13:42 ` [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk Jon Hunter
  3 siblings, 2 replies; 9+ messages in thread
From: JC Kuo @ 2019-06-14  7:48 UTC (permalink / raw)
  To: gregkh, thierry.reding, jonathanh, pdeschrijver, afrid
  Cc: linux-tegra, linux-usb, devicetree, nkristam, skomatineni, JC Kuo

This commit enables XUSB host controller ELPG for runtime and system
power management.

NEED CLEANUP.

Signed-off-by: JC Kuo <jckuo@nvidia.com>
---
 drivers/usb/host/xhci-tegra.c | 802 ++++++++++++++++++++++++++++------
 1 file changed, 671 insertions(+), 131 deletions(-)

diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c
index 294158113d62..ade56e63212b 100644
--- a/drivers/usb/host/xhci-tegra.c
+++ b/drivers/usb/host/xhci-tegra.c
@@ -17,6 +17,7 @@
 #include <linux/phy/phy.h>
 #include <linux/phy/tegra/xusb.h>
 #include <linux/platform_device.h>
+#include <linux/usb/ch9.h>
 #include <linux/pm.h>
 #include <linux/pm_domain.h>
 #include <linux/pm_runtime.h>
@@ -38,7 +39,17 @@
 #define XUSB_CFG_4				0x010
 #define  XUSB_BASE_ADDR_SHIFT			15
 #define  XUSB_BASE_ADDR_MASK			0x1ffff
+#define XUSB_CFG_16				0x040
+#define XUSB_CFG_24				0x060
+#define XUSB_CFG_AXI_CFG			0x0f8
+#define XUSB_CFG_ARU_C11PAGESEL			0x404
+#define  XUSB_HSP0				BIT(12)
 #define XUSB_CFG_ARU_C11_CSBRANGE		0x41c
+#define XUSB_CFG_ARU_CONTEXT			0x43c
+#define XUSB_CFG_ARU_CONTEXT_HS_PLS		0x478
+#define XUSB_CFG_ARU_CONTEXT_FS_PLS		0x47c
+#define XUSB_CFG_ARU_CONTEXT_HSFS_SPEED		0x480
+#define XUSB_CFG_ARU_CONTEXT_HSFS_PP		0x484
 #define XUSB_CFG_CSB_BASE_ADDR			0x800
 
 /* FPCI mailbox registers */
@@ -63,11 +74,20 @@
 #define  MBOX_SMI_INTR_EN			BIT(3)
 
 /* IPFS registers */
+#define XUSB_HOST_MSI_BAR_SZ_0			0x0c0
+#define XUSB_HOST_MSI_AXI_BAR_ST_0		0x0c4
+#define XUSB_HOST_MSI_FPCI_BAR_ST_0		0x0c8
+#define XUSB_HOST_MSI_VEC0_0			0x100
+#define XUSB_HOST_MSI_EN_VEC0_0			0x140
 #define IPFS_XUSB_HOST_CONFIGURATION_0		0x180
 #define  IPFS_EN_FPCI				BIT(0)
+#define XUSB_HOST_FPCI_ERROR_MASKS_0		0x184
 #define IPFS_XUSB_HOST_INTR_MASK_0		0x188
 #define  IPFS_IP_INT_MASK			BIT(16)
+#define XUSB_HOST_IPFS_INTR_ENABLE_0		0x198
+#define XUSB_HOST_UFPCI_CONFIG_0		0x19c
 #define IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0	0x1bc
+#define XUSB_HOST_MCCIF_FIFOCTRL_0		0x1dc
 
 #define CSB_PAGE_SELECT_MASK			0x7fffff
 #define CSB_PAGE_SELECT_SHIFT			9
@@ -164,6 +184,31 @@ struct tegra_xusb_soc {
 	bool has_ipfs;
 };
 
+struct tegra_xhci_ipfs_context {
+	u32 msi_bar_sz;
+	u32 msi_axi_barst;
+	u32 msi_fpci_barst;
+	u32 msi_vec0;
+	u32 msi_en_vec0;
+	u32 fpci_error_masks;
+	u32 intr_mask;
+	u32 ipfs_intr_enable;
+	u32 ufpci_config;
+	u32 clkgate_hysteresis;
+	u32 xusb_host_mccif_fifo_cntrl;
+};
+
+struct tegra_xhci_fpci_context {
+	u32 hs_pls;
+	u32 fs_pls;
+	u32 hsfs_speed;
+	u32 hsfs_pp;
+	u32 cfg_aru;
+	u32 cfg_order;
+	u32 cfg_fladj;
+	u32 cfg_sid;
+};
+
 struct tegra_xusb {
 	struct device *dev;
 	void __iomem *regs;
@@ -173,6 +218,7 @@ struct tegra_xusb {
 
 	int xhci_irq;
 	int mbox_irq;
+	int padctl_irq;
 
 	void __iomem *ipfs_base;
 	void __iomem *fpci_base;
@@ -198,8 +244,6 @@ struct tegra_xusb {
 
 	struct device *genpd_dev_host;
 	struct device *genpd_dev_ss;
-	struct device_link *genpd_dl_host;
-	struct device_link *genpd_dl_ss;
 
 	struct phy **phys;
 	unsigned int num_phys;
@@ -210,9 +254,15 @@ struct tegra_xusb {
 		void *virt;
 		dma_addr_t phys;
 	} fw;
+
+	bool suspended;
+	struct tegra_xhci_fpci_context fpci_ctx;
+	struct tegra_xhci_ipfs_context ipfs_ctx;
 };
 
 static struct hc_driver __read_mostly tegra_xhci_hc_driver;
+static int tegra_xhci_exit_elpg(struct tegra_xusb *tegra, bool runtime);
+static int tegra_xhci_enter_elpg(struct tegra_xusb *tegra, bool runtime);
 
 static inline u32 fpci_readl(struct tegra_xusb *tegra, unsigned int offset)
 {
@@ -585,6 +635,14 @@ static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra,
 								     enable);
 			if (err < 0)
 				break;
+
+			if (!enable) {
+				/*
+				 * Add this delay to increase stability of
+				 * directing U3.
+				 */
+				usleep_range(500, 1000);
+			}
 		}
 
 		if (err < 0) {
@@ -621,6 +679,9 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data)
 
 	mutex_lock(&tegra->lock);
 
+	if (pm_runtime_suspended(tegra->dev) || tegra->suspended)
+		goto out;
+
 	value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_DATA_OUT);
 	tegra_xusb_mbox_unpack(&msg, value);
 
@@ -634,13 +695,14 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data)
 
 	tegra_xusb_mbox_handle(tegra, &msg);
 
+out:
 	mutex_unlock(&tegra->lock);
 	return IRQ_HANDLED;
 }
 
-static void tegra_xusb_config(struct tegra_xusb *tegra,
-			      struct resource *regs)
+static void tegra_xusb_config(struct tegra_xusb *tegra)
 {
+	resource_size_t base_addr = tegra->hcd->rsrc_start;
 	u32 value;
 
 	if (tegra->soc->has_ipfs) {
@@ -654,7 +716,7 @@ static void tegra_xusb_config(struct tegra_xusb *tegra,
 	/* Program BAR0 space */
 	value = fpci_readl(tegra, XUSB_CFG_4);
 	value &= ~(XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT);
-	value |= regs->start & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT);
+	value |= base_addr & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT);
 	fpci_writel(tegra, value, XUSB_CFG_4);
 
 	usleep_range(100, 200);
@@ -777,44 +839,57 @@ static void tegra_xusb_phy_disable(struct tegra_xusb *tegra)
 static int tegra_xusb_runtime_suspend(struct device *dev)
 {
 	struct tegra_xusb *tegra = dev_get_drvdata(dev);
+	int ret;
 
-	tegra_xusb_phy_disable(tegra);
-	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
-	tegra_xusb_clk_disable(tegra);
+	synchronize_irq(tegra->mbox_irq);
 
-	return 0;
+	mutex_lock(&tegra->lock);
+	ret = tegra_xhci_enter_elpg(tegra, true);
+	mutex_unlock(&tegra->lock);
+
+	return ret;
 }
 
 static int tegra_xusb_runtime_resume(struct device *dev)
 {
 	struct tegra_xusb *tegra = dev_get_drvdata(dev);
-	int err;
+	int ret;
 
-	err = tegra_xusb_clk_enable(tegra);
-	if (err) {
-		dev_err(dev, "failed to enable clocks: %d\n", err);
-		return err;
-	}
+	mutex_lock(&tegra->lock);
+	ret = tegra_xhci_exit_elpg(tegra, true);
+	mutex_unlock(&tegra->lock);
 
-	err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies);
-	if (err) {
-		dev_err(dev, "failed to enable regulators: %d\n", err);
-		goto disable_clk;
-	}
+	return ret;
+}
 
-	err = tegra_xusb_phy_enable(tegra);
-	if (err < 0) {
-		dev_err(dev, "failed to enable PHYs: %d\n", err);
-		goto disable_regulator;
+static int tegra_xusb_request_firmware(struct tegra_xusb *tegra)
+{
+	struct tegra_xusb_fw_header *header;
+	struct device *dev = tegra->dev;
+	const struct firmware *fw;
+	int rc;
+
+	if (!tegra->fw.virt) {
+		rc = request_firmware(&fw, tegra->soc->firmware, tegra->dev);
+		if (rc < 0) {
+			dev_err(dev, "failed to request firmware: %d\n", rc);
+			return rc;
+		}
+
+		header = (struct tegra_xusb_fw_header *)fw->data;
+		tegra->fw.size = le32_to_cpu(header->fwimg_len);
+		tegra->fw.virt = dma_alloc_coherent(dev, tegra->fw.size,
+						   &tegra->fw.phys, GFP_KERNEL);
+		if (!tegra->fw.virt) {
+			release_firmware(fw);
+			return -ENOMEM;
+		}
+
+		memcpy(tegra->fw.virt, fw->data, tegra->fw.size);
+		release_firmware(fw);
 	}
 
 	return 0;
-
-disable_regulator:
-	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
-disable_clk:
-	tegra_xusb_clk_disable(tegra);
-	return err;
 }
 
 static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
@@ -822,7 +897,6 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
 	unsigned int code_tag_blocks, code_size_blocks, code_blocks;
 	struct tegra_xusb_fw_header *header;
 	struct device *dev = tegra->dev;
-	const struct firmware *fw;
 	unsigned long timeout;
 	time64_t timestamp;
 	struct tm time;
@@ -830,27 +904,9 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
 	u32 value;
 	int err;
 
-	err = request_firmware(&fw, tegra->soc->firmware, tegra->dev);
-	if (err < 0) {
-		dev_err(tegra->dev, "failed to request firmware: %d\n", err);
+	err = tegra_xusb_request_firmware(tegra);
+	if (err)
 		return err;
-	}
-
-	/* Load Falcon controller with its firmware. */
-	header = (struct tegra_xusb_fw_header *)fw->data;
-	tegra->fw.size = le32_to_cpu(header->fwimg_len);
-
-	tegra->fw.virt = dma_alloc_coherent(tegra->dev, tegra->fw.size,
-					    &tegra->fw.phys, GFP_KERNEL);
-	if (!tegra->fw.virt) {
-		dev_err(tegra->dev, "failed to allocate memory for firmware\n");
-		release_firmware(fw);
-		return -ENOMEM;
-	}
-
-	header = (struct tegra_xusb_fw_header *)tegra->fw.virt;
-	memcpy(tegra->fw.virt, fw->data, tegra->fw.size);
-	release_firmware(fw);
 
 	if (csb_readl(tegra, XUSB_CSB_MP_ILOAD_BASE_LO) != 0) {
 		dev_info(dev, "Firmware already loaded, Falcon state %#x\n",
@@ -865,6 +921,7 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
 	 * Boot code of the firmware reads the ILOAD_BASE registers
 	 * to get to the start of the DFI in system memory.
 	 */
+	header = (struct tegra_xusb_fw_header *)tegra->fw.virt;
 	address = tegra->fw.phys + sizeof(*header);
 	csb_writel(tegra, address >> 32, XUSB_CSB_MP_ILOAD_BASE_HI);
 	csb_writel(tegra, address, XUSB_CSB_MP_ILOAD_BASE_LO);
@@ -942,10 +999,6 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
 static void tegra_xusb_powerdomain_remove(struct device *dev,
 					  struct tegra_xusb *tegra)
 {
-	if (tegra->genpd_dl_ss)
-		device_link_del(tegra->genpd_dl_ss);
-	if (tegra->genpd_dl_host)
-		device_link_del(tegra->genpd_dl_host);
 	if (!IS_ERR_OR_NULL(tegra->genpd_dev_ss))
 		dev_pm_domain_detach(tegra->genpd_dev_ss, true);
 	if (!IS_ERR_OR_NULL(tegra->genpd_dev_host))
@@ -971,25 +1024,102 @@ static int tegra_xusb_powerdomain_init(struct device *dev,
 		return err;
 	}
 
-	tegra->genpd_dl_host = device_link_add(dev, tegra->genpd_dev_host,
-					       DL_FLAG_PM_RUNTIME |
-					       DL_FLAG_STATELESS);
-	if (!tegra->genpd_dl_host) {
-		dev_err(dev, "adding host device link failed!\n");
-		return -ENODEV;
+	return 0;
+}
+
+static int tegra_xusb_unpowergate_partitions(struct tegra_xusb *tegra)
+{
+	struct device *dev = tegra->dev;
+	bool use_genpd;
+	int rc;
+
+	use_genpd = of_property_read_bool(dev->of_node, "power-domains");
+
+	if (use_genpd)
+		rc = pm_runtime_get_sync(tegra->genpd_dev_ss);
+	else {
+		rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA,
+							tegra->ss_clk,
+							tegra->ss_rst);
+	}
+	if (rc < 0) {
+		dev_err(dev, "failed to enable XUSB SS partition: %d\n", rc);
+		return rc;
+	}
+
+	if (use_genpd)
+		rc = pm_runtime_get_sync(tegra->genpd_dev_host);
+	else {
+		rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC,
+							tegra->host_clk,
+							tegra->host_rst);
+	}
+	if (rc < 0) {
+		dev_err(dev, "failed to enable XUSB Host partition: %d\n", rc);
+		if (use_genpd)
+			pm_runtime_put_sync(tegra->genpd_dev_ss);
+		else
+			tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int tegra_xusb_powergate_partitions(struct tegra_xusb *tegra)
+{
+	struct device *dev = tegra->dev;
+	bool use_genpd;
+	int rc;
+
+	use_genpd = of_property_read_bool(dev->of_node, "power-domains");
+
+	if (use_genpd)
+		rc = pm_runtime_put_sync(tegra->genpd_dev_host);
+	else
+		rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC);
+
+	if (rc < 0) {
+		dev_err(dev, "failed to disable XUSB Host partition: %d\n", rc);
+		return rc;
 	}
 
-	tegra->genpd_dl_ss = device_link_add(dev, tegra->genpd_dev_ss,
-					     DL_FLAG_PM_RUNTIME |
-					     DL_FLAG_STATELESS);
-	if (!tegra->genpd_dl_ss) {
-		dev_err(dev, "adding superspeed device link failed!\n");
-		return -ENODEV;
+	if (use_genpd)
+		rc = pm_runtime_put_sync(tegra->genpd_dev_ss);
+	else
+		rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
+
+	if (rc < 0) {
+		dev_err(dev, "failed to disable XUSB SS partition: %d\n", rc);
+		if (use_genpd)
+			pm_runtime_get_sync(tegra->genpd_dev_host);
+		else {
+			tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC,
+							  tegra->host_clk,
+							  tegra->host_rst);
+		}
+		return rc;
 	}
 
 	return 0;
 }
 
+static irqreturn_t tegra_xusb_padctl_irq(int irq, void *data)
+{
+	struct tegra_xusb *tegra = data;
+
+	mutex_lock(&tegra->lock);
+	if (tegra->suspended) {
+		mutex_unlock(&tegra->lock);
+		return IRQ_HANDLED;
+	}
+	mutex_unlock(&tegra->lock);
+
+	pm_runtime_resume(tegra->dev);
+
+	return IRQ_HANDLED;
+}
+
 static int tegra_xusb_probe(struct platform_device *pdev)
 {
 	struct tegra_xusb_mbox_msg msg;
@@ -1035,6 +1165,10 @@ static int tegra_xusb_probe(struct platform_device *pdev)
 	if (tegra->mbox_irq < 0)
 		return tegra->mbox_irq;
 
+	tegra->padctl_irq = platform_get_irq(pdev, 2);
+	if (tegra->padctl_irq < 0)
+		return tegra->padctl_irq;
+
 	tegra->padctl = tegra_xusb_padctl_get(&pdev->dev);
 	if (IS_ERR(tegra->padctl))
 		return PTR_ERR(tegra->padctl);
@@ -1119,25 +1253,6 @@ static int tegra_xusb_probe(struct platform_device *pdev)
 				err);
 			goto put_padctl;
 		}
-
-		err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA,
-							tegra->ss_clk,
-							tegra->ss_rst);
-		if (err) {
-			dev_err(&pdev->dev,
-				"failed to enable XUSBA domain: %d\n", err);
-			goto put_padctl;
-		}
-
-		err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC,
-							tegra->host_clk,
-							tegra->host_rst);
-		if (err) {
-			tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
-			dev_err(&pdev->dev,
-				"failed to enable XUSBC domain: %d\n", err);
-			goto put_padctl;
-		}
 	} else {
 		err = tegra_xusb_powerdomain_init(&pdev->dev, tegra);
 		if (err)
@@ -1197,6 +1312,10 @@ static int tegra_xusb_probe(struct platform_device *pdev)
 		err = -ENOMEM;
 		goto put_powerdomains;
 	}
+	tegra->hcd->skip_phy_initialization = 1;
+	tegra->hcd->regs = tegra->regs;
+	tegra->hcd->rsrc_start = regs->start;
+	tegra->hcd->rsrc_len = resource_size(regs);
 
 	/*
 	 * This must happen after usb_create_hcd(), because usb_create_hcd()
@@ -1204,33 +1323,40 @@ static int tegra_xusb_probe(struct platform_device *pdev)
 	 */
 	platform_set_drvdata(pdev, tegra);
 
-	pm_runtime_enable(&pdev->dev);
-	if (pm_runtime_enabled(&pdev->dev))
-		err = pm_runtime_get_sync(&pdev->dev);
-	else
-		err = tegra_xusb_runtime_resume(&pdev->dev);
+	err = tegra_xusb_clk_enable(tegra);
+	if (err) {
+		dev_err(tegra->dev, "failed to enable clocks: %d\n", err);
+		goto put_hcd;
+	}
+
+	err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies);
+	if (err) {
+		dev_err(tegra->dev, "failed to enable regulators: %d\n", err);
+		goto disable_clk;
+	}
 
+	err = tegra_xusb_phy_enable(tegra);
 	if (err < 0) {
-		dev_err(&pdev->dev, "failed to enable device: %d\n", err);
-		goto disable_rpm;
+		dev_err(tegra->dev, "failed to enable PHYs: %d\n", err);
+		goto disable_regulator;
 	}
 
-	tegra_xusb_config(tegra, regs);
+	err = tegra_xusb_unpowergate_partitions(tegra);
+	if (err)
+		goto disable_phy;
+
+	tegra_xusb_config(tegra);
 
 	err = tegra_xusb_load_firmware(tegra);
 	if (err < 0) {
 		dev_err(&pdev->dev, "failed to load firmware: %d\n", err);
-		goto put_rpm;
+		goto powergate;
 	}
 
-	tegra->hcd->regs = tegra->regs;
-	tegra->hcd->rsrc_start = regs->start;
-	tegra->hcd->rsrc_len = resource_size(regs);
-
 	err = usb_add_hcd(tegra->hcd, tegra->xhci_irq, IRQF_SHARED);
 	if (err < 0) {
 		dev_err(&pdev->dev, "failed to add USB HCD: %d\n", err);
-		goto put_rpm;
+		goto powergate;
 	}
 
 	device_wakeup_enable(tegra->hcd->self.controller);
@@ -1253,6 +1379,26 @@ static int tegra_xusb_probe(struct platform_device *pdev)
 		goto put_usb3;
 	}
 
+	err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq,
+					tegra_xusb_mbox_irq,
+					tegra_xusb_mbox_thread, 0,
+					dev_name(&pdev->dev), tegra);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to request mbox IRQ: %d\n", err);
+		goto remove_usb3;
+	}
+
+	err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq,
+					NULL,
+					tegra_xusb_padctl_irq,
+					IRQF_ONESHOT |
+					IRQF_TRIGGER_HIGH,
+					dev_name(&pdev->dev), tegra);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to request padctl IRQ: %d\n", err);
+		goto remove_usb3;
+	}
+
 	mutex_lock(&tegra->lock);
 
 	/* Enable firmware messages from controller. */
@@ -1268,14 +1414,16 @@ static int tegra_xusb_probe(struct platform_device *pdev)
 
 	mutex_unlock(&tegra->lock);
 
-	err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq,
-					tegra_xusb_mbox_irq,
-					tegra_xusb_mbox_thread, 0,
-					dev_name(&pdev->dev), tegra);
-	if (err < 0) {
-		dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
-		goto remove_usb3;
-	}
+	/* Enable wake for both USB 2.0 and USB 3.0 roothubs */
+	device_init_wakeup(&tegra->hcd->self.root_hub->dev, true);
+	device_init_wakeup(&xhci->shared_hcd->self.root_hub->dev, true);
+	device_init_wakeup(tegra->dev, true);
+
+	pm_runtime_use_autosuspend(tegra->dev);
+	pm_runtime_set_autosuspend_delay(tegra->dev, 2000);
+	pm_runtime_mark_last_busy(tegra->dev);
+	pm_runtime_set_active(tegra->dev);
+	pm_runtime_enable(tegra->dev);
 
 	return 0;
 
@@ -1285,19 +1433,18 @@ static int tegra_xusb_probe(struct platform_device *pdev)
 	usb_put_hcd(xhci->shared_hcd);
 remove_usb2:
 	usb_remove_hcd(tegra->hcd);
-put_rpm:
-	if (!pm_runtime_status_suspended(&pdev->dev))
-		tegra_xusb_runtime_suspend(&pdev->dev);
-disable_rpm:
-	pm_runtime_disable(&pdev->dev);
+powergate:
+	tegra_xusb_powergate_partitions(tegra);
+disable_phy:
+	tegra_xusb_phy_disable(tegra);
+disable_regulator:
+	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
+disable_clk:
+	tegra_xusb_clk_disable(tegra);
+put_hcd:
 	usb_put_hcd(tegra->hcd);
 put_powerdomains:
-	if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) {
-		tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC);
-		tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
-	} else {
-		tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
-	}
+	tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
 put_padctl:
 	tegra_xusb_padctl_put(tegra->padctl);
 	return err;
@@ -1308,6 +1455,8 @@ static int tegra_xusb_remove(struct platform_device *pdev)
 	struct tegra_xusb *tegra = platform_get_drvdata(pdev);
 	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
 
+	pm_runtime_get_sync(&pdev->dev);
+
 	usb_remove_hcd(xhci->shared_hcd);
 	usb_put_hcd(xhci->shared_hcd);
 	xhci->shared_hcd = NULL;
@@ -1317,38 +1466,429 @@ static int tegra_xusb_remove(struct platform_device *pdev)
 	dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt,
 			  tegra->fw.phys);
 
-	pm_runtime_put_sync(&pdev->dev);
 	pm_runtime_disable(&pdev->dev);
+	pm_runtime_put(&pdev->dev);
 
-	if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) {
-		tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC);
-		tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
-	} else {
+	tegra_xusb_powergate_partitions(tegra);
+
+	if (of_property_read_bool(pdev->dev.of_node, "power-domains"))
 		tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
-	}
 
+	tegra_xusb_phy_disable(tegra);
+	tegra_xusb_clk_disable(tegra);
+	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
 	tegra_xusb_padctl_put(tegra->padctl);
 
 	return 0;
 }
 
+static void tegra_xhci_save_context(struct tegra_xusb *tegra)
+{
+	if (tegra->soc->has_ipfs) {
+		/* Save IPFS registers */
+		tegra->ipfs_ctx.msi_bar_sz =
+			ipfs_readl(tegra, XUSB_HOST_MSI_BAR_SZ_0);
+		tegra->ipfs_ctx.msi_axi_barst =
+			ipfs_readl(tegra, XUSB_HOST_MSI_AXI_BAR_ST_0);
+		tegra->ipfs_ctx.msi_fpci_barst =
+			ipfs_readl(tegra, XUSB_HOST_MSI_FPCI_BAR_ST_0);
+		tegra->ipfs_ctx.msi_vec0 =
+			ipfs_readl(tegra, XUSB_HOST_MSI_VEC0_0);
+		tegra->ipfs_ctx.msi_en_vec0 =
+			ipfs_readl(tegra, XUSB_HOST_MSI_EN_VEC0_0);
+		tegra->ipfs_ctx.fpci_error_masks =
+			ipfs_readl(tegra, XUSB_HOST_FPCI_ERROR_MASKS_0);
+		tegra->ipfs_ctx.intr_mask =
+			ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0);
+		tegra->ipfs_ctx.ipfs_intr_enable =
+			ipfs_readl(tegra, XUSB_HOST_IPFS_INTR_ENABLE_0);
+		tegra->ipfs_ctx.ufpci_config =
+			ipfs_readl(tegra, XUSB_HOST_UFPCI_CONFIG_0);
+		tegra->ipfs_ctx.clkgate_hysteresis =
+			ipfs_readl(tegra,
+				IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0);
+		tegra->ipfs_ctx.xusb_host_mccif_fifo_cntrl =
+			ipfs_readl(tegra, XUSB_HOST_MCCIF_FIFOCTRL_0);
+	}
+
+	/* Save FPCI registers */
+	tegra->fpci_ctx.hs_pls =
+		fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_HS_PLS);
+	tegra->fpci_ctx.fs_pls =
+		fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_FS_PLS);
+	tegra->fpci_ctx.hsfs_speed =
+		fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_HSFS_SPEED);
+	tegra->fpci_ctx.hsfs_pp =
+		fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_HSFS_PP);
+	tegra->fpci_ctx.cfg_aru = fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT);
+	tegra->fpci_ctx.cfg_order = fpci_readl(tegra, XUSB_CFG_AXI_CFG);
+	tegra->fpci_ctx.cfg_fladj = fpci_readl(tegra, XUSB_CFG_24);
+	tegra->fpci_ctx.cfg_sid = fpci_readl(tegra, XUSB_CFG_16);
+}
+
+static void tegra_xhci_restore_context(struct tegra_xusb *tegra)
+{
+	/* Restore FPCI registers */
+	fpci_writel(tegra, tegra->fpci_ctx.hs_pls, XUSB_CFG_ARU_CONTEXT_HS_PLS);
+	fpci_writel(tegra, tegra->fpci_ctx.fs_pls, XUSB_CFG_ARU_CONTEXT_FS_PLS);
+	fpci_writel(tegra, tegra->fpci_ctx.hsfs_speed,
+		    XUSB_CFG_ARU_CONTEXT_HSFS_SPEED);
+	fpci_writel(tegra, tegra->fpci_ctx.hsfs_pp,
+		    XUSB_CFG_ARU_CONTEXT_HSFS_PP);
+	fpci_writel(tegra, tegra->fpci_ctx.cfg_aru, XUSB_CFG_ARU_CONTEXT);
+	fpci_writel(tegra, tegra->fpci_ctx.cfg_order, XUSB_CFG_AXI_CFG);
+	fpci_writel(tegra, tegra->fpci_ctx.cfg_fladj, XUSB_CFG_24);
+	fpci_writel(tegra, tegra->fpci_ctx.cfg_sid, XUSB_CFG_16);
+
+	if (tegra->soc->has_ipfs) {
+		/* Restore IPFS registers */
+		ipfs_writel(tegra, tegra->ipfs_ctx.msi_bar_sz,
+			XUSB_HOST_MSI_BAR_SZ_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.msi_axi_barst,
+			XUSB_HOST_MSI_AXI_BAR_ST_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.msi_fpci_barst,
+			XUSB_HOST_MSI_FPCI_BAR_ST_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.msi_vec0,
+			XUSB_HOST_MSI_VEC0_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.msi_en_vec0,
+			XUSB_HOST_MSI_EN_VEC0_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.fpci_error_masks,
+			XUSB_HOST_FPCI_ERROR_MASKS_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.intr_mask,
+			IPFS_XUSB_HOST_INTR_MASK_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.ipfs_intr_enable,
+			XUSB_HOST_IPFS_INTR_ENABLE_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.ufpci_config,
+			XUSB_HOST_UFPCI_CONFIG_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.clkgate_hysteresis,
+			IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0);
+		ipfs_writel(tegra, tegra->ipfs_ctx.xusb_host_mccif_fifo_cntrl,
+			XUSB_HOST_MCCIF_FIFOCTRL_0);
+	}
+}
+
+static enum usb_device_speed
+tegra_xhci_portsc_to_speed(struct tegra_xusb *tegra, u32 portsc)
+{
+	if (DEV_LOWSPEED(portsc))
+		return USB_SPEED_LOW;
+	else if (DEV_HIGHSPEED(portsc))
+		return USB_SPEED_HIGH;
+	else if (DEV_FULLSPEED(portsc))
+		return USB_SPEED_FULL;
+	else if (DEV_SUPERSPEED_ANY(portsc))
+		return USB_SPEED_SUPER;
+	else
+		return USB_SPEED_UNKNOWN;
+}
+
+static void tegra_xhci_enable_phy_sleepwalk_wake(struct tegra_xusb *tegra)
+{
+	struct tegra_xusb_padctl *padctl = tegra->padctl;
+	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
+	enum usb_device_speed speed;
+	struct phy *phy;
+	int index, offset;
+	int i, j, k;
+	struct xhci_hub *rhub;
+	u32 portsc;
+
+	for (i = 0, k = 0; i < tegra->soc->num_types; i++) {
+		if (strcmp(tegra->soc->phy_types[i].name, "usb3") == 0)
+			rhub = &xhci->usb3_rhub;
+		else
+			rhub = &xhci->usb2_rhub;
+
+		if (strcmp(tegra->soc->phy_types[i].name, "hsic") == 0)
+			offset = tegra->soc->ports.usb2.count;
+		else
+			offset = 0;
+
+		for (j = 0; j < tegra->soc->phy_types[i].num; j++) {
+			phy = tegra->phys[k++];
+
+			if (!phy)
+				continue;
+
+			index = j + offset;
+
+			if (index >= rhub->num_ports)
+				continue;
+
+			portsc = readl(rhub->ports[index]->addr);
+			speed = tegra_xhci_portsc_to_speed(tegra, portsc);
+			tegra_xusb_padctl_enable_phy_sleepwalk(padctl, phy,
+							       speed);
+			tegra_xusb_padctl_enable_phy_wake(padctl, phy);
+		}
+	}
+}
+
+static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra)
+{
+	struct tegra_xusb_padctl *padctl = tegra->padctl;
+	int i;
+
+	for (i = 0; i < tegra->num_phys; i++) {
+		if (!tegra->phys[i])
+			continue;
+
+		tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]);
+	}
+}
+
+static void tegra_xhci_disable_phy_sleepwalk(struct tegra_xusb *tegra)
+{
+	struct tegra_xusb_padctl *padctl = tegra->padctl;
+	int i;
+
+	for (i = 0; i < tegra->num_phys; i++) {
+		if (!tegra->phys[i])
+			continue;
+
+		tegra_xusb_padctl_disable_phy_sleepwalk(padctl, tegra->phys[i]);
+	}
+}
+
+static int tegra_xhci_check_ports_for_u3(struct tegra_xusb *tegra)
+{
+	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
+	struct device *dev = tegra->dev;
+	struct xhci_hub *rhubs[] = {&xhci->usb2_rhub, &xhci->usb3_rhub, NULL};
+	struct xhci_hub **rhub;
+	unsigned long flags;
+	u32 usbcmd, reg;
+	int i, ret = 0;
+
+	spin_lock_irqsave(&xhci->lock, flags);
+
+	usbcmd = readl(&xhci->op_regs->command);
+	usbcmd &= ~CMD_EIE;
+	writel(usbcmd, &xhci->op_regs->command);
+
+	for (rhub = rhubs; (*rhub) != NULL; rhub++) {
+		for (i = 0; i < (*rhub)->num_ports; i++) {
+			reg = readl((*rhub)->ports[i]->addr);
+			if (!(reg & PORT_PE))
+				continue;
+
+			if ((reg & PORT_PLS_MASK) != XDEV_U3) {
+				dev_info(dev, "%d-%d isn't suspended: 0x%08x\n",
+					 (*rhub)->hcd->self.busnum, i + 1, reg);
+				ret = -EBUSY;
+			}
+		}
+	}
+
+	spin_unlock_irqrestore(&xhci->lock, flags);
+
+	return ret;
+}
+
+/* caller must hold tegra->lock */
+static int tegra_xhci_enter_elpg(struct tegra_xusb *tegra, bool runtime)
+{
+	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
+	struct device *dev = tegra->dev;
+	bool do_wakeup = runtime ? true : device_may_wakeup(dev);
+	unsigned int i;
+	int rc;
+
+	dev_info(dev, "entering ELPG\n");
+
+	rc = tegra_xhci_check_ports_for_u3(tegra);
+	if (rc < 0)
+		goto out;
+
+	rc = xhci_suspend(xhci, do_wakeup);
+
+	if (rc) {
+		dev_warn(dev, "xhci_suspend() failed %d\n", rc);
+		goto out;
+	}
+
+	tegra_xhci_save_context(tegra);
+
+	if (do_wakeup)
+		tegra_xhci_enable_phy_sleepwalk_wake(tegra);
+
+	tegra_xusb_powergate_partitions(tegra);
+
+	for (i = 0; i < tegra->num_phys; i++) {
+		if (!tegra->phys[i])
+			continue;
+
+		phy_power_off(tegra->phys[i]);
+		if (!do_wakeup)
+			phy_exit(tegra->phys[i]);
+	}
+
+	tegra_xusb_clk_disable(tegra);
+out:
+	if (!rc)
+		dev_info(tegra->dev, "entering ELPG done\n");
+	else {
+		u32 usbcmd;
+
+		usbcmd = readl(&xhci->op_regs->command);
+		usbcmd |= CMD_EIE;
+		writel(usbcmd, &xhci->op_regs->command);
+
+		dev_info(tegra->dev, "entering ELPG failed\n");
+		pm_runtime_mark_last_busy(tegra->dev);
+	}
+
+	return rc;
+}
+
+/* caller must hold tegra->lock */
+static int tegra_xhci_exit_elpg(struct tegra_xusb *tegra, bool runtime)
+{
+	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
+	struct device *dev = tegra->dev;
+	struct tegra_xusb_mbox_msg msg;
+	bool do_wakeup = runtime ? true : device_may_wakeup(dev);
+	unsigned int i;
+	int rc;
+	u32 usbcmd;
+
+	dev_info(dev, "exiting ELPG\n");
+	pm_runtime_mark_last_busy(tegra->dev);
+
+	rc = tegra_xusb_clk_enable(tegra);
+	if (rc) {
+		dev_warn(dev, "failed to enable xhci clocks %d\n", rc);
+		goto out;
+	}
+
+	rc = tegra_xusb_unpowergate_partitions(tegra);
+	if (rc)
+		goto disable_clks;
+
+	if (do_wakeup)
+		tegra_xhci_disable_phy_wake(tegra);
+
+	for (i = 0; i < tegra->num_phys; i++) {
+		if (!tegra->phys[i])
+			continue;
+
+		if (!do_wakeup)
+			phy_init(tegra->phys[i]);
+
+		phy_power_on(tegra->phys[i]);
+	}
+
+	tegra_xusb_config(tegra);
+	tegra_xhci_restore_context(tegra);
+
+	rc = tegra_xusb_load_firmware(tegra);
+	if (rc < 0)
+		goto disable_phy;
+
+	msg.cmd = MBOX_CMD_MSG_ENABLED;
+	msg.data = 0;
+
+	rc = tegra_xusb_mbox_send(tegra, &msg);
+	if (rc < 0) {
+		dev_err(dev, "failed to enable messages: %d\n", rc);
+		goto disable_phy;
+	}
+
+	if (do_wakeup)
+		tegra_xhci_disable_phy_sleepwalk(tegra);
+
+	rc = xhci_resume(xhci, 0);
+
+	usbcmd = readl(&xhci->op_regs->command);
+	usbcmd |= CMD_EIE;
+	writel(usbcmd, &xhci->op_regs->command);
+
+	goto out;
+
+disable_phy:
+	for (i = 0; i < tegra->num_phys; i++) {
+		if (!tegra->phys[i])
+			continue;
+
+		phy_power_off(tegra->phys[i]);
+		if (!do_wakeup)
+			phy_exit(tegra->phys[i]);
+	}
+	tegra_xusb_powergate_partitions(tegra);
+disable_clks:
+	tegra_xusb_clk_disable(tegra);
+out:
+	if (!rc)
+		dev_info(dev, "exiting ELPG done\n");
+	else
+		dev_info(dev, "exiting ELPG failed\n");
+
+	return rc;
+}
+
 #ifdef CONFIG_PM_SLEEP
 static int tegra_xusb_suspend(struct device *dev)
 {
 	struct tegra_xusb *tegra = dev_get_drvdata(dev);
-	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
-	bool wakeup = device_may_wakeup(dev);
+	int ret;
+
+	synchronize_irq(tegra->mbox_irq);
+
+	mutex_lock(&tegra->lock);
+
+	if (pm_runtime_suspended(dev)) {
+		ret = tegra_xhci_exit_elpg(tegra, true);
+		if (ret < 0)
+			goto out;
+	}
+
+	ret = tegra_xhci_enter_elpg(tegra, false);
+	if (ret < 0) {
+		if (pm_runtime_suspended(dev)) {
+			pm_runtime_disable(dev);
+			pm_runtime_set_active(dev);
+			pm_runtime_enable(dev);
+		}
 
-	/* TODO: Powergate controller across suspend/resume. */
-	return xhci_suspend(xhci, wakeup);
+		goto out;
+	}
+
+out:
+	if (!ret) {
+		tegra->suspended = true;
+		pm_runtime_disable(dev);
+	}
+
+	mutex_unlock(&tegra->lock);
+
+	return ret;
 }
 
 static int tegra_xusb_resume(struct device *dev)
 {
 	struct tegra_xusb *tegra = dev_get_drvdata(dev);
-	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
+	int ret = 0;
+
+	mutex_lock(&tegra->lock);
+
+	if (!tegra->suspended) {
+		mutex_unlock(&tegra->lock);
+		return 0;
+	}
+
+	ret = tegra_xhci_exit_elpg(tegra, true);
+	if (ret < 0) {
+		mutex_unlock(&tegra->lock);
+		return ret;
+	}
+
+	tegra->suspended = false;
+	mutex_unlock(&tegra->lock);
 
-	return xhci_resume(xhci, 0);
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	return 0;
 }
 #endif
 
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM
  2019-06-14  7:48 ` [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM JC Kuo
@ 2019-06-18  6:33   ` Greg KH
  2019-06-18  8:01     ` JC Kuo
  2019-07-04 14:05   ` Jon Hunter
  1 sibling, 1 reply; 9+ messages in thread
From: Greg KH @ 2019-06-18  6:33 UTC (permalink / raw)
  To: JC Kuo
  Cc: thierry.reding, jonathanh, pdeschrijver, afrid, linux-tegra,
	linux-usb, devicetree, nkristam, skomatineni

On Fri, Jun 14, 2019 at 03:48:24PM +0800, JC Kuo wrote:
> This commit enables XUSB host controller ELPG for runtime and system
> power management.
> 
> NEED CLEANUP.

Odd kernel changelog comment...

Please cleanup when you resend. :)

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM
  2019-06-18  6:33   ` Greg KH
@ 2019-06-18  8:01     ` JC Kuo
  0 siblings, 0 replies; 9+ messages in thread
From: JC Kuo @ 2019-06-18  8:01 UTC (permalink / raw)
  To: Greg KH
  Cc: thierry.reding, jonathanh, pdeschrijver, afrid, linux-tegra,
	linux-usb, devicetree, nkristam, skomatineni

Hi Greg,
Code cleanup was done but I forgot the "NEED CLEANUP" in the commit comment. Sorry for that. I will wait for review comments to come and fix the commit message together with code improvements.

Thanks,
JC

On 6/18/19 2:33 PM, Greg KH wrote:
> On Fri, Jun 14, 2019 at 03:48:24PM +0800, JC Kuo wrote:
>> This commit enables XUSB host controller ELPG for runtime and system
>> power management.
>>
>> NEED CLEANUP.
> 
> Odd kernel changelog comment...
> 
> Please cleanup when you resend. :)
> 
> thanks,
> 
> greg k-h
> 

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk
  2019-06-14  7:48 [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk JC Kuo
                   ` (2 preceding siblings ...)
  2019-06-14  7:48 ` [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM JC Kuo
@ 2019-07-04 13:42 ` Jon Hunter
  3 siblings, 0 replies; 9+ messages in thread
From: Jon Hunter @ 2019-07-04 13:42 UTC (permalink / raw)
  To: JC Kuo, gregkh, thierry.reding, pdeschrijver, afrid
  Cc: linux-tegra, linux-usb, devicetree, nkristam, skomatineni


On 14/06/2019 08:48, JC Kuo wrote:
> This commit implements Tegra210 PMC USB 2.0 (UTMI and HSIC) Sleepwalk
> programming sequence. With Sleepwalk enabled, XUSB host controller
> can be put into ELPG (Engine Level PowerGate) state when controller
> is idle to save power. The Sleepwalk logic is in charge of wake event
> detection and maintain resume signal accordingly till XUSB host
> controller is bring out of ELPG.

Ok, so these new global APIs will be called directly by the Tegra XUSB
driver? Could this be implemented as (another) pinctrl driver for the
PMC that the XUSB driver could reference from DT?

Cheers
Jon

-- 
nvpublic

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 6/8] phy: tegra: xusb: t210: support wake and sleepwalk
  2019-06-14  7:48 ` [PATCH 6/8] phy: tegra: xusb: t210: support wake and sleepwalk JC Kuo
@ 2019-07-04 13:53   ` Jon Hunter
  0 siblings, 0 replies; 9+ messages in thread
From: Jon Hunter @ 2019-07-04 13:53 UTC (permalink / raw)
  To: JC Kuo, gregkh, thierry.reding, pdeschrijver, afrid
  Cc: linux-tegra, linux-usb, devicetree, nkristam, skomatineni


On 14/06/2019 08:48, JC Kuo wrote:
> This commit implements Tegra210 XUSB PADCTL wake and sleepwalk
> routines.

This patch does not apply cleanly. I am not sure how this was generated
but appears to come from windows (with ^M end of line character). Please
fix this.

Alot of these enable/disble functions looks very similar; programming
the same registers just with different bits. Maybe worth considering
consolidating these functions.

Cheers
Jon

-- 
nvpublic

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM
  2019-06-14  7:48 ` [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM JC Kuo
  2019-06-18  6:33   ` Greg KH
@ 2019-07-04 14:05   ` Jon Hunter
  1 sibling, 0 replies; 9+ messages in thread
From: Jon Hunter @ 2019-07-04 14:05 UTC (permalink / raw)
  To: JC Kuo, gregkh, thierry.reding, pdeschrijver, afrid
  Cc: linux-tegra, linux-usb, devicetree, nkristam, skomatineni



On 14/06/2019 08:48, JC Kuo wrote:
> This commit enables XUSB host controller ELPG for runtime and system
> power management.
> 
> NEED CLEANUP.
> 
> Signed-off-by: JC Kuo <jckuo@nvidia.com>
> ---
>  drivers/usb/host/xhci-tegra.c | 802 ++++++++++++++++++++++++++++------
>  1 file changed, 671 insertions(+), 131 deletions(-)
> 
> diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c
> index 294158113d62..ade56e63212b 100644
> --- a/drivers/usb/host/xhci-tegra.c
> +++ b/drivers/usb/host/xhci-tegra.c
> @@ -17,6 +17,7 @@
>  #include <linux/phy/phy.h>
>  #include <linux/phy/tegra/xusb.h>
>  #include <linux/platform_device.h>
> +#include <linux/usb/ch9.h>
>  #include <linux/pm.h>
>  #include <linux/pm_domain.h>
>  #include <linux/pm_runtime.h>
> @@ -38,7 +39,17 @@
>  #define XUSB_CFG_4				0x010
>  #define  XUSB_BASE_ADDR_SHIFT			15
>  #define  XUSB_BASE_ADDR_MASK			0x1ffff
> +#define XUSB_CFG_16				0x040
> +#define XUSB_CFG_24				0x060
> +#define XUSB_CFG_AXI_CFG			0x0f8
> +#define XUSB_CFG_ARU_C11PAGESEL			0x404
> +#define  XUSB_HSP0				BIT(12)
>  #define XUSB_CFG_ARU_C11_CSBRANGE		0x41c
> +#define XUSB_CFG_ARU_CONTEXT			0x43c
> +#define XUSB_CFG_ARU_CONTEXT_HS_PLS		0x478
> +#define XUSB_CFG_ARU_CONTEXT_FS_PLS		0x47c
> +#define XUSB_CFG_ARU_CONTEXT_HSFS_SPEED		0x480
> +#define XUSB_CFG_ARU_CONTEXT_HSFS_PP		0x484
>  #define XUSB_CFG_CSB_BASE_ADDR			0x800
>  
>  /* FPCI mailbox registers */
> @@ -63,11 +74,20 @@
>  #define  MBOX_SMI_INTR_EN			BIT(3)
>  
>  /* IPFS registers */
> +#define XUSB_HOST_MSI_BAR_SZ_0			0x0c0
> +#define XUSB_HOST_MSI_AXI_BAR_ST_0		0x0c4
> +#define XUSB_HOST_MSI_FPCI_BAR_ST_0		0x0c8
> +#define XUSB_HOST_MSI_VEC0_0			0x100
> +#define XUSB_HOST_MSI_EN_VEC0_0			0x140
>  #define IPFS_XUSB_HOST_CONFIGURATION_0		0x180
>  #define  IPFS_EN_FPCI				BIT(0)
> +#define XUSB_HOST_FPCI_ERROR_MASKS_0		0x184
>  #define IPFS_XUSB_HOST_INTR_MASK_0		0x188
>  #define  IPFS_IP_INT_MASK			BIT(16)
> +#define XUSB_HOST_IPFS_INTR_ENABLE_0		0x198
> +#define XUSB_HOST_UFPCI_CONFIG_0		0x19c
>  #define IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0	0x1bc
> +#define XUSB_HOST_MCCIF_FIFOCTRL_0		0x1dc
>  
>  #define CSB_PAGE_SELECT_MASK			0x7fffff
>  #define CSB_PAGE_SELECT_SHIFT			9
> @@ -164,6 +184,31 @@ struct tegra_xusb_soc {
>  	bool has_ipfs;
>  };
>  
> +struct tegra_xhci_ipfs_context {
> +	u32 msi_bar_sz;
> +	u32 msi_axi_barst;
> +	u32 msi_fpci_barst;
> +	u32 msi_vec0;
> +	u32 msi_en_vec0;
> +	u32 fpci_error_masks;
> +	u32 intr_mask;
> +	u32 ipfs_intr_enable;
> +	u32 ufpci_config;
> +	u32 clkgate_hysteresis;
> +	u32 xusb_host_mccif_fifo_cntrl;
> +};
> +
> +struct tegra_xhci_fpci_context {
> +	u32 hs_pls;
> +	u32 fs_pls;
> +	u32 hsfs_speed;
> +	u32 hsfs_pp;
> +	u32 cfg_aru;
> +	u32 cfg_order;
> +	u32 cfg_fladj;
> +	u32 cfg_sid;
> +};
> +
>  struct tegra_xusb {
>  	struct device *dev;
>  	void __iomem *regs;
> @@ -173,6 +218,7 @@ struct tegra_xusb {
>  
>  	int xhci_irq;
>  	int mbox_irq;
> +	int padctl_irq;
>  
>  	void __iomem *ipfs_base;
>  	void __iomem *fpci_base;
> @@ -198,8 +244,6 @@ struct tegra_xusb {
>  
>  	struct device *genpd_dev_host;
>  	struct device *genpd_dev_ss;
> -	struct device_link *genpd_dl_host;
> -	struct device_link *genpd_dl_ss;
>  
>  	struct phy **phys;
>  	unsigned int num_phys;
> @@ -210,9 +254,15 @@ struct tegra_xusb {
>  		void *virt;
>  		dma_addr_t phys;
>  	} fw;
> +
> +	bool suspended;
> +	struct tegra_xhci_fpci_context fpci_ctx;
> +	struct tegra_xhci_ipfs_context ipfs_ctx;
>  };
>  
>  static struct hc_driver __read_mostly tegra_xhci_hc_driver;
> +static int tegra_xhci_exit_elpg(struct tegra_xusb *tegra, bool runtime);
> +static int tegra_xhci_enter_elpg(struct tegra_xusb *tegra, bool runtime);
>  
>  static inline u32 fpci_readl(struct tegra_xusb *tegra, unsigned int offset)
>  {
> @@ -585,6 +635,14 @@ static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra,
>  								     enable);
>  			if (err < 0)
>  				break;
> +
> +			if (!enable) {
> +				/*
> +				 * Add this delay to increase stability of
> +				 * directing U3.
> +				 */
> +				usleep_range(500, 1000);

Please elaborate.

> +			}
>  		}
>  
>  		if (err < 0) {
> @@ -621,6 +679,9 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data)
>  
>  	mutex_lock(&tegra->lock);
>  
> +	if (pm_runtime_suspended(tegra->dev) || tegra->suspended)
> +		goto out;
> +
>  	value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_DATA_OUT);
>  	tegra_xusb_mbox_unpack(&msg, value);
>  
> @@ -634,13 +695,14 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data)
>  
>  	tegra_xusb_mbox_handle(tegra, &msg);
>  
> +out:
>  	mutex_unlock(&tegra->lock);
>  	return IRQ_HANDLED;
>  }
>  
> -static void tegra_xusb_config(struct tegra_xusb *tegra,
> -			      struct resource *regs)
> +static void tegra_xusb_config(struct tegra_xusb *tegra)
>  {
> +	resource_size_t base_addr = tegra->hcd->rsrc_start;
>  	u32 value;
>  
>  	if (tegra->soc->has_ipfs) {
> @@ -654,7 +716,7 @@ static void tegra_xusb_config(struct tegra_xusb *tegra,
>  	/* Program BAR0 space */
>  	value = fpci_readl(tegra, XUSB_CFG_4);
>  	value &= ~(XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT);
> -	value |= regs->start & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT);
> +	value |= base_addr & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT);
>  	fpci_writel(tegra, value, XUSB_CFG_4);
>  
>  	usleep_range(100, 200);
> @@ -777,44 +839,57 @@ static void tegra_xusb_phy_disable(struct tegra_xusb *tegra)
>  static int tegra_xusb_runtime_suspend(struct device *dev)
>  {
>  	struct tegra_xusb *tegra = dev_get_drvdata(dev);
> +	int ret;
>  
> -	tegra_xusb_phy_disable(tegra);
> -	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
> -	tegra_xusb_clk_disable(tegra);
> +	synchronize_irq(tegra->mbox_irq);
>  
> -	return 0;
> +	mutex_lock(&tegra->lock);
> +	ret = tegra_xhci_enter_elpg(tegra, true);
> +	mutex_unlock(&tegra->lock);
> +
> +	return ret;
>  }
>  
>  static int tegra_xusb_runtime_resume(struct device *dev)
>  {
>  	struct tegra_xusb *tegra = dev_get_drvdata(dev);
> -	int err;
> +	int ret;
>  
> -	err = tegra_xusb_clk_enable(tegra);
> -	if (err) {
> -		dev_err(dev, "failed to enable clocks: %d\n", err);
> -		return err;
> -	}
> +	mutex_lock(&tegra->lock);
> +	ret = tegra_xhci_exit_elpg(tegra, true);
> +	mutex_unlock(&tegra->lock);
>  
> -	err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies);
> -	if (err) {
> -		dev_err(dev, "failed to enable regulators: %d\n", err);
> -		goto disable_clk;
> -	}
> +	return ret;
> +}
>  
> -	err = tegra_xusb_phy_enable(tegra);
> -	if (err < 0) {
> -		dev_err(dev, "failed to enable PHYs: %d\n", err);
> -		goto disable_regulator;
> +static int tegra_xusb_request_firmware(struct tegra_xusb *tegra)
> +{
> +	struct tegra_xusb_fw_header *header;
> +	struct device *dev = tegra->dev;
> +	const struct firmware *fw;
> +	int rc;
> +
> +	if (!tegra->fw.virt) {

Either return here or check this before calling this function.

> +		rc = request_firmware(&fw, tegra->soc->firmware, tegra->dev);
> +		if (rc < 0) {
> +			dev_err(dev, "failed to request firmware: %d\n", rc);
> +			return rc;
> +		}
> +
> +		header = (struct tegra_xusb_fw_header *)fw->data;
> +		tegra->fw.size = le32_to_cpu(header->fwimg_len);
> +		tegra->fw.virt = dma_alloc_coherent(dev, tegra->fw.size,
> +						   &tegra->fw.phys, GFP_KERNEL);
> +		if (!tegra->fw.virt) {
> +			release_firmware(fw);
> +			return -ENOMEM;
> +		}
> +
> +		memcpy(tegra->fw.virt, fw->data, tegra->fw.size);
> +		release_firmware(fw);
>  	}
>  
>  	return 0;
> -
> -disable_regulator:
> -	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
> -disable_clk:
> -	tegra_xusb_clk_disable(tegra);
> -	return err;
>  }
>  
>  static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
> @@ -822,7 +897,6 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
>  	unsigned int code_tag_blocks, code_size_blocks, code_blocks;
>  	struct tegra_xusb_fw_header *header;
>  	struct device *dev = tegra->dev;
> -	const struct firmware *fw;
>  	unsigned long timeout;
>  	time64_t timestamp;
>  	struct tm time;
> @@ -830,27 +904,9 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
>  	u32 value;
>  	int err;
>  
> -	err = request_firmware(&fw, tegra->soc->firmware, tegra->dev);
> -	if (err < 0) {
> -		dev_err(tegra->dev, "failed to request firmware: %d\n", err);
> +	err = tegra_xusb_request_firmware(tegra);
> +	if (err)
>  		return err;
> -	}
> -
> -	/* Load Falcon controller with its firmware. */
> -	header = (struct tegra_xusb_fw_header *)fw->data;
> -	tegra->fw.size = le32_to_cpu(header->fwimg_len);
> -
> -	tegra->fw.virt = dma_alloc_coherent(tegra->dev, tegra->fw.size,
> -					    &tegra->fw.phys, GFP_KERNEL);
> -	if (!tegra->fw.virt) {
> -		dev_err(tegra->dev, "failed to allocate memory for firmware\n");
> -		release_firmware(fw);
> -		return -ENOMEM;
> -	}
> -
> -	header = (struct tegra_xusb_fw_header *)tegra->fw.virt;
> -	memcpy(tegra->fw.virt, fw->data, tegra->fw.size);
> -	release_firmware(fw);
>  
>  	if (csb_readl(tegra, XUSB_CSB_MP_ILOAD_BASE_LO) != 0) {
>  		dev_info(dev, "Firmware already loaded, Falcon state %#x\n",
> @@ -865,6 +921,7 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
>  	 * Boot code of the firmware reads the ILOAD_BASE registers
>  	 * to get to the start of the DFI in system memory.
>  	 */
> +	header = (struct tegra_xusb_fw_header *)tegra->fw.virt;
>  	address = tegra->fw.phys + sizeof(*header);
>  	csb_writel(tegra, address >> 32, XUSB_CSB_MP_ILOAD_BASE_HI);
>  	csb_writel(tegra, address, XUSB_CSB_MP_ILOAD_BASE_LO);
> @@ -942,10 +999,6 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
>  static void tegra_xusb_powerdomain_remove(struct device *dev,
>  					  struct tegra_xusb *tegra)
>  {
> -	if (tegra->genpd_dl_ss)
> -		device_link_del(tegra->genpd_dl_ss);
> -	if (tegra->genpd_dl_host)
> -		device_link_del(tegra->genpd_dl_host);
>  	if (!IS_ERR_OR_NULL(tegra->genpd_dev_ss))
>  		dev_pm_domain_detach(tegra->genpd_dev_ss, true);
>  	if (!IS_ERR_OR_NULL(tegra->genpd_dev_host))
> @@ -971,25 +1024,102 @@ static int tegra_xusb_powerdomain_init(struct device *dev,
>  		return err;
>  	}
>  
> -	tegra->genpd_dl_host = device_link_add(dev, tegra->genpd_dev_host,
> -					       DL_FLAG_PM_RUNTIME |
> -					       DL_FLAG_STATELESS);
> -	if (!tegra->genpd_dl_host) {
> -		dev_err(dev, "adding host device link failed!\n");
> -		return -ENODEV;
> +	return 0;
> +}
> +
> +static int tegra_xusb_unpowergate_partitions(struct tegra_xusb *tegra)
> +{
> +	struct device *dev = tegra->dev;
> +	bool use_genpd;
> +	int rc;
> +
> +	use_genpd = of_property_read_bool(dev->of_node, "power-domains");
> +
> +	if (use_genpd)
> +		rc = pm_runtime_get_sync(tegra->genpd_dev_ss);
> +	else {
> +		rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA,
> +							tegra->ss_clk,
> +							tegra->ss_rst);
> +	}
> +	if (rc < 0) {
> +		dev_err(dev, "failed to enable XUSB SS partition: %d\n", rc);
> +		return rc;
> +	}
> +
> +	if (use_genpd)
> +		rc = pm_runtime_get_sync(tegra->genpd_dev_host);
> +	else {
> +		rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC,
> +							tegra->host_clk,
> +							tegra->host_rst);
> +	}
> +	if (rc < 0) {
> +		dev_err(dev, "failed to enable XUSB Host partition: %d\n", rc);
> +		if (use_genpd)
> +			pm_runtime_put_sync(tegra->genpd_dev_ss);
> +		else
> +			tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
> +		return rc;
> +	}
> +
> +	return 0;
> +}
> +
> +static int tegra_xusb_powergate_partitions(struct tegra_xusb *tegra)
> +{
> +	struct device *dev = tegra->dev;
> +	bool use_genpd;
> +	int rc;
> +
> +	use_genpd = of_property_read_bool(dev->of_node, "power-domains");
> +
> +	if (use_genpd)
> +		rc = pm_runtime_put_sync(tegra->genpd_dev_host);
> +	else
> +		rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC);
> +
> +	if (rc < 0) {
> +		dev_err(dev, "failed to disable XUSB Host partition: %d\n", rc);
> +		return rc;
>  	}
>  
> -	tegra->genpd_dl_ss = device_link_add(dev, tegra->genpd_dev_ss,
> -					     DL_FLAG_PM_RUNTIME |
> -					     DL_FLAG_STATELESS);
> -	if (!tegra->genpd_dl_ss) {
> -		dev_err(dev, "adding superspeed device link failed!\n");
> -		return -ENODEV;
> +	if (use_genpd)
> +		rc = pm_runtime_put_sync(tegra->genpd_dev_ss);
> +	else
> +		rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
> +
> +	if (rc < 0) {
> +		dev_err(dev, "failed to disable XUSB SS partition: %d\n", rc);
> +		if (use_genpd)
> +			pm_runtime_get_sync(tegra->genpd_dev_host);
> +		else {
> +			tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC,
> +							  tegra->host_clk,
> +							  tegra->host_rst);
> +		}
> +		return rc;

Sorry but NAK!

It has taken us literally years to get the XUSB driver working with
genpd for power-domains, this is a massive step backwards. Sorry but no.
There should be no need to use these legacy APIs.

>  	}
>  
>  	return 0;
>  }
>  
> +static irqreturn_t tegra_xusb_padctl_irq(int irq, void *data)
> +{
> +	struct tegra_xusb *tegra = data;
> +
> +	mutex_lock(&tegra->lock);
> +	if (tegra->suspended) {
> +		mutex_unlock(&tegra->lock);
> +		return IRQ_HANDLED;
> +	}
> +	mutex_unlock(&tegra->lock);
> +
> +	pm_runtime_resume(tegra->dev);
> +
> +	return IRQ_HANDLED;
> +}
> +
>  static int tegra_xusb_probe(struct platform_device *pdev)
>  {
>  	struct tegra_xusb_mbox_msg msg;
> @@ -1035,6 +1165,10 @@ static int tegra_xusb_probe(struct platform_device *pdev)
>  	if (tegra->mbox_irq < 0)
>  		return tegra->mbox_irq;
>  
> +	tegra->padctl_irq = platform_get_irq(pdev, 2);
> +	if (tegra->padctl_irq < 0)
> +		return tegra->padctl_irq;
> +
>  	tegra->padctl = tegra_xusb_padctl_get(&pdev->dev);
>  	if (IS_ERR(tegra->padctl))
>  		return PTR_ERR(tegra->padctl);
> @@ -1119,25 +1253,6 @@ static int tegra_xusb_probe(struct platform_device *pdev)
>  				err);
>  			goto put_padctl;
>  		}
> -
> -		err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA,
> -							tegra->ss_clk,
> -							tegra->ss_rst);
> -		if (err) {
> -			dev_err(&pdev->dev,
> -				"failed to enable XUSBA domain: %d\n", err);
> -			goto put_padctl;
> -		}
> -
> -		err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC,
> -							tegra->host_clk,
> -							tegra->host_rst);
> -		if (err) {
> -			tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
> -			dev_err(&pdev->dev,
> -				"failed to enable XUSBC domain: %d\n", err);
> -			goto put_padctl;
> -		}
>  	} else {
>  		err = tegra_xusb_powerdomain_init(&pdev->dev, tegra);
>  		if (err)
> @@ -1197,6 +1312,10 @@ static int tegra_xusb_probe(struct platform_device *pdev)
>  		err = -ENOMEM;
>  		goto put_powerdomains;
>  	}
> +	tegra->hcd->skip_phy_initialization = 1;
> +	tegra->hcd->regs = tegra->regs;
> +	tegra->hcd->rsrc_start = regs->start;
> +	tegra->hcd->rsrc_len = resource_size(regs);
>  
>  	/*
>  	 * This must happen after usb_create_hcd(), because usb_create_hcd()
> @@ -1204,33 +1323,40 @@ static int tegra_xusb_probe(struct platform_device *pdev)
>  	 */
>  	platform_set_drvdata(pdev, tegra);
>  
> -	pm_runtime_enable(&pdev->dev);
> -	if (pm_runtime_enabled(&pdev->dev))
> -		err = pm_runtime_get_sync(&pdev->dev);
> -	else
> -		err = tegra_xusb_runtime_resume(&pdev->dev);
> +	err = tegra_xusb_clk_enable(tegra);
> +	if (err) {
> +		dev_err(tegra->dev, "failed to enable clocks: %d\n", err);
> +		goto put_hcd;
> +	}
> +
> +	err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies);
> +	if (err) {
> +		dev_err(tegra->dev, "failed to enable regulators: %d\n", err);
> +		goto disable_clk;
> +	}
>  
> +	err = tegra_xusb_phy_enable(tegra);
>  	if (err < 0) {
> -		dev_err(&pdev->dev, "failed to enable device: %d\n", err);
> -		goto disable_rpm;
> +		dev_err(tegra->dev, "failed to enable PHYs: %d\n", err);
> +		goto disable_regulator;
>  	}
>  
> -	tegra_xusb_config(tegra, regs);
> +	err = tegra_xusb_unpowergate_partitions(tegra);
> +	if (err)
> +		goto disable_phy;
> +
> +	tegra_xusb_config(tegra);
>  
>  	err = tegra_xusb_load_firmware(tegra);
>  	if (err < 0) {
>  		dev_err(&pdev->dev, "failed to load firmware: %d\n", err);
> -		goto put_rpm;
> +		goto powergate;
>  	}
>  
> -	tegra->hcd->regs = tegra->regs;
> -	tegra->hcd->rsrc_start = regs->start;
> -	tegra->hcd->rsrc_len = resource_size(regs);
> -
>  	err = usb_add_hcd(tegra->hcd, tegra->xhci_irq, IRQF_SHARED);
>  	if (err < 0) {
>  		dev_err(&pdev->dev, "failed to add USB HCD: %d\n", err);
> -		goto put_rpm;
> +		goto powergate;
>  	}
>  
>  	device_wakeup_enable(tegra->hcd->self.controller);
> @@ -1253,6 +1379,26 @@ static int tegra_xusb_probe(struct platform_device *pdev)
>  		goto put_usb3;
>  	}
>  
> +	err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq,
> +					tegra_xusb_mbox_irq,
> +					tegra_xusb_mbox_thread, 0,
> +					dev_name(&pdev->dev), tegra);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "failed to request mbox IRQ: %d\n", err);
> +		goto remove_usb3;
> +	}
> +
> +	err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq,
> +					NULL,
> +					tegra_xusb_padctl_irq,
> +					IRQF_ONESHOT |
> +					IRQF_TRIGGER_HIGH,
> +					dev_name(&pdev->dev), tegra);
> +	if (err < 0) {
> +		dev_err(&pdev->dev, "failed to request padctl IRQ: %d\n", err);
> +		goto remove_usb3;
> +	}
> +
>  	mutex_lock(&tegra->lock);
>  
>  	/* Enable firmware messages from controller. */
> @@ -1268,14 +1414,16 @@ static int tegra_xusb_probe(struct platform_device *pdev)
>  
>  	mutex_unlock(&tegra->lock);
>  
> -	err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq,
> -					tegra_xusb_mbox_irq,
> -					tegra_xusb_mbox_thread, 0,
> -					dev_name(&pdev->dev), tegra);
> -	if (err < 0) {
> -		dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
> -		goto remove_usb3;
> -	}
> +	/* Enable wake for both USB 2.0 and USB 3.0 roothubs */
> +	device_init_wakeup(&tegra->hcd->self.root_hub->dev, true);
> +	device_init_wakeup(&xhci->shared_hcd->self.root_hub->dev, true);
> +	device_init_wakeup(tegra->dev, true);
> +
> +	pm_runtime_use_autosuspend(tegra->dev);
> +	pm_runtime_set_autosuspend_delay(tegra->dev, 2000);
> +	pm_runtime_mark_last_busy(tegra->dev);
> +	pm_runtime_set_active(tegra->dev);
> +	pm_runtime_enable(tegra->dev);
>  
>  	return 0;
>  
> @@ -1285,19 +1433,18 @@ static int tegra_xusb_probe(struct platform_device *pdev)
>  	usb_put_hcd(xhci->shared_hcd);
>  remove_usb2:
>  	usb_remove_hcd(tegra->hcd);
> -put_rpm:
> -	if (!pm_runtime_status_suspended(&pdev->dev))
> -		tegra_xusb_runtime_suspend(&pdev->dev);
> -disable_rpm:
> -	pm_runtime_disable(&pdev->dev);
> +powergate:
> +	tegra_xusb_powergate_partitions(tegra);
> +disable_phy:
> +	tegra_xusb_phy_disable(tegra);
> +disable_regulator:
> +	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
> +disable_clk:
> +	tegra_xusb_clk_disable(tegra);
> +put_hcd:
>  	usb_put_hcd(tegra->hcd);
>  put_powerdomains:
> -	if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) {
> -		tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC);
> -		tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
> -	} else {
> -		tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
> -	}
> +	tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
>  put_padctl:
>  	tegra_xusb_padctl_put(tegra->padctl);
>  	return err;
> @@ -1308,6 +1455,8 @@ static int tegra_xusb_remove(struct platform_device *pdev)
>  	struct tegra_xusb *tegra = platform_get_drvdata(pdev);
>  	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
>  
> +	pm_runtime_get_sync(&pdev->dev);
> +
>  	usb_remove_hcd(xhci->shared_hcd);
>  	usb_put_hcd(xhci->shared_hcd);
>  	xhci->shared_hcd = NULL;
> @@ -1317,38 +1466,429 @@ static int tegra_xusb_remove(struct platform_device *pdev)
>  	dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt,
>  			  tegra->fw.phys);
>  
> -	pm_runtime_put_sync(&pdev->dev);
>  	pm_runtime_disable(&pdev->dev);
> +	pm_runtime_put(&pdev->dev);
>  
> -	if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) {
> -		tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC);
> -		tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
> -	} else {
> +	tegra_xusb_powergate_partitions(tegra);
> +
> +	if (of_property_read_bool(pdev->dev.of_node, "power-domains"))
>  		tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
> -	}
>  
> +	tegra_xusb_phy_disable(tegra);
> +	tegra_xusb_clk_disable(tegra);
> +	regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
>  	tegra_xusb_padctl_put(tegra->padctl);

Do you release the firmware anywhere?

>  
>  	return 0;
>  }
>  
> +static void tegra_xhci_save_context(struct tegra_xusb *tegra)
> +{
> +	if (tegra->soc->has_ipfs) {
> +		/* Save IPFS registers */
> +		tegra->ipfs_ctx.msi_bar_sz =
> +			ipfs_readl(tegra, XUSB_HOST_MSI_BAR_SZ_0);
> +		tegra->ipfs_ctx.msi_axi_barst =
> +			ipfs_readl(tegra, XUSB_HOST_MSI_AXI_BAR_ST_0);
> +		tegra->ipfs_ctx.msi_fpci_barst =
> +			ipfs_readl(tegra, XUSB_HOST_MSI_FPCI_BAR_ST_0);
> +		tegra->ipfs_ctx.msi_vec0 =
> +			ipfs_readl(tegra, XUSB_HOST_MSI_VEC0_0);
> +		tegra->ipfs_ctx.msi_en_vec0 =
> +			ipfs_readl(tegra, XUSB_HOST_MSI_EN_VEC0_0);
> +		tegra->ipfs_ctx.fpci_error_masks =
> +			ipfs_readl(tegra, XUSB_HOST_FPCI_ERROR_MASKS_0);
> +		tegra->ipfs_ctx.intr_mask =
> +			ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0);
> +		tegra->ipfs_ctx.ipfs_intr_enable =
> +			ipfs_readl(tegra, XUSB_HOST_IPFS_INTR_ENABLE_0);
> +		tegra->ipfs_ctx.ufpci_config =
> +			ipfs_readl(tegra, XUSB_HOST_UFPCI_CONFIG_0);
> +		tegra->ipfs_ctx.clkgate_hysteresis =
> +			ipfs_readl(tegra,
> +				IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0);
> +		tegra->ipfs_ctx.xusb_host_mccif_fifo_cntrl =
> +			ipfs_readl(tegra, XUSB_HOST_MCCIF_FIFOCTRL_0);
> +	}
> +
> +	/* Save FPCI registers */
> +	tegra->fpci_ctx.hs_pls =
> +		fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_HS_PLS);
> +	tegra->fpci_ctx.fs_pls =
> +		fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_FS_PLS);
> +	tegra->fpci_ctx.hsfs_speed =
> +		fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_HSFS_SPEED);
> +	tegra->fpci_ctx.hsfs_pp =
> +		fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_HSFS_PP);
> +	tegra->fpci_ctx.cfg_aru = fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT);
> +	tegra->fpci_ctx.cfg_order = fpci_readl(tegra, XUSB_CFG_AXI_CFG);
> +	tegra->fpci_ctx.cfg_fladj = fpci_readl(tegra, XUSB_CFG_24);
> +	tegra->fpci_ctx.cfg_sid = fpci_readl(tegra, XUSB_CFG_16);
> +}
> +
> +static void tegra_xhci_restore_context(struct tegra_xusb *tegra)
> +{
> +	/* Restore FPCI registers */
> +	fpci_writel(tegra, tegra->fpci_ctx.hs_pls, XUSB_CFG_ARU_CONTEXT_HS_PLS);
> +	fpci_writel(tegra, tegra->fpci_ctx.fs_pls, XUSB_CFG_ARU_CONTEXT_FS_PLS);
> +	fpci_writel(tegra, tegra->fpci_ctx.hsfs_speed,
> +		    XUSB_CFG_ARU_CONTEXT_HSFS_SPEED);
> +	fpci_writel(tegra, tegra->fpci_ctx.hsfs_pp,
> +		    XUSB_CFG_ARU_CONTEXT_HSFS_PP);
> +	fpci_writel(tegra, tegra->fpci_ctx.cfg_aru, XUSB_CFG_ARU_CONTEXT);
> +	fpci_writel(tegra, tegra->fpci_ctx.cfg_order, XUSB_CFG_AXI_CFG);
> +	fpci_writel(tegra, tegra->fpci_ctx.cfg_fladj, XUSB_CFG_24);
> +	fpci_writel(tegra, tegra->fpci_ctx.cfg_sid, XUSB_CFG_16);
> +
> +	if (tegra->soc->has_ipfs) {
> +		/* Restore IPFS registers */
> +		ipfs_writel(tegra, tegra->ipfs_ctx.msi_bar_sz,
> +			XUSB_HOST_MSI_BAR_SZ_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.msi_axi_barst,
> +			XUSB_HOST_MSI_AXI_BAR_ST_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.msi_fpci_barst,
> +			XUSB_HOST_MSI_FPCI_BAR_ST_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.msi_vec0,
> +			XUSB_HOST_MSI_VEC0_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.msi_en_vec0,
> +			XUSB_HOST_MSI_EN_VEC0_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.fpci_error_masks,
> +			XUSB_HOST_FPCI_ERROR_MASKS_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.intr_mask,
> +			IPFS_XUSB_HOST_INTR_MASK_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.ipfs_intr_enable,
> +			XUSB_HOST_IPFS_INTR_ENABLE_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.ufpci_config,
> +			XUSB_HOST_UFPCI_CONFIG_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.clkgate_hysteresis,
> +			IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0);
> +		ipfs_writel(tegra, tegra->ipfs_ctx.xusb_host_mccif_fifo_cntrl,
> +			XUSB_HOST_MCCIF_FIFOCTRL_0);
> +	}
> +}
> +
> +static enum usb_device_speed
> +tegra_xhci_portsc_to_speed(struct tegra_xusb *tegra, u32 portsc)
> +{
> +	if (DEV_LOWSPEED(portsc))
> +		return USB_SPEED_LOW;
> +	else if (DEV_HIGHSPEED(portsc))
> +		return USB_SPEED_HIGH;
> +	else if (DEV_FULLSPEED(portsc))
> +		return USB_SPEED_FULL;
> +	else if (DEV_SUPERSPEED_ANY(portsc))
> +		return USB_SPEED_SUPER;
> +	else
> +		return USB_SPEED_UNKNOWN;
> +}
> +
> +static void tegra_xhci_enable_phy_sleepwalk_wake(struct tegra_xusb *tegra)
> +{
> +	struct tegra_xusb_padctl *padctl = tegra->padctl;
> +	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
> +	enum usb_device_speed speed;
> +	struct phy *phy;
> +	int index, offset;
> +	int i, j, k;
> +	struct xhci_hub *rhub;
> +	u32 portsc;
> +
> +	for (i = 0, k = 0; i < tegra->soc->num_types; i++) {
> +		if (strcmp(tegra->soc->phy_types[i].name, "usb3") == 0)
> +			rhub = &xhci->usb3_rhub;
> +		else
> +			rhub = &xhci->usb2_rhub;
> +
> +		if (strcmp(tegra->soc->phy_types[i].name, "hsic") == 0)
> +			offset = tegra->soc->ports.usb2.count;
> +		else
> +			offset = 0;
> +
> +		for (j = 0; j < tegra->soc->phy_types[i].num; j++) {
> +			phy = tegra->phys[k++];
> +
> +			if (!phy)
> +				continue;
> +
> +			index = j + offset;
> +
> +			if (index >= rhub->num_ports)
> +				continue;
> +
> +			portsc = readl(rhub->ports[index]->addr);
> +			speed = tegra_xhci_portsc_to_speed(tegra, portsc);
> +			tegra_xusb_padctl_enable_phy_sleepwalk(padctl, phy,
> +							       speed);
> +			tegra_xusb_padctl_enable_phy_wake(padctl, phy);
> +		}
> +	}
> +}
> +
> +static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra)
> +{
> +	struct tegra_xusb_padctl *padctl = tegra->padctl;
> +	int i;
> +
> +	for (i = 0; i < tegra->num_phys; i++) {
> +		if (!tegra->phys[i])
> +			continue;
> +
> +		tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]);
> +	}
> +}
> +
> +static void tegra_xhci_disable_phy_sleepwalk(struct tegra_xusb *tegra)
> +{
> +	struct tegra_xusb_padctl *padctl = tegra->padctl;
> +	int i;
> +
> +	for (i = 0; i < tegra->num_phys; i++) {
> +		if (!tegra->phys[i])
> +			continue;
> +
> +		tegra_xusb_padctl_disable_phy_sleepwalk(padctl, tegra->phys[i]);
> +	}
> +}
> +
> +static int tegra_xhci_check_ports_for_u3(struct tegra_xusb *tegra)
> +{
> +	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
> +	struct device *dev = tegra->dev;
> +	struct xhci_hub *rhubs[] = {&xhci->usb2_rhub, &xhci->usb3_rhub, NULL};
> +	struct xhci_hub **rhub;
> +	unsigned long flags;
> +	u32 usbcmd, reg;
> +	int i, ret = 0;
> +
> +	spin_lock_irqsave(&xhci->lock, flags);
> +
> +	usbcmd = readl(&xhci->op_regs->command);
> +	usbcmd &= ~CMD_EIE;
> +	writel(usbcmd, &xhci->op_regs->command);
> +
> +	for (rhub = rhubs; (*rhub) != NULL; rhub++) {
> +		for (i = 0; i < (*rhub)->num_ports; i++) {
> +			reg = readl((*rhub)->ports[i]->addr);
> +			if (!(reg & PORT_PE))
> +				continue;
> +
> +			if ((reg & PORT_PLS_MASK) != XDEV_U3) {
> +				dev_info(dev, "%d-%d isn't suspended: 0x%08x\n",
> +					 (*rhub)->hcd->self.busnum, i + 1, reg);
> +				ret = -EBUSY;
> +			}
> +		}
> +	}
> +
> +	spin_unlock_irqrestore(&xhci->lock, flags);
> +
> +	return ret;
> +}
> +
> +/* caller must hold tegra->lock */
> +static int tegra_xhci_enter_elpg(struct tegra_xusb *tegra, bool runtime)
> +{
> +	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
> +	struct device *dev = tegra->dev;
> +	bool do_wakeup = runtime ? true : device_may_wakeup(dev);
> +	unsigned int i;
> +	int rc;
> +
> +	dev_info(dev, "entering ELPG\n");
> +
> +	rc = tegra_xhci_check_ports_for_u3(tegra);
> +	if (rc < 0)
> +		goto out;
> +
> +	rc = xhci_suspend(xhci, do_wakeup);
> +
> +	if (rc) {
> +		dev_warn(dev, "xhci_suspend() failed %d\n", rc);
> +		goto out;
> +	}
> +
> +	tegra_xhci_save_context(tegra);
> +
> +	if (do_wakeup)
> +		tegra_xhci_enable_phy_sleepwalk_wake(tegra);
> +
> +	tegra_xusb_powergate_partitions(tegra);
> +
> +	for (i = 0; i < tegra->num_phys; i++) {
> +		if (!tegra->phys[i])
> +			continue;
> +
> +		phy_power_off(tegra->phys[i]);
> +		if (!do_wakeup)
> +			phy_exit(tegra->phys[i]);
> +	}
> +
> +	tegra_xusb_clk_disable(tegra);
> +out:
> +	if (!rc)
> +		dev_info(tegra->dev, "entering ELPG done\n");
> +	else {
> +		u32 usbcmd;
> +
> +		usbcmd = readl(&xhci->op_regs->command);
> +		usbcmd |= CMD_EIE;
> +		writel(usbcmd, &xhci->op_regs->command);
> +
> +		dev_info(tegra->dev, "entering ELPG failed\n");
> +		pm_runtime_mark_last_busy(tegra->dev);
> +	}
> +
> +	return rc;
> +}
> +
> +/* caller must hold tegra->lock */
> +static int tegra_xhci_exit_elpg(struct tegra_xusb *tegra, bool runtime)
> +{
> +	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
> +	struct device *dev = tegra->dev;
> +	struct tegra_xusb_mbox_msg msg;
> +	bool do_wakeup = runtime ? true : device_may_wakeup(dev);
> +	unsigned int i;
> +	int rc;
> +	u32 usbcmd;
> +
> +	dev_info(dev, "exiting ELPG\n");
> +	pm_runtime_mark_last_busy(tegra->dev);
> +
> +	rc = tegra_xusb_clk_enable(tegra);
> +	if (rc) {
> +		dev_warn(dev, "failed to enable xhci clocks %d\n", rc);
> +		goto out;
> +	}
> +
> +	rc = tegra_xusb_unpowergate_partitions(tegra);
> +	if (rc)
> +		goto disable_clks;
> +
> +	if (do_wakeup)
> +		tegra_xhci_disable_phy_wake(tegra);
> +
> +	for (i = 0; i < tegra->num_phys; i++) {
> +		if (!tegra->phys[i])
> +			continue;
> +
> +		if (!do_wakeup)
> +			phy_init(tegra->phys[i]);
> +
> +		phy_power_on(tegra->phys[i]);
> +	}
> +
> +	tegra_xusb_config(tegra);
> +	tegra_xhci_restore_context(tegra);
> +
> +	rc = tegra_xusb_load_firmware(tegra);
> +	if (rc < 0)
> +		goto disable_phy;
> +
> +	msg.cmd = MBOX_CMD_MSG_ENABLED;
> +	msg.data = 0;
> +
> +	rc = tegra_xusb_mbox_send(tegra, &msg);
> +	if (rc < 0) {
> +		dev_err(dev, "failed to enable messages: %d\n", rc);
> +		goto disable_phy;
> +	}
> +
> +	if (do_wakeup)
> +		tegra_xhci_disable_phy_sleepwalk(tegra);
> +
> +	rc = xhci_resume(xhci, 0);
> +
> +	usbcmd = readl(&xhci->op_regs->command);
> +	usbcmd |= CMD_EIE;
> +	writel(usbcmd, &xhci->op_regs->command);
> +
> +	goto out;
> +
> +disable_phy:
> +	for (i = 0; i < tegra->num_phys; i++) {
> +		if (!tegra->phys[i])
> +			continue;
> +
> +		phy_power_off(tegra->phys[i]);
> +		if (!do_wakeup)
> +			phy_exit(tegra->phys[i]);
> +	}
> +	tegra_xusb_powergate_partitions(tegra);
> +disable_clks:
> +	tegra_xusb_clk_disable(tegra);
> +out:
> +	if (!rc)
> +		dev_info(dev, "exiting ELPG done\n");
> +	else
> +		dev_info(dev, "exiting ELPG failed\n");
> +
> +	return rc;
> +}
> +
>  #ifdef CONFIG_PM_SLEEP
>  static int tegra_xusb_suspend(struct device *dev)
>  {
>  	struct tegra_xusb *tegra = dev_get_drvdata(dev);
> -	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
> -	bool wakeup = device_may_wakeup(dev);
> +	int ret;
> +
> +	synchronize_irq(tegra->mbox_irq);
> +
> +	mutex_lock(&tegra->lock);
> +
> +	if (pm_runtime_suspended(dev)) {
> +		ret = tegra_xhci_exit_elpg(tegra, true);
> +		if (ret < 0)
> +			goto out;
> +	}
> +
> +	ret = tegra_xhci_enter_elpg(tegra, false);
> +	if (ret < 0) {
> +		if (pm_runtime_suspended(dev)) {
> +			pm_runtime_disable(dev);
> +			pm_runtime_set_active(dev);
> +			pm_runtime_enable(dev);
> +		}
>  
> -	/* TODO: Powergate controller across suspend/resume. */
> -	return xhci_suspend(xhci, wakeup);
> +		goto out;
> +	}
> +
> +out:
> +	if (!ret) {
> +		tegra->suspended = true;
> +		pm_runtime_disable(dev);
> +	}
> +
> +	mutex_unlock(&tegra->lock);
> +
> +	return ret;
>  }
>  
>  static int tegra_xusb_resume(struct device *dev)
>  {
>  	struct tegra_xusb *tegra = dev_get_drvdata(dev);
> -	struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd);
> +	int ret = 0;
> +
> +	mutex_lock(&tegra->lock);
> +
> +	if (!tegra->suspended) {
> +		mutex_unlock(&tegra->lock);
> +		return 0;
> +	}
> +
> +	ret = tegra_xhci_exit_elpg(tegra, true);
> +	if (ret < 0) {
> +		mutex_unlock(&tegra->lock);
> +		return ret;
> +	}
> +
> +	tegra->suspended = false;
> +	mutex_unlock(&tegra->lock);
>  
> -	return xhci_resume(xhci, 0);
> +	pm_runtime_set_active(dev);
> +	pm_runtime_enable(dev);
> +
> +	return 0;
>  }
>  #endif
>  
> 

-- 
nvpublic

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2019-07-04 14:06 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-06-14  7:48 [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk JC Kuo
2019-06-14  7:48 ` [PATCH 6/8] phy: tegra: xusb: t210: support wake and sleepwalk JC Kuo
2019-07-04 13:53   ` Jon Hunter
2019-06-14  7:48 ` [PATCH 7/8] arm64: tegra: add Tegra210 XUSB PADCTL irq JC Kuo
2019-06-14  7:48 ` [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM JC Kuo
2019-06-18  6:33   ` Greg KH
2019-06-18  8:01     ` JC Kuo
2019-07-04 14:05   ` Jon Hunter
2019-07-04 13:42 ` [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk Jon Hunter

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).