/*
 * SPDX-FileCopyrightText: Copyright (c) 2019-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"
#include "nv-platform.h"

#include <soc/tegra/bpmp-abi.h>
#include <soc/tegra/bpmp.h>

// Use the CCF APIs if enabled in Kernel config and RM build
// has Dual license define enabled.
#if defined(CONFIG_COMMON_CLK)
#define HAS_COMMON_CLOCK_FRAMEWORK 1
#else
#define HAS_COMMON_CLOCK_FRAMEWORK 0
#endif

#if HAS_COMMON_CLOCK_FRAMEWORK
#if defined(NV_DEVM_CLK_BULK_GET_ALL_PRESENT)
/*!
 * @brief The below defined static const array points to the
 * clock mentioned in enum defined in below file.
 *
 * arch/nvalloc/unix/include/nv.h
 * enum TEGRASOC_WHICH_CLK
 *
 * The order should be maintained/updated together.
 */
static const char *osMapClk[] = {
    [TEGRASOC_WHICH_CLK_NVDISPLAYHUB]      = "nvdisplayhub_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_DISP]    = "nvdisplay_disp_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_P0]      = "nvdisplay_p0_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_P1]      = "nvdisplay_p1_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_P2]      = "nvdisplay_p2_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_P3]      = "nvdisplay_p3_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_P4]      = "nvdisplay_p4_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_P5]      = "nvdisplay_p5_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_P6]      = "nvdisplay_p6_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_P7]      = "nvdisplay_p7_clk",
    [TEGRASOC_WHICH_CLK_DPAUX0]            = "dpaux0_clk",
    [TEGRASOC_WHICH_CLK_FUSE]              = "fuse_clk",
    [TEGRASOC_WHICH_CLK_DSIPLL_VCO]        = "dsipll_vco_clk",
    [TEGRASOC_WHICH_CLK_DSIPLL_CLKOUTPN]   = "dsipll_clkoutpn_clk",
    [TEGRASOC_WHICH_CLK_DSIPLL_CLKOUTA]    = "dsipll_clkouta_clk",
    [TEGRASOC_WHICH_CLK_SPPLL0_VCO]        = "sppll0_vco_clk",
    [TEGRASOC_WHICH_CLK_SPPLL0_CLKOUTA]    = "sppll0_clkouta_clk",
    [TEGRASOC_WHICH_CLK_SPPLL0_CLKOUTB]    = "sppll0_clkoutb_clk",
    [TEGRASOC_WHICH_CLK_SPPLL0_CLKOUTPN]   = "sppll0_clkoutpn_clk",
    [TEGRASOC_WHICH_CLK_SPPLL1_CLKOUTPN]   = "sppll1_clkoutpn_clk",
    [TEGRASOC_WHICH_CLK_SPPLL0_DIV27]      = "sppll0_div27_clk",
    [TEGRASOC_WHICH_CLK_SPPLL1_DIV27]      = "sppll1_div27_clk",
    [TEGRASOC_WHICH_CLK_SPPLL0_DIV10]      = "sppll0_div10_clk",
    [TEGRASOC_WHICH_CLK_SPPLL0_DIV25]      = "sppll0_div25_clk",
    [TEGRASOC_WHICH_CLK_SPPLL1_VCO]        = "sppll1_vco_clk",
    [TEGRASOC_WHICH_CLK_VPLL0_REF]         = "vpll0_ref_clk",
    [TEGRASOC_WHICH_CLK_VPLL0]             = "vpll0_clk",
    [TEGRASOC_WHICH_CLK_VPLL1]             = "vpll1_clk",
    [TEGRASOC_WHICH_CLK_VPLL2]             = "vpll2_clk",
    [TEGRASOC_WHICH_CLK_VPLL3]             = "vpll3_clk",
    [TEGRASOC_WHICH_CLK_VPLL4]             = "vpll4_clk",
    [TEGRASOC_WHICH_CLK_VPLL5]             = "vpll5_clk",
    [TEGRASOC_WHICH_CLK_VPLL6]             = "vpll6_clk",
    [TEGRASOC_WHICH_CLK_VPLL7]             = "vpll7_clk",
    [TEGRASOC_WHICH_CLK_NVDISPLAY_P0_REF]  = "nvdisplay_p0_ref_clk",
    [TEGRASOC_WHICH_CLK_RG0]               = "rg0_clk",
    [TEGRASOC_WHICH_CLK_RG1]               = "rg1_clk",
    [TEGRASOC_WHICH_CLK_RG2]               = "rg2_clk",
    [TEGRASOC_WHICH_CLK_RG3]               = "rg3_clk",
    [TEGRASOC_WHICH_CLK_RG4]               = "rg4_clk",
    [TEGRASOC_WHICH_CLK_RG5]               = "rg5_clk",
    [TEGRASOC_WHICH_CLK_RG6]               = "rg6_clk",
    [TEGRASOC_WHICH_CLK_RG7]               = "rg7_clk",
    [TEGRASOC_WHICH_CLK_DISPPLL]           = "disppll_clk",
    [TEGRASOC_WHICH_CLK_DISPHUBPLL]        = "disphubpll_clk",
    [TEGRASOC_WHICH_CLK_DSI_LP]            = "dsi_lp_clk",
    [TEGRASOC_WHICH_CLK_DSI_CORE]          = "dsi_core_clk",
    [TEGRASOC_WHICH_CLK_DSI_PIXEL]         = "dsi_pixel_clk",
    [TEGRASOC_WHICH_CLK_PRE_SOR0]          = "pre_sor0_clk",
    [TEGRASOC_WHICH_CLK_PRE_SOR1]          = "pre_sor1_clk",
    [TEGRASOC_WHICH_CLK_PRE_SOR2]          = "pre_sor2_clk",
    [TEGRASOC_WHICH_CLK_PRE_SOR3]          = "pre_sor3_clk",
    [TEGRASOC_WHICH_CLK_DP_LINKA_REF]      = "dp_link_ref_clk",
    [TEGRASOC_WHICH_CLK_DP_LINKB_REF]      = "dp_linkb_ref_clk",
    [TEGRASOC_WHICH_CLK_DP_LINKC_REF]      = "dp_linkc_ref_clk",
    [TEGRASOC_WHICH_CLK_DP_LINKD_REF]      = "dp_linkd_ref_clk",
    [TEGRASOC_WHICH_CLK_SOR_LINKA_INPUT]   = "sor_linka_input_clk",
    [TEGRASOC_WHICH_CLK_SOR_LINKB_INPUT]   = "sor_linkb_input_clk",
    [TEGRASOC_WHICH_CLK_SOR_LINKC_INPUT]   = "sor_linkc_input_clk",
    [TEGRASOC_WHICH_CLK_SOR_LINKD_INPUT]   = "sor_linkd_input_clk",
    [TEGRASOC_WHICH_CLK_SOR_LINKA_AFIFO]   = "sor_linka_afifo_clk",
    [TEGRASOC_WHICH_CLK_SOR_LINKB_AFIFO]   = "sor_linkb_afifo_clk",
    [TEGRASOC_WHICH_CLK_SOR_LINKC_AFIFO]   = "sor_linkc_afifo_clk",
    [TEGRASOC_WHICH_CLK_SOR_LINKD_AFIFO]   = "sor_linkd_afifo_clk",
    [TEGRASOC_WHICH_CLK_SOR_LINKA_AFIFO_M] = "sor_linka_afifo_m_clk",
    [TEGRASOC_WHICH_CLK_RG0_M]             = "rg0_m_clk",
    [TEGRASOC_WHICH_CLK_RG1_M]             = "rg1_m_clk",
    [TEGRASOC_WHICH_CLK_SOR0_M]            = "sor0_m_clk",
    [TEGRASOC_WHICH_CLK_SOR1_M]            = "sor1_m_clk",
    [TEGRASOC_WHICH_CLK_PLLHUB]            = "pllhub_clk",
    [TEGRASOC_WHICH_CLK_SOR0]              = "sor0_clk",
    [TEGRASOC_WHICH_CLK_SOR1]              = "sor1_clk",
    [TEGRASOC_WHICH_CLK_SOR2]              = "sor2_clk",
    [TEGRASOC_WHICH_CLK_SOR3]              = "sor3_clk",
    [TEGRASOC_WHICH_CLK_SOR_PADA_INPUT]    = "sor_pad_input_clk",
    [TEGRASOC_WHICH_CLK_SOR_PADB_INPUT]    = "sor_padb_input_clk",
    [TEGRASOC_WHICH_CLK_SOR_PADC_INPUT]    = "sor_padc_input_clk",
    [TEGRASOC_WHICH_CLK_SOR_PADD_INPUT]    = "sor_padd_input_clk",
    [TEGRASOC_WHICH_CLK_SOR0_PAD]          = "sor0_pad_clk",
    [TEGRASOC_WHICH_CLK_SOR1_PAD]          = "sor1_pad_clk",
    [TEGRASOC_WHICH_CLK_SOR2_PAD]          = "sor2_pad_clk",
    [TEGRASOC_WHICH_CLK_SOR3_PAD]          = "sor3_pad_clk",
    [TEGRASOC_WHICH_CLK_PRE_SF0]           = "pre_sf0_clk",
    [TEGRASOC_WHICH_CLK_SF0]               = "sf0_clk",
    [TEGRASOC_WHICH_CLK_SF1]               = "sf1_clk",
    [TEGRASOC_WHICH_CLK_SF2]               = "sf2_clk",
    [TEGRASOC_WHICH_CLK_SF3]               = "sf3_clk",
    [TEGRASOC_WHICH_CLK_SF4]               = "sf4_clk",
    [TEGRASOC_WHICH_CLK_SF5]               = "sf5_clk",
    [TEGRASOC_WHICH_CLK_SF6]               = "sf6_clk",
    [TEGRASOC_WHICH_CLK_SF7]               = "sf7_clk",
    [TEGRASOC_WHICH_CLK_DSI_PAD_INPUT]     = "dsi_pad_input_clk",
    [TEGRASOC_WHICH_CLK_PRE_SOR0_REF]      = "pre_sor0_ref_clk",
    [TEGRASOC_WHICH_CLK_PRE_SOR1_REF]      = "pre_sor1_ref_clk",
    [TEGRASOC_WHICH_CLK_SOR0_PLL_REF]      = "sor0_ref_pll_clk",
    [TEGRASOC_WHICH_CLK_SOR1_PLL_REF]      = "sor1_ref_pll_clk",
    [TEGRASOC_WHICH_CLK_SOR2_PLL_REF]      = "sor2_ref_pll_clk",
    [TEGRASOC_WHICH_CLK_SOR3_PLL_REF]      = "sor3_ref_pll_clk",
    [TEGRASOC_WHICH_CLK_SOR0_REF]          = "sor0_ref_clk",
    [TEGRASOC_WHICH_CLK_SOR1_REF]          = "sor1_ref_clk",
    [TEGRASOC_WHICH_CLK_SOR2_REF]          = "sor2_ref_clk",
    [TEGRASOC_WHICH_CLK_SOR3_REF]          = "sor3_ref_clk",
    [TEGRASOC_WHICH_CLK_OSC]               = "osc_clk",
    [TEGRASOC_WHICH_CLK_DSC]               = "dsc_clk",
    [TEGRASOC_WHICH_CLK_MAUD]              = "maud_clk",
    [TEGRASOC_WHICH_CLK_AZA_2XBIT]         = "aza_2xbit_clk",
    [TEGRASOC_WHICH_CLK_AZA_BIT]           = "aza_bit_clk",
    [TEGRASOC_WHICH_CLK_MIPI_CAL]          = "mipi_cal_clk",
    [TEGRASOC_WHICH_CLK_UART_FST_MIPI_CAL] = "uart_fst_mipi_cal_clk",
    [TEGRASOC_WHICH_CLK_SOR0_DIV]          = "sor0_div_clk",
    [TEGRASOC_WHICH_CLK_DISP_ROOT]         = "disp_root",
    [TEGRASOC_WHICH_CLK_HUB_ROOT]          = "hub_root",
    [TEGRASOC_WHICH_CLK_PLLA_DISP]         = "plla_disp",
    [TEGRASOC_WHICH_CLK_PLLA_DISPHUB]      = "plla_disphub",
    [TEGRASOC_WHICH_CLK_PLLA]              = "plla",
    [TEGRASOC_WHICH_CLK_VPLLX_SOR0_MUXED]  = "vpllx_sor0_muxed_clk",
    [TEGRASOC_WHICH_CLK_VPLLX_SOR1_MUXED]  = "vpllx_sor1_muxed_clk",
    [TEGRASOC_WHICH_CLK_VPLLX_SOR2_MUXED]  = "vpllx_sor2_muxed_clk",
    [TEGRASOC_WHICH_CLK_VPLLX_SOR3_MUXED]  = "vpllx_sor3_muxed_clk",
    [TEGRASOC_WHICH_CLK_SF0_SOR]           = "sf0_sor_clk",
    [TEGRASOC_WHICH_CLK_SF1_SOR]           = "sf1_sor_clk",
    [TEGRASOC_WHICH_CLK_SF2_SOR]           = "sf2_sor_clk",
    [TEGRASOC_WHICH_CLK_SF3_SOR]           = "sf3_sor_clk",
    [TEGRASOC_WHICH_CLK_SF4_SOR]           = "sf4_sor_clk",
    [TEGRASOC_WHICH_CLK_SF5_SOR]           = "sf5_sor_clk",
    [TEGRASOC_WHICH_CLK_SF6_SOR]           = "sf6_sor_clk",
    [TEGRASOC_WHICH_CLK_SF7_SOR]           = "sf7_sor_clk",
    [TEGRASOC_WHICH_CLK_EMC]               = "emc_clk",
    [TEGRASOC_WHICH_CLK_GPU_SYS]           = "sysclk",
    [TEGRASOC_WHICH_CLK_GPU_NVD]           = "nvdclk",
    [TEGRASOC_WHICH_CLK_GPU_UPROC]         = "uprocclk",
    [TEGRASOC_WHICH_CLK_GPU_GPC0]          = "gpc0clk",
    [TEGRASOC_WHICH_CLK_GPU_GPC1]          = "gpc1clk",
    [TEGRASOC_WHICH_CLK_GPU_GPC2]          = "gpc2clk",
};
#endif

