mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-13 16:37:30 +00:00
1e94b46f73
This old patch was marked as deferred. Bring it back to life, to continue towards the removal of common.h Move this out of the common header and include it only where needed. Signed-off-by: Simon Glass <sjg@chromium.org>
600 lines
13 KiB
C
600 lines
13 KiB
C
/*
|
|
* U-Boot command for OneNAND support
|
|
*
|
|
* Copyright (C) 2005-2008 Samsung Electronics
|
|
* Kyungmin Park <kyungmin.park@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <command.h>
|
|
#include <malloc.h>
|
|
#include <linux/printk.h>
|
|
|
|
#include <linux/compat.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/onenand.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
static struct mtd_info *mtd;
|
|
|
|
static loff_t next_ofs;
|
|
static loff_t skip_ofs;
|
|
|
|
static int arg_off_size_onenand(int argc, char *const 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%llx) exceeded!\n", mtd->size);
|
|
return -1;
|
|
}
|
|
|
|
if (*size == mtd->size)
|
|
puts("whole chip\n");
|
|
else
|
|
printf("offset 0x%lx, size 0x%zx\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_write_oneblock_withoob(loff_t to, const u_char * buf,
|
|
size_t *retlen)
|
|
{
|
|
struct mtd_oob_ops ops = {
|
|
.len = mtd->writesize,
|
|
.ooblen = mtd->oobsize,
|
|
.mode = MTD_OPS_AUTO_OOB,
|
|
};
|
|
int page, ret = 0;
|
|
for (page = 0; page < (mtd->erasesize / mtd->writesize); page ++) {
|
|
ops.datbuf = (u_char *)buf;
|
|
buf += mtd->writesize;
|
|
ops.oobbuf = (u_char *)buf;
|
|
buf += mtd->oobsize;
|
|
ret = mtd_write_oob(mtd, to, &ops);
|
|
if (ret)
|
|
break;
|
|
to += mtd->writesize;
|
|
}
|
|
|
|
*retlen = (ret) ? 0 : mtd->erasesize;
|
|
return ret;
|
|
}
|
|
|
|
static int onenand_block_write(loff_t to, size_t len,
|
|
size_t *retlen, const u_char * buf, int withoob)
|
|
{
|
|
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 & (mtd->writesize - 1)) != 0) {
|
|
printf("Attempt to write non block-aligned data\n");
|
|
*retlen = 0;
|
|
return 1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (!withoob)
|
|
ret = mtd_write(mtd, ofs, blocksize, &_retlen, buf);
|
|
else
|
|
ret = onenand_write_oneblock_withoob(ofs, buf, &_retlen);
|
|
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 = {};
|
|
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 = {};
|
|
|
|
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;
|
|
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;
|
|
p = oobbuf;
|
|
|
|
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;
|
|
}
|
|
|
|
static int do_onenand_info(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
printf("%s\n", mtd->name);
|
|
return 0;
|
|
}
|
|
|
|
static int do_onenand_bad(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
ulong ofs;
|
|
|
|
mtd = &onenand_mtd;
|
|
/* 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;
|
|
}
|
|
|
|
static int do_onenand_read(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
char *s;
|
|
int oob = 0;
|
|
ulong addr, ofs;
|
|
size_t len;
|
|
int ret = 0;
|
|
size_t retlen = 0;
|
|
|
|
if (argc < 3)
|
|
return CMD_RET_USAGE;
|
|
|
|
s = strchr(argv[0], '.');
|
|
if ((s != NULL) && (!strcmp(s, ".oob")))
|
|
oob = 1;
|
|
|
|
addr = (ulong)hextoul(argv[1], NULL);
|
|
|
|
printf("\nOneNAND read: ");
|
|
if (arg_off_size_onenand(argc - 2, argv + 2, &ofs, &len) != 0)
|
|
return 1;
|
|
|
|
ret = onenand_block_read(ofs, len, &retlen, (u8 *)addr, oob);
|
|
|
|
printf(" %zu bytes read: %s\n", retlen, ret ? "ERROR" : "OK");
|
|
|
|
return ret == 0 ? 0 : 1;
|
|
}
|
|
|
|
static int do_onenand_write(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
ulong addr, ofs;
|
|
size_t len;
|
|
int ret = 0, withoob = 0;
|
|
size_t retlen = 0;
|
|
|
|
if (argc < 3)
|
|
return CMD_RET_USAGE;
|
|
|
|
if (strncmp(argv[0] + 6, "yaffs", 5) == 0)
|
|
withoob = 1;
|
|
|
|
addr = (ulong)hextoul(argv[1], NULL);
|
|
|
|
printf("\nOneNAND write: ");
|
|
if (arg_off_size_onenand(argc - 2, argv + 2, &ofs, &len) != 0)
|
|
return 1;
|
|
|
|
ret = onenand_block_write(ofs, len, &retlen, (u8 *)addr, withoob);
|
|
|
|
printf(" %zu bytes written: %s\n", retlen, ret ? "ERROR" : "OK");
|
|
|
|
return ret == 0 ? 0 : 1;
|
|
}
|
|
|
|
static int do_onenand_erase(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
ulong ofs;
|
|
int ret = 0;
|
|
size_t len;
|
|
int force;
|
|
|
|
/*
|
|
* Syntax is:
|
|
* 0 1 2 3 4
|
|
* onenand erase [force] [off size]
|
|
*/
|
|
argc--;
|
|
argv++;
|
|
if (argc)
|
|
{
|
|
if (!strcmp("force", argv[0]))
|
|
{
|
|
force = 1;
|
|
argc--;
|
|
argv++;
|
|
}
|
|
}
|
|
printf("\nOneNAND erase: ");
|
|
|
|
/* skip first two or three arguments, look for offset and size */
|
|
if (arg_off_size_onenand(argc, argv, &ofs, &len) != 0)
|
|
return 1;
|
|
|
|
ret = onenand_block_erase(ofs, len, force);
|
|
|
|
printf("%s\n", ret ? "ERROR" : "OK");
|
|
|
|
return ret == 0 ? 0 : 1;
|
|
}
|
|
|
|
static int do_onenand_test(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
ulong ofs;
|
|
int ret = 0;
|
|
size_t len;
|
|
|
|
/*
|
|
* Syntax is:
|
|
* 0 1 2 3 4
|
|
* onenand test [force] [off size]
|
|
*/
|
|
|
|
printf("\nOneNAND test: ");
|
|
|
|
/* skip first two or three arguments, look for offset and size */
|
|
if (arg_off_size_onenand(argc - 1, argv + 1, &ofs, &len) != 0)
|
|
return 1;
|
|
|
|
ret = onenand_block_test(ofs, len);
|
|
|
|
printf("%s\n", ret ? "ERROR" : "OK");
|
|
|
|
return ret == 0 ? 0 : 1;
|
|
}
|
|
|
|
static int do_onenand_dump(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
ulong ofs;
|
|
int ret = 0;
|
|
char *s;
|
|
|
|
if (argc < 2)
|
|
return CMD_RET_USAGE;
|
|
|
|
s = strchr(argv[0], '.');
|
|
ofs = (int)hextoul(argv[1], NULL);
|
|
|
|
if (s != NULL && strcmp(s, ".oob") == 0)
|
|
ret = onenand_dump(mtd, ofs, 1);
|
|
else
|
|
ret = onenand_dump(mtd, ofs, 0);
|
|
|
|
return ret == 0 ? 1 : 0;
|
|
}
|
|
|
|
static int do_onenand_markbad(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
int ret = 0;
|
|
ulong addr;
|
|
|
|
argc -= 2;
|
|
argv += 2;
|
|
|
|
if (argc <= 0)
|
|
return CMD_RET_USAGE;
|
|
|
|
while (argc > 0) {
|
|
addr = hextoul(*argv, NULL);
|
|
|
|
if (mtd_block_markbad(mtd, addr)) {
|
|
printf("block 0x%08lx NOT marked "
|
|
"as bad! ERROR %d\n",
|
|
addr, ret);
|
|
ret = 1;
|
|
} else {
|
|
printf("block 0x%08lx successfully "
|
|
"marked as bad\n",
|
|
addr);
|
|
}
|
|
--argc;
|
|
++argv;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static struct cmd_tbl cmd_onenand_sub[] = {
|
|
U_BOOT_CMD_MKENT(info, 1, 0, do_onenand_info, "", ""),
|
|
U_BOOT_CMD_MKENT(bad, 1, 0, do_onenand_bad, "", ""),
|
|
U_BOOT_CMD_MKENT(read, 4, 0, do_onenand_read, "", ""),
|
|
U_BOOT_CMD_MKENT(write, 4, 0, do_onenand_write, "", ""),
|
|
U_BOOT_CMD_MKENT(write.yaffs, 4, 0, do_onenand_write, "", ""),
|
|
U_BOOT_CMD_MKENT(erase, 3, 0, do_onenand_erase, "", ""),
|
|
U_BOOT_CMD_MKENT(test, 3, 0, do_onenand_test, "", ""),
|
|
U_BOOT_CMD_MKENT(dump, 2, 0, do_onenand_dump, "", ""),
|
|
U_BOOT_CMD_MKENT(markbad, CONFIG_SYS_MAXARGS, 0, do_onenand_markbad, "", ""),
|
|
};
|
|
|
|
static int do_onenand(struct cmd_tbl *cmdtp, int flag, int argc,
|
|
char *const argv[])
|
|
{
|
|
struct cmd_tbl *c;
|
|
|
|
if (argc < 2)
|
|
return CMD_RET_USAGE;
|
|
|
|
mtd = &onenand_mtd;
|
|
|
|
/* Strip off leading 'onenand' command argument */
|
|
argc--;
|
|
argv++;
|
|
|
|
c = find_cmd_tbl(argv[0], &cmd_onenand_sub[0], ARRAY_SIZE(cmd_onenand_sub));
|
|
|
|
if (c)
|
|
return c->cmd(cmdtp, flag, argc, argv);
|
|
else
|
|
return CMD_RET_USAGE;
|
|
}
|
|
|
|
U_BOOT_CMD(
|
|
onenand, CONFIG_SYS_MAXARGS, 1, do_onenand,
|
|
"OneNAND sub-system",
|
|
"info - show available OneNAND devices\n"
|
|
"onenand bad - show bad blocks\n"
|
|
"onenand read[.oob] addr off size\n"
|
|
"onenand write[.yaffs] 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(s) at offset (UNSAFE)"
|
|
);
|