]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/mtd/onenand/onenand_base.c
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[linux-2.6-omap-h63xx.git] / drivers / mtd / onenand / onenand_base.c
index 8006d51b7d27332433f37ac12b97cd77ddc9bbb1..7d194cfdb873ce22a82e47572d7ddcbe40ef278a 100644 (file)
@@ -4,6 +4,11 @@
  *  Copyright (C) 2005-2007 Samsung Electronics
  *  Kyungmin Park <kyungmin.park@samsung.com>
  *
+ *  Credits:
+ *     Adrian Hunter <ext-adrian.hunter@nokia.com>:
+ *     auto-placement support, read-while load support, various fixes
+ *     Copyright (C) Nokia Corporation, 2007
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
@@ -201,6 +206,15 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le
        default:
                block = (int) (addr >> this->erase_shift);
                page = (int) (addr >> this->page_shift);
+
+               if (ONENAND_IS_2PLANE(this)) {
+                       /* Make the even block number */
+                       block &= ~1;
+                       /* Is it the odd plane? */
+                       if (addr & this->writesize)
+                               block++;
+                       page >>= 1;
+               }
                page &= this->page_mask;
                break;
        }
@@ -211,8 +225,12 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le
                value = onenand_bufferram_address(this, block);
                this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2);
 
-               /* Switch to the next data buffer */
-               ONENAND_SET_NEXT_BUFFERRAM(this);
+               if (ONENAND_IS_2PLANE(this))
+                       /* It is always BufferRAM0 */
+                       ONENAND_SET_BUFFERRAM0(this);
+               else
+                       /* Switch to the next data buffer */
+                       ONENAND_SET_NEXT_BUFFERRAM(this);
 
                return 0;
        }
@@ -242,6 +260,8 @@ static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t le
                        break;
 
                default:
+                       if (ONENAND_IS_2PLANE(this) && cmd == ONENAND_CMD_PROG)
+                               cmd = ONENAND_CMD_2X_PROG;
                        dataram = ONENAND_CURRENT_BUFFERRAM(this);
                        break;
                }
@@ -440,8 +460,9 @@ static inline int onenand_bufferram_offset(struct mtd_info *mtd, int area)
        struct onenand_chip *this = mtd->priv;
 
        if (ONENAND_CURRENT_BUFFERRAM(this)) {
+               /* Note: the 'this->writesize' is a real page size */
                if (area == ONENAND_DATARAM)
-                       return mtd->writesize;
+                       return this->writesize;
                if (area == ONENAND_SPARERAM)
                        return mtd->oobsize;
        }
@@ -566,6 +587,30 @@ static int onenand_write_bufferram(struct mtd_info *mtd, int area,
        return 0;
 }
 
+/**
+ * onenand_get_2x_blockpage - [GENERIC] Get blockpage at 2x program mode
+ * @param mtd          MTD data structure
+ * @param addr         address to check
+ * @return             blockpage address
+ *
+ * Get blockpage address at 2x program mode
+ */
+static int onenand_get_2x_blockpage(struct mtd_info *mtd, loff_t addr)
+{
+       struct onenand_chip *this = mtd->priv;
+       int blockpage, block, page;
+
+       /* Calculate the even block number */
+       block = (int) (addr >> this->erase_shift) & ~1;
+       /* Is it the odd plane? */
+       if (addr & this->writesize)
+               block++;
+       page = (int) (addr >> (this->page_shift + 1)) & this->page_mask;
+       blockpage = (block << 7) | page;
+
+       return blockpage;
+}
+
 /**
  * onenand_check_bufferram - [GENERIC] Check BufferRAM information
  * @param mtd          MTD data structure
@@ -580,7 +625,10 @@ static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr)
        int blockpage, found = 0;
        unsigned int i;
 
-       blockpage = (int) (addr >> this->page_shift);
+       if (ONENAND_IS_2PLANE(this))
+               blockpage = onenand_get_2x_blockpage(mtd, addr);
+       else
+               blockpage = (int) (addr >> this->page_shift);
 
        /* Is there valid data? */
        i = ONENAND_CURRENT_BUFFERRAM(this);
