All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/2] dma: at_hdmac: Fix residue calculation and add mem to
@ 2015-02-23 16:54 Torsten Fleischer
  2015-02-23 16:54 ` [PATCH v3 1/2] dma: at_hdmac: Fix calculation of the residual bytes Torsten Fleischer
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Torsten Fleischer @ 2015-02-23 16:54 UTC (permalink / raw)
  To: nicolas.ferre, dan.j.williams, vinod.koul, linux-arm-kernel,
	dmaengine, linux-kernel
  Cc: Torsten Fleischer

From: Torsten Fleischer <torfl6749@gmail.com>

This series fixes the calculation of the residual bytes and adds support for
memory to memory scatter-gather transfers.

Changes from V1:
   * Fixed coding style of the multi-line comments.
   * Improved accuracy of the residue calculation.

Changes from V2:
   * Fixed residue calculation for 'slave_sg' transfers.

Torsten Fleischer (2):
  dma: at_hdmac: Fix calculation of the residual bytes
  dma: at_hdmac: Add support for memory to memory sg transfers

 drivers/dma/at_hdmac.c      | 359 ++++++++++++++++++++++++++++++++++----------
 drivers/dma/at_hdmac_regs.h |   7 +-
 2 files changed, 279 insertions(+), 87 deletions(-)

-- 
2.1.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v3 1/2] dma: at_hdmac: Fix calculation of the residual bytes
  2015-02-23 16:54 [PATCH v3 0/2] dma: at_hdmac: Fix residue calculation and add mem to Torsten Fleischer
@ 2015-02-23 16:54 ` Torsten Fleischer
  2015-03-03 18:26     ` Nicolas Ferre
  2015-02-23 16:54 ` [PATCH v3 2/2] dma: at_hdmac: Add support for memory to memory sg transfers Torsten Fleischer
  2015-03-13  9:29   ` Vinod Koul
  2 siblings, 1 reply; 9+ messages in thread
From: Torsten Fleischer @ 2015-02-23 16:54 UTC (permalink / raw)
  To: nicolas.ferre, dan.j.williams, vinod.koul, linux-arm-kernel,
	dmaengine, linux-kernel
  Cc: Torsten Fleischer

From: Torsten Fleischer <torfl6749@gmail.com>

This patch fixes the following issues regarding to the calculation of the
residue:

1. The residue is always calculated for the current transfer even if the
cookie is associated to a pending transfer.

2. For scatter/gather DMA the calculation of the residue for the current
transfer doesn't include the bytes of the child descriptors that are already
transferred.
It only calculates the difference between the transfer's total length minus
the number of bytes that are already transferred for the current child
descriptor.
For example: There is a scatter/gather DMA transfer with a total length of
1 MByte. Getting the residue several times while the transfer is running shows
something like that:

1: residue = 975584
2: residue = 1002766
3: residue = 992627
4: residue = 983767
5: residue = 985694
6: residue = 1008094
7: residue = 1009741
8: residue = 1011195

3. The driver stores the residue but never resets it when starting a new
transfer.
For example: If there are two subsequent DMA transfers. The first one with
a total length of 1 MByte and the second one with a total length of 1 kByte.
Getting the residue for both transfers shows something like that:

transfer 1: residue = 975584
transfer 2: residue = 1048380

Changes from V1:
   * Fixed coding style of the multi-line comments.
   * Improved accuracy of the residue calculation when the transfer for the
     first descriptor is active.

Changes from V2:
   * Member 'tx_width' of 'struct at_desc' restored, because the transfer width
     can't be derived from the source width when using "slave_sg".
     The transfer width is needed for the calculation of the residue if either
     the transfer of the first or the last descriptor is in progress.
     In the case of a "memory_to_memory_sg" transfer (part of this patch
     series) the transfer width of both descriptors may differ. Thus it is
     required to additionally set 'tx_width' of the last descriptor.
   * Added functions for multiply used calculations.

Signed-off-by: Torsten Fleischer <torfl6749@gmail.com>
---
 drivers/dma/at_hdmac.c      | 184 +++++++++++++++++++++++++++-----------------
 drivers/dma/at_hdmac_regs.h |   7 +-
 2 files changed, 115 insertions(+), 76 deletions(-)

diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
index 1e1a4c5..0b4fc6f 100644
--- a/drivers/dma/at_hdmac.c
+++ b/drivers/dma/at_hdmac.c
@@ -238,93 +238,126 @@ static void atc_dostart(struct at_dma_chan *atchan, struct at_desc *first)
 }
 
 /*
- * 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
+ * atc_get_desc_by_cookie - get the descriptor of a cookie
+ * @atchan: the DMA channel
+ * @cookie: the cookie to get the descriptor for
  */
-static struct at_desc *atc_get_current_descriptors(struct at_dma_chan *atchan,
-							u32 dscr_addr)
+static struct at_desc *atc_get_desc_by_cookie(struct at_dma_chan *atchan,
+						dma_cookie_t cookie)
 {
-	struct at_desc  *desc, *_desc, *child, *desc_cur = NULL;
+	struct at_desc *desc, *_desc;
 
-	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_safe(desc, _desc, &atchan->queue, desc_node) {
+		if (desc->txd.cookie == cookie)
+			return desc;
+	}
 
-		list_for_each_entry(child, &desc->tx_list, desc_node) {
-			if (child->lli.dscr == dscr_addr) {
-				desc_cur = child;
-				break;
-			}
-		}
+	list_for_each_entry_safe(desc, _desc, &atchan->active_list, desc_node) {
+		if (desc->txd.cookie == cookie)
+			return desc;
 	}
 
-	return desc_cur;
+	return NULL;
 }
 
-/*
- * atc_get_bytes_left -
- * Get the number of bytes residue in dma buffer,
- * @chan: the channel we want to start
+/**
+ * atc_calc_bytes_left - calculates the number of bytes left according to the
+ * value read from CTRLA.
+ *
+ * @current_len: the number of bytes left before reading CTRLA
+ * @ctrla: the value of CTRLA
+ * @desc: the descriptor containing the transfer width
+ */
+static inline int atc_calc_bytes_left(int current_len, u32 ctrla,
+					struct at_desc *desc)
+{
+	return current_len - ((ctrla & ATC_BTSIZE_MAX) << desc->tx_width);
+}
+
+/**
+ * atc_calc_bytes_left_from_reg - calculates the number of bytes left according
+ * to the current value of CTRLA.
+ *
+ * @current_len: the number of bytes left before reading CTRLA
+ * @atchan: the channel to read CTRLA for
+ * @desc: the descriptor containing the transfer width
+ */
+static inline int atc_calc_bytes_left_from_reg(int current_len,
+			struct at_dma_chan *atchan, struct at_desc *desc)
+{
+	u32 ctrla = channel_readl(atchan, CTRLA);
+
+	return atc_calc_bytes_left(current_len, ctrla, desc);
+}
+
+/**
+ * atc_get_bytes_left - get the number of bytes residue for a cookie
+ * @chan: DMA channel
+ * @cookie: transaction identifier to check status of
  */
-static int atc_get_bytes_left(struct dma_chan *chan)
+static int atc_get_bytes_left(struct dma_chan *chan, dma_cookie_t cookie)
 {
 	struct at_dma_chan      *atchan = to_at_dma_chan(chan);
-	struct at_dma           *atdma = to_at_dma(chan->device);
-	int	chan_id = atchan->chan_common.chan_id;
 	struct at_desc *desc_first = atc_first_active(atchan);
-	struct at_desc *desc_cur;
-	int ret = 0, count = 0;
+	struct at_desc *desc;
+	int ret;
+	u32 ctrla, dscr;
 
 	/*
-	 * Initialize necessary values in the first time.
-	 * remain_desc record remain desc length.
+	 * If the cookie doesn't match to the currently running transfer then
+	 * we can return the total length of the associated DMA transfer,
+	 * because it is still queued.
 	 */
-	if (atchan->remain_desc == 0)
-		/* First descriptor embedds the transaction length */
-		atchan->remain_desc = desc_first->len;
+	desc = atc_get_desc_by_cookie(atchan, cookie);
+	if (desc == NULL)
+		return -EINVAL;
+	else if (desc != desc_first)
+		return desc->total_len;
 
-	/*
-	 * This happens when current descriptor transfer complete.
-	 * The residual buffer size should reduce current descriptor length.
-	 */
-	if (unlikely(test_bit(ATC_IS_BTC, &atchan->status))) {
-		clear_bit(ATC_IS_BTC, &atchan->status);
-		desc_cur = atc_get_current_descriptors(atchan,
-						channel_readl(atchan, DSCR));
-		if (!desc_cur) {
-			ret = -EINVAL;
-			goto out;
-		}
+	/* cookie matches to the currently running transfer */
+	ret = desc_first->total_len;
 
-		count = (desc_cur->lli.ctrla & ATC_BTSIZE_MAX)
-			<< desc_first->tx_width;
-		if (atchan->remain_desc < count) {
-			ret = -EINVAL;
-			goto out;
+	if (desc_first->lli.dscr) {
+		/* hardware linked list transfer */
+
+		/*
+		 * Calculate the residue by removing the length of the child
+		 * descriptors already transferred from the total length.
+		 * To get the current child descriptor we can use the value of
+		 * the channel's DSCR register and compare it against the value
+		 * of the hardware linked list structure of each child
+		 * descriptor.
+		 */
+
+		ctrla = channel_readl(atchan, CTRLA);
+		rmb(); /* ensure CTRLA is read before DSCR */
+		dscr = channel_readl(atchan, DSCR);
+
+		/* for the first descriptor we can be more accurate */
+		if (desc_first->lli.dscr == dscr)
+			return atc_calc_bytes_left(ret, ctrla, desc_first);
+
+		ret -= desc_first->len;
+		list_for_each_entry(desc, &desc_first->tx_list, desc_node) {
+			if (desc->lli.dscr == dscr)
+				break;
+
+			ret -= desc->len;
 		}
 
-		atchan->remain_desc -= count;
-		ret = atchan->remain_desc;
-	} else {
 		/*
-		 * Get residual bytes when current
-		 * descriptor transfer in progress.
+		 * For the last descriptor in the chain we can calculate
+		 * the remaining bytes using the channel's register.
+		 * Note that the transfer width of the first and last
+		 * descriptor may differ.
 		 */
-		count = (channel_readl(atchan, CTRLA) & ATC_BTSIZE_MAX)
-				<< (desc_first->tx_width);
-		ret = atchan->remain_desc - count;
+		if (!desc->lli.dscr)
+			ret = atc_calc_bytes_left_from_reg(ret, atchan, desc);
+	} else {
+		/* single transfer */
+		ret = atc_calc_bytes_left_from_reg(ret, atchan, desc_first);
 	}
-	/*
-	 * Check fifo empty.
-	 */
-	if (!(dma_readl(atdma, CHSR) & AT_DMA_EMPT(chan_id)))
-		atc_issue_pending(chan);
 
-out:
 	return ret;
 }
 
@@ -539,8 +572,6 @@ 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;
 			}
@@ -653,14 +684,18 @@ atc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
 		desc->lli.ctrlb = ctrlb;
 
 		desc->txd.cookie = 0;
+		desc->len = xfer_count << src_width;
 
 		atc_desc_chain(&first, &prev, desc);
 	}
 
 	/* First descriptor of the chain embedds additional information */
 	first->txd.cookie = -EBUSY;
-	first->len = len;
+	first->total_len = len;
+
+	/* set transfer width for the calculation of the residue */
 	first->tx_width = src_width;
+	prev->tx_width = src_width;
 
 	/* set end-of-link to the last link descriptor of list*/
 	set_desc_eol(desc);
@@ -752,6 +787,7 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
 					| ATC_SRC_WIDTH(mem_width)
 					| len >> mem_width;
 			desc->lli.ctrlb = ctrlb;
+			desc->len = len;
 
 			atc_desc_chain(&first, &prev, desc);
 			total_len += len;
@@ -792,6 +828,7 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
 					| ATC_DST_WIDTH(mem_width)
 					| len >> reg_width;
 			desc->lli.ctrlb = ctrlb;
+			desc->len = len;
 
 			atc_desc_chain(&first, &prev, desc);
 			total_len += len;
@@ -806,8 +843,11 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
 
 	/* First descriptor of the chain embedds additional information */
 	first->txd.cookie = -EBUSY;
-	first->len = total_len;
+	first->total_len = total_len;
+
+	/* set transfer width for the calculation of the residue */
 	first->tx_width = reg_width;
+	prev->tx_width = reg_width;
 
 	/* first link descriptor of list is responsible of flags */
 	first->txd.flags = flags; /* client is in control of this ack */
@@ -872,6 +912,7 @@ atc_dma_cyclic_fill_desc(struct dma_chan *chan, struct at_desc *desc,
 				| ATC_FC_MEM2PER
 				| ATC_SIF(atchan->mem_if)
 				| ATC_DIF(atchan->per_if);
+		desc->len = period_len;
 		break;
 
 	case DMA_DEV_TO_MEM:
@@ -883,6 +924,7 @@ atc_dma_cyclic_fill_desc(struct dma_chan *chan, struct at_desc *desc,
 				| ATC_FC_PER2MEM
 				| ATC_SIF(atchan->per_if)
 				| ATC_DIF(atchan->mem_if);
+		desc->len = period_len;
 		break;
 
 	default:
@@ -964,7 +1006,7 @@ atc_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
 
 	/* First descriptor of the chain embedds additional information */
 	first->txd.cookie = -EBUSY;
-	first->len = buf_len;
+	first->total_len = buf_len;
 	first->tx_width = reg_width;
 
 	return &first->txd;
@@ -1118,7 +1160,7 @@ atc_tx_status(struct dma_chan *chan,
 	spin_lock_irqsave(&atchan->lock, flags);
 
 	/*  Get number of bytes left in the active transactions */
-	bytes = atc_get_bytes_left(chan);
+	bytes = atc_get_bytes_left(chan, cookie);
 
 	spin_unlock_irqrestore(&atchan->lock, flags);
 
@@ -1214,7 +1256,6 @@ static int atc_alloc_chan_resources(struct dma_chan *chan)
 
 	spin_lock_irqsave(&atchan->lock, flags);
 	atchan->descs_allocated = i;
-	atchan->remain_desc = 0;
 	list_splice(&tmp_list, &atchan->free_list);
 	dma_cookie_init(chan);
 	spin_unlock_irqrestore(&atchan->lock, flags);
@@ -1257,7 +1298,6 @@ static void atc_free_chan_resources(struct dma_chan *chan)
 	list_splice_init(&atchan->free_list, &list);
 	atchan->descs_allocated = 0;
 	atchan->status = 0;
-	atchan->remain_desc = 0;
 
 	dev_vdbg(chan2dev(chan), "free_chan_resources: done\n");
 }
diff --git a/drivers/dma/at_hdmac_regs.h b/drivers/dma/at_hdmac_regs.h
index d6bba6c..2727ca5 100644
--- a/drivers/dma/at_hdmac_regs.h
+++ b/drivers/dma/at_hdmac_regs.h
@@ -181,8 +181,9 @@ struct at_lli {
  * @at_lli: hardware lli structure
  * @txd: support for the async_tx api
  * @desc_node: node on the channed descriptors list
- * @len: total transaction bytecount
+ * @len: descriptor byte count
  * @tx_width: transfer width
+ * @total_len: total transaction byte count
  */
 struct at_desc {
 	/* FIRST values the hardware uses */
@@ -194,6 +195,7 @@ struct at_desc {
 	struct list_head		desc_node;
 	size_t				len;
 	u32				tx_width;
+	size_t				total_len;
 };
 
 static inline struct at_desc *
@@ -213,7 +215,6 @@ 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,
 };
 
@@ -231,7 +232,6 @@ 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
- * @remain_desc: to save remain desc length
  * @dma_sconfig: configuration for slave transfers, passed via
  * .device_config
  * @lock: serializes enqueue/dequeue operations to descriptors lists
@@ -251,7 +251,6 @@ struct at_dma_chan {
 	struct tasklet_struct	tasklet;
 	u32			save_cfg;
 	u32			save_dscr;
-	u32			remain_desc;
 	struct dma_slave_config dma_sconfig;
 
 	spinlock_t		lock;
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v3 2/2] dma: at_hdmac: Add support for memory to memory sg transfers
  2015-02-23 16:54 [PATCH v3 0/2] dma: at_hdmac: Fix residue calculation and add mem to Torsten Fleischer
  2015-02-23 16:54 ` [PATCH v3 1/2] dma: at_hdmac: Fix calculation of the residual bytes Torsten Fleischer
