blob: f0bef27ba8a71d8a015d0685f50390419716ae43 [file] [log] [blame]
/*
* Frame buffer control
*
* (C) Copyright 2001-2003 Geert Uytterhoeven
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <asm/page.h>
#include "types.h"
#include "fb.h"
#include "util.h"
#include "colormap.h"
static int fb_fd = -1;
struct fb_fix_screeninfo fb_fix;
struct fb_var_screeninfo fb_var;
struct fb_cmap fb_cmap;
static unsigned long fb_start;
static u32 fb_len, fb_offset;
u8 *fb;
/*
* Saved frame buffer device state
*/
static struct fb_var_screeninfo saved_var;
static struct fb_fix_screeninfo saved_fix;
static struct fb_cmap saved_cmap;
static u16 *saved_red, *saved_green, *saved_blue, *saved_transp;
static u8 *saved_fb;
static void fix_validate(void);
static void var_validate(void);
static void var_validate_change(const struct fb_var_screeninfo *old,
int error);
static void cmap_validate(void);
static void cmap_validate_change(const struct fb_cmap *old, int error);
static void fb_dump_cmap(void);
/*
* Open the frame buffer device
*/
void fb_open(void)
{
Debug("fb_open()\n");
if ((fb_fd = open(Opt_Fbdev, O_RDWR)) == -1) {
Fatal("open %s: %s\n", Opt_Fbdev, strerror(errno));
}
}
/*
* Close the frame buffer device
*/
void fb_close(void)
{
Debug("fb_close()\n");
if (fb_fd != -1) {
close(fb_fd);
fb_fd = -1;
}
}
/*
* Get the fixed information about a frame buffer
*/
int fb_get_fix(void)
{
Debug("fb_get_fix()\n");
if (ioctl(fb_fd, FBIOGET_FSCREENINFO, &fb_fix) == -1) {
Fatal("ioctl FBIOGET_FSCREENINFO: %s\n", strerror(errno));
}
fix_validate();
return 1;
}
/*
* Get the variable information about a frame buffer
*/
int fb_get_var(void)
{
Debug("fb_get_var()\n");
if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &fb_var) == -1) {
Fatal("ioctl FBIOGET_VSCREENINFO: %s\n", strerror(errno));
}
var_validate();
return 1;
}
/*
* Set the variable information about a frame buffer
*/
int fb_set_var(void)
{
struct fb_var_screeninfo var = fb_var;
int error;
Debug("fb_set_var()\n");
error = ioctl(fb_fd, FBIOPUT_VSCREENINFO, &fb_var);
var_validate_change(&var, error);
if (error == -1) {
Fatal("ioctl FBIOPUT_VSCREENINFO: %s\n", strerror(errno));
}
return 1;
}
/*
* Get the colormap
*/
int fb_get_cmap(void)
{
Debug("fb_get_cmap()\n");
if (ioctl(fb_fd, FBIOGETCMAP, &fb_cmap) == -1) {
Fatal("ioctl FBIOGETCMAP: %s\n", strerror(errno));
}
cmap_validate();
if (Opt_Debug)
fb_dump_cmap();
return 1;
}
/*
* Set the colormap
*/
int fb_set_cmap(void)
{
struct fb_cmap cmap = fb_cmap;
int error;
Debug("fb_set_cmap()\n");
if (Opt_Debug)
fb_dump_cmap();
error = ioctl(fb_fd, FBIOPUTCMAP, &fb_cmap);
cmap_validate_change(&cmap, error);
if (error == -1) {
Fatal("ioctl FBIOPUTCMAP: %s\n", strerror(errno));
}
return 1;
}
/*
* Map the frame buffer
*/
void fb_map(void)
{
caddr_t addr;
Debug("fb_map()\n");
fb_start = (unsigned long)fb_fix.smem_start & PAGE_MASK;
fb_offset = (unsigned long)fb_fix.smem_start & ~PAGE_MASK;
fb_len = (fb_offset+fb_fix.smem_len+~PAGE_MASK) & PAGE_MASK;
Debug("fb_start = %lx, fb_offset = %x, fb_len = %x\n", fb_start, fb_offset,
fb_len);
addr = mmap(NULL, fb_len, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (addr == (caddr_t)-1)
Fatal("mmap smem: %s\n", strerror(errno));
fb = addr+fb_offset;
}
/*
* Unmap the frame buffer
*/
void fb_unmap(void)
{
Debug("fb_unmap()\n");
if (munmap((caddr_t)((unsigned long)fb & PAGE_MASK), fb_len) == -1)
Fatal("munmap smem: %s\n", strerror(errno));
}
/*
* Save the frame buffer contents
*/
void fb_save(void)
{
Debug("fb_save()\n");
if (!(saved_fb = malloc(fb_fix.smem_len)))
Fatal("malloc %d: %s\n", fb_fix.smem_len, strerror(errno));
memcpy(saved_fb, fb, fb_fix.smem_len);
}
/*
* Restore the frame buffer contents
*/
void fb_restore(void)
{
Debug("fb_restore()\n");
memcpy(fb, saved_fb, fb_fix.smem_len);
free(saved_fb);
}
/*
* Clear the frame buffer
*
* We can't use memset(), because on PPC it uses dcbz, which is not
* allowed on non-cacheable memory :-(
*/
void fb_clear(void)
{
int size = fb_fix.smem_len/sizeof(u32);
u32 *p = (u32 *)fb;
Debug("fb_clear()\n");
while (size--)
*p++ = 0;
}
/*
* Validate the fixed information about a frame buffer
*/
static void fix_validate(void)
{
/* FIXME: check for impossible values */
}
/*
* Validate the variable information about a frame buffer
*/
static void var_validate(void)
{
/* FIXME: check for impossible values */
}
/*
* Validate a change of the variable information about a frame buffer
*/
#define CHECK_CHANGE(x) \
do { \
if (fb_var.x != old->x) { \
if (error == -1) \
Error(#x "changed from %u to %u\n", old->x, fb_var.x); \
else \
Warning(#x "changed from %u to %u\n", old->x, fb_var.x);\
} \
} while (0)
#define CHECK_ROUNDING(x) \
do { \
if (fb_var.x < old->x) \
Error(#x " was rounded down\n"); \
} while (0)
#define CHECK_CHANGE_AND_ROUNDING(x) \
do { \
CHECK_CHANGE(x); \
if (error != -1) \
CHECK_ROUNDING(x); \
} while (0)
static void var_validate_change(const struct fb_var_screeninfo *old, int error)
{
CHECK_CHANGE_AND_ROUNDING(xres);
CHECK_CHANGE_AND_ROUNDING(yres);
CHECK_CHANGE_AND_ROUNDING(xres_virtual);
CHECK_CHANGE_AND_ROUNDING(yres_virtual);
CHECK_CHANGE_AND_ROUNDING(xoffset);
CHECK_CHANGE_AND_ROUNDING(yoffset);
CHECK_CHANGE_AND_ROUNDING(bits_per_pixel);
CHECK_CHANGE(grayscale);
CHECK_CHANGE_AND_ROUNDING(red.offset);
CHECK_CHANGE_AND_ROUNDING(red.length);
CHECK_CHANGE(red.msb_right);
CHECK_CHANGE_AND_ROUNDING(green.offset);
CHECK_CHANGE_AND_ROUNDING(green.length);
CHECK_CHANGE_AND_ROUNDING(green.length);
CHECK_CHANGE(green.msb_right);
CHECK_CHANGE_AND_ROUNDING(blue.offset);
CHECK_CHANGE_AND_ROUNDING(blue.length);
CHECK_CHANGE_AND_ROUNDING(blue.length);
CHECK_CHANGE(blue.msb_right);
CHECK_CHANGE_AND_ROUNDING(transp.offset);
CHECK_CHANGE_AND_ROUNDING(transp.length);
CHECK_CHANGE_AND_ROUNDING(transp.length);
CHECK_CHANGE(transp.msb_right);
CHECK_CHANGE(nonstd);
CHECK_CHANGE(activate);
CHECK_CHANGE_AND_ROUNDING(height);
CHECK_CHANGE_AND_ROUNDING(width);
CHECK_CHANGE(accel_flags);
CHECK_CHANGE_AND_ROUNDING(pixclock);
CHECK_CHANGE_AND_ROUNDING(left_margin);
CHECK_CHANGE_AND_ROUNDING(right_margin);
CHECK_CHANGE_AND_ROUNDING(upper_margin);
CHECK_CHANGE_AND_ROUNDING(lower_margin);
CHECK_CHANGE_AND_ROUNDING(hsync_len);
CHECK_CHANGE_AND_ROUNDING(vsync_len);
CHECK_CHANGE(sync);
CHECK_CHANGE(vmode);
CHECK_CHANGE(reserved[0]);
CHECK_CHANGE(reserved[1]);
CHECK_CHANGE(reserved[2]);
CHECK_CHANGE(reserved[3]);
CHECK_CHANGE(reserved[4]);
CHECK_CHANGE(reserved[5]);
var_validate();
}
#undef CHECK_CHANGE
#undef CHECK_ROUNDING
#undef CHECK_CHANGE_AND_ROUNDING
/*
* Validate the colormap
*/
static void cmap_validate(void)
{
/* FIXME */
}
/*
* Validate a change of the colormap
*/
static void cmap_validate_change(const struct fb_cmap *old, int error)
{
/* FIXME */
cmap_validate();
}
/*
* Validate the variable and fixed information about a frame buffer
*/
static void var_fix_validate(void)
{
/* FIXME: check for impossible combinations */
}
/*
* Initialization
*/
#define ALLOC_AND_SAVE_COMPONENT(name) \
do { \
if (!(saved_ ## name = malloc(fb_cmap.len*sizeof(u16)))) \
Fatal("malloc %d: %s\n", fb_cmap.len*sizeof(u16), \
strerror(errno)); \
memcpy(saved_ ## name, fb_cmap.name, fb_cmap.len*sizeof(u16)); \
} while (0)
void fb_init(void)
{
Debug("fb_init()\n");
fb_open();
fb_get_var();
saved_var = fb_var;
if (fb_var.xoffset || fb_var.yoffset || fb_var.accel_flags) {
fb_var.xoffset = 0;
fb_var.yoffset = 0;
fb_var.accel_flags = 0;
fb_set_var();
}
fb_get_fix();
var_fix_validate();
saved_fix = fb_fix;
switch (fb_fix.visual) {
case FB_VISUAL_MONO01:
case FB_VISUAL_MONO10:
case FB_VISUAL_TRUECOLOR:
/* no colormap */
break;
case FB_VISUAL_PSEUDOCOLOR:
case FB_VISUAL_STATIC_PSEUDOCOLOR:
cmap_init(1<<fb_var.bits_per_pixel);
break;
case FB_VISUAL_DIRECTCOLOR:
cmap_init(1<<(max(max(fb_var.red.length, fb_var.green.length),
max(fb_var.blue.length, fb_var.transp.length))));
break;
}
if (fb_cmap.len) {
fb_get_cmap();
saved_cmap = fb_cmap;
ALLOC_AND_SAVE_COMPONENT(red);
ALLOC_AND_SAVE_COMPONENT(green);
ALLOC_AND_SAVE_COMPONENT(blue);
if (fb_cmap.transp)
ALLOC_AND_SAVE_COMPONENT(transp);
}
fb_map();
fb_save();
fb_clear();
}
#undef ALLOC_AND_SAVE_COMPONENT
/*
* Clean up
*/
#define RESTORE_AND_FREE_COMPONENT(name) \
do { \
memcpy(fb_cmap.name, saved_ ## name, fb_cmap.len*sizeof(u16)); \
free(saved_ ## name); \
} while (0)
void fb_cleanup(void)
{
Debug("fb_cleanup()\n");
if (saved_fb)
fb_restore();
if (fb)
fb_unmap();
if (fb_fd != -1) {
if (saved_cmap.len) {
fb_cmap = saved_cmap;
RESTORE_AND_FREE_COMPONENT(red);
RESTORE_AND_FREE_COMPONENT(green);
RESTORE_AND_FREE_COMPONENT(blue);
if (fb_cmap.transp)
RESTORE_AND_FREE_COMPONENT(transp);
fb_set_cmap();
}
fb_var = saved_var;
fb_set_var();
fb_get_var();
/* FIXME: compare fb_var with saved_var */
fb_get_fix();
/* FIXME: compare fb_fix with saved_fix */
fb_close();
}
}
#undef RESTORE_AND_FREE_COMPONENT
/*
* Dump the colormap
*/
static void fb_dump_cmap(void)
{
int i;
Debug("Colormap start = %d len = %d\n", fb_cmap.start, fb_cmap.len);
for (i = 0; i < fb_cmap.len; i++)
Debug("%4d: R %04x G %04x B %04x A %04x\n", i, fb_cmap.red[i],
fb_cmap.green[i], fb_cmap.blue[i], fb_cmap.transp[i]);
}