1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/ |
4 | * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> |
5 | */ |
6 | |
7 | #include <drm/drm_atomic.h> |
8 | #include <drm/drm_atomic_helper.h> |
9 | #include <drm/drm_crtc.h> |
10 | #include <drm/drm_gem_dma_helper.h> |
11 | #include <drm/drm_vblank.h> |
12 | |
13 | #include "tidss_crtc.h" |
14 | #include "tidss_dispc.h" |
15 | #include "tidss_drv.h" |
16 | #include "tidss_irq.h" |
17 | #include "tidss_plane.h" |
18 | |
19 | /* Page flip and frame done IRQs */ |
20 | |
21 | static void tidss_crtc_finish_page_flip(struct tidss_crtc *tcrtc) |
22 | { |
23 | struct drm_device *ddev = tcrtc->crtc.dev; |
24 | struct tidss_device *tidss = to_tidss(ddev); |
25 | struct drm_pending_vblank_event *event; |
26 | unsigned long flags; |
27 | bool busy; |
28 | |
29 | spin_lock_irqsave(&ddev->event_lock, flags); |
30 | |
31 | /* |
32 | * New settings are taken into use at VFP, and GO bit is cleared at |
33 | * the same time. This happens before the vertical blank interrupt. |
34 | * So there is a small change that the driver sets GO bit after VFP, but |
35 | * before vblank, and we have to check for that case here. |
36 | */ |
37 | busy = dispc_vp_go_busy(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport); |
38 | if (busy) { |
39 | spin_unlock_irqrestore(lock: &ddev->event_lock, flags); |
40 | return; |
41 | } |
42 | |
43 | event = tcrtc->event; |
44 | tcrtc->event = NULL; |
45 | |
46 | if (!event) { |
47 | spin_unlock_irqrestore(lock: &ddev->event_lock, flags); |
48 | return; |
49 | } |
50 | |
51 | drm_crtc_send_vblank_event(crtc: &tcrtc->crtc, e: event); |
52 | |
53 | spin_unlock_irqrestore(lock: &ddev->event_lock, flags); |
54 | |
55 | drm_crtc_vblank_put(crtc: &tcrtc->crtc); |
56 | } |
57 | |
58 | void tidss_crtc_vblank_irq(struct drm_crtc *crtc) |
59 | { |
60 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
61 | |
62 | drm_crtc_handle_vblank(crtc); |
63 | |
64 | tidss_crtc_finish_page_flip(tcrtc); |
65 | } |
66 | |
67 | void tidss_crtc_framedone_irq(struct drm_crtc *crtc) |
68 | { |
69 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
70 | |
71 | complete(&tcrtc->framedone_completion); |
72 | } |
73 | |
74 | void tidss_crtc_error_irq(struct drm_crtc *crtc, u64 irqstatus) |
75 | { |
76 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
77 | |
78 | dev_err_ratelimited(crtc->dev->dev, "CRTC%u SYNC LOST: (irq %llx)\n" , |
79 | tcrtc->hw_videoport, irqstatus); |
80 | } |
81 | |
82 | /* drm_crtc_helper_funcs */ |
83 | |
84 | static int tidss_crtc_atomic_check(struct drm_crtc *crtc, |
85 | struct drm_atomic_state *state) |
86 | { |
87 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, |
88 | crtc); |
89 | struct drm_device *ddev = crtc->dev; |
90 | struct tidss_device *tidss = to_tidss(ddev); |
91 | struct dispc_device *dispc = tidss->dispc; |
92 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
93 | u32 hw_videoport = tcrtc->hw_videoport; |
94 | const struct drm_display_mode *mode; |
95 | enum drm_mode_status ok; |
96 | |
97 | dev_dbg(ddev->dev, "%s\n" , __func__); |
98 | |
99 | if (!crtc_state->enable) |
100 | return 0; |
101 | |
102 | mode = &crtc_state->adjusted_mode; |
103 | |
104 | ok = dispc_vp_mode_valid(dispc, hw_videoport, mode); |
105 | if (ok != MODE_OK) { |
106 | dev_dbg(ddev->dev, "%s: bad mode: %ux%u pclk %u kHz\n" , |
107 | __func__, mode->hdisplay, mode->vdisplay, mode->clock); |
108 | return -EINVAL; |
109 | } |
110 | |
111 | return dispc_vp_bus_check(dispc, hw_videoport, state: crtc_state); |
112 | } |
113 | |
114 | /* |
115 | * This needs all affected planes to be present in the atomic |
116 | * state. The untouched planes are added to the state in |
117 | * tidss_atomic_check(). |
118 | */ |
119 | static void tidss_crtc_position_planes(struct tidss_device *tidss, |
120 | struct drm_crtc *crtc, |
121 | struct drm_crtc_state *old_state, |
122 | bool newmodeset) |
123 | { |
124 | struct drm_atomic_state *ostate = old_state->state; |
125 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
126 | struct drm_crtc_state *cstate = crtc->state; |
127 | int layer; |
128 | |
129 | if (!newmodeset && !cstate->zpos_changed && |
130 | !to_tidss_crtc_state(cstate)->plane_pos_changed) |
131 | return; |
132 | |
133 | for (layer = 0; layer < tidss->feat->num_planes; layer++) { |
134 | struct drm_plane_state *pstate; |
135 | struct drm_plane *plane; |
136 | bool layer_active = false; |
137 | int i; |
138 | |
139 | for_each_new_plane_in_state(ostate, plane, pstate, i) { |
140 | if (pstate->crtc != crtc || !pstate->visible) |
141 | continue; |
142 | |
143 | if (pstate->normalized_zpos == layer) { |
144 | layer_active = true; |
145 | break; |
146 | } |
147 | } |
148 | |
149 | if (layer_active) { |
150 | struct tidss_plane *tplane = to_tidss_plane(plane); |
151 | |
152 | dispc_ovr_set_plane(dispc: tidss->dispc, hw_plane: tplane->hw_plane_id, |
153 | hw_videoport: tcrtc->hw_videoport, |
154 | x: pstate->crtc_x, y: pstate->crtc_y, |
155 | layer); |
156 | } |
157 | dispc_ovr_enable_layer(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport, layer, |
158 | enable: layer_active); |
159 | } |
160 | } |
161 | |
162 | static void tidss_crtc_atomic_flush(struct drm_crtc *crtc, |
163 | struct drm_atomic_state *state) |
164 | { |
165 | struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, |
166 | crtc); |
167 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
168 | struct drm_device *ddev = crtc->dev; |
169 | struct tidss_device *tidss = to_tidss(ddev); |
170 | unsigned long flags; |
171 | |
172 | dev_dbg(ddev->dev, "%s: %s is %sactive, %s modeset, event %p\n" , |
173 | __func__, crtc->name, crtc->state->active ? "" : "not " , |
174 | drm_atomic_crtc_needs_modeset(crtc->state) ? "needs" : "doesn't need" , |
175 | crtc->state->event); |
176 | |
177 | /* |
178 | * Flush CRTC changes with go bit only if new modeset is not |
179 | * coming, so CRTC is enabled trough out the commit. |
180 | */ |
181 | if (drm_atomic_crtc_needs_modeset(state: crtc->state)) |
182 | return; |
183 | |
184 | /* If the GO bit is stuck we better quit here. */ |
185 | if (WARN_ON(dispc_vp_go_busy(tidss->dispc, tcrtc->hw_videoport))) |
186 | return; |
187 | |
188 | /* We should have event if CRTC is enabled through out this commit. */ |
189 | if (WARN_ON(!crtc->state->event)) |
190 | return; |
191 | |
192 | /* Write vp properties to HW if needed. */ |
193 | dispc_vp_setup(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport, state: crtc->state, newmodeset: false); |
194 | |
195 | /* Update plane positions if needed. */ |
196 | tidss_crtc_position_planes(tidss, crtc, old_state: old_crtc_state, newmodeset: false); |
197 | |
198 | WARN_ON(drm_crtc_vblank_get(crtc) != 0); |
199 | |
200 | spin_lock_irqsave(&ddev->event_lock, flags); |
201 | dispc_vp_go(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport); |
202 | |
203 | WARN_ON(tcrtc->event); |
204 | |
205 | tcrtc->event = crtc->state->event; |
206 | crtc->state->event = NULL; |
207 | |
208 | spin_unlock_irqrestore(lock: &ddev->event_lock, flags); |
209 | } |
210 | |
211 | static void tidss_crtc_atomic_enable(struct drm_crtc *crtc, |
212 | struct drm_atomic_state *state) |
213 | { |
214 | struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, |
215 | crtc); |
216 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
217 | struct drm_device *ddev = crtc->dev; |
218 | struct tidss_device *tidss = to_tidss(ddev); |
219 | const struct drm_display_mode *mode = &crtc->state->adjusted_mode; |
220 | unsigned long flags; |
221 | int r; |
222 | |
223 | dev_dbg(ddev->dev, "%s, event %p\n" , __func__, crtc->state->event); |
224 | |
225 | tidss_runtime_get(tidss); |
226 | |
227 | r = dispc_vp_set_clk_rate(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport, |
228 | rate: mode->clock * 1000); |
229 | if (r != 0) |
230 | return; |
231 | |
232 | r = dispc_vp_enable_clk(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport); |
233 | if (r != 0) |
234 | return; |
235 | |
236 | dispc_vp_setup(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport, state: crtc->state, newmodeset: true); |
237 | tidss_crtc_position_planes(tidss, crtc, old_state, newmodeset: true); |
238 | |
239 | /* Turn vertical blanking interrupt reporting on. */ |
240 | drm_crtc_vblank_on(crtc); |
241 | |
242 | dispc_vp_prepare(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport, state: crtc->state); |
243 | |
244 | dispc_vp_enable(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport, state: crtc->state); |
245 | |
246 | spin_lock_irqsave(&ddev->event_lock, flags); |
247 | |
248 | if (crtc->state->event) { |
249 | drm_crtc_send_vblank_event(crtc, e: crtc->state->event); |
250 | crtc->state->event = NULL; |
251 | } |
252 | |
253 | spin_unlock_irqrestore(lock: &ddev->event_lock, flags); |
254 | } |
255 | |
256 | static void tidss_crtc_atomic_disable(struct drm_crtc *crtc, |
257 | struct drm_atomic_state *state) |
258 | { |
259 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
260 | struct drm_device *ddev = crtc->dev; |
261 | struct tidss_device *tidss = to_tidss(ddev); |
262 | unsigned long flags; |
263 | |
264 | dev_dbg(ddev->dev, "%s, event %p\n" , __func__, crtc->state->event); |
265 | |
266 | reinit_completion(x: &tcrtc->framedone_completion); |
267 | |
268 | /* |
269 | * If a layer is left enabled when the videoport is disabled, and the |
270 | * vid pipeline that was used for the layer is taken into use on |
271 | * another videoport, the DSS will report sync lost issues. Disable all |
272 | * the layers here as a work-around. |
273 | */ |
274 | for (u32 layer = 0; layer < tidss->feat->num_planes; layer++) |
275 | dispc_ovr_enable_layer(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport, layer, |
276 | enable: false); |
277 | |
278 | dispc_vp_disable(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport); |
279 | |
280 | if (!wait_for_completion_timeout(x: &tcrtc->framedone_completion, |
281 | timeout: msecs_to_jiffies(m: 500))) |
282 | dev_err(tidss->dev, "Timeout waiting for framedone on crtc %d" , |
283 | tcrtc->hw_videoport); |
284 | |
285 | dispc_vp_unprepare(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport); |
286 | |
287 | spin_lock_irqsave(&ddev->event_lock, flags); |
288 | if (crtc->state->event) { |
289 | drm_crtc_send_vblank_event(crtc, e: crtc->state->event); |
290 | crtc->state->event = NULL; |
291 | } |
292 | spin_unlock_irqrestore(lock: &ddev->event_lock, flags); |
293 | |
294 | drm_crtc_vblank_off(crtc); |
295 | |
296 | dispc_vp_disable_clk(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport); |
297 | |
298 | tidss_runtime_put(tidss); |
299 | } |
300 | |
301 | static |
302 | enum drm_mode_status tidss_crtc_mode_valid(struct drm_crtc *crtc, |
303 | const struct drm_display_mode *mode) |
304 | { |
305 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
306 | struct drm_device *ddev = crtc->dev; |
307 | struct tidss_device *tidss = to_tidss(ddev); |
308 | |
309 | return dispc_vp_mode_valid(dispc: tidss->dispc, hw_videoport: tcrtc->hw_videoport, mode); |
310 | } |
311 | |
312 | static const struct drm_crtc_helper_funcs tidss_crtc_helper_funcs = { |
313 | .atomic_check = tidss_crtc_atomic_check, |
314 | .atomic_flush = tidss_crtc_atomic_flush, |
315 | .atomic_enable = tidss_crtc_atomic_enable, |
316 | .atomic_disable = tidss_crtc_atomic_disable, |
317 | |
318 | .mode_valid = tidss_crtc_mode_valid, |
319 | }; |
320 | |
321 | /* drm_crtc_funcs */ |
322 | |
323 | static int tidss_crtc_enable_vblank(struct drm_crtc *crtc) |
324 | { |
325 | struct drm_device *ddev = crtc->dev; |
326 | struct tidss_device *tidss = to_tidss(ddev); |
327 | |
328 | dev_dbg(ddev->dev, "%s\n" , __func__); |
329 | |
330 | tidss_runtime_get(tidss); |
331 | |
332 | tidss_irq_enable_vblank(crtc); |
333 | |
334 | return 0; |
335 | } |
336 | |
337 | static void tidss_crtc_disable_vblank(struct drm_crtc *crtc) |
338 | { |
339 | struct drm_device *ddev = crtc->dev; |
340 | struct tidss_device *tidss = to_tidss(ddev); |
341 | |
342 | dev_dbg(ddev->dev, "%s\n" , __func__); |
343 | |
344 | tidss_irq_disable_vblank(crtc); |
345 | |
346 | tidss_runtime_put(tidss); |
347 | } |
348 | |
349 | static void tidss_crtc_reset(struct drm_crtc *crtc) |
350 | { |
351 | struct tidss_crtc_state *tcrtc; |
352 | |
353 | if (crtc->state) |
354 | __drm_atomic_helper_crtc_destroy_state(state: crtc->state); |
355 | |
356 | kfree(objp: crtc->state); |
357 | |
358 | tcrtc = kzalloc(size: sizeof(*tcrtc), GFP_KERNEL); |
359 | if (!tcrtc) { |
360 | crtc->state = NULL; |
361 | return; |
362 | } |
363 | |
364 | __drm_atomic_helper_crtc_reset(crtc, state: &tcrtc->base); |
365 | } |
366 | |
367 | static struct drm_crtc_state *tidss_crtc_duplicate_state(struct drm_crtc *crtc) |
368 | { |
369 | struct tidss_crtc_state *state, *current_state; |
370 | |
371 | if (WARN_ON(!crtc->state)) |
372 | return NULL; |
373 | |
374 | current_state = to_tidss_crtc_state(crtc->state); |
375 | |
376 | state = kmalloc(size: sizeof(*state), GFP_KERNEL); |
377 | if (!state) |
378 | return NULL; |
379 | |
380 | __drm_atomic_helper_crtc_duplicate_state(crtc, state: &state->base); |
381 | |
382 | state->plane_pos_changed = false; |
383 | |
384 | state->bus_format = current_state->bus_format; |
385 | state->bus_flags = current_state->bus_flags; |
386 | |
387 | return &state->base; |
388 | } |
389 | |
390 | static void tidss_crtc_destroy(struct drm_crtc *crtc) |
391 | { |
392 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
393 | |
394 | drm_crtc_cleanup(crtc); |
395 | kfree(objp: tcrtc); |
396 | } |
397 | |
398 | static const struct drm_crtc_funcs tidss_crtc_funcs = { |
399 | .reset = tidss_crtc_reset, |
400 | .destroy = tidss_crtc_destroy, |
401 | .set_config = drm_atomic_helper_set_config, |
402 | .page_flip = drm_atomic_helper_page_flip, |
403 | .atomic_duplicate_state = tidss_crtc_duplicate_state, |
404 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, |
405 | .enable_vblank = tidss_crtc_enable_vblank, |
406 | .disable_vblank = tidss_crtc_disable_vblank, |
407 | }; |
408 | |
409 | struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss, |
410 | u32 hw_videoport, |
411 | struct drm_plane *primary) |
412 | { |
413 | struct tidss_crtc *tcrtc; |
414 | struct drm_crtc *crtc; |
415 | unsigned int gamma_lut_size = 0; |
416 | bool has_ctm = tidss->feat->vp_feat.color.has_ctm; |
417 | int ret; |
418 | |
419 | tcrtc = kzalloc(size: sizeof(*tcrtc), GFP_KERNEL); |
420 | if (!tcrtc) |
421 | return ERR_PTR(error: -ENOMEM); |
422 | |
423 | tcrtc->hw_videoport = hw_videoport; |
424 | init_completion(x: &tcrtc->framedone_completion); |
425 | |
426 | crtc = &tcrtc->crtc; |
427 | |
428 | ret = drm_crtc_init_with_planes(dev: &tidss->ddev, crtc, primary, |
429 | NULL, funcs: &tidss_crtc_funcs, NULL); |
430 | if (ret < 0) { |
431 | kfree(objp: tcrtc); |
432 | return ERR_PTR(error: ret); |
433 | } |
434 | |
435 | drm_crtc_helper_add(crtc, funcs: &tidss_crtc_helper_funcs); |
436 | |
437 | /* |
438 | * The dispc gamma functions adapt to what ever size we ask |
439 | * from it no matter what HW supports. X-server assumes 256 |
440 | * element gamma tables so lets use that. |
441 | */ |
442 | if (tidss->feat->vp_feat.color.gamma_size) |
443 | gamma_lut_size = 256; |
444 | |
445 | drm_crtc_enable_color_mgmt(crtc, degamma_lut_size: 0, has_ctm, gamma_lut_size); |
446 | if (gamma_lut_size) |
447 | drm_mode_crtc_set_gamma_size(crtc, gamma_size: gamma_lut_size); |
448 | |
449 | return tcrtc; |
450 | } |
451 | |