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:
Thor Thayer
2016-02-10 13:26:21 -06:00
committed by Borislav Petkov
parent 9bf4f00567
commit c3eea1942a
3 changed files with 512 additions and 8 deletions

View File

@@ -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");