@ 2015-02-23 16:54 ` Torsten Fleischer
  2015-03-03 18:29     ` Nicolas Ferre
  2015-03-13  9:29   ` Vinod Koul
  2 siblings, 1 reply; 9+ messages in thread
From: Torsten Fleischer @ 2015-02-23 16:54 UTC (permalink / raw)
  To: nicolas.ferre, dan.j.williams, vinod.koul, linux-arm-kernel,
	dmaengine, linux-kernel
  Cc: Torsten Fleischer

From: Torsten Fleischer <torfl6749@gmail.com>

This patch adds support for memory to memory scatter-gather transfers.

Changes from V1:
   * Fixed coding style of the multi-line comments.

Changes from V2:
   * Added setup of 'desc->tx_width' that is needed to calculate the
     residue.

Signed-off-by: Torsten Fleischer <torfl6749@gmail.com>
---
 drivers/dma/at_hdmac.c | 175 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 164 insertions(+), 11 deletions(-)

diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
index 0b4fc6f..57b2141 100644
--- a/drivers/dma/at_hdmac.c
+++ b/drivers/dma/at_hdmac.c
@@ -65,6 +65,21 @@ static void atc_issue_pending(struct dma_chan *chan);
 
 /*----------------------------------------------------------------------*/
 
+static inline unsigned int atc_get_xfer_width(dma_addr_t src, dma_addr_t dst,
+						size_t len)
+{
+	unsigned int width;
+
+	if (!((src | dst  | len) & 3))
+		width = 2;
+	else if (!((src | dst | len) & 1))
+		width = 1;
+	else
+		width = 0;
+
+	return width;
+}
+
 static struct at_desc *atc_first_active(struct at_dma_chan *atchan)
 {
 	return list_first_entry(&atchan->active_list,
@@ -659,16 +674,10 @@ atc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
 	 * We can be a lot more clever here, but this should take care
 	 * of the most common optimization.
 	 */
-	if (!((src | dest  | len) & 3)) {
-		ctrla = ATC_SRC_WIDTH_WORD | ATC_DST_WIDTH_WORD;
-		src_width = dst_width = 2;
-	} else if (!((src | dest | len) & 1)) {
-		ctrla = ATC_SRC_WIDTH_HALFWORD | ATC_DST_WIDTH_HALFWORD;
-		src_width = dst_width = 1;
-	} else {
-		ctrla = ATC_SRC_WIDTH_BYTE | ATC_DST_WIDTH_BYTE;
-		src_width = dst_width = 0;
-	}
+	src_width = dst_width = atc_get_xfer_width(src, dest, len);
+
+	ctrla = ATC_SRC_WIDTH(src_width) |
+		ATC_DST_WIDTH(dst_width);
 
 	for (offset = 0; offset < len; offset += xfer_count << src_width) {
 		xfer_count = min_t(size_t, (len - offset) >> src_width,
@@ -862,6 +871,144 @@ err:
 }
 
 /**
+ * atc_prep_dma_sg - prepare memory to memory scather-gather operation
+ * @chan: the channel to prepare operation on
+ * @dst_sg: destination scatterlist
+ * @dst_nents: number of destination scatterlist entries
+ * @src_sg: source scatterlist
+ * @src_nents: number of source scatterlist entries
+ * @flags: tx descriptor status flags
+ */
+static struct dma_async_tx_descriptor *
+atc_prep_dma_sg(struct dma_chan *chan,
+		struct scatterlist *dst_sg, unsigned int dst_nents,
+		struct scatterlist *src_sg, unsigned int src_nents,
+		unsigned long flags)
+{
+	struct at_dma_chan	*atchan = to_at_dma_chan(chan);
+	struct at_desc		*desc = NULL;
+	struct at_desc		*first = NULL;
+	struct at_desc		*prev = NULL;
+	unsigned int		src_width;
+	unsigned int		dst_width;
+	size_t			xfer_count;
+	u32			ctrla;
+	u32			ctrlb;
+	size_t			dst_len = 0, src_len = 0;
+	dma_addr_t		dst = 0, src = 0;
+	size_t			len = 0, total_len = 0;
+
+	if (unlikely(dst_nents == 0 || src_nents == 0))
+		return NULL;
+
+	if (unlikely(dst_sg == NULL || src_sg == NULL))
+		return NULL;
+
+	ctrlb =   ATC_DEFAULT_CTRLB | ATC_IEN
+		| ATC_SRC_ADDR_MODE_INCR
+		| ATC_DST_ADDR_MODE_INCR
+		| ATC_FC_MEM2MEM;
+
+	/*
+	 * loop until there is either no more source or no more destination
+	 * scatterlist entry
+	 */
+	while (true) {
+
+		/* prepare the next transfer */
+		if (dst_len == 0) {
+
+			/* no more destination scatterlist entries */
+			if (!dst_sg || !dst_nents)
+				break;
+
+			dst = sg_dma_address(dst_sg);
+			dst_len = sg_dma_len(dst_sg);
+
+			dst_sg = sg_next(dst_sg);
+			dst_nents--;
+		}
+
+		if (src_len == 0) {
+
+			/* no more source scatterlist entries */
+			if (!src_sg || !src_nents)
+				break;
+
+			src = sg_dma_address(src_sg);
+			src_len = sg_dma_len(src_sg);
+
+			src_sg = sg_next(src_sg);
+			src_nents--;
+		}
+
+		len = min_t(size_t, src_len, dst_len);
+		if (len == 0)
+			continue;
+
+		/* take care for the alignment */
+		src_width = dst_width = atc_get_xfer_width(src, dst, len);
+
+		ctrla = ATC_SRC_WIDTH(src_width) |
+			ATC_DST_WIDTH(dst_width);
+
+		/*
+		 * The number of transfers to set up refer to the source width
+		 * that depends on the alignment.
+		 */
+		xfer_count = len >> src_width;
+		if (xfer_count > ATC_BTSIZE_MAX) {
+			xfer_count = ATC_BTSIZE_MAX;
+			len = ATC_BTSIZE_MAX << src_width;
+		}
+
+		/* create the transfer */
+		desc = atc_desc_get(atchan);
+		if (!desc)
+			goto err_desc_get;
+
+		desc->lli.saddr = src;
+		desc->lli.daddr = dst;
+		desc->lli.ctrla = ctrla | xfer_count;
+		desc->lli.ctrlb = ctrlb;
+
+		desc->txd.cookie = 0;
+		desc->len = len;
+
+		/*
+		 * Although we only need the transfer width for the first and
+		 * the last descriptor, its easier to set it to all descriptors.
+		 */
+		desc->tx_width = src_width;
+
+		atc_desc_chain(&first, &prev, desc);
+
+		/* update the lengths and addresses for the next loop cycle */
+		dst_len -= len;
+		src_len -= len;
+		dst += len;
+		src += len;
+
+		total_len += len;
+	}
+
+	/* First descriptor of the chain embedds additional information */
+	first->txd.cookie = -EBUSY;
+	first->total_len = total_len;
+
+	/* set end-of-link to the last link descriptor of list*/
+	set_desc_eol(desc);
+
+	first->txd.flags = flags; /* client is in control of this ack */
+
+	return &first->txd;
+
+err_desc_get:
+	atc_desc_put(atchan, first);
+	return NULL;
+}
+
+/**
  * atc_dma_cyclic_check_values
  * Check for too big/unaligned periods and unaligned DMA buffer
  */
@@ -1461,8 +1608,10 @@ static int __init at_dma_probe(struct platform_device *pdev)
 
 	/* setup platform data for each SoC */
 	dma_cap_set(DMA_MEMCPY, at91sam9rl_config.cap_mask);
+	dma_cap_set(DMA_SG, at91sam9rl_config.cap_mask);
 	dma_cap_set(DMA_MEMCPY, at91sam9g45_config.cap_mask);
 	dma_cap_set(DMA_SLAVE, at91sam9g45_config.cap_mask);
+	dma_cap_set(DMA_SG, at91sam9g45_config.cap_mask);
 
 	/* get DMA parameters from controller type */
 	plat_dat = at_dma_get_driver_data(pdev);
@@ -1582,11 +1731,15 @@ static int __init at_dma_probe(struct platform_device *pdev)
 		atdma->dma_common.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
 	}
 
+	if (dma_has_cap(DMA_SG, atdma->dma_common.cap_mask))
+		atdma->dma_common.device_prep_dma_sg = atc_prep_dma_sg;
+
 	dma_writel(atdma, EN, AT_DMA_ENABLE);
 
-	dev_info(&pdev->dev, "Atmel AHB DMA Controller ( %s%s), %d channels\n",
+	dev_info(&pdev->dev, "Atmel AHB DMA Controller ( %s%s%s), %d channels\n",
 	  dma_has_cap(DMA_MEMCPY, atdma->dma_common.cap_mask) ? "cpy " : "",
 	  dma_has_cap(DMA_SLAVE, atdma->dma_common.cap_mask)  ? "slave " : "",
+	  dma_has_cap(DMA_SG, atdma->dma_common.cap_mask)  ? "sg-cpy " : "",
 	  plat_dat->nr_channels);
 
 	dma_async_device_register(&atdma->dma_common);
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v3 1/2] dma: at_hdmac: Fix calculation of the residual bytes
  2015-02-23 16:54 ` [PATCH v3 1/2] dma: at_hdmac: Fix calculation of the residual bytes Torsten Fleischer
