1 /* ______ ___ ___
2 * /\ _ \ /\_ \ /\_ \
3 * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___
4 * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\
5 * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \
6 * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7 * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8 * /\____/
9 * \_/__/
10 *
11 * Video driver for the Linux framebuffer device.
12 *
13 * By Shawn Hargreaves.
14 *
15 * Proper mode setting support added by George Foot.
16 *
17 * Modified by Grzegorz Adam Hankiewicz.
18 *
19 * See readme.txt for copyright information.
20 */
21
22
23 #include "allegro.h"
24 #include "allegro/internal/aintern.h"
25 #include "allegro/platform/aintunix.h"
26
27
28 #if (defined ALLEGRO_LINUX_FBCON) && ((!defined ALLEGRO_WITH_MODULES) || (defined ALLEGRO_MODULE))
29
30 #if !defined(_POSIX_MAPPED_FILES) || !defined(ALLEGRO_HAVE_MMAP)
31 #error "Sorry, mapped files are required for Linux console Allegro to work!"
32 #endif
33
34 #include <stdio.h>
35 #include <string.h>
36 #include <sys/ioctl.h>
37 #include <linux/fb.h>
38 #include <sys/mman.h>
39
40 /* 2.4 kernel doesn't seem to have these, so assume that they don't
41 * "have sticky," heheh. */
42
43 #ifndef FB_VBLANK_HAVE_STICKY
44 #define FB_VBLANK_HAVE_STICKY 0
45 #endif
46
47 #ifndef FB_VBLANK_STICKY
48 #define FB_VBLANK_STICKY 0
49 #endif
50
51
52
53 #define PREFIX_I "al-fbcon INFO: "
54 #define PREFIX_W "al-fbcon WARNING: "
55 #define PREFIX_E "al-fbcon ERROR: "
56
57
58
59 static BITMAP *fb_init(int w, int h, int v_w, int v_h, int color_depth);
60 static int fb_open_device(void);
61 static void fb_exit(BITMAP *b);
62 static void fb_save(void);
63 static void fb_restore(void);
64 static int fb_scroll(int x, int y);
65 static void fb_vsync(void);
66 static void fb_set_palette(AL_CONST RGB *p, int from, int to, int vsync);
67 static void fb_save_cmap(void);
68 static void fb_restore_cmap(void);
69
70
71
72 GFX_DRIVER gfx_fbcon =
73 {
74 GFX_FBCON,
75 empty_string,
76 empty_string,
77 "fbcon",
78 fb_init,
79 fb_exit,
80 fb_scroll,
81 fb_vsync,
82 fb_set_palette,
83 NULL, NULL, NULL, /* no triple buffering */
84 NULL, NULL, NULL, NULL, /* no video bitmaps */
85 NULL, NULL, /* no system bitmaps */
86 NULL, NULL, NULL, NULL, /* no hardware cursor */
87 NULL, /* no drawing mode hook */
88 fb_save,
89 fb_restore,
90 NULL, // AL_METHOD(void, set_blender_mode, (int mode, int r, int g, int b, int a));
91 NULL, /* no fetch mode hook */
92 0, 0,
93 TRUE,
94 0, 0, 0, 0, FALSE
95 };
96
97
98 static char fb_desc[256] = EMPTY_STRING; /* description string */
99
100 static int fb_mode_read = FALSE; /* has orig_mode been read? */
101
102 static struct fb_fix_screeninfo fix_info; /* read-only video mode info */
103 static struct fb_var_screeninfo orig_mode; /* original video mode info */
104 static struct fb_var_screeninfo my_mode; /* my video mode info */
105
106 static void *fbaddr; /* frame buffer address */
107
108 static int fbfd; /* file descriptor */
109
110 static int fb_approx; /* emulate small resolution */
111
112 #ifdef FBIOGET_VBLANK
113 static int vblank_flags; /* supports retrace detection? */
114 #endif
115
116 static int in_fb_restore;
117
118
119
120 static int update_timings(struct fb_var_screeninfo *mode);
121
122
123
set_ramp_cmap(AL_CONST struct fb_fix_screeninfo * fix,AL_CONST struct fb_var_screeninfo * var)124 static void set_ramp_cmap(AL_CONST struct fb_fix_screeninfo *fix,
125 AL_CONST struct fb_var_screeninfo *var)
126 {
127 struct fb_cmap cmap;
128 static unsigned short r[256], g[256], b[256]; /* 1.5 KB, so in .bss */
129 int rlen, glen, blen, rdiv, gdiv, bdiv;
130 unsigned int c;
131
132 ASSERT(fix);
133 ASSERT(var);
134 ASSERT(fix->visual == FB_VISUAL_DIRECTCOLOR);
135
136 /* initialize what is common */
137 cmap.start = 0;
138 cmap.red = r;
139 cmap.green = g;
140 cmap.blue = b;
141 cmap.transp = NULL;
142
143 /* from the mode's color depth information */
144 rlen = 1<<var->red.length;
145 glen = 1<<var->green.length;
146 blen = 1<<var->blue.length;
147 cmap.len = MAX(rlen, MAX(glen, blen));
148 ASSERT(cmap.len <= 256);
149 if (cmap.len > 256)
150 cmap.len = 256; /* are there cards with more than 8 bits per color ? */
151
152 rdiv = rlen<=1 ? 1 : rlen-1;
153 gdiv = glen<=1 ? 1 : glen-1;
154 bdiv = blen<=1 ? 1 : blen-1;
155
156 /* the easy case (should not happen) */
157 if (cmap.len == 0)
158 return;
159
160 /* build the colormap */
161 for (c=0; c<cmap.len; ++c) {
162 cmap.red[c] = c*65535/rdiv;
163 cmap.green[c] = c*65535/gdiv;
164 cmap.blue[c] = c*65535/bdiv;
165 }
166
167 /* wait a little to set colors once the frame is drawn */
168 fb_vsync();
169
170 /* ioctl to the FB driver with our colormap */
171 if (ioctl(fbfd, FBIOPUTCMAP, &cmap)) {
172 /* well, we can continue with potentially screwed up colors, I guess */
173 ASSERT(0);
174 }
175 }
176
177
178
calculate_refresh_rate(AL_CONST struct fb_var_screeninfo * mode)179 static void calculate_refresh_rate(AL_CONST struct fb_var_screeninfo *mode)
180 {
181 int h, v, hz;
182
183 ASSERT(mode);
184
185 h = mode->left_margin+mode->xres+mode->left_margin+mode->hsync_len;
186 v = mode->upper_margin+mode->yres+mode->lower_margin+mode->vsync_len;
187 hz = (int)(0.5f+1.0e12f/((float)mode->pixclock*h*v));
188 _set_current_refresh_rate(hz);
189 }
190
191
192
193 /* __al_linux_get_fb_color_depth:
194 * Get the colour depth of the framebuffer
195 */
__al_linux_get_fb_color_depth(void)196 int __al_linux_get_fb_color_depth(void)
197 {
198 if ((!fb_mode_read) && (fb_open_device() != 0))
199 return 0;
200 return orig_mode.bits_per_pixel;
201 }
202
203
204
205 /* __al_linux_get_fb_resolution:
206 * Get the resolution of the framebuffer
207 */
__al_linux_get_fb_resolution(int * width,int * height)208 int __al_linux_get_fb_resolution(int *width, int *height)
209 {
210 if ((!fb_mode_read) && (fb_open_device() != 0))
211 return -1;
212
213 *width = orig_mode.xres;
214 *height = orig_mode.yres;
215 return 0;
216 }
217
218
219
220 /* fb_init:
221 * Sets a graphics mode.
222 */
fb_init(int w,int h,int v_w,int v_h,int color_depth)223 static BITMAP *fb_init(int w, int h, int v_w, int v_h, int color_depth)
224 {
225 AL_CONST char *p;
226 int stride, tries, original_color_depth = _color_depth;
227 BITMAP *b;
228 char tmp[16];
229
230 /* open framebuffer and store info in global variables */
231 if ((!fb_mode_read) && (fb_open_device() != 0))
232 return NULL;
233
234 /* look for a nice graphics mode in several passes */
235 fb_approx = FALSE;
236
237 ASSERT (w >= 0);
238 ASSERT (h >= 0);
239
240 /* preset a resolution if the user didn't ask for one */
241 if (((!w) && (!h)) || _safe_gfx_mode_change) {
242 w = orig_mode.xres;
243 h = orig_mode.yres;
244 TRACE(PREFIX_I "User didn't ask for a resolution, and we are trying "
245 "to set a safe mode, so I will try resolution %dx%d\n", w, h);
246 }
247
248 if (_safe_gfx_mode_change) tries = -1;
249 else tries = 0;
250
251 if (__al_linux_console_graphics() != 0) {
252 ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
253 close(fbfd);
254 return NULL;
255 }
256
257 for (; tries<3; tries++) {
258 TRACE(PREFIX_I "...try number %d...\n", tries);
259 my_mode = orig_mode;
260
261 switch (tries) {
262
263 case -1:
264 /* let's see if we can get the actual screen mode */
265 /* shouldn't we be keeping the previous color depth setting? */
266 switch (orig_mode.bits_per_pixel) {
267 case 8:
268 case 16:
269 case 24:
270 case 32:
271 color_depth = orig_mode.bits_per_pixel;
272 set_color_depth(color_depth);
273 }
274 break;
275 case 0:
276 /* try for largest possible virtual screen size */
277 my_mode.xres = w;
278 my_mode.yres = h;
279 my_mode.xres_virtual = MAX(w, v_w);
280 my_mode.yres_virtual = MAX(MAX(h, v_h), (int)(fix_info.smem_len / (my_mode.xres_virtual * BYTES_PER_PIXEL(color_depth))));
281 break;
282
283 case 1:
284 /* try setting the exact size that was requested */
285 my_mode.xres = w;
286 my_mode.yres = h;
287 my_mode.xres_virtual = MAX(w, v_w);
288 my_mode.yres_virtual = MAX(h, v_h);
289 break;
290
291 case 2:
292 /* see if we can fake a smaller mode (better than nothing) */
293 if (((int)my_mode.xres < w) || ((int)my_mode.yres < h) || (v_w > w) || (v_h > h))
294 continue;
295 fb_approx = TRUE;
296 break;
297 }
298
299 my_mode.bits_per_pixel = color_depth;
300 my_mode.grayscale = 0;
301 my_mode.activate = FB_ACTIVATE_NOW;
302 my_mode.xoffset = 0;
303 my_mode.yoffset = 0;
304
305 switch (color_depth) {
306
307 #ifdef ALLEGRO_COLOR16
308
309 case 15:
310 my_mode.red.offset = 10;
311 my_mode.red.length = 5;
312 my_mode.green.offset = 5;
313 my_mode.green.length = 5;
314 my_mode.blue.offset = 0;
315 my_mode.blue.length = 5;
316 break;
317
318 case 16:
319 my_mode.red.offset = 11;
320 my_mode.red.length = 5;
321 my_mode.green.offset = 5;
322 my_mode.green.length = 6;
323 my_mode.blue.offset = 0;
324 my_mode.blue.length = 5;
325 break;
326
327 #endif
328
329 #if (defined ALLEGRO_COLOR24) || (defined ALLEGRO_COLOR32)
330
331 case 24:
332 case 32:
333 my_mode.red.offset = 16;
334 my_mode.red.length = 8;
335 my_mode.green.offset = 8;
336 my_mode.green.length = 8;
337 my_mode.blue.offset = 0;
338 my_mode.blue.length = 8;
339 break;
340
341 #endif
342
343 case 8:
344 default:
345 my_mode.red.offset = 0;
346 my_mode.red.length = 0;
347 my_mode.green.offset = 0;
348 my_mode.green.length = 0;
349 my_mode.blue.offset = 0;
350 my_mode.blue.length = 0;
351 break;
352 }
353
354 my_mode.red.msb_right = 0;
355 my_mode.green.msb_right = 0;
356 my_mode.blue.msb_right = 0;
357
358 /* fill in the timings */
359 if (update_timings(&my_mode) != 0)
360 continue;
361
362 /* try to set the mode */
363 if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &my_mode) == 0) {
364 if (my_mode.bits_per_pixel == (unsigned)color_depth)
365 goto got_a_nice_mode;
366 }
367 }
368
369 /* oops! */
370 if (_safe_gfx_mode_change) {
371 set_color_depth(original_color_depth);
372 TRACE(PREFIX_I "Restoring color depth %d\n", original_color_depth);
373 }
374
375 ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
376 __al_linux_console_text();
377 close(fbfd);
378 ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Framebuffer resolution not available"));
379 TRACE(PREFIX_E "Resolution %dx%d not available...\n", w, h);
380 return NULL;
381
382 got_a_nice_mode:
383
384 /* get the fb_fix_screeninfo again, as it may have changed */
385 if (ioctl(fbfd, FBIOGET_FSCREENINFO, &fix_info) != 0) {
386 ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
387 __al_linux_console_text();
388 close(fbfd);
389 ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Framebuffer ioctl() failed"));
390 return NULL;
391 }
392
393 /* map the framebuffer */
394 fbaddr = mmap(NULL, fix_info.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
395 if (fbaddr == MAP_FAILED) {
396 ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
397 __al_linux_console_text();
398 close(fbfd);
399 ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can't map framebuffer"));
400 TRACE(PREFIX_E "Couldn't map framebuffer for %dx%d. Restored old "
401 "resolution.\n", w, h);
402 return NULL;
403 }
404
405 /* set up the screen bitmap */
406 gfx_fbcon.w = w;
407 gfx_fbcon.h = h;
408
409 gfx_fbcon.vid_mem = fix_info.smem_len;
410
411 stride = fix_info.line_length;
412 v_w = my_mode.xres_virtual;
413 v_h = my_mode.yres_virtual;
414 p = fbaddr;
415
416 if (fb_approx) {
417 v_w = w;
418 v_h = h;
419
420 p += (my_mode.xres-w)/2 * BYTES_PER_PIXEL(color_depth) +
421 (my_mode.yres-h)/2 * stride;
422 }
423
424 b = _make_bitmap(v_w, v_h, (unsigned long)p, &gfx_fbcon, color_depth, stride);
425 if (!b) {
426 ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
427 munmap(fbaddr, fix_info.smem_len);
428 __al_linux_console_text();
429 close(fbfd);
430 TRACE(PREFIX_E "Couldn't make bitmap `b', sorry.\n");
431 return NULL;
432 }
433
434 b->vtable->acquire = __al_linux_acquire_bitmap;
435 b->vtable->release = __al_linux_release_bitmap;
436
437 do_uconvert(fix_info.id, U_ASCII, fb_desc, U_CURRENT, sizeof(fb_desc));
438
439 if (fb_approx) {
440 ustrzcat(fb_desc, sizeof(fb_desc), uconvert_ascii(", ", tmp));
441 ustrzcat(fb_desc, sizeof(fb_desc), get_config_text("approx."));
442 }
443
444 gfx_fbcon.desc = fb_desc;
445
446 /* set up the truecolor pixel format */
447 switch (color_depth) {
448
449 #ifdef ALLEGRO_COLOR16
450
451 case 15:
452 _rgb_r_shift_15 = my_mode.red.offset;
453 _rgb_g_shift_15 = my_mode.green.offset;
454 _rgb_b_shift_15 = my_mode.blue.offset;
455 break;
456
457 case 16:
458 _rgb_r_shift_16 = my_mode.red.offset;
459 _rgb_g_shift_16 = my_mode.green.offset;
460 _rgb_b_shift_16 = my_mode.blue.offset;
461 break;
462
463 #endif
464
465 #ifdef ALLEGRO_COLOR24
466
467 case 24:
468 _rgb_r_shift_24 = my_mode.red.offset;
469 _rgb_g_shift_24 = my_mode.green.offset;
470 _rgb_b_shift_24 = my_mode.blue.offset;
471 break;
472
473 #endif
474
475 #ifdef ALLEGRO_COLOR32
476
477 case 32:
478 _rgb_r_shift_32 = my_mode.red.offset;
479 _rgb_g_shift_32 = my_mode.green.offset;
480 _rgb_b_shift_32 = my_mode.blue.offset;
481 break;
482
483 #endif
484 }
485
486 /* is retrace detection available? */
487 #ifdef FBIOGET_VBLANK
488 {
489 struct fb_vblank vblank;
490
491 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) == 0)
492 vblank_flags = vblank.flags;
493 else
494 vblank_flags = 0;
495 }
496
497 if (!(vblank_flags & (FB_VBLANK_HAVE_VBLANK | FB_VBLANK_HAVE_STICKY | FB_VBLANK_HAVE_VCOUNT)))
498 #endif
499 {
500 ustrzcat(fb_desc, sizeof(fb_desc), uconvert_ascii(", ", tmp));
501 ustrzcat(fb_desc, sizeof(fb_desc), get_config_text("no vsync"));
502 }
503
504 /* is scrolling available? */
505 if ((my_mode.xres_virtual > my_mode.xres) ||
506 (my_mode.yres_virtual > my_mode.yres)) {
507
508 fb_scroll(0, 0);
509
510 if ((fb_approx) ||
511 ((my_mode.xres_virtual > my_mode.xres) && (!fix_info.xpanstep)) ||
512 ((my_mode.yres_virtual > my_mode.yres) && (!fix_info.ypanstep)))
513 gfx_fbcon.scroll = NULL;
514 else
515 gfx_fbcon.scroll = fb_scroll;
516 }
517 else
518 gfx_fbcon.scroll = NULL;
519
520 if (fb_approx)
521 memset(fbaddr, 0, gfx_fbcon.vid_mem);
522
523 fb_save_cmap(); /* Maybe we should fill in our default palette too... */
524
525 /* for directcolor modes, set up a ramp colormap, so colors are mapped
526 onto themselves, so to speak */
527 if (fix_info.visual == FB_VISUAL_DIRECTCOLOR)
528 set_ramp_cmap(&fix_info, &my_mode);
529
530 calculate_refresh_rate(&my_mode);
531
532 /* Note: Locked in __al_linux_console_graphics(). */
533 __al_linux_display_switch_lock(FALSE, FALSE);
534
535 TRACE(PREFIX_I "Got a bitmap %dx%dx%d\n", b->w, b->h, bitmap_color_depth(b));
536 return b;
537 }
538
539
540
541 /* fb_open_device:
542 * Opens the framebuffer device, first checking config values or
543 * environment variables. Returns 0 on success.
544 */
fb_open_device(void)545 static int fb_open_device(void)
546 {
547 char fname[1024], tmp1[256], tmp2[256];
548 AL_CONST char *p;
549
550 /* find the device filename */
551 p = get_config_string(uconvert_ascii("graphics", tmp1), uconvert_ascii("framebuffer", tmp2), NULL);
552
553 if (p && ugetc(p))
554 do_uconvert(p, U_CURRENT, fname, U_ASCII, sizeof(fname));
555 else {
556 p = getenv("FRAMEBUFFER");
557
558 if ((p) && (p[0])) {
559 _al_sane_strncpy(fname, p, sizeof(fname));
560 }
561 else
562 _al_sane_strncpy(fname, "/dev/fb0", 1024);
563 }
564
565 /* open the framebuffer device */
566 if ((fbfd = open(fname, O_RDWR)) < 0) {
567 uszprintf(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can't open framebuffer %s"), uconvert_ascii(fname, tmp1));
568 TRACE(PREFIX_E "Couldn't open %s\n", fname);
569 return 1;
570 }
571
572 /* read video mode information */
573 if ((ioctl(fbfd, FBIOGET_FSCREENINFO, &fix_info) != 0) ||
574 (ioctl(fbfd, FBIOGET_VSCREENINFO, &orig_mode) != 0)) {
575 ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Framebuffer ioctl() failed"));
576 TRACE(PREFIX_E "ioctl() failed\n");
577 return 2;
578 }
579
580 TRACE(PREFIX_I "fb device %s opened successfully.\n", fname);
581 fb_mode_read = TRUE;
582 return 0;
583 }
584
585
586
587 /* fb_exit:
588 * Unsets the video mode.
589 */
fb_exit(BITMAP * b)590 static void fb_exit(BITMAP *b)
591 {
592 TRACE(PREFIX_I "Unsetting video mode.\n");
593
594 /* Note: Unlocked in __al_linux_console_text(). */
595 __al_linux_display_switch_lock(TRUE, TRUE);
596
597 ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
598 fb_restore_cmap();
599
600 munmap(fbaddr, fix_info.smem_len);
601 close(fbfd);
602
603 __al_linux_console_text();
604 fb_mode_read = FALSE;
605 }
606
607
608
609 /* fb_save:
610 * Saves the graphics state.
611 */
fb_save(void)612 static void fb_save(void)
613 {
614 ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_mode);
615 }
616
617
618
619 /* fb_restore:
620 * Restores the graphics state.
621 */
fb_restore(void)622 static void fb_restore(void)
623 {
624 ASSERT(!in_fb_restore);
625
626 in_fb_restore = TRUE;
627
628 ioctl(fbfd, FBIOPUT_VSCREENINFO, &my_mode);
629
630 if (fb_approx)
631 memset(fbaddr, 0, gfx_fbcon.vid_mem);
632
633 if (fix_info.visual == FB_VISUAL_DIRECTCOLOR)
634 set_ramp_cmap(&fix_info, &my_mode);
635
636 in_fb_restore = FALSE;
637 }
638
639
640
641 /* fb_scroll:
642 * Hardware scrolling routine.
643 */
fb_scroll(int x,int y)644 static int fb_scroll(int x, int y)
645 {
646 int ret;
647
648 my_mode.xoffset = x;
649 my_mode.yoffset = y;
650
651 ret = ioctl(fbfd, FBIOPAN_DISPLAY, &my_mode);
652
653 if (_wait_for_vsync) {
654 /* On a lot of graphics hardware the scroll registers don't
655 * take effect until the start of the next frame, so when you do a
656 * scroll you want to make sure you've waited at least until then.
657 * If you have reliable vsyncing, one call to vysnc is enough,
658 * but if you don't then the first call waits some time between 0
659 * and 1/60th of a second, and since it's not really synced to the
660 * retrace, this may or may not include an actual retrace. �However,
661 * by making a second call, we then wait for a full 1/60th of a second
662 * after the first call, and this is sure to include a retrace,
663 * assuming the refresh rate is at least 60Hz. �Sometimes overall
664 * we'll have waited past two retraces, or even more if the refresh
665 * rate is much higher than the fake vblank timer frequency, but
666 * it's still better than sometimes missing the retrace completely.
667 */
668 fb_vsync();
669
670 #ifdef FBIOGET_VBLANK
671 if (!vblank_flags)
672 #endif
673 fb_vsync();
674 }
675
676 return (ret) ? -1 : 0;
677 }
678
679
680
681 /* fb_vsync:
682 * Waits for a retrace.
683 */
fb_vsync(void)684 static void fb_vsync(void)
685 {
686 unsigned int prev;
687
688 #ifdef FBIOGET_VBLANK
689
690 struct fb_vblank vblank;
691
692 if (vblank_flags & FB_VBLANK_HAVE_STICKY) {
693 /* it's really good if sticky bits are available */
694 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
695 return;
696
697 do {
698 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
699 break;
700 } while (!(vblank.flags & FB_VBLANK_STICKY));
701 }
702 else if (vblank_flags & FB_VBLANK_HAVE_VCOUNT) {
703 /* we can read the exact scanline position, which avoids skipping */
704 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
705 return;
706
707 do {
708 prev = vblank.vcount;
709 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
710 break;
711 } while (vblank.vcount >= prev);
712 }
713 else if (vblank_flags & FB_VBLANK_HAVE_VBLANK) {
714 /* boring, normal style poll operation */
715 do {
716 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
717 break;
718 } while (vblank.flags & FB_VBLANK_VBLANKING);
719
720 do {
721 if (ioctl(fbfd, FBIOGET_VBLANK, &vblank) != 0)
722 break;
723 } while (!(vblank.flags & FB_VBLANK_VBLANKING));
724 }
725 else
726
727 #endif
728
729 /* bodged implementation for when the framebuffer doesn't support it */
730 /* We must not to run this loop while returning from a VT switch as timer
731 * "interrupts" will not be running at that time so retrace_count will
732 * remain constant.
733 */
734 if (_timer_installed && !in_fb_restore) {
735 prev = retrace_count;
736
737 do {
738 } while (retrace_count == (int)prev);
739 }
740 }
741
742
743
744 /* fb_set_palette:
745 * Sets the palette.
746 */
fb_set_palette(AL_CONST RGB * p,int from,int to,int vsync)747 static void fb_set_palette(AL_CONST RGB *p, int from, int to, int vsync)
748 {
749 unsigned short r[256], g[256], b[256];
750 struct fb_cmap cmap;
751 int i;
752 ASSERT(p);
753
754 cmap.start = from;
755 cmap.len = to-from+1;
756 cmap.red = r;
757 cmap.green = g;
758 cmap.blue = b;
759 cmap.transp = NULL;
760
761 for (i=0; i < (int)cmap.len; i++) {
762 r[i] = p[from+i].r << 10;
763 g[i] = p[from+i].g << 10;
764 b[i] = p[from+i].b << 10;
765 }
766
767 fb_vsync();
768
769 ioctl(fbfd, FBIOPUTCMAP, &cmap);
770 }
771
772
773
774 static unsigned short *orig_cmap_data; /* original palette data */
775
776 /* fb_do_cmap:
777 * Helper for fb_{save|restore}_cmap.
778 */
fb_do_cmap(int ioctlno)779 static void fb_do_cmap (int ioctlno)
780 {
781 struct fb_cmap cmap;
782 cmap.start = 0;
783 cmap.len = 256;
784 cmap.red = orig_cmap_data;
785 cmap.green = orig_cmap_data+256;
786 cmap.blue = orig_cmap_data+512;
787 cmap.transp = NULL;
788 ioctl(fbfd, ioctlno, &cmap);
789 }
790
791
792
793 /* fb_{save|restore}_cmap:
794 * Routines to save and restore the whole palette.
795 */
fb_save_cmap(void)796 static void fb_save_cmap (void)
797 {
798 if (orig_cmap_data) _AL_FREE (orig_cmap_data); /* can't happen */
799 orig_cmap_data = _AL_MALLOC_ATOMIC (sizeof *orig_cmap_data* 768);
800 if (orig_cmap_data)
801 fb_do_cmap (FBIOGETCMAP);
802 }
803
804
805
fb_restore_cmap(void)806 static void fb_restore_cmap (void)
807 {
808 if (orig_cmap_data) {
809 fb_do_cmap (FBIOPUTCMAP);
810 _AL_FREE (orig_cmap_data);
811 orig_cmap_data = NULL;
812 }
813 }
814
815
816
817 static struct timings {
818 char config_item[1024];
819 int pixclock;
820 int left_margin;
821 int right_margin;
822 int upper_margin;
823 int lower_margin;
824 int hsync_len;
825 int vsync_len;
826 int vmode;
827 int sync;
828 int xres;
829 int yres;
830 } _fb_current_timings;
831
832 static struct timings temp_timings;
833
834
835 static int read_config_file (int w, int h);
836 static int read_fbmodes_file (int w, int h);
837 static int tweak_timings (int w, int h);
838
839
840 /* _fb_get_timings:
841 * Returns a pointer to a struct as above containing timings for the given
842 * resolution, or NULL on error.
843 */
_fb_get_timings(int w,int h)844 static struct timings *_fb_get_timings (int w, int h)
845 {
846 /* First try the config file */
847 if (read_config_file (w, h)) return &temp_timings;
848
849 /* Failing that, try fb.modes */
850 if (read_fbmodes_file (w, h)) return &temp_timings;
851
852 /* Still no luck, so tweak the current mode instead */
853 if (tweak_timings (w, h)) return &temp_timings;
854 return NULL;
855 }
856
857
858 /* read_config_file:
859 * Assigns timing settings from the config file or returns 0.
860 */
read_config_file(int w,int h)861 static int read_config_file (int w, int h)
862 {
863 char tmp[128];
864 char **argv;
865 int argc;
866
867 /* Let the setup program know what config string we read for this mode */
868 uszprintf(temp_timings.config_item, sizeof(temp_timings.config_item), uconvert_ascii("fb_mode_%dx%d", tmp), w, h);
869
870 /* First try the config file */
871 argv = get_config_argv (NULL, temp_timings.config_item, &argc);
872 if (argv) {
873 #define get_info(info) if (*argv) temp_timings.info = ustrtol (*argv++, NULL, 10)
874 get_info(pixclock);
875 get_info(left_margin);
876 get_info(right_margin);
877 get_info(upper_margin);
878 get_info(lower_margin);
879 get_info(hsync_len);
880 get_info(vsync_len);
881
882 if (*argv) {
883 if (!ustrcmp (*argv, uconvert_ascii("none", tmp)))
884 temp_timings.vmode = FB_VMODE_NONINTERLACED;
885 else if (!ustrcmp (*argv, uconvert_ascii("interlaced", tmp)))
886 temp_timings.vmode = FB_VMODE_INTERLACED;
887 else if (!ustrcmp (*argv, uconvert_ascii("doublescan", tmp)))
888 temp_timings.vmode = FB_VMODE_DOUBLE;
889 argv++;
890 } else
891 temp_timings.vmode = FB_VMODE_NONINTERLACED;
892
893 get_info(sync);
894 #undef get_info
895
896 temp_timings.xres = w;
897 temp_timings.yres = h;
898
899 return 1;
900 }
901 return 0;
902 }
903
904
905 /* helper to read the relevant parts of a line from fb.modes */
get_line(FILE * file)906 static char *get_line (FILE *file)
907 {
908 static char buffer[1024];
909 char *ch;
910 ASSERT (file);
911
912 if (!fgets (buffer, sizeof (buffer), file))
913 return 0;
914
915 /* if there's no eol, get one before continuing */
916 if (!strchr (buffer, '\n') && strlen (buffer) == 1 + sizeof (buffer)) {
917 char waste[128], *ret;
918
919 do {
920 ret = fgets (waste, sizeof (waste), file);
921 } while (ret && !strchr (waste, '\n'));
922 /* this doesn't actually exit because we still have buffer */
923 }
924
925 if ((ch = strpbrk (buffer, "#\n")))
926 *ch = 0;
927
928 ch = buffer;
929 while (uisspace(*ch)) ch++;
930 return ch;
931 }
932
933 /* read_fbmodes_file:
934 * Assigns timing settings from the fbmodes file or returns 0.
935 */
read_fbmodes_file(int w,int h)936 static int read_fbmodes_file (int w, int h)
937 {
938 char *mode_id = NULL;
939 char *geometry = NULL;
940 char *timings = NULL;
941 int sync = 0, vmode = 0;
942 char *s, *t;
943 FILE *fbmodes;
944 int ret = 0;
945
946 fbmodes = fopen ("/etc/fb.modes", "r");
947 if (!fbmodes) return 0;
948
949 do {
950 s = get_line (fbmodes);
951 if (!s) break;
952 t = strchr (s, ' ');
953 if (t) {
954 *t++ = '\0';
955 while (uisspace(*t)) t++;
956 } else {
957 t = strchr (s, '\0');
958 }
959
960 if (!strcmp (s, "mode")) {
961 _AL_FREE (mode_id);
962 _AL_FREE (geometry);
963 _AL_FREE (timings);
964 mode_id = strdup (t);
965 geometry = timings = NULL;
966 sync = 0;
967 vmode = FB_VMODE_NONINTERLACED;
968 } else if (!strcmp (s, "endmode")) {
969 if (geometry && timings) {
970 int mw, mh;
971 sscanf (geometry, "%d %d", &mw, &mh);
972 if ((mw == w) && (mh == h)) {
973 sscanf (timings, "%d %d %d %d %d %d %d",
974 &temp_timings.pixclock,
975 &temp_timings.left_margin,
976 &temp_timings.right_margin,
977 &temp_timings.upper_margin,
978 &temp_timings.lower_margin,
979 &temp_timings.hsync_len,
980 &temp_timings.vsync_len
981 );
982 temp_timings.sync = sync;
983 temp_timings.vmode = vmode;
984 temp_timings.xres = w;
985 temp_timings.yres = h;
986 ret = 1;
987 s = NULL;
988 }
989 }
990 _AL_FREE (mode_id);
991 _AL_FREE (geometry);
992 _AL_FREE (timings);
993 mode_id = geometry = timings = NULL;
994 } else if (!strcmp (s, "geometry")) {
995 _AL_FREE (geometry);
996 geometry = strdup (t);
997 } else if (!strcmp (s, "timings")) {
998 _AL_FREE (timings);
999 timings = strdup (t);
1000 } else if (!strcmp (s, "hsync")) {
1001 #define set_bit(var,bit,on) var = ((var) &~ (bit)) | ((on) ? bit : 0)
1002 set_bit (sync, FB_SYNC_HOR_HIGH_ACT, t[0] == 'h');
1003 } else if (!strcmp (s, "vsync")) {
1004 set_bit (sync, FB_SYNC_VERT_HIGH_ACT, t[0] == 'h');
1005 } else if (!strcmp (s, "csync")) {
1006 set_bit (sync, FB_SYNC_COMP_HIGH_ACT, t[0] == 'h');
1007 } else if (!strcmp (s, "gsync")) {
1008 set_bit (sync, FB_SYNC_ON_GREEN, t[0] == 'h');
1009 } else if (!strcmp (s, "extsync")) {
1010 set_bit (sync, FB_SYNC_EXT, t[0] == 't');
1011 } else if (!strcmp (s, "bcast")) {
1012 set_bit (sync, FB_SYNC_BROADCAST, t[0] == 't');
1013 } else if (!strcmp (s, "laced")) {
1014 if (t[0] == 't') vmode = FB_VMODE_INTERLACED;
1015 } else if (!strcmp (s, "double")) {
1016 if (t[0] == 't') vmode = FB_VMODE_DOUBLE;
1017 }
1018 } while (s);
1019
1020 _AL_FREE (mode_id);
1021 _AL_FREE (geometry);
1022 _AL_FREE (timings);
1023
1024 fclose (fbmodes);
1025 return ret;
1026 }
1027
1028
1029 /* tweak_timings:
1030 * Tweak the timings to match the mode we want to set. Only works if
1031 * the parent is a higher resolution.
1032 */
tweak_timings(int w,int h)1033 static int tweak_timings (int w, int h)
1034 {
1035 if ((w <= temp_timings.xres) && (h <= temp_timings.yres)) {
1036 int diff = temp_timings.xres - w;
1037 temp_timings.left_margin += diff/2;
1038 temp_timings.right_margin += diff/2 + diff%2;
1039 temp_timings.xres = w;
1040
1041 diff = temp_timings.yres - h;
1042 temp_timings.upper_margin += diff/2;
1043 temp_timings.lower_margin += diff/2 + diff%2;
1044 temp_timings.yres = h;
1045
1046 return 1;
1047 }
1048 return 0;
1049 }
1050
1051
set_default_timings(void)1052 static void set_default_timings (void)
1053 {
1054 char tmp[128];
1055
1056 #define cp(x) temp_timings.x = orig_mode.x
1057 cp(pixclock);
1058 cp(left_margin);
1059 cp(right_margin);
1060 cp(upper_margin);
1061 cp(lower_margin);
1062 cp(hsync_len);
1063 cp(vsync_len);
1064 cp(vmode);
1065 cp(sync);
1066 cp(xres);
1067 cp(yres);
1068 #undef cp
1069 uszprintf(temp_timings.config_item, sizeof(temp_timings.config_item), uconvert_ascii("fb_mode_%dx%d", tmp),
1070 orig_mode.xres, orig_mode.yres);
1071 }
1072
1073
1074 /* update_timings:
1075 * Updates the timing section of the mode info. Maybe we can make
1076 * this algorithmic, as a backup, at some point. For now it searches
1077 * the config file or /etc/fb.modes for the data.
1078 *
1079 * We could make the init routine give up if the data isn't there, or
1080 * use an algorithmic guesser.
1081 *
1082 * If we go right ahead with this system, I think the setup program
1083 * should offer quite a few options -- dotclock probing might be useful,
1084 * along with the functionality of xvidtune.
1085 */
update_timings(struct fb_var_screeninfo * mode)1086 static int update_timings(struct fb_var_screeninfo *mode)
1087 {
1088 struct timings *t;
1089 ASSERT(mode);
1090
1091 set_default_timings();
1092 t = _fb_get_timings (mode->xres, mode->yres);
1093 if (!t) return -1;
1094
1095 /* for debugging, maybe for the setup program too */
1096 memcpy (&_fb_current_timings, t, sizeof(struct timings));
1097
1098 /* update the mode struct */
1099 mode->pixclock = t->pixclock;
1100 mode->left_margin = t->left_margin;
1101 mode->right_margin = t->right_margin;
1102 mode->upper_margin = t->upper_margin;
1103 mode->lower_margin = t->lower_margin;
1104 mode->hsync_len = t->hsync_len;
1105 mode->vsync_len = t->vsync_len;
1106 mode->vmode = t->vmode;
1107 mode->sync = t->sync;
1108
1109 return 0;
1110 }
1111
1112
1113 /* I'm not sure whether these work or not -- my Matrox seems capable
1114 * of setting whatever you ask it to. */
1115
1116 #if 0
1117
1118 static int _fb_get_pixclock(void)
1119 {
1120 struct fb_var_screeninfo mode;
1121 if (ioctl (fbfd, FBIOGET_VSCREENINFO, &mode)) return -1;
1122 return mode.pixclock;
1123 }
1124
1125 static void _fb_set_pixclock(int new_val)
1126 {
1127 struct fb_var_screeninfo mode;
1128 if (ioctl (fbfd, FBIOGET_VSCREENINFO, &mode)) return;
1129 mode.pixclock = new_val;
1130 if (ioctl (fbfd, FBIOPUT_VSCREENINFO, &mode)) return;
1131 }
1132
1133 #endif
1134
1135
1136
1137 #ifdef ALLEGRO_MODULE
1138
1139 /* _module_init:
1140 * Called when loaded as a dynamically linked module.
1141 */
_module_init(int system_driver)1142 void _module_init(int system_driver)
1143 {
1144 if (system_driver == SYSTEM_LINUX) {
1145 _unix_register_gfx_driver(GFX_FBCON, &gfx_fbcon, TRUE, TRUE);
1146 /* Register resolution and colour depth getters.
1147 * This is a bit ugly because these are actually part of the
1148 * system driver rather than the graphics driver...
1149 */
1150 system_linux.desktop_color_depth = __al_linux_get_fb_color_depth;
1151 system_linux.get_desktop_resolution = __al_linux_get_fb_resolution;
1152 }
1153 }
1154
1155 #endif /* ifdef ALLEGRO_MODULE */
1156
1157
1158
1159 #endif /* if (defined ALLEGRO_LINUX_FBCON) ... */
1160