Merge branch 'master' of git://git.denx.de/u-boot-sparc
[platform/kernel/u-boot.git] / common / cmd_onenand.c
1 /*
2  *  U-Boot command for OneNAND support
3  *
4  *  Copyright (C) 2005-2008 Samsung Electronics
5  *  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>
14 #include <malloc.h>
15
16 #include <linux/mtd/compat.h>
17 #include <linux/mtd/mtd.h>
18 #include <linux/mtd/onenand.h>
19
20 #include <asm/io.h>
21
22 static struct mtd_info *mtd;
23
24 static loff_t next_ofs;
25 static loff_t skip_ofs;
26
27 static inline int str2long(char *p, ulong *num)
28 {
29         char *endptr;
30
31         *num = simple_strtoul(p, &endptr, 16);
32         return (*p != '\0' && *endptr == '\0') ? 1 : 0;
33 }
34
35 static int arg_off_size(int argc, char * const argv[], ulong *off, size_t *size)
36 {
37         if (argc >= 1) {
38                 if (!(str2long(argv[0], off))) {
39                         printf("'%s' is not a number\n", argv[0]);
40                         return -1;
41                 }
42         } else {
43                 *off = 0;
44         }
45
46         if (argc >= 2) {
47                 if (!(str2long(argv[1], (ulong *)size))) {
48                         printf("'%s' is not a number\n", argv[1]);
49                         return -1;
50                 }
51         } else {
52                 *size = mtd->size - *off;
53         }
54
55         if ((*off + *size) > mtd->size) {
56                 printf("total chip size (0x%llx) exceeded!\n", mtd->size);
57                 return -1;
58         }
59
60         if (*size == mtd->size)
61                 puts("whole chip\n");
62         else
63                 printf("offset 0x%lx, size 0x%x\n", *off, *size);
64
65         return 0;
66 }
67
68 static int onenand_block_read(loff_t from, size_t len,
69                               size_t *retlen, u_char *buf, int oob)
70 {
71         struct onenand_chip *this = mtd->priv;
72         int blocks = (int) len >> this->erase_shift;
73         int blocksize = (1 << this->erase_shift);
74         loff_t ofs = from;
75         struct mtd_oob_ops ops = {
76                 .retlen         = 0,
77         };
78         int ret;
79
80         if (oob)
81                 ops.ooblen = blocksize;
82         else
83                 ops.len = blocksize;
84
85         while (blocks) {
86                 ret = mtd->block_isbad(mtd, ofs);
87                 if (ret) {
88                         printk("Bad blocks %d at 0x%x\n",
89                                (u32)(ofs >> this->erase_shift), (u32)ofs);
90                         ofs += blocksize;
91                         continue;
92                 }
93
94                 if (oob)
95                         ops.oobbuf = buf;
96                 else
97                         ops.datbuf = buf;
98
99                 ops.retlen = 0;
100                 ret = mtd->read_oob(mtd, ofs, &ops);
101                 if (ret) {
102                         printk("Read failed 0x%x, %d\n", (u32)ofs, ret);
103                         ofs += blocksize;
104                         continue;
105                 }
106                 ofs += blocksize;
107                 buf += blocksize;
108                 blocks--;
109                 *retlen += ops.retlen;
110         }
111
112         return 0;
113 }
114
115 static int onenand_block_write(loff_t to, size_t len,
116                                size_t *retlen, const u_char * buf)
117 {
118         struct onenand_chip *this = mtd->priv;
119         int blocks = len >> this->erase_shift;
120         int blocksize = (1 << this->erase_shift);
121         loff_t ofs;
122         size_t _retlen = 0;
123         int ret;
124
125         if (to == next_ofs) {
126                 next_ofs = to + len;
127                 to += skip_ofs;
128         } else {
129                 next_ofs = to + len;
130                 skip_ofs = 0;
131         }
132         ofs = to;
133
134         while (blocks) {
135                 ret = mtd->block_isbad(mtd, ofs);
136                 if (ret) {
137                         printk("Bad blocks %d at 0x%x\n",
138                                (u32)(ofs >> this->erase_shift), (u32)ofs);
139                         skip_ofs += blocksize;
140                         goto next;
141                 }
142
143                 ret = mtd->write(mtd, ofs, blocksize, &_retlen, buf);
144                 if (ret) {
145                         printk("Write failed 0x%x, %d", (u32)ofs, ret);
146                         skip_ofs += blocksize;
147                         goto next;
148                 }
149
150                 buf += blocksize;
151                 blocks--;
152                 *retlen += _retlen;
153 next:
154                 ofs += blocksize;
155         }
156
157         return 0;
158 }
159
160 static int onenand_block_erase(u32 start, u32 size, int force)
161 {
162         struct onenand_chip *this = mtd->priv;
163         struct erase_info instr = {
164                 .callback       = NULL,
165         };
166         loff_t ofs;
167         int ret;
168         int blocksize = 1 << this->erase_shift;
169
170         for (ofs = start; ofs < (start + size); ofs += blocksize) {
171                 ret = mtd->block_isbad(mtd, ofs);
172                 if (ret && !force) {
173                         printf("Skip erase bad block %d at 0x%x\n",
174                                (u32)(ofs >> this->erase_shift), (u32)ofs);
175                         continue;
176                 }
177
178                 instr.addr = ofs;
179                 instr.len = blocksize;
180                 instr.priv = force;
181                 instr.mtd = mtd;
182                 ret = mtd->erase(mtd, &instr);
183                 if (ret) {
184                         printf("erase failed block %d at 0x%x\n",
185                                (u32)(ofs >> this->erase_shift), (u32)ofs);
186                         continue;
187                 }
188         }
189
190         return 0;
191 }
192
193 static int onenand_block_test(u32 start, u32 size)
194 {
195         struct onenand_chip *this = mtd->priv;
196         struct erase_info instr = {
197                 .callback       = NULL,
198                 .priv           = 0,
199         };
200
201         int blocks;
202         loff_t ofs;
203         int blocksize = 1 << this->erase_shift;
204         int start_block, end_block;
205         size_t retlen;
206         u_char *buf;
207         u_char *verify_buf;
208         int ret;
209
210         buf = malloc(blocksize);
211         if (!buf) {
212                 printf("Not enough malloc space available!\n");
213                 return -1;
214         }
215
216         verify_buf = malloc(blocksize);
217         if (!verify_buf) {
218                 printf("Not enough malloc space available!\n");
219                 return -1;
220         }
221
222         start_block = start >> this->erase_shift;
223         end_block = (start + size) >> this->erase_shift;
224
225         /* Protect boot-loader from badblock testing */
226         if (start_block < 2)
227                 start_block = 2;
228
229         if (end_block > (mtd->size >> this->erase_shift))
230                 end_block = mtd->size >> this->erase_shift;
231
232         blocks = start_block;
233         ofs = start;
234         while (blocks < end_block) {
235                 printf("\rTesting block %d at 0x%x", (u32)(ofs >> this->erase_shift), (u32)ofs);
236
237                 ret = mtd->block_isbad(mtd, ofs);
238                 if (ret) {
239                         printf("Skip erase bad block %d at 0x%x\n",
240                                (u32)(ofs >> this->erase_shift), (u32)ofs);
241                         goto next;
242                 }
243
244                 instr.addr = ofs;
245                 instr.len = blocksize;
246                 ret = mtd->erase(mtd, &instr);
247                 if (ret) {
248                         printk("Erase failed 0x%x, %d\n", (u32)ofs, ret);
249                         goto next;
250                 }
251
252                 ret = mtd->write(mtd, ofs, blocksize, &retlen, buf);
253                 if (ret) {
254                         printk("Write failed 0x%x, %d\n", (u32)ofs, ret);
255                         goto next;
256                 }
257
258                 ret = mtd->read(mtd, ofs, blocksize, &retlen, verify_buf);
259                 if (ret) {
260                         printk("Read failed 0x%x, %d\n", (u32)ofs, ret);
261                         goto next;
262                 }
263
264                 if (memcmp(buf, verify_buf, blocksize))
265                         printk("\nRead/Write test failed at 0x%x\n", (u32)ofs);
266
267 next:
268                 ofs += blocksize;
269                 blocks++;
270         }
271         printf("...Done\n");
272
273         free(buf);
274         free(verify_buf);
275
276         return 0;
277 }
278
279 static int onenand_dump(struct mtd_info *mtd, ulong off, int only_oob)
280 {
281         int i;
282         u_char *datbuf, *oobbuf, *p;
283         struct mtd_oob_ops ops;
284         loff_t addr;
285
286         datbuf = malloc(mtd->writesize + mtd->oobsize);
287         oobbuf = malloc(mtd->oobsize);
288         if (!datbuf || !oobbuf) {
289                 puts("No memory for page buffer\n");
290                 return 1;
291         }
292         off &= ~(mtd->writesize - 1);
293         addr = (loff_t) off;
294         memset(&ops, 0, sizeof(ops));
295         ops.datbuf = datbuf;
296         ops.oobbuf = oobbuf; /* must exist, but oob data will be appended to ops.datbuf */
297         ops.len = mtd->writesize;
298         ops.ooblen = mtd->oobsize;
299         ops.retlen = 0;
300         i = mtd->read_oob(mtd, addr, &ops);
301         if (i < 0) {
302                 printf("Error (%d) reading page %08lx\n", i, off);
303                 free(datbuf);
304                 free(oobbuf);
305                 return 1;
306         }
307         printf("Page %08lx dump:\n", off);
308         i = mtd->writesize >> 4;
309         p = datbuf;
310
311         while (i--) {
312                 if (!only_oob)
313                         printf("\t%02x %02x %02x %02x %02x %02x %02x %02x"
314                                "  %02x %02x %02x %02x %02x %02x %02x %02x\n",
315                                p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
316                                p[8], p[9], p[10], p[11], p[12], p[13], p[14],
317                                p[15]);
318                 p += 16;
319         }
320         puts("OOB:\n");
321         i = mtd->oobsize >> 3;
322         while (i--) {
323                 printf("\t%02x %02x %02x %02x %02x %02x %02x %02x\n",
324                        p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
325                 p += 8;
326         }
327         free(datbuf);
328         free(oobbuf);
329
330         return 0;
331 }
332
333 static int do_onenand_info(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
334 {
335         printf("%s\n", mtd->name);
336         return 0;
337 }
338
339 static int do_onenand_bad(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
340 {
341         ulong ofs;
342
343         mtd = &onenand_mtd;
344         /* Currently only one OneNAND device is supported */
345         printf("\nDevice %d bad blocks:\n", 0);
346         for (ofs = 0; ofs < mtd->size; ofs += mtd->erasesize) {
347                 if (mtd->block_isbad(mtd, ofs))
348                         printf("  %08x\n", (u32)ofs);
349         }
350
351         return 0;
352 }
353
354 static int do_onenand_read(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
355 {
356         char *s;
357         int oob = 0;
358         ulong addr, ofs;
359         size_t len;
360         int ret = 0;
361         size_t retlen = 0;
362
363         if (argc < 3)
364                 return cmd_usage(cmdtp);
365
366         s = strchr(argv[0], '.');
367         if ((s != NULL) && (!strcmp(s, ".oob")))
368                 oob = 1;
369
370         addr = (ulong)simple_strtoul(argv[1], NULL, 16);
371
372         printf("\nOneNAND read: ");
373         if (arg_off_size(argc - 2, argv + 2, &ofs, &len) != 0)
374                 return 1;
375
376         ret = onenand_block_read(ofs, len, &retlen, (u8 *)addr, oob);
377
378         printf(" %d bytes read: %s\n", retlen, ret ? "ERROR" : "OK");
379
380         return ret == 0 ? 0 : 1;
381 }
382
383 static int do_onenand_write(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
384 {
385         ulong addr, ofs;
386         size_t len;
387         int ret = 0;
388         size_t retlen = 0;
389
390         if (argc < 3)
391                 return cmd_usage(cmdtp);
392
393         addr = (ulong)simple_strtoul(argv[1], NULL, 16);
394
395         printf("\nOneNAND write: ");
396         if (arg_off_size(argc - 2, argv + 2, &ofs, &len) != 0)
397                 return 1;
398
399         ret = onenand_block_write(ofs, len, &retlen, (u8 *)addr);
400
401         printf(" %d bytes written: %s\n", retlen, ret ? "ERROR" : "OK");
402
403         return ret == 0 ? 0 : 1;
404 }
405
406 static int do_onenand_erase(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
407 {
408         ulong ofs;
409         int ret = 0;
410         size_t len;
411         int force;
412
413         /*
414          * Syntax is:
415          *   0       1     2       3    4
416          *   onenand erase [force] [off size]
417          */
418         argc--;
419         argv++;
420         if (argc)
421         {
422                 if (!strcmp("force", argv[0]))
423                 {
424                         force = 1;
425                         argc--;
426                         argv++;
427                 }
428         }
429         printf("\nOneNAND erase: ");
430
431         /* skip first two or three arguments, look for offset and size */
432         if (arg_off_size(argc, argv, &ofs, &len) != 0)
433                 return 1;
434
435         ret = onenand_block_erase(ofs, len, force);
436
437         printf("%s\n", ret ? "ERROR" : "OK");
438
439         return ret == 0 ? 0 : 1;
440 }
441
442 static int do_onenand_test(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
443 {
444         ulong ofs;
445         int ret = 0;
446         size_t len;
447
448         /*
449          * Syntax is:
450          *   0       1     2       3    4
451          *   onenand test [force] [off size]
452          */
453
454         printf("\nOneNAND test: ");
455
456         /* skip first two or three arguments, look for offset and size */
457         if (arg_off_size(argc - 1, argv + 1, &ofs, &len) != 0)
458                 return 1;
459
460         ret = onenand_block_test(ofs, len);
461
462         printf("%s\n", ret ? "ERROR" : "OK");
463
464         return ret == 0 ? 0 : 1;
465 }
466
467 static int do_onenand_dump(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
468 {
469         ulong ofs;
470         int ret = 0;
471         char *s;
472
473         if (argc < 2)
474                 return cmd_usage(cmdtp);
475
476         s = strchr(argv[0], '.');
477         ofs = (int)simple_strtoul(argv[1], NULL, 16);
478
479         if (s != NULL && strcmp(s, ".oob") == 0)
480                 ret = onenand_dump(mtd, ofs, 1);
481         else
482                 ret = onenand_dump(mtd, ofs, 0);
483
484         return ret == 0 ? 1 : 0;
485 }
486
487 static int do_onenand_markbad(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
488 {
489         int ret = 0;
490         ulong addr;
491
492         argc -= 2;
493         argv += 2;
494
495         if (argc <= 0)
496                 return cmd_usage(cmdtp);
497
498         while (argc > 0) {
499                 addr = simple_strtoul(*argv, NULL, 16);
500
501                 if (mtd->block_markbad(mtd, addr)) {
502                         printf("block 0x%08lx NOT marked "
503                                 "as bad! ERROR %d\n",
504                                 addr, ret);
505                         ret = 1;
506                 } else {
507                         printf("block 0x%08lx successfully "
508                                 "marked as bad\n",
509                                 addr);
510                 }
511                 --argc;
512                 ++argv;
513         }
514         return ret;
515 }
516
517 static cmd_tbl_t cmd_onenand_sub[] = {
518         U_BOOT_CMD_MKENT(info, 1, 0, do_onenand_info, "", ""),
519         U_BOOT_CMD_MKENT(bad, 1, 0, do_onenand_bad, "", ""),
520         U_BOOT_CMD_MKENT(read, 4, 0, do_onenand_read, "", ""),
521         U_BOOT_CMD_MKENT(write, 4, 0, do_onenand_write, "", ""),
522         U_BOOT_CMD_MKENT(erase, 3, 0, do_onenand_erase, "", ""),
523         U_BOOT_CMD_MKENT(test, 3, 0, do_onenand_test, "", ""),
524         U_BOOT_CMD_MKENT(dump, 2, 0, do_onenand_dump, "", ""),
525         U_BOOT_CMD_MKENT(markbad, CONFIG_SYS_MAXARGS, 0, do_onenand_markbad, "", ""),
526 };
527
528 #ifndef CONFIG_RELOC_FIXUP_WORKS
529 void onenand_reloc(void) {
530         fixup_cmdtable(cmd_onenand_sub, ARRAY_SIZE(cmd_onenand_sub));
531 }
532 #endif
533
534 static int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
535 {
536         cmd_tbl_t *c;
537
538         mtd = &onenand_mtd;
539
540         /* Strip off leading 'onenand' command argument */
541         argc--;
542         argv++;
543
544         c = find_cmd_tbl(argv[0], &cmd_onenand_sub[0], ARRAY_SIZE(cmd_onenand_sub));
545
546         if (c)
547                 return c->cmd(cmdtp, flag, argc, argv);
548         else
549                 return cmd_usage(cmdtp);
550 }
551
552 U_BOOT_CMD(
553         onenand,        CONFIG_SYS_MAXARGS,     1,      do_onenand,
554         "OneNAND sub-system",
555         "info - show available OneNAND devices\n"
556         "onenand bad - show bad blocks\n"
557         "onenand read[.oob] addr off size\n"
558         "onenand write addr off size\n"
559         "    read/write 'size' bytes starting at offset 'off'\n"
560         "    to/from memory address 'addr', skipping bad blocks.\n"
561         "onenand erase [force] [off size] - erase 'size' bytes from\n"
562         "onenand test [off size] - test 'size' bytes from\n"
563         "    offset 'off' (entire device if not specified)\n"
564         "onenand dump[.oob] off - dump page\n"
565         "onenand markbad off [...] - mark bad block(s) at offset (UNSAFE)"
566 );