diff options
Diffstat (limited to 'common/cmd_onenand.c')
| -rw-r--r-- | common/cmd_onenand.c | 530 | 
1 files changed, 420 insertions, 110 deletions
| diff --git a/common/cmd_onenand.c b/common/cmd_onenand.c index 8d87b787f..5832ff8d3 100644 --- a/common/cmd_onenand.c +++ b/common/cmd_onenand.c @@ -1,7 +1,7 @@  /*   *  U-Boot command for OneNAND support   * - *  Copyright (C) 2005-2007 Samsung Electronics + *  Copyright (C) 2005-2008 Samsung Electronics   *  Kyungmin Park <kyungmin.park@samsung.com>   *   * This program is free software; you can redistribute it and/or modify @@ -11,6 +11,7 @@  #include <common.h>  #include <command.h> +#include <malloc.h>  #include <linux/mtd/compat.h>  #include <linux/mtd/mtd.h> @@ -18,159 +19,468 @@  #include <asm/io.h> -extern struct mtd_info onenand_mtd; -extern struct onenand_chip onenand_chip; +static struct mtd_info *mtd; + +static loff_t next_ofs; +static loff_t skip_ofs; + +static inline int str2long(char *p, ulong *num) +{ +	char *endptr; + +	*num = simple_strtoul(p, &endptr, 16); +	return (*p != '\0' && *endptr == '\0') ? 1 : 0; +} + +static int arg_off_size(int argc, char *argv[], ulong *off, size_t *size) +{ +	if (argc >= 1) { +		if (!(str2long(argv[0], off))) { +			printf("'%s' is not a number\n", argv[0]); +			return -1; +		} +	} else { +		*off = 0; +	} + +	if (argc >= 2) { +		if (!(str2long(argv[1], (ulong *)size))) { +			printf("'%s' is not a number\n", argv[1]); +			return -1; +		} +	} else { +		*size = mtd->size - *off; +	} + +	if ((*off + *size) > mtd->size) { +		printf("total chip size (0x%x) exceeded!\n", mtd->size); +		return -1; +	} + +	if (*size == mtd->size) +		puts("whole chip\n"); +	else +		printf("offset 0x%lx, size 0x%x\n", *off, *size); + +	return 0; +} + +static int onenand_block_read(loff_t from, size_t len, +			      size_t *retlen, u_char *buf, int oob) +{ +	struct onenand_chip *this = mtd->priv; +	int blocks = (int) len >> this->erase_shift; +	int blocksize = (1 << this->erase_shift); +	loff_t ofs = from; +	struct mtd_oob_ops ops = { +		.retlen		= 0, +	}; +	int ret; + +	if (oob) +		ops.ooblen = blocksize; +	else +		ops.len = blocksize; + +	while (blocks) { +		ret = mtd->block_isbad(mtd, ofs); +		if (ret) { +			printk("Bad blocks %d at 0x%x\n", +			       (u32)(ofs >> this->erase_shift), (u32)ofs); +			ofs += blocksize; +			continue; +		} + +		if (oob) +			ops.oobbuf = buf; +		else +			ops.datbuf = buf; + +		ops.retlen = 0; +		ret = mtd->read_oob(mtd, ofs, &ops); +		if (ret) { +			printk("Read failed 0x%x, %d\n", (u32)ofs, ret); +			ofs += blocksize; +			continue; +		} +		ofs += blocksize; +		buf += blocksize; +		blocks--; +		*retlen += ops.retlen; +	} + +	return 0; +} + +static int onenand_block_write(loff_t to, size_t len, +			       size_t *retlen, const u_char * buf) +{ +	struct onenand_chip *this = mtd->priv; +	int blocks = len >> this->erase_shift; +	int blocksize = (1 << this->erase_shift); +	loff_t ofs; +	size_t _retlen = 0; +	int ret; + +	if (to == next_ofs) { +		next_ofs = to + len; +		to += skip_ofs; +	} else { +		next_ofs = to + len; +		skip_ofs = 0; +	} +	ofs = to; + +	while (blocks) { +		ret = mtd->block_isbad(mtd, ofs); +		if (ret) { +			printk("Bad blocks %d at 0x%x\n", +			       (u32)(ofs >> this->erase_shift), (u32)ofs); +			skip_ofs += blocksize; +			goto next; +		} + +		ret = mtd->write(mtd, ofs, blocksize, &_retlen, buf); +		if (ret) { +			printk("Write failed 0x%x, %d", (u32)ofs, ret); +			skip_ofs += blocksize; +			goto next; +		} + +		buf += blocksize; +		blocks--; +		*retlen += _retlen; +next: +		ofs += blocksize; +	} + +	return 0; +} + +static int onenand_block_erase(u32 start, u32 size, int force) +{ +	struct onenand_chip *this = mtd->priv; +	struct erase_info instr = { +		.callback	= NULL, +	}; +	loff_t ofs; +	int ret; +	int blocksize = 1 << this->erase_shift; + +	for (ofs = start; ofs < (start + size); ofs += blocksize) { +		ret = mtd->block_isbad(mtd, ofs); +		if (ret && !force) { +			printf("Skip erase bad block %d at 0x%x\n", +			       (u32)(ofs >> this->erase_shift), (u32)ofs); +			continue; +		} + +		instr.addr = ofs; +		instr.len = blocksize; +		instr.priv = force; +		instr.mtd = mtd; +		ret = mtd->erase(mtd, &instr); +		if (ret) { +			printf("erase failed block %d at 0x%x\n", +			       (u32)(ofs >> this->erase_shift), (u32)ofs); +			continue; +		} +	} + +	return 0; +} + +static int onenand_block_test(u32 start, u32 size) +{ +	struct onenand_chip *this = mtd->priv; +	struct erase_info instr = { +		.callback	= NULL, +		.priv		= 0, +	}; + +	int blocks; +	loff_t ofs; +	int blocksize = 1 << this->erase_shift; +	int start_block, end_block; +	size_t retlen; +	u_char *buf; +	u_char *verify_buf; +	int ret; + +	buf = malloc(blocksize); +	if (!buf) { +		printf("Not enough malloc space available!\n"); +		return -1; +	} + +	verify_buf = malloc(blocksize); +	if (!verify_buf) { +		printf("Not enough malloc space available!\n"); +		return -1; +	} + +	start_block = start >> this->erase_shift; +	end_block = (start + size) >> this->erase_shift; + +	/* Protect boot-loader from badblock testing */ +	if (start_block < 2) +		start_block = 2; + +	if (end_block > (mtd->size >> this->erase_shift)) +		end_block = mtd->size >> this->erase_shift; + +	blocks = start_block; +	ofs = start; +	while (blocks < end_block) { +		printf("\rTesting block %d at 0x%x", (u32)(ofs >> this->erase_shift), (u32)ofs); + +		ret = mtd->block_isbad(mtd, ofs); +		if (ret) { +			printf("Skip erase bad block %d at 0x%x\n", +			       (u32)(ofs >> this->erase_shift), (u32)ofs); +			goto next; +		} + +		instr.addr = ofs; +		instr.len = blocksize; +		ret = mtd->erase(mtd, &instr); +		if (ret) { +			printk("Erase failed 0x%x, %d\n", (u32)ofs, ret); +			goto next; +		} + +		ret = mtd->write(mtd, ofs, blocksize, &retlen, buf); +		if (ret) { +			printk("Write failed 0x%x, %d\n", (u32)ofs, ret); +			goto next; +		} + +		ret = mtd->read(mtd, ofs, blocksize, &retlen, verify_buf); +		if (ret) { +			printk("Read failed 0x%x, %d\n", (u32)ofs, ret); +			goto next; +		} + +		if (memcmp(buf, verify_buf, blocksize)) +			printk("\nRead/Write test failed at 0x%x\n", (u32)ofs); + +next: +		ofs += blocksize; +		blocks++; +	} +	printf("...Done\n"); + +	free(buf); +	free(verify_buf); + +	return 0; +} + +static int onenand_dump(struct mtd_info *mtd, ulong off, int only_oob) +{ +	int i; +	u_char *datbuf, *oobbuf, *p; +	struct mtd_oob_ops ops; +	loff_t addr; + +	datbuf = malloc(mtd->writesize + mtd->oobsize); +	oobbuf = malloc(mtd->oobsize); +	if (!datbuf || !oobbuf) { +		puts("No memory for page buffer\n"); +		return 1; +	} +	off &= ~(mtd->writesize - 1); +	addr = (loff_t) off; +	memset(&ops, 0, sizeof(ops)); +	ops.datbuf = datbuf; +	ops.oobbuf = oobbuf; /* must exist, but oob data will be appended to ops.datbuf */ +	ops.len = mtd->writesize; +	ops.ooblen = mtd->oobsize; +	ops.retlen = 0; +	i = mtd->read_oob(mtd, addr, &ops); +	if (i < 0) { +		printf("Error (%d) reading page %08lx\n", i, off); +		free(datbuf); +		free(oobbuf); +		return 1; +	} +	printf("Page %08lx dump:\n", off); +	i = mtd->writesize >> 4; +	p = datbuf; + +	while (i--) { +		if (!only_oob) +			printf("\t%02x %02x %02x %02x %02x %02x %02x %02x" +			       "  %02x %02x %02x %02x %02x %02x %02x %02x\n", +			       p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], +			       p[8], p[9], p[10], p[11], p[12], p[13], p[14], +			       p[15]); +		p += 16; +	} +	puts("OOB:\n"); +	i = mtd->oobsize >> 3; +	while (i--) { +		printf("\t%02x %02x %02x %02x %02x %02x %02x %02x\n", +		       p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); +		p += 8; +	} +	free(datbuf); +	free(oobbuf); + +	return 0; +}  int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])  { -	int ret = 0; +	struct onenand_chip *this; +	int blocksize; +	ulong addr, ofs; +	size_t len, retlen = 0; +	int ret; +	char *cmd, *s; + +	mtd = &onenand_mtd; +	this = mtd->priv; +	blocksize = (1 << this->erase_shift); + +	cmd = argv[1];  	switch (argc) {  	case 0:  	case 1: -		printf("Usage:\n%s\n", cmdtp->usage); -		return 1; +		goto usage;  	case 2: -		if (strncmp(argv[1], "open", 4) == 0) { -			onenand_init(); +		if (strcmp(cmd, "info") == 0) { +			printf("%s\n", mtd->name); +			return 0; +		} + +		if (strcmp(cmd, "bad") == 0) { +			/* Currently only one OneNAND device is supported */ +			printf("\nDevice %d bad blocks:\n", 0); +			for (ofs = 0; ofs < mtd->size; ofs += mtd->erasesize) { +				if (mtd->block_isbad(mtd, ofs)) +					printf("  %08x\n", (u32)ofs); +			} +  			return 0;  		} -		printf("%s\n", onenand_mtd.name); -		return 0;  	default:  		/* At least 4 args */ -		if (strncmp(argv[1], "erase", 5) == 0) { -			struct erase_info instr = { -				.callback	= NULL, -			}; -			ulong start, end; -			ulong block; -			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); -			} else { -				start = simple_strtoul(argv[2], NULL, 10); -				end = simple_strtoul(argv[3], NULL, 10); +		/* +		 * Syntax is: +		 *   0       1     2       3    4 +		 *   onenand erase [force] [off size] +		 */ +		if ((strcmp(cmd, "erase") == 0) || (strcmp(cmd, "test") == 0)) { +			int force = argc > 2 && !strcmp("force", argv[2]); +			int o = force ? 3 : 2; +			int erase; -				start >>= onenand_chip.erase_shift; -				end >>= onenand_chip.erase_shift; -				/* Don't include the end block */ -				end--; -			} +			erase = strcmp(cmd, "erase") == 0; /* 1 = erase, 0 = test */ +			printf("\nOneNAND %s: ", erase ? "erase" : "test"); -			if (!end || end < 0) -				end = start; +			/* skip first two or three arguments, look for offset and size */ +			if (arg_off_size(argc - o, argv + o, &ofs, &len) != 0) +				return 1; -			printf("Erase block from %lu to %lu\n", start, end); +			if (erase) +				ret = onenand_block_erase(ofs, len, force); +			else +				ret = onenand_block_test(ofs, len); -			for (block = start; block <= end; block++) { -				instr.addr = block << onenand_chip.erase_shift; -				instr.len = 1 << onenand_chip.erase_shift; -				ret = onenand_erase(&onenand_mtd, &instr); -				if (ret) { -					printf("erase failed %lu\n", block); -					break; -				} -			} +			printf("%s\n", ret ? "ERROR" : "OK"); -			return 0; +			return ret == 0 ? 0 : 1;  		} -		if (strncmp(argv[1], "read", 4) == 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); -			int oob = strncmp(argv[1], "read.oob", 8) ? 0 : 1; -			struct mtd_oob_ops ops; +		if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) { +			int read; +			int oob = 0; -			ops.mode = MTD_OOB_PLACE; +			if (argc < 4) +				goto usage; -			if (oob) { -				ops.len = 0; -				ops.datbuf = NULL; -				ops.ooblen = len; -				ops.oobbuf = (u_char *) addr; -			} else { -				ops.len = len; -				ops.datbuf = (u_char *) addr; -				ops.ooblen = 0; -				ops.oobbuf = NULL; -			} -			ops.retlen = ops.oobretlen = 0; +			addr = (ulong)simple_strtoul(argv[2], NULL, 16); -			onenand_mtd.read_oob(&onenand_mtd, ofs, &ops); -			printf("Done\n"); +			read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */ +			printf("\nOneNAND %s: ", read ? "read" : "write"); +			if (arg_off_size(argc - 3, argv + 3, &ofs, &len) != 0) +				return 1; -			return 0; -		} +			s = strchr(cmd, '.'); +			if ((s != NULL) && (!strcmp(s, ".oob"))) +				oob = 1; -		if (strncmp(argv[1], "write", 5) == 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; +			if (read) { +				ret = onenand_block_read(ofs, len, &retlen, +							 (u8 *)addr, oob); +			} else { +				ret = onenand_block_write(ofs, len, &retlen, +							  (u8 *)addr); +			} -			onenand_write(&onenand_mtd, ofs, len, &retlen, -				      (u_char *) addr); -			printf("Done\n"); +			printf(" %d bytes %s: %s\n", retlen, +			       read ? "read" : "written", ret ? "ERROR" : "OK"); -			return 0; +			return ret == 0 ? 0 : 1;  		} -		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); -			ulong ofs; -			int oob = strncmp(argv[1], "block.oob", 9) ? 0 : 1; -			struct mtd_oob_ops ops; - -			ops.mode = MTD_OOB_PLACE; +		if (strcmp(cmd, "markbad") == 0) { +			addr = (ulong)simple_strtoul(argv[2], NULL, 16); +			int ret = mtd->block_markbad(mtd, addr); +			if (ret == 0) { +				printf("block 0x%08lx successfully marked as bad\n", +						(ulong) addr); +				return 0; +			} else { +				printf("block 0x%08lx NOT marked as bad! ERROR %d\n", +						(ulong) addr, ret); +			} +			return 1; +		} -			ofs = block << onenand_chip.erase_shift; -			if (page) -				ofs += page << onenand_chip.page_shift; +		if (strncmp(cmd, "dump", 4) == 0) { +			if (argc < 3) +				goto usage; -			if (!len) { -				if (oob) -					ops.ooblen = 64; -				else -					ops.len = 512; -			} +			s = strchr(cmd, '.'); +			ofs = (int)simple_strtoul(argv[2], NULL, 16); -			if (oob) { -				ops.datbuf = NULL; -				ops.oobbuf = (u_char *) addr; -			} else { -				ops.datbuf = (u_char *) addr; -				ops.oobbuf = NULL; -			} -			ops.retlen = ops.oobretlen = 0; +			if (s != NULL && strcmp(s, ".oob") == 0) +				ret = onenand_dump(mtd, ofs, 1); +			else +				ret = onenand_dump(mtd, ofs, 0); -			onenand_read_oob(&onenand_mtd, ofs, &ops); -			return 0; +			return ret == 0 ? 1 : 0;  		}  		break;  	}  	return 0; + +usage: +	cmd_usage(cmdtp); +	return 1;  }  U_BOOT_CMD(  	onenand,	6,	1,	do_onenand, -	"onenand - OneNAND sub-system\n", -	"info   - show available OneNAND devices\n" -	"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 sub-system", +	"info - show available OneNAND devices\n" +	"onenand bad - show bad blocks\n" +	"onenand read[.oob] addr off size\n" +	"onenand write[.oob] addr off size\n" +	"    read/write 'size' bytes starting at offset 'off'\n" +	"    to/from memory address 'addr', skipping bad blocks.\n" +	"onenand erase [force] [off size] - erase 'size' bytes from\n" +	"onenand test [off size] - test 'size' bytes from\n" +	"    offset 'off' (entire device if not specified)\n" +	"onenand dump[.oob] off - dump page\n" +	"onenand markbad off - mark bad block at offset (UNSAFE)\n"  ); |