/*
 * SPDX-FileCopyrightText: Copyright (c) 1993-2025 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.
 */


/***************************** HW State Routines ***************************\
*         Core Logic Object Function Definitions.                           *
\***************************************************************************/

#include "core/system.h"
#include "platform/chipset/chipset.h"
#include "platform/platform.h"
#include "platform/chipset/chipset_info.h"
#include "os/os.h"
#include "nvrm_registry.h"
#include "nvpcie.h"
#include "nv_ref.h"
#include "kernel/gpu/bif/kernel_bif.h"
#include "gpu_mgr/gpu_mgr.h"
#include "gpu/gsp/gsp_static_config.h"
#include "ctrl/ctrl2080/ctrl2080bus.h"

// local static funcs
static void clDestructHWBC(OBJHWBC *pHWBC);

NV_STATUS
clConstruct_IMPL(OBJCL *pCl)
{
    // Used to track when the link has gone into Recovery, which can cause CEs.
    pCl->EnteredRecoverySinceErrorsLastChecked = NV_FALSE;

    pCl->pPcieConfigSpaceBase = NULL;

    //
    // We set this property by default.
    // Chipset setup function can override this.
    // Right now only Tegra chipsets overide this setting.
    //
    pCl->setProperty(pCl, PDB_PROP_CL_IS_CHIPSET_IO_COHERENT, NV_TRUE);

    return NV_OK;
}


void
clInitPropertiesFromRegistry_IMPL(OBJGPU *pGpu, OBJCL *pCl)
{
    NvU32  data32;

    if (osReadRegistryDword(pGpu, NV_REG_STR_RM_DISABLE_BR03_FLOW_CONTROL, &data32) == NV_OK
            && data32)
    {
        pCl->setProperty(pCl, PDB_PROP_CL_DISABLE_BR03_FLOW_CONTROL, NV_TRUE);
    }

    if (osReadRegistryDword(pGpu, NV_REG_STR_RM_FORCE_ENABLE_GEN2, &data32) == NV_OK)
    {
        if (NV_REG_STR_RM_FORCE_ENABLE_GEN2_YES == data32)
        {
            pCl->setProperty(pCl, PDB_PROP_CL_PCIE_FORCE_GEN2_ENABLE, NV_TRUE);
        }
    }

    if ((osReadRegistryDword(pGpu, NV_REG_STR_RM_FORCE_DISABLE_IOMAP_WC, &data32) == NV_OK)
            && (data32 == NV_REG_STR_RM_FORCE_DISABLE_IOMAP_WC_YES))
    {
        pCl->setProperty(pCl, PDB_PROP_CL_DISABLE_IOMAP_WC, NV_TRUE);
    }

    osQADbgRegistryInit();
}

static void
clDestructHWBC(OBJHWBC *pHWBC)
{
    if (pHWBC->pSibling)
    {
        clDestructHWBC(pHWBC->pSibling);
        pHWBC->pSibling = NULL;
    }
    if (pHWBC->pFirstChild)
    {
        clDestructHWBC(pHWBC->pFirstChild);
        pHWBC->pFirstChild = NULL;
    }

    portMemFree(pHWBC);
}

void
clDestruct_IMPL(OBJCL *pCl)
{
    if (pCl->pHWBC)
    {
        clDestructHWBC(pCl->pHWBC);
        pCl->pHWBC = NULL;
    }

    clFreeBusTopologyCache(pCl);

    clFreePcieConfigSpaceBase(pCl);
}

