/*
 * Based on retz3fb.c and clgen.c
 */
 
#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/malloc.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/zorro.h>
#include <linux/init.h>
#include <asm/amigahw.h>
#include <asm/pgtable.h>
#include <asm/delay.h>
#include "fbcon.h"

#include "fbcon.h"
#include "fbcon-mfb.h"
#include "fbcon-cfb8.h"
#include "fbcon-cfb16.h"
#include "fbcon-cfb24.h"
#include "fbcon-cfb32.h"

#include "clgenfb.h"

#define CLGEN_VERSION "1.4 ?"
/* #define DEBUG if(1) */
#define DEBUG if(0)

#define arraysize(x)    (sizeof(x)/sizeof(*(x)))

/* zorro IDs */
#define ZORRO_PROD_HELFRICH_SD64_RAM				0x08930A00
#define ZORRO_PROD_HELFRICH_SD64_REG				0x08930B00
#define ZORRO_PROD_HELFRICH_PICCOLO_RAM				0x08930500
#define ZORRO_PROD_HELFRICH_PICCOLO_REG				0x08930600
#define ZORRO_PROD_VILLAGE_TRONIC_PICASSO_II_II_PLUS_RAM	0x08770B00
#define ZORRO_PROD_VILLAGE_TRONIC_PICASSO_II_II_PLUS_REG	0x08770C00
#define ZORRO_PROD_GVP_EGS_28_24_SPECTRUM_RAM			0x08910200
#define ZORRO_PROD_GVP_EGS_28_24_SPECTRUM_REG			0x08910100
#define ZORRO_PROD_VILLAGE_TRONIC_PICASSO_IV_Z3			0x08771800

/* board types */
#define BT_NONE     0
#define BT_SD64     1
#define BT_PICCOLO  2
#define BT_PICASSO  3
#define BT_SPECTRUM 4
#define BT_PICASSO4 5

#define MAX_NUM_BOARDS 7

#define TRUE  1
#define FALSE 0 

struct clgenfb_par
{
    struct fb_var_screeninfo var;

    __u32 line_length;  /* in BYTES! */
    __u32 visual;
    __u32 type;

    long freq;
    long nom;
    long den;
    long div;

    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;
};

/* info about board */
struct clgenfb_info
{
    struct fb_info_gen gen;

    int keyRAM;  /* RAM, REG zorro board keys */
    int keyREG;
    unsigned long fbmem;
    volatile unsigned char *regs;
    unsigned long mem;
    unsigned long size;
    int btype;
    int smallboard;
    unsigned char SFR; /* Shadow of special function register */
    
    struct clgenfb_par currentmode;
};

static struct display disp;

static struct clgenfb_info boards[MAX_NUM_BOARDS];  /* the boards */
static struct clgenfb_info *fb_info=NULL;  /* pointer to current board */

/*
 *    Predefined Video Modes
 */

static struct fb_videomode clgenfb_predefined[] __initdata =
{
	{   "Autodetect", /* autodetect mode */
	    { 0 }
	},

	{   "640x480", /* 640x480, 31.25 kHz, 60 Hz, 25 MHz PixClock */
	    { 
		640, 480, 640, 480, 0, 0, 8, 0, 
		{0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0}, 
		0, 0, -1, -1, FB_ACCEL_NONE, 40000, 32, 32, 33, 10, 96, 2,
		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED
	    }
	},

	/* 1024x768, 55.8 kHz, 70 Hz, 80 MHz PixClock */
	/* 
	    Modeline from XF86Config:
	    Mode "1024x768" 80  1024 1136 1340 1432  768 770 774 805
	*/
	{
	    "1024x768",
	    { 
		1024, 768, 1024, 768, 0, 0, 8, 0, 
		{0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0}, 
		0, 0, -1, -1, FB_ACCEL_NONE, 12500, 92, 112, 31, 2, 204, 4,
		FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED
	    }
	}
};

#define NUM_TOTAL_MODES    arraysize(clgenfb_predefined)
static struct fb_var_screeninfo clgenfb_default;

/*
 *    Frame Buffer Name
 */

static char clgenfb_name[16] = "CLgen";

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

/*--- Interface used by the world ------------------------------------------*/
void clgenfb_init(void);
void clgenfb_setup(char *options, int *ints);
int clgenfb_open(struct fb_info *info, int user);
int clgenfb_release(struct fb_info *info, int user);
int clgenfb_ioctl(struct inode *inode, struct file *file, 
		  unsigned int cmd, unsigned long arg, int con, 
		  struct fb_info *info);

/* function table of the above functions */
static struct fb_ops clgenfb_ops =
{
    clgenfb_open,
    clgenfb_release,
    fbgen_get_fix,   /* using the generic functions */
    fbgen_get_var,   /* makes things much easier... */
    fbgen_set_var,
    fbgen_get_cmap,
    fbgen_set_cmap,
    fbgen_pan_display,
    clgenfb_ioctl,
    NULL
};

/*--- Hardware Specific Routines -------------------------------------------*/
static void clgen_detect(void);
static int  clgen_encode_fix(struct fb_fix_screeninfo *fix, const void *par,
			     struct fb_info_gen *info);
static int  clgen_decode_var(const struct fb_var_screeninfo *var, void *par,
			     struct fb_info_gen *info);
static int  clgen_encode_var(struct fb_var_screeninfo *var, const void *par,
			     struct fb_info_gen *info);
static void clgen_get_par(void *par, struct fb_info_gen *info);
static void clgen_set_par(const void *par, struct fb_info_gen *info);
static int  clgen_getcolreg(unsigned regno, unsigned *red, unsigned *green,
			   unsigned *blue, unsigned *transp,
			   struct fb_info *info);
static int  clgen_setcolreg(unsigned regno, unsigned red, unsigned green,
			    unsigned blue, unsigned transp,
			    struct fb_info *info);
static int  clgen_pan_display(const struct fb_var_screeninfo *var,
			      struct fb_info_gen *info);
static int  clgen_blank(int blank_mode, struct fb_info_gen *info);

static struct display_switch *clgen_get_dispsw(const void *par,
					       struct fb_info_gen *info);

/* function table of the above functions */
static struct fbgen_hwswitch clgen_hwswitch = 
{
    clgen_detect,
    clgen_encode_fix,
    clgen_decode_var,
    clgen_encode_var,
    clgen_get_par,
    clgen_set_par,
    clgen_getcolreg, 
    clgen_setcolreg,
    clgen_pan_display,
    clgen_blank,
    clgen_get_dispsw
};

/* Text console acceleration */

#ifdef FBCON_HAS_CFB8
static void fbcon_clgen8_bmove(struct display *p, int sy, int sx, 
				int dy, int dx, int height, int width);
static void fbcon_clgen8_clear(struct vc_data *conp, struct display *p, 
				int sy, int sx, int height, int width);

static struct display_switch fbcon_clgen_8 = {
    fbcon_cfb8_setup,
    fbcon_clgen8_bmove,
    fbcon_clgen8_clear,
    fbcon_cfb8_putc,
    fbcon_cfb8_putcs,
    fbcon_cfb8_revc,
    NULL,
    NULL,
    fbcon_cfb8_clear_margins,
    FONTWIDTH(4)|FONTWIDTH(8)|FONTWIDTH(12)|FONTWIDTH(16)
};
#endif



/*--- Internal routines ----------------------------------------------------*/
static void init_vgachip(void);
static void switch_monitor(int on);

static void		WGen( int regnum, unsigned char val );
static unsigned char	RGen( int regnum );
static void		WSeq( unsigned char regnum, unsigned char val );
static unsigned char	RSeq( unsigned char regnum );
static void		WCrt( unsigned char regnum, unsigned char val );
static unsigned char	RCrt( unsigned char regnum );
static void		WGfx( unsigned char regnum, unsigned char val );
static unsigned char	RGfx( unsigned char regnum );
static void		WAttr( unsigned char regnum, unsigned char val );
static void		AttrOn( void );
static unsigned char	RAttr( unsigned char regnum );
static void		WHDR( unsigned char val );
static unsigned char	RHDR( void );
static void		WSFR( unsigned char val );
static void		WSFR2( unsigned char val );
static void		WClut( unsigned char regnum, unsigned char red, 
						     unsigned char green, 
						     unsigned char blue );
static void		RClut( unsigned char regnum, unsigned char *red, 
						     unsigned char *green, 
						     unsigned char *blue );
static void		clgen_WaitBLT( void );
static void		clgen_BitBLT (u_short curx, u_short cury,
				      u_short destx, u_short desty,
				      u_short width, u_short height, 
				      u_short line_length);
static void		clgen_RectFill (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);

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

static int opencount = 0;

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

/*--- Close /dev/fbx --------------------------------------------------------*/
int clgenfb_release(struct fb_info *info, int user)
{
    if (--opencount == 0) switch_monitor(0);
    MOD_DEC_USE_COUNT;
    return 0;
}

/*--- handle /dev/fbx ioctl calls ------------------------------------------*/
int clgenfb_ioctl(struct inode *inode, struct file *file,
                  unsigned int cmd, unsigned long arg, int con,
                  struct fb_info *info)
{
    printk(">clgenfb_ioctl()\n");
    /* Nothing exciting here... */
    printk("<clgenfb_ioctl()\n");
    return -EINVAL;
}

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

static void clgen_detect(void)
{
    printk(">clgen_detect()\n");
    printk("<clgen_detect()\n");
}

static int clgen_encode_fix(struct fb_fix_screeninfo *fix, const void *par,
			    struct fb_info_gen *info)
{
    struct clgenfb_par  *_par  = (struct clgenfb_par*) par;
    struct clgenfb_info *_info = (struct clgenfb_info*)info;
    
    memset(fix, 0, sizeof(struct fb_fix_screeninfo));
    strcpy(fix->id, clgenfb_name);
	
    fix->smem_start	= (char*)_info->fbmem;
    
    /* monochrome: only 1 memory plane */
    /* 8 bit and above: Use whole memory area */
    fix->smem_len       = _par->var.bits_per_pixel == 1 ? _info->size / 4 
							: _info->size;
    fix->type		= _par->type;
    fix->type_aux	= 0;
    fix->visual		= _par->visual;
    fix->xpanstep	= 1;
    fix->ypanstep	= 1;
    fix->ywrapstep	= 0;
    fix->line_length	= _par->line_length;
    fix->mmio_start	= (char *)_info->regs;
    fix->mmio_len	= 0x10000;
    fix->accel		= FB_ACCEL_NONE;

    return 0;
}

static int clgen_decode_var(const struct fb_var_screeninfo *var, void *par,
			    struct fb_info_gen *info)
{
    long freq;
    int  xres, hfront, hsync, hback;
    int  yres, vfront, vsync, vback;
    int  nom,den; /* translyting from pixels->bytes */
    int  i;
    static struct
    {
	int xres,yres;
    }
    modes[] = { {1600,1280}, {1280,1024}, {1024,768}, 
		{800,600}, {640,480}, {-1,-1} };

    struct clgenfb_par *_par = (struct clgenfb_par *)par;

    fb_info = (struct clgenfb_info*)info;
    printk("clgen_decode_var()\n");

    printk("Requested: %dx%dx%d\n", var->xres,var->yres,var->bits_per_pixel);
    printk("  virtual: %dx%d\n", var->xres_virtual,var->yres_virtual);
    printk("   offset: (%d,%d)\n", var->xoffset, var->yoffset);
    printk("grayscale: %d\n", var->grayscale);
#if 0
    printk(" activate: 0x%x\n", var->activate);
    printk(" pixclock: %d\n", var->pixclock);
    printk("  htiming: %d;%d  %d\n", var->left_margin,var->right_margin,var->hsync_len);
    printk("  vtiming: %d;%d  %d\n", var->upper_margin,var->lower_margin,var->vsync_len);
    printk("     sync: 0x%x\n", var->sync);
    printk("    vmode: 0x%x\n", var->vmode);
#endif

    _par->var = *var;

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

    if (_par->var.xres*nom/den * _par->var.yres > fb_info->size)
    {
	printk("clgen: mode %dx%dx%d rejected...resolution too high to fit into video memory!\n",
		var->xres, var->yres, var->bits_per_pixel);
	return -EINVAL;
    }
    
    /* use highest possible virtual resolution */
    if (_par->var.xres_virtual == -1 &&
	_par->var.xres_virtual == -1)
    {
	printk("clgen: using maximum available virtual resolution\n");
	for (i=0; modes[i].xres != -1; i++)
	{
	    if (modes[i].xres*nom/den * modes[i].yres < fb_info->size/2)
		break;
	}
	if (modes[i].xres == -1)
	{
	    printk("clgen: could not find a virtual resolution that fits into video memory!!\n");
	    return -EINVAL;
	}
	_par->var.xres_virtual = modes[i].xres;
	_par->var.yres_virtual = modes[i].yres;

	printk("clgen: virtual resolution set to maximum of %dx%d\n",
		_par->var.xres_virtual, _par->var.yres_virtual);
    }
    else if (_par->var.xres_virtual == -1)
    {
    }
    else if (_par->var.yres_virtual == -1)
    {
    }

    if (_par->var.xoffset < 0) _par->var.xoffset = 0;
    if (_par->var.yoffset < 0) _par->var.yoffset = 0;
	
    /* truncate xoffset and yoffset to maximum if too high */
    if (_par->var.xoffset > _par->var.xres_virtual-_par->var.xres)
	_par->var.xoffset = _par->var.xres_virtual-_par->var.xres -1;

    if (_par->var.yoffset > _par->var.yres_virtual-_par->var.yres)
	_par->var.yoffset = _par->var.yres_virtual-_par->var.yres -1;

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

    case 8:
	_par->line_length 	= _par->var.xres_virtual;
	_par->visual		= FB_VISUAL_PSEUDOCOLOR;
	break;

    case 16:
	_par->line_length	= _par->var.xres_virtual * 2;
	_par->visual		= FB_VISUAL_DIRECTCOLOR;
	break;

    case 24:
	_par->line_length	= _par->var.xres_virtual * 3;
	_par->visual		= FB_VISUAL_DIRECTCOLOR;
	break;

    case 32:
	_par->line_length	= _par->var.xres_virtual * 4;
	_par->visual		= FB_VISUAL_DIRECTCOLOR;
	break;
    }
    _par->type		= FB_TYPE_PACKED_PIXELS;

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

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

    /* the SD64/P4 have a higher max. videoclock */
    bestclock(freq, &_par->freq, &_par->nom, &_par->den, &_par->div, 
		fb_info->btype == BT_SD64 || fb_info->btype == BT_PICASSO4
		? 140000 : 90000);

    DEBUG printk("Best possible values for given frequency: best: %ld kHz  nom: %ld  den: %ld  div: %ld\n", 
		 _par->freq, _par->nom, _par->den, _par->div);

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

    yres   = _par->var.yres;
    vfront = _par->var.lower_margin;
    vsync  = _par->var.vsync_len;
    vback  = _par->var.upper_margin;
    
    if (_par->var.vmode & FB_VMODE_DOUBLE)
    {
    	yres   *= 2;
    	vfront *= 2;
    	vsync  *= 2;
    	vback  *= 2;
    }
    else if (_par->var.vmode & FB_VMODE_INTERLACED)
    {
    	yres   = ++yres   / 2;
    	vfront = ++vfront / 2;
    	vsync  = ++vsync  / 2;
    	vback  = ++vback  / 2;
    }

    _par->HorizRes        = xres;
    _par->HorizTotal      = (xres + hfront + hsync + hback)/8 - 5;
    _par->HorizDispEnd    = xres/8 - 1;
    _par->HorizBlankStart = xres/8;
    _par->HorizBlankEnd   = _par->HorizTotal+5; /* does not count with "-5" */
    _par->HorizSyncStart  = (xres + hfront)/8 + 1;
    _par->HorizSyncEnd    = (xres + hfront + hsync)/8 + 1;

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

    if (_par->VertTotal >= 1024)
    {
	printk(KERN_WARNING "clgen: ERROR: VerticalTotal >= 1024; special treatment required! (TODO)\n");
	return -EINVAL;
    }

    return 0;
}


static int clgen_encode_var(struct fb_var_screeninfo *var, const void *par,
			    struct fb_info_gen *info)
{
    *var = ((struct clgenfb_par*)par)->var;
    return 0;
}

/* get current video mode */
static void clgen_get_par(void *par, struct fb_info_gen *info)
{
    struct clgenfb_par *_par  = (struct clgenfb_par*)par;
    struct clgenfb_info*_info = (struct clgenfb_info*)fb_info;

    *_par = _info->currentmode;   
}

