/* * 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 "clk_mgr.h"#include "dccg.h" #include "reg_helper.h" #define REG(reg)\ (clk_src->regs->reg) #define CTX \ clk_src->base.ctx #define DC_LOGGER \ CTX->logger #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_LVDS: ss_parm = clk_src->lvds_ss_params; entrys_num = clk_src->lvds_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;} /** * calculate_fb_and_fractional_fb_divider - Calculates feedback and fractional * feedback dividers values * * @calc_pll_cs: Pointer to clock source information * @target_pix_clk_100hz: Desired frequency in 100 Hz * @ref_divider: Reference divider (already known) * @post_divider: 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 * * return: * 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_100hz, 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_100hz * 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 * 10ull); /*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 = (uint32_t)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 - Calculates Feedback and * Fractional Feedback divider values * for passed Reference and Post divider, * checking for tolerance. * @calc_pll_cs: Pointer to clock source information * @pll_settings: Pointer to PLL settings * @ref_divider: Reference divider (already known) * @post_divider: Post Divider (already known) * @tolerance: Tolerance for Calculated Pixel Clock to be within * * return: * It fills the PLLSettings structure with PLL Dividers values * if calculated values are within required tolerance * It returns - true if error is within tolerance * - false if error 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_100hz; uint32_t abs_err; uint64_t actual_calc_clk_100hz; calculate_fb_and_fractional_fb_divider( calc_pll_cs, pll_settings->adjusted_pix_clk_100hz, ref_divider, post_divider, &feedback_divider, &fract_feedback_divider); /*Actual calculated value*/ actual_calc_clk_100hz = (uint64_t)feedback_divider * calc_pll_cs->fract_fb_divider_factor + fract_feedback_divider; actual_calc_clk_100hz *= (uint64_t)calc_pll_cs->ref_freq_khz * 10; actual_calc_clk_100hz = div_u64(actual_calc_clk_100hz, ref_divider * post_divider * calc_pll_cs->fract_fb_divider_factor); actual_calculated_clock_100hz = (uint32_t)(actual_calc_clk_100hz); abs_err = (actual_calculated_clock_100hz > pll_settings->adjusted_pix_clk_100hz) ? actual_calculated_clock_100hz - pll_settings->adjusted_pix_clk_100hz : pll_settings->adjusted_pix_clk_100hz - actual_calculated_clock_100hz; 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_100hz = actual_calculated_clock_100hz; pll_settings->vco_freq = (uint32_t)div_u64((u64)actual_calculated_clock_100hz * post_divider, 10); 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_100hz * err_tolerance) / 100000; 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 dce110_clk_src *clk_src, 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_100hz == 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_100hz < calc_pll_cs->min_vco_khz * 10) { min_post_divider = calc_pll_cs->min_vco_khz * 10 / pll_settings->adjusted_pix_clk_100hz; if ((min_post_divider * pll_settings->adjusted_pix_clk_100hz) < calc_pll_cs->min_vco_khz * 10) min_post_divider++; } max_post_divider = calc_pll_cs->max_pix_clock_pll_post_divider; if (max_post_divider * pll_settings->adjusted_pix_clk_100hz > calc_pll_cs->max_vco_khz * 10) max_post_divider = calc_pll_cs->max_vco_khz * 10 / pll_settings->adjusted_pix_clk_100hz; } /* 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_100hz = 0; uint32_t requested_clk_100hz = 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_100hz = pix_clk_params->requested_pix_clk_100hz; if (pix_clk_params->pixel_encoding != PIXEL_ENCODING_YCBCR422) { switch (pix_clk_params->color_depth) { case COLOR_DEPTH_101010: requested_clk_100hz = (requested_clk_100hz * 5) >> 2; break; /* x1.25*/ case COLOR_DEPTH_121212: requested_clk_100hz = (requested_clk_100hz * 6) >> 2; break; /* x1.5*/ case COLOR_DEPTH_161616: requested_clk_100hz = requested_clk_100hz * 2; break; /* x2.0*/ default: break; } } actual_pix_clk_100hz = requested_clk_100hz; } break; case SIGNAL_TYPE_DISPLAY_PORT: case SIGNAL_TYPE_DISPLAY_PORT_MST: case SIGNAL_TYPE_EDP: requested_clk_100hz = pix_clk_params->requested_sym_clk * 10; actual_pix_clk_100hz = pix_clk_params->requested_pix_clk_100hz; break; default: requested_clk_100hz = pix_clk_params->requested_pix_clk_100hz; actual_pix_clk_100hz = pix_clk_params->requested_pix_clk_100hz; break; } bp_adjust_pixel_clock_params.pixel_clock = requested_clk_100hz / 10; 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 != 0; 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_100hz = actual_pix_clk_100hz; pll_settings->adjusted_pix_clk_100hz = bp_adjust_pixel_clock_params.adjusted_pixel_clock * 10; 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; /* 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_100hz / 10); 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)) {Lines 1–500 of 1967. Load more
/* * 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 "clk_mgr.h" #include "dccg.h" #include "reg_helper.h" #define REG(reg)\ (clk_src->regs->reg) #define CTX \ clk_src->base.ctx #define DC_LOGGER \ CTX->logger #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_LVDS: ss_parm = clk_src->lvds_ss_params; entrys_num = clk_src->lvds_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; } /** * calculate_fb_and_fractional_fb_divider - Calculates feedback and fractional * feedback dividers values * * @calc_pll_cs: Pointer to clock source information * @target_pix_clk_100hz: Desired frequency in 100 Hz * @ref_divider: Reference divider (already known) * @post_divider: 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 * * return: * 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_100hz, 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_100hz * 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 * 10ull); /*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 = (uint32_t)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 - Calculates Feedback and * Fractional Feedback divider values * for passed Reference and Post divider, * checking for tolerance. * @calc_pll_cs: Pointer to clock source information * @pll_settings: Pointer to PLL settings * @ref_divider: Reference divider (already known) * @post_divider: Post Divider (already known) * @tolerance: Tolerance for Calculated Pixel Clock to be within * * return: * It fills the PLLSettings structure with PLL Dividers values * if calculated values are within required tolerance * It returns - true if error is within tolerance * - false if error 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_100hz; uint32_t abs_err; uint64_t actual_calc_clk_100hz; calculate_fb_and_fractional_fb_divider( calc_pll_cs, pll_settings->adjusted_pix_clk_100hz, ref_divider, post_divider, &feedback_divider, &fract_feedback_divider); /*Actual calculated value*/ actual_calc_clk_100hz = (uint64_t)feedback_divider * calc_pll_cs->fract_fb_divider_factor + fract_feedback_divider; actual_calc_clk_100hz *= (uint64_t)calc_pll_cs->ref_freq_khz * 10; actual_calc_clk_100hz = div_u64(actual_calc_clk_100hz, ref_divider * post_divider * calc_pll_cs->fract_fb_divider_factor); actual_calculated_clock_100hz = (uint32_t)(actual_calc_clk_100hz); abs_err = (actual_calculated_clock_100hz > pll_settings->adjusted_pix_clk_100hz) ? actual_calculated_clock_100hz - pll_settings->adjusted_pix_clk_100hz : pll_settings->adjusted_pix_clk_100hz - actual_calculated_clock_100hz; 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_100hz = actual_calculated_clock_100hz; pll_settings->vco_freq = (uint32_t)div_u64((u64)actual_calculated_clock_100hz * post_divider, 10); 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_100hz * err_tolerance) / 100000; 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 dce110_clk_src *clk_src, 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_100hz == 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_100hz < calc_pll_cs->min_vco_khz * 10) { min_post_divider = calc_pll_cs->min_vco_khz * 10 / pll_settings->adjusted_pix_clk_100hz; if ((min_post_divider * pll_settings->adjusted_pix_clk_100hz) < calc_pll_cs->min_vco_khz * 10) min_post_divider++; } max_post_divider = calc_pll_cs->max_pix_clock_pll_post_divider; if (max_post_divider * pll_settings->adjusted_pix_clk_100hz > calc_pll_cs->max_vco_khz * 10) max_post_divider = calc_pll_cs->max_vco_khz * 10 / pll_settings->adjusted_pix_clk_100hz; } /* 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_100hz = 0; uint32_t requested_clk_100hz = 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_100hz = pix_clk_params->requested_pix_clk_100hz; if (pix_clk_params->pixel_encoding != PIXEL_ENCODING_YCBCR422) { switch (pix_clk_params->color_depth) { case COLOR_DEPTH_101010: requested_clk_100hz = (requested_clk_100hz * 5) >> 2; break; /* x1.25*/ case COLOR_DEPTH_121212: requested_clk_100hz = (requested_clk_100hz * 6) >> 2; break; /* x1.5*/ case COLOR_DEPTH_161616: requested_clk_100hz = requested_clk_100hz * 2; break; /* x2.0*/ default: break; } } actual_pix_clk_100hz = requested_clk_100hz; } break; case SIGNAL_TYPE_DISPLAY_PORT: case SIGNAL_TYPE_DISPLAY_PORT_MST: case SIGNAL_TYPE_EDP: requested_clk_100hz = pix_clk_params->requested_sym_clk * 10; actual_pix_clk_100hz = pix_clk_params->requested_pix_clk_100hz; break; default: requested_clk_100hz = pix_clk_params->requested_pix_clk_100hz; actual_pix_clk_100hz = pix_clk_params->requested_pix_clk_100hz; break; } bp_adjust_pixel_clock_params.pixel_clock = requested_clk_100hz / 10; 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 != 0; 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_100hz = actual_pix_clk_100hz; pll_settings->adjusted_pix_clk_100hz = bp_adjust_pixel_clock_params.adjusted_pixel_clock * 10; 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; /* 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_100hz / 10); 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)) {