[MTD] [OneNAND] main read/write ops support for yaffs2

Now we can use yaffs2 on OneNAND

Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c
index 45bc1433..40d8d6f 100644
--- a/drivers/mtd/onenand/onenand_base.c
+++ b/drivers/mtd/onenand/onenand_base.c
@@ -763,31 +763,83 @@
 }
 
 /**
- * onenand_read - [MTD Interface] Read data from flash
+ * onenand_transfer_auto_oob - [Internal] oob auto-placement transfer
+ * @param mtd		MTD device structure
+ * @param buf		destination address
+ * @param column	oob offset to read from
+ * @param thislen	oob length to read
+ */
+static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, int column,
+				int thislen)
+{
+	struct onenand_chip *this = mtd->priv;
+	struct nand_oobfree *free;
+	int readcol = column;
+	int readend = column + thislen;
+	int lastgap = 0;
+	unsigned int i;
+	uint8_t *oob_buf = this->oob_buf;
+
+	free = this->ecclayout->oobfree;
+	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
+		if (readcol >= lastgap)
+			readcol += free->offset - lastgap;
+		if (readend >= lastgap)
+			readend += free->offset - lastgap;
+		lastgap = free->offset + free->length;
+	}
+	this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize);
+	free = this->ecclayout->oobfree;
+	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
+		int free_end = free->offset + free->length;
+		if (free->offset < readend && free_end > readcol) {
+			int st = max_t(int,free->offset,readcol);
+			int ed = min_t(int,free_end,readend);
+			int n = ed - st;
+			memcpy(buf, oob_buf + st, n);
+			buf += n;
+		} else if (column == 0)
+			break;
+	}
+	return 0;
+}
+
+/**
+ * onenand_read_ops - [OneNAND Interface] OneNAND read main and/or out-of-band
  * @param mtd		MTD device structure
  * @param from		offset to read from
- * @param len		number of bytes to read
- * @param retlen	pointer to variable to store the number of read bytes
- * @param buf		the databuffer to put data
+ * @param ops:		oob operation description structure
  *
- * Read with ecc
-*/
-static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
-	size_t *retlen, u_char *buf)
+ * OneNAND read main and/or out-of-band data
+ */
+static int onenand_read_ops(struct mtd_info *mtd, loff_t from,
+				struct mtd_oob_ops *ops)
 {
 	struct onenand_chip *this = mtd->priv;
 	struct mtd_ecc_stats stats;
-	int read = 0, column;
-	int thislen;
+	size_t len = ops->len;
+	size_t ooblen = ops->ooblen;
+	u_char *buf = ops->datbuf;
+	u_char *oobbuf = ops->oobbuf;
+	int read = 0, column, thislen;
+	int oobread = 0, oobcolumn, thisooblen, oobsize;
 	int ret = 0, boundary = 0;
 	int writesize = this->writesize;
 
-	DEBUG(MTD_DEBUG_LEVEL3, "onenand_read: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
+	DEBUG(MTD_DEBUG_LEVEL3, "onenand_read_ops: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
+
+	if (ops->mode == MTD_OOB_AUTO)
+		oobsize = this->ecclayout->oobavail;
+	else
+		oobsize = mtd->oobsize;
+
+	oobcolumn = from & (mtd->oobsize - 1);
 
 	/* Do not allow reads past end of device */
 	if ((from + len) > mtd->size) {
-		printk(KERN_ERR "onenand_read: Attempt read beyond end of device\n");
-		*retlen = 0;
+		printk(KERN_ERR "onenand_read_ops: Attempt read beyond end of device\n");
+		ops->retlen = 0;
+		ops->oobretlen = 0;
 		return -EINVAL;
 	}
 
@@ -832,6 +884,21 @@
  		}
  		/* While load is going, read from last bufferRAM */
  		this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen);
+
+		/* Read oob area if needed */
+		if (oobbuf) {
+			thisooblen = oobsize - oobcolumn;
+			thisooblen = min_t(int, thisooblen, ooblen - oobread);
+
+			if (ops->mode == MTD_OOB_AUTO)
+				onenand_transfer_auto_oob(mtd, oobbuf, oobcolumn, thisooblen);
+			else
+				this->read_bufferram(mtd, ONENAND_SPARERAM, oobbuf, oobcolumn, thisooblen);
+			oobread += thisooblen;
+			oobbuf += thisooblen;
+			oobcolumn = 0;
+		}
+
  		/* See if we are done */
  		read += thislen;
  		if (read == len)
@@ -857,7 +924,8 @@
 	 * fs driver will take care of that, because
 	 * retlen == desired len and result == -EBADMSG
 	 */
-	*retlen = read;
+	ops->retlen = read;
+	ops->oobretlen = oobread;
 
 	if (mtd->ecc_stats.failed - stats.failed)
 		return -EBADMSG;
@@ -869,55 +937,10 @@
 }
 
 /**
- * onenand_transfer_auto_oob - [Internal] oob auto-placement transfer
- * @param mtd		MTD device structure
- * @param buf		destination address
- * @param column	oob offset to read from
- * @param thislen	oob length to read
- */
-static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, int column,
-				int thislen)
-{
-	struct onenand_chip *this = mtd->priv;
-	struct nand_oobfree *free;
-	int readcol = column;
-	int readend = column + thislen;
-	int lastgap = 0;
-	unsigned int i;
-	uint8_t *oob_buf = this->oob_buf;
-
-	free = this->ecclayout->oobfree;
-	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
-		if (readcol >= lastgap)
-			readcol += free->offset - lastgap;
-		if (readend >= lastgap)
-			readend += free->offset - lastgap;
-		lastgap = free->offset + free->length;
-	}
-	this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize);
-	free = this->ecclayout->oobfree;
-	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
-		int free_end = free->offset + free->length;
-		if (free->offset < readend && free_end > readcol) {
-			int st = max_t(int,free->offset,readcol);
-			int ed = min_t(int,free_end,readend);
-			int n = ed - st;
-			memcpy(buf, oob_buf + st, n);
-			buf += n;
-		} else if (column == 0)
-			break;
-	}
-	return 0;
-}
-
-/**
  * onenand_do_read_oob - [MTD Interface] OneNAND read out-of-band
  * @param mtd		MTD device structure
  * @param from		offset to read from
- * @param len		number of bytes to read
- * @param retlen	pointer to variable to store the number of read bytes
- * @param buf		the databuffer to put data
- * @param mode		operation mode
+ * @param ops:		oob operation description structure
  *
  * OneNAND read out-of-band data from the spare area
  */
