root/drivers/gpu/drm/amd/display/dc/clk_mgr/dcn42/dcn42_clk_mgr.c
// SPDX-License-Identifier: MIT
//
// Copyright 2026 Advanced Micro Devices, Inc.

#include "dcn42_clk_mgr.h"

#include "dccg.h"
#include "clk_mgr_internal.h"

// For dce12_get_dp_ref_freq_khz
#include "dce100/dce_clk_mgr.h"

// For dcn20_update_clocks_update_dpp_dto
#include "dcn20/dcn20_clk_mgr.h"




#include "reg_helper.h"
#include "core_types.h"
#include "dcn42_smu.h"
#include "dm_helpers.h"

/* TODO: remove this include once we ported over remaining clk mgr functions*/
#include "dcn30/dcn30_clk_mgr.h"
#include "dcn31/dcn31_clk_mgr.h"

#include "dcn35/dcn35_clk_mgr.h"

#include "dc_dmub_srv.h"
#include "link_service.h"
#include "logger_types.h"

#include "clk/clk_15_0_0_offset.h"
#include "clk/clk_15_0_0_sh_mask.h"
#include "dcn/dcn_4_2_0_offset.h"
#include "dcn/dcn_4_2_0_sh_mask.h"


#undef DC_LOGGER
#define DC_LOGGER \
        dc_logger
#define DC_LOGGER_INIT(logger) \
        struct dal_logger *dc_logger = logger

#undef FN
#define FN(reg_name, field_name) \
        clk_mgr->clk_mgr_shift->field_name, clk_mgr->clk_mgr_mask->field_name

#define REG(reg) \
        (clk_mgr->regs->reg)

// for DCN register access
#define DCN_BASE__INST0_SEG0 0x00000012
#define DCN_BASE__INST0_SEG1 0x000000C0

#define BASE_INNER(seg) DCN_BASE__INST0_SEG ## seg

#define BASE(seg) BASE_INNER(seg)

