aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/edac
diff options
context:
space:
mode:
authorRob Herring <rob.herring@calxeda.com>2012-06-13 13:01:55 -0400
committerMauro Carvalho Chehab <mchehab@redhat.com>2012-06-27 08:00:57 -0400
commita1b01edb274518c7da6d69b84e7558c092282aad (patch)
treefe476bbb5048472e9347c20b832fc4ccba08f27e /drivers/edac
parente7930ba49e469d9ce7374a788336caf955f8d7e2 (diff)
edac: add support for Calxeda highbank memory controller
Add support for memory controller on Calxeda Highbank platforms. Highbank platforms support a single 4GB mini-DIMM with 1-bit correction and 2-bit detection. Signed-off-by: Rob Herring <rob.herring@calxeda.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/edac')
-rw-r--r--drivers/edac/Kconfig9
-rw-r--r--drivers/edac/Makefile2
-rw-r--r--drivers/edac/highbank_mc_edac.c264
3 files changed, 274 insertions, 1 deletions
diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig
index 3b3f84ff351f..f7efa8b282f1 100644
--- a/drivers/edac/Kconfig
+++ b/drivers/edac/Kconfig
@@ -7,7 +7,7 @@
7menuconfig EDAC 7menuconfig EDAC
8 bool "EDAC (Error Detection And Correction) reporting" 8 bool "EDAC (Error Detection And Correction) reporting"
9 depends on HAS_IOMEM 9 depends on HAS_IOMEM
10 depends on X86 || PPC || TILE 10 depends on X86 || PPC || TILE || ARM
11 help 11 help
12 EDAC is designed to report errors in the core system. 12 EDAC is designed to report errors in the core system.
13 These are low-level errors that are reported in the CPU or 13 These are low-level errors that are reported in the CPU or
@@ -302,4 +302,11 @@ config EDAC_TILE
302 Support for error detection and correction on the 302 Support for error detection and correction on the
303 Tilera memory controller. 303 Tilera memory controller.
304 304
305config EDAC_HIGHBANK_MC
306 tristate "Highbank Memory Controller"
307 depends on EDAC_MM_EDAC && ARCH_HIGHBANK
308 help
309 Support for error detection and correction on the
310 Calxeda Highbank memory controller.
311
305endif # EDAC 312endif # EDAC
diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile
index 196a63dd37c5..44f2044d4079 100644
--- a/drivers/edac/Makefile
+++ b/drivers/edac/Makefile
@@ -55,3 +55,5 @@ obj-$(CONFIG_EDAC_AMD8111) += amd8111_edac.o
55obj-$(CONFIG_EDAC_AMD8131) += amd8131_edac.o 55obj-$(CONFIG_EDAC_AMD8131) += amd8131_edac.o
56 56
57obj-$(CONFIG_EDAC_TILE) += tile_edac.o 57obj-$(CONFIG_EDAC_TILE) += tile_edac.o
58
59obj-$(CONFIG_EDAC_HIGHBANK_MC) += highbank_mc_edac.o
diff --git a/drivers/edac/highbank_mc_edac.c b/drivers/edac/highbank_mc_edac.c
new file mode 100644
index 000000000000..c769f477fd22
--- /dev/null
+++ b/drivers/edac/highbank_mc_edac.c
@@ -0,0 +1,264 @@
1/*
2 * Copyright 2011-2012 Calxeda, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License along with
14 * this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#include <linux/types.h>
17#include <linux/kernel.h>
18#include <linux/ctype.h>
19#include <linux/edac.h>
20#include <linux/interrupt.h>
21#include <linux/platform_device.h>
22#include <linux/of_platform.h>
23#include <linux/uaccess.h>
24
25#include "edac_core.h"
26#include "edac_module.h"
27
28/* DDR Ctrlr Error Registers */
29#define HB_DDR_ECC_OPT 0x128
30#define HB_DDR_ECC_U_ERR_ADDR 0x130
31#define HB_DDR_ECC_U_ERR_STAT 0x134
32#define HB_DDR_ECC_U_ERR_DATAL 0x138
33#define HB_DDR_ECC_U_ERR_DATAH 0x13c
34#define HB_DDR_ECC_C_ERR_ADDR 0x140
35#define HB_DDR_ECC_C_ERR_STAT 0x144
36#define HB_DDR_ECC_C_ERR_DATAL 0x148
37#define HB_DDR_ECC_C_ERR_DATAH 0x14c
38#define HB_DDR_ECC_INT_STATUS 0x180
39#define HB_DDR_ECC_INT_ACK 0x184
40#define HB_DDR_ECC_U_ERR_ID 0x424
41#define HB_DDR_ECC_C_ERR_ID 0x428
42
43#define HB_DDR_ECC_INT_STAT_CE 0x8
44#define HB_DDR_ECC_INT_STAT_DOUBLE_CE 0x10
45#define HB_DDR_ECC_INT_STAT_UE 0x20
46#define HB_DDR_ECC_INT_STAT_DOUBLE_UE 0x40
47
48#define HB_DDR_ECC_OPT_MODE_MASK 0x3
49#define HB_DDR_ECC_OPT_FWC 0x100
50#define HB_DDR_ECC_OPT_XOR_SHIFT 16
51
52struct hb_mc_drvdata {
53 void __iomem *mc_vbase;
54};
55
56static irqreturn_t highbank_mc_err_handler(int irq, void *dev_id)
57{
58 struct mem_ctl_info *mci = dev_id;
59 struct hb_mc_drvdata *drvdata = mci->pvt_info;
60 u32 status, err_addr;
61
62 /* Read the interrupt status register */
63 status = readl(drvdata->mc_vbase + HB_DDR_ECC_INT_STATUS);
64
65 if (status & HB_DDR_ECC_INT_STAT_UE) {
66 err_addr = readl(drvdata->mc_vbase + HB_DDR_ECC_U_ERR_ADDR);
67 edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1,
68 err_addr >> PAGE_SHIFT,
69 err_addr & ~PAGE_MASK, 0,
70 0, 0, -1,
71 mci->ctl_name, "");
72 }
73 if (status & HB_DDR_ECC_INT_STAT_CE) {
74 u32 syndrome = readl(drvdata->mc_vbase + HB_DDR_ECC_C_ERR_STAT);
75 syndrome = (syndrome >> 8) & 0xff;
76 err_addr = readl(drvdata->mc_vbase + HB_DDR_ECC_C_ERR_ADDR);
77 edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1,
78 err_addr >> PAGE_SHIFT,
79 err_addr & ~PAGE_MASK, syndrome,
80 0, 0, -1,
81 mci->ctl_name, "");
82 }
83
84 /* clear the error, clears the interrupt */
85 writel(status, drvdata->mc_vbase + HB_DDR_ECC_INT_ACK);
86 return IRQ_HANDLED;
87}
88
89#ifdef CONFIG_EDAC_DEBUG
90static ssize_t highbank_mc_err_inject_write(struct file *file,
91 const char __user *data,
92 size_t count, loff_t *ppos)
93{
94 struct mem_ctl_info *mci = file->private_data;
95 struct hb_mc_drvdata *pdata = mci->pvt_info;
96 char buf[32];
97 size_t buf_size;
98 u32 reg;
99 u8 synd;
100
101 buf_size = min(count, (sizeof(buf)-1));
102 if (copy_from_user(buf, data, buf_size))
103 return -EFAULT;
104 buf[buf_size] = 0;
105
106 if (!kstrtou8(buf, 16, &synd)) {
107 reg = readl(pdata->mc_vbase + HB_DDR_ECC_OPT);
108 reg &= HB_DDR_ECC_OPT_MODE_MASK;
109 reg |= (synd << HB_DDR_ECC_OPT_XOR_SHIFT) | HB_DDR_ECC_OPT_FWC;
110 writel(reg, pdata->mc_vbase + HB_DDR_ECC_OPT);
111 }
112
113 return count;
114}
115
116static int debugfs_open(struct inode *inode, struct file *file)
117{
118 file->private_data = inode->i_private;
119 return 0;
120}
121
122static const struct file_operations highbank_mc_debug_inject_fops = {
123 .open = debugfs_open,
124 .write = highbank_mc_err_inject_write,
125 .llseek = generic_file_llseek,
126};
127
128static void __devinit highbank_mc_create_debugfs_nodes(struct mem_ctl_info *mci)
129{
130 if (mci->debugfs)
131 debugfs_create_file("inject_ctrl", S_IWUSR, mci->debugfs, mci,
132 &highbank_mc_debug_inject_fops);
133;
134}
135#else
136static void __devinit highbank_mc_create_debugfs_nodes(struct mem_ctl_info *mci)
137{}
138#endif
139
140static int __devinit highbank_mc_probe(struct platform_device *pdev)
141{
142 struct edac_mc_layer layers[2];
143 struct mem_ctl_info *mci;
144 struct hb_mc_drvdata *drvdata;
145 struct dimm_info *dimm;
146 struct resource *r;
147 u32 control;
148 int irq;
149 int res = 0;
150
151 layers[0].type = EDAC_MC_LAYER_CHIP_SELECT;
152 layers[0].size = 1;
153 layers[0].is_virt_csrow = true;
154 layers[1].type = EDAC_MC_LAYER_CHANNEL;
155 layers[1].size = 1;
156 layers[1].is_virt_csrow = false;
157 mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers,
158 sizeof(struct hb_mc_drvdata));
159 if (!mci)
160 return -ENOMEM;
161
162 mci->pdev = &pdev->dev;
163 drvdata = mci->pvt_info;
164 platform_set_drvdata(pdev, mci);
165
166 if (!devres_open_group(&pdev->dev, NULL, GFP_KERNEL))
167 return -ENOMEM;
168
169 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
170 if (!r) {
171 dev_err(&pdev->dev, "Unable to get mem resource\n");
172 res = -ENODEV;
173 goto err;
174 }
175
176 if (!devm_request_mem_region(&pdev->dev, r->start,
177 resource_size(r), dev_name(&pdev->dev))) {
178 dev_err(&pdev->dev, "Error while requesting mem region\n");
179 res = -EBUSY;
180 goto err;
181 }
182
183 drvdata->mc_vbase = devm_ioremap(&pdev->dev,
184 r->start, resource_size(r));
185 if (!drvdata->mc_vbase) {
186 dev_err(&pdev->dev, "Unable to map regs\n");
187 res = -ENOMEM;
188 goto err;
189 }
190
191 control = readl(drvdata->mc_vbase + HB_DDR_ECC_OPT) & 0x3;
192 if (!control || (control == 0x2)) {
193 dev_err(&pdev->dev, "No ECC present, or ECC disabled\n");
194 res = -ENODEV;
195 goto err;
196 }
197
198 irq = platform_get_irq(pdev, 0);
199 res = devm_request_irq(&pdev->dev, irq, highbank_mc_err_handler,
200 0, dev_name(&pdev->dev), mci);
201 if (res < 0) {
202 dev_err(&pdev->dev, "Unable to request irq %d\n", irq);
203 goto err;
204 }
205
206 mci->mtype_cap = MEM_FLAG_DDR3;
207 mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED;
208 mci->edac_cap = EDAC_FLAG_SECDED;
209 mci->mod_name = dev_name(&pdev->dev);
210 mci->mod_ver = "1";
211 mci->ctl_name = dev_name(&pdev->dev);
212 mci->scrub_mode = SCRUB_SW_SRC;
213
214 /* Only a single 4GB DIMM is supported */
215 dimm = *mci->dimms;
216 dimm->nr_pages = (~0UL >> PAGE_SHIFT) + 1;
217 dimm->grain = 8;
218 dimm->dtype = DEV_X8;
219 dimm->mtype = MEM_DDR3;
220 dimm->edac_mode = EDAC_SECDED;
221
222 res = edac_mc_add_mc(mci);
223 if (res < 0)
224 goto err;
225
226 highbank_mc_create_debugfs_nodes(mci);
227
228 devres_close_group(&pdev->dev, NULL);
229 return 0;
230err:
231 devres_release_group(&pdev->dev, NULL);
232 edac_mc_free(mci);
233 return res;
234}
235
236static int highbank_mc_remove(struct platform_device *pdev)
237{
238 struct mem_ctl_info *mci = platform_get_drvdata(pdev);
239
240 edac_mc_del_mc(&pdev->dev);
241 edac_mc_free(mci);
242 return 0;
243}
244
245static const struct of_device_id hb_ddr_ctrl_of_match[] = {
246 { .compatible = "calxeda,hb-ddr-ctrl", },
247 {},
248};
249MODULE_DEVICE_TABLE(of, hb_ddr_ctrl_of_match);
250
251static struct platform_driver highbank_mc_edac_driver = {
252 .probe = highbank_mc_probe,
253 .remove = highbank_mc_remove,
254 .driver = {
255 .name = "hb_mc_edac",
256 .of_match_table = hb_ddr_ctrl_of_match,
257 },
258};
259
260module_platform_driver(highbank_mc_edac_driver);
261
262MODULE_LICENSE("GPL v2");
263MODULE_AUTHOR("Calxeda, Inc.");
264MODULE_DESCRIPTION("EDAC Driver for Calxeda Highbank");