From 1084c21e6bdf703df2299df8452067d43c79764d Mon Sep 17 00:00:00 2001 From: Manikandan Pillai Date: Wed, 13 Aug 2008 11:25:30 +0530 Subject: [PATCH] OneNAND updated support similar to NAND Signed-off-by: Manikandan Pillai --- board/omap3evm/mem.c | 33 ++++-- common/cmd_onenand.c | 150 +++++++++++++++++++-------- common/env_onenand.c | 1 + drivers/mtd/onenand/onenand_base.c | 201 ++++++++++++++++++++++++++++-------- include/linux/mtd/onenand.h | 1 + include/onenand_uboot.h | 4 + 6 files changed, 294 insertions(+), 96 deletions(-) diff --git a/board/omap3evm/mem.c b/board/omap3evm/mem.c index ba95317..8ad4b33 100644 --- a/board/omap3evm/mem.c +++ b/board/omap3evm/mem.c @@ -246,37 +246,51 @@ void gpmc_init(void) __raw_writel(0, GPMC_CONFIG7 + GPMC_CONFIG_CS0); sdelay(1000); -#if defined(CONFIG_CMD_NAND) - /* CS 0 */ +#if defined(CONFIG_CMD_NAND) /* CS 0 */ gpmc_config = gpmc_m_nand; +#if defined(CFG_ENV_IS_IN_NAND) gpmc_base = GPMC_CONFIG_CS0 + (0 * GPMC_CONFIG_WIDTH); +#else + gpmc_base = GPMC_CONFIG_CS0 + (1 * GPMC_CONFIG_WIDTH); +#endif base = PISMO1_NAND_BASE; size = PISMO1_NAND_SIZE; enable_gpmc_config(gpmc_config, gpmc_base, base, size); - - f_off = SMNAND_ENV_OFFSET; - f_sec = SZ_128K; is_nand = 1; nand_cs_base = gpmc_base; +#if defined(CFG_ENV_IS_IN_NAND) + f_off = SMNAND_ENV_OFFSET; + f_sec = SZ_128K; + /* env setup */ + boot_flash_base = base; + boot_flash_off = f_off; + boot_flash_sec = f_sec; + boot_flash_env_addr = f_off; +#endif #endif #if defined(CONFIG_CMD_ONENAND) gpmc_config = gpmc_onenand; +#if defined(CFG_ENV_IS_IN_ONENAND) gpmc_base = GPMC_CONFIG_CS0 + (0 * GPMC_CONFIG_WIDTH); +#else + gpmc_base = GPMC_CONFIG_CS0 + (1 * GPMC_CONFIG_WIDTH); +#endif base = PISMO1_ONEN_BASE; size = PISMO1_ONEN_SIZE; enable_gpmc_config(gpmc_config, gpmc_base, base, size); - - f_off = (ONENAND_MAP + ONENAND_ENV_OFFSET); - f_sec = SZ_128K; is_onenand = 1; onenand_cs_base = gpmc_base; -#endif +#if defined(CFG_ENV_IS_IN_ONENAND) + f_off = ONENAND_ENV_OFFSET; + f_sec = SZ_128K; /* env setup */ boot_flash_base = base; boot_flash_off = f_off; boot_flash_sec = f_sec; boot_flash_env_addr = f_off; +#endif +#endif #ifdef ENV_IS_VARIABLE boot_env_get_char_spec = env_get_char_spec; @@ -284,5 +298,4 @@ void gpmc_init(void) boot_saveenv = saveenv; boot_env_relocate_spec = env_relocate_spec; #endif - } diff --git a/common/cmd_onenand.c b/common/cmd_onenand.c index d6d3376..5d97c80 100644 --- a/common/cmd_onenand.c +++ b/common/cmd_onenand.c @@ -26,103 +26,144 @@ extern struct onenand_chip onenand_chip; int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[]) { int ret = 0; + loff_t ofs = 0; switch (argc) { case 0: case 1: + /* printf version info if no of arguments are not enough */ printf("Usage:\n%s\n", cmdtp->usage); return 1; case 2: - if (strncmp(argv[1], "open", 4) == 0) { - onenand_init(); + if (strncmp(argv[1], "info", 4) != 0) + printf("ONND: Incorrect command/number of args\n"); + printf("Usage:\n%s\n", cmdtp->usage); + return 1; + + case 3: + if ((strncmp(argv[1], "markbad", 7) == 0) && (argc == 3)) { + int ret ; + + ofs = simple_strtoul(argv[2], NULL, 16); + if (ofs >= onenand_mtd.size) { + printf("Error : offset exceeds size\n"); + return 0; + } else + ret = onenand_block_markbad(&onenand_mtd, ofs); + + if (ret) + printf("Error marking bad-block\n"); + else + printf("Done\n"); return 0; } + printf("ONND : wrong number of arguments\n"); onenand_print_device_info(onenand_chip.device_id, 1); + printf("Usage:\n%s\n", cmdtp->usage); return 0; default: /* At least 4 args */ - if (strncmp(argv[1], "erase", 5) == 0) { + if (((strncmp(argv[1], "erase", 5) == 0) || + (strncmp(argv[1], "scrub", 5) == 0)) && + (argc == 4)) { struct erase_info instr = { .callback = NULL, }; - ulong start, end; - ulong block; + ulong start = 0, end = 0; + ulong block = 0; char *endtail; if (strncmp(argv[2], "block", 5) == 0) { start = simple_strtoul(argv[3], NULL, 10); endtail = strchr(argv[3], '-'); end = simple_strtoul(endtail + 1, NULL, 10); + if (end < start) { + printf("Error : erase failed - "); + printf("end block incorrect\n"); + break; + } } else { start = simple_strtoul(argv[2], NULL, 10); end = simple_strtoul(argv[3], NULL, 10); - start >>= onenand_chip.erase_shift; end >>= onenand_chip.erase_shift; /* Don't include the end block */ end--; - } - - if (!end || end < 0) - end = start; + if (!end || (end < start)) { + printf("Error : erase failed "); + printf("end address incorrect\n"); + break; + } + } printf("Erase block from %lu to %lu\n", start, end); for (block = start; block <= end; block++) { instr.addr = block << onenand_chip.erase_shift; instr.len = 1 << onenand_chip.erase_shift; + if (strncmp(argv[1], "scrub", 5) == 0) + instr.priv = ONENAND_SCRUB; + else + instr.priv = 0; ret = onenand_erase(&onenand_mtd, &instr); - if (ret) { + if (ret) printf("erase failed %lu\n", block); - break; - } } - return 0; - } - - if (strncmp(argv[1], "read", 4) == 0) { + } else if ((strncmp(argv[1], "read", 4) == 0) && + (argc == 5)) { + size_t len = 0, retlen = 0; ulong addr = simple_strtoul(argv[2], NULL, 16); ulong ofs = simple_strtoul(argv[3], NULL, 16); - size_t len = simple_strtoul(argv[4], NULL, 16); - size_t retlen = 0; int oob = strncmp(argv[1], "read.oob", 8) ? 0 : 1; + ofs = simple_strtoul(argv[3], NULL, 16); + len = simple_strtoul(argv[4], NULL, 16); + if (oob) - onenand_read_oob(&onenand_mtd, ofs, len, + ret = onenand_read_oob(&onenand_mtd, ofs, len, &retlen, (u_char *) addr); else - onenand_read(&onenand_mtd, ofs, len, &retlen, - (u_char *) addr); - printf("Done\n"); + ret = onenand_read(&onenand_mtd, ofs, len, + &retlen, (u_char *)addr); + if (ret) + printf("Error reading from device\n"); + else + printf("Done\n"); return 0; - } + } else if ((strncmp(argv[1], "write", 5) == 0) && - if (strncmp(argv[1], "write", 5) == 0) { + (argc == 5)) { + size_t len = 0, retlen = 0; ulong addr = simple_strtoul(argv[2], NULL, 16); - ulong ofs = simple_strtoul(argv[3], NULL, 16); - size_t len = simple_strtoul(argv[4], NULL, 16); - size_t retlen = 0; + ofs = simple_strtoul(argv[3], NULL, 16); + len = simple_strtoul(argv[4], NULL, 16); - onenand_write(&onenand_mtd, ofs, len, &retlen, + ret = onenand_write(&onenand_mtd, ofs, len, &retlen, (u_char *) addr); - printf("Done\n"); + if (ret) + printf("Error writing to device\n"); + else + printf("Done\n"); return 0; - } + } else if (strncmp(argv[1], "block", 5) == 0) { + ulong page = 0; + size_t len = 0; + size_t retlen = 0; - if (strncmp(argv[1], "block", 5) == 0) { ulong addr = simple_strtoul(argv[2], NULL, 16); ulong block = simple_strtoul(argv[3], NULL, 10); - ulong page = simple_strtoul(argv[4], NULL, 10); - size_t len = simple_strtol(argv[5], NULL, 10); - size_t retlen = 0; - ulong ofs; int oob = strncmp(argv[1], "block.oob", 9) ? 0 : 1; + if (argc >= 5) + page = simple_strtoul(argv[4], NULL, 10); + if (argc >= 6) + len = simple_strtol(argv[5], NULL, 10); + ofs = block << onenand_chip.erase_shift; if (page) ofs += page << onenand_chip.page_shift; @@ -135,14 +176,33 @@ int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[]) } if (oob) - onenand_read_oob(&onenand_mtd, ofs, len, + ret = onenand_read_oob(&onenand_mtd, ofs, len, &retlen, (u_char *) addr); else - onenand_read(&onenand_mtd, ofs, len, &retlen, - (u_char *) addr); + ret = onenand_read(&onenand_mtd, ofs, len, + &retlen, (u_char *) addr); + if (ret) + printf("Error reading from device\n"); + else + printf("Done\n"); return 0; - } + } else if (strncmp(argv[1], "unlock", 6) == 0) { + ulong start = simple_strtoul(argv[2], NULL, 10); + + ofs = simple_strtoul(argv[3], NULL, 10); + if (!ofs) + ofs = (1 << onenand_chip.erase_shift); + + start = start << onenand_chip.erase_shift; + onenand_unlock(&onenand_mtd, start, start + ofs); + + return 0; + } else { + printf("ONND : wrong number of arguments\n"); + onenand_print_device_info(onenand_chip.device_id, 1); + printf("Usage:\n%s\n", cmdtp->usage); + } break; } @@ -156,8 +216,14 @@ U_BOOT_CMD( "onenand read[.oob] addr ofs len - read data at ofs with len to addr\n" "onenand write addr ofs len - write data at ofs with len from addr\n" "onenand erase saddr eaddr - erase block start addr to end addr\n" - "onenand block[.oob] addr block [page] [len] - " - "read data with (block [, page]) to addr" + "onenand erase block sblk-endblk - erase blocks sblk to endblk\n" + " ---erase command does not erase bad blocks\n" + "onenand scrub block start-end - erase block from start to end\n" + "onenand scrub saddr eaddr - erase blocks start addr to end addr\n" + " ---CAUTION :scrub command erases bad blocks also\n" + "onenand block[.oob] addr block [page] [len]\n" + " ---read data with (block [, page]) to addr\n" + "onenand markbad ofs - mark bad-block at ofs\n" ); #endif /* CONFIG_CMD_ONENAND */ diff --git a/common/env_onenand.c b/common/env_onenand.c index dbd0883..ecf93c4 100644 --- a/common/env_onenand.c +++ b/common/env_onenand.c @@ -100,6 +100,7 @@ int saveenv(void) instr.len = CFG_ENV_SIZE; instr.addr = env_addr; + if (onenand_erase(&onenand_mtd, &instr)) { printf("OneNAND: erase failed at 0x%08lx\n", env_addr); return 1; diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c index a7054ae..154e4a4 100644 --- a/drivers/mtd/onenand/onenand_base.c +++ b/drivers/mtd/onenand/onenand_base.c @@ -20,6 +20,9 @@ #include #include +int onenand_write_oob(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf); + /* It should access 16-bit instead of 8-bit */ static inline void *memcpy_16(void *dst, const void *src, unsigned int len) { @@ -68,6 +71,86 @@ static void onenand_writew(unsigned short value, void __iomem * addr) } /** + * onenand_block_checkbad - [GENERIC] Check if a block is marked bad + * @param mtd MTD device structure + * @param ofs offset from device start + * @param getchip 0, if the chip is already selected + * @param allowbbt 1, if its allowed to access the bbt area + * + * Check, if the block is bad. Either by reading the bad block table or + * calling of the scan function. + */ +int onenand_block_checkbad(struct mtd_info *mtd, int ofs, int getchip, + int allowbbt) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + + /* Return info from the table */ + return bbm->isbad_bbt(mtd, ofs, allowbbt); +} + +/** + * onenand_block_isbad - [MTD Intf] Chk if the block at the given offs is bad + * @param mtd MTD device structure + * @param ofs offset relative to mtd start + */ +int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs) +{ + /* Check for invalid offset */ + if (ofs > mtd->size) + return -EINVAL; + return onenand_block_checkbad(mtd, ofs, 1, 0); +} + +/** + * onenand_default_block_markbad - [DEFAULT] mark a block bad + * @param mtd MTD device structure + * @param ofs offset from device start + * + * This is the default implementation, which can be overridden by + * a hardware specific driver. + */ +int onenand_default_block_markbad(struct mtd_info *mtd, int ofs) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + u_char buf[2] = {0x0A, 0x0A}; + size_t retlen; + int block; + + /* Get block number */ + block = ((int) ofs) >> bbm->bbt_erase_shift; + if (bbm->bbt) + bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1); + + /* We write two bytes, so we dont have to mess with 16 bit access */ + ofs += mtd->oobsize + (bbm->badblockpos & ~0x01); + return onenand_write_oob(mtd, ofs , 2, &retlen, buf); +} + +/** + * onenand_block_markbad - [MTD Interface] Mark the block at the given offset as bad + * @param mtd MTD device structure + * @param ofs offset relative to mtd start + */ +int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct onenand_chip *this = mtd->priv; + int ret; + + ret = onenand_block_isbad(mtd, ofs); + if (ret) { + /* If it was bad already, return success and do nothing */ + if (ret > 0) + return 0; + return ret; + } + return this->block_markbad(mtd, ofs); +} + + +/** * onenand_block_address - [DEFAULT] Get block address * @param device the device id * @param block the block @@ -546,16 +629,31 @@ static int onenand_read_ecc(struct mtd_info *mtd, loff_t from, size_t len, if (column + thislen > mtd->oobblock) thislen = mtd->oobblock - column; + if (onenand_block_isbad(mtd, from) > 0) { + MTDDEBUG(MTD_DEBUG_LEVEL0, "onenand_read_ecc: \ + detected bad block @0x%x, skipping\n", from); + from += mtd->erasesize; + continue; + } if (!onenand_check_bufferram(mtd, from)) { this->command(mtd, ONENAND_CMD_READ, from, - mtd->oobblock); + mtd->oobblock); ret = this->wait(mtd, FL_READING); /* First copy data and check return value for ECC handling */ onenand_update_bufferram(mtd, from, 1); } this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, - thislen); + thislen); + + if (ret) { + MTDDEBUG(MTD_DEBUG_LEVEL0, "onenand_read_ecc \ + :read failed = %d\n", ret); + MTDDEBUG(MTD_DEBUG_LEVEL0, "block @ 0x%x \ + has gone bad\n", from); + onenand_block_markbad(mtd, from); + goto out; + } read += thislen; if (read == len) @@ -571,6 +669,7 @@ static int onenand_read_ecc(struct mtd_info *mtd, loff_t from, size_t len, buf += thislen; } +out: /* Deselect and wake up anyone waiting on the device */ onenand_release_device(mtd); @@ -751,6 +850,11 @@ static int onenand_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(len))) { MTDDEBUG (MTD_DEBUG_LEVEL0, "onenand_write_ecc: " "Attempt to write not page aligned data\n"); + } + + if (unlikely(NOTALIGNED(len))) { + MTDDEBUG(MTD_DEBUG_LEVEL0, "onenand_write_ecc: \ + Attempt to write not page aligned size\n"); return -EINVAL; } @@ -761,6 +865,13 @@ static int onenand_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, while (written < len) { int thislen = min_t(int, mtd->oobblock, len - written); + if (onenand_block_isbad(mtd, to) > 0) { + MTDDEBUG(MTD_DEBUG_LEVEL0, "onenand_write_ecc: \ + detected bad block @0x%x, skipping\n", to); + to += mtd->erasesize; + continue; + } + this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobblock); this->write_bufferram(mtd, ONENAND_DATARAM, buf, 0, thislen); @@ -773,9 +884,11 @@ static int onenand_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, ret = this->wait(mtd, FL_WRITING); if (ret) { - MTDDEBUG (MTD_DEBUG_LEVEL0, - "onenand_write_ecc: write filaed %d\n", ret); - break; + MTDDEBUG(MTD_DEBUG_LEVEL0, + "onenand_write_ecc: write filaed %d\n", ret); + MTDDEBUG(MTD_DEBUG_LEVEL0, "block @ 0x%x is bad\n", to); + onenand_block_markbad(mtd, to); + goto out; } written += thislen; @@ -783,9 +896,11 @@ static int onenand_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, /* Only check verify write turn on */ ret = onenand_verify_page(mtd, (u_char *) buf, to); if (ret) { - MTDDEBUG (MTD_DEBUG_LEVEL0, - "onenand_write_ecc: verify failed %d\n", ret); - break; + MTDDEBUG(MTD_DEBUG_LEVEL0, + "onenand_write_ecc: verify failed %d\n", ret); + MTDDEBUG(MTD_DEBUG_LEVEL0, "block @ 0x%x is bad\n", to); + onenand_block_markbad(mtd, to); + goto out; } if (written == len) @@ -795,11 +910,11 @@ static int onenand_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, buf += thislen; } +out: /* Deselect and wake up anyone waiting on the device */ onenand_release_device(mtd); *retlen = written; - return ret; } @@ -873,7 +988,7 @@ int onenand_write_oob(struct mtd_info *mtd, loff_t to, size_t len, status = this->wait(mtd, FL_WRITING); if (status) - break; + goto out; written += thislen; if (written == len) @@ -883,6 +998,7 @@ int onenand_write_oob(struct mtd_info *mtd, loff_t to, size_t len, buf += thislen; } +out: /* Deselect and wake up anyone waiting on the device */ onenand_release_device(mtd); @@ -945,10 +1061,31 @@ int onenand_erase(struct mtd_info *mtd, struct erase_info *instr) while (len) { - /* TODO Check badblock */ - - this->command(mtd, ONENAND_CMD_ERASE, addr, block_size); - + if (instr->priv == ONENAND_SCRUB) { + if (onenand_block_isbad(mtd, addr) != 0x3) { + /* erase user marked bad blocks only */ + this->command(mtd, ONENAND_CMD_ERASE, addr, block_size); + } else { + /* skip the factory marked bad blocks */ + MTDDEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: \ + not erasing factory bad block @0x%x\n", addr); + len -= block_size; + addr += block_size; + continue; + } + } else { + if (onenand_block_isbad(mtd, addr) == 0) { + /* block is not a known bad block. Try to erase it */ + this->command(mtd, ONENAND_CMD_ERASE, addr, block_size); + } else { + /* skip the user and factory bad blocks */ + MTDDEBUG(MTD_DEBUG_LEVEL0, "onenand_erase: \ + not erasing bad block @0x%x\n", addr); + len -= block_size; + addr += block_size; + continue; + } + } ret = this->wait(mtd, FL_ERASING); /* Check, if it is write protected */ if (ret) { @@ -973,6 +1110,8 @@ int onenand_erase(struct mtd_info *mtd, struct erase_info *instr) erase_exit: ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO; + /* Deselect and wake up anyone waiting on the device */ + onenand_release_device(mtd); /* Do call back function */ if (!ret) mtd_erase_callback(instr); @@ -1001,36 +1140,6 @@ void onenand_sync(struct mtd_info *mtd) } /** - * onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad - * @param mtd MTD device structure - * @param ofs offset relative to mtd start - */ -int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs) -{ - /* - * TODO - * 1. Bad block table (BBT) - * -> using NAND BBT to support JFFS2 - * 2. Bad block management (BBM) - * -> bad block replace scheme - * - * Currently we do nothing - */ - return 0; -} - -/** - * onenand_block_markbad - [MTD Interface] Mark the block at the given offset as bad - * @param mtd MTD device structure - * @param ofs offset relative to mtd start - */ -int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs) -{ - /* see above */ - return 0; -} - -/** * onenand_unlock - [MTD Interface] Unlock block(s) * @param mtd MTD device structure * @param ofs offset relative to mtd start @@ -1271,6 +1380,9 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) if (!this->write_bufferram) this->write_bufferram = onenand_write_bufferram; + if (!this->block_markbad) + this->block_markbad = onenand_default_block_markbad; + if (onenand_probe(mtd)) return -ENXIO; @@ -1291,6 +1403,7 @@ int onenand_scan(struct mtd_info *mtd, int maxchips) */ void onenand_release(struct mtd_info *mtd) { + return; } #endif /* CONFIG_CMD_ONENAND */ diff --git a/include/linux/mtd/onenand.h b/include/linux/mtd/onenand.h index 4b0c2df..9137e99 100644 --- a/include/linux/mtd/onenand.h +++ b/include/linux/mtd/onenand.h @@ -103,6 +103,7 @@ struct onenand_chip { unsigned short (*read_word) (void __iomem * addr); void (*write_word) (unsigned short value, void __iomem * addr); void (*mmcontrol) (struct mtd_info * mtd, int sync_read); + int (*block_markbad)(struct mtd_info *mtd, int ofs); spinlock_t chip_lock; wait_queue_head_t wq; diff --git a/include/onenand_uboot.h b/include/onenand_uboot.h index 4449f98..89203b6 100644 --- a/include/onenand_uboot.h +++ b/include/onenand_uboot.h @@ -16,6 +16,8 @@ #include +#define ONENAND_SCRUB 0x10 + struct kvec { void *iov_base; size_t iov_len; @@ -41,4 +43,6 @@ extern int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len); extern void onenand_print_device_info(int device, int verbose); +extern int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs); + #endif /* __UBOOT_ONENAND_H */ -- 1.5.6