| // 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_new = mc_remove, |
| }; |
| |
| module_platform_driver(xilinx_ddr_edac_mc_driver); |
| |
| MODULE_AUTHOR("AMD Inc"); |
| MODULE_DESCRIPTION("Xilinx DDRMC ECC driver"); |
| MODULE_LICENSE("GPL"); |