/*
 *  linux/drivers/video/tgafb.c -- DEC 21030 TGA frame buffer device
 *
 *	Copyright (C) 1997 Geert Uytterhoeven
 *
 *  This driver is partly based on the original TGA console driver
 *
 *	Copyright (C) 1995  Jay Estabrook
 *
 *  This file is subject to the terms and conditions of the GNU General Public
 *  License. See the file COPYING in the main directory of this archive for
 *  more details.
 */


/* KNOWN PROBLEMS/TO DO ===================================================== *
 *
 *	- How to set a single color register?
 *
 *	- We don't have support for CFB32 yet (fbcon-cfb32.c)
 *
 *	- Hardware cursor (useful for other graphics boards too)
 *
 * KNOWN PROBLEMS/TO DO ==================================================== */


#include <linux/module.h>
#include <linux/sched.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/init.h>
#include <linux/bios32.h>
#include <linux/pci.h>
#include <linux/selection.h>
#include <asm/io.h>


/* TGA hardware description (minimal) */
/*
 * Offsets within Memory Space
 */
#define	TGA_ROM_OFFSET			0x0000000
#define	TGA_REGS_OFFSET			0x0100000
#define	TGA_8PLANE_FB_OFFSET		0x0200000
#define	TGA_24PLANE_FB_OFFSET		0x0804000
#define	TGA_24PLUSZ_FB_OFFSET		0x1004000

#define	TGA_PLANEMASK_REG		0x0028
#define	TGA_MODE_REG			0x0030
#define	TGA_RASTEROP_REG		0x0034
#define	TGA_DEEP_REG			0x0050
#define	TGA_PIXELMASK_REG		0x005c
#define	TGA_CURSOR_BASE_REG		0x0060
#define	TGA_HORIZ_REG			0x0064
#define	TGA_VERT_REG			0x0068
#define	TGA_BASE_ADDR_REG		0x006c
#define	TGA_VALID_REG			0x0070
#define	TGA_CURSOR_XY_REG		0x0074
#define	TGA_INTR_STAT_REG		0x007c
#define	TGA_RAMDAC_SETUP_REG		0x00c0
#define	TGA_BLOCK_COLOR0_REG		0x0140
#define	TGA_BLOCK_COLOR1_REG		0x0144
#define	TGA_CLOCK_REG			0x01e8
#define	TGA_RAMDAC_REG			0x01f0
#define	TGA_CMD_STAT_REG		0x01f8

/*
 * useful defines for managing the BT485 on the 8-plane TGA
 */
#define	BT485_READ_BIT			0x01
#define	BT485_WRITE_BIT			0x00

#define	BT485_ADDR_PAL_WRITE		0x00
#define	BT485_DATA_PAL			0x02
#define	BT485_PIXEL_MASK		0x04
#define	BT485_ADDR_PAL_READ		0x06
#define	BT485_ADDR_CUR_WRITE		0x08
#define	BT485_DATA_CUR			0x0a
#define	BT485_CMD_0			0x0c
#define	BT485_ADDR_CUR_READ		0x0e
#define	BT485_CMD_1			0x10
#define	BT485_CMD_2			0x12
#define	BT485_STATUS			0x14
#define	BT485_CMD_3			0x14
#define	BT485_CUR_RAM			0x16
#define	BT485_CUR_LOW_X			0x18
#define	BT485_CUR_HIGH_X		0x1a
#define	BT485_CUR_LOW_Y			0x1c
#define	BT485_CUR_HIGH_Y		0x1e

/*
 * useful defines for managing the BT463 on the 24-plane TGAs
 */
#define	BT463_ADDR_LO		0x0
#define	BT463_ADDR_HI		0x1
#define	BT463_REG_ACC		0x2
#define	BT463_PALETTE		0x3

#define	BT463_CUR_CLR_0		0x0100
#define	BT463_CUR_CLR_1		0x0101

#define	BT463_CMD_REG_0		0x0201
#define	BT463_CMD_REG_1		0x0202
#define	BT463_CMD_REG_2		0x0203

