usb: ehci: add Faraday USB 2.0 EHCI support

This patch adds support to both Faraday FUSBH200 and FOTG210,
the differences between Faraday EHCI and standard EHCI are
listed bellow:

1. The PORTSC starts at 0x30 instead of 0x44.
2. The CONFIGFLAG(0x40) is not only un-implemented, and
   also has its address space removed.
3. Faraday EHCI is a TDI design, but it doesn't
   compatible with the general TDI implementation
   found at both U-Boot and Linux.
4. The ISOC descriptors differ from standard EHCI in
   several ways. But since U-boot doesn't support ISOC,
   we don't have to worry about that.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
CC: Marek Vasut <marex@denx.de>
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 87a5970..98f2a10 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -43,6 +43,7 @@
 else
 COBJS-$(CONFIG_USB_EHCI_FSL) += ehci-fsl.o
 endif
+COBJS-$(CONFIG_USB_EHCI_FARADAY) += ehci-faraday.o
 COBJS-$(CONFIG_USB_EHCI_EXYNOS) += ehci-exynos.o
 COBJS-$(CONFIG_USB_EHCI_MXC) += ehci-mxc.o
 COBJS-$(CONFIG_USB_EHCI_MXS) += ehci-mxs.o
diff --git a/drivers/usb/host/ehci-faraday.c b/drivers/usb/host/ehci-faraday.c
new file mode 100644
index 0000000..86add36
--- /dev/null
+++ b/drivers/usb/host/ehci-faraday.c
@@ -0,0 +1,148 @@
+/*
+ * Faraday USB 2.0 EHCI Controller
+ *
+ * (C) Copyright 2010 Faraday Technology
+ * Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is released under the terms of GPL v2 and any later version.
+ * See the file COPYING in the root directory of the source tree for details.
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <usb.h>
+#include <usb/fusbh200.h>
+#include <usb/fotg210.h>
+
+#include "ehci.h"
+
+#ifndef CONFIG_USB_EHCI_BASE_LIST
+#define CONFIG_USB_EHCI_BASE_LIST	{ CONFIG_USB_EHCI_BASE }
+#endif
+
+union ehci_faraday_regs {
+	struct fusbh200_regs usb;
+	struct fotg210_regs  otg;
+};
+
+static inline int ehci_is_fotg2xx(union ehci_faraday_regs *regs)
+{
+	return !readl(&regs->usb.easstr);
+}
+
+/*
+ * Create the appropriate control structures to manage
+ * a new EHCI host controller.
+ */
+int ehci_hcd_init(int index, struct ehci_hccr **ret_hccr,
+		struct ehci_hcor **ret_hcor)
+{
+	struct ehci_hccr *hccr;
+	struct ehci_hcor *hcor;
+	union ehci_faraday_regs *regs;
+	uint32_t base_list[] = CONFIG_USB_EHCI_BASE_LIST;
+
+	if (index < 0 || index >= ARRAY_SIZE(base_list))
+		return -1;
+	regs = (void __iomem *)base_list[index];
+	hccr = (struct ehci_hccr *)&regs->usb.hccr;
+	hcor = (struct ehci_hcor *)&regs->usb.hcor;
+
+	if (ehci_is_fotg2xx(regs)) {
+		/* A-device bus reset */
+		/* ... Power off A-device */
+		setbits_le32(&regs->otg.otgcsr, OTGCSR_A_BUSDROP);
+		/* ... Drop vbus and bus traffic */
+		clrbits_le32(&regs->otg.otgcsr, OTGCSR_A_BUSREQ);
+		mdelay(1);
+		/* ... Power on A-device */
+		clrbits_le32(&regs->otg.otgcsr, OTGCSR_A_BUSDROP);
+		/* ... Drive vbus and bus traffic */
+		setbits_le32(&regs->otg.otgcsr, OTGCSR_A_BUSREQ);
+		mdelay(1);
+		/* Disable OTG & DEV interrupts, triggered at level-high */
+		writel(IMR_IRQLH | IMR_OTG | IMR_DEV, &regs->otg.imr);
+		/* Clear all interrupt status */
+		writel(ISR_HOST | ISR_OTG | ISR_DEV, &regs->otg.isr);
+	} else {
+		/* Interrupt=level-high */
+		setbits_le32(&regs->usb.bmcsr, BMCSR_IRQLH);
+		/* VBUS on */
+		clrbits_le32(&regs->usb.bmcsr, BMCSR_VBUS_OFF);
+		/* Disable all interrupts */
+		writel(0x00, &regs->usb.bmier);
+		writel(0x1f, &regs->usb.bmisr);
+	}
+
+	*ret_hccr = hccr;
+	*ret_hcor = hcor;
+
+	return 0;
+}
+
+/*
+ * Destroy the appropriate control structures corresponding
+ * the the EHCI host controller.
+ */
+int ehci_hcd_stop(int index)
+{
+	return 0;
+}
+
+/*
+ * This ehci_set_usbmode() overrides the weak function
+ * in "ehci-hcd.c".
+ */
+void ehci_set_usbmode(int index)
+{
+	/* nothing needs to be done */
+}
+
+/*
+ * This ehci_get_port_speed() overrides the weak function
+ * in "ehci-hcd.c".
+ */
+int ehci_get_port_speed(struct ehci_hcor *hcor, uint32_t reg)
+{
+	int spd, ret = PORTSC_PSPD_HS;
+	union ehci_faraday_regs *regs = (void __iomem *)((ulong)hcor - 0x10);
+
+	if (ehci_is_fotg2xx(regs))
+		spd = OTGCSR_SPD(readl(&regs->otg.otgcsr));
+	else
+		spd = BMCSR_SPD(readl(&regs->usb.bmcsr));
+
+	switch (spd) {
+	case 0:    /* full speed */
+		ret = PORTSC_PSPD_FS;
+		break;
+	case 1:    /* low  speed */
+		ret = PORTSC_PSPD_LS;
+		break;
+	case 2:    /* high speed */
+		ret = PORTSC_PSPD_HS;
+		break;
+	default:
+		printf("ehci-faraday: invalid device speed\n");
+		break;
+	}
+
+	return ret;
+}
+
+/*
+ * This ehci_get_portsc_register() overrides the weak function
+ * in "ehci-hcd.c".
+ */
+uint32_t *ehci_get_portsc_register(struct ehci_hcor *hcor, int port)
+{
+	/* Faraday EHCI has one and only one portsc register */
+	if (port) {
+		/* Printing the message would cause a scan failure! */
+		debug("The request port(%d) is not configured\n", port);
+		return NULL;
+	}
+
+	/* Faraday EHCI PORTSC register offset is 0x20 from hcor */
+	return (uint32_t *)((uint8_t *)hcor + 0x20);
+}
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 7e8e85c..28b6007 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -589,10 +589,12 @@
 		dev->act_len = length - QT_TOKEN_GET_TOTALBYTES(token);
 	} else {
 		dev->act_len = 0;
+#ifndef CONFIG_USB_EHCI_FARADAY
 		debug("dev=%u, usbsts=%#x, p[1]=%#x, p[2]=%#x\n",
 		      dev->devnum, ehci_readl(&ctrl->hcor->or_usbsts),
 		      ehci_readl(&ctrl->hcor->or_portsc[0]),
 		      ehci_readl(&ctrl->hcor->or_portsc[1]));
+#endif
 	}
 
 	free(qtd);
@@ -980,10 +982,13 @@
 	cmd |= CMD_RUN;
 	ehci_writel(&ehcic[index].hcor->or_usbcmd, cmd);
 
+#ifndef CONFIG_USB_EHCI_FARADAY
 	/* take control over the ports */
 	cmd = ehci_readl(&ehcic[index].hcor->or_configflag);
 	cmd |= FLAG_CF;
 	ehci_writel(&ehcic[index].hcor->or_configflag, cmd);
+#endif
+
 	/* unblock posted write */
 	cmd = ehci_readl(&ehcic[index].hcor->or_usbcmd);
 	mdelay(5);