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

Tested-by: Matt Merhar <mattmer...@protonmail.com>
Tested-by: Nicolas Chauvet <kwiz...@gmail.com>
Tested-by: Peter Geis <pgwipe...@gmail.com>
Tested-by: Ion Agorria <i...@agorria.com>
Acked-by: Thierry Reding <tred...@nvidia.com>
Signed-off-by: Dmitry Osipenko <dig...@gmail.com>
---
 drivers/usb/phy/phy-tegra-usb.c   | 100 ++++++++++++++++++++++++++----
 include/linux/usb/tegra_usb_phy.h |   2 +
 2 files changed, 91 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c
index 1296524e1bee..a48452a6172b 100644
--- a/drivers/usb/phy/phy-tegra-usb.c
+++ b/drivers/usb/phy/phy-tegra-usb.c
@@ -45,6 +45,7 @@
 #define TEGRA_PORTSC1_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC)
 
 #define USB_SUSP_CTRL                          0x400
+#define   USB_WAKE_ON_RESUME_EN                        BIT(2)
 #define   USB_WAKE_ON_CNNT_EN_DEV              BIT(3)
 #define   USB_WAKE_ON_DISCON_EN_DEV            BIT(4)
 #define   USB_SUSP_CLR                         BIT(5)
@@ -56,6 +57,15 @@
 #define   USB_SUSP_SET                         BIT(14)
 #define   USB_WAKEUP_DEBOUNCE_COUNT(x)         (((x) & 0x7) << 16)
 
+#define USB_PHY_VBUS_SENSORS                   0x404
+#define   B_SESS_VLD_WAKEUP_EN                 BIT(6)
+#define   B_VBUS_VLD_WAKEUP_EN                 BIT(14)
+#define   A_SESS_VLD_WAKEUP_EN                 BIT(22)
+#define   A_VBUS_VLD_WAKEUP_EN                 BIT(30)
+
+#define USB_PHY_VBUS_WAKEUP_ID                 0x408
+#define   VBUS_WAKEUP_WAKEUP_EN                        BIT(30)
+
 #define USB1_LEGACY_CTRL                       0x410
 #define   USB1_NO_LEGACY_MODE                  BIT(0)
 #define   USB1_VBUS_SENSE_CTL_MASK             (3 << 1)
@@ -334,6 +344,11 @@ static int utmip_pad_power_on(struct tegra_usb_phy *phy)
                writel_relaxed(val, base + UTMIP_BIAS_CFG0);
        }
 
+       if (phy->pad_wakeup) {
+               phy->pad_wakeup = false;
+               utmip_pad_count--;
+       }
+
        spin_unlock(&utmip_pad_lock);
 
        clk_disable_unprepare(phy->pad_clk);
@@ -359,6 +374,17 @@ static int utmip_pad_power_off(struct tegra_usb_phy *phy)
                goto ulock;
        }
 
+       /*
+        * In accordance to TRM, OTG and Bias pad circuits could be turned off
+        * to save power if wake is enabled, but the VBUS-change detection
+        * method is board-specific and these circuits may need to be enabled
+        * to generate wakeup event, hence we will just keep them both enabled.
+        */
+       if (phy->wakeup_enabled) {
+               phy->pad_wakeup = true;
+               utmip_pad_count++;
+       }
+
        if (--utmip_pad_count == 0) {
                val = readl_relaxed(base + UTMIP_BIAS_CFG0);
                val |= UTMIP_OTGPD | UTMIP_BIASPD;
@@ -503,11 +529,24 @@ static int utmi_phy_power_on(struct tegra_usb_phy *phy)
                writel_relaxed(val, base + UTMIP_PLL_CFG1);
        }
 
+       val = readl_relaxed(base + USB_SUSP_CTRL);
+       val &= ~USB_WAKE_ON_RESUME_EN;
+       writel_relaxed(val, base + USB_SUSP_CTRL);
+
        if (phy->mode == USB_DR_MODE_PERIPHERAL) {
                val = readl_relaxed(base + USB_SUSP_CTRL);
                val &= ~(USB_WAKE_ON_CNNT_EN_DEV | USB_WAKE_ON_DISCON_EN_DEV);
                writel_relaxed(val, base + USB_SUSP_CTRL);
 
+               val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID);
+               val &= ~VBUS_WAKEUP_WAKEUP_EN;
+               writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID);
+
+               val = readl_relaxed(base + USB_PHY_VBUS_SENSORS);
+               val &= ~(A_VBUS_VLD_WAKEUP_EN | A_SESS_VLD_WAKEUP_EN);
+               val &= ~(B_VBUS_VLD_WAKEUP_EN | B_SESS_VLD_WAKEUP_EN);
+               writel_relaxed(val, base + USB_PHY_VBUS_SENSORS);
+
                val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0);
                val &= ~UTMIP_PD_CHRG;
                writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0);
@@ -605,31 +644,51 @@ static int utmi_phy_power_off(struct tegra_usb_phy *phy)
 
        utmi_phy_clk_disable(phy);
 
