/*
 *
 * tdfxfb.c
 *
 * Author: Hannu Mallat <hmallat@cc.hut.fi>
 *
 * Copyright © 1999 Hannu Mallat
 * All rights reserved
 *
 * Created      : Thu Sep 23 18:17:43 1999, hmallat
 * Last modified: Tue Nov  2 21:19:47 1999, hmallat
 *
 * Lots of the information here comes from the Daryll Strauss' Banshee 
 * patches to the XF86 server, and the rest comes from the 3dfx
 * Banshee specification. I'm very much indebted to Daryll for his
 * work on the X server.
 *
 * Voodoo3 support was contributed Harold Oga. Lots of additions
 * (proper acceleration, 24 bpp, hardware cursor) and bug fixes by Attila
 * Kesmarki. Thanks guys!
 * 
 * While I _am_ grateful to 3Dfx for releasing the specs for Banshee,
 * I do wish the next version is a bit more complete. Without the XF86
 * patches I couldn't have gotten even this far... for instance, the
 * extensions to the VGA register set go completely unmentioned in the
 * spec! Also, lots of references are made to the 'SST core', but no
 * spec is publicly available, AFAIK.
 *
 * The structure of this driver comes pretty much from the Permedia
 * driver by Ilario Nardinocchi, which in turn is based on skeletonfb.
 * 
 * TODO:
 * - support for 16/32 bpp needs fixing (funky bootup penguin)
 * - multihead support (basically need to support an array of fb_infos)
 * - banshee and voodoo3 now supported -- any others? afaik, the original
 *   voodoo was a 3d-only card, so we won't consider that. what about
 *   voodoo2?
 * - support other architectures (PPC, Alpha); does the fact that the VGA
 *   core can be accessed only thru I/O (not memory mapped) complicate
 *   things?
 *
 * Version history:
 *
 * 0.1.3 (released 1999-11-02) added Attila's panning support, code
 *			       reorg, hwcursor address page size alignment
 *                             (for mmaping both frame buffer and regs),
 *                             and my changes to get rid of hardcoded
 *                             VGA i/o register locations (uses PCI
 *                             configuration info now)
 * 0.1.2 (released 1999-10-19) added Attila Kesmarki's bug fixes and
 *                             improvements
 * 0.1.1 (released 1999-10-07) added Voodoo3 support by Harold Oga.
 * 0.1.0 (released 1999-10-06) initial version
 *
 */

#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/malloc.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/selection.h>
#include <linux/console.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/nvram.h>
#include <linux/kd.h>
#include <linux/vt_kern.h>
#include <asm/io.h>
#include <linux/timer.h>

#ifdef CONFIG_MTRR
#include <asm/mtrr.h>
#endif

#include <video/fbcon.h>
#include <video/fbcon-cfb8.h>
#include <video/fbcon-cfb16.h>
#include <video/fbcon-cfb24.h>
#include <video/fbcon-cfb32.h>

#ifndef LINUX_VERSION_CODE
#include <linux/version.h>
#endif

#ifndef KERNEL_VERSION
#define KERNEL_VERSION(x,y,z) (((x)<<16)+((y)<<8)+(z))
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
#define PCI_DEVICE_ID_3DFX_VOODOO3      0x0005
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
/* nothing? */
#else
#include <linux/spinlock.h>
#endif

/* membase0 register offsets */
#define STATUS		0x00
#define PCIINIT0	0x04
#define SIPMONITOR	0x08
#define LFBMEMORYCONFIG	0x0c
#define MISCINIT0	0x10
#define MISCINIT1	0x14
#define DRAMINIT0	0x18
#define DRAMINIT1	0x1c
#define AGPINIT		0x20
#define TMUGBEINIT	0x24
#define VGAINIT0	0x28
#define VGAINIT1	0x2c
#define DRAMCOMMAND	0x30
#define DRAMDATA	0x34
/* reserved             0x38 */
/* reserved             0x3c */
#define PLLCTRL0	0x40
#define PLLCTRL1	0x44
#define PLLCTRL2	0x48
#define DACMODE		0x4c
#define DACADDR		0x50
#define DACDATA		0x54
#define RGBMAXDELTA	0x58
#define VIDPROCCFG	0x5c
#define HWCURPATADDR	0x60
#define HWCURLOC	0x64
#define HWCURC0		0x68
#define HWCURC1		0x6c
#define VIDINFORMAT	0x70
#define VIDINSTATUS	0x74
#define VIDSERPARPORT	0x78
#define VIDINXDELTA	0x7c
#define VIDININITERR	0x80
#define VIDINYDELTA	0x84
#define VIDPIXBUFTHOLD	0x88
#define VIDCHRMIN	0x8c
#define VIDCHRMAX	0x90
#define VIDCURLIN	0x94
#define VIDSCREENSIZE	0x98
#define VIDOVRSTARTCRD	0x9c
#define VIDOVRENDCRD	0xa0
#define VIDOVRDUDX	0xa4
#define VIDOVRDUDXOFF	0xa8
#define VIDOVRDVDY	0xac
/*  ... */
#define VIDOVRDVDYOFF	0xe0
#define VIDDESKSTART	0xe4
#define VIDDESKSTRIDE	0xe8
#define VIDINADDR0	0xec
#define VIDINADDR1	0xf0
#define VIDINADDR2	0xf4
#define VIDINSTRIDE	0xf8
#define VIDCUROVRSTART	0xfc

#define INTCTRL		(0x00100000 + 0x04)
#define CLIP0MIN	(0x00100000 + 0x08)
#define CLIP0MAX	(0x00100000 + 0x0c)
#define DSTBASE		(0x00100000 + 0x10)
#define DSTFORMAT	(0x00100000 + 0x14)
#define SRCBASE		(0x00100000 + 0x34)
#define COMMANDEXTRA_2D	(0x00100000 + 0x38)
#define CLIP1MIN	(0x00100000 + 0x4c)
#define CLIP1MAX	(0x00100000 + 0x50)
#define SRCFORMAT	(0x00100000 + 0x54)
#define SRCSIZE		(0x00100000 + 0x58)
#define SRCXY		(0x00100000 + 0x5c)
#define COLORBACK	(0x00100000 + 0x60)
#define COLORFORE	(0x00100000 + 0x64)
#define DSTSIZE		(0x00100000 + 0x68)
#define DSTXY		(0x00100000 + 0x6c)
#define COMMAND_2D	(0x00100000 + 0x70)
#define LAUNCH_2D	(0x00100000 + 0x80)

#define COMMAND_3D	(0x00200000 + 0x120)

/* register bitfields (not all, only as needed) */

#define BIT(x) (1UL << (x))

/* COMMAND_2D reg. values */
#define ROP_COPY	0xcc     // src
#define ROP_INVERT      0x55     // NOT dst
#define ROP_XOR         0x66     // src XOR dst

#define AUTOINC_DSTX                    BIT(10)
#define AUTOINC_DSTY                    BIT(11)
#define COMMAND_2D_FILLRECT		0x05
#define COMMAND_2D_S2S_BITBLT		0x01      // screen to screen
#define COMMAND_2D_H2S_BITBLT           0x03       // host to screen


#define COMMAND_3D_NOP			0x00
#define STATUS_RETRACE			BIT(6)
#define STATUS_BUSY			BIT(9)
#define MISCINIT1_CLUT_INV		BIT(0)
#define MISCINIT1_2DBLOCK_DIS		BIT(15)
#define DRAMINIT0_SGRAM_NUM		BIT(26)
#define DRAMINIT0_SGRAM_TYPE		BIT(27)
#define DRAMINIT1_MEM_SDRAM		BIT(30)
#define VGAINIT0_VGA_DISABLE		BIT(0)
#define VGAINIT0_EXT_TIMING		BIT(1)
#define VGAINIT0_8BIT_DAC		BIT(2)
#define VGAINIT0_EXT_ENABLE		BIT(6)
#define VGAINIT0_WAKEUP_3C3		BIT(8)
#define VGAINIT0_LEGACY_DISABLE		BIT(9)
#define VGAINIT0_ALT_READBACK		BIT(10)
#define VGAINIT0_FAST_BLINK		BIT(11)
#define VGAINIT0_EXTSHIFTOUT		BIT(12)
#define VGAINIT0_DECODE_3C6		BIT(13)
#define VGAINIT0_SGRAM_HBLANK_DISABLE	BIT(22)
#define VGAINIT1_MASK			0x1fffff
#define VIDCFG_VIDPROC_ENABLE		BIT(0)
#define VIDCFG_CURS_X11			BIT(1)
#define VIDCFG_HALF_MODE		BIT(4)
#define VIDCFG_DESK_ENABLE		BIT(7)
#define VIDCFG_CLUT_BYPASS		BIT(10)
#define VIDCFG_2X			BIT(26)
#define VIDCFG_HWCURSOR_ENABLE          BIT(27)
#define VIDCFG_PIXFMT_SHIFT		18
#define DACMODE_2X			BIT(0)

/* VGA rubbish, need to change this for multihead support */
#define MISC_W 	0x3c2
#define MISC_R 	0x3cc
#define SEQ_I 	0x3c4
#define SEQ_D	0x3c5
#define CRT_I	0x3d4
#define CRT_D	0x3d5
#define ATT_IW	0x3c0
#define IS1_R	0x3da
#define GRA_I	0x3ce
#define GRA_D	0x3cf

#ifndef FB_ACCEL_3DFX_BANSHEE 
#define FB_ACCEL_3DFX_BANSHEE 31
#endif

#define TDFXF_HSYNC_ACT_HIGH	0x01
#define TDFXF_HSYNC_ACT_LOW	0x02
#define TDFXF_VSYNC_ACT_HIGH	0x04
#define TDFXF_VSYNC_ACT_LOW	0x08
#define TDFXF_LINE_DOUBLE	0x10
#define TDFXF_VIDEO_ENABLE	0x20

#define TDFXF_HSYNC_MASK	0x03
#define TDFXF_VSYNC_MASK	0x0c

//#define TDFXFB_DEBUG 
#ifdef TDFXFB_DEBUG
#define DPRINTK(a,b...) printk(KERN_DEBUG "fb: %s: " a, __FUNCTION__ , ## b)
#else
#define DPRINTK(a,b...)
#endif 

#define PICOS2KHZ(a) (1000000000UL/(a))
#define KHZ2PICOS(a) (1000000000UL/(a))

#define BANSHEE_MAX_PIXCLOCK 270000.0
#define VOODOO3_MAX_PIXCLOCK 300000.0

struct banshee_reg {
  /* VGA rubbish */
  unsigned char att[21];
  unsigned char crt[25];
  unsigned char gra[ 9];
  unsigned char misc[1];
  unsigned char seq[ 5];

