808945ca0c0b2501e22a3b10d0d3f2005be17a62
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;
166 }
168 static struct buffer **
169 alloc_buffers(struct display *disp, uint32_t n,
170 uint32_t fourcc, uint32_t w, uint32_t h)
171 {
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;
194 }
196 static struct buffer **
197 get_buffers(struct display *disp, uint32_t n)
198 {
199 return alloc_buffers(disp, n, 0, disp->width, disp->height);
200 }
202 static struct buffer **
203 get_vid_buffers(struct display *disp, uint32_t n,
204 uint32_t fourcc, uint32_t w, uint32_t h)
205 {
206 return alloc_buffers(disp, n, fourcc, w, h);
207 }
209 static void
210 page_flip_handler(int fd, unsigned int frame,
211 unsigned int sec, unsigned int usec, void *data)
212 {
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);
220 }
222 static int
223 post_buffer(struct display *disp, struct buffer *buf)
224 {
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;
295 }
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)
300 {
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;
350 }
352 static void
353 connector_find_mode(struct display *disp, struct connector *c)
354 {
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 }
428 }
430 void
431 disp_kms_usage(void)
432 {
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");
437 }
439 struct display *
440 disp_kms_open(int argc, char **argv)
441 {
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;
558 }