]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mtd/onenand/onenand_base.c
[MTD] [NAND] make oobavail public
[linux-2.6-omap-h63xx.git] / drivers / mtd / onenand / onenand_base.c
index 6df309b89e9ac8f362ee789c186cd55946658f00..621c3f8ec27b85d7641dffbce3ec537d6825bd71 100644 (file)
@@ -304,16 +304,16 @@ 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) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: controller error = 0x%04x\n", ctrl);
+               printk(KERN_ERR "onenand_wait: controller error = 0x%04x\n", ctrl);
                if (ctrl & ONENAND_CTRL_LOCK)
-                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: it's locked error.\n");
+                       printk(KERN_ERR "onenand_wait: it's locked error.\n");
                return ctrl;
        }
 
        if (interrupt & ONENAND_INT_READ) {
                int ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS);
                if (ecc) {
-                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_wait: ECC error = 0x%04x\n", ecc);
+                       printk(KERN_ERR "onenand_wait: ECC error = 0x%04x\n", ecc);
                        if (ecc & ONENAND_ECC_2BIT_ALL) {
                                mtd->ecc_stats.failed++;
                                return ecc;
@@ -577,7 +577,7 @@ static int onenand_write_bufferram(struct mtd_info *mtd, int area,
 static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr)
 {
        struct onenand_chip *this = mtd->priv;
-       int blockpage;
+       int blockpage, found = 0;
        unsigned int i;
 
        blockpage = (int) (addr >> this->page_shift);
@@ -585,16 +585,24 @@ static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr)
        /* Is there valid data? */
        i = ONENAND_CURRENT_BUFFERRAM(this);
        if (this->bufferram[i].blockpage == blockpage)
-               return 1;
+               found = 1;
+       else {
+               /* Check another BufferRAM */
+               i = ONENAND_NEXT_BUFFERRAM(this);
+               if (this->bufferram[i].blockpage == blockpage) {
+                       ONENAND_SET_NEXT_BUFFERRAM(this);
+                       found = 1;
+               }
+       }
 
-       /* Check another BufferRAM */
-       i = ONENAND_NEXT_BUFFERRAM(this);
-       if (this->bufferram[i].blockpage == blockpage) {
-               ONENAND_SET_NEXT_BUFFERRAM(this);
-               return 1;
+       if (found && ONENAND_IS_DDP(this)) {
+               /* Select DataRAM for DDP */
+               int block = (int) (addr >> this->erase_shift);
+               int value = onenand_bufferram_address(this, block);
+               this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2);
        }
 
-       return 0;
+       return found;
 }
 
 /**
@@ -627,6 +635,29 @@ static void onenand_update_bufferram(struct mtd_info *mtd, loff_t addr,
                this->bufferram[i].blockpage = -1;
 }
 
+/**
+ * onenand_invalidate_bufferram - [GENERIC] Invalidate BufferRAM information
+ * @param mtd          MTD data structure
+ * @param addr         start address to invalidate
+ * @param len          length to invalidate
+ *
+ * Invalidate BufferRAM information
+ */
+static void onenand_invalidate_bufferram(struct mtd_info *mtd, loff_t addr,
+               unsigned int len)
+{
+       struct onenand_chip *this = mtd->priv;
+       int i;
+       loff_t end_addr = addr + len;
+
+       /* Invalidate BufferRAM */
+       for (i = 0; i < MAX_BUFFERRAM; i++) {
+               loff_t buf_addr = this->bufferram[i].blockpage << this->page_shift;
+               if (buf_addr >= addr && buf_addr < end_addr)
+                       this->bufferram[i].blockpage = -1;
+       }
+}
+
 /**
  * onenand_get_device - [GENERIC] Get chip for selected access
  * @param mtd          MTD device structure
@@ -703,7 +734,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
 
        /* Do not allow reads past end of device */
        if ((from + len) > mtd->size) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_read: Attempt read beyond end of device\n");
+               printk(KERN_ERR "onenand_read: Attempt read beyond end of device\n");
                *retlen = 0;
                return -EINVAL;
        }
@@ -834,7 +865,7 @@ static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, int col
  *
  * OneNAND read out-of-band data from the spare area
  */
-int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
+static int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
                        size_t *retlen, u_char *buf, mtd_oob_mode_t mode)
 {
        struct onenand_chip *this = mtd->priv;
@@ -854,7 +885,7 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
        column = from & (mtd->oobsize - 1);
 
        if (unlikely(column >= oobsize)) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: Attempted to start read outside oob\n");
+               printk(KERN_ERR "onenand_read_oob: Attempted to start read outside oob\n");
                return -EINVAL;
        }
 
@@ -862,7 +893,7 @@ int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
        if (unlikely(from >= mtd->size ||
                     column + len > ((mtd->size >> this->page_shift) -
                                     (from >> this->page_shift)) * oobsize)) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_read_oob: Attempted to read beyond end of device\n");
+               printk(KERN_ERR "onenand_read_oob: Attempted to read beyond end of device\n");
                return -EINVAL;
        }
 
@@ -888,7 +919,7 @@ int onenand_do_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);
+                       printk(KERN_ERR "onenand_read_oob: read failed = 0x%x\n", ret);
                        break;
                }
 
@@ -936,6 +967,121 @@ static int onenand_read_oob(struct mtd_info *mtd, loff_t from,
                                   &ops->oobretlen, ops->oobbuf, ops->mode);
 }
 
+/**
+ * onenand_bbt_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_bbt_wait(struct mtd_info *mtd, int state)
+{
+       struct onenand_chip *this = mtd->priv;
+       unsigned long timeout;
+       unsigned int interrupt;
+       unsigned int ctrl;
+
+       /* The 20 msec is enough */
+       timeout = jiffies + msecs_to_jiffies(20);
+       while (time_before(jiffies, timeout)) {
+               interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT);
+               if (interrupt & ONENAND_INT_MASTER)
+                       break;
+       }
+       /* To get correct interrupt status in timeout case */
+       interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT);
+       ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS);
+
+       if (ctrl & ONENAND_CTRL_ERROR) {
+               printk(KERN_DEBUG "onenand_bbt_wait: controller error = 0x%04x\n", ctrl);
+               /* Initial bad block case */
+               if (ctrl & ONENAND_CTRL_LOAD)
+                       return ONENAND_BBT_READ_ERROR;
+               return ONENAND_BBT_READ_FATAL_ERROR;
+       }
+
+       if (interrupt & ONENAND_INT_READ) {
+               int ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS);
+               if (ecc & ONENAND_ECC_2BIT_ALL)
+                       return ONENAND_BBT_READ_ERROR;
+       } else {
+               printk(KERN_ERR "onenand_bbt_wait: read timeout!"
+                       "ctrl=0x%04x intr=0x%04x\n", ctrl, interrupt);
+               return ONENAND_BBT_READ_FATAL_ERROR;
+       }
+
+       return 0;
+}
+
+/**
+ * onenand_bbt_read_oob - [MTD Interface] OneNAND read out-of-band for bbt scan
+ * @param mtd          MTD device structure
+ * @param from         offset to read from
+ * @param @ops         oob operation description structure
+ *
+ * OneNAND read out-of-band data from the spare area for bbt scan
+ */
+int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from, 
+                           struct mtd_oob_ops *ops)
+{
+       struct onenand_chip *this = mtd->priv;
+       int read = 0, thislen, column;
+       int ret = 0;
+       size_t len = ops->ooblen;
+       u_char *buf = ops->oobbuf;
+
+       DEBUG(MTD_DEBUG_LEVEL3, "onenand_bbt_read_oob: from = 0x%08x, len = %zi\n", (unsigned int) from, len);
+
+       /* Initialize return value */
+       ops->oobretlen = 0;
+
+       /* Do not allow reads past end of device */
+       if (unlikely((from + len) > mtd->size)) {
+               printk(KERN_ERR "onenand_bbt_read_oob: Attempt read beyond end of device\n");
+               return ONENAND_BBT_READ_FATAL_ERROR;
+       }
+
+       /* Grab the lock and see if the device is available */
+       onenand_get_device(mtd, FL_READING);
+
+       column = from & (mtd->oobsize - 1);
+
+       while (read < len) {
+               cond_resched();
+
+               thislen = mtd->oobsize - column;
+               thislen = min_t(int, thislen, len);
+
+               this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize);
+
+               onenand_update_bufferram(mtd, from, 0);
+
+               ret = onenand_bbt_wait(mtd, FL_READING);
+               if (ret)
+                       break;
+
+               this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen);
+               read += thislen;
+               if (read == len)
+                       break;
+
+               buf += thislen;
+
+               /* Read more? */
+               if (read < len) {
+                       /* Update Page size */
+                       from += mtd->writesize;
+                       column = 0;
+               }
+       }
+
+       /* Deselect and wake up anyone waiting on the device */
+       onenand_release_device(mtd);
+
+       ops->oobretlen = read;
+       return ret;
+}
+
 #ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE
 /**
  * onenand_verify_oob - [GENERIC] verify the oob contents after a write
@@ -1040,64 +1186,62 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
 
        /* Do not allow writes past end of device */
        if (unlikely((to + len) > mtd->size)) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: Attempt write to past end of device\n");
+               printk(KERN_ERR "onenand_write: Attempt write to past end of device\n");
                return -EINVAL;
        }
 
        /* Reject writes, which are not page aligned */
         if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(len))) {
-                DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: Attempt to write not page aligned data\n");
+                printk(KERN_ERR "onenand_write: Attempt to write not page aligned data\n");
                 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 bytes = mtd->writesize;
-               int thislen = min_t(int, bytes, len - written);
+               int thislen = min_t(int, mtd->writesize - column, len - written);
                u_char *wbuf = (u_char *) buf;
 
                cond_resched();
 
-               this->command(mtd, ONENAND_CMD_BUFFERRAM, to, bytes);
+               this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen);
 
                /* Partial page write */
