diff mbox

MTD: OneNAND: multiblock erase support

Message ID 1245754975-12063-1-git-send-email-ext-mika.2.korhonen@nokia.com
State New, archived
Headers show

Commit Message

Mika Korhonen June 23, 2009, 11:02 a.m. UTC
Add support for multiblock erase command. OneNANDs (excluding Flex-OneNAND)
are capable of simultaneous erase of up to 64 eraseblocks.
This changes the erase requests for regions covering multiple eraseblocks
to be performed using multiblock erase.

Signed-off-by: Mika Korhonen <ext-mika.2.korhonen@nokia.com>
---
 drivers/mtd/onenand/omap2.c        |   22 +++-
 drivers/mtd/onenand/onenand_base.c |  268 ++++++++++++++++++++++++++++-------
 include/linux/mtd/onenand.h        |    2 +
 include/linux/mtd/onenand_regs.h   |    2 +
 4 files changed, 236 insertions(+), 58 deletions(-)

Comments

Artem Bityutskiy June 23, 2009, 11:06 a.m. UTC | #1
On Tue, 2009-06-23 at 14:02 +0300, Mika Korhonen wrote:
> Add support for multiblock erase command. OneNANDs (excluding Flex-OneNAND)
> are capable of simultaneous erase of up to 64 eraseblocks.
> This changes the erase requests for regions covering multiple eraseblocks
> to be performed using multiblock erase.
> 
> Signed-off-by: Mika Korhonen <ext-mika.2.korhonen@nokia.com>
> ---
>  drivers/mtd/onenand/omap2.c        |   22 +++-
>  drivers/mtd/onenand/onenand_base.c |  268 ++++++++++++++++++++++++++++-------
>  include/linux/mtd/onenand.h        |    2 +
>  include/linux/mtd/onenand_regs.h   |    2 +
>  4 files changed, 236 insertions(+), 58 deletions(-)

Just out of curiosity, what is your use-case for this?
Mika Korhonen June 23, 2009, 11:27 a.m. UTC | #2
As you probably noticed, this is the revised version of the initial patch in
http://lists.infradead.org/pipermail/linux-mtd/2009-June/026050.html

I made some changes based on the comments from Adrian Hunter and Kyungmin Park:
* removed the configuration option from Kconfig as it is supported by
all OneNAND chips and Flex-case is handled easily in code, bringing
the option back can be done easily if needed
* spin on multiblock erase and erase verify commands instead using
interrupt line: mb erase usually delays just 1 us, verify usually 30
us (is this still acceptable?)
* handle regions bigger than 64 eraseblocks instead of falling back to
block-by-block erase
* check bad blocks prior to sending mb erase commands
* Flex does not support mb erase, so single block version is always used

I extracted the single block erase to separate function, as well as
the multiblock one.

The erase speed in optimal full 64-eraseblock case seems to be about 1400 MB/s.

br
Mika
Mika Korhonen June 23, 2009, 1:19 p.m. UTC | #3
2009/6/23 Artem Bityutskiy <dedekind@infradead.org>:
> On Tue, 2009-06-23 at 14:02 +0300, Mika Korhonen wrote:
>> Add support for multiblock erase command. OneNANDs (excluding Flex-OneNAND)
>> are capable of simultaneous erase of up to 64 eraseblocks.
>> This changes the erase requests for regions covering multiple eraseblocks
>> to be performed using multiblock erase.
>>
>> Signed-off-by: Mika Korhonen <ext-mika.2.korhonen@nokia.com>
>> ---
>>  drivers/mtd/onenand/omap2.c        |   22 +++-
>>  drivers/mtd/onenand/onenand_base.c |  268 ++++++++++++++++++++++++++++-------
>>  include/linux/mtd/onenand.h        |    2 +
>>  include/linux/mtd/onenand_regs.h   |    2 +
>>  4 files changed, 236 insertions(+), 58 deletions(-)
>
> Just out of curiosity, what is your use-case for this?
>

When erasing and programming large amounts, e.g. filesystem images on
OneNAND the erase part takes about 10-15% of the time. Using
multiblock erase it takes almost no time (up to 30x faster) :)
Obviously, it is especially useful if the data to be programmed covers
only part of the erased region.

Mika
diff mbox

Patch

diff --git a/drivers/mtd/onenand/omap2.c b/drivers/mtd/onenand/omap2.c
index 38d656b..044621c 100644
--- a/drivers/mtd/onenand/omap2.c
+++ b/drivers/mtd/onenand/omap2.c
@@ -112,10 +112,24 @@  static int omap2_onenand_wait(struct mtd_info *mtd, int state)
 	unsigned long timeout;
 	u32 syscfg;
 
-	if (state == FL_RESETING) {
-		int i;
+	if (state == FL_RESETING || state == FL_PREPARING_ERASE ||
+		state == FL_VERIFYING_ERASE) {
+
+		int i = 21;
+		unsigned int intr_flags = ONENAND_INT_MASTER;
+		switch (state) {
+		case FL_RESETING:
+			intr_flags |= ONENAND_INT_RESET;
+			break;
+		case FL_PREPARING_ERASE:
+			intr_flags |= ONENAND_INT_ERASE;
+			break;
+		case FL_VERIFYING_ERASE:
+			i = 51;
+			break;
+		}
 
-		for (i = 0; i < 20; i++) {
+		while (--i) {
 			udelay(1);
 			intr = read_reg(c, ONENAND_REG_INTERRUPT);
 			if (intr & ONENAND_INT_MASTER)
@@ -126,7 +140,7 @@  static int omap2_onenand_wait(struct mtd_info *mtd, int state)
 			wait_err("controller error", state, ctrl, intr);
 			return -EIO;
 		}
-		if (!(intr & ONENAND_INT_RESET)) {
+		if (!(intr & intr_flags)) {
 			wait_err("timeout", state, ctrl, intr);
 			return -EIO;
 		}
diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c
index 6e82909..1b36277 100644
--- a/drivers/mtd/onenand/onenand_base.c
+++ b/drivers/mtd/onenand/onenand_base.c
@@ -32,6 +32,12 @@ 
 
 #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 };
 
@@ -339,6 +345,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 +491,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 */
@@ -513,6 +521,18 @@  static int onenand_wait(struct mtd_info *mtd, int state)
 		return -EIO;
 	}
 
+	if (state == FL_PREPARING_ERASE && !(interrupt & ONENAND_INT_ERASE)) {
+		printk(KERN_ERR "onenand_wait: mb erase timeout! ctrl=0x%04x intr=0x%04x\n",
+			ctrl, interrupt);
+		return -EIO;
+	}
+
+	if (!(interrupt & ONENAND_INT_MASTER)) {
+		printk(KERN_ERR "onenand_wait: timeout! ctrl=0x%04x intr=0x%04x\n",
+			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",
@@ -2140,70 +2160,144 @@  static int onenand_block_isbad_nolock(struct mtd_info *mtd, loff_t ofs, int allo
 	return bbm->isbad_bbt(mtd, ofs, allowbbt);
 }
 
-/**
- * onenand_erase - [MTD Interface] erase block(s)
- * @param mtd		MTD device structure
- * @param instr		erase instruction
- *
- * Erase one ore more blocks
- */
-static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
+
+static int onenand_multiblock_erase_verify(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, i;
-	struct mtd_erase_region_info *region = NULL;
-	loff_t region_end = 0;
+	int len = instr->len;
+	unsigned int block_size = (1 << this->erase_shift);
+	int ret = 0;
 
-	DEBUG(MTD_DEBUG_LEVEL3, "onenand_erase: start = 0x%012llx, len = %llu\n", (unsigned long long) instr->addr, (unsigned long long) instr->len);
+	while (len) {
+		this->command(mtd, ONENAND_CMD_ERASE_VERIFY, addr, block_size);
+		ret = this->wait(mtd, FL_VERIFYING_ERASE);
+		if (ret) {
+			printk(KERN_ERR "onenand_multiblock_erase_verify: "
+			       "Failed verify, block %d\n", onenand_block(this, addr));
+			instr->state = MTD_ERASE_FAILED;
+			instr->fail_addr = addr;
+			return -1;
+		}
+		len -= block_size;
+		addr += block_size;
+	}
+	return 0;
+}
 
-	/* 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;
+
+static int onenand_multiblock_erase(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 eb_count = 0;
+	int ret = 0;
+
+	instr->state = MTD_ERASING;
+
+	/* 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 "onenand_erase: attempt to erase a bad block at addr 0x%012llx\n", (unsigned long long) addr);
+			instr->state = MTD_ERASE_FAILED;
+			return -1;
+		}
+		len -= block_size;
+		addr += block_size;
 	}
 
-	if (FLEXONENAND(this)) {
-		/* Find the eraseregion of this address */
-		i = flexonenand_region(mtd, addr);
-		region = &mtd->eraseregions[i];
+	len = instr->len;
+	addr = instr->addr;
 
-		block_size = region->erasesize;
-		region_end = region->offset + region->erasesize * region->numblocks;
 
-		/* 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;
+		verify_instr.addr = addr;
+		verify_instr.len = 0;
+
+		eb_count = 0;
+
+		while (len > block_size &&
+		       eb_count < (MB_ERASE_MAX_BLK_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 "onenand_multiblock_erase: "
+				       "Failed multiblock erase, block %d\n",
+				       onenand_block(this, addr));
+				instr->state = MTD_ERASE_FAILED;
+				instr->fail_addr = addr;
+				return -1;
+			}
+
+			len -= block_size;
+			addr += block_size;
+			eb_count++;
 		}
-	} 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;
+		/* 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 "onenand_erase: Failed erase, block %d\n",
+			       onenand_block(this, addr));
+			instr->state = MTD_ERASE_FAILED;
+			instr->fail_addr = addr;
+			return -1;
+		}
+
+		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 -1;
 		}
-	}
 
-	/* 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);
 
-	/* Loop throught the pages */
+static int onenand_single_block_erase(struct mtd_info *mtd,
+				      struct erase_info *instr,
+				      struct mtd_erase_region_info *region)
+{
+	struct onenand_chip *this = mtd->priv;
+	loff_t addr = instr->addr;
+	int len = instr->len;
+	unsigned int block_size;
+	loff_t region_end = 0;
+	int ret = 0;
+
+	if (region) {
+		/* region is set for Flex-OneNAND */
+		block_size = region->erasesize;
+		region_end = region->offset + region->erasesize * region->numblocks;
+	} else {
+		block_size = (1 << this->erase_shift);
+	}
+
 	instr->state = MTD_ERASING;
 
+	/* Loop through the blocks */
 	while (len) {
 		cond_resched();
 
@@ -2211,7 +2305,7 @@  static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
 		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);
 			instr->state = MTD_ERASE_FAILED;
-			goto erase_exit;
+			return -1;
 		}
 
 		this->command(mtd, ONENAND_CMD_ERASE, addr, block_size);
@@ -2222,10 +2316,10 @@  static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
 		/* Check, if it is write protected */
 		if (ret) {
 			printk(KERN_ERR "onenand_erase: Failed erase, block %d\n",
-						 onenand_block(this, addr));
+			       onenand_block(this, addr));
 			instr->state = MTD_ERASE_FAILED;
 			instr->fail_addr = addr;
-			goto erase_exit;
+			return -1;
 		}
 
 		len -= block_size;
@@ -2235,24 +2329,90 @@  static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
 			if (!len)
 				break;
 			region++;
-
 			block_size = region->erasesize;
 			region_end = region->offset + region->erasesize * region->numblocks;
 
 			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;
+				return -1;
 			}
 		}
+	}
+
+	return 0;
+}
+
+
+/**
+ * onenand_erase - [MTD Interface] erase block(s)
+ * @param mtd		MTD device structure
+ * @param instr		erase instruction
+ *
+ * Erase one ore 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, "onenand_erase: start = 0x%012llx, len = %llu\n", (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 "onenand_erase: Erase past end of device\n");
+		return -EINVAL;
 	}
 
-	instr->state = MTD_ERASE_DONE;
+	if (FLEXONENAND(this)) {
+		/* Find the eraseregion of this address */
+		int i = flexonenand_region(mtd, addr);
+		region = &mtd->eraseregions[i];
+		block_size = region->erasesize;
 
