1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright 2019 NXP. |
4 | */ |
5 | |
6 | #include <drm/drm_atomic.h> |
7 | #include <drm/drm_atomic_helper.h> |
8 | #include <drm/drm_blend.h> |
9 | #include <drm/drm_fb_dma_helper.h> |
10 | #include <drm/drm_framebuffer.h> |
11 | #include <drm/drm_gem_atomic_helper.h> |
12 | #include <drm/drm_gem_dma_helper.h> |
13 | |
14 | #include "dcss-dev.h" |
15 | #include "dcss-kms.h" |
16 | |
17 | static const u32 dcss_common_formats[] = { |
18 | /* RGB */ |
19 | DRM_FORMAT_ARGB8888, |
20 | DRM_FORMAT_XRGB8888, |
21 | DRM_FORMAT_ABGR8888, |
22 | DRM_FORMAT_XBGR8888, |
23 | DRM_FORMAT_RGBA8888, |
24 | DRM_FORMAT_RGBX8888, |
25 | DRM_FORMAT_BGRA8888, |
26 | DRM_FORMAT_BGRX8888, |
27 | DRM_FORMAT_XRGB2101010, |
28 | DRM_FORMAT_XBGR2101010, |
29 | DRM_FORMAT_RGBX1010102, |
30 | DRM_FORMAT_BGRX1010102, |
31 | DRM_FORMAT_ARGB2101010, |
32 | DRM_FORMAT_ABGR2101010, |
33 | DRM_FORMAT_RGBA1010102, |
34 | DRM_FORMAT_BGRA1010102, |
35 | }; |
36 | |
37 | static const u64 dcss_video_format_modifiers[] = { |
38 | DRM_FORMAT_MOD_LINEAR, |
39 | DRM_FORMAT_MOD_INVALID, |
40 | }; |
41 | |
42 | static const u64 dcss_graphics_format_modifiers[] = { |
43 | DRM_FORMAT_MOD_VIVANTE_TILED, |
44 | DRM_FORMAT_MOD_VIVANTE_SUPER_TILED, |
45 | DRM_FORMAT_MOD_LINEAR, |
46 | DRM_FORMAT_MOD_INVALID, |
47 | }; |
48 | |
49 | static inline struct dcss_plane *to_dcss_plane(struct drm_plane *p) |
50 | { |
51 | return container_of(p, struct dcss_plane, base); |
52 | } |
53 | |
54 | static inline bool dcss_plane_fb_is_linear(const struct drm_framebuffer *fb) |
55 | { |
56 | return ((fb->flags & DRM_MODE_FB_MODIFIERS) == 0) || |
57 | ((fb->flags & DRM_MODE_FB_MODIFIERS) != 0 && |
58 | fb->modifier == DRM_FORMAT_MOD_LINEAR); |
59 | } |
60 | |
61 | static void dcss_plane_destroy(struct drm_plane *plane) |
62 | { |
63 | struct dcss_plane *dcss_plane = container_of(plane, struct dcss_plane, |
64 | base); |
65 | |
66 | drm_plane_cleanup(plane); |
67 | kfree(objp: dcss_plane); |
68 | } |
69 | |
70 | static bool dcss_plane_format_mod_supported(struct drm_plane *plane, |
71 | u32 format, |
72 | u64 modifier) |
73 | { |
74 | switch (plane->type) { |
75 | case DRM_PLANE_TYPE_PRIMARY: |
76 | switch (format) { |
77 | case DRM_FORMAT_ARGB8888: |
78 | case DRM_FORMAT_XRGB8888: |
79 | case DRM_FORMAT_ARGB2101010: |
80 | return modifier == DRM_FORMAT_MOD_LINEAR || |
81 | modifier == DRM_FORMAT_MOD_VIVANTE_TILED || |
82 | modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED; |
83 | default: |
84 | return modifier == DRM_FORMAT_MOD_LINEAR; |
85 | } |
86 | break; |
87 | case DRM_PLANE_TYPE_OVERLAY: |
88 | return modifier == DRM_FORMAT_MOD_LINEAR; |
89 | default: |
90 | return false; |
91 | } |
92 | } |
93 | |
94 | static const struct drm_plane_funcs dcss_plane_funcs = { |
95 | .update_plane = drm_atomic_helper_update_plane, |
96 | .disable_plane = drm_atomic_helper_disable_plane, |
97 | .destroy = dcss_plane_destroy, |
98 | .reset = drm_atomic_helper_plane_reset, |
99 | .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, |
100 | .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, |
101 | .format_mod_supported = dcss_plane_format_mod_supported, |
102 | }; |
103 | |
104 | static bool dcss_plane_can_rotate(const struct drm_format_info *format, |
105 | bool mod_present, u64 modifier, |
106 | unsigned int rotation) |
107 | { |
108 | bool linear_format = !mod_present || modifier == DRM_FORMAT_MOD_LINEAR; |
109 | u32 supported_rotation = DRM_MODE_ROTATE_0; |
110 | |
111 | if (!format->is_yuv && linear_format) |
112 | supported_rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 | |
113 | DRM_MODE_REFLECT_MASK; |
114 | else if (!format->is_yuv && |
115 | (modifier == DRM_FORMAT_MOD_VIVANTE_TILED || |
116 | modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED)) |
117 | supported_rotation = DRM_MODE_ROTATE_MASK | |
118 | DRM_MODE_REFLECT_MASK; |
119 | else if (format->is_yuv && linear_format && |
120 | (format->format == DRM_FORMAT_NV12 || |
121 | format->format == DRM_FORMAT_NV21)) |
122 | supported_rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 | |
123 | DRM_MODE_REFLECT_MASK; |
124 | |
125 | return !!(rotation & supported_rotation); |
126 | } |
127 | |
128 | static bool dcss_plane_is_source_size_allowed(u16 src_w, u16 src_h, u32 pix_fmt) |
129 | { |
130 | if (src_w < 64 && |
131 | (pix_fmt == DRM_FORMAT_NV12 || pix_fmt == DRM_FORMAT_NV21)) |
132 | return false; |
133 | else if (src_w < 32 && |
134 | (pix_fmt == DRM_FORMAT_UYVY || pix_fmt == DRM_FORMAT_VYUY || |
135 | pix_fmt == DRM_FORMAT_YUYV || pix_fmt == DRM_FORMAT_YVYU)) |
136 | return false; |
137 | |
138 | return src_w >= 16 && src_h >= 8; |
139 | } |
140 | |
141 | static int dcss_plane_atomic_check(struct drm_plane *plane, |
142 | struct drm_atomic_state *state) |
143 | { |
144 | struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, |
145 | plane); |
146 | struct dcss_plane *dcss_plane = to_dcss_plane(p: plane); |
147 | struct dcss_dev *dcss = plane->dev->dev_private; |
148 | struct drm_framebuffer *fb = new_plane_state->fb; |
149 | bool is_primary_plane = plane->type == DRM_PLANE_TYPE_PRIMARY; |
150 | struct drm_gem_dma_object *dma_obj; |
151 | struct drm_crtc_state *crtc_state; |
152 | int hdisplay, vdisplay; |
153 | int min, max; |
154 | int ret; |
155 | |
156 | if (!fb || !new_plane_state->crtc) |
157 | return 0; |
158 | |
159 | dma_obj = drm_fb_dma_get_gem_obj(fb, plane: 0); |
160 | WARN_ON(!dma_obj); |
161 | |
162 | crtc_state = drm_atomic_get_existing_crtc_state(state, |
163 | crtc: new_plane_state->crtc); |
164 | |
165 | hdisplay = crtc_state->adjusted_mode.hdisplay; |
166 | vdisplay = crtc_state->adjusted_mode.vdisplay; |
167 | |
168 | if (!dcss_plane_is_source_size_allowed(src_w: new_plane_state->src_w >> 16, |
169 | src_h: new_plane_state->src_h >> 16, |
170 | pix_fmt: fb->format->format)) { |
171 | DRM_DEBUG_KMS("Source plane size is not allowed!\n" ); |
172 | return -EINVAL; |
173 | } |
174 | |
175 | dcss_scaler_get_min_max_ratios(scl: dcss->scaler, ch_num: dcss_plane->ch_num, |
176 | min: &min, max: &max); |
177 | |
178 | ret = drm_atomic_helper_check_plane_state(plane_state: new_plane_state, crtc_state, |
179 | min_scale: min, max_scale: max, can_position: !is_primary_plane, |
180 | can_update_disabled: false); |
181 | if (ret) |
182 | return ret; |
183 | |
184 | if (!new_plane_state->visible) |
185 | return 0; |
186 | |
187 | if (!dcss_plane_can_rotate(format: fb->format, |
188 | mod_present: !!(fb->flags & DRM_MODE_FB_MODIFIERS), |
189 | modifier: fb->modifier, |
190 | rotation: new_plane_state->rotation)) { |
191 | DRM_DEBUG_KMS("requested rotation is not allowed!\n" ); |
192 | return -EINVAL; |
193 | } |
194 | |
195 | if ((new_plane_state->crtc_x < 0 || new_plane_state->crtc_y < 0 || |
196 | new_plane_state->crtc_x + new_plane_state->crtc_w > hdisplay || |
197 | new_plane_state->crtc_y + new_plane_state->crtc_h > vdisplay) && |
198 | !dcss_plane_fb_is_linear(fb)) { |
199 | DRM_DEBUG_KMS("requested cropping operation is not allowed!\n" ); |
200 | return -EINVAL; |
201 | } |
202 | |
203 | if ((fb->flags & DRM_MODE_FB_MODIFIERS) && |
204 | !plane->funcs->format_mod_supported(plane, |
205 | fb->format->format, |
206 | fb->modifier)) { |
207 | DRM_DEBUG_KMS("Invalid modifier: %llx" , fb->modifier); |
208 | return -EINVAL; |
209 | } |
210 | |
211 | return 0; |
212 | } |
213 | |
214 | static void dcss_plane_atomic_set_base(struct dcss_plane *dcss_plane) |
215 | { |
216 | struct drm_plane *plane = &dcss_plane->base; |
217 | struct drm_plane_state *state = plane->state; |
218 | struct dcss_dev *dcss = plane->dev->dev_private; |
219 | struct drm_framebuffer *fb = state->fb; |
220 | const struct drm_format_info *format = fb->format; |
221 | struct drm_gem_dma_object *dma_obj = drm_fb_dma_get_gem_obj(fb, plane: 0); |
222 | unsigned long p1_ba = 0, p2_ba = 0; |
223 | |
224 | if (!format->is_yuv || |
225 | format->format == DRM_FORMAT_NV12 || |
226 | format->format == DRM_FORMAT_NV21) |
227 | p1_ba = dma_obj->dma_addr + fb->offsets[0] + |
228 | fb->pitches[0] * (state->src.y1 >> 16) + |
229 | format->char_per_block[0] * (state->src.x1 >> 16); |
230 | else if (format->format == DRM_FORMAT_UYVY || |
231 | format->format == DRM_FORMAT_VYUY || |
232 | format->format == DRM_FORMAT_YUYV || |
233 | format->format == DRM_FORMAT_YVYU) |
234 | p1_ba = dma_obj->dma_addr + fb->offsets[0] + |
235 | fb->pitches[0] * (state->src.y1 >> 16) + |
236 | 2 * format->char_per_block[0] * (state->src.x1 >> 17); |
237 | |
238 | if (format->format == DRM_FORMAT_NV12 || |
239 | format->format == DRM_FORMAT_NV21) |
240 | p2_ba = dma_obj->dma_addr + fb->offsets[1] + |
241 | (((fb->pitches[1] >> 1) * (state->src.y1 >> 17) + |
242 | (state->src.x1 >> 17)) << 1); |
243 | |
244 | dcss_dpr_addr_set(dpr: dcss->dpr, ch_num: dcss_plane->ch_num, luma_base_addr: p1_ba, chroma_base_addr: p2_ba, |
245 | pitch: fb->pitches[0]); |
246 | } |
247 | |
248 | static bool dcss_plane_needs_setup(struct drm_plane_state *state, |
249 | struct drm_plane_state *old_state) |
250 | { |
251 | struct drm_framebuffer *fb = state->fb; |
252 | struct drm_framebuffer *old_fb = old_state->fb; |
253 | |
254 | return state->crtc_x != old_state->crtc_x || |
255 | state->crtc_y != old_state->crtc_y || |
256 | state->crtc_w != old_state->crtc_w || |
257 | state->crtc_h != old_state->crtc_h || |
258 | state->src_x != old_state->src_x || |
259 | state->src_y != old_state->src_y || |
260 | state->src_w != old_state->src_w || |
261 | state->src_h != old_state->src_h || |
262 | fb->format->format != old_fb->format->format || |
263 | fb->modifier != old_fb->modifier || |
264 | state->rotation != old_state->rotation || |
265 | state->scaling_filter != old_state->scaling_filter; |
266 | } |
267 | |
268 | static void dcss_plane_atomic_update(struct drm_plane *plane, |
269 | struct drm_atomic_state *state) |
270 | { |
271 | struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, |
272 | plane); |
273 | struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, |
274 | plane); |
275 | struct dcss_plane *dcss_plane = to_dcss_plane(p: plane); |
276 | struct dcss_dev *dcss = plane->dev->dev_private; |
277 | struct drm_framebuffer *fb = new_state->fb; |
278 | struct drm_crtc_state *crtc_state; |
279 | bool modifiers_present; |
280 | u32 src_w, src_h, dst_w, dst_h; |
281 | struct drm_rect src, dst; |
282 | bool enable = true; |
283 | bool is_rotation_90_or_270; |
284 | |
285 | if (!fb || !new_state->crtc || !new_state->visible) |
286 | return; |
287 | |
288 | crtc_state = new_state->crtc->state; |
289 | modifiers_present = !!(fb->flags & DRM_MODE_FB_MODIFIERS); |
290 | |
291 | if (old_state->fb && !drm_atomic_crtc_needs_modeset(state: crtc_state) && |
292 | !dcss_plane_needs_setup(state: new_state, old_state)) { |
293 | dcss_plane_atomic_set_base(dcss_plane); |
294 | return; |
295 | } |
296 | |
297 | src = plane->state->src; |
298 | dst = plane->state->dst; |
299 | |
300 | /* |
301 | * The width and height after clipping. |
302 | */ |
303 | src_w = drm_rect_width(r: &src) >> 16; |
304 | src_h = drm_rect_height(r: &src) >> 16; |
305 | dst_w = drm_rect_width(r: &dst); |
306 | dst_h = drm_rect_height(r: &dst); |
307 | |
308 | if (plane->type == DRM_PLANE_TYPE_OVERLAY && |
309 | modifiers_present && fb->modifier == DRM_FORMAT_MOD_LINEAR) |
310 | modifiers_present = false; |
311 | |
312 | dcss_dpr_format_set(dpr: dcss->dpr, ch_num: dcss_plane->ch_num, |
313 | format: new_state->fb->format, |
314 | modifier: modifiers_present ? fb->modifier : |
315 | DRM_FORMAT_MOD_LINEAR); |
316 | dcss_dpr_set_res(dpr: dcss->dpr, ch_num: dcss_plane->ch_num, xres: src_w, yres: src_h); |
317 | dcss_dpr_set_rotation(dpr: dcss->dpr, ch_num: dcss_plane->ch_num, |
318 | rotation: new_state->rotation); |
319 | |
320 | dcss_plane_atomic_set_base(dcss_plane); |
321 | |
322 | is_rotation_90_or_270 = new_state->rotation & (DRM_MODE_ROTATE_90 | |
323 | DRM_MODE_ROTATE_270); |
324 | |
325 | dcss_scaler_set_filter(scl: dcss->scaler, ch_num: dcss_plane->ch_num, |
326 | scaling_filter: new_state->scaling_filter); |
327 | |
328 | dcss_scaler_setup(scl: dcss->scaler, ch_num: dcss_plane->ch_num, |
329 | format: new_state->fb->format, |
330 | src_xres: is_rotation_90_or_270 ? src_h : src_w, |
331 | src_yres: is_rotation_90_or_270 ? src_w : src_h, |
332 | dst_xres: dst_w, dst_yres: dst_h, |
333 | vrefresh_hz: drm_mode_vrefresh(mode: &crtc_state->mode)); |
334 | |
335 | dcss_dtg_plane_pos_set(dtg: dcss->dtg, ch_num: dcss_plane->ch_num, |
336 | px: dst.x1, py: dst.y1, pw: dst_w, ph: dst_h); |
337 | dcss_dtg_plane_alpha_set(dtg: dcss->dtg, ch_num: dcss_plane->ch_num, |
338 | format: fb->format, alpha: new_state->alpha >> 8); |
339 | |
340 | if (!dcss_plane->ch_num && (new_state->alpha >> 8) == 0) |
341 | enable = false; |
342 | |
343 | dcss_dpr_enable(dpr: dcss->dpr, ch_num: dcss_plane->ch_num, en: enable); |
344 | dcss_scaler_ch_enable(scl: dcss->scaler, ch_num: dcss_plane->ch_num, en: enable); |
345 | |
346 | if (!enable) |
347 | dcss_dtg_plane_pos_set(dtg: dcss->dtg, ch_num: dcss_plane->ch_num, |
348 | px: 0, py: 0, pw: 0, ph: 0); |
349 | |
350 | dcss_dtg_ch_enable(dtg: dcss->dtg, ch_num: dcss_plane->ch_num, en: enable); |
351 | } |
352 | |
353 | static void dcss_plane_atomic_disable(struct drm_plane *plane, |
354 | struct drm_atomic_state *state) |
355 | { |
356 | struct dcss_plane *dcss_plane = to_dcss_plane(p: plane); |
357 | struct dcss_dev *dcss = plane->dev->dev_private; |
358 | |
359 | dcss_dpr_enable(dpr: dcss->dpr, ch_num: dcss_plane->ch_num, en: false); |
360 | dcss_scaler_ch_enable(scl: dcss->scaler, ch_num: dcss_plane->ch_num, en: false); |
361 | dcss_dtg_plane_pos_set(dtg: dcss->dtg, ch_num: dcss_plane->ch_num, px: 0, py: 0, pw: 0, ph: 0); |
362 | dcss_dtg_ch_enable(dtg: dcss->dtg, ch_num: dcss_plane->ch_num, en: false); |
363 | } |
364 | |
365 | static const struct drm_plane_helper_funcs dcss_plane_helper_funcs = { |
366 | .atomic_check = dcss_plane_atomic_check, |
367 | .atomic_update = dcss_plane_atomic_update, |
368 | .atomic_disable = dcss_plane_atomic_disable, |
369 | }; |
370 | |
371 | struct dcss_plane *dcss_plane_init(struct drm_device *drm, |
372 | unsigned int possible_crtcs, |
373 | enum drm_plane_type type, |
374 | unsigned int zpos) |
375 | { |
376 | struct dcss_plane *dcss_plane; |
377 | const u64 *format_modifiers = dcss_video_format_modifiers; |
378 | int ret; |
379 | |
380 | if (zpos > 2) |
381 | return ERR_PTR(error: -EINVAL); |
382 | |
383 | dcss_plane = kzalloc(size: sizeof(*dcss_plane), GFP_KERNEL); |
384 | if (!dcss_plane) { |
385 | DRM_ERROR("failed to allocate plane\n" ); |
386 | return ERR_PTR(error: -ENOMEM); |
387 | } |
388 | |
389 | if (type == DRM_PLANE_TYPE_PRIMARY) |
390 | format_modifiers = dcss_graphics_format_modifiers; |
391 | |
392 | ret = drm_universal_plane_init(dev: drm, plane: &dcss_plane->base, possible_crtcs, |
393 | funcs: &dcss_plane_funcs, formats: dcss_common_formats, |
394 | ARRAY_SIZE(dcss_common_formats), |
395 | format_modifiers, type, NULL); |
396 | if (ret) { |
397 | DRM_ERROR("failed to initialize plane\n" ); |
398 | kfree(objp: dcss_plane); |
399 | return ERR_PTR(error: ret); |
400 | } |
401 | |
402 | drm_plane_helper_add(plane: &dcss_plane->base, funcs: &dcss_plane_helper_funcs); |
403 | |
404 | ret = drm_plane_create_zpos_immutable_property(plane: &dcss_plane->base, zpos); |
405 | if (ret) |
406 | return ERR_PTR(error: ret); |
407 | |
408 | drm_plane_create_scaling_filter_property(plane: &dcss_plane->base, |
409 | BIT(DRM_SCALING_FILTER_DEFAULT) | |
410 | BIT(DRM_SCALING_FILTER_NEAREST_NEIGHBOR)); |
411 | |
412 | drm_plane_create_rotation_property(plane: &dcss_plane->base, |
413 | DRM_MODE_ROTATE_0, |
414 | DRM_MODE_ROTATE_0 | |
415 | DRM_MODE_ROTATE_90 | |
416 | DRM_MODE_ROTATE_180 | |
417 | DRM_MODE_ROTATE_270 | |
418 | DRM_MODE_REFLECT_X | |
419 | DRM_MODE_REFLECT_Y); |
420 | |
421 | dcss_plane->ch_num = zpos; |
422 | |
423 | return dcss_plane; |
424 | } |
425 | |