Merge tag 'gpio-v4.8-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux...
[cascardo/linux.git] / drivers / edac / altera_edac.c
index 63e4209..5b4d223 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
@@ -78,27 +79,6 @@ static const struct altr_sdram_prv_data a10_data = {
        .ue_set_mask        = A10_DIAGINT_TDERRA_MASK,
 };
 
-/************************** EDAC Device Defines **************************/
-
-/* OCRAM ECC Management Group Defines */
-#define ALTR_MAN_GRP_OCRAM_ECC_OFFSET   0x04
-#define ALTR_OCR_ECC_EN                 BIT(0)
-#define ALTR_OCR_ECC_INJS               BIT(1)
-#define ALTR_OCR_ECC_INJD               BIT(2)
-#define ALTR_OCR_ECC_SERR               BIT(3)
-#define ALTR_OCR_ECC_DERR               BIT(4)
-
-/* L2 ECC Management Group Defines */
-#define ALTR_MAN_GRP_L2_ECC_OFFSET      0x00
-#define ALTR_L2_ECC_EN                  BIT(0)
-#define ALTR_L2_ECC_INJS                BIT(1)
-#define ALTR_L2_ECC_INJD                BIT(2)
-
-#define ALTR_UE_TRIGGER_CHAR            'U'   /* Trigger for UE */
-#define ALTR_TRIGGER_READ_WRD_CNT       32    /* Line size x 4 */
-#define ALTR_TRIG_OCRAM_BYTE_SIZE       128   /* Line size x 4 */
-#define ALTR_TRIG_L2C_BYTE_SIZE         4096  /* Full Page */
-
 /*********************** EDAC Memory Controller Functions ****************/
 
 /* The SDRAM controller uses the EDAC Memory Controller framework.       */
@@ -252,8 +232,8 @@ static unsigned long get_total_mem(void)
 }
 
 static const struct of_device_id altr_sdram_ctrl_of_match[] = {
-       { .compatible = "altr,sdram-edac", .data = (void *)&c5_data},
-       { .compatible = "altr,sdram-edac-a10", .data = (void *)&a10_data},
+       { .compatible = "altr,sdram-edac", .data = &c5_data},
+       { .compatible = "altr,sdram-edac-a10", .data = &a10_data},
        {},
 };
 MODULE_DEVICE_TABLE(of, altr_sdram_ctrl_of_match);
@@ -570,28 +550,8 @@ module_platform_driver(altr_edac_driver);
 
 const struct edac_device_prv_data ocramecc_data;
 const struct edac_device_prv_data l2ecc_data;
-
-struct edac_device_prv_data {
-       int (*setup)(struct platform_device *pdev, void __iomem *base);
-       int ce_clear_mask;
-       int ue_clear_mask;
-       char dbgfs_name[20];
-       void * (*alloc_mem)(size_t size, void **other);
-       void (*free_mem)(void *p, size_t size, void *other);
-       int ecc_enable_mask;
-       int ce_set_mask;
-       int ue_set_mask;
-       int trig_alloc_sz;
-};
-
-struct altr_edac_device_dev {
-       void __iomem *base;
-       int sb_irq;
-       int db_irq;
-       const struct edac_device_prv_data *data;
-       struct dentry *debugfs_dir;
-       char *edac_dev_name;
-};
+const struct edac_device_prv_data a10_ocramecc_data;
+const struct edac_device_prv_data a10_l2ecc_data;
 
 static irqreturn_t altr_edac_device_handler(int irq, void *dev_id)
 {
@@ -665,8 +625,9 @@ static ssize_t altr_edac_device_trig(struct file *file,
                if (ACCESS_ONCE(ptemp[i]))
                        result = -1;
                /* Toggle Error bit (it is latched), leave ECC enabled */
-               writel(error_mask, drvdata->base);
-               writel(priv->ecc_enable_mask, drvdata->base);
+               writel(error_mask, (drvdata->base + priv->set_err_ofst));
+               writel(priv->ecc_enable_mask, (drvdata->base +
+                                              priv->set_err_ofst));
                ptemp[i] = i;
        }
        /* Ensure it has been written out */
@@ -694,6 +655,16 @@ static const struct file_operations altr_edac_device_inject_fops = {
        .llseek = generic_file_llseek,
 };
 
+static ssize_t altr_edac_a10_device_trig(struct file *file,
+                                        const char __user *user_buf,
+                                        size_t count, loff_t *ppos);
+
+static const struct file_operations altr_edac_a10_device_inject_fops = {
+       .open = simple_open,
+       .write = altr_edac_a10_device_trig,
+       .llseek = generic_file_llseek,
+};
+
 static void altr_create_edacdev_dbgfs(struct edac_device_ctl_info *edac_dci,
                                      const struct edac_device_prv_data *priv)
 {
@@ -708,17 +679,18 @@ static void altr_create_edacdev_dbgfs(struct edac_device_ctl_info *edac_dci,
 
        if (!edac_debugfs_create_file(priv->dbgfs_name, S_IWUSR,
                                      drvdata->debugfs_dir, edac_dci,
-                                     &altr_edac_device_inject_fops))
+                                     priv->inject_fops))
                debugfs_remove_recursive(drvdata->debugfs_dir);
 }
 
 static const struct of_device_id altr_edac_device_of_match[] = {
 #ifdef CONFIG_EDAC_ALTERA_L2C
-       { .compatible = "altr,socfpga-l2-ecc", .data = (void *)&l2ecc_data },
+       { .compatible = "altr,socfpga-l2-ecc", .data = &l2ecc_data },
+       { .compatible = "altr,socfpga-a10-l2-ecc", .data = &a10_l2ecc_data },
 #endif
 #ifdef CONFIG_EDAC_ALTERA_OCRAM
-       { .compatible = "altr,socfpga-ocram-ecc",
-         .data = (void *)&ocramecc_data },
+       { .compatible = "altr,socfpga-ocram-ecc", .data = &ocramecc_data },
+       { .compatible = "altr,socfpga-a10-ocram-ecc", .data = &a10_ocramecc_data },
 #endif
        {},
 };
