Commit | Line | Data |
---|---|---|
9b7e6242 SS |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright 2018, 2019 Cisco Systems | |
4 | */ | |
5 | ||
6 | #include <linux/edac.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/init.h> | |
9 | #include <linux/interrupt.h> | |
10 | #include <linux/platform_device.h> | |
11 | #include <linux/stop_machine.h> | |
12 | #include <linux/io.h> | |
13 | #include <linux/of_address.h> | |
14 | #include <linux/regmap.h> | |
15 | #include "edac_module.h" | |
16 | ||
17 | ||
18 | #define DRV_NAME "aspeed-edac" | |
19 | ||
20 | ||
21 | #define ASPEED_MCR_PROT 0x00 /* protection key register */ | |
22 | #define ASPEED_MCR_CONF 0x04 /* configuration register */ | |
23 | #define ASPEED_MCR_INTR_CTRL 0x50 /* interrupt control/status register */ | |
24 | #define ASPEED_MCR_ADDR_UNREC 0x58 /* address of first un-recoverable error */ | |
25 | #define ASPEED_MCR_ADDR_REC 0x5c /* address of last recoverable error */ | |
26 | #define ASPEED_MCR_LAST ASPEED_MCR_ADDR_REC | |
27 | ||
28 | ||
29 | #define ASPEED_MCR_PROT_PASSWD 0xfc600309 | |
30 | #define ASPEED_MCR_CONF_DRAM_TYPE BIT(4) | |
31 | #define ASPEED_MCR_CONF_ECC BIT(7) | |
32 | #define ASPEED_MCR_INTR_CTRL_CLEAR BIT(31) | |
33 | #define ASPEED_MCR_INTR_CTRL_CNT_REC GENMASK(23, 16) | |
34 | #define ASPEED_MCR_INTR_CTRL_CNT_UNREC GENMASK(15, 12) | |
35 | #define ASPEED_MCR_INTR_CTRL_ENABLE (BIT(0) | BIT(1)) | |
36 | ||
37 | ||
38 | static struct regmap *aspeed_regmap; | |
39 | ||
40 | ||
41 | static int regmap_reg_write(void *context, unsigned int reg, unsigned int val) | |
42 | { | |
43 | void __iomem *regs = (void __iomem *)context; | |
44 | ||
45 | /* enable write to MCR register set */ | |
46 | writel(ASPEED_MCR_PROT_PASSWD, regs + ASPEED_MCR_PROT); | |
47 | ||
48 | writel(val, regs + reg); | |
49 | ||
50 | /* disable write to MCR register set */ | |
51 | writel(~ASPEED_MCR_PROT_PASSWD, regs + ASPEED_MCR_PROT); | |
52 | ||
53 | return 0; | |
54 | } | |
55 | ||
56 | ||
57 | static int regmap_reg_read(void *context, unsigned int reg, unsigned int *val) | |
58 | { | |
59 | void __iomem *regs = (void __iomem *)context; | |
60 | ||
61 | *val = readl(regs + reg); | |
62 | ||
63 | return 0; | |
64 | } | |
65 | ||
66 | static bool regmap_is_volatile(struct device *dev, unsigned int reg) | |
67 | { | |
68 | switch (reg) { | |
69 | case ASPEED_MCR_PROT: | |
70 | case ASPEED_MCR_INTR_CTRL: | |
71 | case ASPEED_MCR_ADDR_UNREC: | |
72 | case ASPEED_MCR_ADDR_REC: | |
73 | return true; | |
74 | default: | |
75 | return false; | |
76 | } | |
77 | } | |
78 | ||
79 | ||
80 | static const struct regmap_config aspeed_regmap_config = { | |
81 | .reg_bits = 32, | |
82 | .val_bits = 32, | |
83 | .reg_stride = 4, | |
84 | .max_register = ASPEED_MCR_LAST, | |
85 | .reg_write = regmap_reg_write, | |
86 | .reg_read = regmap_reg_read, | |
87 | .volatile_reg = regmap_is_volatile, | |
88 | .fast_io = true, | |
89 | }; | |
90 | ||
91 | ||
92 | static void count_rec(struct mem_ctl_info *mci, u8 rec_cnt, u32 rec_addr) | |
93 | { | |
94 | struct csrow_info *csrow = mci->csrows[0]; | |
95 | u32 page, offset, syndrome; | |
96 | ||
97 | if (!rec_cnt) | |
98 | return; | |
99 | ||
100 | /* report first few errors (if there are) */ | |
101 | /* note: no addresses are recorded */ | |
102 | if (rec_cnt > 1) { | |
103 | /* page, offset and syndrome are not available */ | |
104 | page = 0; | |
105 | offset = 0; | |
106 | syndrome = 0; | |
107 | edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, rec_cnt-1, | |
108 | page, offset, syndrome, 0, 0, -1, | |
109 | "address(es) not available", ""); | |
110 | } | |
111 | ||
112 | /* report last error */ | |
113 | /* note: rec_addr is the last recoverable error addr */ | |
114 | page = rec_addr >> PAGE_SHIFT; | |
115 | offset = rec_addr & ~PAGE_MASK; | |
116 | /* syndrome is not available */ | |
117 | syndrome = 0; | |
118 | edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, | |
119 | csrow->first_page + page, offset, syndrome, | |
120 | 0, 0, -1, "", ""); | |
121 | } | |
122 | ||
123 | ||
124 | static void count_un_rec(struct mem_ctl_info *mci, u8 un_rec_cnt, | |
125 | u32 un_rec_addr) | |
126 | { | |
127 | struct csrow_info *csrow = mci->csrows[0]; | |
128 | u32 page, offset, syndrome; | |
129 | ||
130 | if (!un_rec_cnt) | |
131 | return; | |
132 | ||
133 | /* report 1. error */ | |
134 | /* note: un_rec_addr is the first unrecoverable error addr */ | |
135 | page = un_rec_addr >> PAGE_SHIFT; | |
136 | offset = un_rec_addr & ~PAGE_MASK; | |
137 | /* syndrome is not available */ | |
138 | syndrome = 0; | |
139 | edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, | |
140 | csrow->first_page + page, offset, syndrome, | |
141 | 0, 0, -1, "", ""); | |
142 | ||
143 | /* report further errors (if there are) */ | |
144 | /* note: no addresses are recorded */ | |
145 | if (un_rec_cnt > 1) { | |
146 | /* page, offset and syndrome are not available */ | |
147 | page = 0; | |
148 | offset = 0; | |
149 | syndrome = 0; | |
150 | edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, un_rec_cnt-1, | |
151 | page, offset, syndrome, 0, 0, -1, | |
152 | "address(es) not available", ""); | |
153 | } | |
154 | } | |
155 | ||
156 | ||
157 | static irqreturn_t mcr_isr(int irq, void *arg) | |
158 | { | |
159 | struct mem_ctl_info *mci = arg; | |
160 | u32 rec_addr, un_rec_addr; | |
161 | u32 reg50, reg5c, reg58; | |
162 | u8 rec_cnt, un_rec_cnt; | |
163 | ||
164 | regmap_read(aspeed_regmap, ASPEED_MCR_INTR_CTRL, ®50); | |
165 | dev_dbg(mci->pdev, "received edac interrupt w/ mcr register 50: 0x%x\n", | |
166 | reg50); | |
167 | ||
168 | /* collect data about recoverable and unrecoverable errors */ | |
169 | rec_cnt = (reg50 & ASPEED_MCR_INTR_CTRL_CNT_REC) >> 16; | |
170 | un_rec_cnt = (reg50 & ASPEED_MCR_INTR_CTRL_CNT_UNREC) >> 12; | |
171 | ||
172 | dev_dbg(mci->pdev, "%d recoverable interrupts and %d unrecoverable interrupts\n", | |
173 | rec_cnt, un_rec_cnt); | |
174 | ||
175 | regmap_read(aspeed_regmap, ASPEED_MCR_ADDR_UNREC, ®58); | |
176 | un_rec_addr = reg58; | |
177 | ||
178 | regmap_read(aspeed_regmap, ASPEED_MCR_ADDR_REC, ®5c); | |
179 | rec_addr = reg5c; | |
180 | ||
181 | /* clear interrupt flags and error counters: */ | |
182 | regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, | |
183 | ASPEED_MCR_INTR_CTRL_CLEAR, | |
184 | ASPEED_MCR_INTR_CTRL_CLEAR); | |
185 | ||
186 | regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, | |
187 | ASPEED_MCR_INTR_CTRL_CLEAR, 0); | |
188 | ||
189 | /* process recoverable and unrecoverable errors */ | |
190 | count_rec(mci, rec_cnt, rec_addr); | |
191 | count_un_rec(mci, un_rec_cnt, un_rec_addr); | |
192 | ||
193 | if (!rec_cnt && !un_rec_cnt) | |
194 | dev_dbg(mci->pdev, "received edac interrupt, but did not find any ECC counters\n"); | |
195 | ||
196 | regmap_read(aspeed_regmap, ASPEED_MCR_INTR_CTRL, ®50); | |
197 | dev_dbg(mci->pdev, "edac interrupt handled. mcr reg 50 is now: 0x%x\n", | |
198 | reg50); | |
199 | ||
200 | return IRQ_HANDLED; | |
201 | } | |
202 | ||
203 | ||
204 | static int config_irq(void *ctx, struct platform_device *pdev) | |
205 | { | |
206 | int irq; | |
207 | int rc; | |
208 | ||
209 | /* register interrupt handler */ | |
210 | irq = platform_get_irq(pdev, 0); | |
211 | dev_dbg(&pdev->dev, "got irq %d\n", irq); | |
afce6996 KK |
212 | if (irq < 0) |
213 | return irq; | |
9b7e6242 SS |
214 | |
215 | rc = devm_request_irq(&pdev->dev, irq, mcr_isr, IRQF_TRIGGER_HIGH, | |
216 | DRV_NAME, ctx); | |
217 | if (rc) { | |
218 | dev_err(&pdev->dev, "unable to request irq %d\n", irq); | |
219 | return rc; | |
220 | } | |
221 | ||
222 | /* enable interrupts */ | |
223 | regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, | |
224 | ASPEED_MCR_INTR_CTRL_ENABLE, | |
225 | ASPEED_MCR_INTR_CTRL_ENABLE); | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | ||
231 | static int init_csrows(struct mem_ctl_info *mci) | |
232 | { | |
233 | struct csrow_info *csrow = mci->csrows[0]; | |
234 | u32 nr_pages, dram_type; | |
235 | struct dimm_info *dimm; | |
236 | struct device_node *np; | |
237 | struct resource r; | |
238 | u32 reg04; | |
239 | int rc; | |
240 | ||
241 | /* retrieve info about physical memory from device tree */ | |
edfc2d73 | 242 | np = of_find_node_by_name(NULL, "memory"); |
9b7e6242 SS |
243 | if (!np) { |
244 | dev_err(mci->pdev, "dt: missing /memory node\n"); | |
245 | return -ENODEV; | |
a651c6c6 | 246 | } |
9b7e6242 SS |
247 | |
248 | rc = of_address_to_resource(np, 0, &r); | |
249 | ||
250 | of_node_put(np); | |
251 | ||
252 | if (rc) { | |
253 | dev_err(mci->pdev, "dt: failed requesting resource for /memory node\n"); | |
254 | return rc; | |
a651c6c6 | 255 | } |
9b7e6242 | 256 | |
2e2f16d5 AB |
257 | dev_dbg(mci->pdev, "dt: /memory node resources: first page %pR, PAGE_SHIFT macro=0x%x\n", |
258 | &r, PAGE_SHIFT); | |
9b7e6242 SS |
259 | |
260 | csrow->first_page = r.start >> PAGE_SHIFT; | |
261 | nr_pages = resource_size(&r) >> PAGE_SHIFT; | |
262 | csrow->last_page = csrow->first_page + nr_pages - 1; | |
263 | ||
264 | regmap_read(aspeed_regmap, ASPEED_MCR_CONF, ®04); | |
265 | dram_type = (reg04 & ASPEED_MCR_CONF_DRAM_TYPE) ? MEM_DDR4 : MEM_DDR3; | |
266 | ||
267 | dimm = csrow->channels[0]->dimm; | |
268 | dimm->mtype = dram_type; | |
269 | dimm->edac_mode = EDAC_SECDED; | |
270 | dimm->nr_pages = nr_pages / csrow->nr_channels; | |
271 | ||
272 | dev_dbg(mci->pdev, "initialized dimm with first_page=0x%lx and nr_pages=0x%x\n", | |
273 | csrow->first_page, nr_pages); | |
274 | ||
275 | return 0; | |
276 | } | |
277 | ||
278 | ||
279 | static int aspeed_probe(struct platform_device *pdev) | |
280 | { | |
281 | struct device *dev = &pdev->dev; | |
282 | struct edac_mc_layer layers[2]; | |
283 | struct mem_ctl_info *mci; | |
9b7e6242 SS |
284 | void __iomem *regs; |
285 | u32 reg04; | |
286 | int rc; | |
287 | ||
5bbab3cf | 288 | regs = devm_platform_ioremap_resource(pdev, 0); |
9b7e6242 SS |
289 | if (IS_ERR(regs)) |
290 | return PTR_ERR(regs); | |
291 | ||
292 | aspeed_regmap = devm_regmap_init(dev, NULL, (__force void *)regs, | |
293 | &aspeed_regmap_config); | |
294 | if (IS_ERR(aspeed_regmap)) | |
295 | return PTR_ERR(aspeed_regmap); | |
296 | ||
297 | /* bail out if ECC mode is not configured */ | |
298 | regmap_read(aspeed_regmap, ASPEED_MCR_CONF, ®04); | |
299 | if (!(reg04 & ASPEED_MCR_CONF_ECC)) { | |
300 | dev_err(&pdev->dev, "ECC mode is not configured in u-boot\n"); | |
301 | return -EPERM; | |
302 | } | |
303 | ||
304 | edac_op_state = EDAC_OPSTATE_INT; | |
305 | ||
306 | /* allocate & init EDAC MC data structure */ | |
307 | layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; | |
308 | layers[0].size = 1; | |
309 | layers[0].is_virt_csrow = true; | |
310 | layers[1].type = EDAC_MC_LAYER_CHANNEL; | |
311 | layers[1].size = 1; | |
312 | layers[1].is_virt_csrow = false; | |
313 | ||
314 | mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, 0); | |
315 | if (!mci) | |
316 | return -ENOMEM; | |
317 | ||
318 | mci->pdev = &pdev->dev; | |
319 | mci->mtype_cap = MEM_FLAG_DDR3 | MEM_FLAG_DDR4; | |
320 | mci->edac_ctl_cap = EDAC_FLAG_SECDED; | |
321 | mci->edac_cap = EDAC_FLAG_SECDED; | |
322 | mci->scrub_cap = SCRUB_FLAG_HW_SRC; | |
323 | mci->scrub_mode = SCRUB_HW_SRC; | |
324 | mci->mod_name = DRV_NAME; | |
325 | mci->ctl_name = "MIC"; | |
326 | mci->dev_name = dev_name(&pdev->dev); | |
327 | ||
328 | rc = init_csrows(mci); | |
329 | if (rc) { | |
330 | dev_err(&pdev->dev, "failed to init csrows\n"); | |
331 | goto probe_exit02; | |
332 | } | |
333 | ||
334 | platform_set_drvdata(pdev, mci); | |
335 | ||
336 | /* register with edac core */ | |
337 | rc = edac_mc_add_mc(mci); | |
338 | if (rc) { | |
339 | dev_err(&pdev->dev, "failed to register with EDAC core\n"); | |
340 | goto probe_exit02; | |
341 | } | |
342 | ||
343 | /* register interrupt handler and enable interrupts */ | |
344 | rc = config_irq(mci, pdev); | |
345 | if (rc) { | |
346 | dev_err(&pdev->dev, "failed setting up irq\n"); | |
347 | goto probe_exit01; | |
348 | } | |
349 | ||
350 | return 0; | |
351 | ||
352 | probe_exit01: | |
353 | edac_mc_del_mc(&pdev->dev); | |
354 | probe_exit02: | |
355 | edac_mc_free(mci); | |
356 | return rc; | |
357 | } | |
358 | ||
359 | ||
360 | static int aspeed_remove(struct platform_device *pdev) | |
361 | { | |
362 | struct mem_ctl_info *mci; | |
363 | ||
364 | /* disable interrupts */ | |
365 | regmap_update_bits(aspeed_regmap, ASPEED_MCR_INTR_CTRL, | |
366 | ASPEED_MCR_INTR_CTRL_ENABLE, 0); | |
367 | ||
368 | /* free resources */ | |
369 | mci = edac_mc_del_mc(&pdev->dev); | |
370 | if (mci) | |
371 | edac_mc_free(mci); | |
372 | ||
373 | return 0; | |
374 | } | |
375 | ||
376 | ||
377 | static const struct of_device_id aspeed_of_match[] = { | |
edfc2d73 | 378 | { .compatible = "aspeed,ast2400-sdram-edac" }, |
9b7e6242 | 379 | { .compatible = "aspeed,ast2500-sdram-edac" }, |
edfc2d73 | 380 | { .compatible = "aspeed,ast2600-sdram-edac" }, |
9b7e6242 SS |
381 | {}, |
382 | }; | |
383 | ||
edfc2d73 | 384 | MODULE_DEVICE_TABLE(of, aspeed_of_match); |
9b7e6242 SS |
385 | |
386 | static struct platform_driver aspeed_driver = { | |
387 | .driver = { | |
388 | .name = DRV_NAME, | |
389 | .of_match_table = aspeed_of_match | |
390 | }, | |
391 | .probe = aspeed_probe, | |
392 | .remove = aspeed_remove | |
393 | }; | |
07def587 | 394 | module_platform_driver(aspeed_driver); |
9b7e6242 SS |
395 | |
396 | MODULE_LICENSE("GPL"); | |
397 | MODULE_AUTHOR("Stefan Schaeckeler <sschaeck@cisco.com>"); | |
edfc2d73 | 398 | MODULE_DESCRIPTION("Aspeed BMC SoC EDAC driver"); |
9b7e6242 | 399 | MODULE_VERSION("1.0"); |