/* * SPDX-FileCopyrightText: Copyright (c) 2020-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. */ #define __NO_VERSION__ #include "os-interface.h" #include "nv-linux.h" #if defined(NV_SOC_TEGRA_TEGRA_BPMP_H_PRESENT) || IS_ENABLED(CONFIG_TEGRA_BPMP) #include #endif #if IS_ENABLED(CONFIG_TEGRA_BPMP) #include #elif defined(NV_SOC_TEGRA_TEGRA_BPMP_H_PRESENT) #include #endif // IS_ENABLED(CONFIG_TEGRA_BPMP) #if defined NV_DT_BINDINGS_INTERCONNECT_TEGRA_ICC_ID_H_PRESENT #include #endif #ifdef NV_LINUX_PLATFORM_TEGRA_MC_UTILS_H_PRESENT #include #endif // // IMP requires information from various BPMP and MC driver functions. The // macro below checks that all of the required functions are present. // #define IMP_SUPPORT_FUNCTIONS_PRESENT \ (defined(NV_SOC_TEGRA_TEGRA_BPMP_H_PRESENT) || \ IS_ENABLED(CONFIG_TEGRA_BPMP)) && \ defined(NV_LINUX_PLATFORM_TEGRA_MC_UTILS_H_PRESENT) // // Also create a macro to check if all the required ICC symbols are present. // DT endpoints are defined in dt-bindings/interconnect/tegra_icc_id.h. // #define ICC_SUPPORT_FUNCTIONS_PRESENT \ defined(NV_DT_BINDINGS_INTERCONNECT_TEGRA_ICC_ID_H_PRESENT) /*! * @brief Returns IMP-relevant data passed by UEFI GOP Driver * * @param[in] nv Per GPU Linux state * @param[out] iso_bw_kbps ISO BW set by UEFI * @param[out] floor_bw_kbps DRAM Floor BW set by UEFI * * @returns NV_OK if successful, * NV_ERR_GENERIC if there is an error in parsing DT, * NV_ERR_NOT_SUPPORTED if the functionality is not available. */ NV_STATUS NV_API_CALL nv_imp_get_uefi_data ( nv_state_t *nv, NvU32 *iso_bw_kbps, NvU32 *floor_bw_kbps ) { nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv); NV_STATUS status = NV_OK; NvU32 value = 0; int ret = 0; *iso_bw_kbps = 0; *floor_bw_kbps = 0; #if NV_SUPPORTS_PLATFORM_DISPLAY_DEVICE ret = of_property_read_u32(nvl->dev->of_node, "nvidia,iso-bandwidth-kbps", &value); if (ret != 0) { nv_printf(NV_DBG_ERRORS, "NVRM: Unable to read nvidia,iso-bandwidth-kbps\n"); status = NV_ERR_GENERIC; goto done; } else { *iso_bw_kbps = value; } ret = of_property_read_u32(nvl->dev->of_node, "nvidia,dram-floor-kbps", &value); if (ret != 0) { nv_printf(NV_DBG_ERRORS, "NVRM: Unable to read nvidia,dram-floor-kbps\n"); status = NV_ERR_GENERIC; goto done; } else { *floor_bw_kbps = value; } #endif done: return status; } /*! * @brief Returns IMP-relevant data collected from other modules * * @param[out] tegra_imp_import_data Structure to receive the data * * @returns NV_OK if successful, * NV_ERR_NOT_SUPPORTED if the functionality is not available. */ NV_STATUS NV_API_CALL nv_imp_get_import_data ( TEGRA_IMP_IMPORT_DATA *tegra_imp_import_data ) { #if IMP_SUPPORT_FUNCTIONS_PRESENT tegra_imp_import_data->num_dram_channels = get_dram_num_channels(); nv_printf(NV_DBG_INFO, "NVRM: num_dram_channels = %u\n", tegra_imp_import_data->num_dram_channels); return NV_OK; #else // IMP_SUPPORT_FUNCTIONS_PRESENT return NV_ERR_NOT_SUPPORTED; #endif } /*! * @brief Tells BPMP whether or not RFL is valid * * Display HW generates an ok_to_switch signal which asserts when mempool * occupancy is high enough to be able to turn off memory long enough to * execute a dramclk frequency switch without underflowing display output. * ok_to_switch drives the RFL ("request for latency") signal in the memory * unit, and the switch sequencer waits for this signal to go active before * starting a dramclk switch. However, if the signal is not valid (e.g., if * display HW or SW has not been initialized yet), the switch sequencer ignores * the signal. This API tells BPMP whether or not the signal is valid. * * @param[in] nv Per GPU Linux state * @param[in] bEnable True if RFL will be valid; false if invalid * * @returns NV_OK if successful, * NV_ERR_NOT_SUPPORTED if the functionality is not available, or * NV_ERR_GENERIC if some other kind of error occurred. */ NV_STATUS NV_API_CALL nv_imp_enable_disable_rfl ( nv_state_t *nv, NvBool bEnable ) { NV_STATUS status = NV_ERR_NOT_SUPPORTED; #if IMP_SUPPORT_FUNCTIONS_PRESENT #if IS_ENABLED(CONFIG_TEGRA_BPMP) && NV_SUPPORTS_PLATFORM_DEVICE nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv); struct tegra_bpmp *bpmp = tegra_bpmp_get(nvl->dev); struct tegra_bpmp_message msg; struct mrq_emc_disp_rfl_request emc_disp_rfl_request; int rc; memset(&emc_disp_rfl_request, 0, sizeof(emc_disp_rfl_request)); emc_disp_rfl_request.mode = bEnable ? EMC_DISP_RFL_MODE_ENABLED : EMC_DISP_RFL_MODE_DISABLED; msg.mrq = MRQ_EMC_DISP_RFL; msg.tx.data = &emc_disp_rfl_request; msg.tx.size = sizeof(emc_disp_rfl_request); msg.rx.data = NULL; msg.rx.size = 0; rc = tegra_bpmp_transfer(bpmp, &msg); if (rc == 0) { nv_printf(NV_DBG_INFO, "\"Wait for RFL\" is %s via MRQ_EMC_DISP_RFL\n", bEnable ? "enabled" : "disabled"); status = NV_OK; } else { nv_printf(NV_DBG_ERRORS, "MRQ_EMC_DISP_RFL failed to %s \"Wait for RFL\" (error code = %d)\n", bEnable ? "enable" : "disable", rc); status = NV_ERR_GENERIC; } #else nv_printf(NV_DBG_ERRORS, "nv_imp_enable_disable_rfl stub called!\n"); #endif #endif return status; } /*! * @brief Obtains a handle for the display data path * * If a handle is obtained successfully, it is not returned to the caller; it * is saved for later use by subsequent nv_imp_icc_set_bw calls. * nv_imp_icc_get must be called prior to calling nv_imp_icc_set_bw. * * @param[out] nv Per GPU Linux state * * @returns NV_OK if successful, * NV_ERR_NOT_SUPPORTED if the functionality is not available, or * NV_ERR_GENERIC if some other error occurred. */ NV_STATUS NV_API_CALL nv_imp_icc_get ( nv_state_t *nv ) { #if ICC_SUPPORT_FUNCTIONS_PRESENT && NV_SUPPORTS_PLATFORM_DEVICE nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv); NV_STATUS status = NV_OK; #if defined(NV_DEVM_ICC_GET_PRESENT) // Needs to use devm_of_icc_get function as per the latest ICC driver nvl->nv_imp_icc_path = devm_of_icc_get(nvl->dev, "read-1"); if (nvl->nv_imp_icc_path == NULL) { nv_printf(NV_DBG_INFO, "NVRM: devm_of_icc_get failed\n"); return NV_ERR_NOT_SUPPORTED; } else if (!IS_ERR(nvl->nv_imp_icc_path)) { nvl->is_upstream_icc_path = NV_TRUE; return NV_OK; } // // Till we modify all DTs to have interconnect node specified as per // the latest ICC driver, fallback to older ICC mechanism. // #endif nvl->nv_imp_icc_path = NULL; #if defined(NV_ICC_GET_PRESENT) struct device_node *np; // Check if ICC is present in the device tree, and enabled. np = of_find_node_by_path("/icc"); if (np != NULL) { if (of_device_is_available(np)) { // Get the ICC data path. nvl->nv_imp_icc_path = icc_get(nvl->dev, TEGRA_ICC_DISPLAY, TEGRA_ICC_PRIMARY); } of_node_put(np); } #else nv_printf(NV_DBG_ERRORS, "NVRM: icc_get() not present\n"); return NV_ERR_NOT_SUPPORTED; #endif if (nvl->nv_imp_icc_path == NULL) { nv_printf(NV_DBG_INFO, "NVRM: icc_get disabled\n"); status = NV_ERR_NOT_SUPPORTED; } else if (IS_ERR(nvl->nv_imp_icc_path)) { nv_printf(NV_DBG_ERRORS, "NVRM: invalid path = %ld\n", PTR_ERR(nvl->nv_imp_icc_path)); nvl->nv_imp_icc_path = NULL; status = NV_ERR_GENERIC; } return status; #else return NV_ERR_NOT_SUPPORTED; #endif } /*! * @brief Releases the handle obtained by nv_imp_icc_get * * @param[in] nv Per GPU Linux state */ void nv_imp_icc_put ( nv_state_t *nv ) { #if ICC_SUPPORT_FUNCTIONS_PRESENT nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv); #if defined(NV_DEVM_ICC_GET_PRESENT) // // If devm_of_icc_get API is used for requesting the bandwidth, // it does not require to call put explicitly. // if (nvl->is_upstream_icc_path) { goto done; } #endif #if defined(NV_ICC_PUT_PRESENT) && NV_SUPPORTS_PLATFORM_DISPLAY_DEVICE if (nvl->nv_imp_icc_path != NULL) { icc_put(nvl->nv_imp_icc_path); } #else nv_printf(NV_DBG_ERRORS, "icc_put() not present\n"); #endif done: nvl->nv_imp_icc_path = NULL; #endif } /*! * @brief Allocates a specified amount of ISO memory bandwidth for display * * floor_bw_kbps is the minimum required (i.e., floor) dramclk frequency * multiplied by the width of the pipe over which the display data will travel. * (It is understood that the bandwidth calculated by multiplying the clock * frequency by the pipe width will not be realistically achievable, due to * overhead in the memory subsystem. ICC will not actually use the bandwidth * value, except to reverse the calculation to get the required dramclk * frequency.) * * nv_imp_icc_get must be called prior to calling this function. * * @param[in] nv Per GPU Linux state * @param[in] avg_bw_kbps Amount of ISO memory bandwidth requested * @param[in] floor_bw_kbps Min required dramclk freq * pipe width * * @returns NV_OK if successful, * NV_ERR_INSUFFICIENT_RESOURCES if one of the bandwidth values is too * high, and bandwidth cannot be allocated, * NV_ERR_NOT_SUPPORTED if the functionality is not available, or * NV_ERR_GENERIC if some other kind of error occurred. */ NV_STATUS NV_API_CALL nv_imp_icc_set_bw ( nv_state_t *nv, NvU32 avg_bw_kbps, NvU32 floor_bw_kbps ) { #if ICC_SUPPORT_FUNCTIONS_PRESENT && NV_SUPPORTS_PLATFORM_DEVICE nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv); int rc; NV_STATUS status = NV_OK; // // avg_bw_kbps can be either ISO bw request or NISO bw request. // Use floor_bw_kbps to make floor requests. // #if defined(NV_ICC_SET_BW_PRESENT) // // nv_imp_icc_path will be NULL on AV + L systems because ICC is disabled. // In this case, skip the allocation call, and just return a success // status. // if (nvl->nv_imp_icc_path == NULL) { return NV_OK; } rc = icc_set_bw(nvl->nv_imp_icc_path, avg_bw_kbps, floor_bw_kbps); #else nv_printf(NV_DBG_ERRORS, "icc_set_bw() not present\n"); return NV_ERR_NOT_SUPPORTED; #endif if (rc < 0) { // A negative return value indicates an error. if (rc == -ENOMEM) { status = NV_ERR_INSUFFICIENT_RESOURCES; } else { status = NV_ERR_GENERIC; } } return status; #else return NV_ERR_NOT_SUPPORTED; #endif }