-erase_exit:
+		/* 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;
+	}
 
-	ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO;
+	/* Start address must align on block boundary */
+	if (unlikely((addr - region_offset) & (block_size - 1))) {
+		printk(KERN_ERR "onenand_erase: Unaligned address\n");
+		return -EINVAL;
+	}
+
+	/* Length must align on block boundary */
+	if (unlikely(len & (block_size - 1))) {
+		printk(KERN_ERR "onenand_erase: Length not block aligned\n");
+		return -EINVAL;
+	}
+
+	instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
+
+	/* Grab the lock and see if the device is available */
+	onenand_get_device(mtd, FL_ERASING);
+
+
+	if (region || instr->len < MB_ERASE_MIN_BLK_COUNT * block_size) {
+		/* region is set for Flex-OneNAND (no mb erase) */
+		ret = onenand_single_block_erase(mtd, instr, region);
+	} else {
+		ret = onenand_multiblock_erase(mtd, instr);
+	}
+
+	if (ret) {
+		ret = -EIO;
+	} else {
+		instr->state = MTD_ERASE_DONE;
+	}
 
 	/* Deselect and wake up anyone waiting on the device */
 	onenand_release_device(mtd);
diff --git a/include/linux/mtd/onenand.h b/include/linux/mtd/onenand.h
index 8ed8733..42384f3 100644
--- a/include/linux/mtd/onenand.h
+++ b/include/linux/mtd/onenand.h
@@ -39,6 +39,8 @@  typedef enum {
 	FL_RESETING,
 	FL_OTPING,
 	FL_PM_SUSPENDED,
+	FL_PREPARING_ERASE,
+	FL_VERIFYING_ERASE
 } onenand_state_t;
 
 /**
diff --git a/include/linux/mtd/onenand_regs.h b/include/linux/mtd/onenand_regs.h
index 86a6bbe..bd346e5 100644
--- a/include/linux/mtd/onenand_regs.h
+++ b/include/linux/mtd/onenand_regs.h
@@ -131,6 +131,8 @@ 
 #define ONENAND_CMD_LOCK_TIGHT		(0x2C)
 #define ONENAND_CMD_UNLOCK_ALL		(0x27)
 #define ONENAND_CMD_ERASE		(0x94)
+#define ONENAND_CMD_MULTIBLOCK_ERASE	(0x95)
+#define ONENAND_CMD_ERASE_VERIFY	(0x71)
 #define ONENAND_CMD_RESET		(0xF0)
 #define ONENAND_CMD_OTP_ACCESS		(0x65)
 #define ONENAND_CMD_READID		(0x90)