1 | /* |
2 | * Copyright © 2006-2010 Intel Corporation |
3 | * Copyright (c) 2006 Dave Airlie <airlied@linux.ie> |
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 (including the next |
13 | * paragraph) shall be included in all copies or substantial portions of the |
14 | * Software. |
15 | * |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
22 | * DEALINGS IN THE SOFTWARE. |
23 | * |
24 | * Authors: |
25 | * Eric Anholt <eric@anholt.net> |
26 | * Dave Airlie <airlied@linux.ie> |
27 | * Jesse Barnes <jesse.barnes@intel.com> |
28 | * Chris Wilson <chris@chris-wilson.co.uk> |
29 | */ |
30 | |
31 | #include <linux/kernel.h> |
32 | #include <linux/pwm.h> |
33 | |
34 | #include <drm/drm_edid.h> |
35 | |
36 | #include "i915_reg.h" |
37 | #include "intel_backlight.h" |
38 | #include "intel_connector.h" |
39 | #include "intel_de.h" |
40 | #include "intel_display_driver.h" |
41 | #include "intel_display_types.h" |
42 | #include "intel_drrs.h" |
43 | #include "intel_lvds_regs.h" |
44 | #include "intel_panel.h" |
45 | #include "intel_quirks.h" |
46 | #include "intel_vrr.h" |
47 | |
48 | bool intel_panel_use_ssc(struct drm_i915_private *i915) |
49 | { |
50 | if (i915->display.params.panel_use_ssc >= 0) |
51 | return i915->display.params.panel_use_ssc != 0; |
52 | return i915->display.vbt.lvds_use_ssc && |
53 | !intel_has_quirk(i915, quirk: QUIRK_LVDS_SSC_DISABLE); |
54 | } |
55 | |
56 | const struct drm_display_mode * |
57 | intel_panel_preferred_fixed_mode(struct intel_connector *connector) |
58 | { |
59 | return list_first_entry_or_null(&connector->panel.fixed_modes, |
60 | struct drm_display_mode, head); |
61 | } |
62 | |
63 | static bool is_best_fixed_mode(struct intel_connector *connector, |
64 | int vrefresh, int fixed_mode_vrefresh, |
65 | const struct drm_display_mode *best_mode) |
66 | { |
67 | /* we want to always return something */ |
68 | if (!best_mode) |
69 | return true; |
70 | |
71 | /* |
72 | * With VRR always pick a mode with equal/higher than requested |
73 | * vrefresh, which we can then reduce to match the requested |
74 | * vrefresh by extending the vblank length. |
75 | */ |
76 | if (intel_vrr_is_in_range(connector, vrefresh) && |
77 | intel_vrr_is_in_range(connector, vrefresh: fixed_mode_vrefresh) && |
78 | fixed_mode_vrefresh < vrefresh) |
79 | return false; |
80 | |
81 | /* pick the fixed_mode that is closest in terms of vrefresh */ |
82 | return abs(fixed_mode_vrefresh - vrefresh) < |
83 | abs(drm_mode_vrefresh(best_mode) - vrefresh); |
84 | } |
85 | |
86 | const struct drm_display_mode * |
87 | intel_panel_fixed_mode(struct intel_connector *connector, |
88 | const struct drm_display_mode *mode) |
89 | { |
90 | const struct drm_display_mode *fixed_mode, *best_mode = NULL; |
91 | int vrefresh = drm_mode_vrefresh(mode); |
92 | |
93 | list_for_each_entry(fixed_mode, &connector->panel.fixed_modes, head) { |
94 | int fixed_mode_vrefresh = drm_mode_vrefresh(mode: fixed_mode); |
95 | |
96 | if (is_best_fixed_mode(connector, vrefresh, |
97 | fixed_mode_vrefresh, best_mode)) |
98 | best_mode = fixed_mode; |
99 | } |
100 | |
101 | return best_mode; |
102 | } |
103 | |
104 | static bool is_alt_drrs_mode(const struct drm_display_mode *mode, |
105 | const struct drm_display_mode *preferred_mode) |
106 | { |
107 | return drm_mode_match(mode1: mode, mode2: preferred_mode, |
108 | DRM_MODE_MATCH_TIMINGS | |
109 | DRM_MODE_MATCH_FLAGS | |
110 | DRM_MODE_MATCH_3D_FLAGS) && |
111 | mode->clock != preferred_mode->clock; |
112 | } |
113 | |
114 | static bool is_alt_fixed_mode(const struct drm_display_mode *mode, |
115 | const struct drm_display_mode *preferred_mode) |
116 | { |
117 | u32 sync_flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC | |
118 | DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC; |
119 | |
120 | return (mode->flags & ~sync_flags) == (preferred_mode->flags & ~sync_flags) && |
121 | mode->hdisplay == preferred_mode->hdisplay && |
122 | mode->vdisplay == preferred_mode->vdisplay; |
123 | } |
124 | |
125 | const struct drm_display_mode * |
126 | intel_panel_downclock_mode(struct intel_connector *connector, |
127 | const struct drm_display_mode *adjusted_mode) |
128 | { |
129 | const struct drm_display_mode *fixed_mode, *best_mode = NULL; |
130 | int min_vrefresh = connector->panel.vbt.seamless_drrs_min_refresh_rate; |
131 | int max_vrefresh = drm_mode_vrefresh(mode: adjusted_mode); |
132 | |
133 | /* pick the fixed_mode with the lowest refresh rate */ |
134 | list_for_each_entry(fixed_mode, &connector->panel.fixed_modes, head) { |
135 | int vrefresh = drm_mode_vrefresh(mode: fixed_mode); |
136 | |
137 | if (is_alt_drrs_mode(mode: fixed_mode, preferred_mode: adjusted_mode) && |
138 | vrefresh >= min_vrefresh && vrefresh < max_vrefresh) { |
139 | max_vrefresh = vrefresh; |
140 | best_mode = fixed_mode; |
141 | } |
142 | } |
143 | |
144 | return best_mode; |
145 | } |
146 | |
147 | const struct drm_display_mode * |
148 | intel_panel_highest_mode(struct intel_connector *connector, |
149 | const struct drm_display_mode *adjusted_mode) |
150 | { |
151 | const struct drm_display_mode *fixed_mode, *best_mode = adjusted_mode; |
152 | |
153 | /* pick the fixed_mode that has the highest clock */ |
154 | list_for_each_entry(fixed_mode, &connector->panel.fixed_modes, head) { |
155 | if (fixed_mode->clock > best_mode->clock) |
156 | best_mode = fixed_mode; |
157 | } |
158 | |
159 | return best_mode; |
160 | } |
161 | |
162 | int intel_panel_get_modes(struct intel_connector *connector) |
163 | { |
164 | const struct drm_display_mode *fixed_mode; |
165 | int num_modes = 0; |
166 | |
167 | list_for_each_entry(fixed_mode, &connector->panel.fixed_modes, head) { |
168 | struct drm_display_mode *mode; |
169 | |
170 | mode = drm_mode_duplicate(dev: connector->base.dev, mode: fixed_mode); |
171 | if (mode) { |
172 | drm_mode_probed_add(connector: &connector->base, mode); |
173 | num_modes++; |
174 | } |
175 | } |
176 | |
177 | return num_modes; |
178 | } |
179 | |
180 | static bool has_drrs_modes(struct intel_connector *connector) |
181 | { |
182 | const struct drm_display_mode *mode1; |
183 | |
184 | list_for_each_entry(mode1, &connector->panel.fixed_modes, head) { |
185 | const struct drm_display_mode *mode2 = mode1; |
186 | |
187 | list_for_each_entry_continue(mode2, &connector->panel.fixed_modes, head) { |
188 | if (is_alt_drrs_mode(mode: mode1, preferred_mode: mode2)) |
189 | return true; |
190 | } |
191 | } |
192 | |
193 | return false; |
194 | } |
195 | |
196 | enum drrs_type intel_panel_drrs_type(struct intel_connector *connector) |
197 | { |
198 | return connector->panel.vbt.drrs_type; |
199 | } |
200 | |
201 | int intel_panel_compute_config(struct intel_connector *connector, |
202 | struct drm_display_mode *adjusted_mode) |
203 | { |
204 | const struct drm_display_mode *fixed_mode = |
205 | intel_panel_fixed_mode(connector, mode: adjusted_mode); |
206 | int vrefresh, fixed_mode_vrefresh; |
207 | bool is_vrr; |
208 | |
209 | if (!fixed_mode) |
210 | return 0; |
211 | |
212 | vrefresh = drm_mode_vrefresh(mode: adjusted_mode); |
213 | fixed_mode_vrefresh = drm_mode_vrefresh(mode: fixed_mode); |
214 | |
215 | /* |
216 | * Assume that we shouldn't muck about with the |
217 | * timings if they don't land in the VRR range. |
218 | */ |
219 | is_vrr = intel_vrr_is_in_range(connector, vrefresh) && |
220 | intel_vrr_is_in_range(connector, vrefresh: fixed_mode_vrefresh); |
221 | |
222 | if (!is_vrr) { |
223 | /* |
224 | * We don't want to lie too much to the user about the refresh |
225 | * rate they're going to get. But we have to allow a bit of latitude |
226 | * for Xorg since it likes to automagically cook up modes with slightly |
227 | * off refresh rates. |
228 | */ |
229 | if (abs(vrefresh - fixed_mode_vrefresh) > 1) { |
230 | drm_dbg_kms(connector->base.dev, |
231 | "[CONNECTOR:%d:%s] Requested mode vrefresh (%d Hz) does not match fixed mode vrefresh (%d Hz)\n" , |
232 | connector->base.base.id, connector->base.name, |
233 | vrefresh, fixed_mode_vrefresh); |
234 | |
235 | return -EINVAL; |
236 | } |
237 | } |
238 | |
239 | drm_mode_copy(dst: adjusted_mode, src: fixed_mode); |
240 | |
241 | if (is_vrr && fixed_mode_vrefresh != vrefresh) |
242 | adjusted_mode->vtotal = |
243 | DIV_ROUND_CLOSEST(adjusted_mode->clock * 1000, |
244 | adjusted_mode->htotal * vrefresh); |
245 | |
246 | drm_mode_set_crtcinfo(p: adjusted_mode, adjust_flags: 0); |
247 | |
248 | return 0; |
249 | } |
250 | |
251 | static void intel_panel_add_edid_alt_fixed_modes(struct intel_connector *connector) |
252 | { |
253 | struct drm_i915_private *dev_priv = to_i915(dev: connector->base.dev); |
254 | const struct drm_display_mode *preferred_mode = |
255 | intel_panel_preferred_fixed_mode(connector); |
256 | struct drm_display_mode *mode, *next; |
257 | |
258 | list_for_each_entry_safe(mode, next, &connector->base.probed_modes, head) { |
259 | if (!is_alt_fixed_mode(mode, preferred_mode)) |
260 | continue; |
261 | |
262 | drm_dbg_kms(&dev_priv->drm, |
263 | "[CONNECTOR:%d:%s] using alternate EDID fixed mode: " DRM_MODE_FMT "\n" , |
264 | connector->base.base.id, connector->base.name, |
265 | DRM_MODE_ARG(mode)); |
266 | |
267 | list_move_tail(list: &mode->head, head: &connector->panel.fixed_modes); |
268 | } |
269 | } |
270 | |
271 | static void intel_panel_add_edid_preferred_mode(struct intel_connector *connector) |
272 | { |
273 | struct drm_i915_private *dev_priv = to_i915(dev: connector->base.dev); |
274 | struct drm_display_mode *scan, *fixed_mode = NULL; |
275 | |
276 | if (list_empty(head: &connector->base.probed_modes)) |
277 | return; |
278 | |
279 | /* make sure the preferred mode is first */ |
280 | list_for_each_entry(scan, &connector->base.probed_modes, head) { |
281 | if (scan->type & DRM_MODE_TYPE_PREFERRED) { |
282 | fixed_mode = scan; |
283 | break; |
284 | } |
285 | } |
286 | |
287 | if (!fixed_mode) |
288 | fixed_mode = list_first_entry(&connector->base.probed_modes, |
289 | typeof(*fixed_mode), head); |
290 | |
291 | drm_dbg_kms(&dev_priv->drm, |
292 | "[CONNECTOR:%d:%s] using %s EDID fixed mode: " DRM_MODE_FMT "\n" , |
293 | connector->base.base.id, connector->base.name, |
294 | fixed_mode->type & DRM_MODE_TYPE_PREFERRED ? "preferred" : "first" , |
295 | DRM_MODE_ARG(fixed_mode)); |
296 | |
297 | fixed_mode->type |= DRM_MODE_TYPE_PREFERRED; |
298 | |
299 | list_move_tail(list: &fixed_mode->head, head: &connector->panel.fixed_modes); |
300 | } |
301 | |
302 | static void intel_panel_destroy_probed_modes(struct intel_connector *connector) |
303 | { |
304 | struct drm_i915_private *i915 = to_i915(dev: connector->base.dev); |
305 | struct drm_display_mode *mode, *next; |
306 | |
307 | list_for_each_entry_safe(mode, next, &connector->base.probed_modes, head) { |
308 | drm_dbg_kms(&i915->drm, |
309 | "[CONNECTOR:%d:%s] not using EDID mode: " DRM_MODE_FMT "\n" , |
310 | connector->base.base.id, connector->base.name, |
311 | DRM_MODE_ARG(mode)); |
312 | list_del(entry: &mode->head); |
313 | drm_mode_destroy(dev: &i915->drm, mode); |
314 | } |
315 | } |
316 | |
317 | void intel_panel_add_edid_fixed_modes(struct intel_connector *connector, |
318 | bool use_alt_fixed_modes) |
319 | { |
320 | intel_panel_add_edid_preferred_mode(connector); |
321 | if (intel_panel_preferred_fixed_mode(connector) && use_alt_fixed_modes) |
322 | intel_panel_add_edid_alt_fixed_modes(connector); |
323 | intel_panel_destroy_probed_modes(connector); |
324 | } |
325 | |
326 | static void intel_panel_add_fixed_mode(struct intel_connector *connector, |
327 | struct drm_display_mode *fixed_mode, |
328 | const char *type) |
329 | { |
330 | struct drm_i915_private *i915 = to_i915(dev: connector->base.dev); |
331 | struct drm_display_info *info = &connector->base.display_info; |
332 | |
333 | if (!fixed_mode) |
334 | return; |
335 | |
336 | fixed_mode->type |= DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; |
337 | |
338 | info->width_mm = fixed_mode->width_mm; |
339 | info->height_mm = fixed_mode->height_mm; |
340 | |
341 | drm_dbg_kms(&i915->drm, "[CONNECTOR:%d:%s] using %s fixed mode: " DRM_MODE_FMT "\n" , |
342 | connector->base.base.id, connector->base.name, type, |
343 | DRM_MODE_ARG(fixed_mode)); |
344 | |
345 | list_add_tail(new: &fixed_mode->head, head: &connector->panel.fixed_modes); |
346 | } |
347 | |
348 | void intel_panel_add_vbt_lfp_fixed_mode(struct intel_connector *connector) |
349 | { |
350 | struct drm_i915_private *i915 = to_i915(dev: connector->base.dev); |
351 | const struct drm_display_mode *mode; |
352 | |
353 | mode = connector->panel.vbt.lfp_lvds_vbt_mode; |
354 | if (!mode) |
355 | return; |
356 | |
357 | intel_panel_add_fixed_mode(connector, |
358 | fixed_mode: drm_mode_duplicate(dev: &i915->drm, mode), |
359 | type: "VBT LFP" ); |
360 | } |
361 | |
362 | void intel_panel_add_vbt_sdvo_fixed_mode(struct intel_connector *connector) |
363 | { |
364 | struct drm_i915_private *i915 = to_i915(dev: connector->base.dev); |
365 | const struct drm_display_mode *mode; |
366 | |
367 | mode = connector->panel.vbt.sdvo_lvds_vbt_mode; |
368 | if (!mode) |
369 | return; |
370 | |
371 | intel_panel_add_fixed_mode(connector, |
372 | fixed_mode: drm_mode_duplicate(dev: &i915->drm, mode), |
373 | type: "VBT SDVO" ); |
374 | } |
375 | |
376 | void intel_panel_add_encoder_fixed_mode(struct intel_connector *connector, |
377 | struct intel_encoder *encoder) |
378 | { |
379 | intel_panel_add_fixed_mode(connector, |
380 | fixed_mode: intel_encoder_current_mode(encoder), |
381 | type: "current (BIOS)" ); |
382 | } |
383 | |
384 | /* adjusted_mode has been preset to be the panel's fixed mode */ |
385 | static int pch_panel_fitting(struct intel_crtc_state *crtc_state, |
386 | const struct drm_connector_state *conn_state) |
387 | { |
388 | const struct drm_display_mode *adjusted_mode = |
389 | &crtc_state->hw.adjusted_mode; |
390 | int pipe_src_w = drm_rect_width(r: &crtc_state->pipe_src); |
391 | int pipe_src_h = drm_rect_height(r: &crtc_state->pipe_src); |
392 | int x, y, width, height; |
393 | |
394 | /* Native modes don't need fitting */ |
395 | if (adjusted_mode->crtc_hdisplay == pipe_src_w && |
396 | adjusted_mode->crtc_vdisplay == pipe_src_h && |
397 | crtc_state->output_format != INTEL_OUTPUT_FORMAT_YCBCR420) |
398 | return 0; |
399 | |
400 | switch (conn_state->scaling_mode) { |
401 | case DRM_MODE_SCALE_CENTER: |
402 | width = pipe_src_w; |
403 | height = pipe_src_h; |
404 | x = (adjusted_mode->crtc_hdisplay - width + 1)/2; |
405 | y = (adjusted_mode->crtc_vdisplay - height + 1)/2; |
406 | break; |
407 | |
408 | case DRM_MODE_SCALE_ASPECT: |
409 | /* Scale but preserve the aspect ratio */ |
410 | { |
411 | u32 scaled_width = adjusted_mode->crtc_hdisplay * pipe_src_h; |
412 | u32 scaled_height = pipe_src_w * adjusted_mode->crtc_vdisplay; |
413 | if (scaled_width > scaled_height) { /* pillar */ |
414 | width = scaled_height / pipe_src_h; |
415 | if (width & 1) |
416 | width++; |
417 | x = (adjusted_mode->crtc_hdisplay - width + 1) / 2; |
418 | y = 0; |
419 | height = adjusted_mode->crtc_vdisplay; |
420 | } else if (scaled_width < scaled_height) { /* letter */ |
421 | height = scaled_width / pipe_src_w; |
422 | if (height & 1) |
423 | height++; |
424 | y = (adjusted_mode->crtc_vdisplay - height + 1) / 2; |
425 | x = 0; |
426 | width = adjusted_mode->crtc_hdisplay; |
427 | } else { |
428 | x = y = 0; |
429 | width = adjusted_mode->crtc_hdisplay; |
430 | height = adjusted_mode->crtc_vdisplay; |
431 | } |
432 | } |
433 | break; |
434 | |
435 | case DRM_MODE_SCALE_NONE: |
436 | WARN_ON(adjusted_mode->crtc_hdisplay != pipe_src_w); |
437 | WARN_ON(adjusted_mode->crtc_vdisplay != pipe_src_h); |
438 | fallthrough; |
439 | case DRM_MODE_SCALE_FULLSCREEN: |
440 | x = y = 0; |
441 | width = adjusted_mode->crtc_hdisplay; |
442 | height = adjusted_mode->crtc_vdisplay; |
443 | break; |
444 | |
445 | default: |
446 | MISSING_CASE(conn_state->scaling_mode); |
447 | return -EINVAL; |
448 | } |
449 | |
450 | drm_rect_init(r: &crtc_state->pch_pfit.dst, |
451 | x, y, width, height); |
452 | crtc_state->pch_pfit.enabled = true; |
453 | |
454 | return 0; |
455 | } |
456 | |
457 | static void |
458 | centre_horizontally(struct drm_display_mode *adjusted_mode, |
459 | int width) |
460 | { |
461 | u32 border, sync_pos, blank_width, sync_width; |
462 | |
463 | /* keep the hsync and hblank widths constant */ |
464 | sync_width = adjusted_mode->crtc_hsync_end - adjusted_mode->crtc_hsync_start; |
465 | blank_width = adjusted_mode->crtc_hblank_end - adjusted_mode->crtc_hblank_start; |
466 | sync_pos = (blank_width - sync_width + 1) / 2; |
467 | |
468 | border = (adjusted_mode->crtc_hdisplay - width + 1) / 2; |
469 | border += border & 1; /* make the border even */ |
470 | |
471 | adjusted_mode->crtc_hdisplay = width; |
472 | adjusted_mode->crtc_hblank_start = width + border; |
473 | adjusted_mode->crtc_hblank_end = adjusted_mode->crtc_hblank_start + blank_width; |
474 | |
475 | adjusted_mode->crtc_hsync_start = adjusted_mode->crtc_hblank_start + sync_pos; |
476 | adjusted_mode->crtc_hsync_end = adjusted_mode->crtc_hsync_start + sync_width; |
477 | } |
478 | |
479 | static void |
480 | centre_vertically(struct drm_display_mode *adjusted_mode, |
481 | int height) |
482 | { |
483 | u32 border, sync_pos, blank_width, sync_width; |
484 | |
485 | /* keep the vsync and vblank widths constant */ |
486 | sync_width = adjusted_mode->crtc_vsync_end - adjusted_mode->crtc_vsync_start; |
487 | blank_width = adjusted_mode->crtc_vblank_end - adjusted_mode->crtc_vblank_start; |
488 | sync_pos = (blank_width - sync_width + 1) / 2; |
489 | |
490 | border = (adjusted_mode->crtc_vdisplay - height + 1) / 2; |
491 | |
492 | adjusted_mode->crtc_vdisplay = height; |
493 | adjusted_mode->crtc_vblank_start = height + border; |
494 | adjusted_mode->crtc_vblank_end = adjusted_mode->crtc_vblank_start + blank_width; |
495 | |
496 | adjusted_mode->crtc_vsync_start = adjusted_mode->crtc_vblank_start + sync_pos; |
497 | adjusted_mode->crtc_vsync_end = adjusted_mode->crtc_vsync_start + sync_width; |
498 | } |
499 | |
500 | static u32 panel_fitter_scaling(u32 source, u32 target) |
501 | { |
502 | /* |
503 | * Floating point operation is not supported. So the FACTOR |
504 | * is defined, which can avoid the floating point computation |
505 | * when calculating the panel ratio. |
506 | */ |
507 | #define ACCURACY 12 |
508 | #define FACTOR (1 << ACCURACY) |
509 | u32 ratio = source * FACTOR / target; |
510 | return (FACTOR * ratio + FACTOR/2) / FACTOR; |
511 | } |
512 | |
513 | static void i965_scale_aspect(struct intel_crtc_state *crtc_state, |
514 | u32 *pfit_control) |
515 | { |
516 | const struct drm_display_mode *adjusted_mode = |
517 | &crtc_state->hw.adjusted_mode; |
518 | int pipe_src_w = drm_rect_width(r: &crtc_state->pipe_src); |
519 | int pipe_src_h = drm_rect_height(r: &crtc_state->pipe_src); |
520 | u32 scaled_width = adjusted_mode->crtc_hdisplay * pipe_src_h; |
521 | u32 scaled_height = pipe_src_w * adjusted_mode->crtc_vdisplay; |
522 | |
523 | /* 965+ is easy, it does everything in hw */ |
524 | if (scaled_width > scaled_height) |
525 | *pfit_control |= PFIT_ENABLE | |
526 | PFIT_SCALING_PILLAR; |
527 | else if (scaled_width < scaled_height) |
528 | *pfit_control |= PFIT_ENABLE | |
529 | PFIT_SCALING_LETTER; |
530 | else if (adjusted_mode->crtc_hdisplay != pipe_src_w) |
531 | *pfit_control |= PFIT_ENABLE | PFIT_SCALING_AUTO; |
532 | } |
533 | |
534 | static void i9xx_scale_aspect(struct intel_crtc_state *crtc_state, |
535 | u32 *pfit_control, u32 *pfit_pgm_ratios, |
536 | u32 *border) |
537 | { |
538 | struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; |
539 | int pipe_src_w = drm_rect_width(r: &crtc_state->pipe_src); |
540 | int pipe_src_h = drm_rect_height(r: &crtc_state->pipe_src); |
541 | u32 scaled_width = adjusted_mode->crtc_hdisplay * pipe_src_h; |
542 | u32 scaled_height = pipe_src_w * adjusted_mode->crtc_vdisplay; |
543 | u32 bits; |
544 | |
545 | /* |
546 | * For earlier chips we have to calculate the scaling |
547 | * ratio by hand and program it into the |
548 | * PFIT_PGM_RATIO register |
549 | */ |
550 | if (scaled_width > scaled_height) { /* pillar */ |
551 | centre_horizontally(adjusted_mode, |
552 | width: scaled_height / pipe_src_h); |
553 | |
554 | *border = LVDS_BORDER_ENABLE; |
555 | if (pipe_src_h != adjusted_mode->crtc_vdisplay) { |
556 | bits = panel_fitter_scaling(source: pipe_src_h, |
557 | target: adjusted_mode->crtc_vdisplay); |
558 | |
559 | *pfit_pgm_ratios |= (PFIT_HORIZ_SCALE(bits) | |
560 | PFIT_VERT_SCALE(bits)); |
561 | *pfit_control |= (PFIT_ENABLE | |
562 | PFIT_VERT_INTERP_BILINEAR | |
563 | PFIT_HORIZ_INTERP_BILINEAR); |
564 | } |
565 | } else if (scaled_width < scaled_height) { /* letter */ |
566 | centre_vertically(adjusted_mode, |
567 | height: scaled_width / pipe_src_w); |
568 | |
569 | *border = LVDS_BORDER_ENABLE; |
570 | if (pipe_src_w != adjusted_mode->crtc_hdisplay) { |
571 | bits = panel_fitter_scaling(source: pipe_src_w, |
572 | target: adjusted_mode->crtc_hdisplay); |
573 | |
574 | *pfit_pgm_ratios |= (PFIT_HORIZ_SCALE(bits) | |
575 | PFIT_VERT_SCALE(bits)); |
576 | *pfit_control |= (PFIT_ENABLE | |
577 | PFIT_VERT_INTERP_BILINEAR | |
578 | PFIT_HORIZ_INTERP_BILINEAR); |
579 | } |
580 | } else { |
581 | /* Aspects match, Let hw scale both directions */ |
582 | *pfit_control |= (PFIT_ENABLE | |
583 | PFIT_VERT_AUTO_SCALE | |
584 | PFIT_HORIZ_AUTO_SCALE | |
585 | PFIT_VERT_INTERP_BILINEAR | |
586 | PFIT_HORIZ_INTERP_BILINEAR); |
587 | } |
588 | } |
589 | |
590 | static int gmch_panel_fitting(struct intel_crtc_state *crtc_state, |
591 | const struct drm_connector_state *conn_state) |
592 | { |
593 | struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); |
594 | struct drm_i915_private *dev_priv = to_i915(dev: crtc->base.dev); |
595 | u32 pfit_control = 0, pfit_pgm_ratios = 0, border = 0; |
596 | struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; |
597 | int pipe_src_w = drm_rect_width(r: &crtc_state->pipe_src); |
598 | int pipe_src_h = drm_rect_height(r: &crtc_state->pipe_src); |
599 | |
600 | /* Native modes don't need fitting */ |
601 | if (adjusted_mode->crtc_hdisplay == pipe_src_w && |
602 | adjusted_mode->crtc_vdisplay == pipe_src_h) |
603 | goto out; |
604 | |
605 | switch (conn_state->scaling_mode) { |
606 | case DRM_MODE_SCALE_CENTER: |
607 | /* |
608 | * For centered modes, we have to calculate border widths & |
609 | * heights and modify the values programmed into the CRTC. |
610 | */ |
611 | centre_horizontally(adjusted_mode, width: pipe_src_w); |
612 | centre_vertically(adjusted_mode, height: pipe_src_h); |
613 | border = LVDS_BORDER_ENABLE; |
614 | break; |
615 | case DRM_MODE_SCALE_ASPECT: |
616 | /* Scale but preserve the aspect ratio */ |
617 | if (DISPLAY_VER(dev_priv) >= 4) |
618 | i965_scale_aspect(crtc_state, pfit_control: &pfit_control); |
619 | else |
620 | i9xx_scale_aspect(crtc_state, pfit_control: &pfit_control, |
621 | pfit_pgm_ratios: &pfit_pgm_ratios, border: &border); |
622 | break; |
623 | case DRM_MODE_SCALE_FULLSCREEN: |
624 | /* |
625 | * Full scaling, even if it changes the aspect ratio. |
626 | * Fortunately this is all done for us in hw. |
627 | */ |
628 | if (pipe_src_h != adjusted_mode->crtc_vdisplay || |
629 | pipe_src_w != adjusted_mode->crtc_hdisplay) { |
630 | pfit_control |= PFIT_ENABLE; |
631 | if (DISPLAY_VER(dev_priv) >= 4) |
632 | pfit_control |= PFIT_SCALING_AUTO; |
633 | else |
634 | pfit_control |= (PFIT_VERT_AUTO_SCALE | |
635 | PFIT_VERT_INTERP_BILINEAR | |
636 | PFIT_HORIZ_AUTO_SCALE | |
637 | PFIT_HORIZ_INTERP_BILINEAR); |
638 | } |
639 | break; |
640 | default: |
641 | MISSING_CASE(conn_state->scaling_mode); |
642 | return -EINVAL; |
643 | } |
644 | |
645 | /* 965+ wants fuzzy fitting */ |
646 | /* FIXME: handle multiple panels by failing gracefully */ |
647 | if (DISPLAY_VER(dev_priv) >= 4) |
648 | pfit_control |= PFIT_PIPE(crtc->pipe) | PFIT_FILTER_FUZZY; |
649 | |
650 | out: |
651 | if ((pfit_control & PFIT_ENABLE) == 0) { |
652 | pfit_control = 0; |
653 | pfit_pgm_ratios = 0; |
654 | } |
655 | |
656 | /* Make sure pre-965 set dither correctly for 18bpp panels. */ |
657 | if (DISPLAY_VER(dev_priv) < 4 && crtc_state->pipe_bpp == 18) |
658 | pfit_control |= PFIT_PANEL_8TO6_DITHER_ENABLE; |
659 | |
660 | crtc_state->gmch_pfit.control = pfit_control; |
661 | crtc_state->gmch_pfit.pgm_ratios = pfit_pgm_ratios; |
662 | crtc_state->gmch_pfit.lvds_border_bits = border; |
663 | |
664 | return 0; |
665 | } |
666 | |
667 | int intel_panel_fitting(struct intel_crtc_state *crtc_state, |
668 | const struct drm_connector_state *conn_state) |
669 | { |
670 | struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); |
671 | struct drm_i915_private *i915 = to_i915(dev: crtc->base.dev); |
672 | |
673 | if (HAS_GMCH(i915)) |
674 | return gmch_panel_fitting(crtc_state, conn_state); |
675 | else |
676 | return pch_panel_fitting(crtc_state, conn_state); |
677 | } |
678 | |
679 | enum drm_connector_status |
680 | intel_panel_detect(struct drm_connector *connector, bool force) |
681 | { |
682 | struct drm_i915_private *i915 = to_i915(dev: connector->dev); |
683 | |
684 | if (!intel_display_device_enabled(i915)) |
685 | return connector_status_disconnected; |
686 | |
687 | if (!intel_display_driver_check_access(i915)) |
688 | return connector->status; |
689 | |
690 | return connector_status_connected; |
691 | } |
692 | |
693 | enum drm_mode_status |
694 | intel_panel_mode_valid(struct intel_connector *connector, |
695 | const struct drm_display_mode *mode) |
696 | { |
697 | const struct drm_display_mode *fixed_mode = |
698 | intel_panel_fixed_mode(connector, mode); |
699 | |
700 | if (!fixed_mode) |
701 | return MODE_OK; |
702 | |
703 | if (mode->hdisplay != fixed_mode->hdisplay) |
704 | return MODE_PANEL; |
705 | |
706 | if (mode->vdisplay != fixed_mode->vdisplay) |
707 | return MODE_PANEL; |
708 | |
709 | if (drm_mode_vrefresh(mode) != drm_mode_vrefresh(mode: fixed_mode)) |
710 | return MODE_PANEL; |
711 | |
712 | return MODE_OK; |
713 | } |
714 | |
715 | void intel_panel_init_alloc(struct intel_connector *connector) |
716 | { |
717 | struct intel_panel *panel = &connector->panel; |
718 | |
719 | connector->panel.vbt.panel_type = -1; |
720 | connector->panel.vbt.backlight.controller = -1; |
721 | INIT_LIST_HEAD(list: &panel->fixed_modes); |
722 | } |
723 | |
724 | int intel_panel_init(struct intel_connector *connector, |
725 | const struct drm_edid *fixed_edid) |
726 | { |
727 | struct intel_panel *panel = &connector->panel; |
728 | |
729 | panel->fixed_edid = fixed_edid; |
730 | |
731 | intel_backlight_init_funcs(panel); |
732 | |
733 | if (!has_drrs_modes(connector)) |
734 | connector->panel.vbt.drrs_type = DRRS_TYPE_NONE; |
735 | |
736 | drm_dbg_kms(connector->base.dev, |
737 | "[CONNECTOR:%d:%s] DRRS type: %s\n" , |
738 | connector->base.base.id, connector->base.name, |
739 | intel_drrs_type_str(intel_panel_drrs_type(connector))); |
740 | |
741 | return 0; |
742 | } |
743 | |
744 | void intel_panel_fini(struct intel_connector *connector) |
745 | { |
746 | struct intel_panel *panel = &connector->panel; |
747 | struct drm_display_mode *fixed_mode, *next; |
748 | |
749 | if (!IS_ERR_OR_NULL(ptr: panel->fixed_edid)) |
750 | drm_edid_free(drm_edid: panel->fixed_edid); |
751 | |
752 | intel_backlight_destroy(panel); |
753 | |
754 | intel_bios_fini_panel(panel); |
755 | |
756 | list_for_each_entry_safe(fixed_mode, next, &panel->fixed_modes, head) { |
757 | list_del(entry: &fixed_mode->head); |
758 | drm_mode_destroy(dev: connector->base.dev, mode: fixed_mode); |
759 | } |
760 | } |
761 | |