//
// Find our NV device on the PCI bus and save it's pci bus/device address.
//
NvU32
clInitMappingPciBusDevice_IMPL
(
    OBJGPU       *pGpu,
    OBJCL        *pCl
)
{
    void *handle;
    NvU32 domain;
    NvU16 bus;
    NvU8 device;
    NvU16 vendorID, deviceID;
    NvBool bFoundDevice = NV_FALSE;

    if (IsT194(pGpu) || IsT234(pGpu))
        return NV0000_CTRL_GPU_INVALID_ID;

    // do we already know our domain/bus/device?
    if (!gpuIsDBDFValid(pGpu))
    {
        // we're checking all the device/funcs for the first 10 buses!
        // Note that we give up the enumeration once we find our first
        // or in the Multichip case our second device.
        for (domain = 0; (domain < PCI_MAX_DOMAINS) && !bFoundDevice; domain++)
        {
            for (bus = 0; (bus < PCI_MAX_BUSES) && !bFoundDevice; bus++)
            {
                for (device = 0; device < PCI_MAX_DEVICES; device++)
                {
                    // read at domain, bus, device (we're always function 0)
                    handle = osPciInitHandle(domain, (NvU8) bus, device, 0, &vendorID, &deviceID);
                    if (!handle)
                        continue;

                    if (vendorID != NV_CONFIG_PCI_NV_0_VENDOR_ID_NVIDIA)
                        continue;

                    // make sure we're a VGA device class
                    if ((osPciReadByte(handle, 0xB)) != PCI_CLASS_DISPLAY_DEV)
                        continue;

                    // if the BAR0 matches our PhysAddr, it's the correct device
                    if (((osPciReadDword(handle, PCI_BASE_ADDRESS_0) & PCI_BASE_ADDRESS_0_VALID_MASK)) !=
                        pGpu->busInfo.gpuPhysAddr)
                        continue;

                    // save our domain/bus/device/function
                    pGpu->busInfo.nvDomainBusDeviceFunc = gpuEncodeDomainBusDevice(domain, (NvU8)bus, device);
                    pGpu->busInfo.bNvDomainBusDeviceFuncValid = NV_TRUE;

                    bFoundDevice = NV_TRUE;

                    break;
                }
            }
        }
    }

    domain = gpuGetDomain(pGpu);
    bus = gpuGetBus(pGpu);
    device = gpuGetDevice(pGpu);

    if (!gpuIsDBDFValid(pGpu))
    {
        {
            NV_PRINTF(LEVEL_ERROR,
                    "NVRM initMappingPciBusDevice: can't find a device!\n");
            return NV0000_CTRL_GPU_INVALID_ID;    // couldn't find it
        }
    }

    return gpuGenerate32BitId(domain, (NvU8)bus, device);
}

//
// Walk the PCIE Capabilities and if the subsystem ID is found then return
// the subvendorID and subdeviceID
//
static void getSubsystemFromPCIECapabilities
(
    NvU32 domain,
    NvU8 bus,
    NvU8 device,
    NvU8 func,
    NvU16 *subvendorID,
    NvU16 *subdeviceID
)
{
    void *handle;
    NvU32 PCIECapPtr;
    NvU32 PCIECap;
    NvU32 PCIECapNext;

    handle = osPciInitHandle(domain, bus, device, func, NULL, NULL);

    // We start from Cap. List and search for Subsystem ID Capability
    PCIECapNext = osPciReadByte(handle, PCI_CAPABILITY_LIST);
    if (PCIECapNext)
    {
        do
        {
            PCIECapPtr = PCIECapNext;
            PCIECap = osPciReadDword(handle, CL_PCIE_CAP - CL_PCIE_BEGIN + PCIECapPtr);
            PCIECapNext = (NvU8)((PCIECap >> 8) & 0xFF);
        } while ((PCIECapNext != 0) &&
                 ((PCIECap & CAP_ID_MASK) != CAP_ID_SUBSYSTEM_ID));

        if ((PCIECap & CAP_ID_MASK) == CAP_ID_SUBSYSTEM_ID)
        {
            if (subvendorID)
            {
                *subvendorID = osPciReadWord(handle, CL_PCIE_CAP - CL_PCIE_BEGIN + PCIECapPtr + 4);
            }
            if (subdeviceID)
            {
                *subdeviceID = osPciReadWord(handle, CL_PCIE_CAP - CL_PCIE_BEGIN + PCIECapPtr + 6);
            }
        }
    }
}

//
// PCI Express Support
// Find first host bridge's domain, bus, device, function, if not already found
//

