]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mtd/onenand/onenand_base.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/x86/linux...
[linux-2.6-omap-h63xx.git] / drivers / mtd / onenand / onenand_base.c
index b281b116aaeb55dab9c8c4e210cba43231392a17..5d7965f7e9ce6a3f8232a53cb2c0e2a243f13379 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/sched.h>
+#include <linux/delay.h>
 #include <linux/interrupt.h>
 #include <linux/jiffies.h>
 #include <linux/mtd/mtd.h>
@@ -328,6 +329,21 @@ static int onenand_wait(struct mtd_info *mtd, int state)
                printk(KERN_ERR "onenand_wait: controller error = 0x%04x\n", ctrl);
                if (ctrl & ONENAND_CTRL_LOCK)
                        printk(KERN_ERR "onenand_wait: it's locked error.\n");
+               if (state == FL_READING) {
+                       /*
+                        * A power loss while writing can result in a page
+                        * becoming unreadable.  When the device is mounted
+                        * again, reading that page gives controller errors.
+                        * Upper level software like JFFS2 treat -EIO as fatal,
+                        * refusing to mount at all.  That means it is necessary
+                        * to treat the error as an ECC error to allow recovery.
+                        * Note that typically in this case, the eraseblock can
+                        * still be erased and rewritten i.e. it has not become
+                        * a bad block.
+                        */
+                       mtd->ecc_stats.failed++;
+                       return -EBADMSG;
+               }
                return -EIO;
        }
 
@@ -1284,6 +1300,112 @@ static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr,
 
 #define NOTALIGNED(x)  ((x & (this->subpagesize - 1)) != 0)
 
+static void onenand_panic_wait(struct mtd_info *mtd)
+{
+       struct onenand_chip *this = mtd->priv;
+       unsigned int interrupt;
+       int i;
+       
+       for (i = 0; i < 2000; i++) {
+               interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT);
+               if (interrupt & ONENAND_INT_MASTER)
+                       break;
+               udelay(10);
+       }
+}
+
+/**
+ * onenand_panic_write - [MTD Interface] write buffer to FLASH in a panic context
+ * @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
+ *
+ * Write with ECC
+ */
+static int onenand_panic_write(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, subpage;
+       int written = 0;
+       int ret = 0;
+
+       if (this->state == FL_PM_SUSPENDED)
+               return -EBUSY;
+
+       /* 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);
+
+       /* 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");
+               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");
+                return -EINVAL;
+        }
+
+       column = to & (mtd->writesize - 1);
+
+       /* Loop until all data write */
+       while (written < len) {
+               int thislen = min_t(int, mtd->writesize - column, len - written);
+               u_char *wbuf = (u_char *) buf;
+
+               this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen);
+
+               /* Partial page write */
+               subpage = thislen < mtd->writesize;
+               if (subpage) {
+                       memset(this->page_buf, 0xff, mtd->writesize);
+                       memcpy(this->page_buf + column, buf, thislen);
+                       wbuf = this->page_buf;
+               }
+
+               this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize);
+               this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0, mtd->oobsize);
+
+               this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize);
+
+               onenand_panic_wait(mtd);
+
+               /* In partial page write we don't update bufferram */
+               onenand_update_bufferram(mtd, to, !ret && !subpage);
+               if (ONENAND_IS_2PLANE(this)) {
+                       ONENAND_SET_BUFFERRAM1(this);
+                       onenand_update_bufferram(mtd, to + this->writesize, !ret && !subpage);
+               }
+
+               if (ret) {
+                       printk(KERN_ERR "onenand_panic_write: write failed %d\n", ret);
+                       break;
+               }
+
+               written += thislen;
+
+               if (written == len)
+                       break;
+
+               column = 0;
+               to += thislen;
+               buf += thislen;
+       }
+
+       *retlen = written;
+       return ret;
+}
+
 /**
  * onenand_fill_auto_oob - [Internal] oob auto-placement transfer
  * @param mtd          MTD device structure
@@ -1359,7 +1481,7 @@ static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
        }
 
        /* Reject writes, which are not page aligned */
-        if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(len))) {
+        if (unlikely(NOTALIGNED(to) || NOTALIGNED(len))) {
                 printk(KERN_ERR "onenand_write_ops_nolock: Attempt to write not page aligned data\n");
                 return -EINVAL;
         }
@@ -1945,7 +2067,7 @@ static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
  *
  * Check lock status
  */
-static void onenand_check_lock_status(struct onenand_chip *this)
+static int onenand_check_lock_status(struct onenand_chip *this)
 {
        unsigned int value, block, status;
        unsigned int end;
@@ -1963,9 +2085,13 @@ static void 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))
+               if (!(status & ONENAND_WP_US)) {
                        printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status);
+                       return 0;
+               }
        }
+
+       return 1;
 }
 
 /**
@@ -1974,9 +2100,11 @@ static void onenand_check_lock_status(struct onenand_chip *this)
  *
  * Unlock all blocks
  */
-static int onenand_unlock_all(struct mtd_info *mtd)
+static void onenand_unlock_all(struct mtd_info *mtd)
 {
        struct onenand_chip *this = mtd->priv;
+       loff_t ofs = 0;
+       size_t len = this->chipsize;
 
        if (this->options & ONENAND_HAS_UNLOCK_ALL) {
                /* Set start block address */
@@ -1992,23 +2120,19 @@ static int onenand_unlock_all(struct mtd_info *mtd)
                    & ONENAND_CTRL_ONGO)
                        continue;
 
+               /* Check lock status */
+               if (onenand_check_lock_status(this))
+                       return;
+
                /* Workaround for all block unlock in DDP */
                if (ONENAND_IS_DDP(this)) {
-                       /* 1st block on another chip */
-                       loff_t ofs = this->chipsize >> 1;
-                       size_t len = mtd->erasesize;
-
-                       onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK);
+                       /* All blocks on another chip */
+                       ofs = this->chipsize >> 1;
+                       len = this->chipsize >> 1;
                }
-
-               onenand_check_lock_status(this);
-
-               return 0;
        }
 
-       onenand_do_lock_cmd(mtd, 0x0, this->chipsize, ONENAND_CMD_UNLOCK);
-
-       return 0;
+       onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK);
 }
 
 #ifdef CONFIG_MTD_ONENAND_OTP
@@ -2673,6 +2797,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
        mtd->write = onenand_write;
        mtd->read_oob = onenand_read_oob;
        mtd->write_oob = onenand_write_oob;
+       mtd->panic_write = onenand_panic_write;
 #ifdef CONFIG_MTD_ONENAND_OTP
        mtd->get_fact_prot_info = onenand_get_fact_prot_info;
        mtd->read_fact_prot_reg = onenand_read_fact_prot_reg;