mtd: nand: omap: enable BCH ECC scheme using ELM for generic platform

BCH8_ECC scheme implemented in omap_gpmc.c driver has following favours
+-----------------------------------+-----------------+-----------------+
|ECC Scheme                         | ECC Calculation | Error Detection |
+-----------------------------------+-----------------+-----------------+
|OMAP_ECC_BCH8_CODE_HW              |GPMC             |ELM H/W engine   |
|OMAP_ECC_BCH8_CODE_HW_DETECTION_SW |GPMC             |S/W BCH library  |
+-----------------------------------+-----------------+-----------------+

Current implementation limits the BCH8_CODE_HW only for AM33xx device family.
(using CONFIG_AM33XX). However, other SoC families (like TI81xx) also have
ELM hardware module, and can support ECC error detection using ELM.

This patch
- removes CONFIG_AM33xx
	Thus this driver can be reused by all devices having ELM h/w engine.
- adds omap_select_ecc_scheme()
	A common function to handle ecc-scheme related configurations. This
	can be used both during device-probe and via user-space u-boot commads
	to change ecc-scheme. During device probe ecc-scheme is selected based
	on CONFIG_NAND_OMAP_ELM or CONFIG_NAND_OMAP_BCH8
- enables CONFIG_BCH
	S/W library (lib/bch.c) required by OMAP_ECC_BCHx_CODE_HW_DETECTION_SW
  	is enabled by CONFIG_BCH.
- enables CONFIG_SYS_NAND_ONFI_DETECTION
	for auto-detection of ONFI compliant NAND devices
- updates following README doc
	doc/README.nand
	board/ti/am335x/README
	doc/README.omap3

Signed-off-by: Pekon Gupta <pekon@ti.com>
[scottwood@freescale.com: fixed unused variable warning]
Signed-off-by: Scott Wood <scottwood@freescale.com>
diff --git a/drivers/mtd/nand/omap_gpmc.c b/drivers/mtd/nand/omap_gpmc.c
index c828859..e6b289d 100644
--- a/drivers/mtd/nand/omap_gpmc.c
+++ b/drivers/mtd/nand/omap_gpmc.c
@@ -15,15 +15,13 @@
 #include <linux/bch.h>
 #include <linux/compiler.h>
 #include <nand.h>
-#ifdef CONFIG_AM33XX
 #include <asm/omap_elm.h>
-#endif
+
+#define BADBLOCK_MARKER_LENGTH	2
+#define SECTOR_BYTES		512
 
 static uint8_t cs;
-static __maybe_unused struct nand_ecclayout hw_nand_oob =
-	GPMC_NAND_HW_ECC_LAYOUT;
-static __maybe_unused struct nand_ecclayout hw_bch8_nand_oob =
-	GPMC_NAND_HW_BCH8_ECC_LAYOUT;
+static __maybe_unused struct nand_ecclayout omap_ecclayout;
 
 /*
  * omap_nand_hwcontrol - Set the address pointers corretly for the
@@ -233,6 +231,7 @@
 	uint8_t type;
 	uint8_t nibbles;
 	struct bch_control *control;
+	enum omap_ecc ecc_scheme;
 };
 
 /* bch types */
@@ -274,17 +273,15 @@
 {
 	uint32_t val;
 	uint32_t dev_width = (chip->options & NAND_BUSWIDTH_16) >> 1;
-#ifdef CONFIG_AM33XX
 	uint32_t unused_length = 0;
-#endif
 	uint32_t wr_mode = BCH_WRAPMODE_6;
 	struct nand_bch_priv *bch = chip->priv;
 
 	/* Clear the ecc result registers, select ecc reg as 1 */
 	writel(ECCCLEAR | ECCRESULTREG1, &gpmc_cfg->ecc_control);
 
-#ifdef CONFIG_AM33XX
-	wr_mode = BCH_WRAPMODE_1;
+	if (bch->ecc_scheme == OMAP_ECC_BCH8_CODE_HW) {
+		wr_mode = BCH_WRAPMODE_1;
 
 	switch (bch->nibbles) {
 	case ECC_BCH4_NIBBLES:
@@ -320,7 +317,7 @@
 		val |= (unused_length << 22);
 		break;
 	}
-#else
+	} else {
 	/*
 	 * This ecc_size_config setting is for BCH sw library.
 	 *
@@ -333,7 +330,7 @@
 	 *  size1 = 32 (skip 32 nibbles = 16 bytes per sector in spare area)
 	 */
 	val = (32 << 22) | (0 << 12);
-#endif
+	}
 	/* ecc size configuration */
 	writel(val, &gpmc_cfg->ecc_size_config);
 
@@ -376,9 +373,9 @@
 }
 
 /*
- * BCH8 support (needs ELM and thus AM33xx-only)
+ * BCH support using ELM module
  */
