/*
 * drivers/video/cirrusfb.c - driver for Cirrus Logic chipsets
 *
 * Copyright 1999-2001 Jeff Garzik <jgarzik@pobox.com>
 *
 * Contributors (thanks, all!)
 *
 *	David Eger:
 *	Overhaul for Linux 2.6
 *
 *      Jeff Rugen:
 *      Major contributions;  Motorola PowerStack (PPC and PCI) support,
 *      GD54xx, 1280x1024 mode support, change MCLK based on VCLK.
 *
 *	Geert Uytterhoeven:
 *	Excellent code review.
 *
 *	Lars Hecking:
 *	Amiga updates and testing.
 *
 * Original cirrusfb author:  Frank Neumann
 *
 * Based on retz3fb.c and cirrusfb.c:
 *      Copyright (C) 1997 Jes Sorensen
 *      Copyright (C) 1996 Frank Neumann
 *
 ***************************************************************
 *
 * Format this code with GNU indent '-kr -i8 -pcs' options.
 *
 * 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.
 *
 */

#define CIRRUSFB_VERSION "2.0-pre2"

#include <linux/config.h>
#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/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/selection.h>
#include <asm/pgtable.h>

#ifdef CONFIG_ZORRO
#include <linux/zorro.h>
#endif
#ifdef CONFIG_PCI
#include <linux/pci.h>
#endif
#ifdef CONFIG_AMIGA
#include <asm/amigahw.h>
#endif
#ifdef CONFIG_PPC_PREP
#include <asm/processor.h>
#define isPReP (_machine == _MACH_prep)
#else
#define isPReP 0
#endif

#include "video/vga.h"
#include "video/cirrus.h"


/*****************************************************************
 *
 * debugging and utility macros
 *
 */

/* enable debug output? */
/* #define CIRRUSFB_DEBUG 1 */

/* disable runtime assertions? */
/* #define CIRRUSFB_NDEBUG */

/* debug output */
#ifdef CIRRUSFB_DEBUG
#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args)
#else
#define DPRINTK(fmt, args...)
#endif

