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;
101 }
103 static struct buffer *
104 alloc_buffer(struct display *disp, uint32_t fourcc, uint32_t w, uint32_t h)
105 {
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->bo[1] = alloc_bo(disp, 16, buf->width/2, buf->height/2,
146 &bo_handles[1], &buf->pitches[1]);
147 } else {
148 buf->nbo = 1;
149 buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height * 3 / 2,
150 &bo_handles[0], &buf->pitches[0]);
151 bo_handles[1] = bo_handles[0];
152 buf->pitches[1] = buf->pitches[0];
153 offsets[1] = buf->width * buf->height;
154 buf->multiplanar = false;
155 }
156 break;
157 case FOURCC('I','4','2','0'):
158 buf->nbo = 3;
159 buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
160 &bo_handles[0], &buf->pitches[0]);
161 buf->bo[1] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
162 &bo_handles[1], &buf->pitches[1]);
163 buf->bo[2] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
164 &bo_handles[2], &buf->pitches[2]);
165 break;
166 default:
167 ERROR("invalid format: 0x%08x", fourcc);
168 goto fail;
169 }
171 ret = drmModeAddFB2(disp->fd, buf->width, buf->height, fourcc,
172 bo_handles, buf->pitches, offsets, &buf_kms->fb_id, 0);
173 if (ret) {
174 ERROR("drmModeAddFB2 failed: %s (%d)", strerror(errno), ret);
175 goto fail;
176 }
178 return buf;
180 fail:
181 // XXX cleanup
182 return NULL;
183 }
185 static struct buffer **
186 alloc_buffers(struct display *disp, uint32_t n,
187 uint32_t fourcc, uint32_t w, uint32_t h)
188 {
189 struct buffer **bufs;
190 uint32_t i = 0;
192 bufs = calloc(n, sizeof(*bufs));
193 if (!bufs) {
194 ERROR("allocation failed");
195 goto fail;
196 }
198 for (i = 0; i < n; i++) {
199 bufs[i] = alloc_buffer(disp, fourcc, w, h);
200 if (!bufs[i]) {
201 ERROR("allocation failed");
202 goto fail;
203 }
204 }
206 return bufs;
208 fail:
209 // XXX cleanup
210 return NULL;
211 }
213 static struct buffer **
214 get_buffers(struct display *disp, uint32_t n)
215 {
216 return alloc_buffers(disp, n, 0, disp->width, disp->height);
217 }
219 static struct buffer **
220 get_vid_buffers(struct display *disp, uint32_t n,
221 uint32_t fourcc, uint32_t w, uint32_t h)
222 {
223 return alloc_buffers(disp, n, fourcc, w, h);
224 }
226 static void
227 page_flip_handler(int fd, unsigned int frame,
228 unsigned int sec, unsigned int usec, void *data)
229 {
230 struct display *disp = data;
231 struct display_kms *disp_kms = to_display_kms(disp);
233 disp_kms->completed_flips++;
235 MSG("Page flip: frame=%d, sec=%d, usec=%d, remaining=%d", frame, sec, usec,
236 disp_kms->scheduled_flips - disp_kms->completed_flips);
237 }
239 static int
240 post_buffer(struct display *disp, struct buffer *buf)
241 {
242 struct display_kms *disp_kms = to_display_kms(disp);
243 struct buffer_kms *buf_kms = to_buffer_kms(buf);
244 int ret, last_err = 0, x = 0;
245 uint32_t i;
247 for (i = 0; i < disp_kms->connectors_count; i++) {
248 struct connector *connector = &disp_kms->connector[i];
250 if (! connector->mode) {
251 continue;
252 }
254 if (! disp_kms->current) {
255 /* first buffer we flip to, setup the mode (since this can't
256 * be done earlier without a buffer to scanout)
257 */
258 MSG("Setting mode %s on connector %d, crtc %d",
259 connector->mode_str, connector->id, connector->crtc);
261 ret = drmModeSetCrtc(disp->fd, connector->crtc, buf_kms->fb_id,
262 x, 0, &connector->id, 1, connector->mode);
264 x += connector->mode->hdisplay;
265 } else {
266 ret = drmModePageFlip(disp->fd, connector->crtc, buf_kms->fb_id,
267 DRM_MODE_PAGE_FLIP_EVENT, disp);
268 disp_kms->scheduled_flips++;
269 }
271 if (ret) {
272 ERROR("Could not post buffer on crtc %d: %s (%d)",
273 connector->crtc, strerror(errno), ret);
274 last_err = ret;
275 /* well, keep trying the reset of the connectors.. */
276 }
277 }
279 /* if we flipped, wait for all flips to complete! */
280 while (disp_kms->scheduled_flips > disp_kms->completed_flips) {
281 drmEventContext evctx = {
282 .version = DRM_EVENT_CONTEXT_VERSION,
283 .page_flip_handler = page_flip_handler,
284 };
285 struct timeval timeout = {
286 .tv_sec = 3,
287 .tv_usec = 0,
288 };
289 fd_set fds;
291 FD_ZERO(&fds);
292 FD_SET(disp->fd, &fds);
294 ret = select(disp->fd + 1, &fds, NULL, NULL, &timeout);
295 if (ret <= 0) {
296 if (errno == EAGAIN) {
297 continue; /* keep going */
298 } else {
299 ERROR("Timeout waiting for flip complete: %s (%d)",
300 strerror(errno), ret);
301 last_err = ret;
302 break;
303 }
304 }
306 drmHandleEvent(disp->fd, &evctx);
307 }
309 disp_kms->current = buf;
311 return last_err;
312 }
314 static int
315 post_vid_buffer(struct display *disp, struct buffer *buf,
316 uint32_t x, uint32_t y, uint32_t w, uint32_t h)
317 {
318 struct display_kms *disp_kms = to_display_kms(disp);
319 struct buffer_kms *buf_kms = to_buffer_kms(buf);
320 int ret = 0;
321 uint32_t i, j;
323 /* ensure we have the overlay setup: */
324 for (i = 0; i < disp_kms->connectors_count; i++) {
325 struct connector *connector = &disp_kms->connector[i];
326 uint32_t used_planes = 0;
327 drmModeModeInfo *mode = connector->mode;
329 if (! mode) {
330 continue;
331 }
333 if (! disp_kms->ovr[i]) {
335 for (j = 0; j < disp_kms->plane_resources->count_planes; j++) {
336 drmModePlane *ovr = drmModeGetPlane(disp->fd,
337 disp_kms->plane_resources->planes[j]);
338 if ((ovr->possible_crtcs & (1 << connector->pipe)) &&
339 !(used_planes & (1 << j))) {
340 disp_kms->ovr[i] = ovr;
341 used_planes |= (1 << j);
342 break;
343 }
344 }
345 }
347 if (! disp_kms->ovr[i]) {
348 MSG("Could not find plane for crtc %d", connector->crtc);
349 ret = -1;
350 /* carry on and see if we can find at least one usable plane */
351 continue;
352 }
354 ret = drmModeSetPlane(disp->fd, disp_kms->ovr[i]->plane_id,
355 connector->crtc, buf_kms->fb_id, 0,
356 /* make video fullscreen: */
357 0, 0, mode->hdisplay, mode->vdisplay,
358 /* source/cropping coordinates are given in Q16 */
359 x << 16, y << 16, w << 16, h << 16);
360 if (ret) {
361 ERROR("failed to enable plane %d: %s",
362 disp_kms->ovr[i]->plane_id, strerror(errno));
363 }
364 }
366 return ret;
367 }
369 static void
370 close_kms(struct display *disp)
371 {
372 }
374 static void
375 connector_find_mode(struct display *disp, struct connector *c)
376 {
377 struct display_kms *disp_kms = to_display_kms(disp);
378 drmModeConnector *connector;
379 int i, j;
381 /* First, find the connector & mode */
382 c->mode = NULL;
383 for (i = 0; i < disp_kms->resources->count_connectors; i++) {
384 connector = drmModeGetConnector(disp->fd,
385 disp_kms->resources->connectors[i]);
387 if (!connector) {
388 ERROR("could not get connector %i: %s",
389 disp_kms->resources->connectors[i], strerror(errno));
390 drmModeFreeConnector(connector);
391 continue;
392 }
394 if (!connector->count_modes) {
395 drmModeFreeConnector(connector);
396 continue;
397 }
399 if (connector->connector_id != c->id) {
400 drmModeFreeConnector(connector);
401 continue;
402 }
404 for (j = 0; j < connector->count_modes; j++) {
405 c->mode = &connector->modes[j];
406 if (!strcmp(c->mode->name, c->mode_str))
407 break;
408 }
410 /* Found it, break out */
411 if (c->mode)
412 break;
414 drmModeFreeConnector(connector);
415 }
417 if (!c->mode) {
418 ERROR("failed to find mode \"%s\"", c->mode_str);
419 return;
420 }
422 /* Now get the encoder */
423 for (i = 0; i < disp_kms->resources->count_encoders; i++) {
424 c->encoder = drmModeGetEncoder(disp->fd,
425 disp_kms->resources->encoders[i]);
427 if (!c->encoder) {
428 ERROR("could not get encoder %i: %s",
429 disp_kms->resources->encoders[i], strerror(errno));
430 drmModeFreeEncoder(c->encoder);
431 continue;
432 }
434 if (c->encoder->encoder_id == connector->encoder_id)
435 break;
437 drmModeFreeEncoder(c->encoder);
438 }
440 if (c->crtc == -1)
441 c->crtc = c->encoder->crtc_id;
443 /* and figure out which crtc index it is: */
444 for (i = 0; i < disp_kms->resources->count_crtcs; i++) {
445 if (c->crtc == (int)disp_kms->resources->crtcs[i]) {
446 c->pipe = i;
447 break;
448 }
449 }
450 }
452 void
453 disp_kms_usage(void)
454 {
455 MSG("KMS Display Options:");
456 MSG("\t-1 \t\tforce single-plane buffers");
457 MSG("\t-t <tiled-mode>\t8, 16, 32, or auto");
458 MSG("\t-s <connector_id>:<mode>\tset a mode");
459 MSG("\t-s <connector_id>@<crtc_id>:<mode>\tset a mode");
460 }
462 struct display *
463 disp_kms_open(int argc, char **argv)
464 {
465 struct display_kms *disp_kms = NULL;
466 struct display *disp;
467 int i;
469 disp_kms = calloc(1, sizeof(*disp_kms));
470 if (!disp_kms) {
471 ERROR("allocation failed");
472 goto fail;
473 }
474 disp = &disp_kms->base;
476 disp->fd = drmOpen("omapdrm", NULL);
477 if (disp->fd < 0) {
478 ERROR("could not open drm device: %s (%d)", strerror(errno), errno);
479 goto fail;
480 }
482 disp->dev = omap_device_new(disp->fd);
483 if (!disp->dev) {
484 ERROR("couldn't create device");
485 goto fail;
486 }
488 disp->get_buffers = get_buffers;
489 disp->get_vid_buffers = get_vid_buffers;
490 disp->post_buffer = post_buffer;
491 disp->post_vid_buffer = post_vid_buffer;
492 disp->close = close_kms;
494 disp_kms->resources = drmModeGetResources(disp->fd);
495 if (!disp_kms->resources) {
496 ERROR("drmModeGetResources failed: %s", strerror(errno));
497 goto fail;
498 }
500 disp_kms->plane_resources = drmModeGetPlaneResources(disp->fd);
501 if (!disp_kms->plane_resources) {
502 ERROR("drmModeGetPlaneResources failed: %s", strerror(errno));
503 goto fail;
504 }
506 disp->multiplanar = true;
508 /* note: set args to NULL after we've parsed them so other modules know
509 * that it is already parsed (since the arg parsing is decentralized)
510 */
511 for (i = 1; i < argc; i++) {
512 if (!argv[i]) {
513 continue;
514 }
515 if (!strcmp("-1", argv[i])) {
516 disp->multiplanar = false;
517 } else if (!strcmp("-t", argv[i])) {
518 int n;
519 argv[i++] = NULL;
520 if (!strcmp(argv[i], "auto")) {
521 n = 0;
522 } else if (sscanf(argv[i], "%d", &n) != 1) {
523 ERROR("invalid arg: %s", argv[i]);
524 goto fail;
525 }
527 disp_kms->bo_flags &= ~OMAP_BO_TILED;
529 if (n == 8) {
530 disp_kms->bo_flags |= OMAP_BO_TILED_8;
531 } else if (n == 16) {
532 disp_kms->bo_flags |= OMAP_BO_TILED_16;
533 } else if (n == 32) {
534 disp_kms->bo_flags |= OMAP_BO_TILED_32;
535 } else if (n == 0) {
536 disp_kms->bo_flags |= OMAP_BO_TILED;
537 } else {
538 ERROR("invalid arg: %s", argv[i]);
539 goto fail;
540 }
541 } else if (!strcmp("-s", argv[i])) {
542 struct connector *connector =
543 &disp_kms->connector[disp_kms->connectors_count++];
544 connector->crtc = -1;
545 argv[i++] = NULL;
546 if (sscanf(argv[i], "%d:%64s",
547 &connector->id,
548 connector->mode_str) != 2 &&
549 sscanf(argv[i], "%d@%d:%64s",
550 &connector->id,
551 &connector->crtc,
552 connector->mode_str) != 3) {
553 // TODO: we could support connector specified as a name too, I suppose
554 ERROR("invalid arg: %s", argv[i]);
555 goto fail;
556 }
557 disp_kms->bo_flags |= OMAP_BO_SCANOUT;
558 } else {
559 /* ignore */
560 continue;
561 }
562 argv[i] = NULL;
563 }
565 disp->width = 0;
566 disp->height = 0;
567 for (i = 0; i < (int)disp_kms->connectors_count; i++) {
568 struct connector *c = &disp_kms->connector[i];
569 connector_find_mode(disp, c);
570 if (c->mode == NULL)
571 continue;
572 /* setup side-by-side virtual display */
573 disp->width += c->mode->hdisplay;
574 if (disp->height < c->mode->vdisplay) {
575 disp->height = c->mode->vdisplay;
576 }
577 }
579 MSG("using %d connectors, %dx%d display, multiplanar: %d",
580 disp_kms->connectors_count, disp->width, disp->height, disp->multiplanar);
582 return disp;
584 fail:
585 // XXX cleanup
586 return NULL;
587 }