@ 2015-03-03 18:26     ` Nicolas Ferre
  0 siblings, 0 replies; 9+ messages in thread
From: Nicolas Ferre @ 2015-03-03 18:26 UTC (permalink / raw)
  To: Torsten Fleischer, dan.j.williams, vinod.koul, linux-arm-kernel,
	dmaengine, linux-kernel

Le 23/02/2015 17:54, Torsten Fleischer a écrit :
> From: Torsten Fleischer <torfl6749@gmail.com>
> 
> This patch fixes the following issues regarding to the calculation of the
> residue:
> 
> 1. The residue is always calculated for the current transfer even if the
> cookie is associated to a pending transfer.
> 
> 2. For scatter/gather DMA the calculation of the residue for the current
> transfer doesn't include the bytes of the child descriptors that are already
> transferred.
> It only calculates the difference between the transfer's total length minus
> the number of bytes that are already transferred for the current child
> descriptor.
> For example: There is a scatter/gather DMA transfer with a total length of
> 1 MByte. Getting the residue several times while the transfer is running shows
> something like that:
> 
> 1: residue = 975584
> 2: residue = 1002766
> 3: residue = 992627
> 4: residue = 983767
> 5: residue = 985694
> 6: residue = 1008094
> 7: residue = 1009741
> 8: residue = 1011195
> 
> 3. The driver stores the residue but never resets it when starting a new
> transfer.
> For example: If there are two subsequent DMA transfers. The first one with
> a total length of 1 MByte and the second one with a total length of 1 kByte.
> Getting the residue for both transfers shows something like that:
> 
> transfer 1: residue = 975584
> transfer 2: residue = 1048380
> 
> Changes from V1:
>    * Fixed coding style of the multi-line comments.
>    * Improved accuracy of the residue calculation when the transfer for the
>      first descriptor is active.
> 
> Changes from V2:
>    * Member 'tx_width' of 'struct at_desc' restored, because the transfer width
>      can't be derived from the source width when using "slave_sg".
>      The transfer width is needed for the calculation of the residue if either
>      the transfer of the first or the last descriptor is in progress.
>      In the case of a "memory_to_memory_sg" transfer (part of this patch
>      series) the transfer width of both descriptors may differ. Thus it is
>      required to additionally set 'tx_width' of the last descriptor.
>    * Added functions for multiply used calculations.
> 
> Signed-off-by: Torsten Fleischer <torfl6749@gmail.com>


It looks good to me:
Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>

Vinod, can we still queue this one for 4.0 as a fix?

Bye,


> ---
>  drivers/dma/at_hdmac.c      | 184 +++++++++++++++++++++++++++-----------------
>  drivers/dma/at_hdmac_regs.h |   7 +-
>  2 files changed, 115 insertions(+), 76 deletions(-)
> 
> diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
> index 1e1a4c5..0b4fc6f 100644
> --- a/drivers/dma/at_hdmac.c
> +++ b/drivers/dma/at_hdmac.c
> @@ -238,93 +238,126 @@ static void atc_dostart(struct at_dma_chan *atchan, struct at_desc *first)
>  }
>  
>  /*
> - * 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
> + * atc_get_desc_by_cookie - get the descriptor of a cookie
> + * @atchan: the DMA channel
> + * @cookie: the cookie to get the descriptor for
>   */
> -static struct at_desc *atc_get_current_descriptors(struct at_dma_chan *atchan,
> -							u32 dscr_addr)
> +static struct at_desc *atc_get_desc_by_cookie(struct at_dma_chan *atchan,
> +						dma_cookie_t cookie)
>  {
> -	struct at_desc  *desc, *_desc, *child, *desc_cur = NULL;
> +	struct at_desc *desc, *_desc;
>  
> -	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_safe(desc, _desc, &atchan->queue, desc_node) {
> +		if (desc->txd.cookie == cookie)
> +			return desc;
> +	}
>  
> -		list_for_each_entry(child, &desc->tx_list, desc_node) {
> -			if (child->lli.dscr == dscr_addr) {
> -				desc_cur = child;
> -				break;
> -			}
> -		}
> +	list_for_each_entry_safe(desc, _desc, &atchan->active_list, desc_node) {
> +		if (desc->txd.cookie == cookie)
> +			return desc;
>  	}
>  
> -	return desc_cur;
> +	return NULL;
>  }
>  
> -/*
> - * atc_get_bytes_left -
> - * Get the number of bytes residue in dma buffer,
> - * @chan: the channel we want to start
> +/**
> + * atc_calc_bytes_left - calculates the number of bytes left according to the
> + * value read from CTRLA.
> + *
> + * @current_len: the number of bytes left before reading CTRLA
> + * @ctrla: the value of CTRLA
> + * @desc: the descriptor containing the transfer width
> + */
> +static inline int atc_calc_bytes_left(int current_len, u32 ctrla,
> +					struct at_desc *desc)
> +{
> +	return current_len - ((ctrla & ATC_BTSIZE_MAX) << desc->tx_width);
> +}
> +
> +/**
> + * atc_calc_bytes_left_from_reg - calculates the number of bytes left according
> + * to the current value of CTRLA.
> + *
> + * @current_len: the number of bytes left before reading CTRLA
> + * @atchan: the channel to read CTRLA for
> + * @desc: the descriptor containing the transfer width
> + */
> +static inline int atc_calc_bytes_left_from_reg(int current_len,
> +			struct at_dma_chan *atchan, struct at_desc *desc)
> +{
> +	u32 ctrla = channel_readl(atchan, CTRLA);
> +
> +	return atc_calc_bytes_left(current_len, ctrla, desc);
> +}
> +
> +/**
> + * atc_get_bytes_left - get the number of bytes residue for a cookie
> + * @chan: DMA channel
> + * @cookie: transaction identifier to check status of
>   */
> -static int atc_get_bytes_left(struct dma_chan *chan)
> +static int atc_get_bytes_left(struct dma_chan *chan, dma_cookie_t cookie)
>  {
>  	struct at_dma_chan      *atchan = to_at_dma_chan(chan);
> -	struct at_dma           *atdma = to_at_dma(chan->device);
> -	int	chan_id = atchan->chan_common.chan_id;
>  	struct at_desc *desc_first = atc_first_active(atchan);
> -	struct at_desc *desc_cur;
> -	int ret = 0, count = 0;
> +	struct at_desc *desc;
> +	int ret;
> +	u32 ctrla, dscr;
>  
>  	/*
> -	 * Initialize necessary values in the first time.
> -	 * remain_desc record remain desc length.
> +	 * If the cookie doesn't match to the currently running transfer then
> +	 * we can return the total length of the associated DMA transfer,
> +	 * because it is still queued.
>  	 */
> -	if (atchan->remain_desc == 0)
> -		/* First descriptor embedds the transaction length */
> -		atchan->remain_desc = desc_first->len;
> +	desc = atc_get_desc_by_cookie(atchan, cookie);
> +	if (desc == NULL)
> +		return -EINVAL;
> +	else if (desc != desc_first)
> +		return desc->total_len;
>  
> -	/*
> -	 * This happens when current descriptor transfer complete.
> -	 * The residual buffer size should reduce current descriptor length.
> -	 */
> -	if (unlikely(test_bit(ATC_IS_BTC, &atchan->status))) {
> -		clear_bit(ATC_IS_BTC, &atchan->status);
> -		desc_cur = atc_get_current_descriptors(atchan,
> -						channel_readl(atchan, DSCR));
> -		if (!desc_cur) {
> -			ret = -EINVAL;
> -			goto out;
> -		}
> +	/* cookie matches to the currently running transfer */
> +	ret = desc_first->total_len;
>  
> -		count = (desc_cur->lli.ctrla & ATC_BTSIZE_MAX)
> -			<< desc_first->tx_width;
> -		if (atchan->remain_desc < count) {
> -			ret = -EINVAL;
> -			goto out;
> +	if (desc_first->lli.dscr) {
> +		/* hardware linked list transfer */
> +
> +		/*
> +		 * Calculate the residue by removing the length of the child
> +		 * descriptors already transferred from the total length.
> +		 * To get the current child descriptor we can use the value of
> +		 * the channel's DSCR register and compare it against the value
> +		 * of the hardware linked list structure of each child
> +		 * descriptor.
> +		 */
> +
> +		ctrla = channel_readl(atchan, CTRLA);
> +		rmb(); /* ensure CTRLA is read before DSCR */
> +		dscr = channel_readl(atchan, DSCR);
> +
> +		/* for the first descriptor we can be more accurate */
> +		if (desc_first->lli.dscr == dscr)
> +			return atc_calc_bytes_left(ret, ctrla, desc_first);
> +
> +		ret -= desc_first->len;
> +		list_for_each_entry(desc, &desc_first->tx_list, desc_node) {
> +			if (desc->lli.dscr == dscr)
> +				break;
> +
> +			ret -= desc->len;
>  		}
>  
> -		atchan->remain_desc -= count;
> -		ret = atchan->remain_desc;
> -	} else {
>  		/*
> -		 * Get residual bytes when current
> -		 * descriptor transfer in progress.
> +		 * For the last descriptor in the chain we can calculate
> +		 * the remaining bytes using the channel's register.
> +		 * Note that the transfer width of the first and last
> +		 * descriptor may differ.
>  		 */
> -		count = (channel_readl(atchan, CTRLA) & ATC_BTSIZE_MAX)
> -				<< (desc_first->tx_width);
> -		ret = atchan->remain_desc - count;
> +		if (!desc->lli.dscr)
> +			ret = atc_calc_bytes_left_from_reg(ret, atchan, desc);
> +	} else {
> +		/* single transfer */
> +		ret = atc_calc_bytes_left_from_reg(ret, atchan, desc_first);
>  	}
> -	/*
> -	 * Check fifo empty.
> -	 */
> -	if (!(dma_readl(atdma, CHSR) & AT_DMA_EMPT(chan_id)))
> -		atc_issue_pending(chan);
>  
> -out:
>  	return ret;
>  }
>  
> @@ -539,8 +572,6 @@ 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;
>  			}
> @@ -653,14 +684,18 @@ atc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
>  		desc->lli.ctrlb = ctrlb;
>  
>  		desc->txd.cookie = 0;
> +		desc->len = xfer_count << src_width;
>  
>  		atc_desc_chain(&first, &prev, desc);
>  	}
>  
>  	/* First descriptor of the chain embedds additional information */
>  	first->txd.cookie = -EBUSY;
> -	first->len = len;
> +	first->total_len = len;
> +
> +	/* set transfer width for the calculation of the residue */
>  	first->tx_width = src_width;
> +	prev->tx_width = src_width;
>  
>  	/* set end-of-link to the last link descriptor of list*/
>  	set_desc_eol(desc);
> @@ -752,6 +787,7 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
>  					| ATC_SRC_WIDTH(mem_width)
>  					| len >> mem_width;
>  			desc->lli.ctrlb = ctrlb;
> +			desc->len = len;
>  
>  			atc_desc_chain(&first, &prev, desc);
>  			total_len += len;
> @@ -792,6 +828,7 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
>  					| ATC_DST_WIDTH(mem_width)
>  					| len >> reg_width;
>  			desc->lli.ctrlb = ctrlb;
> +			desc->len = len;
>  
>  			atc_desc_chain(&first, &prev, desc);
>  			total_len += len;
> @@ -806,8 +843,11 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
>  
>  	/* First descriptor of the chain embedds additional information */
>  	first->txd.cookie = -EBUSY;
> -	first->len = total_len;
> +	first->total_len = total_len;
> +
> +	/* set transfer width for the calculation of the residue */
>  	first->tx_width = reg_width;
> +	prev->tx_width = reg_width;
>  
>  	/* first link descriptor of list is responsible of flags */
>  	first->txd.flags = flags; /* client is in control of this ack */
> @@ -872,6 +912,7 @@ atc_dma_cyclic_fill_desc(struct dma_chan *chan, struct at_desc *desc,
>  				| ATC_FC_MEM2PER
>  				| ATC_SIF(atchan->mem_if)
>  				| ATC_DIF(atchan->per_if);
> +		desc->len = period_len;
>  		break;
>  
>  	case DMA_DEV_TO_MEM:
> @@ -883,6 +924,7 @@ atc_dma_cyclic_fill_desc(struct dma_chan *chan, struct at_desc *desc,
>  				| ATC_FC_PER2MEM
>  				| ATC_SIF(atchan->per_if)
>  				| ATC_DIF(atchan->mem_if);
> +		desc->len = period_len;
>  		break;
>  
>  	default:
> @@ -964,7 +1006,7 @@ atc_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
>  
>  	/* First descriptor of the chain embedds additional information */
>  	first->txd.cookie = -EBUSY;
> -	first->len = buf_len;
> +	first->total_len = buf_len;
>  	first->tx_width = reg_width;
>  
>  	return &first->txd;
> @@ -1118,7 +1160,7 @@ atc_tx_status(struct dma_chan *chan,
>  	spin_lock_irqsave(&atchan->lock, flags);
>  
>  	/*  Get number of bytes left in the active transactions */
> -	bytes = atc_get_bytes_left(chan);
> +	bytes = atc_get_bytes_left(chan, cookie);
>  
>  	spin_unlock_irqrestore(&atchan->lock, flags);
>  
> @@ -1214,7 +1256,6 @@ static int atc_alloc_chan_resources(struct dma_chan *chan)
>  
>  	spin_lock_irqsave(&atchan->lock, flags);
>  	atchan->descs_allocated = i;
> -	atchan->remain_desc = 0;
>  	list_splice(&tmp_list, &atchan->free_list);
>  	dma_cookie_init(chan);
>  	spin_unlock_irqrestore(&atchan->lock, flags);
> @@ -1257,7 +1298,6 @@ static void atc_free_chan_resources(struct dma_chan *chan)
>  	list_splice_init(&atchan->free_list, &list);
>  	atchan->descs_allocated = 0;
>  	atchan->status = 0;
> -	atchan->remain_desc = 0;
>  
>  	dev_vdbg(chan2dev(chan), "free_chan_resources: done\n");
>  }
> diff --git a/drivers/dma/at_hdmac_regs.h b/drivers/dma/at_hdmac_regs.h
> index d6bba6c..2727ca5 100644
> --- a/drivers/dma/at_hdmac_regs.h
> +++ b/drivers/dma/at_hdmac_regs.h
> @@ -181,8 +181,9 @@ struct at_lli {
>   * @at_lli: hardware lli structure
>   * @txd: support for the async_tx api
>   * @desc_node: node on the channed descriptors list
> - * @len: total transaction bytecount
> + * @len: descriptor byte count
>   * @tx_width: transfer width
> + * @total_len: total transaction byte count
>   */
>  struct at_desc {
>  	/* FIRST values the hardware uses */
> @@ -194,6 +195,7 @@ struct at_desc {
>  	struct list_head		desc_node;
>  	size_t				len;
>  	u32				tx_width;
> +	size_t				total_len;
>  };
>  
>  static inline struct at_desc *
> @@ -213,7 +215,6 @@ 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,
>  };
>  
> @@ -231,7 +232,6 @@ 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
> - * @remain_desc: to save remain desc length
>   * @dma_sconfig: configuration for slave transfers, passed via
>   * .device_config
>   * @lock: serializes enqueue/dequeue operations to descriptors lists
> @@ -251,7 +251,6 @@ struct at_dma_chan {
>  	struct tasklet_struct	tasklet;
>  	u32			save_cfg;
>  	u32			save_dscr;
> -	u32			remain_desc;
>  	struct dma_slave_config dma_sconfig;
>  
>  	spinlock_t		lock;
> 


-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v3 1/2] dma: at_hdmac: Fix calculation of the residual bytes
@ 2015-03-03 18:26     ` Nicolas Ferre
  0 siblings, 0 replies; 9+ messages in thread
