Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c79a14de authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull EDAC updates from Borislav Petkov:
 "This last cycle, Thor was busy adding Arria10 eth FIFO support to the
  altera_edac driver along with other improvements.  We have two
  cleanups/fixes too.

  Summary:

   - Altera Arria10 ethernet FIFO buffer support (Thor Thayer)

   - Minor cleanups"

* tag 'edac_for_4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/bp/bp:
  ARM: dts: Add Arria10 Ethernet EDAC devicetree entry
  EDAC, altera: Add Arria10 Ethernet EDAC support
  EDAC, altera: Add Arria10 ECC memory init functions
  Documentation: dt: socfpga: Add Arria10 Ethernet binding
  EDAC, altera: Drop some ifdeffery
  EDAC, altera: Add panic flag check to A10 IRQ
  EDAC, altera: Check parent status for Arria10 EDAC block
  EDAC, altera: Make all private data structures static
  EDAC: Correct channel count limit
  EDAC, amd64_edac: Init opstate at the proper time during init
  EDAC, altera: Handle Arria10 SDRAM child node
  EDAC, altera: Add ECC Manager IRQ controller support
  Documentation: dt: socfpga: Add interrupt-controller to ecc-manager
parents 6e8d666e a67adb32
Loading
Loading
Loading
Loading
+37 −1
Original line number Diff line number Diff line
@@ -61,7 +61,9 @@ Required Properties:
- #address-cells: must be 1
- #size-cells: must be 1
- interrupts : Should be single bit error interrupt, then double bit error
	interrupt. Note the rising edge type.
	interrupt.
- interrupt-controller : boolean indicator that ECC Manager is an interrupt controller
- #interrupt-cells : must be set to 2.
- ranges : standard definition, should translate from local addresses

Subcomponents:
@@ -70,11 +72,23 @@ L2 Cache ECC
Required Properties:
- compatible : Should be "altr,socfpga-a10-l2-ecc"
- reg : Address and size for ECC error interrupt clear registers.
- interrupts : Should be single bit error interrupt, then double bit error
	interrupt, in this order.

On-Chip RAM ECC
Required Properties:
- compatible : Should be "altr,socfpga-a10-ocram-ecc"
- reg        : Address and size for ECC block registers.
- interrupts : Should be single bit error interrupt, then double bit error
	interrupt, in this order.

Ethernet FIFO ECC
Required Properties:
- compatible      : Should be "altr,socfpga-eth-mac-ecc"
- reg             : Address and size for ECC block registers.
- altr,ecc-parent : phandle to parent Ethernet node.
- interrupts      : Should be single bit error interrupt, then double bit error
	interrupt, in this order.

Example:

@@ -85,15 +99,37 @@ Example:
		#size-cells = <1>;
		interrupts = <0 2 IRQ_TYPE_LEVEL_HIGH>,
			     <0 0 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-controller;
		#interrupt-cells = <2>;
		ranges;

		l2-ecc@ffd06010 {
			compatible = "altr,socfpga-a10-l2-ecc";
			reg = <0xffd06010 0x4>;
			interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
				     <32 IRQ_TYPE_LEVEL_HIGH>;
		};

		ocram-ecc@ff8c3000 {
			compatible = "altr,socfpga-a10-ocram-ecc";
			reg = <0xff8c3000 0x90>;
			interrupts = <1 IRQ_TYPE_LEVEL_HIGH>,
				     <33 IRQ_TYPE_LEVEL_HIGH> ;
		};

		emac0-rx-ecc@ff8c0800 {
			compatible = "altr,socfpga-eth-mac-ecc";
			reg = <0xff8c0800 0x400>;
			altr,ecc-parent = <&gmac0>;
			interrupts = <4 IRQ_TYPE_LEVEL_HIGH>,
				     <36 IRQ_TYPE_LEVEL_HIGH>;
		};

		emac0-tx-ecc@ff8c0c00 {
			compatible = "altr,socfpga-eth-mac-ecc";
			reg = <0xff8c0c00 0x400>;
			altr,ecc-parent = <&gmac0>;
			interrupts = <5 IRQ_TYPE_LEVEL_HIGH>,
				     <37 IRQ_TYPE_LEVEL_HIGH>;
		};
	};
