at91: add hwecc method for nand
authorNikolay Petukhov <nikolaypetukhov@gmail.com>
Fri, 19 Mar 2010 05:49:27 +0000 (10:49 +0500)
committerScott Wood <scottwood@freescale.com>
Tue, 23 Mar 2010 19:29:09 +0000 (14:29 -0500)
This is a patch to use the hardware ECC controller of
the AT91SAM9260 for the AT91 nand. Taken from the kernel 2.6.33.

Signed-off-by: Nikolay Petukhov <Nikolay.Petukhov@gmail.com>
drivers/mtd/nand/atmel_nand.c
drivers/mtd/nand/atmel_nand_ecc.h [new file with mode: 0644]

index 40002be..d5eb54a 100644 (file)
 
 #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;
 }
diff --git a/drivers/mtd/nand/atmel_nand_ecc.h b/drivers/mtd/nand/atmel_nand_ecc.h
new file mode 100644 (file)
index 0000000..1ee7f99
--- /dev/null
@@ -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