[PATCH] Hardware ECC controller support on AT91SAM9260/3

Richard Genoud rgenoud at adeneo.adetelgroup.com
Wed Oct 17 07:00:25 EDT 2007


This is a patch to use the hardware ECC controller of
the AT91SAM9260 and AT91SAM9263 for the AT91 nand.
On AT91 NAND, there's now a choice between ECC soft,
ECC hard or no ECC (for debug).

It has been tested only on AT91SAM9263-EK for now
(with 8 bits large page NAND).
Any comments are welcome.

kernel version : 2.6.23

Signed-off-by: Richard Genoud <richard.genoud at gmail.com>

---
diff -pruN a/arch/arm/mach-at91/board-sam9260ek.c b/arch/arm/mach-at91/board-sam9260ek.c
--- a/arch/arm/mach-at91/board-sam9260ek.c	2007-09-11 04:50:29.000000000 +0200
+++ b/arch/arm/mach-at91/board-sam9260ek.c	2007-10-11 15:57:46.000000000 +0200
@@ -158,6 +158,9 @@ static struct at91_nand_data __initdata 
 #else
 	.bus_width_16	= 0,
 #endif
+#if defined(CONFIG_MTD_NAND_AT91_ECC_HW)
+	.ecc_base	= AT91_ECC,
+#endif
 };
 

diff -pruN a/arch/arm/mach-at91/board-sam9263ek.c b/arch/arm/mach-at91/board-sam9263ek.c
--- a/arch/arm/mach-at91/board-sam9263ek.c	2007-09-11 04:50:29.000000000 +0200
+++ b/arch/arm/mach-at91/board-sam9263ek.c	2007-10-11 15:57:31.000000000 +0200
@@ -201,6 +201,9 @@ static struct at91_nand_data __initdata 
 #else
 	.bus_width_16	= 0,
 #endif
+#if defined(CONFIG_MTD_NAND_AT91_ECC_HW)
+	.ecc_base	= AT91_ECC0,
+#endif
 };
 

diff -pruN a/drivers/mtd/nand/at91_nand.c b/drivers/mtd/nand/at91_nand.c
--- a/drivers/mtd/nand/at91_nand.c	2007-10-05 12:50:45.000000000 +0200
+++ b/drivers/mtd/nand/at91_nand.c	2007-10-11 15:58:47.000000000 +0200
@@ -9,6 +9,14 @@
  *  Derived from drivers/mtd/spia.c
  *	 Copyright (C) 2000 Steven J. Hill (sjhill at cotw.com)
  *
+ *
+ *  Add Hardware ECC support for AT91SAM9260 / AT91SAM9263
+ *     Richard Genoud (richard.genoud at gmail.com), Adeneo Copyright (C) 2007
+ *
+ *     Derived from Das U-Boot source code (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c)
+ *     (C) Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas
+ *
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
@@ -29,6 +37,34 @@
 #include <asm/arch/board.h>
 #include <asm/arch/gpio.h>
 
