|  | // 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"); |