blob: c9efb15e369a224347ca3b4a747c25a2afe20e2c [file] [log] [blame]
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +02001/********************************************************************
2 *
3 * Unless otherwise specified, Copyright (C) 2004-2005 Barco Control Rooms
4 *
5 * $Source: /home/services/cvs/firmware/ppc/u-boot-1.1.2/board/barco/flash.c,v $
6 * $Revision: 1.3 $
7 * $Author: mleeman $
8 * $Date: 2005/02/21 12:48:58 $
9 *
10 * Last ChangeLog Entry
11 * $Log: flash.c,v $
12 * Revision 1.3 2005/02/21 12:48:58 mleeman
13 * update of copyright years (feedback wd)
14 *
15 * Revision 1.2 2005/02/21 11:04:04 mleeman
16 * remove dead code and Coding style (feedback wd)
17 *
18 * Revision 1.1 2005/02/14 09:23:46 mleeman
19 * - moved 'barcohydra' directory to a more generic barco; since we will be
20 * supporting and adding multiple boards
21 *
22 * Revision 1.2 2005/02/09 12:56:23 mleeman
23 * add generic header to track changes in sources
24 *
25 *
26 *******************************************************************/
27
28/*
29 * (C) Copyright 2000
30 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
31 *
32 * See file CREDITS for list of people who contributed to this
33 * project.
34 *
35 * This program is free software; you can redistribute it and/or
36 * modify it under the terms of the GNU General Public License as
37 * published by the Free Software Foundation; either version 2 of
38 * the License, or (at your option) any later version.
39 *
40 * This program is distributed in the hope that it will be useful,
41 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
43 * GNU General Public License for more details.
44 *
45 * You should have received a copy of the GNU General Public License
46 * along with this program; if not, write to the Free Software
47 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
48 * MA 02111-1307 USA
49 */
50
51#include <common.h>
52#include <mpc824x.h>
53#include <asm/processor.h>
54#include <flash.h>
55
56#define ROM_CS0_START 0xFF800000
57#define ROM_CS1_START 0xFF000000
58
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +020059flash_info_t flash_info[CONFIG_SYS_MAX_FLASH_BANKS]; /* info for FLASH chips */
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +020060
Jean-Christophe PLAGNIOL-VILLARD5a1aceb2008-09-10 22:48:04 +020061#if defined(CONFIG_ENV_IS_IN_FLASH)
Jean-Christophe PLAGNIOL-VILLARD0e8d1582008-09-10 22:48:06 +020062# ifndef CONFIG_ENV_ADDR
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +020063# define CONFIG_ENV_ADDR (CONFIG_SYS_FLASH_BASE + CONFIG_ENV_OFFSET)
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +020064# endif
Jean-Christophe PLAGNIOL-VILLARD0e8d1582008-09-10 22:48:06 +020065# ifndef CONFIG_ENV_SIZE
66# define CONFIG_ENV_SIZE CONFIG_ENV_SECT_SIZE
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +020067# endif
Jean-Christophe PLAGNIOL-VILLARD0e8d1582008-09-10 22:48:06 +020068# ifndef CONFIG_ENV_SECT_SIZE
69# define CONFIG_ENV_SECT_SIZE CONFIG_ENV_SIZE
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +020070# endif
71#endif
72
73/*-----------------------------------------------------------------------
74 * Functions
75 */
76static int write_word (flash_info_t *info, ulong dest, ulong data);
77
78/*flash command address offsets*/
79
80#define ADDR0 (0xAAA)
81#define ADDR1 (0x555)
82#define ADDR3 (0x001)
83
84#define FLASH_WORD_SIZE unsigned char
85
86/*-----------------------------------------------------------------------
87 */
88
89static unsigned long flash_id(unsigned char mfct, unsigned char chip) __attribute__ ((const));
90
91typedef struct{
92 FLASH_WORD_SIZE extval;
93 unsigned short intval;
94} map_entry;
95
96static unsigned long flash_id(unsigned char mfct, unsigned char chip)
97{
98 static const map_entry mfct_map[] = {
99 {(FLASH_WORD_SIZE) AMD_MANUFACT, (unsigned short) ((unsigned long) FLASH_MAN_AMD >> 16)},
100 {(FLASH_WORD_SIZE) FUJ_MANUFACT, (unsigned short) ((unsigned long) FLASH_MAN_FUJ >> 16)},
101 {(FLASH_WORD_SIZE) STM_MANUFACT, (unsigned short) ((unsigned long) FLASH_MAN_STM >> 16)},
102 {(FLASH_WORD_SIZE) MT_MANUFACT, (unsigned short) ((unsigned long) FLASH_MAN_MT >> 16)},
103 {(FLASH_WORD_SIZE) INTEL_MANUFACT,(unsigned short) ((unsigned long) FLASH_MAN_INTEL >> 16)},
104 {(FLASH_WORD_SIZE) INTEL_ALT_MANU,(unsigned short) ((unsigned long) FLASH_MAN_INTEL >> 16)}
105 };
106
107 static const map_entry chip_map[] = {
108 {AMD_ID_F040B, FLASH_AM040},
109 {AMD_ID_F033C, FLASH_AM033},
110 {AMD_ID_F065D, FLASH_AM065},
111 {ATM_ID_LV040, FLASH_AT040},
112 {(FLASH_WORD_SIZE) STM_ID_x800AB, FLASH_STM800AB}
113 };
114
115 const map_entry *p;
116 unsigned long result = FLASH_UNKNOWN;
117
118 /* find chip id */
119 for(p = &chip_map[0]; p < &chip_map[sizeof chip_map / sizeof chip_map[0]]; p++){
120 if(p->extval == chip){
121 result = FLASH_VENDMASK | p->intval;
122 break;
123 }
124 }
125
126 /* find vendor id */
127 for(p = &mfct_map[0]; p < &mfct_map[sizeof mfct_map / sizeof mfct_map[0]]; p++){
128 if(p->extval == mfct){
129 result &= ~FLASH_VENDMASK;
130 result |= (unsigned long) p->intval << 16;
131 break;
132 }
133 }
134
135 return result;
136}
137
138
139unsigned long flash_init(void)
140{
141 unsigned long i;
142 unsigned char j;
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200143 static const ulong flash_banks[] = CONFIG_SYS_FLASH_BANKS;
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +0200144
145 /* Init: no FLASHes known */
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200146 for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++){
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +0200147 flash_info_t * const pflinfo = &flash_info[i];
148 pflinfo->flash_id = FLASH_UNKNOWN;
149 pflinfo->size = 0;
150 pflinfo->sector_count = 0;
151 }
152
153 /* Enable writes to Hydra/Argus flash */
154 {
155 register unsigned int temp;
156 CONFIG_READ_WORD(PICR1,temp);
157 temp |= PICR1_FLASH_WR_EN;
158 CONFIG_WRITE_WORD(PICR1,temp);
159 }
160
161 for(i = 0; i < sizeof flash_banks / sizeof flash_banks[0]; i++){
162 flash_info_t * const pflinfo = &flash_info[i];
163 const unsigned long base_address = flash_banks[i];
164 volatile FLASH_WORD_SIZE * const flash = (FLASH_WORD_SIZE *) base_address;
165
166 /* write autoselect sequence */
167 flash[0x5555] = 0xaa;
168 flash[0x2aaa] = 0x55;
169 flash[0x5555] = 0x90;
170 __asm__ __volatile__("sync");
171
172 pflinfo->flash_id = flash_id(flash[0x0], flash[0x1]);
173
174 switch(pflinfo->flash_id & FLASH_TYPEMASK){
175 case FLASH_AM033:
176 pflinfo->size = 0x00200000;
177 pflinfo->sector_count = 64;
178 for(j = 0; j < 64; j++){
179 pflinfo->start[j] = base_address + 0x00010000 * j;
180 pflinfo->protect[j] = flash[(j << 16) | 0x2];
181 }
182 break;
183 case FLASH_AM065:
184 pflinfo->size = 0x00800000;
185 pflinfo->sector_count =128;
186 for(j = 0; j < 128; j++){
187 pflinfo->start[j] = base_address + 0x00010000 * j;
188 pflinfo->protect[j] = flash[(j << 16) | 0x2];
189 }
190 break;
191 case FLASH_AT040:
192 pflinfo->size = 0x00080000;
193 pflinfo->sector_count = 2;
194 pflinfo->start[0] = base_address ;
195 pflinfo->start[1] = base_address + 0x00004000;
196 pflinfo->protect[0] = ((flash[0x02] & 0X01)==0) ? 0X02 : 0X01;
197 pflinfo->protect[1] = 0X02;
198 break;
199 case FLASH_AM040:
200 pflinfo->size = 0x00080000;
201 pflinfo->sector_count = 8;
202 for(j = 0; j < 8; j++){
203 pflinfo->start[j] = base_address + 0x00010000 * j;
204 pflinfo->protect[j] = flash[(j << 16) | 0x2];
205 }
206 break;
207 case FLASH_STM800AB:
208 pflinfo->size = 0x00100000;
209 pflinfo->sector_count = 19;
210 pflinfo->start[0] = base_address;
211 pflinfo->start[1] = base_address + 0x4000;
212 pflinfo->start[2] = base_address + 0x6000;
213 pflinfo->start[3] = base_address + 0x8000;
214 for(j = 1; j < 16; j++){
215 pflinfo->start[j+3] = base_address + 0x00010000 * j;
216 }
217 break;
218 }
219 /* Protect monitor and environment sectors */
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200220#if CONFIG_SYS_MONITOR_BASE >= CONFIG_SYS_FLASH_BASE
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +0200221 flash_protect(FLAG_PROTECT_SET,
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200222 CONFIG_SYS_MONITOR_BASE,
223 CONFIG_SYS_MONITOR_BASE + monitor_flash_len - 1,
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +0200224 &flash_info[0]);
225#endif
226
Jean-Christophe PLAGNIOL-VILLARD0e8d1582008-09-10 22:48:06 +0200227#if defined(CONFIG_ENV_IS_IN_FLASH) && defined(CONFIG_ENV_ADDR)
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +0200228 flash_protect(FLAG_PROTECT_SET,
Jean-Christophe PLAGNIOL-VILLARD0e8d1582008-09-10 22:48:06 +0200229 CONFIG_ENV_ADDR,
230 CONFIG_ENV_ADDR + CONFIG_ENV_SIZE - 1,
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +0200231 &flash_info[0]);
232#endif
233
234 /* reset device to read mode */
235 flash[0x0000] = 0xf0;
236 __asm__ __volatile__("sync");
237 }
238
239 return flash_info[0].size + flash_info[1].size;
240}
241
242/*-----------------------------------------------------------------------
243 */
244void flash_print_info(flash_info_t *info)
245{
246 static const char unk[] = "Unknown";
247 const char *mfct = unk, *type = unk;
248 unsigned int i;
249
250 if(info->flash_id != FLASH_UNKNOWN){
251 switch(info->flash_id & FLASH_VENDMASK){
252 case FLASH_MAN_ATM:
253 mfct = "Atmel";
254 break;
255 case FLASH_MAN_AMD:
256 mfct = "AMD";
257 break;
258 case FLASH_MAN_FUJ:
259 mfct = "FUJITSU";
260 break;
261 case FLASH_MAN_STM:
262 mfct = "STM";
263 break;
264 case FLASH_MAN_SST:
265 mfct = "SST";
266 break;
267 case FLASH_MAN_BM:
268 mfct = "Bright Microelectonics";
269 break;
270 case FLASH_MAN_INTEL:
271 mfct = "Intel";
272 break;
273 }
274
275 switch(info->flash_id & FLASH_TYPEMASK){
276 case FLASH_AT040:
277 type = "AT49LV040 (512K * 8, uniform sector size)";
278 break;
279 case FLASH_AM033:
280 type = "AM29F033C (4 Mbit * 8, uniform sector size)";
281 break;
282 case FLASH_AM040:
283 type = "AM29F040B (512K * 8, uniform sector size)";
284 break;
285 case FLASH_AM065:
286 type = "AM29F0465D ( 8 MBit * 8, uniform sector size) or part of AM29F652D( 16 MB)";
287 break;
288 case FLASH_AM400B:
289 type = "AM29LV400B (4 Mbit, bottom boot sect)";
290 break;
291 case FLASH_AM400T:
292 type = "AM29LV400T (4 Mbit, top boot sector)";
293 break;
294 case FLASH_AM800B:
295 type = "AM29LV800B (8 Mbit, bottom boot sect)";
296 break;
297 case FLASH_AM800T:
298 type = "AM29LV800T (8 Mbit, top boot sector)";
299 break;
300 case FLASH_AM160T:
301 type = "AM29LV160T (16 Mbit, top boot sector)";
302 break;
303 case FLASH_AM320B:
304 type = "AM29LV320B (32 Mbit, bottom boot sect)";
305 break;
306 case FLASH_AM320T:
307 type = "AM29LV320T (32 Mbit, top boot sector)";
308 break;
309 case FLASH_STM800AB:
310 type = "M29W800AB (8 Mbit, bottom boot sect)";
311 break;
312 case FLASH_SST800A:
313 type = "SST39LF/VF800 (8 Mbit, uniform sector size)";
314 break;
315 case FLASH_SST160A:
316 type = "SST39LF/VF160 (16 Mbit, uniform sector size)";
317 break;
318 }
319 }
320
321 printf(
322 "\n Brand: %s Type: %s\n"
323 " Size: %lu KB in %d Sectors\n",
324 mfct,
325 type,
326 info->size >> 10,
327 info->sector_count
328 );
329
330 printf (" Sector Start Addresses:");
331
332 for (i = 0; i < info->sector_count; i++){
333 unsigned long size;
334 unsigned int erased;
335 unsigned long * flash = (unsigned long *) info->start[i];
336
337 /*
338 * Check if whole sector is erased
339 */
340 size =
341 (i != (info->sector_count - 1)) ?
342 (info->start[i + 1] - info->start[i]) >> 2 :
343 (info->start[0] + info->size - info->start[i]) >> 2;
344
345 for(
346 flash = (unsigned long *) info->start[i], erased = 1;
347 (flash != (unsigned long *) info->start[i] + size) && erased;
348 flash++
349 ){
350 erased = *flash == ~0x0UL;
351 }
352
353 printf(
354 "%s %08lX %s %s",
355 (i % 5) ? "" : "\n ",
356 info->start[i],
357 erased ? "E" : " ",
358 info->protect[i] ? "RO" : " "
359 );
360 }
361
362 puts("\n");
363 return;
364}
365
366int flash_erase(flash_info_t *info, int s_first, int s_last)
367{
368 volatile FLASH_WORD_SIZE *addr = (FLASH_WORD_SIZE *)(info->start[0]);
369 int flag, prot, sect, l_sect;
370 ulong start, now, last;
371 unsigned char sh8b;
372
373 if ((s_first < 0) || (s_first > s_last)) {
374 if (info->flash_id == FLASH_UNKNOWN) {
375 printf ("- missing\n");
376 } else {
377 printf ("- no sectors to erase\n");
378 }
379 return 1;
380 }
381
382 if ((info->flash_id == FLASH_UNKNOWN) ||
383 (info->flash_id > (FLASH_MAN_STM | FLASH_AMD_COMP))) {
384 printf ("Can't erase unknown flash type - aborted\n");
385 return 1;
386 }
387
388 prot = 0;
389 for (sect=s_first; sect<=s_last; ++sect) {
390 if (info->protect[sect]) {
391 prot++;
392 }
393 }
394
395 if (prot) {
396 printf ("- Warning: %d protected sectors will not be erased!\n",
397 prot);
398 } else {
399 printf ("\n");
400 }
401
402 l_sect = -1;
403
404 /* Check the ROM CS */
405 if ((info->start[0] >= ROM_CS1_START) && (info->start[0] < ROM_CS0_START)){
406 sh8b = 3;
407 }
408 else{
409 sh8b = 0;
410 }
411
412 /* Disable interrupts which might cause a timeout here */
413 flag = disable_interrupts();
414
415 addr[ADDR0 << sh8b] = (FLASH_WORD_SIZE)0x00AA00AA;
416 addr[ADDR1 << sh8b] = (FLASH_WORD_SIZE)0x00550055;
417 addr[ADDR0 << sh8b] = (FLASH_WORD_SIZE)0x00800080;
418 addr[ADDR0 << sh8b] = (FLASH_WORD_SIZE)0x00AA00AA;
419 addr[ADDR1 << sh8b] = (FLASH_WORD_SIZE)0x00550055;
420
421 /* Start erase on unprotected sectors */
422 for (sect = s_first; sect<=s_last; sect++) {
423 if (info->protect[sect] == 0) { /* not protected */
424 addr = (FLASH_WORD_SIZE *)(info->start[0] + (
425 (info->start[sect] - info->start[0]) << sh8b));
426 if (info->flash_id & FLASH_MAN_SST){
427 addr[ADDR0 << sh8b] = (FLASH_WORD_SIZE)0x00AA00AA;
428 addr[ADDR1 << sh8b] = (FLASH_WORD_SIZE)0x00550055;
429 addr[ADDR0 << sh8b] = (FLASH_WORD_SIZE)0x00800080;
430 addr[ADDR0 << sh8b] = (FLASH_WORD_SIZE)0x00AA00AA;
431 addr[ADDR1 << sh8b] = (FLASH_WORD_SIZE)0x00550055;
432 addr[0] = (FLASH_WORD_SIZE)0x00500050; /* block erase */
433 udelay(30000); /* wait 30 ms */
434 }
435 else
436 addr[0] = (FLASH_WORD_SIZE)0x00300030; /* sector erase */
437 l_sect = sect;
438 }
439 }
440
441 /* re-enable interrupts if necessary */
442 if (flag){
443 enable_interrupts();
444 }
445
446 /* wait at least 80us - let's wait 1 ms */
447 udelay (1000);
448
449 /*
450 * We wait for the last triggered sector
451 */
452 if (l_sect < 0){
453 goto DONE;
454 }
455
456 start = get_timer (0);
457 last = start;
458 addr = (FLASH_WORD_SIZE *)(info->start[0] + (
459 (info->start[l_sect] - info->start[0]) << sh8b));
460 while ((addr[0] & (FLASH_WORD_SIZE)0x00800080) != (FLASH_WORD_SIZE)0x00800080) {
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200461 if ((now = get_timer(start)) > CONFIG_SYS_FLASH_ERASE_TOUT) {
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +0200462 printf ("Timeout\n");
463 return 1;
464 }
465 /* show that we're waiting */
466 if ((now - last) > 1000) { /* every second */
467 serial_putc ('.');
468 last = now;
469 }
470 }
471
472DONE:
473 /* reset to read mode */
474 addr = (FLASH_WORD_SIZE *)info->start[0];
475 addr[0] = (FLASH_WORD_SIZE)0x00F000F0; /* reset bank */
476
477 printf (" done\n");
478 return 0;
479}
480
481/*-----------------------------------------------------------------------
482 * Copy memory to flash, returns:
483 * 0 - OK
484 * 1 - write timeout
485 * 2 - Flash not erased
486 */
487
488int write_buff (flash_info_t *info, uchar *src, ulong addr, ulong cnt)
489{
490 ulong cp, wp, data;
491 int i, l, rc;
492
493 wp = (addr & ~3); /* get lower word aligned address */
494
495 /*
496 * handle unaligned start bytes
497 */
498 if ((l = addr - wp) != 0) {
499 data = 0;
500 for (i=0, cp=wp; i<l; ++i, ++cp) {
501 data = (data << 8) | (*(uchar *)cp);
502 }
503 for (; i<4 && cnt>0; ++i) {
504 data = (data << 8) | *src++;
505 --cnt;
506 ++cp;
507 }
508 for (; cnt==0 && i<4; ++i, ++cp) {
509 data = (data << 8) | (*(uchar *)cp);
510 }
511
512 if ((rc = write_word(info, wp, data)) != 0) {
513 return (rc);
514 }
515 wp += 4;
516 }
517
518 /*
519 * handle word aligned part
520 */
521 while (cnt >= 4) {
522 data = 0;
523 for (i=0; i<4; ++i) {
524 data = (data << 8) | *src++;
525 }
526 if ((rc = write_word(info, wp, data)) != 0) {
527 return (rc);
528 }
529 wp += 4;
530 cnt -= 4;
531 }
532
533 if (cnt == 0) {
534 return (0);
535 }
536
537 /*
538 * handle unaligned tail bytes
539 */
540 data = 0;
541 for (i=0, cp=wp; i<4 && cnt>0; ++i, ++cp) {
542 data = (data << 8) | *src++;
543 --cnt;
544 }
545 for (; i<4; ++i, ++cp) {
546 data = (data << 8) | (*(uchar *)cp);
547 }
548
549 return (write_word(info, wp, data));
550}
551
552/*-----------------------------------------------------------------------
553 * Write a word to Flash, returns:
554 * 0 - OK
555 * 1 - write timeout
556 * 2 - Flash not erased
557 */
558static int write_word (flash_info_t *info, ulong dest, ulong data)
559{
560 volatile FLASH_WORD_SIZE *addr2 = (FLASH_WORD_SIZE *)info->start[0];
561 volatile FLASH_WORD_SIZE *dest2;
562 volatile FLASH_WORD_SIZE *data2 = (FLASH_WORD_SIZE *)&data;
563 ulong start;
564 int flag;
565 int i;
566 unsigned char sh8b;
567
568 /* Check the ROM CS */
569 if ((info->start[0] >= ROM_CS1_START) && (info->start[0] < ROM_CS0_START)){
570 sh8b = 3;
571 }
572 else{
573 sh8b = 0;
574 }
575
576 dest2 = (FLASH_WORD_SIZE *)(((dest - info->start[0]) << sh8b) +
577 info->start[0]);
578
579 /* Check if Flash is (sufficiently) erased */
580 if ((*dest2 & (FLASH_WORD_SIZE)data) != (FLASH_WORD_SIZE)data) {
581 return (2);
582 }
583 /* Disable interrupts which might cause a timeout here */
584 flag = disable_interrupts();
585
586 for (i=0; i<4/sizeof(FLASH_WORD_SIZE); i++){
587 addr2[ADDR0 << sh8b] = (FLASH_WORD_SIZE)0x00AA00AA;
588 addr2[ADDR1 << sh8b] = (FLASH_WORD_SIZE)0x00550055;
589 addr2[ADDR0 << sh8b] = (FLASH_WORD_SIZE)0x00A000A0;
590
591 dest2[i << sh8b] = data2[i];
592
593 /* re-enable interrupts if necessary */
594 if (flag){
595 enable_interrupts();
596 }
597
598 /* data polling for D7 */
599 start = get_timer (0);
600 while ((dest2[i << sh8b] & (FLASH_WORD_SIZE)0x00800080) !=
601 (data2[i] & (FLASH_WORD_SIZE)0x00800080)) {
Jean-Christophe PLAGNIOL-VILLARD6d0f6bc2008-10-16 15:01:15 +0200602 if (get_timer(start) > CONFIG_SYS_FLASH_WRITE_TOUT) {
Wolfgang Denk8e6f1a82005-09-25 18:59:36 +0200603 return (1);
604 }
605 }
606 }
607
608 return (0);
609}
610
611/*----------------------------------------------------------------------- */