/*!
 * @brief Get the clock handles.
 *
 * Look up and obtain the clock handles for each display
 * clock at boot-time and later using all those handles
 * for rest of the operations. for example, enable/disable
 * clocks, get current/max frequency of the clock.
 *
 * For more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in]  nv      Per gpu linux state
 *
 * @returns NV_STATUS
 */
NV_STATUS NV_API_CALL nv_clk_get_handles(
    nv_state_t *nv)
{
    NV_STATUS status = NV_OK;
#if defined(NV_DEVM_CLK_BULK_GET_ALL_PRESENT)
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NvU32 i, j;
    int clk_count;
    struct clk_bulk_data *clks;

    clk_count = devm_clk_bulk_get_all(nvl->dev, &clks);

    if (clk_count <= 0)
    {
        nv_printf(NV_DBG_INFO,"NVRM: No clk handles for the dev\n");
        status = NV_ERR_OBJECT_NOT_FOUND;
    }

    //
    // TEGRASOC_WHICH_CLK_MAX is maximum clock defined in below enum
    // arch/nvalloc/unix/include/nv.h
    // enum TEGRASOC_WHICH_CLK
    //
    for (i = 0U; i < clk_count; i++)
    {
        for (j = 0U; j < TEGRASOC_WHICH_CLK_MAX; j++)
        {
            if (!strcmp(osMapClk[j], clks[i].id))
            {
                nvl->soc_clk_handles.clk[j].handles = clks[i].clk;
                nvl->soc_clk_handles.clk[j].clkName = __clk_get_name(clks[i].clk);
                break;
            }
        }
        if (j == TEGRASOC_WHICH_CLK_MAX)
        {
            nv_printf(NV_DBG_INFO,"NVRM: nv_clk_get_handles, failed to find TEGRA_SOC_WHICH_CLK for %s\n", clks[i].id);
        }
    }
#else
    nv_printf(NV_DBG_INFO, "NVRM: devm_clk_bulk_get_all API is not present\n");
    status = NV_ERR_FEATURE_NOT_ENABLED;
#endif

    return status;
}

