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");
 	}
 }
 