/* debugging assertions */
#ifndef CIRRUSFB_NDEBUG
#define assert(expr) \
        if(!(expr)) { \
        printk( "Assertion failed! %s,%s,%s,line=%d\n",\
        #expr,__FILE__,__FUNCTION__,__LINE__); \
        }
#else
#define assert(expr)
#endif

#ifdef TRUE
#undef TRUE
#endif
#ifdef FALSE
#undef FALSE
#endif
#define TRUE  1
#define FALSE 0

#define MB_ (1024*1024)
#define KB_ (1024)

#define MAX_NUM_BOARDS 7


/*****************************************************************
 *
 * chipset information
 *
 */

/* board types */
typedef enum {
	BT_NONE = 0,
	BT_SD64,
	BT_PICCOLO,
	BT_PICASSO,
	BT_SPECTRUM,
	BT_PICASSO4,	/* GD5446 */
	BT_ALPINE,	/* GD543x/4x */
	BT_GD5480,
	BT_LAGUNA,	/* GD546x */
} cirrusfb_board_t;


/*
 * per-board-type information, used for enumerating and abstracting
 * chip-specific information
 * NOTE: MUST be in the same order as cirrusfb_board_t in order to
 * use direct indexing on this array
 * NOTE: '__initdata' cannot be used as some of this info
 * is required at runtime.  Maybe separate into an init-only and
 * a run-time table?
 */
static const struct cirrusfb_board_info_rec {
	char *name;		/* ASCII name of chipset */
	long maxclock[5];		/* maximum video clock */
	/* for  1/4bpp, 8bpp 15/16bpp, 24bpp, 32bpp - numbers from xorg code */
	unsigned init_sr07 : 1;	/* init SR07 during init_vgachip() */
	unsigned init_sr1f : 1; /* write SR1F during init_vgachip() */
	unsigned scrn_start_bit19 : 1; /* construct bit 19 of screen start address */

	/* initial SR07 value, then for each mode */
	unsigned char sr07;
	unsigned char sr07_1bpp;
	unsigned char sr07_1bpp_mux;
	unsigned char sr07_8bpp;
	unsigned char sr07_8bpp_mux;

	unsigned char sr1f;	/* SR1F VGA initial register value */
} cirrusfb_board_info[] = {
	[BT_SD64] = {
		.name			= "CL SD64",
		.maxclock		= {
			/* guess */
			/* the SD64/P4 have a higher max. videoclock */
			140000, 140000, 140000, 140000, 140000,
		},
		.init_sr07		= TRUE,
		.init_sr1f		= TRUE,
		.scrn_start_bit19	= TRUE,
		.sr07			= 0xF0,
		.sr07_1bpp		= 0xF0,
		.sr07_8bpp		= 0xF1,
		.sr1f			= 0x20
	},
	[BT_PICCOLO] = {
		.name			= "CL Piccolo",
		.maxclock		= {
			/* guess */
			90000, 90000, 90000, 90000, 90000
		},
		.init_sr07		= TRUE,
		.init_sr1f		= TRUE,
		.scrn_start_bit19	= FALSE,
		.sr07			= 0x80,
		.sr07_1bpp		= 0x80,
		.sr07_8bpp		= 0x81,
		.sr1f			= 0x22
	},
	[BT_PICASSO] = {
		.name			= "CL Picasso",
		.maxclock		= {
			/* guess */
			90000, 90000, 90000, 90000, 90000
		},
		.init_sr07		= TRUE,
		.init_sr1f		= TRUE,
		.scrn_start_bit19	= FALSE,
		.sr07			= 0x20,
		.sr07_1bpp		= 0x20,
		.sr07_8bpp		= 0x21,
		.sr1f			= 0x22
	},
	[BT_SPECTRUM] = {
		.name			= "CL Spectrum",
		.maxclock		= {
			/* guess */
			90000, 90000, 90000, 90000, 90000
		},
		.init_sr07		= TRUE,
		.init_sr1f		= TRUE,
		.scrn_start_bit19	= FALSE,
		.sr07			= 0x80,
		.sr07_1bpp		= 0x80,
		.sr07_8bpp		= 0x81,
		.sr1f			= 0x22
	},
	[BT_PICASSO4] = {
		.name			= "CL Picasso4",
		.maxclock		= {
			135100, 135100, 85500, 85500, 0
		},
		.init_sr07		= TRUE,
		.init_sr1f		= FALSE,
		.scrn_start_bit19	= TRUE,
		.sr07			= 0x20,
		.sr07_1bpp		= 0x20,
		.sr07_8bpp		= 0x21,
		.sr1f			= 0
	},
	[BT_ALPINE] = {
		.name			= "CL Alpine",
		.maxclock		= {
			/* for the GD5430.  GD5446 can do more... */
			85500, 85500, 50000, 28500, 0
		},
		.init_sr07		= TRUE,
		.init_sr1f		= TRUE,
		.scrn_start_bit19	= TRUE,
		.sr07			= 0xA0,
		.sr07_1bpp		= 0xA1,
		.sr07_1bpp_mux		= 0xA7,
		.sr07_8bpp		= 0xA1,
		.sr07_8bpp_mux		= 0xA7,
		.sr1f			= 0x1C
	},
	[BT_GD5480] = {
		.name			= "CL GD5480",
		.maxclock		= {
			135100, 200000, 200000, 135100, 135100
		},
		.init_sr07		= TRUE,
		.init_sr1f		= TRUE,
		.scrn_start_bit19	= TRUE,
		.sr07			= 0x10,
		.sr07_1bpp		= 0x11,
		.sr07_8bpp		= 0x11,
		.sr1f			= 0x1C
	},
	[BT_LAGUNA] = {
		.name			= "CL Laguna",
		.maxclock		= {
			/* guess */
			135100, 135100, 135100, 135100, 135100,
		},
		.init_sr07		= FALSE,
		.init_sr1f		= FALSE,
		.scrn_start_bit19	= TRUE,
	}
};


#ifdef CONFIG_PCI
#define CHIP(id, btype) \
	{ PCI_VENDOR_ID_CIRRUS, id, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (btype) }

static struct pci_device_id cirrusfb_pci_table[] = {
	CHIP( PCI_DEVICE_ID_CIRRUS_5436, BT_ALPINE ),
	CHIP( PCI_DEVICE_ID_CIRRUS_5434_8, BT_ALPINE ),
	CHIP( PCI_DEVICE_ID_CIRRUS_5434_4, BT_ALPINE ),
	CHIP( PCI_DEVICE_ID_CIRRUS_5430, BT_ALPINE ), /* GD-5440 is same id */
	CHIP( PCI_DEVICE_ID_CIRRUS_7543, BT_ALPINE ),
	CHIP( PCI_DEVICE_ID_CIRRUS_7548, BT_ALPINE ),
	CHIP( PCI_DEVICE_ID_CIRRUS_5480, BT_GD5480 ), /* MacPicasso likely */
	CHIP( PCI_DEVICE_ID_CIRRUS_5446, BT_PICASSO4 ), /* Picasso 4 is 5446 */
	CHIP( PCI_DEVICE_ID_CIRRUS_5462, BT_LAGUNA ), /* CL Laguna */
	CHIP( PCI_DEVICE_ID_CIRRUS_5464, BT_LAGUNA ), /* CL Laguna 3D */
	CHIP( PCI_DEVICE_ID_CIRRUS_5465, BT_LAGUNA ), /* CL Laguna 3DA*/
	{ 0, }
};
MODULE_DEVICE_TABLE(pci, cirrusfb_pci_table);
#undef CHIP
#endif /* CONFIG_PCI */


#ifdef CONFIG_ZORRO
static const struct zorro_device_id cirrusfb_zorro_table[] = {
	{
		.id		= ZORRO_PROD_HELFRICH_SD64_RAM,
		.driver_data	= BT_SD64,
	}, {
		.id		= ZORRO_PROD_HELFRICH_PICCOLO_RAM,
		.driver_data	= BT_PICCOLO,
	}, {
		.id		= ZORRO_PROD_VILLAGE_TRONIC_PICASSO_II_II_PLUS_RAM,
		.driver_data	= BT_PICASSO,
	}, {
		.id		= ZORRO_PROD_GVP_EGS_28_24_SPECTRUM_RAM,
		.driver_data	= BT_SPECTRUM,
	}, {
		.id		= ZORRO_PROD_VILLAGE_TRONIC_PICASSO_IV_Z3,
		.driver_data	= BT_PICASSO4,
	},
	{ 0 }
};

static const struct {
	zorro_id id2;
	unsigned long size;
} cirrusfb_zorro_table2[] = {
	[BT_SD64] = {
		.id2	= ZORRO_PROD_HELFRICH_SD64_REG,
		.size	= 0x400000
	},
	[BT_PICCOLO] = {
		.id2	= ZORRO_PROD_HELFRICH_PICCOLO_REG,
		.size	= 0x200000
	},
	[BT_PICASSO] = {
		.id2	= ZORRO_PROD_VILLAGE_TRONIC_PICASSO_II_II_PLUS_REG,
		.size	= 0x200000
	},
	[BT_SPECTRUM] = {
		.id2	= ZORRO_PROD_GVP_EGS_28_24_SPECTRUM_REG,
		.size	= 0x200000
	},
	[BT_PICASSO4] = {
		.id2	= 0,
		.size	= 0x400000
	}
};
#endif /* CONFIG_ZORRO */


struct cirrusfb_regs {
	__u32 line_length;	/* in BYTES! */
	__u32 visual;
	__u32 type;

	long freq;
	long nom;
	long den;
	long div;
	long multiplexing;
	long mclk;
	long divMCLK;

	long HorizRes;		/* The x resolution in pixel */
	long HorizTotal;
	long HorizDispEnd;
	long HorizBlankStart;
	long HorizBlankEnd;
	long HorizSyncStart;
	long HorizSyncEnd;

	long VertRes;		/* the physical y resolution in scanlines */
	long VertTotal;
	long VertDispEnd;
	long VertSyncStart;
	long VertSyncEnd;
	long VertBlankStart;
	long VertBlankEnd;
};



#ifdef CIRRUSFB_DEBUG
typedef enum {
        CRT,
        SEQ
} cirrusfb_dbg_reg_class_t;
#endif                          /* CIRRUSFB_DEBUG */




/* info about board */
struct cirrusfb_info {
	struct fb_info *info;

	u8 __iomem *fbmem;
	u8 __iomem *regbase;
	u8 __iomem *mem;
	unsigned long size;
	cirrusfb_board_t btype;
	unsigned char SFR;	/* Shadow of special function register */

	unsigned long fbmem_phys;
	unsigned long fbregs_phys;

	struct cirrusfb_regs currentmode;
	int blank_mode;

	u32	pseudo_palette[16];
	struct { u8 red, green, blue, pad; } palette[256];

#ifdef CONFIG_ZORRO
	struct zorro_dev *zdev;
#endif
#ifdef CONFIG_PCI
	struct pci_dev *pdev;
#endif
	void (*unmap)(struct cirrusfb_info *cinfo);
};


static unsigned cirrusfb_def_mode = 1;
static int noaccel = 0;

/*
 *    Predefined Video Modes
 */

static const struct {
	const char *name;
	struct fb_var_screeninfo var;
} cirrusfb_predefined[] = {
	{
		/* autodetect mode */
		.name	= "Autodetect",
	}, {
		/* 640x480, 31.25 kHz, 60 Hz, 25 MHz PixClock */
		.name	= "640x480",
		.var	= {
			.xres		= 640,
			.yres		= 480,
			.xres_virtual	= 640,
			.yres_virtual	= 480,
			.bits_per_pixel	= 8,
			.red		= { .length = 8 },
			.green		= { .length = 8 },
			.blue		= { .length = 8 },
			.width		= -1,
			.height		= -1,
			.pixclock	= 40000,
			.left_margin	= 48,
			.right_margin	= 16,
			.upper_margin	= 32,
			.lower_margin	= 8,
			.hsync_len	= 96,
			.vsync_len	= 4,
			.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
			.vmode		= FB_VMODE_NONINTERLACED
		 }
	}, {
		/* 800x600, 48 kHz, 76 Hz, 50 MHz PixClock */
		.name	= "800x600",
		.var	= {
			.xres		= 800,
			.yres		= 600,
			.xres_virtual	= 800,
			.yres_virtual	= 600,
			.bits_per_pixel	= 8,
			.red		= { .length = 8 },
			.green		= { .length = 8 },
			.blue		= { .length = 8 },
			.width		= -1,
			.height		= -1,
			.pixclock	= 20000,
			.left_margin	= 128,
			.right_margin	= 16,
			.upper_margin	= 24,
			.lower_margin	= 2,
			.hsync_len	= 96,
			.vsync_len	= 6,
			.vmode		= FB_VMODE_NONINTERLACED
		 }
	}, {
		/*
		 * Modeline from XF86Config:
		 * Mode "1024x768" 80  1024 1136 1340 1432  768 770 774 805
		 */
		/* 1024x768, 55.8 kHz, 70 Hz, 80 MHz PixClock */
		.name	= "1024x768",
		.var	= {
			.xres		= 1024,
			.yres		= 768,
			.xres_virtual	= 1024,
			.yres_virtual	= 768,
			.bits_per_pixel	= 8,
			.red		= { .length = 8 },
			.green		= { .length = 8 },
			.blue		= { .length = 8 },
			.width		= -1,
			.height		= -1,
			.pixclock	= 12500,
			.left_margin	= 144,
			.right_margin	= 32,
			.upper_margin	= 30,
			.lower_margin	= 2,
			.hsync_len	= 192,
			.vsync_len	= 6,
			.vmode		= FB_VMODE_NONINTERLACED
		}
	}
};

#define NUM_TOTAL_MODES    ARRAY_SIZE(cirrusfb_predefined)

/****************************************************************************/
/**** BEGIN PROTOTYPES ******************************************************/


/*--- Interface used by the world ------------------------------------------*/
static int cirrusfb_init (void);
#ifndef MODULE
static int cirrusfb_setup (char *options);
#endif

static int cirrusfb_open (struct fb_info *info, int user);
static int cirrusfb_release (struct fb_info *info, int user);
static int cirrusfb_setcolreg (unsigned regno, unsigned red, unsigned green,
			       unsigned blue, unsigned transp,
			       struct fb_info *info);
static int cirrusfb_check_var (struct fb_var_screeninfo *var,
			       struct fb_info *info);
static int cirrusfb_set_par (struct fb_info *info);
static int cirrusfb_pan_display (struct fb_var_screeninfo *var,
				 struct fb_info *info);
static int cirrusfb_blank (int blank_mode, struct fb_info *info);
static void cirrusfb_fillrect (struct fb_info *info, const struct fb_fillrect *region);
static void cirrusfb_copyarea(struct fb_info *info, const struct fb_copyarea *area);
static void cirrusfb_imageblit(struct fb_info *info, const struct fb_image *image);

/* function table of the above functions */
static struct fb_ops cirrusfb_ops = {
	.owner		= THIS_MODULE,
	.fb_open	= cirrusfb_open,
	.fb_release	= cirrusfb_release,
	.fb_setcolreg	= cirrusfb_setcolreg,
	.fb_check_var	= cirrusfb_check_var,
	.fb_set_par	= cirrusfb_set_par,
	.fb_pan_display = cirrusfb_pan_display,
	.fb_blank	= cirrusfb_blank,
	.fb_fillrect	= cirrusfb_fillrect,
	.fb_copyarea	= cirrusfb_copyarea,
	.fb_imageblit	= cirrusfb_imageblit,
};

/*--- Hardware Specific Routines -------------------------------------------*/
static int cirrusfb_decode_var (const struct fb_var_screeninfo *var,
				struct cirrusfb_regs *regs,
				const struct fb_info *info);
/*--- Internal routines ----------------------------------------------------*/
static void init_vgachip (struct cirrusfb_info *cinfo);
static void switch_monitor (struct cirrusfb_info *cinfo, int on);
static void WGen (const struct cirrusfb_info *cinfo,
		  int regnum, unsigned char val);
static unsigned char RGen (const struct cirrusfb_info *cinfo, int regnum);
static void AttrOn (const struct cirrusfb_info *cinfo);
static void WHDR (const struct cirrusfb_info *cinfo, unsigned char val);
static void WSFR (struct cirrusfb_info *cinfo, unsigned char val);
static void WSFR2 (struct cirrusfb_info *cinfo, unsigned char val);
static void WClut (struct cirrusfb_info *cinfo, unsigned char regnum, unsigned char red,
		   unsigned char green,
		   unsigned char blue);
#if 0
static void RClut (struct cirrusfb_info *cinfo, unsigned char regnum, unsigned char *red,
		   unsigned char *green,
		   unsigned char *blue);
#endif
static void cirrusfb_WaitBLT (u8 __iomem *regbase);
static void cirrusfb_BitBLT (u8 __iomem *regbase, int bits_per_pixel,
			     u_short curx, u_short cury,
			     u_short destx, u_short desty,
			     u_short width, u_short height,
			     u_short line_length);
static void cirrusfb_RectFill (u8 __iomem *regbase, int bits_per_pixel,
			       u_short x, u_short y,
			       u_short width, u_short height,
			       u_char color, u_short line_length);

static void bestclock (long freq, long *best,
		       long *nom, long *den,
		       long *div, long maxfreq);

#ifdef CIRRUSFB_DEBUG
static void cirrusfb_dump (void);
static void cirrusfb_dbg_reg_dump (caddr_t regbase);
static void cirrusfb_dbg_print_regs (caddr_t regbase, cirrusfb_dbg_reg_class_t reg_class,...);
static void cirrusfb_dbg_print_byte (const char *name, unsigned char val);
#endif /* CIRRUSFB_DEBUG */

/*** END   PROTOTYPES ********************************************************/
/*****************************************************************************/
/*** BEGIN Interface Used by the World ***************************************/

static int opencount = 0;

/*--- Open /dev/fbx ---------------------------------------------------------*/
static int cirrusfb_open (struct fb_info *info, int user)
{
	if (opencount++ == 0)
		switch_monitor (info->par, 1);
	return 0;
}

/*--- Close /dev/fbx --------------------------------------------------------*/
static int cirrusfb_release (struct fb_info *info, int user)
{
	if (--opencount == 0)
		switch_monitor (info->par, 0);
	return 0;
}

/**** END   Interface used by the World *************************************/
/****************************************************************************/
/**** BEGIN Hardware specific Routines **************************************/

/* Get a good MCLK value */
static long cirrusfb_get_mclk (long freq, int bpp, long *div)
{
	long mclk;

	assert (div != NULL);

	/* Calculate MCLK, in case VCLK is high enough to require > 50MHz.
	 * Assume a 64-bit data path for now.  The formula is:
	 * ((B * PCLK * 2)/W) * 1.2
	 * B = bytes per pixel, PCLK = pixclock, W = data width in bytes */
	mclk = ((bpp / 8) * freq * 2) / 4;
	mclk = (mclk * 12) / 10;
	if (mclk < 50000)
		mclk = 50000;
	DPRINTK ("Use MCLK of %ld kHz\n", mclk);

	/* Calculate value for SR1F.  Multiply by 2 so we can round up. */
	mclk = ((mclk * 16) / 14318);
	mclk = (mclk + 1) / 2;
	DPRINTK ("Set SR1F[5:0] to 0x%lx\n", mclk);

	/* Determine if we should use MCLK instead of VCLK, and if so, what we
	   * should divide it by to get VCLK */
	switch (freq) {
	case 24751 ... 25249:
		*div = 2;
		DPRINTK ("Using VCLK = MCLK/2\n");
		break;
	case 49501 ... 50499:
		*div = 1;
		DPRINTK ("Using VCLK = MCLK\n");
		break;
	default:
		*div = 0;
		break;
	}

	return mclk;
}

static int cirrusfb_check_var(struct fb_var_screeninfo *var,
			      struct fb_info *info)
{
	struct cirrusfb_info *cinfo = info->par;
	int nom, den;		/* translyting from pixels->bytes */
	int yres, i;
	static struct { int xres, yres; } modes[] =
	{ { 1600, 1280 },
	  { 1280, 1024 },
	  { 1024, 768 },
	  { 800, 600 },
	  { 640, 480 },
	  { -1, -1 } };

	switch (var->bits_per_pixel) {
	case 0 ... 1:
		var->bits_per_pixel = 1;
		nom = 4;
		den = 8;
		break;		/* 8 pixel per byte, only 1/4th of mem usable */
	case 2 ... 8:
		var->bits_per_pixel = 8;
		nom = 1;
		den = 1;
		break;		/* 1 pixel == 1 byte */
	case 9 ... 16:
		var->bits_per_pixel = 16;
		nom = 2;
		den = 1;
		break;		/* 2 bytes per pixel */
	case 17 ... 24:
		var->bits_per_pixel = 24;
		nom = 3;
		den = 1;
		break;		/* 3 bytes per pixel */
	case 25 ... 32:
		var->bits_per_pixel = 32;
		nom = 4;
		den = 1;
		break;		/* 4 bytes per pixel */
	default:
		printk ("cirrusfb: mode %dx%dx%d rejected...color depth not supported.\n",
			var->xres, var->yres, var->bits_per_pixel);
		DPRINTK ("EXIT - EINVAL error\n");
		return -EINVAL;
	}

	if (var->xres * nom / den * var->yres > cinfo->size) {
		printk ("cirrusfb: mode %dx%dx%d rejected...resolution too high to fit into video memory!\n",
			var->xres, var->yres, var->bits_per_pixel);
		DPRINTK ("EXIT - EINVAL error\n");
		return -EINVAL;
	}

	/* use highest possible virtual resolution */
	if (var->xres_virtual == -1 &&
	    var->yres_virtual == -1) {
		printk ("cirrusfb: using maximum available virtual resolution\n");
		for (i = 0; modes[i].xres != -1; i++) {
			if (modes[i].xres * nom / den * modes[i].yres < cinfo->size / 2)
				break;
		}
		if (modes[i].xres == -1) {
			printk ("cirrusfb: could not find a virtual resolution that fits into video memory!!\n");
			DPRINTK ("EXIT - EINVAL error\n");
			return -EINVAL;
		}
		var->xres_virtual = modes[i].xres;
		var->yres_virtual = modes[i].yres;

		printk ("cirrusfb: virtual resolution set to maximum of %dx%d\n",
			var->xres_virtual, var->yres_virtual);
	}

	if (var->xres_virtual < var->xres)
		var->xres_virtual = var->xres;
	if (var->yres_virtual < var->yres)
		var->yres_virtual = var->yres;

	if (var->xoffset < 0)
		var->xoffset = 0;
	if (var->yoffset < 0)
		var->yoffset = 0;

	/* truncate xoffset and yoffset to maximum if too high */
	if (var->xoffset > var->xres_virtual - var->xres)
		var->xoffset = var->xres_virtual - var->xres - 1;
	if (var->yoffset > var->yres_virtual - var->yres)
		var->yoffset = var->yres_virtual - var->yres - 1;

	switch (var->bits_per_pixel) {
	case 1:
		var->red.offset = 0;
		var->red.length = 1;
		var->green.offset = 0;
		var->green.length = 1;
		var->blue.offset = 0;
		var->blue.length = 1;
		break;

	case 8:
		var->red.offset = 0;
		var->red.length = 6;
		var->green.offset = 0;
		var->green.length = 6;
		var->blue.offset = 0;
		var->blue.length = 6;
		break;

	case 16:
		if(isPReP) {
			var->red.offset = 2;
			var->green.offset = -3;
			var->blue.offset = 8;
		} else {
			var->red.offset = 10;
			var->green.offset = 5;
			var->blue.offset = 0;
		}
		var->red.length = 5;
		var->green.length = 5;
		var->blue.length = 5;
		break;

	case 24:
		if(isPReP) {
			var->red.offset = 8;
			var->green.offset = 16;
			var->blue.offset = 24;
		} else {
			var->red.offset = 16;
			var->green.offset = 8;
			var->blue.offset = 0;
		}
		var->red.length = 8;
		var->green.length = 8;
		var->blue.length = 8;
		break;

	case 32:
		if(isPReP) {
			var->red.offset = 8;
			var->green.offset = 16;
			var->blue.offset = 24;
		} else {
			var->red.offset = 16;
			var->green.offset = 8;
			var->blue.offset = 0;
		}
		var->red.length = 8;
		var->green.length = 8;
		var->blue.length = 8;
		break;

	default:
		DPRINTK("Unsupported bpp size: %d\n", var->bits_per_pixel);
		assert (FALSE);
		/* should never occur */
		break;
	}

	var->red.msb_right =
	    var->green.msb_right =
	    var->blue.msb_right =
	    var->transp.offset =
	    var->transp.length =
	    var->transp.msb_right = 0;

	yres = var->yres;
	if (var->vmode & FB_VMODE_DOUBLE)
		yres *= 2;
	else if (var->vmode & FB_VMODE_INTERLACED)
		yres = (yres + 1) / 2;

	if (yres >= 1280) {
		printk (KERN_WARNING "cirrusfb: ERROR: VerticalTotal >= 1280; special treatment required! (TODO)\n");
		DPRINTK ("EXIT - EINVAL error\n");
		return -EINVAL;
	}

	return 0;
}

static int cirrusfb_decode_var (const struct fb_var_screeninfo *var,
				struct cirrusfb_regs *regs,
				const struct fb_info *info)
{
	long freq;
	long maxclock;
	int maxclockidx = 0;
	struct cirrusfb_info *cinfo = info->par;
	int xres, hfront, hsync, hback;
	int yres, vfront, vsync, vback;

	switch(var->bits_per_pixel) {
	case 1:
		regs->line_length = var->xres_virtual / 8;
		regs->visual = FB_VISUAL_MONO10;
		maxclockidx = 0;
		break;

	case 8:
		regs->line_length = var->xres_virtual;
		regs->visual = FB_VISUAL_PSEUDOCOLOR;
		maxclockidx = 1;
		break;

	case 16:
		regs->line_length = var->xres_virtual * 2;
		regs->visual = FB_VISUAL_DIRECTCOLOR;
		maxclockidx = 2;
		break;

	case 24:
		regs->line_length = var->xres_virtual * 3;
		regs->visual = FB_VISUAL_DIRECTCOLOR;
		maxclockidx = 3;
		break;

	case 32:
		regs->line_length = var->xres_virtual * 4;
		regs->visual = FB_VISUAL_DIRECTCOLOR;
		maxclockidx = 4;
		break;

	default:
		DPRINTK("Unsupported bpp size: %d\n", var->bits_per_pixel);
		assert (FALSE);
		/* should never occur */
		break;
	}

	regs->type = FB_TYPE_PACKED_PIXELS;

	/* convert from ps to kHz */
	freq = 1000000000 / var->pixclock;

	DPRINTK ("desired pixclock: %ld kHz\n", freq);

	maxclock = cirrusfb_board_info[cinfo->btype].maxclock[maxclockidx];
	regs->multiplexing = 0;

	/* If the frequency is greater than we can support, we might be able
	 * to use multiplexing for the video mode */
	if (freq > maxclock) {
		switch (cinfo->btype) {
		case BT_ALPINE:
		case BT_GD5480:
			regs->multiplexing = 1;
			break;

		default:
			printk (KERN_WARNING "cirrusfb: ERROR: Frequency greater than maxclock (%ld kHz)\n", maxclock);
			DPRINTK ("EXIT - return -EINVAL\n");
			return -EINVAL;
		}
	}
#if 0
	/* TODO: If we have a 1MB 5434, we need to put ourselves in a mode where
	 * the VCLK is double the pixel clock. */
	switch (var->bits_per_pixel) {
	case 16:
	case 32:
		if (regs->HorizRes <= 800)
			freq /= 2;	/* Xbh has this type of clock for 32-bit */
		break;
	}
#endif

	bestclock (freq, &regs->freq, &regs->nom, &regs->den, &regs->div,
		   maxclock);
	regs->mclk = cirrusfb_get_mclk (freq, var->bits_per_pixel, &regs->divMCLK);

	xres = var->xres;
	hfront = var->right_margin;
	hsync = var->hsync_len;
	hback = var->left_margin;

	yres = var->yres;
	vfront = var->lower_margin;
	vsync = var->vsync_len;
	vback = var->upper_margin;

	if (var->vmode & FB_VMODE_DOUBLE) {
		yres *= 2;
		vfront *= 2;
		vsync *= 2;
		vback *= 2;
	} else if (var->vmode & FB_VMODE_INTERLACED) {
		yres = (yres + 1) / 2;
		vfront = (vfront + 1) / 2;
		vsync = (vsync + 1) / 2;
		vback = (vback + 1) / 2;
	}
	regs->HorizRes = xres;
	regs->HorizTotal = (xres + hfront + hsync + hback) / 8 - 5;
	regs->HorizDispEnd = xres / 8 - 1;
	regs->HorizBlankStart = xres / 8;
	regs->HorizBlankEnd = regs->HorizTotal + 5;	/* does not count with "-5" */
	regs->HorizSyncStart = (xres + hfront) / 8 + 1;
	regs->HorizSyncEnd = (xres + hfront + hsync) / 8 + 1;

	regs->VertRes = yres;
	regs->VertTotal = yres + vfront + vsync + vback - 2;
	regs->VertDispEnd = yres - 1;
	regs->VertBlankStart = yres;
	regs->VertBlankEnd = regs->VertTotal;
	regs->VertSyncStart = yres + vfront - 1;
	regs->VertSyncEnd = yres + vfront + vsync - 1;

	if (regs->VertRes >= 1024) {
		regs->VertTotal /= 2;
		regs->VertSyncStart /= 2;
		regs->VertSyncEnd /= 2;
		regs->VertDispEnd /= 2;
	}
	if (regs->multiplexing) {
		regs->HorizTotal /= 2;
		regs->HorizSyncStart /= 2;
		regs->HorizSyncEnd /= 2;
		regs->HorizDispEnd /= 2;
	}

	return 0;
}


static void cirrusfb_set_mclk (const struct cirrusfb_info *cinfo, int val, int div)
{
	assert (cinfo != NULL);

	if (div == 2) {
		/* VCLK = MCLK/2 */
		unsigned char old = vga_rseq (cinfo->regbase, CL_SEQR1E);
		vga_wseq (cinfo->regbase, CL_SEQR1E, old | 0x1);
		vga_wseq (cinfo->regbase, CL_SEQR1F, 0x40 | (val & 0x3f));
	} else if (div == 1) {
		/* VCLK = MCLK */
		unsigned char old = vga_rseq (cinfo->regbase, CL_SEQR1E);
		vga_wseq (cinfo->regbase, CL_SEQR1E, old & ~0x1);
		vga_wseq (cinfo->regbase, CL_SEQR1F, 0x40 | (val & 0x3f));
	} else {
		vga_wseq (cinfo->regbase, CL_SEQR1F, val & 0x3f);
	}
}

/*************************************************************************
	cirrusfb_set_par_foo()

	actually writes the values for a new video mode into the hardware,
**************************************************************************/
static int cirrusfb_set_par_foo (struct fb_info *info)
{
	struct cirrusfb_info *cinfo = info->par;
	struct fb_var_screeninfo *var = &info->var;
	struct cirrusfb_regs regs;
	u8 __iomem *regbase = cinfo->regbase;
	unsigned char tmp;
	int offset = 0, err;
	const struct cirrusfb_board_info_rec *bi;

	DPRINTK ("ENTER\n");
	DPRINTK ("Requested mode: %dx%dx%d\n",
	       var->xres, var->yres, var->bits_per_pixel);
	DPRINTK ("pixclock: %d\n", var->pixclock);

	init_vgachip (cinfo);

	err = cirrusfb_decode_var(var, &regs, info);
	if(err) {
		/* should never happen */
		DPRINTK("mode change aborted.  invalid var.\n");
		return -EINVAL;
	}

	bi = &cirrusfb_board_info[cinfo->btype];


	/* unlock register VGA_CRTC_H_TOTAL..CRT7 */
	vga_wcrt (regbase, VGA_CRTC_V_SYNC_END, 0x20);	/* previously: 0x00) */

	/* if debugging is enabled, all parameters get output before writing */
	DPRINTK ("CRT0: %ld\n", regs.HorizTotal);
	vga_wcrt (regbase, VGA_CRTC_H_TOTAL, regs.HorizTotal);

	DPRINTK ("CRT1: %ld\n", regs.HorizDispEnd);
	vga_wcrt (regbase, VGA_CRTC_H_DISP, regs.HorizDispEnd);

	DPRINTK ("CRT2: %ld\n", regs.HorizBlankStart);
	vga_wcrt (regbase, VGA_CRTC_H_BLANK_START, regs.HorizBlankStart);

	DPRINTK ("CRT3: 128+%ld\n", regs.HorizBlankEnd % 32);	/*  + 128: Compatible read */
	vga_wcrt (regbase, VGA_CRTC_H_BLANK_END, 128 + (regs.HorizBlankEnd % 32));

	DPRINTK ("CRT4: %ld\n", regs.HorizSyncStart);
	vga_wcrt (regbase, VGA_CRTC_H_SYNC_START, regs.HorizSyncStart);

	tmp = regs.HorizSyncEnd % 32;
	if (regs.HorizBlankEnd & 32)
		tmp += 128;
	DPRINTK ("CRT5: %d\n", tmp);
	vga_wcrt (regbase, VGA_CRTC_H_SYNC_END, tmp);

	DPRINTK ("CRT6: %ld\n", regs.VertTotal & 0xff);
	vga_wcrt (regbase, VGA_CRTC_V_TOTAL, (regs.VertTotal & 0xff));

	tmp = 16;		/* LineCompare bit #9 */
	if (regs.VertTotal & 256)
		tmp |= 1;
	if (regs.VertDispEnd & 256)
		tmp |= 2;
	if (regs.VertSyncStart & 256)
		tmp |= 4;
	if (regs.VertBlankStart & 256)
		tmp |= 8;
	if (regs.VertTotal & 512)
		tmp |= 32;
	if (regs.VertDispEnd & 512)
		tmp |= 64;
	if (regs.VertSyncStart & 512)
		tmp |= 128;
	DPRINTK ("CRT7: %d\n", tmp);
	vga_wcrt (regbase, VGA_CRTC_OVERFLOW, tmp);

	tmp = 0x40;		/* LineCompare bit #8 */
	if (regs.VertBlankStart & 512)
		tmp |= 0x20;
	if (var->vmode & FB_VMODE_DOUBLE)
		tmp |= 0x80;
	DPRINTK ("CRT9: %d\n", tmp);
	vga_wcrt (regbase, VGA_CRTC_MAX_SCAN, tmp);

	DPRINTK ("CRT10: %ld\n", regs.VertSyncStart & 0xff);
	vga_wcrt (regbase, VGA_CRTC_V_SYNC_START, (regs.VertSyncStart & 0xff));

	DPRINTK ("CRT11: 64+32+%ld\n", regs.VertSyncEnd % 16);
	vga_wcrt (regbase, VGA_CRTC_V_SYNC_END, (regs.VertSyncEnd % 16 + 64 + 32));

	DPRINTK ("CRT12: %ld\n", regs.VertDispEnd & 0xff);
	vga_wcrt (regbase, VGA_CRTC_V_DISP_END, (regs.VertDispEnd & 0xff));

	DPRINTK ("CRT15: %ld\n", regs.VertBlankStart & 0xff);
	vga_wcrt (regbase, VGA_CRTC_V_BLANK_START, (regs.VertBlankStart & 0xff));

	DPRINTK ("CRT16: %ld\n", regs.VertBlankEnd & 0xff);
	vga_wcrt (regbase, VGA_CRTC_V_BLANK_END, (regs.VertBlankEnd & 0xff));

	DPRINTK ("CRT18: 0xff\n");
	vga_wcrt (regbase, VGA_CRTC_LINE_COMPARE, 0xff);

	tmp = 0;
	if (var->vmode & FB_VMODE_INTERLACED)
		tmp |= 1;
	if (regs.HorizBlankEnd & 64)
		tmp |= 16;
	if (regs.HorizBlankEnd & 128)
		tmp |= 32;
	if (regs.VertBlankEnd & 256)
		tmp |= 64;
	if (regs.VertBlankEnd & 512)
		tmp |= 128;

	DPRINTK ("CRT1a: %d\n", tmp);
	vga_wcrt (regbase, CL_CRT1A, tmp);

	/* set VCLK0 */
	/* hardware RefClock: 14.31818 MHz */
	/* formula: VClk = (OSC * N) / (D * (1+P)) */
	/* Example: VClk = (14.31818 * 91) / (23 * (1+1)) = 28.325 MHz */

	vga_wseq (regbase, CL_SEQRB, regs.nom);
	tmp = regs.den << 1;
	if (regs.div != 0)
		tmp |= 1;

	if ((cinfo->btype == BT_SD64) ||
	    (cinfo->btype == BT_ALPINE) ||
	    (cinfo->btype == BT_GD5480))
		tmp |= 0x80;	/* 6 bit denom; ONLY 5434!!! (bugged me 10 days) */

	DPRINTK ("CL_SEQR1B: %ld\n", (long) tmp);
	vga_wseq (regbase, CL_SEQR1B, tmp);

	if (regs.VertRes >= 1024)
		/* 1280x1024 */
		vga_wcrt (regbase, VGA_CRTC_MODE, 0xc7);
	else
		/* mode control: VGA_CRTC_START_HI enable, ROTATE(?), 16bit
		 * address wrap, no compat. */
		vga_wcrt (regbase, VGA_CRTC_MODE, 0xc3);

/* HAEH?        vga_wcrt (regbase, VGA_CRTC_V_SYNC_END, 0x20);  * previously: 0x00  unlock VGA_CRTC_H_TOTAL..CRT7 */

	/* don't know if it would hurt to also program this if no interlaced */
	/* mode is used, but I feel better this way.. :-) */
	if (var->vmode & FB_VMODE_INTERLACED)
		vga_wcrt (regbase, VGA_CRTC_REGS, regs.HorizTotal / 2);
	else
		vga_wcrt (regbase, VGA_CRTC_REGS, 0x00);	/* interlace control */

	vga_wseq (regbase, VGA_SEQ_CHARACTER_MAP, 0);

	/* adjust horizontal/vertical sync type (low/high) */
	tmp = 0x03;		/* enable display memory & CRTC I/O address for color mode */
	if (var->sync & FB_SYNC_HOR_HIGH_ACT)
		tmp |= 0x40;
	if (var->sync & FB_SYNC_VERT_HIGH_ACT)
		tmp |= 0x80;
	WGen (cinfo, VGA_MIS_W, tmp);

	vga_wcrt (regbase, VGA_CRTC_PRESET_ROW, 0);	/* Screen A Preset Row-Scan register */
	vga_wcrt (regbase, VGA_CRTC_CURSOR_START, 0);	/* text cursor on and start line */
	vga_wcrt (regbase, VGA_CRTC_CURSOR_END, 31);	/* text cursor end line */

	/******************************************************
	 *
	 * 1 bpp
	 *
	 */

	/* programming for different color depths */
	if (var->bits_per_pixel == 1) {
		DPRINTK ("cirrusfb: preparing for 1 bit deep display\n");
		vga_wgfx (regbase, VGA_GFX_MODE, 0);	/* mode register */

		/* SR07 */
		switch (cinfo->btype) {
		case BT_SD64:
		case BT_PICCOLO:
		case BT_PICASSO:
		case BT_SPECTRUM:
		case BT_PICASSO4:
		case BT_ALPINE:
		case BT_GD5480:
			DPRINTK (" (for GD54xx)\n");
			vga_wseq (regbase, CL_SEQR7,
				  regs.multiplexing ?
					bi->sr07_1bpp_mux : bi->sr07_1bpp);
			break;

		case BT_LAGUNA:
			DPRINTK (" (for GD546x)\n");
			vga_wseq (regbase, CL_SEQR7,
				vga_rseq (regbase, CL_SEQR7) & ~0x01);
			break;

		default:
			printk (KERN_WARNING "cirrusfb: unknown Board\n");
			break;
		}

		/* Extended Sequencer Mode */
		switch (cinfo->btype) {
		case BT_SD64:
			/* setting the SEQRF on SD64 is not necessary (only during init) */
			DPRINTK ("(for SD64)\n");
			vga_wseq (regbase, CL_SEQR1F, 0x1a);		/*  MCLK select */
			break;

		case BT_PICCOLO:
			DPRINTK ("(for Piccolo)\n");
/* ### ueberall 0x22? */
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* ##vorher 1c MCLK select */
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* evtl d0 bei 1 bit? avoid FIFO underruns..? */
			break;

		case BT_PICASSO:
			DPRINTK ("(for Picasso)\n");
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* ##vorher 22 MCLK select */
			vga_wseq (regbase, CL_SEQRF, 0xd0);	/* ## vorher d0 avoid FIFO underruns..? */
			break;

		case BT_SPECTRUM:
			DPRINTK ("(for Spectrum)\n");
/* ### ueberall 0x22? */
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* ##vorher 1c MCLK select */
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* evtl d0? avoid FIFO underruns..? */
			break;

		case BT_PICASSO4:
		case BT_ALPINE:
		case BT_GD5480:
		case BT_LAGUNA:
			DPRINTK (" (for GD54xx)\n");
			/* do nothing */
			break;

		default:
			printk (KERN_WARNING "cirrusfb: unknown Board\n");
			break;
		}

		WGen (cinfo, VGA_PEL_MSK, 0x01);	/* pixel mask: pass-through for first plane */
		if (regs.multiplexing)
			WHDR (cinfo, 0x4a);	/* hidden dac reg: 1280x1024 */
		else
			WHDR (cinfo, 0);	/* hidden dac: nothing */
		vga_wseq (regbase, VGA_SEQ_MEMORY_MODE, 0x06);	/* memory mode: odd/even, ext. memory */
		vga_wseq (regbase, VGA_SEQ_PLANE_WRITE, 0x01);	/* plane mask: only write to first plane */
		offset = var->xres_virtual / 16;
	}

	/******************************************************
	 *
	 * 8 bpp
	 *
	 */

	else if (var->bits_per_pixel == 8) {
		DPRINTK ("cirrusfb: preparing for 8 bit deep display\n");
		switch (cinfo->btype) {
		case BT_SD64:
		case BT_PICCOLO:
		case BT_PICASSO:
		case BT_SPECTRUM:
		case BT_PICASSO4:
		case BT_ALPINE:
		case BT_GD5480:
			DPRINTK (" (for GD54xx)\n");
			vga_wseq (regbase, CL_SEQR7,
				  regs.multiplexing ?
					bi->sr07_8bpp_mux : bi->sr07_8bpp);
			break;

		case BT_LAGUNA:
			DPRINTK (" (for GD546x)\n");
			vga_wseq (regbase, CL_SEQR7,
				vga_rseq (regbase, CL_SEQR7) | 0x01);
			break;

		default:
			printk (KERN_WARNING "cirrusfb: unknown Board\n");
			break;
		}

		switch (cinfo->btype) {
		case BT_SD64:
			vga_wseq (regbase, CL_SEQR1F, 0x1d);		/* MCLK select */
			break;

		case BT_PICCOLO:
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* ### vorher 1c MCLK select */
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* Fast Page-Mode writes */
			break;

		case BT_PICASSO:
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* ### vorher 1c MCLK select */
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* Fast Page-Mode writes */
			break;

		case BT_SPECTRUM:
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* ### vorher 1c MCLK select */
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* Fast Page-Mode writes */
			break;

		case BT_PICASSO4:
#ifdef CONFIG_ZORRO
			vga_wseq (regbase, CL_SEQRF, 0xb8);	/* ### INCOMPLETE!! */
#endif
/*          vga_wseq (regbase, CL_SEQR1F, 0x1c); */
			break;

		case BT_ALPINE:
			DPRINTK (" (for GD543x)\n");
			cirrusfb_set_mclk (cinfo, regs.mclk, regs.divMCLK);
			/* We already set SRF and SR1F */
			break;

		case BT_GD5480:
		case BT_LAGUNA:
			DPRINTK (" (for GD54xx)\n");
			/* do nothing */
			break;

		default:
			printk (KERN_WARNING "cirrusfb: unknown Board\n");
			break;
		}

		vga_wgfx (regbase, VGA_GFX_MODE, 64);	/* mode register: 256 color mode */
		WGen (cinfo, VGA_PEL_MSK, 0xff);	/* pixel mask: pass-through all planes */
		if (regs.multiplexing)
			WHDR (cinfo, 0x4a);	/* hidden dac reg: 1280x1024 */
		else
			WHDR (cinfo, 0);	/* hidden dac: nothing */
		vga_wseq (regbase, VGA_SEQ_MEMORY_MODE, 0x0a);	/* memory mode: chain4, ext. memory */
		vga_wseq (regbase, VGA_SEQ_PLANE_WRITE, 0xff);	/* plane mask: enable writing to all 4 planes */
		offset = var->xres_virtual / 8;
	}

	/******************************************************
	 *
	 * 16 bpp
	 *
	 */

	else if (var->bits_per_pixel == 16) {
		DPRINTK ("cirrusfb: preparing for 16 bit deep display\n");
		switch (cinfo->btype) {
		case BT_SD64:
			vga_wseq (regbase, CL_SEQR7, 0xf7);	/* Extended Sequencer Mode: 256c col. mode */
			vga_wseq (regbase, CL_SEQR1F, 0x1e);		/* MCLK select */
			break;

		case BT_PICCOLO:
			vga_wseq (regbase, CL_SEQR7, 0x87);
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* Fast Page-Mode writes */
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* MCLK select */
			break;

		case BT_PICASSO:
			vga_wseq (regbase, CL_SEQR7, 0x27);
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* Fast Page-Mode writes */
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* MCLK select */
			break;

		case BT_SPECTRUM:
			vga_wseq (regbase, CL_SEQR7, 0x87);
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* Fast Page-Mode writes */
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* MCLK select */
			break;

		case BT_PICASSO4:
			vga_wseq (regbase, CL_SEQR7, 0x27);
/*          vga_wseq (regbase, CL_SEQR1F, 0x1c);  */
			break;

		case BT_ALPINE:
			DPRINTK (" (for GD543x)\n");
			if (regs.HorizRes >= 1024)
				vga_wseq (regbase, CL_SEQR7, 0xa7);
			else
				vga_wseq (regbase, CL_SEQR7, 0xa3);
			cirrusfb_set_mclk (cinfo, regs.mclk, regs.divMCLK);
			break;

		case BT_GD5480:
			DPRINTK (" (for GD5480)\n");
			vga_wseq (regbase, CL_SEQR7, 0x17);
			/* We already set SRF and SR1F */
			break;

		case BT_LAGUNA:
			DPRINTK (" (for GD546x)\n");
			vga_wseq (regbase, CL_SEQR7,
				vga_rseq (regbase, CL_SEQR7) & ~0x01);
			break;

		default:
			printk (KERN_WARNING "CIRRUSFB: unknown Board\n");
			break;
		}

		vga_wgfx (regbase, VGA_GFX_MODE, 64);	/* mode register: 256 color mode */
		WGen (cinfo, VGA_PEL_MSK, 0xff);	/* pixel mask: pass-through all planes */
#ifdef CONFIG_PCI
		WHDR (cinfo, 0xc0);	/* Copy Xbh */
#elif defined(CONFIG_ZORRO)
		/* FIXME: CONFIG_PCI and CONFIG_ZORRO may be defined both */
		WHDR (cinfo, 0xa0);	/* hidden dac reg: nothing special */
#endif
		vga_wseq (regbase, VGA_SEQ_MEMORY_MODE, 0x0a);	/* memory mode: chain4, ext. memory */
		vga_wseq (regbase, VGA_SEQ_PLANE_WRITE, 0xff);	/* plane mask: enable writing to all 4 planes */
		offset = var->xres_virtual / 4;
	}

	/******************************************************
	 *
	 * 32 bpp
	 *
	 */

	else if (var->bits_per_pixel == 32) {
		DPRINTK ("cirrusfb: preparing for 24/32 bit deep display\n");
		switch (cinfo->btype) {
		case BT_SD64:
			vga_wseq (regbase, CL_SEQR7, 0xf9);	/* Extended Sequencer Mode: 256c col. mode */
			vga_wseq (regbase, CL_SEQR1F, 0x1e);		/* MCLK select */
			break;

		case BT_PICCOLO:
			vga_wseq (regbase, CL_SEQR7, 0x85);
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* Fast Page-Mode writes */
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* MCLK select */
			break;

		case BT_PICASSO:
			vga_wseq (regbase, CL_SEQR7, 0x25);
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* Fast Page-Mode writes */
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* MCLK select */
			break;

		case BT_SPECTRUM:
			vga_wseq (regbase, CL_SEQR7, 0x85);
			vga_wseq (regbase, CL_SEQRF, 0xb0);	/* Fast Page-Mode writes */
			vga_wseq (regbase, CL_SEQR1F, 0x22);		/* MCLK select */
			break;

		case BT_PICASSO4:
			vga_wseq (regbase, CL_SEQR7, 0x25);
/*          vga_wseq (regbase, CL_SEQR1F, 0x1c);  */
			break;

		case BT_ALPINE:
			DPRINTK (" (for GD543x)\n");
			vga_wseq (regbase, CL_SEQR7, 0xa9);
			cirrusfb_set_mclk (cinfo, regs.mclk, regs.divMCLK);
			break;

		case BT_GD5480:
			DPRINTK (" (for GD5480)\n");
			vga_wseq (regbase, CL_SEQR7, 0x19);
			/* We already set SRF and SR1F */
			break;

		case BT_LAGUNA:
			DPRINTK (" (for GD546x)\n");
			vga_wseq (regbase, CL_SEQR7,
				vga_rseq (regbase, CL_SEQR7) & ~0x01);
			break;

		default:
			printk (KERN_WARNING "cirrusfb: unknown Board\n");
			break;
		}

		vga_wgfx (regbase, VGA_GFX_MODE, 64);	/* mode register: 256 color mode */
		WGen (cinfo, VGA_PEL_MSK, 0xff);	/* pixel mask: pass-through all planes */
		WHDR (cinfo, 0xc5);	/* hidden dac reg: 8-8-8 mode (24 or 32) */
		vga_wseq (regbase, VGA_SEQ_MEMORY_MODE, 0x0a);	/* memory mode: chain4, ext. memory */
		vga_wseq (regbase, VGA_SEQ_PLANE_WRITE, 0xff);	/* plane mask: enable writing to all 4 planes */
		offset = var->xres_virtual / 4;
	}

	/******************************************************
	 *
	 * unknown/unsupported bpp
	 *
	 */

	else {
		printk (KERN_ERR "cirrusfb: What's this?? requested color depth == %d.\n",
			var->bits_per_pixel);
	}

	vga_wcrt (regbase, VGA_CRTC_OFFSET, offset & 0xff);
	tmp = 0x22;
	if (offset & 0x100)
		tmp |= 0x10;	/* offset overflow bit */

	vga_wcrt (regbase, CL_CRT1B, tmp);	/* screen start addr #16-18, fastpagemode cycles */

	if (cinfo->btype == BT_SD64 ||
	    cinfo->btype == BT_PICASSO4 ||
	    cinfo->btype == BT_ALPINE ||
	    cinfo->btype == BT_GD5480)
		vga_wcrt (regbase, CL_CRT1D, 0x00);	/* screen start address bit 19 */

	vga_wcrt (regbase, VGA_CRTC_CURSOR_HI, 0);	/* text cursor location high */
	vga_wcrt (regbase, VGA_CRTC_CURSOR_LO, 0);	/* text cursor location low */
	vga_wcrt (regbase, VGA_CRTC_UNDERLINE, 0);	/* underline row scanline = at very bottom */

	vga_wattr (regbase, VGA_ATC_MODE, 1);	/* controller mode */
	vga_wattr (regbase, VGA_ATC_OVERSCAN, 0);		/* overscan (border) color */
	vga_wattr (regbase, VGA_ATC_PLANE_ENABLE, 15);	/* color plane enable */
	vga_wattr (regbase, CL_AR33, 0);	/* pixel panning */
	vga_wattr (regbase, VGA_ATC_COLOR_PAGE, 0);	/* color select */

	/* [ EGS: SetOffset(); ] */
	/* From SetOffset(): Turn on VideoEnable bit in Attribute controller */
	AttrOn (cinfo);

	vga_wgfx (regbase, VGA_GFX_SR_VALUE, 0);	/* set/reset register */
	vga_wgfx (regbase, VGA_GFX_SR_ENABLE, 0);		/* set/reset enable */
	vga_wgfx (regbase, VGA_GFX_COMPARE_VALUE, 0);	/* color compare */
	vga_wgfx (regbase, VGA_GFX_DATA_ROTATE, 0);	/* data rotate */
	vga_wgfx (regbase, VGA_GFX_PLANE_READ, 0);	/* read map select */
	vga_wgfx (regbase, VGA_GFX_MISC, 1);	/* miscellaneous register */
	vga_wgfx (regbase, VGA_GFX_COMPARE_MASK, 15);	/* color don't care */
	vga_wgfx (regbase, VGA_GFX_BIT_MASK, 255);	/* bit mask */

	vga_wseq (regbase, CL_SEQR12, 0x0);	/* graphics cursor attributes: nothing special */

	/* finally, turn on everything - turn off "FullBandwidth" bit */
	/* also, set "DotClock%2" bit where requested */
	tmp = 0x01;

/*** FB_VMODE_CLOCK_HALVE in linux/fb.h not defined anymore ?
    if (var->vmode & FB_VMODE_CLOCK_HALVE)
	tmp |= 0x08;
*/

	vga_wseq (regbase, VGA_SEQ_CLOCK_MODE, tmp);
	DPRINTK ("CL_SEQR1: %d\n", tmp);

	cinfo->currentmode = regs;
	info->fix.type = regs.type;
	info->fix.visual = regs.visual;
	info->fix.line_length = regs.line_length;

	/* pan to requested offset */
	cirrusfb_pan_display (var, info);

#ifdef CIRRUSFB_DEBUG
	cirrusfb_dump ();
#endif

	DPRINTK ("EXIT\n");
	return 0;
}

/* for some reason incomprehensible to me, cirrusfb requires that you write
 * the registers twice for the settings to take..grr. -dte */
static int cirrusfb_set_par (struct fb_info *info)
{
	cirrusfb_set_par_foo (info);
	return cirrusfb_set_par_foo (info);
}

static int cirrusfb_setcolreg (unsigned regno, unsigned red, unsigned green,
			       unsigned blue, unsigned transp,
			       struct fb_info *info)
{
	struct cirrusfb_info *cinfo = info->par;

	if (regno > 255)
		return -EINVAL;

	if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
		u32 v;
		red >>= (16 - info->var.red.length);
		green >>= (16 - info->var.green.length);
		blue >>= (16 - info->var.blue.length);

		if (regno>=16)
			return 1;
		v = (red << info->var.red.offset) |
		    (green << info->var.green.offset) |
		    (blue << info->var.blue.offset);

		switch (info->var.bits_per_pixel) {
			case 8:
				cinfo->pseudo_palette[regno] = v;
				break;
			case 16:
				cinfo->pseudo_palette[regno] = v;
				break;
			case 24:
			case 32:
				cinfo->pseudo_palette[regno] = v;
				break;
		}
		return 0;
	}

	cinfo->palette[regno].red = red;
	cinfo->palette[regno].green = green;
	cinfo->palette[regno].blue = blue;

	if (info->var.bits_per_pixel == 8) {
			WClut (cinfo, regno, red >> 10, green >> 10, blue >> 10);
	}

	return 0;

}

