[PATCH v3 09/12] dma: edma: Implement multiple linked sets for continuity

From: Joel Fernandes
Date: Mon Aug 05 2013 - 12:20:39 EST


Here we implement splitting up of the total MAX number of slots
available for a channel into 2 cyclically linked sets. Transfer
completion Interrupts are enabled on both linked sets and respective
handler recycles them on completion to process the next linked set.
Both linked sets are cyclically linked to each other to ensure
continuity of DMA operations. Interrupt handlers execute asynchronously
to the EDMA events and recycles the linked sets at the right time,
as a result EDMA is not blocked or dependent on interrupts and DMA
continues till the end of the SG-lists without any interruption.

Suggested-by: Sekhar Nori <nsekhar@xxxxxx>
Signed-off-by: Joel Fernandes <joelf@xxxxxx>
---
drivers/dma/edma.c | 157 +++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 118 insertions(+), 39 deletions(-)

diff --git a/drivers/dma/edma.c b/drivers/dma/edma.c
index df50a04..70923a2 100644
--- a/drivers/dma/edma.c
+++ b/drivers/dma/edma.c
@@ -48,6 +48,7 @@

/* Max of 16 segments per channel to conserve PaRAM slots */
#define MAX_NR_SG 16
+#define MAX_NR_LS (MAX_NR_SG >> 1)
#define EDMA_MAX_SLOTS (MAX_NR_SG+1)
#define EDMA_DESCRIPTORS 16

@@ -57,6 +58,7 @@ struct edma_desc {
int absync;
int pset_nr;
int total_processed;
+ int next_setup_linkset;
struct edmacc_param pset[0];
};

@@ -140,7 +142,9 @@ static void edma_execute(struct edma_chan *echan)
struct edma_desc *edesc;
struct device *dev = echan->vchan.chan.device->dev;

- int i, j, total_left, total_process;
+ int i, total_left, total_link_set;
+ int ls_cur_off, ls_next_off, slot_off;
+ struct edmacc_param tmp_param;