+#ifdef CONFIG_MTD_NAND_AT91_ECC_HW
+
+#include <asm/arch/at91_ecc.h> /* AT91SAM9260/3 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 at91_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 at91_oobinfo_small = {
+	.eccbytes = 4,
+	.eccpos = {0, 1, 2, 3},
+	.oobfree = {{6, 10}}
+};
+
+#endif 
+
 struct at91_nand_host {
 	struct nand_chip	nand_chip;
 	struct mtd_info		mtd;
@@ -82,6 +118,173 @@ static void at91_nand_disable(struct at9
 		at91_set_gpio_value(host->board->enable_pin, 1);
 }
 
+#ifdef CONFIG_MTD_NAND_AT91_ECC_HW
+/*
+ * Calculate HW ECC
+ *
+ * @mtd:        MTD block structure
+ * @dat:        raw data (unused)
+ * @ecc_code:   buffer for ECC
+ */
+static int at91sam9263_nand_calculate(struct mtd_info *mtd,
+		const u_char *dat, unsigned char *ecc_code)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct at91_nand_host *host = nand_chip->priv;
+	unsigned int ecc_value;
+
+	/* get the first 2 ECC bytes */
+	ecc_value = at91_sys_read (host->board->ecc_base + AT91_ECC_PR) & AT91_ECC_PARITY;
+
+	ecc_code[nand_chip->ecc.layout->eccpos[0]] = ecc_value & 0xFF;
+	ecc_code[nand_chip->ecc.layout->eccpos[1]] = (ecc_value >> 8) & 0xFF;
+
+	/* get the last 2 ECC bytes */
+	ecc_value = at91_sys_read (host->board->ecc_base + AT91_ECC_NPR) & AT91_ECC_NPARITY;
+
+	ecc_code[nand_chip->ecc.layout->eccpos[2]] = ecc_value & 0xFF;
+	ecc_code[nand_chip->ecc.layout->eccpos[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 at91sam9263_nand_read_page(struct mtd_info *mtd,
+		struct nand_chip *chip, uint8_t *buf)
+{
+	int eccsize = chip->ecc.size;
+	int eccbytes = chip->ecc.bytes;
+	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 */
+	chip->cmdfunc(mtd, NAND_CMD_RNDOUT, mtd->writesize + chip->ecc.layout->eccpos[0], -1);
+
+	/* the ECC controller needs to read the ECC just after the data */
+	ecc_pos = oob + chip->ecc.layout->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 == -1)
+		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
+ *
+ * @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 at91sam9263_nand_correct(struct mtd_info *mtd, u_char *dat,
+		u_char *read_ecc, u_char *isnull)
+{
+	struct nand_chip *nand_chip = mtd->priv;
+	struct at91_nand_host *host = nand_chip->priv;
+	unsigned int ecc_status;
+	unsigned int ecc_word, ecc_bit;
+	int status;
+
+	status = 0;
+
+	/* get the status from the Status Register */
+	ecc_status = at91_sys_read (host->board->ecc_base + AT91_ECC_SR);
+
+	/* if there is an error */
+	if (ecc_status & AT91_ECC_RECERR)
+	{
+		/* get error bit offset (4 bits) */
+		ecc_bit = at91_sys_read (host->board->ecc_base + AT91_ECC_PR)
+			& AT91_ECC_BITADDR;
+		/* get word address (12 bits) */
+		ecc_word = (at91_sys_read (host->board->ecc_base + AT91_ECC_PR)
+				& AT91_ECC_WORDADDR) >> 4;
+
+		/* if there are multiple errors */
+		if (ecc_status & AT91_ECC_MULERR)
+		{
+			/* check if it is a freshly erased block (filled with 0xff) */
+			if ((ecc_bit == AT91_ECC_BITADDR)
+					&& (ecc_word == (AT91_ECC_WORDADDR >> 4)))
+			{
+				/* the block has juste been erase, return OK */
+				printk(KERN_INFO "at91_nand : freshly erased block detected\n");
+				status = 0;
+			}
+			else
+			{
+				/* it doesn't seems to be a freshly erased block */
+				/* We can't correct so many errors */
+				printk(KERN_INFO "at91_nand : multiple errors detected. Unable to correct.\n");
+				status = -1;
+			}
+		}
+		else
+		{
+			/* if there's a single bit error : we can correct it */
+			if (ecc_status & AT91_ECC_ECCERR)
+			{
+				/* there's nothing much to do here.
+				 * the bit error is on the ECC itself.
+				 */
+				printk(KERN_INFO "at91_nand : one bit error on ECC code. Nothing to correct\n");
+			}
+			else
+			{
+				printk(KERN_INFO "at91_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_INFO "at91_nand : error corrected\n");
+			}
+		}
+	}
+	return status;
+}
+
+/*
+ * Enable HW ECC : unsused
+ */
+static void at91sam9263_nand_hwctl(struct mtd_info *mtd, int mode) {;}
+
+#endif
+
 #ifdef CONFIG_MTD_PARTITIONS
 const char *part_probes[] = { "cmdlinepart", NULL };
 #endif
@@ -132,7 +335,21 @@ static int __init at91_nand_probe(struct
 	if (host->board->rdy_pin)
 		nand_chip->dev_ready = at91_nand_device_ready;
 
+#ifdef CONFIG_MTD_NAND_AT91_ECC_HW
+	nand_chip->ecc.mode = NAND_ECC_HW_SYNDROME;		/* HW SYNDROME ECC */
+	nand_chip->ecc.calculate = at91sam9263_nand_calculate;	/* function called after a write */
+	nand_chip->ecc.correct = at91sam9263_nand_correct;	/* function called after a read */
+	nand_chip->ecc.hwctl = at91sam9263_nand_hwctl;		/* unused */
+	nand_chip->ecc.read_page = at91sam9263_nand_read_page;	/* read page function */
+	nand_chip->ecc.bytes = 4;				/* 4 ECC bytes for any page size */
+	nand_chip->ecc.postpad = 0;
+	nand_chip->ecc.prepad = 0;
+
+#elif defined CONFIG_MTD_NAND_AT91_ECC_SOFT
 	nand_chip->ecc.mode = NAND_ECC_SOFT;	/* enable ECC */
+#else
+	nand_chip->ecc.mode = NAND_ECC_NONE;
+#endif
 	nand_chip->chip_delay = 20;		/* 20us command delay time */
 
 	if (host->board->bus_width_16)		/* 16-bit bus width */
@@ -149,11 +366,70 @@ static int __init at91_nand_probe(struct
 		}
 	}
 
+#ifdef CONFIG_MTD_NAND_AT91_ECC_HW
+	/* first scan to find the device and get the page size */
+	if (nand_scan_ident(mtd, 1))
+	{
+		res = -ENXIO;
+		goto out;
+	}
+
+	/* ECC is calculated for the whole page (1 step) */
+	nand_chip->ecc.size = mtd->writesize;
+
+	/* set ECC page size and oob layout */
+	switch (mtd->writesize)
+	{
+		case 512:
+			nand_chip->ecc.layout = &at91_oobinfo_small;
+			at91_sys_write (host->board->ecc_base +
+					AT91_ECC_MR,
+					AT91_ECC_PAGESIZE_528 & AT91_ECC_PAGESIZE);
+			break;
+		case 1024:
+			nand_chip->ecc.layout = &at91_oobinfo_large;
+			at91_sys_write (host->board->ecc_base +
+					AT91_ECC_MR,
+					AT91_ECC_PAGESIZE_1056 & AT91_ECC_PAGESIZE);
+			break;
+		case 2048:
+			nand_chip->ecc.layout = &at91_oobinfo_large;
+			at91_sys_write (host->board->ecc_base +
+					AT91_ECC_MR,
+					AT91_ECC_PAGESIZE_2112 & AT91_ECC_PAGESIZE);
+			break;
+		case 4096:
+			nand_chip->ecc.layout = &at91_oobinfo_large;
+			at91_sys_write (host->board->ecc_base +
+					AT91_ECC_MR,
+					AT91_ECC_PAGESIZE_4224 & AT91_ECC_PAGESIZE);
+			break;
+		default:
+			/* page size not handled by HW ECC */
+			/* switching back to soft ECC */
+			nand_chip->ecc.mode = NAND_ECC_SOFT;
+			nand_chip->ecc.calculate = NULL;
+			nand_chip->ecc.correct = NULL;
+			nand_chip->ecc.hwctl = NULL;
+			nand_chip->ecc.read_page = NULL;
+			nand_chip->ecc.postpad = 0;
+			nand_chip->ecc.prepad = 0;
+			nand_chip->ecc.bytes = 0;
+			break;
+	}
+	/* second phase scan */
+	if (nand_scan_tail(mtd))
+	{
+		res = -ENXIO;
+		goto out;
+	}
+#else
 	/* Scan to find existance of the device */
 	if (nand_scan(mtd, 1)) {
 		res = -ENXIO;
 		goto out;
 	}
+#endif
 
 #ifdef CONFIG_MTD_PARTITIONS
 	if (host->board->partition_info)
@@ -179,7 +455,9 @@ static int __init at91_nand_probe(struct
 	if (!res)
 		return res;
 
+#ifdef CONFIG_MTD_PARTITIONS
 release:
+#endif
 	nand_release(mtd);
 out:
 	at91_nand_disable(host);
diff -pruN a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
--- a/drivers/mtd/nand/Kconfig	2007-10-05 12:50:45.000000000 +0200
+++ b/drivers/mtd/nand/Kconfig	2007-10-11 14:59:34.000000000 +0200
@@ -259,6 +259,40 @@ config MTD_NAND_AT91
 	help
 	  Enables support for NAND Flash / Smart Media Card interface
 	  on Atmel AT91 processors.
+choice
+	prompt "ECC gestion for NAND Flash / SmartMedia on AT91"
+	depends on MTD_NAND_AT91
+
+config MTD_NAND_AT91_ECC_HW
+	bool "Hardware ECC"
+	depends on MTD_NAND_AT91 && (ARCH_AT91SAM9263 || ARCH_AT91SAM9260)
+	help
+	  Uses hardware ECC provided by the at91sam9260/at91sam9263 chip
+	  instead of software ECC.
+	  The hardware ECC controller is capable of single bit error
+	  correction and 2-bit random detection per page.
+
+	  If unsure, say Y
+
+config MTD_NAND_AT91_ECC_SOFT
+	bool "Software ECC"
+	depends on MTD_NAND_AT91
+	help
+	  Uses software ECC
+
+	  If unsure, say Y
+
+config MTD_NAND_AT91_ECC_NO
+	bool "No ECC (Testing Only)"
+	depends on MTD_NAND_AT91
+	help
+	  No ECC will be used.
+	  It's not a good idea and it should be reserved for testing
+	  purpose only.
+
+	  If unsure, say N
+
+endchoice
 
 config MTD_NAND_CM_X270
 	tristate "Support for NAND Flash on CM-X270 modules"
diff -pruN a/include/asm-arm/arch-at91/at91_ecc.h b/include/asm-arm/arch-at91/at91_ecc.h
--- a/include/asm-arm/arch-at91/at91_ecc.h	2007-10-05 12:50:45.000000000 +0200
+++ b/include/asm-arm/arch-at91/at91_ecc.h	2007-10-11 14:59:34.000000000 +0200
@@ -13,26 +13,28 @@
 #ifndef AT91_ECC_H
 #define AT91_ECC_H
 
-#define AT91_ECC_CR		(AT91_ECC + 0x00)	/* Control register */
+/* ECC registers offsets */
+#define AT91_ECC_CR		0x00				/* Control register */
 #define		AT91_ECC_RST		(1 << 0)		/* Reset parity */
 
-#define AT91_ECC_MR		(AT91_ECC + 0x04)	/* Mode register */
+#define AT91_ECC_MR		0x04				/* Mode register */
 #define		AT91_ECC_PAGESIZE	(3 << 0)		/* Page Size */
 #define			AT91_ECC_PAGESIZE_528		(0)
 #define			AT91_ECC_PAGESIZE_1056		(1)
 #define			AT91_ECC_PAGESIZE_2112		(2)
 #define			AT91_ECC_PAGESIZE_4224		(3)
 
-#define AT91_ECC_SR		(AT91_ECC + 0x08)	/* Status register */
+#define AT91_ECC_SR		0x08				/* Status register */
 #define		AT91_ECC_RECERR		(1 << 0)		/* Recoverable Error */
 #define		AT91_ECC_ECCERR		(1 << 1)		/* ECC Single Bit Error */
 #define		AT91_ECC_MULERR		(1 << 2)		/* Multiple Errors */
 
-#define AT91_ECC_PR		(AT91_ECC + 0x0c)	/* Parity register */
+#define AT91_ECC_PR		0x0c				/* Parity register */
 #define		AT91_ECC_BITADDR	(0xf << 0)		/* Bit Error Address */
 #define		AT91_ECC_WORDADDR	(0xfff << 4)		/* Word Error Address */
+#define		AT91_ECC_PARITY		(0xffff << 0)		/* Parity */
 
-#define AT91_ECC_NPR		(AT91_ECC + 0x10)	/* NParity register */
+#define AT91_ECC_NPR		0x10				/* NParity register */
 #define		AT91_ECC_NPARITY	(0xffff << 0)		/* NParity */
 
 #endif
diff -pruN a/include/asm-arm/arch-at91/board.h b/include/asm-arm/arch-at91/board.h
--- a/include/asm-arm/arch-at91/board.h	2007-09-11 04:50:29.000000000 +0200
+++ b/include/asm-arm/arch-at91/board.h	2007-10-11 15:58:18.000000000 +0200
@@ -89,6 +89,9 @@ struct at91_nand_data {
 	u8		ale;		/* address line number connected to ALE */
 	u8		cle;		/* address line number connected to CLE */
 	u8		bus_width_16;	/* buswidth is 16 bit */
+#if defined(CONFIG_MTD_NAND_AT91_ECC_HW)
+	u32		ecc_base;	/* ECC controller (offset from AT91_BASE_SYS) */
+#endif
 	struct mtd_partition* (*partition_info)(int, int*);
 };
 extern void __init at91_add_device_nand(struct at91_nand_data *data);




More information about the linux-mtd mailing list