mtd: sh_flctl: Simplify the hardware ecc page read/write
[linux-2.6-microblaze.git] / drivers / mtd / nand / omap2.c
index c2b0bba..d7f681d 100644 (file)
 #include <linux/io.h>
 #include <linux/slab.h>
 
+#ifdef CONFIG_MTD_NAND_OMAP_BCH
+#include <linux/bch.h>
+#endif
+
 #include <plat/dma.h>
 #include <plat/gpmc.h>
 #include <plat/nand.h>
@@ -127,6 +131,11 @@ struct omap_nand_info {
        } iomode;
        u_char                          *buf;
        int                                     buf_len;
+
+#ifdef CONFIG_MTD_NAND_OMAP_BCH
+       struct bch_control             *bch;
+       struct nand_ecclayout           ecclayout;
+#endif
 };
 
 /**
@@ -402,7 +411,7 @@ static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr,
                        PREFETCH_FIFOTHRESHOLD_MAX, 0x1, len, is_write);
        if (ret)
                /* PFPW engine is busy, use cpu copy method */
-               goto out_copy;
+               goto out_copy_unmap;
 
        init_completion(&info->comp);
 
@@ -421,6 +430,8 @@ static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr,
        dma_unmap_single(&info->pdev->dev, dma_addr, len, dir);
        return 0;
 
+out_copy_unmap:
+       dma_unmap_single(&info->pdev->dev, dma_addr, len, dir);
 out_copy:
        if (info->nand.options & NAND_BUSWIDTH_16)
                is_write == 0 ? omap_read_buf16(mtd, (u_char *) addr, len)
@@ -879,7 +890,7 @@ static int omap_wait(struct mtd_info *mtd, struct nand_chip *chip)
        struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
                                                        mtd);
        unsigned long timeo = jiffies;
-       int status = NAND_STATUS_FAIL, state = this->state;
+       int status, state = this->state;
 
        if (state == FL_ERASING)
                timeo += (HZ * 400) / 1000;
@@ -894,6 +905,8 @@ static int omap_wait(struct mtd_info *mtd, struct nand_chip *chip)
                        break;
                cond_resched();
        }
+
+       status = gpmc_nand_read(info->gpmc_cs, GPMC_NAND_DATA);
        return status;
 }
 
@@ -925,6 +938,226 @@ static int omap_dev_ready(struct mtd_info *mtd)
        return 1;
 }
 