@@ -1007,10 +1030,39 @@
 }
 
 /**
- * onenand_read_oob - [MTD Interface] NAND write data and/or out-of-band
+ * onenand_read - [MTD Interface] Read data from flash
+ * @param mtd		MTD device structure
+ * @param from		offset to read from
+ * @param len		number of bytes to read
+ * @param retlen	pointer to variable to store the number of read bytes
+ * @param buf		the databuffer to put data
+ *
+ * Read with ecc
+*/
+static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
+	size_t *retlen, u_char *buf)
+{
+	struct mtd_oob_ops ops = {
+		.len	= len,
+		.ooblen	= 0,
+		.datbuf	= buf,
+		.oobbuf	= NULL,
+	};
+	int ret;
+
+	ret = onenand_read_ops(mtd, from, &ops);
+	*retlen = ops.retlen;
+
+	return ret;
+}
+
+/**
+ * onenand_read_oob - [MTD Interface] Read main and/or out-of-band
  * @param mtd:		MTD device structure
  * @param from:		offset to read from
  * @param ops:		oob operation description structure
+
+ * Read main and/or out-of-band
  */
 static int onenand_read_oob(struct mtd_info *mtd, loff_t from,
 			    struct mtd_oob_ops *ops)
@@ -1024,6 +1076,10 @@
 	default:
 		return -EINVAL;
 	}
+
+	if (ops->datbuf)
+		return onenand_read_ops(mtd, from, ops);
+
 	return onenand_do_read_oob(mtd, from, ops);
 }
 
@@ -1148,7 +1204,6 @@
  * @param mtd		MTD device structure
  * @param buf		the databuffer to verify
  * @param to		offset to read from
