Skip to content

Commit 94e0f9f

Browse files
committed
bcm2835-dma: Fixes for dma_abort
There is a problem with the current abort scheme when dma is blocked on a DREQ which prevents halting. This is triggered by SPI driver which aborts dma in this state and so leads to a halt timeout. Discussion with Broadcom suggests the sequence: CS.ACTIVE=0 while (CS.OUTSTANDING_TRANSACTIONS == 0) wait() DEBUG.RESET=1 should be safe on a dma40 channel. Unfortunately the non-dma40 channels don't have OUTSTANDING_TRANSACTIONS, only WAITING_FOR_OUTSTANDING_WRITES which doesn't consider reads. Signed-off-by: Dom Cobley <[email protected]>
1 parent ed10309 commit 94e0f9f

File tree

1 file changed

+23
-32
lines changed

1 file changed

+23
-32
lines changed

drivers/dma/bcm2835-dma.c

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ struct bcm2835_desc {
245245
#define BCM2711_DMA40_ERR BIT(10)
246246
#define BCM2711_DMA40_QOS(x) (((x) & 0x1f) << 16)
247247
#define BCM2711_DMA40_PANIC_QOS(x) (((x) & 0x1f) << 20)
248+
#define BCM2711_DMA40_TRANSACTIONS BIT(25)
248249
#define BCM2711_DMA40_WAIT_FOR_WRITES BIT(28)
249250
#define BCM2711_DMA40_DISDEBUG BIT(29)
250251
#define BCM2711_DMA40_ABORT BIT(30)
@@ -665,28 +666,30 @@ static void bcm2835_dma_abort(struct bcm2835_chan *c)
665666
void __iomem *chan_base = c->chan_base;
666667
long int timeout = 10000;
667668

668-
/*
669-
* A zero control block address means the channel is idle.
670-
* (The ACTIVE flag in the CS register is not a reliable indicator.)
671-
*/
672-
if (!readl(chan_base + BCM2835_DMA_ADDR))
673-
return;
674-
675669
if (c->is_40bit_channel) {
676-
/* Halt the current DMA */
677-
writel(readl(chan_base + BCM2711_DMA40_CS) | BCM2711_DMA40_HALT,
670+
/*
671+
* A zero control block address means the channel is idle.
672+
* (The ACTIVE flag in the CS register is not a reliable indicator.)
673+
*/
674+
if (!readl(chan_base + BCM2711_DMA40_CB))
675+
return;
676+
677+
/* Pause the current DMA */
678+
writel(readl(chan_base + BCM2711_DMA40_CS) & ~BCM2711_DMA40_ACTIVE,
678679
chan_base + BCM2711_DMA40_CS);
679680

680-
while ((readl(chan_base + BCM2711_DMA40_CS) & BCM2711_DMA40_HALT) && --timeout)
681+
/* wait for outstanding transactions to complete */
682+
while ((readl(chan_base + BCM2711_DMA40_CS) & BCM2711_DMA40_TRANSACTIONS) && --timeout)
681683
cpu_relax();
682684

683-
/* Peripheral might be stuck and fail to halt */
685+
/* Peripheral might be stuck and fail to complete */
684686
if (!timeout)
685687
dev_err(c->vc.chan.device->dev,
686-
"failed to halt dma\n");
688+
"failed to complete pause on dma %d (CS:%08x)\n", c->ch,
689+
readl(chan_base + BCM2711_DMA40_CS));
687690

688-
writel(BCM2711_DMA40_PROT, chan_base + BCM2711_DMA40_CS);
689-
writel(0, chan_base + BCM2711_DMA40_CB);
691+
/* Reset the DMA */
692+
writel(BCM2711_DMA40_DEBUG_RESET, chan_base + BCM2711_DMA40_DEBUG);
690693
} else {
691694
/*
692695
* A zero control block address means the channel is idle.
@@ -704,27 +707,15 @@ static void bcm2835_dma_abort(struct bcm2835_chan *c)
704707
&& --timeout)
705708
cpu_relax();
706709

707-
/* Peripheral might be stuck and fail to signal AXI write responses */
710+
/* Peripheral might be stuck and fail to complete */
708711
if (!timeout)
709712
dev_err(c->vc.chan.device->dev,
710-
"failed to pause dma\n");
713+
"failed to complete pause on dma %d (CS:%08x)\n", c->ch,
714+
readl(chan_base + BCM2835_DMA_CS));
711715

712-
/* We need to clear the next DMA block pending */
713-
writel(0, chan_base + BCM2835_DMA_NEXTCB);
714-
715-
/* Abort the DMA, which needs to be enabled to complete */
716-
writel(readl(chan_base + BCM2835_DMA_CS) | BCM2835_DMA_ABORT | BCM2835_DMA_ACTIVE,
717-
chan_base + BCM2835_DMA_CS);
718-
719-
/* wait for DMA to have been aborted */
720-
timeout = 10000;
721-
while ((readl(chan_base + BCM2835_DMA_CS) & BCM2835_DMA_ABORT) && --timeout)
722-
cpu_relax();
723-
724-
/* Peripheral might be stuck and fail to signal AXI write responses */
725-
if (!timeout)
726-
dev_err(c->vc.chan.device->dev,
727-
"failed to abort dma\n");
716+
/* Reset the DMA */
717+
writel(readl(chan_base + BCM2835_DMA_CS) | BCM2835_DMA_RESET,
718+
chan_base + BCM2835_DMA_CS);
728719
}
729720
}
730721

0 commit comments

Comments
 (0)