1 | /* |
2 | * Copyright 2018 Red Hat Inc. |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person obtaining a |
5 | * copy of this software and associated documentation files (the "Software"), |
6 | * to deal in the Software without restriction, including without limitation |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
8 | * and/or sell copies of the Software, and to permit persons to whom the |
9 | * Software is furnished to do so, subject to the following conditions: |
10 | * |
11 | * The above copyright notice and this permission notice shall be included in |
12 | * all copies or substantial portions of the Software. |
13 | * |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
20 | * OTHER DEALINGS IN THE SOFTWARE. |
21 | */ |
22 | #include "head.h" |
23 | #include "base.h" |
24 | #include "core.h" |
25 | #include "curs.h" |
26 | #include "ovly.h" |
27 | #include "crc.h" |
28 | |
29 | #include <nvif/class.h> |
30 | #include <nvif/event.h> |
31 | #include <nvif/cl0046.h> |
32 | |
33 | #include <drm/drm_atomic.h> |
34 | #include <drm/drm_atomic_helper.h> |
35 | #include <drm/drm_edid.h> |
36 | #include <drm/drm_vblank.h> |
37 | #include "nouveau_connector.h" |
38 | |
39 | void |
40 | nv50_head_flush_clr(struct nv50_head *head, |
41 | struct nv50_head_atom *asyh, bool flush) |
42 | { |
43 | union nv50_head_atom_mask clr = { |
44 | .mask = asyh->clr.mask & ~(flush ? 0 : asyh->set.mask), |
45 | }; |
46 | if (clr.crc) nv50_crc_atomic_clr(head); |
47 | if (clr.olut) head->func->olut_clr(head); |
48 | if (clr.core) head->func->core_clr(head); |
49 | if (clr.curs) head->func->curs_clr(head); |
50 | } |
51 | |
52 | void |
53 | nv50_head_flush_set_wndw(struct nv50_head *head, struct nv50_head_atom *asyh) |
54 | { |
55 | if (asyh->set.curs ) head->func->curs_set(head, asyh); |
56 | if (asyh->set.olut ) { |
57 | asyh->olut.offset = nv50_lut_load(&head->olut, |
58 | buffer: asyh->olut.buffer, |
59 | asyh->state.gamma_lut, |
60 | asyh->olut.load); |
61 | head->func->olut_set(head, asyh); |
62 | } |
63 | } |
64 | |
65 | void |
66 | nv50_head_flush_set(struct nv50_head *head, struct nv50_head_atom *asyh) |
67 | { |
68 | if (asyh->set.view ) head->func->view (head, asyh); |
69 | if (asyh->set.mode ) head->func->mode (head, asyh); |
70 | if (asyh->set.core ) head->func->core_set(head, asyh); |
71 | if (asyh->set.base ) head->func->base (head, asyh); |
72 | if (asyh->set.ovly ) head->func->ovly (head, asyh); |
73 | if (asyh->set.dither ) head->func->dither (head, asyh); |
74 | if (asyh->set.procamp) head->func->procamp (head, asyh); |
75 | if (asyh->set.crc ) nv50_crc_atomic_set (head, asyh); |
76 | if (asyh->set.or ) head->func->or (head, asyh); |
77 | } |
78 | |
79 | static void |
80 | nv50_head_atomic_check_procamp(struct nv50_head_atom *armh, |
81 | struct nv50_head_atom *asyh, |
82 | struct nouveau_conn_atom *asyc) |
83 | { |
84 | const int vib = asyc->procamp.color_vibrance - 100; |
85 | const int hue = asyc->procamp.vibrant_hue - 90; |
86 | const int adj = (vib > 0) ? 50 : 0; |
87 | asyh->procamp.sat.cos = ((vib * 2047 + adj) / 100) & 0xfff; |
88 | asyh->procamp.sat.sin = ((hue * 2047) / 100) & 0xfff; |
89 | asyh->set.procamp = true; |
90 | } |
91 | |
92 | static void |
93 | nv50_head_atomic_check_dither(struct nv50_head_atom *armh, |
94 | struct nv50_head_atom *asyh, |
95 | struct nouveau_conn_atom *asyc) |
96 | { |
97 | u32 mode = 0x00; |
98 | |
99 | if (asyc->dither.mode) { |
100 | if (asyc->dither.mode == DITHERING_MODE_AUTO) { |
101 | if (asyh->base.depth > asyh->or.bpc * 3) |
102 | mode = DITHERING_MODE_DYNAMIC2X2; |
103 | } else { |
104 | mode = asyc->dither.mode; |
105 | } |
106 | |
107 | if (asyc->dither.depth == DITHERING_DEPTH_AUTO) { |
108 | if (asyh->or.bpc >= 8) |
109 | mode |= DITHERING_DEPTH_8BPC; |
110 | } else { |
111 | mode |= asyc->dither.depth; |
112 | } |
113 | } |
114 | |
115 | asyh->dither.enable = NVVAL_GET(mode, NV507D, HEAD_SET_DITHER_CONTROL, ENABLE); |
116 | asyh->dither.bits = NVVAL_GET(mode, NV507D, HEAD_SET_DITHER_CONTROL, BITS); |
117 | asyh->dither.mode = NVVAL_GET(mode, NV507D, HEAD_SET_DITHER_CONTROL, MODE); |
118 | asyh->set.dither = true; |
119 | } |
120 | |
121 | static void |
122 | nv50_head_atomic_check_view(struct nv50_head_atom *armh, |
123 | struct nv50_head_atom *asyh, |
124 | struct nouveau_conn_atom *asyc) |
125 | { |
126 | struct drm_connector *connector = asyc->state.connector; |
127 | struct drm_display_mode *omode = &asyh->state.adjusted_mode; |
128 | struct drm_display_mode *umode = &asyh->state.mode; |
129 | int mode = asyc->scaler.mode; |
130 | struct edid *edid; |
131 | int umode_vdisplay, omode_hdisplay, omode_vdisplay; |
132 | |
133 | if (connector->edid_blob_ptr) |
134 | edid = (struct edid *)connector->edid_blob_ptr->data; |
135 | else |
136 | edid = NULL; |
137 | |
138 | if (!asyc->scaler.full) { |
139 | if (mode == DRM_MODE_SCALE_NONE) |
140 | omode = umode; |
141 | } else { |
142 | /* Non-EDID LVDS/eDP mode. */ |
143 | mode = DRM_MODE_SCALE_FULLSCREEN; |
144 | } |
145 | |
146 | /* For the user-specified mode, we must ignore doublescan and |
147 | * the like, but honor frame packing. |
148 | */ |
149 | umode_vdisplay = umode->vdisplay; |
150 | if ((umode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING) |
151 | umode_vdisplay += umode->vtotal; |
152 | asyh->view.iW = umode->hdisplay; |
153 | asyh->view.iH = umode_vdisplay; |
154 | /* For the output mode, we can just use the stock helper. */ |
155 | drm_mode_get_hv_timing(mode: omode, hdisplay: &omode_hdisplay, vdisplay: &omode_vdisplay); |
156 | asyh->view.oW = omode_hdisplay; |
157 | asyh->view.oH = omode_vdisplay; |
158 | |
159 | /* Add overscan compensation if necessary, will keep the aspect |
160 | * ratio the same as the backend mode unless overridden by the |
161 | * user setting both hborder and vborder properties. |
162 | */ |
163 | if ((asyc->scaler.underscan.mode == UNDERSCAN_ON || |
164 | (asyc->scaler.underscan.mode == UNDERSCAN_AUTO && |
165 | drm_detect_hdmi_monitor(edid)))) { |
166 | u32 bX = asyc->scaler.underscan.hborder; |
167 | u32 bY = asyc->scaler.underscan.vborder; |
168 | u32 r = (asyh->view.oH << 19) / asyh->view.oW; |
169 | |
170 | if (bX) { |
171 | asyh->view.oW -= (bX * 2); |
172 | if (bY) asyh->view.oH -= (bY * 2); |
173 | else asyh->view.oH = ((asyh->view.oW * r) + (r / 2)) >> 19; |
174 | } else { |
175 | asyh->view.oW -= (asyh->view.oW >> 4) + 32; |
176 | if (bY) asyh->view.oH -= (bY * 2); |
177 | else asyh->view.oH = ((asyh->view.oW * r) + (r / 2)) >> 19; |
178 | } |
179 | } |
180 | |
181 | /* Handle CENTER/ASPECT scaling, taking into account the areas |
182 | * removed already for overscan compensation. |
183 | */ |
184 | switch (mode) { |
185 | case DRM_MODE_SCALE_CENTER: |
186 | /* NOTE: This will cause scaling when the input is |
187 | * larger than the output. |
188 | */ |
189 | asyh->view.oW = min(asyh->view.iW, asyh->view.oW); |
190 | asyh->view.oH = min(asyh->view.iH, asyh->view.oH); |
191 | break; |
192 | case DRM_MODE_SCALE_ASPECT: |
193 | /* Determine whether the scaling should be on width or on |
194 | * height. This is done by comparing the aspect ratios of the |
195 | * sizes. If the output AR is larger than input AR, that means |
196 | * we want to change the width (letterboxed on the |
197 | * left/right), otherwise on the height (letterboxed on the |
198 | * top/bottom). |
199 | * |
200 | * E.g. 4:3 (1.333) AR image displayed on a 16:10 (1.6) AR |
201 | * screen will have letterboxes on the left/right. However a |
202 | * 16:9 (1.777) AR image on that same screen will have |
203 | * letterboxes on the top/bottom. |
204 | * |
205 | * inputAR = iW / iH; outputAR = oW / oH |
206 | * outputAR > inputAR is equivalent to oW * iH > iW * oH |
207 | */ |
208 | if (asyh->view.oW * asyh->view.iH > asyh->view.iW * asyh->view.oH) { |
209 | /* Recompute output width, i.e. left/right letterbox */ |
210 | u32 r = (asyh->view.iW << 19) / asyh->view.iH; |
211 | asyh->view.oW = ((asyh->view.oH * r) + (r / 2)) >> 19; |
212 | } else { |
213 | /* Recompute output height, i.e. top/bottom letterbox */ |
214 | u32 r = (asyh->view.iH << 19) / asyh->view.iW; |
215 | asyh->view.oH = ((asyh->view.oW * r) + (r / 2)) >> 19; |
216 | } |
217 | break; |
218 | default: |
219 | break; |
220 | } |
221 | |
222 | asyh->set.view = true; |
223 | } |
224 | |
225 | static int |
226 | nv50_head_atomic_check_lut(struct nv50_head *head, |
227 | struct nv50_head_atom *asyh) |
228 | { |
229 | struct drm_device *dev = head->base.base.dev; |
230 | struct drm_crtc *crtc = &head->base.base; |
231 | struct nv50_disp *disp = nv50_disp(dev); |
232 | struct nouveau_drm *drm = nouveau_drm(dev); |
233 | struct drm_property_blob *olut = asyh->state.gamma_lut, |
234 | *ilut = asyh->state.degamma_lut; |
235 | int size; |
236 | |
237 | /* Ensure that the ilut is valid */ |
238 | if (ilut) { |
239 | size = drm_color_lut_size(blob: ilut); |
240 | if (!head->func->ilut_check(size)) { |
241 | NV_ATOMIC(drm, "Invalid size %d for degamma on [CRTC:%d:%s]\n" , |
242 | size, crtc->base.id, crtc->name); |
243 | return -EINVAL; |
244 | } |
245 | } |
246 | |
247 | /* Determine whether core output LUT should be enabled. */ |
248 | if (olut) { |
249 | /* Check if any window(s) have stolen the core output LUT |
250 | * to as an input LUT for legacy gamma + I8 colour format. |
251 | */ |
252 | if (asyh->wndw.olut) { |
253 | /* If any window has stolen the core output LUT, |
254 | * all of them must. |
255 | */ |
256 | if (asyh->wndw.olut != asyh->wndw.mask) |
257 | return -EINVAL; |
258 | olut = NULL; |
259 | } |
260 | } |
261 | |
262 | if (!olut) { |
263 | if (!head->func->olut_identity) { |
264 | asyh->olut.handle = 0; |
265 | return 0; |
266 | } |
267 | size = 0; |
268 | } else { |
269 | size = drm_color_lut_size(blob: olut); |
270 | } |
271 | |
272 | if (!head->func->olut(head, asyh, size)) { |
273 | NV_ATOMIC(drm, "Invalid size %d for gamma on [CRTC:%d:%s]\n" , |
274 | size, crtc->base.id, crtc->name); |
275 | return -EINVAL; |
276 | } |
277 | asyh->olut.handle = disp->core->chan.vram.handle; |
278 | asyh->olut.buffer = !asyh->olut.buffer; |
279 | |
280 | return 0; |
281 | } |
282 | |
283 | static void |
284 | nv50_head_atomic_check_mode(struct nv50_head *head, struct nv50_head_atom *asyh) |
285 | { |
286 | struct drm_display_mode *mode = &asyh->state.adjusted_mode; |
287 | struct nv50_head_mode *m = &asyh->mode; |
288 | u32 blankus; |
289 | |
290 | drm_mode_set_crtcinfo(p: mode, CRTC_INTERLACE_HALVE_V | CRTC_STEREO_DOUBLE); |
291 | |
292 | /* |
293 | * DRM modes are defined in terms of a repeating interval |
294 | * starting with the active display area. The hardware modes |
295 | * are defined in terms of a repeating interval starting one |
296 | * unit (pixel or line) into the sync pulse. So, add bias. |
297 | */ |
298 | |
299 | m->h.active = mode->crtc_htotal; |
300 | m->h.synce = mode->crtc_hsync_end - mode->crtc_hsync_start - 1; |
301 | m->h.blanke = mode->crtc_hblank_end - mode->crtc_hsync_start - 1; |
302 | m->h.blanks = m->h.blanke + mode->crtc_hdisplay; |
303 | |
304 | m->v.active = mode->crtc_vtotal; |
305 | m->v.synce = mode->crtc_vsync_end - mode->crtc_vsync_start - 1; |
306 | m->v.blanke = mode->crtc_vblank_end - mode->crtc_vsync_start - 1; |
307 | m->v.blanks = m->v.blanke + mode->crtc_vdisplay; |
308 | |
309 | /*XXX: Safe underestimate, even "0" works */ |
310 | blankus = (m->v.active - mode->crtc_vdisplay - 2) * m->h.active; |
311 | blankus *= 1000; |
312 | blankus /= mode->crtc_clock; |
313 | m->v.blankus = blankus; |
314 | |
315 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) { |
316 | m->v.blank2e = m->v.active + m->v.blanke; |
317 | m->v.blank2s = m->v.blank2e + mode->crtc_vdisplay; |
318 | m->v.active = (m->v.active * 2) + 1; |
319 | m->interlace = true; |
320 | } else { |
321 | m->v.blank2e = 0; |
322 | m->v.blank2s = 1; |
323 | m->interlace = false; |
324 | } |
325 | m->clock = mode->crtc_clock; |
326 | |
327 | asyh->or.nhsync = !!(mode->flags & DRM_MODE_FLAG_NHSYNC); |
328 | asyh->or.nvsync = !!(mode->flags & DRM_MODE_FLAG_NVSYNC); |
329 | asyh->set.or = head->func->or != NULL; |
330 | asyh->set.mode = true; |
331 | } |
332 | |
333 | static int |
334 | nv50_head_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) |
335 | { |
336 | struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, |
337 | crtc); |
338 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, |
339 | crtc); |
340 | struct nouveau_drm *drm = nouveau_drm(crtc->dev); |
341 | struct nv50_head *head = nv50_head(crtc); |
342 | struct nv50_head_atom *armh = nv50_head_atom(old_crtc_state); |
343 | struct nv50_head_atom *asyh = nv50_head_atom(crtc_state); |
344 | struct nouveau_conn_atom *asyc = NULL; |
345 | struct drm_connector_state *conns; |
346 | struct drm_connector *conn; |
347 | int i, ret; |
348 | bool check_lut = asyh->state.color_mgmt_changed || |
349 | memcmp(p: &armh->wndw, q: &asyh->wndw, size: sizeof(asyh->wndw)); |
350 | |
351 | NV_ATOMIC(drm, "%s atomic_check %d\n" , crtc->name, asyh->state.active); |
352 | |
353 | if (check_lut) { |
354 | ret = nv50_head_atomic_check_lut(head, asyh); |
355 | if (ret) |
356 | return ret; |
357 | } |
358 | |
359 | if (asyh->state.active) { |
360 | for_each_new_connector_in_state(asyh->state.state, conn, conns, i) { |
361 | if (conns->crtc == crtc) { |
362 | asyc = nouveau_conn_atom(conns); |
363 | break; |
364 | } |
365 | } |
366 | |
367 | if (armh->state.active) { |
368 | if (asyc) { |
369 | if (asyh->state.mode_changed) |
370 | asyc->set.scaler = true; |
371 | if (armh->base.depth != asyh->base.depth) |
372 | asyc->set.dither = true; |
373 | } |
374 | } else { |
375 | if (asyc) |
376 | asyc->set.mask = ~0; |
377 | asyh->set.mask = ~0; |
378 | asyh->set.or = head->func->or != NULL; |
379 | } |
380 | |
381 | if (asyh->state.mode_changed || asyh->state.connectors_changed) |
382 | nv50_head_atomic_check_mode(head, asyh); |
383 | |
384 | if (check_lut) |
385 | asyh->olut.visible = asyh->olut.handle != 0; |
386 | |
387 | if (asyc) { |
388 | if (asyc->set.scaler) |
389 | nv50_head_atomic_check_view(armh, asyh, asyc); |
390 | if (asyc->set.dither) |
391 | nv50_head_atomic_check_dither(armh, asyh, asyc); |
392 | if (asyc->set.procamp) |
393 | nv50_head_atomic_check_procamp(armh, asyh, asyc); |
394 | } |
395 | |
396 | if (head->func->core_calc) { |
397 | head->func->core_calc(head, asyh); |
398 | if (!asyh->core.visible) |
399 | asyh->olut.visible = false; |
400 | } |
401 | |
402 | asyh->set.base = armh->base.cpp != asyh->base.cpp; |
403 | asyh->set.ovly = armh->ovly.cpp != asyh->ovly.cpp; |
404 | } else { |
405 | asyh->olut.visible = false; |
406 | asyh->core.visible = false; |
407 | asyh->curs.visible = false; |
408 | asyh->base.cpp = 0; |
409 | asyh->ovly.cpp = 0; |
410 | } |
411 | |
412 | if (!drm_atomic_crtc_needs_modeset(state: &asyh->state)) { |
413 | if (asyh->core.visible) { |
414 | if (memcmp(p: &armh->core, q: &asyh->core, size: sizeof(asyh->core))) |
415 | asyh->set.core = true; |
416 | } else |
417 | if (armh->core.visible) { |
418 | asyh->clr.core = true; |
419 | } |
420 | |
421 | if (asyh->curs.visible) { |
422 | if (memcmp(p: &armh->curs, q: &asyh->curs, size: sizeof(asyh->curs))) |
423 | asyh->set.curs = true; |
424 | } else |
425 | if (armh->curs.visible) { |
426 | asyh->clr.curs = true; |
427 | } |
428 | |
429 | if (asyh->olut.visible) { |
430 | if (memcmp(p: &armh->olut, q: &asyh->olut, size: sizeof(asyh->olut))) |
431 | asyh->set.olut = true; |
432 | } else |
433 | if (armh->olut.visible) { |
434 | asyh->clr.olut = true; |
435 | } |
436 | } else { |
437 | asyh->clr.olut = armh->olut.visible; |
438 | asyh->clr.core = armh->core.visible; |
439 | asyh->clr.curs = armh->curs.visible; |
440 | asyh->set.olut = asyh->olut.visible; |
441 | asyh->set.core = asyh->core.visible; |
442 | asyh->set.curs = asyh->curs.visible; |
443 | } |
444 | |
445 | ret = nv50_crc_atomic_check_head(head, asyh, armh); |
446 | if (ret) |
447 | return ret; |
448 | |
449 | if (asyh->clr.mask || asyh->set.mask) |
450 | nv50_atom(asyh->state.state)->lock_core = true; |
451 | return 0; |
452 | } |
453 | |
454 | static const struct drm_crtc_helper_funcs |
455 | nv50_head_help = { |
456 | .atomic_check = nv50_head_atomic_check, |
457 | .get_scanout_position = nouveau_display_scanoutpos, |
458 | }; |
459 | |
460 | static void |
461 | nv50_head_atomic_destroy_state(struct drm_crtc *crtc, |
462 | struct drm_crtc_state *state) |
463 | { |
464 | struct nv50_head_atom *asyh = nv50_head_atom(state); |
465 | __drm_atomic_helper_crtc_destroy_state(state: &asyh->state); |
466 | kfree(objp: asyh); |
467 | } |
468 | |
469 | static struct drm_crtc_state * |
470 | nv50_head_atomic_duplicate_state(struct drm_crtc *crtc) |
471 | { |
472 | struct nv50_head_atom *armh = nv50_head_atom(crtc->state); |
473 | struct nv50_head_atom *asyh; |
474 | if (!(asyh = kmalloc(size: sizeof(*asyh), GFP_KERNEL))) |
475 | return NULL; |
476 | __drm_atomic_helper_crtc_duplicate_state(crtc, state: &asyh->state); |
477 | asyh->wndw = armh->wndw; |
478 | asyh->view = armh->view; |
479 | asyh->mode = armh->mode; |
480 | asyh->olut = armh->olut; |
481 | asyh->core = armh->core; |
482 | asyh->curs = armh->curs; |
483 | asyh->base = armh->base; |
484 | asyh->ovly = armh->ovly; |
485 | asyh->dither = armh->dither; |
486 | asyh->procamp = armh->procamp; |
487 | asyh->crc = armh->crc; |
488 | asyh->or = armh->or; |
489 | asyh->dp = armh->dp; |
490 | asyh->clr.mask = 0; |
491 | asyh->set.mask = 0; |
492 | return &asyh->state; |
493 | } |
494 | |
495 | static void |
496 | nv50_head_reset(struct drm_crtc *crtc) |
497 | { |
498 | struct nv50_head_atom *asyh; |
499 | |
500 | if (WARN_ON(!(asyh = kzalloc(sizeof(*asyh), GFP_KERNEL)))) |
501 | return; |
502 | |
503 | if (crtc->state) |
504 | nv50_head_atomic_destroy_state(crtc, state: crtc->state); |
505 | |
506 | __drm_atomic_helper_crtc_reset(crtc, state: &asyh->state); |
507 | } |
508 | |
509 | static int |
510 | nv50_head_late_register(struct drm_crtc *crtc) |
511 | { |
512 | return nv50_head_crc_late_register(nv50_head(crtc)); |
513 | } |
514 | |
515 | static void |
516 | nv50_head_destroy(struct drm_crtc *crtc) |
517 | { |
518 | struct nv50_head *head = nv50_head(crtc); |
519 | |
520 | nvif_event_dtor(&head->base.vblank); |
521 | nvif_head_dtor(&head->base.head); |
522 | nv50_lut_fini(&head->olut); |
523 | drm_crtc_cleanup(crtc); |
524 | kfree(objp: head); |
525 | } |
526 | |
527 | static const struct drm_crtc_funcs |
528 | nv50_head_func = { |
529 | .reset = nv50_head_reset, |
530 | .destroy = nv50_head_destroy, |
531 | .set_config = drm_atomic_helper_set_config, |
532 | .page_flip = drm_atomic_helper_page_flip, |
533 | .atomic_duplicate_state = nv50_head_atomic_duplicate_state, |
534 | .atomic_destroy_state = nv50_head_atomic_destroy_state, |
535 | .enable_vblank = nouveau_display_vblank_enable, |
536 | .disable_vblank = nouveau_display_vblank_disable, |
537 | .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, |
538 | .late_register = nv50_head_late_register, |
539 | }; |
540 | |
541 | static const struct drm_crtc_funcs |
542 | nvd9_head_func = { |
543 | .reset = nv50_head_reset, |
544 | .destroy = nv50_head_destroy, |
545 | .set_config = drm_atomic_helper_set_config, |
546 | .page_flip = drm_atomic_helper_page_flip, |
547 | .atomic_duplicate_state = nv50_head_atomic_duplicate_state, |
548 | .atomic_destroy_state = nv50_head_atomic_destroy_state, |
549 | .enable_vblank = nouveau_display_vblank_enable, |
550 | .disable_vblank = nouveau_display_vblank_disable, |
551 | .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, |
552 | .verify_crc_source = nv50_crc_verify_source, |
553 | .get_crc_sources = nv50_crc_get_sources, |
554 | .set_crc_source = nv50_crc_set_source, |
555 | .late_register = nv50_head_late_register, |
556 | }; |
557 | |
558 | static int |
559 | nv50_head_vblank_handler(struct nvif_event *event, void *repv, u32 repc) |
560 | { |
561 | struct nouveau_crtc *nv_crtc = container_of(event, struct nouveau_crtc, vblank); |
562 | |
563 | if (drm_crtc_handle_vblank(crtc: &nv_crtc->base)) |
564 | nv50_crc_handle_vblank(nv50_head(&nv_crtc->base)); |
565 | |
566 | return NVIF_EVENT_KEEP; |
567 | } |
568 | |
569 | struct nv50_head * |
570 | nv50_head_create(struct drm_device *dev, int index) |
571 | { |
572 | struct nouveau_drm *drm = nouveau_drm(dev); |
573 | struct nv50_disp *disp = nv50_disp(dev); |
574 | struct nv50_head *head; |
575 | struct nv50_wndw *base, *ovly, *curs; |
576 | struct nouveau_crtc *nv_crtc; |
577 | struct drm_crtc *crtc; |
578 | const struct drm_crtc_funcs *funcs; |
579 | int ret; |
580 | |
581 | head = kzalloc(size: sizeof(*head), GFP_KERNEL); |
582 | if (!head) |
583 | return ERR_PTR(error: -ENOMEM); |
584 | |
585 | head->func = disp->core->func->head; |
586 | head->base.index = index; |
587 | |
588 | if (disp->disp->object.oclass < GF110_DISP) |
589 | funcs = &nv50_head_func; |
590 | else |
591 | funcs = &nvd9_head_func; |
592 | |
593 | if (disp->disp->object.oclass < GV100_DISP) { |
594 | ret = nv50_base_new(drm, head: head->base.index, &base); |
595 | ret = nv50_ovly_new(drm, head: head->base.index, &ovly); |
596 | } else { |
597 | ret = nv50_wndw_new(drm, DRM_PLANE_TYPE_PRIMARY, |
598 | index: head->base.index * 2 + 0, &base); |
599 | ret = nv50_wndw_new(drm, DRM_PLANE_TYPE_OVERLAY, |
600 | index: head->base.index * 2 + 1, &ovly); |
601 | } |
602 | if (ret == 0) |
603 | ret = nv50_curs_new(drm, head: head->base.index, &curs); |
604 | if (ret) { |
605 | kfree(objp: head); |
606 | return ERR_PTR(error: ret); |
607 | } |
608 | |
609 | nv_crtc = &head->base; |
610 | crtc = &nv_crtc->base; |
611 | drm_crtc_init_with_planes(dev, crtc, primary: &base->plane, cursor: &curs->plane, |
612 | funcs, name: "head-%d" , head->base.index); |
613 | drm_crtc_helper_add(crtc, funcs: &nv50_head_help); |
614 | /* Keep the legacy gamma size at 256 to avoid compatibility issues */ |
615 | drm_mode_crtc_set_gamma_size(crtc, gamma_size: 256); |
616 | drm_crtc_enable_color_mgmt(crtc, degamma_lut_size: base->func->ilut_size, |
617 | has_ctm: disp->disp->object.oclass >= GF110_DISP, |
618 | gamma_lut_size: head->func->olut_size); |
619 | |
620 | if (head->func->olut_set) { |
621 | ret = nv50_lut_init(disp, &drm->client.mmu, &head->olut); |
622 | if (ret) { |
623 | nv50_head_destroy(crtc); |
624 | return ERR_PTR(error: ret); |
625 | } |
626 | } |
627 | |
628 | ret = nvif_head_ctor(disp->disp, head->base.base.name, head->base.index, &head->base.head); |
629 | if (ret) |
630 | return ERR_PTR(error: ret); |
631 | |
632 | ret = nvif_head_vblank_event_ctor(&head->base.head, "kmsVbl" , nv50_head_vblank_handler, |
633 | false, &nv_crtc->vblank); |
634 | if (ret) |
635 | return ERR_PTR(error: ret); |
636 | |
637 | return head; |
638 | } |
639 | |