/*
 * 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 "common_nvswitch.h"
#include "error_nvswitch.h"
#include "regkey_nvswitch.h"
#include "haldef_nvswitch.h"
#include "lr10/lr10.h"
#include "lr10/clock_lr10.h"
#include "lr10/minion_lr10.h"
#include "lr10/soe_lr10.h"
#include "lr10/pmgr_lr10.h"
#include "lr10/therm_lr10.h"
#include "lr10/inforom_lr10.h"
#include "lr10/smbpbi_lr10.h"
#include "flcn/flcnable_nvswitch.h"
#include "soe/soe_nvswitch.h"

#include "nvswitch/lr10/dev_nvs_top.h"
#include "nvswitch/lr10/dev_pri_ringmaster.h"
#include "nvswitch/lr10/dev_pri_ringstation_sys.h"
#include "nvswitch/lr10/dev_nvlsaw_ip.h"
#include "nvswitch/lr10/dev_nvlsaw_ip_addendum.h"
#include "nvswitch/lr10/dev_nvs_master.h"
#include "nvswitch/lr10/dev_nvltlc_ip.h"
#include "nvswitch/lr10/dev_nvldl_ip.h"
#include "nvswitch/lr10/dev_nvlipt_lnk_ip.h"
#include "nvswitch/lr10/dev_nvlctrl_ip.h"
#include "nvswitch/lr10/dev_npg_ip.h"
#include "nvswitch/lr10/dev_npgperf_ip.h"
#include "nvswitch/lr10/dev_nport_ip.h"
#include "nvswitch/lr10/dev_ingress_ip.h"
#include "nvswitch/lr10/dev_tstate_ip.h"
#include "nvswitch/lr10/dev_egress_ip.h"
#include "nvswitch/lr10/dev_route_ip.h"
#include "nvswitch/lr10/dev_therm.h"
#include "nvswitch/lr10/dev_soe_ip.h"
#include "nvswitch/lr10/dev_route_ip_addendum.h"
#include "nvswitch/lr10/dev_minion_ip.h"
#include "nvswitch/lr10/dev_minion_ip_addendum.h"
#include "nvswitch/lr10/dev_nport_ip_addendum.h"
#include "nvswitch/lr10/dev_nxbar_tile_ip.h"
#include "nvswitch/lr10/dev_nxbar_tc_global_ip.h"
#include "nvswitch/lr10/dev_sourcetrack_ip.h"

#include "oob/smbpbi.h"

#define DMA_ADDR_WIDTH_LR10     64
#define ROUTE_GANG_TABLE_SIZE (1 << DRF_SIZE(NV_ROUTE_REG_TABLE_ADDRESS_INDEX))

static void
_nvswitch_deassert_link_resets_lr10
(
    nvswitch_device *device
)
{
    NvU32 val, i;
    NVSWITCH_TIMEOUT timeout;
    NvBool           keepPolling;

    NVSWITCH_PRINT(device, WARN,
        "%s: NVSwitch Driver is taking the links out of reset. This should only happen during forced config.\n",
        __FUNCTION__);

    for (i = 0; i < NVSWITCH_LINK_COUNT(device); i++)
    {
        if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLIPT_LNK, i)) continue;

        val = NVSWITCH_LINK_RD32_LR10(device, i,
                NVLIPT_LNK, _NVLIPT_LNK, _RESET_RSTSEQ_LINK_RESET);
        val = FLD_SET_DRF_NUM(_NVLIPT_LNK, _RESET_RSTSEQ_LINK_RESET, _LINK_RESET,
                          NV_NVLIPT_LNK_RESET_RSTSEQ_LINK_RESET_LINK_RESET_DEASSERT, val);

        NVSWITCH_LINK_WR32_LR10(device, i,
                NVLIPT_LNK, _NVLIPT_LNK, _RESET_RSTSEQ_LINK_RESET, val);
    }

    for (i = 0; i < NVSWITCH_LINK_COUNT(device); i++)
    {
        if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLIPT_LNK, i)) continue;

        // Poll for _RESET_STATUS == _DEASSERTED
        nvswitch_timeout_create(25*NVSWITCH_INTERVAL_1MSEC_IN_NS, &timeout);

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

            val = NVSWITCH_LINK_RD32_LR10(device, i,
                    NVLIPT_LNK, _NVLIPT_LNK, _RESET_RSTSEQ_LINK_RESET);
            if (FLD_TEST_DRF(_NVLIPT_LNK, _RESET_RSTSEQ_LINK_RESET,
                        _LINK_RESET_STATUS, _DEASSERTED, val))
            {
                break;
            }

            nvswitch_os_sleep(1);
        }
        while (keepPolling);

        if (!FLD_TEST_DRF(_NVLIPT_LNK, _RESET_RSTSEQ_LINK_RESET,
                    _LINK_RESET_STATUS, _DEASSERTED, val))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: Timeout waiting for link %d_LINK_RESET_STATUS == _DEASSERTED\n",
                __FUNCTION__, i);
                // Bug 2974064: Review this timeout handling (fall through)
        }
    }
}

static void
_nvswitch_train_forced_config_link_lr10
(
    nvswitch_device *device,
    NvU32            linkId
)
{
    NvU32 data, i;
    nvlink_link *link;

    link = nvswitch_get_link(device, linkId);

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

    data = NVSWITCH_LINK_RD32_LR10(device, linkId, NVLDL, _NVLDL_TOP, _LINK_TEST);
    data = FLD_SET_DRF(_NVLDL_TOP, _LINK_TEST, _AUTO_HWCFG, _ENABLE, data);
    NVSWITCH_LINK_WR32_LR10(device, linkId, NVLDL, _NVLDL_TOP, _LINK_TEST, data);

    // Add some delay to let the sim/emu go to SAFE
    NVSWITCH_NSEC_DELAY(400 * NVSWITCH_INTERVAL_1USEC_IN_NS);

    data = NVSWITCH_LINK_RD32_LR10(device, linkId, NVLDL, _NVLDL_TOP, _LINK_TEST);
    data = FLD_SET_DRF(_NVLDL_TOP, _LINK_TEST, _AUTO_NVHS, _ENABLE, data);
    NVSWITCH_LINK_WR32_LR10(device, linkId, NVLDL, _NVLDL_TOP, _LINK_TEST, data);

    // Add some delay to let the sim/emu go to HS
    NVSWITCH_NSEC_DELAY(400 * NVSWITCH_INTERVAL_1USEC_IN_NS);

    data = NVSWITCH_LINK_RD32_LR10(device, linkId, NVLDL, _NVLDL_TOP, _LINK_CHANGE);
    data = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _NEWSTATE,      _ACTIVE, data);
    data = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _OLDSTATE_MASK, _DONTCARE, data);
    data = FLD_SET_DRF(_NVLDL_TOP, _LINK_CHANGE, _ACTION,        _LTSSM_CHANGE, data);
    NVSWITCH_LINK_WR32_LR10(device, linkId, NVLDL, _NVLDL_TOP, _LINK_CHANGE, data);

    i = 0;

    // Poll until LINK_CHANGE[1:0] != 2b01.
    while (i < 5)
    {
        data = NVSWITCH_LINK_RD32_LR10(device, linkId, NVLDL, _NVLDL_TOP, _LINK_CHANGE);

        if (FLD_TEST_DRF(_NVLDL_TOP, _LINK_CHANGE, _STATUS, _BUSY, data))
        {
            NVSWITCH_PRINT(device, INFO,
                "%s : Waiting for link %d to go to ACTIVE\n",
                __FUNCTION__, linkId);
        }
        else if (FLD_TEST_DRF(_NVLDL_TOP, _LINK_CHANGE, _STATUS, _FAULT, data))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s : Fault while changing LINK to ACTIVE. Link = %d\n",
                __FUNCTION__, linkId);
            break;
        }
        else
        {
            break;
        }

        NVSWITCH_NSEC_DELAY(5 * NVSWITCH_INTERVAL_1USEC_IN_NS);
        i++;
    }

    data = NVSWITCH_LINK_RD32_LR10(device, linkId, NVLDL, _NVLDL_TOP, _LINK_STATE);

    if (FLD_TEST_DRF(_NVLDL_TOP, _LINK_STATE, _STATE, _ACTIVE, data))
    {
        NVSWITCH_PRINT(device, INFO,
            "%s : Link %d is in ACTIVE state, setting BUFFER_READY\n",
            __FUNCTION__, linkId);

        // Set buffer ready only for nvlink TLC and not NPORT
        nvswitch_init_buffer_ready(device, link, NV_FALSE);
    }
    else
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s : Timeout while waiting for link %d to go to ACTIVE\n",
            __FUNCTION__, linkId);
        NVSWITCH_PRINT(device, ERROR,
            "%s : Link %d is in 0x%x state\n",
            __FUNCTION__, linkId,DRF_VAL(_NVLDL_TOP, _LINK_STATE, _STATE, data));
    }

}

void
_nvswitch_setup_chiplib_forced_config_lr10
(
    nvswitch_device *device
)
{
    NvU64 links = ((NvU64)device->regkeys.chiplib_forced_config_link_mask) +
                  ((NvU64)device->regkeys.chiplib_forced_config_link_mask2 << 32);
    NvU32 i;

    if (links == 0)
    {
        return;
    }

    //
    // First, take the links out of reset
    //
    // NOTE: On LR10, MINION will take the links out of reset during INITPHASE1
    // On platforms where MINION is not present and/or we want to run with forced
    // config, the driver must de-assert the link reset
    //
    _nvswitch_deassert_link_resets_lr10(device);

    // Next, train the links to ACTIVE/NVHS
    FOR_EACH_INDEX_IN_MASK(64, i, links)
    {
        if (device->link[i].valid)
        {
            _nvswitch_train_forced_config_link_lr10(device, i);
        }
    }
    FOR_EACH_INDEX_IN_MASK_END;
}

static NvU32
_nvswitch_get_nvlink_linerate
(
    nvswitch_device *device,
    NvU32            val
)
{
    NvU32  lineRate = 0;
    switch (val)
    {
        case NV_SWITCH_REGKEY_SPEED_CONTROL_SPEED_16G:
            lineRate = NV_NVLIPT_LNK_CTRL_SYSTEM_LINK_CLK_CTRL_LINE_RATE_16_00000_GBPS;
            break;
        case NV_SWITCH_REGKEY_SPEED_CONTROL_SPEED_20G:
            lineRate = NV_NVLIPT_LNK_CTRL_SYSTEM_LINK_CLK_CTRL_LINE_RATE_20_00000_GBPS;
            break;
        case NV_SWITCH_REGKEY_SPEED_CONTROL_SPEED_25G:
            lineRate = NV_NVLIPT_LNK_CTRL_SYSTEM_LINK_CLK_CTRL_LINE_RATE_25_00000_GBPS;
            break;
        case NV_SWITCH_REGKEY_SPEED_CONTROL_SPEED_32G:
            lineRate = NV_NVLIPT_LNK_CTRL_SYSTEM_LINK_CLK_CTRL_LINE_RATE_32_00000_GBPS;
            break;
        case NV_SWITCH_REGKEY_SPEED_CONTROL_SPEED_40G:
            lineRate = NV_NVLIPT_LNK_CTRL_SYSTEM_LINK_CLK_CTRL_LINE_RATE_40_00000_GBPS;
            break;
        case NV_SWITCH_REGKEY_SPEED_CONTROL_SPEED_50G:
            lineRate = NV_NVLIPT_LNK_CTRL_SYSTEM_LINK_CLK_CTRL_LINE_RATE_50_00000_GBPS;
            break;
        case NV_SWITCH_REGKEY_SPEED_CONTROL_SPEED_53_12500G:
            lineRate = NV_NVLIPT_LNK_CTRL_SYSTEM_LINK_CLK_CTRL_LINE_RATE_53_12500_GBPS;
            break;
        default:
            NVSWITCH_PRINT(device, SETUP, "%s:ERROR LINE_RATE = 0x%x requested by regkey\n",
                       __FUNCTION__, lineRate);
            lineRate = NV_NVLIPT_LNK_CTRL_SYSTEM_LINK_CLK_CTRL_LINE_RATE_ILLEGAL_LINE_RATE;
    }
    return lineRate;
}

static void
_nvswitch_setup_link_system_registers_lr10
(
    nvswitch_device *device,
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config,
    nvlink_link *link
)
{
    NvU32 regval, fldval;
    NvU32 lineRate = 0;
    NVLINK_CONFIG_DATA_LINKENTRY *vbios_link_entry = NULL;

    //
    // Identify the valid link entry to update. If not, proceed with the default settings
    //
    if ((bios_config == NULL) || (bios_config->bit_address == 0))
    {
        NVSWITCH_PRINT(device, SETUP,
            "%s: No override with VBIOS - VBIOS NvLink configuration table not found\n",
            __FUNCTION__);
    }
    else
    {
        vbios_link_entry = &bios_config->link_vbios_entry[bios_config->link_base_entry_assigned][link->linkNumber];
    }

    // LINE_RATE SYSTEM register
    if (device->regkeys.nvlink_speed_control != NV_SWITCH_REGKEY_SPEED_CONTROL_SPEED_DEFAULT)
    {
        regval   = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK,
                                           _NVLIPT_LNK_CTRL_SYSTEM_LINK, _CLK_CTRL);
        lineRate = _nvswitch_get_nvlink_linerate(device, device->regkeys.nvlink_speed_control);
        regval   = FLD_SET_DRF_NUM(_NVLIPT_LNK_CTRL_SYSTEM_LINK, _CLK_CTRL,
                                    _LINE_RATE, lineRate, regval);
        NVSWITCH_PRINT(device, SETUP, "%s: LINE_RATE = 0x%x requested by regkey\n",
                       __FUNCTION__, lineRate);
        NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLIPT_LNK,
                            _NVLIPT_LNK_CTRL_SYSTEM_LINK, _CLK_CTRL, regval);
    }

    // TXTRAIN SYSTEM register
    regval = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK,
                                     _NVLIPT_LNK_CTRL_SYSTEM_LINK, _CHANNEL_CTRL);

    fldval = DRF_VAL(_SWITCH_REGKEY, _TXTRAIN_CONTROL, _FOM_FORMAT,
                     device->regkeys.txtrain_control);
    if (fldval != NV_SWITCH_REGKEY_TXTRAIN_CONTROL_FOM_FORMAT_NOP)
    {
        NVSWITCH_PRINT(device, SETUP, "%s: FOM_FORMAT = 0x%x requested by regkey\n",
                       __FUNCTION__, fldval);
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK_CTRL_SYSTEM_LINK, _CHANNEL_CTRL,
                                 _TXTRAIN_FOM_FORMAT, fldval, regval);
    }
    else if (vbios_link_entry != NULL)
    {
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK, _CTRL_SYSTEM_LINK_CHANNEL_CTRL, _TXTRAIN_FOM_FORMAT,
                                    DRF_VAL(_NVLINK_VBIOS,_PARAM5,_TXTRAIN_FOM_FORMAT, vbios_link_entry->nvLinkparam5),
                                    regval);
    }

    fldval = DRF_VAL(_SWITCH_REGKEY, _TXTRAIN_CONTROL, _OPTIMIZATION_ALGORITHM,
                     device->regkeys.txtrain_control);
    if (fldval != NV_SWITCH_REGKEY_TXTRAIN_CONTROL_OPTIMIZATION_ALGORITHM_NOP)
    {
        NVSWITCH_PRINT(device, SETUP, "%s: OPTIMIZATION_ALGORITHM = 0x%x requested by regkey\n",
                       __FUNCTION__, fldval);
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK_CTRL_SYSTEM_LINK, _CHANNEL_CTRL,
                                 _TXTRAIN_OPTIMIZATION_ALGORITHM, fldval, regval);
    }
    else if (vbios_link_entry != NULL)
    {
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK, _CTRL_SYSTEM_LINK_CHANNEL_CTRL, _TXTRAIN_OPTIMIZATION_ALGORITHM,
                                 vbios_link_entry->nvLinkparam4, regval);
    }

    fldval = DRF_VAL(_SWITCH_REGKEY, _TXTRAIN_CONTROL, _ADJUSTMENT_ALGORITHM,
                     device->regkeys.txtrain_control);
    if (fldval != NV_SWITCH_REGKEY_TXTRAIN_CONTROL_ADJUSTMENT_ALGORITHM_NOP)
    {
        NVSWITCH_PRINT(device, SETUP, "%s: ADJUSTMENT_ALGORITHM = 0x%x requested by regkey\n",
                       __FUNCTION__, fldval);
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK_CTRL_SYSTEM_LINK, _CHANNEL_CTRL,
                                 _TXTRAIN_ADJUSTMENT_ALGORITHM, fldval, regval);
    }
    else if (vbios_link_entry != NULL)
    {
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK, _CTRL_SYSTEM_LINK_CHANNEL_CTRL, _TXTRAIN_ADJUSTMENT_ALGORITHM,
                                     DRF_VAL(_NVLINK_VBIOS,_PARAM5,_TXTRAIN_ADJUSTMENT_ALGORITHM, vbios_link_entry->nvLinkparam5),
                                     regval);
    }

    fldval = DRF_VAL(_SWITCH_REGKEY, _TXTRAIN_CONTROL, _MINIMUM_TRAIN_TIME_MANTISSA,
                     device->regkeys.txtrain_control);
    if (fldval != NV_SWITCH_REGKEY_TXTRAIN_CONTROL_MINIMUM_TRAIN_TIME_MANTISSA_NOP)
    {
        NVSWITCH_PRINT(device, SETUP, "%s: MINIMUM_TRAIN_TIME_MANTISSA = 0x%x requested by regkey\n",
                       __FUNCTION__, fldval);
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK_CTRL_SYSTEM_LINK, _CHANNEL_CTRL,
                                 _TXTRAIN_MINIMUM_TRAIN_TIME_MANTISSA, fldval, regval);
    }
    else if (vbios_link_entry != NULL)
    {
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK, _CTRL_SYSTEM_LINK_CHANNEL_CTRL, _TXTRAIN_MINIMUM_TRAIN_TIME_MANTISSA,
                                 DRF_VAL(_NVLINK_VBIOS,_PARAM6,_TXTRAIN_MINIMUM_TRAIN_TIME_MANTISSA, vbios_link_entry->nvLinkparam6),
                                 regval);
    }
    else
    {
        //
        // Default working configuration for LR10
        // This will be provided by VBIOS once support available (bug 2767390)
        //
        NVSWITCH_PRINT(device, SETUP, "%s: MINIMUM_TRAIN_TIME_MANTISSA = 0x5 forced by driver\n",
                       __FUNCTION__);
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK_CTRL_SYSTEM_LINK, _CHANNEL_CTRL,
                                 _TXTRAIN_MINIMUM_TRAIN_TIME_MANTISSA, 0x5, regval);
    }

    fldval = DRF_VAL(_SWITCH_REGKEY, _TXTRAIN_CONTROL, _MINIMUM_TRAIN_TIME_EXPONENT,
                     device->regkeys.txtrain_control);
    if (fldval != NV_SWITCH_REGKEY_TXTRAIN_CONTROL_MINIMUM_TRAIN_TIME_EXPONENT_NOP)
    {
        NVSWITCH_PRINT(device, SETUP, "%s: MINIMUM_TRAIN_TIME_EXPONENT = 0x%x requested by regkey\n",
                       __FUNCTION__, fldval);
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK_CTRL_SYSTEM_LINK, _CHANNEL_CTRL,
                                 _TXTRAIN_MINIMUM_TRAIN_TIME_EXPONENT, fldval, regval);
    }
    else if (vbios_link_entry != NULL)
    {
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK, _CTRL_SYSTEM_LINK_CHANNEL_CTRL, _TXTRAIN_MINIMUM_TRAIN_TIME_EXPONENT,
                                 DRF_VAL(_NVLINK_VBIOS,_PARAM6,_TXTRAIN_MINIMUM_TRAIN_TIME_EXPONENT, vbios_link_entry->nvLinkparam6),
                                 regval);
    }
    else
    {
        //
        // Default working configuration for LR10
        // This will be provided by VBIOS once support available  (bug 2767390)
        //
        NVSWITCH_PRINT(device, SETUP, "%s: MINIMUM_TRAIN_TIME_EXPONENT = 0x4 forced by driver\n",
                       __FUNCTION__);
        regval = FLD_SET_DRF_NUM(_NVLIPT_LNK_CTRL_SYSTEM_LINK, _CHANNEL_CTRL,
                                 _TXTRAIN_MINIMUM_TRAIN_TIME_EXPONENT, 0x4, regval);
    }

    NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLIPT_LNK,
                            _NVLIPT_LNK_CTRL_SYSTEM_LINK, _CHANNEL_CTRL, regval);

    // Disable L2 (Bug 3176196)
    regval = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK, _NVLIPT_LNK, _CTRL_SYSTEM_LINK_AN1_CTRL);
    regval = FLD_SET_DRF(_NVLIPT_LNK, _CTRL_SYSTEM_LINK_AN1_CTRL, _PWRM_L2_ENABLE, _DISABLE, regval);
    NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLIPT_LNK, _NVLIPT_LNK, _CTRL_SYSTEM_LINK_AN1_CTRL, regval);

    // SW WAR: Bug 3364420
    nvswitch_apply_recal_settings(device, link);
}

/*!
 * @brief Parse packed little endian data and unpack into padded structure
 *
 * @param[in]   format          Data format
 * @param[in]   packedData      Packed little endian data
 * @param[out]  unpackedData    Unpacked padded structure
 * @param[out]  unpackedSize    Unpacked data size
 * @param[out]  fieldsCount     Number of fields
 *
 * @return 'NV_OK'
 */
NV_STATUS
_nvswitch_devinit_unpack_structure
(
    const char *format,
    const NvU8 *packedData,
    NvU32      *unpackedData,
    NvU32      *unpackedSize,
    NvU32      *fieldsCount
)
{
    NvU32 unpkdSize = 0;
    NvU32 fields = 0;
    NvU32 count;
    NvU32 data;
    char fmt;

    while ((fmt = *format++))
    {
        count = 0;
        while ((fmt >= '0') && (fmt <= '9'))
        {
            count *= 10;
            count += fmt - '0';
            fmt = *format++;
        }
        if (count == 0)
            count = 1;

        while (count--)
        {
            switch (fmt)
            {
                case 'b':
                    data = *packedData++;
                    unpkdSize += 1;
                    break;

                case 's':    // signed byte
                    data = *packedData++;
                    if (data & 0x80)
                        data |= ~0xff;
                    unpkdSize += 1;
                    break;

                case 'w':
                    data  = *packedData++;
                    data |= *packedData++ << 8;
                    unpkdSize += 2;
                    break;

                case 'd':
                    data  = *packedData++;
                    data |= *packedData++ << 8;
                    data |= *packedData++ << 16;
                    data |= *packedData++ << 24;
                    unpkdSize += 4;
                    break;

                default:
                    return NV_ERR_GENERIC;
            }
            *unpackedData++ = data;
            fields++;
        }
    }

    if (unpackedSize != NULL)
        *unpackedSize = unpkdSize;

    if (fieldsCount != NULL)
        *fieldsCount = fields;

    return NV_OK;
}

/*!
 * @brief Calculate packed and unpacked data size based on given data format
 *
 * @param[in]   format          Data format
 * @param[out]  packedSize      Packed data size
 * @param[out]  unpackedSize    Unpacked data size
 *
 */
void
_nvswitch_devinit_calculate_sizes
(
    const char *format,
    NvU32      *packedSize,
    NvU32      *unpackedSize
)
{
    NvU32 unpkdSize = 0;
    NvU32 pkdSize = 0;
    NvU32 count;
    char fmt;

    while ((fmt = *format++))
    {
        count = 0;
        while ((fmt >= '0') && (fmt <= '9'))
        {
            count *= 10;
            count += fmt - '0';
            fmt = *format++;
        }
        if (count == 0)
            count = 1;

        switch (fmt)
        {
            case 'b':
                pkdSize += count * 1;
                unpkdSize += count * sizeof(bios_U008);
                break;

            case 's':    // signed byte
                pkdSize += count * 1;
                unpkdSize += count * sizeof(bios_S008);
                break;

            case 'w':
                pkdSize += count * 2;
                unpkdSize += count * sizeof(bios_U016);
                break;

            case 'd':
                pkdSize += count * 4;
                unpkdSize += count * sizeof(bios_U032);
                break;
        }
    }

    if (packedSize != NULL)
        *packedSize = pkdSize;

    if (unpackedSize != NULL)
        *unpackedSize = unpkdSize;
}

/*!
 * @brief Calculate packed and unpacked data size based on given data format
 *
 * @param[in]   format          Data format
 * @param[out]  packedSize      Packed data size
 * @param[out]  unpackedSize    Unpacked data size
 *
 */

NV_STATUS
_nvswitch_vbios_read_structure
(
    nvswitch_device *device,
    void            *structure,
    NvU32           offset,
    NvU32           *ppacked_size,
    const char      *format
)
{
    NvU32  packed_size;
    NvU8  *packed_data;
    NvU32  unpacked_bytes;

    // calculate the size of the data as indicated by its packed format.
    _nvswitch_devinit_calculate_sizes(format, &packed_size, &unpacked_bytes);

    if (ppacked_size)
        *ppacked_size = packed_size;

    //
    // is 'offset' too big?
    // happens when we read bad ptrs from fixed addrs in image frequently
    //
    if ((offset + packed_size) > device->biosImage.size)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: Bad offset in bios read: 0x%x, max is 0x%x, fmt is '%s'\n",
                       __FUNCTION__, offset, device->biosImage.size, format);
        return NV_ERR_GENERIC;
    }

    packed_data = &device->biosImage.pImage[offset];
    return _nvswitch_devinit_unpack_structure(format, packed_data, structure,
                                              &unpacked_bytes, NULL);
}

NvU8
_nvswitch_vbios_read8
(
    nvswitch_device *device,
    NvU32           offset
)
{
    bios_U008 data;     // BiosReadStructure expects 'bios' types

    _nvswitch_vbios_read_structure(device, &data, offset, (NvU32 *) 0, "b");

    return (NvU8) data;
}

NvU16
_nvswitch_vbios_read16
(
    nvswitch_device *device,
    NvU32           offset
)
{
    bios_U016 data;     // BiosReadStructure expects 'bios' types

    _nvswitch_vbios_read_structure(device, &data, offset, (NvU32 *) 0, "w");

    return (NvU16) data;
}


NvU32
_nvswitch_vbios_read32
(
    nvswitch_device *device,
    NvU32           offset
)
{
    bios_U032 data;     // BiosReadStructure expects 'bios' types

    _nvswitch_vbios_read_structure(device, &data, offset, (NvU32 *) 0, "d");

    return (NvU32) data;
}

NV_STATUS
_nvswitch_perform_BIT_offset_update
(
    nvswitch_device *device,
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config
)
{
    BIT_HEADER_V1_00         bitHeader;
    BIT_TOKEN_V1_00          bitToken;
    NV_STATUS                rmStatus;
    NvU32                    dataPointerOffset;
    NvU32 i;

    rmStatus = _nvswitch_vbios_read_structure(device,
                                              (NvU8*) &bitHeader,
                                              bios_config->bit_address,
                                              (NvU32 *) 0,
                                              BIT_HEADER_V1_00_FMT);

    if(rmStatus != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR,
                       "%s: Failed to read BIT table structure!.\n",
                       __FUNCTION__);
        return rmStatus;
    }
   
    for(i=0; i < bitHeader.TokenEntries; i++)
    {
        NvU32 BitTokenLocation = bios_config->bit_address + bitHeader.HeaderSize + (i * bitHeader.TokenSize);
        rmStatus = _nvswitch_vbios_read_structure(device,
                                                 (NvU8*) &bitToken,
                                                 BitTokenLocation,
                                                 (NvU32 *) 0,
                                                 BIT_TOKEN_V1_00_FMT);
        if(rmStatus != NV_OK)
        {
            NVSWITCH_PRINT(device, WARN,
                "%s: Failed to read BIT token %d!\n",
                __FUNCTION__, i);
            return NV_ERR_GENERIC;
        }

        dataPointerOffset = (bios_config->pci_image_address + bitToken.DataPtr);
        switch(bitToken.TokenId)
        {
            case BIT_TOKEN_NVINIT_PTRS:
            {
                BIT_DATA_NVINIT_PTRS_V1 nvInitTablePtrs;
                rmStatus = _nvswitch_vbios_read_structure(device,
                                                          (NvU8*) &nvInitTablePtrs,
                                                          dataPointerOffset,
                                                          (NvU32 *) 0,
                                                          BIT_DATA_NVINIT_PTRS_V1_30_FMT);
                if (rmStatus != NV_OK)
                {
                    NVSWITCH_PRINT(device, WARN,
                                   "%s: Failed to read internal data structure\n",
                                   __FUNCTION__);
                    return NV_ERR_GENERIC;
                }
                // Update the retrived info with device info
                bios_config->nvlink_config_table_address = (nvInitTablePtrs.NvlinkConfigDataPtr + bios_config->pci_image_address);
            }
            break;
        }
    }

    return NV_OK;
}

NV_STATUS
_nvswitch_validate_BIT_header
(
    nvswitch_device *device,
    NvU32            bit_address
)
{
    NvU32    headerSize = 0;
    NvU32    chkSum = 0;
    NvU32    i;

    //
    // For now let's assume the Header Size is always at the same place.
    // We can create something more complex if needed later.
    //
    headerSize = (NvU32)_nvswitch_vbios_read8(device, bit_address + BIT_HEADER_SIZE_OFFSET);

    // Now perform checksum
    for (i = 0; i < headerSize; i++)
        chkSum += (NvU32)_nvswitch_vbios_read8(device, bit_address + i);

    //Byte checksum removes upper bytes
    chkSum = chkSum & 0xFF;

    if (chkSum)
        return NV_ERR_GENERIC;

    return NV_OK;
}


NV_STATUS
nvswitch_verify_header
(
    nvswitch_device *device,
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config
)
{
    NvU32       i;
    NV_STATUS   status = NV_ERR_GENERIC;

    if ((bios_config == NULL) || (!bios_config->pci_image_address))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: PCI Image offset is not identified\n",
            __FUNCTION__);
        return status;
    }

    // attempt to find the init info in the BIOS
    for (i = bios_config->pci_image_address; i < device->biosImage.size - 3; i++)
    {
        NvU16 bitheaderID = _nvswitch_vbios_read16(device, i);
        if (bitheaderID == BIT_HEADER_ID)
        {
            NvU32 signature = _nvswitch_vbios_read32(device, i + 2);
            if (signature == BIT_HEADER_SIGNATURE)
            {
                bios_config->bit_address = i;

                // Checksum BIT to prove accuracy
                if (NV_OK != _nvswitch_validate_BIT_header(device, bios_config->bit_address))
                {
                    device->biosImage.pImage = 0;
                    device->biosImage.size = 0;
                }
            }
        }
        // only if we find the bit address do we break
        if (bios_config->bit_address)
            break;
    }
    if (bios_config->bit_address)
    {
        status = NV_OK;
    }

    return status;
}

NV_STATUS
_nvswitch_vbios_update_bit_Offset
(
    nvswitch_device *device,
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config
)
{
    NV_STATUS   status = NV_OK;

    if (bios_config->bit_address)
    {
        goto vbios_update_bit_Offset_done;
    }

    status = nvswitch_verify_header(device, bios_config);
    if (status != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: *** BIT header is not found in vbios!\n",
            __FUNCTION__);
        goto vbios_update_bit_Offset_done;
    }

    if (bios_config->bit_address)
    {

        status = _nvswitch_perform_BIT_offset_update(device, bios_config);
        if (status != NV_OK)
            goto vbios_update_bit_Offset_done;
    }

vbios_update_bit_Offset_done:
    return status;
}


NV_STATUS
_nvswitch_vbios_identify_pci_image_loc
(
    nvswitch_device         *device,
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config
)
{
    NV_STATUS   status = NV_OK;
    NvU32       i;

    if (bios_config->pci_image_address)
    {
        goto vbios_identify_pci_image_loc_done;
    }

    // Match the PCI_EXP_ROM_SIGNATURE and followed by the PCI Data structure
    // with PCIR and matching vendor ID
    NVSWITCH_PRINT(device, SETUP,
        "%s: Verifying and extracting PCI Data.\n",
        __FUNCTION__);

    // attempt to find the init info in the BIOS
    for (i = 0; i < (device->biosImage.size - PCI_ROM_HEADER_PCI_DATA_SIZE); i++)
    {
        NvU16 pci_rom_sigature = _nvswitch_vbios_read16(device, i);

        if (pci_rom_sigature == PCI_EXP_ROM_SIGNATURE)
        {
            NvU32 pcir_data_dffSet  = _nvswitch_vbios_read16(device, i + PCI_ROM_HEADER_SIZE);  // 0x16 -> 0x18 i.e, including the ROM Signature bytes

            if (((i + pcir_data_dffSet) + PCI_DATA_STRUCT_SIZE) < device->biosImage.size)
            {
                NvU32 pcirSigature = _nvswitch_vbios_read32(device, (i + pcir_data_dffSet));

                if (pcirSigature == PCI_DATA_STRUCT_SIGNATURE)
                {
                    PCI_DATA_STRUCT pciData;
                    status = _nvswitch_vbios_read_structure(device,
                                                           (NvU8*) &pciData,
                                                            i + pcir_data_dffSet,
                                                            (NvU32 *) 0,
                                                            PCI_DATA_STRUCT_FMT);
                    if (status != NV_OK)
                    {
                        NVSWITCH_PRINT(device, WARN,
                                       "%s: Failed to PCI Data for validation\n",
                                       __FUNCTION__);
                        goto vbios_identify_pci_image_loc_done;
                    }

                    // Validate the vendor details as well
                    if (pciData.vendorID == PCI_VENDOR_ID_NVIDIA)
                    {
                        bios_config->pci_image_address = i;
                        break;
                    }
                }
            }
        }
    }

vbios_identify_pci_image_loc_done:
    return status;
}