#define SR(reg_name) \
        .reg_name = BASE(reg ## reg_name ## _BASE_IDX) + reg ## reg_name

// for CLKIP register access
#define CLK_BASE__INST0_SEG0 0x00016C00

#define CLK_BASE_INNER(seg) \
        CLK_BASE__INST0_SEG ## seg

#define CLK_SR_DCN42(reg_name) \
        .reg_name = CLK_BASE(reg ## reg_name ## _BASE_IDX) + reg ## reg_name

static const struct clk_mgr_registers clk_mgr_regs_dcn42 = {
        CLK_REG_LIST_DCN42()
};

static const struct clk_mgr_shift clk_mgr_shift_dcn42 = {
        CLK_COMMON_MASK_SH_LIST_DCN42(__SHIFT)
};

static const struct clk_mgr_mask clk_mgr_mask_dcn42 = {
        CLK_COMMON_MASK_SH_LIST_DCN42(_MASK)
};

#define TO_CLK_MGR_DCN42(clk_mgr_int)\
        container_of(clk_mgr_int, struct clk_mgr_dcn42, base)

bool dcn42_has_active_display(struct dc *dc, const struct dc_state *context)
{
        int i, active_count = 0;

        for (i = 0; i < context->stream_count; i++) {
                const struct dc_stream_state *stream = context->streams[i];

                /* Checking stream / link detection ensuring that PHY is active*/
                if (dc_is_hdmi_signal(stream->signal) ||
                    dc_is_dvi_signal(stream->signal) ||
                    (dc_is_dp_signal(stream->signal) && !stream->dpms_off))
                        active_count++;
        }

        for (i = 0; i < dc->link_count; i++) {
                const struct dc_link *link = dc->links[i];

                /* abusing the fact that the dig and phy are coupled to see if the phy is enabled */
                if (link->link_enc && link->link_enc->funcs->is_dig_enabled &&
                                link->link_enc->funcs->is_dig_enabled(link->link_enc))
                        active_count++;
        }

        return active_count > 0;
}

static uint32_t dcn42_get_clock_freq_from_clkip(struct clk_mgr *clk_mgr_base, enum clock_type clock)
{
        struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);
        uint64_t clock_freq_mhz = 0;
        uint32_t timer_threshold = 0;

        // always safer to read the timer threshold instead of using cached value
        REG_GET(CLK8_CLK_TICK_CNT_CONFIG_REG, TIMER_THRESHOLD, &timer_threshold);

        if (timer_threshold == 0) {
                BREAK_TO_DEBUGGER();
                return 0;
        }

        switch (clock) {
        case clock_type_dispclk:
                clock_freq_mhz = REG_READ(CLK8_CLK0_CURRENT_CNT);
                break;
        case clock_type_dppclk:
                clock_freq_mhz = REG_READ(CLK8_CLK1_CURRENT_CNT);
                break;
        case clock_type_dprefclk:
                clock_freq_mhz = REG_READ(CLK8_CLK2_CURRENT_CNT);
                break;
        case clock_type_dcfclk:
                clock_freq_mhz = REG_READ(CLK8_CLK3_CURRENT_CNT);
                break;
        case clock_type_dtbclk:
                clock_freq_mhz = REG_READ(CLK8_CLK4_CURRENT_CNT);
                break;
        default:
                break;
        }

        clock_freq_mhz *= DCN42_CLKIP_REFCLK;
        clock_freq_mhz = div_u64(clock_freq_mhz, timer_threshold);

        // there are no DCN clocks over 0xFFFFFFFF MHz
        ASSERT(clock_freq_mhz <= 0xFFFFFFFF);

        return (uint32_t)clock_freq_mhz;
}

void dcn42_update_clocks_update_dtb_dto(struct clk_mgr_internal *clk_mgr,
                struct dc_state *context,
                int ref_dtbclk_khz)
{
        (void)clk_mgr;
        (void)context;
        (void)ref_dtbclk_khz;
        /* DCN42 does not implement set_dtbclk_dto function, so this is a no-op */
}

void dcn42_update_clocks_update_dpp_dto(struct clk_mgr_internal *clk_mgr,
                struct dc_state *context, bool safe_to_lower)
{
        uint32_t i;
        bool dppclk_active[MAX_PIPES] = {0};


        clk_mgr->dccg->ref_dppclk = clk_mgr->base.clks.dppclk_khz;
        for (i = 0; i < clk_mgr->base.ctx->dc->res_pool->pipe_count; i++) {
                int dpp_inst = 0, dppclk_khz, prev_dppclk_khz;

                dppclk_khz = context->res_ctx.pipe_ctx[i].plane_res.bw.dppclk_khz;

                if (context->res_ctx.pipe_ctx[i].plane_res.dpp)
                        dpp_inst = context->res_ctx.pipe_ctx[i].plane_res.dpp->inst;
                else if (!context->res_ctx.pipe_ctx[i].plane_res.dpp && dppclk_khz == 0) {
                        /* dpp == NULL && dppclk_khz == 0 is valid because of pipe harvesting.
                         * In this case just continue in loop
                         */
                        continue;
                } else if (!context->res_ctx.pipe_ctx[i].plane_res.dpp && dppclk_khz > 0) {
                        /* The software state is not valid if dpp resource is NULL and
                         * dppclk_khz > 0.
                         */
                        ASSERT(false);
                        continue;
                }

                prev_dppclk_khz = clk_mgr->dccg->pipe_dppclk_khz[i];

                if (safe_to_lower || prev_dppclk_khz < dppclk_khz)
                        clk_mgr->dccg->funcs->update_dpp_dto(
                                                        clk_mgr->dccg, dpp_inst, dppclk_khz);
                dppclk_active[dpp_inst] = true;
        }
        if (safe_to_lower)
                for (i = 0; i < clk_mgr->base.ctx->dc->res_pool->pipe_count; i++) {
                        struct dpp *old_dpp = clk_mgr->base.ctx->dc->current_state->res_ctx.pipe_ctx[i].plane_res.dpp;

                        if (old_dpp && !dppclk_active[old_dpp->inst])
                                clk_mgr->dccg->funcs->update_dpp_dto(clk_mgr->dccg, old_dpp->inst, 0);
                }
}

void dcn42_update_clocks(struct clk_mgr *clk_mgr_base,
                        struct dc_state *context,
                        bool safe_to_lower)
{
        union dmub_rb_cmd cmd;
        struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);
        struct dc_clocks *new_clocks = &context->bw_ctx.bw.dcn.clk;
        struct dc *dc = clk_mgr_base->ctx->dc;
        bool update_dppclk = false;
        bool update_dispclk = false;
        bool dpp_clock_lowered = false;
        bool has_active_display;

        if (dc->work_arounds.skip_clock_update)
                return;

        has_active_display = dcn42_has_active_display(dc, context);

        if (new_clocks->dtbclk_en && new_clocks->ref_dtbclk_khz < 590000)
                new_clocks->ref_dtbclk_khz = 600000;
        /*
         * if it is safe to lower, but we are already in the lower state, we don't have to do anything
         * also if safe to lower is false, we just go in the higher state
         */
        if (safe_to_lower) {
                if (new_clocks->zstate_support != DCN_ZSTATE_SUPPORT_DISALLOW &&
                                new_clocks->zstate_support != clk_mgr_base->clks.zstate_support) {
                        dcn42_smu_set_zstate_support(clk_mgr, new_clocks->zstate_support);
                        clk_mgr_base->clks.zstate_support = new_clocks->zstate_support;
                }

                if (clk_mgr_base->clks.dtbclk_en && !new_clocks->dtbclk_en) {
                        if (clk_mgr->base.ctx->dc->config.allow_0_dtb_clk)
                                dcn42_smu_set_dtbclk(clk_mgr, false);
                        clk_mgr_base->clks.dtbclk_en = new_clocks->dtbclk_en;
                }
                /* check that we're not already in lower */
                if (clk_mgr_base->clks.pwr_state != DCN_PWR_STATE_LOW_POWER) {
                        /* if we can go lower, go lower */
                        if (has_active_display == false)
                                clk_mgr_base->clks.pwr_state = DCN_PWR_STATE_LOW_POWER;
                }
        } else {
                if (new_clocks->zstate_support == DCN_ZSTATE_SUPPORT_DISALLOW &&
                                new_clocks->zstate_support != clk_mgr_base->clks.zstate_support) {
                        dcn42_smu_set_zstate_support(clk_mgr, DCN_ZSTATE_SUPPORT_DISALLOW);
                        clk_mgr_base->clks.zstate_support = new_clocks->zstate_support;
                }
                /* Only attempt to enable dtbclk if currently disabled AND new state requests it.
                 * For dcn42b (no dtbclk hardware), init_clk_states sets dtbclk_en=false and
                 * new_clocks->dtbclk_en should always be false, so this block never executes.
                 */
                if (!clk_mgr_base->clks.dtbclk_en && new_clocks->dtbclk_en) {
                        int actual_dtbclk = 0;

                        dcn42_update_clocks_update_dtb_dto(clk_mgr, context, new_clocks->ref_dtbclk_khz);
                        dcn42_smu_set_dtbclk(clk_mgr, true);
                        actual_dtbclk = dcn42_get_clock_freq_from_clkip(clk_mgr_base, clock_type_dtbclk);

                        if (actual_dtbclk > 590000) {
                                clk_mgr_base->clks.ref_dtbclk_khz = new_clocks->ref_dtbclk_khz;
                                clk_mgr_base->clks.dtbclk_en = new_clocks->dtbclk_en;
                        }
                }

                /* check that we're not already in D0 */
                if (clk_mgr_base->clks.pwr_state != DCN_PWR_STATE_MISSION_MODE) {
                        union display_idle_optimization_u idle_info = { 0 };

                        dcn42_smu_set_display_idle_optimization(clk_mgr, idle_info.data);
                        /* update power state */
                        clk_mgr_base->clks.pwr_state = DCN_PWR_STATE_MISSION_MODE;
                }
        }
        if (dc->debug.force_min_dcfclk_mhz > 0) {
                int force_min_dcfclk_khz = dc->debug.force_min_dcfclk_mhz * 1000;

                new_clocks->dcfclk_khz = (new_clocks->dcfclk_khz > force_min_dcfclk_khz) ?
                                new_clocks->dcfclk_khz : force_min_dcfclk_khz;
        }

        if (should_set_clock(safe_to_lower, new_clocks->dcfclk_khz, clk_mgr_base->clks.dcfclk_khz)) {
                clk_mgr_base->clks.dcfclk_khz = new_clocks->dcfclk_khz;
                clk_mgr_base->clks.fclk_khz = new_clocks->fclk_khz;
                clk_mgr_base->clks.dramclk_khz = new_clocks->dramclk_khz;
                dcn42_smu_set_hard_min_dcfclk(clk_mgr, clk_mgr_base->clks.dcfclk_khz);
        }

        if (should_set_clock(safe_to_lower,
                        new_clocks->dcfclk_deep_sleep_khz, clk_mgr_base->clks.dcfclk_deep_sleep_khz)) {
                clk_mgr_base->clks.dcfclk_deep_sleep_khz = new_clocks->dcfclk_deep_sleep_khz;

                /* Clamp the requested clock to PMFW based on DCN limit. */
                if (dc->debug.min_deep_sleep_dcfclk_khz > 0 &&
                        clk_mgr_base->clks.dcfclk_deep_sleep_khz < (int)dc->debug.min_deep_sleep_dcfclk_khz)
                        clk_mgr_base->clks.dcfclk_deep_sleep_khz = (int)dc->debug.min_deep_sleep_dcfclk_khz;

                dcn42_smu_set_min_deep_sleep_dcfclk(clk_mgr, clk_mgr_base->clks.dcfclk_deep_sleep_khz);
        }

        // workaround: Limit dppclk to 100Mhz to avoid lower eDP panel switch to plus 4K monitor underflow.
        if (new_clocks->dppclk_khz < 100000)
                new_clocks->dppclk_khz = 100000;

        if (should_set_clock(safe_to_lower, new_clocks->dppclk_khz, clk_mgr->base.clks.dppclk_khz)) {
                if (clk_mgr->base.clks.dppclk_khz > new_clocks->dppclk_khz)
                        dpp_clock_lowered = true;
                clk_mgr_base->clks.dppclk_khz = new_clocks->dppclk_khz;
                update_dppclk = true;
        }

        if (should_set_clock(safe_to_lower, new_clocks->dispclk_khz, clk_mgr_base->clks.dispclk_khz) &&
            (new_clocks->dispclk_khz > 0 || (safe_to_lower && has_active_display == false))) {
                uint32_t requested_dispclk_khz = new_clocks->dispclk_khz;

                dcn35_disable_otg_wa(clk_mgr_base, context, safe_to_lower, true);

                /* Clamp the requested clock to PMFW based on their limit. */
                if (dc->debug.min_disp_clk_khz > 0 && requested_dispclk_khz < (uint32_t)dc->debug.min_disp_clk_khz)
                        requested_dispclk_khz = (uint32_t)dc->debug.min_disp_clk_khz;

                dcn42_smu_set_dispclk(clk_mgr, requested_dispclk_khz);
                clk_mgr_base->clks.dispclk_khz = new_clocks->dispclk_khz;
                dcn35_disable_otg_wa(clk_mgr_base, context, safe_to_lower, false);

                update_dispclk = true;
        }

        /* clock limits are received with MHz precision, divide by 1000 to prevent setting clocks at every call */
        if (!dc->debug.disable_dtb_ref_clk_switch && new_clocks->dtbclk_en &&
            should_set_clock(safe_to_lower, new_clocks->ref_dtbclk_khz / 1000,
                             clk_mgr_base->clks.ref_dtbclk_khz / 1000)) {
                dcn42_update_clocks_update_dtb_dto(clk_mgr, context, new_clocks->ref_dtbclk_khz);
                clk_mgr_base->clks.ref_dtbclk_khz = new_clocks->ref_dtbclk_khz;
        }

        if (dpp_clock_lowered) {
                // increase per DPP DTO before lowering global dppclk
                dcn42_update_clocks_update_dpp_dto(clk_mgr, context, safe_to_lower);
                dcn42_smu_set_dppclk(clk_mgr, clk_mgr_base->clks.dppclk_khz);
        } else {
                // increase global DPPCLK before lowering per DPP DTO
                if (update_dppclk || update_dispclk)
                        dcn42_smu_set_dppclk(clk_mgr, clk_mgr_base->clks.dppclk_khz);
                dcn42_update_clocks_update_dpp_dto(clk_mgr, context, safe_to_lower);
        }
        // notify DMCUB of latest clocks
        memset(&cmd, 0, sizeof(cmd));
        cmd.notify_clocks.header.type = DMUB_CMD__CLK_MGR;
        cmd.notify_clocks.header.sub_type = DMUB_CMD__CLK_MGR_NOTIFY_CLOCKS;
        cmd.notify_clocks.clocks.dcfclk_khz = clk_mgr_base->clks.dcfclk_khz;
        cmd.notify_clocks.clocks.dcfclk_deep_sleep_khz =
                clk_mgr_base->clks.dcfclk_deep_sleep_khz;
        cmd.notify_clocks.clocks.dispclk_khz = clk_mgr_base->clks.dispclk_khz;
        cmd.notify_clocks.clocks.dppclk_khz = clk_mgr_base->clks.dppclk_khz;

        dc_wake_and_execute_dmub_cmd(dc->ctx, &cmd, DM_DMUB_WAIT_TYPE_WAIT);
}


