58746edc4c3524315705a3396022f34ac3a7845e
[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         bool no_master;
59         int mastership;
60 };
62 #define to_buffer_kms(x) container_of(x, struct buffer_kms, base)
63 struct buffer_kms {
64         struct buffer base;
65         uint32_t fb_id;
66 };
68 static int global_fd = 0;
69 static uint32_t used_planes = 0;
70 static int ndisplays = 0;
72 static struct omap_bo *
73 alloc_bo(struct display *disp, uint32_t bpp, uint32_t width, uint32_t height,
74                 uint32_t *bo_handle, uint32_t *pitch)
75 {
76         struct display_kms *disp_kms = to_display_kms(disp);
77         struct omap_bo *bo;
78         uint32_t bo_flags = disp_kms->bo_flags;
80         if ((bo_flags & OMAP_BO_TILED) == OMAP_BO_TILED) {
81                 bo_flags &= ~OMAP_BO_TILED;
82                 if (bpp == 8) {
83                         bo_flags |= OMAP_BO_TILED_8;
84                 } else if (bpp == 16) {
85                         bo_flags |= OMAP_BO_TILED_16;
86                 } else if (bpp == 32) {
87                         bo_flags |= OMAP_BO_TILED_32;
88                 }
89         }
90         bo_flags |= OMAP_BO_WC;
92         if (bo_flags & OMAP_BO_TILED) {
93                 bo = omap_bo_new_tiled(disp->dev, ALIGN2(width,7), height, bo_flags);
94         } else {
95                 bo = omap_bo_new(disp->dev, width * height * bpp / 8, bo_flags);
96         }
98         if (bo) {
99                 *bo_handle = omap_bo_handle(bo);
100                 *pitch = width * bpp / 8;
101                 if (bo_flags & OMAP_BO_TILED)
102                         *pitch = ALIGN2(*pitch, PAGE_SHIFT);
103         }
105         return bo;
108 static struct buffer *
109 alloc_buffer(struct display *disp, uint32_t fourcc, uint32_t w, uint32_t h)
111         struct buffer_kms *buf_kms;
112         struct buffer *buf;
113         uint32_t bo_handles[4] = {0}, offsets[4] = {0};
114         int ret;
116         buf_kms = calloc(1, sizeof(*buf_kms));
117         if (!buf_kms) {
118                 ERROR("allocation failed");
119                 return NULL;
120         }
121         buf = &buf_kms->base;
123         buf->fourcc = fourcc;
124         buf->width = w;
125         buf->height = h;
126         buf->multiplanar = true;
128         buf->nbo = 1;
130         if (!fourcc)
131                 fourcc = FOURCC('A','R','2','4');
133         switch(fourcc) {
134         case FOURCC('A','R','2','4'):
135                 buf->nbo = 1;
136                 buf->bo[0] = alloc_bo(disp, 32, buf->width, buf->height,
137                                 &bo_handles[0], &buf->pitches[0]);
138                 break;
139         case FOURCC('U','Y','V','Y'):
140         case FOURCC('Y','U','Y','V'):
141                 buf->nbo = 1;
142                 buf->bo[0] = alloc_bo(disp, 16, buf->width, buf->height,
143                                 &bo_handles[0], &buf->pitches[0]);
144                 break;
145         case FOURCC('N','V','1','2'):
146                 if (disp->multiplanar) {
147                         buf->nbo = 2;
148                         buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
149                                         &bo_handles[0], &buf->pitches[0]);
150                         buf->fd[0] = omap_bo_dmabuf(buf->bo[0]);
151                         buf->bo[1] = alloc_bo(disp, 16, buf->width/2, buf->height/2,
152                                         &bo_handles[1], &buf->pitches[1]);
153                         buf->fd[1] = omap_bo_dmabuf(buf->bo[1]);
154                 } else {
155                         buf->nbo = 1;
156                         buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height * 3 / 2,
157                                         &bo_handles[0], &buf->pitches[0]);
158                         buf->fd[0] = omap_bo_dmabuf(buf->bo[0]);
159                         bo_handles[1] = bo_handles[0];
160                         buf->pitches[1] = buf->pitches[0];
161                         offsets[1] = buf->width * buf->height;
162                         buf->multiplanar = false;
163                 }
164                 break;
165         case FOURCC('I','4','2','0'):
166                 buf->nbo = 3;
167                 buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
168                                 &bo_handles[0], &buf->pitches[0]);
169                 buf->bo[1] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
170                                 &bo_handles[1], &buf->pitches[1]);
171                 buf->bo[2] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
172                                 &bo_handles[2], &buf->pitches[2]);
173                 break;
174         default:
175                 ERROR("invalid format: 0x%08x", fourcc);
176                 goto fail;
177         }
179         ret = drmModeAddFB2(disp->fd, buf->width, buf->height, fourcc,
180                         bo_handles, buf->pitches, offsets, &buf_kms->fb_id, 0);
181         if (ret) {
182                 ERROR("drmModeAddFB2 failed: %s (%d)", strerror(errno), ret);
183                 goto fail;
184         }
186         return buf;
188 fail:
189         // XXX cleanup
190         return NULL;
193 void
194 free_buffers(struct display *disp, uint32_t n)
196         uint32_t i;
197         for (i = 0; i < n; i++) {
198                 if (disp->buf[i]) {
199                         close(disp->buf[i]->fd[0]);
200                         omap_bo_del(disp->buf[i]->bo[0]);
201                         if(disp->multiplanar){
202                                 close(disp->buf[i]->fd[1]);
203                                 omap_bo_del(disp->buf[i]->bo[1]);
204                         }
205                 }
206         }
207 free(disp->buf);
210 static struct buffer **
211 alloc_buffers(struct display *disp, uint32_t n,
212                 uint32_t fourcc, uint32_t w, uint32_t h)
214         struct buffer **bufs;
215         uint32_t i = 0;
217         bufs = calloc(n, sizeof(*bufs));
218         if (!bufs) {
219                 ERROR("allocation failed");
220                 goto fail;
221         }
223         for (i = 0; i < n; i++) {
224                 bufs[i] = alloc_buffer(disp, fourcc, w, h);
225                 if (!bufs[i]) {
226                         ERROR("allocation failed");
227                         goto fail;
228                 }
229         }
230         disp->buf=bufs;
231         return bufs;
233 fail:
234         // XXX cleanup
235         return NULL;
238 static struct buffer **
239 get_buffers(struct display *disp, uint32_t n)
241         return alloc_buffers(disp, n, 0, disp->width, disp->height);
244 static struct buffer **
245 get_vid_buffers(struct display *disp, uint32_t n,
246                 uint32_t fourcc, uint32_t w, uint32_t h)
248         return alloc_buffers(disp, n, fourcc, w, h);
251 static void
252 page_flip_handler(int fd, unsigned int frame,
253                 unsigned int sec, unsigned int usec, void *data)
255         struct display *disp = data;
256         struct display_kms *disp_kms = to_display_kms(disp);
258         disp_kms->completed_flips++;
260         MSG("Page flip: frame=%d, sec=%d, usec=%d, remaining=%d", frame, sec, usec,
261                         disp_kms->scheduled_flips - disp_kms->completed_flips);
264 static int
265 post_buffer(struct display *disp, struct buffer *buf)
267         struct display_kms *disp_kms = to_display_kms(disp);
268         struct buffer_kms *buf_kms = to_buffer_kms(buf);
269         int ret, last_err = 0, x = 0;
270         uint32_t i;
272         for (i = 0; i < disp_kms->connectors_count; i++) {
273                 struct connector *connector = &disp_kms->connector[i];
275                 if (! connector->mode) {
276                         continue;
277                 }
279                 if (! disp_kms->current) {
280                         /* first buffer we flip to, setup the mode (since this can't
281                          * be done earlier without a buffer to scanout)
282                          */
283                         MSG("Setting mode %s on connector %d, crtc %d",
284                                         connector->mode_str, connector->id, connector->crtc);
286                         ret = drmModeSetCrtc(disp->fd, connector->crtc, buf_kms->fb_id,
287                                         x, 0, &connector->id, 1, connector->mode);
289                         x += connector->mode->hdisplay;
290                 } else {
291                         ret = drmModePageFlip(disp->fd, connector->crtc, buf_kms->fb_id,
292                                         DRM_MODE_PAGE_FLIP_EVENT, disp);
293                         disp_kms->scheduled_flips++;
294                 }
296                 if (ret) {
297                         ERROR("Could not post buffer on crtc %d: %s (%d)",
298                                         connector->crtc, strerror(errno), ret);
299                         last_err = ret;
300                         /* well, keep trying the reset of the connectors.. */
301                 }
302         }
304         /* if we flipped, wait for all flips to complete! */
305         while (disp_kms->scheduled_flips > disp_kms->completed_flips) {
306                 drmEventContext evctx = {
307                                 .version = DRM_EVENT_CONTEXT_VERSION,
308                                 .page_flip_handler = page_flip_handler,
309                 };
310                 struct timeval timeout = {
311                                 .tv_sec = 3,
312                                 .tv_usec = 0,
313                 };
314                 fd_set fds;
316                 FD_ZERO(&fds);
317                 FD_SET(disp->fd, &fds);
319                 ret = select(disp->fd + 1, &fds, NULL, NULL, &timeout);
320                 if (ret <= 0) {
321                         if (errno == EAGAIN) {
322                                 continue;    /* keep going */
323                         } else {
324                                 ERROR("Timeout waiting for flip complete: %s (%d)",
325                                                 strerror(errno), ret);
326                                 last_err = ret;
327                                 break;
328                         }
329                 }
331                 drmHandleEvent(disp->fd, &evctx);
332         }
334         disp_kms->current = buf;
336         return last_err;
339 int
340 get_overlay_plane(struct display *disp, struct buffer *buf)
342         struct display_kms *disp_kms = to_display_kms(disp);
343         uint32_t i, ret;
345         for (i = 0; i < disp_kms->connectors_count; i++) {
346                 struct connector *connector = &disp_kms->connector[i];
347                 drmModeModeInfo *mode = connector->mode;
349                 disp_kms->ovr[i] = drmModeGetPlane(disp->fd,
350                                         disp_kms->plane_resources->planes[1]);
352                 ret = drmModeObjectSetProperty(disp->fd,
353                         disp_kms->ovr[i]->plane_id, DRM_MODE_OBJECT_PLANE, 7, 3);
354                 if (ret < 0) {
355                         MSG("Could not set Z order for plane");
356                         return ret;
357                 }
358         }
359         return 0;
362 static int
363 post_vid_buffer(struct display *disp, struct buffer *buf,
364                 uint32_t x, uint32_t y, uint32_t w, uint32_t h)
366         struct display_kms *disp_kms = to_display_kms(disp);
367         struct buffer_kms *buf_kms = to_buffer_kms(buf);
368         int ret = 0;
369         uint32_t i, j;
371         /* ensure we have the overlay setup: */
372         for (i = 0; i < disp_kms->connectors_count; i++) {
373                 struct connector *connector = &disp_kms->connector[i];
374                 drmModeModeInfo *mode = connector->mode;
376                 if (! mode) {
377                         continue;
378                 }
380                 if (! disp_kms->ovr[i]) {
382                         for (j = 0; j < disp_kms->plane_resources->count_planes; j++) {
383                                 if (used_planes & (1 << j)) {
384                                         continue;
385                                 }
386                                 drmModePlane *ovr = drmModeGetPlane(disp->fd,
387                                                 disp_kms->plane_resources->planes[j]);
388                                 if (ovr->possible_crtcs & (1 << connector->pipe)) {
389                                         disp_kms->ovr[i] = ovr;
390                                         used_planes |= (1 << j);
391                                         break;
392                                 }
393                         }
394                 }
396                 if (! disp_kms->ovr[i]) {
397                         MSG("Could not find plane for crtc %d", connector->crtc);
398                         ret = -1;
399                         /* carry on and see if we can find at least one usable plane */
400                         continue;
401                 }
403                 if(buf->noScale) {
404                         ret = drmModeSetPlane(disp->fd, disp_kms->ovr[i]->plane_id,
405                                 connector->crtc, buf_kms->fb_id, 0,
406                                 /* Use x and y as co-ordinates of overlay */
407                                 x, y, buf->width, buf->height,
408                                 /* Consider source x and y is 0 always */
409                                 0, 0, w << 16, h << 16);
410                 } else {
411                         ret = drmModeSetPlane(disp->fd, disp_kms->ovr[i]->plane_id,
412                                 connector->crtc, buf_kms->fb_id, 0,
413                                 /* make video fullscreen: */
414                                 0, 0, mode->hdisplay, mode->vdisplay,
415                                 /* source/cropping coordinates are given in Q16 */
416                                 x << 16, y << 16, w << 16, h << 16);
417                 }
418                 if (ret) {
419                         ERROR("failed to enable plane %d: %s",
420                                         disp_kms->ovr[i]->plane_id, strerror(errno));
421                 }
422         }
424         if (disp_kms->no_master && disp_kms->mastership) {
425                 /* Drop mastership after the first buffer on each plane is
426                  * displayed. This will lock these planes for us and allow
427                  * others to consume remaining
428                  */
429                 disp_kms->mastership = 0;
430                 drmDropMaster(disp->fd);
431         }
433         return ret;
436 static void
437 close_kms(struct display *disp)
439         omap_device_del(disp->dev);
440         disp->dev = NULL;
441         if (used_planes) {
442                 used_planes >>= 1;
443         }
444         if (--ndisplays == 0) {
445                 close(global_fd);
446         }
449 static void
450 connector_find_mode(struct display *disp, struct connector *c)
452         struct display_kms *disp_kms = to_display_kms(disp);
453         drmModeConnector *connector;
454         int i, j;
456         /* First, find the connector & mode */
457         c->mode = NULL;
458         for (i = 0; i < disp_kms->resources->count_connectors; i++) {
459                 connector = drmModeGetConnector(disp->fd,
460                                 disp_kms->resources->connectors[i]);
462                 if (!connector) {
463                         ERROR("could not get connector %i: %s",
464                                         disp_kms->resources->connectors[i], strerror(errno));
465                         drmModeFreeConnector(connector);
466                         continue;
467                 }
469                 if (!connector->count_modes) {
470                         drmModeFreeConnector(connector);
471                         continue;
472                 }
474                 if (connector->connector_id != c->id) {
475                         drmModeFreeConnector(connector);
476                         continue;
477                 }
479                 for (j = 0; j < connector->count_modes; j++) {
480                         c->mode = &connector->modes[j];
481                         if (!strcmp(c->mode->name, c->mode_str))
482                                 break;
483                 }
485                 /* Found it, break out */
486                 if (c->mode)
487                         break;
489                 drmModeFreeConnector(connector);
490         }
492         if (!c->mode) {
493                 ERROR("failed to find mode \"%s\"", c->mode_str);
494                 return;
495         }
497         /* Now get the encoder */
498         for (i = 0; i < connector->count_encoders; i++) {
499                 c->encoder = drmModeGetEncoder(disp->fd,
500                                 connector->encoders[i]);
502                 if (!c->encoder) {
503                         ERROR("could not get encoder %i: %s",
504                                         disp_kms->resources->encoders[i], strerror(errno));
505                         continue;
506                 }
508                 /* Take the fisrt one, if none is assigned */
509                 if (!connector->encoder_id)
510                 {
511                         connector->encoder_id = c->encoder->encoder_id;
512                 }
514                 if (c->encoder->encoder_id  == connector->encoder_id) {
515                         /* find the first valid CRTC if not assigned */
516                         if (!c->encoder->crtc_id)
517                         {
518                                 int k;
519                                 for (k = 0; k < disp_kms->resources->count_crtcs; ++k) {
520                                         /* check whether this CRTC works with the encoder */
521                                         if (!(c->encoder->possible_crtcs & (1 << k)))
522                                                 continue;
524                                         c->encoder->crtc_id = disp_kms->resources->crtcs[k];
525                                         break;
526                                 }
528                                 if (!c->encoder->crtc_id)
529                                 {
530                                         ERROR("Encoder(%d): no CRTC found!\n", c->encoder->encoder_id);
531                                         drmModeFreeEncoder(c->encoder);
532                                         continue;
533                                 }
534                         }
535                         break;
536                 }
537                 drmModeFreeEncoder(c->encoder);
538         }
540         if (c->crtc == -1)
541                 c->crtc = c->encoder->crtc_id;
543         /* and figure out which crtc index it is: */
544         for (i = 0; i < disp_kms->resources->count_crtcs; i++) {
545                 if (c->crtc == (int)disp_kms->resources->crtcs[i]) {
546                         c->pipe = i;
547                         break;
548                 }
549         }
552 void
553 disp_kms_usage(void)
555         MSG("KMS Display Options:");
556         MSG("\t-1 \t\tforce single-plane buffers");
557         MSG("\t-t <tiled-mode>\t8, 16, 32, or auto");
558         MSG("\t-s <connector_id>:<mode>\tset a mode");
559         MSG("\t-s <connector_id>@<crtc_id>:<mode>\tset a mode");
562 struct display *
563 disp_kms_open(int argc, char **argv)
565         struct display_kms *disp_kms = NULL;
566         struct display *disp;
567         int i;
569         disp_kms = calloc(1, sizeof(*disp_kms));
570         if (!disp_kms) {
571                 ERROR("allocation failed");
572                 goto fail;
573         }
574         disp = &disp_kms->base;
576         if (!global_fd) {
577                 global_fd = drmOpen("omapdrm", NULL);
578                 if (global_fd < 0) {
579                         ERROR("could not open drm device: %s (%d)", strerror(errno), errno);
580                         goto fail;
581                 }
582         }
584         disp->fd = global_fd;
585         ndisplays++;  /* increment the number of displays counter */
587         disp->dev = omap_device_new(disp->fd);
588         if (!disp->dev) {
589                 ERROR("couldn't create device");
590                 goto fail;
591         }
593         disp->get_buffers = get_buffers;
594         disp->get_vid_buffers = get_vid_buffers;
595         disp->post_buffer = post_buffer;
596         disp->post_vid_buffer = post_vid_buffer;
597         disp->close = close_kms;
598         disp->disp_free_buf = free_buffers ;
599         disp_kms->resources = drmModeGetResources(disp->fd);
600         if (!disp_kms->resources) {
601                 ERROR("drmModeGetResources failed: %s", strerror(errno));
602                 goto fail;
603         }
605         disp_kms->plane_resources = drmModeGetPlaneResources(disp->fd);
606         if (!disp_kms->plane_resources) {
607                 ERROR("drmModeGetPlaneResources failed: %s", strerror(errno));
608                 goto fail;
609         }
611         disp->multiplanar = true;
613         /* note: set args to NULL after we've parsed them so other modules know
614          * that it is already parsed (since the arg parsing is decentralized)
615          */
616         for (i = 1; i < argc; i++) {
617                 if (!argv[i]) {
618                         continue;
619                 }
620                 if (!strcmp("-1", argv[i])) {
621                         disp->multiplanar = false;
622                 } else if (!strcmp("-t", argv[i])) {
623                         int n;
624                         argv[i++] = NULL;
625                         if (!strcmp(argv[i], "auto")) {
626                                 n = 0;
627                         } else if (sscanf(argv[i], "%d", &n) != 1) {
628                                 ERROR("invalid arg: %s", argv[i]);
629                                 goto fail;
630                         }
632                         disp_kms->bo_flags &= ~OMAP_BO_TILED;
634                         if (n == 8) {
635                                 disp_kms->bo_flags |= OMAP_BO_TILED_8;
636                         } else if (n == 16) {
637                                 disp_kms->bo_flags |= OMAP_BO_TILED_16;
638                         } else if (n == 32) {
639                                 disp_kms->bo_flags |= OMAP_BO_TILED_32;
640                         } else if (n == 0) {
641                                 disp_kms->bo_flags |= OMAP_BO_TILED;
642                         } else {
643                                 ERROR("invalid arg: %s", argv[i]);
644                                 goto fail;
645                         }
646                 } else if (!strcmp("-s", argv[i])) {
647                         struct connector *connector =
648                                         &disp_kms->connector[disp_kms->connectors_count++];
649                         connector->crtc = -1;
650                         argv[i++] = NULL;
651                         if (sscanf(argv[i], "%d:%64s",
652                                    &connector->id,
653                                    connector->mode_str) != 2 &&
654                             sscanf(argv[i], "%d@%d:%64s",
655                                    &connector->id,
656                                    &connector->crtc,
657                                    connector->mode_str) != 3) {
658                                 // TODO: we could support connector specified as a name too, I suppose
659                                 ERROR("invalid arg: %s", argv[i]);
660                                 goto fail;
661                         }
662                         disp_kms->bo_flags |= OMAP_BO_SCANOUT;
663                 } else if (!strcmp("-nm", argv[i])) {
664                         disp_kms->no_master = true;
665                         disp_kms->mastership = 1;
666                 } else {
667                         /* ignore */
668                         continue;
669                 }
670                 argv[i] = NULL;
671         }
673         disp->width = 0;
674         disp->height = 0;
675         for (i = 0; i < (int)disp_kms->connectors_count; i++) {
676                 struct connector *c = &disp_kms->connector[i];
677                 connector_find_mode(disp, c);
678                 if (c->mode == NULL)
679                         continue;
680                 /* setup side-by-side virtual display */
681                 disp->width += c->mode->hdisplay;
682                 if (disp->height < c->mode->vdisplay) {
683                         disp->height = c->mode->vdisplay;
684                 }
685         }
687         MSG("using %d connectors, %dx%d display, multiplanar: %d",
688                         disp_kms->connectors_count, disp->width, disp->height, disp->multiplanar);
690         return disp;
692 fail:
693         // XXX cleanup
694         return NULL;