1*558aff7aSZhao Qunqin // SPDX-License-Identifier: GPL-2.0 2*558aff7aSZhao Qunqin /* 3*558aff7aSZhao Qunqin * Copyright (C) 2024 Loongson Technology Corporation Limited. 4*558aff7aSZhao Qunqin */ 5*558aff7aSZhao Qunqin 6*558aff7aSZhao Qunqin #include <linux/acpi.h> 7*558aff7aSZhao Qunqin #include <linux/edac.h> 8*558aff7aSZhao Qunqin #include <linux/init.h> 9*558aff7aSZhao Qunqin #include <linux/io-64-nonatomic-lo-hi.h> 10*558aff7aSZhao Qunqin #include <linux/module.h> 11*558aff7aSZhao Qunqin #include <linux/platform_device.h> 12*558aff7aSZhao Qunqin #include "edac_module.h" 13*558aff7aSZhao Qunqin 14*558aff7aSZhao Qunqin #define ECC_CS_COUNT_REG 0x18 15*558aff7aSZhao Qunqin 16*558aff7aSZhao Qunqin struct loongson_edac_pvt { 17*558aff7aSZhao Qunqin void __iomem *ecc_base; 18*558aff7aSZhao Qunqin 19*558aff7aSZhao Qunqin /* 20*558aff7aSZhao Qunqin * The ECC register in this controller records the number of errors 21*558aff7aSZhao Qunqin * encountered since reset and cannot be zeroed so in order to be able 22*558aff7aSZhao Qunqin * to report the error count at each check, this records the previous 23*558aff7aSZhao Qunqin * register state. 24*558aff7aSZhao Qunqin */ 25*558aff7aSZhao Qunqin int last_ce_count; 26*558aff7aSZhao Qunqin }; 27*558aff7aSZhao Qunqin 28*558aff7aSZhao Qunqin static int read_ecc(struct mem_ctl_info *mci) 29*558aff7aSZhao Qunqin { 30*558aff7aSZhao Qunqin struct loongson_edac_pvt *pvt = mci->pvt_info; 31*558aff7aSZhao Qunqin u64 ecc; 32*558aff7aSZhao Qunqin int cs; 33*558aff7aSZhao Qunqin 34*558aff7aSZhao Qunqin ecc = readq(pvt->ecc_base + ECC_CS_COUNT_REG); 35*558aff7aSZhao Qunqin /* cs0 -- cs3 */ 36*558aff7aSZhao Qunqin cs = ecc & 0xff; 37*558aff7aSZhao Qunqin cs += (ecc >> 8) & 0xff; 38*558aff7aSZhao Qunqin cs += (ecc >> 16) & 0xff; 39*558aff7aSZhao Qunqin cs += (ecc >> 24) & 0xff; 40*558aff7aSZhao Qunqin 41*558aff7aSZhao Qunqin return cs; 42*558aff7aSZhao Qunqin } 43*558aff7aSZhao Qunqin 44*558aff7aSZhao Qunqin static void edac_check(struct mem_ctl_info *mci) 45*558aff7aSZhao Qunqin { 46*558aff7aSZhao Qunqin struct loongson_edac_pvt *pvt = mci->pvt_info; 47*558aff7aSZhao Qunqin int new, add; 48*558aff7aSZhao Qunqin 49*558aff7aSZhao Qunqin new = read_ecc(mci); 50*558aff7aSZhao Qunqin add = new - pvt->last_ce_count; 51*558aff7aSZhao Qunqin pvt->last_ce_count = new; 52*558aff7aSZhao Qunqin if (add <= 0) 53*558aff7aSZhao Qunqin return; 54*558aff7aSZhao Qunqin 55*558aff7aSZhao Qunqin edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, add, 56*558aff7aSZhao Qunqin 0, 0, 0, 0, 0, -1, "error", ""); 57*558aff7aSZhao Qunqin } 58*558aff7aSZhao Qunqin 59*558aff7aSZhao Qunqin static void dimm_config_init(struct mem_ctl_info *mci) 60*558aff7aSZhao Qunqin { 61*558aff7aSZhao Qunqin struct dimm_info *dimm; 62*558aff7aSZhao Qunqin u32 size, npages; 63*558aff7aSZhao Qunqin 64*558aff7aSZhao Qunqin /* size not used */ 65*558aff7aSZhao Qunqin size = -1; 66*558aff7aSZhao Qunqin npages = MiB_TO_PAGES(size); 67*558aff7aSZhao Qunqin 68*558aff7aSZhao Qunqin dimm = edac_get_dimm(mci, 0, 0, 0); 69*558aff7aSZhao Qunqin dimm->nr_pages = npages; 70*558aff7aSZhao Qunqin snprintf(dimm->label, sizeof(dimm->label), 71*558aff7aSZhao Qunqin "MC#%uChannel#%u_DIMM#%u", mci->mc_idx, 0, 0); 72*558aff7aSZhao Qunqin dimm->grain = 8; 73*558aff7aSZhao Qunqin } 74*558aff7aSZhao Qunqin 75*558aff7aSZhao Qunqin static void pvt_init(struct mem_ctl_info *mci, void __iomem *vbase) 76*558aff7aSZhao Qunqin { 77*558aff7aSZhao Qunqin struct loongson_edac_pvt *pvt = mci->pvt_info; 78*558aff7aSZhao Qunqin 79*558aff7aSZhao Qunqin pvt->ecc_base = vbase; 80*558aff7aSZhao Qunqin pvt->last_ce_count = read_ecc(mci); 81*558aff7aSZhao Qunqin } 82*558aff7aSZhao Qunqin 83*558aff7aSZhao Qunqin static int edac_probe(struct platform_device *pdev) 84*558aff7aSZhao Qunqin { 85*558aff7aSZhao Qunqin struct edac_mc_layer layers[2]; 86*558aff7aSZhao Qunqin struct mem_ctl_info *mci; 87*558aff7aSZhao Qunqin void __iomem *vbase; 88*558aff7aSZhao Qunqin int ret; 89*558aff7aSZhao Qunqin 90*558aff7aSZhao Qunqin vbase = devm_platform_ioremap_resource(pdev, 0); 91*558aff7aSZhao Qunqin if (IS_ERR(vbase)) 92*558aff7aSZhao Qunqin return PTR_ERR(vbase); 93*558aff7aSZhao Qunqin 94*558aff7aSZhao Qunqin layers[0].type = EDAC_MC_LAYER_CHANNEL; 95*558aff7aSZhao Qunqin layers[0].size = 1; 96*558aff7aSZhao Qunqin layers[0].is_virt_csrow = false; 97*558aff7aSZhao Qunqin layers[1].type = EDAC_MC_LAYER_SLOT; 98*558aff7aSZhao Qunqin layers[1].size = 1; 99*558aff7aSZhao Qunqin layers[1].is_virt_csrow = true; 100*558aff7aSZhao Qunqin mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 101*558aff7aSZhao Qunqin sizeof(struct loongson_edac_pvt)); 102*558aff7aSZhao Qunqin if (mci == NULL) 103*558aff7aSZhao Qunqin return -ENOMEM; 104*558aff7aSZhao Qunqin 105*558aff7aSZhao Qunqin mci->mc_idx = edac_device_alloc_index(); 106*558aff7aSZhao Qunqin mci->mtype_cap = MEM_FLAG_RDDR4; 107*558aff7aSZhao Qunqin mci->edac_ctl_cap = EDAC_FLAG_NONE; 108*558aff7aSZhao Qunqin mci->edac_cap = EDAC_FLAG_NONE; 109*558aff7aSZhao Qunqin mci->mod_name = "loongson_edac.c"; 110*558aff7aSZhao Qunqin mci->ctl_name = "loongson_edac_ctl"; 111*558aff7aSZhao Qunqin mci->dev_name = "loongson_edac_dev"; 112*558aff7aSZhao Qunqin mci->ctl_page_to_phys = NULL; 113*558aff7aSZhao Qunqin mci->pdev = &pdev->dev; 114*558aff7aSZhao Qunqin mci->error_desc.grain = 8; 115*558aff7aSZhao Qunqin mci->edac_check = edac_check; 116*558aff7aSZhao Qunqin 117*558aff7aSZhao Qunqin pvt_init(mci, vbase); 118*558aff7aSZhao Qunqin dimm_config_init(mci); 119*558aff7aSZhao Qunqin 120*558aff7aSZhao Qunqin ret = edac_mc_add_mc(mci); 121*558aff7aSZhao Qunqin if (ret) { 122*558aff7aSZhao Qunqin edac_dbg(0, "MC: failed edac_mc_add_mc()\n"); 123*558aff7aSZhao Qunqin edac_mc_free(mci); 124*558aff7aSZhao Qunqin return ret; 125*558aff7aSZhao Qunqin } 126*558aff7aSZhao Qunqin edac_op_state = EDAC_OPSTATE_POLL; 127*558aff7aSZhao Qunqin 128*558aff7aSZhao Qunqin return 0; 129*558aff7aSZhao Qunqin } 130*558aff7aSZhao Qunqin 131*558aff7aSZhao Qunqin static void edac_remove(struct platform_device *pdev) 132*558aff7aSZhao Qunqin { 133*558aff7aSZhao Qunqin struct mem_ctl_info *mci = edac_mc_del_mc(&pdev->dev); 134*558aff7aSZhao Qunqin 135*558aff7aSZhao Qunqin if (mci) 136*558aff7aSZhao Qunqin edac_mc_free(mci); 137*558aff7aSZhao Qunqin } 138*558aff7aSZhao Qunqin 139*558aff7aSZhao Qunqin static const struct acpi_device_id loongson_edac_acpi_match[] = { 140*558aff7aSZhao Qunqin {"LOON0010", 0}, 141*558aff7aSZhao Qunqin {} 142*558aff7aSZhao Qunqin }; 143*558aff7aSZhao Qunqin MODULE_DEVICE_TABLE(acpi, loongson_edac_acpi_match); 144*558aff7aSZhao Qunqin 145*558aff7aSZhao Qunqin static struct platform_driver loongson_edac_driver = { 146*558aff7aSZhao Qunqin .probe = edac_probe, 147*558aff7aSZhao Qunqin .remove = edac_remove, 148*558aff7aSZhao Qunqin .driver = { 149*558aff7aSZhao Qunqin .name = "loongson-mc-edac", 150*558aff7aSZhao Qunqin .acpi_match_table = loongson_edac_acpi_match, 151*558aff7aSZhao Qunqin }, 152*558aff7aSZhao Qunqin }; 153*558aff7aSZhao Qunqin module_platform_driver(loongson_edac_driver); 154*558aff7aSZhao Qunqin 155*558aff7aSZhao Qunqin MODULE_LICENSE("GPL"); 156*558aff7aSZhao Qunqin MODULE_AUTHOR("Zhao Qunqin <zhaoqunqin@loongson.cn>"); 157*558aff7aSZhao Qunqin MODULE_DESCRIPTION("EDAC driver for loongson memory controller"); 158