789 lines
20 KiB
C
789 lines
20 KiB
C
#define pr_fmt(fmt) "nand: " fmt
|
|
|
|
#include <common.h>
|
|
#include <errno.h>
|
|
#include <clock.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/nand.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <asm/byteorder.h>
|
|
#include <io.h>
|
|
#include <malloc.h>
|
|
#include <module.h>
|
|
|
|
#include "nand.h"
|
|
|
|
static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
|
|
struct mtd_oob_ops *ops);
|
|
|
|
/**
|
|
* nand_write_buf - [DEFAULT] write buffer to chip
|
|
* @mtd: MTD device structure
|
|
* @buf: data buffer
|
|
* @len: number of bytes to write
|
|
*
|
|
* Default write function for 8bit buswith
|
|
*/
|
|
void nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
|
{
|
|
int i;
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
for (i = 0; i < len; i++)
|
|
writeb(buf[i], chip->IO_ADDR_W);
|
|
}
|
|
|
|
/**
|
|
* nand_write_buf16 - [DEFAULT] write buffer to chip
|
|
* @mtd: MTD device structure
|
|
* @buf: data buffer
|
|
* @len: number of bytes to write
|
|
*
|
|
* Default write function for 16bit buswith
|
|
*/
|
|
void nand_write_buf16(struct mtd_info *mtd, const uint8_t *buf, int len)
|
|
{
|
|
int i;
|
|
struct nand_chip *chip = mtd->priv;
|
|
u16 *p = (u16 *) buf;
|
|
len >>= 1;
|
|
|
|
for (i = 0; i < len; i++)
|
|
writew(p[i], chip->IO_ADDR_W);
|
|
|
|
}
|
|
|
|
/**
|
|
* nand_default_block_markbad - [DEFAULT] mark a block bad
|
|
* @mtd: MTD device structure
|
|
* @ofs: offset from device start
|
|
*
|
|
* This is the default implementation, which can be overridden by a hardware
|
|
* specific driver. We try operations in the following order, according to our
|
|
* bbt_options (NAND_BBT_NO_OOB_BBM and NAND_BBT_USE_FLASH):
|
|
* (1) erase the affected block, to allow OOB marker to be written cleanly
|
|
* (2) update in-memory BBT
|
|
* (3) write bad block marker to OOB area of affected block
|
|
* (4) update flash-based BBT
|
|
* Note that we retain the first error encountered in (3) or (4), finish the
|
|
* procedures, and dump the error in the end.
|
|
*/
|
|
int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
uint8_t buf[2] = { 0, 0 };
|
|
int block, res, ret = 0, i = 0;
|
|
int write_oob = 1; /* Currently we do not have NAND_BBT_NO_OOB_BBM */
|
|
|
|
if (write_oob) {
|
|
struct erase_info einfo;
|
|
|
|
/* Attempt erase before marking OOB */
|
|
memset(&einfo, 0, sizeof(einfo));
|
|
einfo.mtd = mtd;
|
|
einfo.addr = ofs;
|
|
einfo.len = 1 << chip->phys_erase_shift;
|
|
nand_erase_nand(mtd, &einfo, 0);
|
|
}
|
|
|
|
/* Get block number */
|
|
block = (int)(ofs >> chip->bbt_erase_shift);
|
|
/* Mark block bad in memory-based BBT */
|
|
if (chip->bbt)
|
|
chip->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1);
|
|
|
|
/* Write bad block marker to OOB */
|
|
if (write_oob) {
|
|
struct mtd_oob_ops ops;
|
|
loff_t wr_ofs = ofs;
|
|
|
|
ops.datbuf = NULL;
|
|
ops.oobbuf = buf;
|
|
ops.ooboffs = chip->badblockpos;
|
|
if (chip->options & NAND_BUSWIDTH_16) {
|
|
ops.ooboffs &= ~0x01;
|
|
ops.len = ops.ooblen = 2;
|
|
} else {
|
|
ops.len = ops.ooblen = 1;
|
|
}
|
|
ops.mode = MTD_OPS_PLACE_OOB;
|
|
|
|
/* Write to first/last page(s) if necessary */
|
|
if (chip->options & NAND_BBT_LASTBLOCK)
|
|
wr_ofs += mtd->erasesize - mtd->writesize;
|
|
do {
|
|
res = nand_do_write_oob(mtd, wr_ofs, &ops);
|
|
if (!ret)
|
|
ret = res;
|
|
|
|
i++;
|
|
wr_ofs += mtd->writesize;
|
|
} while ((chip->options & NAND_BBT_SCAN2NDPAGE) && i < 2);
|
|
}
|
|
|
|
/* Update flash-based bad block table */
|
|
if (chip->options & NAND_BBT_USE_FLASH) {
|
|
res = nand_update_bbt(mtd, ofs);
|
|
if (!ret)
|
|
ret = res;
|
|
}
|
|
|
|
if (!ret)
|
|
mtd->ecc_stats.badblocks++;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* nand_check_wp - [GENERIC] check if the chip is write protected
|
|
* @mtd: MTD device structure
|
|
* Check, if the device is write protected
|
|
*
|
|
* The function expects, that the device is already selected
|
|
*/
|
|
static int nand_check_wp(struct mtd_info *mtd)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
/* Check the WP bit */
|
|
chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
|
|
return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
|
|
}
|
|
|
|
/**
|
|
* nand_write_oob_std - [REPLACABLE] the most common OOB data write function
|
|
* @mtd: mtd info structure
|
|
* @chip: nand chip info structure
|
|
* @page: page number to write
|
|
*/
|
|
int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip,
|
|
int page)
|
|
{
|
|
int status = 0;
|
|
const uint8_t *buf = chip->oob_poi;
|
|
int length = mtd->oobsize;
|
|
|
|
chip->cmdfunc(mtd, NAND_CMD_SEQIN, mtd->writesize, page);
|
|
chip->write_buf(mtd, buf, length);
|
|
/* Send command to program the OOB data */
|
|
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
|
|
|
|
status = chip->waitfunc(mtd, chip);
|
|
|
|
return status & NAND_STATUS_FAIL ? -EIO : 0;
|
|
}
|
|
|
|
/**
|
|
* nand_write_page_raw - [Intern] raw page write function
|
|
* @mtd: mtd info structure
|
|
* @chip: nand chip info structure
|
|
* @buf: data buffer
|
|
*/
|
|
void nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
{
|
|
chip->write_buf(mtd, buf, mtd->writesize);
|
|
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
|
|
}
|
|
|
|
/**
|
|
* nand_write_page - [REPLACEABLE] write one page
|
|
* @mtd: MTD device structure
|
|
* @chip: NAND chip descriptor
|
|
* @buf: the data to write
|
|
* @page: page number to write
|
|
* @cached: cached programming
|
|
* @raw: use _raw version of write_page
|
|
*/
|
|
int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf, int page, int cached, int raw)
|
|
{
|
|
int status;
|
|
|
|
chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
|
|
|
|
if (unlikely(raw))
|
|
chip->ecc.write_page_raw(mtd, chip, buf);
|
|
else
|
|
chip->ecc.write_page(mtd, chip, buf);
|
|
|
|
/*
|
|
* Cached progamming disabled for now, Not sure if its worth the
|
|
* trouble. The speed gain is not very impressive. (2.3->2.6Mib/s)
|
|
*/
|
|
cached = 0;
|
|
|
|
if (!cached || !(chip->options & NAND_CACHEPRG)) {
|
|
|
|
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
|
|
status = chip->waitfunc(mtd, chip);
|
|
/*
|
|
* See if operation failed and additional status checks are
|
|
* available
|
|
*/
|
|
if ((status & NAND_STATUS_FAIL) && (chip->errstat))
|
|
status = chip->errstat(mtd, chip, FL_WRITING, status,
|
|
page);
|
|
|
|
if (status & NAND_STATUS_FAIL) {
|
|
return -EIO;
|
|
}
|
|
} else {
|
|
chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1);
|
|
status = chip->waitfunc(mtd, chip);
|
|
}
|
|
|
|
#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
|
|
/* Send command to read back the data */
|
|
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
|
|
|
|
if (chip->verify_buf(mtd, buf, mtd->writesize))
|
|
return -EIO;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* nand_fill_oob - [Internal] Transfer client buffer to oob
|
|
* @chip: nand chip structure
|
|
* @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)
|
|
{
|
|
size_t len = ops->ooblen;
|
|
|
|
switch(ops->mode) {
|
|
|
|
case MTD_OPS_PLACE_OOB:
|
|
case MTD_OPS_RAW:
|
|
memcpy(chip->oob_poi + ops->ooboffs, oob, len);
|
|
return oob + len;
|
|
|
|
case MTD_OPS_AUTO_OOB: {
|
|
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
|
|
|
|
/**
|
|
* nand_do_write_ops - [Internal] NAND write with ECC
|
|
* @mtd: MTD device structure
|
|
* @to: offset to write to
|
|
* @ops: oob operations description structure
|
|
*
|
|
* NAND write with ECC
|
|
*/
|
|
int nand_do_write_ops(struct mtd_info *mtd, loff_t to,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
int chipnr, realpage, page, blockmask, column;
|
|
struct nand_chip *chip = mtd->priv;
|
|
uint32_t writelen = ops->len;
|
|
uint8_t *oob = ops->oobbuf;
|
|
uint8_t *buf = ops->datbuf;
|
|
int ret = 0, subpage;
|
|
|
|
ops->retlen = 0;
|
|
if (!writelen)
|
|
return 0;
|
|
|
|
column = to & (mtd->writesize - 1);
|
|
subpage = column || (writelen & (mtd->writesize - 1));
|
|
|
|
if (subpage && oob)
|
|
return -EINVAL;
|
|
|
|
chipnr = (int)(to >> chip->chip_shift);
|
|
chip->select_chip(mtd, chipnr);
|
|
|
|
/* Check, if it is write protected */
|
|
if (nand_check_wp(mtd)) {
|
|
return -EIO;
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
} else {
|
|
/* We still need to erase leftover OOB data */
|
|
memset(chip->oob_poi, 0xff, mtd->oobsize);
|
|
}
|
|
|
|
if (oob || !mtd_all_ff(wbuf, mtd->writesize)) {
|
|
ret = chip->write_page(mtd, chip, wbuf, page, cached,
|
|
(ops->mode == MTD_OPS_RAW));
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
writelen -= bytes;
|
|
if (!writelen)
|
|
break;
|
|
|
|
column = 0;
|
|
buf += bytes;
|
|
realpage++;
|
|
|
|
page = realpage & chip->pagemask;
|
|
/* Check, if we cross a chip boundary */
|
|
if (!page) {
|
|
chipnr++;
|
|
chip->select_chip(mtd, -1);
|
|
chip->select_chip(mtd, chipnr);
|
|
}
|
|
}
|
|
|
|
ops->retlen = ops->len - writelen;
|
|
if (unlikely(oob))
|
|
ops->oobretlen = ops->ooblen;
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* nand_write - [MTD Interface] NAND write with ECC
|
|
* @mtd: MTD device structure
|
|
* @to: offset to write to
|
|
* @len: number of bytes to write
|
|
* @retlen: pointer to variable to store the number of written bytes
|
|
* @buf: the data to write
|
|
*
|
|
* NAND write with ECC
|
|
*/
|
|
int nand_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|
size_t *retlen, const uint8_t *buf)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
int ret;
|
|
|
|
/* Do not allow reads past end of device */
|
|
if ((to + len) > mtd->size)
|
|
return -EINVAL;
|
|
if (!len)
|
|
return 0;
|
|
|
|
chip->ops.len = len;
|
|
chip->ops.datbuf = (uint8_t *)buf;
|
|
chip->ops.oobbuf = NULL;
|
|
|
|
ret = nand_do_write_ops(mtd, to, &chip->ops);
|
|
|
|
*retlen = chip->ops.retlen;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* nand_do_write_oob - [MTD Interface] NAND write out-of-band
|
|
* @mtd: MTD device structure
|
|
* @to: offset to write to
|
|
* @ops: oob operation description structure
|
|
*
|
|
* NAND write out-of-band
|
|
*/
|
|
static int nand_do_write_oob(struct mtd_info *mtd, loff_t to,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
int chipnr, page, status, len;
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL3, "nand_write_oob: to = 0x%08x, len = %i\n",
|
|
(unsigned int)to, (int)ops->ooblen);
|
|
|
|
if (ops->mode == MTD_OPS_AUTO_OOB)
|
|
len = chip->ecc.layout->oobavail;
|
|
else
|
|
len = mtd->oobsize;
|
|
|
|
/* Do not allow write past end of page */
|
|
if ((ops->ooboffs + ops->ooblen) > len) {
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_write_oob: "
|
|
"Attempt to write past end of page\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (unlikely(ops->ooboffs >= len)) {
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
|
"Attempt to start write outside oob\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Do not allow reads past end of device */
|
|
if (unlikely(to >= mtd->size ||
|
|
ops->ooboffs + ops->ooblen >
|
|
((mtd->size >> chip->page_shift) -
|
|
(to >> chip->page_shift)) * len)) {
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
|
"Attempt write beyond end of device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
chipnr = (int)(to >> chip->chip_shift);
|
|
chip->select_chip(mtd, chipnr);
|
|
|
|
/* Shift to get page */
|
|
page = (int)(to >> chip->page_shift);
|
|
|
|
/*
|
|
* Reset the chip. Some chips (like the Toshiba TC5832DC found in one
|
|
* of my DiskOnChip 2000 test units) will clear the whole data page too
|
|
* if we don't do this. I have no clue why, but I seem to have 'fixed'
|
|
* it in the doc2000 driver in August 1999. dwmw2.
|
|
*/
|
|
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
|
|
|
|
/* Check, if it is write protected */
|
|
if (nand_check_wp(mtd))
|
|
return -EROFS;
|
|
|
|
/* Invalidate the page cache, if we write to the cached page */
|
|
if (page == chip->pagebuf)
|
|
chip->pagebuf = -1;
|
|
|
|
memset(chip->oob_poi, 0xff, mtd->oobsize);
|
|
nand_fill_oob(chip, ops->oobbuf, ops);
|
|
status = chip->ecc.write_oob(mtd, chip, page & chip->pagemask);
|
|
memset(chip->oob_poi, 0xff, mtd->oobsize);
|
|
|
|
if (status)
|
|
return status;
|
|
|
|
ops->oobretlen = ops->ooblen;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* nand_write_oob - [MTD Interface] NAND write data and/or out-of-band
|
|
* @mtd: MTD device structure
|
|
* @to: offset to write to
|
|
* @ops: oob operation description structure
|
|
*/
|
|
int nand_write_oob(struct mtd_info *mtd, loff_t to,
|
|
struct mtd_oob_ops *ops)
|
|
{
|
|
int ret = -ENOSYS;
|
|
|
|
ops->retlen = 0;
|
|
|
|
/* Do not allow writes past end of device */
|
|
if (ops->datbuf && (to + ops->len) > mtd->size) {
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_read_oob: "
|
|
"Attempt read beyond end of device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch(ops->mode) {
|
|
case MTD_OPS_PLACE_OOB:
|
|
case MTD_OPS_AUTO_OOB:
|
|
case MTD_OPS_RAW:
|
|
break;
|
|
|
|
default:
|
|
goto out;
|
|
}
|
|
|
|
if (!ops->datbuf)
|
|
ret = nand_do_write_oob(mtd, to, ops);
|
|
else
|
|
ret = nand_do_write_ops(mtd, to, ops);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* single_erease_cmd - [GENERIC] NAND standard block erase command function
|
|
* @mtd: MTD device structure
|
|
* @page: the page address of the block which will be erased
|
|
*
|
|
* Standard erase command for NAND chips
|
|
*/
|
|
void single_erase_cmd(struct mtd_info *mtd, int page)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
/* Send commands to erase a block */
|
|
chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
|
|
chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
|
|
}
|
|
|
|
/**
|
|
* multi_erease_cmd - [GENERIC] AND specific block erase command function
|
|
* @mtd: MTD device structure
|
|
* @page: the page address of the block which will be erased
|
|
*
|
|
* AND multi block erase command function
|
|
* Erase 4 consecutive blocks
|
|
*/
|
|
void multi_erase_cmd(struct mtd_info *mtd, int page)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
/* Send commands to erase a block */
|
|
chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page++);
|
|
chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page++);
|
|
chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page++);
|
|
chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
|
|
chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
|
|
}
|
|
|
|
/**
|
|
* nand_erase - [MTD Interface] erase block(s)
|
|
* @mtd: MTD device structure
|
|
* @instr: erase instruction
|
|
*
|
|
* Erase one ore more blocks
|
|
*/
|
|
int nand_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|
{
|
|
return nand_erase_nand(mtd, instr, 0);
|
|
}
|
|
|
|
#define BBT_PAGE_MASK 0xffffff3f
|
|
/**
|
|
* nand_erase_nand - [Internal] erase block(s)
|
|
* @mtd: MTD device structure
|
|
* @instr: erase instruction
|
|
* @allowbbt: allow erasing the bbt area
|
|
*
|
|
* Erase one ore more blocks
|
|
*/
|
|
int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
|
|
int allowbbt)
|
|
{
|
|
int page, len, status, pages_per_block, ret, chipnr;
|
|
struct nand_chip *chip = mtd->priv;
|
|
int rewrite_bbt[NAND_MAX_CHIPS]={0};
|
|
unsigned int bbt_masked_page = 0xffffffff;
|
|
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL3, "nand_erase: start = 0x%08x, len = %i\n",
|
|
(unsigned int)instr->addr, (unsigned int)instr->len);
|
|
|
|
/* Start address must align on block boundary */
|
|
if (instr->addr & ((1 << chip->phys_erase_shift) - 1)) {
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: Unaligned address\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Length must align on block boundary */
|
|
if (instr->len & ((1 << chip->phys_erase_shift) - 1)) {
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: "
|
|
"Length not block aligned\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Do not allow erase past end of device */
|
|
if ((instr->len + instr->addr) > mtd->size) {
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: "
|
|
"Erase past end of device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
instr->fail_addr = 0xffffffff;
|
|
|
|
/* Shift to get first page */
|
|
page = (int)(instr->addr >> chip->page_shift);
|
|
chipnr = (int)(instr->addr >> chip->chip_shift);
|
|
|
|
/* Calculate pages in each block */
|
|
pages_per_block = 1 << (chip->phys_erase_shift - chip->page_shift);
|
|
|
|
/* Select the NAND device */
|
|
chip->select_chip(mtd, chipnr);
|
|
|
|
/* Check, if it is write protected */
|
|
if (nand_check_wp(mtd)) {
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: "
|
|
"Device is write protected!!!\n");
|
|
instr->state = MTD_ERASE_FAILED;
|
|
goto erase_exit;
|
|
}
|
|
|
|
/*
|
|
* If BBT requires refresh, set the BBT page mask to see if the BBT
|
|
* should be rewritten. Otherwise the mask is set to 0xffffffff which
|
|
* can not be matched. This is also done when the bbt is actually
|
|
* erased to avoid recusrsive updates
|
|
*/
|
|
if (chip->options & BBT_AUTO_REFRESH && !allowbbt)
|
|
bbt_masked_page = chip->bbt_td->pages[chipnr] & BBT_PAGE_MASK;
|
|
|
|
/* Loop through the pages */
|
|
len = instr->len;
|
|
|
|
instr->state = MTD_ERASING;
|
|
|
|
while (len) {
|
|
/*
|
|
* heck if we have a bad block, we do not erase bad blocks !
|
|
*/
|
|
if (!mtd->allow_erasebad &&
|
|
nand_block_checkbad(mtd, ((loff_t) page) <<
|
|
chip->page_shift, 0, allowbbt)) {
|
|
pr_warn("nand_erase: attempt to erase a "
|
|
"bad block at page 0x%08x\n", page);
|
|
instr->state = MTD_ERASE_FAILED;
|
|
goto erase_exit;
|
|
}
|
|
|
|
/*
|
|
* Invalidate the page cache, if we erase the block which
|
|
* contains the current cached page
|
|
*/
|
|
if (page <= chip->pagebuf && chip->pagebuf <
|
|
(page + pages_per_block))
|
|
chip->pagebuf = -1;
|
|
|
|
chip->erase_cmd(mtd, page & chip->pagemask);
|
|
|
|
status = chip->waitfunc(mtd, chip);
|
|
|
|
/*
|
|
* See if operation failed and additional status checks are
|
|
* available
|
|
*/
|
|
if ((status & NAND_STATUS_FAIL) && (chip->errstat))
|
|
status = chip->errstat(mtd, chip, FL_ERASING,
|
|
status, page);
|
|
|
|
/* See if block erase succeeded */
|
|
if (status & NAND_STATUS_FAIL) {
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: "
|
|
"Failed erase, page 0x%08x\n", page);
|
|
instr->state = MTD_ERASE_FAILED;
|
|
instr->fail_addr = (page << chip->page_shift);
|
|
goto erase_exit;
|
|
}
|
|
|
|
/*
|
|
* If BBT requires refresh, set the BBT rewrite flag to the
|
|
* page being erased
|
|
*/
|
|
if (bbt_masked_page != 0xffffffff &&
|
|
(page & BBT_PAGE_MASK) == bbt_masked_page)
|
|
rewrite_bbt[chipnr] = (page << chip->page_shift);
|
|
|
|
/* Increment page address and decrement length */
|
|
len -= (1 << chip->phys_erase_shift);
|
|
page += pages_per_block;
|
|
|
|
/* Check, if we cross a chip boundary */
|
|
if (len && !(page & chip->pagemask)) {
|
|
chipnr++;
|
|
chip->select_chip(mtd, -1);
|
|
chip->select_chip(mtd, chipnr);
|
|
|
|
/*
|
|
* If BBT requires refresh and BBT-PERCHIP, set the BBT
|
|
* page mask to see if this BBT should be rewritten
|
|
*/
|
|
if (bbt_masked_page != 0xffffffff &&
|
|
(chip->bbt_td->options & NAND_BBT_PERCHIP))
|
|
bbt_masked_page = chip->bbt_td->pages[chipnr] &
|
|
BBT_PAGE_MASK;
|
|
}
|
|
}
|
|
instr->state = MTD_ERASE_DONE;
|
|
|
|
erase_exit:
|
|
|
|
ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO;
|
|
|
|
/* Do call back function */
|
|
if (!ret)
|
|
mtd_erase_callback(instr);
|
|
|
|
/*
|
|
* If BBT requires refresh and erase was successful, rewrite any
|
|
* selected bad block tables
|
|
*/
|
|
if (bbt_masked_page == 0xffffffff || ret)
|
|
return ret;
|
|
|
|
if (!IS_ENABLED(CONFIG_NAND_BBT))
|
|
return ret;
|
|
|
|
for (chipnr = 0; chipnr < chip->numchips; chipnr++) {
|
|
if (!rewrite_bbt[chipnr])
|
|
continue;
|
|
/* update the BBT for chip */
|
|
MTD_DEBUG(MTD_DEBUG_LEVEL0, "nand_erase_nand: nand_update_bbt "
|
|
"(%d:0x%0x 0x%0x)\n", chipnr, rewrite_bbt[chipnr],
|
|
chip->bbt_td->pages[chipnr]);
|
|
nand_update_bbt(mtd, rewrite_bbt[chipnr]);
|
|
}
|
|
|
|
/* Return more or less happy */
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* nand_block_markbad - [MTD Interface] Mark block at the given offset as bad
|
|
* @mtd: MTD device structure
|
|
* @ofs: offset relative to mtd start
|
|
*/
|
|
int nand_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
int ret;
|
|
|
|
if ((ret = nand_block_isbad(mtd, ofs))) {
|
|
/* If it was bad already, return success and do nothing. */
|
|
if (ret > 0)
|
|
return 0;
|
|
return ret;
|
|
}
|
|
|
|
return chip->block_markbad(mtd, ofs);
|
|
}
|