/*
 * Framebuffer driver for VBE 2.0+ compliant graphic boards
 *
 * (c) 2004-2005 Michal Januszewski <spock@gentoo.org
 *     Based upon vesafb code by Gerd Knorr <kraxel@goldbach.in-berlin.de
 *
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/completion.h>
#include <linux/platform_device.h>
#include <video/edid.h>
#include <video/vesa.h>
#include <video/vga.h>
#include <asm/io.h>
#include <asm/mtrr.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include "edid.h"

#define dac_reg	(0x3c8)
#define dac_val	(0x3c9)

#define VESAFB_NEED_EXACT_RES 	1
#define VESAFB_NEED_EXACT_DEPTH 2

/* --------------------------------------------------------------------- */

static struct fb_var_screeninfo vesafb_defined __initdata = {
	.activate	= FB_ACTIVATE_NOW,
	.height		= 0,
	.width		= 0,
	.right_margin	= 32,
	.upper_margin	= 16,
	.lower_margin	= 4,
	.vsync_len	= 4,
	.vmode		= FB_VMODE_NONINTERLACED,
};

static struct fb_fix_screeninfo vesafb_fix __initdata = {
	.id	= "VESA VGA",
	.type	= FB_TYPE_PACKED_PIXELS,
	.accel	= FB_ACCEL_NONE,
};

static int  mtrr       = 0;	/* disable mtrr by default */
static int  ypan       = 0;	/* 0 - nothing, 1 - ypan, 2 - ywrap */
static int  pmi_setpal = 0;	/* pmi for palette changes */
static u16  *pmi_base  = NULL;  /* protected mode interface location */
static void (*pmi_start)(void) = NULL;
static void (*pmi_pal)(void)   = NULL;
static struct vesafb_vbe_ib  vbe_ib;
static struct vesafb_mode_ib *vbe_modes;
static int                   vbe_modes_cnt = 0;
static struct fb_info	     *vesafb_info = NULL;
static int  nocrtc		    = 0; /* ignore CRTC settings */
static int  noedid       __initdata = 0; /* don't try DDC transfers */
static int  vram_remap   __initdata = 0; /* set amount of memory to be used */
static int  vram_total   __initdata = 0; /* set total amount of memory */
static u16  maxclk       __initdata = 0; /* maximum pixel clock */
static u16  maxvf        __initdata = 0; /* maximum vertical frequency */
static u16  maxhf        __initdata = 0; /* maximum horizontal frequency */
static int  gtf          __initdata = 0; /* forces use of the GTF */
static char *mode_option __initdata = NULL;
static u16  vbemode      __initdata = 0;

/* --------------------------------------------------------------------- */

static int vesafb_find_vbe_mode(int xres, int yres, int depth,
				unsigned char flags)
{
	int i, match = -1, h = 0, d = 0x7fffffff;

	for (i = 0; i < vbe_modes_cnt; i++) {
		h = abs(vbe_modes[i].x_res - xres) +
		    abs(vbe_modes[i].y_res - yres) +
		    abs(depth - vbe_modes[i].depth);
		if (h == 0)
			return i;
		if (h < d || (h == d && vbe_modes[i].depth > depth)) {
			d = h;
			match = i;
		}
	}
	i = 1;

	if (flags & VESAFB_NEED_EXACT_DEPTH && vbe_modes[match].depth != depth)
		i = 0;
	if (flags & VESAFB_NEED_EXACT_RES && d > 24)
		i = 0;
	if (i != 0)
		return match;
	else
		return -1;
}

static int vesafb_pan_display(struct fb_var_screeninfo *var,
                              struct fb_info *info)
{
	int offset;

	if (!ypan)
		return -EINVAL;
	if (var->xoffset)
		return -EINVAL;
	if (var->yoffset > var->yres_virtual)
		return -EINVAL;
	if ((ypan==1) && var->yoffset+var->yres > var->yres_virtual)
		return -EINVAL;

	offset = (var->yoffset * info->fix.line_length + var->xoffset) / 4;

	/* It turns out it's not the best idea to do panning via vm86,
	 * so we only allow it if we have a PMI. */
	if (pmi_start) {
		__asm__ __volatile__(
			"call *(%%edi)"
			: /* no return value */
			: "a" (0x4f07),         /* EAX */
			  "b" (0),              /* EBX */
			  "c" (offset),         /* ECX */
			  "d" (offset >> 16),   /* EDX */
			  "D" (&pmi_start));    /* EDI */
	}
	return 0;
}

static int vesafb_blank(int blank, struct fb_info *info)
{
	struct vesafb_task *tsk;
	int err = 1;

	if (vbe_ib.capabilities & VBE_CAP_VGACOMPAT) {
		int loop = 10000;
		u8 seq = 0, crtc17 = 0;

		if (blank == FB_BLANK_POWERDOWN) {
			seq = 0x20;
			crtc17 = 0x00;
			err = 0;
		} else {
			seq = 0x00;
			crtc17 = 0x80;
			err = (blank == FB_BLANK_UNBLANK) ? 0 : -EINVAL;
		}

		vga_wseq(NULL, 0x00, 0x01);
		seq |= vga_rseq(NULL, 0x01) & ~0x20;
		vga_wseq(NULL, 0x00, seq);

		crtc17 |= vga_rcrt(NULL, 0x17) & ~0x80;
		while (loop--);
		vga_wcrt(NULL, 0x17, crtc17);
		vga_wseq(NULL, 0x00, 0x03);
	} else {
		vesafb_create_task (tsk);
		if (!tsk)
			return -ENOMEM;
		tsk->regs.eax = 0x4f10;
		switch (blank) {
		case FB_BLANK_UNBLANK:
			tsk->regs.ebx = 0x0001;
			break;
		case FB_BLANK_NORMAL:
			tsk->regs.ebx = 0x0101;	/* standby */
			break;
		case FB_BLANK_POWERDOWN:
			tsk->regs.ebx = 0x0401;	/* powerdown */
			break;
		default:
			goto out;
		}
		tsk->flags = TF_CALL;
		if (!vesafb_queue_task (tsk))
			vesafb_wait_for_task(tsk);

		if ((tsk->regs.eax & 0xffff) == 0x004f)
			err = 0;
out:		kfree(tsk);
	}
	return err;
}