/*!
 * @brief Enable the clock.
 *
 * Enabling the clock before performing any operation
 * on it. The below function will prepare the clock for use
 * and enable them.
 *
 * for more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in] nv          Per gpu linux state
 * @param[in] whichClkOS  Enum value of the target clock
 *
 * @returns NV_STATUS
 */
NV_STATUS NV_API_CALL nv_enable_clk(
    nv_state_t *nv, TEGRASOC_WHICH_CLK whichClkOS)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NV_STATUS status;
    int ret;

    if (nvl->soc_clk_handles.clk[whichClkOS].handles != NULL)
    {
        ret = clk_prepare_enable(nvl->soc_clk_handles.clk[whichClkOS].handles);

        if (ret == 0)
        {
            status = NV_OK;
        }
        else
        {
            status = NV_ERR_FEATURE_NOT_ENABLED;
            nv_printf(NV_DBG_ERRORS, "NVRM: clk_prepare_enable failed with error: %d\n", ret);
        }
    }
    else
    {
        status = NV_ERR_OBJECT_NOT_FOUND;
    }

    return status;
}

/*!
 * @brief Disable the clock.
 *
 * Disabling the clock after performing operation or required
 * work with that clock is done with that particular clock.
 * The below function will unprepare the clock for further use
 * and disable them.
 *
 * Note: make sure to disable clock before clk_put is called.
 *
 * For more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in] nv          Per gpu linux state
 * @param[in] whichClkOS  Enum value of the target clock
 */
