Add the memory-to-memory data path for the eDMA5 dmadev.

The eDMA5 exposes a single transfer control descriptor and a single
completion flag per hardware channel, so jobs are serialised in software.
Each enqueued job is played out synchronously as one or more single-block
transfers: the per-channel TCD is programmed, the transfer is
software-started and the driver busy-waits for the DONE flag with a
wall-clock bounded timeout. Scatter-gather copies walk the source and
destination segment lists as two cursors, emitting one single-block
sub-transfer per iteration that fits both current segments.

As the eDMA5 is a non-coherent bus master, source and destination buffers
are cleaned from the CPU cache before a transfer and the destination is
invalidated after completion so the application observes the DMA result.

This adds copy, copy_sg, submit, completed, completed_status and
burst_capacity, wired through the device fast-path object.

Signed-off-by: Gagandeep Singh <[email protected]>
Signed-off-by: Prashant Gupta <[email protected]>
---
 drivers/dma/imx_edma5/imx_edma5_dmadev.c | 571 +++++++++++++++++++++++
 1 file changed, 571 insertions(+)

diff --git a/drivers/dma/imx_edma5/imx_edma5_dmadev.c 
b/drivers/dma/imx_edma5/imx_edma5_dmadev.c
index fe5539b612..e7f3bec528 100644
--- a/drivers/dma/imx_edma5/imx_edma5_dmadev.c
+++ b/drivers/dma/imx_edma5/imx_edma5_dmadev.c
@@ -337,6 +337,570 @@ imx_edma5_close(struct rte_dma_dev *dev)
        return 0;
 }
 
