[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260126073652.3293564-5-den@valinux.co.jp>
Date: Mon, 26 Jan 2026 16:36:51 +0900
From: Koichiro Den <den@...inux.co.jp>
To: vkoul@...nel.org,
mani@...nel.org,
Frank.Li@....com
Cc: dmaengine@...r.kernel.org,
linux-kernel@...r.kernel.org
Subject: [PATCH 4/5] dmaengine: dw-edma: Poll completion when local IRQ handling is disabled
Poll for completion on channels where local done/abort IRQ handling is
disabled (e.g. remote ACK scenarios).
This is useful when a transaction descriptor is prepared and submitted
locally, while irq_mode is configured such that the interrupt
acknowledgment is handled by the peer. Without polling, locally
submitted transactions would never complete and would get stuck.
Signed-off-by: Koichiro Den <den@...inux.co.jp>
---
drivers/dma/dw-edma/dw-edma-core.c | 104 ++++++++++++++++++++++++-----
drivers/dma/dw-edma/dw-edma-core.h | 4 ++
2 files changed, 91 insertions(+), 17 deletions(-)
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index e006f1fa2ee5..910a4d516c3a 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -6,6 +6,7 @@
* Author: Gustavo Pimentel <gustavo.pimentel@...opsys.com>
*/
+#include <linux/cleanup.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
@@ -312,24 +313,9 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
chan->request = EDMA_REQ_STOP;
}
- return err;
-}
-
-static void dw_edma_device_issue_pending(struct dma_chan *dchan)
-{
- struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
- unsigned long flags;
-
- if (!chan->configured)
- return;
+ cancel_delayed_work_sync(&chan->poll_work);
- spin_lock_irqsave(&chan->vc.lock, flags);
- if (vchan_issue_pending(&chan->vc) && chan->request == EDMA_REQ_NONE &&
- chan->status == EDMA_ST_IDLE) {
- chan->status = EDMA_ST_BUSY;
- dw_edma_start_transfer(chan);
- }
- spin_unlock_irqrestore(&chan->vc.lock, flags);
+ return err;
}
static enum dma_status
@@ -712,6 +698,70 @@ static irqreturn_t dw_edma_interrupt_common(int irq, void *data)
return ret;
}
+static void dw_edma_done_arm(struct dw_edma_chan *chan)
+{
+ if (!dw_edma_core_ch_ignore_irq(chan))
+ /* Local side handles IRQs so polling is not needed */
+ return;
+
+ queue_delayed_work(system_wq, &chan->poll_work, 1);
+}
+
+static void dw_edma_chan_poll_done(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ enum dma_status st;
+
+ if (!dw_edma_core_ch_ignore_irq(chan))
+ /* Local side handles IRQs so polling is not needed */
+ return;
+
+ guard(spinlock_irqsave)(&chan->poll_lock);
+
+ if (chan->status != EDMA_ST_BUSY)
+ return;
+
+ st = dw_edma_core_ch_status(chan);
+
+ switch (st) {
+ case DMA_COMPLETE:
+ dw_edma_done_interrupt(chan);
+ if (chan->status == EDMA_ST_BUSY)
+ dw_edma_done_arm(chan);
+ break;
+ case DMA_IN_PROGRESS:
+ dw_edma_done_arm(chan);
+ break;
+ case DMA_ERROR:
+ dw_edma_abort_interrupt(chan);
+ break;
+ default:
+ break;
+ }
+}
+
+static void dw_edma_device_issue_pending(struct dma_chan *dchan)
+{
+ struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+ unsigned long flags;
+
+ if (!chan->configured)
+ return;
+
+ dw_edma_chan_poll_done(dchan);
+
+ spin_lock_irqsave(&chan->vc.lock, flags);
+ if (vchan_issue_pending(&chan->vc) && chan->request == EDMA_REQ_NONE &&
+ chan->status == EDMA_ST_IDLE) {
+ chan->status = EDMA_ST_BUSY;
+ dw_edma_start_transfer(chan);
+ }
+
+ dw_edma_done_arm(chan);
+
+ spin_unlock_irqrestore(&chan->vc.lock, flags);
+}
+
static int dw_edma_alloc_chan_resources(struct dma_chan *dchan)
{
struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
@@ -739,6 +789,19 @@ static void dw_edma_free_chan_resources(struct dma_chan *dchan)
}
}
+static void dw_edma_poll_work(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct dw_edma_chan *chan =
+ container_of(dwork, struct dw_edma_chan, poll_work);
+ struct dma_chan *dchan = &chan->vc.chan;
+
+ if (!chan->configured)
+ return;
+
+ dw_edma_chan_poll_done(dchan);
+}
+
static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
{
struct dw_edma_chip *chip = dw->chip;
@@ -772,6 +835,9 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
chan->status = EDMA_ST_IDLE;
chan->irq_mode = DW_EDMA_CH_IRQ_DEFAULT;
+ spin_lock_init(&chan->poll_lock);
+ INIT_DELAYED_WORK(&chan->poll_work, dw_edma_poll_work);
+
if (chan->dir == EDMA_DIR_WRITE)
chan->ll_max = (chip->ll_region_wr[chan->id].sz / EDMA_LL_SZ);
else
@@ -1026,6 +1092,10 @@ int dw_edma_remove(struct dw_edma_chip *chip)
if (!dw)
return -ENODEV;
+ /* Poll work can re-arm itself. Disable and drain. */
+ list_for_each_entry(chan, &dw->dma.channels, vc.chan.device_node)
+ disable_delayed_work_sync(&chan->poll_work);
+
/* Disable eDMA */
dw_edma_core_off(dw);
diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
index 0608b9044a08..560a2d2fea86 100644
--- a/drivers/dma/dw-edma/dw-edma-core.h
+++ b/drivers/dma/dw-edma/dw-edma-core.h
@@ -11,6 +11,7 @@
#include <linux/msi.h>
#include <linux/dma/edma.h>
+#include <linux/workqueue.h>
#include "../virt-dma.h"
@@ -83,6 +84,9 @@ struct dw_edma_chan {
enum dw_edma_ch_irq_mode irq_mode;
+ struct delayed_work poll_work;
+ spinlock_t poll_lock;
+
enum dw_edma_request request;
enum dw_edma_status status;
u8 configured;
--
2.51.0
Powered by blists - more mailing lists