  /* Banshee extensions */
  unsigned char ext[2];
  unsigned long vidcfg;
  unsigned long vidpll;
  unsigned long mempll;
  unsigned long gfxpll;
  unsigned long dacmode;
  unsigned long vgainit0;
  unsigned long vgainit1;
  unsigned long screensize;
  unsigned long stride;
  unsigned long cursloc;
  unsigned long curspataddr;
  unsigned long cursc0;
  unsigned long cursc1;
  unsigned long startaddr;
  unsigned long clip0min;
  unsigned long clip0max;
  unsigned long clip1min;
  unsigned long clip1max;
  unsigned long srcbase;
  unsigned long dstbase;
};

struct tdfxfb_par {
  u32 pixclock;

  u32 baseline;

  u32 width;
  u32 height;
  u32 width_virt;
  u32 height_virt;
  u32 lpitch; /* line pitch, in bytes */
  u32 ppitch; /* pixel pitch, in bits */
  u32 bpp;    

  u32 hdispend;
  u32 hsyncsta;
  u32 hsyncend;
  u32 htotal;

  u32 vdispend;
  u32 vsyncsta;
  u32 vsyncend;
  u32 vtotal;

  u32 video;
  u32 accel_flags;
  u32 cmap_len;
};

struct fb_info_tdfx {
  struct fb_info fb_info;

  u16 dev;
  u32 max_pixclock;

  unsigned long regbase_phys;
  unsigned long regbase_virt;
  unsigned long regbase_size;
  unsigned long bufbase_phys;
  unsigned long bufbase_virt;
  unsigned long bufbase_size;
  unsigned long iobase;

  struct { unsigned red, green, blue, pad; } palette[256];
  struct tdfxfb_par default_par;
  struct tdfxfb_par current_par;
  struct display disp;
  struct display_switch dispsw;
#if defined(FBCON_HAS_CFB16) || defined(FBCON_HAS_CFB24) || defined(FBCON_HAS_CFB32)  
  union {
#ifdef FBCON_HAS_CFB16
    u16 cfb16[16];
#endif
#ifdef FBCON_HAS_CFB24
    u32 cfb24[16];
#endif
#ifdef FBCON_HAS_CFB32
    u32 cfb32[16];
#endif
  } fbcon_cmap;
#endif
  struct { 
     int type;             
     int state;             
     int w,u,d;             
     int x,y,redraw;
     unsigned long enable,disable;    
     unsigned long cursorimage;       
     struct timer_list timer;
  } cursor;
 
  spinlock_t DAClock; 
};

/*
 *  Frame buffer device API
 */
static int tdfxfb_open(struct fb_info* info, 
		       int user);
static int tdfxfb_release(struct fb_info* info, 
			  int user);
static int tdfxfb_get_fix(struct fb_fix_screeninfo* fix, 
			  int con,
			  struct fb_info* fb);
static int tdfxfb_get_var(struct fb_var_screeninfo* var, 
			  int con,
			  struct fb_info* fb);
static int tdfxfb_set_var(struct fb_var_screeninfo* var,
			  int con,
			  struct fb_info* fb);
static int tdfxfb_pan_display(struct fb_var_screeninfo* var, 
			      int con,
			      struct fb_info* fb);
static int tdfxfb_get_cmap(struct fb_cmap *cmap, 
			   int kspc, 
			   int con,
			   struct fb_info* info);
static int tdfxfb_set_cmap(struct fb_cmap* cmap, 
			   int kspc, 
			   int con,
			   struct fb_info* info);
static int tdfxfb_ioctl(struct inode* inode, 
			struct file* file, 
			u_int cmd,
			u_long arg, 
			int con, 
			struct fb_info* info);

/*
 *  Interface to the low level console driver
 */
static int  tdfxfb_switch_con(int con, 
			      struct fb_info* fb);
static int  tdfxfb_updatevar(int con, 
			     struct fb_info* fb);
static void tdfxfb_blank(int blank, 
			 struct fb_info* fb);

/*
 *  Internal routines
 */
static void tdfxfb_set_par(const struct tdfxfb_par* par,
			   struct fb_info_tdfx* 
			   info);
static int  tdfxfb_decode_var(const struct fb_var_screeninfo *var,
			      struct tdfxfb_par *par,
			      const struct fb_info_tdfx *info);
static int  tdfxfb_encode_var(struct fb_var_screeninfo* var,
			      const struct tdfxfb_par* par,
			      const struct fb_info_tdfx* info);
static int  tdfxfb_encode_fix(struct fb_fix_screeninfo* fix,
			      const struct tdfxfb_par* par,
			      const struct fb_info_tdfx* info);
static void tdfxfb_set_disp(struct display* disp, 
			    struct fb_info_tdfx* info,
			    int bpp, 
			    int accel);
static int  tdfxfb_getcolreg(u_int regno,
			     u_int* red, 
			     u_int* green, 
			     u_int* blue,
			     u_int* transp, 
			     struct fb_info* fb);
static int  tdfxfb_setcolreg(u_int regno, 
			     u_int red, 
			     u_int green, 
			     u_int blue,
			     u_int transp, 
			     struct fb_info* fb);
static void  tdfxfb_install_cmap(struct display *d, 
				 struct fb_info *info);

static void tdfxfb_hwcursor_init(void);
static void tdfxfb_createcursorshape(struct display* p);
static void tdfxfb_createcursor(struct display * p);  

/*
 * do_xxx: Hardware-specific functions
 */
static void  do_pan_var(struct fb_var_screeninfo* var, struct fb_info_tdfx *i);
static void  do_flashcursor(unsigned long ptr);
static void  do_bitblt(u32 curx, u32 cury, u32 dstx,u32 dsty, 
		      u32 width, u32 height,u32 stride,u32 bpp);
static void  do_fillrect(u32 x, u32 y, u32 w,u32 h, 
			u32 color,u32 stride,u32 bpp,u32 rop);
static void  do_putc(u32 fgx, u32 bgx,struct display *p,
			int c, int yy,int xx);
static void  do_putcs(u32 fgx, u32 bgx,struct display *p,
		     const unsigned short *s,int count, int yy,int xx);
static u32 do_calc_pll(int freq, int* freq_out);
static void  do_write_regs(struct banshee_reg* reg);
static unsigned long do_lfb_size(void);

/*
 *  Interface used by the world
 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
void tdfxfb_init(void);
#else
int tdfxfb_init(void);
#endif
void tdfxfb_setup(char *options, 
		  int *ints);

static int currcon = 0;

static struct fb_ops tdfxfb_ops = {
  tdfxfb_open, 
  tdfxfb_release, 
  tdfxfb_get_fix, 
  tdfxfb_get_var, 
  tdfxfb_set_var,
  tdfxfb_get_cmap, 
  tdfxfb_set_cmap, 
  tdfxfb_pan_display, 
  tdfxfb_ioctl,
  NULL,           // fb_mmap
  NULL            // fb_rasterimg
};

struct mode {
  char* name;
  struct fb_var_screeninfo var;
} mode;

/* 2.3.x kernels have a fb mode database, so supply only one backup default */
struct mode default_mode[] = {
  { "640x480-8@60", /* @ 60 Hz */
    {
      640, 480, 640, 1024, 0, 0, 8, 0,
      {0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0},
      0, FB_ACTIVATE_NOW, -1, -1, FB_ACCELF_TEXT, 
      39722, 40, 24, 32, 11, 96, 2,
      0, FB_VMODE_NONINTERLACED
    }
  }
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
  ,
  { "800x600-8@56", /* @ 56 Hz */
    {
      800, 600, 800, 600, 0, 0, 8, 0,
      {0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0},
      0, FB_ACTIVATE_NOW, -1, -1, FB_ACCELF_TEXT, 
      27778, 128, 24, 22, 1, 72, 2,
      0, FB_VMODE_NONINTERLACED
    }
  },
  { "1024x768-8@60", /* @ 60 Hz */
    {
      1024, 768, 1024, 768, 0, 0, 8, 0,
      {0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0},
      0, FB_ACTIVATE_NOW, -1, -1, FB_ACCELF_TEXT, 
      15385, 168, 8, 29, 3, 144, 6,
      0, FB_VMODE_NONINTERLACED
    }
  },
  { "1280x1024-8@61", /* @ 61 Hz */
    {
      1280, 1024, 1280, 1024, 0, 0, 8, 0,
      {0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0},
      0, FB_ACTIVATE_NOW, -1, -1, FB_ACCELF_TEXT, 
      9091, 200, 48, 26, 1, 184, 3,
      0, FB_VMODE_NONINTERLACED
    }
  },
  { "1024x768-16@60", /* @ 60 Hz */ /* basically for testing */
    {
      1024, 768, 1024, 768, 0, 0, 16, 0,
      {11, 5, 0}, {5, 6, 0}, {0, 5, 0}, {0, 0, 0},
      0, FB_ACTIVATE_NOW, -1, -1, FB_ACCELF_TEXT, 
      15385, 168, 8, 29, 3, 144, 6,
      0, FB_VMODE_NONINTERLACED
    }
  },
  { "1024x768-24@60", /* @ 60 Hz */ 
    {
      1024, 768, 1024, 768, 0, 0, 24, 0,
      {16, 8, 0}, {8, 8, 0}, {0, 8, 0}, {0, 0, 0},
      0, FB_ACTIVATE_NOW, -1, -1, FB_ACCELF_TEXT, 
      15385, 168, 8, 29, 3, 144, 6,
      0, FB_VMODE_NONINTERLACED
    }
  },
  { "1024x768-32@60", /* @ 60 Hz */ 
    {
      1024, 768, 1024, 768, 0, 0, 32, 0,
      {16, 8, 0}, {8, 8, 0}, {0, 8, 0}, {0, 0, 0},
      0, FB_ACTIVATE_NOW, -1, -1, FB_ACCELF_TEXT, 
      15385, 168, 8, 29, 3, 144, 6,
      0, FB_VMODE_NONINTERLACED
    }
  }   
   
#endif
};

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
static int modes = sizeof(default_mode)/sizeof(struct mode);
static int default_mode_index = 0;
#endif

static struct fb_info_tdfx fb_info;

static int  noaccel = 0;
static int  nopan   = 0;
static int  nowrap  = 1;      // not implemented (yet)
static int  inverse = 0;
static int  nomtrr = 0;
static int  nohwcursor = 0;
static char __initdata fontname[40] = { 0 };
static const char *mode_option __initdata = NULL;

/* ------------------------------------------------------------------------- 
 *                      Hardware-specific funcions
 * ------------------------------------------------------------------------- */

#ifdef VGA_REG_IO 
static inline  u8 vga_inb(u32 reg) { return inb(reg); }
static inline u16 vga_inw(u32 reg) { return inw(reg); }
static inline u16 vga_inl(u32 reg) { return inl(reg); }

static inline void vga_outb(u32 reg,  u8 val) { outb(val, reg); }
static inline void vga_outw(u32 reg, u16 val) { outw(val, reg); }
static inline void vga_outl(u32 reg, u32 val) { outl(val, reg); }
#else
static inline  u8 vga_inb(u32 reg) { 
  return inb(fb_info.iobase + reg - 0x300); 
}
static inline u16 vga_inw(u32 reg) { 
  return inw(fb_info.iobase + reg - 0x300); 
}
static inline u16 vga_inl(u32 reg) { 
  return inl(fb_info.iobase + reg - 0x300); 
}

