Patchwork [U-Boot,v4] nand: Hack to support 4k page in fsl_elbc_nand

login
register
mail settings
Submitter Rafael Beims
Date July 3, 2012, 9:07 p.m.
Message ID <CABXAApF_UuFKHgewU8WkL484RpRE92MnrqskPADTEK2V4hFueA@mail.gmail.com>
Download mbox | patch
Permalink /patch/168855/
State Changes Requested
Delegated to: Scott Wood
Headers show

Comments

Rafael Beims - July 3, 2012, 9:07 p.m.
Freescale FCM controller has a 2K size limitation of buffer RAM. In order
to support the Nand flash chip with pagesize larger than 2K bytes,
we read/write 2k data repeatedly by issuing FIR_OP_RB/FIR_OP_WB and save
them to a large buffer.
Because of this, the in flash layout of the oob is different from the
default for 4096kiB page sizes. Therefore, we need to migrate the
factory bad block markers from the original position to the new layout.

Signed-off-by: Shengzhou Liu <Shengzhou.Liu@freescale.com>
Signed-off-by: Liu Shuo <b35362@freescale.com>
Signed-off-by: Rafael Beims <rafael.beims@gmail.com>
---
Changes in v2:
 - Added check to disallow the migration code to run in devices with
 page size <= 2048

Changes in v3:
 - Corrected memset writing beyond newoob buffer
 - Corrected various code formatting errors
 - Cosmetic changes
 - Generated random magic number for migration marker
 - Removed direct references to nand_bbt.c code
 - Removed useless bbt free
 - Removed useless dir variable
 - Removed vmalloc and kmalloc calls. Only malloc() and free() used now
 - Start searching the migration marker from the end of NAND

Changes in v4:
 - Read all the pages of the block until migrated marker is found
 - Write the marker in all pages of the block

 drivers/mtd/nand/fsl_elbc_nand.c |  443 +++++++++++++++++++++++++++++++++++---
 1 files changed, 415 insertions(+), 28 deletions(-)

 	     "index %x, pes %d ps %d\n",
@@ -256,13 +291,14 @@ static int fsl_elbc_run_command(struct mtd_info *mtd)
 	return ctrl->status == LTESR_CC ? 0 : -EIO;
 }

