Blame view
cmd/nand.c
24.2 KB
dc7c9a1a5
|
1 2 3 4 5 |
/* * Driver for NAND support, Rick Bronson * borrowed heavily from: * (c) 1999 Machine Vision Holdings, Inc. * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org> |
384cc6874
|
6 |
* |
c9f7351b5
|
7 8 9 10 |
* Ported 'dynenv' to 'nand env.oob' command * (C) 2010 Nanometrics, Inc. * 'dynenv' -- Dynamic environment offset in NAND OOB * (C) Copyright 2006-2007 OpenMoko, Inc. |
384cc6874
|
11 12 |
* Added 16-bit nand support * (C) 2004 Texas Instruments |
ea533c260
|
13 |
* |
418396e21
|
14 |
* Copyright 2010, 2012 Freescale Semiconductor |
ea533c260
|
15 16 17 18 19 |
* The portions of this file whose copyright is held by Freescale and which * are not considered a derived work of GPL v2-only code may be distributed * and/or modified under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. |
dc7c9a1a5
|
20 21 22 |
*/ #include <common.h> |
8e8ccfe1a
|
23 |
#include <image.h> |
cfa460adf
|
24 |
#include <linux/mtd/mtd.h> |
addb2e165
|
25 |
#include <command.h> |
24b852a7a
|
26 |
#include <console.h> |
c7694dd48
|
27 |
#include <env.h> |
addb2e165
|
28 29 30 |
#include <watchdog.h> #include <malloc.h> #include <asm/byteorder.h> |
addb2e165
|
31 32 |
#include <jffs2/jffs2.h> #include <nand.h> |
eb446ef62
|
33 |
#include "legacy-mtd-utils.h" |
0c8a84916
|
34 |
#if defined(CONFIG_CMD_MTDPARTS) |
856f05441
|
35 |
|
445093d17
|
36 |
/* partition handling routines */ |
856f05441
|
37 |
int mtdparts_init(void); |
856f05441
|
38 |
int find_dev_and_part(const char *id, struct mtd_device **dev, |
4b0708093
|
39 |
u8 *part_num, struct part_info **part); |
856f05441
|
40 |
#endif |
151c06ec6
|
41 42 |
static int nand_dump(struct mtd_info *mtd, ulong off, int only_oob, int repeat) |
addb2e165
|
43 44 |
{ int i; |
9ad754fef
|
45 |
u_char *datbuf, *oobbuf, *p; |
8c5659a6d
|
46 |
static loff_t last; |
e40520b5b
|
47 |
int ret = 0; |
8c5659a6d
|
48 49 |
if (repeat) |
151c06ec6
|
50 |
off = last + mtd->writesize; |
8c5659a6d
|
51 52 |
last = off; |
addb2e165
|
53 |
|
151c06ec6
|
54 |
datbuf = memalign(ARCH_DMA_MINALIGN, mtd->writesize); |
e40520b5b
|
55 |
if (!datbuf) { |
addb2e165
|
56 57 58 59 |
puts("No memory for page buffer "); return 1; } |
e40520b5b
|
60 |
|
151c06ec6
|
61 |
oobbuf = memalign(ARCH_DMA_MINALIGN, mtd->oobsize); |
e40520b5b
|
62 63 64 65 66 67 |
if (!oobbuf) { puts("No memory for page buffer "); ret = 1; goto free_dat; } |
151c06ec6
|
68 |
off &= ~(mtd->writesize - 1); |
cfa460adf
|
69 |
loff_t addr = (loff_t) off; |
9ad754fef
|
70 71 72 |
struct mtd_oob_ops ops; memset(&ops, 0, sizeof(ops)); ops.datbuf = datbuf; |
cfdae12f3
|
73 |
ops.oobbuf = oobbuf; |
151c06ec6
|
74 75 |
ops.len = mtd->writesize; ops.ooblen = mtd->oobsize; |
dfe64e2c8
|
76 |
ops.mode = MTD_OPS_RAW; |
151c06ec6
|
77 |
i = mtd_read_oob(mtd, addr, &ops); |
addb2e165
|
78 |
if (i < 0) { |
e870690bd
|
79 80 |
printf("Error (%d) reading page %08lx ", i, off); |
e40520b5b
|
81 82 |
ret = 1; goto free_all; |
addb2e165
|
83 |
} |
e870690bd
|
84 85 |
printf("Page %08lx dump: ", off); |
4b0708093
|
86 |
|
7d25cd34e
|
87 |
if (!only_oob) { |
151c06ec6
|
88 |
i = mtd->writesize >> 4; |
7d25cd34e
|
89 90 91 |
p = datbuf; while (i--) { |
9ad754fef
|
92 93 94 95 |
printf("\t%02x %02x %02x %02x %02x %02x %02x %02x" " %02x %02x %02x %02x %02x %02x %02x %02x ", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], |
dfbf617ff
|
96 97 |
p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); |
7d25cd34e
|
98 99 |
p += 16; } |
addb2e165
|
100 |
} |
7d25cd34e
|
101 |
|
addb2e165
|
102 103 |
puts("OOB: "); |
151c06ec6
|
104 |
i = mtd->oobsize >> 3; |
cfdae12f3
|
105 |
p = oobbuf; |
addb2e165
|
106 |
while (i--) { |
cfa460adf
|
107 108 109 |
printf("\t%02x %02x %02x %02x %02x %02x %02x %02x ", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); |
addb2e165
|
110 111 |
p += 8; } |
e40520b5b
|
112 113 |
free_all: |
9ad754fef
|
114 |
free(oobbuf); |
e40520b5b
|
115 116 |
free_dat: free(datbuf); |
addb2e165
|
117 |
|
e40520b5b
|
118 |
return ret; |
addb2e165
|
119 120 121 |
} /* ------------------------------------------------------------------------- */ |
ea533c260
|
122 123 |
static int set_dev(int dev) { |
ad92dff28
|
124 125 126 127 |
struct mtd_info *mtd = get_nand_dev_by_index(dev); if (!mtd) return -ENODEV; |
ea533c260
|
128 129 130 |
if (nand_curr_device == dev) return 0; |
ad92dff28
|
131 |
printf("Device %d: %s", dev, mtd->name); |
ea533c260
|
132 133 134 135 136 |
puts("... is now current device "); nand_curr_device = dev; #ifdef CONFIG_SYS_NAND_SELECT_DEVICE |
6308e71be
|
137 |
board_nand_select_device(mtd_to_nand(mtd), dev); |
ea533c260
|
138 139 140 141 |
#endif return 0; } |
50657c273
|
142 143 144 |
#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK static void print_status(ulong start, ulong end, ulong erasesize, int status) { |
e70bfa298
|
145 146 147 148 149 150 |
/* * Micron NAND flash (e.g. MT29F4G08ABADAH4) BLOCK LOCK READ STATUS is * not the same as others. Instead of bit 1 being lock, it is * #lock_tight. To make the driver support either format, ignore bit 1 * and use only bit 0 and bit 2. */ |
50657c273
|
151 152 153 154 155 156 |
printf("%08lx - %08lx: %08lx blocks %s%s%s ", start, end - 1, (end - start) / erasesize, ((status & NAND_LOCK_STATUS_TIGHT) ? "TIGHT " : ""), |
e70bfa298
|
157 |
(!(status & NAND_LOCK_STATUS_UNLOCK) ? "LOCK " : ""), |
50657c273
|
158 159 |
((status & NAND_LOCK_STATUS_UNLOCK) ? "UNLOCK " : "")); } |
151c06ec6
|
160 |
static void do_nand_status(struct mtd_info *mtd) |
50657c273
|
161 162 163 164 |
{ ulong block_start = 0; ulong off; int last_status = -1; |
17cb4b8f3
|
165 |
struct nand_chip *nand_chip = mtd_to_nand(mtd); |
50657c273
|
166 |
/* check the WP bit */ |
151c06ec6
|
167 |
nand_chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); |
50657c273
|
168 169 |
printf("device is %swrite protected ", |
151c06ec6
|
170 171 |
(nand_chip->read_byte(mtd) & 0x80 ? "NOT " : "")); |
50657c273
|
172 |
|
151c06ec6
|
173 174 |
for (off = 0; off < mtd->size; off += mtd->erasesize) { int s = nand_get_lock_status(mtd, off); |
50657c273
|
175 176 177 |
/* print message only if status has changed */ if (s != last_status && off != 0) { |
151c06ec6
|
178 |
print_status(block_start, off, mtd->erasesize, |
50657c273
|
179 180 181 182 183 184 |
last_status); block_start = off; } last_status = s; } /* Print the last block info */ |
151c06ec6
|
185 |
print_status(block_start, off, mtd->erasesize, last_status); |
50657c273
|
186 187 |
} #endif |
c9f7351b5
|
188 189 |
#ifdef CONFIG_ENV_OFFSET_OOB unsigned long nand_env_oob_offset; |
ea533c260
|
190 |
int do_nand_env_oob(cmd_tbl_t *cmdtp, int argc, char *const argv[]) |
c9f7351b5
|
191 192 193 |
{ int ret; uint32_t oob_buf[ENV_OFFSET_SIZE/sizeof(uint32_t)]; |
ad92dff28
|
194 |
struct mtd_info *mtd = get_nand_dev_by_index(0); |
c9f7351b5
|
195 |
char *cmd = argv[1]; |
8b7d51249
|
196 |
if (CONFIG_SYS_MAX_NAND_DEVICE == 0 || !mtd) { |
ea533c260
|
197 198 199 200 201 202 |
puts("no devices available "); return 1; } set_dev(0); |
c9f7351b5
|
203 |
if (!strcmp(cmd, "get")) { |
151c06ec6
|
204 |
ret = get_nand_env_oob(mtd, &nand_env_oob_offset); |
53504a278
|
205 |
if (ret) |
c9f7351b5
|
206 |
return 1; |
53504a278
|
207 208 209 |
printf("0x%08lx ", nand_env_oob_offset); |
c9f7351b5
|
210 |
} else if (!strcmp(cmd, "set")) { |
ea533c260
|
211 212 |
loff_t addr; loff_t maxsize; |
c9f7351b5
|
213 |
struct mtd_oob_ops ops; |
ea533c260
|
214 |
int idx = 0; |
c9f7351b5
|
215 216 217 |
if (argc < 3) goto usage; |
ad92dff28
|
218 |
mtd = get_nand_dev_by_index(idx); |
c39d6a0ea
|
219 |
/* We don't care about size, or maxsize. */ |
09c328075
|
220 |
if (mtd_arg_off(argv[2], &idx, &addr, &maxsize, &maxsize, |
ad92dff28
|
221 |
MTD_DEV_TYPE_NAND, mtd->size)) { |
09c328075
|
222 223 224 225 226 |
puts("Offset or partition name expected "); return 1; } if (set_dev(idx)) { |
ea533c260
|
227 228 229 230 231 232 233 234 |
puts("Offset or partition name expected "); return 1; } if (idx != 0) { puts("Partition not on first NAND device "); |
c9f7351b5
|
235 236 |
return 1; } |
151c06ec6
|
237 |
if (mtd->oobavail < ENV_OFFSET_SIZE) { |
53504a278
|
238 239 240 241 242 |
printf("Insufficient available OOB bytes: " "%d OOB bytes available but %d required for " "env.oob support ", |
151c06ec6
|
243 |
mtd->oobavail, ENV_OFFSET_SIZE); |
c9f7351b5
|
244 245 |
return 1; } |
151c06ec6
|
246 |
if ((addr & (mtd->erasesize - 1)) != 0) { |
c9f7351b5
|
247 248 249 250 251 252 253 254 255 256 257 258 |
printf("Environment offset must be block-aligned "); return 1; } ops.datbuf = NULL; ops.mode = MTD_OOB_AUTO; ops.ooboffs = 0; ops.ooblen = ENV_OFFSET_SIZE; ops.oobbuf = (void *) oob_buf; oob_buf[0] = ENV_OOB_MARKER; |
151c06ec6
|
259 |
oob_buf[1] = addr / mtd->erasesize; |
c9f7351b5
|
260 |
|
151c06ec6
|
261 |
ret = mtd->write_oob(mtd, ENV_OFFSET_SIZE, &ops); |
53504a278
|
262 |
if (ret) { |
c9f7351b5
|
263 264 265 266 |
printf("Error writing OOB block 0 "); return ret; } |
53504a278
|
267 |
|
151c06ec6
|
268 |
ret = get_nand_env_oob(mtd, &nand_env_oob_offset); |
53504a278
|
269 270 271 272 273 274 275 276 |
if (ret) { printf("Error reading env offset in OOB "); return ret; } if (addr != nand_env_oob_offset) { printf("Verification of env offset in OOB failed: " |
ea533c260
|
277 278 279 |
"0x%08llx expected but got 0x%08lx ", (unsigned long long)addr, nand_env_oob_offset); |
53504a278
|
280 281 |
return 1; } |
c9f7351b5
|
282 283 284 285 286 287 288 |
} else { goto usage; } return ret; usage: |
4c12eeb8b
|
289 |
return CMD_RET_USAGE; |
c9f7351b5
|
290 291 292 |
} #endif |
ce80ddc18
|
293 |
static void nand_print_and_set_info(int idx) |
672ed2aee
|
294 |
{ |
ad92dff28
|
295 296 297 298 299 300 |
struct mtd_info *mtd; struct nand_chip *chip; mtd = get_nand_dev_by_index(idx); if (!mtd) return; |
ce80ddc18
|
301 |
|
ad92dff28
|
302 |
chip = mtd_to_nand(mtd); |
672ed2aee
|
303 304 305 306 307 |
printf("Device %d: ", idx); if (chip->numchips > 1) printf("%dx ", chip->numchips); printf("%s, sector size %u KiB ", |
151c06ec6
|
308 309 310 311 312 313 314 |
mtd->name, mtd->erasesize >> 10); printf(" Page size %8d b ", mtd->writesize); printf(" OOB size %8d b ", mtd->oobsize); printf(" Erase size %8d b ", mtd->erasesize); |
5db73feb1
|
315 316 |
printf(" subpagesize %8d b ", chip->subpagesize); |
66dc09c55
|
317 318 319 320 |
printf(" options 0x%08x ", chip->options); printf(" bbt options 0x%08x ", chip->bbt_options); |
ce80ddc18
|
321 322 |
/* Set geometry info */ |
018f53032
|
323 324 325 |
env_set_hex("nand_writesize", mtd->writesize); env_set_hex("nand_oobsize", mtd->oobsize); env_set_hex("nand_erasesize", mtd->erasesize); |
672ed2aee
|
326 |
} |
151c06ec6
|
327 |
static int raw_access(struct mtd_info *mtd, ulong addr, loff_t off, |
2dc3c483a
|
328 |
ulong count, int read, int no_verify) |
418396e21
|
329 330 |
{ int ret = 0; |
418396e21
|
331 332 333 334 335 |
while (count--) { /* Raw access */ mtd_oob_ops_t ops = { .datbuf = (u8 *)addr, |
151c06ec6
|
336 337 338 |
.oobbuf = ((u8 *)addr) + mtd->writesize, .len = mtd->writesize, .ooblen = mtd->oobsize, |
dfe64e2c8
|
339 |
.mode = MTD_OPS_RAW |
418396e21
|
340 |
}; |
6b94f118a
|
341 |
if (read) { |
151c06ec6
|
342 |
ret = mtd_read_oob(mtd, off, &ops); |
6b94f118a
|
343 |
} else { |
151c06ec6
|
344 |
ret = mtd_write_oob(mtd, off, &ops); |
2dc3c483a
|
345 |
if (!ret && !no_verify) |
151c06ec6
|
346 |
ret = nand_verify_page_oob(mtd, &ops, off); |
6b94f118a
|
347 |
} |
418396e21
|
348 349 350 351 352 353 354 |
if (ret) { printf("%s: error at offset %llx, ret %d ", __func__, (long long)off, ret); break; } |
151c06ec6
|
355 356 |
addr += mtd->writesize + mtd->oobsize; off += mtd->writesize; |
418396e21
|
357 358 359 360 |
} return ret; } |
e834402fa
|
361 |
/* Adjust a chip/partition size down for bad blocks so we don't |
9b80aa8ec
|
362 |
* read/write past the end of a chip/partition by accident. |
e834402fa
|
363 364 365 366 367 368 |
*/ static void adjust_size_for_badblocks(loff_t *size, loff_t offset, int dev) { /* We grab the nand info object here fresh because this is usually * called after arg_off_size() which can change the value of dev. */ |
ad92dff28
|
369 |
struct mtd_info *mtd = get_nand_dev_by_index(dev); |
e834402fa
|
370 371 372 373 |
loff_t maxoffset = offset + *size; int badblocks = 0; /* count badblocks in NAND from offset to offset + size */ |
151c06ec6
|
374 375 |
for (; offset < maxoffset; offset += mtd->erasesize) { if (nand_block_isbad(mtd, offset)) |
e834402fa
|
376 377 378 379 |
badblocks++; } /* adjust size if any bad blocks found */ if (badblocks) { |
151c06ec6
|
380 |
*size -= badblocks * mtd->erasesize; |
e834402fa
|
381 382 383 384 385 |
printf("size adjusted to 0x%llx (%d bad blocks) ", (unsigned long long)*size, badblocks); } } |
088f1b199
|
386 |
static int do_nand(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
addb2e165
|
387 |
{ |
ea533c260
|
388 389 |
int i, ret = 0; ulong addr; |
c39d6a0ea
|
390 |
loff_t off, size, maxsize; |
addb2e165
|
391 |
char *cmd, *s; |
151c06ec6
|
392 |
struct mtd_info *mtd; |
6d0f6bcf3
|
393 394 |
#ifdef CONFIG_SYS_NAND_QUIET int quiet = CONFIG_SYS_NAND_QUIET; |
c750d2e66
|
395 |
#else |
2255b2d20
|
396 |
int quiet = 0; |
c750d2e66
|
397 |
#endif |
00caae6d4
|
398 |
const char *quiet_str = env_get("quiet"); |
ea533c260
|
399 |
int dev = nand_curr_device; |
8c5659a6d
|
400 |
int repeat = flag & CMD_FLAG_REPEAT; |
addb2e165
|
401 402 403 404 |
/* at least two arguments please */ if (argc < 2) goto usage; |
2255b2d20
|
405 406 |
if (quiet_str) quiet = simple_strtoul(quiet_str, NULL, 0) != 0; |
addb2e165
|
407 |
cmd = argv[1]; |
8c5659a6d
|
408 409 410 |
/* Only "dump" is repeatable. */ if (repeat && strcmp(cmd, "dump")) return 0; |
addb2e165
|
411 412 413 414 |
if (strcmp(cmd, "info") == 0) { putc(' '); |
ad92dff28
|
415 416 |
for (i = 0; i < CONFIG_SYS_MAX_NAND_DEVICE; i++) nand_print_and_set_info(i); |
addb2e165
|
417 418 419 420 |
return 0; } if (strcmp(cmd, "device") == 0) { |
addb2e165
|
421 |
if (argc < 3) { |
672ed2aee
|
422 423 |
putc(' '); |
ea533c260
|
424 |
if (dev < 0 || dev >= CONFIG_SYS_MAX_NAND_DEVICE) |
672ed2aee
|
425 426 |
puts("no devices available "); |
addb2e165
|
427 |
else |
ce80ddc18
|
428 |
nand_print_and_set_info(dev); |
addb2e165
|
429 430 |
return 0; } |
43a2b0e76
|
431 |
|
ea533c260
|
432 433 |
dev = (int)simple_strtoul(argv[2], NULL, 10); set_dev(dev); |
43a2b0e76
|
434 |
|
addb2e165
|
435 436 |
return 0; } |
c9f7351b5
|
437 438 |
#ifdef CONFIG_ENV_OFFSET_OOB /* this command operates only on the first nand device */ |
ea533c260
|
439 440 |
if (strcmp(cmd, "env.oob") == 0) return do_nand_env_oob(cmdtp, argc - 1, argv + 1); |
c9f7351b5
|
441 |
#endif |
ea533c260
|
442 443 444 445 446 447 |
/* The following commands operate on the current device, unless * overridden by a partition specifier. Note that if somehow the * current device is invalid, it will have to be changed to a valid * one before these commands can run, even if a partition specifier * for another device is to be used. */ |
ad92dff28
|
448 449 |
mtd = get_nand_dev_by_index(dev); if (!mtd) { |
addb2e165
|
450 451 452 453 454 |
puts(" no devices available "); return 1; } |
addb2e165
|
455 456 |
if (strcmp(cmd, "bad") == 0) { |
ea533c260
|
457 458 459 |
printf(" Device %d bad blocks: ", dev); |
151c06ec6
|
460 461 |
for (off = 0; off < mtd->size; off += mtd->erasesize) if (nand_block_isbad(mtd, off)) |
ea533c260
|
462 463 |
printf(" %08llx ", (unsigned long long)off); |
addb2e165
|
464 465 |
return 0; } |
856f05441
|
466 467 468 469 470 |
/* * Syntax is: * 0 1 2 3 4 * nand erase [clean] [off size] */ |
304863225
|
471 |
if (strncmp(cmd, "erase", 5) == 0 || strncmp(cmd, "scrub", 5) == 0) { |
2255b2d20
|
472 |
nand_erase_options_t opts; |
856f05441
|
473 |
/* "clean" at index 2 means request to write cleanmarker */ |
3043c045d
|
474 |
int clean = argc > 2 && !strcmp("clean", argv[2]); |
608998166
|
475 476 |
int scrub_yes = argc > 2 && !strcmp("-y", argv[2]); int o = (clean || scrub_yes) ? 3 : 2; |
304863225
|
477 |
int scrub = !strncmp(cmd, "scrub", 5); |
304863225
|
478 479 |
int spread = 0; int args = 2; |
608998166
|
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
const char *scrub_warn = "Warning: " "scrub option will erase all factory set bad blocks! " " " "There is no reliable way to recover them. " " " "Use this command only for testing purposes if you " " " "are sure of what you are doing! " " Really scrub this NAND flash? <y/N> "; |
304863225
|
496 497 498 499 500 |
if (cmd[5] != 0) { if (!strcmp(&cmd[5], ".spread")) { spread = 1; } else if (!strcmp(&cmd[5], ".part")) { |
304863225
|
501 502 |
args = 1; } else if (!strcmp(&cmd[5], ".chip")) { |
304863225
|
503 504 505 506 507 508 509 510 511 512 513 514 515 |
args = 0; } else { goto usage; } } /* * Don't allow missing arguments to cause full chip/partition * erases -- easy to do accidentally, e.g. with a misspelled * variable name. */ if (argc != o + args) goto usage; |
2255b2d20
|
516 |
|
304863225
|
517 518 |
printf(" NAND %s: ", cmd); |
856f05441
|
519 |
/* skip first two or three arguments, look for offset and size */ |
09c328075
|
520 521 |
if (mtd_arg_off_size(argc - o, argv + o, &dev, &off, &size, &maxsize, MTD_DEV_TYPE_NAND, |
ad92dff28
|
522 |
mtd->size) != 0) |
09c328075
|
523 524 525 |
return 1; if (set_dev(dev)) |
856f05441
|
526 |
return 1; |
2255b2d20
|
527 |
|
ad92dff28
|
528 |
mtd = get_nand_dev_by_index(dev); |
ea533c260
|
529 |
|
2255b2d20
|
530 531 532 533 534 |
memset(&opts, 0, sizeof(opts)); opts.offset = off; opts.length = size; opts.jffs2 = clean; opts.quiet = quiet; |
304863225
|
535 |
opts.spread = spread; |
2255b2d20
|
536 537 |
if (scrub) { |
a5dffa4b6
|
538 |
if (scrub_yes) { |
608998166
|
539 |
opts.scrub = 1; |
a5dffa4b6
|
540 541 542 |
} else { puts(scrub_warn); if (confirm_yesno()) { |
6b94b4962
|
543 |
opts.scrub = 1; |
a5dffa4b6
|
544 |
} else { |
6b94b4962
|
545 546 |
puts("scrub aborted "); |
46aabcc44
|
547 |
return 1; |
6b94b4962
|
548 |
} |
2255b2d20
|
549 550 |
} } |
151c06ec6
|
551 |
ret = nand_erase_opts(mtd, &opts); |
addb2e165
|
552 553 554 555 556 557 558 559 560 |
printf("%s ", ret ? "ERROR" : "OK"); return ret == 0 ? 0 : 1; } if (strncmp(cmd, "dump", 4) == 0) { if (argc < 3) goto usage; |
addb2e165
|
561 |
off = (int)simple_strtoul(argv[2], NULL, 16); |
151c06ec6
|
562 |
ret = nand_dump(mtd, off, !strcmp(&cmd[4], ".oob"), repeat); |
addb2e165
|
563 564 |
return ret == 0 ? 1 : 0; |
addb2e165
|
565 |
} |
addb2e165
|
566 |
if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) { |
ea533c260
|
567 |
size_t rwsize; |
418396e21
|
568 |
ulong pagecount = 1; |
2255b2d20
|
569 |
int read; |
ced199dc8
|
570 |
int raw = 0; |
2dc3c483a
|
571 |
int no_verify = 0; |
2255b2d20
|
572 |
|
addb2e165
|
573 574 |
if (argc < 4) goto usage; |
2255b2d20
|
575 |
|
addb2e165
|
576 |
addr = (ulong)simple_strtoul(argv[2], NULL, 16); |
2255b2d20
|
577 |
read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */ |
856f05441
|
578 579 |
printf(" NAND %s: ", read ? "read" : "write"); |
4cbb651b2
|
580 |
|
2255b2d20
|
581 |
s = strchr(cmd, '.'); |
418396e21
|
582 |
|
2dc3c483a
|
583 |
if (s && !strncmp(s, ".raw", 4)) { |
418396e21
|
584 |
raw = 1; |
2dc3c483a
|
585 586 |
if (!strcmp(s, ".raw.noverify")) no_verify = 1; |
09c328075
|
587 588 |
if (mtd_arg_off(argv[3], &dev, &off, &size, &maxsize, MTD_DEV_TYPE_NAND, |
ad92dff28
|
589 |
mtd->size)) |
09c328075
|
590 591 592 |
return 1; if (set_dev(dev)) |
418396e21
|
593 |
return 1; |
ad92dff28
|
594 |
mtd = get_nand_dev_by_index(dev); |
93d3232d9
|
595 |
|
418396e21
|
596 597 598 599 600 |
if (argc > 4 && !str2long(argv[4], &pagecount)) { printf("'%s' is not a number ", argv[4]); return 1; } |
151c06ec6
|
601 |
if (pagecount * mtd->writesize > size) { |
418396e21
|
602 603 604 605 |
puts("Size exceeds partition or device limit "); return -1; } |
151c06ec6
|
606 |
rwsize = pagecount * (mtd->writesize + mtd->oobsize); |
418396e21
|
607 |
} else { |
09c328075
|
608 609 610 |
if (mtd_arg_off_size(argc - 3, argv + 3, &dev, &off, &size, &maxsize, MTD_DEV_TYPE_NAND, |
ad92dff28
|
611 |
mtd->size) != 0) |
09c328075
|
612 613 614 |
return 1; if (set_dev(dev)) |
418396e21
|
615 |
return 1; |
e834402fa
|
616 617 618 |
/* size is unspecified */ if (argc < 5) adjust_size_for_badblocks(&size, off, dev); |
418396e21
|
619 620 |
rwsize = size; } |
ad92dff28
|
621 |
mtd = get_nand_dev_by_index(dev); |
93d3232d9
|
622 |
|
984e03cdf
|
623 624 |
if (!s || !strcmp(s, ".jffs2") || !strcmp(s, ".e") || !strcmp(s, ".i")) { |
dfbf617ff
|
625 |
if (read) |
151c06ec6
|
626 |
ret = nand_read_skip_bad(mtd, off, &rwsize, |
c39d6a0ea
|
627 |
NULL, maxsize, |
4b0708093
|
628 |
(u_char *)addr); |
dfbf617ff
|
629 |
else |
151c06ec6
|
630 |
ret = nand_write_skip_bad(mtd, off, &rwsize, |
c39d6a0ea
|
631 |
NULL, maxsize, |
6b94f118a
|
632 633 |
(u_char *)addr, WITH_WR_VERIFY); |
c9494866d
|
634 635 636 637 638 639 640 |
#ifdef CONFIG_CMD_NAND_TRIMFFS } else if (!strcmp(s, ".trimffs")) { if (read) { printf("Unknown nand command suffix '%s' ", s); return 1; } |
151c06ec6
|
641 |
ret = nand_write_skip_bad(mtd, off, &rwsize, NULL, |
c39d6a0ea
|
642 |
maxsize, (u_char *)addr, |
6b94f118a
|
643 |
WITH_DROP_FFS | WITH_WR_VERIFY); |
c9494866d
|
644 |
#endif |
dfc99e143
|
645 |
} else if (!strcmp(s, ".oob")) { |
cfa460adf
|
646 647 648 |
/* out-of-band data */ mtd_oob_ops_t ops = { .oobbuf = (u8 *)addr, |
ea533c260
|
649 |
.ooblen = rwsize, |
dfe64e2c8
|
650 |
.mode = MTD_OPS_RAW |
cfa460adf
|
651 |
}; |
62d4f4365
|
652 |
if (read) |
151c06ec6
|
653 |
ret = mtd_read_oob(mtd, off, &ops); |
62d4f4365
|
654 |
else |
151c06ec6
|
655 |
ret = mtd_write_oob(mtd, off, &ops); |
418396e21
|
656 |
} else if (raw) { |
2dc3c483a
|
657 658 |
ret = raw_access(mtd, addr, off, pagecount, read, no_verify); |
856f05441
|
659 |
} else { |
984e03cdf
|
660 661 662 |
printf("Unknown nand command suffix '%s'. ", s); return 1; |
2255b2d20
|
663 |
} |
ea533c260
|
664 665 |
printf(" %zu bytes %s: %s ", rwsize, |
2255b2d20
|
666 |
read ? "read" : "written", ret ? "ERROR" : "OK"); |
addb2e165
|
667 668 669 |
return ret == 0 ? 0 : 1; } |
2255b2d20
|
670 |
|
3287f6d38
|
671 672 |
#ifdef CONFIG_CMD_NAND_TORTURE if (strcmp(cmd, "torture") == 0) { |
1866be7d2
|
673 674 |
loff_t endoff; unsigned int failed = 0, passed = 0; |
3287f6d38
|
675 676 677 678 679 680 681 682 |
if (argc < 3) goto usage; if (!str2off(argv[2], &off)) { puts("Offset is not a valid number "); return 1; } |
1866be7d2
|
683 684 685 686 687 688 689 690 |
size = mtd->erasesize; if (argc > 3) { if (!str2off(argv[3], &size)) { puts("Size is not a valid number "); return 1; } } |
3287f6d38
|
691 |
|
1866be7d2
|
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 |
endoff = off + size; if (endoff > mtd->size) { puts("Arguments beyond end of NAND "); return 1; } off = round_down(off, mtd->erasesize); endoff = round_up(endoff, mtd->erasesize); size = endoff - off; printf(" NAND torture: device %d offset 0x%llx size 0x%llx (block size 0x%x) ", dev, off, size, mtd->erasesize); while (off < endoff) { ret = nand_torture(mtd, off); if (ret) { failed++; printf(" block at 0x%llx failed ", off); } else { passed++; } off += mtd->erasesize; } printf(" Passed: %u, failed: %u ", passed, failed); return failed != 0; |
3287f6d38
|
720 721 |
} #endif |
2255b2d20
|
722 |
if (strcmp(cmd, "markbad") == 0) { |
8360b66ba
|
723 724 |
argc -= 2; argv += 2; |
2255b2d20
|
725 |
|
8360b66ba
|
726 727 728 729 730 |
if (argc <= 0) goto usage; while (argc > 0) { addr = simple_strtoul(*argv, NULL, 16); |
151c06ec6
|
731 |
if (mtd_block_markbad(mtd, addr)) { |
8360b66ba
|
732 733 734 735 736 737 738 739 740 741 742 743 744 |
printf("block 0x%08lx NOT marked " "as bad! ERROR %d ", addr, ret); ret = 1; } else { printf("block 0x%08lx successfully " "marked as bad ", addr); } --argc; ++argv; |
2255b2d20
|
745 |
} |
8360b66ba
|
746 |
return ret; |
2255b2d20
|
747 |
} |
dfbf617ff
|
748 |
|
2255b2d20
|
749 750 751 752 |
if (strcmp(cmd, "biterr") == 0) { /* todo */ return 1; } |
50657c273
|
753 |
#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK |
2255b2d20
|
754 |
if (strcmp(cmd, "lock") == 0) { |
50657c273
|
755 |
int tight = 0; |
2255b2d20
|
756 757 758 759 760 761 762 |
int status = 0; if (argc == 3) { if (!strcmp("tight", argv[2])) tight = 1; if (!strcmp("status", argv[2])) status = 1; } |
2255b2d20
|
763 |
if (status) { |
151c06ec6
|
764 |
do_nand_status(mtd); |
cfa460adf
|
765 |
} else { |
151c06ec6
|
766 |
if (!nand_lock(mtd, tight)) { |
5e1dae5c3
|
767 768 769 770 771 772 773 |
puts("NAND flash successfully locked "); } else { puts("Error locking NAND flash "); return 1; } |
2255b2d20
|
774 775 776 |
} return 0; } |
eee623a50
|
777 778 779 780 781 782 783 |
if (strncmp(cmd, "unlock", 5) == 0) { int allexcept = 0; s = strchr(cmd, '.'); if (s && !strcmp(s, ".allexcept")) allexcept = 1; |
09c328075
|
784 785 |
if (mtd_arg_off_size(argc - 2, argv + 2, &dev, &off, &size, &maxsize, MTD_DEV_TYPE_NAND, |
ad92dff28
|
786 |
mtd->size) < 0) |
09c328075
|
787 788 789 |
return 1; if (set_dev(dev)) |
856f05441
|
790 |
return 1; |
2255b2d20
|
791 |
|
ad92dff28
|
792 793 794 |
mtd = get_nand_dev_by_index(dev); if (!nand_unlock(mtd, off, size, allexcept)) { |
5e1dae5c3
|
795 796 797 798 |
puts("NAND flash successfully unlocked "); } else { puts("Error unlocking NAND flash, " |
3043c045d
|
799 800 |
"write and erase will probably fail "); |
5e1dae5c3
|
801 802 |
return 1; } |
2255b2d20
|
803 804 |
return 0; } |
50657c273
|
805 |
#endif |
2255b2d20
|
806 |
|
addb2e165
|
807 |
usage: |
4c12eeb8b
|
808 |
return CMD_RET_USAGE; |
addb2e165
|
809 |
} |
088f1b199
|
810 811 |
#ifdef CONFIG_SYS_LONGHELP static char nand_help_text[] = |
a89c33db9
|
812 813 814 815 816 817 818 819 820 821 822 823 |
"info - show available NAND devices " "nand device [dev] - show or set current device " "nand read - addr off|partition size " "nand write - addr off|partition size " " read/write 'size' bytes starting at offset 'off' " " to/from memory address 'addr', skipping bad blocks. " |
418396e21
|
824 825 |
"nand read.raw - addr off|partition [count] " |
2dc3c483a
|
826 827 |
"nand write.raw[.noverify] - addr off|partition [count] " |
418396e21
|
828 829 |
" Use read.raw/write.raw to avoid ECC and access the flash as-is. " |
c9494866d
|
830 831 832 833 834 835 836 837 838 839 |
#ifdef CONFIG_CMD_NAND_TRIMFFS "nand write.trimffs - addr off|partition size " " write 'size' bytes starting at offset 'off' from memory address " " 'addr', skipping bad blocks and dropping any pages at the end " " of eraseblocks that contain only 0xFF " #endif |
eb3abce89
|
840 |
"nand erase[.spread] [clean] off size - erase 'size' bytes " |
304863225
|
841 842 843 844 845 846 847 848 849 850 |
"from offset 'off' " " With '.spread', erase enough for given file size, otherwise, " " 'size' includes skipped bad blocks. " "nand erase.part [clean] partition - erase entire mtd partition' " "nand erase.chip [clean] - erase entire chip' " |
a89c33db9
|
851 852 853 854 |
"nand bad - show bad blocks " "nand dump[.oob] off - dump page " |
3287f6d38
|
855 |
#ifdef CONFIG_CMD_NAND_TORTURE |
1866be7d2
|
856 857 858 859 |
"nand torture off - torture one block at offset " "nand torture off [size] - torture blocks from off to off+size " |
3287f6d38
|
860 |
#endif |
608998166
|
861 862 |
"nand scrub [-y] off size | scrub.part partition | scrub.chip " |
304863225
|
863 864 |
" really clean NAND erasing bad blocks (UNSAFE) " |
a89c33db9
|
865 866 867 |
"nand markbad off [...] - mark bad block(s) at offset (UNSAFE) " "nand biterr off - make a bit error at offset (UNSAFE)" |
50657c273
|
868 |
#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK |
a89c33db9
|
869 870 871 872 873 874 |
" " "nand lock [tight] [status] " " bring nand to lock state or display locked pages " |
eee623a50
|
875 |
"nand unlock[.allexcept] [offset] [size] - unlock section" |
50657c273
|
876 |
#endif |
c9f7351b5
|
877 878 879 880 881 882 883 884 885 886 |
#ifdef CONFIG_ENV_OFFSET_OOB " " "nand env.oob - environment offset in OOB of block 0 of" " first device. " "nand env.oob set off|partition - set enviromnent offset " "nand env.oob get - get environment offset" #endif |
088f1b199
|
887 888 889 890 891 892 |
""; #endif U_BOOT_CMD( nand, CONFIG_SYS_MAXARGS, 1, do_nand, "NAND sub-system", nand_help_text |
50657c273
|
893 |
); |
addb2e165
|
894 |
|
151c06ec6
|
895 |
static int nand_load_image(cmd_tbl_t *cmdtp, struct mtd_info *mtd, |
4b0708093
|
896 |
ulong offset, ulong addr, char *cmd) |
addb2e165
|
897 |
{ |
addb2e165
|
898 |
int r; |
67d668bf9
|
899 |
char *s; |
4ca79f477
|
900 |
size_t cnt; |
c76c93a3d
|
901 |
#if defined(CONFIG_LEGACY_IMAGE_FORMAT) |
addb2e165
|
902 |
image_header_t *hdr; |
21d29f7f9
|
903 |
#endif |
09475f752
|
904 |
#if defined(CONFIG_FIT) |
3bab76a26
|
905 |
const void *fit_hdr = NULL; |
09475f752
|
906 |
#endif |
10e038932
|
907 908 909 |
s = strchr(cmd, '.'); if (s != NULL && |
65d8bc94d
|
910 |
(strcmp(s, ".jffs2") && strcmp(s, ".e") && strcmp(s, ".i"))) { |
984e03cdf
|
911 912 |
printf("Unknown nand load suffix '%s' ", s); |
770605e4f
|
913 |
bootstage_error(BOOTSTAGE_ID_NAND_SUFFIX); |
984e03cdf
|
914 915 |
return 1; } |
addb2e165
|
916 |
|
151c06ec6
|
917 918 919 |
printf(" Loading from %s, offset 0x%lx ", mtd->name, offset); |
addb2e165
|
920 |
|
151c06ec6
|
921 922 923 |
cnt = mtd->writesize; r = nand_read_skip_bad(mtd, offset, &cnt, NULL, mtd->size, (u_char *)addr); |
addb2e165
|
924 |
if (r) { |
856f05441
|
925 926 |
puts("** Read error "); |
770605e4f
|
927 |
bootstage_error(BOOTSTAGE_ID_NAND_HDR_READ); |
addb2e165
|
928 929 |
return 1; } |
770605e4f
|
930 |
bootstage_mark(BOOTSTAGE_ID_NAND_HDR_READ); |
addb2e165
|
931 |
|
9a4daad0a
|
932 |
switch (genimg_get_format ((void *)addr)) { |
c76c93a3d
|
933 |
#if defined(CONFIG_LEGACY_IMAGE_FORMAT) |
d5934ad77
|
934 935 |
case IMAGE_FORMAT_LEGACY: hdr = (image_header_t *)addr; |
770605e4f
|
936 |
bootstage_mark(BOOTSTAGE_ID_NAND_TYPE); |
d5934ad77
|
937 |
image_print_contents (hdr); |
addb2e165
|
938 |
|
d5934ad77
|
939 940 |
cnt = image_get_image_size (hdr); break; |
21d29f7f9
|
941 |
#endif |
d5934ad77
|
942 943 |
#if defined(CONFIG_FIT) case IMAGE_FORMAT_FIT: |
09475f752
|
944 |
fit_hdr = (const void *)addr; |
09475f752
|
945 946 947 948 949 |
puts ("Fit image detected... "); cnt = fit_get_size (fit_hdr); break; |
d5934ad77
|
950 951 |
#endif default: |
770605e4f
|
952 |
bootstage_error(BOOTSTAGE_ID_NAND_TYPE); |
d5934ad77
|
953 954 |
puts ("** Unknown image type "); |
addb2e165
|
955 956 |
return 1; } |
770605e4f
|
957 |
bootstage_mark(BOOTSTAGE_ID_NAND_TYPE); |
addb2e165
|
958 |
|
151c06ec6
|
959 960 |
r = nand_read_skip_bad(mtd, offset, &cnt, NULL, mtd->size, (u_char *)addr); |
addb2e165
|
961 |
if (r) { |
856f05441
|
962 963 |
puts("** Read error "); |
770605e4f
|
964 |
bootstage_error(BOOTSTAGE_ID_NAND_READ); |
addb2e165
|
965 966 |
return 1; } |
770605e4f
|
967 |
bootstage_mark(BOOTSTAGE_ID_NAND_READ); |
addb2e165
|
968 |
|
09475f752
|
969 970 |
#if defined(CONFIG_FIT) /* This cannot be done earlier, we need complete FIT image in RAM first */ |
3bab76a26
|
971 972 |
if (genimg_get_format ((void *)addr) == IMAGE_FORMAT_FIT) { if (!fit_check_format (fit_hdr)) { |
770605e4f
|
973 |
bootstage_error(BOOTSTAGE_ID_NAND_FIT_READ); |
3bab76a26
|
974 975 976 977 |
puts ("** Bad FIT image format "); return 1; } |
770605e4f
|
978 |
bootstage_mark(BOOTSTAGE_ID_NAND_FIT_READ_OK); |
3bab76a26
|
979 980 |
fit_print_contents (fit_hdr); } |
09475f752
|
981 |
#endif |
addb2e165
|
982 |
/* Loading ok, update default load address */ |
bb872dd93
|
983 |
image_load_addr = addr; |
addb2e165
|
984 |
|
67d668bf9
|
985 |
return bootm_maybe_autostart(cmdtp, cmd); |
addb2e165
|
986 |
} |
088f1b199
|
987 988 |
static int do_nandboot(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
856f05441
|
989 990 991 992 |
{ char *boot_device = NULL; int idx; ulong addr, offset = 0; |
ad92dff28
|
993 |
struct mtd_info *mtd; |
0c8a84916
|
994 |
#if defined(CONFIG_CMD_MTDPARTS) |
856f05441
|
995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 |
struct mtd_device *dev; struct part_info *part; u8 pnum; if (argc >= 2) { char *p = (argc == 2) ? argv[1] : argv[2]; if (!(str2long(p, &addr)) && (mtdparts_init() == 0) && (find_dev_and_part(p, &dev, &pnum, &part) == 0)) { if (dev->id->type != MTD_DEV_TYPE_NAND) { puts("Not a NAND device "); return 1; } if (argc > 3) goto usage; if (argc == 3) |
10e038932
|
1011 |
addr = simple_strtoul(argv[1], NULL, 16); |
856f05441
|
1012 |
else |
6d0f6bcf3
|
1013 |
addr = CONFIG_SYS_LOAD_ADDR; |
ad92dff28
|
1014 1015 1016 1017 |
mtd = get_nand_dev_by_index(dev->id->num); return nand_load_image(cmdtp, mtd, part->offset, addr, argv[0]); |
856f05441
|
1018 1019 1020 |
} } #endif |
770605e4f
|
1021 |
bootstage_mark(BOOTSTAGE_ID_NAND_PART); |
856f05441
|
1022 1023 |
switch (argc) { case 1: |
6d0f6bcf3
|
1024 |
addr = CONFIG_SYS_LOAD_ADDR; |
00caae6d4
|
1025 |
boot_device = env_get("bootdevice"); |
856f05441
|
1026 1027 1028 |
break; case 2: addr = simple_strtoul(argv[1], NULL, 16); |
00caae6d4
|
1029 |
boot_device = env_get("bootdevice"); |
856f05441
|
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 |
break; case 3: addr = simple_strtoul(argv[1], NULL, 16); boot_device = argv[2]; break; case 4: addr = simple_strtoul(argv[1], NULL, 16); boot_device = argv[2]; offset = simple_strtoul(argv[3], NULL, 16); break; default: |
0c8a84916
|
1041 |
#if defined(CONFIG_CMD_MTDPARTS) |
856f05441
|
1042 1043 |
usage: #endif |
770605e4f
|
1044 |
bootstage_error(BOOTSTAGE_ID_NAND_SUFFIX); |
4c12eeb8b
|
1045 |
return CMD_RET_USAGE; |
856f05441
|
1046 |
} |
770605e4f
|
1047 |
bootstage_mark(BOOTSTAGE_ID_NAND_SUFFIX); |
856f05441
|
1048 1049 1050 1051 1052 |
if (!boot_device) { puts(" ** No boot device ** "); |
770605e4f
|
1053 |
bootstage_error(BOOTSTAGE_ID_NAND_BOOT_DEVICE); |
856f05441
|
1054 1055 |
return 1; } |
770605e4f
|
1056 |
bootstage_mark(BOOTSTAGE_ID_NAND_BOOT_DEVICE); |
addb2e165
|
1057 |
|
856f05441
|
1058 |
idx = simple_strtoul(boot_device, NULL, 16); |
ad92dff28
|
1059 1060 |
mtd = get_nand_dev_by_index(idx); if (!mtd) { |
856f05441
|
1061 1062 1063 |
printf(" ** Device %d not available ", idx); |
770605e4f
|
1064 |
bootstage_error(BOOTSTAGE_ID_NAND_AVAILABLE); |
856f05441
|
1065 1066 |
return 1; } |
770605e4f
|
1067 |
bootstage_mark(BOOTSTAGE_ID_NAND_AVAILABLE); |
856f05441
|
1068 |
|
ad92dff28
|
1069 |
return nand_load_image(cmdtp, mtd, offset, addr, argv[0]); |
856f05441
|
1070 1071 1072 |
} U_BOOT_CMD(nboot, 4, 1, do_nandboot, |
2fb2604d5
|
1073 |
"boot from NAND device", |
a89c33db9
|
1074 1075 |
"[partition] | [[[loadAddr] dev] offset]" ); |