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>
This commit is contained in:
Bastian Hecht 2012-05-14 14:14:46 +02:00 committed by David Woodhouse
parent 623c55caa3
commit 6667a6d58e
2 changed files with 87 additions and 42 deletions

View file

@ -165,27 +165,56 @@ static void wait_wfifo_ready(struct sh_flctl *flctl)
timeout_error(flctl, __func__); timeout_error(flctl, __func__);
} }
static int wait_recfifo_ready(struct sh_flctl *flctl, int sector_number) static enum flctl_ecc_res_t wait_recfifo_ready
(struct sh_flctl *flctl, int sector_number)
{ {
uint32_t timeout = LOOP_TIMEOUT_MAX; uint32_t timeout = LOOP_TIMEOUT_MAX;
int checked[4];
void __iomem *ecc_reg[4]; void __iomem *ecc_reg[4];
int i; int i;
int state = FL_SUCCESS;
uint32_t data, size; uint32_t data, size;
memset(checked, 0, sizeof(checked)); /*
* First this loops checks in FLDTCNTR if we are ready to read out the
* oob data. This is the case if either all went fine without errors or
* if the bottom part of the loop corrected the errors or marked them as
* uncorrectable and the controller is given time to push the data into
* the FIFO.
*/
while (timeout--) { while (timeout--) {
/* check if all is ok and we can read out the OOB */
size = readl(FLDTCNTR(flctl)) >> 24; size = readl(FLDTCNTR(flctl)) >> 24;
if (size & 0xFF) if ((size & 0xFF) == 4)
return 0; /* success */ return state;
if (readl(FL4ECCCR(flctl)) & _4ECCFA) /* check if a correction code has been calculated */
return 1; /* can't correct */ if (!(readl(FL4ECCCR(flctl)) & _4ECCEND)) {
/*
udelay(1); * either we wait for the fifo to be filled or a
if (!(readl(FL4ECCCR(flctl)) & _4ECCEND)) * correction pattern is being generated
*/
udelay(1);
continue; continue;
}
/* check for an uncorrectable error */
if (readl(FL4ECCCR(flctl)) & _4ECCFA) {
/* check if we face a non-empty page */
for (i = 0; i < 512; i++) {
if (flctl->done_buff[i] != 0xff) {
state = FL_ERROR; /* can't correct */
break;
}
}
if (state == FL_SUCCESS)
dev_dbg(&flctl->pdev->dev,
"reading empty sector %d, ecc error ignored\n",
sector_number);
writel(0, FL4ECCCR(flctl));
continue;
}
/* start error correction */ /* start error correction */
ecc_reg[0] = FL4ECCRESULT0(flctl); ecc_reg[0] = FL4ECCRESULT0(flctl);
@ -194,28 +223,26 @@ static int wait_recfifo_ready(struct sh_flctl *flctl, int sector_number)
ecc_reg[3] = FL4ECCRESULT3(flctl); ecc_reg[3] = FL4ECCRESULT3(flctl);
for (i = 0; i < 3; i++) { for (i = 0; i < 3; i++) {
uint8_t org;
int index;
data = readl(ecc_reg[i]); data = readl(ecc_reg[i]);
if (data != INIT_FL4ECCRESULT_VAL && !checked[i]) {
uint8_t org;
int index;
if (flctl->page_size) if (flctl->page_size)
index = (512 * sector_number) + index = (512 * sector_number) +
(data >> 16); (data >> 16);
else else
index = data >> 16; index = data >> 16;
org = flctl->done_buff[index]; org = flctl->done_buff[index];
flctl->done_buff[index] = org ^ (data & 0xFF); flctl->done_buff[index] = org ^ (data & 0xFF);
checked[i] = 1;
}
} }
state = FL_REPAIRABLE;
writel(0, FL4ECCCR(flctl)); writel(0, FL4ECCCR(flctl));
} }
timeout_error(flctl, __func__); timeout_error(flctl, __func__);
return 1; /* timeout */ return FL_TIMEOUT; /* timeout */
} }
static void wait_wecfifo_ready(struct sh_flctl *flctl) 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)
} }
} }
static int read_ecfiforeg(struct sh_flctl *flctl, uint8_t *buff, int sector) static enum flctl_ecc_res_t read_ecfiforeg
(struct sh_flctl *flctl, uint8_t *buff, int sector)
{ {
int i; int i;
enum flctl_ecc_res_t res;
unsigned long *ecc_buf = (unsigned long *)buff; unsigned long *ecc_buf = (unsigned long *)buff;
void *fifo_addr = (void *)FLECFIFO(flctl);
for (i = 0; i < 4; i++) { res = wait_recfifo_ready(flctl , sector);
if (wait_recfifo_ready(flctl , sector))
return 1; if (res != FL_ERROR) {
ecc_buf[i] = readl(fifo_addr); for (i = 0; i < 4; i++) {
ecc_buf[i] = be32_to_cpu(ecc_buf[i]); ecc_buf[i] = readl(FLECFIFO(flctl));
ecc_buf[i] = be32_to_cpu(ecc_buf[i]);
}
} }
return 0; return res;
} }
static void write_fiforeg(struct sh_flctl *flctl, int rlen, int offset) 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)
{ {
struct sh_flctl *flctl = mtd_to_flctl(mtd); struct sh_flctl *flctl = mtd_to_flctl(mtd);
int sector, page_sectors; int sector, page_sectors;
enum flctl_ecc_res_t ecc_result;
page_sectors = flctl->page_size ? 4 : 1; page_sectors = flctl->page_size ? 4 : 1;
@ -382,17 +413,27 @@ static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr)
start_translation(flctl); start_translation(flctl);
for (sector = 0; sector < page_sectors; sector++) { for (sector = 0; sector < page_sectors; sector++) {
int ret;
read_fiforeg(flctl, 512, 512 * sector); read_fiforeg(flctl, 512, 512 * sector);
ret = read_ecfiforeg(flctl, ecc_result = read_ecfiforeg(flctl,
&flctl->done_buff[mtd->writesize + 16 * sector], &flctl->done_buff[mtd->writesize + 16 * sector],
sector); sector);
if (ret) switch (ecc_result) {
flctl->hwecc_cant_correct[sector] = 1; case FL_REPAIRABLE:
dev_info(&flctl->pdev->dev,
writel(0x0, FL4ECCCR(flctl)); "applied ecc on page 0x%x", page_addr);
flctl->mtd.ecc_stats.corrected++;
break;
case FL_ERROR:
dev_warn(&flctl->pdev->dev,
"page 0x%x contains corrupted data\n",
page_addr);
flctl->mtd.ecc_stats.failed++;
break;
default:
;
}
} }
wait_completion(flctl); wait_completion(flctl);

View file

@ -129,9 +129,15 @@
#define _4ECCEND (0x1 << 1) /* 4 symbols end */ #define _4ECCEND (0x1 << 1) /* 4 symbols end */
#define _4ECCEXST (0x1 << 0) /* 4 symbols exist */ #define _4ECCEXST (0x1 << 0) /* 4 symbols exist */
#define INIT_FL4ECCRESULT_VAL 0x03FF03FF
#define LOOP_TIMEOUT_MAX 0x00010000 #define LOOP_TIMEOUT_MAX 0x00010000
enum flctl_ecc_res_t {
FL_SUCCESS,
FL_REPAIRABLE,
FL_ERROR,
FL_TIMEOUT
};
struct sh_flctl { struct sh_flctl {
struct mtd_info mtd; struct mtd_info mtd;
struct nand_chip chip; struct nand_chip chip;
@ -151,8 +157,6 @@ struct sh_flctl {
uint32_t flcmncr_base; /* base value of FLCMNCR */ uint32_t flcmncr_base; /* base value of FLCMNCR */
uint32_t flintdmacr_base; /* irq enable bits */ uint32_t flintdmacr_base; /* irq enable bits */
int hwecc_cant_correct[4];
unsigned page_size:1; /* NAND page size (0 = 512, 1 = 2048) */ unsigned page_size:1; /* NAND page size (0 = 512, 1 = 2048) */
unsigned hwecc:1; /* Hardware ECC (0 = disabled, 1 = enabled) */ unsigned hwecc:1; /* Hardware ECC (0 = disabled, 1 = enabled) */
unsigned holden:1; /* Hardware has FLHOLDCR and HOLDEN is set */ unsigned holden:1; /* Hardware has FLHOLDCR and HOLDEN is set */