diff options
author | Bastian Hecht <hechtb@googlemail.com> | 2012-05-14 08:14:46 -0400 |
---|---|---|
committer | David Woodhouse <David.Woodhouse@intel.com> | 2012-07-06 13:17:04 -0400 |
commit | 6667a6d58e25d351d8fce7a628a8c9c139a8bdc9 (patch) | |
tree | af1edb613a317991a0451ea1dd5a52b531b18d83 | |
parent | 623c55caa37203ece6b4450daa0d2d058255da30 (diff) |
mtd: sh_flctl: Restructure the hardware ECC handling
There are multiple reasons for a rewrite:
- a race exists: when _4ECCEND is set, _4ECCFA may become true too
meanwhile, which is lost and a non-correctable error is treated as
correctable.
- the ECC statistics don't get properly propagated to the base code.
- empty pages would get marked as corrupted
The rewrite resolves the issues and I hope it gives a more explicit
code flow structure.
Signed-off-by: Bastian Hecht <hechtb@gmail.com>
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
-rw-r--r-- | drivers/mtd/nand/sh_flctl.c | 121 | ||||
-rw-r--r-- | include/linux/mtd/sh_flctl.h | 10 |
2 files changed, 88 insertions, 43 deletions
diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c index 96e242adda6a..bc50e83336bb 100644 --- a/drivers/mtd/nand/sh_flctl.c +++ b/drivers/mtd/nand/sh_flctl.c | |||
@@ -165,27 +165,56 @@ static void wait_wfifo_ready(struct sh_flctl *flctl) | |||
165 | timeout_error(flctl, __func__); | 165 | timeout_error(flctl, __func__); |
166 | } | 166 | } |
167 | 167 | ||
168 | static int wait_recfifo_ready(struct sh_flctl *flctl, int sector_number) | 168 | static enum flctl_ecc_res_t wait_recfifo_ready |
169 | (struct sh_flctl *flctl, int sector_number) | ||
169 | { | 170 | { |
170 | uint32_t timeout = LOOP_TIMEOUT_MAX; | 171 | uint32_t timeout = LOOP_TIMEOUT_MAX; |
171 | int checked[4]; | ||
172 | void __iomem *ecc_reg[4]; | 172 | void __iomem *ecc_reg[4]; |
173 | int i; | 173 | int i; |
174 | int state = FL_SUCCESS; | ||
174 | uint32_t data, size; | 175 | uint32_t data, size; |
175 | 176 | ||
176 | memset(checked, 0, sizeof(checked)); | 177 | /* |
177 | 178 | * First this loops checks in FLDTCNTR if we are ready to read out the | |
179 | * oob data. This is the case if either all went fine without errors or | ||
180 | * if the bottom part of the loop corrected the errors or marked them as | ||
181 | * uncorrectable and the controller is given time to push the data into | ||
182 | * the FIFO. | ||
183 | */ | ||
178 | while (timeout--) { | 184 | while (timeout--) { |
185 | /* check if all is ok and we can read out the OOB */ | ||
179 | size = readl(FLDTCNTR(flctl)) >> 24; | 186 | size = readl(FLDTCNTR(flctl)) >> 24; |
180 | if (size & 0xFF) | 187 | if ((size & 0xFF) == 4) |
181 | return 0; /* success */ | 188 | return state; |
189 | |||
190 | /* check if a correction code has been calculated */ | ||
191 | if (!(readl(FL4ECCCR(flctl)) & _4ECCEND)) { | ||
192 | /* | ||
193 | * either we wait for the fifo to be filled or a | ||
194 | * correction pattern is being generated | ||
195 | */ | ||
196 | udelay(1); | ||
197 | continue; | ||
198 | } | ||
182 | 199 | ||
183 | if (readl(FL4ECCCR(flctl)) & _4ECCFA) | 200 | /* check for an uncorrectable error */ |
184 | return 1; /* can't correct */ | 201 | if (readl(FL4ECCCR(flctl)) & _4ECCFA) { |
202 | /* check if we face a non-empty page */ | ||
203 | for (i = 0; i < 512; i++) { | ||
204 | if (flctl->done_buff[i] != 0xff) { | ||
205 | state = FL_ERROR; /* can't correct */ | ||
206 | break; | ||
207 | } | ||
208 | } | ||
185 | 209 | ||
186 | udelay(1); | 210 | if (state == FL_SUCCESS) |
187 | if (!(readl(FL4ECCCR(flctl)) & _4ECCEND)) | 211 | dev_dbg(&flctl->pdev->dev, |
212 | "reading empty sector %d, ecc error ignored\n", | ||
213 | sector_number); | ||
214 | |||
215 | writel(0, FL4ECCCR(flctl)); | ||
188 | continue; | 216 | continue; |
217 | } | ||
189 | 218 | ||
190 | /* start error correction */ | 219 | /* start error correction */ |
191 | ecc_reg[0] = FL4ECCRESULT0(flctl); | 220 | ecc_reg[0] = FL4ECCRESULT0(flctl); |
@@ -194,28 +223,26 @@ static int wait_recfifo_ready(struct sh_flctl *flctl, int sector_number) | |||
194 | ecc_reg[3] = FL4ECCRESULT3(flctl); | 223 | ecc_reg[3] = FL4ECCRESULT3(flctl); |
195 | 224 | ||
196 | for (i = 0; i < 3; i++) { | 225 | for (i = 0; i < 3; i++) { |
226 | uint8_t org; | ||
227 | int index; | ||
228 | |||
197 | data = readl(ecc_reg[i]); | 229 | data = readl(ecc_reg[i]); |
198 | if (data != INIT_FL4ECCRESULT_VAL && !checked[i]) { | ||
199 | uint8_t org; | ||
200 | int index; | ||
201 | |||
202 | if (flctl->page_size) | ||
203 | index = (512 * sector_number) + | ||
204 | (data >> 16); | ||
205 | else | ||
206 | index = data >> 16; | ||
207 | |||
208 | org = flctl->done_buff[index]; | ||
209 | flctl->done_buff[index] = org ^ (data & 0xFF); | ||
210 | checked[i] = 1; | ||
211 | } | ||
212 | } | ||
213 | 230 | ||
231 | if (flctl->page_size) | ||
232 | index = (512 * sector_number) + | ||
233 | (data >> 16); | ||
234 | else | ||
235 | index = data >> 16; | ||
236 | |||
237 | org = flctl->done_buff[index]; | ||
238 | flctl->done_buff[index] = org ^ (data & 0xFF); | ||
239 | } | ||
240 | state = FL_REPAIRABLE; | ||
214 | writel(0, FL4ECCCR(flctl)); | 241 | writel(0, FL4ECCCR(flctl)); |
215 | } | 242 | } |
216 | 243 | ||
217 | timeout_error(flctl, __func__); | 244 | timeout_error(flctl, __func__); |
218 | return 1; /* timeout */ | 245 | return FL_TIMEOUT; /* timeout */ |
219 | } | 246 | } |
220 | 247 | ||
221 | static void wait_wecfifo_ready(struct sh_flctl *flctl) | 248 | static void wait_wecfifo_ready(struct sh_flctl *flctl) |
@@ -259,20 +286,23 @@ static void read_fiforeg(struct sh_flctl *flctl, int rlen, int offset) | |||
259 | } | 286 | } |
260 | } | 287 | } |
261 | 288 | ||
262 | static int read_ecfiforeg(struct sh_flctl *flctl, uint8_t *buff, int sector) | 289 | static enum flctl_ecc_res_t read_ecfiforeg |
290 | (struct sh_flctl *flctl, uint8_t *buff, int sector) | ||
263 | { | 291 | { |
264 | int i; | 292 | int i; |
293 | enum flctl_ecc_res_t res; | ||
265 | unsigned long *ecc_buf = (unsigned long *)buff; | 294 | unsigned long *ecc_buf = (unsigned long *)buff; |
266 | void *fifo_addr = (void *)FLECFIFO(flctl); | ||
267 | 295 | ||
268 | for (i = 0; i < 4; i++) { | 296 | res = wait_recfifo_ready(flctl , sector); |
269 | if (wait_recfifo_ready(flctl , sector)) | 297 | |
270 | return 1; | 298 | if (res != FL_ERROR) { |
271 | ecc_buf[i] = readl(fifo_addr); | 299 | for (i = 0; i < 4; i++) { |
272 | ecc_buf[i] = be32_to_cpu(ecc_buf[i]); | 300 | ecc_buf[i] = readl(FLECFIFO(flctl)); |
301 | ecc_buf[i] = be32_to_cpu(ecc_buf[i]); | ||
302 | } | ||
273 | } | 303 | } |
274 | 304 | ||
275 | return 0; | 305 | return res; |
276 | } | 306 | } |
277 | 307 | ||
278 | static void write_fiforeg(struct sh_flctl *flctl, int rlen, int offset) | 308 | static void write_fiforeg(struct sh_flctl *flctl, int rlen, int offset) |
@@ -367,6 +397,7 @@ static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr) | |||
367 | { | 397 | { |
368 | struct sh_flctl *flctl = mtd_to_flctl(mtd); | 398 | struct sh_flctl *flctl = mtd_to_flctl(mtd); |
369 | int sector, page_sectors; | 399 | int sector, page_sectors; |
400 | enum flctl_ecc_res_t ecc_result; | ||
370 | 401 | ||
371 | page_sectors = flctl->page_size ? 4 : 1; | 402 | page_sectors = flctl->page_size ? 4 : 1; |
372 | 403 | ||
@@ -382,17 +413,27 @@ static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr) | |||
382 | start_translation(flctl); | 413 | start_translation(flctl); |
383 | 414 | ||
384 | for (sector = 0; sector < page_sectors; sector++) { | 415 | for (sector = 0; sector < page_sectors; sector++) { |
385 | int ret; | ||
386 | read_fiforeg(flctl, 512, 512 * sector); | 416 | read_fiforeg(flctl, 512, 512 * sector); |
387 | 417 | ||
388 | ret = read_ecfiforeg(flctl, | 418 | ecc_result = read_ecfiforeg(flctl, |
389 | &flctl->done_buff[mtd->writesize + 16 * sector], | 419 | &flctl->done_buff[mtd->writesize + 16 * sector], |
390 | sector); | 420 | sector); |
391 | 421 | ||
392 | if (ret) | 422 | switch (ecc_result) { |
393 | flctl->hwecc_cant_correct[sector] = 1; | 423 | case FL_REPAIRABLE: |
394 | 424 | dev_info(&flctl->pdev->dev, | |
395 | writel(0x0, FL4ECCCR(flctl)); | 425 | "applied ecc on page 0x%x", page_addr); |
426 | flctl->mtd.ecc_stats.corrected++; | ||
427 | break; | ||
428 | case FL_ERROR: | ||
429 | dev_warn(&flctl->pdev->dev, | ||
430 | "page 0x%x contains corrupted data\n", | ||
431 | page_addr); | ||
432 | flctl->mtd.ecc_stats.failed++; | ||
433 | break; | ||
434 | default: | ||
435 | ; | ||
436 | } | ||
396 | } | 437 | } |
397 | 438 | ||
398 | wait_completion(flctl); | 439 | wait_completion(flctl); |
diff --git a/include/linux/mtd/sh_flctl.h b/include/linux/mtd/sh_flctl.h index 3feaae062feb..01e4b15b280e 100644 --- a/include/linux/mtd/sh_flctl.h +++ b/include/linux/mtd/sh_flctl.h | |||
@@ -129,9 +129,15 @@ | |||
129 | #define _4ECCEND (0x1 << 1) /* 4 symbols end */ | 129 | #define _4ECCEND (0x1 << 1) /* 4 symbols end */ |
130 | #define _4ECCEXST (0x1 << 0) /* 4 symbols exist */ | 130 | #define _4ECCEXST (0x1 << 0) /* 4 symbols exist */ |
131 | 131 | ||
132 | #define INIT_FL4ECCRESULT_VAL 0x03FF03FF | ||
133 | #define LOOP_TIMEOUT_MAX 0x00010000 | 132 | #define LOOP_TIMEOUT_MAX 0x00010000 |
134 | 133 | ||
134 | enum flctl_ecc_res_t { | ||
135 | FL_SUCCESS, | ||
136 | FL_REPAIRABLE, | ||
137 | FL_ERROR, | ||
138 | FL_TIMEOUT | ||
139 | }; | ||
140 | |||
135 | struct sh_flctl { | 141 | struct sh_flctl { |
136 | struct mtd_info mtd; | 142 | struct mtd_info mtd; |
137 | struct nand_chip chip; | 143 | struct nand_chip chip; |
@@ -151,8 +157,6 @@ struct sh_flctl { | |||
151 | uint32_t flcmncr_base; /* base value of FLCMNCR */ | 157 | uint32_t flcmncr_base; /* base value of FLCMNCR */ |
152 | uint32_t flintdmacr_base; /* irq enable bits */ | 158 | uint32_t flintdmacr_base; /* irq enable bits */ |
153 | 159 | ||
154 | int hwecc_cant_correct[4]; | ||
155 | |||
156 | unsigned page_size:1; /* NAND page size (0 = 512, 1 = 2048) */ | 160 | unsigned page_size:1; /* NAND page size (0 = 512, 1 = 2048) */ |
157 | unsigned hwecc:1; /* Hardware ECC (0 = disabled, 1 = enabled) */ | 161 | unsigned hwecc:1; /* Hardware ECC (0 = disabled, 1 = enabled) */ |
158 | unsigned holden:1; /* Hardware has FLHOLDCR and HOLDEN is set */ | 162 | unsigned holden:1; /* Hardware has FLHOLDCR and HOLDEN is set */ |