dual display fix
[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"
23 #include <libdce.h>
25 #include <xf86drmMode.h>
28 /* NOTE: healthy dose of recycling from libdrm modetest app.. */
30 /*
31  * Mode setting with the kernel interfaces is a bit of a chore.
32  * First you have to find the connector in question and make sure the
33  * requested mode is available.
34  * Then you need to find the encoder attached to that connector so you
35  * can bind it with a free crtc.
36  */
37 struct connector {
38         uint32_t id;
39         char mode_str[64];
40         drmModeModeInfo *mode;
41         drmModeEncoder *encoder;
42         int crtc;
43         int pipe;
44 };
46 #define to_display_kms(x) container_of(x, struct display_kms, base)
47 struct display_kms {
48         struct display base;
50         uint32_t connectors_count;
51         struct connector connector[10];
52         drmModePlane *ovr[10];
54         int scheduled_flips, completed_flips;
55         uint32_t bo_flags;
56         drmModeResPtr resources;
57         drmModePlaneRes *plane_resources;
58         struct buffer *current;
59 };
61 #define to_buffer_kms(x) container_of(x, struct buffer_kms, base)
62 struct buffer_kms {
63         struct buffer base;
64         uint32_t fb_id;
65 };
67 static struct omap_bo *
68 alloc_bo(struct display *disp, uint32_t bpp, uint32_t width, uint32_t height,
69                 uint32_t *bo_handle, uint32_t *pitch)
70 {
71         struct display_kms *disp_kms = to_display_kms(disp);
72         struct omap_bo *bo;
73         uint32_t bo_flags = disp_kms->bo_flags;
75         if ((bo_flags & OMAP_BO_TILED) == OMAP_BO_TILED) {
76                 bo_flags &= ~OMAP_BO_TILED;
77                 if (bpp == 8) {
78                         bo_flags |= OMAP_BO_TILED_8;
79                 } else if (bpp == 16) {
80                         bo_flags |= OMAP_BO_TILED_16;
81                 } else if (bpp == 32) {
82                         bo_flags |= OMAP_BO_TILED_32;
83                 }
84         }
85         bo_flags |= OMAP_BO_WC;
87         if (bo_flags & OMAP_BO_TILED) {
88                 bo = omap_bo_new_tiled(disp->dev, ALIGN2(width,7), 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                 if (disp->multiplanar) {
142                         buf->nbo = 2;
143                         buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
144                                         &bo_handles[0], &buf->pitches[0]);
145                         buf->fd[0] = omap_bo_dmabuf(buf->bo[0]);
146                         buf->bo[1] = alloc_bo(disp, 16, buf->width/2, buf->height/2,
147                                         &bo_handles[1], &buf->pitches[1]);
148                         buf->fd[1] = omap_bo_dmabuf(buf->bo[1]);
149                 } else {
150                         buf->nbo = 1;
151                         buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height * 3 / 2,
152                                         &bo_handles[0], &buf->pitches[0]);
153                         buf->fd[0] = omap_bo_dmabuf(buf->bo[0]);
154                         bo_handles[1] = bo_handles[0];
155                         buf->pitches[1] = buf->pitches[0];
156                         offsets[1] = buf->width * buf->height;
157                         buf->multiplanar = false;
158                 }
159                 break;
160         case FOURCC('I','4','2','0'):
161                 buf->nbo = 3;
162                 buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
163                                 &bo_handles[0], &buf->pitches[0]);
164                 buf->bo[1] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
165                                 &bo_handles[1], &buf->pitches[1]);
166                 buf->bo[2] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
167                                 &bo_handles[2], &buf->pitches[2]);
168                 break;
169         default:
170                 ERROR("invalid format: 0x%08x", fourcc);
171                 goto fail;
172         }
174         ret = drmModeAddFB2(disp->fd, buf->width, buf->height, fourcc,
175                         bo_handles, buf->pitches, offsets, &buf_kms->fb_id, 0);
176         if (ret) {
177                 ERROR("drmModeAddFB2 failed: %s (%d)", strerror(errno), ret);
178                 goto fail;
179         }
181         return buf;
183 fail:
184         // XXX cleanup
185         return NULL;
188 void
189 free_buffers(struct display *disp, uint32_t n)
191         uint32_t i;
192         for (i = 0; i < n; i++) {
193                 if (disp->buf[i]) {
194                         omap_bo_del(disp->buf[i]->bo[0]);
195                         if(disp->multiplanar){
196                                 omap_bo_del(disp->buf[i]->bo[1]);
197                         }
198                 }
199         }
200 free(disp->buf);
203 static struct buffer **
204 alloc_buffers(struct display *disp, uint32_t n,
205                 uint32_t fourcc, uint32_t w, uint32_t h)
207         struct buffer **bufs;
208         uint32_t i = 0;
210         bufs = calloc(n, sizeof(*bufs));
211         if (!bufs) {
212                 ERROR("allocation failed");
213                 goto fail;
214         }
216         for (i = 0; i < n; i++) {
217                 bufs[i] = alloc_buffer(disp, fourcc, w, h);
218                 if (!bufs[i]) {
219                         ERROR("allocation failed");
220                         goto fail;
221                 }
222         }
223         disp->buf=bufs;
224         return bufs;
226 fail:
227         // XXX cleanup
228         return NULL;
231 static struct buffer **
232 get_buffers(struct display *disp, uint32_t n)
234         return alloc_buffers(disp, n, 0, disp->width, disp->height);
237 static struct buffer **
238 get_vid_buffers(struct display *disp, uint32_t n,
239                 uint32_t fourcc, uint32_t w, uint32_t h)
241         return alloc_buffers(disp, n, fourcc, w, h);
244 static void
245 page_flip_handler(int fd, unsigned int frame,
246                 unsigned int sec, unsigned int usec, void *data)
248         struct display *disp = data;
249         struct display_kms *disp_kms = to_display_kms(disp);
251         disp_kms->completed_flips++;
253         MSG("Page flip: frame=%d, sec=%d, usec=%d, remaining=%d", frame, sec, usec,
254                         disp_kms->scheduled_flips - disp_kms->completed_flips);
257 static int
258 post_buffer(struct display *disp, struct buffer *buf)
260         struct display_kms *disp_kms = to_display_kms(disp);
261         struct buffer_kms *buf_kms = to_buffer_kms(buf);
262         int ret, last_err = 0, x = 0;
263         uint32_t i;
265         for (i = 0; i < disp_kms->connectors_count; i++) {
266                 struct connector *connector = &disp_kms->connector[i];
268                 if (! connector->mode) {
269                         continue;
270                 }
272                 if (! disp_kms->current) {
273                         /* first buffer we flip to, setup the mode (since this can't
274                          * be done earlier without a buffer to scanout)
275                          */
276                         MSG("Setting mode %s on connector %d, crtc %d",
277                                         connector->mode_str, connector->id, connector->crtc);
279                         ret = drmModeSetCrtc(disp->fd, connector->crtc, buf_kms->fb_id,
280                                         x, 0, &connector->id, 1, connector->mode);
282                         x += connector->mode->hdisplay;
283                 } else {
284                         ret = drmModePageFlip(disp->fd, connector->crtc, buf_kms->fb_id,
285                                         DRM_MODE_PAGE_FLIP_EVENT, disp);
286                         disp_kms->scheduled_flips++;
287                 }
289                 if (ret) {
290                         ERROR("Could not post buffer on crtc %d: %s (%d)",
291                                         connector->crtc, strerror(errno), ret);
292                         last_err = ret;
293                         /* well, keep trying the reset of the connectors.. */
294                 }
295         }
297         /* if we flipped, wait for all flips to complete! */
298         while (disp_kms->scheduled_flips > disp_kms->completed_flips) {
299                 drmEventContext evctx = {
300                                 .version = DRM_EVENT_CONTEXT_VERSION,
301                                 .page_flip_handler = page_flip_handler,
302                 };
303                 struct timeval timeout = {
304                                 .tv_sec = 3,
305                                 .tv_usec = 0,
306                 };
307                 fd_set fds;
309                 FD_ZERO(&fds);
310                 FD_SET(disp->fd, &fds);
312                 ret = select(disp->fd + 1, &fds, NULL, NULL, &timeout);
313                 if (ret <= 0) {
314                         if (errno == EAGAIN) {
315                                 continue;    /* keep going */
316                         } else {
317                                 ERROR("Timeout waiting for flip complete: %s (%d)",
318                                                 strerror(errno), ret);
319                                 last_err = ret;
320                                 break;
321                         }
322                 }
324                 drmHandleEvent(disp->fd, &evctx);
325         }
327         disp_kms->current = buf;
329         return last_err;
332 static int
333 post_vid_buffer(struct display *disp, struct buffer *buf,
334                 uint32_t x, uint32_t y, uint32_t w, uint32_t h)
336         struct display_kms *disp_kms = to_display_kms(disp);
337         struct buffer_kms *buf_kms = to_buffer_kms(buf);
338         int ret = 0;
339         uint32_t i, j;
340         int busy_planes = 0;
342         /* ensure we have the overlay setup: */
343         for (i = 0; i < disp_kms->connectors_count; i++) {
344                 struct connector *connector = &disp_kms->connector[i];
345                 uint32_t used_planes = 0;
346                 drmModeModeInfo *mode = connector->mode;
348                 if (! mode) {
349                         continue;
350                 }
352                 if (! disp_kms->ovr[i]) {
354                         for (j = 0; j + used_planes < disp_kms->plane_resources->count_planes; j++) {
355                                 drmModePlane *ovr = drmModeGetPlane(disp->fd,
356                                                 disp_kms->plane_resources->planes[j + busy_planes]);
357                                 if ((ovr->possible_crtcs & (1 << connector->pipe)) &&
358                                                 !(used_planes & (1 << j))) {
359                                         disp_kms->ovr[i] = ovr;
360                                         used_planes |= (1 << j);
361 printf("\nused_planes = %d\n", used_planes);
362                                         break;
363                                 }
364                         }
365                 }
367                 if (! disp_kms->ovr[i]) {
368                         MSG("Could not find plane for crtc %d", connector->crtc);
369                         ret = -1;
370                         /* carry on and see if we can find at least one usable plane */
371                         continue;
372                 }
374                 ret = drmModeSetPlane(disp->fd, disp_kms->ovr[i]->plane_id,
375                                 connector->crtc, buf_kms->fb_id, 0,
376                                 /* make video fullscreen: */
377                                 0, 0, mode->hdisplay, mode->vdisplay,
378                                 /* source/cropping coordinates are given in Q16 */
379                                 x << 16, y << 16, w << 16, h << 16);
380                 if (ret) {
381                         ERROR("failed to enable plane %d: %s",
382                                         disp_kms->ovr[i]->plane_id, strerror(errno));
383                 }
384         }
386         return ret;
389 static void
390 close_kms(struct display *disp)
394 static void
395 connector_find_mode(struct display *disp, struct connector *c)
397         struct display_kms *disp_kms = to_display_kms(disp);
398         drmModeConnector *connector;
399         int i, j;
401         /* First, find the connector & mode */
402         c->mode = NULL;
403         for (i = 0; i < disp_kms->resources->count_connectors; i++) {
404                 connector = drmModeGetConnector(disp->fd,
405                                 disp_kms->resources->connectors[i]);
407                 if (!connector) {
408                         ERROR("could not get connector %i: %s",
409                                         disp_kms->resources->connectors[i], strerror(errno));
410                         drmModeFreeConnector(connector);
411                         continue;
412                 }
414                 if (!connector->count_modes) {
415                         drmModeFreeConnector(connector);
416                         continue;
417                 }
419                 if (connector->connector_id != c->id) {
420                         drmModeFreeConnector(connector);
421                         continue;
422                 }
424                 for (j = 0; j < connector->count_modes; j++) {
425                         c->mode = &connector->modes[j];
426                         if (!strcmp(c->mode->name, c->mode_str))
427                                 break;
428                 }
430                 /* Found it, break out */
431                 if (c->mode)
432                         break;
434                 drmModeFreeConnector(connector);
435         }
437         if (!c->mode) {
438                 ERROR("failed to find mode \"%s\"", c->mode_str);
439                 return;
440         }
442         /* Now get the encoder */
443         for (i = 0; i < disp_kms->resources->count_encoders; i++) {
444                 c->encoder = drmModeGetEncoder(disp->fd,
445                                 disp_kms->resources->encoders[i]);
447                 if (!c->encoder) {
448                         ERROR("could not get encoder %i: %s",
449                                         disp_kms->resources->encoders[i], strerror(errno));
450                         drmModeFreeEncoder(c->encoder);
451                         continue;
452                 }
454                 if (c->encoder->encoder_id  == connector->encoder_id)
455                         break;
457                 drmModeFreeEncoder(c->encoder);
458         }
460         if (c->crtc == -1)
461                 c->crtc = c->encoder->crtc_id;
463         /* and figure out which crtc index it is: */
464         for (i = 0; i < disp_kms->resources->count_crtcs; i++) {
465                 if (c->crtc == (int)disp_kms->resources->crtcs[i]) {
466                         c->pipe = i;
467                         break;
468                 }
469         }
472 void
473 disp_kms_usage(void)
475         MSG("KMS Display Options:");
476         MSG("\t-1 \t\tforce single-plane buffers");
477         MSG("\t-t <tiled-mode>\t8, 16, 32, or auto");
478         MSG("\t-s <connector_id>:<mode>\tset a mode");
479         MSG("\t-s <connector_id>@<crtc_id>:<mode>\tset a mode");
482 struct display *
483 disp_kms_open(int argc, char **argv)
485         struct display_kms *disp_kms = NULL;
486         struct display *disp;
487         int i;
489         disp_kms = calloc(1, sizeof(*disp_kms));
490         if (!disp_kms) {
491                 ERROR("allocation failed");
492                 goto fail;
493         }
494         disp = &disp_kms->base;
496         disp->fd = drmOpen("omapdrm", NULL);
497         if (disp->fd < 0) {
498                 ERROR("could not open drm device: %s (%d)", strerror(errno), errno);
499                 goto fail;
500         }
502         disp->dev = omap_device_new(disp->fd);
503         if (!disp->dev) {
504                 ERROR("couldn't create device");
505                 goto fail;
506         }
508         disp->get_buffers = get_buffers;
509         disp->get_vid_buffers = get_vid_buffers;
510         disp->post_buffer = post_buffer;
511         disp->post_vid_buffer = post_vid_buffer;
512         disp->close = close_kms;
513         disp->disp_free_buf = free_buffers ;
514         disp_kms->resources = drmModeGetResources(disp->fd);
515         if (!disp_kms->resources) {
516                 ERROR("drmModeGetResources failed: %s", strerror(errno));
517                 goto fail;
518         }
520         disp_kms->plane_resources = drmModeGetPlaneResources(disp->fd);
521         if (!disp_kms->plane_resources) {
522                 ERROR("drmModeGetPlaneResources failed: %s", strerror(errno));
523                 goto fail;
524         }
526         disp->multiplanar = true;
528         /* note: set args to NULL after we've parsed them so other modules know
529          * that it is already parsed (since the arg parsing is decentralized)
530          */
531         for (i = 1; i < argc; i++) {
532                 if (!argv[i]) {
533                         continue;
534                 }
535                 if (!strcmp("-1", argv[i])) {
536                         disp->multiplanar = false;
537                 } else if (!strcmp("-t", argv[i])) {
538                         int n;
539                         argv[i++] = NULL;
540                         if (!strcmp(argv[i], "auto")) {
541                                 n = 0;
542                         } else if (sscanf(argv[i], "%d", &n) != 1) {
543                                 ERROR("invalid arg: %s", argv[i]);
544                                 goto fail;
545                         }
547                         disp_kms->bo_flags &= ~OMAP_BO_TILED;
549                         if (n == 8) {
550                                 disp_kms->bo_flags |= OMAP_BO_TILED_8;
551                         } else if (n == 16) {
552                                 disp_kms->bo_flags |= OMAP_BO_TILED_16;
553                         } else if (n == 32) {
554                                 disp_kms->bo_flags |= OMAP_BO_TILED_32;
555                         } else if (n == 0) {
556                                 disp_kms->bo_flags |= OMAP_BO_TILED;
557                         } else {
558                                 ERROR("invalid arg: %s", argv[i]);
559                                 goto fail;
560                         }
561                 } else if (!strcmp("-s", argv[i])) {
562                         struct connector *connector =
563                                         &disp_kms->connector[disp_kms->connectors_count++];
564                         connector->crtc = -1;
565                         argv[i++] = NULL;
566                         if (sscanf(argv[i], "%d:%64s",
567                                    &connector->id,
568                                    connector->mode_str) != 2 &&
569                             sscanf(argv[i], "%d@%d:%64s",
570                                    &connector->id,
571                                    &connector->crtc,
572                                    connector->mode_str) != 3) {
573                                 // TODO: we could support connector specified as a name too, I suppose
574                                 ERROR("invalid arg: %s", argv[i]);
575                                 goto fail;
576                         }
577                         disp_kms->bo_flags |= OMAP_BO_SCANOUT;
578                 } else {
579                         /* ignore */
580                         continue;
581                 }
582                 argv[i] = NULL;
583         }
585         disp->width = 0;
586         disp->height = 0;
587         for (i = 0; i < (int)disp_kms->connectors_count; i++) {
588                 struct connector *c = &disp_kms->connector[i];
589                 connector_find_mode(disp, c);
590                 if (c->mode == NULL)
591                         continue;
592                 /* setup side-by-side virtual display */
593                 disp->width += c->mode->hdisplay;
594                 if (disp->height < c->mode->vdisplay) {
595                         disp->height = c->mode->vdisplay;
596                 }
597         }
599         MSG("using %d connectors, %dx%d display, multiplanar: %d",
600                         disp_kms->connectors_count, disp->width, disp->height, disp->multiplanar);
602         return disp;
604 fail:
605         // XXX cleanup
606         return NULL;