void dcn42_enable_pme_wa(struct clk_mgr *clk_mgr_base)
{
        struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);

        dcn42_smu_enable_pme_wa(clk_mgr);
}


bool dcn42_are_clock_states_equal(struct dc_clocks *a,
                struct dc_clocks *b)
{
        if (a->dispclk_khz != b->dispclk_khz)
                return false;
        else if (a->dppclk_khz != b->dppclk_khz)
                return false;
        else if (a->dcfclk_khz != b->dcfclk_khz)
                return false;
        else if (a->dcfclk_deep_sleep_khz != b->dcfclk_deep_sleep_khz)
                return false;
        else if (a->zstate_support != b->zstate_support)
                return false;
        else if (a->dtbclk_en != b->dtbclk_en)
                return false;

        return true;
}

static void dcn42_dump_clk_registers_internal(struct dcn42_clk_internal *internal, struct clk_mgr *clk_mgr_base)
{
        struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);

        REG_GET(CLK8_CLK_TICK_CNT_CONFIG_REG, TIMER_THRESHOLD, &internal->CLK8_CLK_TICK_CNT__TIMER_THRESHOLD);

        // read dcf deep sleep divider
        internal->CLK8_CLK0_DS_CNTL = REG_READ(CLK8_CLK0_DS_CNTL);
        internal->CLK8_CLK3_DS_CNTL = REG_READ(CLK8_CLK3_DS_CNTL);
        // read dispclk
        internal->CLK8_CLK0_CURRENT_CNT = dcn42_get_clock_freq_from_clkip(clk_mgr_base, clock_type_dispclk);
        internal->CLK8_CLK0_BYPASS_CNTL = REG_READ(CLK8_CLK0_BYPASS_CNTL);
        // read dppclk
        internal->CLK8_CLK1_CURRENT_CNT = dcn42_get_clock_freq_from_clkip(clk_mgr_base, clock_type_dppclk);
        internal->CLK8_CLK1_BYPASS_CNTL = REG_READ(CLK8_CLK1_BYPASS_CNTL);
        // read dprefclk
        internal->CLK8_CLK2_CURRENT_CNT = dcn42_get_clock_freq_from_clkip(clk_mgr_base, clock_type_dprefclk);
        internal->CLK8_CLK2_BYPASS_CNTL = REG_READ(CLK8_CLK2_BYPASS_CNTL);
        // read dcfclk
        internal->CLK8_CLK3_CURRENT_CNT = dcn42_get_clock_freq_from_clkip(clk_mgr_base, clock_type_dcfclk);
        internal->CLK8_CLK3_BYPASS_CNTL = REG_READ(CLK8_CLK3_BYPASS_CNTL);
        // read dtbclk
        internal->CLK8_CLK4_CURRENT_CNT = dcn42_get_clock_freq_from_clkip(clk_mgr_base, clock_type_dtbclk);
        internal->CLK8_CLK4_BYPASS_CNTL = REG_READ(CLK8_CLK4_BYPASS_CNTL);
}

static void dcn42_dump_clk_registers(struct clk_state_registers_and_bypass *regs_and_bypass,
                struct clk_mgr_dcn42 *clk_mgr)
{
        struct dcn42_clk_internal internal = {0};
        char *bypass_clks[5] = {"0x0 DFS", "0x1 REFCLK", "0x2 ERROR", "0x3 400 FCH", "0x4 600 FCH"};

        DC_LOGGER_INIT(clk_mgr->base.base.ctx->logger);
        (void)dc_logger;

        dcn42_dump_clk_registers_internal(&internal, &clk_mgr->base.base);
        regs_and_bypass->timer_threshold = internal.CLK8_CLK_TICK_CNT__TIMER_THRESHOLD;
        regs_and_bypass->dcfclk = internal.CLK8_CLK3_CURRENT_CNT / 10;
        regs_and_bypass->dcf_deep_sleep_divider = internal.CLK8_CLK3_DS_CNTL / 10;
        regs_and_bypass->dcf_deep_sleep_allow = internal.CLK8_CLK3_DS_CNTL & 0x10; /*bit 4: CLK0_ALLOW_DS*/
        regs_and_bypass->dprefclk = internal.CLK8_CLK2_CURRENT_CNT / 10;
        regs_and_bypass->dispclk = internal.CLK8_CLK0_CURRENT_CNT / 10;
        regs_and_bypass->dppclk = internal.CLK8_CLK1_CURRENT_CNT / 10;
        regs_and_bypass->dtbclk = internal.CLK8_CLK4_CURRENT_CNT / 10;

