18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Bluefield-specific EDAC driver.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2019 Mellanox Technologies.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/acpi.h>
98c2ecf20Sopenharmony_ci#include <linux/arm-smccc.h>
108c2ecf20Sopenharmony_ci#include <linux/bitfield.h>
118c2ecf20Sopenharmony_ci#include <linux/edac.h>
128c2ecf20Sopenharmony_ci#include <linux/io.h>
138c2ecf20Sopenharmony_ci#include <linux/module.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include "edac_module.h"
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define DRIVER_NAME		"bluefield-edac"
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci/*
218c2ecf20Sopenharmony_ci * Mellanox BlueField EMI (External Memory Interface) register definitions.
228c2ecf20Sopenharmony_ci */
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define MLXBF_ECC_CNT 0x340
258c2ecf20Sopenharmony_ci#define MLXBF_ECC_CNT__SERR_CNT GENMASK(15, 0)
268c2ecf20Sopenharmony_ci#define MLXBF_ECC_CNT__DERR_CNT GENMASK(31, 16)
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#define MLXBF_ECC_ERR 0x348
298c2ecf20Sopenharmony_ci#define MLXBF_ECC_ERR__SECC BIT(0)
308c2ecf20Sopenharmony_ci#define MLXBF_ECC_ERR__DECC BIT(16)
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#define MLXBF_ECC_LATCH_SEL 0x354
338c2ecf20Sopenharmony_ci#define MLXBF_ECC_LATCH_SEL__START BIT(24)
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define MLXBF_ERR_ADDR_0 0x358
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci#define MLXBF_ERR_ADDR_1 0x37c
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci#define MLXBF_SYNDROM 0x35c
408c2ecf20Sopenharmony_ci#define MLXBF_SYNDROM__DERR BIT(0)
418c2ecf20Sopenharmony_ci#define MLXBF_SYNDROM__SERR BIT(1)
428c2ecf20Sopenharmony_ci#define MLXBF_SYNDROM__SYN GENMASK(25, 16)
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci#define MLXBF_ADD_INFO 0x364
458c2ecf20Sopenharmony_ci#define MLXBF_ADD_INFO__ERR_PRANK GENMASK(9, 8)
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci#define MLXBF_EDAC_MAX_DIMM_PER_MC	2
488c2ecf20Sopenharmony_ci#define MLXBF_EDAC_ERROR_GRAIN		8
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci/*
518c2ecf20Sopenharmony_ci * Request MLNX_SIP_GET_DIMM_INFO
528c2ecf20Sopenharmony_ci *
538c2ecf20Sopenharmony_ci * Retrieve information about DIMM on a certain slot.
548c2ecf20Sopenharmony_ci *
558c2ecf20Sopenharmony_ci * Call register usage:
568c2ecf20Sopenharmony_ci * a0: MLNX_SIP_GET_DIMM_INFO
578c2ecf20Sopenharmony_ci * a1: (Memory controller index) << 16 | (Dimm index in memory controller)
588c2ecf20Sopenharmony_ci * a2-7: not used.
598c2ecf20Sopenharmony_ci *
608c2ecf20Sopenharmony_ci * Return status:
618c2ecf20Sopenharmony_ci * a0: MLXBF_DIMM_INFO defined below describing the DIMM.
628c2ecf20Sopenharmony_ci * a1-3: not used.
638c2ecf20Sopenharmony_ci */
648c2ecf20Sopenharmony_ci#define MLNX_SIP_GET_DIMM_INFO		0x82000008
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci/* Format for the SMC response about the memory information */
678c2ecf20Sopenharmony_ci#define MLXBF_DIMM_INFO__SIZE_GB GENMASK_ULL(15, 0)
688c2ecf20Sopenharmony_ci#define MLXBF_DIMM_INFO__IS_RDIMM BIT(16)
698c2ecf20Sopenharmony_ci#define MLXBF_DIMM_INFO__IS_LRDIMM BIT(17)
708c2ecf20Sopenharmony_ci#define MLXBF_DIMM_INFO__IS_NVDIMM BIT(18)
718c2ecf20Sopenharmony_ci#define MLXBF_DIMM_INFO__RANKS GENMASK_ULL(23, 21)
728c2ecf20Sopenharmony_ci#define MLXBF_DIMM_INFO__PACKAGE_X GENMASK_ULL(31, 24)
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistruct bluefield_edac_priv {
758c2ecf20Sopenharmony_ci	int dimm_ranks[MLXBF_EDAC_MAX_DIMM_PER_MC];
768c2ecf20Sopenharmony_ci	void __iomem *emi_base;
778c2ecf20Sopenharmony_ci	int dimm_per_mc;
788c2ecf20Sopenharmony_ci};
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic u64 smc_call1(u64 smc_op, u64 smc_arg)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	struct arm_smccc_res res;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	arm_smccc_smc(smc_op, smc_arg, 0, 0, 0, 0, 0, 0, &res);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	return res.a0;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci/*
908c2ecf20Sopenharmony_ci * Gather the ECC information from the External Memory Interface registers
918c2ecf20Sopenharmony_ci * and report it to the edac handler.
928c2ecf20Sopenharmony_ci */
938c2ecf20Sopenharmony_cistatic void bluefield_gather_report_ecc(struct mem_ctl_info *mci,
948c2ecf20Sopenharmony_ci					int error_cnt,
958c2ecf20Sopenharmony_ci					int is_single_ecc)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	struct bluefield_edac_priv *priv = mci->pvt_info;
988c2ecf20Sopenharmony_ci	u32 dram_additional_info, err_prank, edea0, edea1;
998c2ecf20Sopenharmony_ci	u32 ecc_latch_select, dram_syndrom, serr, derr, syndrom;
1008c2ecf20Sopenharmony_ci	enum hw_event_mc_err_type ecc_type;
1018c2ecf20Sopenharmony_ci	u64 ecc_dimm_addr;
1028c2ecf20Sopenharmony_ci	int ecc_dimm;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	ecc_type = is_single_ecc ? HW_EVENT_ERR_CORRECTED :
1058c2ecf20Sopenharmony_ci				   HW_EVENT_ERR_UNCORRECTED;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	/*
1088c2ecf20Sopenharmony_ci	 * Tell the External Memory Interface to populate the relevant
1098c2ecf20Sopenharmony_ci	 * registers with information about the last ECC error occurrence.
1108c2ecf20Sopenharmony_ci	 */
1118c2ecf20Sopenharmony_ci	ecc_latch_select = MLXBF_ECC_LATCH_SEL__START;
1128c2ecf20Sopenharmony_ci	writel(ecc_latch_select, priv->emi_base + MLXBF_ECC_LATCH_SEL);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	/*
1158c2ecf20Sopenharmony_ci	 * Verify that the ECC reported info in the registers is of the
1168c2ecf20Sopenharmony_ci	 * same type as the one asked to report. If not, just report the
1178c2ecf20Sopenharmony_ci	 * error without the detailed information.
1188c2ecf20Sopenharmony_ci	 */
1198c2ecf20Sopenharmony_ci	dram_syndrom = readl(priv->emi_base + MLXBF_SYNDROM);
1208c2ecf20Sopenharmony_ci	serr = FIELD_GET(MLXBF_SYNDROM__SERR, dram_syndrom);
1218c2ecf20Sopenharmony_ci	derr = FIELD_GET(MLXBF_SYNDROM__DERR, dram_syndrom);
1228c2ecf20Sopenharmony_ci	syndrom = FIELD_GET(MLXBF_SYNDROM__SYN, dram_syndrom);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if ((is_single_ecc && !serr) || (!is_single_ecc && !derr)) {
1258c2ecf20Sopenharmony_ci		edac_mc_handle_error(ecc_type, mci, error_cnt, 0, 0, 0,
1268c2ecf20Sopenharmony_ci				     0, 0, -1, mci->ctl_name, "");
1278c2ecf20Sopenharmony_ci		return;
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	dram_additional_info = readl(priv->emi_base + MLXBF_ADD_INFO);
1318c2ecf20Sopenharmony_ci	err_prank = FIELD_GET(MLXBF_ADD_INFO__ERR_PRANK, dram_additional_info);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	ecc_dimm = (err_prank >= 2 && priv->dimm_ranks[0] <= 2) ? 1 : 0;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	edea0 = readl(priv->emi_base + MLXBF_ERR_ADDR_0);
1368c2ecf20Sopenharmony_ci	edea1 = readl(priv->emi_base + MLXBF_ERR_ADDR_1);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	ecc_dimm_addr = ((u64)edea1 << 32) | edea0;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	edac_mc_handle_error(ecc_type, mci, error_cnt,
1418c2ecf20Sopenharmony_ci			     PFN_DOWN(ecc_dimm_addr),
1428c2ecf20Sopenharmony_ci			     offset_in_page(ecc_dimm_addr),
1438c2ecf20Sopenharmony_ci			     syndrom, ecc_dimm, 0, 0, mci->ctl_name, "");
1448c2ecf20Sopenharmony_ci}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_cistatic void bluefield_edac_check(struct mem_ctl_info *mci)
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	struct bluefield_edac_priv *priv = mci->pvt_info;
1498c2ecf20Sopenharmony_ci	u32 ecc_count, single_error_count, double_error_count, ecc_error = 0;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	/*
1528c2ecf20Sopenharmony_ci	 * The memory controller might not be initialized by the firmware
1538c2ecf20Sopenharmony_ci	 * when there isn't memory, which may lead to bad register readings.
1548c2ecf20Sopenharmony_ci	 */
1558c2ecf20Sopenharmony_ci	if (mci->edac_cap == EDAC_FLAG_NONE)
1568c2ecf20Sopenharmony_ci		return;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	ecc_count = readl(priv->emi_base + MLXBF_ECC_CNT);
1598c2ecf20Sopenharmony_ci	single_error_count = FIELD_GET(MLXBF_ECC_CNT__SERR_CNT, ecc_count);
1608c2ecf20Sopenharmony_ci	double_error_count = FIELD_GET(MLXBF_ECC_CNT__DERR_CNT, ecc_count);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	if (single_error_count) {
1638c2ecf20Sopenharmony_ci		ecc_error |= MLXBF_ECC_ERR__SECC;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci		bluefield_gather_report_ecc(mci, single_error_count, 1);
1668c2ecf20Sopenharmony_ci	}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	if (double_error_count) {
1698c2ecf20Sopenharmony_ci		ecc_error |= MLXBF_ECC_ERR__DECC;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci		bluefield_gather_report_ecc(mci, double_error_count, 0);
1728c2ecf20Sopenharmony_ci	}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	/* Write to clear reported errors. */
1758c2ecf20Sopenharmony_ci	if (ecc_count)
1768c2ecf20Sopenharmony_ci		writel(ecc_error, priv->emi_base + MLXBF_ECC_ERR);
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci/* Initialize the DIMMs information for the given memory controller. */
1808c2ecf20Sopenharmony_cistatic void bluefield_edac_init_dimms(struct mem_ctl_info *mci)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	struct bluefield_edac_priv *priv = mci->pvt_info;
1838c2ecf20Sopenharmony_ci	int mem_ctrl_idx = mci->mc_idx;
1848c2ecf20Sopenharmony_ci	struct dimm_info *dimm;
1858c2ecf20Sopenharmony_ci	u64 smc_info, smc_arg;
1868c2ecf20Sopenharmony_ci	int is_empty = 1, i;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	for (i = 0; i < priv->dimm_per_mc; i++) {
1898c2ecf20Sopenharmony_ci		dimm = mci->dimms[i];
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci		smc_arg = mem_ctrl_idx << 16 | i;
1928c2ecf20Sopenharmony_ci		smc_info = smc_call1(MLNX_SIP_GET_DIMM_INFO, smc_arg);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci		if (!FIELD_GET(MLXBF_DIMM_INFO__SIZE_GB, smc_info)) {
1958c2ecf20Sopenharmony_ci			dimm->mtype = MEM_EMPTY;
1968c2ecf20Sopenharmony_ci			continue;
1978c2ecf20Sopenharmony_ci		}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci		is_empty = 0;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci		dimm->edac_mode = EDAC_SECDED;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci		if (FIELD_GET(MLXBF_DIMM_INFO__IS_NVDIMM, smc_info))
2048c2ecf20Sopenharmony_ci			dimm->mtype = MEM_NVDIMM;
2058c2ecf20Sopenharmony_ci		else if (FIELD_GET(MLXBF_DIMM_INFO__IS_LRDIMM, smc_info))
2068c2ecf20Sopenharmony_ci			dimm->mtype = MEM_LRDDR4;
2078c2ecf20Sopenharmony_ci		else if (FIELD_GET(MLXBF_DIMM_INFO__IS_RDIMM, smc_info))
2088c2ecf20Sopenharmony_ci			dimm->mtype = MEM_RDDR4;
2098c2ecf20Sopenharmony_ci		else
2108c2ecf20Sopenharmony_ci			dimm->mtype = MEM_DDR4;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci		dimm->nr_pages =
2138c2ecf20Sopenharmony_ci			FIELD_GET(MLXBF_DIMM_INFO__SIZE_GB, smc_info) *
2148c2ecf20Sopenharmony_ci			(SZ_1G / PAGE_SIZE);
2158c2ecf20Sopenharmony_ci		dimm->grain = MLXBF_EDAC_ERROR_GRAIN;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci		/* Mem controller for BlueField only supports x4, x8 and x16 */
2188c2ecf20Sopenharmony_ci		switch (FIELD_GET(MLXBF_DIMM_INFO__PACKAGE_X, smc_info)) {
2198c2ecf20Sopenharmony_ci		case 4:
2208c2ecf20Sopenharmony_ci			dimm->dtype = DEV_X4;
2218c2ecf20Sopenharmony_ci			break;
2228c2ecf20Sopenharmony_ci		case 8:
2238c2ecf20Sopenharmony_ci			dimm->dtype = DEV_X8;
2248c2ecf20Sopenharmony_ci			break;
2258c2ecf20Sopenharmony_ci		case 16:
2268c2ecf20Sopenharmony_ci			dimm->dtype = DEV_X16;
2278c2ecf20Sopenharmony_ci			break;
2288c2ecf20Sopenharmony_ci		default:
2298c2ecf20Sopenharmony_ci			dimm->dtype = DEV_UNKNOWN;
2308c2ecf20Sopenharmony_ci		}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci		priv->dimm_ranks[i] =
2338c2ecf20Sopenharmony_ci			FIELD_GET(MLXBF_DIMM_INFO__RANKS, smc_info);
2348c2ecf20Sopenharmony_ci	}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	if (is_empty)
2378c2ecf20Sopenharmony_ci		mci->edac_cap = EDAC_FLAG_NONE;
2388c2ecf20Sopenharmony_ci	else
2398c2ecf20Sopenharmony_ci		mci->edac_cap = EDAC_FLAG_SECDED;
2408c2ecf20Sopenharmony_ci}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_cistatic int bluefield_edac_mc_probe(struct platform_device *pdev)
2438c2ecf20Sopenharmony_ci{
2448c2ecf20Sopenharmony_ci	struct bluefield_edac_priv *priv;
2458c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
2468c2ecf20Sopenharmony_ci	struct edac_mc_layer layers[1];
2478c2ecf20Sopenharmony_ci	struct mem_ctl_info *mci;
2488c2ecf20Sopenharmony_ci	struct resource *emi_res;
2498c2ecf20Sopenharmony_ci	unsigned int mc_idx, dimm_count;
2508c2ecf20Sopenharmony_ci	int rc, ret;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	/* Read the MSS (Memory SubSystem) index from ACPI table. */
2538c2ecf20Sopenharmony_ci	if (device_property_read_u32(dev, "mss_number", &mc_idx)) {
2548c2ecf20Sopenharmony_ci		dev_warn(dev, "bf_edac: MSS number unknown\n");
2558c2ecf20Sopenharmony_ci		return -EINVAL;
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	/* Read the DIMMs per MC from ACPI table. */
2598c2ecf20Sopenharmony_ci	if (device_property_read_u32(dev, "dimm_per_mc", &dimm_count)) {
2608c2ecf20Sopenharmony_ci		dev_warn(dev, "bf_edac: DIMMs per MC unknown\n");
2618c2ecf20Sopenharmony_ci		return -EINVAL;
2628c2ecf20Sopenharmony_ci	}
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	if (dimm_count > MLXBF_EDAC_MAX_DIMM_PER_MC) {
2658c2ecf20Sopenharmony_ci		dev_warn(dev, "bf_edac: DIMMs per MC not valid\n");
2668c2ecf20Sopenharmony_ci		return -EINVAL;
2678c2ecf20Sopenharmony_ci	}
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	emi_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
2708c2ecf20Sopenharmony_ci	if (!emi_res)
2718c2ecf20Sopenharmony_ci		return -EINVAL;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	layers[0].type = EDAC_MC_LAYER_SLOT;
2748c2ecf20Sopenharmony_ci	layers[0].size = dimm_count;
2758c2ecf20Sopenharmony_ci	layers[0].is_virt_csrow = true;
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	mci = edac_mc_alloc(mc_idx, ARRAY_SIZE(layers), layers, sizeof(*priv));
2788c2ecf20Sopenharmony_ci	if (!mci)
2798c2ecf20Sopenharmony_ci		return -ENOMEM;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	priv = mci->pvt_info;
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	priv->dimm_per_mc = dimm_count;
2848c2ecf20Sopenharmony_ci	priv->emi_base = devm_ioremap_resource(dev, emi_res);
2858c2ecf20Sopenharmony_ci	if (IS_ERR(priv->emi_base)) {
2868c2ecf20Sopenharmony_ci		dev_err(dev, "failed to map EMI IO resource\n");
2878c2ecf20Sopenharmony_ci		ret = PTR_ERR(priv->emi_base);
2888c2ecf20Sopenharmony_ci		goto err;
2898c2ecf20Sopenharmony_ci	}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	mci->pdev = dev;
2928c2ecf20Sopenharmony_ci	mci->mtype_cap = MEM_FLAG_DDR4 | MEM_FLAG_RDDR4 |
2938c2ecf20Sopenharmony_ci			 MEM_FLAG_LRDDR4 | MEM_FLAG_NVDIMM;
2948c2ecf20Sopenharmony_ci	mci->edac_ctl_cap = EDAC_FLAG_SECDED;
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	mci->mod_name = DRIVER_NAME;
2978c2ecf20Sopenharmony_ci	mci->ctl_name = "BlueField_Memory_Controller";
2988c2ecf20Sopenharmony_ci	mci->dev_name = dev_name(dev);
2998c2ecf20Sopenharmony_ci	mci->edac_check = bluefield_edac_check;
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	/* Initialize mci with the actual populated DIMM information. */
3028c2ecf20Sopenharmony_ci	bluefield_edac_init_dimms(mci);
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, mci);
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	/* Register with EDAC core */
3078c2ecf20Sopenharmony_ci	rc = edac_mc_add_mc(mci);
3088c2ecf20Sopenharmony_ci	if (rc) {
3098c2ecf20Sopenharmony_ci		dev_err(dev, "failed to register with EDAC core\n");
3108c2ecf20Sopenharmony_ci		ret = rc;
3118c2ecf20Sopenharmony_ci		goto err;
3128c2ecf20Sopenharmony_ci	}
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	/* Only POLL mode supported so far. */
3158c2ecf20Sopenharmony_ci	edac_op_state = EDAC_OPSTATE_POLL;
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	return 0;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_cierr:
3208c2ecf20Sopenharmony_ci	edac_mc_free(mci);
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ci	return ret;
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci}
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_cistatic int bluefield_edac_mc_remove(struct platform_device *pdev)
3278c2ecf20Sopenharmony_ci{
3288c2ecf20Sopenharmony_ci	struct mem_ctl_info *mci = platform_get_drvdata(pdev);
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	edac_mc_del_mc(&pdev->dev);
3318c2ecf20Sopenharmony_ci	edac_mc_free(mci);
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci	return 0;
3348c2ecf20Sopenharmony_ci}
3358c2ecf20Sopenharmony_ci
3368c2ecf20Sopenharmony_cistatic const struct acpi_device_id bluefield_mc_acpi_ids[] = {
3378c2ecf20Sopenharmony_ci	{"MLNXBF08", 0},
3388c2ecf20Sopenharmony_ci	{}
3398c2ecf20Sopenharmony_ci};
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, bluefield_mc_acpi_ids);
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_cistatic struct platform_driver bluefield_edac_mc_driver = {
3448c2ecf20Sopenharmony_ci	.driver = {
3458c2ecf20Sopenharmony_ci		.name = DRIVER_NAME,
3468c2ecf20Sopenharmony_ci		.acpi_match_table = bluefield_mc_acpi_ids,
3478c2ecf20Sopenharmony_ci	},
3488c2ecf20Sopenharmony_ci	.probe = bluefield_edac_mc_probe,
3498c2ecf20Sopenharmony_ci	.remove = bluefield_edac_mc_remove,
3508c2ecf20Sopenharmony_ci};
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_cimodule_platform_driver(bluefield_edac_mc_driver);
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Mellanox BlueField memory edac driver");
3558c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mellanox Technologies");
3568c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
357