* [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).