        regs_and_bypass->dispclk_bypass = get_reg_field_value(internal.CLK8_CLK0_BYPASS_CNTL, CLK8_CLK0_BYPASS_CNTL, CLK0_BYPASS_SEL);
        regs_and_bypass->dppclk_bypass = get_reg_field_value(internal.CLK8_CLK1_BYPASS_CNTL, CLK8_CLK1_BYPASS_CNTL, CLK1_BYPASS_SEL);
        regs_and_bypass->dprefclk_bypass = get_reg_field_value(internal.CLK8_CLK2_BYPASS_CNTL, CLK8_CLK2_BYPASS_CNTL, CLK2_BYPASS_SEL);
        regs_and_bypass->dcfclk_bypass = get_reg_field_value(internal.CLK8_CLK3_BYPASS_CNTL, CLK8_CLK3_BYPASS_CNTL, CLK3_BYPASS_SEL);

        if (clk_mgr->base.base.ctx->dc->debug.pstate_enabled) {
                DC_LOG_SMU("clk_type,clk_value,deepsleep_cntl,deepsleep_allow,bypass\n");

                DC_LOG_SMU("dcfclk,%d,%d,%d,%s\n",
                                   regs_and_bypass->dcfclk,
                                   regs_and_bypass->dcf_deep_sleep_divider,
                                   regs_and_bypass->dcf_deep_sleep_allow,
                                   bypass_clks[(int) regs_and_bypass->dcfclk_bypass]);

                DC_LOG_SMU("dprefclk,%d,N/A,N/A,%s\n",
                        regs_and_bypass->dprefclk,
                        bypass_clks[(int) regs_and_bypass->dprefclk_bypass]);

                DC_LOG_SMU("dispclk,%d,N/A,N/A,%s\n",
                        regs_and_bypass->dispclk,
                        bypass_clks[(int) regs_and_bypass->dispclk_bypass]);

                //split
                DC_LOG_SMU("SPLIT\n");

                // REGISTER VALUES
                DC_LOG_SMU("reg_name,value,clk_type\n");
                DC_LOG_SMU("CLK1_CLK3_CURRENT_CNT,%d,dcfclk\n",
                                internal.CLK8_CLK3_CURRENT_CNT);

                DC_LOG_SMU("CLK1_CLK3_DS_CNTL,%d,dcf_deep_sleep_divider\n",
                                        internal.CLK8_CLK3_DS_CNTL);

                DC_LOG_SMU("CLK1_CLK3_ALLOW_DS,%d,dcf_deep_sleep_allow\n",
                                        (internal.CLK8_CLK3_DS_CNTL & 0x10));

                DC_LOG_SMU("CLK1_CLK2_CURRENT_CNT,%d,dprefclk\n",
                                        internal.CLK8_CLK2_CURRENT_CNT);

                DC_LOG_SMU("CLK1_CLK0_CURRENT_CNT,%d,dispclk\n",
                                        internal.CLK8_CLK0_CURRENT_CNT);

                DC_LOG_SMU("CLK1_CLK1_CURRENT_CNT,%d,dppclk\n",
                                        internal.CLK8_CLK1_CURRENT_CNT);

                DC_LOG_SMU("CLK1_CLK4_CURRENT_CNT,%d,dtbclk\n",
                                        internal.CLK8_CLK4_CURRENT_CNT);

                DC_LOG_SMU("CLK1_CLK3_BYPASS_CNTL,%d,dcfclk_bypass\n",
                                        internal.CLK8_CLK3_BYPASS_CNTL);

                DC_LOG_SMU("CLK1_CLK2_BYPASS_CNTL,%d,dprefclk_bypass\n",
                                        internal.CLK8_CLK2_BYPASS_CNTL);

                DC_LOG_SMU("CLK1_CLK0_BYPASS_CNTL,%d,dispclk_bypass\n",
                                        internal.CLK8_CLK0_BYPASS_CNTL);

                DC_LOG_SMU("CLK1_CLK1_BYPASS_CNTL,%d,dppclk_bypass\n",
                                        internal.CLK8_CLK1_BYPASS_CNTL);
        }
}

bool dcn42_is_spll_ssc_enabled(struct clk_mgr *clk_mgr_base)
{
        struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);
        struct dc_context *ctx = clk_mgr->base.ctx;


        if (ctx->dc->config.ignore_dpref_ss) {
                /*revert bios's ss info for test only*/
                return (clk_mgr->dprefclk_ss_percentage == 0);
        }
        /*need to update after BU*/
        return false;
}

static void init_clk_states(struct clk_mgr *clk_mgr)
{
        uint32_t ref_dtbclk = clk_mgr->clks.ref_dtbclk_khz;

        memset(&(clk_mgr->clks), 0, sizeof(struct dc_clocks));

        clk_mgr->clks.dtbclk_en = true; // request DTBCLK disable on first commit
        clk_mgr->clks.ref_dtbclk_khz = ref_dtbclk;      // restore ref_dtbclk
        clk_mgr->clks.p_state_change_support = true;
        clk_mgr->clks.prev_p_state_change_support = true;
        clk_mgr->clks.pwr_state = DCN_PWR_STATE_UNKNOWN;
        clk_mgr->clks.zstate_support = DCN_ZSTATE_SUPPORT_UNKNOWN;
}

void dcn42_get_dpm_table_from_smu(struct clk_mgr_internal *clk_mgr,
                struct dcn42_smu_dpm_clks *smu_dpm_clks)
{
        DpmClocks_t_dcn42 *table = smu_dpm_clks->dpm_clks;

        if (!clk_mgr->smu_ver)
                return;

        if (!table || smu_dpm_clks->mc_address.quad_part == 0)
                return;

        memset(table, 0, sizeof(*table));

        dcn42_smu_set_dram_addr_high(clk_mgr,
                        smu_dpm_clks->mc_address.high_part);
        dcn42_smu_set_dram_addr_low(clk_mgr,
                        smu_dpm_clks->mc_address.low_part);
        dcn42_smu_transfer_dpm_table_smu_2_dram(clk_mgr);
}

void dcn42_init_single_clock(unsigned int *entry_0,
                uint32_t *smu_entry_0,
                uint8_t num_levels)
{
        int i;
        char *entry_i = (char *)entry_0;

        ASSERT(num_levels <= MAX_NUM_DPM_LVL);
        if (num_levels > MAX_NUM_DPM_LVL)
                num_levels = MAX_NUM_DPM_LVL;


        for (i = 0; i < num_levels; i++) {
                *((unsigned int *)entry_i) = smu_entry_0[i];
                entry_i += sizeof(struct clk_limit_table_entry);
        }
}

unsigned int dcn42_convert_wck_ratio(uint8_t wck_ratio)
{
        switch (wck_ratio) {
        case WCK_RATIO_1_2:
                return 2;

        case WCK_RATIO_1_4:
                return 4;

        default:
                        break;
        }

        return 1;
}

