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