@@ -789,7 +761,7 @@ static int altr_edac_device_probe(struct platform_device *pdev)
 
        /* Check specific dependencies for the module */
        if (drvdata->data->setup) {
-               res = drvdata->data->setup(pdev, drvdata->base);
+               res = drvdata->data->setup(drvdata);
                if (res)
                        goto fail1;
        }
@@ -856,6 +828,25 @@ module_platform_driver(altr_edac_device_driver);
 /*********************** OCRAM EDAC Device Functions *********************/
 
 #ifdef CONFIG_EDAC_ALTERA_OCRAM
+/*
+ *  Test for memory's ECC dependencies upon entry because platform specific
+ *  startup should have initialized the memory and enabled the ECC.
+ *  Can't turn on ECC here because accessing un-initialized memory will
+ *  cause CE/UE errors possibly causing an ABORT.
+ */
+static int altr_check_ecc_deps(struct altr_edac_device_dev *device)
+{
+       void __iomem  *base = device->base;
+       const struct edac_device_prv_data *prv = device->data;
+
+       if (readl(base + prv->ecc_en_ofst) & prv->ecc_enable_mask)
+               return 0;
+
+       edac_printk(KERN_ERR, EDAC_DEVICE,
+                   "%s: No ECC present or ECC disabled.\n",
+                   device->edac_dev_name);
+       return -ENODEV;
+}
 
 static void *ocram_alloc_mem(size_t size, void **other)
 {
@@ -891,36 +882,53 @@ static void ocram_free_mem(void *p, size_t size, void *other)
        gen_pool_free((struct gen_pool *)other, (u32)p, size);
 }
 
-/*
- * altr_ocram_check_deps()
- *     Test for OCRAM cache ECC dependencies upon entry because
- *     platform specific startup should have initialized the
- *     On-Chip RAM memory and enabled the ECC.
- *     Can't turn on ECC here because accessing un-initialized
- *     memory will cause CE/UE errors possibly causing an ABORT.
- */
-static int altr_ocram_check_deps(struct platform_device *pdev,
-                                void __iomem *base)
+static irqreturn_t altr_edac_a10_ecc_irq(struct altr_edac_device_dev *dci,
+                                        bool sberr)
 {
-       if (readl(base) & ALTR_OCR_ECC_EN)
-               return 0;
+       void __iomem  *base = dci->base;
 
-       edac_printk(KERN_ERR, EDAC_DEVICE,
-                   "OCRAM: No ECC present or ECC disabled.\n");
-       return -ENODEV;
+       if (sberr) {
+               writel(ALTR_A10_ECC_SERRPENA,
+                      base + ALTR_A10_ECC_INTSTAT_OFST);
+               edac_device_handle_ce(dci->edac_dev, 0, 0, dci->edac_dev_name);
+       } else {
+               writel(ALTR_A10_ECC_DERRPENA,
+                      base + ALTR_A10_ECC_INTSTAT_OFST);
+               edac_device_handle_ue(dci->edac_dev, 0, 0, dci->edac_dev_name);
+               panic("\nEDAC:ECC_DEVICE[Uncorrectable errors]\n");
+       }
+       return IRQ_HANDLED;
 }
 
 const struct edac_device_prv_data ocramecc_data = {
-       .setup = altr_ocram_check_deps,
+       .setup = altr_check_ecc_deps,
        .ce_clear_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_SERR),
        .ue_clear_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_DERR),
        .dbgfs_name = "altr_ocram_trigger",
        .alloc_mem = ocram_alloc_mem,
        .free_mem = ocram_free_mem,
        .ecc_enable_mask = ALTR_OCR_ECC_EN,