static int vesafb_setpalette(struct vesafb_pal_entry *entries, int count,
			     int start, struct fb_info *info)
{
	struct vesafb_task *tsk;
	int i = ((struct vesafb_par*)info->par)->mode_idx;
	int ret = 0;

	/* We support palette modifications for 8 bpp modes only, so
	 * there can never be more than 256 entries. */
	if (start + count > 256)
		return -EINVAL;

	if (pmi_setpal) {
		__asm__ __volatile__(
		"call *(%%esi)"
		: /* no return value */
		: "a" (0x4f09),         /* EAX */
		  "b" (0),              /* EBX */
		  "c" (count),          /* ECX */
		  "d" (start),          /* EDX */
		  "D" (entries),        /* EDI */
		  "S" (&pmi_pal));      /* ESI */
		return 0;
	}

	/* Use VGA registers if mode is VGA-compatible. */
	if (i >= 0 && i < vbe_modes_cnt &&
	    vbe_modes[i].mode_attr & VBE_MODE_VGACOMPAT) {
		for (i = 0; i < count; i++) {
			outb_p(start + i,        dac_reg);
			outb_p(entries[i].red,   dac_val);
			outb_p(entries[i].green, dac_val);
			outb_p(entries[i].blue,  dac_val);
		}
	} else {
		vesafb_create_task (tsk);
		if (!tsk)
			return -ENOMEM;
		tsk->regs.eax = 0x4f09;
		tsk->regs.ebx = 0x0;
		tsk->regs.ecx = count;
		tsk->regs.edx = start;
		tsk->buf = entries;
		tsk->buf_len = sizeof(struct vesafb_pal_entry) * count;
		tsk->flags = TF_CALL | TF_USE_BUF;

		if (!vesafb_queue_task (tsk))
			vesafb_wait_for_task(tsk);
		if ((tsk->regs.eax & 0xffff) != 0x004f)
			ret = 1;
		kfree(tsk);
	}
	return ret;
}

static int vesafb_setcolreg(unsigned regno, unsigned red, unsigned green,
			    unsigned blue, unsigned transp,
			    struct fb_info *info)
{
	struct vesafb_pal_entry entry;
	int shift = 16 - info->var.green.length;
	int ret = 0;

	if (regno >= info->cmap.len)
		return -EINVAL;

	if (info->var.bits_per_pixel == 8) {
		entry.red   = red   >> shift;
		entry.green = green >> shift;
		entry.blue  = blue  >> shift;
		entry.pad   = 0;

		ret = vesafb_setpalette(&entry, 1, regno, info);
	} else if (regno < 16) {
		switch (info->var.bits_per_pixel) {
		case 16:
			if (info->var.red.offset == 10) {
				/* 1:5:5:5 */
				((u32*) (info->pseudo_palette))[regno] =
						((red   & 0xf800) >>  1) |
						((green & 0xf800) >>  6) |
						((blue  & 0xf800) >> 11);
			} else {
				/* 0:5:6:5 */
				((u32*) (info->pseudo_palette))[regno] =
						((red   & 0xf800)      ) |
						((green & 0xfc00) >>  5) |
						((blue  & 0xf800) >> 11);
			}
			break;

		case 24:
		case 32:
			red   >>= 8;
			green >>= 8;
			blue  >>= 8;
			((u32 *)(info->pseudo_palette))[regno] =
				(red   << info->var.red.offset)   |
				(green << info->var.green.offset) |
				(blue  << info->var.blue.offset);
			break;
		}
	}
	return ret;
}

static int vesafb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
{
	struct vesafb_pal_entry *entries;
	int shift = 16 - info->var.green.length;
	int i, ret = 0;

	if (info->var.bits_per_pixel == 8) {
		if (cmap->start + cmap->len > info->cmap.start +
		    info->cmap.len || cmap->start < info->cmap.start)
			return -EINVAL;

		entries = vmalloc(sizeof(struct vesafb_pal_entry) * cmap->len);
		if (!entries)
			return -ENOMEM;
		for (i = 0; i < cmap->len; i++) {
			entries[i].red   = cmap->red[i]   >> shift;
			entries[i].green = cmap->green[i] >> shift;
			entries[i].blue  = cmap->blue[i]  >> shift;
			entries[i].pad   = 0;
		}
		ret = vesafb_setpalette(entries, cmap->len, cmap->start, info);
		vfree(entries);
	} else {
		/* For modes with bpp > 8, we only set the pseudo palette in
		 * the fb_info struct. We rely on vesafb_setcolreg to do all
		 * sanity checking. */
		for (i = 0; i < cmap->len; i++) {
			ret += vesafb_setcolreg(cmap->start + i, cmap->red[i],
						cmap->green[i], cmap->blue[i],
						0, info);
		}
	}
	return ret;
}