static inline void vga_outb(u32 reg,  u8 val) { 
  outb(val, fb_info.iobase + reg - 0x300); 
}
static inline void vga_outw(u32 reg, u16 val) { 
  outw(val, fb_info.iobase + reg - 0x300); 
}
static inline void vga_outl(u32 reg, u32 val) { 
  outl(val, fb_info.iobase + reg - 0x300); 
}
#endif

static inline void gra_outb(u32 idx, u8 val) {
  vga_outb(GRA_I, idx); vga_outb(GRA_D, val);
}

static inline u8 gra_inb(u32 idx) {
  vga_outb(GRA_I, idx); return vga_inb(GRA_D);
}

static inline void seq_outb(u32 idx, u8 val) {
  vga_outb(SEQ_I, idx); vga_outb(SEQ_D, val);
}

static inline u8 seq_inb(u32 idx) {
  vga_outb(SEQ_I, idx); return vga_inb(SEQ_D);
}

static inline void crt_outb(u32 idx, u8 val) {
  vga_outb(CRT_I, idx); vga_outb(CRT_D, val);
}

static inline u8 crt_inb(u32 idx) {
  vga_outb(CRT_I, idx); return vga_inb(CRT_D);
}

static inline void att_outb(u32 idx, u8 val) {
  unsigned char tmp;
  tmp = vga_inb(IS1_R);
  vga_outb(ATT_IW, idx);
  vga_outb(ATT_IW, val);
}

static inline u8 att_inb(u32 idx) {
  unsigned char tmp;
  tmp = vga_inb(IS1_R);
  vga_outb(ATT_IW, idx);
  return vga_inb(ATT_IW);
}

static inline void vga_disable_video(void) {
  unsigned char s;
  s = seq_inb(0x01) | 0x20;
  seq_outb(0x00, 0x01);
  seq_outb(0x01, s);
  seq_outb(0x00, 0x03);
}

static inline void vga_enable_video(void) {
  unsigned char s;
  s = seq_inb(0x01) & 0xdf;
  seq_outb(0x00, 0x01);
  seq_outb(0x01, s);
  seq_outb(0x00, 0x03);
}

static inline void vga_disable_palette(void) {
  vga_inb(IS1_R);
  vga_outb(ATT_IW, 0x00);
}

static inline void vga_enable_palette(void) {
  vga_inb(IS1_R);
  vga_outb(ATT_IW, 0x20);
}

static inline u32 tdfx_inl(unsigned int reg) {
  return readl(fb_info.regbase_virt + reg);
}

static inline void tdfx_outl(unsigned int reg, u32 val) {
  writel(val, fb_info.regbase_virt + reg);
}

static inline void banshee_make_room(int size) {
  while((tdfx_inl(STATUS) & 0x1f) < size);
}
 
static inline void banshee_wait_idle(void) {
  int i = 0;

  banshee_make_room(1);
  tdfx_outl(COMMAND_3D, COMMAND_3D_NOP);

  while(1) {
    i = (tdfx_inl(STATUS) & STATUS_BUSY) ? 0 : i + 1;
    if(i == 3) break;
  }
}
/*
 * Set the color of a palette entry in 8bpp mode 
 */
static inline void do_setpalentry(unsigned regno, u32 c) {  
   banshee_make_room(2); tdfx_outl(DACADDR,  regno); tdfx_outl(DACDATA,  c); }

/* 
 * Set the starting position of the visible screen to var->yoffset 
 */
static void do_pan_var(struct fb_var_screeninfo* var, struct fb_info_tdfx *i)
{
    u32 addr;
    addr = var->yoffset*i->current_par.lpitch;
    banshee_make_room(1);
    tdfx_outl(VIDDESKSTART, addr);
}
   
/*
 * Invert the hardware cursor image (timerfunc)  
 */
static void do_flashcursor(unsigned long ptr)
{
   struct fb_info_tdfx* i=(struct fb_info_tdfx *)ptr;
   spin_lock(&i->DAClock);
   banshee_make_room(1);
   tdfx_outl( VIDPROCCFG, tdfx_inl(VIDPROCCFG) ^ VIDCFG_HWCURSOR_ENABLE );
   i->cursor.timer.expires=jiffies+HZ/2;
   add_timer(&i->cursor.timer);
   spin_unlock(&i->DAClock);
}

/*
 * FillRect 2D command (solidfill or invert (via ROP_XOR))   
 */
static void do_fillrect(u32 x, u32 y, u32 w, u32 h, 
			u32 color, u32 stride, u32 bpp, u32 rop) {

   u32 fmt= stride | ((bpp+((bpp==8) ? 0 : 8)) << 13); 

   banshee_make_room(5);
   tdfx_outl(DSTFORMAT, fmt);
   tdfx_outl(COLORFORE, color);
   tdfx_outl(COMMAND_2D, COMMAND_2D_FILLRECT | (rop << 24));
   tdfx_outl(DSTSIZE,    w | (h << 16));
   tdfx_outl(LAUNCH_2D,  x | (y << 16));
   banshee_wait_idle();
}

/*
 * Screen-to-Screen BitBlt 2D command (for the bmove fb op.) 
 */

static void do_bitblt(u32 curx, 
			   u32 cury, 
			   u32 dstx,
			   u32 dsty, 
			   u32 width, 
			   u32 height,
			   u32 stride,
			   u32 bpp) {

   u32 blitcmd = COMMAND_2D_S2S_BITBLT | (ROP_COPY << 24);
   u32 fmt= stride | ((bpp+((bpp==8) ? 0 : 8)) << 13); 
   
   if (curx <= dstx) {
     //-X 
     blitcmd |= BIT(14);
     curx += width-1;
     dstx += width-1;
   }
   if (cury <= dsty) {
     //-Y  
     blitcmd |= BIT(15);
     cury += height-1;
     dsty += height-1;
   }
   
   banshee_make_room(6);

   tdfx_outl(SRCFORMAT, fmt);
   tdfx_outl(DSTFORMAT, fmt);
   tdfx_outl(COMMAND_2D, blitcmd); 
   tdfx_outl(DSTSIZE,   width | (height << 16));
   tdfx_outl(DSTXY,     dstx | (dsty << 16));
   tdfx_outl(LAUNCH_2D, curx | (cury << 16)); 
   banshee_wait_idle();
}

static void do_putc(u32 fgx, u32 bgx,
			 struct display *p,
			 int c, int yy,int xx)
{   
   int i;
   int stride=fb_info.current_par.lpitch;
   u32 bpp=fb_info.current_par.bpp;
   int fw=(fontwidth(p)+7)>>3;
   u8 *chardata=p->fontdata+(c&p->charmask)*fontheight(p)*fw;
   u32 fmt= stride | ((bpp+((bpp==8) ? 0 : 8)) << 13); 
   
   xx *= fontwidth(p);
   yy *= fontheight(p);

   banshee_make_room(8+((fontheight(p)*fw+3)>>2) );
   tdfx_outl(COLORFORE, fgx);
   tdfx_outl(COLORBACK, bgx);
   tdfx_outl(SRCXY,     0);
   tdfx_outl(DSTXY,     xx | (yy << 16));
   tdfx_outl(COMMAND_2D, COMMAND_2D_H2S_BITBLT | (ROP_COPY << 24));
   tdfx_outl(SRCFORMAT, 0x400000);
   tdfx_outl(DSTFORMAT, fmt);
   tdfx_outl(DSTSIZE,   fontwidth(p) | (fontheight(p) << 16));
   i=fontheight(p);
   switch (fw) {
    case 1:
     while (i>=4) {
         tdfx_outl(LAUNCH_2D,*(u32*)chardata);
	 chardata+=4;
	 i-=4;
     }
     switch (i) {
      case 0: break;
      case 1:  tdfx_outl(LAUNCH_2D,*chardata); break;
      case 2:  tdfx_outl(LAUNCH_2D,*(u16*)chardata); break;
      case 3:  tdfx_outl(LAUNCH_2D,*(u16*)chardata | ((chardata[3]) << 24)); break;
     }
     break;
   case 2:
     while (i>=2) {
         tdfx_outl(LAUNCH_2D,*(u32*)chardata);
	 chardata+=4;
	 i-=2;
     }
     if (i) tdfx_outl(LAUNCH_2D,*(u16*)chardata); 
     break;
   default:
     // Is there a font with width more that 16 pixels ?
     for (i=fontheight(p);i>0;i--) {
	 tdfx_outl(LAUNCH_2D,*(u32*)chardata);
	 chardata+=4;
     }
     break;
   }
   banshee_wait_idle();
}

static void do_putcs(u32 fgx, u32 bgx,
			  struct display *p,
			  const unsigned short *s,
			  int count, int yy,int xx)
{   
   int i;
   int stride=fb_info.current_par.lpitch;
   u32 bpp=fb_info.current_par.bpp;
   int fw=(fontwidth(p)+7)>>3;
   int w=fontwidth(p);
   int h=fontheight(p);
   int regsneed=1+((h*fw+3)>>2);
   u32 fmt= stride | ((bpp+((bpp==8) ? 0 : 8)) << 13); 

   xx *= w;
   yy = (yy*h) << 16;
   banshee_make_room(8);

   tdfx_outl(COMMAND_3D, COMMAND_3D_NOP);
   tdfx_outl(COLORFORE, fgx);
   tdfx_outl(COLORBACK, bgx);
   tdfx_outl(SRCFORMAT, 0x400000);
   tdfx_outl(DSTFORMAT, fmt);
   tdfx_outl(DSTSIZE, w | (h << 16));
   tdfx_outl(SRCXY,     0);
   tdfx_outl(COMMAND_2D, COMMAND_2D_H2S_BITBLT | (ROP_COPY << 24));
   
   while (count--) {
      u8 *chardata=p->fontdata+(scr_readw(s++) & p->charmask)*h*fw;
   
      banshee_make_room(regsneed);
      tdfx_outl(DSTXY, xx | yy);
      xx+=w;
      
      i=h;
      switch (fw) {
       case 1:
        while (i>=4) {
           tdfx_outl(LAUNCH_2D,*(u32*)chardata);
	   chardata+=4;
	   i-=4;
        }
        switch (i) {
          case 0: break;
          case 1:  tdfx_outl(LAUNCH_2D,*chardata); break;
          case 2:  tdfx_outl(LAUNCH_2D,*(u16*)chardata); break;
          case 3:  tdfx_outl(LAUNCH_2D,*(u16*)chardata | ((chardata[3]) << 24)); break;
        }
        break;
       case 2:
        while (i>=2) {
         tdfx_outl(LAUNCH_2D,*(u32*)chardata);
	 chardata+=4;
	 i-=2;
        }
        if (i) tdfx_outl(LAUNCH_2D,*(u16*)chardata); 
        break;
       default:
       // Is there a font with width more that 16 pixels ?
        for (;i>0;i--) {
	  tdfx_outl(LAUNCH_2D,*(u32*)chardata);
	  chardata+=4;
        }
        break;
     }
   }
   banshee_wait_idle();
}

