dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec

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 <Frank.Li@nxp.com>
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
Link: https://patch.msgid.link/20260916-fsl-edma-dyn-sg-v11-1-02792de71b92@bootlin.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
Benoît Monin 2026-09-16 10:24:11 +02:00 committed by Vinod Koul
parent ef399305a5
commit e2174129dd
3 changed files with 123 additions and 0 deletions

View File

@ -3,6 +3,7 @@
// Copyright (c) 2013-2014 Freescale Semiconductor, Inc
// Copyright (c) 2017 Sysam, Angelo Dureghello <angelo@sysam.it>
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/dmapool.h>
@ -673,6 +674,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,

View File

@ -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,

View File

@ -846,6 +846,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;