diff options
author | Patrick Georgi <patrick@georgi-clan.de> | 2011-12-26 11:24:40 +0100 |
---|---|---|
committer | Gerrit <chrome-bot@google.com> | 2012-01-04 12:14:25 -0800 |
commit | d83fe0bf0e0cc417579ade635c6ea212a95f40a7 (patch) | |
tree | b2c0d0a34d8b5c465c31ee88613593303841f881 /drivers | |
parent | a371cd8fe159132bdd9bf08b8b9a4a5f08b11387 (diff) |
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.
BUG=chrome-os-partner:5752
TEST=Use USB keyboard in u-boot (recovery mode) when later
commits make use of this code
Change-Id: I9bf0ef838af1076f8060c39bd942d95271cf0035
Signed-off-by: Patrick Georgi <patrick@georgi-clan.de>
Reviewed-on: https://gerrit.chromium.org/gerrit/13489
Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
Tested-by: Vincent Palatin <vpalatin@chromium.org>
Commit-Ready: Vincent Palatin <vpalatin@chromium.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 277 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 1 |
2 files changed, 277 insertions, 1 deletions
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 0ec89dbc7dc..559b19da0c7 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -41,6 +41,8 @@ static struct ehci_ctrl { struct QH qh_list __attribute__((aligned(32))); struct QH qh_pool __attribute__((aligned(32))); struct qTD td_pool[3] __attribute__((aligned (32))); + struct QH periodic_queue __attribute__((aligned(32))); + uint32_t *periodic_list; int ntds; } ehcic[CONFIG_USB_MAX_CONTROLLER_COUNT]; @@ -872,6 +874,7 @@ void *usb_lowlevel_init(int index) struct ehci_hccr *hccr; volatile struct ehci_hcor *hcor; struct QH *qh_list; + struct QH *periodic; if (ehci_hcd_init(index, &hccr, (struct ehci_hcor **)&hcor) != 0) return NULL; @@ -904,6 +907,32 @@ void *usb_lowlevel_init(int index) /* Set async. queue head pointer. */ ehci_writel(&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); + int i; + 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(&hcor->or_periodiclistbase, + (uint32_t)ehcic[index].periodic_list); + reg = ehci_readl(&hccr->cr_hcsparams); descriptor.hub.bNbrPorts = HCS_N_PORTS(reg); printf("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts); @@ -970,14 +999,260 @@ 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; + volatile struct ehci_hcor *hcor = ctrl->hcor; + cmd = ehci_readl(&hcor->or_usbcmd); + cmd |= CMD_PSE; + ehci_writel(&hcor->or_usbcmd, cmd); + + int ret = handshake((uint32_t *)&hcor->or_usbsts, + STD_PSS, STD_PSS, 100 * 1000); + if (ret < 0) { + printf("EHCI failed: timeout when enabling periodic list\n"); + return -1; + } + udelay(1000); + return 0; +} + +static int +disable_periodic(struct ehci_ctrl *ctrl) +{ + uint32_t cmd; + volatile struct ehci_hcor *hcor = ctrl->hcor; + cmd = ehci_readl(&hcor->or_usbcmd); + cmd &= ~CMD_PSE; + ehci_writel(&hcor->or_usbcmd, cmd); + + int ret = handshake((uint32_t *)&hcor->or_usbsts, + STD_PSS, 0, 100 * 1000); + if (ret < 0) { + printf("EHCI failed: timeout when enabling periodic list\n"); + return -1; + } + return 0; +} + +int periodic_schedules; + +struct int_queue * +create_int_queue(struct usb_device *dev, unsigned long pipe, int queuesize, + int elementsize, void *buffer) +{ + int i; + struct ehci_ctrl *ctrl = dev->controller; + struct int_queue *result = NULL; + 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 + elementsize - 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 = (void **)qh->fill; + + 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) | /* TODO: Data Toggle Control */ + (usb_pipespeed(pipe) << 12) | /* EPS */ + (usb_pipeendpoint(pipe) << 8) | /* Endpoint Number */ + (usb_pipedevice(pipe) << 0); + qh->qh_endpt2 = (1 << 30); /* 1 Tx per mframe */ + if (usb_pipespeed(pipe) < 2) { /* full or low speed */ + 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 */ + (1 << 0); /* S-mask: microframe 0 */ + } + + 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; +} + +/* TODO: requeue depleted buffers */ +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; + } + /* TODO: Handle failures */ + 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 *(void **)cur->fill; +} + +/* 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; + + if (disable_periodic(ctrl) < 0) { + debug("FATAL: periodic should never fail, but did"); + goto out; + } + periodic_schedules--; + + struct QH *cur = &ctrl->periodic_queue; + 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; + goto out; + } + cur = NEXT_QH(cur); + } + + if (periodic_schedules > 0) + if (enable_periodic(ctrl) < 0) { + debug("FATAL: periodic should never fail, but did"); + result = -1; + } + +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; debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d", dev, pipe, buffer, length, interval); - return ehci_submit_async(dev, pipe, buffer, length, NULL); + + queue = create_int_queue(dev, pipe, 1, length, buffer); + + /* TODO: pick some useful timeout rule */ + while ((backbuffer = poll_int_queue(dev, queue)) == NULL) + ; + + if (backbuffer != buffer) { + debug("got wrong buffer back (%x instead of %x)\n", + (uint32_t)backbuffer, (uint32_t)buffer); + return -1; + } + + if (destroy_int_queue(dev, queue) == -1) + return -1; + + /* everything worked out fine */ + return 0; } #ifdef CONFIG_SYS_USB_EVENT_POLL diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 2aa5c25a37d..0b7bbbf2c8a 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 STD_ASS (1 << 15) +#define STD_PSS (1 << 14) #define STS_HALT (1 << 12) uint32_t or_usbintr; #define INTR_UE (1 << 0) /* USB interrupt enable */ |