1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * JZ4725B BCH controller driver
4 *
5 * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
6 *
7 * Based on jz4780_bch.c
8 */
9
10 #include <linux/bitops.h>
11 #include <linux/device.h>
12 #include <linux/io.h>
13 #include <linux/iopoll.h>
14 #include <linux/module.h>
15 #include <linux/mutex.h>
16 #include <linux/of_platform.h>
17 #include <linux/platform_device.h>
18
19 #include "ingenic_ecc.h"
20
21 #define BCH_BHCR 0x0
22 #define BCH_BHCSR 0x4
23 #define BCH_BHCCR 0x8
24 #define BCH_BHCNT 0xc
25 #define BCH_BHDR 0x10
26 #define BCH_BHPAR0 0x14
27 #define BCH_BHERR0 0x28
28 #define BCH_BHINT 0x24
29 #define BCH_BHINTES 0x3c
30 #define BCH_BHINTEC 0x40
31 #define BCH_BHINTE 0x38
32
33 #define BCH_BHCR_ENCE BIT(3)
34 #define BCH_BHCR_BSEL BIT(2)
35 #define BCH_BHCR_INIT BIT(1)
36 #define BCH_BHCR_BCHE BIT(0)
37
38 #define BCH_BHCNT_DEC_COUNT_SHIFT 16
39 #define BCH_BHCNT_DEC_COUNT_MASK (0x3ff << BCH_BHCNT_DEC_COUNT_SHIFT)
40 #define BCH_BHCNT_ENC_COUNT_SHIFT 0
41 #define BCH_BHCNT_ENC_COUNT_MASK (0x3ff << BCH_BHCNT_ENC_COUNT_SHIFT)
42
43 #define BCH_BHERR_INDEX0_SHIFT 0
44 #define BCH_BHERR_INDEX0_MASK (0x1fff << BCH_BHERR_INDEX0_SHIFT)
45 #define BCH_BHERR_INDEX1_SHIFT 16
46 #define BCH_BHERR_INDEX1_MASK (0x1fff << BCH_BHERR_INDEX1_SHIFT)
47
48 #define BCH_BHINT_ERRC_SHIFT 28
49 #define BCH_BHINT_ERRC_MASK (0xf << BCH_BHINT_ERRC_SHIFT)
50 #define BCH_BHINT_TERRC_SHIFT 16
51 #define BCH_BHINT_TERRC_MASK (0x7f << BCH_BHINT_TERRC_SHIFT)
52 #define BCH_BHINT_ALL_0 BIT(5)
53 #define BCH_BHINT_ALL_F BIT(4)
54 #define BCH_BHINT_DECF BIT(3)
55 #define BCH_BHINT_ENCF BIT(2)
56 #define BCH_BHINT_UNCOR BIT(1)
57 #define BCH_BHINT_ERR BIT(0)
58
59 /* Timeout for BCH calculation/correction. */
60 #define BCH_TIMEOUT_US 100000
61
jz4725b_bch_config_set(struct ingenic_ecc * bch,u32 cfg)62 static inline void jz4725b_bch_config_set(struct ingenic_ecc *bch, u32 cfg)
63 {
64 writel(cfg, bch->base + BCH_BHCSR);
65 }
66
jz4725b_bch_config_clear(struct ingenic_ecc * bch,u32 cfg)67 static inline void jz4725b_bch_config_clear(struct ingenic_ecc *bch, u32 cfg)
68 {
69 writel(cfg, bch->base + BCH_BHCCR);
70 }
71
jz4725b_bch_reset(struct ingenic_ecc * bch,struct ingenic_ecc_params * params,bool calc_ecc)72 static int jz4725b_bch_reset(struct ingenic_ecc *bch,
73 struct ingenic_ecc_params *params, bool calc_ecc)
74 {
75 u32 reg, max_value;
76
77 /* Clear interrupt status. */
78 writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);
79
80 /* Initialise and enable BCH. */
81 jz4725b_bch_config_clear(bch, 0x1f);
82 jz4725b_bch_config_set(bch, BCH_BHCR_BCHE);
83
84 if (params->strength == 8)
85 jz4725b_bch_config_set(bch, BCH_BHCR_BSEL);
86 else
87 jz4725b_bch_config_clear(bch, BCH_BHCR_BSEL);
88
89 if (calc_ecc) /* calculate ECC from data */
90 jz4725b_bch_config_set(bch, BCH_BHCR_ENCE);
91 else /* correct data from ECC */
92 jz4725b_bch_config_clear(bch, BCH_BHCR_ENCE);
93
94 jz4725b_bch_config_set(bch, BCH_BHCR_INIT);
95
96 max_value = BCH_BHCNT_ENC_COUNT_MASK >> BCH_BHCNT_ENC_COUNT_SHIFT;
97 if (params->size > max_value)
98 return -EINVAL;
99
100 max_value = BCH_BHCNT_DEC_COUNT_MASK >> BCH_BHCNT_DEC_COUNT_SHIFT;
101 if (params->size + params->bytes > max_value)
102 return -EINVAL;
103
104 /* Set up BCH count register. */
105 reg = params->size << BCH_BHCNT_ENC_COUNT_SHIFT;
106 reg |= (params->size + params->bytes) << BCH_BHCNT_DEC_COUNT_SHIFT;
107 writel(reg, bch->base + BCH_BHCNT);
108
109 return 0;
110 }
111
jz4725b_bch_disable(struct ingenic_ecc * bch)112 static void jz4725b_bch_disable(struct ingenic_ecc *bch)
113 {
114 /* Clear interrupts */
115 writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT);
116
117 /* Disable the hardware */
118 jz4725b_bch_config_clear(bch, BCH_BHCR_BCHE);
119 }
120
jz4725b_bch_write_data(struct ingenic_ecc * bch,const u8 * buf,size_t size)121 static void jz4725b_bch_write_data(struct ingenic_ecc *bch, const u8 *buf,
122 size_t size)
123 {
124 while (size--)
125 writeb(*buf++, bch->base + BCH_BHDR);
126 }
127
jz4725b_bch_read_parity(struct ingenic_ecc * bch,u8 * buf,size_t size)128 static void jz4725b_bch_read_parity(struct ingenic_ecc *bch, u8 *buf,
129 size_t size)
130 {
131 size_t size32 = size / sizeof(u32);
132 size_t size8 = size % sizeof(u32);
133 u32 *dest32;
134 u8 *dest8;
135 u32 val, offset = 0;
136
137 dest32 = (u32 *)buf;
138 while (size32--) {
139 *dest32++ = readl_relaxed(bch->base + BCH_BHPAR0 + offset);
140 offset += sizeof(u32);
141 }
142
143 dest8 = (u8 *)dest32;
144 val = readl_relaxed(bch->base + BCH_BHPAR0 + offset);
145 switch (size8) {
146 case 3:
147 dest8[2] = (val >> 16) & 0xff;
148 fallthrough;
149 case 2:
150 dest8[1] = (val >> 8) & 0xff;
151 fallthrough;
152 case 1:
153 dest8[0] = val & 0xff;
154 break;
155 }
156 }
157
jz4725b_bch_wait_complete(struct ingenic_ecc * bch,unsigned int irq,u32 * status)158 static int jz4725b_bch_wait_complete(struct ingenic_ecc *bch, unsigned int irq,
159 u32 *status)
160 {
161 u32 reg;
162 int ret;
163
164 /*
165 * While we could use interrupts here and sleep until the operation
166 * completes, the controller works fairly quickly (usually a few
167 * microseconds) and so the overhead of sleeping until we get an
168 * interrupt quite noticeably decreases performance.
169 */
170 ret = readl_relaxed_poll_timeout(bch->base + BCH_BHINT, reg,
171 reg & irq, 0, BCH_TIMEOUT_US);
172 if (ret)
173 return ret;
174
175 if (status)
176 *status = reg;
177
178 writel(reg, bch->base + BCH_BHINT);
179
180 return 0;
181 }
182
jz4725b_calculate(struct ingenic_ecc * bch,struct ingenic_ecc_params * params,const u8 * buf,u8 * ecc_code)183 static int jz4725b_calculate(struct ingenic_ecc *bch,
184 struct ingenic_ecc_params *params,
185 const u8 *buf, u8 *ecc_code)
186 {
187 int ret;
188
189 mutex_lock(&bch->lock);
190
191 ret = jz4725b_bch_reset(bch, params, true);
192 if (ret) {
193 dev_err(bch->dev, "Unable to init BCH with given parameters\n");
194 goto out_disable;
195 }
196
197 jz4725b_bch_write_data(bch, buf, params->size);
198
199 ret = jz4725b_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL);
200 if (ret) {
201 dev_err(bch->dev, "timed out while calculating ECC\n");
202 goto out_disable;
203 }
204
205 jz4725b_bch_read_parity(bch, ecc_code, params->bytes);
206
207 out_disable:
208 jz4725b_bch_disable(bch);
209 mutex_unlock(&bch->lock);
210
211 return ret;
212 }
213
jz4725b_correct(struct ingenic_ecc * bch,struct ingenic_ecc_params * params,u8 * buf,u8 * ecc_code)214 static int jz4725b_correct(struct ingenic_ecc *bch,
215 struct ingenic_ecc_params *params,
216 u8 *buf, u8 *ecc_code)
217 {
218 u32 reg, errors, bit;
219 unsigned int i;
220 int ret;
221
222 mutex_lock(&bch->lock);
223
224 ret = jz4725b_bch_reset(bch, params, false);
225 if (ret) {
226 dev_err(bch->dev, "Unable to init BCH with given parameters\n");
227 goto out;
228 }
229
230 jz4725b_bch_write_data(bch, buf, params->size);
231 jz4725b_bch_write_data(bch, ecc_code, params->bytes);
232
233 ret = jz4725b_bch_wait_complete(bch, BCH_BHINT_DECF, ®);
234 if (ret) {
235 dev_err(bch->dev, "timed out while correcting data\n");
236 goto out;
237 }
238
239 if (reg & (BCH_BHINT_ALL_F | BCH_BHINT_ALL_0)) {
240 /* Data and ECC is all 0xff or 0x00 - nothing to correct */
241 ret = 0;
242 goto out;
243 }
244
245 if (reg & BCH_BHINT_UNCOR) {
246 /* Uncorrectable ECC error */
247 ret = -EBADMSG;
248 goto out;
249 }
250
251 errors = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT;
252
253 /* Correct any detected errors. */
254 for (i = 0; i < errors; i++) {
255 if (i & 1) {
256 bit = (reg & BCH_BHERR_INDEX1_MASK) >> BCH_BHERR_INDEX1_SHIFT;
257 } else {
258 reg = readl(bch->base + BCH_BHERR0 + (i * 4));
259 bit = (reg & BCH_BHERR_INDEX0_MASK) >> BCH_BHERR_INDEX0_SHIFT;
260 }
261
262 buf[(bit >> 3)] ^= BIT(bit & 0x7);
263 }
264
265 out:
266 jz4725b_bch_disable(bch);
267 mutex_unlock(&bch->lock);
268
269 return ret;
270 }
271
272 static const struct ingenic_ecc_ops jz4725b_bch_ops = {
273 .disable = jz4725b_bch_disable,
274 .calculate = jz4725b_calculate,
275 .correct = jz4725b_correct,
276 };
277
278 static const struct of_device_id jz4725b_bch_dt_match[] = {
279 { .compatible = "ingenic,jz4725b-bch", .data = &jz4725b_bch_ops },
280 {},
281 };
282 MODULE_DEVICE_TABLE(of, jz4725b_bch_dt_match);
283
284 static struct platform_driver jz4725b_bch_driver = {
285 .probe = ingenic_ecc_probe,
286 .driver = {
287 .name = "jz4725b-bch",
288 .of_match_table = jz4725b_bch_dt_match,
289 },
290 };
291 module_platform_driver(jz4725b_bch_driver);
292
293 MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
294 MODULE_DESCRIPTION("Ingenic JZ4725B BCH controller driver");
295 MODULE_LICENSE("GPL v2");
296