uboot/arch/powerpc/cpu/mpc8xx/cpu.c
<<
>>
Prefs
   1/*
   2 * (C) Copyright 2000-2002
   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/*
  25 * m8xx.c
  26 *
  27 * CPU specific code
  28 *
  29 * written or collected and sometimes rewritten by
  30 * Magnus Damm <damm@bitsmart.com>
  31 *
  32 * minor modifications by
  33 * Wolfgang Denk <wd@denx.de>
  34 */
  35
  36#include <common.h>
  37#include <watchdog.h>
  38#include <command.h>
  39#include <mpc8xx.h>
  40#include <commproc.h>
  41#include <netdev.h>
  42#include <asm/cache.h>
  43#include <linux/compiler.h>
  44#include <asm/io.h>
  45
  46#if defined(CONFIG_OF_LIBFDT)
  47#include <libfdt.h>
  48#include <fdt_support.h>
  49#endif
  50
  51DECLARE_GLOBAL_DATA_PTR;
  52
  53static char *cpu_warning = "\n         " \
  54        "*** Warning: CPU Core has Silicon Bugs -- Check the Errata ***";
  55
  56#if ((defined(CONFIG_MPC86x) || defined(CONFIG_MPC855)) && \
  57     !defined(CONFIG_MPC862))
  58
  59static int check_CPU (long clock, uint pvr, uint immr)
  60{
  61        char *id_str =
  62# if defined(CONFIG_MPC855)
  63        "PC855";
  64# elif defined(CONFIG_MPC860P)
  65        "PC860P";
  66# else
  67        NULL;
  68# endif
  69        volatile immap_t *immap = (immap_t *) (immr & 0xFFFF0000);
  70        uint k, m;
  71        char buf[32];
  72        char pre = 'X';
  73        char *mid = "xx";
  74        char *suf;
  75
  76        /* the highest 16 bits should be 0x0050 for a 860 */
  77
  78        if ((pvr >> 16) != 0x0050)
  79                return -1;
  80
  81        k = (immr << 16) | *((ushort *) & immap->im_cpm.cp_dparam[0xB0]);
  82        m = 0;
  83        suf = "";
  84
  85        /*
  86         * Some boards use sockets so different CPUs can be used.
  87         * We have to check chip version in run time.
  88         */
  89        switch (k) {
  90        case 0x00020001: pre = 'P'; break;
  91        case 0x00030001: break;
  92        case 0x00120003: suf = "A"; break;
  93        case 0x00130003: suf = "A3"; break;
  94
  95        case 0x00200004: suf = "B"; break;
  96
  97        case 0x00300004: suf = "C"; break;
  98        case 0x00310004: suf = "C1"; m = 1; break;
  99
 100        case 0x00200064: mid = "SR"; suf = "B"; break;
 101        case 0x00300065: mid = "SR"; suf = "C"; break;
 102        case 0x00310065: mid = "SR"; suf = "C1"; m = 1; break;
 103        case 0x05010000: suf = "D3"; m = 1; break;
 104        case 0x05020000: suf = "D4"; m = 1; break;
 105                /* this value is not documented anywhere */
 106        case 0x40000000: pre = 'P'; suf = "D"; m = 1; break;
 107                /* MPC866P/MPC866T/MPC859T/MPC859DSL/MPC852T */
 108        case 0x08010004:                /* Rev. A.0 */
 109                suf = "A";
 110                /* fall through */
 111        case 0x08000003:                /* Rev. 0.3 */
 112                pre = 'M'; m = 1;
 113                if (id_str == NULL)
 114                        id_str =
 115# if defined(CONFIG_MPC852T)
 116                "PC852T";
 117# elif defined(CONFIG_MPC859T)
 118                "PC859T";
 119# elif defined(CONFIG_MPC859DSL)
 120                "PC859DSL";
 121# elif defined(CONFIG_MPC866T)
 122                "PC866T";
 123# else
 124                "PC866x"; /* Unknown chip from MPC866 family */
 125# endif
 126                break;
 127        case 0x09000000: pre = 'M'; mid = suf = ""; m = 1;
 128                if (id_str == NULL)
 129                        id_str = "PC885"; /* 870/875/880/885 */
 130                break;
 131
 132        default: suf = NULL; break;
 133        }
 134
 135        if (id_str == NULL)
 136                id_str = "PC86x";       /* Unknown 86x chip */
 137        if (suf)
 138                printf ("%c%s%sZPnn%s", pre, id_str, mid, suf);
 139        else
 140                printf ("unknown M%s (0x%08x)", id_str, k);
 141
 142
 143#if defined(CONFIG_SYS_8xx_CPUCLK_MIN) && defined(CONFIG_SYS_8xx_CPUCLK_MAX)
 144        printf (" at %s MHz [%d.%d...%d.%d MHz]\n       ",
 145                strmhz (buf, clock),
 146                CONFIG_SYS_8xx_CPUCLK_MIN / 1000000,
 147                ((CONFIG_SYS_8xx_CPUCLK_MIN % 1000000) + 50000) / 100000,
 148                CONFIG_SYS_8xx_CPUCLK_MAX / 1000000,
 149                ((CONFIG_SYS_8xx_CPUCLK_MAX % 1000000) + 50000) / 100000
 150        );
 151#else
 152        printf (" at %s MHz: ", strmhz (buf, clock));
 153#endif
 154        printf ("%u kB I-Cache %u kB D-Cache",
 155                checkicache () >> 10,
 156                checkdcache () >> 10
 157        );
 158
 159        /* do we have a FEC (860T/P or 852/859/866/885)? */
 160
 161        immap->im_cpm.cp_fec.fec_addr_low = 0x12345678;
 162        if (immap->im_cpm.cp_fec.fec_addr_low == 0x12345678) {
 163                printf (" FEC present");
 164        }
 165
 166        if (!m) {
 167                puts (cpu_warning);
 168        }
 169
 170        putc ('\n');
 171
 172#ifdef DEBUG
 173        if(clock != measure_gclk()) {
 174            printf ("clock %ldHz != %dHz\n", clock, measure_gclk());
 175        }
 176#endif
 177
 178        return 0;
 179}
 180
 181#elif defined(CONFIG_MPC862)
 182
 183static int check_CPU (long clock, uint pvr, uint immr)
 184{
 185        volatile immap_t *immap = (immap_t *) (immr & 0xFFFF0000);
 186        uint k, m;
 187        char buf[32];
 188        char pre = 'X';
 189        __maybe_unused char *mid = "xx";
 190        char *suf;
 191
 192        /* the highest 16 bits should be 0x0050 for a 8xx */
 193
 194        if ((pvr >> 16) != 0x0050)
 195                return -1;
 196
 197        k = (immr << 16) | *((ushort *) & immap->im_cpm.cp_dparam[0xB0]);
 198        m = 0;
 199
 200        switch (k) {
 201
 202                /* this value is not documented anywhere */
 203        case 0x06000000: mid = "P"; suf = "0"; break;
 204        case 0x06010001: mid = "P"; suf = "A"; m = 1; break;
 205        case 0x07000003: mid = "P"; suf = "B"; m = 1; break;
 206        default: suf = NULL; break;
 207        }
 208
 209#ifndef CONFIG_MPC857
 210        if (suf)
 211                printf ("%cPC862%sZPnn%s", pre, mid, suf);
 212        else
 213                printf ("unknown MPC862 (0x%08x)", k);
 214#else
 215        if (suf)
 216                printf ("%cPC857TZPnn%s", pre, suf); /* only 857T tested right now! */
 217        else
 218                printf ("unknown MPC857 (0x%08x)", k);
 219#endif
 220
 221        printf (" at %s MHz:", strmhz (buf, clock));
 222
 223        printf (" %u kB I-Cache", checkicache () >> 10);
 224        printf (" %u kB D-Cache", checkdcache () >> 10);
 225
 226        /* lets check and see if we're running on a 862T (or P?) */
 227
 228        immap->im_cpm.cp_fec.fec_addr_low = 0x12345678;
 229        if (immap->im_cpm.cp_fec.fec_addr_low == 0x12345678) {
 230                printf (" FEC present");
 231        }
 232
 233        if (!m) {
 234                puts (cpu_warning);
 235        }
 236
 237        putc ('\n');
 238
 239        return 0;
 240}
 241
 242#elif defined(CONFIG_MPC823)
 243
 244static int check_CPU (long clock, uint pvr, uint immr)
 245{
 246        volatile immap_t *immap = (immap_t *) (immr & 0xFFFF0000);
 247        uint k, m;
 248        char buf[32];
 249        char *suf;
 250
 251        /* the highest 16 bits should be 0x0050 for a 8xx */
 252
 253        if ((pvr >> 16) != 0x0050)
 254                return -1;
 255
 256        k = (immr << 16) | in_be16((ushort *)&immap->im_cpm.cp_dparam[0xB0]);
 257        m = 0;
 258
 259        switch (k) {
 260                /* MPC823 */
 261        case 0x20000000: suf = "0"; break;
 262        case 0x20010000: suf = "0.1"; break;
 263        case 0x20020000: suf = "Z2/3"; break;
 264        case 0x20020001: suf = "Z3"; break;
 265        case 0x21000000: suf = "A"; break;
 266        case 0x21010000: suf = "B"; m = 1; break;
 267        case 0x21010001: suf = "B2"; m = 1; break;
 268                /* MPC823E */
 269        case 0x24010000: suf = NULL;
 270                        puts ("PPC823EZTnnB2");
 271                        m = 1;
 272                        break;
 273        default:
 274                        suf = NULL;
 275                        printf ("unknown MPC823 (0x%08x)", k);
 276                        break;
 277        }
 278        if (suf)
 279                printf ("PPC823ZTnn%s", suf);
 280
 281        printf (" at %s MHz:", strmhz (buf, clock));
 282
 283        printf (" %u kB I-Cache", checkicache () >> 10);
 284        printf (" %u kB D-Cache", checkdcache () >> 10);
 285
 286        /* lets check and see if we're running on a 860T (or P?) */
 287
 288        immap->im_cpm.cp_fec.fec_addr_low = 0x12345678;
 289        if (immap->im_cpm.cp_fec.fec_addr_low == 0x12345678) {
 290                puts (" FEC present");
 291        }
 292
 293        if (!m) {
 294                puts (cpu_warning);
 295        }
 296
 297        putc ('\n');
 298
 299        return 0;
 300}
 301
 302#elif defined(CONFIG_MPC850)
 303
 304static int check_CPU (long clock, uint pvr, uint immr)
 305{
 306        volatile immap_t *immap = (immap_t *) (immr & 0xFFFF0000);
 307        uint k, m;
 308        char buf[32];
 309
 310        /* the highest 16 bits should be 0x0050 for a 8xx */
 311
 312        if ((pvr >> 16) != 0x0050)
 313                return -1;
 314
 315        k = (immr << 16) | *((ushort *) & immap->im_cpm.cp_dparam[0xB0]);
 316        m = 0;
 317
 318        switch (k) {
 319        case 0x20020001:
 320                printf ("XPC850xxZT");
 321                break;
 322        case 0x21000065:
 323                printf ("XPC850xxZTA");
 324                break;
 325        case 0x21010067:
 326                printf ("XPC850xxZTB");
 327                m = 1;
 328                break;
 329        case 0x21020068:
 330                printf ("XPC850xxZTC");
 331                m = 1;
 332                break;
 333        default:
 334                printf ("unknown MPC850 (0x%08x)", k);
 335        }
 336        printf (" at %s MHz:", strmhz (buf, clock));
 337
 338        printf (" %u kB I-Cache", checkicache () >> 10);
 339        printf (" %u kB D-Cache", checkdcache () >> 10);
 340
 341        /* lets check and see if we're running on a 850T (or P?) */
 342
 343        immap->im_cpm.cp_fec.fec_addr_low = 0x12345678;
 344        if (immap->im_cpm.cp_fec.fec_addr_low == 0x12345678) {
 345                printf (" FEC present");
 346        }
 347
 348        if (!m) {
 349                puts (cpu_warning);
 350        }
 351
 352        putc ('\n');
 353
 354        return 0;
 355}
 356#else
 357#error CPU undefined
 358#endif
 359/* ------------------------------------------------------------------------- */
 360
 361int checkcpu (void)
 362{
 363        ulong clock = gd->cpu_clk;
 364        uint immr = get_immr (0);       /* Return full IMMR contents */
 365        uint pvr = get_pvr ();
 366
 367        puts ("CPU:   ");
 368
 369        /* 850 has PARTNUM 20 */
 370        /* 801 has PARTNUM 10 */
 371        return check_CPU (clock, pvr, immr);
 372}
 373
 374/* ------------------------------------------------------------------------- */
 375/* L1 i-cache                                                                */
 376/* the standard 860 has 128 sets of 16 bytes in 2 ways (= 4 kB)              */
 377/* the 860 P (plus) has 256 sets of 16 bytes in 4 ways (= 16 kB)             */
 378
 379int checkicache (void)
 380{
 381        volatile immap_t *immap = (immap_t *) CONFIG_SYS_IMMR;
 382        volatile memctl8xx_t *memctl = &immap->im_memctl;
 383        u32 cacheon = rd_ic_cst () & IDC_ENABLED;
 384
 385#ifdef CONFIG_IP86x
 386        u32 k = memctl->memc_br1 & ~0x00007fff; /* probe in flash memoryarea */
 387#else
 388        u32 k = memctl->memc_br0 & ~0x00007fff; /* probe in flash memoryarea */
 389#endif
 390        u32 m;
 391        u32 lines = -1;
 392
 393        wr_ic_cst (IDC_UNALL);
 394        wr_ic_cst (IDC_INVALL);
 395        wr_ic_cst (IDC_DISABLE);
 396        __asm__ volatile ("isync");
 397
 398        while (!((m = rd_ic_cst ()) & IDC_CERR2)) {
 399                wr_ic_adr (k);
 400                wr_ic_cst (IDC_LDLCK);
 401                __asm__ volatile ("isync");
 402
 403                lines++;
 404                k += 0x10;                              /* the number of bytes in a cacheline */
 405        }
 406
 407        wr_ic_cst (IDC_UNALL);
 408        wr_ic_cst (IDC_INVALL);
 409
 410        if (cacheon)
 411                wr_ic_cst (IDC_ENABLE);
 412        else
 413                wr_ic_cst (IDC_DISABLE);
 414
 415        __asm__ volatile ("isync");
 416
 417        return lines << 4;
 418};
 419
 420/* ------------------------------------------------------------------------- */
 421/* L1 d-cache                                                                */
 422/* the standard 860 has 128 sets of 16 bytes in 2 ways (= 4 kB)              */
 423/* the 860 P (plus) has 256 sets of 16 bytes in 2 ways (= 8 kB)              */
 424/* call with cache disabled                                                  */
 425
 426int checkdcache (void)
 427{
 428        volatile immap_t *immap = (immap_t *) CONFIG_SYS_IMMR;
 429        volatile memctl8xx_t *memctl = &immap->im_memctl;
 430        u32 cacheon = rd_dc_cst () & IDC_ENABLED;
 431
 432#ifdef CONFIG_IP86x
 433        u32 k = memctl->memc_br1 & ~0x00007fff; /* probe in flash memoryarea */
 434#else
 435        u32 k = memctl->memc_br0 & ~0x00007fff; /* probe in flash memoryarea */
 436#endif
 437        u32 m;
 438        u32 lines = -1;
 439
 440        wr_dc_cst (IDC_UNALL);
 441        wr_dc_cst (IDC_INVALL);
 442        wr_dc_cst (IDC_DISABLE);
 443
 444        while (!((m = rd_dc_cst ()) & IDC_CERR2)) {
 445                wr_dc_adr (k);
 446                wr_dc_cst (IDC_LDLCK);
 447                lines++;
 448                k += 0x10;      /* the number of bytes in a cacheline */
 449        }
 450
 451        wr_dc_cst (IDC_UNALL);
 452        wr_dc_cst (IDC_INVALL);
 453
 454        if (cacheon)
 455                wr_dc_cst (IDC_ENABLE);
 456        else
 457                wr_dc_cst (IDC_DISABLE);
 458
 459        return lines << 4;
 460};
 461
 462/* ------------------------------------------------------------------------- */
 463
 464void upmconfig (uint upm, uint * table, uint size)
 465{
 466        uint i;
 467        uint addr = 0;
 468        volatile immap_t *immap = (immap_t *) CONFIG_SYS_IMMR;
 469        volatile memctl8xx_t *memctl = &immap->im_memctl;
 470
 471        for (i = 0; i < size; i++) {
 472                memctl->memc_mdr = table[i];    /* (16-15) */
 473                memctl->memc_mcr = addr | upm;  /* (16-16) */
 474                addr++;
 475        }
 476}
 477
 478/* ------------------------------------------------------------------------- */
 479
 480#ifndef CONFIG_LWMON
 481
 482int do_reset (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 483{
 484        ulong msr, addr;
 485
 486        volatile immap_t *immap = (immap_t *) CONFIG_SYS_IMMR;
 487
 488        immap->im_clkrst.car_plprcr |= PLPRCR_CSR;      /* Checkstop Reset enable */
 489
 490        /* Interrupts and MMU off */
 491        __asm__ volatile ("mtspr    81, 0");
 492        __asm__ volatile ("mfmsr    %0":"=r" (msr));
 493
 494        msr &= ~0x1030;
 495        __asm__ volatile ("mtmsr    %0"::"r" (msr));
 496
 497        /*
 498         * Trying to execute the next instruction at a non-existing address
 499         * should cause a machine check, resulting in reset
 500         */
 501#ifdef CONFIG_SYS_RESET_ADDRESS
 502        addr = CONFIG_SYS_RESET_ADDRESS;
 503#else
 504        /*
 505         * note: when CONFIG_SYS_MONITOR_BASE points to a RAM address, CONFIG_SYS_MONITOR_BASE
 506         * - sizeof (ulong) is usually a valid address. Better pick an address
 507         * known to be invalid on your system and assign it to CONFIG_SYS_RESET_ADDRESS.
 508         * "(ulong)-1" used to be a good choice for many systems...
 509         */
 510        addr = CONFIG_SYS_MONITOR_BASE - sizeof (ulong);
 511#endif
 512        ((void (*)(void)) addr) ();
 513        return 1;
 514}
 515
 516#else   /* CONFIG_LWMON */
 517
 518/*
 519 * On the LWMON board, the MCLR reset input of the PIC's on the board
 520 * uses a 47K/1n RC combination which has a 47us time  constant.  The
 521 * low  signal on the HRESET pin of the CPU is only 512 clocks = 8 us
 522 * and thus too short to reset the external hardware. So we  use  the
 523 * watchdog to reset the board.
 524 */
 525int do_reset (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 526{
 527        /* prevent triggering the watchdog */
 528        disable_interrupts ();
 529
 530        /* make sure the watchdog is running */
 531        reset_8xx_watchdog ((immap_t *) CONFIG_SYS_IMMR);
 532
 533        /* wait for watchdog reset */
 534        while (1) {};
 535
 536        /* NOTREACHED */
 537        return 1;
 538}
 539
 540#endif  /* CONFIG_LWMON */
 541
 542/* ------------------------------------------------------------------------- */
 543
 544/*
 545 * Get timebase clock frequency (like cpu_clk in Hz)
 546 *
 547 * See sections 14.2 and 14.6 of the User's Manual
 548 */
 549unsigned long get_tbclk (void)
 550{
 551        uint immr = get_immr (0);       /* Return full IMMR contents */
 552        volatile immap_t *immap = (volatile immap_t *)(immr & 0xFFFF0000);
 553        ulong oscclk, factor, pll;
 554
 555        if (immap->im_clkrst.car_sccr & SCCR_TBS) {
 556                return (gd->cpu_clk / 16);
 557        }
 558
 559        pll = immap->im_clkrst.car_plprcr;
 560
 561#define PLPRCR_val(a) ((pll & PLPRCR_ ## a ## _MSK) >> PLPRCR_ ## a ## _SHIFT)
 562
 563        /*
 564         * For newer PQ1 chips (MPC866/87x/88x families), PLL multiplication
 565         * factor is calculated as follows:
 566         *
 567         *                   MFN
 568         *           MFI + -------
 569         *                 MFD + 1
 570         * factor =  -----------------
 571         *           (PDF + 1) * 2^S
 572         *
 573         * For older chips, it's just MF field of PLPRCR plus one.
 574         */
 575        if ((immr & 0x0FFF) >= MPC8xx_NEW_CLK) { /* MPC866/87x/88x series */
 576                factor = (PLPRCR_val(MFI) + PLPRCR_val(MFN)/(PLPRCR_val(MFD)+1))/
 577                        (PLPRCR_val(PDF)+1) / (1<<PLPRCR_val(S));
 578        } else {
 579                factor = PLPRCR_val(MF)+1;
 580        }
 581
 582        oscclk = gd->cpu_clk / factor;
 583
 584        if ((immap->im_clkrst.car_sccr & SCCR_RTSEL) == 0 || factor > 2) {
 585                return (oscclk / 4);
 586        }
 587        return (oscclk / 16);
 588}
 589
 590/* ------------------------------------------------------------------------- */
 591
 592#if defined(CONFIG_WATCHDOG)
 593void watchdog_reset (void)
 594{
 595        int re_enable = disable_interrupts ();
 596
 597        reset_8xx_watchdog ((immap_t *) CONFIG_SYS_IMMR);
 598        if (re_enable)
 599                enable_interrupts ();
 600}
 601#endif /* CONFIG_WATCHDOG */
 602
 603#if defined(CONFIG_WATCHDOG) || defined(CONFIG_LWMON)
 604
 605void reset_8xx_watchdog (volatile immap_t * immr)
 606{
 607# if defined(CONFIG_LWMON)
 608        /*
 609         * The LWMON board uses a MAX6301 Watchdog
 610         * with the trigger pin connected to port PA.7
 611         *
 612         * (The old board version used a MAX706TESA Watchdog, which
 613         * had to be handled exactly the same.)
 614         */
 615# define WATCHDOG_BIT   0x0100
 616        immr->im_ioport.iop_papar &= ~(WATCHDOG_BIT);   /* GPIO     */
 617        immr->im_ioport.iop_padir |= WATCHDOG_BIT;      /* Output   */
 618        immr->im_ioport.iop_paodr &= ~(WATCHDOG_BIT);   /* active output */
 619
 620        immr->im_ioport.iop_padat ^= WATCHDOG_BIT;      /* Toggle WDI   */
 621# elif defined(CONFIG_KUP4K) || defined(CONFIG_KUP4X)
 622        /*
 623         * The KUP4 boards uses a TPS3705 Watchdog
 624         * with the trigger pin connected to port PA.5
 625         */
 626# define WATCHDOG_BIT   0x0400
 627        immr->im_ioport.iop_papar &= ~(WATCHDOG_BIT);   /* GPIO     */
 628        immr->im_ioport.iop_padir |= WATCHDOG_BIT;      /* Output   */
 629        immr->im_ioport.iop_paodr &= ~(WATCHDOG_BIT);   /* active output */
 630
 631        immr->im_ioport.iop_padat ^= WATCHDOG_BIT;      /* Toggle WDI   */
 632# else
 633        /*
 634         * All other boards use the MPC8xx Internal Watchdog
 635         */
 636        immr->im_siu_conf.sc_swsr = 0x556c;     /* write magic1 */
 637        immr->im_siu_conf.sc_swsr = 0xaa39;     /* write magic2 */
 638# endif /* CONFIG_LWMON */
 639}
 640#endif /* CONFIG_WATCHDOG */
 641
 642/*
 643 * Initializes on-chip ethernet controllers.
 644 * to override, implement board_eth_init()
 645 */
 646int cpu_eth_init(bd_t *bis)
 647{
 648#if defined(SCC_ENET) && defined(CONFIG_CMD_NET)
 649        scc_initialize(bis);
 650#endif
 651#if defined(FEC_ENET)
 652        fec_initialize(bis);
 653#endif
 654        return 0;
 655}
 656