]>
Commit | Line | Data |
---|---|---|
f65aad41 RB |
1 | /* |
2 | * This file is subject to the terms and conditions of the GNU General Public | |
3 | * License. See the file "COPYING" in the main directory of this archive | |
4 | * for more details. | |
5 | * | |
6 | * Copyright (C) 2009 Wind River Systems, | |
7 | * written by Ralf Baechle <ralf@linux-mips.org> | |
1bc021e8 DW |
8 | * |
9 | * Copyright (c) 2013 by Cisco Systems, Inc. | |
10 | * All rights reserved. | |
f65aad41 RB |
11 | */ |
12 | #include <linux/module.h> | |
13 | #include <linux/init.h> | |
14 | #include <linux/slab.h> | |
15 | #include <linux/io.h> | |
16 | #include <linux/edac.h> | |
1bc021e8 | 17 | #include <linux/ctype.h> |
f65aad41 | 18 | |
e1ced097 DD |
19 | #include <asm/octeon/octeon.h> |
20 | #include <asm/octeon/cvmx-lmcx-defs.h> | |
f65aad41 | 21 | |
f65aad41 | 22 | #include "edac_module.h" |
f65aad41 | 23 | |
e1ced097 | 24 | #define OCTEON_MAX_MC 4 |
f65aad41 | 25 | |
1bc021e8 DW |
26 | #define to_mci(k) container_of(k, struct mem_ctl_info, dev) |
27 | ||
28 | struct octeon_lmc_pvt { | |
29 | unsigned long inject; | |
30 | unsigned long error_type; | |
31 | unsigned long dimm; | |
32 | unsigned long rank; | |
33 | unsigned long bank; | |
34 | unsigned long row; | |
35 | unsigned long col; | |
36 | }; | |
37 | ||
e1ced097 | 38 | static void octeon_lmc_edac_poll(struct mem_ctl_info *mci) |
f65aad41 | 39 | { |
e1ced097 DD |
40 | union cvmx_lmcx_mem_cfg0 cfg0; |
41 | bool do_clear = false; | |
f65aad41 RB |
42 | char msg[64]; |
43 | ||
e1ced097 DD |
44 | cfg0.u64 = cvmx_read_csr(CVMX_LMCX_MEM_CFG0(mci->mc_idx)); |
45 | if (cfg0.s.sec_err || cfg0.s.ded_err) { | |
46 | union cvmx_lmcx_fadr fadr; | |
47 | fadr.u64 = cvmx_read_csr(CVMX_LMCX_FADR(mci->mc_idx)); | |
48 | snprintf(msg, sizeof(msg), | |
49 | "DIMM %d rank %d bank %d row %d col %d", | |
50 | fadr.cn30xx.fdimm, fadr.cn30xx.fbunk, | |
51 | fadr.cn30xx.fbank, fadr.cn30xx.frow, fadr.cn30xx.fcol); | |
f65aad41 RB |
52 | } |
53 | ||
e1ced097 DD |
54 | if (cfg0.s.sec_err) { |
55 | edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, 0, 0, 0, | |
56 | -1, -1, -1, msg, ""); | |
57 | cfg0.s.sec_err = -1; /* Done, re-arm */ | |
58 | do_clear = true; | |
f65aad41 RB |
59 | } |
60 | ||
e1ced097 DD |
61 | if (cfg0.s.ded_err) { |
62 | edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, 0, 0, 0, | |
63 | -1, -1, -1, msg, ""); | |
64 | cfg0.s.ded_err = -1; /* Done, re-arm */ | |
65 | do_clear = true; | |
66 | } | |
67 | if (do_clear) | |
68 | cvmx_write_csr(CVMX_LMCX_MEM_CFG0(mci->mc_idx), cfg0.u64); | |
f65aad41 RB |
69 | } |
70 | ||
e1ced097 | 71 | static void octeon_lmc_edac_poll_o2(struct mem_ctl_info *mci) |
f65aad41 | 72 | { |
1bc021e8 | 73 | struct octeon_lmc_pvt *pvt = mci->pvt_info; |
e1ced097 DD |
74 | union cvmx_lmcx_int int_reg; |
75 | bool do_clear = false; | |
76 | char msg[64]; | |
f65aad41 | 77 | |
1bc021e8 DW |
78 | if (!pvt->inject) |
79 | int_reg.u64 = cvmx_read_csr(CVMX_LMCX_INT(mci->mc_idx)); | |
80 | else { | |
81 | if (pvt->error_type == 1) | |
82 | int_reg.s.sec_err = 1; | |
83 | if (pvt->error_type == 2) | |
84 | int_reg.s.ded_err = 1; | |
85 | } | |
86 | ||
e1ced097 DD |
87 | if (int_reg.s.sec_err || int_reg.s.ded_err) { |
88 | union cvmx_lmcx_fadr fadr; | |
1bc021e8 DW |
89 | if (likely(!pvt->inject)) |
90 | fadr.u64 = cvmx_read_csr(CVMX_LMCX_FADR(mci->mc_idx)); | |
91 | else { | |
92 | fadr.cn61xx.fdimm = pvt->dimm; | |
93 | fadr.cn61xx.fbunk = pvt->rank; | |
94 | fadr.cn61xx.fbank = pvt->bank; | |
95 | fadr.cn61xx.frow = pvt->row; | |
96 | fadr.cn61xx.fcol = pvt->col; | |
97 | } | |
e1ced097 DD |
98 | snprintf(msg, sizeof(msg), |
99 | "DIMM %d rank %d bank %d row %d col %d", | |
100 | fadr.cn61xx.fdimm, fadr.cn61xx.fbunk, | |
101 | fadr.cn61xx.fbank, fadr.cn61xx.frow, fadr.cn61xx.fcol); | |
102 | } | |
f65aad41 | 103 | |
e1ced097 DD |
104 | if (int_reg.s.sec_err) { |
105 | edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, 0, 0, 0, | |
106 | -1, -1, -1, msg, ""); | |
107 | int_reg.s.sec_err = -1; /* Done, re-arm */ | |
108 | do_clear = true; | |
f65aad41 RB |
109 | } |
110 | ||
e1ced097 DD |
111 | if (int_reg.s.ded_err) { |
112 | edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, 0, 0, 0, | |
113 | -1, -1, -1, msg, ""); | |
114 | int_reg.s.ded_err = -1; /* Done, re-arm */ | |
115 | do_clear = true; | |
116 | } | |
1bc021e8 DW |
117 | |
118 | if (do_clear) { | |
119 | if (likely(!pvt->inject)) | |
120 | cvmx_write_csr(CVMX_LMCX_INT(mci->mc_idx), int_reg.u64); | |
121 | else | |
122 | pvt->inject = 0; | |
123 | } | |
124 | } | |
125 | ||
126 | /************************ MC SYSFS parts ***********************************/ | |
127 | ||
128 | /* Only a couple naming differences per template, so very similar */ | |
129 | #define TEMPLATE_SHOW(reg) \ | |
130 | static ssize_t octeon_mc_inject_##reg##_show(struct device *dev, \ | |
131 | struct device_attribute *attr, \ | |
132 | char *data) \ | |
133 | { \ | |
134 | struct mem_ctl_info *mci = to_mci(dev); \ | |
135 | struct octeon_lmc_pvt *pvt = mci->pvt_info; \ | |
136 | return sprintf(data, "%016llu\n", (u64)pvt->reg); \ | |
137 | } | |
138 | ||
139 | #define TEMPLATE_STORE(reg) \ | |
140 | static ssize_t octeon_mc_inject_##reg##_store(struct device *dev, \ | |
141 | struct device_attribute *attr, \ | |
142 | const char *data, size_t count) \ | |
143 | { \ | |
144 | struct mem_ctl_info *mci = to_mci(dev); \ | |
145 | struct octeon_lmc_pvt *pvt = mci->pvt_info; \ | |
146 | if (isdigit(*data)) { \ | |
147 | if (!kstrtoul(data, 0, &pvt->reg)) \ | |
148 | return count; \ | |
149 | } \ | |
150 | return 0; \ | |
151 | } | |
152 | ||
153 | TEMPLATE_SHOW(inject); | |
154 | TEMPLATE_STORE(inject); | |
155 | TEMPLATE_SHOW(dimm); | |
156 | TEMPLATE_STORE(dimm); | |
157 | TEMPLATE_SHOW(bank); | |
158 | TEMPLATE_STORE(bank); | |
159 | TEMPLATE_SHOW(rank); | |
160 | TEMPLATE_STORE(rank); | |
161 | TEMPLATE_SHOW(row); | |
162 | TEMPLATE_STORE(row); | |
163 | TEMPLATE_SHOW(col); | |
164 | TEMPLATE_STORE(col); | |
165 | ||
166 | static ssize_t octeon_mc_inject_error_type_store(struct device *dev, | |
167 | struct device_attribute *attr, | |
168 | const char *data, | |
169 | size_t count) | |
170 | { | |
171 | struct mem_ctl_info *mci = to_mci(dev); | |
172 | struct octeon_lmc_pvt *pvt = mci->pvt_info; | |
173 | ||
174 | if (!strncmp(data, "single", 6)) | |
175 | pvt->error_type = 1; | |
176 | else if (!strncmp(data, "double", 6)) | |
177 | pvt->error_type = 2; | |
178 | ||
179 | return count; | |
180 | } | |
181 | ||
182 | static ssize_t octeon_mc_inject_error_type_show(struct device *dev, | |
183 | struct device_attribute *attr, | |
184 | char *data) | |
185 | { | |
186 | struct mem_ctl_info *mci = to_mci(dev); | |
187 | struct octeon_lmc_pvt *pvt = mci->pvt_info; | |
188 | if (pvt->error_type == 1) | |
189 | return sprintf(data, "single"); | |
190 | else if (pvt->error_type == 2) | |
191 | return sprintf(data, "double"); | |
192 | ||
193 | return 0; | |
194 | } | |
195 | ||
196 | static DEVICE_ATTR(inject, S_IRUGO | S_IWUSR, | |
197 | octeon_mc_inject_inject_show, octeon_mc_inject_inject_store); | |
198 | static DEVICE_ATTR(error_type, S_IRUGO | S_IWUSR, | |
199 | octeon_mc_inject_error_type_show, octeon_mc_inject_error_type_store); | |
200 | static DEVICE_ATTR(dimm, S_IRUGO | S_IWUSR, | |
201 | octeon_mc_inject_dimm_show, octeon_mc_inject_dimm_store); | |
202 | static DEVICE_ATTR(rank, S_IRUGO | S_IWUSR, | |
203 | octeon_mc_inject_rank_show, octeon_mc_inject_rank_store); | |
204 | static DEVICE_ATTR(bank, S_IRUGO | S_IWUSR, | |
205 | octeon_mc_inject_bank_show, octeon_mc_inject_bank_store); | |
206 | static DEVICE_ATTR(row, S_IRUGO | S_IWUSR, | |
207 | octeon_mc_inject_row_show, octeon_mc_inject_row_store); | |
208 | static DEVICE_ATTR(col, S_IRUGO | S_IWUSR, | |
209 | octeon_mc_inject_col_show, octeon_mc_inject_col_store); | |
210 | ||
1bf06a0d TI |
211 | static struct attribute *octeon_dev_attrs[] = { |
212 | &dev_attr_inject.attr, | |
213 | &dev_attr_error_type.attr, | |
214 | &dev_attr_dimm.attr, | |
215 | &dev_attr_rank.attr, | |
216 | &dev_attr_bank.attr, | |
217 | &dev_attr_row.attr, | |
218 | &dev_attr_col.attr, | |
219 | NULL | |
220 | }; | |
1bc021e8 | 221 | |
1bf06a0d | 222 | ATTRIBUTE_GROUPS(octeon_dev); |
f65aad41 | 223 | |
9b3c6e85 | 224 | static int octeon_lmc_edac_probe(struct platform_device *pdev) |
e1ced097 DD |
225 | { |
226 | struct mem_ctl_info *mci; | |
227 | struct edac_mc_layer layers[1]; | |
228 | int mc = pdev->id; | |
229 | ||
5331de05 DW |
230 | opstate_init(); |
231 | ||
e1ced097 DD |
232 | layers[0].type = EDAC_MC_LAYER_CHANNEL; |
233 | layers[0].size = 1; | |
234 | layers[0].is_virt_csrow = false; | |
235 | ||
75a15a78 | 236 | if (OCTEON_IS_OCTEON1PLUS()) { |
e1ced097 DD |
237 | union cvmx_lmcx_mem_cfg0 cfg0; |
238 | ||
239 | cfg0.u64 = cvmx_read_csr(CVMX_LMCX_MEM_CFG0(0)); | |
240 | if (!cfg0.s.ecc_ena) { | |
241 | dev_info(&pdev->dev, "Disabled (ECC not enabled)\n"); | |
242 | return 0; | |
243 | } | |
244 | ||
1bc021e8 | 245 | mci = edac_mc_alloc(mc, ARRAY_SIZE(layers), layers, sizeof(struct octeon_lmc_pvt)); |
e1ced097 DD |
246 | if (!mci) |
247 | return -ENXIO; | |
248 | ||
249 | mci->pdev = &pdev->dev; | |
250 | mci->dev_name = dev_name(&pdev->dev); | |
251 | ||
252 | mci->mod_name = "octeon-lmc"; | |
253 | mci->ctl_name = "octeon-lmc-err"; | |
254 | mci->edac_check = octeon_lmc_edac_poll; | |
255 | ||
1bf06a0d | 256 | if (edac_mc_add_mc_with_groups(mci, octeon_dev_groups)) { |
e1ced097 DD |
257 | dev_err(&pdev->dev, "edac_mc_add_mc() failed\n"); |
258 | edac_mc_free(mci); | |
259 | return -ENXIO; | |
260 | } | |
261 | ||
262 | cfg0.u64 = cvmx_read_csr(CVMX_LMCX_MEM_CFG0(mc)); | |
263 | cfg0.s.intr_ded_ena = 0; /* We poll */ | |
264 | cfg0.s.intr_sec_ena = 0; | |
265 | cvmx_write_csr(CVMX_LMCX_MEM_CFG0(mc), cfg0.u64); | |
266 | } else { | |
267 | /* OCTEON II */ | |
268 | union cvmx_lmcx_int_en en; | |
269 | union cvmx_lmcx_config config; | |
270 | ||
271 | config.u64 = cvmx_read_csr(CVMX_LMCX_CONFIG(0)); | |
272 | if (!config.s.ecc_ena) { | |
273 | dev_info(&pdev->dev, "Disabled (ECC not enabled)\n"); | |
274 | return 0; | |
275 | } | |
276 | ||
1bc021e8 | 277 | mci = edac_mc_alloc(mc, ARRAY_SIZE(layers), layers, sizeof(struct octeon_lmc_pvt)); |
e1ced097 DD |
278 | if (!mci) |
279 | return -ENXIO; | |
280 | ||
281 | mci->pdev = &pdev->dev; | |
282 | mci->dev_name = dev_name(&pdev->dev); | |
283 | ||
284 | mci->mod_name = "octeon-lmc"; | |
285 | mci->ctl_name = "co_lmc_err"; | |
286 | mci->edac_check = octeon_lmc_edac_poll_o2; | |
287 | ||
1bf06a0d | 288 | if (edac_mc_add_mc_with_groups(mci, octeon_dev_groups)) { |
e1ced097 DD |
289 | dev_err(&pdev->dev, "edac_mc_add_mc() failed\n"); |
290 | edac_mc_free(mci); | |
291 | return -ENXIO; | |
292 | } | |
293 | ||
294 | en.u64 = cvmx_read_csr(CVMX_LMCX_MEM_CFG0(mc)); | |
295 | en.s.intr_ded_ena = 0; /* We poll */ | |
296 | en.s.intr_sec_ena = 0; | |
297 | cvmx_write_csr(CVMX_LMCX_MEM_CFG0(mc), en.u64); | |
298 | } | |
299 | platform_set_drvdata(pdev, mci); | |
f65aad41 RB |
300 | |
301 | return 0; | |
f65aad41 RB |
302 | } |
303 | ||
e1ced097 | 304 | static int octeon_lmc_edac_remove(struct platform_device *pdev) |
f65aad41 RB |
305 | { |
306 | struct mem_ctl_info *mci = platform_get_drvdata(pdev); | |
307 | ||
f65aad41 RB |
308 | edac_mc_del_mc(&pdev->dev); |
309 | edac_mc_free(mci); | |
f65aad41 RB |
310 | return 0; |
311 | } | |
312 | ||
e1ced097 DD |
313 | static struct platform_driver octeon_lmc_edac_driver = { |
314 | .probe = octeon_lmc_edac_probe, | |
315 | .remove = octeon_lmc_edac_remove, | |
f65aad41 | 316 | .driver = { |
e1ced097 | 317 | .name = "octeon_lmc_edac", |
f65aad41 RB |
318 | } |
319 | }; | |
e1ced097 | 320 | module_platform_driver(octeon_lmc_edac_driver); |
f65aad41 RB |
321 | |
322 | MODULE_LICENSE("GPL"); | |
323 | MODULE_AUTHOR("Ralf Baechle <ralf@linux-mips.org>"); |