Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a5e7c7b4 authored by Adrian Hunter's avatar Adrian Hunter Committed by Kyungmin Park
Browse files

[MTD] OneNAND: Add support for auto-placement of out-of-band data



Enable the use of oob operation mode MTD_OOB_AUTO with OneNAND.
Note that MTD_OOB_RAW is still not supported.

Signed-off-by: default avatarAdrian Hunter <ext-adrian.hunter@nokia.com>
Signed-off-by: default avatarKyungmin Park <kyungmin.park@samsung.com>
parent 9bfbc9b2
Loading
Loading
Loading
Loading
+169 −35
Original line number Diff line number Diff line
@@ -786,6 +786,45 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
	return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0;
}

/**
 * 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;
	uint8_t *oob_buf = this->page_buf + mtd->writesize;

	for (free = this->ecclayout->oobfree; free->length; ++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 + readcol,
			     readcol, readend - readcol);
	for (free = this->ecclayout->oobfree; free->length; ++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;
		}
	}
	return 0;
}

/**
 * onenand_do_read_oob - [MTD Interface] OneNAND read out-of-band
 * @param mtd		MTD device structure
@@ -793,14 +832,15 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
 * @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
 *
 * OneNAND read out-of-band data from the spare area
 */
int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
			size_t *retlen, u_char *buf)
			size_t *retlen, u_char *buf, mtd_oob_mode_t mode)
{
	struct onenand_chip *this = mtd->priv;
	int read = 0, thislen, column;
	int read = 0, thislen, column, oobsize;
	int ret = 0;

	DEBUG(MTD_DEBUG_LEVEL3, "onenand_read_oob: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
@@ -808,21 +848,33 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
	/* Initialize return length value */
	*retlen = 0;

	if (mode == MTD_OOB_AUTO)
		oobsize = this->ecclayout->oobavail;
	else
		oobsize = mtd->oobsize;

	column = from & (mtd->oobsize - 1);

	if (unlikely(column >= oobsize)) {
		DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: Attempted to start read outside oob\n");
		return -EINVAL;
	}

	/* Do not allow reads past end of device */
	if (unlikely((from + len) > mtd->size)) {
		DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: Attempt read beyond end of device\n");
	if (unlikely(from >= mtd->size ||
		     column + len > ((mtd->size >> this->page_shift) -
				     (from >> this->page_shift)) * oobsize)) {
		DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: Attempted to read beyond end of device\n");
		return -EINVAL;
	}

	/* Grab the lock and see if the device is available */
	onenand_get_device(mtd, FL_READING);

	column = from & (mtd->oobsize - 1);

	while (read < len) {
		cond_resched();

		thislen = mtd->oobsize - column;
		thislen = oobsize - column;
		thislen = min_t(int, thislen, len);

		this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize);
@@ -832,6 +884,9 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
		ret = this->wait(mtd, FL_READING);
		/* First copy data and check return value for ECC handling */

		if (mode == MTD_OOB_AUTO)
			onenand_transfer_auto_oob(mtd, buf, column, thislen);
		else
			this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen);

		if (ret) {
@@ -871,10 +926,18 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
static int onenand_read_oob(struct mtd_info *mtd, loff_t from,
			    struct mtd_oob_ops *ops)
{
	BUG_ON(ops->mode != MTD_OOB_PLACE);

	switch (ops->mode)
	{
	case MTD_OOB_PLACE:
	case MTD_OOB_AUTO:
		break;
	case MTD_OOB_RAW:
		return -EINVAL; /* Not implemented yet */
	default:
		return -EINVAL;
	}
	return onenand_do_read_oob(mtd, from + ops->ooboffs, ops->ooblen,
				   &ops->oobretlen, ops->oobbuf);
				   &ops->oobretlen, ops->oobbuf, ops->mode);
}

#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE
@@ -883,14 +946,12 @@ static int onenand_read_oob(struct mtd_info *mtd, loff_t from,
 * @param mtd		MTD device structure
 * @param buf		the databuffer to verify
 * @param to		offset to read from
 * @param len		number of bytes to read and compare
 *
 */
static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to, int len)
static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to)
{
	struct onenand_chip *this = mtd->priv;
	char *readp = this->page_buf;
	int column = to & (mtd->oobsize - 1);
	char *readp = this->page_buf + mtd->writesize;
	int status, i;

	this->command(mtd, ONENAND_CMD_READOOB, to, mtd->oobsize);
@@ -899,9 +960,8 @@ static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to
	if (status)
		return status;

	this->read_bufferram(mtd, ONENAND_SPARERAM, readp, column, len);

	for(i = 0; i < len; i++)
	this->read_bufferram(mtd, ONENAND_SPARERAM, readp, 0, mtd->oobsize);
	for(i = 0; i < mtd->oobsize; i++)
		if (buf[i] != 0xFF && buf[i] != readp[i])
			return -EBADMSG;

@@ -1059,6 +1119,44 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
	return ret;
}

/**
 * onenand_fill_auto_oob - [Internal] oob auto-placement transfer
 * @param mtd		MTD device structure
 * @param oob_buf	oob buffer
 * @param buf		source address
 * @param column	oob offset to write to
 * @param thislen	oob length to write
 */
static int onenand_fill_auto_oob(struct mtd_info *mtd, u_char *oob_buf,
				  const u_char *buf, int column, int thislen)
{
	struct onenand_chip *this = mtd->priv;
	struct nand_oobfree *free;
	int writecol = column;
	int writeend = column + thislen;
	int lastgap = 0;

	for (free = this->ecclayout->oobfree; free->length; ++free) {
		if (writecol >= lastgap)
			writecol += free->offset - lastgap;
		if (writeend >= lastgap)
			writeend += free->offset - lastgap;
		lastgap = free->offset + free->length;
	}
	writeend = mtd->oobsize;
	for (free = this->ecclayout->oobfree; free->length; ++free) {
		int free_end = free->offset + free->length;
		if (free->offset < writeend && free_end > writecol) {
			int st = max_t(int,free->offset,writecol);
			int ed = min_t(int,free_end,writeend);
			int n = ed - st;
			memcpy(oob_buf + st, buf, n);
			buf += n;
		}
	}
	return 0;
}

/**
 * onenand_do_write_oob - [Internal] OneNAND write out-of-band
 * @param mtd		MTD device structure
@@ -1066,14 +1164,15 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
 * @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
 * @param mode		operation mode
 *
 * OneNAND write out-of-band
 */
static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
				size_t *retlen, const u_char *buf)
				size_t *retlen, const u_char *buf, mtd_oob_mode_t mode)
{
	struct onenand_chip *this = mtd->priv;
	int column, ret = 0;
	int column, ret = 0, oobsize;
	int written = 0;

	DEBUG(MTD_DEBUG_LEVEL3, "onenand_write_oob: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
@@ -1081,9 +1180,23 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
	/* Initialize retlen, in case of early exit */
	*retlen = 0;

	/* Do not allow writes past end of device */
	if (unlikely((to + len) > mtd->size)) {
		DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: Attempt write to past end of device\n");
	if (mode == MTD_OOB_AUTO)
		oobsize = this->ecclayout->oobavail;
	else
		oobsize = mtd->oobsize;

	column = to & (mtd->oobsize - 1);

	if (unlikely(column >= oobsize)) {
		DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: Attempted to start write outside oob\n");
		return -EINVAL;
	}

	/* Do not allow reads past end of device */
	if (unlikely(to >= mtd->size ||
		     column + len > ((mtd->size >> this->page_shift) -
				     (to >> this->page_shift)) * oobsize)) {
		DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: Attempted to write past end of device\n");
		return -EINVAL;
	}

@@ -1092,17 +1205,18 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,

	/* Loop until all data write */
	while (written < len) {
		int thislen = min_t(int, mtd->oobsize, len - written);
		int thislen = min_t(int, oobsize, len - written);

		cond_resched();

		column = to & (mtd->oobsize - 1);

		this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize);

		/* We send data to spare ram with oobsize
		 * to prevent byte access */
		memset(this->page_buf, 0xff, mtd->oobsize);
		if (mode == MTD_OOB_AUTO)
			onenand_fill_auto_oob(mtd, this->page_buf, buf, column, thislen);
		else
			memcpy(this->page_buf + column, buf, thislen);
		this->write_bufferram(mtd, ONENAND_SPARERAM, this->page_buf, 0, mtd->oobsize);

@@ -1112,11 +1226,11 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,

		ret = this->wait(mtd, FL_WRITING);
		if (ret) {
			DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: write filaed %d\n", ret);
			DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: write failed %d\n", ret);
			goto out;
		}

		ret = onenand_verify_oob(mtd, buf, to, thislen);
		ret = onenand_verify_oob(mtd, this->page_buf, to);
		if (ret) {
			DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: verify failed %d\n", ret);
			goto out;
@@ -1127,8 +1241,9 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
		if (written == len)
			break;

		to += thislen;
		to += mtd->writesize;
		buf += thislen;
		column = 0;
	}

out:
@@ -1149,10 +1264,18 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
static int onenand_write_oob(struct mtd_info *mtd, loff_t to,
			     struct mtd_oob_ops *ops)
{
	BUG_ON(ops->mode != MTD_OOB_PLACE);

	switch (ops->mode)
	{
	case MTD_OOB_PLACE:
	case MTD_OOB_AUTO:
		break;
	case MTD_OOB_RAW:
		return -EINVAL; /* Not implemented yet */
	default:
		return -EINVAL;
	}
	return onenand_do_write_oob(mtd, to + ops->ooboffs, ops->ooblen,
				    &ops->oobretlen, ops->oobbuf);
				    &ops->oobretlen, ops->oobbuf, ops->mode);
}

/**
@@ -1318,7 +1441,7 @@ static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)

        /* We write two bytes, so we dont have to mess with 16 bit access */
        ofs += mtd->oobsize + (bbm->badblockpos & ~0x01);
        return onenand_do_write_oob(mtd, ofs , 2, &retlen, buf);
        return onenand_do_write_oob(mtd, ofs , 2, &retlen, buf, MTD_OOB_PLACE);
}

/**
@@ -1612,7 +1735,7 @@ static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len,
	this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0);
	this->wait(mtd, FL_OTPING);

	ret = onenand_do_write_oob(mtd, from, len, retlen, buf);
	ret = onenand_do_write_oob(mtd, from, len, retlen, buf, MTD_OOB_PLACE);

	/* Exit OTP access mode */
	this->command(mtd, ONENAND_CMD_RESET, 0, 0);
@@ -2019,6 +2142,7 @@ static void onenand_resume(struct mtd_info *mtd)
 */
int onenand_scan(struct mtd_info *mtd, int maxchips)
{
	int i;
	struct onenand_chip *this = mtd->priv;

	if (!this->read_word)
@@ -2090,6 +2214,16 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
	}

	this->subpagesize = mtd->writesize >> mtd->subpage_sft;

	/*
	 * The number of bytes available for a client to place data into
	 * the out of band area
	 */
	this->ecclayout->oobavail = 0;
	for (i = 0; this->ecclayout->oobfree[i].length; i++)
		this->ecclayout->oobavail +=
			this->ecclayout->oobfree[i].length;

	mtd->ecclayout = this->ecclayout;

	/* Fill in remaining MTD driver data */
+2 −2
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@
#include <linux/mtd/compatmac.h>

extern int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
			       size_t *retlen, u_char *buf);
			       size_t *retlen, u_char *buf, mtd_oob_mode_t mode);

/**
 * check_short_pattern - [GENERIC] check if a pattern is in the buffer
@@ -91,7 +91,7 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr
			/* No need to read pages fully,
			 * just read required OOB bytes */
			ret = onenand_do_read_oob(mtd, from + j * mtd->writesize + bd->offs,
						  readlen, &retlen, &buf[0]);
						  readlen, &retlen, &buf[0], MTD_OOB_PLACE);

			/* If it is a initial bad block, just ignore it */
			if (ret && !(ret & ONENAND_CTRL_LOAD))