Merge branch 'omap-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tmlind...
[pandora-kernel.git] / drivers / mtd / nand / nand_base.c
index 8f2958f..4a7b864 100644 (file)
@@ -108,6 +108,35 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
  */
 DEFINE_LED_TRIGGER(nand_led_trigger);
 
+static int check_offs_len(struct mtd_info *mtd,
+                                       loff_t ofs, uint64_t len)
+{
+       struct nand_chip *chip = mtd->priv;
+       int ret = 0;
+
+       /* Start address must align on block boundary */
+       if (ofs & ((1 << chip->phys_erase_shift) - 1)) {
+               DEBUG(MTD_DEBUG_LEVEL0, "%s: Unaligned address\n", __func__);
+               ret = -EINVAL;
+       }
+
+       /* Length must align on block boundary */
+       if (len & ((1 << chip->phys_erase_shift) - 1)) {
+               DEBUG(MTD_DEBUG_LEVEL0, "%s: Length not block aligned\n",
+                                       __func__);
+               ret = -EINVAL;
+       }
+
+       /* Do not allow past end of device */
+       if (ofs + len > mtd->size) {
+               DEBUG(MTD_DEBUG_LEVEL0, "%s: Past end of device\n",
+                                       __func__);
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+
 /**
  * nand_release_device - [GENERIC] release chip
  * @mtd:       MTD device structure
@@ -318,6 +347,9 @@ static int nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
        struct nand_chip *chip = mtd->priv;
        u16 bad;
 
+       if (chip->options & NAND_BB_LAST_PAGE)
+               ofs += mtd->erasesize - mtd->writesize;
+
        page = (int)(ofs >> chip->page_shift) & chip->pagemask;
 
        if (getchip) {
@@ -335,14 +367,18 @@ static int nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
                bad = cpu_to_le16(chip->read_word(mtd));
                if (chip->badblockpos & 0x1)
                        bad >>= 8;
-               if ((bad & 0xFF) != 0xff)
-                       res = 1;
+               else
+                       bad &= 0xFF;
        } else {
                chip->cmdfunc(mtd, NAND_CMD_READOOB, chip->badblockpos, page);
-               if (chip->read_byte(mtd) != 0xff)
-                       res = 1;
+               bad = chip->read_byte(mtd);
        }
 
+       if (likely(chip->badblockbits == 8))
+               res = bad != 0xFF;
+       else
+               res = hweight8(bad) < chip->badblockbits;
+
        if (getchip)
                nand_release_device(mtd);
 
@@ -363,6 +399,9 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
        uint8_t buf[2] = { 0, 0 };
        int block, ret;
 
+       if (chip->options & NAND_BB_LAST_PAGE)
+               ofs += mtd->erasesize - mtd->writesize;
+
        /* Get block number */
        block = (int)(ofs >> chip->bbt_erase_shift);
        if (chip->bbt)
@@ -401,6 +440,11 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
 static int nand_check_wp(struct mtd_info *mtd)
 {
        struct nand_chip *chip = mtd->priv;
+
+       /* broken xD cards report WP despite being writable */
+       if (chip->options & NAND_BROKEN_XD)
+               return 0;
+
        /* Check the WP bit */
        chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
        return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
@@ -744,9 +788,6 @@ nand_get_device(struct nand_chip *chip, struct mtd_info *mtd, int new_state)
                        chip->state = FL_PM_SUSPENDED;
                        spin_unlock(lock);
                        return 0;
-               } else {
-                       spin_unlock(lock);
-                       return -EAGAIN;
                }
        }
        set_current_state(TASK_UNINTERRUPTIBLE);
@@ -834,6 +875,168 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
        return status;
 }
 
