add video buffer lock/unlock API
[glsdk/omapdrmtest.git] / util / display-kms.c
1 /*
2  * Copyright (C) 2011 Texas Instruments
3  * Author: Rob Clark <rob.clark@linaro.org>
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 as published by
7  * the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12  * more details.
13  *
14  * You should have received a copy of the GNU General Public License along with
15  * this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
18 #include "util.h"
20 #include <xf86drmMode.h>
23 /* NOTE: healthy dose of recycling from libdrm modetest app.. */
25 /*
26  * Mode setting with the kernel interfaces is a bit of a chore.
27  * First you have to find the connector in question and make sure the
28  * requested mode is available.
29  * Then you need to find the encoder attached to that connector so you
30  * can bind it with a free crtc.
31  */
32 struct connector {
33         uint32_t id;
34         char mode_str[64];
35         drmModeModeInfo *mode;
36         drmModeEncoder *encoder;
37         int crtc;
38         int pipe;
39 };
41 #define to_display_kms(x) container_of(x, struct display_kms, base)
42 struct display_kms {
43         struct display base;
45         uint32_t connectors_count;
46         struct connector connector[10];
47         drmModePlane *ovr[10];
49         int scheduled_flips, completed_flips;
50         uint32_t bo_flags;
51         drmModeResPtr resources;
52         drmModePlaneRes *plane_resources;
53         struct buffer *current;
54 };
56 #define to_buffer_kms(x) container_of(x, struct buffer_kms, base)
57 struct buffer_kms {
58         struct buffer base;
59         uint32_t fb_id;
60 };
62 static struct omap_bo *
63 alloc_bo(struct display *disp, uint32_t bpp, uint32_t width, uint32_t height,
64                 uint32_t *bo_handle, uint32_t *pitch)
65 {
66         struct display_kms *disp_kms = to_display_kms(disp);
67         struct omap_bo *bo;
68         uint32_t bo_flags = disp_kms->bo_flags;
70         if ((bo_flags & OMAP_BO_TILED) == OMAP_BO_TILED) {
71                 bo_flags &= ~OMAP_BO_TILED;
72                 if (bpp == 8) {
73                         bo_flags |= OMAP_BO_TILED_8;
74                 } else if (bpp == 16) {
75                         bo_flags |= OMAP_BO_TILED_16;
76                 } else if (bpp == 32) {
77                         bo_flags |= OMAP_BO_TILED_32;
78                 }
79         }
81         bo_flags |= OMAP_BO_WC;
83         if (bo_flags & OMAP_BO_TILED) {
84                 bo = omap_bo_new_tiled(disp->dev, width, height, bo_flags);
85         } else {
86                 bo = omap_bo_new(disp->dev, width * height * bpp / 8, bo_flags);
87         }
89         if (bo) {
90                 *bo_handle = omap_bo_handle(bo);
91                 *pitch = width * bpp / 8;
92         }
94         return bo;
95 }
97 static struct buffer *
98 alloc_buffer(struct display *disp, uint32_t fourcc, uint32_t w, uint32_t h)
99 {
100         struct buffer_kms *buf_kms;
101         struct buffer *buf;
102         uint32_t bo_handles[4] = {0}, offsets[4] = {0};
103         int ret;
105         buf_kms = calloc(1, sizeof(*buf_kms));
106         if (!buf_kms) {
107                 ERROR("allocation failed");
108                 return NULL;
109         }
110         buf = &buf_kms->base;
112         buf->fourcc = fourcc;
113         buf->width = w;
114         buf->height = h;
116         buf->nbo = 1;
118         if (!fourcc)
119                 fourcc = FOURCC('A','R','2','4');
121         switch(fourcc) {
122         case FOURCC('A','R','2','4'):
123                 buf->nbo = 1;
124                 buf->bo[0] = alloc_bo(disp, 32, buf->width, buf->height,
125                                 &bo_handles[0], &buf->pitches[0]);
126                 break;
127         case FOURCC('U','Y','V','Y'):
128         case FOURCC('Y','U','Y','V'):
129                 buf->nbo = 1;
130                 buf->bo[0] = alloc_bo(disp, 16, buf->width, buf->height,
131                                 &bo_handles[0], &buf->pitches[0]);
132                 break;
133         case FOURCC('N','V','1','2'):
134                 buf->nbo = 2;
135                 buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
136                                 &bo_handles[0], &buf->pitches[0]);
137                 buf->bo[1] = alloc_bo(disp, 16, buf->width/2, buf->height/2,
138                                 &bo_handles[1], &buf->pitches[1]);
139                 break;
140         case FOURCC('I','4','2','0'):
141                 buf->nbo = 3;
142                 buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
143                                 &bo_handles[0], &buf->pitches[0]);
144                 buf->bo[1] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
145                                 &bo_handles[1], &buf->pitches[1]);
146                 buf->bo[2] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
147                                 &bo_handles[2], &buf->pitches[2]);
148                 break;
149         default:
150                 ERROR("invalid format: 0x%08x", fourcc);
151                 goto fail;
152         }
154         ret = drmModeAddFB2(disp->fd, buf->width, buf->height, fourcc,
155                         bo_handles, buf->pitches, offsets, &buf_kms->fb_id, 0);
156         if (ret) {
157                 ERROR("drmModeAddFB2 failed: %s (%d)", strerror(errno), ret);
158                 goto fail;
159         }
161         return buf;
163 fail:
164         // XXX cleanup
165         return NULL;
168 static struct buffer **
169 alloc_buffers(struct display *disp, uint32_t n,
170                 uint32_t fourcc, uint32_t w, uint32_t h)
172         struct buffer **bufs;
173         uint32_t i = 0;
175         bufs = calloc(n, sizeof(*bufs));
176         if (!bufs) {
177                 ERROR("allocation failed");
178                 goto fail;
179         }
181         for (i = 0; i < n; i++) {
182                 bufs[i] = alloc_buffer(disp, fourcc, w, h);
183                 if (!bufs[i]) {
184                         ERROR("allocation failed");
185                         goto fail;
186                 }
187         }
189         return bufs;
191 fail:
192         // XXX cleanup
193         return NULL;
196 static struct buffer **
197 get_buffers(struct display *disp, uint32_t n)
199         return alloc_buffers(disp, n, 0, disp->width, disp->height);
202 static struct buffer **
203 get_vid_buffers(struct display *disp, uint32_t n,
204                 uint32_t fourcc, uint32_t w, uint32_t h)
206         return alloc_buffers(disp, n, fourcc, w, h);
209 static void
210 page_flip_handler(int fd, unsigned int frame,
211                 unsigned int sec, unsigned int usec, void *data)
213         struct display *disp = data;
214         struct display_kms *disp_kms = to_display_kms(disp);
216         disp_kms->completed_flips++;
218         MSG("Page flip: frame=%d, sec=%d, usec=%d, remaining=%d", frame, sec, usec,
219                         disp_kms->scheduled_flips - disp_kms->completed_flips);
222 static int
223 post_buffer(struct display *disp, struct buffer *buf)
225         struct display_kms *disp_kms = to_display_kms(disp);
226         struct buffer_kms *buf_kms = to_buffer_kms(buf);
227         int ret, last_err = 0, x = 0;
228         uint32_t i;
230         for (i = 0; i < disp_kms->connectors_count; i++) {
231                 struct connector *connector = &disp_kms->connector[i];
233                 if (! connector->mode) {
234                         continue;
235                 }
237                 if (! disp_kms->current) {
238                         /* first buffer we flip to, setup the mode (since this can't
239                          * be done earlier without a buffer to scanout)
240                          */
241                         MSG("Setting mode %s on connector %d, crtc %d",
242                                         connector->mode_str, connector->id, connector->crtc);
244                         ret = drmModeSetCrtc(disp->fd, connector->crtc, buf_kms->fb_id,
245                                         x, 0, &connector->id, 1, connector->mode);
247                         x += connector->mode->hdisplay;
248                 } else {
249                         ret = drmModePageFlip(disp->fd, connector->crtc, buf_kms->fb_id,
250                                         DRM_MODE_PAGE_FLIP_EVENT, disp);
251                         disp_kms->scheduled_flips++;
252                 }
254                 if (ret) {
255                         ERROR("Could not post buffer on crtc %d: %s (%d)",
256                                         connector->crtc, strerror(errno), ret);
257                         last_err = ret;
258                         /* well, keep trying the reset of the connectors.. */
259                 }
260         }
262         /* if we flipped, wait for all flips to complete! */
263         while (disp_kms->scheduled_flips > disp_kms->completed_flips) {
264                 drmEventContext evctx = {
265                                 .version = DRM_EVENT_CONTEXT_VERSION,
266                                 .page_flip_handler = page_flip_handler,
267                 };
268                 struct timeval timeout = {
269                                 .tv_sec = 3,
270                                 .tv_usec = 0,
271                 };
272                 fd_set fds;
274                 FD_ZERO(&fds);
275                 FD_SET(disp->fd, &fds);
277                 ret = select(disp->fd + 1, &fds, NULL, NULL, &timeout);
278                 if (ret <= 0) {
279                         if (errno == EAGAIN) {
280                                 continue;    /* keep going */
281                         } else {
282                                 ERROR("Timeout waiting for flip complete: %s (%d)",
283                                                 strerror(errno), ret);
284                                 last_err = ret;
285                                 break;
286                         }
287                 }
289                 drmHandleEvent(disp->fd, &evctx);
290         }
292         disp_kms->current = buf;
294         return last_err;
297 static int
298 post_vid_buffer(struct display *disp, struct buffer *buf,
299                 uint32_t x, uint32_t y, uint32_t w, uint32_t h)
301         struct display_kms *disp_kms = to_display_kms(disp);
302         struct buffer_kms *buf_kms = to_buffer_kms(buf);
303         int ret = 0;
304         uint32_t i, j;
306         /* ensure we have the overlay setup: */
307         for (i = 0; i < disp_kms->connectors_count; i++) {
308                 struct connector *connector = &disp_kms->connector[i];
309                 uint32_t used_planes = 0;
310                 drmModeModeInfo *mode = connector->mode;
312                 if (! mode) {
313                         continue;
314                 }
316                 if (! disp_kms->ovr[i]) {
318                         for (j = 0; j < disp_kms->plane_resources->count_planes; j++) {
319                                 drmModePlane *ovr = drmModeGetPlane(disp->fd,
320                                                 disp_kms->plane_resources->planes[j]);
321                                 if ((ovr->possible_crtcs & (1 << connector->pipe)) &&
322                                                 !(used_planes & (1 << j))) {
323                                         disp_kms->ovr[i] = ovr;
324                                         used_planes |= (1 << j);
325                                         break;
326                                 }
327                         }
328                 }
330                 if (! disp_kms->ovr[i]) {
331                         MSG("Could not find plane for crtc %d", connector->crtc);
332                         ret = -1;
333                         /* carry on and see if we can find at least one usable plane */
334                         continue;
335                 }
337                 ret = drmModeSetPlane(disp->fd, disp_kms->ovr[i]->plane_id,
338                                 connector->crtc, buf_kms->fb_id, 0,
339                                 /* make video fullscreen: */
340                                 0, 0, mode->hdisplay, mode->vdisplay,
341                                 /* source/cropping coordinates are given in Q16 */
342                                 x << 16, y << 16, w << 16, h << 16);
343                 if (ret) {
344                         ERROR("failed to enable plane %d: %s",
345                                         disp_kms->ovr[i]->plane_id, strerror(errno));
346                 }
347         }
349         return ret;
352 static void
353 connector_find_mode(struct display *disp, struct connector *c)
355         struct display_kms *disp_kms = to_display_kms(disp);
356         drmModeConnector *connector;
357         int i, j;
359         /* First, find the connector & mode */
360         c->mode = NULL;
361         for (i = 0; i < disp_kms->resources->count_connectors; i++) {
362                 connector = drmModeGetConnector(disp->fd,
363                                 disp_kms->resources->connectors[i]);
365                 if (!connector) {
366                         ERROR("could not get connector %i: %s",
367                                         disp_kms->resources->connectors[i], strerror(errno));
368                         drmModeFreeConnector(connector);
369                         continue;
370                 }
372                 if (!connector->count_modes) {
373                         drmModeFreeConnector(connector);
374                         continue;
375                 }
377                 if (connector->connector_id != c->id) {
378                         drmModeFreeConnector(connector);
379                         continue;
380                 }
382                 for (j = 0; j < connector->count_modes; j++) {
383                         c->mode = &connector->modes[j];
384                         if (!strcmp(c->mode->name, c->mode_str))
385                                 break;
386                 }
388                 /* Found it, break out */
389                 if (c->mode)
390                         break;
392                 drmModeFreeConnector(connector);
393         }
395         if (!c->mode) {
396                 ERROR("failed to find mode \"%s\"", c->mode_str);
397                 return;
398         }
400         /* Now get the encoder */
401         for (i = 0; i < disp_kms->resources->count_encoders; i++) {
402                 c->encoder = drmModeGetEncoder(disp->fd,
403                                 disp_kms->resources->encoders[i]);
405                 if (!c->encoder) {
406                         ERROR("could not get encoder %i: %s",
407                                         disp_kms->resources->encoders[i], strerror(errno));
408                         drmModeFreeEncoder(c->encoder);
409                         continue;
410                 }
412                 if (c->encoder->encoder_id  == connector->encoder_id)
413                         break;
415                 drmModeFreeEncoder(c->encoder);
416         }
418         if (c->crtc == -1)
419                 c->crtc = c->encoder->crtc_id;
421         /* and figure out which crtc index it is: */
422         for (i = 0; i < disp_kms->resources->count_crtcs; i++) {
423                 if (c->crtc == (int)disp_kms->resources->crtcs[i]) {
424                         c->pipe = i;
425                         break;
426                 }
427         }
430 void
431 disp_kms_usage(void)
433         MSG("KMS Display Options:");
434         MSG("\t-t <tiled-mode>\t8, 16, 32, or auto");
435         MSG("\t-s <connector_id>:<mode>\tset a mode");
436         MSG("\t-s <connector_id>@<crtc_id>:<mode>\tset a mode");
439 struct display *
440 disp_kms_open(int argc, char **argv)
442         struct display_kms *disp_kms = NULL;
443         struct display *disp;
444         int i;
446         disp_kms = calloc(1, sizeof(*disp_kms));
447         if (!disp_kms) {
448                 ERROR("allocation failed");
449                 goto fail;
450         }
451         disp = &disp_kms->base;
453         disp->fd = drmOpen("omapdrm", NULL);
454         if (disp->fd < 0) {
455                 ERROR("could not open drm device: %s (%d)", strerror(errno), errno);
456                 goto fail;
457         }
459         disp->dev = omap_device_new(disp->fd);
460         if (!disp->dev) {
461                 ERROR("couldn't create device");
462                 goto fail;
463         }
465         disp->get_buffers = get_buffers;
466         disp->get_vid_buffers = get_vid_buffers;
467         disp->post_buffer = post_buffer;
468         disp->post_vid_buffer = post_vid_buffer;
470         disp_kms->resources = drmModeGetResources(disp->fd);
471         if (!disp_kms->resources) {
472                 ERROR("drmModeGetResources failed: %s", strerror(errno));
473                 goto fail;
474         }
476         disp_kms->plane_resources = drmModeGetPlaneResources(disp->fd);
477         if (!disp_kms->plane_resources) {
478                 ERROR("drmModeGetPlaneResources failed: %s", strerror(errno));
479                 goto fail;
480         }
482         /* note: set args to NULL after we've parsed them so other modules know
483          * that it is already parsed (since the arg parsing is decentralized)
484          */
485         for (i = 1; i < argc; i++) {
486                 if (!argv[i]) {
487                         continue;
488                 }
489                 if (!strcmp("-t", argv[i])) {
490                         int n;
491                         argv[i++] = NULL;
492                         if (!strcmp(argv[i], "auto")) {
493                                 n = 0;
494                         } else if (sscanf(argv[i], "%d", &n) != 1) {
495                                 ERROR("invalid arg: %s", argv[i]);
496                                 goto fail;
497                         }
499                         disp_kms->bo_flags &= ~OMAP_BO_TILED;
501                         if (n == 8) {
502                                 disp_kms->bo_flags |= OMAP_BO_TILED_8;
503                         } else if (n == 16) {
504                                 disp_kms->bo_flags |= OMAP_BO_TILED_16;
505                         } else if (n == 32) {
506                                 disp_kms->bo_flags |= OMAP_BO_TILED_32;
507                         } else if (n == 0) {
508                                 disp_kms->bo_flags |= OMAP_BO_TILED;
509                         } else {
510                                 ERROR("invalid arg: %s", argv[i]);
511                                 goto fail;
512                         }
513                 } else if (!strcmp("-s", argv[i])) {
514                         struct connector *connector =
515                                         &disp_kms->connector[disp_kms->connectors_count++];
516                         connector->crtc = -1;
517                         argv[i++] = NULL;
518                         if (sscanf(argv[i], "%d:%64s",
519                                    &connector->id,
520                                    connector->mode_str) != 2 &&
521                             sscanf(argv[i], "%d@%d:%64s",
522                                    &connector->id,
523                                    &connector->crtc,
524                                    connector->mode_str) != 3) {
525                                 // TODO: we could support connector specified as a name too, I suppose
526                                 ERROR("invalid arg: %s", argv[i]);
527                                 goto fail;
528                         }
529                 } else {
530                         /* ignore */
531                         continue;
532                 }
533                 argv[i] = NULL;
534         }
536         disp->width = 0;
537         disp->height = 0;
538         for (i = 0; i < (int)disp_kms->connectors_count; i++) {
539                 struct connector *c = &disp_kms->connector[i];
540                 connector_find_mode(disp, c);
541                 if (c->mode == NULL)
542                         continue;
543                 /* setup side-by-side virtual display */
544                 disp->width += c->mode->hdisplay;
545                 if (disp->height < c->mode->vdisplay) {
546                         disp->height = c->mode->vdisplay;
547                 }
548         }
550         MSG("using %d connectors, %dx%d display",
551                         disp_kms->connectors_count, disp->width, disp->height);
553         return disp;
555 fail:
556         // XXX cleanup
557         return NULL;