743211fa979cf12c4702bb556f4943b052122439
[oweals/u-boot.git] / board / amirix / ap1000 / ap1000.c
1 /*
2  * amirix.c: ppcboot platform support for AMIRIX board
3  *
4  * Copyright 2002 Mind NV
5  * Copyright 2003 AMIRIX Systems Inc.
6  *
7  * http://www.mind.be/
8  * http://www.amirix.com/
9  *
10  * Author : Peter De Schrijver (p2@mind.be)
11  *          Frank Smith (smith@amirix.com)
12  *
13  * Derived from : Other platform support files in this tree, ml2
14  *
15  * This software may be used and distributed according to the terms of
16  * the GNU General Public License (GPL) version 2, incorporated herein by
17  * reference. Drivers based on or derived from this code fall under the GPL
18  * and must retain the authorship, copyright and this license notice. This
19  * file is not a complete program and may only be used when the entire
20  * program is licensed under the GPL.
21  *
22  */
23
24 #include <common.h>
25 #include <command.h>
26 #include <asm/processor.h>
27
28 #include "powerspan.h"
29 #include "ap1000.h"
30
31 int board_pre_init (void)
32 {
33     return 0;
34 }
35
36 /** serial number and platform display at startup */
37 int checkboard (void)
38 {
39     unsigned char *s = getenv ("serial#");
40     unsigned char *e;
41
42     /* After a loadace command, the SystemAce control register is left in a wonky state. */
43     /* this code did not work in board_pre_init */
44     unsigned char* p = (unsigned char*)AP1000_SYSACE_REGBASE;
45     p[SYSACE_CTRLREG0] = 0x0;
46
47     /* add platform and device to banner */
48     switch(get_device()){
49         case AP1xx_AP107_TARGET:{
50             puts(AP1xx_AP107_TARGET_STR);
51             break;
52         }
53         case AP1xx_AP120_TARGET:{
54             puts(AP1xx_AP120_TARGET_STR);
55             break;
56         }
57         case AP1xx_AP130_TARGET:{
58             puts(AP1xx_AP130_TARGET_STR);
59             break;
60         }
61         case AP1xx_AP1070_TARGET:{
62             puts(AP1xx_AP1070_TARGET_STR);
63             break;
64         }
65         case AP1xx_AP1100_TARGET:{
66             puts(AP1xx_AP1100_TARGET_STR);
67             break;
68         }
69         default:{
70             puts(AP1xx_UNKNOWN_STR);
71             break;
72         }
73     }
74     puts(AP1xx_TARGET_STR);
75     puts(" with ");
76
77     switch(get_platform()){
78         case AP100_BASELINE_PLATFORM:
79         case AP1000_BASELINE_PLATFORM:{
80             puts(AP1xx_BASELINE_PLATFORM_STR);
81             break;
82         }
83         case AP1xx_QUADGE_PLATFORM:{
84             puts(AP1xx_QUADGE_PLATFORM_STR);
85             break;
86         }
87         case AP1xx_MGT_REF_PLATFORM:{
88             puts(AP1xx_MGT_REF_PLATFORM_STR);
89             break;
90         }
91         case AP1xx_STANDARD_PLATFORM:{
92             puts(AP1xx_STANDARD_PLATFORM_STR);
93             break;
94         }
95         case AP1xx_DUAL_PLATFORM:{
96             puts(AP1xx_DUAL_PLATFORM_STR);
97             break;
98         }
99         case AP1xx_BASE_SRAM_PLATFORM:{
100             puts(AP1xx_BASE_SRAM_PLATFORM_STR);
101             break;
102         }
103         case AP1xx_PCI_PCB_TESTPLATFORM:
104         case AP1000_PCI_PCB_TESTPLATFORM:{
105             puts(AP1xx_PCI_PCB_TESTPLATFORM_STR);
106             break;
107         }
108         case AP1xx_DUAL_GE_MEZZ_TESTPLATFORM:{
109             puts(AP1xx_DUAL_GE_MEZZ_TESTPLATFORM_STR);
110             break;
111         }
112         case AP1xx_SFP_MEZZ_TESTPLATFORM:{
113             puts(AP1xx_SFP_MEZZ_TESTPLATFORM_STR);
114             break;
115         }
116         default:{
117             puts(AP1xx_UNKNOWN_STR);
118             break;
119         }
120     }
121
122     if((get_platform() & AP1xx_TESTPLATFORM_MASK) != 0){
123         puts(AP1xx_TESTPLATFORM_STR);
124     }
125     else{
126         puts(AP1xx_PLATFORM_STR);
127     }
128
129     putc('\n');
130
131     puts ("Serial#: ");
132
133     if (!s) {
134         printf ("### No HW ID - assuming AMIRIX");
135     } else {
136         for (e = s; *e; ++e) {
137             if (*e == ' ')
138                 break;
139         }
140
141         for (; s < e; ++s) {
142             putc (*s);
143         }
144     }
145
146     putc ('\n');
147
148     return (0);
149 }
150
151
152 long int initdram (int board_type)
153 {
154     unsigned char *s = getenv ("dramsize");
155
156     if(s != NULL){
157         if((s[0] == '0') && ((s[1] == 'x') || (s[1] == 'X'))){
158             s += 2;
159         }
160         return simple_strtoul(s, NULL, 16);
161     }
162     else{
163         /* give all 64 MB */
164                 return 64 * 1024 * 1024;
165     }
166 }
167
168 unsigned int get_platform(void){
169     unsigned int *revision_reg_ptr = (unsigned int *)AP1xx_FPGA_REV_ADDR;
170     return (*revision_reg_ptr & AP1xx_PLATFORM_MASK);
171 }
172
173 unsigned int get_device(void){
174     unsigned int *revision_reg_ptr = (unsigned int *)AP1xx_FPGA_REV_ADDR;
175
176     return (*revision_reg_ptr & AP1xx_TARGET_MASK);
177 }
178
179 #if 0  // loadace is not working; it appears to be a hardware issue with the system ace.
180 /*
181    This function loads FPGA configurations from the SystemACE CompactFlash
182 */
183 int do_loadace (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
184 {
185     unsigned char *p = (unsigned char *)AP1000_SYSACE_REGBASE;
186     int cfg;
187
188     if((p[SYSACE_STATREG0] & 0x10) == 0) {
189         p[SYSACE_CTRLREG0] = 0x80;
190         printf ("\nNo CompactFlash Detected\n\n");
191         p[SYSACE_CTRLREG0] = 0x00;
192         return 1;
193     }
194
195     // reset configuration controller: |  0x80
196     // select cpflash                  & ~0x40
197     // cfg start                       |  0x20
198     // wait for cfgstart               & ~0x10
199     // force cfgmode:                  |  0x08
200     // do no force cfgaddr:            & ~0x04
201     // clear mpulock:                  & ~0x02
202     // do not force lock request       & ~0x01
203
204     p[SYSACE_CTRLREG0] = 0x80 | 0x20 | 0x08;
205     p[SYSACE_CTRLREG1] = 0x00;
206
207     // force config address if arg2 exists
208     if (argc == 2) {
209         cfg = simple_strtoul(argv[1], NULL, 10);
210
211         if(cfg > 7) {
212            printf ("\nInvalid Configuration\n\n");
213          p[SYSACE_CTRLREG0] = 0x00;
214            return 1;
215         }
216         // Set config address
217         p[SYSACE_CTRLREG1] = (cfg << 5);
218         // force cfgaddr
219         p[SYSACE_CTRLREG0] |= 0x04;
220
221     } else {
222         cfg = (p[SYSACE_STATREG1] & 0xE0) >> 5;
223     }
224
225     /* release configuration controller */
226     printf("\nLoading V2PRO with config %d...\n", cfg);
227     p[SYSACE_CTRLREG0] &= ~0x80;
228
229
230     while((p[SYSACE_STATREG1] & 0x01) == 0) {
231
232         if(p[SYSACE_ERRREG0] & 0x80) {
233             // attempting to load an invalid configuration makes the cpflash
234             // appear to be removed. Reset here to avoid that problem
235             p[SYSACE_CTRLREG0] = 0x80;
236             printf("\nConfiguration %d Read Error\n\n", cfg);
237             p[SYSACE_CTRLREG0] = 0x00;
238             return 1;
239         }
240     }
241
242     p[SYSACE_CTRLREG0] |= 0x20;
243
244     return 0;
245 }
246 #endif
247
248 /** Console command to display and set the software reconfigure byte
249   * <pre>
250   * swconfig        - display the current value of the software reconfigure byte
251   * swconfig [#]    - change the software reconfigure byte to #
252   * </pre>
253   * @param  *cmdtp  [IN] as passed by run_command (ignored)
254   * @param  flag    [IN] as passed by run_command (ignored)
255   * @param  argc    [IN] as passed by run_command if 1, display, if 2 change
256   * @param  *argv[] [IN] contains the parameters to use
257   * @return
258   * <pre>
259   *      0 if passed
260   *     -1 if failed
261   * </pre>
262   */
263 int do_swconfigbyte(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]){
264     unsigned char *sector_buffer = NULL;
265     unsigned char input_char;
266     int write_result;
267     unsigned int  input_uint;
268
269     /* display value if no argument */
270     if(argc < 2){
271         printf("Software configuration byte is currently: 0x%02x\n",
272                *((unsigned char *) (SW_BYTE_SECTOR_ADDR + SW_BYTE_SECTOR_OFFSET)));
273         return 0;
274     }
275     else if(argc > 3){
276         printf("Too many arguments\n");
277         return -1;
278     }
279
280     /* if 3 arguments, 3rd argument is the address to use */
281     if(argc == 3){
282         input_uint = simple_strtoul(argv[1], NULL, 16);
283         sector_buffer = (unsigned char *)input_uint;
284     }
285     else{
286         sector_buffer = (unsigned char *)DEFAULT_TEMP_ADDR;
287     }
288
289     input_char = simple_strtoul(argv[1], NULL, 0);
290     if((input_char & ~SW_BYTE_MASK) != 0){
291         printf("Input of 0x%02x will be masked to 0x%02x\n",
292                 input_char, (input_char & SW_BYTE_MASK));
293         input_char = input_char & SW_BYTE_MASK;
294     }
295
296     memcpy(sector_buffer, (void *)SW_BYTE_SECTOR_ADDR, SW_BYTE_SECTOR_SIZE);
297     sector_buffer[SW_BYTE_SECTOR_OFFSET] = input_char;
298
299
300     printf("Erasing Flash...");
301     if (flash_sect_erase (SW_BYTE_SECTOR_ADDR, (SW_BYTE_SECTOR_ADDR + SW_BYTE_SECTOR_OFFSET))){
302         return -1;
303     }
304
305     printf("Writing to Flash... ");
306     write_result = flash_write(sector_buffer, SW_BYTE_SECTOR_ADDR, SW_BYTE_SECTOR_SIZE);
307     if (write_result != 0) {
308         flash_perror (write_result);
309         return -1;
310     }
311     else{
312         printf("done\n");
313         printf("Software configuration byte is now: 0x%02x\n",
314                 *((unsigned char *) (SW_BYTE_SECTOR_ADDR + SW_BYTE_SECTOR_OFFSET)));
315     }
316
317     return 0;
318 }
319
320 #define ONE_SECOND 1000000
321
322 int do_pause(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]){
323     int pause_time;
324     unsigned int delay_time;
325     int break_loop = 0;
326
327     /* display value if no argument */
328     if(argc < 2){
329         pause_time = 1;
330     }
331
332     else if(argc > 2){
333         printf("Too many arguments\n");
334         return -1;
335     }
336     else{
337         pause_time = simple_strtoul(argv[1], NULL, 0);
338     }
339
340     printf("Pausing with a poll time of %d, press any key to reactivate\n", pause_time);
341     delay_time = pause_time * ONE_SECOND;
342     while(break_loop == 0){
343         udelay(delay_time);
344         if(serial_tstc() != 0){
345             break_loop = 1;
346             /* eat user key presses */
347             while(serial_tstc() != 0){
348                 serial_getc();
349             }
350         }
351     }
352
353     return 0;
354 }
355
356 int do_swreconfig(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]){
357     printf("Triggering software reconfigure (software config byte is 0x%02x)...\n",
358            *((unsigned char *) (SW_BYTE_SECTOR_ADDR + SW_BYTE_SECTOR_OFFSET)));
359     udelay (1000);
360     *((unsigned char*)AP1000_CPLD_BASE) = 1;
361
362     return 0;
363 }
364
365 #define GET_DECIMAL(low_byte) ((low_byte >> 5) * 125)
366 #define TEMP_BUSY_BIT   0x80
367 #define TEMP_LHIGH_BIT  0x40
368 #define TEMP_LLOW_BIT   0x20
369 #define TEMP_EHIGH_BIT  0x10
370 #define TEMP_ELOW_BIT   0x08
371 #define TEMP_OPEN_BIT   0x04
372 #define TEMP_ETHERM_BIT 0x02
373 #define TEMP_LTHERM_BIT 0x01
374
375 int do_temp_sensor(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]){
376     char cmd;
377     int ret_val = 0;
378     unsigned char temp_byte;
379     int temp;
380     int temp_low;
381     int low;
382     int low_low;
383     int high;
384     int high_low;
385     int therm;
386     unsigned char user_data[4] = { 0 };
387     int user_data_count = 0;
388     int ii;
389
390     if(argc > 1){
391         cmd = argv[1][0];
392     }
393     else{
394         cmd = 's'; /* default to status */
395     }
396
397     user_data_count = argc - 2;
398     for(ii = 0;ii < user_data_count;ii++){
399         user_data[ii] = simple_strtoul(argv[2 + ii], NULL, 0);
400     }
401     switch (cmd){
402         case 's':{
403
404             if(I2CAccess(0x2, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
405                 goto fail;
406             }
407             printf("Status    : 0x%02x  ", temp_byte);
408             if(temp_byte & TEMP_BUSY_BIT){
409                 printf("BUSY ");
410             }
411
412             if(temp_byte & TEMP_LHIGH_BIT){
413                 printf("LHIGH ");
414             }
415
416             if(temp_byte & TEMP_LLOW_BIT){
417                 printf("LLOW ");
418             }
419
420             if(temp_byte & TEMP_EHIGH_BIT){
421                 printf("EHIGH ");
422             }
423
424             if(temp_byte & TEMP_ELOW_BIT){
425                 printf("ELOW ");
426             }
427
428             if(temp_byte & TEMP_OPEN_BIT){
429                 printf("OPEN ");
430             }
431
432             if(temp_byte & TEMP_ETHERM_BIT){
433                 printf("ETHERM ");
434             }
435
436             if(temp_byte & TEMP_LTHERM_BIT){
437                 printf("LTHERM");
438             }
439             printf("\n");
440
441             if(I2CAccess(0x3, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
442                 goto fail;
443             }
444             printf("Config    : 0x%02x  ", temp_byte);
445
446             if(I2CAccess(0x4, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
447                 printf("\n");
448                 goto fail;
449             }
450             printf("Conversion: 0x%02x\n", temp_byte);
451             if(I2CAccess(0x22, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
452                 goto fail;
453             }
454             printf("Cons Alert: 0x%02x  ", temp_byte);
455
456             if(I2CAccess(0x21, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
457                 printf("\n");
458                 goto fail;
459             }
460             printf("Therm Hyst: %d\n", temp_byte);
461
462             if(I2CAccess(0x0, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
463                 goto fail;
464             }
465             temp = temp_byte;
466             if(I2CAccess(0x6, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
467                 goto fail;
468             }
469             low = temp_byte;
470             if(I2CAccess(0x5, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
471                 goto fail;
472             }
473             high = temp_byte;
474             if(I2CAccess(0x20, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
475                 goto fail;
476             }
477             therm = temp_byte;
478             printf("Local Temp: %2d     Low: %2d     High: %2d     THERM: %2d\n", temp, low, high, therm);
479
480             if(I2CAccess(0x1, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
481                 goto fail;
482             }
483             temp = temp_byte;
484             if(I2CAccess(0x10, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
485                 goto fail;
486             }
487             temp_low = temp_byte;
488             if(I2CAccess(0x8, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
489                 goto fail;
490             }
491             low = temp_byte;
492             if(I2CAccess(0x14, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
493                 goto fail;
494             }
495             low_low = temp_byte;
496             if(I2CAccess(0x7, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
497                 goto fail;
498             }
499             high = temp_byte;
500             if(I2CAccess(0x13, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
501                 goto fail;
502             }
503             high_low = temp_byte;
504             if(I2CAccess(0x19, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
505                 goto fail;
506             }
507             therm = temp_byte;
508             if(I2CAccess(0x11, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &temp_byte, I2C_READ) != 0){
509                 goto fail;
510             }
511             printf("Ext Temp  : %2d.%03d Low: %2d.%03d High: %2d.%03d THERM: %2d Offset: %2d\n", temp, GET_DECIMAL(temp_low), low, GET_DECIMAL(low_low), high, GET_DECIMAL(high_low), therm, temp_byte);
512             break;
513         }
514         case 'l':{ /* alter local limits : low, high, therm */
515             if(argc < 3){
516                 goto usage;
517             }
518
519             /* low */
520             if(I2CAccess(0xC, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[0], I2C_WRITE) != 0){
521                 goto fail;
522             }
523
524             if(user_data_count > 1){
525                 /* high */
526                 if(I2CAccess(0xB, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[1], I2C_WRITE) != 0){
527                     goto fail;
528                 }
529             }
530
531             if(user_data_count > 2){
532                 /* therm */
533                 if(I2CAccess(0x20, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[2], I2C_WRITE) != 0){
534                     goto fail;
535                 }
536             }
537             break;
538         }
539         case 'e':{ /* alter external limits: low, high, therm, offset */
540             if(argc < 3){
541                 goto usage;
542             }
543
544             /* low */
545             if(I2CAccess(0xE, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[0], I2C_WRITE) != 0){
546                 goto fail;
547             }
548
549             if(user_data_count > 1){
550                 /* high */
551                 if(I2CAccess(0xD, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[1], I2C_WRITE) != 0){
552                     goto fail;
553                 }
554             }
555
556             if(user_data_count > 2){
557                 /* therm */
558                 if(I2CAccess(0x19, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[2], I2C_WRITE) != 0){
559                     goto fail;
560                 }
561             }
562
563             if(user_data_count > 3){
564                 /* offset */
565                 if(I2CAccess(0x11, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[3], I2C_WRITE) != 0){
566                     goto fail;
567                 }
568             }
569             break;
570         }
571         case 'c':{ /* alter config settings: config, conv, cons alert, therm hyst */
572             if(argc < 3){
573                 goto usage;
574             }
575
576             /* config */
577             if(I2CAccess(0x9, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[0], I2C_WRITE) != 0){
578                 goto fail;
579             }
580
581             if(user_data_count > 1){
582                 /* conversion */
583                 if(I2CAccess(0xA, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[1], I2C_WRITE) != 0){
584                     goto fail;
585                 }
586             }
587
588             if(user_data_count > 2){
589                 /* cons alert */
590                 if(I2CAccess(0x22, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[2], I2C_WRITE) != 0){
591                     goto fail;
592                 }
593             }
594
595             if(user_data_count > 3){
596                 /* therm hyst */
597                 if(I2CAccess(0x21, I2C_SENSOR_DEV, I2C_SENSOR_CHIP_SEL, &user_data[3], I2C_WRITE) != 0){
598                     goto fail;
599                 }
600             }
601             break;
602         }
603         default:{
604             goto usage;
605         }
606     }
607
608     goto done;
609  fail:
610     printf("Access to sensor failed\n");
611     ret_val = -1;
612     goto done;
613  usage:
614     printf ("Usage:\n%s\n", cmdtp->help);
615
616  done:
617      return ret_val;
618 }
619
620 U_BOOT_CMD(
621     temp,    6,    0,    do_temp_sensor,
622     "temp    - interact with the temperature sensor\n",
623     "temp [s]\n"
624     "        - Show status.\n"
625     "temp l LOW [HIGH] [THERM]\n"
626     "        - Set local limits.\n"
627     "temp e LOW [HIGH] [THERM] [OFFSET]\n"
628     "        - Set external limits.\n"
629     "temp c CONFIG [CONVERSION] [CONS. ALERT] [THERM HYST]\n"
630     "        - Set config options.\n"
631     "\n"
632     "All values can be decimal or hex (hex preceded with 0x).\n"
633     "Only whole numbers are supported for external limits.\n"
634 );
635
636 #if 0
637 U_BOOT_CMD(
638     loadace,    2,    0,     do_loadace,
639     "loadace - load fpga configuration from System ACE compact flash\n",
640     "N\n"
641     "    - Load configuration N (0-7) from System ACE compact flash\n"
642     "loadace\n"
643     "    - loads default configuration\n"
644 );
645 #endif
646
647 U_BOOT_CMD(
648     swconfig,    2,    0,     do_swconfigbyte,
649     "swconfig- display or modify the software configuration byte\n",
650     "N [ADDRESS]\n"
651     "    - set software configuration byte to N, optionally use ADDRESS as\n"
652     "      location of buffer for flash copy\n"
653     "swconfig\n"
654     "    - display software configuration byte\n"
655 );
656
657 U_BOOT_CMD(
658     pause,    2,    0,     do_pause,
659     "pause   - sleep processor until any key is pressed with poll time of N seconds\n",
660     "N\n"
661     "    - sleep processor until any key is pressed with poll time of N seconds\n"
662     "pause\n"
663     "    - sleep processor until any key is pressed with poll time of 1 second\n"
664 );
665
666 U_BOOT_CMD(
667     swrecon,    1,    0,     do_swreconfig,
668     "swrecon - trigger a board reconfigure to the software selected configuration\n",
669     "\n"
670     "    - trigger a board reconfigure to the software selected configuration\n"
671 );
672