NvU32 _nvswitch_get_nvlink_config_address
(
    nvswitch_device         *device,
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config
)
{
    return bios_config->nvlink_config_table_address;
}

NV_STATUS
_nvswitch_read_vbios_link_base_entry
(
    nvswitch_device *device,
    NvU32            tblPtr,
    NVLINK_CONFIG_DATA_BASEENTRY  *link_base_entry
)
{
    NV_STATUS status = NV_ERR_INVALID_PARAMETER;
    NVLINK_VBIOS_CONFIG_DATA_BASEENTRY vbios_link_base_entry;

    status = _nvswitch_vbios_read_structure(device, &vbios_link_base_entry, tblPtr, (NvU32 *)0, NVLINK_CONFIG_DATA_BASEENTRY_FMT);
    if (status != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Error on reading nvlink base entry\n",
            __FUNCTION__);
        return status;
    }

    link_base_entry->positionId = vbios_link_base_entry.positionId;

    return status;
}

NV_STATUS
_nvswitch_read_vbios_link_entries
(
    nvswitch_device *device,
    NvU32            tblPtr,
    NvU32            expected_link_entriesCount,
    NVLINK_CONFIG_DATA_LINKENTRY  *link_entries,
    NvU32            *identified_link_entriesCount
)
{
    NV_STATUS status = NV_ERR_INVALID_PARAMETER;
    NvU32 i;
    NVLINK_VBIOS_CONFIG_DATA_LINKENTRY vbios_link_entry;
    *identified_link_entriesCount = 0;

    for (i = 0; i < expected_link_entriesCount; i++)
    {
        status = _nvswitch_vbios_read_structure(device,
                                                &vbios_link_entry,
                                                tblPtr, (NvU32 *)0,
                                                NVLINK_CONFIG_DATA_LINKENTRY_FMT);
        if (status != NV_OK)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: Error on reading nvlink entry\n",
                __FUNCTION__);
            return status;
        }
        link_entries[i].nvLinkparam0 = (NvU8)vbios_link_entry.nvLinkparam0;
        link_entries[i].nvLinkparam1 = (NvU8)vbios_link_entry.nvLinkparam1;
        link_entries[i].nvLinkparam2 = (NvU8)vbios_link_entry.nvLinkparam2;
        link_entries[i].nvLinkparam3 = (NvU8)vbios_link_entry.nvLinkparam3;
        link_entries[i].nvLinkparam4 = (NvU8)vbios_link_entry.nvLinkparam4;
        link_entries[i].nvLinkparam5 = (NvU8)vbios_link_entry.nvLinkparam5;
        link_entries[i].nvLinkparam6 = (NvU8)vbios_link_entry.nvLinkparam6;
        tblPtr += sizeof(NVLINK_CONFIG_DATA_LINKENTRY);

        NVSWITCH_PRINT(device, SETUP,
            "<<<---- NvLink ID 0x%x ---->>>\n", i);
        NVSWITCH_PRINT(device, SETUP,
            "NVLink Params 0 \t0x%x \tBinary:"BYTE_TO_BINARY_PATTERN"\n", vbios_link_entry.nvLinkparam0, BYTE_TO_BINARY(vbios_link_entry.nvLinkparam0));
        NVSWITCH_PRINT(device, SETUP,
            "NVLink Params 1 \t0x%x \tBinary:"BYTE_TO_BINARY_PATTERN"\n", vbios_link_entry.nvLinkparam1, BYTE_TO_BINARY(vbios_link_entry.nvLinkparam1));
        NVSWITCH_PRINT(device, SETUP,
            "NVLink Params 2 \t0x%x \tBinary:"BYTE_TO_BINARY_PATTERN"\n", vbios_link_entry.nvLinkparam2, BYTE_TO_BINARY(vbios_link_entry.nvLinkparam2));
        NVSWITCH_PRINT(device, SETUP,
            "NVLink Params 3 \t0x%x \tBinary:"BYTE_TO_BINARY_PATTERN"\n", vbios_link_entry.nvLinkparam3, BYTE_TO_BINARY(vbios_link_entry.nvLinkparam3));
        NVSWITCH_PRINT(device, SETUP,
            "NVLink Params 4 \t0x%x \tBinary:"BYTE_TO_BINARY_PATTERN"\n", vbios_link_entry.nvLinkparam4, BYTE_TO_BINARY(vbios_link_entry.nvLinkparam4));
        NVSWITCH_PRINT(device, SETUP,
            "NVLink Params 5 \t0x%x \tBinary:"BYTE_TO_BINARY_PATTERN"\n", vbios_link_entry.nvLinkparam5, BYTE_TO_BINARY(vbios_link_entry.nvLinkparam5));
        NVSWITCH_PRINT(device, SETUP,
            "NVLink Params 6 \t0x%x \tBinary:"BYTE_TO_BINARY_PATTERN"\n", vbios_link_entry.nvLinkparam6, BYTE_TO_BINARY(vbios_link_entry.nvLinkparam6));
        NVSWITCH_PRINT(device, SETUP,
            "<<<---- NvLink ID 0x%x ---->>>\n\n", i);
    }
    *identified_link_entriesCount = i;
    return status;
}

NV_STATUS
_nvswitch_vbios_fetch_nvlink_entries
(
    nvswitch_device         *device,
    NVSWITCH_BIOS_NVLINK_CONFIG    *bios_config
)
{
    NvU32                       tblPtr;
    NvU8                        version;
    NvU8                        size;
    NV_STATUS                   status = NV_ERR_GENERIC;
    NVLINK_CONFIG_DATA_HEADER   header;
    NvU32                       base_entry_index;
    NvU32                       expected_base_entry_count;

    tblPtr = _nvswitch_get_nvlink_config_address(device, bios_config);
    if (!tblPtr)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: No NvLink Config table set\n",
            __FUNCTION__);
        goto vbios_fetch_nvlink_entries_done;
    }

    // Read the table version number
    version = _nvswitch_vbios_read8(device, tblPtr);
    switch (version)
    {
        case NVLINK_CONFIG_DATA_HEADER_VER_20:
            size = _nvswitch_vbios_read8(device, tblPtr + 1);
            if (size == NVLINK_CONFIG_DATA_HEADER_20_SIZE)
            {
                // Grab Nvlink Config Data Header
                status = _nvswitch_vbios_read_structure(device, &header.ver_20, tblPtr, (NvU32 *) 0, NVLINK_CONFIG_DATA_HEADER_20_FMT);

                if (status != NV_OK)
                {
                    NVSWITCH_PRINT(device, ERROR,
                        "%s: Error on reading the nvlink config header\n",
                        __FUNCTION__);
                }
            }
            break;
        default:
            NVSWITCH_PRINT(device, ERROR,
                "%s: Invalid version 0x%x\n",
                __FUNCTION__, version);
    }
    if (status != NV_OK)
    {
        goto vbios_fetch_nvlink_entries_done;
    }

    NVSWITCH_PRINT(device, SETUP,
        "<<<---- NvLink Header ---->>>\n\n");
    NVSWITCH_PRINT(device, SETUP,
        "Version \t\t 0x%x\n", header.ver_20.Version);
    NVSWITCH_PRINT(device, SETUP,
        "Header Size \t0x%x\n", header.ver_20.HeaderSize);
    NVSWITCH_PRINT(device, SETUP,
        "Base Entry Size \t0x%x\n", header.ver_20.BaseEntrySize);
    NVSWITCH_PRINT(device, SETUP,
        "Base Entry count \t0x%x\n", header.ver_20.BaseEntryCount);
    NVSWITCH_PRINT(device, SETUP,
        "Link Entry Size \t0x%x\n", header.ver_20.LinkEntrySize);
    NVSWITCH_PRINT(device, SETUP,
        "Link Entry Count \t0x%x\n", header.ver_20.LinkEntryCount);
    NVSWITCH_PRINT(device, SETUP,
        "Reserved \t0x%x\n", header.ver_20.Reserved);
    NVSWITCH_PRINT(device, SETUP,
        "<<<---- NvLink Header ---->>>\n");

    expected_base_entry_count = header.ver_20.BaseEntryCount;
    if (expected_base_entry_count > NVSWITCH_NUM_BIOS_NVLINK_CONFIG_BASE_ENTRY)
    {
        NVSWITCH_PRINT(device, WARN,
            "%s: Greater than expected base entry count 0x%x - Restricting to count 0x%x\n",
            __FUNCTION__, expected_base_entry_count, NVSWITCH_NUM_BIOS_NVLINK_CONFIG_BASE_ENTRY);
        expected_base_entry_count = NVSWITCH_NUM_BIOS_NVLINK_CONFIG_BASE_ENTRY;
    }

    tblPtr += header.ver_20.HeaderSize;
    for (base_entry_index = 0; base_entry_index < expected_base_entry_count; base_entry_index++)
    {
        NvU32 expected_link_entriesCount = header.ver_20.LinkEntryCount;
        if (expected_link_entriesCount > NVSWITCH_LINK_COUNT(device))
        {
            NVSWITCH_PRINT(device, WARN,
                "%s: Greater than expected link count 0x%x - Restricting to count 0x%x\n",
                __FUNCTION__, expected_link_entriesCount, NVSWITCH_LINK_COUNT(device));
            expected_link_entriesCount = NVSWITCH_LINK_COUNT(device);
        }

        // Grab Nvlink Config Data Base Entry
        _nvswitch_read_vbios_link_base_entry(device, tblPtr, &bios_config->link_vbios_base_entry[base_entry_index]);
        tblPtr += header.ver_20.BaseEntrySize;
        
        _nvswitch_read_vbios_link_entries(device,
                                          tblPtr,
                                          expected_link_entriesCount,
                                          bios_config->link_vbios_entry[base_entry_index],
                                          &bios_config->identified_Link_entries[base_entry_index]);
        tblPtr += (expected_link_entriesCount * sizeof(NVLINK_CONFIG_DATA_LINKENTRY));
    }
vbios_fetch_nvlink_entries_done:
    return status;
}

NV_STATUS
_nvswitch_vbios_assign_base_entry
(
    nvswitch_device         *device,
    NVSWITCH_BIOS_NVLINK_CONFIG    *bios_config
)
{
    NvU32 physical_id;
    NvU32 entry_index;

    physical_id = nvswitch_read_physical_id(device);

    for (entry_index = 0; entry_index < NVSWITCH_NUM_BIOS_NVLINK_CONFIG_BASE_ENTRY; entry_index++)
    {
        if (physical_id == bios_config->link_vbios_base_entry[entry_index].positionId)
        {
            bios_config->link_base_entry_assigned = entry_index;
            return NV_OK;
        }
    }

    // TODO: Bug 3507948
    NVSWITCH_PRINT(device, ERROR,
            "%s: Error on assigning base entry. Setting base entry index = 0\n",
            __FUNCTION__);
    bios_config->link_base_entry_assigned = 0;

    return NV_OK;
}

NV_STATUS
_nvswitch_setup_link_vbios_overrides
(
    nvswitch_device *device,
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config
)
{
    NV_STATUS    status         = NV_OK;

    if (bios_config == NULL)
    {
        NVSWITCH_PRINT(device, ERROR,
                "%s: BIOS config override not supported\n",
                __FUNCTION__);
         return -NVL_ERR_NOT_SUPPORTED;
    }

    bios_config->vbios_disabled_link_mask = 0;

    bios_config->bit_address                 = 0;
    bios_config->pci_image_address           = 0;
    bios_config->nvlink_config_table_address = 0;

    if ((device->biosImage.size == 0) || (device->biosImage.pImage == NULL))
    {
        NVSWITCH_PRINT(device, ERROR,
                "%s: VBIOS not exist size:0x%x\n",
                __FUNCTION__, device->biosImage.size);
         return -NVL_ERR_NOT_SUPPORTED;
    }

    //
    // Locate the PCI ROM Image
    //
    if (_nvswitch_vbios_identify_pci_image_loc(device, bios_config)  != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Error on identifying pci image loc\n",
            __FUNCTION__);
        status = NV_ERR_GENERIC;
        goto setup_link_vbios_overrides_done;
    }

    //
    // Locate and fetch BIT offset
    //
    if (_nvswitch_vbios_update_bit_Offset(device, bios_config) != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Error on identifying pci image loc\n",
            __FUNCTION__);
        status = NV_ERR_GENERIC;
        goto setup_link_vbios_overrides_done;
    }

    //
    // Fetch NvLink Entries
    //
    if (_nvswitch_vbios_fetch_nvlink_entries(device, bios_config) != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Error on identifying pci image loc\n",
            __FUNCTION__);
        status = NV_ERR_GENERIC;
        goto setup_link_vbios_overrides_done;
    }

    //
    // Assign Base Entry for this device
    //
    if (_nvswitch_vbios_assign_base_entry(device, bios_config) != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Error on assigning base entry\n",
            __FUNCTION__);
        status = NV_ERR_GENERIC;
        goto setup_link_vbios_overrides_done;
    }

setup_link_vbios_overrides_done:
    if (status != NV_OK)
    {
        bios_config->bit_address                = 0;
        bios_config->pci_image_address          = 0;
        bios_config->nvlink_config_table_address =0;
    }
    return status;
}

static void
_nvswitch_load_link_disable_settings_lr10
(
    nvswitch_device *device,
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config,
    nvlink_link *link
)
{
    NvU32 val;
    NVLINK_CONFIG_DATA_LINKENTRY *vbios_link_entry = NULL;

    // SW CTRL - clear out LINK_DISABLE on driver load
    val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber,
            NVLIPT_LNK, _NVLIPT_LNK, _CTRL_SW_LINK_MODE_CTRL);
    val = FLD_SET_DRF(_NVLIPT_LNK, _CTRL_SW_LINK_MODE_CTRL, _LINK_DISABLE,
                      _ENABLED, val);
    NVSWITCH_LINK_WR32_LR10(device, link->linkNumber,
            NVLIPT_LNK, _NVLIPT_LNK, _CTRL_SW_LINK_MODE_CTRL, val);

    //
    // SYSTEM CTRL
    // If the SYSTEM_CTRL setting had been overidden by another entity,
    // it should also be locked, so this write would not take effect.
    //
    if (bios_config != NULL)
    {
        vbios_link_entry = &bios_config->link_vbios_entry[bios_config->link_base_entry_assigned][link->linkNumber];
    }

    val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber,
            NVLIPT_LNK, _NVLIPT_LNK, _CTRL_SYSTEM_LINK_MODE_CTRL);

    if ((vbios_link_entry != NULL) &&
         (FLD_TEST_DRF(_NVLINK_VBIOS,_PARAM0, _LINK, _DISABLE, vbios_link_entry->nvLinkparam0)))
    {
        if (!nvswitch_is_link_in_reset(device, link))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: link #%d is not in reset, cannot set LINK_DISABLE\n",
                __FUNCTION__, link->linkNumber);
            return;
        }
        val = FLD_SET_DRF(_NVLIPT_LNK, _CTRL_SYSTEM_LINK_MODE_CTRL, _LINK_DISABLE,
                          _DISABLED, val);
        NVSWITCH_LINK_WR32_LR10(device, link->linkNumber,
                NVLIPT_LNK, _NVLIPT_LNK, _CTRL_SYSTEM_LINK_MODE_CTRL, val);

        // Set link to invalid and unregister from corelib
        device->link[link->linkNumber].valid = NV_FALSE;
        nvlink_lib_unregister_link(link);
        nvswitch_destroy_link(link);

        return;
    }
    else
    {
        val = FLD_SET_DRF(_NVLIPT_LNK, _CTRL_SYSTEM_LINK_MODE_CTRL, _LINK_DISABLE,
                          _ENABLED, val);
        NVSWITCH_LINK_WR32_LR10(device, link->linkNumber,
                NVLIPT_LNK, _NVLIPT_LNK, _CTRL_SYSTEM_LINK_MODE_CTRL, val);
    }
}

/*
 * @Brief : Setting up system registers after device initialization
 *
 * @Description :
 *
 * @param[in] device        a reference to the device to initialize
 */
NvlStatus
nvswitch_setup_link_system_registers_lr10
(
    nvswitch_device *device
)
{
    nvlink_link *link;
    NvU8 i;
    NvU32 val;
    NvU64 enabledLinkMask;
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config;

    bios_config = nvswitch_get_bios_nvlink_config(device);
    if ((bios_config == NULL) || (bios_config->bit_address == 0))
    {
        NVSWITCH_PRINT(device, WARN,
            "%s: VBIOS NvLink configuration table not found\n",
            __FUNCTION__);
    }

    enabledLinkMask = nvswitch_get_enabled_link_mask(device);

    FOR_EACH_INDEX_IN_MASK(64, i, enabledLinkMask)
    {
        NVSWITCH_ASSERT(i < NVSWITCH_LINK_COUNT(device));

        link = nvswitch_get_link(device, i);

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

        // AC vs DC mode SYSTEM register
        if (link->ac_coupled)
        {
            //
            // In NVL3.0, ACMODE is handled by MINION in the INITPHASE1 command
            // Here we just setup the register with the proper info
            //
            val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK,
                    _NVLIPT_LNK, _CTRL_SYSTEM_LINK_CHANNEL_CTRL);
            val = FLD_SET_DRF(_NVLIPT_LNK,
                    _CTRL_SYSTEM_LINK_CHANNEL_CTRL, _AC_DC_MODE, _AC, val);
            NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLIPT_LNK,
                    _NVLIPT_LNK, _CTRL_SYSTEM_LINK_CHANNEL_CTRL, val);
        }

        _nvswitch_setup_link_system_registers_lr10(device, bios_config, link);
        _nvswitch_load_link_disable_settings_lr10(device, bios_config, link);
    }
    FOR_EACH_INDEX_IN_MASK_END;

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_deassert_link_reset_lr10
(
    nvswitch_device *device,
    nvlink_link     *link
)
{
    NvU64 mode;
    NvlStatus status = NVL_SUCCESS;

    status = device->hal.nvswitch_corelib_get_dl_link_mode(link, &mode);

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

    // Check if the link is RESET
    if (mode != NVLINK_LINKSTATE_RESET)
    {
        return NVL_SUCCESS;
    }

    // Send INITPHASE1 to bring link out of reset
    status = link->link_handlers->set_dl_link_mode(link,
                                        NVLINK_LINKSTATE_INITPHASE1,
                                        NVLINK_STATE_CHANGE_ASYNC);

    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
                "%s: INITPHASE1 failed on link %d\n",
                __FUNCTION__, link->linkNumber);
    }

    return status;
}

static NvU32
_nvswitch_get_num_vcs_lr10
(
    nvswitch_device *device
)
{
    return NVSWITCH_NUM_VCS_LR10;
}

void
nvswitch_determine_platform_lr10
(
    nvswitch_device *device
)
{
    NvU32 value;

    //
    // Determine which model we are using SMC_BOOT_2 and OS query
    //
    value = NVSWITCH_REG_RD32(device, _PSMC, _BOOT_2);
    device->is_emulation = FLD_TEST_DRF(_PSMC, _BOOT_2, _EMULATION, _YES, value);

    if (!IS_EMULATION(device))
    {
        // If we are not on fmodel, we must be on RTL sim or silicon
        if (FLD_TEST_DRF(_PSMC, _BOOT_2, _FMODEL, _YES, value))
        {
            device->is_fmodel = NV_TRUE;
        }
        else
        {
            device->is_rtlsim = NV_TRUE;

            // Let OS code finalize RTL sim vs silicon setting
            nvswitch_os_override_platform(device->os_handle, &device->is_rtlsim);
        }
    }

#if defined(NVLINK_PRINT_ENABLED)
    {
        const char *build;
        const char *mode;

        build = "HW";
        if (IS_FMODEL(device))
            mode = "fmodel";
        else if (IS_RTLSIM(device))
            mode = "rtlsim";
        else if (IS_EMULATION(device))
            mode = "emulation";
        else
            mode = "silicon";

        NVSWITCH_PRINT(device, SETUP,
            "%s: build: %s platform: %s\n",
             __FUNCTION__, build, mode);
    }
#endif // NVLINK_PRINT_ENABLED
}

static void
_nvswitch_portstat_reset_latency_counters
(
    nvswitch_device *device
)
{
    // Set SNAPONDEMAND from 0->1 to reset the counters
    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _NPORT, _PORTSTAT_SNAP_CONTROL,
        DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _STARTCOUNTER, _ENABLE) |
        DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _SNAPONDEMAND, _ENABLE));

    // Set SNAPONDEMAND back to 0.
    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _NPORT, _PORTSTAT_SNAP_CONTROL,
        DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _STARTCOUNTER, _ENABLE) |
        DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _SNAPONDEMAND, _DISABLE));
}

//
// Data collector which runs on a background thread, collecting latency stats.
//
// The latency counters have a maximum window period of 3.299 seconds
// (2^32 clk cycles). The counters reset after this period. So SW snaps
// the bins and records latencies every 3 seconds. Setting SNAPONDEMAND from 0->1
// snaps the  latency counters and updates them to PRI registers for
// the SW to read. It then resets the counters to start collecting fresh latencies.
//

void
nvswitch_internal_latency_bin_log_lr10
(
    nvswitch_device *device
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);
    NvU32 idx_nport;
    NvU32 idx_vc;
    NvBool vc_valid;
    NvU32 latency;
    NvU64 time_nsec;
    NvU32 link_type;    // Access or trunk link
    NvU64 last_visited_time_nsec;

    if (chip_device->latency_stats == NULL)
    {
        // Latency stat buffers not allocated yet
        return;
    }

    time_nsec = nvswitch_os_get_platform_time();
    last_visited_time_nsec = chip_device->latency_stats->last_visited_time_nsec;

    // Update last visited time
    chip_device->latency_stats->last_visited_time_nsec = time_nsec;

    // Compare time stamp and reset the counters if the snap is missed
    if (!IS_RTLSIM(device) || !IS_FMODEL(device))
    {
        if ((last_visited_time_nsec != 0) &&
            ((time_nsec - last_visited_time_nsec) > 3 * NVSWITCH_INTERVAL_1SEC_IN_NS))
        {
            NVSWITCH_PRINT(device, ERROR,
                "Latency metrics recording interval missed.  Resetting counters.\n");
            _nvswitch_portstat_reset_latency_counters(device);
            return;
        }
    }

    for (idx_nport=0; idx_nport < NVSWITCH_LINK_COUNT(device); idx_nport++)
    {
        if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, idx_nport))
        {
            continue;
        }

        // Setting SNAPONDEMAND from 0->1 snaps the latencies and resets the counters
        NVSWITCH_LINK_WR32_LR10(device, idx_nport, NPORT, _NPORT, _PORTSTAT_SNAP_CONTROL,
            DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _STARTCOUNTER, _ENABLE) |
            DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _SNAPONDEMAND, _ENABLE));

        //
        // TODO: Check _STARTCOUNTER and don't log if counter not enabled.
        // Currently all counters are always enabled
        //

        link_type = NVSWITCH_LINK_RD32_LR10(device, idx_nport, NPORT, _NPORT, _CTRL);
        for (idx_vc = 0; idx_vc < NVSWITCH_NUM_VCS_LR10; idx_vc++)
        {
            vc_valid = NV_FALSE;

            // VC's CREQ0(0) and RSP0(5) are relevant on access links.
            if (FLD_TEST_DRF(_NPORT, _CTRL, _TRUNKLINKENB, _ACCESSLINK, link_type) &&
                ((idx_vc == NV_NPORT_VC_MAPPING_CREQ0) ||
                (idx_vc == NV_NPORT_VC_MAPPING_RSP0)))
            {
                vc_valid = NV_TRUE;
            }

            // VC's CREQ0(0), RSP0(5), CREQ1(6) and RSP1(7) are relevant on trunk links.
            if (FLD_TEST_DRF(_NPORT, _CTRL, _TRUNKLINKENB, _TRUNKLINK, link_type) &&
                ((idx_vc == NV_NPORT_VC_MAPPING_CREQ0)  ||
                 (idx_vc == NV_NPORT_VC_MAPPING_RSP0)   ||
                 (idx_vc == NV_NPORT_VC_MAPPING_CREQ1)  ||
                 (idx_vc == NV_NPORT_VC_MAPPING_RSP1)))
            {
                vc_valid = NV_TRUE;
            }

            // If the VC is not being used, skip reading it
            if (!vc_valid)
            {
                continue;
            }

            latency = NVSWITCH_NPORT_PORTSTAT_RD32_LR10(device, idx_nport, _COUNT, _LOW, idx_vc);
            chip_device->latency_stats->latency[idx_vc].accum_latency[idx_nport].low += latency;

            latency = NVSWITCH_NPORT_PORTSTAT_RD32_LR10(device, idx_nport, _COUNT, _MEDIUM, idx_vc);
            chip_device->latency_stats->latency[idx_vc].accum_latency[idx_nport].medium += latency;

            latency = NVSWITCH_NPORT_PORTSTAT_RD32_LR10(device, idx_nport, _COUNT, _HIGH, idx_vc);
            chip_device->latency_stats->latency[idx_vc].accum_latency[idx_nport].high += latency;

            latency = NVSWITCH_NPORT_PORTSTAT_RD32_LR10(device, idx_nport, _COUNT, _PANIC, idx_vc);
            chip_device->latency_stats->latency[idx_vc].accum_latency[idx_nport].panic += latency;

            latency = NVSWITCH_NPORT_PORTSTAT_RD32_LR10(device, idx_nport, _PACKET, _COUNT, idx_vc);
            chip_device->latency_stats->latency[idx_vc].accum_latency[idx_nport].count += latency;

            // Note the time of this snap
            chip_device->latency_stats->latency[idx_vc].last_read_time_nsec = time_nsec;
            chip_device->latency_stats->latency[idx_vc].count++;
        }

        // Disable SNAPONDEMAND after fetching the latencies
        NVSWITCH_LINK_WR32_LR10(device, idx_nport, NPORT, _NPORT, _PORTSTAT_SNAP_CONTROL,
            DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _STARTCOUNTER, _ENABLE) |
            DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _SNAPONDEMAND, _DISABLE));
    }
}

void
nvswitch_ecc_writeback_task_lr10
(
    nvswitch_device *device
)
{
}

void
nvswitch_set_ganged_link_table_lr10
(
    nvswitch_device *device,
    NvU32            firstIndex,
    NvU64           *ganged_link_table,
    NvU32            numEntries
)
{
    NvU32 i;

    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _ROUTE, _REG_TABLE_ADDRESS,
        DRF_NUM(_ROUTE, _REG_TABLE_ADDRESS, _INDEX, firstIndex) |
        DRF_NUM(_ROUTE, _REG_TABLE_ADDRESS, _AUTO_INCR, 1));

    for (i = 0; i < numEntries; i++)
    {
        NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _ROUTE, _REG_TABLE_DATA0,
            NvU64_LO32(ganged_link_table[i]));

        NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _ROUTE, _REG_TABLE_DATA0,
            NvU64_HI32(ganged_link_table[i]));
    }
}

static NvlStatus
_nvswitch_init_ganged_link_routing
(
    nvswitch_device *device
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);
    NvU32        gang_index, gang_size;
    NvU64        gang_entry;
    NvU32        block_index;
    NvU32        block_count = 16;
    NvU32        glt_entries = 16;
    NvU32        glt_size = ROUTE_GANG_TABLE_SIZE / 2;
    NvU64        *ganged_link_table = NULL;
    NvU32        block_size = ROUTE_GANG_TABLE_SIZE / block_count;
    NvU32        table_index = 0;
    NvU32        i;

    //
    // Refer to switch IAS 11.2 Figure 82. Limerock Ganged RAM Table Format
    //
    // The ganged link routing table is composed of 512 entries divided into 16 sections.
    // Each section specifies how requests should be routed through the ganged links.
    // Each 32-bit entry is composed of eight 4-bit fields specifying the set of of links
    // to distribute through.  More complex spray patterns could be constructed, but for
    // now initialize it with a uniform distribution pattern.
    //
    // The ganged link routing table will be loaded with following values:
    // Typically the first section would be filled with (0,1,2,3,4,5,6,7), (8,9,10,11,12,13,14,15),...
    // Typically the second section would be filled with (0,0,0,0,0,0,0,0), (0,0,0,0,0,0,0,0),...
    // Typically the third section would be filled with (0,1,0,1,0,1,0,1), (0,1,0,1,0,1,0,1),...
    // Typically the third section would be filled with (0,1,2,0,1,2,0,1), (2,0,1,2,0,1,2,0),...
    //  :
    // The last section would typically be filled with (0,1,2,3,4,5,6,7), (8,9,10,11,12,13,14,0),...
    //
    // Refer table 20: Definition of size bits used with Ganged Link Number Table.
    // Note that section 0 corresponds with 16 ganged links.  Section N corresponds with
    // N ganged links.
    //

    //Alloc memory for Ganged Link Table
    ganged_link_table = nvswitch_os_malloc(glt_size * sizeof(gang_entry));
    if (ganged_link_table == NULL)
    {
        NVSWITCH_PRINT(device, ERROR,
            "Failed to allocate memory for GLT!!\n");
        return -NVL_NO_MEM;
    }

    for (block_index = 0; block_index < block_count; block_index++)
    {
        gang_size = ((block_index==0) ? 16 : block_index);

        for (gang_index = 0; gang_index < block_size/2; gang_index++)
        {
            gang_entry = 0;
            NVSWITCH_ASSERT(table_index < glt_size);

            for (i = 0; i < glt_entries; i++)
            {
                gang_entry |=
                    DRF_NUM64(_ROUTE, _REG_TABLE_DATA0, _GLX(i), (16 * gang_index + i) % gang_size);
            }

            ganged_link_table[table_index++] = gang_entry;
        }
    }

    nvswitch_set_ganged_link_table_lr10(device, 0, ganged_link_table, glt_size);

    chip_device->ganged_link_table = ganged_link_table;

    return NVL_SUCCESS;
}

static NvlStatus
nvswitch_initialize_ip_wrappers_lr10
(
    nvswitch_device *device
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);
    NvU32 engine_enable_mask;
    NvU32 engine_disable_mask;
    NvU32 i, j;
    NvU32 idx_link;

    //
    // Now that software knows the devices and addresses, it must take all
    // the wrapper modules out of reset.  It does this by writing to the
    // PMC module enable registers.
    //

