diff options
author | David Ungar <david.ungar@timesys.com> | 2010-10-11 14:24:08 -0400 |
---|---|---|
committer | David Ungar <david.ungar@timesys.com> | 2010-10-11 14:39:23 -0400 |
commit | a718de3f42a6280fefd8b60b3e4733e015b0f637 (patch) | |
tree | 1b0c5b36cd7fb408e8119d56c542f86903cc56d5 /drivers | |
parent | 411a2e5823bf4280d70d5b9e9ea683e27b09f7b2 (diff) |
Additional NAND support
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mtd/mtdcore.c | 4 | ||||
-rw-r--r-- | drivers/mtd/nand/nand_base.c | 93 | ||||
-rw-r--r-- | drivers/mtd/nand/nand_util.c | 78 | ||||
-rw-r--r-- | drivers/mtd/nand/omap_gpmc.c | 383 |
4 files changed, 536 insertions, 22 deletions
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c index 6eb52ed50c6..7bc62e6f869 100644 --- a/drivers/mtd/mtdcore.c +++ b/drivers/mtd/mtdcore.c @@ -11,6 +11,10 @@ #include <linux/mtd/compat.h> #include <ubi_uboot.h> +#ifdef CONFIG_MTD_DEBUG +int mtd_debug_verbose = CONFIG_MTD_DEBUG_VERBOSE; +#endif + struct mtd_info *mtd_table[MAX_MTD_DEVICES]; int add_mtd_device(struct mtd_info *mtd) diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 360b0708498..1f3a638748a 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -454,11 +454,26 @@ 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; - /* Check the WP bit */ + + /* Check the WP bit. To do so requires resetting the device to + force the status back to its reset value (so WP becomes whether + the WP pin is set). */ + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1; } +static void nand_set_feature(struct mtd_info *mtd, int feature_address, uint8_t *params) +{ + struct nand_chip *chip = mtd->priv; + + chip->cmd_ctrl(mtd, NAND_CMD_SETFEATURE, NAND_CTRL_CLE); + chip->cmd_ctrl(mtd, feature_address, NAND_CTRL_ALE); + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_CTRL_CHANGE | NAND_NCE); + udelay(100); + chip->write_buf(mtd, params, 4); +} + /** * nand_block_checkbad - [GENERIC] Check if a block is marked bad * @mtd: MTD device structure @@ -1217,6 +1232,26 @@ static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, sndcmd = 0; } + /* If in chip ECC mode, need to read the status + to see if an ECC error occurred. */ + if (chip->ecc.mode == NAND_ECC_CHIP) { + int status; + chip->cmd_ctrl(mtd, NAND_CMD_STATUS, + NAND_CTRL_CLE | NAND_CTRL_CHANGE); + chip->cmd_ctrl(mtd, + NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); + status = chip->read_byte(mtd); + chip->cmd_ctrl(mtd, NAND_CMD_READ0, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + chip->cmd_ctrl(mtd, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + + if (status & 0x1) + mtd->ecc_stats.failed++; + else if (status & 0x10) + mtd->ecc_stats.corrected++; + } + /* Now read the page into the buffer */ if (unlikely(ops->mode == MTD_OOB_RAW)) ret = chip->ecc.read_page_raw(mtd, chip, bufpoi); @@ -1420,6 +1455,8 @@ static int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); status = chip->waitfunc(mtd, chip); + if ((~status) & NAND_STATUS_WP) + return -EPERM; return status & NAND_STATUS_FAIL ? -EIO : 0; } @@ -1479,6 +1516,8 @@ static int nand_write_oob_syndrome(struct mtd_info *mtd, chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); status = chip->waitfunc(mtd, chip); + if ((~status) & NAND_STATUS_WP) + return -EPERM; return status & NAND_STATUS_FAIL ? -EIO : 0; } @@ -1768,6 +1807,11 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, * See if operation failed and additional status checks are * available */ + if ((~status) & NAND_STATUS_WP) { + printf("%s: failed to write to write-protected page\n", __FUNCTION__); + return -EPERM; + } + if ((status & NAND_STATUS_FAIL) && (chip->errstat)) status = chip->errstat(mtd, chip, FL_WRITING, status, page); @@ -1777,6 +1821,10 @@ static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip, } else { chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1); status = chip->waitfunc(mtd, chip); + if ((~status) & NAND_STATUS_WP) { + printf("%s: failed to write to write-protected page\n", __FUNCTION__); + return -EPERM; + } } #ifdef CONFIG_MTD_NAND_VERIFY_WRITE @@ -2005,7 +2053,7 @@ static int nand_do_write_oob(struct mtd_info *mtd, loff_t to, /* Do not allow write past end of page */ if ((ops->ooboffs + ops->ooblen) > len) { MTDDEBUG (MTD_DEBUG_LEVEL0, "nand_write_oob: " - "Attempt to write past end of page\n"); + "Attempt to write past end of page (%d+%d>%d)\n", ops->ooboffs, ops->ooblen, len); return -EINVAL; } @@ -2249,7 +2297,7 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, chip->erase_cmd(mtd, page & chip->pagemask); status = chip->waitfunc(mtd, chip); - + /* * See if operation failed and additional status checks are * available @@ -2258,6 +2306,14 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, status = chip->errstat(mtd, chip, FL_ERASING, status, page); + /* If block is write-protected, skip to next */ + if ((~status) & NAND_STATUS_WP) { + printk(KERN_WARNING "nand_erase: attempt to erase a " + "write-protected block at page 0x%08x\n", page); + instr->state = MTD_ERASE_FAILED; + goto erase_exit; + } + /* See if block erase succeeded */ if (status & NAND_STATUS_FAIL) { MTDDEBUG (MTD_DEBUG_LEVEL0, "nand_erase: " @@ -2502,6 +2558,9 @@ static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd, return ERR_PTR(-ENODEV); } + chip->maf_id = tmp_manf; + chip->dev_id = tmp_id; + /* Lookup the flash id */ for (i = 0; nand_flash_ids[i].name != NULL; i++) { if (dev_id == nand_flash_ids[i].id) { @@ -2777,6 +2836,26 @@ int nand_scan_tail(struct mtd_info *mtd) chip->ecc.write_oob = nand_write_oob_std; chip->ecc.size = 256; chip->ecc.bytes = 3; + if (chip->has_chip_ecc) { + /* Put chip into no-ECC mode */ + uint8_t params[4] = {0x00, 0x00, 0x00, 0x00}; + nand_set_feature(mtd, 0x90, params); + } + break; + + case NAND_ECC_CHIP: + chip->ecc.read_page = nand_read_page_raw; + chip->ecc.write_page = nand_write_page_raw; + chip->ecc.read_oob = nand_read_oob_std; + chip->ecc.write_oob = nand_write_oob_std; + chip->ecc.size = mtd->writesize; + chip->ecc.bytes = 0; + + if (chip->has_chip_ecc) { + /* Put chip into ECC mode */ + uint8_t params[4] = {0x08, 0x00, 0x00, 0x00}; + nand_set_feature(mtd, 0x90, params); + } break; case NAND_ECC_NONE: @@ -2788,6 +2867,13 @@ int nand_scan_tail(struct mtd_info *mtd) chip->ecc.write_oob = nand_write_oob_std; chip->ecc.size = mtd->writesize; chip->ecc.bytes = 0; + + if (chip->has_chip_ecc) { + /* Put chip into ECC mode */ + uint8_t params[4] = {0x00, 0x00, 0x00, 0x00}; + nand_set_feature(mtd, 0x90, params); + } + break; default: @@ -2805,6 +2891,7 @@ int nand_scan_tail(struct mtd_info *mtd) chip->ecc.layout->oobavail += chip->ecc.layout->oobfree[i].length; mtd->oobavail = chip->ecc.layout->oobavail; + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s: oobavail %d\n", __FUNCTION__, mtd->oobavail); /* * Set the number of read / write steps for one page depending on ECC diff --git a/drivers/mtd/nand/nand_util.c b/drivers/mtd/nand/nand_util.c index c866c1210c3..7db9b66985f 100644 --- a/drivers/mtd/nand/nand_util.c +++ b/drivers/mtd/nand/nand_util.c @@ -339,6 +339,7 @@ int nand_lock(struct mtd_info *mtd, int tight) int nand_get_lock_status(struct mtd_info *mtd, loff_t offset) { int ret = 0; + int flags = 0; int chipnr; int page; struct nand_chip *chip = mtd->priv; @@ -360,9 +361,19 @@ int nand_get_lock_status(struct mtd_info *mtd, loff_t offset) page = (int)(offset >> chip->page_shift); chip->cmdfunc(mtd, NAND_CMD_LOCK_STATUS, -1, page & chip->pagemask); - ret = chip->read_byte(mtd) & (NAND_LOCK_STATUS_TIGHT - | NAND_LOCK_STATUS_LOCK - | NAND_LOCK_STATUS_UNLOCK); + ret = chip->read_byte(mtd); + + ret &= (NAND_LOCK_STATUS_TIGHT | NAND_LOCK_STATUS_LOCK | NAND_LOCK_STATUS_UNLOCK); + + if (ret & NAND_LOCK_STATUS_TIGHT) + flags |= NAND_LOCK_STATUS_TIGHT; + + if (ret & NAND_LOCK_STATUS_UNLOCK) + flags |= NAND_LOCK_STATUS_UNLOCK; + else + flags |= NAND_LOCK_STATUS_LOCK; + + ret = flags; out: /* de-select the NAND device */ @@ -370,6 +381,32 @@ int nand_get_lock_status(struct mtd_info *mtd, loff_t offset) return ret; } +int nand_get_features(struct mtd_info *mtd, uint8_t faddr, uint8_t *features) +{ + struct nand_chip *chip = mtd->priv; + int i; + + chip->select_chip(mtd, 0); + + /* Send the status command */ + chip->cmd_ctrl(mtd, NAND_CMD_GET_FEATURES, NAND_CTRL_CHANGE | NAND_CTRL_CLE); + + /* Send the feature address */ + chip->cmd_ctrl(mtd, faddr, NAND_CTRL_CHANGE | NAND_CTRL_ALE); + /* Switch to data access */ + chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_CTRL_CHANGE | NAND_NCE); + + ndelay(100); + + for (i=0; i<5; ++i) + features[i] = chip->read_byte(mtd); + +#if 0 + printf("%s: %02x %02x %02x %02x %02x\n", __FUNCTION__, features[0], + features[1], features[2], features[3], features[4]); +#endif + return 0; +} /** * nand_unlock: - Unlock area of NAND pages * only one consecutive area can be unlocked at one time! @@ -388,13 +425,19 @@ int nand_unlock(struct mtd_info *mtd, ulong start, ulong length) int status; int page; struct nand_chip *chip = mtd->priv; +#if 0 printf ("nand_unlock: start: %08x, length: %d!\n", (int)start, (int)length); - +#endif /* select the NAND device */ chipnr = (int)(start >> chip->chip_shift); chip->select_chip(mtd, chipnr); + /* Check the WP bit. To do so requires resetting the device to + force the status back to its reset value (so WP becomes whether + the WP pin is set). */ + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + /* check the WP bit */ chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); if (!(chip->read_byte(mtd) & NAND_STATUS_WP)) { @@ -732,7 +775,11 @@ int nand_write_opts(nand_info_t *meminfo, const nand_write_options_t *opts) .ooblen = meminfo->ecclayout->oobavail, .oobretlen = 0, .ooboffs = 0, +#if 1 + .datbuf = data_buf, +#else .datbuf = NULL, +#endif .oobbuf = oob_buf, }; @@ -744,19 +791,20 @@ int nand_write_opts(nand_info_t *meminfo, const nand_write_options_t *opts) goto restoreoob; } imglen -= meminfo->oobsize; - } + } else { - /* write out the page data */ - result = meminfo->write(meminfo, - mtdoffset, - meminfo->writesize, - &written, - (unsigned char *) &data_buf); + /* write out the page data */ + result = meminfo->write(meminfo, + mtdoffset, + meminfo->writesize, + &written, + (unsigned char *) &data_buf); - if (result != 0) { - printf("writing NAND page at offset 0x%lx failed\n", - mtdoffset); - goto restoreoob; + if (result != 0) { + printf("writing NAND page at offset 0x%lx failed\n", + mtdoffset); + goto restoreoob; + } } imglen -= readlen; diff --git a/drivers/mtd/nand/omap_gpmc.c b/drivers/mtd/nand/omap_gpmc.c index 5624b066771..9590ed920f9 100644 --- a/drivers/mtd/nand/omap_gpmc.c +++ b/drivers/mtd/nand/omap_gpmc.c @@ -26,11 +26,13 @@ #include <asm/errno.h> #include <asm/arch/mem.h> #include <asm/arch/omap_gpmc.h> +#include <asm/arch/sys_proto.h> #include <linux/mtd/nand_ecc.h> #include <nand.h> static uint8_t cs; static struct nand_ecclayout hw_nand_oob = GPMC_NAND_HW_ECC_LAYOUT; +static struct nand_ecclayout chip_nand_oob = GPMC_NAND_CHIP_ECC_LAYOUT; /* * omap_nand_hwcontrol - Set the address pointers corretly for the @@ -158,6 +160,25 @@ static int omap_correct_data(struct mtd_info *mtd, uint8_t *dat, return 0; } +static int omap_correct_chip_hwecc(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + struct nand_chip *chip; + + chip = mtd->priv; + + printf("%s: ecc_status %02x\n", __func__, chip->ecc_status); + /* We stored the read status in info->ecc_status in the read. + If bit 0 is set, then there was an uncorrectable ECC error. + If bit 3 is set, then there was a correctable error (up to + four bits of correction). */ + if (chip->ecc_status & 0x01) + return -1; + if (chip->ecc_status & 0x08) + return 4; + return 0; +} + /* * omap_calculate_ecc - Generate non-inverted ECC bytes. * @@ -194,6 +215,13 @@ static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, return 0; } +static int omap_calculate_chip_hwecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s:\n", __func__); + return 0; +} + /* * omap_enable_ecc - This function enables the hardware ecc functionality * @mtd: MTD device structure @@ -226,14 +254,335 @@ static void omap_enable_hwecc(struct mtd_info *mtd, int32_t mode) } } +static void omap_enable_chip_hwecc(struct mtd_info *mtd, int mode) +{ + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s:\n", __func__); +} + +/* + * omap_nand_chip_has_ecc - return true if chip has internal ECC + */ +int omap_nand_chip_has_ecc(void) +{ + struct nand_chip *chip; + struct mtd_info *mtd; + int i; + uint8_t ident[5]; + + if (nand_curr_device < 0 || + nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE || + !nand_info[nand_curr_device].name) { + printf("Error: Can't switch ecc, no devices available\n"); + return 0; + } + + mtd = &nand_info[nand_curr_device]; + chip = mtd->priv; + +#if 1 + chip->select_chip(mtd, 0); + chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); + + /* Wait for the chip to get the ID ready */ + ndelay(100); + + for (i=0; i<2; ++i) + ident[i] = chip->read_byte(mtd); + + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s:%d %02x %02x\n", __FUNCTION__, __LINE__, ident[0], ident[1]); + if (ident[0] == NAND_MFR_MICRON) { + for (i=2; i<5; ++i) + ident[i] = chip->read_byte(mtd); + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s:%d %02x %02x %02x\n", __FUNCTION__, __LINE__, ident[2], ident[3], ident[4]); + if (ident[4] & 0x3) + chip->has_chip_ecc = 1; + } + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s: has_chip_ecc %d\n", __FUNCTION__, chip->has_chip_ecc); +#else + if (nand->maf_id == NAND_MFR_MICRON) { + switch(nand->dev_id) { + case 0x2c: + case 0xdc: + case 0xcc: + case 0xac: + case 0xbc: + case 0xa3: + case 0xb3: + case 0xd3: + case 0xc3: + nand->has_chip_ecc = 1; + return 1; + default: + break; + } + } +#endif + return chip->has_chip_ecc; +} + +/* + * omap_nand_set_features - set the features in the chip + * @faddr - features address + * @features - array of byts to set as features + */ +static void micron_set_features(struct mtd_info *mtd, uint8_t faddr, uint8_t *features) +{ + struct nand_chip *chip; + + chip = mtd->priv; + + chip->select_chip(mtd, 0); + + /* Send the status command */ + omap_nand_hwcontrol(mtd, NAND_CMD_SET_FEATURES, NAND_CTRL_CHANGE | NAND_CTRL_CLE); + /* Send the feature address */ + omap_nand_hwcontrol(mtd, faddr, NAND_CTRL_CHANGE | NAND_CTRL_ALE); + /* Switch to data access */ + omap_nand_hwcontrol(mtd, NAND_CMD_NONE, NAND_CTRL_CHANGE | NAND_NCE); + + ndelay(100); + if (chip->options & NAND_BUSWIDTH_16) { + uint16_t ftrs16[4]; + int i; + for (i=0; i<4; ++i) + ftrs16[i] = features[i]; + chip->write_buf(mtd, (uint8_t *)ftrs16, sizeof(ftrs16)); + } else + chip->write_buf(mtd, features, 4); + + udelay(2); + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s: faddr %02x [%02x %02x %02x %02x]\n", __FUNCTION__, faddr, features[0], features[1], features[2], features[3]); +} + +static void micron_set_chip_ecc(struct mtd_info *mtd, int enable) +{ + uint8_t params[4]; + + MTDDEBUG(MTD_DEBUG_LEVEL3,"%s:%d enable %d\n", __FUNCTION__, __LINE__, enable); + + memset(params, 0x00, sizeof(params)); + if (enable) + params[0] = 0x08; + micron_set_features(mtd, 0x90, params); + +#if 0 + micron_get_features(mtd, 0x90, params); +#endif + MTDDEBUG(MTD_DEBUG_LEVEL3,"%s: %02x %02x %02x %02x\n", __FUNCTION__, params[0], params[1], params[2], params[3]); +} + +/** + * nand_read_oob_std - [REPLACABLE] the most common OOB data read function + * @mtd: mtd info structure + * @chip: nand chip info structure + * @page: page number to read + * @sndcmd: flag whether to issue read command or not + */ +static int omap_read_oob_chipecc(struct mtd_info *mtd, struct nand_chip *chip, + int page, int sndcmd) +{ + struct nand_chip *nand; + + nand = mtd->priv; + + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s: page = %d, len = %i\n", + __func__, page, mtd->oobsize); + + if (sndcmd) { + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page); + sndcmd = 0; + } + + /* Send the status command */ + omap_nand_hwcontrol(mtd, NAND_CMD_STATUS, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + /* Switch to data access */ + omap_nand_hwcontrol(mtd, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + chip->ecc_status = chip->read_byte(mtd); + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s: ecc_status %02x\n", __func__, chip->ecc_status); + if (chip->ecc_status & (0x8|0x1)) { + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s:%d page %d ecc_status %02x\n", __FUNCTION__, __LINE__, page, chip->ecc_status); + if (chip->ecc_status & 0x1) + mtd->ecc_stats.failed++; + else if (chip->ecc_status & 0x80) + mtd->ecc_stats.corrected += 4; + } + + /* Send the read prefix */ + omap_nand_hwcontrol(mtd, NAND_CMD_READ0, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + /* Switch to data access */ + omap_nand_hwcontrol(mtd, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); + return sndcmd; +} + +/** + * omap_nand_command_lp - Send command to NAND large page device + * @mtd: MTD device structure + * @command: the command to be sent + * @column: the column address for this command, -1 if none + * @page_addr: the page address for this command, -1 if none + * + * Send command to NAND device. This is the version for the new large page + * devices We dont have the separate regions as we have in the small page + * devices. We must emulate NAND_CMD_READOOB to keep the code compatible. + */ +static void omap_nand_command_lp(struct mtd_info *mtd, unsigned int command, + int column, int page_addr) +{ + register struct nand_chip *chip = mtd->priv; + + /* Emulate NAND_CMD_READOOB */ + if (command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; + } + + /* Command latch cycle */ + omap_nand_hwcontrol(mtd, command & 0xff, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + + if (column != -1 || page_addr != -1) { + int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE; + + /* Serially input address */ + if (column != -1) { + /* Adjust columns for 16 bit buswidth */ + if (chip->options & NAND_BUSWIDTH_16) + column >>= 1; + omap_nand_hwcontrol(mtd, column, ctrl); + ctrl &= ~NAND_CTRL_CHANGE; + omap_nand_hwcontrol(mtd, column >> 8, ctrl); + } + if (page_addr != -1) { + omap_nand_hwcontrol(mtd, page_addr, ctrl); + omap_nand_hwcontrol(mtd, page_addr >> 8, + NAND_NCE | NAND_ALE); + /* One more address cycle for devices > 128MiB */ + if (chip->chipsize > (128 << 20)) + omap_nand_hwcontrol(mtd, page_addr >> 16, + NAND_NCE | NAND_ALE); + } + } + omap_nand_hwcontrol(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE); + + /* + * program and erase have their own busy handlers + * status, sequential in, and deplete1 need no delay + */ + switch (command) { + + case NAND_CMD_CACHEDPROG: + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_RNDIN: + case NAND_CMD_STATUS: + case NAND_CMD_DEPLETE1: + return; + + /* + * read error status commands require only a short delay + */ + case NAND_CMD_STATUS_ERROR: + case NAND_CMD_STATUS_ERROR0: + case NAND_CMD_STATUS_ERROR1: + case NAND_CMD_STATUS_ERROR2: + case NAND_CMD_STATUS_ERROR3: + udelay(chip->chip_delay); + return; + + case NAND_CMD_RESET: + if (chip->dev_ready) + break; + udelay(chip->chip_delay); + omap_nand_hwcontrol(mtd, NAND_CMD_STATUS, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + omap_nand_hwcontrol(mtd, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) ; + return; + + case NAND_CMD_RNDOUT: + /* No ready / busy check necessary */ + omap_nand_hwcontrol(mtd, NAND_CMD_RNDOUTSTART, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + omap_nand_hwcontrol(mtd, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + return; + + case NAND_CMD_READ0: + + /* Send the read start */ + omap_nand_hwcontrol(mtd, NAND_CMD_READSTART, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + omap_nand_hwcontrol(mtd, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + + /* This applies to read commands */ + default: + /* + * If we don't have access to the busy pin, we apply the given + * command delay + */ + if (!chip->dev_ready) { + udelay(chip->chip_delay); + goto ready_exit; + } + } + + /* Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. */ + ndelay(100); + + nand_wait_ready(mtd); + +ready_exit: + /* If the chip has internal ECC, then we need to read the status + to determin if there's an ECC error - capture it for handling by + omap_nand_correct_chip_hwecc() later */ + if (command == NAND_CMD_READ0) { + if (chip->has_chip_ecc) { + + /* Send the status command */ + omap_nand_hwcontrol(mtd, NAND_CMD_STATUS, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + /* Switch to data access */ + omap_nand_hwcontrol(mtd, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + chip->ecc_status = chip->read_byte(mtd); + MTDDEBUG(MTD_DEBUG_LEVEL3, "%s: ecc_status %02x\n", __func__, chip->ecc_status); +#if 0 + if (chip->ecc_status & (0x8|0x1)) + printk("%s:%d page %d column %d ecc_status %02x\n", __FUNCTION__, __LINE__, page_addr, column, chip->ecc_status); +#endif + + /* Send the read prefix */ + omap_nand_hwcontrol(mtd, NAND_CMD_READ0, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + /* Switch to data access */ + omap_nand_hwcontrol(mtd, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + + } + } + +} + /* * omap_nand_switch_ecc - switch the ECC operation b/w h/w ecc and s/w ecc. * The default is to come up on s/w ecc * - * @hardware - 1 -switch to h/w ecc, 0 - s/w ecc + * @hardware - 1 -switch to h/w ecc, 0 - s/w ecc, 2 - chip ecc, * */ -void omap_nand_switch_ecc(int32_t hardware) +void omap_nand_switch_ecc(enum omap_nand_ecc_mode mode) { struct nand_chip *nand; struct mtd_info *mtd; @@ -260,7 +609,7 @@ void omap_nand_switch_ecc(int32_t hardware) nand->ecc.calculate = NULL; /* Setup the ecc configurations again */ - if (hardware) { + if (mode == OMAP_ECC_HW) { nand->ecc.mode = NAND_ECC_HW; nand->ecc.layout = &hw_nand_oob; nand->ecc.size = 512; @@ -269,12 +618,38 @@ void omap_nand_switch_ecc(int32_t hardware) nand->ecc.correct = omap_correct_data; nand->ecc.calculate = omap_calculate_ecc; omap_hwecc_init(nand); + if (nand->has_chip_ecc) + micron_set_chip_ecc(mtd, 0); printf("NAND: HW ECC selected\n"); - } else { + } else if (mode == OMAP_ECC_SOFT) { nand->ecc.mode = NAND_ECC_SOFT; /* Use mtd default settings */ nand->ecc.layout = NULL; printf("NAND: SW ECC selected\n"); + if (nand->has_chip_ecc) + micron_set_chip_ecc(mtd, 0); + } else if (mode == OMAP_ECC_CHIP) { + if (!nand->has_chip_ecc) { + printf("NAND: Chip does not have internal ECC!\n"); + return; + } + nand->ecc.bytes = 0; + nand->ecc.size = 2048; + nand->ecc.calculate = omap_calculate_chip_hwecc; + nand->ecc.hwctl = omap_enable_chip_hwecc; + nand->ecc.correct = omap_correct_chip_hwecc; + nand->ecc.read_oob = omap_read_oob_chipecc; + nand->ecc.mode = NAND_ECC_CHIP; /* internal to chip */ + nand->ecc.layout = &chip_nand_oob; + if (nand->options & NAND_BUSWIDTH_16) + nand->cmdfunc = omap_nand_command_lp; + else + printf("%s: Huh? not 16-bit wide\n", __FUNCTION__); + micron_set_chip_ecc(mtd, 1); + printf("NAND: Internal to NAND ECC selected\n"); + } else { + printf("NAND: unknown ECC mode %d\n", mode); + return; } /* Update NAND handling after ECC mode switch */ |