]>
Commit | Line | Data |
---|---|---|
c8b75bca EA |
1 | /* |
2 | * Copyright (C) 2015 Broadcom | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 as | |
6 | * published by the Free Software Foundation. | |
7 | */ | |
8 | ||
9 | /** | |
10 | * DOC: VC4 plane module | |
11 | * | |
12 | * Each DRM plane is a layer of pixels being scanned out by the HVS. | |
13 | * | |
14 | * At atomic modeset check time, we compute the HVS display element | |
15 | * state that would be necessary for displaying the plane (giving us a | |
16 | * chance to figure out if a plane configuration is invalid), then at | |
17 | * atomic flush time the CRTC will ask us to write our element state | |
18 | * into the region of the HVS that it has allocated for us. | |
19 | */ | |
20 | ||
21 | #include "vc4_drv.h" | |
22 | #include "vc4_regs.h" | |
23 | #include "drm_atomic_helper.h" | |
24 | #include "drm_fb_cma_helper.h" | |
25 | #include "drm_plane_helper.h" | |
26 | ||
27 | struct vc4_plane_state { | |
28 | struct drm_plane_state base; | |
f427fb16 EA |
29 | /* System memory copy of the display list for this element, computed |
30 | * at atomic_check time. | |
31 | */ | |
c8b75bca | 32 | u32 *dlist; |
f427fb16 | 33 | u32 dlist_size; /* Number of dwords allocated for the display list */ |
c8b75bca | 34 | u32 dlist_count; /* Number of used dwords in the display list. */ |
b501bacc | 35 | |
6674a904 EA |
36 | /* Offset in the dlist to various words, for pageflip or |
37 | * cursor updates. | |
38 | */ | |
39 | u32 pos0_offset; | |
40 | u32 pos2_offset; | |
41 | u32 ptr0_offset; | |
b501bacc EA |
42 | |
43 | /* Offset where the plane's dlist was last stored in the | |
f427fb16 EA |
44 | * hardware at vc4_crtc_atomic_flush() time. |
45 | */ | |
17eac751 | 46 | u32 __iomem *hw_dlist; |
5c679994 EA |
47 | |
48 | /* Clipped coordinates of the plane on the display. */ | |
49 | int crtc_x, crtc_y, crtc_w, crtc_h; | |
f863e356 EA |
50 | /* Clipped size of the area scanned from in the FB. */ |
51 | u32 src_w, src_h; | |
5c679994 EA |
52 | |
53 | /* Offset to start scanning out from the start of the plane's | |
54 | * BO. | |
55 | */ | |
56 | u32 offset; | |
c8b75bca EA |
57 | }; |
58 | ||
59 | static inline struct vc4_plane_state * | |
60 | to_vc4_plane_state(struct drm_plane_state *state) | |
61 | { | |
62 | return (struct vc4_plane_state *)state; | |
63 | } | |
64 | ||
65 | static const struct hvs_format { | |
66 | u32 drm; /* DRM_FORMAT_* */ | |
67 | u32 hvs; /* HVS_FORMAT_* */ | |
68 | u32 pixel_order; | |
69 | bool has_alpha; | |
70 | } hvs_formats[] = { | |
71 | { | |
72 | .drm = DRM_FORMAT_XRGB8888, .hvs = HVS_PIXEL_FORMAT_RGBA8888, | |
73 | .pixel_order = HVS_PIXEL_ORDER_ABGR, .has_alpha = false, | |
74 | }, | |
75 | { | |
76 | .drm = DRM_FORMAT_ARGB8888, .hvs = HVS_PIXEL_FORMAT_RGBA8888, | |
77 | .pixel_order = HVS_PIXEL_ORDER_ABGR, .has_alpha = true, | |
78 | }, | |
79 | }; | |
80 | ||
81 | static const struct hvs_format *vc4_get_hvs_format(u32 drm_format) | |
82 | { | |
83 | unsigned i; | |
84 | ||
85 | for (i = 0; i < ARRAY_SIZE(hvs_formats); i++) { | |
86 | if (hvs_formats[i].drm == drm_format) | |
87 | return &hvs_formats[i]; | |
88 | } | |
89 | ||
90 | return NULL; | |
91 | } | |
92 | ||
93 | static bool plane_enabled(struct drm_plane_state *state) | |
94 | { | |
95 | return state->fb && state->crtc; | |
96 | } | |
97 | ||
91276ae2 | 98 | static struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane) |
c8b75bca EA |
99 | { |
100 | struct vc4_plane_state *vc4_state; | |
101 | ||
102 | if (WARN_ON(!plane->state)) | |
103 | return NULL; | |
104 | ||
105 | vc4_state = kmemdup(plane->state, sizeof(*vc4_state), GFP_KERNEL); | |
106 | if (!vc4_state) | |
107 | return NULL; | |
108 | ||
109 | __drm_atomic_helper_plane_duplicate_state(plane, &vc4_state->base); | |
110 | ||
111 | if (vc4_state->dlist) { | |
112 | vc4_state->dlist = kmemdup(vc4_state->dlist, | |
113 | vc4_state->dlist_count * 4, | |
114 | GFP_KERNEL); | |
115 | if (!vc4_state->dlist) { | |
116 | kfree(vc4_state); | |
117 | return NULL; | |
118 | } | |
119 | vc4_state->dlist_size = vc4_state->dlist_count; | |
120 | } | |
121 | ||
122 | return &vc4_state->base; | |
123 | } | |
124 | ||
91276ae2 | 125 | static void vc4_plane_destroy_state(struct drm_plane *plane, |
126 | struct drm_plane_state *state) | |
c8b75bca EA |
127 | { |
128 | struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); | |
129 | ||
130 | kfree(vc4_state->dlist); | |
131 | __drm_atomic_helper_plane_destroy_state(plane, &vc4_state->base); | |
132 | kfree(state); | |
133 | } | |
134 | ||
135 | /* Called during init to allocate the plane's atomic state. */ | |
91276ae2 | 136 | static void vc4_plane_reset(struct drm_plane *plane) |
c8b75bca EA |
137 | { |
138 | struct vc4_plane_state *vc4_state; | |
139 | ||
140 | WARN_ON(plane->state); | |
141 | ||
142 | vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL); | |
143 | if (!vc4_state) | |
144 | return; | |
145 | ||
146 | plane->state = &vc4_state->base; | |
147 | vc4_state->base.plane = plane; | |
148 | } | |
149 | ||
150 | static void vc4_dlist_write(struct vc4_plane_state *vc4_state, u32 val) | |
151 | { | |
152 | if (vc4_state->dlist_count == vc4_state->dlist_size) { | |
153 | u32 new_size = max(4u, vc4_state->dlist_count * 2); | |
154 | u32 *new_dlist = kmalloc(new_size * 4, GFP_KERNEL); | |
155 | ||
156 | if (!new_dlist) | |
157 | return; | |
158 | memcpy(new_dlist, vc4_state->dlist, vc4_state->dlist_count * 4); | |
159 | ||
160 | kfree(vc4_state->dlist); | |
161 | vc4_state->dlist = new_dlist; | |
162 | vc4_state->dlist_size = new_size; | |
163 | } | |
164 | ||
165 | vc4_state->dlist[vc4_state->dlist_count++] = val; | |
166 | } | |
167 | ||
5c679994 | 168 | static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state) |
c8b75bca EA |
169 | { |
170 | struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); | |
171 | struct drm_framebuffer *fb = state->fb; | |
5c679994 EA |
172 | |
173 | vc4_state->offset = fb->offsets[0]; | |
174 | ||
bf893acc EA |
175 | if (state->crtc_w << 16 != state->src_w || |
176 | state->crtc_h << 16 != state->src_h) { | |
177 | /* We don't support scaling yet, which involves | |
178 | * allocating the LBM memory for scaling temporary | |
179 | * storage, and putting filter kernels in the HVS | |
180 | * context. | |
181 | */ | |
182 | return -EINVAL; | |
183 | } | |
184 | ||
f863e356 EA |
185 | vc4_state->src_w = state->src_w >> 16; |
186 | vc4_state->src_h = state->src_h >> 16; | |
187 | ||
188 | vc4_state->crtc_x = state->crtc_x; | |
189 | vc4_state->crtc_y = state->crtc_y; | |
190 | vc4_state->crtc_w = state->crtc_w; | |
191 | vc4_state->crtc_h = state->crtc_h; | |
192 | ||
5c679994 EA |
193 | if (vc4_state->crtc_x < 0) { |
194 | vc4_state->offset += (drm_format_plane_cpp(fb->pixel_format, | |
195 | 0) * | |
196 | -vc4_state->crtc_x); | |
f863e356 | 197 | vc4_state->src_w += vc4_state->crtc_x; |
5c679994 | 198 | vc4_state->crtc_x = 0; |
c8b75bca EA |
199 | } |
200 | ||
5c679994 EA |
201 | if (vc4_state->crtc_y < 0) { |
202 | vc4_state->offset += fb->pitches[0] * -vc4_state->crtc_y; | |
f863e356 | 203 | vc4_state->src_h += vc4_state->crtc_y; |
5c679994 | 204 | vc4_state->crtc_y = 0; |
c8b75bca EA |
205 | } |
206 | ||
5c679994 EA |
207 | return 0; |
208 | } | |
209 | ||
210 | ||
211 | /* Writes out a full display list for an active plane to the plane's | |
212 | * private dlist state. | |
213 | */ | |
214 | static int vc4_plane_mode_set(struct drm_plane *plane, | |
215 | struct drm_plane_state *state) | |
216 | { | |
217 | struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); | |
218 | struct drm_framebuffer *fb = state->fb; | |
219 | struct drm_gem_cma_object *bo = drm_fb_cma_get_gem_obj(fb, 0); | |
220 | u32 ctl0_offset = vc4_state->dlist_count; | |
221 | const struct hvs_format *format = vc4_get_hvs_format(fb->pixel_format); | |
222 | int ret; | |
223 | ||
224 | ret = vc4_plane_setup_clipping_and_scaling(state); | |
225 | if (ret) | |
226 | return ret; | |
227 | ||
c8b75bca EA |
228 | vc4_dlist_write(vc4_state, |
229 | SCALER_CTL0_VALID | | |
230 | (format->pixel_order << SCALER_CTL0_ORDER_SHIFT) | | |
231 | (format->hvs << SCALER_CTL0_PIXEL_FORMAT_SHIFT) | | |
232 | SCALER_CTL0_UNITY); | |
233 | ||
234 | /* Position Word 0: Image Positions and Alpha Value */ | |
6674a904 | 235 | vc4_state->pos0_offset = vc4_state->dlist_count; |
c8b75bca EA |
236 | vc4_dlist_write(vc4_state, |
237 | VC4_SET_FIELD(0xff, SCALER_POS0_FIXED_ALPHA) | | |
5c679994 EA |
238 | VC4_SET_FIELD(vc4_state->crtc_x, SCALER_POS0_START_X) | |
239 | VC4_SET_FIELD(vc4_state->crtc_y, SCALER_POS0_START_Y)); | |
c8b75bca EA |
240 | |
241 | /* Position Word 1: Scaled Image Dimensions. | |
242 | * Skipped due to SCALER_CTL0_UNITY scaling. | |
243 | */ | |
244 | ||
245 | /* Position Word 2: Source Image Size, Alpha Mode */ | |
6674a904 | 246 | vc4_state->pos2_offset = vc4_state->dlist_count; |
c8b75bca EA |
247 | vc4_dlist_write(vc4_state, |
248 | VC4_SET_FIELD(format->has_alpha ? | |
249 | SCALER_POS2_ALPHA_MODE_PIPELINE : | |
250 | SCALER_POS2_ALPHA_MODE_FIXED, | |
251 | SCALER_POS2_ALPHA_MODE) | | |
f863e356 EA |
252 | VC4_SET_FIELD(vc4_state->src_w, SCALER_POS2_WIDTH) | |
253 | VC4_SET_FIELD(vc4_state->src_h, SCALER_POS2_HEIGHT)); | |
c8b75bca EA |
254 | |
255 | /* Position Word 3: Context. Written by the HVS. */ | |
256 | vc4_dlist_write(vc4_state, 0xc0c0c0c0); | |
257 | ||
258 | /* Pointer Word 0: RGB / Y Pointer */ | |
6674a904 | 259 | vc4_state->ptr0_offset = vc4_state->dlist_count; |
5c679994 | 260 | vc4_dlist_write(vc4_state, bo->paddr + vc4_state->offset); |
c8b75bca EA |
261 | |
262 | /* Pointer Context Word 0: Written by the HVS */ | |
263 | vc4_dlist_write(vc4_state, 0xc0c0c0c0); | |
264 | ||
265 | /* Pitch word 0: Pointer 0 Pitch */ | |
266 | vc4_dlist_write(vc4_state, | |
267 | VC4_SET_FIELD(fb->pitches[0], SCALER_SRC_PITCH)); | |
268 | ||
269 | vc4_state->dlist[ctl0_offset] |= | |
270 | VC4_SET_FIELD(vc4_state->dlist_count, SCALER_CTL0_SIZE); | |
271 | ||
272 | return 0; | |
273 | } | |
274 | ||
275 | /* If a modeset involves changing the setup of a plane, the atomic | |
276 | * infrastructure will call this to validate a proposed plane setup. | |
277 | * However, if a plane isn't getting updated, this (and the | |
278 | * corresponding vc4_plane_atomic_update) won't get called. Thus, we | |
279 | * compute the dlist here and have all active plane dlists get updated | |
280 | * in the CRTC's flush. | |
281 | */ | |
282 | static int vc4_plane_atomic_check(struct drm_plane *plane, | |
283 | struct drm_plane_state *state) | |
284 | { | |
285 | struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); | |
286 | ||
287 | vc4_state->dlist_count = 0; | |
288 | ||
289 | if (plane_enabled(state)) | |
290 | return vc4_plane_mode_set(plane, state); | |
291 | else | |
292 | return 0; | |
293 | } | |
294 | ||
295 | static void vc4_plane_atomic_update(struct drm_plane *plane, | |
296 | struct drm_plane_state *old_state) | |
297 | { | |
298 | /* No contents here. Since we don't know where in the CRTC's | |
299 | * dlist we should be stored, our dlist is uploaded to the | |
300 | * hardware with vc4_plane_write_dlist() at CRTC atomic_flush | |
301 | * time. | |
302 | */ | |
303 | } | |
304 | ||
305 | u32 vc4_plane_write_dlist(struct drm_plane *plane, u32 __iomem *dlist) | |
306 | { | |
307 | struct vc4_plane_state *vc4_state = to_vc4_plane_state(plane->state); | |
308 | int i; | |
309 | ||
b501bacc EA |
310 | vc4_state->hw_dlist = dlist; |
311 | ||
c8b75bca EA |
312 | /* Can't memcpy_toio() because it needs to be 32-bit writes. */ |
313 | for (i = 0; i < vc4_state->dlist_count; i++) | |
314 | writel(vc4_state->dlist[i], &dlist[i]); | |
315 | ||
316 | return vc4_state->dlist_count; | |
317 | } | |
318 | ||
319 | u32 vc4_plane_dlist_size(struct drm_plane_state *state) | |
320 | { | |
321 | struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); | |
322 | ||
323 | return vc4_state->dlist_count; | |
324 | } | |
325 | ||
b501bacc EA |
326 | /* Updates the plane to immediately (well, once the FIFO needs |
327 | * refilling) scan out from at a new framebuffer. | |
328 | */ | |
329 | void vc4_plane_async_set_fb(struct drm_plane *plane, struct drm_framebuffer *fb) | |
330 | { | |
331 | struct vc4_plane_state *vc4_state = to_vc4_plane_state(plane->state); | |
332 | struct drm_gem_cma_object *bo = drm_fb_cma_get_gem_obj(fb, 0); | |
333 | uint32_t addr; | |
334 | ||
335 | /* We're skipping the address adjustment for negative origin, | |
336 | * because this is only called on the primary plane. | |
337 | */ | |
338 | WARN_ON_ONCE(plane->state->crtc_x < 0 || plane->state->crtc_y < 0); | |
339 | addr = bo->paddr + fb->offsets[0]; | |
340 | ||
341 | /* Write the new address into the hardware immediately. The | |
342 | * scanout will start from this address as soon as the FIFO | |
343 | * needs to refill with pixels. | |
344 | */ | |
6674a904 | 345 | writel(addr, &vc4_state->hw_dlist[vc4_state->ptr0_offset]); |
b501bacc EA |
346 | |
347 | /* Also update the CPU-side dlist copy, so that any later | |
348 | * atomic updates that don't do a new modeset on our plane | |
349 | * also use our updated address. | |
350 | */ | |
6674a904 | 351 | vc4_state->dlist[vc4_state->ptr0_offset] = addr; |
b501bacc EA |
352 | } |
353 | ||
c8b75bca EA |
354 | static const struct drm_plane_helper_funcs vc4_plane_helper_funcs = { |
355 | .prepare_fb = NULL, | |
356 | .cleanup_fb = NULL, | |
357 | .atomic_check = vc4_plane_atomic_check, | |
358 | .atomic_update = vc4_plane_atomic_update, | |
359 | }; | |
360 | ||
361 | static void vc4_plane_destroy(struct drm_plane *plane) | |
362 | { | |
363 | drm_plane_helper_disable(plane); | |
364 | drm_plane_cleanup(plane); | |
365 | } | |
366 | ||
6674a904 EA |
367 | /* Implements immediate (non-vblank-synced) updates of the cursor |
368 | * position, or falls back to the atomic helper otherwise. | |
369 | */ | |
370 | static int | |
371 | vc4_update_plane(struct drm_plane *plane, | |
372 | struct drm_crtc *crtc, | |
373 | struct drm_framebuffer *fb, | |
374 | int crtc_x, int crtc_y, | |
375 | unsigned int crtc_w, unsigned int crtc_h, | |
376 | uint32_t src_x, uint32_t src_y, | |
377 | uint32_t src_w, uint32_t src_h) | |
378 | { | |
379 | struct drm_plane_state *plane_state; | |
380 | struct vc4_plane_state *vc4_state; | |
381 | ||
382 | if (plane != crtc->cursor) | |
383 | goto out; | |
384 | ||
385 | plane_state = plane->state; | |
386 | vc4_state = to_vc4_plane_state(plane_state); | |
387 | ||
388 | if (!plane_state) | |
389 | goto out; | |
390 | ||
391 | /* If we're changing the cursor contents, do that in the | |
392 | * normal vblank-synced atomic path. | |
393 | */ | |
394 | if (fb != plane_state->fb) | |
395 | goto out; | |
396 | ||
397 | /* No configuring new scaling in the fast path. */ | |
398 | if (crtc_w != plane_state->crtc_w || | |
399 | crtc_h != plane_state->crtc_h || | |
400 | src_w != plane_state->src_w || | |
401 | src_h != plane_state->src_h) { | |
402 | goto out; | |
403 | } | |
404 | ||
405 | /* Set the cursor's position on the screen. This is the | |
406 | * expected change from the drm_mode_cursor_universal() | |
407 | * helper. | |
408 | */ | |
409 | plane_state->crtc_x = crtc_x; | |
410 | plane_state->crtc_y = crtc_y; | |
411 | ||
412 | /* Allow changing the start position within the cursor BO, if | |
413 | * that matters. | |
414 | */ | |
415 | plane_state->src_x = src_x; | |
416 | plane_state->src_y = src_y; | |
417 | ||
418 | /* Update the display list based on the new crtc_x/y. */ | |
419 | vc4_plane_atomic_check(plane, plane_state); | |
420 | ||
421 | /* Note that we can't just call vc4_plane_write_dlist() | |
422 | * because that would smash the context data that the HVS is | |
423 | * currently using. | |
424 | */ | |
425 | writel(vc4_state->dlist[vc4_state->pos0_offset], | |
426 | &vc4_state->hw_dlist[vc4_state->pos0_offset]); | |
427 | writel(vc4_state->dlist[vc4_state->pos2_offset], | |
428 | &vc4_state->hw_dlist[vc4_state->pos2_offset]); | |
429 | writel(vc4_state->dlist[vc4_state->ptr0_offset], | |
430 | &vc4_state->hw_dlist[vc4_state->ptr0_offset]); | |
431 | ||
432 | return 0; | |
433 | ||
434 | out: | |
435 | return drm_atomic_helper_update_plane(plane, crtc, fb, | |
436 | crtc_x, crtc_y, | |
437 | crtc_w, crtc_h, | |
438 | src_x, src_y, | |
439 | src_w, src_h); | |
440 | } | |
441 | ||
c8b75bca | 442 | static const struct drm_plane_funcs vc4_plane_funcs = { |
6674a904 | 443 | .update_plane = vc4_update_plane, |
c8b75bca EA |
444 | .disable_plane = drm_atomic_helper_disable_plane, |
445 | .destroy = vc4_plane_destroy, | |
446 | .set_property = NULL, | |
447 | .reset = vc4_plane_reset, | |
448 | .atomic_duplicate_state = vc4_plane_duplicate_state, | |
449 | .atomic_destroy_state = vc4_plane_destroy_state, | |
450 | }; | |
451 | ||
452 | struct drm_plane *vc4_plane_init(struct drm_device *dev, | |
453 | enum drm_plane_type type) | |
454 | { | |
455 | struct drm_plane *plane = NULL; | |
456 | struct vc4_plane *vc4_plane; | |
457 | u32 formats[ARRAY_SIZE(hvs_formats)]; | |
458 | int ret = 0; | |
459 | unsigned i; | |
460 | ||
461 | vc4_plane = devm_kzalloc(dev->dev, sizeof(*vc4_plane), | |
462 | GFP_KERNEL); | |
463 | if (!vc4_plane) { | |
464 | ret = -ENOMEM; | |
465 | goto fail; | |
466 | } | |
467 | ||
468 | for (i = 0; i < ARRAY_SIZE(hvs_formats); i++) | |
469 | formats[i] = hvs_formats[i].drm; | |
470 | plane = &vc4_plane->base; | |
471 | ret = drm_universal_plane_init(dev, plane, 0xff, | |
472 | &vc4_plane_funcs, | |
473 | formats, ARRAY_SIZE(formats), | |
b0b3b795 | 474 | type, NULL); |
c8b75bca EA |
475 | |
476 | drm_plane_helper_add(plane, &vc4_plane_helper_funcs); | |
477 | ||
478 | return plane; | |
479 | fail: | |
480 | if (plane) | |
481 | vc4_plane_destroy(plane); | |
482 | ||
483 | return ERR_PTR(ret); | |
484 | } |