diff mbox

[RFC,6/7] EDAC: Add driver for the AURORA L2 cache

Message ID 20170609083123.6539-7-jlu@pengutronix.de (mailing list archive)
State New, archived
Headers show

Commit Message

Jan Lübbe June 9, 2017, 8:31 a.m. UTC
Signed-off-by: Jan Luebbe <jlu@pengutronix.de>
---
 arch/arm/include/asm/hardware/cache-aurora-l2.h |  49 +++++
 drivers/edac/Kconfig                            |   7 +
 drivers/edac/Makefile                           |   1 +
 drivers/edac/aurora_l2_edac.c                   | 252 ++++++++++++++++++++++++
 4 files changed, 309 insertions(+)
 create mode 100644 drivers/edac/aurora_l2_edac.c

Comments

Chris Packham June 12, 2017, 1:26 a.m. UTC | #1
Hi Jan,

Couple of suggestions below.

On 09/06/17 20:53, Jan Luebbe wrote:
> Signed-off-by: Jan Luebbe <jlu@pengutronix.de>
> ---
>   arch/arm/include/asm/hardware/cache-aurora-l2.h |  49 +++++
>   drivers/edac/Kconfig                            |   7 +
>   drivers/edac/Makefile                           |   1 +
>   drivers/edac/aurora_l2_edac.c                   | 252 ++++++++++++++++++++++++
>   4 files changed, 309 insertions(+)
>   create mode 100644 drivers/edac/aurora_l2_edac.c
> 
> diff --git a/arch/arm/include/asm/hardware/cache-aurora-l2.h b/arch/arm/include/asm/hardware/cache-aurora-l2.h
> index dc5c479ec4c3..80e4aad136df 100644
> --- a/arch/arm/include/asm/hardware/cache-aurora-l2.h
> +++ b/arch/arm/include/asm/hardware/cache-aurora-l2.h
> @@ -14,6 +14,10 @@
>   #ifndef __ASM_ARM_HARDWARE_AURORA_L2_H
>   #define __ASM_ARM_HARDWARE_AURORA_L2_H
>   
> +/* Parity and ECC are configured in L2X0_AUX_CTRL */
> +#define AURORA_CTRL_PARITY_EN	(1 << 21)
> +#define AURORA_CTRL_ECC_EN	(1 << 20)
> +
>   #define AURORA_SYNC_REG		    0x700
>   #define AURORA_RANGE_BASE_ADDR_REG  0x720
>   #define AURORA_FLUSH_PHY_ADDR_REG   0x7f0
> @@ -41,6 +45,51 @@
>   #define AURORA_ACR_FORCE_WRITE_THRO_POLICY	\
>   	(2 << AURORA_ACR_FORCE_WRITE_POLICY_OFFSET)
>   
> +#define AURORA_ERR_CNT_REG          0x600
> +#define AURORA_ERR_ATTR_CAP_REG     0x608
> +#define AURORA_ERR_ADDR_CAP_REG     0x60c
> +#define AURORA_ERR_WAY_CAP_REG      0x610
> +#define AURORA_ERR_INJECT_CTL_REG   0x614
> +#define AURORA_ERR_INJECT_MASK_REG  0x618

In my version I re-sized the resource to absorb the 0x600 offset 
(something I borrowed from mpc85xx_edac.c). I'm not sure if it's a good 
practice but that may be one way around the problem of sharing the 
memory region with other drivers.