/*************************************************************************
	cirrusfb_pan_display()

	performs display panning - provided hardware permits this
**************************************************************************/
static int cirrusfb_pan_display (struct fb_var_screeninfo *var,
				 struct fb_info *info)
{
	int xoffset = 0;
	int yoffset = 0;
	unsigned long base;
	unsigned char tmp = 0, tmp2 = 0, xpix;
	struct cirrusfb_info *cinfo = info->par;

	DPRINTK ("ENTER\n");
	DPRINTK ("virtual offset: (%d,%d)\n", var->xoffset, var->yoffset);

	/* no range checks for xoffset and yoffset,   */
	/* as fb_pan_display has already done this */
	if (var->vmode & FB_VMODE_YWRAP)
		return -EINVAL;

	info->var.xoffset = var->xoffset;
	info->var.yoffset = var->yoffset;

	xoffset = var->xoffset * info->var.bits_per_pixel / 8;
	yoffset = var->yoffset;

	base = yoffset * cinfo->currentmode.line_length + xoffset;

	if (info->var.bits_per_pixel == 1) {
		/* base is already correct */
		xpix = (unsigned char) (var->xoffset % 8);
	} else {
		base /= 4;
		xpix = (unsigned char) ((xoffset % 4) * 2);
	}

        cirrusfb_WaitBLT(cinfo->regbase); /* make sure all the BLT's are done */

