display: add close "virtual" function
[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 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
22 #include "util.h"
24 #include <xf86drmMode.h>
27 /* NOTE: healthy dose of recycling from libdrm modetest app.. */
29 /*
30  * Mode setting with the kernel interfaces is a bit of a chore.
31  * First you have to find the connector in question and make sure the
32  * requested mode is available.
33  * Then you need to find the encoder attached to that connector so you
34  * can bind it with a free crtc.
35  */
36 struct connector {
37         uint32_t id;
38         char mode_str[64];
39         drmModeModeInfo *mode;
40         drmModeEncoder *encoder;
41         int crtc;
42         int pipe;
43 };
45 #define to_display_kms(x) container_of(x, struct display_kms, base)
46 struct display_kms {
47         struct display base;
49         uint32_t connectors_count;
50         struct connector connector[10];
51         drmModePlane *ovr[10];
53         int scheduled_flips, completed_flips;
54         uint32_t bo_flags;
55         drmModeResPtr resources;
56         drmModePlaneRes *plane_resources;
57         struct buffer *current;
58 };
60 #define to_buffer_kms(x) container_of(x, struct buffer_kms, base)
61 struct buffer_kms {
62         struct buffer base;
63         uint32_t fb_id;
64 };
66 static struct omap_bo *
67 alloc_bo(struct display *disp, uint32_t bpp, uint32_t width, uint32_t height,
68                 uint32_t *bo_handle, uint32_t *pitch)
69 {
70         struct display_kms *disp_kms = to_display_kms(disp);
71         struct omap_bo *bo;
72         uint32_t bo_flags = disp_kms->bo_flags;
74         if ((bo_flags & OMAP_BO_TILED) == OMAP_BO_TILED) {
75                 bo_flags &= ~OMAP_BO_TILED;
76                 if (bpp == 8) {
77                         bo_flags |= OMAP_BO_TILED_8;
78                 } else if (bpp == 16) {
79                         bo_flags |= OMAP_BO_TILED_16;
80                 } else if (bpp == 32) {
81                         bo_flags |= OMAP_BO_TILED_32;
82                 }
83         }
85         bo_flags |= OMAP_BO_WC;
87         if (bo_flags & OMAP_BO_TILED) {
88                 bo = omap_bo_new_tiled(disp->dev, width, height, bo_flags);
89         } else {
90                 bo = omap_bo_new(disp->dev, width * height * bpp / 8, bo_flags);
91         }
93         if (bo) {
94                 *bo_handle = omap_bo_handle(bo);
95                 *pitch = width * bpp / 8;
96                 if (bo_flags & OMAP_BO_TILED)
97                         *pitch = ALIGN2(*pitch, PAGE_SHIFT);
98         }
100         return bo;
103 static struct buffer *
104 alloc_buffer(struct display *disp, uint32_t fourcc, uint32_t w, uint32_t h)
106         struct buffer_kms *buf_kms;
107         struct buffer *buf;
108         uint32_t bo_handles[4] = {0}, offsets[4] = {0};
109         int ret;
111         buf_kms = calloc(1, sizeof(*buf_kms));
112         if (!buf_kms) {
113                 ERROR("allocation failed");
114                 return NULL;
115         }
116         buf = &buf_kms->base;
118         buf->fourcc = fourcc;
119         buf->width = w;
120         buf->height = h;
121         buf->multiplanar = true;
123         buf->nbo = 1;
125         if (!fourcc)
126                 fourcc = FOURCC('A','R','2','4');
128         switch(fourcc) {
129         case FOURCC('A','R','2','4'):
130                 buf->nbo = 1;
131                 buf->bo[0] = alloc_bo(disp, 32, buf->width, buf->height,
132                                 &bo_handles[0], &buf->pitches[0]);
133                 break;
134         case FOURCC('U','Y','V','Y'):
135         case FOURCC('Y','U','Y','V'):
136                 buf->nbo = 1;
137                 buf->bo[0] = alloc_bo(disp, 16, buf->width, buf->height,
138                                 &bo_handles[0], &buf->pitches[0]);
139                 break;
140         case FOURCC('N','V','1','2'):
141                 buf->nbo = 2;
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, 16, buf->width/2, buf->height/2,
145                                 &bo_handles[1], &buf->pitches[1]);
146                 break;
147         case FOURCC('I','4','2','0'):
148                 buf->nbo = 3;
149                 buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
150                                 &bo_handles[0], &buf->pitches[0]);
151                 buf->bo[1] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
152                                 &bo_handles[1], &buf->pitches[1]);
153                 buf->bo[2] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
154                                 &bo_handles[2], &buf->pitches[2]);
155                 break;
156         default:
157                 ERROR("invalid format: 0x%08x", fourcc);
158                 goto fail;
159         }
161         ret = drmModeAddFB2(disp->fd, buf->width, buf->height, fourcc,
162                         bo_handles, buf->pitches, offsets, &buf_kms->fb_id, 0);
163         if (ret) {
164                 ERROR("drmModeAddFB2 failed: %s (%d)", strerror(errno), ret);
165                 goto fail;
166         }
168         return buf;
170 fail:
171         // XXX cleanup
172         return NULL;
175 static struct buffer **
176 alloc_buffers(struct display *disp, uint32_t n,
177                 uint32_t fourcc, uint32_t w, uint32_t h)
179         struct buffer **bufs;
180         uint32_t i = 0;
182         bufs = calloc(n, sizeof(*bufs));
183         if (!bufs) {
184                 ERROR("allocation failed");
185                 goto fail;
186         }
188         for (i = 0; i < n; i++) {
189                 bufs[i] = alloc_buffer(disp, fourcc, w, h);
190                 if (!bufs[i]) {
191                         ERROR("allocation failed");
192                         goto fail;
193                 }
194         }
196         return bufs;
198 fail:
199         // XXX cleanup
200         return NULL;
203 static struct buffer **
204 get_buffers(struct display *disp, uint32_t n)
206         return alloc_buffers(disp, n, 0, disp->width, disp->height);
209 static struct buffer **
210 get_vid_buffers(struct display *disp, uint32_t n,
211                 uint32_t fourcc, uint32_t w, uint32_t h)
213         return alloc_buffers(disp, n, fourcc, w, h);
216 static void
217 page_flip_handler(int fd, unsigned int frame,
218                 unsigned int sec, unsigned int usec, void *data)
220         struct display *disp = data;
221         struct display_kms *disp_kms = to_display_kms(disp);
223         disp_kms->completed_flips++;
225         MSG("Page flip: frame=%d, sec=%d, usec=%d, remaining=%d", frame, sec, usec,
226                         disp_kms->scheduled_flips - disp_kms->completed_flips);
229 static int
230 post_buffer(struct display *disp, struct buffer *buf)
232         struct display_kms *disp_kms = to_display_kms(disp);
233         struct buffer_kms *buf_kms = to_buffer_kms(buf);
234         int ret, last_err = 0, x = 0;
235         uint32_t i;
237         for (i = 0; i < disp_kms->connectors_count; i++) {
238                 struct connector *connector = &disp_kms->connector[i];
240                 if (! connector->mode) {
241                         continue;
242                 }
244                 if (! disp_kms->current) {
245                         /* first buffer we flip to, setup the mode (since this can't
246                          * be done earlier without a buffer to scanout)
247                          */
248                         MSG("Setting mode %s on connector %d, crtc %d",
249                                         connector->mode_str, connector->id, connector->crtc);
251                         ret = drmModeSetCrtc(disp->fd, connector->crtc, buf_kms->fb_id,
252                                         x, 0, &connector->id, 1, connector->mode);
254                         x += connector->mode->hdisplay;
255                 } else {
256                         ret = drmModePageFlip(disp->fd, connector->crtc, buf_kms->fb_id,
257                                         DRM_MODE_PAGE_FLIP_EVENT, disp);
258                         disp_kms->scheduled_flips++;
259                 }
261                 if (ret) {
262                         ERROR("Could not post buffer on crtc %d: %s (%d)",
263                                         connector->crtc, strerror(errno), ret);
264                         last_err = ret;
265                         /* well, keep trying the reset of the connectors.. */
266                 }
267         }
269         /* if we flipped, wait for all flips to complete! */
270         while (disp_kms->scheduled_flips > disp_kms->completed_flips) {
271                 drmEventContext evctx = {
272                                 .version = DRM_EVENT_CONTEXT_VERSION,
273                                 .page_flip_handler = page_flip_handler,
274                 };
275                 struct timeval timeout = {
276                                 .tv_sec = 3,
277                                 .tv_usec = 0,
278                 };
279                 fd_set fds;
281                 FD_ZERO(&fds);
282                 FD_SET(disp->fd, &fds);
284                 ret = select(disp->fd + 1, &fds, NULL, NULL, &timeout);
285                 if (ret <= 0) {
286                         if (errno == EAGAIN) {
287                                 continue;    /* keep going */
288                         } else {
289                                 ERROR("Timeout waiting for flip complete: %s (%d)",
290                                                 strerror(errno), ret);
291                                 last_err = ret;
292                                 break;
293                         }
294                 }
296                 drmHandleEvent(disp->fd, &evctx);
297         }
299         disp_kms->current = buf;
301         return last_err;
304 static int
305 post_vid_buffer(struct display *disp, struct buffer *buf,
306                 uint32_t x, uint32_t y, uint32_t w, uint32_t h)
308         struct display_kms *disp_kms = to_display_kms(disp);
309         struct buffer_kms *buf_kms = to_buffer_kms(buf);
310         int ret = 0;
311         uint32_t i, j;
313         /* ensure we have the overlay setup: */
314         for (i = 0; i < disp_kms->connectors_count; i++) {
315                 struct connector *connector = &disp_kms->connector[i];
316                 uint32_t used_planes = 0;
317                 drmModeModeInfo *mode = connector->mode;
319                 if (! mode) {
320                         continue;
321                 }
323                 if (! disp_kms->ovr[i]) {
325                         for (j = 0; j < disp_kms->plane_resources->count_planes; j++) {
326                                 drmModePlane *ovr = drmModeGetPlane(disp->fd,
327                                                 disp_kms->plane_resources->planes[j]);
328                                 if ((ovr->possible_crtcs & (1 << connector->pipe)) &&
329                                                 !(used_planes & (1 << j))) {
330                                         disp_kms->ovr[i] = ovr;
331                                         used_planes |= (1 << j);
332                                         break;
333                                 }
334                         }
335                 }
337                 if (! disp_kms->ovr[i]) {
338                         MSG("Could not find plane for crtc %d", connector->crtc);
339                         ret = -1;
340                         /* carry on and see if we can find at least one usable plane */
341                         continue;
342                 }
344                 ret = drmModeSetPlane(disp->fd, disp_kms->ovr[i]->plane_id,
345                                 connector->crtc, buf_kms->fb_id, 0,
346                                 /* make video fullscreen: */
347                                 0, 0, mode->hdisplay, mode->vdisplay,
348                                 /* source/cropping coordinates are given in Q16 */
349                                 x << 16, y << 16, w << 16, h << 16);
350                 if (ret) {
351                         ERROR("failed to enable plane %d: %s",
352                                         disp_kms->ovr[i]->plane_id, strerror(errno));
353                 }
354         }
356         return ret;
359 static void
360 close_kms(struct display *disp)
364 static void
365 connector_find_mode(struct display *disp, struct connector *c)
367         struct display_kms *disp_kms = to_display_kms(disp);
368         drmModeConnector *connector;
369         int i, j;
371         /* First, find the connector & mode */
372         c->mode = NULL;
373         for (i = 0; i < disp_kms->resources->count_connectors; i++) {
374                 connector = drmModeGetConnector(disp->fd,
375                                 disp_kms->resources->connectors[i]);
377                 if (!connector) {
378                         ERROR("could not get connector %i: %s",
379                                         disp_kms->resources->connectors[i], strerror(errno));
380                         drmModeFreeConnector(connector);
381                         continue;
382                 }
384                 if (!connector->count_modes) {
385                         drmModeFreeConnector(connector);
386                         continue;
387                 }
389                 if (connector->connector_id != c->id) {
390                         drmModeFreeConnector(connector);
391                         continue;
392                 }
394                 for (j = 0; j < connector->count_modes; j++) {
395                         c->mode = &connector->modes[j];
396                         if (!strcmp(c->mode->name, c->mode_str))
397                                 break;
398                 }
400                 /* Found it, break out */
401                 if (c->mode)
402                         break;
404                 drmModeFreeConnector(connector);
405         }
407         if (!c->mode) {
408                 ERROR("failed to find mode \"%s\"", c->mode_str);
409                 return;
410         }
412         /* Now get the encoder */
413         for (i = 0; i < disp_kms->resources->count_encoders; i++) {
414                 c->encoder = drmModeGetEncoder(disp->fd,
415                                 disp_kms->resources->encoders[i]);
417                 if (!c->encoder) {
418                         ERROR("could not get encoder %i: %s",
419                                         disp_kms->resources->encoders[i], strerror(errno));
420                         drmModeFreeEncoder(c->encoder);
421                         continue;
422                 }
424                 if (c->encoder->encoder_id  == connector->encoder_id)
425                         break;
427                 drmModeFreeEncoder(c->encoder);
428         }
430         if (c->crtc == -1)
431                 c->crtc = c->encoder->crtc_id;
433         /* and figure out which crtc index it is: */
434         for (i = 0; i < disp_kms->resources->count_crtcs; i++) {
435                 if (c->crtc == (int)disp_kms->resources->crtcs[i]) {
436                         c->pipe = i;
437                         break;
438                 }
439         }
442 void
443 disp_kms_usage(void)
445         MSG("KMS Display Options:");
446         MSG("\t-t <tiled-mode>\t8, 16, 32, or auto");
447         MSG("\t-s <connector_id>:<mode>\tset a mode");
448         MSG("\t-s <connector_id>@<crtc_id>:<mode>\tset a mode");
451 struct display *
452 disp_kms_open(int argc, char **argv)
454         struct display_kms *disp_kms = NULL;
455         struct display *disp;
456         int i;
458         disp_kms = calloc(1, sizeof(*disp_kms));
459         if (!disp_kms) {
460                 ERROR("allocation failed");
461                 goto fail;
462         }
463         disp = &disp_kms->base;
465         disp->fd = drmOpen("omapdrm", NULL);
466         if (disp->fd < 0) {
467                 ERROR("could not open drm device: %s (%d)", strerror(errno), errno);
468                 goto fail;
469         }
471         disp->dev = omap_device_new(disp->fd);
472         if (!disp->dev) {
473                 ERROR("couldn't create device");
474                 goto fail;
475         }
477         disp->get_buffers = get_buffers;
478         disp->get_vid_buffers = get_vid_buffers;
479         disp->post_buffer = post_buffer;
480         disp->post_vid_buffer = post_vid_buffer;
481         disp->close = close_kms;
483         disp_kms->resources = drmModeGetResources(disp->fd);
484         if (!disp_kms->resources) {
485                 ERROR("drmModeGetResources failed: %s", strerror(errno));
486                 goto fail;
487         }
489         disp_kms->plane_resources = drmModeGetPlaneResources(disp->fd);
490         if (!disp_kms->plane_resources) {
491                 ERROR("drmModeGetPlaneResources failed: %s", strerror(errno));
492                 goto fail;
493         }
495         /* note: set args to NULL after we've parsed them so other modules know
496          * that it is already parsed (since the arg parsing is decentralized)
497          */
498         for (i = 1; i < argc; i++) {
499                 if (!argv[i]) {
500                         continue;
501                 }
502                 if (!strcmp("-t", argv[i])) {
503                         int n;
504                         argv[i++] = NULL;
505                         if (!strcmp(argv[i], "auto")) {
506                                 n = 0;
507                         } else if (sscanf(argv[i], "%d", &n) != 1) {
508                                 ERROR("invalid arg: %s", argv[i]);
509                                 goto fail;
510                         }
512                         disp_kms->bo_flags &= ~OMAP_BO_TILED;
514                         if (n == 8) {
515                                 disp_kms->bo_flags |= OMAP_BO_TILED_8;
516                         } else if (n == 16) {
517                                 disp_kms->bo_flags |= OMAP_BO_TILED_16;
518                         } else if (n == 32) {
519                                 disp_kms->bo_flags |= OMAP_BO_TILED_32;
520                         } else if (n == 0) {
521                                 disp_kms->bo_flags |= OMAP_BO_TILED;
522                         } else {
523                                 ERROR("invalid arg: %s", argv[i]);
524                                 goto fail;
525                         }
526                 } else if (!strcmp("-s", argv[i])) {
527                         struct connector *connector =
528                                         &disp_kms->connector[disp_kms->connectors_count++];
529                         connector->crtc = -1;
530                         argv[i++] = NULL;
531                         if (sscanf(argv[i], "%d:%64s",
532                                    &connector->id,
533                                    connector->mode_str) != 2 &&
534                             sscanf(argv[i], "%d@%d:%64s",
535                                    &connector->id,
536                                    &connector->crtc,
537                                    connector->mode_str) != 3) {
538                                 // TODO: we could support connector specified as a name too, I suppose
539                                 ERROR("invalid arg: %s", argv[i]);
540                                 goto fail;
541                         }
542                         disp_kms->bo_flags |= OMAP_BO_SCANOUT;
543                 } else {
544                         /* ignore */
545                         continue;
546                 }
547                 argv[i] = NULL;
548         }
550         disp->width = 0;
551         disp->height = 0;
552         disp->multiplanar = true;
553         for (i = 0; i < (int)disp_kms->connectors_count; i++) {
554                 struct connector *c = &disp_kms->connector[i];
555                 connector_find_mode(disp, c);
556                 if (c->mode == NULL)
557                         continue;
558                 /* setup side-by-side virtual display */
559                 disp->width += c->mode->hdisplay;
560                 if (disp->height < c->mode->vdisplay) {
561                         disp->height = c->mode->vdisplay;
562                 }
563         }
565         MSG("using %d connectors, %dx%d display, multiplanar: %d",
566                         disp_kms->connectors_count, disp->width, disp->height, disp->multiplanar);
568         return disp;
570 fail:
571         // XXX cleanup
572         return NULL;