From 7c38e90aff5f5b8b7f7ebc27d8d07c51aececde3 Mon Sep 17 00:00:00 2001 From: Vincent Palatin Date: Thu, 13 Dec 2012 22:58:27 -0800 Subject: usb: ehci: generic PCI support Instead of hardcoding the PCI IDs on the USB controller, use the PCI class to detect them. Ensure the busmaster bit is properly set in the PCI configuration. Signed-off-by: Simon Glass --- drivers/usb/host/ehci-pci.c | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 29af02d..001d141 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -19,6 +19,7 @@ */ #include +#include #include #include @@ -32,6 +33,35 @@ static struct pci_device_id ehci_pci_ids[] = { {0x12D8, 0x400F}, /* Pericom */ {0, 0} }; +#else +static pci_dev_t ehci_find_class(void) +{ + 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) + return bdf; + } + } + } + + return -ENODEV; +} #endif /* @@ -41,9 +71,14 @@ static struct pci_device_id ehci_pci_ids[] = { int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor) { pci_dev_t pdev; + uint32_t cmd; +#ifdef CONFIG_PCI_EHCI_DEVICE pdev = pci_find_devices(ehci_pci_ids, CONFIG_PCI_EHCI_DEVICE); - if (pdev == -1) { +#else + pdev = ehci_find_class(); +#endif + if (pdev < 0) { printf("EHCI host controller not found\n"); return -1; } @@ -57,6 +92,10 @@ 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))); + /* enable busmaster */ + pci_read_config_dword(pdev, PCI_COMMAND, &cmd); + cmd |= PCI_COMMAND_MASTER; + pci_write_config_dword(pdev, PCI_COMMAND, cmd); return 0; } -- cgit v1.1 From 2982837e36e56584b0ab0256dd82d306e4ba1249 Mon Sep 17 00:00:00 2001 From: Vincent Palatin Date: Wed, 12 Dec 2012 17:55:22 -0800 Subject: usb: ehci: Add 64-bit controller support On EHCI controller with 64-bit address space support, we must initialize properly the high word for the PCI bus master accesses. Signed-off-by: Vincent Palatin Signed-off-by: Simon Glass --- drivers/usb/host/ehci-hcd.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 7f98a63..20309ad 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -870,6 +870,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; -- cgit v1.1 From 39fd6342a431abc0de786936eab87e731ba29cb7 Mon Sep 17 00:00:00 2001 From: Vipin Kumar Date: Mon, 17 Dec 2012 14:18:55 +0530 Subject: usb/host/ehci: Add support for EHCI on spear Add EHCI support for spear boards Signed-off-by: Armando Visconti Signed-off-by: Vipin Kumar --- drivers/usb/host/Makefile | 1 + drivers/usb/host/ehci-spear.c | 59 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 drivers/usb/host/ehci-spear.c (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 6c94794..9a6f982 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-spear.c b/drivers/usb/host/ehci-spear.c new file mode 100644 index 0000000..f99bd1f --- /dev/null +++ b/drivers/usb/host/ehci-spear.c @@ -0,0 +1,59 @@ +/* + * (C) Copyright 2010 + * Armando Visconti, ST Micoelectronics, . + * + * (C) Copyright 2009 + * Marvell Semiconductor + * Written-by: Prafulla Wadaskar + * + * 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 +#include +#include +#include "ehci.h" +#include + + +/* + * 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; +} -- cgit v1.1 From 24a4775f91b188e7a1e19dff4b0063bd8fc900c8 Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Wed, 6 Mar 2013 14:18:32 +0530 Subject: usb: ehci: exynos: Fix multiple FDT decode With current FDT support driver tries to parse device node twice in ehci_hcd_init() and ehci_hcd_stop(), which shouldn't happen ideally. Making provision to store data in a global structure and thereby passing its pointer when needed. Signed-off-by: Vivek Gautam --- drivers/usb/host/ehci-exynos.c | 44 +++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-exynos.c b/drivers/usb/host/ehci-exynos.c index 3ca4c5c..c4c90be 100644 --- a/drivers/usb/host/ehci-exynos.c +++ b/drivers/usb/host/ehci-exynos.c @@ -42,11 +42,14 @@ DECLARE_GLOBAL_DATA_PTR; */ struct exynos_ehci { struct exynos_usb_phy *usb; - unsigned int *hcd; + struct ehci_hccr *hcd; }; +static struct exynos_ehci exynos; + static int exynos_usb_parse_dt(const void *blob, struct exynos_ehci *exynos) { + fdt_addr_t addr; unsigned int node; int depth; @@ -59,12 +62,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); @@ -144,20 +149,16 @@ 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; + if (exynos_usb_parse_dt(gd->fdt_blob, ctx)) { + debug("Unable to parse device tree for ehci-exynos\n"); + return -ENODEV; } - 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 +166,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 +175,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; } -- cgit v1.1 From c74b0116b6cd839babfc57b0eb83e4e989227f17 Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Wed, 6 Mar 2013 14:18:33 +0530 Subject: usb: ehci: exynos: Enable non-dt path Enabling the non-dt path for the driver so that we don't get any build errors for non-dt configuration. Signed-off-by: Vivek Gautam Acked-by: Simon Glass --- drivers/usb/host/ehci-exynos.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-exynos.c b/drivers/usb/host/ehci-exynos.c index c4c90be..0c797aa 100644 --- a/drivers/usb/host/ehci-exynos.c +++ b/drivers/usb/host/ehci-exynos.c @@ -47,6 +47,7 @@ struct exynos_ehci { 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; @@ -90,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) @@ -151,10 +153,15 @@ int ehci_hcd_init(int index, struct ehci_hccr **hccr, struct ehci_hcor **hcor) { struct exynos_ehci *ctx = &exynos; +#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 setup_usb_phy(ctx->usb); -- cgit v1.1 From 8f62ca646fbad26b17f832cc3c2579080247de6e Mon Sep 17 00:00:00 2001 From: Patrick Georgi Date: Wed, 6 Mar 2013 14:08:31 +0000 Subject: usb: ehci: Support interrupt transfers via periodic list Interrupt transfers aren't meant to be used from the async list (the EHCI spec indicates trouble with low/full-speed intr on async). Build a periodic list instead, and provide an API to make use of it. Then, use that API from the existing interrupt transfer API. This provides support for USB keyboards using EHCI. Use timeouts to ensure we cannot get stuck in the keyboard scanning if something wrong happens (USB device unplugged or fatal I/O error) Signed-off-by: Vincent Palatin Signed-off-by: Julius Werner Signed-off-by: Simon Glass --- drivers/usb/host/ehci-hcd.c | 315 +++++++++++++++++++++++++++++++++++++++++++- drivers/usb/host/ehci.h | 6 +- 2 files changed, 316 insertions(+), 5 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 20309ad..c816878 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -21,12 +21,14 @@ * MA 02111-1307 USA */ #include +#include #include #include #include #include #include #include +#include #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; @@ -887,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); @@ -956,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); @@ -975,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.h b/drivers/usb/host/ehci.h index 1e3cd79..46b535f 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 { + uint8_t fill[16]; + void *buffer; + }; }; /* Low level init functions */ -- cgit v1.1 From 61755c7908be54a8221280cc801e65bc9d91dfad Mon Sep 17 00:00:00 2001 From: Vincent Palatin Date: Wed, 6 Mar 2013 14:08:32 +0000 Subject: usb: ehci: Fix aliasing issue in EHCI interrupt code The interrupt endpoint handling code stores the buffer pointer in the QH padding field. We need to make it the size of a pointer to avoid strict aliasing issue with the compiler. Signed-off-by: Vincent Palatin Signed-off-by: Simon Glass --- drivers/usb/host/ehci.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 46b535f..d090f0a 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -247,7 +247,7 @@ struct QH { * aligned to 32 bytes */ union { - uint8_t fill[16]; + uint32_t fill[4]; void *buffer; }; }; -- cgit v1.1 From ae003d057077d792c1f2131753a7596c94e0bba4 Mon Sep 17 00:00:00 2001 From: Vincent Palatin Date: Tue, 12 Mar 2013 03:45:31 +0000 Subject: usb: Add multiple controllers support for EHCI PCI Use the ability to have several active EHCI controller on a system in the PCI EHCI controller implementation. Signed-off-by: Simon Glass --- drivers/usb/host/ehci-pci.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 001d141..90d7a6f 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -34,7 +34,7 @@ static struct pci_device_id ehci_pci_ids[] = { {0, 0} }; #else -static pci_dev_t ehci_find_class(void) +static pci_dev_t ehci_find_class(int index) { int bus; int devnum; @@ -54,7 +54,8 @@ static pci_dev_t ehci_find_class(void) bdf += PCI_BDF(0, 0, 1)) { pci_read_config_dword(bdf, PCI_CLASS_REVISION, &class); - if (class >> 8 == PCI_CLASS_SERIAL_USB_EHCI) + if ((class >> 8 == PCI_CLASS_SERIAL_USB_EHCI) + && !index--) return bdf; } } @@ -68,29 +69,35 @@ static pci_dev_t ehci_find_class(void) * 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); #else - pdev = ehci_find_class(); + 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); -- cgit v1.1