NV_STATUS
clFindFHBAndGetChipsetInfoIndex_IMPL
(
    OBJCL *pCl,
    NvU16 *pChipsetInfoIndex
)
{
    void     *handle;
    NvU32     domain;
    NvU8      revisionID = 0;
    NvU32     i;
    NvBool    matchFound = NV_FALSE;
    NvU16     bus;
    NvU8      device, func;
    NvU16     vendorID, deviceID, subvendorID, subdeviceID;
    PBUSTOPOLOGYINFO pBusTopologyInfo;
    //
    // PC motherboards have a host bridge to connect PCIE root complex to rest of the system.
    // However, Tegra devices only have a PCI-to-PCI bridge.
    // So allow Tegra chipset initialization, even if a host bridge is not found (SUBBASECLASS_P2P).
    // See bug 1547160 comment#17 for more details (dGPU connected with Tegra device through PCIe).
    // There is no host bridge or PCI-to-PCI bridge present in between
    // Tegra iGPUs and PCIe-RC. Tegra iGPUs are directly connected to endpoint.
    // PCI config for Sub Base Class code will always be programmed as
    // PCI_COMMON_CLASS_SUBBASECLASS_3DCTRL for Tegra iGPUs.
    //
    NvU16    pciSubBaseClass[] = {PCI_COMMON_CLASS_SUBBASECLASS_HOST,
        PCI_COMMON_CLASS_SUBBASECLASS_P2P, PCI_COMMON_CLASS_SUBBASECLASS_3DCTRL};

    // return it, if we've got it already
    if (pCl->chipsetIDBusAddr.valid)
        return NV_OK;

    // Initialize to 0
    pCl->chipsetIDBusAddr.domain = 0x0;
    pCl->chipsetIDBusAddr.bus    = 0x0;
    pCl->chipsetIDBusAddr.device = 0x0;
    pCl->chipsetIDBusAddr.func   = 0x0;
    pCl->chipsetIDBusAddr.valid  = 0x0;
    pCl->chipsetIDBusAddr.handle = NULL;
    pCl->chipsetIDInfo.deviceID    = PCI_INVALID_DEVICEID;
    pCl->chipsetIDInfo.vendorID    = PCI_INVALID_VENDORID;
    pCl->chipsetIDInfo.subdeviceID = PCI_INVALID_SUBDEVICEID;
    pCl->chipsetIDInfo.subvendorID = PCI_INVALID_SUBVENDORID;

    for (i = 0; chipsetInfo[i].vendorID; i++)
    {
        pBusTopologyInfo = pCl->pBusTopologyInfo;
        while (pBusTopologyInfo)
        {
            if ((pBusTopologyInfo->busInfo.vendorID == chipsetInfo[i].vendorID) &&
                (pBusTopologyInfo->busInfo.deviceID == chipsetInfo[i].deviceID))
            {
                matchFound = NV_TRUE;
                break;
            }
            pBusTopologyInfo = pBusTopologyInfo->next;
        }

        if (matchFound)
        {
            if  (pChipsetInfoIndex != NULL)
            {
                *pChipsetInfoIndex = (NvU16) i;
            }

            //
            // IBM Wildhorse system has NV chipset attached to secondary K8 at bus 0x80
            // (bug 227308).
            // Do not change the algorithm for older chipsets where the devcie at bus%0x40 ==0, 0, 0 is
            // considered as a host bridge.
            //
            if (((pBusTopologyInfo->pciSubBaseClass & 0xFF) == PCI_SUBCLASS_BR_HOST) ||
                (!(pBusTopologyInfo->bus % 0x40) && !pBusTopologyInfo->device && !pBusTopologyInfo->func))
            {
                pCl->FHBAddr.domain = pBusTopologyInfo->domain;
                pCl->FHBAddr.bus    = pBusTopologyInfo->bus;
                pCl->FHBAddr.device = pBusTopologyInfo->device;
                pCl->FHBAddr.func   = pBusTopologyInfo->func;
                pCl->FHBAddr.valid  = 0x1;
                pCl->FHBAddr.handle = pBusTopologyInfo->handle;

                // Store a copy of deviceID,  vendorID, subdeviceID and subvendorID;
                pCl->FHBBusInfo.deviceID    = pBusTopologyInfo->busInfo.deviceID;
                pCl->FHBBusInfo.vendorID    = pBusTopologyInfo->busInfo.vendorID;
                pCl->FHBBusInfo.subdeviceID = pBusTopologyInfo->busInfo.subdeviceID;
                pCl->FHBBusInfo.subvendorID = pBusTopologyInfo->busInfo.subvendorID;
                pCl->FHBBusInfo.revisionID  = pBusTopologyInfo->busInfo.revisionID;

                pCl->chipsetIDBusAddr.domain = pBusTopologyInfo->domain;
                pCl->chipsetIDBusAddr.bus    = pBusTopologyInfo->bus;
                pCl->chipsetIDBusAddr.device = pBusTopologyInfo->device;
                pCl->chipsetIDBusAddr.func   = pBusTopologyInfo->func;
                pCl->chipsetIDBusAddr.valid  = 0x1;
                pCl->chipsetIDBusAddr.handle = pBusTopologyInfo->handle;

                // Store a copy of deviceID,  vendorID, subdeviceID and subvendorID;
                pCl->chipsetIDInfo.deviceID    = pBusTopologyInfo->busInfo.deviceID;
                pCl->chipsetIDInfo.vendorID    = pBusTopologyInfo->busInfo.vendorID;
                pCl->chipsetIDInfo.subdeviceID = pBusTopologyInfo->busInfo.subdeviceID;
                pCl->chipsetIDInfo.subvendorID = pBusTopologyInfo->busInfo.subvendorID;
                return NV_OK;
            }
            else
            {
                pCl->chipsetIDBusAddr.domain = pBusTopologyInfo->domain;
                pCl->chipsetIDBusAddr.bus    = pBusTopologyInfo->bus;
                pCl->chipsetIDBusAddr.device = pBusTopologyInfo->device;
                pCl->chipsetIDBusAddr.func   = pBusTopologyInfo->func;
                pCl->chipsetIDBusAddr.valid  = 0x1;
                pCl->chipsetIDBusAddr.handle = pBusTopologyInfo->handle;

                // Store a copy of deviceID,  vendorID, subdeviceID and subvendorID;
                pCl->chipsetIDInfo.deviceID    = pBusTopologyInfo->busInfo.deviceID;
                pCl->chipsetIDInfo.vendorID    = pBusTopologyInfo->busInfo.vendorID;
                pCl->chipsetIDInfo.subdeviceID = pBusTopologyInfo->busInfo.subdeviceID;
                pCl->chipsetIDInfo.subvendorID = pBusTopologyInfo->busInfo.subvendorID;

                if (pCl->chipsetIDInfo.subvendorID == 0)
                {
                    getSubsystemFromPCIECapabilities(pCl->chipsetIDBusAddr.domain,
                                                     pCl->chipsetIDBusAddr.bus,
                                                     pCl->chipsetIDBusAddr.device,
                                                     pCl->chipsetIDBusAddr.func,
                                                     &pCl->chipsetIDInfo.subvendorID,
                                                     &pCl->chipsetIDInfo.subdeviceID);
                }
                break;
            }
        }
    }

    if ((!matchFound) && (pChipsetInfoIndex != NULL))
    {
        // This should be the entry with NULL information
        NV_ASSERT(chipsetInfo[i].vendorID == 0);
        *pChipsetInfoIndex = (NvU16) i;
    }

    //
    // We are here because VendorId and deviceId in chipsetInfo table does not
    // match with Host Bridge ID. In that case we need to find FHB either in
    // cached bus topology or need to loop through PCI bus to find the FHB.
    //
    for (i = 0; i < NV_ARRAY_ELEMENTS(pciSubBaseClass); i++)
    {
        pBusTopologyInfo = pCl->pBusTopologyInfo;
        while (pBusTopologyInfo)
        {
            if (pBusTopologyInfo->pciSubBaseClass == pciSubBaseClass[i])
            {
                pCl->FHBAddr.domain = pBusTopologyInfo->domain;
                pCl->FHBAddr.bus    = pBusTopologyInfo->bus;
                pCl->FHBAddr.device = pBusTopologyInfo->device;
                pCl->FHBAddr.func   = pBusTopologyInfo->func;
                pCl->FHBAddr.valid  = 0x1;
                pCl->FHBAddr.handle = pBusTopologyInfo->handle;

                // Store a copy of deviceID,  vendorID, subdeviceID and subvendorID;
                pCl->FHBBusInfo.deviceID    = pBusTopologyInfo->busInfo.deviceID;
                pCl->FHBBusInfo.vendorID    = pBusTopologyInfo->busInfo.vendorID;
                pCl->FHBBusInfo.subdeviceID = pBusTopologyInfo->busInfo.subdeviceID;
                pCl->FHBBusInfo.subvendorID = pBusTopologyInfo->busInfo.subvendorID;
                pCl->FHBBusInfo.revisionID  = pBusTopologyInfo->busInfo.revisionID;

                if (!matchFound)
                {
                    pCl->chipsetIDBusAddr.domain = pBusTopologyInfo->domain;
                    pCl->chipsetIDBusAddr.bus    = pBusTopologyInfo->bus;
                    pCl->chipsetIDBusAddr.device = pBusTopologyInfo->device;
                    pCl->chipsetIDBusAddr.func   = pBusTopologyInfo->func;
                    pCl->chipsetIDBusAddr.valid  = 0x1;
                    pCl->chipsetIDBusAddr.handle = pBusTopologyInfo->handle;

                    // Store a copy of deviceID,  vendorID, subdeviceID and subvendorID;
                    pCl->chipsetIDInfo.deviceID    = pBusTopologyInfo->busInfo.deviceID;
                    pCl->chipsetIDInfo.vendorID    = pBusTopologyInfo->busInfo.vendorID;
                    pCl->chipsetIDInfo.subdeviceID = pBusTopologyInfo->busInfo.subdeviceID;
                    pCl->chipsetIDInfo.subvendorID = pBusTopologyInfo->busInfo.subvendorID;

                    if (pCl->chipsetIDInfo.subvendorID == 0)
                    {
                        getSubsystemFromPCIECapabilities(pCl->chipsetIDBusAddr.domain,
                                                         pCl->chipsetIDBusAddr.bus,
                                                         pCl->chipsetIDBusAddr.device,
                                                         pCl->chipsetIDBusAddr.func,
                                                         &pCl->chipsetIDInfo.subvendorID,
                                                         &pCl->chipsetIDInfo.subdeviceID);
                    }
                }

                NV_PRINTF(LEVEL_INFO,
                          "DeviceId[%x] VendorID[%x] BDF[%x:%x:%x] SubClassId[%x] device found.\n",
                          pBusTopologyInfo->busInfo.deviceID,
                          pBusTopologyInfo->busInfo.vendorID,
                          pBusTopologyInfo->bus,
                          pBusTopologyInfo->device,
                          pBusTopologyInfo->func,
                          pciSubBaseClass[i]);

                return NV_OK;
            }
            pBusTopologyInfo = pBusTopologyInfo->next;
        }
    }

    NV_PRINTF(LEVEL_ERROR,
              "NVRM : This is Bad. FHB/P2P/3DCTRL not found in cached bus topology!!!\n");

    // HB/P2P/3DCTRL is not present in cached bus topology.
    NV_ASSERT(0);

    //
    // Don't bother scanning all domains, which takes approximately forever.
    // If we can't find it in domain 0, we're probably not going to anyway
    // (and something is already wrong).
    //
    domain = 0;

    for (bus = 0; bus < PCI_MAX_BUSES; bus++)
    {
        for (device = 0; device < PCI_MAX_DEVICES; device++)
        {
            for (func = 0; func < PCI_MAX_FUNCTION; func++)
            {
                // read at domain, bus, device, func
                handle = osPciInitHandle(domain, (NvU8)bus, device, func, &vendorID, &deviceID);
                if (!handle)
                {
                    if (func == 0)
                    {
                        // If a read to function zero of a specified bus/device master aborts,
                        // then it is assumed that no such device exists on the bus since
                        // devices are required to implement function number zero.
                        // In this case reads to the remaining functions are not necessary.
                        break;
                    }
                    else
                    {
                        continue;
                    }
                }

                if (!PCI_IS_VENDORID_VALID(vendorID))
                    break;           // skip to the next device

                if ((osPciReadByte(handle, PCI_HEADER_TYPE0_BASECLASS)) != PCI_CLASS_BRIDGE_DEV)
                    break;           // not a bridge device

                if ((osPciReadByte(handle, PCI_HEADER_TYPE0_SUBCLASS))  != PCI_SUBCLASS_BR_HOST)
                    break;           // not a host bridge

                subdeviceID = osPciReadWord(handle, PCI_HEADER_TYPE0_SUBSYS_ID);
                subvendorID = osPciReadWord(handle, PCI_HEADER_TYPE0_SUBSYS_VEN_ID);
                revisionID  = osPciReadByte(handle, PCI_HEADER_TYPE0_REVISION_ID);

                // Found it
                pCl->FHBAddr.domain = domain;
                pCl->FHBAddr.bus    = (NvU8)bus;
                pCl->FHBAddr.device = device;
                pCl->FHBAddr.func   = func;
                pCl->FHBAddr.valid  = 0x1;
                pCl->FHBAddr.handle = handle;

                // Store a copy of deviceID,  vendorID, subdeviceID and subvendorID;
                pCl->FHBBusInfo.deviceID    = deviceID;
                pCl->FHBBusInfo.vendorID    = vendorID;
                pCl->FHBBusInfo.subdeviceID = subdeviceID;
                pCl->FHBBusInfo.subvendorID = subvendorID;
                pCl->FHBBusInfo.revisionID  = revisionID;

                if (!matchFound)
                {
                    pCl->chipsetIDBusAddr.domain = domain;
                    pCl->chipsetIDBusAddr.bus    = (NvU8)bus;
                    pCl->chipsetIDBusAddr.device = device;
                    pCl->chipsetIDBusAddr.func   = func;
                    pCl->chipsetIDBusAddr.valid  = 0x1;
                    pCl->chipsetIDBusAddr.handle = handle;

                    // Store a copy of deviceID,  vendorID, subdeviceID and subvendorID;
                    pCl->chipsetIDInfo.deviceID    = deviceID;
                    pCl->chipsetIDInfo.vendorID    = vendorID;
                    pCl->chipsetIDInfo.subdeviceID = subdeviceID;
                    pCl->chipsetIDInfo.subvendorID = subvendorID;
                }
                return NV_OK;
            }
        }
    }

    // This is bad, we didn't find the First Host Bridge device (assume domain0/bus0/device0/func0)
    pCl->FHBAddr.domain = 0x0;
    pCl->FHBAddr.bus    = 0x0;
    pCl->FHBAddr.device = 0x0;
    pCl->FHBAddr.func   = 0x0;
    pCl->FHBAddr.valid  = 0x1;
    pCl->FHBAddr.handle = NULL;
    pCl->FHBBusInfo.deviceID    = PCI_INVALID_DEVICEID;
    pCl->FHBBusInfo.vendorID    = PCI_INVALID_VENDORID;
    pCl->FHBBusInfo.subdeviceID = PCI_INVALID_SUBDEVICEID;
    pCl->FHBBusInfo.subvendorID = PCI_INVALID_SUBVENDORID;

    DBG_BREAKPOINT();

    NV_ASSERT(0); //We can't find a host bridge, bad!


    return NV_ERR_NOT_SUPPORTED;
}

