[tip: irq/drivers] MIPS: ath79: Flush the DDR write buffers of an interrupt in parallel
From: tip-bot2 for Rosen Penev
Date: Wed Nov 11 2026 - 15:04:44 EST
The following commit has been merged into the irq/drivers branch of tip:
Commit-ID: 08b814ca0a6945b8effc1a0d64999d562f24c923
Gitweb: https://git.kernel.org/tip/08b814ca0a6945b8effc1a0d64999d562f24c923
Author: Rosen Penev <rosenp@xxxxxxxxx>
AuthorDate: Sun, 08 Nov 2026 13:57:38 -08:00
Committer: Thomas Gleixner <tglx@xxxxxxxxxx>
CommitterDate: Wed, 11 Nov 2026 00:56:19 +01:00
MIPS: ath79: Flush the DDR write buffers of an interrupt in parallel
When several devices share a CPU interrupt, plat_irq_dispatch() flushes
their DDR write buffers one after the other, waiting for each to drain
before starting the next.
Make ath79_ddr_wb_flush() take a mask of channels, and start the flush
of every channel before waiting for any of them, so that the write
buffers drain in parallel. For a single channel the sequence is
unchanged. Its only user is the built-in irqchip, so drop the export.
Keep flushing twice, as the AR7100 datasheet asks, and quote it instead
of the comment that it must be run twice.
Suggested-by: Thomas Gleixner <tglx@xxxxxxxxxx>
Signed-off-by: Rosen Penev <rosenp@xxxxxxxxx>
Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxx>
Link: https://patch.msgid.link/20261108215738.113582-4-rosenp@xxxxxxxxx
---
arch/mips/ath79/common.c | 52 +++++++++++++++++------
arch/mips/include/asm/mach-ath79/ath79.h | 2 +-
drivers/irqchip/irq-ath79-cpu.c | 13 ++----
3 files changed, 46 insertions(+), 21 deletions(-)
diff --git a/arch/mips/ath79/common.c b/arch/mips/ath79/common.c
index 137abbc..672e840 100644
--- a/arch/mips/ath79/common.c
+++ b/arch/mips/ath79/common.c
@@ -9,6 +9,7 @@
* Parts of this file are based on Atheros' 2.6.15/2.6.31 BSP
*/
+#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/types.h>
@@ -53,21 +54,48 @@ void ath79_ddr_ctrl_init(void)
}
EXPORT_SYMBOL_GPL(ath79_ddr_ctrl_init);
-void ath79_ddr_wb_flush(u32 reg)
+static void __ath79_ddr_wb_flush(unsigned long chans)
{
- void __iomem *flush_reg = ath79_ddr_wb_flush_base + (reg * 4);
-
- /* Flush the DDR write buffer. */
- __raw_writel(0x1, flush_reg);
- while (__raw_readl(flush_reg) & 0x1)
- ;
+ u32 __iomem *base = ath79_ddr_wb_flush_base;
+ unsigned int chan;
+
+ /*
+ * Start every flush before waiting for any of them, so that the
+ * write buffers drain in parallel instead of one after the other.
+ */
+ for_each_set_bit(chan, &chans, 32)
+ __raw_writel(0x1, base + chan);
+
+ for_each_set_bit(chan, &chans, 32) {
+ while (__raw_readl(base + chan) & 0x1)
+ ;
+ }
+}
- /* It must be run twice. */
- __raw_writel(0x1, flush_reg);
- while (__raw_readl(flush_reg) & 0x1)
- ;
+/**
+ * ath79_ddr_wb_flush - flush DDR write buffers
+ * @chans: mask of the write buffer channels to flush
+ *
+ * Every DMA master of the SoC (the Ethernet MACs, USB, PCIe, WMAC) writes
+ * to DDR through its own write buffer in the DDR controller. The buffer
+ * may still hold data when the device raises its interrupt, so it has to
+ * be flushed before the interrupt handler looks at the DMA'd memory.
+ * Setting bit 0 of the channel's flush register starts the flush, and the
+ * controller clears it once the buffer is empty.
+ */
+void ath79_ddr_wb_flush(unsigned long chans)
+{
+ /*
+ * The AR7100 datasheet says to flush twice, after reading the
+ * interrupt status of the device, to ensure that its descriptor
+ * updates have reached memory before software reads them. It does
+ * not say why a single flush is not enough. The datasheets of the
+ * later SoCs drop the note, but nothing says the need went away, so
+ * flush twice on all of them.
+ */
+ __ath79_ddr_wb_flush(chans);
+ __ath79_ddr_wb_flush(chans);
}
-EXPORT_SYMBOL_GPL(ath79_ddr_wb_flush);
void ath79_ddr_set_pci_windows(void)
{
diff --git a/arch/mips/include/asm/mach-ath79/ath79.h b/arch/mips/include/asm/mach-ath79/ath79.h
index 70cda74..15897d0 100644
--- a/arch/mips/include/asm/mach-ath79/ath79.h
+++ b/arch/mips/include/asm/mach-ath79/ath79.h
@@ -145,7 +145,7 @@ static inline int soc_is_qca956x(void)
return soc_is_qca9561() || soc_is_qca9563();
}
-void ath79_ddr_wb_flush(unsigned int reg);
+void ath79_ddr_wb_flush(unsigned long chans);
void ath79_ddr_set_pci_windows(void);
extern void __iomem *ath79_pll_base;
diff --git a/drivers/irqchip/irq-ath79-cpu.c b/drivers/irqchip/irq-ath79-cpu.c
index ec39c37..64272b1 100644
--- a/drivers/irqchip/irq-ath79-cpu.c
+++ b/drivers/irqchip/irq-ath79-cpu.c
@@ -10,7 +10,7 @@
* Parts of this file are based on Atheros' 2.6.15/2.6.31 BSP
*/
-#include <linux/bitops.h>
+#include <linux/bits.h>
#include <linux/interrupt.h>
#include <linux/irqchip.h>
#include <linux/of.h>
@@ -44,8 +44,8 @@ static u32 irq_wb_chans[8];
asmlinkage void plat_irq_dispatch(void)
{
- unsigned long pending, chans;
- int irq, chan;
+ unsigned long pending;
+ int irq;
pending = read_c0_status() & read_c0_cause() & ST0_IM;
@@ -57,11 +57,8 @@ asmlinkage void plat_irq_dispatch(void)
pending >>= CAUSEB_IP;
while (pending) {
irq = fls(pending) - 1;
- if (irq < ARRAY_SIZE(irq_wb_chans)) {
- chans = irq_wb_chans[irq];
- for_each_set_bit(chan, &chans, 32)
- ath79_ddr_wb_flush(chan);
- }
+ if (irq < ARRAY_SIZE(irq_wb_chans) && irq_wb_chans[irq])
+ ath79_ddr_wb_flush(irq_wb_chans[irq]);
do_IRQ(MIPS_CPU_IRQ_BASE + irq);
pending &= ~BIT(irq);
}