static u32 do_calc_pll(int freq, int* freq_out) {
  int m, n, k, best_m, best_n, best_k, f_cur, best_error;
  int fref = 14318;
  
  /* this really could be done with more intelligence --
     255*63*4 = 64260 iterations is silly */
  best_error = freq;
  best_n = best_m = best_k = 0;
  for(n = 1; n < 256; n++) {
    for(m = 1; m < 64; m++) {
      for(k = 0; k < 4; k++) {
	f_cur = fref*(n + 2)/(m + 2)/(1 << k);
	if(abs(f_cur - freq) < best_error) {
	  best_error = abs(f_cur-freq);
	  best_n = n;
	  best_m = m;
	  best_k = k;
	}
      }
    }
  }
  n = best_n;
  m = best_m;
  k = best_k;
  *freq_out = fref*(n + 2)/(m + 2)/(1 << k);

  return (n << 8) | (m << 2) | k;
}

static void do_write_regs(struct banshee_reg* reg) {
  int i;

  banshee_wait_idle();

  tdfx_outl(MISCINIT1, tdfx_inl(MISCINIT1) | 0x01);

  crt_outb(0x11, crt_inb(0x11) & 0x7f); /* CRT unprotect */

  banshee_make_room(3);
  tdfx_outl(VGAINIT1,      reg->vgainit1 &  0x001FFFFF);
  tdfx_outl(VIDPROCCFG,    reg->vidcfg   & ~0x00000001);
#if 0
  tdfx_outl(PLLCTRL1,      reg->mempll);
  tdfx_outl(PLLCTRL2,      reg->gfxpll);
#endif
  tdfx_outl(PLLCTRL0,      reg->vidpll);

  vga_outb(MISC_W, reg->misc[0x00] | 0x01);

  for(i = 0; i < 5; i++)
    seq_outb(i, reg->seq[i]);

  for(i = 0; i < 25; i++)
    crt_outb(i, reg->crt[i]);

  for(i = 0; i < 9; i++)
    gra_outb(i, reg->gra[i]);

  for(i = 0; i < 21; i++)
    att_outb(i, reg->att[i]);

  crt_outb(0x1a, reg->ext[0]);
  crt_outb(0x1b, reg->ext[1]);

  vga_enable_palette();
  vga_enable_video();

  banshee_make_room(11);
  tdfx_outl(VGAINIT0,      reg->vgainit0);
  tdfx_outl(DACMODE,       reg->dacmode);
  tdfx_outl(VIDDESKSTRIDE, reg->stride);
  if (nohwcursor) {
     tdfx_outl(HWCURPATADDR,  0);
  } else {
     tdfx_outl(HWCURPATADDR,  reg->curspataddr);
     tdfx_outl(HWCURC0,       reg->cursc0);
     tdfx_outl(HWCURC1,       reg->cursc1);
     tdfx_outl(HWCURLOC,      reg->cursloc);
  }
   
  tdfx_outl(VIDSCREENSIZE, reg->screensize);
  tdfx_outl(VIDDESKSTART,  reg->startaddr);
  tdfx_outl(VIDPROCCFG,    reg->vidcfg);
  tdfx_outl(VGAINIT1,      reg->vgainit1);  

  banshee_make_room(8);
  tdfx_outl(SRCBASE,         reg->srcbase);
  tdfx_outl(DSTBASE,         reg->dstbase);
  tdfx_outl(COMMANDEXTRA_2D, 0);
  tdfx_outl(CLIP0MIN,        0);
  tdfx_outl(CLIP0MAX,        0x0fff0fff);
  tdfx_outl(CLIP1MIN,        0);
  tdfx_outl(CLIP1MAX,        0x0fff0fff);
  tdfx_outl(SRCXY, 0);

  banshee_wait_idle();
}

static unsigned long do_lfb_size(void) {
  u32 draminit0 = 0;
  u32 draminit1 = 0;
  u32 miscinit1 = 0;
  u32 lfbsize   = 0;
  int sgram_p     = 0;

  if(!((fb_info.dev == PCI_DEVICE_ID_3DFX_BANSHEE) ||
       (fb_info.dev == PCI_DEVICE_ID_3DFX_VOODOO3)))
    return 0;

  draminit0 = tdfx_inl(DRAMINIT0);  
  draminit1 = tdfx_inl(DRAMINIT1);
   
  sgram_p = (draminit1 & DRAMINIT1_MEM_SDRAM) ? 0 : 1;
  
  lfbsize = sgram_p ?
    (((draminit0 & DRAMINIT0_SGRAM_NUM)  ? 2 : 1) * 
     ((draminit0 & DRAMINIT0_SGRAM_TYPE) ? 8 : 4) * 1024 * 1024) :
    16 * 1024 * 1024;

  /* disable block writes for SDRAM (why?) */
  miscinit1 = tdfx_inl(MISCINIT1);
  miscinit1 |= sgram_p ? 0 : MISCINIT1_2DBLOCK_DIS;
  miscinit1 |= MISCINIT1_CLUT_INV;

  banshee_make_room(1); 
  tdfx_outl(MISCINIT1, miscinit1);

  return lfbsize;
}

/* ------------------------------------------------------------------------- 
 *              Hardware independent part, interface to the world
 * ------------------------------------------------------------------------- */

#define tdfx_cfb24_putc  tdfx_cfb32_putc
#define tdfx_cfb24_putcs tdfx_cfb32_putcs
#define tdfx_cfb24_clear tdfx_cfb32_clear

static void tdfx_cfbX_clear_margins(struct vc_data* conp, struct display* p,
				    int bottom_only)
{
   unsigned int cw=fontwidth(p);
   unsigned int ch=fontheight(p);
   unsigned int rw=p->var.xres % cw;   // it be in a non-standard mode or not?
   unsigned int bh=p->var.yres % ch;
   unsigned int rs=p->var.xres - rw;
   unsigned int bs=p->var.yres - bh;

   if (!bottom_only && rw) { 
      do_fillrect( p->var.xoffset+rs, 0, 
		  rw, p->var.yres_virtual, 0, 
		  fb_info.current_par.lpitch,
		  fb_info.current_par.bpp, ROP_COPY);
   }
   
   if (bh) { 
      do_fillrect( p->var.xoffset, p->var.yoffset+bs, 
		  rs, bh, 0, 
		  fb_info.current_par.lpitch,
		  fb_info.current_par.bpp, ROP_COPY);
   }
}
static void tdfx_cfbX_bmove(struct display* p, 
				int sy, 
				int sx, 
				int dy,
				int dx, 
				int height, 
				int width) {
   do_bitblt(fontwidth(p)*sx,
		 fontheight(p)*sy, 
		 fontwidth(p)*dx,
		 fontheight(p)*dy, 
		 fontwidth(p)*width, 
		 fontheight(p)*height, 
		 fb_info.current_par.lpitch, 
		 fb_info.current_par.bpp);
}
static void tdfx_cfb8_putc(struct vc_data* conp,
			       struct display* p,
			       int c, int yy,int xx)
{   
   u32 fgx,bgx;
   fgx=attr_fgcol(p, c);
   bgx=attr_bgcol(p, c);
   do_putc( fgx,bgx,p,c,yy,xx );
}

static void tdfx_cfb16_putc(struct vc_data* conp,
			       struct display* p,
			       int c, int yy,int xx)
{   
   u32 fgx,bgx;
   fgx=((u16*)p->dispsw_data)[attr_fgcol(p,c)];
   bgx=((u16*)p->dispsw_data)[attr_bgcol(p,c)];
   do_putc( fgx,bgx,p,c,yy,xx );
}

static void tdfx_cfb32_putc(struct vc_data* conp,
			       struct display* p,
			       int c, int yy,int xx)
{   
   u32 fgx,bgx;
   fgx=((u32*)p->dispsw_data)[attr_fgcol(p,c)];
   bgx=((u32*)p->dispsw_data)[attr_bgcol(p,c)];
   do_putc( fgx,bgx,p,c,yy,xx );
}
static void tdfx_cfb8_putcs(struct vc_data* conp,
			    struct display* p,
			    const unsigned short *s,int count,int yy,int xx)
{
   u32 fgx,bgx;
   fgx=attr_fgcol(p, *s);
   bgx=attr_bgcol(p, *s);
   do_putcs( fgx,bgx,p,s,count,yy,xx );
}
static void tdfx_cfb16_putcs(struct vc_data* conp,
			    struct display* p,
			    const unsigned short *s,int count,int yy,int xx)
{
   u32 fgx,bgx;
   fgx=((u16*)p->dispsw_data)[attr_fgcol(p,*s)];
   bgx=((u16*)p->dispsw_data)[attr_bgcol(p,*s)];
   do_putcs( fgx,bgx,p,s,count,yy,xx );
}
static void tdfx_cfb32_putcs(struct vc_data* conp,
			    struct display* p,
			    const unsigned short *s,int count,int yy,int xx)
{
   u32 fgx,bgx;
   fgx=((u32*)p->dispsw_data)[attr_fgcol(p,*s)];
   bgx=((u32*)p->dispsw_data)[attr_bgcol(p,*s)];
   do_putcs( fgx,bgx,p,s,count,yy,xx );
}

static void tdfx_cfb8_clear(struct vc_data* conp, 
				struct display* p, 
				int sy,
				int sx, 
				int height, 
				int width) {
  u32 bg;

  bg = attr_bgcol_ec(p,conp);
  do_fillrect(fontwidth(p)*sx,
		   fontheight(p)*sy,
		   fontwidth(p)*width, 
		   fontheight(p)*height,
		   bg, 
		   fb_info.current_par.lpitch, 
		   fb_info.current_par.bpp,ROP_COPY);
}

static void tdfx_cfb16_clear(struct vc_data* conp, 
				struct display* p, 
				int sy,
				int sx, 
				int height, 
				int width) {
  u32 bg;

  bg = ((u16*)p->dispsw_data)[attr_bgcol_ec(p,conp)];
  do_fillrect(fontwidth(p)*sx,
		   fontheight(p)*sy,
		   fontwidth(p)*width, 
		   fontheight(p)*height,
		   bg, 
		   fb_info.current_par.lpitch, 
		   fb_info.current_par.bpp,ROP_COPY);
}

