root/usr/src/uts/common/io/bnxe/577xx/drivers/common/lm/device/lm_phy.c
/*******************************************************************************
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright 2014 QLogic Corporation
 * The contents of this file are subject to the terms of the
 * QLogic End User License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://www.qlogic.com/Resources/Documents/DriverDownloadHelp/
 * QLogic_End_User_Software_License.txt
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 *
 * Module Description:
 *
 *
 * History:
 *    11/15/01 Hav Khauv        Inception.
 ******************************************************************************/

#include "lm5710.h"
#include "phy_reg.h"
#include "license.h"
#include "mcp_shmem.h"
#include "lm_stats.h"
#include "577xx_int_offsets.h"

/***********************************************************/
/*              CLC - Common Link Component API            */
/***********************************************************/

/* Driver needs to redefine the cps_cb_st_ptr ( CPS CallBack Struct Pointer ) with its own */

#if defined(ELINK_DEBUG) && !defined(__SunOS)

void elink_cb_dbg(struct elink_dev *bp, _In_ char* fmt )
{
    DbgMessage(bp, WARNelink, fmt);
}
void elink_cb_dbg1(struct elink_dev *bp, _In_ char* fmt, u32 arg1 )
{
    DbgMessage(bp, WARNelink, fmt, arg1);
}
void elink_cb_dbg2(struct elink_dev *bp, _In_ char* fmt, u32 arg1, u32 arg2 )
{
    DbgMessage(bp, WARNelink, fmt, arg1, arg2);
}

void elink_cb_dbg3(struct elink_dev *bp, _In_ char* fmt, u32 arg1, u32 arg2, u32 arg3 )
{
    DbgMessage(bp, WARNelink, fmt, arg1, arg2, arg3);
}

#endif /* ELINK_DEBUG */

u32 elink_cb_reg_read(struct elink_dev *cb, u32 reg_addr )
{
    return REG_RD(cb, reg_addr);
}

void elink_cb_reg_write(struct elink_dev *cb, u32 reg_addr, u32 val )
{
    REG_WR(cb, reg_addr, val);
}

/* wb_write - pointer to 2 32 bits vars to be passed to the DMAE*/
void elink_cb_reg_wb_write(struct elink_dev *cb, u32 offset, u32 *wb_write, u16 len )
{
   REG_WR_DMAE_LEN(cb, offset, wb_write, len);
}

void elink_cb_reg_wb_read(struct elink_dev *cb, u32 offset, u32 *wb_write, u16 len )
{
   REG_RD_DMAE_LEN(cb, offset, wb_write, len);
}

/* mode - 0( LOW ) /1(HIGH)*/
u8 elink_cb_gpio_write(struct elink_dev *cb, u16 gpio_num, u8 mode, u8 port)
{
    return lm_gpio_write(cb, gpio_num, mode, port);
}

u8 elink_cb_gpio_mult_write(struct elink_dev *cb, u8 pins, u8 mode)
{
    return lm_gpio_mult_write(cb, pins, mode);
}

u32 elink_cb_gpio_read(struct elink_dev *cb, u16 gpio_num, u8 port)
{
    u32 val=0;
    lm_gpio_read(cb, gpio_num, &val, port);
    return val;
}

u8 elink_cb_gpio_int_write(struct elink_dev *cb, u16 gpio_num, u8 mode, u8 port)
{
    return lm_gpio_int_write(cb, gpio_num, mode, port);
}
void elink_cb_udelay(struct elink_dev *cb, u32 microsecond)
{
#define MAX_WAIT_INTERVAL 50

    u32_t wait_itr  = (microsecond/MAX_WAIT_INTERVAL) ;
    u32_t cnt       = 0;
    u32_t wait_time = MAX_WAIT_INTERVAL ;

    if( 0 == wait_itr )
    {
        wait_time = microsecond ;
        wait_itr = 1;
    }

    for(cnt = 0; cnt < wait_itr; cnt++)
    {
        mm_wait(cb , wait_time );
    }
}
u32 elink_cb_fw_command(struct elink_dev *cb, u32 command, u32 param)
{
    u32 fw_resp = 0;
    lm_mcp_cmd_send_recieve(cb, lm_mcp_mb_header, command, param, MCP_CMD_DEFAULT_TIMEOUT,
                &fw_resp );
    return fw_resp;
}

void elink_cb_download_progress(struct elink_dev *cb, u32 cur, u32 total)
{
    UNREFERENCED_PARAMETER_(cb);
    UNREFERENCED_PARAMETER_(cur);
    UNREFERENCED_PARAMETER_(total);

#ifdef DOS
    printf("Downloaded %u bytes out of %u bytes\n", cur, total );
#endif // DOS
}

void elink_cb_event_log(struct elink_dev *cb, const elink_log_id_t elink_log_id, ...)
{
    va_list     ap;
    lm_log_id_t lm_log_id = LM_LOG_ID_MAX;

    switch( elink_log_id )
    {
    case ELINK_LOG_ID_OVER_CURRENT:
        lm_log_id = LM_LOG_ID_OVER_CURRENT;
        break;

    case ELINK_LOG_ID_PHY_UNINITIALIZED:
        lm_log_id = LM_LOG_ID_PHY_UNINITIALIZED;
        break;

    case ELINK_LOG_ID_UNQUAL_IO_MODULE:
        lm_log_id = LM_LOG_ID_UNQUAL_IO_MODULE;
        break;

    case ELINK_LOG_ID_MDIO_ACCESS_TIMEOUT:
        lm_log_id = LM_LOG_ID_MDIO_ACCESS_TIMEOUT;
        break;

    case ELINK_LOG_ID_NON_10G_MODULE:
        lm_log_id = LM_LOG_ID_NON_10G_MODULE;
        break;

    default:
        DbgBreakIf(TRUE);
        break;
    } // elink_log_id switch

    va_start(ap, elink_log_id);

    mm_event_log_generic_arg_fwd( cb, lm_log_id, ap );

    va_end(ap);
}

u8 elink_cb_path_id(struct elink_dev *cb)
{
   return PATH_ID(cb);
}

void elink_cb_notify_link_changed(struct elink_dev *cb)
{
   REG_WR(cb, MISC_REG_AEU_GENERAL_ATTN_12 + FUNC_ID((lm_device_t *)cb)*sizeof(u32), 1);
}
/*******************************************************************************
* Macros.
******************************************************************************/

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

#define MDIO_INDIRECT_REG_ADDR      0x1f
#define MDIO_SET_REG_BANK(pdev,reg_bank)\
    lm_mwrite(pdev,MDIO_INDIRECT_REG_ADDR, reg_bank)

#define MDIO_ACCESS_TIMEOUT          1000

#define ELINK_STATUS_TO_LM_STATUS(_rc, _lm_status) switch(_rc)\
{\
case ELINK_STATUS_OK:\
    _lm_status = LM_STATUS_SUCCESS;\
    break;\
case ELINK_STATUS_TIMEOUT:\
    _lm_status = LM_STATUS_TIMEOUT;\
    break;\
case ELINK_STATUS_NO_LINK:\
    _lm_status = LM_STATUS_LINK_DOWN;\
    break;\
case ELINK_STATUS_ERROR:\
default:\
    _lm_status = LM_STATUS_FAILURE;\
    break;\
}

/*******************************************************************************
* Description:
*
* Return:
******************************************************************************/
lm_status_t
lm_mwrite( lm_device_t *pdev,
          u32_t reg,
          u32_t val)
{
    lm_status_t lm_status;
    u32_t tmp;
    u32_t cnt;
    u8_t port = PORT_ID(pdev);
    u32_t emac_base = (port?GRCBASE_EMAC1:GRCBASE_EMAC0);

    REG_WR(pdev,NIG_REG_XGXS0_CTRL_MD_ST + port*0x18, 1);

    DbgMessage(pdev, INFORM, "lm_mwrite\n");

    if(pdev->params.phy_int_mode == PHY_INT_MODE_AUTO_POLLING)
    {
        tmp=REG_RD(pdev,emac_base+EMAC_REG_EMAC_MDIO_MODE);
        tmp &= ~EMAC_MDIO_MODE_AUTO_POLL;

        REG_WR(pdev,emac_base+EMAC_REG_EMAC_MDIO_MODE,tmp);

        mm_wait(pdev, 40);
    }

    tmp = (pdev->vars.phy_addr << 21) | (reg << 16) | (val & EMAC_MDIO_COMM_DATA) |
        EMAC_MDIO_COMM_COMMAND_WRITE_22 |
        EMAC_MDIO_COMM_START_BUSY;

    REG_WR(pdev,emac_base+EMAC_REG_EMAC_MDIO_COMM,tmp);


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

        tmp=REG_RD(pdev,emac_base+EMAC_REG_EMAC_MDIO_COMM);
        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)
    {
        tmp=REG_RD(pdev,emac_base+EMAC_REG_EMAC_MDIO_MODE);
        tmp |= EMAC_MDIO_MODE_AUTO_POLL;

        REG_WR(pdev,emac_base+EMAC_REG_EMAC_MDIO_MODE,tmp);
    }
    REG_WR(pdev,NIG_REG_XGXS0_CTRL_MD_ST +
           port*0x18, 0);
    return lm_status;
} /* lm_mwrite */



