1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/ |
4 | * Author: Rob Clark <rob@ti.com> |
5 | */ |
6 | |
7 | #include <linux/dma-mapping.h> |
8 | |
9 | #include <drm/drm_blend.h> |
10 | #include <drm/drm_modeset_helper.h> |
11 | #include <drm/drm_fourcc.h> |
12 | #include <drm/drm_framebuffer.h> |
13 | #include <drm/drm_gem_framebuffer_helper.h> |
14 | |
15 | #include "omap_dmm_tiler.h" |
16 | #include "omap_drv.h" |
17 | |
18 | /* |
19 | * framebuffer funcs |
20 | */ |
21 | |
22 | static const u32 formats[] = { |
23 | /* 16bpp [A]RGB: */ |
24 | DRM_FORMAT_RGB565, /* RGB16-565 */ |
25 | DRM_FORMAT_RGBX4444, /* RGB12x-4444 */ |
26 | DRM_FORMAT_XRGB4444, /* xRGB12-4444 */ |
27 | DRM_FORMAT_RGBA4444, /* RGBA12-4444 */ |
28 | DRM_FORMAT_ARGB4444, /* ARGB16-4444 */ |
29 | DRM_FORMAT_XRGB1555, /* xRGB15-1555 */ |
30 | DRM_FORMAT_ARGB1555, /* ARGB16-1555 */ |
31 | /* 24bpp RGB: */ |
32 | DRM_FORMAT_RGB888, /* RGB24-888 */ |
33 | /* 32bpp [A]RGB: */ |
34 | DRM_FORMAT_RGBX8888, /* RGBx24-8888 */ |
35 | DRM_FORMAT_XRGB8888, /* xRGB24-8888 */ |
36 | DRM_FORMAT_RGBA8888, /* RGBA32-8888 */ |
37 | DRM_FORMAT_ARGB8888, /* ARGB32-8888 */ |
38 | /* YUV: */ |
39 | DRM_FORMAT_NV12, |
40 | DRM_FORMAT_YUYV, |
41 | DRM_FORMAT_UYVY, |
42 | }; |
43 | |
44 | /* per-plane info for the fb: */ |
45 | struct plane { |
46 | dma_addr_t dma_addr; |
47 | }; |
48 | |
49 | #define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) |
50 | |
51 | struct omap_framebuffer { |
52 | struct drm_framebuffer base; |
53 | int pin_count; |
54 | const struct drm_format_info *format; |
55 | struct plane planes[2]; |
56 | /* lock for pinning (pin_count and planes.dma_addr) */ |
57 | struct mutex lock; |
58 | }; |
59 | |
60 | static int omap_framebuffer_dirty(struct drm_framebuffer *fb, |
61 | struct drm_file *file_priv, |
62 | unsigned flags, unsigned color, |
63 | struct drm_clip_rect *clips, |
64 | unsigned num_clips) |
65 | { |
66 | struct drm_crtc *crtc; |
67 | |
68 | drm_modeset_lock_all(dev: fb->dev); |
69 | |
70 | drm_for_each_crtc(crtc, fb->dev) |
71 | omap_crtc_flush(crtc); |
72 | |
73 | drm_modeset_unlock_all(dev: fb->dev); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static const struct drm_framebuffer_funcs omap_framebuffer_funcs = { |
79 | .create_handle = drm_gem_fb_create_handle, |
80 | .dirty = omap_framebuffer_dirty, |
81 | .destroy = drm_gem_fb_destroy, |
82 | }; |
83 | |
84 | static u32 get_linear_addr(struct drm_framebuffer *fb, |
85 | const struct drm_format_info *format, int n, int x, int y) |
86 | { |
87 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); |
88 | struct plane *plane = &omap_fb->planes[n]; |
89 | u32 offset; |
90 | |
91 | offset = fb->offsets[n] |
92 | + (x * format->cpp[n] / (n == 0 ? 1 : format->hsub)) |
93 | + (y * fb->pitches[n] / (n == 0 ? 1 : format->vsub)); |
94 | |
95 | return plane->dma_addr + offset; |
96 | } |
97 | |
98 | bool omap_framebuffer_supports_rotation(struct drm_framebuffer *fb) |
99 | { |
100 | return omap_gem_flags(obj: fb->obj[0]) & OMAP_BO_TILED_MASK; |
101 | } |
102 | |
103 | /* Note: DRM rotates counter-clockwise, TILER & DSS rotates clockwise */ |
104 | static u32 drm_rotation_to_tiler(unsigned int drm_rot) |
105 | { |
106 | u32 orient; |
107 | |
108 | switch (drm_rot & DRM_MODE_ROTATE_MASK) { |
109 | default: |
110 | case DRM_MODE_ROTATE_0: |
111 | orient = 0; |
112 | break; |
113 | case DRM_MODE_ROTATE_90: |
114 | orient = MASK_XY_FLIP | MASK_X_INVERT; |
115 | break; |
116 | case DRM_MODE_ROTATE_180: |
117 | orient = MASK_X_INVERT | MASK_Y_INVERT; |
118 | break; |
119 | case DRM_MODE_ROTATE_270: |
120 | orient = MASK_XY_FLIP | MASK_Y_INVERT; |
121 | break; |
122 | } |
123 | |
124 | if (drm_rot & DRM_MODE_REFLECT_X) |
125 | orient ^= MASK_X_INVERT; |
126 | |
127 | if (drm_rot & DRM_MODE_REFLECT_Y) |
128 | orient ^= MASK_Y_INVERT; |
129 | |
130 | return orient; |
131 | } |
132 | |
133 | /* update ovl info for scanout, handles cases of multi-planar fb's, etc. |
134 | */ |
135 | void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, |
136 | struct drm_plane_state *state, |
137 | struct omap_overlay_info *info, |
138 | struct omap_overlay_info *r_info) |
139 | { |
140 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); |
141 | const struct drm_format_info *format = omap_fb->format; |
142 | u32 x, y, orient = 0; |
143 | |
144 | info->fourcc = fb->format->format; |
145 | |
146 | info->pos_x = state->crtc_x; |
147 | info->pos_y = state->crtc_y; |
148 | info->out_width = state->crtc_w; |
149 | info->out_height = state->crtc_h; |
150 | info->width = state->src_w >> 16; |
151 | info->height = state->src_h >> 16; |
152 | |
153 | /* DSS driver wants the w & h in rotated orientation */ |
154 | if (drm_rotation_90_or_270(rotation: state->rotation)) |
155 | swap(info->width, info->height); |
156 | |
157 | x = state->src_x >> 16; |
158 | y = state->src_y >> 16; |
159 | |
160 | if (omap_gem_flags(obj: fb->obj[0]) & OMAP_BO_TILED_MASK) { |
161 | u32 w = state->src_w >> 16; |
162 | u32 h = state->src_h >> 16; |
163 | |
164 | orient = drm_rotation_to_tiler(drm_rot: state->rotation); |
165 | |
166 | /* |
167 | * omap_gem_rotated_paddr() wants the x & y in tiler units. |
168 | * Usually tiler unit size is the same as the pixel size, except |
169 | * for YUV422 formats, for which the tiler unit size is 32 bits |
170 | * and pixel size is 16 bits. |
171 | */ |
172 | if (fb->format->format == DRM_FORMAT_UYVY || |
173 | fb->format->format == DRM_FORMAT_YUYV) { |
174 | x /= 2; |
175 | w /= 2; |
176 | } |
177 | |
178 | /* adjust x,y offset for invert: */ |
179 | if (orient & MASK_Y_INVERT) |
180 | y += h - 1; |
181 | if (orient & MASK_X_INVERT) |
182 | x += w - 1; |
183 | |
184 | /* Note: x and y are in TILER units, not pixels */ |
185 | omap_gem_rotated_dma_addr(obj: fb->obj[0], orient, x, y, |
186 | dma_addr: &info->paddr); |
187 | info->rotation_type = OMAP_DSS_ROT_TILER; |
188 | info->rotation = state->rotation ?: DRM_MODE_ROTATE_0; |
189 | /* Note: stride in TILER units, not pixels */ |
190 | info->screen_width = omap_gem_tiled_stride(obj: fb->obj[0], orient); |
191 | } else { |
192 | switch (state->rotation & DRM_MODE_ROTATE_MASK) { |
193 | case 0: |
194 | case DRM_MODE_ROTATE_0: |
195 | /* OK */ |
196 | break; |
197 | |
198 | default: |
199 | dev_warn(fb->dev->dev, |
200 | "rotation '%d' ignored for non-tiled fb\n" , |
201 | state->rotation); |
202 | break; |
203 | } |
204 | |
205 | info->paddr = get_linear_addr(fb, format, n: 0, x, y); |
206 | info->rotation_type = OMAP_DSS_ROT_NONE; |
207 | info->rotation = DRM_MODE_ROTATE_0; |
208 | info->screen_width = fb->pitches[0]; |
209 | } |
210 | |
211 | /* convert to pixels: */ |
212 | info->screen_width /= format->cpp[0]; |
213 | |
214 | if (fb->format->format == DRM_FORMAT_NV12) { |
215 | if (info->rotation_type == OMAP_DSS_ROT_TILER) { |
216 | WARN_ON(!(omap_gem_flags(fb->obj[1]) & OMAP_BO_TILED_MASK)); |
217 | omap_gem_rotated_dma_addr(obj: fb->obj[1], orient, x: x/2, y: y/2, |
218 | dma_addr: &info->p_uv_addr); |
219 | } else { |
220 | info->p_uv_addr = get_linear_addr(fb, format, n: 1, x, y); |
221 | } |
222 | } else { |
223 | info->p_uv_addr = 0; |
224 | } |
225 | |
226 | if (r_info) { |
227 | info->width /= 2; |
228 | info->out_width /= 2; |
229 | |
230 | *r_info = *info; |
231 | |
232 | if (fb->format->is_yuv) { |
233 | if (info->width & 1) { |
234 | info->width++; |
235 | r_info->width--; |
236 | } |
237 | |
238 | if (info->out_width & 1) { |
239 | info->out_width++; |
240 | r_info->out_width--; |
241 | } |
242 | } |
243 | |
244 | r_info->pos_x = info->pos_x + info->out_width; |
245 | |
246 | r_info->paddr = get_linear_addr(fb, format, n: 0, |
247 | x: x + info->width, y); |
248 | if (fb->format->format == DRM_FORMAT_NV12) { |
249 | r_info->p_uv_addr = |
250 | get_linear_addr(fb, format, n: 1, |
251 | x: x + info->width, y); |
252 | } |
253 | } |
254 | } |
255 | |
256 | /* pin, prepare for scanout: */ |
257 | int omap_framebuffer_pin(struct drm_framebuffer *fb) |
258 | { |
259 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); |
260 | int ret, i, n = fb->format->num_planes; |
261 | |
262 | mutex_lock(&omap_fb->lock); |
263 | |
264 | if (omap_fb->pin_count > 0) { |
265 | omap_fb->pin_count++; |
266 | mutex_unlock(lock: &omap_fb->lock); |
267 | return 0; |
268 | } |
269 | |
270 | for (i = 0; i < n; i++) { |
271 | struct plane *plane = &omap_fb->planes[i]; |
272 | ret = omap_gem_pin(obj: fb->obj[i], dma_addr: &plane->dma_addr); |
273 | if (ret) |
274 | goto fail; |
275 | omap_gem_dma_sync_buffer(obj: fb->obj[i], dir: DMA_TO_DEVICE); |
276 | } |
277 | |
278 | omap_fb->pin_count++; |
279 | |
280 | mutex_unlock(lock: &omap_fb->lock); |
281 | |
282 | return 0; |
283 | |
284 | fail: |
285 | for (i--; i >= 0; i--) { |
286 | struct plane *plane = &omap_fb->planes[i]; |
287 | omap_gem_unpin(obj: fb->obj[i]); |
288 | plane->dma_addr = 0; |
289 | } |
290 | |
291 | mutex_unlock(lock: &omap_fb->lock); |
292 | |
293 | return ret; |
294 | } |
295 | |
296 | /* unpin, no longer being scanned out: */ |
297 | void omap_framebuffer_unpin(struct drm_framebuffer *fb) |
298 | { |
299 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); |
300 | int i, n = fb->format->num_planes; |
301 | |
302 | mutex_lock(&omap_fb->lock); |
303 | |
304 | omap_fb->pin_count--; |
305 | |
306 | if (omap_fb->pin_count > 0) { |
307 | mutex_unlock(lock: &omap_fb->lock); |
308 | return; |
309 | } |
310 | |
311 | for (i = 0; i < n; i++) { |
312 | struct plane *plane = &omap_fb->planes[i]; |
313 | omap_gem_unpin(obj: fb->obj[i]); |
314 | plane->dma_addr = 0; |
315 | } |
316 | |
317 | mutex_unlock(lock: &omap_fb->lock); |
318 | } |
319 | |
320 | #ifdef CONFIG_DEBUG_FS |
321 | void omap_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m) |
322 | { |
323 | int i, n = fb->format->num_planes; |
324 | |
325 | seq_printf(m, fmt: "fb: %dx%d@%4.4s\n" , fb->width, fb->height, |
326 | (char *)&fb->format->format); |
327 | |
328 | for (i = 0; i < n; i++) { |
329 | seq_printf(m, fmt: " %d: offset=%d pitch=%d, obj: " , |
330 | i, fb->offsets[n], fb->pitches[i]); |
331 | omap_gem_describe(obj: fb->obj[i], m); |
332 | } |
333 | } |
334 | #endif |
335 | |
336 | struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev, |
337 | struct drm_file *file, const struct drm_mode_fb_cmd2 *mode_cmd) |
338 | { |
339 | const struct drm_format_info *info = drm_get_format_info(dev, |
340 | mode_cmd); |
341 | unsigned int num_planes = info->num_planes; |
342 | struct drm_gem_object *bos[4]; |
343 | struct drm_framebuffer *fb; |
344 | int i; |
345 | |
346 | for (i = 0; i < num_planes; i++) { |
347 | bos[i] = drm_gem_object_lookup(filp: file, handle: mode_cmd->handles[i]); |
348 | if (!bos[i]) { |
349 | fb = ERR_PTR(error: -ENOENT); |
350 | goto error; |
351 | } |
352 | } |
353 | |
354 | fb = omap_framebuffer_init(dev, mode_cmd, bos); |
355 | if (IS_ERR(ptr: fb)) |
356 | goto error; |
357 | |
358 | return fb; |
359 | |
360 | error: |
361 | while (--i >= 0) |
362 | drm_gem_object_put(obj: bos[i]); |
363 | |
364 | return fb; |
365 | } |
366 | |
367 | struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, |
368 | const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos) |
369 | { |
370 | const struct drm_format_info *format = NULL; |
371 | struct omap_framebuffer *omap_fb = NULL; |
372 | struct drm_framebuffer *fb = NULL; |
373 | unsigned int pitch = mode_cmd->pitches[0]; |
374 | int ret, i; |
375 | |
376 | DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%4.4s)" , |
377 | dev, mode_cmd, mode_cmd->width, mode_cmd->height, |
378 | (char *)&mode_cmd->pixel_format); |
379 | |
380 | format = drm_get_format_info(dev, mode_cmd); |
381 | |
382 | for (i = 0; i < ARRAY_SIZE(formats); i++) { |
383 | if (formats[i] == mode_cmd->pixel_format) |
384 | break; |
385 | } |
386 | |
387 | if (!format || i == ARRAY_SIZE(formats)) { |
388 | dev_dbg(dev->dev, "unsupported pixel format: %4.4s\n" , |
389 | (char *)&mode_cmd->pixel_format); |
390 | ret = -EINVAL; |
391 | goto fail; |
392 | } |
393 | |
394 | omap_fb = kzalloc(size: sizeof(*omap_fb), GFP_KERNEL); |
395 | if (!omap_fb) { |
396 | ret = -ENOMEM; |
397 | goto fail; |
398 | } |
399 | |
400 | fb = &omap_fb->base; |
401 | omap_fb->format = format; |
402 | mutex_init(&omap_fb->lock); |
403 | |
404 | /* |
405 | * The code below assumes that no format use more than two planes, and |
406 | * that the two planes of multiplane formats need the same number of |
407 | * bytes per pixel. |
408 | */ |
409 | if (format->num_planes == 2 && pitch != mode_cmd->pitches[1]) { |
410 | dev_dbg(dev->dev, "pitches differ between planes 0 and 1\n" ); |
411 | ret = -EINVAL; |
412 | goto fail; |
413 | } |
414 | |
415 | if (pitch % format->cpp[0]) { |
416 | dev_dbg(dev->dev, |
417 | "buffer pitch (%u bytes) is not a multiple of pixel size (%u bytes)\n" , |
418 | pitch, format->cpp[0]); |
419 | ret = -EINVAL; |
420 | goto fail; |
421 | } |
422 | |
423 | for (i = 0; i < format->num_planes; i++) { |
424 | struct plane *plane = &omap_fb->planes[i]; |
425 | unsigned int vsub = i == 0 ? 1 : format->vsub; |
426 | unsigned int size; |
427 | |
428 | size = pitch * mode_cmd->height / vsub; |
429 | |
430 | if (size > omap_gem_mmap_size(obj: bos[i]) - mode_cmd->offsets[i]) { |
431 | dev_dbg(dev->dev, |
432 | "provided buffer object is too small! %zu < %d\n" , |
433 | bos[i]->size - mode_cmd->offsets[i], size); |
434 | ret = -EINVAL; |
435 | goto fail; |
436 | } |
437 | |
438 | fb->obj[i] = bos[i]; |
439 | plane->dma_addr = 0; |
440 | } |
441 | |
442 | drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd); |
443 | |
444 | ret = drm_framebuffer_init(dev, fb, funcs: &omap_framebuffer_funcs); |
445 | if (ret) { |
446 | dev_err(dev->dev, "framebuffer init failed: %d\n" , ret); |
447 | goto fail; |
448 | } |
449 | |
450 | DBG("create: FB ID: %d (%p)" , fb->base.id, fb); |
451 | |
452 | return fb; |
453 | |
454 | fail: |
455 | kfree(objp: omap_fb); |
456 | |
457 | return ERR_PTR(error: ret); |
458 | } |
459 | |