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
|
||||
Cavium Octeon family of SOCs.
|
||||
|
||||
config EDAC_ALTERA_MC
|
||||
bool "Altera SDRAM Memory Controller EDAC"
|
||||
config EDAC_ALTERA
|
||||
bool "Altera SOCFPGA ECC"
|
||||
depends on EDAC_MM_EDAC=y && ARCH_SOCFPGA
|
||||
help
|
||||
Support for error detection and correction on the
|
||||
Altera SDRAM memory controller. Note that the
|
||||
preloader must initialize the SDRAM before loading
|
||||
the kernel.
|
||||
Altera SOCs. This must be selected for SDRAM ECC.
|
||||
Note that the preloader must initialize the SDRAM
|
||||
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
|
||||
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_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_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.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
|
@ -17,8 +17,10 @@
|
|||
* Adapted from the highbank_mc_edac driver.
|
||||
*/
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/edac.h>
|
||||
#include <linux/genalloc.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
|
@ -34,6 +36,7 @@
|
|||
|
||||
#define EDAC_MOD_STR "altera_edac"
|
||||
#define EDAC_VERSION "1"
|
||||
#define EDAC_DEVICE "Altera"
|
||||
|
||||
static const struct altr_sdram_prv_data c5_data = {
|
||||
.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,
|
||||
};
|
||||
|
||||
/************************** 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)
|
||||
{
|
||||
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);
|
||||
|
||||
/************************* 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_AUTHOR("Thor Thayer");
|
||||
MODULE_DESCRIPTION("EDAC Driver for Altera SDRAM Controller");
|
||||
MODULE_DESCRIPTION("EDAC Driver for Altera Memories");
|
||||
|
|
Loading…
Reference in a new issue