/*******************************************************************************
* Description:
*
* Return:
******************************************************************************/
lm_status_t
lm_mread( lm_device_t *pdev,
         u32_t reg,
         u32_t *ret_val)
{
    lm_status_t lm_status;
    u32_t val;
    u32_t cnt;
    u8_t port = PORT_ID(pdev);
    u32_t emac_base = (port?GRCBASE_EMAC1:GRCBASE_EMAC0);

    REG_WR(pdev,NIG_REG_XGXS0_CTRL_MD_ST + port*0x18, 1);

    DbgMessage(pdev, INFORM, "lm_mread\n");

    if(pdev->params.phy_int_mode == PHY_INT_MODE_AUTO_POLLING)
    {
        val=REG_RD(pdev,emac_base+EMAC_REG_EMAC_MDIO_MODE);
        val &= ~EMAC_MDIO_MODE_AUTO_POLL;

        REG_WR(pdev,emac_base+EMAC_REG_EMAC_MDIO_MODE,val);

        mm_wait(pdev, 40);
    }

    val = (pdev->vars.phy_addr << 21) | (reg << 16) |
        EMAC_MDIO_COMM_COMMAND_READ_22 |
        EMAC_MDIO_COMM_START_BUSY;

    REG_WR(pdev,emac_base+EMAC_REG_EMAC_MDIO_COMM,val);

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

        val=REG_RD(pdev,emac_base+EMAC_REG_EMAC_MDIO_COMM);
        if(!(val & EMAC_MDIO_COMM_START_BUSY))
        {
            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)
    {
        val=REG_RD(pdev,emac_base+EMAC_REG_EMAC_MDIO_MODE);
        val |= EMAC_MDIO_MODE_AUTO_POLL;

        REG_WR(pdev,emac_base+EMAC_REG_EMAC_MDIO_MODE,val);
    }
    REG_WR(pdev,NIG_REG_XGXS0_CTRL_MD_ST +
           port*0x18, 0);
    return lm_status;
} /* lm_mread */

/*******************************************************************************
* Description:
*
* Return:
******************************************************************************/
lm_status_t
lm_phy45_read(
    lm_device_t *pdev,
    u8_t  phy_addr,
    u8_t dev_addr,
    u16_t reg, // offset
    u16_t *ret_val)
{

    u16_t       rc           = ELINK_STATUS_OK;
    lm_status_t lm_status    = LM_STATUS_SUCCESS;

    PHY_HW_LOCK(pdev);

    rc = elink_phy_read(&pdev->params.link, phy_addr, dev_addr, reg, ret_val);

    PHY_HW_UNLOCK(pdev);

    ELINK_STATUS_TO_LM_STATUS( rc, lm_status );

    return lm_status;
}

/*******************************************************************************
* Description:
*
* Return:
******************************************************************************/
lm_status_t
lm_phy45_write(
    lm_device_t *pdev,
    u8_t  phy_addr,
    u8_t  dev_addr,
    u16_t reg, // offset
    u16_t val)
{

    u16_t       rc           = ELINK_STATUS_OK;
    lm_status_t lm_status    = LM_STATUS_SUCCESS;

    PHY_HW_LOCK(pdev);

    rc = elink_phy_write(&pdev->params.link, phy_addr, dev_addr, reg, val);

    PHY_HW_UNLOCK(pdev);

    ELINK_STATUS_TO_LM_STATUS( rc, lm_status );

    return lm_status;
}

lm_status_t
lm_set_phy_addr(lm_device_t *pdev,
                u8_t addr)
{
    if (addr > 0x1f)
    {
        DbgBreakMsg("lm_set_phy_addr: error addr not valid\n");
        return LM_STATUS_FAILURE;
    }
    pdev->vars.phy_addr = addr;
    return LM_STATUS_SUCCESS;
}

/*
 *Function Name: lm_get_speed_real_from_elink_line_speed
 *
 *Parameters: IN line speed (from elink)
 *
 *Description:
 *
 *Returns: "real speed" in mbps units
 *
 */
u32_t lm_get_speed_real_from_elink_line_speed( IN const struct elink_vars* link_vars )
{
    const u16_t line_speed = link_vars->line_speed;
    u32_t       real_speed = 0;

    if( !link_vars->link_up )
    {
        // probably we get here from ioc_get_driver_info in case of no link
        // we return 0 in that case
        return 0;
    }

    switch(line_speed)
    {
    case ELINK_SPEED_10:
        real_speed = 10;
        break;

    case ELINK_SPEED_100:
        real_speed = 100;
        break;

    case ELINK_SPEED_1000:
        real_speed = 1000;
        break;

    case ELINK_SPEED_2500:
        real_speed = 2500;
        break;

    case ELINK_SPEED_10000:
        real_speed = 10000;
        break;

    case ELINK_SPEED_20000:
        real_speed = 20000;
        break;

    default:
        DbgBreakIf(1);
        break;
    }
    return real_speed;
}

/*
 *Function Name: lm_get_speed_medium_from_elink_line_speed
 *
 *Parameters: IN line speed (from elink)
 *
 *Description:
 *
 *Returns: "medium"  translation to LM units
 *
 */
u32_t lm_get_speed_medium_from_elink_line_speed( IN const struct elink_vars* link_vars )
{
    const u16_t line_speed = link_vars->line_speed;
    u32_t       medium     = 0;

    switch(line_speed)
    {
    case ELINK_SPEED_10:
        medium = LM_MEDIUM_SPEED_10MBPS;
        break;

    case ELINK_SPEED_100:
        medium = LM_MEDIUM_SPEED_100MBPS;
        break;

    case ELINK_SPEED_1000:
        medium = LM_MEDIUM_SPEED_1000MBPS;
        break;

    case ELINK_SPEED_2500:
        medium = LM_MEDIUM_SPEED_2500MBPS;
        break;

    case ELINK_SPEED_10000:
        medium = LM_MEDIUM_SPEED_10GBPS;
        break;

    case ELINK_SPEED_20000:
        medium = LM_MEDIUM_SPEED_20GBPS;
        break;

    default:
        DbgBreakIf(1);
        break;
    }
    return medium;
}

u32_t lm_get_port_max_speed(IN struct _lm_device_t *pdev)
{
    static const u32_t PORT_SPEED_10G = 10000;
    static const u32_t PORT_SPEED_1G  = 1000;

    u32_t port_default_cfg = 0;

    if(!CHIP_IS_E3(pdev))
    {
        return PORT_SPEED_10G;
    }

    if(LM_CHIP_PORT_MODE_4 != CHIP_PORT_MODE(pdev))
    {
        return PORT_SPEED_10G;
    }

    LM_SHMEM_READ(pdev,OFFSETOF(shmem_region_t,dev_info.port_hw_config[PORT_ID(pdev)].default_cfg),&port_default_cfg);

    if (GET_FLAGS(port_default_cfg, PORT_HW_CFG_NET_SERDES_IF_MASK) == PORT_HW_CFG_NET_SERDES_IF_SGMII)
    {
        return PORT_SPEED_1G;
    }
    else
    {
        return PORT_SPEED_10G;
    }
}

/*
 *Function Name: lm_loopback_req_meduim_convert
 *
 *Parameters: IN req_medium as received from upper layer
 *
 *Description: convert the req_meduim (recieved from diag driver / BMAPI) to relevant type according to the chip
 *             this is a little bit conusing since we override the value recieved by a new value
 *             but we need to do it for backward compatbiality.
 *Returns: "medium"  translation to LM units
 *
 */
lm_medium_t lm_loopback_req_medium_convert( IN struct _lm_device_t *pdev, IN const lm_medium_t req_medium )
{
    u32_t       default_cfg = 0;
    lm_medium_t ret_medium  = req_medium;

    // Assumption bxdiag always send the following for each test type:
    // LOOPBACK_TYPE_MAC --> LM_MEDIUM_TYPE_BMAC_LOOPBACK/LM_MEDIUM_TYPE_UMAC_LOOPBACK/LM_MEDIUM_TYPE_XMAC_LOOPBACK (bxdiag 7.0.1 only, never gold...)
    // LOOPBACK_TYPE_PHY --> LM_MEDIUM_TYPE_XGXS_10_LOOPBACK

    // Here, we'll "translate" the LM_MEDIUM_TYPE_XXX so it will work correctly in BCM578xx
    if( CHIP_IS_E3(pdev) )
    {
        LM_SHMEM_READ(pdev,OFFSETOF(shmem_region_t,dev_info.port_hw_config[PORT_ID(pdev)].default_cfg),&default_cfg);
        default_cfg &= PORT_HW_CFG_NET_SERDES_IF_MASK;
    }

    switch(req_medium)
    {
        // MAC loopback test
    case LM_MEDIUM_TYPE_BMAC_LOOPBACK:
    case LM_MEDIUM_TYPE_UMAC_LOOPBACK:
    case LM_MEDIUM_TYPE_XMAC_LOOPBACK:
    case LM_MEDIUM_TYPE_MAC_LOOPBACK:
        if( CHIP_IS_E3(pdev) )
        {
            if( PORT_HW_CFG_NET_SERDES_IF_SGMII == default_cfg )
            {
                ret_medium = LM_MEDIUM_TYPE_UMAC_LOOPBACK; //1GB
            }
            else
            {
                ret_medium = LM_MEDIUM_TYPE_XMAC_LOOPBACK; //10GB/20GB
            }
        }
        else
        {
            ret_medium = LM_MEDIUM_TYPE_BMAC_LOOPBACK;
        }
        break;

        // PHY loopback test
    case LM_MEDIUM_TYPE_XGXS_10_LOOPBACK:
        if( CHIP_IS_E3(pdev) )
        {
            switch(default_cfg)
            {
            case PORT_HW_CFG_NET_SERDES_IF_SGMII:
                ret_medium = LM_MEDIUM_TYPE_XGXS_LOOPBACK; //1GB
                break;

            case PORT_HW_CFG_NET_SERDES_IF_XFI:
            case PORT_HW_CFG_NET_SERDES_IF_SFI:
            case PORT_HW_CFG_NET_SERDES_IF_KR:
                ret_medium = LM_MEDIUM_TYPE_XGXS_10_LOOPBACK; //10GB
                break;

            case PORT_HW_CFG_NET_SERDES_IF_DXGXS:
            case PORT_HW_CFG_NET_SERDES_IF_KR2:
            default:
                ret_medium = req_medium; //20GB - TBD!! for T7.2
                break;
            }
        }
        else
        {
            ret_medium = LM_MEDIUM_TYPE_XGXS_10_LOOPBACK; //10GB
        }
        break;

    default:
        break;
    }

    return ret_medium;
}

