From e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb Mon Sep 17 00:00:00 2001 From: Yang Zhang Date: Fri, 28 Aug 2015 09:58:54 +0800 Subject: Add qemu 2.4.0 Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5 Signed-off-by: Yang Zhang --- qemu/roms/SLOF/lib/libusb/usb-ehci.c | 609 +++++++++++++++++++++++++++++++++++ 1 file changed, 609 insertions(+) create mode 100644 qemu/roms/SLOF/lib/libusb/usb-ehci.c (limited to '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 index 000000000..4cca0da15 --- /dev/null +++ b/qemu/roms/SLOF/lib/libusb/usb-ehci.c @@ -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 +#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); +} -- cgit 1.2.3-korg