// Temporary - bug 2069764
//    NVSWITCH_REG_WR32(device, _PSMC, _ENABLE,
//        DRF_DEF(_PSMC, _ENABLE, _SAW, _ENABLE) |
//        DRF_DEF(_PSMC, _ENABLE, _PRIV_RING, _ENABLE) |
//        DRF_DEF(_PSMC, _ENABLE, _PERFMON, _ENABLE));

    NVSWITCH_SAW_WR32_LR10(device, _NVLSAW_NVSPMC, _ENABLE,
        DRF_DEF(_NVLSAW_NVSPMC, _ENABLE, _NXBAR, _ENABLE));

    //
    // At this point the list of discovered devices has been cross-referenced
    // with the ROM configuration, platform configuration, and regkey override.
    // The NVLIPT & NPORT enable filtering done here further updates the MMIO
    // information based on KVM.
    //

    // Enable the NVLIPT units that have been discovered
    engine_enable_mask = 0;
    for (i = 0; i < NVSWITCH_ENG_COUNT(device, NVLW, ); i++)
    {
        if (NVSWITCH_ENG_IS_VALID(device, NVLW, i))
        {
            engine_enable_mask |= NVBIT(i);
        }
    }
    NVSWITCH_SAW_WR32_LR10(device, _NVLSAW_NVSPMC, _ENABLE_NVLIPT, engine_enable_mask);

    //
    // In bare metal we write ENABLE_NVLIPT to enable the units that aren't
    // disabled by ROM configuration, platform configuration, or regkey override.
    // If we are running inside a VM, the hypervisor has already set ENABLE_NVLIPT
    // and write protected it.  Reading ENABLE_NVLIPT tells us which units we
    // are allowed to use inside this VM.
    //
    engine_disable_mask = ~NVSWITCH_SAW_RD32_LR10(device, _NVLSAW_NVSPMC, _ENABLE_NVLIPT);
    if (engine_enable_mask != ~engine_disable_mask)
    {
        NVSWITCH_PRINT(device, WARN,
            "NV_NVLSAW_NVSPMC_ENABLE_NVLIPT mismatch: wrote 0x%x, read 0x%x\n",
            engine_enable_mask,
            ~engine_disable_mask);
        NVSWITCH_PRINT(device, WARN,
            "Ignoring NV_NVLSAW_NVSPMC_ENABLE_NVLIPT readback until supported on fmodel\n");
        engine_disable_mask = ~engine_enable_mask;
    }
    engine_disable_mask &= NVBIT(NVSWITCH_ENG_COUNT(device, NVLW, )) - 1;
    FOR_EACH_INDEX_IN_MASK(32, i, engine_disable_mask)
    {
        chip_device->engNVLW[i].valid = NV_FALSE;
        for (j = 0; j < NVSWITCH_LINKS_PER_NVLW; j++)
        {
            idx_link = i * NVSWITCH_LINKS_PER_NVLW + j;
            if (idx_link < NVSWITCH_LINK_COUNT(device))
            {
                device->link[idx_link].valid = NV_FALSE;
                //
                // TODO: This invalidate used to also invalidate all the
                // associated NVLW engFOO units. This is probably not necessary
                // but code that bypasses the link valid check might touch the
                // underlying units when they are not supposed to.
                //
            }
        }
    }
    FOR_EACH_INDEX_IN_MASK_END;

    // Enable the NPORT units that have been discovered
    engine_enable_mask = 0;
    for (i = 0; i < NVSWITCH_ENG_COUNT(device, NPG, ); i++)
    {
        if (NVSWITCH_ENG_IS_VALID(device, NPG, i))
        {
            engine_enable_mask |= NVBIT(i);
        }
    }
    NVSWITCH_SAW_WR32_LR10(device, _NVLSAW_NVSPMC, _ENABLE_NPG, engine_enable_mask);

    //
    // In bare metal we write ENABLE_NPG to enable the units that aren't
    // disabled by ROM configuration, platform configuration, or regkey override.
    // If we are running inside a VM, the hypervisor has already set ENABLE_NPG
    // and write protected it.  Reading ENABLE_NPG tells us which units we
    // are allowed to use inside this VM.
    //
    engine_disable_mask = ~NVSWITCH_SAW_RD32_LR10(device, _NVLSAW_NVSPMC, _ENABLE_NPG);
    if (engine_enable_mask != ~engine_disable_mask)
    {
        NVSWITCH_PRINT(device, WARN,
            "NV_NVLSAW_NVSPMC_ENABLE_NPG mismatch: wrote 0x%x, read 0x%x\n",
            engine_enable_mask,
            ~engine_disable_mask);
        NVSWITCH_PRINT(device, WARN,
            "Ignoring NV_NVLSAW_NVSPMC_ENABLE_NPG readback until supported on fmodel\n");
        engine_disable_mask = ~engine_enable_mask;
    }
    engine_disable_mask &= NVBIT(NVSWITCH_ENG_COUNT(device, NPG, )) - 1;
    FOR_EACH_INDEX_IN_MASK(32, i, engine_disable_mask)
    {
        chip_device->engNPG[i].valid = NV_FALSE;
        for (j = 0; j < NVSWITCH_LINKS_PER_NPG; j++)
        {
            idx_link = i * NVSWITCH_LINKS_PER_NPG + j;

            if (idx_link < NVSWITCH_LINK_COUNT(device))
            {
                device->link[idx_link].valid = NV_FALSE;
                //
                // TODO: This invalidate used to also invalidate all the
                // associated NPG engFOO units. This is probably not necessary
                // but code that bypasses the link valid check might touch the
                // underlying units when they are not supposed to.
                //
            }
        }
    }
    FOR_EACH_INDEX_IN_MASK_END;

    return NVL_SUCCESS;
}

//
// Bring units out of warm reset on boot.  Used by driver load.
//
static void
_nvswitch_init_warm_reset_lr10
(
    nvswitch_device *device
)
{
    NvU32 idx_npg;
    NvU32 idx_nport;
    NvU32 nport_mask;
    NvU32 nport_disable = 0;

#if defined(NV_NPG_WARMRESET_NPORTDISABLE)
    nport_disable = DRF_NUM(_NPG, _WARMRESET, _NPORTDISABLE, ~nport_mask);
#endif

    //
    // Walk the NPGs and build the mask of extant NPORTs
    //
    for (idx_npg = 0; idx_npg < NVSWITCH_ENG_COUNT(device, NPG, ); idx_npg++)
    {
        if (NVSWITCH_ENG_IS_VALID(device, NPG, idx_npg))
        {
            nport_mask = 0;
            for (idx_nport = 0; idx_nport < NVSWITCH_NPORT_PER_NPG; idx_nport++)
            {
                nport_mask |=
                    (NVSWITCH_ENG_IS_VALID(device, NPORT, idx_npg*NVSWITCH_NPORT_PER_NPG + idx_nport) ?
                    NVBIT(idx_nport) : 0x0);
            }

            NVSWITCH_NPG_WR32_LR10(device, idx_npg,
                _NPG, _WARMRESET,
                nport_disable |
                DRF_NUM(_NPG, _WARMRESET, _NPORTWARMRESET, nport_mask));
        }
    }
}

/*
 * CTRL_NVSWITCH_SET_REMAP_POLICY
 */

NvlStatus
nvswitch_get_remap_table_selector_lr10
(
    nvswitch_device *device,
    NVSWITCH_TABLE_SELECT_REMAP table_selector,
    NvU32 *remap_ram_sel
)
{
    NvU32 ram_sel = 0;

    switch (table_selector)
    {
        case NVSWITCH_TABLE_SELECT_REMAP_PRIMARY:
            ram_sel = NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSREMAPPOLICYRAM;
            break;
        default:
            // Unsupported remap table selector
            return -NVL_ERR_NOT_SUPPORTED;
            break;
    }

    if (remap_ram_sel)
    {
        *remap_ram_sel = ram_sel;
    }

    return NVL_SUCCESS;
}

NvU32
nvswitch_get_ingress_ram_size_lr10
(
    nvswitch_device *device,
    NvU32 ingress_ram_selector      // NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECT*
)
{
    NvU32 ram_size = 0;

    switch (ingress_ram_selector)
    {
        case NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSREMAPPOLICYRAM:
            ram_size = NV_INGRESS_REQRSPMAPADDR_RAM_ADDRESS_REMAPTAB_DEPTH + 1;
            break;
        case NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRIDROUTERAM:
            ram_size = NV_INGRESS_REQRSPMAPADDR_RAM_ADDRESS_RID_TAB_DEPTH + 1;
            break;
        case NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRLANROUTERAM:
            ram_size = NV_INGRESS_REQRSPMAPADDR_RAM_ADDRESS_RLAN_TAB_DEPTH + 1;
            break;
        default:
            // Unsupported ingress RAM selector
            break;
    }

    return ram_size;
}

static void
_nvswitch_set_remap_policy_lr10
(
    nvswitch_device *device,
    NvU32 portNum,
    NvU32 firstIndex,
    NvU32 numEntries,
    NVSWITCH_REMAP_POLICY_ENTRY *remap_policy
)
{
    NvU32 i;
    NvU32 remap_address;
    NvU32 address_offset;
    NvU32 address_base;
    NvU32 address_limit;

    NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _REQRSPMAPADDR,
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _RAM_ADDRESS, firstIndex) |
        DRF_DEF(_INGRESS, _REQRSPMAPADDR, _RAM_SEL, _SELECTSREMAPPOLICYRAM) |
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _AUTO_INCR, 1));

    for (i = 0; i < numEntries; i++)
    {
        // Set each field if enabled, else set it to 0.
        remap_address = DRF_VAL64(_INGRESS, _REMAP, _ADDR_PHYS_LR10, remap_policy[i].address);
        address_offset = DRF_VAL64(_INGRESS, _REMAP, _ADR_OFFSET_PHYS_LR10, remap_policy[i].addressOffset);
        address_base = DRF_VAL64(_INGRESS, _REMAP, _ADR_BASE_PHYS_LR10, remap_policy[i].addressBase);
        address_limit = DRF_VAL64(_INGRESS, _REMAP, _ADR_LIMIT_PHYS_LR10, remap_policy[i].addressLimit);

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _REMAPTABDATA1,
            DRF_NUM(_INGRESS, _REMAPTABDATA1, _REQCTXT_MSK, remap_policy[i].reqCtxMask) |
            DRF_NUM(_INGRESS, _REMAPTABDATA1, _REQCTXT_CHK, remap_policy[i].reqCtxChk));
        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _REMAPTABDATA2,
            DRF_NUM(_INGRESS, _REMAPTABDATA2, _REQCTXT_REP, remap_policy[i].reqCtxRep) |
            DRF_NUM(_INGRESS, _REMAPTABDATA2, _ADR_OFFSET, address_offset));
        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _REMAPTABDATA3,
            DRF_NUM(_INGRESS, _REMAPTABDATA3, _ADR_BASE, address_base) |
            DRF_NUM(_INGRESS, _REMAPTABDATA3, _ADR_LIMIT, address_limit));
        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _REMAPTABDATA4,
            DRF_NUM(_INGRESS, _REMAPTABDATA4, _TGTID, remap_policy[i].targetId) |
            DRF_NUM(_INGRESS, _REMAPTABDATA4, _RFUNC, remap_policy[i].flags));

        // Write last and auto-increment
        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _REMAPTABDATA0,
            DRF_NUM(_INGRESS, _REMAPTABDATA0, _RMAP_ADDR, remap_address) |
            DRF_NUM(_INGRESS, _REMAPTABDATA0, _IRL_SEL, remap_policy[i].irlSelect) |
            DRF_NUM(_INGRESS, _REMAPTABDATA0, _ACLVALID, remap_policy[i].entryValid));
    }
}

NvlStatus
nvswitch_ctrl_set_remap_policy_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_REMAP_POLICY *p
)
{
    NvU32 i;
    NvU32 rfunc;
    NvU32 ram_size;
    NvlStatus retval = NVL_SUCCESS;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, p->portNum))
    {
        NVSWITCH_PRINT(device, ERROR,
            "NPORT port #%d not valid\n",
            p->portNum);
        return -NVL_BAD_ARGS;
    }

    if (p->tableSelect != NVSWITCH_TABLE_SELECT_REMAP_PRIMARY)
    {
        NVSWITCH_PRINT(device, ERROR,
            "Remap table #%d not supported\n",
            p->tableSelect);
        return -NVL_ERR_NOT_SUPPORTED;
    }

    ram_size = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSREMAPPOLICYRAM);
    if ((p->firstIndex >= ram_size) ||
        (p->numEntries > NVSWITCH_REMAP_POLICY_ENTRIES_MAX) ||
        (p->firstIndex + p->numEntries > ram_size))
    {
        NVSWITCH_PRINT(device, ERROR,
            "remapPolicy[%d..%d] overflows range %d..%d or size %d.\n",
            p->firstIndex, p->firstIndex + p->numEntries - 1,
            0, ram_size - 1,
            NVSWITCH_REMAP_POLICY_ENTRIES_MAX);
        return -NVL_BAD_ARGS;
    }

    for (i = 0; i < p->numEntries; i++)
    {
        if (p->remapPolicy[i].targetId &
            ~DRF_MASK(NV_INGRESS_REMAPTABDATA4_TGTID))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].targetId 0x%x out of valid range (0x%x..0x%x)\n",
                i, p->remapPolicy[i].targetId,
                0, DRF_MASK(NV_INGRESS_REMAPTABDATA4_TGTID));
            return -NVL_BAD_ARGS;
        }

        if (p->remapPolicy[i].irlSelect &
            ~DRF_MASK(NV_INGRESS_REMAPTABDATA0_IRL_SEL))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].irlSelect 0x%x out of valid range (0x%x..0x%x)\n",
                i, p->remapPolicy[i].irlSelect,
                0, DRF_MASK(NV_INGRESS_REMAPTABDATA0_IRL_SEL));
            return -NVL_BAD_ARGS;
        }

        rfunc = p->remapPolicy[i].flags &
            (
                NVSWITCH_REMAP_POLICY_FLAGS_REMAP_ADDR |
                NVSWITCH_REMAP_POLICY_FLAGS_REQCTXT_CHECK |
                NVSWITCH_REMAP_POLICY_FLAGS_REQCTXT_REPLACE |
                NVSWITCH_REMAP_POLICY_FLAGS_ADR_BASE |
                NVSWITCH_REMAP_POLICY_FLAGS_ADR_OFFSET
            );
        if (rfunc != p->remapPolicy[i].flags)
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].flags 0x%x has undefined flags (0x%x)\n",
                i, p->remapPolicy[i].flags,
                p->remapPolicy[i].flags ^ rfunc);
            return -NVL_BAD_ARGS;
        }

        // Validate that only bits 46:36 are used
        if (p->remapPolicy[i].address &
            ~DRF_SHIFTMASK64(NV_INGRESS_REMAP_ADDR_PHYS_LR10))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].address 0x%llx & ~0x%llx != 0\n",
                i, p->remapPolicy[i].address,
                DRF_SHIFTMASK64(NV_INGRESS_REMAP_ADDR_PHYS_LR10));
            return -NVL_BAD_ARGS;
        }

        if (p->remapPolicy[i].reqCtxMask &
           ~DRF_MASK(NV_INGRESS_REMAPTABDATA1_REQCTXT_MSK))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].reqCtxMask 0x%x out of valid range (0x%x..0x%x)\n",
                i, p->remapPolicy[i].reqCtxMask,
                0, DRF_MASK(NV_INGRESS_REMAPTABDATA1_REQCTXT_MSK));
            return -NVL_BAD_ARGS;
        }

        if (p->remapPolicy[i].reqCtxChk &
            ~DRF_MASK(NV_INGRESS_REMAPTABDATA1_REQCTXT_CHK))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].reqCtxChk 0x%x out of valid range (0x%x..0x%x)\n",
                i, p->remapPolicy[i].reqCtxChk,
                0, DRF_MASK(NV_INGRESS_REMAPTABDATA1_REQCTXT_CHK));
            return -NVL_BAD_ARGS;
        }

        if (p->remapPolicy[i].reqCtxRep &
            ~DRF_MASK(NV_INGRESS_REMAPTABDATA2_REQCTXT_REP))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].reqCtxRep 0x%x out of valid range (0x%x..0x%x)\n",
                i, p->remapPolicy[i].reqCtxRep,
                0, DRF_MASK(NV_INGRESS_REMAPTABDATA2_REQCTXT_REP));
            return -NVL_BAD_ARGS;
        }

        if ((p->remapPolicy[i].flags & NVSWITCH_REMAP_POLICY_FLAGS_ADR_OFFSET) &&
            !(p->remapPolicy[i].flags & NVSWITCH_REMAP_POLICY_FLAGS_ADR_BASE))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].flags: _FLAGS_ADR_OFFSET should not be set if "
                "_FLAGS_ADR_BASE is not set\n",
                i);
            return -NVL_BAD_ARGS;
        }

        // Validate that only bits 35:20 are used
        if (p->remapPolicy[i].addressBase &
            ~DRF_SHIFTMASK64(NV_INGRESS_REMAP_ADR_BASE_PHYS_LR10))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].addressBase 0x%llx & ~0x%llx != 0\n",
                i, p->remapPolicy[i].addressBase,
                DRF_SHIFTMASK64(NV_INGRESS_REMAP_ADR_BASE_PHYS_LR10));
            return -NVL_BAD_ARGS;
        }

        // Validate that only bits 35:20 are used
        if (p->remapPolicy[i].addressLimit &
            ~DRF_SHIFTMASK64(NV_INGRESS_REMAP_ADR_LIMIT_PHYS_LR10))
        {
            NVSWITCH_PRINT(device, ERROR,
                 "remapPolicy[%d].addressLimit 0x%llx & ~0x%llx != 0\n",
                 i, p->remapPolicy[i].addressLimit,
                 DRF_SHIFTMASK64(NV_INGRESS_REMAP_ADR_LIMIT_PHYS_LR10));
            return -NVL_BAD_ARGS;
        }

        // Validate base & limit describe a region
        if (p->remapPolicy[i].addressBase > p->remapPolicy[i].addressLimit)
        {
            NVSWITCH_PRINT(device, ERROR,
                 "remapPolicy[%d].addressBase/Limit invalid: 0x%llx > 0x%llx\n",
                 i, p->remapPolicy[i].addressBase, p->remapPolicy[i].addressLimit);
            return -NVL_BAD_ARGS;
        }

        // Validate that only bits 35:20 are used
        if (p->remapPolicy[i].addressOffset &
            ~DRF_SHIFTMASK64(NV_INGRESS_REMAP_ADR_OFFSET_PHYS_LR10))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].addressOffset 0x%llx & ~0x%llx != 0\n",
                i, p->remapPolicy[i].addressOffset,
                DRF_SHIFTMASK64(NV_INGRESS_REMAP_ADR_OFFSET_PHYS_LR10));
            return -NVL_BAD_ARGS;
        }

        // Validate limit - base + offset doesn't overflow 64G
        if ((p->remapPolicy[i].addressLimit - p->remapPolicy[i].addressBase +
                p->remapPolicy[i].addressOffset) &
            ~DRF_SHIFTMASK64(NV_INGRESS_REMAP_ADR_OFFSET_PHYS_LR10))
        {
            NVSWITCH_PRINT(device, ERROR,
                "remapPolicy[%d].addressLimit 0x%llx - addressBase 0x%llx + "
                "addressOffset 0x%llx overflows 64GB\n",
                i, p->remapPolicy[i].addressLimit, p->remapPolicy[i].addressBase,
                p->remapPolicy[i].addressOffset);
            return -NVL_BAD_ARGS;
        }
    }

    _nvswitch_set_remap_policy_lr10(device, p->portNum, p->firstIndex, p->numEntries, p->remapPolicy);

    return retval;
}

/*
 * CTRL_NVSWITCH_GET_REMAP_POLICY
 */

#define NVSWITCH_NUM_REMAP_POLICY_REGS_LR10 5

NvlStatus
nvswitch_ctrl_get_remap_policy_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_REMAP_POLICY_PARAMS *params
)
{
    NVSWITCH_REMAP_POLICY_ENTRY *remap_policy;
    NvU32 remap_policy_data[NVSWITCH_NUM_REMAP_POLICY_REGS_LR10]; // 5 REMAP tables
    NvU32 table_index;
    NvU32 remap_count;
    NvU32 remap_address;
    NvU32 address_offset;
    NvU32 address_base;
    NvU32 address_limit;
    NvU32 ram_size;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, params->portNum))
    {
        NVSWITCH_PRINT(device, ERROR,
            "NPORT port #%d not valid\n",
            params->portNum);
        return -NVL_BAD_ARGS;
    }

    if (params->tableSelect != NVSWITCH_TABLE_SELECT_REMAP_PRIMARY)
    {
        NVSWITCH_PRINT(device, ERROR,
            "Remap table #%d not supported\n",
            params->tableSelect);
        return -NVL_ERR_NOT_SUPPORTED;
    }

    ram_size = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSREMAPPOLICYRAM);
    if ((params->firstIndex >= ram_size))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: remapPolicy first index %d out of range[%d..%d].\n",
            __FUNCTION__, params->firstIndex, 0, ram_size - 1);
        return -NVL_BAD_ARGS;
    }

    nvswitch_os_memset(params->entry, 0, (NVSWITCH_REMAP_POLICY_ENTRIES_MAX *
        sizeof(NVSWITCH_REMAP_POLICY_ENTRY)));

    table_index = params->firstIndex;
    remap_policy = params->entry;
    remap_count = 0;

    /* set table offset */
    NVSWITCH_LINK_WR32_LR10(device, params->portNum, NPORT, _INGRESS, _REQRSPMAPADDR,
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _RAM_ADDRESS, params->firstIndex) |
        DRF_DEF(_INGRESS, _REQRSPMAPADDR, _RAM_SEL, _SELECTSREMAPPOLICYRAM) |
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _AUTO_INCR, 1));

    while (remap_count < NVSWITCH_REMAP_POLICY_ENTRIES_MAX &&
        table_index < ram_size)
    {
        remap_policy_data[0] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _REMAPTABDATA0);
        remap_policy_data[1] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _REMAPTABDATA1);
        remap_policy_data[2] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _REMAPTABDATA2);
        remap_policy_data[3] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _REMAPTABDATA3);
        remap_policy_data[4] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _REMAPTABDATA4);

        /* add to remap_entries list if nonzero */
        if (remap_policy_data[0] || remap_policy_data[1] || remap_policy_data[2] ||
            remap_policy_data[3] || remap_policy_data[4])
        {
            remap_policy[remap_count].irlSelect =
                DRF_VAL(_INGRESS, _REMAPTABDATA0, _IRL_SEL, remap_policy_data[0]);

            remap_policy[remap_count].entryValid =
                DRF_VAL(_INGRESS, _REMAPTABDATA0, _ACLVALID, remap_policy_data[0]);

            remap_address =
                DRF_VAL(_INGRESS, _REMAPTABDATA0, _RMAP_ADDR, remap_policy_data[0]);

            remap_policy[remap_count].address =
                DRF_NUM64(_INGRESS, _REMAP, _ADDR_PHYS_LR10, remap_address);

            remap_policy[remap_count].reqCtxMask =
                DRF_VAL(_INGRESS, _REMAPTABDATA1, _REQCTXT_MSK, remap_policy_data[1]);

            remap_policy[remap_count].reqCtxChk =
                DRF_VAL(_INGRESS, _REMAPTABDATA1, _REQCTXT_CHK, remap_policy_data[1]);

            remap_policy[remap_count].reqCtxRep =
                DRF_VAL(_INGRESS, _REMAPTABDATA2, _REQCTXT_REP, remap_policy_data[2]);

            address_offset =
                DRF_VAL(_INGRESS, _REMAPTABDATA2, _ADR_OFFSET, remap_policy_data[2]);

            remap_policy[remap_count].addressOffset =
                DRF_NUM64(_INGRESS, _REMAP, _ADR_OFFSET_PHYS_LR10, address_offset);

            address_base =
                DRF_VAL(_INGRESS, _REMAPTABDATA3, _ADR_BASE, remap_policy_data[3]);

            remap_policy[remap_count].addressBase =
                DRF_NUM64(_INGRESS, _REMAP, _ADR_BASE_PHYS_LR10, address_base);

            address_limit =
                DRF_VAL(_INGRESS, _REMAPTABDATA3, _ADR_LIMIT, remap_policy_data[3]);

            remap_policy[remap_count].addressLimit =
                DRF_NUM64(_INGRESS, _REMAP, _ADR_LIMIT_PHYS_LR10, address_limit);

            remap_policy[remap_count].targetId =
                DRF_VAL(_INGRESS, _REMAPTABDATA4, _TGTID, remap_policy_data[4]);

            remap_policy[remap_count].flags =
                DRF_VAL(_INGRESS, _REMAPTABDATA4, _RFUNC, remap_policy_data[4]);

            remap_count++;
        }

        table_index++;
    }

    params->nextIndex = table_index;
    params->numEntries = remap_count;

    return NVL_SUCCESS;
}

/*
 * CTRL_NVSWITCH_SET_REMAP_POLICY_VALID
 */
NvlStatus
nvswitch_ctrl_set_remap_policy_valid_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_REMAP_POLICY_VALID *p
)
{
    NvU32 remap_ram;
    NvU32 ram_address = p->firstIndex;
    NvU32 remap_policy_data[NVSWITCH_NUM_REMAP_POLICY_REGS_LR10]; // 5 REMAP tables
    NvU32 i;
    NvU32 ram_size;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, p->portNum))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: NPORT port #%d not valid\n",
            __FUNCTION__, p->portNum);
        return -NVL_BAD_ARGS;
    }

    if (p->tableSelect != NVSWITCH_TABLE_SELECT_REMAP_PRIMARY)
    {
        NVSWITCH_PRINT(device, ERROR,
            "Remap table #%d not supported\n",
            p->tableSelect);
        return -NVL_ERR_NOT_SUPPORTED;
    }

    ram_size = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSREMAPPOLICYRAM);
    if ((p->firstIndex >= ram_size) ||
        (p->numEntries > NVSWITCH_REMAP_POLICY_ENTRIES_MAX) ||
        (p->firstIndex + p->numEntries > ram_size))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: remapPolicy[%d..%d] overflows range %d..%d or size %d.\n",
            __FUNCTION__, p->firstIndex, p->firstIndex + p->numEntries - 1,
            0, ram_size - 1,
            NVSWITCH_REMAP_POLICY_ENTRIES_MAX);
        return -NVL_BAD_ARGS;
    }

    // Select REMAPPOLICY RAM and disable Auto Increament.
    remap_ram =
        DRF_DEF(_INGRESS, _REQRSPMAPADDR, _RAM_SEL, _SELECTSREMAPPOLICYRAM) |
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _AUTO_INCR, 0);

    for (i = 0; i < p->numEntries; i++)
    {
        /* set the ram address */
        remap_ram = FLD_SET_DRF_NUM(_INGRESS, _REQRSPMAPADDR, _RAM_ADDRESS, ram_address++, remap_ram);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _REQRSPMAPADDR, remap_ram);

        remap_policy_data[0] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA0);
        remap_policy_data[1] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA1);
        remap_policy_data[2] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA2);
        remap_policy_data[3] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA3);
        remap_policy_data[4] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA4);

        // Set valid bit in REMAPTABDATA0.
        remap_policy_data[0] = FLD_SET_DRF_NUM(_INGRESS, _REMAPTABDATA0, _ACLVALID, p->entryValid[i], remap_policy_data[0]);

        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA4, remap_policy_data[4]);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA3, remap_policy_data[3]);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA2, remap_policy_data[2]);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA1, remap_policy_data[1]);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _REMAPTABDATA0, remap_policy_data[0]);
    }

    return NVL_SUCCESS;
}

//
// Programming invalid entries to 0x3F causes Route block to detect an invalid port number
// and flag a PRIV error to the FM. (See Table 14.RID RAM Programming, IAS 3.3.4)
//

#define NVSWITCH_INVALID_PORT_VAL_LR10   0x3F
#define NVSWITCH_INVALID_VC_VAL_LR10     0x0

#define NVSWITCH_PORTLIST_PORT_LR10(_entry, _idx) \
    ((_idx < _entry.numEntries) ? _entry.portList[_idx].destPortNum : NVSWITCH_INVALID_PORT_VAL_LR10)

#define NVSWITCH_PORTLIST_VC_LR10(_entry, _idx) \
    ((_idx < _entry.numEntries) ? _entry.portList[_idx].vcMap : NVSWITCH_INVALID_VC_VAL_LR10)

/*
 * CTRL_NVSWITCH_SET_ROUTING_ID
 */

static void
_nvswitch_set_routing_id_lr10
(
    nvswitch_device *device,
    NvU32 portNum,
    NvU32 firstIndex,
    NvU32 numEntries,
    NVSWITCH_ROUTING_ID_ENTRY *routing_id
)
{
    NvU32 i;
    NvU32 rmod;

    NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _REQRSPMAPADDR,
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _RAM_ADDRESS, firstIndex) |
        DRF_DEF(_INGRESS, _REQRSPMAPADDR, _RAM_SEL, _SELECTSRIDROUTERAM) |
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _AUTO_INCR, 1));

    for (i = 0; i < numEntries; i++)
    {
        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RIDTABDATA1,
            DRF_NUM(_INGRESS, _RIDTABDATA1, _PORT3,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 3)) |
            DRF_NUM(_INGRESS, _RIDTABDATA1, _VC_MODE3, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 3))   |
            DRF_NUM(_INGRESS, _RIDTABDATA1, _PORT4,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 4)) |
            DRF_NUM(_INGRESS, _RIDTABDATA1, _VC_MODE4, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 4))   |
            DRF_NUM(_INGRESS, _RIDTABDATA1, _PORT5,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 5)) |
            DRF_NUM(_INGRESS, _RIDTABDATA1, _VC_MODE5, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 5)));

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RIDTABDATA2,
            DRF_NUM(_INGRESS, _RIDTABDATA2, _PORT6,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 6)) |
            DRF_NUM(_INGRESS, _RIDTABDATA2, _VC_MODE6, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 6))   |
            DRF_NUM(_INGRESS, _RIDTABDATA2, _PORT7,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 7)) |
            DRF_NUM(_INGRESS, _RIDTABDATA2, _VC_MODE7, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 7))   |
            DRF_NUM(_INGRESS, _RIDTABDATA2, _PORT8,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 8)) |
            DRF_NUM(_INGRESS, _RIDTABDATA2, _VC_MODE8, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 8)));

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RIDTABDATA3,
            DRF_NUM(_INGRESS, _RIDTABDATA3, _PORT9,     NVSWITCH_PORTLIST_PORT_LR10(routing_id[i],  9)) |
            DRF_NUM(_INGRESS, _RIDTABDATA3, _VC_MODE9,  NVSWITCH_PORTLIST_VC_LR10(routing_id[i],  9))   |
            DRF_NUM(_INGRESS, _RIDTABDATA3, _PORT10,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 10)) |
            DRF_NUM(_INGRESS, _RIDTABDATA3, _VC_MODE10, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 10))   |
            DRF_NUM(_INGRESS, _RIDTABDATA3, _PORT11,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 11)) |
            DRF_NUM(_INGRESS, _RIDTABDATA3, _VC_MODE11, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 11)));

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RIDTABDATA4,
            DRF_NUM(_INGRESS, _RIDTABDATA4, _PORT12,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 12)) |
            DRF_NUM(_INGRESS, _RIDTABDATA4, _VC_MODE12, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 12))   |
            DRF_NUM(_INGRESS, _RIDTABDATA4, _PORT13,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 13)) |
            DRF_NUM(_INGRESS, _RIDTABDATA4, _VC_MODE13, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 13))   |
            DRF_NUM(_INGRESS, _RIDTABDATA4, _PORT14,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 14)) |
            DRF_NUM(_INGRESS, _RIDTABDATA4, _VC_MODE14, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 14)));

        rmod =
            (routing_id[i].useRoutingLan ? NVBIT(6) : 0) |
            (routing_id[i].enableIrlErrResponse ? NVBIT(9) : 0);

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RIDTABDATA5,
            DRF_NUM(_INGRESS, _RIDTABDATA5, _PORT15,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 15)) |
            DRF_NUM(_INGRESS, _RIDTABDATA5, _VC_MODE15, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 15))   |
            DRF_NUM(_INGRESS, _RIDTABDATA5, _RMOD,      rmod)                                           |
            DRF_NUM(_INGRESS, _RIDTABDATA5, _ACLVALID,  routing_id[i].entryValid));

        NVSWITCH_ASSERT(routing_id[i].numEntries <= 16);
        // Write last and auto-increment
        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RIDTABDATA0,
            DRF_NUM(_INGRESS, _RIDTABDATA0, _GSIZE,
                (routing_id[i].numEntries == 16) ? 0x0 : routing_id[i].numEntries) |
            DRF_NUM(_INGRESS, _RIDTABDATA0, _PORT0,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 0)) |
            DRF_NUM(_INGRESS, _RIDTABDATA0, _VC_MODE0, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 0))   |
            DRF_NUM(_INGRESS, _RIDTABDATA0, _PORT1,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 1)) |
            DRF_NUM(_INGRESS, _RIDTABDATA0, _VC_MODE1, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 1))   |
            DRF_NUM(_INGRESS, _RIDTABDATA0, _PORT2,    NVSWITCH_PORTLIST_PORT_LR10(routing_id[i], 2)) |
            DRF_NUM(_INGRESS, _RIDTABDATA0, _VC_MODE2, NVSWITCH_PORTLIST_VC_LR10(routing_id[i], 2)));
    }
}

#define NVSWITCH_NUM_RIDTABDATA_REGS_LR10 6