static int vesafb_set_par(struct fb_info *info)
{
	struct vesafb_par *par = (struct vesafb_par *) info->par;
	struct vesafb_task *tsk;
	struct vesafb_crtc_ib *crtc = NULL;
	struct vesafb_mode_ib *mode = NULL;
	int i, err = 0, depth = info->var.bits_per_pixel;

	if (depth > 8 && depth != 32)
		depth = info->var.red.length + info->var.green.length +
			info->var.blue.length;

	i = vesafb_find_vbe_mode(info->var.xres, info->var.yres, depth,
				 VESAFB_NEED_EXACT_RES |
				 VESAFB_NEED_EXACT_DEPTH);
	if (i >= 0)
		mode = &vbe_modes[i];
	else
		return -EINVAL;

	vesafb_create_task (tsk);
	if (!tsk)
		return -ENOMEM;
	tsk->regs.eax = 0x4f02;
	tsk->regs.ebx = mode->mode_id | 0x4000;		/* use LFB */
	tsk->flags = TF_CALL;

	if (vbe_ib.vbe_version >= 0x0300 && !nocrtc &&
	    info->var.pixclock != 0) {
		tsk->regs.ebx |= 0x0800; 		/* use CRTC data */
		tsk->flags |= TF_USE_BUF;
		crtc = kmalloc(sizeof(struct vesafb_crtc_ib), GFP_KERNEL);
		if (!crtc) {
			err = -ENOMEM;
			goto out;
		}
		crtc->horiz_start = info->var.xres + info->var.right_margin;
		crtc->horiz_end	  = crtc->horiz_start + info->var.hsync_len;
		crtc->horiz_total = crtc->horiz_end + info->var.left_margin;

		crtc->vert_start  = info->var.yres + info->var.lower_margin;
		crtc->vert_end    = crtc->vert_start + info->var.vsync_len;
		crtc->vert_total  = crtc->vert_end + info->var.upper_margin;

		crtc->pixel_clock = PICOS2KHZ(info->var.pixclock) * 1000;
		crtc->refresh_rate = (u16)(100 * (crtc->pixel_clock /
				     (crtc->vert_total * crtc->horiz_total)));
		crtc->flags = 0;

		if (info->var.vmode & FB_VMODE_DOUBLE)
			crtc->flags |= 0x1;
		if (info->var.vmode & FB_VMODE_INTERLACED)
			crtc->flags |= 0x2;
		if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT))
			crtc->flags |= 0x4;
		if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT))
			crtc->flags |= 0x8;
		memcpy(&par->crtc, crtc, sizeof(struct vesafb_crtc_ib));
	} else
		memset(&par->crtc, 0, sizeof(struct vesafb_crtc_ib));

	tsk->buf = (void*)crtc;
	tsk->buf_len = sizeof(struct vesafb_crtc_ib);

	if (vesafb_queue_task (tsk)) {
		err = -EINVAL;
		goto out;
	}
	vesafb_wait_for_task(tsk);

	if ((tsk->regs.eax & 0xffff) != 0x004f) {
		printk(KERN_ERR "vesafb: mode switch failed (eax: 0x%lx)\n",
				tsk->regs.eax);
		err = -EINVAL;
		goto out;
	}
	par->mode_idx = i;

	/* For 8bpp modes, always try to set the DAC to 8 bits. */
	if (vbe_ib.capabilities & VBE_CAP_CAN_SWITCH_DAC &&
	    mode->bits_per_pixel <= 8) {
		vesafb_reset_task(tsk);
		tsk->flags = TF_CALL;
		tsk->regs.eax = 0x4f08;
		tsk->regs.ebx = 0x0800;

		if (!vesafb_queue_task (tsk))
			vesafb_wait_for_task(tsk);

		if ((tsk->regs.eax & 0xffff) != 0x004f ||
		    ((tsk->regs.ebx & 0xff00) >> 8) != 8) {
			/* We've failed to set the DAC palette format -
			 * time to correct var. */
			info->var.red.length    = 6;
			info->var.green.length  = 6;
			info->var.blue.length   = 6;
		}
	}

	info->fix.visual = (info->var.bits_per_pixel == 8) ?
		           FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
	info->fix.line_length = mode->bytes_per_scan_line;

	DPRINTK("set new mode %dx%d-%d (0x%x)\n",
		info->var.xres, info->var.yres, info->var.bits_per_pixel,
		mode->mode_id);

out:	if (crtc != NULL)
		kfree(crtc);
	kfree(tsk);

	return err;
}

static void vesafb_setup_var(struct fb_var_screeninfo *var, struct fb_info *info,
			     struct vesafb_mode_ib *mode)
{
	var->xres = mode->x_res;
	var->yres = mode->y_res;
	var->xres_virtual = mode->x_res;
	var->yres_virtual = (ypan) ?
			      info->fix.smem_len / mode->bytes_per_scan_line :
			      mode->y_res;
	var->xoffset = 0;
	var->yoffset = 0;
	var->bits_per_pixel = mode->bits_per_pixel;

	if (var->bits_per_pixel == 15)
		var->bits_per_pixel = 16;

	if (var->bits_per_pixel > 8) {
		var->red.offset    = mode->red_off;
		var->red.length    = mode->red_len;
		var->green.offset  = mode->green_off;
		var->green.length  = mode->green_len;
		var->blue.offset   = mode->blue_off;
		var->blue.length   = mode->blue_len;
		var->transp.offset = mode->rsvd_off;
		var->transp.length = mode->rsvd_len;

		DPRINTK("directcolor: size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n",
			mode->rsvd_len,
			mode->red_len,
			mode->green_len,
			mode->blue_len,
			mode->rsvd_off,
			mode->red_off,
			mode->green_off,
			mode->blue_off);
	} else {
		var->red.offset    = 0;
		var->green.offset  = 0;
		var->blue.offset   = 0;
		var->transp.offset = 0;

		/* We're assuming that we can switch the DAC to 8 bits. If
		 * this proves to be incorrect, we'll update the fields
		 * later in set_par(). */
		if (vbe_ib.capabilities & VBE_CAP_CAN_SWITCH_DAC) {
			var->red.length    = 8;
			var->green.length  = 8;
			var->blue.length   = 8;
			var->transp.length = 0;
		} else {
			var->red.length    = 6;
			var->green.length  = 6;
			var->blue.length   = 6;
			var->transp.length = 0;
		}
	}
}

static void inline vesafb_check_limits(struct fb_var_screeninfo *var,
		 		       struct fb_info *info)
{
	struct fb_videomode *mode;

	if (!var->pixclock)
		return;
	if (vbe_ib.vbe_version < 0x0300) {
		fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60, var, info);
		return;
	}
	if (!fb_validate_mode(var, info))
		return;
	mode = fb_find_best_mode(var, &info->modelist);
	if (mode) {
		DPRINTK("find_best_mode: %d %d @ %d (vmode: %d)\n",
			mode->xres, mode->yres, mode->refresh, mode->vmode);
		if (mode->xres == var->xres && mode->yres == var->yres &&
		    !(mode->vmode & (FB_VMODE_INTERLACED | FB_VMODE_DOUBLE))) {
			fb_videomode_to_var(var, mode);
			return;
		}
	}
	if (info->monspecs.gtf && !fb_get_mode(FB_MAXTIMINGS, 0, var, info))
		return;
	/* Use default refresh rate */
	var->pixclock = 0;
}

static int vesafb_check_var(struct fb_var_screeninfo *var,
			    struct fb_info *info)
{
	int match = -1;
	int depth = var->red.length + var->green.length + var->blue.length;

	/* Various apps will use bits_per_pixel to set the color depth,
	 * which is theoretically incorrect, but which we'll try to handle
	 * here. */
	if (depth == 0 || abs(depth - var->bits_per_pixel) >= 8)
		depth = var->bits_per_pixel;
	match = vesafb_find_vbe_mode(var->xres, var->yres, depth,
				     VESAFB_NEED_EXACT_RES);

	if (match == -1) {
		DPRINTK("vesafb: mode %dx%d-%d not found\n", var->xres,
			var->yres, depth);
		return -EINVAL;
	}

	vesafb_setup_var(var, info, &vbe_modes[match]);
	DPRINTK("found mode 0x%x (%dx%d-%dbpp)\n",
		vbe_modes[match].mode_id, vbe_modes[match].x_res,
		vbe_modes[match].y_res, vbe_modes[match].depth);

