[PATCH 1/4] usb: musb: Enable DMA Mode1 for device mode RX

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: supriya karanth <supriya.karanth@xxxxxxxxxxxxxx>

Generic changes for enabling DMA Mode1.
Cleanup of rxtsate function making programming
same for all platforms

Handles:
1) Known transfer length
      a) Non multiple of packet size
      b) Multiple of packet size

2) Unknown transfer lengths
      - Short packet indicates end of transfer

---------------> OUT Endpoint interrupt recieved
|                          |
|      ____________________|_________________
|      |                                     |
| -> No ongoing transfer   ->DMA Transfer ongoing and RXPKTRDY set?
|      |                   ->Short Packet recieved
|  request queued?         ->PAUSE DMA transfer, Read Residue
|      |                    ________________|____________
|      |                    |                            |
|      |                   residue!=0                 residue=0
|      |            -> abort DMA                    ->Resume DMA
|      |            ->Update request->actual_len    ->Wait for DMA
|      |            -> Clear DMA bits in CSR            completion
|      |            -> Read Short Packet                   |
|      |__________________________|                        |______
|                          |                                      |
|                     call rxstate                                |
|                    ->RXPKTRDY set? Read RXCOUNT                 |
|      _____________________|____________________                 |
|      |                                          |               |
| ->RXCOUNT == EP MAX packet Size   ->RXCOUNT < EP MAX packet Size|
| ->Program DMA in Mode1 for length -> Program in PIO / Mode0     |
|    which is multiple of packet    -> Read Short packet from FIFO|
|    size                           -> Update request->actual_len |
|       |                           -> call musb_giveback         |
|       |________________________________________|                |
|____________________________|                                    |
|                            |                                    |
|                            |____________________________________|
|                               |
|               DMA completion interrupt recieved
|        _______________________|_________________
|        |                                        |
|  ->req->len = req->actual            ->req->len < req->actual
|  ->Clear DMA bits                    -> Short packet expected
|  ->Call musb_giveback                ->Clear DMA bits
|       |                              ->wait for next OUT
|       |_________________________________________|
|_______________________________|

Signed-off-by: Supriya Karanth <supriya.karanth@xxxxxxxxxxxxxx>
Signed-off-by: Praveena NADAHALLY <praveen.nadahally@xxxxxxxxxxxxxx>
Acked-by: Linus Walleij <linus.walleij@xxxxxxxxxx>
---
 drivers/usb/musb/musb_dma.h    |   12 +++
 drivers/usb/musb/musb_gadget.c |  215 +++++++++++++++++++++++-----------------
 drivers/usb/musb/musb_gadget.h |    3 +
 3 files changed, 138 insertions(+), 92 deletions(-)

diff --git a/drivers/usb/musb/musb_dma.h b/drivers/usb/musb/musb_dma.h
index 1b6b827..7754515 100644
--- a/drivers/usb/musb/musb_dma.h
+++ b/drivers/usb/musb/musb_dma.h
@@ -116,6 +116,7 @@ struct dma_controller;
  * @max_len: the maximum number of bytes the channel can move in one
  *	transaction (typically representing many USB maximum-sized packets)
  * @actual_len: how many bytes have been transferred
+ * @prog_len: how many bytes have been programmed for transfer
  * @status: current channel status (updated e.g. on interrupt)
  * @desired_mode: true if mode 1 is desired; false if mode 0 is desired
  *
@@ -127,6 +128,7 @@ struct dma_channel {
 	/* FIXME not void* private_data, but a dma_controller * */
 	size_t			max_len;
 	size_t			actual_len;
+	size_t			prog_len;
 	enum dma_channel_status	status;
 	bool			desired_mode;
 };
@@ -155,6 +157,11 @@ dma_channel_status(struct dma_channel *c)
  * @channel_release: call this to release a DMA channel
  * @channel_abort: call this to abort a pending DMA transaction,
  *	returning it to FREE (but allocated) state
+ * @channel_pause: This function pauses the ongoing DMA transfer
+ * @channel_resume: This function resumes the ongoing DMA transfer
+ * @tx_status: Gets the residue of an ongoing DMA transfer
+ * @check_resiudue: checks if the residue of an ongoing DMA
+ *	transfer is valid
  *
  * Controllers manage dma channels.
  */
@@ -169,6 +176,11 @@ struct dma_controller {
 							dma_addr_t dma_addr,
 							u32 length);
 	int			(*channel_abort)(struct dma_channel *);