/*!
 * @brief Check if ASPM L1 is supported from upstream component
 *
 * @param[in] pGpu GPU object pointer
 * @param[in] pCl  CL  object pointer
 *
 * @return NV_TRUE if L1 is supported
 */
NvBool
clIsL1SupportedForUpstreamPort_IMPL
(
    OBJGPU *pGpu,
    OBJCL  *pCl
)
{
    NvU32     linkCap;
    NvBool    bSupported = NV_FALSE;
    NV_STATUS status     = NV_OK;

    if (pGpu->gpuClData.upstreamPort.addr.valid)
    {
        status = clPcieReadPortConfigReg(pGpu, pCl, &pGpu->gpuClData.upstreamPort, CL_PCIE_LINK_CAP, &linkCap);
        if (status == NV_OK)
        {
            if ((CL_IS_L1_SUPPORTED(linkCap)))
            {
                bSupported = NV_TRUE;
            }
        }
    }
    else if (pGpu->gpuClData.rootPort.addr.valid)
    {
        status = clPcieReadPortConfigReg(pGpu, pCl, &pGpu->gpuClData.rootPort, CL_PCIE_LINK_CAP, &linkCap);
        if (status == NV_OK)
        {
            if ((CL_IS_L1_SUPPORTED(linkCap)))
            {
                bSupported = NV_TRUE;
            }
        }
    }

    return bSupported;
}