void dcn42_init_clocks(struct clk_mgr *clk_mgr_base)
{
        struct clk_mgr_internal *clk_mgr_int = TO_CLK_MGR_INTERNAL(clk_mgr_base);
        struct clk_mgr_dcn42 *clk_mgr = TO_CLK_MGR_DCN42(clk_mgr_int);

        DC_LOGGER_INIT(clk_mgr_base->ctx->logger);
        (void)dc_logger;

        init_clk_states(clk_mgr_base);

        // to adjust dp_dto reference clock if ssc is enable otherwise to apply dprefclk
        if (dcn42_is_spll_ssc_enabled(clk_mgr_base))
                clk_mgr_base->dp_dto_source_clock_in_khz =
                        dce_adjust_dp_ref_freq_for_ss(clk_mgr_int, clk_mgr_base->dprefclk_khz);
        else
                clk_mgr_base->dp_dto_source_clock_in_khz = clk_mgr_base->dprefclk_khz;

        DC_LOG_SMU("dp_dto_source_clock %d, dprefclk %d\n", clk_mgr_base->dp_dto_source_clock_in_khz, clk_mgr_base->dprefclk_khz);
        dcn42_dump_clk_registers(&clk_mgr_base->boot_snapshot, clk_mgr);

        clk_mgr_base->clks.ref_dtbclk_khz =  clk_mgr_base->boot_snapshot.dtbclk * 10;
        clk_mgr_base->clks.dtbclk_en = clk_mgr_base->boot_snapshot.dtbclk > 59000;
}

static struct clk_bw_params dcn42_bw_params = {
        .vram_type = Ddr4MemType,
        .num_channels = 1,
        .clk_table = {
                .num_entries = 4,
        },

};

struct dcn42_ss_info_table dcn42_ss_info_table = {
        .ss_divider = 1000,
        .ss_percentage = {0, 0, 375, 375, 375}
};

static void dcn42_read_ss_info_from_lut(struct clk_mgr_internal *clk_mgr)
{
        uint32_t clock_source;

        clock_source = (REG_READ(CLK8_CLK2_BYPASS_CNTL) & CLK8_CLK2_BYPASS_CNTL__CLK2_BYPASS_SEL_MASK);
        // If it's DFS mode, clock_source is 0.
        if (dcn42_is_spll_ssc_enabled(&clk_mgr->base) && (clock_source < ARRAY_SIZE(dcn42_ss_info_table.ss_percentage))) {
                clk_mgr->dprefclk_ss_percentage = dcn42_ss_info_table.ss_percentage[clock_source];

                if (clk_mgr->dprefclk_ss_percentage != 0) {
                        clk_mgr->ss_on_dprefclk = true;
                        clk_mgr->dprefclk_ss_divider = dcn42_ss_info_table.ss_divider;
                }
        }
}

void dcn42_build_watermark_ranges(struct clk_bw_params *bw_params, struct dcn42_watermarks *table)
{
        uint8_t i, num_valid_sets;

        num_valid_sets = 0;

        for (i = 0; i < WM_SET_COUNT; i++) {
                /* skip empty entries, the smu array has no holes*/
                if (!bw_params->wm_table.entries[i].valid)
                        continue;

                table->WatermarkRow[WM_DCFCLK][num_valid_sets].WmSetting =
                        (uint8_t)bw_params->wm_table.entries[i].wm_inst;
                table->WatermarkRow[WM_DCFCLK][num_valid_sets].WmType =
                        (uint8_t)bw_params->wm_table.entries[i].wm_type;
                /* We will not select WM based on fclk, so leave it as unconstrained */
                table->WatermarkRow[WM_DCFCLK][num_valid_sets].MinClock = 0;
                table->WatermarkRow[WM_DCFCLK][num_valid_sets].MaxClock = 0xFFFF;

                if (table->WatermarkRow[WM_DCFCLK][num_valid_sets].WmType == WM_TYPE_PSTATE_CHG) {
                        if (i == 0)
                                table->WatermarkRow[WM_DCFCLK][num_valid_sets].MinMclk = 0;
                        else {
                                /* add 1 to make it non-overlapping with next lvl */
                                table->WatermarkRow[WM_DCFCLK][num_valid_sets].MinMclk =
                                                (uint16_t)(bw_params->clk_table.entries[i - 1].dcfclk_mhz + 1);
                        }
                        table->WatermarkRow[WM_DCFCLK][num_valid_sets].MaxMclk =
                                        (uint16_t)bw_params->clk_table.entries[i].dcfclk_mhz;

                } else {
                        /* unconstrained for memory retraining */
                        table->WatermarkRow[WM_DCFCLK][num_valid_sets].MinClock = 0;
                        table->WatermarkRow[WM_DCFCLK][num_valid_sets].MaxClock = 0xFFFF;

                        /* Modify previous watermark range to cover up to max */
                        if (num_valid_sets > 0)
                                table->WatermarkRow[WM_DCFCLK][num_valid_sets - 1].MaxClock = 0xFFFF;
                }
                num_valid_sets++;
        }

        ASSERT(num_valid_sets != 0); /* Must have at least one set of valid watermarks */

        /* modify the min and max to make sure we cover the whole range*/
        table->WatermarkRow[WM_DCFCLK][0].MinMclk = 0;
        table->WatermarkRow[WM_DCFCLK][0].MinClock = 0;
        table->WatermarkRow[WM_DCFCLK][num_valid_sets - 1].MaxMclk = 0xFFFF;
        table->WatermarkRow[WM_DCFCLK][num_valid_sets - 1].MaxClock = 0xFFFF;

        /* This is for writeback only, does not matter currently as no writeback support*/
        table->WatermarkRow[WM_SOCCLK][0].WmSetting = WM_A;
        table->WatermarkRow[WM_SOCCLK][0].MinClock = 0;
        table->WatermarkRow[WM_SOCCLK][0].MaxClock = 0xFFFF;
        table->WatermarkRow[WM_SOCCLK][0].MinMclk = 0;
        table->WatermarkRow[WM_SOCCLK][0].MaxMclk = 0xFFFF;
}

