[PATCH] usb: musb: Enable DMA Mode1 for device mode RX in ux500 platform

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

 



From: supriya karanth <supriya.karanth@xxxxxxxxxxxxxx>

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 |  125 +++++++++++++++++++++++++--
 drivers/usb/musb/musb_gadget.h |    3 +
 drivers/usb/musb/ux500_dma.c   |  184 +++++++++++++++++++++++++++++++++++++++-
 4 files changed, 312 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/musb/musb_dma.h b/drivers/usb/musb/musb_dma.h
index 24d3921..bcad195 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 b6b84da..b351f52 100644
--- a/drivers/usb/musb/musb_gadget.c
+++ b/drivers/usb/musb/musb_gadget.c
@@ -806,14 +806,10 @@ static void rxstate(struct musb *musb, struct musb_request *req)
 				/* 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);
+							channel->max_len);
 
 				csr &= ~MUSB_RXCSR_DMAMODE;
 				csr |= (MUSB_RXCSR_DMAENAB |
@@ -821,13 +817,23 @@ static void rxstate(struct musb *musb, struct musb_request *req)
 
 				musb_writew(epio, MUSB_RXCSR, csr);
 
-				if (transfer_size <= musb_ep->packet_sz) {
+				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);
+
+					/* 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;
 				}
 
 				if (c->channel_program(channel,
@@ -835,9 +841,16 @@ static void rxstate(struct musb *musb, struct musb_request *req)
 							channel->desired_mode,
 							request->dma
 							+ request->actual,
-							transfer_size))
+							transfer_size)){
+
+					dev_dbg(musb->controller,
+						"%s OUT/RX DMA Size %d/%d maxpacket %d\n",
+							musb_ep->end_point.name,
+							len, transfer_size,
+							musb_ep->packet_sz);
 
 					return;
+				}
 			}
 #endif	/* Mentor's DMA */
 
@@ -915,6 +928,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;
@@ -924,8 +941,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;
 
@@ -960,7 +981,57 @@ void musb_g_rx(struct musb *musb, u8 epnum)
 		/* "should not happen"; likely RXPKTRDY pending for DMA */
 		dev_dbg(musb->controller, "%s busy, csr %04x\n",
 			musb_ep->end_point.name, csr);
+#ifndef CONFIG_USB_UX500_DMA
 		return;
