|
|
@@ -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);
|
|
|
|