Commit | Line | Data |
---|---|---|
ae02ab00 AS |
1 | /* |
2 | * JZ4780 BCH controller | |
3 | * | |
4 | * Copyright (c) 2015 Imagination Technologies | |
5 | * Author: Alex Smith <alex.smith@imgtec.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License version 2 as published | |
9 | * by the Free Software Foundation. | |
10 | */ | |
11 | ||
12 | #include <linux/bitops.h> | |
13 | #include <linux/clk.h> | |
14 | #include <linux/delay.h> | |
15 | #include <linux/init.h> | |
16 | #include <linux/iopoll.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/mutex.h> | |
19 | #include <linux/of.h> | |
20 | #include <linux/of_platform.h> | |
21 | #include <linux/platform_device.h> | |
22 | #include <linux/sched.h> | |
23 | #include <linux/slab.h> | |
24 | ||
25 | #include "jz4780_bch.h" | |
26 | ||
27 | #define BCH_BHCR 0x0 | |
28 | #define BCH_BHCCR 0x8 | |
29 | #define BCH_BHCNT 0xc | |
30 | #define BCH_BHDR 0x10 | |
31 | #define BCH_BHPAR0 0x14 | |
32 | #define BCH_BHERR0 0x84 | |
33 | #define BCH_BHINT 0x184 | |
34 | #define BCH_BHINTES 0x188 | |
35 | #define BCH_BHINTEC 0x18c | |
36 | #define BCH_BHINTE 0x190 | |
37 | ||
38 | #define BCH_BHCR_BSEL_SHIFT 4 | |
39 | #define BCH_BHCR_BSEL_MASK (0x7f << BCH_BHCR_BSEL_SHIFT) | |
40 | #define BCH_BHCR_ENCE BIT(2) | |
41 | #define BCH_BHCR_INIT BIT(1) | |
42 | #define BCH_BHCR_BCHE BIT(0) | |
43 | ||
44 | #define BCH_BHCNT_PARITYSIZE_SHIFT 16 | |
45 | #define BCH_BHCNT_PARITYSIZE_MASK (0x7f << BCH_BHCNT_PARITYSIZE_SHIFT) | |
46 | #define BCH_BHCNT_BLOCKSIZE_SHIFT 0 | |
47 | #define BCH_BHCNT_BLOCKSIZE_MASK (0x7ff << BCH_BHCNT_BLOCKSIZE_SHIFT) | |
48 | ||
49 | #define BCH_BHERR_MASK_SHIFT 16 | |
50 | #define BCH_BHERR_MASK_MASK (0xffff << BCH_BHERR_MASK_SHIFT) | |
51 | #define BCH_BHERR_INDEX_SHIFT 0 | |
52 | #define BCH_BHERR_INDEX_MASK (0x7ff << BCH_BHERR_INDEX_SHIFT) | |
53 | ||
54 | #define BCH_BHINT_ERRC_SHIFT 24 | |
55 | #define BCH_BHINT_ERRC_MASK (0x7f << BCH_BHINT_ERRC_SHIFT) | |
56 | #define BCH_BHINT_TERRC_SHIFT 16 | |
57 | #define BCH_BHINT_TERRC_MASK (0x7f << BCH_BHINT_TERRC_SHIFT) | |
58 | #define BCH_BHINT_DECF BIT(3) | |
59 | #define BCH_BHINT_ENCF BIT(2) | |
60 | #define BCH_BHINT_UNCOR BIT(1) | |
61 | #define BCH_BHINT_ERR BIT(0) | |
62 | ||
63 | #define BCH_CLK_RATE (200 * 1000 * 1000) | |
64 | ||
65 | /* Timeout for BCH calculation/correction. */ | |
66 | #define BCH_TIMEOUT_US 100000 | |
67 | ||
68 | struct jz4780_bch { | |
69 | struct device *dev; | |
70 | void __iomem *base; | |
71 | struct clk *clk; | |
72 | struct mutex lock; | |
73 | }; | |
74 | ||
75 | static void jz4780_bch_init(struct jz4780_bch *bch, | |
76 | struct jz4780_bch_params *params, bool encode) | |
77 | { | |
78 | u32 reg; | |
79 | ||
80 | /* Clear interrupt status. */ | |
81 | writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); | |
82 | ||
83 | /* Set up BCH count register. */ | |
84 | reg = params->size << BCH_BHCNT_BLOCKSIZE_SHIFT; | |
85 | reg |= params->bytes << BCH_BHCNT_PARITYSIZE_SHIFT; | |
86 | writel(reg, bch->base + BCH_BHCNT); | |
87 | ||
88 | /* Initialise and enable BCH. */ | |
89 | reg = BCH_BHCR_BCHE | BCH_BHCR_INIT; | |
90 | reg |= params->strength << BCH_BHCR_BSEL_SHIFT; | |
91 | if (encode) | |
92 | reg |= BCH_BHCR_ENCE; | |
93 | writel(reg, bch->base + BCH_BHCR); | |
94 | } | |
95 | ||
96 | static void jz4780_bch_disable(struct jz4780_bch *bch) | |
97 | { | |
98 | writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); | |
99 | writel(BCH_BHCR_BCHE, bch->base + BCH_BHCCR); | |
100 | } | |
101 | ||
102 | static void jz4780_bch_write_data(struct jz4780_bch *bch, const void *buf, | |
103 | size_t size) | |
104 | { | |
105 | size_t size32 = size / sizeof(u32); | |
106 | size_t size8 = size % sizeof(u32); | |
107 | const u32 *src32; | |
108 | const u8 *src8; | |
109 | ||
110 | src32 = (const u32 *)buf; | |
111 | while (size32--) | |
112 | writel(*src32++, bch->base + BCH_BHDR); | |
113 | ||
114 | src8 = (const u8 *)src32; | |
115 | while (size8--) | |
116 | writeb(*src8++, bch->base + BCH_BHDR); | |
117 | } | |
118 | ||
119 | static void jz4780_bch_read_parity(struct jz4780_bch *bch, void *buf, | |
120 | size_t size) | |
121 | { | |
122 | size_t size32 = size / sizeof(u32); | |
123 | size_t size8 = size % sizeof(u32); | |
124 | u32 *dest32; | |
125 | u8 *dest8; | |
126 | u32 val, offset = 0; | |
127 | ||
128 | dest32 = (u32 *)buf; | |
129 | while (size32--) { | |
130 | *dest32++ = readl(bch->base + BCH_BHPAR0 + offset); | |
131 | offset += sizeof(u32); | |
132 | } | |
133 | ||
134 | dest8 = (u8 *)dest32; | |
135 | val = readl(bch->base + BCH_BHPAR0 + offset); | |
136 | switch (size8) { | |
137 | case 3: | |
138 | dest8[2] = (val >> 16) & 0xff; | |
139 | case 2: | |
140 | dest8[1] = (val >> 8) & 0xff; | |
141 | case 1: | |
142 | dest8[0] = val & 0xff; | |
143 | break; | |
144 | } | |
145 | } | |
146 | ||
147 | static bool jz4780_bch_wait_complete(struct jz4780_bch *bch, unsigned int irq, | |
148 | u32 *status) | |
149 | { | |
150 | u32 reg; | |
151 | int ret; | |
152 | ||
153 | /* | |
154 | * While we could use interrupts here and sleep until the operation | |
155 | * completes, the controller works fairly quickly (usually a few | |
156 | * microseconds) and so the overhead of sleeping until we get an | |
157 | * interrupt quite noticeably decreases performance. | |
158 | */ | |
159 | ret = readl_poll_timeout(bch->base + BCH_BHINT, reg, | |
160 | (reg & irq) == irq, 0, BCH_TIMEOUT_US); | |
161 | if (ret) | |
162 | return false; | |
163 | ||
164 | if (status) | |
165 | *status = reg; | |
166 | ||
167 | writel(reg, bch->base + BCH_BHINT); | |
168 | return true; | |
169 | } | |
170 | ||
171 | /** | |
172 | * jz4780_bch_calculate() - calculate ECC for a data buffer | |
173 | * @bch: BCH device. | |
174 | * @params: BCH parameters. | |
175 | * @buf: input buffer with raw data. | |
176 | * @ecc_code: output buffer with ECC. | |
177 | * | |
178 | * Return: 0 on success, -ETIMEDOUT if timed out while waiting for BCH | |
179 | * controller. | |
180 | */ | |
181 | int jz4780_bch_calculate(struct jz4780_bch *bch, struct jz4780_bch_params *params, | |
182 | const u8 *buf, u8 *ecc_code) | |
183 | { | |
184 | int ret = 0; | |
185 | ||
186 | mutex_lock(&bch->lock); | |
187 | jz4780_bch_init(bch, params, true); | |
188 | jz4780_bch_write_data(bch, buf, params->size); | |
189 | ||
190 | if (jz4780_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL)) { | |
191 | jz4780_bch_read_parity(bch, ecc_code, params->bytes); | |
192 | } else { | |
193 | dev_err(bch->dev, "timed out while calculating ECC\n"); | |
194 | ret = -ETIMEDOUT; | |
195 | } | |
196 | ||
197 | jz4780_bch_disable(bch); | |
198 | mutex_unlock(&bch->lock); | |
199 | return ret; | |
200 | } | |
201 | EXPORT_SYMBOL(jz4780_bch_calculate); | |
202 | ||
203 | /** | |
204 | * jz4780_bch_correct() - detect and correct bit errors | |
205 | * @bch: BCH device. | |
206 | * @params: BCH parameters. | |
207 | * @buf: raw data read from the chip. | |
208 | * @ecc_code: ECC read from the chip. | |
209 | * | |
210 | * Given the raw data and the ECC read from the NAND device, detects and | |
211 | * corrects errors in the data. | |
212 | * | |
6c1207b5 HH |
213 | * Return: the number of bit errors corrected, -EBADMSG if there are too many |
214 | * errors to correct or -ETIMEDOUT if we timed out waiting for the controller. | |
ae02ab00 AS |
215 | */ |
216 | int jz4780_bch_correct(struct jz4780_bch *bch, struct jz4780_bch_params *params, | |
217 | u8 *buf, u8 *ecc_code) | |
218 | { | |
219 | u32 reg, mask, index; | |
220 | int i, ret, count; | |
221 | ||
222 | mutex_lock(&bch->lock); | |
223 | ||
224 | jz4780_bch_init(bch, params, false); | |
225 | jz4780_bch_write_data(bch, buf, params->size); | |
226 | jz4780_bch_write_data(bch, ecc_code, params->bytes); | |
227 | ||
228 | if (!jz4780_bch_wait_complete(bch, BCH_BHINT_DECF, ®)) { | |
229 | dev_err(bch->dev, "timed out while correcting data\n"); | |
6c1207b5 | 230 | ret = -ETIMEDOUT; |
ae02ab00 AS |
231 | goto out; |
232 | } | |
233 | ||
234 | if (reg & BCH_BHINT_UNCOR) { | |
235 | dev_warn(bch->dev, "uncorrectable ECC error\n"); | |
6c1207b5 | 236 | ret = -EBADMSG; |
ae02ab00 AS |
237 | goto out; |
238 | } | |
239 | ||
240 | /* Correct any detected errors. */ | |
241 | if (reg & BCH_BHINT_ERR) { | |
242 | count = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT; | |
243 | ret = (reg & BCH_BHINT_TERRC_MASK) >> BCH_BHINT_TERRC_SHIFT; | |
244 | ||
245 | for (i = 0; i < count; i++) { | |
246 | reg = readl(bch->base + BCH_BHERR0 + (i * 4)); | |
247 | mask = (reg & BCH_BHERR_MASK_MASK) >> | |
248 | BCH_BHERR_MASK_SHIFT; | |
249 | index = (reg & BCH_BHERR_INDEX_MASK) >> | |
250 | BCH_BHERR_INDEX_SHIFT; | |
251 | buf[(index * 2) + 0] ^= mask; | |
252 | buf[(index * 2) + 1] ^= mask >> 8; | |
253 | } | |
254 | } else { | |
255 | ret = 0; | |
256 | } | |
257 | ||
258 | out: | |
259 | jz4780_bch_disable(bch); | |
260 | mutex_unlock(&bch->lock); | |
261 | return ret; | |
262 | } | |
263 | EXPORT_SYMBOL(jz4780_bch_correct); | |
264 | ||
265 | /** | |
266 | * jz4780_bch_get() - get the BCH controller device | |
267 | * @np: BCH device tree node. | |
268 | * | |
269 | * Gets the BCH controller device from the specified device tree node. The | |
270 | * device must be released with jz4780_bch_release() when it is no longer being | |
271 | * used. | |
272 | * | |
273 | * Return: a pointer to jz4780_bch, errors are encoded into the pointer. | |
274 | * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet. | |
275 | */ | |
276 | static struct jz4780_bch *jz4780_bch_get(struct device_node *np) | |
277 | { | |
278 | struct platform_device *pdev; | |
279 | struct jz4780_bch *bch; | |
280 | ||
281 | pdev = of_find_device_by_node(np); | |
282 | if (!pdev || !platform_get_drvdata(pdev)) | |
283 | return ERR_PTR(-EPROBE_DEFER); | |
284 | ||
285 | get_device(&pdev->dev); | |
286 | ||
287 | bch = platform_get_drvdata(pdev); | |
288 | clk_prepare_enable(bch->clk); | |
289 | ||
ae02ab00 AS |
290 | return bch; |
291 | } | |
292 | ||
293 | /** | |
294 | * of_jz4780_bch_get() - get the BCH controller from a DT node | |
295 | * @of_node: the node that contains a bch-controller property. | |
296 | * | |
297 | * Get the bch-controller property from the given device tree | |
298 | * node and pass it to jz4780_bch_get to do the work. | |
299 | * | |
300 | * Return: a pointer to jz4780_bch, errors are encoded into the pointer. | |
301 | * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet. | |
302 | */ | |
303 | struct jz4780_bch *of_jz4780_bch_get(struct device_node *of_node) | |
304 | { | |
305 | struct jz4780_bch *bch = NULL; | |
306 | struct device_node *np; | |
307 | ||
308 | np = of_parse_phandle(of_node, "ingenic,bch-controller", 0); | |
309 | ||
310 | if (np) { | |
311 | bch = jz4780_bch_get(np); | |
312 | of_node_put(np); | |
313 | } | |
314 | return bch; | |
315 | } | |
316 | EXPORT_SYMBOL(of_jz4780_bch_get); | |
317 | ||
318 | /** | |
319 | * jz4780_bch_release() - release the BCH controller device | |
320 | * @bch: BCH device. | |
321 | */ | |
322 | void jz4780_bch_release(struct jz4780_bch *bch) | |
323 | { | |
324 | clk_disable_unprepare(bch->clk); | |
325 | put_device(bch->dev); | |
326 | } | |
327 | EXPORT_SYMBOL(jz4780_bch_release); | |
328 | ||
329 | static int jz4780_bch_probe(struct platform_device *pdev) | |
330 | { | |
331 | struct device *dev = &pdev->dev; | |
332 | struct jz4780_bch *bch; | |
333 | struct resource *res; | |
334 | ||
335 | bch = devm_kzalloc(dev, sizeof(*bch), GFP_KERNEL); | |
336 | if (!bch) | |
337 | return -ENOMEM; | |
338 | ||
339 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
340 | bch->base = devm_ioremap_resource(dev, res); | |
341 | if (IS_ERR(bch->base)) | |
342 | return PTR_ERR(bch->base); | |
343 | ||
344 | jz4780_bch_disable(bch); | |
345 | ||
346 | bch->clk = devm_clk_get(dev, NULL); | |
347 | if (IS_ERR(bch->clk)) { | |
348 | dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(bch->clk)); | |
349 | return PTR_ERR(bch->clk); | |
350 | } | |
351 | ||
352 | clk_set_rate(bch->clk, BCH_CLK_RATE); | |
353 | ||
354 | mutex_init(&bch->lock); | |
355 | ||
356 | bch->dev = dev; | |
357 | platform_set_drvdata(pdev, bch); | |
358 | ||
359 | return 0; | |
360 | } | |
361 | ||
362 | static const struct of_device_id jz4780_bch_dt_match[] = { | |
363 | { .compatible = "ingenic,jz4780-bch" }, | |
364 | {}, | |
365 | }; | |
366 | MODULE_DEVICE_TABLE(of, jz4780_bch_dt_match); | |
367 | ||
368 | static struct platform_driver jz4780_bch_driver = { | |
369 | .probe = jz4780_bch_probe, | |
370 | .driver = { | |
371 | .name = "jz4780-bch", | |
372 | .of_match_table = of_match_ptr(jz4780_bch_dt_match), | |
373 | }, | |
374 | }; | |
375 | module_platform_driver(jz4780_bch_driver); | |
376 | ||
377 | MODULE_AUTHOR("Alex Smith <alex@alex-smith.me.uk>"); | |
8490c03b | 378 | MODULE_AUTHOR("Harvey Hunt <harveyhuntnexus@gmail.com>"); |
ae02ab00 AS |
379 | MODULE_DESCRIPTION("Ingenic JZ4780 BCH error correction driver"); |
380 | MODULE_LICENSE("GPL v2"); |