]> rtime.felk.cvut.cz Git - vajnamar/linux-xlnx.git/commitdiff
dwc3: Add code for supporting entering into D3 state during suspend
authorAnurag Kumar Vulisha <anurag.kumar.vulisha@xilinx.com>
Wed, 27 Sep 2017 14:05:06 +0000 (19:35 +0530)
committerMichal Simek <michal.simek@xilinx.com>
Wed, 4 Oct 2017 11:59:57 +0000 (13:59 +0200)
This patch adds support for making the core enter D3 state during
suspend. D3 state is only entered for when wakeup capability is
enabled.

Signed-off-by: Anurag Kumar Vulisha <anuragku@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/dwc3-of-simple.c

index bc5bdae1266102c41f99ba247631f75626b0a281..a87da32073a13f5ed39964577c0a2eb58a59acda 100644 (file)
@@ -627,8 +627,6 @@ static int dwc3_phy_setup(struct dwc3 *dwc)
 
 static void dwc3_core_exit(struct dwc3 *dwc)
 {
-       dwc3_event_buffers_cleanup(dwc);
-
        usb_phy_shutdown(dwc->usb2_phy);
        usb_phy_shutdown(dwc->usb3_phy);
        phy_exit(dwc->usb2_generic_phy);
@@ -1275,6 +1273,7 @@ static int dwc3_remove(struct platform_device *pdev)
        dwc3_debugfs_exit(dwc);
        dwc3_core_exit_mode(dwc);
 
+       dwc3_event_buffers_cleanup(dwc);
        dwc3_core_exit(dwc);
        dwc3_ulpi_exit(dwc);
 
@@ -1306,6 +1305,11 @@ static int dwc3_suspend_common(struct dwc3 *dwc)
                break;
        }
 
+       dwc3_event_buffers_cleanup(dwc);
+
+       /* Put the core into D3 state */
+       dwc3_set_usb_core_power(dwc, false);
+
        dwc3_core_exit(dwc);
 
        return 0;
@@ -1316,6 +1320,9 @@ static int dwc3_resume_common(struct dwc3 *dwc)
        unsigned long   flags;
        int             ret;
 
+       /* Bring core to D0 state */
+       dwc3_set_usb_core_power(dwc, true);
+
        ret = dwc3_core_init(dwc);
        if (ret)
                return ret;
index b17c0630e0827275f275ae4faa154035bac7f4b5..cc2854c5be950c4949f151ada3fb9c2d5de5ac6a 100644 (file)
@@ -1176,6 +1176,7 @@ void dwc3_set_phydata(struct device *dev, struct phy *phy);
 void dwc3_simple_wakeup_capable(struct device *dev, bool wakeup);
 void dwc3_set_simple_data(struct dwc3 *dwc);
 void dwc3_simple_check_quirks(struct dwc3 *dwc);
+int dwc3_set_usb_core_power(struct dwc3 *dwc, bool on);
 #else
 static inline int dwc3_enable_hw_coherency(struct device *dev)
 { return 1; }
@@ -1187,6 +1188,8 @@ void dwc3_set_simple_data(struct dwc3 *dwc)
 { ; }
 void dwc3_simple_check_quirks(struct dwc3 *dwc)
 { ; }
+int dwc3_set_usb_core_power(struct dwc3 *dwc, bool on)
+{ ; }
 #endif
 
 #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)\
index 4233923a2a7793e1814a654f1ee1cc8e48ff8705..e5bf8c939fd11f06556ab0a134298c7bdbdd570c 100644 (file)
 #define ULPI_OTG_CTRL_CLEAR            0XC
 #define OTG_CTRL_DRVVBUS_OFFSET                5
 