	/* lower 8 + 8 bits of screen start address */
	vga_wcrt (cinfo->regbase, VGA_CRTC_START_LO, (unsigned char) (base & 0xff));
	vga_wcrt (cinfo->regbase, VGA_CRTC_START_HI, (unsigned char) (base >> 8));

	/* construct bits 16, 17 and 18 of screen start address */
	if (base & 0x10000)
		tmp |= 0x01;
	if (base & 0x20000)
		tmp |= 0x04;
	if (base & 0x40000)
		tmp |= 0x08;

	tmp2 = (vga_rcrt (cinfo->regbase, CL_CRT1B) & 0xf2) | tmp;	/* 0xf2 is %11110010, exclude tmp bits */
	vga_wcrt (cinfo->regbase, CL_CRT1B, tmp2);

	/* construct bit 19 of screen start address */
	if (cirrusfb_board_info[cinfo->btype].scrn_start_bit19) {
		tmp2 = 0;
		if (base & 0x80000)
			tmp2 = 0x80;
		vga_wcrt (cinfo->regbase, CL_CRT1D, tmp2);
	}

	/* write pixel panning value to AR33; this does not quite work in 8bpp */
	/* ### Piccolo..? Will this work? */
	if (info->var.bits_per_pixel == 1)
		vga_wattr (cinfo->regbase, CL_AR33, xpix);

