mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	EDAC, altera: Add Altera L2 cache and OCRAM support
Add L2 Cache and On-Chip RAM EDAC support for the Altera SoCs. The SDRAM controller is using the Memory Controller model. Each type of ECC is individually configurable. Signed-off-by: Thor Thayer <tthayer@opensource.altera.com> Cc: devicetree@vger.kernel.org Cc: dinguyen@opensource.altera.com Cc: galak@codeaurora.org Cc: grant.likely@linaro.org Cc: ijc+devicetree@hellion.org.uk Cc: linux-arm-kernel@lists.infradead.org Cc: linux@arm.linux.org.uk Cc: linux-doc@vger.kernel.org Cc: linux-edac <linux-edac@vger.kernel.org> Cc: mark.rutland@arm.com Cc: Mauro Carvalho Chehab <mchehab@osg.samsung.com> Cc: pawel.moll@arm.com Cc: robh+dt@kernel.org Link: http://lkml.kernel.org/r/1455132384-17108-1-git-send-email-tthayer@opensource.altera.com Signed-off-by: Borislav Petkov <bp@suse.de>
This commit is contained in:
		
							parent
							
								
									9bf4f00567
								
							
						
					
					
						commit
						c3eea1942a
					
				
					 3 changed files with 512 additions and 8 deletions
				
			
		| 
						 | 
					@ -367,14 +367,30 @@ config EDAC_OCTEON_PCI
 | 
				
			||||||
	  Support for error detection and correction on the
 | 
						  Support for error detection and correction on the
 | 
				
			||||||
	  Cavium Octeon family of SOCs.
 | 
						  Cavium Octeon family of SOCs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config EDAC_ALTERA_MC
 | 
					config EDAC_ALTERA
 | 
				
			||||||
	bool "Altera SDRAM Memory Controller EDAC"
 | 
						bool "Altera SOCFPGA ECC"
 | 
				
			||||||
	depends on EDAC_MM_EDAC=y && ARCH_SOCFPGA
 | 
						depends on EDAC_MM_EDAC=y && ARCH_SOCFPGA
 | 
				
			||||||
	help
 | 
						help
 | 
				
			||||||
	  Support for error detection and correction on the
 | 
						  Support for error detection and correction on the
 | 
				
			||||||
	  Altera SDRAM memory controller. Note that the
 | 
						  Altera SOCs. This must be selected for SDRAM ECC.
 | 
				
			||||||
	  preloader must initialize the SDRAM before loading
 | 
						  Note that the preloader must initialize the SDRAM
 | 
				
			||||||
	  the kernel.
 | 
						  before loading the kernel.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config EDAC_ALTERA_L2C
 | 
				
			||||||
 | 
						bool "Altera L2 Cache ECC"
 | 
				
			||||||
 | 
						depends on EDAC_ALTERA=y
 | 
				
			||||||
 | 
						select CACHE_L2X0
 | 
				
			||||||
 | 
						help
 | 
				
			||||||
 | 
						  Support for error detection and correction on the
 | 
				
			||||||
 | 
						  Altera L2 cache Memory for Altera SoCs. This option
 | 
				
			||||||
 | 
						  requires L2 cache so it will force that selection.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config EDAC_ALTERA_OCRAM
 | 
				
			||||||
 | 
						bool "Altera On-Chip RAM ECC"
 | 
				
			||||||
 | 
						depends on EDAC_ALTERA=y && SRAM && GENERIC_ALLOCATOR
 | 
				
			||||||
 | 
						help
 | 
				
			||||||
 | 
						  Support for error detection and correction on the
 | 
				
			||||||
 | 
						  Altera On-Chip RAM Memory for Altera SoCs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config EDAC_SYNOPSYS
 | 
					config EDAC_SYNOPSYS
 | 
				
			||||||
	tristate "Synopsys DDR Memory Controller"
 | 
						tristate "Synopsys DDR Memory Controller"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,6 +67,6 @@ obj-$(CONFIG_EDAC_OCTEON_L2C)		+= octeon_edac-l2c.o
 | 
				
			||||||
obj-$(CONFIG_EDAC_OCTEON_LMC)		+= octeon_edac-lmc.o
 | 
					obj-$(CONFIG_EDAC_OCTEON_LMC)		+= octeon_edac-lmc.o
 | 
				
			||||||