+#define XLNX_USB_CUR_PWR_STATE          0x0000
+#define XLNX_CUR_PWR_STATE_D0           0x00
+#define XLNX_CUR_PWR_STATE_D3           0x0F
+#define XLNX_CUR_PWR_STATE_BITMASK      0x0F
+
+#define XLNX_USB_PME_ENABLE             0x0034
+#define XLNX_PME_ENABLE_SIG_GEN         0x01
+
+#define XLNX_USB_REQ_PWR_STATE          0x003c
+#define XLNX_REQ_PWR_STATE_D0           0x00
+#define XLNX_REQ_PWR_STATE_D3           0x03
+
+/* Number of retries for USB operations */
+#define DWC3_PWR_STATE_RETRIES          1000
+#define DWC3_PWR_TIMEOUT               100
+
 #define DWC3_OF_ADDRESS(ADDR)          ((ADDR) - DWC3_GLOBALS_REGS_START)
 
 struct dwc3_of_simple {
@@ -56,6 +72,8 @@ struct dwc3_of_simple {
        struct dwc3             *dwc;
        bool                    wakeup_capable;
        bool                    dis_u3_susphy_quirk;
+       bool                    enable_d3_suspend;
+       char                    soc_rev;
 };
 
 void dwc3_set_phydata(struct device *dev, struct phy *phy)
@@ -159,6 +177,9 @@ void dwc3_simple_wakeup_capable(struct device *dev, bool wakeup)
 
                /* Set wakeup capable as true or false */
                simple->wakeup_capable = wakeup;
+
+               /* Allow D3 state if wakeup capable only */
+               simple->enable_d3_suspend = wakeup;
        }
 }
 EXPORT_SYMBOL(dwc3_simple_wakeup_capable);
@@ -256,6 +277,9 @@ static int dwc3_of_simple_probe(struct platform_device *pdev)
                        simple->dis_u3_susphy_quirk = true;
                }
 
+               /* Update soc_rev to simple for future use */
+               simple->soc_rev = *soc_rev;
+
                /* Clean soc_rev if got a valid pointer from nvmem driver
                 * else we may end up in kernel panic
                 */
@@ -326,6 +350,77 @@ static void dwc3_simple_vbus(struct dwc3 *dwc, bool vbus_off)
        writel(reg, dwc->regs + addr);
 }
 
+int dwc3_set_usb_core_power(struct dwc3 *dwc, bool on)
+{
+       u32 reg, retries;
+       void __iomem *reg_base;
+       struct platform_device *pdev_parent;
+       struct dwc3_of_simple *simple;
+       struct device_node *node = of_get_parent(dwc->dev->of_node);
+
+       /* this is for Xilinx devices only */
+       if (!of_device_is_compatible(node, "xlnx,zynqmp-dwc3"))
+               return 0;
+
+       pdev_parent = of_find_device_by_node(node);
+       simple = platform_get_drvdata(pdev_parent);
+       reg_base = simple->regs;
+
+       /* Check if entering into D3 state is allowed during suspend */
+       if ((simple->soc_rev < ZYNQMP_SILICON_V4) || !simple->enable_d3_suspend)
+               return 0;
+
+       if (on) {
+               dev_dbg(dwc->dev, "trying to set power state to D0....\n");
+
+               /* change power state to D0 */
+               writel(XLNX_REQ_PWR_STATE_D0,
+                      reg_base + XLNX_USB_REQ_PWR_STATE);
+
+               /* wait till current state is changed to D0 */
+               retries = DWC3_PWR_STATE_RETRIES;
+               do {
+                       reg = readl(reg_base + XLNX_USB_CUR_PWR_STATE);
+                       if ((reg & XLNX_CUR_PWR_STATE_BITMASK) ==
+                            XLNX_CUR_PWR_STATE_D0)
+                               break;
+
+                       usleep_range(DWC3_PWR_TIMEOUT, DWC3_PWR_TIMEOUT * 2);
+               } while (--retries);
+
+               if (!retries) {
+                       dev_err(dwc->dev, "Failed to set power state to D0\n");
+                       return -EIO;
+               }
+       } else {
+               dev_dbg(dwc->dev, "Trying to set power state to D3...\n");
+
+               /* enable PME to wakeup from hibernation */
+               writel(XLNX_PME_ENABLE_SIG_GEN, reg_base + XLNX_USB_PME_ENABLE);
+
+               /* change power state to D3 */
+               writel(XLNX_REQ_PWR_STATE_D3,
+                      reg_base + XLNX_USB_REQ_PWR_STATE);
+
+               /* wait till current state is changed to D3 */
+               retries = DWC3_PWR_STATE_RETRIES;
+               do {
+                       reg = readl(reg_base + XLNX_USB_CUR_PWR_STATE);
+                       if ((reg & XLNX_CUR_PWR_STATE_BITMASK) ==
+                                       XLNX_CUR_PWR_STATE_D3)
+                               break;
+
+                       usleep_range(DWC3_PWR_TIMEOUT, DWC3_PWR_TIMEOUT * 2);
+               } while (--retries);
+
+               if (!retries)
+                       dev_err(dwc->dev, "Failed to set power state to D3\n");
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(dwc3_set_usb_core_power);
+
 static int dwc3_of_simple_suspend(struct device *dev)
 {
        struct dwc3_of_simple   *simple = dev_get_drvdata(dev);