NvlStatus
nvswitch_ctrl_get_routing_id_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_ROUTING_ID_PARAMS *params
)
{
    NVSWITCH_ROUTING_ID_IDX_ENTRY *rid_entries;
    NvU32 table_index;
    NvU32 rid_tab_data[NVSWITCH_NUM_RIDTABDATA_REGS_LR10]; // 6 RID tables
    NvU32 rid_count;
    NvU32 rmod;
    NvU32 gsize;
    NvU32 ram_size;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, params->portNum))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: NPORT port #%d not valid\n",
            __FUNCTION__, params->portNum);
        return -NVL_BAD_ARGS;
    }

    ram_size = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRIDROUTERAM);
    if (params->firstIndex >= ram_size)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: routingId first index %d out of range[%d..%d].\n",
            __FUNCTION__, params->firstIndex, 0, ram_size - 1);
        return -NVL_BAD_ARGS;
    }

    nvswitch_os_memset(params->entries, 0, sizeof(params->entries));

    table_index = params->firstIndex;
    rid_entries = params->entries;
    rid_count = 0;

    /* set table offset */
    NVSWITCH_LINK_WR32_LR10(device, params->portNum, NPORT, _INGRESS, _REQRSPMAPADDR,
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _RAM_ADDRESS, params->firstIndex) |
        DRF_DEF(_INGRESS, _REQRSPMAPADDR, _RAM_SEL, _SELECTSRIDROUTERAM) |
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _AUTO_INCR, 1));

    while (rid_count < NVSWITCH_ROUTING_ID_ENTRIES_MAX &&
           table_index < ram_size)
    {
        rid_tab_data[0] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RIDTABDATA0);
        rid_tab_data[1] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RIDTABDATA1);
        rid_tab_data[2] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RIDTABDATA2);
        rid_tab_data[3] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RIDTABDATA3);
        rid_tab_data[4] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RIDTABDATA4);
        rid_tab_data[5] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RIDTABDATA5);

        /* add to rid_entries list if nonzero */
        if (rid_tab_data[0] || rid_tab_data[1] || rid_tab_data[2] ||
            rid_tab_data[3] || rid_tab_data[4] || rid_tab_data[5])
        {
            rid_entries[rid_count].entry.portList[0].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA0, _PORT0, rid_tab_data[0]);
            rid_entries[rid_count].entry.portList[0].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA0, _VC_MODE0, rid_tab_data[0]);

            rid_entries[rid_count].entry.portList[1].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA0, _PORT1, rid_tab_data[0]);
            rid_entries[rid_count].entry.portList[1].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA0, _VC_MODE1, rid_tab_data[0]);

            rid_entries[rid_count].entry.portList[2].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA0, _PORT2, rid_tab_data[0]);
            rid_entries[rid_count].entry.portList[2].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA0, _VC_MODE2, rid_tab_data[0]);

            rid_entries[rid_count].entry.portList[3].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA1, _PORT3, rid_tab_data[1]);
            rid_entries[rid_count].entry.portList[3].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA1, _VC_MODE3, rid_tab_data[1]);

            rid_entries[rid_count].entry.portList[4].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA1, _PORT4, rid_tab_data[1]);
            rid_entries[rid_count].entry.portList[4].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA1, _VC_MODE4, rid_tab_data[1]);

            rid_entries[rid_count].entry.portList[5].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA1, _PORT5, rid_tab_data[1]);
            rid_entries[rid_count].entry.portList[5].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA1, _VC_MODE5, rid_tab_data[1]);

            rid_entries[rid_count].entry.portList[6].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA2, _PORT6, rid_tab_data[2]);
            rid_entries[rid_count].entry.portList[6].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA2, _VC_MODE6, rid_tab_data[2]);

            rid_entries[rid_count].entry.portList[7].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA2, _PORT7, rid_tab_data[2]);
            rid_entries[rid_count].entry.portList[7].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA2, _VC_MODE7, rid_tab_data[2]);

            rid_entries[rid_count].entry.portList[8].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA2, _PORT8, rid_tab_data[2]);
            rid_entries[rid_count].entry.portList[8].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA2, _VC_MODE8, rid_tab_data[2]);

            rid_entries[rid_count].entry.portList[9].destPortNum  = DRF_VAL(_INGRESS, _RIDTABDATA3, _PORT9, rid_tab_data[3]);
            rid_entries[rid_count].entry.portList[9].vcMap        = DRF_VAL(_INGRESS, _RIDTABDATA3, _VC_MODE9, rid_tab_data[3]);

            rid_entries[rid_count].entry.portList[10].destPortNum = DRF_VAL(_INGRESS, _RIDTABDATA3, _PORT10, rid_tab_data[3]);
            rid_entries[rid_count].entry.portList[10].vcMap       = DRF_VAL(_INGRESS, _RIDTABDATA3, _VC_MODE10, rid_tab_data[3]);

            rid_entries[rid_count].entry.portList[11].destPortNum = DRF_VAL(_INGRESS, _RIDTABDATA3, _PORT11, rid_tab_data[3]);
            rid_entries[rid_count].entry.portList[11].vcMap       = DRF_VAL(_INGRESS, _RIDTABDATA3, _VC_MODE11, rid_tab_data[3]);

            rid_entries[rid_count].entry.portList[12].destPortNum = DRF_VAL(_INGRESS, _RIDTABDATA4, _PORT12, rid_tab_data[4]);
            rid_entries[rid_count].entry.portList[12].vcMap       = DRF_VAL(_INGRESS, _RIDTABDATA4, _VC_MODE12, rid_tab_data[4]);

            rid_entries[rid_count].entry.portList[13].destPortNum = DRF_VAL(_INGRESS, _RIDTABDATA4, _PORT13, rid_tab_data[4]);
            rid_entries[rid_count].entry.portList[13].vcMap       = DRF_VAL(_INGRESS, _RIDTABDATA4, _VC_MODE13, rid_tab_data[4]);

            rid_entries[rid_count].entry.portList[14].destPortNum = DRF_VAL(_INGRESS, _RIDTABDATA4, _PORT14, rid_tab_data[4]);
            rid_entries[rid_count].entry.portList[14].vcMap       = DRF_VAL(_INGRESS, _RIDTABDATA4, _VC_MODE14, rid_tab_data[4]);

            rid_entries[rid_count].entry.portList[15].destPortNum = DRF_VAL(_INGRESS, _RIDTABDATA5, _PORT15, rid_tab_data[5]);
            rid_entries[rid_count].entry.portList[15].vcMap       = DRF_VAL(_INGRESS, _RIDTABDATA5, _VC_MODE15, rid_tab_data[5]);
            rid_entries[rid_count].entry.entryValid               = DRF_VAL(_INGRESS, _RIDTABDATA5, _ACLVALID, rid_tab_data[5]);

            rmod = DRF_VAL(_INGRESS, _RIDTABDATA5, _RMOD, rid_tab_data[5]);
            rid_entries[rid_count].entry.useRoutingLan = (NVBIT(6) & rmod) ? 1 : 0;
            rid_entries[rid_count].entry.enableIrlErrResponse = (NVBIT(9) & rmod) ? 1 : 0;

            // Gsize of 16 falls into the 0th entry of GLT region. The _GSIZE field must be mapped accordingly
            // to the number of port entries (See IAS, Table 20, Sect 3.4.2.2. Packet Routing).
            gsize = DRF_VAL(_INGRESS, _RIDTABDATA0, _GSIZE, rid_tab_data[0]);
            rid_entries[rid_count].entry.numEntries = ((gsize == 0) ? 16 : gsize);

            rid_entries[rid_count].idx = table_index;
            rid_count++;
        }

        table_index++;
    }

    params->nextIndex = table_index;
    params->numEntries = rid_count;

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_set_routing_id_valid_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_ROUTING_ID_VALID *p
)
{
    NvU32 rid_ctrl;
    NvU32 rid_tab_data0;
    NvU32 rid_tab_data1;
    NvU32 rid_tab_data2;
    NvU32 rid_tab_data3;
    NvU32 rid_tab_data4;
    NvU32 rid_tab_data5;
    NvU32 ram_address = p->firstIndex;
    NvU32 i;
    NvU32 ram_size;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, p->portNum))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: NPORT port #%d not valid\n",
            __FUNCTION__, p->portNum);
        return -NVL_BAD_ARGS;
    }

    ram_size = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRIDROUTERAM);
    if ((p->firstIndex >= ram_size) ||
        (p->numEntries > NVSWITCH_ROUTING_ID_ENTRIES_MAX) ||
        (p->firstIndex + p->numEntries > ram_size))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: routingId[%d..%d] overflows range %d..%d or size %d.\n",
            __FUNCTION__, p->firstIndex, p->firstIndex + p->numEntries - 1,
            0, ram_size - 1,
            NVSWITCH_ROUTING_ID_ENTRIES_MAX);
        return -NVL_BAD_ARGS;
    }

    // Select RID RAM and disable Auto Increment.
    rid_ctrl =
        DRF_DEF(_INGRESS, _REQRSPMAPADDR, _RAM_SEL, _SELECTSRIDROUTERAM) |
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _AUTO_INCR, 0);


    for (i = 0; i < p->numEntries; i++)
    {
        /* set the ram address */
        rid_ctrl = FLD_SET_DRF_NUM(_INGRESS, _REQRSPMAPADDR, _RAM_ADDRESS, ram_address++, rid_ctrl);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _REQRSPMAPADDR, rid_ctrl);

        rid_tab_data0 = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA0);
        rid_tab_data1 = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA1);
        rid_tab_data2 = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA2);
        rid_tab_data3 = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA3);
        rid_tab_data4 = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA4);
        rid_tab_data5 = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA5);

        // Set the valid bit in _RIDTABDATA5
        rid_tab_data5 = FLD_SET_DRF_NUM(_INGRESS, _RIDTABDATA5, _ACLVALID,
            p->entryValid[i], rid_tab_data5);

        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA1, rid_tab_data1);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA2, rid_tab_data2);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA3, rid_tab_data3);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA4, rid_tab_data4);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA5, rid_tab_data5);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RIDTABDATA0, rid_tab_data0);
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_set_routing_id_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_ROUTING_ID *p
)
{
    NvU32 i, j;
    NvlStatus retval = NVL_SUCCESS;
    NvU32 ram_size;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, p->portNum))
    {
        NVSWITCH_PRINT(device, ERROR,
            "NPORT port #%d not valid\n",
            p->portNum);
        return -NVL_BAD_ARGS;
    }

    ram_size = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRIDROUTERAM);
    if ((p->firstIndex >= ram_size) ||
        (p->numEntries > NVSWITCH_ROUTING_ID_ENTRIES_MAX) ||
        (p->firstIndex + p->numEntries > ram_size))
    {
        NVSWITCH_PRINT(device, ERROR,
            "routingId[%d..%d] overflows range %d..%d or size %d.\n",
            p->firstIndex, p->firstIndex + p->numEntries - 1,
            0, ram_size - 1,
            NVSWITCH_ROUTING_ID_ENTRIES_MAX);
        return -NVL_BAD_ARGS;
    }

    for (i = 0; i < p->numEntries; i++)
    {
        if ((p->routingId[i].numEntries < 1) ||
            (p->routingId[i].numEntries > NVSWITCH_ROUTING_ID_DEST_PORT_LIST_MAX))
        {
            NVSWITCH_PRINT(device, ERROR,
                "routingId[%d].portList[] size %d overflows range %d..%d\n",
                i, p->routingId[i].numEntries,
                1, NVSWITCH_ROUTING_ID_DEST_PORT_LIST_MAX);
            return -NVL_BAD_ARGS;
        }

        for (j = 0; j < p->routingId[i].numEntries; j++)
        {
            if (p->routingId[i].portList[j].vcMap > DRF_MASK(NV_INGRESS_RIDTABDATA0_VC_MODE0))
            {
                NVSWITCH_PRINT(device, ERROR,
                    "routingId[%d].portList[%d] vcMap 0x%x out of valid range (0x%x..0x%x)\n",
                    i, j,
                    p->routingId[i].portList[j].vcMap,
                    0, DRF_MASK(NV_INGRESS_RIDTABDATA0_VC_MODE0));
                return -NVL_BAD_ARGS;
            }

            if (p->routingId[i].portList[j].destPortNum > DRF_MASK(NV_INGRESS_RIDTABDATA0_PORT0))
            {
                NVSWITCH_PRINT(device, ERROR,
                    "routingId[%d].portList[%d] destPortNum 0x%x out of valid range (0x%x..0x%x)\n",
                    i, j,
                    p->routingId[i].portList[j].destPortNum,
                    0, DRF_MASK(NV_INGRESS_RIDTABDATA0_PORT0));
                return -NVL_BAD_ARGS;
            }
        }
    }

    _nvswitch_set_routing_id_lr10(device, p->portNum, p->firstIndex, p->numEntries, p->routingId);

    return retval;
}

/*
 * CTRL_NVSWITCH_SET_ROUTING_LAN
 */

//
// Check the data field is present in the list.  Return either the data field
// or default if not present.
//
#define NVSWITCH_PORTLIST_VALID_LR10(_entry, _idx, _field, _default) \
    ((_idx < _entry.numEntries) ? _entry.portList[_idx]._field  : _default)

static void
_nvswitch_set_routing_lan_lr10
(
    nvswitch_device *device,
    NvU32 portNum,
    NvU32 firstIndex,
    NvU32 numEntries,
    NVSWITCH_ROUTING_LAN_ENTRY *routing_lan
)
{
    NvU32 i;

    NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _REQRSPMAPADDR,
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _RAM_ADDRESS, firstIndex) |
        DRF_DEF(_INGRESS, _REQRSPMAPADDR, _RAM_SEL, _SELECTSRLANROUTERAM) |
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _AUTO_INCR, 1));

    for (i = 0; i < numEntries; i++)
    {
        //
        // NOTE: The GRP_SIZE field is 4-bits.  A subgroup is size 1 through 16
        // with encoding 0x0=16 and 0x1=1, ..., 0xF=15.
        // Programming of GRP_SIZE takes advantage of the inherent masking of
        // DRF_NUM to truncate 16 to 0.
        // See bug #3300673
        //

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RLANTABDATA1,
            DRF_NUM(_INGRESS, _RLANTABDATA1, _GRP_SEL_3, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 3, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA1, _GRP_SIZE_3, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 3, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA1, _GRP_SEL_4, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 4, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA1, _GRP_SIZE_4, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 4, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA1, _GRP_SEL_5, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 5, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA1, _GRP_SIZE_5, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 5, groupSize, 1)));

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RLANTABDATA2,
            DRF_NUM(_INGRESS, _RLANTABDATA2, _GRP_SEL_6, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 6, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA2, _GRP_SIZE_6, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 6, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA2, _GRP_SEL_7, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 7, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA2, _GRP_SIZE_7, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 7, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA2, _GRP_SEL_8, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 8, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA2, _GRP_SIZE_8, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 8, groupSize, 1)));

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RLANTABDATA3,
            DRF_NUM(_INGRESS, _RLANTABDATA3, _GRP_SEL_9, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 9, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA3, _GRP_SIZE_9, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 9, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA3, _GRP_SEL_10, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 10, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA3, _GRP_SIZE_10, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 10, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA3, _GRP_SEL_11, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 11, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA3, _GRP_SIZE_11, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 11, groupSize, 1)));

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RLANTABDATA4,
            DRF_NUM(_INGRESS, _RLANTABDATA4, _GRP_SEL_12, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 12, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA4, _GRP_SIZE_12, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 12, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA4, _GRP_SEL_13, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 13, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA4, _GRP_SIZE_13, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 13, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA4, _GRP_SEL_14, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 14, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA4, _GRP_SIZE_14, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 14, groupSize, 1)));

        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RLANTABDATA5,
            DRF_NUM(_INGRESS, _RLANTABDATA5, _GRP_SEL_15, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 15, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA5, _GRP_SIZE_15, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 15, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA5, _ACLVALID,  routing_lan[i].entryValid));

        // Write last and auto-increment
        NVSWITCH_LINK_WR32_LR10(device, portNum, NPORT, _INGRESS, _RLANTABDATA0,
            DRF_NUM(_INGRESS, _RLANTABDATA0, _GRP_SEL_0, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 0, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA0, _GRP_SIZE_0, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 0, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA0, _GRP_SEL_1, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 1, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA0, _GRP_SIZE_1, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 1, groupSize, 1)) |
            DRF_NUM(_INGRESS, _RLANTABDATA0, _GRP_SEL_2, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 2, groupSelect, 0)) |
            DRF_NUM(_INGRESS, _RLANTABDATA0, _GRP_SIZE_2, NVSWITCH_PORTLIST_VALID_LR10(routing_lan[i], 2, groupSize, 1)));
    }
}

NvlStatus
nvswitch_ctrl_set_routing_lan_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_ROUTING_LAN *p
)
{
    NvU32 i, j;
    NvlStatus retval = NVL_SUCCESS;
    NvU32 ram_size;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, p->portNum))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: NPORT port #%d not valid\n",
            __FUNCTION__, p->portNum);
        return -NVL_BAD_ARGS;
    }

    ram_size = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRLANROUTERAM);
    if ((p->firstIndex >= ram_size) ||
        (p->numEntries > NVSWITCH_ROUTING_LAN_ENTRIES_MAX) ||
        (p->firstIndex + p->numEntries > ram_size))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: routingLan[%d..%d] overflows range %d..%d or size %d.\n",
            __FUNCTION__, p->firstIndex, p->firstIndex + p->numEntries - 1,
            0, ram_size - 1,
            NVSWITCH_ROUTING_LAN_ENTRIES_MAX);
        return -NVL_BAD_ARGS;
    }

    for (i = 0; i < p->numEntries; i++)
    {
        if (p->routingLan[i].numEntries > NVSWITCH_ROUTING_LAN_GROUP_SEL_MAX)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: routingLan[%d].portList[] size %d overflows range %d..%d\n",
                __FUNCTION__, i, p->routingLan[i].numEntries,
                0, NVSWITCH_ROUTING_LAN_GROUP_SEL_MAX);
            return -NVL_BAD_ARGS;
        }

        for (j = 0; j < p->routingLan[i].numEntries; j++)
        {
            if (p->routingLan[i].portList[j].groupSelect > DRF_MASK(NV_INGRESS_RLANTABDATA0_GRP_SEL_0))
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s: routingLan[%d].portList[%d] groupSelect 0x%x out of valid range (0x%x..0x%x)\n",
                    __FUNCTION__, i, j,
                    p->routingLan[i].portList[j].groupSelect,
                    0, DRF_MASK(NV_INGRESS_RLANTABDATA0_GRP_SEL_0));
                return -NVL_BAD_ARGS;
            }

            if ((p->routingLan[i].portList[j].groupSize == 0) ||
                (p->routingLan[i].portList[j].groupSize > DRF_MASK(NV_INGRESS_RLANTABDATA0_GRP_SIZE_0) + 1))
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s: routingLan[%d].portList[%d] groupSize 0x%x out of valid range (0x%x..0x%x)\n",
                    __FUNCTION__, i, j,
                    p->routingLan[i].portList[j].groupSize,
                    1, DRF_MASK(NV_INGRESS_RLANTABDATA0_GRP_SIZE_0) + 1);
                return -NVL_BAD_ARGS;
            }
        }
    }

    _nvswitch_set_routing_lan_lr10(device, p->portNum, p->firstIndex, p->numEntries, p->routingLan);

    return retval;
}

#define NVSWITCH_NUM_RLANTABDATA_REGS_LR10 6

NvlStatus
nvswitch_ctrl_get_routing_lan_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_ROUTING_LAN_PARAMS *params
)
{
    NVSWITCH_ROUTING_LAN_IDX_ENTRY *rlan_entries;
    NvU32 table_index;
    NvU32 rlan_tab_data[NVSWITCH_NUM_RLANTABDATA_REGS_LR10]; // 6 RLAN tables
    NvU32 rlan_count;
    NvU32 ram_size;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, params->portNum))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: NPORT port #%d not valid\n",
            __FUNCTION__, params->portNum);
        return -NVL_BAD_ARGS;
    }

    ram_size = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRLANROUTERAM);
    if ((params->firstIndex >= ram_size))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: routingLan first index %d out of range[%d..%d].\n",
            __FUNCTION__, params->firstIndex, 0, ram_size - 1);
        return -NVL_BAD_ARGS;
    }

    nvswitch_os_memset(params->entries, 0, (NVSWITCH_ROUTING_LAN_ENTRIES_MAX *
        sizeof(NVSWITCH_ROUTING_LAN_IDX_ENTRY)));

    table_index = params->firstIndex;
    rlan_entries = params->entries;
    rlan_count = 0;

    /* set table offset */
    NVSWITCH_LINK_WR32_LR10(device, params->portNum, NPORT, _INGRESS, _REQRSPMAPADDR,
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _RAM_ADDRESS, params->firstIndex) |
        DRF_DEF(_INGRESS, _REQRSPMAPADDR, _RAM_SEL, _SELECTSRLANROUTERAM)   |
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _AUTO_INCR, 1));

    while (rlan_count < NVSWITCH_ROUTING_LAN_ENTRIES_MAX &&
           table_index < ram_size)
    {
        /* read one entry */
        rlan_tab_data[0] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RLANTABDATA0);
        rlan_tab_data[1] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RLANTABDATA1);
        rlan_tab_data[2] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RLANTABDATA2);
        rlan_tab_data[3] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RLANTABDATA3);
        rlan_tab_data[4] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RLANTABDATA4);
        rlan_tab_data[5] = NVSWITCH_LINK_RD32_LR10(device, params->portNum, NPORT, _INGRESS, _RLANTABDATA5);

        /* add to rlan_entries list if nonzero */
        if (rlan_tab_data[0] || rlan_tab_data[1] || rlan_tab_data[2] ||
            rlan_tab_data[3] || rlan_tab_data[4] || rlan_tab_data[5])
        {
            rlan_entries[rlan_count].entry.portList[0].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA0, _GRP_SEL_0, rlan_tab_data[0]);
            rlan_entries[rlan_count].entry.portList[0].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA0, _GRP_SIZE_0, rlan_tab_data[0]);
            if (rlan_entries[rlan_count].entry.portList[0].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[0].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[1].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA0, _GRP_SEL_1, rlan_tab_data[0]);
            rlan_entries[rlan_count].entry.portList[1].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA0, _GRP_SIZE_1, rlan_tab_data[0]);
            if (rlan_entries[rlan_count].entry.portList[1].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[1].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[2].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA0, _GRP_SEL_2, rlan_tab_data[0]);
            rlan_entries[rlan_count].entry.portList[2].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA0, _GRP_SIZE_2, rlan_tab_data[0]);
            if (rlan_entries[rlan_count].entry.portList[2].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[2].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[3].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA1, _GRP_SEL_3, rlan_tab_data[1]);
            rlan_entries[rlan_count].entry.portList[3].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA1, _GRP_SIZE_3, rlan_tab_data[1]);
            if (rlan_entries[rlan_count].entry.portList[3].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[3].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[4].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA1, _GRP_SEL_4, rlan_tab_data[1]);
            rlan_entries[rlan_count].entry.portList[4].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA1, _GRP_SIZE_4, rlan_tab_data[1]);
            if (rlan_entries[rlan_count].entry.portList[4].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[4].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[5].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA1, _GRP_SEL_5, rlan_tab_data[1]);
            rlan_entries[rlan_count].entry.portList[5].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA1, _GRP_SIZE_5, rlan_tab_data[1]);
            if (rlan_entries[rlan_count].entry.portList[5].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[5].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[6].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA2, _GRP_SEL_6, rlan_tab_data[2]);
            rlan_entries[rlan_count].entry.portList[6].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA2, _GRP_SIZE_6, rlan_tab_data[2]);
            if (rlan_entries[rlan_count].entry.portList[6].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[6].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[7].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA2, _GRP_SEL_7, rlan_tab_data[2]);
            rlan_entries[rlan_count].entry.portList[7].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA2, _GRP_SIZE_7, rlan_tab_data[2]);
            if (rlan_entries[rlan_count].entry.portList[7].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[7].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[8].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA2, _GRP_SEL_8, rlan_tab_data[2]);
            rlan_entries[rlan_count].entry.portList[8].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA2, _GRP_SIZE_8, rlan_tab_data[2]);
            if (rlan_entries[rlan_count].entry.portList[8].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[8].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[9].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA3, _GRP_SEL_9, rlan_tab_data[3]);
            rlan_entries[rlan_count].entry.portList[9].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA3, _GRP_SIZE_9, rlan_tab_data[3]);
            if (rlan_entries[rlan_count].entry.portList[9].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[9].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[10].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA3, _GRP_SEL_10, rlan_tab_data[3]);
            rlan_entries[rlan_count].entry.portList[10].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA3, _GRP_SIZE_10, rlan_tab_data[3]);
            if (rlan_entries[rlan_count].entry.portList[10].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[10].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[11].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA3, _GRP_SEL_11, rlan_tab_data[3]);
            rlan_entries[rlan_count].entry.portList[11].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA3, _GRP_SIZE_11, rlan_tab_data[3]);
            if (rlan_entries[rlan_count].entry.portList[11].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[11].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[12].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA4, _GRP_SEL_12, rlan_tab_data[4]);
            rlan_entries[rlan_count].entry.portList[12].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA4, _GRP_SIZE_12, rlan_tab_data[4]);
            if (rlan_entries[rlan_count].entry.portList[12].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[12].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[13].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA4, _GRP_SEL_13, rlan_tab_data[4]);
            rlan_entries[rlan_count].entry.portList[13].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA4, _GRP_SIZE_13, rlan_tab_data[4]);
            if (rlan_entries[rlan_count].entry.portList[13].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[13].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[14].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA4, _GRP_SEL_14, rlan_tab_data[4]);
            rlan_entries[rlan_count].entry.portList[14].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA4, _GRP_SIZE_14, rlan_tab_data[4]);
            if (rlan_entries[rlan_count].entry.portList[14].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[14].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.portList[15].groupSelect = DRF_VAL(_INGRESS, _RLANTABDATA5, _GRP_SEL_15, rlan_tab_data[5]);
            rlan_entries[rlan_count].entry.portList[15].groupSize   = DRF_VAL(_INGRESS, _RLANTABDATA5, _GRP_SIZE_15, rlan_tab_data[5]);
            if (rlan_entries[rlan_count].entry.portList[15].groupSize == 0)
            {
                rlan_entries[rlan_count].entry.portList[15].groupSize = 16;
            }

            rlan_entries[rlan_count].entry.entryValid               = DRF_VAL(_INGRESS, _RLANTABDATA5, _ACLVALID, rlan_tab_data[5]);
            rlan_entries[rlan_count].entry.numEntries = NVSWITCH_ROUTING_ID_DEST_PORT_LIST_MAX;
            rlan_entries[rlan_count].idx  = table_index;

            rlan_count++;
        }

        table_index++;
    }

    params->nextIndex  = table_index;
    params->numEntries = rlan_count;

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_set_routing_lan_valid_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_ROUTING_LAN_VALID *p
)
{
    NvU32 rlan_ctrl;
    NvU32 rlan_tab_data[NVSWITCH_NUM_RLANTABDATA_REGS_LR10]; // 6 RLAN tables
    NvU32 ram_address = p->firstIndex;
    NvU32 i;
    NvU32 ram_size;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, p->portNum))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: NPORT port #%d not valid\n",
            __FUNCTION__, p->portNum);
        return -NVL_BAD_ARGS;
    }

    ram_size = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRLANROUTERAM);
    if ((p->firstIndex >= ram_size) ||
        (p->numEntries > NVSWITCH_ROUTING_LAN_ENTRIES_MAX) ||
        (p->firstIndex + p->numEntries > ram_size))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: routingLan[%d..%d] overflows range %d..%d or size %d.\n",
            __FUNCTION__, p->firstIndex, p->firstIndex + p->numEntries - 1,
            0, ram_size - 1,
            NVSWITCH_ROUTING_LAN_ENTRIES_MAX);
        return -NVL_BAD_ARGS;
    }

    // Select RLAN RAM and disable Auto Increament.
    rlan_ctrl =
        DRF_DEF(_INGRESS, _REQRSPMAPADDR, _RAM_SEL, _SELECTSRLANROUTERAM) |
        DRF_NUM(_INGRESS, _REQRSPMAPADDR, _AUTO_INCR, 0);

    for (i = 0; i < p->numEntries; i++)
    {
        /* set the RAM address */
        rlan_ctrl = FLD_SET_DRF_NUM(_INGRESS, _REQRSPMAPADDR, _RAM_ADDRESS, ram_address++, rlan_ctrl);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _REQRSPMAPADDR, rlan_ctrl);

        rlan_tab_data[0] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA0);
        rlan_tab_data[1] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA1);
        rlan_tab_data[2] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA2);
        rlan_tab_data[3] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA3);
        rlan_tab_data[4] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA4);
        rlan_tab_data[5] = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA5);

        // Set the valid bit in _RLANTABDATA5
        rlan_tab_data[5] = FLD_SET_DRF_NUM(_INGRESS, _RLANTABDATA5, _ACLVALID,
            p->entryValid[i], rlan_tab_data[5]);

        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA1, rlan_tab_data[1]);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA2, rlan_tab_data[2]);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA3, rlan_tab_data[3]);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA4, rlan_tab_data[4]);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA5, rlan_tab_data[5]);
        NVSWITCH_LINK_WR32_LR10(device, p->portNum, NPORT, _INGRESS, _RLANTABDATA0, rlan_tab_data[0]);
    }

    return NVL_SUCCESS;
}

/*
 * @Brief : Send priv ring command and wait for completion
 *
 * @Description :
 *
 * @param[in] device        a reference to the device to initialize
 * @param[in] cmd           encoded priv ring command
 */
NvlStatus
nvswitch_ring_master_cmd_lr10
(
    nvswitch_device *device,
    NvU32 cmd
)
{
    NvU32 value;
    NVSWITCH_TIMEOUT timeout;
    NvBool           keepPolling;

    NVSWITCH_REG_WR32(device, _PPRIV_MASTER, _RING_COMMAND, cmd);

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

        value = NVSWITCH_REG_RD32(device, _PPRIV_MASTER, _RING_COMMAND);
        if (FLD_TEST_DRF(_PPRIV_MASTER, _RING_COMMAND, _CMD, _NO_CMD, value))
        {
            break;
        }

        nvswitch_os_sleep(1);
    }
    while (keepPolling);

    if (!FLD_TEST_DRF(_PPRIV_MASTER, _RING_COMMAND, _CMD, _NO_CMD, value))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Timeout waiting for RING_COMMAND == NO_CMD (cmd=0x%x).\n",
            __FUNCTION__, cmd);
        return -NVL_INITIALIZATION_TOTAL_FAILURE;
    }

    return NVL_SUCCESS;
}

/*
 * @brief Process the information read from ROM tables and apply it to device
 * settings.
 *
 * @param[in] device    a reference to the device to query
 * @param[in] firmware  Information parsed from ROM tables
 */
static void
_nvswitch_process_firmware_info_lr10
(
    nvswitch_device *device,
    NVSWITCH_FIRMWARE *firmware
)
{
    NvU32 idx_link;
    NvU64 link_enable_mask;

    if (device->firmware.firmware_size == 0)
    {
        return;
    }

    if (device->firmware.nvlink.link_config_found)
    {
        link_enable_mask = ((NvU64)device->regkeys.link_enable_mask2 << 32 |
                            (NvU64)device->regkeys.link_enable_mask);
        //
        // If the link enables were not already overridden by regkey, then
        // apply the ROM link enables
        //
        if (link_enable_mask == NV_U64_MAX)
        {
            for (idx_link = 0; idx_link < nvswitch_get_num_links(device); idx_link++)
            {
                if ((device->firmware.nvlink.link_enable_mask & NVBIT64(idx_link)) == 0)
                {
                    device->link[idx_link].valid = NV_FALSE;
                }
            }
        }
    }
}

static void
_nvswitch_init_npg_multicast_lr10
(
    nvswitch_device *device
)
{
    NvU32 idx_npg;
    NvU32 idx_nport;
    NvU32 nport_mask;

    //
    // Walk the NPGs and build the mask of extant NPORTs
    //
    for (idx_npg = 0; idx_npg < NVSWITCH_ENG_COUNT(device, NPG, ); idx_npg++)
    {
        if (NVSWITCH_ENG_IS_VALID(device, NPG, idx_npg))
        {
            nport_mask = 0;
            for (idx_nport = 0; idx_nport < NVSWITCH_NPORT_PER_NPG; idx_nport++)
            {
                nport_mask |=
                    (NVSWITCH_ENG_IS_VALID(device, NPORT, idx_npg*NVSWITCH_NPORT_PER_NPG + idx_nport) ?
                    NVBIT(idx_nport) : 0x0);
            }

            NVSWITCH_NPG_WR32_LR10(device, idx_npg,
                _NPG, _CTRL_PRI_MULTICAST,
                DRF_NUM(_NPG, _CTRL_PRI_MULTICAST, _NPORT_ENABLE, nport_mask) |
                DRF_DEF(_NPG, _CTRL_PRI_MULTICAST, _READ_MODE, _AND_ALL_BUSSES));

            NVSWITCH_NPGPERF_WR32_LR10(device, idx_npg,
                _NPGPERF, _CTRL_PRI_MULTICAST,
                DRF_NUM(_NPGPERF, _CTRL_PRI_MULTICAST, _NPORT_ENABLE, nport_mask) |
                DRF_DEF(_NPGPERF, _CTRL_PRI_MULTICAST, _READ_MODE, _AND_ALL_BUSSES));
        }
    }
}

static NvlStatus
nvswitch_clear_nport_rams_lr10
(
    nvswitch_device *device
)
{
    NvU32 idx_nport;
    NvU64 nport_mask = 0;
    NvU32 zero_init_mask;
    NvU32 val;
    NVSWITCH_TIMEOUT timeout;
    NvBool           keepPolling;
    NvlStatus retval = NVL_SUCCESS;

    // Build the mask of available NPORTs
    for (idx_nport = 0; idx_nport < NVSWITCH_ENG_COUNT(device, NPORT, ); idx_nport++)
    {
        if (NVSWITCH_ENG_IS_VALID(device, NPORT, idx_nport))
        {
            nport_mask |= NVBIT64(idx_nport);
        }
    }

    // Start the HW zero init
    zero_init_mask =
        DRF_DEF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_0, _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_1, _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_2, _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_3, _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_4, _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_5, _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_6, _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _LINKTABLEINIT, _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _REMAPTABINIT,  _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _RIDTABINIT,    _HWINIT) |
        DRF_DEF(_NPORT, _INITIALIZATION, _RLANTABINIT,   _HWINIT);

    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _NPORT, _INITIALIZATION,
        zero_init_mask);

    nvswitch_timeout_create(25*NVSWITCH_INTERVAL_1MSEC_IN_NS, &timeout);

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

        // Check each enabled NPORT that is still pending until all are done
        for (idx_nport = 0; idx_nport < NVSWITCH_ENG_COUNT(device, NPORT, ); idx_nport++)
        {
            if (NVSWITCH_ENG_IS_VALID(device, NPORT, idx_nport) && (nport_mask & NVBIT64(idx_nport)))
            {
                val = NVSWITCH_ENG_RD32_LR10(device, NPORT, idx_nport, _NPORT, _INITIALIZATION);
                if (val == zero_init_mask)
                {
                    nport_mask &= ~NVBIT64(idx_nport);
                }
            }
        }

        if (nport_mask == 0)
        {
            break;
        }

        nvswitch_os_sleep(1);
    }
    while (keepPolling);

    if (nport_mask != 0)
    {
        NVSWITCH_PRINT(device, WARN,
            "%s: Timeout waiting for NV_NPORT_INITIALIZATION (0x%llx)\n",
            __FUNCTION__, nport_mask);
        // Bug 2974064: Review this timeout handling (fall through)
        retval = -NVL_ERR_INVALID_STATE;
    }

    //bug 2737147 requires SW To init this crumbstore setting for LR10
    val = DRF_NUM(_TSTATE, _RAM_ADDRESS, _ADDR, 0)             |
          DRF_DEF(_TSTATE, _RAM_ADDRESS, _SELECT, _CRUMBSTORE_RAM) |
          DRF_NUM(_TSTATE, _RAM_ADDRESS, _AUTO_INCR, 0)        |
          DRF_DEF(_TSTATE, _RAM_ADDRESS, _VC, _VC5_TRANSDONE);

    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _TSTATE, _RAM_ADDRESS, val);

    return retval;
}