+       .ecc_en_ofst = ALTR_OCR_ECC_REG_OFFSET,
        .ce_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJS),
        .ue_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJD),
+       .set_err_ofst = ALTR_OCR_ECC_REG_OFFSET,
        .trig_alloc_sz = ALTR_TRIG_OCRAM_BYTE_SIZE,
+       .inject_fops = &altr_edac_device_inject_fops,
+};
+
+const struct edac_device_prv_data a10_ocramecc_data = {
+       .setup = altr_check_ecc_deps,
+       .ce_clear_mask = ALTR_A10_ECC_SERRPENA,
+       .ue_clear_mask = ALTR_A10_ECC_DERRPENA,
+       .irq_status_mask = A10_SYSMGR_ECC_INTSTAT_OCRAM,
+       .dbgfs_name = "altr_ocram_trigger",
+       .ecc_enable_mask = ALTR_A10_OCRAM_ECC_EN_CTL,
+       .ecc_en_ofst = ALTR_A10_ECC_CTRL_OFST,
+       .ce_set_mask = ALTR_A10_ECC_TSERRA,
+       .ue_set_mask = ALTR_A10_ECC_TDERRA,
+       .set_err_ofst = ALTR_A10_ECC_INTTEST_OFST,
+       .ecc_irq_handler = altr_edac_a10_ecc_irq,
+       .inject_fops = &altr_edac_a10_device_inject_fops,
 };
 
 #endif /* CONFIG_EDAC_ALTERA_OCRAM */
@@ -966,10 +974,13 @@ static void l2_free_mem(void *p, size_t size, void *other)
  *     Bail if ECC is not enabled.
  *     Note that L2 Cache Enable is forced at build time.
  */
