diff options
author | Philip Avinash <avinashphilip@ti.com> | 2013-01-04 02:56:50 -0500 |
---|---|---|
committer | Artem Bityutskiy <artem.bityutskiy@linux.intel.com> | 2013-02-04 02:26:29 -0500 |
commit | bf22433575ef30a4807f0620498017df0f27df67 (patch) | |
tree | 6d628e0b5af68f733aea230e35cfee44b714bf63 /drivers/mtd | |
parent | c3e4b995e47e8f72297779852907f6d3ecd75139 (diff) |
mtd: devices: elm: Add support for ELM error correction
The ELM hardware module can be used to speedup BCH 4/8/16 ECC scheme
error correction.
For now only 4 & 8 bit support is added
Signed-off-by: Philip Avinash <avinashphilip@ti.com>
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Diffstat (limited to 'drivers/mtd')
-rw-r--r-- | drivers/mtd/devices/Makefile | 4 | ||||
-rw-r--r-- | drivers/mtd/devices/elm.c | 404 |
2 files changed, 407 insertions, 1 deletions
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 395733a30ef4..369a1943ca25 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile | |||
@@ -17,8 +17,10 @@ obj-$(CONFIG_MTD_LART) += lart.o | |||
17 | obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o | 17 | obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o |
18 | obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o | 18 | obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o |
19 | obj-$(CONFIG_MTD_M25P80) += m25p80.o | 19 | obj-$(CONFIG_MTD_M25P80) += m25p80.o |
20 | obj-$(CONFIG_MTD_NAND_OMAP_BCH) += elm.o | ||
20 | obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o | 21 | obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o |
21 | obj-$(CONFIG_MTD_SST25L) += sst25l.o | 22 | obj-$(CONFIG_MTD_SST25L) += sst25l.o |
22 | obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o | 23 | obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o |
23 | 24 | ||
24 | CFLAGS_docg3.o += -I$(src) \ No newline at end of file | 25 | |
26 | CFLAGS_docg3.o += -I$(src) | ||
diff --git a/drivers/mtd/devices/elm.c b/drivers/mtd/devices/elm.c new file mode 100644 index 000000000000..b93a349ae587 --- /dev/null +++ b/drivers/mtd/devices/elm.c | |||
@@ -0,0 +1,404 @@ | |||
1 | /* | ||
2 | * Error Location Module | ||
3 | * | ||
4 | * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/platform_device.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/interrupt.h> | ||
21 | #include <linux/io.h> | ||
22 | #include <linux/of.h> | ||
23 | #include <linux/pm_runtime.h> | ||
24 | #include <linux/platform_data/elm.h> | ||
25 | |||
26 | #define ELM_IRQSTATUS 0x018 | ||
27 | #define ELM_IRQENABLE 0x01c | ||
28 | #define ELM_LOCATION_CONFIG 0x020 | ||
29 | #define ELM_PAGE_CTRL 0x080 | ||
30 | #define ELM_SYNDROME_FRAGMENT_0 0x400 | ||
31 | #define ELM_SYNDROME_FRAGMENT_6 0x418 | ||
32 | #define ELM_LOCATION_STATUS 0x800 | ||
33 | #define ELM_ERROR_LOCATION_0 0x880 | ||
34 | |||
35 | /* ELM Interrupt Status Register */ | ||
36 | #define INTR_STATUS_PAGE_VALID BIT(8) | ||
37 | |||
38 | /* ELM Interrupt Enable Register */ | ||
39 | #define INTR_EN_PAGE_MASK BIT(8) | ||
40 | |||
41 | /* ELM Location Configuration Register */ | ||
42 | #define ECC_BCH_LEVEL_MASK 0x3 | ||
43 | |||
44 | /* ELM syndrome */ | ||
45 | #define ELM_SYNDROME_VALID BIT(16) | ||
46 | |||
47 | /* ELM_LOCATION_STATUS Register */ | ||
48 | #define ECC_CORRECTABLE_MASK BIT(8) | ||
49 | #define ECC_NB_ERRORS_MASK 0x1f | ||
50 | |||
51 | /* ELM_ERROR_LOCATION_0-15 Registers */ | ||
52 | #define ECC_ERROR_LOCATION_MASK 0x1fff | ||
53 | |||
54 | #define ELM_ECC_SIZE 0x7ff | ||
55 | |||
56 | #define SYNDROME_FRAGMENT_REG_SIZE 0x40 | ||
57 | #define ERROR_LOCATION_SIZE 0x100 | ||
58 | |||
59 | struct elm_info { | ||
60 | struct device *dev; | ||
61 | void __iomem *elm_base; | ||
62 | struct completion elm_completion; | ||
63 | struct list_head list; | ||
64 | enum bch_ecc bch_type; | ||
65 | }; | ||
66 | |||
67 | static LIST_HEAD(elm_devices); | ||
68 | |||
69 | static void elm_write_reg(struct elm_info *info, int offset, u32 val) | ||
70 | { | ||
71 | writel(val, info->elm_base + offset); | ||
72 | } | ||
73 | |||
74 | static u32 elm_read_reg(struct elm_info *info, int offset) | ||
75 | { | ||
76 | return readl(info->elm_base + offset); | ||
77 | } | ||
78 | |||
79 | /** | ||
80 | * elm_config - Configure ELM module | ||
81 | * @dev: ELM device | ||
82 | * @bch_type: Type of BCH ecc | ||
83 | */ | ||
84 | void elm_config(struct device *dev, enum bch_ecc bch_type) | ||
85 | { | ||
86 | u32 reg_val; | ||
87 | struct elm_info *info = dev_get_drvdata(dev); | ||
88 | |||
89 | reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16); | ||
90 | elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val); | ||
91 | info->bch_type = bch_type; | ||
92 | } | ||
93 | EXPORT_SYMBOL(elm_config); | ||
94 | |||
95 | /** | ||
96 | * elm_configure_page_mode - Enable/Disable page mode | ||
97 | * @info: elm info | ||
98 | * @index: index number of syndrome fragment vector | ||
99 | * @enable: enable/disable flag for page mode | ||
100 | * | ||
101 | * Enable page mode for syndrome fragment index | ||
102 | */ | ||
103 | static void elm_configure_page_mode(struct elm_info *info, int index, | ||
104 | bool enable) | ||
105 | { | ||
106 | u32 reg_val; | ||
107 | |||
108 | reg_val = elm_read_reg(info, ELM_PAGE_CTRL); | ||
109 | if (enable) | ||
110 | reg_val |= BIT(index); /* enable page mode */ | ||
111 | else | ||
112 | reg_val &= ~BIT(index); /* disable page mode */ | ||
113 | |||
114 | elm_write_reg(info, ELM_PAGE_CTRL, reg_val); | ||
115 | } | ||
116 | |||
117 | /** | ||
118 | * elm_load_syndrome - Load ELM syndrome reg | ||
119 | * @info: elm info | ||
120 | * @err_vec: elm error vectors | ||
121 | * @ecc: buffer with calculated ecc | ||
122 | * | ||
123 | * Load syndrome fragment registers with calculated ecc in reverse order. | ||
124 | */ | ||
125 | static void elm_load_syndrome(struct elm_info *info, | ||
126 | struct elm_errorvec *err_vec, u8 *ecc) | ||
127 | { | ||
128 | int i, offset; | ||
129 | u32 val; | ||
130 | |||
131 | for (i = 0; i < ERROR_VECTOR_MAX; i++) { | ||
132 | |||
133 | /* Check error reported */ | ||
134 | if (err_vec[i].error_reported) { | ||
135 | elm_configure_page_mode(info, i, true); | ||
136 | offset = ELM_SYNDROME_FRAGMENT_0 + | ||
137 | SYNDROME_FRAGMENT_REG_SIZE * i; | ||
138 | |||
139 | /* BCH8 */ | ||
140 | if (info->bch_type) { | ||
141 | |||
142 | /* syndrome fragment 0 = ecc[9-12B] */ | ||
143 | val = cpu_to_be32(*(u32 *) &ecc[9]); | ||
144 | elm_write_reg(info, offset, val); | ||
145 | |||
146 | /* syndrome fragment 1 = ecc[5-8B] */ | ||
147 | offset += 4; | ||
148 | val = cpu_to_be32(*(u32 *) &ecc[5]); | ||
149 | elm_write_reg(info, offset, val); | ||
150 | |||
151 | /* syndrome fragment 2 = ecc[1-4B] */ | ||
152 | offset += 4; | ||
153 | val = cpu_to_be32(*(u32 *) &ecc[1]); | ||
154 | elm_write_reg(info, offset, val); | ||
155 | |||
156 | /* syndrome fragment 3 = ecc[0B] */ | ||
157 | offset += 4; | ||
158 | val = ecc[0]; | ||
159 | elm_write_reg(info, offset, val); | ||
160 | } else { | ||
161 | /* syndrome fragment 0 = ecc[20-52b] bits */ | ||
162 | val = (cpu_to_be32(*(u32 *) &ecc[3]) >> 4) | | ||
163 | ((ecc[2] & 0xf) << 28); | ||
164 | elm_write_reg(info, offset, val); | ||
165 | |||
166 | /* syndrome fragment 1 = ecc[0-20b] bits */ | ||
167 | offset += 4; | ||
168 | val = cpu_to_be32(*(u32 *) &ecc[0]) >> 12; | ||
169 | elm_write_reg(info, offset, val); | ||
170 | } | ||
171 | } | ||
172 | |||
173 | /* Update ecc pointer with ecc byte size */ | ||
174 | ecc += info->bch_type ? BCH8_SIZE : BCH4_SIZE; | ||
175 | } | ||
176 | } | ||
177 | |||
178 | /** | ||
179 | * elm_start_processing - start elm syndrome processing | ||
180 | * @info: elm info | ||
181 | * @err_vec: elm error vectors | ||
182 | * | ||
183 | * Set syndrome valid bit for syndrome fragment registers for which | ||
184 | * elm syndrome fragment registers are loaded. This enables elm module | ||
185 | * to start processing syndrome vectors. | ||
186 | */ | ||
187 | static void elm_start_processing(struct elm_info *info, | ||
188 | struct elm_errorvec *err_vec) | ||
189 | { | ||
190 | int i, offset; | ||
191 | u32 reg_val; | ||
192 | |||
193 | /* | ||
194 | * Set syndrome vector valid, so that ELM module | ||
195 | * will process it for vectors error is reported | ||
196 | */ | ||
197 | for (i = 0; i < ERROR_VECTOR_MAX; i++) { | ||
198 | if (err_vec[i].error_reported) { | ||
199 | offset = ELM_SYNDROME_FRAGMENT_6 + | ||
200 | SYNDROME_FRAGMENT_REG_SIZE * i; | ||
201 | reg_val = elm_read_reg(info, offset); | ||
202 | reg_val |= ELM_SYNDROME_VALID; | ||
203 | elm_write_reg(info, offset, reg_val); | ||
204 | } | ||
205 | } | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * elm_error_correction - locate correctable error position | ||
210 | * @info: elm info | ||
211 | * @err_vec: elm error vectors | ||
212 | * | ||
213 | * On completion of processing by elm module, error location status | ||
214 | * register updated with correctable/uncorrectable error information. | ||
215 | * In case of correctable errors, number of errors located from | ||
216 | * elm location status register & read the positions from | ||
217 | * elm error location register. | ||
218 | */ | ||
219 | static void elm_error_correction(struct elm_info *info, | ||
220 | struct elm_errorvec *err_vec) | ||
221 | { | ||
222 | int i, j, errors = 0; | ||
223 | int offset; | ||
224 | u32 reg_val; | ||
225 | |||
226 | for (i = 0; i < ERROR_VECTOR_MAX; i++) { | ||
227 | |||
228 | /* Check error reported */ | ||
229 | if (err_vec[i].error_reported) { | ||
230 | offset = ELM_LOCATION_STATUS + ERROR_LOCATION_SIZE * i; | ||
231 | reg_val = elm_read_reg(info, offset); | ||
232 | |||
233 | /* Check correctable error or not */ | ||
234 | if (reg_val & ECC_CORRECTABLE_MASK) { | ||
235 | offset = ELM_ERROR_LOCATION_0 + | ||
236 | ERROR_LOCATION_SIZE * i; | ||
237 | |||
238 | /* Read count of correctable errors */ | ||
239 | err_vec[i].error_count = reg_val & | ||
240 | ECC_NB_ERRORS_MASK; | ||
241 | |||
242 | /* Update the error locations in error vector */ | ||
243 | for (j = 0; j < err_vec[i].error_count; j++) { | ||
244 | |||
245 | reg_val = elm_read_reg(info, offset); | ||
246 | err_vec[i].error_loc[j] = reg_val & | ||
247 | ECC_ERROR_LOCATION_MASK; | ||
248 | |||
249 | /* Update error location register */ | ||
250 | offset += 4; | ||
251 | } | ||
252 | |||
253 | errors += err_vec[i].error_count; | ||
254 | } else { | ||
255 | err_vec[i].error_uncorrectable = true; | ||
256 | } | ||
257 | |||
258 | /* Clearing interrupts for processed error vectors */ | ||
259 | elm_write_reg(info, ELM_IRQSTATUS, BIT(i)); | ||
260 | |||
261 | /* Disable page mode */ | ||
262 | elm_configure_page_mode(info, i, false); | ||
263 | } | ||
264 | } | ||
265 | } | ||
266 | |||
267 | /** | ||
268 | * elm_decode_bch_error_page - Locate error position | ||
269 | * @dev: device pointer | ||
270 | * @ecc_calc: calculated ECC bytes from GPMC | ||
271 | * @err_vec: elm error vectors | ||
272 | * | ||
273 | * Called with one or more error reported vectors & vectors with | ||
274 | * error reported is updated in err_vec[].error_reported | ||
275 | */ | ||
276 | void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc, | ||
277 | struct elm_errorvec *err_vec) | ||
278 | { | ||
279 | struct elm_info *info = dev_get_drvdata(dev); | ||
280 | u32 reg_val; | ||
281 | |||
282 | /* Enable page mode interrupt */ | ||
283 | reg_val = elm_read_reg(info, ELM_IRQSTATUS); | ||
284 | elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID); | ||
285 | elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK); | ||
286 | |||
287 | /* Load valid ecc byte to syndrome fragment register */ | ||
288 | elm_load_syndrome(info, err_vec, ecc_calc); | ||
289 | |||
290 | /* Enable syndrome processing for which syndrome fragment is updated */ | ||
291 | elm_start_processing(info, err_vec); | ||
292 | |||
293 | /* Wait for ELM module to finish locating error correction */ | ||
294 | wait_for_completion(&info->elm_completion); | ||
295 | |||
296 | /* Disable page mode interrupt */ | ||
297 | reg_val = elm_read_reg(info, ELM_IRQENABLE); | ||
298 | elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK); | ||
299 | elm_error_correction(info, err_vec); | ||
300 | } | ||
301 | EXPORT_SYMBOL(elm_decode_bch_error_page); | ||
302 | |||
303 | static irqreturn_t elm_isr(int this_irq, void *dev_id) | ||
304 | { | ||
305 | u32 reg_val; | ||
306 | struct elm_info *info = dev_id; | ||
307 | |||
308 | reg_val = elm_read_reg(info, ELM_IRQSTATUS); | ||
309 | |||
310 | /* All error vectors processed */ | ||
311 | if (reg_val & INTR_STATUS_PAGE_VALID) { | ||
312 | elm_write_reg(info, ELM_IRQSTATUS, | ||
313 | reg_val & INTR_STATUS_PAGE_VALID); | ||
314 | complete(&info->elm_completion); | ||
315 | return IRQ_HANDLED; | ||
316 | } | ||
317 | |||
318 | return IRQ_NONE; | ||
319 | } | ||
320 | |||
321 | static int elm_probe(struct platform_device *pdev) | ||
322 | { | ||
323 | int ret = 0; | ||
324 | struct resource *res, *irq; | ||
325 | struct elm_info *info; | ||
326 | |||
327 | info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); | ||
328 | if (!info) { | ||
329 | dev_err(&pdev->dev, "failed to allocate memory\n"); | ||
330 | return -ENOMEM; | ||
331 | } | ||
332 | |||
333 | info->dev = &pdev->dev; | ||
334 | |||
335 | irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
336 | if (!irq) { | ||
337 | dev_err(&pdev->dev, "no irq resource defined\n"); | ||
338 | return -ENODEV; | ||
339 | } | ||
340 | |||
341 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
342 | if (!res) { | ||
343 | dev_err(&pdev->dev, "no memory resource defined\n"); | ||
344 | return -ENODEV; | ||
345 | } | ||
346 | |||
347 | info->elm_base = devm_request_and_ioremap(&pdev->dev, res); | ||
348 | if (!info->elm_base) | ||
349 | return -EADDRNOTAVAIL; | ||
350 | |||
351 | ret = devm_request_irq(&pdev->dev, irq->start, elm_isr, 0, | ||
352 | pdev->name, info); | ||
353 | if (ret) { | ||
354 | dev_err(&pdev->dev, "failure requesting irq %i\n", irq->start); | ||
355 | return ret; | ||
356 | } | ||
357 | |||
358 | pm_runtime_enable(&pdev->dev); | ||
359 | if (pm_runtime_get_sync(&pdev->dev)) { | ||
360 | ret = -EINVAL; | ||
361 | pm_runtime_disable(&pdev->dev); | ||
362 | dev_err(&pdev->dev, "can't enable clock\n"); | ||
363 | return ret; | ||
364 | } | ||
365 | |||
366 | init_completion(&info->elm_completion); | ||
367 | INIT_LIST_HEAD(&info->list); | ||
368 | list_add(&info->list, &elm_devices); | ||
369 | platform_set_drvdata(pdev, info); | ||
370 | return ret; | ||
371 | } | ||
372 | |||
373 | static int elm_remove(struct platform_device *pdev) | ||
374 | { | ||
375 | pm_runtime_put_sync(&pdev->dev); | ||
376 | pm_runtime_disable(&pdev->dev); | ||
377 | platform_set_drvdata(pdev, NULL); | ||
378 | return 0; | ||
379 | } | ||
380 | |||
381 | #ifdef CONFIG_OF | ||
382 | static const struct of_device_id elm_of_match[] = { | ||
383 | { .compatible = "ti,am33xx-elm" }, | ||
384 | {}, | ||
385 | }; | ||
386 | MODULE_DEVICE_TABLE(of, elm_of_match); | ||
387 | #endif | ||
388 | |||
389 | static struct platform_driver elm_driver = { | ||
390 | .driver = { | ||
391 | .name = "elm", | ||
392 | .owner = THIS_MODULE, | ||
393 | .of_match_table = of_match_ptr(elm_of_match), | ||
394 | }, | ||
395 | .probe = elm_probe, | ||
396 | .remove = elm_remove, | ||
397 | }; | ||
398 | |||
399 | module_platform_driver(elm_driver); | ||
400 | |||
401 | MODULE_DESCRIPTION("ELM driver for BCH error correction"); | ||
402 | MODULE_AUTHOR("Texas Instruments"); | ||
403 | MODULE_ALIAS("platform: elm"); | ||
404 | MODULE_LICENSE("GPL v2"); | ||