1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * i.MX IPUv3 DP Overlay Planes |
4 | * |
5 | * Copyright (C) 2013 Philipp Zabel, Pengutronix |
6 | */ |
7 | |
8 | #include <drm/drm_atomic.h> |
9 | #include <drm/drm_atomic_helper.h> |
10 | #include <drm/drm_blend.h> |
11 | #include <drm/drm_fb_dma_helper.h> |
12 | #include <drm/drm_fourcc.h> |
13 | #include <drm/drm_framebuffer.h> |
14 | #include <drm/drm_gem_atomic_helper.h> |
15 | #include <drm/drm_gem_dma_helper.h> |
16 | #include <drm/drm_managed.h> |
17 | |
18 | #include <video/imx-ipu-v3.h> |
19 | |
20 | #include "imx-drm.h" |
21 | #include "ipuv3-plane.h" |
22 | |
23 | struct ipu_plane_state { |
24 | struct drm_plane_state base; |
25 | bool use_pre; |
26 | }; |
27 | |
28 | static inline struct ipu_plane_state * |
29 | to_ipu_plane_state(struct drm_plane_state *p) |
30 | { |
31 | return container_of(p, struct ipu_plane_state, base); |
32 | } |
33 | |
34 | static unsigned int ipu_src_rect_width(const struct drm_plane_state *state) |
35 | { |
36 | return ALIGN(drm_rect_width(&state->src) >> 16, 8); |
37 | } |
38 | |
39 | static inline struct ipu_plane *to_ipu_plane(struct drm_plane *p) |
40 | { |
41 | return container_of(p, struct ipu_plane, base); |
42 | } |
43 | |
44 | static const uint32_t ipu_plane_all_formats[] = { |
45 | DRM_FORMAT_ARGB1555, |
46 | DRM_FORMAT_XRGB1555, |
47 | DRM_FORMAT_ABGR1555, |
48 | DRM_FORMAT_XBGR1555, |
49 | DRM_FORMAT_RGBA5551, |
50 | DRM_FORMAT_BGRA5551, |
51 | DRM_FORMAT_ARGB4444, |
52 | DRM_FORMAT_ARGB8888, |
53 | DRM_FORMAT_XRGB8888, |
54 | DRM_FORMAT_ABGR8888, |
55 | DRM_FORMAT_XBGR8888, |
56 | DRM_FORMAT_RGBA8888, |
57 | DRM_FORMAT_RGBX8888, |
58 | DRM_FORMAT_BGRA8888, |
59 | DRM_FORMAT_BGRX8888, |
60 | DRM_FORMAT_UYVY, |
61 | DRM_FORMAT_VYUY, |
62 | DRM_FORMAT_YUYV, |
63 | DRM_FORMAT_YVYU, |
64 | DRM_FORMAT_YUV420, |
65 | DRM_FORMAT_YVU420, |
66 | DRM_FORMAT_YUV422, |
67 | DRM_FORMAT_YVU422, |
68 | DRM_FORMAT_YUV444, |
69 | DRM_FORMAT_YVU444, |
70 | DRM_FORMAT_NV12, |
71 | DRM_FORMAT_NV16, |
72 | DRM_FORMAT_RGB565, |
73 | DRM_FORMAT_RGB565_A8, |
74 | DRM_FORMAT_BGR565_A8, |
75 | DRM_FORMAT_RGB888_A8, |
76 | DRM_FORMAT_BGR888_A8, |
77 | DRM_FORMAT_RGBX8888_A8, |
78 | DRM_FORMAT_BGRX8888_A8, |
79 | }; |
80 | |
81 | static const uint32_t ipu_plane_rgb_formats[] = { |
82 | DRM_FORMAT_ARGB1555, |
83 | DRM_FORMAT_XRGB1555, |
84 | DRM_FORMAT_ABGR1555, |
85 | DRM_FORMAT_XBGR1555, |
86 | DRM_FORMAT_RGBA5551, |
87 | DRM_FORMAT_BGRA5551, |
88 | DRM_FORMAT_ARGB4444, |
89 | DRM_FORMAT_ARGB8888, |
90 | DRM_FORMAT_XRGB8888, |
91 | DRM_FORMAT_ABGR8888, |
92 | DRM_FORMAT_XBGR8888, |
93 | DRM_FORMAT_RGBA8888, |
94 | DRM_FORMAT_RGBX8888, |
95 | DRM_FORMAT_BGRA8888, |
96 | DRM_FORMAT_BGRX8888, |
97 | DRM_FORMAT_RGB565, |
98 | DRM_FORMAT_RGB565_A8, |
99 | DRM_FORMAT_BGR565_A8, |
100 | DRM_FORMAT_RGB888_A8, |
101 | DRM_FORMAT_BGR888_A8, |
102 | DRM_FORMAT_RGBX8888_A8, |
103 | DRM_FORMAT_BGRX8888_A8, |
104 | }; |
105 | |
106 | static const uint64_t ipu_format_modifiers[] = { |
107 | DRM_FORMAT_MOD_LINEAR, |
108 | DRM_FORMAT_MOD_INVALID |
109 | }; |
110 | |
111 | static const uint64_t pre_format_modifiers[] = { |
112 | DRM_FORMAT_MOD_LINEAR, |
113 | DRM_FORMAT_MOD_VIVANTE_TILED, |
114 | DRM_FORMAT_MOD_VIVANTE_SUPER_TILED, |
115 | DRM_FORMAT_MOD_INVALID |
116 | }; |
117 | |
118 | int ipu_plane_irq(struct ipu_plane *ipu_plane) |
119 | { |
120 | return ipu_idmac_channel_irq(ipu: ipu_plane->ipu, channel: ipu_plane->ipu_ch, |
121 | irq: IPU_IRQ_EOF); |
122 | } |
123 | |
124 | static inline unsigned long |
125 | drm_plane_state_to_eba(struct drm_plane_state *state, int plane) |
126 | { |
127 | struct drm_framebuffer *fb = state->fb; |
128 | struct drm_gem_dma_object *dma_obj; |
129 | int x = state->src.x1 >> 16; |
130 | int y = state->src.y1 >> 16; |
131 | |
132 | dma_obj = drm_fb_dma_get_gem_obj(fb, plane); |
133 | BUG_ON(!dma_obj); |
134 | |
135 | return dma_obj->dma_addr + fb->offsets[plane] + fb->pitches[plane] * y + |
136 | fb->format->cpp[plane] * x; |
137 | } |
138 | |
139 | static inline unsigned long |
140 | drm_plane_state_to_ubo(struct drm_plane_state *state) |
141 | { |
142 | struct drm_framebuffer *fb = state->fb; |
143 | struct drm_gem_dma_object *dma_obj; |
144 | unsigned long eba = drm_plane_state_to_eba(state, plane: 0); |
145 | int x = state->src.x1 >> 16; |
146 | int y = state->src.y1 >> 16; |
147 | |
148 | dma_obj = drm_fb_dma_get_gem_obj(fb, plane: 1); |
149 | BUG_ON(!dma_obj); |
150 | |
151 | x /= fb->format->hsub; |
152 | y /= fb->format->vsub; |
153 | |
154 | return dma_obj->dma_addr + fb->offsets[1] + fb->pitches[1] * y + |
155 | fb->format->cpp[1] * x - eba; |
156 | } |
157 | |
158 | static inline unsigned long |
159 | drm_plane_state_to_vbo(struct drm_plane_state *state) |
160 | { |
161 | struct drm_framebuffer *fb = state->fb; |
162 | struct drm_gem_dma_object *dma_obj; |
163 | unsigned long eba = drm_plane_state_to_eba(state, plane: 0); |
164 | int x = state->src.x1 >> 16; |
165 | int y = state->src.y1 >> 16; |
166 | |
167 | dma_obj = drm_fb_dma_get_gem_obj(fb, plane: 2); |
168 | BUG_ON(!dma_obj); |
169 | |
170 | x /= fb->format->hsub; |
171 | y /= fb->format->vsub; |
172 | |
173 | return dma_obj->dma_addr + fb->offsets[2] + fb->pitches[2] * y + |
174 | fb->format->cpp[2] * x - eba; |
175 | } |
176 | |
177 | static void ipu_plane_put_resources(struct drm_device *dev, void *ptr) |
178 | { |
179 | struct ipu_plane *ipu_plane = ptr; |
180 | |
181 | if (!IS_ERR_OR_NULL(ptr: ipu_plane->dp)) |
182 | ipu_dp_put(ipu_plane->dp); |
183 | if (!IS_ERR_OR_NULL(ptr: ipu_plane->dmfc)) |
184 | ipu_dmfc_put(dmfc: ipu_plane->dmfc); |
185 | if (!IS_ERR_OR_NULL(ptr: ipu_plane->ipu_ch)) |
186 | ipu_idmac_put(ipu_plane->ipu_ch); |
187 | if (!IS_ERR_OR_NULL(ptr: ipu_plane->alpha_ch)) |
188 | ipu_idmac_put(ipu_plane->alpha_ch); |
189 | } |
190 | |
191 | static int ipu_plane_get_resources(struct drm_device *dev, |
192 | struct ipu_plane *ipu_plane) |
193 | { |
194 | int ret; |
195 | int alpha_ch; |
196 | |
197 | ipu_plane->ipu_ch = ipu_idmac_get(ipu: ipu_plane->ipu, channel: ipu_plane->dma); |
198 | if (IS_ERR(ptr: ipu_plane->ipu_ch)) { |
199 | ret = PTR_ERR(ptr: ipu_plane->ipu_ch); |
200 | DRM_ERROR("failed to get idmac channel: %d\n" , ret); |
201 | return ret; |
202 | } |
203 | |
204 | ret = drmm_add_action_or_reset(dev, ipu_plane_put_resources, ipu_plane); |
205 | if (ret) |
206 | return ret; |
207 | |
208 | alpha_ch = ipu_channel_alpha_channel(ch_num: ipu_plane->dma); |
209 | if (alpha_ch >= 0) { |
210 | ipu_plane->alpha_ch = ipu_idmac_get(ipu: ipu_plane->ipu, channel: alpha_ch); |
211 | if (IS_ERR(ptr: ipu_plane->alpha_ch)) { |
212 | ret = PTR_ERR(ptr: ipu_plane->alpha_ch); |
213 | DRM_ERROR("failed to get alpha idmac channel %d: %d\n" , |
214 | alpha_ch, ret); |
215 | return ret; |
216 | } |
217 | } |
218 | |
219 | ipu_plane->dmfc = ipu_dmfc_get(ipu: ipu_plane->ipu, ipuv3_channel: ipu_plane->dma); |
220 | if (IS_ERR(ptr: ipu_plane->dmfc)) { |
221 | ret = PTR_ERR(ptr: ipu_plane->dmfc); |
222 | DRM_ERROR("failed to get dmfc: ret %d\n" , ret); |
223 | return ret; |
224 | } |
225 | |
226 | if (ipu_plane->dp_flow >= 0) { |
227 | ipu_plane->dp = ipu_dp_get(ipu: ipu_plane->ipu, flow: ipu_plane->dp_flow); |
228 | if (IS_ERR(ptr: ipu_plane->dp)) { |
229 | ret = PTR_ERR(ptr: ipu_plane->dp); |
230 | DRM_ERROR("failed to get dp flow: %d\n" , ret); |
231 | return ret; |
232 | } |
233 | } |
234 | |
235 | return 0; |
236 | } |
237 | |
238 | static bool ipu_plane_separate_alpha(struct ipu_plane *ipu_plane) |
239 | { |
240 | switch (ipu_plane->base.state->fb->format->format) { |
241 | case DRM_FORMAT_RGB565_A8: |
242 | case DRM_FORMAT_BGR565_A8: |
243 | case DRM_FORMAT_RGB888_A8: |
244 | case DRM_FORMAT_BGR888_A8: |
245 | case DRM_FORMAT_RGBX8888_A8: |
246 | case DRM_FORMAT_BGRX8888_A8: |
247 | return true; |
248 | default: |
249 | return false; |
250 | } |
251 | } |
252 | |
253 | static void ipu_plane_enable(struct ipu_plane *ipu_plane) |
254 | { |
255 | if (ipu_plane->dp) |
256 | ipu_dp_enable(ipu: ipu_plane->ipu); |
257 | ipu_dmfc_enable_channel(dmfc: ipu_plane->dmfc); |
258 | ipu_idmac_enable_channel(channel: ipu_plane->ipu_ch); |
259 | if (ipu_plane_separate_alpha(ipu_plane)) |
260 | ipu_idmac_enable_channel(channel: ipu_plane->alpha_ch); |
261 | if (ipu_plane->dp) |
262 | ipu_dp_enable_channel(dp: ipu_plane->dp); |
263 | } |
264 | |
265 | void ipu_plane_disable(struct ipu_plane *ipu_plane, bool disable_dp_channel) |
266 | { |
267 | int ret; |
268 | |
269 | DRM_DEBUG_KMS("[%d] %s\n" , __LINE__, __func__); |
270 | |
271 | ret = ipu_idmac_wait_busy(channel: ipu_plane->ipu_ch, ms: 50); |
272 | if (ret == -ETIMEDOUT) { |
273 | DRM_ERROR("[PLANE:%d] IDMAC timeout\n" , |
274 | ipu_plane->base.base.id); |
275 | } |
276 | |
277 | if (ipu_plane->dp && disable_dp_channel) |
278 | ipu_dp_disable_channel(dp: ipu_plane->dp, sync: false); |
279 | ipu_idmac_disable_channel(channel: ipu_plane->ipu_ch); |
280 | if (ipu_plane->alpha_ch) |
281 | ipu_idmac_disable_channel(channel: ipu_plane->alpha_ch); |
282 | ipu_dmfc_disable_channel(dmfc: ipu_plane->dmfc); |
283 | if (ipu_plane->dp) |
284 | ipu_dp_disable(ipu: ipu_plane->ipu); |
285 | if (ipu_prg_present(ipu: ipu_plane->ipu)) |
286 | ipu_prg_channel_disable(ipu_chan: ipu_plane->ipu_ch); |
287 | } |
288 | |
289 | void ipu_plane_disable_deferred(struct drm_plane *plane) |
290 | { |
291 | struct ipu_plane *ipu_plane = to_ipu_plane(p: plane); |
292 | |
293 | if (ipu_plane->disabling) { |
294 | ipu_plane->disabling = false; |
295 | ipu_plane_disable(ipu_plane, disable_dp_channel: false); |
296 | } |
297 | } |
298 | |
299 | static void ipu_plane_state_reset(struct drm_plane *plane) |
300 | { |
301 | struct ipu_plane_state *ipu_state; |
302 | |
303 | if (plane->state) { |
304 | ipu_state = to_ipu_plane_state(p: plane->state); |
305 | __drm_atomic_helper_plane_destroy_state(state: plane->state); |
306 | kfree(objp: ipu_state); |
307 | plane->state = NULL; |
308 | } |
309 | |
310 | ipu_state = kzalloc(size: sizeof(*ipu_state), GFP_KERNEL); |
311 | |
312 | if (ipu_state) |
313 | __drm_atomic_helper_plane_reset(plane, state: &ipu_state->base); |
314 | } |
315 | |
316 | static struct drm_plane_state * |
317 | ipu_plane_duplicate_state(struct drm_plane *plane) |
318 | { |
319 | struct ipu_plane_state *state; |
320 | |
321 | if (WARN_ON(!plane->state)) |
322 | return NULL; |
323 | |
324 | state = kmalloc(size: sizeof(*state), GFP_KERNEL); |
325 | if (state) |
326 | __drm_atomic_helper_plane_duplicate_state(plane, state: &state->base); |
327 | |
328 | return &state->base; |
329 | } |
330 | |
331 | static void ipu_plane_destroy_state(struct drm_plane *plane, |
332 | struct drm_plane_state *state) |
333 | { |
334 | struct ipu_plane_state *ipu_state = to_ipu_plane_state(p: state); |
335 | |
336 | __drm_atomic_helper_plane_destroy_state(state); |
337 | kfree(objp: ipu_state); |
338 | } |
339 | |
340 | static bool ipu_plane_format_mod_supported(struct drm_plane *plane, |
341 | uint32_t format, uint64_t modifier) |
342 | { |
343 | struct ipu_soc *ipu = to_ipu_plane(p: plane)->ipu; |
344 | |
345 | /* linear is supported for all planes and formats */ |
346 | if (modifier == DRM_FORMAT_MOD_LINEAR) |
347 | return true; |
348 | |
349 | /* |
350 | * Without a PRG the possible modifiers list only includes the linear |
351 | * modifier, so we always take the early return from this function and |
352 | * only end up here if the PRG is present. |
353 | */ |
354 | return ipu_prg_format_supported(ipu, format, modifier); |
355 | } |
356 | |
357 | static const struct drm_plane_funcs ipu_plane_funcs = { |
358 | .update_plane = drm_atomic_helper_update_plane, |
359 | .disable_plane = drm_atomic_helper_disable_plane, |
360 | .reset = ipu_plane_state_reset, |
361 | .atomic_duplicate_state = ipu_plane_duplicate_state, |
362 | .atomic_destroy_state = ipu_plane_destroy_state, |
363 | .format_mod_supported = ipu_plane_format_mod_supported, |
364 | }; |
365 | |
366 | static int ipu_plane_atomic_check(struct drm_plane *plane, |
367 | struct drm_atomic_state *state) |
368 | { |
369 | struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, |
370 | plane); |
371 | struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, |
372 | plane); |
373 | struct drm_crtc_state *crtc_state; |
374 | struct device *dev = plane->dev->dev; |
375 | struct drm_framebuffer *fb = new_state->fb; |
376 | struct drm_framebuffer *old_fb = old_state->fb; |
377 | unsigned long eba, ubo, vbo, old_ubo, old_vbo, alpha_eba; |
378 | bool can_position = (plane->type == DRM_PLANE_TYPE_OVERLAY); |
379 | int ret; |
380 | |
381 | /* Ok to disable */ |
382 | if (!fb) |
383 | return 0; |
384 | |
385 | if (WARN_ON(!new_state->crtc)) |
386 | return -EINVAL; |
387 | |
388 | crtc_state = |
389 | drm_atomic_get_existing_crtc_state(state, |
390 | crtc: new_state->crtc); |
391 | if (WARN_ON(!crtc_state)) |
392 | return -EINVAL; |
393 | |
394 | ret = drm_atomic_helper_check_plane_state(plane_state: new_state, crtc_state, |
395 | DRM_PLANE_NO_SCALING, |
396 | DRM_PLANE_NO_SCALING, |
397 | can_position, can_update_disabled: true); |
398 | if (ret) |
399 | return ret; |
400 | |
401 | /* nothing to check when disabling or disabled */ |
402 | if (!crtc_state->enable) |
403 | return 0; |
404 | |
405 | switch (plane->type) { |
406 | case DRM_PLANE_TYPE_PRIMARY: |
407 | /* full plane minimum width is 13 pixels */ |
408 | if (drm_rect_width(r: &new_state->dst) < 13) |
409 | return -EINVAL; |
410 | break; |
411 | case DRM_PLANE_TYPE_OVERLAY: |
412 | break; |
413 | default: |
414 | dev_warn(dev, "Unsupported plane type %d\n" , plane->type); |
415 | return -EINVAL; |
416 | } |
417 | |
418 | if (drm_rect_height(r: &new_state->dst) < 2) |
419 | return -EINVAL; |
420 | |
421 | /* |
422 | * We support resizing active plane or changing its format by |
423 | * forcing CRTC mode change in plane's ->atomic_check callback |
424 | * and disabling all affected active planes in CRTC's ->atomic_disable |
425 | * callback. The planes will be reenabled in plane's ->atomic_update |
426 | * callback. |
427 | */ |
428 | if (old_fb && |
429 | (drm_rect_width(r: &new_state->dst) != drm_rect_width(r: &old_state->dst) || |
430 | drm_rect_height(r: &new_state->dst) != drm_rect_height(r: &old_state->dst) || |
431 | fb->format != old_fb->format)) |
432 | crtc_state->mode_changed = true; |
433 | |
434 | eba = drm_plane_state_to_eba(state: new_state, plane: 0); |
435 | |
436 | if (eba & 0x7) |
437 | return -EINVAL; |
438 | |
439 | if (fb->pitches[0] < 1 || fb->pitches[0] > 16384) |
440 | return -EINVAL; |
441 | |
442 | if (old_fb && fb->pitches[0] != old_fb->pitches[0]) |
443 | crtc_state->mode_changed = true; |
444 | |
445 | if (ALIGN(fb->width, 8) * fb->format->cpp[0] > |
446 | fb->pitches[0] + fb->offsets[0]) { |
447 | dev_warn(dev, "pitch is not big enough for 8 pixels alignment" ); |
448 | return -EINVAL; |
449 | } |
450 | |
451 | switch (fb->format->format) { |
452 | case DRM_FORMAT_YUV420: |
453 | case DRM_FORMAT_YVU420: |
454 | case DRM_FORMAT_YUV422: |
455 | case DRM_FORMAT_YVU422: |
456 | case DRM_FORMAT_YUV444: |
457 | case DRM_FORMAT_YVU444: |
458 | /* |
459 | * Multiplanar formats have to meet the following restrictions: |
460 | * - The (up to) three plane addresses are EBA, EBA+UBO, EBA+VBO |
461 | * - EBA, UBO and VBO are a multiple of 8 |
462 | * - UBO and VBO are unsigned and not larger than 0xfffff8 |
463 | * - Only EBA may be changed while scanout is active |
464 | * - The strides of U and V planes must be identical. |
465 | */ |
466 | vbo = drm_plane_state_to_vbo(state: new_state); |
467 | |
468 | if (vbo & 0x7 || vbo > 0xfffff8) |
469 | return -EINVAL; |
470 | |
471 | if (old_fb && (fb->format == old_fb->format)) { |
472 | old_vbo = drm_plane_state_to_vbo(state: old_state); |
473 | if (vbo != old_vbo) |
474 | crtc_state->mode_changed = true; |
475 | } |
476 | |
477 | if (fb->pitches[1] != fb->pitches[2]) |
478 | return -EINVAL; |
479 | |
480 | fallthrough; |
481 | case DRM_FORMAT_NV12: |
482 | case DRM_FORMAT_NV16: |
483 | ubo = drm_plane_state_to_ubo(state: new_state); |
484 | |
485 | if (ubo & 0x7 || ubo > 0xfffff8) |
486 | return -EINVAL; |
487 | |
488 | if (old_fb && (fb->format == old_fb->format)) { |
489 | old_ubo = drm_plane_state_to_ubo(state: old_state); |
490 | if (ubo != old_ubo) |
491 | crtc_state->mode_changed = true; |
492 | } |
493 | |
494 | if (fb->pitches[1] < 1 || fb->pitches[1] > 16384) |
495 | return -EINVAL; |
496 | |
497 | if (old_fb && old_fb->pitches[1] != fb->pitches[1]) |
498 | crtc_state->mode_changed = true; |
499 | |
500 | /* |
501 | * The x/y offsets must be even in case of horizontal/vertical |
502 | * chroma subsampling. |
503 | */ |
504 | if (((new_state->src.x1 >> 16) & (fb->format->hsub - 1)) || |
505 | ((new_state->src.y1 >> 16) & (fb->format->vsub - 1))) |
506 | return -EINVAL; |
507 | break; |
508 | case DRM_FORMAT_RGB565_A8: |
509 | case DRM_FORMAT_BGR565_A8: |
510 | case DRM_FORMAT_RGB888_A8: |
511 | case DRM_FORMAT_BGR888_A8: |
512 | case DRM_FORMAT_RGBX8888_A8: |
513 | case DRM_FORMAT_BGRX8888_A8: |
514 | alpha_eba = drm_plane_state_to_eba(state: new_state, plane: 1); |
515 | if (alpha_eba & 0x7) |
516 | return -EINVAL; |
517 | |
518 | if (fb->pitches[1] < 1 || fb->pitches[1] > 16384) |
519 | return -EINVAL; |
520 | |
521 | if (old_fb && old_fb->pitches[1] != fb->pitches[1]) |
522 | crtc_state->mode_changed = true; |
523 | break; |
524 | } |
525 | |
526 | return 0; |
527 | } |
528 | |
529 | static void ipu_plane_atomic_disable(struct drm_plane *plane, |
530 | struct drm_atomic_state *state) |
531 | { |
532 | struct ipu_plane *ipu_plane = to_ipu_plane(p: plane); |
533 | |
534 | if (ipu_plane->dp) |
535 | ipu_dp_disable_channel(dp: ipu_plane->dp, sync: true); |
536 | ipu_plane->disabling = true; |
537 | } |
538 | |
539 | static int ipu_chan_assign_axi_id(int ipu_chan) |
540 | { |
541 | switch (ipu_chan) { |
542 | case IPUV3_CHANNEL_MEM_BG_SYNC: |
543 | return 1; |
544 | case IPUV3_CHANNEL_MEM_FG_SYNC: |
545 | return 2; |
546 | case IPUV3_CHANNEL_MEM_DC_SYNC: |
547 | return 3; |
548 | default: |
549 | return 0; |
550 | } |
551 | } |
552 | |
553 | static void ipu_calculate_bursts(u32 width, u32 cpp, u32 stride, |
554 | u8 *burstsize, u8 *num_bursts) |
555 | { |
556 | const unsigned int width_bytes = width * cpp; |
557 | unsigned int npb, bursts; |
558 | |
559 | /* Maximum number of pixels per burst without overshooting stride */ |
560 | for (npb = 64 / cpp; npb > 0; --npb) { |
561 | if (round_up(width_bytes, npb * cpp) <= stride) |
562 | break; |
563 | } |
564 | *burstsize = npb; |
565 | |
566 | /* Maximum number of consecutive bursts without overshooting stride */ |
567 | for (bursts = 8; bursts > 1; bursts /= 2) { |
568 | if (round_up(width_bytes, npb * cpp * bursts) <= stride) |
569 | break; |
570 | } |
571 | *num_bursts = bursts; |
572 | } |
573 | |
574 | static void ipu_plane_atomic_update(struct drm_plane *plane, |
575 | struct drm_atomic_state *state) |
576 | { |
577 | struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, |
578 | plane); |
579 | struct ipu_plane *ipu_plane = to_ipu_plane(p: plane); |
580 | struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, |
581 | plane); |
582 | struct ipu_plane_state *ipu_state = to_ipu_plane_state(p: new_state); |
583 | struct drm_crtc_state *crtc_state = new_state->crtc->state; |
584 | struct drm_framebuffer *fb = new_state->fb; |
585 | struct drm_rect *dst = &new_state->dst; |
586 | unsigned long eba, ubo, vbo; |
587 | unsigned long alpha_eba = 0; |
588 | enum ipu_color_space ics; |
589 | unsigned int axi_id = 0; |
590 | const struct drm_format_info *info; |
591 | u8 burstsize, num_bursts; |
592 | u32 width, height; |
593 | int active; |
594 | |
595 | if (ipu_plane->dp_flow == IPU_DP_FLOW_SYNC_FG) |
596 | ipu_dp_set_window_pos(ipu_plane->dp, x_pos: dst->x1, y_pos: dst->y1); |
597 | |
598 | switch (ipu_plane->dp_flow) { |
599 | case IPU_DP_FLOW_SYNC_BG: |
600 | if (new_state->normalized_zpos == 1) { |
601 | ipu_dp_set_global_alpha(dp: ipu_plane->dp, |
602 | enable: !fb->format->has_alpha, alpha: 0xff, |
603 | bg_chan: true); |
604 | } else { |
605 | ipu_dp_set_global_alpha(dp: ipu_plane->dp, enable: true, alpha: 0, bg_chan: true); |
606 | } |
607 | break; |
608 | case IPU_DP_FLOW_SYNC_FG: |
609 | if (new_state->normalized_zpos == 1) { |
610 | ipu_dp_set_global_alpha(dp: ipu_plane->dp, |
611 | enable: !fb->format->has_alpha, alpha: 0xff, |
612 | bg_chan: false); |
613 | } |
614 | break; |
615 | } |
616 | |
617 | if (ipu_plane->dp_flow == IPU_DP_FLOW_SYNC_BG) |
618 | width = ipu_src_rect_width(state: new_state); |
619 | else |
620 | width = drm_rect_width(r: &new_state->src) >> 16; |
621 | height = drm_rect_height(r: &new_state->src) >> 16; |
622 | |
623 | eba = drm_plane_state_to_eba(state: new_state, plane: 0); |
624 | |
625 | /* |
626 | * Configure PRG channel and attached PRE, this changes the EBA to an |
627 | * internal SRAM location. |
628 | */ |
629 | if (ipu_state->use_pre) { |
630 | axi_id = ipu_chan_assign_axi_id(ipu_chan: ipu_plane->dma); |
631 | ipu_prg_channel_configure(ipu_chan: ipu_plane->ipu_ch, axi_id, width, |
632 | height, stride: fb->pitches[0], |
633 | format: fb->format->format, modifier: fb->modifier, |
634 | eba: &eba); |
635 | } |
636 | |
637 | if (!old_state->fb || |
638 | old_state->fb->format->format != fb->format->format || |
639 | old_state->color_encoding != new_state->color_encoding || |
640 | old_state->color_range != new_state->color_range) { |
641 | ics = ipu_drm_fourcc_to_colorspace(drm_fourcc: fb->format->format); |
642 | switch (ipu_plane->dp_flow) { |
643 | case IPU_DP_FLOW_SYNC_BG: |
644 | ipu_dp_setup_channel(dp: ipu_plane->dp, ycbcr_enc: new_state->color_encoding, |
645 | range: new_state->color_range, in: ics, |
646 | out: IPUV3_COLORSPACE_RGB); |
647 | break; |
648 | case IPU_DP_FLOW_SYNC_FG: |
649 | ipu_dp_setup_channel(dp: ipu_plane->dp, ycbcr_enc: new_state->color_encoding, |
650 | range: new_state->color_range, in: ics, |
651 | out: IPUV3_COLORSPACE_UNKNOWN); |
652 | break; |
653 | } |
654 | } |
655 | |
656 | if (old_state->fb && !drm_atomic_crtc_needs_modeset(state: crtc_state)) { |
657 | /* nothing to do if PRE is used */ |
658 | if (ipu_state->use_pre) |
659 | return; |
660 | active = ipu_idmac_get_current_buffer(channel: ipu_plane->ipu_ch); |
661 | ipu_cpmem_set_buffer(ch: ipu_plane->ipu_ch, bufnum: !active, buf: eba); |
662 | ipu_idmac_select_buffer(channel: ipu_plane->ipu_ch, buf_num: !active); |
663 | if (ipu_plane_separate_alpha(ipu_plane)) { |
664 | active = ipu_idmac_get_current_buffer(channel: ipu_plane->alpha_ch); |
665 | ipu_cpmem_set_buffer(ch: ipu_plane->alpha_ch, bufnum: !active, |
666 | buf: alpha_eba); |
667 | ipu_idmac_select_buffer(channel: ipu_plane->alpha_ch, buf_num: !active); |
668 | } |
669 | return; |
670 | } |
671 | |
672 | ics = ipu_drm_fourcc_to_colorspace(drm_fourcc: fb->format->format); |
673 | switch (ipu_plane->dp_flow) { |
674 | case IPU_DP_FLOW_SYNC_BG: |
675 | ipu_dp_setup_channel(dp: ipu_plane->dp, ycbcr_enc: DRM_COLOR_YCBCR_BT601, |
676 | range: DRM_COLOR_YCBCR_LIMITED_RANGE, in: ics, |
677 | out: IPUV3_COLORSPACE_RGB); |
678 | break; |
679 | case IPU_DP_FLOW_SYNC_FG: |
680 | ipu_dp_setup_channel(dp: ipu_plane->dp, ycbcr_enc: DRM_COLOR_YCBCR_BT601, |
681 | range: DRM_COLOR_YCBCR_LIMITED_RANGE, in: ics, |
682 | out: IPUV3_COLORSPACE_UNKNOWN); |
683 | break; |
684 | } |
685 | |
686 | ipu_dmfc_config_wait4eot(dmfc: ipu_plane->dmfc, width); |
687 | |
688 | info = drm_format_info(format: fb->format->format); |
689 | ipu_calculate_bursts(width, cpp: info->cpp[0], stride: fb->pitches[0], |
690 | burstsize: &burstsize, num_bursts: &num_bursts); |
691 | |
692 | ipu_cpmem_zero(ch: ipu_plane->ipu_ch); |
693 | ipu_cpmem_set_resolution(ch: ipu_plane->ipu_ch, xres: width, yres: height); |
694 | ipu_cpmem_set_fmt(ch: ipu_plane->ipu_ch, drm_fourcc: fb->format->format); |
695 | ipu_cpmem_set_burstsize(ch: ipu_plane->ipu_ch, burstsize); |
696 | ipu_cpmem_set_high_priority(ch: ipu_plane->ipu_ch); |
697 | ipu_idmac_enable_watermark(channel: ipu_plane->ipu_ch, enable: true); |
698 | ipu_idmac_set_double_buffer(channel: ipu_plane->ipu_ch, doublebuffer: 1); |
699 | ipu_cpmem_set_stride(ch: ipu_plane->ipu_ch, stride: fb->pitches[0]); |
700 | ipu_cpmem_set_axi_id(ch: ipu_plane->ipu_ch, id: axi_id); |
701 | |
702 | switch (fb->format->format) { |
703 | case DRM_FORMAT_YUV420: |
704 | case DRM_FORMAT_YVU420: |
705 | case DRM_FORMAT_YUV422: |
706 | case DRM_FORMAT_YVU422: |
707 | case DRM_FORMAT_YUV444: |
708 | case DRM_FORMAT_YVU444: |
709 | ubo = drm_plane_state_to_ubo(state: new_state); |
710 | vbo = drm_plane_state_to_vbo(state: new_state); |
711 | if (fb->format->format == DRM_FORMAT_YVU420 || |
712 | fb->format->format == DRM_FORMAT_YVU422 || |
713 | fb->format->format == DRM_FORMAT_YVU444) |
714 | swap(ubo, vbo); |
715 | |
716 | ipu_cpmem_set_yuv_planar_full(ch: ipu_plane->ipu_ch, |
717 | uv_stride: fb->pitches[1], u_offset: ubo, v_offset: vbo); |
718 | |
719 | dev_dbg(ipu_plane->base.dev->dev, |
720 | "phy = %lu %lu %lu, x = %d, y = %d" , eba, ubo, vbo, |
721 | new_state->src.x1 >> 16, new_state->src.y1 >> 16); |
722 | break; |
723 | case DRM_FORMAT_NV12: |
724 | case DRM_FORMAT_NV16: |
725 | ubo = drm_plane_state_to_ubo(state: new_state); |
726 | |
727 | ipu_cpmem_set_yuv_planar_full(ch: ipu_plane->ipu_ch, |
728 | uv_stride: fb->pitches[1], u_offset: ubo, v_offset: ubo); |
729 | |
730 | dev_dbg(ipu_plane->base.dev->dev, |
731 | "phy = %lu %lu, x = %d, y = %d" , eba, ubo, |
732 | new_state->src.x1 >> 16, new_state->src.y1 >> 16); |
733 | break; |
734 | case DRM_FORMAT_RGB565_A8: |
735 | case DRM_FORMAT_BGR565_A8: |
736 | case DRM_FORMAT_RGB888_A8: |
737 | case DRM_FORMAT_BGR888_A8: |
738 | case DRM_FORMAT_RGBX8888_A8: |
739 | case DRM_FORMAT_BGRX8888_A8: |
740 | alpha_eba = drm_plane_state_to_eba(state: new_state, plane: 1); |
741 | num_bursts = 0; |
742 | |
743 | dev_dbg(ipu_plane->base.dev->dev, "phys = %lu %lu, x = %d, y = %d" , |
744 | eba, alpha_eba, new_state->src.x1 >> 16, |
745 | new_state->src.y1 >> 16); |
746 | |
747 | ipu_cpmem_set_burstsize(ch: ipu_plane->ipu_ch, burstsize: 16); |
748 | |
749 | ipu_cpmem_zero(ch: ipu_plane->alpha_ch); |
750 | ipu_cpmem_set_resolution(ch: ipu_plane->alpha_ch, xres: width, yres: height); |
751 | ipu_cpmem_set_format_passthrough(ch: ipu_plane->alpha_ch, width: 8); |
752 | ipu_cpmem_set_high_priority(ch: ipu_plane->alpha_ch); |
753 | ipu_idmac_set_double_buffer(channel: ipu_plane->alpha_ch, doublebuffer: 1); |
754 | ipu_cpmem_set_stride(ch: ipu_plane->alpha_ch, stride: fb->pitches[1]); |
755 | ipu_cpmem_set_burstsize(ch: ipu_plane->alpha_ch, burstsize: 16); |
756 | ipu_cpmem_set_buffer(ch: ipu_plane->alpha_ch, bufnum: 0, buf: alpha_eba); |
757 | ipu_cpmem_set_buffer(ch: ipu_plane->alpha_ch, bufnum: 1, buf: alpha_eba); |
758 | break; |
759 | default: |
760 | dev_dbg(ipu_plane->base.dev->dev, "phys = %lu, x = %d, y = %d" , |
761 | eba, new_state->src.x1 >> 16, new_state->src.y1 >> 16); |
762 | break; |
763 | } |
764 | ipu_cpmem_set_buffer(ch: ipu_plane->ipu_ch, bufnum: 0, buf: eba); |
765 | ipu_cpmem_set_buffer(ch: ipu_plane->ipu_ch, bufnum: 1, buf: eba); |
766 | ipu_idmac_lock_enable(channel: ipu_plane->ipu_ch, num_bursts); |
767 | ipu_plane_enable(ipu_plane); |
768 | } |
769 | |
770 | static const struct drm_plane_helper_funcs ipu_plane_helper_funcs = { |
771 | .atomic_check = ipu_plane_atomic_check, |
772 | .atomic_disable = ipu_plane_atomic_disable, |
773 | .atomic_update = ipu_plane_atomic_update, |
774 | }; |
775 | |
776 | bool ipu_plane_atomic_update_pending(struct drm_plane *plane) |
777 | { |
778 | struct ipu_plane *ipu_plane = to_ipu_plane(p: plane); |
779 | struct drm_plane_state *state = plane->state; |
780 | struct ipu_plane_state *ipu_state = to_ipu_plane_state(p: state); |
781 | |
782 | /* disabled crtcs must not block the update */ |
783 | if (!state->crtc) |
784 | return false; |
785 | |
786 | if (ipu_state->use_pre) |
787 | return ipu_prg_channel_configure_pending(ipu_chan: ipu_plane->ipu_ch); |
788 | |
789 | /* |
790 | * Pretend no update is pending in the non-PRE/PRG case. For this to |
791 | * happen, an atomic update would have to be deferred until after the |
792 | * start of the next frame and simultaneously interrupt latency would |
793 | * have to be high enough to let the atomic update finish and issue an |
794 | * event before the previous end of frame interrupt handler can be |
795 | * executed. |
796 | */ |
797 | return false; |
798 | } |
799 | int ipu_planes_assign_pre(struct drm_device *dev, |
800 | struct drm_atomic_state *state) |
801 | { |
802 | struct drm_crtc_state *old_crtc_state, *crtc_state; |
803 | struct drm_plane_state *plane_state; |
804 | struct ipu_plane_state *ipu_state; |
805 | struct ipu_plane *ipu_plane; |
806 | struct drm_plane *plane; |
807 | struct drm_crtc *crtc; |
808 | int available_pres = ipu_prg_max_active_channels(); |
809 | int ret, i; |
810 | |
811 | for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, crtc_state, i) { |
812 | ret = drm_atomic_add_affected_planes(state, crtc); |
813 | if (ret) |
814 | return ret; |
815 | } |
816 | |
817 | /* |
818 | * We are going over the planes in 2 passes: first we assign PREs to |
819 | * planes with a tiling modifier, which need the PREs to resolve into |
820 | * linear. Any failure to assign a PRE there is fatal. In the second |
821 | * pass we try to assign PREs to linear FBs, to improve memory access |
822 | * patterns for them. Failure at this point is non-fatal, as we can |
823 | * scan out linear FBs without a PRE. |
824 | */ |
825 | for_each_new_plane_in_state(state, plane, plane_state, i) { |
826 | ipu_state = to_ipu_plane_state(p: plane_state); |
827 | ipu_plane = to_ipu_plane(p: plane); |
828 | |
829 | if (!plane_state->fb) { |
830 | ipu_state->use_pre = false; |
831 | continue; |
832 | } |
833 | |
834 | if (!(plane_state->fb->flags & DRM_MODE_FB_MODIFIERS) || |
835 | plane_state->fb->modifier == DRM_FORMAT_MOD_LINEAR) |
836 | continue; |
837 | |
838 | if (!ipu_prg_present(ipu: ipu_plane->ipu) || !available_pres) |
839 | return -EINVAL; |
840 | |
841 | if (!ipu_prg_format_supported(ipu: ipu_plane->ipu, |
842 | format: plane_state->fb->format->format, |
843 | modifier: plane_state->fb->modifier)) |
844 | return -EINVAL; |
845 | |
846 | ipu_state->use_pre = true; |
847 | available_pres--; |
848 | } |
849 | |
850 | for_each_new_plane_in_state(state, plane, plane_state, i) { |
851 | ipu_state = to_ipu_plane_state(p: plane_state); |
852 | ipu_plane = to_ipu_plane(p: plane); |
853 | |
854 | if (!plane_state->fb) { |
855 | ipu_state->use_pre = false; |
856 | continue; |
857 | } |
858 | |
859 | if ((plane_state->fb->flags & DRM_MODE_FB_MODIFIERS) && |
860 | plane_state->fb->modifier != DRM_FORMAT_MOD_LINEAR) |
861 | continue; |
862 | |
863 | /* make sure that modifier is initialized */ |
864 | plane_state->fb->modifier = DRM_FORMAT_MOD_LINEAR; |
865 | |
866 | if (ipu_prg_present(ipu: ipu_plane->ipu) && available_pres && |
867 | ipu_prg_format_supported(ipu: ipu_plane->ipu, |
868 | format: plane_state->fb->format->format, |
869 | modifier: plane_state->fb->modifier)) { |
870 | ipu_state->use_pre = true; |
871 | available_pres--; |
872 | } else { |
873 | ipu_state->use_pre = false; |
874 | } |
875 | } |
876 | |
877 | return 0; |
878 | } |
879 | |
880 | struct ipu_plane *ipu_plane_init(struct drm_device *dev, struct ipu_soc *ipu, |
881 | int dma, int dp, unsigned int possible_crtcs, |
882 | enum drm_plane_type type) |
883 | { |
884 | struct ipu_plane *ipu_plane; |
885 | const uint64_t *modifiers = ipu_format_modifiers; |
886 | unsigned int zpos = (type == DRM_PLANE_TYPE_PRIMARY) ? 0 : 1; |
887 | unsigned int format_count; |
888 | const uint32_t *formats; |
889 | int ret; |
890 | |
891 | DRM_DEBUG_KMS("channel %d, dp flow %d, possible_crtcs=0x%x\n" , |
892 | dma, dp, possible_crtcs); |
893 | |
894 | if (dp == IPU_DP_FLOW_SYNC_BG || dp == IPU_DP_FLOW_SYNC_FG) { |
895 | formats = ipu_plane_all_formats; |
896 | format_count = ARRAY_SIZE(ipu_plane_all_formats); |
897 | } else { |
898 | formats = ipu_plane_rgb_formats; |
899 | format_count = ARRAY_SIZE(ipu_plane_rgb_formats); |
900 | } |
901 | |
902 | if (ipu_prg_present(ipu)) |
903 | modifiers = pre_format_modifiers; |
904 | |
905 | ipu_plane = drmm_universal_plane_alloc(dev, struct ipu_plane, base, |
906 | possible_crtcs, &ipu_plane_funcs, |
907 | formats, format_count, modifiers, |
908 | type, NULL); |
909 | if (IS_ERR(ptr: ipu_plane)) { |
910 | DRM_ERROR("failed to allocate and initialize %s plane\n" , |
911 | zpos ? "overlay" : "primary" ); |
912 | return ipu_plane; |
913 | } |
914 | |
915 | ipu_plane->ipu = ipu; |
916 | ipu_plane->dma = dma; |
917 | ipu_plane->dp_flow = dp; |
918 | |
919 | drm_plane_helper_add(plane: &ipu_plane->base, funcs: &ipu_plane_helper_funcs); |
920 | |
921 | if (dp == IPU_DP_FLOW_SYNC_BG || dp == IPU_DP_FLOW_SYNC_FG) |
922 | ret = drm_plane_create_zpos_property(plane: &ipu_plane->base, zpos, min: 0, |
923 | max: 1); |
924 | else |
925 | ret = drm_plane_create_zpos_immutable_property(plane: &ipu_plane->base, |
926 | zpos: 0); |
927 | if (ret) |
928 | return ERR_PTR(error: ret); |
929 | |
930 | ret = drm_plane_create_color_properties(plane: &ipu_plane->base, |
931 | BIT(DRM_COLOR_YCBCR_BT601) | |
932 | BIT(DRM_COLOR_YCBCR_BT709), |
933 | BIT(DRM_COLOR_YCBCR_LIMITED_RANGE), |
934 | default_encoding: DRM_COLOR_YCBCR_BT601, |
935 | default_range: DRM_COLOR_YCBCR_LIMITED_RANGE); |
936 | if (ret) |
937 | return ERR_PTR(error: ret); |
938 | |
939 | ret = ipu_plane_get_resources(dev, ipu_plane); |
940 | if (ret) { |
941 | DRM_ERROR("failed to get %s plane resources: %pe\n" , |
942 | zpos ? "overlay" : "primary" , &ret); |
943 | return ERR_PTR(error: ret); |
944 | } |
945 | |
946 | return ipu_plane; |
947 | } |
948 | |