- *
  */
 static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to)
 {
@@ -1176,7 +1231,6 @@
  * @param buf          the databuffer to verify
  * @param addr         offset to read from
  * @param len          number of bytes to read and compare
- *
  */
 static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr, size_t len)
 {
@@ -1222,107 +1276,6 @@
 #define NOTALIGNED(x)	((x & (this->subpagesize - 1)) != 0)
 
 /**
- * onenand_write - [MTD Interface] write buffer to FLASH
- * @param mtd		MTD device structure
- * @param to		offset to write to
- * @param len		number of bytes to write
- * @param retlen	pointer to variable to store the number of written bytes
- * @param buf		the data to write
- *
- * Write with ECC
- */
-static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
-	size_t *retlen, const u_char *buf)
-{
-	struct onenand_chip *this = mtd->priv;
-	int written = 0;
-	int ret = 0;
-	int column, subpage;
-
-	DEBUG(MTD_DEBUG_LEVEL3, "onenand_write: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
-
-	/* Initialize retlen, in case of early exit */
-	*retlen = 0;
-
-	/* Do not allow writes past end of device */
-	if (unlikely((to + len) > mtd->size)) {
-		printk(KERN_ERR "onenand_write: Attempt write to past end of device\n");
-		return -EINVAL;
-	}
-
-	/* Reject writes, which are not page aligned */
-        if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(len))) {
-                printk(KERN_ERR "onenand_write: Attempt to write not page aligned data\n");
-                return -EINVAL;
-        }
-
-	column = to & (mtd->writesize - 1);
-
-	/* Grab the lock and see if the device is available */
-	onenand_get_device(mtd, FL_WRITING);
-
-	/* Loop until all data write */
-	while (written < len) {
-		int thislen = min_t(int, mtd->writesize - column, len - written);
-		u_char *wbuf = (u_char *) buf;
-
-		cond_resched();
-
-		this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen);
-
-		/* Partial page write */
-		subpage = thislen < mtd->writesize;
-		if (subpage) {
-			memset(this->page_buf, 0xff, mtd->writesize);
-			memcpy(this->page_buf + column, buf, thislen);
-			wbuf = this->page_buf;
-		}
-
-		this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize);
-		this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0, mtd->oobsize);
-
-		this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize);
-
-		ret = this->wait(mtd, FL_WRITING);
-
-		/* In partial page write we don't update bufferram */
-		onenand_update_bufferram(mtd, to, !ret && !subpage);
-		if (ONENAND_IS_2PLANE(this)) {
-			ONENAND_SET_BUFFERRAM1(this);
-			onenand_update_bufferram(mtd, to + this->writesize, !ret && !subpage);
-		}
-
-		if (ret) {
-			printk(KERN_ERR "onenand_write: write filaed %d\n", ret);
-			break;
-		}
-
-		/* Only check verify write turn on */
-		ret = onenand_verify(mtd, (u_char *) wbuf, to, thislen);
-		if (ret) {
-			printk(KERN_ERR "onenand_write: verify failed %d\n", ret);
-			break;
-		}
-
-		written += thislen;
-
-		if (written == len)
-			break;
-
-		column = 0;
-		to += thislen;
-		buf += thislen;
-	}
-
-	/* Deselect and wake up anyone waiting on the device */
-	onenand_release_device(mtd);
-
-	*retlen = written;
-
-	return ret;
-}
-
-/**
  * onenand_fill_auto_oob - [Internal] oob auto-placement transfer
  * @param mtd		MTD device structure
  * @param oob_buf	oob buffer
@@ -1364,6 +1317,139 @@
 }
 
 /**
+ * onenand_write_ops - [OneNAND Interface] write main and/or out-of-band
+ * @param mtd		MTD device structure
+ * @param to		offset to write to
+ * @param ops		oob operation description structure
+ *
+ * Write main and/or oob with ECC
+ */
+static int onenand_write_ops(struct mtd_info *mtd, loff_t to,
+				struct mtd_oob_ops *ops)
+{
+	struct onenand_chip *this = mtd->priv;
+	int written = 0, column, thislen, subpage;
+	int oobwritten = 0, oobcolumn, thisooblen, oobsize;
+	size_t len = ops->len;
+	size_t ooblen = ops->ooblen;
+	const u_char *buf = ops->datbuf;
+	const u_char *oob = ops->oobbuf;
+	u_char *oobbuf;
+	int ret = 0;
+
+	DEBUG(MTD_DEBUG_LEVEL3, "onenand_write: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
+
+	/* Initialize retlen, in case of early exit */
+	ops->retlen = 0;
+	ops->oobretlen = 0;
+
+	/* Do not allow writes past end of device */
+	if (unlikely((to + len) > mtd->size)) {
+		printk(KERN_ERR "onenand_write: Attempt write to past end of device\n");
+		return -EINVAL;
+	}
+
+	/* Reject writes, which are not page aligned */
+        if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(len))) {
+                printk(KERN_ERR "onenand_write: Attempt to write not page aligned data\n");
+                return -EINVAL;
+        }
+
+	if (ops->mode == MTD_OOB_AUTO)
+		oobsize = this->ecclayout->oobavail;
+	else
+		oobsize = mtd->oobsize;
+
+	oobcolumn = to & (mtd->oobsize - 1);
+
+	column = to & (mtd->writesize - 1);
+
+	/* Grab the lock and see if the device is available */
+	onenand_get_device(mtd, FL_WRITING);
+
+	/* Loop until all data write */
+	while (written < len) {
+		u_char *wbuf = (u_char *) buf;
+
+		thislen = min_t(int, mtd->writesize - column, len - written);
+		thisooblen = min_t(int, oobsize - oobcolumn, ooblen - oobwritten);
+
+		cond_resched();
+
+		this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen);
+
+		/* Partial page write */
+		subpage = thislen < mtd->writesize;
+		if (subpage) {
+			memset(this->page_buf, 0xff, mtd->writesize);
+			memcpy(this->page_buf + column, buf, thislen);
+			wbuf = this->page_buf;
+		}
+
+		this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize);
+
+		if (oob) {
+			oobbuf = this->oob_buf;
+
+			/* We send data to spare ram with oobsize
+			 * to prevent byte access */
+			memset(oobbuf, 0xff, mtd->oobsize);
+			if (ops->mode == MTD_OOB_AUTO)
+				onenand_fill_auto_oob(mtd, oobbuf, oob, oobcolumn, thisooblen);
+			else
+				memcpy(oobbuf + oobcolumn, oob, thisooblen);
+
+			oobwritten += thisooblen;
+			oob += thisooblen;
+			oobcolumn = 0;
+		} else
+			oobbuf = (u_char *) ffchars;
+
+		this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize);
+
+		this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize);
+
+		ret = this->wait(mtd, FL_WRITING);
+
+		/* In partial page write we don't update bufferram */
+		onenand_update_bufferram(mtd, to, !ret && !subpage);
+		if (ONENAND_IS_2PLANE(this)) {
+			ONENAND_SET_BUFFERRAM1(this);
+			onenand_update_bufferram(mtd, to + this->writesize, !ret && !subpage);
+		}
+
+		if (ret) {
+			printk(KERN_ERR "onenand_write: write filaed %d\n", ret);
+			break;
+		}
+
+		/* Only check verify write turn on */
+		ret = onenand_verify(mtd, (u_char *) wbuf, to, thislen);
+		if (ret) {
+			printk(KERN_ERR "onenand_write: verify failed %d\n", ret);
+			break;
+		}
+
+		written += thislen;
+
+		if (written == len)
+			break;
+
+		column = 0;
+		to += thislen;
+		buf += thislen;
+	}
+
+	/* Deselect and wake up anyone waiting on the device */
+	onenand_release_device(mtd);
+
+	ops->retlen = written;
+
+	return ret;
+}
+
+
+/**
  * onenand_do_write_oob - [Internal] OneNAND write out-of-band
  * @param mtd		MTD device structure
  * @param to		offset to write to
@@ -1479,6 +1565,33 @@
 }
 
 /**
+ * onenand_write - [MTD Interface] write buffer to FLASH
+ * @param mtd		MTD device structure
+ * @param to		offset to write to
+ * @param len		number of bytes to write
+ * @param retlen	pointer to variable to store the number of written bytes
+ * @param buf		the data to write
+ *
+ * Write with ECC
+ */
+static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
+	size_t *retlen, const u_char *buf)
+{
+	struct mtd_oob_ops ops = {
+		.len	= len,
+		.ooblen	= 0,
+		.datbuf	= (u_char *) buf,
+		.oobbuf	= NULL,
+	};
+	int ret;
+
+	ret = onenand_write_ops(mtd, to, &ops);
+	*retlen = ops.retlen;
+
+	return ret;
+}
+
+/**
  * onenand_write_oob - [MTD Interface] NAND write data and/or out-of-band
  * @param mtd:		MTD device structure
  * @param to:		offset to write
@@ -1496,6 +1609,10 @@
 	default:
 		return -EINVAL;
 	}
+
+	if (ops->datbuf)
+		return onenand_write_ops(mtd, to, ops);
+
 	return onenand_do_write_oob(mtd, to, ops);
 }