static void
_nvswitch_init_nport_ecc_control_lr10
(
    nvswitch_device *device
)
{
    // Set ingress ECC error limits
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _INGRESS, _ERR_NCISOC_HDR_ECC_ERROR_COUNTER,
        DRF_NUM(_INGRESS, _ERR_NCISOC_HDR_ECC_ERROR_COUNTER, _ERROR_COUNT, 0x0));
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _INGRESS, _ERR_NCISOC_HDR_ECC_ERROR_COUNTER_LIMIT, 1);

    // Set egress ECC error limits
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _EGRESS, _ERR_NXBAR_ECC_ERROR_COUNTER,
        DRF_NUM(_EGRESS, _ERR_NXBAR_ECC_ERROR_COUNTER, _ERROR_COUNT, 0x0));
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _EGRESS, _ERR_NXBAR_ECC_ERROR_COUNTER_LIMIT, 1);

    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _EGRESS, _ERR_RAM_OUT_ECC_ERROR_COUNTER,
        DRF_NUM(_EGRESS, _ERR_RAM_OUT_ECC_ERROR_COUNTER, _ERROR_COUNT, 0x0));
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _EGRESS, _ERR_RAM_OUT_ECC_ERROR_COUNTER_LIMIT, 1);

    // Set route ECC error limits
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _ROUTE, _ERR_NVS_ECC_ERROR_COUNTER,
        DRF_NUM(_ROUTE, _ERR_NVS_ECC_ERROR_COUNTER, _ERROR_COUNT, 0x0));
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _ROUTE, _ERR_NVS_ECC_ERROR_COUNTER_LIMIT, 1);

    // Set tstate ECC error limits
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _TSTATE, _ERR_CRUMBSTORE_ECC_ERROR_COUNTER,
        DRF_NUM(_TSTATE, _ERR_CRUMBSTORE_ECC_ERROR_COUNTER, _ERROR_COUNT, 0x0));
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _TSTATE, _ERR_CRUMBSTORE_ECC_ERROR_COUNTER_LIMIT, 1);

    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _TSTATE, _ERR_TAGPOOL_ECC_ERROR_COUNTER,
        DRF_NUM(_TSTATE, _ERR_TAGPOOL_ECC_ERROR_COUNTER, _ERROR_COUNT, 0x0));
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _TSTATE, _ERR_TAGPOOL_ECC_ERROR_COUNTER_LIMIT, 1);

    // Set sourcetrack ECC error limits to _PROD value
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _SOURCETRACK, _ERR_CREQ_TCEN0_CRUMBSTORE_ECC_ERROR_COUNTER_LIMIT,
        DRF_NUM(_SOURCETRACK, _ERR_CREQ_TCEN0_CRUMBSTORE_ECC_ERROR_COUNTER, _ERROR_COUNT, 0x0));
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _SOURCETRACK, _ERR_CREQ_TCEN0_CRUMBSTORE_ECC_ERROR_COUNTER_LIMIT, 1);

    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _SOURCETRACK, _ERR_CREQ_TCEN1_CRUMBSTORE_ECC_ERROR_COUNTER_LIMIT,
        DRF_NUM(_SOURCETRACK, _ERR_CREQ_TCEN1_CRUMBSTORE_ECC_ERROR_COUNTER, _ERROR_COUNT, 0x0));
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _SOURCETRACK, _ERR_CREQ_TCEN1_CRUMBSTORE_ECC_ERROR_COUNTER_LIMIT, 1);

    // Enable ECC/parity
    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _INGRESS, _ERR_ECC_CTRL,
        DRF_DEF(_INGRESS, _ERR_ECC_CTRL, _NCISOC_HDR_ECC_ENABLE, __PROD) |
        DRF_DEF(_INGRESS, _ERR_ECC_CTRL, _NCISOC_PARITY_ENABLE, __PROD) |
        DRF_DEF(_INGRESS, _ERR_ECC_CTRL, _REMAPTAB_ECC_ENABLE, __PROD) |
        DRF_DEF(_INGRESS, _ERR_ECC_CTRL, _RIDTAB_ECC_ENABLE, __PROD) |
        DRF_DEF(_INGRESS, _ERR_ECC_CTRL, _RLANTAB_ECC_ENABLE, __PROD));

    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _EGRESS, _ERR_ECC_CTRL,
        DRF_DEF(_EGRESS, _ERR_ECC_CTRL, _NXBAR_ECC_ENABLE, __PROD) |
        DRF_DEF(_EGRESS, _ERR_ECC_CTRL, _NXBAR_PARITY_ENABLE, __PROD) |
        DRF_DEF(_EGRESS, _ERR_ECC_CTRL, _RAM_OUT_ECC_ENABLE, __PROD) |
        DRF_DEF(_EGRESS, _ERR_ECC_CTRL, _NCISOC_ECC_ENABLE, __PROD) |
        DRF_DEF(_EGRESS, _ERR_ECC_CTRL, _NCISOC_PARITY_ENABLE, __PROD));

    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _ROUTE, _ERR_ECC_CTRL,
        DRF_DEF(_ROUTE, _ERR_ECC_CTRL, _GLT_ECC_ENABLE, __PROD) |
        DRF_DEF(_ROUTE, _ERR_ECC_CTRL, _NVS_ECC_ENABLE, __PROD));

    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _TSTATE, _ERR_ECC_CTRL,
        DRF_DEF(_TSTATE, _ERR_ECC_CTRL, _CRUMBSTORE_ECC_ENABLE, __PROD) |
        DRF_DEF(_TSTATE, _ERR_ECC_CTRL, _TAGPOOL_ECC_ENABLE, __PROD) |
        DRF_DEF(_TSTATE, _ERR_ECC_CTRL, _TD_TID_ECC_ENABLE, _DISABLE));

    NVSWITCH_BCAST_WR32_LR10(device, NPORT, _SOURCETRACK, _ERR_ECC_CTRL,
        DRF_DEF(_SOURCETRACK, _ERR_ECC_CTRL, _CREQ_TCEN0_CRUMBSTORE_ECC_ENABLE, __PROD) |
        DRF_DEF(_SOURCETRACK, _ERR_ECC_CTRL, _CREQ_TCEN0_TD_CRUMBSTORE_ECC_ENABLE, _DISABLE) |
        DRF_DEF(_SOURCETRACK, _ERR_ECC_CTRL, _CREQ_TCEN1_CRUMBSTORE_ECC_ENABLE, __PROD));
}

static void
_nvswitch_init_cmd_routing
(
    nvswitch_device *device
)
{
    NvU32 val;

    //Set Hash policy for the requests.
    val = DRF_DEF(_ROUTE, _CMD_ROUTE_TABLE0, _RFUN1, _SPRAY) |
          DRF_DEF(_ROUTE, _CMD_ROUTE_TABLE0, _RFUN2, _SPRAY) |
          DRF_DEF(_ROUTE, _CMD_ROUTE_TABLE0, _RFUN4, _SPRAY) |
          DRF_DEF(_ROUTE, _CMD_ROUTE_TABLE0, _RFUN7, _SPRAY);
    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _ROUTE, _CMD_ROUTE_TABLE0, val);

    // Set Random policy for reponses.
    val = DRF_DEF(_ROUTE, _CMD_ROUTE_TABLE2, _RFUN16, _RANDOM) |
          DRF_DEF(_ROUTE, _CMD_ROUTE_TABLE2, _RFUN17, _RANDOM);
    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _ROUTE, _CMD_ROUTE_TABLE2, val);
}

static NvlStatus
_nvswitch_init_portstat_counters
(
    nvswitch_device *device
)
{
    NvlStatus retval;
    NvU32 idx_channel;
    NVSWITCH_SET_LATENCY_BINS default_latency_bins;
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);

    chip_device->latency_stats = nvswitch_os_malloc(sizeof(NVSWITCH_LATENCY_STATS_LR10));
    if (chip_device->latency_stats == NULL)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: Failed allocate memory for latency stats\n",
            __FUNCTION__);
        return -NVL_NO_MEM;
    }

    nvswitch_os_memset(chip_device->latency_stats, 0, sizeof(NVSWITCH_LATENCY_STATS_LR10));

    //
    // These bin thresholds are values provided by Arch based off
    // switch latency expectations.
    //
    for (idx_channel=0; idx_channel < NVSWITCH_NUM_VCS_LR10; idx_channel++)
    {
        default_latency_bins.bin[idx_channel].lowThreshold = 120;    // 120ns
        default_latency_bins.bin[idx_channel].medThreshold = 200;    // 200ns
        default_latency_bins.bin[idx_channel].hiThreshold  = 1000;   // 1us
    }

    chip_device->latency_stats->sample_interval_msec = 3000; // 3 second sample interval

    retval = nvswitch_ctrl_set_latency_bins(device, &default_latency_bins);
    if (retval != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: Failed to set latency bins\n",
            __FUNCTION__);
        NVSWITCH_ASSERT(0);
        return retval;
    }

    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _NPORT, _PORTSTAT_CONTROL,
        DRF_DEF(_NPORT, _PORTSTAT_CONTROL, _SWEEPMODE, _SWONDEMAND) |
        DRF_DEF(_NPORT, _PORTSTAT_CONTROL, _RANGESELECT, _BITS13TO0));

     NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _NPORT, _PORTSTAT_SOURCE_FILTER_0,
         DRF_NUM(_NPORT, _PORTSTAT_SOURCE_FILTER_0, _SRCFILTERBIT, 0xFFFFFFFF));

    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _NPORT, _PORTSTAT_SOURCE_FILTER_1,
        DRF_NUM(_NPORT, _PORTSTAT_SOURCE_FILTER_1, _SRCFILTERBIT, 0xF));

    // Set window limit to the maximum value
    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _NPORT, _PORTSTAT_WINDOW_LIMIT, 0xffffffff);

     NVSWITCH_SAW_WR32_LR10(device, _NVLSAW, _GLBLLATENCYTIMERCTRL,
         DRF_DEF(_NVLSAW, _GLBLLATENCYTIMERCTRL, _ENABLE, _ENABLE));

     NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _NPORT, _PORTSTAT_SNAP_CONTROL,
         DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _STARTCOUNTER, _ENABLE) |
         DRF_DEF(_NPORT, _PORTSTAT_SNAP_CONTROL, _SNAPONDEMAND, _DISABLE));

     return NVL_SUCCESS;
}

NvlStatus
nvswitch_init_nxbar_lr10
(
    nvswitch_device *device
)
{
    NvU32 tileout;

    // Setting this bit will send error detection info to NPG.
    NVSWITCH_BCAST_WR32_LR10(device, TILE, _NXBAR, _TILE_ERR_CYA,
        DRF_DEF(_NXBAR, _TILE_ERR_CYA, _SRCID_UPDATE_AT_EGRESS_CTRL, __PROD));

    for (tileout = 0; tileout < NUM_NXBAR_TILEOUTS_PER_TC_LR10; tileout++)
    {
        NVSWITCH_BCAST_WR32_LR10(device, NXBAR, _NXBAR, _TC_TILEOUT_ERR_CYA(tileout),
            DRF_DEF(_NXBAR, _TC_TILEOUT0_ERR_CYA, _SRCID_UPDATE_AT_EGRESS_CTRL, __PROD));
    }

    // Enable idle-based clk gating and setup delay count.
    NVSWITCH_BCAST_WR32_LR10(device, TILE, _NXBAR, _TILE_PRI_NXBAR_TILE_CG,
        DRF_DEF(_NXBAR, _TILE_PRI_NXBAR_TILE_CG, _IDLE_CG_EN, __PROD) |
        DRF_DEF(_NXBAR, _TILE_PRI_NXBAR_TILE_CG, _IDLE_CG_DLY_CNT, __PROD));

    NVSWITCH_BCAST_WR32_LR10(device, NXBAR, _NXBAR, _TC_PRI_NXBAR_TC_CG,
        DRF_DEF(_NXBAR, _TC_PRI_NXBAR_TC_CG, _IDLE_CG_EN, __PROD) |
        DRF_DEF(_NXBAR, _TC_PRI_NXBAR_TC_CG, _IDLE_CG_DLY_CNT, __PROD));

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_init_nport_lr10
(
    nvswitch_device *device
)
{
    NvU32 data32, timeout;
    NvU32 idx_nport;
    NvU32 num_nports;

    num_nports = NVSWITCH_ENG_COUNT(device, NPORT, );

    for (idx_nport = 0; idx_nport < num_nports; idx_nport++)
    {
        // Find the first valid nport
        if (NVSWITCH_ENG_IS_VALID(device, NPORT, idx_nport))
        {
            break;
        }
    }

    // There were no valid nports
    if (idx_nport == num_nports)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: No valid nports found!\n", __FUNCTION__);
        return -NVL_ERR_INVALID_STATE;
    }

    _nvswitch_init_nport_ecc_control_lr10(device);

    data32 = NVSWITCH_NPORT_RD32_LR10(device, idx_nport, _ROUTE, _ROUTE_CONTROL);
    data32 = FLD_SET_DRF(_ROUTE, _ROUTE_CONTROL, _URRESPENB, __PROD, data32);
    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _ROUTE, _ROUTE_CONTROL, data32);

    data32 = NVSWITCH_NPORT_RD32_LR10(device, idx_nport, _EGRESS, _CTRL);
    data32 = FLD_SET_DRF(_EGRESS, _CTRL, _DESTINATIONIDCHECKENB, __PROD, data32);
    data32 = FLD_SET_DRF(_EGRESS, _CTRL, _CTO_ENB, __PROD, data32);
    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _EGRESS, _CTRL, data32);

    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _EGRESS, _CTO_TIMER_LIMIT,
        DRF_DEF(_EGRESS, _CTO_TIMER_LIMIT, _LIMIT, __PROD));

    if (DRF_VAL(_SWITCH_REGKEY, _ATO_CONTROL, _DISABLE, device->regkeys.ato_control) ==
        NV_SWITCH_REGKEY_ATO_CONTROL_DISABLE_TRUE)
    {
        // ATO Disable
        data32 = NVSWITCH_NPORT_RD32_LR10(device, idx_nport, _TSTATE, _TAGSTATECONTROL);
        data32 = FLD_SET_DRF(_TSTATE, _TAGSTATECONTROL, _ATO_ENB, _OFF, data32);
        NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _TSTATE, _TAGSTATECONTROL, data32);
    }
    else
    {
        // ATO Enable
        data32 = NVSWITCH_NPORT_RD32_LR10(device, idx_nport, _TSTATE, _TAGSTATECONTROL);
        data32 = FLD_SET_DRF(_TSTATE, _TAGSTATECONTROL, _ATO_ENB, _ON, data32);
        NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _TSTATE, _TAGSTATECONTROL, data32);

        // ATO Timeout value
        timeout = DRF_VAL(_SWITCH_REGKEY, _ATO_CONTROL, _TIMEOUT, device->regkeys.ato_control);
        if (timeout != NV_SWITCH_REGKEY_ATO_CONTROL_TIMEOUT_DEFAULT)
        {
            NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _TSTATE, _ATO_TIMER_LIMIT,
                DRF_NUM(_TSTATE, _ATO_TIMER_LIMIT, _LIMIT, timeout));
        }
        else
        {
            NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _TSTATE, _ATO_TIMER_LIMIT,
                DRF_DEF(_TSTATE, _ATO_TIMER_LIMIT, _LIMIT, __PROD));
        }
    }

    if (DRF_VAL(_SWITCH_REGKEY, _STO_CONTROL, _DISABLE, device->regkeys.sto_control) ==
        NV_SWITCH_REGKEY_STO_CONTROL_DISABLE_TRUE)
    {
        // STO Disable
        data32 = NVSWITCH_NPORT_RD32_LR10(device, idx_nport, _SOURCETRACK, _CTRL);
        data32 = FLD_SET_DRF(_SOURCETRACK, _CTRL, _STO_ENB, _OFF, data32);
        NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _SOURCETRACK, _CTRL, data32);
    }
    else
    {
        // STO Enable
        data32 = NVSWITCH_NPORT_RD32_LR10(device, idx_nport, _SOURCETRACK, _CTRL);
        data32 = FLD_SET_DRF(_SOURCETRACK, _CTRL, _STO_ENB, _ON, data32);
        NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _SOURCETRACK, _CTRL, data32);

        // STO Timeout value
        timeout = DRF_VAL(_SWITCH_REGKEY, _STO_CONTROL, _TIMEOUT, device->regkeys.sto_control);
        if (timeout != NV_SWITCH_REGKEY_STO_CONTROL_TIMEOUT_DEFAULT)
        {
            NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _SOURCETRACK, _MULTISEC_TIMER0,
                DRF_NUM(_SOURCETRACK, _MULTISEC_TIMER0, _TIMERVAL0, timeout));
        }
        else
        {
            NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _SOURCETRACK, _MULTISEC_TIMER0,
                DRF_DEF(_SOURCETRACK, _MULTISEC_TIMER0, _TIMERVAL0, __PROD));
        }
    }

    //
    // WAR for bug 200606509
    // Disable CAM for entry 0 to prevent false ATO trigger
    //
    data32 = NVSWITCH_NPORT_RD32_LR10(device, idx_nport, _TSTATE, _CREQ_CAM_LOCK);
    data32 = DRF_NUM(_TSTATE, _CREQ_CAM_LOCK, _ON, 0x1);
    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _TSTATE, _CREQ_CAM_LOCK, data32);

    //
    // WAR for bug 3115824
    // Clear CONTAIN_AND_DRAIN during init for links in reset.
    // Since SBR does not clear CONTAIN_AND_DRAIN, this will clear the bit
    // when the driver is reloaded after an SBR. If the driver has been reloaded
    // without an SBR, then CONTAIN_AND_DRAIN will be re-triggered.
    //
    NVSWITCH_NPORT_MC_BCAST_WR32_LR10(device, _NPORT, _CONTAIN_AND_DRAIN,
        DRF_DEF(_NPORT, _CONTAIN_AND_DRAIN, _CLEAR, _ENABLE));

    return NVL_SUCCESS;
}

void *
nvswitch_alloc_chipdevice_lr10
(
    nvswitch_device *device
)
{
    void *chip_device;

    chip_device = nvswitch_os_malloc(sizeof(lr10_device));
    if (NULL != chip_device)
    {
        nvswitch_os_memset(chip_device, 0, sizeof(lr10_device));
    }

    device->chip_id = NV_PSMC_BOOT_42_CHIP_ID_LR10;
    return(chip_device);
}

static NvlStatus
nvswitch_initialize_pmgr_lr10
(
    nvswitch_device *device
)
{
    nvswitch_init_pmgr_lr10(device);
    nvswitch_init_pmgr_devices_lr10(device);

    return NVL_SUCCESS;
}

static NvlStatus
nvswitch_initialize_route_lr10
(
    nvswitch_device *device
)
{
    NvlStatus retval;

    retval = _nvswitch_init_ganged_link_routing(device);
    if (NVL_SUCCESS != retval)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Failed to initialize GLT\n",
            __FUNCTION__);
        goto nvswitch_initialize_route_exit;
    }

    _nvswitch_init_cmd_routing(device);

    // Initialize Portstat Counters
    retval = _nvswitch_init_portstat_counters(device);
    if (NVL_SUCCESS != retval)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Failed to initialize portstat counters\n",
            __FUNCTION__);
        goto nvswitch_initialize_route_exit;
    }

nvswitch_initialize_route_exit:
    return retval;
}


NvlStatus
nvswitch_pri_ring_init_lr10
(
    nvswitch_device *device
)
{
    NvU32 i;
    NvU32 value;
    NvBool enumerated = NV_FALSE;
    NvlStatus retval = NVL_SUCCESS;

    //
    // Sometimes on RTL simulation we see the priv ring initialization fail.
    // Retry up to 3 times until this issue is root caused. Bug 1826216.
    //
    for (i = 0; !enumerated && (i < 3); i++)
    {
        value = DRF_DEF(_PPRIV_MASTER, _RING_COMMAND, _CMD, _ENUMERATE_AND_START_RING);
        retval = nvswitch_ring_master_cmd_lr10(device, value);
        if (retval != NVL_SUCCESS)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: PRIV ring enumeration failed\n",
                __FUNCTION__);
            continue;
        }

        value = NVSWITCH_REG_RD32(device, _PPRIV_MASTER, _RING_START_RESULTS);
        if (!FLD_TEST_DRF(_PPRIV_MASTER, _RING_START_RESULTS, _CONNECTIVITY, _PASS, value))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: PRIV ring connectivity failed\n",
                __FUNCTION__);
            continue;
        }

        value = NVSWITCH_REG_RD32(device, _PPRIV_MASTER, _RING_INTERRUPT_STATUS0);
        if (value)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: NV_PPRIV_MASTER_RING_INTERRUPT_STATUS0 = %x\n",
                __FUNCTION__, value);

            if ((!FLD_TEST_DRF_NUM(_PPRIV_MASTER, _RING_INTERRUPT_STATUS0,
                    _RING_START_CONN_FAULT, 0, value)) ||
                (!FLD_TEST_DRF_NUM(_PPRIV_MASTER, _RING_INTERRUPT_STATUS0,
                    _DISCONNECT_FAULT, 0, value))      ||
                (!FLD_TEST_DRF_NUM(_PPRIV_MASTER, _RING_INTERRUPT_STATUS0,
                    _OVERFLOW_FAULT, 0, value)))
            {
                NVSWITCH_PRINT(device, ERROR,
                    "%s: PRIV ring error interrupt\n",
                    __FUNCTION__);
            }

            (void)nvswitch_ring_master_cmd_lr10(device,
                    DRF_DEF(_PPRIV_MASTER, _RING_COMMAND, _CMD, _ACK_INTERRUPT));

            continue;
        }

        enumerated = NV_TRUE;
    }

    if (!enumerated)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Cannot enumerate PRIV ring!\n",
            __FUNCTION__);
        retval = -NVL_INITIALIZATION_TOTAL_FAILURE;
    }

    return retval;
}

/*
 * @Brief : Initializes an NvSwitch hardware state
 *
 * @Description :
 *
 * @param[in] device        a reference to the device to initialize
 *
 * @returns                 NVL_SUCCESS if the action succeeded
 *                          -NVL_BAD_ARGS if bad arguments provided
 *                          -NVL_PCI_ERROR if bar info unable to be retrieved
 */
NvlStatus
nvswitch_initialize_device_state_lr10
(
    nvswitch_device *device
)
{
    NvlStatus retval = NVL_SUCCESS;

    // alloc chip-specific device structure
    device->chip_device = nvswitch_alloc_chipdevice(device);
    if (NULL == device->chip_device)
    {
        NVSWITCH_PRINT(device, ERROR,
            "nvswitch_os_malloc during chip_device creation failed!\n");
        retval = -NVL_NO_MEM;
        goto nvswitch_initialize_device_state_exit;
    }

    NVSWITCH_PRINT(device, SETUP,
        "%s: MMIO discovery\n",
        __FUNCTION__);
    retval = nvswitch_device_discovery(device, NV_SWPTOP_TABLE_BASE_ADDRESS_OFFSET);
    if (NVL_SUCCESS != retval)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Engine discovery failed\n",
            __FUNCTION__);
        goto nvswitch_initialize_device_state_exit;
    }

    nvswitch_filter_discovery(device);

    retval = nvswitch_process_discovery(device);
    if (NVL_SUCCESS != retval)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Discovery processing failed\n",
            __FUNCTION__);
        goto nvswitch_initialize_device_state_exit;
    }

    // now that we have completed discovery, perform initialization steps that
    // depend on engineDescriptors being initialized
    //
    // Temporary location, really needs to be done somewhere common to all flcnables
    if (nvswitch_is_soe_supported(device))
    {
        flcnablePostDiscoveryInit(device, device->pSoe);
    }
    else
    {
        NVSWITCH_PRINT(device, INFO, "%s: Skipping SOE post discovery init.\n",
            __FUNCTION__);
    }

    // Make sure interrupts are disabled before we enable interrupts with the OS.
    nvswitch_lib_disable_interrupts(device);

    retval = nvswitch_pri_ring_init(device);
    if (retval != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: PRI init failed\n", __FUNCTION__);
        goto nvswitch_initialize_device_state_exit;
    }

    NVSWITCH_PRINT(device, SETUP,
        "%s: Enabled links: 0x%llx\n",
        __FUNCTION__,
        ((NvU64)device->regkeys.link_enable_mask2 << 32 |
        (NvU64)device->regkeys.link_enable_mask) &
        ((~0ULL) >> (64 - NVSWITCH_LINK_COUNT(device))));

    if (nvswitch_is_soe_supported(device))
    {
        retval = nvswitch_init_soe(device);
        if (NVL_SUCCESS != retval)
        {
            NVSWITCH_PRINT(device, ERROR, "%s: Init SOE failed\n",
                __FUNCTION__);
            goto nvswitch_initialize_device_state_exit;
        }
    }
    else
    {
        NVSWITCH_PRINT(device, INFO, "%s: Skipping SOE init.\n",
            __FUNCTION__);
    }

    // Read ROM configuration
    nvswitch_read_rom_tables(device, &device->firmware);
    _nvswitch_process_firmware_info_lr10(device, &device->firmware);

    // Init PMGR info
    retval = nvswitch_initialize_pmgr(device);
    if (retval != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: PMGR init failed\n", __FUNCTION__);
        retval = -NVL_INITIALIZATION_TOTAL_FAILURE;
        goto nvswitch_initialize_device_state_exit;
    }

    retval = nvswitch_init_pll_config(device);
    if (retval != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: failed\n", __FUNCTION__);
        retval = -NVL_INITIALIZATION_TOTAL_FAILURE;
        goto nvswitch_initialize_device_state_exit;
    }

    //
    // PLL init should be done *first* before other hardware init
    //
    retval = nvswitch_init_pll(device);
    if (NVL_SUCCESS != retval)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: PLL init failed\n",
            __FUNCTION__);
        goto nvswitch_initialize_device_state_exit;
    }

    //
    // Now that software knows the devices and addresses, it must take all
    // the wrapper modules out of reset.  It does this by writing to the
    // PMC module enable registers.
    //

    // Init IP wrappers
//    _nvswitch_init_mc_enable_lr10(device);
    retval = nvswitch_initialize_ip_wrappers(device);
    if (retval != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: init failed\n", __FUNCTION__);
        retval = -NVL_INITIALIZATION_TOTAL_FAILURE;
        goto nvswitch_initialize_device_state_exit;
    }

    _nvswitch_init_warm_reset_lr10(device);
    _nvswitch_init_npg_multicast_lr10(device);
    retval = nvswitch_clear_nport_rams(device);
    if (NVL_SUCCESS != retval)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: NPORT RAM clear failed\n",
            __FUNCTION__);
        goto nvswitch_initialize_device_state_exit;
    }

    retval = nvswitch_init_nport(device);
    if (retval != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Init NPORTs failed\n",
            __FUNCTION__);
        goto nvswitch_initialize_device_state_exit;
    }

    retval = nvswitch_init_nxbar(device);
    if (retval != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Init NXBARs failed\n",
            __FUNCTION__);
        goto nvswitch_initialize_device_state_exit;
    }

    if (device->regkeys.minion_disable != NV_SWITCH_REGKEY_MINION_DISABLE_YES)
    {
        NVSWITCH_PRINT(device, WARN, "%s: Entering init minion\n", __FUNCTION__);

        retval = nvswitch_init_minion(device);
        if (NVL_SUCCESS != retval)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: Init MINIONs failed\n",
                __FUNCTION__);
            goto nvswitch_initialize_device_state_exit;
        }
    }
    else
    {
        NVSWITCH_PRINT(device, INFO, "MINION is disabled via regkey.\n");

        NVSWITCH_PRINT(device, INFO, "%s: Skipping MINION init\n",
            __FUNCTION__);
    }

    _nvswitch_setup_chiplib_forced_config_lr10(device);

    // Init route
    retval = nvswitch_initialize_route(device);
    if (retval != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: route init failed\n", __FUNCTION__);
        retval = -NVL_INITIALIZATION_TOTAL_FAILURE;
        goto nvswitch_initialize_device_state_exit;
    }

    nvswitch_init_clock_gating(device);

    // Initialize SPI
    if (nvswitch_is_spi_supported(device))
    {
        retval = nvswitch_spi_init(device);
        if (NVL_SUCCESS != retval)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: SPI init failed!, rc: %d\n",
                __FUNCTION__, retval);
            goto nvswitch_initialize_device_state_exit;
        }
    }
    else
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Skipping SPI init.\n",
            __FUNCTION__);
    }

    // Initialize SMBPBI
    if (nvswitch_is_smbpbi_supported(device))
    {
        retval = nvswitch_smbpbi_init(device);
        if (NVL_SUCCESS != retval)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: SMBPBI init failed!, rc: %d\n",
                __FUNCTION__, retval);
            goto nvswitch_initialize_device_state_exit;
        }
    }
    else
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Skipping SMBPBI init.\n",
            __FUNCTION__);
    }

    nvswitch_initialize_interrupt_tree(device);

    // Initialize external thermal sensor
    retval = nvswitch_init_thermal(device);
    if (NVL_SUCCESS != retval)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: External Thermal init failed\n",
            __FUNCTION__);
    }

    return NVL_SUCCESS;

nvswitch_initialize_device_state_exit:
    nvswitch_destroy_device_state(device);

    return retval;
}

/*
 * @Brief : Destroys an NvSwitch hardware state
 *
 * @Description :
 *
 * @param[in] device        a reference to the device to initialize
 */
void
nvswitch_destroy_device_state_lr10
(
    nvswitch_device *device
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);

    if (nvswitch_is_soe_supported(device))
    {
        nvswitch_soe_unregister_events(device);
    }

    if (chip_device != NULL)
    {
        if ((chip_device->latency_stats) != NULL)
        {
            nvswitch_os_free(chip_device->latency_stats);
        }

        if ((chip_device->ganged_link_table) != NULL)
        {
            nvswitch_os_free(chip_device->ganged_link_table);
        }

        nvswitch_free_chipdevice(device);
    }

    nvswitch_i2c_destroy(device);

    return;
}

static void
_nvswitch_set_nvlink_caps_lr10
(
    NvU32 *pCaps
)
{
    NvU8 tempCaps[NVSWITCH_NVLINK_CAPS_TBL_SIZE];

    nvswitch_os_memset(tempCaps, 0, sizeof(tempCaps));

    NVSWITCH_SET_CAP(tempCaps, NVSWITCH_NVLINK_CAPS, _VALID);
    NVSWITCH_SET_CAP(tempCaps, NVSWITCH_NVLINK_CAPS, _SUPPORTED);
    NVSWITCH_SET_CAP(tempCaps, NVSWITCH_NVLINK_CAPS, _P2P_SUPPORTED);
    NVSWITCH_SET_CAP(tempCaps, NVSWITCH_NVLINK_CAPS, _P2P_ATOMICS);

    // Assume IBM P9 for PPC -- TODO Xavier support.
#if defined(NVCPU_PPC64LE)
    NVSWITCH_SET_CAP(tempCaps, NVSWITCH_NVLINK_CAPS, _SYSMEM_ACCESS);
    NVSWITCH_SET_CAP(tempCaps, NVSWITCH_NVLINK_CAPS, _SYSMEM_ATOMICS);
#endif

    nvswitch_os_memcpy(pCaps, tempCaps, sizeof(tempCaps));
}

/*
 * @brief Determines if a link's lanes are reversed
 *
 * @param[in] device    a reference to the device to query
 * @param[in] linkId    Target link ID
 *
 * @return NV_TRUE if a link's lanes are reversed
 */