+16 −0
Original line number Diff line number Diff line
@@ -621,6 +621,22 @@
				compatible = "altr,socfpga-a10-ocram-ecc";
				reg = <0xff8c3000 0x400>;
			};

			emac0-rx-ecc@ff8c0800 {
				compatible = "altr,socfpga-eth-mac-ecc";
				reg = <0xff8c0800 0x400>;
				altr,ecc-parent = <&gmac0>;
				interrupts = <4 IRQ_TYPE_LEVEL_HIGH>,
					     <36 IRQ_TYPE_LEVEL_HIGH>;
			};

			emac0-tx-ecc@ff8c0c00 {
				compatible = "altr,socfpga-eth-mac-ecc";
				reg = <0xff8c0c00 0x400>;
				altr,ecc-parent = <&gmac0>;
				interrupts = <5 IRQ_TYPE_LEVEL_HIGH>,
					     <37 IRQ_TYPE_LEVEL_HIGH>;
			};
		};

		rst: rstmgr@ffd05000 {
+7 −0
Original line number Diff line number Diff line
@@ -391,6 +391,13 @@ config EDAC_ALTERA_OCRAM
	  Support for error detection and correction on the
	  Altera On-Chip RAM Memory for Altera SoCs.

config EDAC_ALTERA_ETHERNET
	bool "Altera Ethernet FIFO ECC"
	depends on EDAC_ALTERA=y
	help
	  Support for error detection and correction on the
	  Altera Ethernet FIFO Memory for Altera SoCs.

config EDAC_SYNOPSYS
	tristate "Synopsys DDR Memory Controller"
	depends on EDAC_MM_EDAC && ARCH_ZYNQ
+423 −69
Original line number Diff line number Diff line
@@ -19,12 +19,15 @@

#include <asm/cacheflush.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/edac.h>
#include <linux/genalloc.h>
#include <linux/interrupt.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
@@ -548,10 +551,10 @@ module_platform_driver(altr_edac_driver);
 * trigger testing are different for each memory.
 */

const struct edac_device_prv_data ocramecc_data;
const struct edac_device_prv_data l2ecc_data;
const struct edac_device_prv_data a10_ocramecc_data;
const struct edac_device_prv_data a10_l2ecc_data;
static const struct edac_device_prv_data ocramecc_data;
static const struct edac_device_prv_data l2ecc_data;
static const struct edac_device_prv_data a10_ocramecc_data;
static const struct edac_device_prv_data a10_l2ecc_data;

static irqreturn_t altr_edac_device_handler(int irq, void *dev_id)
{
@@ -686,11 +689,9 @@ static void altr_create_edacdev_dbgfs(struct edac_device_ctl_info *edac_dci,
static const struct of_device_id altr_edac_device_of_match[] = {
#ifdef CONFIG_EDAC_ALTERA_L2C
	{ .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 = &ocramecc_data },
	{ .compatible = "altr,socfpga-a10-ocram-ecc", .data = &a10_ocramecc_data },
#endif
	{},
};
@@ -825,16 +826,16 @@ static struct platform_driver altr_edac_device_driver = {
};
module_platform_driver(altr_edac_device_driver);

/*********************** OCRAM EDAC Device Functions *********************/
/******************* Arria10 Device ECC Shared 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)
static int __maybe_unused
altr_check_ecc_deps(struct altr_edac_device_dev *device)
{
	void __iomem  *base = device->base;
	const struct edac_device_prv_data *prv = device->data;
@@ -848,6 +849,227 @@ static int altr_check_ecc_deps(struct altr_edac_device_dev *device)
	return -ENODEV;
}

static irqreturn_t __maybe_unused altr_edac_a10_ecc_irq(int irq, void *dev_id)
{
	struct altr_edac_device_dev *dci = dev_id;
	void __iomem  *base = dci->base;

	if (irq == dci->sb_irq) {
		writel(ALTR_A10_ECC_SERRPENA,
		       base + ALTR_A10_ECC_INTSTAT_OFST);
		edac_device_handle_ce(dci->edac_dev, 0, 0, dci->edac_dev_name);

		return IRQ_HANDLED;
	} else if (irq == dci->db_irq) {
		writel(ALTR_A10_ECC_DERRPENA,
		       base + ALTR_A10_ECC_INTSTAT_OFST);
		edac_device_handle_ue(dci->edac_dev, 0, 0, dci->edac_dev_name);
		if (dci->data->panic)
			panic("\nEDAC:ECC_DEVICE[Uncorrectable errors]\n");

		return IRQ_HANDLED;
	}

	WARN_ON(1);

	return IRQ_NONE;
}

/******************* Arria10 Memory Buffer Functions *********************/

static inline int a10_get_irq_mask(struct device_node *np)
{
	int irq;
	const u32 *handle = of_get_property(np, "interrupts", NULL);

	if (!handle)
		return -ENODEV;
	irq = be32_to_cpup(handle);
	return irq;
}

static inline void ecc_set_bits(u32 bit_mask, void __iomem *ioaddr)
{
	u32 value = readl(ioaddr);

	value |= bit_mask;
	writel(value, ioaddr);
}

static inline void ecc_clear_bits(u32 bit_mask, void __iomem *ioaddr)
{
	u32 value = readl(ioaddr);

	value &= ~bit_mask;
	writel(value, ioaddr);
}

static inline int ecc_test_bits(u32 bit_mask, void __iomem *ioaddr)
{
	u32 value = readl(ioaddr);

	return (value & bit_mask) ? 1 : 0;
}

/*
 * This function uses the memory initialization block in the Arria10 ECC
 * controller to initialize/clear the entire memory data and ECC data.
 */
static int __maybe_unused altr_init_memory_port(void __iomem *ioaddr, int port)
{
	int limit = ALTR_A10_ECC_INIT_WATCHDOG_10US;
	u32 init_mask, stat_mask, clear_mask;
	int ret = 0;

	if (port) {
		init_mask = ALTR_A10_ECC_INITB;
		stat_mask = ALTR_A10_ECC_INITCOMPLETEB;
		clear_mask = ALTR_A10_ECC_ERRPENB_MASK;
	} else {
		init_mask = ALTR_A10_ECC_INITA;
		stat_mask = ALTR_A10_ECC_INITCOMPLETEA;
		clear_mask = ALTR_A10_ECC_ERRPENA_MASK;
	}

	ecc_set_bits(init_mask, (ioaddr + ALTR_A10_ECC_CTRL_OFST));
	while (limit--) {
		if (ecc_test_bits(stat_mask,
				  (ioaddr + ALTR_A10_ECC_INITSTAT_OFST)))
			break;
		udelay(1);
	}
	if (limit < 0)
		ret = -EBUSY;

	/* Clear any pending ECC interrupts */
	writel(clear_mask, (ioaddr + ALTR_A10_ECC_INTSTAT_OFST));

	return ret;
}

static __init int __maybe_unused
altr_init_a10_ecc_block(struct device_node *np, u32 irq_mask,
			u32 ecc_ctrl_en_mask, bool dual_port)
{
	int ret = 0;
	void __iomem *ecc_block_base;
	struct regmap *ecc_mgr_map;
	char *ecc_name;
	struct device_node *np_eccmgr;

	ecc_name = (char *)np->name;

	/* Get the ECC Manager - parent of the device EDACs */
	np_eccmgr = of_get_parent(np);
	ecc_mgr_map = syscon_regmap_lookup_by_phandle(np_eccmgr,
						      "altr,sysmgr-syscon");
	of_node_put(np_eccmgr);
	if (IS_ERR(ecc_mgr_map)) {
		edac_printk(KERN_ERR, EDAC_DEVICE,
			    "Unable to get syscon altr,sysmgr-syscon\n");
		return -ENODEV;
	}

	/* Map the ECC Block */
	ecc_block_base = of_iomap(np, 0);
	if (!ecc_block_base) {
		edac_printk(KERN_ERR, EDAC_DEVICE,
			    "Unable to map %s ECC block\n", ecc_name);
		return -ENODEV;
	}

	/* Disable ECC */
	regmap_write(ecc_mgr_map, A10_SYSMGR_ECC_INTMASK_SET_OFST, irq_mask);
	writel(ALTR_A10_ECC_SERRINTEN,
	       (ecc_block_base + ALTR_A10_ECC_ERRINTENR_OFST));
	ecc_clear_bits(ecc_ctrl_en_mask,
		       (ecc_block_base + ALTR_A10_ECC_CTRL_OFST));
	/* Ensure all writes complete */
	wmb();
	/* Use HW initialization block to initialize memory for ECC */
	ret = altr_init_memory_port(ecc_block_base, 0);
	if (ret) {
		edac_printk(KERN_ERR, EDAC_DEVICE,
			    "ECC: cannot init %s PORTA memory\n", ecc_name);
		goto out;
	}

	if (dual_port) {
		ret = altr_init_memory_port(ecc_block_base, 1);
		if (ret) {
			edac_printk(KERN_ERR, EDAC_DEVICE,
				    "ECC: cannot init %s PORTB memory\n",
				    ecc_name);
			goto out;
		}
	}

	/* Interrupt mode set to every SBERR */
	regmap_write(ecc_mgr_map, ALTR_A10_ECC_INTMODE_OFST,
		     ALTR_A10_ECC_INTMODE);
	/* Enable ECC */
	ecc_set_bits(ecc_ctrl_en_mask, (ecc_block_base +
					ALTR_A10_ECC_CTRL_OFST));
	writel(ALTR_A10_ECC_SERRINTEN,
	       (ecc_block_base + ALTR_A10_ECC_ERRINTENS_OFST));
	regmap_write(ecc_mgr_map, A10_SYSMGR_ECC_INTMASK_CLR_OFST, irq_mask);
	/* Ensure all writes complete */
	wmb();
out:
	iounmap(ecc_block_base);
	return ret;
}

static int validate_parent_available(struct device_node *np);
static const struct of_device_id altr_edac_a10_device_of_match[];
static int __init __maybe_unused altr_init_a10_ecc_device_type(char *compat)
{
	int irq;
	struct device_node *child, *np = of_find_compatible_node(NULL, NULL,
					"altr,socfpga-a10-ecc-manager");
	if (!np) {
		edac_printk(KERN_ERR, EDAC_DEVICE, "ECC Manager not found\n");
		return -ENODEV;
	}

	for_each_child_of_node(np, child) {
		const struct of_device_id *pdev_id;
		const struct edac_device_prv_data *prv;

		if (!of_device_is_available(child))
			continue;
		if (!of_device_is_compatible(child, compat))
			continue;

		if (validate_parent_available(child))
			continue;

		irq = a10_get_irq_mask(child);
		if (irq < 0)
			continue;

		/* Get matching node and check for valid result */
		pdev_id = of_match_node(altr_edac_a10_device_of_match, child);
		if (IS_ERR_OR_NULL(pdev_id))
			continue;

		/* Validate private data pointer before dereferencing */
		prv = pdev_id->data;
		if (!prv)
			continue;

		altr_init_a10_ecc_block(child, BIT(irq),
					prv->ecc_enable_mask, 0);
	}

	of_node_put(np);
	return 0;
}

/*********************** OCRAM EDAC Device Functions *********************/

#ifdef CONFIG_EDAC_ALTERA_OCRAM

static void *ocram_alloc_mem(size_t size, void **other)
{
	struct device_node *np;
@@ -882,25 +1104,7 @@ static void ocram_free_mem(void *p, size_t size, void *other)
	gen_pool_free((struct gen_pool *)other, (u32)p, size);
}

static irqreturn_t altr_edac_a10_ecc_irq(struct altr_edac_device_dev *dci,
					 bool sberr)
{
	void __iomem  *base = dci->base;

	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 = {
static const struct edac_device_prv_data ocramecc_data = {
	.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),
@@ -916,7 +1120,7 @@ const struct edac_device_prv_data ocramecc_data = {
	.inject_fops = &altr_edac_device_inject_fops,
};

const struct edac_device_prv_data a10_ocramecc_data = {
static 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,
@@ -929,6 +1133,12 @@ const struct edac_device_prv_data a10_ocramecc_data = {
	.set_err_ofst = ALTR_A10_ECC_INTTEST_OFST,
	.ecc_irq_handler = altr_edac_a10_ecc_irq,
	.inject_fops = &altr_edac_a10_device_inject_fops,
	/*
	 * OCRAM panic on uncorrectable error because sleep/resume
	 * functions and FPGA contents are stored in OCRAM. Prefer
	 * a kernel panic over executing/loading corrupted data.
	 */
	.panic = true,
};

#endif	/* CONFIG_EDAC_ALTERA_OCRAM */
@@ -988,25 +1198,33 @@ static int altr_l2_check_deps(struct altr_edac_device_dev *device)
	return -ENODEV;
}

static irqreturn_t altr_edac_a10_l2_irq(struct altr_edac_device_dev *dci,
					bool sberr)
static irqreturn_t altr_edac_a10_l2_irq(int irq, void *dev_id)
{
	if (sberr) {
	struct altr_edac_device_dev *dci = dev_id;

	if (irq == dci->sb_irq) {
		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 {

		return IRQ_HANDLED;
	} else if (irq == dci->db_irq) {
		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 = {
	WARN_ON(1);

	return IRQ_NONE;
}

static const struct edac_device_prv_data l2ecc_data = {
	.setup = altr_l2_check_deps,
	.ce_clear_mask = 0,
	.ue_clear_mask = 0,
@@ -1021,7 +1239,7 @@ const struct edac_device_prv_data l2ecc_data = {
	.inject_fops = &altr_edac_device_inject_fops,
};

const struct edac_device_prv_data a10_l2ecc_data = {
static 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,
@@ -1040,7 +1258,49 @@ const struct edac_device_prv_data a10_l2ecc_data = {

#endif	/* CONFIG_EDAC_ALTERA_L2C */

/********************* Ethernet Device Functions ********************/

#ifdef CONFIG_EDAC_ALTERA_ETHERNET

static const struct edac_device_prv_data a10_enetecc_data = {
	.setup = altr_check_ecc_deps,
	.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_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,
};

static int __init socfpga_init_ethernet_ecc(void)
{
	return altr_init_a10_ecc_device_type("altr,socfpga-eth-mac-ecc");
}

early_initcall(socfpga_init_ethernet_ecc);

#endif	/* CONFIG_EDAC_ALTERA_ETHERNET */

/********************* Arria10 EDAC Device Functions *************************/
static const struct of_device_id altr_edac_a10_device_of_match[] = {
#ifdef CONFIG_EDAC_ALTERA_L2C
	{ .compatible = "altr,socfpga-a10-l2-ecc", .data = &a10_l2ecc_data },
#endif
#ifdef CONFIG_EDAC_ALTERA_OCRAM
	{ .compatible = "altr,socfpga-a10-ocram-ecc",
	  .data = &a10_ocramecc_data },
#endif
#ifdef CONFIG_EDAC_ALTERA_ETHERNET
	{ .compatible = "altr,socfpga-eth-mac-ecc",
	  .data = &a10_enetecc_data },
#endif
	{},
};
MODULE_DEVICE_TABLE(of, altr_edac_a10_device_of_match);

/*
 * The Arria10 EDAC Device Functions differ from the Cyclone5/Arria5
@@ -1075,28 +1335,42 @@ static ssize_t altr_edac_a10_device_trig(struct file *file,
	return count;
}

static irqreturn_t altr_edac_a10_irq_handler(int irq, void *dev_id)
static void altr_edac_a10_irq_handler(struct irq_desc *desc)
{
	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;
	int dberr, bit, sm_offset, irq_status;
	struct altr_arria10_edac *edac = irq_desc_get_handler_data(desc);
	struct irq_chip *chip = irq_desc_get_chip(desc);
	int irq = irq_desc_get_irq(desc);

	dberr = (irq == edac->db_irq) ? 1 : 0;
	sm_offset = dberr ? A10_SYSMGR_ECC_INTSTAT_DERR_OFST :
			    A10_SYSMGR_ECC_INTSTAT_SERR_OFST;

	chained_irq_enter(chip, desc);

	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);
	for_each_set_bit(bit, (unsigned long *)&irq_status, 32) {
		irq = irq_linear_revmap(edac->domain, dberr * 32 + bit);
		if (irq)
			generic_handle_irq(irq);
	}

	chained_irq_exit(chip, desc);
}

	return rc;
static int validate_parent_available(struct device_node *np)
{
	struct device_node *parent;
	int ret = 0;

	/* Ensure parent device is enabled if parent node exists */
	parent = of_parse_phandle(np, "altr,ecc-parent", 0);
	if (parent && !of_device_is_available(parent))
		ret = -ENODEV;

	of_node_put(parent);
	return ret;
}

static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
@@ -1111,7 +1385,7 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
	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);
		of_match_node(altr_edac_a10_device_of_match, np);
	if (IS_ERR_OR_NULL(pdev_id))
		return -ENODEV;

@@ -1120,6 +1394,9 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
	if (IS_ERR_OR_NULL(prv))
		return -ENODEV;

	if (validate_parent_available(np))
		return -ENODEV;

	if (!devres_open_group(edac->dev, altr_edac_a10_device_add, GFP_KERNEL))
		return -ENOMEM;

@@ -1168,6 +1445,34 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
			goto err_release_group1;
	}

	altdev->sb_irq = irq_of_parse_and_map(np, 0);
	if (!altdev->sb_irq) {
		edac_printk(KERN_ERR, EDAC_DEVICE, "Error allocating SBIRQ\n");
		rc = -ENODEV;
		goto err_release_group1;
	}
	rc = devm_request_irq(edac->dev, altdev->sb_irq,
			      prv->ecc_irq_handler,
			      IRQF_SHARED, ecc_name, altdev);
	if (rc) {
		edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n");
		goto err_release_group1;
	}

	altdev->db_irq = irq_of_parse_and_map(np, 1);
	if (!altdev->db_irq) {
		edac_printk(KERN_ERR, EDAC_DEVICE, "Error allocating DBIRQ\n");
		rc = -ENODEV;
		goto err_release_group1;
	}
	rc = devm_request_irq(edac->dev, altdev->db_irq,
			      prv->ecc_irq_handler,
			      IRQF_SHARED, ecc_name, altdev);
	if (rc) {
		edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n");
		goto err_release_group1;
	}

	rc = edac_device_add_device(dci);
	if (rc) {
		dev_err(edac->dev, "edac_device_add_device failed\n");
@@ -1186,7 +1491,6 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
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);
@@ -1194,11 +1498,43 @@ static int altr_edac_a10_device_add(struct altr_arria10_edac *edac,
	return rc;
}

static void a10_eccmgr_irq_mask(struct irq_data *d)
{
	struct altr_arria10_edac *edac = irq_data_get_irq_chip_data(d);

	regmap_write(edac->ecc_mgr_map,	A10_SYSMGR_ECC_INTMASK_SET_OFST,
		     BIT(d->hwirq));
}

static void a10_eccmgr_irq_unmask(struct irq_data *d)
{
	struct altr_arria10_edac *edac = irq_data_get_irq_chip_data(d);

	regmap_write(edac->ecc_mgr_map,	A10_SYSMGR_ECC_INTMASK_CLR_OFST,
		     BIT(d->hwirq));
}

static int a10_eccmgr_irqdomain_map(struct irq_domain *d, unsigned int irq,
				    irq_hw_number_t hwirq)
{
	struct altr_arria10_edac *edac = d->host_data;

	irq_set_chip_and_handler(irq, &edac->irq_chip, handle_simple_irq);
	irq_set_chip_data(irq, edac);
	irq_set_noprobe(irq);

	return 0;
}

struct irq_domain_ops a10_eccmgr_ic_ops = {
	.map = a10_eccmgr_irqdomain_map,
	.xlate = irq_domain_xlate_twocell,
};

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)
@@ -1216,32 +1552,50 @@ static int altr_edac_a10_probe(struct platform_device *pdev)
		return PTR_ERR(edac->ecc_mgr_map);
	}

	edac->irq_chip.name = pdev->dev.of_node->name;
	edac->irq_chip.irq_mask = a10_eccmgr_irq_mask;
	edac->irq_chip.irq_unmask = a10_eccmgr_irq_unmask;
	edac->domain = irq_domain_add_linear(pdev->dev.of_node, 64,
					     &a10_eccmgr_ic_ops, edac);
	if (!edac->domain) {
		dev_err(&pdev->dev, "Error adding IRQ domain\n");
		return -ENOMEM;
	}

	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;
	if (edac->sb_irq < 0) {
		dev_err(&pdev->dev, "No SBERR IRQ resource\n");
		return edac->sb_irq;
	}

	edac->db_irq = platform_get_irq(pdev, 1);
	rc = devm_request_irq(&pdev->dev, edac->db_irq,
	irq_set_chained_handler_and_data(edac->sb_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;
					 edac);

	edac->db_irq = platform_get_irq(pdev, 1);
	if (edac->db_irq < 0) {
		dev_err(&pdev->dev, "No DBERR IRQ resource\n");
		return edac->db_irq;
	}
	irq_set_chained_handler_and_data(edac->db_irq,
					 altr_edac_a10_irq_handler,
					 edac);

	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"))
		else if ((of_device_is_compatible(child,
					"altr,socfpga-a10-ocram-ecc")) ||
			 (of_device_is_compatible(child,
					"altr,socfpga-eth-mac-ecc")))
			altr_edac_a10_device_add(edac, child);
		else if (of_device_is_compatible(child,
						 "altr,sdram-edac-a10"))
			of_platform_populate(pdev->dev.of_node,
					     altr_sdram_ctrl_of_match,
					     NULL, &pdev->dev);
	}

	return 0;
+15 −2
Original line number Diff line number Diff line
@@ -230,8 +230,13 @@ struct altr_sdram_mc_data {
#define ALTR_A10_ECC_INITCOMPLETEB      BIT(8)

#define ALTR_A10_ECC_ERRINTEN_OFST      0x10
#define ALTR_A10_ECC_ERRINTENS_OFST     0x14
#define ALTR_A10_ECC_ERRINTENR_OFST     0x18
#define ALTR_A10_ECC_SERRINTEN          BIT(0)

#define ALTR_A10_ECC_INTMODE_OFST       0x1C
#define ALTR_A10_ECC_INTMODE            BIT(0)

#define ALTR_A10_ECC_INTSTAT_OFST       0x20
#define ALTR_A10_ECC_SERRPENA           BIT(0)
#define ALTR_A10_ECC_DERRPENA           BIT(8)
@@ -280,6 +285,12 @@ struct altr_sdram_mc_data {
/* Arria 10 OCRAM ECC Management Group Defines */
#define ALTR_A10_OCRAM_ECC_EN_CTL       (BIT(1) | BIT(0))

/* Arria 10 Ethernet ECC Management Group Defines */
#define ALTR_A10_COMMON_ECC_EN_CTL      BIT(0)

/* A10 ECC Controller memory initialization timeout */
#define ALTR_A10_ECC_INIT_WATCHDOG_10US      10000

struct altr_edac_device_dev;

struct edac_device_prv_data {
@@ -295,10 +306,10 @@ struct edac_device_prv_data {
	int ce_set_mask;
	int ue_set_mask;
	int set_err_ofst;
	irqreturn_t (*ecc_irq_handler)(struct altr_edac_device_dev *dci,
				       bool sb);
	irqreturn_t (*ecc_irq_handler)(int irq, void *dev_id);
	int trig_alloc_sz;
	const struct file_operations *inject_fops;
	bool panic;
};

struct altr_edac_device_dev {
@@ -320,6 +331,8 @@ struct altr_arria10_edac {
	struct regmap		*ecc_mgr_map;
	int sb_irq;
	int db_irq;
	struct irq_domain	*domain;
	struct irq_chip		irq_chip;
	struct list_head	a10_ecc_devices;
};

Loading