From 8284064c8291b6092de253b28d5182d485fc1d8b Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Sun, 29 Sep 2013 11:07:23 +0800 Subject: [PATCH] ENGR00286426-20 usb: chipidea: host: add quirk for ehci operation For chipidea controller, it does not follow ehci spec strictly. Taking resume signal as an example, it will stop resume signal about 20-21ms later automatically, but standard ehci spec says, the resume signal is controlled by software (clear portsc.PORT_RESUME). This operation causes some remote wakeup problems for high speed devices due to host controller does not send SOF in time since software can't guarantee set run/stop bit in time (run/stop bit was cleared at the ehci suspend routine). When software sets run/stop bit, it needs 1 SoF time to make it effect. If we close the PHY clock just after setting run/stop bit, it does not be set in practice, so a software delay is needed. Signed-off-by: Peter Chen --- drivers/usb/chipidea/host.c | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 177ad484bb9a..b5e455b86a31 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -33,6 +33,53 @@ #include "host.h" static struct hc_driver __read_mostly ci_ehci_hc_driver; +static int (*orig_bus_suspend)(struct usb_hcd *hcd); + +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 path consumes too much + * time (~23ms-24ms), in that case, the SOF will not + * send out within 3ms after resume ends, then the + * device will enter suspend again. + */ + if (hcd->self.root_hub->do_remote_wakeup) { + ehci_dbg(ehci, + "Remote wakeup is enabled, " + "and device is on the port\n"); + + tmp = ehci_readl(ehci, &ehci->regs->command); + tmp |= CMD_RUN; + ehci_writel(ehci, tmp, &ehci->regs->command); + /* + * It needs a short delay between set RUNSTOP + * and set PHCD. + */ + udelay(125); + } + } + } + + return 0; +} static irqreturn_t host_irq(struct ci_hdrc *ci) { @@ -146,5 +193,9 @@ int ci_hdrc_host_init(struct ci_hdrc *ci) ehci_init_driver(&ci_ehci_hc_driver, NULL); + orig_bus_suspend = ci_ehci_hc_driver.bus_suspend; + + ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend; + return 0; } -- 2.39.2