diff options
Diffstat (limited to 'drivers/mtd/nand/atmel_nand.c')
| -rw-r--r-- | drivers/mtd/nand/atmel_nand.c | 265 | 
1 files changed, 265 insertions, 0 deletions
| diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c index 40002be41..d5eb54ad8 100644 --- a/drivers/mtd/nand/atmel_nand.c +++ b/drivers/mtd/nand/atmel_nand.c @@ -31,6 +31,209 @@  #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; +	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 +267,11 @@ static int at91_nand_ready(struct mtd_info *mtd)  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 +282,62 @@ int board_nand_init(struct nand_chip *nand)  #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;  } |