[PATCH v2] DMA: AT91: Get residual bytes in dma buffer

Elen Song elen.song at atmel.com
Fri Oct 26 03:32:26 EDT 2012


Add support for returning the residue for a particular descriptor by
reading the BTSIZE in CTRLA register.

- If the cookie has completed successfully, the residue will be zero.
- If the cookie is in progress or the channel is paused, it will be
  the number of bytes yet to be transferred.
- If the cookie is queued, it will be the number of bytes in the
  descriptor.
- If get residue error, the cookie will be turn into error status,
  and dma transfer will be terminated.

Signed-off-by: Elen Song <elen.song at atmel.com>
---
Change compare to v1:
1. Do not need to use DMA_CTRL_ACK flag to know descriptor transfer complete.
2. Pause dma before read residue.
3. Change comment.

 drivers/dma/at_hdmac.c      |  137 +++++++++++++++++++++++++++++++++++++++----
 drivers/dma/at_hdmac_regs.h |    5 ++
 2 files changed, 131 insertions(+), 11 deletions(-)

diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
index 13a02f4..c771e83 100644
--- a/drivers/dma/at_hdmac.c
+++ b/drivers/dma/at_hdmac.c
@@ -229,6 +229,101 @@ static void atc_dostart(struct at_dma_chan *atchan, struct at_desc *first)
 	vdbg_dump_regs(atchan);
 }
 
+/*
+ * atc_get_current_descriptors -
+ * locate the descriptor which equal to physical address in DSCR
+ * @atchan: the channel we want to start
+ * @dscr_addr: physical descriptor address in DSCR
+ */
+static struct at_desc *atc_get_current_descriptors(struct at_dma_chan *atchan,
+							u32 dscr_addr)
+{
+	struct at_desc  *desc, *_desc, *child, *desc_cur = NULL;
+
+	list_for_each_entry_safe(desc, _desc, &atchan->active_list, desc_node) {
+
+		if (desc->lli.dscr == dscr_addr) {
+			desc_cur = desc;
+			break;
+		}
+
+		list_for_each_entry(child, &desc->tx_list, desc_node) {
+
+			if (child->lli.dscr == dscr_addr) {
+				desc_cur = child;
+				break;
+			}
+		}
+	}
+
+	return desc_cur;
+}
+
+/*
+ * atc_get_bytes_left -
+ * Get the number of bytes residue in dma buffer,
+ * the channel should be paused when calling this
+ * @atchan: the channel we want to start
+ */
+static int atc_get_bytes_left(struct at_dma_chan *atchan)
+{
+	struct at_desc *desc_first;
+	int left = 0, count = 0;
+	u32 dscr_addr;
+
+	/* First descriptor embedds the transaction length */
+	desc_first = atc_first_active(atchan);
+	if (!desc_first)
+		return -EINVAL;
+	/*
+	 * Initialize necessary values in the first time.
+	 * We use desc_cur to save current descriptor,
+	 * save_len record residual dma buffer length.
+	 */
+	if (!atchan->desc_cur &&
+	    !atchan->save_len) {
+		atchan->desc_cur = desc_first;
+		atchan->save_len = desc_first->len;
+	}
+	/*
+	 * This happens when current descriptor transfer complete.
+	 * When transfer terminated, we reset the necessary values.
+	 * Otherwise, we calculate the remain buffer size.
+	 */
+	if (test_and_clear_bit(ATC_IS_BTC, &atchan->status)) {
+		dscr_addr = channel_readl(atchan, DSCR);
+		/* End of a single or cyclic transfer */
+		if (dscr_addr == 0 || dscr_addr == desc_first->lli.dscr) {
+			atchan->desc_cur = NULL;
+			atchan->save_len = 0;
+		/*
+		 * Current descriptor transfer done and switch to next.
+		 * The residual buffer size should
+		 * reduce current descriptor length.
+		 */
+		} else {
+			atchan->desc_cur = atc_get_current_descriptors(atchan,
+								dscr_addr);
+			if (!atchan->desc_cur)
+				return -EINVAL;
+			atchan->save_len -= (atchan->desc_cur->lli.ctrla
+						& ATC_BTSIZE_MAX);
+		}
+
+		left = atchan->save_len;
+	}
+
+	/*
+	 * Get residual bytes when current descriptor transfer in progress.
+	 */
+	if (atchan->desc_cur) {
+		count = channel_readl(atchan, CTRLA) & ATC_BTSIZE_MAX;
+		left = atchan->save_len - count;
+	}
+
+	return left;
+}
+
 /**
  * atc_chain_complete - finish work for one transaction chain
  * @atchan: channel we work on
@@ -494,6 +589,8 @@ static irqreturn_t at_dma_interrupt(int irq, void *dev_id)
 					/* Give information to tasklet */
 					set_bit(ATC_IS_ERROR, &atchan->status);
 				}
