* [PATCH RFC 0/2] dmaengine: fsl-edma: Scatter/gather improvements
@ 2026-04-30 9:49 Benoît Monin
2026-04-30 9:49 ` [PATCH RFC 1/2] dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec Benoît Monin
2026-04-30 9:49 ` [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining Benoît Monin
0 siblings, 2 replies; 11+ messages in thread
From: Benoît Monin @ 2026-04-30 9:49 UTC (permalink / raw)
To: Frank Li, Vinod Koul
Cc: Thomas Petazzoni, Frank Li, imx, dmaengine, linux-kernel,
Benoît Monin
This series adds support for scatter/gather DMA transfers via dma_vec
and dynamic descriptor chaining to the Freescale eDMA controller driver.
The first patch implements the .device_prep_peripheral_dma_vec() callback,
enabling the DMA engine to accept an array of dma_vec structures. This
callback supports both regular and cyclic transfer modes.
The second patch introduces dynamic scatter/gather chaining, which allows
multiple DMA descriptors to be linked together without stopping the channel.
This optimization eliminates idle periods when back-to-back transfers are
submitted, improving throughput and reducing latency. The implementation
carefully preserves cyclic transfer semantics and respects hardware
constraints on platforms with split register layouts.
I am posting this as an RFC since I only tested it on the i.MX93. The
dynamic scatter/gather chaining should work with other eDMA controller
with split register layout.
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
Benoît Monin (2):
dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec
dmaengine: fsl-edma: Support dynamic scatter/gather chaining
drivers/dma/fsl-edma-common.c | 174 +++++++++++++++++++++++++++++++++++++++++-
drivers/dma/fsl-edma-common.h | 4 +
drivers/dma/fsl-edma-main.c | 2 +
drivers/dma/fsl-edma-trace.h | 5 ++
4 files changed, 181 insertions(+), 4 deletions(-)
---
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
change-id: 20260428-fsl-edma-dyn-sg-960731e37da2
Best regards,
--
Benoît Monin, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC 1/2] dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec
2026-04-30 9:49 [PATCH RFC 0/2] dmaengine: fsl-edma: Scatter/gather improvements Benoît Monin
@ 2026-04-30 9:49 ` Benoît Monin
2026-05-04 15:58 ` Frank Li
2026-04-30 9:49 ` [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining Benoît Monin
1 sibling, 1 reply; 11+ messages in thread
From: Benoît Monin @ 2026-04-30 9:49 UTC (permalink / raw)
To: Frank Li, Vinod Koul
Cc: Thomas Petazzoni, Frank Li, imx, dmaengine, linux-kernel,
Benoît Monin
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.
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
drivers/dma/fsl-edma-common.c | 110 ++++++++++++++++++++++++++++++++++++++++++
drivers/dma/fsl-edma-common.h | 4 ++
drivers/dma/fsl-edma-main.c | 2 +
3 files changed, 116 insertions(+)
diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c
index bb7531c456df..26a5ecf493b9 100644
--- a/drivers/dma/fsl-edma-common.c
+++ b/drivers/dma/fsl-edma-common.c
@@ -673,6 +673,116 @@ 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);
+ struct fsl_edma_desc *fsl_desc;
+ dma_addr_t src_addr, dst_addr, last_sg;
+ 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 (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;
+ }
+ 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);
+}
+
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.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining
2026-04-30 9:49 [PATCH RFC 0/2] dmaengine: fsl-edma: Scatter/gather improvements Benoît Monin
2026-04-30 9:49 ` [PATCH RFC 1/2] dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec Benoît Monin
@ 2026-04-30 9:49 ` Benoît Monin
2026-05-04 16:04 ` Frank Li
1 sibling, 1 reply; 11+ messages in thread
From: Benoît Monin @ 2026-04-30 9:49 UTC (permalink / raw)
To: Frank Li, Vinod Koul
Cc: Thomas Petazzoni, Frank Li, imx, dmaengine, linux-kernel,
Benoît Monin
Implement dynamic linking of scatter/gather transfers to enable
chaining multiple DMA descriptors without stopping the channel.
This avoids waiting for the channel to go idle if there is another
transaction already issued.
Add fsl_edma_link_sg() to dynamically link the last TCD of a previously
submitted descriptor to the first TCD of a new descriptor by setting
the scatter/gather address and the E_SG flag, and keeping the channel
active by clearing the DREQ bit.
Linking is only done if the last TCD was set to disable the DMA channel,
to prevent corrupting cyclic transaction.
Update fsl_edma_xfer_desc() to avoid re-initializing the hardware when a
transfer is already in progress, allowing seamless chaining of descriptors.
Modify the transfer completion handler to check the DONE flag in the
channel CSR before marking the transfer complete. Since this flag is
only available on SoC with the split registers layout, we only link
transactions for DMA controllers flagged with FSL_EDMA_DRV_SPLIT_REG.
Add trace event for scatter/gather linking operations.
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
drivers/dma/fsl-edma-common.c | 64 ++++++++++++++++++++++++++++++++++++++++---
drivers/dma/fsl-edma-trace.h | 5 ++++
2 files changed, 65 insertions(+), 4 deletions(-)
diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c
index 26a5ecf493b9..7094c747defa 100644
--- a/drivers/dma/fsl-edma-common.c
+++ b/drivers/dma/fsl-edma-common.c
@@ -58,7 +58,10 @@ void fsl_edma_tx_chan_handler(struct fsl_edma_chan *fsl_chan)
list_del(&fsl_chan->edesc->vdesc.node);
vchan_cookie_complete(&fsl_chan->edesc->vdesc);
fsl_chan->edesc = NULL;
- fsl_chan->status = DMA_COMPLETE;
+ if (!(fsl_edma_drvflags(fsl_chan) & FSL_EDMA_DRV_SPLIT_REG) ||
+ (edma_readl_chreg(fsl_chan, ch_csr) & EDMA_V3_CH_CSR_DONE)) {
+ fsl_chan->status = DMA_COMPLETE;
+ }
} else {
vchan_cyclic_callback(&fsl_chan->edesc->vdesc);
}
@@ -673,6 +676,51 @@ struct dma_async_tx_descriptor *fsl_edma_prep_dma_cyclic(
return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
}
+static void fsl_edma_link_sg(struct fsl_edma_chan *fsl_chan, struct fsl_edma_desc *fsl_desc)
+{
+ u32 flags = fsl_edma_drvflags(fsl_chan);
+ struct virt_dma_desc *vdesc;
+ struct fsl_edma_desc *prev_desc;
+ struct fsl_edma_hw_tcd *last_tcd;
+ u16 csr;
+
+ if (!(flags & FSL_EDMA_DRV_SPLIT_REG))
+ return;
+
+ guard(spinlock_irqsave)(&fsl_chan->vchan.lock);
+
+ vdesc = list_last_entry_or_null(&fsl_chan->vchan.desc_issued,
+ struct virt_dma_desc, node);
+ if (!vdesc)
+ vdesc = list_last_entry_or_null(&fsl_chan->vchan.desc_submitted,
+ struct virt_dma_desc, node);
+ if (!vdesc)
+ return;
+
+ prev_desc = to_fsl_edma_desc(vdesc);
+ last_tcd = prev_desc->tcd[prev_desc->n_tcds - 1].vtcd;
+
+ csr = fsl_edma_get_tcd_to_cpu(fsl_chan, last_tcd, csr);
+ if (!(csr & EDMA_TCD_CSR_D_REQ))
+ return;
+
+ fsl_edma_set_tcd_to_le(fsl_chan, last_tcd, fsl_desc->tcd[0].ptcd, dlast_sga);
+
+ csr &= ~EDMA_TCD_CSR_D_REQ;
+ csr |= EDMA_TCD_CSR_E_SG;
+ fsl_edma_set_tcd_to_le(fsl_chan, last_tcd, csr, csr);
+
+ if (prev_desc == fsl_chan->edesc && prev_desc->n_tcds == 1) {
+ if (flags & FSL_EDMA_DRV_CLEAR_DONE_E_SG)
+ edma_writel_chreg(fsl_chan, edma_readl_chreg(fsl_chan, ch_csr), ch_csr);
+
+ edma_cp_tcd_to_reg(fsl_chan, last_tcd, dlast_sga);
+ edma_cp_tcd_to_reg(fsl_chan, last_tcd, csr);
+ }
+
+ trace_edma_link_sg(fsl_chan, last_tcd);
+}
+
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,
@@ -780,6 +828,9 @@ struct dma_async_tx_descriptor *fsl_edma_prep_peripheral_dma_vec(
}
}
+ if (!fsl_desc->iscyclic)
+ fsl_edma_link_sg(fsl_chan, fsl_desc);
+
return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
}
@@ -883,6 +934,8 @@ struct dma_async_tx_descriptor *fsl_edma_prep_slave_sg(
}
}
+ fsl_edma_link_sg(fsl_chan, fsl_desc);
+
return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
}
@@ -925,9 +978,12 @@ void fsl_edma_xfer_desc(struct fsl_edma_chan *fsl_chan)
if (!vdesc)
return;
fsl_chan->edesc = to_fsl_edma_desc(vdesc);
- fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd);
- fsl_edma_enable_request(fsl_chan);
- fsl_chan->status = DMA_IN_PROGRESS;
+
+ if (fsl_chan->status != DMA_IN_PROGRESS) {
+ fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd);
+ fsl_edma_enable_request(fsl_chan);
+ fsl_chan->status = DMA_IN_PROGRESS;
+ }
}
void fsl_edma_issue_pending(struct dma_chan *chan)
diff --git a/drivers/dma/fsl-edma-trace.h b/drivers/dma/fsl-edma-trace.h
index d3541301a247..ac319d2dbb90 100644
--- a/drivers/dma/fsl-edma-trace.h
+++ b/drivers/dma/fsl-edma-trace.h
@@ -119,6 +119,11 @@ DEFINE_EVENT(edma_log_tcd, edma_fill_tcd,
TP_ARGS(chan, tcd)
);
+DEFINE_EVENT(edma_log_tcd, edma_link_sg,
+ TP_PROTO(struct fsl_edma_chan *chan, void *tcd),
+ TP_ARGS(chan, tcd)
+);
+
#endif
/* this part must be outside header guard */
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH RFC 1/2] dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec
2026-04-30 9:49 ` [PATCH RFC 1/2] dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec Benoît Monin
@ 2026-05-04 15:58 ` Frank Li
2026-05-05 13:51 ` Benoît Monin
0 siblings, 1 reply; 11+ messages in thread
From: Frank Li @ 2026-05-04 15:58 UTC (permalink / raw)
To: Benoît Monin
Cc: Vinod Koul, Thomas Petazzoni, Frank Li, imx, dmaengine,
linux-kernel
On Thu, Apr 30, 2026 at 11:49:32AM +0200, Benoît Monin wrote:
> 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.
>
> Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
> ---
Please remove RFC for this patch.
Frank
> drivers/dma/fsl-edma-common.c | 110 ++++++++++++++++++++++++++++++++++++++++++
> drivers/dma/fsl-edma-common.h | 4 ++
> drivers/dma/fsl-edma-main.c | 2 +
> 3 files changed, 116 insertions(+)
>
> diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c
> index bb7531c456df..26a5ecf493b9 100644
> --- a/drivers/dma/fsl-edma-common.c
> +++ b/drivers/dma/fsl-edma-common.c
> @@ -673,6 +673,116 @@ 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);
> + struct fsl_edma_desc *fsl_desc;
> + dma_addr_t src_addr, dst_addr, last_sg;
> + 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 (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;
> + }
> + 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);
> +}
> +
> 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.54.0
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining
2026-04-30 9:49 ` [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining Benoît Monin
@ 2026-05-04 16:04 ` Frank Li
2026-05-05 13:51 ` Benoît Monin
0 siblings, 1 reply; 11+ messages in thread
From: Frank Li @ 2026-05-04 16:04 UTC (permalink / raw)
To: Benoît Monin
Cc: Vinod Koul, Thomas Petazzoni, Frank Li, imx, dmaengine,
linux-kernel
On Thu, Apr 30, 2026 at 11:49:33AM +0200, Benoît Monin wrote:
> Implement dynamic linking of scatter/gather transfers to enable
> chaining multiple DMA descriptors without stopping the channel.
> This avoids waiting for the channel to go idle if there is another
> transaction already issued.
>
> Add fsl_edma_link_sg() to dynamically link the last TCD of a previously
> submitted descriptor to the first TCD of a new descriptor by setting
> the scatter/gather address and the E_SG flag, and keeping the channel
> active by clearing the DREQ bit.
Thank for your trying this, which I want to do long time ago.
The key problem is
how to guarratee safe when link to last TCD and DMA is working it?
if update last TCD's next pointer before DMA load it, it is good.
but, if update last TCD's next pointer after DMA load it. DMA engine
may stop.
how do you test it? and how much preformance improved?
Frank
>
> Linking is only done if the last TCD was set to disable the DMA channel,
> to prevent corrupting cyclic transaction.
>
> Update fsl_edma_xfer_desc() to avoid re-initializing the hardware when a
> transfer is already in progress, allowing seamless chaining of descriptors.
>
> Modify the transfer completion handler to check the DONE flag in the
> channel CSR before marking the transfer complete. Since this flag is
> only available on SoC with the split registers layout, we only link
> transactions for DMA controllers flagged with FSL_EDMA_DRV_SPLIT_REG.
>
> Add trace event for scatter/gather linking operations.
>
> Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
> ---
> drivers/dma/fsl-edma-common.c | 64 ++++++++++++++++++++++++++++++++++++++++---
> drivers/dma/fsl-edma-trace.h | 5 ++++
> 2 files changed, 65 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c
> index 26a5ecf493b9..7094c747defa 100644
> --- a/drivers/dma/fsl-edma-common.c
> +++ b/drivers/dma/fsl-edma-common.c
> @@ -58,7 +58,10 @@ void fsl_edma_tx_chan_handler(struct fsl_edma_chan *fsl_chan)
> list_del(&fsl_chan->edesc->vdesc.node);
> vchan_cookie_complete(&fsl_chan->edesc->vdesc);
> fsl_chan->edesc = NULL;
> - fsl_chan->status = DMA_COMPLETE;
> + if (!(fsl_edma_drvflags(fsl_chan) & FSL_EDMA_DRV_SPLIT_REG) ||
> + (edma_readl_chreg(fsl_chan, ch_csr) & EDMA_V3_CH_CSR_DONE)) {
> + fsl_chan->status = DMA_COMPLETE;
> + }
> } else {
> vchan_cyclic_callback(&fsl_chan->edesc->vdesc);
> }
> @@ -673,6 +676,51 @@ struct dma_async_tx_descriptor *fsl_edma_prep_dma_cyclic(
> return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
> }
>
> +static void fsl_edma_link_sg(struct fsl_edma_chan *fsl_chan, struct fsl_edma_desc *fsl_desc)
> +{
> + u32 flags = fsl_edma_drvflags(fsl_chan);
> + struct virt_dma_desc *vdesc;
> + struct fsl_edma_desc *prev_desc;
> + struct fsl_edma_hw_tcd *last_tcd;
> + u16 csr;
> +
> + if (!(flags & FSL_EDMA_DRV_SPLIT_REG))
> + return;
> +
> + guard(spinlock_irqsave)(&fsl_chan->vchan.lock);
> +
> + vdesc = list_last_entry_or_null(&fsl_chan->vchan.desc_issued,
> + struct virt_dma_desc, node);
> + if (!vdesc)
> + vdesc = list_last_entry_or_null(&fsl_chan->vchan.desc_submitted,
> + struct virt_dma_desc, node);
> + if (!vdesc)
> + return;
> +
> + prev_desc = to_fsl_edma_desc(vdesc);
> + last_tcd = prev_desc->tcd[prev_desc->n_tcds - 1].vtcd;
> +
> + csr = fsl_edma_get_tcd_to_cpu(fsl_chan, last_tcd, csr);
> + if (!(csr & EDMA_TCD_CSR_D_REQ))
> + return;
> +
> + fsl_edma_set_tcd_to_le(fsl_chan, last_tcd, fsl_desc->tcd[0].ptcd, dlast_sga);
> +
> + csr &= ~EDMA_TCD_CSR_D_REQ;
> + csr |= EDMA_TCD_CSR_E_SG;
> + fsl_edma_set_tcd_to_le(fsl_chan, last_tcd, csr, csr);
> +
> + if (prev_desc == fsl_chan->edesc && prev_desc->n_tcds == 1) {
> + if (flags & FSL_EDMA_DRV_CLEAR_DONE_E_SG)
> + edma_writel_chreg(fsl_chan, edma_readl_chreg(fsl_chan, ch_csr), ch_csr);
> +
> + edma_cp_tcd_to_reg(fsl_chan, last_tcd, dlast_sga);
> + edma_cp_tcd_to_reg(fsl_chan, last_tcd, csr);
> + }
> +
> + trace_edma_link_sg(fsl_chan, last_tcd);
> +}
> +
> 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,
> @@ -780,6 +828,9 @@ struct dma_async_tx_descriptor *fsl_edma_prep_peripheral_dma_vec(
> }
> }
>
> + if (!fsl_desc->iscyclic)
> + fsl_edma_link_sg(fsl_chan, fsl_desc);
> +
> return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
> }
>
> @@ -883,6 +934,8 @@ struct dma_async_tx_descriptor *fsl_edma_prep_slave_sg(
> }
> }
>
> + fsl_edma_link_sg(fsl_chan, fsl_desc);
> +
> return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
> }
>
> @@ -925,9 +978,12 @@ void fsl_edma_xfer_desc(struct fsl_edma_chan *fsl_chan)
> if (!vdesc)
> return;
> fsl_chan->edesc = to_fsl_edma_desc(vdesc);
> - fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd);
> - fsl_edma_enable_request(fsl_chan);
> - fsl_chan->status = DMA_IN_PROGRESS;
> +
> + if (fsl_chan->status != DMA_IN_PROGRESS) {
> + fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd);
> + fsl_edma_enable_request(fsl_chan);
> + fsl_chan->status = DMA_IN_PROGRESS;
> + }
> }
>
> void fsl_edma_issue_pending(struct dma_chan *chan)
> diff --git a/drivers/dma/fsl-edma-trace.h b/drivers/dma/fsl-edma-trace.h
> index d3541301a247..ac319d2dbb90 100644
> --- a/drivers/dma/fsl-edma-trace.h
> +++ b/drivers/dma/fsl-edma-trace.h
> @@ -119,6 +119,11 @@ DEFINE_EVENT(edma_log_tcd, edma_fill_tcd,
> TP_ARGS(chan, tcd)
> );
>
> +DEFINE_EVENT(edma_log_tcd, edma_link_sg,
> + TP_PROTO(struct fsl_edma_chan *chan, void *tcd),
> + TP_ARGS(chan, tcd)
> +);
> +
> #endif
>
> /* this part must be outside header guard */
>
> --
> 2.54.0
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC 1/2] dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec
2026-05-04 15:58 ` Frank Li
@ 2026-05-05 13:51 ` Benoît Monin
0 siblings, 0 replies; 11+ messages in thread
From: Benoît Monin @ 2026-05-05 13:51 UTC (permalink / raw)
To: Frank Li
Cc: Vinod Koul, Thomas Petazzoni, Frank Li, imx, dmaengine,
linux-kernel
On Monday, 4 May 2026 at 17:58:08 CEST, Frank Li wrote:
> On Thu, Apr 30, 2026 at 11:49:32AM +0200, Benoît Monin wrote:
> > 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.
> >
> > Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
> > ---
>
> Please remove RFC for this patch.
>
Ok, will do.
Best regards,
--
Benoît Monin, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining
2026-05-04 16:04 ` Frank Li
@ 2026-05-05 13:51 ` Benoît Monin
2026-05-05 15:07 ` Frank Li
0 siblings, 1 reply; 11+ messages in thread
From: Benoît Monin @ 2026-05-05 13:51 UTC (permalink / raw)
To: Frank Li
Cc: Vinod Koul, Thomas Petazzoni, Frank Li, imx, dmaengine,
linux-kernel
On Monday, 4 May 2026 at 18:04:49 CEST, Frank Li wrote:
> On Thu, Apr 30, 2026 at 11:49:33AM +0200, Benoît Monin wrote:
> > Implement dynamic linking of scatter/gather transfers to enable
> > chaining multiple DMA descriptors without stopping the channel.
> > This avoids waiting for the channel to go idle if there is another
> > transaction already issued.
> >
> > Add fsl_edma_link_sg() to dynamically link the last TCD of a previously
> > submitted descriptor to the first TCD of a new descriptor by setting
> > the scatter/gather address and the E_SG flag, and keeping the channel
> > active by clearing the DREQ bit.
>
> Thank for your trying this, which I want to do long time ago.
>
> The key problem is
>
> how to guarratee safe when link to last TCD and DMA is working it?
> if update last TCD's next pointer before DMA load it, it is good.
> but, if update last TCD's next pointer after DMA load it. DMA engine
> may stop.
>
I followed what is described in the dynamic scatter/gather chapter of the
i.MX93 reference manual. We update two registers of the last TCD when
chaining SG transfers, first TCD_DLAST_SGA then TCD_CSR. This gives us
three possibilities of concurrence between CPU writes and eDMA reads.
* First case, both registers are updated before the eDMA reads the TCD, we
get the expected chaining.
* Second case, both registers are updated too late and the eDMA have already
read the TCD, we are back to the current implementation. After processing
the last TCD, the eDMA will disable the channel and the call to
fsl_edma_xfer_desc() from fsl_edma_tx_chan_handler() will move to the next
issued descriptor and re-enable the channel.
* Final case, only TCD_DLAST_SGA gets picked up by the eDMA. The eDMA will
also disable the channel after processing the last TCD, the only difference
is that it will update TCD_DADDR by adding the value TCD_DLAST_SGA. Since
we are not reusing TCD_DADDR, this has no impact.
> how do you test it? and how much preformance improved?
I did my tests by doing SPI transfers with the LPSPI controllers, doing DMA
transactions with different number of buffers and different buffer sizes.
Without chaining, interruptions on the SPI bus occur between each DMA
transaction. With chaining, the activity on the SPI bus is continuous as
long as DMA transactions are issued before the end of the current
transaction.
Best regards,
--
Benoît Monin, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining
2026-05-05 13:51 ` Benoît Monin
@ 2026-05-05 15:07 ` Frank Li
2026-05-06 14:01 ` Benoît Monin
0 siblings, 1 reply; 11+ messages in thread
From: Frank Li @ 2026-05-05 15:07 UTC (permalink / raw)
To: Benoît Monin
Cc: Vinod Koul, Thomas Petazzoni, Frank Li, imx, dmaengine,
linux-kernel
On Tue, May 05, 2026 at 03:51:51PM +0200, Benoît Monin wrote:
> On Monday, 4 May 2026 at 18:04:49 CEST, Frank Li wrote:
> > On Thu, Apr 30, 2026 at 11:49:33AM +0200, Benoît Monin wrote:
> > > Implement dynamic linking of scatter/gather transfers to enable
> > > chaining multiple DMA descriptors without stopping the channel.
> > > This avoids waiting for the channel to go idle if there is another
> > > transaction already issued.
> > >
> > > Add fsl_edma_link_sg() to dynamically link the last TCD of a previously
> > > submitted descriptor to the first TCD of a new descriptor by setting
> > > the scatter/gather address and the E_SG flag, and keeping the channel
> > > active by clearing the DREQ bit.
> >
> > Thank for your trying this, which I want to do long time ago.
> >
> > The key problem is
> >
> > how to guarratee safe when link to last TCD and DMA is working it?
> > if update last TCD's next pointer before DMA load it, it is good.
> > but, if update last TCD's next pointer after DMA load it. DMA engine
> > may stop.
> >
> I followed what is described in the dynamic scatter/gather chapter of the
> i.MX93 reference manual. We update two registers of the last TCD when
> chaining SG transfers, first TCD_DLAST_SGA then TCD_CSR. This gives us
> three possibilities of concurrence between CPU writes and eDMA reads.
>
> * First case, both registers are updated before the eDMA reads the TCD, we
> get the expected chaining.
> * Second case, both registers are updated too late and the eDMA have already
> read the TCD, we are back to the current implementation. After processing
> the last TCD, the eDMA will disable the channel and the call to
> fsl_edma_xfer_desc() from fsl_edma_tx_chan_handler() will move to the next
> issued descriptor and re-enable the channel.
> * Final case, only TCD_DLAST_SGA gets picked up by the eDMA. The eDMA will
> also disable the channel after processing the last TCD, the only difference
> is that it will update TCD_DADDR by adding the value TCD_DLAST_SGA. Since
> we are not reusing TCD_DADDR, this has no impact.
Okay, possible work, let me think more detail after you remove RFC.
>
> > how do you test it? and how much preformance improved?
> I did my tests by doing SPI transfers with the LPSPI controllers, doing DMA
> transactions with different number of buffers and different buffer sizes.
> Without chaining, interruptions on the SPI bus occur between each DMA
> transaction. With chaining, the activity on the SPI bus is continuous as
> long as DMA transactions are issued before the end of the current
> transaction.
Does SPI support issue new transfers without wait for previous transfer
complete, or SPI transfer already support async queue?
Frank
>
> Best regards,
> --
> Benoît Monin, Bootlin
> Embedded Linux and Kernel engineering
> https://bootlin.com
>
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining
2026-05-05 15:07 ` Frank Li
@ 2026-05-06 14:01 ` Benoît Monin
2026-05-06 14:48 ` Frank Li
0 siblings, 1 reply; 11+ messages in thread
From: Benoît Monin @ 2026-05-06 14:01 UTC (permalink / raw)
To: Frank Li
Cc: Vinod Koul, Thomas Petazzoni, Frank Li, imx, dmaengine,
linux-kernel
On Tuesday, 5 May 2026 at 17:07:48 CEST, Frank Li wrote:
> > > how do you test it? and how much preformance improved?
> > I did my tests by doing SPI transfers with the LPSPI controllers, doing DMA
> > transactions with different number of buffers and different buffer sizes.
> > Without chaining, interruptions on the SPI bus occur between each DMA
> > transaction. With chaining, the activity on the SPI bus is continuous as
> > long as DMA transactions are issued before the end of the current
> > transaction.
>
> Does SPI support issue new transfers without wait for previous transfer
> complete, or SPI transfer already support async queue?
>
This is done with a local version of fsl-lpspi driver adding a simple
offload support by borrowing the DMA channels allocated to the SPI
controller. I can then issue multiple DMA transactions with the dma_buf API
of the IIO subsystem and trigger SG chaining.
Best regards,
--
Benoît Monin, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining
2026-05-06 14:01 ` Benoît Monin
@ 2026-05-06 14:48 ` Frank Li
2026-05-07 14:15 ` Benoît Monin
0 siblings, 1 reply; 11+ messages in thread
From: Frank Li @ 2026-05-06 14:48 UTC (permalink / raw)
To: Benoît Monin
Cc: Vinod Koul, Thomas Petazzoni, Frank Li, imx, dmaengine,
linux-kernel
On Wed, May 06, 2026 at 04:01:37PM +0200, Benoît Monin wrote:
> On Tuesday, 5 May 2026 at 17:07:48 CEST, Frank Li wrote:
> > > > how do you test it? and how much preformance improved?
> > > I did my tests by doing SPI transfers with the LPSPI controllers, doing DMA
> > > transactions with different number of buffers and different buffer sizes.
> > > Without chaining, interruptions on the SPI bus occur between each DMA
> > > transaction. With chaining, the activity on the SPI bus is continuous as
> > > long as DMA transactions are issued before the end of the current
> > > transaction.
> >
> > Does SPI support issue new transfers without wait for previous transfer
> > complete, or SPI transfer already support async queue?
> >
> This is done with a local version of fsl-lpspi driver adding a simple
> offload support by borrowing the DMA channels allocated to the SPI
> controller. I can then issue multiple DMA transactions with the dma_buf API
> of the IIO subsystem and trigger SG chaining.
Can you include these patches to reference?
Frank
>
> Best regards,
> --
> Benoît Monin, Bootlin
> Embedded Linux and Kernel engineering
> https://bootlin.com
>
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining
2026-05-06 14:48 ` Frank Li
@ 2026-05-07 14:15 ` Benoît Monin
0 siblings, 0 replies; 11+ messages in thread
From: Benoît Monin @ 2026-05-07 14:15 UTC (permalink / raw)
To: Frank Li
Cc: Benoît Monin, Vinod Koul, Thomas Petazzoni, Frank Li, imx,
dmaengine, linux-kernel
On Wednesday, 6 May 2026 at 16:48:02 CEST, Frank Li wrote:
> On Wed, May 06, 2026 at 04:01:37PM +0200, Benoît Monin wrote:
> > On Tuesday, 5 May 2026 at 17:07:48 CEST, Frank Li wrote:
> > > > > how do you test it? and how much preformance improved?
> > > > I did my tests by doing SPI transfers with the LPSPI controllers, doing DMA
> > > > transactions with different number of buffers and different buffer sizes.
> > > > Without chaining, interruptions on the SPI bus occur between each DMA
> > > > transaction. With chaining, the activity on the SPI bus is continuous as
> > > > long as DMA transactions are issued before the end of the current
> > > > transaction.
> > >
> > > Does SPI support issue new transfers without wait for previous transfer
> > > complete, or SPI transfer already support async queue?
> > >
> > This is done with a local version of fsl-lpspi driver adding a simple
> > offload support by borrowing the DMA channels allocated to the SPI
> > controller. I can then issue multiple DMA transactions with the dma_buf API
> > of the IIO subsystem and trigger SG chaining.
>
> Can you include these patches to reference?
>
Here it is, beware that this is work in progress.
---
WIP: spi: spi-fsl-lpspi: Add Rx offload support
Add minimal support for SPI offload Currently, only the receive path
can be offloaded. SPI offload is enabled when a trigger-sources entry
is found in the device tree.
SPI offload uses the Rx DMA channel that is normally used by the
controller, thus non-offloaded SPI accesses are done in PIO mode
if offloading is enabled.
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
drivers/spi/spi-fsl-lpspi.c | 180 +++++++++++++++++++++++++++++++++++-
1 file changed, 179 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/spi-fsl-lpspi.c b/drivers/spi/spi-fsl-lpspi.c
index e201309f8aae..d873581a776a 100644
--- a/drivers/spi/spi-fsl-lpspi.c
+++ b/drivers/spi/spi-fsl-lpspi.c
@@ -23,6 +23,7 @@
#include <linux/dma/imx-dma.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
+#include <linux/spi/offload/provider.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>
#include <linux/types.h>
@@ -136,6 +137,12 @@ struct fsl_lpspi_data {
struct completion dma_rx_completion;
struct completion dma_tx_completion;
+ /* offload */
+ struct spi_offload *offload;
+ struct mutex offload_lock;
+ u32 offload_caps;
+ struct dma_chan *offload_rx_chan;
+
const struct fsl_lpspi_devtype_data *devtype_data;
};
@@ -729,6 +736,10 @@ static int fsl_lpspi_dma_init(struct device *dev,
{
int ret;
+ /* The DMA channels are reserved for offload */
+ if (fsl_lpspi->offload)
+ return -EBUSY;
+
/* Prepare for TX DMA: */
controller->dma_tx = dma_request_chan(dev, "tx");
if (IS_ERR(controller->dma_tx)) {
@@ -889,6 +900,151 @@ static int fsl_lpspi_init_rpm(struct fsl_lpspi_data *fsl_lpspi)
return 0;
}
+static int fsl_lpspi_optimize_message(struct spi_message *msg)
+{
+ struct spi_controller *controller = msg->spi->controller;
+ struct fsl_lpspi_data *fsl_lpspi = spi_controller_get_devdata(controller);
+ struct spi_transfer *xfer;
+
+ if (!msg->offload)
+ return 0;
+
+ /* We can only offload one transfer */
+ if (!list_is_singular(&msg->transfers))
+ return -EINVAL;
+
+ /* gather info for offloaded message */
+ xfer = list_first_entry(&msg->transfers, struct spi_transfer, transfer_list);
+ fsl_lpspi->config.mode = msg->spi->mode;
+ fsl_lpspi->config.bpw = xfer->bits_per_word;
+
+ return 0;
+}
+
+static struct spi_offload
+*fsl_lpspi_get_offload(struct spi_device *spi,
+ const struct spi_offload_config *config)
+{
+ struct spi_controller *controller = spi->controller;
+ struct fsl_lpspi_data *fsl_lpspi;
+ int ret;
+
+ fsl_lpspi = spi_controller_get_devdata(controller);
+
+ if (!fsl_lpspi->offload)
+ return ERR_PTR(-ENODEV);
+
+ if (config->capability_flags & ~fsl_lpspi->offload_caps)
+ return ERR_PTR(-EINVAL);
+
+ if (!mutex_trylock(&fsl_lpspi->offload_lock))
+ return ERR_PTR(-EBUSY);
+
+ ret = pm_runtime_resume_and_get(fsl_lpspi->dev);
+ if (ret < 0) {
+ dev_err(fsl_lpspi->dev, "failed to enable clock\n");
+ return ERR_PTR(ret);
+ }
+
+ fsl_lpspi_reset(fsl_lpspi);
+
+ return fsl_lpspi->offload;
+}
+
+static void fsl_lpspi_put_offload(struct spi_offload *offload)
+{
+ struct fsl_lpspi_data *fsl_lpspi = offload->priv;
+
+ pm_runtime_put_autosuspend(fsl_lpspi->dev);
+
+ mutex_unlock(&fsl_lpspi->offload_lock);
+}
+
+static int fsl_lpspi_trigger_enable(struct spi_offload *offload)
+{
+ struct fsl_lpspi_data *fsl_lpspi = offload->priv;
+ enum dma_slave_buswidth buswidth;
+ struct dma_slave_config cfg = {};
+ u32 temp;
+ int ret;
+
+ switch (fsl_lpspi_bytes_per_word(fsl_lpspi->config.bpw)) {
+ case 4:
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ case 2:
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case 1:
+ buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* configure the DMA channel */
+ cfg.direction = DMA_DEV_TO_MEM;
+ cfg.src_addr = fsl_lpspi->base_phys + IMX7ULP_RDR;
+ cfg.src_addr_width = buswidth;
+ cfg.src_maxburst = 1;
+
+ ret = dmaengine_slave_config(fsl_lpspi->offload_rx_chan, &cfg);
+ if (ret) {
+ dev_err(offload->provider_dev, "RX dma configuration failed with %d\n",
+ ret);
+ return ret;
+ }
+ fsl_lpspi_config(fsl_lpspi);
+ fsl_lpspi_set_cmd(fsl_lpspi);
+
+ /* enable Rx DMA */
+ temp = DER_RDDE;
+ writel(temp, fsl_lpspi->base + IMX7ULP_DER);
+
+ return 0;
+}
+
+static void fsl_lpspi_trigger_disable(struct spi_offload *offload)
+{
+ struct fsl_lpspi_data *fsl_lpspi = offload->priv;
+
+ fsl_lpspi_reset(fsl_lpspi);
+}
+
+static struct dma_chan
+*fsl_lpspi_rx_stream_request_dma_chan(struct spi_offload *offload)
+{
+ struct fsl_lpspi_data *fsl_lpspi = offload->priv;
+ struct dma_slave_config cfg = {};
+ int ret;
+
+ fsl_lpspi->offload_rx_chan = dma_request_chan(offload->provider_dev, "rx");
+ if (IS_ERR(fsl_lpspi->offload_rx_chan))
+ return fsl_lpspi->offload_rx_chan;
+
+ /* set a default configuration */
+ cfg.direction = DMA_DEV_TO_MEM;
+ cfg.src_addr = fsl_lpspi->base_phys + IMX7ULP_RDR;
+ cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ cfg.src_maxburst = 1;
+
+ ret = dmaengine_slave_config(fsl_lpspi->offload_rx_chan, &cfg);
+ if (ret) {
+ dma_release_channel(fsl_lpspi->offload_rx_chan);
+ dev_err(offload->provider_dev, "RX dma configuration failed with %d\n",
+ ret);
+ return ERR_PTR(ret);
+ }
+
+ return fsl_lpspi->offload_rx_chan;
+}
+
+static const struct spi_offload_ops fsl_lpspi_offload_ops = {
+ .trigger_enable = fsl_lpspi_trigger_enable,
+ .trigger_disable = fsl_lpspi_trigger_disable,
+ .rx_stream_request_dma_chan = fsl_lpspi_rx_stream_request_dma_chan,
+};
+
static int fsl_lpspi_probe(struct platform_device *pdev)
{
const struct fsl_lpspi_devtype_data *devtype_data;
@@ -958,6 +1114,24 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
return ret;
}
+ if (device_property_present(&pdev->dev, "trigger-sources")) {
+ fsl_lpspi->offload = devm_spi_offload_alloc(&pdev->dev, 0);
+ ret = PTR_ERR_OR_ZERO(fsl_lpspi->offload);
+ if (ret) {
+ dev_err(fsl_lpspi->dev, "failed to allocate offload\n");
+ return ret;
+ }
+ mutex_init(&fsl_lpspi->offload_lock);
+ fsl_lpspi->offload->priv = fsl_lpspi;
+ fsl_lpspi->offload->ops = &fsl_lpspi_offload_ops;
+ fsl_lpspi->offload_caps = SPI_OFFLOAD_CAP_TRIGGER;
+
+ if (device_property_match_string(&pdev->dev, "dma-names", "rx") >= 0) {
+ fsl_lpspi->offload->xfer_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ fsl_lpspi->offload_caps |= SPI_OFFLOAD_CAP_RX_STREAM_DMA;
+ }
+ }
+
/* enable the clock */
ret = fsl_lpspi_init_rpm(fsl_lpspi);
if (ret)
@@ -985,6 +1159,9 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
controller->transfer_one = fsl_lpspi_transfer_one;
controller->prepare_transfer_hardware = lpspi_prepare_xfer_hardware;
controller->unprepare_transfer_hardware = lpspi_unprepare_xfer_hardware;
+ controller->optimize_message = fsl_lpspi_optimize_message;
+ controller->get_offload = fsl_lpspi_get_offload;
+ controller->put_offload = fsl_lpspi_put_offload;
controller->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
controller->flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX;
controller->bus_num = pdev->id;
@@ -997,7 +1174,8 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
if (ret == -EPROBE_DEFER)
goto out_pm_get;
if (ret < 0) {
- dev_warn(&pdev->dev, "dma setup error %d, use pio\n", ret);
+ if (!fsl_lpspi->offload)
+ dev_warn(&pdev->dev, "dma setup error %d, use pio\n", ret);
enable_irq(irq);
}
^ permalink raw reply related [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-05-07 14:15 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-30 9:49 [PATCH RFC 0/2] dmaengine: fsl-edma: Scatter/gather improvements Benoît Monin
2026-04-30 9:49 ` [PATCH RFC 1/2] dmaengine: fsl-edma: Implement device_prep_peripheral_dma_vec Benoît Monin
2026-05-04 15:58 ` Frank Li
2026-05-05 13:51 ` Benoît Monin
2026-04-30 9:49 ` [PATCH RFC 2/2] dmaengine: fsl-edma: Support dynamic scatter/gather chaining Benoît Monin
2026-05-04 16:04 ` Frank Li
2026-05-05 13:51 ` Benoît Monin
2026-05-05 15:07 ` Frank Li
2026-05-06 14:01 ` Benoît Monin
2026-05-06 14:48 ` Frank Li
2026-05-07 14:15 ` Benoît Monin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox