[U-Boot] [PATH] at91: add hwecc method for nand

Nikolay Petukhov nikolaypetukhov at gmail.com
Thu Mar 18 07:55:29 CET 2010


Hi, all.

This is a patch to use the hardware ECC controller of
the AT91SAM9260 for the AT91 nand. Taken from the kernel 2.6.33.

To use this it is necessary to add two definitions in config:
CONFIG_ATMEL_NAND_HWECC
CONFIG_SYS_NAND_ECC_BASE (AT91_ECC for the AT91SAM9260 and AT91_ECC0
for the AT91SAM9263 boards)

It has been tested only on AT91SAM9260-EK.
u-boot version : 2009.11
kernel version : 2.6.27(32)

Results of read bandwidth:
u-boot with SWECC: 1MB/s
u-boot with HWECC: 3MB/s
linux with HWECC: 6MB/s

Signed-off-by: Nikolay Petukhov <Nikolay.Petukhov at gmail.com>

diff -uprN u-boot-2009.11/drivers/mtd/nand/atmel_nand.c
u-boot-2009.11-at91-nand-hwecc/drivers/mtd/nand/atmel_nand.c
--- u-boot-2009.11/drivers/mtd/nand/atmel_nand.c	2009-12-16
03:20:54.000000000 +0500
+++ u-boot-2009.11-at91-nand-hwecc/drivers/mtd/nand/atmel_nand.c	2010-03-18
11:15:01.000000000 +0500
@@ -31,6 +31,210 @@

 #include <nand.h>