void NV_API_CALL nv_disable_clk(
    nv_state_t *nv, TEGRASOC_WHICH_CLK whichClkOS)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);

    clk_disable_unprepare(nvl->soc_clk_handles.clk[whichClkOS].handles);
}

/*!
 * @brief Get current clock frequency.
 *
 * Obtain the current clock rate for a clock source.
 * This is only valid once the clock source has been enabled.
 *
 * For more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in] nv            Per gpu linux state
 * @param[in] whichClkOS    Enum value of the target clock
 * @param[out] pCurrFreqKHz Current clock frequency
 */
NV_STATUS NV_API_CALL nv_get_curr_freq(
    nv_state_t *nv, TEGRASOC_WHICH_CLK whichClkOS, NvU32 *pCurrFreqKHz)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NV_STATUS status;
    unsigned long currFreqHz;

    if (nvl->soc_clk_handles.clk[whichClkOS].handles != NULL)
    {
        currFreqHz = clk_get_rate(nvl->soc_clk_handles.clk[whichClkOS].handles);
        *pCurrFreqKHz = currFreqHz / 1000U;

        if (*pCurrFreqKHz > 0U)
        {
            status = NV_OK;
        }
        else
        {
            status = NV_ERR_FEATURE_NOT_ENABLED;
        }
    }
    else
    {
        status = NV_ERR_OBJECT_NOT_FOUND;
    }

    return status;
}