-       if (phy->mode == USB_DR_MODE_PERIPHERAL) {
+       /* PHY won't resume if reset is asserted */
+       if (!phy->wakeup_enabled) {
                val = readl_relaxed(base + USB_SUSP_CTRL);
-               val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0);
-               val |= USB_WAKE_ON_CNNT_EN_DEV | USB_WAKEUP_DEBOUNCE_COUNT(5);
+               val |= UTMIP_RESET;
                writel_relaxed(val, base + USB_SUSP_CTRL);
        }
 
-       val = readl_relaxed(base + USB_SUSP_CTRL);
-       val |= UTMIP_RESET;
-       writel_relaxed(val, base + USB_SUSP_CTRL);
-
        val = readl_relaxed(base + UTMIP_BAT_CHRG_CFG0);
        val |= UTMIP_PD_CHRG;
        writel_relaxed(val, base + UTMIP_BAT_CHRG_CFG0);
 
-       val = readl_relaxed(base + UTMIP_XCVR_CFG0);
-       val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN |
-              UTMIP_FORCE_PDZI_POWERDOWN;
-       writel_relaxed(val, base + UTMIP_XCVR_CFG0);
+       if (!phy->wakeup_enabled) {
+               val = readl_relaxed(base + UTMIP_XCVR_CFG0);
+               val |= UTMIP_FORCE_PD_POWERDOWN | UTMIP_FORCE_PD2_POWERDOWN |
+                      UTMIP_FORCE_PDZI_POWERDOWN;
+               writel_relaxed(val, base + UTMIP_XCVR_CFG0);
+       }
 
        val = readl_relaxed(base + UTMIP_XCVR_CFG1);
        val |= UTMIP_FORCE_PDDISC_POWERDOWN | UTMIP_FORCE_PDCHRP_POWERDOWN |
               UTMIP_FORCE_PDDR_POWERDOWN;
        writel_relaxed(val, base + UTMIP_XCVR_CFG1);
 
+       if (phy->wakeup_enabled) {
+               val = readl_relaxed(base + USB_SUSP_CTRL);
+               val &= ~USB_WAKEUP_DEBOUNCE_COUNT(~0);
+               val |= USB_WAKEUP_DEBOUNCE_COUNT(5);
+               val |= USB_WAKE_ON_RESUME_EN;
+               writel_relaxed(val, base + USB_SUSP_CTRL);
+
+               /*
+                * Ask VBUS sensor to generate wake event once cable is
+                * connected.
+                */
+               if (phy->mode == USB_DR_MODE_PERIPHERAL) {
+                       val = readl_relaxed(base + USB_PHY_VBUS_WAKEUP_ID);
+                       val |= VBUS_WAKEUP_WAKEUP_EN;
+                       writel_relaxed(val, base + USB_PHY_VBUS_WAKEUP_ID);
+
+                       val = readl_relaxed(base + USB_PHY_VBUS_SENSORS);
+                       val |= A_VBUS_VLD_WAKEUP_EN;
+                       writel_relaxed(val, base + USB_PHY_VBUS_SENSORS);
+               }
+       }
+
        return utmip_pad_power_off(phy);
 }
 
@@ -765,6 +824,15 @@ static int ulpi_phy_power_off(struct tegra_usb_phy *phy)
        usleep_range(5000, 6000);
        clk_disable_unprepare(phy->clk);
 
+       /*
+        * Wakeup currently unimplemented for ULPI, thus PHY needs to be
+        * force-resumed.
+        */
+       if (WARN_ON_ONCE(phy->wakeup_enabled)) {
+               ulpi_phy_power_on(phy);
+               return -EOPNOTSUPP;
+       }
+
        return 0;
 }
 
@@ -827,6 +895,15 @@ static void tegra_usb_phy_shutdown(struct usb_phy *u_phy)
        phy->freq = NULL;
 }
 
+static int tegra_usb_phy_set_wakeup(struct usb_phy *u_phy, bool enable)
+{
+       struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
+
+       phy->wakeup_enabled = enable;
+
+       return 0;
+}
+
 static int tegra_usb_phy_set_suspend(struct usb_phy *u_phy, int suspend)
 {
        struct tegra_usb_phy *phy = to_tegra_usb_phy(u_phy);
@@ -1198,6 +1275,7 @@ static int tegra_usb_phy_probe(struct platform_device 
*pdev)
        tegra_phy->u_phy.dev = &pdev->dev;
        tegra_phy->u_phy.init = tegra_usb_phy_init;
        tegra_phy->u_phy.shutdown = tegra_usb_phy_shutdown;
+       tegra_phy->u_phy.set_wakeup = tegra_usb_phy_set_wakeup;
        tegra_phy->u_phy.set_suspend = tegra_usb_phy_set_suspend;
 
        platform_set_drvdata(pdev, tegra_phy);
diff --git a/include/linux/usb/tegra_usb_phy.h 
b/include/linux/usb/tegra_usb_phy.h
index c29d1b4c9381..fd1c9f6a4e37 100644
--- a/include/linux/usb/tegra_usb_phy.h
+++ b/include/linux/usb/tegra_usb_phy.h
@@ -79,6 +79,8 @@ struct tegra_usb_phy {
        bool is_ulpi_phy;
        struct gpio_desc *reset_gpio;
        struct reset_control *pad_rst;
+       bool wakeup_enabled;
+       bool pad_wakeup;
        bool powered_on;
 };
 
-- 
2.29.2

Reply via email to