+				if (pending & AT_DMA_BTC(i))
+					set_bit(ATC_IS_BTC, &atchan->status);
 				tasklet_schedule(&atchan->tasklet);
 				ret = IRQ_HANDLED;
 			}
@@ -1034,32 +1131,50 @@ atc_tx_status(struct dma_chan *chan,
 	dma_cookie_t		last_complete;
 	unsigned long		flags;
 	enum dma_status		ret;
-
-	spin_lock_irqsave(&atchan->lock, flags);
+	int bytes = 0;
 
 	ret = dma_cookie_status(chan, cookie, txstate);
-	if (ret != DMA_SUCCESS) {
-		atc_cleanup_descriptors(atchan);
-
-		ret = dma_cookie_status(chan, cookie, txstate);
+	/* Residue is zero while transactions complete */
+	if (ret == DMA_SUCCESS) {
+		dma_set_residue(txstate, 0);
+		return ret;
 	}
+	/*
+	 * There's no point calculating the residue if there's
+	 * no txstate to store the value.
+	 */
+	if (!txstate)
+		goto err_dma;
 
-	last_complete = chan->completed_cookie;
-	last_used = chan->cookie;
+	/* Channel should be paused before get residue */
+	if (!atc_chan_is_paused(atchan))
+		atc_control(chan, DMA_PAUSE, 0);
+
+	spin_lock_irqsave(&atchan->lock, flags);
+
+	/*  Get number of bytes left in the active transactions */
+	bytes = atc_get_bytes_left(atchan);
 
 	spin_unlock_irqrestore(&atchan->lock, flags);
 
-	if (ret != DMA_SUCCESS)
-		dma_set_residue(txstate, atc_first_active(atchan)->len);
+	if (bytes < 0)
+		goto err_dma;
+	else
+		dma_set_residue(txstate, bytes);
 
 	if (atc_chan_is_paused(atchan))
-		ret = DMA_PAUSED;
+		atc_control(chan, DMA_RESUME, 0);
 
 	dev_vdbg(chan2dev(chan), "tx_status %d: cookie = %d (d%d, u%d)\n",
 		 ret, cookie, last_complete ? last_complete : 0,
 		 last_used ? last_used : 0);
 
 	return ret;
+
+err_dma:
+	atc_control(chan, DMA_TERMINATE_ALL, 0);
+	ret = DMA_ERROR;
+	return ret;
 }
 
 /**
diff --git a/drivers/dma/at_hdmac_regs.h b/drivers/dma/at_hdmac_regs.h
index 116e4ad..b1861cb 100644
--- a/drivers/dma/at_hdmac_regs.h
+++ b/drivers/dma/at_hdmac_regs.h
@@ -211,6 +211,7 @@ txd_to_at_desc(struct dma_async_tx_descriptor *txd)
 enum atc_status {
 	ATC_IS_ERROR = 0,
 	ATC_IS_PAUSED = 1,
+	ATC_IS_BTC = 2,
 	ATC_IS_CYCLIC = 24,
 };
 
@@ -226,12 +227,14 @@ enum atc_status {
  * @save_cfg: configuration register that is saved on suspend/resume cycle
  * @save_dscr: for cyclic operations, preserve next descriptor address in
  *             the cyclic list on suspend/resume cycle
+ * @save_len: to record dma buffer length
  * @dma_sconfig: configuration for slave transfers, passed via DMA_SLAVE_CONFIG
  * @lock: serializes enqueue/dequeue operations to descriptors lists
  * @active_list: list of descriptors dmaengine is being running on
  * @queue: list of descriptors ready to be submitted to engine
  * @free_list: list of descriptors usable by the channel
  * @descs_allocated: records the actual size of the descriptor pool
+ * @desc_cur: current descriptor
  */
 struct at_dma_chan {
 	struct dma_chan		chan_common;
@@ -242,6 +245,7 @@ struct at_dma_chan {
 	struct tasklet_struct	tasklet;
 	u32			save_cfg;
 	u32			save_dscr;
+	u32			save_len;
 	struct dma_slave_config dma_sconfig;
 
 	spinlock_t		lock;
@@ -251,6 +255,7 @@ struct at_dma_chan {
 	struct list_head	queue;
 	struct list_head	free_list;
 	unsigned int		descs_allocated;
+	struct at_desc		*desc_cur;
 };
 
 #define	channel_readl(atchan, name) \
-- 
1.7.9.5




More information about the linux-arm-kernel mailing list