openwrt/target/linux/bcm27xx/patches-6.1/950-0727-bcm2835-dma-Fix-dma_abort-for-non-40bit-channels.patch

69 lines
2.4 KiB
Diff
Raw Normal View History

From 6609cb1bb479d833d11703b711cd12ade9bf8750 Mon Sep 17 00:00:00 2001
From: Dom Cobley <popcornmix@gmail.com>
Date: Wed, 26 Apr 2023 13:40:34 +0100
Subject: [PATCH] bcm2835-dma: Fix dma_abort for non-40bit channels
The sequence we were doing was not safe.
Clearing CS meant BCM2835_DMA_WAIT_FOR_WRITES was cleared
and so polling BCM2835_DMA_WAITING_FOR_WRITES has no benefit
Broadcom have provided a recommended sequence to abort
a dma lite channel, so switch to that.
Signed-off-by: Dom Cobley <popcornmix@gmail.com>
---
drivers/dma/bcm2835-dma.c | 31 +++++++++++++++++++++++++++----
1 file changed, 27 insertions(+), 4 deletions(-)
--- a/drivers/dma/bcm2835-dma.c
+++ b/drivers/dma/bcm2835-dma.c
@@ -675,10 +675,18 @@ static void bcm2835_dma_abort(struct bcm
writel(0, chan_base + BCM2711_DMA40_CS);
writel(0, chan_base + BCM2711_DMA40_CB);
} else {
+ /*
+ * A zero control block address means the channel is idle.
+ * (The ACTIVE flag in the CS register is not a reliable indicator.)
+ */
+ if (!readl(chan_base + BCM2835_DMA_ADDR))
+ return;
+
/* Write 0 to the active bit - Pause the DMA */
- writel(0, chan_base + BCM2835_DMA_CS);
+ writel(readl(chan_base + BCM2835_DMA_CS) & ~BCM2835_DMA_ACTIVE,
+ chan_base + BCM2835_DMA_CS);
- /* Wait for any current AXI transfer to complete */
+ /* wait for DMA to be paused */
while ((readl(chan_base + BCM2835_DMA_CS) & BCM2835_DMA_WAITING_FOR_WRITES)
&& --timeout)
cpu_relax();
@@ -686,9 +694,24 @@ static void bcm2835_dma_abort(struct bcm
/* Peripheral might be stuck and fail to signal AXI write responses */
if (!timeout)
dev_err(c->vc.chan.device->dev,
- "failed to complete outstanding writes\n");
+ "failed to pause dma\n");
+
+ /* We need to clear the next DMA block pending */
+ writel(0, chan_base + BCM2835_DMA_NEXTCB);
+
+ /* Abort the DMA, which needs to be enabled to complete */
+ writel(readl(chan_base + BCM2835_DMA_CS) | BCM2835_DMA_ABORT | BCM2835_DMA_ACTIVE,
+ chan_base + BCM2835_DMA_CS);
+
+ /* wait for DMA to have been aborted */
+ timeout = 10000;
+ while ((readl(chan_base + BCM2835_DMA_CS) & BCM2835_DMA_ABORT) && --timeout)
+ cpu_relax();
- writel(BCM2835_DMA_RESET, chan_base + BCM2835_DMA_CS);
+ /* Peripheral might be stuck and fail to signal AXI write responses */
+ if (!timeout)
+ dev_err(c->vc.chan.device->dev,
+ "failed to abort dma\n");
}
}