usb: chipidea: host: add .bus_suspend quirk
authorPeter Chen <peter.chen@freescale.com>
Wed, 11 Feb 2015 04:44:59 +0000 (12:44 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 18 Mar 2015 15:19:11 +0000 (16:19 +0100)
For chipidea, its resume sequence is not-EHCI compatible, see
below description for FPR at portsc. So in order to send SoF in
time for remote wakeup sequence(within 3ms), the RUN/STOP bit must
be set before the resume signal is ended, but the usb resume
code may run after resume signal is ended, so we had to set it
at suspend path.

Force Port Resume - RW. Default = 0b.
1= Resume detected/driven on port.
0=No resume (K-state) detected/driven on port.
Host mode:
Software sets this bit to one to drive resume signaling. The Controller sets this bit to '1' if
a J-to-K transition is detected while the port is in the Suspend state. When this bit
transitions to a '1' because a J-to-K transition is detected, the Port Change Detect bit in
the USBSTS register is also set to '1'. This bit will automatically change to '0' after the
resume sequence is complete. This behavior is different from EHCI where the controller
driver is required to set this bit to a '0' after the resume duration is timed in the driver.
Note that when the controller owns the port, the resume sequence follows the defined

sequence documented in the USB Specification Revision 2.0. The resume signaling
(Full-speed 'K') is driven on the port as long as this bit remains a '1'. This bit will remain
a '1' until the port has switched to idle. Writing a '0' has no affect because the port
controller will time the resume operation, clear the bit and the port control state switches
to HS or FS idle.
This field is '0' if Port Power(PP) is '0' in host mode.

This bit is not-EHCI compatible.

Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/chipidea/host.c

index 12edbd0c998aa2c0aa424835f528ba27771984fe..feb9f073522798a194d3207770cf6a82191da205 100644 (file)
@@ -33,6 +33,7 @@
 #include "host.h"
 
 static struct hc_driver __read_mostly ci_ehci_hc_driver;
+static int (*orig_bus_suspend)(struct usb_hcd *hcd);
 
 struct ehci_ci_priv {
        struct regulator *reg_vbus;
@@ -161,6 +162,47 @@ void ci_hdrc_host_destroy(struct ci_hdrc *ci)
                host_stop(ci);
 }
 
+static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
+{
+       struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+       int port;
+       u32 tmp;
+
+       int ret = orig_bus_suspend(hcd);
+
+       if (ret)
+               return ret;
+
+       port = HCS_N_PORTS(ehci->hcs_params);
+       while (port--) {
+               u32 __iomem *reg = &ehci->regs->port_status[port];
+               u32 portsc = ehci_readl(ehci, reg);
+
+               if (portsc & PORT_CONNECT) {
+                       /*
+                        * For chipidea, the resume signal will be ended
+                        * automatically, so for remote wakeup case, the
+                        * usbcmd.rs may not be set before the resume has
+                        * ended if other resume paths consumes too much
+                        * time (~24ms), in that case, the SOF will not
+                        * send out within 3ms after resume ends, then the
+                        * high speed device will enter full speed mode.
+                        */
+
+                       tmp = ehci_readl(ehci, &ehci->regs->command);
+                       tmp |= CMD_RUN;
+                       ehci_writel(ehci, tmp, &ehci->regs->command);
+                       /*
+                        * It needs a short delay between set RS bit and PHCD.
+                        */
+                       usleep_range(150, 200);
+                       break;
+               }
+       }
+
+       return 0;
+}
+
 int ci_hdrc_host_init(struct ci_hdrc *ci)
 {
        struct ci_role_driver *rdrv;
@@ -179,6 +221,8 @@ int ci_hdrc_host_init(struct ci_hdrc *ci)
        ci->roles[CI_ROLE_HOST] = rdrv;
 
        ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides);
+       orig_bus_suspend = ci_ehci_hc_driver.bus_suspend;
+       ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend;
 
        return 0;
 }