summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorDavid Ungar <david.ungar@timesys.com>2010-10-11 14:24:08 -0400
committerDavid Ungar <david.ungar@timesys.com>2010-10-11 14:39:23 -0400
commita718de3f42a6280fefd8b60b3e4733e015b0f637 (patch)
tree1b0c5b36cd7fb408e8119d56c542f86903cc56d5 /drivers
parent411a2e5823bf4280d70d5b9e9ea683e27b09f7b2 (diff)
Additional NAND support
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mtd/mtdcore.c4
-rw-r--r--drivers/mtd/nand/nand_base.c93
-rw-r--r--drivers/mtd/nand/nand_util.c78
-rw-r--r--drivers/mtd/nand/omap_gpmc.c383
4 files changed, 536 insertions, 22 deletions
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 6eb52ed50c..7bc62e6f86 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 360b070849..1f3a638748 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 c866c1210c..7db9b66985 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 5624b06677..9590ed920f 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 */