	/* Check whether we have remapped enough memory for this mode. */
	if (var->yres * vbe_modes[match].bytes_per_scan_line >
	    info->fix.smem_len) {
		return -EINVAL;
	}

	if ((var->vmode & FB_VMODE_DOUBLE) &&
	    !(vbe_modes[match].mode_attr & 0x100))
		var->vmode &= ~FB_VMODE_DOUBLE;
	if ((var->vmode & FB_VMODE_INTERLACED) &&
	    !(vbe_modes[match].mode_attr & 0x200))
		var->vmode &= ~FB_VMODE_INTERLACED;
	vesafb_check_limits(var, info);
	return 0;
}

static void vesafb_platform_release(struct device *device)
{
	return;
}

static int __init vesafb_probe(struct platform_device *device);

static struct fb_ops vesafb_ops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= vesafb_setcolreg,
	.fb_setcmap	= vesafb_setcmap,
	.fb_pan_display	= vesafb_pan_display,
	.fb_blank       = vesafb_blank,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
	.fb_check_var	= vesafb_check_var,
	.fb_set_par	= vesafb_set_par
};

static struct platform_driver vesafb_driver = {
	.probe	= vesafb_probe,
	.driver	= {
		.name	= "vesafb",
	},
};

static struct platform_device vesafb_device = {
	.name	= "vesafb",
        .dev    = {
                .release = vesafb_platform_release,
        }
};

#ifndef MODULE
int __init vesafb_setup(char *options)
{
	char *this_opt;

	if (!options || !*options)
		return 0;

	DPRINTK("options %s\n",options);

	while ((this_opt = strsep(&options, ",")) != NULL) {
		if (!*this_opt) continue;

		DPRINTK("this_opt: %s\n",this_opt);

		if (! strcmp(this_opt, "redraw"))
			ypan=0;
		else if (! strcmp(this_opt, "ypan"))
			ypan=1;
		else if (! strcmp(this_opt, "ywrap"))
			ypan=2;
		else if (! strcmp(this_opt, "vgapal"))
			pmi_setpal=0;
		else if (! strcmp(this_opt, "pmipal"))
			pmi_setpal=1;
		else if (! strncmp(this_opt, "mtrr:", 5))
			mtrr = simple_strtoul(this_opt+5, NULL, 0);
		else if (! strcmp(this_opt, "nomtrr"))
			mtrr=0;
		else if (! strcmp(this_opt, "nocrtc"))
			nocrtc=1;
		else if (! strcmp(this_opt, "noedid"))
			noedid=1;
		else if (! strcmp(this_opt, "gtf"))
			gtf=1;
		else if (! strncmp(this_opt, "vtotal:", 7))
			vram_total = simple_strtoul(this_opt + 7, NULL, 0);
		else if (! strncmp(this_opt, "vremap:", 7))
			vram_remap = simple_strtoul(this_opt + 7, NULL, 0);
		else if (! strncmp(this_opt, "maxhf:", 6))
			maxhf = simple_strtoul(this_opt + 6, NULL, 0);
		else if (! strncmp(this_opt, "maxvf:", 6))
			maxvf = simple_strtoul(this_opt + 6, NULL, 0);
		else if (! strncmp(this_opt, "maxclk:", 7))
			maxclk = simple_strtoul(this_opt + 7, NULL, 0);
		else if (! strncmp(this_opt, "vbemode:", 8))
			vbemode = simple_strtoul(this_opt + 8, NULL,0);
		else if (this_opt[0] >= '0' && this_opt[0] <= '9') {
			DPRINTK("mode_option: %s\n",this_opt);
			mode_option = this_opt;
		} else {
			printk(KERN_WARNING
			       "vesafb: unrecognized option %s\n", this_opt);
		}
	}

	return 0;
}
#endif /* !MODULE */

static int vesafb_read_proc_modes(char *buf, char **start, off_t offset,
			    	  int len, int *eof, void *private)
{
	int clen = 0, i;

	for (i = 0; i < vbe_modes_cnt; i++) {
		clen += sprintf(buf + clen, "%dx%d-%d\n", vbe_modes[i].x_res,
				vbe_modes[i].y_res, vbe_modes[i].depth);
	}
	*start = buf + offset;

	if (clen > offset) {
		clen -= offset;
	} else {
		clen = 0;
	}
	return clen;
}

static int vesafb_read_proc_vbe_info(char *buf, char **start, off_t offset,
			    	     int len, int *eof, void *private)
{
	int clen = 0;

	clen += sprintf(buf + clen, "Version:    %d.%d\n",
			((vbe_ib.vbe_version & 0xff00) >> 8),
			vbe_ib.vbe_version & 0xff);
	clen += sprintf(buf + clen, "Vendor:     %s\n",
			(char*)vbe_ib.oem_vendor_name_ptr);
	clen += sprintf(buf + clen, "Product:    %s\n",
			(char*)vbe_ib.oem_product_name_ptr);
	clen += sprintf(buf + clen, "OEM rev:    %s\n",
			(char*)vbe_ib.oem_product_rev_ptr);
	clen += sprintf(buf + clen, "OEM string: %s\n",
			(char*)vbe_ib.oem_string_ptr);

	*start = buf + offset;

	if (clen > offset) {
		clen -= offset;
	} else {
		clen = 0;
	}
	return clen;
}

static int __init inline vesafb_vbe_getinfo(struct vesafb_task *tsk)
{
	tsk->regs.eax = 0x4f00;
	tsk->flags = TF_CALL | TF_GETVBEIB;
	tsk->buf = &vbe_ib;
	tsk->buf_len = sizeof(vbe_ib);
	if (vesafb_queue_task (tsk))
		return -EINVAL;
	vesafb_wait_for_task(tsk);

	if (vbe_ib.vbe_version < 0x0200) {
		printk(KERN_ERR "vesafb: Sorry, pre-VBE 2.0 cards are "
				"not supported.\n");
		return -EINVAL;
	}

	if ((tsk->regs.eax & 0xffff) != 0x004f) {
		printk(KERN_ERR "vesafb: Getting mode info block failed "
				"(eax=0x%x)\n", (u32)tsk->regs.eax);
		return -EINVAL;
	}

	printk(KERN_INFO "vesafb: %s, %s, %s (OEM: %s)\n",
		(char*)vbe_ib.oem_vendor_name_ptr,
		(char*)vbe_ib.oem_product_name_ptr,
		(char*)vbe_ib.oem_product_rev_ptr,
		(char*)vbe_ib.oem_string_ptr);

	printk(KERN_INFO "vesafb: VBE version: %d.%d\n",
			 ((vbe_ib.vbe_version & 0xff00) >> 8),
			 vbe_ib.vbe_version & 0xff);
	return 0;
}