static void get_link_params(lm_device_t *pdev)
{
    u32_t real_speed                = 0; // speed in 100M steps
    u32_t medium                    = 0; // LM_MEDIUM_XXX
    u16_t max_bw_in_Mbps            = 0; // In Mbps steps
    u16_t max_bw_in_100Mbps         = 0; // In 100Mbps steps

    if (IS_VFDEV(pdev))
    {
        pdev->vars.cable_is_attached = TRUE;
        pdev->vars.link_status = LM_STATUS_LINK_ACTIVE;
        SET_MEDIUM_SPEED(pdev->vars.medium,LM_MEDIUM_SPEED_10GBPS);
        return;
    }
    // link status

    if (!pdev->vars.link.link_up)
    {
        pdev->vars.link_status = LM_STATUS_LINK_DOWN;
        pdev->vars.cable_is_attached = FALSE;

    }
    else
    {
        // if we are in multifunction mode and function is disabled indicate OS link down (unless loopback medium is set)
        // Note that the CLC link is up so pmf handling is still going on
        if (IS_MULTI_VNIC(pdev) && (GET_FLAGS(pdev->hw_info.mf_info.func_mf_cfg, FUNC_MF_CFG_FUNC_DISABLED)) &&
            (!LM_MEDIUM_IS_LOOPBACK(pdev->params.req_medium)))
        {
            pdev->vars.link_status = LM_STATUS_LINK_DOWN;
            pdev->vars.cable_is_attached = FALSE;
        }
        else
        {
            //in NIV mode, link_status is modified only from lm_niv_vif_set or from the FUNCTION_UPDATE completion(for loopback)
            if(!IS_MF_AFEX_MODE(pdev))
            {
                pdev->vars.link_status = LM_STATUS_LINK_ACTIVE;
            }
            pdev->vars.cable_is_attached = TRUE;
        }
        // get speed

        real_speed = lm_get_speed_real_from_elink_line_speed(&pdev->vars.link);
        real_speed = real_speed/100;

        medium     = lm_get_speed_medium_from_elink_line_speed(&pdev->vars.link);

        SET_MEDIUM_SPEED(pdev->vars.medium, medium );

        // get duplex
        SET_MEDIUM_DUPLEX(pdev->vars.medium,LM_MEDIUM_FULL_DUPLEX);
        if (pdev->vars.link.duplex == DUPLEX_HALF )
        {
            SET_MEDIUM_DUPLEX(pdev->vars.medium,LM_MEDIUM_HALF_DUPLEX);
        }
        // get flow_control
        pdev->vars.flow_control = LM_FLOW_CONTROL_NONE;
        if (pdev->vars.link.flow_ctrl & ELINK_FLOW_CTRL_RX)
        {
            pdev->vars.flow_control |= LM_FLOW_CONTROL_RECEIVE_PAUSE;
        }
        if (pdev->vars.link.flow_ctrl & ELINK_FLOW_CTRL_TX)
        {
            pdev->vars.flow_control |= LM_FLOW_CONTROL_TRANSMIT_PAUSE;
        }

        // get EEE state

        if (GET_FLAGS(pdev->vars.link.eee_status,SHMEM_EEE_REQUESTED_BIT))
        {
            pdev->vars.autogreeen = LM_AUTOGREEEN_ENABLED;
            pdev->vars.eee_policy = pdev->vars.link.eee_status & SHMEM_EEE_TIMER_MASK;
        }
        else
        {
            pdev->vars.autogreeen = LM_AUTOGREEEN_DISABLED;
        }

        if (IS_MULTI_VNIC(pdev))
        {

            max_bw_in_Mbps = lm_get_max_bw(pdev,
                                           (real_speed *100),
                                           VNIC_ID(pdev));

            max_bw_in_100Mbps = max_bw_in_Mbps /100; // In 100Mbps steps

            if (real_speed > max_bw_in_100Mbps)
            {
                if (max_bw_in_100Mbps)
                {
                    SET_MEDIUM_SPEED(pdev->vars.medium,(LM_MEDIUM_SPEED_SEQ_START + ((max_bw_in_100Mbps-1)<<8)));
                }
                else
                {
                    // in case the pdev->params.max_bw[VNIC_ID(pdev)] = 0
                    SET_MEDIUM_SPEED(pdev->vars.medium,LM_MEDIUM_SPEED_SEQ_START);
                }
            }
        }
    }
}

void sync_link_status(lm_device_t *pdev)
{
    u32_t      i       = 0;
    const u8_t func_id = FUNC_ID(pdev);
    const u8_t port_id = PORT_ID(pdev);

    DbgMessage(pdev, WARN, "sync_link_status: Func %d \n", func_id );

    // inform all other port vnics not ourself
    for( i=0; i<4 ;i++ )
     {
        if (func_id != (i*2 + port_id))
        {
            REG_WR(pdev,MISC_REG_AEU_GENERAL_ATTN_12 + 4*(i*2 + port_id),0x1);
            DbgMessage(pdev, WARN, "sync_link_status: send attention to Func %d\n", (i*2 + port_id));
        }
    }
}

void
lm_reset_link(lm_device_t *pdev)
{
    if (IS_VFDEV(pdev))
    {
        DbgMessage(pdev, FATAL, "lm_reset_link not implemented for VF\n");
        return;

    }
    // notify stats
    lm_stats_on_link_update(pdev, FALSE );
    pdev->vars.link_status       = LM_STATUS_LINK_DOWN;
    pdev->vars.cable_is_attached = FALSE;
    pdev->vars.mac_type          = MAC_TYPE_NONE;

    PHY_HW_LOCK(pdev);
    elink_lfa_reset(&pdev->params.link,&pdev->vars.link);
    PHY_HW_UNLOCK(pdev);
}
/**
 * @description
 * Configure cmng the firmware to the right CMNG values if this
 * device is the PMF ,after link speed/ETS changes.
 *
 * @note This function must be called under PHY_LOCK
 * @param pdev
 */
void lm_cmng_update(lm_device_t *pdev)
{
    u32_t port_speed = 0;

    /* fairness is only supported for vnics in the meantime... */
    if ((!IS_MULTI_VNIC(pdev)) ||        
        (!pdev->vars.link.link_up))
    {
        return;
    }

    if (!IS_PMF(pdev) && !IS_MF_AFEX_MODE(pdev))
    {
        // in case we are not PMF we still want to run this code in AFEX mode.        
        return;
    }

    port_speed = lm_get_speed_real_from_elink_line_speed(&pdev->vars.link);

    lm_cmng_init(pdev,port_speed);
}

void lm_reload_link_and_cmng(lm_device_t *pdev)
{
    if( IS_MULTI_VNIC(pdev) && pdev->hw_info.mcp_detected )
    {
        lm_cmng_get_shmem_info(pdev);
        lm_cmng_calc_params(pdev);
    }

    get_link_params(pdev);

    lm_cmng_update(pdev);

}

void lm_link_fill_reported_data( IN lm_device_t *pdev, OUT lm_reported_link_params_t *lm_reported_link_params )
{
    lm_reported_link_params->cable_is_attached = pdev->vars.cable_is_attached;
    lm_reported_link_params->link              = pdev->vars.link_status;
    lm_reported_link_params->medium            = pdev->vars.medium;
    lm_reported_link_params->flow_ctrl         = pdev->vars.flow_control;
    lm_reported_link_params->eee_policy        = (u8_t)pdev->vars.eee_policy; // one of PORT_FEAT_CFG_EEE_POWER_MODE_*
}