	cirrusfb_WaitBLT (cinfo->regbase);

	DPRINTK ("EXIT\n");
	return (0);
}


static int cirrusfb_blank (int blank_mode, struct fb_info *info)
{
	/*
	 *  Blank the screen if blank_mode != 0, else unblank. If blank == NULL
	 *  then the caller blanks by setting the CLUT (Color Look Up Table) to all
	 *  black. Return 0 if blanking succeeded, != 0 if un-/blanking failed due
	 *  to e.g. a video mode which doesn't support it. Implements VESA suspend
	 *  and powerdown modes on hardware that supports disabling hsync/vsync:
	 *    blank_mode == 2: suspend vsync
	 *    blank_mode == 3: suspend hsync
	 *    blank_mode == 4: powerdown
	 */
	unsigned char val;
	struct cirrusfb_info *cinfo = info->par;
	int current_mode = cinfo->blank_mode;

	DPRINTK ("ENTER, blank mode = %d\n", blank_mode);

	if (info->state != FBINFO_STATE_RUNNING ||
	    current_mode == blank_mode) {
		DPRINTK ("EXIT, returning 0\n");
		return 0;
	}

	/* Undo current */
	if (current_mode == FB_BLANK_NORMAL ||
	    current_mode == FB_BLANK_UNBLANK) {
		/* unblank the screen */
		val = vga_rseq (cinfo->regbase, VGA_SEQ_CLOCK_MODE);
		vga_wseq (cinfo->regbase, VGA_SEQ_CLOCK_MODE, val & 0xdf);	/* clear "FullBandwidth" bit */
		/* and undo VESA suspend trickery */
		vga_wgfx (cinfo->regbase, CL_GRE, 0x00);
	}

	/* set new */
	if(blank_mode > FB_BLANK_NORMAL) {
		/* blank the screen */
		val = vga_rseq (cinfo->regbase, VGA_SEQ_CLOCK_MODE);
		vga_wseq (cinfo->regbase, VGA_SEQ_CLOCK_MODE, val | 0x20);	/* set "FullBandwidth" bit */
	}

	switch (blank_mode) {
	case FB_BLANK_UNBLANK:
	case FB_BLANK_NORMAL:
		break;
	case FB_BLANK_VSYNC_SUSPEND:
		vga_wgfx (cinfo->regbase, CL_GRE, 0x04);
		break;
	case FB_BLANK_HSYNC_SUSPEND:
		vga_wgfx (cinfo->regbase, CL_GRE, 0x02);
		break;
	case FB_BLANK_POWERDOWN:
		vga_wgfx (cinfo->regbase, CL_GRE, 0x06);
		break;
	default:
		DPRINTK ("EXIT, returning 1\n");
		return 1;
	}

	cinfo->blank_mode = blank_mode;
	DPRINTK ("EXIT, returning 0\n");

	/* Let fbcon do a soft blank for us */
	return (blank_mode == FB_BLANK_NORMAL) ? 1 : 0;
}
/**** END   Hardware specific Routines **************************************/
/****************************************************************************/
/**** BEGIN Internal Routines ***********************************************/

static void init_vgachip (struct cirrusfb_info *cinfo)
{
	const struct cirrusfb_board_info_rec *bi;

	DPRINTK ("ENTER\n");

	assert (cinfo != NULL);

	bi = &cirrusfb_board_info[cinfo->btype];

	/* reset board globally */
	switch (cinfo->btype) {
	case BT_PICCOLO:
		WSFR (cinfo, 0x01);
		udelay (500);
		WSFR (cinfo, 0x51);
		udelay (500);
		break;
	case BT_PICASSO:
		WSFR2 (cinfo, 0xff);
		udelay (500);
		break;
	case BT_SD64:
	case BT_SPECTRUM:
		WSFR (cinfo, 0x1f);
		udelay (500);
		WSFR (cinfo, 0x4f);
		udelay (500);
		break;
	case BT_PICASSO4:
		vga_wcrt (cinfo->regbase, CL_CRT51, 0x00);	/* disable flickerfixer */
		mdelay (100);
		vga_wgfx (cinfo->regbase, CL_GR2F, 0x00);	/* from Klaus' NetBSD driver: */
		vga_wgfx (cinfo->regbase, CL_GR33, 0x00);	/* put blitter into 542x compat */
		vga_wgfx (cinfo->regbase, CL_GR31, 0x00);	/* mode */
		break;

	case BT_GD5480:
		vga_wgfx (cinfo->regbase, CL_GR2F, 0x00);	/* from Klaus' NetBSD driver: */
		break;

	case BT_ALPINE:
		/* Nothing to do to reset the board. */
		break;

	default:
		printk (KERN_ERR "cirrusfb: Warning: Unknown board type\n");
		break;
	}

	assert (cinfo->size > 0); /* make sure RAM size set by this point */

	/* the P4 is not fully initialized here; I rely on it having been */
	/* inited under AmigaOS already, which seems to work just fine    */
	/* (Klaus advised to do it this way)                              */

	if (cinfo->btype != BT_PICASSO4) {
		WGen (cinfo, CL_VSSM, 0x10);	/* EGS: 0x16 */
		WGen (cinfo, CL_POS102, 0x01);
		WGen (cinfo, CL_VSSM, 0x08);	/* EGS: 0x0e */

		if (cinfo->btype != BT_SD64)
			WGen (cinfo, CL_VSSM2, 0x01);

		vga_wseq (cinfo->regbase, CL_SEQR0, 0x03);	/* reset sequencer logic */

		vga_wseq (cinfo->regbase, VGA_SEQ_CLOCK_MODE, 0x21);	/* FullBandwidth (video off) and 8/9 dot clock */
		WGen (cinfo, VGA_MIS_W, 0xc1);	/* polarity (-/-), disable access to display memory, VGA_CRTC_START_HI base address: color */

/*      vga_wgfx (cinfo->regbase, CL_GRA, 0xce);    "magic cookie" - doesn't make any sense to me.. */
		vga_wseq (cinfo->regbase, CL_SEQR6, 0x12);	/* unlock all extension registers */

		vga_wgfx (cinfo->regbase, CL_GR31, 0x04);	/* reset blitter */

		switch (cinfo->btype) {
		case BT_GD5480:
			vga_wseq (cinfo->regbase, CL_SEQRF, 0x98);
			break;
		case BT_ALPINE:
			break;
		case BT_SD64:
			vga_wseq (cinfo->regbase, CL_SEQRF, 0xb8);
			break;
		default:
			vga_wseq (cinfo->regbase, CL_SEQR16, 0x0f);
			vga_wseq (cinfo->regbase, CL_SEQRF, 0xb0);
			break;
		}
	}
	vga_wseq (cinfo->regbase, VGA_SEQ_PLANE_WRITE, 0xff);	/* plane mask: nothing */
	vga_wseq (cinfo->regbase, VGA_SEQ_CHARACTER_MAP, 0x00);	/* character map select: doesn't even matter in gx mode */
	vga_wseq (cinfo->regbase, VGA_SEQ_MEMORY_MODE, 0x0e);	/* memory mode: chain-4, no odd/even, ext. memory */

	/* controller-internal base address of video memory */
	if (bi->init_sr07)
		vga_wseq (cinfo->regbase, CL_SEQR7, bi->sr07);

	/*  vga_wseq (cinfo->regbase, CL_SEQR8, 0x00); *//* EEPROM control: shouldn't be necessary to write to this at all.. */

	vga_wseq (cinfo->regbase, CL_SEQR10, 0x00);		/* graphics cursor X position (incomplete; position gives rem. 3 bits */
	vga_wseq (cinfo->regbase, CL_SEQR11, 0x00);		/* graphics cursor Y position (..."... ) */
	vga_wseq (cinfo->regbase, CL_SEQR12, 0x00);		/* graphics cursor attributes */
	vga_wseq (cinfo->regbase, CL_SEQR13, 0x00);		/* graphics cursor pattern address */

	/* writing these on a P4 might give problems..  */
	if (cinfo->btype != BT_PICASSO4) {
		vga_wseq (cinfo->regbase, CL_SEQR17, 0x00);		/* configuration readback and ext. color */
		vga_wseq (cinfo->regbase, CL_SEQR18, 0x02);		/* signature generator */
	}

	/* MCLK select etc. */
	if (bi->init_sr1f)
		vga_wseq (cinfo->regbase, CL_SEQR1F, bi->sr1f);

	vga_wcrt (cinfo->regbase, VGA_CRTC_PRESET_ROW, 0x00);	/* Screen A preset row scan: none */
	vga_wcrt (cinfo->regbase, VGA_CRTC_CURSOR_START, 0x20);	/* Text cursor start: disable text cursor */
	vga_wcrt (cinfo->regbase, VGA_CRTC_CURSOR_END, 0x00);	/* Text cursor end: - */
	vga_wcrt (cinfo->regbase, VGA_CRTC_START_HI, 0x00);	/* Screen start address high: 0 */
	vga_wcrt (cinfo->regbase, VGA_CRTC_START_LO, 0x00);	/* Screen start address low: 0 */
	vga_wcrt (cinfo->regbase, VGA_CRTC_CURSOR_HI, 0x00);	/* text cursor location high: 0 */
	vga_wcrt (cinfo->regbase, VGA_CRTC_CURSOR_LO, 0x00);	/* text cursor location low: 0 */

	vga_wcrt (cinfo->regbase, VGA_CRTC_UNDERLINE, 0x00);	/* Underline Row scanline: - */
	vga_wcrt (cinfo->regbase, VGA_CRTC_MODE, 0xc3);	/* mode control: timing enable, byte mode, no compat modes */
	vga_wcrt (cinfo->regbase, VGA_CRTC_LINE_COMPARE, 0x00);	/* Line Compare: not needed */
	/* ### add 0x40 for text modes with > 30 MHz pixclock */
	vga_wcrt (cinfo->regbase, CL_CRT1B, 0x02);	/* ext. display controls: ext.adr. wrap */

	vga_wgfx (cinfo->regbase, VGA_GFX_SR_VALUE, 0x00);	/* Set/Reset registes: - */
	vga_wgfx (cinfo->regbase, VGA_GFX_SR_ENABLE, 0x00);	/* Set/Reset enable: - */
	vga_wgfx (cinfo->regbase, VGA_GFX_COMPARE_VALUE, 0x00);	/* Color Compare: - */
	vga_wgfx (cinfo->regbase, VGA_GFX_DATA_ROTATE, 0x00);	/* Data Rotate: - */
	vga_wgfx (cinfo->regbase, VGA_GFX_PLANE_READ, 0x00);	/* Read Map Select: - */
	vga_wgfx (cinfo->regbase, VGA_GFX_MODE, 0x00);	/* Mode: conf. for 16/4/2 color mode, no odd/even, read/write mode 0 */
	vga_wgfx (cinfo->regbase, VGA_GFX_MISC, 0x01);	/* Miscellaneous: memory map base address, graphics mode */
	vga_wgfx (cinfo->regbase, VGA_GFX_COMPARE_MASK, 0x0f);	/* Color Don't care: involve all planes */
	vga_wgfx (cinfo->regbase, VGA_GFX_BIT_MASK, 0xff);	/* Bit Mask: no mask at all */
	if (cinfo->btype == BT_ALPINE)
		vga_wgfx (cinfo->regbase, CL_GRB, 0x20);	/* (5434 can't have bit 3 set for bitblt) */
	else
		vga_wgfx (cinfo->regbase, CL_GRB, 0x28);	/* Graphics controller mode extensions: finer granularity, 8byte data latches */

	vga_wgfx (cinfo->regbase, CL_GRC, 0xff);	/* Color Key compare: - */
	vga_wgfx (cinfo->regbase, CL_GRD, 0x00);	/* Color Key compare mask: - */
	vga_wgfx (cinfo->regbase, CL_GRE, 0x00);	/* Miscellaneous control: - */
	/*  vga_wgfx (cinfo->regbase, CL_GR10, 0x00); *//* Background color byte 1: - */
/*  vga_wgfx (cinfo->regbase, CL_GR11, 0x00); */

	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE0, 0x00);	/* Attribute Controller palette registers: "identity mapping" */
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE1, 0x01);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE2, 0x02);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE3, 0x03);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE4, 0x04);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE5, 0x05);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE6, 0x06);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE7, 0x07);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE8, 0x08);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTE9, 0x09);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTEA, 0x0a);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTEB, 0x0b);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTEC, 0x0c);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTED, 0x0d);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTEE, 0x0e);
	vga_wattr (cinfo->regbase, VGA_ATC_PALETTEF, 0x0f);

	vga_wattr (cinfo->regbase, VGA_ATC_MODE, 0x01);	/* Attribute Controller mode: graphics mode */
	vga_wattr (cinfo->regbase, VGA_ATC_OVERSCAN, 0x00);	/* Overscan color reg.: reg. 0 */
	vga_wattr (cinfo->regbase, VGA_ATC_PLANE_ENABLE, 0x0f);	/* Color Plane enable: Enable all 4 planes */
/* ###  vga_wattr (cinfo->regbase, CL_AR33, 0x00); * Pixel Panning: - */
	vga_wattr (cinfo->regbase, VGA_ATC_COLOR_PAGE, 0x00);	/* Color Select: - */

	WGen (cinfo, VGA_PEL_MSK, 0xff);	/* Pixel mask: no mask */

	if (cinfo->btype != BT_ALPINE && cinfo->btype != BT_GD5480)
		WGen (cinfo, VGA_MIS_W, 0xc3);	/* polarity (-/-), enable display mem, VGA_CRTC_START_HI i/o base = color */

	vga_wgfx (cinfo->regbase, CL_GR31, 0x04);	/* BLT Start/status: Blitter reset */
	vga_wgfx (cinfo->regbase, CL_GR31, 0x00);	/* - " -           : "end-of-reset" */

	/* misc... */
	WHDR (cinfo, 0);	/* Hidden DAC register: - */

	printk (KERN_DEBUG "cirrusfb: This board has %ld bytes of DRAM memory\n", cinfo->size);
	DPRINTK ("EXIT\n");
	return;
}

