[linux-yocto] [PATCH 1/9] dma: lsi-dma32: Add support for sg operation

Charlie Paul cpaul.windriver at gmail.com
Thu Jun 19 13:13:43 PDT 2014


From: Anders Berg <anders.berg at lsi.com>

The driver is extended with implementation of DMA scatterlist operation
(device_prep_dma_sg). This allows for DMA operations to be performed on
non-contiguous ranges of memory.

Due to hardware limitations, each entry in the scatterlist needs to have
identical (physical) address bits [36:32]. The reason behind this is that the
top address bits are not maintained per-descriptor, but held in a per-channel
register and is initialized when a transfer is started (with the address bits
from the first scatterlist entry).

Signed-off-by: Anders Berg <anders.berg at lsi.com>
---
 drivers/dma/lsi-dma32.c |  235 +++++++++++++++++++++++++++++++++++------------
 drivers/dma/lsi-dma32.h |    7 +-
 2 files changed, 181 insertions(+), 61 deletions(-)

diff --git a/drivers/dma/lsi-dma32.c b/drivers/dma/lsi-dma32.c
index 0977ef4..a5f3804 100644
--- a/drivers/dma/lsi-dma32.c
+++ b/drivers/dma/lsi-dma32.c
@@ -145,11 +145,13 @@ static int alloc_desc_table(struct gpdma_engine *engine)
 	engine_dbg(engine, "order=%d pa=%#llx va=%p\n",
 		   engine->pool.order, engine->pool.phys, engine->pool.va);
 
-	engine->pool.free = NULL;
-	for (i = 0; i < GPDMA_MAX_DESCRIPTORS-1; i++)
-		engine->pool.va[i].chain = &engine->pool.va[i+1];
-	engine->pool.va[GPDMA_MAX_DESCRIPTORS-1].chain = NULL;
-	engine->pool.free = &engine->pool.va[0];
+	INIT_LIST_HEAD(&engine->free_list);
+	for (i = 0; i < GPDMA_MAX_DESCRIPTORS; i++) {
+		struct gpdma_desc *desc = &engine->pool.va[i];
+		async_tx_ack(&desc->vdesc.tx);
+		desc->engine = engine;
+		list_add_tail(&desc->vdesc.node, &engine->free_list);
+	}
 
 	return 0;
 }