> +
> +#define AURORA_ERR_CNT_CLR_OFFSET         31
> +#define AURORA_ERR_CNT_CLR		   \
> +	(0x1 << AURORA_ERR_CNT_CLR_OFFSET)
> +#define AURORA_ERR_CNT_UE_OFFSET          16
> +#define AURORA_ERR_CNT_UE_MASK             \
> +	(0x7fff << AURORA_ERR_CNT_UE_OFFSET)
> +#define AURORA_ERR_CNT_CE_OFFSET            0
> +#define AURORA_ERR_CNT_CE_MASK              \
> +	(0xffff << AURORA_ERR_CNT_CE_OFFSET)
> +
> +#define AURORA_ERR_ATTR_CAP_ERR_SOURCE_OFFSET       16
> +#define AURORA_ERR_ATTR_CAP_ERR_SOURCE_MASK          \
> +	(0x7 << AURORA_ERR_ATTR_CAP_ERR_SOURCE_OFFSET)
> +#define AURORA_ERR_ATTR_CAP_TRANS_TYPE_OFFSET       12
> +#define AURORA_ERR_ATTR_CAP_TRANS_TYPE_MASK          \
> +	(0xf << AURORA_ERR_ATTR_CAP_TRANS_TYPE_OFFSET)
> +#define AURORA_ERR_ATTR_CAP_ERR_TYPE_OFFSET          8
> +#define AURORA_ERR_ATTR_CAP_ERR_TYPE_MASK            \
> +	(0x3 << AURORA_ERR_ATTR_CAP_ERR_TYPE_OFFSET)
> +#define AURORA_ERR_ATTR_CAP_VALID_OFFSET             0
> +#define AURORA_ERR_ATTR_CAP_VALID                    \
> +	(0x1 << AURORA_ERR_ATTR_CAP_VALID_OFFSET)
> +
> +#define AURORA_ERR_ADDR_CAP_ADDR_MASK 0xffffffe0
> +
> +#define AURORA_ERR_WAY_CAP_INDEX_OFFSET          8
> +#define AURORA_ERR_WAY_CAP_INDEX_MASK            \
> +	(0xfff << AURORA_ERR_WAY_CAP_INDEX_OFFSET)
> +#define AURORA_ERR_WAY_CAP_WAY_OFFSET          1
> +#define AURORA_ERR_WAY_CAP_WAY_MASK            \
> +	(0xf << AURORA_ERR_WAY_CAP_WAY_OFFSET)
> +
> +#define AURORA_ERR_INJECT_CTL_ADDR_MASK 0xfffffff0
> +#define AURORA_ERR_ATTR_CAP_TRANS_TYPE_OFFSET   12
> +#define AURORA_ERR_INJECT_CTL_EN_MASK          0x3
> +#define AURORA_ERR_INJECT_CTL_EN_PARITY        0x2
> +#define AURORA_ERR_INJECT_CTL_EN_ECC           0x1
> +
>   #define AURORA_MAX_RANGE_SIZE	1024
>   
>   #define AURORA_WAY_SIZE_SHIFT	2
> diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig
> index 96afb2aeed18..8d9f680c8545 100644
> --- a/drivers/edac/Kconfig
> +++ b/drivers/edac/Kconfig
> @@ -443,6 +443,13 @@ config EDAC_ALTERA_SDMMC
>   	  Support for error detection and correction on the
>   	  Altera SDMMC FIFO Memory for Altera SoCs.
>   
> +config EDAC_AURORA_L2
> +	bool "Marvell AURORA L2 Cache ECC"
> +	depends on ARCH_MVEBU

Does this also need to depend on OF or is that a given these days?