obj-$(CONFIG_EDAC_OCTEON_PCI)		+= octeon_edac-pci.o
 | 
					obj-$(CONFIG_EDAC_OCTEON_PCI)		+= octeon_edac-pci.o
 | 
				
			||||||
 | 
					
 | 
				
			||||||
obj-$(CONFIG_EDAC_ALTERA_MC)		+= altera_edac.o
 | 
					obj-$(CONFIG_EDAC_ALTERA)		+= altera_edac.o
 | 
				
			||||||
obj-$(CONFIG_EDAC_SYNOPSYS)		+= synopsys_edac.o
 | 
					obj-$(CONFIG_EDAC_SYNOPSYS)		+= synopsys_edac.o
 | 
				
			||||||
obj-$(CONFIG_EDAC_XGENE)		+= xgene_edac.o
 | 
					obj-$(CONFIG_EDAC_XGENE)		+= xgene_edac.o
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 *  Copyright Altera Corporation (C) 2014-2015. All rights reserved.
 | 
					 *  Copyright Altera Corporation (C) 2014-2016. All rights reserved.
 | 
				
			||||||
 *  Copyright 2011-2012 Calxeda, Inc.
 | 
					 *  Copyright 2011-2012 Calxeda, Inc.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * This program is free software; you can redistribute it and/or modify it
 | 
					 * This program is free software; you can redistribute it and/or modify it
 | 
				
			||||||
| 
						 | 
					@ -17,8 +17,10 @@
 | 
				
			||||||
 * Adapted from the highbank_mc_edac driver.
 | 
					 * Adapted from the highbank_mc_edac driver.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <asm/cacheflush.h>
 | 
				
			||||||
#include <linux/ctype.h>
 | 
					#include <linux/ctype.h>
 | 
				
			||||||
#include <linux/edac.h>
 | 
					#include <linux/edac.h>
 | 
				
			||||||
 | 
					#include <linux/genalloc.h>
 | 
				
			||||||
#include <linux/interrupt.h>
 | 
					#include <linux/interrupt.h>
 | 
				
			||||||
#include <linux/kernel.h>
 | 
					#include <linux/kernel.h>
 | 
				
			||||||
#include <linux/mfd/syscon.h>
 | 
					#include <linux/mfd/syscon.h>
 | 
				
			||||||
| 
						 | 
					@ -34,6 +36,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define EDAC_MOD_STR		"altera_edac"
 | 
					#define EDAC_MOD_STR		"altera_edac"
 | 
				
			||||||