@@ -163,18 +165,59 @@ static void free_desc_table(struct gpdma_engine *engine)
 static struct gpdma_desc *get_descriptor(struct gpdma_engine *engine)
 {
 	unsigned long flags;
-	struct gpdma_desc *desc;
+	struct gpdma_desc *new = NULL, *desc, *tmp;
 
 	spin_lock_irqsave(&engine->lock, flags);
-	desc = engine->pool.free;
-	if (desc) {
-		engine->pool.free = desc->chain;
-		desc->chain = NULL;
-		desc->engine = engine;
+	list_for_each_entry_safe(desc, tmp, &engine->free_list, vdesc.node) {
+		if (async_tx_test_ack(&desc->vdesc.tx)) {
+			list_del(&desc->vdesc.node);
+			new = desc;
+			new->chain = NULL;
+			pr_info(" get_desc %p\n", new);
+			break;
+		}
 	}
 	spin_unlock_irqrestore(&engine->lock, flags);
 
-	return desc;
+	return new;
+}
+
+/**
+ * init_descriptor - Fill out all descriptor fields
+ */
+static void init_descriptor(struct gpdma_desc *desc,
+			    dma_addr_t src, u32 src_acc,
+			    dma_addr_t dst, u32 dst_acc,
+			    size_t len)
+{
+	u32 src_count = len >> src_acc;
+	u32 dst_count = len >> dst_acc;
+	u32 rot_len = (2 * (1 << src_acc)) - 1;
+
+	BUG_ON(src_count * (1<<src_acc) != len);
+	BUG_ON(dst_count * (1<<dst_acc) != len);
+
+	desc->src = src;
+	desc->dst = dst;
+
+	desc->hw.src_x_ctr     = cpu_to_le16(src_count - 1);
+	desc->hw.src_y_ctr     = 0;
+	desc->hw.src_x_mod     = cpu_to_le32(1 << src_acc);
+	desc->hw.src_y_mod     = 0;
+	desc->hw.src_addr      = cpu_to_le32(src & 0xffffffff);
+	desc->hw.src_data_mask = ~0;
+	desc->hw.src_access    = cpu_to_le16((rot_len << 6) |
+					    (src_acc << 3) |
+					    (burst & 7));
+	desc->hw.dst_access    = cpu_to_le16((dst_acc << 3) |
+					    (burst & 7));
+	desc->hw.ch_config     = cpu_to_le32(DMA_CONFIG_ONE_SHOT(1));
+	desc->hw.next_ptr      = 0;
+	desc->hw.dst_x_ctr     = cpu_to_le16(dst_count - 1);
+	desc->hw.dst_y_ctr     = 0;
+	desc->hw.dst_x_mod     = cpu_to_le32(1 << dst_acc);
+	desc->hw.dst_y_mod     = 0;
+	desc->hw.dst_addr      = cpu_to_le32(dst & 0xffffffff);
 }
 
 static phys_addr_t desc_to_paddr(const struct gpdma_channel *dmac,
@@ -195,16 +238,15 @@ static void free_descriptor(struct virt_dma_desc *vd)
 	struct gpdma_desc *desc = to_gpdma_desc(vd);
 	struct gpdma_engine *engine = desc->engine;
 	unsigned long flags;
-	struct gpdma_desc *tail;
 
 	BUG_ON(desc == NULL);
 
-	for (tail = desc; tail->chain != NULL; tail = tail->chain)
-		;
-
 	spin_lock_irqsave(&engine->lock, flags);
-	tail->chain = engine->pool.free;
-	engine->pool.free = desc;
+	while (desc) {
+		pr_info("free_desc %p\n", desc);
+		list_add_tail(&desc->vdesc.node, &engine->free_list);
+		desc = desc->chain;
+	}
 	spin_unlock_irqrestore(&engine->lock, flags);
 }
 
@@ -388,10 +430,8 @@ reset_engine(struct device *dev,
 
 	return count;
 }
-
 static DEVICE_ATTR(soft_reset, S_IWUSR, NULL, reset_engine);
 
-
 /*
  *===========================================================================
  *
@@ -426,6 +466,109 @@ static void gpdma_free_chan_resources(struct dma_chan *chan)
 }
 
 /**
+ * gpdma_prep_sg - Prepares a transfer using sg lists.
+ *
+ */
+static struct dma_async_tx_descriptor *
+gpdma_prep_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 gpdma_channel *dmac = to_gpdma_chan(chan);
+	struct gpdma_desc *first = NULL, *prev = NULL, *new;
+	size_t dst_avail, src_avail;
+	dma_addr_t dst, src;
+	u32 src_acc, dst_acc;
+	size_t len;
+
+	if (dst_nents == 0 || src_nents == 0)
+		return NULL;
+
+	if (dst_sg == NULL || src_sg == NULL)
+		return NULL;
+
+	dst_avail = sg_dma_len(dst_sg);
+	src_avail = sg_dma_len(src_sg);
+
+	/* Loop until we run out of entries... */
+	for (;;) {
+		/* Descriptor count is limited to 64K */
+		len = min_t(size_t, src_avail, dst_avail);
+		len = min_t(size_t, len, (size_t)SZ_64K);
+
+		if (len > 0) {
+			dst = sg_dma_address(dst_sg) +
+				sg_dma_len(dst_sg) - dst_avail;
+			src = sg_dma_address(src_sg) +
+				sg_dma_len(src_sg) - src_avail;
+
+			src_acc = min(ffs((u32)src | len) - 1, 4);
+			dst_acc = min(ffs((u32)dst | len) - 1, 4);
+
+			new = get_descriptor(dmac->engine);
+			if (!new) {
+				ch_dbg(dmac, "ERROR: No descriptor\n");
+				goto fail;
+			}
+
+			init_descriptor(new, src, src_acc, dst, dst_acc, len);
+
+			/* Link descriptors together */
+			if (!first) {
+				first = new;
+			} else {
+				prev->hw.next_ptr = desc_to_paddr(dmac, new);
+				prev->chain = new;
+			}
+			prev = new;
+
+			/* update metadata */
+			dst_avail -= len;
+			src_avail -= len;
+		}
+
+		/* dst: Advance to next sg-entry */
+		if (dst_avail == 0) {
+			/* no more entries: we're done */
+			if (dst_nents == 0)
+				break;
+			/* fetch the next entry: if there are no more: done */
+			dst_sg = sg_next(dst_sg);
+			if (dst_sg == NULL)
+				break;
+
+			dst_nents--;
+			dst_avail = sg_dma_len(dst_sg);
+		}
+
+		/* src: Advance to next sg-entry */
+		if (src_avail == 0) {
+			/* no more entries: we're done */
+			if (src_nents == 0)
+				break;
+			/* fetch the next entry: if there are no more: done */
+			src_sg = sg_next(src_sg);
+			if (src_sg == NULL)
+				break;
+
+			src_nents--;
+			src_avail = sg_dma_len(src_sg);
+		}
+	}
+
+	/* Interrupt on last descriptor in chain */
+	prev->hw.ch_config |= cpu_to_le32(DMA_CONFIG_END);
+
+	return vchan_tx_prep(&dmac->vc, &first->vdesc, flags);
+
+fail:
+	if (first)
+		free_descriptor(&first->vdesc);
+	return NULL;
+}
+
+/**
  * gpdma_prep_memcpy - Prepares a memcpy operation.
  *
  */