+               subpage = thislen < mtd->writesize;
                if (subpage) {
-                       bytes = min_t(int, bytes - column, (int) len);
                        memset(this->page_buf, 0xff, mtd->writesize);
-                       memcpy(this->page_buf + column, buf, bytes);
+                       memcpy(this->page_buf + column, buf, thislen);
                        wbuf = this->page_buf;
-                       /* Even though partial write, we need page size */
-                       thislen = mtd->writesize;
                }
 
-               this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, thislen);
+               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);
 
+               ret = this->wait(mtd, FL_WRITING);
+
                /* In partial page write we don't update bufferram */
-               onenand_update_bufferram(mtd, to, !subpage);
+               onenand_update_bufferram(mtd, to, !ret && !subpage);
 
-               ret = this->wait(mtd, FL_WRITING);
                if (ret) {
-                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: write filaed %d\n", ret);
+                       printk(KERN_ERR "onenand_write: write filaed %d\n", ret);
                        break;
                }
 
                /* Only check verify write turn on */
                ret = onenand_verify(mtd, (u_char *) wbuf, to, thislen);
                if (ret) {
-                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_write: verify failed %d\n", ret);
+                       printk(KERN_ERR "onenand_write: verify failed %d\n", ret);
                        break;
                }
 
                written += thislen;
+
                if (written == len)
                        break;
 
@@ -1182,7 +1326,14 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
        column = to & (mtd->oobsize - 1);
 
        if (unlikely(column >= oobsize)) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: Attempted to start write outside oob\n");
+               printk(KERN_ERR "onenand_write_oob: Attempted to start write outside oob\n");
+               return -EINVAL;
+       }
+
+       /* For compatibility with NAND: Do not allow write past end of page */
+       if (column + len > oobsize) {
+               printk(KERN_ERR "onenand_write_oob: "
+                     "Attempt to write past end of page\n");
                return -EINVAL;
        }
 
@@ -1190,7 +1341,7 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
        if (unlikely(to >= mtd->size ||
                     column + len > ((mtd->size >> this->page_shift) -
                                     (to >> this->page_shift)) * oobsize)) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: Attempted to write past end of device\n");
+               printk(KERN_ERR "onenand_write_oob: Attempted to write past end of device\n");
                return -EINVAL;
        }
 
@@ -1220,13 +1371,13 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
 
                ret = this->wait(mtd, FL_WRITING);
                if (ret) {
-                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: write failed %d\n", ret);
+                       printk(KERN_ERR "onenand_write_oob: write failed %d\n", ret);
                        break;
                }
 
                ret = onenand_verify_oob(mtd, this->page_buf, to);
                if (ret) {
-                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_write_oob: verify failed %d\n", ret);
+                       printk(KERN_ERR "onenand_write_oob: verify failed %d\n", ret);
                        break;
                }
 
@@ -1309,19 +1460,19 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
 
        /* Start address must align on block boundary */
        if (unlikely(instr->addr & (block_size - 1))) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Unaligned address\n");
+               printk(KERN_ERR "onenand_erase: Unaligned address\n");
                return -EINVAL;
        }
 
        /* Length must align on block boundary */
        if (unlikely(instr->len & (block_size - 1))) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Length not block aligned\n");
+               printk(KERN_ERR "onenand_erase: Length not block aligned\n");
                return -EINVAL;
        }
 
        /* Do not allow erase past end of device */
        if (unlikely((instr->len + instr->addr) > mtd->size)) {
-               DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Erase past end of device\n");
+               printk(KERN_ERR "onenand_erase: Erase past end of device\n");
                return -EINVAL;
        }
 
@@ -1348,10 +1499,12 @@ static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr)
 
                this->command(mtd, ONENAND_CMD_ERASE, addr, block_size);
 
+               onenand_invalidate_bufferram(mtd, addr, block_size);
+
                ret = this->wait(mtd, FL_ERASING);
                /* Check, if it is write protected */
                if (ret) {
-                       DEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: Failed erase, block %d\n", (unsigned) (addr >> this->erase_shift));
+                       printk(KERN_ERR "onenand_erase: Failed erase, block %d\n", (unsigned) (addr >> this->erase_shift));
                        instr->state = MTD_ERASE_FAILED;
                        instr->fail_addr = addr;
                        goto erase_exit;
@@ -2214,13 +2367,13 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
        for (i = 0; this->ecclayout->oobfree[i].length; i++)
                this->ecclayout->oobavail +=
                        this->ecclayout->oobfree[i].length;
+       mtd->oobavail = this->ecclayout->oobavail;
 
        mtd->ecclayout = this->ecclayout;
 
        /* Fill in remaining MTD driver data */
        mtd->type = MTD_NANDFLASH;
        mtd->flags = MTD_CAP_NANDFLASH;
-       mtd->ecctype = MTD_ECC_SW;
        mtd->erase = onenand_erase;
        mtd->point = NULL;
        mtd->unpoint = NULL;