xref: /openbsd-src/sys/dev/pci/drm/amd/display/modules/freesync/freesync.c (revision cad63ac9edb87eec92b58e7cce36c95bdaa8212f)
1fb4d8502Sjsg /*
2f005ef32Sjsg  * Copyright 2016-2023 Advanced Micro Devices, Inc.
3fb4d8502Sjsg  *
4fb4d8502Sjsg  * Permission is hereby granted, free of charge, to any person obtaining a
5fb4d8502Sjsg  * copy of this software and associated documentation files (the "Software"),
6fb4d8502Sjsg  * to deal in the Software without restriction, including without limitation
7fb4d8502Sjsg  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8fb4d8502Sjsg  * and/or sell copies of the Software, and to permit persons to whom the
9fb4d8502Sjsg  * Software is furnished to do so, subject to the following conditions:
10fb4d8502Sjsg  *
11fb4d8502Sjsg  * The above copyright notice and this permission notice shall be included in
12fb4d8502Sjsg  * all copies or substantial portions of the Software.
13fb4d8502Sjsg  *
14fb4d8502Sjsg  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15fb4d8502Sjsg  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16fb4d8502Sjsg  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17fb4d8502Sjsg  * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
18fb4d8502Sjsg  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19fb4d8502Sjsg  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20fb4d8502Sjsg  * OTHER DEALINGS IN THE SOFTWARE.
21fb4d8502Sjsg  *
22fb4d8502Sjsg  * Authors: AMD
23fb4d8502Sjsg  *
24fb4d8502Sjsg  */
25fb4d8502Sjsg 
26fb4d8502Sjsg #include "dm_services.h"
27fb4d8502Sjsg #include "dc.h"
28fb4d8502Sjsg #include "mod_freesync.h"
29fb4d8502Sjsg #include "core_types.h"
30fb4d8502Sjsg 
31fb4d8502Sjsg #define MOD_FREESYNC_MAX_CONCURRENT_STREAMS  32
32fb4d8502Sjsg 
33ad8b1aafSjsg #define MIN_REFRESH_RANGE 10
34fb4d8502Sjsg /* Refresh rate ramp at a fixed rate of 65 Hz/second */
35fb4d8502Sjsg #define STATIC_SCREEN_RAMP_DELTA_REFRESH_RATE_PER_FRAME ((1000 / 60) * 65)
36fb4d8502Sjsg /* Number of elements in the render times cache array */
37fb4d8502Sjsg #define RENDER_TIMES_MAX_COUNT 10
38c349dbc7Sjsg /* Threshold to exit/exit BTR (to avoid frequent enter-exits at the lower limit) */
39c349dbc7Sjsg #define BTR_MAX_MARGIN 2500
40c349dbc7Sjsg /* Threshold to change BTR multiplier (to avoid frequent changes) */
41c349dbc7Sjsg #define BTR_DRIFT_MARGIN 2000
42c349dbc7Sjsg /* Threshold to exit fixed refresh rate */
435ca02815Sjsg #define FIXED_REFRESH_EXIT_MARGIN_IN_HZ 1
44fb4d8502Sjsg /* Number of consecutive frames to check before entering/exiting fixed refresh */
45fb4d8502Sjsg #define FIXED_REFRESH_ENTER_FRAME_COUNT 5
465ca02815Sjsg #define FIXED_REFRESH_EXIT_FRAME_COUNT 10
471bb76ff1Sjsg /* Flip interval workaround constants */
481bb76ff1Sjsg #define VSYNCS_BETWEEN_FLIP_THRESHOLD 2
491bb76ff1Sjsg #define FREESYNC_CONSEC_FLIP_AFTER_VSYNC 5
501bb76ff1Sjsg #define FREESYNC_VSYNC_TO_FLIP_DELTA_IN_US 500
51fb4d8502Sjsg 
52fb4d8502Sjsg struct core_freesync {
53fb4d8502Sjsg 	struct mod_freesync public;
54fb4d8502Sjsg 	struct dc *dc;
55fb4d8502Sjsg };
56fb4d8502Sjsg 
57fb4d8502Sjsg #define MOD_FREESYNC_TO_CORE(mod_freesync)\
58fb4d8502Sjsg 		container_of(mod_freesync, struct core_freesync, public)
59fb4d8502Sjsg 
60fb4d8502Sjsg struct mod_freesync *mod_freesync_create(struct dc *dc)
61fb4d8502Sjsg {
62fb4d8502Sjsg 	struct core_freesync *core_freesync =
63fb4d8502Sjsg 			kzalloc(sizeof(struct core_freesync), GFP_KERNEL);
64fb4d8502Sjsg 
65fb4d8502Sjsg 	if (core_freesync == NULL)
66fb4d8502Sjsg 		goto fail_alloc_context;
67fb4d8502Sjsg 
68fb4d8502Sjsg 	if (dc == NULL)
69fb4d8502Sjsg 		goto fail_construct;
70fb4d8502Sjsg 
71fb4d8502Sjsg 	core_freesync->dc = dc;
72fb4d8502Sjsg 	return &core_freesync->public;
73fb4d8502Sjsg 
74fb4d8502Sjsg fail_construct:
75fb4d8502Sjsg 	kfree(core_freesync);
76fb4d8502Sjsg 
77fb4d8502Sjsg fail_alloc_context:
78fb4d8502Sjsg 	return NULL;
79fb4d8502Sjsg }
80fb4d8502Sjsg 
81fb4d8502Sjsg void mod_freesync_destroy(struct mod_freesync *mod_freesync)
82fb4d8502Sjsg {
83c349dbc7Sjsg 	struct core_freesync *core_freesync = NULL;
84c349dbc7Sjsg 	if (mod_freesync == NULL)
85c349dbc7Sjsg 		return;
86c349dbc7Sjsg 	core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync);
87fb4d8502Sjsg 	kfree(core_freesync);
88fb4d8502Sjsg }
89fb4d8502Sjsg 
905ca02815Sjsg #if 0 /* Unused currently */
91c349dbc7Sjsg static unsigned int calc_refresh_in_uhz_from_duration(
92c349dbc7Sjsg 		unsigned int duration_in_ns)
93fb4d8502Sjsg {
94c349dbc7Sjsg 	unsigned int refresh_in_uhz =
95c349dbc7Sjsg 			((unsigned int)(div64_u64((1000000000ULL * 1000000),
96c349dbc7Sjsg 					duration_in_ns)));
97c349dbc7Sjsg 	return refresh_in_uhz;
98c349dbc7Sjsg }
99c349dbc7Sjsg #endif
100fb4d8502Sjsg 
101c349dbc7Sjsg static unsigned int calc_duration_in_us_from_refresh_in_uhz(
102c349dbc7Sjsg 		unsigned int refresh_in_uhz)
103fb4d8502Sjsg {
104c349dbc7Sjsg 	unsigned int duration_in_us =
105c349dbc7Sjsg 			((unsigned int)(div64_u64((1000000000ULL * 1000),
106c349dbc7Sjsg 					refresh_in_uhz)));
107c349dbc7Sjsg 	return duration_in_us;
108fb4d8502Sjsg }
109fb4d8502Sjsg 
110c349dbc7Sjsg static unsigned int calc_duration_in_us_from_v_total(
111c349dbc7Sjsg 		const struct dc_stream_state *stream,
112c349dbc7Sjsg 		const struct mod_vrr_params *in_vrr,
113c349dbc7Sjsg 		unsigned int v_total)
114fb4d8502Sjsg {
115c349dbc7Sjsg 	unsigned int duration_in_us =
116c349dbc7Sjsg 			(unsigned int)(div64_u64(((unsigned long long)(v_total)
117c349dbc7Sjsg 				* 10000) * stream->timing.h_total,
118c349dbc7Sjsg 					stream->timing.pix_clk_100hz));
119fb4d8502Sjsg 
120c349dbc7Sjsg 	return duration_in_us;
121fb4d8502Sjsg }
122fb4d8502Sjsg 
1235ca02815Sjsg unsigned int mod_freesync_calc_v_total_from_refresh(
124c349dbc7Sjsg 		const struct dc_stream_state *stream,
125c349dbc7Sjsg 		unsigned int refresh_in_uhz)
126fb4d8502Sjsg {
127c349dbc7Sjsg 	unsigned int v_total;
128c349dbc7Sjsg 	unsigned int frame_duration_in_ns;
129fb4d8502Sjsg 
130c349dbc7Sjsg 	frame_duration_in_ns =
131c349dbc7Sjsg 			((unsigned int)(div64_u64((1000000000ULL * 1000000),
132c349dbc7Sjsg 					refresh_in_uhz)));
133fb4d8502Sjsg 
134c349dbc7Sjsg 	v_total = div64_u64(div64_u64(((unsigned long long)(
135c349dbc7Sjsg 			frame_duration_in_ns) * (stream->timing.pix_clk_100hz / 10)),
136*cad63ac9Sjsg 			stream->timing.h_total) + 500000, 1000000);
137fb4d8502Sjsg 
138c349dbc7Sjsg 	/* v_total cannot be less than nominal */
139c349dbc7Sjsg 	if (v_total < stream->timing.v_total) {
140c349dbc7Sjsg 		ASSERT(v_total < stream->timing.v_total);
141c349dbc7Sjsg 		v_total = stream->timing.v_total;
142fb4d8502Sjsg 	}
143fb4d8502Sjsg 
144c349dbc7Sjsg 	return v_total;
145fb4d8502Sjsg }
146fb4d8502Sjsg 
147c349dbc7Sjsg static unsigned int calc_v_total_from_duration(
148c349dbc7Sjsg 		const struct dc_stream_state *stream,
149c349dbc7Sjsg 		const struct mod_vrr_params *vrr,
150c349dbc7Sjsg 		unsigned int duration_in_us)
151fb4d8502Sjsg {
152c349dbc7Sjsg 	unsigned int v_total = 0;
153c349dbc7Sjsg 
154c349dbc7Sjsg 	if (duration_in_us < vrr->min_duration_in_us)
155c349dbc7Sjsg 		duration_in_us = vrr->min_duration_in_us;
156c349dbc7Sjsg 
157c349dbc7Sjsg 	if (duration_in_us > vrr->max_duration_in_us)
158c349dbc7Sjsg 		duration_in_us = vrr->max_duration_in_us;
159c349dbc7Sjsg 
1601bb76ff1Sjsg 	if (dc_is_hdmi_signal(stream->signal)) {
1611bb76ff1Sjsg 		uint32_t h_total_up_scaled;
1621bb76ff1Sjsg 
1631bb76ff1Sjsg 		h_total_up_scaled = stream->timing.h_total * 10000;
1641bb76ff1Sjsg 		v_total = div_u64((unsigned long long)duration_in_us
1651bb76ff1Sjsg 					* stream->timing.pix_clk_100hz + (h_total_up_scaled - 1),
1661bb76ff1Sjsg 					h_total_up_scaled);
1671bb76ff1Sjsg 	} else {
168c349dbc7Sjsg 		v_total = div64_u64(div64_u64(((unsigned long long)(
169c349dbc7Sjsg 					duration_in_us) * (stream->timing.pix_clk_100hz / 10)),
170c349dbc7Sjsg 					stream->timing.h_total), 1000);
1711bb76ff1Sjsg 	}
172c349dbc7Sjsg 
173c349dbc7Sjsg 	/* v_total cannot be less than nominal */
174c349dbc7Sjsg 	if (v_total < stream->timing.v_total) {
175c349dbc7Sjsg 		ASSERT(v_total < stream->timing.v_total);
176c349dbc7Sjsg 		v_total = stream->timing.v_total;
177fb4d8502Sjsg 	}
178fb4d8502Sjsg 
179c349dbc7Sjsg 	return v_total;
180c349dbc7Sjsg }
181fb4d8502Sjsg 
182c349dbc7Sjsg static void update_v_total_for_static_ramp(
183c349dbc7Sjsg 		struct core_freesync *core_freesync,
184c349dbc7Sjsg 		const struct dc_stream_state *stream,
185c349dbc7Sjsg 		struct mod_vrr_params *in_out_vrr)
186c349dbc7Sjsg {
187c349dbc7Sjsg 	unsigned int v_total = 0;
188c349dbc7Sjsg 	unsigned int current_duration_in_us =
189c349dbc7Sjsg 			calc_duration_in_us_from_v_total(
190c349dbc7Sjsg 				stream, in_out_vrr,
191c349dbc7Sjsg 				in_out_vrr->adjust.v_total_max);
192c349dbc7Sjsg 	unsigned int target_duration_in_us =
193c349dbc7Sjsg 			calc_duration_in_us_from_refresh_in_uhz(
194c349dbc7Sjsg 				in_out_vrr->fixed.target_refresh_in_uhz);
195c349dbc7Sjsg 	bool ramp_direction_is_up = (current_duration_in_us >
196c349dbc7Sjsg 				target_duration_in_us) ? true : false;
197fb4d8502Sjsg 
1985ca02815Sjsg 	/* Calculate ratio between new and current frame duration with 3 digit */
199fb4d8502Sjsg 	unsigned int frame_duration_ratio = div64_u64(1000000,
200fb4d8502Sjsg 		(1000 +  div64_u64(((unsigned long long)(
201fb4d8502Sjsg 		STATIC_SCREEN_RAMP_DELTA_REFRESH_RATE_PER_FRAME) *
202c349dbc7Sjsg 		current_duration_in_us),
203c349dbc7Sjsg 		1000000)));
204fb4d8502Sjsg 
205c349dbc7Sjsg 	/* Calculate delta between new and current frame duration in us */
206fb4d8502Sjsg 	unsigned int frame_duration_delta = div64_u64(((unsigned long long)(
207c349dbc7Sjsg 		current_duration_in_us) *
208fb4d8502Sjsg 		(1000 - frame_duration_ratio)), 1000);
209fb4d8502Sjsg 
210fb4d8502Sjsg 	/* Adjust frame duration delta based on ratio between current and
211fb4d8502Sjsg 	 * standard frame duration (frame duration at 60 Hz refresh rate).
212fb4d8502Sjsg 	 */
213fb4d8502Sjsg 	unsigned int ramp_rate_interpolated = div64_u64(((unsigned long long)(
214c349dbc7Sjsg 		frame_duration_delta) * current_duration_in_us), 16666);
215fb4d8502Sjsg 
216fb4d8502Sjsg 	/* Going to a higher refresh rate (lower frame duration) */
217c349dbc7Sjsg 	if (ramp_direction_is_up) {
2185ca02815Sjsg 		/* Reduce frame duration */
219c349dbc7Sjsg 		current_duration_in_us -= ramp_rate_interpolated;
220fb4d8502Sjsg 
2215ca02815Sjsg 		/* Adjust for frame duration below min */
222c349dbc7Sjsg 		if (current_duration_in_us <= target_duration_in_us) {
223c349dbc7Sjsg 			in_out_vrr->fixed.ramping_active = false;
224c349dbc7Sjsg 			in_out_vrr->fixed.ramping_done = true;
225c349dbc7Sjsg 			current_duration_in_us =
226c349dbc7Sjsg 				calc_duration_in_us_from_refresh_in_uhz(
227c349dbc7Sjsg 				in_out_vrr->fixed.target_refresh_in_uhz);
228fb4d8502Sjsg 		}
229fb4d8502Sjsg 	/* Going to a lower refresh rate (larger frame duration) */
230fb4d8502Sjsg 	} else {
2315ca02815Sjsg 		/* Increase frame duration */
232c349dbc7Sjsg 		current_duration_in_us += ramp_rate_interpolated;
233fb4d8502Sjsg 
2345ca02815Sjsg 		/* Adjust for frame duration above max */
235c349dbc7Sjsg 		if (current_duration_in_us >= target_duration_in_us) {
236c349dbc7Sjsg 			in_out_vrr->fixed.ramping_active = false;
237c349dbc7Sjsg 			in_out_vrr->fixed.ramping_done = true;
238c349dbc7Sjsg 			current_duration_in_us =
239c349dbc7Sjsg 				calc_duration_in_us_from_refresh_in_uhz(
240c349dbc7Sjsg 				in_out_vrr->fixed.target_refresh_in_uhz);
241fb4d8502Sjsg 		}
242fb4d8502Sjsg 	}
243fb4d8502Sjsg 
244c349dbc7Sjsg 	v_total = div64_u64(div64_u64(((unsigned long long)(
245c349dbc7Sjsg 			current_duration_in_us) * (stream->timing.pix_clk_100hz / 10)),
246c349dbc7Sjsg 				stream->timing.h_total), 1000);
247c349dbc7Sjsg 
248c349dbc7Sjsg 	/* v_total cannot be less than nominal */
249c349dbc7Sjsg 	if (v_total < stream->timing.v_total)
250c349dbc7Sjsg 		v_total = stream->timing.v_total;
251c349dbc7Sjsg 
252c349dbc7Sjsg 	in_out_vrr->adjust.v_total_min = v_total;
253c349dbc7Sjsg 	in_out_vrr->adjust.v_total_max = v_total;
254fb4d8502Sjsg }
255fb4d8502Sjsg 
256c349dbc7Sjsg static void apply_below_the_range(struct core_freesync *core_freesync,
257c349dbc7Sjsg 		const struct dc_stream_state *stream,
258c349dbc7Sjsg 		unsigned int last_render_time_in_us,
259c349dbc7Sjsg 		struct mod_vrr_params *in_out_vrr)
260fb4d8502Sjsg {
261c349dbc7Sjsg 	unsigned int inserted_frame_duration_in_us = 0;
262c349dbc7Sjsg 	unsigned int mid_point_frames_ceil = 0;
263c349dbc7Sjsg 	unsigned int mid_point_frames_floor = 0;
264c349dbc7Sjsg 	unsigned int frame_time_in_us = 0;
265c349dbc7Sjsg 	unsigned int delta_from_mid_point_in_us_1 = 0xFFFFFFFF;
266c349dbc7Sjsg 	unsigned int delta_from_mid_point_in_us_2 = 0xFFFFFFFF;
267c349dbc7Sjsg 	unsigned int frames_to_insert = 0;
268c349dbc7Sjsg 	unsigned int delta_from_mid_point_delta_in_us;
269c349dbc7Sjsg 	unsigned int max_render_time_in_us =
270c349dbc7Sjsg 			in_out_vrr->max_duration_in_us - in_out_vrr->btr.margin_in_us;
271fb4d8502Sjsg 
272c349dbc7Sjsg 	/* Program BTR */
273c349dbc7Sjsg 	if ((last_render_time_in_us + in_out_vrr->btr.margin_in_us / 2) < max_render_time_in_us) {
274c349dbc7Sjsg 		/* Exit Below the Range */
275c349dbc7Sjsg 		if (in_out_vrr->btr.btr_active) {
276c349dbc7Sjsg 			in_out_vrr->btr.frame_counter = 0;
277c349dbc7Sjsg 			in_out_vrr->btr.btr_active = false;
278fb4d8502Sjsg 		}
279c349dbc7Sjsg 	} else if (last_render_time_in_us > (max_render_time_in_us + in_out_vrr->btr.margin_in_us / 2)) {
280c349dbc7Sjsg 		/* Enter Below the Range */
281c349dbc7Sjsg 		if (!in_out_vrr->btr.btr_active) {
282c349dbc7Sjsg 			in_out_vrr->btr.btr_active = true;
283fb4d8502Sjsg 		}
284fb4d8502Sjsg 	}
285fb4d8502Sjsg 
286c349dbc7Sjsg 	/* BTR set to "not active" so disengage */
287c349dbc7Sjsg 	if (!in_out_vrr->btr.btr_active) {
288c349dbc7Sjsg 		in_out_vrr->btr.inserted_duration_in_us = 0;
289c349dbc7Sjsg 		in_out_vrr->btr.frames_to_insert = 0;
290c349dbc7Sjsg 		in_out_vrr->btr.frame_counter = 0;
291fb4d8502Sjsg 
292fb4d8502Sjsg 		/* Restore FreeSync */
293c349dbc7Sjsg 		in_out_vrr->adjust.v_total_min =
2945ca02815Sjsg 			mod_freesync_calc_v_total_from_refresh(stream,
295c349dbc7Sjsg 				in_out_vrr->max_refresh_in_uhz);
296c349dbc7Sjsg 		in_out_vrr->adjust.v_total_max =
2975ca02815Sjsg 			mod_freesync_calc_v_total_from_refresh(stream,
298c349dbc7Sjsg 				in_out_vrr->min_refresh_in_uhz);
299c349dbc7Sjsg 	/* BTR set to "active" so engage */
300fb4d8502Sjsg 	} else {
301c349dbc7Sjsg 
302c349dbc7Sjsg 		/* Calculate number of midPoint frames that could fit within
303c349dbc7Sjsg 		 * the render time interval - take ceil of this value
304c349dbc7Sjsg 		 */
305c349dbc7Sjsg 		mid_point_frames_ceil = (last_render_time_in_us +
306c349dbc7Sjsg 				in_out_vrr->btr.mid_point_in_us - 1) /
307c349dbc7Sjsg 					in_out_vrr->btr.mid_point_in_us;
308c349dbc7Sjsg 
309c349dbc7Sjsg 		if (mid_point_frames_ceil > 0) {
310c349dbc7Sjsg 			frame_time_in_us = last_render_time_in_us /
311c349dbc7Sjsg 				mid_point_frames_ceil;
312c349dbc7Sjsg 			delta_from_mid_point_in_us_1 =
313c349dbc7Sjsg 				(in_out_vrr->btr.mid_point_in_us >
314c349dbc7Sjsg 				frame_time_in_us) ?
315c349dbc7Sjsg 				(in_out_vrr->btr.mid_point_in_us - frame_time_in_us) :
316c349dbc7Sjsg 				(frame_time_in_us - in_out_vrr->btr.mid_point_in_us);
317fb4d8502Sjsg 		}
318fb4d8502Sjsg 
319c349dbc7Sjsg 		/* Calculate number of midPoint frames that could fit within
320c349dbc7Sjsg 		 * the render time interval - take floor of this value
321c349dbc7Sjsg 		 */
322c349dbc7Sjsg 		mid_point_frames_floor = last_render_time_in_us /
323c349dbc7Sjsg 				in_out_vrr->btr.mid_point_in_us;
324fb4d8502Sjsg 
325c349dbc7Sjsg 		if (mid_point_frames_floor > 0) {
326fb4d8502Sjsg 
327c349dbc7Sjsg 			frame_time_in_us = last_render_time_in_us /
328c349dbc7Sjsg 				mid_point_frames_floor;
329c349dbc7Sjsg 			delta_from_mid_point_in_us_2 =
330c349dbc7Sjsg 				(in_out_vrr->btr.mid_point_in_us >
331c349dbc7Sjsg 				frame_time_in_us) ?
332c349dbc7Sjsg 				(in_out_vrr->btr.mid_point_in_us - frame_time_in_us) :
333c349dbc7Sjsg 				(frame_time_in_us - in_out_vrr->btr.mid_point_in_us);
334fb4d8502Sjsg 		}
335fb4d8502Sjsg 
336c349dbc7Sjsg 		/* Choose number of frames to insert based on how close it
337c349dbc7Sjsg 		 * can get to the mid point of the variable range.
338ad8b1aafSjsg 		 *  - Delta for CEIL: delta_from_mid_point_in_us_1
339ad8b1aafSjsg 		 *  - Delta for FLOOR: delta_from_mid_point_in_us_2
340c349dbc7Sjsg 		 */
3414369b360Sjsg 		if (mid_point_frames_ceil &&
3424369b360Sjsg 		    (last_render_time_in_us / mid_point_frames_ceil) <
3434369b360Sjsg 		    in_out_vrr->min_duration_in_us) {
344ad8b1aafSjsg 			/* Check for out of range.
345ad8b1aafSjsg 			 * If using CEIL produces a value that is out of range,
346ad8b1aafSjsg 			 * then we are forced to use FLOOR.
347ad8b1aafSjsg 			 */
348c349dbc7Sjsg 			frames_to_insert = mid_point_frames_floor;
349ad8b1aafSjsg 		} else if (mid_point_frames_floor < 2) {
350ad8b1aafSjsg 			/* Check if FLOOR would result in non-LFC. In this case
351ad8b1aafSjsg 			 * choose to use CEIL
352ad8b1aafSjsg 			 */
353ad8b1aafSjsg 			frames_to_insert = mid_point_frames_ceil;
354ad8b1aafSjsg 		} else if (delta_from_mid_point_in_us_1 < delta_from_mid_point_in_us_2) {
355ad8b1aafSjsg 			/* If choosing CEIL results in a frame duration that is
356ad8b1aafSjsg 			 * closer to the mid point of the range.
357ad8b1aafSjsg 			 * Choose CEIL
358ad8b1aafSjsg 			 */
359ad8b1aafSjsg 			frames_to_insert = mid_point_frames_ceil;
360ad8b1aafSjsg 		} else {
361ad8b1aafSjsg 			/* If choosing FLOOR results in a frame duration that is
362ad8b1aafSjsg 			 * closer to the mid point of the range.
363ad8b1aafSjsg 			 * Choose FLOOR
364ad8b1aafSjsg 			 */
365ad8b1aafSjsg 			frames_to_insert = mid_point_frames_floor;
366fb4d8502Sjsg 		}
367fb4d8502Sjsg 
368c349dbc7Sjsg 		/* Prefer current frame multiplier when BTR is enabled unless it drifts
369c349dbc7Sjsg 		 * too far from the midpoint
370c349dbc7Sjsg 		 */
371ad8b1aafSjsg 		if (delta_from_mid_point_in_us_1 < delta_from_mid_point_in_us_2) {
372ad8b1aafSjsg 			delta_from_mid_point_delta_in_us = delta_from_mid_point_in_us_2 -
373ad8b1aafSjsg 					delta_from_mid_point_in_us_1;
374ad8b1aafSjsg 		} else {
375ad8b1aafSjsg 			delta_from_mid_point_delta_in_us = delta_from_mid_point_in_us_1 -
376ad8b1aafSjsg 					delta_from_mid_point_in_us_2;
377ad8b1aafSjsg 		}
378c349dbc7Sjsg 		if (in_out_vrr->btr.frames_to_insert != 0 &&
379c349dbc7Sjsg 				delta_from_mid_point_delta_in_us < BTR_DRIFT_MARGIN) {
380c349dbc7Sjsg 			if (((last_render_time_in_us / in_out_vrr->btr.frames_to_insert) <
381c349dbc7Sjsg 					max_render_time_in_us) &&
382c349dbc7Sjsg 				((last_render_time_in_us / in_out_vrr->btr.frames_to_insert) >
383c349dbc7Sjsg 					in_out_vrr->min_duration_in_us))
384c349dbc7Sjsg 				frames_to_insert = in_out_vrr->btr.frames_to_insert;
385fb4d8502Sjsg 		}
386fb4d8502Sjsg 
387c349dbc7Sjsg 		/* Either we've calculated the number of frames to insert,
388c349dbc7Sjsg 		 * or we need to insert min duration frames
389c349dbc7Sjsg 		 */
3904369b360Sjsg 		if (frames_to_insert &&
3914369b360Sjsg 		    (last_render_time_in_us / frames_to_insert) <
392c349dbc7Sjsg 		    in_out_vrr->min_duration_in_us){
393c349dbc7Sjsg 			frames_to_insert -= (frames_to_insert > 1) ?
394c349dbc7Sjsg 					1 : 0;
395fb4d8502Sjsg 		}
396fb4d8502Sjsg 
397c349dbc7Sjsg 		if (frames_to_insert > 0)
398c349dbc7Sjsg 			inserted_frame_duration_in_us = last_render_time_in_us /
399c349dbc7Sjsg 							frames_to_insert;
400c349dbc7Sjsg 
401c349dbc7Sjsg 		if (inserted_frame_duration_in_us < in_out_vrr->min_duration_in_us)
402c349dbc7Sjsg 			inserted_frame_duration_in_us = in_out_vrr->min_duration_in_us;
403c349dbc7Sjsg 
404c349dbc7Sjsg 		/* Cache the calculated variables */
405c349dbc7Sjsg 		in_out_vrr->btr.inserted_duration_in_us =
406c349dbc7Sjsg 			inserted_frame_duration_in_us;
407c349dbc7Sjsg 		in_out_vrr->btr.frames_to_insert = frames_to_insert;
408c349dbc7Sjsg 		in_out_vrr->btr.frame_counter = frames_to_insert;
409c349dbc7Sjsg 	}
410c349dbc7Sjsg }
411c349dbc7Sjsg 
412c349dbc7Sjsg static void apply_fixed_refresh(struct core_freesync *core_freesync,
413c349dbc7Sjsg 		const struct dc_stream_state *stream,
414c349dbc7Sjsg 		unsigned int last_render_time_in_us,
415c349dbc7Sjsg 		struct mod_vrr_params *in_out_vrr)
416fb4d8502Sjsg {
417c349dbc7Sjsg 	bool update = false;
418c349dbc7Sjsg 	unsigned int max_render_time_in_us = in_out_vrr->max_duration_in_us;
419fb4d8502Sjsg 
420c349dbc7Sjsg 	/* Compute the exit refresh rate and exit frame duration */
421c349dbc7Sjsg 	unsigned int exit_refresh_rate_in_milli_hz = ((1000000000/max_render_time_in_us)
422c349dbc7Sjsg 			+ (1000*FIXED_REFRESH_EXIT_MARGIN_IN_HZ));
423c349dbc7Sjsg 	unsigned int exit_frame_duration_in_us = 1000000000/exit_refresh_rate_in_milli_hz;
424fb4d8502Sjsg 
425c349dbc7Sjsg 	if (last_render_time_in_us < exit_frame_duration_in_us) {
426c349dbc7Sjsg 		/* Exit Fixed Refresh mode */
427c349dbc7Sjsg 		if (in_out_vrr->fixed.fixed_active) {
428c349dbc7Sjsg 			in_out_vrr->fixed.frame_counter++;
429fb4d8502Sjsg 
430c349dbc7Sjsg 			if (in_out_vrr->fixed.frame_counter >
431c349dbc7Sjsg 					FIXED_REFRESH_EXIT_FRAME_COUNT) {
432c349dbc7Sjsg 				in_out_vrr->fixed.frame_counter = 0;
433c349dbc7Sjsg 				in_out_vrr->fixed.fixed_active = false;
434c349dbc7Sjsg 				in_out_vrr->fixed.target_refresh_in_uhz = 0;
435c349dbc7Sjsg 				update = true;
436c349dbc7Sjsg 			}
4375ca02815Sjsg 		} else
4385ca02815Sjsg 			in_out_vrr->fixed.frame_counter = 0;
439c349dbc7Sjsg 	} else if (last_render_time_in_us > max_render_time_in_us) {
440c349dbc7Sjsg 		/* Enter Fixed Refresh mode */
441c349dbc7Sjsg 		if (!in_out_vrr->fixed.fixed_active) {
442c349dbc7Sjsg 			in_out_vrr->fixed.frame_counter++;
443fb4d8502Sjsg 
444c349dbc7Sjsg 			if (in_out_vrr->fixed.frame_counter >
445c349dbc7Sjsg 					FIXED_REFRESH_ENTER_FRAME_COUNT) {
446c349dbc7Sjsg 				in_out_vrr->fixed.frame_counter = 0;
447c349dbc7Sjsg 				in_out_vrr->fixed.fixed_active = true;
448c349dbc7Sjsg 				in_out_vrr->fixed.target_refresh_in_uhz =
449c349dbc7Sjsg 						in_out_vrr->max_refresh_in_uhz;
450c349dbc7Sjsg 				update = true;
451c349dbc7Sjsg 			}
4525ca02815Sjsg 		} else
4535ca02815Sjsg 			in_out_vrr->fixed.frame_counter = 0;
454c349dbc7Sjsg 	}
455c349dbc7Sjsg 
456c349dbc7Sjsg 	if (update) {
457c349dbc7Sjsg 		if (in_out_vrr->fixed.fixed_active) {
458c349dbc7Sjsg 			in_out_vrr->adjust.v_total_min =
4595ca02815Sjsg 				mod_freesync_calc_v_total_from_refresh(
460c349dbc7Sjsg 				stream, in_out_vrr->max_refresh_in_uhz);
461c349dbc7Sjsg 			in_out_vrr->adjust.v_total_max =
462c349dbc7Sjsg 					in_out_vrr->adjust.v_total_min;
463c349dbc7Sjsg 		} else {
464c349dbc7Sjsg 			in_out_vrr->adjust.v_total_min =
4655ca02815Sjsg 				mod_freesync_calc_v_total_from_refresh(stream,
466c349dbc7Sjsg 					in_out_vrr->max_refresh_in_uhz);
467c349dbc7Sjsg 			in_out_vrr->adjust.v_total_max =
4685ca02815Sjsg 				mod_freesync_calc_v_total_from_refresh(stream,
469c349dbc7Sjsg 					in_out_vrr->min_refresh_in_uhz);
470c349dbc7Sjsg 		}
471c349dbc7Sjsg 	}
472c349dbc7Sjsg }
473c349dbc7Sjsg 
4741bb76ff1Sjsg static void determine_flip_interval_workaround_req(struct mod_vrr_params *in_vrr,
4751bb76ff1Sjsg 		unsigned int curr_time_stamp_in_us)
4761bb76ff1Sjsg {
4771bb76ff1Sjsg 	in_vrr->flip_interval.vsync_to_flip_in_us = curr_time_stamp_in_us -
4781bb76ff1Sjsg 			in_vrr->flip_interval.v_update_timestamp_in_us;
4791bb76ff1Sjsg 
4801bb76ff1Sjsg 	/* Determine conditions for stopping workaround */
4811bb76ff1Sjsg 	if (in_vrr->flip_interval.flip_interval_workaround_active &&
4821bb76ff1Sjsg 			in_vrr->flip_interval.vsyncs_between_flip < VSYNCS_BETWEEN_FLIP_THRESHOLD &&
4831bb76ff1Sjsg 			in_vrr->flip_interval.vsync_to_flip_in_us > FREESYNC_VSYNC_TO_FLIP_DELTA_IN_US) {
4841bb76ff1Sjsg 		in_vrr->flip_interval.flip_interval_detect_counter = 0;
4851bb76ff1Sjsg 		in_vrr->flip_interval.program_flip_interval_workaround = true;
4861bb76ff1Sjsg 		in_vrr->flip_interval.flip_interval_workaround_active = false;
4871bb76ff1Sjsg 	} else {
4881bb76ff1Sjsg 		/* Determine conditions for starting workaround */
4891bb76ff1Sjsg 		if (in_vrr->flip_interval.vsyncs_between_flip >= VSYNCS_BETWEEN_FLIP_THRESHOLD &&
4901bb76ff1Sjsg 				in_vrr->flip_interval.vsync_to_flip_in_us < FREESYNC_VSYNC_TO_FLIP_DELTA_IN_US) {
4911bb76ff1Sjsg 			/* Increase flip interval counter we have 2 vsyncs between flips and
4921bb76ff1Sjsg 			 * vsync to flip interval is less than 500us
4931bb76ff1Sjsg 			 */
4941bb76ff1Sjsg 			in_vrr->flip_interval.flip_interval_detect_counter++;
4951bb76ff1Sjsg 			if (in_vrr->flip_interval.flip_interval_detect_counter > FREESYNC_CONSEC_FLIP_AFTER_VSYNC) {
4961bb76ff1Sjsg 				/* Start workaround if we detect 5 consecutive instances of the above case */
4971bb76ff1Sjsg 				in_vrr->flip_interval.program_flip_interval_workaround = true;
4981bb76ff1Sjsg 				in_vrr->flip_interval.flip_interval_workaround_active = true;
4991bb76ff1Sjsg 			}
5001bb76ff1Sjsg 		} else {
5011bb76ff1Sjsg 			/* Reset the flip interval counter if we condition is no longer met */
5021bb76ff1Sjsg 			in_vrr->flip_interval.flip_interval_detect_counter = 0;
5031bb76ff1Sjsg 		}
5041bb76ff1Sjsg 	}
5051bb76ff1Sjsg 
5061bb76ff1Sjsg 	in_vrr->flip_interval.vsyncs_between_flip = 0;
5071bb76ff1Sjsg }
5081bb76ff1Sjsg 
509c349dbc7Sjsg static bool vrr_settings_require_update(struct core_freesync *core_freesync,
510c349dbc7Sjsg 		struct mod_freesync_config *in_config,
511c349dbc7Sjsg 		unsigned int min_refresh_in_uhz,
512c349dbc7Sjsg 		unsigned int max_refresh_in_uhz,
513c349dbc7Sjsg 		struct mod_vrr_params *in_vrr)
514c349dbc7Sjsg {
515c349dbc7Sjsg 	if (in_vrr->state != in_config->state) {
516fb4d8502Sjsg 		return true;
517c349dbc7Sjsg 	} else if (in_vrr->state == VRR_STATE_ACTIVE_FIXED &&
518c349dbc7Sjsg 			in_vrr->fixed.target_refresh_in_uhz !=
519ad8b1aafSjsg 					in_config->fixed_refresh_in_uhz) {
520c349dbc7Sjsg 		return true;
521c349dbc7Sjsg 	} else if (in_vrr->min_refresh_in_uhz != min_refresh_in_uhz) {
522c349dbc7Sjsg 		return true;
523c349dbc7Sjsg 	} else if (in_vrr->max_refresh_in_uhz != max_refresh_in_uhz) {
524c349dbc7Sjsg 		return true;
525c349dbc7Sjsg 	}
526c349dbc7Sjsg 
527c349dbc7Sjsg 	return false;
528fb4d8502Sjsg }
529fb4d8502Sjsg 
530fb4d8502Sjsg bool mod_freesync_get_vmin_vmax(struct mod_freesync *mod_freesync,
531c349dbc7Sjsg 		const struct dc_stream_state *stream,
532fb4d8502Sjsg 		unsigned int *vmin,
533fb4d8502Sjsg 		unsigned int *vmax)
534fb4d8502Sjsg {
535c349dbc7Sjsg 	*vmin = stream->adjust.v_total_min;
536c349dbc7Sjsg 	*vmax = stream->adjust.v_total_max;
537fb4d8502Sjsg 
538fb4d8502Sjsg 	return true;
539fb4d8502Sjsg }
540fb4d8502Sjsg 
541fb4d8502Sjsg bool mod_freesync_get_v_position(struct mod_freesync *mod_freesync,
542fb4d8502Sjsg 		struct dc_stream_state *stream,
543fb4d8502Sjsg 		unsigned int *nom_v_pos,
544fb4d8502Sjsg 		unsigned int *v_pos)
545fb4d8502Sjsg {
546fb4d8502Sjsg 	struct core_freesync *core_freesync = NULL;
547fb4d8502Sjsg 	struct crtc_position position;
548fb4d8502Sjsg 
549fb4d8502Sjsg 	if (mod_freesync == NULL)
550fb4d8502Sjsg 		return false;
551fb4d8502Sjsg 
552fb4d8502Sjsg 	core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync);
553fb4d8502Sjsg 
554fb4d8502Sjsg 	if (dc_stream_get_crtc_position(core_freesync->dc, &stream, 1,
555fb4d8502Sjsg 					&position.vertical_count,
556fb4d8502Sjsg 					&position.nominal_vcount)) {
557fb4d8502Sjsg 
558fb4d8502Sjsg 		*nom_v_pos = position.nominal_vcount;
559fb4d8502Sjsg 		*v_pos = position.vertical_count;
560fb4d8502Sjsg 
561fb4d8502Sjsg 		return true;
562fb4d8502Sjsg 	}
563fb4d8502Sjsg 
564fb4d8502Sjsg 	return false;
565fb4d8502Sjsg }
566fb4d8502Sjsg 
567ad8b1aafSjsg static void build_vrr_infopacket_data_v1(const struct mod_vrr_params *vrr,
5685ca02815Sjsg 		struct dc_info_packet *infopacket,
5695ca02815Sjsg 		bool freesync_on_desktop)
570fb4d8502Sjsg {
571c349dbc7Sjsg 	/* PB1 = 0x1A (24bit AMD IEEE OUI (0x00001A) - Byte 0) */
572c349dbc7Sjsg 	infopacket->sb[1] = 0x1A;
573c349dbc7Sjsg 
574c349dbc7Sjsg 	/* PB2 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 1) */
575c349dbc7Sjsg 	infopacket->sb[2] = 0x00;
576c349dbc7Sjsg 
577c349dbc7Sjsg 	/* PB3 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 2) */
578c349dbc7Sjsg 	infopacket->sb[3] = 0x00;
579c349dbc7Sjsg 
580c349dbc7Sjsg 	/* PB4 = Reserved */
581c349dbc7Sjsg 
582c349dbc7Sjsg 	/* PB5 = Reserved */
583c349dbc7Sjsg 
584c349dbc7Sjsg 	/* PB6 = [Bits 7:3 = Reserved] */
585c349dbc7Sjsg 
586c349dbc7Sjsg 	/* PB6 = [Bit 0 = FreeSync Supported] */
587c349dbc7Sjsg 	if (vrr->state != VRR_STATE_UNSUPPORTED)
588c349dbc7Sjsg 		infopacket->sb[6] |= 0x01;
589c349dbc7Sjsg 
590c349dbc7Sjsg 	/* PB6 = [Bit 1 = FreeSync Enabled] */
591c349dbc7Sjsg 	if (vrr->state != VRR_STATE_DISABLED &&
592c349dbc7Sjsg 			vrr->state != VRR_STATE_UNSUPPORTED)
593c349dbc7Sjsg 		infopacket->sb[6] |= 0x02;
594c349dbc7Sjsg 
5955ca02815Sjsg 	if (freesync_on_desktop) {
596c349dbc7Sjsg 		/* PB6 = [Bit 2 = FreeSync Active] */
5975ca02815Sjsg 		if (vrr->state != VRR_STATE_DISABLED &&
5985ca02815Sjsg 			vrr->state != VRR_STATE_UNSUPPORTED)
5995ca02815Sjsg 			infopacket->sb[6] |= 0x04;
6005ca02815Sjsg 	} else {
601c349dbc7Sjsg 		if (vrr->state == VRR_STATE_ACTIVE_VARIABLE ||
602c349dbc7Sjsg 			vrr->state == VRR_STATE_ACTIVE_FIXED)
603c349dbc7Sjsg 			infopacket->sb[6] |= 0x04;
6045ca02815Sjsg 	}
605c349dbc7Sjsg 
606ad8b1aafSjsg 	// For v1 & 2 infoframes program nominal if non-fs mode, otherwise full range
607c349dbc7Sjsg 	/* PB7 = FreeSync Minimum refresh rate (Hz) */
608ad8b1aafSjsg 	if (vrr->state == VRR_STATE_ACTIVE_VARIABLE ||
609ad8b1aafSjsg 			vrr->state == VRR_STATE_ACTIVE_FIXED) {
610c349dbc7Sjsg 		infopacket->sb[7] = (unsigned char)((vrr->min_refresh_in_uhz + 500000) / 1000000);
611ad8b1aafSjsg 	} else {
612ad8b1aafSjsg 		infopacket->sb[7] = (unsigned char)((vrr->max_refresh_in_uhz + 500000) / 1000000);
613ad8b1aafSjsg 	}
614c349dbc7Sjsg 
615c349dbc7Sjsg 	/* PB8 = FreeSync Maximum refresh rate (Hz)
616c349dbc7Sjsg 	 * Note: We should never go above the field rate of the mode timing set.
617c349dbc7Sjsg 	 */
618c349dbc7Sjsg 	infopacket->sb[8] = (unsigned char)((vrr->max_refresh_in_uhz + 500000) / 1000000);
619ad8b1aafSjsg }
620ad8b1aafSjsg 
621ad8b1aafSjsg static void build_vrr_infopacket_data_v3(const struct mod_vrr_params *vrr,
622f005ef32Sjsg 		struct dc_info_packet *infopacket,
623f005ef32Sjsg 		bool freesync_on_desktop)
624ad8b1aafSjsg {
6255ca02815Sjsg 	unsigned int min_refresh;
6265ca02815Sjsg 	unsigned int max_refresh;
6275ca02815Sjsg 	unsigned int fixed_refresh;
6285ca02815Sjsg 	unsigned int min_programmed;
6295ca02815Sjsg 	unsigned int max_programmed;
6305ca02815Sjsg 
631ad8b1aafSjsg 	/* PB1 = 0x1A (24bit AMD IEEE OUI (0x00001A) - Byte 0) */
632ad8b1aafSjsg 	infopacket->sb[1] = 0x1A;
633ad8b1aafSjsg 
634ad8b1aafSjsg 	/* PB2 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 1) */
635ad8b1aafSjsg 	infopacket->sb[2] = 0x00;
636ad8b1aafSjsg 
637ad8b1aafSjsg 	/* PB3 = 0x00 (24bit AMD IEEE OUI (0x00001A) - Byte 2) */
638ad8b1aafSjsg 	infopacket->sb[3] = 0x00;
639ad8b1aafSjsg 
640ad8b1aafSjsg 	/* PB4 = Reserved */
641ad8b1aafSjsg 
642ad8b1aafSjsg 	/* PB5 = Reserved */
643ad8b1aafSjsg 
644ad8b1aafSjsg 	/* PB6 = [Bits 7:3 = Reserved] */
645ad8b1aafSjsg 
646ad8b1aafSjsg 	/* PB6 = [Bit 0 = FreeSync Supported] */
647ad8b1aafSjsg 	if (vrr->state != VRR_STATE_UNSUPPORTED)
648ad8b1aafSjsg 		infopacket->sb[6] |= 0x01;
649ad8b1aafSjsg 
650ad8b1aafSjsg 	/* PB6 = [Bit 1 = FreeSync Enabled] */
651ad8b1aafSjsg 	if (vrr->state != VRR_STATE_DISABLED &&
652ad8b1aafSjsg 			vrr->state != VRR_STATE_UNSUPPORTED)
653ad8b1aafSjsg 		infopacket->sb[6] |= 0x02;
654ad8b1aafSjsg 
655ad8b1aafSjsg 	/* PB6 = [Bit 2 = FreeSync Active] */
656f005ef32Sjsg 	if (freesync_on_desktop) {
657f005ef32Sjsg 		if (vrr->state != VRR_STATE_DISABLED &&
658f005ef32Sjsg 			vrr->state != VRR_STATE_UNSUPPORTED)
659f005ef32Sjsg 			infopacket->sb[6] |= 0x04;
660f005ef32Sjsg 	} else {
661ad8b1aafSjsg 		if (vrr->state == VRR_STATE_ACTIVE_VARIABLE ||
662ad8b1aafSjsg 			vrr->state == VRR_STATE_ACTIVE_FIXED)
663ad8b1aafSjsg 			infopacket->sb[6] |= 0x04;
664f005ef32Sjsg 	}
665ad8b1aafSjsg 
6665ca02815Sjsg 	min_refresh = (vrr->min_refresh_in_uhz + 500000) / 1000000;
6675ca02815Sjsg 	max_refresh = (vrr->max_refresh_in_uhz + 500000) / 1000000;
6685ca02815Sjsg 	fixed_refresh = (vrr->fixed_refresh_in_uhz + 500000) / 1000000;
6695ca02815Sjsg 
6705ca02815Sjsg 	min_programmed = (vrr->state == VRR_STATE_ACTIVE_FIXED) ? fixed_refresh :
6715ca02815Sjsg 			(vrr->state == VRR_STATE_ACTIVE_VARIABLE) ? min_refresh :
6725ca02815Sjsg 			(vrr->state == VRR_STATE_INACTIVE) ? min_refresh :
6735ca02815Sjsg 			max_refresh; // Non-fs case, program nominal range
6745ca02815Sjsg 
6755ca02815Sjsg 	max_programmed = (vrr->state == VRR_STATE_ACTIVE_FIXED) ? fixed_refresh :
6765ca02815Sjsg 			(vrr->state == VRR_STATE_ACTIVE_VARIABLE) ? max_refresh :
6775ca02815Sjsg 			max_refresh;// Non-fs case, program nominal range
6785ca02815Sjsg 
679ad8b1aafSjsg 	/* PB7 = FreeSync Minimum refresh rate (Hz) */
6805ca02815Sjsg 	infopacket->sb[7] = min_programmed & 0xFF;
6815ca02815Sjsg 
682ad8b1aafSjsg 	/* PB8 = FreeSync Maximum refresh rate (Hz) */
6835ca02815Sjsg 	infopacket->sb[8] = max_programmed & 0xFF;
6845ca02815Sjsg 
6855ca02815Sjsg 	/* PB11 : MSB FreeSync Minimum refresh rate [Hz] - bits 9:8 */
6865ca02815Sjsg 	infopacket->sb[11] = (min_programmed >> 8) & 0x03;
6875ca02815Sjsg 
6885ca02815Sjsg 	/* PB12 : MSB FreeSync Maximum refresh rate [Hz] - bits 9:8 */
6895ca02815Sjsg 	infopacket->sb[12] = (max_programmed >> 8) & 0x03;
6905ca02815Sjsg 
6915ca02815Sjsg 	/* PB16 : Reserved bits 7:1, FixedRate bit 0 */
6925ca02815Sjsg 	infopacket->sb[16] = (vrr->state == VRR_STATE_ACTIVE_FIXED) ? 1 : 0;
693c349dbc7Sjsg }
694c349dbc7Sjsg 
695c349dbc7Sjsg static void build_vrr_infopacket_fs2_data(enum color_transfer_func app_tf,
696c349dbc7Sjsg 		struct dc_info_packet *infopacket)
697c349dbc7Sjsg {
698c349dbc7Sjsg 	if (app_tf != TRANSFER_FUNC_UNKNOWN) {
699c349dbc7Sjsg 		infopacket->valid = true;
700c349dbc7Sjsg 
701f005ef32Sjsg 		if (app_tf != TRANSFER_FUNC_PQ2084) {
702c349dbc7Sjsg 			infopacket->sb[6] |= 0x08;  // PB6 = [Bit 3 = Native Color Active]
703f005ef32Sjsg 			if (app_tf == TRANSFER_FUNC_GAMMA_22)
704c349dbc7Sjsg 				infopacket->sb[9] |= 0x04;  // PB6 = [Bit 2 = Gamma 2.2 EOTF Active]
705c349dbc7Sjsg 		}
706c349dbc7Sjsg 	}
707c349dbc7Sjsg }
708c349dbc7Sjsg 
709c349dbc7Sjsg static void build_vrr_infopacket_header_v1(enum amd_signal_type signal,
710c349dbc7Sjsg 		struct dc_info_packet *infopacket,
711c349dbc7Sjsg 		unsigned int *payload_size)
712c349dbc7Sjsg {
713c349dbc7Sjsg 	if (dc_is_hdmi_signal(signal)) {
714c349dbc7Sjsg 
715c349dbc7Sjsg 		/* HEADER */
716c349dbc7Sjsg 
717c349dbc7Sjsg 		/* HB0  = Packet Type = 0x83 (Source Product
718c349dbc7Sjsg 		 *	  Descriptor InfoFrame)
719c349dbc7Sjsg 		 */
720c349dbc7Sjsg 		infopacket->hb0 = DC_HDMI_INFOFRAME_TYPE_SPD;
721c349dbc7Sjsg 
722c349dbc7Sjsg 		/* HB1  = Version = 0x01 */
723c349dbc7Sjsg 		infopacket->hb1 = 0x01;
724c349dbc7Sjsg 
725c349dbc7Sjsg 		/* HB2  = [Bits 7:5 = 0] [Bits 4:0 = Length = 0x08] */
726c349dbc7Sjsg 		infopacket->hb2 = 0x08;
727c349dbc7Sjsg 
728c349dbc7Sjsg 		*payload_size = 0x08;
729c349dbc7Sjsg 
730c349dbc7Sjsg 	} else if (dc_is_dp_signal(signal)) {
731c349dbc7Sjsg 
732c349dbc7Sjsg 		/* HEADER */
733c349dbc7Sjsg 
734c349dbc7Sjsg 		/* HB0  = Secondary-data Packet ID = 0 - Only non-zero
735c349dbc7Sjsg 		 *	  when used to associate audio related info packets
736c349dbc7Sjsg 		 */
737c349dbc7Sjsg 		infopacket->hb0 = 0x00;
738c349dbc7Sjsg 
739c349dbc7Sjsg 		/* HB1  = Packet Type = 0x83 (Source Product
740c349dbc7Sjsg 		 *	  Descriptor InfoFrame)
741c349dbc7Sjsg 		 */
742c349dbc7Sjsg 		infopacket->hb1 = DC_HDMI_INFOFRAME_TYPE_SPD;
743c349dbc7Sjsg 
744c349dbc7Sjsg 		/* HB2  = [Bits 7:0 = Least significant eight bits -
745c349dbc7Sjsg 		 *	  For INFOFRAME, the value must be 1Bh]
746c349dbc7Sjsg 		 */
747c349dbc7Sjsg 		infopacket->hb2 = 0x1B;
748c349dbc7Sjsg 
749c349dbc7Sjsg 		/* HB3  = [Bits 7:2 = INFOFRAME SDP Version Number = 0x1]
750c349dbc7Sjsg 		 *	  [Bits 1:0 = Most significant two bits = 0x00]
751c349dbc7Sjsg 		 */
752c349dbc7Sjsg 		infopacket->hb3 = 0x04;
753c349dbc7Sjsg 
754c349dbc7Sjsg 		*payload_size = 0x1B;
755c349dbc7Sjsg 	}
756c349dbc7Sjsg }
757c349dbc7Sjsg 
758c349dbc7Sjsg static void build_vrr_infopacket_header_v2(enum amd_signal_type signal,
759c349dbc7Sjsg 		struct dc_info_packet *infopacket,
760c349dbc7Sjsg 		unsigned int *payload_size)
761c349dbc7Sjsg {
762c349dbc7Sjsg 	if (dc_is_hdmi_signal(signal)) {
763c349dbc7Sjsg 
764c349dbc7Sjsg 		/* HEADER */
765c349dbc7Sjsg 
766c349dbc7Sjsg 		/* HB0  = Packet Type = 0x83 (Source Product
767c349dbc7Sjsg 		 *	  Descriptor InfoFrame)
768c349dbc7Sjsg 		 */
769c349dbc7Sjsg 		infopacket->hb0 = DC_HDMI_INFOFRAME_TYPE_SPD;
770c349dbc7Sjsg 
771c349dbc7Sjsg 		/* HB1  = Version = 0x02 */
772c349dbc7Sjsg 		infopacket->hb1 = 0x02;
773c349dbc7Sjsg 
774c349dbc7Sjsg 		/* HB2  = [Bits 7:5 = 0] [Bits 4:0 = Length = 0x09] */
775c349dbc7Sjsg 		infopacket->hb2 = 0x09;
776c349dbc7Sjsg 
77760287b98Sjsg 		*payload_size = 0x09;
778c349dbc7Sjsg 	} else if (dc_is_dp_signal(signal)) {
779c349dbc7Sjsg 
780c349dbc7Sjsg 		/* HEADER */
781c349dbc7Sjsg 
782c349dbc7Sjsg 		/* HB0  = Secondary-data Packet ID = 0 - Only non-zero
783c349dbc7Sjsg 		 *	  when used to associate audio related info packets
784c349dbc7Sjsg 		 */
785c349dbc7Sjsg 		infopacket->hb0 = 0x00;
786c349dbc7Sjsg 
787c349dbc7Sjsg 		/* HB1  = Packet Type = 0x83 (Source Product
788c349dbc7Sjsg 		 *	  Descriptor InfoFrame)
789c349dbc7Sjsg 		 */
790c349dbc7Sjsg 		infopacket->hb1 = DC_HDMI_INFOFRAME_TYPE_SPD;
791c349dbc7Sjsg 
792c349dbc7Sjsg 		/* HB2  = [Bits 7:0 = Least significant eight bits -
793c349dbc7Sjsg 		 *	  For INFOFRAME, the value must be 1Bh]
794c349dbc7Sjsg 		 */
795c349dbc7Sjsg 		infopacket->hb2 = 0x1B;
796c349dbc7Sjsg 
797c349dbc7Sjsg 		/* HB3  = [Bits 7:2 = INFOFRAME SDP Version Number = 0x2]
798c349dbc7Sjsg 		 *	  [Bits 1:0 = Most significant two bits = 0x00]
799c349dbc7Sjsg 		 */
800c349dbc7Sjsg 		infopacket->hb3 = 0x08;
801c349dbc7Sjsg 
802c349dbc7Sjsg 		*payload_size = 0x1B;
803c349dbc7Sjsg 	}
804c349dbc7Sjsg }
805c349dbc7Sjsg 
8065ca02815Sjsg static void build_vrr_infopacket_header_v3(enum amd_signal_type signal,
8075ca02815Sjsg 		struct dc_info_packet *infopacket,
8085ca02815Sjsg 		unsigned int *payload_size)
8095ca02815Sjsg {
8105ca02815Sjsg 	unsigned char version;
8115ca02815Sjsg 
8125ca02815Sjsg 	version = 3;
8135ca02815Sjsg 	if (dc_is_hdmi_signal(signal)) {
8145ca02815Sjsg 
8155ca02815Sjsg 		/* HEADER */
8165ca02815Sjsg 
8175ca02815Sjsg 		/* HB0  = Packet Type = 0x83 (Source Product
8185ca02815Sjsg 		 *	  Descriptor InfoFrame)
8195ca02815Sjsg 		 */
8205ca02815Sjsg 		infopacket->hb0 = DC_HDMI_INFOFRAME_TYPE_SPD;
8215ca02815Sjsg 
8225ca02815Sjsg 		/* HB1  = Version = 0x03 */
8235ca02815Sjsg 		infopacket->hb1 = version;
8245ca02815Sjsg 
8255ca02815Sjsg 		/* HB2  = [Bits 7:5 = 0] [Bits 4:0 = Length] */
82660287b98Sjsg 		infopacket->hb2 = 0x10;
8275ca02815Sjsg 
82860287b98Sjsg 		*payload_size = 0x10;
8295ca02815Sjsg 	} else if (dc_is_dp_signal(signal)) {
8305ca02815Sjsg 
8315ca02815Sjsg 		/* HEADER */
8325ca02815Sjsg 
8335ca02815Sjsg 		/* HB0  = Secondary-data Packet ID = 0 - Only non-zero
8345ca02815Sjsg 		 *	  when used to associate audio related info packets
8355ca02815Sjsg 		 */
8365ca02815Sjsg 		infopacket->hb0 = 0x00;
8375ca02815Sjsg 
8385ca02815Sjsg 		/* HB1  = Packet Type = 0x83 (Source Product
8395ca02815Sjsg 		 *	  Descriptor InfoFrame)
8405ca02815Sjsg 		 */
8415ca02815Sjsg 		infopacket->hb1 = DC_HDMI_INFOFRAME_TYPE_SPD;
8425ca02815Sjsg 
8435ca02815Sjsg 		/* HB2  = [Bits 7:0 = Least significant eight bits -
8445ca02815Sjsg 		 *	  For INFOFRAME, the value must be 1Bh]
8455ca02815Sjsg 		 */
8465ca02815Sjsg 		infopacket->hb2 = 0x1B;
8475ca02815Sjsg 
8485ca02815Sjsg 		/* HB3  = [Bits 7:2 = INFOFRAME SDP Version Number = 0x2]
8495ca02815Sjsg 		 *	  [Bits 1:0 = Most significant two bits = 0x00]
8505ca02815Sjsg 		 */
8515ca02815Sjsg 
8525ca02815Sjsg 		infopacket->hb3 = (version & 0x3F) << 2;
8535ca02815Sjsg 
8545ca02815Sjsg 		*payload_size = 0x1B;
8555ca02815Sjsg 	}
8565ca02815Sjsg }
8575ca02815Sjsg 
858c349dbc7Sjsg static void build_vrr_infopacket_checksum(unsigned int *payload_size,
859c349dbc7Sjsg 		struct dc_info_packet *infopacket)
860c349dbc7Sjsg {
861c349dbc7Sjsg 	/* Calculate checksum */
862c349dbc7Sjsg 	unsigned int idx = 0;
863c349dbc7Sjsg 	unsigned char checksum = 0;
864c349dbc7Sjsg 
865c349dbc7Sjsg 	checksum += infopacket->hb0;
866c349dbc7Sjsg 	checksum += infopacket->hb1;
867c349dbc7Sjsg 	checksum += infopacket->hb2;
868c349dbc7Sjsg 	checksum += infopacket->hb3;
869c349dbc7Sjsg 
870c349dbc7Sjsg 	for (idx = 1; idx <= *payload_size; idx++)
871c349dbc7Sjsg 		checksum += infopacket->sb[idx];
872c349dbc7Sjsg 
873c349dbc7Sjsg 	/* PB0 = Checksum (one byte complement) */
874c349dbc7Sjsg 	infopacket->sb[0] = (unsigned char)(0x100 - checksum);
875c349dbc7Sjsg 
876c349dbc7Sjsg 	infopacket->valid = true;
877c349dbc7Sjsg }
878c349dbc7Sjsg 
879c349dbc7Sjsg static void build_vrr_infopacket_v1(enum amd_signal_type signal,
880c349dbc7Sjsg 		const struct mod_vrr_params *vrr,
8815ca02815Sjsg 		struct dc_info_packet *infopacket,
8825ca02815Sjsg 		bool freesync_on_desktop)
883c349dbc7Sjsg {
884c349dbc7Sjsg 	/* SPD info packet for FreeSync */
885c349dbc7Sjsg 	unsigned int payload_size = 0;
886c349dbc7Sjsg 
887c349dbc7Sjsg 	build_vrr_infopacket_header_v1(signal, infopacket, &payload_size);
8885ca02815Sjsg 	build_vrr_infopacket_data_v1(vrr, infopacket, freesync_on_desktop);
889c349dbc7Sjsg 	build_vrr_infopacket_checksum(&payload_size, infopacket);
890c349dbc7Sjsg 
891c349dbc7Sjsg 	infopacket->valid = true;
892c349dbc7Sjsg }
893c349dbc7Sjsg 
894c349dbc7Sjsg static void build_vrr_infopacket_v2(enum amd_signal_type signal,
895c349dbc7Sjsg 		const struct mod_vrr_params *vrr,
896c349dbc7Sjsg 		enum color_transfer_func app_tf,
8975ca02815Sjsg 		struct dc_info_packet *infopacket,
8985ca02815Sjsg 		bool freesync_on_desktop)
899c349dbc7Sjsg {
900c349dbc7Sjsg 	unsigned int payload_size = 0;
901c349dbc7Sjsg 
902c349dbc7Sjsg 	build_vrr_infopacket_header_v2(signal, infopacket, &payload_size);
9035ca02815Sjsg 	build_vrr_infopacket_data_v1(vrr, infopacket, freesync_on_desktop);
904c349dbc7Sjsg 
905c349dbc7Sjsg 	build_vrr_infopacket_fs2_data(app_tf, infopacket);
906c349dbc7Sjsg 
907c349dbc7Sjsg 	build_vrr_infopacket_checksum(&payload_size, infopacket);
908c349dbc7Sjsg 
909c349dbc7Sjsg 	infopacket->valid = true;
910c349dbc7Sjsg }
911ad8b1aafSjsg 
912ad8b1aafSjsg static void build_vrr_infopacket_v3(enum amd_signal_type signal,
913ad8b1aafSjsg 		const struct mod_vrr_params *vrr,
914ad8b1aafSjsg 		enum color_transfer_func app_tf,
915f005ef32Sjsg 		struct dc_info_packet *infopacket,
916f005ef32Sjsg 		bool freesync_on_desktop)
917ad8b1aafSjsg {
918ad8b1aafSjsg 	unsigned int payload_size = 0;
919ad8b1aafSjsg 
9205ca02815Sjsg 	build_vrr_infopacket_header_v3(signal, infopacket, &payload_size);
921f005ef32Sjsg 	build_vrr_infopacket_data_v3(vrr, infopacket, freesync_on_desktop);
922ad8b1aafSjsg 
923ad8b1aafSjsg 	build_vrr_infopacket_fs2_data(app_tf, infopacket);
924ad8b1aafSjsg 
925ad8b1aafSjsg 	build_vrr_infopacket_checksum(&payload_size, infopacket);
926ad8b1aafSjsg 
927ad8b1aafSjsg 	infopacket->valid = true;
928ad8b1aafSjsg }
929c349dbc7Sjsg 
9305ca02815Sjsg static void build_vrr_infopacket_sdp_v1_3(enum vrr_packet_type packet_type,
9315ca02815Sjsg 										struct dc_info_packet *infopacket)
9325ca02815Sjsg {
9335ca02815Sjsg 	uint8_t idx = 0, size = 0;
9345ca02815Sjsg 
9355ca02815Sjsg 	size = ((packet_type == PACKET_TYPE_FS_V1) ? 0x08 :
9365ca02815Sjsg 			(packet_type == PACKET_TYPE_FS_V3) ? 0x10 :
9375ca02815Sjsg 												0x09);
9385ca02815Sjsg 
9395ca02815Sjsg 	for (idx = infopacket->hb2; idx > 1; idx--) // Data Byte Count: 0x1B
9405ca02815Sjsg 		infopacket->sb[idx] = infopacket->sb[idx-1];
9415ca02815Sjsg 
9425ca02815Sjsg 	infopacket->sb[1] = size;                         // Length
9435ca02815Sjsg 	infopacket->sb[0] = (infopacket->hb3 >> 2) & 0x3F;//Version
9445ca02815Sjsg 	infopacket->hb3   = (0x13 << 2);                  // Header,SDP 1.3
9455ca02815Sjsg 	infopacket->hb2   = 0x1D;
9465ca02815Sjsg }
9475ca02815Sjsg 
948c349dbc7Sjsg void mod_freesync_build_vrr_infopacket(struct mod_freesync *mod_freesync,
949c349dbc7Sjsg 		const struct dc_stream_state *stream,
950c349dbc7Sjsg 		const struct mod_vrr_params *vrr,
951c349dbc7Sjsg 		enum vrr_packet_type packet_type,
952c349dbc7Sjsg 		enum color_transfer_func app_tf,
9535ca02815Sjsg 		struct dc_info_packet *infopacket,
9545ca02815Sjsg 		bool pack_sdp_v1_3)
955c349dbc7Sjsg {
956c349dbc7Sjsg 	/* SPD info packet for FreeSync
957c349dbc7Sjsg 	 * VTEM info packet for HdmiVRR
958c349dbc7Sjsg 	 * Check if Freesync is supported. Return if false. If true,
959c349dbc7Sjsg 	 * set the corresponding bit in the info packet
960c349dbc7Sjsg 	 */
961ad8b1aafSjsg 	if (!vrr->send_info_frame)
962c349dbc7Sjsg 		return;
963c349dbc7Sjsg 
964c349dbc7Sjsg 	switch (packet_type) {
965ad8b1aafSjsg 	case PACKET_TYPE_FS_V3:
966f005ef32Sjsg 		build_vrr_infopacket_v3(stream->signal, vrr, app_tf, infopacket, stream->freesync_on_desktop);
967ad8b1aafSjsg 		break;
968ad8b1aafSjsg 	case PACKET_TYPE_FS_V2:
9695ca02815Sjsg 		build_vrr_infopacket_v2(stream->signal, vrr, app_tf, infopacket, stream->freesync_on_desktop);
970c349dbc7Sjsg 		break;
971c349dbc7Sjsg 	case PACKET_TYPE_VRR:
972ad8b1aafSjsg 	case PACKET_TYPE_FS_V1:
973c349dbc7Sjsg 	default:
9745ca02815Sjsg 		build_vrr_infopacket_v1(stream->signal, vrr, infopacket, stream->freesync_on_desktop);
975c349dbc7Sjsg 	}
9765ca02815Sjsg 
9775ca02815Sjsg 	if (true == pack_sdp_v1_3 &&
9785ca02815Sjsg 		true == dc_is_dp_signal(stream->signal) &&
9795ca02815Sjsg 		packet_type != PACKET_TYPE_VRR &&
9805ca02815Sjsg 		packet_type != PACKET_TYPE_VTEM)
9815ca02815Sjsg 		build_vrr_infopacket_sdp_v1_3(packet_type, infopacket);
982c349dbc7Sjsg }
983c349dbc7Sjsg 
984c349dbc7Sjsg void mod_freesync_build_vrr_params(struct mod_freesync *mod_freesync,
985c349dbc7Sjsg 		const struct dc_stream_state *stream,
986c349dbc7Sjsg 		struct mod_freesync_config *in_config,
987c349dbc7Sjsg 		struct mod_vrr_params *in_out_vrr)
988c349dbc7Sjsg {
989fb4d8502Sjsg 	struct core_freesync *core_freesync = NULL;
990c349dbc7Sjsg 	unsigned long long nominal_field_rate_in_uhz = 0;
991c349dbc7Sjsg 	unsigned long long rounded_nominal_in_uhz = 0;
992c349dbc7Sjsg 	unsigned int refresh_range = 0;
993c349dbc7Sjsg 	unsigned long long min_refresh_in_uhz = 0;
994c349dbc7Sjsg 	unsigned long long max_refresh_in_uhz = 0;
995f005ef32Sjsg 	unsigned long long min_hardware_refresh_in_uhz = 0;
996fb4d8502Sjsg 
997fb4d8502Sjsg 	if (mod_freesync == NULL)
998fb4d8502Sjsg 		return;
999fb4d8502Sjsg 
1000fb4d8502Sjsg 	core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync);
1001fb4d8502Sjsg 
1002c349dbc7Sjsg 	/* Calculate nominal field rate for stream */
1003c349dbc7Sjsg 	nominal_field_rate_in_uhz =
1004c349dbc7Sjsg 			mod_freesync_calc_nominal_field_rate(stream);
1005fb4d8502Sjsg 
1006f005ef32Sjsg 	if (stream->ctx->dc->caps.max_v_total != 0 && stream->timing.h_total != 0) {
1007f005ef32Sjsg 		min_hardware_refresh_in_uhz = div64_u64((stream->timing.pix_clk_100hz * 100000000ULL),
1008f005ef32Sjsg 			(stream->timing.h_total * stream->ctx->dc->caps.max_v_total));
1009f005ef32Sjsg 	}
1010f005ef32Sjsg 	/* Limit minimum refresh rate to what can be supported by hardware */
1011f005ef32Sjsg 	min_refresh_in_uhz = min_hardware_refresh_in_uhz > in_config->min_refresh_in_uhz ?
1012f005ef32Sjsg 		min_hardware_refresh_in_uhz : in_config->min_refresh_in_uhz;
1013c349dbc7Sjsg 	max_refresh_in_uhz = in_config->max_refresh_in_uhz;
1014fb4d8502Sjsg 
10155ca02815Sjsg 	/* Full range may be larger than current video timing, so cap at nominal */
1016c349dbc7Sjsg 	if (max_refresh_in_uhz > nominal_field_rate_in_uhz)
1017c349dbc7Sjsg 		max_refresh_in_uhz = nominal_field_rate_in_uhz;
1018fb4d8502Sjsg 
10195ca02815Sjsg 	/* Full range may be larger than current video timing, so cap at nominal */
1020c349dbc7Sjsg 	if (min_refresh_in_uhz > max_refresh_in_uhz)
1021c349dbc7Sjsg 		min_refresh_in_uhz = max_refresh_in_uhz;
1022fb4d8502Sjsg 
10235ca02815Sjsg 	/* If a monitor reports exactly max refresh of 2x of min, enforce it on nominal */
1024c349dbc7Sjsg 	rounded_nominal_in_uhz =
1025c349dbc7Sjsg 			div_u64(nominal_field_rate_in_uhz + 50000, 100000) * 100000;
1026c349dbc7Sjsg 	if (in_config->max_refresh_in_uhz == (2 * in_config->min_refresh_in_uhz) &&
1027c349dbc7Sjsg 		in_config->max_refresh_in_uhz == rounded_nominal_in_uhz)
1028c349dbc7Sjsg 		min_refresh_in_uhz = div_u64(nominal_field_rate_in_uhz, 2);
1029fb4d8502Sjsg 
1030c349dbc7Sjsg 	if (!vrr_settings_require_update(core_freesync,
1031c349dbc7Sjsg 			in_config, (unsigned int)min_refresh_in_uhz, (unsigned int)max_refresh_in_uhz,
1032c349dbc7Sjsg 			in_out_vrr))
1033fb4d8502Sjsg 		return;
1034fb4d8502Sjsg 
1035c349dbc7Sjsg 	in_out_vrr->state = in_config->state;
1036c349dbc7Sjsg 	in_out_vrr->send_info_frame = in_config->vsif_supported;
1037fb4d8502Sjsg 
1038c349dbc7Sjsg 	if (in_config->state == VRR_STATE_UNSUPPORTED) {
1039c349dbc7Sjsg 		in_out_vrr->state = VRR_STATE_UNSUPPORTED;
1040c349dbc7Sjsg 		in_out_vrr->supported = false;
1041c349dbc7Sjsg 		in_out_vrr->adjust.v_total_min = stream->timing.v_total;
1042c349dbc7Sjsg 		in_out_vrr->adjust.v_total_max = stream->timing.v_total;
1043fb4d8502Sjsg 
1044fb4d8502Sjsg 		return;
1045fb4d8502Sjsg 
1046fb4d8502Sjsg 	} else {
1047c349dbc7Sjsg 		in_out_vrr->min_refresh_in_uhz = (unsigned int)min_refresh_in_uhz;
1048c349dbc7Sjsg 		in_out_vrr->max_duration_in_us =
1049c349dbc7Sjsg 				calc_duration_in_us_from_refresh_in_uhz(
1050c349dbc7Sjsg 						(unsigned int)min_refresh_in_uhz);
1051fb4d8502Sjsg 
1052c349dbc7Sjsg 		in_out_vrr->max_refresh_in_uhz = (unsigned int)max_refresh_in_uhz;
1053c349dbc7Sjsg 		in_out_vrr->min_duration_in_us =
1054c349dbc7Sjsg 				calc_duration_in_us_from_refresh_in_uhz(
1055c349dbc7Sjsg 						(unsigned int)max_refresh_in_uhz);
1056c349dbc7Sjsg 
1057ad8b1aafSjsg 		if (in_config->state == VRR_STATE_ACTIVE_FIXED)
1058ad8b1aafSjsg 			in_out_vrr->fixed_refresh_in_uhz = in_config->fixed_refresh_in_uhz;
1059ad8b1aafSjsg 		else
1060ad8b1aafSjsg 			in_out_vrr->fixed_refresh_in_uhz = 0;
1061ad8b1aafSjsg 
1062ad8b1aafSjsg 		refresh_range = div_u64(in_out_vrr->max_refresh_in_uhz + 500000, 1000000) -
1063ad8b1aafSjsg +				div_u64(in_out_vrr->min_refresh_in_uhz + 500000, 1000000);
1064c349dbc7Sjsg 
1065c349dbc7Sjsg 		in_out_vrr->supported = true;
1066c349dbc7Sjsg 	}
1067c349dbc7Sjsg 
1068c349dbc7Sjsg 	in_out_vrr->fixed.ramping_active = in_config->ramping;
1069c349dbc7Sjsg 
1070c349dbc7Sjsg 	in_out_vrr->btr.btr_enabled = in_config->btr;
1071c349dbc7Sjsg 
1072c349dbc7Sjsg 	if (in_out_vrr->max_refresh_in_uhz < (2 * in_out_vrr->min_refresh_in_uhz))
1073c349dbc7Sjsg 		in_out_vrr->btr.btr_enabled = false;
1074c349dbc7Sjsg 	else {
1075c349dbc7Sjsg 		in_out_vrr->btr.margin_in_us = in_out_vrr->max_duration_in_us -
1076c349dbc7Sjsg 				2 * in_out_vrr->min_duration_in_us;
1077c349dbc7Sjsg 		if (in_out_vrr->btr.margin_in_us > BTR_MAX_MARGIN)
1078c349dbc7Sjsg 			in_out_vrr->btr.margin_in_us = BTR_MAX_MARGIN;
1079c349dbc7Sjsg 	}
1080c349dbc7Sjsg 
1081c349dbc7Sjsg 	in_out_vrr->btr.btr_active = false;
1082c349dbc7Sjsg 	in_out_vrr->btr.inserted_duration_in_us = 0;
1083c349dbc7Sjsg 	in_out_vrr->btr.frames_to_insert = 0;
1084c349dbc7Sjsg 	in_out_vrr->btr.frame_counter = 0;
1085c349dbc7Sjsg 	in_out_vrr->fixed.fixed_active = false;
1086c349dbc7Sjsg 	in_out_vrr->fixed.target_refresh_in_uhz = 0;
1087c349dbc7Sjsg 
1088c349dbc7Sjsg 	in_out_vrr->btr.mid_point_in_us =
1089c349dbc7Sjsg 				(in_out_vrr->min_duration_in_us +
1090c349dbc7Sjsg 				 in_out_vrr->max_duration_in_us) / 2;
1091c349dbc7Sjsg 
1092c349dbc7Sjsg 	if (in_out_vrr->state == VRR_STATE_UNSUPPORTED) {
1093c349dbc7Sjsg 		in_out_vrr->adjust.v_total_min = stream->timing.v_total;
1094c349dbc7Sjsg 		in_out_vrr->adjust.v_total_max = stream->timing.v_total;
1095c349dbc7Sjsg 	} else if (in_out_vrr->state == VRR_STATE_DISABLED) {
1096c349dbc7Sjsg 		in_out_vrr->adjust.v_total_min = stream->timing.v_total;
1097c349dbc7Sjsg 		in_out_vrr->adjust.v_total_max = stream->timing.v_total;
1098c349dbc7Sjsg 	} else if (in_out_vrr->state == VRR_STATE_INACTIVE) {
1099c349dbc7Sjsg 		in_out_vrr->adjust.v_total_min = stream->timing.v_total;
1100c349dbc7Sjsg 		in_out_vrr->adjust.v_total_max = stream->timing.v_total;
1101c349dbc7Sjsg 	} else if (in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE &&
1102ad8b1aafSjsg 			refresh_range >= MIN_REFRESH_RANGE) {
1103c349dbc7Sjsg 
1104c349dbc7Sjsg 		in_out_vrr->adjust.v_total_min =
11055ca02815Sjsg 			mod_freesync_calc_v_total_from_refresh(stream,
1106c349dbc7Sjsg 				in_out_vrr->max_refresh_in_uhz);
1107c349dbc7Sjsg 		in_out_vrr->adjust.v_total_max =
11085ca02815Sjsg 			mod_freesync_calc_v_total_from_refresh(stream,
1109c349dbc7Sjsg 				in_out_vrr->min_refresh_in_uhz);
1110c349dbc7Sjsg 	} else if (in_out_vrr->state == VRR_STATE_ACTIVE_FIXED) {
1111c349dbc7Sjsg 		in_out_vrr->fixed.target_refresh_in_uhz =
1112ad8b1aafSjsg 				in_out_vrr->fixed_refresh_in_uhz;
1113c349dbc7Sjsg 		if (in_out_vrr->fixed.ramping_active &&
1114c349dbc7Sjsg 				in_out_vrr->fixed.fixed_active) {
1115c349dbc7Sjsg 			/* Do not update vtotals if ramping is already active
1116c349dbc7Sjsg 			 * in order to continue ramp from current refresh.
1117c349dbc7Sjsg 			 */
1118c349dbc7Sjsg 			in_out_vrr->fixed.fixed_active = true;
1119c349dbc7Sjsg 		} else {
1120c349dbc7Sjsg 			in_out_vrr->fixed.fixed_active = true;
1121c349dbc7Sjsg 			in_out_vrr->adjust.v_total_min =
11225ca02815Sjsg 				mod_freesync_calc_v_total_from_refresh(stream,
1123c349dbc7Sjsg 					in_out_vrr->fixed.target_refresh_in_uhz);
1124c349dbc7Sjsg 			in_out_vrr->adjust.v_total_max =
1125c349dbc7Sjsg 				in_out_vrr->adjust.v_total_min;
1126c349dbc7Sjsg 		}
1127c349dbc7Sjsg 	} else {
1128c349dbc7Sjsg 		in_out_vrr->state = VRR_STATE_INACTIVE;
1129c349dbc7Sjsg 		in_out_vrr->adjust.v_total_min = stream->timing.v_total;
1130c349dbc7Sjsg 		in_out_vrr->adjust.v_total_max = stream->timing.v_total;
1131fb4d8502Sjsg 	}
1132fb4d8502Sjsg }
1133fb4d8502Sjsg 
1134c349dbc7Sjsg void mod_freesync_handle_preflip(struct mod_freesync *mod_freesync,
1135c349dbc7Sjsg 		const struct dc_plane_state *plane,
1136c349dbc7Sjsg 		const struct dc_stream_state *stream,
1137c349dbc7Sjsg 		unsigned int curr_time_stamp_in_us,
1138c349dbc7Sjsg 		struct mod_vrr_params *in_out_vrr)
1139fb4d8502Sjsg {
1140fb4d8502Sjsg 	struct core_freesync *core_freesync = NULL;
1141c349dbc7Sjsg 	unsigned int last_render_time_in_us = 0;
1142fb4d8502Sjsg 
1143fb4d8502Sjsg 	if (mod_freesync == NULL)
1144fb4d8502Sjsg 		return;
1145fb4d8502Sjsg 
1146fb4d8502Sjsg 	core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync);
1147fb4d8502Sjsg 
1148c349dbc7Sjsg 	if (in_out_vrr->supported &&
1149c349dbc7Sjsg 			in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE) {
1150fb4d8502Sjsg 
1151fb4d8502Sjsg 		last_render_time_in_us = curr_time_stamp_in_us -
1152c349dbc7Sjsg 				plane->time.prev_update_time_in_us;
1153fb4d8502Sjsg 
1154c349dbc7Sjsg 		if (in_out_vrr->btr.btr_enabled) {
1155fb4d8502Sjsg 			apply_below_the_range(core_freesync,
1156c349dbc7Sjsg 					stream,
1157c349dbc7Sjsg 					last_render_time_in_us,
1158c349dbc7Sjsg 					in_out_vrr);
1159fb4d8502Sjsg 		} else {
1160fb4d8502Sjsg 			apply_fixed_refresh(core_freesync,
1161c349dbc7Sjsg 				stream,
1162c349dbc7Sjsg 				last_render_time_in_us,
1163c349dbc7Sjsg 				in_out_vrr);
1164c349dbc7Sjsg 		}
1165c349dbc7Sjsg 
11661bb76ff1Sjsg 		determine_flip_interval_workaround_req(in_out_vrr,
11671bb76ff1Sjsg 				curr_time_stamp_in_us);
11681bb76ff1Sjsg 
1169fb4d8502Sjsg 	}
1170fb4d8502Sjsg }
1171fb4d8502Sjsg 
1172c349dbc7Sjsg void mod_freesync_handle_v_update(struct mod_freesync *mod_freesync,
1173c349dbc7Sjsg 		const struct dc_stream_state *stream,
1174c349dbc7Sjsg 		struct mod_vrr_params *in_out_vrr)
1175c349dbc7Sjsg {
1176c349dbc7Sjsg 	struct core_freesync *core_freesync = NULL;
11771bb76ff1Sjsg 	unsigned int cur_timestamp_in_us;
11781bb76ff1Sjsg 	unsigned long long cur_tick;
1179c349dbc7Sjsg 
1180c349dbc7Sjsg 	if ((mod_freesync == NULL) || (stream == NULL) || (in_out_vrr == NULL))
1181c349dbc7Sjsg 		return;
1182c349dbc7Sjsg 
1183c349dbc7Sjsg 	core_freesync = MOD_FREESYNC_TO_CORE(mod_freesync);
1184c349dbc7Sjsg 
1185c349dbc7Sjsg 	if (in_out_vrr->supported == false)
1186c349dbc7Sjsg 		return;
1187c349dbc7Sjsg 
11881bb76ff1Sjsg 	cur_tick = dm_get_timestamp(core_freesync->dc->ctx);
11891bb76ff1Sjsg 	cur_timestamp_in_us = (unsigned int)
11901bb76ff1Sjsg 			div_u64(dm_get_elapse_time_in_ns(core_freesync->dc->ctx, cur_tick, 0), 1000);
11911bb76ff1Sjsg 
11921bb76ff1Sjsg 	in_out_vrr->flip_interval.vsyncs_between_flip++;
11931bb76ff1Sjsg 	in_out_vrr->flip_interval.v_update_timestamp_in_us = cur_timestamp_in_us;
11941bb76ff1Sjsg 
11951bb76ff1Sjsg 	if (in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE &&
11961bb76ff1Sjsg 			(in_out_vrr->flip_interval.flip_interval_workaround_active ||
11971bb76ff1Sjsg 			(!in_out_vrr->flip_interval.flip_interval_workaround_active &&
11981bb76ff1Sjsg 			in_out_vrr->flip_interval.program_flip_interval_workaround))) {
11991bb76ff1Sjsg 		// set freesync vmin vmax to nominal for workaround
12001bb76ff1Sjsg 		in_out_vrr->adjust.v_total_min =
12011bb76ff1Sjsg 			mod_freesync_calc_v_total_from_refresh(
12021bb76ff1Sjsg 			stream, in_out_vrr->max_refresh_in_uhz);
12031bb76ff1Sjsg 		in_out_vrr->adjust.v_total_max =
12041bb76ff1Sjsg 				in_out_vrr->adjust.v_total_min;
12051bb76ff1Sjsg 		in_out_vrr->flip_interval.program_flip_interval_workaround = false;
12061bb76ff1Sjsg 		in_out_vrr->flip_interval.do_flip_interval_workaround_cleanup = true;
12071bb76ff1Sjsg 		return;
12081bb76ff1Sjsg 	}
12091bb76ff1Sjsg 
12101bb76ff1Sjsg 	if (in_out_vrr->state != VRR_STATE_ACTIVE_VARIABLE &&
12111bb76ff1Sjsg 			in_out_vrr->flip_interval.do_flip_interval_workaround_cleanup) {
12121bb76ff1Sjsg 		in_out_vrr->flip_interval.do_flip_interval_workaround_cleanup = false;
12131bb76ff1Sjsg 		in_out_vrr->flip_interval.flip_interval_detect_counter = 0;
12141bb76ff1Sjsg 		in_out_vrr->flip_interval.vsyncs_between_flip = 0;
12151bb76ff1Sjsg 		in_out_vrr->flip_interval.vsync_to_flip_in_us = 0;
12161bb76ff1Sjsg 	}
12171bb76ff1Sjsg 
1218c349dbc7Sjsg 	/* Below the Range Logic */
1219c349dbc7Sjsg 
1220c349dbc7Sjsg 	/* Only execute if in fullscreen mode */
1221c349dbc7Sjsg 	if (in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE &&
1222c349dbc7Sjsg 					in_out_vrr->btr.btr_active) {
1223c349dbc7Sjsg 		/* TODO: pass in flag for Pre-DCE12 ASIC
1224c349dbc7Sjsg 		 * in order for frame variable duration to take affect,
1225c349dbc7Sjsg 		 * it needs to be done one VSYNC early, which is at
1226c349dbc7Sjsg 		 * frameCounter == 1.
1227c349dbc7Sjsg 		 * For DCE12 and newer updates to V_TOTAL_MIN/MAX
1228c349dbc7Sjsg 		 * will take affect on current frame
1229c349dbc7Sjsg 		 */
1230c349dbc7Sjsg 		if (in_out_vrr->btr.frames_to_insert ==
1231c349dbc7Sjsg 				in_out_vrr->btr.frame_counter) {
1232c349dbc7Sjsg 			in_out_vrr->adjust.v_total_min =
1233c349dbc7Sjsg 				calc_v_total_from_duration(stream,
1234c349dbc7Sjsg 				in_out_vrr,
1235c349dbc7Sjsg 				in_out_vrr->btr.inserted_duration_in_us);
1236c349dbc7Sjsg 			in_out_vrr->adjust.v_total_max =
1237c349dbc7Sjsg 				in_out_vrr->adjust.v_total_min;
1238fb4d8502Sjsg 		}
1239fb4d8502Sjsg 
1240c349dbc7Sjsg 		if (in_out_vrr->btr.frame_counter > 0)
1241c349dbc7Sjsg 			in_out_vrr->btr.frame_counter--;
1242c349dbc7Sjsg 
1243c349dbc7Sjsg 		/* Restore FreeSync */
1244c349dbc7Sjsg 		if (in_out_vrr->btr.frame_counter == 0) {
1245c349dbc7Sjsg 			in_out_vrr->adjust.v_total_min =
12465ca02815Sjsg 				mod_freesync_calc_v_total_from_refresh(stream,
1247c349dbc7Sjsg 				in_out_vrr->max_refresh_in_uhz);
1248c349dbc7Sjsg 			in_out_vrr->adjust.v_total_max =
12495ca02815Sjsg 				mod_freesync_calc_v_total_from_refresh(stream,
1250c349dbc7Sjsg 				in_out_vrr->min_refresh_in_uhz);
1251c349dbc7Sjsg 		}
1252c349dbc7Sjsg 	}
1253c349dbc7Sjsg 
1254c349dbc7Sjsg 	/* If in fullscreen freesync mode or in video, do not program
1255c349dbc7Sjsg 	 * static screen ramp values
1256c349dbc7Sjsg 	 */
1257c349dbc7Sjsg 	if (in_out_vrr->state == VRR_STATE_ACTIVE_VARIABLE)
1258c349dbc7Sjsg 		in_out_vrr->fixed.ramping_active = false;
1259c349dbc7Sjsg 
12605ca02815Sjsg 	/* Gradual Static Screen Ramping Logic
12615ca02815Sjsg 	 * Execute if ramp is active and user enabled freesync static screen
12625ca02815Sjsg 	 */
1263c349dbc7Sjsg 	if (in_out_vrr->state == VRR_STATE_ACTIVE_FIXED &&
1264c349dbc7Sjsg 				in_out_vrr->fixed.ramping_active) {
1265c349dbc7Sjsg 		update_v_total_for_static_ramp(
1266c349dbc7Sjsg 				core_freesync, stream, in_out_vrr);
1267fb4d8502Sjsg 	}
1268fb4d8502Sjsg }
1269fb4d8502Sjsg 
1270fb4d8502Sjsg void mod_freesync_get_settings(struct mod_freesync *mod_freesync,
1271c349dbc7Sjsg 		const struct mod_vrr_params *vrr,
1272fb4d8502Sjsg 		unsigned int *v_total_min, unsigned int *v_total_max,
1273fb4d8502Sjsg 		unsigned int *event_triggers,
1274fb4d8502Sjsg 		unsigned int *window_min, unsigned int *window_max,
1275fb4d8502Sjsg 		unsigned int *lfc_mid_point_in_us,
1276fb4d8502Sjsg 		unsigned int *inserted_frames,
1277fb4d8502Sjsg 		unsigned int *inserted_duration_in_us)
1278fb4d8502Sjsg {
1279fb4d8502Sjsg 	if (mod_freesync == NULL)
1280fb4d8502Sjsg 		return;
1281fb4d8502Sjsg 
1282c349dbc7Sjsg 	if (vrr->supported) {
1283c349dbc7Sjsg 		*v_total_min = vrr->adjust.v_total_min;
1284c349dbc7Sjsg 		*v_total_max = vrr->adjust.v_total_max;
1285fb4d8502Sjsg 		*event_triggers = 0;
1286c349dbc7Sjsg 		*lfc_mid_point_in_us = vrr->btr.mid_point_in_us;
1287c349dbc7Sjsg 		*inserted_frames = vrr->btr.frames_to_insert;
1288c349dbc7Sjsg 		*inserted_duration_in_us = vrr->btr.inserted_duration_in_us;
1289c349dbc7Sjsg 	}
1290fb4d8502Sjsg }
1291fb4d8502Sjsg 
1292c349dbc7Sjsg unsigned long long mod_freesync_calc_nominal_field_rate(
1293c349dbc7Sjsg 			const struct dc_stream_state *stream)
1294c349dbc7Sjsg {
1295c349dbc7Sjsg 	unsigned long long nominal_field_rate_in_uhz = 0;
1296c349dbc7Sjsg 	unsigned int total = stream->timing.h_total * stream->timing.v_total;
1297c349dbc7Sjsg 
1298c349dbc7Sjsg 	/* Calculate nominal field rate for stream, rounded up to nearest integer */
1299c349dbc7Sjsg 	nominal_field_rate_in_uhz = stream->timing.pix_clk_100hz;
1300c349dbc7Sjsg 	nominal_field_rate_in_uhz *= 100000000ULL;
1301c349dbc7Sjsg 
1302c349dbc7Sjsg 	nominal_field_rate_in_uhz =	div_u64(nominal_field_rate_in_uhz, total);
1303c349dbc7Sjsg 
1304c349dbc7Sjsg 	return nominal_field_rate_in_uhz;
1305fb4d8502Sjsg }
1306c349dbc7Sjsg 
13075ca02815Sjsg unsigned long long mod_freesync_calc_field_rate_from_timing(
13085ca02815Sjsg 		unsigned int vtotal, unsigned int htotal, unsigned int pix_clk)
13095ca02815Sjsg {
13105ca02815Sjsg 	unsigned long long field_rate_in_uhz = 0;
13115ca02815Sjsg 	unsigned int total = htotal * vtotal;
13125ca02815Sjsg 
13135ca02815Sjsg 	/* Calculate nominal field rate for stream, rounded up to nearest integer */
13145ca02815Sjsg 	field_rate_in_uhz = pix_clk;
13155ca02815Sjsg 	field_rate_in_uhz *= 1000000ULL;
13165ca02815Sjsg 
13175ca02815Sjsg 	field_rate_in_uhz =	div_u64(field_rate_in_uhz, total);
13185ca02815Sjsg 
13195ca02815Sjsg 	return field_rate_in_uhz;
13205ca02815Sjsg }
13215ca02815Sjsg 
13221bb76ff1Sjsg bool mod_freesync_get_freesync_enabled(struct mod_vrr_params *pVrr)
13231bb76ff1Sjsg {
13241bb76ff1Sjsg 	return (pVrr->state != VRR_STATE_UNSUPPORTED) && (pVrr->state != VRR_STATE_DISABLED);
13251bb76ff1Sjsg }
13261bb76ff1Sjsg 
1327ad8b1aafSjsg bool mod_freesync_is_valid_range(uint32_t min_refresh_cap_in_uhz,
1328c349dbc7Sjsg 		uint32_t max_refresh_cap_in_uhz,
1329ad8b1aafSjsg 		uint32_t nominal_field_rate_in_uhz)
1330c349dbc7Sjsg {
1331c349dbc7Sjsg 
1332c349dbc7Sjsg 	/* Typically nominal refresh calculated can have some fractional part.
1333c349dbc7Sjsg 	 * Allow for some rounding error of actual video timing by taking floor
1334c349dbc7Sjsg 	 * of caps and request. Round the nominal refresh rate.
1335c349dbc7Sjsg 	 *
1336c349dbc7Sjsg 	 * Dividing will convert everything to units in Hz although input
1337c349dbc7Sjsg 	 * variable name is in uHz!
1338c349dbc7Sjsg 	 *
1339c349dbc7Sjsg 	 * Also note, this takes care of rounding error on the nominal refresh
1340c349dbc7Sjsg 	 * so by rounding error we only expect it to be off by a small amount,
1341c349dbc7Sjsg 	 * such as < 0.1 Hz. i.e. 143.9xxx or 144.1xxx.
1342c349dbc7Sjsg 	 *
1343c349dbc7Sjsg 	 * Example 1. Caps    Min = 40 Hz, Max = 144 Hz
1344c349dbc7Sjsg 	 *            Request Min = 40 Hz, Max = 144 Hz
1345c349dbc7Sjsg 	 *                    Nominal = 143.5x Hz rounded to 144 Hz
1346c349dbc7Sjsg 	 *            This function should allow this as valid request
1347c349dbc7Sjsg 	 *
1348c349dbc7Sjsg 	 * Example 2. Caps    Min = 40 Hz, Max = 144 Hz
1349c349dbc7Sjsg 	 *            Request Min = 40 Hz, Max = 144 Hz
1350c349dbc7Sjsg 	 *                    Nominal = 144.4x Hz rounded to 144 Hz
1351c349dbc7Sjsg 	 *            This function should allow this as valid request
1352c349dbc7Sjsg 	 *
1353c349dbc7Sjsg 	 * Example 3. Caps    Min = 40 Hz, Max = 144 Hz
1354c349dbc7Sjsg 	 *            Request Min = 40 Hz, Max = 144 Hz
1355c349dbc7Sjsg 	 *                    Nominal = 120.xx Hz rounded to 120 Hz
1356c349dbc7Sjsg 	 *            This function should return NOT valid since the requested
1357c349dbc7Sjsg 	 *            max is greater than current timing's nominal
1358c349dbc7Sjsg 	 *
1359c349dbc7Sjsg 	 * Example 4. Caps    Min = 40 Hz, Max = 120 Hz
1360c349dbc7Sjsg 	 *            Request Min = 40 Hz, Max = 120 Hz
1361c349dbc7Sjsg 	 *                    Nominal = 144.xx Hz rounded to 144 Hz
1362c349dbc7Sjsg 	 *            This function should return NOT valid since the nominal
1363c349dbc7Sjsg 	 *            is greater than the capability's max refresh
1364c349dbc7Sjsg 	 */
1365c349dbc7Sjsg 	nominal_field_rate_in_uhz =
1366c349dbc7Sjsg 			div_u64(nominal_field_rate_in_uhz + 500000, 1000000);
1367c349dbc7Sjsg 	min_refresh_cap_in_uhz /= 1000000;
1368c349dbc7Sjsg 	max_refresh_cap_in_uhz /= 1000000;
1369c349dbc7Sjsg 
13705ca02815Sjsg 	/* Check nominal is within range */
1371c349dbc7Sjsg 	if (nominal_field_rate_in_uhz > max_refresh_cap_in_uhz ||
1372c349dbc7Sjsg 		nominal_field_rate_in_uhz < min_refresh_cap_in_uhz)
1373c349dbc7Sjsg 		return false;
1374c349dbc7Sjsg 
13755ca02815Sjsg 	/* If nominal is less than max, limit the max allowed refresh rate */
1376c349dbc7Sjsg 	if (nominal_field_rate_in_uhz < max_refresh_cap_in_uhz)
1377c349dbc7Sjsg 		max_refresh_cap_in_uhz = nominal_field_rate_in_uhz;
1378c349dbc7Sjsg 
13795ca02815Sjsg 	/* Check min is within range */
1380ad8b1aafSjsg 	if (min_refresh_cap_in_uhz > max_refresh_cap_in_uhz)
1381c349dbc7Sjsg 		return false;
1382c349dbc7Sjsg 
13835ca02815Sjsg 	/* For variable range, check for at least 10 Hz range */
1384ad8b1aafSjsg 	if (nominal_field_rate_in_uhz - min_refresh_cap_in_uhz < 10)
1385c349dbc7Sjsg 		return false;
1386c349dbc7Sjsg 
1387c349dbc7Sjsg 	return true;
1388fb4d8502Sjsg }
1389