@@ -620,7 +668,10 @@ static void onenand_update_bufferram(struct mtd_info *mtd, loff_t addr,
        int blockpage;
        unsigned int i;
 
-       blockpage = (int) (addr >> this->page_shift);
+       if (ONENAND_IS_2PLANE(this))
+               blockpage = onenand_get_2x_blockpage(mtd, addr);
+       else
+               blockpage = (int) (addr >> this->page_shift);
 
        /* Invalidate another BufferRAM */
        i = ONENAND_NEXT_BUFFERRAM(this);
@@ -729,6 +780,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
        int read = 0, column;
        int thislen;
        int ret = 0, boundary = 0;
+       int writesize = this->writesize;
 
        DEBUG(MTD_DEBUG_LEVEL3, "onenand_read: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
 
@@ -749,22 +801,22 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
        /* Do first load to bufferRAM */
        if (read < len) {
                if (!onenand_check_bufferram(mtd, from)) {
-                       this->command(mtd, ONENAND_CMD_READ, from, mtd->writesize);
+                       this->command(mtd, ONENAND_CMD_READ, from, writesize);
                        ret = this->wait(mtd, FL_READING);
                        onenand_update_bufferram(mtd, from, !ret);
                }
        }
 
-       thislen = min_t(int, mtd->writesize, len - read);
-       column = from & (mtd->writesize - 1);
-       if (column + thislen > mtd->writesize)
-               thislen = mtd->writesize - column;
+       thislen = min_t(int, writesize, len - read);
+       column = from & (writesize - 1);
+       if (column + thislen > writesize)
+               thislen = writesize - column;
 
        while (!ret) {
                /* If there is more to load then start next load */
                from += thislen;
                if (read + thislen < len) {
-                       this->command(mtd, ONENAND_CMD_READ, from, mtd->writesize);
+                       this->command(mtd, ONENAND_CMD_READ, from, writesize);
                        /*
                         * Chip boundary handling in DDP
                         * Now we issued chip 1 read and pointed chip 1
@@ -789,7 +841,7 @@ static int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
                        this->write_word(ONENAND_DDP_CHIP1, this->base + ONENAND_REG_START_ADDRESS2);
                ONENAND_SET_NEXT_BUFFERRAM(this);
                buf += thislen;
-               thislen = min_t(int, mtd->writesize, len - read);
+               thislen = min_t(int, writesize, len - read);
                column = 0;
                cond_resched();
                /* Now wait for load */
@@ -831,9 +883,11 @@ static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, int col
        int readcol = column;
        int readend = column + thislen;
        int lastgap = 0;
-       uint8_t *oob_buf = this->page_buf + mtd->writesize;
+       unsigned int i;
+       uint8_t *oob_buf = this->oob_buf;
 
-       for (free = this->ecclayout->oobfree; free->length; ++free) {
+       free = this->ecclayout->oobfree;
+       for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
                if (readcol >= lastgap)
                        readcol += free->offset - lastgap;
                if (readend >= lastgap)
@@ -841,7 +895,8 @@ static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, int col
                lastgap = free->offset + free->length;
        }
        this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize);
-       for (free = this->ecclayout->oobfree; free->length; ++free) {
+       free = this->ecclayout->oobfree;
+       for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
                int free_end = free->offset + free->length;
                if (free->offset < readend && free_end > readcol) {
                        int st = max_t(int,free->offset,readcol);
@@ -849,7 +904,8 @@ static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, int col
                        int n = ed - st;
                        memcpy(buf, oob_buf + st, n);
                        buf += n;
-               }
+               } else if (column == 0)
+                       break;
        }
        return 0;
 }
@@ -947,9 +1003,9 @@ static int onenand_do_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
 
 /**
  * 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
+ * @param mtd:         MTD device structure
+ * @param from:                offset to read from
+ * @param ops:         oob operation description structure
  */
 static int onenand_read_oob(struct mtd_info *mtd, loff_t from,
                            struct mtd_oob_ops *ops)
@@ -1017,7 +1073,7 @@ static int onenand_bbt_wait(struct mtd_info *mtd, int state)
  * 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
+ * @param ops          oob operation description structure
  *
  * OneNAND read out-of-band data from the spare area for bbt scan
  */
@@ -1070,7 +1126,7 @@ int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from,
                /* Read more? */
                if (read < len) {
                        /* Update Page size */
-                       from += mtd->writesize;
+                       from += this->writesize;
                        column = 0;
                }
        }
