summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Lin <jilin@nvidia.com>2011-04-17 11:58:25 +0300
committerGreg Kroah-Hartman <gregkh@suse.de>2011-04-29 17:24:37 -0700
commit1f594b64a4f74ece0b7166ca4db05a71a64bd685 (patch)
treeb997ecc45c7eba7d410af1d360d3190231c85a56
parent3c86c07baaa22e1ebae1922b5285f79a39e93d83 (diff)
USB: ehci: tegra: fix USB1 port reset issue
Tegra USB1 port needs to issue Port Reset twice internally, otherwise it fails to enumerate devices attached to it Signed-off-by: Jim Lin <jilin@nvidia.com> Signed-off-by: Olof Johansson <olofj@chromium.org> [ squash two patches into one and minor style cleanups ] Signed-off-by: Mike Rapoport <mike@compulab.co.il> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/usb/host/ehci-tegra.c72
1 files changed, 72 insertions, 0 deletions
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index a516af28c29b..7359bcbe4176 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -58,6 +58,71 @@ static void tegra_ehci_power_down(struct usb_hcd *hcd)
clk_disable(tegra->emc_clk);
}
+static int tegra_ehci_internal_port_reset(
+ struct ehci_hcd *ehci,
+ u32 __iomem *portsc_reg
+)
+{
+ u32 temp;
+ unsigned long flags;
+ int retval = 0;
+ int i, tries;
+ u32 saved_usbintr;
+
+ spin_lock_irqsave(&ehci->lock, flags);
+ saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable);
+ /* disable USB interrupt */
+ ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ /*
+ * Here we have to do Port Reset at most twice for
+ * Port Enable bit to be set.
+ */
+ for (i = 0; i < 2; i++) {
+ temp = ehci_readl(ehci, portsc_reg);
+ temp |= PORT_RESET;
+ ehci_writel(ehci, temp, portsc_reg);
+ mdelay(10);
+ temp &= ~PORT_RESET;
+ ehci_writel(ehci, temp, portsc_reg);
+ mdelay(1);
+ tries = 100;
+ do {
+ mdelay(1);
+ /*
+ * Up to this point, Port Enable bit is
+ * expected to be set after 2 ms waiting.
+ * USB1 usually takes extra 45 ms, for safety,
+ * we take 100 ms as timeout.
+ */
+ temp = ehci_readl(ehci, portsc_reg);
+ } while (!(temp & PORT_PE) && tries--);
+ if (temp & PORT_PE)
+ break;
+ }
+ if (i == 2)
+ retval = -ETIMEDOUT;
+
+ /*
+ * Clear Connect Status Change bit if it's set.
+ * We can't clear PORT_PEC. It will also cause PORT_PE to be cleared.
+ */
+ if (temp & PORT_CSC)
+ ehci_writel(ehci, PORT_CSC, portsc_reg);
+
+ /*
+ * Write to clear any interrupt status bits that might be set
+ * during port reset.
+ */
+ temp = ehci_readl(ehci, &ehci->regs->status);
+ ehci_writel(ehci, temp, &ehci->regs->status);
+
+ /* restore original interrupt enable bits */
+ ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable);
+ return retval;
+}
+
static int tegra_ehci_hub_control(
struct usb_hcd *hcd,
u16 typeReq,
@@ -121,6 +186,13 @@ static int tegra_ehci_hub_control(
goto done;
}
+ /* For USB1 port we need to issue Port Reset twice internally */
+ if (tegra->phy->instance == 0 &&
+ (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_RESET)) {
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ return tegra_ehci_internal_port_reset(ehci, status_reg);
+ }
+
/*
* Tegra host controller will time the resume operation to clear the bit
* when the port control state switches to HS or FS Idle. This behavior