diff options
Diffstat (limited to 'drivers/mtd/nand/nand_base.c')
-rw-r--r-- | drivers/mtd/nand/nand_base.c | 312 |
1 files changed, 253 insertions, 59 deletions
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 8f2958fe2148..b9dc65c7253c 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -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 @@ -335,14 +364,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); @@ -401,6 +434,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 +782,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); @@ -835,6 +870,168 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) } /** + * __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 * @chip: nand chip info structure @@ -1232,6 +1429,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 +1482,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 +2076,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 +2133,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 +2179,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 +2198,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 +2376,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 +2499,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,10 +2771,10 @@ 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 dev_id, maf_idx; int tmp_id, tmp_manf; /* Select the device */ @@ -2624,15 +2813,14 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, 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) @@ -2704,6 +2892,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; @@ -2741,13 +2930,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 +2950,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 +3180,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 +3242,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 +3269,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); |