/*!
 * @brief Check if L0s mask is enabled for upstream component
 *
 * @param[in] pGpu GPU object pointer
 * @param[in] pCl  CL  object pointer
 *
 * @return NV_TRUE if mask is enabled (implies L0s is disabled)
 */
NvBool
clIsL0sMaskEnabledForUpstreamPort_IMPL
(
    OBJGPU *pGpu,
    OBJCL  *pCl
)
{
    NvU32  linkCtrl;
    NvBool bEnable = NV_FALSE;

    if (!pGpu->gpuClData.upstreamPort.addr.valid)
    {
        if (!pGpu->gpuClData.rootPort.addr.valid)
        {
            bEnable = NV_TRUE;
        }
        else
        {
            if (clPcieReadPortConfigReg(pGpu, pCl, &pGpu->gpuClData.rootPort,
                CL_PCIE_LINK_CTRL_STATUS, &linkCtrl) != NV_OK)
            {
                bEnable = NV_TRUE;
            }
            else
            {
                if (!(linkCtrl & CL_PCIE_LINK_CTRL_STATUS_ASPM_L0S_BIT))
                {
                    bEnable = NV_TRUE;
                }
            }
        }
    }
    else
    {
        if (clPcieReadPortConfigReg(pGpu, pCl, &pGpu->gpuClData.upstreamPort,
            CL_PCIE_LINK_CTRL_STATUS, &linkCtrl) != NV_OK)
        {
            bEnable = NV_TRUE;
        }
        else
        {
            if (!(linkCtrl & CL_PCIE_LINK_CTRL_STATUS_ASPM_L0S_BIT))
            {
                bEnable = NV_TRUE;
            }
        }
    }

    return bEnable;
}