// This function is called due to link change attention for none pmf it gets the link status from the shmem
void lm_link_report(lm_device_t *pdev)
{
    u8_t                      pause_ena           = 0;
    lm_reported_link_params_t current_link_params = {0};
    u8_t                      b_indicate          = TRUE;

    lm_reload_link_and_cmng(pdev);

    // get current link params into current_link_params
    lm_link_fill_reported_data(pdev, &current_link_params );

    /* Don't report link down again (if it is already down) */
    if( (LM_STATUS_LINK_DOWN == pdev->vars.last_reported_link_params.link) &&
        (LM_STATUS_LINK_DOWN == current_link_params.link) )
    {
        b_indicate = FALSE;
    }
    else
    {
        // Don't report exact same link status twice
        ASSERT_STATIC( sizeof(current_link_params) == sizeof(pdev->vars.last_reported_link_params) );
        b_indicate = ( FALSE == mm_memcmp( &current_link_params, &pdev->vars.last_reported_link_params, sizeof(current_link_params)) );
    }

    if (pdev->vars.link.link_up)
    {
        // link up
        // dropless flow control
        if (IS_PMF(pdev) && pdev->params.l2_fw_flow_ctrl)
        {
            if (pdev->vars.link.flow_ctrl & ELINK_FLOW_CTRL_TX)
            {
                pause_ena = 1;
            }
            LM_INTMEM_WRITE16(pdev,USTORM_ETH_PAUSE_ENABLED_OFFSET(PORT_ID(pdev)), pause_ena, BAR_USTRORM_INTMEM);
        }
        pdev->vars.mac_type = pdev->vars.link.mac_type;
        DbgBreakIf(pdev->vars.mac_type >= MAC_TYPE_MAX);

        // indicate link up - except if we're in NIV mode where we wait for the VIF-SET/enable command from the MCP.
        if( IS_MF_AFEX_MODE(pdev) )
        {
            b_indicate = FALSE;
        }

        // indicate link up
        if( b_indicate )
        {
            mm_indicate_link(pdev, pdev->vars.link_status, pdev->vars.medium);
            DbgMessage(pdev, WARN, "lm_link_update: indicate link %d 0x%x \n",pdev->vars.link_status,pdev->vars.medium);
        }

        // notify stats
        lm_stats_on_link_update(pdev, TRUE );
    }
    else
    {   // link down
        // indicate link down
        pdev->vars.mac_type = MAC_TYPE_NONE;
        pdev->vars.stats.stats_collect.stats_hw.b_is_link_up = FALSE;

        // indicate link down
        if( b_indicate )
        {
            mm_indicate_link(pdev, pdev->vars.link_status, pdev->vars.medium);
            DbgMessage(pdev, WARN, "lm_link_update: indicate link %d 0x%x \n",pdev->vars.link_status,pdev->vars.medium);
        }
    }

    // notify othres funcs
    if (IS_MULTI_VNIC(pdev) && IS_PMF(pdev))
    {
        sync_link_status(pdev);
    }
}

// This function is called due to link change interrupt for the relevant function
// NOTE: this function must be called under phy lock
lm_status_t lm_link_update(lm_device_t *pdev)
{
    if CHK_NULL( pdev )
    {
        DbgBreakIf(!pdev);
        return LM_STATUS_FAILURE;
    }
    // notify stats
    lm_stats_on_link_update(pdev, FALSE );

    if( pdev->params.i2c_interval_sec )
    {
        pdev->params.i2c_elink_status[I2C_SECTION_A0] = ELINK_STATUS_INVALID_IMAGE;
        pdev->params.i2c_elink_status[I2C_SECTION_A2] = ELINK_STATUS_INVALID_IMAGE;
    }

    PHY_HW_LOCK(pdev);
    elink_link_update(&pdev->params.link,&pdev->vars.link);
    PHY_HW_UNLOCK(pdev);
    lm_link_report(pdev);
    // increment link_chng_cnt counter to indicate there was some link change.
    pdev->vars.link_chng_cnt++;
    return LM_STATUS_SUCCESS;
}

static void lm_set_phy_selection( lm_device_t *pdev, u8_t i)
{
    u32 phy_sel ;
    if (pdev->params.link.multi_phy_config & PORT_HW_CFG_PHY_SWAPPED_ENABLED)
    {
        phy_sel = PORT_HW_CFG_PHY_SELECTION_SECOND_PHY - (i - ELINK_EXT_PHY1);
    }
    else
    {
        phy_sel = PORT_HW_CFG_PHY_SELECTION_FIRST_PHY + (i - ELINK_EXT_PHY1);
    }
    RESET_FLAGS( pdev->params.link.multi_phy_config, PORT_HW_CFG_PHY_SELECTION_MASK );
    SET_FLAGS( pdev->params.link.multi_phy_config, phy_sel);
}

static void lm_set_phy_priority_selection( lm_device_t *pdev, u8_t i)
{
    u32 phy_sel;

    if (pdev->params.link.multi_phy_config & PORT_HW_CFG_PHY_SWAPPED_ENABLED)
    {
        phy_sel = PORT_HW_CFG_PHY_SELECTION_SECOND_PHY_PRIORITY - (i - ELINK_EXT_PHY1);
    }
    else
    {
        phy_sel = PORT_HW_CFG_PHY_SELECTION_FIRST_PHY_PRIORITY + (i - ELINK_EXT_PHY1);
    }
    RESET_FLAGS( pdev->params.link.multi_phy_config, PORT_HW_CFG_PHY_SELECTION_MASK );
    SET_FLAGS( pdev->params.link.multi_phy_config, phy_sel);
}

/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC
lm_status_t lm_set_phy_priority_mode(lm_device_t *pdev)
{
    lm_status_t lm_status = LM_STATUS_SUCCESS;
    u8_t        i         = 0;

    if (CHK_NULL(pdev))
    {
        return LM_STATUS_INVALID_PARAMETER;
    }

    switch (pdev->params.phy_priority_mode)
    {
    case PHY_PRIORITY_MODE_HW_DEF:
        RESET_FLAGS( pdev->params.link.multi_phy_config, PORT_HW_CFG_PHY_SELECTION_MASK );
        SET_FLAGS( pdev->params.link.multi_phy_config, pdev->hw_info.multi_phy_config);
        break;

    case PHY_PRIORITY_MODE_10GBASET:
        i = ELINK_EXT_PHY1;
        while (i < ELINK_MAX_PHYS)
        {
            if (pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_BASE_T)
            {
                lm_set_phy_priority_selection(pdev, i);
                break;
                }
            i++;
            }
            break;

    case PHY_PRIORITY_MODE_SERDES:
        i = ELINK_EXT_PHY1;
        while (i < ELINK_MAX_PHYS)
        {
            if ((pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_SFPP_10G_FIBER) ||
                (pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_SFP_1G_FIBER)   ||
                (pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_XFP_FIBER)      ||
                (pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_DA_TWINAX)      ||
                (pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_NOT_PRESENT))
            {
                lm_set_phy_priority_selection(pdev, i);
                break;
            }
            i++;
        }
        break;

    case PHY_PRIORITY_MODE_HW_PIN:
        RESET_FLAGS( pdev->params.link.multi_phy_config, PORT_HW_CFG_PHY_SELECTION_MASK );
        SET_FLAGS( pdev->params.link.multi_phy_config, PORT_HW_CFG_PHY_SELECTION_HARDWARE_DEFAULT);
        break;

    default:
        DbgBreak();
        lm_status = LM_STATUS_FAILURE;
        break;
    }

    return lm_status;
}

