/* $Id: mode.c,v 1.70 1999/12/18 18:40:52 marcus Exp $
******************************************************************************

   Display-FBDEV

   Copyright (C) 1998 Andrew Apted	[andrew@ggi-project.org]
   Copyright (C) 1999 Marcus Sundberg	[marcus@ggi-project.org]

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

******************************************************************************
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include <linux/fb.h>

#include <ggi/internal/ggi-dl.h>
#include <ggi/display/fbdev.h>

#include "../common/ggi-auto.inc"
#include "../common/gt-auto.inc"

#ifndef MAP_FAILED
#define MAP_FAILED ((void*)-1)
#endif

#define FB_KLUDGE_FONTX  8
#define FB_KLUDGE_FONTY  16
#define FB_KLUDGE_TEXTMODE  13
#define TIMINGFILE "/etc/fb.modes"


static void _GGI_free_dbs(ggi_visual *vis) 
{
	int i;

	for (i = LIBGGI_APPLIST(vis)->num-1; i >= 0; i--) {
		_ggi_db_free(LIBGGI_APPBUFS(vis)[i]);
		_ggi_db_del_buffer(LIBGGI_APPLIST(vis), i);
	}
}

int GGI_fbdev_kgicommand(ggi_visual *vis,int cmd,void *args)
{
	return fbdev_doioctl(vis, cmd, args);
}
        

int GGI_fbdev_getapi(ggi_visual *vis, int num, char *apiname, char *arguments)
{
	fbdev_hook *priv = LIBGGI_PRIVATE(vis);
	int size = GT_SIZE(LIBGGI_GT(vis));

	strcpy(arguments, "");

	switch(num) {

	case 0: strcpy(apiname, "display-fbdev");
		return 0;

	case 1: strcpy(apiname, "generic-stubs");
		return 0;

	case 2: strcpy(apiname, "generic-color");
		return 0;

	case 3: if (GT_SCHEME(LIBGGI_GT(vis)) == GT_TEXT) {
			sprintf(apiname, "generic-text-%d", size);
			return 0;
		} 
		
		if (priv->fix.type == FB_TYPE_PLANES) {
			strcpy(apiname, "generic-planar");
			return 0;
		}
		
		if (priv->fix.type == FB_TYPE_INTERLEAVED_PLANES) {
			sprintf(apiname, "generic-%s",
				(priv->fix.type_aux == 2) ? 
				"iplanar-2p" : "ilbm");
			return 0;
		}

		if ((size == 1 && (priv->flags & GGI_FBDEV_1BPP_REV)) ||
		    (size == 4 && (priv->flags & GGI_FBDEV_4BPP_REV))) {
			sprintf(apiname, "generic-linear-%d-r", size);
		} else {
			sprintf(apiname, "generic-linear-%d", size);
		}
		return 0;
		
	case 4:
		if (priv->have_accel) {
			strcpy(apiname, priv->accel);
			return 0;
		}
		break;
	}

	return -1;
}

static int get_timing(const char *file,struct fb_var_screeninfo *var)
{
	FILE *infile;
	char buffer[1024],*bufp;
	struct timing {
		uint32 xres;			/* visible resolution		*/
		uint32 yres;
		uint32 xres_virtual;		/* virtual resolution		*/
		uint32 yres_virtual;
		uint32 bits_per_pixel;		/* guess what			*/

		uint32 pixclock;		/* pixel clock in ps (pico seconds) */
		uint32 left_margin;		/* time from sync to picture	*/
		uint32 right_margin;		/* time from picture to sync	*/
		uint32 upper_margin;		/* time from sync to picture	*/
		uint32 lower_margin;
		uint32 hsync_len;		/* length of horizontal sync	*/
		uint32 vsync_len;		/* length of vertical sync	*/
	} timing;

	/* open the fb.modes file 
	 */
	if (NULL==(infile=fopen(file,"r"))) 
		return GGI_ENOFILE;
	
	/* Scan for the wanted mode.
	 */
	while(!feof(infile)) {

		/* EOF ? */
		if (NULL==fgets(buffer,sizeof(buffer),infile)) 
			break;

		/* Kill comments */
		if (strchr(buffer,'#')) *strchr(buffer,'#')='\0';

		/* Skip whitespace */
		bufp=buffer;
		while(*bufp==' '||*bufp=='\t'||*bufp=='\n') 
			bufp++;

		/* parse keywords 
		 */
		if (0==strncmp(bufp,"mode",4)) {
			memset(&timing,0,sizeof(timing));
		} else if (0==strncmp(bufp,"geometry",8)) {
			sscanf(bufp+8," %d %d %d %d %d",
				&timing.xres        ,&timing.yres,
				&timing.xres_virtual,&timing.yres_virtual,
				&timing.bits_per_pixel);
		} else if (0==strncmp(bufp,"timings",7)) {
			sscanf(bufp+7," %d %d %d %d %d %d %d",
				&timing.pixclock    ,
				/* Apparently the documentation for fb.modes is wrong here ! 
				   It says <right> <left> <upper> <lower> <hs> <vs> */
				&timing.left_margin,&timing.right_margin,
				&timing.upper_margin,&timing.lower_margin,
				&timing.hsync_len   ,&timing.vsync_len);
		} else if (0==strncmp(bufp,"endmode",7)) {
			if (timing.pixclock &&	/* Have a valid timings entry ? */
			    timing.xres          ==var->xres &&
			    timing.yres          ==var->yres &&
			    timing.bits_per_pixel==var->bits_per_pixel) {
				if (timing.xres_virtual!=var->xres_virtual ||
				    timing.yres_virtual!=var->yres_virtual ) {
					GGIDPRINT_MODE("display-fbdev: virtual doesn't match for timing. Hope this doesn't matter.\n");
				}
				var->pixclock    =timing.pixclock;
				var->right_margin=timing.right_margin;
				var->left_margin =timing.left_margin;
				var->upper_margin=timing.upper_margin;
				var->lower_margin=timing.lower_margin;
				var->hsync_len   =timing.hsync_len;
				var->vsync_len   =timing.vsync_len;
				return 0;
			}
		}
	}
	return GGI_ENOTFOUND;	
}

