|
@@ -1,17 +1,19 @@
|
|
|
/*
|
|
|
* linux/drivers/mtd/onenand/onenand_base.c
|
|
|
*
|
|
|
- * Copyright (C) 2005-2007 Samsung Electronics
|
|
|
+ * Copyright © 2005-2009 Samsung Electronics
|
|
|
+ * Copyright © 2007 Nokia Corporation
|
|
|
+ *
|
|
|
* Kyungmin Park <kyungmin.park@samsung.com>
|
|
|
*
|
|
|
* Credits:
|
|
|
* Adrian Hunter <ext-adrian.hunter@nokia.com>:
|
|
|
* auto-placement support, read-while load support, various fixes
|
|
|
- * Copyright (C) Nokia Corporation, 2007
|
|
|
*
|
|
|
* Vishak G <vishak.g at samsung.com>, Rohit Hagargundgi <h.rohit at samsung.com>
|
|
|
* Flex-OneNAND support
|
|
|
- * Copyright (C) Samsung Electronics, 2008
|
|
|
+ * Amul Kumar Saha <amul.saha at samsung.com>
|
|
|
+ * OTP support
|
|
|
*
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
@@ -32,6 +34,13 @@
|
|
|
|
|
|
#include <asm/io.h>
|
|
|
|
|
|
+/*
|
|
|
+ * Multiblock erase if number of blocks to erase is 2 or more.
|
|
|
+ * Maximum number of blocks for simultaneous erase is 64.
|
|
|
+ */
|
|
|
+#define MB_ERASE_MIN_BLK_COUNT 2
|
|
|
+#define MB_ERASE_MAX_BLK_COUNT 64
|
|
|
+
|
|
|
/* Default Flex-OneNAND boundary and lock respectively */
|
|
|
static int flex_bdry[MAX_DIES * 2] = { -1, 0, -1, 0 };
|
|
|
|
|
@@ -43,6 +52,18 @@ MODULE_PARM_DESC(flex_bdry, "SLC Boundary information for Flex-OneNAND"
|
|
|
" : 0->Set boundary in unlocked status"
|
|
|
" : 1->Set boundary in locked status");
|
|
|
|
|
|
+/* Default OneNAND/Flex-OneNAND OTP options*/
|
|
|
+static int otp;
|
|
|
+
|
|
|
+module_param(otp, int, 0400);
|
|
|
+MODULE_PARM_DESC(otp, "Corresponding behaviour of OneNAND in OTP"
|
|
|
+ "Syntax : otp=LOCK_TYPE"
|
|
|
+ "LOCK_TYPE : Keys issued, for specific OTP Lock type"
|
|
|
+ " : 0 -> Default (No Blocks Locked)"
|
|
|
+ " : 1 -> OTP Block lock"
|
|
|
+ " : 2 -> 1st Block lock"
|
|
|
+ " : 3 -> BOTH OTP Block and 1st Block lock");
|
|
|
+
|
|
|
/**
|
|
|
* onenand_oob_128 - oob info for Flex-Onenand with 4KB page
|
|
|
* For now, we expose only 64 out of 80 ecc bytes
|
|
@@ -339,6 +360,8 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le
|
|
|
break;
|
|
|
|
|
|
case ONENAND_CMD_ERASE:
|
|
|
+ case ONENAND_CMD_MULTIBLOCK_ERASE:
|
|
|
+ case ONENAND_CMD_ERASE_VERIFY:
|
|
|
case ONENAND_CMD_BUFFERRAM:
|
|
|
case ONENAND_CMD_OTP_ACCESS:
|
|
|
block = onenand_block(this, addr);
|
|
@@ -483,7 +506,7 @@ static int onenand_wait(struct mtd_info *mtd, int state)
|
|
|
if (interrupt & flags)
|
|
|
break;
|
|
|
|
|
|
- if (state != FL_READING)
|
|
|
+ if (state != FL_READING && state != FL_PREPARING_ERASE)
|
|
|
cond_resched();
|
|
|
}
|
|
|
/* To get correct interrupt status in timeout case */
|
|
@@ -500,25 +523,40 @@ static int onenand_wait(struct mtd_info *mtd, int state)
|
|
|
int ecc = onenand_read_ecc(this);
|
|
|
if (ecc) {
|
|
|
if (ecc & ONENAND_ECC_2BIT_ALL) {
|
|
|
- printk(KERN_ERR "onenand_wait: ECC error = 0x%04x\n", ecc);
|
|
|
+ printk(KERN_ERR "%s: ECC error = 0x%04x\n",
|
|
|
+ __func__, ecc);
|
|
|
mtd->ecc_stats.failed++;
|
|
|
return -EBADMSG;
|
|
|
} else if (ecc & ONENAND_ECC_1BIT_ALL) {
|
|
|
- printk(KERN_DEBUG "onenand_wait: correctable ECC error = 0x%04x\n", ecc);
|
|
|
+ printk(KERN_DEBUG "%s: correctable ECC error = 0x%04x\n",
|
|
|
+ __func__, ecc);
|
|
|
mtd->ecc_stats.corrected++;
|
|
|
}
|
|
|
}
|
|
|
} else if (state == FL_READING) {
|
|
|
- printk(KERN_ERR "onenand_wait: read timeout! ctrl=0x%04x intr=0x%04x\n", ctrl, interrupt);
|
|
|
+ printk(KERN_ERR "%s: read timeout! ctrl=0x%04x intr=0x%04x\n",
|
|
|
+ __func__, ctrl, interrupt);
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (state == FL_PREPARING_ERASE && !(interrupt & ONENAND_INT_ERASE)) {
|
|
|
+ printk(KERN_ERR "%s: mb erase timeout! ctrl=0x%04x intr=0x%04x\n",
|
|
|
+ __func__, ctrl, interrupt);
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!(interrupt & ONENAND_INT_MASTER)) {
|
|
|
+ printk(KERN_ERR "%s: timeout! ctrl=0x%04x intr=0x%04x\n",
|
|
|
+ __func__, ctrl, interrupt);
|
|
|
return -EIO;
|
|
|
}
|
|
|
|
|
|
/* If there's controller error, it's a real error */
|
|
|
if (ctrl & ONENAND_CTRL_ERROR) {
|
|
|
- printk(KERN_ERR "onenand_wait: controller error = 0x%04x\n",
|
|
|
- ctrl);
|
|
|
+ printk(KERN_ERR "%s: controller error = 0x%04x\n",
|
|
|
+ __func__, ctrl);
|
|
|
if (ctrl & ONENAND_CTRL_LOCK)
|
|
|
- printk(KERN_ERR "onenand_wait: it's locked error.\n");
|
|
|
+ printk(KERN_ERR "%s: it's locked error.\n", __func__);
|
|
|
return -EIO;
|
|
|
}
|
|
|
|
|
@@ -1015,7 +1053,8 @@ static int onenand_recover_lsb(struct mtd_info *mtd, loff_t addr, int status)
|
|
|
/* We are attempting to reread, so decrement stats.failed
|
|
|
* which was incremented by onenand_wait due to read failure
|
|
|
*/
|
|
|
- printk(KERN_INFO "onenand_recover_lsb: Attempting to recover from uncorrectable read\n");
|
|
|
+ printk(KERN_INFO "%s: Attempting to recover from uncorrectable read\n",
|
|
|
+ __func__);
|
|
|
mtd->ecc_stats.failed--;
|
|
|
|
|
|
/* Issue the LSB page recovery command */
|
|
@@ -1046,7 +1085,8 @@ static int onenand_mlc_read_ops_nolock(struct mtd_info *mtd, loff_t from,
|
|
|
int ret = 0;
|
|
|
int writesize = this->writesize;
|
|
|
|
|
|
- DEBUG(MTD_DEBUG_LEVEL3, "onenand_mlc_read_ops_nolock: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
|
|
|
+ DEBUG(MTD_DEBUG_LEVEL3, "%s: from = 0x%08x, len = %i\n",
|
|
|
+ __func__, (unsigned int) from, (int) len);
|
|
|
|
|
|
if (ops->mode == MTD_OOB_AUTO)
|
|
|
oobsize = this->ecclayout->oobavail;
|
|
@@ -1057,7 +1097,8 @@ static int onenand_mlc_read_ops_nolock(struct mtd_info *mtd, loff_t from,
|
|
|
|
|
|
/* Do not allow reads past end of device */
|
|
|
if (from + len > mtd->size) {
|
|
|
- printk(KERN_ERR "onenand_mlc_read_ops_nolock: Attempt read beyond end of device\n");
|
|
|
+ printk(KERN_ERR "%s: Attempt read beyond end of device\n",
|
|
|
+ __func__);
|
|
|
ops->retlen = 0;
|
|
|
ops->oobretlen = 0;
|
|
|
return -EINVAL;
|
|
@@ -1146,7 +1187,8 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from,
|
|
|
int ret = 0, boundary = 0;
|
|
|
int writesize = this->writesize;
|
|
|
|
|
|
- DEBUG(MTD_DEBUG_LEVEL3, "onenand_read_ops_nolock: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
|
|
|
+ DEBUG(MTD_DEBUG_LEVEL3, "%s: from = 0x%08x, len = %i\n",
|
|
|
+ __func__, (unsigned int) from, (int) len);
|
|
|
|
|
|
if (ops->mode == MTD_OOB_AUTO)
|
|
|
oobsize = this->ecclayout->oobavail;
|
|
@@ -1157,7 +1199,8 @@ static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from,
|
|
|
|
|
|
/* Do not allow reads past end of device */
|
|
|
if ((from + len) > mtd->size) {
|
|
|
- printk(KERN_ERR "onenand_read_ops_nolock: Attempt read beyond end of device\n");
|
|
|
+ printk(KERN_ERR "%s: Attempt read beyond end of device\n",
|
|
|
+ __func__);
|
|
|
ops->retlen = 0;
|
|
|
ops->oobretlen = 0;
|
|
|
return -EINVAL;
|
|
@@ -1275,7 +1318,8 @@ static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from,
|
|
|
|
|
|
from += ops->ooboffs;
|
|
|
|
|
|
- DEBUG(MTD_DEBUG_LEVEL3, "onenand_read_oob_nolock: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
|
|
|
+ DEBUG(MTD_DEBUG_LEVEL3, "%s: from = 0x%08x, len = %i\n",
|
|
|
+ __func__, (unsigned int) from, (int) len);
|
|
|
|
|
|
/* Initialize return length value */
|
|
|
ops->oobretlen = 0;
|
|
@@ -1288,7 +1332,8 @@ static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from,
|
|
|
column = from & (mtd->oobsize - 1);
|
|
|
|
|
|
if (unlikely(column >= oobsize)) {
|
|
|
- printk(KERN_ERR "onenand_read_oob_nolock: Attempted to start read outside oob\n");
|
|
|
+ printk(KERN_ERR "%s: Attempted to start read outside oob\n",
|
|
|
+ __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
@@ -1296,7 +1341,8 @@ static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from,
|
|
|
if (unlikely(from >= mtd->size ||
|
|
|
column + len > ((mtd->size >> this->page_shift) -
|
|
|
(from >> this->page_shift)) * oobsize)) {
|
|
|
- printk(KERN_ERR "onenand_read_oob_nolock: Attempted to read beyond end of device\n");
|
|
|
+ printk(KERN_ERR "%s: Attempted to read beyond end of device\n",
|
|
|
+ __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
@@ -1319,7 +1365,8 @@ static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from,
|
|
|
ret = onenand_recover_lsb(mtd, from, ret);
|
|
|
|
|
|
if (ret && ret != -EBADMSG) {
|
|
|
- printk(KERN_ERR "onenand_read_oob_nolock: read failed = 0x%x\n", ret);
|
|
|
+ printk(KERN_ERR "%s: read failed = 0x%x\n",
|
|
|
+ __func__, ret);
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -1450,20 +1497,21 @@ static int onenand_bbt_wait(struct mtd_info *mtd, int state)
|
|
|
if (interrupt & ONENAND_INT_READ) {
|
|
|
int ecc = onenand_read_ecc(this);
|
|
|
if (ecc & ONENAND_ECC_2BIT_ALL) {
|
|
|
- printk(KERN_INFO "onenand_bbt_wait: ecc error = 0x%04x"
|
|
|
- ", controller error 0x%04x\n", ecc, ctrl);
|
|
|
+ printk(KERN_WARNING "%s: ecc error = 0x%04x, "
|
|
|
+ "controller error 0x%04x\n",
|
|
|
+ __func__, ecc, ctrl);
|
|
|
return ONENAND_BBT_READ_ECC_ERROR;
|
|
|
}
|
|
|
} else {
|
|
|
- printk(KERN_ERR "onenand_bbt_wait: read timeout!"
|
|
|
- "ctrl=0x%04x intr=0x%04x\n", ctrl, interrupt);
|
|
|
+ printk(KERN_ERR "%s: read timeout! ctrl=0x%04x intr=0x%04x\n",
|
|
|
+ __func__, ctrl, interrupt);
|
|
|
return ONENAND_BBT_READ_FATAL_ERROR;
|
|
|
}
|
|
|
|
|
|
/* Initial bad block case: 0x2400 or 0x0400 */
|
|
|
if (ctrl & ONENAND_CTRL_ERROR) {
|
|
|
- printk(KERN_DEBUG "onenand_bbt_wait: "
|
|
|
- "controller error = 0x%04x\n", ctrl);
|
|
|
+ printk(KERN_DEBUG "%s: controller error = 0x%04x\n",
|
|
|
+ __func__, ctrl);
|
|
|
return ONENAND_BBT_READ_ERROR;
|
|
|
}
|
|
|
|
|
@@ -1487,14 +1535,16 @@ int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from,
|
|
|
size_t len = ops->ooblen;
|
|
|
u_char *buf = ops->oobbuf;
|
|
|
|
|
|
- DEBUG(MTD_DEBUG_LEVEL3, "onenand_bbt_read_oob: from = 0x%08x, len = %zi\n", (unsigned int) from, len);
|
|
|
+ DEBUG(MTD_DEBUG_LEVEL3, "%s: from = 0x%08x, len = %zi\n",
|
|
|
+ __func__, (unsigned int) from, len);
|
|
|
|
|
|
/* Initialize return value */
|
|
|
ops->oobretlen = 0;
|
|
|
|
|
|
/* Do not allow reads past end of device */
|
|
|
if (unlikely((from + len) > mtd->size)) {
|
|
|
- printk(KERN_ERR "onenand_bbt_read_oob: Attempt read beyond end of device\n");
|
|
|
+ printk(KERN_ERR "%s: Attempt read beyond end of device\n",
|
|
|
+ __func__);
|
|
|
return ONENAND_BBT_READ_FATAL_ERROR;
|
|
|
}
|
|
|
|
|
@@ -1661,21 +1711,23 @@ static int onenand_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|
|
/* Wait for any existing operation to clear */
|
|
|
onenand_panic_wait(mtd);
|
|
|
|
|
|
- DEBUG(MTD_DEBUG_LEVEL3, "onenand_panic_write: to = 0x%08x, len = %i\n",
|
|
|
- (unsigned int) to, (int) len);
|
|
|
+ DEBUG(MTD_DEBUG_LEVEL3, "%s: to = 0x%08x, len = %i\n",
|
|
|
+ __func__, (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_panic_write: Attempt write to past end of device\n");
|
|
|
+ printk(KERN_ERR "%s: Attempt write to past end of device\n",
|
|
|
+ __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
/* Reject writes, which are not page aligned */
|
|
|
if (unlikely(NOTALIGNED(to) || NOTALIGNED(len))) {
|
|
|
- printk(KERN_ERR "onenand_panic_write: Attempt to write not page aligned data\n");
|
|
|
+ printk(KERN_ERR "%s: Attempt to write not page aligned data\n",
|
|
|
+ __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
@@ -1711,7 +1763,7 @@ static int onenand_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|
|
}
|
|
|
|
|
|
if (ret) {
|
|
|
- printk(KERN_ERR "onenand_panic_write: write failed %d\n", ret);
|
|
|
+ printk(KERN_ERR "%s: write failed %d\n", __func__, ret);
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -1792,7 +1844,8 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
u_char *oobbuf;
|
|
|
int ret = 0;
|
|
|
|
|
|
- DEBUG(MTD_DEBUG_LEVEL3, "onenand_write_ops_nolock: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
|
|
|
+ DEBUG(MTD_DEBUG_LEVEL3, "%s: to = 0x%08x, len = %i\n",
|
|
|
+ __func__, (unsigned int) to, (int) len);
|
|
|
|
|
|
/* Initialize retlen, in case of early exit */
|
|
|
ops->retlen = 0;
|
|
@@ -1800,13 +1853,15 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
|
|
|
/* Do not allow writes past end of device */
|
|
|
if (unlikely((to + len) > mtd->size)) {
|
|
|
- printk(KERN_ERR "onenand_write_ops_nolock: Attempt write to past end of device\n");
|
|
|
+ printk(KERN_ERR "%s: Attempt write to past end of device\n",
|
|
|
+ __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
/* Reject writes, which are not page aligned */
|
|
|
if (unlikely(NOTALIGNED(to) || NOTALIGNED(len))) {
|
|
|
- printk(KERN_ERR "onenand_write_ops_nolock: Attempt to write not page aligned data\n");
|
|
|
+ printk(KERN_ERR "%s: Attempt to write not page aligned data\n",
|
|
|
+ __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
@@ -1879,7 +1934,8 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
onenand_update_bufferram(mtd, prev, !ret && !prev_subpage);
|
|
|
if (ret) {
|
|
|
written -= prevlen;
|
|
|
- printk(KERN_ERR "onenand_write_ops_nolock: write failed %d\n", ret);
|
|
|
+ printk(KERN_ERR "%s: write failed %d\n",
|
|
|
+ __func__, ret);
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -1887,7 +1943,8 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
/* Only check verify write turn on */
|
|
|
ret = onenand_verify(mtd, buf - len, to - len, len);
|
|
|
if (ret)
|
|
|
- printk(KERN_ERR "onenand_write_ops_nolock: verify failed %d\n", ret);
|
|
|
+ printk(KERN_ERR "%s: verify failed %d\n",
|
|
|
+ __func__, ret);
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -1905,14 +1962,16 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
/* In partial page write we don't update bufferram */
|
|
|
onenand_update_bufferram(mtd, to, !ret && !subpage);
|
|
|
if (ret) {
|
|
|
- printk(KERN_ERR "onenand_write_ops_nolock: write failed %d\n", ret);
|
|
|
+ printk(KERN_ERR "%s: write failed %d\n",
|
|
|
+ __func__, ret);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
/* Only check verify write turn on */
|
|
|
ret = onenand_verify(mtd, buf, to, thislen);
|
|
|
if (ret) {
|
|
|
- printk(KERN_ERR "onenand_write_ops_nolock: verify failed %d\n", ret);
|
|
|
+ printk(KERN_ERR "%s: verify failed %d\n",
|
|
|
+ __func__, ret);
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -1968,7 +2027,8 @@ static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
|
|
|
to += ops->ooboffs;
|
|
|
|
|
|
- DEBUG(MTD_DEBUG_LEVEL3, "onenand_write_oob_nolock: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
|
|
|
+ DEBUG(MTD_DEBUG_LEVEL3, "%s: to = 0x%08x, len = %i\n",
|
|
|
+ __func__, (unsigned int) to, (int) len);
|
|
|
|
|
|
/* Initialize retlen, in case of early exit */
|
|
|
ops->oobretlen = 0;
|
|
@@ -1981,14 +2041,15 @@ static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
column = to & (mtd->oobsize - 1);
|
|
|
|
|
|
if (unlikely(column >= oobsize)) {
|
|
|
- printk(KERN_ERR "onenand_write_oob_nolock: Attempted to start write outside oob\n");
|
|
|
+ printk(KERN_ERR "%s: Attempted to start write outside oob\n",
|
|
|
+ __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
/* For compatibility with NAND: Do not allow write past end of page */
|
|
|
if (unlikely(column + len > oobsize)) {
|
|
|
- printk(KERN_ERR "onenand_write_oob_nolock: "
|
|
|
- "Attempt to write past end of page\n");
|
|
|
+ printk(KERN_ERR "%s: Attempt to write past end of page\n",
|
|
|
+ __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
@@ -1996,7 +2057,8 @@ static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
if (unlikely(to >= mtd->size ||
|
|
|
column + len > ((mtd->size >> this->page_shift) -
|
|
|
(to >> this->page_shift)) * oobsize)) {
|
|
|
- printk(KERN_ERR "onenand_write_oob_nolock: Attempted to write past end of device\n");
|
|
|
+ printk(KERN_ERR "%s: Attempted to write past end of device\n",
|
|
|
+ __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
@@ -2038,13 +2100,14 @@ static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
|
|
|
ret = this->wait(mtd, FL_WRITING);
|
|
|
if (ret) {
|
|
|
- printk(KERN_ERR "onenand_write_oob_nolock: write failed %d\n", ret);
|
|
|
+ printk(KERN_ERR "%s: write failed %d\n", __func__, ret);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
ret = onenand_verify_oob(mtd, oobbuf, to);
|
|
|
if (ret) {
|
|
|
- printk(KERN_ERR "onenand_write_oob_nolock: verify failed %d\n", ret);
|
|
|
+ printk(KERN_ERR "%s: verify failed %d\n",
|
|
|
+ __func__, ret);
|
|
|
break;
|
|
|
}
|
|
|
|
|
@@ -2140,78 +2203,186 @@ static int onenand_block_isbad_nolock(struct mtd_info *mtd, loff_t ofs, int allo
|
|
|
return bbm->isbad_bbt(mtd, ofs, allowbbt);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+static int onenand_multiblock_erase_verify(struct mtd_info *mtd,
|
|
|
+ struct erase_info *instr)
|
|
|
+{
|
|
|
+ struct onenand_chip *this = mtd->priv;
|
|
|
+ loff_t addr = instr->addr;
|
|
|
+ int len = instr->len;
|
|
|
+ unsigned int block_size = (1 << this->erase_shift);
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ while (len) {
|
|
|
+ this->command(mtd, ONENAND_CMD_ERASE_VERIFY, addr, block_size);
|
|
|
+ ret = this->wait(mtd, FL_VERIFYING_ERASE);
|
|
|
+ if (ret) {
|
|
|
+ printk(KERN_ERR "%s: Failed verify, block %d\n",
|
|
|
+ __func__, onenand_block(this, addr));
|
|
|
+ instr->state = MTD_ERASE_FAILED;
|
|
|
+ instr->fail_addr = addr;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ len -= block_size;
|
|
|
+ addr += block_size;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
- * onenand_erase - [MTD Interface] erase block(s)
|
|
|
+ * onenand_multiblock_erase - [Internal] erase block(s) using multiblock erase
|
|
|
* @param mtd MTD device structure
|
|
|
* @param instr erase instruction
|
|
|
+ * @param region erase region
|
|
|
*
|
|
|
- * Erase one ore more blocks
|
|
|
+ * Erase one or more blocks up to 64 block at a time
|
|
|
*/
|
|
|
-static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|
|
+static int onenand_multiblock_erase(struct mtd_info *mtd,
|
|
|
+ struct erase_info *instr,
|
|
|
+ unsigned int block_size)
|
|
|
{
|
|
|
struct onenand_chip *this = mtd->priv;
|
|
|
- unsigned int block_size;
|
|
|
loff_t addr = instr->addr;
|
|
|
- loff_t len = instr->len;
|
|
|
- int ret = 0, i;
|
|
|
- struct mtd_erase_region_info *region = NULL;
|
|
|
- loff_t region_end = 0;
|
|
|
+ int len = instr->len;
|
|
|
+ int eb_count = 0;
|
|
|
+ int ret = 0;
|
|
|
+ int bdry_block = 0;
|
|
|
|
|
|
- DEBUG(MTD_DEBUG_LEVEL3, "onenand_erase: start = 0x%012llx, len = %llu\n", (unsigned long long) instr->addr, (unsigned long long) instr->len);
|
|
|
+ instr->state = MTD_ERASING;
|
|
|
|
|
|
- /* Do not allow erase past end of device */
|
|
|
- if (unlikely((len + addr) > mtd->size)) {
|
|
|
- printk(KERN_ERR "onenand_erase: Erase past end of device\n");
|
|
|
- return -EINVAL;
|
|
|
+ if (ONENAND_IS_DDP(this)) {
|
|
|
+ loff_t bdry_addr = this->chipsize >> 1;
|
|
|
+ if (addr < bdry_addr && (addr + len) > bdry_addr)
|
|
|
+ bdry_block = bdry_addr >> this->erase_shift;
|
|
|
}
|
|
|
|
|
|
- if (FLEXONENAND(this)) {
|
|
|
- /* Find the eraseregion of this address */
|
|
|
- i = flexonenand_region(mtd, addr);
|
|
|
- region = &mtd->eraseregions[i];
|
|
|
+ /* Pre-check bbs */
|
|
|
+ while (len) {
|
|
|
+ /* Check if we have a bad block, we do not erase bad blocks */
|
|
|
+ if (onenand_block_isbad_nolock(mtd, addr, 0)) {
|
|
|
+ printk(KERN_WARNING "%s: attempt to erase a bad block "
|
|
|
+ "at addr 0x%012llx\n",
|
|
|
+ __func__, (unsigned long long) addr);
|
|
|
+ instr->state = MTD_ERASE_FAILED;
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+ len -= block_size;
|
|
|
+ addr += block_size;
|
|
|
+ }
|
|
|
|
|
|
- block_size = region->erasesize;
|
|
|
- region_end = region->offset + region->erasesize * region->numblocks;
|
|
|
+ len = instr->len;
|
|
|
+ addr = instr->addr;
|
|
|
|
|
|
- /* Start address within region must align on block boundary.
|
|
|
- * Erase region's start offset is always block start address.
|
|
|
- */
|
|
|
- if (unlikely((addr - region->offset) & (block_size - 1))) {
|
|
|
- printk(KERN_ERR "onenand_erase: Unaligned address\n");
|
|
|
- return -EINVAL;
|
|
|
+ /* loop over 64 eb batches */
|
|
|
+ while (len) {
|
|
|
+ struct erase_info verify_instr = *instr;
|
|
|
+ int max_eb_count = MB_ERASE_MAX_BLK_COUNT;
|
|
|
+
|
|
|
+ verify_instr.addr = addr;
|
|
|
+ verify_instr.len = 0;
|
|
|
+
|
|
|
+ /* do not cross chip boundary */
|
|
|
+ if (bdry_block) {
|
|
|
+ int this_block = (addr >> this->erase_shift);
|
|
|
+
|
|
|
+ if (this_block < bdry_block) {
|
|
|
+ max_eb_count = min(max_eb_count,
|
|
|
+ (bdry_block - this_block));
|
|
|
+ }
|
|
|
}
|
|
|
- } else {
|
|
|
- block_size = 1 << this->erase_shift;
|
|
|
|
|
|
- /* Start address must align on block boundary */
|
|
|
- if (unlikely(addr & (block_size - 1))) {
|
|
|
- printk(KERN_ERR "onenand_erase: Unaligned address\n");
|
|
|
- return -EINVAL;
|
|
|
+ eb_count = 0;
|
|
|
+
|
|
|
+ while (len > block_size && eb_count < (max_eb_count - 1)) {
|
|
|
+ this->command(mtd, ONENAND_CMD_MULTIBLOCK_ERASE,
|
|
|
+ addr, block_size);
|
|
|
+ onenand_invalidate_bufferram(mtd, addr, block_size);
|
|
|
+
|
|
|
+ ret = this->wait(mtd, FL_PREPARING_ERASE);
|
|
|
+ if (ret) {
|
|
|
+ printk(KERN_ERR "%s: Failed multiblock erase, "
|
|
|
+ "block %d\n", __func__,
|
|
|
+ onenand_block(this, addr));
|
|
|
+ instr->state = MTD_ERASE_FAILED;
|
|
|
+ instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+
|
|
|
+ len -= block_size;
|
|
|
+ addr += block_size;
|
|
|
+ eb_count++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* last block of 64-eb series */
|
|
|
+ cond_resched();
|
|
|
+ this->command(mtd, ONENAND_CMD_ERASE, addr, block_size);
|
|
|
+ onenand_invalidate_bufferram(mtd, addr, block_size);
|
|
|
+
|
|
|
+ ret = this->wait(mtd, FL_ERASING);
|
|
|
+ /* Check if it is write protected */
|
|
|
+ if (ret) {
|
|
|
+ printk(KERN_ERR "%s: Failed erase, block %d\n",
|
|
|
+ __func__, onenand_block(this, addr));
|
|
|
+ instr->state = MTD_ERASE_FAILED;
|
|
|
+ instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+
|
|
|
+ len -= block_size;
|
|
|
+ addr += block_size;
|
|
|
+ eb_count++;
|
|
|
+
|
|
|
+ /* verify */
|
|
|
+ verify_instr.len = eb_count * block_size;
|
|
|
+ if (onenand_multiblock_erase_verify(mtd, &verify_instr)) {
|
|
|
+ instr->state = verify_instr.state;
|
|
|
+ instr->fail_addr = verify_instr.fail_addr;
|
|
|
+ return -EIO;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- /* Length must align on block boundary */
|
|
|
- if (unlikely(len & (block_size - 1))) {
|
|
|
- printk(KERN_ERR "onenand_erase: Length not block aligned\n");
|
|
|
- return -EINVAL;
|
|
|
}
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
- instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
|
|
|
|
|
|
- /* Grab the lock and see if the device is available */
|
|
|
- onenand_get_device(mtd, FL_ERASING);
|
|
|
+/**
|
|
|
+ * onenand_block_by_block_erase - [Internal] erase block(s) using regular erase
|
|
|
+ * @param mtd MTD device structure
|
|
|
+ * @param instr erase instruction
|
|
|
+ * @param region erase region
|
|
|
+ * @param block_size erase block size
|
|
|
+ *
|
|
|
+ * Erase one or more blocks one block at a time
|
|
|
+ */
|
|
|
+static int onenand_block_by_block_erase(struct mtd_info *mtd,
|
|
|
+ struct erase_info *instr,
|
|
|
+ struct mtd_erase_region_info *region,
|
|
|
+ unsigned int block_size)
|
|
|
+{
|
|
|
+ struct onenand_chip *this = mtd->priv;
|
|
|
+ loff_t addr = instr->addr;
|
|
|
+ int len = instr->len;
|
|
|
+ loff_t region_end = 0;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ if (region) {
|
|
|
+ /* region is set for Flex-OneNAND */
|
|
|
+ region_end = region->offset + region->erasesize * region->numblocks;
|
|
|
+ }
|
|
|
|
|
|
- /* Loop through the blocks */
|
|
|
instr->state = MTD_ERASING;
|
|
|
|
|
|
+ /* Loop through the blocks */
|
|
|
while (len) {
|
|
|
cond_resched();
|
|
|
|
|
|
/* Check if we have a bad block, we do not erase bad blocks */
|
|
|
if (onenand_block_isbad_nolock(mtd, addr, 0)) {
|
|
|
- printk (KERN_WARNING "onenand_erase: attempt to erase a bad block at addr 0x%012llx\n", (unsigned long long) addr);
|
|
|
+ printk(KERN_WARNING "%s: attempt to erase a bad block "
|
|
|
+ "at addr 0x%012llx\n",
|
|
|
+ __func__, (unsigned long long) addr);
|
|
|
instr->state = MTD_ERASE_FAILED;
|
|
|
- goto erase_exit;
|
|
|
+ return -EIO;
|
|
|
}
|
|
|
|
|
|
this->command(mtd, ONENAND_CMD_ERASE, addr, block_size);
|
|
@@ -2221,11 +2392,11 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|
|
ret = this->wait(mtd, FL_ERASING);
|
|
|
/* Check, if it is write protected */
|
|
|
if (ret) {
|
|
|
- printk(KERN_ERR "onenand_erase: Failed erase, block %d\n",
|
|
|
- onenand_block(this, addr));
|
|
|
+ printk(KERN_ERR "%s: Failed erase, block %d\n",
|
|
|
+ __func__, onenand_block(this, addr));
|
|
|
instr->state = MTD_ERASE_FAILED;
|
|
|
instr->fail_addr = addr;
|
|
|
- goto erase_exit;
|
|
|
+ return -EIO;
|
|
|
}
|
|
|
|
|
|
len -= block_size;
|
|
@@ -2241,25 +2412,88 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|
|
|
|
|
if (len & (block_size - 1)) {
|
|
|
/* FIXME: This should be handled at MTD partitioning level. */
|
|
|
- printk(KERN_ERR "onenand_erase: Unaligned address\n");
|
|
|
- goto erase_exit;
|
|
|
+ printk(KERN_ERR "%s: Unaligned address\n",
|
|
|
+ __func__);
|
|
|
+ return -EIO;
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * onenand_erase - [MTD Interface] erase block(s)
|
|
|
+ * @param mtd MTD device structure
|
|
|
+ * @param instr erase instruction
|
|
|
+ *
|
|
|
+ * Erase one or more blocks
|
|
|
+ */
|
|
|
+static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|
|
+{
|
|
|
+ struct onenand_chip *this = mtd->priv;
|
|
|
+ unsigned int block_size;
|
|
|
+ loff_t addr = instr->addr;
|
|
|
+ loff_t len = instr->len;
|
|
|
+ int ret = 0;
|
|
|
+ struct mtd_erase_region_info *region = NULL;
|
|
|
+ loff_t region_offset = 0;
|
|
|
+
|
|
|
+ DEBUG(MTD_DEBUG_LEVEL3, "%s: start=0x%012llx, len=%llu\n", __func__,
|
|
|
+ (unsigned long long) instr->addr, (unsigned long long) instr->len);
|
|
|
+
|
|
|
+ /* Do not allow erase past end of device */
|
|
|
+ if (unlikely((len + addr) > mtd->size)) {
|
|
|
+ printk(KERN_ERR "%s: Erase past end of device\n", __func__);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (FLEXONENAND(this)) {
|
|
|
+ /* Find the eraseregion of this address */
|
|
|
+ int i = flexonenand_region(mtd, addr);
|
|
|
+
|
|
|
+ region = &mtd->eraseregions[i];
|
|
|
+ block_size = region->erasesize;
|
|
|
+
|
|
|
+ /* Start address within region must align on block boundary.
|
|
|
+ * Erase region's start offset is always block start address.
|
|
|
+ */
|
|
|
+ region_offset = region->offset;
|
|
|
+ } else
|
|
|
+ block_size = 1 << this->erase_shift;
|
|
|
+
|
|
|
+ /* Start address must align on block boundary */
|
|
|
+ if (unlikely((addr - region_offset) & (block_size - 1))) {
|
|
|
+ printk(KERN_ERR "%s: Unaligned address\n", __func__);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
|
|
|
+ /* Length must align on block boundary */
|
|
|
+ if (unlikely(len & (block_size - 1))) {
|
|
|
+ printk(KERN_ERR "%s: Length not block aligned\n", __func__);
|
|
|
+ return -EINVAL;
|
|
|
}
|
|
|
|
|
|
- instr->state = MTD_ERASE_DONE;
|
|
|
+ instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
|
|
|
|
|
|
-erase_exit:
|
|
|
+ /* Grab the lock and see if the device is available */
|
|
|
+ onenand_get_device(mtd, FL_ERASING);
|
|
|
|
|
|
- ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO;
|
|
|
+ if (region || instr->len < MB_ERASE_MIN_BLK_COUNT * block_size) {
|
|
|
+ /* region is set for Flex-OneNAND (no mb erase) */
|
|
|
+ ret = onenand_block_by_block_erase(mtd, instr,
|
|
|
+ region, block_size);
|
|
|
+ } else {
|
|
|
+ ret = onenand_multiblock_erase(mtd, instr, block_size);
|
|
|
+ }
|
|
|
|
|
|
/* Deselect and wake up anyone waiting on the device */
|
|
|
onenand_release_device(mtd);
|
|
|
|
|
|
/* Do call back function */
|
|
|
- if (!ret)
|
|
|
+ if (!ret) {
|
|
|
+ instr->state = MTD_ERASE_DONE;
|
|
|
mtd_erase_callback(instr);
|
|
|
+ }
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
@@ -2272,7 +2506,7 @@ erase_exit:
|
|
|
*/
|
|
|
static void onenand_sync(struct mtd_info *mtd)
|
|
|
{
|
|
|
- DEBUG(MTD_DEBUG_LEVEL3, "onenand_sync: called\n");
|
|
|
+ DEBUG(MTD_DEBUG_LEVEL3, "%s: called\n", __func__);
|
|
|
|
|
|
/* Grab the lock and see if the device is available */
|
|
|
onenand_get_device(mtd, FL_SYNCING);
|
|
@@ -2406,7 +2640,8 @@ static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int
|
|
|
/* Check lock status */
|
|
|
status = this->read_word(this->base + ONENAND_REG_WP_STATUS);
|
|
|
if (!(status & wp_status_mask))
|
|
|
- printk(KERN_ERR "wp status = 0x%x\n", status);
|
|
|
+ printk(KERN_ERR "%s: wp status = 0x%x\n",
|
|
|
+ __func__, status);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -2435,7 +2670,8 @@ static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int
|
|
|
/* Check lock status */
|
|
|
status = this->read_word(this->base + ONENAND_REG_WP_STATUS);
|
|
|
if (!(status & wp_status_mask))
|
|
|
- printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status);
|
|
|
+ printk(KERN_ERR "%s: block = %d, wp status = 0x%x\n",
|
|
|
+ __func__, block, status);
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
@@ -2502,7 +2738,8 @@ static int onenand_check_lock_status(struct onenand_chip *this)
|
|
|
/* Check lock status */
|
|
|
status = this->read_word(this->base + ONENAND_REG_WP_STATUS);
|
|
|
if (!(status & ONENAND_WP_US)) {
|
|
|
- printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status);
|
|
|
+ printk(KERN_ERR "%s: block = %d, wp status = 0x%x\n",
|
|
|
+ __func__, block, status);
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
@@ -2557,6 +2794,208 @@ static void onenand_unlock_all(struct mtd_info *mtd)
|
|
|
|
|
|
#ifdef CONFIG_MTD_ONENAND_OTP
|
|
|
|
|
|
+/**
|
|
|
+ * onenand_otp_command - Send OTP specific command to OneNAND device
|
|
|
+ * @param mtd MTD device structure
|
|
|
+ * @param cmd the command to be sent
|
|
|
+ * @param addr offset to read from or write to
|
|
|
+ * @param len number of bytes to read or write
|
|
|
+ */
|
|
|
+static int onenand_otp_command(struct mtd_info *mtd, int cmd, loff_t addr,
|
|
|
+ size_t len)
|
|
|
+{
|
|
|
+ struct onenand_chip *this = mtd->priv;
|
|
|
+ int value, block, page;
|
|
|
+
|
|
|
+ /* Address translation */
|
|
|
+ switch (cmd) {
|
|
|
+ case ONENAND_CMD_OTP_ACCESS:
|
|
|
+ block = (int) (addr >> this->erase_shift);
|
|
|
+ page = -1;
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ block = (int) (addr >> this->erase_shift);
|
|
|
+ page = (int) (addr >> this->page_shift);
|
|
|
+
|
|
|
+ if (ONENAND_IS_2PLANE(this)) {
|
|
|
+ /* Make the even block number */
|
|
|
+ block &= ~1;
|
|
|
+ /* Is it the odd plane? */
|
|
|
+ if (addr & this->writesize)
|
|
|
+ block++;
|
|
|
+ page >>= 1;
|
|
|
+ }
|
|
|
+ page &= this->page_mask;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (block != -1) {
|
|
|
+ /* Write 'DFS, FBA' of Flash */
|
|
|
+ value = onenand_block_address(this, block);
|
|
|
+ this->write_word(value, this->base +
|
|
|
+ ONENAND_REG_START_ADDRESS1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (page != -1) {
|
|
|
+ /* Now we use page size operation */
|
|
|
+ int sectors = 4, count = 4;
|
|
|
+ int dataram;
|
|
|
+
|
|
|
+ switch (cmd) {
|
|
|
+ default:
|
|
|
+ if (ONENAND_IS_2PLANE(this) && cmd == ONENAND_CMD_PROG)
|
|
|
+ cmd = ONENAND_CMD_2X_PROG;
|
|
|
+ dataram = ONENAND_CURRENT_BUFFERRAM(this);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Write 'FPA, FSA' of Flash */
|
|
|
+ value = onenand_page_address(page, sectors);
|
|
|
+ this->write_word(value, this->base +
|
|
|
+ ONENAND_REG_START_ADDRESS8);
|
|
|
+
|
|
|
+ /* Write 'BSA, BSC' of DataRAM */
|
|
|
+ value = onenand_buffer_address(dataram, sectors, count);
|
|
|
+ this->write_word(value, this->base + ONENAND_REG_START_BUFFER);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Interrupt clear */
|
|
|
+ this->write_word(ONENAND_INT_CLEAR, this->base + ONENAND_REG_INTERRUPT);
|
|
|
+
|
|
|
+ /* Write command */
|
|
|
+ this->write_word(cmd, this->base + ONENAND_REG_COMMAND);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * onenand_otp_write_oob_nolock - [Internal] OneNAND write out-of-band, specific to OTP
|
|
|
+ * @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
|
|
|
+ *
|
|
|
+ * OneNAND write out-of-band only for OTP
|
|
|
+ */
|
|
|
+static int onenand_otp_write_oob_nolock(struct mtd_info *mtd, loff_t to,
|
|
|
+ struct mtd_oob_ops *ops)
|
|
|
+{
|
|
|
+ struct onenand_chip *this = mtd->priv;
|
|
|
+ int column, ret = 0, oobsize;
|
|
|
+ int written = 0;
|
|
|
+ u_char *oobbuf;
|
|
|
+ size_t len = ops->ooblen;
|
|
|
+ const u_char *buf = ops->oobbuf;
|
|
|
+ int block, value, status;
|
|
|
+
|
|
|
+ to += ops->ooboffs;
|
|
|
+
|
|
|
+ /* Initialize retlen, in case of early exit */
|
|
|
+ ops->oobretlen = 0;
|
|
|
+
|
|
|
+ oobsize = mtd->oobsize;
|
|
|
+
|
|
|
+ column = to & (mtd->oobsize - 1);
|
|
|
+
|
|
|
+ oobbuf = this->oob_buf;
|
|
|
+
|
|
|
+ /* Loop until all data write */
|
|
|
+ while (written < len) {
|
|
|
+ int thislen = min_t(int, oobsize, len - written);
|
|
|
+
|
|
|
+ cond_resched();
|
|
|
+
|
|
|
+ block = (int) (to >> this->erase_shift);
|
|
|
+ /*
|
|
|
+ * Write 'DFS, FBA' of Flash
|
|
|
+ * Add: F100h DQ=DFS, FBA
|
|
|
+ */
|
|
|
+
|
|
|
+ value = onenand_block_address(this, block);
|
|
|
+ this->write_word(value, this->base +
|
|
|
+ ONENAND_REG_START_ADDRESS1);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Select DataRAM for DDP
|
|
|
+ * Add: F101h DQ=DBS
|
|
|
+ */
|
|
|
+
|
|
|
+ value = onenand_bufferram_address(this, block);
|
|
|
+ this->write_word(value, this->base +
|
|
|
+ ONENAND_REG_START_ADDRESS2);
|
|
|
+ ONENAND_SET_NEXT_BUFFERRAM(this);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Enter OTP access mode
|
|
|
+ */
|
|
|
+ this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0);
|
|
|
+ this->wait(mtd, FL_OTPING);
|
|
|
+
|
|
|
+ /* We send data to spare ram with oobsize
|
|
|
+ * to prevent byte access */
|
|
|
+ memcpy(oobbuf + column, buf, thislen);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Write Data into DataRAM
|
|
|
+ * Add: 8th Word
|
|
|
+ * in sector0/spare/page0
|
|
|
+ * DQ=XXFCh
|
|
|
+ */
|
|
|
+ this->write_bufferram(mtd, ONENAND_SPARERAM,
|
|
|
+ oobbuf, 0, mtd->oobsize);
|
|
|
+
|
|
|
+ onenand_otp_command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize);
|
|
|
+ onenand_update_bufferram(mtd, to, 0);
|
|
|
+ if (ONENAND_IS_2PLANE(this)) {
|
|
|
+ ONENAND_SET_BUFFERRAM1(this);
|
|
|
+ onenand_update_bufferram(mtd, to + this->writesize, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = this->wait(mtd, FL_WRITING);
|
|
|
+ if (ret) {
|
|
|
+ printk(KERN_ERR "%s: write failed %d\n", __func__, ret);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Exit OTP access mode */
|
|
|
+ this->command(mtd, ONENAND_CMD_RESET, 0, 0);
|
|
|
+ this->wait(mtd, FL_RESETING);
|
|
|
+
|
|
|
+ status = this->read_word(this->base + ONENAND_REG_CTRL_STATUS);
|
|
|
+ status &= 0x60;
|
|
|
+
|
|
|
+ if (status == 0x60) {
|
|
|
+ printk(KERN_DEBUG "\nBLOCK\tSTATUS\n");
|
|
|
+ printk(KERN_DEBUG "1st Block\tLOCKED\n");
|
|
|
+ printk(KERN_DEBUG "OTP Block\tLOCKED\n");
|
|
|
+ } else if (status == 0x20) {
|
|
|
+ printk(KERN_DEBUG "\nBLOCK\tSTATUS\n");
|
|
|
+ printk(KERN_DEBUG "1st Block\tLOCKED\n");
|
|
|
+ printk(KERN_DEBUG "OTP Block\tUN-LOCKED\n");
|
|
|
+ } else if (status == 0x40) {
|
|
|
+ printk(KERN_DEBUG "\nBLOCK\tSTATUS\n");
|
|
|
+ printk(KERN_DEBUG "1st Block\tUN-LOCKED\n");
|
|
|
+ printk(KERN_DEBUG "OTP Block\tLOCKED\n");
|
|
|
+ } else {
|
|
|
+ printk(KERN_DEBUG "Reboot to check\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ written += thislen;
|
|
|
+ if (written == len)
|
|
|
+ break;
|
|
|
+
|
|
|
+ to += mtd->writesize;
|
|
|
+ buf += thislen;
|
|
|
+ column = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ ops->oobretlen = written;
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
/* Internal OTP operation */
|
|
|
typedef int (*otp_op_t)(struct mtd_info *mtd, loff_t form, size_t len,
|
|
|
size_t *retlen, u_char *buf);
|
|
@@ -2659,11 +3098,11 @@ static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len,
|
|
|
struct mtd_oob_ops ops;
|
|
|
int ret;
|
|
|
|
|
|
- /* Enter OTP access mode */
|
|
|
- this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0);
|
|
|
- this->wait(mtd, FL_OTPING);
|
|
|
-
|
|
|
if (FLEXONENAND(this)) {
|
|
|
+
|
|
|
+ /* Enter OTP access mode */
|
|
|
+ this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0);
|
|
|
+ this->wait(mtd, FL_OTPING);
|
|
|
/*
|
|
|
* For Flex-OneNAND, we write lock mark to 1st word of sector 4 of
|
|
|
* main area of page 49.
|
|
@@ -2674,19 +3113,19 @@ static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len,
|
|
|
ops.oobbuf = NULL;
|
|
|
ret = onenand_write_ops_nolock(mtd, mtd->writesize * 49, &ops);
|
|
|
*retlen = ops.retlen;
|
|
|
+
|
|
|
+ /* Exit OTP access mode */
|
|
|
+ this->command(mtd, ONENAND_CMD_RESET, 0, 0);
|
|
|
+ this->wait(mtd, FL_RESETING);
|
|
|
} else {
|
|
|
ops.mode = MTD_OOB_PLACE;
|
|
|
ops.ooblen = len;
|
|
|
ops.oobbuf = buf;
|
|
|
ops.ooboffs = 0;
|
|
|
- ret = onenand_write_oob_nolock(mtd, from, &ops);
|
|
|
+ ret = onenand_otp_write_oob_nolock(mtd, from, &ops);
|
|
|
*retlen = ops.oobretlen;
|
|
|
}
|
|
|
|
|
|
- /* Exit OTP access mode */
|
|
|
- this->command(mtd, ONENAND_CMD_RESET, 0, 0);
|
|
|
- this->wait(mtd, FL_RESETING);
|
|
|
-
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
@@ -2717,16 +3156,21 @@ static int onenand_otp_walk(struct mtd_info *mtd, loff_t from, size_t len,
|
|
|
if (density < ONENAND_DEVICE_DENSITY_512Mb)
|
|
|
otp_pages = 20;
|
|
|
else
|
|
|
- otp_pages = 10;
|
|
|
+ otp_pages = 50;
|
|
|
|
|
|
if (mode == MTD_OTP_FACTORY) {
|
|
|
from += mtd->writesize * otp_pages;
|
|
|
- otp_pages = 64 - otp_pages;
|
|
|
+ otp_pages = ONENAND_PAGES_PER_BLOCK - otp_pages;
|
|
|
}
|
|
|
|
|
|
/* Check User/Factory boundary */
|
|
|
- if (((mtd->writesize * otp_pages) - (from + len)) < 0)
|
|
|
- return 0;
|
|
|
+ if (mode == MTD_OTP_USER) {
|
|
|
+ if (mtd->writesize * otp_pages < from + len)
|
|
|
+ return 0;
|
|
|
+ } else {
|
|
|
+ if (mtd->writesize * otp_pages < len)
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
|
|
|
onenand_get_device(mtd, FL_OTPING);
|
|
|
while (len > 0 && otp_pages > 0) {
|
|
@@ -2749,13 +3193,12 @@ static int onenand_otp_walk(struct mtd_info *mtd, loff_t from, size_t len,
|
|
|
*retlen += sizeof(struct otp_info);
|
|
|
} else {
|
|
|
size_t tmp_retlen;
|
|
|
- int size = len;
|
|
|
|
|
|
ret = action(mtd, from, len, &tmp_retlen, buf);
|
|
|
|
|
|
- buf += size;
|
|
|
- len -= size;
|
|
|
- *retlen += size;
|
|
|
+ buf += tmp_retlen;
|
|
|
+ len -= tmp_retlen;
|
|
|
+ *retlen += tmp_retlen;
|
|
|
|
|
|
if (ret)
|
|
|
break;
|
|
@@ -2868,20 +3311,10 @@ static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
|
|
|
u_char *buf = FLEXONENAND(this) ? this->page_buf : this->oob_buf;
|
|
|
size_t retlen;
|
|
|
int ret;
|
|
|
+ unsigned int otp_lock_offset = ONENAND_OTP_LOCK_OFFSET;
|
|
|
|
|
|
memset(buf, 0xff, FLEXONENAND(this) ? this->writesize
|
|
|
: mtd->oobsize);
|
|
|
- /*
|
|
|
- * Note: OTP lock operation
|
|
|
- * OTP block : 0xXXFC
|
|
|
- * 1st block : 0xXXF3 (If chip support)
|
|
|
- * Both : 0xXXF0 (If chip support)
|
|
|
- */
|
|
|
- if (FLEXONENAND(this))
|
|
|
- buf[FLEXONENAND_OTP_LOCK_OFFSET] = 0xFC;
|
|
|
- else
|
|
|
- buf[ONENAND_OTP_LOCK_OFFSET] = 0xFC;
|
|
|
-
|
|
|
/*
|
|
|
* Write lock mark to 8th word of sector0 of page0 of the spare0.
|
|
|
* We write 16 bytes spare area instead of 2 bytes.
|
|
@@ -2892,10 +3325,30 @@ static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
|
|
|
from = 0;
|
|
|
len = FLEXONENAND(this) ? mtd->writesize : 16;
|
|
|
|
|
|
+ /*
|
|
|
+ * Note: OTP lock operation
|
|
|
+ * OTP block : 0xXXFC XX 1111 1100
|
|
|
+ * 1st block : 0xXXF3 (If chip support) XX 1111 0011
|
|
|
+ * Both : 0xXXF0 (If chip support) XX 1111 0000
|
|
|
+ */
|
|
|
+ if (FLEXONENAND(this))
|
|
|
+ otp_lock_offset = FLEXONENAND_OTP_LOCK_OFFSET;
|
|
|
+
|
|
|
+ /* ONENAND_OTP_AREA | ONENAND_OTP_BLOCK0 | ONENAND_OTP_AREA_BLOCK0 */
|
|
|
+ if (otp == 1)
|
|
|
+ buf[otp_lock_offset] = 0xFC;
|
|
|
+ else if (otp == 2)
|
|
|
+ buf[otp_lock_offset] = 0xF3;
|
|
|
+ else if (otp == 3)
|
|
|
+ buf[otp_lock_offset] = 0xF0;
|
|
|
+ else if (otp != 0)
|
|
|
+ printk(KERN_DEBUG "[OneNAND] Invalid option selected for OTP\n");
|
|
|
+
|
|
|
ret = onenand_otp_walk(mtd, from, len, &retlen, buf, do_otp_lock, MTD_OTP_USER);
|
|
|
|
|
|
return ret ? : retlen;
|
|
|
}
|
|
|
+
|
|
|
#endif /* CONFIG_MTD_ONENAND_OTP */
|
|
|
|
|
|
/**
|
|
@@ -3172,7 +3625,8 @@ static int flexonenand_check_blocks_erased(struct mtd_info *mtd, int start, int
|
|
|
break;
|
|
|
|
|
|
if (i != mtd->oobsize) {
|
|
|
- printk(KERN_WARNING "Block %d not erased.\n", block);
|
|
|
+ printk(KERN_WARNING "%s: Block %d not erased.\n",
|
|
|
+ __func__, block);
|
|
|
return 1;
|
|
|
}
|
|
|
}
|
|
@@ -3204,8 +3658,8 @@ int flexonenand_set_boundary(struct mtd_info *mtd, int die,
|
|
|
blksperdie >>= ONENAND_IS_DDP(this) ? 1 : 0;
|
|
|
|
|
|
if (boundary >= blksperdie) {
|
|
|
- printk(KERN_ERR "flexonenand_set_boundary: Invalid boundary value. "
|
|
|
- "Boundary not changed.\n");
|
|
|
+ printk(KERN_ERR "%s: Invalid boundary value. "
|
|
|
+ "Boundary not changed.\n", __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
@@ -3214,7 +3668,8 @@ int flexonenand_set_boundary(struct mtd_info *mtd, int die,
|
|
|
new = boundary + (die * this->density_mask);
|
|
|
ret = flexonenand_check_blocks_erased(mtd, min(old, new) + 1, max(old, new));
|
|
|
if (ret) {
|
|
|
- printk(KERN_ERR "flexonenand_set_boundary: Please erase blocks before boundary change\n");
|
|
|
+ printk(KERN_ERR "%s: Please erase blocks "
|
|
|
+ "before boundary change\n", __func__);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
@@ -3227,12 +3682,12 @@ int flexonenand_set_boundary(struct mtd_info *mtd, int die,
|
|
|
|
|
|
thisboundary = this->read_word(this->base + ONENAND_DATARAM);
|
|
|
if ((thisboundary >> FLEXONENAND_PI_UNLOCK_SHIFT) != 3) {
|
|
|
- printk(KERN_ERR "flexonenand_set_boundary: boundary locked\n");
|
|
|
+ printk(KERN_ERR "%s: boundary locked\n", __func__);
|
|
|
ret = 1;
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- printk(KERN_INFO "flexonenand_set_boundary: Changing die %d boundary: %d%s\n",
|
|
|
+ printk(KERN_INFO "Changing die %d boundary: %d%s\n",
|
|
|
die, boundary, lock ? "(Locked)" : "(Unlocked)");
|
|
|
|
|
|
addr = die ? this->diesize[0] : 0;
|
|
@@ -3243,7 +3698,8 @@ int flexonenand_set_boundary(struct mtd_info *mtd, int die,
|
|
|
this->command(mtd, ONENAND_CMD_ERASE, addr, 0);
|
|
|
ret = this->wait(mtd, FL_ERASING);
|
|
|
if (ret) {
|
|
|
- printk(KERN_ERR "flexonenand_set_boundary: Failed PI erase for Die %d\n", die);
|
|
|
+ printk(KERN_ERR "%s: Failed PI erase for Die %d\n",
|
|
|
+ __func__, die);
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
@@ -3251,7 +3707,8 @@ int flexonenand_set_boundary(struct mtd_info *mtd, int die,
|
|
|
this->command(mtd, ONENAND_CMD_PROG, addr, 0);
|
|
|
ret = this->wait(mtd, FL_WRITING);
|
|
|
if (ret) {
|
|
|
- printk(KERN_ERR "flexonenand_set_boundary: Failed PI write for Die %d\n", die);
|
|
|
+ printk(KERN_ERR "%s: Failed PI write for Die %d\n",
|
|
|
+ __func__, die);
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
@@ -3408,8 +3865,8 @@ static void onenand_resume(struct mtd_info *mtd)
|
|
|
if (this->state == FL_PM_SUSPENDED)
|
|
|
onenand_release_device(mtd);
|
|
|
else
|
|
|
- printk(KERN_ERR "resume() called for the chip which is not"
|
|
|
- "in suspended state\n");
|
|
|
+ printk(KERN_ERR "%s: resume() called for the chip which is not "
|
|
|
+ "in suspended state\n", __func__);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -3464,7 +3921,8 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
|
|
|
if (!this->page_buf) {
|
|
|
this->page_buf = kzalloc(mtd->writesize, GFP_KERNEL);
|
|
|
if (!this->page_buf) {
|
|
|
- printk(KERN_ERR "onenand_scan(): Can't allocate page_buf\n");
|
|
|
+ printk(KERN_ERR "%s: Can't allocate page_buf\n",
|
|
|
+ __func__);
|
|
|
return -ENOMEM;
|
|
|
}
|
|
|
this->options |= ONENAND_PAGEBUF_ALLOC;
|
|
@@ -3472,7 +3930,8 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
|
|
|
if (!this->oob_buf) {
|
|
|
this->oob_buf = kzalloc(mtd->oobsize, GFP_KERNEL);
|
|
|
if (!this->oob_buf) {
|
|
|
- printk(KERN_ERR "onenand_scan(): Can't allocate oob_buf\n");
|
|
|
+ printk(KERN_ERR "%s: Can't allocate oob_buf\n",
|
|
|
+ __func__);
|
|
|
if (this->options & ONENAND_PAGEBUF_ALLOC) {
|
|
|
this->options &= ~ONENAND_PAGEBUF_ALLOC;
|
|
|
kfree(this->page_buf);
|
|
@@ -3505,8 +3964,8 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
- printk(KERN_WARNING "No OOB scheme defined for oobsize %d\n",
|
|
|
- mtd->oobsize);
|
|
|
+ printk(KERN_WARNING "%s: No OOB scheme defined for oobsize %d\n",
|
|
|
+ __func__, mtd->oobsize);
|
|
|
mtd->subpage_sft = 0;
|
|
|
/* To prevent kernel oops */
|
|
|
this->ecclayout = &onenand_oob_32;
|