/*!
 * @brief Get maximum clock frequency.
 *
 * Obtain the maximum clock rate a clock source can provide.
 * This is only valid once the clock source has been enabled.
 *
 * For more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in] nv           Per gpu linux state
 * @param[in] whichClkOS   Enum value of the target clock
 * @param[out] pMaxFreqKHz Maximum clock frequency
 */
NV_STATUS NV_API_CALL nv_get_max_freq(
    nv_state_t *nv, TEGRASOC_WHICH_CLK whichClkOS, NvU32 *pMaxFreqKHz)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NV_STATUS status;
    long ret;

    if (nvl->soc_clk_handles.clk[whichClkOS].handles != NULL)
    {
        //
        // clk_round_rate(struct clk *clk, rate);
        // rate is the maximum possible rate we give,
        // it returns rounded clock rate in Hz, i.e.,
        // maximum clock rate the source clock can
        // support or negative errno.
        // Here, rate = NV_S64_MAX
        // 0 < currFreq < maxFreq < NV_S64_MAX
        // clk_round_rate() round of and return the
        // nearest freq what a clock can provide.
        // sending NV_S64_MAX will return maxFreq.
        //
        ret = clk_round_rate(nvl->soc_clk_handles.clk[whichClkOS].handles, NV_U32_MAX);

        if (ret >= 0)
        {
            *pMaxFreqKHz = (NvU32) (ret / 1000);
            status = NV_OK;
        }
        else
        {
            status = NV_ERR_FEATURE_NOT_ENABLED;
            nv_printf(NV_DBG_ERRORS, "NVRM: clk_round_rate failed with error: %ld\n", ret);
        }
    }
    else
    {
        status = NV_ERR_OBJECT_NOT_FOUND;
    }

    return status;
}

/*!
 * @brief Get minimum clock frequency.
 *
 * Obtain the minimum clock rate a clock source can provide.
 * This is only valid once the clock source has been enabled.
 *
 * For more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in] nv           Per gpu linux state
 * @param[in] whichClkOS   Enum value of the target clock
 * @param[out] pMinFreqKHz Minimum clock frequency
 */
NV_STATUS NV_API_CALL nv_get_min_freq(
    nv_state_t *nv, TEGRASOC_WHICH_CLK whichClkOS, NvU32 *pMinFreqKHz)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NV_STATUS status;
    long ret;

    if (nvl->soc_clk_handles.clk[whichClkOS].handles != NULL)
    {
        //
        // clk_round_rate(struct clk *clk, rate);
        // rate is the minimum possible rate we give,
        // it returns rounded clock rate in Hz, i.e.,
        // minimum clock rate the source clock can
        // support or negative errno.
        // Here, rate = NV_S64_MAX
        // 0 < minFreq currFreq < maxFreq < NV_S64_MAX
        // clk_round_rate() round of and return the
        // nearest freq what a clock can provide.
        // sending 0 will return minFreq.
        //
        ret = clk_round_rate(nvl->soc_clk_handles.clk[whichClkOS].handles, 0);

        if (ret >= 0)
        {
            *pMinFreqKHz = (NvU32) (ret / 1000);
            status = NV_OK;
        }
        else
        {
            status = NV_ERR_FEATURE_NOT_ENABLED;
            nv_printf(NV_DBG_ERRORS, "NVRM: clk_round_rate failed with error: %ld\n", ret);
        }
    }
    else
    {
        status = NV_ERR_OBJECT_NOT_FOUND;
    }

    return status;
}


