/*
 * SPDX-FileCopyrightText: Copyright (c) 2018-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "nvlink_export.h"

#include "export_nvswitch.h"
#include "common_nvswitch.h"
#include "regkey_nvswitch.h"
#include "lr10/lr10.h"
#include "lr10/minion_lr10.h"

#include "nvswitch/lr10/dev_nvldl_ip.h"
#include "nvswitch/lr10/dev_nvldl_ip_addendum.h"
#include "nvswitch/lr10/dev_minion_ip_addendum.h"
#include "nvswitch/lr10/dev_nvlipt_lnk_ip.h"
#include "nvswitch/lr10/dev_nvlphyctl_ip.h"
#include "nvswitch/lr10/dev_nvltlc_ip.h"
#include "nvswitch/lr10/dev_minion_ip.h"
#include "nvswitch/lr10/dev_trim.h"
#include "nvswitch/lr10/dev_pri_ringstation_sys.h"
#include "nvswitch/lr10/dev_nvlperf_ip.h"
#include "nvswitch/lr10/dev_nvlipt_ip.h"
#include "nvswitch/lr10/dev_nport_ip.h"

void
nvswitch_setup_link_loopback_mode_lr10
(
    nvswitch_device *device,
    NvU32            linkNumber
)
{
    nvlink_link *link;
    NV_STATUS status;

    link = nvswitch_get_link(device, linkNumber);

    if ((link == NULL) ||
        !NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber) ||
        (linkNumber >= NVSWITCH_NVLINK_MAX_LINKS))
    {
        return;
    }

    if (device->link[link->linkNumber].nea)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Setting NEA on link %d\n",
            __FUNCTION__, link->linkNumber);

        status = nvswitch_minion_send_command(device, link->linkNumber,
                    NV_MINION_NVLINK_DL_CMD_COMMAND_SETNEA, 0);
        if (status != NVL_SUCCESS)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: SETNEA CMD failed for link %d.\n",
                __FUNCTION__, link->linkNumber);
        }
    }

    if (device->link[link->linkNumber].ned)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Setting NED on link %d\n",
            __FUNCTION__, link->linkNumber);

        // setting NEDR
        status = nvswitch_minion_send_command(device, link->linkNumber,
                    NV_MINION_NVLINK_DL_CMD_COMMAND_SETNEDR, 0);
        if (status != NVL_SUCCESS)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: SETNEDR CMD failed for link %d.\n",
                __FUNCTION__, link->linkNumber);
        }
        
        // setting NEDW
        status = nvswitch_minion_send_command(device, link->linkNumber,
                    NV_MINION_NVLINK_DL_CMD_COMMAND_SETNEDW, 0);
        if (status != NVL_SUCCESS)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: SETNEDW CMD failed for link %d.\n",
                __FUNCTION__, link->linkNumber);
        }
    }
}

static NV_STATUS
_nvswitch_ioctrl_setup_link_plls_lr10
(
    nvlink_link *link
)
{
    NV_STATUS status = NV_OK;
    NvU32     linkId, tempRegVal;
    NVSWITCH_TIMEOUT timeout;
    NvBool           keepPolling;

    nvswitch_device *device = link->dev->pDevInfo;
    linkId = link->linkNumber;

    if (IS_EMULATION(device))
    {
        NVSWITCH_PRINT(device, ERROR,"Skipping PLL init on emulation. \n");
        return status;
    }

    nvswitch_timeout_create(NVSWITCH_INTERVAL_1MSEC_IN_NS * 400, &timeout);

    do
    {
        keepPolling = (nvswitch_timeout_check(&timeout)) ? NV_FALSE : NV_TRUE;
        tempRegVal = NVSWITCH_LINK_RD32_LR10(device, linkId, NVLIPT_LNK , _NVLIPT_LNK , _CTRL_CLK_CTRL);

        if (FLD_TEST_DRF(_NVLIPT_LNK, _CTRL_CLK_CTRL, _PLL_PWR_STS, _ON, tempRegVal))
            break;

        nvswitch_os_sleep(1);
    } while (keepPolling == NV_TRUE);

    if (FLD_TEST_DRF(_NVLIPT_LNK, _CTRL_CLK_CTRL, _PLL_PWR_STS, _OFF, tempRegVal))
    {
        NVSWITCH_PRINT(device, ERROR,
                  "PLL_PWR_STS did not turn _ON for linkId = 0x%x!!\n", linkId);
        return NV_ERR_TIMEOUT;
    }

    // Request Minion to setup the NVLink clocks
    status = nvswitch_minion_send_command(device, linkId,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_TXCLKSWITCH_PLL, 0); 
    if (status != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR,
                  "Error sending TXCLKSWITCH_PLL command to MINION. Link = %d\n", linkId);
        NVSWITCH_ASSERT_INFO(NV_ERR_NVLINK_CLOCK_ERROR, NVBIT32(link->linkNumber), TXCLKSWITCH_PLL_ERROR);
        return NV_ERR_NVLINK_CLOCK_ERROR;
    }

    // Poll for the links to switch to NVLink clocks
    nvswitch_timeout_create(NVSWITCH_INTERVAL_1MSEC_IN_NS * 400, &timeout);

    do
    {
        keepPolling = (nvswitch_timeout_check(&timeout)) ? NV_FALSE : NV_TRUE;
        tempRegVal = NVSWITCH_LINK_RD32_LR10(device, linkId, NVLIPT_LNK , _NVLIPT_LNK , _CTRL_CLK_CTRL);
        if (FLD_TEST_DRF(_NVLIPT_LNK, _CTRL_CLK_CTRL, _TXCLK_STS, _PLL_CLK, tempRegVal))
            break;

        nvswitch_os_sleep(1);
    } while (keepPolling == NV_TRUE);

    if (!FLD_TEST_DRF(_NVLIPT_LNK, _CTRL_CLK_CTRL, _TXCLK_STS, _PLL_CLK, tempRegVal))
    {
        // Print the links for which we were unable to switch to PLL clock
        NVSWITCH_PRINT(device, ERROR,
                  "TXCLK_STS did not switch to _PLL_CLOCK for linkId = 0x%x!!\n", linkId);
        return NV_ERR_TIMEOUT;
    }

    return status;
}

NvBool
nvswitch_is_link_in_reset_lr10
(
    nvswitch_device *device,
    nvlink_link     *link
)
{
    NvU32 val;
    val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber,
            NVLIPT_LNK, _NVLIPT_LNK, _RESET_RSTSEQ_LINK_RESET);

    return (FLD_TEST_DRF(_NVLIPT_LNK, _RESET_RSTSEQ_LINK_RESET, _LINK_RESET_STATUS,
                _ASSERTED, val)) ? NV_TRUE : NV_FALSE;
}

NvlStatus
nvswitch_poll_sublink_state_lr10
(
    nvswitch_device *device,
    nvlink_link *link
)
{
    NVSWITCH_TIMEOUT timeout;
    NvBool           keepPolling;
    NvU32 val;
    NvBool bPreSiPlatform = (IS_RTLSIM(device) || IS_EMULATION(device) || IS_FMODEL(device));

    nvswitch_timeout_create(NVSWITCH_INTERVAL_1MSEC_IN_NS * (bPreSiPlatform ? 2000: 200), &timeout);

    do
    {
        keepPolling = (nvswitch_timeout_check(&timeout)) ? NV_FALSE : NV_TRUE;

        val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _SUBLINK_CHANGE);

        if (FLD_TEST_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _STATUS, _FAULT, val))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s : Fault while changing sublink state (%s):(%s).\n",
                __FUNCTION__, device->name, link->linkName);
            return -NVL_ERR_INVALID_STATE;
        }

        if (FLD_TEST_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _STATUS, _DONE, val))
        {
            break;
        }

        nvswitch_os_sleep(1);
    }
    while (keepPolling);

    if ((!FLD_TEST_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _STATUS, _DONE, val)))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s : Timeout while waiting sublink state (%s):(%s).\n",
            __FUNCTION__, device->name, link->linkName);
        return -NVL_ERR_GENERIC;
    }

    return NVL_SUCCESS;
}

static NvlStatus
_nvswitch_init_dl_pll
(
    nvlink_link *link
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvlStatus status;

    status = nvswitch_minion_send_command(device, link->linkNumber, NV_MINION_NVLINK_DL_CMD_COMMAND_INITPLL, 0);
    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: INITPLL failed for link %d.\n",
            __FUNCTION__, link->linkNumber);
        NVSWITCH_ASSERT_INFO(NV_ERR_NVLINK_CLOCK_ERROR, NVBIT32(link->linkNumber), INITPLL_ERROR);
        return NV_ERR_NVLINK_CLOCK_ERROR;
    }

    status = _nvswitch_ioctrl_setup_link_plls_lr10(link);
    if (status != NV_OK){
        return status;
    }

    status = nvswitch_minion_send_command(device, link->linkNumber, NV_MINION_NVLINK_DL_CMD_COMMAND_INITPHY, 0);
    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: INITPHY failed for link %d.\n",
            __FUNCTION__, link->linkNumber);
        NVSWITCH_ASSERT_INFO(NV_ERR_NVLINK_INIT_ERROR, NVBIT32(link->linkNumber), INITPHY_ERROR);
        return NV_ERR_NVLINK_INIT_ERROR;
    }


    return NVL_SUCCESS;
}

NvU32
nvswitch_get_sublink_width_lr10
(
    nvswitch_device *device,
    NvU32            linkNumber
)
{
    return NVSWITCH_NUM_LANES_LR10;
}

void
nvswitch_init_dlpl_interrupts_lr10
(
    nvlink_link *link
)
{
    nvswitch_device *device            = link->dev->pDevInfo;
    NvU32            linkNumber        = link->linkNumber;
    NvU32            crcShortRegkeyVal = device->regkeys.crc_bit_error_rate_short;
    NvU32            crcLongRegkeyVal  = device->regkeys.crc_bit_error_rate_long;
    NvU32            intrRegVal;
    NvU32            crcRegVal;
    NvU32            shortRateMask;
    NvU32            longRateMask;

    ct_assert(DRF_BASE(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_THRESHOLD_MAN)   == 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_SHORT_THRESHOLD_MAN));
    ct_assert(DRF_EXTENT(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_THRESHOLD_MAN) == 
              DRF_EXTENT(NV_NVLDL_RX_ERROR_RATE_CTRL_SHORT_THRESHOLD_MAN));
    ct_assert(DRF_BASE(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_THRESHOLD_EXP)   == 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_SHORT_THRESHOLD_EXP));
    ct_assert(DRF_EXTENT(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_THRESHOLD_EXP) == 
              DRF_EXTENT(NV_NVLDL_RX_ERROR_RATE_CTRL_SHORT_THRESHOLD_EXP));
    ct_assert(DRF_BASE(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_TIMESCALE_MAN)   == 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_SHORT_TIMESCALE_MAN));
    ct_assert(DRF_EXTENT(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_TIMESCALE_MAN) == 
              DRF_EXTENT(NV_NVLDL_RX_ERROR_RATE_CTRL_SHORT_TIMESCALE_MAN));
    ct_assert(DRF_BASE(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_TIMESCALE_EXP)   == 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_SHORT_TIMESCALE_EXP));
    ct_assert(DRF_EXTENT(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_TIMESCALE_EXP) == 
              DRF_EXTENT(NV_NVLDL_RX_ERROR_RATE_CTRL_SHORT_TIMESCALE_EXP));
    
    ct_assert(DRF_BASE(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_THRESHOLD_MAN)    == 
             (DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN)   - 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN)));
    ct_assert(DRF_EXTENT(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_THRESHOLD_MAN)  == 
             (DRF_EXTENT(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN) - 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN)));
    ct_assert(DRF_BASE(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_THRESHOLD_EXP)    == 
             (DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_EXP)   - 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN)));
    ct_assert(DRF_EXTENT(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_THRESHOLD_EXP)  == 
             (DRF_EXTENT(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_EXP) - 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN)));
    ct_assert(DRF_BASE(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_TIMESCALE_MAN)    == 
             (DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_TIMESCALE_MAN)   - 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN)));
    ct_assert(DRF_EXTENT(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_TIMESCALE_MAN)  == 
             (DRF_EXTENT(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_TIMESCALE_MAN) - 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN)));
    ct_assert(DRF_BASE(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_TIMESCALE_EXP)    == 
             (DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_TIMESCALE_EXP)   - 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN)));
    ct_assert(DRF_EXTENT(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_TIMESCALE_EXP)  == 
             (DRF_EXTENT(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_TIMESCALE_EXP) - 
              DRF_BASE(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN)));

    // W1C any stale state.
    NVSWITCH_LINK_WR32_LR10(device, linkNumber, NVLDL, _NVLDL_TOP, _INTR, 0xffffffff);
    NVSWITCH_LINK_WR32_LR10(device, linkNumber, NVLDL, _NVLDL_TOP, _INTR_SW2, 0xffffffff);

    // Stall tree routes to INTR_A which is connected to NVLIPT fatal tree
    NVSWITCH_LINK_WR32_LR10(device, linkNumber, NVLDL, _NVLDL_TOP, _INTR_STALL_EN,
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _TX_REPLAY, _DISABLE)               |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _TX_RECOVERY_SHORT, _DISABLE)       |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _LTSSM_FAULT_UP, _ENABLE)           |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _TX_FAULT_RAM, _ENABLE)             |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _TX_FAULT_INTERFACE, _ENABLE)       |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _TX_FAULT_SUBLINK_CHANGE, _DISABLE) |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _RX_FAULT_SUBLINK_CHANGE, _DISABLE) |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _RX_FAULT_DL_PROTOCOL, _ENABLE)     |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _RX_SHORT_ERROR_RATE, _DISABLE)     |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _RX_LONG_ERROR_RATE, _DISABLE)      |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _RX_ILA_TRIGGER, _DISABLE)          |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _RX_CRC_COUNTER, _DISABLE)          |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _LTSSM_PROTOCOL, _DISABLE)          |
              DRF_DEF(_NVLDL_TOP, _INTR_STALL_EN, _MINION_REQUEST, _DISABLE));

    // NONSTALL -> NONFATAL
    NVSWITCH_LINK_WR32_LR10(device, linkNumber, NVLDL, _NVLDL_TOP, _INTR_NONSTALL_EN,
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _TX_REPLAY, _DISABLE)               |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _TX_RECOVERY_SHORT, _DISABLE)       |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _LTSSM_FAULT_UP, _DISABLE)          |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _TX_FAULT_RAM, _DISABLE)            |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _TX_FAULT_INTERFACE, _DISABLE)      |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _TX_FAULT_SUBLINK_CHANGE, _DISABLE) |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _RX_FAULT_SUBLINK_CHANGE, _DISABLE) |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _RX_FAULT_DL_PROTOCOL, _DISABLE)    |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _RX_SHORT_ERROR_RATE, _DISABLE)     |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _RX_LONG_ERROR_RATE, _DISABLE)      |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _RX_ILA_TRIGGER, _DISABLE)          |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _RX_CRC_COUNTER, _ENABLE)           |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _LTSSM_PROTOCOL, _DISABLE)          |
              DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _MINION_REQUEST, _DISABLE));

    intrRegVal = NVSWITCH_LINK_RD32_LR10(device, linkNumber, NVLDL, 
                                         _NVLDL_TOP, _INTR_NONSTALL_EN);
    crcRegVal  = NVSWITCH_LINK_RD32_LR10(device, linkNumber, NVLDL, 
                                         _NVLDL_RX, _ERROR_RATE_CTRL);

    // Enable RX error rate short interrupt if the regkey is set
    if (crcShortRegkeyVal != NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_OFF)
    {
        shortRateMask = DRF_SHIFTMASK(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_THRESHOLD_MAN)     |
                            DRF_SHIFTMASK(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_THRESHOLD_EXP) |
                            DRF_SHIFTMASK(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_TIMESCALE_MAN) |
                            DRF_SHIFTMASK(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_SHORT_TIMESCALE_EXP);
        
        intrRegVal |= DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _RX_SHORT_ERROR_RATE, _ENABLE);
        crcRegVal  &= ~shortRateMask;
        crcRegVal  |= crcShortRegkeyVal;
    }
    // Enable RX error rate long interrupt if the regkey is set
    if (crcLongRegkeyVal != NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_OFF)
    {
        longRateMask = DRF_SHIFTMASK(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_THRESHOLD_MAN)      |
                            DRF_SHIFTMASK(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_THRESHOLD_EXP) |
                            DRF_SHIFTMASK(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_TIMESCALE_MAN) |
                            DRF_SHIFTMASK(NV_SWITCH_REGKEY_CRC_BIT_ERROR_RATE_LONG_TIMESCALE_EXP);

        intrRegVal |= DRF_DEF(_NVLDL_TOP, _INTR_NONSTALL_EN, _RX_LONG_ERROR_RATE, _ENABLE);
        crcRegVal  &= ~longRateMask;
        crcRegVal  |= crcLongRegkeyVal << DRF_SHIFT(NV_NVLDL_RX_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN);
    }

    NVSWITCH_LINK_WR32_LR10(device, linkNumber, NVLDL, 
                            _NVLDL_TOP, _INTR_NONSTALL_EN, intrRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNumber, NVLDL, 
                            _NVLDL_RX, _ERROR_RATE_CTRL, crcRegVal);
}

static void
_nvswitch_disable_dlpl_interrupts
(
    nvlink_link *link
)
{
    nvswitch_device *device    = link->dev->pDevInfo;
    NvU32           linkNumber = link->linkNumber;

    NVSWITCH_LINK_WR32_LR10(device, linkNumber, NVLDL, _NVLDL_TOP, _INTR_STALL_EN,    0x0);
    NVSWITCH_LINK_WR32_LR10(device, linkNumber, NVLDL, _NVLDL_TOP, _INTR_NONSTALL_EN, 0x0);
}

void
nvswitch_store_topology_information_lr10
(
    nvswitch_device *device,
    nvlink_link *link
)
{
    NvU32            tempval;

    link->bInitnegotiateConfigGood = NV_TRUE;
    link->remoteSid = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK,
                                             _NVLIPT_LNK, _TOPOLOGY_REMOTE_CHIP_SID_HI);
    link->remoteSid = link->remoteSid << 32;
    link->remoteSid |= NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK,
                                             _NVLIPT_LNK, _TOPOLOGY_REMOTE_CHIP_SID_LO);

    tempval = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK, _NVLIPT_LNK, _TOPOLOGY_REMOTE_LINK_INFO);
    link->remoteLinkId = DRF_VAL(_NVLIPT_LNK, _TOPOLOGY_REMOTE_LINK_INFO, _LINK_NUMBER, tempval);

    link->localSid = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT,
                                            _NVLIPT_COMMON, _TOPOLOGY_LOCAL_CHIP_SID_HI);
    link->localSid = link->localSid << 32;
    link->localSid |= NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT,
                                             _NVLIPT_COMMON, _TOPOLOGY_LOCAL_CHIP_SID_LO);

    tempval = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK,
                                             _NVLIPT_LNK, _TOPOLOGY_REMOTE_CHIP_TYPE);

    // Update the remoteDeviceType with NV2080_CTRL_NVLINK_DEVICE_INFO_DEVICE_TYPE values.
    switch(tempval)
    {
        case NV_NVLIPT_LNK_TOPOLOGY_REMOTE_CHIP_TYPE_TYPE_NV3P0AMP:
            link->remoteDeviceType = NVSWITCH_NVLINK_DEVICE_INFO_DEVICE_TYPE_GPU;
        break;
        case NV_NVLIPT_LNK_TOPOLOGY_REMOTE_CHIP_TYPE_TYPE_NV3P0LRK:
            link->remoteDeviceType = NVSWITCH_NVLINK_DEVICE_INFO_DEVICE_TYPE_SWITCH;
        break;
        default:
            link->remoteDeviceType = NVSWITCH_NVLINK_DEVICE_INFO_DEVICE_TYPE_NONE;
        break;
    }
}

void
nvswitch_init_lpwr_regs_lr10
(
    nvlink_link *link
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvU32 linkNum = link->linkNumber;
    NvU32 tempRegVal, icLimit, fbIcInc, lpIcInc, fbIcDec, lpIcDec, lpEntryThreshold;
    NvU32 lpExitThreshold;
    NvU8  softwareDesired, hardwareDisable;
    NvBool bLpEnable;

    if (device->regkeys.enable_pm == NV_SWITCH_REGKEY_ENABLE_PM_NO)
    {
        return;
    }

    //
    // Power Management threshold settings
    // These settings are currently being hard coded.
    // They will be parsed from the VBIOS NVLink LPWR table once bug 2767390 is
    // implemented
    //

    // IC Limit
    icLimit = 16110000;

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_TX_LNK, _PWRM_IC_LIMIT, _LIMIT, icLimit,
        tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _PWRM_IC_LIMIT,
        tempRegVal);

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_RX_LNK,_PWRM_IC_LIMIT, _LIMIT, icLimit,
        tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _PWRM_IC_LIMIT,
        tempRegVal);

    //IC Inc
    fbIcInc = 1;
    lpIcInc = 1;

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_TX_LNK, _PWRM_IC_INC, _FBINC, fbIcInc, tempRegVal);
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_TX_LNK, _PWRM_IC_INC, _LPINC, lpIcInc, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _PWRM_IC_INC,
        tempRegVal);

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_RX_LNK, _PWRM_IC_INC, _FBINC, fbIcInc, tempRegVal);
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_RX_LNK, _PWRM_IC_INC, _LPINC, lpIcInc, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _PWRM_IC_INC,
        tempRegVal);

    //IC Dec
    fbIcDec = 1;
    lpIcDec = 65535;

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_TX_LNK, _PWRM_IC_DEC, _FBDEC, fbIcDec, tempRegVal);
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_TX_LNK, _PWRM_IC_DEC, _LPDEC, lpIcDec, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _PWRM_IC_DEC,
        tempRegVal);

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_RX_LNK, _PWRM_IC_DEC, _FBDEC, fbIcDec,   tempRegVal);
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_RX_LNK, _PWRM_IC_DEC, _LPDEC, lpIcDec, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _PWRM_IC_DEC,
        tempRegVal);

    //IC Enter Threshold
    lpEntryThreshold = 16110000;

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_TX_LNK, _PWRM_IC_LP_ENTER_THRESHOLD, _THRESHOLD, lpEntryThreshold, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _PWRM_IC_LP_ENTER_THRESHOLD,
        tempRegVal);

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_RX_LNK, _PWRM_IC_LP_ENTER_THRESHOLD, _THRESHOLD, lpEntryThreshold, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _PWRM_IC_LP_ENTER_THRESHOLD,
        tempRegVal);

    //IC Exit Threshold
    lpExitThreshold = 16044465;

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_TX_LNK, _PWRM_IC_LP_EXIT_THRESHOLD, _THRESHOLD, lpExitThreshold, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _PWRM_IC_LP_EXIT_THRESHOLD,
        tempRegVal);

    tempRegVal = 0;
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_RX_LNK, _PWRM_IC_LP_EXIT_THRESHOLD, _THRESHOLD, lpExitThreshold, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _PWRM_IC_LP_EXIT_THRESHOLD,
        tempRegVal);

    //LP Entry Enable
    bLpEnable = NV_TRUE;
    softwareDesired = (bLpEnable) ? 0x1 : 0x0;
    hardwareDisable = (bLpEnable) ? 0x0 : 0x1;

    tempRegVal = NVSWITCH_LINK_RD32_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _PWRM_IC_SW_CTRL);
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_TX_LNK, _PWRM_IC_SW_CTRL, _SOFTWAREDESIRED,
                    softwareDesired, tempRegVal);
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_TX_LNK, _PWRM_IC_SW_CTRL, _HARDWAREDISABLE,
                    hardwareDisable, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _PWRM_IC_SW_CTRL,
                    tempRegVal);

    tempRegVal = NVSWITCH_LINK_RD32_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _PWRM_IC_SW_CTRL);
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_RX_LNK, _PWRM_IC_SW_CTRL, _SOFTWAREDESIRED,
                    softwareDesired, tempRegVal);
    tempRegVal = FLD_SET_DRF_NUM(_NVLTLC_RX_LNK, _PWRM_IC_SW_CTRL, _HARDWAREDISABLE,
                    hardwareDisable, tempRegVal);
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _PWRM_IC_SW_CTRL,
                    tempRegVal);
}


void
nvswitch_init_buffer_ready_lr10
(
    nvswitch_device *device,
    nvlink_link *link,
    NvBool bNportBufferReady
)
{
    NvU32 val;
    NvU32 linkNum = link->linkNumber;

    if (FLD_TEST_DRF(_SWITCH_REGKEY, _SKIP_BUFFER_READY, _TLC, _NO,
                     device->regkeys.skip_buffer_ready))
    {
        val = DRF_NUM(_NVLTLC_RX_SYS, _CTRL_BUFFER_READY, _BUFFERRDY, 0x1);
        NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_SYS, _CTRL_BUFFER_READY, val);
        val = DRF_NUM(_NVLTLC_TX_SYS, _CTRL_BUFFER_READY, _BUFFERRDY, 0x1);
        NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_SYS, _CTRL_BUFFER_READY, val);
    }

    if (bNportBufferReady &&
        FLD_TEST_DRF(_SWITCH_REGKEY, _SKIP_BUFFER_READY, _NPORT, _NO,
                     device->regkeys.skip_buffer_ready))
    {
        val = DRF_NUM(_NPORT, _CTRL_BUFFER_READY, _BUFFERRDY, 0x1);
        NVSWITCH_LINK_WR32_LR10(device, linkNum, NPORT, _NPORT, _CTRL_BUFFER_READY, val);
    }
}

static void
_nvswitch_configure_reserved_throughput_counters
(
    nvlink_link *link
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvU32 linkNum = link->linkNumber;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLTLC, link->linkNumber))
    {
        NVSWITCH_PRINT(device, INFO,
                       "Invalid link, skipping NVLink throughput counter config for link %d\n",
                       link->linkNumber);
        return;
    }

    //
    // Counters 0 and 2 will be reserved for monitoring tools
    // Counters 1 and 3 will be user-configurable and used by devtools
    //

    // Rx0 config
    NVSWITCH_LINK_WR32_IDX_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, 0,
        DRF_DEF(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, _UNIT, _FLITS)      |
        DRF_DEF(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _DATA) |
        DRF_DEF(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, _VCSETFILTERMODE, _INIT));

    // Tx0 config
    NVSWITCH_LINK_WR32_IDX_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, 0,
        DRF_DEF(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, _UNIT, _FLITS)      |
        DRF_DEF(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _DATA) |
        DRF_DEF(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, _VCSETFILTERMODE, _INIT));

    // Rx2 config
    NVSWITCH_LINK_WR32_IDX_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, 2,
        DRF_DEF(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, _UNIT, _FLITS)      |
        DRF_DEF(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _HEAD) |
        DRF_DEF(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _AE) |
        DRF_DEF(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _BE) |
        DRF_DEF(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _DATA) |
        DRF_DEF(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL_0, _VCSETFILTERMODE, _INIT));

    // Tx2 config
    NVSWITCH_LINK_WR32_IDX_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, 2,
        DRF_DEF(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, _UNIT, _FLITS)      |
        DRF_DEF(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _HEAD) |
        DRF_DEF(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _AE) |
        DRF_DEF(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _BE) |
        DRF_DEF(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, _FLITFILTER, _DATA) |
        DRF_DEF(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL_0, _VCSETFILTERMODE, _INIT));

    // Enable Rx for counters 0, 2
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL,
        DRF_NUM(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL, _ENRX0, 0x1) |
        DRF_NUM(_NVLTLC_RX_LNK, _DEBUG_TP_CNTR_CTRL, _ENRX2, 0x1));

    // Enable Tx for counters 0, 2
    NVSWITCH_LINK_WR32_LR10(device, linkNum, NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL,
        DRF_NUM(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL, _ENTX0, 0x1) |
        DRF_NUM(_NVLTLC_TX_LNK, _DEBUG_TP_CNTR_CTRL, _ENTX2, 0x1));
}

static NvlStatus
_nvswitch_init_link_post_active
(
    nvlink_link *link,
    NvU32       flags
)
{
    NvlStatus       status = NVL_SUCCESS;
    nvswitch_device *device = link->dev->pDevInfo;

    nvswitch_init_lpwr_regs(link);
    status = nvswitch_request_tl_link_state_lr10(link,
                NV_NVLIPT_LNK_CTRL_LINK_STATE_REQUEST_REQUEST_ACTIVE,
                flags == NVLINK_STATE_CHANGE_SYNC);
    if (status != NVL_SUCCESS)
    {
        return status;
    }

    // Note: buffer_rdy should be asserted last!
    nvswitch_init_buffer_ready(device, link, NV_TRUE);

    return status;
}

static void
_nvswitch_power_down_link_plls
(
    nvlink_link *link
)
{
    NvlStatus       status = NVL_SUCCESS;
    nvswitch_device *device = link->dev->pDevInfo;

    if (IS_EMULATION(device))
    {
        NVSWITCH_PRINT(device, ERROR,"Skipping PLL init on emulation. \n");
        return;
    }

    status = nvswitch_minion_send_command(device, link->linkNumber,
        NV_MINION_NVLINK_DL_CMD_COMMAND_TXCLKSWITCH_ALT, 0);

    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: TXCLKSWITCH_ALT CMD failed for link %d.\n",
            __FUNCTION__, link->linkNumber);
        return;
    }

    return;
}

NvlStatus
nvswitch_corelib_add_link_lr10
(
    nvlink_link *link
)
{
    return NVL_SUCCESS;
}

NvlStatus
nvswitch_corelib_remove_link_lr10
(
    nvlink_link *link
)
{
    return NVL_SUCCESS;
}

NvlStatus
nvswitch_corelib_set_dl_link_mode_lr10
(
    nvlink_link *link,
    NvU64 mode,
    NvU32 flags
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvU32           val;
    NvU32           link_state;
    NvlStatus       status = NVL_SUCCESS;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_UNBOUND_DEVICE;
    }

    switch (mode)
    {
        case NVLINK_LINKSTATE_SAFE:
        {
            // check if link is in reset
            if (nvswitch_is_link_in_reset(device, link))
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s: link #%d is still in reset, cannot change link state\n",
                    __FUNCTION__, link->linkNumber);
                return NVL_ERR_INVALID_STATE;
            }

            val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_STATE);
            link_state = DRF_VAL(_NVLDL_TOP, _LINK_STATE, _STATE, val);

            if (link_state == NV_NVLDL_TOP_LINK_STATE_STATE_SWCFG)
            {
                NVSWITCH_PRINT(device, INFO,
                    "%s : Link is already in Safe mode for (%s).\n",
                    __FUNCTION__, link->linkName);
                break;
            }
            else if (link_state == NV_NVLDL_TOP_LINK_STATE_STATE_HWCFG)
            {
                NVSWITCH_PRINT(device, INFO,
                    "%s : Link already transitioning to Safe mode for (%s).\n",
                    __FUNCTION__, link->linkName);
                break;
            }

            NVSWITCH_PRINT(device, INFO,
                "NVRM: %s : Changing Link state to Safe for (%s):(%s).\n",
                __FUNCTION__, device->name, link->linkName);

            if (link_state == NV_NVLDL_TOP_LINK_STATE_STATE_INIT)
            {
                val = 0;
                val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_CHANGE);
                val = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _NEWSTATE, _HWCFG, val);
                val = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _OLDSTATE_MASK, _DONTCARE, val);
                val = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _ACTION, _LTSSM_CHANGE, val);
                NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_CHANGE, val);
            }
            else if (link_state == NV_NVLDL_TOP_LINK_STATE_STATE_ACTIVE)
            {
                val = 0;
                val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_CHANGE);
                val = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _NEWSTATE, _SWCFG, val);
                val = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _OLDSTATE_MASK, _DONTCARE, val);
                val = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _ACTION, _LTSSM_CHANGE, val);
                NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_CHANGE, val);
            }
            else
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : Link is in invalid state"
                    " cannot set to safe state (%s):(%s). (%x) (%x)\n",
                    __FUNCTION__, device->name, link->linkName, val, link_state);
                return -NVL_ERR_INVALID_STATE;
            }

            break;
        }

        case NVLINK_LINKSTATE_HS:
        {
            // check if link is in reset
            if (nvswitch_is_link_in_reset(device, link))
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s: link #%d is still in reset, cannot change link state\n",
                    __FUNCTION__, link->linkNumber);
                return NVL_ERR_INVALID_STATE;
            }

            val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_STATE);
            link_state = DRF_VAL(_NVLDL_TOP, _LINK_STATE, _STATE, val);

            if (link_state == NV_NVLDL_TOP_LINK_STATE_STATE_ACTIVE)
            {
                NVSWITCH_PRINT(device, INFO,
                    "%s : Link is already in Active mode (%s).\n",
                    __FUNCTION__, link->linkName);
                break;
            }
            else if (link_state == NV_NVLDL_TOP_LINK_STATE_STATE_INIT)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : Link cannot be taken from INIT state to"
                    " Active mode for (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return -NVL_ERR_INVALID_STATE;
            }
            else if (link_state == NV_NVLDL_TOP_LINK_STATE_STATE_SWCFG)
            {
                NVSWITCH_PRINT(device, INFO,
                    "%s : Changing Link state to Active for (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);

                val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_CHANGE);
                val = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _NEWSTATE, _ACTIVE, val);
                val = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _OLDSTATE_MASK, _DONTCARE, val);
                val = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _ACTION, _LTSSM_CHANGE, val);
                NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_CHANGE, val);
            }
            else
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : Link is in invalid state"
                    " cannot set to active state (%s):(%s). (%x) (%x)\n",
                    __FUNCTION__, device->name, link->linkName, val, link_state);
                return -NVL_ERR_INVALID_STATE;
            }

            break;
        }

        case NVLINK_LINKSTATE_OFF:
        {
            _nvswitch_power_down_link_plls(link);

            if (nvswitch_lib_notify_client_events(device,
                        NVSWITCH_DEVICE_EVENT_PORT_DOWN) != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR, "%s: Failed to notify PORT_DOWN event\n",
                             __FUNCTION__);
            }

            break;
        }

        case NVLINK_LINKSTATE_RESET:
        {
            break;
        }

        case NVLINK_LINKSTATE_ENABLE_PM:
        {
            if (device->regkeys.enable_pm == NV_SWITCH_REGKEY_ENABLE_PM_YES)
            {
                status = nvswitch_minion_send_command(device, link->linkNumber,
                    NV_MINION_NVLINK_DL_CMD_COMMAND_ENABLEPM, 0);

                if (status != NVL_SUCCESS)
                {
                    NVSWITCH_PRINT(device, ERROR,
                        "%s: ENABLEPM CMD failed for link %d.\n",
                        __FUNCTION__, link->linkNumber);
                    return status;
                }
            }
            break;
        }

        case NVLINK_LINKSTATE_DISABLE_PM:
        {
            if (device->regkeys.enable_pm == NV_SWITCH_REGKEY_ENABLE_PM_YES)
            {
                status = nvswitch_minion_send_command(device, link->linkNumber,
                    NV_MINION_NVLINK_DL_CMD_COMMAND_DISABLEPM, 0);

                if (status != NVL_SUCCESS)
                {
                    NVSWITCH_PRINT(device, ERROR,
                        "%s: DISABLEPM CMD failed for link %d.\n",
                        __FUNCTION__, link->linkNumber);
                    return status;
                }
            }
            break;
        }

        case NVLINK_LINKSTATE_DISABLE_HEARTBEAT:
        {
            // NOP
            break;
        }

        case NVLINK_LINKSTATE_PRE_HS:
        {
            break;
        }

        case NVLINK_LINKSTATE_TRAFFIC_SETUP:
        {
            status = _nvswitch_init_link_post_active(link, flags);
            if (status != NVL_SUCCESS)
            {
                return status;
            }

            break;
        }

        case NVLINK_LINKSTATE_DISABLE_ERR_DETECT:
        {
            // Disable DL/PL interrupts
            _nvswitch_disable_dlpl_interrupts(link);
            break;
        }

        case NVLINK_LINKSTATE_LANE_DISABLE:
        {
            status = nvswitch_minion_send_command(device, link->linkNumber,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_LANEDISABLE, 0);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : LANEDISABLE CMD failed for link (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return status;
            }

            break;
        }

        case NVLINK_LINKSTATE_LANE_SHUTDOWN:
        {
            status = nvswitch_minion_send_command(device, link->linkNumber,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_LANESHUTDOWN, 0);

            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : SHUTDOWN CMD failed for link (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return status;
            }

            break;
        }

        case NVLINK_LINKSTATE_INITPHASE1:
        {

           status = nvswitch_minion_send_command(device, link->linkNumber,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_INITPHASE1, 0);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : INITPHASE1 failed for link (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                NVSWITCH_ASSERT_INFO(NV_ERR_NVLINK_CONFIGURATION_ERROR, 
                    NVBIT32(link->linkNumber), INITPHASE1_ERROR);
                return NV_ERR_NVLINK_CONFIGURATION_ERROR;
            }

            // After INITPHASE1, apply NEA setting
            nvswitch_setup_link_loopback_mode(device, link->linkNumber);
            break;
        }

        case NVLINK_LINKSTATE_INITOPTIMIZE:
        {
            status = nvswitch_minion_send_command(device, link->linkNumber,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_INITOPTIMIZE, 0);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : INITOPTIMIZE failed for link (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return status;
            }

            break;
        }

        case NVLINK_LINKSTATE_POST_INITOPTIMIZE:
        {
            // Poll for TRAINING_GOOD
            status  = nvswitch_minion_get_initoptimize_status_lr10(device, link->linkNumber);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                            "%s Error polling for INITOPTIMIZE TRAINING_GOOD. Link (%s):(%s)\n",
                            __FUNCTION__, device->name, link->linkName);
                NVSWITCH_ASSERT_INFO(NV_ERR_NVLINK_TRAINING_ERROR, NVBIT32(link->linkNumber), INITOPTIMIZE_ERROR);
                return NV_ERR_NVLINK_TRAINING_ERROR;
            }

            // Send INITTL DLCMD
            status = nvswitch_minion_send_command(device, link->linkNumber,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_INITTL, 0);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : INITTL failed for link (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                NVSWITCH_ASSERT_INFO(NV_ERR_NVLINK_TRAINING_ERROR, NVBIT32(link->linkNumber), INITTL_ERROR);
                return NV_ERR_NVLINK_TRAINING_ERROR;
            }

            break;
        }

        case NVLINK_LINKSTATE_INITNEGOTIATE:
        {
            status = nvswitch_minion_send_command(device, link->linkNumber,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_INITNEGOTIATE, 0);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : INITNEGOTIATE failed for link (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return status;
            }

            break;
        }

        case NVLINK_LINKSTATE_POST_INITNEGOTIATE:
        {
            // Poll for CONFIG_GOOD
            status  = nvswitch_minion_get_initnegotiate_status_lr10(device, link->linkNumber);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                            "%s Error polling for INITNEGOTIATE CONFIG_GOOD. Link (%s):(%s)\n",
                            __FUNCTION__, device->name, link->linkName);
                NVSWITCH_ASSERT_INFO(NV_ERR_NVLINK_CONFIGURATION_ERROR,
                    NVBIT32(link->linkNumber), INITNEGOTIATE_ERROR);
                return NV_ERR_NVLINK_CONFIGURATION_ERROR;
            }
            else
            {
                nvswitch_store_topology_information(device, link);
            }

            break;
        }

        default:
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s : Invalid mode specified.\n",
                __FUNCTION__);
            break;
        }
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_corelib_get_dl_link_mode_lr10
(
    nvlink_link *link,
    NvU64 *mode
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvU32 link_state;
    NvU32 val = 0;

    *mode = NVLINK_LINKSTATE_OFF;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_UNBOUND_DEVICE;
    }

    // check if links are in reset
    if (nvswitch_is_link_in_reset(device, link))
    {
        *mode = NVLINK_LINKSTATE_RESET;
        return NVL_SUCCESS;
    }

    val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_STATE);

    link_state = DRF_VAL(_NVLDL_TOP, _LINK_STATE, _STATE, val);

    switch (link_state)
    {
        case NV_NVLDL_TOP_LINK_STATE_STATE_INIT:
            *mode = NVLINK_LINKSTATE_OFF;
            break;
        case NV_NVLDL_TOP_LINK_STATE_STATE_HWCFG:
            *mode = NVLINK_LINKSTATE_DETECT;
            break;
        case NV_NVLDL_TOP_LINK_STATE_STATE_SWCFG:
            *mode = NVLINK_LINKSTATE_SAFE;
            break;
        case NV_NVLDL_TOP_LINK_STATE_STATE_ACTIVE:
            *mode = NVLINK_LINKSTATE_HS;
            break;
        case NV_NVLDL_TOP_LINK_STATE_STATE_FAULT:
            *mode = NVLINK_LINKSTATE_FAULT;
            break;
        case NV_NVLDL_TOP_LINK_STATE_STATE_RCVY_AC:
        case NV_NVLDL_TOP_LINK_STATE_STATE_RCVY_RX:
            *mode = NVLINK_LINKSTATE_RECOVERY;
            break;
        default:
            *mode = NVLINK_LINKSTATE_OFF;
            break;
    }

    return NVL_SUCCESS;
}

void
nvswitch_corelib_get_uphy_load_lr10
(
    nvlink_link *link,
    NvBool *bUnlocked
)
{
    *bUnlocked = NV_FALSE;
}

NvlStatus
nvswitch_corelib_set_tl_link_mode_lr10
(
    nvlink_link *link,
    NvU64 mode,
    NvU32 flags
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvlStatus       status = NVL_SUCCESS;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_UNBOUND_DEVICE;
    }

    switch (mode)
    {
        case NVLINK_LINKSTATE_RESET:
        {
            // perform TL reset
            NVSWITCH_PRINT(device, INFO,
                "%s: Performing TL Reset on link %d\n",
                __FUNCTION__, link->linkNumber);

            status = nvswitch_request_tl_link_state_lr10(link,
                NV_NVLIPT_LNK_CTRL_LINK_STATE_REQUEST_REQUEST_RESET,
                flags == NVLINK_STATE_CHANGE_SYNC);

            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s: NvLink Reset has failed for link %d\n",
                    __FUNCTION__, link->linkNumber);
                return status;
            }
            break;
        }

        default:
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s : Invalid mode specified.\n",
                __FUNCTION__);
            break;
        }
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_corelib_get_tl_link_mode_lr10
(
    nvlink_link *link,
    NvU64 *mode
)
{
#if defined(INCLUDE_NVLINK_LIB)

    nvswitch_device *device = link->dev->pDevInfo;
    NvU32 link_state;
    NvU32 val = 0;

    *mode = NVLINK_LINKSTATE_OFF;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_UNBOUND_DEVICE;
    }

    // check if links are in reset
    if (nvswitch_is_link_in_reset(device, link))
    {
        *mode = NVLINK_LINKSTATE_RESET;
        return NVL_SUCCESS;
    }

    // Read state from NVLIPT HW
    val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK,
            _NVLIPT_LNK, _CTRL_LINK_STATE_STATUS);

    link_state = DRF_VAL(_NVLIPT_LNK, _CTRL_LINK_STATE_STATUS, _CURRENTLINKSTATE,
            val);

    switch(link_state)
    {
        case NV_NVLIPT_LNK_CTRL_LINK_STATE_STATUS_CURRENTLINKSTATE_ACTIVE:
            *mode = NVLINK_LINKSTATE_HS;
            break;

        case NV_NVLIPT_LNK_CTRL_LINK_STATE_STATUS_CURRENTLINKSTATE_L2:
            *mode = NVLINK_LINKSTATE_SLEEP;
            break;

        case NV_NVLIPT_LNK_CTRL_LINK_STATE_STATUS_CURRENTLINKSTATE_CONTAIN:
            *mode = NVLINK_LINKSTATE_CONTAIN;
            break;

        default:
            // Currently, only ACTIVE, L2 and CONTAIN states are supported
            return NVL_ERR_INVALID_STATE;
            break;
    }

#endif

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_corelib_set_tx_mode_lr10
(
    nvlink_link *link,
    NvU64 mode,
    NvU32 flags
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvU32 tx_sublink_state;
    NvU32 val;
    NvlStatus status = NVL_SUCCESS;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_UNBOUND_DEVICE;
    }

    // check if link is in reset
    if (nvswitch_is_link_in_reset(device, link))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d is still in reset, cannot change sub-link state\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_ERR_INVALID_STATE;
    }

    val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TX, _SLSM_STATUS_TX);

    tx_sublink_state = DRF_VAL(_NVLDL_TX, _SLSM_STATUS_TX, _PRIMARY_STATE, val);

    // Check if Sublink State Machine is ready to accept a sublink change request.
    status = nvswitch_poll_sublink_state(device, link);
    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s : SLSM not ready to accept a state change request for(%s):(%s).\n",
            __FUNCTION__, device->name, link->linkName);
        return status;
    }

    switch (mode)
    {
        case NVLINK_SUBLINK_STATE_TX_COMMON_MODE:
        {
            val = _nvswitch_init_dl_pll(link);
            if (val != NVL_SUCCESS)
            {
                return val;
            }

            break;
        }

        case NVLINK_SUBLINK_STATE_TX_COMMON_MODE_DISABLE:
        {
            // Not applicable for NV IP
            break;
        }

        case NVLINK_SUBLINK_STATE_TX_DATA_READY:
        {
            status = nvswitch_minion_send_command(device, link->linkNumber,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_INITDLPL, 0);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s: INITNVLDL CMD failed for link %d.\n",
                    __FUNCTION__, link->linkNumber);
                NVSWITCH_ASSERT_INFO(NV_ERR_NVLINK_INIT_ERROR,
                    NVBIT32(link->linkNumber), INITDLPL_ERROR);
                return NV_ERR_NVLINK_INIT_ERROR;
            }

            status = nvswitch_minion_send_command(device, link->linkNumber,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_INITLANEENABLE, 0);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s: INITLANEENABLE CMD failed for link %d.\n",
                    __FUNCTION__, link->linkNumber);
                NVSWITCH_ASSERT_INFO(NV_ERR_NVLINK_INIT_ERROR, NVBIT32(link->linkNumber), INITLANEENABLE_ERROR);
                return NV_ERR_NVLINK_INIT_ERROR;
            }

            break;
        }

        case NVLINK_SUBLINK_STATE_TX_PRBS_EN:
        {
            // Not needed with ALT
            break;
        }

        case NVLINK_SUBLINK_STATE_TX_POST_HS:
        {
          // NOP: In general, there is no point to downgrade *_PRBS_* and *_SCRAM_* values.
          break;
        }

        case NVLINK_SUBLINK_STATE_TX_EQ:
        {
            //TODO: To be implemented
            break;
        }

        case NVLINK_SUBLINK_STATE_TX_HS:
        {
            // Not needed with ALT
            break;
        }

        case NVLINK_SUBLINK_STATE_TX_SAFE:
        {
            if (tx_sublink_state == NV_NVLDL_TX_SLSM_STATUS_TX_PRIMARY_STATE_SAFE)
            {
                NVSWITCH_PRINT(device, INFO,
                    "%s : TX already in Safe mode for  (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                break;
            }

            NVSWITCH_PRINT(device, INFO,
                  "%s : Changing TX sublink state to Safe mode for (%s):(%s).\n",
                  __FUNCTION__, device->name, link->linkName);

            val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _SUBLINK_CHANGE);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _NEWSTATE, _SAFE, val);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _SUBLINK, _TX, val);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _ACTION, _SLSM_CHANGE, val);
            NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _SUBLINK_CHANGE, val);

            status = nvswitch_poll_sublink_state(device, link);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : Error while changing TX sublink to Safe Mode for (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return status;
            }

            break;
        }

        case NVLINK_SUBLINK_STATE_TX_OFF:
        {
            if (tx_sublink_state == NV_NVLDL_TX_SLSM_STATUS_TX_PRIMARY_STATE_OFF)
            {
                NVSWITCH_PRINT(device, INFO,
                    "%s : TX already OFF (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                break;
            }
            else if (tx_sublink_state == NV_NVLDL_TX_SLSM_STATUS_TX_PRIMARY_STATE_HS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : TX cannot be taken from HS to OFF directly for (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return -NVL_ERR_GENERIC;
            }

            NVSWITCH_PRINT(device, INFO,
                "%s : Changing TX sublink state to OFF for (%s):(%s).\n",
                __FUNCTION__, device->name, link->linkName);

            val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _SUBLINK_CHANGE);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _COUNTDOWN, _IMMEDIATE, val);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _NEWSTATE, _OFF, val);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _SUBLINK, _TX, val);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _ACTION, _SLSM_CHANGE, val);
            NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _SUBLINK_CHANGE, val);

            status = nvswitch_poll_sublink_state(device, link);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : Error while changing TX sublink to off Mode for (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return status;
            }
            break;
        }

        default:
            NVSWITCH_PRINT(device, ERROR,
                 "%s : Invalid TX sublink mode specified.\n",
                __FUNCTION__);
            break;
    }

    return status;
}

NvlStatus
nvswitch_corelib_get_tx_mode_lr10
(
    nvlink_link *link,
    NvU64 *mode,
    NvU32 *subMode
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvU32 tx_sublink_state;
    NvU32 data = 0;

    *mode = NVLINK_SUBLINK_STATE_TX_OFF;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_UNBOUND_DEVICE;
    }

    // check if link is in reset
    if (nvswitch_is_link_in_reset(device, link))
    {
        *mode = NVLINK_SUBLINK_STATE_TX_OFF;
        return NVL_SUCCESS;
    }

    data = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TX, _SLSM_STATUS_TX);

    tx_sublink_state = DRF_VAL(_NVLDL_TX, _SLSM_STATUS_TX, _PRIMARY_STATE, data);

    // Return NVLINK_SUBLINK_SUBSTATE_TX_STABLE for sub-state
    *subMode = NVLINK_SUBLINK_SUBSTATE_TX_STABLE;

    switch (tx_sublink_state)
    {
        case NV_NVLDL_TX_SLSM_STATUS_TX_PRIMARY_STATE_EIGHTH:
            *mode = NVLINK_SUBLINK_STATE_TX_SINGLE_LANE;
            break;

        case NV_NVLDL_TX_SLSM_STATUS_TX_PRIMARY_STATE_HS:
            *mode = NVLINK_SUBLINK_STATE_TX_HS;
            break;

        case NV_NVLDL_TX_SLSM_STATUS_TX_PRIMARY_STATE_TRAIN:
            *mode = NVLINK_SUBLINK_STATE_TX_TRAIN;
            break;

        case NV_NVLDL_TX_SLSM_STATUS_TX_PRIMARY_STATE_SAFE:
            *mode = NVLINK_SUBLINK_STATE_TX_SAFE;
            break;

        case NV_NVLDL_TX_SLSM_STATUS_TX_PRIMARY_STATE_OFF:
            *mode = NVLINK_SUBLINK_STATE_TX_OFF;
            break;

        default:
            *mode = NVLINK_SUBLINK_STATE_TX_OFF;
            break;
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_corelib_set_rx_mode_lr10
(
    nvlink_link *link,
    NvU64 mode,
    NvU32 flags
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvU32 rx_sublink_state;
    NvU32 val;
    NvlStatus status = NVL_SUCCESS;
    NvU32 delay_ns;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_UNBOUND_DEVICE;
    }

    // check if link is in reset
    if (nvswitch_is_link_in_reset(device, link))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d is still in reset, cannot change sub-link state\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_ERR_INVALID_STATE;
    }


    val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_RX, _SLSM_STATUS_RX);

    rx_sublink_state = DRF_VAL(_NVLDL_RX, _SLSM_STATUS_RX, _PRIMARY_STATE, val);

    // Check if Sublink State Machine is ready to accept a sublink change request.
    status = nvswitch_poll_sublink_state(device, link);
    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s : SLSM not ready to accept a state change request for(%s):(%s).\n",
            __FUNCTION__, device->name, link->linkName);
        return status;
    }

    switch (mode)
    {
        case NVLINK_SUBLINK_STATE_RX_HS:
            break;

        case NVLINK_SUBLINK_STATE_RX_SAFE:
            break;

        case NVLINK_SUBLINK_STATE_RX_OFF:
        {
            if (rx_sublink_state == NV_NVLDL_RX_SLSM_STATUS_RX_PRIMARY_STATE_OFF)
            {
                NVSWITCH_PRINT(device, INFO,
                    "%s : RX already OFF (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                break;
            }
            else if (rx_sublink_state == NV_NVLDL_RX_SLSM_STATUS_RX_PRIMARY_STATE_HS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : RX cannot be taken from HS to OFF directly for (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                status = -NVL_ERR_GENERIC;
                return status;
            }

            NVSWITCH_PRINT(device, INFO,
                "%s : Changing RX sublink state to OFF for (%s):(%s).\n",
                __FUNCTION__, device->name, link->linkName);

            val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _SUBLINK_CHANGE);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _COUNTDOWN, _IMMEDIATE, val);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _OLDSTATE_MASK, _DONTCARE, val);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _NEWSTATE, _OFF, val);
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _SUBLINK, _RX, val);

            // When changing RX sublink state use FORCE, otherwise it will fault.
            val = FLD_SET_DRF(_NVLDL_TOP, _SUBLINK_CHANGE, _ACTION, _SLSM_FORCE, val);
            NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _SUBLINK_CHANGE, val);

            NVSWITCH_PRINT(device, INFO,
                "%s : NV_NVLDL_TOP_SUBLINK_CHANGE = 0x%08x\n", __FUNCTION__, val);

            status = nvswitch_poll_sublink_state(device, link);
            if (status != NVL_SUCCESS)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : Error while changing RX sublink to Off Mode for (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return status;
            }
            break;
        }

        case NVLINK_SUBLINK_STATE_RX_RXCAL:
        {
            // Enable RXCAL in CFG_CTL_6, Delay 200us (bug 2551877), and check CFG_STATUS_0 for RXCAL_DONE=1.
            val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLPHYCTL_COMMON, _CFG_CTL_6);
            val = FLD_SET_DRF(_NVLPHYCTL_COMMON, _CFG_CTL_6, _RXCAL , _ON, val);
            NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLDL, _NVLPHYCTL_COMMON, _CFG_CTL_6, val);

            if (IS_FMODEL(device) || IS_EMULATION(device) || IS_RTLSIM(device))
            {
                delay_ns = NVSWITCH_INTERVAL_1SEC_IN_NS;
            }
            else
            {
                delay_ns = 200 * NVSWITCH_INTERVAL_1USEC_IN_NS;
            }

            NVSWITCH_NSEC_DELAY(delay_ns);

            val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLPHYCTL_COMMON, _CFG_STATUS_0);
            if (!FLD_TEST_DRF_NUM(_NVLPHYCTL_COMMON, _CFG_STATUS_0, _RXCAL_DONE, 0x1, val))
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s: Timeout while waiting for RXCAL_DONE on link %d.\n",
                    __FUNCTION__, link->linkNumber);
                return -NVL_ERR_GENERIC;
            }
            break;
        }

        case NVLINK_SUBLINK_STATE_RX_INIT_TERM:
        {
            // Invoke MINION routine to enable RX Termination
            status = nvswitch_minion_set_rx_term_lr10(device, link->linkNumber);

            if (status != NV_OK)
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s : Error while setting RX INIT_TERM for (%s):(%s).\n",
                    __FUNCTION__, device->name, link->linkName);
                return status;
            }

            break;
        }


        default:
            NVSWITCH_PRINT(device, ERROR,
                "%s : Invalid RX sublink mode specified.\n",
                __FUNCTION__);
            break;
    }

    return status;
}

NvlStatus
nvswitch_corelib_get_rx_mode_lr10
(
    nvlink_link *link,
    NvU64 *mode,
    NvU32 *subMode
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvU32 rx_sublink_state;
    NvU32 data = 0;

    *mode = NVLINK_SUBLINK_STATE_RX_OFF;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_UNBOUND_DEVICE;
    }

    // check if link is in reset
    if (nvswitch_is_link_in_reset(device, link))
    {
        *mode = NVLINK_SUBLINK_STATE_RX_OFF;
        return NVL_SUCCESS;
    }

    data = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_RX, _SLSM_STATUS_RX);

    rx_sublink_state = DRF_VAL(_NVLDL_RX, _SLSM_STATUS_RX, _PRIMARY_STATE, data);

    // Return NVLINK_SUBLINK_SUBSTATE_RX_STABLE for sub-state
    *subMode = NVLINK_SUBLINK_SUBSTATE_RX_STABLE;

    switch (rx_sublink_state)
    {
        case NV_NVLDL_RX_SLSM_STATUS_RX_PRIMARY_STATE_HS:
            *mode = NVLINK_SUBLINK_STATE_RX_HS;
            break;

        case NV_NVLDL_RX_SLSM_STATUS_RX_PRIMARY_STATE_TRAIN:
            *mode = NVLINK_SUBLINK_STATE_RX_TRAIN;
            break;

        case NV_NVLDL_RX_SLSM_STATUS_RX_PRIMARY_STATE_SAFE:
            *mode = NVLINK_SUBLINK_STATE_RX_SAFE;
            break;

        case NV_NVLDL_RX_SLSM_STATUS_RX_PRIMARY_STATE_EIGHTH:
            *mode = NVLINK_SUBLINK_STATE_RX_SINGLE_LANE;
            break;

        case NV_NVLDL_RX_SLSM_STATUS_RX_PRIMARY_STATE_OFF:
            *mode = NVLINK_SUBLINK_STATE_RX_OFF;
            break;

        default:
            *mode = NVLINK_SUBLINK_STATE_RX_OFF;
            break;
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_corelib_set_rx_detect_lr10
(
    nvlink_link *link,
    NvU32 flags
)
{
    NvlStatus status;
    nvswitch_device *device = link->dev->pDevInfo;

    status = nvswitch_minion_send_command(device, link->linkNumber,
                        NV_MINION_NVLINK_DL_CMD_COMMAND_TURING_RXDET, 0);

    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Set RXDET failed for link %d.\n",
            __FUNCTION__, link->linkNumber);
        return status;
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_corelib_get_rx_detect_lr10
(
    nvlink_link *link
)
{
    NvlStatus status;
    nvswitch_device *device = link->dev->pDevInfo;

    status = nvswitch_minion_get_rxdet_status_lr10(device, link->linkNumber);

    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, WARN,
            "%s: Get RXDET failed for link %d.\n",
            __FUNCTION__, link->linkNumber);
        return status;
    }
    return NVL_SUCCESS;
}

void
nvswitch_corelib_training_complete_lr10
(
    nvlink_link *link
)
{
    nvswitch_device *device = link->dev->pDevInfo;

    nvswitch_init_dlpl_interrupts(link);

    _nvswitch_configure_reserved_throughput_counters(link);

    if (nvswitch_lib_notify_client_events(device,
                NVSWITCH_DEVICE_EVENT_PORT_UP) != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: Failed to notify PORT_UP event\n",
                     __FUNCTION__);
    }

}

NvlStatus
nvswitch_wait_for_tl_request_ready_lr10
(
    nvlink_link *link
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NVSWITCH_TIMEOUT timeout;
    NvBool           keepPolling;
    NvU32     linkRequest;
#if defined(DEVELOP) || defined(DEBUG) || defined(NV_MODS)
    NvU32 linkStatus, linkErr;
#endif

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLIPT_LNK, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_BAD_ARGS;
    }

    nvswitch_timeout_create(NVSWITCH_INTERVAL_1MSEC_IN_NS * 400, &timeout);

    do
    {
        keepPolling = (nvswitch_timeout_check(&timeout)) ? NV_FALSE : NV_TRUE;

        linkRequest = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber,
                NVLIPT_LNK , _NVLIPT_LNK , _CTRL_LINK_STATE_REQUEST);

        if (FLD_TEST_DRF_NUM(_NVLIPT_LNK, _CTRL_LINK_STATE_REQUEST, _READY, 1, linkRequest))
        {
            return NVL_SUCCESS;
        }

        nvswitch_os_sleep(1);
    }
    while(keepPolling);

    //
    // NVSWITCH_PRINT is not defined for release builds,
    // so this keeps compiler happy
    //
#if defined(DEVELOP) || defined(DEBUG) || defined(NV_MODS)
    linkStatus  = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber,
            NVLIPT_LNK , _NVLIPT_LNK , _CTRL_LINK_STATE_STATUS);
    linkErr     = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber,
            NVLIPT_LNK , _NVLIPT_LNK , _ERR_STATUS_0);
#endif

    NVSWITCH_PRINT(device, ERROR,
        "%s: Timeout waiting for TL link state ready on link #%d! "
              "NV_NVLIPT_LNK_CTRL_LINK_STATE_REQUEST = 0x%x, "
              "NV_NVLIPT_LNK_CTRL_LINK_STATE_STATUS = 0x%x, "
              "NV_NVLIPT_LNK_ERR_STATUS_0 = 0x%x\n",
        __FUNCTION__, link->linkNumber, linkRequest, linkStatus, linkErr);

    return -NVL_ERR_GENERIC;
}

NvlStatus
nvswitch_request_tl_link_state_lr10
(
    nvlink_link *link,
    NvU32        tlLinkState,
    NvBool       bSync
)
{
    nvswitch_device *device = link->dev->pDevInfo;
    NvlStatus status = NVL_SUCCESS;
    NvU32 linkStatus;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLIPT_LNK, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return -NVL_UNBOUND_DEVICE;
    }

    // Wait for the TL link state register to report ready
    status = nvswitch_wait_for_tl_request_ready_lr10(link);
    if (status != NVL_SUCCESS)
    {
        return status;
    }

    // Request RESET state through CTRL_LINK_STATE_REQUEST
    NVSWITCH_LINK_WR32_LR10(device, link->linkNumber,
            NVLIPT_LNK, _NVLIPT_LNK, _CTRL_LINK_STATE_REQUEST,
            DRF_NUM(_NVLIPT_LNK, _CTRL_LINK_STATE_REQUEST, _REQUEST, tlLinkState));

    if (bSync)
    {
        // Wait for the TL link state register to complete
        status = nvswitch_wait_for_tl_request_ready_lr10(link);
        if (status != NVL_SUCCESS)
        {
            return status;
        }

        // Check for state requested
        linkStatus  = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber,
                NVLIPT_LNK , _NVLIPT_LNK , _CTRL_LINK_STATE_STATUS);

        if (DRF_VAL(_NVLIPT_LNK, _CTRL_LINK_STATE_STATUS, _CURRENTLINKSTATE, linkStatus) !=
                    tlLinkState)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: TL link state request to state 0x%x for link #%d did not complete!\n",
                __FUNCTION__, tlLinkState, link->linkNumber);
            return -NVL_ERR_GENERIC;
        }
    }

    return status;

}

void
nvswitch_execute_unilateral_link_shutdown_lr10
(
    nvlink_link *link
)
{
    nvswitch_device *device = link->dev->pDevInfo;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link #%d invalid\n",
            __FUNCTION__, link->linkNumber);
        return;
    }

    //
    // Perform unilateral shutdown
    // This follows "Unilateral variant" from
    // NVLink 3.x Shutdown (confluence page ID: 164573291)
    //
    // Status is explicitly ignored here since we are required to soldier-on
    // in this scenario
    //
    nvswitch_corelib_set_dl_link_mode_lr10(link, NVLINK_LINKSTATE_DISABLE_PM, 0);
    nvswitch_corelib_set_dl_link_mode_lr10(link, NVLINK_LINKSTATE_DISABLE_ERR_DETECT, 0);
    nvswitch_corelib_set_dl_link_mode_lr10(link, NVLINK_LINKSTATE_LANE_DISABLE, 0);
    nvswitch_corelib_set_dl_link_mode_lr10(link, NVLINK_LINKSTATE_OFF, 0);
}

void 
nvswitch_reset_persistent_link_hw_state_lr10
(
    nvswitch_device *device,
    NvU32            linkNumber
)
{
    // Not Implemented for LR10
}

void
nvswitch_apply_recal_settings_lr10
(
    nvswitch_device *device,
    nvlink_link *link
)
{
    // Not supported on LR10
    return;
}

