root/usr/src/uts/common/io/bnx/570x/driver/common/lmdev/bnx_hw_phy.c
/*
 * Copyright 2014-2017 Cavium, Inc.
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License, v.1,  (the "License").
 *
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License at available
 * at http://opensource.org/licenses/CDDL-1.0
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "54xx_reg.h"
#include "serdes.h"
#include "lm5706.h"
#include "netlink.h"



/*******************************************************************************
 * Macros.
 ******************************************************************************/

#define MII_REG(_type, _field)          (OFFSETOF(_type, _field)/2)



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
lm_status_t
lm_mwrite(
    lm_device_t *pdev,
    u32_t phy_addr,
    u32_t reg,
    u32_t val)
{
    lm_status_t lm_status;
    u32_t tmp;
    u32_t cnt;

    DbgBreakIf(pdev->params.enable_remote_phy);

    if(pdev->params.phy_int_mode == PHY_INT_MODE_AUTO_POLLING)
    {
        REG_RD(pdev, emac.emac_mdio_mode, &tmp);
        tmp &= ~EMAC_MDIO_MODE_AUTO_POLL;

        REG_WR(pdev, emac.emac_mdio_mode, tmp);

        mm_wait(pdev, 40);
    }

    tmp = (phy_addr << 21) |
        (reg << 16) |
        val |
        EMAC_MDIO_COMM_COMMAND_WRITE_TE |
        EMAC_MDIO_COMM_START_BUSY |
        EMAC_MDIO_COMM_DISEXT;

    REG_WR(pdev, emac.emac_mdio_comm, tmp);

    for(cnt = 0; cnt < 1000; cnt++)
    {
        mm_wait(pdev, 10);

        REG_RD(pdev, emac.emac_mdio_comm, &tmp);
        if(!(tmp & EMAC_MDIO_COMM_START_BUSY))
        {
            mm_wait(pdev, 5);
            break;
        }
    }

    if(tmp & EMAC_MDIO_COMM_START_BUSY)
    {
        DbgBreakMsg("Write phy register failed\n");

        lm_status = LM_STATUS_FAILURE;
    }
    else
    {
        lm_status = LM_STATUS_SUCCESS;
    }

    if(pdev->params.phy_int_mode == PHY_INT_MODE_AUTO_POLLING)
    {
        REG_RD(pdev, emac.emac_mdio_mode, &tmp);
        tmp |= EMAC_MDIO_MODE_AUTO_POLL;

        REG_WR(pdev, emac.emac_mdio_mode, tmp);
    }

    return lm_status;
} /* lm_mwrite */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
lm_status_t
lm_mread(
    lm_device_t *pdev,
    u32_t phy_addr,
    u32_t reg,
    u32_t *ret_val)
{
    lm_status_t lm_status;
    u32_t val;
    u32_t cnt;

    DbgBreakIf(pdev->params.enable_remote_phy);

    if(pdev->params.phy_int_mode == PHY_INT_MODE_AUTO_POLLING)
    {
        REG_RD(pdev, emac.emac_mdio_mode, &val);
        val &= ~EMAC_MDIO_MODE_AUTO_POLL;

        REG_WR(pdev, emac.emac_mdio_mode, val);

        mm_wait(pdev, 40);
    }

    val = (phy_addr << 21) |
        (reg << 16) |
        EMAC_MDIO_COMM_COMMAND_READ_TE |
        EMAC_MDIO_COMM_DISEXT |
        EMAC_MDIO_COMM_START_BUSY;

    REG_WR(pdev, emac.emac_mdio_comm, val);

    for(cnt = 0; cnt < 1000; cnt++)
    {
        mm_wait(pdev, 10);

        REG_RD(pdev, emac.emac_mdio_comm, &val);
        if(!(val & EMAC_MDIO_COMM_START_BUSY))
        {
            /* There is a bug here.  The MI_COM_BUSY bit may be cleared
             * before the data is loaded into the register. */
            REG_RD(pdev, emac.emac_mdio_comm, &val);

            REG_RD(pdev, emac.emac_mdio_comm, &val);
            val &= EMAC_MDIO_COMM_DATA;

            break;
        }
    }

    if(val & EMAC_MDIO_COMM_START_BUSY)
    {
        DbgBreakMsg("Read phy register failed\n");

        val = 0;

        lm_status = LM_STATUS_FAILURE;
    }
    else
    {
        lm_status = LM_STATUS_SUCCESS;
    }

    *ret_val = val;

    if(pdev->params.phy_int_mode == PHY_INT_MODE_AUTO_POLLING)
    {
        REG_RD(pdev, emac.emac_mdio_mode, &val);
        val |= EMAC_MDIO_MODE_AUTO_POLL;

        REG_WR(pdev, emac.emac_mdio_mode, val);
    }

    return lm_status;
} /* lm_mread */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC u32_t
phy_ad_settings(
    lm_device_t *pdev,
    lm_medium_t req_medium,
    lm_flow_control_t flow_ctrl)
{
    u32_t val;

    val = 0;

    /* Please refer to Table 28B-3 of the 802.3ab-1999 spec. */
    if((flow_ctrl == LM_FLOW_CONTROL_AUTO_PAUSE) ||
        ((flow_ctrl & LM_FLOW_CONTROL_RECEIVE_PAUSE) &&
        (flow_ctrl & LM_FLOW_CONTROL_TRANSMIT_PAUSE)))
    {
        if(GET_MEDIUM_TYPE(req_medium) == LM_MEDIUM_TYPE_FIBER)
        {
            if(CHIP_NUM(pdev) == CHIP_NUM_5706)
            {
                val |= PHY_AN_AD_1000X_PAUSE_CAPABLE |
                    PHY_AN_AD_1000X_ASYM_PAUSE;
            }
            else
            {
                val |= MII_ADVERT_PAUSE;
            }
        }
        else
        {
            val |= PHY_AN_AD_PAUSE_CAPABLE | PHY_AN_AD_ASYM_PAUSE;
        }
    }
    else if(flow_ctrl & LM_FLOW_CONTROL_TRANSMIT_PAUSE)
    {
        if(GET_MEDIUM_TYPE(req_medium) == LM_MEDIUM_TYPE_FIBER)
        {
            if(CHIP_NUM(pdev) == CHIP_NUM_5706)
            {
                val |= PHY_AN_AD_1000X_ASYM_PAUSE;
            }
            else
            {
                val |= MII_ADVERT_ASYM_PAUSE;
            }
        }
        else
        {
            val |= PHY_AN_AD_ASYM_PAUSE;
        }
    }
    else if(flow_ctrl & LM_FLOW_CONTROL_RECEIVE_PAUSE)
    {
        if(GET_MEDIUM_TYPE(req_medium) == LM_MEDIUM_TYPE_FIBER)
        {
            if(CHIP_NUM(pdev) == CHIP_NUM_5706)
            {
                val |= PHY_AN_AD_1000X_PAUSE_CAPABLE |
                    PHY_AN_AD_1000X_ASYM_PAUSE;
            }
            else
            {
                val |= MII_ADVERT_PAUSE;
            }
        }
        else
        {
            val |= PHY_AN_AD_PAUSE_CAPABLE | PHY_AN_AD_ASYM_PAUSE;
        }
    }

    return val;
} /* phy_ad_settings */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
init_utp(
    lm_device_t *pdev,
    lm_medium_t req_medium,
    lm_flow_control_t flow_ctrl,
    u32_t selective_autoneg,
    u32_t wire_speed,
    u32_t wait_link_timeout_us)
{
    u32_t restart_autoneg;
    lm_status_t lm_status;
    lm_medium_t duplex;
    lm_medium_t speed;
    u32_t val;
    u32_t cnt;

    if(GET_MEDIUM_TYPE(req_medium) != LM_MEDIUM_TYPE_UTP)
    {
        return LM_STATUS_INVALID_PARAMETER;
    }

    speed = GET_MEDIUM_SPEED(req_medium);
    duplex = GET_MEDIUM_DUPLEX(req_medium);

    lm_status = LM_STATUS_SUCCESS;

    (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_CTRL_REG, PHY_CTRL_PHY_RESET);
    for(cnt = 0; cnt < 1000; cnt++)
    {
        mm_wait(pdev, 5);

        (void) lm_mread(pdev, pdev->params.phy_addr, PHY_CTRL_REG, &val);

        if(!(val & PHY_CTRL_PHY_RESET))
        {
            mm_wait(pdev, 5);

            break;
        }
    }

    DbgBreakIf(val & PHY_CTRL_PHY_RESET);

    /* Get the PHY id. */
    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_ID1_REG, &val);
    pdev->hw_info.phy_id = val << 16;
    DbgMessage1(pdev, INFORM, "Phy Id1 0x%x\n", val);

    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_ID2_REG, &val);
    pdev->hw_info.phy_id |= val & 0xffff;
    DbgMessage1(pdev, INFORM, "Phy Id2 0x%x\n", val);

    DbgBreakIf(
        (pdev->hw_info.phy_id & 0x0fffffff) == 0x0fffffff ||
        pdev->hw_info.phy_id == 0);

    if(CHIP_REV(pdev) == CHIP_REV_FPGA)
    {
        /* Configure how the MAC obtain link from the external PHY.
         * On the FPGA board, LED2 is used as a link signal into the
         * MAC.  Configure LED2 to a link event on the AC101L PHY. */
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 28, 0x3044);
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 29, 0x0100);
    }
    else
    {
        if(CHIP_NUM(pdev) == CHIP_NUM_5706 || CHIP_NUM(pdev) == CHIP_NUM_5708)
        {
            /* Gen6 PHY core has a slight increase in CRC error.
             * This will workaround the problem which will be
             * fixed in Gen7 PHY core. */
            (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, 0x0c00);
            (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x17, 0x000a);
            (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x15, 0x310b);
            (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x17, 0x201f);
            (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x15, 0x9506);
            (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x17, 0x401f);
            (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x15, 0x14e2);
            (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, 0x0400);
        }

        /* Enable/Disable Ethernet@WireSpeed. */
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, 0x7007);
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x18, &val);

        val &= 0x0ff8;

        if(wire_speed)
        {
            val |= 0x10;
        }
        else
        {
            val &= ~0x10;
        }
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, val | 0x8000 | 0x7);

        /*
         * Cont00039501 Issue Description: Auto MDIX mode doesn't work in forced speed
         * while two 5716 connected back-to-back
         */
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, 0x7007);
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x18, &val);
        val |= BIT_9; /*auto mdix*/
        (void) lm_mwrite(pdev, pdev->params.phy_addr, BCM5401_AUX_CTRL, val | 0x8000 | 0x7);

    }

    /* Expansion register 0x8 is the 10BT control register.  BIT 8 of this
     * register controls the Early DAC Wakeup Enable.  this bit allows the
     * transmitter to be shutdown in 10BT mode except for sending out link
     * pulses. This allows for a low power operation in 10BT mode which is
     * useful in WOL application.  The default value of this register bit
     * gets loaded from a strap value on the GPHY provided by the chip that
     * instantiates the PHY.  in Xinan this strap value is 1, meaning that
     * the early DAC Wakeup Enable bit is set by default. FW/Driver needs to
     * clear this bit when bringing the PHY out of reset. */
    if(CHIP_ID(pdev) == CHIP_ID_5709_A0 ||
        CHIP_ID(pdev) == CHIP_ID_5709_A1 ||
        CHIP_ID(pdev) == CHIP_ID_5709_B0 ||
        CHIP_ID(pdev) == CHIP_ID_5709_B1)
    {
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x17, 0xf08);
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x15, &val);
        val &= ~0x100;
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x15, val);
    }

    /* Configure the PHY for jumbo frame. */
    if(pdev->params.mtu > MAX_ETHERNET_PACKET_SIZE)
    {
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, 0x0001);
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, 0x4400);
    }
    else
    {
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, 0x0000);
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, 0x0400);
    }

    /* Configure line speed. */
    restart_autoneg = FALSE;

    switch(speed)
    {
        case LM_MEDIUM_SPEED_10MBPS:
            /* Don't advertise 1000mb.  This register is undefined on a
             * 10/100 Mb PHY. */
            (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_1000BASET_CTRL_REG, 0);

            /* Setup AN_AD to advertise 10mb. */
            val = PHY_AN_AD_PROTOCOL_802_3_CSMA_CD;
            val |= phy_ad_settings(pdev, req_medium, flow_ctrl);

            if(duplex == LM_MEDIUM_FULL_DUPLEX)
            {
                val |= PHY_AN_AD_10BASET_FULL;

                if(selective_autoneg == SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
                {
                    val |= PHY_AN_AD_10BASET_HALF;
                }
            }
            else
            {
                val |= PHY_AN_AD_10BASET_HALF;
            }

            (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_AN_AD_REG, val);

            /* Forcing or advertising 10mb. */
            if(selective_autoneg)
            {
                restart_autoneg = TRUE;

                DbgMessage(pdev, INFORM, "autoneg 10mb hd\n");
                if(duplex == LM_MEDIUM_FULL_DUPLEX)
                {
                    DbgMessage(pdev, INFORM, "and 10mb fd\n");
                }
            }
            else
            {
                if(duplex == LM_MEDIUM_HALF_DUPLEX)
                {
                    DbgMessage(pdev, INFORM, "force 10mb hd\n");
                    (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        PHY_CTRL_REG,
                        PHY_CTRL_SPEED_SELECT_10MBPS);
                }
                else
                {
                    DbgMessage(pdev, INFORM, "force 10mb fd\n");
                    (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        PHY_CTRL_REG,
                        PHY_CTRL_SPEED_SELECT_10MBPS |
                            PHY_CTRL_FULL_DUPLEX_MODE);
                }
            }

            break;

        case LM_MEDIUM_SPEED_100MBPS:
            /* Don't advertise 1000mb.  This register is undefined on a
             * 10/100 PHY. */
            (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_1000BASET_CTRL_REG, 0);

            /* Setup AN_AD to advertise 10/100mb. */
            val = PHY_AN_AD_PROTOCOL_802_3_CSMA_CD;
            val |= phy_ad_settings(pdev, req_medium, flow_ctrl);

            if(selective_autoneg == SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
            {
                val |= PHY_AN_AD_10BASET_HALF | PHY_AN_AD_10BASET_FULL;
            }

            if(duplex == LM_MEDIUM_FULL_DUPLEX)
            {
                val |= PHY_AN_AD_100BASETX_FULL;

                if(selective_autoneg == SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
                {
                    val |= PHY_AN_AD_100BASETX_HALF;
                }
            }
            else
            {
                val |= PHY_AN_AD_100BASETX_HALF;
            }

            (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_AN_AD_REG, val);

            /* Forcing or advertising 100mb. */
            if(selective_autoneg)
            {
                restart_autoneg = TRUE;

                DbgMessage(pdev, INFORM, "autoneg 10mb and 100mb hd\n");
                if(duplex == LM_MEDIUM_FULL_DUPLEX)
                {
                    DbgMessage(pdev, INFORM, "and 100mb fd\n");
                }
            }
            else
            {
                if(duplex == LM_MEDIUM_HALF_DUPLEX)
                {
                    DbgMessage(pdev, INFORM, "force 100mb hd\n");
                    (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        PHY_CTRL_REG,
                        PHY_CTRL_SPEED_SELECT_100MBPS);
                }
                else
                {
                    DbgMessage(pdev, INFORM, "force 100mb fd\n");
                    (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        PHY_CTRL_REG,
                        PHY_CTRL_SPEED_SELECT_100MBPS |
                            PHY_CTRL_FULL_DUPLEX_MODE);
                }
            }

            break;

        case LM_MEDIUM_SPEED_1000MBPS:
            /* Don't advertise 10/100mb. */
            val = PHY_AN_AD_PROTOCOL_802_3_CSMA_CD;
            val |= phy_ad_settings(pdev, req_medium, flow_ctrl);

            if(selective_autoneg == SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
            {
                val |= PHY_AN_AD_10BASET_HALF | PHY_AN_AD_10BASET_FULL;
                val |= PHY_AN_AD_100BASETX_HALF | PHY_AN_AD_100BASETX_FULL;
            }

            (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_AN_AD_REG, val);

            /* Setup AN_AD to advertise 1000mb.  This register is defined on
             * a 10/100 Mb PHY. */
            if(duplex == LM_MEDIUM_FULL_DUPLEX)
            {
                val |= PHY_AN_AD_1000BASET_FULL;

                if(selective_autoneg == SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
                {
                    val |= PHY_AN_AD_1000BASET_HALF;
                }
            }
            else
            {
                val |= PHY_AN_AD_1000BASET_HALF;
            }

            /* Forcing or advertising 1000mb. */
            if(selective_autoneg)
            {
                DbgMessage(pdev, INFORM, "autoneg 10/100mb and 1000mb hd\n");
                if(duplex == LM_MEDIUM_FULL_DUPLEX)
                {
                    DbgMessage(pdev, INFORM, "and 1000mb fd\n");
                }

                restart_autoneg = TRUE;
            }
            else
            {
                /* external loopback at 1gb link. */
                (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        PHY_CTRL_REG,
                        PHY_CTRL_SPEED_SELECT_1000MBPS);

                (void) lm_mwrite(pdev, pdev->params.phy_addr, BCM5401_AUX_CTRL, 0x7);
                (void) lm_mread(pdev, pdev->params.phy_addr, BCM5401_AUX_CTRL, &val);
                val |= BCM5401_SHDW_NORMAL_EXTERNAL_LOOPBACK;
                (void) lm_mwrite(pdev, pdev->params.phy_addr, BCM5401_AUX_CTRL, val);

                val = PHY_CONFIG_AS_MASTER | PHY_ENABLE_CONFIG_AS_MASTER;
            }

            (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_1000BASET_CTRL_REG, val);
            break;

        default:
            val = PHY_AN_AD_PROTOCOL_802_3_CSMA_CD |
                PHY_AN_AD_10BASET_HALF |
                PHY_AN_AD_10BASET_FULL |
                PHY_AN_AD_100BASETX_FULL |
                PHY_AN_AD_100BASETX_HALF;
            val |= phy_ad_settings(pdev, req_medium, flow_ctrl);

            /* Set up the 10/100 advertisement register. */
            (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_AN_AD_REG, val);

            /* Advertise 1000Mbps.  This register is undefined on a
             * 10/100 Mb PHY. */
            (void) lm_mwrite(
                pdev,
                pdev->params.phy_addr,
                PHY_1000BASET_CTRL_REG,
                PHY_AN_AD_1000BASET_HALF |
                    PHY_AN_AD_1000BASET_FULL);

            restart_autoneg = TRUE;
            speed = LM_MEDIUM_SPEED_AUTONEG;
            break;
    }

    /* exit mac loopback.  we could be in mac loopback mode if previously
     * the upper module calls lm_init_phy with LM_MEDIUM_TYPE_MAC_LOOPBACK
     * medium type for diagnostic. */
    REG_RD(pdev, emac.emac_mode, &val);
    val &= ~(EMAC_MODE_MAC_LOOP | EMAC_MODE_FORCE_LINK);
    REG_WR(pdev, emac.emac_mode, val);

    /* Restart auto-negotation. */
    if(restart_autoneg)
    {
        DbgMessage(pdev, INFORM, "phy init - restart autoneg\n");

        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            PHY_CTRL_REG,
            PHY_CTRL_AUTO_NEG_ENABLE | PHY_CTRL_RESTART_AUTO_NEG);
    }

    /* Save current medium settings. */
    SET_MEDIUM_TYPE(pdev->vars.medium, LM_MEDIUM_TYPE_UTP);
    SET_MEDIUM_SPEED(pdev->vars.medium, speed);
    SET_MEDIUM_DUPLEX(pdev->vars.medium, duplex);

    pdev->vars.cable_is_attached = FALSE;

    /* Wait for link. */
    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_STATUS_REG, &val);

    if(CHIP_REV(pdev) != CHIP_REV_FPGA)
    {
        /* Wait for link only if the cable is connected. */
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1c, 0x7c00);
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x1c, &val);
        if(val & 0x20)
        {
            for(; ;)
            {
                (void) lm_mread(pdev, pdev->params.phy_addr, PHY_STATUS_REG, &val);
                if(val & PHY_STATUS_LINK_PASS)
                {
                    break;
                }

                mm_wait(pdev, 10);

                if(wait_link_timeout_us <= 10)
                {
                    break;
                }

                wait_link_timeout_us -= 10;
            }

            pdev->vars.cable_is_attached = TRUE;
        }
    }

    /* Need to read a second time to get the current link status. */
    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_STATUS_REG, &val);
    if(val & PHY_STATUS_LINK_PASS)
    {
        pdev->vars.link_status = LM_STATUS_LINK_ACTIVE;
        DbgMessage(pdev, INFORM, "phy init link up\n");

        pdev->vars.cable_is_attached = TRUE;
    }
    else
    {
        pdev->vars.link_status = LM_STATUS_LINK_DOWN;
        DbgMessage(pdev, INFORM, "phy init link down\n");
    }

    return lm_status;
} /* init_utp */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC u32_t
mii_get_serdes_link_status(
    lm_device_t *pdev)
{
    u32_t val;

    /* The link status in the MII status register is not reliable for
     * the SERDES part.  We need to get the link info from the MAC. */
    if(CHIP_NUM(pdev) == CHIP_NUM_5706 &&
        lm_get_medium(pdev) == LM_MEDIUM_TYPE_FIBER)
    {
        REG_RD(pdev, emac.emac_status, &val);
        if(val & EMAC_STATUS_LINK)
        {
            val = PHY_STATUS_LINK_PASS;
        }
        else
        {
            val = 0;
        }
    }
    else
    {
        /* The second read returns the current status. */
        (void) lm_mread(pdev, pdev->params.phy_addr, PHY_STATUS_REG, &val);
        (void) lm_mread(pdev, pdev->params.phy_addr, PHY_STATUS_REG, &val);
    }

    return val;
} /* mii_get_serdes_link_status */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC u8_t
set_5708_serdes_pre_emphasis(
    lm_device_t *pdev,
    u32_t pre_emphasis)
{
    u8_t restart_autoneg;
    u32_t val;

    restart_autoneg = FALSE;

    if(pre_emphasis == 0)
    {
        (void) lm_mread(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_aneg_nxt_pg_rcv2),
            &pre_emphasis);
        pre_emphasis &= 0xf;

        if(pre_emphasis != pdev->vars.serdes_pre_emphasis)
        {
            pdev->vars.serdes_pre_emphasis = pre_emphasis;

            restart_autoneg = TRUE;

            /* Switch to Bank 5. */
            (void) lm_mwrite(
                pdev,
                pdev->params.phy_addr,
                MII_REG(serdes_reg_t, mii_block_addr),
                MII_BLK_ADDR_TXMISC);

            /* Write the new pre-emphasis. */
            (void) lm_mread(
                pdev,
                pdev->params.phy_addr,
                0x10+MII_REG(serdes_tx_misc_reg_t, mii_txactl3),
                &val);

            pre_emphasis =
                ((pre_emphasis & 0x1) << 15) |
                ((pre_emphasis & 0x2) << 13) |
                ((pre_emphasis & 0x4) << 11) |
                ((pre_emphasis & 0x8) << 9);
            val = (val & 0x0fff) | pre_emphasis;

            (void) lm_mwrite(
                pdev,
                pdev->params.phy_addr,
                0x10+MII_REG(serdes_tx_misc_reg_t, mii_txactl3),
                val);

            /* Select Bank 0. */
            (void) lm_mwrite(
                pdev,
                pdev->params.phy_addr,
                MII_REG(serdes_reg_t, mii_block_addr),
                MII_BLK_ADDR_DIGITAL);

            /* Restart autoneg. */
            (void) lm_mwrite(
                pdev,
                pdev->params.phy_addr,
                MII_REG(serdes_reg_t, mii_ctrl),
                MII_CTRL_RESTART_ANEG | MII_CTRL_ANEG_ENA);
        }
    }
    else
    {
        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_block_addr),
            MII_BLK_ADDR_TXMISC);

        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            0x10+MII_REG(serdes_tx_misc_reg_t, mii_txactl3),
            pre_emphasis);

        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_block_addr),
            MII_BLK_ADDR_DIGITAL);
    }

    return restart_autoneg;
} /* set_5708_serdes_pre_emphasis */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
init_5708_serdes(
    lm_device_t *pdev,
    lm_medium_t req_medium,
    lm_flow_control_t flow_ctrl,
    u32_t selective_autoneg,
    u32_t wait_link_timeout_us)
{
    lm_medium_t duplex;
    lm_medium_t speed;
    u32_t cnt;
    u32_t val;

    if(GET_MEDIUM_SPEED(req_medium) == LM_MEDIUM_SPEED_UNKNOWN)
    {
        selective_autoneg = FALSE;
    }

    speed = GET_MEDIUM_SPEED(req_medium);
    duplex = GET_MEDIUM_DUPLEX(req_medium);

    if(speed == LM_MEDIUM_SPEED_HARDWARE_DEFAULT)
    {
        REG_RD_IND(
            pdev,
            pdev->hw_info.shmem_base +
                OFFSETOF(shmem_region_t, dev_info.port_hw_config.config),
            &val);

        switch(val & PORT_HW_CFG_DEFAULT_LINK_MASK)
        {
            case PORT_HW_CFG_DEFAULT_LINK_1G:
                speed = LM_MEDIUM_SPEED_1000MBPS;
                break;

            case PORT_HW_CFG_DEFAULT_LINK_2_5G:
                speed = LM_MEDIUM_SPEED_2500MBPS;
                break;

            default:
                speed = LM_MEDIUM_SPEED_UNKNOWN;
                break;
        }
    }

    /* Reset the SERDES. */
    (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_ctrl),
            MII_CTRL_RESET);

    for(cnt = 0; cnt < 1000; cnt++)
    {
        mm_wait(pdev, 5);

        (void) lm_mread(
                pdev,
                pdev->params.phy_addr,
                MII_REG(serdes_reg_t, mii_ctrl),
                &val);

        if(!(val & MII_CTRL_RESET))
        {
            mm_wait(pdev, 5);

            break;
        }
    }

    DbgBreakIf(val & MII_CTRL_RESET);

    /* Workaround for 5708A0 and B0.
     *
     * Errata 1.75: Tx peak-to-peak amplitude was measured as low
     * as 765mV under full PVT testing, whereas 800mV is considered
     * a passing result. */
    if(CHIP_NUM(pdev) == CHIP_NUM_5708)
    {
        /* Switch to Bank 5. */
        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_block_addr),
            MII_BLK_ADDR_TXMISC);

        (void) lm_mread(
            pdev,
            pdev->params.phy_addr,
            0x10+MII_REG(serdes_tx_misc_reg_t, mii_txactl1),
            &val);

        val &= ~ MII_TXACTL1_DRIVER_VCM;

        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            0x10+MII_REG(serdes_tx_misc_reg_t, mii_txactl1),
            val);
    }

    /* Set up pre-emphasis for a backplane application. */
    if(pdev->hw_info.nvm_hw_config & SHARED_HW_CFG_BACKPLANE_APP)
    {
        (void) set_5708_serdes_pre_emphasis(pdev, pdev->params.serdes_pre_emphasis);
    }

    /* Reset the pre_emphasis. */
    pdev->vars.serdes_pre_emphasis = 0;

    /* Get the PHY id. */
    (void) lm_mread(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_phy_id_msb),
            &val);
    pdev->hw_info.phy_id = val << 16;
    DbgMessage1(pdev, INFORM, "Phy Id1 0x%x\n", val);

    (void) lm_mread(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_phy_id_lsb),
            &val);
    pdev->hw_info.phy_id |= val & 0xffff;
    DbgMessage1(pdev, INFORM, "Phy Id2 0x%x\n", val);

    DbgBreakIf((pdev->hw_info.phy_id & 0x0fffffff) == 0x0fffffff ||
        pdev->hw_info.phy_id == 0);

    /* Enable 2.5G register set to be accessible in the IEEE registers. */
    (void) lm_mwrite(
        pdev,
        pdev->params.phy_addr,
        MII_REG(serdes_reg_t, mii_block_addr),
        MII_BLK_ADDR_DIGITAL3);
    (void) lm_mwrite(
        pdev,
        pdev->params.phy_addr,
        0x10+MII_REG(serdes_digital3_reg_t, mii_digctl_3_0),
        MII_DIG3_USE_IEEE);

    /* Switch back to the IEEE Bank. */
    (void) lm_mwrite(
        pdev,
        pdev->params.phy_addr,
        MII_REG(serdes_reg_t, mii_block_addr),
        MII_BLK_ADDR_DIGITAL);

    /* Enable SGMII/Fiber mode autodetection. */
    (void) lm_mread(
        pdev,
        pdev->params.phy_addr,
        0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl1),
        &val);

    val |= MII_1000X_CTL1_FIBER_MODE | MII_1000X_CTL1_AUTODET_EN;

    /* Sigdet is enabled by default.  For backplane application, we need
     * to disable Sigdet by clearing 0*0x10.2 of the Digital Bank. */
    if(pdev->hw_info.nvm_hw_config & SHARED_HW_CFG_BACKPLANE_APP)
    {
        val &= ~MII_1000X_CTL1_SIG_DET_EN;
    }
    else
    {
        val |= MII_1000X_CTL1_SIG_DET_EN;
    }

    (void) lm_mwrite(
        pdev,
        pdev->params.phy_addr,
        0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl1),
        val);

    /* We should always enable parallel detection. */
    (void) lm_mread(
        pdev,
        pdev->params.phy_addr,
        0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl2),
        &val);

    val |= MII_1000X_CTL2_PAR_DET_EN;

    (void) lm_mwrite(
        pdev,
        pdev->params.phy_addr,
        0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl2),
        val);

    /* Enable/disable 2.5G capability. */
    (void) lm_mread(
        pdev,
        pdev->params.phy_addr,
        MII_REG(serdes_reg_t, mii_aneg_nxt_pg_xmit1),
        &val);

    val &= ~MII_ANEG_NXT_PG_XMIT1_2G5;

    if(selective_autoneg)
    {
        if(speed == LM_MEDIUM_SPEED_2500MBPS)
        {
            val |= MII_ANEG_NXT_PG_XMIT1_2G5;
        }
    }
    else if(speed == LM_MEDIUM_SPEED_AUTONEG)
    {
        if(pdev->hw_info.nvm_hw_config & SHARED_HW_CFG_PHY_FIBER_2_5G)
        {
            val |= MII_ANEG_NXT_PG_XMIT1_2G5;
        }
    }
    else if(speed == LM_MEDIUM_SPEED_2500MBPS)
    {
        val |= MII_ANEG_NXT_PG_XMIT1_2G5;
    }

    (void) lm_mwrite(
        pdev,
        pdev->params.phy_addr,
        MII_REG(serdes_reg_t, mii_aneg_nxt_pg_xmit1),
        val);

    val = 0;

    if(selective_autoneg || speed == LM_MEDIUM_SPEED_UNKNOWN)
    {
        val |= phy_ad_settings(pdev, req_medium, flow_ctrl);

        if((selective_autoneg && speed == LM_MEDIUM_SPEED_1000MBPS) ||
            speed == LM_MEDIUM_SPEED_UNKNOWN)
        {
            val |= MII_ABILITY_HALF | MII_ABILITY_FULL;
        }

        (void) lm_mwrite(
                pdev,
                pdev->params.phy_addr,
                MII_REG(serdes_reg_t, mii_aneg_advert),
                val);

        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_ctrl),
            MII_CTRL_RESTART_ANEG | MII_CTRL_ANEG_ENA);

        speed = LM_MEDIUM_SPEED_AUTONEG;
    }
    else
    {
        switch(speed)
        {
            case LM_MEDIUM_SPEED_10MBPS:
                if(duplex == LM_MEDIUM_FULL_DUPLEX)
                {
                    val |= MII_CTRL_DUPLEX_MODE;
                }

                (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        MII_REG(serdes_reg_t, mii_ctrl),
                        val);
                /* Switch to SGMII mode and disable auto-detect */
                (void) lm_mread(
                        pdev,
                        pdev->params.phy_addr,
                        0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl1),
                        &val);
                (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl1),
                        val & ~(MII_1000X_CTL1_FIBER_MODE | MII_1000X_CTL1_AUTODET_EN));
                break;

            case LM_MEDIUM_SPEED_100MBPS:
                if(duplex == LM_MEDIUM_FULL_DUPLEX)
                {
                    val |= MII_CTRL_DUPLEX_MODE;
                }

                val |= MII_CTRL_MANUAL_SPD0;

                (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        MII_REG(serdes_reg_t, mii_ctrl),
                        val);
                /* Switch to SGMII mode and disable auto-detect */
                (void) lm_mread(
                        pdev,
                        pdev->params.phy_addr,
                        0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl1),
                        &val);
                (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl1),
                        val & ~(MII_1000X_CTL1_FIBER_MODE | MII_1000X_CTL1_AUTODET_EN));
                break;

            case LM_MEDIUM_SPEED_1000MBPS:
                if(duplex == LM_MEDIUM_FULL_DUPLEX)
                {
                    val |= MII_CTRL_DUPLEX_MODE;
                }

                val |= MII_CTRL_MANUAL_SPD1;

                (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        MII_REG(serdes_reg_t, mii_ctrl),
                        val);
                break;

            case LM_MEDIUM_SPEED_2500MBPS:
                if(duplex == LM_MEDIUM_FULL_DUPLEX)
                {
                    val |= MII_CTRL_DUPLEX_MODE;
                }

                val |= MII_CTRL_MANUAL_FORCE_2500;

                (void) lm_mwrite(
                        pdev,
                        pdev->params.phy_addr,
                        MII_REG(serdes_reg_t, mii_ctrl),
                        val);
                break;
        }
    }

    /* exit mac loopback.  we could be in mac loopback mode if previously
     * the upper module calls lm_init_phy with LM_MEDIUM_TYPE_MAC_LOOPBACK
     * medium type for diagnostic. */
    REG_RD(pdev, emac.emac_mode, &val);
    val &= ~(EMAC_MODE_MAC_LOOP | EMAC_MODE_FORCE_LINK);
    REG_WR(pdev, emac.emac_mode, val);

    /* Configure the PHY for jumbo frame. */
    if(pdev->params.mtu > MAX_ETHERNET_PACKET_SIZE)
    {
        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl3),
            MII_1000X_CTL3_FIFO_ELAST_10K);
    }
    else
    {
        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            0x10+MII_REG(serdes_digital_reg_t, mii_1000x_ctl3),
            0);
    }

    /* Save current medium settings. */
    SET_MEDIUM_TYPE(pdev->vars.medium, LM_MEDIUM_TYPE_FIBER);
    SET_MEDIUM_SPEED(pdev->vars.medium, speed);
    SET_MEDIUM_DUPLEX(pdev->vars.medium, duplex);

    pdev->vars.cable_is_attached = FALSE;

    /* Wait for link. */
    (void) lm_mread(
        pdev,
        pdev->params.phy_addr,
        MII_REG(serdes_reg_t, mii_status),
        &val);

    for(; ;)
    {
        (void) lm_mread(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_status),
            &val);
        if(val & MII_STAT_LINK_STATUS)
        {
            break;
        }

        mm_wait(pdev, 10);

        if(wait_link_timeout_us <= 10)
        {
            break;
        }

        wait_link_timeout_us -= 10;
    }

    /* Need to read a second time to get the current link status. */
    (void) lm_mread(
        pdev,
        pdev->params.phy_addr,
        MII_REG(serdes_reg_t, mii_status),
        &val);
    if(val & MII_STAT_LINK_STATUS)
    {
        pdev->vars.link_status = LM_STATUS_LINK_ACTIVE;
        DbgMessage(pdev, INFORM, "phy init link up\n");

        pdev->vars.cable_is_attached = TRUE;
    }
    else
    {
        pdev->vars.link_status = LM_STATUS_LINK_DOWN;
        DbgMessage(pdev, INFORM, "phy init link down\n");
    }

    return LM_STATUS_SUCCESS;
} /* init_5708_serdes */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC void
force_5709_serdes_link(
    lm_device_t *pdev,
    lm_medium_t speed,
    lm_medium_t duplex)
{
    u32_t val;

    /* select serdes digital block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0x8300);

    /* disable sgmii/fiber mode autodetection. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x10, &val);
    val &= ~0x10;

    /* sgmii or 1000x_fiber mode. */
    val &= ~1;
    if(speed == LM_MEDIUM_SPEED_2500MBPS)
    {
        val |= 1;
    }

    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, val);

    /* select combo ieee0 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffe0);

    /* phy control register. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x10, &val);

    val &= ~0x1000; /* autoneg. */
    val &= ~0x100;  /* duplex. */
    val &= ~0x2060; /* speed. */

    if(duplex == LM_MEDIUM_FULL_DUPLEX)
    {
        val |= 0x100;
    }

    if(speed == LM_MEDIUM_SPEED_10MBPS)
    {
        /* bit 13 and 6 are already cleared. */
        ;
    }
    else if(speed == LM_MEDIUM_SPEED_100MBPS)
    {
        val |= 0x2000;
    }
    else if(speed == LM_MEDIUM_SPEED_1000MBPS)
    {
        val |= 0x2040;
    }
    else if(speed == LM_MEDIUM_SPEED_2500MBPS)
    {
        val |= 0x20;
    }
    else
    {
        DbgBreakMsg("unknown forced speed.\n");
    }

    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, val);

    /* set speed. */
    if(speed == LM_MEDIUM_SPEED_2500MBPS)
    {
        /* select serdes digital block. */
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0x8300);

        /* set 2.5g speed. */
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x18, &val);
        val &= 0xfff0;
        val |= 0x10;
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, val);
    }
} /* force_5709_serdes_link */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC void
init_5709_serdes_for_autoneg(
    lm_device_t *pdev,
    lm_medium_t req_medium,
    lm_flow_control_t flow_ctrl,
    u32_t selective_autoneg)
{
    u32_t val;

    if(GET_MEDIUM_SPEED(req_medium) == LM_MEDIUM_SPEED_UNKNOWN)
    {
        selective_autoneg = FALSE;
    }

    if(!(pdev->hw_info.nvm_hw_config & SHARED_HW_CFG_PHY_FIBER_2_5G) ||
        (selective_autoneg &&
            GET_MEDIUM_SPEED(req_medium) != LM_MEDIUM_SPEED_2500MBPS))
    {
        /* disable 2.5g adv */
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0x8320);
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x19, &val);
        val &= ~1;
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x19, val);
    }

    /* select serdes digital block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0x8300);

    /* enable sgmii/fiber mode autodetection. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x10, &val);
    val |= 0x10;
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, val);

    /* disable parallel detection. */
    if(selective_autoneg)
    {
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x11, &val);
        val &= ~0x1;
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x11, val);
    }

    /* select bam next page block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0x8350);

    /* mp5_next_page_control. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x10, &val);
    val &= ~3;
    val |= 1;   /* set bam mode. */
    val |= 2;   /* enable t2 mode. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, val);

    /* select cl73_userb0 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0x8370);

    /* enable bam_en, bam_station_mngr_en, bam_np_after_bp_en. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x12, 0xe000);

    /* select ieee1 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0x10);

    /* advertise 1000kx. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x1, &val);
    val |= 0x20;
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1, val);

    /* select ieee0 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0);

    /* enable cl73 aneg. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x0, &val);
    val |= 0x1200;
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x0, val);

    /* select combo ieee0 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffe0);

    /* advertise pause capability and duplex mode. */
    val = phy_ad_settings(pdev, req_medium, flow_ctrl);
    if(selective_autoneg &&
        GET_MEDIUM_SPEED(req_medium) == LM_MEDIUM_SPEED_2500MBPS)
    {
        val &= ~0x60;
    }
    else
    {
        val |= 0x60;    /* half/full duplex. */
    }
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x14, val);

    /* autoneg_enable and restart. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x10, &val);
    val |= 0x1200;
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, val);
} /* init_5709_serdes_for_autoneg */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
init_5709_serdes(
    lm_device_t *pdev,
    lm_medium_t req_medium,
    lm_flow_control_t flow_ctrl,
    u32_t selective_autoneg,
    u32_t wait_link_timeout_us)
{
    lm_medium_t duplex;
    lm_medium_t speed;
    u32_t idx;
    u32_t val;

    speed = GET_MEDIUM_SPEED(req_medium);
    duplex = GET_MEDIUM_DUPLEX(req_medium);

    /* use nvram link speed configuration. */
    if(speed == LM_MEDIUM_SPEED_HARDWARE_DEFAULT)
    {
        REG_RD_IND(
            pdev,
            pdev->hw_info.shmem_base +
                OFFSETOF(shmem_region_t, dev_info.port_hw_config.config),
            &val);

        switch(val & PORT_HW_CFG_DEFAULT_LINK_MASK)
        {
            case PORT_HW_CFG_DEFAULT_LINK_1G:
                speed = LM_MEDIUM_SPEED_1000MBPS;
                break;

            case PORT_HW_CFG_DEFAULT_LINK_2_5G:
                speed = LM_MEDIUM_SPEED_2500MBPS;
                break;

            default:
                speed = LM_MEDIUM_SPEED_UNKNOWN;
                break;
        }

        selective_autoneg = FALSE;
    }

    /* set an_mmd.  an_mmd is the only register set we need for
     * programming xinan serdes.  all other registers are can
     * be access through an_mmd. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffd0);
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1e, 0x3800);

    /* select combo_ieee0 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffe0);

    /* reset. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, MII_CTRL_RESET);
    for(idx = 0; idx < 1000; idx++)
    {
        mm_wait(pdev, 5);

        (void) lm_mread(pdev, pdev->params.phy_addr, 0x10, &val);
        if(!(val & MII_CTRL_RESET))
        {
            mm_wait(pdev, 5);
            break;
        }
    }
    DbgBreakIf(val & MII_CTRL_RESET);

    /* get phy id. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x12, &val);
    pdev->hw_info.phy_id = val << 16;
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x13, &val);
    pdev->hw_info.phy_id |= val & 0xffff;

    if(speed == LM_MEDIUM_SPEED_AUTONEG_1G_FALLBACK)
    {
        speed = LM_MEDIUM_SPEED_AUTONEG;
    }

    /* config link speed or autoneg setting. */
    if(speed == LM_MEDIUM_SPEED_AUTONEG || selective_autoneg)
    {
        init_5709_serdes_for_autoneg(
            pdev,
            req_medium,
            flow_ctrl,
            selective_autoneg);
    }
    else
    {
        force_5709_serdes_link(pdev, speed, duplex);
    }

    /* exit mac loopback.  we could be in mac loopback mode if previously
     * the upper module calls lm_init_phy with LM_MEDIUM_TYPE_MAC_LOOPBACK
     * medium type for diagnostic. */
    REG_RD(pdev, emac.emac_mode, &val);
    val &= ~(EMAC_MODE_MAC_LOOP | EMAC_MODE_FORCE_LINK);
    REG_WR(pdev, emac.emac_mode, val);

    SET_MEDIUM_TYPE(pdev->vars.medium, LM_MEDIUM_TYPE_FIBER);
    SET_MEDIUM_SPEED(pdev->vars.medium, speed);
    SET_MEDIUM_DUPLEX(pdev->vars.medium, duplex);

    pdev->vars.cable_is_attached = FALSE;

    /* select combo_ieee0 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffe0);

    /* wait for link. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x11, &val);

    for(; ;)
    {
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x11, &val);
        if(val & 0x4)
        {
            break;
        }

        mm_wait(pdev, 10);

        if(wait_link_timeout_us <= 10)
        {
            break;
        }

        wait_link_timeout_us -= 10;
    }

    /* need to read a second time to get the current link status. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x11, &val);

    if(val & MII_STAT_LINK_STATUS)
    {
        pdev->vars.link_status = LM_STATUS_LINK_ACTIVE;
        pdev->vars.cable_is_attached = TRUE;
    }
    else
    {
        pdev->vars.link_status = LM_STATUS_LINK_DOWN;
    }

    return LM_STATUS_SUCCESS;
} /* init_5709_serdes */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
init_5706_serdes(
    lm_device_t *pdev,
    lm_medium_t req_medium,
    lm_flow_control_t flow_ctrl,
    u32_t wait_link_timeout_us)
{
    lm_medium_t duplex;
    lm_medium_t speed;
    u32_t val;
    u32_t cnt;

    speed = GET_MEDIUM_SPEED(req_medium);
    duplex = GET_MEDIUM_DUPLEX(req_medium);

    if(speed == LM_MEDIUM_SPEED_HARDWARE_DEFAULT)
    {
        REG_RD_IND(
            pdev,
            pdev->hw_info.shmem_base +
                OFFSETOF(shmem_region_t, dev_info.port_hw_config.config),
            &val);
        switch(val & PORT_HW_CFG_DEFAULT_LINK_MASK)
        {
            case PORT_HW_CFG_DEFAULT_LINK_1G:
                speed = LM_MEDIUM_SPEED_1000MBPS;
                break;

            case PORT_HW_CFG_DEFAULT_LINK_2_5G:
                speed = LM_MEDIUM_SPEED_2500MBPS;
                break;

            case PORT_HW_CFG_DEFAULT_LINK_AN_1G_FALLBACK:
                speed = LM_MEDIUM_SPEED_AUTONEG_1G_FALLBACK;
                break;

            case PORT_HW_CFG_DEFAULT_LINK_AN_2_5G_FALLBACK:
                speed = LM_MEDIUM_SPEED_AUTONEG_2_5G_FALLBACK;
                break;

            default:
                speed = LM_MEDIUM_SPEED_UNKNOWN;
        }
    }

    (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_CTRL_REG, PHY_CTRL_PHY_RESET);
    for(cnt = 0; cnt < 1000; cnt++)
    {
        mm_wait(pdev, 5);

        (void) lm_mread(pdev, pdev->params.phy_addr, PHY_CTRL_REG, &val);

        if(!(val & PHY_CTRL_PHY_RESET))
        {
            mm_wait(pdev, 5);

            break;
        }
    }

    DbgBreakIf(val & PHY_CTRL_PHY_RESET);

    /* Get the PHY id. */
    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_ID1_REG, &val);
    pdev->hw_info.phy_id = val << 16;
    DbgMessage1(pdev, INFORM, "Phy Id1 0x%x\n", val);

    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_ID2_REG, &val);
    pdev->hw_info.phy_id |= val & 0xffff;
    DbgMessage1(pdev, INFORM, "Phy Id2 0x%x\n", val);

    DbgBreakIf((pdev->hw_info.phy_id & 0x0fffffff) == 0x0fffffff ||
        pdev->hw_info.phy_id == 0);

    /* The 5706S has problem determining link so getting link from
     * the MII status register is not reliable.  This will force
     * the MAC to qualify the link ready signal with signal detect.
     * We will need to get the link status from the MAC instead of
     * the SERDES (MII status register). */
    if(CHIP_NUM(pdev) == CHIP_NUM_5706 &&
        lm_get_medium(pdev) == LM_MEDIUM_TYPE_FIBER)
    {
        REG_WR(pdev, misc.misc_gp_hw_ctl0,
                MISC_GP_HW_CTL0_ENA_SEL_VAUX_B_IN_L2_TE |
                MISC_GP_HW_CTL0_GRC_BNK_FREE_FIX_TE);
    }

    /* Setup flow control capabilities advertisement. */
    val = PHY_AN_AD_1000X_HALF_DUPLEX;
    if(duplex == LM_MEDIUM_FULL_DUPLEX)
    {
        val |= PHY_AN_AD_1000X_FULL_DUPLEX;
    }
    val |= phy_ad_settings(pdev, req_medium, flow_ctrl);

    (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_AN_AD_REG, val);

    /* Determine the fallback selection. */
    switch(speed)
    {
        case LM_MEDIUM_SPEED_AUTONEG_1G_FALLBACK:
            DbgMessage(pdev, INFORM, "enable serdes_fallback_1g\n");
            pdev->vars.serdes_fallback_select = SERDES_FALLBACK_1G;
            break;

        case LM_MEDIUM_SPEED_AUTONEG_2_5G_FALLBACK:
            DbgMessage(pdev, INFORM, "enable serdes_fallback_2.5g\n");
            pdev->vars.serdes_fallback_select = SERDES_FALLBACK_2_5G;
            break;

        default:
            DbgMessage(pdev, INFORM, "disable serdes_fallback.\n");
            pdev->vars.serdes_fallback_select = SERDES_FALLBACK_NONE;
            pdev->vars.serdes_fallback_status = SERDES_FALLBACK_NONE;
            break;
    }

    /* This routine could be called anytime.  So if has not gone down
     * yet, we want to perserve the fallback setting. */
    if(pdev->vars.serdes_fallback_select != SERDES_FALLBACK_NONE)
    {
        speed = LM_MEDIUM_SPEED_AUTONEG;

        if(pdev->vars.link_status == LM_STATUS_LINK_ACTIVE)
        {
            if(pdev->vars.serdes_fallback_status == SERDES_FALLBACK_1G)
            {
                speed = LM_MEDIUM_SPEED_1000MBPS;
            }
            else if(pdev->vars.serdes_fallback_status == SERDES_FALLBACK_2_5G)
            {
                speed = LM_MEDIUM_SPEED_2500MBPS;
            }
        }
    }

    if(speed == LM_MEDIUM_SPEED_1000MBPS)
    {
        val = PHY_CTRL_SPEED_SELECT_1000MBPS;
        if(duplex == LM_MEDIUM_FULL_DUPLEX)
        {
            val |= PHY_CTRL_FULL_DUPLEX_MODE;
        }

        (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_CTRL_REG, val);
    }
    else
    {
        val = PHY_CTRL_AUTO_NEG_ENABLE | PHY_CTRL_RESTART_AUTO_NEG;

        (void) lm_mwrite(pdev, pdev->params.phy_addr, PHY_CTRL_REG, val);

        speed = LM_MEDIUM_SPEED_AUTONEG;
    }

    /* exit mac loopback.  we could be in mac loopback mode if previously
     * the upper module calls lm_init_phy with LM_MEDIUM_TYPE_MAC_LOOPBACK
     * medium type for diagnostic. */
    REG_RD(pdev, emac.emac_mode, &val);
    val &= ~(EMAC_MODE_MAC_LOOP | EMAC_MODE_FORCE_LINK);
    REG_WR(pdev, emac.emac_mode, val);

    /* Configure the PHY for jumbo frame. */
    if(pdev->params.mtu > MAX_ETHERNET_PACKET_SIZE)
    {
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, 0x4400);
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1c, 0xec87);
    }
    else
    {
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x18, 0x0400);
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1c, 0xec85);
    }

    SET_MEDIUM_TYPE(pdev->vars.medium, LM_MEDIUM_TYPE_FIBER);
    SET_MEDIUM_SPEED(pdev->vars.medium, speed);
    SET_MEDIUM_DUPLEX(pdev->vars.medium, duplex);

    pdev->vars.cable_is_attached = FALSE;

    /* Clear the latch bits.  The second read below will get the
     * current status. */
    val = mii_get_serdes_link_status(pdev);

    /* Wait for link only if the cable is connected. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1c, 0x7c00);
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x1c, &val);
    if(val & 0x10)
    {
        for(; ;)
        {
            val = mii_get_serdes_link_status(pdev);

            if(val & PHY_STATUS_LINK_PASS)
            {
                break;
            }

            mm_wait(pdev, 10);

            if(wait_link_timeout_us <= 10)
            {
                break;
            }

            wait_link_timeout_us -= 10;
        }

        pdev->vars.cable_is_attached = TRUE;
    }

    /* Need to read a second time to get the current link status. */
    val = mii_get_serdes_link_status(pdev);

    if(val & PHY_STATUS_LINK_PASS)
    {
        pdev->vars.link_status = LM_STATUS_LINK_ACTIVE;
        DbgMessage(pdev, INFORM, "phy init link up\n");
    }
    else
    {
        pdev->vars.link_status = LM_STATUS_LINK_DOWN;
        DbgMessage(pdev, INFORM, "phy init link down\n");
    }

    return LM_STATUS_SUCCESS;
} /* init_5706_serdes */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC void
init_serdes_or_phy_loopback(
    lm_device_t *pdev)
{
    u32_t cnt;
    u32_t val;

    (void) lm_mwrite(
        pdev,
        pdev->params.phy_addr,
        PHY_CTRL_REG,
        PHY_CTRL_PHY_RESET);
    for(cnt = 0; cnt < 1000; cnt++)
    {
        mm_wait(pdev, 5);

        (void) lm_mread(pdev, pdev->params.phy_addr, PHY_CTRL_REG, &val);

        if(!(val & PHY_CTRL_PHY_RESET))
        {
            mm_wait(pdev, 5);
            break;
        }
    }

    DbgBreakIf(val & PHY_CTRL_PHY_RESET);

    /* Get the PHY id. */
    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_ID1_REG, &val);
    pdev->hw_info.phy_id = val << 16;
    DbgMessage1(pdev, INFORM, "Phy Id1 0x%x\n", val);

    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_ID2_REG, &val);
    pdev->hw_info.phy_id |= val & 0xffff;
    DbgMessage1(pdev, INFORM, "Phy Id2 0x%x\n", val);

    DbgBreakIf((pdev->hw_info.phy_id & 0x0fffffff) == 0x0fffffff ||
        pdev->hw_info.phy_id == 0);

    REG_WR(pdev, emac.emac_tx_lengths, 0x26ff);

    /* Set the phy into loopback mode. */
    (void) lm_mwrite(
        pdev,
        pdev->params.phy_addr,
        PHY_CTRL_REG,
        PHY_CTRL_LOOPBACK_MODE |
            PHY_CTRL_FULL_DUPLEX_MODE |
            PHY_CTRL_SPEED_SELECT_1000MBPS);
} /* init_serdes_or_phy_loopback */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC void
init_5709_serdes_loopback(
    lm_device_t *pdev)
{
    u32_t val;

    /*
     * reset causes the speed not be back to 2.5g intermittently
     * after phy lookback test when connecting to a switch.
     */
#if 0
    /* select combo_ieee0 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffe0);

    /* reset. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, MII_CTRL_RESET);
    for(idx = 0; idx < 1000; idx++)
    {
        mm_wait(pdev, 5);

        (void) lm_mread(pdev, pdev->params.phy_addr, 0x10, &val);
        if(!(val & MII_CTRL_RESET))
        {
            mm_wait(pdev, 5);
            break;
        }
    }
    DbgBreakIf(val & MII_CTRL_RESET);
#endif

    /* set an_mmd.  an_mmd is the only register set we need for
     * programming xinan serdes.  all other registers are can
     * be access through an_mmd. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffd0);
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1e, 0x3800);

    /* get phy id. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x12, &val);
    pdev->hw_info.phy_id = val << 16;
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x13, &val);
    pdev->hw_info.phy_id |= val & 0xffff;

    /* select combo_ieee0 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffe0);

    /*CQ31687:set autoneg_enable bit too.*/
    /* Set the phy into loopback mode. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x10, 0x5140);

} /* init_5709_serdes_loopback */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
init_loopback_mac_link(
    lm_device_t *pdev,
    lm_medium_t req_medium,
    lm_flow_control_t flow_ctrl)
{
    lm_status_t lm_status;
    u32_t val;

    lm_status = LM_STATUS_SUCCESS;

    if(GET_MEDIUM_TYPE(req_medium) == LM_MEDIUM_TYPE_PHY_LOOPBACK)
    {
        if(CHIP_NUM(pdev) == CHIP_NUM_5709 &&
            lm_get_medium(pdev) == LM_MEDIUM_TYPE_FIBER)
        {
            init_5709_serdes_loopback(pdev);
        }
        else
        {
            init_serdes_or_phy_loopback(pdev);
        }

        REG_WR(pdev, emac.emac_tx_lengths, 0x26ff);

        REG_RD(pdev, emac.emac_mode, &val);
        val &= ~(EMAC_MODE_MAC_LOOP | EMAC_MODE_PORT);
        val |= EMAC_MODE_FORCE_LINK | EMAC_MODE_PORT_GMII;
        REG_WR(pdev, emac.emac_mode, val);

        SET_MEDIUM_TYPE(pdev->vars.medium, LM_MEDIUM_TYPE_PHY_LOOPBACK);
        SET_MEDIUM_SPEED(pdev->vars.medium, LM_MEDIUM_SPEED_UNKNOWN);
        SET_MEDIUM_DUPLEX(pdev->vars.medium, LM_MEDIUM_FULL_DUPLEX);

        /* Save current link status. */
        pdev->vars.link_status = LM_STATUS_LINK_ACTIVE;

        pdev->vars.cable_is_attached = TRUE;
    }
    else if(GET_MEDIUM_TYPE(req_medium) == LM_MEDIUM_TYPE_MAC_LOOPBACK)
    {
        DbgMessage(pdev, INFORM, "Set up MAC loopback mode.\n");

        /* Set the MAC into loopback mode.  Mac loopback will intermittenly
         * fail if half_duplex bit is set.  CQ#24594. */
        REG_RD(pdev, emac.emac_mode, &val);
        val &= ~(EMAC_MODE_PORT | EMAC_MODE_HALF_DUPLEX);
        val |= EMAC_MODE_MAC_LOOP | EMAC_MODE_FORCE_LINK;

        /* The port mode must be set to none on the real chip. */
        if(CHIP_REV(pdev) == CHIP_REV_FPGA)
        {
            val |= EMAC_MODE_PORT_GMII;
        }

        REG_WR(pdev, emac.emac_mode, val);

        SET_MEDIUM_TYPE(pdev->vars.medium, LM_MEDIUM_TYPE_MAC_LOOPBACK);
        SET_MEDIUM_SPEED(pdev->vars.medium, LM_MEDIUM_SPEED_UNKNOWN);
        SET_MEDIUM_DUPLEX(pdev->vars.medium, LM_MEDIUM_FULL_DUPLEX);

        /* Save current link status. */
        pdev->vars.link_status = LM_STATUS_LINK_ACTIVE;

        pdev->vars.cable_is_attached = TRUE;
    }
    else
    {
        DbgBreakMsg("Not loopback medium type.\n");

        lm_status = LM_STATUS_FAILURE;

        /* Save current link status. */
        pdev->vars.link_status = LM_STATUS_LINK_DOWN;

        pdev->vars.cable_is_attached = FALSE;
    }

    /* Enable status block link attention. */
    REG_RD(pdev, hc.hc_attn_bits_enable, &val);
    val |= STATUS_ATTN_BITS_LINK_STATE;
    REG_WR(pdev, hc.hc_attn_bits_enable, val);

    return lm_status;
} /* init_loopback_mac_link */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
init_null_phy(
    lm_device_t *pdev,
    lm_medium_t req_medium,
    lm_flow_control_t flow_ctrl,
    u32_t wait_link_timeout_us)
{
    DbgMessage(pdev, INFORM, "### init_null_phy\n");

    if(GET_MEDIUM_TYPE(req_medium) != LM_MEDIUM_TYPE_NULL)
    {
        return LM_STATUS_INVALID_PARAMETER;
    }

    /* Save current medium settings. */
    SET_MEDIUM_TYPE(pdev->vars.medium, LM_MEDIUM_TYPE_NULL);
    SET_MEDIUM_SPEED(pdev->vars.medium, LM_MEDIUM_SPEED_1000MBPS);
    SET_MEDIUM_DUPLEX(pdev->vars.medium, LM_MEDIUM_FULL_DUPLEX);

    pdev->vars.cable_is_attached = TRUE;

    return LM_STATUS_SUCCESS;
} /* init_null_phy */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC u32_t
netlink_pause_ad(
    lm_flow_control_t flow_ctrl)
{
    u32_t pause_ad;

    pause_ad = 0;

    if((flow_ctrl == LM_FLOW_CONTROL_AUTO_PAUSE) ||
        ((flow_ctrl & LM_FLOW_CONTROL_RECEIVE_PAUSE) &&
        (flow_ctrl & LM_FLOW_CONTROL_TRANSMIT_PAUSE)))
    {
        pause_ad |= NETLINK_DRV_SET_LINK_FC_SYM_PAUSE |
                    NETLINK_DRV_SET_LINK_FC_ASYM_PAUSE;
    }
    else if(flow_ctrl & LM_FLOW_CONTROL_TRANSMIT_PAUSE)
    {
        pause_ad |= NETLINK_DRV_SET_LINK_FC_ASYM_PAUSE;
    }
    else if(flow_ctrl & LM_FLOW_CONTROL_RECEIVE_PAUSE)
    {
        pause_ad |= NETLINK_DRV_SET_LINK_FC_SYM_PAUSE |
                    NETLINK_DRV_SET_LINK_FC_ASYM_PAUSE;
    }

    return pause_ad;
} /* netlink_pause_ad */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC u32_t
link_setting_to_netlink(
    lm_link_settings_t *link_settings,
    u32_t serdes)
{
    lm_medium_t duplex;
    lm_medium_t speed;
    u32_t netlink;

    speed = GET_MEDIUM_SPEED(link_settings->req_medium);
    duplex = GET_MEDIUM_DUPLEX(link_settings->req_medium);
    netlink = 0;

    switch(speed)
    {
        case LM_MEDIUM_SPEED_10MBPS:
            if(duplex == LM_MEDIUM_FULL_DUPLEX)
            {
                netlink |= NETLINK_DRV_SET_LINK_SPEED_10FULL;

                if((link_settings->flag & LINK_FLAG_SELECTIVE_AUTONEG_MASK) ==
                    LINK_FLAG_SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
                {
                    netlink |= NETLINK_DRV_SET_LINK_SPEED_10HALF;
                }
            }
            else
            {
                netlink |= NETLINK_DRV_SET_LINK_SPEED_10HALF;
            }
            break;

        case LM_MEDIUM_SPEED_100MBPS:
            if((link_settings->flag & LINK_FLAG_SELECTIVE_AUTONEG_MASK) ==
                LINK_FLAG_SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
            {
                netlink |= NETLINK_DRV_SET_LINK_SPEED_10FULL;
                netlink |= NETLINK_DRV_SET_LINK_SPEED_10HALF;
            }

            if(duplex == LM_MEDIUM_FULL_DUPLEX)
            {
                netlink |= NETLINK_DRV_SET_LINK_SPEED_100FULL;

                if((link_settings->flag & LINK_FLAG_SELECTIVE_AUTONEG_MASK) ==
                    LINK_FLAG_SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
                {
                    netlink |= NETLINK_DRV_SET_LINK_SPEED_100HALF;
                }
            }
            else
            {
                netlink |= NETLINK_DRV_SET_LINK_SPEED_100HALF;
            }
            break;

        case LM_MEDIUM_SPEED_1000MBPS:
            if((link_settings->flag & LINK_FLAG_SELECTIVE_AUTONEG_MASK) ==
                LINK_FLAG_SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
            {
                netlink |= NETLINK_DRV_SET_LINK_SPEED_10FULL;
                netlink |= NETLINK_DRV_SET_LINK_SPEED_10HALF;
                netlink |= NETLINK_DRV_SET_LINK_SPEED_100FULL;
                netlink |= NETLINK_DRV_SET_LINK_SPEED_100HALF;
            }

            if(duplex == LM_MEDIUM_FULL_DUPLEX)
            {
                netlink |= NETLINK_DRV_SET_LINK_SPEED_1GFULL;

                if((link_settings->flag & LINK_FLAG_SELECTIVE_AUTONEG_MASK) ==
                    LINK_FLAG_SELECTIVE_AUTONEG_ENABLE_SLOWER_SPEEDS)
                {
                    netlink |= NETLINK_DRV_SET_LINK_SPEED_1GHALF;
                }
            }
            else
            {
                netlink |= NETLINK_DRV_SET_LINK_SPEED_1GHALF;
            }
            break;

        default:
            if (serdes)
            {
                netlink |= NETLINK_DRV_SET_LINK_ENABLE_AUTONEG |
                    NETLINK_DRV_SET_LINK_SPEED_1GHALF |
                    NETLINK_DRV_SET_LINK_SPEED_1GFULL;
            }
            else
            {
                netlink |= NETLINK_DRV_SET_LINK_ENABLE_AUTONEG |
                    NETLINK_DRV_SET_LINK_SPEED_10HALF |
                    NETLINK_DRV_SET_LINK_SPEED_10FULL |
                    NETLINK_DRV_SET_LINK_SPEED_100HALF |
                    NETLINK_DRV_SET_LINK_SPEED_100FULL |
                    NETLINK_DRV_SET_LINK_SPEED_1GHALF |
                    NETLINK_DRV_SET_LINK_SPEED_1GFULL;
            }
            break;
    }

    netlink |= NETLINK_DRV_SET_LINK_PHY_RESET;

    if(link_settings->flag & LINK_FLAG_SELECTIVE_AUTONEG_MASK)
    {
        netlink |= NETLINK_DRV_SET_LINK_ENABLE_AUTONEG;
    }

    if(link_settings->flag & LINK_FLAG_WIRE_SPEED)
    {
        netlink |= NETLINK_DRV_SET_LINK_ETH_AT_WIRESPEED_ENABLE;
    }

    netlink |= netlink_pause_ad(link_settings->flow_ctrl);

    return netlink;
} /* link_settings_to_netlink */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
lm_status_t
lm_init_remote_phy(
    lm_device_t *pdev,
    lm_link_settings_t *serdes_link,
    lm_link_settings_t *rphy_link)
{
    u32_t serdes_netlink;
    u32_t rphy_netlink;
    u32_t set_link_arg;
    u32_t val;

    DbgBreakIf(pdev->params.enable_remote_phy == FALSE);

    serdes_netlink = link_setting_to_netlink(serdes_link, TRUE);
    rphy_netlink = link_setting_to_netlink(rphy_link, FALSE);

    REG_WR_IND(
        pdev,
        pdev->hw_info.shmem_base +
            OFFSETOF(shmem_region_t, remotephy.serdes_link_pref),
        serdes_netlink);

    REG_WR_IND(
        pdev,
        pdev->hw_info.shmem_base +
            OFFSETOF(shmem_region_t, remotephy.copper_phy_link_pref),
        rphy_netlink | NETLINK_DRV_SET_LINK_PHY_APP_REMOTE);

    REG_RD_IND(
        pdev,
        pdev->hw_info.shmem_base +
            OFFSETOF(shmem_region_t, drv_fw_mb.link_status),
        &val);
    if(val & NETLINK_GET_LINK_STATUS_SERDES_LINK)
    {
        set_link_arg = serdes_netlink;
    }
    else
    {
        set_link_arg = rphy_netlink | NETLINK_DRV_SET_LINK_PHY_APP_REMOTE;
    }

    REG_WR_IND(
        pdev,
        pdev->hw_info.shmem_base +
            OFFSETOF(shmem_region_t, drv_fw_mb.mb_args[0]),
        set_link_arg);

    (void) lm_submit_fw_cmd(pdev, DRV_MSG_CODE_CMD_SET_LINK);

    return LM_STATUS_SUCCESS;
} /* lm_init_remote_phy */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
lm_status_t
lm_init_phy(
    lm_device_t *pdev,
    lm_medium_t req_medium,
    lm_flow_control_t flow_ctrl,
    u32_t selective_autoneg,
    u32_t wire_speed,
    u32_t wait_link_timeout_us)
{
    lm_status_t lm_status;

    DbgBreakIf(pdev->params.enable_remote_phy);

    if(GET_MEDIUM_AUTONEG_MODE(req_medium) == LM_MEDIUM_SELECTIVE_AUTONEG)
    {
        selective_autoneg = TRUE;
    }

    if(GET_MEDIUM_TYPE(req_medium) == LM_MEDIUM_AUTO_DETECT)
    {
        if(CHIP_REV(pdev) == CHIP_REV_IKOS)
        {
            req_medium = LM_MEDIUM_TYPE_NULL;
        }
        else if(CHIP_REV(pdev) == CHIP_REV_FPGA)
        {
            selective_autoneg = TRUE;
            req_medium = LM_MEDIUM_TYPE_UTP |
                LM_MEDIUM_SPEED_10MBPS |
                LM_MEDIUM_FULL_DUPLEX;
        }
        else if(lm_get_medium(pdev) == LM_MEDIUM_TYPE_FIBER)
        {
            if(req_medium == LM_MEDIUM_AUTO_DETECT)
            {
                req_medium = LM_MEDIUM_TYPE_FIBER;
            }
            else
            {
                SET_MEDIUM_TYPE(req_medium, LM_MEDIUM_TYPE_FIBER);
            }
        }
        else
        {
            if(req_medium == LM_MEDIUM_AUTO_DETECT)
            {
                req_medium = LM_MEDIUM_TYPE_UTP;
            }
            else
            {
                SET_MEDIUM_TYPE(req_medium, LM_MEDIUM_TYPE_UTP);
            }
        }
    }

    switch(GET_MEDIUM_TYPE(req_medium))
    {
        case LM_MEDIUM_TYPE_UTP:
            lm_status = init_utp(
                pdev,
                req_medium,
                flow_ctrl,
                selective_autoneg,
                wire_speed,
                wait_link_timeout_us);
            break;

        case LM_MEDIUM_TYPE_FIBER:
            DbgBreakIf(CHIP_NUM(pdev) != CHIP_NUM_5706 &&
                       CHIP_NUM(pdev) != CHIP_NUM_5708 &&
                       CHIP_NUM(pdev) != CHIP_NUM_5709);

            if(CHIP_NUM(pdev) == CHIP_NUM_5706)
            {
                lm_status = init_5706_serdes(
                    pdev,
                    req_medium,
                    flow_ctrl,
                    wait_link_timeout_us);
            }
            else if(CHIP_NUM(pdev) == CHIP_NUM_5708)
            {
                lm_status = init_5708_serdes(
                    pdev,
                    req_medium,
                    flow_ctrl,
                    selective_autoneg,
                    wait_link_timeout_us);
            }
            else
            {
                lm_status = init_5709_serdes(
                    pdev,
                    req_medium,
                    flow_ctrl,
                    selective_autoneg,
                    wait_link_timeout_us);
            }

            break;

        case LM_MEDIUM_TYPE_NULL:
            lm_status = init_null_phy(
                pdev,
                req_medium,
                flow_ctrl,
                wait_link_timeout_us);
            break;

        case LM_MEDIUM_TYPE_PHY_LOOPBACK:
        case LM_MEDIUM_TYPE_MAC_LOOPBACK:
            lm_status = init_loopback_mac_link(
                pdev,
                req_medium,
                flow_ctrl);
            break;

        default:
            lm_status = LM_STATUS_UNKNOWN_MEDIUM;
            break;
    }

    return lm_status;
} /* lm_init_phy */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC void
get_serdes_phy_ad(
    lm_device_t *pdev,
    u32_t *local_phy_ad,
    u32_t *remote_phy_ad)
{
    u32_t val;

    *local_phy_ad = 0;
    *remote_phy_ad = 0;

    if(CHIP_NUM(pdev) == CHIP_NUM_5706 || CHIP_NUM(pdev) == CHIP_NUM_5708)
    {
        (void) lm_mread(pdev, pdev->params.phy_addr, PHY_AN_AD_REG, &val);

        if(val & PHY_AN_AD_1000X_PAUSE_CAPABLE)
        {
            *local_phy_ad |= PHY_AN_AD_PAUSE_CAPABLE;
        }

        if(val & PHY_AN_AD_1000X_ASYM_PAUSE)
        {
            *local_phy_ad |= PHY_AN_AD_ASYM_PAUSE;
        }

        (void) lm_mread(pdev,pdev->params.phy_addr,PHY_LINK_PARTNER_ABILITY_REG,&val);

        if(val & PHY_AN_AD_1000X_PAUSE_CAPABLE)
        {
            *remote_phy_ad |= PHY_AN_AD_PAUSE_CAPABLE;
        }

        if(val & PHY_AN_AD_1000X_ASYM_PAUSE)
        {
            *remote_phy_ad |= PHY_AN_AD_ASYM_PAUSE;
        }
    }
    else
    {
        /* select combo ieee0 block. */
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffe0);

        /* local advertisement. */
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x14, &val);

        if(val & 0x80)
        {
            *local_phy_ad |= PHY_AN_AD_PAUSE_CAPABLE;
        }

        if(val & 0x100)
        {
            *local_phy_ad |= PHY_AN_AD_ASYM_PAUSE;
        }

        /* remote advertisement. */
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x15, &val);

        if(val & 0x80)
        {
            *remote_phy_ad |= PHY_AN_AD_PAUSE_CAPABLE;
        }

        if(val & 0x100)
        {
            *remote_phy_ad |= PHY_AN_AD_ASYM_PAUSE;
        }
    }
} /* get_serdes_phy_ad */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_flow_control_t
set_mac_flow_control(
    lm_device_t *pdev,
    lm_medium_t medium,
    lm_flow_control_t flow_control_cap)
{
    lm_flow_control_t flow_ctrl;
    u32_t remote_phy_link;
    u32_t remote_phy_ad;
    u32_t local_phy_ad;
    u32_t val;
    lm_rx_chain_t *rxq;
    u32_t idx;

    if(pdev->params.enable_remote_phy)
    {
        local_phy_ad = 0;

        if((flow_control_cap == LM_FLOW_CONTROL_AUTO_PAUSE) ||
            ((flow_control_cap & LM_FLOW_CONTROL_RECEIVE_PAUSE) &&
            (flow_control_cap & LM_FLOW_CONTROL_TRANSMIT_PAUSE)) ||
            (flow_control_cap & LM_FLOW_CONTROL_RECEIVE_PAUSE))
        {
            local_phy_ad |= (PHY_AN_AD_PAUSE_CAPABLE | PHY_AN_AD_ASYM_PAUSE);
        }
        else if(flow_control_cap & LM_FLOW_CONTROL_TRANSMIT_PAUSE)
        {
            local_phy_ad |= PHY_AN_AD_ASYM_PAUSE;
        }

        remote_phy_ad = 0;

        REG_RD_IND(
            pdev,
            pdev->hw_info.shmem_base +
                OFFSETOF(shmem_region_t, drv_fw_mb.link_status),
            &remote_phy_link);

        if(remote_phy_link & NETLINK_GET_LINK_STATUS_PARTNER_SYM_PAUSE_CAP)
        {
            remote_phy_ad |= PHY_LINK_PARTNER_PAUSE_CAPABLE;
        }

        if(remote_phy_link & NETLINK_GET_LINK_STATUS_PARTNER_ASYM_PAUSE_CAP)
        {
            remote_phy_ad |= PHY_LINK_PARTNER_ASYM_PAUSE;
        }
    }
    else
    {
        if(GET_MEDIUM_TYPE(medium) == LM_MEDIUM_TYPE_FIBER)
        {
            get_serdes_phy_ad(pdev, &local_phy_ad, &remote_phy_ad);
        }
        else
        {
            (void) lm_mread(
                pdev,
                pdev->params.phy_addr,
                PHY_AN_AD_REG,
                &local_phy_ad);

            (void) lm_mread(
                pdev,
                pdev->params.phy_addr,
                PHY_LINK_PARTNER_ABILITY_REG,
                &remote_phy_ad);
        }
    }

    DbgMessage(pdev, INFORM, "Local flow control settings.\n");

    if(local_phy_ad & PHY_AN_AD_PAUSE_CAPABLE)
    {
        DbgMessage(pdev, INFORM, "   PAUSE capable.\n");
    }

    if(local_phy_ad & PHY_AN_AD_ASYM_PAUSE)
    {
        DbgMessage(pdev, INFORM, "   ASYM_PAUSE capable.\n");
    }

    DbgMessage(pdev, INFORM, "Remote flow control settings.\n");

    if(remote_phy_ad & PHY_LINK_PARTNER_PAUSE_CAPABLE)
    {
        DbgMessage(pdev, INFORM, "   PAUSE capable.\n");
    }

    if(remote_phy_ad & PHY_LINK_PARTNER_ASYM_PAUSE)
    {
        DbgMessage(pdev, INFORM, "   ASYM_PAUSE capable.\n");
    }

    /* Resultant flow control setting. */
    flow_ctrl = LM_FLOW_CONTROL_NONE;

    if((flow_control_cap & LM_FLOW_CONTROL_AUTO_PAUSE) ||
        pdev->params.flow_control_reporting_mode)
    {
        /* See Table 28B-3 of 802.3ab-1999 spec. */
        if(local_phy_ad & PHY_AN_AD_PAUSE_CAPABLE)
        {
            if(local_phy_ad & PHY_AN_AD_ASYM_PAUSE)
            {
                if(remote_phy_ad & PHY_LINK_PARTNER_PAUSE_CAPABLE)
                {
                    DbgMessage(pdev, INFORM, "FlowCap: tx/rx\n");

                    flow_ctrl =
                        LM_FLOW_CONTROL_TRANSMIT_PAUSE |
                        LM_FLOW_CONTROL_RECEIVE_PAUSE;
                }
                else if(remote_phy_ad & PHY_LINK_PARTNER_ASYM_PAUSE)
                {
                    DbgMessage(pdev, INFORM, "FlowCap: rx PAUSE\n");

                    flow_ctrl = LM_FLOW_CONTROL_RECEIVE_PAUSE;
                }
            }
            else
            {
                if(remote_phy_ad & PHY_LINK_PARTNER_PAUSE_CAPABLE)
                {
                    DbgMessage(pdev, INFORM, "FlowCap: tx/rx\n");

                    flow_ctrl =
                        LM_FLOW_CONTROL_TRANSMIT_PAUSE |
                        LM_FLOW_CONTROL_RECEIVE_PAUSE;
                }
            }
        }
        else if(local_phy_ad & PHY_AN_AD_ASYM_PAUSE)
        {
            if((remote_phy_ad & PHY_LINK_PARTNER_PAUSE_CAPABLE) &&
                (remote_phy_ad & PHY_LINK_PARTNER_ASYM_PAUSE))
            {
                DbgMessage(pdev, INFORM, "FlowCap: tx PAUSE\n");

                flow_ctrl = LM_FLOW_CONTROL_TRANSMIT_PAUSE;
            }
        }
    }
    else
    {
        flow_ctrl = flow_control_cap;
    }

    DbgMessage(pdev, INFORM, "Flow control capabilities.\n");

    if(flow_ctrl & LM_FLOW_CONTROL_TRANSMIT_PAUSE)
    {
        DbgMessage(pdev, INFORM, "   tx PAUSE\n");
    }

    if(flow_ctrl & LM_FLOW_CONTROL_RECEIVE_PAUSE)
    {
        DbgMessage(pdev, INFORM, "   rx PAUSE\n");
    }

    if(flow_ctrl == LM_FLOW_CONTROL_NONE)
    {
        DbgMessage(pdev, INFORM, "   none.\n");
    }

    /* Enable/disable rx PAUSE. */
    REG_RD(pdev, emac.emac_rx_mode, &val);
    val &= ~EMAC_RX_MODE_FLOW_EN;

    if(flow_ctrl & LM_FLOW_CONTROL_RECEIVE_PAUSE)
    {
        val |= EMAC_RX_MODE_FLOW_EN;
        DbgMessage(pdev, INFORM, "Enable rx PAUSE.\n");
    }
    REG_WR(pdev, emac.emac_rx_mode, val);

    /* Enable/disable tx PAUSE. */
    REG_RD(pdev, emac.emac_tx_mode, &val);
    val &= ~EMAC_TX_MODE_FLOW_EN;

    if(flow_ctrl & LM_FLOW_CONTROL_TRANSMIT_PAUSE)
    {
        val |= EMAC_TX_MODE_FLOW_EN;
        DbgMessage(pdev, INFORM, "Enable tx PAUSE.\n");
    }
    REG_WR(pdev, emac.emac_tx_mode, val);

    for(idx = 0; idx < pdev->rx_info.num_rxq; idx++)
    {
        rxq = &pdev->rx_info.chain[idx];
        val = CTX_RD(
            pdev,
            rxq->cid_addr,
            WORD_ALIGNED_OFFSETOF(l2_bd_chain_context_t, l2ctx_ctx_type));

        /* Enable/disable RV2P wait (i.e. watermark field) for buffer post based on flow control setting. */
        if(flow_ctrl & LM_FLOW_CONTROL_TRANSMIT_PAUSE)
        {
            val |= 0xFF;
        }
        else
        {
            // RV2P is checking for non-zero in this byte field
            val &= ~0xFF;
        }
        CTX_WR(
            pdev,
            rxq->cid_addr,
            WORD_ALIGNED_OFFSETOF(l2_bd_chain_context_t, l2ctx_ctx_type),
            val);
    }

    return flow_ctrl;
} /* set_mac_flow_control */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
get_copper_phy_link(
    lm_device_t *pdev,
    lm_medium_t *medium)
{
    lm_medium_t duplex;
    lm_medium_t speed;
    lm_status_t link;
    u32_t phy_status;
    u32_t remote_adv;
    u32_t local_adv;
    u32_t phy_ctrl;
    u32_t val;

    DbgMessage(pdev, INFORM, "### get_copper_phy_link\n");

    *medium = LM_MEDIUM_TYPE_UTP |
        LM_MEDIUM_SPEED_UNKNOWN |
        LM_MEDIUM_FULL_DUPLEX;

    pdev->vars.cable_is_attached = FALSE;

    /* Check for link.  The first read returns the latched value, the
     * second read returns the current value. */
    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_STATUS_REG, &phy_status);
    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_STATUS_REG, &phy_status);
    if((phy_status & PHY_STATUS_LINK_PASS) == 0)
    {
        DbgMessage(pdev, INFORM, "link down.\n");

        if(CHIP_REV(pdev) != CHIP_REV_FPGA)
        {
            (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1c, 0x7c00);
            (void) lm_mread(pdev, pdev->params.phy_addr, 0x1c, &val);
            if(val & 0x20)
            {
                pdev->vars.cable_is_attached = TRUE;
            }
        }

        return LM_STATUS_LINK_DOWN;
    }

    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_CTRL_REG, &phy_ctrl);

    /* Make sure the PHY control register is valid. */
    DbgBreakIf(phy_ctrl & (
        PHY_CTRL_COLLISION_TEST_ENABLE |
        PHY_CTRL_RESTART_AUTO_NEG |
        PHY_CTRL_ISOLATE_PHY |
        PHY_CTRL_LOOPBACK_MODE |
        PHY_CTRL_PHY_RESET));

    link = LM_STATUS_LINK_ACTIVE;
    pdev->vars.cable_is_attached = TRUE;

    /* Determine duplex mode.  Link is present also means autoneg is done. */
    if(phy_ctrl & PHY_CTRL_AUTO_NEG_ENABLE)
    {
        /* Autonegotiation is enabled.  And since we have link, we know
         * autonegotiation has completed.
         *
         * Infer the link speed by figuring out the highest common speed
         * between us and our link partner. */

        /* Get local and remote 1000BASET advertisement. */
        (void) lm_mread(
                pdev,
                pdev->params.phy_addr,
                PHY_1000BASET_CTRL_REG,
                &local_adv);
        (void) lm_mread(
                pdev,
                pdev->params.phy_addr,
                PHY_1000BASET_STATUS_REG,
                &remote_adv);

        val = local_adv & (remote_adv >> 2);
        if(val & PHY_AN_AD_1000BASET_FULL)
        {
            DbgMessage(pdev, INFORM, "detected 1gb full autoneg.\n");

            speed = LM_MEDIUM_SPEED_1000MBPS;
            duplex = LM_MEDIUM_FULL_DUPLEX;
        }
        else if(val & PHY_AN_AD_1000BASET_HALF)
        {
            DbgMessage(pdev, INFORM, "detected 1gb half autoneg.\n");

            speed = LM_MEDIUM_SPEED_1000MBPS;
            duplex = LM_MEDIUM_HALF_DUPLEX;
        }
        else
        {
            /* Get local and remote 10/100 mb advertisement. */
            (void) lm_mread(
                    pdev,
                    pdev->params.phy_addr,
                    PHY_AN_AD_REG,
                    &local_adv);

            (void) lm_mread(
                    pdev,
                    pdev->params.phy_addr,
                    PHY_LINK_PARTNER_ABILITY_REG,
                    &remote_adv);

            val = local_adv & remote_adv;
            if(val & PHY_AN_AD_100BASETX_FULL)
            {
                DbgMessage(pdev, INFORM, "detected 100mb full autoneg.\n");

                speed = LM_MEDIUM_SPEED_100MBPS;
                duplex = LM_MEDIUM_FULL_DUPLEX;
            }
            else if(val & PHY_AN_AD_100BASETX_HALF)
            {
                DbgMessage(pdev, INFORM, "detected 100mb half autoneg.\n");

                speed = LM_MEDIUM_SPEED_100MBPS;
                duplex = LM_MEDIUM_HALF_DUPLEX;
            }
            else if(val & PHY_AN_AD_10BASET_FULL)
            {
                DbgMessage(pdev, INFORM, "detected 10mb full autoneg.\n");

                speed = LM_MEDIUM_SPEED_10MBPS;
                duplex = LM_MEDIUM_FULL_DUPLEX;
            }
            else if(val & PHY_AN_AD_10BASET_HALF)
            {
                DbgMessage(pdev, INFORM, "detected 10mb half autoneg.\n");

                speed = LM_MEDIUM_SPEED_10MBPS;
                duplex = LM_MEDIUM_HALF_DUPLEX;
            }
            else
            {
                DbgBreakMsg("unable to determine autoneg speed.\n");

                speed = LM_MEDIUM_SPEED_UNKNOWN;
                duplex = LM_MEDIUM_FULL_DUPLEX;
                link = LM_STATUS_LINK_DOWN;
            }
        }
    }
    else
    {
        /* The link speed speed and duplex mode are forced.  Get the forced
         * line settings from the PHY control register. */
        if(phy_ctrl & PHY_CTRL_SPEED_SELECT_100MBPS)
        {
            DbgMessage(pdev, INFORM, "PHY forced to 100mb.\n");
            speed = LM_MEDIUM_SPEED_100MBPS;
        }
        else if(phy_ctrl & PHY_CTRL_SPEED_SELECT_1000MBPS)
        {
            DbgMessage(pdev, INFORM, "PHY forced to 1gb.\n");

            speed = LM_MEDIUM_SPEED_1000MBPS;
        }
        else
        {
            DbgMessage(pdev, INFORM, "PHY forced to 10mb.\n");

            speed = LM_MEDIUM_SPEED_10MBPS;
        }

        if(phy_ctrl & PHY_CTRL_FULL_DUPLEX_MODE)
        {
            DbgMessage(pdev, INFORM, "PHY forced to full duplex.\n");

            duplex = LM_MEDIUM_FULL_DUPLEX;
        }
        else
        {
            DbgMessage(pdev, INFORM, "PHY forced to half duplex.\n");

            duplex = LM_MEDIUM_HALF_DUPLEX;
        }
    }

    *medium = LM_MEDIUM_TYPE_UTP | speed | duplex;

    return link;
} /* get_copper_phy_link */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC void
init_mac_link(
    lm_device_t *pdev,
    lm_status_t link,
    lm_medium_t medium,
    lm_flow_control_t flow_ctrl)
{
    u32_t val;

    /* Configure slot time, IPG, and 802.3 flow control. */
    REG_WR(pdev, emac.emac_tx_lengths, 0x2620);
    if(link == LM_STATUS_LINK_ACTIVE)
    {
        if(GET_MEDIUM_SPEED(medium) == LM_MEDIUM_SPEED_1000MBPS &&
            GET_MEDIUM_DUPLEX(medium) == LM_MEDIUM_HALF_DUPLEX)
        {
            REG_WR(pdev, emac.emac_tx_lengths, 0x26ff);
        }

        pdev->vars.flow_control = set_mac_flow_control(pdev, medium, flow_ctrl);
    }

    /* Configure the EMAC mode register. */
    REG_RD(pdev, emac.emac_mode, &val);

    val &= ~(EMAC_MODE_PORT | EMAC_MODE_FORCE_LINK);

    if(link == LM_STATUS_LINK_ACTIVE)
    {
        if(GET_MEDIUM_SPEED(medium) == LM_MEDIUM_SPEED_10MBPS)
        {
            if(CHIP_NUM(pdev) == CHIP_NUM_5706)
            {
                val |= EMAC_MODE_PORT_MII;
            }
            else
            {
                /* 5708 setting. */
                val |= EMAC_MODE_PORT_MII_10M;
            }
        }
        else if(GET_MEDIUM_SPEED(medium) == LM_MEDIUM_SPEED_100MBPS)
        {
            val |= EMAC_MODE_PORT_MII;
        }
        else
        {
            val |= EMAC_MODE_PORT_GMII;
        }

        if(GET_MEDIUM_SPEED(medium) == LM_MEDIUM_SPEED_2500MBPS)
        {
            val |= EMAC_MODE_25G_MODE;
        }

        /* We need to set the port mode to GMII when we are running in
         * the FPGA mode, regardless of the actual line speed. */
        if(CHIP_REV(pdev) == CHIP_REV_FPGA)
        {
            val &= ~EMAC_MODE_PORT;
            val |= EMAC_MODE_PORT_GMII;
        }
    }
    else
    {
        val |= EMAC_MODE_PORT_GMII;
    }

    if(GET_MEDIUM_TYPE(medium) == LM_MEDIUM_TYPE_NULL)
    {
        val |= EMAC_MODE_FORCE_LINK;
    }

    /* Set the MAC to operate in the appropriate duplex mode. */
    val &= ~EMAC_MODE_HALF_DUPLEX;
    if(GET_MEDIUM_DUPLEX(medium) == LM_MEDIUM_HALF_DUPLEX)
    {
        val |= EMAC_MODE_HALF_DUPLEX;
    }
    REG_WR(pdev, emac.emac_mode, val);

    /* Acknowledge the interrupt. */
    REG_WR(pdev, emac.emac_status, EMAC_STATUS_LINK_CHANGE);

    /* Enable phy link change attention. */
    if(pdev->params.phy_int_mode == PHY_INT_MODE_MI_INTERRUPT)
    {
        REG_WR(pdev, emac.emac_attention_ena, EMAC_ATTENTION_ENA_MI_INT);
    }
    else
    {
        REG_WR(pdev, emac.emac_attention_ena, EMAC_ATTENTION_ENA_LINK);
    }

    /* Enable status block link attention. */
    REG_RD(pdev, hc.hc_attn_bits_enable, &val);
    val &= ~STATUS_ATTN_BITS_LINK_STATE;
    if(pdev->params.link_chng_mode == LINK_CHNG_MODE_USE_STATUS_BLOCK)
    {
        val |= STATUS_ATTN_BITS_LINK_STATE;
    }
    REG_WR(pdev, hc.hc_attn_bits_enable, val);

    pdev->vars.medium = medium;
    pdev->vars.link_status = link;
} /* init_mac_link */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
serdes_fallback(
    lm_device_t *pdev,
    u8_t fallback_select)
{
    u32_t intr_exp_status;
    u8_t fallback_to;
    u32_t phy_status;
    u32_t phy_ctrl;
    u32_t val;
    u32_t cnt;

    pdev->vars.serdes_fallback_status = SERDES_FALLBACK_NONE;

    if(fallback_select == SERDES_FALLBACK_NONE)
    {
        return LM_STATUS_LINK_DOWN;
    }

    /* See if the cable is connected. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1c, 0x7c00);
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x1c, &val);

    /* We think the cable is not attached, set up the serdes to
     * autoneg as the default. */
    if(!(val & 0x10))       /* SIG_DETECT */
    {
        DbgMessage(pdev, INFORM, "no cable, default to autoneg.\n");

        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            PHY_CTRL_REG,
            PHY_CTRL_AUTO_NEG_ENABLE);

        return LM_STATUS_LINK_DOWN;
    }

    /* Read the interrupt expansion register to see if rudi_c is set.
     * rudi_c is set when we are receiving config words which means
     * the link partner is attempting to autonegotiate.
     *
     * When the link partner is attempting to autonegotiate and we
     * are not able to get linke, it could mean our transmit cable
     * is not plugged in.  In this case we don't want to fallback
     * to the force mode.  We want to remain in autonegotiation mode. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x17, 0x0f01);
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x15, &intr_exp_status);
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x15, &intr_exp_status);

    /* See if autoneg is enabled and the remote is not sending us
     * configs.  If this is the case and link is currently down, we
     * will switch to the force mode and disable autonegotiation.
     *
     * If we are current in the forced mode or the link partner is
     * sending use configs, we'll enable autoneg and restart it. */
    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_CTRL_REG, &phy_ctrl);
    if((phy_ctrl & PHY_CTRL_AUTO_NEG_ENABLE) && !(intr_exp_status & 0x20))
    {
        DbgMessage(pdev, INFORM, "switch to force mode - 1G full\n");

        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            PHY_CTRL_REG,
            PHY_CTRL_SPEED_SELECT_1000MBPS | PHY_CTRL_FULL_DUPLEX_MODE);

        fallback_to = SERDES_FALLBACK_1G;
    }
    else
    {
        DbgMessage(pdev, INFORM, "switch to autoneg mode - 1G full\n");

        /* Switch to autoneg mode. */
        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            PHY_CTRL_REG,
            PHY_CTRL_AUTO_NEG_ENABLE | PHY_CTRL_RESTART_AUTO_NEG);

        fallback_to = SERDES_FALLBACK_NONE;
    }

    for(cnt = 0; cnt < 100; cnt++)
    {
        mm_wait(pdev, 10);
    }

    phy_status = mii_get_serdes_link_status(pdev);

    if(phy_status & PHY_STATUS_LINK_PASS)
    {
        pdev->vars.serdes_fallback_status = fallback_to;

        return LM_STATUS_LINK_ACTIVE;
    }

    return LM_STATUS_LINK_DOWN;
} /* serdes_fallback */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
get_5708_serdes_link(
    lm_device_t *pdev,
    lm_medium_t *medium)
{
    u8_t restarted_autoneg;
    lm_medium_t duplex;
    lm_medium_t speed;
    lm_status_t link;
    u32_t val;
    u32_t idx;

    *medium = LM_MEDIUM_TYPE_FIBER |
        LM_MEDIUM_SPEED_UNKNOWN |
        LM_MEDIUM_FULL_DUPLEX;

    pdev->vars.cable_is_attached = FALSE;

    /* Check for link.  The first read returns the latched value, the
     * second read returns the current value. */
    (void) lm_mread(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_status),
            &val);
    (void) lm_mread(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_status),
            &val);

    /* CQ#23742 - Link status in the status block and the link status
     * in the mii_status are not consistent.  mii_status appears to
     * return invalid value.  Added a workaround here. */
    for(idx = 0; idx < 10 && val == 0; idx++)
    {
        mm_wait(pdev, 10);

        (void) lm_mread(
                pdev,
                pdev->params.phy_addr,
                MII_REG(serdes_reg_t, mii_status),
                &val);
    }

    if((val & MII_STAT_LINK_STATUS) == 0)
    {
        DbgMessage(pdev, INFORM, "serdes link down.\n");

        pdev->vars.cable_is_attached = FALSE;

        return LM_STATUS_LINK_DOWN;
    }

    link = LM_STATUS_LINK_ACTIVE;
    pdev->vars.cable_is_attached = TRUE;

    /* Determine duplex mode.  Link is present also means autoneg is done. */
    (void) lm_mread(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_ctrl),
            &val);
    if(val & MII_CTRL_ANEG_ENA)
    {
        /* Select Bank 0. */
        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            MII_REG(serdes_reg_t, mii_block_addr),
            MII_BLK_ADDR_DIGITAL);

        /* Get the negotiated speed and duplex mode. */
        (void) lm_mread(
            pdev,
            pdev->params.phy_addr,
            0x10+MII_REG(serdes_digital_reg_t, mii_1000x_stat1),
            &val);
        switch(val & MII_1000X_STAT1_SPEED)
        {
            case MII_1000X_STAT1_SPEED_2G5:
                DbgMessage(pdev, INFORM, "serdes autoneg to 2.5gb.\n");
                speed = LM_MEDIUM_SPEED_2500MBPS;
                break;

            case MII_1000X_STAT1_SPEED_1G:
                DbgMessage(pdev, INFORM, "serdes autoneg to 1gb.\n");
                speed = LM_MEDIUM_SPEED_1000MBPS;
                break;

            case MII_1000X_STAT1_SPEED_100:
                DbgMessage(pdev, INFORM, "serdes autoneg to 100mb.\n");
                speed = LM_MEDIUM_SPEED_100MBPS;
                break;

            case MII_1000X_STAT1_SPEED_10:
            default:
                DbgMessage(pdev, INFORM, "serdes autoneg to 10mb.\n");
                speed = LM_MEDIUM_SPEED_10MBPS;
                break;
        }

        /* Get the duplex mode. */
        duplex = LM_MEDIUM_FULL_DUPLEX;
        if(val & MII_1000X_STAT1_DUPLEX)
        {
            DbgMessage(pdev, INFORM, "serdes autoneg to full duplex.\n");
        }
        else
        {
            (void) lm_mread(
                    pdev,
                    pdev->params.phy_addr,
                    MII_REG(serdes_reg_t, mii_status),
                    &val);
            if(val & MII_STAT_ANEG_CMPL)
            {
                duplex = LM_MEDIUM_HALF_DUPLEX;
                DbgMessage(pdev, INFORM, "serdes autoneg to half duplex.\n");
            }
            else
            {
                DbgMessage(pdev, INFORM, "serdes autoneg to full duplex.\n");
            }
        }

        /* Set up pre-emphasis for a backplane application. */
        if(pdev->hw_info.nvm_hw_config & SHARED_HW_CFG_BACKPLANE_APP)
        {
            restarted_autoneg = set_5708_serdes_pre_emphasis(
                pdev,
                pdev->params.serdes_pre_emphasis);

            if(restarted_autoneg)
            {
                speed = LM_MEDIUM_SPEED_UNKNOWN;
                duplex = LM_MEDIUM_FULL_DUPLEX;
                link = LM_STATUS_LINK_DOWN;
            }
        }
    }
    else
    {
        /* Determine the forced link settings. */
        if(val & MII_CTRL_MANUAL_FORCE_2500)
        {
            DbgMessage(pdev, INFORM, "serdes forced to 2.5gb.\n");
            speed = LM_MEDIUM_SPEED_2500MBPS;
        }
        else if(val & MII_CTRL_MANUAL_SPD1)
        {
            DbgMessage(pdev, INFORM, "serdes forced to 1gb.\n");
            speed = LM_MEDIUM_SPEED_1000MBPS;
        }
        else if(val & MII_CTRL_MANUAL_SPD0)
        {
            DbgMessage(pdev, INFORM, "serdes forced to 100mb.\n");
            speed = LM_MEDIUM_SPEED_100MBPS;
        }
        else
        {
            DbgMessage(pdev, INFORM, "serdes forced to 10mb.\n");
            speed = LM_MEDIUM_SPEED_10MBPS;
        }

        if(val & MII_CTRL_DUPLEX_MODE)
        {
            DbgMessage(pdev, INFORM, "serdes forced to full duplex.\n");
            duplex = LM_MEDIUM_FULL_DUPLEX;
        }
        else
        {
            DbgMessage(pdev, INFORM, "serdes forced to half duplex.\n");
            duplex = LM_MEDIUM_HALF_DUPLEX;
        }
    }

    *medium = LM_MEDIUM_TYPE_FIBER | speed | duplex;

    return link;
} /* get_5708_serdes_link */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
get_5709_serdes_link(
    lm_device_t *pdev,
    lm_medium_t *medium)
{
    lm_medium_t duplex = LM_MEDIUM_FULL_DUPLEX;
    lm_medium_t speed = LM_MEDIUM_SPEED_UNKNOWN;
    lm_status_t link = LM_STATUS_LINK_UNKNOWN;
    u32_t mac_status;
    u32_t val;

    *medium = LM_MEDIUM_TYPE_FIBER |
        LM_MEDIUM_SPEED_UNKNOWN |
        LM_MEDIUM_FULL_DUPLEX;

    pdev->vars.cable_is_attached = FALSE;

    /* select gp_status block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0x8120);

    /* get status. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x1b, &val);
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x1b, &val);  /* is this needed? */

    /* sometimes when we get a link event, mii register 0x1b does not
     * reflect the current link status but mac_status does reflect the
     * correct link status. */
    REG_RD(pdev, emac.emac_status, &mac_status);

    /* link down. */
    if((val & 0x4) == 0 && (mac_status & EMAC_STATUS_LINK) == 0)
    {
        return LM_STATUS_LINK_DOWN;
    }

    link = LM_STATUS_LINK_ACTIVE;
    pdev->vars.cable_is_attached = TRUE;

    /* select combo ieee0 block. */
    (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0xffe0);

    /* phy_ctrl register. */
    (void) lm_mread(pdev, pdev->params.phy_addr, 0x10, &val);

    if(val & 0x1000)
    {
        /* select gp_status block. */
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1f, 0x8120);

        /* get status. */
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x1b, &val);

        /* duplex mode. */
        if(val & 0x8)
        {
            duplex = LM_MEDIUM_FULL_DUPLEX;
        }
        else
        {
            duplex = LM_MEDIUM_HALF_DUPLEX;
        }

        /* Determine autoneg speed. */
        val = (val >> 8) & 0x3f;
        if(val == 0)
        {
            speed = LM_MEDIUM_SPEED_10MBPS;
        }
        else if(val == 1)
        {
            speed = LM_MEDIUM_SPEED_100MBPS;
        }
        else if(val == 2 || val == 13)
        {
            speed = LM_MEDIUM_SPEED_1000MBPS;
        }
        else if(val == 3)
        {
            speed = LM_MEDIUM_SPEED_2500MBPS;
        }
        else
        {
            DbgBreakMsg("unknown link speed status.\n");
        }
    }
    else
    {
        /* get forced duplex mode. */
        if(val & 0x100)
        {
            duplex = LM_MEDIUM_FULL_DUPLEX;
        }
        else
        {
            duplex = LM_MEDIUM_HALF_DUPLEX;
        }

        /* get forced speed. */
        if(val & 0x20)
        {
            speed = LM_MEDIUM_SPEED_2500MBPS;
        }
        else if((val & 0x2040) == 0)
        {
            speed = LM_MEDIUM_SPEED_10MBPS;
        }
        else if((val & 0x2040) == 0x2000)
        {
            speed = LM_MEDIUM_SPEED_100MBPS;
        }
        else if((val & 0x2040) == 0x40)
        {
            speed = LM_MEDIUM_SPEED_1000MBPS;
        }
        else
        {
            DbgBreakMsg("unknown speed.\n");
        }
    }

    *medium = LM_MEDIUM_TYPE_FIBER | speed | duplex;

    return link;
} /* get_5709_serdes_link */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
get_5706_serdes_link(
    lm_device_t *pdev,
    lm_medium_t *medium)
{
    lm_status_t link;
    u32_t phy_status;
    u32_t remote_adv;
    u32_t local_adv;
    u32_t phy_ctrl;
    u32_t val;

    *medium = LM_MEDIUM_TYPE_FIBER;

    phy_status = mii_get_serdes_link_status(pdev);

    if(phy_status & PHY_STATUS_LINK_PASS)
    {
        DbgMessage(pdev, INFORM, "serdes link up.\n");

        link = LM_STATUS_LINK_ACTIVE;
        *medium |= LM_MEDIUM_SPEED_1000MBPS;

        /* Determine duplex mode.  Link is present also means
         * autoneg is done. */
        (void) lm_mread(pdev, pdev->params.phy_addr, PHY_CTRL_REG, &phy_ctrl);
        if(phy_ctrl & PHY_CTRL_AUTO_NEG_ENABLE)
        {
            (void) lm_mread(
                    pdev,
                    pdev->params.phy_addr,
                    PHY_AN_AD_REG,
                    &local_adv);
            (void) lm_mread(
                    pdev,
                    pdev->params.phy_addr,
                    PHY_LINK_PARTNER_ABILITY_REG,
                    &remote_adv);

            val = local_adv & remote_adv;
            if(val & PHY_AN_AD_1000X_FULL_DUPLEX)
            {
                DbgMessage(pdev, INFORM, "serdes autoneg to full duplex.\n");

                *medium |= LM_MEDIUM_FULL_DUPLEX;
            }
            else
            {
                DbgMessage(pdev, INFORM, "serdes autoneg to half duplex.\n");

                *medium |= LM_MEDIUM_HALF_DUPLEX;
            }

            pdev->vars.serdes_fallback_status = SERDES_FALLBACK_NONE;
        }
        else
        {
            if(phy_ctrl & PHY_CTRL_FULL_DUPLEX_MODE)
            {
                DbgMessage(pdev, INFORM, "serdes forced to full duplex.\n");

                *medium |= LM_MEDIUM_FULL_DUPLEX;
            }
            else
            {
                DbgMessage(pdev, INFORM, "serdes forced to half duplex.\n");

                *medium |= LM_MEDIUM_HALF_DUPLEX;
            }

            if(pdev->vars.serdes_fallback_select)
            {
                pdev->vars.serdes_fallback_status = SERDES_FALLBACK_1G;
            }
        }
    }
    else
    {
        DbgMessage(pdev, INFORM, "serdes link down.\n");

        /* This routine is called only when the link is down. */
        link = serdes_fallback(pdev, pdev->vars.serdes_fallback_select);
    }

    /* cq#30504 - restore the tx driver current so we can get link. */
    if(pdev->vars.bcm5706s_tx_drv_cur)
    {
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x17, 0x0f03);

        (void) lm_mwrite(
            pdev,
            pdev->params.phy_addr,
            0x15,
            pdev->vars.bcm5706s_tx_drv_cur);

        pdev->vars.bcm5706s_tx_drv_cur = 0;
    }

    pdev->vars.cable_is_attached = TRUE;

    if(link == LM_STATUS_LINK_DOWN)
    {
        (void) lm_mwrite(pdev, pdev->params.phy_addr, 0x1c, 0x7c00);
        (void) lm_mread(pdev, pdev->params.phy_addr, 0x1c, &val);
        if(!(val & 0x10))
        {
            pdev->vars.cable_is_attached = FALSE;
        }
    }

    return link;
} /* get_5706_serdes_link */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC lm_status_t
get_remote_phy_link(
    lm_device_t *pdev,
    lm_medium_t *medium)
{
    u32_t remote_phy_link;
    lm_medium_t duplex;
    lm_medium_t speed;
    lm_status_t link;

    DbgBreakIf(pdev->params.enable_remote_phy == FALSE);

    *medium = LM_MEDIUM_TYPE_FIBER |
        LM_MEDIUM_SPEED_UNKNOWN |
        LM_MEDIUM_FULL_DUPLEX;

    pdev->vars.cable_is_attached = FALSE;

    REG_RD_IND(
        pdev,
        pdev->hw_info.shmem_base +
            OFFSETOF(shmem_region_t, drv_fw_mb.link_status),
        &remote_phy_link);

    pdev->vars.rphy_status = 0;

    if((remote_phy_link & NETLINK_GET_LINK_STATUS_SERDES_LINK) == 0)
    {
        pdev->vars.rphy_status |= RPHY_STATUS_ACTIVE;
    }

    if((remote_phy_link & NETLINK_GET_LINK_STATUS_NO_MEDIA_DETECTED) == 0)
    {
        pdev->vars.rphy_status |= RPHY_STATUS_MODULE_PRESENT;
    }

    if((remote_phy_link & NETLINK_GET_LINK_STATUS_LINK_UP) == 0)
    {
        return LM_STATUS_LINK_DOWN;
    }

    link = LM_STATUS_LINK_ACTIVE;
    pdev->vars.cable_is_attached = TRUE;

    switch(remote_phy_link & NETLINK_GET_LINK_STATUS_SPEED_MASK)
    {
        case NETLINK_GET_LINK_STATUS_10HALF:
            speed = LM_MEDIUM_SPEED_10MBPS;
            duplex = LM_MEDIUM_HALF_DUPLEX;
            break;

        case NETLINK_GET_LINK_STATUS_10FULL:
            speed = LM_MEDIUM_SPEED_10MBPS;
            duplex = LM_MEDIUM_FULL_DUPLEX;
            break;

        case NETLINK_GET_LINK_STATUS_100HALF:
            speed = LM_MEDIUM_SPEED_100MBPS;
            duplex = LM_MEDIUM_HALF_DUPLEX;
            break;

        case NETLINK_GET_LINK_STATUS_100FULL:
            speed = LM_MEDIUM_SPEED_100MBPS;
            duplex = LM_MEDIUM_FULL_DUPLEX;
            break;

        case NETLINK_GET_LINK_STATUS_1000HALF:
            speed = LM_MEDIUM_SPEED_1000MBPS;
            duplex = LM_MEDIUM_HALF_DUPLEX;
            break;

        case NETLINK_GET_LINK_STATUS_1000FULL:
            speed = LM_MEDIUM_SPEED_1000MBPS;
            duplex = LM_MEDIUM_FULL_DUPLEX;
            break;

        case NETLINK_GET_LINK_STATUS_2500HALF:
            speed = LM_MEDIUM_SPEED_2500MBPS;
            duplex = LM_MEDIUM_HALF_DUPLEX;
            break;

        case NETLINK_GET_LINK_STATUS_2500FULL:
            speed = LM_MEDIUM_SPEED_2500MBPS;
            duplex = LM_MEDIUM_FULL_DUPLEX;
            break;

        default:
            speed = LM_MEDIUM_SPEED_UNKNOWN;
            duplex = LM_MEDIUM_FULL_DUPLEX;
            break;
    }

    *medium = LM_MEDIUM_TYPE_FIBER | speed | duplex;

    return link;
} /* get_remote_phy_link */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
lm_status_t
lm_init_mac_link(
    lm_device_t *pdev)
{
    lm_status_t lm_status;
    lm_medium_t medium;
    lm_status_t link;
    u32_t val, phy_ctrl, phy_status;

    if(pdev->params.enable_remote_phy)
    {
        link = get_remote_phy_link(pdev, &medium);
        init_mac_link(pdev, link, medium, pdev->params.flow_ctrl_cap);

        return LM_STATUS_SUCCESS;
    }

    switch(GET_MEDIUM_TYPE(pdev->vars.medium))
    {
        case LM_MEDIUM_TYPE_UTP:
            link = get_copper_phy_link(pdev, &medium);
            init_mac_link(pdev, link, medium, pdev->params.flow_ctrl_cap);

            lm_status = LM_STATUS_SUCCESS;
            break;

        case LM_MEDIUM_TYPE_FIBER:
            DbgBreakIf(CHIP_NUM(pdev) != CHIP_NUM_5706 &&
                       CHIP_NUM(pdev) != CHIP_NUM_5708 &&
                       CHIP_NUM(pdev) != CHIP_NUM_5709);

            if(CHIP_NUM(pdev) == CHIP_NUM_5706)
            {
                link = get_5706_serdes_link(pdev, &medium);
            }
            else if(CHIP_NUM(pdev) == CHIP_NUM_5708)
            {
                link = get_5708_serdes_link(pdev, &medium);
            }
            else
            {
                link = get_5709_serdes_link(pdev, &medium);
            }

            init_mac_link(pdev, link, medium, pdev->params.flow_ctrl_cap);

            lm_status = LM_STATUS_SUCCESS;
            break;

        case LM_MEDIUM_TYPE_PHY_LOOPBACK:
        case LM_MEDIUM_TYPE_MAC_LOOPBACK:
            lm_status = init_loopback_mac_link(
                pdev,
                pdev->params.req_medium,
                pdev->params.flow_ctrl_cap);
            break;

        case LM_MEDIUM_TYPE_NULL:
            init_mac_link(
                pdev,
                LM_STATUS_LINK_ACTIVE,
                LM_MEDIUM_TYPE_NULL |
                    LM_MEDIUM_SPEED_1000MBPS |
                    LM_MEDIUM_FULL_DUPLEX,
                pdev->params.flow_ctrl_cap);

            lm_status = LM_STATUS_SUCCESS;
            break;

        default:
            lm_status = LM_STATUS_UNKNOWN_MEDIUM;
            break;
    }

    /* Report the currnet link status to the management firmware. */
    val = 0;

    if(pdev->vars.link_status == LM_STATUS_LINK_ACTIVE)
    {
        val |= NETLINK_GET_LINK_STATUS_LINK_UP;

        if(lm_get_medium(pdev) == LM_MEDIUM_TYPE_FIBER)
        {
            val |= NETLINK_GET_LINK_STATUS_SERDES_LINK;
        }
    }

    switch(GET_MEDIUM_SPEED(pdev->vars.medium))
    {
        case LM_MEDIUM_SPEED_10MBPS:
            if(GET_MEDIUM_DUPLEX(pdev->vars.medium) == LM_MEDIUM_FULL_DUPLEX)
            {
                val |= NETLINK_GET_LINK_STATUS_10FULL;
            }
            else
            {
                val |= NETLINK_GET_LINK_STATUS_10HALF;
            }
            break;

        case LM_MEDIUM_SPEED_100MBPS:
            if(GET_MEDIUM_DUPLEX(pdev->vars.medium) == LM_MEDIUM_FULL_DUPLEX)
            {
                val |= NETLINK_GET_LINK_STATUS_100FULL;
            }
            else
            {
                val |= NETLINK_GET_LINK_STATUS_100HALF;
            }
            break;

        case LM_MEDIUM_SPEED_1000MBPS:
            if(GET_MEDIUM_DUPLEX(pdev->vars.medium) == LM_MEDIUM_FULL_DUPLEX)
            {
                val |= NETLINK_GET_LINK_STATUS_1000FULL;
            }
            else
            {
                val |= NETLINK_GET_LINK_STATUS_1000HALF;
            }
            break;

        case LM_MEDIUM_SPEED_2500MBPS:
            if(GET_MEDIUM_DUPLEX(pdev->vars.medium) == LM_MEDIUM_FULL_DUPLEX)
            {
                val |= NETLINK_GET_LINK_STATUS_2500FULL;
            }
            else
            {
                val |= NETLINK_GET_LINK_STATUS_2500HALF;
            }
            break;
    }

    // read PHY_CTRL_REG to see if auto-negotiation is enabled/completed
    (void) lm_mread(pdev, pdev->params.phy_addr, PHY_CTRL_REG, &phy_ctrl);

    if(phy_ctrl & PHY_CTRL_AUTO_NEG_ENABLE)
    {
        val |= NETLINK_GET_LINK_STATUS_AN_ENABLED;
        (void) lm_mread(pdev, pdev->params.phy_addr, PHY_STATUS_REG, &phy_status);
        if(phy_status & PHY_STATUS_AUTO_NEG_COMPLETE)
        {
            val |= NETLINK_GET_LINK_STATUS_AN_COMPLETE;
            // Following bits are valid for copper (i.e. SerDes flag == 0)
            if ((val & NETLINK_GET_LINK_STATUS_SERDES_LINK) == 0)
            {
                u32_t remote_phy_ad;
                (void) lm_mread(
                        pdev,
                        pdev->params.phy_addr,
                        PHY_1000BASET_STATUS_REG,
                        &remote_phy_ad);

                if(remote_phy_ad & PHY_LINK_PARTNER_1000BASET_FULL)
                    val |= NETLINK_GET_LINK_STATUS_PARTNER_AD_1000FULL;
                if(remote_phy_ad & PHY_LINK_PARTNER_1000BASET_HALF)
                    val |= NETLINK_GET_LINK_STATUS_PARTNER_AD_1000HALF;

                (void) lm_mread(
                    pdev,
                    pdev->params.phy_addr,
                    PHY_LINK_PARTNER_ABILITY_REG,
                    &remote_phy_ad);
                if (remote_phy_ad & PHY_LINK_PARTNER_10BASET_HALF)
                    val |= NETLINK_GET_LINK_STATUS_PARTNER_AD_10HALF;
                if (remote_phy_ad & PHY_LINK_PARTNER_10BASET_FULL)
                    val |= NETLINK_GET_LINK_STATUS_PARTNER_AD_10FULL;
                if (remote_phy_ad & PHY_LINK_PARTNER_100BASETX_HALF)
                    val |= NETLINK_GET_LINK_STATUS_PARTNER_AD_100HALF;
                if (remote_phy_ad & PHY_LINK_PARTNER_100BASETX_FULL)
                    val |= NETLINK_GET_LINK_STATUS_PARTNER_AD_100FULL;
                if (remote_phy_ad & PHY_LINK_PARTNER_PAUSE_CAPABLE)
                    val |= NETLINK_GET_LINK_STATUS_PARTNER_SYM_PAUSE_CAP;
                if (remote_phy_ad & PHY_LINK_PARTNER_ASYM_PAUSE)
                    val |= NETLINK_GET_LINK_STATUS_PARTNER_ASYM_PAUSE_CAP;
                // Read PHY_AN_EXPANSION_REG to see if Link partner support auto
                // negotiation
                (void) lm_mread(
                    pdev,
                    pdev->params.phy_addr,
                    PHY_AN_EXPANSION_REG,
                    &remote_phy_ad);
                // If Link partner does not support auto negotiation,  we assume
                // parallel detection was used to get link.
                if ((remote_phy_ad & PHY_LINK_PARTNER_AUTONEG_ABILITY) == 0)
                    val |= NETLINK_GET_LINK_STATUS_PARALLEL_DET;
            }
        }
    }
    if(pdev->vars.flow_control & LM_FLOW_CONTROL_TRANSMIT_PAUSE)
    {
        val |= NETLINK_GET_LINK_STATUS_TX_FC_ENABLED;
    }
    if(pdev->vars.flow_control & LM_FLOW_CONTROL_RECEIVE_PAUSE)
    {
        val |= NETLINK_GET_LINK_STATUS_RX_FC_ENABLED;
    }
    // Following bits are not supported yet
    // NETLINK_GET_LINK_STATUS_NO_MEDIA_DETECTED;
    // NETLINK_GET_LINK_STATUS_CABLESENSE;
    // NETLINK_GET_LINK_STATUS_SW_TIMER_EVENT;
    REG_WR_IND(
        pdev,
        pdev->hw_info.shmem_base +
            OFFSETOF(shmem_region_t, drv_fw_mb.link_status),
        val);

    return lm_status;
} /* lm_init_mac_link */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
void
lm_service_phy_int(
    lm_device_t *pdev,
    u32_t force_service_int)
{
    u32_t deasserted_attns;
    u32_t asserted_attns;
    u32_t link_chng;
    u32_t val;

    link_chng = FALSE;

    if(pdev->params.link_chng_mode == LINK_CHNG_MODE_USE_STATUS_REG)
    {
        REG_RD(pdev, emac.emac_status, &val);
        if(pdev->params.phy_int_mode == PHY_INT_MODE_MI_INTERRUPT)
        {
            if(val & EMAC_STATUS_MI_INT)
            {
                link_chng = TRUE;
            }
        }
        else if(val & EMAC_STATUS_LINK_CHANGE)
        {
            link_chng = TRUE;
        }
    }
    else
    {
        link_chng = FALSE;

        GET_ATTN_CHNG_BITS(pdev, &asserted_attns, &deasserted_attns);

        asserted_attns &= STATUS_ATTN_BITS_LINK_STATE;
        deasserted_attns &= STATUS_ATTN_BITS_LINK_STATE;

        if(asserted_attns)
        {
            link_chng = TRUE;

            REG_WR(
                pdev,
                pci_config.pcicfg_status_bit_set_cmd,
                asserted_attns);
        }
        else if(deasserted_attns)
        {
            link_chng = TRUE;

            REG_WR(
                pdev,
                pci_config.pcicfg_status_bit_clear_cmd,
                deasserted_attns);
        }
    }

    if(link_chng || force_service_int || pdev->params.enable_remote_phy)
    {
        (void) lm_init_mac_link(pdev);

        mm_indicate_link(pdev, pdev->vars.link_status, pdev->vars.medium);
    }
} /* lm_service_phy_int */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
lm_medium_t
lm_get_medium(
    lm_device_t *pdev)
{
    u32_t decode;
    u32_t val;

    if(CHIP_REV(pdev) == CHIP_REV_IKOS)
    {
        return LM_MEDIUM_TYPE_NULL;
    }

    if(CHIP_REV(pdev) == CHIP_REV_FPGA)
    {
        return LM_MEDIUM_TYPE_UTP;
    }

    if(CHIP_NUM(pdev) == CHIP_NUM_5706 || CHIP_NUM(pdev) == CHIP_NUM_5708)
    {
        if(CHIP_BOND_ID(pdev) & CHIP_BOND_ID_SERDES_BIT)
        {
            return LM_MEDIUM_TYPE_FIBER;
        }

        return LM_MEDIUM_TYPE_UTP;
    }

    if(CHIP_NUM(pdev) == CHIP_NUM_5709)
    {
        REG_RD(pdev, misc.misc_dual_media_ctrl, &val);

        if((val & MISC_DUAL_MEDIA_CTRL_BOND_ID) ==
            MISC_DUAL_MEDIA_CTRL_BOND_ID_C)
        {
            return LM_MEDIUM_TYPE_UTP;
        }

        if((val & MISC_DUAL_MEDIA_CTRL_BOND_ID) ==
            MISC_DUAL_MEDIA_CTRL_BOND_ID_S)
        {
            return LM_MEDIUM_TYPE_FIBER;
        }

        /* mac to phy/serdes decode.
         *    swap strap mac0 mac1
         *    ==== ===== ==== ====
         *    0    000   phy0 phy1
         *    0    001   phy0 ser0
         *    0    010   phy0 ser1
         *    0    110   ser0 phy0
         *    0    101   ser0 phy1
         *    0    100   ser0 ser1
         *
         *    1    000   phy1 phy0
         *    1    001   phy1 ser1
         *    1    010   phy1 ser0
         *    1    110   ser1 phy1
         *    1    101   ser1 phy0
         *    1    100   ser1 ser0 */
        if(val & MISC_DUAL_MEDIA_CTRL_STRAP_OVERRIDE)
        {
            decode = (val & MISC_DUAL_MEDIA_CTRL_PHY_CTRL) >> 21;

            if(val & MISC_DUAL_MEDIA_CTRL_PORT_SWAP)
            {
                decode |= 0x8;
            }
        }
        else
        {
            decode = (val & MISC_DUAL_MEDIA_CTRL_PHY_CTRL_STRAP) >> 8;

            if(val & MISC_DUAL_MEDIA_CTRL_PORT_SWAP_PIN)
            {
                decode |= 0x8;
            }
        }

        decode |= pdev->hw_info.mac_id << 4;

        /* mac:4, swap:3, strap:2-0. */
        switch(decode)
        {
            case 0x00: /* 00000 - mac0, phy0 */
            case 0x01: /* 00001 - mac0, phy0 */
            case 0x02: /* 00010 - mac0, phy0 */
            case 0x08: /* 01000 - mac0, phy1 */
            case 0x09: /* 01001 - mac0, phy1 */
            case 0x0a: /* 01010 - mac0, phy1 */
            case 0x10: /* 10000 - mac1, phy1 */
            case 0x15: /* 10101 - mac1, phy1 */
            case 0x16: /* 10110 - mac1, phy0 */
            case 0x18: /* 11000 - mac1, phy0 */
            case 0x1d: /* 11101 - mac1, phy0 */
            case 0x1e: /* 11110 - mac1, phy1 */
                return LM_MEDIUM_TYPE_UTP;

            case 0x04: /* 00100 - mac0, ser0 */
            case 0x05: /* 00101 - mac0, ser0 */
            case 0x06: /* 00110 - mac0, ser0 */
            case 0x0c: /* 01100 - mac0, ser1 */
            case 0x0d: /* 01101 - mac0, ser1 */
            case 0x0e: /* 01110 - mac0, ser1 */
            case 0x11: /* 10001 - mac1, ser0 */
            case 0x12: /* 10010 - mac1, ser1 */
            case 0x14: /* 10100 - mac1, ser1 */
            case 0x19: /* 11001 - mac1, ser1 */
            case 0x1a: /* 11010 - mac1, ser0 */
            case 0x1c: /* 11100 - mac1, ser0 */
                return LM_MEDIUM_TYPE_FIBER;
        }
    }

    return LM_MEDIUM_TYPE_NULL;
} /* lm_get_medium */