|
@@ -21,6 +21,10 @@
|
|
|
#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
|
|
|
};
|
|
|
|
|
|
/**
|
|
@@ -929,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;
|
|
@@ -1067,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
|
|
@@ -1098,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 */
|
|
@@ -1126,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)
|