NvBool
nvswitch_link_lane_reversed_lr10
(
    nvswitch_device *device,
    NvU32            linkId
)
{
    NvU32 regData;
    nvlink_link *link;

    link = nvswitch_get_link(device, linkId);
    if (nvswitch_is_link_in_reset(device, link))
    {
        return NV_FALSE;
    }

    regData = NVSWITCH_LINK_RD32_LR10(device, linkId, NVLDL, _NVLDL_RX, _CONFIG_RX);

    // HW may reverse the lane ordering or it may be overridden by SW.
    if (FLD_TEST_DRF(_NVLDL_RX, _CONFIG_RX, _REVERSAL_OVERRIDE, _ON, regData))
    {
        // Overridden
        if (FLD_TEST_DRF(_NVLDL_RX, _CONFIG_RX, _LANE_REVERSE, _ON, regData))
        {
            return NV_TRUE;
        }
        else
        {
            return NV_FALSE;
        }
    }
    else
    {
        // Sensed in HW
        if (FLD_TEST_DRF(_NVLDL_RX, _CONFIG_RX, _HW_LANE_REVERSE, _ON, regData))
        {
            return NV_TRUE;
        }
        else
        {
            return NV_FALSE;
        }
    }

    return NV_FALSE;
}

NvlStatus
nvswitch_ctrl_get_nvlink_status_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_NVLINK_STATUS_PARAMS *ret
)
{
    NvlStatus retval = NVL_SUCCESS;
    nvlink_link *link;
    NvU8 i;
    NvU32 linkState, txSublinkStatus, rxSublinkStatus;
    nvlink_conn_info conn_info = {0};
    NvU64 enabledLinkMask;
    NvU32 nvlink_caps_version;

    enabledLinkMask = nvswitch_get_enabled_link_mask(device);
    ret->enabledLinkMask = enabledLinkMask;

    FOR_EACH_INDEX_IN_MASK(64, i, enabledLinkMask)
    {
        NVSWITCH_ASSERT(i < NVSWITCH_LINK_COUNT(device));

        link = nvswitch_get_link(device, i);

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

        //
        // Call the core library to get the remote end information. On the first
        // invocation this will also trigger link training, if link-training is
        // not externally managed by FM. Therefore it is necessary that this be
        // before link status on the link is populated since this call will
        // actually change link state.
        //
        if (device->regkeys.external_fabric_mgmt)
        {
            nvlink_lib_get_remote_conn_info(link, &conn_info);
        }
        else
        {
            nvlink_lib_discover_and_get_remote_conn_info(link, &conn_info, NVLINK_STATE_CHANGE_SYNC);
        }

        // Set NVLINK per-link caps
        _nvswitch_set_nvlink_caps_lr10(&ret->linkInfo[i].capsTbl);

        ret->linkInfo[i].phyType = NVSWITCH_NVLINK_STATUS_PHY_NVHS;
        ret->linkInfo[i].subLinkWidth = nvswitch_get_sublink_width(device, link->linkNumber);

        if (!nvswitch_is_link_in_reset(device, link))
        {
            linkState = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TOP, _LINK_STATE);
            linkState = DRF_VAL(_NVLDL_TOP, _LINK_STATE, _STATE, linkState);

            txSublinkStatus = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_TX, _SLSM_STATUS_TX);
            txSublinkStatus = DRF_VAL(_NVLDL_TX, _SLSM_STATUS_TX, _PRIMARY_STATE, txSublinkStatus);

            rxSublinkStatus = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLDL, _NVLDL_RX, _SLSM_STATUS_RX);
            rxSublinkStatus = DRF_VAL(_NVLDL_RX, _SLSM_STATUS_RX, _PRIMARY_STATE, rxSublinkStatus);

            ret->linkInfo[i].bLaneReversal = nvswitch_link_lane_reversed_lr10(device, i);
        }
        else
        {
            linkState       = NVSWITCH_NVLINK_STATUS_LINK_STATE_INIT;
            txSublinkStatus = NVSWITCH_NVLINK_STATUS_SUBLINK_TX_STATE_OFF;
            rxSublinkStatus = NVSWITCH_NVLINK_STATUS_SUBLINK_RX_STATE_OFF;
        }

        ret->linkInfo[i].linkState       = linkState;
        ret->linkInfo[i].txSublinkStatus = txSublinkStatus;
        ret->linkInfo[i].rxSublinkStatus = rxSublinkStatus;

        nvlink_caps_version = nvswitch_get_caps_nvlink_version(device);
        if (nvlink_caps_version == NVSWITCH_NVLINK_CAPS_NVLINK_VERSION_3_0)
        {
            ret->linkInfo[i].nvlinkVersion = NVSWITCH_NVLINK_STATUS_NVLINK_VERSION_3_0;
            ret->linkInfo[i].nciVersion = NVSWITCH_NVLINK_STATUS_NCI_VERSION_3_0;
        }
        else
        {
            NVSWITCH_PRINT(device, WARN,
                "%s WARNING: Unknown NVSWITCH_NVLINK_CAPS_NVLINK_VERSION 0x%x\n",
                __FUNCTION__, nvlink_caps_version);
            ret->linkInfo[i].nvlinkVersion = NVSWITCH_NVLINK_STATUS_NVLINK_VERSION_INVALID;
            ret->linkInfo[i].nciVersion = NVSWITCH_NVLINK_STATUS_NCI_VERSION_INVALID;
        }

        ret->linkInfo[i].phyVersion = NVSWITCH_NVLINK_STATUS_NVHS_VERSION_1_0;

        if (conn_info.bConnected)
        {
            ret->linkInfo[i].connected = NVSWITCH_NVLINK_STATUS_CONNECTED_TRUE;
            ret->linkInfo[i].remoteDeviceLinkNumber = (NvU8)conn_info.linkNumber;

            ret->linkInfo[i].remoteDeviceInfo.domain = conn_info.domain;
            ret->linkInfo[i].remoteDeviceInfo.bus = conn_info.bus;
            ret->linkInfo[i].remoteDeviceInfo.device = conn_info.device;
            ret->linkInfo[i].remoteDeviceInfo.function = conn_info.function;
            ret->linkInfo[i].remoteDeviceInfo.pciDeviceId = conn_info.pciDeviceId;
            ret->linkInfo[i].remoteDeviceInfo.deviceType = conn_info.deviceType;

            ret->linkInfo[i].localLinkSid  = link->localSid;
            ret->linkInfo[i].remoteLinkSid = link->remoteSid;

            if (0 != conn_info.pciDeviceId)
            {
                ret->linkInfo[i].remoteDeviceInfo.deviceIdFlags =
                    FLD_SET_DRF(SWITCH_NVLINK, _DEVICE_INFO, _DEVICE_ID_FLAGS,
                         _PCI, ret->linkInfo[i].remoteDeviceInfo.deviceIdFlags);
            }

            // Does not use loopback
            ret->linkInfo[i].loopProperty =
                NVSWITCH_NVLINK_STATUS_LOOP_PROPERTY_NONE;
        }
        else
        {
            ret->linkInfo[i].connected =
                NVSWITCH_NVLINK_STATUS_CONNECTED_FALSE;
            ret->linkInfo[i].remoteDeviceInfo.deviceType =
                NVSWITCH_NVLINK_DEVICE_INFO_DEVICE_TYPE_NONE;
        }

        // Set the device information for the local end of the link
        ret->linkInfo[i].localDeviceInfo.domain = device->nvlink_device->pciInfo.domain;
        ret->linkInfo[i].localDeviceInfo.bus = device->nvlink_device->pciInfo.bus;
        ret->linkInfo[i].localDeviceInfo.device = device->nvlink_device->pciInfo.device;
        ret->linkInfo[i].localDeviceInfo.function = device->nvlink_device->pciInfo.function;
        ret->linkInfo[i].localDeviceInfo.pciDeviceId = 0xdeadbeef; // TODO
        ret->linkInfo[i].localDeviceLinkNumber = i;
        ret->linkInfo[i].laneRxdetStatusMask = device->link[i].lane_rxdet_status_mask;
        ret->linkInfo[i].localDeviceInfo.deviceType =
            NVSWITCH_NVLINK_DEVICE_INFO_DEVICE_TYPE_SWITCH;

        // Clock data
        ret->linkInfo[i].nvlinkLineRateMbps = nvswitch_minion_get_line_rate_Mbps_lr10(device, i);
        ret->linkInfo[i].nvlinkLinkDataRateKiBps = nvswitch_minion_get_data_rate_KiBps_lr10(device, i);
        ret->linkInfo[i].nvlinkLinkClockMhz = ret->linkInfo[i].nvlinkLineRateMbps / 32;
        ret->linkInfo[i].nvlinkRefClkSpeedMhz = 156;
        ret->linkInfo[i].nvlinkRefClkType = NVSWITCH_NVLINK_REFCLK_TYPE_NVHS;

    }
    FOR_EACH_INDEX_IN_MASK_END;

//    NVSWITCH_ASSERT(ret->enabledLinkMask == enabledLinkMask);

    return retval;
}

NvlStatus
nvswitch_ctrl_get_counters_lr10
(
    nvswitch_device *device,
    NVSWITCH_NVLINK_GET_COUNTERS_PARAMS *ret
)
{
    nvlink_link *link;
    NvU8   i;
    NvU32  counterMask;
    NvU32  data;
    NvU32  val;
    NvU64  tx0TlCount;
    NvU64  tx1TlCount;
    NvU64  rx0TlCount;
    NvU64  rx1TlCount;
    NvU32  laneId;
    NvBool bLaneReversed;
    NvlStatus status;
    NvBool minion_enabled;

    ct_assert(NVSWITCH_NUM_LANES_LR10 <= NVSWITCH_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE__SIZE);

    link = nvswitch_get_link(device, ret->linkId);
    if ((link == NULL) ||
        !NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLDL, link->linkNumber))
    {
        return -NVL_BAD_ARGS;
    }

    minion_enabled = nvswitch_is_minion_initialized(device, NVSWITCH_GET_LINK_ENG_INST(device, link->linkNumber, MINION));

    counterMask = ret->counterMask;

    // Common usage allows one of these to stand for all of them
    if (counterMask & (NVSWITCH_NVLINK_COUNTER_TL_TX0 |
                       NVSWITCH_NVLINK_COUNTER_TL_TX1 |
                       NVSWITCH_NVLINK_COUNTER_TL_RX0 |
                       NVSWITCH_NVLINK_COUNTER_TL_RX1))
    {
        tx0TlCount = nvswitch_read_64bit_counter(device,
            NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber, NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_LO(0)),
            NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber, NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_HI(0)));
        if (NVBIT64(63) & tx0TlCount)
        {
            ret->bTx0TlCounterOverflow = NV_TRUE;
            tx0TlCount &= ~(NVBIT64(63));
        }

        tx1TlCount = nvswitch_read_64bit_counter(device,
            NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber, NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_LO(1)),
            NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber, NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_HI(1)));
        if (NVBIT64(63) & tx1TlCount)
        {
            ret->bTx1TlCounterOverflow = NV_TRUE;
            tx1TlCount &= ~(NVBIT64(63));
        }

        rx0TlCount = nvswitch_read_64bit_counter(device,
            NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber, NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_LO(0)),
            NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber, NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_HI(0)));
        if (NVBIT64(63) & rx0TlCount)
        {
            ret->bRx0TlCounterOverflow = NV_TRUE;
            rx0TlCount &= ~(NVBIT64(63));
        }

        rx1TlCount = nvswitch_read_64bit_counter(device,
            NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber, NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_LO(1)),
            NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber, NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_HI(1)));
        if (NVBIT64(63) & rx1TlCount)
        {
            ret->bRx1TlCounterOverflow = NV_TRUE;
            rx1TlCount &= ~(NVBIT64(63));
        }

        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_TL_TX0)] = tx0TlCount;
        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_TL_TX1)] = tx1TlCount;
        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_TL_RX0)] = rx0TlCount;
        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_TL_RX1)] = rx1TlCount;
    }

    if (counterMask & NVSWITCH_NVLINK_COUNTER_DL_RX_ERR_CRC_FLIT)
    {
        if (minion_enabled)
        {
            status = nvswitch_minion_get_dl_status(device, link->linkNumber,
                                    NV_NVLSTAT_RX01, 0, &data);
            if (status != NVL_SUCCESS)
            {
                return status;
            }
            data = DRF_VAL(_NVLSTAT, _RX01, _FLIT_CRC_ERRORS_VALUE, data);
        }
        else
        {
            // MINION disabled
            data = 0;
        }

        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_DL_RX_ERR_CRC_FLIT)]
            = data;
    }

    data = 0x0;
    bLaneReversed = nvswitch_link_lane_reversed_lr10(device, link->linkNumber);

    for (laneId = 0; laneId < NVSWITCH_NUM_LANES_LR10; laneId++)
    {
        //
        // HW may reverse the lane ordering or it may be overridden by SW.
        // If so, invert the interpretation of the lane CRC errors.
        //
        i = (NvU8)((bLaneReversed) ? (NVSWITCH_NUM_LANES_LR10 - 1) - laneId : laneId);

        if (minion_enabled)
        {
            status = nvswitch_minion_get_dl_status(device, link->linkNumber,
                                    NV_NVLSTAT_DB01, 0, &data);
            if (status != NVL_SUCCESS)
            {
                return status;
            }
        }
        else
        {
            // MINION disabled
            data = 0;
        }

        if (counterMask & NVSWITCH_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L(laneId))
        {
            val = BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L(laneId));

            switch (i)
            {
                case 0:
                    ret->nvlinkCounters[val]
                        = DRF_VAL(_NVLSTAT, _DB01, _ERROR_COUNT_ERR_LANECRC_L0, data);
                    break;
                case 1:
                    ret->nvlinkCounters[val]
                        = DRF_VAL(_NVLSTAT, _DB01, _ERROR_COUNT_ERR_LANECRC_L1, data);
                    break;
                case 2:
                    ret->nvlinkCounters[val]
                        = DRF_VAL(_NVLSTAT, _DB01, _ERROR_COUNT_ERR_LANECRC_L2, data);
                    break;
                case 3:
                    ret->nvlinkCounters[val]
                        = DRF_VAL(_NVLSTAT, _DB01, _ERROR_COUNT_ERR_LANECRC_L3, data);
                    break;
            }
        }
    }

    if (counterMask & NVSWITCH_NVLINK_COUNTER_DL_TX_ERR_REPLAY)
    {
        if (minion_enabled)
        {
            status = nvswitch_minion_get_dl_status(device, link->linkNumber,
                                    NV_NVLSTAT_TX09, 0, &data);
            if (status != NVL_SUCCESS)
            {
                return status;
            }
            data = DRF_VAL(_NVLSTAT, _TX09, _REPLAY_EVENTS_VALUE, data);
        }
        else
        {
            // MINION disabled
            data = 0;
        }

        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_DL_TX_ERR_REPLAY)]
            = data;
    }

    if (counterMask & NVSWITCH_NVLINK_COUNTER_DL_TX_ERR_RECOVERY)
    {
        if (minion_enabled)
        {
            status = nvswitch_minion_get_dl_status(device, link->linkNumber,
                                    NV_NVLSTAT_LNK1, 0, &data);
            if (status != NVL_SUCCESS)
            {
                return status;
            }
            data = DRF_VAL(_NVLSTAT, _LNK1, _ERROR_COUNT1_RECOVERY_EVENTS_VALUE, data);
        }
        else
        {
            // MINION disabled
            data = 0;
        }

        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_DL_TX_ERR_RECOVERY)]
            = data;
    }

    if (counterMask & NVSWITCH_NVLINK_COUNTER_DL_RX_ERR_REPLAY)
    {
        if (minion_enabled)
        {
            status = nvswitch_minion_get_dl_status(device, link->linkNumber,
                                    NV_NVLSTAT_RX00, 0, &data);
            if (status != NVL_SUCCESS)
            {
                return status;
            }
            data = DRF_VAL(_NVLSTAT, _RX00, _REPLAY_EVENTS_VALUE, data);
        }
        else
        {
            // MINION disabled
            data = 0;
        }

        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_DL_RX_ERR_REPLAY)]
            = data;
    }

    if (counterMask & NVSWITCH_NVLINK_COUNTER_PHY_REFRESH_PASS)
    {
        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_PHY_REFRESH_PASS)] = 0;
    }

    if (counterMask & NVSWITCH_NVLINK_COUNTER_PHY_REFRESH_FAIL)
    {
        ret->nvlinkCounters[BIT_IDX_32(NVSWITCH_NVLINK_COUNTER_PHY_REFRESH_FAIL)] = 0;
    }

    return NVL_SUCCESS;
}

/*
 * CTRL_NVSWITCH_GET_INFO
 *
 * Query for miscellaneous information analogous to NV2080_CTRL_GPU_INFO
 * This provides a single API to query for multiple pieces of miscellaneous
 * information via a single call.
 *
 */

static NvU32
_nvswitch_get_info_chip_id
(
    nvswitch_device *device
)
{
    NvU32 val = NVSWITCH_REG_RD32(device, _PSMC, _BOOT_42);

    return (DRF_VAL(_PSMC, _BOOT_42, _CHIP_ID, val));
}

static NvU32
_nvswitch_get_info_revision_major
(
    nvswitch_device *device
)
{
    NvU32 val = NVSWITCH_REG_RD32(device, _PSMC, _BOOT_42);

    return (DRF_VAL(_PSMC, _BOOT_42, _MAJOR_REVISION, val));
}

static NvU32
_nvswitch_get_info_revision_minor
(
    nvswitch_device *device
)
{
    NvU32 val = NVSWITCH_REG_RD32(device, _PSMC, _BOOT_42);

    return (DRF_VAL(_PSMC, _BOOT_42, _MINOR_REVISION, val));
}

static NvU32
_nvswitch_get_info_revision_minor_ext
(
    nvswitch_device *device
)
{
    NvU32 val = NVSWITCH_REG_RD32(device, _PSMC, _BOOT_42);

    return (DRF_VAL(_PSMC, _BOOT_42, _MINOR_EXTENDED_REVISION, val));
}

static NvU32
_nvswitch_get_info_voltage
(
    nvswitch_device *device
)
{
    NvU32 voltage = 0;

    return voltage;
}

static NvBool
_nvswitch_inforom_nvl_supported
(
    nvswitch_device *device
)
{
    return NV_FALSE;
}

static NvBool
_nvswitch_inforom_bbx_supported
(
    nvswitch_device *device
)
{
    return NV_FALSE;
}

/*
 * CTRL_NVSWITCH_GET_INFO
 *
 * Query for miscellaneous information analogous to NV2080_CTRL_GPU_INFO
 * This provides a single API to query for multiple pieces of miscellaneous
 * information via a single call.
 *
 */

NvlStatus
nvswitch_ctrl_get_info_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_INFO *p
)
{
    NvlStatus retval = NVL_SUCCESS;
    NvU32 i;

    if (p->count > NVSWITCH_GET_INFO_COUNT_MAX)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Invalid args\n",
            __FUNCTION__);
        return -NVL_BAD_ARGS;
    }

    nvswitch_os_memset(p->info, 0, sizeof(NvU32)*NVSWITCH_GET_INFO_COUNT_MAX);

    for (i = 0; i < p->count; i++)
    {
        switch (p->index[i])
        {
            case NVSWITCH_GET_INFO_INDEX_ARCH:
                p->info[i] = device->chip_arch;
                break;
            case NVSWITCH_GET_INFO_INDEX_PLATFORM:
                if (IS_RTLSIM(device))
                {
                    p->info[i] = NVSWITCH_GET_INFO_INDEX_PLATFORM_RTLSIM;
                }
                else if (IS_FMODEL(device))
                {
                    p->info[i] = NVSWITCH_GET_INFO_INDEX_PLATFORM_FMODEL;
                }
                else if (IS_EMULATION(device))
                {
                    p->info[i] = NVSWITCH_GET_INFO_INDEX_PLATFORM_EMULATION;
                }
                else
                {
                    p->info[i] = NVSWITCH_GET_INFO_INDEX_PLATFORM_SILICON;
                }
                break;
            case NVSWITCH_GET_INFO_INDEX_IMPL:
                p->info[i] = device->chip_impl;
                break;
            case NVSWITCH_GET_INFO_INDEX_CHIPID:
                p->info[i] = _nvswitch_get_info_chip_id(device);
                break;
            case NVSWITCH_GET_INFO_INDEX_REVISION_MAJOR:
                p->info[i] = _nvswitch_get_info_revision_major(device);
                break;
            case NVSWITCH_GET_INFO_INDEX_REVISION_MINOR:
                p->info[i] = _nvswitch_get_info_revision_minor(device);
                break;
            case NVSWITCH_GET_INFO_INDEX_REVISION_MINOR_EXT:
                p->info[i] = _nvswitch_get_info_revision_minor_ext(device);
                break;
            case NVSWITCH_GET_INFO_INDEX_DEVICE_ID:
                p->info[i] = device->nvlink_device->pciInfo.pciDeviceId;
                break;
            case NVSWITCH_GET_INFO_INDEX_NUM_PORTS:
                p->info[i] = NVSWITCH_LINK_COUNT(device);
                break;
            case NVSWITCH_GET_INFO_INDEX_ENABLED_PORTS_MASK_31_0:
                p->info[i] = NvU64_LO32(nvswitch_get_enabled_link_mask(device));
                break;
            case NVSWITCH_GET_INFO_INDEX_ENABLED_PORTS_MASK_63_32:
                p->info[i] = NvU64_HI32(nvswitch_get_enabled_link_mask(device));
                break;
            case NVSWITCH_GET_INFO_INDEX_NUM_VCS:
                p->info[i] = _nvswitch_get_num_vcs_lr10(device);
                break;
            case NVSWITCH_GET_INFO_INDEX_REMAP_POLICY_TABLE_SIZE:
                {
                    NvU32 remap_ram_sel;
                    NvlStatus status;

                    status = nvswitch_get_remap_table_selector(device, NVSWITCH_TABLE_SELECT_REMAP_PRIMARY, &remap_ram_sel);
                    if (status == NVL_SUCCESS)
                    {
                        p->info[i] = nvswitch_get_ingress_ram_size(device, remap_ram_sel);
                    }
                    else
                    {
                        p->info[i] = 0;
                    }
                }
                break;
            case NVSWITCH_GET_INFO_INDEX_REMAP_POLICY_EXTA_TABLE_SIZE:
                {
                    NvU32 remap_ram_sel;
                    NvlStatus status;

                    status = nvswitch_get_remap_table_selector(device, NVSWITCH_TABLE_SELECT_REMAP_EXTA, &remap_ram_sel);
                    if (status == NVL_SUCCESS)
                    {
                        p->info[i] = nvswitch_get_ingress_ram_size(device, remap_ram_sel);
                    }
                    else
                    {
                        p->info[i] = 0;
                    }
                }
                break;
            case NVSWITCH_GET_INFO_INDEX_REMAP_POLICY_EXTB_TABLE_SIZE:
                {
                    NvU32 remap_ram_sel;
                    NvlStatus status;

                    status = nvswitch_get_remap_table_selector(device, NVSWITCH_TABLE_SELECT_REMAP_EXTB, &remap_ram_sel);
                    if (status == NVL_SUCCESS)
                    {
                        p->info[i] = nvswitch_get_ingress_ram_size(device, remap_ram_sel);
                    }
                    else
                    {
                        p->info[i] = 0;
                    }
                }
                break;
            case NVSWITCH_GET_INFO_INDEX_ROUTING_ID_TABLE_SIZE:
                p->info[i] = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRIDROUTERAM);
                break;
            case NVSWITCH_GET_INFO_INDEX_ROUTING_LAN_TABLE_SIZE:
                p->info[i] = nvswitch_get_ingress_ram_size(device, NV_INGRESS_REQRSPMAPADDR_RAM_SEL_SELECTSRLANROUTERAM);
                break;
            case NVSWITCH_GET_INFO_INDEX_FREQ_KHZ:
                p->info[i] = device->switch_pll.freq_khz;
                break;
            case NVSWITCH_GET_INFO_INDEX_VCOFREQ_KHZ:
                p->info[i] = device->switch_pll.vco_freq_khz;
                break;
            case NVSWITCH_GET_INFO_INDEX_VOLTAGE_MVOLT:
                p->info[i] = _nvswitch_get_info_voltage(device);
                break;
            case NVSWITCH_GET_INFO_INDEX_PHYSICAL_ID:
                p->info[i] = nvswitch_read_physical_id(device);
                break;
            case NVSWITCH_GET_INFO_INDEX_PCI_DOMAIN:
                p->info[i] = device->nvlink_device->pciInfo.domain;
                break;
            case NVSWITCH_GET_INFO_INDEX_PCI_BUS:
                p->info[i] = device->nvlink_device->pciInfo.bus;
                break;
            case NVSWITCH_GET_INFO_INDEX_PCI_DEVICE:
                p->info[i] = device->nvlink_device->pciInfo.device;
                break;
            case NVSWITCH_GET_INFO_INDEX_PCI_FUNCTION:
                p->info[i] = device->nvlink_device->pciInfo.function;
                break;
            default:
                NVSWITCH_PRINT(device, ERROR,
                    "%s: Undefined NVSWITCH_GET_INFO_INDEX 0x%x\n",
                    __FUNCTION__,
                    p->index[i]);
                retval = -NVL_BAD_ARGS;
                break;
        }
    }

    return retval;
}

NvlStatus
nvswitch_set_nport_port_config_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_SWITCH_PORT_CONFIG *p
)
{
    NvU32   val;

    if (p->requesterLinkID > DRF_MASK(NV_NPORT_REQLINKID_REQROUTINGID))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Invalid requester RID 0x%x\n",
            __FUNCTION__, p->requesterLinkID);
        return -NVL_BAD_ARGS;
    }

    if (p->requesterLanID > DRF_MASK(NV_NPORT_REQLINKID_REQROUTINGLAN))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Invalid requester RLAN 0x%x\n",
            __FUNCTION__, p->requesterLanID);
        return -NVL_BAD_ARGS;
    }

    val = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _NPORT, _CTRL);
    switch (p->type)
    {
        case CONNECT_ACCESS_GPU:
        case CONNECT_ACCESS_CPU:
        case CONNECT_ACCESS_SWITCH:
            val = FLD_SET_DRF(_NPORT, _CTRL, _TRUNKLINKENB, _ACCESSLINK, val);
            break;
        case CONNECT_TRUNK_SWITCH:
            val = FLD_SET_DRF(_NPORT, _CTRL, _TRUNKLINKENB, _TRUNKLINK, val);
            break;
        default:
            NVSWITCH_PRINT(device, ERROR,
                "%s: invalid type #%d\n",
                __FUNCTION__, p->type);
            return -NVL_BAD_ARGS;
    }

    switch(p->count)
    {
        case CONNECT_COUNT_512:
            val = FLD_SET_DRF(_NPORT, _CTRL, _ENDPOINT_COUNT, _512, val);
            break;
        case CONNECT_COUNT_1024:
            val = FLD_SET_DRF(_NPORT, _CTRL, _ENDPOINT_COUNT, _1024, val);
            break;
        case CONNECT_COUNT_2048:
            val = FLD_SET_DRF(_NPORT, _CTRL, _ENDPOINT_COUNT, _2048, val);
            break;
        default:
            NVSWITCH_PRINT(device, ERROR,
                "%s: invalid count #%d\n",
                __FUNCTION__, p->count);
            return -NVL_BAD_ARGS;
    }
    NVSWITCH_LINK_WR32(device, p->portNum, NPORT, _NPORT, _CTRL, val);

    NVSWITCH_LINK_WR32(device, p->portNum, NPORT, _NPORT, _REQLINKID,
        DRF_NUM(_NPORT, _REQLINKID, _REQROUTINGID, p->requesterLinkID) |
        DRF_NUM(_NPORT, _REQLINKID, _REQROUTINGLAN, p->requesterLanID));

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_set_switch_port_config_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_SWITCH_PORT_CONFIG *p
)
{
    nvlink_link *link;
    NvU32 val;
    NvlStatus status;

    if (!NVSWITCH_IS_LINK_ENG_VALID(device, p->portNum, NPORT))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: invalid link #%d\n",
            __FUNCTION__, p->portNum);
        return -NVL_BAD_ARGS;
    }

    if (p->enableVC1 && (p->type != CONNECT_TRUNK_SWITCH))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: VC1 only allowed on trunk links\n",
            __FUNCTION__);
        return -NVL_BAD_ARGS;
    }

    // Validate chip-specific NPORT settings and program port config settings.
    status = nvswitch_set_nport_port_config(device, p);
    if (status != NVL_SUCCESS)
    {
        return status;
    }

    link = nvswitch_get_link(device, (NvU8)p->portNum);
    if (link == NULL)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: invalid link\n",
            __FUNCTION__);
        return -NVL_ERR_INVALID_STATE;
    }

    //
    // If ac_coupled_mask is configured during nvswitch_create_link,
    // give preference to it.
    //
    if (device->regkeys.ac_coupled_mask  ||
        device->regkeys.ac_coupled_mask2 ||
        device->firmware.nvlink.link_ac_coupled_mask)
    {
        if (link->ac_coupled != p->acCoupled)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: port[%d]: Unsupported AC coupled change (%s)\n",
                __FUNCTION__, p->portNum, p->acCoupled ? "AC" : "DC");
            return -NVL_BAD_ARGS;
        }
    }

    link->ac_coupled = p->acCoupled;

    // AC vs DC mode SYSTEM register
    if (link->ac_coupled)
    {
        //
        // In NVL3.0, ACMODE is handled by MINION in the INITPHASE1 command
        // Here we just setup the register with the proper info
        //
        val = NVSWITCH_LINK_RD32_LR10(device, link->linkNumber, NVLIPT_LNK,
                _NVLIPT_LNK, _CTRL_SYSTEM_LINK_CHANNEL_CTRL);
        val = FLD_SET_DRF(_NVLIPT_LNK,
                _CTRL_SYSTEM_LINK_CHANNEL_CTRL, _AC_DC_MODE, _AC, val);
        NVSWITCH_LINK_WR32_LR10(device, link->linkNumber, NVLIPT_LNK,
                _NVLIPT_LNK, _CTRL_SYSTEM_LINK_CHANNEL_CTRL, val);
    }

    // If _BUFFER_RDY is asserted, credits are locked.
    val = NVSWITCH_LINK_RD32_LR10(device, p->portNum, NPORT, _NPORT, _CTRL_BUFFER_READY);
    if (FLD_TEST_DRF(_NPORT, _CTRL_BUFFER_READY, _BUFFERRDY, _ENABLE, val))
    {
        NVSWITCH_PRINT(device, SETUP,
            "%s: port[%d]: BUFFERRDY already enabled.\n",
            __FUNCTION__, p->portNum);
        return NVL_SUCCESS;
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_set_ingress_request_table_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_INGRESS_REQUEST_TABLE *p
)
{
    return -NVL_ERR_NOT_SUPPORTED;
}

NvlStatus
nvswitch_ctrl_get_ingress_request_table_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_INGRESS_REQUEST_TABLE_PARAMS *params
)
{
    return -NVL_ERR_NOT_SUPPORTED;
}

NvlStatus
nvswitch_ctrl_set_ingress_request_valid_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_INGRESS_REQUEST_VALID *p
)
{
    return -NVL_ERR_NOT_SUPPORTED;
}

NvlStatus
nvswitch_ctrl_get_ingress_response_table_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_INGRESS_RESPONSE_TABLE_PARAMS *params
)
{
    return -NVL_ERR_NOT_SUPPORTED;
}


NvlStatus
nvswitch_ctrl_set_ingress_response_table_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_INGRESS_RESPONSE_TABLE *p
)
{
    return -NVL_ERR_NOT_SUPPORTED;
}

static NvlStatus
nvswitch_ctrl_set_ganged_link_table_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_GANGED_LINK_TABLE *p
)
{
    return -NVL_ERR_NOT_SUPPORTED;
}

