[U-Boot-Users] [PATCH RFC] ARM: Davinci: NAND fix for large page ECC and linux compatibility

Bernard Blackham bernard at largestprime.net
Fri Jun 27 07:12:00 CEST 2008


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 code depends on a #define CFG_LINUX_COMPATIBLE_ECC, which is
undefined by default, making the default state backwards compatible.
I have verified this by compiling without the #define and producing
a binary byte-for-byte identical to one without this patch.

[NOTE: I have not yet been able to get my hands on a board with
small-page NAND to test, but large page does work. If anybody is
interested in testing it, please do and let me know if it works for
you (i.e. uboot with this patch and davinci git kernel can
read/write the same NAND).]

Signed-off-by: Bernard Blackham <bernard at largestprime.net>

---
 cpu/arm926ejs/davinci/nand.c    |   79 ++++++++++++++++++++++++++++++++++++++--
 include/configs/davinci_dvevm.h |   12 ++++++
 2 files changed, 89 insertions(+), 2 deletions(-)

Index: u-boot-1.3.3/cpu/arm926ejs/davinci/nand.c
===================================================================
--- u-boot-1.3.3.orig/cpu/arm926ejs/davinci/nand.c	2008-05-19 18:47:11.000000000 +0800
+++ u-boot-1.3.3/cpu/arm926ejs/davinci/nand.c	2008-06-27 13:04:03.000000000 +0800
@@ -87,6 +87,10 @@ static void nand_davinci_select_chip(str
 }
 
 #ifdef CFG_NAND_HW_ECC
+
+#ifndef CFG_LINUX_COMPATIBLE_ECC
+/* Linux-compatible ECC uses MTD defaults. */
+/* These layouts are not compatible with Linux or RBL/UBL. */
 #ifdef CFG_NAND_LARGEPAGE
 static struct nand_oobinfo davinci_nand_oobinfo = {
 	.useecc = MTD_NANDECC_AUTOPLACE,
@@ -104,6 +108,7 @@ static struct nand_oobinfo davinci_nand_
 #else
 #error "Either CFG_NAND_LARGEPAGE or CFG_NAND_SMALLPAGE must be defined!"
 #endif
+#endif
 
 static void nand_davinci_enable_hwecc(struct mtd_info *mtd, int mode)
 {
@@ -141,12 +146,29 @@ static u_int32_t nand_davinci_readecc(st
 
 static int nand_davinci_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
 {
+#ifdef CFG_LINUX_COMPATIBLE_ECC
+	unsigned int ecc_val = nand_davinci_readecc(mtd, 1);
+	/* squeeze 0 middle bits out so that it fits in 3 bytes */
+	unsigned int tmp = (ecc_val&0x0fff)|((ecc_val&0x0fff0000)>>4);
+	/* invert so that erased block ecc is correct */
+	tmp = ~tmp;
+	ecc_code[0] = (u_char)(tmp);
+	ecc_code[1] = (u_char)(tmp >> 8);
+	ecc_code[2] = (u_char)(tmp >> 16);
+#else
 	u_int32_t		tmp;
 	int			region, n;
 	struct nand_chip	*this = mtd->priv;
 
 	n = (this->eccmode == NAND_ECC_HW12_2048) ? 4 : 1;
 
+	/*
+	 * 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.
+	 */
 	region = 1;
 	while (n--) {
 		tmp = nand_davinci_readecc(mtd, region);
@@ -155,9 +177,51 @@ static int nand_davinci_calculate_ecc(st
 		*ecc_code++ = ((tmp >> 8) & 0x0f) | ((tmp >> 20) & 0xf0);
 		region++;
 	}
+#endif
+
 	return(0);
 }
 
+#ifdef CFG_LINUX_COMPATIBLE_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 *chip = mtd->priv;
+	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)) < chip->eccsize) {
+				uint8_t find_bit = 1 << ((diff>>12)&7);
+				uint32_t find_byte = diff>>(12+3);
+				dat[find_byte] ^= find_bit;
+				DEBUG (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 */
+			DEBUG (MTD_DEBUG_LEVEL0, "Single bit ECC error in ECC.\n");
+			return 1;
+		} else {
+			/* Uncorrectable error */
+			DEBUG (MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n");
+			return -1;
+		}
+
+	}
+	return 0;
+}
+
+#else
+
 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);
@@ -291,7 +355,9 @@ static int nand_davinci_correct_data(str
 	}
 	return(0);
 }
-#endif
+#endif /* CFG_LINUX_COMPATIBLE_ECC */
+
+#endif /* CFG_NAND_HW_ECC */
 
 static int nand_davinci_dev_ready(struct mtd_info *mtd)
 {
@@ -356,7 +422,13 @@ int board_nand_init(struct nand_chip *na
 #ifdef CFG_NAND_USE_FLASH_BBT
 	nand->options	  = NAND_USE_FLASH_BBT;
 #endif
+
 #ifdef CFG_NAND_HW_ECC
+
+#ifdef CFG_LINUX_COMPATIBLE_ECC
+	nand->eccmode     = NAND_ECC_HW3_512;
+#else
+
 #ifdef CFG_NAND_LARGEPAGE
 	nand->eccmode     = NAND_ECC_HW12_2048;
 #elif defined(CFG_NAND_SMALLPAGE)
@@ -365,12 +437,15 @@ int board_nand_init(struct nand_chip *na
 #error "Either CFG_NAND_LARGEPAGE or CFG_NAND_SMALLPAGE must be defined!"
 #endif
 	nand->autooob	  = &davinci_nand_oobinfo;
+
+#endif /* CFG_LINUX_COMPATIBLE_ECC */
+
 	nand->calculate_ecc = nand_davinci_calculate_ecc;
 	nand->correct_data  = nand_davinci_correct_data;
 	nand->enable_hwecc  = nand_davinci_enable_hwecc;
 #else
 	nand->eccmode     = NAND_ECC_SOFT;
-#endif
+#endif /* CFG_NAND_HW_ECC */
 
 	/* Set address of hardware control function */
 	nand->hwcontrol = nand_davinci_hwcontrol;
Index: u-boot-1.3.3/include/configs/davinci_dvevm.h
===================================================================
--- u-boot-1.3.3.orig/include/configs/davinci_dvevm.h	2008-05-19 18:47:11.000000000 +0800
+++ u-boot-1.3.3/include/configs/davinci_dvevm.h	2008-06-27 13:04:07.000000000 +0800
@@ -46,6 +46,18 @@
 #define CONFIG_NOR_UART_BOOT
  */
 
+/*
+ * Previous versions of u-boot (1.3.3 and prior) and Montavista Linux kernels
+ * generated bogus ECCs on large-page NAND. Both large and small page NAND ECCs
+ * were incompatible with the Linux davinci git tree (since NAND was integrated
+ * in 2.6.24).
+ * Don't turn this on if you want backwards compatibility.
+ * Do turn this on if you want u-boot to be able to read and write NAND
+ * that can be written or read by the Linux davinci git kernel.
+ *
+#define CFG_LINUX_COMPATIBLE_ECC
+ */
+
 /*=======*/
 /* Board */
 /*=======*/




More information about the U-Boot mailing list