#define	BT463_READ_MASK_0	0x0205
#define	BT463_READ_MASK_1	0x0206
#define	BT463_READ_MASK_2	0x0207
#define	BT463_READ_MASK_3	0x0208

#define	BT463_BLINK_MASK_0	0x0209
#define	BT463_BLINK_MASK_1	0x020a
#define	BT463_BLINK_MASK_2	0x020b
#define	BT463_BLINK_MASK_3	0x020c

#define	BT463_WINDOW_TYPE_BASE	0x0300


int tga_type;
unsigned int tga_mem_base;
unsigned long tga_fb_base;
unsigned long tga_regs_base;

static unsigned int fb_offset_presets[4] __initdata = {
	TGA_8PLANE_FB_OFFSET,
	TGA_24PLANE_FB_OFFSET,
	0xffffffff,
	TGA_24PLUSZ_FB_OFFSET
};

static unsigned int deep_presets[4] __initdata = {
  0x00014000,
  0x0001440d,
  0xffffffff,
  0x0001441d
};

static unsigned int rasterop_presets[4] __initdata = {
  0x00000003,
  0x00000303,
  0xffffffff,
  0x00000303
};

static unsigned int mode_presets[4] __initdata = {
  0x00002000,
  0x00002300,
  0xffffffff,
  0x00002300
};

static unsigned int base_addr_presets[4] __initdata = {
  0x00000000,
  0x00000001,
  0xffffffff,
  0x00000001
};

#define TGA_WRITE_REG(v,r) \
	{ writel((v), tga_regs_base+(r)); mb(); }

#define TGA_READ_REG(r) readl(tga_regs_base+(r))

#define BT485_WRITE(v,r) \
	  TGA_WRITE_REG((r),TGA_RAMDAC_SETUP_REG);		\
	  TGA_WRITE_REG(((v)&0xff)|((r)<<8),TGA_RAMDAC_REG);

#define BT463_LOAD_ADDR(a) \
	TGA_WRITE_REG(BT463_ADDR_LO<<2, TGA_RAMDAC_SETUP_REG); \
	TGA_WRITE_REG((BT463_ADDR_LO<<10)|((a)&0xff), TGA_RAMDAC_REG); \
	TGA_WRITE_REG(BT463_ADDR_HI<<2, TGA_RAMDAC_SETUP_REG); \
	TGA_WRITE_REG((BT463_ADDR_HI<<10)|(((a)>>8)&0xff), TGA_RAMDAC_REG);

#define BT463_WRITE(m,a,v) \
	BT463_LOAD_ADDR((a)); \
	TGA_WRITE_REG(((m)<<2),TGA_RAMDAC_SETUP_REG); \
	TGA_WRITE_REG(((m)<<10)|((v)&0xff),TGA_RAMDAC_REG);


unsigned char PLLbits[7] __initdata = { 0x80, 0x04, 0x00, 0x24, 0x44, 0x80, 0xb8 };

const unsigned long bt485_cursor_source[64] __initdata = {
  0x00000000000000ff,0x00000000000000ff,0x00000000000000ff,0x00000000000000ff,
  0x00000000000000ff,0x00000000000000ff,0x00000000000000ff,0x00000000000000ff,
  0x00000000000000ff,0x00000000000000ff,0x00000000000000ff,0x00000000000000ff,
  0x00000000000000ff,0x00000000000000ff,0x00000000000000ff,0x00000000000000ff,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

const unsigned int bt463_cursor_source[256] __initdata = {
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0xffff0000, 0x00000000, 0x00000000, 0x00000000,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};


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

static int currcon = 0;
static struct display disp;
static struct fb_info fb_info;
static struct { u_char red, green, blue, pad; } palette[256];
static char tgafb_name[16] = "DEC TGA ";

static struct fb_fix_screeninfo fb_fix;
static struct fb_var_screeninfo fb_var = { 0, };


    /*
     *  Interface used by the world
     */

void tgafb_video_setup(char *options, int *ints);

static int tgafb_open(int fbidx);
static int tgafb_release(int fbidx);
static int tgafb_get_fix(struct fb_fix_screeninfo *fix, int con);
static int tgafb_get_var(struct fb_var_screeninfo *var, int con);
static int tgafb_set_var(struct fb_var_screeninfo *var, int con);
static int tgafb_pan_display(struct fb_var_screeninfo *var, int con);
static int tgafb_get_cmap(struct fb_cmap *cmap, int kspc, int con);
static int tgafb_set_cmap(struct fb_cmap *cmap, int kspc, int con);
static int tgafb_ioctl(struct inode *inode, struct file *file, u_int cmd,
		       u_long arg, int con);


    /*
     *  Interface to the low level console driver
     */

unsigned long tgafb_init(unsigned long mem_start);
static int tgafbcon_switch(int con);
static int tgafbcon_updatevar(int con);
static void tgafbcon_blank(int blank);
static int tgafbcon_setcmap(struct fb_cmap *cmap, int con);


    /*
     *  Internal routines
     */

static int tgafb_getcolreg(u_int regno, u_int *red, u_int *green, u_int *blue,
			   u_int *transp);
static int tgafb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
			   u_int transp);