static NvlStatus
nvswitch_ctrl_get_internal_latency_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_INTERNAL_LATENCY *pLatency
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);
    NvU32 vc_selector = pLatency->vc_selector;
    NvU32 idx_nport;

    // Validate VC selector
    if (vc_selector >= NVSWITCH_NUM_VCS_LR10)
    {
        return -NVL_BAD_ARGS;
    }

    nvswitch_os_memset(pLatency, 0, sizeof(*pLatency));
    pLatency->vc_selector = vc_selector;

    for (idx_nport=0; idx_nport < NVSWITCH_LINK_COUNT(device); idx_nport++)
    {
        if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, idx_nport))
        {
            continue;
        }

        pLatency->egressHistogram[idx_nport].low =
            chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].low;
        pLatency->egressHistogram[idx_nport].medium =
            chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].medium;
        pLatency->egressHistogram[idx_nport].high =
           chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].high;
        pLatency->egressHistogram[idx_nport].panic =
           chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].panic;
        pLatency->egressHistogram[idx_nport].count =
           chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].count;
    }

    pLatency->elapsed_time_msec =
      (chip_device->latency_stats->latency[vc_selector].last_read_time_nsec -
       chip_device->latency_stats->latency[vc_selector].start_time_nsec)/1000000ULL;

    chip_device->latency_stats->latency[vc_selector].start_time_nsec =
        chip_device->latency_stats->latency[vc_selector].last_read_time_nsec;

    chip_device->latency_stats->latency[vc_selector].count = 0;

    // Clear accum_latency[]
    for (idx_nport = 0; idx_nport < NVSWITCH_LINK_COUNT(device); idx_nport++)
    {
        chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].low = 0;
        chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].medium = 0;
        chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].high = 0;
        chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].panic = 0;
        chip_device->latency_stats->latency[vc_selector].accum_latency[idx_nport].count = 0;
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_set_latency_bins_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_LATENCY_BINS *pLatency
)
{
    NvU32 vc_selector;
    const NvU32 freq_mhz = 1330;
    const NvU32 switchpll_hz = freq_mhz * 1000000ULL; // TODO: Update this with device->switch_pll.freq_khz after LR10 PLL update
    const NvU32 min_threshold = 10;   // Must be > zero to avoid div by zero
    const NvU32 max_threshold = 10000;

    // Quick input validation and ns to register value conversion
    for (vc_selector = 0; vc_selector < NVSWITCH_NUM_VCS_LR10; vc_selector++)
    {
        if ((pLatency->bin[vc_selector].lowThreshold > max_threshold)                           ||
            (pLatency->bin[vc_selector].lowThreshold < min_threshold)                           ||
            (pLatency->bin[vc_selector].medThreshold > max_threshold)                           ||
            (pLatency->bin[vc_selector].medThreshold < min_threshold)                           ||
            (pLatency->bin[vc_selector].hiThreshold  > max_threshold)                           ||
            (pLatency->bin[vc_selector].hiThreshold  < min_threshold)                           ||
            (pLatency->bin[vc_selector].lowThreshold > pLatency->bin[vc_selector].medThreshold) ||
            (pLatency->bin[vc_selector].medThreshold > pLatency->bin[vc_selector].hiThreshold))
        {
            return -NVL_BAD_ARGS;
        }

        pLatency->bin[vc_selector].lowThreshold =
            switchpll_hz / (1000000000 / pLatency->bin[vc_selector].lowThreshold);
        pLatency->bin[vc_selector].medThreshold =
            switchpll_hz / (1000000000 / pLatency->bin[vc_selector].medThreshold);
        pLatency->bin[vc_selector].hiThreshold =
            switchpll_hz / (1000000000 / pLatency->bin[vc_selector].hiThreshold);

        NVSWITCH_PORTSTAT_BCAST_WR32_LR10(device, _LIMIT, _LOW,    vc_selector, pLatency->bin[vc_selector].lowThreshold);
        NVSWITCH_PORTSTAT_BCAST_WR32_LR10(device, _LIMIT, _MEDIUM, vc_selector, pLatency->bin[vc_selector].medThreshold);
        NVSWITCH_PORTSTAT_BCAST_WR32_LR10(device, _LIMIT, _HIGH,   vc_selector, pLatency->bin[vc_selector].hiThreshold);
    }

    return NVL_SUCCESS;
}

#define NV_NPORT_REQLINKID_REQROUTINGLAN_1024  18:18
#define NV_NPORT_REQLINKID_REQROUTINGLAN_2048  18:17

/*
 * @brief Returns the ingress requester link id.
 *
 * On LR10, REQROUTINGID only gives the endpoint but not the specific port of the response packet.
 * To identify the specific port, the routing_ID must be appended with the upper bits of REQROUTINGLAN.
 *
 * When NV_NPORT_CTRL_ENDPOINT_COUNT = 1024, the upper bit of NV_NPORT_REQLINKID_REQROUTINGLAN become REQROUTINGID[9].
 * When NV_NPORT_CTRL_ENDPOINT_COUNT = 2048, the upper two bits of NV_NPORT_REQLINKID_REQROUTINGLAN become REQROUTINGID[10:9].
 *
 * @param[in] device            nvswitch device
 * @param[in] params            NVSWITCH_GET_INGRESS_REQLINKID_PARAMS
 *
 * @returns                     NVL_SUCCESS if action succeeded,
 *                              -NVL_ERR_INVALID_STATE invalid link
 */
NvlStatus
nvswitch_ctrl_get_ingress_reqlinkid_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_INGRESS_REQLINKID_PARAMS *params
)
{
    NvU32 regval;
    NvU32 reqRid;
    NvU32 reqRlan;
    NvU32 rlan_shift = DRF_SHIFT_RT(NV_NPORT_REQLINKID_REQROUTINGID) + 1;

    if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, params->portNum))
    {
        return -NVL_BAD_ARGS;
    }

    regval = NVSWITCH_NPORT_RD32_LR10(device, params->portNum, _NPORT, _REQLINKID);
    reqRid = DRF_VAL(_NPORT, _REQLINKID, _REQROUTINGID, regval);
    reqRlan = regval;

    regval = NVSWITCH_NPORT_RD32_LR10(device, params->portNum, _NPORT, _CTRL);
    if (FLD_TEST_DRF(_NPORT, _CTRL, _ENDPOINT_COUNT, _1024, regval))
    {
        reqRlan = DRF_VAL(_NPORT, _REQLINKID, _REQROUTINGLAN_1024, reqRlan);
        params->requesterLinkID = (reqRid | (reqRlan << rlan_shift));
    }
    else if (FLD_TEST_DRF(_NPORT, _CTRL, _ENDPOINT_COUNT, _2048, regval))
    {
        reqRlan = DRF_VAL(_NPORT, _REQLINKID, _REQROUTINGLAN_2048, reqRlan);
        params->requesterLinkID = (reqRid | (reqRlan << rlan_shift));
    }
    else
    {
        params->requesterLinkID = reqRid;
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_get_bios_info_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_BIOS_INFO_PARAMS *p
)
{
    NvU32 biosVersionBytes;
    NvU32 biosOemVersionBytes;
    NvU32 biosMagic = 0x9210;

    //
    // Example: 92.10.09.00.00 is the formatted version string
    //          |         |  |
    //          |         |  |__ BIOS OEM version byte
    //          |         |
    //          |_________|_____ BIOS version bytes
    //
    biosVersionBytes = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW_SW, _SCRATCH_6);
    biosOemVersionBytes = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW_SW, _SCRATCH_7);

    //
    // LR10 is built out of core92 and the BIOS version will always begin with
    // 92.10.xx.xx.xx
    //
    if ((biosVersionBytes >> 16) != biosMagic)
    {
        NVSWITCH_PRINT(device, ERROR,
                "BIOS version not found in scratch register\n");
        return -NVL_ERR_INVALID_STATE;
    }

    p->version = (((NvU64)biosVersionBytes) << 8) | (biosOemVersionBytes & 0xff);

    return NVL_SUCCESS;
}

static void
_nvlink_clear_corelib_state
(
    nvlink_link *link
)
{
    // Receiver Detect needs to happen again
    link->bRxDetected = NV_FALSE;

    // INITNEGOTIATE needs to happen again
    link->bInitnegotiateConfigGood = NV_FALSE;

    // TxCommonMode needs to happen again
    link->bTxCommonModeFail = NV_FALSE;

    // SAFE transition needs to happen again
    link->bSafeTransitionFail = NV_FALSE;

    // Reset the SW state tracking the link and sublink states
    link->state            = NVLINK_LINKSTATE_OFF;
    link->tx_sublink_state = NVLINK_SUBLINK_STATE_TX_OFF;
    link->rx_sublink_state = NVLINK_SUBLINK_STATE_RX_OFF;
}

const static NvU32 nport_reg_addr[] =
{
    NV_NPORT_CTRL,
    NV_NPORT_CTRL_SLCG,
    NV_NPORT_REQLINKID,
    NV_NPORT_PORTSTAT_CONTROL,
    NV_NPORT_PORTSTAT_SNAP_CONTROL,
    NV_NPORT_PORTSTAT_WINDOW_LIMIT,
    NV_NPORT_PORTSTAT_LIMIT_LOW_0,
    NV_NPORT_PORTSTAT_LIMIT_MEDIUM_0,
    NV_NPORT_PORTSTAT_LIMIT_HIGH_0,
    NV_NPORT_PORTSTAT_LIMIT_LOW_1,
    NV_NPORT_PORTSTAT_LIMIT_MEDIUM_1,
    NV_NPORT_PORTSTAT_LIMIT_HIGH_1,
    NV_NPORT_PORTSTAT_LIMIT_LOW_2,
    NV_NPORT_PORTSTAT_LIMIT_MEDIUM_2,
    NV_NPORT_PORTSTAT_LIMIT_HIGH_2,
    NV_NPORT_PORTSTAT_LIMIT_LOW_3,
    NV_NPORT_PORTSTAT_LIMIT_MEDIUM_3,
    NV_NPORT_PORTSTAT_LIMIT_HIGH_3,
    NV_NPORT_PORTSTAT_LIMIT_LOW_4,
    NV_NPORT_PORTSTAT_LIMIT_MEDIUM_4,
    NV_NPORT_PORTSTAT_LIMIT_HIGH_4,
    NV_NPORT_PORTSTAT_LIMIT_LOW_5,
    NV_NPORT_PORTSTAT_LIMIT_MEDIUM_5,
    NV_NPORT_PORTSTAT_LIMIT_HIGH_5,
    NV_NPORT_PORTSTAT_LIMIT_LOW_6,
    NV_NPORT_PORTSTAT_LIMIT_MEDIUM_6,
    NV_NPORT_PORTSTAT_LIMIT_HIGH_6,
    NV_NPORT_PORTSTAT_LIMIT_LOW_7,
    NV_NPORT_PORTSTAT_LIMIT_MEDIUM_7,
    NV_NPORT_PORTSTAT_LIMIT_HIGH_7,
    NV_NPORT_PORTSTAT_SOURCE_FILTER_0,
    NV_NPORT_PORTSTAT_SOURCE_FILTER_1,
    NV_ROUTE_ROUTE_CONTROL,
    NV_ROUTE_CMD_ROUTE_TABLE0,
    NV_ROUTE_CMD_ROUTE_TABLE1,
    NV_ROUTE_CMD_ROUTE_TABLE2,
    NV_ROUTE_CMD_ROUTE_TABLE3,
    NV_ROUTE_ERR_LOG_EN_0,
    NV_ROUTE_ERR_CONTAIN_EN_0,
    NV_ROUTE_ERR_ECC_CTRL,
    NV_ROUTE_ERR_GLT_ECC_ERROR_COUNTER_LIMIT,
    NV_ROUTE_ERR_NVS_ECC_ERROR_COUNTER_LIMIT,
    NV_INGRESS_ERR_LOG_EN_0,
    NV_INGRESS_ERR_CONTAIN_EN_0,
    NV_INGRESS_ERR_ECC_CTRL,
    NV_INGRESS_ERR_REMAPTAB_ECC_ERROR_COUNTER_LIMIT,
    NV_INGRESS_ERR_RIDTAB_ECC_ERROR_COUNTER_LIMIT,
    NV_INGRESS_ERR_RLANTAB_ECC_ERROR_COUNTER_LIMIT,
    NV_INGRESS_ERR_NCISOC_HDR_ECC_ERROR_COUNTER_LIMIT,
    NV_EGRESS_CTRL,
    NV_EGRESS_CTO_TIMER_LIMIT,
    NV_EGRESS_ERR_LOG_EN_0,
    NV_EGRESS_ERR_CONTAIN_EN_0,
    NV_EGRESS_ERR_ECC_CTRL,
    NV_EGRESS_ERR_NXBAR_ECC_ERROR_COUNTER_LIMIT,
    NV_EGRESS_ERR_RAM_OUT_ECC_ERROR_COUNTER_LIMIT,
    NV_TSTATE_TAGSTATECONTROL,
    NV_TSTATE_ATO_TIMER_LIMIT,
    NV_TSTATE_CREQ_CAM_LOCK,
    NV_TSTATE_ERR_LOG_EN_0,
    NV_TSTATE_ERR_CONTAIN_EN_0,
    NV_TSTATE_ERR_ECC_CTRL,
    NV_TSTATE_ERR_CRUMBSTORE_ECC_ERROR_COUNTER_LIMIT,
    NV_TSTATE_ERR_TAGPOOL_ECC_ERROR_COUNTER_LIMIT,
    NV_TSTATE_ERR_TD_TID_RAM_ECC_ERROR_COUNTER_LIMIT,
    NV_SOURCETRACK_CTRL,
    NV_SOURCETRACK_MULTISEC_TIMER0,
    NV_SOURCETRACK_ERR_LOG_EN_0,
    NV_SOURCETRACK_ERR_CONTAIN_EN_0,
    NV_SOURCETRACK_ERR_ECC_CTRL,
    NV_SOURCETRACK_ERR_CREQ_TCEN0_CRUMBSTORE_ECC_ERROR_COUNTER_LIMIT,
    NV_SOURCETRACK_ERR_CREQ_TCEN0_TD_CRUMBSTORE_ECC_ERROR_COUNTER_LIMIT,
    NV_SOURCETRACK_ERR_CREQ_TCEN1_CRUMBSTORE_ECC_ERROR_COUNTER_LIMIT,
};

/*
 *  Disable interrupts comming from NPG & NVLW blocks.
 */
static void
_nvswitch_link_disable_interrupts_lr10
(
    nvswitch_device *device,
    NvU32 link
)
{
    NvU32 i;

    NVSWITCH_NPORT_WR32_LR10(device, link, _NPORT, _ERR_CONTROL_COMMON_NPORT,
        DRF_NUM(_NPORT, _ERR_CONTROL_COMMON_NPORT, _CORRECTABLEENABLE, 0x0) |
        DRF_NUM(_NPORT, _ERR_CONTROL_COMMON_NPORT, _FATALENABLE, 0x0) |
        DRF_NUM(_NPORT, _ERR_CONTROL_COMMON_NPORT, _NONFATALENABLE, 0x0));

    for (i = 0; i < NV_NVLCTRL_LINK_INTR_0_STATUS__SIZE_1; i++)
    {
        NVSWITCH_LINK_WR32_LR10(device, link, NVLW, _NVLCTRL, _LINK_INTR_0_MASK(i),
            DRF_NUM(_NVLCTRL, _LINK_INTR_0_MASK, _FATAL, 0x0) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_0_MASK, _NONFATAL, 0x0) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_0_MASK, _CORRECTABLE, 0x0));

        NVSWITCH_LINK_WR32_LR10(device, link, NVLW, _NVLCTRL, _LINK_INTR_1_MASK(i),
            DRF_NUM(_NVLCTRL, _LINK_INTR_1_MASK, _FATAL, 0x0) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_1_MASK, _NONFATAL, 0x0) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_1_MASK, _CORRECTABLE, 0x0));

        NVSWITCH_LINK_WR32_LR10(device, link, NVLW, _NVLCTRL, _LINK_INTR_2_MASK(i),
            DRF_NUM(_NVLCTRL, _LINK_INTR_2_MASK, _FATAL, 0x0) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_2_MASK, _NONFATAL, 0x0) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_2_MASK, _CORRECTABLE, 0x0));
    }
}

/*
 *  Reset NPG & NVLW interrupt state.
 */
static void
_nvswitch_link_reset_interrupts_lr10
(
    nvswitch_device *device,
    NvU32 link
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);
    NvU32 i;

    NVSWITCH_NPORT_WR32_LR10(device, link, _NPORT, _ERR_CONTROL_COMMON_NPORT,
        DRF_NUM(_NPORT, _ERR_CONTROL_COMMON_NPORT, _CORRECTABLEENABLE, 0x1) |
        DRF_NUM(_NPORT, _ERR_CONTROL_COMMON_NPORT, _FATALENABLE, 0x1) |
        DRF_NUM(_NPORT, _ERR_CONTROL_COMMON_NPORT, _NONFATALENABLE, 0x1));

    for (i = 0; i < NV_NVLCTRL_LINK_INTR_0_STATUS__SIZE_1; i++)
    {
        NVSWITCH_LINK_WR32_LR10(device, link, NVLW, _NVLCTRL, _LINK_INTR_0_MASK(i),
            DRF_NUM(_NVLCTRL, _LINK_INTR_0_MASK, _FATAL, 0x1) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_0_MASK, _NONFATAL, 0x1) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_0_MASK, _CORRECTABLE, 0x1));

        NVSWITCH_LINK_WR32_LR10(device, link, NVLW, _NVLCTRL, _LINK_INTR_1_MASK(i),
            DRF_NUM(_NVLCTRL, _LINK_INTR_1_MASK, _FATAL, 0x1) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_1_MASK, _NONFATAL, 0x1) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_1_MASK, _CORRECTABLE, 0x1));

        NVSWITCH_LINK_WR32_LR10(device, link, NVLW, _NVLCTRL, _LINK_INTR_2_MASK(i),
            DRF_NUM(_NVLCTRL, _LINK_INTR_2_MASK, _FATAL, 0x1) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_2_MASK, _NONFATAL, 0x1) |
            DRF_NUM(_NVLCTRL, _LINK_INTR_2_MASK, _CORRECTABLE, 0x1));
    }

    // Enable interrupts which are disabled to prevent interrupt storm.
    NVSWITCH_NPORT_WR32_LR10(device, link, _ROUTE, _ERR_FATAL_REPORT_EN_0, chip_device->intr_mask.route.fatal);
    NVSWITCH_NPORT_WR32_LR10(device, link, _ROUTE, _ERR_NON_FATAL_REPORT_EN_0, chip_device->intr_mask.route.nonfatal);
    NVSWITCH_NPORT_WR32_LR10(device, link, _INGRESS, _ERR_FATAL_REPORT_EN_0, chip_device->intr_mask.ingress.fatal);
    NVSWITCH_NPORT_WR32_LR10(device, link, _INGRESS, _ERR_NON_FATAL_REPORT_EN_0, chip_device->intr_mask.ingress.nonfatal);
    NVSWITCH_NPORT_WR32_LR10(device, link, _EGRESS, _ERR_FATAL_REPORT_EN_0, chip_device->intr_mask.egress.fatal);
    NVSWITCH_NPORT_WR32_LR10(device, link, _EGRESS, _ERR_NON_FATAL_REPORT_EN_0, chip_device->intr_mask.egress.nonfatal);
    NVSWITCH_NPORT_WR32_LR10(device, link, _TSTATE, _ERR_FATAL_REPORT_EN_0, chip_device->intr_mask.tstate.fatal);
    NVSWITCH_NPORT_WR32_LR10(device, link, _TSTATE, _ERR_NON_FATAL_REPORT_EN_0, chip_device->intr_mask.tstate.nonfatal);
    NVSWITCH_NPORT_WR32_LR10(device, link, _SOURCETRACK, _ERR_FATAL_REPORT_EN_0, chip_device->intr_mask.sourcetrack.fatal);
    NVSWITCH_NPORT_WR32_LR10(device, link, _SOURCETRACK, _ERR_NON_FATAL_REPORT_EN_0, chip_device->intr_mask.sourcetrack.nonfatal);

    // Clear fatal error status
    device->link[link].fatal_error_occurred = NV_FALSE;
}

/*
 * @Brief : Control to reset and drain the links.
 *
 * @param[in] device        A reference to the device to initialize
 * @param[in] linkMask      A mask of link(s) to be reset.
 *
 * @returns :               NVL_SUCCESS if there were no errors
 *                         -NVL_BAD_PARAMS if input parameters are wrong.
 *                         -NVL_ERR_INVALID_STATE if other errors are present and a full-chip reset is required.
 *                         -NVL_INITIALIZATION_TOTAL_FAILURE if NPORT initialization failed and a retry is required.
 */

NvlStatus
nvswitch_reset_and_drain_links_lr10
(
    nvswitch_device *device,
    NvU64 link_mask
)
{
    NvlStatus status = -NVL_ERR_GENERIC;
    nvlink_link *link_info;
    NvU32 val;
    NvU32 link;
    NvU32 idx_nport;
    NvU32 npg;
    NVSWITCH_TIMEOUT timeout;
    NvBool           keepPolling;
    NvU32 i;
    NvU64 link_mode, tx_sublink_mode, rx_sublink_mode;
    NvU32 tx_sublink_submode, rx_sublink_submode;
    NvU32 *nport_reg_val = NULL;
    NvU32 reg_count = NV_ARRAY_ELEMENTS(nport_reg_addr);
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);

    if ((link_mask == 0) ||
        (link_mask >> NVSWITCH_LINK_COUNT(device)))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Invalid link_mask = 0x%llx\n",
            __FUNCTION__, link_mask);

        return -NVL_BAD_ARGS;
    }

    // Check for in-active links
    FOR_EACH_INDEX_IN_MASK(64, link, link_mask)
    {
        if (!nvswitch_is_link_valid(device, link))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: link #%d invalid\n",
                __FUNCTION__, link);

            continue;
        }
        if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NPORT, link))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: NPORT #%d invalid\n",
                __FUNCTION__, link);

            continue;
        }

        if (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLW, link))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: NVLW #%d invalid\n",
                __FUNCTION__, link);

            continue;
        }
    }
    FOR_EACH_INDEX_IN_MASK_END;

    // Buffer to backup NPORT state
    nport_reg_val = nvswitch_os_malloc(sizeof(nport_reg_addr));
    if (nport_reg_val == NULL)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Failed to allocate memory\n",
            __FUNCTION__);

        return -NVL_NO_MEM;
    }

    FOR_EACH_INDEX_IN_MASK(64, link, link_mask)
    {
        // Unregister links to make them unusable while reset is in progress.
        link_info = nvswitch_get_link(device, link);
        if (link_info == NULL)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: invalid link %d\n",
                __FUNCTION__, link);
            continue;
        }

        nvlink_lib_unregister_link(link_info);

        //
        // Step 0 :
        // Prior to starting port reset, FM must shutdown the NVlink links
        // it wishes to reset.
        // However, with shared-virtualization, FM is unable to shut down the links
        // since the GPU is no longer attached to the service VM.
        // In this case, we must perform unilateral shutdown on the LR10 side
        // of the link.
        //
        // If links are in OFF or RESET, we don't need to perform shutdown
        // If links already went through a proper pseudo-clean shutdown sequence,
        // they'll be in SAFE + sublinks in OFF
        //

        status = nvswitch_corelib_get_dl_link_mode_lr10(link_info, &link_mode);
        if (status != NVL_SUCCESS)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: Unable to get link mode from link %d\n",
                __FUNCTION__, link);
            goto nvswitch_reset_and_drain_links_exit;
        }
        status = nvswitch_corelib_get_tx_mode_lr10(link_info, &tx_sublink_mode, &tx_sublink_submode);
        if (status != NVL_SUCCESS)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: Unable to get tx sublink mode from link %d\n",
                __FUNCTION__, link);
            goto nvswitch_reset_and_drain_links_exit;
        }
        status = nvswitch_corelib_get_rx_mode_lr10(link_info, &rx_sublink_mode, &rx_sublink_submode);
        if (status != NVL_SUCCESS)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: Unable to get rx sublink mode from link %d\n",
                __FUNCTION__, link);
            goto nvswitch_reset_and_drain_links_exit;
        }

        if (!((link_mode == NVLINK_LINKSTATE_RESET) ||
              (link_mode == NVLINK_LINKSTATE_OFF) ||
              ((link_mode == NVLINK_LINKSTATE_SAFE) &&
               (tx_sublink_mode == NVLINK_SUBLINK_STATE_TX_OFF) &&
               (rx_sublink_mode == NVLINK_SUBLINK_STATE_RX_OFF))))
        {
            nvswitch_execute_unilateral_link_shutdown_lr10(link_info);
            _nvlink_clear_corelib_state(link_info);
        }

        //
        // Step 1 : Perform surgical reset
        // Refer to switch IAS 11.5.2 Link Reset.
        //

        // Step 1.a : Backup NPORT state before reset
        for (i = 0; i < reg_count; i++)
        {
            nport_reg_val[i] = NVSWITCH_ENG_OFF_RD32(device, NPORT, _UNICAST, link,
                nport_reg_addr[i]);
        }

        // Step 1.b : Assert INGRESS_STOP / EGRESS_STOP
        val = NVSWITCH_NPORT_RD32_LR10(device, link, _NPORT, _CTRL_STOP);
        val = FLD_SET_DRF(_NPORT, _CTRL_STOP, _INGRESS_STOP, _STOP, val);
        val = FLD_SET_DRF(_NPORT, _CTRL_STOP, _EGRESS_STOP, _STOP, val);
        NVSWITCH_NPORT_WR32_LR10(device, link, _NPORT, _CTRL_STOP, val);

        // Wait for stop operation to take effect at TLC.
        // Expected a minimum of 256 clk cycles.
        nvswitch_os_sleep(1);

        //
        // Step 1.c : Disable NPG & NVLW interrupts
        //
        _nvswitch_link_disable_interrupts_lr10(device, link);

        // Step 1.d : Assert NPortWarmReset
        npg = link / NVSWITCH_LINKS_PER_NPG;
        val = NVSWITCH_NPG_RD32_LR10(device, npg, _NPG, _WARMRESET);

        idx_nport = link % NVSWITCH_LINKS_PER_NPG;
        NVSWITCH_NPG_WR32_LR10(device, npg, _NPG, _WARMRESET,
            DRF_NUM(_NPG, _WARMRESET, _NPORTWARMRESET, ~NVBIT(idx_nport)));

        // Step 1.e : Initiate Minion reset sequence.
        status = nvswitch_request_tl_link_state_lr10(link_info,
            NV_NVLIPT_LNK_CTRL_LINK_STATE_REQUEST_REQUEST_RESET, NV_TRUE);
        if (status != NVL_SUCCESS)
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: NvLink Reset has failed for link %d\n",
                __FUNCTION__, link);
            goto nvswitch_reset_and_drain_links_exit;
        }

        // Step 1.e : De-assert NPortWarmReset
        NVSWITCH_NPG_WR32_LR10(device, npg, _NPG, _WARMRESET, val);

        // Step 1.f : Assert and De-assert NPort debug_clear
        // to clear the error status
        NVSWITCH_NPG_WR32_LR10(device, npg, _NPG, _DEBUG_CLEAR,
            DRF_NUM(_NPG, _DEBUG_CLEAR, _CLEAR, NVBIT(idx_nport)));

        NVSWITCH_NPG_WR32_LR10(device, npg, _NPG, _DEBUG_CLEAR,
            DRF_DEF(_NPG, _DEBUG_CLEAR, _CLEAR, _DEASSERT));

        // Step 1.g : Clear CONTAIN_AND_DRAIN to clear contain state (Bug 3115824)
        NVSWITCH_NPORT_WR32_LR10(device, link, _NPORT, _CONTAIN_AND_DRAIN,
            DRF_DEF(_NPORT, _CONTAIN_AND_DRAIN, _CLEAR, _ENABLE));

        val = NVSWITCH_NPORT_RD32_LR10(device, link, _NPORT, _CONTAIN_AND_DRAIN);
        if (FLD_TEST_DRF(_NPORT, _CONTAIN_AND_DRAIN, _CLEAR, _ENABLE, val))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: NPORT Contain and Drain Clear has failed for link %d\n",
                __FUNCTION__, link);
            status = NVL_ERR_INVALID_STATE;
            goto nvswitch_reset_and_drain_links_exit;
        }

        //
        // Step 2 : Assert NPORT Reset after Control & Drain routine.
        //  Clear Tagpool, CrumbStore and CAM RAMs
        //

        // Step 2.a Clear Tagpool RAM
        NVSWITCH_NPORT_WR32_LR10(device, link, _NPORT, _INITIALIZATION,
            DRF_DEF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_0, _HWINIT));

        nvswitch_timeout_create(25 * NVSWITCH_INTERVAL_1MSEC_IN_NS, &timeout);

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

            // Check if NPORT initialization is done
            val = NVSWITCH_NPORT_RD32_LR10(device, link, _NPORT, _INITIALIZATION);
            if (FLD_TEST_DRF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_0, _HWINIT, val))
            {
                break;
            }

            nvswitch_os_sleep(1);
        }
        while (keepPolling);

        if (!FLD_TEST_DRF(_NPORT, _INITIALIZATION, _TAGPOOLINIT_0, _HWINIT, val))
        {
            NVSWITCH_PRINT(device, ERROR,
                "%s: Timeout waiting for TAGPOOL Initialization on link %d)\n",
                __FUNCTION__, link);

            status = -NVL_INITIALIZATION_TOTAL_FAILURE;
            goto nvswitch_reset_and_drain_links_exit;
        }

        // Step 2.b Clear CrumbStore RAM
        val = DRF_NUM(_TSTATE, _RAM_ADDRESS, _ADDR, 0) |
              DRF_DEF(_TSTATE, _RAM_ADDRESS, _SELECT, _CRUMBSTORE_RAM) |
              DRF_NUM(_TSTATE, _RAM_ADDRESS, _AUTO_INCR, 1);

        NVSWITCH_NPORT_WR32_LR10(device, link, _TSTATE, _RAM_ADDRESS, val);
        NVSWITCH_NPORT_WR32_LR10(device, link, _TSTATE, _RAM_DATA1, 0x0);

        val = DRF_NUM(_TSTATE, _RAM_DATA0, _ECC, 0x7f);
        for (i = 0; i <= NV_TSTATE_RAM_ADDRESS_ADDR_TAGPOOL_CRUMBSTORE_TDTID_DEPTH; i++)
        {
            NVSWITCH_NPORT_WR32_LR10(device, link, _TSTATE, _RAM_DATA0, val);
        }

        // Step 2.c Clear CAM RAM
        val = DRF_NUM(_TSTATE, _RAM_ADDRESS, _ADDR, 0) |
              DRF_DEF(_TSTATE, _RAM_ADDRESS, _SELECT, _CREQ_CAM) |
              DRF_NUM(_TSTATE, _RAM_ADDRESS, _AUTO_INCR, 1);

        NVSWITCH_NPORT_WR32_LR10(device, link, _TSTATE, _RAM_ADDRESS, val);
        NVSWITCH_NPORT_WR32_LR10(device, link, _TSTATE, _RAM_DATA1, 0x0);
        NVSWITCH_NPORT_WR32_LR10(device, link, _TSTATE, _RAM_DATA2, 0x0);

        for (i = 0; i <= NV_TSTATE_RAM_ADDRESS_ADDR_CREQ_CAM_DEPTH; i++)
        {
            NVSWITCH_NPORT_WR32_LR10(device, link, _TSTATE, _RAM_DATA0, 0x0);
        }

        //
        // Step 3 : Restore link state
        //

        // Restore NPORT state after reset
        for (i = 0; i < reg_count; i++)
        {
            NVSWITCH_ENG_OFF_WR32(device, NPORT, _UNICAST, link,
                                  nport_reg_addr[i], nport_reg_val[i]);
        }

        // Initialize GLT
        nvswitch_set_ganged_link_table_lr10(device, 0, chip_device->ganged_link_table,
                                            ROUTE_GANG_TABLE_SIZE/2);

        // Initialize select scratch registers to 0x0
        nvswitch_init_scratch_lr10(device);

        // Reset NVLW and NPORT interrupt state
        _nvswitch_link_reset_interrupts_lr10(device, link);

        // Re-register links.
        status = nvlink_lib_register_link(device->nvlink_device, link_info);
        if (status != NVL_SUCCESS)
        {
            nvswitch_destroy_link(link_info);
            goto nvswitch_reset_and_drain_links_exit;
        }
    }
    FOR_EACH_INDEX_IN_MASK_END;

nvswitch_reset_and_drain_links_exit:
    nvswitch_os_free(nport_reg_val);
    return status;
}

NvlStatus
nvswitch_get_nvlink_ecc_errors_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_NVLINK_ECC_ERRORS_PARAMS *params
)
{
    NvU32 statData;
    NvU8 i, j;
    NvlStatus status;
    NvBool bLaneReversed;

    nvswitch_os_memset(params->errorLink, 0, sizeof(params->errorLink));

    FOR_EACH_INDEX_IN_MASK(64, i, params->linkMask)
    {
        nvlink_link         *link;
        NVSWITCH_LANE_ERROR *errorLane;
        NvU8                offset;
        NvBool              minion_enabled;
        NvU32               sublinkWidth;

        link = nvswitch_get_link(device, i);
        sublinkWidth = device->hal.nvswitch_get_sublink_width(device, i);

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

        minion_enabled = nvswitch_is_minion_initialized(device,
            NVSWITCH_GET_LINK_ENG_INST(device, link->linkNumber, MINION));

        bLaneReversed = nvswitch_link_lane_reversed_lr10(device, link->linkNumber);

        for (j = 0; j < NVSWITCH_NVLINK_MAX_LANES; j++)
        {
            if (minion_enabled && (j < sublinkWidth))
            {
                status = nvswitch_minion_get_dl_status(device, i,
                                        (NV_NVLSTAT_RX12 + j), 0, &statData);

                if (status != NVL_SUCCESS)
                {
                    return status;
                }
                offset = bLaneReversed ? ((sublinkWidth - 1) - j) : j;
                errorLane                = &params->errorLink[i].errorLane[offset];
                errorLane->valid         = NV_TRUE;
            }
            else
            {
                // MINION disabled
                statData                 = 0;
                offset                   = j;
                errorLane                = &params->errorLink[i].errorLane[offset];
                errorLane->valid         = NV_FALSE;
            }

            errorLane->eccErrorValue = DRF_VAL(_NVLSTAT, _RX12, _ECC_CORRECTED_ERR_L0_VALUE, statData);
            errorLane->overflowed    = DRF_VAL(_NVLSTAT, _RX12, _ECC_CORRECTED_ERR_L0_OVER, statData);
        }
    }
    FOR_EACH_INDEX_IN_MASK_END;

    return NVL_SUCCESS;
}

