summaryrefslogtreecommitdiff
path: root/drivers/usb/host
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/Makefile1
-rw-r--r--drivers/usb/host/ehci-exynos.c51
-rw-r--r--drivers/usb/host/ehci-hcd.c318
-rw-r--r--drivers/usb/host/ehci-mx6.c1
-rw-r--r--drivers/usb/host/ehci-mxs.c155
-rw-r--r--drivers/usb/host/ehci-pci.c60
-rw-r--r--drivers/usb/host/ehci-spear.c59
-rw-r--r--drivers/usb/host/ehci-tegra.c546
-rw-r--r--drivers/usb/host/ehci.h6
9 files changed, 1067 insertions, 130 deletions
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 6c9479492..9a6f98208 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -54,6 +54,7 @@ COBJS-$(CONFIG_USB_EHCI_PPC4XX) += ehci-ppc4xx.o
COBJS-$(CONFIG_USB_EHCI_IXP4XX) += ehci-ixp.o
COBJS-$(CONFIG_USB_EHCI_MARVELL) += ehci-marvell.o
COBJS-$(CONFIG_USB_EHCI_PCI) += ehci-pci.o
+COBJS-$(CONFIG_USB_EHCI_SPEAR) += ehci-spear.o
COBJS-$(CONFIG_USB_EHCI_TEGRA) += ehci-tegra.o
COBJS-$(CONFIG_USB_EHCI_VCT) += ehci-vct.o
diff --git a/drivers/usb/host/ehci-exynos.c b/drivers/usb/host/ehci-exynos.c
index 3ca4c5c33..0c797aa04 100644
--- a/drivers/usb/host/ehci-exynos.c
+++ b/drivers/usb/host/ehci-exynos.c
@@ -42,11 +42,15 @@ DECLARE_GLOBAL_DATA_PTR;
*/
struct exynos_ehci {
struct exynos_usb_phy *usb;
- unsigned int *hcd;
+ struct ehci_hccr *hcd;
};
+static struct exynos_ehci exynos;
+
+#ifdef CONFIG_OF_CONTROL
static int exynos_usb_parse_dt(const void *blob, struct exynos_ehci *exynos)
{
+ fdt_addr_t addr;
unsigned int node;
int depth;
@@ -59,12 +63,14 @@ static int exynos_usb_parse_dt(const void *blob, struct exynos_ehci *exynos)
/*
* Get the base address for EHCI controller from the device node
*/
- exynos->hcd = (unsigned int *)fdtdec_get_addr(blob, node, "reg");
- if (exynos->hcd == NULL) {
+ addr = fdtdec_get_addr(blob, node, "reg");
+ if (addr == FDT_ADDR_T_NONE) {
debug("Can't get the EHCI register address\n");
return -ENXIO;
}
+ exynos->hcd = (struct ehci_hccr *)addr;
+
depth = 0;
node = fdtdec_next_compatible_subnode(blob, node,
COMPAT_SAMSUNG_EXYNOS_USB_PHY, &depth);
@@ -85,6 +91,7 @@ static int exynos_usb_parse_dt(const void *blob, struct exynos_ehci *exynos)
return 0;
}
+#endif
/* Setup the EHCI host controller. */
static void setup_usb_phy(struct exynos_usb_phy *usb)
@@ -144,20 +151,21 @@ static void reset_usb_phy(struct exynos_usb_phy *usb)
*/
int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor)
{
- struct exynos_ehci *exynos = NULL;
+ struct exynos_ehci *ctx = &exynos;
- exynos = (struct exynos_ehci *)
- kzalloc(sizeof(struct exynos_ehci), GFP_KERNEL);
- if (!exynos) {
- debug("failed to allocate exynos ehci context\n");
- return -ENOMEM;
+#ifdef CONFIG_OF_CONTROL
+ if (exynos_usb_parse_dt(gd->fdt_blob, ctx)) {
+ debug("Unable to parse device tree for ehci-exynos\n");
+ return -ENODEV;
}
+#else
+ ctx->usb = (struct exynos_usb_phy *)samsung_get_base_usb_phy();
+ ctx->hcd = (struct ehci_hccr *)samsung_get_base_usb_ehci();
+#endif
- exynos_usb_parse_dt(gd->fdt_blob, exynos);
+ setup_usb_phy(ctx->usb);
- setup_usb_phy(exynos->usb);
-
- *hccr = (struct ehci_hccr *)(exynos->hcd);
+ *hccr = ctx->hcd;
*hcor = (struct ehci_hcor *)((uint32_t) *hccr
+ HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
@@ -165,8 +173,6 @@ int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor)
(uint32_t)*hccr, (uint32_t)*hcor,
(uint32_t)HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
- kfree(exynos);
-
return 0;
}
@@ -176,20 +182,9 @@ int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor)
*/
int ehci_hcd_stop(int index)
{
- struct exynos_ehci *exynos = NULL;
-
- exynos = (struct exynos_ehci *)
- kzalloc(sizeof(struct exynos_ehci), GFP_KERNEL);
- if (!exynos) {
- debug("failed to allocate exynos ehci context\n");
- return -ENOMEM;
- }
-
- exynos_usb_parse_dt(gd->fdt_blob, exynos);
-
- reset_usb_phy(exynos->usb);
+ struct exynos_ehci *ctx = &exynos;
- kfree(exynos);
+ reset_usb_phy(ctx->usb);
return 0;
}
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 7f98a6354..c81687820 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -21,12 +21,14 @@
* MA 02111-1307 USA
*/
#include <common.h>
+#include <errno.h>
#include <asm/byteorder.h>
#include <asm/unaligned.h>
#include <usb.h>
#include <asm/io.h>
#include <malloc.h>
#include <watchdog.h>
+#include <linux/compiler.h>
#include "ehci.h"
@@ -39,7 +41,10 @@ static struct ehci_ctrl {
struct ehci_hcor *hcor;
int rootdev;
uint16_t portreset;
- struct QH qh_list __attribute__((aligned(USB_DMA_MINALIGN)));
+ struct QH qh_list __aligned(USB_DMA_MINALIGN);
+ struct QH periodic_queue __aligned(USB_DMA_MINALIGN);
+ uint32_t *periodic_list;
+ int ntds;
} ehcic[CONFIG_USB_MAX_CONTROLLER_COUNT];
#define ALIGN_END_ADDR(type, ptr, size) \
@@ -858,6 +863,8 @@ int usb_lowlevel_init(int index, void **controller)
uint32_t reg;
uint32_t cmd;
struct QH *qh_list;
+ struct QH *periodic;
+ int i;
if (ehci_hcd_init(index, &ehcic[index].hccr, &ehcic[index].hcor))
return -1;
@@ -870,6 +877,9 @@ int usb_lowlevel_init(int index, void **controller)
if (ehci_hcd_init(index, &ehcic[index].hccr, &ehcic[index].hcor))
return -1;
#endif
+ /* Set the high address word (aka segment) for 64-bit controller */
+ if (ehci_readl(&ehcic[index].hccr->cr_hccparams) & 1)
+ ehci_writel(ehcic[index].hcor->or_ctrldssegment, 0);
qh_list = &ehcic[index].qh_list;
@@ -884,6 +894,40 @@ int usb_lowlevel_init(int index, void **controller)
qh_list->qh_overlay.qt_token =
cpu_to_hc32(QT_TOKEN_STATUS(QT_TOKEN_STATUS_HALTED));
+ /* Set async. queue head pointer. */
+ ehci_writel(&ehcic[index].hcor->or_asynclistaddr, (uint32_t)qh_list);
+
+ /*
+ * Set up periodic list
+ * Step 1: Parent QH for all periodic transfers.
+ */
+ periodic = &ehcic[index].periodic_queue;
+ memset(periodic, 0, sizeof(*periodic));
+ periodic->qh_link = cpu_to_hc32(QH_LINK_TERMINATE);
+ periodic->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
+ periodic->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
+
+ /*
+ * Step 2: Setup frame-list: Every microframe, USB tries the same list.
+ * In particular, device specifications on polling frequency
+ * are disregarded. Keyboards seem to send NAK/NYet reliably
+ * when polled with an empty buffer.
+ *
+ * Split Transactions will be spread across microframes using
+ * S-mask and C-mask.
+ */
+ ehcic[index].periodic_list = memalign(4096, 1024*4);
+ if (!ehcic[index].periodic_list)
+ return -ENOMEM;
+ for (i = 0; i < 1024; i++) {
+ ehcic[index].periodic_list[i] = (uint32_t)periodic
+ | QH_LINK_TYPE_QH;
+ }
+
+ /* Set periodic list base address */
+ ehci_writel(&ehcic[index].hcor->or_periodiclistbase,
+ (uint32_t)ehcic[index].periodic_list);
+
reg = ehci_readl(&ehcic[index].hccr->cr_hcsparams);
descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);
debug("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts);
@@ -953,10 +997,254 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
return ehci_submit_async(dev, pipe, buffer, length, setup);
}
+struct int_queue {
+ struct QH *first;
+ struct QH *current;
+ struct QH *last;
+ struct qTD *tds;
+};
+
+#define NEXT_QH(qh) (struct QH *)((qh)->qh_link & ~0x1f)
+
+static int
+enable_periodic(struct ehci_ctrl *ctrl)
+{
+ uint32_t cmd;
+ struct ehci_hcor *hcor = ctrl->hcor;
+ int ret;
+
+ cmd = ehci_readl(&hcor->or_usbcmd);
+ cmd |= CMD_PSE;
+ ehci_writel(&hcor->or_usbcmd, cmd);
+
+ ret = handshake((uint32_t *)&hcor->or_usbsts,
+ STS_PSS, STS_PSS, 100 * 1000);
+ if (ret < 0) {
+ printf("EHCI failed: timeout when enabling periodic list\n");
+ return -ETIMEDOUT;
+ }
+ udelay(1000);
+ return 0;
+}
+
+static int
+disable_periodic(struct ehci_ctrl *ctrl)
+{
+ uint32_t cmd;
+ struct ehci_hcor *hcor = ctrl->hcor;
+ int ret;
+
+ cmd = ehci_readl(&hcor->or_usbcmd);
+ cmd &= ~CMD_PSE;
+ ehci_writel(&hcor->or_usbcmd, cmd);
+
+ ret = handshake((uint32_t *)&hcor->or_usbsts,
+ STS_PSS, 0, 100 * 1000);
+ if (ret < 0) {
+ printf("EHCI failed: timeout when disabling periodic list\n");
+ return -ETIMEDOUT;
+ }
+ return 0;
+}
+
+static int periodic_schedules;
+
+struct int_queue *
+create_int_queue(struct usb_device *dev, unsigned long pipe, int queuesize,
+ int elementsize, void *buffer)
+{
+ struct ehci_ctrl *ctrl = dev->controller;
+ struct int_queue *result = NULL;
+ int i;
+
+ debug("Enter create_int_queue\n");
+ if (usb_pipetype(pipe) != PIPE_INTERRUPT) {
+ debug("non-interrupt pipe (type=%lu)", usb_pipetype(pipe));
+ return NULL;
+ }
+
+ /* limit to 4 full pages worth of data -
+ * we can safely fit them in a single TD,
+ * no matter the alignment
+ */
+ if (elementsize >= 16384) {
+ debug("too large elements for interrupt transfers\n");
+ return NULL;
+ }
+
+ result = malloc(sizeof(*result));
+ if (!result) {
+ debug("ehci intr queue: out of memory\n");
+ goto fail1;
+ }
+ result->first = memalign(32, sizeof(struct QH) * queuesize);
+ if (!result->first) {
+ debug("ehci intr queue: out of memory\n");
+ goto fail2;
+ }
+ result->current = result->first;
+ result->last = result->first + queuesize - 1;
+ result->tds = memalign(32, sizeof(struct qTD) * queuesize);
+ if (!result->tds) {
+ debug("ehci intr queue: out of memory\n");
+ goto fail3;
+ }
+ memset(result->first, 0, sizeof(struct QH) * queuesize);
+ memset(result->tds, 0, sizeof(struct qTD) * queuesize);
+
+ for (i = 0; i < queuesize; i++) {
+ struct QH *qh = result->first + i;
+ struct qTD *td = result->tds + i;
+ void **buf = &qh->buffer;
+
+ qh->qh_link = (uint32_t)(qh+1) | QH_LINK_TYPE_QH;
+ if (i == queuesize - 1)
+ qh->qh_link = QH_LINK_TERMINATE;
+
+ qh->qh_overlay.qt_next = (uint32_t)td;
+ qh->qh_endpt1 = (0 << 28) | /* No NAK reload (ehci 4.9) */
+ (usb_maxpacket(dev, pipe) << 16) | /* MPS */
+ (1 << 14) |
+ QH_ENDPT1_EPS(ehci_encode_speed(dev->speed)) |
+ (usb_pipeendpoint(pipe) << 8) | /* Endpoint Number */
+ (usb_pipedevice(pipe) << 0);
+ qh->qh_endpt2 = (1 << 30) | /* 1 Tx per mframe */
+ (1 << 0); /* S-mask: microframe 0 */
+ if (dev->speed == USB_SPEED_LOW ||
+ dev->speed == USB_SPEED_FULL) {
+ debug("TT: port: %d, hub address: %d\n",
+ dev->portnr, dev->parent->devnum);
+ qh->qh_endpt2 |= (dev->portnr << 23) |
+ (dev->parent->devnum << 16) |
+ (0x1c << 8); /* C-mask: microframes 2-4 */
+ }
+
+ td->qt_next = QT_NEXT_TERMINATE;
+ td->qt_altnext = QT_NEXT_TERMINATE;
+ debug("communication direction is '%s'\n",
+ usb_pipein(pipe) ? "in" : "out");
+ td->qt_token = (elementsize << 16) |
+ ((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */
+ 0x80; /* active */
+ td->qt_buffer[0] = (uint32_t)buffer + i * elementsize;
+ td->qt_buffer[1] = (td->qt_buffer[0] + 0x1000) & ~0xfff;
+ td->qt_buffer[2] = (td->qt_buffer[0] + 0x2000) & ~0xfff;
+ td->qt_buffer[3] = (td->qt_buffer[0] + 0x3000) & ~0xfff;
+ td->qt_buffer[4] = (td->qt_buffer[0] + 0x4000) & ~0xfff;
+
+ *buf = buffer + i * elementsize;
+ }
+
+ if (disable_periodic(ctrl) < 0) {
+ debug("FATAL: periodic should never fail, but did");
+ goto fail3;
+ }
+
+ /* hook up to periodic list */
+ struct QH *list = &ctrl->periodic_queue;
+ result->last->qh_link = list->qh_link;
+ list->qh_link = (uint32_t)result->first | QH_LINK_TYPE_QH;
+
+ if (enable_periodic(ctrl) < 0) {
+ debug("FATAL: periodic should never fail, but did");
+ goto fail3;
+ }
+ periodic_schedules++;
+
+ debug("Exit create_int_queue\n");
+ return result;
+fail3:
+ if (result->tds)
+ free(result->tds);
+fail2:
+ if (result->first)
+ free(result->first);
+ if (result)
+ free(result);
+fail1:
+ return NULL;
+}
+
+void *poll_int_queue(struct usb_device *dev, struct int_queue *queue)
+{
+ struct QH *cur = queue->current;
+
+ /* depleted queue */
+ if (cur == NULL) {
+ debug("Exit poll_int_queue with completed queue\n");
+ return NULL;
+ }
+ /* still active */
+ if (cur->qh_overlay.qt_token & 0x80) {
+ debug("Exit poll_int_queue with no completed intr transfer. "
+ "token is %x\n", cur->qh_overlay.qt_token);
+ return NULL;
+ }
+ if (!(cur->qh_link & QH_LINK_TERMINATE))
+ queue->current++;
+ else
+ queue->current = NULL;
+ debug("Exit poll_int_queue with completed intr transfer. "
+ "token is %x at %p (first at %p)\n", cur->qh_overlay.qt_token,
+ &cur->qh_overlay.qt_token, queue->first);
+ return cur->buffer;
+}
+
+/* Do not free buffers associated with QHs, they're owned by someone else */
+int
+destroy_int_queue(struct usb_device *dev, struct int_queue *queue)
+{
+ struct ehci_ctrl *ctrl = dev->controller;
+ int result = -1;
+ unsigned long timeout;
+
+ if (disable_periodic(ctrl) < 0) {
+ debug("FATAL: periodic should never fail, but did");
+ goto out;
+ }
+ periodic_schedules--;
+
+ struct QH *cur = &ctrl->periodic_queue;
+ timeout = get_timer(0) + 500; /* abort after 500ms */
+ while (!(cur->qh_link & QH_LINK_TERMINATE)) {
+ debug("considering %p, with qh_link %x\n", cur, cur->qh_link);
+ if (NEXT_QH(cur) == queue->first) {
+ debug("found candidate. removing from chain\n");
+ cur->qh_link = queue->last->qh_link;
+ result = 0;
+ break;
+ }
+ cur = NEXT_QH(cur);
+ if (get_timer(0) > timeout) {
+ printf("Timeout destroying interrupt endpoint queue\n");
+ result = -1;
+ goto out;
+ }
+ }
+
+ if (periodic_schedules > 0) {
+ result = enable_periodic(ctrl);
+ if (result < 0)
+ debug("FATAL: periodic should never fail, but did");
+ }
+
+out:
+ free(queue->tds);
+ free(queue->first);
+ free(queue);
+
+ return result;
+}
+
int
submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
int length, int interval)
{
+ void *backbuffer;
+ struct int_queue *queue;
+ unsigned long timeout;
+ int result = 0, ret;
+
debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d",
dev, pipe, buffer, length, interval);
@@ -972,9 +1260,31 @@ submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
* not require more than a single qTD.
*/
if (length > usb_maxpacket(dev, pipe)) {
- printf("%s: Interrupt transfers requiring several transactions "
- "are not supported.\n", __func__);
+ printf("%s: Interrupt transfers requiring several "
+ "transactions are not supported.\n", __func__);
return -1;
}
- return ehci_submit_async(dev, pipe, buffer, length, NULL);
+
+ queue = create_int_queue(dev, pipe, 1, length, buffer);
+
+ timeout = get_timer(0) + USB_TIMEOUT_MS(pipe);
+ while ((backbuffer = poll_int_queue(dev, queue)) == NULL)
+ if (get_timer(0) > timeout) {
+ printf("Timeout poll on interrupt endpoint\n");
+ result = -ETIMEDOUT;
+ break;
+ }
+
+ if (backbuffer != buffer) {
+ debug("got wrong buffer back (%x instead of %x)\n",
+ (uint32_t)backbuffer, (uint32_t)buffer);
+ return -EINVAL;
+ }
+
+ ret = destroy_int_queue(dev, queue);
+ if (ret < 0)
+ return ret;
+
+ /* everything worked out fine */
+ return result;
}
diff --git a/drivers/usb/host/ehci-mx6.c b/drivers/usb/host/ehci-mx6.c
index 1b20e4185..c1ae3d908 100644
--- a/drivers/usb/host/ehci-mx6.c
+++ b/drivers/usb/host/ehci-mx6.c
@@ -21,7 +21,6 @@
#include <asm/io.h>
#include <asm/arch/imx-regs.h>
#include <asm/arch/clock.h>
-#include <asm/arch/mx6x_pins.h>
#include <asm/imx-common/iomux-v3.h>
#include "ehci.h"
diff --git a/drivers/usb/host/ehci-mxs.c b/drivers/usb/host/ehci-mxs.c
index 5062af555..f320d3eb5 100644
--- a/drivers/usb/host/ehci-mxs.c
+++ b/drivers/usb/host/ehci-mxs.c
@@ -21,91 +21,107 @@
#include <common.h>
#include <asm/io.h>
-#include <asm/arch/regs-common.h>
-#include <asm/arch/regs-base.h>
-#include <asm/arch/regs-clkctrl-mx28.h>
-#include <asm/arch/regs-usb.h>
-#include <asm/arch/regs-usbphy.h>
+#include <asm/arch/imx-regs.h>
+#include <errno.h>
#include "ehci.h"
-#if (CONFIG_EHCI_MXS_PORT != 0) && (CONFIG_EHCI_MXS_PORT != 1)
-#error "MXS EHCI: Invalid port selected!"
-#endif
-
-#ifndef CONFIG_EHCI_MXS_PORT
-#error "MXS EHCI: Please define correct port using CONFIG_EHCI_MXS_PORT!"
-#endif
+/* This DIGCTL register ungates clock to USB */
+#define HW_DIGCTL_CTRL 0x8001c000
+#define HW_DIGCTL_CTRL_USB0_CLKGATE (1 << 2)
+#define HW_DIGCTL_CTRL_USB1_CLKGATE (1 << 16)
-static struct ehci_mxs {
- struct mxs_usb_regs *usb_regs;
+struct ehci_mxs_port {
+ uint32_t usb_regs;
struct mxs_usbphy_regs *phy_regs;
-} ehci_mxs;
-int mxs_ehci_get_port(struct ehci_mxs *mxs_usb, int port)
+ struct mxs_register_32 *pll;
+ uint32_t pll_en_bits;
+ uint32_t pll_dis_bits;
+ uint32_t gate_bits;
+};
+
+static const struct ehci_mxs_port mxs_port[] = {
+#ifdef CONFIG_EHCI_MXS_PORT0
+ {
+ MXS_USBCTRL0_BASE,
+ (struct mxs_usbphy_regs *)MXS_USBPHY0_BASE,
+ (struct mxs_register_32 *)(MXS_CLKCTRL_BASE +
+ offsetof(struct mxs_clkctrl_regs,
+ hw_clkctrl_pll0ctrl0_reg)),
+ CLKCTRL_PLL0CTRL0_EN_USB_CLKS | CLKCTRL_PLL0CTRL0_POWER,
+ CLKCTRL_PLL0CTRL0_EN_USB_CLKS,
+ HW_DIGCTL_CTRL_USB0_CLKGATE,
+ },
+#endif
+#ifdef CONFIG_EHCI_MXS_PORT1
+ {
+ MXS_USBCTRL1_BASE,
+ (struct mxs_usbphy_regs *)MXS_USBPHY1_BASE,
+ (struct mxs_register_32 *)(MXS_CLKCTRL_BASE +
+ offsetof(struct mxs_clkctrl_regs,
+ hw_clkctrl_pll1ctrl0_reg)),
+ CLKCTRL_PLL1CTRL0_EN_USB_CLKS | CLKCTRL_PLL1CTRL0_POWER,
+ CLKCTRL_PLL1CTRL0_EN_USB_CLKS,
+ HW_DIGCTL_CTRL_USB1_CLKGATE,
+ },
+#endif
+};
+
+static int ehci_mxs_toggle_clock(const struct ehci_mxs_port *port, int enable)
{
- uint32_t usb_base, phy_base;
- switch (port) {
- case 0:
- usb_base = MXS_USBCTRL0_BASE;
- phy_base = MXS_USBPHY0_BASE;
- break;
- case 1:
- usb_base = MXS_USBCTRL1_BASE;
- phy_base = MXS_USBPHY1_BASE;
- break;
- default:
- printf("CONFIG_EHCI_MXS_PORT (port = %d)\n", port);
- return -1;
+ struct mxs_register_32 *digctl_ctrl =
+ (struct mxs_register_32 *)HW_DIGCTL_CTRL;
+ int pll_offset, dig_offset;
+
+ if (enable) {
+ pll_offset = offsetof(struct mxs_register_32, reg_set);
+ dig_offset = offsetof(struct mxs_register_32, reg_clr);
+ writel(port->gate_bits, (u32)&digctl_ctrl->reg + dig_offset);
+ writel(port->pll_en_bits, (u32)port->pll + pll_offset);
+ } else {
+ pll_offset = offsetof(struct mxs_register_32, reg_clr);
+ dig_offset = offsetof(struct mxs_register_32, reg_set);
+ writel(port->pll_dis_bits, (u32)port->pll + pll_offset);
+ writel(port->gate_bits, (u32)&digctl_ctrl->reg + dig_offset);
}
- mxs_usb->usb_regs = (struct mxs_usb_regs *)usb_base;
- mxs_usb->phy_regs = (struct mxs_usbphy_regs *)phy_base;
return 0;
}
-/* This DIGCTL register ungates clock to USB */
-#define HW_DIGCTL_CTRL 0x8001c000
-#define HW_DIGCTL_CTRL_USB0_CLKGATE (1 << 2)
-#define HW_DIGCTL_CTRL_USB1_CLKGATE (1 << 16)
-
int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor)
{
int ret;
uint32_t usb_base, cap_base;
- struct mxs_register_32 *digctl_ctrl =
- (struct mxs_register_32 *)HW_DIGCTL_CTRL;
- struct mxs_clkctrl_regs *clkctrl_regs =
- (struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
+ const struct ehci_mxs_port *port;
- ret = mxs_ehci_get_port(&ehci_mxs, CONFIG_EHCI_MXS_PORT);
- if (ret)
- return ret;
+ if ((index < 0) || (index >= ARRAY_SIZE(mxs_port))) {
+ printf("Invalid port index (index = %d)!\n", index);
+ return -EINVAL;
+ }
+
+ port = &mxs_port[index];
/* Reset the PHY block */
- writel(USBPHY_CTRL_SFTRST, &ehci_mxs.phy_regs->hw_usbphy_ctrl_set);
+ writel(USBPHY_CTRL_SFTRST, &port->phy_regs->hw_usbphy_ctrl_set);
udelay(10);
writel(USBPHY_CTRL_SFTRST | USBPHY_CTRL_CLKGATE,
- &ehci_mxs.phy_regs->hw_usbphy_ctrl_clr);
+ &port->phy_regs->hw_usbphy_ctrl_clr);
/* Enable USB clock */
- writel(CLKCTRL_PLL0CTRL0_EN_USB_CLKS | CLKCTRL_PLL0CTRL0_POWER,
- &clkctrl_regs->hw_clkctrl_pll0ctrl0_set);
- writel(CLKCTRL_PLL1CTRL0_EN_USB_CLKS | CLKCTRL_PLL1CTRL0_POWER,
- &clkctrl_regs->hw_clkctrl_pll1ctrl0_set);
-
- writel(HW_DIGCTL_CTRL_USB0_CLKGATE | HW_DIGCTL_CTRL_USB1_CLKGATE,
- &digctl_ctrl->reg_clr);
+ ret = ehci_mxs_toggle_clock(port, 1);
+ if (ret)
+ return ret;
/* Start USB PHY */
- writel(0, &ehci_mxs.phy_regs->hw_usbphy_pwd);
+ writel(0, &port->phy_regs->hw_usbphy_pwd);
/* Enable UTMI+ Level 2 and Level 3 compatibility */
writel(USBPHY_CTRL_ENUTMILEVEL3 | USBPHY_CTRL_ENUTMILEVEL2 | 1,
- &ehci_mxs.phy_regs->hw_usbphy_ctrl_set);
+ &port->phy_regs->hw_usbphy_ctrl_set);
- usb_base = ((uint32_t)ehci_mxs.usb_regs) + 0x100;
+ usb_base = port->usb_regs + 0x100;
*hccr = (struct ehci_hccr *)usb_base;
cap_base = ehci_readl(&(*hccr)->cr_capbase);
@@ -118,19 +134,19 @@ int ehci_hcd_stop(int index)
{
int ret;
uint32_t usb_base, cap_base, tmp;
- struct mxs_register_32 *digctl_ctrl =
- (struct mxs_register_32 *)HW_DIGCTL_CTRL;
- struct mxs_clkctrl_regs *clkctrl_regs =
- (struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
struct ehci_hccr *hccr;
struct ehci_hcor *hcor;
+ const struct ehci_mxs_port *port;
- ret = mxs_ehci_get_port(&ehci_mxs, CONFIG_EHCI_MXS_PORT);
- if (ret)
- return ret;
+ if ((index < 0) || (index >= ARRAY_SIZE(mxs_port))) {
+ printf("Invalid port index (index = %d)!\n", index);
+ return -EINVAL;
+ }
+
+ port = &mxs_port[index];
/* Stop the USB port */
- usb_base = ((uint32_t)ehci_mxs.usb_regs) + 0x100;
+ usb_base = port->usb_regs + 0x100;
hccr = (struct ehci_hccr *)usb_base;
cap_base = ehci_readl(&hccr->cr_capbase);
hcor = (struct ehci_hcor *)(usb_base + HC_LENGTH(cap_base));
@@ -144,17 +160,10 @@ int ehci_hcd_stop(int index)
USBPHY_PWD_RXPWD1PT1 | USBPHY_PWD_RXPWDENV |
USBPHY_PWD_TXPWDV2I | USBPHY_PWD_TXPWDIBIAS |
USBPHY_PWD_TXPWDFS;
- writel(tmp, &ehci_mxs.phy_regs->hw_usbphy_pwd);
+ writel(tmp, &port->phy_regs->hw_usbphy_pwd);
/* Disable USB clock */
- writel(CLKCTRL_PLL0CTRL0_EN_USB_CLKS,
- &clkctrl_regs->hw_clkctrl_pll0ctrl0_clr);
- writel(CLKCTRL_PLL1CTRL0_EN_USB_CLKS,
- &clkctrl_regs->hw_clkctrl_pll1ctrl0_clr);
+ ret = ehci_mxs_toggle_clock(port, 0);
- /* Gate off the USB clock */
- writel(HW_DIGCTL_CTRL_USB0_CLKGATE | HW_DIGCTL_CTRL_USB1_CLKGATE,
- &digctl_ctrl->reg_set);
-
- return 0;
+ return ret;
}
diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c
index 29af02dc5..90d7a6feb 100644
--- a/drivers/usb/host/ehci-pci.c
+++ b/drivers/usb/host/ehci-pci.c
@@ -19,6 +19,7 @@
*/
#include <common.h>
+#include <errno.h>
#include <pci.h>
#include <usb.h>
@@ -32,31 +33,76 @@ static struct pci_device_id ehci_pci_ids[] = {
{0x12D8, 0x400F}, /* Pericom */
{0, 0}
};
+#else
+static pci_dev_t ehci_find_class(int index)
+{
+ int bus;
+ int devnum;
+ pci_dev_t bdf;
+ uint32_t class;
+
+ for (bus = 0; bus <= pci_last_busno(); bus++) {
+ for (devnum = 0; devnum < PCI_MAX_PCI_DEVICES-1; devnum++) {
+ pci_read_config_dword(PCI_BDF(bus, devnum, 0),
+ PCI_CLASS_REVISION, &class);
+ if (class >> 16 == 0xffff)
+ continue;
+
+ for (bdf = PCI_BDF(bus, devnum, 0);
+ bdf <= PCI_BDF(bus, devnum,
+ PCI_MAX_PCI_FUNCTIONS - 1);
+ bdf += PCI_BDF(0, 0, 1)) {
+ pci_read_config_dword(bdf, PCI_CLASS_REVISION,
+ &class);
+ if ((class >> 8 == PCI_CLASS_SERIAL_USB_EHCI)
+ && !index--)
+ return bdf;
+ }
+ }
+ }
+
+ return -ENODEV;
+}
#endif
/*
* Create the appropriate control structures to manage
* a new EHCI host controller.
*/
-int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor)
+int ehci_hcd_init(int index, struct ehci_hccr **ret_hccr,
+ struct ehci_hcor **ret_hcor)
{
pci_dev_t pdev;
+ uint32_t cmd;
+ struct ehci_hccr *hccr;
+ struct ehci_hcor *hcor;
+#ifdef CONFIG_PCI_EHCI_DEVICE
pdev = pci_find_devices(ehci_pci_ids, CONFIG_PCI_EHCI_DEVICE);
- if (pdev == -1) {
+#else
+ pdev = ehci_find_class(index);
+#endif
+ if (pdev < 0) {
printf("EHCI host controller not found\n");
return -1;
}
- *hccr = (struct ehci_hccr *)pci_map_bar(pdev,
+ hccr = (struct ehci_hccr *)pci_map_bar(pdev,
PCI_BASE_ADDRESS_0, PCI_REGION_MEM);
- *hcor = (struct ehci_hcor *)((uint32_t) *hccr +
- HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
+ hcor = (struct ehci_hcor *)((uint32_t) hccr +
+ HC_LENGTH(ehci_readl(&hccr->cr_capbase)));
debug("EHCI-PCI init hccr 0x%x and hcor 0x%x hc_length %d\n",
- (uint32_t)*hccr, (uint32_t)*hcor,
- (uint32_t)HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
+ (uint32_t)hccr, (uint32_t)hcor,
+ (uint32_t)HC_LENGTH(ehci_readl(&hccr->cr_capbase)));
+
+ *ret_hccr = hccr;
+ *ret_hcor = hcor;
+ /* enable busmaster */
+ pci_read_config_dword(pdev, PCI_COMMAND, &cmd);
+ cmd |= PCI_COMMAND_MASTER;
+ pci_write_config_dword(pdev, PCI_COMMAND, cmd);
return 0;
}
diff --git a/drivers/usb/host/ehci-spear.c b/drivers/usb/host/ehci-spear.c
new file mode 100644
index 000000000..f99bd1f69
--- /dev/null
+++ b/drivers/usb/host/ehci-spear.c
@@ -0,0 +1,59 @@
+/*
+ * (C) Copyright 2010
+ * Armando Visconti, ST Micoelectronics, <armando.visconti@st.com>.
+ *
+ * (C) Copyright 2009
+ * Marvell Semiconductor <www.marvell.com>
+ * Written-by: Prafulla Wadaskar <prafulla@marvell.com>
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <usb.h>
+#include "ehci.h"
+#include <asm/arch/hardware.h>
+
+
+/*
+ * Create the appropriate control structures to manage
+ * a new EHCI host controller.
+ */
+int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor)
+{
+ *hccr = (struct ehci_hccr *)(CONFIG_SYS_UHC0_EHCI_BASE + 0x100);
+ *hcor = (struct ehci_hcor *)((uint32_t)*hccr
+ + HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
+
+ debug("SPEAr-ehci: init hccr %x and hcor %x hc_length %d\n",
+ (uint32_t)*hccr, (uint32_t)*hcor,
+ (uint32_t)HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
+
+ return 0;
+}
+
+/*
+ * Destroy the appropriate control structures corresponding
+ * the the EHCI host controller.
+ */
+int ehci_hcd_stop(int index)
+{
+ return 0;
+}
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index a1c43f833..554145a25 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -1,5 +1,7 @@
/*
+ * Copyright (c) 2011 The Chromium OS Authors.
* Copyright (c) 2009-2012 NVIDIA Corporation
+ * Copyright (c) 2013 Lucas Stach
*
* See file CREDITS for list of people who contributed to this
* project.
@@ -21,12 +23,128 @@
*/
#include <common.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm-generic/gpio.h>
+#include <asm/arch/clock.h>
+#include <asm/arch-tegra/usb.h>
#include <usb.h>
+#include <usb/ulpi.h>
+#include <libfdt.h>
+#include <fdtdec.h>
#include "ehci.h"
-#include <asm/errno.h>
-#include <asm/arch/usb.h>
+#ifdef CONFIG_USB_ULPI
+ #ifndef CONFIG_USB_ULPI_VIEWPORT
+ #error "To use CONFIG_USB_ULPI on Tegra Boards you have to also \
+ define CONFIG_USB_ULPI_VIEWPORT"
+ #endif
+#endif
+
+enum {
+ USB_PORTS_MAX = 3, /* Maximum ports we allow */
+};
+
+/* Parameters we need for USB */
+enum {
+ PARAM_DIVN, /* PLL FEEDBACK DIVIDer */
+ PARAM_DIVM, /* PLL INPUT DIVIDER */
+ PARAM_DIVP, /* POST DIVIDER (2^N) */
+ PARAM_CPCON, /* BASE PLLC CHARGE Pump setup ctrl */
+ PARAM_LFCON, /* BASE PLLC LOOP FILter setup ctrl */
+ PARAM_ENABLE_DELAY_COUNT, /* PLL-U Enable Delay Count */
+ PARAM_STABLE_COUNT, /* PLL-U STABLE count */
+ PARAM_ACTIVE_DELAY_COUNT, /* PLL-U Active delay count */
+ PARAM_XTAL_FREQ_COUNT, /* PLL-U XTAL frequency count */
+ PARAM_DEBOUNCE_A_TIME, /* 10MS DELAY for BIAS_DEBOUNCE_A */
+ PARAM_BIAS_TIME, /* 20US DELAY AFter bias cell op */
+
+ PARAM_COUNT
+};
+
+/* Possible port types (dual role mode) */
+enum dr_mode {
+ DR_MODE_NONE = 0,
+ DR_MODE_HOST, /* supports host operation */
+ DR_MODE_DEVICE, /* supports device operation */
+ DR_MODE_OTG, /* supports both */
+};
+
+/* Information about a USB port */
+struct fdt_usb {
+ struct usb_ctlr *reg; /* address of registers in physical memory */
+ unsigned utmi:1; /* 1 if port has external tranceiver, else 0 */
+ unsigned ulpi:1; /* 1 if port has external ULPI transceiver */
+ unsigned enabled:1; /* 1 to enable, 0 to disable */
+ unsigned has_legacy_mode:1; /* 1 if this port has legacy mode */
+ unsigned initialized:1; /* has this port already been initialized? */
+ enum dr_mode dr_mode; /* dual role mode */
+ enum periph_id periph_id;/* peripheral id */
+ struct fdt_gpio_state vbus_gpio; /* GPIO for vbus enable */
+ struct fdt_gpio_state phy_reset_gpio; /* GPIO to reset ULPI phy */
+};
+
+static struct fdt_usb port[USB_PORTS_MAX]; /* List of valid USB ports */
+static unsigned port_count; /* Number of available ports */
+
+/*
+ * This table has USB timing parameters for each Oscillator frequency we
+ * support. There are four sets of values:
+ *
+ * 1. PLLU configuration information (reference clock is osc/clk_m and
+ * PLLU-FOs are fixed at 12MHz/60MHz/480MHz).
+ *
+ * Reference frequency 13.0MHz 19.2MHz 12.0MHz 26.0MHz
+ * ----------------------------------------------------------------------
+ * DIVN 960 (0x3c0) 200 (0c8) 960 (3c0h) 960 (3c0)
+ * DIVM 13 (0d) 4 (04) 12 (0c) 26 (1a)
+ * Filter frequency (MHz) 1 4.8 6 2
+ * CPCON 1100b 0011b 1100b 1100b
+ * LFCON0 0 0 0 0
+ *
+ * 2. PLL CONFIGURATION & PARAMETERS for different clock generators:
+ *
+ * Reference frequency 13.0MHz 19.2MHz 12.0MHz 26.0MHz
+ * ---------------------------------------------------------------------------
+ * PLLU_ENABLE_DLY_COUNT 02 (0x02) 03 (03) 02 (02) 04 (04)
+ * PLLU_STABLE_COUNT 51 (33) 75 (4B) 47 (2F) 102 (66)
+ * PLL_ACTIVE_DLY_COUNT 05 (05) 06 (06) 04 (04) 09 (09)
+ * XTAL_FREQ_COUNT 127 (7F) 187 (BB) 118 (76) 254 (FE)
+ *
+ * 3. Debounce values IdDig, Avalid, Bvalid, VbusValid, VbusWakeUp, and
+ * SessEnd. Each of these signals have their own debouncer and for each of
+ * those one out of two debouncing times can be chosen (BIAS_DEBOUNCE_A or
+ * BIAS_DEBOUNCE_B).
+ *
+ * The values of DEBOUNCE_A and DEBOUNCE_B are calculated as follows:
+ * 0xffff -> No debouncing at all
+ * <n> ms = <n> *1000 / (1/19.2MHz) / 4
+ *
+ * So to program a 1 ms debounce for BIAS_DEBOUNCE_A, we have:
+ * BIAS_DEBOUNCE_A[15:0] = 1000 * 19.2 / 4 = 4800 = 0x12c0
+ *
+ * We need to use only DebounceA for BOOTROM. We don't need the DebounceB
+ * values, so we can keep those to default.
+ *
+ * 4. The 20 microsecond delay after bias cell operation.
+ */
+static const unsigned usb_pll[CLOCK_OSC_FREQ_COUNT][PARAM_COUNT] = {
+ /* DivN, DivM, DivP, CPCON, LFCON, Delays Debounce, Bias */
+ { 0x3C0, 0x0D, 0x00, 0xC, 0, 0x02, 0x33, 0x05, 0x7F, 0x7EF4, 5 },
+ { 0x0C8, 0x04, 0x00, 0x3, 0, 0x03, 0x4B, 0x06, 0xBB, 0xBB80, 7 },
+ { 0x3C0, 0x0C, 0x00, 0xC, 0, 0x02, 0x2F, 0x04, 0x76, 0x7530, 5 },
+ { 0x3C0, 0x1A, 0x00, 0xC, 0, 0x04, 0x66, 0x09, 0xFE, 0xFDE8, 9 }
+};
+
+/* UTMIP Idle Wait Delay */
+static const u8 utmip_idle_wait_delay = 17;
+
+/* UTMIP Elastic limit */
+static const u8 utmip_elastic_limit = 16;
+
+/* UTMIP High Speed Sync Start Delay */
+static const u8 utmip_hs_sync_start_delay = 9;
/*
* A known hardware issue where Connect Status Change bit of PORTSC register
@@ -45,32 +163,428 @@ void ehci_powerup_fixup(uint32_t *status_reg, uint32_t *reg)
*reg |= EHCI_PS_CSC;
}
-/*
- * Create the appropriate control structures to manage
- * a new EHCI host controller.
- */
-int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor)
+/* Put the port into host mode */
+static void set_host_mode(struct fdt_usb *config)
+{
+ /*
+ * If we are an OTG port, check if remote host is driving VBus and
+ * bail out in this case.
+ */
+ if (config->dr_mode == DR_MODE_OTG &&
+ (readl(&config->reg->phy_vbus_sensors) & VBUS_VLD_STS))
+ return;
+
+ /*
+ * If not driving, we set the GPIO to enable VBUS. We assume
+ * that the pinmux is set up correctly for this.
+ */
+ if (fdt_gpio_isvalid(&config->vbus_gpio)) {
+ fdtdec_setup_gpio(&config->vbus_gpio);
+ gpio_direction_output(config->vbus_gpio.gpio,
+ (config->vbus_gpio.flags & FDT_GPIO_ACTIVE_LOW) ?
+ 0 : 1);
+ debug("set_host_mode: GPIO %d %s\n", config->vbus_gpio.gpio,
+ (config->vbus_gpio.flags & FDT_GPIO_ACTIVE_LOW) ?
+ "low" : "high");
+ }
+}
+
+void usbf_reset_controller(struct fdt_usb *config, struct usb_ctlr *usbctlr)
+{
+ /* Reset the USB controller with 2us delay */
+ reset_periph(config->periph_id, 2);
+
+ /*
+ * Set USB1_NO_LEGACY_MODE to 1, Registers are accessible under
+ * base address
+ */
+ if (config->has_legacy_mode)
+ setbits_le32(&usbctlr->usb1_legacy_ctrl, USB1_NO_LEGACY_MODE);
+
+ /* Put UTMIP1/3 in reset */
+ setbits_le32(&usbctlr->susp_ctrl, UTMIP_RESET);
+
+ /* Enable the UTMIP PHY */
+ if (config->utmi)
+ setbits_le32(&usbctlr->susp_ctrl, UTMIP_PHY_ENB);
+}
+
+/* set up the UTMI USB controller with the parameters provided */
+static int init_utmi_usb_controller(struct fdt_usb *config)
{
- u32 our_hccr, our_hcor;
+ u32 val;
+ int loop_count;
+ const unsigned *timing;
+ struct usb_ctlr *usbctlr = config->reg;
+
+ clock_enable(config->periph_id);
+
+ /* Reset the usb controller */
+ usbf_reset_controller(config, usbctlr);
+
+ /* Stop crystal clock by setting UTMIP_PHY_XTAL_CLOCKEN low */
+ clrbits_le32(&usbctlr->utmip_misc_cfg1, UTMIP_PHY_XTAL_CLOCKEN);
+
+ /* Follow the crystal clock disable by >100ns delay */
+ udelay(1);
+
+ /*
+ * To Use the A Session Valid for cable detection logic, VBUS_WAKEUP
+ * mux must be switched to actually use a_sess_vld threshold.
+ */
+ if (fdt_gpio_isvalid(&config->vbus_gpio)) {
+ clrsetbits_le32(&usbctlr->usb1_legacy_ctrl,
+ VBUS_SENSE_CTL_MASK,
+ VBUS_SENSE_CTL_A_SESS_VLD << VBUS_SENSE_CTL_SHIFT);
+ }
+
+ /*
+ * PLL Delay CONFIGURATION settings. The following parameters control
+ * the bring up of the plls.
+ */
+ timing = usb_pll[clock_get_osc_freq()];
+
+ val = readl(&usbctlr->utmip_misc_cfg1);
+ clrsetbits_le32(&val, UTMIP_PLLU_STABLE_COUNT_MASK,
+ timing[PARAM_STABLE_COUNT] << UTMIP_PLLU_STABLE_COUNT_SHIFT);
+ clrsetbits_le32(&val, UTMIP_PLL_ACTIVE_DLY_COUNT_MASK,
+ timing[PARAM_ACTIVE_DELAY_COUNT] <<
+ UTMIP_PLL_ACTIVE_DLY_COUNT_SHIFT);
+ writel(val, &usbctlr->utmip_misc_cfg1);
+
+ /* Set PLL enable delay count and crystal frequency count */
+ val = readl(&usbctlr->utmip_pll_cfg1);
+ clrsetbits_le32(&val, UTMIP_PLLU_ENABLE_DLY_COUNT_MASK,
+ timing[PARAM_ENABLE_DELAY_COUNT] <<
+ UTMIP_PLLU_ENABLE_DLY_COUNT_SHIFT);
+ clrsetbits_le32(&val, UTMIP_XTAL_FREQ_COUNT_MASK,
+ timing[PARAM_XTAL_FREQ_COUNT] <<
+ UTMIP_XTAL_FREQ_COUNT_SHIFT);
+ writel(val, &usbctlr->utmip_pll_cfg1);
+
+ /* Setting the tracking length time */
+ clrsetbits_le32(&usbctlr->utmip_bias_cfg1,
+ UTMIP_BIAS_PDTRK_COUNT_MASK,
+ timing[PARAM_BIAS_TIME] << UTMIP_BIAS_PDTRK_COUNT_SHIFT);
+
+ /* Program debounce time for VBUS to become valid */
+ clrsetbits_le32(&usbctlr->utmip_debounce_cfg0,
+ UTMIP_DEBOUNCE_CFG0_MASK,
+ timing[PARAM_DEBOUNCE_A_TIME] << UTMIP_DEBOUNCE_CFG0_SHIFT);
+
+ setbits_le32(&usbctlr->utmip_tx_cfg0, UTMIP_FS_PREAMBLE_J);
+
+ /* Disable battery charge enabling bit */
+ setbits_le32(&usbctlr->utmip_bat_chrg_cfg0, UTMIP_PD_CHRG);
+
+ clrbits_le32(&usbctlr->utmip_xcvr_cfg0, UTMIP_XCVR_LSBIAS_SE);
+ setbits_le32(&usbctlr->utmip_spare_cfg0, FUSE_SETUP_SEL);
/*
- * Select the first port, as we don't have a way of selecting others
- * yet
+ * Configure the UTMIP_IDLE_WAIT and UTMIP_ELASTIC_LIMIT
+ * Setting these fields, together with default values of the
+ * other fields, results in programming the registers below as
+ * follows:
+ * UTMIP_HSRX_CFG0 = 0x9168c000
+ * UTMIP_HSRX_CFG1 = 0x13
*/
- if (tegrausb_start_port(index, &our_hccr, &our_hcor))
+
+ /* Set PLL enable delay count and Crystal frequency count */
+ val = readl(&usbctlr->utmip_hsrx_cfg0);
+ clrsetbits_le32(&val, UTMIP_IDLE_WAIT_MASK,
+ utmip_idle_wait_delay << UTMIP_IDLE_WAIT_SHIFT);
+ clrsetbits_le32(&val, UTMIP_ELASTIC_LIMIT_MASK,
+ utmip_elastic_limit << UTMIP_ELASTIC_LIMIT_SHIFT);
+ writel(val, &usbctlr->utmip_hsrx_cfg0);
+
+ /* Configure the UTMIP_HS_SYNC_START_DLY */
+ clrsetbits_le32(&usbctlr->utmip_hsrx_cfg1,
+ UTMIP_HS_SYNC_START_DLY_MASK,
+ utmip_hs_sync_start_delay << UTMIP_HS_SYNC_START_DLY_SHIFT);
+
+ /* Preceed the crystal clock disable by >100ns delay. */
+ udelay(1);
+
+ /* Resuscitate crystal clock by setting UTMIP_PHY_XTAL_CLOCKEN */
+ setbits_le32(&usbctlr->utmip_misc_cfg1, UTMIP_PHY_XTAL_CLOCKEN);
+
+ /* Finished the per-controller init. */
+
+ /* De-assert UTMIP_RESET to bring out of reset. */
+ clrbits_le32(&usbctlr->susp_ctrl, UTMIP_RESET);
+
+ /* Wait for the phy clock to become valid in 100 ms */
+ for (loop_count = 100000; loop_count != 0; loop_count--) {
+ if (readl(&usbctlr->susp_ctrl) & USB_PHY_CLK_VALID)
+ break;
+ udelay(1);
+ }
+ if (!loop_count)
+ return -1;
+
+ /* Disable ICUSB FS/LS transceiver */
+ clrbits_le32(&usbctlr->icusb_ctrl, IC_ENB1);
+
+ /* Select UTMI parallel interface */
+ clrsetbits_le32(&usbctlr->port_sc1, PTS_MASK,
+ PTS_UTMI << PTS_SHIFT);
+ clrbits_le32(&usbctlr->port_sc1, STS);
+
+ /* Deassert power down state */
+ clrbits_le32(&usbctlr->utmip_xcvr_cfg0, UTMIP_FORCE_PD_POWERDOWN |
+ UTMIP_FORCE_PD2_POWERDOWN | UTMIP_FORCE_PDZI_POWERDOWN);
+ clrbits_le32(&usbctlr->utmip_xcvr_cfg1, UTMIP_FORCE_PDDISC_POWERDOWN |
+ UTMIP_FORCE_PDCHRP_POWERDOWN | UTMIP_FORCE_PDDR_POWERDOWN);
+
+ return 0;
+}
+
+#ifdef CONFIG_USB_ULPI
+/* if board file does not set a ULPI reference frequency we default to 24MHz */
+#ifndef CONFIG_ULPI_REF_CLK
+#define CONFIG_ULPI_REF_CLK 24000000
+#endif
+
+/* set up the ULPI USB controller with the parameters provided */
+static int init_ulpi_usb_controller(struct fdt_usb *config)
+{
+ u32 val;
+ int loop_count;
+ struct ulpi_viewport ulpi_vp;
+ struct usb_ctlr *usbctlr = config->reg;
+
+ /* set up ULPI reference clock on pllp_out4 */
+ clock_enable(PERIPH_ID_DEV2_OUT);
+ clock_set_pllout(CLOCK_ID_PERIPH, PLL_OUT4, CONFIG_ULPI_REF_CLK);
+
+ /* reset ULPI phy */
+ if (fdt_gpio_isvalid(&config->phy_reset_gpio)) {
+ fdtdec_setup_gpio(&config->phy_reset_gpio);
+ gpio_direction_output(config->phy_reset_gpio.gpio, 0);
+ mdelay(5);
+ gpio_set_value(config->phy_reset_gpio.gpio, 1);
+ }
+
+ /* Reset the usb controller */
+ clock_enable(config->periph_id);
+ usbf_reset_controller(config, usbctlr);
+
+ /* enable pinmux bypass */
+ setbits_le32(&usbctlr->ulpi_timing_ctrl_0,
+ ULPI_CLKOUT_PINMUX_BYP | ULPI_OUTPUT_PINMUX_BYP);
+
+ /* Select ULPI parallel interface */
+ clrsetbits_le32(&usbctlr->port_sc1, PTS_MASK, PTS_ULPI << PTS_SHIFT);
+
+ /* enable ULPI transceiver */
+ setbits_le32(&usbctlr->susp_ctrl, ULPI_PHY_ENB);
+
+ /* configure ULPI transceiver timings */
+ val = 0;
+ writel(val, &usbctlr->ulpi_timing_ctrl_1);
+
+ val |= ULPI_DATA_TRIMMER_SEL(4);
+ val |= ULPI_STPDIRNXT_TRIMMER_SEL(4);
+ val |= ULPI_DIR_TRIMMER_SEL(4);
+ writel(val, &usbctlr->ulpi_timing_ctrl_1);
+ udelay(10);
+
+ val |= ULPI_DATA_TRIMMER_LOAD;
+ val |= ULPI_STPDIRNXT_TRIMMER_LOAD;
+ val |= ULPI_DIR_TRIMMER_LOAD;
+ writel(val, &usbctlr->ulpi_timing_ctrl_1);
+
+ /* set up phy for host operation with external vbus supply */
+ ulpi_vp.port_num = 0;
+ ulpi_vp.viewport_addr = (u32)&usbctlr->ulpi_viewport;
+
+ if (ulpi_init(&ulpi_vp)) {
+ printf("Tegra ULPI viewport init failed\n");
+ return -1;
+ }
+
+ ulpi_set_vbus(&ulpi_vp, 1, 1);
+ ulpi_set_vbus_indicator(&ulpi_vp, 1, 1, 0);
+
+ /* enable wakeup events */
+ setbits_le32(&usbctlr->port_sc1, WKCN | WKDS | WKOC);
+
+ /* Enable and wait for the phy clock to become valid in 100 ms */
+ setbits_le32(&usbctlr->susp_ctrl, USB_SUSP_CLR);
+ for (loop_count = 100000; loop_count != 0; loop_count--) {
+ if (readl(&usbctlr->susp_ctrl) & USB_PHY_CLK_VALID)
+ break;
+ udelay(1);
+ }
+ if (!loop_count)
+ return -1;
+ clrbits_le32(&usbctlr->susp_ctrl, USB_SUSP_CLR);
+
+ return 0;
+}
+#else
+static int init_ulpi_usb_controller(struct fdt_usb *config)
+{
+ printf("No code to set up ULPI controller, please enable"
+ "CONFIG_USB_ULPI and CONFIG_USB_ULPI_VIEWPORT");
+ return -1;
+}
+#endif
+
+static void config_clock(const u32 timing[])
+{
+ clock_start_pll(CLOCK_ID_USB,
+ timing[PARAM_DIVM], timing[PARAM_DIVN], timing[PARAM_DIVP],
+ timing[PARAM_CPCON], timing[PARAM_LFCON]);
+}
+
+int fdt_decode_usb(const void *blob, int node, struct fdt_usb *config)
+{
+ const char *phy, *mode;
+
+ config->reg = (struct usb_ctlr *)fdtdec_get_addr(blob, node, "reg");
+ mode = fdt_getprop(blob, node, "dr_mode", NULL);
+ if (mode) {
+ if (0 == strcmp(mode, "host"))
+ config->dr_mode = DR_MODE_HOST;
+ else if (0 == strcmp(mode, "peripheral"))
+ config->dr_mode = DR_MODE_DEVICE;
+ else if (0 == strcmp(mode, "otg"))
+ config->dr_mode = DR_MODE_OTG;
+ else {
+ debug("%s: Cannot decode dr_mode '%s'\n", __func__,
+ mode);
+ return -FDT_ERR_NOTFOUND;
+ }
+ } else {
+ config->dr_mode = DR_MODE_HOST;
+ }
+
+ phy = fdt_getprop(blob, node, "phy_type", NULL);
+ config->utmi = phy && 0 == strcmp("utmi", phy);
+ config->ulpi = phy && 0 == strcmp("ulpi", phy);
+ config->enabled = fdtdec_get_is_enabled(blob, node);
+ config->has_legacy_mode = fdtdec_get_bool(blob, node,
+ "nvidia,has-legacy-mode");
+ config->periph_id = clock_decode_periph_id(blob, node);
+ if (config->periph_id == PERIPH_ID_NONE) {
+ debug("%s: Missing/invalid peripheral ID\n", __func__);
+ return -FDT_ERR_NOTFOUND;
+ }
+ fdtdec_decode_gpio(blob, node, "nvidia,vbus-gpio", &config->vbus_gpio);
+ fdtdec_decode_gpio(blob, node, "nvidia,phy-reset-gpio",
+ &config->phy_reset_gpio);
+ debug("enabled=%d, legacy_mode=%d, utmi=%d, ulpi=%d, periph_id=%d, "
+ "vbus=%d, phy_reset=%d, dr_mode=%d\n",
+ config->enabled, config->has_legacy_mode, config->utmi,
+ config->ulpi, config->periph_id, config->vbus_gpio.gpio,
+ config->phy_reset_gpio.gpio, config->dr_mode);
+
+ return 0;
+}
+
+int board_usb_init(const void *blob)
+{
+ struct fdt_usb config;
+ enum clock_osc_freq freq;
+ int node_list[USB_PORTS_MAX];
+ int node, count, i;
+
+ /* Set up the USB clocks correctly based on our oscillator frequency */
+ freq = clock_get_osc_freq();
+ config_clock(usb_pll[freq]);
+
+ /* count may return <0 on error */
+ count = fdtdec_find_aliases_for_id(blob, "usb",
+ COMPAT_NVIDIA_TEGRA20_USB, node_list, USB_PORTS_MAX);
+ for (i = 0; i < count; i++) {
+ if (port_count == USB_PORTS_MAX) {
+ printf("tegrausb: Cannot register more than %d ports\n",
+ USB_PORTS_MAX);
+ return -1;
+ }
+
+ debug("USB %d: ", i);
+ node = node_list[i];
+ if (!node)
+ continue;
+ if (fdt_decode_usb(blob, node, &config)) {
+ debug("Cannot decode USB node %s\n",
+ fdt_get_name(blob, node, NULL));
+ return -1;
+ }
+ config.initialized = 0;
+
+ /* add new USB port to the list of available ports */
+ port[port_count++] = config;
+ }
+
+ return 0;
+}
+
+/**
+ * Start up the given port number (ports are numbered from 0 on each board).
+ * This returns values for the appropriate hccr and hcor addresses to use for
+ * USB EHCI operations.
+ *
+ * @param index port number to start
+ * @param hccr returns start address of EHCI HCCR registers
+ * @param hcor returns start address of EHCI HCOR registers
+ * @return 0 if ok, -1 on error (generally invalid port number)
+ */
+int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor)
+{
+ struct fdt_usb *config;
+ struct usb_ctlr *usbctlr;
+
+ if (index >= port_count)
return -1;
- *hccr = (struct ehci_hccr *)our_hccr;
- *hcor = (struct ehci_hcor *)our_hcor;
+ config = &port[index];
+
+ /* skip init, if the port is already initialized */
+ if (config->initialized)
+ goto success;
+
+ if (config->utmi && init_utmi_usb_controller(config)) {
+ printf("tegrausb: Cannot init port %d\n", index);
+ return -1;
+ }
+
+ if (config->ulpi && init_ulpi_usb_controller(config)) {
+ printf("tegrausb: Cannot init port %d\n", index);
+ return -1;
+ }
+ set_host_mode(config);
+
+ config->initialized = 1;
+
+success:
+ usbctlr = config->reg;
+ *hccr = (struct ehci_hccr *)&usbctlr->cap_length;
+ *hcor = (struct ehci_hcor *)&usbctlr->usb_cmd;
return 0;
}
/*
- * Destroy the appropriate control structures corresponding
- * the the EHCI host controller.
+ * Bring down the specified USB controller
*/
int ehci_hcd_stop(int index)
{
- return tegrausb_stop_port(index);
+ struct usb_ctlr *usbctlr;
+
+ usbctlr = port[index].reg;
+
+ /* Stop controller */
+ writel(0, &usbctlr->usb_cmd);
+ udelay(1000);
+
+ /* Initiate controller reset */
+ writel(2, &usbctlr->usb_cmd);
+ udelay(1000);
+
+ port[index].initialized = 0;
+
+ return 0;
}
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 1e3cd793b..d090f0a53 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -69,6 +69,7 @@ struct ehci_hcor {
#define CMD_RUN (1 << 0) /* start/stop HC */
uint32_t or_usbsts;
#define STS_ASS (1 << 15)
+#define STS_PSS (1 << 14)
#define STS_HALT (1 << 12)
uint32_t or_usbintr;
#define INTR_UE (1 << 0) /* USB interrupt enable */
@@ -245,7 +246,10 @@ struct QH {
* Add dummy fill value to make the size of this struct
* aligned to 32 bytes
*/
- uint8_t fill[16];
+ union {
+ uint32_t fill[4];
+ void *buffer;
+ };
};
/* Low level init functions */