From: Nicolas Ferre @ 2015-03-03 18:26 UTC (permalink / raw)
  To: linux-arm-kernel

Le 23/02/2015 17:54, Torsten Fleischer a ?crit :
> From: Torsten Fleischer <torfl6749@gmail.com>
> 
> This patch fixes the following issues regarding to the calculation of the
> residue:
> 
> 1. The residue is always calculated for the current transfer even if the
> cookie is associated to a pending transfer.
> 
> 2. For scatter/gather DMA the calculation of the residue for the current
> transfer doesn't include the bytes of the child descriptors that are already
> transferred.
> It only calculates the difference between the transfer's total length minus
> the number of bytes that are already transferred for the current child
> descriptor.
> For example: There is a scatter/gather DMA transfer with a total length of
> 1 MByte. Getting the residue several times while the transfer is running shows
> something like that:
> 
> 1: residue = 975584
> 2: residue = 1002766
> 3: residue = 992627
> 4: residue = 983767
> 5: residue = 985694
> 6: residue = 1008094
> 7: residue = 1009741
> 8: residue = 1011195
> 
> 3. The driver stores the residue but never resets it when starting a new
> transfer.
> For example: If there are two subsequent DMA transfers. The first one with
> a total length of 1 MByte and the second one with a total length of 1 kByte.
> Getting the residue for both transfers shows something like that:
> 
> transfer 1: residue = 975584
> transfer 2: residue = 1048380
> 
> Changes from V1:
>    * Fixed coding style of the multi-line comments.
>    * Improved accuracy of the residue calculation when the transfer for the
>      first descriptor is active.
> 
> Changes from V2:
>    * Member 'tx_width' of 'struct at_desc' restored, because the transfer width
>      can't be derived from the source width when using "slave_sg".
>      The transfer width is needed for the calculation of the residue if either
>      the transfer of the first or the last descriptor is in progress.
>      In the case of a "memory_to_memory_sg" transfer (part of this patch
>      series) the transfer width of both descriptors may differ. Thus it is
>      required to additionally set 'tx_width' of the last descriptor.
>    * Added functions for multiply used calculations.
> 
> Signed-off-by: Torsten Fleischer <torfl6749@gmail.com>


