diff options
author | Stephen Warren <swarren@nvidia.com> | 2014-06-10 21:27:39 (GMT) |
---|---|---|
committer | Marek Vasut <marex@denx.de> | 2014-06-11 00:26:05 (GMT) |
commit | e0672b3c3ae4fdd48a66af9f7932d2c8e839e459 (patch) | |
tree | a3e33a98fefb69486c3f7ac4fd7a6b361a8d3366 /drivers/usb/gadget | |
parent | b7c0051687f42173e15b151a74e524587e8b59db (diff) | |
download | u-boot-fsl-qoriq-e0672b3c3ae4fdd48a66af9f7932d2c8e839e459.tar.xz |
usb: ci_udc: terminate ep0 INs with a zlp when required
Sometimes, a zero-length packet is required at the end of an IN
transaction so that the host knows the device is done sending data.
Enhance ci_udc to send a zlp when necessary. See the comments for
more details.
Signed-off-by: Stephen Warren <swarren@nvidia.com>
Diffstat (limited to 'drivers/usb/gadget')
-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); |