X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=drivers%2Fusb%2Fgadget%2Fci_udc.c;h=05c01ce5d6ea31d90b1f23987bdc0e267fe526f1;hb=87fcdca6be8641d8aa7c4e31ccb6cb7d5177359e;hp=b18bee43ad894ed886076ac126a23fa42a6761e5;hpb=ed1d98d801dfb6384d0f2fff45ce1ebf884944ca;p=u-boot diff --git a/drivers/usb/gadget/ci_udc.c b/drivers/usb/gadget/ci_udc.c index b18bee43ad..05c01ce5d6 100644 --- a/drivers/usb/gadget/ci_udc.c +++ b/drivers/usb/gadget/ci_udc.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -34,6 +34,24 @@ #error This driver can not work on systems with caches longer than 128b #endif +/* + * Every QTD must be individually aligned, since we can program any + * QTD's address into HW. Cache flushing requires ARCH_DMA_MINALIGN, + * and the USB HW requires 32-byte alignment. Align to both: + */ +#define ILIST_ALIGN roundup(ARCH_DMA_MINALIGN, 32) +/* Each QTD is this size */ +#define ILIST_ENT_RAW_SZ sizeof(struct ept_queue_item) +/* + * Align the size of the QTD too, so we can add this value to each + * QTD's address to get another aligned address. + */ +#define ILIST_ENT_SZ roundup(ILIST_ENT_RAW_SZ, ILIST_ALIGN) +/* For each endpoint, we need 2 QTDs, one for each of IN and OUT */ +#define ILIST_SZ (NUM_ENDPOINTS * 2 * ILIST_ENT_SZ) + +#define EP_MAX_LENGTH_TRANSFER 0x4000 + #ifndef DEBUG #define DBG(x...) do {} while (0) #else @@ -69,6 +87,7 @@ static int ci_ep_enable(struct usb_ep *ep, static int ci_ep_disable(struct usb_ep *ep); static int ci_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags); +static int ci_ep_dequeue(struct usb_ep *ep, struct usb_request *req); static struct usb_request * ci_ep_alloc_request(struct usb_ep *ep, unsigned int gfp_flags); static void ci_ep_free_request(struct usb_ep *ep, struct usb_request *_req); @@ -81,18 +100,34 @@ static struct usb_ep_ops ci_ep_ops = { .enable = ci_ep_enable, .disable = ci_ep_disable, .queue = ci_ep_queue, + .dequeue = ci_ep_dequeue, .alloc_request = ci_ep_alloc_request, .free_request = ci_ep_free_request, }; /* Init values for USB endpoints. */ -static const struct usb_ep ci_ep_init[2] = { +static const struct usb_ep ci_ep_init[5] = { [0] = { /* EP 0 */ .maxpacket = 64, .name = "ep0", .ops = &ci_ep_ops, }, - [1] = { /* EP 1..n */ + [1] = { + .maxpacket = 512, + .name = "ep1in-bulk", + .ops = &ci_ep_ops, + }, + [2] = { + .maxpacket = 512, + .name = "ep2out-bulk", + .ops = &ci_ep_ops, + }, + [3] = { + .maxpacket = 512, + .name = "ep3in-int", + .ops = &ci_ep_ops, + }, + [4] = { .maxpacket = 512, .name = "ep-", .ops = &ci_ep_ops, @@ -130,7 +165,9 @@ static struct ept_queue_head *ci_get_qh(int ep_num, int dir_in) */ static struct ept_queue_item *ci_get_qtd(int ep_num, int dir_in) { - return controller.items[(ep_num * 2) + dir_in]; + int index = (ep_num * 2) + dir_in; + uint8_t *imem = controller.items_mem + (index * ILIST_ENT_SZ); + return (struct ept_queue_item *)imem; } /** @@ -142,8 +179,8 @@ static struct ept_queue_item *ci_get_qtd(int ep_num, int dir_in) static void ci_flush_qh(int ep_num) { struct ept_queue_head *head = ci_get_qh(ep_num, 0); - const uint32_t start = (uint32_t)head; - const uint32_t end = start + 2 * sizeof(*head); + const unsigned long start = (unsigned long)head; + const unsigned long end = start + 2 * sizeof(*head); flush_dcache_range(start, end); } @@ -157,8 +194,8 @@ static void ci_flush_qh(int ep_num) static void ci_invalidate_qh(int ep_num) { struct ept_queue_head *head = ci_get_qh(ep_num, 0); - uint32_t start = (uint32_t)head; - uint32_t end = start + 2 * sizeof(*head); + unsigned long start = (unsigned long)head; + unsigned long end = start + 2 * sizeof(*head); invalidate_dcache_range(start, end); } @@ -172,13 +209,25 @@ static void ci_invalidate_qh(int ep_num) static void ci_flush_qtd(int ep_num) { struct ept_queue_item *item = ci_get_qtd(ep_num, 0); - const uint32_t start = (uint32_t)item; - const uint32_t end_raw = start + 2 * sizeof(*item); - const uint32_t end = roundup(end_raw, ARCH_DMA_MINALIGN); + const unsigned long start = (unsigned long)item; + const unsigned long end = start + 2 * ILIST_ENT_SZ; flush_dcache_range(start, end); } +/** + * ci_flush_td - flush cache over queue item + * @td: td pointer + * + * This function flushes cache for particular transfer descriptor. + */ +static void ci_flush_td(struct ept_queue_item *td) +{ + const unsigned long start = (unsigned long)td; + const unsigned long end = (unsigned long)td + ILIST_ENT_SZ; + flush_dcache_range(start, end); +} + /** * ci_invalidate_qtd - invalidate cache over queue item * @ep_num: Endpoint number @@ -188,30 +237,43 @@ static void ci_flush_qtd(int ep_num) static void ci_invalidate_qtd(int ep_num) { struct ept_queue_item *item = ci_get_qtd(ep_num, 0); - const uint32_t start = (uint32_t)item; - const uint32_t end_raw = start + 2 * sizeof(*item); - const uint32_t end = roundup(end_raw, ARCH_DMA_MINALIGN); + const unsigned long start = (unsigned long)item; + const unsigned long end = start + 2 * ILIST_ENT_SZ; invalidate_dcache_range(start, end); } +/** + * ci_invalidate_td - invalidate cache over queue item + * @td: td pointer + * + * This function invalidates cache for particular transfer descriptor. + */ +static void ci_invalidate_td(struct ept_queue_item *td) +{ + const unsigned long start = (unsigned long)td; + const unsigned long end = start + ILIST_ENT_SZ; + invalidate_dcache_range(start, end); +} + static struct usb_request * ci_ep_alloc_request(struct usb_ep *ep, unsigned int gfp_flags) { struct ci_ep *ci_ep = container_of(ep, struct ci_ep, ep); - int num; + int num = -1; struct ci_req *ci_req; - num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + if (ci_ep->desc) + num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + if (num == 0 && controller.ep0_req) return &controller.ep0_req->req; - ci_req = memalign(ARCH_DMA_MINALIGN, sizeof(*ci_req)); + ci_req = calloc(1, sizeof(*ci_req)); if (!ci_req) return NULL; INIT_LIST_HEAD(&ci_req->queue); - ci_req->b_buf = 0; if (num == 0) controller.ep0_req = ci_req; @@ -223,11 +285,16 @@ static void ci_ep_free_request(struct usb_ep *ep, struct usb_request *req) { struct ci_ep *ci_ep = container_of(ep, struct ci_ep, ep); struct ci_req *ci_req = container_of(req, struct ci_req, req); - int num; + int num = -1; - num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - if (num == 0) + if (ci_ep->desc) + num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + + if (num == 0) { + if (!controller.ep0_req) + return; controller.ep0_req = 0; + } if (ci_req->b_buf) free(ci_req->b_buf); @@ -290,8 +357,8 @@ static int ci_ep_disable(struct usb_ep *ep) static int ci_bounce(struct ci_req *ci_req, int in) { struct usb_request *req = &ci_req->req; - uint32_t addr = (uint32_t)req->buf; - uint32_t hwaddr; + unsigned long addr = (unsigned long)req->buf; + unsigned long hwaddr; uint32_t aligned_used_len; /* Input buffer address is not aligned. */ @@ -325,7 +392,7 @@ align: memcpy(ci_req->hw_buf, req->buf, req->length); flush: - hwaddr = (uint32_t)ci_req->hw_buf; + hwaddr = (unsigned long)ci_req->hw_buf; aligned_used_len = roundup(req->length, ARCH_DMA_MINALIGN); flush_dcache_range(hwaddr, hwaddr + aligned_used_len); @@ -335,8 +402,8 @@ flush: static void ci_debounce(struct ci_req *ci_req, int in) { struct usb_request *req = &ci_req->req; - uint32_t addr = (uint32_t)req->buf; - uint32_t hwaddr = (uint32_t)ci_req->hw_buf; + unsigned long addr = (unsigned long)req->buf; + unsigned long hwaddr = (unsigned long)ci_req->hw_buf; uint32_t aligned_used_len; if (in) @@ -358,6 +425,9 @@ static void ci_ep_submit_next_request(struct ci_ep *ci_ep) struct ept_queue_head *head; int bit, num, len, in; struct ci_req *ci_req; + u8 *buf; + uint32_t len_left, len_this_dtd; + struct ept_queue_item *dtd, *qtd; ci_ep->req_primed = true; @@ -369,16 +439,39 @@ static void ci_ep_submit_next_request(struct ci_ep *ci_ep) ci_req = list_first_entry(&ci_ep->queue, struct ci_req, queue); len = ci_req->req.length; - item->info = INFO_BYTES(len) | INFO_ACTIVE; - item->page0 = (uint32_t)ci_req->hw_buf; - item->page1 = ((uint32_t)ci_req->hw_buf & 0xfffff000) + 0x1000; - item->page2 = ((uint32_t)ci_req->hw_buf & 0xfffff000) + 0x2000; - item->page3 = ((uint32_t)ci_req->hw_buf & 0xfffff000) + 0x3000; - item->page4 = ((uint32_t)ci_req->hw_buf & 0xfffff000) + 0x4000; - - head->next = (unsigned) item; + head->next = (unsigned long)item; head->info = 0; + ci_req->dtd_count = 0; + buf = ci_req->hw_buf; + len_left = len; + dtd = item; + + do { + len_this_dtd = min(len_left, (unsigned)EP_MAX_LENGTH_TRANSFER); + + dtd->info = INFO_BYTES(len_this_dtd) | INFO_ACTIVE; + dtd->page0 = (unsigned long)buf; + dtd->page1 = ((unsigned long)buf & 0xfffff000) + 0x1000; + dtd->page2 = ((unsigned long)buf & 0xfffff000) + 0x2000; + dtd->page3 = ((unsigned long)buf & 0xfffff000) + 0x3000; + dtd->page4 = ((unsigned long)buf & 0xfffff000) + 0x4000; + + len_left -= len_this_dtd; + buf += len_this_dtd; + + if (len_left) { + qtd = (struct ept_queue_item *) + memalign(ILIST_ALIGN, ILIST_ENT_SZ); + dtd->next = (unsigned long)qtd; + dtd = qtd; + memset(dtd, 0, ILIST_ENT_SZ); + } + + ci_req->dtd_count++; + } while (len_left); + + item = dtd; /* * When sending the data for an IN transaction, the attached host * knows that all data for the IN is sent when one of the following @@ -401,10 +494,11 @@ static void ci_ep_submit_next_request(struct ci_ep *ci_ep) * only 1 is used at a time since either an IN or an OUT but * not both is queued. For an IN transaction, item currently * points at the second of these items, so we know that we - * can use (item - 1) to transmit the extra zero-length packet + * can use the other to transmit the extra zero-length packet. */ - item->next = (unsigned)(item - 1); - item--; + struct ept_queue_item *other_item = ci_get_qtd(num, 0); + item->next = (unsigned long)other_item; + item = other_item; item->info = INFO_ACTIVE; } @@ -413,6 +507,12 @@ static void ci_ep_submit_next_request(struct ci_ep *ci_ep) ci_flush_qtd(num); + item = (struct ept_queue_item *)(unsigned long)head->next; + while (item->next != TERMINATE) { + ci_flush_td((struct ept_queue_item *)(unsigned long)item->next); + item = (struct ept_queue_item *)(unsigned long)item->next; + } + DBG("ept%d %s queue len %x, req %p, buffer %p\n", num, in ? "in" : "out", len, ci_req, ci_req->hw_buf); ci_flush_qh(num); @@ -425,6 +525,30 @@ static void ci_ep_submit_next_request(struct ci_ep *ci_ep) writel(bit, &udc->epprime); } +static int ci_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct ci_ep *ci_ep = container_of(_ep, struct ci_ep, ep); + struct ci_req *ci_req; + + list_for_each_entry(ci_req, &ci_ep->queue, queue) { + if (&ci_req->req == _req) + break; + } + + if (&ci_req->req != _req) + return -EINVAL; + + list_del_init(&ci_req->queue); + + if (ci_req->req.status == -EINPROGRESS) { + ci_req->req.status = -ECONNRESET; + if (ci_req->req.complete) + ci_req->req.complete(_ep, _req); + } + + return 0; +} + static int ci_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags) { @@ -468,36 +592,47 @@ static int ci_ep_queue(struct usb_ep *ep, static void flip_ep0_direction(void) { if (ep0_desc.bEndpointAddress == USB_DIR_IN) { - DBG("%s: Flipping ep0 ot OUT\n", __func__); + DBG("%s: Flipping ep0 to OUT\n", __func__); ep0_desc.bEndpointAddress = 0; } else { - DBG("%s: Flipping ep0 ot IN\n", __func__); + DBG("%s: Flipping ep0 to IN\n", __func__); ep0_desc.bEndpointAddress = USB_DIR_IN; } } -static void handle_ep_complete(struct ci_ep *ep) +static void handle_ep_complete(struct ci_ep *ci_ep) { - struct ept_queue_item *item; - int num, in, len; + struct ept_queue_item *item, *next_td; + int num, in, len, j; struct ci_req *ci_req; - num = ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; - in = (ep->desc->bEndpointAddress & USB_DIR_IN) != 0; + num = ci_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + in = (ci_ep->desc->bEndpointAddress & USB_DIR_IN) != 0; item = ci_get_qtd(num, in); ci_invalidate_qtd(num); + ci_req = list_first_entry(&ci_ep->queue, struct ci_req, queue); - len = (item->info >> 16) & 0x7fff; - if (item->info & 0xff) - printf("EP%d/%s FAIL info=%x pg0=%x\n", - num, in ? "in" : "out", item->info, item->page0); + next_td = item; + len = 0; + for (j = 0; j < ci_req->dtd_count; j++) { + ci_invalidate_td(next_td); + item = next_td; + len += (item->info >> 16) & 0x7fff; + if (item->info & 0xff) + printf("EP%d/%s FAIL info=%x pg0=%x\n", + num, in ? "in" : "out", item->info, item->page0); + if (j != ci_req->dtd_count - 1) + next_td = (struct ept_queue_item *)(unsigned long) + item->next; + if (j != 0) + free(item); + } - ci_req = list_first_entry(&ep->queue, struct ci_req, queue); list_del_init(&ci_req->queue); - ep->req_primed = false; + ci_ep->req_primed = false; - if (!list_empty(&ep->queue)) - ci_ep_submit_next_request(ep); + if (!list_empty(&ci_ep->queue)) + ci_ep_submit_next_request(ci_ep); ci_req->req.actual = ci_req->req.length - len; ci_debounce(ci_req, in); @@ -505,7 +640,7 @@ static void handle_ep_complete(struct ci_ep *ep) DBG("ept%d %s req %p, complete %x\n", num, in ? "in" : "out", ci_req, len); if (num != 0 || controller.ep0_data_phase) - ci_req->req.complete(&ep->ep, &ci_req->req); + ci_req->req.complete(&ci_ep->ep, &ci_req->req); if (num == 0 && controller.ep0_data_phase) { /* * Data Stage is complete, so flip ep0 dir for Status Stage, @@ -515,7 +650,7 @@ static void handle_ep_complete(struct ci_ep *ep) flip_ep0_direction(); controller.ep0_data_phase = false; ci_req->req.length = 0; - usb_ep_queue(&ep->ep, &ci_req->req, 0); + usb_ep_queue(&ci_ep->ep, &ci_req->req, 0); } } @@ -722,7 +857,7 @@ void udc_irq(void) } } -int usb_gadget_handle_interrupts(void) +int usb_gadget_handle_interrupts(int index) { u32 value; struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; @@ -753,11 +888,16 @@ static int ci_pullup(struct usb_gadget *gadget, int is_on) writel(USBCMD_ITC(MICRO_8FRAME) | USBCMD_RST, &udc->usbcmd); udelay(200); - writel((unsigned)controller.epts, &udc->epinitaddr); + writel((unsigned long)controller.epts, &udc->epinitaddr); /* select DEVICE mode */ writel(USBMODE_DEVICE, &udc->usbmode); +#if !defined(CONFIG_USB_GADGET_DUALSPEED) + /* Port force Full-Speed Connect */ + setbits_le32(&udc->portsc, PFSC); +#endif + writel(0xffffffff, &udc->epflush); /* Turn on the USB connection by enabling the pullup resistor */ @@ -772,7 +912,6 @@ static int ci_pullup(struct usb_gadget *gadget, int is_on) static int ci_udc_probe(void) { struct ept_queue_head *head; - uint8_t *imem; int i; const int num = 2 * NUM_ENDPOINTS; @@ -782,29 +921,18 @@ static int ci_udc_probe(void) const int eplist_raw_sz = num * sizeof(struct ept_queue_head); const int eplist_sz = roundup(eplist_raw_sz, ARCH_DMA_MINALIGN); - const int ilist_align = roundup(ARCH_DMA_MINALIGN, 32); - const int ilist_ent_raw_sz = 2 * sizeof(struct ept_queue_item); - const int ilist_ent_sz = roundup(ilist_ent_raw_sz, ARCH_DMA_MINALIGN); - const int ilist_sz = NUM_ENDPOINTS * ilist_ent_sz; - /* The QH list must be aligned to 4096 bytes. */ controller.epts = memalign(eplist_align, eplist_sz); if (!controller.epts) return -ENOMEM; memset(controller.epts, 0, eplist_sz); - /* - * Each qTD item must be 32-byte aligned, each qTD touple must be - * cacheline aligned. There are two qTD items for each endpoint and - * only one of them is used for the endpoint at time, so we can group - * them together. - */ - controller.items_mem = memalign(ilist_align, ilist_sz); + controller.items_mem = memalign(ILIST_ALIGN, ILIST_SZ); if (!controller.items_mem) { free(controller.epts); return -ENOMEM; } - memset(controller.items_mem, 0, ilist_sz); + memset(controller.items_mem, 0, ILIST_SZ); for (i = 0; i < 2 * NUM_ENDPOINTS; i++) { /* @@ -824,15 +952,9 @@ static int ci_udc_probe(void) head->next = TERMINATE; head->info = 0; - imem = controller.items_mem + ((i >> 1) * ilist_ent_sz); - if (i & 1) - imem += sizeof(struct ept_queue_item); - - controller.items[i] = (struct ept_queue_item *)imem; - if (i & 1) { - ci_flush_qh(i - 1); - ci_flush_qtd(i - 1); + ci_flush_qh(i / 2); + ci_flush_qtd(i / 2); } } @@ -846,9 +968,19 @@ static int ci_udc_probe(void) controller.gadget.ep0 = &controller.ep[0].ep; INIT_LIST_HEAD(&controller.gadget.ep0->ep_list); - /* Init EP 1..n */ - for (i = 1; i < NUM_ENDPOINTS; i++) { - memcpy(&controller.ep[i].ep, &ci_ep_init[1], + /* Init EP 1..3 */ + for (i = 1; i < 4; i++) { + memcpy(&controller.ep[i].ep, &ci_ep_init[i], + sizeof(*ci_ep_init)); + INIT_LIST_HEAD(&controller.ep[i].queue); + controller.ep[i].req_primed = false; + list_add_tail(&controller.ep[i].ep.ep_list, + &controller.gadget.ep_list); + } + + /* Init EP 4..n */ + for (i = 4; i < NUM_ENDPOINTS; i++) { + memcpy(&controller.ep[i].ep, &ci_ep_init[4], sizeof(*ci_ep_init)); INIT_LIST_HEAD(&controller.ep[i].queue); controller.ep[i].req_primed = false; @@ -877,23 +1009,19 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver) if (driver->speed != USB_SPEED_FULL && driver->speed != USB_SPEED_HIGH) return -EINVAL; +#ifdef CONFIG_DM_USB + ret = usb_setup_ehci_gadget(&controller.ctrl); +#else ret = usb_lowlevel_init(0, USB_INIT_DEVICE, (void **)&controller.ctrl); +#endif if (ret) return ret; ret = ci_udc_probe(); -#if defined(CONFIG_USB_EHCI_MX6) || defined(CONFIG_USB_EHCI_MXS) - /* - * FIXME: usb_lowlevel_init()->ehci_hcd_init() should be doing all - * HW-specific initialization, e.g. ULPI-vs-UTMI PHY selection - */ - if (!ret) { - struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; - - /* select ULPI phy */ - writel(PTS(PTS_ENABLE) | PFSC, &udc->portsc); + if (ret) { + DBG("udc probe failed, returned %d\n", ret); + return ret; } -#endif ret = driver->bind(&controller.gadget); if (ret) { @@ -909,9 +1037,19 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) { udc_disconnect(); + driver->unbind(&controller.gadget); + controller.driver = NULL; + ci_ep_free_request(&controller.ep[0].ep, &controller.ep0_req->req); free(controller.items_mem); free(controller.epts); return 0; } + +bool dfu_usb_get_reset(void) +{ + struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor; + + return !!(readl(&udc->usbsts) & STS_URI); +}