18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2018, 2019 Cisco Systems 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/edac.h> 78c2ecf20Sopenharmony_ci#include <linux/module.h> 88c2ecf20Sopenharmony_ci#include <linux/init.h> 98c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 118c2ecf20Sopenharmony_ci#include <linux/stop_machine.h> 128c2ecf20Sopenharmony_ci#include <linux/io.h> 138c2ecf20Sopenharmony_ci#include <linux/of_address.h> 148c2ecf20Sopenharmony_ci#include <linux/regmap.h> 158c2ecf20Sopenharmony_ci#include "edac_module.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define DRV_NAME "aspeed-edac" 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define ASPEED_MCR_PROT 0x00 /* protection key register */ 228c2ecf20Sopenharmony_ci#define ASPEED_MCR_CONF 0x04 /* configuration register */ 238c2ecf20Sopenharmony_ci#define ASPEED_MCR_INTR_CTRL 0x50 /* interrupt control/status register */ 248c2ecf20Sopenharmony_ci#define ASPEED_MCR_ADDR_UNREC 0x58 /* address of first un-recoverable error */ 258c2ecf20Sopenharmony_ci#define ASPEED_MCR_ADDR_REC 0x5c /* address of last recoverable error */ 268c2ecf20Sopenharmony_ci#define ASPEED_MCR_LAST ASPEED_MCR_ADDR_REC 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define ASPEED_MCR_PROT_PASSWD 0xfc600309 308c2ecf20Sopenharmony_ci#define ASPEED_MCR_CONF_DRAM_TYPE BIT(4) 318c2ecf20Sopenharmony_ci#define ASPEED_MCR_CONF_ECC BIT(7) 328c2ecf20Sopenharmony_ci#define ASPEED_MCR_INTR_CTRL_CLEAR BIT(31) 338c2ecf20Sopenharmony_ci#define ASPEED_MCR_INTR_CTRL_CNT_REC GENMASK(23, 16) 348c2ecf20Sopenharmony_ci#define ASPEED_MCR_INTR_CTRL_CNT_UNREC GENMASK(15, 12) 358c2ecf20Sopenharmony_ci#define ASPEED_MCR_INTR_CTRL_ENABLE (BIT(0) | BIT(1)) 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic struct regmap *aspeed_regmap; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic int regmap_reg_write(void *context, unsigned int reg, unsigned int val) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci void __iomem *regs = (void __iomem *)context; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci /* enable write to MCR register set */ 468c2ecf20Sopenharmony_ci writel(ASPEED_MCR_PROT_PASSWD, regs + ASPEED_MCR_PROT); 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci writel(val, regs + reg); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci /* disable write to MCR register set */ 518c2ecf20Sopenharmony_ci writel(~ASPEED_MCR_PROT_PASSWD, regs + ASPEED_MCR_PROT); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci return 0; 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int regmap_reg_read(void *context, unsigned int reg, unsigned int *val) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci void __iomem *regs = (void __iomem *)context; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci *val = readl(regs + reg); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci return 0; 648c2ecf20Sopenharmony_ci} 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic bool regmap_is_volatile(struct device *dev, unsigned int reg) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci switch (reg) { 698c2ecf20Sopenharmony_ci case ASPEED_MCR_PROT: 708c2ecf20Sopenharmony_ci case ASPEED_MCR_INTR_CTRL: 718c2ecf20Sopenharmony_ci case ASPEED_MCR_ADDR_UNREC: 728c2ecf20Sopenharmony_ci case ASPEED_MCR_ADDR_REC: 738c2ecf20Sopenharmony_ci return true; 748c2ecf20Sopenharmony_ci default: 758c2ecf20Sopenharmony_ci return false; 768c2ecf20Sopenharmony_ci } 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic const struct regmap_config aspeed_regmap_config = { 818c2ecf20Sopenharmony_ci .reg_bits = 32, 828c2ecf20Sopenharmony_ci .val_bits = 32, 838c2ecf20Sopenharmony_ci .reg_stride = 4, 848c2ecf20Sopenharmony_ci .max_register = ASPEED_MCR_LAST, 858c2ecf20Sopenharmony_ci .reg_write = regmap_reg_write, 868c2ecf20Sopenharmony_ci .reg_read = regmap_reg_read, 878c2ecf20Sopenharmony_ci .volatile_reg = regmap_is_volatile, 888c2ecf20Sopenharmony_ci .fast_io = true, 898c2ecf20Sopenharmony_ci}; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic void count_rec(struct mem_ctl_info *mci, u8 rec_cnt, u32 rec_addr) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci struct csrow_info *csrow = mci->csrows[0]; 958c2ecf20Sopenharmony_ci u32 page, offset, syndrome; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci if (!rec_cnt) 988c2ecf20Sopenharmony_ci return; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci /* report first few errors (if there are) */ 1018c2ecf20Sopenharmony_ci /* note: no addresses are recorded */ 1028c2ecf20Sopenharmony_ci if (rec_cnt > 1) { 1038c2ecf20Sopenharmony_ci /* page, offset and syndrome are not available */ 1048c2ecf20Sopenharmony_ci page = 0; 1058c2ecf20Sopenharmony_ci offset = 0; 1068c2ecf20Sopenharmony_ci syndrome = 0; 1078c2ecf20Sopenharmony_ci edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, rec_cnt-1, 1088c2ecf20Sopenharmony_ci page, offset, syndrome, 0, 0, -1, 1098c2ecf20Sopenharmony_ci "address(es) not available", ""); 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci /* report last error */ 1138c2ecf20Sopenharmony_ci /* note: rec_addr is the last recoverable error addr */ 1148c2ecf20Sopenharmony_ci page = rec_addr >> PAGE_SHIFT; 1158c2ecf20Sopenharmony_ci offset = rec_addr & ~PAGE_MASK; 1168c2ecf20Sopenharmony_ci /* syndrome is not available */ 1178c2ecf20Sopenharmony_ci syndrome = 0; 1188c2ecf20Sopenharmony_ci edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, 1198c2ecf20Sopenharmony_ci csrow->first_page + page, offset, syndrome, 1208c2ecf20Sopenharmony_ci 0, 0, -1, "", ""); 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic void count_un_rec(struct mem_ctl_info *mci, u8 un_rec_cnt, 1258c2ecf20Sopenharmony_ci u32 un_rec_addr) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct csrow_info *csrow = mci->csrows[0]; 1288c2ecf20Sopenharmony_ci u32 page, offset, syndrome; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci if (!un_rec_cnt) 1318c2ecf20Sopenharmony_ci return; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci /* report 1. error */ 1348c2ecf20Sopenharmony_ci /* note: un_rec_addr is the first unrecoverable error addr */ 1358c2ecf20Sopenharmony_ci page = un_rec_addr >> PAGE_SHIFT; 1368c2ecf20Sopenharmony_ci offset = un_rec_addr & ~PAGE_MASK; 1378c2ecf20Sopenharmony_ci /* syndrome is not available */ 1388c2ecf20Sopenharmony_ci syndrome = 0; 1398c2ecf20Sopenharmony_ci edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, 1408c2ecf20Sopenharmony_ci csrow->first_page + page, offset, syndrome, 1418c2ecf20Sopenharmony_ci 0, 0, -1, "", ""); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci /* report further errors (if there are) */ 1448c2ecf20Sopenharmony_ci /* note: no addresses are recorded */ 1458c2ecf20Sopenharmony_ci if (un_rec_cnt > 1) { 1468c2ecf20Sopenharmony_ci /* page, offset and syndrome are not available */ 1478c2ecf20Sopenharmony_ci page = 0; 1488c2ecf20Sopenharmony_ci offset = 0; 1498c2ecf20Sopenharmony_ci syndrome = 0; 1508c2ecf20Sopenharmony_ci edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, un_rec_cnt-1, 1518c2ecf20Sopenharmony_ci page, offset, syndrome, 0, 0, -1, 1528c2ecf20Sopenharmony_ci "address(es) not available", ""); 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic irqreturn_t mcr_isr(int irq, void *arg) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci struct mem_ctl_info *mci = arg; 1608c2ecf20Sopenharmony_ci u32 rec_addr, un_rec_addr; 1618c2ecf20Sopenharmony_ci u32 reg50, reg5c, reg58; 1628c2ecf20Sopenharmony_ci u8 rec_cnt, un_rec_cnt; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci regmap_read(aspeed_regmap, ASPEED_MCR_INTR_CTRL, ®50); 1658c2ecf20Sopenharmony_ci dev_dbg(mci->pdev, "received edac interrupt w/ mcr register 50: 0x%x\n", 1668c2ecf20Sopenharmony_ci reg50); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci /* collect data about recoverable and unrecoverable errors */ 1698c2ecf20Sopenharmony_ci rec_cnt = (reg50 & ASPEED_MCR_INTR_CTRL_CNT_REC) >> 16; 1708c2ecf20Sopenharmony_ci un_rec_cnt = (reg50 & ASPEED_MCR_INTR_CTRL_CNT_UNREC) >> 12; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci dev_dbg(mci->pdev, "%d recoverable interrupts and %d unrecoverable interrupts\n", 1738c2ecf20Sopenharmony_ci rec_cnt, un_rec_cnt); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci regmap_read(aspeed_regmap, ASPEED_MCR_ADDR_UNREC, ®58); 1768c2ecf20Sopenharmony_ci un_rec_addr = reg58; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci regmap_read(aspeed_regmap, ASPEED_MCR_ADDR_REC, ®5c); 1798c2ecf20Sopenharmony_ci rec_addr = reg5c; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci /* clear interrupt flags and error counters: */ 1828c2ecf20Sopenharmony_ci regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, 1838c2ecf20Sopenharmony_ci ASPEED_MCR_INTR_CTRL_CLEAR, 1848c2ecf20Sopenharmony_ci ASPEED_MCR_INTR_CTRL_CLEAR); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, 1878c2ecf20Sopenharmony_ci ASPEED_MCR_INTR_CTRL_CLEAR, 0); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci /* process recoverable and unrecoverable errors */ 1908c2ecf20Sopenharmony_ci count_rec(mci, rec_cnt, rec_addr); 1918c2ecf20Sopenharmony_ci count_un_rec(mci, un_rec_cnt, un_rec_addr); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (!rec_cnt && !un_rec_cnt) 1948c2ecf20Sopenharmony_ci dev_dbg(mci->pdev, "received edac interrupt, but did not find any ECC counters\n"); 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci regmap_read(aspeed_regmap, ASPEED_MCR_INTR_CTRL, ®50); 1978c2ecf20Sopenharmony_ci dev_dbg(mci->pdev, "edac interrupt handled. mcr reg 50 is now: 0x%x\n", 1988c2ecf20Sopenharmony_ci reg50); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci return IRQ_HANDLED; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_cistatic int config_irq(void *ctx, struct platform_device *pdev) 2058c2ecf20Sopenharmony_ci{ 2068c2ecf20Sopenharmony_ci int irq; 2078c2ecf20Sopenharmony_ci int rc; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci /* register interrupt handler */ 2108c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 2118c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "got irq %d\n", irq); 2128c2ecf20Sopenharmony_ci if (irq < 0) 2138c2ecf20Sopenharmony_ci return irq; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci rc = devm_request_irq(&pdev->dev, irq, mcr_isr, IRQF_TRIGGER_HIGH, 2168c2ecf20Sopenharmony_ci DRV_NAME, ctx); 2178c2ecf20Sopenharmony_ci if (rc) { 2188c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "unable to request irq %d\n", irq); 2198c2ecf20Sopenharmony_ci return rc; 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci /* enable interrupts */ 2238c2ecf20Sopenharmony_ci regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, 2248c2ecf20Sopenharmony_ci ASPEED_MCR_INTR_CTRL_ENABLE, 2258c2ecf20Sopenharmony_ci ASPEED_MCR_INTR_CTRL_ENABLE); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci return 0; 2288c2ecf20Sopenharmony_ci} 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_cistatic int init_csrows(struct mem_ctl_info *mci) 2328c2ecf20Sopenharmony_ci{ 2338c2ecf20Sopenharmony_ci struct csrow_info *csrow = mci->csrows[0]; 2348c2ecf20Sopenharmony_ci u32 nr_pages, dram_type; 2358c2ecf20Sopenharmony_ci struct dimm_info *dimm; 2368c2ecf20Sopenharmony_ci struct device_node *np; 2378c2ecf20Sopenharmony_ci struct resource r; 2388c2ecf20Sopenharmony_ci u32 reg04; 2398c2ecf20Sopenharmony_ci int rc; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci /* retrieve info about physical memory from device tree */ 2428c2ecf20Sopenharmony_ci np = of_find_node_by_path("/memory"); 2438c2ecf20Sopenharmony_ci if (!np) { 2448c2ecf20Sopenharmony_ci dev_err(mci->pdev, "dt: missing /memory node\n"); 2458c2ecf20Sopenharmony_ci return -ENODEV; 2468c2ecf20Sopenharmony_ci } 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci rc = of_address_to_resource(np, 0, &r); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci of_node_put(np); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if (rc) { 2538c2ecf20Sopenharmony_ci dev_err(mci->pdev, "dt: failed requesting resource for /memory node\n"); 2548c2ecf20Sopenharmony_ci return rc; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci dev_dbg(mci->pdev, "dt: /memory node resources: first page r.start=0x%x, resource_size=0x%x, PAGE_SHIFT macro=0x%x\n", 2588c2ecf20Sopenharmony_ci r.start, resource_size(&r), PAGE_SHIFT); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci csrow->first_page = r.start >> PAGE_SHIFT; 2618c2ecf20Sopenharmony_ci nr_pages = resource_size(&r) >> PAGE_SHIFT; 2628c2ecf20Sopenharmony_ci csrow->last_page = csrow->first_page + nr_pages - 1; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci regmap_read(aspeed_regmap, ASPEED_MCR_CONF, ®04); 2658c2ecf20Sopenharmony_ci dram_type = (reg04 & ASPEED_MCR_CONF_DRAM_TYPE) ? MEM_DDR4 : MEM_DDR3; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci dimm = csrow->channels[0]->dimm; 2688c2ecf20Sopenharmony_ci dimm->mtype = dram_type; 2698c2ecf20Sopenharmony_ci dimm->edac_mode = EDAC_SECDED; 2708c2ecf20Sopenharmony_ci dimm->nr_pages = nr_pages / csrow->nr_channels; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci dev_dbg(mci->pdev, "initialized dimm with first_page=0x%lx and nr_pages=0x%x\n", 2738c2ecf20Sopenharmony_ci csrow->first_page, nr_pages); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci return 0; 2768c2ecf20Sopenharmony_ci} 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_cistatic int aspeed_probe(struct platform_device *pdev) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2828c2ecf20Sopenharmony_ci struct edac_mc_layer layers[2]; 2838c2ecf20Sopenharmony_ci struct mem_ctl_info *mci; 2848c2ecf20Sopenharmony_ci void __iomem *regs; 2858c2ecf20Sopenharmony_ci u32 reg04; 2868c2ecf20Sopenharmony_ci int rc; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci regs = devm_platform_ioremap_resource(pdev, 0); 2898c2ecf20Sopenharmony_ci if (IS_ERR(regs)) 2908c2ecf20Sopenharmony_ci return PTR_ERR(regs); 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci aspeed_regmap = devm_regmap_init(dev, NULL, (__force void *)regs, 2938c2ecf20Sopenharmony_ci &aspeed_regmap_config); 2948c2ecf20Sopenharmony_ci if (IS_ERR(aspeed_regmap)) 2958c2ecf20Sopenharmony_ci return PTR_ERR(aspeed_regmap); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci /* bail out if ECC mode is not configured */ 2988c2ecf20Sopenharmony_ci regmap_read(aspeed_regmap, ASPEED_MCR_CONF, ®04); 2998c2ecf20Sopenharmony_ci if (!(reg04 & ASPEED_MCR_CONF_ECC)) { 3008c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "ECC mode is not configured in u-boot\n"); 3018c2ecf20Sopenharmony_ci return -EPERM; 3028c2ecf20Sopenharmony_ci } 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci edac_op_state = EDAC_OPSTATE_INT; 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci /* allocate & init EDAC MC data structure */ 3078c2ecf20Sopenharmony_ci layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; 3088c2ecf20Sopenharmony_ci layers[0].size = 1; 3098c2ecf20Sopenharmony_ci layers[0].is_virt_csrow = true; 3108c2ecf20Sopenharmony_ci layers[1].type = EDAC_MC_LAYER_CHANNEL; 3118c2ecf20Sopenharmony_ci layers[1].size = 1; 3128c2ecf20Sopenharmony_ci layers[1].is_virt_csrow = false; 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); 3158c2ecf20Sopenharmony_ci if (!mci) 3168c2ecf20Sopenharmony_ci return -ENOMEM; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci mci->pdev = &pdev->dev; 3198c2ecf20Sopenharmony_ci mci->mtype_cap = MEM_FLAG_DDR3 | MEM_FLAG_DDR4; 3208c2ecf20Sopenharmony_ci mci->edac_ctl_cap = EDAC_FLAG_SECDED; 3218c2ecf20Sopenharmony_ci mci->edac_cap = EDAC_FLAG_SECDED; 3228c2ecf20Sopenharmony_ci mci->scrub_cap = SCRUB_FLAG_HW_SRC; 3238c2ecf20Sopenharmony_ci mci->scrub_mode = SCRUB_HW_SRC; 3248c2ecf20Sopenharmony_ci mci->mod_name = DRV_NAME; 3258c2ecf20Sopenharmony_ci mci->ctl_name = "MIC"; 3268c2ecf20Sopenharmony_ci mci->dev_name = dev_name(&pdev->dev); 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci rc = init_csrows(mci); 3298c2ecf20Sopenharmony_ci if (rc) { 3308c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to init csrows\n"); 3318c2ecf20Sopenharmony_ci goto probe_exit02; 3328c2ecf20Sopenharmony_ci } 3338c2ecf20Sopenharmony_ci 3348c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, mci); 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci /* register with edac core */ 3378c2ecf20Sopenharmony_ci rc = edac_mc_add_mc(mci); 3388c2ecf20Sopenharmony_ci if (rc) { 3398c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to register with EDAC core\n"); 3408c2ecf20Sopenharmony_ci goto probe_exit02; 3418c2ecf20Sopenharmony_ci } 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci /* register interrupt handler and enable interrupts */ 3448c2ecf20Sopenharmony_ci rc = config_irq(mci, pdev); 3458c2ecf20Sopenharmony_ci if (rc) { 3468c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed setting up irq\n"); 3478c2ecf20Sopenharmony_ci goto probe_exit01; 3488c2ecf20Sopenharmony_ci } 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci return 0; 3518c2ecf20Sopenharmony_ci 3528c2ecf20Sopenharmony_ciprobe_exit01: 3538c2ecf20Sopenharmony_ci edac_mc_del_mc(&pdev->dev); 3548c2ecf20Sopenharmony_ciprobe_exit02: 3558c2ecf20Sopenharmony_ci edac_mc_free(mci); 3568c2ecf20Sopenharmony_ci return rc; 3578c2ecf20Sopenharmony_ci} 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_cistatic int aspeed_remove(struct platform_device *pdev) 3618c2ecf20Sopenharmony_ci{ 3628c2ecf20Sopenharmony_ci struct mem_ctl_info *mci; 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci /* disable interrupts */ 3658c2ecf20Sopenharmony_ci regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, 3668c2ecf20Sopenharmony_ci ASPEED_MCR_INTR_CTRL_ENABLE, 0); 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci /* free resources */ 3698c2ecf20Sopenharmony_ci mci = edac_mc_del_mc(&pdev->dev); 3708c2ecf20Sopenharmony_ci if (mci) 3718c2ecf20Sopenharmony_ci edac_mc_free(mci); 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci return 0; 3748c2ecf20Sopenharmony_ci} 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_cistatic const struct of_device_id aspeed_of_match[] = { 3788c2ecf20Sopenharmony_ci { .compatible = "aspeed,ast2500-sdram-edac" }, 3798c2ecf20Sopenharmony_ci {}, 3808c2ecf20Sopenharmony_ci}; 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_cistatic struct platform_driver aspeed_driver = { 3848c2ecf20Sopenharmony_ci .driver = { 3858c2ecf20Sopenharmony_ci .name = DRV_NAME, 3868c2ecf20Sopenharmony_ci .of_match_table = aspeed_of_match 3878c2ecf20Sopenharmony_ci }, 3888c2ecf20Sopenharmony_ci .probe = aspeed_probe, 3898c2ecf20Sopenharmony_ci .remove = aspeed_remove 3908c2ecf20Sopenharmony_ci}; 3918c2ecf20Sopenharmony_cimodule_platform_driver(aspeed_driver); 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3948c2ecf20Sopenharmony_ciMODULE_AUTHOR("Stefan Schaeckeler <sschaeck@cisco.com>"); 3958c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Aspeed AST2500 EDAC driver"); 3968c2ecf20Sopenharmony_ciMODULE_VERSION("1.0"); 397