static void switch_monitor (struct cirrusfb_info *cinfo, int on)
{
#ifdef CONFIG_ZORRO /* only works on Zorro boards */
	static int IsOn = 0;	/* XXX not ok for multiple boards */

	DPRINTK ("ENTER\n");

	if (cinfo->btype == BT_PICASSO4)
		return;		/* nothing to switch */
	if (cinfo->btype == BT_ALPINE)
		return;		/* nothing to switch */
	if (cinfo->btype == BT_GD5480)
		return;		/* nothing to switch */
	if (cinfo->btype == BT_PICASSO) {
		if ((on && !IsOn) || (!on && IsOn))
			WSFR (cinfo, 0xff);

		DPRINTK ("EXIT\n");
		return;
	}
	if (on) {
		switch (cinfo->btype) {
		case BT_SD64:
			WSFR (cinfo, cinfo->SFR | 0x21);
			break;
		case BT_PICCOLO:
			WSFR (cinfo, cinfo->SFR | 0x28);
			break;
		case BT_SPECTRUM:
			WSFR (cinfo, 0x6f);
			break;
		default: /* do nothing */ break;
		}
	} else {
		switch (cinfo->btype) {
		case BT_SD64:
			WSFR (cinfo, cinfo->SFR & 0xde);
			break;
		case BT_PICCOLO:
			WSFR (cinfo, cinfo->SFR & 0xd7);
			break;
		case BT_SPECTRUM:
			WSFR (cinfo, 0x4f);
			break;
		default: /* do nothing */ break;
		}
	}

	DPRINTK ("EXIT\n");
#endif /* CONFIG_ZORRO */
}


/******************************************/
/* Linux 2.6-style  accelerated functions */
/******************************************/

static void cirrusfb_prim_fillrect(struct cirrusfb_info *cinfo,
				   const struct fb_fillrect *region)
{
	int m; /* bytes per pixel */
	u32 color = (cinfo->info->fix.visual == FB_VISUAL_TRUECOLOR) ?
		cinfo->pseudo_palette[region->color] : region->color;

	if(cinfo->info->var.bits_per_pixel == 1) {
		cirrusfb_RectFill(cinfo->regbase, cinfo->info->var.bits_per_pixel,
				  region->dx / 8, region->dy,
				  region->width / 8, region->height,
				  color,
				  cinfo->currentmode.line_length);
	} else {
		m = ( cinfo->info->var.bits_per_pixel + 7 ) / 8;
		cirrusfb_RectFill(cinfo->regbase, cinfo->info->var.bits_per_pixel,
				  region->dx * m, region->dy,
				  region->width * m, region->height,
				  color,
				  cinfo->currentmode.line_length);
	}
	return;
}

static void cirrusfb_fillrect (struct fb_info *info, const struct fb_fillrect *region)
{
	struct cirrusfb_info *cinfo = info->par;
	struct fb_fillrect modded;
	int vxres, vyres;

	if (info->state != FBINFO_STATE_RUNNING)
		return;
	if (info->flags & FBINFO_HWACCEL_DISABLED) {
		cfb_fillrect(info, region);
		return;
	}

	vxres = info->var.xres_virtual;
	vyres = info->var.yres_virtual;

	memcpy(&modded, region, sizeof(struct fb_fillrect));

	if(!modded.width || !modded.height ||
	   modded.dx >= vxres || modded.dy >= vyres)
		return;

	if(modded.dx + modded.width  > vxres) modded.width  = vxres - modded.dx;
	if(modded.dy + modded.height > vyres) modded.height = vyres - modded.dy;

	cirrusfb_prim_fillrect(cinfo, &modded);
}

static void cirrusfb_prim_copyarea(struct cirrusfb_info *cinfo,
				   const struct fb_copyarea *area)
{
	int m; /* bytes per pixel */
	if(cinfo->info->var.bits_per_pixel == 1) {
		cirrusfb_BitBLT(cinfo->regbase, cinfo->info->var.bits_per_pixel,
				area->sx / 8, area->sy,
				area->dx / 8, area->dy,
				area->width / 8, area->height,
				cinfo->currentmode.line_length);
	} else {
		m = ( cinfo->info->var.bits_per_pixel + 7 ) / 8;
		cirrusfb_BitBLT(cinfo->regbase, cinfo->info->var.bits_per_pixel,
				area->sx * m, area->sy,
				area->dx * m, area->dy,
				area->width * m, area->height,
				cinfo->currentmode.line_length);
	}
	return;
}


static void cirrusfb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
{
	struct cirrusfb_info *cinfo = info->par;
	struct fb_copyarea modded;
	u32 vxres, vyres;
	modded.sx = area->sx;
	modded.sy = area->sy;
	modded.dx = area->dx;
	modded.dy = area->dy;
	modded.width  = area->width;
	modded.height = area->height;

	if (info->state != FBINFO_STATE_RUNNING)
		return;
	if (info->flags & FBINFO_HWACCEL_DISABLED) {
		cfb_copyarea(info, area);
		return;
	}

	vxres = info->var.xres_virtual;
	vyres = info->var.yres_virtual;

	if(!modded.width || !modded.height ||
	   modded.sx >= vxres || modded.sy >= vyres ||
	   modded.dx >= vxres || modded.dy >= vyres)
		return;

	if(modded.sx + modded.width > vxres)  modded.width = vxres - modded.sx;
	if(modded.dx + modded.width > vxres)  modded.width = vxres - modded.dx;
	if(modded.sy + modded.height > vyres) modded.height = vyres - modded.sy;
	if(modded.dy + modded.height > vyres) modded.height = vyres - modded.dy;

	cirrusfb_prim_copyarea(cinfo, &modded);
}

static void cirrusfb_imageblit(struct fb_info *info, const struct fb_image *image)
{
	struct cirrusfb_info *cinfo = info->par;

        cirrusfb_WaitBLT(cinfo->regbase);
	cfb_imageblit(info, image);
}


#ifdef CONFIG_PPC_PREP
#define PREP_VIDEO_BASE ((volatile unsigned long) 0xC0000000)
#define PREP_IO_BASE    ((volatile unsigned char *) 0x80000000)
static void get_prep_addrs (unsigned long *display, unsigned long *registers)
{
	DPRINTK ("ENTER\n");

	*display = PREP_VIDEO_BASE;
	*registers = (unsigned long) PREP_IO_BASE;

	DPRINTK ("EXIT\n");
}

#endif				/* CONFIG_PPC_PREP */


#ifdef CONFIG_PCI
static int release_io_ports = 0;

/* Pulled the logic from XFree86 Cirrus driver to get the memory size,
 * based on the DRAM bandwidth bit and DRAM bank switching bit.  This
 * works with 1MB, 2MB and 4MB configurations (which the Motorola boards
 * seem to have. */
static unsigned int cirrusfb_get_memsize (u8 __iomem *regbase)
{
	unsigned long mem;
	unsigned char SRF;

	DPRINTK ("ENTER\n");

	SRF = vga_rseq (regbase, CL_SEQRF);
	switch ((SRF & 0x18)) {
	    case 0x08: mem = 512 * 1024; break;
	    case 0x10: mem = 1024 * 1024; break;
		/* 64-bit DRAM data bus width; assume 2MB. Also indicates 2MB memory
		   * on the 5430. */
	    case 0x18: mem = 2048 * 1024; break;
	    default: printk ("CLgenfb: Unknown memory size!\n");
		mem = 1024 * 1024;
	}
	if (SRF & 0x80) {
		/* If DRAM bank switching is enabled, there must be twice as much
		   * memory installed. (4MB on the 5434) */
		mem *= 2;
	}
	/* TODO: Handling of GD5446/5480 (see XF86 sources ...) */

	DPRINTK ("EXIT\n");
	return mem;
}



static void get_pci_addrs (const struct pci_dev *pdev,
			   unsigned long *display, unsigned long *registers)
{
	assert (pdev != NULL);
	assert (display != NULL);
	assert (registers != NULL);

	DPRINTK ("ENTER\n");

	*display = 0;
	*registers = 0;

	/* This is a best-guess for now */

	if (pci_resource_flags(pdev, 0) & IORESOURCE_IO) {
		*display = pci_resource_start(pdev, 1);
		*registers = pci_resource_start(pdev, 0);
	} else {
		*display = pci_resource_start(pdev, 0);
		*registers = pci_resource_start(pdev, 1);
	}

	assert (*display != 0);

	DPRINTK ("EXIT\n");
}


static void cirrusfb_pci_unmap (struct cirrusfb_info *cinfo)
{
	struct pci_dev *pdev = cinfo->pdev;

	iounmap(cinfo->fbmem);
#if 0 /* if system didn't claim this region, we would... */
	release_mem_region(0xA0000, 65535);
#endif
	if (release_io_ports)
		release_region(0x3C0, 32);
	pci_release_regions(pdev);
	framebuffer_release(cinfo->info);
	pci_disable_device(pdev);
}
#endif /* CONFIG_PCI */


#ifdef CONFIG_ZORRO
static void __devexit cirrusfb_zorro_unmap (struct cirrusfb_info *cinfo)
{
	zorro_release_device(cinfo->zdev);

	if (cinfo->btype == BT_PICASSO4) {
		cinfo->regbase -= 0x600000;
		iounmap ((void *)cinfo->regbase);
		iounmap ((void *)cinfo->fbmem);
	} else {
		if (zorro_resource_start(cinfo->zdev) > 0x01000000)
			iounmap ((void *)cinfo->fbmem);
	}
	framebuffer_release(cinfo->info);
}
#endif /* CONFIG_ZORRO */

static int cirrusfb_set_fbinfo(struct cirrusfb_info *cinfo)
{
	struct fb_info *info = cinfo->info;
	struct fb_var_screeninfo *var = &info->var;

	info->par = cinfo;
	info->pseudo_palette = cinfo->pseudo_palette;
	info->flags = FBINFO_DEFAULT
		    | FBINFO_HWACCEL_XPAN
		    | FBINFO_HWACCEL_YPAN
		    | FBINFO_HWACCEL_FILLRECT
		    | FBINFO_HWACCEL_COPYAREA;
	if (noaccel)
		info->flags |= FBINFO_HWACCEL_DISABLED;
	info->fbops = &cirrusfb_ops;
	info->screen_base = cinfo->fbmem;
	if (cinfo->btype == BT_GD5480) {
		if (var->bits_per_pixel == 16)
			info->screen_base += 1 * MB_;
		if (var->bits_per_pixel == 24 || var->bits_per_pixel == 32)
			info->screen_base += 2 * MB_;
	}

	/* Fill fix common fields */
	strlcpy(info->fix.id, cirrusfb_board_info[cinfo->btype].name,
		sizeof(info->fix.id));

	/* monochrome: only 1 memory plane */
	/* 8 bit and above: Use whole memory area */
	info->fix.smem_start = cinfo->fbmem_phys;
	info->fix.smem_len   = (var->bits_per_pixel == 1) ? cinfo->size / 4 : cinfo->size;
	info->fix.type       = cinfo->currentmode.type;
	info->fix.type_aux   = 0;
	info->fix.visual     = cinfo->currentmode.visual;
	info->fix.xpanstep   = 1;
	info->fix.ypanstep   = 1;
	info->fix.ywrapstep  = 0;
	info->fix.line_length = cinfo->currentmode.line_length;

	/* FIXME: map region at 0xB8000 if available, fill in here */
	info->fix.mmio_start = cinfo->fbregs_phys;
	info->fix.mmio_len   = 0;
	info->fix.accel = FB_ACCEL_NONE;

	fb_alloc_cmap(&info->cmap, 256, 0);

	return 0;
}

static int cirrusfb_register(struct cirrusfb_info *cinfo)
{
	struct fb_info *info;
	int err;
	cirrusfb_board_t btype;

	DPRINTK ("ENTER\n");

	printk (KERN_INFO "cirrusfb: Driver for Cirrus Logic based graphic boards, v" CIRRUSFB_VERSION "\n");

	info = cinfo->info;
	btype = cinfo->btype;

	/* sanity checks */
	assert (btype != BT_NONE);

	DPRINTK ("cirrusfb: (RAM start set to: 0x%p)\n", cinfo->fbmem);

	/* Make pretend we've set the var so our structures are in a "good" */
	/* state, even though we haven't written the mode to the hw yet...  */
	info->var = cirrusfb_predefined[cirrusfb_def_mode].var;
	info->var.activate = FB_ACTIVATE_NOW;

	err = cirrusfb_decode_var(&info->var, &cinfo->currentmode, info);
	if (err < 0) {
		/* should never happen */
		DPRINTK("choking on default var... umm, no good.\n");
		goto err_unmap_cirrusfb;
	}

	/* set all the vital stuff */
	cirrusfb_set_fbinfo(cinfo);

	err = register_framebuffer(info);
	if (err < 0) {
		printk (KERN_ERR "cirrusfb: could not register fb device; err = %d!\n", err);
		goto err_dealloc_cmap;
	}

	DPRINTK ("EXIT, returning 0\n");
	return 0;

err_dealloc_cmap:
	fb_dealloc_cmap(&info->cmap);
err_unmap_cirrusfb:
	cinfo->unmap(cinfo);
	return err;
}

static void __devexit cirrusfb_cleanup (struct fb_info *info)
{
	struct cirrusfb_info *cinfo = info->par;
	DPRINTK ("ENTER\n");

	switch_monitor (cinfo, 0);

	unregister_framebuffer (info);
	fb_dealloc_cmap (&info->cmap);
	printk ("Framebuffer unregistered\n");
	cinfo->unmap(cinfo);

	DPRINTK ("EXIT\n");
}


#ifdef CONFIG_PCI
static int cirrusfb_pci_register (struct pci_dev *pdev,
				  const struct pci_device_id *ent)
{
	struct cirrusfb_info *cinfo;
	struct fb_info *info;
	cirrusfb_board_t btype;
	unsigned long board_addr, board_size;
	int ret;

	ret = pci_enable_device(pdev);
	if (ret < 0) {
		printk(KERN_ERR "cirrusfb: Cannot enable PCI device\n");
		goto err_out;
	}

	info = framebuffer_alloc(sizeof(struct cirrusfb_info), &pdev->dev);
	if (!info) {
		printk(KERN_ERR "cirrusfb: could not allocate memory\n");
		ret = -ENOMEM;
		goto err_disable;
	}

	cinfo = info->par;
	cinfo->info = info;
	cinfo->pdev = pdev;
	cinfo->btype = btype = (cirrusfb_board_t) ent->driver_data;

	DPRINTK (" Found PCI device, base address 0 is 0x%lx, btype set to %d\n",
		pdev->resource[0].start, btype);
	DPRINTK (" base address 1 is 0x%lx\n", pdev->resource[1].start);

	if(isPReP) {
		pci_write_config_dword (pdev, PCI_BASE_ADDRESS_0, 0x00000000);
#ifdef CONFIG_PPC_PREP
		get_prep_addrs (&board_addr, &cinfo->fbregs_phys);
#endif
		/* PReP dies if we ioremap the IO registers, but it works w/out... */
		cinfo->regbase = (char __iomem *) cinfo->fbregs_phys;
	} else {
		DPRINTK ("Attempt to get PCI info for Cirrus Graphics Card\n");
		get_pci_addrs (pdev, &board_addr, &cinfo->fbregs_phys);
		cinfo->regbase = NULL;		/* FIXME: this forces VGA.  alternatives? */
	}

	DPRINTK ("Board address: 0x%lx, register address: 0x%lx\n", board_addr, cinfo->fbregs_phys);

	board_size = (btype == BT_GD5480) ?
		32 * MB_ : cirrusfb_get_memsize (cinfo->regbase);

	ret = pci_request_regions(pdev, "cirrusfb");
	if (ret <0) {
		printk(KERN_ERR "cirrusfb: cannot reserve region 0x%lx, abort\n",
		       board_addr);
		goto err_release_fb;
	}
#if 0 /* if the system didn't claim this region, we would... */
	if (!request_mem_region(0xA0000, 65535, "cirrusfb")) {
		printk(KERN_ERR "cirrusfb: cannot reserve region 0x%lx, abort\n"
,
		       0xA0000L);
		ret = -EBUSY;
		goto err_release_regions;
	}
#endif
	if (request_region(0x3C0, 32, "cirrusfb"))
		release_io_ports = 1;

	cinfo->fbmem = ioremap(board_addr, board_size);
	if (!cinfo->fbmem) {
		ret = -EIO;
		goto err_release_legacy;
	}

	cinfo->fbmem_phys = board_addr;
	cinfo->size = board_size;
	cinfo->unmap = cirrusfb_pci_unmap;

	printk (" RAM (%lu kB) at 0xx%lx, ", cinfo->size / KB_, board_addr);
	printk ("Cirrus Logic chipset on PCI bus\n");
	pci_set_drvdata(pdev, info);

	return cirrusfb_register(cinfo);

err_release_legacy:
	if (release_io_ports)
		release_region(0x3C0, 32);
#if 0
	release_mem_region(0xA0000, 65535);
err_release_regions:
#endif
	pci_release_regions(pdev);
err_release_fb:
	framebuffer_release(info);
err_disable:
	pci_disable_device(pdev);
err_out:
	return ret;
}

