linux/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
<<
>>
Prefs
   1/*******************************************************************************
   2  This is the driver for the GMAC on-chip Ethernet controller for ST SoCs.
   3  DWC Ether MAC 10/100/1000 Universal version 3.41a  has been used for
   4  developing this code.
   5
   6  This only implements the mac core functions for this chip.
   7
   8  Copyright (C) 2007-2009  STMicroelectronics Ltd
   9
  10  This program is free software; you can redistribute it and/or modify it
  11  under the terms and conditions of the GNU General Public License,
  12  version 2, as published by the Free Software Foundation.
  13
  14  This program is distributed in the hope it will be useful, but WITHOUT
  15  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  16  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  17  more details.
  18
  19  The full GNU General Public License is included in this distribution in
  20  the file called "COPYING".
  21
  22  Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
  23*******************************************************************************/
  24
  25#include <linux/crc32.h>
  26#include <linux/slab.h>
  27#include <linux/ethtool.h>
  28#include <net/dsa.h>
  29#include <asm/io.h>
  30#include "stmmac.h"
  31#include "stmmac_pcs.h"
  32#include "dwmac1000.h"
  33
  34static void dwmac1000_core_init(struct mac_device_info *hw,
  35                                struct net_device *dev)
  36{
  37        void __iomem *ioaddr = hw->pcsr;
  38        u32 value = readl(ioaddr + GMAC_CONTROL);
  39        int mtu = dev->mtu;
  40
  41        /* Configure GMAC core */
  42        value |= GMAC_CORE_INIT;
  43
  44        /* Clear ACS bit because Ethernet switch tagging formats such as
  45         * Broadcom tags can look like invalid LLC/SNAP packets and cause the
  46         * hardware to truncate packets on reception.
  47         */
  48        if (netdev_uses_dsa(dev))
  49                value &= ~GMAC_CONTROL_ACS;
  50
  51        if (mtu > 1500)
  52                value |= GMAC_CONTROL_2K;
  53        if (mtu > 2000)
  54                value |= GMAC_CONTROL_JE;
  55
  56        if (hw->ps) {
  57                value |= GMAC_CONTROL_TE;
  58
  59                value &= ~hw->link.speed_mask;
  60                switch (hw->ps) {
  61                case SPEED_1000:
  62                        value |= hw->link.speed1000;
  63                        break;
  64                case SPEED_100:
  65                        value |= hw->link.speed100;
  66                        break;
  67                case SPEED_10:
  68                        value |= hw->link.speed10;
  69                        break;
  70                }
  71        }
  72
  73        writel(value, ioaddr + GMAC_CONTROL);
  74
  75        /* Mask GMAC interrupts */
  76        value = GMAC_INT_DEFAULT_MASK;
  77
  78        if (hw->pcs)
  79                value &= ~GMAC_INT_DISABLE_PCS;
  80
  81        writel(value, ioaddr + GMAC_INT_MASK);
  82
  83#ifdef STMMAC_VLAN_TAG_USED
  84        /* Tag detection without filtering */
  85        writel(0x0, ioaddr + GMAC_VLAN_TAG);
  86#endif
  87}
  88
  89static int dwmac1000_rx_ipc_enable(struct mac_device_info *hw)
  90{
  91        void __iomem *ioaddr = hw->pcsr;
  92        u32 value = readl(ioaddr + GMAC_CONTROL);
  93
  94        if (hw->rx_csum)
  95                value |= GMAC_CONTROL_IPC;
  96        else
  97                value &= ~GMAC_CONTROL_IPC;
  98
  99        writel(value, ioaddr + GMAC_CONTROL);
 100
 101        value = readl(ioaddr + GMAC_CONTROL);
 102
 103        return !!(value & GMAC_CONTROL_IPC);
 104}
 105
 106static void dwmac1000_dump_regs(struct mac_device_info *hw, u32 *reg_space)
 107{
 108        void __iomem *ioaddr = hw->pcsr;
 109        int i;
 110
 111        for (i = 0; i < 55; i++)
 112                reg_space[i] = readl(ioaddr + i * 4);
 113}
 114
 115static void dwmac1000_set_umac_addr(struct mac_device_info *hw,
 116                                    unsigned char *addr,
 117                                    unsigned int reg_n)
 118{
 119        void __iomem *ioaddr = hw->pcsr;
 120        stmmac_set_mac_addr(ioaddr, addr, GMAC_ADDR_HIGH(reg_n),
 121                            GMAC_ADDR_LOW(reg_n));
 122}
 123
 124static void dwmac1000_get_umac_addr(struct mac_device_info *hw,
 125                                    unsigned char *addr,
 126                                    unsigned int reg_n)
 127{
 128        void __iomem *ioaddr = hw->pcsr;
 129        stmmac_get_mac_addr(ioaddr, addr, GMAC_ADDR_HIGH(reg_n),
 130                            GMAC_ADDR_LOW(reg_n));
 131}
 132
 133static void dwmac1000_set_mchash(void __iomem *ioaddr, u32 *mcfilterbits,
 134                                 int mcbitslog2)
 135{
 136        int numhashregs, regs;
 137
 138        switch (mcbitslog2) {
 139        case 6:
 140                writel(mcfilterbits[0], ioaddr + GMAC_HASH_LOW);
 141                writel(mcfilterbits[1], ioaddr + GMAC_HASH_HIGH);
 142                return;
 143                break;
 144        case 7:
 145                numhashregs = 4;
 146                break;
 147        case 8:
 148                numhashregs = 8;
 149                break;
 150        default:
 151                pr_debug("STMMAC: err in setting multicast filter\n");
 152                return;
 153                break;
 154        }
 155        for (regs = 0; regs < numhashregs; regs++)
 156                writel(mcfilterbits[regs],
 157                       ioaddr + GMAC_EXTHASH_BASE + regs * 4);
 158}
 159
 160static void dwmac1000_set_filter(struct mac_device_info *hw,
 161                                 struct net_device *dev)
 162{
 163        void __iomem *ioaddr = (void __iomem *)dev->base_addr;
 164        unsigned int value = 0;
 165        unsigned int perfect_addr_number = hw->unicast_filter_entries;
 166        u32 mc_filter[8];
 167        int mcbitslog2 = hw->mcast_bits_log2;
 168
 169        pr_debug("%s: # mcasts %d, # unicast %d\n", __func__,
 170                 netdev_mc_count(dev), netdev_uc_count(dev));
 171
 172        memset(mc_filter, 0, sizeof(mc_filter));
 173
 174        if (dev->flags & IFF_PROMISC) {
 175                value = GMAC_FRAME_FILTER_PR;
 176        } else if (dev->flags & IFF_ALLMULTI) {
 177                value = GMAC_FRAME_FILTER_PM;   /* pass all multi */
 178        } else if (!netdev_mc_empty(dev)) {
 179                struct netdev_hw_addr *ha;
 180
 181                /* Hash filter for multicast */
 182                value = GMAC_FRAME_FILTER_HMC;
 183
 184                netdev_for_each_mc_addr(ha, dev) {
 185                        /* The upper n bits of the calculated CRC are used to
 186                         * index the contents of the hash table. The number of
 187                         * bits used depends on the hardware configuration
 188                         * selected at core configuration time.
 189                         */
 190                        int bit_nr = bitrev32(~crc32_le(~0, ha->addr,
 191                                              ETH_ALEN)) >>
 192                                              (32 - mcbitslog2);
 193                        /* The most significant bit determines the register to
 194                         * use (H/L) while the other 5 bits determine the bit
 195                         * within the register.
 196                         */
 197                        mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
 198                }
 199        }
 200
 201        dwmac1000_set_mchash(ioaddr, mc_filter, mcbitslog2);
 202
 203        /* Handle multiple unicast addresses (perfect filtering) */
 204        if (netdev_uc_count(dev) > perfect_addr_number)
 205                /* Switch to promiscuous mode if more than unicast
 206                 * addresses are requested than supported by hardware.
 207                 */
 208                value |= GMAC_FRAME_FILTER_PR;
 209        else {
 210                int reg = 1;
 211                struct netdev_hw_addr *ha;
 212
 213                netdev_for_each_uc_addr(ha, dev) {
 214                        stmmac_set_mac_addr(ioaddr, ha->addr,
 215                                            GMAC_ADDR_HIGH(reg),
 216                                            GMAC_ADDR_LOW(reg));
 217                        reg++;
 218                }
 219        }
 220
 221#ifdef FRAME_FILTER_DEBUG
 222        /* Enable Receive all mode (to debug filtering_fail errors) */
 223        value |= GMAC_FRAME_FILTER_RA;
 224#endif
 225        writel(value, ioaddr + GMAC_FRAME_FILTER);
 226}
 227
 228
 229static void dwmac1000_flow_ctrl(struct mac_device_info *hw, unsigned int duplex,
 230                                unsigned int fc, unsigned int pause_time,
 231                                u32 tx_cnt)
 232{
 233        void __iomem *ioaddr = hw->pcsr;
 234        /* Set flow such that DZPQ in Mac Register 6 is 0,
 235         * and unicast pause detect is enabled.
 236         */
 237        unsigned int flow = GMAC_FLOW_CTRL_UP;
 238
 239        pr_debug("GMAC Flow-Control:\n");
 240        if (fc & FLOW_RX) {
 241                pr_debug("\tReceive Flow-Control ON\n");
 242                flow |= GMAC_FLOW_CTRL_RFE;
 243        }
 244        if (fc & FLOW_TX) {
 245                pr_debug("\tTransmit Flow-Control ON\n");
 246                flow |= GMAC_FLOW_CTRL_TFE;
 247        }
 248
 249        if (duplex) {
 250                pr_debug("\tduplex mode: PAUSE %d\n", pause_time);
 251                flow |= (pause_time << GMAC_FLOW_CTRL_PT_SHIFT);
 252        }
 253
 254        writel(flow, ioaddr + GMAC_FLOW_CTRL);
 255}
 256
 257static void dwmac1000_pmt(struct mac_device_info *hw, unsigned long mode)
 258{
 259        void __iomem *ioaddr = hw->pcsr;
 260        unsigned int pmt = 0;
 261
 262        if (mode & WAKE_MAGIC) {
 263                pr_debug("GMAC: WOL Magic frame\n");
 264                pmt |= power_down | magic_pkt_en;
 265        }
 266        if (mode & WAKE_UCAST) {
 267                pr_debug("GMAC: WOL on global unicast\n");
 268                pmt |= power_down | global_unicast | wake_up_frame_en;
 269        }
 270
 271        writel(pmt, ioaddr + GMAC_PMT);
 272}
 273
 274/* RGMII or SMII interface */
 275static void dwmac1000_rgsmii(void __iomem *ioaddr, struct stmmac_extra_stats *x)
 276{
 277        u32 status;
 278
 279        status = readl(ioaddr + GMAC_RGSMIIIS);
 280        x->irq_rgmii_n++;
 281
 282        /* Check the link status */
 283        if (status & GMAC_RGSMIIIS_LNKSTS) {
 284                int speed_value;
 285
 286                x->pcs_link = 1;
 287
 288                speed_value = ((status & GMAC_RGSMIIIS_SPEED) >>
 289                               GMAC_RGSMIIIS_SPEED_SHIFT);
 290                if (speed_value == GMAC_RGSMIIIS_SPEED_125)
 291                        x->pcs_speed = SPEED_1000;
 292                else if (speed_value == GMAC_RGSMIIIS_SPEED_25)
 293                        x->pcs_speed = SPEED_100;
 294                else
 295                        x->pcs_speed = SPEED_10;
 296
 297                x->pcs_duplex = (status & GMAC_RGSMIIIS_LNKMOD_MASK);
 298
 299                pr_info("Link is Up - %d/%s\n", (int)x->pcs_speed,
 300                        x->pcs_duplex ? "Full" : "Half");
 301        } else {
 302                x->pcs_link = 0;
 303                pr_info("Link is Down\n");
 304        }
 305}
 306
 307static int dwmac1000_irq_status(struct mac_device_info *hw,
 308                                struct stmmac_extra_stats *x)
 309{
 310        void __iomem *ioaddr = hw->pcsr;
 311        u32 intr_status = readl(ioaddr + GMAC_INT_STATUS);
 312        u32 intr_mask = readl(ioaddr + GMAC_INT_MASK);
 313        int ret = 0;
 314
 315        /* Discard masked bits */
 316        intr_status &= ~intr_mask;
 317
 318        /* Not used events (e.g. MMC interrupts) are not handled. */
 319        if ((intr_status & GMAC_INT_STATUS_MMCTIS))
 320                x->mmc_tx_irq_n++;
 321        if (unlikely(intr_status & GMAC_INT_STATUS_MMCRIS))
 322                x->mmc_rx_irq_n++;
 323        if (unlikely(intr_status & GMAC_INT_STATUS_MMCCSUM))
 324                x->mmc_rx_csum_offload_irq_n++;
 325        if (unlikely(intr_status & GMAC_INT_DISABLE_PMT)) {
 326                /* clear the PMT bits 5 and 6 by reading the PMT status reg */
 327                readl(ioaddr + GMAC_PMT);
 328                x->irq_receive_pmt_irq_n++;
 329        }
 330
 331        /* MAC tx/rx EEE LPI entry/exit interrupts */
 332        if (intr_status & GMAC_INT_STATUS_LPIIS) {
 333                /* Clean LPI interrupt by reading the Reg 12 */
 334                ret = readl(ioaddr + LPI_CTRL_STATUS);
 335
 336                if (ret & LPI_CTRL_STATUS_TLPIEN)
 337                        x->irq_tx_path_in_lpi_mode_n++;
 338                if (ret & LPI_CTRL_STATUS_TLPIEX)
 339                        x->irq_tx_path_exit_lpi_mode_n++;
 340                if (ret & LPI_CTRL_STATUS_RLPIEN)
 341                        x->irq_rx_path_in_lpi_mode_n++;
 342                if (ret & LPI_CTRL_STATUS_RLPIEX)
 343                        x->irq_rx_path_exit_lpi_mode_n++;
 344        }
 345
 346        dwmac_pcs_isr(ioaddr, GMAC_PCS_BASE, intr_status, x);
 347
 348        if (intr_status & PCS_RGSMIIIS_IRQ)
 349                dwmac1000_rgsmii(ioaddr, x);
 350
 351        return ret;
 352}
 353
 354static void dwmac1000_set_eee_mode(struct mac_device_info *hw,
 355                                   bool en_tx_lpi_clockgating)
 356{
 357        void __iomem *ioaddr = hw->pcsr;
 358        u32 value;
 359
 360        /*TODO - en_tx_lpi_clockgating treatment */
 361
 362        /* Enable the link status receive on RGMII, SGMII ore SMII
 363         * receive path and instruct the transmit to enter in LPI
 364         * state.
 365         */
 366        value = readl(ioaddr + LPI_CTRL_STATUS);
 367        value |= LPI_CTRL_STATUS_LPIEN | LPI_CTRL_STATUS_LPITXA;
 368        writel(value, ioaddr + LPI_CTRL_STATUS);
 369}
 370
 371static void dwmac1000_reset_eee_mode(struct mac_device_info *hw)
 372{
 373        void __iomem *ioaddr = hw->pcsr;
 374        u32 value;
 375
 376        value = readl(ioaddr + LPI_CTRL_STATUS);
 377        value &= ~(LPI_CTRL_STATUS_LPIEN | LPI_CTRL_STATUS_LPITXA);
 378        writel(value, ioaddr + LPI_CTRL_STATUS);
 379}
 380
 381static void dwmac1000_set_eee_pls(struct mac_device_info *hw, int link)
 382{
 383        void __iomem *ioaddr = hw->pcsr;
 384        u32 value;
 385
 386        value = readl(ioaddr + LPI_CTRL_STATUS);
 387
 388        if (link)
 389                value |= LPI_CTRL_STATUS_PLS;
 390        else
 391                value &= ~LPI_CTRL_STATUS_PLS;
 392
 393        writel(value, ioaddr + LPI_CTRL_STATUS);
 394}
 395
 396static void dwmac1000_set_eee_timer(struct mac_device_info *hw, int ls, int tw)
 397{
 398        void __iomem *ioaddr = hw->pcsr;
 399        int value = ((tw & 0xffff)) | ((ls & 0x7ff) << 16);
 400
 401        /* Program the timers in the LPI timer control register:
 402         * LS: minimum time (ms) for which the link
 403         *  status from PHY should be ok before transmitting
 404         *  the LPI pattern.
 405         * TW: minimum time (us) for which the core waits
 406         *  after it has stopped transmitting the LPI pattern.
 407         */
 408        writel(value, ioaddr + LPI_TIMER_CTRL);
 409}
 410
 411static void dwmac1000_ctrl_ane(void __iomem *ioaddr, bool ane, bool srgmi_ral,
 412                               bool loopback)
 413{
 414        dwmac_ctrl_ane(ioaddr, GMAC_PCS_BASE, ane, srgmi_ral, loopback);
 415}
 416
 417static void dwmac1000_rane(void __iomem *ioaddr, bool restart)
 418{
 419        dwmac_rane(ioaddr, GMAC_PCS_BASE, restart);
 420}
 421
 422static void dwmac1000_get_adv_lp(void __iomem *ioaddr, struct rgmii_adv *adv)
 423{
 424        dwmac_get_adv_lp(ioaddr, GMAC_PCS_BASE, adv);
 425}
 426
 427static void dwmac1000_debug(void __iomem *ioaddr, struct stmmac_extra_stats *x,
 428                            u32 rx_queues, u32 tx_queues)
 429{
 430        u32 value = readl(ioaddr + GMAC_DEBUG);
 431
 432        if (value & GMAC_DEBUG_TXSTSFSTS)
 433                x->mtl_tx_status_fifo_full++;
 434        if (value & GMAC_DEBUG_TXFSTS)
 435                x->mtl_tx_fifo_not_empty++;
 436        if (value & GMAC_DEBUG_TWCSTS)
 437                x->mmtl_fifo_ctrl++;
 438        if (value & GMAC_DEBUG_TRCSTS_MASK) {
 439                u32 trcsts = (value & GMAC_DEBUG_TRCSTS_MASK)
 440                             >> GMAC_DEBUG_TRCSTS_SHIFT;
 441                if (trcsts == GMAC_DEBUG_TRCSTS_WRITE)
 442                        x->mtl_tx_fifo_read_ctrl_write++;
 443                else if (trcsts == GMAC_DEBUG_TRCSTS_TXW)
 444                        x->mtl_tx_fifo_read_ctrl_wait++;
 445                else if (trcsts == GMAC_DEBUG_TRCSTS_READ)
 446                        x->mtl_tx_fifo_read_ctrl_read++;
 447                else
 448                        x->mtl_tx_fifo_read_ctrl_idle++;
 449        }
 450        if (value & GMAC_DEBUG_TXPAUSED)
 451                x->mac_tx_in_pause++;
 452        if (value & GMAC_DEBUG_TFCSTS_MASK) {
 453                u32 tfcsts = (value & GMAC_DEBUG_TFCSTS_MASK)
 454                              >> GMAC_DEBUG_TFCSTS_SHIFT;
 455
 456                if (tfcsts == GMAC_DEBUG_TFCSTS_XFER)
 457                        x->mac_tx_frame_ctrl_xfer++;
 458                else if (tfcsts == GMAC_DEBUG_TFCSTS_GEN_PAUSE)
 459                        x->mac_tx_frame_ctrl_pause++;
 460                else if (tfcsts == GMAC_DEBUG_TFCSTS_WAIT)
 461                        x->mac_tx_frame_ctrl_wait++;
 462                else
 463                        x->mac_tx_frame_ctrl_idle++;
 464        }
 465        if (value & GMAC_DEBUG_TPESTS)
 466                x->mac_gmii_tx_proto_engine++;
 467        if (value & GMAC_DEBUG_RXFSTS_MASK) {
 468                u32 rxfsts = (value & GMAC_DEBUG_RXFSTS_MASK)
 469                             >> GMAC_DEBUG_RRCSTS_SHIFT;
 470
 471                if (rxfsts == GMAC_DEBUG_RXFSTS_FULL)
 472                        x->mtl_rx_fifo_fill_level_full++;
 473                else if (rxfsts == GMAC_DEBUG_RXFSTS_AT)
 474                        x->mtl_rx_fifo_fill_above_thresh++;
 475                else if (rxfsts == GMAC_DEBUG_RXFSTS_BT)
 476                        x->mtl_rx_fifo_fill_below_thresh++;
 477                else
 478                        x->mtl_rx_fifo_fill_level_empty++;
 479        }
 480        if (value & GMAC_DEBUG_RRCSTS_MASK) {
 481                u32 rrcsts = (value & GMAC_DEBUG_RRCSTS_MASK) >>
 482                             GMAC_DEBUG_RRCSTS_SHIFT;
 483
 484                if (rrcsts == GMAC_DEBUG_RRCSTS_FLUSH)
 485                        x->mtl_rx_fifo_read_ctrl_flush++;
 486                else if (rrcsts == GMAC_DEBUG_RRCSTS_RSTAT)
 487                        x->mtl_rx_fifo_read_ctrl_read_data++;
 488                else if (rrcsts == GMAC_DEBUG_RRCSTS_RDATA)
 489                        x->mtl_rx_fifo_read_ctrl_status++;
 490                else
 491                        x->mtl_rx_fifo_read_ctrl_idle++;
 492        }
 493        if (value & GMAC_DEBUG_RWCSTS)
 494                x->mtl_rx_fifo_ctrl_active++;
 495        if (value & GMAC_DEBUG_RFCFCSTS_MASK)
 496                x->mac_rx_frame_ctrl_fifo = (value & GMAC_DEBUG_RFCFCSTS_MASK)
 497                                            >> GMAC_DEBUG_RFCFCSTS_SHIFT;
 498        if (value & GMAC_DEBUG_RPESTS)
 499                x->mac_gmii_rx_proto_engine++;
 500}
 501
 502const struct stmmac_ops dwmac1000_ops = {
 503        .core_init = dwmac1000_core_init,
 504        .set_mac = stmmac_set_mac,
 505        .rx_ipc = dwmac1000_rx_ipc_enable,
 506        .dump_regs = dwmac1000_dump_regs,
 507        .host_irq_status = dwmac1000_irq_status,
 508        .set_filter = dwmac1000_set_filter,
 509        .flow_ctrl = dwmac1000_flow_ctrl,
 510        .pmt = dwmac1000_pmt,
 511        .set_umac_addr = dwmac1000_set_umac_addr,
 512        .get_umac_addr = dwmac1000_get_umac_addr,
 513        .set_eee_mode = dwmac1000_set_eee_mode,
 514        .reset_eee_mode = dwmac1000_reset_eee_mode,
 515        .set_eee_timer = dwmac1000_set_eee_timer,
 516        .set_eee_pls = dwmac1000_set_eee_pls,
 517        .debug = dwmac1000_debug,
 518        .pcs_ctrl_ane = dwmac1000_ctrl_ane,
 519        .pcs_rane = dwmac1000_rane,
 520        .pcs_get_adv_lp = dwmac1000_get_adv_lp,
 521};
 522
 523int dwmac1000_setup(struct stmmac_priv *priv)
 524{
 525        struct mac_device_info *mac = priv->hw;
 526
 527        dev_info(priv->device, "\tDWMAC1000\n");
 528
 529        priv->dev->priv_flags |= IFF_UNICAST_FLT;
 530        mac->pcsr = priv->ioaddr;
 531        mac->multicast_filter_bins = priv->plat->multicast_filter_bins;
 532        mac->unicast_filter_entries = priv->plat->unicast_filter_entries;
 533        mac->mcast_bits_log2 = 0;
 534
 535        if (mac->multicast_filter_bins)
 536                mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
 537
 538        mac->link.duplex = GMAC_CONTROL_DM;
 539        mac->link.speed10 = GMAC_CONTROL_PS;
 540        mac->link.speed100 = GMAC_CONTROL_PS | GMAC_CONTROL_FES;
 541        mac->link.speed1000 = 0;
 542        mac->link.speed_mask = GMAC_CONTROL_PS | GMAC_CONTROL_FES;
 543        mac->mii.addr = GMAC_MII_ADDR;
 544        mac->mii.data = GMAC_MII_DATA;
 545        mac->mii.addr_shift = 11;
 546        mac->mii.addr_mask = 0x0000F800;
 547        mac->mii.reg_shift = 6;
 548        mac->mii.reg_mask = 0x000007C0;
 549        mac->mii.clk_csr_shift = 2;
 550        mac->mii.clk_csr_mask = GENMASK(5, 2);
 551
 552        return 0;
 553}
 554