It looks good to me:
Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>

Vinod, can we still queue this one for 4.0 as a fix?

Bye,


> ---
>  drivers/dma/at_hdmac.c      | 184 +++++++++++++++++++++++++++-----------------
>  drivers/dma/at_hdmac_regs.h |   7 +-
>  2 files changed, 115 insertions(+), 76 deletions(-)
> 
> diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
> index 1e1a4c5..0b4fc6f 100644
> --- a/drivers/dma/at_hdmac.c
> +++ b/drivers/dma/at_hdmac.c
> @@ -238,93 +238,126 @@ static void atc_dostart(struct at_dma_chan *atchan, struct at_desc *first)
>  }
>  
>  /*
> - * 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
> + * atc_get_desc_by_cookie - get the descriptor of a cookie
> + * @atchan: the DMA channel
> + * @cookie: the cookie to get the descriptor for
>   */
> -static struct at_desc *atc_get_current_descriptors(struct at_dma_chan *atchan,
> -							u32 dscr_addr)
> +static struct at_desc *atc_get_desc_by_cookie(struct at_dma_chan *atchan,
> +						dma_cookie_t cookie)
>  {
> -	struct at_desc  *desc, *_desc, *child, *desc_cur = NULL;
> +	struct at_desc *desc, *_desc;
>  
> -	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_safe(desc, _desc, &atchan->queue, desc_node) {
> +		if (desc->txd.cookie == cookie)
> +			return desc;
> +	}
>  
> -		list_for_each_entry(child, &desc->tx_list, desc_node) {
> -			if (child->lli.dscr == dscr_addr) {
> -				desc_cur = child;
> -				break;
> -			}
> -		}
> +	list_for_each_entry_safe(desc, _desc, &atchan->active_list, desc_node) {
> +		if (desc->txd.cookie == cookie)
> +			return desc;
>  	}
>  
> -	return desc_cur;
> +	return NULL;
>  }
>  
> -/*
> - * atc_get_bytes_left -
> - * Get the number of bytes residue in dma buffer,
> - * @chan: the channel we want to start
> +/**
> + * atc_calc_bytes_left - calculates the number of bytes left according to the
> + * value read from CTRLA.
> + *
> + * @current_len: the number of bytes left before reading CTRLA
> + * @ctrla: the value of CTRLA
> + * @desc: the descriptor containing the transfer width
> + */
> +static inline int atc_calc_bytes_left(int current_len, u32 ctrla,
> +					struct at_desc *desc)
> +{
> +	return current_len - ((ctrla & ATC_BTSIZE_MAX) << desc->tx_width);
> +}
> +
> +/**
> + * atc_calc_bytes_left_from_reg - calculates the number of bytes left according
> + * to the current value of CTRLA.
> + *
> + * @current_len: the number of bytes left before reading CTRLA
> + * @atchan: the channel to read CTRLA for
> + * @desc: the descriptor containing the transfer width
> + */
> +static inline int atc_calc_bytes_left_from_reg(int current_len,
> +			struct at_dma_chan *atchan, struct at_desc *desc)
> +{
> +	u32 ctrla = channel_readl(atchan, CTRLA);
> +
> +	return atc_calc_bytes_left(current_len, ctrla, desc);
> +}
> +
> +/**
> + * atc_get_bytes_left - get the number of bytes residue for a cookie
> + * @chan: DMA channel
> + * @cookie: transaction identifier to check status of
>   */
> -static int atc_get_bytes_left(struct dma_chan *chan)
> +static int atc_get_bytes_left(struct dma_chan *chan, dma_cookie_t cookie)
>  {
>  	struct at_dma_chan      *atchan = to_at_dma_chan(chan);
> -	struct at_dma           *atdma = to_at_dma(chan->device);
> -	int	chan_id = atchan->chan_common.chan_id;
>  	struct at_desc *desc_first = atc_first_active(atchan);
> -	struct at_desc *desc_cur;
> -	int ret = 0, count = 0;
> +	struct at_desc *desc;
> +	int ret;
> +	u32 ctrla, dscr;
>  
>  	/*
> -	 * Initialize necessary values in the first time.
> -	 * remain_desc record remain desc length.
> +	 * If the cookie doesn't match to the currently running transfer then
> +	 * we can return the total length of the associated DMA transfer,
> +	 * because it is still queued.
>  	 */
> -	if (atchan->remain_desc == 0)
> -		/* First descriptor embedds the transaction length */
> -		atchan->remain_desc = desc_first->len;
> +	desc = atc_get_desc_by_cookie(atchan, cookie);
> +	if (desc == NULL)
> +		return -EINVAL;
> +	else if (desc != desc_first)
> +		return desc->total_len;
>  
> -	/*
> -	 * This happens when current descriptor transfer complete.
> -	 * The residual buffer size should reduce current descriptor length.
> -	 */
> -	if (unlikely(test_bit(ATC_IS_BTC, &atchan->status))) {
> -		clear_bit(ATC_IS_BTC, &atchan->status);
> -		desc_cur = atc_get_current_descriptors(atchan,
> -						channel_readl(atchan, DSCR));
> -		if (!desc_cur) {
> -			ret = -EINVAL;
> -			goto out;
> -		}
> +	/* cookie matches to the currently running transfer */
> +	ret = desc_first->total_len;
>  
> -		count = (desc_cur->lli.ctrla & ATC_BTSIZE_MAX)
> -			<< desc_first->tx_width;
> -		if (atchan->remain_desc < count) {
> -			ret = -EINVAL;
> -			goto out;
> +	if (desc_first->lli.dscr) {
> +		/* hardware linked list transfer */
> +
> +		/*
> +		 * Calculate the residue by removing the length of the child
> +		 * descriptors already transferred from the total length.
> +		 * To get the current child descriptor we can use the value of
> +		 * the channel's DSCR register and compare it against the value
> +		 * of the hardware linked list structure of each child
> +		 * descriptor.
> +		 */
> +
> +		ctrla = channel_readl(atchan, CTRLA);
> +		rmb(); /* ensure CTRLA is read before DSCR */
> +		dscr = channel_readl(atchan, DSCR);
> +
> +		/* for the first descriptor we can be more accurate */
> +		if (desc_first->lli.dscr == dscr)
> +			return atc_calc_bytes_left(ret, ctrla, desc_first);
> +
> +		ret -= desc_first->len;
> +		list_for_each_entry(desc, &desc_first->tx_list, desc_node) {
> +			if (desc->lli.dscr == dscr)
> +				break;
> +
> +			ret -= desc->len;
>  		}
>  
> -		atchan->remain_desc -= count;
> -		ret = atchan->remain_desc;
> -	} else {
>  		/*
> -		 * Get residual bytes when current
> -		 * descriptor transfer in progress.
> +		 * For the last descriptor in the chain we can calculate
> +		 * the remaining bytes using the channel's register.
> +		 * Note that the transfer width of the first and last
> +		 * descriptor may differ.
>  		 */
> -		count = (channel_readl(atchan, CTRLA) & ATC_BTSIZE_MAX)
> -				<< (desc_first->tx_width);
> -		ret = atchan->remain_desc - count;
> +		if (!desc->lli.dscr)
> +			ret = atc_calc_bytes_left_from_reg(ret, atchan, desc);
> +	} else {
> +		/* single transfer */
> +		ret = atc_calc_bytes_left_from_reg(ret, atchan, desc_first);
>  	}
> -	/*
> -	 * Check fifo empty.
> -	 */
> -	if (!(dma_readl(atdma, CHSR) & AT_DMA_EMPT(chan_id)))
> -		atc_issue_pending(chan);
>  
> -out:
>  	return ret;
>  }
>  
> @@ -539,8 +572,6 @@ 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;
>  			}
> @@ -653,14 +684,18 @@ atc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
>  		desc->lli.ctrlb = ctrlb;
>  
>  		desc->txd.cookie = 0;
> +		desc->len = xfer_count << src_width;
>  
>  		atc_desc_chain(&first, &prev, desc);
>  	}
>  
>  	/* First descriptor of the chain embedds additional information */
>  	first->txd.cookie = -EBUSY;
> -	first->len = len;
> +	first->total_len = len;
> +
> +	/* set transfer width for the calculation of the residue */
>  	first->tx_width = src_width;
> +	prev->tx_width = src_width;
>  
>  	/* set end-of-link to the last link descriptor of list*/
>  	set_desc_eol(desc);
> @@ -752,6 +787,7 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
>  					| ATC_SRC_WIDTH(mem_width)
>  					| len >> mem_width;
>  			desc->lli.ctrlb = ctrlb;
> +			desc->len = len;
>  
>  			atc_desc_chain(&first, &prev, desc);
>  			total_len += len;
> @@ -792,6 +828,7 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
>  					| ATC_DST_WIDTH(mem_width)
>  					| len >> reg_width;
>  			desc->lli.ctrlb = ctrlb;
> +			desc->len = len;
>  
>  			atc_desc_chain(&first, &prev, desc);
>  			total_len += len;
> @@ -806,8 +843,11 @@ atc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
>  
>  	/* First descriptor of the chain embedds additional information */
>  	first->txd.cookie = -EBUSY;
> -	first->len = total_len;
> +	first->total_len = total_len;
> +
> +	/* set transfer width for the calculation of the residue */
>  	first->tx_width = reg_width;
> +	prev->tx_width = reg_width;
>  
>  	/* first link descriptor of list is responsible of flags */
>  	first->txd.flags = flags; /* client is in control of this ack */
> @@ -872,6 +912,7 @@ atc_dma_cyclic_fill_desc(struct dma_chan *chan, struct at_desc *desc,
>  				| ATC_FC_MEM2PER
>  				| ATC_SIF(atchan->mem_if)
>  				| ATC_DIF(atchan->per_if);
> +		desc->len = period_len;
>  		break;
>  
>  	case DMA_DEV_TO_MEM:
> @@ -883,6 +924,7 @@ atc_dma_cyclic_fill_desc(struct dma_chan *chan, struct at_desc *desc,
>  				| ATC_FC_PER2MEM
>  				| ATC_SIF(atchan->per_if)
>  				| ATC_DIF(atchan->mem_if);
> +		desc->len = period_len;
>  		break;
>  
>  	default:
> @@ -964,7 +1006,7 @@ atc_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
>  
>  	/* First descriptor of the chain embedds additional information */
>  	first->txd.cookie = -EBUSY;
> -	first->len = buf_len;
> +	first->total_len = buf_len;
>  	first->tx_width = reg_width;
>  
>  	return &first->txd;
> @@ -1118,7 +1160,7 @@ atc_tx_status(struct dma_chan *chan,
>  	spin_lock_irqsave(&atchan->lock, flags);
>  
>  	/*  Get number of bytes left in the active transactions */
> -	bytes = atc_get_bytes_left(chan);
> +	bytes = atc_get_bytes_left(chan, cookie);
>  
>  	spin_unlock_irqrestore(&atchan->lock, flags);
>  
> @@ -1214,7 +1256,6 @@ static int atc_alloc_chan_resources(struct dma_chan *chan)
>  
>  	spin_lock_irqsave(&atchan->lock, flags);
>  	atchan->descs_allocated = i;
> -	atchan->remain_desc = 0;
>  	list_splice(&tmp_list, &atchan->free_list);
>  	dma_cookie_init(chan);
>  	spin_unlock_irqrestore(&atchan->lock, flags);
> @@ -1257,7 +1298,6 @@ static void atc_free_chan_resources(struct dma_chan *chan)
>  	list_splice_init(&atchan->free_list, &list);
>  	atchan->descs_allocated = 0;
>  	atchan->status = 0;
> -	atchan->remain_desc = 0;
>  
>  	dev_vdbg(chan2dev(chan), "free_chan_resources: done\n");
>  }
> diff --git a/drivers/dma/at_hdmac_regs.h b/drivers/dma/at_hdmac_regs.h
> index d6bba6c..2727ca5 100644
> --- a/drivers/dma/at_hdmac_regs.h
> +++ b/drivers/dma/at_hdmac_regs.h
> @@ -181,8 +181,9 @@ struct at_lli {
>   * @at_lli: hardware lli structure
>   * @txd: support for the async_tx api
>   * @desc_node: node on the channed descriptors list
> - * @len: total transaction bytecount
> + * @len: descriptor byte count
>   * @tx_width: transfer width
> + * @total_len: total transaction byte count
>   */
>  struct at_desc {
>  	/* FIRST values the hardware uses */
> @@ -194,6 +195,7 @@ struct at_desc {
>  	struct list_head		desc_node;
>  	size_t				len;
>  	u32				tx_width;
> +	size_t				total_len;
>  };
>  
>  static inline struct at_desc *
> @@ -213,7 +215,6 @@ 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,
>  };
>  
> @@ -231,7 +232,6 @@ 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
> - * @remain_desc: to save remain desc length
>   * @dma_sconfig: configuration for slave transfers, passed via
>   * .device_config
>   * @lock: serializes enqueue/dequeue operations to descriptors lists
> @@ -251,7 +251,6 @@ struct at_dma_chan {
>  	struct tasklet_struct	tasklet;
>  	u32			save_cfg;
>  	u32			save_dscr;
> -	u32			remain_desc;
>  	struct dma_slave_config dma_sconfig;
>  
>  	spinlock_t		lock;
> 


-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v3 2/2] dma: at_hdmac: Add support for memory to memory sg transfers
  2015-02-23 16:54 ` [PATCH v3 2/2] dma: at_hdmac: Add support for memory to memory sg transfers Torsten Fleischer
@ 2015-03-03 18:29     ` Nicolas Ferre
  0 siblings, 0 replies; 9+ messages in thread
From: Nicolas Ferre @ 2015-03-03 18:29 UTC (permalink / raw)
  To: Torsten Fleischer, dan.j.williams, vinod.koul, linux-arm-kernel,
	dmaengine, linux-kernel

Le 23/02/2015 17:54, Torsten Fleischer a écrit :
> From: Torsten Fleischer <torfl6749@gmail.com>
> 
> This patch adds support for memory to memory scatter-gather transfers.
> 
> Changes from V1:
>    * Fixed coding style of the multi-line comments.
> 
> Changes from V2:
>    * Added setup of 'desc->tx_width' that is needed to calculate the
>      residue.
> 
> Signed-off-by: Torsten Fleischer <torfl6749@gmail.com>

Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>

Thanks!

> ---
>  drivers/dma/at_hdmac.c | 175 +++++++++++++++++++++++++++++++++++++++++++++----
>  1 file changed, 164 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
> index 0b4fc6f..57b2141 100644
> --- a/drivers/dma/at_hdmac.c
> +++ b/drivers/dma/at_hdmac.c
> @@ -65,6 +65,21 @@ static void atc_issue_pending(struct dma_chan *chan);
>  
>  /*----------------------------------------------------------------------*/
>  
> +static inline unsigned int atc_get_xfer_width(dma_addr_t src, dma_addr_t dst,
> +						size_t len)
> +{
> +	unsigned int width;
> +
> +	if (!((src | dst  | len) & 3))
> +		width = 2;
> +	else if (!((src | dst | len) & 1))
> +		width = 1;
> +	else
> +		width = 0;
> +
> +	return width;
> +}
> +
>  static struct at_desc *atc_first_active(struct at_dma_chan *atchan)
>  {
>  	return list_first_entry(&atchan->active_list,
> @@ -659,16 +674,10 @@ atc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
>  	 * We can be a lot more clever here, but this should take care
>  	 * of the most common optimization.
>  	 */
> -	if (!((src | dest  | len) & 3)) {
> -		ctrla = ATC_SRC_WIDTH_WORD | ATC_DST_WIDTH_WORD;
> -		src_width = dst_width = 2;
> -	} else if (!((src | dest | len) & 1)) {
> -		ctrla = ATC_SRC_WIDTH_HALFWORD | ATC_DST_WIDTH_HALFWORD;
> -		src_width = dst_width = 1;
> -	} else {
> -		ctrla = ATC_SRC_WIDTH_BYTE | ATC_DST_WIDTH_BYTE;
> -		src_width = dst_width = 0;
> -	}
> +	src_width = dst_width = atc_get_xfer_width(src, dest, len);
> +
> +	ctrla = ATC_SRC_WIDTH(src_width) |
> +		ATC_DST_WIDTH(dst_width);
>  
>  	for (offset = 0; offset < len; offset += xfer_count << src_width) {
>  		xfer_count = min_t(size_t, (len - offset) >> src_width,
> @@ -862,6 +871,144 @@ err:
>  }
>  
>  /**
> + * atc_prep_dma_sg - prepare memory to memory scather-gather operation
> + * @chan: the channel to prepare operation on
> + * @dst_sg: destination scatterlist
> + * @dst_nents: number of destination scatterlist entries
> + * @src_sg: source scatterlist
> + * @src_nents: number of source scatterlist entries
> + * @flags: tx descriptor status flags
> + */
> +static struct dma_async_tx_descriptor *
> +atc_prep_dma_sg(struct dma_chan *chan,
> +		struct scatterlist *dst_sg, unsigned int dst_nents,
> +		struct scatterlist *src_sg, unsigned int src_nents,
> +		unsigned long flags)
> +{
> +	struct at_dma_chan	*atchan = to_at_dma_chan(chan);
> +	struct at_desc		*desc = NULL;
> +	struct at_desc		*first = NULL;
> +	struct at_desc		*prev = NULL;
> +	unsigned int		src_width;
> +	unsigned int		dst_width;
> +	size_t			xfer_count;
> +	u32			ctrla;
> +	u32			ctrlb;
> +	size_t			dst_len = 0, src_len = 0;
> +	dma_addr_t		dst = 0, src = 0;
> +	size_t			len = 0, total_len = 0;
> +
> +	if (unlikely(dst_nents == 0 || src_nents == 0))
> +		return NULL;
> +
> +	if (unlikely(dst_sg == NULL || src_sg == NULL))
> +		return NULL;
> +
> +	ctrlb =   ATC_DEFAULT_CTRLB | ATC_IEN
> +		| ATC_SRC_ADDR_MODE_INCR
> +		| ATC_DST_ADDR_MODE_INCR
> +		| ATC_FC_MEM2MEM;
> +
> +	/*
> +	 * loop until there is either no more source or no more destination
> +	 * scatterlist entry
> +	 */
> +	while (true) {
> +
> +		/* prepare the next transfer */
> +		if (dst_len == 0) {
> +
> +			/* no more destination scatterlist entries */
> +			if (!dst_sg || !dst_nents)
> +				break;
> +
> +			dst = sg_dma_address(dst_sg);
> +			dst_len = sg_dma_len(dst_sg);
> +
> +			dst_sg = sg_next(dst_sg);
> +			dst_nents--;
> +		}
> +
> +		if (src_len == 0) {
> +
> +			/* no more source scatterlist entries */
> +			if (!src_sg || !src_nents)
> +				break;
> +
> +			src = sg_dma_address(src_sg);
> +			src_len = sg_dma_len(src_sg);
> +
> +			src_sg = sg_next(src_sg);
> +			src_nents--;
> +		}
> +
> +		len = min_t(size_t, src_len, dst_len);
> +		if (len == 0)
> +			continue;
> +
> +		/* take care for the alignment */
> +		src_width = dst_width = atc_get_xfer_width(src, dst, len);
> +
> +		ctrla = ATC_SRC_WIDTH(src_width) |
> +			ATC_DST_WIDTH(dst_width);
> +
> +		/*
> +		 * The number of transfers to set up refer to the source width
> +		 * that depends on the alignment.
> +		 */
> +		xfer_count = len >> src_width;
> +		if (xfer_count > ATC_BTSIZE_MAX) {
> +			xfer_count = ATC_BTSIZE_MAX;
> +			len = ATC_BTSIZE_MAX << src_width;
> +		}
> +
> +		/* create the transfer */
> +		desc = atc_desc_get(atchan);
> +		if (!desc)
> +			goto err_desc_get;
> +
> +		desc->lli.saddr = src;
> +		desc->lli.daddr = dst;
> +		desc->lli.ctrla = ctrla | xfer_count;
> +		desc->lli.ctrlb = ctrlb;
> +
> +		desc->txd.cookie = 0;
> +		desc->len = len;
> +
> +		/*
> +		 * Although we only need the transfer width for the first and
> +		 * the last descriptor, its easier to set it to all descriptors.
> +		 */
> +		desc->tx_width = src_width;
> +
> +		atc_desc_chain(&first, &prev, desc);
> +
> +		/* update the lengths and addresses for the next loop cycle */
> +		dst_len -= len;
> +		src_len -= len;
> +		dst += len;
> +		src += len;
> +
> +		total_len += len;
> +	}
> +
> +	/* First descriptor of the chain embedds additional information */
> +	first->txd.cookie = -EBUSY;
> +	first->total_len = total_len;
> +
> +	/* set end-of-link to the last link descriptor of list*/
> +	set_desc_eol(desc);
> +
> +	first->txd.flags = flags; /* client is in control of this ack */
> +
> +	return &first->txd;
> +
> +err_desc_get:
> +	atc_desc_put(atchan, first);
> +	return NULL;
> +}
> +
> +/**
>   * atc_dma_cyclic_check_values
>   * Check for too big/unaligned periods and unaligned DMA buffer
>   */
> @@ -1461,8 +1608,10 @@ static int __init at_dma_probe(struct platform_device *pdev)
>  
>  	/* setup platform data for each SoC */
>  	dma_cap_set(DMA_MEMCPY, at91sam9rl_config.cap_mask);
> +	dma_cap_set(DMA_SG, at91sam9rl_config.cap_mask);
>  	dma_cap_set(DMA_MEMCPY, at91sam9g45_config.cap_mask);
>  	dma_cap_set(DMA_SLAVE, at91sam9g45_config.cap_mask);
> +	dma_cap_set(DMA_SG, at91sam9g45_config.cap_mask);
>  
>  	/* get DMA parameters from controller type */
>  	plat_dat = at_dma_get_driver_data(pdev);
> @@ -1582,11 +1731,15 @@ static int __init at_dma_probe(struct platform_device *pdev)
>  		atdma->dma_common.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
>  	}
>  
> +	if (dma_has_cap(DMA_SG, atdma->dma_common.cap_mask))
> +		atdma->dma_common.device_prep_dma_sg = atc_prep_dma_sg;
> +
>  	dma_writel(atdma, EN, AT_DMA_ENABLE);
>  
> -	dev_info(&pdev->dev, "Atmel AHB DMA Controller ( %s%s), %d channels\n",
> +	dev_info(&pdev->dev, "Atmel AHB DMA Controller ( %s%s%s), %d channels\n",
>  	  dma_has_cap(DMA_MEMCPY, atdma->dma_common.cap_mask) ? "cpy " : "",
>  	  dma_has_cap(DMA_SLAVE, atdma->dma_common.cap_mask)  ? "slave " : "",
> +	  dma_has_cap(DMA_SG, atdma->dma_common.cap_mask)  ? "sg-cpy " : "",
>  	  plat_dat->nr_channels);
>  
>  	dma_async_device_register(&atdma->dma_common);
> 


-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v3 2/2] dma: at_hdmac: Add support for memory to memory sg transfers
@ 2015-03-03 18:29     ` Nicolas Ferre
  0 siblings, 0 replies; 9+ messages in thread
From: Nicolas Ferre @ 2015-03-03 18:29 UTC (permalink / raw)
  To: linux-arm-kernel

Le 23/02/2015 17:54, Torsten Fleischer a ?crit :
> From: Torsten Fleischer <torfl6749@gmail.com>
> 
> This patch adds support for memory to memory scatter-gather transfers.
> 
> Changes from V1:
>    * Fixed coding style of the multi-line comments.
> 
> Changes from V2:
>    * Added setup of 'desc->tx_width' that is needed to calculate the
>      residue.
> 
> Signed-off-by: Torsten Fleischer <torfl6749@gmail.com>

Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>

Thanks!

> ---
>  drivers/dma/at_hdmac.c | 175 +++++++++++++++++++++++++++++++++++++++++++++----
>  1 file changed, 164 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
> index 0b4fc6f..57b2141 100644
> --- a/drivers/dma/at_hdmac.c
> +++ b/drivers/dma/at_hdmac.c
> @@ -65,6 +65,21 @@ static void atc_issue_pending(struct dma_chan *chan);
>  
>  /*----------------------------------------------------------------------*/
>  
> +static inline unsigned int atc_get_xfer_width(dma_addr_t src, dma_addr_t dst,
> +						size_t len)
> +{
> +	unsigned int width;
> +
> +	if (!((src | dst  | len) & 3))
> +		width = 2;
> +	else if (!((src | dst | len) & 1))
> +		width = 1;
> +	else
> +		width = 0;
> +
> +	return width;
> +}
> +
>  static struct at_desc *atc_first_active(struct at_dma_chan *atchan)
>  {
>  	return list_first_entry(&atchan->active_list,
> @@ -659,16 +674,10 @@ atc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
>  	 * We can be a lot more clever here, but this should take care
>  	 * of the most common optimization.
>  	 */
> -	if (!((src | dest  | len) & 3)) {
> -		ctrla = ATC_SRC_WIDTH_WORD | ATC_DST_WIDTH_WORD;
> -		src_width = dst_width = 2;
> -	} else if (!((src | dest | len) & 1)) {
> -		ctrla = ATC_SRC_WIDTH_HALFWORD | ATC_DST_WIDTH_HALFWORD;
> -		src_width = dst_width = 1;
> -	} else {
> -		ctrla = ATC_SRC_WIDTH_BYTE | ATC_DST_WIDTH_BYTE;
> -		src_width = dst_width = 0;
> -	}
> +	src_width = dst_width = atc_get_xfer_width(src, dest, len);
> +
> +	ctrla = ATC_SRC_WIDTH(src_width) |
> +		ATC_DST_WIDTH(dst_width);
>  
>  	for (offset = 0; offset < len; offset += xfer_count << src_width) {
>  		xfer_count = min_t(size_t, (len - offset) >> src_width,
> @@ -862,6 +871,144 @@ err:
>  }
>  
>  /**
> + * atc_prep_dma_sg - prepare memory to memory scather-gather operation
> + * @chan: the channel to prepare operation on
> + * @dst_sg: destination scatterlist
> + * @dst_nents: number of destination scatterlist entries
> + * @src_sg: source scatterlist
> + * @src_nents: number of source scatterlist entries
> + * @flags: tx descriptor status flags
> + */
> +static struct dma_async_tx_descriptor *
> +atc_prep_dma_sg(struct dma_chan *chan,
> +		struct scatterlist *dst_sg, unsigned int dst_nents,
> +		struct scatterlist *src_sg, unsigned int src_nents,
> +		unsigned long flags)
> +{
> +	struct at_dma_chan	*atchan = to_at_dma_chan(chan);
> +	struct at_desc		*desc = NULL;
> +	struct at_desc		*first = NULL;
> +	struct at_desc		*prev = NULL;
> +	unsigned int		src_width;
> +	unsigned int		dst_width;
> +	size_t			xfer_count;
> +	u32			ctrla;
> +	u32			ctrlb;
> +	size_t			dst_len = 0, src_len = 0;
> +	dma_addr_t		dst = 0, src = 0;
> +	size_t			len = 0, total_len = 0;
> +
> +	if (unlikely(dst_nents == 0 || src_nents == 0))
> +		return NULL;
> +
> +	if (unlikely(dst_sg == NULL || src_sg == NULL))
> +		return NULL;
> +
> +	ctrlb =   ATC_DEFAULT_CTRLB | ATC_IEN
> +		| ATC_SRC_ADDR_MODE_INCR
> +		| ATC_DST_ADDR_MODE_INCR
> +		| ATC_FC_MEM2MEM;
> +
> +	/*
> +	 * loop until there is either no more source or no more destination
> +	 * scatterlist entry
> +	 */
> +	while (true) {
> +
> +		/* prepare the next transfer */
> +		if (dst_len == 0) {
> +
> +			/* no more destination scatterlist entries */
> +			if (!dst_sg || !dst_nents)
> +				break;
> +
> +			dst = sg_dma_address(dst_sg);
> +			dst_len = sg_dma_len(dst_sg);
> +
> +			dst_sg = sg_next(dst_sg);
> +			dst_nents--;
> +		}
> +
> +		if (src_len == 0) {
> +
> +			/* no more source scatterlist entries */
> +			if (!src_sg || !src_nents)
> +				break;
> +
> +			src = sg_dma_address(src_sg);
> +			src_len = sg_dma_len(src_sg);
> +
> +			src_sg = sg_next(src_sg);
> +			src_nents--;
> +		}
> +
> +		len = min_t(size_t, src_len, dst_len);
> +		if (len == 0)
> +			continue;
> +
> +		/* take care for the alignment */
> +		src_width = dst_width = atc_get_xfer_width(src, dst, len);
> +
> +		ctrla = ATC_SRC_WIDTH(src_width) |
> +			ATC_DST_WIDTH(dst_width);
> +
> +		/*
> +		 * The number of transfers to set up refer to the source width
> +		 * that depends on the alignment.
> +		 */
> +		xfer_count = len >> src_width;
> +		if (xfer_count > ATC_BTSIZE_MAX) {
> +			xfer_count = ATC_BTSIZE_MAX;
> +			len = ATC_BTSIZE_MAX << src_width;
> +		}
> +
> +		/* create the transfer */
> +		desc = atc_desc_get(atchan);
> +		if (!desc)
> +			goto err_desc_get;
> +
> +		desc->lli.saddr = src;
> +		desc->lli.daddr = dst;
> +		desc->lli.ctrla = ctrla | xfer_count;
> +		desc->lli.ctrlb = ctrlb;
> +
> +		desc->txd.cookie = 0;
> +		desc->len = len;
> +
> +		/*
> +		 * Although we only need the transfer width for the first and
> +		 * the last descriptor, its easier to set it to all descriptors.
> +		 */
> +		desc->tx_width = src_width;
> +
> +		atc_desc_chain(&first, &prev, desc);
> +
> +		/* update the lengths and addresses for the next loop cycle */
> +		dst_len -= len;
> +		src_len -= len;
> +		dst += len;
> +		src += len;
> +
> +		total_len += len;
> +	}
> +
> +	/* First descriptor of the chain embedds additional information */
> +	first->txd.cookie = -EBUSY;
> +	first->total_len = total_len;
> +
> +	/* set end-of-link to the last link descriptor of list*/
> +	set_desc_eol(desc);
> +
> +	first->txd.flags = flags; /* client is in control of this ack */
> +
> +	return &first->txd;
> +
> +err_desc_get:
> +	atc_desc_put(atchan, first);
> +	return NULL;
> +}
> +
> +/**
>   * atc_dma_cyclic_check_values
>   * Check for too big/unaligned periods and unaligned DMA buffer
>   */
> @@ -1461,8 +1608,10 @@ static int __init at_dma_probe(struct platform_device *pdev)
>  
>  	/* setup platform data for each SoC */
>  	dma_cap_set(DMA_MEMCPY, at91sam9rl_config.cap_mask);
> +	dma_cap_set(DMA_SG, at91sam9rl_config.cap_mask);
>  	dma_cap_set(DMA_MEMCPY, at91sam9g45_config.cap_mask);
>  	dma_cap_set(DMA_SLAVE, at91sam9g45_config.cap_mask);
> +	dma_cap_set(DMA_SG, at91sam9g45_config.cap_mask);
>  
>  	/* get DMA parameters from controller type */
>  	plat_dat = at_dma_get_driver_data(pdev);
> @@ -1582,11 +1731,15 @@ static int __init at_dma_probe(struct platform_device *pdev)
>  		atdma->dma_common.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
>  	}
>  
> +	if (dma_has_cap(DMA_SG, atdma->dma_common.cap_mask))
> +		atdma->dma_common.device_prep_dma_sg = atc_prep_dma_sg;
> +
>  	dma_writel(atdma, EN, AT_DMA_ENABLE);
>  
> -	dev_info(&pdev->dev, "Atmel AHB DMA Controller ( %s%s), %d channels\n",
> +	dev_info(&pdev->dev, "Atmel AHB DMA Controller ( %s%s%s), %d channels\n",
>  	  dma_has_cap(DMA_MEMCPY, atdma->dma_common.cap_mask) ? "cpy " : "",
>  	  dma_has_cap(DMA_SLAVE, atdma->dma_common.cap_mask)  ? "slave " : "",
> +	  dma_has_cap(DMA_SG, atdma->dma_common.cap_mask)  ? "sg-cpy " : "",
>  	  plat_dat->nr_channels);
>  
>  	dma_async_device_register(&atdma->dma_common);
> 


-- 
Nicolas Ferre

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v3 0/2] dma: at_hdmac: Fix residue calculation and add mem to
  2015-02-23 16:54 [PATCH v3 0/2] dma: at_hdmac: Fix residue calculation and add mem to Torsten Fleischer
@ 2015-03-13  9:29   ` Vinod Koul
  2015-02-23 16:54 ` [PATCH v3 2/2] dma: at_hdmac: Add support for memory to memory sg transfers Torsten Fleischer
  2015-03-13  9:29   ` Vinod Koul
  2 siblings, 0 replies; 9+ messages in thread
From: Vinod Koul @ 2015-03-13  9:29 UTC (permalink / raw)
  To: Torsten Fleischer
  Cc: nicolas.ferre, dan.j.williams, linux-arm-kernel, dmaengine, linux-kernel

On Mon, Feb 23, 2015 at 05:54:09PM +0100, Torsten Fleischer wrote:
> From: Torsten Fleischer <torfl6749@gmail.com>
> 
> This series fixes the calculation of the residual bytes and adds support for
> memory to memory scatter-gather transfers.

Applied both,

Thanks

-- 
~Vinod


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v3 0/2] dma: at_hdmac: Fix residue calculation and add mem to
@ 2015-03-13  9:29   ` Vinod Koul
  0 siblings, 0 replies; 9+ messages in thread
From: Vinod Koul @ 2015-03-13  9:29 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Feb 23, 2015 at 05:54:09PM +0100, Torsten Fleischer wrote:
> From: Torsten Fleischer <torfl6749@gmail.com>
> 
> This series fixes the calculation of the residual bytes and adds support for
> memory to memory scatter-gather transfers.

Applied both,

Thanks

-- 
~Vinod

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2015-03-13  9:33 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-02-23 16:54 [PATCH v3 0/2] dma: at_hdmac: Fix residue calculation and add mem to Torsten Fleischer
2015-02-23 16:54 ` [PATCH v3 1/2] dma: at_hdmac: Fix calculation of the residual bytes Torsten Fleischer
2015-03-03 18:26   ` Nicolas Ferre
2015-03-03 18:26     ` Nicolas Ferre
2015-02-23 16:54 ` [PATCH v3 2/2] dma: at_hdmac: Add support for memory to memory sg transfers Torsten Fleischer
2015-03-03 18:29   ` Nicolas Ferre
2015-03-03 18:29     ` Nicolas Ferre
2015-03-13  9:29 ` [PATCH v3 0/2] dma: at_hdmac: Fix residue calculation and add mem to Vinod Koul
2015-03-13  9:29   ` Vinod Koul

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.