]> git.sur5r.net Git - u-boot/blobdiff - drivers/usb/host/ehci-hcd.c
Merge branch 'u-boot-samsung/master' into 'u-boot-arm/master'
[u-boot] / drivers / usb / host / ehci-hcd.c
index 20309adfb075d244fd077e83085b0112b79131cc..c8168782069a3ded3c053c16b04ec0c0c7c9a6ba 100644 (file)
  * 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;
@@ -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;
 }