From 78f0357ec8baa1ffe9c14b81f88cb0f3a5e1c15a Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 11 Feb 2015 12:44:59 +0800 Subject: [PATCH] usb: chipidea: host: add .bus_suspend quirk 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 Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/host.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 12edbd0..feb9f07 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -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; } -- 2.7.4