1 // SPDX-License-Identifier: GPL-2.0
3 * JZ4725B BCH controller driver
5 * Copyright (C) 2019 Paul Cercueil <paul@crapouillou.net>
7 * Based on jz4780_bch.c
10 #include <linux/bitops.h>
11 #include <linux/device.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>
19 #include "ingenic_ecc.h"
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
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)
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)
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)
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)
59 /* Timeout for BCH calculation/correction. */
60 #define BCH_TIMEOUT_US 100000
62 static inline void jz4725b_bch_config_set(struct ingenic_ecc
*bch
, u32 cfg
)
64 writel(cfg
, bch
->base
+ BCH_BHCSR
);
67 static inline void jz4725b_bch_config_clear(struct ingenic_ecc
*bch
, u32 cfg
)
69 writel(cfg
, bch
->base
+ BCH_BHCCR
);
72 static int jz4725b_bch_reset(struct ingenic_ecc
*bch
,
73 struct ingenic_ecc_params
*params
, bool calc_ecc
)
77 /* Clear interrupt status. */
78 writel(readl(bch
->base
+ BCH_BHINT
), bch
->base
+ BCH_BHINT
);
80 /* Initialise and enable BCH. */
81 jz4725b_bch_config_clear(bch
, 0x1f);
82 jz4725b_bch_config_set(bch
, BCH_BHCR_BCHE
);
84 if (params
->strength
== 8)
85 jz4725b_bch_config_set(bch
, BCH_BHCR_BSEL
);
87 jz4725b_bch_config_clear(bch
, BCH_BHCR_BSEL
);
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
);
94 jz4725b_bch_config_set(bch
, BCH_BHCR_INIT
);
96 max_value
= BCH_BHCNT_ENC_COUNT_MASK
>> BCH_BHCNT_ENC_COUNT_SHIFT
;
97 if (params
->size
> max_value
)
100 max_value
= BCH_BHCNT_DEC_COUNT_MASK
>> BCH_BHCNT_DEC_COUNT_SHIFT
;
101 if (params
->size
+ params
->bytes
> max_value
)
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
);
112 static void jz4725b_bch_disable(struct ingenic_ecc
*bch
)
114 /* Clear interrupts */
115 writel(readl(bch
->base
+ BCH_BHINT
), bch
->base
+ BCH_BHINT
);
117 /* Disable the hardware */
118 jz4725b_bch_config_clear(bch
, BCH_BHCR_BCHE
);
121 static void jz4725b_bch_write_data(struct ingenic_ecc
*bch
, const u8
*buf
,
125 writeb(*buf
++, bch
->base
+ BCH_BHDR
);
128 static void jz4725b_bch_read_parity(struct ingenic_ecc
*bch
, u8
*buf
,
131 size_t size32
= size
/ sizeof(u32
);
132 size_t size8
= size
% sizeof(u32
);
139 *dest32
++ = readl_relaxed(bch
->base
+ BCH_BHPAR0
+ offset
);
140 offset
+= sizeof(u32
);
143 dest8
= (u8
*)dest32
;
144 val
= readl_relaxed(bch
->base
+ BCH_BHPAR0
+ offset
);
147 dest8
[2] = (val
>> 16) & 0xff;
150 dest8
[1] = (val
>> 8) & 0xff;
153 dest8
[0] = val
& 0xff;
158 static int jz4725b_bch_wait_complete(struct ingenic_ecc
*bch
, unsigned int irq
,
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.
170 ret
= readl_relaxed_poll_timeout(bch
->base
+ BCH_BHINT
, reg
,
171 reg
& irq
, 0, BCH_TIMEOUT_US
);
178 writel(reg
, bch
->base
+ BCH_BHINT
);
183 static int jz4725b_calculate(struct ingenic_ecc
*bch
,
184 struct ingenic_ecc_params
*params
,
185 const u8
*buf
, u8
*ecc_code
)
189 mutex_lock(&bch
->lock
);
191 ret
= jz4725b_bch_reset(bch
, params
, true);
193 dev_err(bch
->dev
, "Unable to init BCH with given parameters\n");
197 jz4725b_bch_write_data(bch
, buf
, params
->size
);
199 ret
= jz4725b_bch_wait_complete(bch
, BCH_BHINT_ENCF
, NULL
);
201 dev_err(bch
->dev
, "timed out while calculating ECC\n");
205 jz4725b_bch_read_parity(bch
, ecc_code
, params
->bytes
);
208 jz4725b_bch_disable(bch
);
209 mutex_unlock(&bch
->lock
);
214 static int jz4725b_correct(struct ingenic_ecc
*bch
,
215 struct ingenic_ecc_params
*params
,
216 u8
*buf
, u8
*ecc_code
)
218 u32 reg
, errors
, bit
;
222 mutex_lock(&bch
->lock
);
224 ret
= jz4725b_bch_reset(bch
, params
, false);
226 dev_err(bch
->dev
, "Unable to init BCH with given parameters\n");
230 jz4725b_bch_write_data(bch
, buf
, params
->size
);
231 jz4725b_bch_write_data(bch
, ecc_code
, params
->bytes
);
233 ret
= jz4725b_bch_wait_complete(bch
, BCH_BHINT_DECF
, ®
);
235 dev_err(bch
->dev
, "timed out while correcting data\n");
239 if (reg
& (BCH_BHINT_ALL_F
| BCH_BHINT_ALL_0
)) {
240 /* Data and ECC is all 0xff or 0x00 - nothing to correct */
245 if (reg
& BCH_BHINT_UNCOR
) {
246 /* Uncorrectable ECC error */
251 errors
= (reg
& BCH_BHINT_ERRC_MASK
) >> BCH_BHINT_ERRC_SHIFT
;
253 /* Correct any detected errors. */
254 for (i
= 0; i
< errors
; i
++) {
256 bit
= (reg
& BCH_BHERR_INDEX1_MASK
) >> BCH_BHERR_INDEX1_SHIFT
;
258 reg
= readl(bch
->base
+ BCH_BHERR0
+ (i
* 4));
259 bit
= (reg
& BCH_BHERR_INDEX0_MASK
) >> BCH_BHERR_INDEX0_SHIFT
;
262 buf
[(bit
>> 3)] ^= BIT(bit
& 0x7);
266 jz4725b_bch_disable(bch
);
267 mutex_unlock(&bch
->lock
);
272 static const struct ingenic_ecc_ops jz4725b_bch_ops
= {
273 .disable
= jz4725b_bch_disable
,
274 .calculate
= jz4725b_calculate
,
275 .correct
= jz4725b_correct
,
278 static const struct of_device_id jz4725b_bch_dt_match
[] = {
279 { .compatible
= "ingenic,jz4725b-bch", .data
= &jz4725b_bch_ops
},
282 MODULE_DEVICE_TABLE(of
, jz4725b_bch_dt_match
);
284 static struct platform_driver jz4725b_bch_driver
= {
285 .probe
= ingenic_ecc_probe
,
287 .name
= "jz4725b-bch",
288 .of_match_table
= jz4725b_bch_dt_match
,
291 module_platform_driver(jz4725b_bch_driver
);
293 MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
294 MODULE_DESCRIPTION("Ingenic JZ4725B BCH controller driver");
295 MODULE_LICENSE("GPL v2");