/*!
 * @brief set clock frequency.
 *
 * Setting the frequency of clock source.
 * This is only valid once the clock source has been enabled.
 *
 * For more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in] nv           Per gpu linux state
 * @param[in] whichClkOS   Enum value of the target clock
 * @param[in] reqFreqKHz   Required frequency
 */
NV_STATUS NV_API_CALL nv_set_freq(
    nv_state_t *nv, TEGRASOC_WHICH_CLK whichClkOS, NvU32 reqFreqKHz)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NV_STATUS status;
    int ret;

    if (nvl->soc_clk_handles.clk[whichClkOS].handles != NULL)
    {
        ret = clk_set_rate(nvl->soc_clk_handles.clk[whichClkOS].handles,
                           reqFreqKHz * 1000U);
        if (ret == 0)
        {
            status = NV_OK;
        }
        else
        {
            status = NV_ERR_INVALID_REQUEST;
            nv_printf(NV_DBG_ERRORS, "NVRM: clk_set_rate failed with error: %d\n", ret);
        }
    }
    else
    {
        status = NV_ERR_OBJECT_NOT_FOUND;
    }

    return status;
}
#else
NV_STATUS NV_API_CALL nv_clk_get_handles
(
    nv_state_t *nv
)
{
    return NV_ERR_NOT_SUPPORTED;
}

NV_STATUS NV_API_CALL nv_enable_clk
(
    nv_state_t *nv,
    TEGRASOC_WHICH_CLK whichClkOS
)
{
    return NV_ERR_NOT_SUPPORTED;
}

void NV_API_CALL nv_disable_clk
(
    nv_state_t *nv,
    TEGRASOC_WHICH_CLK whichClkOS
)
{
    return;
}

NV_STATUS NV_API_CALL nv_get_curr_freq
(
    nv_state_t *nv,
    TEGRASOC_WHICH_CLK whichClkOS,
    NvU32 *pCurrFreqKHz
)
{
    return NV_ERR_NOT_SUPPORTED;
}

NV_STATUS NV_API_CALL nv_get_max_freq
(
    nv_state_t *nv,
    TEGRASOC_WHICH_CLK whichClkOS,
    NvU32 *pMaxFreqKHz
)
{
    return NV_ERR_NOT_SUPPORTED;
}

NV_STATUS NV_API_CALL nv_get_min_freq
(
    nv_state_t *nv,
    TEGRASOC_WHICH_CLK whichClkOS,
    NvU32 *pMinFreqKHz
)
{
    return NV_ERR_NOT_SUPPORTED;
}

NV_STATUS NV_API_CALL nv_set_freq
(
    nv_state_t *nv,
    TEGRASOC_WHICH_CLK whichClkOS,
    NvU32 freqKHz
)
{
    return NV_ERR_NOT_SUPPORTED;
}
#endif

/*!
 * @brief Clear the clock handles assigned by nv_clk_get_handles()
 *
 * Clear the clock handle for each display of the clocks at shutdown-time.
 * Since clock handles are obtained by devm managed devm_clk_bulk_get_all()
 * API, devm_clk_bulk_release_all() API is called on all the enumerated
 * clk handles automatically when module gets unloaded. Hence, no need
 * to explicitly free those handles.
 *
 * For more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in]  nv      Per gpu linux state
 */
void NV_API_CALL nv_clk_clear_handles(
    nv_state_t *nv)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NvU32 i;

    //
    // TEGRASOC_WHICH_CLK_MAX is maximum clock defined in below enum
    // arch/nvalloc/unix/include/nv.h
    // enum TEGRASOC_WHICH_CLK
    //
    for (i = 0U; i < TEGRASOC_WHICH_CLK_MAX; i++)
    {
        if (nvl->soc_clk_handles.clk[i].handles != NULL)
        {
            nvl->soc_clk_handles.clk[i].handles = NULL;
        }
    }
}