#define EDAC_VERSION		"1"
 | 
					#define EDAC_VERSION		"1"
 | 
				
			||||||
 | 
					#define EDAC_DEVICE		"Altera"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const struct altr_sdram_prv_data c5_data = {
 | 
					static const struct altr_sdram_prv_data c5_data = {
 | 
				
			||||||
	.ecc_ctrl_offset    = CV_CTLCFG_OFST,
 | 
						.ecc_ctrl_offset    = CV_CTLCFG_OFST,
 | 
				
			||||||
| 
						 | 
					@ -75,6 +78,31 @@ static const struct altr_sdram_prv_data a10_data = {
 | 
				
			||||||
	.ue_set_mask        = A10_DIAGINT_TDERRA_MASK,
 | 
						.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.       */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static irqreturn_t altr_sdram_mc_err_handler(int irq, void *dev_id)
 | 
					static irqreturn_t altr_sdram_mc_err_handler(int irq, void *dev_id)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct mem_ctl_info *mci = dev_id;
 | 
						struct mem_ctl_info *mci = dev_id;
 | 
				
			||||||
| 
						 | 
					@ -504,6 +532,466 @@ static struct platform_driver altr_sdram_edac_driver = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module_platform_driver(altr_sdram_edac_driver);
 | 
					module_platform_driver(altr_sdram_edac_driver);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/************************* EDAC Parent Probe *************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct of_device_id altr_edac_device_of_match[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct of_device_id altr_edac_of_match[] = {
 | 
				
			||||||
 | 
						{ .compatible = "altr,socfpga-ecc-manager" },
 | 
				
			||||||
 | 
						{},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					MODULE_DEVICE_TABLE(of, altr_edac_of_match);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int altr_edac_probe(struct platform_device *pdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						of_platform_populate(pdev->dev.of_node, altr_edac_device_of_match,
 | 
				
			||||||
 | 
								     NULL, &pdev->dev);
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct platform_driver altr_edac_driver = {
 | 
				
			||||||
 | 
						.probe =  altr_edac_probe,
 | 
				
			||||||
 | 
						.driver = {
 | 
				
			||||||
 | 
							.name = "socfpga_ecc_manager",
 | 
				
			||||||
 | 
							.of_match_table = altr_edac_of_match,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					module_platform_driver(altr_edac_driver);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/************************* EDAC Device Functions *************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * EDAC Device Functions (shared between various IPs).
 | 
				
			||||||
 | 
					 * The discrete memories use the EDAC Device framework. The probe
 | 
				
			||||||
 | 
					 * and error handling functions are very similar between memories
 | 
				
			||||||
 | 
					 * so they are shared. The memory allocation and freeing for EDAC
 | 
				
			||||||
 | 
					 * trigger testing are different for each memory.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static irqreturn_t altr_edac_device_handler(int irq, void *dev_id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						irqreturn_t ret_value = IRQ_NONE;
 | 
				
			||||||
 | 
						struct edac_device_ctl_info *dci = dev_id;
 | 
				
			||||||
 | 
						struct altr_edac_device_dev *drvdata = dci->pvt_info;
 | 
				
			||||||
 | 
						const struct edac_device_prv_data *priv = drvdata->data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (irq == drvdata->sb_irq) {
 | 
				
			||||||
 | 
							if (priv->ce_clear_mask)
 | 
				
			||||||
 | 
								writel(priv->ce_clear_mask, drvdata->base);
 | 
				
			||||||
 | 
							edac_device_handle_ce(dci, 0, 0, drvdata->edac_dev_name);
 | 
				
			||||||
 | 
							ret_value = IRQ_HANDLED;
 | 
				
			||||||
 | 
						} else if (irq == drvdata->db_irq) {
 | 
				
			||||||
 | 
							if (priv->ue_clear_mask)
 | 
				
			||||||
 | 
								writel(priv->ue_clear_mask, drvdata->base);
 | 
				
			||||||
 | 
							edac_device_handle_ue(dci, 0, 0, drvdata->edac_dev_name);
 | 
				
			||||||
 | 
							panic("\nEDAC:ECC_DEVICE[Uncorrectable errors]\n");
 | 
				
			||||||
 | 
							ret_value = IRQ_HANDLED;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							WARN_ON(1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret_value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t altr_edac_device_trig(struct file *file,
 | 
				
			||||||
 | 
									     const char __user *user_buf,
 | 
				
			||||||
 | 
									     size_t count, loff_t *ppos)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						u32 *ptemp, i, error_mask;
 | 
				
			||||||
 | 
						int result = 0;
 | 
				
			||||||
 | 
						u8 trig_type;
 | 
				
			||||||
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						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 *generic_ptr = edac_dci->dev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!user_buf || get_user(trig_type, user_buf))
 | 
				
			||||||
 | 
							return -EFAULT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!priv->alloc_mem)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Note that generic_ptr is initialized to the device * but in
 | 
				
			||||||
 | 
						 * some alloc_functions, this is overridden and returns data.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						ptemp = priv->alloc_mem(priv->trig_alloc_sz, &generic_ptr);
 | 
				
			||||||
 | 
						if (!ptemp) {
 | 
				
			||||||
 | 
							edac_printk(KERN_ERR, EDAC_DEVICE,
 | 
				
			||||||
 | 
								    "Inject: Buffer Allocation error\n");
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (trig_type == ALTR_UE_TRIGGER_CHAR)
 | 
				
			||||||
 | 
							error_mask = priv->ue_set_mask;
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							error_mask = priv->ce_set_mask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						edac_printk(KERN_ALERT, EDAC_DEVICE,
 | 
				
			||||||
 | 
							    "Trigger Error Mask (0x%X)\n", error_mask);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						local_irq_save(flags);
 | 
				
			||||||
 | 
						/* write ECC corrupted data out. */
 | 
				
			||||||
 | 
						for (i = 0; i < (priv->trig_alloc_sz / sizeof(*ptemp)); i++) {
 | 
				
			||||||
 | 
							/* Read data so we're in the correct state */
 | 
				
			||||||
 | 
							rmb();
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
 | 
							ptemp[i] = i;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						/* Ensure it has been written out */
 | 
				
			||||||
 | 
						wmb();
 | 
				
			||||||
 | 
						local_irq_restore(flags);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (result)
 | 
				
			||||||
 | 
							edac_printk(KERN_ERR, EDAC_DEVICE, "Mem Not Cleared\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Read out written data. ECC error caused here */
 | 
				
			||||||
 | 
						for (i = 0; i < ALTR_TRIGGER_READ_WRD_CNT; i++)
 | 
				
			||||||
 | 
							if (ACCESS_ONCE(ptemp[i]) != i)
 | 
				
			||||||
 | 
								edac_printk(KERN_ERR, EDAC_DEVICE,
 | 
				
			||||||
 | 
									    "Read doesn't match written data\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (priv->free_mem)
 | 
				
			||||||
 | 
							priv->free_mem(ptemp, priv->trig_alloc_sz, generic_ptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return count;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct file_operations altr_edac_device_inject_fops = {
 | 
				
			||||||
 | 
						.open = simple_open,
 | 
				
			||||||
 | 
						.write = altr_edac_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)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct altr_edac_device_dev *drvdata = edac_dci->pvt_info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!IS_ENABLED(CONFIG_EDAC_DEBUG))
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->debugfs_dir = edac_debugfs_create_dir(drvdata->edac_dev_name);
 | 
				
			||||||
 | 
						if (!drvdata->debugfs_dir)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!edac_debugfs_create_file(priv->dbgfs_name, S_IWUSR,
 | 
				
			||||||
 | 
									      drvdata->debugfs_dir, edac_dci,
 | 
				
			||||||
 | 
									      &altr_edac_device_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 },
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef CONFIG_EDAC_ALTERA_OCRAM
 | 
				
			||||||
 | 
						{ .compatible = "altr,socfpga-ocram-ecc",
 | 
				
			||||||
 | 
						  .data = (void *)&ocramecc_data },
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
						{},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					MODULE_DEVICE_TABLE(of, altr_edac_device_of_match);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * altr_edac_device_probe()
 | 
				
			||||||
 | 
					 *	This is a generic EDAC device driver that will support
 | 
				
			||||||
 | 
					 *	various Altera memory devices such as the L2 cache ECC and
 | 
				
			||||||
 | 
					 *	OCRAM ECC as well as the memories for other peripherals.
 | 
				
			||||||
 | 
					 *	Module specific initialization is done by passing the
 | 
				
			||||||
 | 
					 *	function index in the device tree.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static int altr_edac_device_probe(struct platform_device *pdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct edac_device_ctl_info *dci;
 | 
				
			||||||
 | 
						struct altr_edac_device_dev *drvdata;
 | 
				
			||||||
 | 
						struct resource *r;
 | 
				
			||||||
 | 
						int res = 0;
 | 
				
			||||||
 | 
						struct device_node *np = pdev->dev.of_node;
 | 
				
			||||||
 | 
						char *ecc_name = (char *)np->name;
 | 
				
			||||||
 | 
						static int dev_instance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL)) {
 | 
				
			||||||
 | 
							edac_printk(KERN_ERR, EDAC_DEVICE,
 | 
				
			||||||
 | 
								    "Unable to open devm\n");
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | 
				
			||||||
 | 
						if (!r) {
 | 
				
			||||||
 | 
							edac_printk(KERN_ERR, EDAC_DEVICE,
 | 
				
			||||||
 | 
								    "Unable to get mem resource\n");
 | 
				
			||||||
 | 
							res = -ENODEV;
 | 
				
			||||||
 | 
							goto fail;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r),
 | 
				
			||||||
 | 
									     dev_name(&pdev->dev))) {
 | 
				
			||||||
 | 
							edac_printk(KERN_ERR, EDAC_DEVICE,
 | 
				
			||||||
 | 
								    "%s:Error requesting mem region\n", ecc_name);
 | 
				
			||||||
 | 
							res = -EBUSY;
 | 
				
			||||||
 | 
							goto fail;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dci = edac_device_alloc_ctl_info(sizeof(*drvdata), ecc_name,
 | 
				
			||||||
 | 
										 1, ecc_name, 1, 0, NULL, 0,
 | 
				
			||||||
 | 
										 dev_instance++);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!dci) {
 | 
				
			||||||
 | 
							edac_printk(KERN_ERR, EDAC_DEVICE,
 | 
				
			||||||
 | 
								    "%s: Unable to allocate EDAC device\n", ecc_name);
 | 
				
			||||||
 | 
							res = -ENOMEM;
 | 
				
			||||||
 | 
							goto fail;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata = dci->pvt_info;
 | 
				
			||||||
 | 
						dci->dev = &pdev->dev;
 | 
				
			||||||
 | 
						platform_set_drvdata(pdev, dci);
 | 
				
			||||||
 | 
						drvdata->edac_dev_name = ecc_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
 | 
				
			||||||
 | 
						if (!drvdata->base)
 | 
				
			||||||
 | 
							goto fail1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Get driver specific data for this EDAC device */
 | 
				
			||||||
 | 
						drvdata->data = of_match_node(altr_edac_device_of_match, np)->data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Check specific dependencies for the module */
 | 
				
			||||||
 | 
						if (drvdata->data->setup) {
 | 
				
			||||||
 | 
							res = drvdata->data->setup(pdev, drvdata->base);
 | 
				
			||||||
 | 
							if (res)
 | 
				
			||||||
 | 
								goto fail1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->sb_irq = platform_get_irq(pdev, 0);
 | 
				
			||||||
 | 
						res = devm_request_irq(&pdev->dev, drvdata->sb_irq,
 | 
				
			||||||
 | 
								       altr_edac_device_handler,
 | 
				
			||||||
 | 
								       0, dev_name(&pdev->dev), dci);
 | 
				
			||||||
 | 
						if (res)
 | 
				
			||||||
 | 
							goto fail1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->db_irq = platform_get_irq(pdev, 1);
 | 
				
			||||||
 | 
						res = devm_request_irq(&pdev->dev, drvdata->db_irq,
 | 
				
			||||||
 | 
								       altr_edac_device_handler,
 | 
				
			||||||
 | 
								       0, dev_name(&pdev->dev), dci);
 | 
				
			||||||
 | 
						if (res)
 | 
				
			||||||
 | 
							goto fail1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dci->mod_name = "Altera ECC Manager";
 | 
				
			||||||
 | 
						dci->dev_name = drvdata->edac_dev_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res = edac_device_add_device(dci);
 | 
				
			||||||
 | 
						if (res)
 | 
				
			||||||
 | 
							goto fail1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						altr_create_edacdev_dbgfs(dci, drvdata->data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						devres_close_group(&pdev->dev, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fail1:
 | 
				
			||||||
 | 
						edac_device_free_ctl_info(dci);
 | 
				
			||||||
 | 
					fail:
 | 
				
			||||||
 | 
						devres_release_group(&pdev->dev, NULL);
 | 
				
			||||||
 | 
						edac_printk(KERN_ERR, EDAC_DEVICE,
 | 
				
			||||||
 | 
							    "%s:Error setting up EDAC device: %d\n", ecc_name, res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int altr_edac_device_remove(struct platform_device *pdev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct edac_device_ctl_info *dci = platform_get_drvdata(pdev);
 | 
				
			||||||
 | 
						struct altr_edac_device_dev *drvdata = dci->pvt_info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						debugfs_remove_recursive(drvdata->debugfs_dir);
 | 
				
			||||||
 | 
						edac_device_del_device(&pdev->dev);
 | 
				
			||||||
 | 
						edac_device_free_ctl_info(dci);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct platform_driver altr_edac_device_driver = {
 | 
				
			||||||
 | 
						.probe =  altr_edac_device_probe,
 | 
				
			||||||
 | 
						.remove = altr_edac_device_remove,
 | 
				
			||||||
 | 
						.driver = {
 | 
				
			||||||
 | 
							.name = "altr_edac_device",
 | 
				
			||||||
 | 
							.of_match_table = altr_edac_device_of_match,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					module_platform_driver(altr_edac_device_driver);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*********************** OCRAM EDAC Device Functions *********************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_EDAC_ALTERA_OCRAM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void *ocram_alloc_mem(size_t size, void **other)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct device_node *np;
 | 
				
			||||||
 | 
						struct gen_pool *gp;
 | 
				
			||||||
 | 
						void *sram_addr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np = of_find_compatible_node(NULL, NULL, "altr,socfpga-ocram-ecc");
 | 
				
			||||||
 | 
						if (!np)
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gp = of_gen_pool_get(np, "iram", 0);
 | 
				
			||||||
 | 
						of_node_put(np);
 | 
				
			||||||
 | 
						if (!gp)
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sram_addr = (void *)gen_pool_alloc(gp, size);
 | 
				
			||||||
 | 
						if (!sram_addr)
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						memset(sram_addr, 0, size);
 | 
				
			||||||
 | 
						/* Ensure data is written out */
 | 
				
			||||||
 | 
						wmb();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Remember this handle for freeing  later */
 | 
				
			||||||
 | 
						*other = gp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sram_addr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (readl(base) & ALTR_OCR_ECC_EN)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						edac_printk(KERN_ERR, EDAC_DEVICE,
 | 
				
			||||||
 | 
							    "OCRAM: No ECC present or ECC disabled.\n");
 | 
				
			||||||
 | 
						return -ENODEV;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const struct edac_device_prv_data ocramecc_data = {
 | 
				
			||||||
 | 
						.setup = altr_ocram_check_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,
 | 
				
			||||||
 | 
						.ce_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJS),
 | 
				
			||||||
 | 
						.ue_set_mask = (ALTR_OCR_ECC_EN | ALTR_OCR_ECC_INJD),
 | 
				
			||||||
 | 
						.trig_alloc_sz = ALTR_TRIG_OCRAM_BYTE_SIZE,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif	/* CONFIG_EDAC_ALTERA_OCRAM */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/********************* L2 Cache EDAC Device Functions ********************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_EDAC_ALTERA_L2C
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void *l2_alloc_mem(size_t size, void **other)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct device *dev = *other;
 | 
				
			||||||
 | 
						void *ptemp = devm_kzalloc(dev, size, GFP_KERNEL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!ptemp)
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Make sure everything is written out */
 | 
				
			||||||
 | 
						wmb();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Clean all cache levels up to LoC (includes L2)
 | 
				
			||||||
 | 
						 * This ensures the corrupted data is written into
 | 
				
			||||||
 | 
						 * L2 cache for readback test (which causes ECC error).
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						flush_cache_all();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ptemp;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void l2_free_mem(void *p, size_t size, void *other)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct device *dev = other;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (dev && p)
 | 
				
			||||||
 | 
							devm_kfree(dev, p);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * altr_l2_check_deps()
 | 
				
			||||||
 | 
					 *	Test for L2 cache ECC dependencies upon entry because
 | 
				
			||||||
 | 
					 *	platform specific startup should have initialized the L2
 | 
				
			||||||
 | 
					 *	memory and enabled the ECC.
 | 
				
			||||||
 | 
					 *	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)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (readl(base) & ALTR_L2_ECC_EN)
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						edac_printk(KERN_ERR, EDAC_DEVICE,
 | 
				
			||||||
 | 
							    "L2: No ECC present, or ECC disabled\n");
 | 
				
			||||||
 | 
						return -ENODEV;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const struct edac_device_prv_data l2ecc_data = {
 | 
				
			||||||
 | 
						.setup = altr_l2_check_deps,
 | 
				
			||||||
 | 
						.ce_clear_mask = 0,
 | 
				
			||||||
 | 
						.ue_clear_mask = 0,
 | 
				
			||||||
 | 
						.dbgfs_name = "altr_l2_trigger",
 | 
				
			||||||
 | 
						.alloc_mem = l2_alloc_mem,
 | 
				
			||||||
 | 
						.free_mem = l2_free_mem,
 | 
				
			||||||
 | 
						.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),
 | 
				
			||||||
 | 
						.trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif	/* CONFIG_EDAC_ALTERA_L2C */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MODULE_LICENSE("GPL v2");
 | 
					MODULE_LICENSE("GPL v2");
 | 
				
			||||||
MODULE_AUTHOR("Thor Thayer");
 | 
					MODULE_AUTHOR("Thor Thayer");
 | 
				
			||||||
MODULE_DESCRIPTION("EDAC Driver for Altera SDRAM Controller");
 | 
					MODULE_DESCRIPTION("EDAC Driver for Altera Memories");
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue