[MTD] OneNAND: add subpage write support
[pandora-kernel.git] / drivers / mtd / onenand / onenand_base.c
index 7a24191..51fb840 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  linux/drivers/mtd/onenand/onenand_base.c
  *
- *  Copyright (C) 2005 Samsung Electronics
+ *  Copyright (C) 2005-2006 Samsung Electronics
  *  Kyungmin Park <kyungmin.park@samsung.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/sched.h>
+#include <linux/interrupt.h>
 #include <linux/jiffies.h>
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/onenand.h>
@@ -23,8 +24,7 @@
 /**
  * onenand_oob_64 - oob info for large (2KB) page
  */
-static struct nand_oobinfo onenand_oob_64 = {
-       .useecc         = MTD_NANDECC_AUTOPLACE,
+static struct nand_ecclayout onenand_oob_64 = {
        .eccbytes       = 20,
        .eccpos         = {
                8, 9, 10, 11, 12,
@@ -41,8 +41,7 @@ static struct nand_oobinfo onenand_oob_64 = {
 /**
  * onenand_oob_32 - oob info for middle (1KB) page
  */
-static struct nand_oobinfo onenand_oob_32 = {
-       .useecc         = MTD_NANDECC_AUTOPLACE,
+static struct nand_ecclayout onenand_oob_32 = {
        .eccbytes       = 10,
        .eccpos         = {
                8, 9, 10, 11, 12,
@@ -193,14 +192,13 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le
        struct onenand_chip *this = mtd->priv;
        int value, readcmd = 0, block_cmd = 0;
        int block, page;
-       /* Now we use page size operation */
-       int sectors = 4, count = 4;
 
        /* Address translation */
        switch (cmd) {
        case ONENAND_CMD_UNLOCK:
        case ONENAND_CMD_LOCK:
        case ONENAND_CMD_LOCK_TIGHT:
+       case ONENAND_CMD_UNLOCK_ALL:
                block = -1;
                page = -1;
                break;
@@ -245,6 +243,8 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le
        }
 
        if (page != -1) {
+               /* Now we use page size operation */
+               int sectors = 4, count = 4;
                int dataram;
 
                switch (cmd) {
@@ -318,28 +318,131 @@ static int onenand_wait(struct mtd_info *mtd, int state)
        ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS);
 
        if (ctrl & ONENAND_CTRL_ERROR) {
-               /* It maybe occur at initial bad block */
                DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: controller error = 0x%04x\n", ctrl);
-               /* Clear other interrupt bits for preventing ECC error */
-               interrupt &= ONENAND_INT_MASTER;
-       }
-
-       if (ctrl & ONENAND_CTRL_LOCK) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: it's locked error = 0x%04x\n", ctrl);
-               return -EACCES;
+               if (ctrl & ONENAND_CTRL_LOCK)
+                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: it's locked error.\n");
+               return ctrl;
        }
 
        if (interrupt & ONENAND_INT_READ) {
                ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS);
-               if (ecc & ONENAND_ECC_2BIT_ALL) {
+               if (ecc) {
                        DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: ECC error = 0x%04x\n", ecc);
-                       return -EBADMSG;
+                       if (ecc & ONENAND_ECC_2BIT_ALL)
+                               mtd->ecc_stats.failed++;
+                       else if (ecc & ONENAND_ECC_1BIT_ALL)
+                               mtd->ecc_stats.corrected++;
                }
        }
 
        return 0;
 }
 
+/*
+ * onenand_interrupt - [DEFAULT] onenand interrupt handler
+ * @param irq          onenand interrupt number
+ * @param dev_id       interrupt data
+ *
+ * complete the work
+ */
+static irqreturn_t onenand_interrupt(int irq, void *data)
+{
+       struct onenand_chip *this = (struct onenand_chip *) data;
+
+       /* To handle shared interrupt */
+       if (!this->complete.done)
+               complete(&this->complete);
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * onenand_interrupt_wait - [DEFAULT] wait until the command is done
+ * @param mtd          MTD device structure
+ * @param state                state to select the max. timeout value
+ *
+ * Wait for command done.
+ */
+static int onenand_interrupt_wait(struct mtd_info *mtd, int state)
+{
+       struct onenand_chip *this = mtd->priv;
+
+       /* To prevent soft lockup */
+       touch_softlockup_watchdog();
+
+       wait_for_completion(&this->complete);
+
+       return onenand_wait(mtd, state);
+}
+
+/*
+ * onenand_try_interrupt_wait - [DEFAULT] try interrupt wait
+ * @param mtd          MTD device structure
+ * @param state                state to select the max. timeout value
+ *
+ * Try interrupt based wait (It is used one-time)
+ */
+static int onenand_try_interrupt_wait(struct mtd_info *mtd, int state)
+{
+       struct onenand_chip *this = mtd->priv;
+       unsigned long remain, timeout;
+
+       /* We use interrupt wait first */
+       this->wait = onenand_interrupt_wait;
+
+       /* To prevent soft lockup */
+       touch_softlockup_watchdog();
+
+       timeout = msecs_to_jiffies(100);
+       remain = wait_for_completion_timeout(&this->complete, timeout);
+       if (!remain) {
+               printk(KERN_INFO "OneNAND: There's no interrupt. "
+                               "We use the normal wait\n");
+
+               /* Release the irq */
+               free_irq(this->irq, this);
+
+               this->wait = onenand_wait;
+       }
+
+       return onenand_wait(mtd, state);
+}
+
+/*
+ * onenand_setup_wait - [OneNAND Interface] setup onenand wait method
+ * @param mtd          MTD device structure
+ *
+ * There's two method to wait onenand work
+ * 1. polling - read interrupt status register
+ * 2. interrupt - use the kernel interrupt method
+ */
+static void onenand_setup_wait(struct mtd_info *mtd)
+{
+       struct onenand_chip *this = mtd->priv;
+       int syscfg;
+
+       init_completion(&this->complete);
+
+       if (this->irq <= 0) {
+               this->wait = onenand_wait;
+               return;
+       }
+
+       if (request_irq(this->irq, &onenand_interrupt,
+                               IRQF_SHARED, "onenand", this)) {
+               /* If we can't get irq, use the normal wait */
+               this->wait = onenand_wait;
+               return;
+       }
+
+       /* Enable interrupt */
+       syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1);
+       syscfg |= ONENAND_SYS_CFG1_IOBE;
+       this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1);
+
+       this->wait = onenand_try_interrupt_wait;
+}
+
 /**
  * onenand_bufferram_offset - [DEFAULT] BufferRAM offset
  * @param mtd          MTD data structure
@@ -610,6 +713,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
        size_t *retlen, u_char *buf)
 {
        struct onenand_chip *this = mtd->priv;
+       struct mtd_ecc_stats stats;
        int read = 0, column;
        int thislen;
        int ret = 0;
@@ -628,6 +732,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
 
        /* TODO handling oob */
 
+       stats = mtd->ecc_stats;
        while (read < len) {
                thislen = min_t(int, mtd->writesize, len - read);
 
@@ -640,21 +745,21 @@ static int onenand_read(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 */
-                       onenand_update_bufferram(mtd, from, 1);
+                       onenand_update_bufferram(mtd, from, !ret);
                }
 
                this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen);
 
-               read += thislen;
-
-               if (read == len)
-                       break;
-
                if (ret) {
                        DEBUG(MTD_DEBUG_LEVEL0, "onenand_read: read failed = %d\n", ret);
                        goto out;
                }
 
+               read += thislen;
+
+               if (read == len)
+                       break;
+
                from += thislen;
                buf += thislen;
        }
@@ -669,11 +774,15 @@ out:
         * retlen == desired len and result == -EBADMSG
         */
        *retlen = read;
-       return ret;
+
+       if (mtd->ecc_stats.failed - stats.failed)
+               return -EBADMSG;
+
+       return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0;
 }
 
 /**
- * onenand_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 from         offset to read from
  * @param len          number of bytes to read
@@ -682,8 +791,8 @@ out:
  *
  * OneNAND read out-of-band data from the spare area
  */
-static int onenand_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
-       size_t *retlen, u_char *buf)
+int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
+                       size_t *retlen, u_char *buf)
 {
        struct onenand_chip *this = mtd->priv;
        int read = 0, thislen, column;
@@ -718,16 +827,16 @@ static int onenand_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
 
                this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen);
 
+               if (ret) {
+                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: read failed = 0x%x\n", ret);
+                       goto out;
+               }
+
                read += thislen;
 
                if (read == len)
                        break;
 
-               if (ret) {
-                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: read failed = %d\n", ret);
-                       goto out;
-               }
-
                buf += thislen;
 
                /* Read more? */
@@ -746,6 +855,21 @@ out:
        return ret;
 }
 
+/**
+ * onenand_read_oob - [MTD Interface] NAND write data and/or out-of-band
+ * @mtd:       MTD device structure
+ * @from:      offset to read from
+ * @ops:       oob operation description structure
+ */
+static int onenand_read_oob(struct mtd_info *mtd, loff_t from,
+                           struct mtd_oob_ops *ops)
+{
+       BUG_ON(ops->mode != MTD_OOB_PLACE);
+
+       return onenand_do_read_oob(mtd, from + ops->ooboffs, ops->ooblen,
+                                  &ops->oobretlen, ops->oobbuf);
+}
+
 #ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE
 /**
  * onenand_verify_oob - [GENERIC] verify the oob contents after a write
@@ -790,6 +914,10 @@ static int onenand_verify_page(struct mtd_info *mtd, u_char *buf, loff_t addr)
        void __iomem *dataram0, *dataram1;
        int ret = 0;
 
+       /* In partial page write, just skip it */
+       if ((addr & (mtd->writesize - 1)) != 0)
+               return 0;
+
        this->command(mtd, ONENAND_CMD_READ, addr, mtd->writesize);
 
        ret = this->wait(mtd, FL_READING);
@@ -812,7 +940,7 @@ static int onenand_verify_page(struct mtd_info *mtd, u_char *buf, loff_t addr)
 #define onenand_verify_oob(...)                (0)
 #endif
 
-#define NOTALIGNED(x)  ((x & (mtd->writesize - 1)) != 0)
+#define NOTALIGNED(x)  ((x & (this->subpagesize - 1)) != 0)
 
 /**
  * onenand_write - [MTD Interface] write buffer to FLASH
@@ -830,6 +958,7 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
        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);
 
@@ -848,45 +977,61 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
                 return -EINVAL;
         }
 
+       column = to & (mtd->writesize - 1);
+       subpage = column || (len & (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, len - written);
-
-               this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->writesize);
+               int bytes = mtd->writesize;
+               int thislen = min_t(int, bytes, len - written);
+               u_char *wbuf = (u_char *) buf;
+
+               this->command(mtd, ONENAND_CMD_BUFFERRAM, to, bytes);
+
+               /* Partial page write */
+               if (subpage) {
+                       bytes = min_t(int, bytes - column, (int) len);
+                       memset(this->page_buf, 0xff, mtd->writesize);
+                       memcpy(this->page_buf + column, buf, bytes);
+                       wbuf = this->page_buf;
+                       /* Even though partial write, we need page size */
+                       thislen = mtd->writesize;
+               }
 
-               this->write_bufferram(mtd, ONENAND_DATARAM, buf, 0, thislen);
+               this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, thislen);
                this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0, mtd->oobsize);
 
                this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize);
 
-               onenand_update_bufferram(mtd, to, 1);
+               /* In partial page write we don't update bufferram */
+               onenand_update_bufferram(mtd, to, !subpage);
 
                ret = this->wait(mtd, FL_WRITING);
                if (ret) {
                        DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: write filaed %d\n", ret);
-                       goto out;
+                       break;
                }
 
-               written += thislen;
-
                /* Only check verify write turn on */
-               ret = onenand_verify_page(mtd, (u_char *) buf, to);
+               ret = onenand_verify_page(mtd, (u_char *) wbuf, to);
                if (ret) {
                        DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: verify failed %d\n", ret);
-                       goto out;
+                       break;
                }
 
+               written += thislen;
+
                if (written == len)
                        break;
 
+               column = 0;
                to += thislen;
                buf += thislen;
        }
 
-out:
        /* Deselect and wake up anyone waiting on the device */
        onenand_release_device(mtd);
 
@@ -896,7 +1041,7 @@ out:
 }
 
 /**
- * onenand_write_oob - [MTD Interface] OneNAND write out-of-band
+ * onenand_do_write_oob - [Internal] OneNAND write out-of-band
  * @param mtd          MTD device structure
  * @param to           offset to write to
  * @param len          number of bytes to write
@@ -905,8 +1050,8 @@ out:
  *
  * OneNAND write out-of-band
  */
-static int onenand_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
-       size_t *retlen, const u_char *buf)
+static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
+                               size_t *retlen, const u_char *buf)
 {
        struct onenand_chip *this = mtd->priv;
        int column, ret = 0;
@@ -974,6 +1119,21 @@ out:
        return ret;
 }
 
+/**
+ * onenand_write_oob - [MTD Interface] NAND write data and/or out-of-band
+ * @mtd:       MTD device structure
+ * @from:      offset to read from
+ * @ops:       oob operation description structure
+ */
+static int onenand_write_oob(struct mtd_info *mtd, loff_t to,
+                            struct mtd_oob_ops *ops)
+{
+       BUG_ON(ops->mode != MTD_OOB_PLACE);
+
+       return onenand_do_write_oob(mtd, to + ops->ooboffs, ops->ooblen,
+                                   &ops->oobretlen, ops->oobbuf);
+}
+
 /**
  * onenand_block_checkbad - [GENERIC] Check if a block is marked bad
  * @param mtd          MTD device structure
@@ -1055,10 +1215,7 @@ 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) {
-                       if (ret == -EPERM)
-                               DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Device is write protected!!!\n");
-                       else
-                               DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Failed erase, block %d\n", (unsigned) (addr >> this->erase_shift));
+                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Failed erase, block %d\n", (unsigned) (addr >> this->erase_shift));
                        instr->state = MTD_ERASE_FAILED;
                        instr->fail_addr = addr;
                        goto erase_exit;
@@ -1100,7 +1257,6 @@ static void onenand_sync(struct mtd_info *mtd)
        onenand_release_device(mtd);
 }
 
-
 /**
  * onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad
  * @param mtd          MTD device structure
@@ -1140,7 +1296,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 mtd->write_oob(mtd, ofs , 2, &retlen, buf);
+        return onenand_do_write_oob(mtd, ofs , 2, &retlen, buf);
 }
 
 /**
@@ -1167,32 +1323,38 @@ static int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs)
 }
 
 /**
- * onenand_unlock - [MTD Interface] Unlock block(s)
+ * onenand_do_lock_cmd - [OneNAND Interface] Lock or unlock block(s)
  * @param mtd          MTD device structure
  * @param ofs          offset relative to mtd start
- * @param len          number of bytes to unlock
+ * @param len          number of bytes to lock or unlock
  *
- * Unlock one or more blocks
+ * Lock or unlock one or more blocks
  */
-static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
+static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int cmd)
 {
        struct onenand_chip *this = mtd->priv;
        int start, end, block, value, status;
+       int wp_status_mask;
 
        start = ofs >> this->erase_shift;
        end = len >> this->erase_shift;
 
+       if (cmd == ONENAND_CMD_LOCK)
+               wp_status_mask = ONENAND_WP_LS;
+       else
+               wp_status_mask = ONENAND_WP_US;
+
        /* Continuous lock scheme */
-       if (this->options & ONENAND_CONT_LOCK) {
+       if (this->options & ONENAND_HAS_CONT_LOCK) {
                /* Set start block address */
                this->write_word(start, this->base + ONENAND_REG_START_BLOCK_ADDRESS);
                /* Set end block address */
-               this->write_word(end - 1, this->base + ONENAND_REG_END_BLOCK_ADDRESS);
-               /* Write unlock command */
-               this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0);
+               this->write_word(start + end - 1, this->base + ONENAND_REG_END_BLOCK_ADDRESS);
+               /* Write lock command */
+               this->command(mtd, cmd, 0, 0);
 
                /* There's no return value */
-               this->wait(mtd, FL_UNLOCKING);
+               this->wait(mtd, FL_LOCKING);
 
                /* Sanity check */
                while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS)
@@ -1201,14 +1363,14 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
 
                /* Check lock status */
                status = this->read_word(this->base + ONENAND_REG_WP_STATUS);
-               if (!(status & ONENAND_WP_US))
+               if (!(status & wp_status_mask))
                        printk(KERN_ERR "wp status = 0x%x\n", status);
 
                return 0;
        }
 
        /* Block lock scheme */
-       for (block = start; block < end; block++) {
+       for (block = start; block < start + end; block++) {
                /* Set block address */
                value = onenand_block_address(this, block);
                this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1);
@@ -1217,22 +1379,121 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
                this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2);
                /* Set start block address */
                this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS);
-               /* Write unlock command */
-               this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0);
+               /* Write lock command */
+               this->command(mtd, cmd, 0, 0);
 
                /* There's no return value */
-               this->wait(mtd, FL_UNLOCKING);
+               this->wait(mtd, FL_LOCKING);
 
                /* Sanity check */
                while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS)
                    & ONENAND_CTRL_ONGO)
                        continue;
 
+               /* 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);
+       }
+
+       return 0;
+}
+
+/**
+ * onenand_lock - [MTD Interface] Lock block(s)
+ * @param mtd          MTD device structure
+ * @param ofs          offset relative to mtd start
+ * @param len          number of bytes to unlock
+ *
+ * Lock one or more blocks
+ */
+static int onenand_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+       return onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_LOCK);
+}
+
+/**
+ * onenand_unlock - [MTD Interface] Unlock block(s)
+ * @param mtd          MTD device structure
+ * @param ofs          offset relative to mtd start
+ * @param len          number of bytes to unlock
+ *
+ * Unlock one or more blocks
+ */
+static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+       return onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK);
+}
+
+/**
+ * onenand_check_lock_status - [OneNAND Interface] Check lock status
+ * @param this         onenand chip data structure
+ *
+ * Check lock status
+ */
+static void onenand_check_lock_status(struct onenand_chip *this)
+{
+       unsigned int value, block, status;
+       unsigned int end;
+
+       end = this->chipsize >> this->erase_shift;
+       for (block = 0; block < end; block++) {
+               /* Set block address */
+               value = onenand_block_address(this, block);
+               this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1);
+               /* Select DataRAM for DDP */
+               value = onenand_bufferram_address(this, block);
+               this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2);
+               /* Set start block address */
+               this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS);
+
                /* 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);
        }
+}
+
+/**
+ * onenand_unlock_all - [OneNAND Interface] unlock all blocks
+ * @param mtd          MTD device structure
+ *
+ * Unlock all blocks
+ */
+static int onenand_unlock_all(struct mtd_info *mtd)
+{
+       struct onenand_chip *this = mtd->priv;
+
+       if (this->options & ONENAND_HAS_UNLOCK_ALL) {
+               /* Write unlock command */
+               this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0);
+
+               /* There's no return value */
+               this->wait(mtd, FL_LOCKING);
+
+               /* Sanity check */
+               while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS)
+                   & ONENAND_CTRL_ONGO)
+                       continue;
+
+               /* Workaround for all block unlock in DDP */
+               if (this->device_id & ONENAND_DEVICE_IS_DDP) {
+                       loff_t ofs;
+                       size_t len;
+
+                       /* 1st block on another chip */
+                       ofs = this->chipsize >> 1;
+                       len = 1 << this->erase_shift;
+
+                       onenand_unlock(mtd, ofs, len);
+               }
+
+               onenand_check_lock_status(this);
+
+               return 0;
+       }
+
+       onenand_unlock(mtd, 0x0, this->chipsize);
 
        return 0;
 }
@@ -1330,7 +1591,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 = mtd->write_oob(mtd, from, len, retlen, buf);
+       ret = onenand_do_write_oob(mtd, from, len, retlen, buf);
 
        /* Exit OTP access mode */
        this->command(mtd, ONENAND_CMD_RESET, 0, 0);
@@ -1535,13 +1796,44 @@ static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
 }
 #endif /* CONFIG_MTD_ONENAND_OTP */
 
+/**
+ * onenand_lock_scheme - Check and set OneNAND lock scheme
+ * @param mtd          MTD data structure
+ *
+ * Check and set OneNAND lock scheme
+ */
+static void onenand_lock_scheme(struct mtd_info *mtd)
+{
+       struct onenand_chip *this = mtd->priv;
+       unsigned int density, process;
+
+       /* Lock scheme depends on density and process */
+       density = this->device_id >> ONENAND_DEVICE_DENSITY_SHIFT;
+       process = this->version_id >> ONENAND_VERSION_PROCESS_SHIFT;
+
+       /* Lock scheme */
+       if (density >= ONENAND_DEVICE_DENSITY_1Gb) {
+               /* A-Die has all block unlock */
+               if (process) {
+                       printk(KERN_DEBUG "Chip support all block unlock\n");
+                       this->options |= ONENAND_HAS_UNLOCK_ALL;
+               }
+       } else {
+               /* Some OneNAND has continues lock scheme */
+               if (!process) {
+                       printk(KERN_DEBUG "Lock scheme is Continues Lock\n");
+                       this->options |= ONENAND_HAS_CONT_LOCK;
+               }
+       }
+}
+
 /**
  * onenand_print_device_info - Print device ID
  * @param device        device ID
  *
  * Print device ID
  */
-static void onenand_print_device_info(int device)
+static void onenand_print_device_info(int device, int version)
 {
         int vcc, demuxed, ddp, density;
 
@@ -1555,6 +1847,7 @@ static void onenand_print_device_info(int device)
                 (16 << density),
                 vcc ? "2.65/3.3" : "1.8",
                 device);
+       printk(KERN_DEBUG "OneNAND version = 0x%04x\n", version);
 }
 
 static const struct onenand_manufacturers onenand_manuf_ids[] = {
@@ -1597,9 +1890,14 @@ static int onenand_check_maf(int manuf)
 static int onenand_probe(struct mtd_info *mtd)
 {
        struct onenand_chip *this = mtd->priv;
-       int bram_maf_id, bram_dev_id, maf_id, dev_id;
-       int version_id;
+       int bram_maf_id, bram_dev_id, maf_id, dev_id, ver_id;
        int density;
+       int syscfg;
+
+       /* Save system configuration 1 */
+       syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1);
+       /* Clear Sync. Burst Read mode to read BootRAM */
+       this->write_word((syscfg & ~ONENAND_SYS_CFG1_SYNC_READ), this->base + ONENAND_REG_SYS_CFG1);
 
        /* Send the command for reading device ID from BootRAM */
        this->write_word(ONENAND_CMD_READID, this->base + ONENAND_BOOTRAM);
@@ -1608,24 +1906,31 @@ static int onenand_probe(struct mtd_info *mtd)
        bram_maf_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x0);
        bram_dev_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x2);
 
+       /* Reset OneNAND to read default register values */
+       this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_BOOTRAM);
+       /* Wait reset */
+       this->wait(mtd, FL_RESETING);
+
+       /* Restore system configuration 1 */
+       this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1);
+
        /* Check manufacturer ID */
        if (onenand_check_maf(bram_maf_id))
                return -ENXIO;
 
-       /* Reset OneNAND to read default register values */
-       this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_BOOTRAM);
-
        /* Read manufacturer and device IDs from Register */
        maf_id = this->read_word(this->base + ONENAND_REG_MANUFACTURER_ID);
        dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID);
+       ver_id = this->read_word(this->base + ONENAND_REG_VERSION_ID);
 
        /* Check OneNAND device */
        if (maf_id != bram_maf_id || dev_id != bram_dev_id)
                return -ENXIO;
 
        /* Flash device information */
-       onenand_print_device_info(dev_id);
+       onenand_print_device_info(dev_id, ver_id);
        this->device_id = dev_id;
+       this->version_id = ver_id;
 
        density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT;
        this->chipsize = (16 << density) << 20;
@@ -1648,16 +1953,8 @@ static int onenand_probe(struct mtd_info *mtd)
 
        mtd->size = this->chipsize;
 
-       /* Version ID */
-       version_id = this->read_word(this->base + ONENAND_REG_VERSION_ID);
-       printk(KERN_DEBUG "OneNAND version = 0x%04x\n", version_id);
-
-       /* Lock scheme */
-       if (density <= ONENAND_DEVICE_DENSITY_512Mb &&
-           !(version_id >> ONENAND_VERSION_PROCESS_SHIFT)) {
-               printk(KERN_INFO "Lock scheme is Continues Lock\n");
-               this->options |= ONENAND_CONT_LOCK;
-       }
+       /* Check OneNAND lock scheme */
+       onenand_lock_scheme(mtd);
 
        return 0;
 }
@@ -1708,7 +2005,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
        if (!this->command)
                this->command = onenand_command;
        if (!this->wait)
-               this->wait = onenand_wait;
+               onenand_setup_wait(mtd);
 
        if (!this->read_bufferram)
                this->read_bufferram = onenand_read_bufferram;
@@ -1745,24 +2042,31 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
        init_waitqueue_head(&this->wq);
        spin_lock_init(&this->chip_lock);
 
+       /*
+        * Allow subpage writes up to oobsize.
+        */
        switch (mtd->oobsize) {
        case 64:
-               this->autooob = &onenand_oob_64;
+               this->ecclayout = &onenand_oob_64;
+               mtd->subpage_sft = 2;
                break;
 
        case 32:
-               this->autooob = &onenand_oob_32;
+               this->ecclayout = &onenand_oob_32;
+               mtd->subpage_sft = 1;
                break;
 
        default:
                printk(KERN_WARNING "No OOB scheme defined for oobsize %d\n",
                        mtd->oobsize);
+               mtd->subpage_sft = 0;
                /* To prevent kernel oops */
-               this->autooob = &onenand_oob_32;
+               this->ecclayout = &onenand_oob_32;
                break;
        }
 
-       memcpy(&mtd->oobinfo, this->autooob, sizeof(mtd->oobinfo));
+       this->subpagesize = mtd->writesize >> mtd->subpage_sft;
+       mtd->ecclayout = this->ecclayout;
 
        /* Fill in remaining MTD driver data */
        mtd->type = MTD_NANDFLASH;
@@ -1784,7 +2088,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
        mtd->lock_user_prot_reg = onenand_lock_user_prot_reg;
 #endif
        mtd->sync = onenand_sync;
-       mtd->lock = NULL;
+       mtd->lock = onenand_lock;
        mtd->unlock = onenand_unlock;
        mtd->suspend = onenand_suspend;
        mtd->resume = onenand_resume;
@@ -1793,7 +2097,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
        mtd->owner = THIS_MODULE;
 
        /* Unlock whole block */
-       mtd->unlock(mtd, 0x0, this->chipsize);
+       onenand_unlock_all(mtd);
 
        return this->scan_bbt(mtd);
 }