blob: e8f06d7f433bad70f62648fc7cf96ea5cc596fec [file] [log] [blame]
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +09001/*
2 * U-Boot command for OneNAND support
3 *
Stefan Roesec438ea12008-11-12 13:47:24 +01004 * Copyright (C) 2005-2008 Samsung Electronics
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +09005 * Kyungmin Park <kyungmin.park@samsung.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#include <common.h>
13#include <command.h>
Stefan Roesec438ea12008-11-12 13:47:24 +010014#include <malloc.h>
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +090015
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +090016#include <linux/mtd/compat.h>
17#include <linux/mtd/mtd.h>
18#include <linux/mtd/onenand.h>
19
20#include <asm/io.h>
21
Stefan Roese8d2effe2009-05-11 16:03:55 +020022#if !defined(CONFIG_SYS_64BIT_VSPRINTF)
23#warning Please define CONFIG_SYS_64BIT_VSPRINTF for correct output!
24#endif
25
Stefan Roesec438ea12008-11-12 13:47:24 +010026static struct mtd_info *mtd;
27
28static loff_t next_ofs;
29static loff_t skip_ofs;
30
31static inline int str2long(char *p, ulong *num)
32{
33 char *endptr;
34
35 *num = simple_strtoul(p, &endptr, 16);
36 return (*p != '\0' && *endptr == '\0') ? 1 : 0;
37}
38
39static int arg_off_size(int argc, char *argv[], ulong *off, size_t *size)
40{
41 if (argc >= 1) {
42 if (!(str2long(argv[0], off))) {
43 printf("'%s' is not a number\n", argv[0]);
44 return -1;
45 }
46 } else {
47 *off = 0;
48 }
49
50 if (argc >= 2) {
51 if (!(str2long(argv[1], (ulong *)size))) {
52 printf("'%s' is not a number\n", argv[1]);
53 return -1;
54 }
55 } else {
56 *size = mtd->size - *off;
57 }
58
59 if ((*off + *size) > mtd->size) {
Stefan Roese8d2effe2009-05-11 16:03:55 +020060 printf("total chip size (0x%llx) exceeded!\n", mtd->size);
Stefan Roesec438ea12008-11-12 13:47:24 +010061 return -1;
62 }
63
64 if (*size == mtd->size)
65 puts("whole chip\n");
66 else
67 printf("offset 0x%lx, size 0x%x\n", *off, *size);
68
69 return 0;
70}
71
72static int onenand_block_read(loff_t from, size_t len,
73 size_t *retlen, u_char *buf, int oob)
74{
75 struct onenand_chip *this = mtd->priv;
76 int blocks = (int) len >> this->erase_shift;
77 int blocksize = (1 << this->erase_shift);
78 loff_t ofs = from;
79 struct mtd_oob_ops ops = {
80 .retlen = 0,
81 };
82 int ret;
83
84 if (oob)
85 ops.ooblen = blocksize;
86 else
87 ops.len = blocksize;
88
89 while (blocks) {
90 ret = mtd->block_isbad(mtd, ofs);
91 if (ret) {
92 printk("Bad blocks %d at 0x%x\n",
93 (u32)(ofs >> this->erase_shift), (u32)ofs);
94 ofs += blocksize;
95 continue;
96 }
97
98 if (oob)
99 ops.oobbuf = buf;
100 else
101 ops.datbuf = buf;
102
103 ops.retlen = 0;
104 ret = mtd->read_oob(mtd, ofs, &ops);
105 if (ret) {
106 printk("Read failed 0x%x, %d\n", (u32)ofs, ret);
107 ofs += blocksize;
108 continue;
109 }
110 ofs += blocksize;
111 buf += blocksize;
112 blocks--;
113 *retlen += ops.retlen;
114 }
115
116 return 0;
117}
118
119static int onenand_block_write(loff_t to, size_t len,
120 size_t *retlen, const u_char * buf)
121{
122 struct onenand_chip *this = mtd->priv;
123 int blocks = len >> this->erase_shift;
124 int blocksize = (1 << this->erase_shift);
125 loff_t ofs;
126 size_t _retlen = 0;
127 int ret;
128
129 if (to == next_ofs) {
130 next_ofs = to + len;
131 to += skip_ofs;
132 } else {
133 next_ofs = to + len;
134 skip_ofs = 0;
135 }
136 ofs = to;
137
138 while (blocks) {
139 ret = mtd->block_isbad(mtd, ofs);
140 if (ret) {
141 printk("Bad blocks %d at 0x%x\n",
142 (u32)(ofs >> this->erase_shift), (u32)ofs);
143 skip_ofs += blocksize;
144 goto next;
145 }
146
147 ret = mtd->write(mtd, ofs, blocksize, &_retlen, buf);
148 if (ret) {
149 printk("Write failed 0x%x, %d", (u32)ofs, ret);
150 skip_ofs += blocksize;
151 goto next;
152 }
153
154 buf += blocksize;
155 blocks--;
156 *retlen += _retlen;
157next:
158 ofs += blocksize;
159 }
160
161 return 0;
162}
163
164static int onenand_block_erase(u32 start, u32 size, int force)
165{
166 struct onenand_chip *this = mtd->priv;
167 struct erase_info instr = {
168 .callback = NULL,
169 };
170 loff_t ofs;
171 int ret;
172 int blocksize = 1 << this->erase_shift;
173
174 for (ofs = start; ofs < (start + size); ofs += blocksize) {
175 ret = mtd->block_isbad(mtd, ofs);
176 if (ret && !force) {
177 printf("Skip erase bad block %d at 0x%x\n",
178 (u32)(ofs >> this->erase_shift), (u32)ofs);
179 continue;
180 }
181
182 instr.addr = ofs;
183 instr.len = blocksize;
184 instr.priv = force;
185 instr.mtd = mtd;
186 ret = mtd->erase(mtd, &instr);
187 if (ret) {
188 printf("erase failed block %d at 0x%x\n",
189 (u32)(ofs >> this->erase_shift), (u32)ofs);
190 continue;
191 }
192 }
193
194 return 0;
195}
196
197static int onenand_block_test(u32 start, u32 size)
198{
199 struct onenand_chip *this = mtd->priv;
200 struct erase_info instr = {
201 .callback = NULL,
202 .priv = 0,
203 };
204
205 int blocks;
206 loff_t ofs;
207 int blocksize = 1 << this->erase_shift;
208 int start_block, end_block;
209 size_t retlen;
210 u_char *buf;
211 u_char *verify_buf;
212 int ret;
213
214 buf = malloc(blocksize);
215 if (!buf) {
216 printf("Not enough malloc space available!\n");
217 return -1;
218 }
219
220 verify_buf = malloc(blocksize);
221 if (!verify_buf) {
222 printf("Not enough malloc space available!\n");
223 return -1;
224 }
225
226 start_block = start >> this->erase_shift;
227 end_block = (start + size) >> this->erase_shift;
228
229 /* Protect boot-loader from badblock testing */
230 if (start_block < 2)
231 start_block = 2;
232
233 if (end_block > (mtd->size >> this->erase_shift))
234 end_block = mtd->size >> this->erase_shift;
235
236 blocks = start_block;
237 ofs = start;
238 while (blocks < end_block) {
239 printf("\rTesting block %d at 0x%x", (u32)(ofs >> this->erase_shift), (u32)ofs);
240
241 ret = mtd->block_isbad(mtd, ofs);
242 if (ret) {
243 printf("Skip erase bad block %d at 0x%x\n",
244 (u32)(ofs >> this->erase_shift), (u32)ofs);
245 goto next;
246 }
247
248 instr.addr = ofs;
249 instr.len = blocksize;
250 ret = mtd->erase(mtd, &instr);
251 if (ret) {
252 printk("Erase failed 0x%x, %d\n", (u32)ofs, ret);
253 goto next;
254 }
255
256 ret = mtd->write(mtd, ofs, blocksize, &retlen, buf);
257 if (ret) {
258 printk("Write failed 0x%x, %d\n", (u32)ofs, ret);
259 goto next;
260 }
261
262 ret = mtd->read(mtd, ofs, blocksize, &retlen, verify_buf);
263 if (ret) {
264 printk("Read failed 0x%x, %d\n", (u32)ofs, ret);
265 goto next;
266 }
267
268 if (memcmp(buf, verify_buf, blocksize))
269 printk("\nRead/Write test failed at 0x%x\n", (u32)ofs);
270
271next:
272 ofs += blocksize;
273 blocks++;
274 }
275 printf("...Done\n");
276
277 free(buf);
278 free(verify_buf);
279
280 return 0;
281}
282
283static int onenand_dump(struct mtd_info *mtd, ulong off, int only_oob)
284{
285 int i;
286 u_char *datbuf, *oobbuf, *p;
287 struct mtd_oob_ops ops;
288 loff_t addr;
289
290 datbuf = malloc(mtd->writesize + mtd->oobsize);
291 oobbuf = malloc(mtd->oobsize);
292 if (!datbuf || !oobbuf) {
293 puts("No memory for page buffer\n");
294 return 1;
295 }
296 off &= ~(mtd->writesize - 1);
297 addr = (loff_t) off;
298 memset(&ops, 0, sizeof(ops));
299 ops.datbuf = datbuf;
300 ops.oobbuf = oobbuf; /* must exist, but oob data will be appended to ops.datbuf */
301 ops.len = mtd->writesize;
302 ops.ooblen = mtd->oobsize;
303 ops.retlen = 0;
304 i = mtd->read_oob(mtd, addr, &ops);
305 if (i < 0) {
306 printf("Error (%d) reading page %08lx\n", i, off);
307 free(datbuf);
308 free(oobbuf);
309 return 1;
310 }
311 printf("Page %08lx dump:\n", off);
312 i = mtd->writesize >> 4;
313 p = datbuf;
314
315 while (i--) {
316 if (!only_oob)
317 printf("\t%02x %02x %02x %02x %02x %02x %02x %02x"
318 " %02x %02x %02x %02x %02x %02x %02x %02x\n",
319 p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
320 p[8], p[9], p[10], p[11], p[12], p[13], p[14],
321 p[15]);
322 p += 16;
323 }
324 puts("OOB:\n");
325 i = mtd->oobsize >> 3;
326 while (i--) {
327 printf("\t%02x %02x %02x %02x %02x %02x %02x %02x\n",
328 p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
329 p += 8;
330 }
331 free(datbuf);
332 free(oobbuf);
333
334 return 0;
335}
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900336
337int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
338{
Stefan Roesec438ea12008-11-12 13:47:24 +0100339 struct onenand_chip *this;
340 int blocksize;
341 ulong addr, ofs;
342 size_t len, retlen = 0;
343 int ret;
344 char *cmd, *s;
345
346 mtd = &onenand_mtd;
347 this = mtd->priv;
348 blocksize = (1 << this->erase_shift);
349
350 cmd = argv[1];
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900351
352 switch (argc) {
353 case 0:
354 case 1:
Stefan Roesec438ea12008-11-12 13:47:24 +0100355 goto usage;
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900356
357 case 2:
Stefan Roesec438ea12008-11-12 13:47:24 +0100358 if (strcmp(cmd, "info") == 0) {
359 printf("%s\n", mtd->name);
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900360 return 0;
361 }
Stefan Roesec438ea12008-11-12 13:47:24 +0100362
363 if (strcmp(cmd, "bad") == 0) {
364 /* Currently only one OneNAND device is supported */
365 printf("\nDevice %d bad blocks:\n", 0);
366 for (ofs = 0; ofs < mtd->size; ofs += mtd->erasesize) {
367 if (mtd->block_isbad(mtd, ofs))
368 printf(" %08x\n", (u32)ofs);
369 }
370
371 return 0;
372 }
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900373
374 default:
375 /* At least 4 args */
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900376
Stefan Roesec438ea12008-11-12 13:47:24 +0100377 /*
378 * Syntax is:
379 * 0 1 2 3 4
380 * onenand erase [force] [off size]
381 */
382 if ((strcmp(cmd, "erase") == 0) || (strcmp(cmd, "test") == 0)) {
383 int force = argc > 2 && !strcmp("force", argv[2]);
384 int o = force ? 3 : 2;
385 int erase;
Kyungmin Parka9da2b42008-03-31 10:40:19 +0900386
Stefan Roesec438ea12008-11-12 13:47:24 +0100387 erase = strcmp(cmd, "erase") == 0; /* 1 = erase, 0 = test */
388 printf("\nOneNAND %s: ", erase ? "erase" : "test");
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900389
Stefan Roesec438ea12008-11-12 13:47:24 +0100390 /* skip first two or three arguments, look for offset and size */
391 if (arg_off_size(argc - o, argv + o, &ofs, &len) != 0)
392 return 1;
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900393
Stefan Roesec438ea12008-11-12 13:47:24 +0100394 if (erase)
395 ret = onenand_block_erase(ofs, len, force);
396 else
397 ret = onenand_block_test(ofs, len);
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900398
Stefan Roesec438ea12008-11-12 13:47:24 +0100399 printf("%s\n", ret ? "ERROR" : "OK");
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900400
Stefan Roesec438ea12008-11-12 13:47:24 +0100401 return ret == 0 ? 0 : 1;
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900402 }
403
Stefan Roesec438ea12008-11-12 13:47:24 +0100404 if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {
405 int read;
406 int oob = 0;
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900407
Stefan Roesec438ea12008-11-12 13:47:24 +0100408 if (argc < 4)
409 goto usage;
Kyungmin Parkbfd7f382008-08-19 08:42:53 +0900410
Stefan Roesec438ea12008-11-12 13:47:24 +0100411 addr = (ulong)simple_strtoul(argv[2], NULL, 16);
412
413 read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
414 printf("\nOneNAND %s: ", read ? "read" : "write");
415 if (arg_off_size(argc - 3, argv + 3, &ofs, &len) != 0)
416 return 1;
417
418 s = strchr(cmd, '.');
419 if ((s != NULL) && (!strcmp(s, ".oob")))
420 oob = 1;
421
422 if (read) {
423 ret = onenand_block_read(ofs, len, &retlen,
424 (u8 *)addr, oob);
Kyungmin Parkbfd7f382008-08-19 08:42:53 +0900425 } else {
Stefan Roesec438ea12008-11-12 13:47:24 +0100426 ret = onenand_block_write(ofs, len, &retlen,
427 (u8 *)addr);
Kyungmin Parkbfd7f382008-08-19 08:42:53 +0900428 }
Kyungmin Parkbfd7f382008-08-19 08:42:53 +0900429
Stefan Roesec438ea12008-11-12 13:47:24 +0100430 printf(" %d bytes %s: %s\n", retlen,
431 read ? "read" : "written", ret ? "ERROR" : "OK");
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900432
Stefan Roesec438ea12008-11-12 13:47:24 +0100433 return ret == 0 ? 0 : 1;
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900434 }
435
Stefan Roesec438ea12008-11-12 13:47:24 +0100436 if (strcmp(cmd, "markbad") == 0) {
437 addr = (ulong)simple_strtoul(argv[2], NULL, 16);
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900438
Stefan Roesec438ea12008-11-12 13:47:24 +0100439 int ret = mtd->block_markbad(mtd, addr);
440 if (ret == 0) {
441 printf("block 0x%08lx successfully marked as bad\n",
442 (ulong) addr);
443 return 0;
444 } else {
445 printf("block 0x%08lx NOT marked as bad! ERROR %d\n",
446 (ulong) addr, ret);
447 }
448 return 1;
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900449 }
450
Stefan Roesec438ea12008-11-12 13:47:24 +0100451 if (strncmp(cmd, "dump", 4) == 0) {
452 if (argc < 3)
453 goto usage;
Kyungmin Parkbfd7f382008-08-19 08:42:53 +0900454
Stefan Roesec438ea12008-11-12 13:47:24 +0100455 s = strchr(cmd, '.');
456 ofs = (int)simple_strtoul(argv[2], NULL, 16);
Kyungmin Parkbfd7f382008-08-19 08:42:53 +0900457
Stefan Roesec438ea12008-11-12 13:47:24 +0100458 if (s != NULL && strcmp(s, ".oob") == 0)
459 ret = onenand_dump(mtd, ofs, 1);
460 else
461 ret = onenand_dump(mtd, ofs, 0);
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900462
Stefan Roesec438ea12008-11-12 13:47:24 +0100463 return ret == 0 ? 1 : 0;
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900464 }
465
466 break;
467 }
468
469 return 0;
Stefan Roesec438ea12008-11-12 13:47:24 +0100470
471usage:
Peter Tyser62c3ae72009-01-27 18:03:10 -0600472 cmd_usage(cmdtp);
Stefan Roesec438ea12008-11-12 13:47:24 +0100473 return 1;
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900474}
475
476U_BOOT_CMD(
477 onenand, 6, 1, do_onenand,
Peter Tyser2fb26042009-01-27 18:03:12 -0600478 "OneNAND sub-system",
Stefan Roesec438ea12008-11-12 13:47:24 +0100479 "info - show available OneNAND devices\n"
480 "onenand bad - show bad blocks\n"
481 "onenand read[.oob] addr off size\n"
482 "onenand write[.oob] addr off size\n"
483 " read/write 'size' bytes starting at offset 'off'\n"
484 " to/from memory address 'addr', skipping bad blocks.\n"
485 "onenand erase [force] [off size] - erase 'size' bytes from\n"
486 "onenand test [off size] - test 'size' bytes from\n"
487 " offset 'off' (entire device if not specified)\n"
488 "onenand dump[.oob] off - dump page\n"
489 "onenand markbad off - mark bad block at offset (UNSAFE)\n"
Kyungmin Parkd7e8ce12007-09-10 17:15:14 +0900490);