/*************************************************************************
	clgen_set_par()

	actually writes the values for a new video mode into the hardware,
**************************************************************************/
static void clgen_set_par(const void *par, struct fb_info_gen *info)
{
    unsigned char tmp;
    int offset = 0;
    struct clgenfb_par *_par = (struct clgenfb_par*)par;

    printk(KERN_INFO">clgen_set_par()\n");
    printk(KERN_INFO"Requested mode: %dx%dx%d\n",
		    _par->var.xres, _par->var.yres, _par->var.bits_per_pixel);
    printk(KERN_INFO"pixclock: %d\n", _par->var.pixclock);
    
    fb_info = (struct clgenfb_info *)info;

    /* unlock register CRT0..CRT7 */
    WCrt(CRT11, 0x20); /* previously: 0x00) */

    /* if DEBUG is set, all parameters get output before writing */
    DEBUG printk("CRT0: %ld\n", _par->HorizTotal); 
    WCrt(CRT0, _par->HorizTotal);

    DEBUG printk("CRT1: %ld\n", _par->HorizDispEnd); 
    WCrt(CRT1, _par->HorizDispEnd);

    DEBUG printk("CRT2: %ld\n", _par->HorizBlankStart); 
    WCrt(CRT2, _par->HorizBlankStart);

    DEBUG printk("CRT3: 128+%ld\n", _par->HorizBlankEnd % 32); /*  + 128: Compatible read */
    WCrt(CRT3, 128 + (_par->HorizBlankEnd % 32));

    DEBUG printk("CRT4: %ld\n", _par->HorizSyncStart);
    WCrt(CRT4, _par->HorizSyncStart);

    tmp = _par->HorizSyncEnd % 32;
    if (_par->HorizBlankEnd & 32)	
    	tmp += 128;
    DEBUG printk("CRT5: %d\n", tmp); 
    WCrt(CRT5, tmp);

    DEBUG printk("CRT6: %ld\n", _par->VertTotal & 0xff); 
    WCrt(CRT6, (_par->VertTotal & 0xff));

    tmp = 16;  /* LineCompare bit #9 */
    if (_par->VertTotal      & 256) tmp |= 1;
    if (_par->VertDispEnd    & 256) tmp |= 2;
    if (_par->VertSyncStart  & 256) tmp |= 4;
    if (_par->VertBlankStart & 256) tmp |= 8;
    if (_par->VertTotal      & 512) tmp |= 32;
    if (_par->VertDispEnd    & 512) tmp |= 64;
    if (_par->VertSyncStart  & 512) tmp |= 128;
    DEBUG printk("CRT7: %d\n", tmp);
    WCrt(CRT7, tmp);

    tmp = 0x40; /* LineCompare bit #8 */
    if (_par->VertBlankStart & 512)		tmp |= 0x20;
    if (_par->var.vmode & FB_VMODE_DOUBLE)	tmp |= 0x80;
    DEBUG printk("CRT9: %d\n", tmp);
    WCrt(CRT9, tmp);

    DEBUG printk("CRT10: %ld\n", _par->VertSyncStart & 0xff);
    WCrt(CRT10, (_par->VertSyncStart & 0xff));

    DEBUG printk("CRT11: 64+32+%ld\n", _par->VertSyncEnd % 16);
    WCrt(CRT11, (_par->VertSyncEnd % 16 + 64 + 32));

    DEBUG printk("CRT12: %ld\n", _par->VertDispEnd & 0xff);
    WCrt(CRT12, (_par->VertDispEnd & 0xff));

    DEBUG printk("CRT15: %ld\n", _par->VertBlankStart & 0xff);
    WCrt(CRT15, (_par->VertBlankStart & 0xff));

    DEBUG printk("CRT16: %ld\n", _par->VertBlankEnd & 0xff);
    WCrt(CRT16, (_par->VertBlankEnd & 0xff));

    DEBUG printk("CRT18: 0xff\n");
    WCrt(CRT18, 0xff);

    tmp = 0;
    if (_par->var.vmode & FB_VMODE_INTERLACED)	tmp |= 1;
    if (_par->HorizBlankEnd & 64)		tmp |= 16;
    if (_par->HorizBlankEnd & 128)		tmp |= 32;
    if (_par->VertBlankEnd  & 256)		tmp |= 64;
    if (_par->VertBlankEnd  & 512)		tmp |= 128;
    
    DEBUG printk("CRT1a: %d\n", tmp);
    WCrt(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 */

    WSeq(SEQRB, _par->nom);
    tmp = _par->den<<1; 
    if (_par->div != 0) tmp |= 1;

    if (fb_info->btype == BT_SD64)
    	tmp |= 0x80; /* 6 bit denom; ONLY 5434!!! (bugged me 10 days) */
    
    WSeq(SEQR1B, tmp);

    WCrt(CRT17, 0xc3); /* mode control: CRTC enable, ROTATE(?), 16bit address wrap, no compat. */

/* HAEH?	WCrt(CRT11, 0x20);  * previously: 0x00  unlock CRT0..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 (_par->var.vmode & FB_VMODE_INTERLACED)
    	WCrt(CRT19, _par->HorizTotal / 2);
    else
    	WCrt(CRT19, 0x00);	/* interlace control */

    WSeq(SEQR3, 0);

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

    WCrt(CRT8,    0);      /* Screen A Preset Row-Scan register */
    WCrt(CRTA,    0);      /* text cursor on and start line */
    WCrt(CRTB,   31);      /* text cursor end line */

    /* programming for different color depths */
    if (_par->var.bits_per_pixel == 1)
    {
	DEBUG printk(KERN_INFO "clgen: preparing for 1 bit deep display\n");
#if 0
    	/* restore first 2 color registers for mono mode */
    	WClut( 0, 0x00, 0x00, 0x00);  /* background: black */
	WClut( 1, 0xff, 0xff, 0xff);  /* foreground: white */
#endif
	WGfx(GR5,     0);      /* mode register */
	
	/* Extended Sequencer Mode */
	switch(fb_info->btype)
	{
	case BT_SD64:
	    /* setting the SEQRF on SD64 is not necessary (only during init) */
	    DEBUG printk(KERN_INFO "(for SD64)\n");
	    WSeq(SEQR7,  0xf0);
	    WSeq(SEQR1F, 0x1a);     /*  MCLK select */
	    break;

	case BT_PICCOLO:
	    DEBUG printk(KERN_INFO "(for Piccolo)\n");
	    WSeq(SEQR7, 0x80);
/* ### ueberall 0x22? */
	    WSeq(SEQR1F, 0x22);     /* ##vorher 1c MCLK select */
	    WSeq(SEQRF, 0xb0);    /* evtl d0 bei 1 bit? avoid FIFO underruns..? */
	    break;

	case BT_PICASSO:
	    DEBUG printk(KERN_INFO "(for Picasso)\n");
	    WSeq(SEQR7, 0x20);
	    WSeq(SEQR1F, 0x22);     /* ##vorher 22 MCLK select */
	    WSeq(SEQRF, 0xd0);    /* ## vorher d0 avoid FIFO underruns..? */
	    break;

	case BT_SPECTRUM:
	    DEBUG printk(KERN_INFO "(for Spectrum)\n");
	    WSeq(SEQR7, 0x80);
/* ### ueberall 0x22? */
	    WSeq(SEQR1F, 0x22);     /* ##vorher 1c MCLK select */
	    WSeq(SEQRF, 0xb0);    /* evtl d0? avoid FIFO underruns..? */
	    break;

	case BT_PICASSO4:
	    DEBUG printk(KERN_INFO "(for Picasso 4)\n");
	    WSeq(SEQR7, 0x20);
/*	    WSeq(SEQR1F, 0x1c); */
/* SEQRF not being set here...	WSeq(SEQRF, 0xd0); */
	    break;

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

	WGen(M_3C6,0x01);     /* pixel mask: pass-through for first plane */
	WHDR(0);              /* hidden dac reg: nothing special */
	WSeq(SEQR4,  0x06);     /* memory mode: odd/even, ext. memory */
	WSeq(SEQR2, 0x01);      /* plane mask: only write to first plane */
	offset 			= _par->var.xres_virtual / 16;
    }
    else if (_par->var.bits_per_pixel == 8)
    {
	DEBUG printk(KERN_INFO "clgen: preparing for 8 bit deep display\n");
	switch(fb_info->btype)
	{
	case BT_SD64:
	    WSeq(SEQR7,  0xf1); /* Extended Sequencer Mode: 256c col. mode */
	    WSeq(SEQR1F, 0x1d);     /* MCLK select */
	    break;

	case BT_PICCOLO:
	    WSeq(SEQR7, 0x81);
	    WSeq(SEQR1F, 0x22);     /* ### vorher 1c MCLK select */
	    WSeq(SEQRF, 0xb0);    /* Fast Page-Mode writes */
	    break;

	case BT_PICASSO:
	    WSeq(SEQR7, 0x21);
	    WSeq(SEQR1F, 0x22);     /* ### vorher 1c MCLK select */
	    WSeq(SEQRF, 0xb0);    /* Fast Page-Mode writes */
	    break;

	case BT_SPECTRUM:
	    WSeq(SEQR7, 0x81);
	    WSeq(SEQR1F, 0x22);     /* ### vorher 1c MCLK select */
	    WSeq(SEQRF, 0xb0);    /* Fast Page-Mode writes */
	    break;

	case BT_PICASSO4:
	    WSeq(SEQR7, 0x21);
	    WSeq(SEQRF, 0xb8); /* ### INCOMPLETE!! */
/*	    WSeq(SEQR1F, 0x1c); */
	    break;

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

	WGfx(GR5,    64);     /* mode register: 256 color mode */
	WGen(M_3C6,0xff);     /* pixel mask: pass-through all planes */
	WHDR(0);              /* hidden dac reg: nothing special */
	WSeq(SEQR4,  0x0a);     /* memory mode: chain4, ext. memory */
	WSeq(SEQR2,  0xff);     /* plane mask: enable writing to all 4 planes */
	offset 			= _par->var.xres_virtual / 8;
    }
    else if (_par->var.bits_per_pixel == 16)
    {
	DEBUG printk(KERN_INFO "clgen: preparing for 16 bit deep display\n");
	switch(fb_info->btype)
	{
	case BT_SD64:
	    WSeq(SEQR7,  0xf7); /* Extended Sequencer Mode: 256c col. mode */
	    WSeq(SEQR1F, 0x1e);     /* MCLK select */
	    break;

	case BT_PICCOLO:
	    WSeq(SEQR7, 0x87);
	    WSeq(SEQRF, 0xb0);    /* Fast Page-Mode writes */
	    WSeq(SEQR1F, 0x22);     /* MCLK select */
	    break;

	case BT_PICASSO:
	    WSeq(SEQR7, 0x27);
	    WSeq(SEQRF, 0xb0);    /* Fast Page-Mode writes */
	    WSeq(SEQR1F, 0x22);     /* MCLK select */
	    break;

	case BT_SPECTRUM:
	    WSeq(SEQR7, 0x87);
	    WSeq(SEQRF, 0xb0);    /* Fast Page-Mode writes */
	    WSeq(SEQR1F, 0x22);     /* MCLK select */
	    break;

	case BT_PICASSO4:
	    WSeq(SEQR7, 0x27);
/*	    WSeq(SEQR1F, 0x1c);  */
	    break;

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

	WGfx(GR5,    64);     /* mode register: 256 color mode */
	WGen(M_3C6,0xff);     /* pixel mask: pass-through all planes */
	WHDR(0xa0);           /* hidden dac reg: nothing special */
	WSeq(SEQR4,  0x0a);     /* memory mode: chain4, ext. memory */
	WSeq(SEQR2,  0xff);     /* plane mask: enable writing to all 4 planes */
	offset 			= _par->var.xres_virtual / 4;
    }
    else if (_par->var.bits_per_pixel == 32)
    {
	DEBUG printk(KERN_INFO "clgen: preparing for 24/32 bit deep display\n");
	switch(fb_info->btype)
	{
	case BT_SD64:
	    WSeq(SEQR7,  0xf9); /* Extended Sequencer Mode: 256c col. mode */
	    WSeq(SEQR1F, 0x1e);     /* MCLK select */
	    break;

	case BT_PICCOLO:
	    WSeq(SEQR7, 0x85);
	    WSeq(SEQRF, 0xb0);    /* Fast Page-Mode writes */
	    WSeq(SEQR1F, 0x22);     /* MCLK select */
	    break;

	case BT_PICASSO:
	    WSeq(SEQR7, 0x25);
	    WSeq(SEQRF, 0xb0);    /* Fast Page-Mode writes */
	    WSeq(SEQR1F, 0x22);     /* MCLK select */
	    break;

	case BT_SPECTRUM:
	    WSeq(SEQR7, 0x85);
	    WSeq(SEQRF, 0xb0);    /* Fast Page-Mode writes */
	    WSeq(SEQR1F, 0x22);     /* MCLK select */
	    break;

	case BT_PICASSO4:
	    WSeq(SEQR7, 0x25);
/*	    WSeq(SEQR1F, 0x1c);  */
	    break;

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

	WGfx(GR5,    64);     /* mode register: 256 color mode */
	WGen(M_3C6,0xff);     /* pixel mask: pass-through all planes */
	WHDR(0xc5);           /* hidden dac reg: 8-8-8 mode (24 or 32) */
	WSeq(SEQR4,  0x0a);     /* memory mode: chain4, ext. memory */
	WSeq(SEQR2,  0xff);     /* plane mask: enable writing to all 4 planes */
	offset			= _par->var.xres_virtual / 4;
    }
    else
	printk(KERN_ERR "clgen: What's this?? requested color depth == %d.\n",
		_par->var.bits_per_pixel);

    WCrt(CRT13, offset & 0xff);
    tmp = 0x22;
    if (offset & 0x100) tmp |= 0x10; /* offset overflow bit */
    	
    WCrt(CRT1B,tmp);    /* screen start addr #16-18, fastpagemode cycles */

    if (fb_info->btype == BT_SD64 || fb_info->btype == BT_PICASSO4)
    	WCrt(CRT1D, 0x00);   /* screen start address bit 19 */

    WCrt(CRTE,    0);      /* text cursor location high */
    WCrt(CRTF,    0);      /* text cursor location low */
    WCrt(CRT14,   0);      /* underline row scanline = at very bottom */

    WAttr(AR10,  1);      /* controller mode */
    WAttr(AR11,  0);      /* overscan (border) color */
    WAttr(AR12, 15);      /* color plane enable */
    WAttr(AR33,  0);      /* pixel panning */
    WAttr(AR14,  0);      /* color select */

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

    WGfx(GR0,    0);      /* set/reset register */
    WGfx(GR1,    0);      /* set/reset enable */
    WGfx(GR2,    0);      /* color compare */
    WGfx(GR3,    0);      /* data rotate */
    WGfx(GR4,    0);      /* read map select */
    WGfx(GR6,    1);      /* miscellaneous register */
    WGfx(GR7,   15);      /* color don't care */
    WGfx(GR8,  255);      /* bit mask */

    WSeq(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;
*/

    WSeq(SEQR1, tmp);
    DEBUG printk("SEQR1: %d\n", tmp);

#if 0
    DEBUG printk(KERN_INFO "clgen: clearing display...");
    clgen_RectFill(0, 0, _par->HorizRes, _par->VertRes, 0, _par->line_length);
    clgen_WaitBLT();
    DEBUG printk("done.\n");
#endif

    fb_info->currentmode = *_par;

    printk("virtual offset: (%d,%d)\n", _par->var.xoffset,_par->var.yoffset);
    /* pan to requested offset */
    clgen_pan_display (&fb_info->currentmode.var, (struct fb_info_gen*)fb_info);

    DEBUG printk("<clgen_set_par()\n");
    return;
}

static int clgen_getcolreg(unsigned regno, unsigned *red, unsigned *green,
			   unsigned *blue, unsigned *transp,
			   struct fb_info *info)
{
    unsigned char bred, bgreen, bblue;

    if (regno > 255 || regno < 0)
	return (1);

    fb_info = (struct clgenfb_info *)info;

    RClut(regno, &bred, &bgreen, &bblue);

    *red    = (u_int)bred;
    *green  = (u_int)bgreen;
    *blue   = (u_int)bblue;
    *transp = 0;
    return (0);
}

static int clgen_setcolreg(unsigned regno, unsigned red, unsigned green,
			   unsigned blue, unsigned transp, 
			   struct fb_info *info)
{
    if (regno > 255 || regno < 0)
	return (1);

    fb_info = (struct clgenfb_info *)info;
    
    /* "transparent" stuff is completely ignored. */
    WClut(regno, (red & 0xff), (green & 0xff), (blue & 0xff));

    return (0);
}

/*************************************************************************
	clgen_pan_display()

	performs display panning - provided hardware permits this
**************************************************************************/
static int clgen_pan_display(const struct fb_var_screeninfo *var,
			     struct fb_info_gen *info)
{
    int xoffset = 0;
    int yoffset = 0;
    unsigned long base;
    unsigned char tmp = 0, tmp2 = 0, xpix;

    fb_info = (struct clgenfb_info*)fb_info;

    /* no range checks for xoffset and yoffset,   */
    /* as fbgen_pan_display has already done this */

    fb_info->currentmode.var.xoffset = var->xoffset;
    fb_info->currentmode.var.yoffset = var->yoffset;

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

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

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

    /* lower 8 + 8 bits of screen start address */
    WCrt(CRTD, (unsigned char)(base & 0xff));
    WCrt(CRTC, (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 = (RCrt(CRT1B) & 0xf2) | tmp; /* 0xf2 is %11110010, exclude tmp bits */
    WCrt(CRT1B, tmp2);
    /* construct bit 19 of screen start address (only on SD64) */
    if (fb_info->btype == BT_SD64 ||
	fb_info->btype == BT_PICASSO4)
    {
	tmp2 = 0;
	if (base & 0x80000) tmp2 = 0x80;
	WCrt(CRT1D, tmp2);
    }

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

    return(0);
}


static int clgen_blank(int blank_mode, struct fb_info_gen *info)
{
    unsigned char val;
    printk(">clgen_blank(%d)\n",blank_mode);

    fb_info = (struct clgenfb_info *)info;

    val = RSeq(SEQR1);
    if (blank_mode)
	WSeq(SEQR1, val | 0x20); /* set "FullBandwidth" bit */
    else
	WSeq(SEQR1, val & 0xdf); /* clear "FullBandwidth" bit */

    printk("<clgen_blank()\n");
    return 0;
}

/**** END   Hardware specific Routines **************************************/
/****************************************************************************/
/**** BEGIN Internal Routines ***********************************************/

static void init_vgachip(void)
{
    printk(">init_vgachip()\n");

    /* reset board globally */
    switch(fb_info->btype)
    {
    case BT_SD64:     WSFR(0x1f);  udelay(500); WSFR(0x4f); udelay(500); break;
    case BT_PICCOLO:  WSFR(0x01);  udelay(500); WSFR(0x51); udelay(500); break;
    case BT_PICASSO:  WSFR2(0xff); udelay(500);				 break;
    case BT_SPECTRUM: WSFR(0x1f);  udelay(500); WSFR(0x4f); udelay(500); break;
    case BT_PICASSO4: 
	WCrt(CRT51, 0x00); /* disable flickerfixer */
	udelay(100000);
	WGfx(GR2F, 0x00); /* from Klaus' NetBSD driver: */
	WGfx(GR33, 0x00); /* put blitter into 542x compat */
	WGfx(GR31, 0x00); /* mode */
	break;

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

    /* "pre-set" a RAMsize; if the test succeeds, double it */
    if (fb_info->btype == BT_SD64 ||
        fb_info->btype == BT_PICASSO4)
        fb_info->size = 0x400000;
    else
        fb_info->size = 0x200000;

    /* assume it's a "large memory" board (2/4 MB) */
    fb_info->smallboard = FALSE;

    /* 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 (fb_info->btype != BT_PICASSO4)
    {
	WGen(VSSM, 0x10);  /* EGS: 0x16 */
	WGen(POS102, 0x01);
	WGen(VSSM, 0x08);  /* EGS: 0x0e */

	if(fb_info->btype != BT_SD64)
		WGen(VSSM2, 0x01);

	WSeq(SEQR0, 0x03);  /* reset sequencer logic */

	WSeq(SEQR1, 0x21);  /* FullBandwidth (video off) and 8/9 dot clock */
	WGen(MISC_W, 0xc1);  /* polarity (-/-), disable access to display memory, CRTC base address: color */

/*	WGfx(GRA, 0xce);    "magic cookie" - doesn't make any sense to me.. */
	WSeq(SEQR6, 0x12);   /* unlock all extension registers */

	WGfx(GR31, 0x04);  /* reset blitter */

	if (fb_info->btype == BT_SD64)
	{
	    WSeq(SEQRF, 0xb8);  /* 4 MB Ram SD64, disable CRT fifo(!), 64 bit bus */
	}
	else
	{
	    WSeq(SEQR16, 0x0f); /* Perf. Tuning: Fix value..(?) */
	    WSeq(SEQRF, 0xb0); /* 2 MB DRAM, 8level write buffer, 32bit bus */
	}
    }

    WSeq(SEQR2, 0xff);  /* plane mask: nothing */
    WSeq(SEQR3, 0x00);  /* character map select: doesn't even matter in gx mode */
    WSeq(SEQR4, 0x0e);  /* memory mode: chain-4, no odd/even, ext. memory */

    /* controller-internal base address of video memory */
    switch(fb_info->btype)
    {
    case BT_SD64:     WSeq(SEQR7, 0xf0); break;
    case BT_PICCOLO:  WSeq(SEQR7, 0x80); break;
    case BT_SPECTRUM: WSeq(SEQR7, 0x80); break;
    case BT_PICASSO:  WSeq(SEQR7, 0x20); break;
    case BT_PICASSO4: WSeq(SEQR7, 0x20); break;
    }

/*  WSeq(SEQR8, 0x00);*/  /* EEPROM control: shouldn't be necessary to write to this at all.. */

    WSeq(SEQR10, 0x00); /* graphics cursor X position (incomplete; position gives rem. 3 bits */
    WSeq(SEQR11, 0x00); /* graphics cursor Y position (..."... ) */
    WSeq(SEQR12, 0x00); /* graphics cursor attributes */
    WSeq(SEQR13, 0x00); /* graphics cursor pattern address */

    /* writing these on a P4 might give problems..  */
    if (fb_info->btype != BT_PICASSO4)
    {
	WSeq(SEQR17, 0x00); /* configuration readback and ext. color */
	WSeq(SEQR18, 0x02); /* signature generator */
    }

    /* MCLK select etc. */
    switch(fb_info->btype)
    {
    case BT_PICCOLO:
    case BT_PICASSO:
    case BT_SPECTRUM:  WSeq(SEQR1F, 0x22); break;
    case BT_SD64:      WSeq(SEQR1F, 0x20); break;
    case BT_PICASSO4:/*WSeq(SEQR1F, 0x1c); */ break;
    }

    WCrt(CRT8, 0x00);  /* Screen A preset row scan: none */
    WCrt(CRTA, 0x20);  /* Text cursor start: disable text cursor */
    WCrt(CRTB, 0x00);  /* Text cursor end: - */
    WCrt(CRTC, 0x00);  /* Screen start address high: 0 */
    WCrt(CRTD, 0x00);  /* Screen start address low: 0 */
    WCrt(CRTE, 0x00);  /* text cursor location high: 0 */
    WCrt(CRTF, 0x00);  /* text cursor location low: 0 */

    WCrt(CRT14, 0x00); /* Underline Row scanline: - */
    WCrt(CRT17, 0xc3); /* mode control: timing enable, byte mode, no compat modes */
    WCrt(CRT18, 0x00); /* Line Compare: not needed */
    /* ### add 0x40 for text modes with > 30 MHz pixclock */
    WCrt(CRT1B, 0x02); /* ext. display controls: ext.adr. wrap */

    WGfx(GR0, 0x00); /* Set/Reset registes: - */
    WGfx(GR1, 0x00); /* Set/Reset enable: - */
    WGfx(GR2, 0x00); /* Color Compare: - */
    WGfx(GR3, 0x00); /* Data Rotate: - */
    WGfx(GR4, 0x00); /* Read Map Select: - */
    WGfx(GR5, 0x00); /* Mode: conf. for 16/4/2 color mode, no odd/even, read/write mode 0 */
    WGfx(GR6, 0x01); /* Miscellaneous: memory map base address, graphics mode */
    WGfx(GR7, 0x0f); /* Color Don't care: involve all planes */
    WGfx(GR8, 0xff); /* Bit Mask: no mask at all */
    WGfx(GRB, 0x28); /* Graphics controller mode extensions: finer granularity, 8byte data latches */

    WGfx(GRC, 0xff); /* Color Key compare: - */
    WGfx(GRD, 0x00); /* Color Key compare mask: - */
    WGfx(GRE, 0x00); /* Miscellaneous control: - */
/*  WGfx(GR10, 0x00);*/ /* Background color byte 1: - */
/*  WGfx(GR11, 0x00); */

    WAttr(AR0, 0x00); /* Attribute Controller palette registers: "identity mapping" */
    WAttr(AR1, 0x01);
    WAttr(AR2, 0x02);
    WAttr(AR3, 0x03);
    WAttr(AR4, 0x04);
    WAttr(AR5, 0x05);
    WAttr(AR6, 0x06);
    WAttr(AR7, 0x07);
    WAttr(AR8, 0x08);
    WAttr(AR9, 0x09);
    WAttr(ARA, 0x0a);
    WAttr(ARB, 0x0b);
    WAttr(ARC, 0x0c);
    WAttr(ARD, 0x0d);
    WAttr(ARE, 0x0e);
    WAttr(ARF, 0x0f);

    WAttr(AR10, 0x01); /* Attribute Controller mode: graphics mode */
    WAttr(AR11, 0x00); /* Overscan color reg.: reg. 0 */
    WAttr(AR12, 0x0f); /* Color Plane enable: Enable all 4 planes */
/* ### 	WAttr(AR33, 0x00); * Pixel Panning: - */
    WAttr(AR14, 0x00); /* Color Select: - */

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

    WGen(MISC_W, 0xc3); /* polarity (-/-), enable display mem, CRTC i/o base = color */

    WGfx(GR31, 0x04); /* BLT Start/status: Blitter reset */
    WGfx(GR31, 0x00); /* - " -           : "end-of-reset" */

    /* CLUT setup */
    WClut( 0, 0x00, 0x00, 0x00);  /* background: black */
    WClut( 1, 0xff, 0xff, 0xff);  /* foreground: white */
    WClut( 2, 0x00, 0x80, 0x00);
    WClut( 3, 0x00, 0x80, 0x80);
    WClut( 4, 0x80, 0x00, 0x00);
    WClut( 5, 0x80, 0x00, 0x80);
    WClut( 6, 0x80, 0x40, 0x00);
    WClut( 7, 0x80, 0x80, 0x80);
    WClut( 8, 0x40, 0x40, 0x40);
    WClut( 9, 0x40, 0x40, 0xc0);
    WClut(10, 0x40, 0xc0, 0x40);
    WClut(11, 0x40, 0xc0, 0xc0);
    WClut(12, 0xc0, 0x40, 0x40);
    WClut(13, 0xc0, 0x40, 0xc0);
    WClut(14, 0xc0, 0xc0, 0x40);
    WClut(15, 0xc0, 0xc0, 0xc0);

    /* the rest a grey ramp */
    {
	int i;

	for (i = 16; i < 256; i++)
	    WClut(i, i, i, i);
    }


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

#if 0
    /* check for 1/2 MB Piccolo/Picasso/Spectrum resp. 2/4 MB SD64 */
    /* DRAM register has already been pre-set for "large", so it is*/
    /* only modified if we find that this is a "small" version */
    {
	unsigned volatile char *ram = fb_info->fbmem;
	int i, flag = 0;

	ram += (fb_info->size >> 1);

	for (i = 0; i < 256; i++)
	    ram[i] = (unsigned char)i;

	for (i = 0; i < 256; i++)
	{
	    if (ram[i] != i)
		flag = 1;
	}

	/* if the DRAM test failed, halve RAM value */
	if (flag)
	{
	    fb_info->size /= 2;
	    fb_info->smallboard = TRUE;
	    switch(fb_info->btype)
	    {
	    case BT_SD64:     WSeq(SEQRF, 0x38); break; /* 2 MB Ram SD64 */
	    case BT_PICASSO4: WSeq(SEQRF, 0x38); break; /* ### like SD64? */
	    case BT_PICCOLO:
	    case BT_PICASSO:
	    case BT_SPECTRUM: WSeq(SEQRF, 0x30); break; /* 1 MB DRAM */
	    default:
		printk(KERN_WARNING "clgen: Uuhh..could not determine RAM size!\n");
	    }
	}

    }
#endif
    printk(KERN_INFO "clgen: This board has %ld bytes of DRAM memory\n", fb_info->size);
    printk("<init_vgachip()\n");
    return;
}

static void switch_monitor(int on)
{
    static int IsOn = 0;    /* XXX not ok for multiple boards */

    if (fb_info->btype == BT_PICASSO4) return; /* nothing to switch */
    if (fb_info->btype == BT_PICASSO)
    {
	if ((on && !IsOn) || (!on && IsOn))
	    WSFR(0xff);
	return;
    }    
    if (on)
        switch(fb_info->btype)
	{
        case BT_SD64:     WSFR(fb_info->SFR | 0x21);	break;
        case BT_PICCOLO:  WSFR(fb_info->SFR | 0x28);	break;
        case BT_SPECTRUM: WSFR(0x6f);			break;
	}
    else
        switch(fb_info->btype)
	{
        case BT_SD64:     WSFR(fb_info->SFR & 0xde);	break;
        case BT_PICCOLO:  WSFR(fb_info->SFR & 0xd7);	break;
        case BT_SPECTRUM: WSFR(0x4f);			break;
	}
}

static struct display_switch *clgen_get_dispsw(const void *par,
					     struct fb_info_gen *info)
{
    struct clgenfb_par *_par = (struct clgenfb_par*) par;

    printk("clgen_get_dispsw(): ");
    switch (_par->var.bits_per_pixel)
    {
#ifdef FBCON_HAS_MFB
    case 1:
	printk("monochrome\n");
	return &fbcon_mfb;
#endif
#ifdef FBCON_HAS_CFB8
    case 8:
	printk("8 bit color depth\n");
	return &fbcon_clgen_8;
#endif
#ifdef FBCON_HAS_CFB16
    case 16:
	printk("16 bit color depth\n");
	return &fbcon_cfb16;
#endif
#ifdef FBCON_HAS_CFB24
    case 24:
	printk("24 bit color depth\n");
	return &fbcon_cfb24;
#endif
#ifdef FBCON_HAS_CFB32
    case 32:
	printk("32 bit color depth\n");
	return &fbcon_cfb32;
#endif

    default:
	printk("unsupported color depth\n");
	return NULL;
    }
}

static void fbcon_clgen8_bmove(struct display *p, int sy, int sx, 
				int dy, int dx, int height, int width)
{
    sx     *= p->fontwidth;
    sy     *= p->fontheight;
    dx     *= p->fontwidth;
    dy     *= p->fontheight;
    width  *= p->fontwidth;
    height *= p->fontheight;

    fb_info = (struct clgenfb_info*)p->fb_info;

    clgen_BitBLT((unsigned short)sx, (unsigned short)sy,
		 (unsigned short)dx, (unsigned short)dy,
		 (unsigned short)width, (unsigned short)height,
		 fb_info->currentmode.line_length);
    clgen_WaitBLT();
}

static void fbcon_clgen8_clear(struct vc_data *conp, struct display *p, 
				int sy, int sx, int height, int width)
{
    unsigned short col;
    
    fb_info = (struct clgenfb_info*)p->fb_info;

    sx     *= p->fontwidth;
    sy     *= p->fontheight;
    width  *= p->fontwidth;
    height *= p->fontheight;

    col = attr_bgcol_ec(p, conp);
    col &= 0xff;

    clgen_RectFill((unsigned short)sx, (unsigned short)sy,
		    (unsigned short)width,(unsigned short)height,
		     col, fb_info->currentmode.line_length);
    clgen_WaitBLT();
}


/********************************************************************/
/* clgenfb_init() - master initialization function                  */
/********************************************************************/
__initfunc(void clgenfb_init(void))
{
    const struct ConfigDev *cd  = NULL;
    const struct ConfigDev *cd2 = NULL;
    int err;
    int btype;
    int key,key2;
    unsigned long board_addr,board_size;

    printk(">clgenfb_init()\n");
    printk(KERN_INFO "clgen: Driver for Cirrus Logic based graphic boards, v" CLGEN_VERSION "\n");

    btype = -1;

    if ((key = zorro_find(ZORRO_PROD_HELFRICH_SD64_RAM, 0, 0)))
    {
	key2 = zorro_find(ZORRO_PROD_HELFRICH_SD64_REG, 0, 0);
        btype = BT_SD64;
        printk(KERN_INFO "clgen: SD64 board detected; ");
    }
    else if ((key = zorro_find(ZORRO_PROD_HELFRICH_PICCOLO_RAM, 0, 0)))
    {
        key2      = zorro_find(ZORRO_PROD_HELFRICH_PICCOLO_REG, 0, 0);
    	btype = BT_PICCOLO;
    	printk(KERN_INFO "clgen: Piccolo board detected; ");
    }
    else if ((key = zorro_find(ZORRO_PROD_VILLAGE_TRONIC_PICASSO_II_II_PLUS_RAM, 0, 0)))
    {
        key2      = zorro_find(ZORRO_PROD_VILLAGE_TRONIC_PICASSO_II_II_PLUS_REG, 0, 0);
	btype = BT_PICASSO;
    	printk(KERN_INFO "clgen: Picasso II board detected; ");
    }
    else if ((key = zorro_find(ZORRO_PROD_GVP_EGS_28_24_SPECTRUM_RAM, 0, 0)))
    {
        key2      = zorro_find(ZORRO_PROD_GVP_EGS_28_24_SPECTRUM_REG, 0, 0);
        btype = BT_SPECTRUM;
        printk(KERN_INFO "clgen: Spectrum board detected; ");
    }
    else if ((key = zorro_find(ZORRO_PROD_VILLAGE_TRONIC_PICASSO_IV_Z3, 0, 0)))
    {
        btype = BT_PICASSO4;
        printk(KERN_INFO "clgen: Picasso 4 board detected; ");
    }
    else
    {
	printk(KERN_NOTICE "clgen: no supported board found.\n");
	return;
    }
    
    fb_info = &boards[0]; /* FIXME support multiple boards ...*/
    
    fb_info->keyRAM = key;
    fb_info->keyREG = key2;
    fb_info->btype  = btype;
    
    cd = zorro_get_board(key);
    board_addr = (unsigned long)cd->cd_BoardAddr;
    board_size = (unsigned long)cd->cd_BoardSize;
    printk(" RAM (%lu MB) at $%lx, ", board_size/0x100000, board_addr);

    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) */
	fb_info->regs = (unsigned char *)kernel_map(board_addr, 16777216, 
	                                      KERNELMAP_NOCACHE_SER, NULL);
        DEBUG printk(KERN_INFO "clgen: Virtual address for board set to: $%p\n", fb_info->regs);
	fb_info->regs += 0x600000;

	fb_info->fbmem = kernel_map(board_addr + 16777216, 16777216, 
			      KERNELMAP_NOCACHE_SER, NULL);
	DEBUG printk(KERN_INFO "clgen: (RAM start set to: $%lx)\n", fb_info->fbmem);
    }
    else
    {
        cd2 = zorro_get_board(key2);
        printk(" REG at $%lx\n", (unsigned long)cd2->cd_BoardAddr);

        if (board_addr > 0x01000000)
	    fb_info->fbmem = kernel_map(board_addr, board_size, 
				  KERNELMAP_NOCACHE_SER, NULL);
	else
	    fb_info->fbmem = ZTWO_VADDR(board_addr);

        /* set address for REG area of board */
	fb_info->regs = (unsigned char *)ZTWO_VADDR(cd2->cd_BoardAddr);

        DEBUG printk(KERN_INFO "clgen: Virtual address for board set to: $%p\n", fb_info->regs);
	DEBUG printk(KERN_INFO "clgen: (RAM start set to: $%lx)\n", fb_info->fbmem);
    }

    init_vgachip();

    /* set up a few more things, register framebuffer driver etc */
    fb_info->gen.parsize         = sizeof(struct clgenfb_par);
    fb_info->gen.fbhw            = &clgen_hwswitch;
    strcpy (fb_info->gen.info.modename, clgenfb_name);
    fb_info->gen.info.node       = -1;
    fb_info->gen.info.fbops      = &clgenfb_ops;
    fb_info->gen.info.disp       = &disp;
    fb_info->gen.info.changevar  = NULL;
    fb_info->gen.info.switch_con = &fbgen_switch;
    fb_info->gen.info.updatevar  = &fbgen_update_var;
    fb_info->gen.info.blank      = &fbgen_blank;
    
    /* mark this board as "autoconfigured" */
    zorro_config_board(key, 0);
    if (btype != BT_PICASSO4)
	zorro_config_board(key2, 0);

    /* now that we know the board has been registered n' stuff, we */
    /* can finally initialize it to a default mode (640x480) */
    clgenfb_default = clgenfb_predefined[1].var;
    clgenfb_default.activate = FB_ACTIVATE_NOW;
    clgenfb_default.yres_virtual = 480*3; /* for fast scrolling (YPAN-Mode) */
    err = fbgen_do_set_var(&clgenfb_default, 1, &fb_info->gen);

    if (err)
	return;

    disp.var = clgenfb_default;
    fbgen_set_disp(-1, &fb_info->gen);
    fbgen_install_cmap(0, &fb_info->gen);

    err = register_framebuffer(&fb_info->gen.info);
    if (err)
    {
	printk(KERN_ERR "clgen: ERROR - could not register fb device; err = %d!\n", err);
	return;
    }

    printk("<clgenfb_init()\n");
    return;
}

    /*
     *  Cleanup
     */

void clgenfb_cleanup(struct clgenfb_info *info)
{
    printk(">clgenfb_cleanup()\n");

    fb_info = info;

    switch_monitor(0);

    zorro_unconfig_board(info->keyRAM, 0);
    if (fb_info->btype != BT_PICASSO4)
	zorro_unconfig_board(info->keyREG, 0);

    unregister_framebuffer(&info->gen.info);
    printk("Framebuffer unregistered\n");
    printk("<clgenfb_cleanup()\n");
}


/* A strtok which returns empty strings, too */
static char *strtoke(char *s,const char *ct)
{
	char *sbegin, *send;
	static char *ssave = NULL;

	sbegin  = s ? s : ssave;
	if (!sbegin)
		return NULL;
	if (*sbegin == '\0') {
		ssave = NULL;
		return NULL;
	}
	send = strpbrk(sbegin, ct);
	if (send && *send != '\0')
		*send++ = '\0';
	ssave = send;
	return sbegin;
}

/*****************************************************************/
/* clgenfb_setup() might be used later for parsing possible      */
/* arguments to the video= bootstrap parameter. Right now, there */
/* is nothing I do here.                                         */
/*****************************************************************/
__initfunc(void clgenfb_setup(char *options, int *ints))
{
//    char *this_opt;

//    printk("clgenfb_setup(): options: %s\n", options);	
}


    /*
     *  Modularization
     */

#ifdef MODULE
int init_module(void)
{
    printk("init_module()\n");
    clgenfb_init(0);
    return 0;
}

void cleanup_module(void)
{
    printk("module_cleanup()\n");
    clgenfb_cleanup(fb_info);
}
#endif /* MODULE */



/**********************************************************************/
/* 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 ***/
void WGen(int regnum, unsigned char val)
{
	unsigned volatile char *reg = fb_info->regs + regnum;

	if(fb_info->btype == BT_PICASSO)
	{
		/* Picasso II specific hack */
/*		if (regnum == M_3C7_W || regnum == M_3C9 || regnum == VSSM2) */
		if (regnum == M_3C7_W || regnum == M_3C9)
			reg += 0xfff;
	}

	*reg = val;
}

/*** RGen() - read out one of the external/general registers ***/
unsigned char RGen(int regnum)
{
	unsigned volatile char *reg = fb_info->regs + regnum;

	if(fb_info->btype == BT_PICASSO)
	{
		/* Picasso II specific hack */
/*		if (regnum == M_3C7_W || regnum == M_3C9 || regnum == VSSM2) */
		if (regnum == M_3C7_W || regnum == M_3C9)
			reg += 0xfff;
	}

	return *reg;
}

/*** WSeq() - write into a register of the sequencer ***/
void WSeq(unsigned char regnum, unsigned char val)
{
	fb_info->regs[SEQRX]   = regnum;
	fb_info->regs[SEQRX+1] = val;
}

/*** RSeq() - read out one of the Sequencer registers ***/
unsigned char RSeq(unsigned char regnum)
{
	fb_info->regs[SEQRX] = regnum;
	return fb_info->regs[SEQRX+1];
}

/*** WCrt() - write into a register of the CRT controller ***/
void WCrt(unsigned char regnum, unsigned char val)
{
	fb_info->regs[CRTX]   = regnum;
	fb_info->regs[CRTX+1] = val;
}

/*** RCrt() - read out one of the CRT controller registers ***/
unsigned char RCrt(unsigned char regnum)
{
	fb_info->regs[CRTX] = regnum;
	return fb_info->regs[CRTX+1];
}

/*** WGfx() - write into a register of the Gfx controller ***/
void WGfx(unsigned char regnum, unsigned char val)
{
	fb_info->regs[GRX]   = regnum;
	fb_info->regs[GRX+1] = val;
}

/*** RGfx() - read out one of the Gfx controller registers ***/
unsigned char RGfx(unsigned char regnum)
{
	fb_info->regs[GRX] = regnum;
	return fb_info->regs[GRX+1];
}

/*** WAttr() - write into a register of the Attribute controller ***/
void WAttr(unsigned char regnum, unsigned char val)
{
	/* if the next access to the attribute controller is a data write access, */
	/* simply write back the information that was already there before, so that */
	/* the next write access after that will be an index write. */
	if (RCrt(CRT24) & 0x80)
		/* can't use WAttr() here - we would go into a recursive loop otherwise */
		fb_info->regs[ARX] = fb_info->regs[ARX+1];

	if (RCrt(CRT24) & 0x80)
		printk(KERN_WARNING "clgen: *** AttrIdx BAD!***\n");

	/* now, first set index and after that the value - both to the same address (!) */
	fb_info->regs[ARX] = regnum;
	fb_info->regs[ARX] = val;
}

/*** AttrOn() - turn on VideoEnable for Attribute controller ***/
void AttrOn()
{
	if (RCrt(CRT24) & 0x80)
		/* if we're just in "write value" mode, write back the */
		/* same value as before to not modify anything */
		fb_info->regs[ARX] = fb_info->regs[ARX+1];

	/* turn on video bit */
/*	fb_info->regs[ARX] = 0x20; */
	fb_info->regs[ARX] = 0x33;

	/* dummy write on Reg0 to be on "write index" mode next time */
	fb_info->regs[ARX] = 0x00;
}

/*** RAttr() - read out a register of the Attribute controller ***/
unsigned char RAttr(unsigned char regnum)
{
	/* (explanation see above in WAttr() ) */
	if (RCrt(CRT24) & 0x80)
		fb_info->regs[ARX] = fb_info->regs[ARX+1];

	fb_info->regs[ARX] = regnum;
	return fb_info->regs[ARX+1];
}


/*** 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
 */
void WHDR(unsigned char val)
{
	unsigned char dummy;

	if(fb_info->btype == BT_PICASSO)
	{
		/* Klaus' hint for correct access to HDR on some boards */
		/* first write 0 to pixel mask (3c6) */
		WGen(M_3C6, 0x00); udelay(200);
		/* next read dummy from pixel address (3c8) */
		dummy = RGen(M_3C8); udelay(200);
	}

	/* now do the usual stuff to access the HDR */

	dummy = RGen(M_3C6); udelay(200);
	dummy = RGen(M_3C6); udelay(200);
	dummy = RGen(M_3C6); udelay(200);
	dummy = RGen(M_3C6); udelay(200);

	WGen(M_3C6, val); udelay(200);

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

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

/*** RHDR() - read out the Hidden DAC register ***/
/* I hope this does not break on the GD5428 - cannot test it. */
/* (Is there any board for the Amiga that uses the 5428 ?) */
unsigned char RHDR()
{
	unsigned char dummy;

	dummy = RGen(M_3C6);
	dummy = RGen(M_3C6);
	dummy = RGen(M_3C6);
	dummy = RGen(M_3C6);

	return RGen(M_3C6);
}


/*** WSFR() - write to the "special function register" (SFR) ***/
void WSFR(unsigned char val)
{
	fb_info->SFR          = val;
	fb_info->regs[0x8000] = val;
}

/* The Picasso has a second register for switching the monitor bit */
void WSFR2(unsigned char val)
{
	/* writing an arbitrary value to this one causes the monitor switcher */
	/* to flip to Amiga display */
	fb_info->SFR          = val;
	fb_info->regs[0x9000] = val;
}

/*** WClut - set CLUT entry (range: 0..255 is automat. shifted to 0..63) ***/
void WClut(unsigned char regnum, unsigned char red, unsigned char green, unsigned char blue)
{
	unsigned int data = 0x3c9;

	/* address write mode register is not translated.. */
	fb_info->regs[0x3c8] = regnum;

	if(fb_info->btype == BT_PICASSO || fb_info->btype == BT_PICASSO4)
	{
		/* but DAC data register IS, at least for Picasso II */
		if(fb_info->btype == BT_PICASSO)
			data += 0xfff;
		fb_info->regs[data] = (red   >> 2);
		fb_info->regs[data] = (green >> 2);
		fb_info->regs[data] = (blue  >> 2);
	}
	else
	{
		fb_info->regs[data] = (blue  >> 2);
		fb_info->regs[data] = (green >> 2);
		fb_info->regs[data] = (red   >> 2);
	}
}

/*** RClut - read CLUT entry and convert to 0..255 range ***/
void RClut(unsigned char regnum, unsigned char *red, unsigned char *green, unsigned char *blue)
{
	unsigned int data = 0x3c9;

	fb_info->regs[0x3c7] = regnum;

	if(fb_info->btype == BT_PICASSO || fb_info->btype == BT_PICASSO4)
	{
		if(fb_info->btype == BT_PICASSO)
			data += 0xfff;
		*red   = fb_info->regs[data] << 2;
		*green = fb_info->regs[data] << 2;
		*blue  = fb_info->regs[data] << 2;
	}
	else
	{
		*blue  = fb_info->regs[data] << 2;
		*green = fb_info->regs[data] << 2;
		*red   = fb_info->regs[data] << 2;
	}
}


/*******************************************************************
	clgen_WaitBLT()

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

void clgen_WaitBLT()
{
	/* now busy-wait until we're done */
	while (RGfx(GR31) & 0x08)
		;
}

/*******************************************************************
	clgen_BitBLT()

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

void clgen_BitBLT (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;

	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;
	}

//	clgen_WaitBLT(); /* ### NOT OK for multiple boards! */

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

	/* pitch: set to line_length */
	WGfx(GR24, line_length & 0xff);	/* dest pitch low */
	WGfx(GR25, (line_length >> 8));	/* dest pitch hi */
	WGfx(GR26, line_length & 0xff);	/* source pitch low */
	WGfx(GR27, (line_length >> 8));	/* source pitch hi */

	/* BLT width: actual number of pixels - 1 */
	WGfx(GR20, nwidth & 0xff);	/* BLT width low */
	WGfx(GR21, (nwidth >> 8));	/* BLT width hi */

	/* BLT height: actual number of lines -1 */
	WGfx(GR22, nheight & 0xff);	/* BLT height low */
	WGfx(GR23, (nheight >> 8));	/* BLT width hi */

	/* BLT destination */
	WGfx(GR28, (u_char)(ndest & 0xff));	/* BLT dest low */
	WGfx(GR29, (u_char)(ndest >> 8));	/* BLT dest mid */
	WGfx(GR2A, (u_char)(ndest >> 16));	/* BLT dest hi */

	/* BLT source */
	WGfx(GR2C, (u_char)(nsrc & 0xff));	/* BLT src low */
	WGfx(GR2D, (u_char)(nsrc >> 8));	/* BLT src mid */
	WGfx(GR2E, (u_char)(nsrc >> 16));	/* BLT src hi */

	/* BLT mode */
	WGfx(GR30, bltmode);	/* BLT mode */

	/* BLT ROP: SrcCopy */
	WGfx(GR32, 0x0d);	/* BLT ROP */

	/* and finally: GO! */
	WGfx(GR31, 0x02);	/* BLT Start/status */
}

/*******************************************************************
	clgen_RectFill()

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

void clgen_RectFill (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;

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

	ndest = (y * line_length) + x;

//	clgen_WaitBLT(); /* ### NOT OK for multiple boards! */

	/* pitch: set to line_length */
	WGfx(GR24, line_length & 0xff);	/* dest pitch low */
	WGfx(GR25, (line_length >> 8));	/* dest pitch hi */
	WGfx(GR26, line_length & 0xff);	/* source pitch low */
	WGfx(GR27, (line_length >> 8));	/* source pitch hi */

	/* BLT width: actual number of pixels - 1 */
	WGfx(GR20, nwidth & 0xff);	/* BLT width low */
	WGfx(GR21, (nwidth >> 8));	/* BLT width hi */

	/* BLT height: actual number of lines -1 */
	WGfx(GR22, nheight & 0xff);	/* BLT height low */
	WGfx(GR23, (nheight >> 8));	/* BLT width hi */

	/* BLT destination */
	WGfx(GR28, (u_char)(ndest & 0xff));	/* BLT dest low */
	WGfx(GR29, (u_char)(ndest >> 8));	/* BLT dest mid */
	WGfx(GR2A, (u_char)(ndest >> 16));	/* BLT dest hi */

	/* BLT source: set to 0 (is a dummy here anyway) */
	WGfx(GR2C, 0x00);	/* BLT src low */
	WGfx(GR2D, 0x00);	/* BLT src mid */
	WGfx(GR2E, 0x00);	/* BLT src hi */

	/* This is a ColorExpand Blt, using the */
	/* same color for foreground and background */
	WGfx(GR0, color);	/* foreground color */
	WGfx(GR1, color);	/* background color */

	/* BLT mode: color expand, Enable 8x8 copy (faster?) */
	WGfx(GR30, 0xc0);	/* BLT mode */

	/* BLT ROP: SrcCopy */
	WGfx(GR32, 0x0d);	/* BLT ROP */

	/* and finally: GO! */
	WGfx(GR31, 0x02);	/* BLT Start/status */
}

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

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

    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;
	        }
	    }
	}
    }
}

