| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Xilinx Versal memory controller driver | |
| * Copyright (C) 2023 Advanced Micro Devices, Inc. | |
| */ | |
| #include <linux/bitfield.h> | |
| #include <linux/edac.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/of_address.h> | |
| #include <linux/of_device.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/sizes.h> | |
| #include <linux/firmware/xlnx-zynqmp.h> | |
| #include <linux/firmware/xlnx-event-manager.h> | |
| #include "edac_module.h" | |
| /* Granularity of reported error in bytes */ | |
| #define XDDR_EDAC_ERR_GRAIN 1 | |
| #define XDDR_EDAC_MSG_SIZE 256 | |
| #define EVENT 2 | |
| #define XDDR_PCSR_OFFSET 0xC | |
| #define XDDR_ISR_OFFSET 0x14 | |
| #define XDDR_IRQ_EN_OFFSET 0x20 | |
| #define XDDR_IRQ1_EN_OFFSET 0x2C | |
| #define XDDR_IRQ_DIS_OFFSET 0x24 | |
| #define XDDR_IRQ_CE_MASK GENMASK(18, 15) | |
| #define XDDR_IRQ_UE_MASK GENMASK(14, 11) | |
| #define XDDR_REG_CONFIG0_OFFSET 0x258 | |
| #define XDDR_REG_CONFIG0_BUS_WIDTH_MASK GENMASK(19, 18) | |
| #define XDDR_REG_CONFIG0_NUM_CHANS_MASK BIT(17) | |
| #define XDDR_REG_CONFIG0_NUM_RANKS_MASK GENMASK(15, 14) | |
| #define XDDR_REG_CONFIG0_SIZE_MASK GENMASK(10, 8) | |
| #define XDDR_REG_PINOUT_OFFSET 0x25C | |
| #define XDDR_REG_PINOUT_ECC_EN_MASK GENMASK(7, 5) | |
| #define ECCW0_FLIP_CTRL 0x109C | |
| #define ECCW0_FLIP0_OFFSET 0x10A0 | |
| #define ECCW0_FLIP0_BITS 31 | |
| #define ECCW0_FLIP1_OFFSET 0x10A4 | |
| #define ECCW1_FLIP_CTRL 0x10AC | |
| #define ECCW1_FLIP0_OFFSET 0x10B0 | |
| #define ECCW1_FLIP1_OFFSET 0x10B4 | |
| #define ECCR0_CERR_STAT_OFFSET 0x10BC | |
| #define ECCR0_CE_ADDR_LO_OFFSET 0x10C0 | |
| #define ECCR0_CE_ADDR_HI_OFFSET 0x10C4 | |
| #define ECCR0_CE_DATA_LO_OFFSET 0x10C8 | |
| #define ECCR0_CE_DATA_HI_OFFSET 0x10CC | |
| #define ECCR0_CE_DATA_PAR_OFFSET 0x10D0 | |
| #define ECCR0_UERR_STAT_OFFSET 0x10D4 | |
| #define ECCR0_UE_ADDR_LO_OFFSET 0x10D8 | |
| #define ECCR0_UE_ADDR_HI_OFFSET 0x10DC | |
| #define ECCR0_UE_DATA_LO_OFFSET 0x10E0 | |
| #define ECCR0_UE_DATA_HI_OFFSET 0x10E4 | |
| #define ECCR0_UE_DATA_PAR_OFFSET 0x10E8 | |
| #define ECCR1_CERR_STAT_OFFSET 0x10F4 | |
| #define ECCR1_CE_ADDR_LO_OFFSET 0x10F8 | |
| #define ECCR1_CE_ADDR_HI_OFFSET 0x10FC | |
| #define ECCR1_CE_DATA_LO_OFFSET 0x1100 | |
| #define ECCR1_CE_DATA_HI_OFFSET 0x110C | |
| #define ECCR1_CE_DATA_PAR_OFFSET 0x1108 | |
| #define ECCR1_UERR_STAT_OFFSET 0x110C | |
| #define ECCR1_UE_ADDR_LO_OFFSET 0x1110 | |
| #define ECCR1_UE_ADDR_HI_OFFSET 0x1114 | |
| #define ECCR1_UE_DATA_LO_OFFSET 0x1118 | |
| #define ECCR1_UE_DATA_HI_OFFSET 0x111C | |
| #define ECCR1_UE_DATA_PAR_OFFSET 0x1120 | |
| #define XDDR_NOC_REG_ADEC4_OFFSET 0x44 | |
| #define RANK_1_MASK GENMASK(11, 6) | |
| #define LRANK_0_MASK GENMASK(17, 12) | |
| #define LRANK_1_MASK GENMASK(23, 18) | |
| #define MASK_24 GENMASK(29, 24) | |
| #define XDDR_NOC_REG_ADEC5_OFFSET 0x48 | |
| #define XDDR_NOC_REG_ADEC6_OFFSET 0x4C | |
| #define XDDR_NOC_REG_ADEC7_OFFSET 0x50 | |
| #define XDDR_NOC_REG_ADEC8_OFFSET 0x54 | |
| #define XDDR_NOC_REG_ADEC9_OFFSET 0x58 | |
| #define XDDR_NOC_REG_ADEC10_OFFSET 0x5C | |
| #define XDDR_NOC_REG_ADEC11_OFFSET 0x60 | |
| #define MASK_0 GENMASK(5, 0) | |
| #define GRP_0_MASK GENMASK(11, 6) | |
| #define GRP_1_MASK GENMASK(17, 12) | |
| #define CH_0_MASK GENMASK(23, 18) | |
| #define XDDR_NOC_REG_ADEC12_OFFSET 0x71C | |
| #define XDDR_NOC_REG_ADEC13_OFFSET 0x720 | |
| #define XDDR_NOC_REG_ADEC14_OFFSET 0x724 | |
| #define XDDR_NOC_ROW_MATCH_MASK GENMASK(17, 0) | |
| #define XDDR_NOC_COL_MATCH_MASK GENMASK(27, 18) | |
| #define XDDR_NOC_BANK_MATCH_MASK GENMASK(29, 28) | |
| #define XDDR_NOC_GRP_MATCH_MASK GENMASK(31, 30) | |
| #define XDDR_NOC_REG_ADEC15_OFFSET 0x728 | |
| #define XDDR_NOC_RANK_MATCH_MASK GENMASK(1, 0) | |
| #define XDDR_NOC_LRANK_MATCH_MASK GENMASK(4, 2) | |
| #define XDDR_NOC_CH_MATCH_MASK BIT(5) | |
| #define XDDR_NOC_MOD_SEL_MASK BIT(6) | |
| #define XDDR_NOC_MATCH_EN_MASK BIT(8) | |
| #define ECCR_UE_CE_ADDR_HI_ROW_MASK GENMASK(7, 0) | |
| #define XDDR_EDAC_NR_CSROWS 1 | |
| #define XDDR_EDAC_NR_CHANS 1 | |
| #define XDDR_BUS_WIDTH_64 0 | |
| #define XDDR_BUS_WIDTH_32 1 | |
| #define XDDR_BUS_WIDTH_16 2 | |
| #define XDDR_MAX_ROW_CNT 18 | |
| #define XDDR_MAX_COL_CNT 10 | |
| #define XDDR_MAX_RANK_CNT 2 | |
| #define XDDR_MAX_LRANK_CNT 3 | |
| #define XDDR_MAX_BANK_CNT 2 | |
| #define XDDR_MAX_GRP_CNT 2 | |
| /* | |
| * Config and system registers are usually locked. This is the | |
| * code which unlocks them in order to accept writes. See | |
| * | |
| * https://docs.xilinx.com/r/en-US/am012-versal-register-reference/PCSR_LOCK-XRAM_SLCR-Register | |
| */ | |
| #define PCSR_UNLOCK_VAL 0xF9E8D7C6 | |
| #define PCSR_LOCK_VAL 1 | |
| #define XDDR_ERR_TYPE_CE 0 | |
| #define XDDR_ERR_TYPE_UE 1 | |
| #define XILINX_DRAM_SIZE_4G 0 | |
| #define XILINX_DRAM_SIZE_6G 1 | |
| #define XILINX_DRAM_SIZE_8G 2 | |
| #define XILINX_DRAM_SIZE_12G 3 | |
| #define XILINX_DRAM_SIZE_16G 4 | |
| #define XILINX_DRAM_SIZE_32G 5 | |
| #define NUM_UE_BITPOS 2 | |
| /** | |
| * struct ecc_error_info - ECC error log information. | |
| * @burstpos: Burst position. | |
| * @lrank: Logical Rank number. | |
| * @rank: Rank number. | |
| * @group: Group number. | |
| * @bank: Bank number. | |
| * @col: Column number. | |
| * @row: Row number. | |
| * @rowhi: Row number higher bits. | |
| * @i: ECC error info. | |
| */ | |
| union ecc_error_info { | |
| struct { | |
| u32 burstpos:3; | |
| u32 lrank:3; | |
| u32 rank:2; | |
| u32 group:2; | |
| u32 bank:2; | |
| u32 col:10; | |
| u32 row:10; | |
| u32 rowhi; | |
| }; | |
| u64 i; | |
| } __packed; | |
| union edac_info { | |
| struct { | |
| u32 row0:6; | |
| u32 row1:6; | |
| u32 row2:6; | |
| u32 row3:6; | |
| u32 row4:6; | |
| u32 reserved:2; | |
| }; | |
| struct { | |
| u32 col1:6; | |
| u32 col2:6; | |
| u32 col3:6; | |
| u32 col4:6; | |
| u32 col5:6; | |
| u32 reservedcol:2; | |
| }; | |
| u32 i; | |
| } __packed; | |
| /** | |
| * struct ecc_status - ECC status information to report. | |
| * @ceinfo: Correctable error log information. | |
| * @ueinfo: Uncorrectable error log information. | |
| * @channel: Channel number. | |
| * @error_type: Error type information. | |
| */ | |
| struct ecc_status { | |
| union ecc_error_info ceinfo[2]; | |
| union ecc_error_info ueinfo[2]; | |
| u8 channel; | |
| u8 error_type; | |
| }; | |
| /** | |
| * struct edac_priv - DDR memory controller private instance data. | |
| * @ddrmc_baseaddr: Base address of the DDR controller. | |
| * @ddrmc_noc_baseaddr: Base address of the DDRMC NOC. | |
| * @message: Buffer for framing the event specific info. | |
| * @mc_id: Memory controller ID. | |
| * @ce_cnt: Correctable error count. | |
| * @ue_cnt: UnCorrectable error count. | |
| * @stat: ECC status information. | |
| * @lrank_bit: Bit shifts for lrank bit. | |
| * @rank_bit: Bit shifts for rank bit. | |
| * @row_bit: Bit shifts for row bit. | |
| * @col_bit: Bit shifts for column bit. | |
| * @bank_bit: Bit shifts for bank bit. | |
| * @grp_bit: Bit shifts for group bit. | |
| * @ch_bit: Bit shifts for channel bit. | |
| * @err_inject_addr: Data poison address. | |
| * @debugfs: Debugfs handle. | |
| */ | |
| struct edac_priv { | |
| void __iomem *ddrmc_baseaddr; | |
| void __iomem *ddrmc_noc_baseaddr; | |
| char message[XDDR_EDAC_MSG_SIZE]; | |
| u32 mc_id; | |
| u32 ce_cnt; | |
| u32 ue_cnt; | |
| struct ecc_status stat; | |
| u32 lrank_bit[3]; | |
| u32 rank_bit[2]; | |
| u32 row_bit[18]; | |
| u32 col_bit[10]; | |
| u32 bank_bit[2]; | |
| u32 grp_bit[2]; | |
| u32 ch_bit; | |
| #ifdef CONFIG_EDAC_DEBUG | |
| u64 err_inject_addr; | |
| struct dentry *debugfs; | |
| #endif | |
| }; | |
| static void get_ce_error_info(struct edac_priv *priv) | |
| { | |
| void __iomem *ddrmc_base; | |
| struct ecc_status *p; | |
| u32 regval; | |
| u64 reghi; | |
| ddrmc_base = priv->ddrmc_baseaddr; | |
| p = &priv->stat; | |
| p->error_type = XDDR_ERR_TYPE_CE; | |
| regval = readl(ddrmc_base + ECCR0_CE_ADDR_LO_OFFSET); | |
| reghi = regval & ECCR_UE_CE_ADDR_HI_ROW_MASK; | |
| p->ceinfo[0].i = regval | reghi << 32; | |
| regval = readl(ddrmc_base + ECCR0_CE_ADDR_HI_OFFSET); | |
| edac_dbg(2, "ERR DATA: 0x%08X%08X ERR DATA PARITY: 0x%08X\n", | |
| readl(ddrmc_base + ECCR0_CE_DATA_LO_OFFSET), | |
| readl(ddrmc_base + ECCR0_CE_DATA_HI_OFFSET), | |
| readl(ddrmc_base + ECCR0_CE_DATA_PAR_OFFSET)); | |
| regval = readl(ddrmc_base + ECCR1_CE_ADDR_LO_OFFSET); | |
| reghi = readl(ddrmc_base + ECCR1_CE_ADDR_HI_OFFSET); | |
| p->ceinfo[1].i = regval | reghi << 32; | |
| regval = readl(ddrmc_base + ECCR1_CE_ADDR_HI_OFFSET); | |
| edac_dbg(2, "ERR DATA: 0x%08X%08X ERR DATA PARITY: 0x%08X\n", | |
| readl(ddrmc_base + ECCR1_CE_DATA_LO_OFFSET), | |
| readl(ddrmc_base + ECCR1_CE_DATA_HI_OFFSET), | |
| readl(ddrmc_base + ECCR1_CE_DATA_PAR_OFFSET)); | |
| } | |
| static void get_ue_error_info(struct edac_priv *priv) | |
| { | |
| void __iomem *ddrmc_base; | |
| struct ecc_status *p; | |
| u32 regval; | |
| u64 reghi; | |
| ddrmc_base = priv->ddrmc_baseaddr; | |
| p = &priv->stat; | |
| p->error_type = XDDR_ERR_TYPE_UE; | |
| regval = readl(ddrmc_base + ECCR0_UE_ADDR_LO_OFFSET); | |
| reghi = readl(ddrmc_base + ECCR0_UE_ADDR_HI_OFFSET); | |
| p->ueinfo[0].i = regval | reghi << 32; | |
| regval = readl(ddrmc_base + ECCR0_UE_ADDR_HI_OFFSET); | |
| edac_dbg(2, "ERR DATA: 0x%08X%08X ERR DATA PARITY: 0x%08X\n", | |
| readl(ddrmc_base + ECCR0_UE_DATA_LO_OFFSET), | |
| readl(ddrmc_base + ECCR0_UE_DATA_HI_OFFSET), | |
| readl(ddrmc_base + ECCR0_UE_DATA_PAR_OFFSET)); | |
| regval = readl(ddrmc_base + ECCR1_UE_ADDR_LO_OFFSET); | |
| reghi = readl(ddrmc_base + ECCR1_UE_ADDR_HI_OFFSET); | |
| p->ueinfo[1].i = regval | reghi << 32; | |
| edac_dbg(2, "ERR DATA: 0x%08X%08X ERR DATA PARITY: 0x%08X\n", | |
| readl(ddrmc_base + ECCR1_UE_DATA_LO_OFFSET), | |
| readl(ddrmc_base + ECCR1_UE_DATA_HI_OFFSET), | |
| readl(ddrmc_base + ECCR1_UE_DATA_PAR_OFFSET)); | |
| } | |
| static bool get_error_info(struct edac_priv *priv) | |
| { | |
| u32 eccr0_ceval, eccr1_ceval, eccr0_ueval, eccr1_ueval; | |
| void __iomem *ddrmc_base; | |
| struct ecc_status *p; | |
| ddrmc_base = priv->ddrmc_baseaddr; | |
| p = &priv->stat; | |
| eccr0_ceval = readl(ddrmc_base + ECCR0_CERR_STAT_OFFSET); | |
| eccr1_ceval = readl(ddrmc_base + ECCR1_CERR_STAT_OFFSET); | |
| eccr0_ueval = readl(ddrmc_base + ECCR0_UERR_STAT_OFFSET); | |
| eccr1_ueval = readl(ddrmc_base + ECCR1_UERR_STAT_OFFSET); | |
| if (!eccr0_ceval && !eccr1_ceval && !eccr0_ueval && !eccr1_ueval) | |
| return 1; | |
| if (!eccr0_ceval) | |
| p->channel = 1; | |
| else | |
| p->channel = 0; | |
| if (eccr0_ceval || eccr1_ceval) | |
| get_ce_error_info(priv); | |
| if (eccr0_ueval || eccr1_ueval) { | |
| if (!eccr0_ueval) | |
| p->channel = 1; | |
| else | |
| p->channel = 0; | |
| get_ue_error_info(priv); | |
| } | |
| /* Unlock the PCSR registers */ | |
| writel(PCSR_UNLOCK_VAL, ddrmc_base + XDDR_PCSR_OFFSET); | |
| writel(0, ddrmc_base + ECCR0_CERR_STAT_OFFSET); | |
| writel(0, ddrmc_base + ECCR1_CERR_STAT_OFFSET); | |
| writel(0, ddrmc_base + ECCR0_UERR_STAT_OFFSET); | |
| writel(0, ddrmc_base + ECCR1_UERR_STAT_OFFSET); | |
| /* Lock the PCSR registers */ | |
| writel(1, ddrmc_base + XDDR_PCSR_OFFSET); | |
| return 0; | |
| } | |
| /** | |
| * convert_to_physical - Convert to physical address. | |
| * @priv: DDR memory controller private instance data. | |
| * @pinf: ECC error info structure. | |
| * | |
| * Return: Physical address of the DDR memory. | |
| */ | |
| static unsigned long convert_to_physical(struct edac_priv *priv, union ecc_error_info pinf) | |
| { | |
| unsigned long err_addr = 0; | |
| u32 index; | |
| u32 row; | |
| row = pinf.rowhi << 10 | pinf.row; | |
| for (index = 0; index < XDDR_MAX_ROW_CNT; index++) { | |
| err_addr |= (row & BIT(0)) << priv->row_bit[index]; | |
| row >>= 1; | |
| } | |
| for (index = 0; index < XDDR_MAX_COL_CNT; index++) { | |
| err_addr |= (pinf.col & BIT(0)) << priv->col_bit[index]; | |
| pinf.col >>= 1; | |
| } | |
| for (index = 0; index < XDDR_MAX_BANK_CNT; index++) { | |
| err_addr |= (pinf.bank & BIT(0)) << priv->bank_bit[index]; | |
| pinf.bank >>= 1; | |
| } | |
| for (index = 0; index < XDDR_MAX_GRP_CNT; index++) { | |
| err_addr |= (pinf.group & BIT(0)) << priv->grp_bit[index]; | |
| pinf.group >>= 1; | |
| } | |
| for (index = 0; index < XDDR_MAX_RANK_CNT; index++) { | |
| err_addr |= (pinf.rank & BIT(0)) << priv->rank_bit[index]; | |
| pinf.rank >>= 1; | |
| } | |
| for (index = 0; index < XDDR_MAX_LRANK_CNT; index++) { | |
| err_addr |= (pinf.lrank & BIT(0)) << priv->lrank_bit[index]; | |
| pinf.lrank >>= 1; | |
| } | |
| err_addr |= (priv->stat.channel & BIT(0)) << priv->ch_bit; | |
| return err_addr; | |
| } | |
| /** | |
| * handle_error - Handle Correctable and Uncorrectable errors. | |
| * @mci: EDAC memory controller instance. | |
| * @stat: ECC status structure. | |
| * | |
| * Handles ECC correctable and uncorrectable errors. | |
| */ | |
| static void handle_error(struct mem_ctl_info *mci, struct ecc_status *stat) | |
| { | |
| struct edac_priv *priv = mci->pvt_info; | |
| union ecc_error_info pinf; | |
| if (stat->error_type == XDDR_ERR_TYPE_CE) { | |
| priv->ce_cnt++; | |
| pinf = stat->ceinfo[stat->channel]; | |
| snprintf(priv->message, XDDR_EDAC_MSG_SIZE, | |
| "Error type:%s MC ID: %d Addr at %lx Burst Pos: %d\n", | |
| "CE", priv->mc_id, | |
| convert_to_physical(priv, pinf), pinf.burstpos); | |
| edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, | |
| 1, 0, 0, 0, 0, 0, -1, | |
| priv->message, ""); | |
| } | |
| if (stat->error_type == XDDR_ERR_TYPE_UE) { | |
| priv->ue_cnt++; | |
| pinf = stat->ueinfo[stat->channel]; | |
| snprintf(priv->message, XDDR_EDAC_MSG_SIZE, | |
| "Error type:%s MC ID: %d Addr at %lx Burst Pos: %d\n", | |
| "UE", priv->mc_id, | |
| convert_to_physical(priv, pinf), pinf.burstpos); | |
| edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, | |
| 1, 0, 0, 0, 0, 0, -1, | |
| priv->message, ""); | |
| } | |
| memset(stat, 0, sizeof(*stat)); | |
| } | |
| /** | |
| * err_callback - Handle Correctable and Uncorrectable errors. | |
| * @payload: payload data. | |
| * @data: mci controller data. | |
| * | |
| * Handles ECC correctable and uncorrectable errors. | |
| */ | |
| static void err_callback(const u32 *payload, void *data) | |
| { | |
| struct mem_ctl_info *mci = (struct mem_ctl_info *)data; | |
| struct edac_priv *priv; | |
| struct ecc_status *p; | |
| int regval; | |
| priv = mci->pvt_info; | |
| p = &priv->stat; | |
| regval = readl(priv->ddrmc_baseaddr + XDDR_ISR_OFFSET); | |
| if (payload[EVENT] == XPM_EVENT_ERROR_MASK_DDRMC_CR) | |
| p->error_type = XDDR_ERR_TYPE_CE; | |
| if (payload[EVENT] == XPM_EVENT_ERROR_MASK_DDRMC_NCR) | |
| p->error_type = XDDR_ERR_TYPE_UE; | |
| if (get_error_info(priv)) | |
| return; | |
| handle_error(mci, &priv->stat); | |
| /* Unlock the PCSR registers */ | |
| writel(PCSR_UNLOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| /* Clear the ISR */ | |
| writel(regval, priv->ddrmc_baseaddr + XDDR_ISR_OFFSET); | |
| /* Lock the PCSR registers */ | |
| writel(PCSR_LOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| edac_dbg(3, "Total error count CE %d UE %d\n", | |
| priv->ce_cnt, priv->ue_cnt); | |
| } | |
| /** | |
| * get_dwidth - Return the controller memory width. | |
| * @base: DDR memory controller base address. | |
| * | |
| * Get the EDAC device type width appropriate for the controller | |
| * configuration. | |
| * | |
| * Return: a device type width enumeration. | |
| */ | |
| static enum dev_type get_dwidth(const void __iomem *base) | |
| { | |
| enum dev_type dt; | |
| u32 regval; | |
| u32 width; | |
| regval = readl(base + XDDR_REG_CONFIG0_OFFSET); | |
| width = FIELD_GET(XDDR_REG_CONFIG0_BUS_WIDTH_MASK, regval); | |
| switch (width) { | |
| case XDDR_BUS_WIDTH_16: | |
| dt = DEV_X2; | |
| break; | |
| case XDDR_BUS_WIDTH_32: | |
| dt = DEV_X4; | |
| break; | |
| case XDDR_BUS_WIDTH_64: | |
| dt = DEV_X8; | |
| break; | |
| default: | |
| dt = DEV_UNKNOWN; | |
| } | |
| return dt; | |
| } | |
| /** | |
| * get_ecc_state - Return the controller ECC enable/disable status. | |
| * @base: DDR memory controller base address. | |
| * | |
| * Get the ECC enable/disable status for the controller. | |
| * | |
| * Return: a ECC status boolean i.e true/false - enabled/disabled. | |
| */ | |
| static bool get_ecc_state(void __iomem *base) | |
| { | |
| enum dev_type dt; | |
| u32 ecctype; | |
| dt = get_dwidth(base); | |
| if (dt == DEV_UNKNOWN) | |
| return false; | |
| ecctype = readl(base + XDDR_REG_PINOUT_OFFSET); | |
| ecctype &= XDDR_REG_PINOUT_ECC_EN_MASK; | |
| return !!ecctype; | |
| } | |
| /** | |
| * get_memsize - Get the size of the attached memory device. | |
| * @priv: DDR memory controller private instance data. | |
| * | |
| * Return: the memory size in bytes. | |
| */ | |
| static u64 get_memsize(struct edac_priv *priv) | |
| { | |
| u32 regval; | |
| u64 size; | |
| regval = readl(priv->ddrmc_baseaddr + XDDR_REG_CONFIG0_OFFSET); | |
| regval = FIELD_GET(XDDR_REG_CONFIG0_SIZE_MASK, regval); | |
| switch (regval) { | |
| case XILINX_DRAM_SIZE_4G: | |
| size = 4U; break; | |
| case XILINX_DRAM_SIZE_6G: | |
| size = 6U; break; | |
| case XILINX_DRAM_SIZE_8G: | |
| size = 8U; break; | |
| case XILINX_DRAM_SIZE_12G: | |
| size = 12U; break; | |
| case XILINX_DRAM_SIZE_16G: | |
| size = 16U; break; | |
| case XILINX_DRAM_SIZE_32G: | |
| size = 32U; break; | |
| /* Invalid configuration */ | |
| default: | |
| size = 0; break; | |
| } | |
| size *= SZ_1G; | |
| return size; | |
| } | |
| /** | |
| * init_csrows - Initialize the csrow data. | |
| * @mci: EDAC memory controller instance. | |
| * | |
| * Initialize the chip select rows associated with the EDAC memory | |
| * controller instance. | |
| */ | |
| static void init_csrows(struct mem_ctl_info *mci) | |
| { | |
| struct edac_priv *priv = mci->pvt_info; | |
| struct csrow_info *csi; | |
| struct dimm_info *dimm; | |
| unsigned long size; | |
| u32 row; | |
| int ch; | |
| size = get_memsize(priv); | |
| for (row = 0; row < mci->nr_csrows; row++) { | |
| csi = mci->csrows[row]; | |
| for (ch = 0; ch < csi->nr_channels; ch++) { | |
| dimm = csi->channels[ch]->dimm; | |
| dimm->edac_mode = EDAC_SECDED; | |
| dimm->mtype = MEM_DDR4; | |
| dimm->nr_pages = (size >> PAGE_SHIFT) / csi->nr_channels; | |
| dimm->grain = XDDR_EDAC_ERR_GRAIN; | |
| dimm->dtype = get_dwidth(priv->ddrmc_baseaddr); | |
| } | |
| } | |
| } | |
| /** | |
| * mc_init - Initialize one driver instance. | |
| * @mci: EDAC memory controller instance. | |
| * @pdev: platform device. | |
| * | |
| * Perform initialization of the EDAC memory controller instance and | |
| * related driver-private data associated with the memory controller the | |
| * instance is bound to. | |
| */ | |
| static void mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) | |
| { | |
| mci->pdev = &pdev->dev; | |
| platform_set_drvdata(pdev, mci); | |
| /* Initialize controller capabilities and configuration */ | |
| mci->mtype_cap = MEM_FLAG_DDR4; | |
| mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; | |
| mci->scrub_cap = SCRUB_HW_SRC; | |
| mci->scrub_mode = SCRUB_NONE; | |
| mci->edac_cap = EDAC_FLAG_SECDED; | |
| mci->ctl_name = "xlnx_ddr_controller"; | |
| mci->dev_name = dev_name(&pdev->dev); | |
| mci->mod_name = "xlnx_edac"; | |
| edac_op_state = EDAC_OPSTATE_INT; | |
| init_csrows(mci); | |
| } | |
| static void enable_intr(struct edac_priv *priv) | |
| { | |
| /* Unlock the PCSR registers */ | |
| writel(PCSR_UNLOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| /* Enable UE and CE Interrupts to support the interrupt case */ | |
| writel(XDDR_IRQ_CE_MASK | XDDR_IRQ_UE_MASK, | |
| priv->ddrmc_baseaddr + XDDR_IRQ_EN_OFFSET); | |
| writel(XDDR_IRQ_UE_MASK, | |
| priv->ddrmc_baseaddr + XDDR_IRQ1_EN_OFFSET); | |
| /* Lock the PCSR registers */ | |
| writel(PCSR_LOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| } | |
| static void disable_intr(struct edac_priv *priv) | |
| { | |
| /* Unlock the PCSR registers */ | |
| writel(PCSR_UNLOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| /* Disable UE/CE Interrupts */ | |
| writel(XDDR_IRQ_CE_MASK | XDDR_IRQ_UE_MASK, | |
| priv->ddrmc_baseaddr + XDDR_IRQ_DIS_OFFSET); | |
| /* Lock the PCSR registers */ | |
| writel(PCSR_LOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| } | |
| #define to_mci(k) container_of(k, struct mem_ctl_info, dev) | |
| #ifdef CONFIG_EDAC_DEBUG | |
| /** | |
| * poison_setup - Update poison registers. | |
| * @priv: DDR memory controller private instance data. | |
| * | |
| * Update poison registers as per DDR mapping upon write of the address | |
| * location the fault is injected. | |
| * Return: none. | |
| */ | |
| static void poison_setup(struct edac_priv *priv) | |
| { | |
| u32 col = 0, row = 0, bank = 0, grp = 0, rank = 0, lrank = 0, ch = 0; | |
| u32 index, regval; | |
| for (index = 0; index < XDDR_MAX_ROW_CNT; index++) { | |
| row |= (((priv->err_inject_addr >> priv->row_bit[index]) & | |
| BIT(0)) << index); | |
| } | |
| for (index = 0; index < XDDR_MAX_COL_CNT; index++) { | |
| col |= (((priv->err_inject_addr >> priv->col_bit[index]) & | |
| BIT(0)) << index); | |
| } | |
| for (index = 0; index < XDDR_MAX_BANK_CNT; index++) { | |
| bank |= (((priv->err_inject_addr >> priv->bank_bit[index]) & | |
| BIT(0)) << index); | |
| } | |
| for (index = 0; index < XDDR_MAX_GRP_CNT; index++) { | |
| grp |= (((priv->err_inject_addr >> priv->grp_bit[index]) & | |
| BIT(0)) << index); | |
| } | |
| for (index = 0; index < XDDR_MAX_RANK_CNT; index++) { | |
| rank |= (((priv->err_inject_addr >> priv->rank_bit[index]) & | |
| BIT(0)) << index); | |
| } | |
| for (index = 0; index < XDDR_MAX_LRANK_CNT; index++) { | |
| lrank |= (((priv->err_inject_addr >> priv->lrank_bit[index]) & | |
| BIT(0)) << index); | |
| } | |
| ch = (priv->err_inject_addr >> priv->ch_bit) & BIT(0); | |
| if (ch) | |
| writel(0xFF, priv->ddrmc_baseaddr + ECCW1_FLIP_CTRL); | |
| else | |
| writel(0xFF, priv->ddrmc_baseaddr + ECCW0_FLIP_CTRL); | |
| writel(0, priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC12_OFFSET); | |
| writel(0, priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC13_OFFSET); | |
| regval = row & XDDR_NOC_ROW_MATCH_MASK; | |
| regval |= FIELD_PREP(XDDR_NOC_COL_MATCH_MASK, col); | |
| regval |= FIELD_PREP(XDDR_NOC_BANK_MATCH_MASK, bank); | |
| regval |= FIELD_PREP(XDDR_NOC_GRP_MATCH_MASK, grp); | |
| writel(regval, priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC14_OFFSET); | |
| regval = rank & XDDR_NOC_RANK_MATCH_MASK; | |
| regval |= FIELD_PREP(XDDR_NOC_LRANK_MATCH_MASK, lrank); | |
| regval |= FIELD_PREP(XDDR_NOC_CH_MATCH_MASK, ch); | |
| regval |= (XDDR_NOC_MOD_SEL_MASK | XDDR_NOC_MATCH_EN_MASK); | |
| writel(regval, priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC15_OFFSET); | |
| } | |
| static void xddr_inject_data_ce_store(struct mem_ctl_info *mci, u8 ce_bitpos) | |
| { | |
| u32 ecc0_flip0, ecc1_flip0, ecc0_flip1, ecc1_flip1; | |
| struct edac_priv *priv = mci->pvt_info; | |
| if (ce_bitpos < ECCW0_FLIP0_BITS) { | |
| ecc0_flip0 = BIT(ce_bitpos); | |
| ecc1_flip0 = BIT(ce_bitpos); | |
| ecc0_flip1 = 0; | |
| ecc1_flip1 = 0; | |
| } else { | |
| ce_bitpos = ce_bitpos - ECCW0_FLIP0_BITS; | |
| ecc0_flip1 = BIT(ce_bitpos); | |
| ecc1_flip1 = BIT(ce_bitpos); | |
| ecc0_flip0 = 0; | |
| ecc1_flip0 = 0; | |
| } | |
| writel(ecc0_flip0, priv->ddrmc_baseaddr + ECCW0_FLIP0_OFFSET); | |
| writel(ecc1_flip0, priv->ddrmc_baseaddr + ECCW1_FLIP0_OFFSET); | |
| writel(ecc0_flip1, priv->ddrmc_baseaddr + ECCW0_FLIP1_OFFSET); | |
| writel(ecc1_flip1, priv->ddrmc_baseaddr + ECCW1_FLIP1_OFFSET); | |
| } | |
| /* | |
| * To inject a correctable error, the following steps are needed: | |
| * | |
| * - Write the correctable error bit position value: | |
| * echo <bit_pos val> > /sys/kernel/debug/edac/<controller instance>/inject_ce | |
| * | |
| * poison_setup() derives the row, column, bank, group and rank and | |
| * writes to the ADEC registers based on the address given by the user. | |
| * | |
| * The ADEC12 and ADEC13 are mask registers; write 0 to make sure default | |
| * configuration is there and no addresses are masked. | |
| * | |
| * The row, column, bank, group and rank registers are written to the | |
| * match ADEC bit to generate errors at the particular address. ADEC14 | |
| * and ADEC15 have the match bits. | |
| * | |
| * xddr_inject_data_ce_store() updates the ECC FLIP registers with the | |
| * bits to be corrupted based on the bit position given by the user. | |
| * | |
| * Upon doing a read to the address the errors are injected. | |
| */ | |
| static ssize_t inject_data_ce_store(struct file *file, const char __user *data, | |
| size_t count, loff_t *ppos) | |
| { | |
| struct device *dev = file->private_data; | |
| struct mem_ctl_info *mci = to_mci(dev); | |
| struct edac_priv *priv = mci->pvt_info; | |
| u8 ce_bitpos; | |
| int ret; | |
| ret = kstrtou8_from_user(data, count, 0, &ce_bitpos); | |
| if (ret) | |
| return ret; | |
| /* Unlock the PCSR registers */ | |
| writel(PCSR_UNLOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| writel(PCSR_UNLOCK_VAL, priv->ddrmc_noc_baseaddr + XDDR_PCSR_OFFSET); | |
| poison_setup(priv); | |
| xddr_inject_data_ce_store(mci, ce_bitpos); | |
| ret = count; | |
| /* Lock the PCSR registers */ | |
| writel(PCSR_LOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| writel(PCSR_LOCK_VAL, priv->ddrmc_noc_baseaddr + XDDR_PCSR_OFFSET); | |
| return ret; | |
| } | |
| static const struct file_operations xddr_inject_ce_fops = { | |
| .open = simple_open, | |
| .write = inject_data_ce_store, | |
| .llseek = generic_file_llseek, | |
| }; | |
| static void xddr_inject_data_ue_store(struct mem_ctl_info *mci, u32 val0, u32 val1) | |
| { | |
| struct edac_priv *priv = mci->pvt_info; | |
| writel(val0, priv->ddrmc_baseaddr + ECCW0_FLIP0_OFFSET); | |
| writel(val0, priv->ddrmc_baseaddr + ECCW0_FLIP1_OFFSET); | |
| writel(val1, priv->ddrmc_baseaddr + ECCW1_FLIP1_OFFSET); | |
| writel(val1, priv->ddrmc_baseaddr + ECCW1_FLIP1_OFFSET); | |
| } | |
| /* | |
| * To inject an uncorrectable error, the following steps are needed: | |
| * echo <bit_pos val> > /sys/kernel/debug/edac/<controller instance>/inject_ue | |
| * | |
| * poison_setup() derives the row, column, bank, group and rank and | |
| * writes to the ADEC registers based on the address given by the user. | |
| * | |
| * The ADEC12 and ADEC13 are mask registers; write 0 so that none of the | |
| * addresses are masked. The row, column, bank, group and rank registers | |
| * are written to the match ADEC bit to generate errors at the | |
| * particular address. ADEC14 and ADEC15 have the match bits. | |
| * | |
| * xddr_inject_data_ue_store() updates the ECC FLIP registers with the | |
| * bits to be corrupted based on the bit position given by the user. For | |
| * uncorrectable errors | |
| * 2 bit errors are injected. | |
| * | |
| * Upon doing a read to the address the errors are injected. | |
| */ | |
| static ssize_t inject_data_ue_store(struct file *file, const char __user *data, | |
| size_t count, loff_t *ppos) | |
| { | |
| struct device *dev = file->private_data; | |
| struct mem_ctl_info *mci = to_mci(dev); | |
| struct edac_priv *priv = mci->pvt_info; | |
| char buf[6], *pbuf, *token[2]; | |
| u32 val0 = 0, val1 = 0; | |
| u8 len, ue0, ue1; | |
| int i, ret; | |
| len = min_t(size_t, count, sizeof(buf)); | |
| if (copy_from_user(buf, data, len)) | |
| return -EFAULT; | |
| buf[len] = '\0'; | |
| pbuf = &buf[0]; | |
| for (i = 0; i < NUM_UE_BITPOS; i++) | |
| token[i] = strsep(&pbuf, ","); | |
| if (!token[0] || !token[1]) | |
| return -EFAULT; | |
| ret = kstrtou8(token[0], 0, &ue0); | |
| if (ret) | |
| return ret; | |
| ret = kstrtou8(token[1], 0, &ue1); | |
| if (ret) | |
| return ret; | |
| if (ue0 < ECCW0_FLIP0_BITS) { | |
| val0 = BIT(ue0); | |
| } else { | |
| ue0 = ue0 - ECCW0_FLIP0_BITS; | |
| val1 = BIT(ue0); | |
| } | |
| if (ue1 < ECCW0_FLIP0_BITS) { | |
| val0 |= BIT(ue1); | |
| } else { | |
| ue1 = ue1 - ECCW0_FLIP0_BITS; | |
| val1 |= BIT(ue1); | |
| } | |
| /* Unlock the PCSR registers */ | |
| writel(PCSR_UNLOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| writel(PCSR_UNLOCK_VAL, priv->ddrmc_noc_baseaddr + XDDR_PCSR_OFFSET); | |
| poison_setup(priv); | |
| xddr_inject_data_ue_store(mci, val0, val1); | |
| /* Lock the PCSR registers */ | |
| writel(PCSR_LOCK_VAL, priv->ddrmc_noc_baseaddr + XDDR_PCSR_OFFSET); | |
| writel(PCSR_LOCK_VAL, priv->ddrmc_baseaddr + XDDR_PCSR_OFFSET); | |
| return count; | |
| } | |
| static const struct file_operations xddr_inject_ue_fops = { | |
| .open = simple_open, | |
| .write = inject_data_ue_store, | |
| .llseek = generic_file_llseek, | |
| }; | |
| static void create_debugfs_attributes(struct mem_ctl_info *mci) | |
| { | |
| struct edac_priv *priv = mci->pvt_info; | |
| priv->debugfs = edac_debugfs_create_dir(mci->dev_name); | |
| if (!priv->debugfs) | |
| return; | |
| if (!edac_debugfs_create_file("inject_ce", 0200, priv->debugfs, | |
| &mci->dev, &xddr_inject_ce_fops)) { | |
| debugfs_remove_recursive(priv->debugfs); | |
| return; | |
| } | |
| if (!edac_debugfs_create_file("inject_ue", 0200, priv->debugfs, | |
| &mci->dev, &xddr_inject_ue_fops)) { | |
| debugfs_remove_recursive(priv->debugfs); | |
| return; | |
| } | |
| debugfs_create_x64("address", 0600, priv->debugfs, | |
| &priv->err_inject_addr); | |
| mci->debugfs = priv->debugfs; | |
| } | |
| static inline void process_bit(struct edac_priv *priv, unsigned int start, u32 regval) | |
| { | |
| union edac_info rows; | |
| rows.i = regval; | |
| priv->row_bit[start] = rows.row0; | |
| priv->row_bit[start + 1] = rows.row1; | |
| priv->row_bit[start + 2] = rows.row2; | |
| priv->row_bit[start + 3] = rows.row3; | |
| priv->row_bit[start + 4] = rows.row4; | |
| } | |
| static void setup_row_address_map(struct edac_priv *priv) | |
| { | |
| u32 regval; | |
| union edac_info rows; | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC5_OFFSET); | |
| process_bit(priv, 0, regval); | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC6_OFFSET); | |
| process_bit(priv, 5, regval); | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC7_OFFSET); | |
| process_bit(priv, 10, regval); | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC8_OFFSET); | |
| rows.i = regval; | |
| priv->row_bit[15] = rows.row0; | |
| priv->row_bit[16] = rows.row1; | |
| priv->row_bit[17] = rows.row2; | |
| } | |
| static void setup_column_address_map(struct edac_priv *priv) | |
| { | |
| u32 regval; | |
| union edac_info cols; | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC8_OFFSET); | |
| priv->col_bit[0] = FIELD_GET(MASK_24, regval); | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC9_OFFSET); | |
| cols.i = regval; | |
| priv->col_bit[1] = cols.col1; | |
| priv->col_bit[2] = cols.col2; | |
| priv->col_bit[3] = cols.col3; | |
| priv->col_bit[4] = cols.col4; | |
| priv->col_bit[5] = cols.col5; | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC10_OFFSET); | |
| cols.i = regval; | |
| priv->col_bit[6] = cols.col1; | |
| priv->col_bit[7] = cols.col2; | |
| priv->col_bit[8] = cols.col3; | |
| priv->col_bit[9] = cols.col4; | |
| } | |
| static void setup_bank_grp_ch_address_map(struct edac_priv *priv) | |
| { | |
| u32 regval; | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC10_OFFSET); | |
| priv->bank_bit[0] = FIELD_GET(MASK_24, regval); | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC11_OFFSET); | |
| priv->bank_bit[1] = (regval & MASK_0); | |
| priv->grp_bit[0] = FIELD_GET(GRP_0_MASK, regval); | |
| priv->grp_bit[1] = FIELD_GET(GRP_1_MASK, regval); | |
| priv->ch_bit = FIELD_GET(CH_0_MASK, regval); | |
| } | |
| static void setup_rank_lrank_address_map(struct edac_priv *priv) | |
| { | |
| u32 regval; | |
| regval = readl(priv->ddrmc_noc_baseaddr + XDDR_NOC_REG_ADEC4_OFFSET); | |
| priv->rank_bit[0] = (regval & MASK_0); | |
| priv->rank_bit[1] = FIELD_GET(RANK_1_MASK, regval); | |
| priv->lrank_bit[0] = FIELD_GET(LRANK_0_MASK, regval); | |
| priv->lrank_bit[1] = FIELD_GET(LRANK_1_MASK, regval); | |
| priv->lrank_bit[2] = FIELD_GET(MASK_24, regval); | |
| } | |
| /** | |
| * setup_address_map - Set Address Map by querying ADDRMAP registers. | |
| * @priv: DDR memory controller private instance data. | |
| * | |
| * Set Address Map by querying ADDRMAP registers. | |
| * | |
| * Return: none. | |
| */ | |
| static void setup_address_map(struct edac_priv *priv) | |
| { | |
| setup_row_address_map(priv); | |
| setup_column_address_map(priv); | |
| setup_bank_grp_ch_address_map(priv); | |
| setup_rank_lrank_address_map(priv); | |
| } | |
| #endif /* CONFIG_EDAC_DEBUG */ | |
| static const struct of_device_id xlnx_edac_match[] = { | |
| { .compatible = "xlnx,versal-ddrmc", }, | |
| { | |
| /* end of table */ | |
| } | |
| }; | |
| MODULE_DEVICE_TABLE(of, xlnx_edac_match); | |
| static u32 emif_get_id(struct device_node *node) | |
| { | |
| u32 addr, my_addr, my_id = 0; | |
| struct device_node *np; | |
| const __be32 *addrp; | |
| addrp = of_get_address(node, 0, NULL, NULL); | |
| my_addr = (u32)of_translate_address(node, addrp); | |
| for_each_matching_node(np, xlnx_edac_match) { | |
| if (np == node) | |
| continue; | |
| addrp = of_get_address(np, 0, NULL, NULL); | |
| addr = (u32)of_translate_address(np, addrp); | |
| edac_printk(KERN_INFO, EDAC_MC, | |
| "addr=%x, my_addr=%x\n", | |
| addr, my_addr); | |
| if (addr < my_addr) | |
| my_id++; | |
| } | |
| return my_id; | |
| } | |
| static int mc_probe(struct platform_device *pdev) | |
| { | |
| void __iomem *ddrmc_baseaddr, *ddrmc_noc_baseaddr; | |
| struct edac_mc_layer layers[2]; | |
| struct mem_ctl_info *mci; | |
| u8 num_chans, num_csrows; | |
| struct edac_priv *priv; | |
| u32 edac_mc_id, regval; | |
| int rc; | |
| ddrmc_baseaddr = devm_platform_ioremap_resource_byname(pdev, "base"); | |
| if (IS_ERR(ddrmc_baseaddr)) | |
| return PTR_ERR(ddrmc_baseaddr); | |
| ddrmc_noc_baseaddr = devm_platform_ioremap_resource_byname(pdev, "noc"); | |
| if (IS_ERR(ddrmc_noc_baseaddr)) | |
| return PTR_ERR(ddrmc_noc_baseaddr); | |
| if (!get_ecc_state(ddrmc_baseaddr)) | |
| return -ENXIO; | |
| /* Allocate ID number for the EMIF controller */ | |
| edac_mc_id = emif_get_id(pdev->dev.of_node); | |
| regval = readl(ddrmc_baseaddr + XDDR_REG_CONFIG0_OFFSET); | |
| num_chans = FIELD_GET(XDDR_REG_CONFIG0_NUM_CHANS_MASK, regval); | |
| num_chans++; | |
| num_csrows = FIELD_GET(XDDR_REG_CONFIG0_NUM_RANKS_MASK, regval); | |
| num_csrows *= 2; | |
| if (!num_csrows) | |
| num_csrows = 1; | |
| layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; | |
| layers[0].size = num_csrows; | |
| layers[0].is_virt_csrow = true; | |
| layers[1].type = EDAC_MC_LAYER_CHANNEL; | |
| layers[1].size = num_chans; | |
| layers[1].is_virt_csrow = false; | |
| mci = edac_mc_alloc(edac_mc_id, ARRAY_SIZE(layers), layers, | |
| sizeof(struct edac_priv)); | |
| if (!mci) { | |
| edac_printk(KERN_ERR, EDAC_MC, | |
| "Failed memory allocation for mc instance\n"); | |
| return -ENOMEM; | |
| } | |
| priv = mci->pvt_info; | |
| priv->ddrmc_baseaddr = ddrmc_baseaddr; | |
| priv->ddrmc_noc_baseaddr = ddrmc_noc_baseaddr; | |
| priv->ce_cnt = 0; | |
| priv->ue_cnt = 0; | |
| priv->mc_id = edac_mc_id; | |
| mc_init(mci, pdev); | |
| rc = edac_mc_add_mc(mci); | |
| if (rc) { | |
| edac_printk(KERN_ERR, EDAC_MC, | |
| "Failed to register with EDAC core\n"); | |
| goto free_edac_mc; | |
| } | |
| rc = xlnx_register_event(PM_NOTIFY_CB, VERSAL_EVENT_ERROR_PMC_ERR1, | |
| XPM_EVENT_ERROR_MASK_DDRMC_CR | XPM_EVENT_ERROR_MASK_DDRMC_NCR, | |
| false, err_callback, mci); | |
| if (rc) { | |
| if (rc == -EACCES) | |
| rc = -EPROBE_DEFER; | |
| goto del_mc; | |
| } | |
| #ifdef CONFIG_EDAC_DEBUG | |
| create_debugfs_attributes(mci); | |
| setup_address_map(priv); | |
| #endif | |
| enable_intr(priv); | |
| return rc; | |
| del_mc: | |
| edac_mc_del_mc(&pdev->dev); | |
| free_edac_mc: | |
| edac_mc_free(mci); | |
| return rc; | |
| } | |
| static void mc_remove(struct platform_device *pdev) | |
| { | |
| struct mem_ctl_info *mci = platform_get_drvdata(pdev); | |
| struct edac_priv *priv = mci->pvt_info; | |
| disable_intr(priv); | |
| #ifdef CONFIG_EDAC_DEBUG | |
| debugfs_remove_recursive(priv->debugfs); | |
| #endif | |
| xlnx_unregister_event(PM_NOTIFY_CB, VERSAL_EVENT_ERROR_PMC_ERR1, | |
| XPM_EVENT_ERROR_MASK_DDRMC_CR | | |
| XPM_EVENT_ERROR_MASK_DDRMC_NCR, err_callback, mci); | |
| edac_mc_del_mc(&pdev->dev); | |
| edac_mc_free(mci); | |
| } | |
| static struct platform_driver xilinx_ddr_edac_mc_driver = { | |
| .driver = { | |
| .name = "xilinx-ddrmc-edac", | |
| .of_match_table = xlnx_edac_match, | |
| }, | |
| .probe = mc_probe, | |
| .remove = mc_remove, | |
| }; | |
| module_platform_driver(xilinx_ddr_edac_mc_driver); | |
| MODULE_AUTHOR("AMD Inc"); | |
| MODULE_DESCRIPTION("Xilinx DDRMC ECC driver"); | |
| MODULE_LICENSE("GPL"); |