diff options
author | Hugo Villeneuve <hugo.villeneuve@lyrtech.com> | 2008-08-30 17:06:55 -0400 |
---|---|---|
committer | Scott Wood <scottwood@freescale.com> | 2008-09-10 11:40:30 -0500 |
commit | 9b05aa788bfdd3264ff1bc9418cb19550a7234e4 (patch) | |
tree | ca6159b8743ad6d92abb021e003b3978cf16427b /cpu/arm926ejs/davinci | |
parent | 0b7c5639891f4103a0e31ec7ae0beb3e97ed3836 (diff) |
ARM DaVinci: Fix broken HW ECC for large page NAND.
Based on original patch by Bernard Blackham <bernard@largestprime.net>
U-boot's HW ECC support for large page NAND on Davinci is completely
broken. Some kernels, such as the 2.6.10 one supported by
MontaVista for DaVinci, rely upon this broken behaviour as they
share the same code for ECCs. In the existing scheme, error
detection *might* work on large page, but error correction
definitely does not. Small page ECC correction works, but the
format is not compatible with the mainline git kernel.
This patch adds ECC code that matches what is currently in the
Davinci git repository (since NAND support was added in 2.6.24).
This makes the ECC and OOB layout written by u-boot compatible with
Linux for both small page and large page devices and fixes ECC
correction for large page devices.
The old behaviour can be restored by defining the macro
CFG_DAVINCI_BROKEN_ECC, which is undefined by default.
Signed-off-by: Hugo Villeneuve <hugo.villeneuve@lyrtech.com>
Acked-by: Sergey Kubushyn <ksi@koi8.net>
Signed-off-by: Scott Wood <scottwood@freescale.com>
Diffstat (limited to 'cpu/arm926ejs/davinci')
-rw-r--r-- | cpu/arm926ejs/davinci/nand.c | 81 |
1 files changed, 76 insertions, 5 deletions
diff --git a/cpu/arm926ejs/davinci/nand.c b/cpu/arm926ejs/davinci/nand.c index 5a1da633d3a..f7cf0c9ec26 100644 --- a/cpu/arm926ejs/davinci/nand.c +++ b/cpu/arm926ejs/davinci/nand.c @@ -88,6 +88,9 @@ static void nand_davinci_select_chip(struct mtd_info *mtd, int chip) } #ifdef CFG_NAND_HW_ECC +#ifdef CFG_DAVINCI_BROKEN_ECC +/* Linux-compatible ECC uses MTD defaults. */ +/* These layouts are not compatible with Linux or RBL/UBL. */ #ifdef CFG_NAND_LARGEPAGE static struct nand_ecclayout davinci_nand_ecclayout = { .eccbytes = 12, @@ -112,6 +115,7 @@ static struct nand_ecclayout davinci_nand_ecclayout = { #else #error "Either CFG_NAND_LARGEPAGE or CFG_NAND_SMALLPAGE must be defined!" #endif +#endif /* CFG_DAVINCI_BROKEN_ECC */ static void nand_davinci_enable_hwecc(struct mtd_info *mtd, int mode) { @@ -150,6 +154,15 @@ static u_int32_t nand_davinci_readecc(struct mtd_info *mtd, u_int32_t region) static int nand_davinci_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) { u_int32_t tmp; +#ifdef CFG_DAVINCI_BROKEN_ECC + /* + * This is not how you should read ECCs on large page Davinci devices. + * The region parameter gets you ECCs for flash chips on different chip + * selects, not the 4x512 byte pages in a 2048 byte page. + * + * Preserved for backwards compatibility though. + */ + int region, n; struct nand_chip *this = mtd->priv; @@ -163,9 +176,26 @@ static int nand_davinci_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u *ecc_code++ = ((tmp >> 8) & 0x0f) | ((tmp >> 20) & 0xf0); region++; } +#else + const int region = 1; + + tmp = nand_davinci_readecc(mtd, region); + + /* Squeeze 4 bytes ECC into 3 bytes by removing RESERVED bits + * and shifting. RESERVED bits are 31 to 28 and 15 to 12. */ + tmp = (tmp & 0x00000fff) | ((tmp & 0x0fff0000) >> 4); + + /* Invert so that erased block ECC is correct */ + tmp = ~tmp; + + *ecc_code++ = tmp; + *ecc_code++ = tmp >> 8; + *ecc_code++ = tmp >> 16; +#endif /* CFG_DAVINCI_BROKEN_ECC */ return(0); } +#ifdef CFG_DAVINCI_BROKEN_ECC static void nand_davinci_gen_true_ecc(u_int8_t *ecc_buf) { u_int32_t tmp = ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xf0) << 20) | ((ecc_buf[2] & 0x0f) << 8); @@ -282,13 +312,14 @@ static int nand_davinci_compare_ecc(u_int8_t *ecc_nand, u_int8_t *ecc_calc, u_in return(-1); } } +#endif /* CFG_DAVINCI_BROKEN_ECC */ static int nand_davinci_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc) { - struct nand_chip *this; + struct nand_chip *this = mtd->priv; +#ifdef CFG_DAVINCI_BROKEN_ECC int block_count = 0, i, rc; - this = mtd->priv; block_count = (this->ecc.size/512); for (i = 0; i < block_count; i++) { if (memcmp(read_ecc, calc_ecc, 3) != 0) { @@ -301,9 +332,44 @@ static int nand_davinci_correct_data(struct mtd_info *mtd, u_char *dat, u_char * calc_ecc += 3; dat += 512; } +#else + u_int32_t ecc_nand = read_ecc[0] | (read_ecc[1] << 8) | + (read_ecc[2] << 16); + u_int32_t ecc_calc = calc_ecc[0] | (calc_ecc[1] << 8) | + (calc_ecc[2] << 16); + u_int32_t diff = ecc_calc ^ ecc_nand; + + if (diff) { + if ((((diff >> 12) ^ diff) & 0xfff) == 0xfff) { + /* Correctable error */ + if ((diff >> (12 + 3)) < this->ecc.size) { + uint8_t find_bit = 1 << ((diff >> 12) & 7); + uint32_t find_byte = diff >> (12 + 3); + + dat[find_byte] ^= find_bit; + MTDDEBUG(MTD_DEBUG_LEVEL0, "Correcting single " + "bit ECC error at offset: %d, bit: " + "%d\n", find_byte, find_bit); + return 1; + } else { + return -1; + } + } else if (!(diff & (diff - 1))) { + /* Single bit ECC error in the ECC itself, + nothing to fix */ + MTDDEBUG(MTD_DEBUG_LEVEL0, "Single bit ECC error in " + "ECC.\n"); + return 1; + } else { + /* Uncorrectable error */ + MTDDEBUG(MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n"); + return -1; + } + } +#endif /* CFG_DAVINCI_BROKEN_ECC */ return(0); } -#endif +#endif /* CFG_NAND_HW_ECC */ static int nand_davinci_dev_ready(struct mtd_info *mtd) { @@ -370,6 +436,8 @@ int board_nand_init(struct nand_chip *nand) #endif #ifdef CFG_NAND_HW_ECC nand->ecc.mode = NAND_ECC_HW; +#ifdef CFG_DAVINCI_BROKEN_ECC + nand->ecc.layout = &davinci_nand_ecclayout; #ifdef CFG_NAND_LARGEPAGE nand->ecc.size = 2048; nand->ecc.bytes = 12; @@ -379,13 +447,16 @@ int board_nand_init(struct nand_chip *nand) #else #error "Either CFG_NAND_LARGEPAGE or CFG_NAND_SMALLPAGE must be defined!" #endif - nand->ecc.layout = &davinci_nand_ecclayout; +#else + nand->ecc.size = 512; + nand->ecc.bytes = 3; +#endif /* CFG_DAVINCI_BROKEN_ECC */ nand->ecc.calculate = nand_davinci_calculate_ecc; nand->ecc.correct = nand_davinci_correct_data; nand->ecc.hwctl = nand_davinci_enable_hwecc; #else nand->ecc.mode = NAND_ECC_SOFT; -#endif +#endif /* CFG_NAND_HW_ECC */ /* Set address of hardware control function */ nand->cmd_ctrl = nand_davinci_hwcontrol; |