-static void fsl_elbc_do_read(struct nand_chip *chip, int oob)
+static void fsl_elbc_do_read(struct mtd_info *mtd, int oob)
 {
+	struct nand_chip *chip = mtd->priv;
 	struct fsl_elbc_mtd *priv = chip->priv;
 	struct fsl_elbc_ctrl *ctrl = priv->ctrl;
 	fsl_lbc_t *lbc = ctrl->regs;

-	if (priv->page_size) {
+	if (mtd->writesize >= 2048) {
 		out_be32(&lbc->fir,
 			 (FIR_OP_CW0 << FIR_OP0_SHIFT) |
 			 (FIR_OP_CA  << FIR_OP1_SHIFT) |
@@ -295,6 +331,7 @@ static void fsl_elbc_cmdfunc(struct mtd_info *mtd,
unsigned int command,
 	struct fsl_elbc_mtd *priv = chip->priv;
 	struct fsl_elbc_ctrl *ctrl = priv->ctrl;
 	fsl_lbc_t *lbc = ctrl->regs;
+	int i, nps = mtd->writesize / 2048;

 	ctrl->use_mdr = 0;

@@ -319,8 +356,28 @@ static void fsl_elbc_cmdfunc(struct mtd_info
*mtd, unsigned int command,
 		ctrl->read_bytes = mtd->writesize + mtd->oobsize;
 		ctrl->index += column;

-		fsl_elbc_do_read(chip, 0);
+		fsl_elbc_do_read(mtd, 0);
 		fsl_elbc_run_command(mtd);
+
+		if (mtd->writesize <= 2048)
+			return;
+
+		/* Continue to read the rest bytes if writesize > 2048 */
+		io_to_buffer(mtd, 0, 0);
+		io_to_buffer(mtd, 0, 1);
+		/*
+		 * Maybe there are some reasons of FCM hardware timing,
+		 * we must insert a FIR_OP_NOP(0x00) before FIR_OP_RB.
+		 */
+		out_be32(&lbc->fir, FIR_OP_NOP << FIR_OP0_SHIFT |
+				    FIR_OP_RB << FIR_OP1_SHIFT);
+
+		for (i = 1; i < nps; i++) {
+			fsl_elbc_run_command(mtd);
+			io_to_buffer(mtd, i, 0);
+			io_to_buffer(mtd, i, 1);
+		}
+
 		return;

 	/* READOOB reads only the OOB because no ECC is performed. */
@@ -328,14 +385,35 @@ static void fsl_elbc_cmdfunc(struct mtd_info
*mtd, unsigned int command,
 		vdbg("fsl_elbc_cmdfunc: NAND_CMD_READOOB, page_addr:"
 		     " 0x%x, column: 0x%x.\n", page_addr, column);

-		out_be32(&lbc->fbcr, mtd->oobsize - column);
-		set_addr(mtd, column, page_addr, 1);
+		if (mtd->writesize <= 2048) {
+			out_be32(&lbc->fbcr, mtd->oobsize - column);
+			set_addr(mtd, column, page_addr, 1);
+		} else {
+			out_be32(&lbc->fbcr, 64);
+			set_addr(mtd, 0, page_addr, 1);
+			ctrl->index += column;
+		}

 		ctrl->read_bytes = mtd->writesize + mtd->oobsize;
-
-		fsl_elbc_do_read(chip, 1);
+		fsl_elbc_do_read(mtd, 1);
 		fsl_elbc_run_command(mtd);

+		if (mtd->writesize <= 2048)
+			return;
+
+		if (column < 64)
+			io_to_buffer(mtd, 0, 1);
+
+		out_be32(&lbc->fpar, in_be32(&lbc->fpar) & ~FPAR_LP_MS);
+		out_be32(&lbc->fir, FIR_OP_RB << FIR_OP1_SHIFT);
+		out_be32(&lbc->fbcr, 2112);
+
+		for (i = 1; i < nps; i++) {
+			fsl_elbc_run_command(mtd);
+			if (column < (64 * (i + 1)))
+				io_to_buffer(mtd, i, 1);
+		}
+
 		return;

 	/* READID must read all 5 possible bytes while CEB is active */
@@ -357,6 +435,8 @@ static void fsl_elbc_cmdfunc(struct mtd_info *mtd,
unsigned int command,
 		ctrl->mdr = column;
 		set_addr(mtd, 0, 0, 0);
 		fsl_elbc_run_command(mtd);
+		if (mtd->writesize > 2048)
+			memcpy_fromio(ctrl->buffer, ctrl->addr, 256);
 		return;

 	/* ERASE1 stores the block and page address */
@@ -394,8 +474,28 @@ static void fsl_elbc_cmdfunc(struct mtd_info
*mtd, unsigned int command,

 		ctrl->column = column;
 		ctrl->oob = 0;
+		if (column >= mtd->writesize) {
+			/* OOB area */
+			column -= mtd->writesize;
+			ctrl->oob = 1;
+		} else {
+			ctrl->oob = 0;
+		}
+
+		if (mtd->writesize > 2048) {
+			/* writesize > 2048 */
+			fcr = (NAND_CMD_STATUS << FCR_CMD1_SHIFT) |
+				(NAND_CMD_SEQIN << FCR_CMD2_SHIFT) |
+				(NAND_CMD_PAGEPROG << FCR_CMD3_SHIFT);
+			if (ctrl->oob)
+				fcr |= NAND_CMD_RNDIN << FCR_CMD0_SHIFT;

-		if (priv->page_size) {
+			out_be32(&lbc->fir,
+				(FIR_OP_CM2 << FIR_OP0_SHIFT) |
+				(FIR_OP_CA  << FIR_OP1_SHIFT) |
+				(FIR_OP_PA  << FIR_OP2_SHIFT) |
+				(FIR_OP_WB  << FIR_OP3_SHIFT));
+		} else if (mtd->writesize == 2048) {
 			fcr = (NAND_CMD_SEQIN << FCR_CMD0_SHIFT) |
 			      (NAND_CMD_PAGEPROG << FCR_CMD1_SHIFT);

@@ -438,6 +538,7 @@ static void fsl_elbc_cmdfunc(struct mtd_info *mtd,
unsigned int command,

 	/* PAGEPROG reuses all of the setup from SEQIN and adds the length */
 	case NAND_CMD_PAGEPROG: {
+		int pos;
 		vdbg("fsl_elbc_cmdfunc: NAND_CMD_PAGEPROG "
 		     "writing %d bytes.\n", ctrl->index);

@@ -445,14 +546,70 @@ static void fsl_elbc_cmdfunc(struct mtd_info
*mtd, unsigned int command,
 		 * then set the exact length, otherwise use a full page
 		 * write so the HW generates the ECC.
 		 */
+
 		if (ctrl->oob || ctrl->column != 0 ||
-		    ctrl->index != mtd->writesize + mtd->oobsize)
-			out_be32(&lbc->fbcr, ctrl->index);
-		else
+				ctrl->index != mtd->writesize + mtd->oobsize) {
+			if (ctrl->oob && mtd->writesize > 2048) {
+				out_be32(&lbc->fbcr, 64);
+			} else {
+				out_be32(&lbc->fbcr, ctrl->index -
+					ctrl->column);
+			}
+		} else {
 			out_be32(&lbc->fbcr, 0);
+		}
+
+		if (mtd->writesize > 2048) {
+			if (!ctrl->oob)
+				buffer_to_io(mtd, 0, 0);
+			buffer_to_io(mtd, 0, 1);
+		}

 		fsl_elbc_run_command(mtd);

+		if (mtd->writesize <= 2048)
+			return;
+
+		if (ctrl->oob) {
+			pos = 2048;
+			out_be32(&lbc->fir,
+				(FIR_OP_CM0 << FIR_OP0_SHIFT) |
+				(FIR_OP_UA  << FIR_OP1_SHIFT) |
+				(FIR_OP_UA  << FIR_OP2_SHIFT) |
+				(FIR_OP_WB  << FIR_OP3_SHIFT));
+			for (i = 1; i < nps; i++) {
+				pos += 2112;
+				ctrl->mdr = pos;
+				ctrl->use_mdr = 1;
+				if (i == nps - 1) {
+					out_be32(&lbc->fir,
+						(FIR_OP_CM0 << FIR_OP1_SHIFT) |
+						(FIR_OP_UA  << FIR_OP2_SHIFT) |
+						(FIR_OP_UA  << FIR_OP3_SHIFT) |
+						(FIR_OP_WB  << FIR_OP4_SHIFT) |
+						(FIR_OP_CM3 << FIR_OP5_SHIFT) |
+						(FIR_OP_CW1 << FIR_OP6_SHIFT) |
+						(FIR_OP_RS  << FIR_OP7_SHIFT));
+				}
+				buffer_to_io(mtd, i, 1);
+				fsl_elbc_run_command(mtd);
+			}
+		} else {
+			out_be32(&lbc->fir, FIR_OP_WB << FIR_OP1_SHIFT);
+			for (i = 1; i < nps; i++) {
+				if (i == nps - 1) {
+					ctrl->use_mdr = 1;
+					out_be32(&lbc->fir,
+						(FIR_OP_WB  << FIR_OP1_SHIFT) |
+						(FIR_OP_CM3 << FIR_OP2_SHIFT) |
+						(FIR_OP_CW1 << FIR_OP3_SHIFT) |
+						(FIR_OP_RS  << FIR_OP4_SHIFT));
+				}
+				buffer_to_io(mtd, i, 0);
+				buffer_to_io(mtd, i, 1);
+				fsl_elbc_run_command(mtd);
+			}
+		}
 		return;
 	}

@@ -473,6 +630,8 @@ static void fsl_elbc_cmdfunc(struct mtd_info *mtd,
unsigned int command,
 		 * write-protected, even when it is not.
 		 */
 		out_8(ctrl->addr, in_8(ctrl->addr) | NAND_STATUS_WP);
+		if (mtd->writesize > 2048)
+			ctrl->buffer[0] = in_8(ctrl->addr);
 		return;

 	/* RESET without waiting for the ready line */
@@ -519,7 +678,11 @@ static void fsl_elbc_write_buf(struct mtd_info
*mtd, const u8 *buf, int len)
 		len = bufsize - ctrl->index;
 	}

-	memcpy_toio(&ctrl->addr[ctrl->index], buf, len);
+	if (mtd->writesize > 2048)
+		memcpy(&ctrl->buffer[ctrl->index], buf, len);
+	else
+		memcpy_toio(&ctrl->addr[ctrl->index], buf, len);
+
 	/*
 	 * This is workaround for the weird elbc hangs during nand write,
 	 * Scott Wood says: "...perhaps difference in how long it takes a
@@ -543,8 +706,13 @@ static u8 fsl_elbc_read_byte(struct mtd_info *mtd)
 	struct fsl_elbc_ctrl *ctrl = priv->ctrl;

 	/* If there are still bytes in the FCM, then use the next byte. */
-	if (ctrl->index < ctrl->read_bytes)
-		return in_8(&ctrl->addr[ctrl->index++]);
+	if (ctrl->index < ctrl->read_bytes) {
+		int index = ctrl->index++;
+		if (mtd->writesize > 2048)
+			return ctrl->buffer[index];
+		else
+			return in_8(&ctrl->addr[index]);
+	}

 	printf("read_byte beyond end of buffer\n");
 	return ERR_BYTE;
@@ -564,7 +732,10 @@ static void fsl_elbc_read_buf(struct mtd_info
*mtd, u8 *buf, int len)
 		return;

 	avail = min((unsigned int)len, ctrl->read_bytes - ctrl->index);
-	memcpy_fromio(buf, &ctrl->addr[ctrl->index], avail);
+	if (mtd->writesize > 2048)
+		memcpy(buf, &ctrl->buffer[ctrl->index], avail);
+	else
+		memcpy_fromio(buf, &ctrl->addr[ctrl->index], avail);
 	ctrl->index += avail;

 	if (len > avail)
@@ -598,9 +769,17 @@ static int fsl_elbc_verify_buf(struct mtd_info *mtd,
 		return -EINVAL;
 	}

-	for (i = 0; i < len; i++)
-		if (in_8(&ctrl->addr[ctrl->index + i]) != buf[i])
-			break;
+	if (mtd->writesize > 2048) {
+		for (i = 0; i < len; i++) {
+			if (ctrl->buffer[ctrl->index + i] != buf[i])
+				break;
+		}
+	} else {
+		for (i = 0; i < len; i++) {
+			if (in_8(&ctrl->addr[ctrl->index + i]) != buf[i])
+				break;
+		}
+	}

 	ctrl->index += len;
 	return i == len && ctrl->status == LTESR_CC ? 0 : -EIO;
@@ -637,6 +816,8 @@ static int fsl_elbc_wait(struct mtd_info *mtd,
struct nand_chip *chip)
 	 * write-protected, even when it is not.
 	 */
 	out_8(ctrl->addr, in_8(ctrl->addr) | NAND_STATUS_WP);
+	if (mtd->writesize > 2048)
+		ctrl->buffer[0] = in_8(ctrl->addr);
 	return fsl_elbc_read_byte(mtd);
 }

@@ -686,6 +867,187 @@ static void fsl_elbc_ctrl_init(void)
 	elbc_ctrl->addr = NULL;
 }

+/*
+ * Scan read raw data from flash
+ */
+static int fsl_elbc_scan_read_raw_oob(struct mtd_info *mtd, uint8_t *buf,
+						loff_t offs, size_t len)
+{
+	fsl_elbc_cmdfunc(mtd, NAND_CMD_READOOB, 0, offs);
+	fsl_elbc_read_buf(mtd, buf, mtd->oobsize);
+
+	return 0;
+}
+
+static int fsl_elbc_migrate_badblocks(struct mtd_info *mtd)
+{
+	struct nand_chip *this = mtd->priv;
+	int len, numblocks, i;
+	int startblock;
+	loff_t from;
+	uint8_t *newoob;
+
+	struct fsl_elbc_mtd *priv = this->priv;
+	struct fsl_elbc_ctrl *ctrl = priv->ctrl;
+
+	int num_subpages = mtd->writesize / 2048;
+	len = mtd->writesize + mtd->oobsize;
+	numblocks = this->chipsize >> (this->phys_erase_shift - 1);
+	startblock = 0;
+	from = 0;
+
+	newoob = malloc(mtd->oobsize);
+	memset(newoob, 0xff, mtd->oobsize);
+
+	for (i = startblock; i < numblocks; i += 2) {
+		int page = (from >> this->page_shift) & this->pagemask;
+		fsl_elbc_cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+
+		/*
+		 * As we are already using the hack to read the bytes
+		 * from NAND, the original badblock marker is offset
+		 * from its original location in the internal buffer.
+		 * This is because the hack reads 2048 + 64 and already
+		 * positions the spare in the correct region
+		 * (after the 4096 offset)
+		 */
+		uint8_t *badblock_pattern = (ctrl->buffer + mtd->writesize -
+			     (((num_subpages - 1) * 64) + this->badblockpos));
+
+		if (*badblock_pattern != *this->badblock_pattern->pattern) {
+			printf("Badblock found at %08x, migrating...\n", page);
+			memcpy(newoob, badblock_pattern, 1);
+			fsl_elbc_cmdfunc(mtd, NAND_CMD_SEQIN,
+						mtd->writesize, page);
+			fsl_elbc_write_buf(mtd, newoob, mtd->oobsize);
+			fsl_elbc_cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+		}
+
+		from += (1 << this->phys_erase_shift);
+	}
+
+	free(newoob);
+	return 0;
+}
+
+static int fsl_elbc_write_migration_marker(struct mtd_info *mtd,
+						uint8_t *buf, int len)
+{
+	struct nand_chip *this = mtd->priv;
+	int startblock;
+	int i, j;
+	int blocks_to_write = 2;
+
+	/* Start below maximum bbt */
+	startblock = (mtd->size >> this->phys_erase_shift);
+
+	for (i = 1; i < BADBLOCK_MIGRATED_MB && blocks_to_write != 0; i++) {
+		int actblock = startblock - i;
+		loff_t offs = (loff_t)actblock << this->phys_erase_shift;
+		int page = (offs >> this->page_shift) & this->pagemask;
+		/* Avoid badblocks writing the marker... */
+		fsl_elbc_scan_read_raw_oob(mtd, buf,
+					   page, mtd->writesize);
+
+		if (*(buf + this->badblockpos) ==
+		    *this->badblock_pattern->pattern) {
+			/* We are reusing this buffer, reset it */
+			memset(buf, 0xff, len);
+			memcpy(buf+BADBLOCK_MIGRATED_OFF,
+				migrated_pattern, sizeof(migrated_pattern));
+
+			/*
+			 *  Mark the block as bad the same way that
+			 *  it's done for the bbt. This should avoid
+			 *  this block being overwritten
+			 */
+			memset(buf+this->badblockpos, 0x02, 1);
+
+			/* Repeat the marker in all the pages of the block */
+			for (j = 0; j < mtd->erasesize; j += mtd->writesize) {
+				page = ((offs + j) >> this->page_shift) &
+								this->pagemask;
+				fsl_elbc_cmdfunc(mtd, NAND_CMD_SEQIN,
+							mtd->writesize, page);
+				fsl_elbc_write_buf(mtd, buf, mtd->oobsize);
+				fsl_elbc_cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
+			}
+			blocks_to_write--;
+			printf("Wrote migration marker to offset: %x\n", page);
+		}
+	}
+
+	if (blocks_to_write != 0)
+		printf("Could not write migration marker due to badblocks\n");
+
+	return 0;
+}
+
+static int fsl_elbc_scan_bbt(struct mtd_info *mtd)
+{
+	struct nand_chip *this = mtd->priv;
+	int len;
+	int startblock, block;
+	uint8_t *buf = 0;
+	int migrate;
+	int i, j;
+
+	if (mtd->writesize > 2048) {
+		/* Start from the end of NAND */
+		startblock = (mtd->size >> this->phys_erase_shift);
+
+		/* Allocate a temporary buffer for one eraseblock incl. oob */
+		len = (1 << this->phys_erase_shift);
+		len += (len >> this->page_shift) * mtd->oobsize;
+		buf = malloc(len);
+		if (!buf) {
+			printf("fsl_elbc_nand: Out of memory\n");
+			return -ENOMEM;
+		}
+
+		for (block = 1; block < BADBLOCK_MIGRATED_MB; block++) {
+			int page;
+			int actblock = startblock - block;
+			loff_t offs = (loff_t)actblock <<
+						    this->phys_erase_shift;
+
+			/*
+			 * When writing the marker, we repeat it over all pages
+			 * of the block, for redundancy. Make use of this
+			 * redundancy here...
+			 */
+			for (j = 0; j < mtd->erasesize; j += mtd->writesize) {
+				migrate = 0;
+				page = ((offs + j) >> this->page_shift)
+							& this->pagemask;
+				fsl_elbc_scan_read_raw_oob(mtd, buf,
+							page, mtd->writesize);
+				for (i = 0; i < sizeof(migrated_pattern); i++) {
+					if (buf[i+BADBLOCK_MIGRATED_OFF] !=
+							migrated_pattern[i]) {
+						migrate = -1;
+						break;
+					}
+				}
+				if (migrate == 0)
+					goto out;
+			}
+		}
+
+		/* If we get here we could not find the marker, migrate... */
+		printf("Moving factory marked badblocks to new oob\n");
+		fsl_elbc_migrate_badblocks(mtd);
+		fsl_elbc_write_migration_marker(mtd, buf, len);
+	}
+	/*
+	 * Now that we checked and possibly migrated badblock
+	 * markers, continue with default bbt scanning
+	 */
+out:
+	free(buf);
+	return nand_scan_bbt(mtd, this->badblock_pattern);
+}
+
 static int fsl_elbc_chip_init(int devnum, u8 *addr)
 {
 	struct mtd_info *mtd = &nand_info[devnum];
@@ -741,6 +1103,7 @@ static int fsl_elbc_chip_init(int devnum, u8 *addr)
 	nand->select_chip = fsl_elbc_select_chip;
 	nand->cmdfunc = fsl_elbc_cmdfunc;
 	nand->waitfunc = fsl_elbc_wait;
+	nand->scan_bbt = fsl_elbc_scan_bbt;

 	/* set up nand options */
 	nand->bbt_td = &bbt_main_descr;
@@ -804,6 +1167,30 @@ static int fsl_elbc_chip_init(int devnum, u8 *addr)
 	if (ret)
 		return ret;

+	/*
+	 * Freescale FCM controller has a 2KB size limitation of buffer RAM,
+	 * so elbc_ctrl->buffer has to be used if pagesize of NAND devices
+	 * chip greater than 2048.
+	 */
+	if (mtd->writesize > 2048) {
+		elbc_ctrl->buffer =
+			malloc(mtd->writesize + mtd->oobsize);
+		memset(elbc_ctrl->buffer, 0xff, mtd->writesize + mtd->oobsize);
+		if (!elbc_ctrl->buffer) {
+			printf("failed to allocate memory for elbc buffer\n");
+			return -ENOMEM;
+		}
+
+		/*
+		 * We can use only n x 64 bytes of the spare area
+		 * where n is the number of 2048k pages used to read
+		 * the full device page
+		 */
+		mtd->oobsize = (mtd->writesize / 2048) * 64;
+	} else {
+		elbc_ctrl->buffer = NULL;
+	}
+
 	ret = nand_scan_tail(mtd);
 	if (ret)
 		return ret;
Scott Wood - Feb. 19, 2013, 11:46 p.m.
On Tue, Jul 03, 2012 at 11:07:26AM -0000, Rafael Beims wrote:
> Freescale FCM controller has a 2K size limitation of buffer RAM. In order
> to support the Nand flash chip with pagesize larger than 2K bytes,
> we read/write 2k data repeatedly by issuing FIR_OP_RB/FIR_OP_WB and save
> them to a large buffer.
> Because of this, the in flash layout of the oob is different from the
> default for 4096kiB page sizes. Therefore, we need to migrate the
> factory bad block markers from the original position to the new layout.
> 
> Signed-off-by: Shengzhou Liu <Shengzhou.Liu@freescale.com>
> Signed-off-by: Liu Shuo <b35362@freescale.com>
> Signed-off-by: Rafael Beims <rafael.beims@gmail.com>
> 
> ---
> Changes in v2:
>  - Added check to disallow the migration code to run in devices with
>  page size <= 2048
> 
> Changes in v3:
>  - Corrected memset writing beyond newoob buffer
>  - Corrected various code formatting errors
>  - Cosmetic changes
>  - Generated random magic number for migration marker
>  - Removed direct references to nand_bbt.c code
>  - Removed useless bbt free
>  - Removed useless dir variable
>  - Removed vmalloc and kmalloc calls. Only malloc() and free() used now
>  - Start searching the migration marker from the end of NAND
> 
> Changes in v4:
>  - Read all the pages of the block until migrated marker is found
>  - Write the marker in all pages of the block
> 
>  drivers/mtd/nand/fsl_elbc_nand.c |  443 +++++++++++++++++++++++++++++++++++---
>  1 files changed, 415 insertions(+), 28 deletions(-)
> 
>  	     "index %x, pes %d ps %d\n",
> @@ -256,13 +291,14 @@ static int fsl_elbc_run_command(struct mtd_info *mtd)

Sorry for the delay, but this patch is mangled and cannot be applied. 
E.g. the above diff chunk is missing the header that indicates the file
it is for (I can guess, but "git am" can't, and it suggests that part of
the patch may be missing).

> @@ -295,6 +331,7 @@ static void fsl_elbc_cmdfunc(struct mtd_info *mtd,
> unsigned int command,

There is also line wrapping.

I'd also like to see at least the larger portions of the code that this
adds be ifdeffed to avoid increasing image size further for boards that
don't need it.

-Scott

Patch

diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c
index 9076ad4..f5afa9b 100644
--- a/drivers/mtd/nand/fsl_elbc_nand.c
+++ b/drivers/mtd/nand/fsl_elbc_nand.c
@@ -76,15 +76,17 @@  struct fsl_elbc_ctrl {

 	/* device info */
 	fsl_lbc_t *regs;
-	u8 __iomem *addr;        /* Address of assigned FCM buffer        */
-	unsigned int page;       /* Last page written to / read from      */
-	unsigned int read_bytes; /* Number of bytes read during command   */
-	unsigned int column;     /* Saved column from SEQIN               */
-	unsigned int index;      /* Pointer to next byte to 'read'        */
-	unsigned int status;     /* status read from LTESR after last op  */
-	unsigned int mdr;        /* UPM/FCM Data Register value           */
-	unsigned int use_mdr;    /* Non zero if the MDR is to be set      */
-	unsigned int oob;        /* Non zero if operating on OOB data     */
+	u8 __iomem *addr;           /* Address of assigned FCM buffer       */
+	unsigned int page;          /* Last page written to / read from     */
+	unsigned int read_bytes;    /* Number of bytes read during command  */
+	unsigned int column;        /* Saved column from SEQIN              */
+	unsigned int index;         /* Pointer to next byte to 'read'       */
+	unsigned int status;        /* status read from LTESR after last op */
+	unsigned int mdr;           /* UPM/FCM Data Register value          */
+	unsigned int use_mdr;       /* Non zero if the MDR is to be set     */
+	unsigned int oob;           /* Non zero if operating on OOB data    */
+	uint8_t *buffer;            /* Just used when pagesize is greater   */
+				    /* than FCM RAM 2K limitation           */
 };

 /* These map to the positions used by the FCM hardware ECC generator */
@@ -131,6 +133,10 @@  static struct nand_bbt_descr largepage_memorybased = {
 	.pattern = scan_ff_pattern,
 };

+static u8 migrated_pattern[] = { 0x61, 0xa1, 0x87, 0xf0 };
+#define BADBLOCK_MIGRATED_OFF 1
+#define BADBLOCK_MIGRATED_MB 3
+
 /*
  * ELBC may use HW ECC, so that OOB offsets, that NAND core uses for bbt,
  * interfere with ECC positions, that's why we implement our own descriptors.
@@ -159,6 +165,35 @@  static struct nand_bbt_descr bbt_mirror_descr = {
 	.pattern = mirror_pattern,
 };

+static void io_to_buffer(struct mtd_info *mtd, int subpage, int oob)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct fsl_elbc_mtd *priv = chip->priv;
+	struct fsl_elbc_ctrl *ctrl = priv->ctrl;
+	void *src, *dst;
+	int len = oob ? 64 : 2048;
+
+	/* for emulating 4096+ bytes NAND using 2048-byte FCM RAM */
+	dst = ctrl->buffer + (oob ? mtd->writesize : 0) + subpage * len;
+	src = ctrl->addr + (oob ? 2048 : 0);
+	memcpy_fromio(dst, src, len);
+}
+
+static void buffer_to_io(struct mtd_info *mtd, int subpage, int oob)
+{
+	struct nand_chip *chip = mtd->priv;
+	struct fsl_elbc_mtd *priv = chip->priv;
+	struct fsl_elbc_ctrl *ctrl = priv->ctrl;
+	void *src, *dst;
+	int len = oob ? 64 : 2048;
+
+	src = ctrl->buffer + (oob ? mtd->writesize : 0) + subpage * len;
+	dst = ctrl->addr + (oob ? 2048 : 0);
+	memcpy_toio(dst, src, len);
+	/* See the in_8() in fsl_elbc_write_buf() */
+	in_8(ctrl->addr);
+}
+
 /*=================================*/

 /*
@@ -194,7 +229,7 @@  static void set_addr(struct mtd_info *mtd, int
column, int page_addr, int oob)

 	/* for OOB data point to the second half of the buffer */
 	if (oob)
-		ctrl->index += priv->page_size ? 2048 : 512;
+		ctrl->index += mtd->writesize;

 	vdbg("set_addr: bank=%d, ctrl->addr=0x%p (0x%p), "