static int do_change_mode(ggi_visual *vis)
{
	fbdev_hook *priv = LIBGGI_PRIVATE(vis);
	ggi_mode *mode = LIBGGI_MODE(vis);
	ggi_graphtype gt = mode->graphtype;
	struct fb_var_screeninfo var;
	int err;

	/* setup var structure */
	var = priv->orig_var;

	var.xres = mode->visible.x * mode->dpp.x;
	var.yres = mode->visible.y * mode->dpp.y;
	var.xres_virtual = mode->virt.x * mode->dpp.x;
	var.yres_virtual = mode->virt.y * mode->dpp.y * mode->frames;
	
	var.activate = FB_ACTIVATE_NOW;
	var.nonstd = 0;
	var.grayscale = (GT_SCHEME(gt) == GT_GREYSCALE) ? 1 : 0;
        var.xoffset = var.yoffset = 0;

	switch (GT_SCHEME(gt)) {
		case GT_GREYSCALE:
		case GT_PALETTE:
			var.bits_per_pixel = GT_DEPTH(gt);
			break;
			
		case GT_TRUECOLOR:
			if (GT_DEPTH(gt) <= 16) {
				var.bits_per_pixel = GT_DEPTH(gt);
			} else {
				var.bits_per_pixel = GT_SIZE(gt);
			}
			break;

		case GT_TEXT: /* Oh this is such a hack */
			var.bits_per_pixel = FB_KLUDGE_TEXTMODE;
			break;
	}

#ifdef HAVE_NEW_FBDEV
	if (priv->accel) {
		/* Enable user space acceleration */
		var.accel_flags = 0;
	}
#endif /* HAVE_NEW_FBDEV */

	/* Try to get the timing from the standard database */
	if (get_timing(TIMINGFILE,&var)) {
		GGIDPRINT_MODE("display-fbdev: cannot get timing from "TIMINGFILE". Just hoping it works.\n");
	}
	
	/* tell framebuffer to change mode */
	if (fbdev_doioctl(vis, FBIOPUT_VSCREENINFO, &var) < 0) {
		perror("display-fbdev: FBIOPUT_VSCREENINFO");
		return -1;
	}
	priv->var = var;

	if (fbdev_doioctl(vis, FBIOGET_FSCREENINFO, &priv->fix) < 0) {
		perror("display-fbdev: FBIOGET_FSCREENINFO");
		return GGI_EFATAL;
	}

	/* check whether we got what we asked for */
	err = 0;
	switch (GT_SCHEME(gt)) {

		case GT_GREYSCALE:
			if (! priv->var.grayscale ||
			    GT_DEPTH(gt) != priv->var.bits_per_pixel ||
#ifdef FB_TYPE_TEXT
			    priv->fix.type == FB_TYPE_TEXT ||
#endif
			    priv->fix.visual == FB_VISUAL_TRUECOLOR ||
			    priv->fix.visual == FB_VISUAL_DIRECTCOLOR)
				err = GGI_EFATAL;
			break;
	
		case GT_PALETTE:
			if (GT_DEPTH(gt) != priv->var.bits_per_pixel ||
#ifdef FB_TYPE_TEXT
			    priv->fix.type == FB_TYPE_TEXT ||
#endif
			    priv->fix.visual == FB_VISUAL_TRUECOLOR ||
			    priv->fix.visual == FB_VISUAL_DIRECTCOLOR)
				err = GGI_EFATAL;
			break;

		case GT_TRUECOLOR:
			if ((GT_SIZE(gt)  != priv->var.bits_per_pixel &&
			     GT_DEPTH(gt) != priv->var.bits_per_pixel) ||
#ifdef FB_TYPE_TEXT
			    priv->fix.type  == FB_TYPE_TEXT ||
#endif
			    (priv->fix.visual != FB_VISUAL_TRUECOLOR &&
			     priv->fix.visual != FB_VISUAL_DIRECTCOLOR))
				err = GGI_EFATAL;
			break;
		
#ifdef FB_TYPE_TEXT
		case GT_TEXT:
			if (priv->fix.type != FB_TYPE_TEXT)
#endif
				err = GGI_EFATAL;
	}
	
	if (err) {
		GGIDPRINT_MODE("display-fbdev: Santa passed us by :-(\n");
	} else {
		GGIDPRINT_MODE("display-fbdev: Change mode OK.\n");
	}
	
	if (! err && (priv->fix.ypanstep == 0) && (mode->frames > 1)) {
		mode->frames = 1;
		GGIDPRINT_MODE("display-fbdev: cannot do vertical panning "
				"-- frames reduced to 1.\n");
	}

	/* Reset panning and frames */
        vis->d_frame_num = vis->origin_x = vis->origin_y = 0;

	return err;
}


