blob: 7e971822e07a73fda2dc4ae880ff45ae5fe772b6 [file] [log] [blame]
wdenk7aa78612003-05-03 15:50:43 +00001/*
2 * (C) Copyright 2003
3 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
4 *
5 * See file CREDITS for list of people who contributed to this
6 * project.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
21 * MA 02111-1307 USA
22 */
23
24#include <common.h>
25
26flash_info_t flash_info[CFG_MAX_FLASH_BANKS]; /* info for FLASH chips */
27
28/* NOTE - CONFIG_FLASH_16BIT means the CPU interface is 16-bit, it
29 * has nothing to do with the flash chip being 8-bit or 16-bit.
30 */
31#ifdef CONFIG_FLASH_16BIT
32typedef unsigned short FLASH_PORT_WIDTH;
33typedef volatile unsigned short FLASH_PORT_WIDTHV;
34#define FLASH_ID_MASK 0xFFFF
35#else
36typedef unsigned long FLASH_PORT_WIDTH;
37typedef volatile unsigned long FLASH_PORT_WIDTHV;
38#define FLASH_ID_MASK 0xFFFFFFFF
39#endif
40
41#define FPW FLASH_PORT_WIDTH
42#define FPWV FLASH_PORT_WIDTHV
43
44#define ORMASK(size) ((-size) & OR_AM_MSK)
45
46#define FLASH_CYCLE1 0x0555
47#define FLASH_CYCLE2 0x02aa
48
49/*-----------------------------------------------------------------------
50 * Functions
51 */
52static ulong flash_get_size(FPWV *addr, flash_info_t *info);
53static void flash_reset(flash_info_t *info);
54static int write_word_intel(flash_info_t *info, FPWV *dest, FPW data);
55static int write_word_amd(flash_info_t *info, FPWV *dest, FPW data);
56static void flash_get_offsets(ulong base, flash_info_t *info);
57static flash_info_t *flash_get_info(ulong base);
58
59/*-----------------------------------------------------------------------
60 * flash_init()
61 *
62 * sets up flash_info and returns size of FLASH (bytes)
63 */
64unsigned long flash_init (void)
65{
66 unsigned long size = 0;
67 int i;
68
69 /* Init: no FLASHes known */
70 for (i=0; i < CFG_MAX_FLASH_BANKS; ++i) {
71#if 0
72 ulong flashbase = (i == 0) ? PHYS_FLASH_1 : PHYS_FLASH_2;
73#else
74 ulong flashbase = CFG_FLASH_BASE;
75#endif
76
77 memset(&flash_info[i], 0, sizeof(flash_info_t));
78
79 flash_info[i].size =
80 flash_get_size((FPW *)flashbase, &flash_info[i]);
81
82 if (flash_info[i].flash_id == FLASH_UNKNOWN) {
83 printf ("## Unknown FLASH on Bank %d - Size = 0x%08lx\n",
84 i, flash_info[i].size);
85 }
86
87 size += flash_info[i].size;
88 }
89
90#if CFG_MONITOR_BASE >= CFG_FLASH_BASE
91 /* monitor protection ON by default */
92 flash_protect(FLAG_PROTECT_SET,
93 CFG_MONITOR_BASE,
94 CFG_MONITOR_BASE+CFG_MONITOR_LEN-1,
95 flash_get_info(CFG_MONITOR_BASE));
96#endif
97
98#ifdef CFG_ENV_IS_IN_FLASH
99 /* ENV protection ON by default */
100 flash_protect(FLAG_PROTECT_SET,
101 CFG_ENV_ADDR,
102 CFG_ENV_ADDR+CFG_ENV_SIZE-1,
103 flash_get_info(CFG_ENV_ADDR));
104#endif
105
106
107 return size ? size : 1;
108}
109
110/*-----------------------------------------------------------------------
111 */
112static void flash_reset(flash_info_t *info)
113{
114 FPWV *base = (FPWV *)(info->start[0]);
115
116 /* Put FLASH back in read mode */
117 if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_INTEL)
118 *base = (FPW)0x00FF00FF; /* Intel Read Mode */
119 else if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_AMD)
120 *base = (FPW)0x00F000F0; /* AMD Read Mode */
121}
122
123/*-----------------------------------------------------------------------
124 */
125static void flash_get_offsets (ulong base, flash_info_t *info)
126{
127 int i;
128
129 /* set up sector start address table */
130 if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_INTEL
131 && (info->flash_id & FLASH_BTYPE)) {
132 int bootsect_size; /* number of bytes/boot sector */
133 int sect_size; /* number of bytes/regular sector */
134
135 bootsect_size = 0x00002000 * (sizeof(FPW)/2);
136 sect_size = 0x00010000 * (sizeof(FPW)/2);
137
138 /* set sector offsets for bottom boot block type */
139 for (i = 0; i < 8; ++i) {
140 info->start[i] = base + (i * bootsect_size);
141 }
142 for (i = 8; i < info->sector_count; i++) {
143 info->start[i] = base + ((i - 7) * sect_size);
144 }
145 }
146 else if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_AMD
147 && (info->flash_id & FLASH_TYPEMASK) == FLASH_AM640U) {
148
149 int sect_size; /* number of bytes/sector */
150
151 sect_size = 0x00010000 * (sizeof(FPW)/2);
152
153 /* set up sector start address table (uniform sector type) */
154 for( i = 0; i < info->sector_count; i++ )
155 info->start[i] = base + (i * sect_size);
156 }
157}
158
159/*-----------------------------------------------------------------------
160 */
161
162static flash_info_t *flash_get_info(ulong base)
163{
164 int i;
165 flash_info_t * info;
166
167 for (i = 0; i < CFG_MAX_FLASH_BANKS; i ++) {
168 info = & flash_info[i];
169 if (info->start[0] <= base && base < info->start[0] + info->size)
170 break;
171 }
172
173 return i == CFG_MAX_FLASH_BANKS ? 0 : info;
174}
175
176/*-----------------------------------------------------------------------
177 */
178
179void flash_print_info (flash_info_t *info)
180{
181 int i;
182 uchar *boottype;
183 uchar *bootletter;
184 uchar *fmt;
185 uchar botbootletter[] = "B";
186 uchar topbootletter[] = "T";
187 uchar botboottype[] = "bottom boot sector";
188 uchar topboottype[] = "top boot sector";
189
190 if (info->flash_id == FLASH_UNKNOWN) {
191 printf ("missing or unknown FLASH type\n");
192 return;
193 }
194
195 switch (info->flash_id & FLASH_VENDMASK) {
196 case FLASH_MAN_AMD: printf ("AMD "); break;
197 case FLASH_MAN_BM: printf ("BRIGHT MICRO "); break;
198 case FLASH_MAN_FUJ: printf ("FUJITSU "); break;
199 case FLASH_MAN_SST: printf ("SST "); break;
200 case FLASH_MAN_STM: printf ("STM "); break;
201 case FLASH_MAN_INTEL: printf ("INTEL "); break;
202 default: printf ("Unknown Vendor "); break;
203 }
204
205 /* check for top or bottom boot, if it applies */
206 if (info->flash_id & FLASH_BTYPE) {
207 boottype = botboottype;
208 bootletter = botbootletter;
209 }
210 else {
211 boottype = topboottype;
212 bootletter = topbootletter;
213 }
214
215 switch (info->flash_id & FLASH_TYPEMASK) {
216 case FLASH_AM640U:
217 fmt = "29LV641D (64 Mbit, uniform sectors)\n";
218 break;
219 case FLASH_28F800C3B:
220 case FLASH_28F800C3T:
221 fmt = "28F800C3%s (8 Mbit, %s)\n";
222 break;
223 case FLASH_INTEL800B:
224 case FLASH_INTEL800T:
225 fmt = "28F800B3%s (8 Mbit, %s)\n";
226 break;
227 case FLASH_28F160C3B:
228 case FLASH_28F160C3T:
229 fmt = "28F160C3%s (16 Mbit, %s)\n";
230 break;
231 case FLASH_INTEL160B:
232 case FLASH_INTEL160T:
233 fmt = "28F160B3%s (16 Mbit, %s)\n";
234 break;
235 case FLASH_28F320C3B:
236 case FLASH_28F320C3T:
237 fmt = "28F320C3%s (32 Mbit, %s)\n";
238 break;
239 case FLASH_INTEL320B:
240 case FLASH_INTEL320T:
241 fmt = "28F320B3%s (32 Mbit, %s)\n";
242 break;
243 case FLASH_28F640C3B:
244 case FLASH_28F640C3T:
245 fmt = "28F640C3%s (64 Mbit, %s)\n";
246 break;
247 case FLASH_INTEL640B:
248 case FLASH_INTEL640T:
249 fmt = "28F640B3%s (64 Mbit, %s)\n";
250 break;
251 default:
252 fmt = "Unknown Chip Type\n";
253 break;
254 }
255
256 printf (fmt, bootletter, boottype);
257
258 printf (" Size: %ld MB in %d Sectors\n",
259 info->size >> 20,
260 info->sector_count);
261
262 printf (" Sector Start Addresses:");
263
264 for (i=0; i<info->sector_count; ++i) {
265 if ((i % 5) == 0) {
266 printf ("\n ");
267 }
268
269 printf (" %08lX%s", info->start[i],
270 info->protect[i] ? " (RO)" : " ");
271 }
272
273 printf ("\n");
274}
275
276/*-----------------------------------------------------------------------
277 */
278
279/*
280 * The following code cannot be run from FLASH!
281 */
282
283ulong flash_get_size (FPWV *addr, flash_info_t *info)
284{
285 /* Write auto select command: read Manufacturer ID */
286
287 /* Write auto select command sequence and test FLASH answer */
288 addr[FLASH_CYCLE1] = (FPW)0x00AA00AA; /* for AMD, Intel ignores this */
289 addr[FLASH_CYCLE2] = (FPW)0x00550055; /* for AMD, Intel ignores this */
290 addr[FLASH_CYCLE1] = (FPW)0x00900090; /* selects Intel or AMD */
291
292 /* The manufacturer codes are only 1 byte, so just use 1 byte.
293 * This works for any bus width and any FLASH device width.
294 */
wdenke6009622003-05-05 17:09:41 +0000295 udelay(100);
296 switch (addr[0] & 0xff) {
wdenk7aa78612003-05-03 15:50:43 +0000297
298 case (uchar)AMD_MANUFACT:
299 info->flash_id = FLASH_MAN_AMD;
300 break;
301
302 case (uchar)INTEL_MANUFACT:
303 info->flash_id = FLASH_MAN_INTEL;
304 break;
305
306 default:
307 info->flash_id = FLASH_UNKNOWN;
308 info->sector_count = 0;
309 info->size = 0;
310 break;
311 }
312
313 /* Check 16 bits or 32 bits of ID so work on 32 or 16 bit bus. */
wdenk7aa78612003-05-03 15:50:43 +0000314 if (info->flash_id != FLASH_UNKNOWN) switch (addr[1]) {
315
316 case (FPW)AMD_ID_LV640U: /* 29LV640 and 29LV641 have same ID */
317 info->flash_id += FLASH_AM640U;
318 info->sector_count = 128;
319 info->size = 0x00800000 * (sizeof(FPW)/2);
320 break; /* => 8 or 16 MB */
321
322 case (FPW)INTEL_ID_28F800C3B:
323 info->flash_id += FLASH_28F800C3B;
324 info->sector_count = 23;
325 info->size = 0x00100000 * (sizeof(FPW)/2);
326 break; /* => 1 or 2 MB */
327
328 case (FPW)INTEL_ID_28F800B3B:
329 info->flash_id += FLASH_INTEL800B;
330 info->sector_count = 23;
331 info->size = 0x00100000 * (sizeof(FPW)/2);
332 break; /* => 1 or 2 MB */
333
334 case (FPW)INTEL_ID_28F160C3B:
335 info->flash_id += FLASH_28F160C3B;
336 info->sector_count = 39;
337 info->size = 0x00200000 * (sizeof(FPW)/2);
338 break; /* => 2 or 4 MB */
339
340 case (FPW)INTEL_ID_28F160B3B:
341 info->flash_id += FLASH_INTEL160B;
342 info->sector_count = 39;
343 info->size = 0x00200000 * (sizeof(FPW)/2);
344 break; /* => 2 or 4 MB */
345
346 case (FPW)INTEL_ID_28F320C3B:
347 info->flash_id += FLASH_28F320C3B;
348 info->sector_count = 71;
349 info->size = 0x00400000 * (sizeof(FPW)/2);
350 break; /* => 4 or 8 MB */
351
352 case (FPW)INTEL_ID_28F320B3B:
353 info->flash_id += FLASH_INTEL320B;
354 info->sector_count = 71;
355 info->size = 0x00400000 * (sizeof(FPW)/2);
356 break; /* => 4 or 8 MB */
357
358 case (FPW)INTEL_ID_28F640C3B:
359 info->flash_id += FLASH_28F640C3B;
360 info->sector_count = 135;
361 info->size = 0x00800000 * (sizeof(FPW)/2);
362 break; /* => 8 or 16 MB */
363
364 case (FPW)INTEL_ID_28F640B3B:
365 info->flash_id += FLASH_INTEL640B;
366 info->sector_count = 135;
367 info->size = 0x00800000 * (sizeof(FPW)/2);
368 break; /* => 8 or 16 MB */
369
370 default:
371 info->flash_id = FLASH_UNKNOWN;
372 info->sector_count = 0;
373 info->size = 0;
374 return (0); /* => no or unknown flash */
375 }
376
377 flash_get_offsets((ulong)addr, info);
378
379 /* Put FLASH back in read mode */
380 flash_reset(info);
381
382 return (info->size);
383}
384
385/*-----------------------------------------------------------------------
386 */
387
388int flash_erase (flash_info_t *info, int s_first, int s_last)
389{
390 FPWV *addr;
391 int flag, prot, sect;
392 int intel = (info->flash_id & FLASH_VENDMASK) == FLASH_MAN_INTEL;
393 ulong start, now, last;
394 int rcode = 0;
395
396 if ((s_first < 0) || (s_first > s_last)) {
397 if (info->flash_id == FLASH_UNKNOWN) {
398 printf ("- missing\n");
399 } else {
400 printf ("- no sectors to erase\n");
401 }
402 return 1;
403 }
404
405 switch (info->flash_id & FLASH_TYPEMASK) {
406 case FLASH_INTEL800B:
407 case FLASH_INTEL160B:
408 case FLASH_INTEL320B:
409 case FLASH_INTEL640B:
410 case FLASH_28F800C3B:
411 case FLASH_28F160C3B:
412 case FLASH_28F320C3B:
413 case FLASH_28F640C3B:
414 case FLASH_AM640U:
415 break;
416 case FLASH_UNKNOWN:
417 default:
418 printf ("Can't erase unknown flash type %08lx - aborted\n",
419 info->flash_id);
420 return 1;
421 }
422
423 prot = 0;
424 for (sect=s_first; sect<=s_last; ++sect) {
425 if (info->protect[sect]) {
426 prot++;
427 }
428 }
429
430 if (prot) {
431 printf ("- Warning: %d protected sectors will not be erased!\n",
432 prot);
433 } else {
434 printf ("\n");
435 }
436
437 last = get_timer(0);
438
439 /* Start erase on unprotected sectors */
440 for (sect = s_first; sect<=s_last && rcode == 0; sect++) {
441
442 if (info->protect[sect] != 0) /* protected, skip it */
443 continue;
444
445 /* Disable interrupts which might cause a timeout here */
446 flag = disable_interrupts();
447
448 addr = (FPWV *)(info->start[sect]);
449 if (intel) {
450 *addr = (FPW)0x00500050; /* clear status register */
451 *addr = (FPW)0x00200020; /* erase setup */
452 *addr = (FPW)0x00D000D0; /* erase confirm */
453 }
454 else {
455 /* must be AMD style if not Intel */
456 FPWV *base; /* first address in bank */
457
458 base = (FPWV *)(info->start[0]);
459 base[FLASH_CYCLE1] = (FPW)0x00AA00AA; /* unlock */
460 base[FLASH_CYCLE2] = (FPW)0x00550055; /* unlock */
461 base[FLASH_CYCLE1] = (FPW)0x00800080; /* erase mode */
462 base[FLASH_CYCLE1] = (FPW)0x00AA00AA; /* unlock */
463 base[FLASH_CYCLE2] = (FPW)0x00550055; /* unlock */
464 *addr = (FPW)0x00300030; /* erase sector */
465 }
466
467 /* re-enable interrupts if necessary */
468 if (flag)
469 enable_interrupts();
470
471 start = get_timer(0);
472
473 /* wait at least 50us for AMD, 80us for Intel.
474 * Let's wait 1 ms.
475 */
476 udelay (1000);
477
478 while ((*addr & (FPW)0x00800080) != (FPW)0x00800080) {
479 if ((now = get_timer(start)) > CFG_FLASH_ERASE_TOUT) {
480 printf ("Timeout\n");
481
482 if (intel) {
483 /* suspend erase */
484 *addr = (FPW)0x00B000B0;
485 }
486
487 flash_reset(info); /* reset to read mode */
488 rcode = 1; /* failed */
489 break;
490 }
491
492 /* show that we're waiting */
493 if ((get_timer(last)) > CFG_HZ) {/* every second */
494 putc ('.');
495 last = get_timer(0);
496 }
497 }
498
499 /* show that we're waiting */
500 if ((get_timer(last)) > CFG_HZ) { /* every second */
501 putc ('.');
502 last = get_timer(0);
503 }
504
505 flash_reset(info); /* reset to read mode */
506 }
507
508 printf (" done\n");
509 return rcode;
510}
511
512/*-----------------------------------------------------------------------
513 * Copy memory to flash, returns:
514 * 0 - OK
515 * 1 - write timeout
516 * 2 - Flash not erased
517 */
518int write_buff (flash_info_t *info, uchar *src, ulong addr, ulong cnt)
519{
wdenke6009622003-05-05 17:09:41 +0000520 FPW data = 0; /* 16 or 32 bit word, matches flash bus width on MPC8XX */
521 int bytes; /* number of bytes to program in current word */
522 int left; /* number of bytes left to program */
523 int i, res;
wdenk7aa78612003-05-03 15:50:43 +0000524
wdenke6009622003-05-05 17:09:41 +0000525 for (left = cnt, res = 0;
526 left > 0 && res == 0;
527 addr += sizeof(data), left -= sizeof(data) - bytes) {
wdenk7aa78612003-05-03 15:50:43 +0000528
wdenke6009622003-05-05 17:09:41 +0000529 bytes = addr & (sizeof(data) - 1);
530 addr &= ~(sizeof(data) - 1);
wdenk7aa78612003-05-03 15:50:43 +0000531
wdenke6009622003-05-05 17:09:41 +0000532 /* combine source and destination data so can program
533 * an entire word of 16 or 32 bits
534 */
535 for (i = 0; i < sizeof(data); i++) {
536 data <<= 8;
537 if (i < bytes || i - bytes >= left )
538 data += *((uchar *)addr + i);
539 else
540 data += *src++;
541 }
542
543 /* write one word to the flash */
544 switch (info->flash_id & FLASH_VENDMASK) {
545 case FLASH_MAN_AMD:
546 res = write_word_amd(info, (FPWV *)addr, data);
547 break;
548 case FLASH_MAN_INTEL:
549 res = write_word_intel(info, (FPWV *)addr, data);
550 break;
551 default:
552 /* unknown flash type, error! */
553 printf ("missing or unknown FLASH type\n");
554 res = 1; /* not really a timeout, but gives error */
555 break;
556 }
wdenk7aa78612003-05-03 15:50:43 +0000557 }
558
wdenke6009622003-05-05 17:09:41 +0000559 return (res);
wdenk7aa78612003-05-03 15:50:43 +0000560}
561
562/*-----------------------------------------------------------------------
563 * Write a word to Flash for AMD FLASH
564 * A word is 16 or 32 bits, whichever the bus width of the flash bank
565 * (not an individual chip) is.
566 *
567 * returns:
568 * 0 - OK
569 * 1 - write timeout
570 * 2 - Flash not erased
571 */
572static int write_word_amd (flash_info_t *info, FPWV *dest, FPW data)
573{
wdenke6009622003-05-05 17:09:41 +0000574 ulong start;
575 int flag;
576 int res = 0; /* result, assume success */
577 FPWV *base; /* first address in flash bank */
wdenk7aa78612003-05-03 15:50:43 +0000578
wdenke6009622003-05-05 17:09:41 +0000579 /* Check if Flash is (sufficiently) erased */
580 if ((*dest & data) != data) {
581 return (2);
wdenk7aa78612003-05-03 15:50:43 +0000582 }
wdenk7aa78612003-05-03 15:50:43 +0000583
wdenke6009622003-05-05 17:09:41 +0000584
585 base = (FPWV *)(info->start[0]);
586
587 /* Disable interrupts which might cause a timeout here */
588 flag = disable_interrupts();
589
590 base[FLASH_CYCLE1] = (FPW)0x00AA00AA; /* unlock */
591 base[FLASH_CYCLE2] = (FPW)0x00550055; /* unlock */
592 base[FLASH_CYCLE1] = (FPW)0x00A000A0; /* selects program mode */
593
594 *dest = data; /* start programming the data */
595
596 /* re-enable interrupts if necessary */
597 if (flag)
598 enable_interrupts();
599
600 start = get_timer (0);
601
602 /* data polling for D7 */
603 while (res == 0 && (*dest & (FPW)0x00800080) != (data & (FPW)0x00800080)) {
604 if (get_timer(start) > CFG_FLASH_WRITE_TOUT) {
605 *dest = (FPW)0x00F000F0; /* reset bank */
606 res = 1;
607 }
608 }
609
610 return (res);
wdenk7aa78612003-05-03 15:50:43 +0000611}
612
613/*-----------------------------------------------------------------------
614 * Write a word to Flash for Intel FLASH
615 * A word is 16 or 32 bits, whichever the bus width of the flash bank
616 * (not an individual chip) is.
617 *
618 * returns:
619 * 0 - OK
620 * 1 - write timeout
621 * 2 - Flash not erased
622 */
623static int write_word_intel (flash_info_t *info, FPWV *dest, FPW data)
624{
wdenke6009622003-05-05 17:09:41 +0000625 ulong start;
626 int flag;
627 int res = 0; /* result, assume success */
wdenk7aa78612003-05-03 15:50:43 +0000628
wdenke6009622003-05-05 17:09:41 +0000629 /* Check if Flash is (sufficiently) erased */
630 if ((*dest & data) != data) {
631 return (2);
wdenk7aa78612003-05-03 15:50:43 +0000632 }
wdenk7aa78612003-05-03 15:50:43 +0000633
wdenke6009622003-05-05 17:09:41 +0000634 /* Disable interrupts which might cause a timeout here */
635 flag = disable_interrupts();
wdenk7aa78612003-05-03 15:50:43 +0000636
wdenke6009622003-05-05 17:09:41 +0000637 *dest = (FPW)0x00500050; /* clear status register */
638 *dest = (FPW)0x00FF00FF; /* make sure in read mode */
639 *dest = (FPW)0x00400040; /* program setup */
wdenk7aa78612003-05-03 15:50:43 +0000640
wdenke6009622003-05-05 17:09:41 +0000641 *dest = data; /* start programming the data */
642
643 /* re-enable interrupts if necessary */
644 if (flag)
645 enable_interrupts();
646
647 start = get_timer (0);
648
649 while (res == 0 && (*dest & (FPW)0x00800080) != (FPW)0x00800080) {
650 if (get_timer(start) > CFG_FLASH_WRITE_TOUT) {
651 *dest = (FPW)0x00B000B0; /* Suspend program */
652 res = 1;
653 }
654 }
655
656 if (res == 0 && (*dest & (FPW)0x00100010))
657 res = 1; /* write failed, time out error is close enough */
658
659 *dest = (FPW)0x00500050; /* clear status register */
660 *dest = (FPW)0x00FF00FF; /* make sure in read mode */
661
662 return (res);
wdenk7aa78612003-05-03 15:50:43 +0000663}