nand: Extend nand_(read|write)_skip_bad with *actual and limit parameters
[platform/kernel/u-boot.git] / drivers / mtd / nand / nand_util.c
index 78e70cc..4727f9c 100644 (file)
@@ -11,6 +11,9 @@
  *             nandwrite.c by Steven J. Hill (sjhill@realitydiluted.com)
  *                            and Thomas Gleixner (tglx@linutronix.de)
  *
+ * Copyright (C) 2008 Nokia Corporation: drop_ffs() function by
+ * Artem Bityutskiy <dedekind1@gmail.com> from mtd-utils
+ *
  * See file CREDITS for list of people who contributed to this
  * project.
  *
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  * MA 02111-1307 USA
  *
+ * Copyright 2010 Freescale Semiconductor
+ * The portions of this file whose copyright is held by Freescale and which
+ * are not considered a derived work of GPL v2-only code may be distributed
+ * and/or modified under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
  */
 
 #include <common.h>
-
-#if defined(CONFIG_CMD_NAND) && !defined(CFG_NAND_LEGACY)
-
 #include <command.h>
 #include <watchdog.h>
 #include <malloc.h>
 #include <div64.h>
 
-
 #include <asm/errno.h>
 #include <linux/mtd/mtd.h>
 #include <nand.h>
 #include <jffs2/jffs2.h>
 
-typedef struct erase_info erase_info_t;
-typedef struct mtd_info          mtd_info_t;
+typedef struct erase_info      erase_info_t;
+typedef struct mtd_info                mtd_info_t;
 
 /* support only for native endian JFFS2 */
 #define cpu_to_je16(x) (x)
 #define cpu_to_je32(x) (x)
 
-/*****************************************************************************/
-static int nand_block_bad_scrub(struct mtd_info *mtd, loff_t ofs, int getchip)
-{
-       return 0;
-}
-
 /**
  * nand_erase_opts: - erase NAND flash with support for various options
- *                   (jffs2 formating)
+ *                   (jffs2 formatting)
  *
  * @param meminfo      NAND device to erase
  * @param opts         options,  @see struct nand_erase_options
@@ -73,72 +72,66 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts)
 {
        struct jffs2_unknown_node cleanmarker;
        erase_info_t erase;
-       ulong erase_length;
+       unsigned long erase_length, erased_length; /* in blocks */
        int bbtest = 1;
        int result;
        int percent_complete = -1;
-       int (*nand_block_bad_old)(struct mtd_info *, loff_t, int) = NULL;
        const char *mtd_device = meminfo->name;
        struct mtd_oob_ops oob_opts;
        struct nand_chip *chip = meminfo->priv;
-       uint8_t buf[64];
 
-       memset(buf, 0, sizeof(buf));
+       if ((opts->offset & (meminfo->erasesize - 1)) != 0) {
+               printf("Attempt to erase non block-aligned data\n");
+               return -1;
+       }
+
        memset(&erase, 0, sizeof(erase));
        memset(&oob_opts, 0, sizeof(oob_opts));
 
        erase.mtd = meminfo;
        erase.len  = meminfo->erasesize;
        erase.addr = opts->offset;
-       erase_length = opts->length;
-
+       erase_length = lldiv(opts->length + meminfo->erasesize - 1,
+                            meminfo->erasesize);
 
-       cleanmarker.magic = cpu_to_je16 (JFFS2_MAGIC_BITMASK);
-       cleanmarker.nodetype = cpu_to_je16 (JFFS2_NODETYPE_CLEANMARKER);
+       cleanmarker.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK);
+       cleanmarker.nodetype = cpu_to_je16(JFFS2_NODETYPE_CLEANMARKER);
        cleanmarker.totlen = cpu_to_je32(8);
-       cleanmarker.hdr_crc = cpu_to_je32(
-       crc32_no_comp(0, (unsigned char *) &cleanmarker,
-       sizeof(struct jffs2_unknown_node) - 4));
 
        /* scrub option allows to erase badblock. To prevent internal
         * check from erase() method, set block check method to dummy
         * and disable bad block table while erasing.
         */
        if (opts->scrub) {
-               struct nand_chip *priv_nand = meminfo->priv;
-
-               nand_block_bad_old = priv_nand->block_bad;
-               priv_nand->block_bad = nand_block_bad_scrub;
-               /* we don't need the bad block table anymore...
+               erase.scrub = opts->scrub;
+               /*
+                * We don't need the bad block table anymore...
                 * after scrub, there are no bad blocks left!
                 */
-               if (priv_nand->bbt) {
-                       kfree(priv_nand->bbt);
+               if (chip->bbt) {
+                       kfree(chip->bbt);
                }
-               priv_nand->bbt = NULL;
-       }
-
-       if (erase_length < meminfo->erasesize) {
-               printf("Warning: Erase size 0x%08lx smaller than one "  \
-                      "erase block 0x%08x\n",erase_length, meminfo->erasesize);
-               printf("         Erasing 0x%08x instead\n", meminfo->erasesize);
-               erase_length = meminfo->erasesize;
+               chip->bbt = NULL;
        }
 
-       for (;
-            erase.addr < opts->offset + erase_length;
+       for (erased_length = 0;
+            erased_length < erase_length;
             erase.addr += meminfo->erasesize) {
-               
-               WATCHDOG_RESET ();
+
+               WATCHDOG_RESET();
 
                if (!opts->scrub && bbtest) {
                        int ret = meminfo->block_isbad(meminfo, erase.addr);
                        if (ret > 0) {
                                if (!opts->quiet)
                                        printf("\rSkipping bad block at  "
-                                              "0x%08x                   "
+                                              "0x%08llx                 "
                                               "                         \n",
                                               erase.addr);
+
+                               if (!opts->spread)
+                                       erased_length++;
+
                                continue;
 
                        } else if (ret < 0) {
@@ -149,6 +142,8 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts)
                        }
                }
 
+               erased_length++;
+
                result = meminfo->erase(meminfo, &erase);
                if (result != 0) {
                        printf("\n%s: MTD Erase failure: %d\n",
@@ -157,29 +152,25 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts)
                }
 
                /* format for JFFS2 ? */
-               if (opts->jffs2) {
-
-                       chip->ops.len = chip->ops.ooblen = 64;
+               if (opts->jffs2 && chip->ecc.layout->oobavail >= 8) {
+                       chip->ops.ooblen = 8;
                        chip->ops.datbuf = NULL;
-                       chip->ops.oobbuf = buf;
-                       chip->ops.ooboffs = chip->badblockpos & ~0x01;
-                       
+                       chip->ops.oobbuf = (uint8_t *)&cleanmarker;
+                       chip->ops.ooboffs = 0;
+                       chip->ops.mode = MTD_OOB_AUTO;
+
                        result = meminfo->write_oob(meminfo,
-                                                       erase.addr + meminfo->oobsize,
-                                                       &chip->ops);
+                                                   erase.addr,
+                                                   &chip->ops);
                        if (result != 0) {
                                printf("\n%s: MTD writeoob failure: %d\n",
-                               mtd_device, result);
+                                      mtd_device, result);
                                continue;
                        }
-                       else
-                               printf("%s: MTD writeoob at 0x%08x\n",mtd_device, erase.addr + meminfo->oobsize );
                }
 
                if (!opts->quiet) {
-                       unsigned long long n =(unsigned long long)
-                               (erase.addr + meminfo->erasesize - opts->offset)
-                               * 100;
+                       unsigned long long n = erased_length * 100ULL;
                        int percent;
 
                        do_div(n, erase_length);
@@ -192,819 +183,694 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts)
                        if (percent != percent_complete) {
                                percent_complete = percent;
 
-                               printf("\rErasing at 0x%x -- %3d%% complete.",
-                               erase.addr, percent);
+                               printf("\rErasing at 0x%llx -- %3d%% complete.",
+                                      erase.addr, percent);
 
                                if (opts->jffs2 && result == 0)
-                               printf(" Cleanmarker written at 0x%x.",
-                               erase.addr);
+                                       printf(" Cleanmarker written at 0x%llx.",
+                                              erase.addr);
                        }
                }
        }
        if (!opts->quiet)
                printf("\n");
 
