summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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),