/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC
lm_status_t lm_set_phy_link_params(lm_device_t     *pdev,
                                 lm_medium_t        req_medium,
                                 lm_flow_control_t  flow_control,
                                 u8_t               sw_config,
                                 u8_t               phy_num)
{
    lm_medium_t speed  = GET_MEDIUM_SPEED(req_medium);
    lm_medium_t duplex = GET_MEDIUM_DUPLEX(req_medium);

    DbgMessage(pdev, WARN, "lm_set_phy_link_params: speed 0x%x\n",speed);
    // Get speed from shared memory not registry - if mcp is detected...
    if(pdev->hw_info.mcp_detected && ((speed == LM_MEDIUM_SPEED_HARDWARE_DEFAULT) || (IS_MULTI_VNIC(pdev))))
    {
        DbgMessage(pdev, WARN, "lm_init_phy: pdev->hw_info.link_config[phy_num] = 0x%x\n",pdev->hw_info.link_config[phy_num]);
        switch(pdev->hw_info.link_config[phy_num] & PORT_FEATURE_LINK_SPEED_MASK)
        {

        case PORT_FEATURE_LINK_SPEED_10M_FULL:
            SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_10MBPS);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_FULL_DUPLEX);
            break;
        case PORT_FEATURE_LINK_SPEED_10M_HALF:
            SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_10MBPS);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_HALF_DUPLEX);
            break;
        case PORT_FEATURE_LINK_SPEED_100M_FULL:
            SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_100MBPS);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_FULL_DUPLEX);
            break;
        case PORT_FEATURE_LINK_SPEED_100M_HALF:
            SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_100MBPS);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_HALF_DUPLEX);
            break;
        case PORT_FEATURE_LINK_SPEED_1G:
            SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_1000MBPS);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_FULL_DUPLEX);
            break;
        case PORT_FEATURE_LINK_SPEED_2_5G:
            SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_2500MBPS);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_FULL_DUPLEX);
            break;
        case PORT_FEATURE_LINK_SPEED_10G_CX4:
            SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_10GBPS);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_FULL_DUPLEX);
            break;
    case PORT_FEATURE_LINK_SPEED_20G:
        SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_20GBPS);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_FULL_DUPLEX);
            break;
        case PORT_FEATURE_LINK_SPEED_AUTO:
            SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_AUTONEG);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_FULL_DUPLEX);
            break;
        default:
            //Follow Teton solution:We need to do this because Microsoft's definition
            // is not complete, like speed 2.5gb or some other speeds.
            SET_MEDIUM_SPEED(speed,LM_MEDIUM_SPEED_AUTONEG);
            SET_MEDIUM_DUPLEX(duplex,LM_MEDIUM_FULL_DUPLEX);
            break;
        }

        DbgMessage(pdev, WARN, "lm_set_phy_link_params: speed 0x%x duplex 0x%x\n",speed,duplex);
    }
    pdev->params.link.req_duplex[phy_num] = DUPLEX_FULL;
    if ( duplex == LM_MEDIUM_HALF_DUPLEX)
    {
        pdev->params.link.req_duplex[phy_num] = DUPLEX_HALF;
    }

    switch (speed)
    {
    case  LM_MEDIUM_SPEED_AUTONEG:
        pdev->params.link.req_line_speed[phy_num] = ELINK_SPEED_AUTO_NEG;
        break;
    case  LM_MEDIUM_SPEED_10MBPS:
        pdev->params.link.req_line_speed[phy_num] = ELINK_SPEED_10;
        break;
    case  LM_MEDIUM_SPEED_100MBPS:
        pdev->params.link.req_line_speed[phy_num] = ELINK_SPEED_100;
        break;
    case  LM_MEDIUM_SPEED_1000MBPS:
        pdev->params.link.req_line_speed[phy_num] = ELINK_SPEED_1000;
        break;
    case  LM_MEDIUM_SPEED_2500MBPS:
        pdev->params.link.req_line_speed[phy_num] = ELINK_SPEED_2500;
        break;
    case  LM_MEDIUM_SPEED_10GBPS:
        pdev->params.link.req_line_speed[phy_num] = ELINK_SPEED_10000;
        break;
    case  LM_MEDIUM_SPEED_20GBPS:
        pdev->params.link.req_line_speed[phy_num] = ELINK_SPEED_20000;
        break;
    default:
        DbgBreakIf(!DBG_BREAK_ON(UNDER_TEST));
        return LM_STATUS_INVALID_PARAMETER;
    }

    pdev->params.link.req_flow_ctrl[phy_num] = 0;
    if (flow_control == LM_FLOW_CONTROL_NONE)
    {
        pdev->params.link.req_flow_ctrl[phy_num] = ELINK_FLOW_CTRL_NONE;
    }
    else if (flow_control & LM_FLOW_CONTROL_AUTO_PAUSE)
    {
        pdev->params.link.req_flow_ctrl[phy_num] = ELINK_FLOW_CTRL_AUTO;
    }
    else
    {
        /* Under flow control reporting mode we */
        if ((speed == LM_MEDIUM_SPEED_AUTONEG) &&
            (pdev->params.flow_control_reporting_mode == LM_FLOW_CONTROL_REPORTING_MODE_ENABLED))
        {
            pdev->params.link.req_flow_ctrl[phy_num] = ELINK_FLOW_CTRL_AUTO;
        }
        else
        {
            if (flow_control & LM_FLOW_CONTROL_RECEIVE_PAUSE)
            {
                pdev->params.link.req_flow_ctrl[phy_num] |= ELINK_FLOW_CTRL_RX;
            }
            if (flow_control & LM_FLOW_CONTROL_TRANSMIT_PAUSE)
            {
                pdev->params.link.req_flow_ctrl[phy_num] |= ELINK_FLOW_CTRL_TX;
            }
        }
    }

    return LM_STATUS_SUCCESS;
}

/**
 * @Description
 *      this function sets the flow control auto negotiation
 *      advertise parameter.
 *
 * @param pdev
 * @param flow_control
 */