-#ifdef CONFIG_AM33XX
+#ifdef CONFIG_NAND_OMAP_ELM
 /*
  * omap_read_bch8_result - Read BCH result for BCH8 level
  *
@@ -631,20 +628,20 @@
 	}
 	return 0;
 }
-#endif /* CONFIG_AM33XX */
+#endif /* CONFIG_NAND_OMAP_ELM */
 
 /*
  * OMAP3 BCH8 support (with BCH library)
  */
-#ifdef CONFIG_NAND_OMAP_BCH8
+#ifdef CONFIG_BCH
 /*
- *  omap_calculate_ecc_bch - Read BCH ECC result
+ *  omap_calculate_ecc_bch_sw - Read BCH ECC result
  *
  *  @mtd:	MTD device structure
  *  @dat:	The pointer to data on which ecc is computed (unused here)
  *  @ecc:	The ECC output buffer
  */
-static int omap_calculate_ecc_bch(struct mtd_info *mtd, const uint8_t *dat,
+static int omap_calculate_ecc_bch_sw(struct mtd_info *mtd, const uint8_t *dat,
 				uint8_t *ecc)
 {
 	int ret = 0;
@@ -689,13 +686,13 @@
 }
 
 /**
- * omap_correct_data_bch - Decode received data and correct errors
+ * omap_correct_data_bch_sw - 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 omap_correct_data_bch(struct mtd_info *mtd, u_char *data,
+static int omap_correct_data_bch_sw(struct mtd_info *mtd, u_char *data,
 				 u_char *read_ecc, u_char *calc_ecc)
 {
 	int i, count;
@@ -752,7 +749,150 @@
 		chip_priv->control = NULL;
 	}
 }
-#endif /* CONFIG_NAND_OMAP_BCH8 */
+#endif /* CONFIG_BCH */
+
+/**
+ * omap_select_ecc_scheme - configures driver for particular ecc-scheme
+ * @nand: NAND chip device structure
+ * @ecc_scheme: ecc scheme to configure
+ * @pagesize: number of main-area bytes per page of NAND device
+ * @oobsize: number of OOB/spare bytes per page of NAND device
+ */
+static int omap_select_ecc_scheme(struct nand_chip *nand,
+	enum omap_ecc ecc_scheme, unsigned int pagesize, unsigned int oobsize) {
+	struct nand_bch_priv	*bch		= nand->priv;
+	struct nand_ecclayout	*ecclayout	= nand->ecc.layout;
+	int eccsteps = pagesize / SECTOR_BYTES;
+	int i;
+
+	switch (ecc_scheme) {
+	case OMAP_ECC_HAM1_CODE_SW:
+		debug("nand: selected OMAP_ECC_HAM1_CODE_SW\n");
+		/* For this ecc-scheme, ecc.bytes, ecc.layout, ... are
+		 * initialized in nand_scan_tail(), so just set ecc.mode */
+		bch_priv.control	= NULL;
+		bch_priv.type		= 0;
+		nand->ecc.mode		= NAND_ECC_SOFT;
+		nand->ecc.layout	= NULL;
+		nand->ecc.size		= pagesize;
+		bch->ecc_scheme		= OMAP_ECC_HAM1_CODE_SW;
+		break;
+
+	case OMAP_ECC_HAM1_CODE_HW:
+		debug("nand: selected OMAP_ECC_HAM1_CODE_HW\n");
+		/* check ecc-scheme requirements before updating ecc info */
+		if ((3 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) {
+			printf("nand: error: insufficient OOB: require=%d\n", (
+				(3 * eccsteps) + BADBLOCK_MARKER_LENGTH));
+			return -EINVAL;
+		}
+		bch_priv.control	= NULL;
+		bch_priv.type		= 0;
+		/* populate ecc specific fields */
+		nand->ecc.mode		= NAND_ECC_HW;
+		nand->ecc.strength	= 1;
+		nand->ecc.size		= SECTOR_BYTES;
+		nand->ecc.bytes		= 3;
+		nand->ecc.hwctl		= omap_enable_hwecc;
+		nand->ecc.correct	= omap_correct_data;
+		nand->ecc.calculate	= omap_calculate_ecc;
+		/* define ecc-layout */
+		ecclayout->eccbytes	= nand->ecc.bytes * eccsteps;
+		for (i = 0; i < ecclayout->eccbytes; i++)
+			ecclayout->eccpos[i] = i + BADBLOCK_MARKER_LENGTH;
+		ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH;
+		ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes -
+						BADBLOCK_MARKER_LENGTH;
+		bch->ecc_scheme		= OMAP_ECC_HAM1_CODE_HW;
+		break;
+
+	case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW:
+#ifdef CONFIG_BCH
+		debug("nand: selected OMAP_ECC_BCH8_CODE_HW_DETECTION_SW\n");
+		/* check ecc-scheme requirements before updating ecc info */
+		if ((13 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) {
+			printf("nand: error: insufficient OOB: require=%d\n", (
+				(13 * eccsteps) + BADBLOCK_MARKER_LENGTH));
+			return -EINVAL;
+		}
+		/* check if BCH S/W library can be used for error detection */
+		bch_priv.control = init_bch(13, 8, 0x201b);
+		if (!bch_priv.control) {
+			printf("nand: error: could not init_bch()\n");
+			return -ENODEV;
+		}
+		bch_priv.type = ECC_BCH8;
+		/* populate ecc specific fields */
+		nand->ecc.mode		= NAND_ECC_HW;
+		nand->ecc.strength	= 8;
+		nand->ecc.size		= SECTOR_BYTES;
+		nand->ecc.bytes		= 13;
+		nand->ecc.hwctl		= omap_enable_ecc_bch;
+		nand->ecc.correct	= omap_correct_data_bch_sw;
+		nand->ecc.calculate	= omap_calculate_ecc_bch_sw;
+		/* define ecc-layout */
+		ecclayout->eccbytes	= nand->ecc.bytes * eccsteps;
+		ecclayout->eccpos[0]	= BADBLOCK_MARKER_LENGTH;
+		for (i = 1; i < ecclayout->eccbytes; i++) {
+			if (i % nand->ecc.bytes)
+				ecclayout->eccpos[i] =
+						ecclayout->eccpos[i - 1] + 1;
+			else
+				ecclayout->eccpos[i] =
+						ecclayout->eccpos[i - 1] + 2;
+		}
+		ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH;
+		ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes -
+						BADBLOCK_MARKER_LENGTH;
+		omap_hwecc_init_bch(nand, NAND_ECC_READ);
+		bch->ecc_scheme		= OMAP_ECC_BCH8_CODE_HW_DETECTION_SW;
+		break;
+#else
+		printf("nand: error: CONFIG_BCH required for ECC\n");
+		return -EINVAL;
+#endif
+
+	case OMAP_ECC_BCH8_CODE_HW:
+#ifdef CONFIG_NAND_OMAP_ELM
+		debug("nand: selected OMAP_ECC_BCH8_CODE_HW\n");
+		/* check ecc-scheme requirements before updating ecc info */
+		if ((14 * eccsteps) + BADBLOCK_MARKER_LENGTH > oobsize) {
+			printf("nand: error: insufficient OOB: require=%d\n", (
+				(14 * eccsteps) + BADBLOCK_MARKER_LENGTH));
+			return -EINVAL;
+		}
+		/* intialize ELM for ECC error detection */
+		elm_init();
+		bch_priv.type		= ECC_BCH8;
+		/* populate ecc specific fields */
+		nand->ecc.mode		= NAND_ECC_HW;
+		nand->ecc.strength	= 8;
+		nand->ecc.size		= SECTOR_BYTES;
+		nand->ecc.bytes		= 14;
+		nand->ecc.hwctl		= omap_enable_ecc_bch;
+		nand->ecc.correct	= omap_correct_data_bch;
+		nand->ecc.calculate	= omap_calculate_ecc_bch;
+		nand->ecc.read_page	= omap_read_page_bch;
+		/* define ecc-layout */
+		ecclayout->eccbytes	= nand->ecc.bytes * eccsteps;
+		for (i = 0; i < ecclayout->eccbytes; i++)
+			ecclayout->eccpos[i] = i + BADBLOCK_MARKER_LENGTH;
+		ecclayout->oobfree[0].offset = i + BADBLOCK_MARKER_LENGTH;
+		ecclayout->oobfree[0].length = oobsize - ecclayout->eccbytes -
+						BADBLOCK_MARKER_LENGTH;
+		bch->ecc_scheme		= OMAP_ECC_BCH8_CODE_HW;
+		break;
+#else
+		printf("nand: error: CONFIG_NAND_OMAP_ELM required for ECC\n");
+		return -EINVAL;
+#endif
+
+	default:
+		debug("nand: error: ecc scheme not enabled or supported\n");
+		return -EINVAL;
+	}
+	return 0;
+}
 
 #ifndef CONFIG_SPL_BUILD
 /*
@@ -763,77 +903,45 @@
  * @eccstrength		- the number of bits that could be corrected
  *			  (1 - hamming, 4 - BCH4, 8 - BCH8, 16 - BCH16)
  */
-void omap_nand_switch_ecc(uint32_t hardware, uint32_t eccstrength)
+int __maybe_unused omap_nand_switch_ecc(uint32_t hardware, uint32_t eccstrength)
 {
 	struct nand_chip *nand;
 	struct mtd_info *mtd;
+	int err = 0;
 
 	if (nand_curr_device < 0 ||
 	    nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE ||
 	    !nand_info[nand_curr_device].name) {
-		printf("Error: Can't switch ecc, no devices available\n");
-		return;
+		printf("nand: error: no NAND devices found\n");
+		return -ENODEV;
 	}
 
 	mtd = &nand_info[nand_curr_device];
 	nand = mtd->priv;
-
 	nand->options |= NAND_OWN_BUFFERS;
-
-	/* Reset ecc interface */
-	nand->ecc.mode = NAND_ECC_NONE;
-	nand->ecc.read_page = NULL;
-	nand->ecc.write_page = NULL;
-	nand->ecc.read_oob = NULL;
-	nand->ecc.write_oob = NULL;
-	nand->ecc.hwctl = NULL;
-	nand->ecc.correct = NULL;
-	nand->ecc.calculate = NULL;
-	nand->ecc.strength = eccstrength;
-
 	/* Setup the ecc configurations again */
 	if (hardware) {
 		if (eccstrength == 1) {
-			nand->ecc.mode = NAND_ECC_HW;
-			nand->ecc.layout = &hw_nand_oob;
-			nand->ecc.size = 512;
-			nand->ecc.bytes = 3;
-			nand->ecc.hwctl = omap_enable_hwecc;
-			nand->ecc.correct = omap_correct_data;
-			nand->ecc.calculate = omap_calculate_ecc;
-			omap_hwecc_init(nand);
-			printf("1-bit hamming HW ECC selected\n");
+			err = omap_select_ecc_scheme(nand,
+					OMAP_ECC_HAM1_CODE_HW,
+					mtd->writesize, mtd->oobsize);
+		} else if (eccstrength == 8) {
+			err = omap_select_ecc_scheme(nand,
+					OMAP_ECC_BCH8_CODE_HW,
+					mtd->writesize, mtd->oobsize);
+		} else {
+			printf("nand: error: unsupported ECC scheme\n");
+			return -EINVAL;
 		}
-#if defined(CONFIG_AM33XX) || defined(CONFIG_NAND_OMAP_BCH8)
-		else if (eccstrength == 8) {
-			nand->ecc.mode = NAND_ECC_HW;
-			nand->ecc.layout = &hw_bch8_nand_oob;
-			nand->ecc.size = 512;
-#ifdef CONFIG_AM33XX
-			nand->ecc.bytes = 14;
-			nand->ecc.read_page = omap_read_page_bch;
-#else
-			nand->ecc.bytes = 13;
-#endif
-			nand->ecc.hwctl = omap_enable_ecc_bch;
-			nand->ecc.correct = omap_correct_data_bch;
-			nand->ecc.calculate = omap_calculate_ecc_bch;
-			omap_hwecc_init_bch(nand, NAND_ECC_READ);
-			printf("8-bit BCH HW ECC selected\n");
-		}
-#endif
 	} else {
-		nand->ecc.mode = NAND_ECC_SOFT;
-		/* Use mtd default settings */
-		nand->ecc.layout = NULL;
-		nand->ecc.size = 0;
-		printf("SW ECC selected\n");
+		err = omap_select_ecc_scheme(nand, OMAP_ECC_HAM1_CODE_SW,
+					mtd->writesize, mtd->oobsize);
 	}
 
 	/* Update NAND handling after ECC mode switch */
-	nand_scan_tail(mtd);
-
-	nand->options &= ~NAND_OWN_BUFFERS;
+	if (!err)
+		err = nand_scan_tail(mtd);
+	return err;
 }
 #endif /* CONFIG_SPL_BUILD */
 
@@ -856,7 +964,7 @@
 {
 	int32_t gpmc_config = 0;
 	cs = 0;
-
+	int err = 0;
 	/*
 	 * xloader/Uboot's gpmc configuration would have configured GPMC for
 	 * nand type of memory. The following logic scans and latches on to the
@@ -873,7 +981,7 @@
 		cs++;
 	}
 	if (cs >= GPMC_MAX_CS) {
-		printf("NAND: Unable to find NAND settings in "
+		printf("nand: error: Unable to find NAND settings in "
 			"GPMC Configuration - quitting\n");
 		return -ENODEV;
 	}
@@ -885,64 +993,32 @@
 
 	nand->IO_ADDR_R = (void __iomem *)&gpmc_cfg->cs[cs].nand_dat;
 	nand->IO_ADDR_W = (void __iomem *)&gpmc_cfg->cs[cs].nand_cmd;
-
-	nand->cmd_ctrl = omap_nand_hwcontrol;
-	nand->options = NAND_NO_PADDING | NAND_CACHEPRG;
+	nand->priv	= &bch_priv;
+	nand->cmd_ctrl	= omap_nand_hwcontrol;
+	nand->options	|= NAND_NO_PADDING | NAND_CACHEPRG;
 	/* If we are 16 bit dev, our gpmc config tells us that */
 	if ((readl(&gpmc_cfg->cs[cs].config1) & 0x3000) == 0x1000)
 		nand->options |= NAND_BUSWIDTH_16;
 
 	nand->chip_delay = 100;
+	nand->ecc.layout = &omap_ecclayout;
 
-#if defined(CONFIG_AM33XX) || defined(CONFIG_NAND_OMAP_BCH8)
-#ifdef CONFIG_AM33XX
-	/* AM33xx uses the ELM */
-	/* required in case of BCH */
-	elm_init();
+	/* select ECC scheme */
+#if defined(CONFIG_NAND_OMAP_ELM)
+	err = omap_select_ecc_scheme(nand, OMAP_ECC_BCH8_CODE_HW,
+			CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE);
+#elif defined(CONFIG_NAND_OMAP_BCH8)
+	err = omap_select_ecc_scheme(nand, OMAP_ECC_BCH8_CODE_HW_DETECTION_SW,
+			CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE);
+#elif !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_NAND_SOFTECC)
+	err = omap_select_ecc_scheme(nand, OMAP_ECC_HAM1_CODE_SW,
+			0, 0);
 #else
-	/*
-	 * Whereas other OMAP based SoC do not have the ELM, they use the BCH
-	 * SW library.
-	 */
-	bch_priv.control = init_bch(13, 8, 0x201b /* hw polynominal */);
-	if (!bch_priv.control) {
-		puts("Could not init_bch()\n");
-		return -ENODEV;
-	}
+	err = omap_select_ecc_scheme(nand, OMAP_ECC_HAM1_CODE_HW,
+			CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE);
 #endif
-	/* BCH info that will be correct for SPL or overridden otherwise. */
-	nand->priv = &bch_priv;
-#endif
-
-	/* Default ECC mode */
-#if defined(CONFIG_AM33XX) || defined(CONFIG_NAND_OMAP_BCH8)
-	nand->ecc.mode = NAND_ECC_HW;
-	nand->ecc.layout = &hw_bch8_nand_oob;
-	nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE;
-	nand->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES;
-	nand->ecc.strength = 8;
-	nand->ecc.hwctl = omap_enable_ecc_bch;
-	nand->ecc.correct = omap_correct_data_bch;
-	nand->ecc.calculate = omap_calculate_ecc_bch;
-#ifdef CONFIG_AM33XX
-	nand->ecc.read_page = omap_read_page_bch;
-#endif
-	omap_hwecc_init_bch(nand, NAND_ECC_READ);
-#else
-#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_NAND_SOFTECC)
-	nand->ecc.mode = NAND_ECC_SOFT;
-#else
-	nand->ecc.mode = NAND_ECC_HW;
-	nand->ecc.layout = &hw_nand_oob;
-	nand->ecc.size = CONFIG_SYS_NAND_ECCSIZE;
-	nand->ecc.bytes = CONFIG_SYS_NAND_ECCBYTES;
-	nand->ecc.hwctl = omap_enable_hwecc;
-	nand->ecc.correct = omap_correct_data;
-	nand->ecc.calculate = omap_calculate_ecc;
-	nand->ecc.strength = 1;
-	omap_hwecc_init(nand);
-#endif
-#endif
+	if (err)
+		return err;
 
 #ifdef CONFIG_SPL_BUILD
 	if (nand->options & NAND_BUSWIDTH_16)