[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: Adrian Hunter <ext-adrian.hunter@nokia.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
This commit is contained in:
Adrian Hunter 2007-01-31 17:19:28 +02:00 committed by Kyungmin Park
parent 9bfbc9b24f
commit a5e7c7b447
2 changed files with 171 additions and 37 deletions

View file

@ -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; 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 * onenand_do_read_oob - [MTD Interface] OneNAND read out-of-band
* @param mtd MTD device structure * @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 len number of bytes to read
* @param retlen pointer to variable to store the number of read bytes * @param retlen pointer to variable to store the number of read bytes
* @param buf the databuffer to put data * @param buf the databuffer to put data
* @param mode operation mode
* *
* OneNAND read out-of-band data from the spare area * 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, 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; struct onenand_chip *this = mtd->priv;
int read = 0, thislen, column; int read = 0, thislen, column, oobsize;
int ret = 0; int ret = 0;
DEBUG(MTD_DEBUG_LEVEL3, "onenand_read_oob: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len); 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 */ /* Initialize return length value */
*retlen = 0; *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 */ /* Do not allow reads past end of device */
if (unlikely((from + len) > mtd->size)) { if (unlikely(from >= mtd->size ||
DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: Attempt read beyond end of device\n"); 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; return -EINVAL;
} }
/* Grab the lock and see if the device is available */ /* Grab the lock and see if the device is available */
onenand_get_device(mtd, FL_READING); onenand_get_device(mtd, FL_READING);
column = from & (mtd->oobsize - 1);
while (read < len) { while (read < len) {
cond_resched(); cond_resched();
thislen = mtd->oobsize - column; thislen = oobsize - column;
thislen = min_t(int, thislen, len); thislen = min_t(int, thislen, len);
this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize); this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize);
@ -832,7 +884,10 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
ret = this->wait(mtd, FL_READING); ret = this->wait(mtd, FL_READING);
/* First copy data and check return value for ECC handling */ /* First copy data and check return value for ECC handling */
this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen); 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) { if (ret) {
DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: read failed = 0x%x\n", ret); DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: read failed = 0x%x\n", 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, static int onenand_read_oob(struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops) 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, 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 #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 mtd MTD device structure
* @param buf the databuffer to verify * @param buf the databuffer to verify
* @param to offset to read from * @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; struct onenand_chip *this = mtd->priv;
char *readp = this->page_buf; char *readp = this->page_buf + mtd->writesize;
int column = to & (mtd->oobsize - 1);
int status, i; int status, i;
this->command(mtd, ONENAND_CMD_READOOB, to, mtd->oobsize); 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) if (status)
return status; return status;
this->read_bufferram(mtd, ONENAND_SPARERAM, readp, column, len); this->read_bufferram(mtd, ONENAND_SPARERAM, readp, 0, mtd->oobsize);
for(i = 0; i < mtd->oobsize; i++)
for(i = 0; i < len; i++)
if (buf[i] != 0xFF && buf[i] != readp[i]) if (buf[i] != 0xFF && buf[i] != readp[i])
return -EBADMSG; return -EBADMSG;
@ -1059,6 +1119,44 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
return ret; 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 * onenand_do_write_oob - [Internal] OneNAND write out-of-band
* @param mtd MTD device structure * @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 len number of bytes to write
* @param retlen pointer to variable to store the number of written bytes * @param retlen pointer to variable to store the number of written bytes
* @param buf the data to write * @param buf the data to write
* @param mode operation mode
* *
* OneNAND write out-of-band * OneNAND write out-of-band
*/ */
static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len, 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; struct onenand_chip *this = mtd->priv;
int column, ret = 0; int column, ret = 0, oobsize;
int written = 0; int written = 0;
DEBUG(MTD_DEBUG_LEVEL3, "onenand_write_oob: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len); 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 */ /* Initialize retlen, in case of early exit */
*retlen = 0; *retlen = 0;
/* Do not allow writes past end of device */ if (mode == MTD_OOB_AUTO)
if (unlikely((to + len) > mtd->size)) { oobsize = this->ecclayout->oobavail;
DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: Attempt write to past end of device\n"); 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; return -EINVAL;
} }
@ -1092,18 +1205,19 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
/* Loop until all data write */ /* Loop until all data write */
while (written < len) { while (written < len) {
int thislen = min_t(int, mtd->oobsize, len - written); int thislen = min_t(int, oobsize, len - written);
cond_resched(); cond_resched();
column = to & (mtd->oobsize - 1);
this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize); this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize);
/* We send data to spare ram with oobsize /* We send data to spare ram with oobsize
* to prevent byte access */ * to prevent byte access */
memset(this->page_buf, 0xff, mtd->oobsize); memset(this->page_buf, 0xff, mtd->oobsize);
memcpy(this->page_buf + column, buf, thislen); 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); this->write_bufferram(mtd, ONENAND_SPARERAM, this->page_buf, 0, mtd->oobsize);
this->command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize); this->command(mtd, ONENAND_CMD_PROGOOB, to, 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); ret = this->wait(mtd, FL_WRITING);
if (ret) { 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; goto out;
} }
ret = onenand_verify_oob(mtd, buf, to, thislen); ret = onenand_verify_oob(mtd, this->page_buf, to);
if (ret) { if (ret) {
DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: verify failed %d\n", ret); DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: verify failed %d\n", ret);
goto out; 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) if (written == len)
break; break;
to += thislen; to += mtd->writesize;
buf += thislen; buf += thislen;
column = 0;
} }
out: 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, static int onenand_write_oob(struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops) 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, 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 */ /* We write two bytes, so we dont have to mess with 16 bit access */
ofs += mtd->oobsize + (bbm->badblockpos & ~0x01); 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->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0);
this->wait(mtd, FL_OTPING); 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 */ /* Exit OTP access mode */
this->command(mtd, ONENAND_CMD_RESET, 0, 0); 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 onenand_scan(struct mtd_info *mtd, int maxchips)
{ {
int i;
struct onenand_chip *this = mtd->priv; struct onenand_chip *this = mtd->priv;
if (!this->read_word) if (!this->read_word)
@ -2090,6 +2214,16 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
} }
this->subpagesize = mtd->writesize >> mtd->subpage_sft; 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; mtd->ecclayout = this->ecclayout;
/* Fill in remaining MTD driver data */ /* Fill in remaining MTD driver data */

View file

@ -18,7 +18,7 @@
#include <linux/mtd/compatmac.h> #include <linux/mtd/compatmac.h>
extern int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len, 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 * 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, /* No need to read pages fully,
* just read required OOB bytes */ * just read required OOB bytes */
ret = onenand_do_read_oob(mtd, from + j * mtd->writesize + bd->offs, 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 it is a initial bad block, just ignore it */
if (ret && !(ret & ONENAND_CTRL_LOAD)) if (ret && !(ret & ONENAND_CTRL_LOAD))