static int do_mmap(ggi_visual *vis)
{
	fbdev_hook *priv = LIBGGI_PRIVATE(vis);
	ggi_graphtype gt;
	int xres_in_bytes;
	int i;

	/* Unmap previous mapping */
	if (priv->fb_ptr != NULL) {
		_GGI_free_dbs(vis);
		/* Clear old contents */
		memset(priv->fb_ptr, 0, priv->mmap_size);
		munmap(priv->fb_ptr, priv->mmap_size);
	}

        /* calculate framebuffer size */
	gt = LIBGGI_MODE(vis)->graphtype;

	switch (priv->fix.type) {
	case FB_TYPE_PLANES:
		if (priv->fix.line_length) {
			xres_in_bytes = priv->fix.line_length * GT_SIZE(gt);
		} else {
			xres_in_bytes = priv->var.xres_virtual * GT_SIZE(gt)/8;
		}
		break;
	case FB_TYPE_INTERLEAVED_PLANES:
		if (priv->fix.line_length) {
			xres_in_bytes = priv->fix.line_length *
				priv->var.bits_per_pixel * GT_SIZE(gt);
		} else {
			xres_in_bytes = priv->var.xres_virtual *
				priv->var.bits_per_pixel * GT_SIZE(gt) / 8;
		}
		break;
	default:
		if (priv->fix.line_length) {
			xres_in_bytes = priv->fix.line_length;
		} else {
			xres_in_bytes = priv->var.xres_virtual *
				priv->var.bits_per_pixel/8;
		}
		break;
	}

	priv->frame_size = xres_in_bytes * LIBGGI_VIRTY(vis);
	priv->fb_size = priv->frame_size * LIBGGI_MODE(vis)->frames;
#ifdef GGIFBDEV_MAP_EXACT
	priv->mmap_size = (priv->fb_size + 0x0fff) & ~0x0fff;
#else	
	priv->mmap_size = priv->fix.smem_len;
#endif
	
	GGIDPRINT_MODE("display-fbdev: frame_size=0x%x fb_size=0x%x "
		    "mmap_size=0x%x\n", priv->frame_size,
		    priv->fb_size, priv->mmap_size);

	priv->fb_ptr = mmap(NULL, priv->mmap_size, PROT_READ | PROT_WRITE, 
			    MAP_SHARED, LIBGGI_FD(vis), 0);

	GGIDPRINT_MODE("display-fbdev: FB_PTR=%p\n", priv->fb_ptr);

	if (priv->fb_ptr == MAP_FAILED) {
		priv->fb_ptr = NULL;
		return GGI_EFATAL;
	}

	/* clear all frames */
	memset(priv->fb_ptr, 0, priv->fb_size);

	/* Set up pixel format */
	memset(LIBGGI_PIXFMT(vis), 0, sizeof(ggi_pixelformat));
	LIBGGI_PIXFMT(vis)->size  = GT_SIZE(gt);
	LIBGGI_PIXFMT(vis)->depth = GT_DEPTH(gt);

	switch (GT_SCHEME(gt)) {

	case GT_PALETTE:
	case GT_GREYSCALE:
		LIBGGI_PIXFMT(vis)->clut_mask = (1 << GT_DEPTH(gt)) - 1;
		break;

	case GT_TRUECOLOR:
		GGIDPRINT_MODE("fbdev: RGB %d:%d:%d offsets %d:%d:%d\n",
			priv->var.red.length, priv->var.green.length,
			priv->var.blue.length, priv->var.red.offset,
			priv->var.green.offset, priv->var.blue.offset);

		LIBGGI_PIXFMT(vis)->red_mask =
		((1 << priv->var.red.length) - 1) << priv->var.red.offset;
			
		LIBGGI_PIXFMT(vis)->green_mask =
		((1 << priv->var.green.length) - 1) << priv->var.green.offset;
			
		LIBGGI_PIXFMT(vis)->blue_mask =
		((1 << priv->var.blue.length) - 1) << priv->var.blue.offset;
		break;

	case GT_TEXT:
		/* Assumes VGA text */
		LIBGGI_PIXFMT(vis)->texture_mask = 0x00ff;
		LIBGGI_PIXFMT(vis)->fg_mask = 0x0f00;
		LIBGGI_PIXFMT(vis)->bg_mask = 0xf000;
		break;
	}
	_ggi_build_pixfmt(LIBGGI_PIXFMT(vis));

	/* Set up DirectBuffers */
	for (i=0; i < LIBGGI_MODE(vis)->frames; i++) {
		ggi_directbuffer *buf;

		_ggi_db_add_buffer(LIBGGI_APPLIST(vis), _ggi_db_get_new());

		buf = LIBGGI_APPBUFS(vis)[i];

		buf->frame = i;
		buf->type  = GGI_DB_NORMAL;
		buf->read  = (uint8 *) priv->fb_ptr + i * priv->frame_size;
		buf->write = buf->read;

		if (priv->fix.type == FB_TYPE_PLANES) {
			buf->layout = blPixelPlanarBuffer;

			buf->buffer.plan.next_line =
				xres_in_bytes /	GT_SIZE(gt);
			buf->buffer.plan.next_plane = 
				buf->buffer.plan.next_line * LIBGGI_VIRTY(vis);
			buf->buffer.plan.pixelformat = LIBGGI_PIXFMT(vis);

		} else if (priv->fix.type == FB_TYPE_INTERLEAVED_PLANES) {
			buf->layout = blPixelPlanarBuffer;

			buf->buffer.plan.next_line =
				xres_in_bytes / GT_SIZE(gt);
			buf->buffer.plan.next_plane = priv->fix.type_aux;
			buf->buffer.plan.pixelformat = LIBGGI_PIXFMT(vis);

		} else {
			buf->type  |= GGI_DB_SIMPLE_PLB;
			buf->layout = blPixelLinearBuffer;

			buf->buffer.plb.stride = xres_in_bytes;
			buf->buffer.plb.pixelformat = LIBGGI_PIXFMT(vis);
		}
	}

	return 0;
}