-       if (nand_block_bad_old) {
-               struct nand_chip *priv_nand = meminfo->priv;
-
-               priv_nand->block_bad = nand_block_bad_old;
-               priv_nand->scan_bbt(meminfo);
-       }
+       if (opts->scrub)
+               chip->scan_bbt(meminfo);
 
        return 0;
 }
 
-/* XXX U-BOOT XXX */
-#if 0
-
-#define MAX_PAGE_SIZE  2048
-#define MAX_OOB_SIZE   64
-
-/*
- * buffer array used for writing data
- */
-static unsigned char data_buf[MAX_PAGE_SIZE];
-static unsigned char oob_buf[MAX_OOB_SIZE];
-
-/* OOB layouts to pass into the kernel as default */
-static struct nand_ecclayout none_ecclayout = {
-       .useecc = MTD_NANDECC_OFF,
-};
-
-static struct nand_ecclayout jffs2_ecclayout = {
-       .useecc = MTD_NANDECC_PLACE,
-       .eccbytes = 6,
-       .eccpos = { 0, 1, 2, 3, 6, 7 }
-};
-
-static struct nand_ecclayout yaffs_ecclayout = {
-       .useecc = MTD_NANDECC_PLACE,
-       .eccbytes = 6,
-       .eccpos = { 8, 9, 10, 13, 14, 15}
-};
-
-static struct nand_ecclayout autoplace_ecclayout = {
-       .useecc = MTD_NANDECC_AUTOPLACE
-};
-#endif
+#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK
 
+/******************************************************************************
+ * Support for locking / unlocking operations of some NAND devices
+ *****************************************************************************/
 
 /**
- * nand_fill_oob - [Internal] Transfer client buffer to oob
- * @chip:      nand chip structure
- * @oob:       oob data buffer
- * @ops:       oob ops structure
- * 
- * Copied from nand_base.c
- */
-static uint8_t *nand_fill_oob(struct nand_chip *chip, uint8_t *oob,
-                                 struct mtd_oob_ops *ops)
-{
-       size_t len = ops->ooblen;
-
-       switch(ops->mode) {
-
-       case MTD_OOB_PLACE:
-       case MTD_OOB_RAW:
-               memcpy(chip->oob_poi + ops->ooboffs, oob, len);
-               return oob + len;
-
-       case MTD_OOB_AUTO: {
-               struct nand_oobfree *free = chip->ecc.layout->oobfree;
-               uint32_t boffs = 0, woffs = ops->ooboffs;
-               size_t bytes = 0;
-
-               for(; free->length && len; free++, len -= bytes) {
-                       /* Write request not from offset 0 ? */
-                       if (unlikely(woffs)) {
-                               if (woffs >= free->length) {
-                                       woffs -= free->length;
-                                       continue;
-                               }
-                               boffs = free->offset + woffs;
-                               bytes = min_t(size_t, len,
-                                             (free->length - woffs));
-                               woffs = 0;
-                       } else {
-                               bytes = min_t(size_t, len, free->length);
-                               boffs = free->offset;
-                       }
-                       memcpy(chip->oob_poi + boffs, oob, bytes);
-                       oob += bytes;
-               }
-               return oob;
-       }
-       default:
-               BUG();
-       }
-       return NULL;
-}
-
-#define NOTALIGNED(x)  (x & (chip->subpagesize - 1)) != 0
-
-
-/* copied from nand_base.c: nand_do_write_ops()
- * Only very small changes
+ * nand_lock: Set all pages of NAND flash chip to the LOCK or LOCK-TIGHT
+ *           state
+ *
+ * @param mtd          nand mtd instance
+ * @param tight                bring device in lock tight mode
+ *
+ * @return             0 on success, -1 in case of error
+ *
+ * The lock / lock-tight command only applies to the whole chip. To get some
+ * parts of the chip lock and others unlocked use the following sequence:
+ *
+ * - Lock all pages of the chip using nand_lock(mtd, 0) (or the lockpre pin)
+ * - Call nand_unlock() once for each consecutive area to be unlocked
+ * - If desired: Bring the chip to the lock-tight state using nand_lock(mtd, 1)
+ *
+ *   If the device is in lock-tight state software can't change the
+ *   current active lock/unlock state of all pages. nand_lock() / nand_unlock()
+ *   calls will fail. It is only posible to leave lock-tight state by
+ *   an hardware signal (low pulse on _WP pin) or by power down.
  */