void dcn42_notify_wm_ranges(struct clk_mgr *clk_mgr_base)
{
        unsigned int i = 0;
        struct dcn42_watermarks *table = NULL;
        struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);
        struct clk_mgr_dcn42 *clk_mgr_dcn42 = TO_CLK_MGR_DCN42(clk_mgr);

        if (!clk_mgr->smu_ver)
                return;
        /*send once already skip*/
        if (clk_mgr_base->bw_params->wm_table.entries[WM_A].valid == true)
                return;
        clk_mgr_dcn42->smu_wm_set.wm_set = (struct dcn42_watermarks *)dm_helpers_allocate_gpu_mem(
                                clk_mgr->base.ctx,
                                DC_MEM_ALLOC_TYPE_GART,
                                sizeof(struct dcn42_watermarks),
                                &clk_mgr_dcn42->smu_wm_set.mc_address.quad_part);

        ASSERT(clk_mgr_dcn42->smu_wm_set.wm_set);

        table = clk_mgr_dcn42->smu_wm_set.wm_set;

        if (!table || clk_mgr_dcn42->smu_wm_set.mc_address.quad_part == 0)
                return;

        memset(table, 0, sizeof(*table));
        /*same as previous asic, set wm valid before building watermark ranges*/
        for (i = 0; i < WM_SET_COUNT; i++) {
                clk_mgr_base->bw_params->wm_table.entries[i].wm_inst = i;

                if (i >= clk_mgr_base->bw_params->clk_table.num_entries) {
                        clk_mgr_base->bw_params->wm_table.entries[i].valid = false;
                        continue;
                }
                clk_mgr_base->bw_params->wm_table.entries[i].wm_type = WM_TYPE_PSTATE_CHG;
                clk_mgr_base->bw_params->wm_table.entries[i].valid = true;
        }
        /* build watermark_range will check this valid range*/
        dcn42_build_watermark_ranges(clk_mgr_base->bw_params, table);

        dcn42_smu_set_dram_addr_high(clk_mgr,
                        clk_mgr_dcn42->smu_wm_set.mc_address.high_part);
        dcn42_smu_set_dram_addr_low(clk_mgr,
                        clk_mgr_dcn42->smu_wm_set.mc_address.low_part);
        dcn42_smu_transfer_wm_table_dram_2_smu(clk_mgr);

        if (clk_mgr_dcn42->smu_wm_set.wm_set && clk_mgr_dcn42->smu_wm_set.mc_address.quad_part != 0) {
                dm_helpers_free_gpu_mem(clk_mgr->base.ctx, DC_MEM_ALLOC_TYPE_GART,
                                clk_mgr_dcn42->smu_wm_set.wm_set);
                clk_mgr_dcn42->smu_wm_set.wm_set = NULL;
                clk_mgr_dcn42->smu_wm_set.mc_address.quad_part = 0;
        }

}

void dcn42_set_low_power_state(struct clk_mgr *clk_mgr_base)
{
        struct dc *dc = clk_mgr_base->ctx->dc;
        struct dc_state *context = dc->current_state;

        if (clk_mgr_base->clks.pwr_state != DCN_PWR_STATE_LOW_POWER) {
                /* if we can go lower, go lower */
                if (dcn42_has_active_display(dc, context) == false)
                        clk_mgr_base->clks.pwr_state = DCN_PWR_STATE_LOW_POWER;
        }

        if (clk_mgr_base->clks.pwr_state == DCN_PWR_STATE_LOW_POWER) {
                union display_idle_optimization_u idle_info = { 0 };
                struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);

                idle_info.idle_info.df_request_disabled = 1;
                idle_info.idle_info.phy_ref_clk_off = 1;
                idle_info.idle_info.s0i2_rdy = 1;
                dcn42_smu_set_display_idle_optimization(clk_mgr, idle_info.data);
        }
}

void dcn42_exit_low_power_state(struct clk_mgr *clk_mgr_base)
{
        (void)clk_mgr_base;

}

static void dcn42_init_clocks_fpga(struct clk_mgr *clk_mgr)
{
        init_clk_states(clk_mgr);

}

void dcn42_update_clocks_fpga(struct clk_mgr *clk_mgr,
                struct dc_state *context,
                bool safe_to_lower)
{
        struct clk_mgr_internal *clk_mgr_int = TO_CLK_MGR_INTERNAL(clk_mgr);
        struct dc_clocks *new_clocks = &context->bw_ctx.bw.dcn.clk;
        int fclk_adj = new_clocks->fclk_khz;

        /* TODO: remove this after correctly set by DML */
        new_clocks->dcfclk_khz = 400000;
        new_clocks->socclk_khz = 400000;

        /* Min fclk = 1.2GHz since all the extra scemi logic seems to run off of it */
        //int fclk_adj = new_clocks->fclk_khz > 1200000 ? new_clocks->fclk_khz : 1200000;
        new_clocks->fclk_khz = 4320000;

        if (should_set_clock(safe_to_lower, new_clocks->phyclk_khz, clk_mgr->clks.phyclk_khz))
                clk_mgr->clks.phyclk_khz = new_clocks->phyclk_khz;

        if (should_set_clock(safe_to_lower, new_clocks->dcfclk_khz, clk_mgr->clks.dcfclk_khz))
                clk_mgr->clks.dcfclk_khz = new_clocks->dcfclk_khz;

        if (should_set_clock(safe_to_lower,
                        new_clocks->dcfclk_deep_sleep_khz, clk_mgr->clks.dcfclk_deep_sleep_khz))
                clk_mgr->clks.dcfclk_deep_sleep_khz = new_clocks->dcfclk_deep_sleep_khz;

        if (should_set_clock(safe_to_lower, new_clocks->socclk_khz, clk_mgr->clks.socclk_khz))
                clk_mgr->clks.socclk_khz = new_clocks->socclk_khz;

        if (should_set_clock(safe_to_lower, new_clocks->dramclk_khz, clk_mgr->clks.dramclk_khz))
                clk_mgr->clks.dramclk_khz = new_clocks->dramclk_khz;

        if (should_set_clock(safe_to_lower, new_clocks->dppclk_khz, clk_mgr->clks.dppclk_khz))
                clk_mgr->clks.dppclk_khz = new_clocks->dppclk_khz;

        if (should_set_clock(safe_to_lower, fclk_adj, clk_mgr->clks.fclk_khz))
                clk_mgr->clks.fclk_khz = fclk_adj;

        if (should_set_clock(safe_to_lower, new_clocks->dispclk_khz, clk_mgr->clks.dispclk_khz))
                clk_mgr->clks.dispclk_khz = new_clocks->dispclk_khz;

        /* Both fclk and ref_dppclk run on the same scemi clock.
         * So take the higher value since the DPP DTO is typically programmed
         * such that max dppclk is 1:1 with ref_dppclk.
         */
        if (clk_mgr->clks.fclk_khz > clk_mgr->clks.dppclk_khz)
                clk_mgr->clks.dppclk_khz = clk_mgr->clks.fclk_khz;
        if (clk_mgr->clks.dppclk_khz > clk_mgr->clks.fclk_khz)
                clk_mgr->clks.fclk_khz = clk_mgr->clks.dppclk_khz;

        // Both fclk and ref_dppclk run on the same scemi clock.
        clk_mgr_int->dccg->ref_dppclk = clk_mgr->clks.fclk_khz;

        dm_set_dcn_clocks(clk_mgr->ctx, &clk_mgr->clks);
        if (clk_mgr->clks.dtbclk_en) {
                dcn42_update_clocks_update_dtb_dto(clk_mgr_int, context, clk_mgr->clks.ref_dtbclk_khz);
        } else {
                clk_mgr->clks.ref_dtbclk_khz = 0;
        }
        dcn42_update_clocks_update_dpp_dto(clk_mgr_int, context, safe_to_lower);
}

