libnvdimm/altmap: Track namespace boundaries in altmap
[linux-2.6-block.git] / drivers / mtd / nand / raw / ingenic / jz4725b_bch.c
CommitLineData
3e6ac2ad
PC
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
62static inline void jz4725b_bch_config_set(struct ingenic_ecc *bch, u32 cfg)
63{
64 writel(cfg, bch->base + BCH_BHCSR);
65}
66
67static inline void jz4725b_bch_config_clear(struct ingenic_ecc *bch, u32 cfg)
68{
69 writel(cfg, bch->base + BCH_BHCCR);
70}
71
72static 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
112static 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
121static 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
128static 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 /* fall-through */
149 case 2:
150 dest8[1] = (val >> 8) & 0xff;
151 /* fall-through */
152 case 1:
153 dest8[0] = val & 0xff;
154 break;
155 }
156}
157
158static 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
183static 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
207out_disable:
208 jz4725b_bch_disable(bch);
209 mutex_unlock(&bch->lock);
210
211 return ret;
212}
213
214static 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, &reg);
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
265out:
266 jz4725b_bch_disable(bch);
267 mutex_unlock(&bch->lock);
268
269 return ret;
270}
271
272static const struct ingenic_ecc_ops jz4725b_bch_ops = {
273 .disable = jz4725b_bch_disable,
274 .calculate = jz4725b_calculate,
275 .correct = jz4725b_correct,
276};
277
278static const struct of_device_id jz4725b_bch_dt_match[] = {
279 { .compatible = "ingenic,jz4725b-bch", .data = &jz4725b_bch_ops },
280 {},
281};
282MODULE_DEVICE_TABLE(of, jz4725b_bch_dt_match);
283
284static 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};
291module_platform_driver(jz4725b_bch_driver);
292
293MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
294MODULE_DESCRIPTION("Ingenic JZ4725B BCH controller driver");
295MODULE_LICENSE("GPL v2");