static int do_setmode(ggi_visual *vis)
{
	fbdev_hook *priv = LIBGGI_PRIVATE(vis);
	int err, id;
	char libname[256], libargs[256];
	ggi_graphtype gt;

	err = do_change_mode(vis);
	if (err) {
		return err;
	}

	err = do_mmap(vis); 
	if (err) {
		return err;
	}

	_ggiZapMode(vis, ~GGI_DL_OPDISPLAY);
	priv->have_accel = 0;
	priv->flush = NULL;
	priv->idleaccel = NULL;
	for(id=1; GGI_fbdev_getapi(vis, id, libname, libargs) == 0; id++) {
		if (_ggiOpenDL(vis, libname, libargs, NULL) == NULL) {
			fprintf(stderr,"display-fbdev: Error opening the "
				"%s (%s) library.\n", libname, libargs);
			return GGI_EFATAL;
		}

		GGIDPRINT_LIBS("Success in loading %s (%s)\n",
			       libname, libargs);
	}

	if (priv->accel &&
	    _ggiOpenDL(vis, priv->accel, NULL, NULL) != NULL) {
		priv->have_accel = 1;
	} else {
		LIBGGI_GC(vis) = priv->normalgc;
		priv->have_accel = 0;
	}
	vis->accelactive = 0;

	/* Set up palette */
	if (vis->palette) {
		free(vis->palette);
		vis->palette = NULL;
	}
	gt = LIBGGI_GT(vis);
	if ((GT_SCHEME(gt) == GT_PALETTE) || (GT_SCHEME(gt) == GT_TEXT)) {
	    	int nocols = 1 << GT_DEPTH(gt);

		vis->palette = _ggi_malloc(nocols * sizeof(ggi_color));
		vis->opcolor->setpalvec = GGI_fbdev_setpalvec;
		/* Initialize palette */
		ggiSetColorfulPalette(vis);
	}
	
	vis->opdraw->setorigin = GGI_fbdev_setorigin;
	vis->opdraw->setdisplayframe = GGI_fbdev_setdisplayframe;

	ggiIndicateChange(vis, GGI_CHG_APILIST);

	GGIDPRINT_MODE("display-fbdev: do_setmode SUCCESS\n");

	return 0;
}


