]>
Commit | Line | Data |
---|---|---|
bb5c2d9a RC |
1 | /* |
2 | * drivers/staging/omapdrm/omap_plane.c | |
3 | * | |
4 | * Copyright (C) 2011 Texas Instruments | |
5 | * Author: Rob Clark <rob.clark@linaro.org> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License version 2 as published by | |
9 | * the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
14 | * more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along with | |
17 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
b33f34d3 RC |
20 | #include <linux/kfifo.h> |
21 | ||
bb5c2d9a RC |
22 | #include "omap_drv.h" |
23 | ||
24 | /* some hackery because omapdss has an 'enum omap_plane' (which would be | |
25 | * better named omap_plane_id).. and compiler seems unhappy about having | |
26 | * both a 'struct omap_plane' and 'enum omap_plane' | |
27 | */ | |
28 | #define omap_plane _omap_plane | |
29 | ||
30 | /* | |
31 | * plane funcs | |
32 | */ | |
33 | ||
34 | #define to_omap_plane(x) container_of(x, struct omap_plane, base) | |
35 | ||
36 | struct omap_plane { | |
37 | struct drm_plane base; | |
38 | struct omap_overlay *ovl; | |
39 | struct omap_overlay_info info; | |
40 | ||
41 | /* Source values, converted to integers because we don't support | |
42 | * fractional positions: | |
43 | */ | |
44 | unsigned int src_x, src_y; | |
9a0774e0 RC |
45 | |
46 | /* last fb that we pinned: */ | |
47 | struct drm_framebuffer *pinned_fb; | |
bb5c2d9a | 48 | |
a890e662 RC |
49 | uint32_t nformats; |
50 | uint32_t formats[32]; | |
b33f34d3 RC |
51 | |
52 | /* for synchronizing access to unpins fifo */ | |
53 | struct mutex unpin_mutex; | |
54 | ||
55 | /* set of bo's pending unpin until next END_WIN irq */ | |
56 | DECLARE_KFIFO_PTR(unpin_fifo, struct drm_gem_object *); | |
57 | int num_unpins, pending_num_unpins; | |
58 | ||
59 | /* for deferred unpin when we need to wait for scanout complete irq */ | |
60 | struct work_struct work; | |
61 | }; | |
62 | ||
63 | /* map from ovl->id to the irq we are interested in for scanout-done */ | |
64 | static const uint32_t id2irq[] = { | |
65 | [OMAP_DSS_GFX] = DISPC_IRQ_GFX_END_WIN, | |
66 | [OMAP_DSS_VIDEO1] = DISPC_IRQ_VID1_END_WIN, | |
67 | [OMAP_DSS_VIDEO2] = DISPC_IRQ_VID2_END_WIN, | |
68 | [OMAP_DSS_VIDEO3] = DISPC_IRQ_VID3_END_WIN, | |
a890e662 | 69 | }; |
bb5c2d9a | 70 | |
b33f34d3 RC |
71 | static void dispc_isr(void *arg, uint32_t mask) |
72 | { | |
73 | struct drm_plane *plane = arg; | |
74 | struct omap_plane *omap_plane = to_omap_plane(plane); | |
75 | struct omap_drm_private *priv = plane->dev->dev_private; | |
76 | ||
77 | omap_dispc_unregister_isr(dispc_isr, plane, | |
78 | id2irq[omap_plane->ovl->id]); | |
79 | ||
80 | queue_work(priv->wq, &omap_plane->work); | |
81 | } | |
82 | ||
83 | static void unpin_worker(struct work_struct *work) | |
84 | { | |
85 | struct omap_plane *omap_plane = | |
86 | container_of(work, struct omap_plane, work); | |
87 | ||
88 | mutex_lock(&omap_plane->unpin_mutex); | |
89 | DBG("unpinning %d of %d", omap_plane->num_unpins, | |
90 | omap_plane->num_unpins + omap_plane->pending_num_unpins); | |
91 | while (omap_plane->num_unpins > 0) { | |
92 | struct drm_gem_object *bo = NULL; | |
93 | int ret = kfifo_get(&omap_plane->unpin_fifo, &bo); | |
94 | WARN_ON(!ret); | |
95 | omap_gem_put_paddr(bo); | |
96 | drm_gem_object_unreference_unlocked(bo); | |
97 | omap_plane->num_unpins--; | |
98 | } | |
99 | mutex_unlock(&omap_plane->unpin_mutex); | |
100 | } | |
101 | ||
bb5c2d9a RC |
102 | /* push changes down to dss2 */ |
103 | static int commit(struct drm_plane *plane) | |
104 | { | |
105 | struct drm_device *dev = plane->dev; | |
106 | struct omap_plane *omap_plane = to_omap_plane(plane); | |
107 | struct omap_overlay *ovl = omap_plane->ovl; | |
108 | struct omap_overlay_info *info = &omap_plane->info; | |
109 | int ret; | |
110 | ||
111 | DBG("%s", ovl->name); | |
112 | DBG("%dx%d -> %dx%d (%d)", info->width, info->height, info->out_width, | |
113 | info->out_height, info->screen_width); | |
9a0774e0 RC |
114 | DBG("%d,%d %08x %08x", info->pos_x, info->pos_y, |
115 | info->paddr, info->p_uv_addr); | |
bb5c2d9a RC |
116 | |
117 | /* NOTE: do we want to do this at all here, or just wait | |
118 | * for dpms(ON) since other CRTC's may not have their mode | |
119 | * set yet, so fb dimensions may still change.. | |
120 | */ | |
121 | ret = ovl->set_overlay_info(ovl, info); | |
122 | if (ret) { | |
123 | dev_err(dev->dev, "could not set overlay info\n"); | |
124 | return ret; | |
125 | } | |
126 | ||
b33f34d3 RC |
127 | mutex_lock(&omap_plane->unpin_mutex); |
128 | omap_plane->num_unpins += omap_plane->pending_num_unpins; | |
129 | omap_plane->pending_num_unpins = 0; | |
130 | mutex_unlock(&omap_plane->unpin_mutex); | |
131 | ||
bb5c2d9a RC |
132 | /* our encoder doesn't necessarily get a commit() after this, in |
133 | * particular in the dpms() and mode_set_base() cases, so force the | |
134 | * manager to update: | |
135 | * | |
136 | * could this be in the encoder somehow? | |
137 | */ | |
138 | if (ovl->manager) { | |
139 | ret = ovl->manager->apply(ovl->manager); | |
140 | if (ret) { | |
141 | dev_err(dev->dev, "could not apply settings\n"); | |
142 | return ret; | |
143 | } | |
b33f34d3 RC |
144 | |
145 | /* | |
146 | * NOTE: really this should be atomic w/ mgr->apply() but | |
147 | * omapdss does not expose such an API | |
148 | */ | |
149 | if (omap_plane->num_unpins > 0) { | |
150 | ret = omap_dispc_register_isr(dispc_isr, | |
151 | plane, id2irq[ovl->id]); | |
152 | } | |
153 | ||
154 | /* | |
155 | * omapdss has upper limit on # of registered irq handlers, | |
156 | * which we shouldn't hit.. but if we do the limit should | |
157 | * be raised or bad things happen: | |
158 | */ | |
159 | WARN_ON(ret == -EBUSY); | |
160 | ||
161 | } else { | |
162 | struct omap_drm_private *priv = dev->dev_private; | |
163 | queue_work(priv->wq, &omap_plane->work); | |
bb5c2d9a RC |
164 | } |
165 | ||
b33f34d3 | 166 | |
2f53700d | 167 | if (ovl->is_enabled(ovl)) { |
bb5c2d9a RC |
168 | omap_framebuffer_flush(plane->fb, info->pos_x, info->pos_y, |
169 | info->out_width, info->out_height); | |
170 | } | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | /* when CRTC that we are attached to has potentially changed, this checks | |
176 | * if we are attached to proper manager, and if necessary updates. | |
177 | */ | |
178 | static void update_manager(struct drm_plane *plane) | |
179 | { | |
180 | struct omap_drm_private *priv = plane->dev->dev_private; | |
181 | struct omap_plane *omap_plane = to_omap_plane(plane); | |
182 | struct omap_overlay *ovl = omap_plane->ovl; | |
183 | struct omap_overlay_manager *mgr = NULL; | |
184 | int i; | |
185 | ||
186 | if (plane->crtc) { | |
187 | for (i = 0; i < priv->num_encoders; i++) { | |
188 | struct drm_encoder *encoder = priv->encoders[i]; | |
189 | if (encoder->crtc == plane->crtc) { | |
190 | mgr = omap_encoder_get_manager(encoder); | |
191 | break; | |
192 | } | |
193 | } | |
194 | } | |
195 | ||
196 | if (ovl->manager != mgr) { | |
2f53700d | 197 | bool enabled = ovl->is_enabled(ovl); |
bb5c2d9a RC |
198 | |
199 | /* don't switch things around with enabled overlays: */ | |
200 | if (enabled) | |
201 | omap_plane_dpms(plane, DRM_MODE_DPMS_OFF); | |
202 | ||
203 | if (ovl->manager) { | |
204 | DBG("disconnecting %s from %s", ovl->name, | |
205 | ovl->manager->name); | |
206 | ovl->unset_manager(ovl); | |
207 | } | |
208 | ||
209 | if (mgr) { | |
210 | DBG("connecting %s to %s", ovl->name, mgr->name); | |
211 | ovl->set_manager(ovl, mgr); | |
212 | } | |
213 | ||
214 | if (enabled && mgr) | |
215 | omap_plane_dpms(plane, DRM_MODE_DPMS_ON); | |
216 | } | |
217 | } | |
218 | ||
b33f34d3 RC |
219 | static void unpin(void *arg, struct drm_gem_object *bo) |
220 | { | |
221 | struct drm_plane *plane = arg; | |
222 | struct omap_plane *omap_plane = to_omap_plane(plane); | |
223 | ||
224 | if (kfifo_put(&omap_plane->unpin_fifo, | |
225 | (const struct drm_gem_object **)&bo)) { | |
226 | omap_plane->pending_num_unpins++; | |
227 | /* also hold a ref so it isn't free'd while pinned */ | |
228 | drm_gem_object_reference(bo); | |
229 | } else { | |
230 | dev_err(plane->dev->dev, "unpin fifo full!\n"); | |
231 | omap_gem_put_paddr(bo); | |
232 | } | |
233 | } | |
234 | ||
9a0774e0 RC |
235 | /* update which fb (if any) is pinned for scanout */ |
236 | static int update_pin(struct drm_plane *plane, struct drm_framebuffer *fb) | |
237 | { | |
238 | struct omap_plane *omap_plane = to_omap_plane(plane); | |
b33f34d3 RC |
239 | struct drm_framebuffer *pinned_fb = omap_plane->pinned_fb; |
240 | ||
241 | if (pinned_fb != fb) { | |
242 | int ret; | |
243 | ||
244 | DBG("%p -> %p", pinned_fb, fb); | |
245 | ||
246 | mutex_lock(&omap_plane->unpin_mutex); | |
247 | ret = omap_framebuffer_replace(pinned_fb, fb, plane, unpin); | |
248 | mutex_unlock(&omap_plane->unpin_mutex); | |
249 | ||
250 | if (ret) { | |
251 | dev_err(plane->dev->dev, "could not swap %p -> %p\n", | |
252 | omap_plane->pinned_fb, fb); | |
253 | omap_plane->pinned_fb = NULL; | |
254 | return ret; | |
255 | } | |
9a0774e0 | 256 | |
9a0774e0 | 257 | omap_plane->pinned_fb = fb; |
9a0774e0 RC |
258 | } |
259 | ||
b33f34d3 | 260 | return 0; |
9a0774e0 RC |
261 | } |
262 | ||
bb5c2d9a RC |
263 | /* update parameters that are dependent on the framebuffer dimensions and |
264 | * position within the fb that this plane scans out from. This is called | |
265 | * when framebuffer or x,y base may have changed. | |
266 | */ | |
267 | static void update_scanout(struct drm_plane *plane) | |
268 | { | |
269 | struct omap_plane *omap_plane = to_omap_plane(plane); | |
9a0774e0 RC |
270 | struct omap_overlay_info *info = &omap_plane->info; |
271 | int ret; | |
bb5c2d9a | 272 | |
9a0774e0 RC |
273 | ret = update_pin(plane, plane->fb); |
274 | if (ret) { | |
275 | dev_err(plane->dev->dev, | |
276 | "could not pin fb: %d\n", ret); | |
2f53700d RC |
277 | omap_plane_dpms(plane, DRM_MODE_DPMS_OFF); |
278 | return; | |
9a0774e0 | 279 | } |
bb5c2d9a | 280 | |
9a0774e0 RC |
281 | omap_framebuffer_update_scanout(plane->fb, |
282 | omap_plane->src_x, omap_plane->src_y, info); | |
bb5c2d9a | 283 | |
9a0774e0 RC |
284 | DBG("%s: %d,%d: %08x %08x (%d)", omap_plane->ovl->name, |
285 | omap_plane->src_x, omap_plane->src_y, | |
286 | (u32)info->paddr, (u32)info->p_uv_addr, | |
287 | info->screen_width); | |
bb5c2d9a RC |
288 | } |
289 | ||
2f53700d | 290 | int omap_plane_mode_set(struct drm_plane *plane, |
bb5c2d9a RC |
291 | struct drm_crtc *crtc, struct drm_framebuffer *fb, |
292 | int crtc_x, int crtc_y, | |
293 | unsigned int crtc_w, unsigned int crtc_h, | |
294 | uint32_t src_x, uint32_t src_y, | |
295 | uint32_t src_w, uint32_t src_h) | |
296 | { | |
297 | struct omap_plane *omap_plane = to_omap_plane(plane); | |
298 | ||
299 | /* src values are in Q16 fixed point, convert to integer: */ | |
300 | src_x = src_x >> 16; | |
301 | src_y = src_y >> 16; | |
302 | src_w = src_w >> 16; | |
303 | src_h = src_h >> 16; | |
304 | ||
bb5c2d9a RC |
305 | omap_plane->info.pos_x = crtc_x; |
306 | omap_plane->info.pos_y = crtc_y; | |
307 | omap_plane->info.out_width = crtc_w; | |
308 | omap_plane->info.out_height = crtc_h; | |
309 | omap_plane->info.width = src_w; | |
310 | omap_plane->info.height = src_h; | |
311 | omap_plane->src_x = src_x; | |
312 | omap_plane->src_y = src_y; | |
313 | ||
314 | /* note: this is done after this fxn returns.. but if we need | |
315 | * to do a commit/update_scanout, etc before this returns we | |
316 | * need the current value. | |
317 | */ | |
318 | plane->fb = fb; | |
319 | plane->crtc = crtc; | |
320 | ||
321 | update_scanout(plane); | |
322 | update_manager(plane); | |
bb5c2d9a RC |
323 | |
324 | return 0; | |
325 | } | |
326 | ||
2f53700d RC |
327 | static int omap_plane_update(struct drm_plane *plane, |
328 | struct drm_crtc *crtc, struct drm_framebuffer *fb, | |
329 | int crtc_x, int crtc_y, | |
330 | unsigned int crtc_w, unsigned int crtc_h, | |
331 | uint32_t src_x, uint32_t src_y, | |
332 | uint32_t src_w, uint32_t src_h) | |
333 | { | |
334 | omap_plane_mode_set(plane, crtc, fb, crtc_x, crtc_y, crtc_w, crtc_h, | |
335 | src_x, src_y, src_w, src_h); | |
336 | return omap_plane_dpms(plane, DRM_MODE_DPMS_ON); | |
337 | } | |
338 | ||
bb5c2d9a RC |
339 | static int omap_plane_disable(struct drm_plane *plane) |
340 | { | |
341 | return omap_plane_dpms(plane, DRM_MODE_DPMS_OFF); | |
342 | } | |
343 | ||
344 | static void omap_plane_destroy(struct drm_plane *plane) | |
345 | { | |
346 | struct omap_plane *omap_plane = to_omap_plane(plane); | |
347 | DBG("%s", omap_plane->ovl->name); | |
348 | omap_plane_disable(plane); | |
349 | drm_plane_cleanup(plane); | |
b33f34d3 RC |
350 | WARN_ON(omap_plane->pending_num_unpins + omap_plane->num_unpins > 0); |
351 | kfifo_free(&omap_plane->unpin_fifo); | |
bb5c2d9a RC |
352 | kfree(omap_plane); |
353 | } | |
354 | ||
355 | int omap_plane_dpms(struct drm_plane *plane, int mode) | |
356 | { | |
357 | struct omap_plane *omap_plane = to_omap_plane(plane); | |
2f53700d RC |
358 | struct omap_overlay *ovl = omap_plane->ovl; |
359 | int r; | |
bb5c2d9a RC |
360 | |
361 | DBG("%s: %d", omap_plane->ovl->name, mode); | |
362 | ||
363 | if (mode == DRM_MODE_DPMS_ON) { | |
364 | update_scanout(plane); | |
2f53700d RC |
365 | r = commit(plane); |
366 | if (!r) | |
367 | r = ovl->enable(ovl); | |
bb5c2d9a | 368 | } else { |
b33f34d3 | 369 | struct omap_drm_private *priv = plane->dev->dev_private; |
2f53700d | 370 | r = ovl->disable(ovl); |
9a0774e0 | 371 | update_pin(plane, NULL); |
b33f34d3 | 372 | queue_work(priv->wq, &omap_plane->work); |
bb5c2d9a RC |
373 | } |
374 | ||
2f53700d | 375 | return r; |
bb5c2d9a RC |
376 | } |
377 | ||
378 | static const struct drm_plane_funcs omap_plane_funcs = { | |
379 | .update_plane = omap_plane_update, | |
380 | .disable_plane = omap_plane_disable, | |
381 | .destroy = omap_plane_destroy, | |
382 | }; | |
383 | ||
bb5c2d9a RC |
384 | /* initialize plane */ |
385 | struct drm_plane *omap_plane_init(struct drm_device *dev, | |
386 | struct omap_overlay *ovl, unsigned int possible_crtcs, | |
387 | bool priv) | |
388 | { | |
389 | struct drm_plane *plane = NULL; | |
390 | struct omap_plane *omap_plane; | |
b33f34d3 | 391 | int ret; |
bb5c2d9a RC |
392 | |
393 | DBG("%s: possible_crtcs=%08x, priv=%d", ovl->name, | |
394 | possible_crtcs, priv); | |
395 | ||
b33f34d3 RC |
396 | /* friendly reminder to update table for future hw: */ |
397 | WARN_ON(ovl->id >= ARRAY_SIZE(id2irq)); | |
398 | ||
bb5c2d9a RC |
399 | omap_plane = kzalloc(sizeof(*omap_plane), GFP_KERNEL); |
400 | if (!omap_plane) { | |
401 | dev_err(dev->dev, "could not allocate plane\n"); | |
402 | goto fail; | |
403 | } | |
404 | ||
b33f34d3 RC |
405 | mutex_init(&omap_plane->unpin_mutex); |
406 | ||
407 | ret = kfifo_alloc(&omap_plane->unpin_fifo, 16, GFP_KERNEL); | |
408 | if (ret) { | |
409 | dev_err(dev->dev, "could not allocate unpin FIFO\n"); | |
410 | goto fail; | |
411 | } | |
412 | ||
413 | INIT_WORK(&omap_plane->work, unpin_worker); | |
414 | ||
a890e662 RC |
415 | omap_plane->nformats = omap_framebuffer_get_formats( |
416 | omap_plane->formats, ARRAY_SIZE(omap_plane->formats), | |
417 | ovl->supported_modes); | |
bb5c2d9a RC |
418 | omap_plane->ovl = ovl; |
419 | plane = &omap_plane->base; | |
420 | ||
421 | drm_plane_init(dev, plane, possible_crtcs, &omap_plane_funcs, | |
a890e662 | 422 | omap_plane->formats, omap_plane->nformats, priv); |
bb5c2d9a RC |
423 | |
424 | /* get our starting configuration, set defaults for parameters | |
425 | * we don't currently use, etc: | |
426 | */ | |
427 | ovl->get_overlay_info(ovl, &omap_plane->info); | |
428 | omap_plane->info.rotation_type = OMAP_DSS_ROT_DMA; | |
429 | omap_plane->info.rotation = OMAP_DSS_ROT_0; | |
430 | omap_plane->info.global_alpha = 0xff; | |
431 | omap_plane->info.mirror = 0; | |
432 | omap_plane->info.mirror = 0; | |
433 | ||
434 | /* Set defaults depending on whether we are a CRTC or overlay | |
435 | * layer. | |
436 | * TODO add ioctl to give userspace an API to change this.. this | |
437 | * will come in a subsequent patch. | |
438 | */ | |
439 | if (priv) | |
440 | omap_plane->info.zorder = 0; | |
441 | else | |
b66848eb | 442 | omap_plane->info.zorder = ovl->id; |
bb5c2d9a | 443 | |
bb5c2d9a RC |
444 | update_manager(plane); |
445 | ||
446 | return plane; | |
447 | ||
448 | fail: | |
449 | if (plane) { | |
450 | omap_plane_destroy(plane); | |
451 | } | |
452 | return NULL; | |
453 | } |