From 6fe5a6acdc126107e54a6c584536e09ab7dde949 Mon Sep 17 00:00:00 2001 From: Vimal Singh Date: Wed, 3 Feb 2010 14:12:24 +0530 Subject: mtd: nand: create a helper verification function ... verification for 'nand_erase_nand' These checks are expected to be used by 'nand_lock' and 'nand_unlock' routines too. As all these three are block aligned operations. So, creating a helper function for this makes sense. Signed-off-by: Vimal Singh Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 48 +++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 18 deletions(-) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 8f2958fe2148..2dfeb4bea83a 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 @@ -2293,25 +2322,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__); - 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__); + if (check_offs_len(mtd, instr->addr, instr->len)) return -EINVAL; - } instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; -- cgit v1.2.3 From 7d70f334ad2bf1b3aaa1f0699c0f442e14bcc9e0 Mon Sep 17 00:00:00 2001 From: Vimal Singh Date: Mon, 8 Feb 2010 15:50:49 +0530 Subject: mtd: nand: add lock/unlock routines Add nand lock / unlock routines. At least 'micron' parts support this. Signed-off-by: Vimal Singh Signed-off-by: Artem Bityutskiy Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 164 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/nand.h | 10 +++ 2 files changed, 174 insertions(+) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 2dfeb4bea83a..ed62e1ee0f81 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -863,6 +863,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 @@ -3089,6 +3251,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); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index ccab9dfc5217..48bc2c54302c 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -38,6 +38,12 @@ extern void nand_release (struct mtd_info *mtd); /* Internal helper for board drivers which need to override command function */ extern void nand_wait_ready(struct mtd_info *mtd); +/* locks all blockes present in the device */ +extern int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); + +/* unlocks specified locked blockes */ +extern int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); + /* The maximum number of NAND chips in an array */ #define NAND_MAX_CHIPS 8 @@ -82,6 +88,10 @@ extern void nand_wait_ready(struct mtd_info *mtd); #define NAND_CMD_ERASE2 0xd0 #define NAND_CMD_RESET 0xff +#define NAND_CMD_LOCK 0x2a +#define NAND_CMD_UNLOCK1 0x23 +#define NAND_CMD_UNLOCK2 0x24 + /* Extended commands for large page devices */ #define NAND_CMD_READSTART 0x30 #define NAND_CMD_RNDOUTSTART 0xE0 -- cgit v1.2.3 From 49ef3c6ee11e221b26caf4ac55c2702a37cca103 Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Mon, 22 Feb 2010 20:39:34 +0200 Subject: mtd: nand: make suspend work if device is accessed by kernel threads. Since all userspace threads are frozen at the time the nand_suspend is called, they aren't inside any nand function. We don't call try_to_freeze in nand ether. Thus the only user that can be inside the nand functions is an non freezeable kernel thread. Thus we can safely wait for it to finish. Signed-off-by: Maxim Levitsky Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index ed62e1ee0f81..7442b3a29b25 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -773,9 +773,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); -- cgit v1.2.3 From 9aca334e854c319ccafea871006fda3814196e7b Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Mon, 22 Feb 2010 20:39:35 +0200 Subject: mtd: nand: make MTD_OOB_PLACE work correctly. MTD_OOB_PLACE is supposed to read/write the raw oob data similiar to the MTD_OOB_RAW however due to a bug, currently it is not possible to read more data that is specified by the oob 'free' regions. Signed-off-by: Maxim Levitsky Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 7442b3a29b25..cada4cffacf8 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -1420,6 +1420,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; @@ -1470,10 +1473,11 @@ 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); + max_oobsize); if (toread) { oob = nand_transfer_oob(chip, oob, ops, toread); -- cgit v1.2.3 From 782ce79a45b3b850b108896fcf7da26754061c8f Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Mon, 22 Feb 2010 20:39:36 +0200 Subject: mtd: nand: cleanup the nand_do_write_ops nand_do_write_ops was broken in regard to writing several pages, each with its own oob. Although nand_do_write_ops intends to allow such mode, it fails do do so Probably this was never tested. Also add missing checks for attempts to write at illegal offsets. Signed-off-by: Maxim Levitsky Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index cada4cffacf8..138674183c1c 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2072,11 +2072,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: @@ -2131,6 +2129,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; @@ -2172,6 +2175,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; @@ -2187,8 +2194,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)); @@ -2362,7 +2372,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); -- cgit v1.2.3 From b64d39d8b03fea88417d53715ccbebf71d4dcc9f Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Mon, 22 Feb 2010 20:39:37 +0200 Subject: mtd: nand: make reads using MTD_OOB_RAW affect only ECC validation This changes the behavier of MTD_OOB_RAW. It used to read both OOB and data to the data buffer, however you would still need to specify the dummy oob buffer. This is only used in one place, but makes it hard to read data+oob without ECC test, thus I removed that behavier, and fixed the user. Now MTD_OOB_RAW behaves just like MTD_OOB_PLACE, but doesn't do ECC validation Signed-off-by: Maxim Levitsky Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 19 +++++++------------ drivers/mtd/nand/nand_bbt.c | 26 ++++++++++++++++++++++---- include/linux/mtd/mtd.h | 4 +--- 3 files changed, 30 insertions(+), 19 deletions(-) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 138674183c1c..51dfea1b3ce6 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -1474,18 +1474,13 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, if (unlikely(oob)) { - /* Raw mode does data:oob:data:oob */ - if (ops->mode != MTD_OOB_RAW) { - int toread = min(oobreadlen, - max_oobsize); - 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)) { diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c index 55c23e5cd210..387c45c366fe 100644 --- a/drivers/mtd/nand/nand_bbt.c +++ b/drivers/mtd/nand/nand_bbt.c @@ -237,15 +237,33 @@ static int scan_read_raw(struct mtd_info *mtd, uint8_t *buf, loff_t offs, size_t len) { struct mtd_oob_ops ops; + int res; ops.mode = MTD_OOB_RAW; ops.ooboffs = 0; ops.ooblen = mtd->oobsize; - ops.oobbuf = buf; - ops.datbuf = buf; - ops.len = len; - return mtd->read_oob(mtd, offs, &ops); + + while (len > 0) { + if (len <= mtd->writesize) { + ops.oobbuf = buf + len; + ops.datbuf = buf; + ops.len = len; + return mtd->read_oob(mtd, offs, &ops); + } else { + ops.oobbuf = buf + mtd->writesize; + ops.datbuf = buf; + ops.len = mtd->writesize; + res = mtd->read_oob(mtd, offs, &ops); + + if (res) + return res; + } + + buf += mtd->oobsize + mtd->writesize; + len -= mtd->writesize; + } + return 0; } /* diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 11d8e68d17c0..5326435a7571 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -60,9 +60,7 @@ struct mtd_erase_region_info { * MTD_OOB_PLACE: oob data are placed at the given offset * MTD_OOB_AUTO: oob data are automatically placed at the free areas * which are defined by the ecclayout - * MTD_OOB_RAW: mode to read raw data+oob in one chunk. The oob data - * is inserted into the data. Thats a raw image of the - * flash contents. + * MTD_OOB_RAW: mode to read oob and data without doing ECC checking */ typedef enum { MTD_OOB_PLACE, -- cgit v1.2.3 From e0b58d0a7005cd4b9c7fa4694a437a2d86719c13 Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Mon, 22 Feb 2010 20:39:38 +0200 Subject: mtd: nand: add ->badblockbits for minimum number of set bits in bad block byte This can be used to protect against bitflips in that field, but now mostly for smartmedia. Signed-off-by: Maxim Levitsky Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 13 +++++++++---- include/linux/mtd/nand.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 51dfea1b3ce6..ba29a29bd743 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -364,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); @@ -2884,6 +2888,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; diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 48bc2c54302c..f2d4a1ac14b8 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -401,6 +401,7 @@ struct nand_chip { int subpagesize; uint8_t cellinfo; int badblockpos; + int badblockbits; flstate_t state; -- cgit v1.2.3 From 5e81e88a4c140586d9212999cea683bcd66a15c6 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Fri, 26 Feb 2010 18:32:56 +0000 Subject: mtd: nand: Allow caller to pass alternative ID table to nand_scan_ident() Signed-off-by: David Woodhouse --- drivers/mtd/nand/atmel_nand.c | 2 +- drivers/mtd/nand/bcm_umi_nand.c | 2 +- drivers/mtd/nand/cafe_nand.c | 2 +- drivers/mtd/nand/davinci_nand.c | 2 +- drivers/mtd/nand/fsl_elbc_nand.c | 2 +- drivers/mtd/nand/mxc_nand.c | 2 +- drivers/mtd/nand/nand_base.c | 29 +++++++++++++++-------------- drivers/mtd/nand/s3c2410.c | 3 ++- drivers/mtd/nand/sh_flctl.c | 2 +- drivers/mtd/nand/sm_common.c | 2 +- drivers/mtd/nand/socrates_nand.c | 2 +- drivers/mtd/nand/txx9ndfmc.c | 2 +- include/linux/mtd/nand.h | 4 +++- 13 files changed, 30 insertions(+), 26 deletions(-) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/atmel_nand.c b/drivers/mtd/nand/atmel_nand.c index 524e6c9e0672..04d30887ca7f 100644 --- a/drivers/mtd/nand/atmel_nand.c +++ b/drivers/mtd/nand/atmel_nand.c @@ -474,7 +474,7 @@ static int __init atmel_nand_probe(struct platform_device *pdev) } /* first scan to find the device and get the page size */ - if (nand_scan_ident(mtd, 1)) { + if (nand_scan_ident(mtd, 1, NULL)) { res = -ENXIO; goto err_scan_ident; } diff --git a/drivers/mtd/nand/bcm_umi_nand.c b/drivers/mtd/nand/bcm_umi_nand.c index 087bcd745bb7..5ff90b7e565e 100644 --- a/drivers/mtd/nand/bcm_umi_nand.c +++ b/drivers/mtd/nand/bcm_umi_nand.c @@ -446,7 +446,7 @@ static int __devinit bcm_umi_nand_probe(struct platform_device *pdev) * layout we'll be using. */ - err = nand_scan_ident(board_mtd, 1); + err = nand_scan_ident(board_mtd, 1, NULL); if (err) { printk(KERN_ERR "nand_scan failed: %d\n", err); iounmap(bcm_umi_io_base); diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c index 67e2b33f7eff..01a6fe1c7805 100644 --- a/drivers/mtd/nand/cafe_nand.c +++ b/drivers/mtd/nand/cafe_nand.c @@ -761,7 +761,7 @@ static int __devinit cafe_nand_probe(struct pci_dev *pdev, cafe_readl(cafe, GLOBAL_CTRL), cafe_readl(cafe, GLOBAL_IRQ_MASK)); /* Scan to find existence of the device */ - if (nand_scan_ident(mtd, 2)) { + if (nand_scan_ident(mtd, 2, NULL)) { err = -ENXIO; goto out_irq; } diff --git a/drivers/mtd/nand/davinci_nand.c b/drivers/mtd/nand/davinci_nand.c index e2eeaf1e51a3..45bb931c0848 100644 --- a/drivers/mtd/nand/davinci_nand.c +++ b/drivers/mtd/nand/davinci_nand.c @@ -690,7 +690,7 @@ static int __init nand_davinci_probe(struct platform_device *pdev) spin_unlock_irq(&davinci_nand_lock); /* Scan to find existence of the device(s) */ - ret = nand_scan_ident(&info->mtd, pdata->mask_chipsel ? 2 : 1); + ret = nand_scan_ident(&info->mtd, pdata->mask_chipsel ? 2 : 1, NULL); if (ret < 0) { dev_dbg(&pdev->dev, "no NAND chip(s) found\n"); goto err_scan; diff --git a/drivers/mtd/nand/fsl_elbc_nand.c b/drivers/mtd/nand/fsl_elbc_nand.c index 1b8328fbb9dc..3f38fb8e6666 100644 --- a/drivers/mtd/nand/fsl_elbc_nand.c +++ b/drivers/mtd/nand/fsl_elbc_nand.c @@ -891,7 +891,7 @@ static int __devinit fsl_elbc_chip_probe(struct fsl_elbc_ctrl *ctrl, if (ret) goto err; - ret = nand_scan_ident(&priv->mtd, 1); + ret = nand_scan_ident(&priv->mtd, 1, NULL); if (ret) goto err; diff --git a/drivers/mtd/nand/mxc_nand.c b/drivers/mtd/nand/mxc_nand.c index 06cc378196b5..474a09e53131 100644 --- a/drivers/mtd/nand/mxc_nand.c +++ b/drivers/mtd/nand/mxc_nand.c @@ -819,7 +819,7 @@ static int __init mxcnd_probe(struct platform_device *pdev) } /* first scan to find the device and get the page size */ - if (nand_scan_ident(mtd, 1)) { + if (nand_scan_ident(mtd, 1, NULL)) { err = -ENXIO; goto escan; } diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index ba29a29bd743..1c4823696be2 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2766,10 +2766,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 */ @@ -2808,15 +2808,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) @@ -2926,13 +2925,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; @@ -2944,7 +2945,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)) @@ -3235,7 +3236,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; diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index c41ad2285c63..dc02dcd0c08f 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -1013,7 +1013,8 @@ static int s3c24xx_nand_probe(struct platform_device *pdev) s3c2410_nand_init_chip(info, nmtd, sets); nmtd->scan_res = nand_scan_ident(&nmtd->mtd, - (sets) ? sets->nr_chips : 1); + (sets) ? sets->nr_chips : 1, + NULL); if (nmtd->scan_res == 0) { s3c2410_nand_update_chip(info, nmtd); diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c index 4260ab78f95c..dbc09a81866e 100644 --- a/drivers/mtd/nand/sh_flctl.c +++ b/drivers/mtd/nand/sh_flctl.c @@ -825,7 +825,7 @@ static int __init flctl_probe(struct platform_device *pdev) nand->select_chip = flctl_select_chip; nand->cmdfunc = flctl_cmdfunc; - ret = nand_scan_ident(flctl_mtd, 1); + ret = nand_scan_ident(flctl_mtd, 1, NULL); if (ret) goto err; diff --git a/drivers/mtd/nand/sm_common.c b/drivers/mtd/nand/sm_common.c index 07b6f725723f..f52bb3949275 100644 --- a/drivers/mtd/nand/sm_common.c +++ b/drivers/mtd/nand/sm_common.c @@ -75,7 +75,7 @@ int sm_register_device(struct mtd_info *mtd) chip->options |= NAND_SKIP_BBTSCAN | NAND_SMARTMEDIA; /* Scan for card properties */ - ret = nand_scan_ident(mtd, 1); + ret = nand_scan_ident(mtd, 1, NULL); if (ret) return ret; diff --git a/drivers/mtd/nand/socrates_nand.c b/drivers/mtd/nand/socrates_nand.c index 65748ea2b348..b37cbde6e7db 100644 --- a/drivers/mtd/nand/socrates_nand.c +++ b/drivers/mtd/nand/socrates_nand.c @@ -220,7 +220,7 @@ static int __devinit socrates_nand_probe(struct of_device *ofdev, dev_set_drvdata(&ofdev->dev, host); /* first scan to find the device and get the page size */ - if (nand_scan_ident(mtd, 1)) { + if (nand_scan_ident(mtd, 1, NULL)) { res = -ENXIO; goto out; } diff --git a/drivers/mtd/nand/txx9ndfmc.c b/drivers/mtd/nand/txx9ndfmc.c index 863513c3b69a..054a41c0ef4a 100644 --- a/drivers/mtd/nand/txx9ndfmc.c +++ b/drivers/mtd/nand/txx9ndfmc.c @@ -274,7 +274,7 @@ static int txx9ndfmc_nand_scan(struct mtd_info *mtd) struct nand_chip *chip = mtd->priv; int ret; - ret = nand_scan_ident(mtd, 1); + ret = nand_scan_ident(mtd, 1, NULL); if (!ret) { if (mtd->writesize >= 512) { chip->ecc.size = mtd->writesize; diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index f2d4a1ac14b8..d152bdf9161f 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -25,11 +25,13 @@ #include struct mtd_info; +struct nand_flash_dev; /* Scan and identify a NAND device */ extern int nand_scan (struct mtd_info *mtd, int max_chips); /* Separate phases of nand_scan(), allowing board driver to intervene * and override command or ECC setup according to flash type */ -extern int nand_scan_ident(struct mtd_info *mtd, int max_chips); +extern int nand_scan_ident(struct mtd_info *mtd, int max_chips, + struct nand_flash_dev *table); extern int nand_scan_tail(struct mtd_info *mtd); /* Free resources held by the NAND device */ -- cgit v1.2.3 From 93edbad69b0491d794c2ec86bcc65c69eac676e3 Mon Sep 17 00:00:00 2001 From: Maxim Levitsky Date: Mon, 22 Feb 2010 20:39:40 +0200 Subject: mtd: Workaround wrong write protect status on some xD cards Signed-off-by: Maxim Levitsky Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 8 +++++++- include/linux/mtd/nand.h | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 1c4823696be2..b9dc65c7253c 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -434,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; @@ -3175,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; diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index d152bdf9161f..8bdacb885f90 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -182,6 +182,12 @@ typedef enum { /* Chip does not allow subpage writes */ #define NAND_NO_SUBPAGE_WRITE 0x00000200 +/* Device is one of 'new' xD cards that expose fake nand command set */ +#define NAND_BROKEN_XD 0x00000400 + +/* Device behaves just like nand, but is readonly */ +#define NAND_ROM 0x00000800 + /* Options valid for Samsung large page devices */ #define NAND_SAMSUNG_LP_OPTIONS \ (NAND_NO_PADDING | NAND_CACHEPRG | NAND_COPYBACK) -- cgit v1.2.3 From 426c457a3216fac74e3d44dd39729b0689f4c7ab Mon Sep 17 00:00:00 2001 From: Kevin Cernekee Date: Tue, 4 May 2010 20:58:03 -0700 Subject: mtd: nand: extend NAND flash detection to new MLC chips Some of the newer MLC devices have a 6-byte ID sequence in which several field definitions differ from older chips in a manner that is not backward compatible. For instance: Samsung K9GAG08U0M (5-byte sequence): ec d5 14 b6 74 4th byte, bits 1:0 encode the page size: 0=1KiB, 1=2KiB, 2=4KiB, 3=8KiB 4th byte, bits 5:4 encode the block size: 0=64KiB, 1=128KiB, ... 4th byte, bit 6 encodes the OOB size: 0=8B/512B, 1=16B/512B Samsung K9GAG08U0D (6-byte sequence): ec d5 94 29 34 41 4th byte, bits 1:0 encode the page size: 0=2KiB, 1=4KiB, 3=8KiB, 4=rsvd 4th byte, bits 7;5:4 encode the block size: 0=128KiB, 1=256KiB, ... 4th byte, bits 6;3:2 encode the OOB size: 1=128B/page, 2=218B/page This patch uses the new 6-byte scheme if the following conditions are all true: 1) The ID code wraps around after exactly 6 bytes 2) Manufacturer is Samsung 3) 6th byte is zero The patch also extends the maximum OOB size from 128B to 256B. Signed-off-by: Kevin Cernekee Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 64 ++++++++++++++++++++++++++++++-------------- include/linux/mtd/nand.h | 2 +- 2 files changed, 45 insertions(+), 21 deletions(-) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index b9dc65c7253c..85891dcc27ad 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2774,8 +2774,8 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, int busw, int *maf_id, struct nand_flash_dev *type) { - int dev_id, maf_idx; - int tmp_id, tmp_manf; + int i, dev_id, maf_idx; + u8 id_data[8]; /* Select the device */ chip->select_chip(mtd, 0); @@ -2801,15 +2801,15 @@ 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); } @@ -2832,21 +2832,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 diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 8bdacb885f90..50f3aa00a452 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -53,7 +53,7 @@ extern int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); * is supported now. If you add a chip with bigger oobsize/page * adjust this accordingly. */ -#define NAND_MAX_OOBSIZE 128 +#define NAND_MAX_OOBSIZE 256 #define NAND_MAX_PAGESIZE 4096 /* -- cgit v1.2.3 From b60b08b02ca8d9575985ae6711bd656dd67e9039 Mon Sep 17 00:00:00 2001 From: Kevin Cernekee Date: Tue, 4 May 2010 20:58:10 -0700 Subject: mtd: nand: support alternate BB marker locations on MLC This is a slightly modified version of a patch submitted last year by Reuben Dowle . His original comments follow: This patch adds support for some MLC NAND flashes that place the BB marker in the LAST page of the bad block rather than the FIRST page used for SLC NAND and other types of MLC nand. Lifted from Samsung datasheet for K9LG8G08U0A (1Gbyte MLC NAND): " Identifying Initial Invalid Block(s) All device locations are erased(FFh) except locations where the initial invalid block(s) information is written prior to shipping. The initial invalid block(s) status is defined by the 1st byte in the spare area. Samsung makes sure that the last page of every initial invalid block has non-FFh data at the column address of 2,048. ... " As far as I can tell, this is the same for all Samsung MLC nand, and in fact the samsung bsp for the processor used in our project (s3c6410) actually contained a hack similar to this patch but less portable to enable use of their NAND parts. I discovered this problem when trying to use a Micron NAND which does not used this layout - I wish samsung would put their stuff in main-line to avoid this type of problem. Currently this patch causes all MLC nand with manufacturer codes from Samsung and ST(Numonyx) to use this alternative location, since these are the manufactures that I know of that use this layout. Signed-off-by: Kevin Cernekee Signed-off-by: David Woodhouse --- drivers/mtd/nand/nand_base.c | 15 +++++++++++++++ drivers/mtd/nand/nand_bbt.c | 3 +++ include/linux/mtd/nand.h | 2 ++ 3 files changed, 20 insertions(+) (limited to 'drivers/mtd/nand/nand_base.c') diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 85891dcc27ad..4a7b86423ee9 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -347,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) { @@ -396,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) @@ -2933,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; diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c index 387c45c366fe..ad97c0ce73b2 100644 --- a/drivers/mtd/nand/nand_bbt.c +++ b/drivers/mtd/nand/nand_bbt.c @@ -432,6 +432,9 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf, from = (loff_t)startblock << (this->bbt_erase_shift - 1); } + if (this->options & NAND_BB_LAST_PAGE) + from += mtd->erasesize - (mtd->writesize * len); + for (i = startblock; i < numblocks;) { int ret; diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 50f3aa00a452..a81b185e23a7 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -181,6 +181,8 @@ typedef enum { #define NAND_NO_READRDY 0x00000100 /* Chip does not allow subpage writes */ #define NAND_NO_SUBPAGE_WRITE 0x00000200 +/* Chip stores bad block marker on the last page of the eraseblock */ +#define NAND_BB_LAST_PAGE 0x00000400 /* Device is one of 'new' xD cards that expose fake nand command set */ #define NAND_BROKEN_XD 0x00000400 -- cgit v1.2.3