+	int			(*channel_pause)(struct dma_channel *);
+	int			(*channel_resume)(struct dma_channel *);
+	int			(*tx_status)(struct dma_channel *);
+	int			(*check_residue)(struct dma_channel *,
+							u32 residue);
 	int			(*is_compatible)(struct dma_channel *channel,
 							u16 maxpacket,
 							void *buf, u32 length);
diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c
index 9db664a..9d47276 100644
--- a/drivers/usb/musb/musb_gadget.c
+++ b/drivers/usb/musb/musb_gadget.c
@@ -666,13 +666,17 @@ static void rxstate(struct musb *musb, struct musb_request *req)
 		 * file_storage and f_mass_storage drivers
 		 */
 
-		if (request->short_not_ok && fifo_count == musb_ep->packet_sz)
+		if (fifo_count == musb_ep->packet_sz)
 			use_mode_1 = 1;
 		else
 			use_mode_1 = 0;
 
+#ifdef CONFIG_USB_TUSB_OMAP_DMA
+		if (tusb_dma_omap())
+			use_mode_1 = 0;
+#endif
+
 		if (request->actual < request->length) {
-#ifdef CONFIG_USB_INVENTRA_DMA
 			struct dma_controller	*c;
 			struct dma_channel	*channel;
 			int			use_dma = 0;
@@ -681,45 +685,35 @@ static void rxstate(struct musb *musb, struct musb_request *req)
 			c = musb->dma_controller;
 			channel = musb_ep->dma;
 
-	/* We use DMA Req mode 0 in rx_csr, and DMA controller operates in
-	 * mode 0 only. So we do not get endpoint interrupts due to DMA
-	 * completion. We only get interrupts from DMA controller.
-	 *
-	 * We could operate in DMA mode 1 if we knew the size of the tranfer
-	 * in advance. For mass storage class, request->length = what the host
-	 * sends, so that'd work.  But for pretty much everything else,
-	 * request->length is routinely more than what the host sends. For
-	 * most these gadgets, end of is signified either by a short packet,
-	 * or filling the last byte of the buffer.  (Sending extra data in
-	 * that last pckate should trigger an overflow fault.)  But in mode 1,
-	 * we don't get DMA completion interrupt for short packets.
-	 *
-	 * Theoretically, we could enable DMAReq irq (MUSB_RXCSR_DMAMODE = 1),
-	 * to get endpoint interrupt on every DMA req, but that didn't seem
-	 * to work reliably.
-	 *
-	 * REVISIT an updated g_file_storage can set req->short_not_ok, which
-	 * then becomes usable as a runtime "use mode 1" hint...
-	 */
-
-			/* Experimental: Mode1 works with mass storage use cases */
 			if (use_mode_1) {
 				csr |= MUSB_RXCSR_AUTOCLEAR;
 				musb_writew(epio, MUSB_RXCSR, csr);
 				csr |= MUSB_RXCSR_DMAENAB;
 				musb_writew(epio, MUSB_RXCSR, csr);
 
+				musb_writew(epio, MUSB_RXCSR,
+						csr | MUSB_RXCSR_DMAMODE);
+
+#ifdef CONFIG_USB_INVENTRA_DMA
 				/*
 				 * this special sequence (enabling and then
 				 * disabling MUSB_RXCSR_DMAMODE) is required
 				 * to get DMAReq to activate
 				 */
-				musb_writew(epio, MUSB_RXCSR,
-						csr | MUSB_RXCSR_DMAMODE);
 				musb_writew(epio, MUSB_RXCSR, csr);
-
+#endif
 				transfer_size = min(request->length - request->actual,
 						channel->max_len);
+
+				/* Program the transfer length to be
+				 * a multiple of packet size because
+				 * short packets cant be transferred
+				 * over mode1
+				 */
+				transfer_size = transfer_size -
+						(transfer_size
+						 % musb_ep->packet_sz);
+				musb_ep->dma->prog_len = transfer_size;
 				musb_ep->dma->desired_mode = 1;
 
 			} else {
@@ -742,54 +736,16 @@ static void rxstate(struct musb *musb, struct musb_request *req)
 					+ request->actual,
 					transfer_size);
 
-			if (use_dma)
+			if (use_dma) {
+				dev_dbg(musb->controller,
+					"%s OUT/RX DMA Size %d/%d maxpacket %d\n",
+						musb_ep->end_point.name,
+						fifo_count, transfer_size,
+						musb_ep->packet_sz);
 				return;
-#elif defined(CONFIG_USB_UX500_DMA)
-			if (request->actual < request->length) {
-				struct dma_controller *c;
-				struct dma_channel *channel;
-				int transfer_size = 0;
-
-				c = musb->dma_controller;
-				channel = musb_ep->dma;
-
-				/* In case first packet is short */
-				if (fifo_count < musb_ep->packet_sz)
-					transfer_size = fifo_count;
-				else if (request->short_not_ok)
-					transfer_size =	min(request->length -
-							request->actual,
-							channel->max_len);
-				else
-					transfer_size = min(request->length -
-							request->actual,
-							(unsigned)fifo_count);
-
-				csr &= ~MUSB_RXCSR_DMAMODE;
-				csr |= (MUSB_RXCSR_DMAENAB |
-					MUSB_RXCSR_AUTOCLEAR);
-
-				musb_writew(epio, MUSB_RXCSR, csr);
-
-				if (transfer_size <= musb_ep->packet_sz) {
-					musb_ep->dma->desired_mode = 0;
-				} else {
-					musb_ep->dma->desired_mode = 1;
-					/* Mode must be set after DMAENAB */
-					csr |= MUSB_RXCSR_DMAMODE;
-					musb_writew(epio, MUSB_RXCSR, csr);
-				}
-
-				if (c->channel_program(channel,
-							musb_ep->packet_sz,
-							channel->desired_mode,
-							request->dma
-							+ request->actual,
-							transfer_size))
-
-					return;
 			}
-#endif	/* Mentor's DMA */
+
+			/* DMA programming failed. Use PIO mode */
 
 			len = request->length - request->actual;
 			dev_dbg(musb->controller, "%s OUT/RX pio fifo %d/%d, maxpacket %d\n",
@@ -799,22 +755,6 @@ static void rxstate(struct musb *musb, struct musb_request *req)
 
 			fifo_count = min_t(unsigned, len, fifo_count);
 
-#ifdef	CONFIG_USB_TUSB_OMAP_DMA
-			if (tusb_dma_omap()) {
-				struct dma_controller *c = musb->dma_controller;
-				struct dma_channel *channel = musb_ep->dma;
-				u32 dma_addr = request->dma + request->actual;
-				int ret;
-
-				ret = c->channel_program(channel,
-						musb_ep->packet_sz,
-						channel->desired_mode,
-						dma_addr,
-						fifo_count);
-				if (ret)
-					return;
-			}
-#endif
 			/*
 			 * Unmap the dma buffer back to cpu if dma channel
 			 * programming fails. This buffer is mapped if the
@@ -863,6 +803,10 @@ void musb_g_rx(struct musb *musb, u8 epnum)
 	void __iomem		*epio = musb->endpoints[epnum].regs;
 	struct dma_channel	*dma;
 	struct musb_hw_ep	*hw_ep = &musb->endpoints[epnum];
+	u16			len;
+	u32			residue;
+	struct dma_controller	*c = musb->dma_controller;
+	int			status;
 
 	if (hw_ep->is_shared_fifo)
 		musb_ep = &hw_ep->ep_in;
@@ -872,8 +816,12 @@ void musb_g_rx(struct musb *musb, u8 epnum)
 	musb_ep_select(mbase, epnum);
 
 	req = next_request(musb_ep);
-	if (!req)
+	if (!req) {
+		musb_ep->rx_pending = 1;
+		dev_dbg(musb->controller, "Packet recieved on %s but no request queued\n",
+			musb_ep->end_point.name);
 		return;
+	}
 
 	request = &req->request;
 
@@ -905,9 +853,56 @@ void musb_g_rx(struct musb *musb, u8 epnum)
 	}
 
 	if (dma_channel_status(dma) == MUSB_DMA_STATUS_BUSY) {
-		/* "should not happen"; likely RXPKTRDY pending for DMA */
+
 		dev_dbg(musb->controller, "%s busy, csr %04x\n",
 			musb_ep->end_point.name, csr);
+
+		/* For short_not_ok type transfers and mode0 transfers */
+		if (dma->desired_mode == 0 || request->short_not_ok)
+			return;
+
+		if (!(csr & MUSB_RXCSR_RXPKTRDY)) {
+			dev_dbg(musb->controller,
+				"%s: %s, DMA busy and Packet not ready\n",
+				__func__, musb_ep->end_point.name);
+			return;
+		}
+
+		/* For Mode1 we get here for the last short packet */
+		len = musb_readw(epio, MUSB_RXCOUNT);
+
+		/* We should get here only for a short packet.*/
+		if (len == musb_ep->packet_sz) {
+			dev_dbg(musb->controller,
+				"%s: %s, Packet not short RXCOUNT=%d\n",
+				__func__, musb_ep->end_point.name, len);
+			return;
+		}
+		/* Pause the channel to get the correct transfer residue.*/
+		status = c->channel_pause(musb_ep->dma);
+		residue = c->tx_status(musb_ep->dma);
+		status = c->check_residue(musb_ep->dma, residue);
+
+		if (status) {
+			/* Something's wrong */
+			status = c->channel_resume(musb_ep->dma);
+			return;
+		}
+		/* In cases when we don't know the transfer length the short
+		 * packet indicates end of current transfer.
+		 */
+		status = c->channel_abort(musb_ep->dma);
+		/* Update with the actual number of bytes transferred */
+		request->actual = musb_ep->dma->prog_len - residue;
+		/* Clear DMA bits in the CSR */
+		csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAENAB
+				| MUSB_RXCSR_DMAMODE);
+		musb_writew(epio, MUSB_RXCSR, csr);
+		/* Proceed to read the short packet */
+		rxstate(musb, req);
+		/* Don't program next transfer, it will tamper with the DMA
+		 * busy condition. Wait for next OUT
+		 */
 		return;
 	}
 
@@ -936,6 +931,26 @@ void musb_g_rx(struct musb *musb, u8 epnum)
 			musb_writew(epio, MUSB_RXCSR, csr);
 		}
 
