/* * Copyright 2012-15 Advanced Micro Devices, Inc. * * 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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. * * Authors: AMD * */ #include "dm_services.h" #include "dc_types.h" #include "core_types.h" #include "include/grph_object_id.h" #include "include/logger_interface.h" #include "dce_clock_source.h" #include "reg_helper.h" #define REG(reg)\ (clk_src->regs->reg) #define CTX \ clk_src->base.ctx #define DC_LOGGER_INIT() #undef FN #define FN(reg_name, field_name) \ clk_src->cs_shift->field_name, clk_src->cs_mask->field_name #define FRACT_FB_DIVIDER_DEC_POINTS_MAX_NUM 6 #define CALC_PLL_CLK_SRC_ERR_TOLERANCE 1 #define MAX_PLL_CALC_ERROR 0xFFFFFFFF static const struct spread_spectrum_data *get_ss_data_entry( struct dce110_clk_src *clk_src, enum signal_type signal, uint32_t pix_clk_khz) { uint32_t entrys_num; uint32_t i; struct spread_spectrum_data *ss_parm = NULL; struct spread_spectrum_data *ret = NULL; switch (signal) { case SIGNAL_TYPE_DVI_SINGLE_LINK: case SIGNAL_TYPE_DVI_DUAL_LINK: ss_parm = clk_src->dvi_ss_params; entrys_num = clk_src->dvi_ss_params_cnt; break; case SIGNAL_TYPE_HDMI_TYPE_A: ss_parm = clk_src->hdmi_ss_params; entrys_num = clk_src->hdmi_ss_params_cnt; break; case SIGNAL_TYPE_DISPLAY_PORT: case SIGNAL_TYPE_DISPLAY_PORT_MST: case SIGNAL_TYPE_EDP: case SIGNAL_TYPE_VIRTUAL: ss_parm = clk_src->dp_ss_params; entrys_num = clk_src->dp_ss_params_cnt; break; default: ss_parm = NULL; entrys_num = 0; break; } if (ss_parm == NULL) return ret; for (i = 0; i < entrys_num; ++i, ++ss_parm) { if (ss_parm->freq_range_khz >= pix_clk_khz) { ret = ss_parm; break; } } return ret; } /** * Function: calculate_fb_and_fractional_fb_divider * * * DESCRIPTION: Calculates feedback and fractional feedback dividers values * *PARAMETERS: * targetPixelClock Desired frequency in 10 KHz * ref_divider Reference divider (already known) * postDivider Post Divider (already known) * feedback_divider_param Pointer where to store * calculated feedback divider value * fract_feedback_divider_param Pointer where to store * calculated fract feedback divider value * *RETURNS: * It fills the locations pointed by feedback_divider_param * and fract_feedback_divider_param * It returns - true if feedback divider not 0 * - false should never happen) */ static bool calculate_fb_and_fractional_fb_divider( struct calc_pll_clock_source *calc_pll_cs, uint32_t target_pix_clk_khz, uint32_t ref_divider, uint32_t post_divider, uint32_t *feedback_divider_param, uint32_t *fract_feedback_divider_param) { uint64_t feedback_divider; feedback_divider = (uint64_t)target_pix_clk_khz * ref_divider * post_divider; feedback_divider *= 10; /* additional factor, since we divide by 10 afterwards */ feedback_divider *= (uint64_t)(calc_pll_cs->fract_fb_divider_factor); feedback_divider = div_u64(feedback_divider, calc_pll_cs->ref_freq_khz); /*Round to the number of precision * The following code replace the old code (ullfeedbackDivider + 5)/10 * for example if the difference between the number * of fractional feedback decimal point and the fractional FB Divider precision * is 2 then the equation becomes (ullfeedbackDivider + 5*100) / (10*100))*/ feedback_divider += 5ULL * calc_pll_cs->fract_fb_divider_precision_factor; feedback_divider = div_u64(feedback_divider, calc_pll_cs->fract_fb_divider_precision_factor * 10); feedback_divider *= (uint64_t) (calc_pll_cs->fract_fb_divider_precision_factor); *feedback_divider_param = div_u64_rem( feedback_divider, calc_pll_cs->fract_fb_divider_factor, fract_feedback_divider_param); if (*feedback_divider_param != 0) return true; return false; } /** *calc_fb_divider_checking_tolerance * *DESCRIPTION: Calculates Feedback and Fractional Feedback divider values * for passed Reference and Post divider, checking for tolerance. *PARAMETERS: * pll_settings Pointer to structure * ref_divider Reference divider (already known) * postDivider Post Divider (already known) * tolerance Tolerance for Calculated Pixel Clock to be within * *RETURNS: * It fills the PLLSettings structure with PLL Dividers values * if calculated values are within required tolerance * It returns - true if eror is within tolerance * - false if eror is not within tolerance */ static bool calc_fb_divider_checking_tolerance( struct calc_pll_clock_source *calc_pll_cs, struct pll_settings *pll_settings, uint32_t ref_divider, uint32_t post_divider, uint32_t tolerance) { uint32_t feedback_divider; uint32_t fract_feedback_divider; uint32_t actual_calculated_clock_khz; uint32_t abs_err; uint64_t actual_calc_clk_khz; calculate_fb_and_fractional_fb_divider( calc_pll_cs, pll_settings->adjusted_pix_clk, ref_divider, post_divider, &feedback_divider, &fract_feedback_divider); /*Actual calculated value*/ actual_calc_clk_khz = (uint64_t)feedback_divider * calc_pll_cs->fract_fb_divider_factor + fract_feedback_divider; actual_calc_clk_khz *= calc_pll_cs->ref_freq_khz; actual_calc_clk_khz = div_u64(actual_calc_clk_khz, ref_divider * post_divider * calc_pll_cs->fract_fb_divider_factor); actual_calculated_clock_khz = (uint32_t)(actual_calc_clk_khz); abs_err = (actual_calculated_clock_khz > pll_settings->adjusted_pix_clk) ? actual_calculated_clock_khz - pll_settings->adjusted_pix_clk : pll_settings->adjusted_pix_clk - actual_calculated_clock_khz; if (abs_err <= tolerance) { /*found good values*/ pll_settings->reference_freq = calc_pll_cs->ref_freq_khz; pll_settings->reference_divider = ref_divider; pll_settings->feedback_divider = feedback_divider; pll_settings->fract_feedback_divider = fract_feedback_divider; pll_settings->pix_clk_post_divider = post_divider; pll_settings->calculated_pix_clk = actual_calculated_clock_khz; pll_settings->vco_freq = actual_calculated_clock_khz * post_divider; return true; } return false; } static bool calc_pll_dividers_in_range( struct calc_pll_clock_source *calc_pll_cs, struct pll_settings *pll_settings, uint32_t min_ref_divider, uint32_t max_ref_divider, uint32_t min_post_divider, uint32_t max_post_divider, uint32_t err_tolerance) { uint32_t ref_divider; uint32_t post_divider; uint32_t tolerance; /* This is err_tolerance / 10000 = 0.0025 - acceptable error of 0.25% * This is errorTolerance / 10000 = 0.0001 - acceptable error of 0.01%*/ tolerance = (pll_settings->adjusted_pix_clk * err_tolerance) / 10000; if (tolerance < CALC_PLL_CLK_SRC_ERR_TOLERANCE) tolerance = CALC_PLL_CLK_SRC_ERR_TOLERANCE; for ( post_divider = max_post_divider; post_divider >= min_post_divider; --post_divider) { for ( ref_divider = min_ref_divider; ref_divider <= max_ref_divider; ++ref_divider) { if (calc_fb_divider_checking_tolerance( calc_pll_cs, pll_settings, ref_divider, post_divider, tolerance)) { return true; } } } return false; } static uint32_t calculate_pixel_clock_pll_dividers( struct calc_pll_clock_source *calc_pll_cs, struct pll_settings *pll_settings) { uint32_t err_tolerance; uint32_t min_post_divider; uint32_t max_post_divider; uint32_t min_ref_divider; uint32_t max_ref_divider; if (pll_settings->adjusted_pix_clk == 0) { DC_LOG_ERROR( "%s Bad requested pixel clock", __func__); return MAX_PLL_CALC_ERROR; } /* 1) Find Post divider ranges */ if (pll_settings->pix_clk_post_divider) { min_post_divider = pll_settings->pix_clk_post_divider; max_post_divider = pll_settings->pix_clk_post_divider; } else { min_post_divider = calc_pll_cs->min_pix_clock_pll_post_divider; if (min_post_divider * pll_settings->adjusted_pix_clk < calc_pll_cs->min_vco_khz) { min_post_divider = calc_pll_cs->min_vco_khz / pll_settings->adjusted_pix_clk; if ((min_post_divider * pll_settings->adjusted_pix_clk) < calc_pll_cs->min_vco_khz) min_post_divider++; } max_post_divider = calc_pll_cs->max_pix_clock_pll_post_divider; if (max_post_divider * pll_settings->adjusted_pix_clk > calc_pll_cs->max_vco_khz) max_post_divider = calc_pll_cs->max_vco_khz / pll_settings->adjusted_pix_clk; } /* 2) Find Reference divider ranges * When SS is enabled, or for Display Port even without SS, * pll_settings->referenceDivider is not zero. * So calculate PPLL FB and fractional FB divider * using the passed reference divider*/ if (pll_settings->reference_divider) { min_ref_divider = pll_settings->reference_divider; max_ref_divider = pll_settings->reference_divider; } else { min_ref_divider = ((calc_pll_cs->ref_freq_khz / calc_pll_cs->max_pll_input_freq_khz) > calc_pll_cs->min_pll_ref_divider) ? calc_pll_cs->ref_freq_khz / calc_pll_cs->max_pll_input_freq_khz : calc_pll_cs->min_pll_ref_divider; max_ref_divider = ((calc_pll_cs->ref_freq_khz / calc_pll_cs->min_pll_input_freq_khz) < calc_pll_cs->max_pll_ref_divider) ? calc_pll_cs->ref_freq_khz / calc_pll_cs->min_pll_input_freq_khz : calc_pll_cs->max_pll_ref_divider; } /* If some parameters are invalid we could have scenario when "min">"max" * which produced endless loop later. * We should investigate why we get the wrong parameters. * But to follow the similar logic when "adjustedPixelClock" is set to be 0 * it is better to return here than cause system hang/watchdog timeout later. * ## SVS Wed 15 Jul 2009 */ if (min_post_divider > max_post_divider) { DC_LOG_ERROR( "%s Post divider range is invalid", __func__); return MAX_PLL_CALC_ERROR; } if (min_ref_divider > max_ref_divider) { DC_LOG_ERROR( "%s Reference divider range is invalid", __func__); return MAX_PLL_CALC_ERROR; } /* 3) Try to find PLL dividers given ranges * starting with minimal error tolerance. * Increase error tolerance until PLL dividers found*/ err_tolerance = MAX_PLL_CALC_ERROR; while (!calc_pll_dividers_in_range( calc_pll_cs, pll_settings, min_ref_divider, max_ref_divider, min_post_divider, max_post_divider, err_tolerance)) err_tolerance += (err_tolerance > 10) ? (err_tolerance / 10) : 1; return err_tolerance; } static bool pll_adjust_pix_clk( struct dce110_clk_src *clk_src, struct pixel_clk_params *pix_clk_params, struct pll_settings *pll_settings) { uint32_t actual_pix_clk_khz = 0; uint32_t requested_clk_khz = 0; struct bp_adjust_pixel_clock_parameters bp_adjust_pixel_clock_params = { 0 }; enum bp_result bp_result; switch (pix_clk_params->signal_type) { case SIGNAL_TYPE_HDMI_TYPE_A: { requested_clk_khz = pix_clk_params->requested_pix_clk; if (pix_clk_params->pixel_encoding != PIXEL_ENCODING_YCBCR422) { switch (pix_clk_params->color_depth) { case COLOR_DEPTH_101010: requested_clk_khz = (requested_clk_khz * 5) >> 2; break; /* x1.25*/ case COLOR_DEPTH_121212: requested_clk_khz = (requested_clk_khz * 6) >> 2; break; /* x1.5*/ case COLOR_DEPTH_161616: requested_clk_khz = requested_clk_khz * 2; break; /* x2.0*/ default: break; } } actual_pix_clk_khz = requested_clk_khz; } break; case SIGNAL_TYPE_DISPLAY_PORT: case SIGNAL_TYPE_DISPLAY_PORT_MST: case SIGNAL_TYPE_EDP: requested_clk_khz = pix_clk_params->requested_sym_clk; actual_pix_clk_khz = pix_clk_params->requested_pix_clk; break; default: requested_clk_khz = pix_clk_params->requested_pix_clk; actual_pix_clk_khz = pix_clk_params->requested_pix_clk; break; } bp_adjust_pixel_clock_params.pixel_clock = requested_clk_khz; bp_adjust_pixel_clock_params. encoder_object_id = pix_clk_params->encoder_object_id; bp_adjust_pixel_clock_params.signal_type = pix_clk_params->signal_type; bp_adjust_pixel_clock_params. ss_enable = pix_clk_params->flags.ENABLE_SS; bp_result = clk_src->bios->funcs->adjust_pixel_clock( clk_src->bios, &bp_adjust_pixel_clock_params); if (bp_result == BP_RESULT_OK) { pll_settings->actual_pix_clk = actual_pix_clk_khz; pll_settings->adjusted_pix_clk = bp_adjust_pixel_clock_params.adjusted_pixel_clock; pll_settings->reference_divider = bp_adjust_pixel_clock_params.reference_divider; pll_settings->pix_clk_post_divider = bp_adjust_pixel_clock_params.pixel_clock_post_divider; return true; } return false; } /** * Calculate PLL Dividers for given Clock Value. * First will call VBIOS Adjust Exec table to check if requested Pixel clock * will be Adjusted based on usage. * Then it will calculate PLL Dividers for this Adjusted clock using preferred * method (Maximum VCO frequency). * * \return * Calculation error in units of 0.01% */ static uint32_t dce110_get_pix_clk_dividers_helper ( struct dce110_clk_src *clk_src, struct pll_settings *pll_settings, struct pixel_clk_params *pix_clk_params) { uint32_t field = 0; uint32_t pll_calc_error = MAX_PLL_CALC_ERROR; DC_LOGGER_INIT(); /* Check if reference clock is external (not pcie/xtalin) * HW Dce80 spec: * 00 - PCIE_REFCLK, 01 - XTALIN, 02 - GENERICA, 03 - GENERICB * 04 - HSYNCA, 05 - GENLK_CLK, 06 - PCIE_REFCLK, 07 - DVOCLK0 */ REG_GET(PLL_CNTL, PLL_REF_DIV_SRC, &field); pll_settings->use_external_clk = (field > 1); /* VBIOS by default enables DP SS (spread on IDCLK) for DCE 8.0 always * (we do not care any more from SI for some older DP Sink which * does not report SS support, no known issues) */ if ((pix_clk_params->flags.ENABLE_SS) || (dc_is_dp_signal(pix_clk_params->signal_type))) { const struct spread_spectrum_data *ss_data = get_ss_data_entry( clk_src, pix_clk_params->signal_type, pll_settings->adjusted_pix_clk); if (NULL != ss_data) pll_settings->ss_percentage = ss_data->percentage; } /* Check VBIOS AdjustPixelClock Exec table */ if (!pll_adjust_pix_clk(clk_src, pix_clk_params, pll_settings)) { /* Should never happen, ASSERT and fill up values to be able * to continue. */ DC_LOG_ERROR( "%s: Failed to adjust pixel clock!!", __func__); pll_settings->actual_pix_clk = pix_clk_params->requested_pix_clk; pll_settings->adjusted_pix_clk = pix_clk_params->requested_pix_clk; if (dc_is_dp_signal(pix_clk_params->signal_type)) pll_settings->adjusted_pix_clk = 100000; } /* Calculate Dividers */ if (pix_clk_params->signal_type == SIGNAL_TYPE_HDMI_TYPE_A) /*Calculate Dividers by HDMI object, no SS case or SS case */ pll_calc_error = calculate_pixel_clock_pll_dividers( &clk_src->calc_pll_hdmi, pll_settings); else /*Calculate Dividers by default object, no SS case or SS case */ pll_calc_error = calculate_pixel_clock_pll_dividers( &clk_src->calc_pll, pll_settings); return pll_calc_error; } static void dce112_get_pix_clk_dividers_helper ( struct dce110_clk_src *clk_src, struct pll_settings *pll_settings, struct pixel_clk_params *pix_clk_params) { uint32_t actualPixelClockInKHz; actualPixelClockInKHz = pix_clk_params->requested_pix_clk; /* Calculate Dividers */ if (pix_clk_params->signal_type == SIGNAL_TYPE_HDMI_TYPE_A) { switch (pix_clk_params->color_depth) { case COLOR_DEPTH_101010: actualPixelClockInKHz = (actualPixelClockInKHz * 5) >> 2; break; case COLOR_DEPTH_121212: actualPixelClockInKHz = (actualPixelClockInKHz * 6) >> 2; break; case COLOR_DEPTH_161616: actualPixelClockInKHz = actualPixelClockInKHz * 2; break; default: break; } } pll_settings->actual_pix_clk = actualPixelClockInKHz; pll_settings->adjusted_pix_clk = actualPixelClockInKHz; pll_settings->calculated_pix_clk = pix_clk_params->requested_pix_clk; } static uint32_t dce110_get_pix_clk_dividers( struct clock_source *cs, struct pixel_clk_params *pix_clk_params, struct pll_settings *pll_settings) { struct dce110_clk_src *clk_src = TO_DCE110_CLK_SRC(cs); uint32_t pll_calc_error = MAX_PLL_CALC_ERROR; DC_LOGGER_INIT(); if (pix_clk_params == NULL || pll_settings == NULL || pix_clk_params->requested_pix_clk == 0) { DC_LOG_ERROR( "%s: Invalid parameters!!\n", __func__); return pll_calc_error; } memset(pll_settings, 0, sizeof(*pll_settings)); if (cs->id == CLOCK_SOURCE_ID_DP_DTO || cs->id == CLOCK_SOURCE_ID_EXTERNAL) { pll_settings->adjusted_pix_clk = clk_src->ext_clk_khz; pll_settings->calculated_pix_clk = clk_src->ext_clk_khz; pll_settings->actual_pix_clk = pix_clk_params->requested_pix_clk; return 0; } switch (cs->ctx->dce_version) { case DCE_VERSION_8_0: case DCE_VERSION_8_1: case DCE_VERSION_8_3: case DCE_VERSION_10_0: case DCE_VERSION_11_0: pll_calc_error = dce110_get_pix_clk_dividers_helper(clk_src, pll_settings, pix_clk_params); break; case DCE_VERSION_11_2: case DCE_VERSION_11_22: case DCE_VERSION_12_0: #if defined(CONFIG_DRM_AMD_DC_DCN1_0) case DCN_VERSION_1_0: #endif dce112_get_pix_clk_dividers_helper(clk_src, pll_settings, pix_clk_params); break; default: break; } return pll_calc_error; } static uint32_t dce110_get_pll_pixel_rate_in_hz( struct clock_source *cs, struct pixel_clk_params *pix_clk_params, struct pll_settings *pll_settings) { uint32_t inst = pix_clk_params->controller_id - CONTROLLER_ID_D0; struct dc *dc_core = cs->ctx->dc; struct dc_state *context = dc_core->current_state; struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[inst]; /* This function need separate to different DCE version, before separate, just use pixel clock */ return pipe_ctx->stream->phy_pix_clk; } static uint32_t dce110_get_dp_pixel_rate_from_combo_phy_pll( struct clock_source *cs, struct pixel_clk_params *pix_clk_params, struct pll_settings *pll_settings) { uint32_t inst = pix_clk_params->controller_id - CONTROLLER_ID_D0; struct dc *dc_core = cs->ctx->dc; struct dc_state *context = dc_core->current_state; struct pipe_ctx *pipe_ctx = &context->res_ctx.pipe_ctx[inst]; /* This function need separate to different DCE version, before separate, just use pixel clock */ return pipe_ctx->stream->phy_pix_clk; } static uint32_t dce110_get_d_to_pixel_rate_in_hz( struct clock_source *cs, struct pixel_clk_params *pix_clk_params, struct pll_settings *pll_settings) { uint32_t inst = pix_clk_params->controller_id - CONTROLLER_ID_D0; struct dce110_clk_src *clk_src = TO_DCE110_CLK_SRC(cs); int dto_enabled = 0; struct fixed31_32 pix_rate; REG_GET(PIXEL_RATE_CNTL[inst], DP_DTO0_ENABLE, &dto_enabled); if (dto_enabled) { uint32_t phase = 0; uint32_t modulo = 0; REG_GET(PHASE[inst], DP_DTO0_PHASE, &phase); REG_GET(MODULO[inst], DP_DTO0_MODULO, &modulo); if (modulo == 0) { return 0; } pix_rate = dc_fixpt_from_int(clk_src->ref_freq_khz); pix_rate = dc_fixpt_mul_int(pix_rate, 1000); pix_rate = dc_fixpt_mul_int(pix_rate, phase); pix_rate = dc_fixpt_div_int(pix_rate, modulo); return dc_fixpt_round(pix_rate); } else { return dce110_get_dp_pixel_rate_from_combo_phy_pll(cs, pix_clk_params, pll_settings); } } static uint32_t dce110_get_pix_rate_in_hz( struct clock_source *cs, struct pixel_clk_params *pix_clk_params, struct pll_settings *pll_settings) { uint32_t pix_rate = 0; switch (pix_clk_params->signal_type) { case SIGNAL_TYPE_DISPLAY_PORT: case SIGNAL_TYPE_DISPLAY_PORT_MST: case SIGNAL_TYPE_EDP: case SIGNAL_TYPE_VIRTUAL: pix_rate = dce110_get_d_to_pixel_rate_in_hz(cs, pix_clk_params, pll_settings); break; case SIGNAL_TYPE_HDMI_TYPE_A: default: pix_rate = dce110_get_pll_pixel_rate_in_hz(cs, pix_clk_params, pll_settings); break; } return pix_rate; } static bool disable_spread_spectrum(struct dce110_clk_src *clk_src) { enum bp_result result; struct bp_spread_spectrum_parameters bp_ss_params = {0}; bp_ss_params.pll_id = clk_src->base.id; /*Call ASICControl to process ATOMBIOS Exec table*/ result = clk_src->bios->funcs->enable_spread_spectrum_on_ppll( clk_src->bios, &bp_ss_params, false); return result == BP_RESULT_OK; } static bool calculate_ss( const struct pll_settings *pll_settings, const struct spread_spectrum_data *ss_data, struct delta_sigma_data *ds_data) { struct fixed31_32 fb_div; struct fixed31_32 ss_amount; struct fixed31_32 ss_nslip_amount; struct fixed31_32 ss_ds_frac_amount; struct fixed31_32 ss_step_size; struct fixed31_32 modulation_time; if (ds_data == NULL) return false; if (ss_data == NULL) return false; if (ss_data->percentage == 0) return false; if (pll_settings == NULL) return false; memset(ds_data, 0, sizeof(struct delta_sigma_data)); /* compute SS_AMOUNT_FBDIV & SS_AMOUNT_NFRAC_SLIP & SS_AMOUNT_DSFRAC*/ /* 6 decimal point support in fractional feedback divider */ fb_div = dc_fixpt_from_fraction( pll_settings->fract_feedback_divider, 1000000); fb_div = dc_fixpt_add_int(fb_div, pll_settings->feedback_divider); ds_data->ds_frac_amount = 0; /*spreadSpectrumPercentage is in the unit of .01%, * so have to divided by 100 * 100*/ ss_amount = dc_fixpt_mul( fb_div, dc_fixpt_from_fraction(ss_data->percentage, 100 * ss_data->percentage_divider)); ds_data->feedback_amount = dc_fixpt_floor(ss_amount); ss_nslip_amount = dc_fixpt_sub(ss_amount, dc_fixpt_from_int(ds_data->feedback_amount)); ss_nslip_amount = dc_fixpt_mul_int(ss_nslip_amount, 10); ds_data->nfrac_amount = dc_fixpt_floor(ss_nslip_amount); ss_ds_frac_amount = dc_fixpt_sub(ss_nslip_amount, dc_fixpt_from_int(ds_data->nfrac_amount)); ss_ds_frac_amount = dc_fixpt_mul_int(ss_ds_frac_amount, 65536); ds_data->ds_frac_amount = dc_fixpt_floor(ss_ds_frac_amount); /* compute SS_STEP_SIZE_DSFRAC */ modulation_time = dc_fixpt_from_fraction( pll_settings->reference_freq * 1000, pll_settings->reference_divider * ss_data->modulation_freq_hz); if (ss_data->flags.CENTER_SPREAD) modulation_time = dc_fixpt_div_int(modulation_time, 4); else modulation_time = dc_fixpt_div_int(modulation_time, 2); ss_step_size = dc_fixpt_div(ss_amount, modulation_time); /* SS_STEP_SIZE_DSFRAC_DEC = Int(SS_STEP_SIZE * 2 ^ 16 * 10)*/ ss_step_size = dc_fixpt_mul_int(ss_step_size, 65536 * 10); ds_data->ds_frac_size = dc_fixpt_floor(ss_step_size); return true; } static bool enable_spread_spectrum( struct dce110_clk_src *clk_src, enum signal_type signal, struct pll_settings *pll_settings) { struct bp_spread_spectrum_parameters bp_params = {0}; struct delta_sigma_data d_s_data; const struct spread_spectrum_data *ss_data = NULL; ss_data = get_ss_data_entry( clk_src, signal, pll_settings->calculated_pix_clk); /* Pixel clock PLL has been programmed to generate desired pixel clock, * now enable SS on pixel clock */ /* TODO is it OK to return true not doing anything ??*/ if (ss_data != NULL && pll_settings->ss_percentage != 0) { if (calculate_ss(pll_settings, ss_data, &d_s_data)) { bp_params.ds.feedback_amount = d_s_data.feedback_amount; bp_params.ds.nfrac_amount = d_s_data.nfrac_amount; bp_params.ds.ds_frac_size = d_s_data.ds_frac_size; bp_params.ds_frac_amount = d_s_data.ds_frac_amount; bp_params.flags.DS_TYPE = 1; bp_params.pll_id = clk_src->base.id; bp_params.percentage = ss_data->percentage; if (ss_data->flags.CENTER_SPREAD) bp_params.flags.CENTER_SPREAD = 1; if (ss_data->flags.EXTERNAL_SS) bp_params.flags.EXTERNAL_SS = 1; if (BP_RESULT_OK != clk_src->bios->funcs-> enable_spread_spectrum_on_ppll( clk_src->bios, &bp_params, true)) return false; } else return false; } return true; } static void dce110_program_pixel_clk_resync( struct dce110_clk_src *clk_src, enum signal_type signal_type, enum dc_color_depth colordepth) { REG_UPDATE(RESYNC_CNTL, DCCG_DEEP_COLOR_CNTL1, 0); /* 24 bit mode: TMDS clock = 1.0 x pixel clock (1:1) 30 bit mode: TMDS clock = 1.25 x pixel clock (5:4) 36 bit mode: TMDS clock = 1.5 x pixel clock (3:2) 48 bit mode: TMDS clock = 2 x pixel clock (2:1) */ if (signal_type != SIGNAL_TYPE_HDMI_TYPE_A) return; switch (colordepth) { case COLOR_DEPTH_888: REG_UPDATE(RESYNC_CNTL, DCCG_DEEP_COLOR_CNTL1, 0); break; case COLOR_DEPTH_101010: REG_UPDATE(RESYNC_CNTL, DCCG_DEEP_COLOR_CNTL1, 1); break; case COLOR_DEPTH_121212: REG_UPDATE(RESYNC_CNTL, DCCG_DEEP_COLOR_CNTL1, 2); break; case COLOR_DEPTH_161616: REG_UPDATE(RESYNC_CNTL, DCCG_DEEP_COLOR_CNTL1, 3); break; default: break; } } static void dce112_program_pixel_clk_resync( struct dce110_clk_src *clk_src, enum signal_type signal_type, enum dc_color_depth colordepth, bool enable_ycbcr420) { uint32_t deep_color_cntl = 0; uint32_t double_rate_enable = 0; /* 24 bit mode: TMDS clock = 1.0 x pixel clock (1:1) 30 bit mode: TMDS clock = 1.25 x pixel clock (5:4) 36 bit mode: TMDS clock = 1.5 x pixel clock (3:2) 48 bit mode: TMDS clock = 2 x pixel clock (2:1) */ if (signal_type == SIGNAL_TYPE_HDMI_TYPE_A) { double_rate_enable = enable_ycbcr420 ? 1 : 0; switch (colordepth) { case COLOR_DEPTH_888: deep_color_cntl = 0; break; case COLOR_DEPTH_101010: deep_color_cntl = 1; break; case COLOR_DEPTH_121212: deep_color_cntl = 2; break; case COLOR_DEPTH_161616: deep_color_cntl = 3; break; default: break; } } if (clk_src->cs_mask->PHYPLLA_PIXCLK_DOUBLE_RATE_ENABLE) REG_UPDATE_2(PIXCLK_RESYNC_CNTL, PHYPLLA_DCCG_DEEP_COLOR_CNTL, deep_color_cntl, PHYPLLA_PIXCLK_DOUBLE_RATE_ENABLE, double_rate_enable); else REG_UPDATE(PIXCLK_RESYNC_CNTL, PHYPLLA_DCCG_DEEP_COLOR_CNTL, deep_color_cntl); } static bool dce110_program_pix_clk( struct clock_source *clock_source, struct pixel_clk_params *pix_clk_params, struct pll_settings *pll_settings) { struct dce110_clk_src *clk_src = TO_DCE110_CLK_SRC(clock_source); struct bp_pixel_clock_parameters bp_pc_params = {0}; #if defined(CONFIG_DRM_AMD_DC_DCN1_0) if (IS_FPGA_MAXIMUS_DC(clock_source->ctx->dce_environment)) { unsigned int inst = pix_clk_params->controller_id - CONTROLLER_ID_D0; unsigned dp_dto_ref_kHz = 700000; unsigned clock_kHz = pll_settings->actual_pix_clk; /* Set DTO values: phase = target clock, modulo = reference clock */ REG_WRITE(PHASE[inst], clock_kHz); REG_WRITE(MODULO[inst], dp_dto_ref_kHz); /* Enable DTO */ REG_UPDATE(PIXEL_RATE_CNTL[inst], DP_DTO0_ENABLE, 1); return true; } #endif /* First disable SS * ATOMBIOS will enable by default SS on PLL for DP, * do not disable it here */ if (clock_source->id != CLOCK_SOURCE_ID_EXTERNAL && !dc_is_dp_signal(pix_clk_params->signal_type) && clock_source->ctx->dce_version <= DCE_VERSION_11_0) disable_spread_spectrum(clk_src); /*ATOMBIOS expects pixel rate adjusted by deep color ratio)*/ bp_pc_params.controller_id = pix_clk_params->controller_id; bp_pc_params.pll_id = clock_source->id; bp_pc_params.target_pixel_clock = pll_settings->actual_pix_clk; bp_pc_params.encoder_object_id = pix_clk_params->encoder_object_id; bp_pc_params.signal_type = pix_clk_params->signal_type; switch (clock_source->ctx->dce_version) { case DCE_VERSION_8_0: case DCE_VERSION_8_1: case DCE_VERSION_8_3: case DCE_VERSION_10_0: case DCE_VERSION_11_0: bp_pc_params.reference_divider = pll_settings->reference_divider; bp_pc_params.feedback_divider = pll_settings->feedback_divider; bp_pc_params.fractional_feedback_divider = pll_settings->fract_feedback_divider; bp_pc_params.pixel_clock_post_divider = pll_settings->pix_clk_post_divider; bp_pc_params.flags.SET_EXTERNAL_REF_DIV_SRC = pll_settings->use_external_clk; if (clk_src->bios->funcs->set_pixel_clock( clk_src->bios, &bp_pc_params) != BP_RESULT_OK) return false; /* Enable SS * ATOMBIOS will enable by default SS for DP on PLL ( DP ID clock), * based on HW display PLL team, SS control settings should be programmed * during PLL Reset, but they do not have effect * until SS_EN is asserted.*/ if (clock_source->id != CLOCK_SOURCE_ID_EXTERNAL && !dc_is_dp_signal(pix_clk_params->signal_type)) { if (pix_clk_params->flags.ENABLE_SS) if (!enable_spread_spectrum(clk_src, pix_clk_params->signal_type, pll_settings)) return false; /* Resync deep color DTO */ dce110_program_pixel_clk_resync(clk_src, pix_clk_params->signal_type, pix_clk_params->color_depth); } break; case DCE_VERSION_11_2: case DCE_VERSION_11_22: case DCE_VERSION_12_0: #if defined(CONFIG_DRM_AMD_DC_DCN1_0) case DCN_VERSION_1_0: #endif if (clock_source->id != CLOCK_SOURCE_ID_DP_DTO) { bp_pc_params.flags.SET_GENLOCK_REF_DIV_SRC = pll_settings->use_external_clk; bp_pc_params.flags.SET_XTALIN_REF_SRC = !pll_settings->use_external_clk; if (pix_clk_params->flags.SUPPORT_YCBCR420) { bp_pc_params.flags.SUPPORT_YUV_420 = 1; } } if (clk_src->bios->funcs->set_pixel_clock( clk_src->bios, &bp_pc_params) != BP_RESULT_OK) return false; /* Resync deep color DTO */ if (clock_source->id != CLOCK_SOURCE_ID_DP_DTO) dce112_program_pixel_clk_resync(clk_src, pix_clk_params->signal_type, pix_clk_params->color_depth, pix_clk_params->flags.SUPPORT_YCBCR420); break; default: break; } return true; } static bool dce110_clock_source_power_down( struct clock_source *clk_src) { struct dce110_clk_src *dce110_clk_src = TO_DCE110_CLK_SRC(clk_src); enum bp_result bp_result; struct bp_pixel_clock_parameters bp_pixel_clock_params = {0}; if (clk_src->dp_clk_src) return true; /* If Pixel Clock is 0 it means Power Down Pll*/ bp_pixel_clock_params.controller_id = CONTROLLER_ID_UNDEFINED; bp_pixel_clock_params.pll_id = clk_src->id; bp_pixel_clock_params.flags.FORCE_PROGRAMMING_OF_PLL = 1; /*Call ASICControl to process ATOMBIOS Exec table*/ bp_result = dce110_clk_src->bios->funcs->set_pixel_clock( dce110_clk_src->bios, &bp_pixel_clock_params); return bp_result == BP_RESULT_OK; } /*****************************************/ /* Constructor */ /*****************************************/ static const struct clock_source_funcs dce110_clk_src_funcs = { .cs_power_down = dce110_clock_source_power_down, .program_pix_clk = dce110_program_pix_clk, .get_pix_clk_dividers = dce110_get_pix_clk_dividers, .get_pix_rate_in_hz = dce110_get_pix_rate_in_hz }; static void get_ss_info_from_atombios( struct dce110_clk_src *clk_src, enum as_signal_type as_signal, struct spread_spectrum_data *spread_spectrum_data[], uint32_t *ss_entries_num) { enum bp_result bp_result = BP_RESULT_FAILURE; struct spread_spectrum_info *ss_info; struct spread_spectrum_data *ss_data; struct spread_spectrum_info *ss_info_cur; struct spread_spectrum_data *ss_data_cur; uint32_t i; DC_LOGGER_INIT(); if (ss_entries_num == NULL) { DC_LOG_SYNC( "Invalid entry !!!\n"); return; } if (spread_spectrum_data == NULL) { DC_LOG_SYNC( "Invalid array pointer!!!\n"); return; } spread_spectrum_data[0] = NULL; *ss_entries_num = 0; *ss_entries_num = clk_src->bios->funcs->get_ss_entry_number( clk_src->bios, as_signal); if (*ss_entries_num == 0) return; ss_info = kcalloc(*ss_entries_num, sizeof(struct spread_spectrum_info), GFP_KERNEL); ss_info_cur = ss_info; if (ss_info == NULL) return; ss_data = kcalloc(*ss_entries_num, sizeof(struct spread_spectrum_data), GFP_KERNEL); if (ss_data == NULL) goto out_free_info; for (i = 0, ss_info_cur = ss_info; i < (*ss_entries_num); ++i, ++ss_info_cur) { bp_result = clk_src->bios->funcs->get_spread_spectrum_info( clk_src->bios, as_signal, i, ss_info_cur); if (bp_result != BP_RESULT_OK) goto out_free_data; } for (i = 0, ss_info_cur = ss_info, ss_data_cur = ss_data; i < (*ss_entries_num); ++i, ++ss_info_cur, ++ss_data_cur) { if (ss_info_cur->type.STEP_AND_DELAY_INFO != false) { DC_LOG_SYNC( "Invalid ATOMBIOS SS Table!!!\n"); goto out_free_data; } /* for HDMI check SS percentage, * if it is > 6 (0.06%), the ATOMBIOS table info is invalid*/ if (as_signal == AS_SIGNAL_TYPE_HDMI && ss_info_cur->spread_spectrum_percentage > 6){ /* invalid input, do nothing */ DC_LOG_SYNC( "Invalid SS percentage "); DC_LOG_SYNC( "for HDMI in ATOMBIOS info Table!!!\n"); continue; } if (ss_info_cur->spread_percentage_divider == 1000) { /* Keep previous precision from ATOMBIOS for these * in case new precision set by ATOMBIOS for these * (otherwise all code in DCE specific classes * for all previous ASICs would need * to be updated for SS calculations, * Audio SS compensation and DP DTO SS compensation * which assumes fixed SS percentage Divider = 100)*/ ss_info_cur->spread_spectrum_percentage /= 10; ss_info_cur->spread_percentage_divider = 100; } ss_data_cur->freq_range_khz = ss_info_cur->target_clock_range; ss_data_cur->percentage = ss_info_cur->spread_spectrum_percentage; ss_data_cur->percentage_divider = ss_info_cur->spread_percentage_divider; ss_data_cur->modulation_freq_hz = ss_info_cur->spread_spectrum_range; if (ss_info_cur->type.CENTER_MODE) ss_data_cur->flags.CENTER_SPREAD = 1; if (ss_info_cur->type.EXTERNAL) ss_data_cur->flags.EXTERNAL_SS = 1; } *spread_spectrum_data = ss_data; kfree(ss_info); return; out_free_data: kfree(ss_data); *ss_entries_num = 0; out_free_info: kfree(ss_info); } static void ss_info_from_atombios_create( struct dce110_clk_src *clk_src) { get_ss_info_from_atombios( clk_src, AS_SIGNAL_TYPE_DISPLAY_PORT, &clk_src->dp_ss_params, &clk_src->dp_ss_params_cnt); get_ss_info_from_atombios( clk_src, AS_SIGNAL_TYPE_HDMI, &clk_src->hdmi_ss_params, &clk_src->hdmi_ss_params_cnt); get_ss_info_from_atombios( clk_src, AS_SIGNAL_TYPE_DVI, &clk_src->dvi_ss_params, &clk_src->dvi_ss_params_cnt); } static bool calc_pll_max_vco_construct( struct calc_pll_clock_source *calc_pll_cs, struct calc_pll_clock_source_init_data *init_data) { uint32_t i; struct dc_firmware_info fw_info = { { 0 } }; if (calc_pll_cs == NULL || init_data == NULL || init_data->bp == NULL) return false; if (init_data->bp->funcs->get_firmware_info( init_data->bp, &fw_info) != BP_RESULT_OK) return false; calc_pll_cs->ctx = init_data->ctx; calc_pll_cs->ref_freq_khz = fw_info.pll_info.crystal_frequency; calc_pll_cs->min_vco_khz = fw_info.pll_info.min_output_pxl_clk_pll_frequency; calc_pll_cs->max_vco_khz = fw_info.pll_info.max_output_pxl_clk_pll_frequency; if (init_data->max_override_input_pxl_clk_pll_freq_khz != 0) calc_pll_cs->max_pll_input_freq_khz = init_data->max_override_input_pxl_clk_pll_freq_khz; else calc_pll_cs->max_pll_input_freq_khz = fw_info.pll_info.max_input_pxl_clk_pll_frequency; if (init_data->min_override_input_pxl_clk_pll_freq_khz != 0) calc_pll_cs->min_pll_input_freq_khz = init_data->min_override_input_pxl_clk_pll_freq_khz; else calc_pll_cs->min_pll_input_freq_khz = fw_info.pll_info.min_input_pxl_clk_pll_frequency; calc_pll_cs->min_pix_clock_pll_post_divider = init_data->min_pix_clk_pll_post_divider; calc_pll_cs->max_pix_clock_pll_post_divider = init_data->max_pix_clk_pll_post_divider; calc_pll_cs->min_pll_ref_divider = init_data->min_pll_ref_divider; calc_pll_cs->max_pll_ref_divider = init_data->max_pll_ref_divider; if (init_data->num_fract_fb_divider_decimal_point == 0 || init_data->num_fract_fb_divider_decimal_point_precision > init_data->num_fract_fb_divider_decimal_point) { DC_LOG_ERROR( "The dec point num or precision is incorrect!"); return false; } if (init_data->num_fract_fb_divider_decimal_point_precision == 0) { DC_LOG_ERROR( "Incorrect fract feedback divider precision num!"); return false; } calc_pll_cs->fract_fb_divider_decimal_points_num = init_data->num_fract_fb_divider_decimal_point; calc_pll_cs->fract_fb_divider_precision = init_data->num_fract_fb_divider_decimal_point_precision; calc_pll_cs->fract_fb_divider_factor = 1; for (i = 0; i < calc_pll_cs->fract_fb_divider_decimal_points_num; ++i) calc_pll_cs->fract_fb_divider_factor *= 10; calc_pll_cs->fract_fb_divider_precision_factor = 1; for ( i = 0; i < (calc_pll_cs->fract_fb_divider_decimal_points_num - calc_pll_cs->fract_fb_divider_precision); ++i) calc_pll_cs->fract_fb_divider_precision_factor *= 10; return true; } bool dce110_clk_src_construct( struct dce110_clk_src *clk_src, struct dc_context *ctx, struct dc_bios *bios, enum clock_source_id id, const struct dce110_clk_src_regs *regs, const struct dce110_clk_src_shift *cs_shift, const struct dce110_clk_src_mask *cs_mask) { struct dc_firmware_info fw_info = { { 0 } }; struct calc_pll_clock_source_init_data calc_pll_cs_init_data_hdmi; struct calc_pll_clock_source_init_data calc_pll_cs_init_data; clk_src->base.ctx = ctx; clk_src->bios = bios; clk_src->base.id = id; clk_src->base.funcs = &dce110_clk_src_funcs; clk_src->regs = regs; clk_src->cs_shift = cs_shift; clk_src->cs_mask = cs_mask; if (clk_src->bios->funcs->get_firmware_info( clk_src->bios, &fw_info) != BP_RESULT_OK) { ASSERT_CRITICAL(false); goto unexpected_failure; } clk_src->ext_clk_khz = fw_info.external_clock_source_frequency_for_dp; switch (clk_src->base.ctx->dce_version) { case DCE_VERSION_8_0: case DCE_VERSION_8_1: case DCE_VERSION_8_3: case DCE_VERSION_10_0: case DCE_VERSION_11_0: /* structure normally used with PLL ranges from ATOMBIOS; DS on by default */ calc_pll_cs_init_data.bp = bios; calc_pll_cs_init_data.min_pix_clk_pll_post_divider = 1; calc_pll_cs_init_data.max_pix_clk_pll_post_divider = clk_src->cs_mask->PLL_POST_DIV_PIXCLK; calc_pll_cs_init_data.min_pll_ref_divider = 1; calc_pll_cs_init_data.max_pll_ref_divider = clk_src->cs_mask->PLL_REF_DIV; /* when 0 use minInputPxlClkPLLFrequencyInKHz from firmwareInfo*/ calc_pll_cs_init_data.min_override_input_pxl_clk_pll_freq_khz = 0; /* when 0 use maxInputPxlClkPLLFrequencyInKHz from firmwareInfo*/ calc_pll_cs_init_data.max_override_input_pxl_clk_pll_freq_khz = 0; /*numberOfFractFBDividerDecimalPoints*/ calc_pll_cs_init_data.num_fract_fb_divider_decimal_point = FRACT_FB_DIVIDER_DEC_POINTS_MAX_NUM; /*number of decimal point to round off for fractional feedback divider value*/ calc_pll_cs_init_data.num_fract_fb_divider_decimal_point_precision = FRACT_FB_DIVIDER_DEC_POINTS_MAX_NUM; calc_pll_cs_init_data.ctx = ctx; /*structure for HDMI, no SS or SS% <= 0.06% for 27 MHz Ref clock */ calc_pll_cs_init_data_hdmi.bp = bios; calc_pll_cs_init_data_hdmi.min_pix_clk_pll_post_divider = 1; calc_pll_cs_init_data_hdmi.max_pix_clk_pll_post_divider = clk_src->cs_mask->PLL_POST_DIV_PIXCLK; calc_pll_cs_init_data_hdmi.min_pll_ref_divider = 1; calc_pll_cs_init_data_hdmi.max_pll_ref_divider = clk_src->cs_mask->PLL_REF_DIV; /* when 0 use minInputPxlClkPLLFrequencyInKHz from firmwareInfo*/ calc_pll_cs_init_data_hdmi.min_override_input_pxl_clk_pll_freq_khz = 13500; /* when 0 use maxInputPxlClkPLLFrequencyInKHz from firmwareInfo*/ calc_pll_cs_init_data_hdmi.max_override_input_pxl_clk_pll_freq_khz = 27000; /*numberOfFractFBDividerDecimalPoints*/ calc_pll_cs_init_data_hdmi.num_fract_fb_divider_decimal_point = FRACT_FB_DIVIDER_DEC_POINTS_MAX_NUM; /*number of decimal point to round off for fractional feedback divider value*/ calc_pll_cs_init_data_hdmi.num_fract_fb_divider_decimal_point_precision = FRACT_FB_DIVIDER_DEC_POINTS_MAX_NUM; calc_pll_cs_init_data_hdmi.ctx = ctx; clk_src->ref_freq_khz = fw_info.pll_info.crystal_frequency; if (clk_src->base.id == CLOCK_SOURCE_ID_EXTERNAL) return true; /* PLL only from here on */ ss_info_from_atombios_create(clk_src); if (!calc_pll_max_vco_construct( &clk_src->calc_pll, &calc_pll_cs_init_data)) { ASSERT_CRITICAL(false); goto unexpected_failure; } calc_pll_cs_init_data_hdmi. min_override_input_pxl_clk_pll_freq_khz = clk_src->ref_freq_khz/2; calc_pll_cs_init_data_hdmi. max_override_input_pxl_clk_pll_freq_khz = clk_src->ref_freq_khz; if (!calc_pll_max_vco_construct( &clk_src->calc_pll_hdmi, &calc_pll_cs_init_data_hdmi)) { ASSERT_CRITICAL(false); goto unexpected_failure; } break; default: break; } return true; unexpected_failure: return false; }