[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-Id: <20251028092232.773991-7-niravkumarlaxmidas.rabara@altera.com>
Date: Tue, 28 Oct 2025 17:22:32 +0800
From: niravkumarlaxmidas.rabara@...era.com
To: dinguyen@...nel.org,
matthew.gerlach@...era.com,
robh@...nel.org,
krzk+dt@...nel.org,
conor+dt@...nel.org,
bp@...en8.de,
tony.luck@...el.com
Cc: linux-edac@...r.kernel.org,
devicetree@...r.kernel.org,
linux-kernel@...r.kernel.org,
Niravkumar L Rabara <niravkumarlaxmidas.rabara@...era.com>
Subject: [PATCH 6/6] EDAC: altera: Add ECC support for SDM QSPI on Agilex5
From: Niravkumar L Rabara <niravkumarlaxmidas.rabara@...era.com>
On Agilex5 SoCFPGA, the Secure Device Manager(SDM) manages ECC protection
for the QSPI. Since SDM operates in secure mode, all register accesses
must go through ARM Trusted Firmware (ATF) using Secure Monitor Calls
(SMC).
This driver uses the SMC interface to initialize ECC, handle correctable
and uncorrectable error interrupts, and support error injection using
debugfs.
Signed-off-by: Niravkumar L Rabara <niravkumarlaxmidas.rabara@...era.com>
---
drivers/edac/Kconfig | 11 +++
drivers/edac/altera_edac.c | 177 ++++++++++++++++++++++++++++++++++++-
drivers/edac/altera_edac.h | 5 ++
3 files changed, 192 insertions(+), 1 deletion(-)
diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig
index 701b15e73a39..439b823a6549 100644
--- a/drivers/edac/Kconfig
+++ b/drivers/edac/Kconfig
@@ -470,6 +470,17 @@ config EDAC_ALTERA_QSPI
Support for error detection and correction on the
Altera QSPI FIFO Memory for Altera SoCs.
+config EDAC_ALTERA_SDM_QSPI
+ bool "Altera SDM QSPI FIFO ECC"
+ depends on EDAC_ALTERA=y && ARM64 && SPI_CADENCE_QUADSPI
+ help
+ Support for error detection and correction on the
+ Secure Device Manager (SDM) QSPI FIFO Memory that HPS
+ access on Agilex5 onwards platform.
+
+ SDM QSPI ECC is always in secure mode, so access to register
+ is through ATF using ARM Secure Monitor Call(SMC).
+
config EDAC_ALTERA_SDMMC
bool "Altera SDMMC FIFO ECC"
depends on EDAC_ALTERA=y && MMC_DW
diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c
index ac2151c625a2..2f2755ab2c45 100644
--- a/drivers/edac/altera_edac.c
+++ b/drivers/edac/altera_edac.c
@@ -682,6 +682,19 @@ altr_edac_io96b_inject_fops __maybe_unused = {
};
#endif
+#if IS_ENABLED(CONFIG_EDAC_ALTERA_SDM_QSPI)
+static ssize_t __maybe_unused
+altr_edac_sdm_qspi_device_trig(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos);
+
+static const struct file_operations
+altr_edac_sdm_qspi_device_inject_fops __maybe_unused = {
+ .open = simple_open,
+ .write = altr_edac_sdm_qspi_device_trig,
+ .llseek = generic_file_llseek,
+};
+#endif
+
static ssize_t __maybe_unused
altr_edac_a10_device_trig2(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos);
@@ -1585,6 +1598,104 @@ static const struct edac_device_prv_data a10_qspiecc_data = {
#endif /* CONFIG_EDAC_ALTERA_QSPI */
+#if IS_ENABLED(CONFIG_EDAC_ALTERA_SDM_QSPI)
+
+static ssize_t __maybe_unused
+altr_edac_sdm_qspi_device_trig(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct edac_device_ctl_info *edac_dci = file->private_data;
+ struct altr_edac_device_dev *drvdata = edac_dci->pvt_info;
+ unsigned long flags;
+ u8 trig_type;
+ struct arm_smccc_res result;
+
+ if (!user_buf || get_user(trig_type, user_buf))
+ return -EFAULT;
+
+ local_irq_save(flags);
+ if (trig_type == ALTR_UE_TRIGGER_CHAR)
+ arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE,
+ drvdata->sdm_qspi_addr + ALTR_A10_ECC_INTTEST_OFST,
+ ALTR_A10_ECC_TDERRA, 0, 0, 0, 0, 0, &result);
+ else
+ arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE,
+ drvdata->sdm_qspi_addr + ALTR_A10_ECC_INTTEST_OFST,
+ ALTR_A10_ECC_TSERRA, 0, 0, 0, 0, 0, &result);
+
+ /* Ensure the interrupt test bits are set */
+ wmb();
+ local_irq_restore(flags);
+
+ return count;
+}
+
+static int __init socfpga_init_sdm_qspi_ecc(struct altr_edac_device_dev *device)
+{
+ struct arm_smccc_res result;
+ u32 read_reg;
+ int limit = ALTR_A10_ECC_INIT_WATCHDOG_10US;
+
+ /* Disable ECC */
+ arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE,
+ device->sdm_qspi_addr + ALTR_A10_ECC_ERRINTENR_OFST,
+ ALTR_A10_ECC_SERRINTEN, 0, 0, 0, 0, 0, &result);
+
+ arm_smccc_smc(INTEL_SIP_SMC_REG_READ,
+ device->sdm_qspi_addr + ALTR_A10_ECC_CTRL_OFST,
+ 0, 0, 0, 0, 0, 0, &result);
+ read_reg = (unsigned int)result.a1 & 0x00;
+
+ arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE,
+ device->sdm_qspi_addr + ALTR_A10_ECC_CTRL_OFST,
+ read_reg, 0, 0, 0, 0, 0, &result);
+
+ /* Ensure all writes complete */
+ wmb();
+ arm_smccc_smc(INTEL_SIP_SMC_REG_READ,
+ device->sdm_qspi_addr + ALTR_A10_ECC_CTRL_OFST,
+ 0, 0, 0, 0, 0, 0, &result);
+ read_reg = (unsigned int)result.a1 | ALTR_A10_ECC_INITA;
+
+ arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE,
+ device->sdm_qspi_addr + ALTR_A10_ECC_CTRL_OFST,
+ read_reg, 0, 0, 0, 0, 0, &result);
+
+ while (limit--) {
+ arm_smccc_smc(INTEL_SIP_SMC_REG_READ,
+ device->sdm_qspi_addr + ALTR_A10_ECC_INITSTAT_OFST,
+ 0, 0, 0, 0, 0, 0, &result);
+
+ if ((unsigned int)result.a1 & ALTR_A10_ECC_INITCOMPLETEA)
+ break;
+ udelay(1);
+ }
+ if (limit <= 0)
+ return -EBUSY;
+
+ /* Enable ECC */
+ arm_smccc_smc(INTEL_SIP_SMC_REG_READ,
+ device->sdm_qspi_addr + ALTR_A10_ECC_CTRL_OFST,
+ 0, 0, 0, 0, 0, 0, &result);
+ read_reg = (unsigned int)result.a1 | ALTR_A10_ECC_SERRINTEN;
+
+ arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE,
+ device->sdm_qspi_addr + ALTR_A10_ECC_CTRL_OFST,
+ read_reg, 0, 0, 0, 0, 0, &result);
+
+ arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE,
+ device->sdm_qspi_addr + ALTR_A10_ECC_ERRINTEN_OFST,
+ ALTR_A10_ECC_SERRINTEN, 0, 0, 0, 0, 0, &result);
+ return 0;
+}
+
+static const struct edac_device_prv_data a10_sdmqspiecc_data = {
+ .setup = socfpga_init_sdm_qspi_ecc,
+ .inject_fops = &altr_edac_sdm_qspi_device_inject_fops,
+};
+
+#endif /* CONFIG_EDAC_ALTERA_SDM_QSPI */
+
/********************* SDMMC Device Functions **********************/
#ifdef CONFIG_EDAC_ALTERA_SDMMC
@@ -1815,6 +1926,10 @@ static const struct of_device_id altr_edac_a10_device_of_match[] = {
#ifdef CONFIG_EDAC_ALTERA_QSPI
{ .compatible = "altr,socfpga-qspi-ecc", .data = &a10_qspiecc_data },
#endif
+#if IS_ENABLED(CONFIG_EDAC_ALTERA_SDM_QSPI)
+ { .compatible = "altr,socfpga-sdm-qspi-ecc",
+ .data = &a10_sdmqspiecc_data },
+#endif
#ifdef CONFIG_EDAC_ALTERA_SDMMC
{ .compatible = "altr,socfpga-sdmmc-ecc", .data = &a10_sdmmcecca_data },
#endif
@@ -2037,6 +2152,25 @@ static irqreturn_t io96b_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static irqreturn_t sdm_qspi_irq_handler(int irq, void *dev_id)
+{
+ struct altr_edac_device_dev *dci = dev_id;
+ struct arm_smccc_res result;
+
+ if (irq == dci->sdm_qspi_sb_irq) {
+ arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE,
+ dci->sdm_qspi_addr + ALTR_A10_ECC_INTSTAT_OFST,
+ ALTR_A10_ECC_SERRPENA, 0, 0, 0, 0, 0, &result);
+ edac_device_handle_ce(dci->edac_dev, 0, 0, dci->edac_dev_name);
+ } else {
+ arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE,
+ dci->sdm_qspi_addr + ALTR_A10_ECC_INTSTAT_OFST,
+ ALTR_A10_ECC_DERRPENA, 0, 0, 0, 0, 0, &result);
+ edac_device_handle_ue(dci->edac_dev, 0, 0, dci->edac_dev_name);
+ }
+ return IRQ_HANDLED;
+}
+
static void altr_edac_a10_irq_handler(struct irq_desc *desc)
{
int dberr, bit, sm_offset, irq_status;
@@ -2214,6 +2348,7 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
struct resource res;
int edac_idx;
int rc = 0;
+ bool sdm_qspi_ecc = false;
bool io96b0_ecc = false;
bool io96b1_ecc = false;
const struct edac_device_prv_data *prv;
@@ -2237,6 +2372,8 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
io96b0_ecc = true;
} else if (of_device_is_compatible(np, "altr,socfpga-io96b1-ecc")) {
io96b1_ecc = true;
+ } else if (of_device_is_compatible(np, "altr,socfpga-sdm-qspi-ecc")) {
+ sdm_qspi_ecc = true;
} else if (of_device_is_compatible(np, "altr,sdram-edac-s10")) {
rc = get_s10_sdram_edac_resource(np, &res);
} else {
@@ -2283,6 +2420,13 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
rc = PTR_ERR(altdev->base);
goto err_release_group1;
}
+ } else if (sdm_qspi_ecc) {
+ altdev->sdm_qspi_addr =
+ (u32)of_translate_address(np,
+ of_get_address(np,
+ 0,
+ NULL,
+ NULL));
} else {
altdev->base = devm_ioremap_resource(edac->dev, &res);
if (IS_ERR(altdev->base)) {
@@ -2298,7 +2442,24 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
goto err_release_group1;
}
- if (io96b0_ecc) {
+ if (sdm_qspi_ecc) {
+ altdev->sdm_qspi_sb_irq = altdev->edac->sdm_qspi_sb_irq;
+ rc = devm_request_threaded_irq(edac->dev, altdev->sdm_qspi_sb_irq, NULL,
+ sdm_qspi_irq_handler, IRQF_ONESHOT,
+ ecc_name, altdev);
+ if (rc) {
+ edac_printk(KERN_ERR, EDAC_DEVICE, "No SDM QSPI SBE IRQ resource\n");
+ goto err_release_group1;
+ }
+ altdev->sdm_qspi_db_irq = altdev->edac->sdm_qspi_db_irq;
+ rc = devm_request_threaded_irq(edac->dev, altdev->sdm_qspi_db_irq, NULL,
+ sdm_qspi_irq_handler, IRQF_ONESHOT,
+ ecc_name, altdev);
+ if (rc) {
+ edac_printk(KERN_ERR, EDAC_DEVICE, "No SDM QSPI DBE IRQ resource\n");
+ goto err_release_group1;
+ }
+ } else if (io96b0_ecc) {
altdev->io96b0_irq = altdev->edac->io96b0_irq;
rc = devm_request_threaded_irq(edac->dev, altdev->io96b0_irq, NULL,
io96b_irq_handler, IRQF_ONESHOT,
@@ -2578,6 +2739,20 @@ static int altr_edac_a10_probe(struct platform_device *pdev)
return edac->io96b1_irq;
}
#endif
+
+#if IS_ENABLED(CONFIG_EDAC_ALTERA_SDM_QSPI)
+ edac->sdm_qspi_sb_irq = platform_get_irq_byname(pdev, "sdm_qspi_sbe");
+ if (edac->sdm_qspi_sb_irq < 0) {
+ dev_err(&pdev->dev, "no %s IRQ defined\n", "sdm_qspi_sbe");
+ return edac->sdm_qspi_sb_irq;
+ }
+
+ edac->sdm_qspi_db_irq = platform_get_irq_byname(pdev, "sdm_qspi_dbe");
+ if (edac->sdm_qspi_db_irq < 0) {
+ dev_err(&pdev->dev, "no %s IRQ defined\n", "sdm_qspi_dbe");
+ return edac->sdm_qspi_db_irq;
+ }
+#endif
}
#else
diff --git a/drivers/edac/altera_edac.h b/drivers/edac/altera_edac.h
index 8b475dc692e1..e537304522fb 100644
--- a/drivers/edac/altera_edac.h
+++ b/drivers/edac/altera_edac.h
@@ -431,6 +431,9 @@ struct altr_edac_device_dev {
int edac_idx;
int io96b0_irq;
int io96b1_irq;
+ int sdm_qspi_sb_irq;
+ int sdm_qspi_db_irq;
+ u32 sdm_qspi_addr;
int seu_irq;
struct altr_seu seu;
};
@@ -446,6 +449,8 @@ struct altr_arria10_edac {
struct notifier_block panic_notifier;
int io96b0_irq;
int io96b1_irq;
+ int sdm_qspi_sb_irq;
+ int sdm_qspi_db_irq;
};
#endif /* #ifndef _ALTERA_EDAC_H */
--
2.25.1
Powered by blists - more mailing lists