| /***************************************************************************** |
| * 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 ...) do {} while (0) |
| |
| #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 (usb_setup_new_device(dev, i)) |
| usb_slof_populate_new_device(dev); |
| else |
| 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); |
| } |