NV_STATUS
clInit_IMPL(
    OBJGPU *pGpu,
    OBJCL  *pCl
)
{
    //
    // Common code for all buses
    //
    (void)clInitMappingPciBusDevice(pGpu, pCl);

    if (gpuGetBusIntfType_HAL(pGpu) == NV2080_CTRL_BUS_INFO_TYPE_PCI_EXPRESS)
    {
        return clInitPcie(pGpu, pCl);
    }

    return NV_OK; // no Init is needed for PCI
}

void
clUpdateConfig_IMPL
(
    OBJGPU *pGpu,
    OBJCL  *pCl
)
{
    // Common code for all buses
    clInitMappingPciBusDevice(pGpu, pCl);

    if (gpuGetBusIntfType_HAL(pGpu) == NV2080_CTRL_BUS_INFO_TYPE_PCI_EXPRESS)
    {
        clUpdatePcieConfig(pGpu, pCl);
        return;
    }

    return; // no Update is needed for PCI
}

NV_STATUS
clTeardown_IMPL(
    OBJGPU *pGpu,
    OBJCL  *pCl
)
{
    clFreeBusTopologyCache(pCl);

    switch (gpuGetBusIntfType_HAL(pGpu))
    {
        case NV2080_CTRL_BUS_INFO_TYPE_PCI_EXPRESS:
            return clTeardownPcie(pGpu, pCl);

        case NV2080_CTRL_BUS_INFO_TYPE_PCI:
        case NV2080_CTRL_BUS_INFO_TYPE_FPCI:
            return NV_OK;

        default:
            return NV_ERR_GENERIC;
    }
}

