1 | /* SPDX-License-Identifier: MIT */ |
2 | /* |
3 | * Copyright 2023 Advanced Micro Devices, Inc. |
4 | * |
5 | * Permission is hereby granted, free of charge, to any person obtaining a |
6 | * copy of this software and associated documentation files (the "Software"), |
7 | * to deal in the Software without restriction, including without limitation |
8 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
9 | * and/or sell copies of the Software, and to permit persons to whom the |
10 | * Software is furnished to do so, subject to the following conditions: |
11 | * |
12 | * The above copyright notice and this permission notice shall be included in |
13 | * all copies or substantial portions of the Software. |
14 | * |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
18 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
19 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
20 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
21 | * OTHER DEALINGS IN THE SOFTWARE. |
22 | * |
23 | * Authors: AMD |
24 | * |
25 | */ |
26 | |
27 | #include "dml2_dc_types.h" |
28 | #include "dml2_internal_types.h" |
29 | #include "dml2_utils.h" |
30 | #include "dml2_mall_phantom.h" |
31 | |
32 | unsigned int dml2_helper_calculate_num_ways_for_subvp(struct dml2_context *ctx, struct dc_state *context) |
33 | { |
34 | uint32_t num_ways = 0; |
35 | uint32_t bytes_per_pixel = 0; |
36 | uint32_t cache_lines_used = 0; |
37 | uint32_t lines_per_way = 0; |
38 | uint32_t total_cache_lines = 0; |
39 | uint32_t bytes_in_mall = 0; |
40 | uint32_t num_mblks = 0; |
41 | uint32_t cache_lines_per_plane = 0; |
42 | uint32_t i = 0; |
43 | uint32_t mblk_width = 0; |
44 | uint32_t mblk_height = 0; |
45 | uint32_t full_vp_width_blk_aligned = 0; |
46 | uint32_t mall_alloc_width_blk_aligned = 0; |
47 | uint32_t mall_alloc_height_blk_aligned = 0; |
48 | |
49 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
50 | struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i]; |
51 | |
52 | // Find the phantom pipes |
53 | if (pipe->stream && pipe->plane_state && !pipe->top_pipe && !pipe->prev_odm_pipe && |
54 | ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe) == SUBVP_PHANTOM) { |
55 | bytes_per_pixel = pipe->plane_state->format >= SURFACE_PIXEL_FORMAT_GRPH_ARGB16161616 ? 8 : 4; |
56 | mblk_width = ctx->config.mall_cfg.mblk_width_pixels; |
57 | mblk_height = bytes_per_pixel == 4 ? mblk_width = ctx->config.mall_cfg.mblk_height_4bpe_pixels : ctx->config.mall_cfg.mblk_height_8bpe_pixels; |
58 | |
59 | /* full_vp_width_blk_aligned = FLOOR(vp_x_start + full_vp_width + blk_width - 1, blk_width) - |
60 | * FLOOR(vp_x_start, blk_width) |
61 | */ |
62 | full_vp_width_blk_aligned = ((pipe->plane_res.scl_data.viewport.x + |
63 | pipe->plane_res.scl_data.viewport.width + mblk_width - 1) / mblk_width * mblk_width) + |
64 | (pipe->plane_res.scl_data.viewport.x / mblk_width * mblk_width); |
65 | |
66 | /* mall_alloc_width_blk_aligned_l/c = full_vp_width_blk_aligned_l/c */ |
67 | mall_alloc_width_blk_aligned = full_vp_width_blk_aligned; |
68 | |
69 | /* mall_alloc_height_blk_aligned_l/c = CEILING(sub_vp_height_l/c - 1, blk_height_l/c) + blk_height_l/c */ |
70 | mall_alloc_height_blk_aligned = (pipe->stream->timing.v_addressable - 1 + mblk_height - 1) / |
71 | mblk_height * mblk_height + mblk_height; |
72 | |
73 | /* full_mblk_width_ub_l/c = malldml2_mall_phantom.c_alloc_width_blk_aligned_l/c; |
74 | * full_mblk_height_ub_l/c = mall_alloc_height_blk_aligned_l/c; |
75 | * num_mblk_l/c = (full_mblk_width_ub_l/c / mblk_width_l/c) * (full_mblk_height_ub_l/c / mblk_height_l/c); |
76 | * (Should be divisible, but round up if not) |
77 | */ |
78 | num_mblks = ((mall_alloc_width_blk_aligned + mblk_width - 1) / mblk_width) * |
79 | ((mall_alloc_height_blk_aligned + mblk_height - 1) / mblk_height); |
80 | bytes_in_mall = num_mblks * ctx->config.mall_cfg.mblk_size_bytes; |
81 | // cache lines used is total bytes / cache_line size. Add +2 for worst case alignment |
82 | // (MALL is 64-byte aligned) |
83 | cache_lines_per_plane = bytes_in_mall / ctx->config.mall_cfg.cache_line_size_bytes + 2; |
84 | |
85 | // For DCC we must cache the meat surface, so double cache lines required |
86 | if (pipe->plane_state->dcc.enable) |
87 | cache_lines_per_plane *= 2; |
88 | cache_lines_used += cache_lines_per_plane; |
89 | } |
90 | } |
91 | |
92 | total_cache_lines = ctx->config.mall_cfg.max_cab_allocation_bytes / ctx->config.mall_cfg.cache_line_size_bytes; |
93 | lines_per_way = total_cache_lines / ctx->config.mall_cfg.cache_num_ways; |
94 | num_ways = cache_lines_used / lines_per_way; |
95 | if (cache_lines_used % lines_per_way > 0) |
96 | num_ways++; |
97 | |
98 | return num_ways; |
99 | } |
100 | |
101 | static void merge_pipes_for_subvp(struct dml2_context *ctx, struct dc_state *context) |
102 | { |
103 | int i; |
104 | |
105 | /* merge pipes if necessary */ |
106 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
107 | struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i]; |
108 | |
109 | // For now merge all pipes for SubVP since pipe split case isn't supported yet |
110 | |
111 | /* if ODM merge we ignore mpc tree, mpo pipes will have their own flags */ |
112 | if (pipe->prev_odm_pipe) { |
113 | /*split off odm pipe*/ |
114 | pipe->prev_odm_pipe->next_odm_pipe = pipe->next_odm_pipe; |
115 | if (pipe->next_odm_pipe) |
116 | pipe->next_odm_pipe->prev_odm_pipe = pipe->prev_odm_pipe; |
117 | |
118 | pipe->bottom_pipe = NULL; |
119 | pipe->next_odm_pipe = NULL; |
120 | pipe->plane_state = NULL; |
121 | pipe->stream = NULL; |
122 | pipe->top_pipe = NULL; |
123 | pipe->prev_odm_pipe = NULL; |
124 | if (pipe->stream_res.dsc) |
125 | ctx->config.svp_pstate.callbacks.release_dsc(&context->res_ctx, ctx->config.svp_pstate.callbacks.dc->res_pool, &pipe->stream_res.dsc); |
126 | memset(&pipe->plane_res, 0, sizeof(pipe->plane_res)); |
127 | memset(&pipe->stream_res, 0, sizeof(pipe->stream_res)); |
128 | } else if (pipe->top_pipe && pipe->top_pipe->plane_state == pipe->plane_state) { |
129 | struct pipe_ctx *top_pipe = pipe->top_pipe; |
130 | struct pipe_ctx *bottom_pipe = pipe->bottom_pipe; |
131 | |
132 | top_pipe->bottom_pipe = bottom_pipe; |
133 | if (bottom_pipe) |
134 | bottom_pipe->top_pipe = top_pipe; |
135 | |
136 | pipe->top_pipe = NULL; |
137 | pipe->bottom_pipe = NULL; |
138 | pipe->plane_state = NULL; |
139 | pipe->stream = NULL; |
140 | memset(&pipe->plane_res, 0, sizeof(pipe->plane_res)); |
141 | memset(&pipe->stream_res, 0, sizeof(pipe->stream_res)); |
142 | } |
143 | } |
144 | } |
145 | |
146 | static bool all_pipes_have_stream_and_plane(struct dml2_context *ctx, const struct dc_state *context) |
147 | { |
148 | int i; |
149 | |
150 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
151 | const struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i]; |
152 | |
153 | if (!pipe->stream) |
154 | continue; |
155 | |
156 | if (!pipe->plane_state) |
157 | return false; |
158 | } |
159 | return true; |
160 | } |
161 | |
162 | static bool mpo_in_use(const struct dc_state *context) |
163 | { |
164 | int i; |
165 | |
166 | for (i = 0; i < context->stream_count; i++) { |
167 | if (context->stream_status[i].plane_count > 1) |
168 | return true; |
169 | } |
170 | return false; |
171 | } |
172 | |
173 | /* |
174 | * dcn32_get_num_free_pipes: Calculate number of free pipes |
175 | * |
176 | * This function assumes that a "used" pipe is a pipe that has |
177 | * both a stream and a plane assigned to it. |
178 | * |
179 | * @dc: current dc state |
180 | * @context: new dc state |
181 | * |
182 | * Return: |
183 | * Number of free pipes available in the context |
184 | */ |
185 | static unsigned int get_num_free_pipes(struct dml2_context *ctx, struct dc_state *state) |
186 | { |
187 | unsigned int i; |
188 | unsigned int free_pipes = 0; |
189 | unsigned int num_pipes = 0; |
190 | |
191 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
192 | struct pipe_ctx *pipe = &state->res_ctx.pipe_ctx[i]; |
193 | |
194 | if (pipe->stream && !pipe->top_pipe) { |
195 | while (pipe) { |
196 | num_pipes++; |
197 | pipe = pipe->bottom_pipe; |
198 | } |
199 | } |
200 | } |
201 | |
202 | free_pipes = ctx->config.dcn_pipe_count - num_pipes; |
203 | return free_pipes; |
204 | } |
205 | |
206 | /* |
207 | * dcn32_assign_subvp_pipe: Function to decide which pipe will use Sub-VP. |
208 | * |
209 | * We enter this function if we are Sub-VP capable (i.e. enough pipes available) |
210 | * and regular P-State switching (i.e. VACTIVE/VBLANK) is not supported, or if |
211 | * we are forcing SubVP P-State switching on the current config. |
212 | * |
213 | * The number of pipes used for the chosen surface must be less than or equal to the |
214 | * number of free pipes available. |
215 | * |
216 | * In general we choose surfaces with the longest frame time first (better for SubVP + VBLANK). |
217 | * For multi-display cases the ActiveDRAMClockChangeMargin doesn't provide enough info on its own |
218 | * for determining which should be the SubVP pipe (need a way to determine if a pipe / plane doesn't |
219 | * support MCLK switching naturally [i.e. ACTIVE or VBLANK]). |
220 | * |
221 | * @param dc: current dc state |
222 | * @param context: new dc state |
223 | * @param index: [out] dc pipe index for the pipe chosen to have phantom pipes assigned |
224 | * |
225 | * Return: |
226 | * True if a valid pipe assignment was found for Sub-VP. Otherwise false. |
227 | */ |
228 | static bool assign_subvp_pipe(struct dml2_context *ctx, struct dc_state *context, unsigned int *index) |
229 | { |
230 | unsigned int i, pipe_idx; |
231 | unsigned int max_frame_time = 0; |
232 | bool valid_assignment_found = false; |
233 | unsigned int free_pipes = 2; //dcn32_get_num_free_pipes(dc, context); |
234 | bool current_assignment_freesync = false; |
235 | struct vba_vars_st *vba = &context->bw_ctx.dml.vba; |
236 | |
237 | for (i = 0, pipe_idx = 0; i < ctx->config.dcn_pipe_count; i++) { |
238 | struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i]; |
239 | unsigned int num_pipes = 0; |
240 | unsigned int refresh_rate = 0; |
241 | |
242 | if (!pipe->stream) |
243 | continue; |
244 | |
245 | // Round up |
246 | refresh_rate = (pipe->stream->timing.pix_clk_100hz * 100 + |
247 | pipe->stream->timing.v_total * pipe->stream->timing.h_total - 1) |
248 | / (double)(pipe->stream->timing.v_total * pipe->stream->timing.h_total); |
249 | /* SubVP pipe candidate requirements: |
250 | * - Refresh rate < 120hz |
251 | * - Not able to switch in vactive naturally (switching in active means the |
252 | * DET provides enough buffer to hide the P-State switch latency -- trying |
253 | * to combine this with SubVP can cause issues with the scheduling). |
254 | */ |
255 | if (pipe->plane_state && !pipe->top_pipe && |
256 | ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe) == SUBVP_NONE && refresh_rate < 120 && |
257 | vba->ActiveDRAMClockChangeLatencyMarginPerState[vba->VoltageLevel][vba->maxMpcComb][vba->pipe_plane[pipe_idx]] <= 0) { |
258 | while (pipe) { |
259 | num_pipes++; |
260 | pipe = pipe->bottom_pipe; |
261 | } |
262 | |
263 | pipe = &context->res_ctx.pipe_ctx[i]; |
264 | if (num_pipes <= free_pipes) { |
265 | struct dc_stream_state *stream = pipe->stream; |
266 | unsigned int frame_us = (stream->timing.v_total * stream->timing.h_total / |
267 | (double)(stream->timing.pix_clk_100hz * 100)) * 1000000; |
268 | if (frame_us > max_frame_time && !stream->ignore_msa_timing_param) { |
269 | *index = i; |
270 | max_frame_time = frame_us; |
271 | valid_assignment_found = true; |
272 | current_assignment_freesync = false; |
273 | /* For the 2-Freesync display case, still choose the one with the |
274 | * longest frame time |
275 | */ |
276 | } else if (stream->ignore_msa_timing_param && (!valid_assignment_found || |
277 | (current_assignment_freesync && frame_us > max_frame_time))) { |
278 | *index = i; |
279 | valid_assignment_found = true; |
280 | current_assignment_freesync = true; |
281 | } |
282 | } |
283 | } |
284 | pipe_idx++; |
285 | } |
286 | return valid_assignment_found; |
287 | } |
288 | |
289 | /* |
290 | * enough_pipes_for_subvp: Function to check if there are "enough" pipes for SubVP. |
291 | * |
292 | * This function returns true if there are enough free pipes |
293 | * to create the required phantom pipes for any given stream |
294 | * (that does not already have phantom pipe assigned). |
295 | * |
296 | * e.g. For a 2 stream config where the first stream uses one |
297 | * pipe and the second stream uses 2 pipes (i.e. pipe split), |
298 | * this function will return true because there is 1 remaining |
299 | * pipe which can be used as the phantom pipe for the non pipe |
300 | * split pipe. |
301 | * |
302 | * @dc: current dc state |
303 | * @context: new dc state |
304 | * |
305 | * Return: |
306 | * True if there are enough free pipes to assign phantom pipes to at least one |
307 | * stream that does not already have phantom pipes assigned. Otherwise false. |
308 | */ |
309 | static bool enough_pipes_for_subvp(struct dml2_context *ctx, struct dc_state *state) |
310 | { |
311 | unsigned int i, split_cnt, free_pipes; |
312 | unsigned int min_pipe_split = ctx->config.dcn_pipe_count + 1; // init as max number of pipes + 1 |
313 | bool subvp_possible = false; |
314 | |
315 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
316 | struct pipe_ctx *pipe = &state->res_ctx.pipe_ctx[i]; |
317 | |
318 | // Find the minimum pipe split count for non SubVP pipes |
319 | if (pipe->stream && !pipe->top_pipe && |
320 | ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(state, pipe) == SUBVP_NONE) { |
321 | split_cnt = 0; |
322 | while (pipe) { |
323 | split_cnt++; |
324 | pipe = pipe->bottom_pipe; |
325 | } |
326 | |
327 | if (split_cnt < min_pipe_split) |
328 | min_pipe_split = split_cnt; |
329 | } |
330 | } |
331 | |
332 | free_pipes = get_num_free_pipes(ctx, state); |
333 | |
334 | // SubVP only possible if at least one pipe is being used (i.e. free_pipes |
335 | // should not equal to the pipe_count) |
336 | if (free_pipes >= min_pipe_split && free_pipes < ctx->config.dcn_pipe_count) |
337 | subvp_possible = true; |
338 | |
339 | return subvp_possible; |
340 | } |
341 | |
342 | /* |
343 | * subvp_subvp_schedulable: Determine if SubVP + SubVP config is schedulable |
344 | * |
345 | * High level algorithm: |
346 | * 1. Find longest microschedule length (in us) between the two SubVP pipes |
347 | * 2. Check if the worst case overlap (VBLANK in middle of ACTIVE) for both |
348 | * pipes still allows for the maximum microschedule to fit in the active |
349 | * region for both pipes. |
350 | * |
351 | * @dc: current dc state |
352 | * @context: new dc state |
353 | * |
354 | * Return: |
355 | * bool - True if the SubVP + SubVP config is schedulable, false otherwise |
356 | */ |
357 | static bool subvp_subvp_schedulable(struct dml2_context *ctx, struct dc_state *context) |
358 | { |
359 | struct pipe_ctx *subvp_pipes[2]; |
360 | struct dc_stream_state *phantom = NULL; |
361 | uint32_t microschedule_lines = 0; |
362 | uint32_t index = 0; |
363 | uint32_t i; |
364 | uint32_t max_microschedule_us = 0; |
365 | int32_t vactive1_us, vactive2_us, vblank1_us, vblank2_us; |
366 | |
367 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
368 | struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i]; |
369 | uint32_t time_us = 0; |
370 | |
371 | /* Loop to calculate the maximum microschedule time between the two SubVP pipes, |
372 | * and also to store the two main SubVP pipe pointers in subvp_pipes[2]. |
373 | */ |
374 | if (pipe->stream && pipe->plane_state && !pipe->top_pipe && |
375 | ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe) == SUBVP_MAIN) { |
376 | phantom = ctx->config.svp_pstate.callbacks.get_paired_subvp_stream(context, pipe->stream); |
377 | microschedule_lines = (phantom->timing.v_total - phantom->timing.v_front_porch) + |
378 | phantom->timing.v_addressable; |
379 | |
380 | // Round up when calculating microschedule time (+ 1 at the end) |
381 | time_us = (microschedule_lines * phantom->timing.h_total) / |
382 | (double)(phantom->timing.pix_clk_100hz * 100) * 1000000 + |
383 | ctx->config.svp_pstate.subvp_prefetch_end_to_mall_start_us + |
384 | ctx->config.svp_pstate.subvp_fw_processing_delay_us + 1; |
385 | if (time_us > max_microschedule_us) |
386 | max_microschedule_us = time_us; |
387 | |
388 | subvp_pipes[index] = pipe; |
389 | index++; |
390 | |
391 | // Maximum 2 SubVP pipes |
392 | if (index == 2) |
393 | break; |
394 | } |
395 | } |
396 | vactive1_us = ((subvp_pipes[0]->stream->timing.v_addressable * subvp_pipes[0]->stream->timing.h_total) / |
397 | (double)(subvp_pipes[0]->stream->timing.pix_clk_100hz * 100)) * 1000000; |
398 | vactive2_us = ((subvp_pipes[1]->stream->timing.v_addressable * subvp_pipes[1]->stream->timing.h_total) / |
399 | (double)(subvp_pipes[1]->stream->timing.pix_clk_100hz * 100)) * 1000000; |
400 | vblank1_us = (((subvp_pipes[0]->stream->timing.v_total - subvp_pipes[0]->stream->timing.v_addressable) * |
401 | subvp_pipes[0]->stream->timing.h_total) / |
402 | (double)(subvp_pipes[0]->stream->timing.pix_clk_100hz * 100)) * 1000000; |
403 | vblank2_us = (((subvp_pipes[1]->stream->timing.v_total - subvp_pipes[1]->stream->timing.v_addressable) * |
404 | subvp_pipes[1]->stream->timing.h_total) / |
405 | (double)(subvp_pipes[1]->stream->timing.pix_clk_100hz * 100)) * 1000000; |
406 | |
407 | if ((vactive1_us - vblank2_us) / 2 > max_microschedule_us && |
408 | (vactive2_us - vblank1_us) / 2 > max_microschedule_us) |
409 | return true; |
410 | |
411 | return false; |
412 | } |
413 | |
414 | /* |
415 | * dml2_svp_drr_schedulable: Determine if SubVP + DRR config is schedulable |
416 | * |
417 | * High level algorithm: |
418 | * 1. Get timing for SubVP pipe, phantom pipe, and DRR pipe |
419 | * 2. Determine the frame time for the DRR display when adding required margin for MCLK switching |
420 | * (the margin is equal to the MALL region + DRR margin (500us)) |
421 | * 3.If (SubVP Active - Prefetch > Stretched DRR frame + max(MALL region, Stretched DRR frame)) |
422 | * then report the configuration as supported |
423 | * |
424 | * @dc: current dc state |
425 | * @context: new dc state |
426 | * @drr_pipe: DRR pipe_ctx for the SubVP + DRR config |
427 | * |
428 | * Return: |
429 | * bool - True if the SubVP + DRR config is schedulable, false otherwise |
430 | */ |
431 | bool dml2_svp_drr_schedulable(struct dml2_context *ctx, struct dc_state *context, struct dc_crtc_timing *drr_timing) |
432 | { |
433 | bool schedulable = false; |
434 | uint32_t i; |
435 | struct pipe_ctx *pipe = NULL; |
436 | struct dc_crtc_timing *main_timing = NULL; |
437 | struct dc_crtc_timing *phantom_timing = NULL; |
438 | struct dc_stream_state *phantom_stream; |
439 | int16_t prefetch_us = 0; |
440 | int16_t mall_region_us = 0; |
441 | int16_t drr_frame_us = 0; // nominal frame time |
442 | int16_t subvp_active_us = 0; |
443 | int16_t stretched_drr_us = 0; |
444 | int16_t drr_stretched_vblank_us = 0; |
445 | int16_t max_vblank_mallregion = 0; |
446 | |
447 | // Find SubVP pipe |
448 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
449 | pipe = &context->res_ctx.pipe_ctx[i]; |
450 | |
451 | // We check for master pipe, but it shouldn't matter since we only need |
452 | // the pipe for timing info (stream should be same for any pipe splits) |
453 | if (!pipe->stream || !pipe->plane_state || pipe->top_pipe || pipe->prev_odm_pipe) |
454 | continue; |
455 | |
456 | // Find the SubVP pipe |
457 | if (ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe) == SUBVP_MAIN) |
458 | break; |
459 | } |
460 | |
461 | phantom_stream = ctx->config.svp_pstate.callbacks.get_paired_subvp_stream(context, pipe->stream); |
462 | main_timing = &pipe->stream->timing; |
463 | phantom_timing = &phantom_stream->timing; |
464 | prefetch_us = (phantom_timing->v_total - phantom_timing->v_front_porch) * phantom_timing->h_total / |
465 | (double)(phantom_timing->pix_clk_100hz * 100) * 1000000 + |
466 | ctx->config.svp_pstate.subvp_prefetch_end_to_mall_start_us; |
467 | subvp_active_us = main_timing->v_addressable * main_timing->h_total / |
468 | (double)(main_timing->pix_clk_100hz * 100) * 1000000; |
469 | drr_frame_us = drr_timing->v_total * drr_timing->h_total / |
470 | (double)(drr_timing->pix_clk_100hz * 100) * 1000000; |
471 | // P-State allow width and FW delays already included phantom_timing->v_addressable |
472 | mall_region_us = phantom_timing->v_addressable * phantom_timing->h_total / |
473 | (double)(phantom_timing->pix_clk_100hz * 100) * 1000000; |
474 | stretched_drr_us = drr_frame_us + mall_region_us + SUBVP_DRR_MARGIN_US; |
475 | drr_stretched_vblank_us = (drr_timing->v_total - drr_timing->v_addressable) * drr_timing->h_total / |
476 | (double)(drr_timing->pix_clk_100hz * 100) * 1000000 + (stretched_drr_us - drr_frame_us); |
477 | max_vblank_mallregion = drr_stretched_vblank_us > mall_region_us ? drr_stretched_vblank_us : mall_region_us; |
478 | |
479 | /* We consider SubVP + DRR schedulable if the stretched frame duration of the DRR display (i.e. the |
480 | * highest refresh rate + margin that can support UCLK P-State switch) passes the static analysis |
481 | * for VBLANK: (VACTIVE region of the SubVP pipe can fit the MALL prefetch, VBLANK frame time, |
482 | * and the max of (VBLANK blanking time, MALL region)). |
483 | */ |
484 | if (stretched_drr_us < (1 / (double)drr_timing->min_refresh_in_uhz) * 1000000 * 1000000 && |
485 | subvp_active_us - prefetch_us - stretched_drr_us - max_vblank_mallregion > 0) |
486 | schedulable = true; |
487 | |
488 | return schedulable; |
489 | } |
490 | |
491 | |
492 | /* |
493 | * subvp_vblank_schedulable: Determine if SubVP + VBLANK config is schedulable |
494 | * |
495 | * High level algorithm: |
496 | * 1. Get timing for SubVP pipe, phantom pipe, and VBLANK pipe |
497 | * 2. If (SubVP Active - Prefetch > Vblank Frame Time + max(MALL region, Vblank blanking time)) |
498 | * then report the configuration as supported |
499 | * 3. If the VBLANK display is DRR, then take the DRR static schedulability path |
500 | * |
501 | * @dc: current dc state |
502 | * @context: new dc state |
503 | * |
504 | * Return: |
505 | * bool - True if the SubVP + VBLANK/DRR config is schedulable, false otherwise |
506 | */ |
507 | static bool subvp_vblank_schedulable(struct dml2_context *ctx, struct dc_state *context) |
508 | { |
509 | struct pipe_ctx *pipe = NULL; |
510 | struct pipe_ctx *subvp_pipe = NULL; |
511 | bool found = false; |
512 | bool schedulable = false; |
513 | uint32_t i = 0; |
514 | uint8_t vblank_index = 0; |
515 | uint16_t prefetch_us = 0; |
516 | uint16_t mall_region_us = 0; |
517 | uint16_t vblank_frame_us = 0; |
518 | uint16_t subvp_active_us = 0; |
519 | uint16_t vblank_blank_us = 0; |
520 | uint16_t max_vblank_mallregion = 0; |
521 | struct dc_crtc_timing *main_timing = NULL; |
522 | struct dc_crtc_timing *phantom_timing = NULL; |
523 | struct dc_crtc_timing *vblank_timing = NULL; |
524 | struct dc_stream_state *phantom_stream; |
525 | enum mall_stream_type pipe_mall_type; |
526 | |
527 | /* For SubVP + VBLANK/DRR cases, we assume there can only be |
528 | * a single VBLANK/DRR display. If DML outputs SubVP + VBLANK |
529 | * is supported, it is either a single VBLANK case or two VBLANK |
530 | * displays which are synchronized (in which case they have identical |
531 | * timings). |
532 | */ |
533 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
534 | pipe = &context->res_ctx.pipe_ctx[i]; |
535 | pipe_mall_type = ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe); |
536 | |
537 | // We check for master pipe, but it shouldn't matter since we only need |
538 | // the pipe for timing info (stream should be same for any pipe splits) |
539 | if (!pipe->stream || !pipe->plane_state || pipe->top_pipe || pipe->prev_odm_pipe) |
540 | continue; |
541 | |
542 | if (!found && pipe_mall_type == SUBVP_NONE) { |
543 | // Found pipe which is not SubVP or Phantom (i.e. the VBLANK pipe). |
544 | vblank_index = i; |
545 | found = true; |
546 | } |
547 | |
548 | if (!subvp_pipe && pipe_mall_type == SUBVP_MAIN) |
549 | subvp_pipe = pipe; |
550 | } |
551 | // Use ignore_msa_timing_param flag to identify as DRR |
552 | if (found && context->res_ctx.pipe_ctx[vblank_index].stream->ignore_msa_timing_param) { |
553 | // SUBVP + DRR case |
554 | schedulable = dml2_svp_drr_schedulable(ctx, context, drr_timing: &context->res_ctx.pipe_ctx[vblank_index].stream->timing); |
555 | } else if (found) { |
556 | phantom_stream = ctx->config.svp_pstate.callbacks.get_paired_subvp_stream(context, subvp_pipe->stream); |
557 | main_timing = &subvp_pipe->stream->timing; |
558 | phantom_timing = &phantom_stream->timing; |
559 | vblank_timing = &context->res_ctx.pipe_ctx[vblank_index].stream->timing; |
560 | // Prefetch time is equal to VACTIVE + BP + VSYNC of the phantom pipe |
561 | // Also include the prefetch end to mallstart delay time |
562 | prefetch_us = (phantom_timing->v_total - phantom_timing->v_front_porch) * phantom_timing->h_total / |
563 | (double)(phantom_timing->pix_clk_100hz * 100) * 1000000 + |
564 | ctx->config.svp_pstate.subvp_prefetch_end_to_mall_start_us; |
565 | // P-State allow width and FW delays already included phantom_timing->v_addressable |
566 | mall_region_us = phantom_timing->v_addressable * phantom_timing->h_total / |
567 | (double)(phantom_timing->pix_clk_100hz * 100) * 1000000; |
568 | vblank_frame_us = vblank_timing->v_total * vblank_timing->h_total / |
569 | (double)(vblank_timing->pix_clk_100hz * 100) * 1000000; |
570 | vblank_blank_us = (vblank_timing->v_total - vblank_timing->v_addressable) * vblank_timing->h_total / |
571 | (double)(vblank_timing->pix_clk_100hz * 100) * 1000000; |
572 | subvp_active_us = main_timing->v_addressable * main_timing->h_total / |
573 | (double)(main_timing->pix_clk_100hz * 100) * 1000000; |
574 | max_vblank_mallregion = vblank_blank_us > mall_region_us ? vblank_blank_us : mall_region_us; |
575 | |
576 | // Schedulable if VACTIVE region of the SubVP pipe can fit the MALL prefetch, VBLANK frame time, |
577 | // and the max of (VBLANK blanking time, MALL region) |
578 | // TODO: Possibly add some margin (i.e. the below conditions should be [...] > X instead of [...] > 0) |
579 | if (subvp_active_us - prefetch_us - vblank_frame_us - max_vblank_mallregion > 0) |
580 | schedulable = true; |
581 | } |
582 | return schedulable; |
583 | } |
584 | |
585 | /* |
586 | * subvp_validate_static_schedulability: Check which SubVP case is calculated and handle |
587 | * static analysis based on the case. |
588 | * |
589 | * Three cases: |
590 | * 1. SubVP + SubVP |
591 | * 2. SubVP + VBLANK (DRR checked internally) |
592 | * 3. SubVP + VACTIVE (currently unsupported) |
593 | * |
594 | * @dc: current dc state |
595 | * @context: new dc state |
596 | * @vlevel: Voltage level calculated by DML |
597 | * |
598 | * Return: |
599 | * bool - True if statically schedulable, false otherwise |
600 | */ |
601 | bool dml2_svp_validate_static_schedulability(struct dml2_context *ctx, struct dc_state *context, enum dml_dram_clock_change_support pstate_change_type) |
602 | { |
603 | bool schedulable = true; // true by default for single display case |
604 | struct vba_vars_st *vba = &context->bw_ctx.dml.vba; |
605 | uint32_t i, pipe_idx; |
606 | uint8_t subvp_count = 0; |
607 | uint8_t vactive_count = 0; |
608 | |
609 | for (i = 0, pipe_idx = 0; i < ctx->config.dcn_pipe_count; i++) { |
610 | struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i]; |
611 | enum mall_stream_type pipe_mall_type = ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe); |
612 | |
613 | if (!pipe->stream) |
614 | continue; |
615 | |
616 | if (pipe->plane_state && !pipe->top_pipe && |
617 | pipe_mall_type == SUBVP_MAIN) |
618 | subvp_count++; |
619 | |
620 | // Count how many planes that aren't SubVP/phantom are capable of VACTIVE |
621 | // switching (SubVP + VACTIVE unsupported). In situations where we force |
622 | // SubVP for a VACTIVE plane, we don't want to increment the vactive_count. |
623 | if (vba->ActiveDRAMClockChangeLatencyMargin[vba->pipe_plane[pipe_idx]] > 0 && |
624 | pipe_mall_type == SUBVP_NONE) { |
625 | vactive_count++; |
626 | } |
627 | pipe_idx++; |
628 | } |
629 | |
630 | if (subvp_count == 2) { |
631 | // Static schedulability check for SubVP + SubVP case |
632 | schedulable = subvp_subvp_schedulable(ctx, context); |
633 | } else if (pstate_change_type == dml_dram_clock_change_vblank_w_mall_sub_vp) { |
634 | // Static schedulability check for SubVP + VBLANK case. Also handle the case where |
635 | // DML outputs SubVP + VBLANK + VACTIVE (DML will report as SubVP + VBLANK) |
636 | if (vactive_count > 0) |
637 | schedulable = false; |
638 | else |
639 | schedulable = subvp_vblank_schedulable(ctx, context); |
640 | } else if (pstate_change_type == dml_dram_clock_change_vactive_w_mall_sub_vp && |
641 | vactive_count > 0) { |
642 | // For single display SubVP cases, DML will output dm_dram_clock_change_vactive_w_mall_sub_vp by default. |
643 | // We tell the difference between SubVP vs. SubVP + VACTIVE by checking the vactive_count. |
644 | // SubVP + VACTIVE currently unsupported |
645 | schedulable = false; |
646 | } |
647 | return schedulable; |
648 | } |
649 | |
650 | static void set_phantom_stream_timing(struct dml2_context *ctx, struct dc_state *state, |
651 | struct pipe_ctx *ref_pipe, |
652 | struct dc_stream_state *phantom_stream, |
653 | unsigned int dc_pipe_idx, |
654 | unsigned int svp_height, |
655 | unsigned int svp_vstartup) |
656 | { |
657 | unsigned int i, pipe_idx; |
658 | double line_time, fp_and_sync_width_time; |
659 | struct pipe_ctx *pipe; |
660 | uint32_t phantom_vactive, phantom_bp, pstate_width_fw_delay_lines; |
661 | static const double cvt_rb_vblank_max = ((double) 460 / (1000 * 1000)); |
662 | |
663 | // Find DML pipe index (pipe_idx) using dc_pipe_idx |
664 | for (i = 0, pipe_idx = 0; i < ctx->config.dcn_pipe_count; i++) { |
665 | pipe = &state->res_ctx.pipe_ctx[i]; |
666 | |
667 | if (!pipe->stream) |
668 | continue; |
669 | |
670 | if (i == dc_pipe_idx) |
671 | break; |
672 | |
673 | pipe_idx++; |
674 | } |
675 | |
676 | // Calculate lines required for pstate allow width and FW processing delays |
677 | pstate_width_fw_delay_lines = ((double)(ctx->config.svp_pstate.subvp_fw_processing_delay_us + |
678 | ctx->config.svp_pstate.subvp_pstate_allow_width_us) / 1000000) * |
679 | (ref_pipe->stream->timing.pix_clk_100hz * 100) / |
680 | (double)ref_pipe->stream->timing.h_total; |
681 | |
682 | // DML calculation for MALL region doesn't take into account FW delay |
683 | // and required pstate allow width for multi-display cases |
684 | /* Add 16 lines margin to the MALL REGION because SUB_VP_START_LINE must be aligned |
685 | * to 2 swaths (i.e. 16 lines) |
686 | */ |
687 | phantom_vactive = svp_height + pstate_width_fw_delay_lines + ctx->config.svp_pstate.subvp_swath_height_margin_lines; |
688 | |
689 | phantom_stream->timing.v_front_porch = 1; |
690 | |
691 | line_time = phantom_stream->timing.h_total / ((double)phantom_stream->timing.pix_clk_100hz * 100); |
692 | fp_and_sync_width_time = (phantom_stream->timing.v_front_porch + phantom_stream->timing.v_sync_width) * line_time; |
693 | |
694 | if ((svp_vstartup * line_time) + fp_and_sync_width_time > cvt_rb_vblank_max) { |
695 | svp_vstartup = (cvt_rb_vblank_max - fp_and_sync_width_time) / line_time; |
696 | } |
697 | |
698 | // For backporch of phantom pipe, use vstartup of the main pipe |
699 | phantom_bp = svp_vstartup; |
700 | |
701 | phantom_stream->dst.y = 0; |
702 | phantom_stream->dst.height = phantom_vactive; |
703 | phantom_stream->src.y = 0; |
704 | phantom_stream->src.height = phantom_vactive; |
705 | |
706 | phantom_stream->timing.v_addressable = phantom_vactive; |
707 | |
708 | phantom_stream->timing.v_total = phantom_stream->timing.v_addressable + |
709 | phantom_stream->timing.v_front_porch + |
710 | phantom_stream->timing.v_sync_width + |
711 | phantom_bp; |
712 | phantom_stream->timing.flags.DSC = 0; // Don't need DSC for phantom timing |
713 | } |
714 | |
715 | static struct dc_stream_state *enable_phantom_stream(struct dml2_context *ctx, struct dc_state *state, unsigned int dc_pipe_idx, unsigned int svp_height, unsigned int vstartup) |
716 | { |
717 | struct pipe_ctx *ref_pipe = &state->res_ctx.pipe_ctx[dc_pipe_idx]; |
718 | struct dc_stream_state *phantom_stream = ctx->config.svp_pstate.callbacks.create_phantom_stream( |
719 | ctx->config.svp_pstate.callbacks.dc, |
720 | state, |
721 | ref_pipe->stream); |
722 | |
723 | /* stream has limited viewport and small timing */ |
724 | memcpy(&phantom_stream->timing, &ref_pipe->stream->timing, sizeof(phantom_stream->timing)); |
725 | memcpy(&phantom_stream->src, &ref_pipe->stream->src, sizeof(phantom_stream->src)); |
726 | memcpy(&phantom_stream->dst, &ref_pipe->stream->dst, sizeof(phantom_stream->dst)); |
727 | set_phantom_stream_timing(ctx, state, ref_pipe, phantom_stream, dc_pipe_idx, svp_height, svp_vstartup: vstartup); |
728 | |
729 | ctx->config.svp_pstate.callbacks.add_phantom_stream(ctx->config.svp_pstate.callbacks.dc, |
730 | state, |
731 | phantom_stream, |
732 | ref_pipe->stream); |
733 | return phantom_stream; |
734 | } |
735 | |
736 | static void enable_phantom_plane(struct dml2_context *ctx, |
737 | struct dc_state *state, |
738 | struct dc_stream_state *phantom_stream, |
739 | unsigned int dc_pipe_idx) |
740 | { |
741 | struct dc_plane_state *phantom_plane = NULL; |
742 | struct dc_plane_state *prev_phantom_plane = NULL; |
743 | struct pipe_ctx *curr_pipe = &state->res_ctx.pipe_ctx[dc_pipe_idx]; |
744 | |
745 | while (curr_pipe) { |
746 | if (curr_pipe->top_pipe && curr_pipe->top_pipe->plane_state == curr_pipe->plane_state) { |
747 | phantom_plane = prev_phantom_plane; |
748 | } else { |
749 | phantom_plane = ctx->config.svp_pstate.callbacks.create_phantom_plane( |
750 | ctx->config.svp_pstate.callbacks.dc, |
751 | state, |
752 | curr_pipe->plane_state); |
753 | } |
754 | |
755 | memcpy(&phantom_plane->address, &curr_pipe->plane_state->address, sizeof(phantom_plane->address)); |
756 | memcpy(&phantom_plane->scaling_quality, &curr_pipe->plane_state->scaling_quality, |
757 | sizeof(phantom_plane->scaling_quality)); |
758 | memcpy(&phantom_plane->src_rect, &curr_pipe->plane_state->src_rect, sizeof(phantom_plane->src_rect)); |
759 | memcpy(&phantom_plane->dst_rect, &curr_pipe->plane_state->dst_rect, sizeof(phantom_plane->dst_rect)); |
760 | memcpy(&phantom_plane->clip_rect, &curr_pipe->plane_state->clip_rect, sizeof(phantom_plane->clip_rect)); |
761 | memcpy(&phantom_plane->plane_size, &curr_pipe->plane_state->plane_size, |
762 | sizeof(phantom_plane->plane_size)); |
763 | memcpy(&phantom_plane->tiling_info, &curr_pipe->plane_state->tiling_info, |
764 | sizeof(phantom_plane->tiling_info)); |
765 | memcpy(&phantom_plane->dcc, &curr_pipe->plane_state->dcc, sizeof(phantom_plane->dcc)); |
766 | //phantom_plane->tiling_info.gfx10compatible.compat_level = curr_pipe->plane_state->tiling_info.gfx10compatible.compat_level; |
767 | phantom_plane->format = curr_pipe->plane_state->format; |
768 | phantom_plane->rotation = curr_pipe->plane_state->rotation; |
769 | phantom_plane->visible = curr_pipe->plane_state->visible; |
770 | |
771 | /* Shadow pipe has small viewport. */ |
772 | phantom_plane->clip_rect.y = 0; |
773 | phantom_plane->clip_rect.height = phantom_stream->timing.v_addressable; |
774 | |
775 | ctx->config.svp_pstate.callbacks.add_phantom_plane(ctx->config.svp_pstate.callbacks.dc, phantom_stream, phantom_plane, state); |
776 | |
777 | curr_pipe = curr_pipe->bottom_pipe; |
778 | prev_phantom_plane = phantom_plane; |
779 | } |
780 | } |
781 | |
782 | static void add_phantom_pipes_for_main_pipe(struct dml2_context *ctx, struct dc_state *state, unsigned int main_pipe_idx, unsigned int svp_height, unsigned int vstartup) |
783 | { |
784 | struct dc_stream_state *phantom_stream = NULL; |
785 | unsigned int i; |
786 | |
787 | // The index of the DC pipe passed into this function is guarenteed to |
788 | // be a valid candidate for SubVP (i.e. has a plane, stream, doesn't |
789 | // already have phantom pipe assigned, etc.) by previous checks. |
790 | phantom_stream = enable_phantom_stream(ctx, state, dc_pipe_idx: main_pipe_idx, svp_height, vstartup); |
791 | enable_phantom_plane(ctx, state, phantom_stream, dc_pipe_idx: main_pipe_idx); |
792 | |
793 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
794 | struct pipe_ctx *pipe = &state->res_ctx.pipe_ctx[i]; |
795 | |
796 | // Build scaling params for phantom pipes which were newly added. |
797 | // We determine which phantom pipes were added by comparing with |
798 | // the phantom stream. |
799 | if (pipe->plane_state && pipe->stream && pipe->stream == phantom_stream && |
800 | ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(state, pipe) == SUBVP_PHANTOM) { |
801 | pipe->stream->use_dynamic_meta = false; |
802 | pipe->plane_state->flip_immediate = false; |
803 | if (!ctx->config.svp_pstate.callbacks.build_scaling_params(pipe)) { |
804 | // Log / remove phantom pipes since failed to build scaling params |
805 | } |
806 | } |
807 | } |
808 | } |
809 | |
810 | static bool remove_all_phantom_planes_for_stream(struct dml2_context *ctx, struct dc_stream_state *stream, struct dc_state *context) |
811 | { |
812 | int i, old_plane_count; |
813 | struct dc_stream_status *stream_status = NULL; |
814 | struct dc_plane_state *del_planes[MAX_SURFACE_NUM] = { 0 }; |
815 | |
816 | for (i = 0; i < context->stream_count; i++) |
817 | if (context->streams[i] == stream) { |
818 | stream_status = &context->stream_status[i]; |
819 | break; |
820 | } |
821 | |
822 | if (stream_status == NULL) { |
823 | return false; |
824 | } |
825 | |
826 | old_plane_count = stream_status->plane_count; |
827 | |
828 | for (i = 0; i < old_plane_count; i++) |
829 | del_planes[i] = stream_status->plane_states[i]; |
830 | |
831 | for (i = 0; i < old_plane_count; i++) { |
832 | if (!ctx->config.svp_pstate.callbacks.remove_phantom_plane(ctx->config.svp_pstate.callbacks.dc, stream, del_planes[i], context)) |
833 | return false; |
834 | ctx->config.svp_pstate.callbacks.release_phantom_plane(ctx->config.svp_pstate.callbacks.dc, context, del_planes[i]); |
835 | } |
836 | |
837 | return true; |
838 | } |
839 | |
840 | bool dml2_svp_remove_all_phantom_pipes(struct dml2_context *ctx, struct dc_state *state) |
841 | { |
842 | int i; |
843 | bool removed_pipe = false; |
844 | struct dc_stream_state *phantom_stream = NULL; |
845 | |
846 | for (i = 0; i < ctx->config.dcn_pipe_count; i++) { |
847 | struct pipe_ctx *pipe = &state->res_ctx.pipe_ctx[i]; |
848 | // build scaling params for phantom pipes |
849 | if (pipe->plane_state && pipe->stream && ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(state, pipe) == SUBVP_PHANTOM) { |
850 | phantom_stream = pipe->stream; |
851 | |
852 | remove_all_phantom_planes_for_stream(ctx, stream: phantom_stream, context: state); |
853 | ctx->config.svp_pstate.callbacks.remove_phantom_stream(ctx->config.svp_pstate.callbacks.dc, state, phantom_stream); |
854 | ctx->config.svp_pstate.callbacks.release_phantom_stream(ctx->config.svp_pstate.callbacks.dc, state, phantom_stream); |
855 | |
856 | removed_pipe = true; |
857 | } |
858 | |
859 | if (pipe->plane_state) { |
860 | pipe->plane_state->is_phantom = false; |
861 | } |
862 | } |
863 | return removed_pipe; |
864 | } |
865 | |
866 | |
867 | /* Conditions for setting up phantom pipes for SubVP: |
868 | * 1. Not force disable SubVP |
869 | * 2. Full update (i.e. !fast_validate) |
870 | * 3. Enough pipes are available to support SubVP (TODO: Which pipes will use VACTIVE / VBLANK / SUBVP?) |
871 | * 4. Display configuration passes validation |
872 | * 5. (Config doesn't support MCLK in VACTIVE/VBLANK || dc->debug.force_subvp_mclk_switch) |
873 | */ |
874 | bool dml2_svp_add_phantom_pipe_to_dc_state(struct dml2_context *ctx, struct dc_state *state, struct dml_mode_support_info_st *mode_support_info) |
875 | { |
876 | unsigned int dc_pipe_idx, dml_pipe_idx; |
877 | unsigned int svp_height, vstartup; |
878 | |
879 | if (ctx->config.svp_pstate.force_disable_subvp) |
880 | return false; |
881 | |
882 | if (!all_pipes_have_stream_and_plane(ctx, context: state)) |
883 | return false; |
884 | |
885 | if (mpo_in_use(context: state)) |
886 | return false; |
887 | |
888 | merge_pipes_for_subvp(ctx, context: state); |
889 | // to re-initialize viewport after the pipe merge |
890 | for (int i = 0; i < ctx->config.dcn_pipe_count; i++) { |
891 | struct pipe_ctx *pipe_ctx = &state->res_ctx.pipe_ctx[i]; |
892 | |
893 | if (!pipe_ctx->plane_state || !pipe_ctx->stream) |
894 | continue; |
895 | |
896 | ctx->config.svp_pstate.callbacks.build_scaling_params(pipe_ctx); |
897 | } |
898 | |
899 | if (enough_pipes_for_subvp(ctx, state) && assign_subvp_pipe(ctx, context: state, index: &dc_pipe_idx)) { |
900 | dml_pipe_idx = dml2_helper_find_dml_pipe_idx_by_stream_id(ctx, stream_id: state->res_ctx.pipe_ctx[dc_pipe_idx].stream->stream_id); |
901 | svp_height = mode_support_info->SubViewportLinesNeededInMALL[dml_pipe_idx]; |
902 | vstartup = dml_get_vstartup_calculated(mode_lib: &ctx->v20.dml_core_ctx, surface_idx: dml_pipe_idx); |
903 | |
904 | add_phantom_pipes_for_main_pipe(ctx, state, main_pipe_idx: dc_pipe_idx, svp_height, vstartup); |
905 | |
906 | return true; |
907 | } |
908 | |
909 | return false; |
910 | } |
911 | |