[PATCH] LoongArch: Workaround LS2K/LS7A GPU DMA hang bug

From: Huacai Chen

Date: Tue Mar 17 2026 - 03:14:47 EST


1. Hardware limitation: GPU, DC and VPU are typically PCI device 06.0,
06.1 and 06.2. They share some hardware resources, so when configure the
PCI 06.0 device BAR1, DMA memory access cannot be performed through this
BAR, otherwise it will cause hardware abnormalities.

2. In typical scenarios of reboot or S3/S4, DC access to memory through
BAR is not prohibited, resulting in GPU DMA hangs.

3. Workaround method: When configuring the 06.0 device BAR1, turn off
the memory access of DC, GPU and VPU (via DC's CRTC registers).

Signed-off-by: Qianhai Wu <wuqianhai@xxxxxxxxxxx>
Signed-off-by: Huacai Chen <chenhuacai@xxxxxxxxxxx>
---
arch/loongarch/pci/pci.c | 79 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)

diff --git a/arch/loongarch/pci/pci.c b/arch/loongarch/pci/pci.c
index d923295ab8c6..88ef6b16fb31 100644
--- a/arch/loongarch/pci/pci.c
+++ b/arch/loongarch/pci/pci.c
@@ -5,6 +5,7 @@
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/acpi.h>
+#include <linux/delay.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/vgaarb.h>
@@ -15,6 +16,9 @@
#define PCI_DEVICE_ID_LOONGSON_DC1 0x7a06
#define PCI_DEVICE_ID_LOONGSON_DC2 0x7a36
#define PCI_DEVICE_ID_LOONGSON_DC3 0x7a46
+#define PCI_DEVICE_ID_LOONGSON_GPU1 0x7a15
+#define PCI_DEVICE_ID_LOONGSON_GPU2 0x7a25
+#define PCI_DEVICE_ID_LOONGSON_GPU3 0x7a35

int raw_pci_read(unsigned int domain, unsigned int bus, unsigned int devfn,
int reg, int len, u32 *val)
@@ -99,3 +103,78 @@ static void pci_fixup_vgadev(struct pci_dev *pdev)
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC1, pci_fixup_vgadev);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC2, pci_fixup_vgadev);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC3, pci_fixup_vgadev);
+
+#define CRTC_NUM_MAX 2
+#define CRTC_OUTPUT_ENABLE 0x100
+
+static void loongson_gpu_fixup_dma_hang(struct pci_dev *pdev, bool on)
+{
+ u32 i, val, count, crtc_offset, device;
+ void __iomem *crtc_reg, *base, *regbase;
+ static u32 crtc_status[CRTC_NUM_MAX] = { 0 };
+
+ base = pdev->bus->ops->map_bus(pdev->bus, pdev->devfn + 1, 0);
+ device = readw(base + PCI_DEVICE_ID);
+
+ regbase = ioremap(readq(base + PCI_BASE_ADDRESS_0) & ~0xffull, SZ_64K);
+ if (!regbase) {
+ pci_err(pdev, "Failed to ioremap()\n");
+ return;
+ }
+
+ switch (device) {
+ case PCI_DEVICE_ID_LOONGSON_DC2:
+ crtc_reg = regbase + 0x1240;
+ crtc_offset = 0x10;
+ break;
+ case PCI_DEVICE_ID_LOONGSON_DC3:
+ crtc_reg = regbase;
+ crtc_offset = 0x400;
+ break;
+ }
+
+ for (i = 0; i < CRTC_NUM_MAX; i++, crtc_reg += crtc_offset) {
+ val = readl(crtc_reg);
+
+ if (!on)
+ crtc_status[i] = val;
+
+ /* No need to fixup if the status is off at startup. */
+ if (!(crtc_status[i] & CRTC_OUTPUT_ENABLE))
+ continue;
+
+ if (on)
+ val |= CRTC_OUTPUT_ENABLE;
+ else
+ val &= ~CRTC_OUTPUT_ENABLE;
+
+ mb();
+ writel(val, crtc_reg);
+
+ for (count = 0; count < 32; count++) {
+ val = readl(crtc_reg) & CRTC_OUTPUT_ENABLE;
+ if ((on && val) || (!on && !val))
+ break;
+ udelay(1000);
+ }
+
+ pci_info(pdev, "DMA hang fixup at reg[0x%lx]: 0x%x\n",
+ (unsigned long)crtc_reg & 0xffff, readl(crtc_reg));
+ }
+
+ iounmap(regbase);
+}
+
+static void pci_fixup_dma_hang_early(struct pci_dev *pdev)
+{
+ loongson_gpu_fixup_dma_hang(pdev, false);
+}
+DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_GPU2, pci_fixup_dma_hang_early);
+DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_GPU3, pci_fixup_dma_hang_early);
+
+static void pci_fixup_dma_hang_final(struct pci_dev *pdev)
+{
+ loongson_gpu_fixup_dma_hang(pdev, true);
+}
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_GPU2, pci_fixup_dma_hang_final);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_GPU3, pci_fixup_dma_hang_final);
--
2.52.0