summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnand Gadiyar <gadiyar@ti.com>2011-03-09 11:51:41 -0600
committerSebastien Jan <s-jan@ti.com>2011-07-28 11:22:42 +0200
commitc75826b3749994785071b9e488a296042f31488a (patch)
tree134fbe227dc8358188fa0bf523ecfadfc0268cf5
parent6a61915f545760f521ad959027eed317f2aaf497 (diff)
OMAP4: USB Host: Re-implement EHCI Hub Control to workaround errata
The OMAP4 EHCI controller has the following defect (errata ID i640): Title: USB HOST EHCI In TLL Mode Will See The Port Being Disabled Upon Resume Or Remote Wakeup. Description: This bug impacts the TLL module when used in conjunction with the EHCI part of the USB host module. Upon resume or remote wakeup from a bus suspended state, the device will switch from full speed to high speed before the host does.This will incorrectly generate a disconnect IRQ on the host side (USBSTS[2]:PCD=1), and the hardware will disable the port (PORTSC[2]:PED=0). The peripheral will then go back to suspend because there is no more activity on the bus. PHY mode (non-TLL) is not impacted. OHCI is not impacted. WORKAROUND: The workaround consists of stalling the device for some time so that the host can switch to HS before the device. The programming guide to follow at the end of the resume sequence is: 1. The host ends the resume sequence by clearing PORTSC.FPR bit. 2. The software waits for the TLL to report the LineState change from K to SE0 on ULPI. PORTSC.LS will switch from 0x1(K) to 0x0(SE0). 3. Gate the TLL ULPI clock and thereby the device controller by PRCM control: the whole TLL is frozen, including the ULPI clock - Clear the appropriate OPTFCLKEN_USB_CHx_CLK bit in the CM_L3INIT_HSUSBTLL_CLKCTRL register 4. Wait 3 us for the host to go to HS. 5. Restart the TLL ULPI clock and the device controller by PRCM control, to unfreeze the entire TLL, including the ULPI clock - Set back the appropriate OPTFCLKEN_USB_CHx_CLK bit in the CM_L3INIT_HSUSBTLL_CLKCTRL register 6. The USB device switches to HS. Add a wrapper function to default ehci_hub_control function that is called for omap4 devices for ports configured for TLL mode. Please note that OMAP4430 ES2.3 devices are not impacted by this errata. Change-Id: Iaf3e116c80681a5073febec3a254ac81f90f8bc6 Signed-off-by: Anand Gadiyar <gadiyar@ti.com> Signed-off-by: Ravikumar Vembu <a18394@motorola.com> Signed-off-by: Jon Hunter <jon-hunter@ti.com>
-rw-r--r--drivers/usb/host/ehci-omap.c248
1 files changed, 244 insertions, 4 deletions
diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c
index d321b8854c8d..16a669f9c938 100644
--- a/drivers/usb/host/ehci-omap.c
+++ b/drivers/usb/host/ehci-omap.c
@@ -51,6 +51,7 @@
#define EHCI_INSNREG05_ULPI_REGADD_SHIFT 16
#define EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT 8
#define EHCI_INSNREG05_ULPI_WRDATA_SHIFT 0
+#define L3INIT_HSUSBTLL_CLKCTRL 0x4A009368
/*-------------------------------------------------------------------------*/
@@ -86,7 +87,245 @@ struct ehci_hcd_omap {
/*-------------------------------------------------------------------------*/
-static const struct hc_driver ehci_omap_hc_driver;
+static struct hc_driver ehci_omap_hc_driver;
+
+static int omap4_ehci_tll_hub_control(
+ struct usb_hcd *hcd,
+ u16 typeReq,
+ u16 wValue,
+ u16 wIndex,
+ char *buf,
+ u16 wLength
+) {
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 __iomem *status_reg = &ehci->regs->port_status[
+ (wIndex & 0xff) - 1];
+ u32 __iomem *hostpc_reg = NULL;
+ u32 temp, temp1, status;
+ unsigned long flags;
+ int retval = 0;
+ u32 runstop, temp_reg;
+
+ spin_lock_irqsave(&ehci->lock, flags);
+ switch (typeReq) {
+ case GetPortStatus:
+ wIndex--;
+ status = 0;
+ temp = ehci_readl(ehci, status_reg);
+
+ /* wPortChange bits */
+ if (temp & PORT_CSC)
+ status |= USB_PORT_STAT_C_CONNECTION << 16;
+ if (temp & PORT_PEC)
+ status |= USB_PORT_STAT_C_ENABLE << 16;
+
+ if ((temp & PORT_OCC) && !ignore_oc) {
+ status |= USB_PORT_STAT_C_OVERCURRENT << 16;
+
+ /*
+ * Hubs should disable port power on over-current.
+ * However, not all EHCI implementations do this
+ * automatically, even if they _do_ support per-port
+ * power switching; they're allowed to just limit the
+ * current. khubd will turn the power back on.
+ */
+ if (HCS_PPC(ehci->hcs_params)) {
+ ehci_writel(ehci,
+ temp & ~(PORT_RWC_BITS | PORT_POWER),
+ status_reg);
+ }
+ }
+
+ /* whoever resumes must GetPortStatus to complete it!! */
+ if (temp & PORT_RESUME) {
+
+ /* Remote Wakeup received? */
+ if (!ehci->reset_done[wIndex]) {
+ /* resume signaling for 20 msec */
+ ehci->reset_done[wIndex] = jiffies
+ + msecs_to_jiffies(20);
+ /* check the port again */
+ mod_timer(&ehci_to_hcd(ehci)->rh_timer,
+ ehci->reset_done[wIndex]);
+ }
+
+ /* resume completed? */
+ else if (time_after_eq(jiffies,
+ ehci->reset_done[wIndex])) {
+ clear_bit(wIndex, &ehci->suspended_ports);
+ set_bit(wIndex, &ehci->port_c_suspend);
+ ehci->reset_done[wIndex] = 0;
+
+ /*
+ * Workaround for OMAP errata:
+ * To Stop Resume Signalling, it is required
+ * to Stop the Host Controller and disable the
+ * TLL Functional Clock.
+ */
+
+ /* Stop the Host Controller */
+ runstop = ehci_readl(ehci,
+ &ehci->regs->command);
+ ehci_writel(ehci, (runstop & ~CMD_RUN),
+ &ehci->regs->command);
+ (void) ehci_readl(ehci, &ehci->regs->command);
+ handshake(ehci, &ehci->regs->status,
+ STS_HALT,
+ STS_HALT,
+ 2000);
+
+ temp_reg = omap_readl(L3INIT_HSUSBTLL_CLKCTRL);
+ temp_reg &= ~(1 << (wIndex + 8));
+
+ /* stop resume signaling */
+ temp = ehci_readl(ehci, status_reg);
+ ehci_writel(ehci,
+ temp & ~(PORT_RWC_BITS | PORT_RESUME),
+ status_reg);
+
+ /* Disable the Channel Optional Fclk */
+ omap_writel(temp_reg, L3INIT_HSUSBTLL_CLKCTRL);
+
+ retval = handshake(ehci, status_reg,
+ PORT_RESUME, 0, 2000 /* 2msec */);
+
+ /*
+ * Enable the Host Controller and start the
+ * Channel Optional Fclk since resume has
+ * finished.
+ */
+ udelay(3);
+ omap_writel((temp_reg | (1 << (wIndex + 8))),
+ L3INIT_HSUSBTLL_CLKCTRL);
+ ehci_writel(ehci, (runstop),
+ &ehci->regs->command);
+ (void) ehci_readl(ehci, &ehci->regs->command);
+
+ if (retval != 0) {
+ ehci_err(ehci,
+ "port %d resume error %d\n",
+ wIndex + 1, retval);
+ spin_unlock_irqrestore(&ehci->lock,
+ flags);
+ return -EPIPE;
+ }
+ temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
+ }
+ }
+
+ /* whoever resets must GetPortStatus to complete it!! */
+ if ((temp & PORT_RESET)
+ && time_after_eq(jiffies,
+ ehci->reset_done[wIndex])) {
+ status |= USB_PORT_STAT_C_RESET << 16;
+ ehci->reset_done[wIndex] = 0;
+
+ /* force reset to complete */
+ ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_RESET),
+ status_reg);
+ /* REVISIT: some hardware needs 550+ usec to clear
+ * this bit; seems too long to spin routinely...
+ */
+ retval = handshake(ehci, status_reg,
+ PORT_RESET, 0, 1000);
+ if (retval != 0) {
+ ehci_err(ehci, "port %d reset error %d\n",
+ wIndex + 1, retval);
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ return -EPIPE;
+ }
+
+ /* see what we found out */
+ temp = check_reset_complete(ehci, wIndex, status_reg,
+ ehci_readl(ehci, status_reg));
+ }
+
+ if (!(temp & (PORT_RESUME|PORT_RESET)))
+ ehci->reset_done[wIndex] = 0;
+
+ /* transfer dedicated ports to the companion hc */
+ if ((temp & PORT_CONNECT) &&
+ test_bit(wIndex, &ehci->companion_ports)) {
+ temp &= ~PORT_RWC_BITS;
+ temp |= PORT_OWNER;
+ ehci_writel(ehci, temp, status_reg);
+ ehci_dbg(ehci, "port %d --> companion\n", wIndex + 1);
+ temp = ehci_readl(ehci, status_reg);
+ }
+
+ /*
+ * Even if OWNER is set, there's no harm letting khubd
+ * see the wPortStatus values (they should all be 0 except
+ * for PORT_POWER anyway).
+ */
+
+ if (temp & PORT_CONNECT) {
+ status |= USB_PORT_STAT_CONNECTION;
+ /* status may be from integrated TT */
+ if (ehci->has_hostpc) {
+ temp1 = ehci_readl(ehci, hostpc_reg);
+ status |= ehci_port_speed(ehci, temp1);
+ } else
+ status |= ehci_port_speed(ehci, temp);
+ }
+ if (temp & PORT_PE)
+ status |= USB_PORT_STAT_ENABLE;
+
+ /* maybe the port was unsuspended without our knowledge */
+ if (temp & (PORT_SUSPEND|PORT_RESUME)) {
+ status |= USB_PORT_STAT_SUSPEND;
+ } else if (test_bit(wIndex, &ehci->suspended_ports)) {
+ clear_bit(wIndex, &ehci->suspended_ports);
+ ehci->reset_done[wIndex] = 0;
+ if (temp & PORT_PE)
+ set_bit(wIndex, &ehci->port_c_suspend);
+ }
+
+ if (temp & PORT_OC)
+ status |= USB_PORT_STAT_OVERCURRENT;
+ if (temp & PORT_RESET)
+ status |= USB_PORT_STAT_RESET;
+ if (temp & PORT_POWER)
+ status |= USB_PORT_STAT_POWER;
+ if (test_bit(wIndex, &ehci->port_c_suspend))
+ status |= USB_PORT_STAT_C_SUSPEND << 16;
+
+#ifndef VERBOSE_DEBUG
+ if (status & ~0xffff) /* only if wPortChange is interesting */
+#endif
+ dbg_port(ehci, "GetStatus", wIndex + 1, temp);
+ put_unaligned_le32(status, buf);
+ break;
+ default:
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ return ehci_hub_control(hcd, typeReq, wValue, wIndex,
+ buf, wLength);
+ }
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ return retval;
+}
+
+static int omap_ehci_hub_control(
+ struct usb_hcd *hcd,
+ u16 typeReq,
+ u16 wValue,
+ u16 wIndex,
+ char *buf,
+ u16 wLength
+) {
+ struct device *dev = hcd->self.controller;
+ struct ehci_hcd_omap *omap =
+ platform_get_drvdata(to_platform_device(dev));
+ struct usbhs_omap_platform_data pdata = omap->platdata;
+
+ if ((wIndex > 0) && (wIndex < OMAP3_HS_USB_PORTS)) {
+ if (pdata.port_mode[wIndex-1] == OMAP_EHCI_PORT_MODE_TLL)
+ return omap4_ehci_tll_hub_control(hcd, typeReq, wValue,
+ wIndex, buf, wLength);
+ }
+
+ return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
+}
static void omap_ehci_soft_phy_reset(struct ehci_hcd_omap *omap, u8 port)
{
@@ -169,6 +408,9 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev)
goto err_mem;
}
+ if (cpu_is_omap44xx() && (omap_rev() < OMAP4430_REV_ES2_3))
+ ehci_omap_hc_driver.hub_control = omap_ehci_hub_control;
+
hcd = usb_create_hcd(&ehci_omap_hc_driver, &pdev->dev,
dev_name(&pdev->dev));
if (!hcd) {
@@ -297,8 +539,6 @@ static void ehci_hcd_omap_shutdown(struct platform_device *pdev)
hcd->driver->shutdown(hcd);
}
-
-
static struct platform_driver ehci_hcd_omap_driver = {
.probe = ehci_hcd_omap_probe,
.remove = ehci_hcd_omap_remove,
@@ -347,7 +587,7 @@ static int ehci_omap_bus_resume(struct usb_hcd *hcd)
/*-------------------------------------------------------------------------*/
-static const struct hc_driver ehci_omap_hc_driver = {
+static struct hc_driver ehci_omap_hc_driver = {
.description = hcd_name,
.product_desc = "OMAP-EHCI Host Controller",
.hcd_priv_size = sizeof(struct ehci_hcd),