]>
Commit | Line | Data |
---|---|---|
5db66b3a MR |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * mtd.c | |
4 | * | |
5 | * Generic command to handle basic operations on any memory device. | |
6 | * | |
7 | * Copyright: Bootlin, 2018 | |
8 | * Author: Miquèl Raynal <[email protected]> | |
9 | */ | |
10 | ||
11 | #include <command.h> | |
12 | #include <common.h> | |
13 | #include <console.h> | |
14 | #include <malloc.h> | |
15 | #include <mapmem.h> | |
16 | #include <mtd.h> | |
61b29b82 SG |
17 | #include <dm/devres.h> |
18 | #include <linux/err.h> | |
5db66b3a | 19 | |
9671243e BB |
20 | #include <linux/ctype.h> |
21 | ||
22 | static struct mtd_info *get_mtd_by_name(const char *name) | |
23 | { | |
24 | struct mtd_info *mtd; | |
25 | ||
26 | mtd_probe_devices(); | |
27 | ||
28 | mtd = get_mtd_device_nm(name); | |
29 | if (IS_ERR_OR_NULL(mtd)) | |
30 | printf("MTD device %s not found, ret %ld\n", name, | |
31 | PTR_ERR(mtd)); | |
32 | ||
33 | return mtd; | |
34 | } | |
35 | ||
5db66b3a MR |
36 | static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len) |
37 | { | |
38 | do_div(len, mtd->writesize); | |
39 | ||
40 | return len; | |
41 | } | |
42 | ||
43 | static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size) | |
44 | { | |
45 | return !do_div(size, mtd->writesize); | |
46 | } | |
47 | ||
48 | static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size) | |
49 | { | |
50 | return !do_div(size, mtd->erasesize); | |
51 | } | |
52 | ||
53 | static void mtd_dump_buf(const u8 *buf, uint len, uint offset) | |
54 | { | |
55 | int i, j; | |
56 | ||
57 | for (i = 0; i < len; ) { | |
58 | printf("0x%08x:\t", offset + i); | |
59 | for (j = 0; j < 8; j++) | |
60 | printf("%02x ", buf[i + j]); | |
61 | printf(" "); | |
62 | i += 8; | |
63 | for (j = 0; j < 8; j++) | |
64 | printf("%02x ", buf[i + j]); | |
65 | printf("\n"); | |
66 | i += 8; | |
67 | } | |
68 | } | |
69 | ||
70 | static void mtd_dump_device_buf(struct mtd_info *mtd, u64 start_off, | |
71 | const u8 *buf, u64 len, bool woob) | |
72 | { | |
73 | bool has_pages = mtd->type == MTD_NANDFLASH || | |
74 | mtd->type == MTD_MLCNANDFLASH; | |
75 | int npages = mtd_len_to_pages(mtd, len); | |
76 | uint page; | |
77 | ||
78 | if (has_pages) { | |
79 | for (page = 0; page < npages; page++) { | |
80 | u64 data_off = page * mtd->writesize; | |
81 | ||
82 | printf("\nDump %d data bytes from 0x%08llx:\n", | |
83 | mtd->writesize, start_off + data_off); | |
84 | mtd_dump_buf(&buf[data_off], | |
85 | mtd->writesize, start_off + data_off); | |
86 | ||
87 | if (woob) { | |
88 | u64 oob_off = page * mtd->oobsize; | |
89 | ||
90 | printf("Dump %d OOB bytes from page at 0x%08llx:\n", | |
91 | mtd->oobsize, start_off + data_off); | |
92 | mtd_dump_buf(&buf[len + oob_off], | |
93 | mtd->oobsize, 0); | |
94 | } | |
95 | } | |
96 | } else { | |
97 | printf("\nDump %lld data bytes from 0x%llx:\n", | |
98 | len, start_off); | |
99 | mtd_dump_buf(buf, len, start_off); | |
100 | } | |
101 | } | |
102 | ||
103 | static void mtd_show_parts(struct mtd_info *mtd, int level) | |
104 | { | |
105 | struct mtd_info *part; | |
106 | int i; | |
107 | ||
108 | list_for_each_entry(part, &mtd->partitions, node) { | |
109 | for (i = 0; i < level; i++) | |
110 | printf("\t"); | |
111 | printf(" - 0x%012llx-0x%012llx : \"%s\"\n", | |
112 | part->offset, part->offset + part->size, part->name); | |
113 | ||
114 | mtd_show_parts(part, level + 1); | |
115 | } | |
116 | } | |
117 | ||
118 | static void mtd_show_device(struct mtd_info *mtd) | |
119 | { | |
120 | /* Device */ | |
121 | printf("* %s\n", mtd->name); | |
122 | #if defined(CONFIG_DM) | |
123 | if (mtd->dev) { | |
124 | printf(" - device: %s\n", mtd->dev->name); | |
125 | printf(" - parent: %s\n", mtd->dev->parent->name); | |
126 | printf(" - driver: %s\n", mtd->dev->driver->name); | |
127 | } | |
128 | #endif | |
0b6f907d MB |
129 | if (IS_ENABLED(CONFIG_OF_CONTROL) && mtd->dev) { |
130 | char buf[256]; | |
131 | int res; | |
132 | ||
133 | res = ofnode_get_path(mtd_get_ofnode(mtd), buf, 256); | |
134 | printf(" - path: %s\n", res == 0 ? buf : "unavailable"); | |
135 | } | |
5db66b3a MR |
136 | |
137 | /* MTD device information */ | |
138 | printf(" - type: "); | |
139 | switch (mtd->type) { | |
140 | case MTD_RAM: | |
141 | printf("RAM\n"); | |
142 | break; | |
143 | case MTD_ROM: | |
144 | printf("ROM\n"); | |
145 | break; | |
146 | case MTD_NORFLASH: | |
147 | printf("NOR flash\n"); | |
148 | break; | |
149 | case MTD_NANDFLASH: | |
150 | printf("NAND flash\n"); | |
151 | break; | |
152 | case MTD_DATAFLASH: | |
153 | printf("Data flash\n"); | |
154 | break; | |
155 | case MTD_UBIVOLUME: | |
156 | printf("UBI volume\n"); | |
157 | break; | |
158 | case MTD_MLCNANDFLASH: | |
159 | printf("MLC NAND flash\n"); | |
160 | break; | |
161 | case MTD_ABSENT: | |
162 | default: | |
163 | printf("Unknown\n"); | |
164 | break; | |
165 | } | |
166 | ||
167 | printf(" - block size: 0x%x bytes\n", mtd->erasesize); | |
168 | printf(" - min I/O: 0x%x bytes\n", mtd->writesize); | |
169 | ||
170 | if (mtd->oobsize) { | |
171 | printf(" - OOB size: %u bytes\n", mtd->oobsize); | |
172 | printf(" - OOB available: %u bytes\n", mtd->oobavail); | |
173 | } | |
174 | ||
175 | if (mtd->ecc_strength) { | |
176 | printf(" - ECC strength: %u bits\n", mtd->ecc_strength); | |
177 | printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size); | |
178 | printf(" - bitflip threshold: %u bits\n", | |
179 | mtd->bitflip_threshold); | |
180 | } | |
181 | ||
182 | printf(" - 0x%012llx-0x%012llx : \"%s\"\n", | |
183 | mtd->offset, mtd->offset + mtd->size, mtd->name); | |
184 | ||
185 | /* MTD partitions, if any */ | |
186 | mtd_show_parts(mtd, 1); | |
187 | } | |
188 | ||
189 | /* Logic taken from fs/ubifs/recovery.c:is_empty() */ | |
190 | static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op) | |
191 | { | |
192 | int i; | |
193 | ||
194 | for (i = 0; i < op->len; i++) | |
195 | if (op->datbuf[i] != 0xff) | |
196 | return false; | |
197 | ||
198 | for (i = 0; i < op->ooblen; i++) | |
199 | if (op->oobbuf[i] != 0xff) | |
200 | return false; | |
201 | ||
202 | return true; | |
203 | } | |
204 | ||
09140113 SG |
205 | static int do_mtd_list(struct cmd_tbl *cmdtp, int flag, int argc, |
206 | char *const argv[]) | |
5db66b3a MR |
207 | { |
208 | struct mtd_info *mtd; | |
209 | int dev_nb = 0; | |
210 | ||
211 | /* Ensure all devices (and their partitions) are probed */ | |
212 | mtd_probe_devices(); | |
213 | ||
214 | printf("List of MTD devices:\n"); | |
215 | mtd_for_each_device(mtd) { | |
216 | if (!mtd_is_partition(mtd)) | |
217 | mtd_show_device(mtd); | |
218 | ||
219 | dev_nb++; | |
220 | } | |
221 | ||
222 | if (!dev_nb) { | |
223 | printf("No MTD device found\n"); | |
224 | return CMD_RET_FAILURE; | |
225 | } | |
226 | ||
227 | return CMD_RET_SUCCESS; | |
228 | } | |
229 | ||
230 | static int mtd_special_write_oob(struct mtd_info *mtd, u64 off, | |
231 | struct mtd_oob_ops *io_op, | |
232 | bool write_empty_pages, bool woob) | |
233 | { | |
234 | int ret = 0; | |
235 | ||
236 | /* | |
237 | * By default, do not write an empty page. | |
238 | * Skip it by simulating a successful write. | |
239 | */ | |
240 | if (!write_empty_pages && mtd_oob_write_is_empty(io_op)) { | |
241 | io_op->retlen = mtd->writesize; | |
242 | io_op->oobretlen = woob ? mtd->oobsize : 0; | |
243 | } else { | |
244 | ret = mtd_write_oob(mtd, off, io_op); | |
245 | } | |
246 | ||
247 | return ret; | |
248 | } | |
249 | ||
09140113 SG |
250 | static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc, |
251 | char *const argv[]) | |
5db66b3a | 252 | { |
9671243e BB |
253 | bool dump, read, raw, woob, write_empty_pages, has_pages = false; |
254 | u64 start_off, off, len, remaining, default_len; | |
255 | struct mtd_oob_ops io_op = {}; | |
256 | uint user_addr = 0, npages; | |
257 | const char *cmd = argv[0]; | |
5db66b3a | 258 | struct mtd_info *mtd; |
9671243e BB |
259 | u32 oob_len; |
260 | u8 *buf; | |
261 | int ret; | |
5db66b3a | 262 | |
5db66b3a MR |
263 | if (argc < 2) |
264 | return CMD_RET_USAGE; | |
265 | ||
9671243e BB |
266 | mtd = get_mtd_by_name(argv[1]); |
267 | if (IS_ERR_OR_NULL(mtd)) | |
268 | return CMD_RET_FAILURE; | |
5db66b3a | 269 | |
9671243e BB |
270 | if (mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH) |
271 | has_pages = true; | |
5db66b3a | 272 | |
9671243e BB |
273 | dump = !strncmp(cmd, "dump", 4); |
274 | read = dump || !strncmp(cmd, "read", 4); | |
275 | raw = strstr(cmd, ".raw"); | |
276 | woob = strstr(cmd, ".oob"); | |
277 | write_empty_pages = !has_pages || strstr(cmd, ".dontskipff"); | |
5db66b3a | 278 | |
9671243e BB |
279 | argc -= 2; |
280 | argv += 2; | |
5db66b3a | 281 | |
9671243e BB |
282 | if (!dump) { |
283 | if (!argc) { | |
284 | ret = CMD_RET_USAGE; | |
285 | goto out_put_mtd; | |
5db66b3a MR |
286 | } |
287 | ||
7e5f460e | 288 | user_addr = hextoul(argv[0], NULL); |
9671243e BB |
289 | argc--; |
290 | argv++; | |
291 | } | |
5db66b3a | 292 | |
7e5f460e | 293 | start_off = argc > 0 ? hextoul(argv[0], NULL) : 0; |
9671243e BB |
294 | if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) { |
295 | printf("Offset not aligned with a page (0x%x)\n", | |
296 | mtd->writesize); | |
297 | ret = CMD_RET_FAILURE; | |
298 | goto out_put_mtd; | |
299 | } | |
5db66b3a | 300 | |
9671243e | 301 | default_len = dump ? mtd->writesize : mtd->size; |
7e5f460e | 302 | len = argc > 1 ? hextoul(argv[1], NULL) : default_len; |
9671243e BB |
303 | if (!mtd_is_aligned_with_min_io_size(mtd, len)) { |
304 | len = round_up(len, mtd->writesize); | |
305 | printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n", | |
306 | mtd->writesize, len); | |
307 | } | |
5db66b3a | 308 | |
9671243e BB |
309 | remaining = len; |
310 | npages = mtd_len_to_pages(mtd, len); | |
311 | oob_len = woob ? npages * mtd->oobsize : 0; | |
5db66b3a | 312 | |
9671243e BB |
313 | if (dump) |
314 | buf = kmalloc(len + oob_len, GFP_KERNEL); | |
315 | else | |
316 | buf = map_sysmem(user_addr, 0); | |
317 | ||
318 | if (!buf) { | |
319 | printf("Could not map/allocate the user buffer\n"); | |
320 | ret = CMD_RET_FAILURE; | |
321 | goto out_put_mtd; | |
322 | } | |
323 | ||
324 | if (has_pages) | |
325 | printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n", | |
326 | read ? "Reading" : "Writing", len, npages, start_off, | |
327 | raw ? " [raw]" : "", woob ? " [oob]" : "", | |
328 | !read && write_empty_pages ? " [dontskipff]" : ""); | |
329 | else | |
330 | printf("%s %lld byte(s) at offset 0x%08llx\n", | |
331 | read ? "Reading" : "Writing", len, start_off); | |
332 | ||
333 | io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB; | |
334 | io_op.len = has_pages ? mtd->writesize : len; | |
335 | io_op.ooblen = woob ? mtd->oobsize : 0; | |
336 | io_op.datbuf = buf; | |
337 | io_op.oobbuf = woob ? &buf[len] : NULL; | |
338 | ||
339 | /* Search for the first good block after the given offset */ | |
340 | off = start_off; | |
341 | while (mtd_block_isbad(mtd, off)) | |
342 | off += mtd->erasesize; | |
343 | ||
344 | /* Loop over the pages to do the actual read/write */ | |
345 | while (remaining) { | |
346 | /* Skip the block if it is bad */ | |
347 | if (mtd_is_aligned_with_block_size(mtd, off) && | |
348 | mtd_block_isbad(mtd, off)) { | |
349 | off += mtd->erasesize; | |
350 | continue; | |
5db66b3a MR |
351 | } |
352 | ||
9671243e BB |
353 | if (read) |
354 | ret = mtd_read_oob(mtd, off, &io_op); | |
5db66b3a | 355 | else |
9671243e BB |
356 | ret = mtd_special_write_oob(mtd, off, &io_op, |
357 | write_empty_pages, woob); | |
5db66b3a | 358 | |
9671243e BB |
359 | if (ret) { |
360 | printf("Failure while %s at offset 0x%llx\n", | |
361 | read ? "reading" : "writing", off); | |
362 | break; | |
363 | } | |
5db66b3a | 364 | |
9671243e BB |
365 | off += io_op.retlen; |
366 | remaining -= io_op.retlen; | |
367 | io_op.datbuf += io_op.retlen; | |
368 | io_op.oobbuf += io_op.oobretlen; | |
369 | } | |
5db66b3a | 370 | |
9671243e BB |
371 | if (!ret && dump) |
372 | mtd_dump_device_buf(mtd, start_off, buf, len, woob); | |
5db66b3a | 373 | |
9671243e BB |
374 | if (dump) |
375 | kfree(buf); | |
376 | else | |
377 | unmap_sysmem(buf); | |
5db66b3a | 378 | |
9671243e BB |
379 | if (ret) { |
380 | printf("%s on %s failed with error %d\n", | |
381 | read ? "Read" : "Write", mtd->name, ret); | |
382 | ret = CMD_RET_FAILURE; | |
383 | } else { | |
384 | ret = CMD_RET_SUCCESS; | |
385 | } | |
5db66b3a | 386 | |
9671243e BB |
387 | out_put_mtd: |
388 | put_mtd_device(mtd); | |
5db66b3a | 389 | |
9671243e BB |
390 | return ret; |
391 | } | |
5db66b3a | 392 | |
09140113 SG |
393 | static int do_mtd_erase(struct cmd_tbl *cmdtp, int flag, int argc, |
394 | char *const argv[]) | |
9671243e BB |
395 | { |
396 | struct erase_info erase_op = {}; | |
397 | struct mtd_info *mtd; | |
398 | u64 off, len; | |
399 | bool scrub; | |
b1b147f2 | 400 | int ret = 0; |
5db66b3a | 401 | |
9671243e BB |
402 | if (argc < 2) |
403 | return CMD_RET_USAGE; | |
5db66b3a | 404 | |
9671243e BB |
405 | mtd = get_mtd_by_name(argv[1]); |
406 | if (IS_ERR_OR_NULL(mtd)) | |
407 | return CMD_RET_FAILURE; | |
5db66b3a | 408 | |
9671243e | 409 | scrub = strstr(argv[0], ".dontskipbad"); |
5db66b3a | 410 | |
9671243e BB |
411 | argc -= 2; |
412 | argv += 2; | |
5db66b3a | 413 | |
7e5f460e SG |
414 | off = argc > 0 ? hextoul(argv[0], NULL) : 0; |
415 | len = argc > 1 ? hextoul(argv[1], NULL) : mtd->size; | |
5db66b3a | 416 | |
9671243e BB |
417 | if (!mtd_is_aligned_with_block_size(mtd, off)) { |
418 | printf("Offset not aligned with a block (0x%x)\n", | |
419 | mtd->erasesize); | |
420 | ret = CMD_RET_FAILURE; | |
421 | goto out_put_mtd; | |
422 | } | |
5db66b3a | 423 | |
9671243e BB |
424 | if (!mtd_is_aligned_with_block_size(mtd, len)) { |
425 | printf("Size not a multiple of a block (0x%x)\n", | |
426 | mtd->erasesize); | |
427 | ret = CMD_RET_FAILURE; | |
428 | goto out_put_mtd; | |
429 | } | |
5db66b3a | 430 | |
9671243e BB |
431 | printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n", |
432 | off, off + len - 1, mtd_div_by_eb(len, mtd)); | |
5db66b3a | 433 | |
9671243e BB |
434 | erase_op.mtd = mtd; |
435 | erase_op.addr = off; | |
b1b147f2 | 436 | erase_op.len = mtd->erasesize; |
9671243e | 437 | erase_op.scrub = scrub; |
5db66b3a | 438 | |
b1b147f2 | 439 | while (len) { |
9671243e | 440 | ret = mtd_erase(mtd, &erase_op); |
5db66b3a | 441 | |
b1b147f2 PD |
442 | if (ret) { |
443 | /* Abort if its not a bad block error */ | |
444 | if (ret != -EIO) | |
445 | break; | |
446 | printf("Skipping bad block at 0x%08llx\n", | |
447 | erase_op.addr); | |
448 | } | |
9671243e | 449 | |
b1b147f2 PD |
450 | len -= mtd->erasesize; |
451 | erase_op.addr += mtd->erasesize; | |
9671243e BB |
452 | } |
453 | ||
454 | if (ret && ret != -EIO) | |
455 | ret = CMD_RET_FAILURE; | |
456 | else | |
457 | ret = CMD_RET_SUCCESS; | |
458 | ||
459 | out_put_mtd: | |
460 | put_mtd_device(mtd); | |
461 | ||
462 | return ret; | |
463 | } | |
464 | ||
09140113 SG |
465 | static int do_mtd_bad(struct cmd_tbl *cmdtp, int flag, int argc, |
466 | char *const argv[]) | |
9671243e BB |
467 | { |
468 | struct mtd_info *mtd; | |
469 | loff_t off; | |
470 | ||
471 | if (argc < 2) | |
5db66b3a | 472 | return CMD_RET_USAGE; |
9671243e BB |
473 | |
474 | mtd = get_mtd_by_name(argv[1]); | |
475 | if (IS_ERR_OR_NULL(mtd)) | |
476 | return CMD_RET_FAILURE; | |
477 | ||
478 | if (!mtd_can_have_bb(mtd)) { | |
479 | printf("Only NAND-based devices can have bad blocks\n"); | |
480 | goto out_put_mtd; | |
481 | } | |
482 | ||
483 | printf("MTD device %s bad blocks list:\n", mtd->name); | |
484 | for (off = 0; off < mtd->size; off += mtd->erasesize) { | |
485 | if (mtd_block_isbad(mtd, off)) | |
486 | printf("\t0x%08llx\n", off); | |
5db66b3a MR |
487 | } |
488 | ||
9671243e BB |
489 | out_put_mtd: |
490 | put_mtd_device(mtd); | |
491 | ||
5db66b3a MR |
492 | return CMD_RET_SUCCESS; |
493 | } | |
494 | ||
9671243e | 495 | #ifdef CONFIG_AUTO_COMPLETE |
09140113 | 496 | static int mtd_name_complete(int argc, char *const argv[], char last_char, |
9671243e BB |
497 | int maxv, char *cmdv[]) |
498 | { | |
499 | int len = 0, n_found = 0; | |
500 | struct mtd_info *mtd; | |
501 | ||
502 | argc--; | |
503 | argv++; | |
504 | ||
505 | if (argc > 1 || | |
506 | (argc == 1 && (last_char == '\0' || isblank(last_char)))) | |
507 | return 0; | |
508 | ||
509 | if (argc) | |
510 | len = strlen(argv[0]); | |
511 | ||
512 | mtd_for_each_device(mtd) { | |
513 | if (argc && | |
514 | (len > strlen(mtd->name) || | |
515 | strncmp(argv[0], mtd->name, len))) | |
516 | continue; | |
517 | ||
518 | if (n_found >= maxv - 2) { | |
519 | cmdv[n_found++] = "..."; | |
520 | break; | |
521 | } | |
522 | ||
523 | cmdv[n_found++] = mtd->name; | |
524 | } | |
525 | ||
526 | cmdv[n_found] = NULL; | |
527 | ||
528 | return n_found; | |
529 | } | |
530 | #endif /* CONFIG_AUTO_COMPLETE */ | |
531 | ||
5db66b3a | 532 | #ifdef CONFIG_SYS_LONGHELP |
a645831c | 533 | static char mtd_help_text[] = |
5db66b3a MR |
534 | "- generic operations on memory technology devices\n\n" |
535 | "mtd list\n" | |
536 | "mtd read[.raw][.oob] <name> <addr> [<off> [<size>]]\n" | |
537 | "mtd dump[.raw][.oob] <name> [<off> [<size>]]\n" | |
538 | "mtd write[.raw][.oob][.dontskipff] <name> <addr> [<off> [<size>]]\n" | |
539 | "mtd erase[.dontskipbad] <name> [<off> [<size>]]\n" | |
540 | "\n" | |
541 | "Specific functions:\n" | |
542 | "mtd bad <name>\n" | |
543 | "\n" | |
544 | "With:\n" | |
e41a2bc6 | 545 | "\t<name>: NAND partition/chip name (or corresponding DM device name or OF path)\n" |
5db66b3a MR |
546 | "\t<addr>: user address from/to which data will be retrieved/stored\n" |
547 | "\t<off>: offset in <name> in bytes (default: start of the part)\n" | |
548 | "\t\t* must be block-aligned for erase\n" | |
549 | "\t\t* must be page-aligned otherwise\n" | |
550 | "\t<size>: length of the operation in bytes (default: the entire device)\n" | |
551 | "\t\t* must be a multiple of a block for erase\n" | |
552 | "\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n" | |
553 | "\n" | |
a645831c | 554 | "The .dontskipff option forces writing empty pages, don't use it if unsure.\n"; |
5db66b3a | 555 | #endif |
5db66b3a | 556 | |
9671243e BB |
557 | U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text, |
558 | U_BOOT_SUBCMD_MKENT(list, 1, 1, do_mtd_list), | |
559 | U_BOOT_SUBCMD_MKENT_COMPLETE(read, 5, 0, do_mtd_io, | |
560 | mtd_name_complete), | |
561 | U_BOOT_SUBCMD_MKENT_COMPLETE(write, 5, 0, do_mtd_io, | |
562 | mtd_name_complete), | |
563 | U_BOOT_SUBCMD_MKENT_COMPLETE(dump, 4, 0, do_mtd_io, | |
564 | mtd_name_complete), | |
565 | U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase, | |
566 | mtd_name_complete), | |
567 | U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad, | |
568 | mtd_name_complete)); |