summaryrefslogtreecommitdiff
path: root/drivers/usb/host/ehci-tegra.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/ehci-tegra.c')
-rw-r--r--drivers/usb/host/ehci-tegra.c115
1 files changed, 115 insertions, 0 deletions
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index 4ddb279cfb35..095447d9e3a2 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -23,12 +23,16 @@
#include <mach/usb_phy.h>
#include <mach/iomap.h>
+#include "../../../arch/arm/mach-tegra/tegra_usb_phy.h"
+
#if 0
#define EHCI_DBG(stuff...) pr_info("ehci-tegra: " stuff)
#else
#define EHCI_DBG(stuff...) do {} while (0)
#endif
+#define TEGRA_USB_PORTSC1_PFSC (1 << 24)
+
static const char driver_name[] = "tegra-ehci";
#define TEGRA_USB_DMA_ALIGN 32
@@ -51,6 +55,22 @@ struct dma_align_buffer {
u8 data[0];
};
+#ifdef CONFIG_MACH_COLIBRI_T20
+/* To limit the speed of USB to full speed */
+int g_usb_high_speed = 0;
+
+/* To limit the speed of USB to full speed */
+static int __init enable_usb_high_speed(char *s)
+{
+ if (!(*s) || !strcmp(s, "1"))
+ g_usb_high_speed = 1;
+
+ return 0;
+}
+__setup("usb_high_speed=", enable_usb_high_speed);
+EXPORT_SYMBOL_GPL(g_usb_high_speed);
+#endif /* CONFIG_MACH_COLIBRI_T20 */
+
static void free_align_buffer(struct urb *urb)
{
struct dma_align_buffer *temp = container_of(urb->transfer_buffer,
@@ -186,6 +206,71 @@ static irqreturn_t tegra_ehci_irq(struct usb_hcd *hcd)
return irq_status;
}
+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,
@@ -201,6 +286,29 @@ static int tegra_ehci_hub_control(
int retval = 0;
u32 __iomem *status_reg;
+#ifdef CONFIG_MACH_COLIBRI_T20
+ u32 temp;
+
+ /* To limit the speed of USB to full speed */
+ if (!g_usb_high_speed) {
+ /* Check whether port is not 2nd one internally connected to
+ ASIX Ethernet chip, set PFSC (Port Force Full Speed) only
+ for externally accessible OTG and host port */
+ if (tegra->phy->inst != 1) {
+ status_reg = &ehci->regs->port_status[(wIndex & 0xff)
+ - 1];
+ temp = ehci_readl(ehci, status_reg);
+ /* Check whether PFSC bit is already set or not */
+ if (!(temp & TEGRA_USB_PORTSC1_PFSC)) {
+ ehci_writel(ehci, (temp |
+ TEGRA_USB_PORTSC1_PFSC),
+ status_reg);
+ temp = ehci_readl(ehci, status_reg);
+ }
+ }
+ }
+#endif /* CONFIG_MACH_COLIBRI_T20 */
+
if (!tegra_usb_phy_hw_accessible(tegra->phy)) {
if (buf)
memset(buf, 0, wLength);
@@ -249,6 +357,13 @@ static int tegra_ehci_hub_control(
break;
}
+ /* For USB1 port we need to issue Port Reset twice internally */
+ if (tegra->phy->inst == 0 &&
+ (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_RESET)) {
+ status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1];
+ return tegra_ehci_internal_port_reset(ehci, status_reg);
+ }
+
/* handle ehci hub control request */
retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);