@@ -1126,12 +1182,12 @@ static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr,
        int thislen, column;
 
        while (len != 0) {
-               thislen = min_t(int, mtd->writesize, len);
-               column = addr & (mtd->writesize - 1);
-               if (column + thislen > mtd->writesize)
-                       thislen = mtd->writesize - column;
+               thislen = min_t(int, this->writesize, len);
+               column = addr & (this->writesize - 1);
+               if (column + thislen > this->writesize)
+                       thislen = this->writesize - column;
 
-               this->command(mtd, ONENAND_CMD_READ, addr, mtd->writesize);
+               this->command(mtd, ONENAND_CMD_READ, addr, this->writesize);
 
                onenand_update_bufferram(mtd, addr, 0);
 
@@ -1227,6 +1283,10 @@ static int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
 
                /* 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_write: write filaed %d\n", ret);
@@ -1274,15 +1334,18 @@ static int onenand_fill_auto_oob(struct mtd_info *mtd, u_char *oob_buf,
        int writecol = column;
        int writeend = column + thislen;
        int lastgap = 0;
+       unsigned int i;
 
-       for (free = this->ecclayout->oobfree; free->length; ++free) {
+       free = this->ecclayout->oobfree;
+       for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
                if (writecol >= lastgap)
                        writecol += free->offset - lastgap;
                if (writeend >= lastgap)
                        writeend += free->offset - lastgap;
                lastgap = free->offset + free->length;
        }
-       for (free = this->ecclayout->oobfree; free->length; ++free) {
+       free = this->ecclayout->oobfree;
+       for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
                int free_end = free->offset + free->length;
                if (free->offset < writeend && free_end > writecol) {
                        int st = max_t(int,free->offset,writecol);
@@ -1290,7 +1353,8 @@ static int onenand_fill_auto_oob(struct mtd_info *mtd, u_char *oob_buf,
                        int n = ed - st;
                        memcpy(oob_buf + st, buf, n);
                        buf += n;
-               }
+               } else if (column == 0)
+                       break;
        }
        return 0;
 }
@@ -1349,7 +1413,7 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
        /* Grab the lock and see if the device is available */
        onenand_get_device(mtd, FL_WRITING);
 
-       oobbuf = this->page_buf + mtd->writesize;
+       oobbuf = this->oob_buf;
 
        /* Loop until all data write */
        while (written < len) {
@@ -1371,6 +1435,10 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
                this->command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize);
 
                onenand_update_bufferram(mtd, to, 0);
+               if (ONENAND_IS_2PLANE(this)) {
+                       ONENAND_SET_BUFFERRAM1(this);
+                       onenand_update_bufferram(mtd, to + this->writesize, 0);
+               }
 
                ret = this->wait(mtd, FL_WRITING);
                if (ret) {
@@ -1403,9 +1471,9 @@ static int onenand_do_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
 
 /**
  * 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
+ * @param mtd:         MTD device structure
+ * @param to:          offset to write
+ * @param ops:         oob operation description structure
  */
 static int onenand_write_oob(struct mtd_info *mtd, loff_t to,
                             struct mtd_oob_ops *ops)
@@ -1619,6 +1687,7 @@ static int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs)
  * @param mtd          MTD device structure
  * @param ofs          offset relative to mtd start
  * @param len          number of bytes to lock or unlock
+ * @param cmd          lock or unlock command
  *
  * Lock or unlock one or more blocks
  */
@@ -2093,6 +2162,7 @@ static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
  *
  * Check and set OneNAND features
  * - lock scheme
+ * - two plane
  */
 static void onenand_check_features(struct mtd_info *mtd)
 {
@@ -2104,26 +2174,43 @@ static void onenand_check_features(struct mtd_info *mtd)
        process = this->version_id >> ONENAND_VERSION_PROCESS_SHIFT;
 
        /* Lock scheme */
-       if (density >= ONENAND_DEVICE_DENSITY_1Gb) {
+       switch (density) {
+       case ONENAND_DEVICE_DENSITY_4Gb:
+               this->options |= ONENAND_HAS_2PLANE;
+
+       case ONENAND_DEVICE_DENSITY_2Gb:
+               /* 2Gb DDP don't have 2 plane */
+               if (!ONENAND_IS_DDP(this))
+                       this->options |= ONENAND_HAS_2PLANE;
+               this->options |= ONENAND_HAS_UNLOCK_ALL;
+
+       case ONENAND_DEVICE_DENSITY_1Gb:
                /* A-Die has all block unlock */
-               if (process) {
-                       printk(KERN_DEBUG "Chip support all block unlock\n");
+               if (process)
                        this->options |= ONENAND_HAS_UNLOCK_ALL;
-               }
-       } else {
-               /* Some OneNAND has continues lock scheme */
-               if (!process) {
-                       printk(KERN_DEBUG "Lock scheme is Continues Lock\n");
+               break;
+
+       default:
+               /* Some OneNAND has continuous lock scheme */
+               if (!process)
                        this->options |= ONENAND_HAS_CONT_LOCK;
-               }
+               break;
        }
+
+       if (this->options & ONENAND_HAS_CONT_LOCK)
+               printk(KERN_DEBUG "Lock scheme is Continuous Lock\n");
+       if (this->options & ONENAND_HAS_UNLOCK_ALL)
+               printk(KERN_DEBUG "Chip support all block unlock\n");
+       if (this->options & ONENAND_HAS_2PLANE)
+               printk(KERN_DEBUG "Chip has 2 plane\n");
 }
 
 /**
- * onenand_print_device_info - Print device ID
+ * onenand_print_device_info - Print device & version ID
  * @param device        device ID
+ * @param version      version ID
  *
- * Print device ID
+ * Print device & version ID
  */
 static void onenand_print_device_info(int device, int version)
 {
@@ -2177,7 +2264,7 @@ static int onenand_check_maf(int manuf)
  * @param mtd          MTD device structure
  *
  * OneNAND detection method:
- *   Compare the the values from command with ones from register
+ *   Compare the values from command with ones from register
  */
 static int onenand_probe(struct mtd_info *mtd)
 {
@@ -2242,6 +2329,8 @@ static int onenand_probe(struct mtd_info *mtd)
        this->erase_shift = ffs(mtd->erasesize) - 1;
        this->page_shift = ffs(mtd->writesize) - 1;
        this->page_mask = (1 << (this->erase_shift - this->page_shift)) - 1;
+       /* It's real page size */
+       this->writesize = mtd->writesize;
 
        /* REVIST: Multichip handling */
 
@@ -2250,6 +2339,17 @@ static int onenand_probe(struct mtd_info *mtd)
        /* Check OneNAND features */
        onenand_check_features(mtd);
 
+       /*
+        * We emulate the 4KiB page and 256KiB erase block size
+        * But oobsize is still 64 bytes.
+        * It is only valid if you turn on 2X program support,
+        * Otherwise it will be ignored by compiler.
+        */
+       if (ONENAND_IS_2PLANE(this)) {
+               mtd->writesize <<= 1;
+               mtd->erasesize <<= 1;
+       }
+
        return 0;
 }
 
@@ -2323,15 +2423,25 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
 
        /* Allocate buffers, if necessary */
        if (!this->page_buf) {
-               size_t len;
-               len = mtd->writesize + mtd->oobsize;
-               this->page_buf = kmalloc(len, GFP_KERNEL);
+               this->page_buf = kzalloc(mtd->writesize, GFP_KERNEL);
                if (!this->page_buf) {
                        printk(KERN_ERR "onenand_scan(): Can't allocate page_buf\n");
                        return -ENOMEM;
                }
                this->options |= ONENAND_PAGEBUF_ALLOC;
        }
+       if (!this->oob_buf) {
+               this->oob_buf = kzalloc(mtd->oobsize, GFP_KERNEL);
+               if (!this->oob_buf) {
+                       printk(KERN_ERR "onenand_scan(): Can't allocate oob_buf\n");
+                       if (this->options & ONENAND_PAGEBUF_ALLOC) {
+                               this->options &= ~ONENAND_PAGEBUF_ALLOC;
+                               kfree(this->page_buf);
+                       }
+                       return -ENOMEM;
+               }
+               this->options |= ONENAND_OOBBUF_ALLOC;
+       }
 
        this->state = FL_READY;
        init_waitqueue_head(&this->wq);
@@ -2367,7 +2477,8 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
         * the out of band area
         */
        this->ecclayout->oobavail = 0;
-       for (i = 0; this->ecclayout->oobfree[i].length; i++)
+       for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES &&
+           this->ecclayout->oobfree[i].length; i++)
                this->ecclayout->oobavail +=
                        this->ecclayout->oobfree[i].length;
        mtd->oobavail = this->ecclayout->oobavail;
@@ -2428,9 +2539,11 @@ void onenand_release(struct mtd_info *mtd)
                kfree(bbm->bbt);
                kfree(this->bbm);
        }
-       /* Buffer allocated by onenand_scan */
+       /* Buffers allocated by onenand_scan */
        if (this->options & ONENAND_PAGEBUF_ALLOC)
                kfree(this->page_buf);
+       if (this->options & ONENAND_OOBBUF_ALLOC)
+               kfree(this->oob_buf);
 }
 
 EXPORT_SYMBOL_GPL(onenand_scan);