Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

EDAC: Add an EDAC driver for the Loongson memory controller

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>

authored by

Zhao Qunqin and committed by
Borislav Petkov (AMD)
558aff7a 584e0974

+173
+6
MAINTAINERS
··· 13544 13544 F: Documentation/devicetree/bindings/thermal/loongson,ls2k-thermal.yaml 13545 13545 F: drivers/thermal/loongson2_thermal.c 13546 13546 13547 + LOONGSON EDAC DRIVER 13548 + M: Zhao Qunqin <zhaoqunqin@loongson.cn> 13549 + L: linux-edac@vger.kernel.org 13550 + S: Maintained 13551 + F: drivers/edac/loongson_edac.c 13552 + 13547 13553 LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI) 13548 13554 M: Sathya Prakash <sathya.prakash@broadcom.com> 13549 13555 M: Sreekanth Reddy <sreekanth.reddy@broadcom.com>
+1
arch/loongarch/Kconfig
··· 81 81 select BUILDTIME_TABLE_SORT 82 82 select COMMON_CLK 83 83 select CPU_PM 84 + select EDAC_SUPPORT 84 85 select EFI 85 86 select GENERIC_CLOCKEVENTS 86 87 select GENERIC_CMOS_UPDATE
+8
drivers/edac/Kconfig
··· 546 546 Support injecting both correctable and uncorrectable errors 547 547 for debugging purposes. 548 548 549 + config EDAC_LOONGSON 550 + tristate "Loongson Memory Controller" 551 + depends on LOONGARCH && ACPI 552 + help 553 + Support for error detection and correction on the Loongson 554 + family memory controller. This driver reports single bit 555 + errors (CE) only. Loongson-3A5000/3C5000/3D5000/3A6000/3C6000 556 + are compatible. 549 557 550 558 endif # EDAC
+1
drivers/edac/Makefile
··· 86 86 obj-$(CONFIG_EDAC_NPCM) += npcm_edac.o 87 87 obj-$(CONFIG_EDAC_ZYNQMP) += zynqmp_edac.o 88 88 obj-$(CONFIG_EDAC_VERSAL) += versal_edac.o 89 + obj-$(CONFIG_EDAC_LOONGSON) += loongson_edac.o
+157
drivers/edac/loongson_edac.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (C) 2024 Loongson Technology Corporation Limited. 4 + */ 5 + 6 + #include <linux/acpi.h> 7 + #include <linux/edac.h> 8 + #include <linux/init.h> 9 + #include <linux/io-64-nonatomic-lo-hi.h> 10 + #include <linux/module.h> 11 + #include <linux/platform_device.h> 12 + #include "edac_module.h" 13 + 14 + #define ECC_CS_COUNT_REG 0x18 15 + 16 + struct loongson_edac_pvt { 17 + void __iomem *ecc_base; 18 + 19 + /* 20 + * The ECC register in this controller records the number of errors 21 + * encountered since reset and cannot be zeroed so in order to be able 22 + * to report the error count at each check, this records the previous 23 + * register state. 24 + */ 25 + int last_ce_count; 26 + }; 27 + 28 + static int read_ecc(struct mem_ctl_info *mci) 29 + { 30 + struct loongson_edac_pvt *pvt = mci->pvt_info; 31 + u64 ecc; 32 + int cs; 33 + 34 + ecc = readq(pvt->ecc_base + ECC_CS_COUNT_REG); 35 + /* cs0 -- cs3 */ 36 + cs = ecc & 0xff; 37 + cs += (ecc >> 8) & 0xff; 38 + cs += (ecc >> 16) & 0xff; 39 + cs += (ecc >> 24) & 0xff; 40 + 41 + return cs; 42 + } 43 + 44 + static void edac_check(struct mem_ctl_info *mci) 45 + { 46 + struct loongson_edac_pvt *pvt = mci->pvt_info; 47 + int new, add; 48 + 49 + new = read_ecc(mci); 50 + add = new - pvt->last_ce_count; 51 + pvt->last_ce_count = new; 52 + if (add <= 0) 53 + return; 54 + 55 + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, add, 56 + 0, 0, 0, 0, 0, -1, "error", ""); 57 + } 58 + 59 + static void dimm_config_init(struct mem_ctl_info *mci) 60 + { 61 + struct dimm_info *dimm; 62 + u32 size, npages; 63 + 64 + /* size not used */ 65 + size = -1; 66 + npages = MiB_TO_PAGES(size); 67 + 68 + dimm = edac_get_dimm(mci, 0, 0, 0); 69 + dimm->nr_pages = npages; 70 + snprintf(dimm->label, sizeof(dimm->label), 71 + "MC#%uChannel#%u_DIMM#%u", mci->mc_idx, 0, 0); 72 + dimm->grain = 8; 73 + } 74 + 75 + static void pvt_init(struct mem_ctl_info *mci, void __iomem *vbase) 76 + { 77 + struct loongson_edac_pvt *pvt = mci->pvt_info; 78 + 79 + pvt->ecc_base = vbase; 80 + pvt->last_ce_count = read_ecc(mci); 81 + } 82 + 83 + static int edac_probe(struct platform_device *pdev) 84 + { 85 + struct edac_mc_layer layers[2]; 86 + struct mem_ctl_info *mci; 87 + void __iomem *vbase; 88 + int ret; 89 + 90 + vbase = devm_platform_ioremap_resource(pdev, 0); 91 + if (IS_ERR(vbase)) 92 + return PTR_ERR(vbase); 93 + 94 + layers[0].type = EDAC_MC_LAYER_CHANNEL; 95 + layers[0].size = 1; 96 + layers[0].is_virt_csrow = false; 97 + layers[1].type = EDAC_MC_LAYER_SLOT; 98 + layers[1].size = 1; 99 + layers[1].is_virt_csrow = true; 100 + mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 101 + sizeof(struct loongson_edac_pvt)); 102 + if (mci == NULL) 103 + return -ENOMEM; 104 + 105 + mci->mc_idx = edac_device_alloc_index(); 106 + mci->mtype_cap = MEM_FLAG_RDDR4; 107 + mci->edac_ctl_cap = EDAC_FLAG_NONE; 108 + mci->edac_cap = EDAC_FLAG_NONE; 109 + mci->mod_name = "loongson_edac.c"; 110 + mci->ctl_name = "loongson_edac_ctl"; 111 + mci->dev_name = "loongson_edac_dev"; 112 + mci->ctl_page_to_phys = NULL; 113 + mci->pdev = &pdev->dev; 114 + mci->error_desc.grain = 8; 115 + mci->edac_check = edac_check; 116 + 117 + pvt_init(mci, vbase); 118 + dimm_config_init(mci); 119 + 120 + ret = edac_mc_add_mc(mci); 121 + if (ret) { 122 + edac_dbg(0, "MC: failed edac_mc_add_mc()\n"); 123 + edac_mc_free(mci); 124 + return ret; 125 + } 126 + edac_op_state = EDAC_OPSTATE_POLL; 127 + 128 + return 0; 129 + } 130 + 131 + static void edac_remove(struct platform_device *pdev) 132 + { 133 + struct mem_ctl_info *mci = edac_mc_del_mc(&pdev->dev); 134 + 135 + if (mci) 136 + edac_mc_free(mci); 137 + } 138 + 139 + static const struct acpi_device_id loongson_edac_acpi_match[] = { 140 + {"LOON0010", 0}, 141 + {} 142 + }; 143 + MODULE_DEVICE_TABLE(acpi, loongson_edac_acpi_match); 144 + 145 + static struct platform_driver loongson_edac_driver = { 146 + .probe = edac_probe, 147 + .remove = edac_remove, 148 + .driver = { 149 + .name = "loongson-mc-edac", 150 + .acpi_match_table = loongson_edac_acpi_match, 151 + }, 152 + }; 153 + module_platform_driver(loongson_edac_driver); 154 + 155 + MODULE_LICENSE("GPL"); 156 + MODULE_AUTHOR("Zhao Qunqin <zhaoqunqin@loongson.cn>"); 157 + MODULE_DESCRIPTION("EDAC driver for loongson memory controller");