/***************************************************************************** * 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 (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); }