void lm_set_fc_auto_adv_params(lm_device_t * pdev, lm_flow_control_t flow_control)
{
    u16_t req_fc_auto_adv     = ELINK_FLOW_CTRL_BOTH;
    u8_t  mtu_above_thr       = FALSE;
    u8_t  report_mode_tx_only = FALSE;

    /* There are two cases where we will set flow control auto adv to TX only.
     * 1. Has to do with a bug in E1/E1x in which we can't support rx flow control if mtu is larger than
     *    a certain threshold. (mtu_above_th)
     * 2. cq CQ57772, required only under special registry key, in which we want the flow control displayed
     *    in gui (i.e. received by ioctl) to show the resolved flow control (after auto negotiation) and not
     *    the requested flow control (in case forced force control is used). For this purpose, if we're in auto-neg
     *    and a forced flow control was requested, we set the request flow control to auto (later on in set_link_parameters)
     *    if forced TX is requested, we se the adv to tx only..(report_mode_tx_only)
     */
    mtu_above_thr       = CHIP_IS_E1x(pdev) && !IS_MULTI_VNIC(pdev) && (pdev->params.mtu_max > LM_MTU_FLOW_CTRL_TX_THR);
    report_mode_tx_only = (pdev->params.flow_control_reporting_mode == LM_FLOW_CONTROL_REPORTING_MODE_ENABLED) &&
                          (flow_control == LM_FLOW_CONTROL_TRANSMIT_PAUSE);

    if (mtu_above_thr || report_mode_tx_only)
    {
        req_fc_auto_adv = ELINK_FLOW_CTRL_TX;
    }

    pdev->params.link.req_fc_auto_adv = req_fc_auto_adv;
}
/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
lm_status_t
lm_init_phy( lm_device_t       *pdev,
             lm_medium_t       req_medium,
             lm_flow_control_t flow_control,
             u32_t             selective_autoneg,
             u32_t             wire_speed,
             u32_t             wait_link_timeout_us)
{
    u8_t                   i                    = 0;
    u8_t                   sw_config            = 0;
    u8_t                   elink_status         = ELINK_STATUS_OK;
    lm_medium_t            speed                = 0;
    lm_medium_t            type                 = GET_MEDIUM_TYPE(req_medium);
    struct elink_params    *link                = &pdev->params.link;
    lm_status_t            lm_status            = LM_STATUS_SUCCESS;
    iscsi_info_block_hdr_t iscsi_info_block_hdr = {0} ;

    UNREFERENCED_PARAMETER_(wait_link_timeout_us);
    UNREFERENCED_PARAMETER_(wire_speed);
    UNREFERENCED_PARAMETER_(selective_autoneg);

    if (IS_VFDEV(pdev))
    {
        return LM_STATUS_SUCCESS;
    }

    //fill clc params
    if CHK_NULL( pdev )
    {
        DbgBreakIf(!pdev) ;
        return LM_STATUS_FAILURE;
    }

    // override preemphasis for specific svid/ssid
    if( 0x1120 == pdev->hw_info.svid )
    {
        switch (pdev->hw_info.ssid)
        {
        case 0x4f70:
        case 0x4375:
            {
                if( pdev->params.preemphasis_enable )
                {
                    // The relevant ssids are from SINGLE_MEDIA board type, so only EXT_PHY1 needs to be set.
                    SET_FLAGS(pdev->params.link.feature_config_flags, ELINK_FEATURE_CONFIG_OVERRIDE_PREEMPHASIS_ENABLED);
                    pdev->params.link.phy[ELINK_EXT_PHY1].rx_preemphasis[0] = (u16_t)pdev->params.preemphasis_rx_0;
                    pdev->params.link.phy[ELINK_EXT_PHY1].rx_preemphasis[1] = (u16_t)pdev->params.preemphasis_rx_1;
                    pdev->params.link.phy[ELINK_EXT_PHY1].rx_preemphasis[2] = (u16_t)pdev->params.preemphasis_rx_2;
                    pdev->params.link.phy[ELINK_EXT_PHY1].rx_preemphasis[3] = (u16_t)pdev->params.preemphasis_rx_3;
                    pdev->params.link.phy[ELINK_EXT_PHY1].tx_preemphasis[0] = (u16_t)pdev->params.preemphasis_tx_0;
                    pdev->params.link.phy[ELINK_EXT_PHY1].tx_preemphasis[1] = (u16_t)pdev->params.preemphasis_tx_1;
                    pdev->params.link.phy[ELINK_EXT_PHY1].tx_preemphasis[2] = (u16_t)pdev->params.preemphasis_tx_2;
                    pdev->params.link.phy[ELINK_EXT_PHY1].tx_preemphasis[3] = (u16_t)pdev->params.preemphasis_tx_3;
                }
            }
            break;

        default:
            break;
        }
    }

    /* Set req_fc_auto_adv */
    lm_set_fc_auto_adv_params(pdev, flow_control);

    for (i = 0 ; i < 6 ; i++)
    {
        pdev->params.link.mac_addr[i] = pdev->params.mac_addr[i];
    }

    sw_config = (u8_t)pdev->params.sw_config;
    DbgMessage(pdev, WARN, "lm_init_phy: sw_config 0x%x\n",sw_config);

    if (LM_SWCFG_HW_DEF == sw_config )
    {
        if (CHIP_IS_E1x(pdev) || CHIP_IS_E2(pdev))
        {
            sw_config = (u8_t)(pdev->params.link.switch_cfg>>PORT_FEATURE_CONNECTED_SWITCH_SHIFT);
        }
        else
        {
            sw_config = LM_SWCFG_10G;
        }
        DbgMessage(pdev, WARN, "lm_init_phy: sw_config 0x%x\n",sw_config);
    }

#ifndef EDIAG

    switch( pdev->params.autogreeen )
    {
        case LM_AUTOGREEEN_NVRAM:

            // Use whatever is configured in the NVRAM
            break;

        case LM_AUTOGREEEN_DISABLED:

            RESET_FLAGS(pdev->params.link.feature_config_flags, ELINK_FEATURE_CONFIG_AUTOGREEEN_ENABLED );

            RESET_FLAGS(pdev->params.link.eee_mode, // no EEE
                        ELINK_EEE_MODE_ENABLE_LPI |
                        ELINK_EEE_MODE_ADV_LPI);

            break;

        case LM_AUTOGREEEN_ENABLED:

            SET_FLAGS(pdev->params.link.feature_config_flags, ELINK_FEATURE_CONFIG_AUTOGREEEN_ENABLED );

            RESET_FLAGS(pdev->params.link.eee_mode, ELINK_EEE_MODE_OVERRIDE_NVRAM);
            RESET_FLAGS(pdev->params.link.eee_mode, ELINK_EEE_MODE_NVRAM_MASK);

            switch (pdev->params.eee_policy)
            {
                case LM_EEE_CONTROL_HIGH: // enable EEE mode, advertise "AGGRESSIVE" (Registry: MaxPowerSave)

                    SET_FLAGS(pdev->params.link.eee_mode,
                              ELINK_EEE_MODE_OVERRIDE_NVRAM |
                              ELINK_EEE_MODE_ADV_LPI |
                              ELINK_EEE_MODE_ENABLE_LPI |
                              PORT_FEAT_CFG_EEE_POWER_MODE_AGGRESSIVE );
                    break;

                case LM_EEE_CONTROL_MED: // enable EEE mode, advertise "BALANCED" (Registry: Balance)

                    SET_FLAGS(pdev->params.link.eee_mode,
                              ELINK_EEE_MODE_OVERRIDE_NVRAM |
                              ELINK_EEE_MODE_ADV_LPI |
                              ELINK_EEE_MODE_ENABLE_LPI |
                              PORT_FEAT_CFG_EEE_POWER_MODE_BALANCED);
                    break;

                case LM_EEE_CONTROL_LOW: // enable EEE mode, advertise "LOW_LATENCY" (Registry: MaxPerformace)

                    SET_FLAGS(pdev->params.link.eee_mode,
                              ELINK_EEE_MODE_OVERRIDE_NVRAM |
                              ELINK_EEE_MODE_ADV_LPI |
                              ELINK_EEE_MODE_ENABLE_LPI |
                              PORT_FEAT_CFG_EEE_POWER_MODE_LOW_LATENCY);
                    break;

                case LM_EEE_CONTROL_NVRAM: // use NVRAM value

                    SET_FLAGS(pdev->params.link.eee_mode,
                              ELINK_EEE_MODE_ENABLE_LPI |
                              ELINK_EEE_MODE_ADV_LPI);
                    break;

                default:

                    // break here if illegal value was read from registry (CHK version only).
                    DbgBreakIf(1);

                    break;
            }

            break;

        default:

            DbgBreakIf(1); // unknown value
    }

    DbgMessage(pdev, WARN, "lm_init_phy: autogreeen 0x%x\n", pdev->params.autogreeen);
#endif

    switch (sw_config)
    {
        // TODO change to shmem defines
        case LM_SWCFG_1G:
            SET_MEDIUM_TYPE(pdev->vars.medium, LM_MEDIUM_TYPE_SERDES);
            break;
        case LM_SWCFG_10G:
            SET_MEDIUM_TYPE(pdev->vars.medium, LM_MEDIUM_TYPE_XGXS);
            break;
        default:
            DbgBreakIf(1);
            break;
    }
    // Override setting if dual media and phy type specified from miniport
    if ((ELINK_DUAL_MEDIA(link)) &&
        ((type == LM_MEDIUM_TYPE_SERDES) ||
         (type == LM_MEDIUM_TYPE_XGXS)))
    {
        SET_MEDIUM_TYPE(pdev->vars.medium, type);
    }

    lm_status = lm_set_phy_link_params(pdev, req_medium, flow_control, sw_config, ELINK_INT_PHY);
    if (LM_STATUS_SUCCESS == lm_status) {
    if (ELINK_DUAL_MEDIA(link))
    {
        lm_set_phy_link_params(pdev, req_medium, flow_control, sw_config, ELINK_EXT_PHY1);
    }
    } else {
        return lm_status;
    }

    /* If 10G is requested and it is blocked on this KR, issue event log */
    if( pdev->hw_info.no_10g_kr )
    {
        speed = GET_MEDIUM_SPEED(req_medium);
        if( LM_MEDIUM_SPEED_10GBPS == speed )
        {
            DbgMessage(pdev, WARN, "lm_init_phy: 10gb speed parameter is blocked 0x%x\n",speed);

            // block this request (elink does not support it) & log
            mm_event_log_generic(pdev, LM_LOG_ID_NO_10G_SUPPORT, PORT_ID(pdev) );
            return LM_STATUS_SUCCESS;
        }
    }

    switch (type)
    {
    case LM_MEDIUM_TYPE_XGXS_LOOPBACK:
        pdev->params.link.loopback_mode = ELINK_LOOPBACK_XGXS;
        pdev->params.link.req_line_speed[0] = ELINK_SPEED_1000;
        break;
    case LM_MEDIUM_TYPE_XGXS_10_LOOPBACK:
        pdev->params.link.loopback_mode = ELINK_LOOPBACK_XGXS;
        // for bacs PHY loopback test set speed to 10G.
        // Otherwise do not overwrite the speed        
        if (!pdev->params.link.req_line_speed[0]) 
        {
             if (pdev->params.link.speed_cap_mask[0] & PORT_HW_CFG_SPEED_CAPABILITY2_D0_20G)
             {
                 pdev->params.link.req_line_speed[0] = ELINK_SPEED_20000;
             }
             else
             {
                 pdev->params.link.req_line_speed[0] = ELINK_SPEED_10000;
             }
        }
        break;
    case LM_MEDIUM_TYPE_EMAC_LOOPBACK:
        pdev->params.link.loopback_mode = ELINK_LOOPBACK_EMAC;
        break;
    case LM_MEDIUM_TYPE_BMAC_LOOPBACK:
        pdev->params.link.loopback_mode = ELINK_LOOPBACK_BMAC;
        break;
    case LM_MEDIUM_TYPE_EXT_PHY_LOOPBACK:
        pdev->params.link.loopback_mode = ELINK_LOOPBACK_EXT_PHY;
        if (pdev->params.link.speed_cap_mask[0] & PORT_HW_CFG_SPEED_CAPABILITY2_D0_20G)
        {
            pdev->params.link.req_line_speed[0] = ELINK_SPEED_20000;
        }
        else if (pdev->params.link.speed_cap_mask[0] & PORT_HW_CFG_SPEED_CAPABILITY2_D0_10G)
        {
            pdev->params.link.req_line_speed[0] = ELINK_SPEED_10000;
        }
        else
        {
            pdev->params.link.req_line_speed[0] = ELINK_SPEED_1000;
        }
        // TBD: Dual Media ext PHY loopback test for second ext PHY ?
        break;
    case LM_MEDIUM_TYPE_EXT_LOOPBACK:
        pdev->params.link.loopback_mode = ELINK_LOOPBACK_EXT;
        break;
    case LM_MEDIUM_TYPE_XMAC_LOOPBACK:
        pdev->params.link.loopback_mode = ELINK_LOOPBACK_XMAC;
        break;
    case LM_MEDIUM_TYPE_UMAC_LOOPBACK:
        pdev->params.link.loopback_mode = ELINK_LOOPBACK_UMAC;
        break;
    default:
        pdev->params.link.loopback_mode = ELINK_LOOPBACK_NONE;
        break;
    }

    // Handle dual media boards, if phy type specified from miniport
    if (ELINK_DUAL_MEDIA(link))
    {
        switch (type)
        {
        case LM_MEDIUM_TYPE_SERDES:
            i = ELINK_EXT_PHY1;
            while (i < ELINK_MAX_PHYS)
            {
                if ((pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_SFPP_10G_FIBER) ||
                    (pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_SFP_1G_FIBER) ||
                    (pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_XFP_FIBER) ||
                    (pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_DA_TWINAX))
                {
                    lm_set_phy_selection(pdev, i);
                    break;
                }
                i++;
            }
            break;

        case LM_MEDIUM_TYPE_XGXS:
            i = ELINK_EXT_PHY1;
            while (i < ELINK_MAX_PHYS)
            {
                if ((pdev->params.link.phy[i].media_type == ELINK_ETH_PHY_BASE_T))
                {
                    lm_set_phy_selection(pdev, i);
                    break;
                }
                i++;
            }
            break;

        case LM_MEDIUM_AUTO_DETECT:
            lm_set_phy_priority_mode(pdev);
            break;

        case LM_MEDIUM_TYPE_XGXS_LOOPBACK:
        case LM_MEDIUM_TYPE_XGXS_10_LOOPBACK:
        case LM_MEDIUM_TYPE_EMAC_LOOPBACK:
        case LM_MEDIUM_TYPE_BMAC_LOOPBACK:
        case LM_MEDIUM_TYPE_EXT_PHY_LOOPBACK:
        case LM_MEDIUM_TYPE_EXT_LOOPBACK:
        case LM_MEDIUM_TYPE_XMAC_LOOPBACK:
        case LM_MEDIUM_TYPE_UMAC_LOOPBACK:
            // Do nothing.
            break;
        default:
            DbgBreak();
            break;
        }
    }

    DbgMessage(pdev, WARN, "lm_init_phy: loopback_mode 0x%x\n",pdev->params.link.loopback_mode);
    if (IS_PMF(pdev))
    {
        if( pdev->params.i2c_interval_sec )
        {
            pdev->params.i2c_elink_status[I2C_SECTION_A0] = ELINK_STATUS_INVALID_IMAGE;
            pdev->params.i2c_elink_status[I2C_SECTION_A2] = ELINK_STATUS_INVALID_IMAGE;
        }
        if (lm_get_iscsi_boot_info_block(pdev,&iscsi_info_block_hdr) == LM_STATUS_SUCCESS)
        {
           if (iscsi_info_block_hdr.boot_flags & BOOT_INFO_FLAGS_UEFI_BOOT)
           {
              SET_FLAGS(pdev->params.link.feature_config_flags,ELINK_FEATURE_CONFIG_BOOT_FROM_SAN);
           }
        }

        PHY_HW_LOCK(pdev);
        elink_status = elink_phy_init(&pdev->params.link,&pdev->vars.link);
        PHY_HW_UNLOCK(pdev);
    }
    else
    {
        elink_link_status_update(&pdev->params.link,&pdev->vars.link);
    }
    // Emulation FPGA or LOOPBACK non pmf in multi vnic mode link might be up now
    lm_link_report(pdev);
    return LM_STATUS_SUCCESS;
} /* lm_init_phy */