+#else
+		/* 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;
+#endif
 	}
 
 	if (dma && (csr & MUSB_RXCSR_DMAENAB)) {
@@ -988,6 +1059,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
@@ -1361,8 +1452,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)
diff --git a/drivers/usb/musb/ux500_dma.c b/drivers/usb/musb/ux500_dma.c
index f1059e7..6eddf14 100644
--- a/drivers/usb/musb/ux500_dma.c
+++ b/drivers/usb/musb/ux500_dma.c
@@ -106,7 +106,8 @@ static bool ux500_configure_channel(struct dma_channel *channel,
 
 	direction = ux500_channel->is_tx ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
 	addr_width = (len & 0x3) ? DMA_SLAVE_BUSWIDTH_1_BYTE :
-					DMA_SLAVE_BUSWIDTH_4_BYTES;
+				((dma_addr & 0x2) ? DMA_SLAVE_BUSWIDTH_2_BYTES :
+					DMA_SLAVE_BUSWIDTH_4_BYTES);
 
 	slave_conf.direction = direction;
 	slave_conf.src_addr = usb_fifo_addr;
@@ -134,6 +135,15 @@ static bool ux500_configure_channel(struct dma_channel *channel,
 	return true;
 }
 
+/**
+ * ux500_dma_controller_allocate() - allocates the DMA channels
+ * @c: pointer to DMA controller
+ * @hw_ep: pointer to endpoint
+ * @is_tx: transmit or receive direction
+ *
+ * This function allocates the DMA channel and initializes
+ * the channel
+*/
 static struct dma_channel *ux500_dma_channel_allocate(struct dma_controller *c,
 				struct musb_hw_ep *hw_ep, u8 is_tx)
 {
@@ -173,6 +183,13 @@ static struct dma_channel *ux500_dma_channel_allocate(struct dma_controller *c,
 	return &(ux500_channel->channel);
 }
 
+/**
+ * ux500_dma_channel_release() - releases the DMA channel
+ * @channel:	channel to be released
+ *
+ * This function releases the DMA channel
+ *
+*/
 static void ux500_dma_channel_release(struct dma_channel *channel)
 {
 	struct ux500_dma_channel *ux500_channel = channel->private_data;
@@ -192,13 +209,22 @@ static int ux500_dma_is_compatible(struct dma_channel *channel,
 {
 	if ((maxpacket & 0x3)		||
 		((int)buf & 0x3)	||
-		(length < 512)		||
-		(length & 0x3))
+		(length < 512))
 		return false;
 	else
 		return true;
 }
 
+/**
+ * ux500_dma_channel_program() - Configures the channel and initiates transfer
+ * @channel:	pointer to DMA channel
+ * @packet_sz:	packet size
+ * @mode: mode
+ * @dma_addr: physical address of memory
+ * @len: length
+ *
+ * This function configures the channel and initiates the DMA transfer
+*/
 static int ux500_dma_channel_program(struct dma_channel *channel,
 				u16 packet_sz, u8 mode,
 				dma_addr_t dma_addr, u32 len)
@@ -220,6 +246,12 @@ static int ux500_dma_channel_program(struct dma_channel *channel,
 	return ret;
 }
 
+/**
+ * ux500_dma_channel_abort() - aborts the DMA transfer
+ * @channel:	pointer to DMA channel.
+ *
+ * This function aborts the DMA transfer.
+*/
 static int ux500_dma_channel_abort(struct dma_channel *channel)
 {
 	struct ux500_dma_channel *ux500_channel = channel->private_data;
@@ -254,6 +286,124 @@ static int ux500_dma_channel_abort(struct dma_channel *channel)
 	return 0;
 }
 
+/**
+ * ux500_dma_channel_pause() - pauses the DMA transfer
+ * @channel:	pointer to DMA channel.
+ *
+ * This function pauses the DMA transfer. This is needed to get
+ * the correct residue of an ongoing DMA transfer
+*/
+static int ux500_dma_channel_pause(struct dma_channel *channel)
+{
+	struct ux500_dma_channel *ux500_channel = channel->private_data;
+	struct ux500_dma_controller *controller = ux500_channel->controller;
+	struct musb *musb = controller->private_data;
+	int status;
+
+	status = ux500_channel->dma_chan->device->
+			device_control(ux500_channel->dma_chan,
+			DMA_PAUSE, 0);
+
+	dev_dbg(musb->controller,
+		"%s channel=%d, is_tx=%d, status=%d\n",
+		__func__, ux500_channel->ch_num, ux500_channel->is_tx
+		, status);
+	return status;
+}
+
+/**
+ * ux500_dma_channel_resume() - resumes the DMA transfer
+ * @channel:	pointer to DMA channel.
+ *
+ * This function resumes a paused DMA transfer.
+*/
+static int ux500_dma_channel_resume(struct dma_channel *channel)
+{
+	struct ux500_dma_channel *ux500_channel = channel->private_data;
+	struct ux500_dma_controller *controller = ux500_channel->controller;
+	struct musb *musb = controller->private_data;
+	int status;
+
+	status = ux500_channel->dma_chan->device->
+			device_control(ux500_channel->dma_chan,
+			DMA_RESUME, 0);
+	dev_dbg(musb->controller,
+		"%s channel=%d, is_tx=%d, status=%d\n",
+		__func__, ux500_channel->ch_num, ux500_channel->is_tx
+		, status);
+	return status;
+}
+
+/**
+ * ux500_dma_tx_status() - Gets the residue of an ongoing DMA transfer
+ * @channel:    pointer to DMA channel
+ *
+ * This function will get the number of bytes left to be transferred
+ * over the DMA
+ */
+static int ux500_dma_tx_status(struct dma_channel *channel)
+{
+	struct ux500_dma_channel *ux500_channel = channel->private_data;
+	struct ux500_dma_controller *controller = ux500_channel->controller;
+	struct musb *musb = controller->private_data;
+	struct dma_tx_state txstate;
+
+	ux500_channel->dma_chan->device->
+			device_tx_status(ux500_channel->dma_chan,
+					ux500_channel->cookie, &txstate);
+	dev_dbg(musb->controller,
+		"%s channel=%d, is_tx=%d, residue=%d\n",
+		__func__, ux500_channel->ch_num, ux500_channel->is_tx
+		, txstate.residue);
+	return txstate.residue;
+}
+
+/**
+ * ux500_dma_check_residue() - Checks if the DMA transfer residue is valid
+ * @channel:    pointer to DMA channel
+ */
+static int ux500_dma_check_residue(struct dma_channel *channel, u32 residue)
+{
+	struct ux500_dma_channel *ux500_channel = channel->private_data;
+	struct ux500_dma_controller *controller = ux500_channel->controller;
+	struct musb *musb = controller->private_data;
+	int status;
+
+	dev_dbg(musb->controller,
+		"%s channel=%d, is_tx=%d, residue=%d\n",
+		__func__, ux500_channel->ch_num, ux500_channel->is_tx
+		, residue);
+
+	/* In cases where we know the transfer length and were expecting
+	 * a DMA completion we could get into the DMA busy condition
+	 * here if the next packet is short and the EP interrupt occurs
+	 * before we recieve dma_completion interrupt for current transfer
+	 * Wait for dma_completion. MUSB will interrupt us again for this
+	 * short packet when we clear the DMA bits
+	 */
+	if (!residue) {
+		dev_dbg(musb->controller,
+		      "%s: Wait for DMA completion\n",
+		      __func__);
+		status = -EINPROGRESS;
+	} else if (residue == ux500_channel->channel.prog_len) {
+		/* Nothing transferred over DMA? */
+		WARN_ON(1);
+		status = -EINVAL;
+	} else {
+		/* residue looks OK */
+		status = 0;
+	}
+
+	return status;
+}
+
+/**
+ * ux500_dma_controller_stop() - releases all the channels and frees the DMA pipes
+ * @c: pointer to DMA controller
+ *
+ * This function frees all of the logical channels and frees the DMA pipes
+*/
 static int ux500_dma_controller_stop(struct dma_controller *c)
 {
 	struct ux500_dma_controller *controller = container_of(c,
@@ -285,6 +435,15 @@ static int ux500_dma_controller_stop(struct dma_controller *c)
 	return 0;
 }
 
+
+/**
+ * ux500_dma_controller_start() - creates the logical channels pool and registers callbacks
+ * @c:	pointer to DMA Controller
+ *
+ * This function requests the logical channels from the DMA driver and creates
+ * logical channels based on event lines and also registers the callbacks which
+ * are invoked after data transfer in the transmit or receive direction.
+*/
 static int ux500_dma_controller_start(struct dma_controller *c)
 {
 	struct ux500_dma_controller *controller = container_of(c,
@@ -356,6 +515,12 @@ static int ux500_dma_controller_start(struct dma_controller *c)
 	return 0;
 }
 
+/**
+ * dma_controller_destroy() - deallocates the DMA controller
+ * @c:	pointer to dma controller.
+ *
+ * This function deallocates the DMA controller.
+*/
 void dma_controller_destroy(struct dma_controller *c)
 {
 	struct ux500_dma_controller *controller = container_of(c,
@@ -364,6 +529,15 @@ void dma_controller_destroy(struct dma_controller *c)
 	kfree(controller);
 }
 
+/**
+ * dma_controller_create() - creates the dma controller and initializes callbacks
+ *
+ * @musb:	pointer to mentor core driver data instance|
+ * @base:	base address of musb registers.
+ *
+ * This function creates the DMA controller and initializes the callbacks
+ * that are invoked from the Mentor IP core.
+*/
 struct dma_controller *__devinit
 dma_controller_create(struct musb *musb, void __iomem *base)
 {
@@ -387,6 +561,10 @@ dma_controller_create(struct musb *musb, void __iomem *base)
 	controller->controller.channel_release = ux500_dma_channel_release;
 	controller->controller.channel_program = ux500_dma_channel_program;
 	controller->controller.channel_abort = ux500_dma_channel_abort;
+	controller->controller.channel_pause = ux500_dma_channel_pause;
+	controller->controller.channel_resume = ux500_dma_channel_resume;
+	controller->controller.tx_status = ux500_dma_tx_status;
+	controller->controller.check_residue = ux500_dma_check_residue;
 	controller->controller.is_compatible = ux500_dma_is_compatible;
 
 	return &controller->controller;
-- 
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