diff options
Diffstat (limited to 'drivers/usb/gadget/ci_udc.c')
-rw-r--r-- | drivers/usb/gadget/ci_udc.c | 38 |
1 files changed, 35 insertions, 3 deletions
diff --git a/drivers/usb/gadget/ci_udc.c b/drivers/usb/gadget/ci_udc.c index 435a272..b18bee4 100644 --- a/drivers/usb/gadget/ci_udc.c +++ b/drivers/usb/gadget/ci_udc.c @@ -369,18 +369,50 @@ 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->next = TERMINATE; - item->info = INFO_BYTES(len) | INFO_IOC | INFO_ACTIVE; + 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; - ci_flush_qtd(num); head->next = (unsigned) item; head->info = 0; + /* + * 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 + * occurs: + * a) A zero-length packet is transmitted. + * b) A packet with length that isn't an exact multiple of the ep's + * maxpacket is transmitted. + * c) Enough data is sent to exactly fill the host's maximum expected + * IN transaction size. + * + * One of these conditions MUST apply at the end of an IN transaction, + * or the transaction will not be considered complete by the host. If + * none of (a)..(c) already applies, then we must force (a) to apply + * by explicitly sending an extra zero-length packet. + */ + /* IN !a !b !c */ + if (in && len && !(len % ci_ep->ep.maxpacket) && ci_req->req.zero) { + /* + * Each endpoint has 2 items allocated, even though typically + * 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 + */ + item->next = (unsigned)(item - 1); + item--; + item->info = INFO_ACTIVE; + } + + item->next = TERMINATE; + item->info |= INFO_IOC; + + ci_flush_qtd(num); + 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); |