Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / SLOF / lib / libusb / usb-ehci.c
diff --git a/qemu/roms/SLOF/lib/libusb/usb-ehci.c b/qemu/roms/SLOF/lib/libusb/usb-ehci.c
new file mode 100644 (file)
index 0000000..4cca0da
--- /dev/null
@@ -0,0 +1,609 @@
+/*****************************************************************************
+ * Copyright (c) 2013 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials
+ * are made available under the terms of the BSD License
+ * which accompanies this distribution, and is available at
+ * http://www.opensource.org/licenses/bsd-license.php
+ *
+ * Contributors:
+ *     IBM Corporation - initial implementation
+ *****************************************************************************/
+
+#include <string.h>
+#include "usb.h"
+#include "usb-core.h"
+#include "usb-ehci.h"
+#include "tools.h"
+#include "paflof.h"
+
+#undef EHCI_DEBUG
+//#define EHCI_DEBUG
+#ifdef EHCI_DEBUG
+#define dprintf(_x ...) do { printf(_x); } while(0)
+#else
+#define dprintf(_x ...)
+#endif
+
+#ifdef EHCI_DEBUG
+static void dump_ehci_regs(struct ehci_hcd *ehcd)
+{
+       struct ehci_cap_regs *cap_regs;
+       struct ehci_op_regs *op_regs;
+
+       cap_regs = ehcd->cap_regs;
+       op_regs = ehcd->op_regs;
+
+       dprintf("\n - CAPLENGTH           %02X", read_reg8(&cap_regs->caplength));
+       dprintf("\n - HCIVERSION          %04X", read_reg16(&cap_regs->hciversion));
+       dprintf("\n - HCSPARAMS           %08X", read_reg32(&cap_regs->hcsparams));
+       dprintf("\n - HCCPARAMS           %08X", read_reg32(&cap_regs->hccparams));
+       dprintf("\n - HCSP_PORTROUTE      %016llX", read_reg64(&cap_regs->portroute));
+       dprintf("\n");
+
+       dprintf("\n - USBCMD              %08X", read_reg32(&op_regs->usbcmd));
+       dprintf("\n - USBSTS              %08X", read_reg32(&op_regs->usbsts));
+       dprintf("\n - USBINTR             %08X", read_reg32(&op_regs->usbintr));
+       dprintf("\n - FRINDEX             %08X", read_reg32(&op_regs->frindex));
+       dprintf("\n - CTRLDSSEGMENT       %08X", read_reg32(&op_regs->ctrldssegment));
+       dprintf("\n - PERIODICLISTBASE    %08X", read_reg32(&op_regs->periodiclistbase));
+       dprintf("\n - ASYNCLISTADDR       %08X", read_reg32(&op_regs->asynclistaddr));
+       dprintf("\n - CONFIGFLAG          %08X", read_reg32(&op_regs->configflag));
+       dprintf("\n - PORTSC              %08X", read_reg32(&op_regs->portsc[0]));
+       dprintf("\n");
+}
+#endif
+
+static int ehci_hub_check_ports(struct ehci_hcd *ehcd)
+{
+       uint32_t num_ports, portsc, i;
+       struct usb_dev *dev;
+
+       dprintf("%s: enter\n", __func__);
+       num_ports = read_reg32(&ehcd->cap_regs->hcsparams) & HCS_NPORTS_MASK;
+       for (i = 0; i < num_ports; i++) {
+               dprintf("%s: device %d\n", __func__, i);
+               portsc = read_reg32(&ehcd->op_regs->portsc[i]);
+               if (portsc & PORT_CONNECT) { /* Device present */
+                       dprintf("usb-ehci: Device present on port %d\n", i);
+                       /* Reset the port */
+                       portsc = read_reg32(&ehcd->op_regs->portsc[i]);
+                       portsc = (portsc & ~PORT_PE) | PORT_RESET;
+                       write_reg32(&ehcd->op_regs->portsc[i], portsc);
+                       SLOF_msleep(20);
+                       portsc = read_reg32(&ehcd->op_regs->portsc[i]);
+                       portsc &= ~PORT_RESET;
+                       write_reg32(&ehcd->op_regs->portsc[i], portsc);
+                       SLOF_msleep(20);
+                       dev = usb_devpool_get();
+                       dprintf("usb-ehci: allocated device %p\n", dev);
+                       dev->hcidev = ehcd->hcidev;
+                       dev->speed = USB_HIGH_SPEED; /* TODO: Check for Low/Full speed device */
+                       if (!setup_new_device(dev, i))
+                               printf("usb-ehci: unable to setup device on port %d\n", i);
+               }
+       }
+       dprintf("%s: exit\n", __func__);
+       return 0;
+}
+
+static int ehci_hcd_init(struct ehci_hcd *ehcd)
+{
+       uint32_t usbcmd;
+       uint32_t time;
+       struct ehci_framelist *fl;
+       struct ehci_qh *qh_intr, *qh_async;
+       int i;
+       long fl_phys = 0, qh_intr_phys = 0, qh_async_phys;
+
+       /* Reset the host controller */
+       time = SLOF_GetTimer() + 250;
+       usbcmd = read_reg32(&ehcd->op_regs->usbcmd);
+       write_reg32(&ehcd->op_regs->usbcmd, (usbcmd & ~(CMD_PSE | CMD_ASE)) | CMD_HCRESET);
+       while (time > SLOF_GetTimer())
+               cpu_relax();
+       usbcmd = read_reg32(&ehcd->op_regs->usbcmd);
+       if (usbcmd & CMD_HCRESET) {
+               printf("usb-ehci: reset failed\n");
+               return -1;
+       }
+
+       /* Initialize periodic list */
+       fl = SLOF_dma_alloc(sizeof(*fl));
+       if (!fl) {
+               printf("usb-ehci: Unable to allocate frame list\n");
+               goto fail;
+       }
+       fl_phys = SLOF_dma_map_in(fl, sizeof(*fl), true);
+       dprintf("fl %p, fl_phys %lx\n", fl, fl_phys);
+
+       /* TODO: allocate qh pool */
+       qh_intr = SLOF_dma_alloc(sizeof(*qh_intr));
+       if (!qh_intr) {
+               printf("usb-ehci: Unable to allocate interrupt queue head\n");
+               goto fail_qh_intr;
+       }
+       qh_intr_phys = SLOF_dma_map_in(qh_intr, sizeof(*qh_intr), true);
+       dprintf("qh_intr %p, qh_intr_phys %lx\n", qh_intr, qh_intr_phys);
+
+       memset(qh_intr, 0, sizeof(*qh_intr));
+       qh_intr->qh_ptr = QH_PTR_TERM;
+       qh_intr->ep_cap2 = cpu_to_le32(0x01 << QH_SMASK_SHIFT);
+       qh_intr->next_qtd = qh_intr->alt_next_qtd = QH_PTR_TERM;
+       qh_intr->token = cpu_to_le32(QH_STS_HALTED);
+       for (i = 0; i < FL_SIZE; i++)
+               fl->fl_ptr[i] = cpu_to_le32(qh_intr_phys | EHCI_TYP_QH);
+       write_reg32(&ehcd->op_regs->periodiclistbase, fl_phys);
+
+       /* Initialize async list */
+       qh_async = SLOF_dma_alloc(sizeof(*qh_async));
+       if (!qh_async) {
+               printf("usb-ehci: Unable to allocate async queue head\n");
+               goto fail_qh_async;
+       }
+       qh_async_phys = SLOF_dma_map_in(qh_async, sizeof(*qh_async), true);
+       dprintf("qh_async %p, qh_async_phys %lx\n", qh_async, qh_async_phys);
+
+       memset(qh_async, 0, sizeof(*qh_async));
+       qh_async->qh_ptr = cpu_to_le32(qh_async_phys | EHCI_TYP_QH);
+       qh_async->ep_cap1 = cpu_to_le32(QH_CAP_H);
+       qh_async->next_qtd = qh_async->alt_next_qtd = QH_PTR_TERM;
+       qh_async->token = cpu_to_le32(QH_STS_HALTED);
+       write_reg32(&ehcd->op_regs->asynclistaddr, qh_async_phys);
+       ehcd->qh_async = qh_async;
+       ehcd->qh_async_phys = qh_async_phys;
+       ehcd->qh_intr = qh_intr;
+       ehcd->qh_intr_phys = qh_intr_phys;
+       ehcd->fl = fl;
+       ehcd->fl_phys = fl_phys;
+
+       write_reg32(&ehcd->op_regs->usbcmd, usbcmd | CMD_ASE | CMD_RUN);
+       write_reg32(&ehcd->op_regs->configflag, 1);
+
+       return 0;
+
+fail_qh_async:
+       SLOF_dma_map_out(qh_intr_phys, qh_intr, sizeof(*qh_intr));
+       SLOF_dma_free(qh_intr, sizeof(*qh_intr));
+fail_qh_intr:
+       SLOF_dma_map_out(fl_phys, fl, sizeof(*fl));
+       SLOF_dma_free(fl, sizeof(*fl));
+fail:
+       return -1;
+}
+
+static int ehci_hcd_exit(struct ehci_hcd *ehcd)
+{
+       uint32_t usbcmd;
+
+       if (!ehcd) {
+               dprintf("NULL pointer\n");
+               return false;
+       }
+
+       usbcmd = read_reg32(&ehcd->op_regs->usbcmd);
+       write_reg32(&ehcd->op_regs->usbcmd, usbcmd | ~CMD_RUN);
+       write_reg32(&ehcd->op_regs->periodiclistbase, 0);
+
+       if (ehcd->pool) {
+               SLOF_dma_map_out(ehcd->pool_phys, ehcd->pool, EHCI_PIPE_POOL_SIZE);
+               SLOF_dma_free(ehcd->pool, EHCI_PIPE_POOL_SIZE);
+       }
+       if (ehcd->qh_intr) {
+               SLOF_dma_map_out(ehcd->qh_intr_phys, ehcd->qh_intr, sizeof(struct ehci_qh));
+               SLOF_dma_free(ehcd->qh_intr, sizeof(struct ehci_qh));
+       }
+       if (ehcd->qh_async) {
+               SLOF_dma_map_out(ehcd->qh_async_phys, ehcd->qh_async, sizeof(struct ehci_qh));
+               SLOF_dma_free(ehcd->qh_async, sizeof(struct ehci_qh));
+       }
+       if (ehcd->fl) {
+               SLOF_dma_map_out(ehcd->fl_phys, ehcd->fl, sizeof(struct ehci_framelist));
+               SLOF_dma_free(ehcd->fl, sizeof(struct ehci_framelist));
+       }
+       return true;
+}
+
+static int ehci_alloc_pipe_pool(struct ehci_hcd *ehcd)
+{
+       struct ehci_pipe *epipe, *curr, *prev;
+       unsigned int i, count;
+       long epipe_phys = 0;
+
+       count = EHCI_PIPE_POOL_SIZE/sizeof(*epipe);
+       ehcd->pool = epipe = SLOF_dma_alloc(EHCI_PIPE_POOL_SIZE);
+       if (!epipe)
+               return -1;
+       ehcd->pool_phys = epipe_phys = SLOF_dma_map_in(epipe, EHCI_PIPE_POOL_SIZE, true);
+       dprintf("%s: epipe %p, epipe_phys %lx\n", __func__, epipe, epipe_phys);
+
+       /* Although an array, link them */
+       for (i = 0, curr = epipe, prev = NULL; i < count; i++, curr++) {
+               if (prev)
+                       prev->pipe.next = &curr->pipe;
+               curr->pipe.next = NULL;
+               prev = curr;
+               curr->qh_phys = epipe_phys + (curr - epipe) * sizeof(*curr) +
+                       offset_of(struct ehci_pipe, qh);
+               dprintf("%s - %d: qh %p, qh_phys %lx\n", __func__,
+                       i, &curr->qh, curr->qh_phys);
+       }
+
+       if (!ehcd->freelist)
+               ehcd->freelist = &epipe->pipe;
+       else
+               ehcd->end->next = &epipe->pipe;
+       ehcd->end = &prev->pipe;
+
+       return 0;
+}
+
+static void ehci_init(struct usb_hcd_dev *hcidev)
+{
+       struct ehci_hcd *ehcd;
+
+       printf("  EHCI: Initializing\n");
+       dprintf("%s: device base address %p\n", __func__, hcidev->base);
+
+       ehcd = SLOF_alloc_mem(sizeof(*ehcd));
+       if (!ehcd) {
+               printf("usb-ehci: Unable to allocate memory\n");
+               return;
+       }
+       memset(ehcd, 0, sizeof(*ehcd));
+
+       hcidev->nextaddr = 1;
+       hcidev->priv = ehcd;
+       ehcd->hcidev = hcidev;
+       ehcd->cap_regs = (struct ehci_cap_regs *)(hcidev->base);
+       ehcd->op_regs = (struct ehci_op_regs *)(hcidev->base +
+                                               read_reg8(&ehcd->cap_regs->caplength));
+#ifdef EHCI_DEBUG
+       dump_ehci_regs(ehcd);
+#endif
+       ehci_hcd_init(ehcd);
+       ehci_hub_check_ports(ehcd);
+}
+
+static void ehci_exit(struct usb_hcd_dev *hcidev)
+{
+       struct ehci_hcd *ehcd;
+       static int count = 0;
+
+       dprintf("%s: enter \n", __func__);
+
+       if (!hcidev && !hcidev->priv) {
+               return;
+       }
+       count++;
+       if (count > 1) {
+               printf("%s: already called once \n", __func__);
+               return;
+       }
+       ehcd = hcidev->priv;
+       ehci_hcd_exit(ehcd);
+       SLOF_free_mem(ehcd, sizeof(*ehcd));
+       hcidev->priv = NULL;
+}
+
+static void ehci_detect(void)
+{
+
+}
+
+static void ehci_disconnect(void)
+{
+
+}
+
+static int ehci_handshake(struct ehci_hcd *ehcd, uint32_t timeout)
+{
+       uint32_t usbsts = 0, time;
+       uint32_t usbcmd;
+       mb();
+       usbcmd = read_reg32(&ehcd->op_regs->usbcmd);
+       /* Ring a doorbell */
+       write_reg32(&ehcd->op_regs->usbcmd, usbcmd | CMD_IAAD);
+       mb();
+       time = SLOF_GetTimer() + timeout;
+       while ((time > SLOF_GetTimer())) {
+               /* Wait for controller to confirm */
+               usbsts = read_reg32(&ehcd->op_regs->usbsts);
+               if (usbsts & STS_IAA) {
+                       /* Acknowledge it, for next doorbell to work */
+                       write_reg32(&ehcd->op_regs->usbsts, STS_IAA);
+                       return true;
+               }
+               cpu_relax();
+       }
+       return false;
+}
+
+static int fill_qtd_buff(struct ehci_qtd *qtd, long data, uint32_t size)
+{
+       long i, rem;
+       long pos = (data + 0x1000) & ~0xfff;
+
+       qtd->buffer[0] = cpu_to_le32(PTR_U32(data));
+       for (i = 1; i < 5; i++) {
+               if ((data + size - 1) >= pos) {
+                       //dprintf("data spans page boundary: %d, %p\n", i, pos);
+                       qtd->buffer[i] = cpu_to_le32(pos);
+                       pos += 0x1000;
+               } else
+                       break;
+       }
+       if ((data + size) > pos)
+               rem = data + size - pos;
+       else
+               rem = 0;
+       return rem;
+}
+
+static int ehci_send_ctrl(struct usb_pipe *pipe, struct usb_dev_req *req, void *data)
+{
+       struct ehci_hcd *ehcd;
+       struct ehci_qtd *qtd, *qtds, *qtds_phys;
+       struct ehci_pipe *epipe;
+       uint32_t transfer_size = sizeof(*req);
+       uint32_t datalen, pid;
+       uint32_t time;
+       long req_phys = 0, data_phys = 0;
+       int ret = true;
+
+       if (pipe->type != USB_EP_TYPE_CONTROL) {
+               printf("usb-ehci: Not a control pipe.\n");
+               return false;
+       }
+
+       ehcd = pipe->dev->hcidev->priv;
+       qtds = qtd = SLOF_dma_alloc(sizeof(*qtds) * 3);
+       if (!qtds) {
+               printf("Error allocating qTDs.\n");
+               return false;
+       }
+       qtds_phys = (struct ehci_qtd *)SLOF_dma_map_in(qtds, sizeof(*qtds) * 3, true);
+       memset(qtds, 0, sizeof(*qtds) * 3);
+       req_phys = SLOF_dma_map_in(req, sizeof(struct usb_dev_req), true);
+       qtd->next_qtd = cpu_to_le32(PTR_U32(&qtds_phys[1]));
+       qtd->alt_next_qtd = QH_PTR_TERM;
+       qtd->token = cpu_to_le32((transfer_size << TOKEN_TBTT_SHIFT) |
+                       (3 << TOKEN_CERR_SHIFT) |
+                       (PID_SETUP << TOKEN_PID_SHIFT) |
+                       (QH_STS_ACTIVE << TOKEN_STATUS_SHIFT));
+       fill_qtd_buff(qtd, req_phys, sizeof(*req));
+
+       qtd++;
+       datalen = cpu_to_le16(req->wLength);
+       pid = (req->bmRequestType & REQT_DIR_IN) ? PID_IN : PID_OUT;
+       if (datalen) {
+               data_phys = SLOF_dma_map_in(data, datalen, true);
+               qtd->next_qtd = cpu_to_le32(PTR_U32(&qtds_phys[2]));
+               qtd->alt_next_qtd = QH_PTR_TERM;
+               qtd->token = cpu_to_le32((1 << TOKEN_DT_SHIFT) |
+                               (datalen << TOKEN_TBTT_SHIFT) |
+                               (3 << TOKEN_CERR_SHIFT) |
+                               (pid << TOKEN_PID_SHIFT) |
+                               (QH_STS_ACTIVE << TOKEN_STATUS_SHIFT));
+               fill_qtd_buff(qtd, data_phys, datalen);
+               qtd++;
+       }
+
+       if (pid == PID_IN)
+               pid = PID_OUT;
+       else
+               pid = PID_IN;
+       qtd->next_qtd = QH_PTR_TERM;
+       qtd->alt_next_qtd = QH_PTR_TERM;
+       qtd->token = cpu_to_le32((1 << TOKEN_DT_SHIFT) |
+                       (3 << TOKEN_CERR_SHIFT) |
+                       (pid << TOKEN_PID_SHIFT) |
+                       (QH_STS_ACTIVE << TOKEN_STATUS_SHIFT));
+
+       /* link qtd to qh and attach to ehcd */
+       mb();
+       epipe = container_of(pipe, struct ehci_pipe, pipe);
+       epipe->qh.next_qtd = cpu_to_le32(PTR_U32(qtds_phys));
+       epipe->qh.qh_ptr = cpu_to_le32(ehcd->qh_async_phys | EHCI_TYP_QH);
+       epipe->qh.ep_cap1 = cpu_to_le32((pipe->mps << QH_MPS_SHIFT) |
+                               (pipe->speed << QH_EPS_SHIFT) |
+                               (pipe->epno << QH_EP_SHIFT) |
+                               (pipe->dev->addr << QH_DEV_ADDR_SHIFT));
+       mb();
+
+       ehcd->qh_async->qh_ptr = cpu_to_le32(epipe->qh_phys | EHCI_TYP_QH);
+
+       /* transfer data */
+       mb();
+       qtd = &qtds[0];
+       time = SLOF_GetTimer() + USB_TIMEOUT;
+       do {
+               if (le32_to_cpu(qtd->token) & (QH_STS_ACTIVE << TOKEN_STATUS_SHIFT))
+                       mb();
+               else
+                       qtd++;
+
+               if (time < SLOF_GetTimer()) { /* timed out */
+                       printf("usb-ehci: control transfer timed out_\n");
+                       ret = false;
+                       break;
+               }
+       } while (qtd->next_qtd != QH_PTR_TERM);
+
+       ehcd->qh_async->qh_ptr = cpu_to_le32(ehcd->qh_async_phys | EHCI_TYP_QH);
+       mb();
+       if (!ehci_handshake(ehcd, USB_TIMEOUT)) {
+               printf("%s: handshake failed\n", __func__);
+               ret = false;
+       }
+
+       SLOF_dma_map_out(req_phys, req, sizeof(struct usb_dev_req));
+       SLOF_dma_map_out(data_phys, data, datalen);
+       SLOF_dma_map_out(PTR_U32(qtds_phys), qtds, sizeof(*qtds) * 3);
+       SLOF_dma_free(qtds, sizeof(*qtds) * 3);
+
+       return ret;
+}
+
+static int ehci_transfer_bulk(struct usb_pipe *pipe, void *td, void *td_phys,
+                       void *data_phys, int size)
+{
+       struct ehci_hcd *ehcd;
+       struct ehci_qtd *qtd, *qtd_phys;
+       struct ehci_pipe *epipe;
+       uint32_t pid;
+       int i, rem, ret = true;
+       uint32_t time;
+       long ptr;
+
+       dprintf("usb-ehci: bulk transfer: data %p, size %d, td %p, td_phys %p\n",
+               data_phys, size, td, td_phys);
+
+       if (pipe->type != USB_EP_TYPE_BULK) {
+               printf("usb-ehci: Not a bulk pipe.\n");
+               return false;
+       }
+
+       if (size > QTD_MAX_TRANSFER_LEN) {
+               printf("usb-ehci: bulk transfer size too big\n");
+               return false;
+       }
+
+       ehcd = pipe->dev->hcidev->priv;
+       pid = (pipe->dir == USB_PIPE_OUT) ? PID_OUT : PID_IN;
+       qtd = (struct ehci_qtd *)td;
+       qtd_phys = (struct ehci_qtd *)td_phys;
+       ptr = (long)data_phys;
+       for (i = 0; i < NUM_BULK_QTDS; i++) {
+               memset(qtd, 0, sizeof(*qtd));
+               rem = fill_qtd_buff(qtd, ptr, size);
+               qtd->token = cpu_to_le32((1 << TOKEN_DT_SHIFT) |
+                               ((size - rem) << TOKEN_TBTT_SHIFT) |
+                               (3 << TOKEN_CERR_SHIFT) |
+                               (pid << TOKEN_PID_SHIFT) |
+                               (QH_STS_ACTIVE << TOKEN_STATUS_SHIFT));
+               if (rem) {
+                       qtd->next_qtd = cpu_to_le32(PTR_U32(&qtd_phys[i+1]));
+                       qtd->alt_next_qtd = QH_PTR_TERM;
+                       ptr += size - rem;
+                       size = rem;
+                       qtd++;
+               } else {
+                       qtd->next_qtd = qtd->alt_next_qtd = QH_PTR_TERM;
+                       break; /* no more data */
+               }
+       }
+
+       /* link qtd to qh and attach to ehcd */
+       mb();
+       epipe = container_of(pipe, struct ehci_pipe, pipe);
+       epipe->qh.next_qtd = cpu_to_le32(PTR_U32(qtd_phys));
+       epipe->qh.qh_ptr = cpu_to_le32(ehcd->qh_async_phys | EHCI_TYP_QH);
+       epipe->qh.ep_cap1 = cpu_to_le32((pipe->mps << QH_MPS_SHIFT) |
+                               (pipe->speed << QH_EPS_SHIFT) |
+                               (pipe->epno << QH_EP_SHIFT) |
+                               (pipe->dev->addr << QH_DEV_ADDR_SHIFT));
+       mb();
+
+       ehcd->qh_async->qh_ptr = cpu_to_le32(epipe->qh_phys | EHCI_TYP_QH);
+
+       /* transfer data */
+       mb();
+       qtd = (struct ehci_qtd *)td;
+       for (i = 0; i < NUM_BULK_QTDS; i++) {
+               time = SLOF_GetTimer() + USB_TIMEOUT;
+               while ((time > SLOF_GetTimer()) &&
+                       (le32_to_cpu(qtd->token) & (QH_STS_ACTIVE << TOKEN_STATUS_SHIFT)))
+                       cpu_relax();
+               mb();
+               if (qtd->next_qtd == QH_PTR_TERM)
+                       break;
+
+               if (le32_to_cpu(qtd->token) & (QH_STS_ACTIVE << TOKEN_STATUS_SHIFT)) {
+                       printf("usb-ehci: bulk transfer timed out_\n");
+                       ret = false;
+                       break;
+               }
+               qtd++;
+       }
+
+       ehcd->qh_async->qh_ptr = cpu_to_le32(ehcd->qh_async_phys | EHCI_TYP_QH);
+       mb();
+       if (!ehci_handshake(ehcd, USB_TIMEOUT)) {
+               printf("%s: handshake failed\n", __func__);
+               ret = false;
+       }
+       return ret;
+}
+
+static struct usb_pipe *ehci_get_pipe(struct usb_dev *dev, struct usb_ep_descr *ep,
+                               char *buf, size_t len)
+{
+       struct ehci_hcd *ehcd;
+       struct usb_pipe *new = NULL;
+
+       if (!dev)
+               return NULL;
+
+       ehcd = (struct ehci_hcd *)dev->hcidev->priv;
+       if (!ehcd->freelist) {
+               dprintf("usb-ehci: %s allocating pool\n", __func__);
+               if (ehci_alloc_pipe_pool(ehcd))
+                       return NULL;
+       }
+
+       new = ehcd->freelist;
+       ehcd->freelist = ehcd->freelist->next;
+       if (!ehcd->freelist)
+               ehcd->end = NULL;
+
+       memset(new, 0, sizeof(*new));
+       new->dev = dev;
+       new->next = NULL;
+       new->type = ep->bmAttributes & USB_EP_TYPE_MASK;
+       new->speed = dev->speed;
+       new->mps = ep->wMaxPacketSize;
+       new->dir = (ep->bEndpointAddress & 0x80) >> 7;
+       new->epno = ep->bEndpointAddress & 0x0f;
+
+       return new;
+}
+
+static void ehci_put_pipe(struct usb_pipe *pipe)
+{
+       struct ehci_hcd *ehcd;
+
+       dprintf("usb-ehci: %s enter - %p\n", __func__, pipe);
+       if (!pipe || !pipe->dev)
+               return;
+       ehcd = pipe->dev->hcidev->priv;
+       if (ehcd->end)
+               ehcd->end->next = pipe;
+       else
+               ehcd->freelist = pipe;
+
+       ehcd->end = pipe;
+       pipe->next = NULL;
+       pipe->dev = NULL;
+       memset(pipe, 0, sizeof(*pipe));
+       dprintf("usb-ehci: %s exit\n", __func__);
+}
+
+struct usb_hcd_ops ehci_ops = {
+       .name          = "ehci-hcd",
+       .init          = ehci_init,
+       .exit          = ehci_exit,
+       .detect        = ehci_detect,
+       .disconnect    = ehci_disconnect,
+       .get_pipe      = ehci_get_pipe,
+       .put_pipe      = ehci_put_pipe,
+       .send_ctrl     = ehci_send_ctrl,
+       .transfer_bulk = ehci_transfer_bulk,
+       .usb_type      = USB_EHCI,
+       .next          = NULL,
+};
+
+void usb_ehci_register(void)
+{
+       usb_hcd_register(&ehci_ops);
+}