Commit | Line | Data |
---|---|---|
32a1795f JS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
9410113f | 3 | * Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/ |
32a1795f JS |
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> | |
4a83c26a | 10 | #include <drm/drm_gem_dma_helper.h> |
32a1795f JS |
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" | |
b33b5474 | 17 | #include "tidss_plane.h" |
32a1795f JS |
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; | |
02bb1317 | 24 | struct tidss_device *tidss = to_tidss(ddev); |
32a1795f JS |
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(tidss->dispc, tcrtc->hw_videoport); | |
38 | if (busy) { | |
39 | spin_unlock_irqrestore(&ddev->event_lock, flags); | |
40 | return; | |
41 | } | |
42 | ||
43 | event = tcrtc->event; | |
44 | tcrtc->event = NULL; | |
45 | ||
46 | if (!event) { | |
47 | spin_unlock_irqrestore(&ddev->event_lock, flags); | |
48 | return; | |
49 | } | |
50 | ||
51 | drm_crtc_send_vblank_event(&tcrtc->crtc, event); | |
52 | ||
53 | spin_unlock_irqrestore(&ddev->event_lock, flags); | |
54 | ||
55 | drm_crtc_vblank_put(&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, | |
29b77ad7 | 85 | struct drm_atomic_state *state) |
32a1795f | 86 | { |
29b77ad7 MR |
87 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, |
88 | crtc); | |
32a1795f | 89 | struct drm_device *ddev = crtc->dev; |
02bb1317 | 90 | struct tidss_device *tidss = to_tidss(ddev); |
32a1795f JS |
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 | ||
29b77ad7 | 99 | if (!crtc_state->enable) |
32a1795f JS |
100 | return 0; |
101 | ||
29b77ad7 | 102 | mode = &crtc_state->adjusted_mode; |
32a1795f JS |
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 | ||
29b77ad7 | 111 | return dispc_vp_bus_check(dispc, hw_videoport, crtc_state); |
32a1795f JS |
112 | } |
113 | ||
b33b5474 JS |
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(tidss->dispc, tplane->hw_plane_id, | |
153 | tcrtc->hw_videoport, | |
154 | pstate->crtc_x, pstate->crtc_y, | |
155 | layer); | |
156 | } | |
157 | dispc_ovr_enable_layer(tidss->dispc, tcrtc->hw_videoport, layer, | |
158 | layer_active); | |
159 | } | |
160 | } | |
161 | ||
32a1795f | 162 | static void tidss_crtc_atomic_flush(struct drm_crtc *crtc, |
f6ebe9f9 | 163 | struct drm_atomic_state *state) |
32a1795f | 164 | { |
f6ebe9f9 MR |
165 | struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, |
166 | crtc); | |
32a1795f JS |
167 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
168 | struct drm_device *ddev = crtc->dev; | |
02bb1317 | 169 | struct tidss_device *tidss = to_tidss(ddev); |
32a1795f JS |
170 | unsigned long flags; |
171 | ||
172 | dev_dbg(ddev->dev, | |
173 | "%s: %s enabled %d, needs modeset %d, event %p\n", __func__, | |
174 | crtc->name, drm_atomic_crtc_needs_modeset(crtc->state), | |
175 | crtc->state->enable, crtc->state->event); | |
176 | ||
177 | /* There is nothing to do if CRTC is not going to be enabled. */ | |
178 | if (!crtc->state->enable) | |
179 | return; | |
180 | ||
181 | /* | |
182 | * Flush CRTC changes with go bit only if new modeset is not | |
183 | * coming, so CRTC is enabled trough out the commit. | |
184 | */ | |
185 | if (drm_atomic_crtc_needs_modeset(crtc->state)) | |
186 | return; | |
187 | ||
188 | /* If the GO bit is stuck we better quit here. */ | |
189 | if (WARN_ON(dispc_vp_go_busy(tidss->dispc, tcrtc->hw_videoport))) | |
190 | return; | |
191 | ||
192 | /* We should have event if CRTC is enabled through out this commit. */ | |
193 | if (WARN_ON(!crtc->state->event)) | |
194 | return; | |
195 | ||
196 | /* Write vp properties to HW if needed. */ | |
197 | dispc_vp_setup(tidss->dispc, tcrtc->hw_videoport, crtc->state, false); | |
198 | ||
b33b5474 JS |
199 | /* Update plane positions if needed. */ |
200 | tidss_crtc_position_planes(tidss, crtc, old_crtc_state, false); | |
201 | ||
32a1795f JS |
202 | WARN_ON(drm_crtc_vblank_get(crtc) != 0); |
203 | ||
204 | spin_lock_irqsave(&ddev->event_lock, flags); | |
205 | dispc_vp_go(tidss->dispc, tcrtc->hw_videoport); | |
206 | ||
207 | WARN_ON(tcrtc->event); | |
208 | ||
209 | tcrtc->event = crtc->state->event; | |
210 | crtc->state->event = NULL; | |
211 | ||
212 | spin_unlock_irqrestore(&ddev->event_lock, flags); | |
213 | } | |
214 | ||
215 | static void tidss_crtc_atomic_enable(struct drm_crtc *crtc, | |
351f950d | 216 | struct drm_atomic_state *state) |
32a1795f | 217 | { |
351f950d MR |
218 | struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, |
219 | crtc); | |
32a1795f JS |
220 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); |
221 | struct drm_device *ddev = crtc->dev; | |
02bb1317 | 222 | struct tidss_device *tidss = to_tidss(ddev); |
32a1795f JS |
223 | const struct drm_display_mode *mode = &crtc->state->adjusted_mode; |
224 | unsigned long flags; | |
225 | int r; | |
226 | ||
227 | dev_dbg(ddev->dev, "%s, event %p\n", __func__, crtc->state->event); | |
228 | ||
229 | tidss_runtime_get(tidss); | |
230 | ||
231 | r = dispc_vp_set_clk_rate(tidss->dispc, tcrtc->hw_videoport, | |
232 | mode->clock * 1000); | |
233 | if (r != 0) | |
234 | return; | |
235 | ||
236 | r = dispc_vp_enable_clk(tidss->dispc, tcrtc->hw_videoport); | |
237 | if (r != 0) | |
238 | return; | |
239 | ||
240 | dispc_vp_setup(tidss->dispc, tcrtc->hw_videoport, crtc->state, true); | |
b33b5474 | 241 | tidss_crtc_position_planes(tidss, crtc, old_state, true); |
32a1795f JS |
242 | |
243 | /* Turn vertical blanking interrupt reporting on. */ | |
244 | drm_crtc_vblank_on(crtc); | |
245 | ||
246 | dispc_vp_prepare(tidss->dispc, tcrtc->hw_videoport, crtc->state); | |
247 | ||
248 | dispc_vp_enable(tidss->dispc, tcrtc->hw_videoport, crtc->state); | |
249 | ||
250 | spin_lock_irqsave(&ddev->event_lock, flags); | |
251 | ||
252 | if (crtc->state->event) { | |
253 | drm_crtc_send_vblank_event(crtc, crtc->state->event); | |
254 | crtc->state->event = NULL; | |
255 | } | |
256 | ||
257 | spin_unlock_irqrestore(&ddev->event_lock, flags); | |
258 | } | |
259 | ||
260 | static void tidss_crtc_atomic_disable(struct drm_crtc *crtc, | |
351f950d | 261 | struct drm_atomic_state *state) |
32a1795f JS |
262 | { |
263 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); | |
264 | struct drm_device *ddev = crtc->dev; | |
02bb1317 | 265 | struct tidss_device *tidss = to_tidss(ddev); |
32a1795f JS |
266 | unsigned long flags; |
267 | ||
268 | dev_dbg(ddev->dev, "%s, event %p\n", __func__, crtc->state->event); | |
269 | ||
270 | reinit_completion(&tcrtc->framedone_completion); | |
271 | ||
272 | dispc_vp_disable(tidss->dispc, tcrtc->hw_videoport); | |
273 | ||
274 | if (!wait_for_completion_timeout(&tcrtc->framedone_completion, | |
275 | msecs_to_jiffies(500))) | |
276 | dev_err(tidss->dev, "Timeout waiting for framedone on crtc %d", | |
277 | tcrtc->hw_videoport); | |
278 | ||
279 | dispc_vp_unprepare(tidss->dispc, tcrtc->hw_videoport); | |
280 | ||
281 | spin_lock_irqsave(&ddev->event_lock, flags); | |
282 | if (crtc->state->event) { | |
283 | drm_crtc_send_vblank_event(crtc, crtc->state->event); | |
284 | crtc->state->event = NULL; | |
285 | } | |
286 | spin_unlock_irqrestore(&ddev->event_lock, flags); | |
287 | ||
288 | drm_crtc_vblank_off(crtc); | |
289 | ||
290 | dispc_vp_disable_clk(tidss->dispc, tcrtc->hw_videoport); | |
291 | ||
292 | tidss_runtime_put(tidss); | |
293 | } | |
294 | ||
295 | static | |
296 | enum drm_mode_status tidss_crtc_mode_valid(struct drm_crtc *crtc, | |
297 | const struct drm_display_mode *mode) | |
298 | { | |
299 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); | |
300 | struct drm_device *ddev = crtc->dev; | |
02bb1317 | 301 | struct tidss_device *tidss = to_tidss(ddev); |
32a1795f JS |
302 | |
303 | return dispc_vp_mode_valid(tidss->dispc, tcrtc->hw_videoport, mode); | |
304 | } | |
305 | ||
306 | static const struct drm_crtc_helper_funcs tidss_crtc_helper_funcs = { | |
307 | .atomic_check = tidss_crtc_atomic_check, | |
308 | .atomic_flush = tidss_crtc_atomic_flush, | |
309 | .atomic_enable = tidss_crtc_atomic_enable, | |
310 | .atomic_disable = tidss_crtc_atomic_disable, | |
311 | ||
312 | .mode_valid = tidss_crtc_mode_valid, | |
313 | }; | |
314 | ||
315 | /* drm_crtc_funcs */ | |
316 | ||
317 | static int tidss_crtc_enable_vblank(struct drm_crtc *crtc) | |
318 | { | |
319 | struct drm_device *ddev = crtc->dev; | |
02bb1317 | 320 | struct tidss_device *tidss = to_tidss(ddev); |
32a1795f JS |
321 | |
322 | dev_dbg(ddev->dev, "%s\n", __func__); | |
323 | ||
324 | tidss_runtime_get(tidss); | |
325 | ||
326 | tidss_irq_enable_vblank(crtc); | |
327 | ||
328 | return 0; | |
329 | } | |
330 | ||
331 | static void tidss_crtc_disable_vblank(struct drm_crtc *crtc) | |
332 | { | |
333 | struct drm_device *ddev = crtc->dev; | |
02bb1317 | 334 | struct tidss_device *tidss = to_tidss(ddev); |
32a1795f JS |
335 | |
336 | dev_dbg(ddev->dev, "%s\n", __func__); | |
337 | ||
338 | tidss_irq_disable_vblank(crtc); | |
339 | ||
340 | tidss_runtime_put(tidss); | |
341 | } | |
342 | ||
343 | static void tidss_crtc_reset(struct drm_crtc *crtc) | |
344 | { | |
345 | struct tidss_crtc_state *tcrtc; | |
346 | ||
347 | if (crtc->state) | |
348 | __drm_atomic_helper_crtc_destroy_state(crtc->state); | |
349 | ||
350 | kfree(crtc->state); | |
351 | ||
352 | tcrtc = kzalloc(sizeof(*tcrtc), GFP_KERNEL); | |
353 | if (!tcrtc) { | |
354 | crtc->state = NULL; | |
355 | return; | |
356 | } | |
357 | ||
51f644b4 | 358 | __drm_atomic_helper_crtc_reset(crtc, &tcrtc->base); |
32a1795f JS |
359 | } |
360 | ||
361 | static struct drm_crtc_state *tidss_crtc_duplicate_state(struct drm_crtc *crtc) | |
362 | { | |
363 | struct tidss_crtc_state *state, *current_state; | |
364 | ||
365 | if (WARN_ON(!crtc->state)) | |
366 | return NULL; | |
367 | ||
368 | current_state = to_tidss_crtc_state(crtc->state); | |
369 | ||
370 | state = kmalloc(sizeof(*state), GFP_KERNEL); | |
371 | if (!state) | |
372 | return NULL; | |
373 | ||
374 | __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); | |
375 | ||
b33b5474 JS |
376 | state->plane_pos_changed = false; |
377 | ||
32a1795f JS |
378 | state->bus_format = current_state->bus_format; |
379 | state->bus_flags = current_state->bus_flags; | |
380 | ||
381 | return &state->base; | |
382 | } | |
383 | ||
9da67433 TV |
384 | static void tidss_crtc_destroy(struct drm_crtc *crtc) |
385 | { | |
386 | struct tidss_crtc *tcrtc = to_tidss_crtc(crtc); | |
387 | ||
388 | drm_crtc_cleanup(crtc); | |
389 | kfree(tcrtc); | |
390 | } | |
391 | ||
32a1795f JS |
392 | static const struct drm_crtc_funcs tidss_crtc_funcs = { |
393 | .reset = tidss_crtc_reset, | |
9da67433 | 394 | .destroy = tidss_crtc_destroy, |
32a1795f JS |
395 | .set_config = drm_atomic_helper_set_config, |
396 | .page_flip = drm_atomic_helper_page_flip, | |
397 | .atomic_duplicate_state = tidss_crtc_duplicate_state, | |
398 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, | |
399 | .enable_vblank = tidss_crtc_enable_vblank, | |
400 | .disable_vblank = tidss_crtc_disable_vblank, | |
401 | }; | |
402 | ||
403 | struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss, | |
404 | u32 hw_videoport, | |
405 | struct drm_plane *primary) | |
406 | { | |
407 | struct tidss_crtc *tcrtc; | |
408 | struct drm_crtc *crtc; | |
409 | unsigned int gamma_lut_size = 0; | |
410 | bool has_ctm = tidss->feat->vp_feat.color.has_ctm; | |
411 | int ret; | |
412 | ||
9da67433 | 413 | tcrtc = kzalloc(sizeof(*tcrtc), GFP_KERNEL); |
32a1795f JS |
414 | if (!tcrtc) |
415 | return ERR_PTR(-ENOMEM); | |
416 | ||
417 | tcrtc->hw_videoport = hw_videoport; | |
418 | init_completion(&tcrtc->framedone_completion); | |
419 | ||
420 | crtc = &tcrtc->crtc; | |
421 | ||
422 | ret = drm_crtc_init_with_planes(&tidss->ddev, crtc, primary, | |
423 | NULL, &tidss_crtc_funcs, NULL); | |
9da67433 TV |
424 | if (ret < 0) { |
425 | kfree(tcrtc); | |
32a1795f | 426 | return ERR_PTR(ret); |
9da67433 | 427 | } |
32a1795f JS |
428 | |
429 | drm_crtc_helper_add(crtc, &tidss_crtc_helper_funcs); | |
430 | ||
431 | /* | |
432 | * The dispc gamma functions adapt to what ever size we ask | |
433 | * from it no matter what HW supports. X-server assumes 256 | |
434 | * element gamma tables so lets use that. | |
435 | */ | |
436 | if (tidss->feat->vp_feat.color.gamma_size) | |
437 | gamma_lut_size = 256; | |
438 | ||
439 | drm_crtc_enable_color_mgmt(crtc, 0, has_ctm, gamma_lut_size); | |
440 | if (gamma_lut_size) | |
441 | drm_mode_crtc_set_gamma_size(crtc, gamma_lut_size); | |
442 | ||
443 | return tcrtc; | |
444 | } |