]> www.infradead.org Git - users/hch/misc.git/commitdiff
EDAC: Add an EDAC driver for the Loongson memory controller
authorZhao Qunqin <zhaoqunqin@loongson.cn>
Thu, 19 Dec 2024 12:48:46 +0000 (20:48 +0800)
committerBorislav Petkov (AMD) <bp@alien8.de>
Sat, 4 Jan 2025 11:02:04 +0000 (12:02 +0100)
Add ECC support for Loongson SoC DDR controller. This driver reports single
bit errors (CE) only.

Only ACPI firmware is supported.

  [ bp: Document what last_ce_count is for. ]

Signed-off-by: Zhao Qunqin <zhaoqunqin@loongson.cn>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Reviewed-by: Huacai Chen <chenhuacai@loongson.cn>
Link: https://lore.kernel.org/r/20241219124846.1876-1-zhaoqunqin@loongson.cn
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
MAINTAINERS
arch/loongarch/Kconfig
drivers/edac/Kconfig
drivers/edac/Makefile
drivers/edac/loongson_edac.c [new file with mode: 0644]

index 17daa9ee9384509c1ef3f2a3825a4594eab88741..4adedd0517a33e6141f531653f8dc8e8f0caf438 100644 (file)
@@ -13544,6 +13544,12 @@ S:     Maintained
 F:     Documentation/devicetree/bindings/thermal/loongson,ls2k-thermal.yaml
 F:     drivers/thermal/loongson2_thermal.c
 
+LOONGSON EDAC DRIVER
+M:     Zhao Qunqin <zhaoqunqin@loongson.cn>
+L:     linux-edac@vger.kernel.org
+S:     Maintained
+F:     drivers/edac/loongson_edac.c
+
 LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
 M:     Sathya Prakash <sathya.prakash@broadcom.com>
 M:     Sreekanth Reddy <sreekanth.reddy@broadcom.com>
index dae3a9104ca6584133d9b6a3059ee666c216d31b..28acd0b04115e1b8d34f162f033044fd27e3e63b 100644 (file)
@@ -81,6 +81,7 @@ config LOONGARCH
        select BUILDTIME_TABLE_SORT
        select COMMON_CLK
        select CPU_PM
+       select EDAC_SUPPORT
        select EFI
        select GENERIC_CLOCKEVENTS
        select GENERIC_CMOS_UPDATE
index 06f7b43a6f7884a8ffe8b27cf60bd2d0c507510c..0d001760747d8e550f1f0f8e3590eed1d8266bfd 100644 (file)
@@ -546,5 +546,13 @@ config EDAC_VERSAL
          Support injecting both correctable and uncorrectable errors
          for debugging purposes.
 
+config EDAC_LOONGSON
+       tristate "Loongson Memory Controller"
+       depends on LOONGARCH && ACPI
+       help
+         Support for error detection and correction on the Loongson
+         family memory controller. This driver reports single bit
+         errors (CE) only. Loongson-3A5000/3C5000/3D5000/3A6000/3C6000
+         are compatible.
 
 endif # EDAC
index f9cf19d8d13d47356d7a4f7ae9659470349d8fd2..bb980ec6309d70e08be51147c360997408506f9d 100644 (file)
@@ -86,3 +86,4 @@ obj-$(CONFIG_EDAC_DMC520)             += dmc520_edac.o
 obj-$(CONFIG_EDAC_NPCM)                        += npcm_edac.o
 obj-$(CONFIG_EDAC_ZYNQMP)              += zynqmp_edac.o
 obj-$(CONFIG_EDAC_VERSAL)              += versal_edac.o
