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, &reg50);
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, &reg58);
1768c2ecf20Sopenharmony_ci	un_rec_addr = reg58;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	regmap_read(aspeed_regmap, ASPEED_MCR_ADDR_REC, &reg5c);
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, &reg50);
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, &reg04);
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, &reg04);
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