summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorStephen Warren <swarren@nvidia.com>2014-06-10 15:27:39 -0600
committerMarek Vasut <marex@denx.de>2014-06-11 02:26:05 +0200
commite0672b3c3ae4fdd48a66af9f7932d2c8e839e459 (patch)
treea3e33a98fefb69486c3f7ac4fd7a6b361a8d3366 /drivers
parentb7c0051687f42173e15b151a74e524587e8b59db (diff)
downloadblackbird-obmc-uboot-e0672b3c3ae4fdd48a66af9f7932d2c8e839e459.tar.gz
blackbird-obmc-uboot-e0672b3c3ae4fdd48a66af9f7932d2c8e839e459.zip
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')
-rw-r--r--drivers/usb/gadget/ci_udc.c38
1 files changed, 35 insertions, 3 deletions
diff --git a/drivers/usb/gadget/ci_udc.c b/drivers/usb/gadget/ci_udc.c
index 435a2720e9..b18bee43ad 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);
OpenPOWER on IntegriCloud