/* If either we processed all psets or we're still not started */
if (!echan->edesc ||
@@ -159,48 +163,121 @@ static void edma_execute(struct edma_chan *echan)

/* Find out how many left */
total_left = edesc->pset_nr - edesc->total_processed;
- total_process = total_left > MAX_NR_SG ? MAX_NR_SG : total_left;
-
-
- /* Write descriptor PaRAM set(s) */
- for (i = 0; i < total_process; i++) {
- j = i + edesc->total_processed;
- edma_write_slot(echan->slot[i], &edesc->pset[j]);
- dev_dbg(echan->vchan.chan.device->dev,
- "\n pset[%d]:\n"
- " chnum\t%d\n"
- " slot\t%d\n"
- " opt\t%08x\n"
- " src\t%08x\n"
- " dst\t%08x\n"
- " abcnt\t%08x\n"
- " ccnt\t%08x\n"
- " bidx\t%08x\n"
- " cidx\t%08x\n"
- " lkrld\t%08x\n",
- j, echan->ch_num, echan->slot[i],
- edesc->pset[j].opt,
- edesc->pset[j].src,
- edesc->pset[j].dst,
- edesc->pset[j].a_b_cnt,
- edesc->pset[j].ccnt,
- edesc->pset[j].src_dst_bidx,
- edesc->pset[j].src_dst_cidx,
- edesc->pset[j].link_bcntrld);
- /* Link to the previous slot if not the last set */
- if (i != (total_process - 1))
+ total_link_set = total_left > MAX_NR_LS ? MAX_NR_LS : total_left;
+
+ /* First time, setup 2 cyclically linked sets, each containing half
+ the slots allocated for this channel */
+ if (edesc->total_processed == 0) {
+ for (i = 0; i < total_link_set; i++) {
+ edma_write_slot(echan->slot[i+1], &edesc->pset[i]);
+
+ if (i != total_link_set - 1) {
+ edma_link(echan->slot[i+1], echan->slot[i+2]);
+ dump_pset(echan, echan->slot[i+1],
+ edesc->pset, i);
+ }
+ }
+
+ edesc->total_processed += total_link_set;
+
+ total_left = edesc->pset_nr - edesc->total_processed;
+
+ total_link_set = total_left > MAX_NR_LS ?
+ MAX_NR_LS : total_left;
+
+ if (total_link_set) {
+ /* Don't setup interrupt for first linked set for cases
+ where total pset_nr is strictly within MAX_NR size */
+ if (total_left > total_link_set)
+ edma_enable_interrupt(echan->slot[i]);
+
+ /* Setup link between linked set 0 to set 1 */
edma_link(echan->slot[i], echan->slot[i+1]);
- /* Final pset links to the dummy pset */
- else
+
+ dump_pset(echan, echan->slot[i], edesc->pset, i-1);
+
+ /* Write out linked set 1 */
+ for (; i < total_link_set + MAX_NR_LS; i++) {
+ edma_write_slot(echan->slot[i+1],
+ &edesc->pset[i]);
+
+ if (i != total_link_set + MAX_NR_LS - 1) {
+ edma_link(echan->slot[i+1],
+ echan->slot[i+2]);
+ dump_pset(echan, echan->slot[i+1],
+ edesc->pset, i);
+ }
+ }
+
+ edesc->total_processed += total_link_set;
+ total_left = edesc->pset_nr - edesc->total_processed;
+
+ if (total_left)
+ /* Setup a link from linked set 1 to set 0 */
+ edma_link(echan->slot[i], echan->slot[1]);
+ else
+ /* Setup a link between linked set 1 to dummy */
+ edma_link(echan->slot[i], echan->ecc->dummy_slot);
+ } else {
+ /* First linked set was enough, simply link to dummy */
edma_link(echan->slot[i], echan->ecc->dummy_slot);
- }
+ }
+
+ edma_enable_interrupt(echan->slot[i]);
+ dump_pset(echan, echan->slot[i], edesc->pset, i-1);

- edesc->total_processed += total_process;
+ edesc->next_setup_linkset = 0;

- if (edesc->total_processed <= MAX_NR_SG) {
+ /* Start the ball rolling... */
dev_dbg(dev, "first transfer starting %d\n", echan->ch_num);
+
+ edma_read_slot(echan->slot[1], &tmp_param);
+ edma_write_slot(echan->slot[0], &tmp_param);
edma_start(echan->ch_num);
+
+ return;
+ }
+
+ /* We got called in the middle of an SG-list transaction as one of the
+ linked sets completed */
+
+ /* Setup offsets into echan_slot, +1 is as slot 0 is for chan */
+ if (edesc->next_setup_linkset == 1) {
+ edesc->next_setup_linkset = 0;
+ ls_cur_off = MAX_NR_LS + 1;
+ ls_next_off = 1;
+ } else {
+ edesc->next_setup_linkset = 1;
+ ls_cur_off = 1;
+ ls_next_off = MAX_NR_LS + 1;
+ }
+
+ for (i = 0; i < total_link_set; i++) {
+ edma_write_slot(echan->slot[i + ls_cur_off],
+ &edesc->pset[i + edesc->total_processed]);
+
+ if (i != total_link_set - 1) {
+ edma_link(echan->slot[i + ls_cur_off],
+ echan->slot[i + ls_cur_off + 1]);
+
+ dump_pset(echan, echan->slot[i + ls_cur_off],
+ edesc->pset, i + edesc->total_processed);
+ }
}
+
+ edesc->total_processed += total_link_set;
+
+ slot_off = total_link_set + ls_cur_off - 1;
+
+ if (edesc->total_processed == edesc->pset_nr)
+ edma_link(echan->slot[slot_off], echan->ecc->dummy_slot);
+ else
+ edma_link(echan->slot[slot_off], echan->slot[ls_next_off]);
+
+ edma_enable_interrupt(echan->slot[slot_off]);
+
+ dump_pset(echan, echan->slot[slot_off],
+ edesc->pset, edesc->total_processed-1);
}

static int edma_terminate_all(struct edma_chan *echan)
@@ -417,15 +494,17 @@ static void edma_callback(unsigned ch_num, u16 ch_status, void *data)
spin_lock_irqsave(&echan->vchan.lock, flags);

edesc = echan->edesc;
+
if (edesc) {
if (edesc->total_processed == edesc->pset_nr) {
- dev_dbg(dev, "transfer complete." \
+ dev_dbg(dev, "Transfer complete,"
" stopping channel %d\n", ch_num);
edma_stop(echan->ch_num);
vchan_cookie_complete(&edesc->vdesc);
} else {
- dev_dbg(dev, "Intermediate transfer complete" \
- " on channel %d\n", ch_num);
+ dev_dbg(dev, "Intermediate transfer "
+ "complete, setup next linked set on "
+ "%d\n ", ch_num);
}

edma_execute(echan);
--
1.7.9.5

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