int GGI_fbdev_setmode(ggi_visual *vis, ggi_mode *mode)
{ 
	int err;

        if ((err = ggiCheckMode(vis, mode)) != 0) {
		return err;
	}

	GGIDPRINT_MODE("display-fbdev: setmode %dx%d#%dx%dF%d[0x%02x]\n",
			mode->visible.x, mode->visible.y,
			mode->virt.x, mode->virt.y, 
			mode->frames, mode->graphtype);

	memcpy(LIBGGI_MODE(vis), mode, sizeof(ggi_mode));

	/* Now actually set the mode */
	err = do_setmode(vis);
	if (err != 0) {
		return err;
	}

	GGIDPRINT_MODE("display-fbdev: setmode Success.\n");

	return 0;
}


int GGI_fbdev_resetmode(ggi_visual *vis)
{
	fbdev_hook *priv = LIBGGI_PRIVATE(vis);

	if (priv->fb_ptr != NULL) {
		_GGI_free_dbs(vis);
		/* Clear framebuffer */
		memset(priv->fb_ptr, 0, priv->mmap_size);
		munmap(priv->fb_ptr, priv->mmap_size);
	}
	fbdev_doioctl(vis, FBIOPUT_VSCREENINFO, &priv->orig_var);
	if (priv->fix.xpanstep != 0 || priv->fix.ypanstep != 0) {
		fbdev_doioctl(vis, FBIOPAN_DISPLAY, &priv->orig_var);
	}

	return 0;
}


int GGI_fbdev_checkmode(ggi_visual *vis, ggi_mode *mode)
{
	fbdev_hook *priv = LIBGGI_PRIVATE(vis);
	ggi_graphtype gt = mode->graphtype;
	int err;

	GGIDPRINT_MODE("display-fbdev: checkmode %dx%d#%dx%dF%d[0x%02x]\n",
			mode->visible.x, mode->visible.y,
			mode->virt.x, mode->virt.y, 
			mode->frames, mode->graphtype);
	
	/* handle GT_AUTO in graphtype */
	if (gt == GT_AUTO) {
#ifdef FB_TYPE_TEXT
		if (priv->orig_fix.type == FB_TYPE_TEXT)
			GT_SETSCHEME(gt, GT_TEXT);
		else
#endif
		switch (priv->orig_fix.visual) {

		case FB_VISUAL_MONO01:
		case FB_VISUAL_MONO10:
			GT_SETSCHEME(gt, GT_GREYSCALE);
			break;

		case FB_VISUAL_PSEUDOCOLOR:
		case FB_VISUAL_STATIC_PSEUDOCOLOR:
			GT_SETSCHEME(gt, priv->orig_var.grayscale ? 
				GT_GREYSCALE : GT_PALETTE);
			break;

		case FB_VISUAL_TRUECOLOR:
		case FB_VISUAL_DIRECTCOLOR:
			GT_SETSCHEME(gt, GT_TRUECOLOR);
			break;

		default:
			fprintf(stderr, "display-fbdev: WARNING: unknown "
				"visual (0x%02x) of framebuffer.\n",
				priv->orig_fix.visual);
			break;
		}
	}

	if (GT_DEPTH(gt) == GT_AUTO) {

		if ((GT_SCHEME(gt) == GT_TRUECOLOR) &&
		    (priv->orig_fix.visual == FB_VISUAL_TRUECOLOR ||
		     priv->orig_fix.visual == FB_VISUAL_TRUECOLOR)) {

			GT_SETDEPTH(gt, priv->orig_var.red.length   +
					priv->orig_var.green.length +
					priv->orig_var.blue.length);
		} else {
			GT_SETDEPTH(gt, priv->orig_var.bits_per_pixel);
		}
	}

	mode->graphtype = _GGIhandle_gtauto(gt);

	/* handle GGI_AUTO in ggi_mode */
	if (mode->dpp.x == GGI_AUTO) {
		mode->dpp.x = (GT_SCHEME(mode->graphtype) == GT_TEXT) ?
		     FB_KLUDGE_FONTX : 1;
	}

	if (mode->dpp.y == GGI_AUTO) {
		mode->dpp.y = (GT_SCHEME(mode->graphtype) == GT_TEXT) ?
		     FB_KLUDGE_FONTY : 1;
	}

	_GGIhandle_ggiauto(mode, priv->orig_var.xres / mode->dpp.x,
				 priv->orig_var.yres / mode->dpp.y);

	/* now check stuff */
	err = 0;
	if ((mode->visible.x <= 0) || (mode->visible.y <= 0) ||
	    (mode->virt.x    <= 0) || (mode->virt.y    <= 0) ||
	    (GT_SIZE(mode->graphtype) < GT_DEPTH(mode->graphtype))) {

		GGIDPRINT("display-fbdev: checkmode: Bad Geometry.\n");
		return -1;
	}

	if (mode->virt.x < mode->visible.x) {
		mode->virt.x = mode->visible.x;
		err = -1;
	}
		
	if (mode->virt.y < mode->visible.y) {
		mode->virt.y = mode->visible.y;
		err = -1;
	}

	/* Now use FB_ACTIVATE_TEST to let the framebuffer driver check
	 * & upgrade the mode.  !!! IMPLEMENT ME
	 */
	GGIDPRINT_MODE("display-fbdev: result %d %dx%d#%dx%dF%d[0x%02x]\n",
			err, mode->visible.x, mode->visible.y,
			mode->virt.x, mode->virt.y, 
			mode->frames, mode->graphtype);

	return err;
}


int GGI_fbdev_getmode(ggi_visual *vis, ggi_mode *mode)
{
	GGIDPRINT_MODE("display-fbdev: getmode\n");
	
	memcpy(mode, LIBGGI_MODE(vis), sizeof(ggi_mode));

	return 0;
}


int GGI_fbdev_setflags(ggi_visual *vis, ggi_flags flags)
{
	LIBGGI_FLAGS(vis) = flags;

	return 0;
}
