From 6609cb1bb479d833d11703b711cd12ade9bf8750 Mon Sep 17 00:00:00 2001 From: Dom Cobley 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 --- 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"); } }