|
| 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 <[email protected]>"); |
| 157 | +MODULE_DESCRIPTION("EDAC driver for loongson memory controller"); |
0 commit comments