static void tdfx_cfb32_clear(struct vc_data* conp, 
				struct display* p, 
				int sy,
				int sx, 
				int height, 
				int width) {
  u32 bg;

  bg = ((u32*)p->dispsw_data)[attr_bgcol_ec(p,conp)];
  do_fillrect(fontwidth(p)*sx,
		   fontheight(p)*sy,
		   fontwidth(p)*width, 
		   fontheight(p)*height,
		   bg, 
		   fb_info.current_par.lpitch, 
		   fb_info.current_par.bpp,ROP_COPY);
}
static void tdfx_cfbX_revc(struct display *p, int xx, int yy)
{
   int bpp=fb_info.current_par.bpp;
   
   do_fillrect( xx * fontwidth(p), yy * fontheight(p), 
	        fontwidth(p), fontheight(p), 
	        (bpp==8) ? 0x0f : 0xffffffff, 
	        fb_info.current_par.lpitch, bpp, ROP_XOR);
   
}
static void tdfx_cfbX_cursor(struct display *p, int mode, int x, int y) 
{
   unsigned long flags;
   int tip;
   struct fb_info_tdfx *info=(struct fb_info_tdfx *)p->fb_info;
     
   tip=p->conp->vc_cursor_type & CUR_HWMASK;
   if (mode==CM_ERASE) {
	if (info->cursor.state != CM_ERASE) {
	     spin_lock_irqsave(&info->DAClock,flags);
	     info->cursor.state=CM_ERASE;
	     del_timer(&(info->cursor.timer));
	     tdfx_outl(VIDPROCCFG,info->cursor.disable); 
	     spin_unlock_irqrestore(&info->DAClock,flags);
	}
	return;
   }
   if ((p->conp->vc_cursor_type & CUR_HWMASK) != info->cursor.type)
	 tdfxfb_createcursor(p);
   x *= fontwidth(p);
   y *= fontheight(p);
   y -= p->var.yoffset;
   spin_lock_irqsave(&info->DAClock,flags);
   if ((x!=info->cursor.x) ||
      (y!=info->cursor.y) ||
      (info->cursor.redraw)) {
          info->cursor.x=x;
	  info->cursor.y=y;
	  info->cursor.redraw=0;
	  x += 63;
	  y += 63;    
          banshee_make_room(2);
	  tdfx_outl(VIDPROCCFG, info->cursor.disable);
	  tdfx_outl(HWCURLOC, (y << 16) + x);
   }
   info->cursor.state = CM_DRAW;
   mod_timer(&info->cursor.timer,jiffies+HZ/2);
   banshee_make_room(1);
   tdfx_outl(VIDPROCCFG, info->cursor.enable);
   spin_unlock_irqrestore(&info->DAClock,flags);
   return;     
}
#ifdef FBCON_HAS_CFB8
static struct display_switch fbcon_banshee8 = {
   fbcon_cfb8_setup, 
   tdfx_cfbX_bmove, 
   tdfx_cfb8_clear, 
   tdfx_cfb8_putc,
   tdfx_cfb8_putcs, 
   tdfx_cfbX_revc,   
   tdfx_cfbX_cursor, 
   NULL, 
   tdfx_cfbX_clear_margins,
   FONTWIDTH(8)
};
#endif
#ifdef FBCON_HAS_CFB16
static struct display_switch fbcon_banshee16 = {
   fbcon_cfb16_setup, 
   tdfx_cfbX_bmove, 
   tdfx_cfb16_clear, 
   tdfx_cfb16_putc,
   tdfx_cfb16_putcs, 
   tdfx_cfbX_revc, 
   tdfx_cfbX_cursor, 
   NULL, 
   tdfx_cfbX_clear_margins,
   FONTWIDTH(8)
};
#endif
#ifdef FBCON_HAS_CFB24
static struct display_switch fbcon_banshee24 = {
   fbcon_cfb24_setup, 
   tdfx_cfbX_bmove, 
   tdfx_cfb24_clear, 
   tdfx_cfb24_putc,
   tdfx_cfb24_putcs, 
   tdfx_cfbX_revc, 
   tdfx_cfbX_cursor, 
   NULL, 
   tdfx_cfbX_clear_margins,
   FONTWIDTH(8)
};
#endif
#ifdef FBCON_HAS_CFB32
static struct display_switch fbcon_banshee32 = {
   fbcon_cfb32_setup, 
   tdfx_cfbX_bmove, 
   tdfx_cfb32_clear, 
   tdfx_cfb32_putc,
   tdfx_cfb32_putcs, 
   tdfx_cfbX_revc, 
   tdfx_cfbX_cursor, 
   NULL, 
   tdfx_cfbX_clear_margins,
   FONTWIDTH(8)
};
#endif

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

static void tdfxfb_set_par(const struct tdfxfb_par* par,
			   struct fb_info_tdfx*     info) {
  struct fb_info_tdfx* i = (struct fb_info_tdfx*)info;
  struct banshee_reg reg;
  u32 cpp;
  u32 hd, hs, he, ht, hbs, hbe;
  u32 vd, vs, ve, vt, vbs, vbe;
  u32 wd;
  int fout;
  int freq;
   
  memset(&reg, 0, sizeof(reg));

  cpp = (par->bpp + 7)/8;
  
  wd = (par->hdispend >> 3) - 1;

  hd  = (par->hdispend >> 3) - 1;
  hs  = (par->hsyncsta >> 3) - 1;
  he  = (par->hsyncend >> 3) - 1;
  ht  = (par->htotal   >> 3) - 1;
  hbs = hd;
  hbe = ht;

  vd  = par->vdispend - 1;
  vs  = par->vsyncsta - 1;
  ve  = par->vsyncend - 1;
  vt  = par->vtotal   - 2;
  vbs = vd;
  vbe = vt;
  
  /* this is all pretty standard VGA register stuffing */
  reg.misc[0x00] = 
    0x0f |
    (par->hdispend < 400 ? 0xa0 :
     par->hdispend < 480 ? 0x60 :
     par->hdispend < 768 ? 0xe0 : 0x20);
     
  reg.gra[0x00] = 0x00;
  reg.gra[0x01] = 0x00;
  reg.gra[0x02] = 0x00;
  reg.gra[0x03] = 0x00;
  reg.gra[0x04] = 0x00;
  reg.gra[0x05] = 0x40;
  reg.gra[0x06] = 0x05;
  reg.gra[0x07] = 0x0f;
  reg.gra[0x08] = 0xff;

  reg.att[0x00] = 0x00;
  reg.att[0x01] = 0x01;
  reg.att[0x02] = 0x02;
  reg.att[0x03] = 0x03;
  reg.att[0x04] = 0x04;
  reg.att[0x05] = 0x05;
  reg.att[0x06] = 0x06;
  reg.att[0x07] = 0x07;
  reg.att[0x08] = 0x08;
  reg.att[0x09] = 0x09;
  reg.att[0x0a] = 0x0a;
  reg.att[0x0b] = 0x0b;
  reg.att[0x0c] = 0x0c;
  reg.att[0x0d] = 0x0d;
  reg.att[0x0e] = 0x0e;
  reg.att[0x0f] = 0x0f;
  reg.att[0x10] = 0x41;
  reg.att[0x11] = 0x00;
  reg.att[0x12] = 0x0f;
  reg.att[0x13] = 0x00;
  reg.att[0x14] = 0x00;

  reg.seq[0x00] = 0x03;
  reg.seq[0x01] = 0x01; /* fixme: clkdiv2? */
  reg.seq[0x02] = 0x0f;
  reg.seq[0x03] = 0x00;
  reg.seq[0x04] = 0x0e;

  reg.crt[0x00] = ht - 4;
  reg.crt[0x01] = hd;
  reg.crt[0x02] = hbs;
  reg.crt[0x03] = 0x80 | (hbe & 0x1f);
  reg.crt[0x04] = hs;
  reg.crt[0x05] = 
    ((hbe & 0x20) << 2) | 
    (he & 0x1f);
  reg.crt[0x06] = vt;
  reg.crt[0x07] = 
    ((vs & 0x200) >> 2) |
    ((vd & 0x200) >> 3) |
    ((vt & 0x200) >> 4) |
    0x10 |
    ((vbs & 0x100) >> 5) |
    ((vs  & 0x100) >> 6) |
    ((vd  & 0x100) >> 7) |
    ((vt  & 0x100) >> 8);
  reg.crt[0x08] = 0x00;
  reg.crt[0x09] = 
    0x40 |
    ((vbs & 0x200) >> 4);
  reg.crt[0x0a] = 0x00;
  reg.crt[0x0b] = 0x00;
  reg.crt[0x0c] = 0x00;
  reg.crt[0x0d] = 0x00;
  reg.crt[0x0e] = 0x00;
  reg.crt[0x0f] = 0x00;
  reg.crt[0x10] = vs;
  reg.crt[0x11] = 
    (ve & 0x0f) |
    0x20;
  reg.crt[0x12] = vd;
  reg.crt[0x13] = wd;
  reg.crt[0x14] = 0x00;
  reg.crt[0x15] = vbs;
  reg.crt[0x16] = vbe + 1; 
  reg.crt[0x17] = 0xc3;
  reg.crt[0x18] = 0xff;
  
  /* Banshee's nonvga stuff */
  reg.ext[0x00] = (((ht  & 0x100) >> 8) | 
		   ((hd  & 0x100) >> 6) |
		   ((hbs & 0x100) >> 4) |
		   ((hbe &  0x40) >> 1) |
		   ((hs  & 0x100) >> 2) |
		   ((he  &  0x20) << 2)); 
  reg.ext[0x01] = (((vt  & 0x400) >> 10) |
		   ((vd  & 0x400) >>  8) | 
		   ((vbs & 0x400) >>  6) |
		   ((vbe & 0x400) >>  4));
  
  reg.vgainit0 = 
    VGAINIT0_8BIT_DAC     |
    VGAINIT0_EXT_ENABLE   |
    VGAINIT0_WAKEUP_3C3   |
    VGAINIT0_ALT_READBACK |
    VGAINIT0_EXTSHIFTOUT;
  reg.vgainit1 = tdfx_inl(VGAINIT1) & 0x1fffff;

  reg.vidcfg = 
    VIDCFG_VIDPROC_ENABLE |
    VIDCFG_DESK_ENABLE    |
    VIDCFG_CURS_X11 |
    ((cpp - 1) << VIDCFG_PIXFMT_SHIFT) |
    (cpp != 1 ? VIDCFG_CLUT_BYPASS : 0);
  
  fb_info.cursor.enable=reg.vidcfg | VIDCFG_HWCURSOR_ENABLE;
  fb_info.cursor.disable=reg.vidcfg;
   
  reg.stride    = par->width*cpp;
  reg.cursloc   = 0;
   
  reg.cursc0    = 0; 
  reg.cursc1    = 0xffffff;
   
  reg.curspataddr = fb_info.cursor.cursorimage;   
  
  reg.startaddr = par->baseline*reg.stride;
  reg.srcbase   = reg.startaddr;
  reg.dstbase   = reg.startaddr;

  /* PLL settings */
  freq = par->pixclock;

  reg.dacmode &= ~DACMODE_2X;
  reg.vidcfg  &= ~VIDCFG_2X;
  if(freq > i->max_pixclock/2) {
    freq = freq > i->max_pixclock ? i->max_pixclock : freq;
    reg.dacmode |= DACMODE_2X;
    reg.vidcfg  |= VIDCFG_2X;
  }
  reg.vidpll = do_calc_pll(freq, &fout);
#if 0
  reg.mempll = do_calc_pll(..., &fout);
  reg.gfxpll = do_calc_pll(..., &fout);
#endif

  reg.screensize = par->width | (par->height << 12);
  reg.vidcfg &= ~VIDCFG_HALF_MODE;

  do_write_regs(&reg);

  i->current_par = *par;

}