#if NV_SUPPORTS_PLATFORM_DISPLAY_DEVICE
/*!
 * @brief set parent clock.
 *
 * Setting the parent clock of clock source.
 * This is only valid once the clock source and the parent
 * clock have been enabled.
 *
 * For more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in] nv                Per gpu linux state
 * @param[in] whichClkOSsource  Enum value of the source clock
 * @param[in] whichClkOSparent  Enum value of the parent clock
 */
NV_STATUS NV_API_CALL nv_set_parent
(
    nv_state_t *nv,
    TEGRASOC_WHICH_CLK whichClkOSsource,
    TEGRASOC_WHICH_CLK whichClkOSparent
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NV_STATUS status;
    int ret;

    if ((nvl->soc_clk_handles.clk[whichClkOSsource].handles != NULL) &&
        (nvl->soc_clk_handles.clk[whichClkOSparent].handles != NULL))
    {
        ret = clk_set_parent(nvl->soc_clk_handles.clk[whichClkOSsource].handles,
                             nvl->soc_clk_handles.clk[whichClkOSparent].handles);
        if (ret == 0)
        {
            status = NV_OK;
        }
        else
        {
            status = NV_ERR_INVALID_REQUEST;
            nv_printf(NV_DBG_ERRORS, "NVRM: clk_set_parent failed with error: %d\n", ret);
        }
    }
    else
    {
        status = NV_ERR_OBJECT_NOT_FOUND;
    }

    return status;
}

/*!
 * @brief get parent clock.
 *
 * Getting the parent clock of clock source.
 * This is only valid once the clock source and the parent
 * clock have been enabled.
 *
 * For more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in] nv                 Per gpu linux state
 * @param[in] whichClkOSsource   Enum value of the source clock
 * @param[in] pWhichClkOSparent  Enum value of the parent clock
 */
NV_STATUS NV_API_CALL nv_get_parent
(
    nv_state_t          *nv,
    TEGRASOC_WHICH_CLK  whichClkOSsource,
    TEGRASOC_WHICH_CLK  *pWhichClkOSparent
)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    struct clk *ret;
    NvU32 i;

    if (nvl->soc_clk_handles.clk[whichClkOSsource].handles != NULL)
    {
        ret = clk_get_parent(nvl->soc_clk_handles.clk[whichClkOSsource].handles);
        if (!IS_ERR_OR_NULL(ret))
        {
            const char *parentClkName = __clk_get_name(ret);
            //
            // TEGRASOC_WHICH_CLK_MAX is maximum clock defined in below enum
            // arch/nvalloc/unix/include/nv.h
            // enum TEGRASOC_WHICH_CLK
            //
            for (i = 0U; i < TEGRASOC_WHICH_CLK_MAX; i++)
            {
                //
                // soc_clk_handles has array of clks supported on all chips.
                // So depending on the chip, some clks may not be present.
                //
                if (nvl->soc_clk_handles.clk[i].clkName == NULL)
                {
                    continue;
                }

                if (!strcmp(nvl->soc_clk_handles.clk[i].clkName, parentClkName))
                {
                    *pWhichClkOSparent = i;
                    return NV_OK;
                }
            }
            nv_printf(NV_DBG_ERRORS, "NVRM: unexpected parent clock ref addr: %p\n", ret);
            return NV_ERR_INVALID_OBJECT_PARENT;
        }
        else
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: clk_get_parent failed with error: %ld\n", PTR_ERR(ret));
            return NV_ERR_INVALID_POINTER;
        }
    }

    nv_printf(NV_DBG_ERRORS, "NVRM: invalid source clock requested\n");
    return NV_ERR_OBJECT_NOT_FOUND;
}

/*!
 * @brief Check if clock is enable or not.
 *
 * Checking the clock status if it is enabled or not before
 * enabling or disabling it.
 *
 * for more details on CCF functions, please check below file:
 *
 * In the Linux kernel: include/linux/clk.h
 * or
 * https://www.kernel.org/doc/htmldocs/kernel-api/
 *
 * @param[in] nv          Per gpu linux state
 * @param[in] whichClkOS  Enum value of the target clock
 *
 * @returns clock status.
 */
NvBool NV_API_CALL nv_is_clk_enabled(
    nv_state_t *nv, TEGRASOC_WHICH_CLK whichClkOS)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    bool ret = false;

    if (nvl->soc_clk_handles.clk[whichClkOS].handles == NULL)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: clock handle requested not found.\n");
        return NV_FALSE;
    }

    ret = __clk_is_enabled(nvl->soc_clk_handles.clk[whichClkOS].handles);
    return ret == true;
}

