]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
LoongArch: Workaround LS2K/LS7A GPU DMA hang bug
authorHuacai Chen <chenhuacai@loongson.cn>
Thu, 26 Mar 2026 06:29:09 +0000 (14:29 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Thu, 26 Mar 2026 06:29:09 +0000 (14:29 +0800)
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).

Cc: stable@vger.kernel.org
Signed-off-by: Qianhai Wu <wuqianhai@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
arch/loongarch/pci/pci.c

index d923295ab8c665c93569b0d0844c9db3b629e2b0..d233ea2218fe0ab480e00d875ba4394ab90e26b4 100644 (file)
@@ -5,9 +5,11 @@
 #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>
+#include <linux/io-64-nonatomic-lo-hi.h>
 #include <asm/cacheflush.h>
 #include <asm/loongson.h>
 
@@ -15,6 +17,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 +104,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 < 40; 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);