[PATCH] Hardware ECC controller support on AT91SAM9260/3

Richard Genoud richard.genoud at gmail.com
Wed Nov 21 12:55:17 EST 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.

I had the opportunity to test this driver on a 512B page NAND (8bits).
I had to make some adjustments.
Now it's working on large and small pages.

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-11-14 08:13:34.000000000 +0100
@@ -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,223 @@ static void at91_nand_disable(struct at9
		at91_set_gpio_value(host->board->enable_pin, 1);
}

+#ifdef CONFIG_MTD_NAND_AT91_ECC_HW
+/*
+ * write oob for small pages
+ */
+static int at91sam9263_nand_write_oob_512 (struct mtd_info *mtd,
+		struct nand_chip *chip, int page)
+{
+	int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad;
+	int eccsize = chip->ecc.size, length = mtd->oobsize;
+	int len, pos, status = 0;
+	const uint8_t *bufpoi = chip->oob_poi;
+
+	pos = eccsize + chunk;
+
+	chip->cmdfunc(mtd, NAND_CMD_SEQIN, pos, page);
+	len = min_t(int, length, chunk);
+	chip->write_buf(mtd, bufpoi, len);
+	bufpoi += len;
+	length -= len;
+	if (length > 0)
+		chip->write_buf(mtd, bufpoi, length);
+
+	chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+	status = chip->waitfunc(mtd, chip);
+
+	return status & NAND_STATUS_FAIL ? -EIO : 0;
+
+}
+
+/*
+ * read oob for small pages
+ */
+static int at91sam9263_nand_read_oob_512 (struct mtd_info *mtd,
+		struct nand_chip *chip,	int page, int sndcmd)
+{
+	if (sndcmd) {
+		chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
+		sndcmd = 0;
+	}
+	chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+	return sndcmd;
+}
+
+/*
+ * 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 if needed */
+	if (chip->ecc.layout->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 + 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 */
+				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 +385,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 +416,72 @@ 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;
+			nand_chip->ecc.read_oob = at91sam9263_nand_read_oob_512;
+			nand_chip->ecc.write_oob = at91sam9263_nand_write_oob_512;
+			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 +507,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