+/*
+ * Encode the largest natural transfer size (SSIZE/DSIZE) usable for a given
+ * source, destination and length. The address must be aligned to the transfer
+ * size and the byte count must be a multiple of it.
+ */
+static uint16_t
+imx_edma5_calc_attr(uint64_t src, uint64_t dst, uint32_t len)
+{
+       uint32_t sz = IMX_EDMA5_TCD_SIZE_1B;
+
+       if (((src | dst | len) & 0x1F) == 0)
+               sz = IMX_EDMA5_TCD_SIZE_32B;
+       else if (((src | dst | len) & 0xF) == 0)
+               sz = IMX_EDMA5_TCD_SIZE_16B;
+       else if (((src | dst | len) & 0x7) == 0)
+               sz = IMX_EDMA5_TCD_SIZE_8B;
+       else if (((src | dst | len) & 0x3) == 0)
+               sz = IMX_EDMA5_TCD_SIZE_4B;
+       else if (((src | dst | len) & 0x1) == 0)
+               sz = IMX_EDMA5_TCD_SIZE_2B;
+
+       return IMX_EDMA5_TCD_ATTR_SSIZE(sz) | IMX_EDMA5_TCD_ATTR_DSIZE(sz);
+}
+
+/*
+ * Program the channel TCD for a single-block copy: one minor loop of "len"
+ * bytes with a major count of 1. Completion is polled via CH_CSR.DONE.
+ */
+static inline void
+imx_edma5_program_copy(struct imx_edma5_vchan *vc, uint64_t src, uint64_t dst,
+                      uint32_t len)
+{
+       uint8_t *tcd = vc->tcd_regs;
+       uint16_t attr = imx_edma5_calc_attr(src, dst, len);
+
+       imx_edma5_write64(tcd, IMX_EDMA5_TCD_SADDR, src);
+       imx_edma5_write64(tcd, IMX_EDMA5_TCD_DADDR, dst);
+       imx_edma5_write16(tcd, IMX_EDMA5_TCD_ATTR, attr);
+       imx_edma5_write16(tcd, IMX_EDMA5_TCD_SOFF,
+                         (uint16_t)(1u << IMX_EDMA5_TCD_ATTR_GET_SSIZE(attr)));
+       imx_edma5_write16(tcd, IMX_EDMA5_TCD_DOFF,
+                         (uint16_t)(1u << IMX_EDMA5_TCD_ATTR_GET_DSIZE(attr)));
+       imx_edma5_write32(tcd, IMX_EDMA5_TCD_NBYTES, len);
+       imx_edma5_write64(tcd, IMX_EDMA5_TCD_SLAST, 0);
+       imx_edma5_write64(tcd, IMX_EDMA5_TCD_DLAST_SGA, 0);
+       imx_edma5_write16(tcd, IMX_EDMA5_TCD_CITER, 1);
+       imx_edma5_write16(tcd, IMX_EDMA5_TCD_BITER, 1);
+       /* Clear CSR so no scatter-gather link is followed. */
+       imx_edma5_write16(tcd, IMX_EDMA5_TCD_CSR, 0);
+}
+
+/* Fill an in-memory hardware TCD64 descriptor for one copy segment. */
+static inline void
+imx_edma5_fill_tcd(struct imx_edma5_hw_tcd64 *t, uint64_t src, uint64_t dst,
+                  uint32_t len)
+{
+       uint16_t attr = imx_edma5_calc_attr(src, dst, len);
+       uint16_t soff = (uint16_t)(1u << IMX_EDMA5_TCD_ATTR_GET_SSIZE(attr));
+       uint16_t doff = (uint16_t)(1u << IMX_EDMA5_TCD_ATTR_GET_DSIZE(attr));
+
+       t->saddr = rte_cpu_to_le_64(src);
+       t->soff = rte_cpu_to_le_16(soff);
+       t->attr = rte_cpu_to_le_16(attr);
+       t->nbytes = rte_cpu_to_le_32(len);
+       t->slast = 0;
+       t->daddr = rte_cpu_to_le_64(dst);
+       t->dlast_sga = 0;
+       t->doff = rte_cpu_to_le_16(doff);
+       t->citer = rte_cpu_to_le_16(1);
+       t->csr = 0;
+       t->biter = rte_cpu_to_le_16(1);
+}
+
+static inline void
+imx_edma5_hw_start(struct imx_edma5_vchan *vc)
+{
+       uint16_t csr = imx_edma5_read16(vc->tcd_regs, IMX_EDMA5_TCD_CSR);
+
+       csr |= IMX_EDMA5_TCD_CSR_START;
+       imx_edma5_write16(vc->tcd_regs, IMX_EDMA5_TCD_CSR, csr);
+}
+
+/*
+ * Invalidate the CPU cache lines covering a completed job's destination(s) so
+ * the application reads the DMA result rather than stale cache. The lines were
+ * cleaned at enqueue time, so this clean+invalidate behaves as a pure
+ * invalidate.
+ */
+static inline void
+imx_edma5_job_invalidate_dst(struct imx_edma5_job *job)
+{
+       if (job->nb_sg > 0) {
+               uint16_t s;
+
+               for (s = 0; s < job->nb_sg; s++) {
+                       rte_iova_t da = rte_le_to_cpu_64(job->sg_tcd[s].daddr);
+                       uint32_t len = rte_le_to_cpu_32(job->sg_tcd[s].nbytes);
+                       void *va = rte_mem_iova2virt(da);
+
+                       if (va != NULL)
+                               imx_edma5_cache_inval(va, len);
+               }
+       } else if (job->dst_va != NULL) {
+               imx_edma5_cache_inval(job->dst_va, job->len);
+       }
+}
+
+/*
+ * Upper bound on how long to poll for a single-block transfer to complete.
+ * A wall-clock deadline is used rather than a raw spin count so the bound is
+ * independent of CPU speed. Each single-block transfer programmed here is
+ * capped at IMX_EDMA5_MAX_NBYTES (1 GiB - 1); at an AXI bus rate of 2 GB/s
+ * that is 512 ms in the worst case, so 1000 ms is a safe ceiling while still
+ * bounding a wedged channel rather than hanging the CPU.
+ */
+#define IMX_EDMA5_WAIT_TIMEOUT_MS      1000
+
+/*
+ * Wait for the single register TCD transfer to finish and clear its latched
+ * status. Returns true on success, false on a logged channel error (CH_ES.ERR)
+ * or timeout. The per-transfer completion flag CH_CSR.DONE and the CH_ES error
+ * bit are both write-1-to-clear.
+ */
+static inline bool
+imx_edma5_wait_done(struct imx_edma5_vchan *vc)
+{
+       uint64_t deadline = rte_get_timer_cycles() +
+               (rte_get_timer_hz() * IMX_EDMA5_WAIT_TIMEOUT_MS) / 1000;
+
+       do {
+               uint32_t ch_es = imx_edma5_read32(vc->ch_regs, IMX_EDMA5_CH_ES);
+               uint32_t ch_csr;
+
+               if (ch_es & IMX_EDMA5_CH_ES_ERR) {
+                       imx_edma5_write32(vc->ch_regs, IMX_EDMA5_CH_ES,
+                                         IMX_EDMA5_CH_ES_ERR);
+                       /* Reset the errored channel before the next job reuses 
it. */
+                       imx_edma5_reset_hw_chan(vc);
+                       return false;
+               }
+
+               ch_csr = imx_edma5_read32(vc->ch_regs, IMX_EDMA5_CH_CSR);
+               if (ch_csr & IMX_EDMA5_CH_CSR_DONE) {
+                       imx_edma5_write32(vc->ch_regs, IMX_EDMA5_CH_CSR,
+                                         IMX_EDMA5_CH_CSR_DONE);
+                       return true;
+               }
+       } while (rte_get_timer_cycles() < deadline);
+
+       IMX_EDMA5_LOG(ERR,
+                     "channel %u timed out waiting for DONE (CH_CSR=0x%08x "
+                     "CH_ES=0x%08x)",
+                     vc->hw_chan,
+                     imx_edma5_read32(vc->ch_regs, IMX_EDMA5_CH_CSR),
+                     imx_edma5_read32(vc->ch_regs, IMX_EDMA5_CH_ES));
+
+       /* Reset the possibly-still-active channel before the next job reuses 
it. */
+       imx_edma5_reset_hw_chan(vc);
+       return false;
+}
+
+/*
+ * Execute one job to completion on the channel's single register TCD.
+ *
+ * The eDMA5 exposes a single TCD and a single completion flag per channel, so
+ * jobs are serialised in software: a job is run synchronously here (program 
the
+ * TCD, software-start, busy-wait for DONE) and its completion recorded in
+ * job->done for the completion API to reap. Scatter-gather segments are played
+ * out one at a time as single-block transfers, since the eDMA5 does not
+ * auto-advance a hardware TCD chain for software-started mem-to-mem transfers.
+ * On success the destination cache lines are invalidated (non-coherent 
master).
+ */
+static inline void
+imx_edma5_run_job(struct imx_edma5_vchan *vc, struct imx_edma5_job *job)
+{
+       bool ok = true;
+
+       if (job->nb_sg > 0) {
+               uint16_t s;
+
+               for (s = 0; s < job->nb_sg; s++) {
+                       uint64_t src = rte_le_to_cpu_64(job->sg_tcd[s].saddr);
+                       uint64_t dst = rte_le_to_cpu_64(job->sg_tcd[s].daddr);
+                       uint32_t len = rte_le_to_cpu_32(job->sg_tcd[s].nbytes);
+
+                       imx_edma5_program_copy(vc, src, dst, len);
+                       imx_edma5_hw_start(vc);
+                       if (!imx_edma5_wait_done(vc)) {
+                               ok = false;
+                               break;
+                       }
+               }
+       } else {
+               imx_edma5_program_copy(vc, job->src_iova, job->dst_iova,
+                                      job->len);
+               imx_edma5_hw_start(vc);
+               ok = imx_edma5_wait_done(vc);
+       }
+
+       if (ok)
+               imx_edma5_job_invalidate_dst(job);
+
+       job->error = ok ? 0 : 1;
+       job->done = 1;
+       vc->submitted_count++;
+}
+
+static int
+imx_edma5_copy(void *dev_private, uint16_t vchan, rte_iova_t src,
+              rte_iova_t dst, uint32_t length, uint64_t flags)
+{
+       struct imx_edma5_dev *ed = dev_private;
+       struct imx_edma5_vchan *vc = &ed->vchans[vchan];
+       struct imx_edma5_job *job;
+       uint16_t slot;
+
+       /* NBYTES = 0 is undefined on the eDMA5 and can wedge the channel. */
+       if (length == 0)
+               return -EINVAL;
+       /*
+        * TCD_NBYTES bits 31:30 are SMLOE/DMLOE when minor-loop offsets are
+        * enabled; writing a count larger than the 30-bit max would corrupt
+        * those control bits. Reject oversized requests.
+        */
+       if (length > IMX_EDMA5_MAX_NBYTES)
+               return -EINVAL;
+
+       /* Ring full? one slot is kept free to distinguish full from empty. */
+       if (vc->nb_enqueued >= (uint16_t)(vc->nb_desc - 1))
+               return -ENOSPC;
+
+       slot = vc->head;
+       job = &vc->jobs[slot];
+       job->ridx = vc->ridx;
+       job->submitted = 0;
+       job->done = 0;
+       job->error = 0;
+       job->nb_sg = 0;
+       job->len = length;
+       job->src_iova = src;
+       job->dst_iova = dst;
+       /*
+        * rte_mem_iova2virt() returns NULL when the IOVA is not in the
+        * memzone table (e.g. externally-allocated IOVA-contiguous memory not
+        * registered with DPDK). Cache maintenance is silently skipped for such
+        * addresses; callers are responsible for ensuring coherency in that 
case
+        * or for registering the memory so a VA mapping is available.
+        */
+       job->dst_va = rte_mem_iova2virt(dst);
+
+       /*
+        * Non-cache-coherent master: clean the source so the device reads the
+        * CPU's latest writes, and clean the destination so a prior dirty line
+        * cannot be written back over the DMA result (the destination is
+        * invalidated after completion).
+        */
+       {
+               void *src_va = rte_mem_iova2virt(src);
+
+               if (src_va != NULL)
+                       imx_edma5_cache_clean(src_va, length);
+               if (job->dst_va != NULL)
+                       imx_edma5_cache_clean(job->dst_va, length);
+       }
+
+       /*
+        * RTE_DMA_OP_FLAG_SUBMIT is equivalent to calling rte_dma_submit()
+        * after this enqueue: advance the head to include the new job and then
+        * run all pending (unsubmitted) jobs from tail to the new head in FIFO
+        * order, matching the behaviour of imx_edma5_submit().
+        */
+       if (flags & RTE_DMA_OP_FLAG_SUBMIT) {
+               uint16_t idx;
+
+               vc->head = (vc->head + 1) & vc->desc_mask;
+               vc->nb_enqueued++;
+
+               idx = vc->tail;
+               while (idx != vc->head) {
+                       struct imx_edma5_job *j = &vc->jobs[idx];
+
+                       if (!j->submitted) {
+                               imx_edma5_run_job(vc, j);
+                               j->submitted = 1;
+                       }
+                       idx = (idx + 1) & vc->desc_mask;
+               }
+
+               return vc->ridx++;
+       }
+
+       vc->head = (vc->head + 1) & vc->desc_mask;
+       vc->nb_enqueued++;
+
+       return vc->ridx++;
+}
+
+static int
+imx_edma5_copy_sg(void *dev_private, uint16_t vchan,
+                 const struct rte_dma_sge *src, const struct rte_dma_sge *dst,
+                 uint16_t nb_src, uint16_t nb_dst, uint64_t flags)
+{
+       struct imx_edma5_dev *ed = dev_private;
+       struct imx_edma5_vchan *vc = &ed->vchans[vchan];
+       struct imx_edma5_hw_tcd64 *tcd;
+       struct imx_edma5_job *job;
+       uint16_t slot;
+       uint16_t s;
+       uint16_t si = 0, di = 0;        /* current source/destination seg index 
*/
+       uint32_t s_off = 0, d_off = 0;  /* byte offset within current segment */
+       uint16_t nsg = 0;               /* sub-transfers produced so far */
+       uint64_t src_total = 0, dst_total = 0;
+
+       /*
+        * The scatter-gather contract is a byte stream: the source and
+        * destination lists may be segmented independently but their
+        * concatenations are equal. The lists are walked as two cursors,
+        * emitting one single-block sub-transfer per run that fits in both the
+        * current source and destination segments. This yields at most
+        * nb_src + nb_dst - 1 sub-transfers, which fit in 
IMX_EDMA5_SG_TCD_PER_JOB.
+        */
+       if (nb_src == 0 || nb_dst == 0 ||
+           nb_src > IMX_EDMA5_MAX_SGES || nb_dst > IMX_EDMA5_MAX_SGES) {
+               IMX_EDMA5_LOG(ERR, "Unsupported SG shape src=%u dst=%u",
+                             nb_src, nb_dst);
+               return -EINVAL;
+       }
+
+       /* Ring full? one slot is kept free to distinguish full from empty. */
+       if (vc->nb_enqueued >= (uint16_t)(vc->nb_desc - 1))
+               return -ENOSPC;
+
+       slot = vc->head;
+       job = &vc->jobs[slot];
+       job->ridx = vc->ridx;
+       job->submitted = 0;
+       job->done = 0;
+       job->error = 0;
+
+       /* This job's dedicated slice of the in-memory TCD pool. */
+       job->sg_tcd = &vc->sg_tcd_pool[(size_t)slot * IMX_EDMA5_SG_TCD_PER_JOB];
+       tcd = job->sg_tcd;
+
+       /*
+        * Clean every source and destination segment up front (non-coherent
+        * master): the device must read current source data, and dirty
+        * destination lines must be flushed before the transfer.
+        */
+       for (s = 0; s < nb_src; s++) {
+               void *va = rte_mem_iova2virt(src[s].addr);
+
+               src_total += src[s].length;
+               if (va != NULL)
+                       imx_edma5_cache_clean(va, src[s].length);
+       }
+       for (s = 0; s < nb_dst; s++) {
+               void *va = rte_mem_iova2virt(dst[s].addr);
+
+               dst_total += dst[s].length;
+               if (va != NULL)
+                       imx_edma5_cache_clean(va, dst[s].length);
+       }
+
+       /* copy_sg requires equal total bytes on both lists; reject misuse. */
+       if (src_total != dst_total) {
+               IMX_EDMA5_LOG(ERR,
+                             "SG byte count mismatch src=%" PRIu64
+                             " dst=%" PRIu64, src_total, dst_total);
+               return -EINVAL;
+       }
+
+       while (si < nb_src && di < nb_dst) {
+               uint32_t s_rem = src[si].length - s_off;
+               uint32_t d_rem = dst[di].length - d_off;
+               uint32_t len = RTE_MIN(s_rem, d_rem);
+
+               /* Skip zero-length segments without emitting a descriptor. */
+               if (len == 0) {
+                       if (s_rem == 0) {
+                               si++;
+                               s_off = 0;
+                       }
+                       if (d_rem == 0) {
+                               di++;
+                               d_off = 0;
+                       }
+                       continue;
+               }
+
+               if (nsg >= IMX_EDMA5_SG_TCD_PER_JOB) {
+                       IMX_EDMA5_LOG(ERR,
+                                     "SG produced too many sub-transfers "
+                                     "(src=%u dst=%u)", nb_src, nb_dst);
+                       /* Defensive: clear nb_sg so the abandoned slot is not 
reused. */
+                       job->nb_sg = 0;
+                       return -EINVAL;
+               }
+
+               imx_edma5_fill_tcd(&tcd[nsg], src[si].addr + s_off,
+                                  dst[di].addr + d_off, len);
+               nsg++;
+
+               s_off += len;
+               d_off += len;
+               if (s_off == src[si].length) {
+                       si++;
+                       s_off = 0;
+               }
+               if (d_off == dst[di].length) {
+                       di++;
+                       d_off = 0;
+               }
+       }
+
+       /* No sub-transfer (all segments zero-length): NBYTES = 0 wedges eDMA5. 
*/
+       if (nsg == 0) {
+               IMX_EDMA5_LOG(ERR, "SG produced zero sub-transfers");
+               return -EINVAL;
+       }
+
+       job->nb_sg = nsg;
+
+       /*
+        * RTE_DMA_OP_FLAG_SUBMIT is equivalent to calling rte_dma_submit()
+        * after this enqueue: advance the head to include the new job and then
+        * run all pending (unsubmitted) jobs from tail to the new head in FIFO
+        * order, matching the behaviour of imx_edma5_submit().
+        */
+       if (flags & RTE_DMA_OP_FLAG_SUBMIT) {
+               uint16_t idx;
+
+               vc->head = (vc->head + 1) & vc->desc_mask;
+               vc->nb_enqueued++;
+
+               idx = vc->tail;
+               while (idx != vc->head) {
+                       struct imx_edma5_job *j = &vc->jobs[idx];
+
+                       if (!j->submitted) {
+                               imx_edma5_run_job(vc, j);
+                               j->submitted = 1;
+                       }
+                       idx = (idx + 1) & vc->desc_mask;
+               }
+
+               return vc->ridx++;
+       }
+
+       vc->head = (vc->head + 1) & vc->desc_mask;
+       vc->nb_enqueued++;
+
+       return vc->ridx++;
+}
+
+static int
+imx_edma5_submit(void *dev_private, uint16_t vchan)
+{
+       struct imx_edma5_dev *ed = dev_private;
+       struct imx_edma5_vchan *vc = &ed->vchans[vchan];
+       uint16_t idx = vc->tail;
+
+       /* Run every enqueued-but-not-yet-submitted job to completion (FIFO). */
+       while (idx != vc->head) {
+               struct imx_edma5_job *job = &vc->jobs[idx];
+
+               if (!job->submitted) {
+                       imx_edma5_run_job(vc, job);
+                       job->submitted = 1;
+               }
+               idx = (idx + 1) & vc->desc_mask;
+       }
+
+       return 0;
+}
+
+/*
+ * Reap completed jobs from the software ring in FIFO order. Jobs run
+ * synchronously, so a submitted job's result is already in job->done/error.
+ * An unsubmitted job stops the walk.
+ */
+static uint16_t
+imx_edma5_completed(void *dev_private, uint16_t vchan, const uint16_t nb_cpls,
+                   uint16_t *last_idx, bool *has_error)
+{
+       struct imx_edma5_dev *ed = dev_private;
+       struct imx_edma5_vchan *vc = &ed->vchans[vchan];
+       uint16_t count = 0;
+
+       *has_error = false;
+
+       while (count < nb_cpls && vc->tail != vc->head) {
+               struct imx_edma5_job *job = &vc->jobs[vc->tail];
+
+               if (!job->submitted || !job->done)
+                       break;
+
+               /*
+                * Stop before an errored job: it is left in the ring for
+                * rte_dma_completed_status(), and last_idx stays at the last
+                * successful transfer.
+                */
+               if (job->error) {
+                       *has_error = true;
+                       break;
+               }
+
+               vc->last_idx = job->ridx;
+               vc->completed_count++;
+
+               vc->tail = (vc->tail + 1) & vc->desc_mask;
+               vc->nb_enqueued--;
+               count++;
+       }
+
+       *last_idx = vc->last_idx;
+
+       return count;
+}
+
+static uint16_t
+imx_edma5_completed_status(void *dev_private, uint16_t vchan,
+                          const uint16_t nb_cpls, uint16_t *last_idx,
+                          enum rte_dma_status_code *status)
+{
+       struct imx_edma5_dev *ed = dev_private;
+       struct imx_edma5_vchan *vc = &ed->vchans[vchan];
+       uint16_t count = 0;
+
+       while (count < nb_cpls && vc->tail != vc->head) {
+               struct imx_edma5_job *job = &vc->jobs[vc->tail];
+
+               if (!job->submitted || !job->done)
+                       break;
+
+               if (job->error) {
+                       status[count] = RTE_DMA_STATUS_BUS_ERROR;
+                       vc->errors_count++;
+               } else {
+                       status[count] = RTE_DMA_STATUS_SUCCESSFUL;
+               }
+
+               vc->last_idx = job->ridx;
+               vc->completed_count++;
+
+               vc->tail = (vc->tail + 1) & vc->desc_mask;
+               vc->nb_enqueued--;
+               count++;
+       }
+
+       *last_idx = vc->last_idx;
+
+       return count;
+}
+
+static uint16_t
+imx_edma5_burst_capacity(const void *dev_private, uint16_t vchan)
+{
+       const struct imx_edma5_dev *ed = dev_private;
+       const struct imx_edma5_vchan *vc = &ed->vchans[vchan];
+
+       /* One slot is reserved to distinguish full from empty. */
+       return vc->nb_desc - 1 - vc->nb_enqueued;
+}
+
 static const struct rte_dma_dev_ops imx_edma5_ops = {
        .dev_info_get   = imx_edma5_info_get,
        .dev_configure  = imx_edma5_configure,
@@ -382,6 +946,13 @@ imx_edma5_probe(struct rte_platform_device *pdev)
 
        dev->device = &pdev->device;
        dev->dev_ops = &imx_edma5_ops;
+       dev->fp_obj->dev_private = dev->data->dev_private;
+       dev->fp_obj->copy = imx_edma5_copy;
+       dev->fp_obj->copy_sg = imx_edma5_copy_sg;
+       dev->fp_obj->submit = imx_edma5_submit;
+       dev->fp_obj->completed = imx_edma5_completed;
+       dev->fp_obj->completed_status = imx_edma5_completed_status;
+       dev->fp_obj->burst_capacity = imx_edma5_burst_capacity;
 
        ed = dev->data->dev_private;
        ed->reg_base = res->mem.addr;
-- 
2.25.1

Reply via email to