-int nand_write_opts(nand_info_t *mtd, loff_t to, mtd_oob_ops_t *ops)
+int nand_lock(struct mtd_info *mtd, int tight)
 {
-       int chipnr, realpage, page, blockmask, column;
+       int ret = 0;
+       int status;
        struct nand_chip *chip = mtd->priv;
-       uint32_t writelen = ops->len;
-       uint8_t *oob = ops->oobbuf;
-       uint8_t *buf = ops->datbuf;
-       int ret, subpage;
-       
-       ops->retlen = 0;
-       if (!writelen)
-               return 0;
-
-       printk("nand_write_opts: to: 0x%08x, ops->len: 0x%08x\n", to, ops->len);
-       
-       /* reject writes, which are not page aligned */
-       if (NOTALIGNED(to) || NOTALIGNED(ops->len)) {
-               printk(KERN_NOTICE "nand_write: "
-                      "Attempt to write not page aligned data\n");
-               return -EINVAL;
-       }
-
-       column = to & (mtd->writesize - 1);
-       subpage = column || (writelen & (mtd->writesize - 1));
-
-       if (subpage && oob) {
-               printk(KERN_NOTICE "nand_write: "
-                      "Attempt to write oob to subpage\n");
-               return -EINVAL;
-       }
-
-       chipnr = (int)(to >> chip->chip_shift);
-       chip->select_chip(mtd, chipnr);
-
-       /* XXX U-BOOT XXX */
-#if 0
-       /* Check, if it is write protected */
-       if (nand_check_wp(mtd))
-               return -EIO;
-#endif
-
-       realpage = (int)(to >> chip->page_shift);
-       page = realpage & chip->pagemask;
-       blockmask = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1;
 
-       /* Invalidate the page cache, when we write to the cached page */
-       if (to <= (chip->pagebuf << chip->page_shift) &&
-           (chip->pagebuf << chip->page_shift) < (to + ops->len))
-               chip->pagebuf = -1;
+       /* select the NAND device */
+       chip->select_chip(mtd, 0);
 
-       /* If we're not given explicit OOB data, let it be 0xFF */
-       if (likely(!oob)) {
-               printf("!oob, writing %d bytes with 0xff to chip->oob_poi (0x%08x)\n", mtd->oobsize, chip->oob_poi);
-               memset(chip->oob_poi, 0xff, mtd->oobsize);
+       /* check the Lock Tight Status */
+       chip->cmdfunc(mtd, NAND_CMD_LOCK_STATUS, -1, 0);
+       if (chip->read_byte(mtd) & NAND_LOCK_STATUS_TIGHT) {
+               printf("nand_lock: Device is locked tight!\n");
+               ret = -1;
+               goto out;
        }
 
-       while(1) {
-               int bytes = mtd->writesize;
-               int cached = writelen > bytes && page != blockmask;
-               uint8_t *wbuf = buf;
-
-               /* Partial page write ? */
-               if (unlikely(column || writelen < (mtd->writesize - 1))) {
-                       cached = 0;
-                       bytes = min_t(int, bytes - column, (int) writelen);
-                       chip->pagebuf = -1;
-                       memset(chip->buffers->databuf, 0xff, mtd->writesize);
-                       memcpy(&chip->buffers->databuf[column], buf, bytes);
-                       wbuf = chip->buffers->databuf;
-               }
-
-               if (unlikely(oob))
-                       oob = nand_fill_oob(chip, oob, ops);
-
-               ret = chip->write_page(mtd, chip, wbuf, page, cached,
-                                      (ops->mode == MTD_OOB_RAW));
-               if (ret)
-                       break;
-
-               writelen -= bytes;
-               if (!writelen)
-                       break;
+       chip->cmdfunc(mtd,
+                     (tight ? NAND_CMD_LOCK_TIGHT : NAND_CMD_LOCK),
+                     -1, -1);
 
-               column = 0;
-               buf += bytes;
-               realpage++;
+       /* call wait ready function */
+       status = chip->waitfunc(mtd, chip);
 
-               page = realpage & chip->pagemask;
-               /* Check, if we cross a chip boundary */
-               if (!page) {
-                       chipnr++;
-                       chip->select_chip(mtd, -1);
-                       chip->select_chip(mtd, chipnr);
-               }
+       /* see if device thinks it succeeded */
+       if (status & 0x01) {
+               ret = -1;
        }
 
-       ops->retlen = ops->len - writelen;
-       if (unlikely(oob))
-               ops->oobretlen = ops->ooblen;
+ out:
+       /* de-select the NAND device */
+       chip->select_chip(mtd, -1);
        return ret;
 }
 
-/* XXX U-BOOT XXX */
-#if 0
 /**
- * nand_write_opts: - write image to NAND flash with support for various options
+ * nand_get_lock_status: - query current lock state from one page of NAND
+ *                        flash
  *
- * @param meminfo      NAND device to erase
- * @param opts         write options (@see nand_write_options)
- * @return             0 in case of success
+ * @param mtd          nand mtd instance
+ * @param offset       page address to query (must be page-aligned!)
+ *
+ * @return             -1 in case of error
+ *                     >0 lock status:
+ *                       bitfield with the following combinations:
+ *                       NAND_LOCK_STATUS_TIGHT: page in tight state
+ *                       NAND_LOCK_STATUS_UNLOCK: page unlocked
  *
- * This code is ported from nandwrite.c from Linux mtd utils by
- * Steven J. Hill and Thomas Gleixner.
  */
-int nand_write_opts(nand_info_t *meminfo, const nand_write_options_t *opts)
+int nand_get_lock_status(struct mtd_info *mtd, loff_t offset)
 {
-       int imglen = 0;
-       int pagelen;
-       int baderaseblock;
-       int blockstart = -1;
-       loff_t offs;
-       int readlen;
-       int ecclayoutchanged = 0;
-       int percent_complete = -1;
-       struct nand_ecclayout old_ecclayout;
-       ulong mtdoffset = opts->offset;
-       ulong erasesize_blockalign;
-       u_char *buffer = opts->buffer;
-       size_t written;
-       int result;
+       int ret = 0;
+       int chipnr;
+       int page;
+       struct nand_chip *chip = mtd->priv;
 
-       if (opts->pad && opts->writeoob) {
-               printf("Can't pad when oob data is present.\n");
-               return -1;
-       }
+       /* select the NAND device */
+       chipnr = (int)(offset >> chip->chip_shift);
+       chip->select_chip(mtd, chipnr);
 
-       /* set erasesize to specified number of blocks - to match
-        * jffs2 (virtual) block size */
-       if (opts->blockalign == 0) {
-               erasesize_blockalign = meminfo->erasesize;
-       } else {
-               erasesize_blockalign = meminfo->erasesize * opts->blockalign;
-       }
 
-       /* make sure device page sizes are valid */
-       if (!(meminfo->oobsize == 16 && meminfo->writesize == 512)
-           && !(meminfo->oobsize == 8 && meminfo->writesize == 256)
-           && !(meminfo->oobsize == 64 && meminfo->writesize == 2048)) {
-               printf("Unknown flash (not normal NAND)\n");
-               return -1;
+       if ((offset & (mtd->writesize - 1)) != 0) {
+               printf("nand_get_lock_status: "
+                       "Start address must be beginning of "
+                       "nand page!\n");
+               ret = -1;
+               goto out;
        }
 
-       /* read the current oob info */
-       memcpy(&old_ecclayout, &meminfo->ecclayout, sizeof(old_ecclayout));
+       /* check the Lock Status */
+       page = (int)(offset >> chip->page_shift);
+       chip->cmdfunc(mtd, NAND_CMD_LOCK_STATUS, -1, page & chip->pagemask);
 
-       /* write without ecc? */
-       if (opts->noecc) {
-               memcpy(&meminfo->ecclayout, &none_ecclayout,
-                      sizeof(meminfo->ecclayout));
-               ecclayoutchanged = 1;
-       }
+       ret = chip->read_byte(mtd) & (NAND_LOCK_STATUS_TIGHT
+                                         | NAND_LOCK_STATUS_UNLOCK);
 
-       /* autoplace ECC? */
-       if (opts->autoplace && (old_ecclayout.useecc != MTD_NANDECC_AUTOPLACE)) {
+ out:
+       /* de-select the NAND device */
+       chip->select_chip(mtd, -1);
+       return ret;
+}
 
-               memcpy(&meminfo->ecclayout, &autoplace_ecclayout,
-                      sizeof(meminfo->ecclayout));
-               ecclayoutchanged = 1;
-       }
+/**
+ * nand_unlock: - Unlock area of NAND pages
+ *               only one consecutive area can be unlocked at one time!
+ *
+ * @param mtd          nand mtd instance
+ * @param start                start byte address
+ * @param length       number of bytes to unlock (must be a multiple of
+ *                     page size nand->writesize)
+ * @param allexcept    if set, unlock everything not selected
+ *
+ * @return             0 on success, -1 in case of error
+ */
+int nand_unlock(struct mtd_info *mtd, loff_t start, size_t length,
+       int allexcept)
+{
+       int ret = 0;
+       int chipnr;
+       int status;
+       int page;
+       struct nand_chip *chip = mtd->priv;
 
-       /* force OOB layout for jffs2 or yaffs? */
-       if (opts->forcejffs2 || opts->forceyaffs) {
-               struct nand_ecclayout *oobsel =
-                       opts->forcejffs2 ? &jffs2_ecclayout : &yaffs_ecclayout;
+       debug("nand_unlock%s: start: %08llx, length: %d!\n",
+               allexcept ? " (allexcept)" : "", start, length);
 
-               if (meminfo->oobsize == 8) {
-                       if (opts->forceyaffs) {
-                               printf("YAFSS cannot operate on "
-                                      "256 Byte page size\n");
-                               goto restoreoob;
-                       }
-                       /* Adjust number of ecc bytes */
-                       jffs2_ecclayout.eccbytes = 3;
-               }
+       /* select the NAND device */
+       chipnr = (int)(start >> chip->chip_shift);
+       chip->select_chip(mtd, chipnr);
 
-               memcpy(&meminfo->ecclayout, oobsel, sizeof(meminfo->ecclayout));
+       /* check the WP bit */
+       chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+       if (!(chip->read_byte(mtd) & NAND_STATUS_WP)) {
+               printf("nand_unlock: Device is write protected!\n");
+               ret = -1;
+               goto out;
        }
 
-       /* get image length */
-       imglen = opts->length;
-       pagelen = meminfo->writesize
-               + ((opts->writeoob != 0) ? meminfo->oobsize : 0);
+       /* check the Lock Tight Status */
+       page = (int)(start >> chip->page_shift);
+       chip->cmdfunc(mtd, NAND_CMD_LOCK_STATUS, -1, page & chip->pagemask);
+       if (chip->read_byte(mtd) & NAND_LOCK_STATUS_TIGHT) {
+               printf("nand_unlock: Device is locked tight!\n");
+               ret = -1;
+               goto out;
+       }
 
-       /* check, if file is pagealigned */
-       if ((!opts->pad) && ((imglen % pagelen) != 0)) {
-               printf("Input block length is not page aligned\n");
-               goto restoreoob;
+       if ((start & (mtd->erasesize - 1)) != 0) {
+               printf("nand_unlock: Start address must be beginning of "
+                       "nand block!\n");
+               ret = -1;
+               goto out;
        }
 
-       /* check, if length fits into device */
-       if (((imglen / pagelen) * meminfo->writesize)
-            > (meminfo->size - opts->offset)) {
-               printf("Image %d bytes, NAND page %d bytes, "
-                      "OOB area %u bytes, device size %u bytes\n",
-                      imglen, pagelen, meminfo->writesize, meminfo->size);
-               printf("Input block does not fit into device\n");
-               goto restoreoob;
+       if (length == 0 || (length & (mtd->erasesize - 1)) != 0) {
+               printf("nand_unlock: Length must be a multiple of nand block "
+                       "size %08x!\n", mtd->erasesize);
+               ret = -1;
+               goto out;
        }
 
-       if (!opts->quiet)
-               printf("\n");
+       /*
+        * Set length so that the last address is set to the
+        * starting address of the last block
+        */
+       length -= mtd->erasesize;
 
-       /* get data from input and write to the device */
-       while (imglen && (mtdoffset < meminfo->size)) {
+       /* submit address of first page to unlock */
+       chip->cmdfunc(mtd, NAND_CMD_UNLOCK1, -1, page & chip->pagemask);
 
-               WATCHDOG_RESET ();
+       /* submit ADDRESS of LAST page to unlock */
+       page += (int)(length >> chip->page_shift);
 
-               /*
-                * new eraseblock, check for bad block(s). Stay in the
-                * loop to be sure if the offset changes because of
-                * a bad block, that the next block that will be
-                * written to is also checked. Thus avoiding errors if
-                * the block(s) after the skipped block(s) is also bad
-                * (number of blocks depending on the blockalign
-                */
-               while (blockstart != (mtdoffset & (~erasesize_blockalign+1))) {
-                       blockstart = mtdoffset & (~erasesize_blockalign+1);
-                       offs = blockstart;
-                       baderaseblock = 0;
-
-                       /* check all the blocks in an erase block for
-                        * bad blocks */
-                       do {
-                               int ret = meminfo->block_isbad(meminfo, offs);
-
-                               if (ret < 0) {
-                                       printf("Bad block check failed\n");
-                                       goto restoreoob;
-                               }
-                               if (ret == 1) {
-                                       baderaseblock = 1;
-                                       if (!opts->quiet)
-                                               printf("\rBad block at 0x%lx "
-                                                      "in erase block from "
-                                                      "0x%x will be skipped\n",
-                                                      (long) offs,
-                                                      blockstart);
-                               }
-
-                               if (baderaseblock) {
-                                       mtdoffset = blockstart
-                                               + erasesize_blockalign;
-                               }
-                               offs +=  erasesize_blockalign
-                                       / opts->blockalign;
-                       } while (offs < blockstart + erasesize_blockalign);
-               }
+       /*
+        * Page addresses for unlocking are supposed to be block-aligned.
+        * At least some NAND chips use the low bit to indicate that the
+        * page range should be inverted.
+        */
+       if (allexcept)
+               page |= 1;
 
-               readlen = meminfo->writesize;
-               if (opts->pad && (imglen < readlen)) {
-                       readlen = imglen;
-                       memset(data_buf + readlen, 0xff,
-                              meminfo->writesize - readlen);
-               }
+       chip->cmdfunc(mtd, NAND_CMD_UNLOCK2, -1, page & chip->pagemask);
 
-               /* read page data from input memory buffer */
-               memcpy(data_buf, buffer, readlen);
-               buffer += readlen;
+       /* call wait ready function */
+       status = chip->waitfunc(mtd, chip);
+       /* see if device thinks it succeeded */
+       if (status & 0x01) {
+               /* there was an error */
+               ret = -1;
+               goto out;
+       }
 
-               if (opts->writeoob) {
-                       /* read OOB data from input memory block, exit
-                        * on failure */
-                       memcpy(oob_buf, buffer, meminfo->oobsize);
-                       buffer += meminfo->oobsize;
+ out:
+       /* de-select the NAND device */
+       chip->select_chip(mtd, -1);
+       return ret;
+}
+#endif
 
-                       /* write OOB data first, as ecc will be placed
-                        * in there*/
-                       result = meminfo->write_oob(meminfo,
-                                                   mtdoffset,
-                                                   meminfo->oobsize,
-                                                   &written,
-                                                   (unsigned char *)
-                                                   &oob_buf);
+/**
+ * check_skip_len
+ *
+ * Check if there are any bad blocks, and whether length including bad
+ * blocks fits into device
+ *
+ * @param nand NAND device
+ * @param offset offset in flash
+ * @param length image length
+ * @param used length of flash needed for the requested length
+ * @return 0 if the image fits and there are no bad blocks
+ *         1 if the image fits, but there are bad blocks
+ *        -1 if the image does not fit
+ */
+static int check_skip_len(nand_info_t *nand, loff_t offset, size_t length,
+               size_t *used)
+{
+       size_t len_excl_bad = 0;
+       int ret = 0;
 
-                       if (result != 0) {
-                               printf("\nMTD writeoob failure: %d\n",
-                                      result);
-                               goto restoreoob;
-                       }
-                       imglen -= meminfo->oobsize;
-               }
+       while (len_excl_bad < length) {
+               size_t block_len, block_off;
+               loff_t block_start;
 
-               /* write out the page data */
-               result = meminfo->write(meminfo,
-                                       mtdoffset,
-                                       meminfo->writesize,
-                                       &written,
-                                       (unsigned char *) &data_buf);
+               if (offset >= nand->size)
+                       return -1;
 
-               if (result != 0) {
-                       printf("writing NAND page at offset 0x%lx failed\n",
-                              mtdoffset);
-                       goto restoreoob;
-               }
-               imglen -= readlen;
+               block_start = offset & ~(loff_t)(nand->erasesize - 1);
+               block_off = offset & (nand->erasesize - 1);
+               block_len = nand->erasesize - block_off;
 
-               if (!opts->quiet) {
-                       unsigned long long n = (unsigned long long)
-                                (opts->length-imglen) * 100;
-                       int percent;
+               if (!nand_block_isbad(nand, block_start))
+                       len_excl_bad += block_len;
+               else
+                       ret = 1;
 
-                       do_div(n, opts->length);
-                       percent = (int)n;
+               offset += block_len;
+               *used += block_len;
+       }
 
-                       /* output progress message only at whole percent
-                        * steps to reduce the number of messages printed
-                        * on (slow) serial consoles
-                        */
-                       if (percent != percent_complete) {
-                               printf("\rWriting data at 0x%lx "
-                                      "-- %3d%% complete.",
-                                      mtdoffset, percent);
-                               percent_complete = percent;
-                       }
-               }
+       /* If the length is not a multiple of block_len, adjust. */
+       if (len_excl_bad > length)
+               *used -= (len_excl_bad - length);
 
-               mtdoffset += meminfo->writesize;
-       }
+       return ret;
+}
 
-       if (!opts->quiet)
-               printf("\n");
+#ifdef CONFIG_CMD_NAND_TRIMFFS
+static size_t drop_ffs(const nand_info_t *nand, const u_char *buf,
+                       const size_t *len)
+{
+       size_t i, l = *len;
 
-restoreoob:
-       if (ecclayoutchanged) {
-               memcpy(&meminfo->ecclayout, &old_ecclayout,
-                      sizeof(meminfo->ecclayout));
-       }
+       for (i = l - 1; i >= 0; i--)
+               if (buf[i] != 0xFF)
+                       break;
 
-       if (imglen > 0) {
-               printf("Data did not fit into device, due to bad blocks\n");
-               return -1;
-       }
+       /* The resulting length must be aligned to the minimum flash I/O size */
+       l = i + 1;
+       l = (l + nand->writesize - 1) / nand->writesize;
+       l *=  nand->writesize;
 
-       /* return happy */
-       return 0;
+       /*
+        * since the input length may be unaligned, prevent access past the end
+        * of the buffer
+        */
+       return min(l, *len);
 }
+#endif
 
 /**
- * nand_read_opts: - read image from NAND flash with support for various options
+ * nand_write_skip_bad:
  *
- * @param meminfo      NAND device to erase
- * @param opts         read options (@see struct nand_read_options)
- * @return             0 in case of success
+ * Write image to NAND flash.
+ * Blocks that are marked bad are skipped and the is written to the next
+ * block instead as long as the image is short enough to fit even after
+ * skipping the bad blocks.  Due to bad blocks we may not be able to
+ * perform the requested write.  In the case where the write would
+ * extend beyond the end of the NAND device, both length and actual (if
+ * not NULL) are set to 0.  In the case where the write would extend
+ * beyond the limit we are passed, length is set to 0 and actual is set
+ * to the required length.
  *
+ * @param nand         NAND device
+ * @param offset       offset in flash
+ * @param length       buffer length
+ * @param actual       set to size required to write length worth of
+ *                     buffer or 0 on error, if not NULL
+ * @param lim          maximum size that actual may be in order to not
+ *                     exceed the buffer
+ * @param buffer        buffer to read from
+ * @param flags                flags modifying the behaviour of the write to NAND
+ * @return             0 in case of success
  */
-int nand_read_opts(nand_info_t *meminfo, const nand_read_options_t *opts)
+int nand_write_skip_bad(nand_info_t *nand, loff_t offset, size_t *length,
+               size_t *actual, loff_t lim, u_char *buffer, int flags)
 {
-       int imglen = opts->length;
-       int pagelen;
-       int baderaseblock;
-       int blockstart = -1;
-       int percent_complete = -1;
-       loff_t offs;
-       size_t readlen;
-       ulong mtdoffset = opts->offset;
-       u_char *buffer = opts->buffer;
-       int result;
-
-       /* make sure device page sizes are valid */
-       if (!(meminfo->oobsize == 16 && meminfo->writesize == 512)
-           && !(meminfo->oobsize == 8 && meminfo->writesize == 256)
-           && !(meminfo->oobsize == 64 && meminfo->writesize == 2048)) {
-               printf("Unknown flash (not normal NAND)\n");
-               return -1;
+       int rval = 0, blocksize;
+       size_t left_to_write = *length;
+       size_t used_for_write = 0;
+       u_char *p_buffer = buffer;
+       int need_skip;
+
+       if (actual)
+               *actual = 0;
+
+#ifdef CONFIG_CMD_NAND_YAFFS
+       if (flags & WITH_YAFFS_OOB) {
+               if (flags & ~WITH_YAFFS_OOB)
+                       return -EINVAL;
+
+               int pages;
+               pages = nand->erasesize / nand->writesize;
+               blocksize = (pages * nand->oobsize) + nand->erasesize;
+               if (*length % (nand->writesize + nand->oobsize)) {
+                       printf("Attempt to write incomplete page"
+                               " in yaffs mode\n");
+                       return -EINVAL;
+               }
+       } else
+#endif
+       {
+               blocksize = nand->erasesize;
        }
 
-       pagelen = meminfo->writesize
-               + ((opts->readoob != 0) ? meminfo->oobsize : 0);
-
-       /* check, if length is not larger than device */
-       if (((imglen / pagelen) * meminfo->writesize)
-            > (meminfo->size - opts->offset)) {
-               printf("Image %d bytes, NAND page %d bytes, "
-                      "OOB area %u bytes, device size %u bytes\n",
-                      imglen, pagelen, meminfo->writesize, meminfo->size);
-               printf("Input block is larger than device\n");
-               return -1;
+       /*
+        * nand_write() handles unaligned, partial page writes.
+        *
+        * We allow length to be unaligned, for convenience in
+        * using the $filesize variable.
+        *
+        * However, starting at an unaligned offset makes the
+        * semantics of bad block skipping ambiguous (really,
+        * you should only start a block skipping access at a
+        * partition boundary).  So don't try to handle that.
+        */
+       if ((offset & (nand->writesize - 1)) != 0) {
+               printf("Attempt to write non page-aligned data\n");
+               *length = 0;
+               return -EINVAL;
        }
 
-       if (!opts->quiet)
-               printf("\n");
-
-       /* get data from input and write to the device */
-       while (imglen && (mtdoffset < meminfo->size)) {
+       need_skip = check_skip_len(nand, offset, *length, &used_for_write);
 
-               WATCHDOG_RESET ();
-
-               /*
-                * new eraseblock, check for bad block(s). Stay in the
-                * loop to be sure if the offset changes because of
-                * a bad block, that the next block that will be
-                * written to is also checked. Thus avoiding errors if
-                * the block(s) after the skipped block(s) is also bad
-                * (number of blocks depending on the blockalign
-                */
-               while (blockstart != (mtdoffset & (~meminfo->erasesize+1))) {
-                       blockstart = mtdoffset & (~meminfo->erasesize+1);
-                       offs = blockstart;
-                       baderaseblock = 0;
-
-                       /* check all the blocks in an erase block for
-                        * bad blocks */
-                       do {
-                               int ret = meminfo->block_isbad(meminfo, offs);
-
-                               if (ret < 0) {
-                                       printf("Bad block check failed\n");
-                                       return -1;
-                               }
-                               if (ret == 1) {
-                                       baderaseblock = 1;
-                                       if (!opts->quiet)
-                                               printf("\rBad block at 0x%lx "
-                                                      "in erase block from "
-                                                      "0x%x will be skipped\n",
-                                                      (long) offs,
-                                                      blockstart);
-                               }
-
-                               if (baderaseblock) {
-                                       mtdoffset = blockstart
-                                               + meminfo->erasesize;
-                               }
-                               offs +=  meminfo->erasesize;
-
-                       } while (offs < blockstart + meminfo->erasesize);
-               }
+       if (actual)
+               *actual = used_for_write;
 
+       if (need_skip < 0) {
+               printf("Attempt to write outside the flash area\n");
+               *length = 0;
+               return -EINVAL;
+       }
 
-               /* read page data to memory buffer */
-               result = meminfo->read(meminfo,
-                                      mtdoffset,
-                                      meminfo->writesize,
-                                      &readlen,
-                                      (unsigned char *) &data_buf);
+       if (used_for_write > lim) {
+               puts("Size of write exceeds partition or device limit\n");
+               *length = 0;
+               return -EFBIG;
+       }
 
-               if (result != 0) {
-                       printf("reading NAND page at offset 0x%lx failed\n",
-                              mtdoffset);
-                       return -1;
-               }
+       if (!need_skip && !(flags & WITH_DROP_FFS)) {
+               rval = nand_write(nand, offset, length, buffer);
+               if (rval == 0)
+                       return 0;
 
-               if (imglen < readlen) {
-                       readlen = imglen;
-               }
+               *length = 0;
+               printf("NAND write to offset %llx failed %d\n",
+                       offset, rval);
+               return rval;
+       }
 
-               memcpy(buffer, data_buf, readlen);
-               buffer += readlen;
-               imglen -= readlen;
+       while (left_to_write > 0) {
+               size_t block_offset = offset & (nand->erasesize - 1);
+               size_t write_size, truncated_write_size;
 
-               if (opts->readoob) {
-                       result = meminfo->read_oob(meminfo,
-                                                  mtdoffset,
-                                                  meminfo->oobsize,
-                                                  &readlen,
-                                                  (unsigned char *)
-                                                  &oob_buf);
+               WATCHDOG_RESET();
 
-                       if (result != 0) {
-                               printf("\nMTD readoob failure: %d\n",
-                                      result);
-                               return -1;
-                       }
+               if (nand_block_isbad(nand, offset & ~(nand->erasesize - 1))) {
+                       printf("Skip bad block 0x%08llx\n",
+                               offset & ~(nand->erasesize - 1));
+                       offset += nand->erasesize - block_offset;
+                       continue;
+               }
 
+               if (left_to_write < (blocksize - block_offset))
+                       write_size = left_to_write;
+               else
+                       write_size = blocksize - block_offset;
 
-                       if (imglen < readlen) {
-                               readlen = imglen;
-                       }
+#ifdef CONFIG_CMD_NAND_YAFFS
+               if (flags & WITH_YAFFS_OOB) {
+                       int page, pages;
+                       size_t pagesize = nand->writesize;
+                       size_t pagesize_oob = pagesize + nand->oobsize;
+                       struct mtd_oob_ops ops;
 
-                       memcpy(buffer, oob_buf, readlen);
+                       ops.len = pagesize;
+                       ops.ooblen = nand->oobsize;
+                       ops.mode = MTD_OOB_AUTO;
+                       ops.ooboffs = 0;
 
-                       buffer += readlen;
-                       imglen -= readlen;
-               }
+                       pages = write_size / pagesize_oob;
+                       for (page = 0; page < pages; page++) {
+                               WATCHDOG_RESET();
 
-               if (!opts->quiet) {
-                       unsigned long long n = (unsigned long long)
-                                (opts->length-imglen) * 100;
-                       int percent;
+                               ops.datbuf = p_buffer;
+                               ops.oobbuf = ops.datbuf + pagesize;
 
-                       do_div(n, opts->length);
-                       percent = (int)n;
+                               rval = nand->write_oob(nand, offset, &ops);
+                               if (rval != 0)
+                                       break;
 
-                       /* output progress message only at whole percent
-                        * steps to reduce the number of messages printed
-                        * on (slow) serial consoles
-                        */
-                       if (percent != percent_complete) {
-                       if (!opts->quiet)
-                               printf("\rReading data from 0x%lx "
-                                      "-- %3d%% complete.",
-                                      mtdoffset, percent);
-                               percent_complete = percent;
+                               offset += pagesize;
+                               p_buffer += pagesize_oob;
                        }
                }
+               else
+#endif
+               {
+                       truncated_write_size = write_size;
+#ifdef CONFIG_CMD_NAND_TRIMFFS
+                       if (flags & WITH_DROP_FFS)
+                               truncated_write_size = drop_ffs(nand, p_buffer,
+                                               &write_size);
+#endif
 
-               mtdoffset += meminfo->writesize;
-       }
+                       rval = nand_write(nand, offset, &truncated_write_size,
+                                       p_buffer);
+                       offset += write_size;
+                       p_buffer += write_size;
+               }
 
-       if (!opts->quiet)
-               printf("\n");
+               if (rval != 0) {
+                       printf("NAND write to offset %llx failed %d\n",
+                               offset, rval);
+                       *length -= left_to_write;
+                       return rval;
+               }
 
-       if (imglen > 0) {
-               printf("Could not read entire image due to bad blocks\n");
-               return -1;
+               left_to_write -= write_size;
        }
 
-       /* return happy */
        return 0;
 }
-#endif
-
-/* XXX U-BOOT XXX */
-#if 0
-/******************************************************************************
- * Support for locking / unlocking operations of some NAND devices
- *****************************************************************************/
-
-#define NAND_CMD_LOCK          0x2a
-#define NAND_CMD_LOCK_TIGHT    0x2c
-#define NAND_CMD_UNLOCK1       0x23
-#define NAND_CMD_UNLOCK2       0x24
-#define NAND_CMD_LOCK_STATUS   0x7a
 
 /**
- * nand_lock: Set all pages of NAND flash chip to the LOCK or LOCK-TIGHT
- *           state
- *
- * @param meminfo      nand mtd instance
- * @param tight                bring device in lock tight mode
- *
- * @return             0 on success, -1 in case of error
+ * nand_read_skip_bad:
  *
- * The lock / lock-tight command only applies to the whole chip. To get some
- * parts of the chip lock and others unlocked use the following sequence:
- *
- * - Lock all pages of the chip using nand_lock(mtd, 0) (or the lockpre pin)
- * - Call nand_unlock() once for each consecutive area to be unlocked
- * - If desired: Bring the chip to the lock-tight state using nand_lock(mtd, 1)
+ * Read image from NAND flash.
+ * Blocks that are marked bad are skipped and the next block is read
+ * instead as long as the image is short enough to fit even after
+ * skipping the bad blocks.  Due to bad blocks we may not be able to
+ * perform the requested read.  In the case where the read would extend
+ * beyond the end of the NAND device, both length and actual (if not
+ * NULL) are set to 0.  In the case where the read would extend beyond
+ * the limit we are passed, length is set to 0 and actual is set to the
+ * required length.
  *
- *   If the device is in lock-tight state software can't change the
- *   current active lock/unlock state of all pages. nand_lock() / nand_unlock()
- *   calls will fail. It is only posible to leave lock-tight state by
- *   an hardware signal (low pulse on _WP pin) or by power down.
+ * @param nand NAND device
+ * @param offset offset in flash
+ * @param length buffer length, on return holds number of read bytes
+ * @param actual set to size required to read length worth of buffer or 0
+ * on error, if not NULL
+ * @param lim maximum size that actual may be in order to not exceed the
+ * buffer
+ * @param buffer buffer to write to
+ * @return 0 in case of success
  */
-int nand_lock(nand_info_t *meminfo, int tight)
+int nand_read_skip_bad(nand_info_t *nand, loff_t offset, size_t *length,
+               size_t *actual, loff_t lim, u_char *buffer)
 {
-       int ret = 0;
-       int status;
-       struct nand_chip *this = meminfo->priv;
+       int rval;
+       size_t left_to_read = *length;
+       size_t used_for_read = 0;
+       u_char *p_buffer = buffer;
+       int need_skip;
+
+       if ((offset & (nand->writesize - 1)) != 0) {
+               printf("Attempt to read non page-aligned data\n");
+               *length = 0;
+               if (actual)
+                       *actual = 0;
+               return -EINVAL;
+       }
 
-       /* select the NAND device */
-       this->select_chip(meminfo, 0);
+       need_skip = check_skip_len(nand, offset, *length, &used_for_read);
 
-       this->cmdfunc(meminfo,
-                     (tight ? NAND_CMD_LOCK_TIGHT : NAND_CMD_LOCK),
-                     -1, -1);
+       if (actual)
+               *actual = used_for_read;
 
-       /* call wait ready function */
-       status = this->waitfunc(meminfo, this, FL_WRITING);
+       if (need_skip < 0) {
+               printf("Attempt to read outside the flash area\n");
+               *length = 0;
+               return -EINVAL;
+       }
 
-       /* see if device thinks it succeeded */
-       if (status & 0x01) {
-               ret = -1;
+       if (used_for_read > lim) {
+               puts("Size of read exceeds partition or device limit\n");
+               *length = 0;
+               return -EFBIG;
        }
 
-       /* de-select the NAND device */
-       this->select_chip(meminfo, -1);
-       return ret;
-}
+       if (!need_skip) {
+               rval = nand_read(nand, offset, length, buffer);
+               if (!rval || rval == -EUCLEAN)
+                       return 0;
 
-/**
- * nand_get_lock_status: - query current lock state from one page of NAND
- *                        flash
- *
- * @param meminfo      nand mtd instance
- * @param offset       page address to query (muss be page aligned!)
- *
- * @return             -1 in case of error
- *                     >0 lock status:
- *                       bitfield with the following combinations:
- *                       NAND_LOCK_STATUS_TIGHT: page in tight state
- *                       NAND_LOCK_STATUS_LOCK:  page locked
- *                       NAND_LOCK_STATUS_UNLOCK: page unlocked
- *
- */
-int nand_get_lock_status(nand_info_t *meminfo, ulong offset)
-{
-       int ret = 0;
-       int chipnr;
-       int page;
-       struct nand_chip *this = meminfo->priv;
+               *length = 0;
+               printf("NAND read from offset %llx failed %d\n",
+                       offset, rval);
+               return rval;
+       }
 
-       /* select the NAND device */
-       chipnr = (int)(offset >> this->chip_shift);
-       this->select_chip(meminfo, chipnr);
+       while (left_to_read > 0) {
+               size_t block_offset = offset & (nand->erasesize - 1);
+               size_t read_length;
 
+               WATCHDOG_RESET();
 
-       if ((offset & (meminfo->writesize - 1)) != 0) {
-               printf ("nand_get_lock_status: "
-                       "Start address must be beginning of "
-                       "nand page!\n");
-               ret = -1;
-               goto out;
-       }
+               if (nand_block_isbad(nand, offset & ~(nand->erasesize - 1))) {
+                       printf("Skipping bad block 0x%08llx\n",
+                               offset & ~(nand->erasesize - 1));
+                       offset += nand->erasesize - block_offset;
+                       continue;
+               }
 
-       /* check the Lock Status */
-       page = (int)(offset >> this->page_shift);
-       this->cmdfunc(meminfo, NAND_CMD_LOCK_STATUS, -1, page & this->pagemask);
+               if (left_to_read < (nand->erasesize - block_offset))
+                       read_length = left_to_read;
+               else
+                       read_length = nand->erasesize - block_offset;
+
+               rval = nand_read(nand, offset, &read_length, p_buffer);
+               if (rval && rval != -EUCLEAN) {
+                       printf("NAND read from offset %llx failed %d\n",
+                               offset, rval);
+                       *length -= left_to_read;
+                       return rval;
+               }
 
-       ret = this->read_byte(meminfo) & (NAND_LOCK_STATUS_TIGHT
-                                         | NAND_LOCK_STATUS_LOCK
-                                         | NAND_LOCK_STATUS_UNLOCK);
+               left_to_read -= read_length;
+               offset       += read_length;
+               p_buffer     += read_length;
+       }
 
- out:
-       /* de-select the NAND device */
-       this->select_chip(meminfo, -1);
-       return ret;
+       return 0;
 }
 
+#ifdef CONFIG_CMD_NAND_TORTURE
+
 /**
- * nand_unlock: - Unlock area of NAND pages
- *               only one consecutive area can be unlocked at one time!
+ * check_pattern:
  *
- * @param meminfo      nand mtd instance
- * @param start                start byte address
- * @param length       number of bytes to unlock (must be a multiple of
- *                     page size nand->writesize)
+ * Check if buffer contains only a certain byte pattern.
  *
- * @return             0 on success, -1 in case of error
+ * @param buf buffer to check
+ * @param patt the pattern to check
+ * @param size buffer size in bytes
+ * @return 1 if there are only patt bytes in buf
+ *         0 if something else was found
  */
-int nand_unlock(nand_info_t *meminfo, ulong start, ulong length)
+static int check_pattern(const u_char *buf, u_char patt, int size)
 {
-       int ret = 0;
-       int chipnr;
-       int status;
-       int page;
-       struct nand_chip *this = meminfo->priv;
-       printf ("nand_unlock: start: %08x, length: %d!\n",
-               (int)start, (int)length);
+       int i;
 
-       /* select the NAND device */
-       chipnr = (int)(start >> this->chip_shift);
-       this->select_chip(meminfo, chipnr);
+       for (i = 0; i < size; i++)
+               if (buf[i] != patt)
+                       return 0;
+       return 1;
+}
 
-       /* check the WP bit */
-       this->cmdfunc(meminfo, NAND_CMD_STATUS, -1, -1);
-       if ((this->read_byte(meminfo) & 0x80) == 0) {
-               printf ("nand_unlock: Device is write protected!\n");
-               ret = -1;
-               goto out;
+/**
+ * nand_torture:
+ *
+ * Torture a block of NAND flash.
+ * This is useful to determine if a block that caused a write error is still
+ * good or should be marked as bad.
+ *
+ * @param nand NAND device
+ * @param offset offset in flash
+ * @return 0 if the block is still good
+ */
+int nand_torture(nand_info_t *nand, loff_t offset)
+{
+       u_char patterns[] = {0xa5, 0x5a, 0x00};
+       struct erase_info instr = {
+               .mtd = nand,
+               .addr = offset,
+               .len = nand->erasesize,
+       };
+       size_t retlen;
+       int err, ret = -1, i, patt_count;
+       u_char *buf;
+
+       if ((offset & (nand->erasesize - 1)) != 0) {
+               puts("Attempt to torture a block at a non block-aligned offset\n");
+               return -EINVAL;
        }
 
-       if ((start & (meminfo->writesize - 1)) != 0) {
-               printf ("nand_unlock: Start address must be beginning of "
-                       "nand page!\n");
-               ret = -1;
-               goto out;
+       if (offset + nand->erasesize > nand->size) {
+               puts("Attempt to torture a block outside the flash area\n");
+               return -EINVAL;
        }
 
-       if (length == 0 || (length & (meminfo->writesize - 1)) != 0) {
-               printf ("nand_unlock: Length must be a multiple of nand page "
-                       "size!\n");
-               ret = -1;
-               goto out;
+       patt_count = ARRAY_SIZE(patterns);
+
+       buf = malloc(nand->erasesize);
+       if (buf == NULL) {
+               puts("Out of memory for erase block buffer\n");
+               return -ENOMEM;
        }
 
-       /* submit address of first page to unlock */
-       page = (int)(start >> this->page_shift);
-       this->cmdfunc(meminfo, NAND_CMD_UNLOCK1, -1, page & this->pagemask);
+       for (i = 0; i < patt_count; i++) {
+               err = nand->erase(nand, &instr);
+               if (err) {
+                       printf("%s: erase() failed for block at 0x%llx: %d\n",
+                               nand->name, instr.addr, err);
+                       goto out;
+               }
 
-       /* submit ADDRESS of LAST page to unlock */
-       page += (int)(length >> this->page_shift) - 1;
-       this->cmdfunc(meminfo, NAND_CMD_UNLOCK2, -1, page & this->pagemask);
+               /* Make sure the block contains only 0xff bytes */
+               err = nand->read(nand, offset, nand->erasesize, &retlen, buf);
+               if ((err && err != -EUCLEAN) || retlen != nand->erasesize) {
+                       printf("%s: read() failed for block at 0x%llx: %d\n",
+                               nand->name, instr.addr, err);
+                       goto out;
+               }
 
-       /* call wait ready function */
-       status = this->waitfunc(meminfo, this, FL_WRITING);
-       /* see if device thinks it succeeded */
-       if (status & 0x01) {
-               /* there was an error */
-               ret = -1;
-               goto out;
+               err = check_pattern(buf, 0xff, nand->erasesize);
+               if (!err) {
+                       printf("Erased block at 0x%llx, but a non-0xff byte was found\n",
+                               offset);
+                       ret = -EIO;
+                       goto out;
+               }
+
+               /* Write a pattern and check it */
+               memset(buf, patterns[i], nand->erasesize);
+               err = nand->write(nand, offset, nand->erasesize, &retlen, buf);
+               if (err || retlen != nand->erasesize) {
+                       printf("%s: write() failed for block at 0x%llx: %d\n",
+                               nand->name, instr.addr, err);
+                       goto out;
+               }
+
+               err = nand->read(nand, offset, nand->erasesize, &retlen, buf);
+               if ((err && err != -EUCLEAN) || retlen != nand->erasesize) {
+                       printf("%s: read() failed for block at 0x%llx: %d\n",
+                               nand->name, instr.addr, err);
+                       goto out;
+               }
+
+               err = check_pattern(buf, patterns[i], nand->erasesize);
+               if (!err) {
+                       printf("Pattern 0x%.2x checking failed for block at "
+                                       "0x%llx\n", patterns[i], offset);
+                       ret = -EIO;
+                       goto out;
+               }
        }
 
- out:
-       /* de-select the NAND device */
-       this->select_chip(meminfo, -1);
+       ret = 0;
+
+out:
+       free(buf);
        return ret;
 }
-#endif
 
 #endif