static void __devexit cirrusfb_pci_unregister (struct pci_dev *pdev)
{
	struct fb_info *info = pci_get_drvdata(pdev);
	DPRINTK ("ENTER\n");

	cirrusfb_cleanup (info);

	DPRINTK ("EXIT\n");
}

static struct pci_driver cirrusfb_pci_driver = {
	.name		= "cirrusfb",
	.id_table	= cirrusfb_pci_table,
	.probe		= cirrusfb_pci_register,
	.remove		= __devexit_p(cirrusfb_pci_unregister),
#ifdef CONFIG_PM
#if 0
	.suspend	= cirrusfb_pci_suspend,
	.resume		= cirrusfb_pci_resume,
#endif
#endif
};
#endif /* CONFIG_PCI */


#ifdef CONFIG_ZORRO
static int cirrusfb_zorro_register(struct zorro_dev *z,
				   const struct zorro_device_id *ent)
{
	struct cirrusfb_info *cinfo;
	struct fb_info *info;
	cirrusfb_board_t btype;
	struct zorro_dev *z2 = NULL;
	unsigned long board_addr, board_size, size;
	int ret;

	btype = ent->driver_data;
	if (cirrusfb_zorro_table2[btype].id2)
		z2 = zorro_find_device(cirrusfb_zorro_table2[btype].id2, NULL);
	size = cirrusfb_zorro_table2[btype].size;
	printk(KERN_INFO "cirrusfb: %s board detected; ",
	       cirrusfb_board_info[btype].name);

	info = framebuffer_alloc(sizeof(struct cirrusfb_info), &z->dev);
	if (!info) {
		printk (KERN_ERR "cirrusfb: could not allocate memory\n");
		ret = -ENOMEM;
		goto err_out;
	}

	cinfo = info->par;
	cinfo->info = info;
	cinfo->btype = btype;

	assert (z > 0);
	assert (z2 >= 0);
	assert (btype != BT_NONE);

	cinfo->zdev = z;
	board_addr = zorro_resource_start(z);
	board_size = zorro_resource_len(z);
	cinfo->size = size;

	if (!zorro_request_device(z, "cirrusfb")) {
		printk(KERN_ERR "cirrusfb: cannot reserve region 0x%lx, abort\n",
		       board_addr);
		ret = -EBUSY;
		goto err_release_fb;
	}

	printk (" RAM (%lu MB) at $%lx, ", board_size / MB_, board_addr);

	ret = -EIO;

	if (btype == BT_PICASSO4) {
		printk (" REG at $%lx\n", board_addr + 0x600000);

		/* To be precise, for the P4 this is not the */
		/* begin of the board, but the begin of RAM. */
		/* for P4, map in its address space in 2 chunks (### TEST! ) */
		/* (note the ugly hardcoded 16M number) */
		cinfo->regbase = ioremap (board_addr, 16777216);
		if (!cinfo->regbase)
			goto err_release_region;

		DPRINTK ("cirrusfb: Virtual address for board set to: $%p\n", cinfo->regbase);
		cinfo->regbase += 0x600000;
		cinfo->fbregs_phys = board_addr + 0x600000;

		cinfo->fbmem_phys = board_addr + 16777216;
		cinfo->fbmem = ioremap (cinfo->fbmem_phys, 16777216);
		if (!cinfo->fbmem)
			goto err_unmap_regbase;
	} else {
		printk (" REG at $%lx\n", (unsigned long) z2->resource.start);

		cinfo->fbmem_phys = board_addr;
		if (board_addr > 0x01000000)
			cinfo->fbmem = ioremap (board_addr, board_size);
		else
			cinfo->fbmem = (caddr_t) ZTWO_VADDR (board_addr);
		if (!cinfo->fbmem)
			goto err_release_region;

		/* set address for REG area of board */
		cinfo->regbase = (caddr_t) ZTWO_VADDR (z2->resource.start);
		cinfo->fbregs_phys = z2->resource.start;

		DPRINTK ("cirrusfb: Virtual address for board set to: $%p\n", cinfo->regbase);
	}
	cinfo->unmap = cirrusfb_zorro_unmap;

	printk (KERN_INFO "Cirrus Logic chipset on Zorro bus\n");
	zorro_set_drvdata(z, info);

	return cirrusfb_register(cinfo);

err_unmap_regbase:
	/* Parental advisory: explicit hack */
	iounmap(cinfo->regbase - 0x600000);
err_release_region:
	release_region(board_addr, board_size);
err_release_fb:
	framebuffer_release(info);
err_out:
	return ret;
}

void __devexit cirrusfb_zorro_unregister(struct zorro_dev *z)
{
	struct fb_info *info = zorro_get_drvdata(z);
	DPRINTK ("ENTER\n");

	cirrusfb_cleanup (info);

	DPRINTK ("EXIT\n");
}

static struct zorro_driver cirrusfb_zorro_driver = {
	.name		= "cirrusfb",
	.id_table	= cirrusfb_zorro_table,
	.probe		= cirrusfb_zorro_register,
	.remove		= __devexit_p(cirrusfb_zorro_unregister),
};
#endif /* CONFIG_ZORRO */

static int __init cirrusfb_init(void)
{
	int error = 0;

#ifndef MODULE
	char *option = NULL;

	if (fb_get_options("cirrusfb", &option))
		return -ENODEV;
	cirrusfb_setup(option);
#endif

#ifdef CONFIG_ZORRO
	error |= zorro_register_driver(&cirrusfb_zorro_driver);
#endif
#ifdef CONFIG_PCI
	error |= pci_register_driver(&cirrusfb_pci_driver);
#endif
	return error;
}



#ifndef MODULE
static int __init cirrusfb_setup(char *options) {
	char *this_opt, s[32];
	int i;

	DPRINTK ("ENTER\n");

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

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

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

		for (i = 0; i < NUM_TOTAL_MODES; i++) {
			sprintf (s, "mode:%s", cirrusfb_predefined[i].name);
			if (strcmp (this_opt, s) == 0)
				cirrusfb_def_mode = i;
		}
		if (!strcmp(this_opt, "noaccel"))
			noaccel = 1;
	}
	return 0;
}
#endif


    /*
     *  Modularization
     */

MODULE_AUTHOR("Copyright 1999,2000 Jeff Garzik <jgarzik@pobox.com>");
MODULE_DESCRIPTION("Accelerated FBDev driver for Cirrus Logic chips");
MODULE_LICENSE("GPL");

static void __exit cirrusfb_exit (void)
{
#ifdef CONFIG_PCI
	pci_unregister_driver(&cirrusfb_pci_driver);
#endif
#ifdef CONFIG_ZORRO
	zorro_unregister_driver(&cirrusfb_zorro_driver);
#endif
}

module_init(cirrusfb_init);

#ifdef MODULE
module_exit(cirrusfb_exit);
#endif


/**********************************************************************/
/* about the following functions - I have used the same names for the */
/* functions as Markus Wild did in his Retina driver for NetBSD as    */
/* they just made sense for this purpose. Apart from that, I wrote    */
/* these functions myself.                                            */
/**********************************************************************/

/*** WGen() - write into one of the external/general registers ***/
static void WGen (const struct cirrusfb_info *cinfo,
		  int regnum, unsigned char val)
{
	unsigned long regofs = 0;

	if (cinfo->btype == BT_PICASSO) {
		/* Picasso II specific hack */
/*              if (regnum == VGA_PEL_IR || regnum == VGA_PEL_D || regnum == CL_VSSM2) */
		if (regnum == VGA_PEL_IR || regnum == VGA_PEL_D)
			regofs = 0xfff;
	}

	vga_w (cinfo->regbase, regofs + regnum, val);
}

/*** RGen() - read out one of the external/general registers ***/
static unsigned char RGen (const struct cirrusfb_info *cinfo, int regnum)
{
	unsigned long regofs = 0;

	if (cinfo->btype == BT_PICASSO) {
		/* Picasso II specific hack */
/*              if (regnum == VGA_PEL_IR || regnum == VGA_PEL_D || regnum == CL_VSSM2) */
		if (regnum == VGA_PEL_IR || regnum == VGA_PEL_D)
			regofs = 0xfff;
	}

	return vga_r (cinfo->regbase, regofs + regnum);
}

/*** AttrOn() - turn on VideoEnable for Attribute controller ***/
static void AttrOn (const struct cirrusfb_info *cinfo)
{
	assert (cinfo != NULL);

	DPRINTK ("ENTER\n");

	if (vga_rcrt (cinfo->regbase, CL_CRT24) & 0x80) {
		/* if we're just in "write value" mode, write back the */
		/* same value as before to not modify anything */
		vga_w (cinfo->regbase, VGA_ATT_IW,
		       vga_r (cinfo->regbase, VGA_ATT_R));
	}
	/* turn on video bit */
/*      vga_w (cinfo->regbase, VGA_ATT_IW, 0x20); */
	vga_w (cinfo->regbase, VGA_ATT_IW, 0x33);

	/* dummy write on Reg0 to be on "write index" mode next time */
	vga_w (cinfo->regbase, VGA_ATT_IW, 0x00);

	DPRINTK ("EXIT\n");
}

/*** WHDR() - write into the Hidden DAC register ***/
/* as the HDR is the only extension register that requires special treatment
 * (the other extension registers are accessible just like the "ordinary"
 * registers of their functional group) here is a specialized routine for
 * accessing the HDR
 */
static void WHDR (const struct cirrusfb_info *cinfo, unsigned char val)
{
	unsigned char dummy;

	if (cinfo->btype == BT_PICASSO) {
		/* Klaus' hint for correct access to HDR on some boards */
		/* first write 0 to pixel mask (3c6) */
		WGen (cinfo, VGA_PEL_MSK, 0x00);
		udelay (200);
		/* next read dummy from pixel address (3c8) */
		dummy = RGen (cinfo, VGA_PEL_IW);
		udelay (200);
	}
	/* now do the usual stuff to access the HDR */

	dummy = RGen (cinfo, VGA_PEL_MSK);
	udelay (200);
	dummy = RGen (cinfo, VGA_PEL_MSK);
	udelay (200);
	dummy = RGen (cinfo, VGA_PEL_MSK);
	udelay (200);
	dummy = RGen (cinfo, VGA_PEL_MSK);
	udelay (200);

	WGen (cinfo, VGA_PEL_MSK, val);
	udelay (200);

	if (cinfo->btype == BT_PICASSO) {
		/* now first reset HDR access counter */
		dummy = RGen (cinfo, VGA_PEL_IW);
		udelay (200);

		/* and at the end, restore the mask value */
		/* ## is this mask always 0xff? */
		WGen (cinfo, VGA_PEL_MSK, 0xff);
		udelay (200);
	}
}


/*** WSFR() - write to the "special function register" (SFR) ***/
static void WSFR (struct cirrusfb_info *cinfo, unsigned char val)
{
#ifdef CONFIG_ZORRO
	assert (cinfo->regbase != NULL);
	cinfo->SFR = val;
	z_writeb (val, cinfo->regbase + 0x8000);
#endif
}

/* The Picasso has a second register for switching the monitor bit */
static void WSFR2 (struct cirrusfb_info *cinfo, unsigned char val)
{
#ifdef CONFIG_ZORRO
	/* writing an arbitrary value to this one causes the monitor switcher */
	/* to flip to Amiga display */
	assert (cinfo->regbase != NULL);
	cinfo->SFR = val;
	z_writeb (val, cinfo->regbase + 0x9000);
#endif
}


/*** WClut - set CLUT entry (range: 0..63) ***/
static void WClut (struct cirrusfb_info *cinfo, unsigned char regnum, unsigned char red,
	    unsigned char green, unsigned char blue)
{
	unsigned int data = VGA_PEL_D;

	/* address write mode register is not translated.. */
	vga_w (cinfo->regbase, VGA_PEL_IW, regnum);

	if (cinfo->btype == BT_PICASSO || cinfo->btype == BT_PICASSO4 ||
	    cinfo->btype == BT_ALPINE || cinfo->btype == BT_GD5480) {
		/* but DAC data register IS, at least for Picasso II */
		if (cinfo->btype == BT_PICASSO)
			data += 0xfff;
		vga_w (cinfo->regbase, data, red);
		vga_w (cinfo->regbase, data, green);
		vga_w (cinfo->regbase, data, blue);
	} else {
		vga_w (cinfo->regbase, data, blue);
		vga_w (cinfo->regbase, data, green);
		vga_w (cinfo->regbase, data, red);
	}
}


#if 0
/*** RClut - read CLUT entry (range 0..63) ***/
static void RClut (struct cirrusfb_info *cinfo, unsigned char regnum, unsigned char *red,
	    unsigned char *green, unsigned char *blue)
{
	unsigned int data = VGA_PEL_D;

	vga_w (cinfo->regbase, VGA_PEL_IR, regnum);

	if (cinfo->btype == BT_PICASSO || cinfo->btype == BT_PICASSO4 ||
	    cinfo->btype == BT_ALPINE || cinfo->btype == BT_GD5480) {
		if (cinfo->btype == BT_PICASSO)
			data += 0xfff;
		*red = vga_r (cinfo->regbase, data);
		*green = vga_r (cinfo->regbase, data);
		*blue = vga_r (cinfo->regbase, data);
	} else {
		*blue = vga_r (cinfo->regbase, data);
		*green = vga_r (cinfo->regbase, data);
		*red = vga_r (cinfo->regbase, data);
	}
}
#endif


/*******************************************************************
	cirrusfb_WaitBLT()

	Wait for the BitBLT engine to complete a possible earlier job
*********************************************************************/