+#ifdef CONFIG_ATMEL_NAND_HWECC
+
+/* Register access macros */
+#define ecc_readl(add, reg)				\
+	readl(AT91_BASE_SYS + add + ATMEL_ECC_##reg)
+#define ecc_writel(add, reg, value)			\
+	writel((value), AT91_BASE_SYS + add + ATMEL_ECC_##reg)
+
+#include "atmel_nand_ecc.h"	/* Hardware ECC registers */
+
+/* oob layout for large page size
+ * bad block info is on bytes 0 and 1
+ * the bytes have to be consecutives to avoid
+ * several NAND_CMD_RNDOUT during read
+ */
+static struct nand_ecclayout atmel_oobinfo_large = {
+	.eccbytes = 4,
+	.eccpos = {60, 61, 62, 63},
+	.oobfree = {
+		{2, 58}
+	},
+};
+
+/* oob layout for small page size
+ * bad block info is on bytes 4 and 5
+ * the bytes have to be consecutives to avoid
+ * several NAND_CMD_RNDOUT during read
+ */
+static struct nand_ecclayout atmel_oobinfo_small = {
+	.eccbytes = 4,
+	.eccpos = {0, 1, 2, 3},
+	.oobfree = {
+		{6, 10}
+	},
+};
+
+/*
+ * Calculate HW ECC
+ *
+ * function called after a write
+ *
+ * mtd:        MTD block structure
+ * dat:        raw data (unused)
+ * ecc_code:   buffer for ECC
+ */
+static int atmel_nand_calculate(struct mtd_info *mtd,
+		const u_char *dat, unsigned char *ecc_code)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	unsigned int ecc_value;
+
+	/* get the first 2 ECC bytes */
+	ecc_value = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, PR);
+
+	ecc_code[0] = ecc_value & 0xFF;
+	ecc_code[1] = (ecc_value >> 8) & 0xFF;
+
+	/* get the last 2 ECC bytes */
+	ecc_value = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, NPR) & ATMEL_ECC_NPARITY;
+
+	ecc_code[2] = ecc_value & 0xFF;
+	ecc_code[3] = (ecc_value >> 8) & 0xFF;
+
+	return 0;
+}
+
+/*
+ * HW ECC read page function
+ *
+ * mtd:        mtd info structure
+ * chip:       nand chip info structure
+ * buf:        buffer to store read data
+ */
+static int atmel_nand_read_page(struct mtd_info *mtd,
+		struct nand_chip *chip, uint8_t *buf, int page)
+{
+	int eccsize = chip->ecc.size;
+	int eccbytes = chip->ecc.bytes;
+	uint32_t *eccpos = chip->ecc.layout->eccpos;
+	uint8_t *p = buf;
+	uint8_t *oob = chip->oob_poi;
+	uint8_t *ecc_pos;
+	int stat;
+
+	/* read the page */
+	chip->read_buf(mtd, p, eccsize);
+
+	/* move to ECC position if needed */
+	if (eccpos[0] != 0) {
+		/* This only works on large pages
+		 * because the ECC controller waits for
+		 * NAND_CMD_RNDOUTSTART after the
+		 * NAND_CMD_RNDOUT.
+		 * anyway, for small pages, the eccpos[0] == 0
+		 */
+		chip->cmdfunc(mtd, NAND_CMD_RNDOUT,
+				mtd->writesize + eccpos[0], -1);
+	}
+
+	/* the ECC controller needs to read the ECC just after the data */
+	ecc_pos = oob + eccpos[0];
+	chip->read_buf(mtd, ecc_pos, eccbytes);
+
+	/* check if there's an error */
+	stat = chip->ecc.correct(mtd, p, oob, NULL);
+
+	if (stat < 0)
+		mtd->ecc_stats.failed++;
+	else
+		mtd->ecc_stats.corrected += stat;
+
+	/* get back to oob start (end of page) */
+	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize, -1);
+
+	/* read the oob */
+	chip->read_buf(mtd, oob, mtd->oobsize);
+
+	return 0;
+}
+
+/*
+ * HW ECC Correction
+ *
+ * function called after a read
+ *
+ * mtd:        MTD block structure
+ * dat:        raw data read from the chip
+ * read_ecc:   ECC from the chip (unused)
+ * isnull:     unused
+ *
+ * Detect and correct a 1 bit error for a page
+ */
+static int atmel_nand_correct(struct mtd_info *mtd, u_char *dat,
+		u_char *read_ecc, u_char *isnull)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	//struct atmel_nand_host *host = nand_chip->priv;
+	unsigned int ecc_status, ecc_parity, ecc_mode;
+	unsigned int ecc_word, ecc_bit;
+
+	/* get the status from the Status Register */
+	ecc_status = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, SR);
+
+	/* if there's no error */
+	if (likely(!(ecc_status & ATMEL_ECC_RECERR)))
+		return 0;
+
+	/* get error bit offset (4 bits) */
+	ecc_bit = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, PR) & ATMEL_ECC_BITADDR;
+	/* get word address (12 bits) */
+	ecc_word = ecc_readl(CONFIG_SYS_NAND_ECC_BASE, PR) & ATMEL_ECC_WORDADDR;
+	ecc_word >>= 4;
+
+	/* if there are multiple errors */
+	if (ecc_status & ATMEL_ECC_MULERR) {
+		/* check if it is a freshly erased block
+		 * (filled with 0xff) */
+		if ((ecc_bit == ATMEL_ECC_BITADDR)
+				&& (ecc_word == (ATMEL_ECC_WORDADDR >> 4))) {
+			/* the block has just been erased, return OK */
+			return 0;
+		}
+		/* it doesn't seems to be a freshly
+		 * erased block.
+		 * We can't correct so many errors */
+		printk(KERN_WARNING "atmel_nand : multiple errors detected."
+				" Unable to correct.\n");
+		return -EIO;
+	}
+
+	/* if there's a single bit error : we can correct it */
+	if (ecc_status & ATMEL_ECC_ECCERR) {
+		/* there's nothing much to do here.
+		 * the bit error is on the ECC itself.
+		 */
+		printk(KERN_WARNING "atmel_nand : one bit error on ECC code."
+				" Nothing to correct\n");
+		return 0;
+	}
+
+	printk(KERN_WARNING "atmel_nand : one bit error on data."
+			" (word offset in the page :"
+			" 0x%x bit offset : 0x%x)\n",
+			ecc_word, ecc_bit);
+	/* correct the error */
+	if (nand_chip->options & NAND_BUSWIDTH_16) {
+		/* 16 bits words */
+		((unsigned short *) dat)[ecc_word] ^= (1 << ecc_bit);
+	} else {
+		/* 8 bits words */
+		dat[ecc_word] ^= (1 << ecc_bit);
+	}
+	printk(KERN_WARNING "atmel_nand : error corrected\n");
+	return 1;
+}
+
+/*
+ * Enable HW ECC : unused on most chips
+ */
+static void atmel_nand_hwctl(struct mtd_info *mtd, int mode)
+{
+}
+#endif
+
 static void at91_nand_hwcontrol(struct mtd_info *mtd,
 					 int cmd, unsigned int ctrl)
 {
@@ -64,6 +268,11 @@ static int at91_nand_ready(struct mtd_in

 int board_nand_init(struct nand_chip *nand)
 {
+#ifdef CONFIG_ATMEL_NAND_HWECC
+	static int chip_nr = 0;
+	struct mtd_info *mtd;
+#endif
+
 	nand->ecc.mode = NAND_ECC_SOFT;
 #ifdef CONFIG_SYS_NAND_DBW_16
 	nand->options = NAND_BUSWIDTH_16;
@@ -74,5 +283,62 @@ int board_nand_init(struct nand_chip *na
 #endif
 	nand->chip_delay = 20;

+#ifdef CONFIG_ATMEL_NAND_HWECC
+	nand->ecc.mode = NAND_ECC_HW;
+	nand->ecc.calculate = atmel_nand_calculate;
+	nand->ecc.correct = atmel_nand_correct;
+	nand->ecc.hwctl = atmel_nand_hwctl;
+	nand->ecc.read_page = atmel_nand_read_page;
+	nand->ecc.bytes = 4;
+#endif
+
+#ifdef CONFIG_ATMEL_NAND_HWECC
+	mtd = &nand_info[chip_nr++];
+	mtd->priv = nand;
+
+	/* Detect NAND chips */
+	if (nand_scan_ident(mtd, 1)) {
+		printk(KERN_WARNING "NAND Flash not found !\n");
+		return -ENXIO;
+	}
+
+	if (nand->ecc.mode == NAND_ECC_HW) {
+		/* ECC is calculated for the whole page (1 step) */
+		nand->ecc.size = mtd->writesize;
+
+		/* set ECC page size and oob layout */
+		switch (mtd->writesize) {
+		case 512:
+			nand->ecc.layout = &atmel_oobinfo_small;
+			ecc_writel(CONFIG_SYS_NAND_ECC_BASE, MR, ATMEL_ECC_PAGESIZE_528);
+			break;
+		case 1024:
+			nand->ecc.layout = &atmel_oobinfo_large;
+			ecc_writel(CONFIG_SYS_NAND_ECC_BASE, MR, ATMEL_ECC_PAGESIZE_1056);
+			break;
+		case 2048:
+			nand->ecc.layout = &atmel_oobinfo_large;
+			ecc_writel(CONFIG_SYS_NAND_ECC_BASE, MR, ATMEL_ECC_PAGESIZE_2112);
+			break;
+		case 4096:
+			nand->ecc.layout = &atmel_oobinfo_large;
+			ecc_writel(CONFIG_SYS_NAND_ECC_BASE, MR, ATMEL_ECC_PAGESIZE_4224);
+			break;
+		default:
+			/* page size not handled by HW ECC */
+			/* switching back to soft ECC */
+			nand->ecc.mode = NAND_ECC_SOFT;
+			nand->ecc.calculate = NULL;
+			nand->ecc.correct = NULL;
+			nand->ecc.hwctl = NULL;
+			nand->ecc.read_page = NULL;
+			nand->ecc.postpad = 0;
+			nand->ecc.prepad = 0;
+			nand->ecc.bytes = 0;
+			break;
+		}
+	}
+#endif
+
 	return 0;
 }
diff -uprN u-boot-2009.11/drivers/mtd/nand/atmel_nand_ecc.h
u-boot-2009.11-at91-nand-hwecc/drivers/mtd/nand/atmel_nand_ecc.h
--- u-boot-2009.11/drivers/mtd/nand/atmel_nand_ecc.h	1970-01-01
05:00:00.000000000 +0500
+++ u-boot-2009.11-at91-nand-hwecc/drivers/mtd/nand/atmel_nand_ecc.h	2010-03-18
11:15:01.000000000 +0500
@@ -0,0 +1,36 @@
+/*
+ * Error Corrected Code Controller (ECC) - System peripherals regsters.
+ * Based on AT91SAM9260 datasheet revision B.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef ATMEL_NAND_ECC_H
+#define ATMEL_NAND_ECC_H
+
+#define ATMEL_ECC_CR		0x00			/* Control register */
+#define		ATMEL_ECC_RST		(1 << 0)		/* Reset parity */
+
+#define ATMEL_ECC_MR		0x04			/* Mode register */
+#define		ATMEL_ECC_PAGESIZE	(3 << 0)		/* Page Size */
+#define			ATMEL_ECC_PAGESIZE_528		(0)
+#define			ATMEL_ECC_PAGESIZE_1056		(1)
+#define			ATMEL_ECC_PAGESIZE_2112		(2)
+#define			ATMEL_ECC_PAGESIZE_4224		(3)
+
+#define ATMEL_ECC_SR		0x08			/* Status register */
+#define		ATMEL_ECC_RECERR		(1 << 0)		/* Recoverable Error */
+#define		ATMEL_ECC_ECCERR		(1 << 1)		/* ECC Single Bit Error */
+#define		ATMEL_ECC_MULERR		(1 << 2)		/* Multiple Errors */
+
+#define ATMEL_ECC_PR		0x0c			/* Parity register */
+#define		ATMEL_ECC_BITADDR	(0xf << 0)		/* Bit Error Address */
+#define		ATMEL_ECC_WORDADDR	(0xfff << 4)		/* Word Error Address */
+
+#define ATMEL_ECC_NPR		0x10			/* NParity register */
+#define		ATMEL_ECC_NPARITY	(0xffff << 0)		/* NParity */
+
+#endif

--
Nikolay


More information about the U-Boot mailing list