unsigned int dcn42_get_max_clock_khz(struct clk_mgr *clk_mgr_base, enum clk_type clk_type)
{
        struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);

        unsigned int num_clk_levels;

        switch (clk_type) {
        case CLK_TYPE_DISPCLK:
                num_clk_levels = clk_mgr->base.bw_params->clk_table.num_entries_per_clk.num_dispclk_levels;
                return num_clk_levels ?
                                clk_mgr->base.bw_params->clk_table.entries[num_clk_levels - 1].dispclk_mhz * 1000 :
                                clk_mgr->base.boot_snapshot.dispclk;
        case CLK_TYPE_DPPCLK:
                num_clk_levels = clk_mgr->base.bw_params->clk_table.num_entries_per_clk.num_dppclk_levels;
                return num_clk_levels ?
                                clk_mgr->base.bw_params->clk_table.entries[num_clk_levels - 1].dppclk_mhz * 1000 :
                                clk_mgr->base.boot_snapshot.dppclk;
        case CLK_TYPE_DSCCLK:
                num_clk_levels = clk_mgr->base.bw_params->clk_table.num_entries_per_clk.num_dispclk_levels;
                return num_clk_levels ?
                                clk_mgr->base.bw_params->clk_table.entries[num_clk_levels - 1].dispclk_mhz * 1000 / 3 :
                                clk_mgr->base.boot_snapshot.dispclk / 3;
        default:
                break;
        }

        return 0;
}

int dcn42_get_dispclk_from_dentist(struct clk_mgr *clk_mgr_base)
{
        (void)clk_mgr_base;
        struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);
        uint32_t dispclk_wdivider;
        unsigned int disp_divider;

        REG_GET(DENTIST_DISPCLK_CNTL, DENTIST_DISPCLK_WDIVIDER, &dispclk_wdivider);
        disp_divider = dentist_get_divider_from_did(dispclk_wdivider);

        /* Return DISPCLK freq in Khz */
        if (disp_divider)
                return (DENTIST_DIVIDER_RANGE_SCALE_FACTOR * clk_mgr->base.dentist_vco_freq_khz) / disp_divider;

        return 0;
}
bool dcn42_is_smu_present(struct clk_mgr *clk_mgr_base)
{
        struct clk_mgr_internal *clk_mgr = TO_CLK_MGR_INTERNAL(clk_mgr_base);
        return clk_mgr->smu_present;
}

void dcn42_get_smu_clocks(struct clk_mgr_internal *clk_mgr_int)
{
        struct clk_mgr *clk_mgr_base = &clk_mgr_int->base;
        struct dcn42_smu_dpm_clks smu_dpm_clks = { 0 };

        DC_LOGGER_INIT(clk_mgr_base->ctx->logger);
        (void)dc_logger;

        smu_dpm_clks.dpm_clks = (DpmClocks_t_dcn42 *)dm_helpers_allocate_gpu_mem(
                                clk_mgr_base->ctx,
                                DC_MEM_ALLOC_TYPE_GART,
                                sizeof(DpmClocks_t_dcn42),
                                &smu_dpm_clks.mc_address.quad_part);

        ASSERT(smu_dpm_clks.dpm_clks);
        if (clk_mgr_base->ctx->dc->debug.pstate_enabled && smu_dpm_clks.mc_address.quad_part != 0) {
                int i;
                DpmClocks_t_dcn42 *dpm_clks = smu_dpm_clks.dpm_clks;

                dcn42_get_dpm_table_from_smu(clk_mgr_int, &smu_dpm_clks);
                DC_LOG_SMU("NumDcfClkLevelsEnabled: %d\n"
                                   "NumDispClkLevelsEnabled: %d\n"
                                   "NumSocClkLevelsEnabled: %d\n"
                                   "VcnClkLevelsEnabled: %d\n"
                                   "FClkLevelsEnabled: %d\n"
                                   "NumMemPstatesEnabled: %d\n"
                                   "MinGfxClk: %d\n"
                                   "MaxGfxClk: %d\n",
                                   dpm_clks->NumDcfClkLevelsEnabled,
                                   dpm_clks->NumDispClkLevelsEnabled,
                                   dpm_clks->NumSocClkLevelsEnabled,
                                   dpm_clks->VcnClkLevelsEnabled,
                                   dpm_clks->NumFclkLevelsEnabled,
                                   dpm_clks->NumMemPstatesEnabled,
                                   dpm_clks->MinGfxClk,
                                   dpm_clks->MaxGfxClk);

                for (i = 0; i < NUM_DCFCLK_DPM_LEVELS; i++) {
                        DC_LOG_SMU("dpm_clks->DcfClocks[%d] = %d\n",
                                           i,
                                           dpm_clks->DcfClocks[i]);
                }
                for (i = 0; i < NUM_DISPCLK_DPM_LEVELS; i++) {
                        DC_LOG_SMU("dpm_clks->DispClocks[%d] = %d\n",
                                           i, dpm_clks->DispClocks[i]);
                }
                for (i = 0; i < NUM_SOCCLK_DPM_LEVELS; i++) {
                        DC_LOG_SMU("dpm_clks->SocClocks[%d] = %d\n",
                                           i, dpm_clks->SocClocks[i]);
                }
                for (i = 0; i < NUM_FCLK_DPM_LEVELS; i++) {
                        DC_LOG_SMU("dpm_clks->FclkClocks_Freq[%d] = %d\n",
                                           i, dpm_clks->FclkClocks_Freq[i]);
                        DC_LOG_SMU("dpm_clks->FclkClocks_Voltage[%d] = %d\n",
                                           i, dpm_clks->FclkClocks_Voltage[i]);
                }
                for (i = 0; i < NUM_SOCCLK_DPM_LEVELS; i++)
                        DC_LOG_SMU("dpm_clks->SocVoltage[%d] = %d\n",
                                           i, dpm_clks->SocVoltage[i]);

                for (i = 0; i < NUM_MEM_PSTATE_LEVELS; i++) {
                        DC_LOG_SMU("dpm_clks.MemPstateTable[%d].UClk = %d\n"
                                           "dpm_clks->MemPstateTable[%d].MemClk= %d\n"
                                           "dpm_clks->MemPstateTable[%d].Voltage = %d\n",
                                           i, dpm_clks->MemPstateTable[i].UClk,
                                           i, dpm_clks->MemPstateTable[i].MemClk,
                                           i, dpm_clks->MemPstateTable[i].Voltage);
                }

                if (clk_mgr_base->ctx->dc_bios->integrated_info && clk_mgr_base->ctx->dc->config.use_default_clock_table == false) {
                        /* DCFCLK */
                        dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].dcfclk_mhz,
                                        dpm_clks->DcfClocks,
                                        dpm_clks->NumDcfClkLevelsEnabled);
                        clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dcfclk_levels = dpm_clks->NumDcfClkLevelsEnabled;

                        /* SOCCLK */
                        dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].socclk_mhz,
                                        dpm_clks->SocClocks,
                                        dpm_clks->NumSocClkLevelsEnabled);
                        clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_socclk_levels = dpm_clks->NumSocClkLevelsEnabled;

                        /* DISPCLK */
                        dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].dispclk_mhz,
                                        dpm_clks->DispClocks,
                                        dpm_clks->NumDispClkLevelsEnabled);
                        clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dispclk_levels = dpm_clks->NumDispClkLevelsEnabled;

                        /* DPPCLK */
                        dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].dppclk_mhz,
                                        dpm_clks->DppClocks,
                                        dpm_clks->NumDispClkLevelsEnabled);
                        clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dppclk_levels = dpm_clks->NumDispClkLevelsEnabled;

                        /* FCLK */
                        dcn42_init_single_clock(&clk_mgr_base->bw_params->clk_table.entries[0].fclk_mhz,
                                        dpm_clks->FclkClocks_Freq,
                                        NUM_FCLK_DPM_LEVELS);
                        clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_fclk_levels = dpm_clks->NumFclkLevelsEnabled;
                        clk_mgr_base->bw_params->clk_table.num_entries = dpm_clks->NumFclkLevelsEnabled;

                        /* Memory Pstate table is in reverse order*/
                        ASSERT(dpm_clks->NumMemPstatesEnabled <= NUM_MEM_PSTATE_LEVELS);
                        if (dpm_clks->NumMemPstatesEnabled > NUM_MEM_PSTATE_LEVELS)
                                dpm_clks->NumMemPstatesEnabled = NUM_MEM_PSTATE_LEVELS;
                        for (i = 0; i < dpm_clks->NumMemPstatesEnabled; i++) {
                                clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].memclk_mhz = dpm_clks->MemPstateTable[i].MemClk;
                                clk_mgr_base->bw_params->clk_table.entries[dpm_clks->NumMemPstatesEnabled - 1 - i].wck_ratio = dcn42_convert_wck_ratio(dpm_clks->MemPstateTable[i].WckRatio)    ;
                        }
                        clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_memclk_levels = dpm_clks->NumMemPstatesEnabled;

                        /* DTBCLK*/
                        clk_mgr_base->bw_params->clk_table.entries[0].dtbclk_mhz = 600; /* Fixed on platform */
                        clk_mgr_base->bw_params->clk_table.num_entries_per_clk.num_dtbclk_levels = 1;
                }
        }
        if (smu_dpm_clks.dpm_clks && smu_dpm_clks.mc_address.quad_part != 0)
                dm_helpers_free_gpu_mem(clk_mgr_base->ctx, DC_MEM_ALLOC_TYPE_GART,
                                smu_dpm_clks.dpm_clks);
}

