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>
This commit is contained in:
parent
5e40cd4d25
commit
911049845d
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue