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 }
98 return bo;
99 }
101 static struct buffer *
102 alloc_buffer(struct display *disp, uint32_t fourcc, uint32_t w, uint32_t h)
103 {
104 struct buffer_kms *buf_kms;
105 struct buffer *buf;
106 uint32_t bo_handles[4] = {0}, offsets[4] = {0};
107 int ret;
109 buf_kms = calloc(1, sizeof(*buf_kms));
110 if (!buf_kms) {
111 ERROR("allocation failed");
112 return NULL;
113 }
114 buf = &buf_kms->base;
116 buf->fourcc = fourcc;
117 buf->width = w;
118 buf->height = h;
120 buf->nbo = 1;
122 if (!fourcc)
123 fourcc = FOURCC('A','R','2','4');
125 switch(fourcc) {
126 case FOURCC('A','R','2','4'):
127 buf->nbo = 1;
128 buf->bo[0] = alloc_bo(disp, 32, buf->width, buf->height,
129 &bo_handles[0], &buf->pitches[0]);
130 break;
131 case FOURCC('U','Y','V','Y'):
132 case FOURCC('Y','U','Y','V'):
133 buf->nbo = 1;
134 buf->bo[0] = alloc_bo(disp, 16, buf->width, buf->height,
135 &bo_handles[0], &buf->pitches[0]);
136 break;
137 case FOURCC('N','V','1','2'):
138 buf->nbo = 2;
139 buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
140 &bo_handles[0], &buf->pitches[0]);
141 buf->bo[1] = alloc_bo(disp, 16, buf->width/2, buf->height/2,
142 &bo_handles[1], &buf->pitches[1]);
143 break;
144 case FOURCC('I','4','2','0'):
145 buf->nbo = 3;
146 buf->bo[0] = alloc_bo(disp, 8, buf->width, buf->height,
147 &bo_handles[0], &buf->pitches[0]);
148 buf->bo[1] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
149 &bo_handles[1], &buf->pitches[1]);
150 buf->bo[2] = alloc_bo(disp, 8, buf->width/2, buf->height/2,
151 &bo_handles[2], &buf->pitches[2]);
152 break;
153 default:
154 ERROR("invalid format: 0x%08x", fourcc);
155 goto fail;
156 }
158 ret = drmModeAddFB2(disp->fd, buf->width, buf->height, fourcc,
159 bo_handles, buf->pitches, offsets, &buf_kms->fb_id, 0);
160 if (ret) {
161 ERROR("drmModeAddFB2 failed: %s (%d)", strerror(errno), ret);
162 goto fail;
163 }
165 return buf;
167 fail:
168 // XXX cleanup
169 return NULL;
170 }
172 static struct buffer **
173 alloc_buffers(struct display *disp, uint32_t n,
174 uint32_t fourcc, uint32_t w, uint32_t h)
175 {
176 struct buffer **bufs;
177 uint32_t i = 0;
179 bufs = calloc(n, sizeof(*bufs));
180 if (!bufs) {
181 ERROR("allocation failed");
182 goto fail;
183 }
185 for (i = 0; i < n; i++) {
186 bufs[i] = alloc_buffer(disp, fourcc, w, h);
187 if (!bufs[i]) {
188 ERROR("allocation failed");
189 goto fail;
190 }
191 }
193 return bufs;
195 fail:
196 // XXX cleanup
197 return NULL;
198 }
200 static struct buffer **
201 get_buffers(struct display *disp, uint32_t n)
202 {
203 return alloc_buffers(disp, n, 0, disp->width, disp->height);
204 }
206 static struct buffer **
207 get_vid_buffers(struct display *disp, uint32_t n,
208 uint32_t fourcc, uint32_t w, uint32_t h)
209 {
210 return alloc_buffers(disp, n, fourcc, w, h);
211 }
213 static void
214 page_flip_handler(int fd, unsigned int frame,
215 unsigned int sec, unsigned int usec, void *data)
216 {
217 struct display *disp = data;
218 struct display_kms *disp_kms = to_display_kms(disp);
220 disp_kms->completed_flips++;
222 MSG("Page flip: frame=%d, sec=%d, usec=%d, remaining=%d", frame, sec, usec,
223 disp_kms->scheduled_flips - disp_kms->completed_flips);
224 }
226 static int
227 post_buffer(struct display *disp, struct buffer *buf)
228 {
229 struct display_kms *disp_kms = to_display_kms(disp);
230 struct buffer_kms *buf_kms = to_buffer_kms(buf);
231 int ret, last_err = 0, x = 0;
232 uint32_t i;
234 for (i = 0; i < disp_kms->connectors_count; i++) {
235 struct connector *connector = &disp_kms->connector[i];
237 if (! connector->mode) {
238 continue;
239 }
241 if (! disp_kms->current) {
242 /* first buffer we flip to, setup the mode (since this can't
243 * be done earlier without a buffer to scanout)
244 */
245 MSG("Setting mode %s on connector %d, crtc %d",
246 connector->mode_str, connector->id, connector->crtc);
248 ret = drmModeSetCrtc(disp->fd, connector->crtc, buf_kms->fb_id,
249 x, 0, &connector->id, 1, connector->mode);
251 x += connector->mode->hdisplay;
252 } else {
253 ret = drmModePageFlip(disp->fd, connector->crtc, buf_kms->fb_id,
254 DRM_MODE_PAGE_FLIP_EVENT, disp);
255 disp_kms->scheduled_flips++;
256 }
258 if (ret) {
259 ERROR("Could not post buffer on crtc %d: %s (%d)",
260 connector->crtc, strerror(errno), ret);
261 last_err = ret;
262 /* well, keep trying the reset of the connectors.. */
263 }
264 }
266 /* if we flipped, wait for all flips to complete! */
267 while (disp_kms->scheduled_flips > disp_kms->completed_flips) {
268 drmEventContext evctx = {
269 .version = DRM_EVENT_CONTEXT_VERSION,
270 .page_flip_handler = page_flip_handler,
271 };
272 struct timeval timeout = {
273 .tv_sec = 3,
274 .tv_usec = 0,
275 };
276 fd_set fds;
278 FD_ZERO(&fds);
279 FD_SET(disp->fd, &fds);
281 ret = select(disp->fd + 1, &fds, NULL, NULL, &timeout);
282 if (ret <= 0) {
283 if (errno == EAGAIN) {
284 continue; /* keep going */
285 } else {
286 ERROR("Timeout waiting for flip complete: %s (%d)",
287 strerror(errno), ret);
288 last_err = ret;
289 break;
290 }
291 }
293 drmHandleEvent(disp->fd, &evctx);
294 }
296 disp_kms->current = buf;
298 return last_err;
299 }
301 static int
302 post_vid_buffer(struct display *disp, struct buffer *buf,
303 uint32_t x, uint32_t y, uint32_t w, uint32_t h)
304 {
305 struct display_kms *disp_kms = to_display_kms(disp);
306 struct buffer_kms *buf_kms = to_buffer_kms(buf);
307 int ret = 0;
308 uint32_t i, j;
310 /* ensure we have the overlay setup: */
311 for (i = 0; i < disp_kms->connectors_count; i++) {
312 struct connector *connector = &disp_kms->connector[i];
313 uint32_t used_planes = 0;
314 drmModeModeInfo *mode = connector->mode;
316 if (! mode) {
317 continue;
318 }
320 if (! disp_kms->ovr[i]) {
322 for (j = 0; j < disp_kms->plane_resources->count_planes; j++) {
323 drmModePlane *ovr = drmModeGetPlane(disp->fd,
324 disp_kms->plane_resources->planes[j]);
325 if ((ovr->possible_crtcs & (1 << connector->pipe)) &&
326 !(used_planes & (1 << j))) {
327 disp_kms->ovr[i] = ovr;
328 used_planes |= (1 << j);
329 break;
330 }
331 }
332 }
334 if (! disp_kms->ovr[i]) {
335 MSG("Could not find plane for crtc %d", connector->crtc);
336 ret = -1;
337 /* carry on and see if we can find at least one usable plane */
338 continue;
339 }
341 ret = drmModeSetPlane(disp->fd, disp_kms->ovr[i]->plane_id,
342 connector->crtc, buf_kms->fb_id, 0,
343 /* make video fullscreen: */
344 0, 0, mode->hdisplay, mode->vdisplay,
345 /* source/cropping coordinates are given in Q16 */
346 x << 16, y << 16, w << 16, h << 16);
347 if (ret) {
348 ERROR("failed to enable plane %d: %s",
349 disp_kms->ovr[i]->plane_id, strerror(errno));
350 }
351 }
353 return ret;
354 }
356 static void
357 connector_find_mode(struct display *disp, struct connector *c)
358 {
359 struct display_kms *disp_kms = to_display_kms(disp);
360 drmModeConnector *connector;
361 int i, j;
363 /* First, find the connector & mode */
364 c->mode = NULL;
365 for (i = 0; i < disp_kms->resources->count_connectors; i++) {
366 connector = drmModeGetConnector(disp->fd,
367 disp_kms->resources->connectors[i]);
369 if (!connector) {
370 ERROR("could not get connector %i: %s",
371 disp_kms->resources->connectors[i], strerror(errno));
372 drmModeFreeConnector(connector);
373 continue;
374 }
376 if (!connector->count_modes) {
377 drmModeFreeConnector(connector);
378 continue;
379 }
381 if (connector->connector_id != c->id) {
382 drmModeFreeConnector(connector);
383 continue;
384 }
386 for (j = 0; j < connector->count_modes; j++) {
387 c->mode = &connector->modes[j];
388 if (!strcmp(c->mode->name, c->mode_str))
389 break;
390 }
392 /* Found it, break out */
393 if (c->mode)
394 break;
396 drmModeFreeConnector(connector);
397 }
399 if (!c->mode) {
400 ERROR("failed to find mode \"%s\"", c->mode_str);
401 return;
402 }
404 /* Now get the encoder */
405 for (i = 0; i < disp_kms->resources->count_encoders; i++) {
406 c->encoder = drmModeGetEncoder(disp->fd,
407 disp_kms->resources->encoders[i]);
409 if (!c->encoder) {
410 ERROR("could not get encoder %i: %s",
411 disp_kms->resources->encoders[i], strerror(errno));
412 drmModeFreeEncoder(c->encoder);
413 continue;
414 }
416 if (c->encoder->encoder_id == connector->encoder_id)
417 break;
419 drmModeFreeEncoder(c->encoder);
420 }
422 if (c->crtc == -1)
423 c->crtc = c->encoder->crtc_id;
425 /* and figure out which crtc index it is: */
426 for (i = 0; i < disp_kms->resources->count_crtcs; i++) {
427 if (c->crtc == (int)disp_kms->resources->crtcs[i]) {
428 c->pipe = i;
429 break;
430 }
431 }
432 }
434 void
435 disp_kms_usage(void)
436 {
437 MSG("KMS Display Options:");
438 MSG("\t-t <tiled-mode>\t8, 16, 32, or auto");
439 MSG("\t-s <connector_id>:<mode>\tset a mode");
440 MSG("\t-s <connector_id>@<crtc_id>:<mode>\tset a mode");
441 }
443 struct display *
444 disp_kms_open(int argc, char **argv)
445 {
446 struct display_kms *disp_kms = NULL;
447 struct display *disp;
448 int i;
450 disp_kms = calloc(1, sizeof(*disp_kms));
451 if (!disp_kms) {
452 ERROR("allocation failed");
453 goto fail;
454 }
455 disp = &disp_kms->base;
457 disp->fd = drmOpen("omapdrm", NULL);
458 if (disp->fd < 0) {
459 ERROR("could not open drm device: %s (%d)", strerror(errno), errno);
460 goto fail;
461 }
463 disp->dev = omap_device_new(disp->fd);
464 if (!disp->dev) {
465 ERROR("couldn't create device");
466 goto fail;
467 }
469 disp->get_buffers = get_buffers;
470 disp->get_vid_buffers = get_vid_buffers;
471 disp->post_buffer = post_buffer;
472 disp->post_vid_buffer = post_vid_buffer;
474 disp_kms->resources = drmModeGetResources(disp->fd);
475 if (!disp_kms->resources) {
476 ERROR("drmModeGetResources failed: %s", strerror(errno));
477 goto fail;
478 }
480 disp_kms->plane_resources = drmModeGetPlaneResources(disp->fd);
481 if (!disp_kms->plane_resources) {
482 ERROR("drmModeGetPlaneResources failed: %s", strerror(errno));
483 goto fail;
484 }
486 /* note: set args to NULL after we've parsed them so other modules know
487 * that it is already parsed (since the arg parsing is decentralized)
488 */
489 for (i = 1; i < argc; i++) {
490 if (!argv[i]) {
491 continue;
492 }
493 if (!strcmp("-t", argv[i])) {
494 int n;
495 argv[i++] = NULL;
496 if (!strcmp(argv[i], "auto")) {
497 n = 0;
498 } else if (sscanf(argv[i], "%d", &n) != 1) {
499 ERROR("invalid arg: %s", argv[i]);
500 goto fail;
501 }
503 disp_kms->bo_flags &= ~OMAP_BO_TILED;
505 if (n == 8) {
506 disp_kms->bo_flags |= OMAP_BO_TILED_8;
507 } else if (n == 16) {
508 disp_kms->bo_flags |= OMAP_BO_TILED_16;
509 } else if (n == 32) {
510 disp_kms->bo_flags |= OMAP_BO_TILED_32;
511 } else if (n == 0) {
512 disp_kms->bo_flags |= OMAP_BO_TILED;
513 } else {
514 ERROR("invalid arg: %s", argv[i]);
515 goto fail;
516 }
517 } else if (!strcmp("-s", argv[i])) {
518 struct connector *connector =
519 &disp_kms->connector[disp_kms->connectors_count++];
520 connector->crtc = -1;
521 argv[i++] = NULL;
522 if (sscanf(argv[i], "%d:%64s",
523 &connector->id,
524 connector->mode_str) != 2 &&
525 sscanf(argv[i], "%d@%d:%64s",
526 &connector->id,
527 &connector->crtc,
528 connector->mode_str) != 3) {
529 // TODO: we could support connector specified as a name too, I suppose
530 ERROR("invalid arg: %s", argv[i]);
531 goto fail;
532 }
533 } else {
534 /* ignore */
535 continue;
536 }
537 argv[i] = NULL;
538 }
540 disp->width = 0;
541 disp->height = 0;
542 for (i = 0; i < (int)disp_kms->connectors_count; i++) {
543 struct connector *c = &disp_kms->connector[i];
544 connector_find_mode(disp, c);
545 if (c->mode == NULL)
546 continue;
547 /* setup side-by-side virtual display */
548 disp->width += c->mode->hdisplay;
549 if (disp->height < c->mode->vdisplay) {
550 disp->height = c->mode->vdisplay;
551 }
552 }
554 MSG("using %d connectors, %dx%d display",
555 disp_kms->connectors_count, disp->width, disp->height);
557 return disp;
559 fail:
560 // XXX cleanup
561 return NULL;
562 }