#if 1
static void tga_update_palette(void);
#endif
static void do_install_cmap(int con);


static struct fb_ops tgafb_ops = {
    tgafb_open, tgafb_release, tgafb_get_fix, tgafb_get_var, tgafb_set_var,
    tgafb_get_cmap, tgafb_set_cmap, tgafb_pan_display, tgafb_ioctl
};


    /*
     *  Open/Release the frame buffer device
     */

static int tgafb_open(int fbidx)                                       
{
    /*                                                                     
     *  Nothing, only a usage count for the moment                          
     */                                                                    

    MOD_INC_USE_COUNT;
    return(0);                              
}
        
static int tgafb_release(int fbidx)
{
    MOD_DEC_USE_COUNT;
    return(0);                                                    
}


    /*
     *  Get the Fixed Part of the Display
     */

static int tgafb_get_fix(struct fb_fix_screeninfo *fix, int con)
{
    memcpy(fix, &fb_fix, sizeof(fb_fix));
    return 0;
}


    /*
     *  Get the User Defined Part of the Display
     */

static int tgafb_get_var(struct fb_var_screeninfo *var, int con)
{
    memcpy(var, &fb_var, sizeof(fb_var));
    return 0;
}


    /*
     *  Set the User Defined Part of the Display
     */

static int tgafb_set_var(struct fb_var_screeninfo *var, int con)
{
    struct display *display;
    int oldbpp = -1, err;

    if (con >= 0)
	display = &fb_display[con];
    else
	display = &disp;	/* used during initialization */

    if (var->xres > fb_var.xres || var->yres > fb_var.yres ||
	var->xres_virtual > fb_var.xres_virtual ||
	var->yres_virtual > fb_var.yres_virtual ||
	var->bits_per_pixel > fb_var.bits_per_pixel ||
	var->nonstd ||
	(var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED)
	return -EINVAL;
    memcpy(var, &fb_var, sizeof(fb_var));

    if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {
	oldbpp = display->var.bits_per_pixel;
	display->var = *var;
    }
    if (oldbpp != var->bits_per_pixel) {
	if ((err = fb_alloc_cmap(&display->cmap, 0, 0)))
	    return err;
	do_install_cmap(con);
    }
    return 0;
}


    /*
     *  Pan or Wrap the Display
     *
     *  This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
     */

static int tgafb_pan_display(struct fb_var_screeninfo *var, int con)
{
    if (var->xoffset || var->yoffset)
	return -EINVAL;
    else
	return 0;
}

    /*
     *  Get the Colormap
     */

static int tgafb_get_cmap(struct fb_cmap *cmap, int kspc, int con)
{
    if (con == currcon) /* current console? */
	return fb_get_cmap(cmap, &fb_display[con].var, kspc, tgafb_getcolreg);
    else if (fb_display[con].cmap.len) /* non default colormap? */
	fb_copy_cmap(&fb_display[con].cmap, cmap, kspc ? 0 : 2);
    else
	fb_copy_cmap(fb_default_cmap(fb_display[con].var.bits_per_pixel),
		     cmap, kspc ? 0 : 2);
    return 0;
}

    /*
     *  Set the Colormap
     */

static int tgafb_set_cmap(struct fb_cmap *cmap, int kspc, int con)
{
    int err;

    if (!fb_display[con].cmap.len) {	/* no colormap allocated? */
	if ((err = fb_alloc_cmap(&fb_display[con].cmap,
				 1<<fb_display[con].var.bits_per_pixel, 0)))
	    return err;
    }
    if (con == currcon) {		/* current console? */
	err = fb_set_cmap(cmap, &fb_display[con].var, kspc, tgafb_setcolreg);
#if 1
	tga_update_palette();
#endif
	return err;
    } else
	fb_copy_cmap(cmap, &fb_display[con].cmap, kspc ? 0 : 1);
    return 0;
}


static int tgafb_ioctl(struct inode *inode, struct file *file, u_int cmd,
		       u_long arg, int con)
{
    return -EINVAL;
}


    /*
     *  Initialisation
     */

__initfunc(unsigned long tgafb_init(unsigned long mem_start))
{
    unsigned char pci_bus, pci_devfn;
    int status;
    int i, j, temp, err;
    unsigned char *cbp;

    status = pcibios_find_device (PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TGA,
				  0, &pci_bus, &pci_devfn);
    if (status == PCIBIOS_DEVICE_NOT_FOUND)
	return mem_start;

    /*
     * read BASE_REG_0 for memory address
     */
    pcibios_read_config_dword(pci_bus, pci_devfn, PCI_BASE_ADDRESS_0,
			      &tga_mem_base);
    tga_mem_base &= ~15;
#ifdef DEBUG
    printk("tgafb_init: mem_base 0x%x\n", tga_mem_base);
#endif /* DEBUG */

    tga_type = (readl((unsigned long)tga_mem_base) >> 12) & 0x0f;
    switch (tga_type) {
	case 0:
	    strcat(tgafb_name, "8plane");
	    break;
	case 1:
	    strcat(tgafb_name, "24plane");
	    break;
	case 3:
	    strcat(tgafb_name, "24plusZ");
	    break;
	default:
	    printk("TGA type (0x%x) unrecognized!\n", tga_type);
	    return mem_start;
    }
    strcpy(fb_fix.id, tgafb_name);

    tga_regs_base = ((unsigned long)tga_mem_base + TGA_REGS_OFFSET);
    tga_fb_base = ((unsigned long)tga_mem_base + fb_offset_presets[tga_type]);

    /* first, disable video timing */
    TGA_WRITE_REG(0x03, TGA_VALID_REG); /* SCANNING and BLANK */

    /* write the DEEP register */
    while (TGA_READ_REG(TGA_CMD_STAT_REG) & 1) /* wait for not busy */
      continue;

    mb();
    TGA_WRITE_REG(deep_presets[tga_type], TGA_DEEP_REG);
    while (TGA_READ_REG(TGA_CMD_STAT_REG) & 1) /* wait for not busy */
	continue;
    mb();

    /* write some more registers */
    TGA_WRITE_REG(rasterop_presets[tga_type], TGA_RASTEROP_REG);
    TGA_WRITE_REG(mode_presets[tga_type], TGA_MODE_REG);
    TGA_WRITE_REG(base_addr_presets[tga_type], TGA_BASE_ADDR_REG);

    /* write the PLL for 640x480 @ 60Hz */
    for (i = 0; i <= 6; i++) {
	for (j = 0; j <= 7; j++) {
	    temp = (PLLbits[i] >> (7-j)) & 1;
	    if (i == 6 && j == 7)
		temp |= 2;
	    TGA_WRITE_REG(temp, TGA_CLOCK_REG);
	}
    }

    /* write some more registers */
    TGA_WRITE_REG(0xffffffff, TGA_PLANEMASK_REG);
    TGA_WRITE_REG(0xffffffff, TGA_PIXELMASK_REG);
    TGA_WRITE_REG(0x12345678, TGA_BLOCK_COLOR0_REG);
    TGA_WRITE_REG(0x12345678, TGA_BLOCK_COLOR1_REG);

    /* init video timing regs for 640x480 @ 60 Hz */
    TGA_WRITE_REG(0x018608a0, TGA_HORIZ_REG);
    TGA_WRITE_REG(0x084251e0, TGA_VERT_REG);

    if (tga_type == 0) { /* 8-plane */

	fb_var.bits_per_pixel = 8;
	fb_fix.visual = FB_VISUAL_PSEUDOCOLOR;

	/* init BT485 RAMDAC registers */
	BT485_WRITE(0xa2, BT485_CMD_0);
	BT485_WRITE(0x01, BT485_ADDR_PAL_WRITE);
	BT485_WRITE(0x14, BT485_CMD_3); /* cursor 64x64 */
	BT485_WRITE(0x40, BT485_CMD_1);
	BT485_WRITE(0x22, BT485_CMD_2); /* WIN cursor type */
	BT485_WRITE(0xff, BT485_PIXEL_MASK);

	/* fill palette registers */
	BT485_WRITE(0x00, BT485_ADDR_PAL_WRITE);
	TGA_WRITE_REG(BT485_DATA_PAL, TGA_RAMDAC_SETUP_REG);

	for (i = 0; i < 16; i++) {
	    j = color_table[i];
	    TGA_WRITE_REG(default_red[j]|(BT485_DATA_PAL<<8), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(default_grn[j]|(BT485_DATA_PAL<<8), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(default_blu[j]|(BT485_DATA_PAL<<8), TGA_RAMDAC_REG);
	}
	for (i = 0; i < 240*3; i += 4) {
	    TGA_WRITE_REG(0x55|(BT485_DATA_PAL<<8), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(0x00|(BT485_DATA_PAL<<8), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(0x00|(BT485_DATA_PAL<<8), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(0x00|(BT485_DATA_PAL<<8), TGA_RAMDAC_REG);
	}	  

	/* initialize RAMDAC cursor colors */
	BT485_WRITE(0, BT485_ADDR_CUR_WRITE);

	BT485_WRITE(0xaa, BT485_DATA_CUR); /* overscan WHITE */
	BT485_WRITE(0xaa, BT485_DATA_CUR); /* overscan WHITE */
	BT485_WRITE(0xaa, BT485_DATA_CUR); /* overscan WHITE */

	BT485_WRITE(0x00, BT485_DATA_CUR); /* color 1 BLACK */
	BT485_WRITE(0x00, BT485_DATA_CUR); /* color 1 BLACK */
	BT485_WRITE(0x00, BT485_DATA_CUR); /* color 1 BLACK */

	BT485_WRITE(0x00, BT485_DATA_CUR); /* color 2 BLACK */
	BT485_WRITE(0x00, BT485_DATA_CUR); /* color 2 BLACK */
	BT485_WRITE(0x00, BT485_DATA_CUR); /* color 2 BLACK */

	BT485_WRITE(0x00, BT485_DATA_CUR); /* color 3 BLACK */
	BT485_WRITE(0x00, BT485_DATA_CUR); /* color 3 BLACK */
	BT485_WRITE(0x00, BT485_DATA_CUR); /* color 3 BLACK */

	/* initialize RAMDAC cursor RAM */
	BT485_WRITE(0x00, BT485_ADDR_PAL_WRITE);
	cbp = (unsigned char *)bt485_cursor_source;
	for (i = 0; i < 512; i++) {
	    BT485_WRITE(*cbp++, BT485_CUR_RAM);
	}
	for (i = 0; i < 512; i++) {
	    BT485_WRITE(0xff, BT485_CUR_RAM);
	}

    } else { /* 24-plane or 24plusZ */

	fb_var.bits_per_pixel = 32;
	fb_fix.visual = FB_VISUAL_TRUECOLOR;

	TGA_WRITE_REG(0x01, TGA_VALID_REG); /* SCANNING */

	/*
	 * init some registers
	 */
	BT463_WRITE(BT463_REG_ACC, BT463_CMD_REG_0, 0x40);
	BT463_WRITE(BT463_REG_ACC, BT463_CMD_REG_1, 0x08);
	BT463_WRITE(BT463_REG_ACC, BT463_CMD_REG_2, 0x40);

	BT463_WRITE(BT463_REG_ACC, BT463_READ_MASK_0, 0xff);
	BT463_WRITE(BT463_REG_ACC, BT463_READ_MASK_1, 0xff);
	BT463_WRITE(BT463_REG_ACC, BT463_READ_MASK_2, 0xff);
	BT463_WRITE(BT463_REG_ACC, BT463_READ_MASK_3, 0x0f);

	BT463_WRITE(BT463_REG_ACC, BT463_BLINK_MASK_0, 0x00);
	BT463_WRITE(BT463_REG_ACC, BT463_BLINK_MASK_1, 0x00);
	BT463_WRITE(BT463_REG_ACC, BT463_BLINK_MASK_2, 0x00);
	BT463_WRITE(BT463_REG_ACC, BT463_BLINK_MASK_3, 0x00);

	/*
	 * fill the palette
	 */
	BT463_LOAD_ADDR(0x0000);
	TGA_WRITE_REG((BT463_PALETTE<<2), TGA_RAMDAC_REG);

	for (i = 0; i < 16; i++) {
	    j = color_table[i];
	    TGA_WRITE_REG(default_red[j]|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(default_grn[j]|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(default_blu[j]|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	}
	for (i = 0; i < 512*3; i += 4) {
	    TGA_WRITE_REG(0x55|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(0x00|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(0x00|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(0x00|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	}	  

	/*
	 * fill window type table after start of vertical retrace
	 */
	while (!(TGA_READ_REG(TGA_INTR_STAT_REG) & 0x01))
	    continue;
	TGA_WRITE_REG(0x01, TGA_INTR_STAT_REG);
	mb();
	while (!(TGA_READ_REG(TGA_INTR_STAT_REG) & 0x01))
	    continue;
	TGA_WRITE_REG(0x01, TGA_INTR_STAT_REG);

	BT463_LOAD_ADDR(BT463_WINDOW_TYPE_BASE);
	TGA_WRITE_REG((BT463_REG_ACC<<2), TGA_RAMDAC_SETUP_REG);
	
	for (i = 0; i < 16; i++) {
	    TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(0x01|(BT463_REG_ACC<<10), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(0x80|(BT463_REG_ACC<<10), TGA_RAMDAC_REG);
	}

	/*
	 * init cursor colors
	 */
	BT463_LOAD_ADDR(BT463_CUR_CLR_0);

	TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG); /* background */
	TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG); /* background */
	TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG); /* background */

	TGA_WRITE_REG(0xff|(BT463_REG_ACC<<10), TGA_RAMDAC_REG); /* foreground */
	TGA_WRITE_REG(0xff|(BT463_REG_ACC<<10), TGA_RAMDAC_REG); /* foreground */
	TGA_WRITE_REG(0xff|(BT463_REG_ACC<<10), TGA_RAMDAC_REG); /* foreground */

	TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG);
	TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG);
	TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG);

	TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG);
	TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG);
	TGA_WRITE_REG(0x00|(BT463_REG_ACC<<10), TGA_RAMDAC_REG);

	/*
	 * finally, init the cursor shape
	 */
	temp = tga_fb_base - 1024; /* this assumes video starts at base
				     and base is beyond memory start*/

	for (i = 0; i < 256; i++) {
	    writel(bt463_cursor_source[i], temp + i*4);
	}
	TGA_WRITE_REG(temp & 0x000fffff, TGA_CURSOR_BASE_REG);
    }

    /* finally, enable video scan & cursor
       (and pray for the monitor... :-) */
    TGA_WRITE_REG(0x05, TGA_VALID_REG); /* SCANNING and CURSOR */

    fb_var.xres = fb_var.xres_virtual = 640;
    fb_var.yres = fb_var.yres_virtual = 480;
    fb_fix.line_length = 80*fb_var.bits_per_pixel;
    fb_fix.smem_start = (char *)tga_fb_base;
    fb_fix.smem_len = fb_fix.line_length*fb_var.yres;
    fb_fix.type = FB_TYPE_PACKED_PIXELS;
    fb_fix.type_aux = 0;
    fb_fix.mmio_start = (unsigned char *)tga_regs_base;
    fb_fix.mmio_len = 0x1000;		/* Is this sufficient? */

    fb_var.xoffset = fb_var.yoffset = 0;
    fb_var.grayscale = 0;
    fb_var.red.offset = fb_var.green.offset = fb_var.blue.offset = 0;
    fb_var.red.length = fb_var.green.length = fb_var.blue.length = 8;
    fb_var.red.msb_right = fb_var.green.msb_right = fb_var.blue.msb_right = 0;
    fb_var.transp.offset = fb_var.transp.length = fb_var.transp.msb_right = 0;
    fb_var.nonstd = 0;
    fb_var.activate = 0;
    fb_var.height = fb_var.width = -1;
    fb_var.accel = FB_ACCEL_TGA;
    fb_var.pixclock = 39722;
    fb_var.left_margin = 40;
    fb_var.right_margin = 24;
    fb_var.upper_margin = 32;
    fb_var.lower_margin = 11;
    fb_var.hsync_len = 96;
    fb_var.vsync_len = 2;
    fb_var.sync = 0;
    fb_var.vmode = FB_VMODE_NONINTERLACED;

    disp.var = fb_var;
    disp.cmap.start = 0;
    disp.cmap.len = 0;
    disp.cmap.red = disp.cmap.green = disp.cmap.blue = disp.cmap.transp = NULL;
    disp.screen_base = fb_fix.smem_start;
    disp.visual = fb_fix.visual;
    disp.type = fb_fix.type;
    disp.type_aux = fb_fix.type_aux;
    disp.ypanstep = 0;
    disp.ywrapstep = 0;
    disp.line_length = fb_fix.line_length;
    disp.can_soft_blank = 1;
    disp.inverse = 0;

    strcpy(fb_info.modename, tgafb_name);
    fb_info.node = -1;
    fb_info.fbops = &tgafb_ops;
    fb_info.fbvar_num = 1;
    fb_info.fbvar = &fb_var;
    fb_info.disp = &disp;
    fb_info.fontname[0] = '\0';
    fb_info.changevar = NULL;
    fb_info.switch_con = &tgafbcon_switch;
    fb_info.updatevar = &tgafbcon_updatevar;
    fb_info.blank = &tgafbcon_blank;
    fb_info.setcmap = &tgafbcon_setcmap;

    err = register_framebuffer(&fb_info);
    if (err < 0)
	return mem_start;

    tgafb_set_var(&fb_var, -1);

    printk("%s frame buffer device\n", tgafb_name);
    return mem_start;
}


static int tgafbcon_switch(int con)
{
    /* Do we have to save the colormap? */
    if (fb_display[currcon].cmap.len)
	fb_get_cmap(&fb_display[currcon].cmap, &fb_display[currcon].var, 1,
		    tgafb_getcolreg);

    currcon = con;
    /* Install new colormap */
    do_install_cmap(con);
    return 0;
}

    /*
     *  Update the `var' structure (called by fbcon.c)
     */

static int tgafbcon_updatevar(int con)
{
    /* Nothing */
    return 0;
}

    /*
     *  Blank the display.
     */

static void tgafbcon_blank(int blank)
{
    /* Nothing */
}

    /*
     *  Set the colormap
     */

static int tgafbcon_setcmap(struct fb_cmap *cmap, int con)
{
    return(tgafb_set_cmap(cmap, 1, con));
}


    /*
     *  Read a single color register and split it into
     *  colors/transparent. Return != 0 for invalid regno.
     */

static int tgafb_getcolreg(u_int regno, u_int *red, u_int *green, u_int *blue,
                         u_int *transp)
{
    if (regno > 255)
	return 1;
    *red = palette[regno].red;
    *green = palette[regno].green;
    *blue = palette[regno].blue;
    return 0;
}


    /*
     *  Set a single color register. The values supplied are already
     *  rounded down to the hardware's capabilities (according to the
     *  entries in the var structure). Return != 0 for invalid regno.
     */

static int tgafb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
                         u_int transp)
{
    if (regno > 255)
	return 1;
    palette[regno].red = red;
    palette[regno].green = green;
    palette[regno].blue = blue;

    /* How to set a single color register?? */

    return 0;
}


#if 1
    /*
     *	FIXME: since I don't know how to set a single arbitrary color register,
     *  all color palette registers have to be updated
     */

static void tga_update_palette(void)
{
    int i;

    if (tga_type == 0) { /* 8-plane */
	BT485_WRITE(0x00, BT485_ADDR_PAL_WRITE);
	TGA_WRITE_REG(BT485_DATA_PAL, TGA_RAMDAC_SETUP_REG);
	for (i = 0; i < 256; i++) {
	    TGA_WRITE_REG(palette[i].red|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG);
	    TGA_WRITE_REG(palette[i].green|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG);
	    TGA_WRITE_REG(palette[i].blue|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG);
	}
    } else {
	BT463_LOAD_ADDR(0x0000);
	TGA_WRITE_REG((BT463_PALETTE<<2), TGA_RAMDAC_REG);

	for (i = 0; i < 256; i++) {
	    TGA_WRITE_REG(palette[i].red|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(palette[i].green|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	    TGA_WRITE_REG(palette[i].blue|(BT463_PALETTE<<10), TGA_RAMDAC_REG);
	}
    }
}
#endif

static void do_install_cmap(int con)
{
    if (con != currcon)
	return;
    if (fb_display[con].cmap.len)
	fb_set_cmap(&fb_display[con].cmap, &fb_display[con].var, 1,
		    tgafb_setcolreg);
    else
	fb_set_cmap(fb_default_cmap(fb_display[con].var.bits_per_pixel),
				    &fb_display[con].var, 1, tgafb_setcolreg);
#if 1
    tga_update_palette();
#endif
}


#if 0	/* No cursor stuff yet */

/*
 * Hide the cursor from view, during blanking, usually...
 */
void
hide_cursor(void)
{
	unsigned long flags;
	save_flags(flags); cli();

	if (tga_type == 0) {
	  BT485_WRITE(0x20, BT485_CMD_2);
	} else {
	  TGA_WRITE_REG(0x03, TGA_VALID_REG); /* SCANNING and BLANK */
	}

	restore_flags(flags);
}

void
set_cursor(int currcons)
{
  unsigned int idx, xt, yt, row, col;
  unsigned long flags;

  if (currcons != fg_console || console_blanked || vcmode == KD_GRAPHICS)
    return;

  if (__real_origin != __origin)
    __set_origin(__real_origin);

  save_flags(flags); cli();

  if (deccm) {
    idx = (pos - video_mem_base) >> 1;
    col = idx % 80;
    row = (idx - col) / 80;

    if (tga_type == 0) { /* 8-plane */

      xt = col * TGA_F_WIDTH + 64;
      yt = row * TGA_F_HEIGHT_PADDED + 64;

      /* make sure it's enabled */
      BT485_WRITE(0x22, BT485_CMD_2); /* WIN cursor type */

      BT485_WRITE(xt, BT485_CUR_LOW_X);
      BT485_WRITE((xt >> 8), BT485_CUR_HIGH_X);
      BT485_WRITE(yt, BT485_CUR_LOW_Y);
      BT485_WRITE((yt >> 8), BT485_CUR_HIGH_Y);

    } else {

      xt = col * TGA_F_WIDTH + 144;
      yt = row * TGA_F_HEIGHT_PADDED + 35;

      TGA_WRITE_REG(0x05, TGA_VALID_REG); /* SCANNING and CURSOR */
      TGA_WRITE_REG(xt | (yt << 12), TGA_CURSOR_XY_REG);
    }

  } else
    hide_cursor();
  restore_flags(flags);
}

#endif