+obj-$(CONFIG_EDAC_LOONGSON)            += loongson_edac.o
diff --git a/drivers/edac/loongson_edac.c b/drivers/edac/loongson_edac.c
new file mode 100644 (file)
index 0000000..3874580
--- /dev/null
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Loongson Technology Corporation Limited.
+ */
+
+#include <linux/acpi.h>
+#include <linux/edac.h>
+#include <linux/init.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include "edac_module.h"
+
+#define ECC_CS_COUNT_REG       0x18
+
+struct loongson_edac_pvt {
+       void __iomem *ecc_base;
+
+       /*
+        * The ECC register in this controller records the number of errors
+        * encountered since reset and cannot be zeroed so in order to be able
+        * to report the error count at each check, this records the previous
+        * register state.
+        */
+       int last_ce_count;
+};
+
+static int read_ecc(struct mem_ctl_info *mci)
+{
+       struct loongson_edac_pvt *pvt = mci->pvt_info;
+       u64 ecc;
+       int cs;
+
+       ecc = readq(pvt->ecc_base + ECC_CS_COUNT_REG);
+       /* cs0 -- cs3 */
+       cs = ecc & 0xff;
+       cs += (ecc >> 8) & 0xff;
+       cs += (ecc >> 16) & 0xff;
+       cs += (ecc >> 24) & 0xff;
+
+       return cs;
+}
+
+static void edac_check(struct mem_ctl_info *mci)
+{
+       struct loongson_edac_pvt *pvt = mci->pvt_info;
+       int new, add;
+
+       new = read_ecc(mci);
+       add = new - pvt->last_ce_count;
+       pvt->last_ce_count = new;
+       if (add <= 0)
+               return;
+
+       edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, add,
+                            0, 0, 0, 0, 0, -1, "error", "");
+}
+
+static void dimm_config_init(struct mem_ctl_info *mci)
+{
+       struct dimm_info *dimm;
+       u32 size, npages;
+
+       /* size not used */
+       size = -1;
+       npages = MiB_TO_PAGES(size);
+
+       dimm = edac_get_dimm(mci, 0, 0, 0);
+       dimm->nr_pages = npages;
+       snprintf(dimm->label, sizeof(dimm->label),
+                "MC#%uChannel#%u_DIMM#%u", mci->mc_idx, 0, 0);
+       dimm->grain = 8;
+}
+
+static void pvt_init(struct mem_ctl_info *mci, void __iomem *vbase)
+{
+       struct loongson_edac_pvt *pvt = mci->pvt_info;
+
+       pvt->ecc_base = vbase;
+       pvt->last_ce_count = read_ecc(mci);
+}
+
+static int edac_probe(struct platform_device *pdev)
+{
+       struct edac_mc_layer layers[2];
+       struct mem_ctl_info *mci;
+       void __iomem *vbase;
+       int ret;
+
+       vbase = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(vbase))
+               return PTR_ERR(vbase);
+
+       layers[0].type = EDAC_MC_LAYER_CHANNEL;
+       layers[0].size = 1;
+       layers[0].is_virt_csrow = false;
+       layers[1].type = EDAC_MC_LAYER_SLOT;
+       layers[1].size = 1;
+       layers[1].is_virt_csrow = true;
+       mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers,
+                           sizeof(struct loongson_edac_pvt));
+       if (mci == NULL)
+               return -ENOMEM;
+
+       mci->mc_idx = edac_device_alloc_index();
+       mci->mtype_cap = MEM_FLAG_RDDR4;
+       mci->edac_ctl_cap = EDAC_FLAG_NONE;
+       mci->edac_cap = EDAC_FLAG_NONE;
+       mci->mod_name = "loongson_edac.c";
+       mci->ctl_name = "loongson_edac_ctl";
+       mci->dev_name = "loongson_edac_dev";
+       mci->ctl_page_to_phys = NULL;
+       mci->pdev = &pdev->dev;
+       mci->error_desc.grain = 8;
+       mci->edac_check = edac_check;
+
+       pvt_init(mci, vbase);
+       dimm_config_init(mci);
+
+       ret = edac_mc_add_mc(mci);
+       if (ret) {
+               edac_dbg(0, "MC: failed edac_mc_add_mc()\n");
+               edac_mc_free(mci);
+               return ret;
+       }
+       edac_op_state = EDAC_OPSTATE_POLL;
+
+       return 0;
+}
+
+static void edac_remove(struct platform_device *pdev)
+{
+       struct mem_ctl_info *mci = edac_mc_del_mc(&pdev->dev);
+
+       if (mci)
+               edac_mc_free(mci);
+}
+
+static const struct acpi_device_id loongson_edac_acpi_match[] = {
+       {"LOON0010", 0},
+       {}
+};
+MODULE_DEVICE_TABLE(acpi, loongson_edac_acpi_match);
+
+static struct platform_driver loongson_edac_driver = {
+       .probe          = edac_probe,
+       .remove         = edac_remove,
+       .driver         = {
+               .name   = "loongson-mc-edac",
+               .acpi_match_table = loongson_edac_acpi_match,
+       },
+};
+module_platform_driver(loongson_edac_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Zhao Qunqin <zhaoqunqin@loongson.cn>");
+MODULE_DESCRIPTION("EDAC driver for loongson memory controller");