]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
dmaengine: dma-axi-dmac: Gracefully terminate SW cyclic transfers
authorNuno Sá <nuno.sa@analog.com>
Tue, 3 Mar 2026 10:25:03 +0000 (10:25 +0000)
committerVinod Koul <vkoul@kernel.org>
Mon, 9 Mar 2026 07:28:21 +0000 (08:28 +0100)
As of now, to terminate a cyclic transfer, one pretty much needs to use
brute force and terminate all transfers with .device_terminate_all().
With this change, when a cyclic transfer terminates (and generates an
EOT interrupt), look at any new pending transfer with the DMA_PREP_LOAD_EOT
flag set. If there is one, the current cyclic transfer is terminated and
the next one is enqueued. If the flag is not set, that transfer is ignored.

Signed-off-by: Nuno Sá <nuno.sa@analog.com>
Link: https://patch.msgid.link/20260303-axi-dac-cyclic-support-v2-4-0db27b4be95a@analog.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/dma/dma-axi-dmac.c

index a47e08d3dbbc3dd7498249941cc3a51193e47213..e9814d725322edb18b64178fcc94d938a9467fc2 100644 (file)
@@ -233,6 +233,11 @@ static struct axi_dmac_desc *axi_dmac_get_next_desc(struct axi_dmac *dmac,
        struct virt_dma_desc *vdesc;
        struct axi_dmac_desc *desc;
 
+       /*
+        * It means a SW cyclic transfer is in place so we should just return
+        * the same descriptor. SW cyclic transfer termination is handled
+        * in axi_dmac_transfer_done().
+        */
        if (chan->next_desc)
                return chan->next_desc;
 
@@ -411,6 +416,32 @@ static void axi_dmac_compute_residue(struct axi_dmac_chan *chan,
        }
 }
 
+static bool axi_dmac_handle_cyclic_eot(struct axi_dmac_chan *chan,
+                                      struct axi_dmac_desc *active)
+{
+       struct device *dev = chan_to_axi_dmac(chan)->dma_dev.dev;
+       struct virt_dma_desc *vdesc;
+
+       /* wrap around */
+       active->num_completed = 0;
+
+       vdesc = vchan_next_desc(&chan->vchan);
+       if (!vdesc)
+               return false;
+       if (!(vdesc->tx.flags & DMA_PREP_LOAD_EOT)) {
+               dev_warn(dev, "Discarding non EOT transfer after cyclic\n");
+               list_del(&vdesc->node);
+               return false;
+       }
+
+       /* then let's end the cyclic transfer */
+       chan->next_desc = NULL;
+       list_del(&active->vdesc.node);
+       vchan_cookie_complete(&active->vdesc);
+
+       return true;
+}
+
 static bool axi_dmac_transfer_done(struct axi_dmac_chan *chan,
        unsigned int completed_transfers)
 {
@@ -458,7 +489,8 @@ static bool axi_dmac_transfer_done(struct axi_dmac_chan *chan,
                        if (active->num_completed == active->num_sgs ||
                            sg->partial_len) {
                                if (active->cyclic) {
-                                       active->num_completed = 0; /* wrap around */
+                                       /* keep start_next as is, if already true... */
+                                       start_next |= axi_dmac_handle_cyclic_eot(chan, active);
                                } else {
                                        list_del(&active->vdesc.node);
                                        vchan_cookie_complete(&active->vdesc);