static struct clk_mgr_funcs dcn42_funcs = {
        .get_dp_ref_clk_frequency = dce12_get_dp_ref_freq_khz,
        .get_dtb_ref_clk_frequency = dcn31_get_dtb_ref_freq_khz,
        .update_clocks = dcn42_update_clocks,
        .init_clocks = dcn42_init_clocks,
        .enable_pme_wa = dcn42_enable_pme_wa,
        .are_clock_states_equal = dcn42_are_clock_states_equal,
        .notify_wm_ranges = NULL,
        .set_low_power_state = dcn42_set_low_power_state,
        .exit_low_power_state = dcn42_exit_low_power_state,
        .get_max_clock_khz = dcn42_get_max_clock_khz,
        .get_dispclk_from_dentist = dcn42_get_dispclk_from_dentist,
        .is_smu_present = dcn42_is_smu_present,
};

struct clk_mgr_funcs dcn42_fpga_funcs = {
        .get_dp_ref_clk_frequency = dce12_get_dp_ref_freq_khz,
        .update_clocks = dcn42_update_clocks_fpga,
        .init_clocks = dcn42_init_clocks_fpga,
        .get_dtb_ref_clk_frequency = dcn31_get_dtb_ref_freq_khz,
};

void dcn42_clk_mgr_construct(
                struct dc_context *ctx,
                struct clk_mgr_dcn42 *clk_mgr,
                struct pp_smu_funcs *pp_smu,
                struct dccg *dccg)
{
        clk_mgr->base.base.ctx = ctx;
        clk_mgr->base.base.funcs = &dcn42_funcs;
        clk_mgr->base.regs = &clk_mgr_regs_dcn42;
        clk_mgr->base.clk_mgr_shift = &clk_mgr_shift_dcn42;
        clk_mgr->base.clk_mgr_mask = &clk_mgr_mask_dcn42;

        clk_mgr->base.pp_smu = pp_smu;

        clk_mgr->base.dccg = dccg;
        clk_mgr->base.dfs_bypass_disp_clk = 0;

        clk_mgr->base.dprefclk_ss_percentage = 0;
        clk_mgr->base.dprefclk_ss_divider = 1000;
        clk_mgr->base.ss_on_dprefclk = false;
        clk_mgr->base.dfs_ref_freq_khz = 48000; /*sync with pmfw*/
        clk_mgr->base.base.clks.ref_dtbclk_khz = 600000;

        /* Changed from DCN3.2_clock_frequency doc to match
         * dcn32_dump_clk_registers from 4 * dentist_vco_freq_khz /
         * dprefclk DID divider
         */
        clk_mgr->base.base.dprefclk_khz = 600000;

                clk_mgr->base.smu_present = false;
                clk_mgr->base.smu_ver = dcn42_smu_get_pmfw_version(&clk_mgr->base);
                if (clk_mgr->base.smu_ver && clk_mgr->base.smu_ver != -1)
                        clk_mgr->base.smu_present = true;

                if (ctx->dc_bios->integrated_info) {
                        clk_mgr->base.base.dentist_vco_freq_khz = ctx->dc_bios->integrated_info->dentist_vco_freq;

                        dcn42_bw_params.vram_type = ctx->dc_bios->integrated_info->memory_type;
                        dcn42_bw_params.dram_channel_width_bytes = ctx->dc_bios->integrated_info->memory_type == 0x22 ? 8 : 4;
                        dcn42_bw_params.num_channels = ctx->dc_bios->integrated_info->ma_channel_number ? ctx->dc_bios->integrated_info->ma_channel_number : 2;
                        if (clk_mgr->base.smu_present) {
                                clk_mgr->base.base.clks.ref_dtbclk_khz = dcn42_smu_get_dtbclk(&clk_mgr->base);
                                clk_mgr->base.base.dprefclk_khz = dcn42_smu_get_dprefclk(&clk_mgr->base);
                        }
                        clk_mgr->base.base.bw_params = &dcn42_bw_params;

                        if (clk_mgr->base.smu_present)
                                dcn42_get_smu_clocks(&clk_mgr->base);
                }
                /* in case we don't get a value from the BIOS, use default */
                if (clk_mgr->base.base.dentist_vco_freq_khz == 0)
                        clk_mgr->base.base.dentist_vco_freq_khz = 3000000; /* 3000MHz */

                /* Saved clocks configured at boot for debug purposes */
                dcn42_dump_clk_registers(&clk_mgr->base.base.boot_snapshot, clk_mgr);

        dce_clock_read_ss_info(&clk_mgr->base);
        /*when clk src is from FCH, it could have ss, same clock src as DPREF clk*/

        dcn42_read_ss_info_from_lut(&clk_mgr->base);

        clk_mgr->base.base.bw_params = &dcn42_bw_params;
        if (clk_mgr->base.smu_present)
                dcn42_get_smu_clocks(&clk_mgr->base);
}

void dcn42_clk_mgr_destroy(struct clk_mgr_internal *clk_mgr_int)
{
        struct clk_mgr_dcn42 *clk_mgr = TO_CLK_MGR_DCN42(clk_mgr_int);

        if (clk_mgr->smu_wm_set.wm_set && clk_mgr->smu_wm_set.mc_address.quad_part != 0) {
                dm_helpers_free_gpu_mem(clk_mgr_int->base.ctx, DC_MEM_ALLOC_TYPE_GART,
                                clk_mgr->smu_wm_set.wm_set);
                clk_mgr->smu_wm_set.wm_set = NULL;
                clk_mgr->smu_wm_set.mc_address.quad_part = 0;
        }
}