NV_STATUS NV_API_CALL nv_dp_uphy_pll_init
(
    nv_state_t *nv,
    NvU32 link_rate,
    NvU32 lanes_bitmap
)
{
#if defined(NV_CMD_UPHY_DISPLAY_PORT_INIT_PRESENT)
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    struct tegra_bpmp *bpmp;
    struct tegra_bpmp_message msg;
    struct mrq_uphy_request req;
    struct mrq_uphy_response resp;
    int rc;
    NV_STATUS status = NV_OK;

    bpmp = tegra_bpmp_get(nvl->dev);
    if (IS_ERR(bpmp))
    {
        nv_printf(NV_DBG_ERRORS,
                  "NVRM:  Error getting bpmp struct: %s\n",
                  PTR_ERR(bpmp));
        return NV_ERR_GENERIC;
    }

    req.cmd = CMD_UPHY_DISPLAY_PORT_INIT;
    req.display_port_init.link_rate = link_rate;
    req.display_port_init.lanes_bitmap = lanes_bitmap;

    memset(&msg, 0, sizeof(msg));
    msg.mrq = MRQ_UPHY;
    msg.tx.data = &req;
    msg.tx.size = sizeof(req);
    msg.rx.data = &resp;
    msg.rx.size = sizeof(resp);

    rc = tegra_bpmp_transfer(bpmp, &msg);
    if (rc)
    {
        nv_printf(NV_DBG_ERRORS, "DP UPHY pll initialization failed, rc - %d\n", rc);
        status = NV_ERR_GENERIC;
    }

    tegra_bpmp_put(bpmp);

    return status;
#else
    return NV_ERR_NOT_SUPPORTED;
#endif
}

NV_STATUS NV_API_CALL nv_dp_uphy_pll_deinit(nv_state_t *nv)
{
#if defined(NV_CMD_UPHY_DISPLAY_PORT_OFF_PRESENT)
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    struct tegra_bpmp *bpmp;
    struct tegra_bpmp_message msg;
    struct mrq_uphy_request req;
    struct mrq_uphy_response resp;
    int rc;
    NV_STATUS status = NV_OK;

    bpmp = tegra_bpmp_get(nvl->dev);
    if (IS_ERR(bpmp))
    {
        nv_printf(NV_DBG_ERRORS,
                  "NVRM:  Error getting bpmp struct: %s\n",
                  PTR_ERR(bpmp));
        return NV_ERR_GENERIC;
    }

    req.cmd = CMD_UPHY_DISPLAY_PORT_OFF;

    memset(&msg, 0, sizeof(msg));
    msg.mrq = MRQ_UPHY;
    msg.tx.data = &req;
    msg.tx.size = sizeof(req);
    msg.rx.data = &resp;
    msg.rx.size = sizeof(resp);

    rc = tegra_bpmp_transfer(bpmp, &msg);
    if (rc)
    {
        nv_printf(NV_DBG_ERRORS, "DP UPHY pll de-initialization failed, rc - %d\n", rc);
        status = NV_ERR_GENERIC;
    }

    tegra_bpmp_put(bpmp);

    return status;
#else
    return NV_ERR_NOT_SUPPORTED;
#endif
}
#else
NV_STATUS NV_API_CALL nv_set_parent
(
    nv_state_t *nv,
    TEGRASOC_WHICH_CLK whichClkOSsource,
    TEGRASOC_WHICH_CLK whichClkOSparent
)
{
    return NV_ERR_NOT_SUPPORTED;
}


NvBool NV_API_CALL nv_is_clk_enabled(
    nv_state_t *nv, TEGRASOC_WHICH_CLK whichClkOS)
{
    return NV_FALSE;
}

NV_STATUS NV_API_CALL nv_dp_uphy_pll_deinit(nv_state_t *nv)
{
    return NV_ERR_NOT_SUPPORTED;
}

NV_STATUS NV_API_CALL nv_get_parent
(
    nv_state_t          *nv,
    TEGRASOC_WHICH_CLK  whichClkOSsource,
    TEGRASOC_WHICH_CLK  *pWhichClkOSparent
)
{
    return NV_ERR_NOT_SUPPORTED;
}

NV_STATUS NV_API_CALL nv_dp_uphy_pll_init
(
    nv_state_t *nv,
    NvU32 link_rate,
    NvU32 lanes_bitmap
)
{
    return NV_ERR_NOT_SUPPORTED;
}
#endif
