armv7: fix relocation skip
[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;
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         p = oobbuf;
323
324         while (i--) {
325                 printf("\t%02x %02x %02x %02x %02x %02x %02x %02x\n",
326                        p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
327                 p += 8;
328         }
329         free(datbuf);
330         free(oobbuf);
331
332         return 0;
333 }
334
335 static int do_onenand_info(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
336 {
337         printf("%s\n", mtd->name);
338         return 0;
339 }
340
341 static int do_onenand_bad(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
342 {
343         ulong ofs;
344
345         mtd = &onenand_mtd;
346         /* Currently only one OneNAND device is supported */
347         printf("\nDevice %d bad blocks:\n", 0);
348         for (ofs = 0; ofs < mtd->size; ofs += mtd->erasesize) {
349                 if (mtd->block_isbad(mtd, ofs))
350                         printf("  %08x\n", (u32)ofs);
351         }
352
353         return 0;
354 }
355
356 static int do_onenand_read(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
357 {
358         char *s;
359         int oob = 0;
360         ulong addr, ofs;
361         size_t len;
362         int ret = 0;
363         size_t retlen = 0;
364
365         if (argc < 3)
366                 return cmd_usage(cmdtp);
367
368         s = strchr(argv[0], '.');
369         if ((s != NULL) && (!strcmp(s, ".oob")))
370                 oob = 1;
371
372         addr = (ulong)simple_strtoul(argv[1], NULL, 16);
373
374         printf("\nOneNAND read: ");
375         if (arg_off_size(argc - 2, argv + 2, &ofs, &len) != 0)
376                 return 1;
377
378         ret = onenand_block_read(ofs, len, &retlen, (u8 *)addr, oob);
379
380         printf(" %d bytes read: %s\n", retlen, ret ? "ERROR" : "OK");
381
382         return ret == 0 ? 0 : 1;
383 }
384
385 static int do_onenand_write(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
386 {
387         ulong addr, ofs;
388         size_t len;
389         int ret = 0;
390         size_t retlen = 0;
391
392         if (argc < 3)
393                 return cmd_usage(cmdtp);
394
395         addr = (ulong)simple_strtoul(argv[1], NULL, 16);
396
397         printf("\nOneNAND write: ");
398         if (arg_off_size(argc - 2, argv + 2, &ofs, &len) != 0)
399                 return 1;
400
401         ret = onenand_block_write(ofs, len, &retlen, (u8 *)addr);
402
403         printf(" %d bytes written: %s\n", retlen, ret ? "ERROR" : "OK");
404
405         return ret == 0 ? 0 : 1;
406 }
407
408 static int do_onenand_erase(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
409 {
410         ulong ofs;
411         int ret = 0;
412         size_t len;
413         int force;
414
415         /*
416          * Syntax is:
417          *   0       1     2       3    4
418          *   onenand erase [force] [off size]
419          */
420         argc--;
421         argv++;
422         if (argc)
423         {
424                 if (!strcmp("force", argv[0]))
425                 {
426                         force = 1;
427                         argc--;
428                         argv++;
429                 }
430         }
431         printf("\nOneNAND erase: ");
432
433         /* skip first two or three arguments, look for offset and size */
434         if (arg_off_size(argc, argv, &ofs, &len) != 0)
435                 return 1;
436
437         ret = onenand_block_erase(ofs, len, force);
438
439         printf("%s\n", ret ? "ERROR" : "OK");
440
441         return ret == 0 ? 0 : 1;
442 }
443
444 static int do_onenand_test(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
445 {
446         ulong ofs;
447         int ret = 0;
448         size_t len;
449
450         /*
451          * Syntax is:
452          *   0       1     2       3    4
453          *   onenand test [force] [off size]
454          */
455
456         printf("\nOneNAND test: ");
457
458         /* skip first two or three arguments, look for offset and size */
459         if (arg_off_size(argc - 1, argv + 1, &ofs, &len) != 0)
460                 return 1;
461
462         ret = onenand_block_test(ofs, len);
463
464         printf("%s\n", ret ? "ERROR" : "OK");
465
466         return ret == 0 ? 0 : 1;
467 }
468
469 static int do_onenand_dump(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
470 {
471         ulong ofs;
472         int ret = 0;
473         char *s;
474
475         if (argc < 2)
476                 return cmd_usage(cmdtp);
477
478         s = strchr(argv[0], '.');
479         ofs = (int)simple_strtoul(argv[1], NULL, 16);
480
481         if (s != NULL && strcmp(s, ".oob") == 0)
482                 ret = onenand_dump(mtd, ofs, 1);
483         else
484                 ret = onenand_dump(mtd, ofs, 0);
485
486         return ret == 0 ? 1 : 0;
487 }
488
489 static int do_onenand_markbad(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
490 {
491         int ret = 0;
492         ulong addr;
493
494         argc -= 2;
495         argv += 2;
496
497         if (argc <= 0)
498                 return cmd_usage(cmdtp);
499
500         while (argc > 0) {
501                 addr = simple_strtoul(*argv, NULL, 16);
502
503                 if (mtd->block_markbad(mtd, addr)) {
504                         printf("block 0x%08lx NOT marked "
505                                 "as bad! ERROR %d\n",
506                                 addr, ret);
507                         ret = 1;
508                 } else {
509                         printf("block 0x%08lx successfully "
510                                 "marked as bad\n",
511                                 addr);
512                 }
513                 --argc;
514                 ++argv;
515         }
516         return ret;
517 }
518
519 static cmd_tbl_t cmd_onenand_sub[] = {
520         U_BOOT_CMD_MKENT(info, 1, 0, do_onenand_info, "", ""),
521         U_BOOT_CMD_MKENT(bad, 1, 0, do_onenand_bad, "", ""),
522         U_BOOT_CMD_MKENT(read, 4, 0, do_onenand_read, "", ""),
523         U_BOOT_CMD_MKENT(write, 4, 0, do_onenand_write, "", ""),
524         U_BOOT_CMD_MKENT(erase, 3, 0, do_onenand_erase, "", ""),
525         U_BOOT_CMD_MKENT(test, 3, 0, do_onenand_test, "", ""),
526         U_BOOT_CMD_MKENT(dump, 2, 0, do_onenand_dump, "", ""),
527         U_BOOT_CMD_MKENT(markbad, CONFIG_SYS_MAXARGS, 0, do_onenand_markbad, "", ""),
528 };
529
530 #ifdef CONFIG_NEEDS_MANUAL_RELOC
531 void onenand_reloc(void) {
532         fixup_cmdtable(cmd_onenand_sub, ARRAY_SIZE(cmd_onenand_sub));
533 }
534 #endif
535
536 static int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
537 {
538         cmd_tbl_t *c;
539
540         if (argc < 2)
541                 return cmd_usage(cmdtp);
542
543         mtd = &onenand_mtd;
544
545         /* Strip off leading 'onenand' command argument */
546         argc--;
547         argv++;
548
549         c = find_cmd_tbl(argv[0], &cmd_onenand_sub[0], ARRAY_SIZE(cmd_onenand_sub));
550
551         if (c)
552                 return c->cmd(cmdtp, flag, argc, argv);
553         else
554                 return cmd_usage(cmdtp);
555 }
556
557 U_BOOT_CMD(
558         onenand,        CONFIG_SYS_MAXARGS,     1,      do_onenand,
559         "OneNAND sub-system",
560         "info - show available OneNAND devices\n"
561         "onenand bad - show bad blocks\n"
562         "onenand read[.oob] addr off size\n"
563         "onenand write addr off size\n"
564         "    read/write 'size' bytes starting at offset 'off'\n"
565         "    to/from memory address 'addr', skipping bad blocks.\n"
566         "onenand erase [force] [off size] - erase 'size' bytes from\n"
567         "onenand test [off size] - test 'size' bytes from\n"
568         "    offset 'off' (entire device if not specified)\n"
569         "onenand dump[.oob] off - dump page\n"
570         "onenand markbad off [...] - mark bad block(s) at offset (UNSAFE)"
571 );