@@ -438,7 +581,8 @@ gpdma_prep_memcpy(struct dma_chan *chan,
 {
 	struct gpdma_channel *dmac = to_gpdma_chan(chan);
 	struct gpdma_desc *first = NULL, *prev = NULL, *new;
-	u32 rot_len, x_count, src_size, access;
+	u32 src_acc, dst_acc;
+	size_t len;
 
 	if (size == 0)
 		return NULL;
@@ -450,57 +594,30 @@ gpdma_prep_memcpy(struct dma_chan *chan,
 			goto fail;
 		}
 
-		/* Maximize access width based on job src, dst and length */
-		access = min(ffs((u32)dst | (u32)src | size) - 1, 4);
-		src_size = 1 << access;
+		len = min_t(size_t, size, (size_t)SZ_64K);
 
-		/* Counter register is limited to 64K */
-		x_count = min((size >> access), (size_t)SZ_64K);
-		rot_len = (2 * src_size) - 1;
+		/* Maximize access width based on address and length alignmet */
+		src_acc = min(ffs((u32)src | len) - 1, 4);
+		dst_acc = min(ffs((u32)dst | len) - 1, 4);
 
-		/*
-		 * Fill in descriptor in memory.
-		 */
-		new->hw.src_x_ctr     = cpu_to_le16(x_count - 1);
-		new->hw.src_y_ctr     = 0;
-		new->hw.src_x_mod     = cpu_to_le32(src_size);
-		new->hw.src_y_mod     = 0;
-		new->hw.src_addr      = cpu_to_le32(src & 0xffffffff);
-		new->hw.src_data_mask = ~0;
-		new->hw.src_access    = cpu_to_le16((rot_len << 6) |
-						    (access << 3) |
-						    (burst & 7));
-		new->hw.dst_access    = cpu_to_le16((access << 3) |
-						    (burst & 7));
-		new->hw.ch_config     = cpu_to_le32(DMA_CONFIG_ONE_SHOT(1));
-		new->hw.next_ptr      = 0;
-		new->hw.dst_x_ctr     = cpu_to_le16(x_count - 1);
-		new->hw.dst_y_ctr     = 0;
-		new->hw.dst_x_mod     = cpu_to_le32(src_size);
-		new->hw.dst_y_mod     = 0;
-		new->hw.dst_addr      = cpu_to_le32(dst & 0xffffffff);
-
-		/* Setup sw descriptor */
-		new->src        = src;
-		new->dst        = dst;
+		init_descriptor(new, src, src_acc, dst, dst_acc, len);
 
 		if (!first) {
 			first = new;
 		} else {
 			prev->hw.next_ptr = desc_to_paddr(dmac, new);
 			prev->chain = new;
-			prev->hw.ch_config &=
-				~cpu_to_le32(DMA_CONFIG_LAST_BLOCK |
-					     DMA_CONFIG_INT_DST_EOT);
 		}
 		prev = new;
 
-		size -= x_count << access;
-		src  += x_count << access;
-		dst  += x_count << access;
+		size -= len;
+		src  += len;
+		dst  += len;
 
 	} while (size > 0);
 
+	prev->hw.ch_config |= cpu_to_le32(DMA_CONFIG_END);
+
 	return vchan_tx_prep(&dmac->vc, &first->vdesc, DMA_CTRL_ACK);
 
 fail:
@@ -650,12 +767,14 @@ static int gpdma_of_probe(struct platform_device *op)
 	dma->dev = &op->dev;
 	dma_cap_zero(dma->cap_mask);
 	dma_cap_set(DMA_MEMCPY, dma->cap_mask);
+	dma_cap_set(DMA_SG, dma->cap_mask);
 	dma->copy_align = 2;
 	dma->chancnt = engine->chip->num_channels;
 	dma->device_alloc_chan_resources = gpdma_alloc_chan_resources;
 	dma->device_free_chan_resources = gpdma_free_chan_resources;
 	dma->device_tx_status = gpdma_tx_status;
 	dma->device_prep_dma_memcpy = gpdma_prep_memcpy;
+	dma->device_prep_dma_sg = gpdma_prep_sg;
 	dma->device_issue_pending = gpdma_issue_pending;
 	dma->device_control = gpdma_device_control;
 	INIT_LIST_HEAD(&dma->channels);
diff --git a/drivers/dma/lsi-dma32.h b/drivers/dma/lsi-dma32.h
index 37ed31b..c5a2701 100644
--- a/drivers/dma/lsi-dma32.h
+++ b/drivers/dma/lsi-dma32.h
@@ -104,10 +104,11 @@
 					 DMA_STATUS_TR_COMPLETE   | \
 					 DMA_STATUS_BLK_COMPLETE)
 
+#define DMA_CONFIG_END			(DMA_CONFIG_LAST_BLOCK | \
+					 DMA_CONFIG_INT_DST_EOT)
+
 #define DMA_CONFIG_ONE_SHOT(__ext)	(DMA_CONFIG_DST_SPACE((__ext)) | \
 					 DMA_CONFIG_SRC_SPACE((__ext)) | \
-					 DMA_CONFIG_LAST_BLOCK         | \
-					 DMA_CONFIG_INT_DST_EOT        | \
 					 DMA_CONFIG_TX_EN              | \
 					 DMA_CONFIG_CHAN_EN)
 
@@ -206,11 +207,11 @@ struct gpdma_engine {
 	void __iomem			*gbase;
 	void __iomem			*gpreg;
 	spinlock_t			lock;
+	struct list_head                free_list;
 	struct {
 		u32                     order;
 		dma_addr_t              phys;
 		struct gpdma_desc       *va;
-		struct gpdma_desc       *free;
 	} pool;
 	struct dma_device		dma_device;
 };
-- 
1.7.9.5



More information about the linux-yocto mailing list