static NvU32
nvswitch_get_num_links_lr10
(
    nvswitch_device *device
)
{
    NvU32 num_links = NVSWITCH_NUM_LINKS_LR10;
    return num_links;
}

NvBool
nvswitch_is_link_valid_lr10
(
    nvswitch_device *device,
    NvU32            link_id
)
{
    if (link_id >= nvswitch_get_num_links(device))
    {
        return NV_FALSE;
    }
    return device->link[link_id].valid;
}

NvlStatus
nvswitch_ctrl_get_fom_values_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_FOM_VALUES_PARAMS *p
)
{
    NvlStatus status;
    NvU32     statData;

    NVSWITCH_ASSERT(p->linkId < nvswitch_get_num_links(device));

    status = nvswitch_minion_get_dl_status(device, p->linkId,
                                        NV_NVLSTAT_TR16, 0, &statData);
    p->figureOfMeritValues[0] = (NvU16) (statData & 0xFFFF);
    p->figureOfMeritValues[1] = (NvU16) ((statData >> 16) & 0xFFFF);

    status = nvswitch_minion_get_dl_status(device, p->linkId,
                                        NV_NVLSTAT_TR17, 0, &statData);
    p->figureOfMeritValues[2] = (NvU16) (statData & 0xFFFF);
    p->figureOfMeritValues[3] = (NvU16) ((statData >> 16) & 0xFFFF);

    p->numLanes = nvswitch_get_sublink_width(device, p->linkId);

    return status;
}

void
nvswitch_set_fatal_error_lr10
(
    nvswitch_device *device,
    NvBool           device_fatal,
    NvU32            link_id
)
{
    NvU32 reg;

    NVSWITCH_ASSERT(link_id < nvswitch_get_num_links(device));

    // On first fatal error, notify PORT_DOWN
    if (!device->link[link_id].fatal_error_occurred)
    {
        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__);
        }
    }

    device->link[link_id].fatal_error_occurred = NV_TRUE;

    if (device_fatal)
    {
        reg = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW, _SW_SCRATCH_12);
        reg = FLD_SET_DRF_NUM(_NVLSAW, _SW_SCRATCH_12, _DEVICE_RESET_REQUIRED,
                              1, reg);

        NVSWITCH_SAW_WR32_LR10(device, _NVLSAW, _SW_SCRATCH_12, reg);
    }
    else
    {
        reg = NVSWITCH_LINK_RD32_LR10(device, link_id, NPORT, _NPORT, _SCRATCH_WARM);
        reg = FLD_SET_DRF_NUM(_NPORT, _SCRATCH_WARM, _PORT_RESET_REQUIRED,
                              1, reg);

        NVSWITCH_LINK_WR32_LR10(device, link_id, NPORT, _NPORT, _SCRATCH_WARM, reg);
    }
}

static NvU32
nvswitch_get_latency_sample_interval_msec_lr10
(
    nvswitch_device *device
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);
    return chip_device->latency_stats->sample_interval_msec;
}

NvU32
nvswitch_get_swap_clk_default_lr10
(
    nvswitch_device *device
)
{
    return -NVL_ERR_NOT_SUPPORTED;
}

NvBool
nvswitch_is_link_in_use_lr10
(
    nvswitch_device *device,
    NvU32 link_id
)
{
    NvU32 data;
    nvlink_link *link;

    link = nvswitch_get_link(device, link_id);
    if (link == NULL)
    {
        // A query on an invalid link should never occur
        NVSWITCH_ASSERT(link != NULL);
        return NV_FALSE;
    }

    if (nvswitch_is_link_in_reset(device, link))
    {
        return NV_FALSE;
    }

    data = NVSWITCH_LINK_RD32_LR10(device, link_id,
                                   NVLDL, _NVLDL_TOP, _LINK_STATE);

    return (DRF_VAL(_NVLDL_TOP, _LINK_STATE, _STATE, data) !=
            NV_NVLDL_TOP_LINK_STATE_STATE_INIT);
}

static NvU32
nvswitch_get_device_dma_width_lr10
(
    nvswitch_device *device
)
{
    return DMA_ADDR_WIDTH_LR10;
}

NvU32
nvswitch_get_link_ip_version_lr10
(
    nvswitch_device *device,
    NvU32            link_id
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);
    NvU32 nvldl_instance;

    nvldl_instance = NVSWITCH_GET_LINK_ENG_INST(device, link_id, NVLDL);
    if (NVSWITCH_ENG_IS_VALID(device, NVLDL, nvldl_instance))
    {
        return chip_device->engNVLDL[nvldl_instance].version;
    }
    else
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: NVLink[0x%x] NVLDL instance invalid\n",
            __FUNCTION__, link_id);
        return 0;
    }
}

static NvlStatus
nvswitch_test_soe_dma_lr10
(
    nvswitch_device *device
)
{
    return soeTestDma_HAL(device, (PSOE)device->pSoe);
}

static NvlStatus
_nvswitch_get_reserved_throughput_counters
(
    nvswitch_device *device,
    nvlink_link     *link,
    NvU16           counter_mask,
    NvU64           *counter_values
)
{
    NvU16 counter = 0;

    //
    // LR10 to use counters 0 & 2 for monitoring
    // (Same as GPU behavior)
    // Counter 0 counts data flits
    // Counter 2 counts all flits
    //
    FOR_EACH_INDEX_IN_MASK(16, counter, counter_mask)
    {
        NvU32 counter_type = NVBIT(counter);
        NvU64 data = 0;

        switch (counter_type)
        {
            case NVSWITCH_THROUGHPUT_COUNTERS_TYPE_DATA_TX:
            {
                data = nvswitch_read_64bit_counter(device,
                           NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber,
                           NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_LO(0)),
                           NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber,
                           NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_HI(0)));
                break;
            }
            case NVSWITCH_THROUGHPUT_COUNTERS_TYPE_DATA_RX:
            {
                data = nvswitch_read_64bit_counter(device,
                           NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber,
                           NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_LO(0)),
                           NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber,
                           NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_HI(0)));
                break;
            }
            case NVSWITCH_THROUGHPUT_COUNTERS_TYPE_RAW_TX:
            {
                data = nvswitch_read_64bit_counter(device,
                           NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber,
                           NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_LO(2)),
                           NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber,
                           NVLTLC, _NVLTLC_TX_LNK, _DEBUG_TP_CNTR_HI(2)));
                break;
            }
            case NVSWITCH_THROUGHPUT_COUNTERS_TYPE_RAW_RX:
            {
                data = nvswitch_read_64bit_counter(device,
                           NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber,
                           NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_LO(2)),
                           NVSWITCH_LINK_OFFSET_LR10(device, link->linkNumber,
                           NVLTLC, _NVLTLC_RX_LNK, _DEBUG_TP_CNTR_HI(2)));
                break;
            }
            default:
            {
                return -NVL_ERR_NOT_SUPPORTED;
            }
        }
        counter_values[counter] = data;
    }
    FOR_EACH_INDEX_IN_MASK_END;

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_get_throughput_counters_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_THROUGHPUT_COUNTERS_PARAMS *p
)
{
    NvlStatus status;
    nvlink_link *link;
    NvU16 i = 0;

    nvswitch_os_memset(p->counters, 0, sizeof(p->counters));

    FOR_EACH_INDEX_IN_MASK(64, i, p->linkMask)
    {
        link = nvswitch_get_link(device, i);
        if ((link == NULL) || (link->linkNumber >= NVSWITCH_MAX_PORTS) ||
            (!NVSWITCH_IS_LINK_ENG_VALID_LR10(device, NVLTLC, link->linkNumber)))
        {
            continue;
        }

        status = _nvswitch_get_reserved_throughput_counters(device, link, p->counterMask,
                        p->counters[link->linkNumber].values);
        if (status != NVL_SUCCESS)
        {
            NVSWITCH_PRINT(device, ERROR,
                "Failed to get reserved NVLINK throughput counters on link %d\n",
                link->linkNumber);
            return status;
        }
    }
    FOR_EACH_INDEX_IN_MASK_END;

    return NVL_SUCCESS;
}

static NvBool
nvswitch_is_soe_supported_lr10
(
    nvswitch_device *device
)
{
    return NV_TRUE;
}

NvBool
nvswitch_is_inforom_supported_lr10
(
    nvswitch_device *device
)
{
    if (IS_RTLSIM(device) || IS_EMULATION(device) || IS_FMODEL(device))
    {
        NVSWITCH_PRINT(device, INFO,
            "INFOROM is not supported on non-silicon platform\n");
        return NV_FALSE;
    }

    if (!nvswitch_is_soe_supported(device))
    {
        NVSWITCH_PRINT(device, INFO,
            "INFOROM is not supported since SOE is not supported\n");
        return NV_FALSE;
    }

    return NV_TRUE;
}

NvBool
nvswitch_is_spi_supported_lr10
(
    nvswitch_device *device
)
{
    return nvswitch_is_soe_supported(device);
}

NvBool
nvswitch_is_smbpbi_supported_lr10
(
    nvswitch_device *device
)
{
    if (IS_RTLSIM(device) || IS_FMODEL(device))
    {
        NVSWITCH_PRINT(device, INFO,
            "SMBPBI is not supported on RTLSIM/FMODEL platforms\n");
        return NV_FALSE;
    }

    if (!nvswitch_is_soe_supported(device))
    {
        NVSWITCH_PRINT(device, INFO,
            "SMBPBI is not supported since SOE is not supported\n");
        return NV_FALSE;
    }

    return NV_TRUE;
}

/*
 * @Brief : Additional setup needed after device initialization
 *
 * @Description :
 *
 * @param[in] device        a reference to the device to initialize
 */
NvlStatus
nvswitch_post_init_device_setup_lr10
(
    nvswitch_device *device
)
{
    NvlStatus retval;

    if (device->regkeys.soe_dma_self_test ==
            NV_SWITCH_REGKEY_SOE_DMA_SELFTEST_DISABLE)
    {
        NVSWITCH_PRINT(device, INFO,
            "Skipping SOE DMA selftest as requested using regkey\n");
    }
    else if (IS_RTLSIM(device) || IS_FMODEL(device))
    {
        NVSWITCH_PRINT(device, SETUP,
            "Skipping DMA selftest on FMODEL/RTLSIM platforms\n");
    }
    else if (!nvswitch_is_soe_supported(device))
    {
        NVSWITCH_PRINT(device, SETUP,
            "Skipping DMA selftest since SOE is not supported\n");
    }
    else
    {
        retval = nvswitch_test_soe_dma_lr10(device);
        if (retval != NVL_SUCCESS)
        {
            return retval;
        }
    }

    if (nvswitch_is_inforom_supported(device))
    {
        nvswitch_inforom_post_init(device);
    }
    else
    {
        NVSWITCH_PRINT(device, SETUP, "Skipping INFOROM init\n");
    }

    return NVL_SUCCESS;
}

/*
 * @Brief : Additional setup needed after blacklisted device initialization
 *
 * @Description :
 *
 * @param[in] device        a reference to the device to initialize
 */
void
nvswitch_post_init_blacklist_device_setup_lr10
(
    nvswitch_device *device
)
{
    NvlStatus status;

    if (nvswitch_is_inforom_supported(device))
    {
        nvswitch_inforom_post_init(device);
    }

    //
    // Initialize the driver state monitoring callback.
    // This is still needed for SOE to report correct driver state.
    //
    status = nvswitch_smbpbi_post_init(device);
    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR, "Smbpbi post init failed, rc:%d\n",
                       status);
        return;
    }

    //
    // This internally will only flush if OMS value has changed
    //
    status = device->hal.nvswitch_oms_inforom_flush(device);
    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR, "Flushing OMS failed, rc:%d\n",
                       status);
        return;
    }
}

void
nvswitch_load_uuid_lr10
(
    nvswitch_device *device
)
{
    NvU32 regData[4];

    //
    // Read 128-bit UUID from secure scratch registers which must be
    // populated by firmware.
    //
    regData[0] = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW_SW, _SCRATCH_8);
    regData[1] = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW_SW, _SCRATCH_9);
    regData[2] = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW_SW, _SCRATCH_10);
    regData[3] = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW_SW, _SCRATCH_11);

    nvswitch_os_memcpy(&device->uuid.uuid, (NvU8 *)regData, NV_UUID_LEN);
}

NvlStatus
nvswitch_read_oob_blacklist_state_lr10
(
    nvswitch_device *device
)
{
    NvU32 reg;
    NvBool is_oob_blacklist;
    NvlStatus status;

    if (device == NULL)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: Called with invalid argument\n", __FUNCTION__);
        return -NVL_BAD_ARGS;
    }

    reg = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW, _SCRATCH_COLD);

    // Check for uninitialized SCRATCH_COLD before declaring the device blacklisted
    if (reg == NV_NVLSAW_SCRATCH_COLD_DATA_INIT)
        is_oob_blacklist = NV_FALSE;
    else
        is_oob_blacklist = DRF_VAL(_NVLSAW, _SCRATCH_COLD, _OOB_BLACKLIST_DEVICE_REQUESTED, reg);

    status = nvswitch_inforom_oms_set_device_disable(device, is_oob_blacklist);
    if (status != NVL_SUCCESS)
    {
        NVSWITCH_PRINT(device, ERROR,
            "Failed to set device disable to %d, rc:%d\n",
            is_oob_blacklist, status);
    }

    if (is_oob_blacklist)
    {
        device->device_fabric_state = NVSWITCH_DEVICE_FABRIC_STATE_BLACKLISTED;
        device->device_blacklist_reason = NVSWITCH_DEVICE_BLACKLIST_REASON_MANUAL_OUT_OF_BAND;
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_write_fabric_state_lr10
(
    nvswitch_device *device
)
{
    NvU32 reg;

    if (device == NULL)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: Called with invalid argument\n", __FUNCTION__);
        return -NVL_BAD_ARGS;
    }

    // bump the sequence number for each write
    device->fabric_state_sequence_number++;

    reg = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW, _SW_SCRATCH_12);

    reg = FLD_SET_DRF_NUM(_NVLSAW, _SW_SCRATCH_12, _DEVICE_BLACKLIST_REASON,
                          device->device_blacklist_reason, reg);
    reg = FLD_SET_DRF_NUM(_NVLSAW, _SW_SCRATCH_12, _DEVICE_FABRIC_STATE,
                          device->device_fabric_state, reg);
    reg = FLD_SET_DRF_NUM(_NVLSAW, _SW_SCRATCH_12, _DRIVER_FABRIC_STATE,
                          device->driver_fabric_state, reg);
    reg = FLD_SET_DRF_NUM(_NVLSAW, _SW_SCRATCH_12, _EVENT_MESSAGE_COUNT,
                          device->fabric_state_sequence_number, reg);

    NVSWITCH_SAW_WR32_LR10(device, _NVLSAW, _SW_SCRATCH_12, reg);

    return NVL_SUCCESS;
}

static NVSWITCH_ENGINE_DESCRIPTOR_TYPE *
_nvswitch_get_eng_descriptor_lr10
(
    nvswitch_device *device,
    NVSWITCH_ENGINE_ID eng_id
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);
    NVSWITCH_ENGINE_DESCRIPTOR_TYPE  *engine = NULL;

    if (eng_id >= NVSWITCH_ENGINE_ID_SIZE)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Engine_ID 0x%x out of range 0..0x%x\n",
            __FUNCTION__,
            eng_id, NVSWITCH_ENGINE_ID_SIZE-1);
        return NULL;
    }

    engine = &(chip_device->io.common[eng_id]);
    NVSWITCH_ASSERT(eng_id == engine->eng_id);

    return engine;
}

NvU32
nvswitch_get_eng_base_lr10
(
    nvswitch_device *device,
    NVSWITCH_ENGINE_ID eng_id,
    NvU32 eng_bcast,
    NvU32 eng_instance
)
{
    NVSWITCH_ENGINE_DESCRIPTOR_TYPE  *engine;
    NvU32 base_addr = NVSWITCH_BASE_ADDR_INVALID;

    engine = _nvswitch_get_eng_descriptor_lr10(device, eng_id);
    if (engine == NULL)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: ID 0x%x[%d] %s not found\n",
            __FUNCTION__,
            eng_id, eng_instance,
            (
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_UNICAST) ? "UC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_BCAST) ? "BC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_MULTICAST) ? "MC" :
                "??"
            ));
        return NVSWITCH_BASE_ADDR_INVALID;
    }

    if ((eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_UNICAST) &&
        (eng_instance < engine->eng_count))
    {
        base_addr = engine->uc_addr[eng_instance];
    }
    else if (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_BCAST)
    {
        base_addr = engine->bc_addr;
    }
    else if ((eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_MULTICAST) &&
        (eng_instance < engine->mc_addr_count))
    {
        base_addr = engine->mc_addr[eng_instance];
    }
    else
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Unknown address space type 0x%x (not UC, BC, or MC)\n",
            __FUNCTION__,
            eng_bcast);
    }

    if (base_addr == NVSWITCH_BASE_ADDR_INVALID)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: ID 0x%x[%d] %s invalid address\n",
            __FUNCTION__,
            eng_id, eng_instance,
            (
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_UNICAST) ? "UC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_BCAST) ? "BC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_MULTICAST) ? "MC" :
                "??"
            ));
    }

    return base_addr;
}

NvU32
nvswitch_get_eng_count_lr10
(
    nvswitch_device *device,
    NVSWITCH_ENGINE_ID eng_id,
    NvU32 eng_bcast
)
{
    NVSWITCH_ENGINE_DESCRIPTOR_TYPE  *engine;
    NvU32 eng_count = 0;

    engine = _nvswitch_get_eng_descriptor_lr10(device, eng_id);
    if (engine == NULL)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: ID 0x%x %s not found\n",
            __FUNCTION__,
            eng_id,
            (
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_UNICAST) ? "UC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_BCAST) ? "BC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_MULTICAST) ? "MC" :
                "??"
            ));
        return 0;
    }

    if (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_UNICAST)
    {
        eng_count = engine->eng_count;
    }
    else if (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_BCAST)
    {
        if (engine->bc_addr == NVSWITCH_BASE_ADDR_INVALID)
        {
            eng_count = 0;
        }
        else
        {
            eng_count = 1;
        }
    }
    else if (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_MULTICAST)
    {
        eng_count = engine->mc_addr_count;
    }
    else
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Unknown address space type 0x%x (not UC, BC, or MC)\n",
            __FUNCTION__,
            eng_bcast);
    }

    return eng_count;
}

NvU32
nvswitch_eng_rd_lr10
(
    nvswitch_device *device,
    NVSWITCH_ENGINE_ID eng_id,
    NvU32 eng_bcast,
    NvU32 eng_instance,
    NvU32 offset
)
{
    NvU32 base_addr = NVSWITCH_BASE_ADDR_INVALID;
    NvU32 data;

    base_addr = nvswitch_get_eng_base_lr10(device, eng_id, eng_bcast, eng_instance);
    if (base_addr == NVSWITCH_BASE_ADDR_INVALID)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: ID 0x%x[%d] %s invalid address\n",
            __FUNCTION__,
            eng_id, eng_instance,
            (
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_UNICAST) ? "UC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_BCAST) ? "BC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_MULTICAST) ? "MC" :
                "??"
            ));
        NVSWITCH_ASSERT(base_addr != NVSWITCH_BASE_ADDR_INVALID);
        return 0xBADFBADF;
    }

    data = nvswitch_reg_read_32(device, base_addr + offset);

#if defined(DEVELOP) || defined(DEBUG) || defined(NV_MODS)
    {
        NVSWITCH_ENGINE_DESCRIPTOR_TYPE  *engine = _nvswitch_get_eng_descriptor_lr10(device, eng_id);

        NVSWITCH_PRINT(device, MMIO,
            "%s: ENG_RD %s(0x%x)[%d] @0x%08x+0x%06x = 0x%08x\n",
            __FUNCTION__,
            engine->eng_name, engine->eng_id,
            eng_instance,
            base_addr, offset,
            data);
    }
#endif  //defined(DEVELOP) || defined(DEBUG) || defined(NV_MODS)

    return data;
}

void
nvswitch_eng_wr_lr10
(
    nvswitch_device *device,
    NVSWITCH_ENGINE_ID eng_id,
    NvU32 eng_bcast,
    NvU32 eng_instance,
    NvU32 offset,
    NvU32 data
)
{
    NvU32 base_addr = NVSWITCH_BASE_ADDR_INVALID;

    base_addr = nvswitch_get_eng_base_lr10(device, eng_id, eng_bcast, eng_instance);
    if (base_addr == NVSWITCH_BASE_ADDR_INVALID)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: ID 0x%x[%d] %s invalid address\n",
            __FUNCTION__,
            eng_id, eng_instance,
            (
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_UNICAST) ? "UC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_BCAST) ? "BC" :
                (eng_bcast == NVSWITCH_GET_ENG_DESC_TYPE_MULTICAST) ? "MC" :
                "??"
            ));
        NVSWITCH_ASSERT(base_addr != NVSWITCH_BASE_ADDR_INVALID);
        return;
    }

    nvswitch_reg_write_32(device, base_addr + offset,  data);

#if defined(DEVELOP) || defined(DEBUG) || defined(NV_MODS)
    {
        NVSWITCH_ENGINE_DESCRIPTOR_TYPE  *engine = _nvswitch_get_eng_descriptor_lr10(device, eng_id);

        NVSWITCH_PRINT(device, MMIO,
            "%s: ENG_WR %s(0x%x)[%d] @0x%08x+0x%06x = 0x%08x\n",
            __FUNCTION__,
            engine->eng_name, engine->eng_id,
            eng_instance,
            base_addr, offset,
            data);
    }
#endif  //defined(DEVELOP) || defined(DEBUG) || defined(NV_MODS)
}

NvU32
nvswitch_get_link_eng_inst_lr10
(
    nvswitch_device *device,
    NvU32 link_id,
    NVSWITCH_ENGINE_ID eng_id
)
{
    NvU32   eng_instance = NVSWITCH_ENGINE_INSTANCE_INVALID;

    if (link_id >= NVSWITCH_LINK_COUNT(device))
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: link ID 0x%x out-of-range [0x0..0x%x]\n",
            __FUNCTION__,
            link_id, NVSWITCH_LINK_COUNT(device)-1);
        return NVSWITCH_ENGINE_INSTANCE_INVALID;
    }

    switch (eng_id)
    {
        case NVSWITCH_ENGINE_ID_NPG:
            eng_instance = link_id / NVSWITCH_LINKS_PER_NPG;
            break;
        case NVSWITCH_ENGINE_ID_NVLIPT:
            eng_instance = link_id / NVSWITCH_LINKS_PER_NVLIPT;
            break;
        case NVSWITCH_ENGINE_ID_NVLW:
        case NVSWITCH_ENGINE_ID_NVLW_PERFMON:
            eng_instance = link_id / NVSWITCH_LINKS_PER_NVLW;
            break;
        case NVSWITCH_ENGINE_ID_MINION:
            eng_instance = link_id / NVSWITCH_LINKS_PER_MINION;
            break;
        case NVSWITCH_ENGINE_ID_NPORT:
        case NVSWITCH_ENGINE_ID_NVLTLC:
        case NVSWITCH_ENGINE_ID_NVLDL:
        case NVSWITCH_ENGINE_ID_NVLIPT_LNK:
        case NVSWITCH_ENGINE_ID_NPORT_PERFMON:
        case NVSWITCH_ENGINE_ID_RX_PERFMON:
        case NVSWITCH_ENGINE_ID_TX_PERFMON:
            eng_instance = link_id;
            break;
        default:
            NVSWITCH_PRINT(device, ERROR,
                "%s: link ID 0x%x has no association with EngID 0x%x\n",
                __FUNCTION__,
                link_id, eng_id);
            eng_instance = NVSWITCH_ENGINE_INSTANCE_INVALID;
            break;
    }

    return eng_instance;
}

NvU32
nvswitch_get_caps_nvlink_version_lr10
(
    nvswitch_device *device
)
{
    ct_assert(NVSWITCH_NVLINK_STATUS_NVLINK_VERSION_3_0 ==
                NVSWITCH_NVLINK_CAPS_NVLINK_VERSION_3_0);
    return NVSWITCH_NVLINK_CAPS_NVLINK_VERSION_3_0;
}

NVSWITCH_BIOS_NVLINK_CONFIG *
nvswitch_get_bios_nvlink_config_lr10
(
    nvswitch_device *device
)
{
    lr10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LR10(device);

    return (chip_device != NULL) ? &chip_device->bios_config : NULL;
}

/*
 * CTRL_NVSWITCH_SET_RESIDENCY_BINS
 */
static NvlStatus
nvswitch_ctrl_set_residency_bins_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_RESIDENCY_BINS *p
)
{
    NVSWITCH_PRINT(device, ERROR,
        "SET_RESIDENCY_BINS should not be called on LR10\n");
    return -NVL_ERR_NOT_SUPPORTED;
}

/*
 * CTRL_NVSWITCH_GET_RESIDENCY_BINS
 */
static NvlStatus
nvswitch_ctrl_get_residency_bins_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_RESIDENCY_BINS *p
)
{
    NVSWITCH_PRINT(device, ERROR,
        "GET_RESIDENCY_BINS should not be called on LR10\n");
    return -NVL_ERR_NOT_SUPPORTED;
}

/*
 * CTRL_NVSWITCH_GET_RB_STALL_BUSY
 */
static NvlStatus
nvswitch_ctrl_get_rb_stall_busy_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_RB_STALL_BUSY *p
)
{
    NVSWITCH_PRINT(device, ERROR,
        "GET_RB_STALL_BUSY should not be called on LR10\n");
    return -NVL_ERR_NOT_SUPPORTED;
}

/*
* @brief: This function retrieves the NVLIPT public ID for a given global link idx
* @params[in]  device        reference to current nvswitch device
* @params[in]  linkId        link to retrieve NVLIPT public ID from
* @params[out] publicId      Public ID of NVLIPT owning linkId
*/
NvlStatus nvswitch_get_link_public_id_lr10
(
    nvswitch_device *device,
    NvU32 linkId,
    NvU32 *publicId
)
{
    if (!device->hal.nvswitch_is_link_valid(device, linkId) ||
        (publicId == NULL))
    {
        return -NVL_BAD_ARGS;
    }

    *publicId = NVSWITCH_NVLIPT_GET_PUBLIC_ID_LR10(linkId);


    return (NVSWITCH_ENG_VALID_LR10(device, NVLIPT, *publicId)) ?
                NVL_SUCCESS : -NVL_BAD_ARGS;
}

/*
* @brief: This function retrieves the internal link idx for a given global link idx
* @params[in]  device        reference to current nvswitch device
* @params[in]  linkId        link to retrieve NVLIPT public ID from
* @params[out] localLinkIdx  Internal link index of linkId
*/
NvlStatus nvswitch_get_link_local_idx_lr10
(
    nvswitch_device *device,
    NvU32 linkId,
    NvU32 *localLinkIdx
)
{
    if (!device->hal.nvswitch_is_link_valid(device, linkId) ||
        (localLinkIdx == NULL))
    {
        return -NVL_BAD_ARGS;
    }

    *localLinkIdx = NVSWITCH_NVLIPT_GET_LOCAL_LINK_ID_LR10(linkId);

    return NVL_SUCCESS;
}

NvlStatus nvswitch_set_training_error_info_lr10
(
    nvswitch_device *device,
    NVSWITCH_SET_TRAINING_ERROR_INFO_PARAMS *pLinkTrainingErrorInfoParams
)
{
    NVSWITCH_LINK_TRAINING_ERROR_INFO linkTrainingErrorInfo;
    NVSWITCH_LINK_RUNTIME_ERROR_INFO linkRuntimeErrorInfo;

    linkTrainingErrorInfo.isValid = NV_TRUE;
    linkTrainingErrorInfo.attemptedTrainingMask0 =
        pLinkTrainingErrorInfoParams->attemptedTrainingMask0;
    linkTrainingErrorInfo.trainingErrorMask0 =
        pLinkTrainingErrorInfoParams->trainingErrorMask0;

    linkRuntimeErrorInfo.isValid = NV_FALSE;
    linkRuntimeErrorInfo.mask0   = 0;

    return nvswitch_smbpbi_set_link_error_info(device,
                                               &linkTrainingErrorInfo,
                                               &linkRuntimeErrorInfo);
}

NvlStatus nvswitch_ctrl_get_fatal_error_scope_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_FATAL_ERROR_SCOPE_PARAMS *pParams
)
{
    NvU32 linkId;
    NvU32 reg = NVSWITCH_SAW_RD32_LR10(device, _NVLSAW, _SW_SCRATCH_12);
    pParams->device = FLD_TEST_DRF_NUM(_NVLSAW, _SW_SCRATCH_12, _DEVICE_RESET_REQUIRED,
                                       1, reg);

    for (linkId = 0; linkId < NVSWITCH_MAX_PORTS; linkId++)
    {
        if (!nvswitch_is_link_valid(device, linkId))
        {
            pParams->port[linkId] = NV_FALSE;
            continue;
        }

        reg = NVSWITCH_LINK_RD32_LR10(device, linkId, NPORT, _NPORT, _SCRATCH_WARM);
        pParams->port[linkId] = FLD_TEST_DRF_NUM(_NPORT, _SCRATCH_WARM,
                                                 _PORT_RESET_REQUIRED, 1, reg);
    }

    return NVL_SUCCESS;
}

void nvswitch_init_scratch_lr10
(
    nvswitch_device *device
)
{
    NvU32 linkId;
    NvU32 reg;

    for (linkId = 0; linkId < nvswitch_get_num_links(device); linkId++)
    {
        if (!nvswitch_is_link_valid(device, linkId))
        {
            continue;
        }

        reg = NVSWITCH_LINK_RD32_LR10(device, linkId, NPORT, _NPORT, _SCRATCH_WARM);
        if (reg == NV_NPORT_SCRATCH_WARM_DATA_INIT)
        {
            NVSWITCH_LINK_WR32_LR10(device, linkId, NPORT, _NPORT, _SCRATCH_WARM, 0);
        }
    }
}

NvlStatus
nvswitch_set_training_mode_lr10
(
    nvswitch_device *device
)
{
    return NVL_SUCCESS;
}

NvlStatus
nvswitch_parse_bios_image_lr10
(
    nvswitch_device *device
)
{
    NVSWITCH_BIOS_NVLINK_CONFIG *bios_config;
    NV_STATUS status = NV_OK;

    // check if spi is supported
    if (!nvswitch_is_spi_supported(device))
    {
        NVSWITCH_PRINT(device, ERROR,
                "%s: SPI is not supported\n",
                __FUNCTION__);
        return -NVL_ERR_NOT_SUPPORTED;
    }

    bios_config = nvswitch_get_bios_nvlink_config(device);

    // Parse and retrieve the VBIOS info
    status = _nvswitch_setup_link_vbios_overrides(device, bios_config);
    if ((status != NV_OK) && device->pSoe)
    {
        NVSWITCH_PRINT(device, ERROR,
                "%s: error=0x%x\n",
                __FUNCTION__, status);

        return -NVL_ERR_GENERIC;
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_get_nvlink_lp_counters_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_NVLINK_LP_COUNTERS_PARAMS *params
)
{
    return -NVL_ERR_NOT_SUPPORTED;
}

NvlStatus
nvswitch_ctrl_get_sw_info_lr10
(
    nvswitch_device *device,
    NVSWITCH_GET_SW_INFO_PARAMS *p
)
{
    NvlStatus retval = NVL_SUCCESS;
    NvU32 i;

    if (p->count > NVSWITCH_GET_SW_INFO_COUNT_MAX)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: Invalid args\n",
            __FUNCTION__);
        return -NVL_BAD_ARGS;
    }

    nvswitch_os_memset(p->info, 0, sizeof(NvU32)*NVSWITCH_GET_SW_INFO_COUNT_MAX);

    for (i = 0; i < p->count; i++)
    {
        switch (p->index[i])
        {
            case NVSWITCH_GET_SW_INFO_INDEX_INFOROM_NVL_SUPPORTED:
                p->info[i] = (NvU32)_nvswitch_inforom_nvl_supported(device);
                break;
            case NVSWITCH_GET_SW_INFO_INDEX_INFOROM_BBX_SUPPORTED:
                p->info[i] = (NvU32)_nvswitch_inforom_bbx_supported(device);
                break;
            default:
                NVSWITCH_PRINT(device, ERROR,
                    "%s: Undefined NVSWITCH_GET_SW_INFO_INDEX 0x%x\n",
                    __FUNCTION__,
                    p->index[i]);
                retval = -NVL_BAD_ARGS;
                break;
        }
    }

    return retval;
}

//
// This function auto creates the lr10 HAL connectivity from the NVSWITCH_INIT_HAL
// macro in haldef_nvswitch.h
//
// Note: All hal fns must be implemented for each chip.
//       There is no automatic stubbing here.
//
void nvswitch_setup_hal_lr10(nvswitch_device *device)
{
    device->chip_arch = NVSWITCH_GET_INFO_INDEX_ARCH_LR10;

    {
        device->chip_impl = NVSWITCH_GET_INFO_INDEX_IMPL_LR10;
    }

    NVSWITCH_INIT_HAL(device, lr10);
}