static int __init inline vesafb_vbe_getmodes(struct vesafb_task *tsk)
{
	u16 *mode = 0;
	int off = 0;

	/* Count available modes. */
	mode = (u16*)vbe_ib.mode_list_ptr;
	while (*mode != 0xffff) {
		vbe_modes_cnt++;
		mode++;
	}

	vbe_modes = kmalloc(sizeof(struct vesafb_mode_ib)*
			    vbe_modes_cnt, GFP_KERNEL);
	if (!vbe_modes)
		return -ENOMEM;

	/* Get mode info for all available modes. */
	mode = (u16*)vbe_ib.mode_list_ptr;

	while (*mode != 0xffff) {
		struct vesafb_mode_ib *mib;

		vesafb_reset_task(tsk);
		tsk->regs.eax = 0x4f01;
		tsk->regs.ecx = (u32) *mode;
		tsk->flags = TF_CALL | TF_RETURN_BUF;
		tsk->buf = vbe_modes+off;
		tsk->buf_len = sizeof(struct vesafb_mode_ib);
		if (vesafb_queue_task(tsk))
			return -EINVAL;
		vesafb_wait_for_task(tsk);
		mib = p_mode(tsk->buf);
		mib->mode_id = *mode;

		/* We only want modes that are supported with the currennt
		 * hardware configuration (D0), color (D3), graphics (D4)
		 * and that have support for the LFB (D7). */
		if ((mib->mode_attr & 0x99) == 0x99 &&
		    mib->bits_per_pixel >= 8) {
			off++;
		} else {
			vbe_modes_cnt--;
		}
		mode++;
		mib->depth = mib->red_len + mib->green_len + mib->blue_len;
		/* Handle 8bpp modes and modes with broken color component
		 * lengths. */
		if (mib->depth == 0 ||
		    (mib->depth == 24 && mib->bits_per_pixel == 32))
			mib->depth = mib->bits_per_pixel;
	}

	return 0;
}

static int __init inline vesafb_vbe_getpmi(struct vesafb_task *tsk)
{
	int i;

	vesafb_reset_task(tsk);
	tsk->regs.eax = 0x4f0a;
	tsk->regs.ebx = 0x0;
	tsk->flags = TF_CALL;
	if (vesafb_queue_task(tsk))
		return -EINVAL;
	vesafb_wait_for_task(tsk);

	if ((tsk->regs.eax & 0xffff) != 0x004f || tsk->regs.es < 0xc000) {
		pmi_setpal = ypan = 0;
	} else {
		pmi_base  = (u16*)phys_to_virt(((u32)tsk->regs.es << 4) +
			     tsk->regs.edi);
		pmi_start = (void*)((char*)pmi_base + pmi_base[1]);
		pmi_pal   = (void*)((char*)pmi_base + pmi_base[2]);
		printk(KERN_INFO "vesafb: protected mode interface info at "
				 "%04x:%04x\n",
				 (u16)tsk->regs.es, (u16)tsk->regs.edi);
		printk(KERN_INFO "vesafb: pmi: set display start = %p, "
				 "set palette = %p\n", pmi_start, pmi_pal);

		if (pmi_base[3]) {
			printk(KERN_INFO "vesafb: pmi: ports = ");
			for (i = pmi_base[3]/2; pmi_base[i] != 0xffff; i++)
				printk("%x ",pmi_base[i]);
			printk("\n");

			/*
			 * memory areas not supported (yet?)
			 *
			 * Rules are: we have to set up a descriptor for the
			 * requested memory area and pass it in the ES register
			 * to the BIOS function.
			 */
			if (pmi_base[i] != 0xffff) {
				printk(KERN_INFO "vesafb: can't handle memory "
						 "requests, pmi disabled\n");
				ypan = pmi_setpal = 0;
			}
		}
	}
	return 0;
}

static int __init inline vesafb_vbe_getedid(struct vesafb_task *tsk,
					    struct fb_info *info)
{
	int res = 0;

	if (noedid || vbe_ib.vbe_version < 0x0300)
		return -EINVAL;

	vesafb_reset_task(tsk);
	tsk->regs.eax = 0x4f15;
	tsk->regs.ebx = 0;
	tsk->regs.ecx = 0;
	if (vesafb_queue_task(tsk))
		return -EINVAL;
	vesafb_wait_for_task(tsk);

	if ((tsk->regs.eax & 0xffff) != 0x004f)
		return -EINVAL;

	if ((tsk->regs.ebx & 0x3) == 3) {
		printk(KERN_INFO "vesafb: VBIOS/hardware supports both "
				 "DDC1 and DDC2 transfers\n");
	} else if ((tsk->regs.ebx & 0x3) == 2) {
		printk(KERN_INFO "vesafb: VBIOS/hardware supports DDC2 "
				 "transfers\n");
	} else if ((tsk->regs.ebx & 0x3) == 1) {
		printk(KERN_INFO "vesafb: VBIOS/hardware supports DDC1 "
				 "transfers\n");
	} else {
		printk(KERN_INFO "vesafb: VBIOS/hardware doesn't support "
				 "DDC transfers\n");
		return -EINVAL;
	}

	vesafb_reset_task(tsk);
	tsk->regs.eax = 0x4f15;
	tsk->regs.ebx = 1;
	tsk->regs.ecx = tsk->regs.edx = 0;
	tsk->flags = TF_CALL | TF_RETURN_BUF;
	tsk->buf = kmalloc(EDID_LENGTH, GFP_KERNEL);
	tsk->buf_len = EDID_LENGTH;

	if (vesafb_queue_task(tsk)) {
		res = -EINVAL;
		goto out;
	}
	vesafb_wait_for_task(tsk);

	if ((tsk->regs.eax & 0xffff) == 0x004f) {
		fb_edid_to_monspecs(tsk->buf, &info->monspecs);
		fb_videomode_to_modelist(info->monspecs.modedb,
				info->monspecs.modedb_len, &info->modelist);
		if (info->monspecs.vfmax && info->monspecs.hfmax) {
			/* If the maximum pixel clock wasn't specified in
			 * the EDID block, set it to 300 MHz. */
			if (info->monspecs.dclkmax == 0)
				info->monspecs.dclkmax = 300 * 1000000;
			info->monspecs.gtf = 1;
		} else {
			res = -EINVAL;
		}
	}

out:	kfree(tsk->buf);
	return res;
}

static void __init inline vesafb_vbe_getmonspecs(struct vesafb_task *tsk,
		                                 struct fb_info *info)
{
	struct fb_var_screeninfo var;
	int i;
	memset(&info->monspecs, 0, sizeof(struct fb_monspecs));

	/* If we didn't get all necessary data from the EDID block,
	 * mark it as incompatible with the GTF. */
	if (vesafb_vbe_getedid(tsk, info))
		info->monspecs.gtf = 0;

	/* Kernel command line overrides. */
	if (maxclk)
		info->monspecs.dclkmax = maxclk * 1000000;
	if (maxvf)
		info->monspecs.vfmax = maxvf;
	if (maxhf)
		info->monspecs.hfmax = maxhf * 1000;

	/* In case DDC transfers are not supported the user can provide
	 * monitor limits manually. Lower limits are set to "safe" values. */
	if (info->monspecs.gtf == 0 && maxclk && maxvf && maxhf) {
		info->monspecs.dclkmin = 0;
		info->monspecs.vfmin = 60;
		info->monspecs.hfmin = 29000;
		info->monspecs.gtf = 1;
	}

	if (info->monspecs.gtf) {
		printk(KERN_INFO
		       	"vesafb: monitor limits: vf = %d Hz, hf = %d kHz, "
			"clk = %d MHz\n", info->monspecs.vfmax,
			(int)(info->monspecs.hfmax / 1000),
			(int)(info->monspecs.dclkmax / 1000000));
		/* Add valid VESA video modes to our modelist. */
		for (i = 0; i < VESA_MODEDB_SIZE; i++) {
			fb_videomode_to_var(&var, (struct fb_videomode *)
					    &vesa_modes[i]);
			if (!fb_validate_mode(&var, info))
				fb_add_videomode((struct fb_videomode *)
						 &vesa_modes[i],
						 &info->modelist);
		}
	} else {
		/* Add all VESA video modes to our modelist. */
		fb_videomode_to_modelist((struct fb_videomode *)vesa_modes,
				 	 VESA_MODEDB_SIZE, &info->modelist);
		printk(KERN_INFO "vesafb: no monitor limits have been set\n");
	}
	return;
}

static int __init inline vesafb_vbe_init(struct fb_info *info)
{
	struct vesafb_task *tsk;
	int res = 0;

	vesafb_create_task(tsk);
	if (!tsk)
		return -EINVAL;
	if ((res = vesafb_vbe_getinfo(tsk)) != 0)
		goto out;
	if ((res = vesafb_vbe_getmodes(tsk)) != 0)
		goto out;
	if (pmi_setpal || ypan)
		vesafb_vbe_getpmi(tsk);
	vesafb_vbe_getmonspecs(tsk, info);
out:	kfree(tsk);
	return res;
}

static int __init decode_mode(u32 *xres, u32 *yres, u32 *bpp, u32 *refresh)
{
	int len = strlen(mode_option), i, err = 0;
	u8 res_specified = 0, bpp_specified = 0, refresh_specified = 0,
	   yres_specified = 0;

	for (i = len-1; i >= 0; i--) {
 		switch (mode_option[i]) {
		case '@':
    			len = i;
    			if (!refresh_specified && !bpp_specified &&
			    !yres_specified) {
				*refresh = simple_strtoul(&mode_option[i+1],
							  NULL, 0);
				refresh_specified = 1;
			} else
				goto out;
		    	break;
		case '-':
			len = i;
		    	if (!bpp_specified && !yres_specified) {
			    	*bpp = simple_strtoul(&mode_option[i+1],
						      NULL, 0);
				bpp_specified = 1;
		    	} else
				goto out;
		    	break;
		case 'x':
			if (!yres_specified) {
				*yres = simple_strtoul(&mode_option[i+1],
						       NULL, 0);
				yres_specified = 1;
		    	} else
				goto out;
		    	break;
		case '0'...'9':
			break;
		default:
			goto out;
	    	}
	}

	if (i < 0 && yres_specified) {
		*xres = simple_strtoul(mode_option, NULL, 0);
	   	res_specified = 1;
	}

out:	if (!res_specified || !yres_specified) {
		printk(KERN_ERR "vesafb: invalid resolution, "
				"%s not specified\n",
				(!res_specified) ? "width" : "height");
		err = -EINVAL;
	}

	return err;
}

static int __init vesafb_init_set_mode(struct fb_info *info)
{
	struct fb_videomode *fbmode;
	struct fb_videomode mode;
	int i, modeid, refresh = 0;
	u8 refresh_specified = 0;

	if (!mode_option)
		mode_option = CONFIG_FB_VESA_DEFAULT_MODE;

	if (vbemode > 0) {
		for (i = 0; i < vbe_modes_cnt; i++) {
			if (vbe_modes[i].mode_id == vbemode) {
				info->var.vmode = FB_VMODE_NONINTERLACED;
				info->var.sync = FB_SYNC_VERT_HIGH_ACT;
				vesafb_setup_var(&info->var, info,
						 &vbe_modes[i]);
				fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON,
					    60, &info->var, info);
				/* With pixclock set to 0, the default BIOS
				 * timings will be used in set_par(). */
				info->var.pixclock = 0;
				modeid = i;
				goto out;
			}
		}
		printk(KERN_INFO "specified VBE mode %d not found\n",
				 vbemode);
		vbemode = 0;
	}

	/* Decode the mode specified on the kernel command line. We save
	 * the depth into bits_per_pixel, which is wrong, but will work
	 * anyway. */
	if (decode_mode(&info->var.xres, &info->var.yres,
			&info->var.bits_per_pixel, &refresh))
		return -EINVAL;
	if (refresh)
		refresh_specified = 1;
	else
		refresh = 60;

	/* Look for a matching VBE mode. We can live if an exact match
	 * cannot be found. */
	modeid = vesafb_find_vbe_mode(info->var.xres, info->var.yres,
			              info->var.bits_per_pixel, 0);

	if (modeid == -1) {
		return -EINVAL;
	} else {
		info->var.vmode = FB_VMODE_NONINTERLACED;
		info->var.sync = FB_SYNC_VERT_HIGH_ACT;
		vesafb_setup_var(&info->var, info, &vbe_modes[modeid]);
	}
	if (vbe_ib.vbe_version < 0x0300) {
		fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, 60,
			    &info->var, info);
		goto out;
	}
	if (!gtf) {
		struct fb_videomode tmode;

		if (refresh_specified) {
			fb_var_to_videomode(&tmode, &info->var);
			tmode.refresh = refresh;
			fbmode = fb_find_nearest_mode(&tmode,
						      &info->modelist);
		} else
			fbmode = fb_find_best_mode(&info->var,
						   &info->modelist);

		if (fbmode->xres == info->var.xres &&
		    fbmode->yres == info->var.yres &&
		    !(fbmode->vmode & (FB_VMODE_INTERLACED | FB_VMODE_DOUBLE))
		    && (!refresh_specified ||
		    abs(refresh - fbmode->refresh) <= 5)) {
			fb_videomode_to_var(&info->var, fbmode);
			return modeid;
		}
	}
	i = FB_MAXTIMINGS;
	if (!info->monspecs.gtf)
		i = FB_IGNOREMON | FB_VSYNCTIMINGS;
	else if (refresh_specified)
		i = FB_VSYNCTIMINGS;
	if (!fb_get_mode(i, refresh, &info->var, info))
		goto out;
	if (info->monspecs.gtf &&
	    !fb_get_mode(FB_MAXTIMINGS, 0, &info->var, info))
		goto out;
	/* Use default refresh rate */
	printk(KERN_WARNING "vesafb: using default BIOS refresh rate\n");
	info->var.pixclock = 0;

out:
	fb_var_to_videomode(&mode, &info->var);
	fb_add_videomode(&mode, &info->modelist);
	return modeid;
}

static int __init vesafb_probe(struct platform_device *dev)
{
	char entry[16];
	struct fb_info *info;
	struct vesafb_mode_ib *mode = NULL;
	int err = 0, i, h;
	unsigned int size_vmode;
	unsigned int size_remap;
	unsigned int size_total;

	vesafb_info = info = framebuffer_alloc(sizeof(struct vesafb_par) +
			                       sizeof(u32) * 256, &dev->dev);
	if (!info)
	 	return -ENOMEM;

	if (vesafb_wait_for_thread()) {
		printk(KERN_ERR "vesafb: vesafb thread not running\n");
		framebuffer_release(info);
		return -EINVAL;
	}

	if (vesafb_vbe_init(info)) {
		printk(KERN_ERR "vesafb: vbe_init failed\n");
		err = -EINVAL;
		goto out;
	}

	vesafb_fix.ypanstep  = ypan     ? 1 : 0;
	vesafb_fix.ywrapstep = (ypan>1) ? 1 : 0;

	info->pseudo_palette = ((u8*)info->par + sizeof(struct vesafb_par));
	info->fbops = &vesafb_ops;
	info->var = vesafb_defined;
	info->fix = vesafb_fix;

	if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) {
		err = -ENXIO;
		goto out;
	}

	i = vesafb_init_set_mode(info);
	if (i < 0) {
		err = -EINVAL;
		goto out_cmap;
	} else
		mode = &vbe_modes[i];

	/* Find out how much IO memory is required for the mode with
	 * the highest resolution. */
	size_remap = 0;
	for (i = 0; i < vbe_modes_cnt; i++) {
		h = vbe_modes[i].bytes_per_scan_line * vbe_modes[i].y_res;
		if (h > size_remap)
			size_remap = h;
	}
	size_remap *= 2;

	/*   size_vmode -- that is the amount of memory needed for the
	 *                 used video mode, i.e. the minimum amount of
	 *                 memory we need. */
	if (mode != NULL) {
		size_vmode = info->var.yres * mode->bytes_per_scan_line;
	} else {
		size_vmode = info->var.yres * info->var.xres *
			     ((info->var.bits_per_pixel + 7) >> 3);
	}

	/*   size_total -- all video memory we have. Used for mtrr
	 *                 entries, ressource allocation and bounds
	 *                 checking. */
	size_total = vbe_ib.total_memory * 65536;
	if (vram_total)
		size_total = vram_total * 1024 * 1024;
	if (size_total < size_vmode)
		size_total = size_vmode;
	((struct vesafb_par*)(info->par))->mem_total = size_total;

	/*   size_remap -- the amount of video memory we are going to
	 *                 use for vesafb.  With modern cards it is no
	 *                 option to simply use size_total as th
	 *                 wastes plenty of kernel address space. */
	if (vram_remap)
		size_remap = vram_remap * 1024 * 1024;
	if (size_remap < size_vmode)
		size_remap = size_vmode;
	if (size_remap > size_total)
		size_remap = size_total;

	info->fix.smem_len = size_remap;
	info->fix.smem_start = mode->phys_base_ptr;

	/* We have to set it here, because when setup_var() was called,
	 * smem_len wasn't defined yet. */
	info->var.yres_virtual = info->fix.smem_len /
				 mode->bytes_per_scan_line;

	if (ypan && info->var.yres_virtual > info->var.yres) {
		printk(KERN_INFO "vesafb: scrolling: %s "
		       "using protected mode interface, "
		       "yres_virtual=%d\n",
		       (ypan > 1) ? "ywrap" : "ypan",info->var.yres_virtual);
	} else {
		printk(KERN_INFO "vesafb: scrolling: redraw\n");
		info->var.yres_virtual = info->var.yres;
		ypan = 0;
	}

	info->flags = FBINFO_FLAG_DEFAULT |
		(ypan) ? FBINFO_HWACCEL_YPAN : 0;

	if (!request_mem_region(info->fix.smem_start, size_total, "vesafb")) {
		printk(KERN_WARNING "vesafb: cannot reserve video memory at "
		       "0x%lx\n", info->fix.smem_start);
		/* We cannot make this fatal. Sometimes this comes from magic
		   spaces our resource handlers simply don't know about. */
	}

	info->screen_base = ioremap(info->fix.smem_start, info->fix.smem_len);

	if (!info->screen_base) {
		printk(KERN_ERR
		       "vesafb: abort, cannot ioremap video memory "
		       "0x%x @ 0x%lx\n",
		       info->fix.smem_len, info->fix.smem_start);
		err = -EIO;
		goto out_mem;
 	}

	/* Request failure does not faze us, as vgacon probably has this
	   region already (FIXME) */
	request_region(0x3c0, 32, "vesafb");

	if (mtrr && !(info->fix.smem_start & (PAGE_SIZE - 1))) {
		int temp_size = size_total;
		unsigned int type = 0;

		switch (mtrr) {
		case 1:
			type = MTRR_TYPE_UNCACHABLE;
			break;
		case 2:
			type = MTRR_TYPE_WRBACK;
			break;
		case 3:
			type = MTRR_TYPE_WRCOMB;
			break;
		case 4:
			type = MTRR_TYPE_WRTHROUGH;
			break;
		default:
			type = 0;
			break;
		}

		if (type) {
			int rc;

			/* Find the largest power-of-two */
			while (temp_size & (temp_size - 1))
				temp_size &= (temp_size - 1);

			/* Try and find a power of two to add */
			do {
				rc = mtrr_add(info->fix.smem_start,
					      temp_size, type, 1);
				temp_size >>= 1;
			} while (temp_size >= PAGE_SIZE && rc == -EINVAL);
  		}
  	}

	if (register_framebuffer(info) < 0) {
		printk(KERN_ERR
		       "vesafb: failed to register framebuffer device\n");
		err = -EINVAL;
		goto out_mem;
	}

  	printk(KERN_INFO "vesafb: framebuffer at 0x%lx, mapped to 0x%p, "
	       "using %dk, total %dk\n", info->fix.smem_start,
	       info->screen_base, size_remap/1024, size_total/1024);
	printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
	       info->fix.id);

	sprintf(entry, "fb%d", info->node);
	proc_mkdir(entry, 0);

	sprintf(entry, "fb%d/modes", info->node);
	create_proc_read_entry(entry, 0, 0, vesafb_read_proc_modes, NULL);

	sprintf(entry, "fb%d/vbe_info", info->node);
	create_proc_read_entry(entry, 0, 0, vesafb_read_proc_vbe_info, NULL);
	return 0;

out_mem:
	release_mem_region(info->fix.smem_start, size_total);
	if (!list_empty(&info->modelist))
		fb_destroy_modelist(&info->modelist);
	fb_destroy_modedb(info->monspecs.modedb);
out_cmap:
	fb_dealloc_cmap(&info->cmap);
out:
	framebuffer_release(info);
	vesafb_info = NULL;
	kfree(vbe_modes);
	vbe_modes = NULL;
	return err;
}

int __init vesafb_init(void)
{
	int ret;
#ifndef MODULE
	char *option = NULL;

	if (fb_get_options("vesafb", &option))
		return -ENODEV;
	vesafb_setup(option);
#endif
	ret = platform_driver_register(&vesafb_driver);

	if (!ret) {
		ret = platform_device_register(&vesafb_device);
		if (ret)
			platform_driver_unregister(&vesafb_driver);
	}
	return ret;
}

module_init(vesafb_init);

#ifdef MODULE
void __exit vesafb_exit(void)
{
	char entry[16];

	if (vesafb_info)
		unregister_framebuffer(vesafb_info);

	platform_device_unregister(&vesafb_device);
	platform_driver_unregister(&vesafb_driver);

	if (vesafb_info) {
		struct vesafb_par *par = (struct vesafb_par*)vesafb_info->par;

		sprintf(entry, "fb%d/modes", vesafb_info->node);
		remove_proc_entry(entry, NULL);

		sprintf(entry, "fb%d/vbe_info", vesafb_info->node);
		remove_proc_entry(entry, NULL);

		sprintf(entry, "fb%d", vesafb_info->node);
		remove_proc_entry(entry, NULL);

		iounmap(vesafb_info->screen_base);
		release_mem_region(vesafb_info->fix.smem_start,
				   par->mem_total);
		fb_dealloc_cmap(&vesafb_info->cmap);
		if (!list_empty(&vesafb_info->modelist))
			fb_destroy_modelist(&vesafb_info->modelist);
		fb_destroy_modedb(vesafb_info->monspecs.modedb);
		framebuffer_release(vesafb_info);
	}

	if (vbe_modes != NULL)
		kfree(vbe_modes);
}

module_exit(vesafb_exit);

static inline int param_get_scroll(char *buffer, struct kernel_param *kp)
{
	return 0;
}
static inline int param_set_scroll(const char *val, struct kernel_param *kp)
{
	ypan = 0;

	if (! strcmp(val, "redraw"))
		ypan = 0;
	else if (! strcmp(val, "ypan"))
		ypan = 1;
	else if (! strcmp(val, "ywrap"))
		ypan = 2;

	return 0;
}

#define param_check_scroll(name, p) __param_check(name, p, void);

module_param_named(scroll, ypan, scroll, 0);
MODULE_PARM_DESC(scroll,"Scrolling mode, set to 'redraw', 'ypan' or 'ywrap'");
module_param_named(vgapal, pmi_setpal, invbool, 0);
MODULE_PARM_DESC(vgapal,"bool: set palette using VGA registers");
module_param_named(pmipal, pmi_setpal, bool, 0);
MODULE_PARM_DESC(pmipal,"bool: set palette using PMI calls");
module_param_named(nomtrr, mtrr, invbool, 0);
MODULE_PARM_DESC(nomtrr,"bool: disable use of MTRR registers");
module_param(nocrtc, bool, 0);
MODULE_PARM_DESC(nocrtc,"bool: ignore CRTC timings when setting modes");
module_param(noedid, bool, 0);
MODULE_PARM_DESC(noedid,"bool: ignore EDID-provided monitor limits "
		        "when setting modes");
module_param(gtf, bool, 0);
MODULE_PARM_DESC(gtf,"bool: force use of VESA GTF to calculate mode timings");
module_param(vram_remap, uint, 0);
MODULE_PARM_DESC(vram_remap,"Set amount of video memory to be used [MiB]");
module_param(vram_total, uint, 0);
MODULE_PARM_DESC(vram_total,"Set total amount of video memoery [MiB]");
module_param(maxclk, ushort, 0);
MODULE_PARM_DESC(maxclk,"Maximum pixelclock [MHz], overrides EDID data");
module_param(maxhf, ushort, 0);
MODULE_PARM_DESC(maxhf,"Maximum horizontal frequency [kHz], "
		       "overrides EDID data");
module_param(maxvf, ushort, 0);
MODULE_PARM_DESC(maxvf,"Maximum vertical frequency [Hz], "
		       "overrides EDID data");
module_param_named(mode, mode_option, charp, 0);
MODULE_PARM_DESC(mode, "Specify resolution as "
		       "\"<xres>x<yres>[-<bpp>][@<refresh>]\"");
module_param(vbemode, ushort, 0);
MODULE_PARM_DESC(vbemode,"VBE mode number to set, overrides 'mode' setting");

#endif /* MODULE */

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michal Januszewski");
MODULE_DESCRIPTION("Framebuffer driver for VBE2.0+ compliant graphics boards");