/* FIXME: use interrupts instead */
static void cirrusfb_WaitBLT (u8 __iomem *regbase)
{
	/* now busy-wait until we're done */
	while (vga_rgfx (regbase, CL_GR31) & 0x08)
		/* do nothing */ ;
}

/*******************************************************************
	cirrusfb_BitBLT()

	perform accelerated "scrolling"
********************************************************************/

static void cirrusfb_BitBLT (u8 __iomem *regbase, int bits_per_pixel,
			     u_short curx, u_short cury, u_short destx, u_short desty,
			     u_short width, u_short height, u_short line_length)
{
	u_short nwidth, nheight;
	u_long nsrc, ndest;
	u_char bltmode;

	DPRINTK ("ENTER\n");

	nwidth = width - 1;
	nheight = height - 1;

	bltmode = 0x00;
	/* if source adr < dest addr, do the Blt backwards */
	if (cury <= desty) {
		if (cury == desty) {
			/* if src and dest are on the same line, check x */
			if (curx < destx)
				bltmode |= 0x01;
		} else
			bltmode |= 0x01;
	}
	if (!bltmode) {
		/* standard case: forward blitting */
		nsrc = (cury * line_length) + curx;
		ndest = (desty * line_length) + destx;
	} else {
		/* this means start addresses are at the end, counting backwards */
		nsrc = cury * line_length + curx + nheight * line_length + nwidth;
		ndest = desty * line_length + destx + nheight * line_length + nwidth;
	}

	/*
	   run-down of registers to be programmed:
	   destination pitch
	   source pitch
	   BLT width/height
	   source start
	   destination start
	   BLT mode
	   BLT ROP
	   VGA_GFX_SR_VALUE / VGA_GFX_SR_ENABLE: "fill color"
	   start/stop
	 */

        cirrusfb_WaitBLT(regbase);

	/* pitch: set to line_length */
	vga_wgfx (regbase, CL_GR24, line_length & 0xff);	/* dest pitch low */
	vga_wgfx (regbase, CL_GR25, (line_length >> 8));	/* dest pitch hi */
	vga_wgfx (regbase, CL_GR26, line_length & 0xff);	/* source pitch low */
	vga_wgfx (regbase, CL_GR27, (line_length >> 8));	/* source pitch hi */

	/* BLT width: actual number of pixels - 1 */
	vga_wgfx (regbase, CL_GR20, nwidth & 0xff);	/* BLT width low */
	vga_wgfx (regbase, CL_GR21, (nwidth >> 8));	/* BLT width hi */

	/* BLT height: actual number of lines -1 */
	vga_wgfx (regbase, CL_GR22, nheight & 0xff);	/* BLT height low */
	vga_wgfx (regbase, CL_GR23, (nheight >> 8));	/* BLT width hi */

	/* BLT destination */
	vga_wgfx (regbase, CL_GR28, (u_char) (ndest & 0xff));	/* BLT dest low */
	vga_wgfx (regbase, CL_GR29, (u_char) (ndest >> 8));	/* BLT dest mid */
	vga_wgfx (regbase, CL_GR2A, (u_char) (ndest >> 16));	/* BLT dest hi */

	/* BLT source */
	vga_wgfx (regbase, CL_GR2C, (u_char) (nsrc & 0xff));	/* BLT src low */
	vga_wgfx (regbase, CL_GR2D, (u_char) (nsrc >> 8));		/* BLT src mid */
	vga_wgfx (regbase, CL_GR2E, (u_char) (nsrc >> 16));	/* BLT src hi */

	/* BLT mode */
	vga_wgfx (regbase, CL_GR30, bltmode);	/* BLT mode */

	/* BLT ROP: SrcCopy */
	vga_wgfx (regbase, CL_GR32, 0x0d);		/* BLT ROP */

	/* and finally: GO! */
	vga_wgfx (regbase, CL_GR31, 0x02);		/* BLT Start/status */

	DPRINTK ("EXIT\n");
}


/*******************************************************************
	cirrusfb_RectFill()

	perform accelerated rectangle fill
********************************************************************/

static void cirrusfb_RectFill (u8 __iomem *regbase, int bits_per_pixel,
		     u_short x, u_short y, u_short width, u_short height,
		     u_char color, u_short line_length)
{
	u_short nwidth, nheight;
	u_long ndest;
	u_char op;

	DPRINTK ("ENTER\n");

	nwidth = width - 1;
	nheight = height - 1;

	ndest = (y * line_length) + x;

        cirrusfb_WaitBLT(regbase);

	/* pitch: set to line_length */
	vga_wgfx (regbase, CL_GR24, line_length & 0xff);	/* dest pitch low */
	vga_wgfx (regbase, CL_GR25, (line_length >> 8));	/* dest pitch hi */
	vga_wgfx (regbase, CL_GR26, line_length & 0xff);	/* source pitch low */
	vga_wgfx (regbase, CL_GR27, (line_length >> 8));	/* source pitch hi */

	/* BLT width: actual number of pixels - 1 */
	vga_wgfx (regbase, CL_GR20, nwidth & 0xff);	/* BLT width low */
	vga_wgfx (regbase, CL_GR21, (nwidth >> 8));	/* BLT width hi */

	/* BLT height: actual number of lines -1 */
	vga_wgfx (regbase, CL_GR22, nheight & 0xff);		/* BLT height low */
	vga_wgfx (regbase, CL_GR23, (nheight >> 8));		/* BLT width hi */

	/* BLT destination */
	vga_wgfx (regbase, CL_GR28, (u_char) (ndest & 0xff));	/* BLT dest low */
	vga_wgfx (regbase, CL_GR29, (u_char) (ndest >> 8));	/* BLT dest mid */
	vga_wgfx (regbase, CL_GR2A, (u_char) (ndest >> 16));		/* BLT dest hi */

	/* BLT source: set to 0 (is a dummy here anyway) */
	vga_wgfx (regbase, CL_GR2C, 0x00);	/* BLT src low */
	vga_wgfx (regbase, CL_GR2D, 0x00);	/* BLT src mid */
	vga_wgfx (regbase, CL_GR2E, 0x00);	/* BLT src hi */

	/* This is a ColorExpand Blt, using the */
	/* same color for foreground and background */
	vga_wgfx (regbase, VGA_GFX_SR_VALUE, color);	/* foreground color */
	vga_wgfx (regbase, VGA_GFX_SR_ENABLE, color);	/* background color */

	op = 0xc0;
	if (bits_per_pixel == 16) {
		vga_wgfx (regbase, CL_GR10, color);	/* foreground color */
		vga_wgfx (regbase, CL_GR11, color);	/* background color */
		op = 0x50;
		op = 0xd0;
	} else if (bits_per_pixel == 32) {
		vga_wgfx (regbase, CL_GR10, color);	/* foreground color */
		vga_wgfx (regbase, CL_GR11, color);	/* background color */
		vga_wgfx (regbase, CL_GR12, color);	/* foreground color */
		vga_wgfx (regbase, CL_GR13, color);	/* background color */
		vga_wgfx (regbase, CL_GR14, 0);	/* foreground color */
		vga_wgfx (regbase, CL_GR15, 0);	/* background color */
		op = 0x50;
		op = 0xf0;
	}
	/* BLT mode: color expand, Enable 8x8 copy (faster?) */
	vga_wgfx (regbase, CL_GR30, op);	/* BLT mode */

	/* BLT ROP: SrcCopy */
	vga_wgfx (regbase, CL_GR32, 0x0d);	/* BLT ROP */

	/* and finally: GO! */
	vga_wgfx (regbase, CL_GR31, 0x02);	/* BLT Start/status */

	DPRINTK ("EXIT\n");
}


/**************************************************************************
 * bestclock() - determine closest possible clock lower(?) than the
 * desired pixel clock
 **************************************************************************/
static void bestclock (long freq, long *best, long *nom,
		       long *den, long *div, long maxfreq)
{
	long n, h, d, f;

	assert (best != NULL);
	assert (nom != NULL);
	assert (den != NULL);
	assert (div != NULL);
	assert (maxfreq > 0);

	*nom = 0;
	*den = 0;
	*div = 0;

	DPRINTK ("ENTER\n");

	if (freq < 8000)
		freq = 8000;

	if (freq > maxfreq)
		freq = maxfreq;

	*best = 0;
	f = freq * 10;

	for (n = 32; n < 128; n++) {
		d = (143181 * n) / f;
		if ((d >= 7) && (d <= 63)) {
			if (d > 31)
				d = (d / 2) * 2;
			h = (14318 * n) / d;
			if (abs (h - freq) < abs (*best - freq)) {
				*best = h;
				*nom = n;
				if (d < 32) {
					*den = d;
					*div = 0;
				} else {
					*den = d / 2;
					*div = 1;
				}
			}
		}
		d = ((143181 * n) + f - 1) / f;
		if ((d >= 7) && (d <= 63)) {
			if (d > 31)
				d = (d / 2) * 2;
			h = (14318 * n) / d;
			if (abs (h - freq) < abs (*best - freq)) {
				*best = h;
				*nom = n;
				if (d < 32) {
					*den = d;
					*div = 0;
				} else {
					*den = d / 2;
					*div = 1;
				}
			}
		}
	}

	DPRINTK ("Best possible values for given frequency:\n");
	DPRINTK ("        best: %ld kHz  nom: %ld  den: %ld  div: %ld\n",
		 freq, *nom, *den, *div);

	DPRINTK ("EXIT\n");
}


/* -------------------------------------------------------------------------
 *
 * debugging functions
 *
 * -------------------------------------------------------------------------
 */

#ifdef CIRRUSFB_DEBUG

/**
 * cirrusfb_dbg_print_byte
 * @name: name associated with byte value to be displayed
 * @val: byte value to be displayed
 *
 * DESCRIPTION:
 * Display an indented string, along with a hexidecimal byte value, and
 * its decoded bits.  Bits 7 through 0 are listed in left-to-right
 * order.
 */

static
void cirrusfb_dbg_print_byte (const char *name, unsigned char val)
{
	DPRINTK ("%8s = 0x%02X (bits 7-0: %c%c%c%c%c%c%c%c)\n",
		 name, val,
		 val & 0x80 ? '1' : '0',
		 val & 0x40 ? '1' : '0',
		 val & 0x20 ? '1' : '0',
		 val & 0x10 ? '1' : '0',
		 val & 0x08 ? '1' : '0',
		 val & 0x04 ? '1' : '0',
		 val & 0x02 ? '1' : '0',
		 val & 0x01 ? '1' : '0');
}


/**
 * cirrusfb_dbg_print_regs
 * @base: If using newmmio, the newmmio base address, otherwise %NULL
 * @reg_class: type of registers to read: %CRT, or %SEQ
 *
 * DESCRIPTION:
 * Dumps the given list of VGA CRTC registers.  If @base is %NULL,
 * old-style I/O ports are queried for information, otherwise MMIO is
 * used at the given @base address to query the information.
 */

static
void cirrusfb_dbg_print_regs (caddr_t regbase, cirrusfb_dbg_reg_class_t reg_class,...)
{
	va_list list;
	unsigned char val = 0;
	unsigned reg;
	char *name;

	va_start (list, reg_class);

	name = va_arg (list, char *);
	while (name != NULL) {
		reg = va_arg (list, int);

		switch (reg_class) {
		case CRT:
			val = vga_rcrt (regbase, (unsigned char) reg);
			break;
		case SEQ:
			val = vga_rseq (regbase, (unsigned char) reg);
			break;
		default:
			/* should never occur */
			assert (FALSE);
			break;
		}

		cirrusfb_dbg_print_byte (name, val);

		name = va_arg (list, char *);
	}

	va_end (list);
}


/**
 * cirrusfb_dump
 * @cirrusfbinfo:
 *
 * DESCRIPTION:
 */

static
void cirrusfb_dump (void)
{
	cirrusfb_dbg_reg_dump (NULL);
}


/**
 * cirrusfb_dbg_reg_dump
 * @base: If using newmmio, the newmmio base address, otherwise %NULL
 *
 * DESCRIPTION:
 * Dumps a list of interesting VGA and CIRRUSFB registers.  If @base is %NULL,
 * old-style I/O ports are queried for information, otherwise MMIO is
 * used at the given @base address to query the information.
 */

static
void cirrusfb_dbg_reg_dump (caddr_t regbase)
{
	DPRINTK ("CIRRUSFB VGA CRTC register dump:\n");

	cirrusfb_dbg_print_regs (regbase, CRT,
			   "CR00", 0x00,
			   "CR01", 0x01,
			   "CR02", 0x02,
			   "CR03", 0x03,
			   "CR04", 0x04,
			   "CR05", 0x05,
			   "CR06", 0x06,
			   "CR07", 0x07,
			   "CR08", 0x08,
			   "CR09", 0x09,
			   "CR0A", 0x0A,
			   "CR0B", 0x0B,
			   "CR0C", 0x0C,
			   "CR0D", 0x0D,
			   "CR0E", 0x0E,
			   "CR0F", 0x0F,
			   "CR10", 0x10,
			   "CR11", 0x11,
			   "CR12", 0x12,
			   "CR13", 0x13,
			   "CR14", 0x14,
			   "CR15", 0x15,
			   "CR16", 0x16,
			   "CR17", 0x17,
			   "CR18", 0x18,
			   "CR22", 0x22,
			   "CR24", 0x24,
			   "CR26", 0x26,
			   "CR2D", 0x2D,
			   "CR2E", 0x2E,
			   "CR2F", 0x2F,
			   "CR30", 0x30,
			   "CR31", 0x31,
			   "CR32", 0x32,
			   "CR33", 0x33,
			   "CR34", 0x34,
			   "CR35", 0x35,
			   "CR36", 0x36,
			   "CR37", 0x37,
			   "CR38", 0x38,
			   "CR39", 0x39,
			   "CR3A", 0x3A,
			   "CR3B", 0x3B,
			   "CR3C", 0x3C,
			   "CR3D", 0x3D,
			   "CR3E", 0x3E,
			   "CR3F", 0x3F,
			   NULL);

	DPRINTK ("\n");

	DPRINTK ("CIRRUSFB VGA SEQ register dump:\n");

	cirrusfb_dbg_print_regs (regbase, SEQ,
			   "SR00", 0x00,
			   "SR01", 0x01,
			   "SR02", 0x02,
			   "SR03", 0x03,
			   "SR04", 0x04,
			   "SR08", 0x08,
			   "SR09", 0x09,
			   "SR0A", 0x0A,
			   "SR0B", 0x0B,
			   "SR0D", 0x0D,
			   "SR10", 0x10,
			   "SR11", 0x11,
			   "SR12", 0x12,
			   "SR13", 0x13,
			   "SR14", 0x14,
			   "SR15", 0x15,
			   "SR16", 0x16,
			   "SR17", 0x17,
			   "SR18", 0x18,
			   "SR19", 0x19,
			   "SR1A", 0x1A,
			   "SR1B", 0x1B,
			   "SR1C", 0x1C,
			   "SR1D", 0x1D,
			   "SR1E", 0x1E,
			   "SR1F", 0x1F,
			   NULL);

	DPRINTK ("\n");
}

#endif				/* CIRRUSFB_DEBUG */

