Newer
Older
* 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 <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 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 * 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%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) {
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;
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,
};
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;
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 == next_ofs) {
next_ofs = to + len;
to += skip_ofs;
} else {
next_ofs = to + len;
skip_ofs = 0;
}
ofs = to;
while (blocks) {
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);
else
ret = onenand_write_oneblock_withoob(ofs, buf, &_retlen);
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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) {
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;
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
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);
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;
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);
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
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.len = mtd->writesize;
ops.ooblen = mtd->oobsize;
ops.retlen = 0;
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;
}
static int do_onenand_info(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
printf("%s\n", mtd->name);
return 0;
}
static int do_onenand_bad(cmd_tbl_t * 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) {
printf(" %08x\n", (u32)ofs);
}
return 0;
}
static int do_onenand_read(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
{
char *s;
int oob = 0;
int ret = 0;
size_t retlen = 0;
return CMD_RET_USAGE;
s = strchr(argv[0], '.');
if ((s != NULL) && (!strcmp(s, ".oob")))
oob = 1;
addr = (ulong)simple_strtoul(argv[1], NULL, 16);
printf("\nOneNAND read: ");
if (arg_off_size(argc - 2, argv + 2, &ofs, &len) != 0)
return 1;
ret = onenand_block_read(ofs, len, &retlen, (u8 *)addr, oob);
printf(" %d bytes read: %s\n", retlen, ret ? "ERROR" : "OK");
return ret == 0 ? 0 : 1;
}
static int do_onenand_write(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
{
ulong addr, ofs;
size_t len;
size_t retlen = 0;
return CMD_RET_USAGE;
if (strncmp(argv[0] + 6, "yaffs", 5) == 0)
withoob = 1;
addr = (ulong)simple_strtoul(argv[1], NULL, 16);
printf("\nOneNAND write: ");
if (arg_off_size(argc - 2, argv + 2, &ofs, &len) != 0)
return 1;
ret = onenand_block_write(ofs, len, &retlen, (u8 *)addr, withoob);
printf(" %d bytes written: %s\n", retlen, ret ? "ERROR" : "OK");
return ret == 0 ? 0 : 1;
}
static int do_onenand_erase(cmd_tbl_t * 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(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(cmd_tbl_t * 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(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(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
{
ulong ofs;
int ret = 0;
char *s;
return CMD_RET_USAGE;
s = strchr(argv[0], '.');
ofs = (int)simple_strtoul(argv[1], NULL, 16);
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(cmd_tbl_t * 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 = simple_strtoul(*argv, NULL, 16);
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 cmd_tbl_t 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, "", ""),
};
#ifdef CONFIG_NEEDS_MANUAL_RELOC
void onenand_reloc(void) {
fixup_cmdtable(cmd_onenand_sub, ARRAY_SIZE(cmd_onenand_sub));
}
#endif
static int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
{
cmd_tbl_t *c;
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;
onenand, CONFIG_SYS_MAXARGS, 1, do_onenand,
"info - show available OneNAND devices\n"
"onenand bad - show bad blocks\n"
"onenand read[.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(s) at offset (UNSAFE)"