edac, highbank: Add Calxeda ECX-2000 support

Implement edac support for Calxeda ECX-2000.

The ECX-2000 memory controller is similar to Highbank but has
different register bases for error and interrupt registers. There is
an own device tree name "calxeda,ecx-2000-ddr-ctrl" for identification
and initialization of the ECX-2000 and its base addresses.

Signed-off-by: Robert Richter <robert.richter@linaro.org>
Acked-by: Rob Herring <rob.herring@calxeda.com>
Signed-off-by: Robert Richter <rric@kernel.org>
This commit is contained in:
Robert Richter 2013-10-10 17:32:30 +02:00 committed by Robert Richter
parent 982ac2a7b7
commit 0ec8579e16
1 changed files with 70 additions and 35 deletions

View File

@ -26,31 +26,40 @@
#include "edac_module.h"
/* DDR Ctrlr Error Registers */
#define HB_DDR_ECC_OPT 0x128
#define HB_DDR_ECC_U_ERR_ADDR 0x130
#define HB_DDR_ECC_U_ERR_STAT 0x134
#define HB_DDR_ECC_U_ERR_DATAL 0x138
#define HB_DDR_ECC_U_ERR_DATAH 0x13c
#define HB_DDR_ECC_C_ERR_ADDR 0x140
#define HB_DDR_ECC_C_ERR_STAT 0x144
#define HB_DDR_ECC_C_ERR_DATAL 0x148
#define HB_DDR_ECC_C_ERR_DATAH 0x14c
#define HB_DDR_ECC_INT_STATUS 0x180
#define HB_DDR_ECC_INT_ACK 0x184
#define HB_DDR_ECC_U_ERR_ID 0x424
#define HB_DDR_ECC_C_ERR_ID 0x428
#define HB_DDR_ECC_ERR_BASE 0x128
#define MW_DDR_ECC_ERR_BASE 0x1b4
#define HB_DDR_ECC_OPT 0x00
#define HB_DDR_ECC_U_ERR_ADDR 0x08
#define HB_DDR_ECC_U_ERR_STAT 0x0c
#define HB_DDR_ECC_U_ERR_DATAL 0x10
#define HB_DDR_ECC_U_ERR_DATAH 0x14
#define HB_DDR_ECC_C_ERR_ADDR 0x18
#define HB_DDR_ECC_C_ERR_STAT 0x1c
#define HB_DDR_ECC_C_ERR_DATAL 0x20
#define HB_DDR_ECC_C_ERR_DATAH 0x24
#define HB_DDR_ECC_OPT_MODE_MASK 0x3
#define HB_DDR_ECC_OPT_FWC 0x100
#define HB_DDR_ECC_OPT_XOR_SHIFT 16
/* DDR Ctrlr Interrupt Registers */
#define HB_DDR_ECC_INT_BASE 0x180
#define MW_DDR_ECC_INT_BASE 0x218
#define HB_DDR_ECC_INT_STATUS 0x00
#define HB_DDR_ECC_INT_ACK 0x04
#define HB_DDR_ECC_INT_STAT_CE 0x8
#define HB_DDR_ECC_INT_STAT_DOUBLE_CE 0x10
#define HB_DDR_ECC_INT_STAT_UE 0x20
#define HB_DDR_ECC_INT_STAT_DOUBLE_UE 0x40
#define HB_DDR_ECC_OPT_MODE_MASK 0x3
#define HB_DDR_ECC_OPT_FWC 0x100
#define HB_DDR_ECC_OPT_XOR_SHIFT 16
struct hb_mc_drvdata {
void __iomem *mc_vbase;
void __iomem *mc_err_base;
void __iomem *mc_int_base;
};
static irqreturn_t highbank_mc_err_handler(int irq, void *dev_id)
@ -60,10 +69,10 @@ static irqreturn_t highbank_mc_err_handler(int irq, void *dev_id)
u32 status, err_addr;
/* Read the interrupt status register */
status = readl(drvdata->mc_vbase + HB_DDR_ECC_INT_STATUS);
status = readl(drvdata->mc_int_base + HB_DDR_ECC_INT_STATUS);
if (status & HB_DDR_ECC_INT_STAT_UE) {
err_addr = readl(drvdata->mc_vbase + HB_DDR_ECC_U_ERR_ADDR);
err_addr = readl(drvdata->mc_err_base + HB_DDR_ECC_U_ERR_ADDR);
edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1,
err_addr >> PAGE_SHIFT,
err_addr & ~PAGE_MASK, 0,
@ -71,9 +80,9 @@ static irqreturn_t highbank_mc_err_handler(int irq, void *dev_id)
mci->ctl_name, "");
}
if (status & HB_DDR_ECC_INT_STAT_CE) {
u32 syndrome = readl(drvdata->mc_vbase + HB_DDR_ECC_C_ERR_STAT);
u32 syndrome = readl(drvdata->mc_err_base + HB_DDR_ECC_C_ERR_STAT);
syndrome = (syndrome >> 8) & 0xff;
err_addr = readl(drvdata->mc_vbase + HB_DDR_ECC_C_ERR_ADDR);
err_addr = readl(drvdata->mc_err_base + HB_DDR_ECC_C_ERR_ADDR);
edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1,
err_addr >> PAGE_SHIFT,
err_addr & ~PAGE_MASK, syndrome,
@ -82,7 +91,7 @@ static irqreturn_t highbank_mc_err_handler(int irq, void *dev_id)
}
/* clear the error, clears the interrupt */
writel(status, drvdata->mc_vbase + HB_DDR_ECC_INT_ACK);
writel(status, drvdata->mc_int_base + HB_DDR_ECC_INT_ACK);
return IRQ_HANDLED;
}
@ -104,10 +113,10 @@ static ssize_t highbank_mc_err_inject_write(struct file *file,
buf[buf_size] = 0;
if (!kstrtou8(buf, 16, &synd)) {
reg = readl(pdata->mc_vbase + HB_DDR_ECC_OPT);
reg = readl(pdata->mc_err_base + HB_DDR_ECC_OPT);
reg &= HB_DDR_ECC_OPT_MODE_MASK;
reg |= (synd << HB_DDR_ECC_OPT_XOR_SHIFT) | HB_DDR_ECC_OPT_FWC;
writel(reg, pdata->mc_vbase + HB_DDR_ECC_OPT);
writel(reg, pdata->mc_err_base + HB_DDR_ECC_OPT);
}
return count;
@ -131,17 +140,46 @@ static void highbank_mc_create_debugfs_nodes(struct mem_ctl_info *mci)
{}
#endif
struct hb_mc_settings {
int err_offset;
int int_offset;
};
static struct hb_mc_settings hb_settings = {
.err_offset = HB_DDR_ECC_ERR_BASE,
.int_offset = HB_DDR_ECC_INT_BASE,
};
static struct hb_mc_settings mw_settings = {
.err_offset = MW_DDR_ECC_ERR_BASE,
.int_offset = MW_DDR_ECC_INT_BASE,
};
static struct of_device_id hb_ddr_ctrl_of_match[] = {
{ .compatible = "calxeda,hb-ddr-ctrl", .data = &hb_settings },
{ .compatible = "calxeda,ecx-2000-ddr-ctrl", .data = &mw_settings },
{},
};
MODULE_DEVICE_TABLE(of, hb_ddr_ctrl_of_match);
static int highbank_mc_probe(struct platform_device *pdev)
{
const struct of_device_id *id;
const struct hb_mc_settings *settings;
struct edac_mc_layer layers[2];
struct mem_ctl_info *mci;
struct hb_mc_drvdata *drvdata;
struct dimm_info *dimm;
struct resource *r;
void __iomem *base;
u32 control;
int irq;
int res = 0;
id = of_match_device(hb_ddr_ctrl_of_match, &pdev->dev);
if (!id)
return -ENODEV;
layers[0].type = EDAC_MC_LAYER_CHIP_SELECT;
layers[0].size = 1;
layers[0].is_virt_csrow = true;
@ -174,15 +212,18 @@ static int highbank_mc_probe(struct platform_device *pdev)
goto err;
}
drvdata->mc_vbase = devm_ioremap(&pdev->dev,
r->start, resource_size(r));
if (!drvdata->mc_vbase) {
base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
if (!base) {
dev_err(&pdev->dev, "Unable to map regs\n");
res = -ENOMEM;
goto err;
}
control = readl(drvdata->mc_vbase + HB_DDR_ECC_OPT) & 0x3;
settings = id->data;
drvdata->mc_err_base = base + settings->err_offset;
drvdata->mc_int_base = base + settings->int_offset;
control = readl(drvdata->mc_err_base + HB_DDR_ECC_OPT) & 0x3;
if (!control || (control == 0x2)) {
dev_err(&pdev->dev, "No ECC present, or ECC disabled\n");
res = -ENODEV;
@ -238,12 +279,6 @@ static int highbank_mc_remove(struct platform_device *pdev)
return 0;
}
static const struct of_device_id hb_ddr_ctrl_of_match[] = {
{ .compatible = "calxeda,hb-ddr-ctrl", },
{},
};
MODULE_DEVICE_TABLE(of, hb_ddr_ctrl_of_match);
static struct platform_driver highbank_mc_edac_driver = {
.probe = highbank_mc_probe,
.remove = highbank_mc_remove,