diff options
author | Thor Thayer <tthayer@opensource.altera.com> | 2016-08-09 14:40:52 (GMT) |
---|---|---|
committer | Borislav Petkov <bp@suse.de> | 2016-08-10 12:43:14 (GMT) |
commit | 911049845d7096c27304930ff478cbf3f2623ba3 (patch) | |
tree | 5cef3310ecfab5ef2bd1a401a620912c5de34f5b | |
parent | 5e40cd4d258cc0728585a94fd81f73488f7cdce7 (diff) | |
download | linux-911049845d7096c27304930ff478cbf3f2623ba3.tar.xz |
EDAC, altera: Add Arria10 SD-MMC EDAC support
Add Altera Arria10 SD-MMC FIFO memory EDAC support. The SD-MMC is a
dual port RAM implementation which is different than any of the other
peripherals and therefore requires additional code.
Signed-off-by: Thor Thayer <tthayer@opensource.altera.com>
Cc: dinguyen@opensource.altera.com
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-edac <linux-edac@vger.kernel.org>
Link: http://lkml.kernel.org/r/1470753653-23465-3-git-send-email-tthayer@opensource.altera.com
Signed-off-by: Borislav Petkov <bp@suse.de>
-rw-r--r-- | drivers/edac/Kconfig | 7 | ||||
-rw-r--r-- | drivers/edac/altera_edac.c | 188 | ||||
-rw-r--r-- | drivers/edac/altera_edac.h | 5 |
3 files changed, 199 insertions, 1 deletions
diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index 72752f4..394cd16 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -426,6 +426,13 @@ config EDAC_ALTERA_QSPI Support for error detection and correction on the Altera QSPI FIFO Memory for Altera SoCs. +config EDAC_ALTERA_SDMMC + bool "Altera SDMMC FIFO ECC" + depends on EDAC_ALTERA=y && MMC_DW + help + Support for error detection and correction on the + Altera SDMMC FIFO Memory for Altera SoCs. + config EDAC_SYNOPSYS tristate "Synopsys DDR Memory Controller" depends on EDAC_MM_EDAC && ARCH_ZYNQ diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 28247f8..efaf727 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c @@ -1393,6 +1393,188 @@ early_initcall(socfpga_init_qspi_ecc); #endif /* CONFIG_EDAC_ALTERA_QSPI */ +/********************* SDMMC Device Functions **********************/ + +#ifdef CONFIG_EDAC_ALTERA_SDMMC + +static const struct edac_device_prv_data a10_sdmmceccb_data; +static int altr_portb_setup(struct altr_edac_device_dev *device) +{ + struct edac_device_ctl_info *dci; + struct altr_edac_device_dev *altdev; + char *ecc_name = "sdmmcb-ecc"; + int edac_idx, rc; + struct device_node *np; + const struct edac_device_prv_data *prv = &a10_sdmmceccb_data; + + rc = altr_check_ecc_deps(device); + if (rc) + return rc; + + np = of_find_compatible_node(NULL, NULL, "altr,socfpga-sdmmc-ecc"); + if (!np) { + edac_printk(KERN_WARNING, EDAC_DEVICE, "SDMMC node not found\n"); + return -ENODEV; + } + + /* Create the PortB EDAC device */ + edac_idx = edac_device_alloc_index(); + dci = edac_device_alloc_ctl_info(sizeof(*altdev), ecc_name, 1, + ecc_name, 1, 0, NULL, 0, edac_idx); + if (!dci) { + edac_printk(KERN_ERR, EDAC_DEVICE, + "%s: Unable to allocate PortB EDAC device\n", + ecc_name); + return -ENOMEM; + } + + /* Initialize the PortB EDAC device structure from PortA structure */ + altdev = dci->pvt_info; + *altdev = *device; + + if (!devres_open_group(&altdev->ddev, altr_portb_setup, GFP_KERNEL)) + return -ENOMEM; + + /* Update PortB specific values */ + altdev->edac_dev_name = ecc_name; + altdev->edac_idx = edac_idx; + altdev->edac_dev = dci; + altdev->data = prv; + dci->dev = &altdev->ddev; + dci->ctl_name = "Altera ECC Manager"; + dci->mod_name = ecc_name; + dci->dev_name = ecc_name; + + /* Update the IRQs for PortB */ + altdev->sb_irq = irq_of_parse_and_map(np, 2); + if (!altdev->sb_irq) { + edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB SBIRQ alloc\n"); + rc = -ENODEV; + goto err_release_group_1; + } + rc = devm_request_irq(&altdev->ddev, altdev->sb_irq, + prv->ecc_irq_handler, + IRQF_SHARED, ecc_name, altdev); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, "PortB SBERR IRQ error\n"); + goto err_release_group_1; + } + + altdev->db_irq = irq_of_parse_and_map(np, 3); + if (!altdev->db_irq) { + edac_printk(KERN_ERR, EDAC_DEVICE, "Error PortB DBIRQ alloc\n"); + rc = -ENODEV; + goto err_release_group_1; + } + rc = devm_request_irq(&altdev->ddev, altdev->db_irq, + prv->ecc_irq_handler, + IRQF_SHARED, ecc_name, altdev); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, "PortB DBERR IRQ error\n"); + goto err_release_group_1; + } + + rc = edac_device_add_device(dci); + if (rc) { + edac_printk(KERN_ERR, EDAC_DEVICE, + "edac_device_add_device portB failed\n"); + rc = -ENOMEM; + goto err_release_group_1; + } + altr_create_edacdev_dbgfs(dci, prv); + + list_add(&altdev->next, &altdev->edac->a10_ecc_devices); + + devres_remove_group(&altdev->ddev, altr_portb_setup); + + return 0; + +err_release_group_1: + edac_device_free_ctl_info(dci); + devres_release_group(&altdev->ddev, altr_portb_setup); + edac_printk(KERN_ERR, EDAC_DEVICE, + "%s:Error setting up EDAC device: %d\n", ecc_name, rc); + return rc; +} + +static irqreturn_t altr_edac_a10_ecc_irq_portb(int irq, void *dev_id) +{ + struct altr_edac_device_dev *ad = dev_id; + void __iomem *base = ad->base; + const struct edac_device_prv_data *priv = ad->data; + + if (irq == ad->sb_irq) { + writel(priv->ce_clear_mask, + base + ALTR_A10_ECC_INTSTAT_OFST); + edac_device_handle_ce(ad->edac_dev, 0, 0, ad->edac_dev_name); + return IRQ_HANDLED; + } else if (irq == ad->db_irq) { + writel(priv->ue_clear_mask, + base + ALTR_A10_ECC_INTSTAT_OFST); + edac_device_handle_ue(ad->edac_dev, 0, 0, ad->edac_dev_name); + return IRQ_HANDLED; + } + + WARN_ONCE(1, "Unhandled IRQ%d on Port B.", irq); + + return IRQ_NONE; +} + +static const struct edac_device_prv_data a10_sdmmcecca_data = { + .setup = altr_portb_setup, + .ce_clear_mask = ALTR_A10_ECC_SERRPENA, + .ue_clear_mask = ALTR_A10_ECC_DERRPENA, + .dbgfs_name = "altr_trigger", + .ecc_enable_mask = ALTR_A10_COMMON_ECC_EN_CTL, + .ecc_en_ofst = ALTR_A10_ECC_CTRL_OFST, + .ce_set_mask = ALTR_A10_ECC_SERRPENA, + .ue_set_mask = ALTR_A10_ECC_DERRPENA, + .set_err_ofst = ALTR_A10_ECC_INTTEST_OFST, + .ecc_irq_handler = altr_edac_a10_ecc_irq, + .inject_fops = &altr_edac_a10_device_inject_fops, +}; + +static const struct edac_device_prv_data a10_sdmmceccb_data = { + .setup = altr_portb_setup, + .ce_clear_mask = ALTR_A10_ECC_SERRPENB, + .ue_clear_mask = ALTR_A10_ECC_DERRPENB, + .dbgfs_name = "altr_trigger", + .ecc_enable_mask = ALTR_A10_COMMON_ECC_EN_CTL, + .ecc_en_ofst = ALTR_A10_ECC_CTRL_OFST, + .ce_set_mask = ALTR_A10_ECC_TSERRB, + .ue_set_mask = ALTR_A10_ECC_TDERRB, + .set_err_ofst = ALTR_A10_ECC_INTTEST_OFST, + .ecc_irq_handler = altr_edac_a10_ecc_irq_portb, + .inject_fops = &altr_edac_a10_device_inject_fops, +}; + +static int __init socfpga_init_sdmmc_ecc(void) +{ + int rc = -ENODEV; + struct device_node *child = of_find_compatible_node(NULL, NULL, + "altr,socfpga-sdmmc-ecc"); + if (!child) { + edac_printk(KERN_WARNING, EDAC_DEVICE, "SDMMC node not found\n"); + return -ENODEV; + } + + if (!of_device_is_available(child)) + goto exit; + + if (validate_parent_available(child)) + goto exit; + + rc = altr_init_a10_ecc_block(child, ALTR_A10_SDMMC_IRQ_MASK, + a10_sdmmcecca_data.ecc_enable_mask, 1); +exit: + of_node_put(child); + return rc; +} + +early_initcall(socfpga_init_sdmmc_ecc); + +#endif /* CONFIG_EDAC_ALTERA_SDMMC */ + /********************* Arria10 EDAC Device Functions *************************/ static const struct of_device_id altr_edac_a10_device_of_match[] = { #ifdef CONFIG_EDAC_ALTERA_L2C @@ -1418,6 +1600,9 @@ 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 +#ifdef CONFIG_EDAC_ALTERA_SDMMC + { .compatible = "altr,socfpga-sdmmc-ecc", .data = &a10_sdmmcecca_data }, +#endif {}, }; MODULE_DEVICE_TABLE(of, altr_edac_a10_device_of_match); @@ -1711,7 +1896,8 @@ static int altr_edac_a10_probe(struct platform_device *pdev) of_device_is_compatible(child, "altr,socfpga-nand-ecc") || of_device_is_compatible(child, "altr,socfpga-dma-ecc") || of_device_is_compatible(child, "altr,socfpga-usb-ecc") || - of_device_is_compatible(child, "altr,socfpga-qspi-ecc")) + of_device_is_compatible(child, "altr,socfpga-qspi-ecc") || + of_device_is_compatible(child, "altr,socfpga-sdmmc-ecc")) altr_edac_a10_device_add(edac, child); diff --git a/drivers/edac/altera_edac.h b/drivers/edac/altera_edac.h index 687d8e7..cf3a68b 100644 --- a/drivers/edac/altera_edac.h +++ b/drivers/edac/altera_edac.h @@ -250,6 +250,8 @@ struct altr_sdram_mc_data { #define ALTR_A10_ECC_INTTEST_OFST 0x24 #define ALTR_A10_ECC_TSERRA BIT(0) #define ALTR_A10_ECC_TDERRA BIT(8) +#define ALTR_A10_ECC_TSERRB BIT(16) +#define ALTR_A10_ECC_TDERRB BIT(24) /* ECC Manager Defines */ #define A10_SYSMGR_ECC_INTMASK_SET_OFST 0x94 @@ -288,6 +290,9 @@ struct altr_sdram_mc_data { /* Arria 10 Ethernet ECC Management Group Defines */ #define ALTR_A10_COMMON_ECC_EN_CTL BIT(0) +/* Arria 10 SDMMC ECC Management Group Defines */ +#define ALTR_A10_SDMMC_IRQ_MASK (BIT(16) | BIT(15)) + /* A10 ECC Controller memory initialization timeout */ #define ALTR_A10_ECC_INIT_WATCHDOG_10US 10000 |