void clSyncWithGsp_IMPL(OBJCL *pCl, GspSystemInfo *pGSI)
{
    NvU32 idx = 0;
    pGSI->clPdbProperties = 0;

#define CL_SYNC_PDB(prop)                                                       \
    do {                                                                        \
        pGSI->clPdbProperties |= pCl->getProperty(pCl, prop) ? NVBIT64(idx) : 0;\
        idx++;                                                                  \
    } while (0)

    CL_SYNC_PDB(PDB_PROP_CL_PCIE_CONFIG_ACCESSIBLE);
    CL_SYNC_PDB(PDB_PROP_CL_FORCE_SNOOP_READS_AND_WRITES_WAR_BUG_410390);
    CL_SYNC_PDB(PDB_PROP_CL_DISABLE_BR03_FLOW_CONTROL);
    CL_SYNC_PDB(PDB_PROP_CL_ASLM_SUPPORTS_NV_LINK_UPGRADE);
    CL_SYNC_PDB(PDB_PROP_CL_ASLM_SUPPORTS_FAST_LINK_UPGRADE);
    CL_SYNC_PDB(PDB_PROP_CL_ASLM_SUPPORTS_HOT_RESET);
    CL_SYNC_PDB(PDB_PROP_CL_ASLM_SUPPORTS_GEN2_LINK_UPGRADE);
    CL_SYNC_PDB(PDB_PROP_CL_IS_CHIPSET_IN_ASPM_POR_LIST);
    CL_SYNC_PDB(PDB_PROP_CL_ASPM_L0S_CHIPSET_DISABLED);
    CL_SYNC_PDB(PDB_PROP_CL_ASPM_L1_CHIPSET_DISABLED);
    CL_SYNC_PDB(PDB_PROP_CL_WAR_4802761_ENABLED);
    CL_SYNC_PDB(PDB_PROP_CL_ASPM_L0S_CHIPSET_ENABLED_MOBILE_ONLY);
    CL_SYNC_PDB(PDB_PROP_CL_ASPM_L1_CHIPSET_ENABLED_MOBILE_ONLY);
    CL_SYNC_PDB(PDB_PROP_CL_ASPM_L1_UPSTREAM_PORT_SUPPORTED);
    CL_SYNC_PDB(PDB_PROP_CL_PCIE_GEN1_GEN2_SWITCH_CHIPSET_DISABLED);
    CL_SYNC_PDB(PDB_PROP_CL_PCIE_GEN1_GEN2_SWITCH_CHIPSET_DISABLED_GEFORCE);
    CL_SYNC_PDB(PDB_PROP_CL_EXTENDED_TAG_FIELD_NOT_CAPABLE);
    CL_SYNC_PDB(PDB_PROP_CL_NOSNOOP_NOT_CAPABLE);
    CL_SYNC_PDB(PDB_PROP_CL_RELAXED_ORDERING_NOT_CAPABLE);
    CL_SYNC_PDB(PDB_PROP_CL_PCIE_FORCE_GEN2_ENABLE);
    CL_SYNC_PDB(PDB_PROP_CL_PCIE_GEN2_AT_LESS_THAN_X16_DISABLED);
    CL_SYNC_PDB(PDB_PROP_CL_ROOTPORT_NEEDS_NOSNOOP_WAR);
    CL_SYNC_PDB(PDB_PROP_CL_INTEL_CPU_ROOTPORT1_NEEDS_H57_WAR);
    CL_SYNC_PDB(PDB_PROP_CL_PCIE_CONFIG_SKIP_MCFG_READ);
    CL_SYNC_PDB(PDB_PROP_CL_ON_PCIE_GEN3_PATSBURG);
    CL_SYNC_PDB(PDB_PROP_CL_ALLOW_PCIE_GEN3_ON_PATSBURG_WITH_IVBE_CPU);
    CL_SYNC_PDB(PDB_PROP_CL_BUG_999673_P2P_ARBITRARY_SPLIT_WAR);
    CL_SYNC_PDB(PDB_PROP_CL_UPSTREAM_LTR_SUPPORTED);
    CL_SYNC_PDB(PDB_PROP_CL_BUG_1340801_DISABLE_GEN3_ON_GIGABYTE_SNIPER_3);
    CL_SYNC_PDB(PDB_PROP_CL_BUG_1681803_WAR_DISABLE_MSCG);
    CL_SYNC_PDB(PDB_PROP_CL_PCIE_NON_COHERENT_USE_TC0_ONLY);
    CL_SYNC_PDB(PDB_PROP_CL_UNSUPPORTED_CHIPSET);
    CL_SYNC_PDB(PDB_PROP_CL_IS_CHIPSET_IO_COHERENT);
    CL_SYNC_PDB(PDB_PROP_CL_DISABLE_IOMAP_WC);
    CL_SYNC_PDB(PDB_PROP_CL_HAS_RESIZABLE_BAR_ISSUE);
    CL_SYNC_PDB(PDB_PROP_CL_BUG_3751839_GEN_SPEED_WAR);
    CL_SYNC_PDB(PDB_PROP_CL_BUG_3562968_WAR_ALLOW_PCIE_ATOMICS);
    CL_SYNC_PDB(PDB_PROP_CL_WAR_AMD_5107271);

#undef CL_SYNC_PDB

    NV_ASSERT(idx < (sizeof(pGSI->clPdbProperties) * 8));

    pGSI->Chipset = pCl->Chipset;
    pGSI->FHBBusInfo = pCl->FHBBusInfo;
    pGSI->chipsetIDInfo = pCl->chipsetIDInfo;

}