static int tdfxfb_decode_var(const struct fb_var_screeninfo* var,
			     struct tdfxfb_par*              par,
			     const struct fb_info_tdfx*      info) {
  struct fb_info_tdfx* i = (struct fb_info_tdfx*)info;

  if(var->bits_per_pixel != 8  &&
     var->bits_per_pixel != 16 &&
     var->bits_per_pixel != 24 &&
     var->bits_per_pixel != 32) {
    DPRINTK("depth not supported: %u\n", var->bits_per_pixel);
    return -EINVAL;
  }

  if((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
    DPRINTK("interlace not supported\n");
    return -EINVAL;
  }

  if(var->xoffset) {
    DPRINTK("xoffset not supported\n");
    return -EINVAL;
  }

  if(var->xres != var->xres_virtual) {
    DPRINTK("virtual x resolution != physical x resolution not supported\n");
    return -EINVAL;
  }

  if(var->yres > var->yres_virtual) {
    DPRINTK("virtual y resolution < physical y resolution not possible\n");
    return -EINVAL;
  }

  /* fixme: does Voodoo3 support interlace? Banshee doesn't */
  if((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
    DPRINTK("interlace not supported\n");
    return -EINVAL;
  }

  memset(par, 0, sizeof(struct tdfxfb_par));

  switch(i->dev) {
  case PCI_DEVICE_ID_3DFX_BANSHEE:
  case PCI_DEVICE_ID_3DFX_VOODOO3:
    par->width       = (var->xres + 15) & ~15; /* could sometimes be 8 */
    par->width_virt  = par->width;
    par->height      = var->yres;
    par->height_virt = var->yres_virtual;
    par->bpp         = var->bits_per_pixel;
    par->ppitch      = var->bits_per_pixel;
    par->lpitch      = par->width* ((par->ppitch+7)>>3);
    par->cmap_len    = (par->bpp == 8) ? 256 : 16;
     
    par->baseline = 0;

    if(par->width < 320 || par->width > 2048) {
      DPRINTK("width not supported: %u\n", par->width);
      return -EINVAL;
    }
    if(par->height < 200 || par->height > 2048) {
      DPRINTK("height not supported: %u\n", par->height);
      return -EINVAL;
    }
    if(par->lpitch*par->height_virt > i->bufbase_size) {
      DPRINTK("no memory for screen (%ux%ux%u)\n",
	      par->width, par->height_virt, par->bpp);
      return -EINVAL;
    }
    par->pixclock = PICOS2KHZ(var->pixclock);
    if(par->pixclock > i->max_pixclock) {
      DPRINTK("pixclock too high (%uKHz)\n", par->pixclock);
      return -EINVAL;
    }

    par->hdispend = var->xres;
    par->hsyncsta = par->hdispend + var->right_margin;
    par->hsyncend = par->hsyncsta + var->hsync_len;
    par->htotal   = par->hsyncend + var->left_margin;

    par->vdispend = var->yres;
    par->vsyncsta = par->vdispend + var->lower_margin;
    par->vsyncend = par->vsyncsta + var->vsync_len;
    par->vtotal   = par->vsyncend + var->upper_margin;

    if(var->sync & FB_SYNC_HOR_HIGH_ACT)
      par->video |= TDFXF_HSYNC_ACT_HIGH;
    else
      par->video |= TDFXF_HSYNC_ACT_LOW;
    if(var->sync & FB_SYNC_VERT_HIGH_ACT)
      par->video |= TDFXF_VSYNC_ACT_HIGH;
    else
      par->video |= TDFXF_VSYNC_ACT_LOW;
    if((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE)
      par->video |= TDFXF_LINE_DOUBLE;
    if(var->activate == FB_ACTIVATE_NOW)
      par->video |= TDFXF_VIDEO_ENABLE;
  }

  if(var->accel_flags & FB_ACCELF_TEXT)
    par->accel_flags = FB_ACCELF_TEXT;
  else
    par->accel_flags = 0;

  return 0;
}

static int tdfxfb_encode_var(struct fb_var_screeninfo* var,
			    const struct tdfxfb_par* par,
			    const struct fb_info_tdfx* info) {
  struct fb_var_screeninfo v;

  memset(&v, 0, sizeof(struct fb_var_screeninfo));
  v.xres_virtual   = par->width_virt;
  v.yres_virtual   = par->height_virt;
  v.xres           = par->width;
  v.yres           = par->height;
  v.right_margin   = par->hsyncsta - par->hdispend;
  v.hsync_len      = par->hsyncend - par->hsyncsta;
  v.left_margin    = par->htotal   - par->hsyncend;
  v.lower_margin   = par->vsyncsta - par->vdispend;
  v.vsync_len      = par->vsyncend - par->vsyncsta;
  v.upper_margin   = par->vtotal   - par->vsyncend;
  v.bits_per_pixel = par->bpp;
  switch(par->bpp) {
  case 8:
    v.red.length = v.green.length = v.blue.length = 8;
    break;
  case 16:
    v.red.offset   = 11;
    v.red.length   = 5;
    v.green.offset = 5;
    v.green.length = 6;
    v.blue.offset  = 0;
    v.blue.length  = 5;
    break;
  case 24:
    v.red.offset=16;
    v.green.offset=8;
    v.blue.offset=0;
    v.red.length = v.green.length = v.blue.length = 8;
  case 32:
    v.red.offset   = 16;
    v.green.offset = 8;
    v.blue.offset  = 0;
    v.red.length = v.green.length = v.blue.length = 8;
    break;
  }
  v.height = v.width = -1;
  v.pixclock = KHZ2PICOS(par->pixclock);
  if((par->video & TDFXF_HSYNC_MASK) == TDFXF_HSYNC_ACT_HIGH)
    v.sync |= FB_SYNC_HOR_HIGH_ACT;
  if((par->video & TDFXF_VSYNC_MASK) == TDFXF_VSYNC_ACT_HIGH)
    v.sync |= FB_SYNC_VERT_HIGH_ACT;
  if(par->video & TDFXF_LINE_DOUBLE)
    v.vmode = FB_VMODE_DOUBLE;
  *var = v;
  return 0;
}

static int tdfxfb_open(struct fb_info* info, 
		       int user) {
  MOD_INC_USE_COUNT;
  return(0);
}

static int tdfxfb_release(struct fb_info* info, 
			  int user) {
  MOD_DEC_USE_COUNT;
  return(0);
}


static int tdfxfb_encode_fix(struct fb_fix_screeninfo*  fix,
			     const struct tdfxfb_par*   par,
			     const struct fb_info_tdfx* info) {
  memset(fix, 0, sizeof(struct fb_fix_screeninfo));

  switch(info->dev) {
  case PCI_DEVICE_ID_3DFX_BANSHEE:
  case PCI_DEVICE_ID_3DFX_VOODOO3:
    strcpy(fix->id, 
	   info->dev == PCI_DEVICE_ID_3DFX_BANSHEE 
	   ? "3Dfx Banshee"
	   : "3Dfx Voodoo3");
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
    fix->smem_start  = (char*)info->bufbase_phys;
    fix->smem_len    = info->bufbase_size;
    fix->mmio_start  = (char*)info->regbase_phys;
    fix->mmio_len    = info->regbase_size;
#else
    fix->smem_start  = info->bufbase_phys;
    fix->smem_len    = info->bufbase_size;
    fix->mmio_start  = info->regbase_phys;
    fix->mmio_len    = info->regbase_size;
#endif
    fix->accel       = FB_ACCEL_3DFX_BANSHEE;
    fix->type        = FB_TYPE_PACKED_PIXELS;
    fix->type_aux    = 0;
    fix->line_length = par->lpitch;
    fix->visual      = (par->bpp == 8) 
                       ? FB_VISUAL_PSEUDOCOLOR
                       : FB_VISUAL_DIRECTCOLOR;

    fix->xpanstep    = 0; 
    fix->ypanstep    = nopan ? 0 : 1;
    fix->ywrapstep   = nowrap ? 0 : 1;

    break;
  default:
    return -EINVAL;
  }

  return 0;
}

static int tdfxfb_get_fix(struct fb_fix_screeninfo *fix, 
			  int con,
			  struct fb_info *fb) {
  const struct fb_info_tdfx *info = (struct fb_info_tdfx*)fb;
  struct tdfxfb_par par;

  if(con == -1)
    par = info->default_par;
  else
    tdfxfb_decode_var(&fb_display[con].var, &par, info);
  tdfxfb_encode_fix(fix, &par, info);
  return 0;
}

static int tdfxfb_get_var(struct fb_var_screeninfo *var, 
			  int con,
			  struct fb_info *fb) {
  const struct fb_info_tdfx *info = (struct fb_info_tdfx*)fb;

  if(con == -1)
    tdfxfb_encode_var(var, &info->default_par, info);
  else
    *var = fb_display[con].var;
  return 0;
}
 
static void tdfxfb_set_disp(struct display *disp, 
			    struct fb_info_tdfx *info,
			    int bpp, 
			    int accel) {

  if (disp->dispsw && disp->conp) 
     fb_con.con_cursor(disp->conp, CM_ERASE);
  switch(bpp) {
#ifdef FBCON_HAS_CFB8
  case 8:
    info->dispsw = noaccel ? fbcon_cfb8 : fbcon_banshee8;
    disp->dispsw = &info->dispsw;
    if (nohwcursor) fbcon_banshee8.cursor = NULL;
    break;
#endif
#ifdef FBCON_HAS_CFB16
  case 16:
    info->dispsw = noaccel ? fbcon_cfb16 : fbcon_banshee16;
    disp->dispsw = &info->dispsw;
    disp->dispsw_data = info->fbcon_cmap.cfb16;
    if (nohwcursor) fbcon_banshee16.cursor = NULL;
    break;
#endif
#ifdef FBCON_HAS_CFB24
  case 24:
    info->dispsw = noaccel ? fbcon_cfb24 : fbcon_banshee24; 
    disp->dispsw = &info->dispsw;
    disp->dispsw_data = info->fbcon_cmap.cfb24;
    if (nohwcursor) fbcon_banshee24.cursor = NULL;
    break;
#endif
#ifdef FBCON_HAS_CFB32
  case 32:
    info->dispsw = noaccel ? fbcon_cfb32 : fbcon_banshee32;
    disp->dispsw = &info->dispsw;
    disp->dispsw_data = info->fbcon_cmap.cfb32;
    if (nohwcursor) fbcon_banshee32.cursor = NULL;
    break;
#endif
  default:
    info->dispsw = fbcon_dummy;
    disp->dispsw = &info->dispsw;
  }
   
}

static int tdfxfb_set_var(struct fb_var_screeninfo *var, 
			  int con,
			  struct fb_info *fb) {
   struct fb_info_tdfx *info = (struct fb_info_tdfx*)fb;
   struct tdfxfb_par par;
   struct display *display;
   int oldxres, oldyres, oldvxres, oldvyres, oldbpp, oldaccel, accel, err;
   int activate = var->activate;
   int j,k;
   
   if(con >= 0)
     display = &fb_display[con];
   else
     display = fb->disp;	/* used during initialization */
   
   if((err = tdfxfb_decode_var(var, &par, info)))
     return err;
   
   tdfxfb_encode_var(var, &par, info);
   
   if((activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {
      oldxres  = display->var.xres;
      oldyres  = display->var.yres;
      oldvxres = display->var.xres_virtual;
      oldvyres = display->var.yres_virtual;
      oldbpp   = display->var.bits_per_pixel;
      oldaccel = display->var.accel_flags;
      display->var = *var;
      if(con < 0                         ||
	 oldxres  != var->xres           || 
	 oldyres  != var->yres           ||
	 oldvxres != var->xres_virtual   || 
	 oldvyres != var->yres_virtual   ||
	 oldbpp   != var->bits_per_pixel || 
	 oldaccel != var->accel_flags) {
	 struct fb_fix_screeninfo fix;
	 
	 tdfxfb_encode_fix(&fix, &par, info);
	 display->screen_base    = (char *)info->bufbase_virt;
	 display->visual         = fix.visual;
	 display->type           = fix.type;
	 display->type_aux       = fix.type_aux;
	 display->ypanstep       = fix.ypanstep;
	 display->ywrapstep      = fix.ywrapstep;
	 display->line_length    = fix.line_length;
	 display->next_line      = fix.line_length;
	 display->can_soft_blank = 1;
	 display->inverse        = inverse;
	 accel = var->accel_flags & FB_ACCELF_TEXT;
	 tdfxfb_set_disp(display, info, par.bpp, accel);
	 
	 if(nopan) display->scrollmode = SCROLL_YREDRAW;
	
	 if (info->fb_info.changevar)
	   (*info->fb_info.changevar)(con);
      }
      if (var->bits_per_pixel==8)
	for(j = 0; j < 16; j++) {
	   k = color_table[j];
	   fb_info.palette[j].red   = default_red[k];
	   fb_info.palette[j].green = default_grn[k];
	   fb_info.palette[j].blue  = default_blu[k];
	}
      
      del_timer(&(info->cursor.timer)); 
      fb_info.cursor.state=CM_ERASE; 
      if(!info->fb_info.display_fg ||
	 info->fb_info.display_fg->vc_num == con ||
	 con < 0)
	tdfxfb_set_par(&par, info);
      if (!nohwcursor) 
	if (display && display->conp)
	  tdfxfb_createcursor( display );
      info->cursor.redraw=1;
      if(oldbpp != var->bits_per_pixel || con < 0) {
	 if((err = fb_alloc_cmap(&display->cmap, 0, 0)))
	   return err;
	 tdfxfb_install_cmap(display, &(info->fb_info));
      }
   }
  
   return 0;
}

static int tdfxfb_pan_display(struct fb_var_screeninfo* var, 
			      int con,
			      struct fb_info* fb) {
  struct fb_info_tdfx* i = (struct fb_info_tdfx*)fb;

  if(nopan)                return -EINVAL;
  if(var->xoffset)         return -EINVAL;
  if(var->yoffset > var->yres_virtual)   return -EINVAL;
  if(nowrap && 
     (var->yoffset + var->yres > var->yres_virtual)) return -EINVAL;
 
  if (con==currcon)
    do_pan_var(var,i);
   
  fb_display[con].var.xoffset=var->xoffset;
  fb_display[con].var.yoffset=var->yoffset; 
  return 0;
}

static int tdfxfb_get_cmap(struct fb_cmap *cmap, 
			   int kspc, 
			   int con,
			   struct fb_info *fb) {

   struct fb_info_tdfx* i = (struct fb_info_tdfx*)fb;
   struct display *d=(con<0) ? fb->disp : fb_display + con;
   
   if(con == currcon) {
      /* current console? */
      return fb_get_cmap(cmap, kspc, tdfxfb_getcolreg, fb);
   } else if(d->cmap.len) {
      /* non default colormap? */
      fb_copy_cmap(&d->cmap, cmap, kspc ? 0 : 2);
   } else {
      fb_copy_cmap(fb_default_cmap(i->current_par.cmap_len), cmap, kspc ? 0 : 2);
   }
   return 0;
}

static int tdfxfb_set_cmap(struct fb_cmap *cmap, 
			   int kspc, 
			   int con,
			   struct fb_info *fb) {
   struct display *d=(con<0) ? fb->disp : fb_display + con;
   struct fb_info_tdfx *i = (struct fb_info_tdfx*)fb;

   int cmap_len= (i->current_par.bpp == 8) ? 256 : 16;
   if (d->cmap.len!=cmap_len) {
      int err;
      if((err = fb_alloc_cmap(&d->cmap, cmap_len, 0)))
	return err;
   }
   if(con == currcon) {
      /* current console? */
      return fb_set_cmap(cmap, kspc, tdfxfb_setcolreg, fb);
   } else {
      fb_copy_cmap(cmap, &d->cmap, kspc ? 0 : 1);
   }
   return 0;
}

static int tdfxfb_ioctl(struct inode *inode, 
			struct file *file, 
			u_int cmd,
			u_long arg, 
			int con, 
			struct fb_info *fb) {
/* These IOCTLs ar just for testing only... 
   switch (cmd) {
    case 0x4680: 
      nowrap=nopan=0;
      return 0;
    case 0x4681:
      nowrap=nopan=1;
      return 0;
   }*/
   return -EINVAL;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
__initfunc(void tdfxfb_init(void)) {
#else
int __init tdfxfb_init(void) {
#endif
  struct pci_dev *pdev = NULL;
  struct fb_var_screeninfo var;
  
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
  if(!pcibios_present()) return;
#else
  if(!pcibios_present()) return -ENXIO;
#endif

  while ((pdev = pci_find_device(PCI_VENDOR_ID_3DFX, PCI_ANY_ID, pdev))) {
    if(((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) &&
       ((pdev->device == PCI_DEVICE_ID_3DFX_BANSHEE) ||
	(pdev->device == PCI_DEVICE_ID_3DFX_VOODOO3))) {
      char* name = pdev->device == PCI_DEVICE_ID_3DFX_BANSHEE
	? "Banshee"
	: "Voodoo3";

      fb_info.dev   = pdev->device;
      fb_info.max_pixclock = 
	pdev->device == PCI_DEVICE_ID_3DFX_BANSHEE 
	? BANSHEE_MAX_PIXCLOCK
	: VOODOO3_MAX_PIXCLOCK;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
      fb_info.regbase_phys = pdev->base_address[0] & PCI_BASE_ADDRESS_MEM_MASK;
      fb_info.regbase_size = 1 << 24;
      fb_info.regbase_virt = 
	(u32)ioremap_nocache(fb_info.regbase_phys, 1 << 24);
      if(!fb_info.regbase_virt) {
	printk("fb: Can't remap %s register area.\n", name);
	return;
      }

      fb_info.bufbase_phys = pdev->base_address[1] & PCI_BASE_ADDRESS_MEM_MASK;
      if(!(fb_info.bufbase_size = do_lfb_size())) {
	printk("fb: Can't count %s memory.\n", name);
	iounmap((void*)fb_info.regbase_virt);
	return;
      }
      fb_info.bufbase_virt    = 
	(u32)ioremap_nocache(fb_info.bufbase_phys, fb_info.bufbase_size);
      if(!fb_info.regbase_virt) {
	printk("fb: Can't remap %s framebuffer.\n", name);
	iounmap((void*)fb_info.regbase_virt);
	return;
      }

      fb_info.iobase = pdev->base_address[2] & PCI_BASE_ADDRESS_IO_MASK;
#else
      fb_info.regbase_phys = pdev->resource[0].start;
      fb_info.regbase_size = 1 << 24;
      fb_info.regbase_virt = 
	(u32)ioremap_nocache(fb_info.regbase_phys, 1 << 24);
      if(!fb_info.regbase_virt) {
	printk("fb: Can't remap %s register area.\n", name);
	return -ENXIO;
      }
      
      fb_info.bufbase_phys = pdev->resource[1].start;
      if(!(fb_info.bufbase_size = do_lfb_size())) {
	iounmap((void*)fb_info.regbase_virt);
	printk("fb: Can't count %s memory.\n", name);
	return -ENXIO;
      }
      fb_info.bufbase_virt    = 
	(u32)ioremap_nocache(fb_info.bufbase_phys, fb_info.bufbase_size);
      if(!fb_info.regbase_virt) {
	printk("fb: Can't remap %s framebuffer.\n", name);
	iounmap((void*)fb_info.regbase_virt);
	return -ENXIO;
      }

      fb_info.iobase = pdev->resource[2].start;
#endif
      
      printk("fb: %s memory = %ldK\n", name, fb_info.bufbase_size >> 10);

#ifdef CONFIG_MTRR
       if (!nomtrr) {
	  if (mtrr_add(fb_info.bufbase_phys, fb_info.bufbase_size, 
		       MTRR_TYPE_WRCOMB, 1)>=0)
	    printk("fb: MTRR's  turned on\n");
       }
#endif	  	 

      /* clear framebuffer memory */
      memset_io(fb_info.bufbase_virt, 0, fb_info.bufbase_size);
      currcon = -1;
      if (!nohwcursor) tdfxfb_hwcursor_init();
       
      fb_info.cursor.timer.function = do_flashcursor; 
      fb_info.cursor.state = CM_ERASE;
      fb_info.cursor.timer.prev = fb_info.cursor.timer.next=NULL;
      fb_info.cursor.timer.data = (unsigned long)(&fb_info);
      spin_lock_init(&fb_info.DAClock);
       
      strcpy(fb_info.fb_info.modename, "3Dfx "); 
      strcat(fb_info.fb_info.modename, name);
      fb_info.fb_info.changevar  = NULL;
      fb_info.fb_info.node       = -1;
      fb_info.fb_info.fbops      = &tdfxfb_ops;
      fb_info.fb_info.disp       = &fb_info.disp;
      strcpy(fb_info.fb_info.fontname, fontname);
      fb_info.fb_info.switch_con = &tdfxfb_switch_con;
      fb_info.fb_info.updatevar  = &tdfxfb_updatevar;
      fb_info.fb_info.blank      = &tdfxfb_blank;
      fb_info.fb_info.flags      = FBINFO_FLAG_DEFAULT;
      
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
      var = default_mode[default_mode_index < modes
			? default_mode_index
			: 0].var;
#else
      memset(&var, 0, sizeof(var));
      if(!mode_option || 
	 !fb_find_mode(&var, &fb_info.fb_info, mode_option, NULL, 0, NULL, 8))
	var = default_mode[0].var;
#endif
      
      if(noaccel) var.accel_flags &= ~FB_ACCELF_TEXT;
      else var.accel_flags |= FB_ACCELF_TEXT;
      
      if(tdfxfb_decode_var(&var, &fb_info.default_par, &fb_info)) {
	/* ugh -- can't use the mode from the mode db. (or command line),
	   so try the default */

	printk("tdfxfb: "
	       "can't decode the supplied video mode, using default\n");

	var = default_mode[0].var;
	if(noaccel) var.accel_flags &= ~FB_ACCELF_TEXT;
	else var.accel_flags |= FB_ACCELF_TEXT;
      
	if(tdfxfb_decode_var(&var, &fb_info.default_par, &fb_info)) {
	  /* this is getting really bad!... */
	  printk("tdfxfb: can't decode default video mode\n");
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
	  return;
#else
	  return -ENXIO;
#endif
	}
      }
      
      fb_info.disp.screen_base    = (void*)fb_info.bufbase_virt;
      fb_info.disp.var            = var;
      
      if(tdfxfb_set_var(&var, -1, &fb_info.fb_info)) {
	printk("tdfxfb: can't set default video mode\n");
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
	return;
#else
	return -ENXIO;
#endif
      }
      
      if(register_framebuffer(&fb_info.fb_info) < 0) {
	printk("tdfxfb: can't register framebuffer\n");
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
	return;
#else
	return -ENXIO;
#endif
      }

      printk("fb%d: %s frame buffer device\n", 
	     GET_FB_IDX(fb_info.fb_info.node),
	     fb_info.fb_info.modename);
      
      MOD_INC_USE_COUNT;
      
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
      return;
#else
      return 0;
#endif
    }
  }

  /* hmm, no frame suitable buffer found ... */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
  return;
#else
  return -ENXIO;
#endif
}

void tdfxfb_setup(char *options, 
		  int *ints) {
  char* this_opt;

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

  for(this_opt = strtok(options, ","); 
      this_opt;
      this_opt = strtok(NULL, ",")) {
    if(!strcmp(this_opt, "inverse")) {
      inverse = 1;
      fb_invert_cmaps();
    } else if(!strcmp(this_opt, "noaccel")) {
      noaccel = nopan = nowrap = nohwcursor = 1; 
    } else if(!strcmp(this_opt, "nopan")) {
      nopan = 1;
    } else if(!strcmp(this_opt, "nowrap")) {
      nowrap = 1;
    } else if (!strcmp(this_opt, "nohwcursor")) {
      nohwcursor = 1;
#ifdef CONFIG_MTRR
    } else if (!strcmp(this_opt, "nomtrr")) {
      nomtrr = 1;
#endif
    } else if (!strncmp(this_opt, "font:", 5)) {
      strncpy(fontname, this_opt + 5, 40);
    } else {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,1)
      int i;
      for(i = 0; i < modes; i++) {
	if(!strcmp(this_opt, default_mode[i].name)) {
	  default_mode_index = i;
	}
      }
#else
      mode_option = this_opt;
#endif
    }
  } 
}

static int tdfxfb_switch_con(int con, 
			     struct fb_info *fb) {
   struct fb_info_tdfx *info = (struct fb_info_tdfx*)fb;
   struct tdfxfb_par par;

   /* Do we have to save the colormap? */
   if (currcon>=0)
     if(fb_display[currcon].cmap.len)
       fb_get_cmap(&fb_display[currcon].cmap, 1, tdfxfb_getcolreg, fb);
   
   currcon = con;
   fb_display[currcon].var.activate = FB_ACTIVATE_NOW; 
   tdfxfb_decode_var(&fb_display[con].var, &par, info);
   tdfxfb_set_par(&par, info);
   if (fb_display[con].dispsw && fb_display[con].conp)
     fb_con.con_cursor(fb_display[con].conp, CM_ERASE);
   
   del_timer(&(info->cursor.timer));
   fb_info.cursor.state=CM_ERASE; 
   
   if (!nohwcursor) 
     if (fb_display[con].conp)
       tdfxfb_createcursor( &fb_display[con] );
   
   info->cursor.redraw=1;
   
   tdfxfb_set_disp(&fb_display[con], 
		   info, 
		   par.bpp,
		   par.accel_flags & FB_ACCELF_TEXT);
   
   tdfxfb_install_cmap(&fb_display[con], fb);
   tdfxfb_updatevar(con, fb);
   
   return 1;
}

/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */
static void tdfxfb_blank(int blank, 
			 struct fb_info *fb) {
  u32 dacmode, state = 0, vgablank = 0;

  dacmode = tdfx_inl(DACMODE);

  switch(blank) {
  case 0: /* Screen: On; HSync: On, VSync: On */    
    state    = 0;
    vgablank = 0;
    break;
  case 1: /* Screen: Off; HSync: On, VSync: On */
    state    = 0;
    vgablank = 1;
    break;
  case 2: /* Screen: Off; HSync: On, VSync: Off */
    state    = BIT(3);
    vgablank = 1;
    break;
  case 3: /* Screen: Off; HSync: Off, VSync: On */
    state    = BIT(1);
    vgablank = 1;
    break;
  case 4: /* Screen: Off; HSync: Off, VSync: Off */
    state    = BIT(1) | BIT(3);
    vgablank = 1;
    break;
  }

  dacmode &= ~(BIT(1) | BIT(3));
  dacmode |= state;
  banshee_make_room(1); 
  tdfx_outl(DACMODE, dacmode);
  if(vgablank) 
    vga_disable_video();
  else
    vga_enable_video();

  return;
}

static int  tdfxfb_updatevar(int con, 
			     struct fb_info* fb) {

   struct fb_info_tdfx* i = (struct fb_info_tdfx*)fb;
   if ((con==currcon) && (!nopan)) 
     do_pan_var(&fb_display[con].var,i);
   return 0;
}

static int tdfxfb_getcolreg(unsigned        regno, 
			    unsigned*       red, 
			    unsigned*       green,
			    unsigned*       blue, 
			    unsigned*       transp,
			    struct fb_info* fb) {
   struct fb_info_tdfx* i = (struct fb_info_tdfx*)fb;

   if (regno > i->current_par.cmap_len) return 1;
   
   *red    = i->palette[regno].red; 
   *green  = i->palette[regno].green; 
   *blue   = i->palette[regno].blue; 
   *transp = 0;
   
   return 0;
}

static int tdfxfb_setcolreg(unsigned        regno, 
			    unsigned        red, 
			    unsigned        green,
			    unsigned        blue, 
			    unsigned        transp,
			    struct fb_info* info) {
   struct fb_info_tdfx* i = (struct fb_info_tdfx*)info;
   u32 rgbcol;
   if (regno >= i->current_par.cmap_len) return 1;
   
   i->palette[regno].red    = red;
   i->palette[regno].green  = green;
   i->palette[regno].blue   = blue;
   
   switch(i->current_par.bpp) {
#ifdef FBCON_HAS_CFB8
    case 8:
      rgbcol=(((u32)red   & 0xff00) << 8) |
	(((u32)green & 0xff00) << 0) |
	(((u32)blue  & 0xff00) >> 8);
      do_setpalentry(regno,rgbcol);
      break;
#endif
#ifdef FBCON_HAS_CFB16
    case 16:
      i->fbcon_cmap.cfb16[regno] =
	(((u32)red   & 0xf800) >> 0) |
	(((u32)green & 0xfc00) >> 5) |
	(((u32)blue  & 0xf800) >> 11);
	 break;
#endif
#ifdef FBCON_HAS_CFB24
    case 24:
      i->fbcon_cmap.cfb24[regno] =
	(((u32)red & 0xff00) << 8) |
	(((u32)green & 0xff00) << 0) |
	(((u32)blue & 0xff00) >> 8);
      break;
#endif
#ifdef FBCON_HAS_CFB32
    case 32:
      i->fbcon_cmap.cfb32[regno] =
	(((u32)red   & 0xff00) << 8) |
	(((u32)green & 0xff00) << 0) |
	(((u32)blue  & 0xff00) >> 8);
      break;
#endif
    default:
      DPRINTK("bad depth %u\n", i->current_par.bpp);
      break;
   }
   return 0;
}

static void tdfxfb_install_cmap(struct display *d,struct fb_info *info) 
{
   struct fb_info_tdfx* i = (struct fb_info_tdfx*)info;

   if(d->cmap.len) {
      fb_set_cmap(&(d->cmap), 1, tdfxfb_setcolreg, info);
   } else {
      fb_set_cmap(fb_default_cmap(i->current_par.cmap_len), 1, 
		  tdfxfb_setcolreg, info);
   }
}

static void tdfxfb_createcursorshape(struct display* p) 
{
   unsigned int h,cu,cd;
   
   h=fontheight(p);
   cd=h;
   if (cd >= 10) cd --; 
   fb_info.cursor.type=p->conp->vc_cursor_type & CUR_HWMASK;
   switch (fb_info.cursor.type) {
      case CUR_NONE: 
	cu=cd; 
	break;
      case CUR_UNDERLINE: 
	cu=cd - 2; 
	break;
      case CUR_LOWER_THIRD: 
	cu=(h * 2) / 3; 
	break;
      case CUR_LOWER_HALF: 
	cu=h / 2; 
	break;
      case CUR_TWO_THIRDS: 
	cu=h / 3; 
	break;
      case CUR_BLOCK:
      default:
	cu=0;
	cd = h;
	break;
   }
   fb_info.cursor.w=fontwidth(p);
   fb_info.cursor.u=cu;
   fb_info.cursor.d=cd;
}
   
static void tdfxfb_createcursor(struct display *p)
{
   u8 *cursorbase;
   u32 xline;
   unsigned int i;
   unsigned int h,to;

   tdfxfb_createcursorshape(p);
   xline = (1 << fb_info.cursor.w)-1;
   cursorbase=(u8*)fb_info.bufbase_virt;
   h=fb_info.cursor.cursorimage;     
   
   to=fb_info.cursor.u;
   for (i = 0; i < to; i++) {
	writel(0, cursorbase+h);
	writel(0, cursorbase+h+4);
	writel(~0, cursorbase+h+8);
	writel(~0, cursorbase+h+12);
	h += 16;
   }
   
   to = fb_info.cursor.d;
   
   for (; i < to; i++) {
	writel(xline, cursorbase+h);
	writel(0, cursorbase+h+4);
	writel(~0, cursorbase+h+8);
	writel(~0, cursorbase+h+12);
	h += 16;
   }
   
   for (; i < 64; i++) {
	writel(0, cursorbase+h);
	writel(0, cursorbase+h+4);
	writel(~0, cursorbase+h+8);
	writel(~0, cursorbase+h+12);
	h += 16;
   }
}
   
static void tdfxfb_hwcursor_init(void)
{
   unsigned int start;
   start = (fb_info.bufbase_size-1024) & PAGE_MASK;
   fb_info.bufbase_size=start; 
   fb_info.cursor.cursorimage=fb_info.bufbase_size;
   printk("tdfxfb: reserving 1024 bytes for the hwcursor at 0x%08lx\n",
	  fb_info.regbase_virt+fb_info.cursor.cursorimage);
}

 
