diff options
author | Rob Herring <rob.herring@calxeda.com> | 2012-06-13 13:01:55 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2012-06-27 08:00:57 -0400 |
commit | a1b01edb274518c7da6d69b84e7558c092282aad (patch) | |
tree | fe476bbb5048472e9347c20b832fc4ccba08f27e /drivers/edac | |
parent | e7930ba49e469d9ce7374a788336caf955f8d7e2 (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/Kconfig | 9 | ||||
-rw-r--r-- | drivers/edac/Makefile | 2 | ||||
-rw-r--r-- | drivers/edac/highbank_mc_edac.c | 264 |
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 @@ | |||
7 | menuconfig EDAC | 7 | menuconfig 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 | ||
305 | config 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 | |||
305 | endif # EDAC | 312 | endif # 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 | |||
55 | obj-$(CONFIG_EDAC_AMD8131) += amd8131_edac.o | 55 | obj-$(CONFIG_EDAC_AMD8131) += amd8131_edac.o |
56 | 56 | ||
57 | obj-$(CONFIG_EDAC_TILE) += tile_edac.o | 57 | obj-$(CONFIG_EDAC_TILE) += tile_edac.o |
58 | |||
59 | obj-$(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 | |||
52 | struct hb_mc_drvdata { | ||
53 | void __iomem *mc_vbase; | ||
54 | }; | ||
55 | |||
56 | static 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 | ||
90 | static 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 | |||
116 | static int debugfs_open(struct inode *inode, struct file *file) | ||
117 | { | ||
118 | file->private_data = inode->i_private; | ||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | static 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 | |||
128 | static 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 | ||
136 | static void __devinit highbank_mc_create_debugfs_nodes(struct mem_ctl_info *mci) | ||
137 | {} | ||
138 | #endif | ||
139 | |||
140 | static 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; | ||
230 | err: | ||
231 | devres_release_group(&pdev->dev, NULL); | ||
232 | edac_mc_free(mci); | ||
233 | return res; | ||
234 | } | ||
235 | |||
236 | static 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 | |||
245 | static const struct of_device_id hb_ddr_ctrl_of_match[] = { | ||
246 | { .compatible = "calxeda,hb-ddr-ctrl", }, | ||
247 | {}, | ||
248 | }; | ||
249 | MODULE_DEVICE_TABLE(of, hb_ddr_ctrl_of_match); | ||
250 | |||
251 | static 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 | |||
260 | module_platform_driver(highbank_mc_edac_driver); | ||
261 | |||
262 | MODULE_LICENSE("GPL v2"); | ||
263 | MODULE_AUTHOR("Calxeda, Inc."); | ||
264 | MODULE_DESCRIPTION("EDAC Driver for Calxeda Highbank"); | ||