EDAC, altera: Add Arria10 SD-MMC EDAC support
authorThor Thayer <tthayer@opensource.altera.com>
Tue, 9 Aug 2016 14:40:52 +0000 (09:40 -0500)
committerBorislav Petkov <bp@suse.de>
Wed, 10 Aug 2016 12:43:14 +0000 (14:43 +0200)
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>
drivers/edac/Kconfig
drivers/edac/altera_edac.c
drivers/edac/altera_edac.h

index 72752f460cbb09d4a8b1ba663b550c08cab57a74..394cd16dd58fa0c27401d6772aba6586b3dd905d 100644 (file)
@@ -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
index 28247f82e1d5047176409a2c3d49fa612668f50b..efaf727be25aa0ee63d28c8819270d3fe336f3d1 100644 (file)
@@ -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
@@ -1417,6 +1599,9 @@ static const struct of_device_id altr_edac_a10_device_of_match[] = {
 #endif
 #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
        {},
 };
@@ -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);
 
index 687d8e754d36c3a9afb3affec273b6daecdb3ccb..cf3a68bec100a8913236d0b45b4e2a4110fd9e89 100644 (file)
@@ -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