-static int altr_l2_check_deps(struct platform_device *pdev,
-                             void __iomem *base)
+static int altr_l2_check_deps(struct altr_edac_device_dev *device)
 {
-       if (readl(base) & ALTR_L2_ECC_EN)
+       void __iomem *base = device->base;
+       const struct edac_device_prv_data *prv = device->data;
+
+       if ((readl(base) & prv->ecc_enable_mask) ==
+            prv->ecc_enable_mask)
                return 0;
 
        edac_printk(KERN_ERR, EDAC_DEVICE,
@@ -977,6 +988,24 @@ static int altr_l2_check_deps(struct platform_device *pdev,
        return -ENODEV;
 }
 
+static irqreturn_t altr_edac_a10_l2_irq(struct altr_edac_device_dev *dci,
+                                       bool sberr)
+{
+       if (sberr) {
+               regmap_write(dci->edac->ecc_mgr_map,
+                            A10_SYSGMR_MPU_CLEAR_L2_ECC_OFST,
+                            A10_SYSGMR_MPU_CLEAR_L2_ECC_SB);
+               edac_device_handle_ce(dci->edac_dev, 0, 0, dci->edac_dev_name);
+       } else {
+               regmap_write(dci->edac->ecc_mgr_map,
+                            A10_SYSGMR_MPU_CLEAR_L2_ECC_OFST,
+                            A10_SYSGMR_MPU_CLEAR_L2_ECC_MB);
+               edac_device_handle_ue(dci->edac_dev, 0, 0, dci->edac_dev_name);
+               panic("\nEDAC:ECC_DEVICE[Uncorrectable errors]\n");
+       }
+       return IRQ_HANDLED;
+}
+
 const struct edac_device_prv_data l2ecc_data = {
        .setup = altr_l2_check_deps,
        .ce_clear_mask = 0,
@@ -987,11 +1016,252 @@ const struct edac_device_prv_data l2ecc_data = {
        .ecc_enable_mask = ALTR_L2_ECC_EN,
        .ce_set_mask = (ALTR_L2_ECC_EN | ALTR_L2_ECC_INJS),
        .ue_set_mask = (ALTR_L2_ECC_EN | ALTR_L2_ECC_INJD),
+       .set_err_ofst = ALTR_L2_ECC_REG_OFFSET,
+       .trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE,
+       .inject_fops = &altr_edac_device_inject_fops,
+};
+
+const struct edac_device_prv_data a10_l2ecc_data = {
+       .setup = altr_l2_check_deps,
+       .ce_clear_mask = ALTR_A10_L2_ECC_SERR_CLR,
+       .ue_clear_mask = ALTR_A10_L2_ECC_MERR_CLR,
+       .irq_status_mask = A10_SYSMGR_ECC_INTSTAT_L2,
+       .dbgfs_name = "altr_l2_trigger",
+       .alloc_mem = l2_alloc_mem,
+       .free_mem = l2_free_mem,
+       .ecc_enable_mask = ALTR_A10_L2_ECC_EN_CTL,
+       .ce_set_mask = ALTR_A10_L2_ECC_CE_INJ_MASK,
+       .ue_set_mask = ALTR_A10_L2_ECC_UE_INJ_MASK,
+       .set_err_ofst = ALTR_A10_L2_ECC_INJ_OFST,
+       .ecc_irq_handler = altr_edac_a10_l2_irq,
        .trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE,
+       .inject_fops = &altr_edac_device_inject_fops,
 };
 
 #endif /* CONFIG_EDAC_ALTERA_L2C */
 
+/********************* Arria10 EDAC Device Functions *************************/
+
+/*
+ * The Arria10 EDAC Device Functions differ from the Cyclone5/Arria5
+ * because 2 IRQs are shared among the all ECC peripherals. The ECC
+ * manager manages the IRQs and the children.
+ * Based on xgene_edac.c peripheral code.
+ */
+
+static ssize_t altr_edac_a10_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;
+       const struct edac_device_prv_data *priv = drvdata->data;
+       void __iomem *set_addr = (drvdata->base + priv->set_err_ofst);
+       unsigned long flags;
+       u8 trig_type;
+
+       if (!user_buf || get_user(trig_type, user_buf))
+               return -EFAULT;
+
+       local_irq_save(flags);
+       if (trig_type == ALTR_UE_TRIGGER_CHAR)
+               writel(priv->ue_set_mask, set_addr);
+       else
+               writel(priv->ce_set_mask, set_addr);
+       /* Ensure the interrupt test bits are set */
+       wmb();
+       local_irq_restore(flags);
+
+       return count;
+}
+
+static irqreturn_t altr_edac_a10_irq_handler(int irq, void *dev_id)
+{
+       irqreturn_t rc = IRQ_NONE;
+       struct altr_arria10_edac *edac = dev_id;
+       struct altr_edac_device_dev *dci;
+       int irq_status;
+       bool sberr = (irq == edac->sb_irq) ? 1 : 0;
+       int sm_offset = sberr ? A10_SYSMGR_ECC_INTSTAT_SERR_OFST :
+                               A10_SYSMGR_ECC_INTSTAT_DERR_OFST;
+
+       regmap_read(edac->ecc_mgr_map, sm_offset, &irq_status);
+
+       if ((irq != edac->sb_irq) && (irq != edac->db_irq)) {
+               WARN_ON(1);
+       } else {
+               list_for_each_entry(dci, &edac->a10_ecc_devices, next) {
+                       if (irq_status & dci->data->irq_status_mask)
+                               rc = dci->data->ecc_irq_handler(dci, sberr);
+               }
+       }
+
+       return rc;
+}
+
+static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
+                                   struct device_node *np)
+{
+       struct edac_device_ctl_info *dci;
+       struct altr_edac_device_dev *altdev;
+       char *ecc_name = (char *)np->name;
+       struct resource res;
+       int edac_idx;
+       int rc = 0;
+       const struct edac_device_prv_data *prv;
+       /* Get matching node and check for valid result */
+       const struct of_device_id *pdev_id =
+               of_match_node(altr_edac_device_of_match, np);
+       if (IS_ERR_OR_NULL(pdev_id))
+               return -ENODEV;
+
+       /* Get driver specific data for this EDAC device */
+       prv = pdev_id->data;
+       if (IS_ERR_OR_NULL(prv))
+               return -ENODEV;
+
+       if (!devres_open_group(edac->dev, altr_edac_a10_device_add, GFP_KERNEL))
+               return -ENOMEM;
+
+       rc = of_address_to_resource(np, 0, &res);
+       if (rc < 0) {
+               edac_printk(KERN_ERR, EDAC_DEVICE,
+                           "%s: no resource address\n", ecc_name);
+               goto err_release_group;
+       }
+
+       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 EDAC device\n", ecc_name);
+               rc = -ENOMEM;
+               goto err_release_group;
+       }
+
+       altdev = dci->pvt_info;
+       dci->dev = edac->dev;
+       altdev->edac_dev_name = ecc_name;
+       altdev->edac_idx = edac_idx;
+       altdev->edac = edac;
+       altdev->edac_dev = dci;
+       altdev->data = prv;
+       altdev->ddev = *edac->dev;
+       dci->dev = &altdev->ddev;
+       dci->ctl_name = "Altera ECC Manager";
+       dci->mod_name = ecc_name;
+       dci->dev_name = ecc_name;
+
+       altdev->base = devm_ioremap_resource(edac->dev, &res);
+       if (IS_ERR(altdev->base)) {
+               rc = PTR_ERR(altdev->base);
+               goto err_release_group1;
+       }
+
+       /* Check specific dependencies for the module */
+       if (altdev->data->setup) {
+               rc = altdev->data->setup(altdev);
+               if (rc)
+                       goto err_release_group1;
+       }
+
+       rc = edac_device_add_device(dci);
+       if (rc) {
+               dev_err(edac->dev, "edac_device_add_device failed\n");
+               rc = -ENOMEM;
+               goto err_release_group1;
+       }
+
+       altr_create_edacdev_dbgfs(dci, prv);
+
+       list_add(&altdev->next, &edac->a10_ecc_devices);
+
+       devres_remove_group(edac->dev, altr_edac_a10_device_add);
+
+       return 0;
+
+err_release_group1:
+       edac_device_free_ctl_info(dci);
+err_release_group:
+       edac_printk(KERN_ALERT, EDAC_DEVICE, "%s: %d\n", __func__, __LINE__);
+       devres_release_group(edac->dev, NULL);
+       edac_printk(KERN_ERR, EDAC_DEVICE,
+                   "%s:Error setting up EDAC device: %d\n", ecc_name, rc);
+
+       return rc;
+}
+
+static int altr_edac_a10_probe(struct platform_device *pdev)
+{
+       struct altr_arria10_edac *edac;
+       struct device_node *child;
+       int rc;
+
+       edac = devm_kzalloc(&pdev->dev, sizeof(*edac), GFP_KERNEL);
+       if (!edac)
+               return -ENOMEM;
+
+       edac->dev = &pdev->dev;
+       platform_set_drvdata(pdev, edac);
+       INIT_LIST_HEAD(&edac->a10_ecc_devices);
+
+       edac->ecc_mgr_map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+                                                       "altr,sysmgr-syscon");
+       if (IS_ERR(edac->ecc_mgr_map)) {
+               edac_printk(KERN_ERR, EDAC_DEVICE,
+                           "Unable to get syscon altr,sysmgr-syscon\n");
+               return PTR_ERR(edac->ecc_mgr_map);
+       }
+
+       edac->sb_irq = platform_get_irq(pdev, 0);
+       rc = devm_request_irq(&pdev->dev, edac->sb_irq,
+                             altr_edac_a10_irq_handler,
+                             IRQF_SHARED, dev_name(&pdev->dev), edac);
+       if (rc) {
+               edac_printk(KERN_ERR, EDAC_DEVICE, "No SBERR IRQ resource\n");
+               return rc;
+       }
+
+       edac->db_irq = platform_get_irq(pdev, 1);
+       rc = devm_request_irq(&pdev->dev, edac->db_irq,
+                             altr_edac_a10_irq_handler,
+                             IRQF_SHARED, dev_name(&pdev->dev), edac);
+       if (rc) {
+               edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n");
+               return rc;
+       }
+
+       for_each_child_of_node(pdev->dev.of_node, child) {
+               if (!of_device_is_available(child))
+                       continue;
+               if (of_device_is_compatible(child, "altr,socfpga-a10-l2-ecc"))
+                       altr_edac_a10_device_add(edac, child);
+               else if (of_device_is_compatible(child,
+                                                "altr,socfpga-a10-ocram-ecc"))
+                       altr_edac_a10_device_add(edac, child);
+       }
+
+       return 0;
+}
+
+static const struct of_device_id altr_edac_a10_of_match[] = {
+       { .compatible = "altr,socfpga-a10-ecc-manager" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, altr_edac_a10_of_match);
+
+static struct platform_driver altr_edac_a10_driver = {
+       .probe =  altr_edac_a10_probe,
+       .driver = {
+               .name = "socfpga_a10_ecc_manager",
+               .of_match_table = altr_edac_a10_of_match,
+       },
+};
+module_platform_driver(altr_edac_a10_driver);
+
 MODULE_LICENSE("GPL v2");
 MODULE_AUTHOR("Thor Thayer");
 MODULE_DESCRIPTION("EDAC Driver for Altera Memories");