#ifndef EDIAG
/*
 * \brief query i2c information if exists and write it to 3rd party known place
 *
 * \param pdev
 *
 * \return lm_status_t
 *
 */
lm_status_t lm_link_i2c_update(struct _lm_device_t *pdev)
{
    elink_status_t elink_status      = ELINK_STATUS_ERROR;
    u8_t           ext_phy_type   = 0;
    lm_status_t    lm_status      = LM_STATUS_SUCCESS;   
    const u64_t    current_ts     = mm_query_system_time(); // get current system time ms
    const u64_t    current_ms     = current_ts/10000; // get current system time ms
    const u64_t    interval_ms    = pdev->params.i2c_interval_sec*1000;
    const u64_t    delta_ms       = current_ms - (pdev->i2c_binary_info.last_query_ts/10000);
    const u8_t     b_need_refresh = ( interval_ms > 0 ) && ( delta_ms > interval_ms );
    u8_t           sff8472_comp   = 0;
    u8_t           diag_type      = 0;

    DbgBreakIf(!IS_PMF(pdev));

    if( !b_need_refresh )
    {
        // that means we need nothing here...
        return lm_status;
    }

    // Check which PHY controls the SFP+ module
    for( ext_phy_type = ELINK_EXT_PHY1; ext_phy_type < pdev->params.link.num_phys; ext_phy_type++ )
    {
        if(( ELINK_ETH_PHY_SFPP_10G_FIBER == pdev->params.link.phy[ext_phy_type].media_type )||
           ( ELINK_ETH_PHY_SFP_1G_FIBER   == pdev->params.link.phy[ext_phy_type].media_type )||
           ( ELINK_ETH_PHY_DA_TWINAX      == pdev->params.link.phy[ext_phy_type].media_type ))
            {
                pdev->i2c_binary_info.last_query_ts = current_ts;

                // Capture A0 section + static part of A2 section only once if A2 is supportd
                if (( pdev->params.i2c_elink_status[I2C_SECTION_A0] != ELINK_STATUS_OK) ||
                    ((pdev->params.i2c_elink_status[I2C_SECTION_A2] != ELINK_STATUS_OK) &&
                     (pdev->params.i2c_elink_status[I2C_SECTION_A2] != ELINK_OP_NOT_SUPPORTED)))
                {
                    PHY_HW_LOCK(pdev);
                    elink_status = elink_read_sfp_module_eeprom( &pdev->params.link.phy[ext_phy_type], // ELINK_INT_PHY, ELINK_EXT_PHY1, ELINK_EXT_PHY2
                                                              &pdev->params.link,
                                                              ELINK_I2C_DEV_ADDR_A0,
                                                              0,
                                                              I2C_BINARY_SIZE,
                                                              pdev->i2c_binary_info.ax_data[I2C_SECTION_A0] ) ;

                    pdev->params.i2c_elink_status[I2C_SECTION_A0] = elink_status;

                    if (pdev->params.i2c_elink_status[I2C_SECTION_A0] != ELINK_STATUS_OK)
                    {
                        PHY_HW_UNLOCK(pdev);

                        // Set same status to A2 section and quit as A0 is mandatory
                        pdev->params.i2c_elink_status[I2C_SECTION_A2] = elink_status;
                        break; // Quit the loop
                    }

                    // Check if the module is compliant with SFF8472, meaning it supports A2 section.
                    sff8472_comp = pdev->i2c_binary_info.ax_data[I2C_SECTION_A0][ELINK_SFP_EEPROM_SFF_8472_COMP_ADDR];
                    diag_type    = pdev->i2c_binary_info.ax_data[I2C_SECTION_A0][ELINK_SFP_EEPROM_DIAG_TYPE_ADDR];

                    if ( (!sff8472_comp) ||
                         ( diag_type & ELINK_SFP_EEPROM_DIAG_ADDR_CHANGE_REQ) )
                    {
                        // Release the HW LOCK
                        PHY_HW_UNLOCK(pdev);

                        // Set A2 section query status to NOT SUPPORTED and quit
                        pdev->params.i2c_elink_status[I2C_SECTION_A2] = ELINK_OP_NOT_SUPPORTED;

                        // Exit loop
                        break;
                    }

                    elink_status = elink_read_sfp_module_eeprom( &pdev->params.link.phy[ext_phy_type], // ELINK_INT_PHY, ELINK_EXT_PHY1, ELINK_EXT_PHY2
                                                                 &pdev->params.link,
                                                                 ELINK_I2C_DEV_ADDR_A2,
                                                                 I2C_A2_STATIC_OFFSET,
                                                                 I2C_A2_STATIC_SIZE,
                                                                 &pdev->i2c_binary_info.ax_data[I2C_SECTION_A2][I2C_A2_STATIC_OFFSET] ) ;
                    PHY_HW_UNLOCK(pdev);

                    pdev->params.i2c_elink_status[I2C_SECTION_A2] = elink_status;

                    if (pdev->params.i2c_elink_status[I2C_SECTION_A2] != ELINK_STATUS_OK)
                    {                        
                        break; // no use continue if we didn't get A2 data
                    }
                } // !ELINK_STATUS_OK

                /* Avoid reading A2 section if the module doesn't support SFF8472. */
                if (pdev->params.i2c_elink_status[I2C_SECTION_A2] == ELINK_OP_NOT_SUPPORTED)
                {
                    break;
                }

                // Capture the dynamic part of A2
                PHY_HW_LOCK(pdev);

                elink_status = elink_read_sfp_module_eeprom( &pdev->params.link.phy[ext_phy_type], // ELINK_INT_PHY, ELINK_EXT_PHY1, ELINK_EXT_PHY2
                                                          &pdev->params.link,
                                                          ELINK_I2C_DEV_ADDR_A2,
                                                          I2C_A2_DYNAMIC_OFFSET,
                                                          I2C_A2_DYNAMIC_SIZE,
                                                          &pdev->i2c_binary_info.ax_data[I2C_SECTION_A2][I2C_A2_DYNAMIC_OFFSET] );

                PHY_HW_UNLOCK(pdev);

                // Calculate and validate I2C section checksum
                if( ELINK_STATUS_OK == elink_status )
                {
                    elink_status = elink_validate_cc_dmi(pdev->i2c_binary_info.ax_data[I2C_SECTION_A2]);
                    if( ELINK_STATUS_OK != elink_status )
                    {
                        pdev->params.i2c_elink_status[I2C_SECTION_A2] = ELINK_STATUS_INVALID_IMAGE;
                    }
                }
                // only one sfp+ module is expected on board so we exit the ext_phy_type loop
                break;
        } // if( ELINK_ETH_PHY_SFPP_10G_FIBER == ...
    } // for "ext_phy_type"

    // it means that there is a need to write otherwise we even didn't enter the loop
    // so the registry write is redundent.
    if ( current_ts == pdev->i2c_binary_info.last_query_ts )
    {
        lm_status = mm_i2c_update(pdev);
    }
    return lm_status;
} /* lm_link_i2c_update */
#endif

