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