]> rtime.felk.cvut.cz Git - vajnamar/linux-xlnx.git/commitdiff
dwc3: Add support for removing vbus when suspended
authorAnurag Kumar Vulisha <anurag.kumar.vulisha@xilinx.com>
Thu, 27 Jul 2017 06:55:56 +0000 (12:25 +0530)
committerMichal Simek <michal.simek@xilinx.com>
Thu, 27 Jul 2017 08:59:23 +0000 (10:59 +0200)
During suspend, dwc3 host puts the device into U3 state and disables the
clocks. During resume, dwc3 host drives LFPS.polling to detect the
connected usb device and most of the usb devices detect LFPS.polling signal
and drives LFPS.U3 exit signal and gets detected by the dwc3 host. But very
few usb devices during resume doesn't drive u3 exit signalling even after
seeing LFPS.polling signal on the link and wait for the host to initiate an
u3 exit(Currently the USB stack doesn't drive U3 exit during resume from
suspened). These kind of devices doesn't even respond to warm reset
signalling sent by the host. Since these usb devices doesn't respond to
polling or warm reset, host controller doesn't generate USB detect event
after resume from suspend until manual disconnect.

To aviod the above said issue, during suspend we need to ask ULPI phy to
stop driving of VBUS and restore VBUS during resume. Doing so, will make
usb device to enter into RX Detect state and start LFPS.polling after
restoring LFPS.polling signal and thus generating detect event on the host
side.

This patch does the above said with a check to ensure that VBUS is not
disabled for the usb devices that are remote wakeup capable.

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 8e90699ac97918b3814203f56cad6985a001d7c0..79fef4714f09b1c4425c56cdeafd807a51a3daef 100644 (file)
@@ -486,6 +486,11 @@ static int dwc3_config_soc_bus(struct dwc3 *dwc)
                        return ret;
        }
 
+       /* Send struct dwc3 to dwc3-of-simple for configuring VBUS
+        * during suspend/resume
+        */
+       dwc3_set_simple_data(dwc);
+
        return 0;
 }
 
index 79d51bb44d42be950cc8f63d5a8c38a0afcb1980..bf7fec718a7746e7c08ea972571f478a0cc7127e 100644 (file)
@@ -1170,6 +1170,7 @@ static inline bool dwc3_is_usb31(struct dwc3 *dwc)
 int dwc3_enable_hw_coherency(struct device *dev);
 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);
 #else
 static inline int dwc3_enable_hw_coherency(struct device *dev)
 { return 1; }
@@ -1177,6 +1178,8 @@ static inline 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)
+{ ; }
 #endif
 
 #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)\
index cd5944645f19c37aada226223da50e18126a8f92..16d36a218f39e388f2dfb06050ab2a82ee0fdf44 100644 (file)
 #include <linux/of_address.h>
 
 #include "core.h"
+#include "io.h"
 
 /* Xilinx USB 3.0 IP Register */
 #define XLNX_USB_COHERENCY             0x005C
 #define XLNX_USB_COHERENCY_ENABLE      0x1
 
+/* ULPI control registers */
+#define ULPI_OTG_CTRL_SET              0xB
+#define ULPI_OTG_CTRL_CLEAR            0XC
+#define OTG_CTRL_DRVVBUS_OFFSET                5
+
 struct dwc3_of_simple {
        struct device           *dev;
        struct clk              **clks;
        int                     num_clocks;
        void __iomem            *regs;
+       struct dwc3             *dwc;
        bool                    wakeup_capable;
 };
 
@@ -93,6 +100,23 @@ int dwc3_enable_hw_coherency(struct device *dev)
        return 0;
 }
 
+void dwc3_set_simple_data(struct dwc3 *dwc)
+{
+       struct device_node *node =
+               of_find_compatible_node(NULL, NULL, "xlnx,zynqmp-dwc3");
+
+       if (node) {
+               struct platform_device *pdev_parent;
+               struct dwc3_of_simple   *simple;
+
+               pdev_parent = of_find_device_by_node(node);
+               simple = platform_get_drvdata(pdev_parent);
+
+               /* Set (struct dwc3 *) to simple->dwc for future use */
+               simple->dwc =  dwc;
+       }
+}
+
 void dwc3_simple_wakeup_capable(struct device *dev, bool wakeup)
 {
        struct device_node *node =
@@ -269,12 +293,34 @@ static int dwc3_of_simple_remove(struct platform_device *pdev)
 }
 
 #ifdef CONFIG_PM
+
+static void dwc3_simple_vbus(struct dwc3 *dwc, bool vbus_off)
+{
+       u32 reg, addr;
+       u8  val;
+
+       if (vbus_off)
+               addr = ULPI_OTG_CTRL_CLEAR;
+       else
+               addr = ULPI_OTG_CTRL_SET;
+
+       val = (1 << OTG_CTRL_DRVVBUS_OFFSET);
+
+       reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_GUSB2PHYACC_ADDR(addr);
+       reg |= DWC3_GUSB2PHYACC_WRITE | val;
+       dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg);
+}
+
 static int dwc3_of_simple_suspend(struct device *dev)
 {
        struct dwc3_of_simple   *simple = dev_get_drvdata(dev);
        int                     i;
 
        if (!simple->wakeup_capable) {
+               /* Ask ULPI to turn OFF Vbus */
+               dwc3_simple_vbus(simple->dwc, true);
+
+               /* Disable the clocks */
                for (i = 0; i < simple->num_clocks; i++)
                        clk_disable(simple->clks[i]);
        }
@@ -298,6 +344,9 @@ static int dwc3_of_simple_resume(struct device *dev)
                                clk_disable(simple->clks[i]);
                        return ret;
                }
+
+               /* Ask ULPI to turn ON Vbus */
+               dwc3_simple_vbus(simple->dwc, false);
        }
 
        return 0;