+/**
+ * __nand_unlock - [REPLACABLE] unlocks specified locked blockes
+ *
+ * @param mtd - mtd info
+ * @param ofs - offset to start unlock from
+ * @param len - length to unlock
+ * @invert -  when = 0, unlock the range of blocks within the lower and
+ *                      upper boundary address
+ *            whne = 1, unlock the range of blocks outside the boundaries
+ *                      of the lower and upper boundary address
+ *
+ * @return - unlock status
+ */
+static int __nand_unlock(struct mtd_info *mtd, loff_t ofs,
+                                       uint64_t len, int invert)
+{
+       int ret = 0;
+       int status, page;
+       struct nand_chip *chip = mtd->priv;
+
+       /* Submit address of first page to unlock */
+       page = ofs >> chip->page_shift;
+       chip->cmdfunc(mtd, NAND_CMD_UNLOCK1, -1, page & chip->pagemask);
+
+       /* Submit address of last page to unlock */
+       page = (ofs + len) >> chip->page_shift;
+       chip->cmdfunc(mtd, NAND_CMD_UNLOCK2, -1,
+                               (page | invert) & chip->pagemask);
+
+       /* Call wait ready function */
+       status = chip->waitfunc(mtd, chip);
+       udelay(1000);
+       /* See if device thinks it succeeded */
+       if (status & 0x01) {
+               DEBUG(MTD_DEBUG_LEVEL0, "%s: Error status = 0x%08x\n",
+                                       __func__, status);
+               ret = -EIO;
+       }
+
+       return ret;
+}
+
+/**
+ * nand_unlock - [REPLACABLE] unlocks specified locked blockes
+ *
+ * @param mtd - mtd info
+ * @param ofs - offset to start unlock from
+ * @param len - length to unlock
+ *
+ * @return - unlock status
+ */
+int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
+{
+       int ret = 0;
+       int chipnr;
+       struct nand_chip *chip = mtd->priv;
+
+       DEBUG(MTD_DEBUG_LEVEL3, "%s: start = 0x%012llx, len = %llu\n",
+                       __func__, (unsigned long long)ofs, len);
+
+       if (check_offs_len(mtd, ofs, len))
+               ret = -EINVAL;
+
+       /* Align to last block address if size addresses end of the device */
+       if (ofs + len == mtd->size)
+               len -= mtd->erasesize;
+
+       nand_get_device(chip, mtd, FL_UNLOCKING);
+
+       /* Shift to get chip number */
+       chipnr = ofs >> chip->chip_shift;
+
+       chip->select_chip(mtd, chipnr);
+
+       /* Check, if it is write protected */
+       if (nand_check_wp(mtd)) {
+               DEBUG(MTD_DEBUG_LEVEL0, "%s: Device is write protected!!!\n",
+                                       __func__);
+               ret = -EIO;
+               goto out;
+       }
+
+       ret = __nand_unlock(mtd, ofs, len, 0);
+
+out:
+       /* de-select the NAND device */
+       chip->select_chip(mtd, -1);
+
+       nand_release_device(mtd);
+
+       return ret;
+}
+
+/**
+ * nand_lock - [REPLACABLE] locks all blockes present in the device
+ *
+ * @param mtd - mtd info
+ * @param ofs - offset to start unlock from
+ * @param len - length to unlock
+ *
+ * @return - lock status
+ *
+ * This feature is not support in many NAND parts. 'Micron' NAND parts
+ * do have this feature, but it allows only to lock all blocks not for
+ * specified range for block.
+ *
+ * Implementing 'lock' feature by making use of 'unlock', for now.
+ */
+int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
+{
+       int ret = 0;
+       int chipnr, status, page;
+       struct nand_chip *chip = mtd->priv;
+
+       DEBUG(MTD_DEBUG_LEVEL3, "%s: start = 0x%012llx, len = %llu\n",
+                       __func__, (unsigned long long)ofs, len);
+
+       if (check_offs_len(mtd, ofs, len))
+               ret = -EINVAL;
+
+       nand_get_device(chip, mtd, FL_LOCKING);
+
+       /* Shift to get chip number */
+       chipnr = ofs >> chip->chip_shift;
+
+       chip->select_chip(mtd, chipnr);
+
+       /* Check, if it is write protected */
+       if (nand_check_wp(mtd)) {
+               DEBUG(MTD_DEBUG_LEVEL0, "%s: Device is write protected!!!\n",
+                                       __func__);
+               status = MTD_ERASE_FAILED;
+               ret = -EIO;
+               goto out;
+       }
+
+       /* Submit address of first page to lock */
+       page = ofs >> chip->page_shift;
+       chip->cmdfunc(mtd, NAND_CMD_LOCK, -1, page & chip->pagemask);
+
+       /* Call wait ready function */
+       status = chip->waitfunc(mtd, chip);
+       udelay(1000);
+       /* See if device thinks it succeeded */
+       if (status & 0x01) {
+               DEBUG(MTD_DEBUG_LEVEL0, "%s: Error status = 0x%08x\n",
+                                       __func__, status);
+               ret = -EIO;
+               goto out;
+       }
+
+       ret = __nand_unlock(mtd, ofs, len, 0x1);
+
+out:
+       /* de-select the NAND device */
+       chip->select_chip(mtd, -1);
+
+       nand_release_device(mtd);
+
+       return ret;
+}
+
 /**
  * nand_read_page_raw - [Intern] read raw page data without ecc
  * @mtd:       mtd info structure
@@ -1232,6 +1435,9 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
        int ret = 0;
        uint32_t readlen = ops->len;
        uint32_t oobreadlen = ops->ooblen;
+       uint32_t max_oobsize = ops->mode == MTD_OOB_AUTO ?
+               mtd->oobavail : mtd->oobsize;
+
        uint8_t *bufpoi, *oob, *buf;
 
        stats = mtd->ecc_stats;
@@ -1282,18 +1488,14 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
                        buf += bytes;
 
                        if (unlikely(oob)) {
-                               /* Raw mode does data:oob:data:oob */
-                               if (ops->mode != MTD_OOB_RAW) {
-                                       int toread = min(oobreadlen,
-                                               chip->ecc.layout->oobavail);
-                                       if (toread) {
-                                               oob = nand_transfer_oob(chip,
-                                                       oob, ops, toread);
-                                               oobreadlen -= toread;
-                                       }
-                               } else
-                                       buf = nand_transfer_oob(chip,
-                                               buf, ops, mtd->oobsize);
+
+                               int toread = min(oobreadlen, max_oobsize);
+
+                               if (toread) {
+                                       oob = nand_transfer_oob(chip,
+                                               oob, ops, toread);
+                                       oobreadlen -= toread;
+                               }
                        }
 
                        if (!(chip->options & NAND_NO_READRDY)) {
@@ -1880,11 +2082,9 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
  * @oob:       oob data buffer
  * @ops:       oob ops structure
  */
-static uint8_t *nand_fill_oob(struct nand_chip *chip, uint8_t *oob,
-                                 struct mtd_oob_ops *ops)
+static uint8_t *nand_fill_oob(struct nand_chip *chip, uint8_t *oob, size_t len,
+                                               struct mtd_oob_ops *ops)
 {
-       size_t len = ops->ooblen;
-
        switch(ops->mode) {
 
        case MTD_OOB_PLACE:
@@ -1939,6 +2139,11 @@ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to,
        int chipnr, realpage, page, blockmask, column;
        struct nand_chip *chip = mtd->priv;
        uint32_t writelen = ops->len;
+
+       uint32_t oobwritelen = ops->ooblen;
+       uint32_t oobmaxlen = ops->mode == MTD_OOB_AUTO ?
+                               mtd->oobavail : mtd->oobsize;
+
        uint8_t *oob = ops->oobbuf;
        uint8_t *buf = ops->datbuf;
        int ret, subpage;
@@ -1980,6 +2185,10 @@ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to,
        if (likely(!oob))
                memset(chip->oob_poi, 0xff, mtd->oobsize);
 
+       /* Don't allow multipage oob writes with offset */
+       if (ops->ooboffs && (ops->ooboffs + ops->ooblen > oobmaxlen))
+               return -EINVAL;
+
        while(1) {
                int bytes = mtd->writesize;
                int cached = writelen > bytes && page != blockmask;
@@ -1995,8 +2204,11 @@ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to,
                        wbuf = chip->buffers->databuf;
                }
 
-               if (unlikely(oob))
-                       oob = nand_fill_oob(chip, oob, ops);
+               if (unlikely(oob)) {
+                       size_t len = min(oobwritelen, oobmaxlen);
+                       oob = nand_fill_oob(chip, oob, len, ops);
+                       oobwritelen -= len;
+               }
 
                ret = chip->write_page(mtd, chip, wbuf, page, cached,
                                       (ops->mode == MTD_OOB_RAW));
@@ -2170,7 +2382,7 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
                chip->pagebuf = -1;
 
        memset(chip->oob_poi, 0xff, mtd->oobsize);
-       nand_fill_oob(chip, ops->oobbuf, ops);
+       nand_fill_oob(chip, ops->oobbuf, ops->ooblen, ops);
        status = chip->ecc.write_oob(mtd, chip, page & chip->pagemask);
        memset(chip->oob_poi, 0xff, mtd->oobsize);
 
@@ -2293,25 +2505,8 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
                                __func__, (unsigned long long)instr->addr,
                                (unsigned long long)instr->len);
 
-       /* Start address must align on block boundary */
-       if (instr->addr & ((1 << chip->phys_erase_shift) - 1)) {
-               DEBUG(MTD_DEBUG_LEVEL0, "%s: Unaligned address\n", __func__);
+       if (check_offs_len(mtd, instr->addr, instr->len))
                return -EINVAL;
-       }
-
-       /* Length must align on block boundary */
-       if (instr->len & ((1 << chip->phys_erase_shift) - 1)) {
-               DEBUG(MTD_DEBUG_LEVEL0, "%s: Length not block aligned\n",
-                                       __func__);
-               return -EINVAL;
-       }
-
-       /* Do not allow erase past end of device */
-       if ((instr->len + instr->addr) > mtd->size) {
-               DEBUG(MTD_DEBUG_LEVEL0, "%s: Erase past end of device\n",
-                                       __func__);
-               return -EINVAL;
-       }
 
        instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
 
@@ -2582,11 +2777,11 @@ static void nand_set_defaults(struct nand_chip *chip, int busw)
  */
 static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
                                                  struct nand_chip *chip,
-                                                 int busw, int *maf_id)
+                                                 int busw, int *maf_id,
+                                                 struct nand_flash_dev *type)
 {
-       struct nand_flash_dev *type = NULL;
        int i, dev_id, maf_idx;
-       int tmp_id, tmp_manf;
+       u8 id_data[8];
 
        /* Select the device */
        chip->select_chip(mtd, 0);
@@ -2612,27 +2807,26 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
 
        chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
 
-       /* Read manufacturer and device IDs */
+       /* Read entire ID string */
 
-       tmp_manf = chip->read_byte(mtd);
-       tmp_id = chip->read_byte(mtd);
+       for (i = 0; i < 8; i++)
+               id_data[i] = chip->read_byte(mtd);
 
-       if (tmp_manf != *maf_id || tmp_id != dev_id) {
+       if (id_data[0] != *maf_id || id_data[1] != dev_id) {
                printk(KERN_INFO "%s: second ID read did not match "
                       "%02x,%02x against %02x,%02x\n", __func__,
-                      *maf_id, dev_id, tmp_manf, tmp_id);
+                      *maf_id, dev_id, id_data[0], id_data[1]);
                return ERR_PTR(-ENODEV);
        }
 
-       /* Lookup the flash id */
-       for (i = 0; nand_flash_ids[i].name != NULL; i++) {
-               if (dev_id == nand_flash_ids[i].id) {
-                       type =  &nand_flash_ids[i];
-                       break;
-               }
-       }
-
        if (!type)
+               type = nand_flash_ids;
+
+       for (; type->name != NULL; type++)
+               if (dev_id == type->id)
+                        break;
+
+       if (!type->name)
                return ERR_PTR(-ENODEV);
 
        if (!mtd->name)
@@ -2644,21 +2838,45 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
        if (!type->pagesize) {
                int extid;
                /* The 3rd id byte holds MLC / multichip data */
-               chip->cellinfo = chip->read_byte(mtd);
+               chip->cellinfo = id_data[2];
                /* The 4th id byte is the important one */
-               extid = chip->read_byte(mtd);
-               /* Calc pagesize */
-               mtd->writesize = 1024 << (extid & 0x3);
-               extid >>= 2;
-               /* Calc oobsize */
-               mtd->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9);
-               extid >>= 2;
-               /* Calc blocksize. Blocksize is multiples of 64KiB */
-               mtd->erasesize = (64 * 1024) << (extid & 0x03);
-               extid >>= 2;
-               /* Get buswidth information */
-               busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
+               extid = id_data[3];
 
+               /*
+                * Field definitions are in the following datasheets:
+                * Old style (4,5 byte ID): Samsung K9GAG08U0M (p.32)
+                * New style   (6 byte ID): Samsung K9GAG08U0D (p.40)
+                *
+                * Check for wraparound + Samsung ID + nonzero 6th byte
+                * to decide what to do.
+                */
+               if (id_data[0] == id_data[6] && id_data[1] == id_data[7] &&
+                               id_data[0] == NAND_MFR_SAMSUNG &&
+                               id_data[5] != 0x00) {
+                       /* Calc pagesize */
+                       mtd->writesize = 2048 << (extid & 0x03);
+                       extid >>= 2;
+                       /* Calc oobsize */
+                       mtd->oobsize = (extid & 0x03) == 0x01 ? 128 : 218;
+                       extid >>= 2;
+                       /* Calc blocksize */
+                       mtd->erasesize = (128 * 1024) <<
+                               (((extid >> 1) & 0x04) | (extid & 0x03));
+                       busw = 0;
+               } else {
+                       /* Calc pagesize */
+                       mtd->writesize = 1024 << (extid & 0x03);
+                       extid >>= 2;
+                       /* Calc oobsize */
+                       mtd->oobsize = (8 << (extid & 0x01)) *
+                               (mtd->writesize >> 9);
+                       extid >>= 2;
+                       /* Calc blocksize. Blocksize is multiples of 64KiB */
+                       mtd->erasesize = (64 * 1024) << (extid & 0x03);
+                       extid >>= 2;
+                       /* Get buswidth information */
+                       busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
+               }
        } else {
                /*
                 * Old devices have chip data hardcoded in the device id table
@@ -2704,6 +2922,7 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
        /* Set the bad block position */
        chip->badblockpos = mtd->writesize > 512 ?
                NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS;
+       chip->badblockbits = 8;
 
        /* Get chip options, preserve non chip based options */
        chip->options &= ~NAND_CHIPOPTIONS_MSK;
@@ -2720,6 +2939,15 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
        if (*maf_id != NAND_MFR_SAMSUNG && !type->pagesize)
                chip->options &= ~NAND_SAMSUNG_LP_OPTIONS;
 
+       /*
+        * Bad block marker is stored in the last page of each block
+        * on Samsung and Hynix MLC devices
+        */
+       if ((chip->cellinfo & NAND_CI_CELLTYPE_MSK) &&
+                       (*maf_id == NAND_MFR_SAMSUNG ||
+                        *maf_id == NAND_MFR_HYNIX))
+               chip->options |= NAND_BB_LAST_PAGE;
+
        /* Check for AND chips with 4 page planes */
        if (chip->options & NAND_4PAGE_ARRAY)
                chip->erase_cmd = multi_erase_cmd;
@@ -2741,13 +2969,15 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
  * nand_scan_ident - [NAND Interface] Scan for the NAND device
  * @mtd:            MTD device structure
  * @maxchips:       Number of chips to scan for
+ * @table:          Alternative NAND ID table
  *
  * This is the first phase of the normal nand_scan() function. It
  * reads the flash ID and sets up MTD fields accordingly.
  *
  * The mtd->owner field must be set to the module of the caller.
  */
-int nand_scan_ident(struct mtd_info *mtd, int maxchips)
+int nand_scan_ident(struct mtd_info *mtd, int maxchips,
+                   struct nand_flash_dev *table)
 {
        int i, busw, nand_maf_id;
        struct nand_chip *chip = mtd->priv;
@@ -2759,7 +2989,7 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips)
        nand_set_defaults(chip, busw);
 
        /* Read the flash type */
-       type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id);
+       type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id, table);
 
        if (IS_ERR(type)) {
                if (!(chip->options & NAND_SCAN_SILENT_NODEV))
@@ -2989,7 +3219,8 @@ int nand_scan_tail(struct mtd_info *mtd)
 
        /* Fill in remaining MTD driver data */
        mtd->type = MTD_NANDFLASH;
-       mtd->flags = MTD_CAP_NANDFLASH;
+       mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :
+                                               MTD_CAP_NANDFLASH;
        mtd->erase = nand_erase;
        mtd->point = NULL;
        mtd->unpoint = NULL;
@@ -3050,7 +3281,7 @@ int nand_scan(struct mtd_info *mtd, int maxchips)
                BUG();
        }
 
-       ret = nand_scan_ident(mtd, maxchips);
+       ret = nand_scan_ident(mtd, maxchips, NULL);
        if (!ret)
                ret = nand_scan_tail(mtd);
        return ret;
@@ -3077,6 +3308,8 @@ void nand_release(struct mtd_info *mtd)
                kfree(chip->buffers);
 }
 
+EXPORT_SYMBOL_GPL(nand_lock);
+EXPORT_SYMBOL_GPL(nand_unlock);
 EXPORT_SYMBOL_GPL(nand_scan);
 EXPORT_SYMBOL_GPL(nand_scan_ident);
 EXPORT_SYMBOL_GPL(nand_scan_tail);