Home | History | Annotate | Line # | Download | only in amdgpu
amdgpu_pll.c revision 1.1
      1 /*	$NetBSD: amdgpu_pll.c,v 1.1 2018/08/27 01:34:44 riastradh Exp $	*/
      2 
      3 /*
      4  * Copyright 2014 Advanced Micro Devices, Inc.
      5  *
      6  * Permission is hereby granted, free of charge, to any person obtaining a
      7  * copy of this software and associated documentation files (the "Software"),
      8  * to deal in the Software without restriction, including without limitation
      9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
     10  * and/or sell copies of the Software, and to permit persons to whom the
     11  * Software is furnished to do so, subject to the following conditions:
     12  *
     13  * The above copyright notice and this permission notice shall be included in
     14  * all copies or substantial portions of the Software.
     15  *
     16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     19  * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
     20  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
     21  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
     22  * OTHER DEALINGS IN THE SOFTWARE.
     23  *
     24  */
     25 #include <sys/cdefs.h>
     26 __KERNEL_RCSID(0, "$NetBSD: amdgpu_pll.c,v 1.1 2018/08/27 01:34:44 riastradh Exp $");
     27 
     28 #include <drm/drmP.h>
     29 #include <drm/amdgpu_drm.h>
     30 #include "amdgpu.h"
     31 #include "atom.h"
     32 #include "atombios_encoders.h"
     33 #include <asm/div64.h>
     34 #include <linux/gcd.h>
     35 
     36 /**
     37  * amdgpu_pll_reduce_ratio - fractional number reduction
     38  *
     39  * @nom: nominator
     40  * @den: denominator
     41  * @nom_min: minimum value for nominator
     42  * @den_min: minimum value for denominator
     43  *
     44  * Find the greatest common divisor and apply it on both nominator and
     45  * denominator, but make nominator and denominator are at least as large
     46  * as their minimum values.
     47  */
     48 static void amdgpu_pll_reduce_ratio(unsigned *nom, unsigned *den,
     49 				    unsigned nom_min, unsigned den_min)
     50 {
     51 	unsigned tmp;
     52 
     53 	/* reduce the numbers to a simpler ratio */
     54 	tmp = gcd(*nom, *den);
     55 	*nom /= tmp;
     56 	*den /= tmp;
     57 
     58 	/* make sure nominator is large enough */
     59 	if (*nom < nom_min) {
     60 		tmp = DIV_ROUND_UP(nom_min, *nom);
     61 		*nom *= tmp;
     62 		*den *= tmp;
     63 	}
     64 
     65 	/* make sure the denominator is large enough */
     66 	if (*den < den_min) {
     67 		tmp = DIV_ROUND_UP(den_min, *den);
     68 		*nom *= tmp;
     69 		*den *= tmp;
     70 	}
     71 }
     72 
     73 /**
     74  * amdgpu_pll_get_fb_ref_div - feedback and ref divider calculation
     75  *
     76  * @nom: nominator
     77  * @den: denominator
     78  * @post_div: post divider
     79  * @fb_div_max: feedback divider maximum
     80  * @ref_div_max: reference divider maximum
     81  * @fb_div: resulting feedback divider
     82  * @ref_div: resulting reference divider
     83  *
     84  * Calculate feedback and reference divider for a given post divider. Makes
     85  * sure we stay within the limits.
     86  */
     87 static void amdgpu_pll_get_fb_ref_div(unsigned nom, unsigned den, unsigned post_div,
     88 				      unsigned fb_div_max, unsigned ref_div_max,
     89 				      unsigned *fb_div, unsigned *ref_div)
     90 {
     91 	/* limit reference * post divider to a maximum */
     92 	ref_div_max = min(128 / post_div, ref_div_max);
     93 
     94 	/* get matching reference and feedback divider */
     95 	*ref_div = min(max(DIV_ROUND_CLOSEST(den, post_div), 1u), ref_div_max);
     96 	*fb_div = DIV_ROUND_CLOSEST(nom * *ref_div * post_div, den);
     97 
     98 	/* limit fb divider to its maximum */
     99 	if (*fb_div > fb_div_max) {
    100 		*ref_div = DIV_ROUND_CLOSEST(*ref_div * fb_div_max, *fb_div);
    101 		*fb_div = fb_div_max;
    102 	}
    103 }
    104 
    105 /**
    106  * amdgpu_pll_compute - compute PLL paramaters
    107  *
    108  * @pll: information about the PLL
    109  * @dot_clock_p: resulting pixel clock
    110  * fb_div_p: resulting feedback divider
    111  * frac_fb_div_p: fractional part of the feedback divider
    112  * ref_div_p: resulting reference divider
    113  * post_div_p: resulting reference divider
    114  *
    115  * Try to calculate the PLL parameters to generate the given frequency:
    116  * dot_clock = (ref_freq * feedback_div) / (ref_div * post_div)
    117  */
    118 void amdgpu_pll_compute(struct amdgpu_pll *pll,
    119 			u32 freq,
    120 			u32 *dot_clock_p,
    121 			u32 *fb_div_p,
    122 			u32 *frac_fb_div_p,
    123 			u32 *ref_div_p,
    124 			u32 *post_div_p)
    125 {
    126 	unsigned target_clock = pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV ?
    127 		freq : freq / 10;
    128 
    129 	unsigned fb_div_min, fb_div_max, fb_div;
    130 	unsigned post_div_min, post_div_max, post_div;
    131 	unsigned ref_div_min, ref_div_max, ref_div;
    132 	unsigned post_div_best, diff_best;
    133 	unsigned nom, den;
    134 
    135 	/* determine allowed feedback divider range */
    136 	fb_div_min = pll->min_feedback_div;
    137 	fb_div_max = pll->max_feedback_div;
    138 
    139 	if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV) {
    140 		fb_div_min *= 10;
    141 		fb_div_max *= 10;
    142 	}
    143 
    144 	/* determine allowed ref divider range */
    145 	if (pll->flags & AMDGPU_PLL_USE_REF_DIV)
    146 		ref_div_min = pll->reference_div;
    147 	else
    148 		ref_div_min = pll->min_ref_div;
    149 
    150 	if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV &&
    151 	    pll->flags & AMDGPU_PLL_USE_REF_DIV)
    152 		ref_div_max = pll->reference_div;
    153 	else
    154 		ref_div_max = pll->max_ref_div;
    155 
    156 	/* determine allowed post divider range */
    157 	if (pll->flags & AMDGPU_PLL_USE_POST_DIV) {
    158 		post_div_min = pll->post_div;
    159 		post_div_max = pll->post_div;
    160 	} else {
    161 		unsigned vco_min, vco_max;
    162 
    163 		if (pll->flags & AMDGPU_PLL_IS_LCD) {
    164 			vco_min = pll->lcd_pll_out_min;
    165 			vco_max = pll->lcd_pll_out_max;
    166 		} else {
    167 			vco_min = pll->pll_out_min;
    168 			vco_max = pll->pll_out_max;
    169 		}
    170 
    171 		if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV) {
    172 			vco_min *= 10;
    173 			vco_max *= 10;
    174 		}
    175 
    176 		post_div_min = vco_min / target_clock;
    177 		if ((target_clock * post_div_min) < vco_min)
    178 			++post_div_min;
    179 		if (post_div_min < pll->min_post_div)
    180 			post_div_min = pll->min_post_div;
    181 
    182 		post_div_max = vco_max / target_clock;
    183 		if ((target_clock * post_div_max) > vco_max)
    184 			--post_div_max;
    185 		if (post_div_max > pll->max_post_div)
    186 			post_div_max = pll->max_post_div;
    187 	}
    188 
    189 	/* represent the searched ratio as fractional number */
    190 	nom = target_clock;
    191 	den = pll->reference_freq;
    192 
    193 	/* reduce the numbers to a simpler ratio */
    194 	amdgpu_pll_reduce_ratio(&nom, &den, fb_div_min, post_div_min);
    195 
    196 	/* now search for a post divider */
    197 	if (pll->flags & AMDGPU_PLL_PREFER_MINM_OVER_MAXP)
    198 		post_div_best = post_div_min;
    199 	else
    200 		post_div_best = post_div_max;
    201 	diff_best = ~0;
    202 
    203 	for (post_div = post_div_min; post_div <= post_div_max; ++post_div) {
    204 		unsigned diff;
    205 		amdgpu_pll_get_fb_ref_div(nom, den, post_div, fb_div_max,
    206 					  ref_div_max, &fb_div, &ref_div);
    207 		diff = abs(target_clock - (pll->reference_freq * fb_div) /
    208 			(ref_div * post_div));
    209 
    210 		if (diff < diff_best || (diff == diff_best &&
    211 		    !(pll->flags & AMDGPU_PLL_PREFER_MINM_OVER_MAXP))) {
    212 
    213 			post_div_best = post_div;
    214 			diff_best = diff;
    215 		}
    216 	}
    217 	post_div = post_div_best;
    218 
    219 	/* get the feedback and reference divider for the optimal value */
    220 	amdgpu_pll_get_fb_ref_div(nom, den, post_div, fb_div_max, ref_div_max,
    221 				  &fb_div, &ref_div);
    222 
    223 	/* reduce the numbers to a simpler ratio once more */
    224 	/* this also makes sure that the reference divider is large enough */
    225 	amdgpu_pll_reduce_ratio(&fb_div, &ref_div, fb_div_min, ref_div_min);
    226 
    227 	/* avoid high jitter with small fractional dividers */
    228 	if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV && (fb_div % 10)) {
    229 		fb_div_min = max(fb_div_min, (9 - (fb_div % 10)) * 20 + 60);
    230 		if (fb_div < fb_div_min) {
    231 			unsigned tmp = DIV_ROUND_UP(fb_div_min, fb_div);
    232 			fb_div *= tmp;
    233 			ref_div *= tmp;
    234 		}
    235 	}
    236 
    237 	/* and finally save the result */
    238 	if (pll->flags & AMDGPU_PLL_USE_FRAC_FB_DIV) {
    239 		*fb_div_p = fb_div / 10;
    240 		*frac_fb_div_p = fb_div % 10;
    241 	} else {
    242 		*fb_div_p = fb_div;
    243 		*frac_fb_div_p = 0;
    244 	}
    245 
    246 	*dot_clock_p = ((pll->reference_freq * *fb_div_p * 10) +
    247 			(pll->reference_freq * *frac_fb_div_p)) /
    248 		       (ref_div * post_div * 10);
    249 	*ref_div_p = ref_div;
    250 	*post_div_p = post_div;
    251 
    252 	DRM_DEBUG_KMS("%d - %d, pll dividers - fb: %d.%d ref: %d, post %d\n",
    253 		      freq, *dot_clock_p * 10, *fb_div_p, *frac_fb_div_p,
    254 		      ref_div, post_div);
    255 }
    256 
    257 /**
    258  * amdgpu_pll_get_use_mask - look up a mask of which pplls are in use
    259  *
    260  * @crtc: drm crtc
    261  *
    262  * Returns the mask of which PPLLs (Pixel PLLs) are in use.
    263  */
    264 u32 amdgpu_pll_get_use_mask(struct drm_crtc *crtc)
    265 {
    266 	struct drm_device *dev = crtc->dev;
    267 	struct drm_crtc *test_crtc;
    268 	struct amdgpu_crtc *test_amdgpu_crtc;
    269 	u32 pll_in_use = 0;
    270 
    271 	list_for_each_entry(test_crtc, &dev->mode_config.crtc_list, head) {
    272 		if (crtc == test_crtc)
    273 			continue;
    274 
    275 		test_amdgpu_crtc = to_amdgpu_crtc(test_crtc);
    276 		if (test_amdgpu_crtc->pll_id != ATOM_PPLL_INVALID)
    277 			pll_in_use |= (1 << test_amdgpu_crtc->pll_id);
    278 	}
    279 	return pll_in_use;
    280 }
    281 
    282 /**
    283  * amdgpu_pll_get_shared_dp_ppll - return the PPLL used by another crtc for DP
    284  *
    285  * @crtc: drm crtc
    286  *
    287  * Returns the PPLL (Pixel PLL) used by another crtc/encoder which is
    288  * also in DP mode.  For DP, a single PPLL can be used for all DP
    289  * crtcs/encoders.
    290  */
    291 int amdgpu_pll_get_shared_dp_ppll(struct drm_crtc *crtc)
    292 {
    293 	struct drm_device *dev = crtc->dev;
    294 	struct drm_crtc *test_crtc;
    295 	struct amdgpu_crtc *test_amdgpu_crtc;
    296 
    297 	list_for_each_entry(test_crtc, &dev->mode_config.crtc_list, head) {
    298 		if (crtc == test_crtc)
    299 			continue;
    300 		test_amdgpu_crtc = to_amdgpu_crtc(test_crtc);
    301 		if (test_amdgpu_crtc->encoder &&
    302 		    ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(test_amdgpu_crtc->encoder))) {
    303 			/* for DP use the same PLL for all */
    304 			if (test_amdgpu_crtc->pll_id != ATOM_PPLL_INVALID)
    305 				return test_amdgpu_crtc->pll_id;
    306 		}
    307 	}
    308 	return ATOM_PPLL_INVALID;
    309 }
    310 
    311 /**
    312  * amdgpu_pll_get_shared_nondp_ppll - return the PPLL used by another non-DP crtc
    313  *
    314  * @crtc: drm crtc
    315  * @encoder: drm encoder
    316  *
    317  * Returns the PPLL (Pixel PLL) used by another non-DP crtc/encoder which can
    318  * be shared (i.e., same clock).
    319  */
    320 int amdgpu_pll_get_shared_nondp_ppll(struct drm_crtc *crtc)
    321 {
    322 	struct amdgpu_crtc *amdgpu_crtc = to_amdgpu_crtc(crtc);
    323 	struct drm_device *dev = crtc->dev;
    324 	struct drm_crtc *test_crtc;
    325 	struct amdgpu_crtc *test_amdgpu_crtc;
    326 	u32 adjusted_clock, test_adjusted_clock;
    327 
    328 	adjusted_clock = amdgpu_crtc->adjusted_clock;
    329 
    330 	if (adjusted_clock == 0)
    331 		return ATOM_PPLL_INVALID;
    332 
    333 	list_for_each_entry(test_crtc, &dev->mode_config.crtc_list, head) {
    334 		if (crtc == test_crtc)
    335 			continue;
    336 		test_amdgpu_crtc = to_amdgpu_crtc(test_crtc);
    337 		if (test_amdgpu_crtc->encoder &&
    338 		    !ENCODER_MODE_IS_DP(amdgpu_atombios_encoder_get_encoder_mode(test_amdgpu_crtc->encoder))) {
    339 			/* check if we are already driving this connector with another crtc */
    340 			if (test_amdgpu_crtc->connector == amdgpu_crtc->connector) {
    341 				/* if we are, return that pll */
    342 				if (test_amdgpu_crtc->pll_id != ATOM_PPLL_INVALID)
    343 					return test_amdgpu_crtc->pll_id;
    344 			}
    345 			/* for non-DP check the clock */
    346 			test_adjusted_clock = test_amdgpu_crtc->adjusted_clock;
    347 			if ((crtc->mode.clock == test_crtc->mode.clock) &&
    348 			    (adjusted_clock == test_adjusted_clock) &&
    349 			    (amdgpu_crtc->ss_enabled == test_amdgpu_crtc->ss_enabled) &&
    350 			    (test_amdgpu_crtc->pll_id != ATOM_PPLL_INVALID))
    351 				return test_amdgpu_crtc->pll_id;
    352 		}
    353 	}
    354 	return ATOM_PPLL_INVALID;
    355 }
    356