]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
dmaengine: sh: rz-dmac: Add cyclic DMA support
authorClaudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Tue, 26 May 2026 08:47:04 +0000 (11:47 +0300)
committerVinod Koul <vkoul@kernel.org>
Thu, 4 Jun 2026 15:28:33 +0000 (20:58 +0530)
Add cyclic DMA support to the RZ DMAC driver. A per-channel status bit is
introduced to mark cyclic channels and is set during the DMA prepare
callback. The IRQ handler checks this status bit and calls
vchan_cyclic_callback() accordingly.

Tested-by: John Madieu <john.madieu.xa@bp.renesas.com>
Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Link: https://patch.msgid.link/20260526084710.3491480-13-claudiu.beznea@kernel.org
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/dma/sh/rz-dmac.c

index c9c00650ddd5d2268e717211f75b9b706fb05e58..8fd8a4bd9cc904a2cc24e5ce0168d92c5a80b58a 100644 (file)
@@ -35,6 +35,7 @@
 enum  rz_dmac_prep_type {
        RZ_DMAC_DESC_MEMCPY,
        RZ_DMAC_DESC_SLAVE_SG,
+       RZ_DMAC_DESC_CYCLIC,
 };
 
 struct rz_lmdesc {
@@ -67,9 +68,11 @@ struct rz_dmac_desc {
 /**
  * enum rz_dmac_chan_status: RZ DMAC channel status
  * @RZ_DMAC_CHAN_STATUS_PAUSED: Channel is paused though DMA engine callbacks
+ * @RZ_DMAC_CHAN_STATUS_CYCLIC: Channel is cyclic
  */
 enum rz_dmac_chan_status {
        RZ_DMAC_CHAN_STATUS_PAUSED,
+       RZ_DMAC_CHAN_STATUS_CYCLIC,
 };
 
 struct rz_dmac_chan {
@@ -191,6 +194,7 @@ struct rz_dmac {
 
 /* LINK MODE DESCRIPTOR */
 #define HEADER_LV                      BIT(0)
+#define HEADER_WBD                     BIT(2)
 
 #define RZ_DMAC_MAX_CHAN_DESCRIPTORS   16
 #define RZ_DMAC_MAX_CHANNELS           16
@@ -431,6 +435,57 @@ static void rz_dmac_prepare_descs_for_slave_sg(struct rz_dmac_chan *channel)
        channel->chctrl = 0;
 }
 
+static void rz_dmac_prepare_descs_for_cyclic(struct rz_dmac_chan *channel)
+{
+       struct dma_chan *chan = &channel->vc.chan;
+       struct rz_dmac *dmac = to_rz_dmac(chan->device);
+       struct rz_dmac_desc *d = channel->desc;
+       size_t period_len = d->sgcount;
+       struct rz_lmdesc *lmdesc;
+       size_t buf_len = d->len;
+       size_t periods = buf_len / period_len;
+
+       lockdep_assert_held(&channel->vc.lock);
+
+       channel->chcfg |= CHCFG_SEL(channel->index) | CHCFG_DMS;
+
+       if (d->direction == DMA_DEV_TO_MEM) {
+               channel->chcfg |= CHCFG_SAD;
+               channel->chcfg &= ~CHCFG_REQD;
+       } else {
+               channel->chcfg |= CHCFG_DAD | CHCFG_REQD;
+       }
+
+       lmdesc = channel->lmdesc.tail;
+       d->start_lmdesc = lmdesc;
+
+       for (size_t i = 0; i < periods; i++) {
+               if (d->direction == DMA_DEV_TO_MEM) {
+                       lmdesc->sa = d->src;
+                       lmdesc->da = d->dest + (i * period_len);
+               } else {
+                       lmdesc->sa = d->src + (i * period_len);
+                       lmdesc->da = d->dest;
+               }
+
+               lmdesc->tb = period_len;
+               lmdesc->chitvl = 0;
+               lmdesc->chext = 0;
+               lmdesc->chcfg = channel->chcfg;
+               lmdesc->header = HEADER_LV | HEADER_WBD;
+
+               if (i == periods - 1)
+                       lmdesc->nxla = rz_dmac_lmdesc_addr(channel, d->start_lmdesc);
+
+               if (++lmdesc >= (channel->lmdesc.base + DMAC_NR_LMDESC))
+                       lmdesc = channel->lmdesc.base;
+       }
+
+       channel->lmdesc.tail = lmdesc;
+
+       rz_dmac_set_dma_req_no(dmac, channel->index, channel->mid_rid);
+}
+
 static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
 {
        struct virt_dma_desc *vd;
@@ -452,6 +507,10 @@ static void rz_dmac_xfer_desc(struct rz_dmac_chan *chan)
        case RZ_DMAC_DESC_SLAVE_SG:
                rz_dmac_prepare_descs_for_slave_sg(chan);
                break;
+
+       case RZ_DMAC_DESC_CYCLIC:
+               rz_dmac_prepare_descs_for_cyclic(chan);
+               break;
        }
 
        rz_dmac_enable_hw(chan);
@@ -586,6 +645,55 @@ rz_dmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
        return vchan_tx_prep(&channel->vc, &desc->vd, flags);
 }
 
+static struct dma_async_tx_descriptor *
+rz_dmac_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr,
+                       size_t buf_len, size_t period_len,
+                       enum dma_transfer_direction direction,
+                       unsigned long flags)
+{
+       struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
+       struct rz_dmac_desc *desc;
+       size_t periods;
+
+       if (!is_slave_direction(direction))
+               return NULL;
+
+       if (!period_len || !buf_len)
+               return NULL;
+
+       periods = buf_len / period_len;
+       if (!periods || periods > DMAC_NR_LMDESC)
+               return NULL;
+
+       scoped_guard(spinlock_irqsave, &channel->vc.lock) {
+               if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC))
+                       return NULL;
+
+               desc = list_first_entry_or_null(&channel->ld_free, struct rz_dmac_desc, node);
+               if (!desc)
+                       return NULL;
+
+               list_del(&desc->node);
+
+               channel->status |= BIT(RZ_DMAC_CHAN_STATUS_CYCLIC);
+       }
+
+       desc->type = RZ_DMAC_DESC_CYCLIC;
+       desc->sgcount = period_len;
+       desc->len = buf_len;
+       desc->direction = direction;
+
+       if (direction == DMA_DEV_TO_MEM) {
+               desc->src = channel->src_per_address;
+               desc->dest = buf_addr;
+       } else {
+               desc->src = buf_addr;
+               desc->dest = channel->dst_per_address;
+       }
+
+       return vchan_tx_prep(&channel->vc, &desc->vd, flags);
+}
+
 static int rz_dmac_terminate_all(struct dma_chan *chan)
 {
        struct rz_dmac_chan *channel = to_rz_dmac_chan(chan);
@@ -733,9 +841,18 @@ static u32 rz_dmac_calculate_residue_bytes_in_vd(struct rz_dmac_chan *channel,
        }
 
        /* Calculate residue from next lmdesc to end of virtual desc */
-       while (lmdesc->chcfg & CHCFG_DEM) {
-               residue += lmdesc->tb;
-               lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
+       if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) {
+               u32 start_lmdesc_addr = rz_dmac_lmdesc_addr(channel, desc->start_lmdesc);
+
+               while (lmdesc->nxla != start_lmdesc_addr) {
+                       residue += lmdesc->tb;
+                       lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
+               }
+       } else {
+               while (lmdesc->chcfg & CHCFG_DEM) {
+                       residue += lmdesc->tb;
+                       lmdesc = rz_dmac_get_next_lmdesc(channel->lmdesc.base, lmdesc);
+               }
        }
 
        dev_dbg(dmac->dev, "%s: VD residue is %u\n", __func__, residue);
@@ -928,10 +1045,14 @@ static irqreturn_t rz_dmac_irq_handler_thread(int irq, void *dev_id)
        if (!desc)
                return IRQ_HANDLED;
 
-       vchan_cookie_complete(&desc->vd);
-       channel->desc = NULL;
+       if (channel->status & BIT(RZ_DMAC_CHAN_STATUS_CYCLIC)) {
+               vchan_cyclic_callback(&desc->vd);
+       } else {
+               vchan_cookie_complete(&desc->vd);
+               channel->desc = NULL;
 
-       rz_dmac_xfer_desc(channel);
+               rz_dmac_xfer_desc(channel);
+       }
 
        return IRQ_HANDLED;
 }
@@ -1183,6 +1304,8 @@ static int rz_dmac_probe(struct platform_device *pdev)
        engine = &dmac->engine;
        dma_cap_set(DMA_SLAVE, engine->cap_mask);
        dma_cap_set(DMA_MEMCPY, engine->cap_mask);
+       dma_cap_set(DMA_CYCLIC, engine->cap_mask);
+       engine->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
        engine->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
        rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_0_7_COMMON_BASE + DCTRL);
        rz_dmac_writel(dmac, DCTRL_DEFAULT, CHANNEL_8_15_COMMON_BASE + DCTRL);
@@ -1194,6 +1317,7 @@ static int rz_dmac_probe(struct platform_device *pdev)
        engine->device_tx_status = rz_dmac_tx_status;
        engine->device_prep_slave_sg = rz_dmac_prep_slave_sg;
        engine->device_prep_dma_memcpy = rz_dmac_prep_dma_memcpy;
+       engine->device_prep_dma_cyclic = rz_dmac_prep_dma_cyclic;
        engine->device_config = rz_dmac_config;
        engine->device_terminate_all = rz_dmac_terminate_all;
        engine->device_issue_pending = rz_dmac_issue_pending;