/**
 * @Description
 *     This function is called periodically, every time the link
 *     timer expires, it's main purpose is to call elink under
 *     appropriate locks to perform any periodic tasks
 * @assumptions:
 *     called under UM_PHY_LOCK!
 *
 * @param pdev
 *
 * @return lm_status_t
 */
lm_status_t lm_link_on_timer(struct _lm_device_t *pdev)
{
    if (CHIP_REV_IS_SLOW(pdev))
    {
        return LM_STATUS_SUCCESS;
    }

    if (IS_PMF(pdev))
    {
        PHY_HW_LOCK(pdev);

        elink_period_func(&pdev->params.link, &pdev->vars.link);

        PHY_HW_UNLOCK(pdev);

#ifndef EDIAG
        lm_link_i2c_update(pdev);
#endif
    }

    return LM_STATUS_SUCCESS;
}
/*
 *Function Name:lm_get_external_phy_fw_version
 *
 *Parameters:
 *
 *Description:
 *  Funciton should be called under PHY_LOCK
 *Returns:
 *
 */
lm_status_t
lm_get_external_phy_fw_version( lm_device_t *pdev,
                                u8_t *      sz_version,
                                u8_t        len )
{
    u8_t        elink_status = ELINK_STATUS_OK;

    if ( CHK_NULL( sz_version ) || CHK_NULL( pdev ) )
    {
        return LM_STATUS_INVALID_PARAMETER;
    }

    // reset the returned value to zero
    *sz_version = '\0';

    elink_status = elink_get_ext_phy_fw_version(&pdev->params.link, (u8_t *)sz_version, len );

    if (elink_status == ELINK_STATUS_OK)
    {
        // Update internal hw_info structure for debugging purpose
        if( len <= sizeof(pdev->hw_info.sz_ext_phy_fw_ver) )
        {
            mm_memcpy( pdev->hw_info.sz_ext_phy_fw_ver,
                       sz_version,
                       min( (u32_t)sizeof(pdev->hw_info.sz_ext_phy_fw_ver), (u32_t)len) ) ;
        }
        return LM_STATUS_SUCCESS ;
    }
    else
    {
        return LM_STATUS_FAILURE;
    }
}

/*
 *Function Name:lm_update_external_phy_fw_prepare
 *
 *Parameters:
 *
 *Description:
 *
 *Returns:
 *
 */
lm_status_t
lm_update_external_phy_fw_prepare( lm_device_t *pdev )
{
    u8_t        elink_status = ELINK_STATUS_OK;
    lm_status_t lm_status  = LM_STATUS_SUCCESS;

    MM_ACQUIRE_PHY_LOCK(pdev);

    PHY_HW_LOCK(pdev);

    do
    {
        u32_t shmem_base[MAX_PATH_NUM], shmem_base2[MAX_PATH_NUM];
        shmem_base[0] = pdev->hw_info.shmem_base;
        shmem_base2[0] = pdev->hw_info.shmem_base2;

        if (!CHIP_IS_E1x(pdev))
        {
            LM_SHMEM2_READ(pdev, OFFSETOF(shmem2_region_t,other_shmem_base_addr), &shmem_base[1]);
            LM_SHMEM2_READ(pdev, OFFSETOF(shmem2_region_t,other_shmem2_base_addr), &shmem_base2[1]);
        }

        elink_common_init_phy(pdev, shmem_base, shmem_base2, CHIP_ID(pdev), 0);
        elink_pre_init_phy(pdev, shmem_base[0], shmem_base2[0], CHIP_ID(pdev), 0);

        if( ELINK_STATUS_OK != elink_status )
        {
            break;
        }

        elink_status = elink_phy_init(&pdev->params.link,&pdev->vars.link);
        if( ELINK_STATUS_OK != elink_status )
        {
            break;
        }

        elink_status = elink_link_reset(&pdev->params.link,&pdev->vars.link,0);

    } while(0);

    PHY_HW_UNLOCK(pdev);

    lm_link_report(pdev);

    MM_RELEASE_PHY_LOCK(pdev);

    if( ELINK_STATUS_OK != elink_status )
    {
        goto _exit;
    }

    switch( pdev->params.link.phy[ELINK_EXT_PHY1].type )
    {
    case PORT_HW_CFG_XGXS_EXT_PHY_TYPE_SFX7101:
        {
            lm_gpio_write(pdev, MISC_REGISTERS_GPIO_0, MISC_REGISTERS_GPIO_HIGH, PORT_ID(pdev) );
        }
        break;
    default:
        break;
    }

_exit:

    ELINK_STATUS_TO_LM_STATUS( elink_status, lm_status );

    return lm_status;
}

/*
 *Function Name:lm_update_external_phy_fw_reinit
 *
 *Parameters:
 *
 *Description:
 *
 *Returns:
 *
 */
lm_status_t
lm_update_external_phy_fw_reinit( lm_device_t *pdev )
{
    lm_status_t lm_status  = LM_STATUS_SUCCESS;
    u8_t        elink_status = ELINK_STATUS_OK;

    MM_ACQUIRE_PHY_LOCK(pdev);

    lm_reset_link(pdev);

    PHY_HW_LOCK(pdev);
    elink_status = elink_phy_init(&pdev->params.link,&pdev->vars.link);
    PHY_HW_UNLOCK(pdev);

    DbgBreakIf(ELINK_STATUS_OK != elink_status);

    // Emulation FPGA or LOOPBACK non pmf in multi vnic mode link might be up now
    lm_link_report(pdev);

    ELINK_STATUS_TO_LM_STATUS( elink_status, lm_status );

    if( LM_STATUS_SUCCESS == lm_status )
    {
        // in case success -reset version
        pdev->hw_info.sz_ext_phy_fw_ver[0] = '\0';
    }

    MM_RELEASE_PHY_LOCK(pdev);

    return lm_status;
}

/*
 *Function Name:lm_update_external_phy_fw_done
 *
 *Parameters:
 *
 *Description:
 *
 *Returns:
 *
 */
lm_status_t
lm_update_external_phy_fw_done( lm_device_t *pdev )
{
    lm_status_t lm_status    = LM_STATUS_SUCCESS;
    u8_t        ext_phy_addr = 0;
    u8_t        b_exit       = FALSE;

    MM_ACQUIRE_PHY_LOCK(pdev);
    switch( pdev->params.link.phy[ELINK_EXT_PHY1].type )
    {
    case PORT_HW_CFG_XGXS_EXT_PHY_TYPE_SFX7101:
        break;
    default:
        b_exit = TRUE;
        break;
    }
    if( b_exit )
    {
        MM_RELEASE_PHY_LOCK(pdev);
        return lm_status ;
    }

    ext_phy_addr = pdev->params.link.phy[ELINK_EXT_PHY1].addr;

    /* DSP Remove Download Mode */
    lm_gpio_write(pdev, MISC_REGISTERS_GPIO_0, MISC_REGISTERS_GPIO_LOW, PORT_ID(pdev) );

    PHY_HW_LOCK(pdev);
    elink_sfx7101_sp_sw_reset(pdev, &pdev->params.link.phy[ELINK_EXT_PHY1] );
    /* wait 0.5 sec to allow it to run */
    mm_wait( pdev, 500000);
    elink_ext_phy_hw_reset( pdev, PORT_ID(pdev) );
    mm_wait(pdev, 500000);
    PHY_HW_UNLOCK(pdev);

    MM_RELEASE_PHY_LOCK(pdev);

    return lm_status;
}

lm_status_t lm_check_phy_link_params(lm_device_t *pdev, lm_medium_t req_medium)
{
    lm_medium_t speed = GET_MEDIUM_SPEED(req_medium);
    lm_status_t lm_status = LM_STATUS_SUCCESS;

    if (IS_VFDEV(pdev))
    {
        return LM_STATUS_SUCCESS;
    }

    DbgMessage(pdev, WARN, "lm_check_phy_link_params: speed 0x%x\n",speed);
    // Get speed from registry not shared memory  - if mcp is not detected...
    if(!pdev->hw_info.mcp_detected || ((speed != LM_MEDIUM_SPEED_HARDWARE_DEFAULT) && (!IS_MULTI_VNIC(pdev))))
    {
        switch (speed)
        {
        case  LM_MEDIUM_SPEED_AUTONEG:
        case  LM_MEDIUM_SPEED_10MBPS:
        case  LM_MEDIUM_SPEED_100MBPS:
        case  LM_MEDIUM_SPEED_1000MBPS:
        case  LM_MEDIUM_SPEED_2500MBPS:
        case  LM_MEDIUM_SPEED_10GBPS:
        case  LM_MEDIUM_SPEED_20GBPS:
            break;
        default:
            DbgMessage(pdev, FATAL, "lm_check_phy_link_params: abnormal speed parameter 0x%x.\n",speed);
            lm_status = LM_STATUS_INVALID_PARAMETER;
        }
    }
    return lm_status;
}