1
2
3
4
5
6
7
8#define DRIVER_NAME "omap-elm"
9
10#include <linux/platform_device.h>
11#include <linux/module.h>
12#include <linux/interrupt.h>
13#include <linux/io.h>
14#include <linux/of.h>
15#include <linux/sched.h>
16#include <linux/pm_runtime.h>
17#include <linux/platform_data/elm.h>
18
19#define ELM_SYSCONFIG 0x010
20#define ELM_IRQSTATUS 0x018
21#define ELM_IRQENABLE 0x01c
22#define ELM_LOCATION_CONFIG 0x020
23#define ELM_PAGE_CTRL 0x080
24#define ELM_SYNDROME_FRAGMENT_0 0x400
25#define ELM_SYNDROME_FRAGMENT_1 0x404
26#define ELM_SYNDROME_FRAGMENT_2 0x408
27#define ELM_SYNDROME_FRAGMENT_3 0x40c
28#define ELM_SYNDROME_FRAGMENT_4 0x410
29#define ELM_SYNDROME_FRAGMENT_5 0x414
30#define ELM_SYNDROME_FRAGMENT_6 0x418
31#define ELM_LOCATION_STATUS 0x800
32#define ELM_ERROR_LOCATION_0 0x880
33
34
35#define INTR_STATUS_PAGE_VALID BIT(8)
36
37
38#define INTR_EN_PAGE_MASK BIT(8)
39
40
41#define ECC_BCH_LEVEL_MASK 0x3
42
43
44#define ELM_SYNDROME_VALID BIT(16)
45
46
47#define ECC_CORRECTABLE_MASK BIT(8)
48#define ECC_NB_ERRORS_MASK 0x1f
49
50
51#define ECC_ERROR_LOCATION_MASK 0x1fff
52
53#define ELM_ECC_SIZE 0x7ff
54
55#define SYNDROME_FRAGMENT_REG_SIZE 0x40
56#define ERROR_LOCATION_SIZE 0x100
57
58struct elm_registers {
59 u32 elm_irqenable;
60 u32 elm_sysconfig;
61 u32 elm_location_config;
62 u32 elm_page_ctrl;
63 u32 elm_syndrome_fragment_6[ERROR_VECTOR_MAX];
64 u32 elm_syndrome_fragment_5[ERROR_VECTOR_MAX];
65 u32 elm_syndrome_fragment_4[ERROR_VECTOR_MAX];
66 u32 elm_syndrome_fragment_3[ERROR_VECTOR_MAX];
67 u32 elm_syndrome_fragment_2[ERROR_VECTOR_MAX];
68 u32 elm_syndrome_fragment_1[ERROR_VECTOR_MAX];
69 u32 elm_syndrome_fragment_0[ERROR_VECTOR_MAX];
70};
71
72struct elm_info {
73 struct device *dev;
74 void __iomem *elm_base;
75 struct completion elm_completion;
76 struct list_head list;
77 enum bch_ecc bch_type;
78 struct elm_registers elm_regs;
79 int ecc_steps;
80 int ecc_syndrome_size;
81};
82
83static LIST_HEAD(elm_devices);
84
85static void elm_write_reg(struct elm_info *info, int offset, u32 val)
86{
87 writel(val, info->elm_base + offset);
88}
89
90static u32 elm_read_reg(struct elm_info *info, int offset)
91{
92 return readl(info->elm_base + offset);
93}
94
95
96
97
98
99
100
101
102
103int elm_config(struct device *dev, enum bch_ecc bch_type,
104 int ecc_steps, int ecc_step_size, int ecc_syndrome_size)
105{
106 u32 reg_val;
107 struct elm_info *info = dev_get_drvdata(dev);
108
109 if (!info) {
110 dev_err(dev, "Unable to configure elm - device not probed?\n");
111 return -EPROBE_DEFER;
112 }
113
114 if (ecc_step_size > ((ELM_ECC_SIZE + 1) / 2)) {
115 dev_err(dev, "unsupported config ecc-size=%d\n", ecc_step_size);
116 return -EINVAL;
117 }
118
119 if (ecc_steps > ERROR_VECTOR_MAX && ecc_steps % ERROR_VECTOR_MAX) {
120 dev_err(dev, "unsupported config ecc-step=%d\n", ecc_steps);
121 return -EINVAL;
122 }
123
124 reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16);
125 elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val);
126 info->bch_type = bch_type;
127 info->ecc_steps = ecc_steps;
128 info->ecc_syndrome_size = ecc_syndrome_size;
129
130 return 0;
131}
132EXPORT_SYMBOL(elm_config);
133
134
135
136
137
138
139
140
141
142static void elm_configure_page_mode(struct elm_info *info, int index,
143 bool enable)
144{
145 u32 reg_val;
146
147 reg_val = elm_read_reg(info, ELM_PAGE_CTRL);
148 if (enable)
149 reg_val |= BIT(index);
150 else
151 reg_val &= ~BIT(index);
152
153 elm_write_reg(info, ELM_PAGE_CTRL, reg_val);
154}
155
156
157
158
159
160
161
162
163
164static void elm_load_syndrome(struct elm_info *info,
165 struct elm_errorvec *err_vec, u8 *ecc)
166{
167 int i, offset;
168 u32 val;
169
170 for (i = 0; i < info->ecc_steps; i++) {
171
172
173 if (err_vec[i].error_reported) {
174 elm_configure_page_mode(info, i, true);
175 offset = ELM_SYNDROME_FRAGMENT_0 +
176 SYNDROME_FRAGMENT_REG_SIZE * i;
177 switch (info->bch_type) {
178 case BCH8_ECC:
179
180 val = cpu_to_be32(*(u32 *) &ecc[9]);
181 elm_write_reg(info, offset, val);
182
183
184 offset += 4;
185 val = cpu_to_be32(*(u32 *) &ecc[5]);
186 elm_write_reg(info, offset, val);
187
188
189 offset += 4;
190 val = cpu_to_be32(*(u32 *) &ecc[1]);
191 elm_write_reg(info, offset, val);
192
193
194 offset += 4;
195 val = ecc[0];
196 elm_write_reg(info, offset, val);
197 break;
198 case BCH4_ECC:
199
200 val = (cpu_to_be32(*(u32 *) &ecc[3]) >> 4) |
201 ((ecc[2] & 0xf) << 28);
202 elm_write_reg(info, offset, val);
203
204
205 offset += 4;
206 val = cpu_to_be32(*(u32 *) &ecc[0]) >> 12;
207 elm_write_reg(info, offset, val);
208 break;
209 case BCH16_ECC:
210 val = cpu_to_be32(*(u32 *) &ecc[22]);
211 elm_write_reg(info, offset, val);
212 offset += 4;
213 val = cpu_to_be32(*(u32 *) &ecc[18]);
214 elm_write_reg(info, offset, val);
215 offset += 4;
216 val = cpu_to_be32(*(u32 *) &ecc[14]);
217 elm_write_reg(info, offset, val);
218 offset += 4;
219 val = cpu_to_be32(*(u32 *) &ecc[10]);
220 elm_write_reg(info, offset, val);
221 offset += 4;
222 val = cpu_to_be32(*(u32 *) &ecc[6]);
223 elm_write_reg(info, offset, val);
224 offset += 4;
225 val = cpu_to_be32(*(u32 *) &ecc[2]);
226 elm_write_reg(info, offset, val);
227 offset += 4;
228 val = cpu_to_be32(*(u32 *) &ecc[0]) >> 16;
229 elm_write_reg(info, offset, val);
230 break;
231 default:
232 pr_err("invalid config bch_type\n");
233 }
234 }
235
236
237 ecc += info->ecc_syndrome_size;
238 }
239}
240
241
242
243
244
245
246
247
248
249
250static void elm_start_processing(struct elm_info *info,
251 struct elm_errorvec *err_vec)
252{
253 int i, offset;
254 u32 reg_val;
255
256
257
258
259
260 for (i = 0; i < info->ecc_steps; i++) {
261 if (err_vec[i].error_reported) {
262 offset = ELM_SYNDROME_FRAGMENT_6 +
263 SYNDROME_FRAGMENT_REG_SIZE * i;
264 reg_val = elm_read_reg(info, offset);
265 reg_val |= ELM_SYNDROME_VALID;
266 elm_write_reg(info, offset, reg_val);
267 }
268 }
269}
270
271
272
273
274
275
276
277
278
279
280
281
282static void elm_error_correction(struct elm_info *info,
283 struct elm_errorvec *err_vec)
284{
285 int i, j, errors = 0;
286 int offset;
287 u32 reg_val;
288
289 for (i = 0; i < info->ecc_steps; i++) {
290
291
292 if (err_vec[i].error_reported) {
293 offset = ELM_LOCATION_STATUS + ERROR_LOCATION_SIZE * i;
294 reg_val = elm_read_reg(info, offset);
295
296
297 if (reg_val & ECC_CORRECTABLE_MASK) {
298 offset = ELM_ERROR_LOCATION_0 +
299 ERROR_LOCATION_SIZE * i;
300
301
302 err_vec[i].error_count = reg_val &
303 ECC_NB_ERRORS_MASK;
304
305
306 for (j = 0; j < err_vec[i].error_count; j++) {
307
308 reg_val = elm_read_reg(info, offset);
309 err_vec[i].error_loc[j] = reg_val &
310 ECC_ERROR_LOCATION_MASK;
311
312
313 offset += 4;
314 }
315
316 errors += err_vec[i].error_count;
317 } else {
318 err_vec[i].error_uncorrectable = true;
319 }
320
321
322 elm_write_reg(info, ELM_IRQSTATUS, BIT(i));
323
324
325 elm_configure_page_mode(info, i, false);
326 }
327 }
328}
329
330
331
332
333
334
335
336
337
338
339void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc,
340 struct elm_errorvec *err_vec)
341{
342 struct elm_info *info = dev_get_drvdata(dev);
343 u32 reg_val;
344
345
346 reg_val = elm_read_reg(info, ELM_IRQSTATUS);
347 elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID);
348 elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK);
349
350
351 elm_load_syndrome(info, err_vec, ecc_calc);
352
353
354 elm_start_processing(info, err_vec);
355
356
357 wait_for_completion(&info->elm_completion);
358
359
360 reg_val = elm_read_reg(info, ELM_IRQENABLE);
361 elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK);
362 elm_error_correction(info, err_vec);
363}
364EXPORT_SYMBOL(elm_decode_bch_error_page);
365
366static irqreturn_t elm_isr(int this_irq, void *dev_id)
367{
368 u32 reg_val;
369 struct elm_info *info = dev_id;
370
371 reg_val = elm_read_reg(info, ELM_IRQSTATUS);
372
373
374 if (reg_val & INTR_STATUS_PAGE_VALID) {
375 elm_write_reg(info, ELM_IRQSTATUS,
376 reg_val & INTR_STATUS_PAGE_VALID);
377 complete(&info->elm_completion);
378 return IRQ_HANDLED;
379 }
380
381 return IRQ_NONE;
382}
383
384static int elm_probe(struct platform_device *pdev)
385{
386 int ret = 0;
387 struct resource *res, *irq;
388 struct elm_info *info;
389
390 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
391 if (!info)
392 return -ENOMEM;
393
394 info->dev = &pdev->dev;
395
396 irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
397 if (!irq) {
398 dev_err(&pdev->dev, "no irq resource defined\n");
399 return -ENODEV;
400 }
401
402 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
403 info->elm_base = devm_ioremap_resource(&pdev->dev, res);
404 if (IS_ERR(info->elm_base))
405 return PTR_ERR(info->elm_base);
406
407 ret = devm_request_irq(&pdev->dev, irq->start, elm_isr, 0,
408 pdev->name, info);
409 if (ret) {
410 dev_err(&pdev->dev, "failure requesting %pr\n", irq);
411 return ret;
412 }
413
414 pm_runtime_enable(&pdev->dev);
415 if (pm_runtime_get_sync(&pdev->dev) < 0) {
416 ret = -EINVAL;
417 pm_runtime_put_sync(&pdev->dev);
418 pm_runtime_disable(&pdev->dev);
419 dev_err(&pdev->dev, "can't enable clock\n");
420 return ret;
421 }
422
423 init_completion(&info->elm_completion);
424 INIT_LIST_HEAD(&info->list);
425 list_add(&info->list, &elm_devices);
426 platform_set_drvdata(pdev, info);
427 return ret;
428}
429
430static int elm_remove(struct platform_device *pdev)
431{
432 pm_runtime_put_sync(&pdev->dev);
433 pm_runtime_disable(&pdev->dev);
434 return 0;
435}
436
437#ifdef CONFIG_PM_SLEEP
438
439
440
441
442static int elm_context_save(struct elm_info *info)
443{
444 struct elm_registers *regs = &info->elm_regs;
445 enum bch_ecc bch_type = info->bch_type;
446 u32 offset = 0, i;
447
448 regs->elm_irqenable = elm_read_reg(info, ELM_IRQENABLE);
449 regs->elm_sysconfig = elm_read_reg(info, ELM_SYSCONFIG);
450 regs->elm_location_config = elm_read_reg(info, ELM_LOCATION_CONFIG);
451 regs->elm_page_ctrl = elm_read_reg(info, ELM_PAGE_CTRL);
452 for (i = 0; i < ERROR_VECTOR_MAX; i++) {
453 offset = i * SYNDROME_FRAGMENT_REG_SIZE;
454 switch (bch_type) {
455 case BCH16_ECC:
456 regs->elm_syndrome_fragment_6[i] = elm_read_reg(info,
457 ELM_SYNDROME_FRAGMENT_6 + offset);
458 regs->elm_syndrome_fragment_5[i] = elm_read_reg(info,
459 ELM_SYNDROME_FRAGMENT_5 + offset);
460 regs->elm_syndrome_fragment_4[i] = elm_read_reg(info,
461 ELM_SYNDROME_FRAGMENT_4 + offset);
462 fallthrough;
463 case BCH8_ECC:
464 regs->elm_syndrome_fragment_3[i] = elm_read_reg(info,
465 ELM_SYNDROME_FRAGMENT_3 + offset);
466 regs->elm_syndrome_fragment_2[i] = elm_read_reg(info,
467 ELM_SYNDROME_FRAGMENT_2 + offset);
468 fallthrough;
469 case BCH4_ECC:
470 regs->elm_syndrome_fragment_1[i] = elm_read_reg(info,
471 ELM_SYNDROME_FRAGMENT_1 + offset);
472 regs->elm_syndrome_fragment_0[i] = elm_read_reg(info,
473 ELM_SYNDROME_FRAGMENT_0 + offset);
474 break;
475 default:
476 return -EINVAL;
477 }
478
479
480 regs->elm_syndrome_fragment_6[i] = elm_read_reg(info,
481 ELM_SYNDROME_FRAGMENT_6 + offset);
482 }
483 return 0;
484}
485
486
487
488
489
490static int elm_context_restore(struct elm_info *info)
491{
492 struct elm_registers *regs = &info->elm_regs;
493 enum bch_ecc bch_type = info->bch_type;
494 u32 offset = 0, i;
495
496 elm_write_reg(info, ELM_IRQENABLE, regs->elm_irqenable);
497 elm_write_reg(info, ELM_SYSCONFIG, regs->elm_sysconfig);
498 elm_write_reg(info, ELM_LOCATION_CONFIG, regs->elm_location_config);
499 elm_write_reg(info, ELM_PAGE_CTRL, regs->elm_page_ctrl);
500 for (i = 0; i < ERROR_VECTOR_MAX; i++) {
501 offset = i * SYNDROME_FRAGMENT_REG_SIZE;
502 switch (bch_type) {
503 case BCH16_ECC:
504 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset,
505 regs->elm_syndrome_fragment_6[i]);
506 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_5 + offset,
507 regs->elm_syndrome_fragment_5[i]);
508 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_4 + offset,
509 regs->elm_syndrome_fragment_4[i]);
510 fallthrough;
511 case BCH8_ECC:
512 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_3 + offset,
513 regs->elm_syndrome_fragment_3[i]);
514 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_2 + offset,
515 regs->elm_syndrome_fragment_2[i]);
516 fallthrough;
517 case BCH4_ECC:
518 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_1 + offset,
519 regs->elm_syndrome_fragment_1[i]);
520 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_0 + offset,
521 regs->elm_syndrome_fragment_0[i]);
522 break;
523 default:
524 return -EINVAL;
525 }
526
527 elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset,
528 regs->elm_syndrome_fragment_6[i] &
529 ELM_SYNDROME_VALID);
530 }
531 return 0;
532}
533
534static int elm_suspend(struct device *dev)
535{
536 struct elm_info *info = dev_get_drvdata(dev);
537 elm_context_save(info);
538 pm_runtime_put_sync(dev);
539 return 0;
540}
541
542static int elm_resume(struct device *dev)
543{
544 struct elm_info *info = dev_get_drvdata(dev);
545 pm_runtime_get_sync(dev);
546 elm_context_restore(info);
547 return 0;
548}
549#endif
550
551static SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume);
552
553#ifdef CONFIG_OF
554static const struct of_device_id elm_of_match[] = {
555 { .compatible = "ti,am3352-elm" },
556 {},
557};
558MODULE_DEVICE_TABLE(of, elm_of_match);
559#endif
560
561static struct platform_driver elm_driver = {
562 .driver = {
563 .name = DRIVER_NAME,
564 .of_match_table = of_match_ptr(elm_of_match),
565 .pm = &elm_pm_ops,
566 },
567 .probe = elm_probe,
568 .remove = elm_remove,
569};
570
571module_platform_driver(elm_driver);
572
573MODULE_DESCRIPTION("ELM driver for BCH error correction");
574MODULE_AUTHOR("Texas Instruments");
575MODULE_ALIAS("platform:" DRIVER_NAME);
576MODULE_LICENSE("GPL v2");
577