blob: a3c8138841c2aa4098a62203d2dcccc381ac14e5 [file] [log] [blame]
wdenk15647dc2003-10-09 19:00:25 +00001/*
2 * board/eva/flash.c
3 *
4 * (C) Copyright 2002
5 * Sangmoon Kim, Etin Systems, dogoil@etinsys.com.
6 *
7 * See file CREDITS for list of people who contributed to this
8 * project.
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation; either version 2 of
13 * the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
23 * MA 02111-1307 USA
24 */
25
26#include <common.h>
27#include <asm/processor.h>
28#include <asm/pci_io.h>
29#include <mpc824x.h>
30
31int (*do_flash_erase)(flash_info_t*, uint32_t, uint32_t);
32int (*write_dword)(flash_info_t*, ulong, uint64_t);
33
34typedef uint64_t cfi_word;
35
36#define cfi_read(flash, addr) *((volatile cfi_word*)(flash->start[0] + addr))
37
38#define cfi_write(flash, val, addr) \
39 move64((cfi_word*)&val, \
40 (cfi_word*)(flash->start[0] + addr))
41
42#define CMD(x) ((((cfi_word)x)<<48)|(((cfi_word)x)<<32)|(((cfi_word)x)<<16)|(((cfi_word)x)))
43
44static void write32(unsigned long addr, uint32_t value)
45{
46 *(volatile uint32_t*)(addr) = value;
47 asm volatile("sync");
48}
49
50static uint32_t read32(unsigned long addr)
51{
52 uint32_t value;
53 value = *(volatile uint32_t*)addr;
54 asm volatile("sync");
55 return value;
56}
57
58static cfi_word cfi_cmd(flash_info_t *flash, uint8_t cmd, uint32_t addr)
59{
60 uint32_t base = flash->start[0];
61 uint32_t val=(cmd << 16) | cmd;
62 addr <<= 3;
63 write32(base + addr, val);
64 return addr;
65}
66
67static uint16_t cfi_read_query(flash_info_t *flash, uint32_t addr)
68{
69 uint32_t base = flash->start[0];
70 addr <<= 3;
71 return (uint16_t)read32(base + addr);
72}
73
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +020074flash_info_t flash_info[CONFIG_SYS_MAX_FLASH_BANKS]; /* info for FLASH chips */
wdenk15647dc2003-10-09 19:00:25 +000075
76static void move64(uint64_t *src, uint64_t *dest)
77{
78 asm volatile("lfd 0, 0(3)\n\t" /* fpr0 = *scr */
79 "stfd 0, 0(4)" /* *dest = fpr0 */
80 : : : "fr0" ); /* Clobbers fr0 */
81 return;
82}
83
84static int cfi_write_dword(flash_info_t *flash, ulong dest, cfi_word data)
85{
86 unsigned long start;
87 cfi_word status = 0;
88
89 status = cfi_read(flash, dest);
90 data &= status;
91
92 cfi_cmd(flash, 0x40, 0);
93 cfi_write(flash, data, dest);
94
95 udelay(10);
96 start = get_timer (0);
97 for(;;) {
98 status = cfi_read(flash, dest);
99 status &= CMD(0x80);
100 if(status == CMD(0x80))
101 break;
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200102 if (get_timer(start) > CONFIG_SYS_FLASH_WRITE_TOUT) {
wdenk15647dc2003-10-09 19:00:25 +0000103 cfi_cmd(flash, 0xff, 0);
104 return 1;
105 }
106 udelay(1);
107 }
108 cfi_cmd(flash, 0xff, 0);
109
110 return 0;
111}
112
113static int jedec_write_dword (flash_info_t *flash, ulong dest, cfi_word data)
114{
115 ulong start;
116 cfi_word status = 0;
117
118 status = cfi_read(flash, dest);
119 if(status != CMD(0xffff)) return 2;
120
121 cfi_cmd(flash, 0xaa, 0x555);
122 cfi_cmd(flash, 0x55, 0x2aa);
123 cfi_cmd(flash, 0xa0, 0x555);
124
125 cfi_write(flash, data, dest);
126
127 udelay(10);
128 start = get_timer (0);
129 status = ~data;
130 while(status != data) {
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200131 if (get_timer(start) > CONFIG_SYS_FLASH_WRITE_TOUT)
wdenk15647dc2003-10-09 19:00:25 +0000132 return 1;
133 status = cfi_read(flash, dest);
134 udelay(1);
135 }
136 return 0;
137}
138
139static __inline__ unsigned long get_msr(void)
140{
141 unsigned long msr;
142 __asm__ __volatile__ ("mfmsr %0" : "=r" (msr) :);
143 return msr;
144}
145
146static __inline__ void set_msr(unsigned long msr)
147{
148 __asm__ __volatile__ ("mtmsr %0" : : "r" (msr));
149}
150
151int write_buff (flash_info_t *flash, uchar *src, ulong addr, ulong cnt)
152{
153 ulong wp;
154 int i, s, l, rc;
155 cfi_word data;
156 uint8_t *t = (uint8_t*)&data;
157 unsigned long base = flash->start[0];
158 uint32_t msr;
159
160 if (flash->flash_id == FLASH_UNKNOWN)
161 return 4;
162
163 if (cnt == 0)
164 return 0;
165
166 addr -= base;
167
168 msr = get_msr();
169 set_msr(msr|MSR_FP);
170
171 wp = (addr & ~7); /* get lower word aligned address */
172
173 if((addr-wp) != 0) {
174 data = cfi_read(flash, wp);
175 s = addr & 7;
176 l = ( cnt < (8-s) ) ? cnt : (8-s);
177 for(i = 0; i < l; i++)
178 t[s+i] = *src++;
179 if ((rc = write_dword(flash, wp, data)) != 0)
180 goto DONE;
181 wp += 8;
182 cnt -= l;
183 }
184
185 while (cnt >= 8) {
186 for (i = 0; i < 8; i++)
187 t[i] = *src++;
188 if ((rc = write_dword(flash, wp, data)) != 0)
189 goto DONE;
190 wp += 8;
191 cnt -= 8;
192 }
193
194 if (cnt == 0) {
195 rc = 0;
196 goto DONE;
197 }
198
199 data = cfi_read(flash, wp);
200 for(i = 0; i < cnt; i++)
201 t[i] = *src++;
202 rc = write_dword(flash, wp, data);
203DONE:
204 set_msr(msr);
205 return rc;
206}
207
208static int cfi_erase_oneblock(flash_info_t *flash, uint32_t sect)
209{
210 int sa;
211 int flag;
212 ulong start, last, now;
213 cfi_word status;
214
215 flag = disable_interrupts();
216
217 sa = (flash->start[sect] - flash->start[0]);
218 write32(flash->start[sect], 0x00200020);
219 write32(flash->start[sect], 0x00d000d0);
220
221 if (flag)
222 enable_interrupts();
223
224 udelay(1000);
225 start = get_timer (0);
226 last = start;
227
228 for (;;) {
229 status = cfi_read(flash, sa);
230 status &= CMD(0x80);
231 if (status == CMD(0x80))
232 break;
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200233 if ((now = get_timer(start)) > CONFIG_SYS_FLASH_ERASE_TOUT) {
wdenk15647dc2003-10-09 19:00:25 +0000234 cfi_cmd(flash, 0xff, 0);
235 printf ("Timeout\n");
236 return ERR_TIMOUT;
237 }
238
239 if ((now - last) > 1000) {
240 serial_putc ('.');
241 last = now;
242 }
243 udelay(10);
244 }
245 cfi_cmd(flash, 0xff, 0);
246 return ERR_OK;
247}
248
249static int cfi_erase(flash_info_t *flash, uint32_t s_first, uint32_t s_last)
250{
251 int sect;
252 int rc = ERR_OK;
253
254 for (sect = s_first; sect <= s_last; sect++) {
255 if (flash->protect[sect] == 0) {
256 rc = cfi_erase_oneblock(flash, sect);
257 if (rc != ERR_OK) break;
258 }
259 }
260 printf (" done\n");
261 return rc;
262}
263
264static int jedec_erase(flash_info_t *flash, uint32_t s_first, uint32_t s_last)
265{
266 int sect;
267 cfi_word status;
268 int sa = -1;
269 int flag;
270 ulong start, last, now;
271
272 flag = disable_interrupts();
273
274 cfi_cmd(flash, 0xaa, 0x555);
275 cfi_cmd(flash, 0x55, 0x2aa);
276 cfi_cmd(flash, 0x80, 0x555);
277 cfi_cmd(flash, 0xaa, 0x555);
278 cfi_cmd(flash, 0x55, 0x2aa);
279 for ( sect = s_first; sect <= s_last; sect++) {
280 if (flash->protect[sect] == 0) {
281 sa = flash->start[sect] - flash->start[0];
282 write32(flash->start[sect], 0x00300030);
283 }
284 }
285 if (flag)
286 enable_interrupts();
287
288 if (sa < 0)
289 goto DONE;
290
291 udelay (1000);
292 start = get_timer (0);
293 last = start;
294 for(;;) {
295 status = cfi_read(flash, sa);
296 if (status == CMD(0xffff))
297 break;
298
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200299 if ((now = get_timer(start)) > CONFIG_SYS_FLASH_ERASE_TOUT) {
wdenk15647dc2003-10-09 19:00:25 +0000300 printf ("Timeout\n");
301 return ERR_TIMOUT;
302 }
303
304 if ((now - last) > 1000) {
305 serial_putc ('.');
306 last = now;
307 }
308 udelay(10);
309 }
310DONE:
311 cfi_cmd(flash, 0xf0, 0);
312
313 printf (" done\n");
314
315 return ERR_OK;
316}
317
318int flash_erase (flash_info_t *flash, int s_first, int s_last)
319{
320 int sect;
321 int prot;
322
323 if ((s_first < 0) || (s_first > s_last)) {
324 if (flash->flash_id == FLASH_UNKNOWN)
325 printf ("- missing\n");
326 else
327 printf ("- no sectors to erase\n");
328 return ERR_NOT_ERASED;
329 }
330 if (flash->flash_id == FLASH_UNKNOWN) {
331 printf ("Can't erase unknown flash type - aborted\n");
332 return ERR_NOT_ERASED;
333 }
334
335 prot = 0;
336 for (sect = s_first; sect <= s_last; sect++)
337 if (flash->protect[sect]) prot++;
338
339 if (prot)
340 printf ("- Warning: %d protected sectors will not be erased!\n",
341 prot);
342 else
343 printf ("\n");
344
345 return do_flash_erase(flash, s_first, s_last);
346}
347
348struct jedec_flash_info {
349 const uint16_t mfr_id;
350 const uint16_t dev_id;
351 const char *name;
352 const int DevSize;
353 const int InterfaceDesc;
354 const int NumEraseRegions;
355 const ulong regions[4];
356};
357
358#define ERASEINFO(size,blocks) (size<<8)|(blocks-1)
359
360#define SIZE_1MiB 20
361#define SIZE_2MiB 21
362#define SIZE_4MiB 22
363
364static const struct jedec_flash_info jedec_table[] = {
365 {
366 mfr_id: (uint16_t)AMD_MANUFACT,
367 dev_id: (uint16_t)AMD_ID_LV800T,
368 name: "AMD AM29LV800T",
369 DevSize: SIZE_1MiB,
370 NumEraseRegions: 4,
371 regions: {ERASEINFO(0x10000,15),
372 ERASEINFO(0x08000,1),
373 ERASEINFO(0x02000,2),
374 ERASEINFO(0x04000,1)
375 }
376 }, {
377 mfr_id: (uint16_t)AMD_MANUFACT,
378 dev_id: (uint16_t)AMD_ID_LV800B,
379 name: "AMD AM29LV800B",
380 DevSize: SIZE_1MiB,
381 NumEraseRegions: 4,
382 regions: {ERASEINFO(0x10000,15),
383 ERASEINFO(0x08000,1),
384 ERASEINFO(0x02000,2),
385 ERASEINFO(0x04000,1)
386 }
387 }, {
388 mfr_id: (uint16_t)AMD_MANUFACT,
389 dev_id: (uint16_t)AMD_ID_LV160T,
390 name: "AMD AM29LV160T",
391 DevSize: SIZE_2MiB,
392 NumEraseRegions: 4,
393 regions: {ERASEINFO(0x10000,31),
394 ERASEINFO(0x08000,1),
395 ERASEINFO(0x02000,2),
396 ERASEINFO(0x04000,1)
397 }
398 }, {
399 mfr_id: (uint16_t)AMD_MANUFACT,
400 dev_id: (uint16_t)AMD_ID_LV160B,
401 name: "AMD AM29LV160B",
402 DevSize: SIZE_2MiB,
403 NumEraseRegions: 4,
404 regions: {ERASEINFO(0x04000,1),
405 ERASEINFO(0x02000,2),
406 ERASEINFO(0x08000,1),
407 ERASEINFO(0x10000,31)
408 }
409 }, {
410 mfr_id: (uint16_t)AMD_MANUFACT,
411 dev_id: (uint16_t)AMD_ID_LV320T,
412 name: "AMD AM29LV320T",
413 DevSize: SIZE_4MiB,
414 NumEraseRegions: 2,
415 regions: {ERASEINFO(0x10000,63),
416 ERASEINFO(0x02000,8)
417 }
418
419 }, {
420 mfr_id: (uint16_t)AMD_MANUFACT,
421 dev_id: (uint16_t)AMD_ID_LV320B,
422 name: "AMD AM29LV320B",
423 DevSize: SIZE_4MiB,
424 NumEraseRegions: 2,
425 regions: {ERASEINFO(0x02000,8),
426 ERASEINFO(0x10000,63)
427 }
428 }
429};
430
431static ulong cfi_init(uint32_t base, flash_info_t *flash)
432{
433 int sector;
434 int block;
435 int block_count;
436 int offset = 0;
437 int reverse = 0;
438 int primary;
439 int mfr_id;
440 int dev_id;
441
442 flash->start[0] = base;
443 cfi_cmd(flash, 0xF0, 0);
444 cfi_cmd(flash, 0x98, 0);
445 if ( !( cfi_read_query(flash, 0x10) == 'Q' &&
446 cfi_read_query(flash, 0x11) == 'R' &&
447 cfi_read_query(flash, 0x12) == 'Y' )) {
448 cfi_cmd(flash, 0xff, 0);
449 return 0;
450 }
451
452 flash->size = 1 << cfi_read_query(flash, 0x27);
453 flash->size *= 4;
454 block_count = cfi_read_query(flash, 0x2c);
455 primary = cfi_read_query(flash, 0x15);
456 if ( cfi_read_query(flash, primary + 4) == 0x30)
457 reverse = (cfi_read_query(flash, 0x1) & 0x01);
458 else
459 reverse = (cfi_read_query(flash, primary+15) == 3);
460
461 flash->sector_count = 0;
462
463 for ( block = reverse ? block_count - 1 : 0;
464 reverse ? block >= 0 : block < block_count;
465 reverse ? block-- : block ++) {
466 int sector_size =
467 (cfi_read_query(flash, 0x2d + block*4+2) |
468 (cfi_read_query(flash, 0x2d + block*4+3) << 8)) << 8;
469 int sector_count =
470 (cfi_read_query(flash, 0x2d + block*4+0) |
471 (cfi_read_query(flash, 0x2d + block*4+1) << 8)) + 1;
472 for(sector = 0; sector < sector_count; sector++) {
473 flash->start[flash->sector_count++] = base + offset;
474 offset += sector_size * 4;
475 }
476 }
477 mfr_id = cfi_read_query(flash, 0x00);
478 dev_id = cfi_read_query(flash, 0x01);
479
480 cfi_cmd(flash, 0xff, 0);
481
482 flash->flash_id = (mfr_id << 16) | dev_id;
483
484 for (sector = 0; sector < flash->sector_count; sector++) {
485 write32(flash->start[sector], 0x00600060);
486 write32(flash->start[sector], 0x00d000d0);
487 }
488 cfi_cmd(flash, 0xff, 0);
489
490 for (sector = 0; sector < flash->sector_count; sector++)
491 flash->protect[sector] = 0;
492
493 do_flash_erase = cfi_erase;
494 write_dword = cfi_write_dword;
495
496 return flash->size;
497}
498
499static ulong jedec_init(unsigned long base, flash_info_t *flash)
500{
501 int i;
502 int block, block_count;
503 int sector, offset;
504 int mfr_id, dev_id;
505 flash->start[0] = base;
506 cfi_cmd(flash, 0xF0, 0x000);
507 cfi_cmd(flash, 0xAA, 0x555);
508 cfi_cmd(flash, 0x55, 0x2AA);
509 cfi_cmd(flash, 0x90, 0x555);
510 mfr_id = cfi_read_query(flash, 0x000);
511 dev_id = cfi_read_query(flash, 0x0001);
512 cfi_cmd(flash, 0xf0, 0x000);
513
514 for(i=0; i<sizeof(jedec_table)/sizeof(struct jedec_flash_info); i++) {
515 if((jedec_table[i].mfr_id == mfr_id) &&
516 (jedec_table[i].dev_id == dev_id)) {
517
518 flash->flash_id = (mfr_id << 16) | dev_id;
519 flash->size = 1 << jedec_table[0].DevSize;
520 flash->size *= 4;
521 block_count = jedec_table[i].NumEraseRegions;
522 offset = 0;
523 flash->sector_count = 0;
524 for (block = 0; block < block_count; block++) {
525 int sector_size = jedec_table[i].regions[block];
526 int sector_count = (sector_size & 0xff) + 1;
527 sector_size >>= 8;
528 for (sector=0; sector<sector_count; sector++) {
529 flash->start[flash->sector_count++] =
530 base + offset;
531 offset += sector_size * 4;
532 }
533 }
534 break;
535 }
536 }
537
538 for (sector = 0; sector < flash->sector_count; sector++)
539 flash->protect[sector] = 0;
540
541 do_flash_erase = jedec_erase;
542 write_dword = jedec_write_dword;
543
544 return flash->size;
545}
546
547inline void mtibat1u(unsigned int x)
548{
549 __asm__ __volatile__ ("mtspr 530, %0" :: "r" (x));
550}
551
552inline void mtibat1l(unsigned int x)
553{
554 __asm__ __volatile__ ("mtspr 531, %0" :: "r" (x));
555}
556
557inline void mtdbat1u(unsigned int x)
558{
559 __asm__ __volatile__ ("mtspr 538, %0" :: "r" (x));
560}
561
562inline void mtdbat1l(unsigned int x)
563{
564 __asm__ __volatile__ ("mtspr 539, %0" :: "r" (x));
565}
566
567unsigned long flash_init (void)
568{
569 unsigned long size = 0;
570 int i;
571 unsigned int msr;
572
573 /* BAT1 */
574 CONFIG_WRITE_WORD(ERCR3, 0x0C00000C);
575 CONFIG_WRITE_WORD(ERCR4, 0x0800000C);
576 msr = get_msr();
577 set_msr(msr & ~(MSR_IR | MSR_DR));
578 mtibat1l(0x70000000 | BATL_PP_10 | BATL_CACHEINHIBIT);
579 mtibat1u(0x70000000 | BATU_BL_256M | BATU_VS | BATU_VP);
580 mtdbat1l(0x70000000 | BATL_PP_10 | BATL_CACHEINHIBIT);
581 mtdbat1u(0x70000000 | BATU_BL_256M | BATU_VS | BATU_VP);
582 set_msr(msr);
583
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200584 for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++)
wdenk15647dc2003-10-09 19:00:25 +0000585 flash_info[i].flash_id = FLASH_UNKNOWN;
586 size = cfi_init(FLASH_BASE0_PRELIM, &flash_info[0]);
587 if (!size)
588 size = jedec_init(FLASH_BASE0_PRELIM, &flash_info[0]);
589
590 if (flash_info[0].flash_id == FLASH_UNKNOWN)
591 printf ("# Unknown FLASH on Bank 1 - Size = 0x%08lx = %ld MB\n",
592 size, size<<20);
593
594 return size;
595}
596
597void flash_print_info (flash_info_t *flash)
598{
599 int i;
600 int k;
601 int size;
602 int erased;
603 volatile unsigned long *p;
604
605 if (flash->flash_id == FLASH_UNKNOWN) {
606 printf ("missing or unknown FLASH type\n");
607 flash_init();
608 }
609
610 if (flash->flash_id == FLASH_UNKNOWN) {
611 printf ("missing or unknown FLASH type\n");
612 return;
613 }
614
615 switch (((flash->flash_id) >> 16) & 0xff) {
616 case 0x01:
617 printf ("AMD ");
618 break;
619 case 0x04:
620 printf("FUJITSU ");
621 break;
622 case 0x20:
623 printf("STM ");
624 break;
625 case 0xBF:
626 printf("SST ");
627 break;
628 case 0x89:
629 case 0xB0:
630 printf("INTEL ");
631 break;
632 default:
633 printf ("Unknown Vendor ");
634 break;
635 }
636
637 switch ((flash->flash_id) & 0xffff) {
638 case (uint16_t)AMD_ID_LV800T:
639 printf ("AM29LV800T\n");
640 break;
641 case (uint16_t)AMD_ID_LV800B:
642 printf ("AM29LV800B\n");
643 break;
644 case (uint16_t)AMD_ID_LV160T:
645 printf ("AM29LV160T\n");
646 break;
647 case (uint16_t)AMD_ID_LV160B:
648 printf ("AM29LV160B\n");
649 break;
650 case (uint16_t)AMD_ID_LV320T:
651 printf ("AM29LV320T\n");
652 break;
653 case (uint16_t)AMD_ID_LV320B:
654 printf ("AM29LV320B\n");
655 break;
656 case (uint16_t)INTEL_ID_28F800C3T:
657 printf ("28F800C3T\n");
658 break;
659 case (uint16_t)INTEL_ID_28F800C3B:
660 printf ("28F800C3B\n");
661 break;
662 case (uint16_t)INTEL_ID_28F160C3T:
663 printf ("28F160C3T\n");
664 break;
665 case (uint16_t)INTEL_ID_28F160C3B:
666 printf ("28F160C3B\n");
667 break;
668 case (uint16_t)INTEL_ID_28F320C3T:
669 printf ("28F320C3T\n");
670 break;
671 case (uint16_t)INTEL_ID_28F320C3B:
672 printf ("28F320C3B\n");
673 break;
674 case (uint16_t)INTEL_ID_28F640C3T:
675 printf ("28F640C3T\n");
676 break;
677 case (uint16_t)INTEL_ID_28F640C3B:
678 printf ("28F640C3B\n");
679 break;
680 default:
681 printf ("Unknown Chip Type\n");
682 break;
683 }
684
685 if (flash->size >= (1 << 20)) {
686 printf (" Size: %ld MB in %d Sectors\n",
687 flash->size >> 20, flash->sector_count);
688 } else {
689 printf (" Size: %ld kB in %d Sectors\n",
690 flash->size >> 10, flash->sector_count);
691 }
692
693 printf (" Sector Start Addresses:");
694 for (i = 0; i < flash->sector_count; ++i) {
695 /* Check if whole sector is erased*/
696 if (i != (flash->sector_count-1))
697 size = flash->start[i+1] - flash->start[i];
698 else
699 size = flash->start[0] + flash->size - flash->start[i];
700
701 erased = 1;
702 p = (volatile unsigned long *)flash->start[i];
703 size = size >> 2; /* divide by 4 for longword access */
704 for (k=0; k<size; k++) {
705 if (*p++ != 0xffffffff) {
706 erased = 0;
707 break;
708 }
709 }
710
711 if ((i % 5) == 0)
712 printf ("\n ");
713
714 printf (" %08lX%s%s",
715 flash->start[i],
716 erased ? " E" : " ",
717 flash->protect[i] ? "RO " : " ");
718 }
719 printf ("\n");
720}