18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2011-2012 Calxeda, Inc. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci#include <linux/types.h> 68c2ecf20Sopenharmony_ci#include <linux/kernel.h> 78c2ecf20Sopenharmony_ci#include <linux/ctype.h> 88c2ecf20Sopenharmony_ci#include <linux/edac.h> 98c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 118c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 128c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include "edac_module.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/* DDR Ctrlr Error Registers */ 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define HB_DDR_ECC_ERR_BASE 0x128 198c2ecf20Sopenharmony_ci#define MW_DDR_ECC_ERR_BASE 0x1b4 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define HB_DDR_ECC_OPT 0x00 228c2ecf20Sopenharmony_ci#define HB_DDR_ECC_U_ERR_ADDR 0x08 238c2ecf20Sopenharmony_ci#define HB_DDR_ECC_U_ERR_STAT 0x0c 248c2ecf20Sopenharmony_ci#define HB_DDR_ECC_U_ERR_DATAL 0x10 258c2ecf20Sopenharmony_ci#define HB_DDR_ECC_U_ERR_DATAH 0x14 268c2ecf20Sopenharmony_ci#define HB_DDR_ECC_C_ERR_ADDR 0x18 278c2ecf20Sopenharmony_ci#define HB_DDR_ECC_C_ERR_STAT 0x1c 288c2ecf20Sopenharmony_ci#define HB_DDR_ECC_C_ERR_DATAL 0x20 298c2ecf20Sopenharmony_ci#define HB_DDR_ECC_C_ERR_DATAH 0x24 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci#define HB_DDR_ECC_OPT_MODE_MASK 0x3 328c2ecf20Sopenharmony_ci#define HB_DDR_ECC_OPT_FWC 0x100 338c2ecf20Sopenharmony_ci#define HB_DDR_ECC_OPT_XOR_SHIFT 16 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* DDR Ctrlr Interrupt Registers */ 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define HB_DDR_ECC_INT_BASE 0x180 388c2ecf20Sopenharmony_ci#define MW_DDR_ECC_INT_BASE 0x218 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define HB_DDR_ECC_INT_STATUS 0x00 418c2ecf20Sopenharmony_ci#define HB_DDR_ECC_INT_ACK 0x04 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci#define HB_DDR_ECC_INT_STAT_CE 0x8 448c2ecf20Sopenharmony_ci#define HB_DDR_ECC_INT_STAT_DOUBLE_CE 0x10 458c2ecf20Sopenharmony_ci#define HB_DDR_ECC_INT_STAT_UE 0x20 468c2ecf20Sopenharmony_ci#define HB_DDR_ECC_INT_STAT_DOUBLE_UE 0x40 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistruct hb_mc_drvdata { 498c2ecf20Sopenharmony_ci void __iomem *mc_err_base; 508c2ecf20Sopenharmony_ci void __iomem *mc_int_base; 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic irqreturn_t highbank_mc_err_handler(int irq, void *dev_id) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci struct mem_ctl_info *mci = dev_id; 568c2ecf20Sopenharmony_ci struct hb_mc_drvdata *drvdata = mci->pvt_info; 578c2ecf20Sopenharmony_ci u32 status, err_addr; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci /* Read the interrupt status register */ 608c2ecf20Sopenharmony_ci status = readl(drvdata->mc_int_base + HB_DDR_ECC_INT_STATUS); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci if (status & HB_DDR_ECC_INT_STAT_UE) { 638c2ecf20Sopenharmony_ci err_addr = readl(drvdata->mc_err_base + HB_DDR_ECC_U_ERR_ADDR); 648c2ecf20Sopenharmony_ci edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, 658c2ecf20Sopenharmony_ci err_addr >> PAGE_SHIFT, 668c2ecf20Sopenharmony_ci err_addr & ~PAGE_MASK, 0, 678c2ecf20Sopenharmony_ci 0, 0, -1, 688c2ecf20Sopenharmony_ci mci->ctl_name, ""); 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci if (status & HB_DDR_ECC_INT_STAT_CE) { 718c2ecf20Sopenharmony_ci u32 syndrome = readl(drvdata->mc_err_base + HB_DDR_ECC_C_ERR_STAT); 728c2ecf20Sopenharmony_ci syndrome = (syndrome >> 8) & 0xff; 738c2ecf20Sopenharmony_ci err_addr = readl(drvdata->mc_err_base + HB_DDR_ECC_C_ERR_ADDR); 748c2ecf20Sopenharmony_ci edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, 758c2ecf20Sopenharmony_ci err_addr >> PAGE_SHIFT, 768c2ecf20Sopenharmony_ci err_addr & ~PAGE_MASK, syndrome, 778c2ecf20Sopenharmony_ci 0, 0, -1, 788c2ecf20Sopenharmony_ci mci->ctl_name, ""); 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci /* clear the error, clears the interrupt */ 828c2ecf20Sopenharmony_ci writel(status, drvdata->mc_int_base + HB_DDR_ECC_INT_ACK); 838c2ecf20Sopenharmony_ci return IRQ_HANDLED; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic void highbank_mc_err_inject(struct mem_ctl_info *mci, u8 synd) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci struct hb_mc_drvdata *pdata = mci->pvt_info; 898c2ecf20Sopenharmony_ci u32 reg; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci reg = readl(pdata->mc_err_base + HB_DDR_ECC_OPT); 928c2ecf20Sopenharmony_ci reg &= HB_DDR_ECC_OPT_MODE_MASK; 938c2ecf20Sopenharmony_ci reg |= (synd << HB_DDR_ECC_OPT_XOR_SHIFT) | HB_DDR_ECC_OPT_FWC; 948c2ecf20Sopenharmony_ci writel(reg, pdata->mc_err_base + HB_DDR_ECC_OPT); 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci#define to_mci(k) container_of(k, struct mem_ctl_info, dev) 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic ssize_t highbank_mc_inject_ctrl(struct device *dev, 1008c2ecf20Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t count) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci struct mem_ctl_info *mci = to_mci(dev); 1038c2ecf20Sopenharmony_ci u8 synd; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (kstrtou8(buf, 16, &synd)) 1068c2ecf20Sopenharmony_ci return -EINVAL; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci highbank_mc_err_inject(mci, synd); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci return count; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic DEVICE_ATTR(inject_ctrl, S_IWUSR, NULL, highbank_mc_inject_ctrl); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic struct attribute *highbank_dev_attrs[] = { 1168c2ecf20Sopenharmony_ci &dev_attr_inject_ctrl.attr, 1178c2ecf20Sopenharmony_ci NULL 1188c2ecf20Sopenharmony_ci}; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(highbank_dev); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistruct hb_mc_settings { 1238c2ecf20Sopenharmony_ci int err_offset; 1248c2ecf20Sopenharmony_ci int int_offset; 1258c2ecf20Sopenharmony_ci}; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_cistatic struct hb_mc_settings hb_settings = { 1288c2ecf20Sopenharmony_ci .err_offset = HB_DDR_ECC_ERR_BASE, 1298c2ecf20Sopenharmony_ci .int_offset = HB_DDR_ECC_INT_BASE, 1308c2ecf20Sopenharmony_ci}; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic struct hb_mc_settings mw_settings = { 1338c2ecf20Sopenharmony_ci .err_offset = MW_DDR_ECC_ERR_BASE, 1348c2ecf20Sopenharmony_ci .int_offset = MW_DDR_ECC_INT_BASE, 1358c2ecf20Sopenharmony_ci}; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic const struct of_device_id hb_ddr_ctrl_of_match[] = { 1388c2ecf20Sopenharmony_ci { .compatible = "calxeda,hb-ddr-ctrl", .data = &hb_settings }, 1398c2ecf20Sopenharmony_ci { .compatible = "calxeda,ecx-2000-ddr-ctrl", .data = &mw_settings }, 1408c2ecf20Sopenharmony_ci {}, 1418c2ecf20Sopenharmony_ci}; 1428c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, hb_ddr_ctrl_of_match); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic int highbank_mc_probe(struct platform_device *pdev) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci const struct of_device_id *id; 1478c2ecf20Sopenharmony_ci const struct hb_mc_settings *settings; 1488c2ecf20Sopenharmony_ci struct edac_mc_layer layers[2]; 1498c2ecf20Sopenharmony_ci struct mem_ctl_info *mci; 1508c2ecf20Sopenharmony_ci struct hb_mc_drvdata *drvdata; 1518c2ecf20Sopenharmony_ci struct dimm_info *dimm; 1528c2ecf20Sopenharmony_ci struct resource *r; 1538c2ecf20Sopenharmony_ci void __iomem *base; 1548c2ecf20Sopenharmony_ci u32 control; 1558c2ecf20Sopenharmony_ci int irq; 1568c2ecf20Sopenharmony_ci int res = 0; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci id = of_match_device(hb_ddr_ctrl_of_match, &pdev->dev); 1598c2ecf20Sopenharmony_ci if (!id) 1608c2ecf20Sopenharmony_ci return -ENODEV; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; 1638c2ecf20Sopenharmony_ci layers[0].size = 1; 1648c2ecf20Sopenharmony_ci layers[0].is_virt_csrow = true; 1658c2ecf20Sopenharmony_ci layers[1].type = EDAC_MC_LAYER_CHANNEL; 1668c2ecf20Sopenharmony_ci layers[1].size = 1; 1678c2ecf20Sopenharmony_ci layers[1].is_virt_csrow = false; 1688c2ecf20Sopenharmony_ci mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 1698c2ecf20Sopenharmony_ci sizeof(struct hb_mc_drvdata)); 1708c2ecf20Sopenharmony_ci if (!mci) 1718c2ecf20Sopenharmony_ci return -ENOMEM; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci mci->pdev = &pdev->dev; 1748c2ecf20Sopenharmony_ci drvdata = mci->pvt_info; 1758c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, mci); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) { 1788c2ecf20Sopenharmony_ci res = -ENOMEM; 1798c2ecf20Sopenharmony_ci goto free; 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1838c2ecf20Sopenharmony_ci if (!r) { 1848c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unable to get mem resource\n"); 1858c2ecf20Sopenharmony_ci res = -ENODEV; 1868c2ecf20Sopenharmony_ci goto err; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci if (!devm_request_mem_region(&pdev->dev, r->start, 1908c2ecf20Sopenharmony_ci resource_size(r), dev_name(&pdev->dev))) { 1918c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Error while requesting mem region\n"); 1928c2ecf20Sopenharmony_ci res = -EBUSY; 1938c2ecf20Sopenharmony_ci goto err; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); 1978c2ecf20Sopenharmony_ci if (!base) { 1988c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unable to map regs\n"); 1998c2ecf20Sopenharmony_ci res = -ENOMEM; 2008c2ecf20Sopenharmony_ci goto err; 2018c2ecf20Sopenharmony_ci } 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci settings = id->data; 2048c2ecf20Sopenharmony_ci drvdata->mc_err_base = base + settings->err_offset; 2058c2ecf20Sopenharmony_ci drvdata->mc_int_base = base + settings->int_offset; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci control = readl(drvdata->mc_err_base + HB_DDR_ECC_OPT) & 0x3; 2088c2ecf20Sopenharmony_ci if (!control || (control == 0x2)) { 2098c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "No ECC present, or ECC disabled\n"); 2108c2ecf20Sopenharmony_ci res = -ENODEV; 2118c2ecf20Sopenharmony_ci goto err; 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci mci->mtype_cap = MEM_FLAG_DDR3; 2158c2ecf20Sopenharmony_ci mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; 2168c2ecf20Sopenharmony_ci mci->edac_cap = EDAC_FLAG_SECDED; 2178c2ecf20Sopenharmony_ci mci->mod_name = pdev->dev.driver->name; 2188c2ecf20Sopenharmony_ci mci->ctl_name = id->compatible; 2198c2ecf20Sopenharmony_ci mci->dev_name = dev_name(&pdev->dev); 2208c2ecf20Sopenharmony_ci mci->scrub_mode = SCRUB_SW_SRC; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci /* Only a single 4GB DIMM is supported */ 2238c2ecf20Sopenharmony_ci dimm = *mci->dimms; 2248c2ecf20Sopenharmony_ci dimm->nr_pages = (~0UL >> PAGE_SHIFT) + 1; 2258c2ecf20Sopenharmony_ci dimm->grain = 8; 2268c2ecf20Sopenharmony_ci dimm->dtype = DEV_X8; 2278c2ecf20Sopenharmony_ci dimm->mtype = MEM_DDR3; 2288c2ecf20Sopenharmony_ci dimm->edac_mode = EDAC_SECDED; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci res = edac_mc_add_mc_with_groups(mci, highbank_dev_groups); 2318c2ecf20Sopenharmony_ci if (res < 0) 2328c2ecf20Sopenharmony_ci goto err; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 2358c2ecf20Sopenharmony_ci res = devm_request_irq(&pdev->dev, irq, highbank_mc_err_handler, 2368c2ecf20Sopenharmony_ci 0, dev_name(&pdev->dev), mci); 2378c2ecf20Sopenharmony_ci if (res < 0) { 2388c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unable to request irq %d\n", irq); 2398c2ecf20Sopenharmony_ci goto err2; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci devres_close_group(&pdev->dev, NULL); 2438c2ecf20Sopenharmony_ci return 0; 2448c2ecf20Sopenharmony_cierr2: 2458c2ecf20Sopenharmony_ci edac_mc_del_mc(&pdev->dev); 2468c2ecf20Sopenharmony_cierr: 2478c2ecf20Sopenharmony_ci devres_release_group(&pdev->dev, NULL); 2488c2ecf20Sopenharmony_cifree: 2498c2ecf20Sopenharmony_ci edac_mc_free(mci); 2508c2ecf20Sopenharmony_ci return res; 2518c2ecf20Sopenharmony_ci} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic int highbank_mc_remove(struct platform_device *pdev) 2548c2ecf20Sopenharmony_ci{ 2558c2ecf20Sopenharmony_ci struct mem_ctl_info *mci = platform_get_drvdata(pdev); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci edac_mc_del_mc(&pdev->dev); 2588c2ecf20Sopenharmony_ci edac_mc_free(mci); 2598c2ecf20Sopenharmony_ci return 0; 2608c2ecf20Sopenharmony_ci} 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_cistatic struct platform_driver highbank_mc_edac_driver = { 2638c2ecf20Sopenharmony_ci .probe = highbank_mc_probe, 2648c2ecf20Sopenharmony_ci .remove = highbank_mc_remove, 2658c2ecf20Sopenharmony_ci .driver = { 2668c2ecf20Sopenharmony_ci .name = "hb_mc_edac", 2678c2ecf20Sopenharmony_ci .of_match_table = hb_ddr_ctrl_of_match, 2688c2ecf20Sopenharmony_ci }, 2698c2ecf20Sopenharmony_ci}; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_cimodule_platform_driver(highbank_mc_edac_driver); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2748c2ecf20Sopenharmony_ciMODULE_AUTHOR("Calxeda, Inc."); 2758c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("EDAC Driver for Calxeda Highbank"); 276