+#ifdef CONFIG_MTD_NAND_OMAP_BCH
+
+/**
+ * omap3_enable_hwecc_bch - Program OMAP3 GPMC to perform BCH ECC correction
+ * @mtd: MTD device structure
+ * @mode: Read/Write mode
+ */
+static void omap3_enable_hwecc_bch(struct mtd_info *mtd, int mode)
+{
+       int nerrors;
+       unsigned int dev_width;
+       struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+                                                  mtd);
+       struct nand_chip *chip = mtd->priv;
+
+       nerrors = (info->nand.ecc.bytes == 13) ? 8 : 4;
+       dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0;
+       /*
+        * Program GPMC to perform correction on one 512-byte sector at a time.
+        * Using 4 sectors at a time (i.e. ecc.size = 2048) is also possible and
+        * gives a slight (5%) performance gain (but requires additional code).
+        */
+       (void)gpmc_enable_hwecc_bch(info->gpmc_cs, mode, dev_width, 1, nerrors);
+}
+
+/**
+ * omap3_calculate_ecc_bch4 - Generate 7 bytes of ECC bytes
+ * @mtd: MTD device structure
+ * @dat: The pointer to data on which ecc is computed
+ * @ecc_code: The ecc_code buffer
+ */
+static int omap3_calculate_ecc_bch4(struct mtd_info *mtd, const u_char *dat,
+                                   u_char *ecc_code)
+{
+       struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+                                                  mtd);
+       return gpmc_calculate_ecc_bch4(info->gpmc_cs, dat, ecc_code);
+}
+
+/**
+ * omap3_calculate_ecc_bch8 - Generate 13 bytes of ECC bytes
+ * @mtd: MTD device structure
+ * @dat: The pointer to data on which ecc is computed
+ * @ecc_code: The ecc_code buffer
+ */
+static int omap3_calculate_ecc_bch8(struct mtd_info *mtd, const u_char *dat,
+                                   u_char *ecc_code)
+{
+       struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+                                                  mtd);
+       return gpmc_calculate_ecc_bch8(info->gpmc_cs, dat, ecc_code);
+}
+
+/**
+ * omap3_correct_data_bch - Decode received data and correct errors
+ * @mtd: MTD device structure
+ * @data: page data
+ * @read_ecc: ecc read from nand flash
+ * @calc_ecc: ecc read from HW ECC registers
+ */
+static int omap3_correct_data_bch(struct mtd_info *mtd, u_char *data,
+                                 u_char *read_ecc, u_char *calc_ecc)
+{
+       int i, count;
+       /* cannot correct more than 8 errors */
+       unsigned int errloc[8];
+       struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+                                                  mtd);
+
+       count = decode_bch(info->bch, NULL, 512, read_ecc, calc_ecc, NULL,
+                          errloc);
+       if (count > 0) {
+               /* correct errors */
+               for (i = 0; i < count; i++) {
+                       /* correct data only, not ecc bytes */
+                       if (errloc[i] < 8*512)
+                               data[errloc[i]/8] ^= 1 << (errloc[i] & 7);
+                       pr_debug("corrected bitflip %u\n", errloc[i]);
+               }
+       } else if (count < 0) {
+               pr_err("ecc unrecoverable error\n");
+       }
+       return count;
+}
+
+/**
+ * omap3_free_bch - Release BCH ecc resources
+ * @mtd: MTD device structure
+ */
+static void omap3_free_bch(struct mtd_info *mtd)
+{
+       struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+                                                  mtd);
+       if (info->bch) {
+               free_bch(info->bch);
+               info->bch = NULL;
+       }
+}
+
+/**
+ * omap3_init_bch - Initialize BCH ECC
+ * @mtd: MTD device structure
+ * @ecc_opt: OMAP ECC mode (OMAP_ECC_BCH4_CODE_HW or OMAP_ECC_BCH8_CODE_HW)
+ */
+static int omap3_init_bch(struct mtd_info *mtd, int ecc_opt)
+{
+       int ret, max_errors;
+       struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+                                                  mtd);
+#ifdef CONFIG_MTD_NAND_OMAP_BCH8
+       const int hw_errors = 8;
+#else
+       const int hw_errors = 4;
+#endif
+       info->bch = NULL;
+
+       max_errors = (ecc_opt == OMAP_ECC_BCH8_CODE_HW) ? 8 : 4;
+       if (max_errors != hw_errors) {
+               pr_err("cannot configure %d-bit BCH ecc, only %d-bit supported",
+                      max_errors, hw_errors);
+               goto fail;
+       }
+
+       /* initialize GPMC BCH engine */
+       ret = gpmc_init_hwecc_bch(info->gpmc_cs, 1, max_errors);
+       if (ret)
+               goto fail;
+
+       /* software bch library is only used to detect and locate errors */
+       info->bch = init_bch(13, max_errors, 0x201b /* hw polynomial */);
+       if (!info->bch)
+               goto fail;
+
+       info->nand.ecc.size    = 512;
+       info->nand.ecc.hwctl   = omap3_enable_hwecc_bch;
+       info->nand.ecc.correct = omap3_correct_data_bch;
+       info->nand.ecc.mode    = NAND_ECC_HW;
+
+       /*
+        * The number of corrected errors in an ecc block that will trigger
+        * block scrubbing defaults to the ecc strength (4 or 8).
+        * Set mtd->bitflip_threshold here to define a custom threshold.
+        */
+
+       if (max_errors == 8) {
+               info->nand.ecc.strength  = 8;
+               info->nand.ecc.bytes     = 13;
+               info->nand.ecc.calculate = omap3_calculate_ecc_bch8;
+       } else {
+               info->nand.ecc.strength  = 4;
+               info->nand.ecc.bytes     = 7;
+               info->nand.ecc.calculate = omap3_calculate_ecc_bch4;
+       }
+
+       pr_info("enabling NAND BCH ecc with %d-bit correction\n", max_errors);
+       return 0;
+fail:
+       omap3_free_bch(mtd);
+       return -1;
+}
+
+/**
+ * omap3_init_bch_tail - Build an oob layout for BCH ECC correction.
+ * @mtd: MTD device structure
+ */
+static int omap3_init_bch_tail(struct mtd_info *mtd)
+{
+       int i, steps;
+       struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+                                                  mtd);
+       struct nand_ecclayout *layout = &info->ecclayout;
+
+       /* build oob layout */
+       steps = mtd->writesize/info->nand.ecc.size;
+       layout->eccbytes = steps*info->nand.ecc.bytes;
+
+       /* do not bother creating special oob layouts for small page devices */
+       if (mtd->oobsize < 64) {
+               pr_err("BCH ecc is not supported on small page devices\n");
+               goto fail;
+       }
+
+       /* reserve 2 bytes for bad block marker */
+       if (layout->eccbytes+2 > mtd->oobsize) {
+               pr_err("no oob layout available for oobsize %d eccbytes %u\n",
+                      mtd->oobsize, layout->eccbytes);
+               goto fail;
+       }
+
+       /* put ecc bytes at oob tail */
+       for (i = 0; i < layout->eccbytes; i++)
+               layout->eccpos[i] = mtd->oobsize-layout->eccbytes+i;
+
+       layout->oobfree[0].offset = 2;
+       layout->oobfree[0].length = mtd->oobsize-2-layout->eccbytes;
+       info->nand.ecc.layout = layout;
+
+       if (!(info->nand.options & NAND_BUSWIDTH_16))
+               info->nand.badblock_pattern = &bb_descrip_flashbased;
+       return 0;
+fail:
+       omap3_free_bch(mtd);
+       return -1;
+}
+
+#else
+static int omap3_init_bch(struct mtd_info *mtd, int ecc_opt)
+{
+       pr_err("CONFIG_MTD_NAND_OMAP_BCH is not enabled\n");
+       return -1;
+}
+static int omap3_init_bch_tail(struct mtd_info *mtd)
+{
+       return -1;
+}
+static void omap3_free_bch(struct mtd_info *mtd)
+{
+}
+#endif /* CONFIG_MTD_NAND_OMAP_BCH */
+
 static int __devinit omap_nand_probe(struct platform_device *pdev)
 {
        struct omap_nand_info           *info;
@@ -1063,6 +1296,13 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
                info->nand.ecc.hwctl            = omap_enable_hwecc;
                info->nand.ecc.correct          = omap_correct_data;
                info->nand.ecc.mode             = NAND_ECC_HW;
+       } else if ((pdata->ecc_opt == OMAP_ECC_BCH4_CODE_HW) ||
+                  (pdata->ecc_opt == OMAP_ECC_BCH8_CODE_HW)) {
+               err = omap3_init_bch(&info->mtd, pdata->ecc_opt);
+               if (err) {
+                       err = -EINVAL;
+                       goto out_release_mem_region;
+               }
        }
 
        /* DIP switches on some boards change between 8 and 16 bit
@@ -1094,6 +1334,14 @@ static int __devinit omap_nand_probe(struct platform_device *pdev)
                                        (offset + omap_oobinfo.eccbytes);
 
                info->nand.ecc.layout = &omap_oobinfo;
+       } else if ((pdata->ecc_opt == OMAP_ECC_BCH4_CODE_HW) ||
+                  (pdata->ecc_opt == OMAP_ECC_BCH8_CODE_HW)) {
+               /* build OOB layout for BCH ECC correction */
+               err = omap3_init_bch_tail(&info->mtd);
+               if (err) {
+                       err = -EINVAL;
+                       goto out_release_mem_region;
+               }
        }
 
        /* second phase scan */
@@ -1122,6 +1370,7 @@ static int omap_nand_remove(struct platform_device *pdev)
        struct mtd_info *mtd = platform_get_drvdata(pdev);
        struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
                                                        mtd);
+       omap3_free_bch(&info->mtd);
 
        platform_set_drvdata(pdev, NULL);
        if (info->dma_ch != -1)