[PATCH v8 1/2] dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec
Benoît Monin <[email protected]> Mon, 03 Aug 2026 18:02:00 +0200
| Newsgroups | dev.linux.lists.imx,org.kernel.vger.dmaengine,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Add implementation of .device_prep_peripheral_dma_vec() callback to setup a scatter/gather DMA transfer from an array of dma_vec structures. Setup a cyclic transfer if the DMA_PREP_REPEAT flag is set. Reviewed-by: Frank Li <[email protected]> Signed-off-by: Benoît Monin <[email protected]> --- drivers/dma/fsl-edma-common.c | 116 ++++++++++++++++++++++++++++++++++++++++++ drivers/dma/fsl-edma-common.h | 4 ++ drivers/dma/fsl-edma-main.c | 2 + 3 files changed, 122 insertions(+) diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c index bb7531c456df..c5f5951c988b 100644 --- a/drivers/dma/fsl-edma-common.c +++ b/drivers/dma/fsl-edma-common.c @@ -673,6 +673,122 @@ struct dma_async_tx_descriptor *fsl_edma_prep_dma_cyclic( return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags); } +struct dma_async_tx_descriptor * +fsl_edma_prep_peripheral_dma_vec(struct dma_chan *chan, const struct dma_vec *vecs, + size_t nb, enum dma_transfer_direction direction, + unsigned long flags) +{ + struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan); + dma_addr_t src_addr, dst_addr, last_sg; + struct fsl_edma_desc *fsl_desc; + u16 soff, doff, iter; + u32 nbytes; + int i; + + if (!is_slave_direction(direction)) + return NULL; + + if (!fsl_edma_prep_slave_dma(fsl_chan, direction)) + return NULL; + + fsl_desc = fsl_edma_alloc_desc(fsl_chan, nb); + if (!fsl_desc) + return NULL; + fsl_desc->iscyclic = flags & DMA_PREP_REPEAT; + fsl_desc->dirn = direction; + + if (direction == DMA_MEM_TO_DEV) { + if (!fsl_chan->cfg.src_addr_width) + fsl_chan->cfg.src_addr_width = fsl_chan->cfg.dst_addr_width; + fsl_chan->attr = + fsl_edma_get_tcd_attr(fsl_chan->cfg.src_addr_width, + fsl_chan->cfg.dst_addr_width); + nbytes = fsl_chan->cfg.dst_addr_width * fsl_chan->cfg.dst_maxburst; + } else { + if (!fsl_chan->cfg.dst_addr_width) + fsl_chan->cfg.dst_addr_width = fsl_chan->cfg.src_addr_width; + fsl_chan->attr = + fsl_edma_get_tcd_attr(fsl_chan->cfg.src_addr_width, + fsl_chan->cfg.dst_addr_width); + nbytes = fsl_chan->cfg.src_addr_width * fsl_chan->cfg.src_maxburst; + } + + for (i = 0; i < nb; i++) { + if (direction == DMA_MEM_TO_DEV) { + src_addr = vecs[i].addr; + dst_addr = fsl_chan->dma_dev_addr; + soff = fsl_chan->cfg.dst_addr_width; + doff = 0; + } else if (direction == DMA_DEV_TO_MEM) { + src_addr = fsl_chan->dma_dev_addr; + dst_addr = vecs[i].addr; + soff = 0; + doff = fsl_chan->cfg.src_addr_width; + } else { + /* DMA_DEV_TO_DEV */ + src_addr = fsl_chan->cfg.src_addr; + dst_addr = fsl_chan->cfg.dst_addr; + soff = 0; + doff = 0; + } + + /* + * Choose the suitable burst length if dma_vec length is not + * multiple of burst length so that the whole transfer length is + * multiple of minor loop(burst length). + */ + if (nbytes && vecs[i].len % nbytes) { + u32 width = (direction == DMA_DEV_TO_MEM) ? doff : soff; + u32 burst = (direction == DMA_DEV_TO_MEM) ? + fsl_chan->cfg.src_maxburst : + fsl_chan->cfg.dst_maxburst; + int j; + + for (j = burst; j > 1; j--) { + if (!(vecs[i].len % (j * width))) { + nbytes = j * width; + break; + } + } + /* Set burst size as 1 if there's no suitable one */ + if (j == 1) + nbytes = width; + } + + if (!nbytes || vecs[i].len / nbytes > FIELD_MAX(EDMA_TCD_ITER_MASK)) + goto err_free_desc; + + iter = vecs[i].len / nbytes; + if (i < nb - 1) { + last_sg = fsl_desc->tcd[(i + 1)].ptcd; + fsl_edma_fill_tcd(fsl_chan, fsl_desc->tcd[i].vtcd, src_addr, + dst_addr, fsl_chan->attr, soff, + nbytes, 0, iter, iter, doff, last_sg, + false, false, true); + } else { + if (fsl_desc->iscyclic) { + last_sg = fsl_desc->tcd[0].ptcd; + fsl_edma_fill_tcd(fsl_chan, fsl_desc->tcd[i].vtcd, src_addr, + dst_addr, fsl_chan->attr, soff, + nbytes, 0, iter, iter, doff, last_sg, + true, false, true); + } else { + last_sg = 0; + fsl_edma_fill_tcd(fsl_chan, fsl_desc->tcd[i].vtcd, src_addr, + dst_addr, fsl_chan->attr, soff, + nbytes, 0, iter, iter, doff, last_sg, + true, true, false); + } + } + } + + return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags); + +err_free_desc: + fsl_edma_free_desc(&fsl_desc->vdesc); + return NULL; +} + struct dma_async_tx_descriptor *fsl_edma_prep_slave_sg( struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction direction, diff --git a/drivers/dma/fsl-edma-common.h b/drivers/dma/fsl-edma-common.h index 205a96489094..0d028048701d 100644 --- a/drivers/dma/fsl-edma-common.h +++ b/drivers/dma/fsl-edma-common.h @@ -496,6 +496,10 @@ struct dma_async_tx_descriptor *fsl_edma_prep_dma_cyclic( struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len, size_t period_len, enum dma_transfer_direction direction, unsigned long flags); +struct dma_async_tx_descriptor *fsl_edma_prep_peripheral_dma_vec( + struct dma_chan *chan, const struct dma_vec *vecs, + size_t nb, enum dma_transfer_direction direction, + unsigned long flags); struct dma_async_tx_descriptor *fsl_edma_prep_slave_sg( struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction direction, diff --git a/drivers/dma/fsl-edma-main.c b/drivers/dma/fsl-edma-main.c index 36155ab1602a..6693b4270a1a 100644 --- a/drivers/dma/fsl-edma-main.c +++ b/drivers/dma/fsl-edma-main.c @@ -841,6 +841,8 @@ static int fsl_edma_probe(struct platform_device *pdev) fsl_edma->dma_dev.device_free_chan_resources = fsl_edma_free_chan_resources; fsl_edma->dma_dev.device_tx_status = fsl_edma_tx_status; + fsl_edma->dma_dev.device_prep_peripheral_dma_vec + = fsl_edma_prep_peripheral_dma_vec; fsl_edma->dma_dev.device_prep_slave_sg = fsl_edma_prep_slave_sg; fsl_edma->dma_dev.device_prep_dma_cyclic = fsl_edma_prep_dma_cyclic; fsl_edma->dma_dev.device_prep_dma_memcpy = fsl_edma_prep_memcpy; -- 2.55.0