> +	help
> +	  Support for error correction and detection on the Marvell AURORA L2
> +	  cache controller (as found on ARMADA XP).
> +
>   config EDAC_SYNOPSYS
>   	tristate "Synopsys DDR Memory Controller"
>   	depends on ARCH_ZYNQ
> diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile
> index 0fd9ffa63299..04654da04fc9 100644
> --- a/drivers/edac/Makefile
> +++ b/drivers/edac/Makefile
> @@ -76,5 +76,6 @@ obj-$(CONFIG_EDAC_OCTEON_PCI)		+= octeon_edac-pci.o
>   obj-$(CONFIG_EDAC_THUNDERX)		+= thunderx_edac.o
>   
>   obj-$(CONFIG_EDAC_ALTERA)		+= altera_edac.o
> +obj-$(CONFIG_EDAC_AURORA_L2)		+= aurora_l2_edac.o
>   obj-$(CONFIG_EDAC_SYNOPSYS)		+= synopsys_edac.o
>   obj-$(CONFIG_EDAC_XGENE)		+= xgene_edac.o
> diff --git a/drivers/edac/aurora_l2_edac.c b/drivers/edac/aurora_l2_edac.c
> new file mode 100644
> index 000000000000..8bb0ca03e5de
> --- /dev/null
> +++ b/drivers/edac/aurora_l2_edac.c
> @@ -0,0 +1,252 @@
> +/*
> + * Copyright (C) 2017 Pengutronix, Jan Luebbe <kernel@pengutronix.de>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/edac.h>
> +#include <linux/of_platform.h>
> +
> +#include <asm/hardware/cache-l2x0.h>
> +#include <asm/hardware/cache-aurora-l2.h>
> +
> +#include "edac_device.h"
> +#include "edac_module.h"
> +
> +struct aurora_l2_edac_drvdata {
> +	void __iomem *base;
> +
> +	char msg[128];
> +
> +	/* error injection via debugfs */
> +	uint32_t inject_addr;
> +	uint32_t inject_mask;
> +	uint8_t inject_ctl;
> +
> +	struct dentry *debugfs;
> +};
> +
> +static void aurora_l2_edac_inject(struct aurora_l2_edac_drvdata *drvdata)
> +{
> +	drvdata->inject_addr &= AURORA_ERR_INJECT_CTL_ADDR_MASK;
> +	drvdata->inject_ctl &= AURORA_ERR_INJECT_CTL_EN_MASK;
> +	writel(0, drvdata->base + AURORA_ERR_INJECT_CTL_REG);
> +	writel(drvdata->inject_mask,
> +	       drvdata->base + AURORA_ERR_INJECT_MASK_REG);
> +	writel(drvdata->inject_addr | drvdata->inject_ctl,
> +	       drvdata->base + AURORA_ERR_INJECT_CTL_REG);
> +}
> +
> +static void aurora_l2_edac_check(struct edac_device_ctl_info *dci)
> +{
> +	struct aurora_l2_edac_drvdata *drvdata = dci->pvt_info;
> +	uint32_t cnt, attr_cap, addr_cap, way_cap;
> +	unsigned int cnt_ce, cnt_ue;
> +
> +	cnt = readl(drvdata->base + AURORA_ERR_CNT_REG);
> +	attr_cap = readl(drvdata->base + AURORA_ERR_ATTR_CAP_REG);
> +	addr_cap = readl(drvdata->base + AURORA_ERR_ADDR_CAP_REG);
> +	way_cap = readl(drvdata->base + AURORA_ERR_WAY_CAP_REG);
> +
> +	cnt_ce = (cnt & AURORA_ERR_CNT_CE_MASK) >> AURORA_ERR_CNT_CE_OFFSET;
> +	cnt_ue = (cnt & AURORA_ERR_CNT_UE_MASK) >> AURORA_ERR_CNT_UE_OFFSET;
> +	/* clear error counter registers */
> +	if (cnt_ce || cnt_ue)
> +		writel(AURORA_ERR_CNT_CLR, drvdata->base + AURORA_ERR_CNT_REG);
> +
> +	if (attr_cap & AURORA_ERR_ATTR_CAP_VALID) {
> +		char *msg = drvdata->msg;

Is it worth tracking the size of msg and using snprintf?

		size_t len = sizeof(drvdata->msg);
		int n = 0;
...
		n += snprintf(msg+n, len-n, "src=CPU0 ");

> +		switch ((attr_cap & AURORA_ERR_ATTR_CAP_ERR_SOURCE_MASK)
> +			>> AURORA_ERR_ATTR_CAP_ERR_SOURCE_OFFSET) {
> +		case 0:
> +			msg += sprintf(msg, "src=CPU0 ");
> +			break;
> +		case 1:
> +			msg += sprintf(msg, "src=CPU1 ");
> +			break;
> +		case 2:
> +			msg += sprintf(msg, "src=CPU2 ");
> +			break;
> +		case 3:
> +			msg += sprintf(msg, "src=CPU3 ");
> +			break;
> +		case 7:
> +			msg += sprintf(msg, "src=IO ");
> +			break;
> +		}
> +		/* msg size <= 9 */
> +		switch ((attr_cap & AURORA_ERR_ATTR_CAP_TRANS_TYPE_MASK)
> +			>> AURORA_ERR_ATTR_CAP_TRANS_TYPE_OFFSET) {
> +		case 0:
> +			msg += sprintf(msg, "txn=Data-Read ");
> +			break;
> +		case 1:
> +			msg += sprintf(msg, "txn=Isn-Read ");
> +			break;
> +		case 2:
> +			msg += sprintf(msg, "txn=Clean-Flush ");
> +			break;
> +		case 3:
> +			msg += sprintf(msg, "txn=Eviction ");
> +			break;
> +		case 4:
> +			msg += sprintf(msg, "txn=Read-Modify-Write ");
> +			break;
> +		}
> +		/* msg size <= 31 */
> +		switch ((attr_cap & AURORA_ERR_ATTR_CAP_ERR_TYPE_MASK)
> +			>> AURORA_ERR_ATTR_CAP_ERR_TYPE_OFFSET) {
> +		case 0:
> +			msg += sprintf(msg, "err=CorrECC ");
> +			break;
> +		case 1:
> +			msg += sprintf(msg, "err=UnCorrECC ");
> +			break;
> +		case 2:
> +			msg += sprintf(msg, "err=TagParity ");
> +			break;
> +		}
> +		/* msg size <= 45 */
> +		msg +=
> +		    sprintf(msg, "addr=0x%x ",
> +			    addr_cap & AURORA_ERR_ADDR_CAP_ADDR_MASK);
> +		msg +=
> +		    sprintf(msg, "index=0x%x ",
> +			    (way_cap & AURORA_ERR_WAY_CAP_INDEX_MASK)
> +			    >> AURORA_ERR_WAY_CAP_INDEX_OFFSET);
> +		msg +=
> +		    sprintf(msg, "way=0x%x",
> +			    (way_cap & AURORA_ERR_WAY_CAP_WAY_MASK)
> +			    >> AURORA_ERR_WAY_CAP_WAY_OFFSET);
> +		/* msg size <= 92 */
> +		/* clear error capture registers */
> +		writel(AURORA_ERR_ATTR_CAP_VALID,
> +		       drvdata->base + AURORA_ERR_ATTR_CAP_REG);
> +		if ((attr_cap & AURORA_ERR_ATTR_CAP_ERR_TYPE_MASK)
> +		    >> AURORA_ERR_ATTR_CAP_ERR_TYPE_OFFSET) {
> +			/* UnCorrECC or TagParity */
> +			if (cnt_ue)
> +				cnt_ue--;
> +			edac_device_handle_ue(dci, 0, 0, drvdata->msg);
> +		} else {
> +			if (cnt_ce)
> +				cnt_ce--;
> +			edac_device_handle_ce(dci, 0, 0, drvdata->msg);
> +		}
> +	}
> +
> +	/* report remaining errors */
> +	while (cnt_ue--)
> +		edac_device_handle_ue(dci, 0, 0,
> +				      "details unavailable (multiple errors)");
> +	while (cnt_ce--)
> +		edac_device_handle_ue(dci, 0, 0,
> +				      "details unavailable (multiple errors)");
> +
> +	aurora_l2_edac_inject(drvdata);

If/when this supports interrupts we'd need to consider how to do this 
(another debugfs file?). Perhaps it's worth creating 
aurora_l2_edac_poll() which would be implemented as

static void aurora_l2_edac_poll(struct edac_device_ctl_info *dci)
{
	struct aurora_l2_edac_drvdata *drvdata = dci->pvt_info;

	aurora_l2_edac_check(dci);
	aurora_l2_edac_inject(drvdata);
}

> +}
> +
> +static const struct of_device_id aurora_l2_edac_of_match[] = {
> +	{.compatible = "marvell,aurora-system-cache",},
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(of, aurora_l2_edac_of_match);
> +
> +static int aurora_l2_edac_probe(struct platform_device *pdev)
> +{
> +	const struct of_device_id *id;
> +	struct edac_device_ctl_info *dci;
> +	struct aurora_l2_edac_drvdata *drvdata;
> +	struct resource *r;
> +	uint32_t l2x0_aux_ctrl;
> +
> +	dci =
> +	    devm_edac_device_alloc_ctl_info(&pdev->dev, sizeof(*drvdata), "cpu",
> +					    1, "L", 1, 2, NULL, 0, 0);
> +	if (!dci)
> +		return -ENOMEM;
> +
> +	drvdata = dci->pvt_info;
> +	dci->dev = &pdev->dev;
> +	platform_set_drvdata(pdev, dci);
> +
> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!r) {
> +		dev_err(&pdev->dev, "Unable to get mem resource\n");
> +		return -ENODEV;
> +	}
> +
> +	drvdata->base = devm_ioremap_resource(&pdev->dev, r);
> +	if (IS_ERR(drvdata->base)) {
> +		dev_err(&pdev->dev, "Unable to map regs\n");
> +		return PTR_ERR(drvdata->base);
> +	}
> +
> +	l2x0_aux_ctrl = readl(drvdata->base + L2X0_AUX_CTRL);
> +	if (!(l2x0_aux_ctrl & AURORA_CTRL_PARITY_EN))
> +		dev_warn(&pdev->dev, "tag parity is not enabled");
> +	if (!(l2x0_aux_ctrl & AURORA_CTRL_ECC_EN))
> +		dev_warn(&pdev->dev, "data ECC is not enabled");
> +
> +	id = of_match_device(aurora_l2_edac_of_match, &pdev->dev);
> +	dci->edac_check = aurora_l2_edac_check;
> +	dci->mod_name = pdev->dev.driver->name;
> +	dci->ctl_name = id ? id->compatible : "unknown";
> +	dci->dev_name = dev_name(&pdev->dev);
> +
> +	/* clear registers */
> +	writel(AURORA_ERR_CNT_CLR, drvdata->base + AURORA_ERR_CNT_REG);
> +	writel(AURORA_ERR_ATTR_CAP_VALID,
> +	       drvdata->base + AURORA_ERR_ATTR_CAP_REG);
> +
> +	if (devm_edac_device_add_device(&pdev->dev, dci))
> +		return -EINVAL;
> +
> +	drvdata->debugfs = edac_debugfs_create_dir(dev_name(&pdev->dev));
> +	if (drvdata->debugfs) {
> +		edac_debugfs_create_x32("inject_addr", S_IRUGO | S_IWUSR,
> +					drvdata->debugfs,
> +					&drvdata->inject_addr);
> +		edac_debugfs_create_x32("inject_mask", S_IRUGO | S_IWUSR,
> +					drvdata->debugfs,
> +					&drvdata->inject_mask);
> +		edac_debugfs_create_x8("inject_ctl", S_IRUGO | S_IWUSR,
> +				       drvdata->debugfs, &drvdata->inject_ctl);
> +	}
> +
> +	return 0;
> +}
> +
> +static int aurora_l2_edac_remove(struct platform_device *pdev)
> +{
> +	struct edac_device_ctl_info *dci = platform_get_drvdata(pdev);
> +	struct aurora_l2_edac_drvdata *drvdata = dci->pvt_info;
> +
> +	edac_debugfs_remove_recursive(drvdata->debugfs);
> +	return 0;
> +}
> +
> +static struct platform_driver aurora_l2_edac_driver = {
> +	.probe = aurora_l2_edac_probe,
> +	.remove = aurora_l2_edac_remove,
> +	.driver = {
> +		.name = "aurora_l2_edac",
> +		.of_match_table = aurora_l2_edac_of_match,

	.of_match_table = of_match_ptr(aurora_l2_edac_of_match), ??

I don't know if we care about non-OF configs for this driver. The SDRAM 
one on the other hand does have at least one non-OF candidate (MV78200).

> +	},
> +};
> +
> +module_platform_driver(aurora_l2_edac_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Pengutronix");
> +MODULE_DESCRIPTION("EDAC Driver for Marvell Aurora L2 Cache");
>

With or without any of my suggestions

Reviewed-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
diff mbox

Patch

diff --git a/arch/arm/include/asm/hardware/cache-aurora-l2.h b/arch/arm/include/asm/hardware/cache-aurora-l2.h
index dc5c479ec4c3..80e4aad136df 100644
--- a/arch/arm/include/asm/hardware/cache-aurora-l2.h
+++ b/arch/arm/include/asm/hardware/cache-aurora-l2.h
@@ -14,6 +14,10 @@ 
 #ifndef __ASM_ARM_HARDWARE_AURORA_L2_H
 #define __ASM_ARM_HARDWARE_AURORA_L2_H
 
+/* Parity and ECC are configured in L2X0_AUX_CTRL */
+#define AURORA_CTRL_PARITY_EN	(1 << 21)
+#define AURORA_CTRL_ECC_EN	(1 << 20)
+
 #define AURORA_SYNC_REG		    0x700
 #define AURORA_RANGE_BASE_ADDR_REG  0x720
 #define AURORA_FLUSH_PHY_ADDR_REG   0x7f0
@@ -41,6 +45,51 @@ 
 #define AURORA_ACR_FORCE_WRITE_THRO_POLICY	\
 	(2 << AURORA_ACR_FORCE_WRITE_POLICY_OFFSET)
 
+#define AURORA_ERR_CNT_REG          0x600
+#define AURORA_ERR_ATTR_CAP_REG     0x608
+#define AURORA_ERR_ADDR_CAP_REG     0x60c
+#define AURORA_ERR_WAY_CAP_REG      0x610
+#define AURORA_ERR_INJECT_CTL_REG   0x614
+#define AURORA_ERR_INJECT_MASK_REG  0x618
+
+#define AURORA_ERR_CNT_CLR_OFFSET         31
+#define AURORA_ERR_CNT_CLR		   \
+	(0x1 << AURORA_ERR_CNT_CLR_OFFSET)
+#define AURORA_ERR_CNT_UE_OFFSET          16
+#define AURORA_ERR_CNT_UE_MASK             \
+	(0x7fff << AURORA_ERR_CNT_UE_OFFSET)
+#define AURORA_ERR_CNT_CE_OFFSET            0
+#define AURORA_ERR_CNT_CE_MASK              \
+	(0xffff << AURORA_ERR_CNT_CE_OFFSET)
+
+#define AURORA_ERR_ATTR_CAP_ERR_SOURCE_OFFSET       16
+#define AURORA_ERR_ATTR_CAP_ERR_SOURCE_MASK          \
+	(0x7 << AURORA_ERR_ATTR_CAP_ERR_SOURCE_OFFSET)
+#define AURORA_ERR_ATTR_CAP_TRANS_TYPE_OFFSET       12
+#define AURORA_ERR_ATTR_CAP_TRANS_TYPE_MASK          \
+	(0xf << AURORA_ERR_ATTR_CAP_TRANS_TYPE_OFFSET)
+#define AURORA_ERR_ATTR_CAP_ERR_TYPE_OFFSET          8
+#define AURORA_ERR_ATTR_CAP_ERR_TYPE_MASK            \
+	(0x3 << AURORA_ERR_ATTR_CAP_ERR_TYPE_OFFSET)
+#define AURORA_ERR_ATTR_CAP_VALID_OFFSET             0
+#define AURORA_ERR_ATTR_CAP_VALID                    \
+	(0x1 << AURORA_ERR_ATTR_CAP_VALID_OFFSET)
+
+#define AURORA_ERR_ADDR_CAP_ADDR_MASK 0xffffffe0
+
+#define AURORA_ERR_WAY_CAP_INDEX_OFFSET          8
+#define AURORA_ERR_WAY_CAP_INDEX_MASK            \
+	(0xfff << AURORA_ERR_WAY_CAP_INDEX_OFFSET)
+#define AURORA_ERR_WAY_CAP_WAY_OFFSET          1
+#define AURORA_ERR_WAY_CAP_WAY_MASK            \
+	(0xf << AURORA_ERR_WAY_CAP_WAY_OFFSET)
+
+#define AURORA_ERR_INJECT_CTL_ADDR_MASK 0xfffffff0
+#define AURORA_ERR_ATTR_CAP_TRANS_TYPE_OFFSET   12
+#define AURORA_ERR_INJECT_CTL_EN_MASK          0x3
+#define AURORA_ERR_INJECT_CTL_EN_PARITY        0x2
+#define AURORA_ERR_INJECT_CTL_EN_ECC           0x1
+
 #define AURORA_MAX_RANGE_SIZE	1024
 
 #define AURORA_WAY_SIZE_SHIFT	2
diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig
index 96afb2aeed18..8d9f680c8545 100644
--- a/drivers/edac/Kconfig
+++ b/drivers/edac/Kconfig
@@ -443,6 +443,13 @@  config EDAC_ALTERA_SDMMC
 	  Support for error detection and correction on the
 	  Altera SDMMC FIFO Memory for Altera SoCs.
 
+config EDAC_AURORA_L2
+	bool "Marvell AURORA L2 Cache ECC"
+	depends on ARCH_MVEBU
+	help
+	  Support for error correction and detection on the Marvell AURORA L2
+	  cache controller (as found on ARMADA XP).
+
 config EDAC_SYNOPSYS
 	tristate "Synopsys DDR Memory Controller"
 	depends on ARCH_ZYNQ
diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile
index 0fd9ffa63299..04654da04fc9 100644
--- a/drivers/edac/Makefile
+++ b/drivers/edac/Makefile
@@ -76,5 +76,6 @@  obj-$(CONFIG_EDAC_OCTEON_PCI)		+= octeon_edac-pci.o
 obj-$(CONFIG_EDAC_THUNDERX)		+= thunderx_edac.o
 
 obj-$(CONFIG_EDAC_ALTERA)		+= altera_edac.o
+obj-$(CONFIG_EDAC_AURORA_L2)		+= aurora_l2_edac.o
 obj-$(CONFIG_EDAC_SYNOPSYS)		+= synopsys_edac.o
 obj-$(CONFIG_EDAC_XGENE)		+= xgene_edac.o
diff --git a/drivers/edac/aurora_l2_edac.c b/drivers/edac/aurora_l2_edac.c
new file mode 100644
index 000000000000..8bb0ca03e5de
--- /dev/null
+++ b/drivers/edac/aurora_l2_edac.c
@@ -0,0 +1,252 @@ 
+/*
+ * Copyright (C) 2017 Pengutronix, Jan Luebbe <kernel@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/edac.h>
+#include <linux/of_platform.h>
+
+#include <asm/hardware/cache-l2x0.h>
+#include <asm/hardware/cache-aurora-l2.h>
+
+#include "edac_device.h"
+#include "edac_module.h"
+
+struct aurora_l2_edac_drvdata {
+	void __iomem *base;
+
+	char msg[128];
+
+	/* error injection via debugfs */
+	uint32_t inject_addr;
+	uint32_t inject_mask;
+	uint8_t inject_ctl;
+
+	struct dentry *debugfs;
+};
+
+static void aurora_l2_edac_inject(struct aurora_l2_edac_drvdata *drvdata)
+{
+	drvdata->inject_addr &= AURORA_ERR_INJECT_CTL_ADDR_MASK;
+	drvdata->inject_ctl &= AURORA_ERR_INJECT_CTL_EN_MASK;
+	writel(0, drvdata->base + AURORA_ERR_INJECT_CTL_REG);
+	writel(drvdata->inject_mask,
+	       drvdata->base + AURORA_ERR_INJECT_MASK_REG);
+	writel(drvdata->inject_addr | drvdata->inject_ctl,
+	       drvdata->base + AURORA_ERR_INJECT_CTL_REG);
+}
+
+static void aurora_l2_edac_check(struct edac_device_ctl_info *dci)
+{
+	struct aurora_l2_edac_drvdata *drvdata = dci->pvt_info;
+	uint32_t cnt, attr_cap, addr_cap, way_cap;
+	unsigned int cnt_ce, cnt_ue;
+
+	cnt = readl(drvdata->base + AURORA_ERR_CNT_REG);
+	attr_cap = readl(drvdata->base + AURORA_ERR_ATTR_CAP_REG);
+	addr_cap = readl(drvdata->base + AURORA_ERR_ADDR_CAP_REG);
+	way_cap = readl(drvdata->base + AURORA_ERR_WAY_CAP_REG);
+
+	cnt_ce = (cnt & AURORA_ERR_CNT_CE_MASK) >> AURORA_ERR_CNT_CE_OFFSET;
+	cnt_ue = (cnt & AURORA_ERR_CNT_UE_MASK) >> AURORA_ERR_CNT_UE_OFFSET;
+	/* clear error counter registers */
+	if (cnt_ce || cnt_ue)
+		writel(AURORA_ERR_CNT_CLR, drvdata->base + AURORA_ERR_CNT_REG);
+
+	if (attr_cap & AURORA_ERR_ATTR_CAP_VALID) {
+		char *msg = drvdata->msg;
+		switch ((attr_cap & AURORA_ERR_ATTR_CAP_ERR_SOURCE_MASK)
+			>> AURORA_ERR_ATTR_CAP_ERR_SOURCE_OFFSET) {
+		case 0:
+			msg += sprintf(msg, "src=CPU0 ");
+			break;
+		case 1:
+			msg += sprintf(msg, "src=CPU1 ");
+			break;
+		case 2:
+			msg += sprintf(msg, "src=CPU2 ");
+			break;
+		case 3:
+			msg += sprintf(msg, "src=CPU3 ");
+			break;
+		case 7:
+			msg += sprintf(msg, "src=IO ");
+			break;
+		}
+		/* msg size <= 9 */
+		switch ((attr_cap & AURORA_ERR_ATTR_CAP_TRANS_TYPE_MASK)
+			>> AURORA_ERR_ATTR_CAP_TRANS_TYPE_OFFSET) {
+		case 0:
+			msg += sprintf(msg, "txn=Data-Read ");
+			break;
+		case 1:
+			msg += sprintf(msg, "txn=Isn-Read ");
+			break;
+		case 2:
+			msg += sprintf(msg, "txn=Clean-Flush ");
+			break;
+		case 3:
+			msg += sprintf(msg, "txn=Eviction ");
+			break;
+		case 4:
+			msg += sprintf(msg, "txn=Read-Modify-Write ");
+			break;
+		}
+		/* msg size <= 31 */
+		switch ((attr_cap & AURORA_ERR_ATTR_CAP_ERR_TYPE_MASK)
+			>> AURORA_ERR_ATTR_CAP_ERR_TYPE_OFFSET) {
+		case 0:
+			msg += sprintf(msg, "err=CorrECC ");
+			break;
+		case 1:
+			msg += sprintf(msg, "err=UnCorrECC ");
+			break;
+		case 2:
+			msg += sprintf(msg, "err=TagParity ");
+			break;
+		}
+		/* msg size <= 45 */
+		msg +=
+		    sprintf(msg, "addr=0x%x ",
+			    addr_cap & AURORA_ERR_ADDR_CAP_ADDR_MASK);
+		msg +=
+		    sprintf(msg, "index=0x%x ",
+			    (way_cap & AURORA_ERR_WAY_CAP_INDEX_MASK)
+			    >> AURORA_ERR_WAY_CAP_INDEX_OFFSET);
+		msg +=
+		    sprintf(msg, "way=0x%x",
+			    (way_cap & AURORA_ERR_WAY_CAP_WAY_MASK)
+			    >> AURORA_ERR_WAY_CAP_WAY_OFFSET);
+		/* msg size <= 92 */
+		/* clear error capture registers */
+		writel(AURORA_ERR_ATTR_CAP_VALID,
+		       drvdata->base + AURORA_ERR_ATTR_CAP_REG);
+		if ((attr_cap & AURORA_ERR_ATTR_CAP_ERR_TYPE_MASK)
+		    >> AURORA_ERR_ATTR_CAP_ERR_TYPE_OFFSET) {
+			/* UnCorrECC or TagParity */
+			if (cnt_ue)
+				cnt_ue--;
+			edac_device_handle_ue(dci, 0, 0, drvdata->msg);
+		} else {
+			if (cnt_ce)
+				cnt_ce--;
+			edac_device_handle_ce(dci, 0, 0, drvdata->msg);
+		}
+	}
+
+	/* report remaining errors */
+	while (cnt_ue--)
+		edac_device_handle_ue(dci, 0, 0,
+				      "details unavailable (multiple errors)");
+	while (cnt_ce--)
+		edac_device_handle_ue(dci, 0, 0,
+				      "details unavailable (multiple errors)");
+
+	aurora_l2_edac_inject(drvdata);
+}
+
+static const struct of_device_id aurora_l2_edac_of_match[] = {
+	{.compatible = "marvell,aurora-system-cache",},
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, aurora_l2_edac_of_match);
+
+static int aurora_l2_edac_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *id;
+	struct edac_device_ctl_info *dci;
+	struct aurora_l2_edac_drvdata *drvdata;
+	struct resource *r;
+	uint32_t l2x0_aux_ctrl;
+
+	dci =
+	    devm_edac_device_alloc_ctl_info(&pdev->dev, sizeof(*drvdata), "cpu",
+					    1, "L", 1, 2, NULL, 0, 0);
+	if (!dci)
+		return -ENOMEM;
+
+	drvdata = dci->pvt_info;
+	dci->dev = &pdev->dev;
+	platform_set_drvdata(pdev, dci);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r) {
+		dev_err(&pdev->dev, "Unable to get mem resource\n");
+		return -ENODEV;
+	}
+
+	drvdata->base = devm_ioremap_resource(&pdev->dev, r);
+	if (IS_ERR(drvdata->base)) {
+		dev_err(&pdev->dev, "Unable to map regs\n");
+		return PTR_ERR(drvdata->base);
+	}
+
+	l2x0_aux_ctrl = readl(drvdata->base + L2X0_AUX_CTRL);
+	if (!(l2x0_aux_ctrl & AURORA_CTRL_PARITY_EN))
+		dev_warn(&pdev->dev, "tag parity is not enabled");
+	if (!(l2x0_aux_ctrl & AURORA_CTRL_ECC_EN))
+		dev_warn(&pdev->dev, "data ECC is not enabled");
+
+	id = of_match_device(aurora_l2_edac_of_match, &pdev->dev);
+	dci->edac_check = aurora_l2_edac_check;
+	dci->mod_name = pdev->dev.driver->name;
+	dci->ctl_name = id ? id->compatible : "unknown";
+	dci->dev_name = dev_name(&pdev->dev);
+
+	/* clear registers */
+	writel(AURORA_ERR_CNT_CLR, drvdata->base + AURORA_ERR_CNT_REG);
+	writel(AURORA_ERR_ATTR_CAP_VALID,
+	       drvdata->base + AURORA_ERR_ATTR_CAP_REG);
+
+	if (devm_edac_device_add_device(&pdev->dev, dci))
+		return -EINVAL;
+
+	drvdata->debugfs = edac_debugfs_create_dir(dev_name(&pdev->dev));
+	if (drvdata->debugfs) {
+		edac_debugfs_create_x32("inject_addr", S_IRUGO | S_IWUSR,
+					drvdata->debugfs,
+					&drvdata->inject_addr);
+		edac_debugfs_create_x32("inject_mask", S_IRUGO | S_IWUSR,
+					drvdata->debugfs,
+					&drvdata->inject_mask);
+		edac_debugfs_create_x8("inject_ctl", S_IRUGO | S_IWUSR,
+				       drvdata->debugfs, &drvdata->inject_ctl);
+	}
+
+	return 0;
+}
+
+static int aurora_l2_edac_remove(struct platform_device *pdev)
+{
+	struct edac_device_ctl_info *dci = platform_get_drvdata(pdev);
+	struct aurora_l2_edac_drvdata *drvdata = dci->pvt_info;
+
+	edac_debugfs_remove_recursive(drvdata->debugfs);
+	return 0;
+}
+
+static struct platform_driver aurora_l2_edac_driver = {
+	.probe = aurora_l2_edac_probe,
+	.remove = aurora_l2_edac_remove,
+	.driver = {
+		.name = "aurora_l2_edac",
+		.of_match_table = aurora_l2_edac_of_match,
+	},
+};
+
+module_platform_driver(aurora_l2_edac_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Pengutronix");
+MODULE_DESCRIPTION("EDAC Driver for Marvell Aurora L2 Cache");