+		/* We get here after DMA completion */
+		if ((dma->desired_mode == 1) && (!request->short_not_ok)) {
+			/* Incomplete? wait for next OUT packet */
+			if (request->actual < request->length) {
+				dev_dbg(musb->controller,
+					"%s: %s, Wait for next OUT\n",
+					__func__, musb_ep->end_point.name);
+			} else if (request->actual == request->length) {
+				dev_dbg(musb->controller,
+					"%s: %s, Transfer over mode1 done\n",
+					__func__, musb_ep->end_point.name);
+				musb_g_giveback(musb_ep, request, 0);
+			} else {
+				dev_dbg(musb->controller,
+					"%s: %s, Transfer length exceeded!!\n",
+					__func__, musb_ep->end_point.name);
+			}
+			return;
+		}
+
 		/* incomplete, and not short? wait for next IN packet */
 		if ((request->actual < request->length)
 				&& (musb_ep->dma->actual_len
@@ -1305,8 +1320,24 @@ static int musb_gadget_queue(struct usb_ep *ep, struct usb_request *req,
 	list_add_tail(&request->list, &musb_ep->req_list);
 
 	/* it this is the head of the queue, start i/o ... */
-	if (!musb_ep->busy && &request->list == musb_ep->req_list.next)
+	if (!musb_ep->busy && &request->list == musb_ep->req_list.next) {
+
+		/* In case of RX, if there is no packet pending to be read
+		 * from fifo then wait for next interrupt
+		 */
+		if (!request->tx) {
+			if (!musb_ep->rx_pending) {
+				dev_dbg(musb->controller, "No packet pending for %s\n",
+					ep->name);
+				goto cleanup;
+			} else {
+				musb_ep->rx_pending = 0;
+				dev_dbg(musb->controller, "Read packet from fifo %s\n",
+					ep->name);
+			}
+		}
 		musb_ep_restart(musb, request);
+	}
 
 cleanup:
 	spin_unlock_irqrestore(&musb->lock, lockflags);
diff --git a/drivers/usb/musb/musb_gadget.h b/drivers/usb/musb/musb_gadget.h
index 66b7c5e..55eb686 100644
--- a/drivers/usb/musb/musb_gadget.h
+++ b/drivers/usb/musb/musb_gadget.h
@@ -90,6 +90,9 @@ struct musb_ep {
 	u8				busy;
 
 	u8				hb_mult;
+
+	/* true if packet is received in fifo and req_list is empty */
+	u8				rx_pending;
 };
 
 static inline struct musb_ep *to_musb_ep(struct usb_ep *ep)
-- 
1.7.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux