/*
 * XG20, XG21, XG40, XG42 frame buffer device
 * for Linux kernels  2.5.x, 2.6.x
 * Base on TW's sis fbdev code.
 */

/* #include <linux/config.h> */
#include <linux/version.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/console.h>
#include <linux/selection.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/vmalloc.h>
#include <linux/vt_kern.h>
#include <linux/capability.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/proc_fs.h>

#ifndef XGIFB_PAN
#define XGIFB_PAN
#endif

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

#include "XGIfb.h"
#include "vgatypes.h"
#include "XGI_main.h"
#include "vb_util.h"

#define Index_CR_GPIO_Reg1 0x48
#define Index_CR_GPIO_Reg2 0x49
#define Index_CR_GPIO_Reg3 0x4a

#define GPIOG_EN    (1<<6)
#define GPIOG_WRITE (1<<6)
#define GPIOG_READ  (1<<1)
int XGIfb_GetXG21DefaultLVDSModeIdx(void);

#define XGIFB_ROM_SIZE	65536

/* -------------------- Macro definitions ---------------------------- */

#undef XGIFBDEBUG

#ifdef XGIFBDEBUG
#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args)
#else
#define DPRINTK(fmt, args...)
#endif

#ifdef XGIFBDEBUG
static void dumpVGAReg(void)
{
	u8 i, reg;

	outXGIIDXREG(XGISR, 0x05, 0x86);
	/*
	outXGIIDXREG(XGISR, 0x08, 0x4f);
	outXGIIDXREG(XGISR, 0x0f, 0x20);
	outXGIIDXREG(XGISR, 0x11, 0x4f);
	outXGIIDXREG(XGISR, 0x13, 0x45);
	outXGIIDXREG(XGISR, 0x14, 0x51);
	outXGIIDXREG(XGISR, 0x1e, 0x41);
	outXGIIDXREG(XGISR, 0x1f, 0x0);
	outXGIIDXREG(XGISR, 0x20, 0xa1);
	outXGIIDXREG(XGISR, 0x22, 0xfb);
	outXGIIDXREG(XGISR, 0x26, 0x22);
	outXGIIDXREG(XGISR, 0x3e, 0x07);
	*/

	/* outXGIIDXREG(XGICR, 0x19, 0x00); */
	/* outXGIIDXREG(XGICR, 0x1a, 0x3C); */
	/* outXGIIDXREG(XGICR, 0x22, 0xff); */
	/* outXGIIDXREG(XGICR, 0x3D, 0x10); */

	/* outXGIIDXREG(XGICR, 0x4a, 0xf3); */

	/* outXGIIDXREG(XGICR, 0x57, 0x0); */
	/* outXGIIDXREG(XGICR, 0x7a, 0x2c); */

	/* outXGIIDXREG(XGICR, 0x82, 0xcc); */
	/* outXGIIDXREG(XGICR, 0x8c, 0x0); */
	/*
	outXGIIDXREG(XGICR, 0x99, 0x1);
	outXGIIDXREG(XGICR, 0x41, 0x40);
	*/

	for (i = 0; i < 0x4f; i++) {
		inXGIIDXREG(XGISR, i, reg);
		printk("\no 3c4 %x", i);
		printk("\ni 3c5 => %x", reg);
	}

	for (i = 0; i < 0xF0; i++) {
		inXGIIDXREG(XGICR, i, reg);
		printk("\no 3d4 %x", i);
		printk("\ni 3d5 => %x", reg);
	}
	/*
	outXGIIDXREG(XGIPART1,0x2F,1);
	for (i=1; i < 0x50; i++) {
		inXGIIDXREG(XGIPART1, i, reg);
		printk("\no d004 %x", i);
		printk("\ni d005 => %x", reg);
	}

	for (i=0; i < 0x50; i++) {
		 inXGIIDXREG(XGIPART2, i, reg);
		 printk("\no d010 %x", i);
		 printk("\ni d011 => %x", reg);
	}
	for (i=0; i < 0x50; i++) {
		inXGIIDXREG(XGIPART3, i, reg);
		printk("\no d012 %x",i);
		printk("\ni d013 => %x",reg);
	}
	for (i=0; i < 0x50; i++) {
		inXGIIDXREG(XGIPART4, i, reg);
		printk("\no d014 %x",i);
		printk("\ni d015 => %x",reg);
	}
	*/
}
#else
static inline void dumpVGAReg(void)
{
}
#endif

/* data for XGI components */
struct video_info xgi_video_info;

#if 1
#define DEBUGPRN(x)
#else
#define DEBUGPRN(x) printk(KERN_INFO x "\n");
#endif

/* --------------- Hardware Access Routines -------------------------- */

static int XGIfb_mode_rate_to_dclock(struct vb_device_info *XGI_Pr,
		struct xgi_hw_device_info *HwDeviceExtension,
		unsigned char modeno, unsigned char rateindex)
{
	unsigned short ModeNo = modeno;
	unsigned short ModeIdIndex = 0, ClockIndex = 0;
	unsigned short RefreshRateTableIndex = 0;

	/* unsigned long  temp = 0; */
	int Clock;
	XGI_Pr->ROMAddr = HwDeviceExtension->pjVirtualRomBase;
	InitTo330Pointer(HwDeviceExtension->jChipType, XGI_Pr);

	RefreshRateTableIndex = XGI_GetRatePtrCRT2(HwDeviceExtension, ModeNo,
			ModeIdIndex, XGI_Pr);

	/*
	temp = XGI_SearchModeID(ModeNo , &ModeIdIndex,  XGI_Pr) ;
	if (!temp) {
		printk(KERN_ERR "Could not find mode %x\n", ModeNo);
		return 65000;
	}

	RefreshRateTableIndex = XGI_Pr->EModeIDTable[ModeIdIndex].REFindex;
	RefreshRateTableIndex += (rateindex - 1);

	*/
	ClockIndex = XGI_Pr->RefIndex[RefreshRateTableIndex].Ext_CRTVCLK;

	Clock = XGI_Pr->VCLKData[ClockIndex].CLOCK * 1000;

	return Clock;
}

static int XGIfb_mode_rate_to_ddata(struct vb_device_info *XGI_Pr,
		struct xgi_hw_device_info *HwDeviceExtension,
		unsigned char modeno, unsigned char rateindex,
		u32 *left_margin, u32 *right_margin, u32 *upper_margin,
		u32 *lower_margin, u32 *hsync_len, u32 *vsync_len, u32 *sync,
		u32 *vmode)
{
	unsigned short ModeNo = modeno;
	unsigned short ModeIdIndex = 0, index = 0;
	unsigned short RefreshRateTableIndex = 0;

	unsigned short VRE, VBE, VRS, VBS, VDE, VT;
	unsigned short HRE, HBE, HRS, HBS, HDE, HT;
	unsigned char sr_data, cr_data, cr_data2;
	unsigned long cr_data3;
	int A, B, C, D, E, F, temp, j;
	XGI_Pr->ROMAddr = HwDeviceExtension->pjVirtualRomBase;
	InitTo330Pointer(HwDeviceExtension->jChipType, XGI_Pr);
	RefreshRateTableIndex = XGI_GetRatePtrCRT2(HwDeviceExtension, ModeNo,
			ModeIdIndex, XGI_Pr);
	/*
	temp = XGI_SearchModeID(ModeNo, &ModeIdIndex, XGI_Pr);
	if (!temp)
		return 0;

	RefreshRateTableIndex = XGI_Pr->EModeIDTable[ModeIdIndex].REFindex;
	RefreshRateTableIndex += (rateindex - 1);
	*/
	index = XGI_Pr->RefIndex[RefreshRateTableIndex].Ext_CRT1CRTC;

	sr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[5];

	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[0];

	/* Horizontal total */
	HT = (cr_data & 0xff) | ((unsigned short) (sr_data & 0x03) << 8);
	A = HT + 5;

	/*
	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[1];

	Horizontal display enable end
	HDE = (cr_data & 0xff) | ((unsigned short) (sr_data & 0x0C) << 6);
	*/
	HDE = (XGI_Pr->RefIndex[RefreshRateTableIndex].XRes >> 3) - 1;
	E = HDE + 1;

	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[3];

	/* Horizontal retrace (=sync) start */
	HRS = (cr_data & 0xff) | ((unsigned short) (sr_data & 0xC0) << 2);
	F = HRS - E - 3;

	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[1];

	/* Horizontal blank start */
	HBS = (cr_data & 0xff) | ((unsigned short) (sr_data & 0x30) << 4);

	sr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[6];

	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[2];

	cr_data2 = XGI_Pr->XGINEWUB_CRT1Table[index].CR[4];

	/* Horizontal blank end */
	HBE = (cr_data & 0x1f) | ((unsigned short) (cr_data2 & 0x80) >> 2)
			| ((unsigned short) (sr_data & 0x03) << 6);

	/* Horizontal retrace (=sync) end */
	HRE = (cr_data2 & 0x1f) | ((sr_data & 0x04) << 3);

	temp = HBE - ((E - 1) & 255);
	B = (temp > 0) ? temp : (temp + 256);

	temp = HRE - ((E + F + 3) & 63);
	C = (temp > 0) ? temp : (temp + 64);

	D = B - F - C;

	*left_margin = D * 8;
	*right_margin = F * 8;
	*hsync_len = C * 8;

	sr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[14];

	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[8];

	cr_data2 = XGI_Pr->XGINEWUB_CRT1Table[index].CR[9];

	/* Vertical total */
	VT = (cr_data & 0xFF) | ((unsigned short) (cr_data2 & 0x01) << 8)
			| ((unsigned short) (cr_data2 & 0x20) << 4)
			| ((unsigned short) (sr_data & 0x01) << 10);
	A = VT + 2;

	/* cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[10]; */

	/* Vertical display enable end */
	/*
	VDE = (cr_data & 0xff) |
		((unsigned short) (cr_data2 & 0x02) << 7) |
		((unsigned short) (cr_data2 & 0x40) << 3) |
		((unsigned short) (sr_data & 0x02) << 9);
	*/
	VDE = XGI_Pr->RefIndex[RefreshRateTableIndex].YRes - 1;
	E = VDE + 1;

	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[10];

	/* Vertical retrace (=sync) start */
	VRS = (cr_data & 0xff) | ((unsigned short) (cr_data2 & 0x04) << 6)
			| ((unsigned short) (cr_data2 & 0x80) << 2)
			| ((unsigned short) (sr_data & 0x08) << 7);
	F = VRS + 1 - E;

	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[12];

	cr_data3 = (XGI_Pr->XGINEWUB_CRT1Table[index].CR[14] & 0x80) << 5;

	/* Vertical blank start */
	VBS = (cr_data & 0xff) | ((unsigned short) (cr_data2 & 0x08) << 5)
			| ((unsigned short) (cr_data3 & 0x20) << 4)
			| ((unsigned short) (sr_data & 0x04) << 8);

	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[13];

	/* Vertical blank end */
	VBE = (cr_data & 0xff) | ((unsigned short) (sr_data & 0x10) << 4);
	temp = VBE - ((E - 1) & 511);
	B = (temp > 0) ? temp : (temp + 512);

	cr_data = XGI_Pr->XGINEWUB_CRT1Table[index].CR[11];

	/* Vertical retrace (=sync) end */
	VRE = (cr_data & 0x0f) | ((sr_data & 0x20) >> 1);
	temp = VRE - ((E + F - 1) & 31);
	C = (temp > 0) ? temp : (temp + 32);

	D = B - F - C;

	*upper_margin = D;
	*lower_margin = F;
	*vsync_len = C;

	if (XGI_Pr->RefIndex[RefreshRateTableIndex].Ext_InfoFlag & 0x8000)
		*sync &= ~FB_SYNC_VERT_HIGH_ACT;
	else
		*sync |= FB_SYNC_VERT_HIGH_ACT;

	if (XGI_Pr->RefIndex[RefreshRateTableIndex].Ext_InfoFlag & 0x4000)
		*sync &= ~FB_SYNC_HOR_HIGH_ACT;
	else
		*sync |= FB_SYNC_HOR_HIGH_ACT;

	*vmode = FB_VMODE_NONINTERLACED;
	if (XGI_Pr->RefIndex[RefreshRateTableIndex].Ext_InfoFlag & 0x0080)
		*vmode = FB_VMODE_INTERLACED;
	else {
		j = 0;
		while (XGI_Pr->EModeIDTable[j].Ext_ModeID != 0xff) {
			if (XGI_Pr->EModeIDTable[j].Ext_ModeID
					== XGI_Pr->RefIndex[RefreshRateTableIndex].ModeID) {
				if (XGI_Pr->EModeIDTable[j].Ext_ModeFlag
						& DoubleScanMode) {
					*vmode = FB_VMODE_DOUBLE;
				}
				break;
			}
			j++;
		}
	}

	return 1;
}

static void XGIRegInit(struct vb_device_info *XGI_Pr, unsigned long BaseAddr)
{
	XGI_Pr->RelIO = BaseAddr;
	XGI_Pr->P3c4 = BaseAddr + 0x14;
	XGI_Pr->P3d4 = BaseAddr + 0x24;
	XGI_Pr->P3c0 = BaseAddr + 0x10;
	XGI_Pr->P3ce = BaseAddr + 0x1e;
	XGI_Pr->P3c2 = BaseAddr + 0x12;
	XGI_Pr->P3ca = BaseAddr + 0x1a;
	XGI_Pr->P3c6 = BaseAddr + 0x16;
	XGI_Pr->P3c7 = BaseAddr + 0x17;
	XGI_Pr->P3c8 = BaseAddr + 0x18;
	XGI_Pr->P3c9 = BaseAddr + 0x19;
	XGI_Pr->P3da = BaseAddr + 0x2A;
	XGI_Pr->Part1Port = BaseAddr + XGI_CRT2_PORT_04; /* Digital video interface registers (LCD) */
	XGI_Pr->Part2Port = BaseAddr + XGI_CRT2_PORT_10; /* 301 TV Encoder registers */
	XGI_Pr->Part3Port = BaseAddr + XGI_CRT2_PORT_12; /* 301 Macrovision registers */
	XGI_Pr->Part4Port = BaseAddr + XGI_CRT2_PORT_14; /* 301 VGA2 (and LCD) registers */
	XGI_Pr->Part5Port = BaseAddr + XGI_CRT2_PORT_14 + 2; /* 301 palette address port registers */

}

void XGIfb_set_reg4(u16 port, unsigned long data)
{
	outl((u32)(data & 0xffffffff), port);
}

u32 XGIfb_get_reg3(u16 port)
{
	u32 data;

	data = inl(port);
	return data;
}

/* ------------ Interface for init & mode switching code ------------- */

unsigned char XGIfb_query_VGA_config_space(
		struct xgi_hw_device_info *pXGIhw_ext, unsigned long offset,
		unsigned long set, unsigned long *value)
{
	static struct pci_dev *pdev = NULL;
	static unsigned char init = 0, valid_pdev = 0;

	if (!set)
		DPRINTK("XGIfb: Get VGA offset 0x%lx\n", offset);
	else
		DPRINTK("XGIfb: Set offset 0x%lx to 0x%lx\n", offset, *value);

	if (!init) {
		init = 1;
		pdev = pci_get_device(PCI_VENDOR_ID_XG, xgi_video_info.chip_id,
				pdev);
		if (pdev) {
			valid_pdev = 1;
			pci_dev_put(pdev);
		}
	}

	if (!valid_pdev) {
		printk(KERN_DEBUG "XGIfb: Can't find XGI %d VGA device.\n",
				xgi_video_info.chip_id);
		return 0;
	}

	if (set == 0)
		pci_read_config_dword(pdev, offset, (u32 *) value);
	else
		pci_write_config_dword(pdev, offset, (u32)(*value));

	return 1;
}

/*
unsigned char XGIfb_query_north_bridge_space(struct xgi_hw_device_info *pXGIhw_ext,
	unsigned long offset, unsigned long set, unsigned long *value)
{
	static struct pci_dev *pdev = NULL;
	static unsigned char init = 0, valid_pdev = 0;
	u16 nbridge_id = 0;

	if (!init) {
		init = 1;
		switch (xgi_video_info.chip) {
		case XGI_540:
			nbridge_id = PCI_DEVICE_ID_XG_540;
			break;
		case XGI_630:
			nbridge_id = PCI_DEVICE_ID_XG_630;
			break;
		case XGI_730:
			nbridge_id = PCI_DEVICE_ID_XG_730;
			break;
		case XGI_550:
			nbridge_id = PCI_DEVICE_ID_XG_550;
			break;
		case XGI_650:
			nbridge_id = PCI_DEVICE_ID_XG_650;
			break;
		case XGI_740:
			nbridge_id = PCI_DEVICE_ID_XG_740;
			break;
		default:
			nbridge_id = 0;
			break;
		}

		pdev = pci_get_device(PCI_VENDOR_ID_SI, nbridge_id, pdev);
		if (pdev) {
			valid_pdev = 1;
			pci_dev_put(pdev);
		}
	}

	if (!valid_pdev) {
		printk(KERN_DEBUG "XGIfb: Can't find XGI %d North Bridge device.\n",
			nbridge_id);
		return 0;
	}

	if (set == 0)
		pci_read_config_dword(pdev, offset, (u32 *)value);
	else
		pci_write_config_dword(pdev, offset, (u32)(*value));

	return 1;
}
*/
/* ------------------ Internal helper routines ----------------- */

static void XGIfb_search_mode(const char *name)
{
	int i = 0, j = 0, l;

	if (name == NULL) {
		printk(KERN_ERR "XGIfb: Internal error, using default mode.\n");
		xgifb_mode_idx = DEFAULT_MODE;
		if ((xgi_video_info.chip == XG21)
				&& ((xgi_video_info.disp_state & DISPTYPE_DISP2)
						== DISPTYPE_LCD)) {
			xgifb_mode_idx = XGIfb_GetXG21DefaultLVDSModeIdx();
		}
		return;
	}

	if (!strcmp(name, XGIbios_mode[MODE_INDEX_NONE].name)) {
		printk(KERN_ERR "XGIfb: Mode 'none' not supported anymore. Using default.\n");
		xgifb_mode_idx = DEFAULT_MODE;
		if ((xgi_video_info.chip == XG21)
				&& ((xgi_video_info.disp_state & DISPTYPE_DISP2)
						== DISPTYPE_LCD)) {
			xgifb_mode_idx = XGIfb_GetXG21DefaultLVDSModeIdx();
		}
		return;
	}

	while (XGIbios_mode[i].mode_no != 0) {
		l = min(strlen(name), strlen(XGIbios_mode[i].name));
		if (!strncmp(name, XGIbios_mode[i].name, l)) {
			xgifb_mode_idx = i;
			j = 1;
			break;
		}
		i++;
	}
	if (!j)
		printk(KERN_INFO "XGIfb: Invalid mode '%s'\n", name);
}

static void XGIfb_search_vesamode(unsigned int vesamode)
{
	int i = 0, j = 0;

	if (vesamode == 0) {

		printk(KERN_ERR "XGIfb: Mode 'none' not supported anymore. Using default.\n");
		xgifb_mode_idx = DEFAULT_MODE;
		if ((xgi_video_info.chip == XG21)
				&& ((xgi_video_info.disp_state & DISPTYPE_DISP2)
						== DISPTYPE_LCD)) {
			xgifb_mode_idx = XGIfb_GetXG21DefaultLVDSModeIdx();
		}
		return;
	}

	vesamode &= 0x1dff; /* Clean VESA mode number from other flags */

	while (XGIbios_mode[i].mode_no != 0) {
		if ((XGIbios_mode[i].vesa_mode_no_1 == vesamode)
				|| (XGIbios_mode[i].vesa_mode_no_2 == vesamode)) {
			xgifb_mode_idx = i;
			j = 1;
			break;
		}
		i++;
	}
	if (!j)
		printk(KERN_INFO "XGIfb: Invalid VESA mode 0x%x'\n", vesamode);
}

static int XGIfb_GetXG21LVDSData(void)
{
	u8 tmp;
	unsigned char *pData;
	int i, j, k;

	inXGIIDXREG(XGISR, 0x1e, tmp);
	outXGIIDXREG(XGISR, 0x1e, tmp | 4);

	pData = xgi_video_info.mmio_vbase + 0x20000;
	if ((pData[0x0] == 0x55) && (pData[0x1] == 0xAA) && (pData[0x65] & 0x1)) {
		i = pData[0x316] | (pData[0x317] << 8);
		j = pData[i - 1];
		if (j == 0xff)
			j = 1;

		k = 0;
		do {
			XGI21_LCDCapList[k].LVDS_Capability = pData[i]
					| (pData[i + 1] << 8);
			XGI21_LCDCapList[k].LVDSHT = pData[i + 2] | (pData[i
					+ 3] << 8);
			XGI21_LCDCapList[k].LVDSVT = pData[i + 4] | (pData[i
					+ 5] << 8);
			XGI21_LCDCapList[k].LVDSHDE = pData[i + 6] | (pData[i
					+ 7] << 8);
			XGI21_LCDCapList[k].LVDSVDE = pData[i + 8] | (pData[i
					+ 9] << 8);
			XGI21_LCDCapList[k].LVDSHFP = pData[i + 10] | (pData[i
					+ 11] << 8);
			XGI21_LCDCapList[k].LVDSVFP = pData[i + 12] | (pData[i
					+ 13] << 8);
			XGI21_LCDCapList[k].LVDSHSYNC = pData[i + 14]
					| (pData[i + 15] << 8);
			XGI21_LCDCapList[k].LVDSVSYNC = pData[i + 16]
					| (pData[i + 17] << 8);
			XGI21_LCDCapList[k].VCLKData1 = pData[i + 18];
			XGI21_LCDCapList[k].VCLKData2 = pData[i + 19];
			XGI21_LCDCapList[k].PSC_S1 = pData[i + 20];
			XGI21_LCDCapList[k].PSC_S2 = pData[i + 21];
			XGI21_LCDCapList[k].PSC_S3 = pData[i + 22];
			XGI21_LCDCapList[k].PSC_S4 = pData[i + 23];
			XGI21_LCDCapList[k].PSC_S5 = pData[i + 24];
			i += 25;
			j--;
			k++;
		} while ((j > 0) && (k < (sizeof(XGI21_LCDCapList)
				/ sizeof(struct XGI21_LVDSCapStruct))));
		return 1;
	}
	return 0;
}

int XGIfb_GetXG21DefaultLVDSModeIdx(void)
{

	int found_mode = 0;
	int XGIfb_mode_idx = 0;

	found_mode = 0;
	while ((XGIbios_mode[XGIfb_mode_idx].mode_no != 0)
			&& (XGIbios_mode[XGIfb_mode_idx].xres
					<= XGI21_LCDCapList[0].LVDSHDE)) {
		if ((XGIbios_mode[XGIfb_mode_idx].xres
				== XGI21_LCDCapList[0].LVDSHDE)
				&& (XGIbios_mode[XGIfb_mode_idx].yres
						== XGI21_LCDCapList[0].LVDSVDE)
				&& (XGIbios_mode[XGIfb_mode_idx].bpp == 8)) {
			XGIfb_mode_no = XGIbios_mode[XGIfb_mode_idx].mode_no;
			found_mode = 1;
			break;
		}
		XGIfb_mode_idx++;
	}
	if (!found_mode)
		XGIfb_mode_idx = 0;

	return XGIfb_mode_idx;
}

static int XGIfb_validate_mode(int myindex)
{
	u16 xres, yres;

	if (xgi_video_info.chip == XG21) {
		if ((xgi_video_info.disp_state & DISPTYPE_DISP2)
				== DISPTYPE_LCD) {
			xres = XGI21_LCDCapList[0].LVDSHDE;
			yres = XGI21_LCDCapList[0].LVDSVDE;
			if (XGIbios_mode[myindex].xres > xres)
				return -1;
			if (XGIbios_mode[myindex].yres > yres)
				return -1;
			if ((XGIbios_mode[myindex].xres < xres)
					&& (XGIbios_mode[myindex].yres < yres)) {
				if (XGIbios_mode[myindex].bpp > 8)
					return -1;
			}

		}
		return myindex;

	}

	/* FIXME: for now, all is valid on XG27 */
	if (xgi_video_info.chip == XG27)
		return myindex;

	if (!(XGIbios_mode[myindex].chipset & MD_XGI315))
		return -1;

	switch (xgi_video_info.disp_state & DISPTYPE_DISP2) {
	case DISPTYPE_LCD:
		switch (XGIhw_ext.ulCRT2LCDType) {
		case LCD_640x480:
			xres = 640;
			yres = 480;
			break;
		case LCD_800x600:
			xres = 800;
			yres = 600;
			break;
		case LCD_1024x600:
			xres = 1024;
			yres = 600;
			break;
		case LCD_1024x768:
			xres = 1024;
			yres = 768;
			break;
		case LCD_1152x768:
			xres = 1152;
			yres = 768;
			break;
		case LCD_1280x960:
			xres = 1280;
			yres = 960;
			break;
		case LCD_1280x768:
			xres = 1280;
			yres = 768;
			break;
		case LCD_1280x1024:
			xres = 1280;
			yres = 1024;
			break;
		case LCD_1400x1050:
			xres = 1400;
			yres = 1050;
			break;
		case LCD_1600x1200:
			xres = 1600;
			yres = 1200;
			break;
		/* case LCD_320x480: */ /* TW: FSTN */
			/*
			xres =  320;
			yres =  480;
			break;
			*/
		default:
			xres = 0;
			yres = 0;
			break;
		}
		if (XGIbios_mode[myindex].xres > xres)
			return -1;
		if (XGIbios_mode[myindex].yres > yres)
			return -1;
		if ((XGIhw_ext.ulExternalChip == 0x01) || /* LVDS */
				(XGIhw_ext.ulExternalChip == 0x05)) { /* LVDS+Chrontel */
			switch (XGIbios_mode[myindex].xres) {
			case 512:
				if (XGIbios_mode[myindex].yres != 512)
					return -1;
				if (XGIhw_ext.ulCRT2LCDType == LCD_1024x600)
					return -1;
				break;
			case 640:
				if ((XGIbios_mode[myindex].yres != 400)
						&& (XGIbios_mode[myindex].yres
								!= 480))
					return -1;
				break;
			case 800:
				if (XGIbios_mode[myindex].yres != 600)
					return -1;
				break;
			case 1024:
				if ((XGIbios_mode[myindex].yres != 600)
						&& (XGIbios_mode[myindex].yres
								!= 768))
					return -1;
				if ((XGIbios_mode[myindex].yres == 600)
						&& (XGIhw_ext.ulCRT2LCDType
								!= LCD_1024x600))
					return -1;
				break;
			case 1152:
				if ((XGIbios_mode[myindex].yres) != 768)
					return -1;
				if (XGIhw_ext.ulCRT2LCDType != LCD_1152x768)
					return -1;
				break;
			case 1280:
				if ((XGIbios_mode[myindex].yres != 768)
						&& (XGIbios_mode[myindex].yres
								!= 1024))
					return -1;
				if ((XGIbios_mode[myindex].yres == 768)
						&& (XGIhw_ext.ulCRT2LCDType
								!= LCD_1280x768))
					return -1;
				break;
			case 1400:
				if (XGIbios_mode[myindex].yres != 1050)
					return -1;
				break;
			case 1600:
				if (XGIbios_mode[myindex].yres != 1200)
					return -1;
				break;
			default:
				return -1;
			}
		} else {
			switch (XGIbios_mode[myindex].xres) {
			case 512:
				if (XGIbios_mode[myindex].yres != 512)
					return -1;
				break;
			case 640:
				if ((XGIbios_mode[myindex].yres != 400)
						&& (XGIbios_mode[myindex].yres
								!= 480))
					return -1;
				break;
			case 800:
				if (XGIbios_mode[myindex].yres != 600)
					return -1;
				break;
			case 1024:
				if (XGIbios_mode[myindex].yres != 768)
					return -1;
				break;
			case 1280:
				if ((XGIbios_mode[myindex].yres != 960)
						&& (XGIbios_mode[myindex].yres
								!= 1024))
					return -1;
				if (XGIbios_mode[myindex].yres == 960) {
					if (XGIhw_ext.ulCRT2LCDType
							== LCD_1400x1050)
						return -1;
				}
				break;
			case 1400:
				if (XGIbios_mode[myindex].yres != 1050)
					return -1;
				break;
			case 1600:
				if (XGIbios_mode[myindex].yres != 1200)
					return -1;
				break;
			default:
				return -1;
			}
		}
		break;
	case DISPTYPE_TV:
		switch (XGIbios_mode[myindex].xres) {
		case 512:
		case 640:
		case 800:
			break;
		case 720:
			if (xgi_video_info.TV_type == TVMODE_NTSC) {
				if (XGIbios_mode[myindex].yres != 480)
					return -1;
			} else if (xgi_video_info.TV_type == TVMODE_PAL) {
				if (XGIbios_mode[myindex].yres != 576)
					return -1;
			}
			/*  TW: LVDS/CHRONTEL does not support 720 */
			if (xgi_video_info.hasVB == HASVB_LVDS_CHRONTEL
					|| xgi_video_info.hasVB	== HASVB_CHRONTEL) {
				return -1;
			}
			break;
		case 1024:
			if (xgi_video_info.TV_type == TVMODE_NTSC) {
				if (XGIbios_mode[myindex].bpp == 32)
					return -1;
			}
			break;
		default:
			return -1;
		}
		break;
	case DISPTYPE_CRT2:
		if (XGIbios_mode[myindex].xres > 1280)
			return -1;
		break;
	}
	return myindex;

}

static void XGIfb_search_crt2type(const char *name)
{
	int i = 0;

	if (name == NULL)
		return;

	while (XGI_crt2type[i].type_no != -1) {
		if (!strcmp(name, XGI_crt2type[i].name)) {
			XGIfb_crt2type = XGI_crt2type[i].type_no;
			XGIfb_tvplug = XGI_crt2type[i].tvplug_no;
			break;
		}
		i++;
	}
	if (XGIfb_crt2type < 0)
		printk(KERN_INFO "XGIfb: Invalid CRT2 type: %s\n", name);
}

static u8 XGIfb_search_refresh_rate(unsigned int rate)
{
	u16 xres, yres;
	int i = 0;

	xres = XGIbios_mode[xgifb_mode_idx].xres;
	yres = XGIbios_mode[xgifb_mode_idx].yres;

	XGIfb_rate_idx = 0;
	while ((XGIfb_vrate[i].idx != 0) && (XGIfb_vrate[i].xres <= xres)) {
		if ((XGIfb_vrate[i].xres == xres) && (XGIfb_vrate[i].yres
				== yres)) {
			if (XGIfb_vrate[i].refresh == rate) {
				XGIfb_rate_idx = XGIfb_vrate[i].idx;
				break;
			} else if (XGIfb_vrate[i].refresh > rate) {
				if ((XGIfb_vrate[i].refresh - rate) <= 3) {
					DPRINTK("XGIfb: Adjusting rate from %d up to %d\n",
							rate, XGIfb_vrate[i].refresh);
					XGIfb_rate_idx = XGIfb_vrate[i].idx;
					xgi_video_info.refresh_rate
							= XGIfb_vrate[i].refresh;
				} else if (((rate - XGIfb_vrate[i - 1].refresh)
						<= 2) && (XGIfb_vrate[i].idx
						!= 1)) {
					DPRINTK("XGIfb: Adjusting rate from %d down to %d\n",
							rate, XGIfb_vrate[i-1].refresh);
					XGIfb_rate_idx = XGIfb_vrate[i - 1].idx;
					xgi_video_info.refresh_rate
							= XGIfb_vrate[i - 1].refresh;
				}
				break;
			} else if ((rate - XGIfb_vrate[i].refresh) <= 2) {
				DPRINTK("XGIfb: Adjusting rate from %d down to %d\n",
						rate, XGIfb_vrate[i].refresh);
				XGIfb_rate_idx = XGIfb_vrate[i].idx;
				break;
			}
		}
		i++;
	}
	if (XGIfb_rate_idx > 0) {
		return XGIfb_rate_idx;
	} else {
		printk(KERN_INFO
				"XGIfb: Unsupported rate %d for %dx%d\n", rate, xres, yres);
		return 0;
	}
}

static void XGIfb_search_tvstd(const char *name)
{
	int i = 0;

	if (name == NULL)
		return;

	while (XGI_tvtype[i].type_no != -1) {
		if (!strcmp(name, XGI_tvtype[i].name)) {
			XGIfb_tvmode = XGI_tvtype[i].type_no;
			break;
		}
		i++;
	}
}

/* ----------- FBDev related routines for all series ----------- */

static void XGIfb_bpp_to_var(struct fb_var_screeninfo *var)
{
	switch (var->bits_per_pixel) {
	case 8:
		var->red.offset = var->green.offset = var->blue.offset = 0;
		var->red.length = var->green.length = var->blue.length = 6;
		xgi_video_info.video_cmap_len = 256;
		break;
	case 16:
		var->red.offset = 11;
		var->red.length = 5;
		var->green.offset = 5;
		var->green.length = 6;
		var->blue.offset = 0;
		var->blue.length = 5;
		var->transp.offset = 0;
		var->transp.length = 0;
		xgi_video_info.video_cmap_len = 16;
		break;
	case 32:
		var->red.offset = 16;
		var->red.length = 8;
		var->green.offset = 8;
		var->green.length = 8;
		var->blue.offset = 0;
		var->blue.length = 8;
		var->transp.offset = 24;
		var->transp.length = 8;
		xgi_video_info.video_cmap_len = 16;
		break;
	}
}

static int XGIfb_do_set_var(struct fb_var_screeninfo *var, int isactive,
		struct fb_info *info)
{

	unsigned int htotal = var->left_margin + var->xres + var->right_margin
			+ var->hsync_len;
	unsigned int vtotal = var->upper_margin + var->yres + var->lower_margin
			+ var->vsync_len;
#if defined(__powerpc__)
	u8 sr_data, cr_data;
#endif
	unsigned int drate = 0, hrate = 0;
	int found_mode = 0;
	int old_mode;
	/* unsigned char reg, reg1; */

	DEBUGPRN("Inside do_set_var");
	/* printk(KERN_DEBUG "XGIfb:var->yres=%d, var->upper_margin=%d, var->lower_margin=%d, var->vsync_len=%d\n", var->yres, var->upper_margin, var->lower_margin, var->vsync_len); */

	info->var.xres_virtual = var->xres_virtual;
	info->var.yres_virtual = var->yres_virtual;
	info->var.bits_per_pixel = var->bits_per_pixel;

	if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED)
		vtotal <<= 1;
	else if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE)
		vtotal <<= 2;
	else if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
		/* vtotal <<= 1; */
		/* var->yres <<= 1; */
	}

	if (!htotal || !vtotal) {
		DPRINTK("XGIfb: Invalid 'var' information\n");
		return -EINVAL;
	} printk(KERN_DEBUG "XGIfb: var->pixclock=%d, htotal=%d, vtotal=%d\n",
			var->pixclock, htotal, vtotal);

	if (var->pixclock && htotal && vtotal) {
		drate = 1000000000 / var->pixclock;
		hrate = (drate * 1000) / htotal;
		xgi_video_info.refresh_rate = (unsigned int) (hrate * 2
				/ vtotal);
	} else {
		xgi_video_info.refresh_rate = 60;
	}

	printk(KERN_DEBUG "XGIfb: Change mode to %dx%dx%d-%dHz\n",
			var->xres, var->yres, var->bits_per_pixel, xgi_video_info.refresh_rate);

	old_mode = xgifb_mode_idx;
	xgifb_mode_idx = 0;

	while ((XGIbios_mode[xgifb_mode_idx].mode_no != 0)
			&& (XGIbios_mode[xgifb_mode_idx].xres <= var->xres)) {
		if ((XGIbios_mode[xgifb_mode_idx].xres == var->xres)
				&& (XGIbios_mode[xgifb_mode_idx].yres
						== var->yres)
				&& (XGIbios_mode[xgifb_mode_idx].bpp
						== var->bits_per_pixel)) {
			XGIfb_mode_no = XGIbios_mode[xgifb_mode_idx].mode_no;
			found_mode = 1;
			break;
		}
		xgifb_mode_idx++;
	}

	if (found_mode)
		xgifb_mode_idx = XGIfb_validate_mode(xgifb_mode_idx);
	else
		xgifb_mode_idx = -1;

	if (xgifb_mode_idx < 0) {
		printk(KERN_ERR "XGIfb: Mode %dx%dx%d not supported\n", var->xres,
				var->yres, var->bits_per_pixel);
		xgifb_mode_idx = old_mode;
		return -EINVAL;
	}

	if (XGIfb_search_refresh_rate(xgi_video_info.refresh_rate) == 0) {
		XGIfb_rate_idx = XGIbios_mode[xgifb_mode_idx].rate_idx;
		xgi_video_info.refresh_rate = 60;
	}

	if (isactive) {

		XGIfb_pre_setmode();
		if (XGISetModeNew(&XGIhw_ext, XGIfb_mode_no) == 0) {
			printk(KERN_ERR "XGIfb: Setting mode[0x%x] failed\n", XGIfb_mode_no);
			return -EINVAL;
		}
		info->fix.line_length = ((info->var.xres_virtual
				* info->var.bits_per_pixel) >> 6);

		outXGIIDXREG(XGISR, IND_XGI_PASSWORD, XGI_PASSWORD);

		outXGIIDXREG(XGICR, 0x13, (info->fix.line_length & 0x00ff));
		outXGIIDXREG(XGISR, 0x0E, (info->fix.line_length & 0xff00) >> 8);

		XGIfb_post_setmode();

		DPRINTK("XGIfb: Set new mode: %dx%dx%d-%d\n",
				XGIbios_mode[xgifb_mode_idx].xres,
				XGIbios_mode[xgifb_mode_idx].yres,
				XGIbios_mode[xgifb_mode_idx].bpp,
				xgi_video_info.refresh_rate);

		xgi_video_info.video_bpp = XGIbios_mode[xgifb_mode_idx].bpp;
		xgi_video_info.video_vwidth = info->var.xres_virtual;
		xgi_video_info.video_width = XGIbios_mode[xgifb_mode_idx].xres;
		xgi_video_info.video_vheight = info->var.yres_virtual;
		xgi_video_info.video_height = XGIbios_mode[xgifb_mode_idx].yres;
		xgi_video_info.org_x = xgi_video_info.org_y = 0;
		xgi_video_info.video_linelength = info->var.xres_virtual
				* (xgi_video_info.video_bpp >> 3);
		switch (xgi_video_info.video_bpp) {
		case 8:
			xgi_video_info.DstColor = 0x0000;
			xgi_video_info.XGI310_AccelDepth = 0x00000000;
			xgi_video_info.video_cmap_len = 256;
#if defined(__powerpc__)
			inXGIIDXREG(XGICR, 0x4D, cr_data);
			outXGIIDXREG(XGICR, 0x4D, (cr_data & 0xE0));
#endif
			break;
		case 16:
			xgi_video_info.DstColor = 0x8000;
			xgi_video_info.XGI310_AccelDepth = 0x00010000;
#if defined(__powerpc__)
			inXGIIDXREG(XGICR, 0x4D, cr_data);
			outXGIIDXREG(XGICR, 0x4D, ((cr_data & 0xE0) | 0x0B));
#endif
			xgi_video_info.video_cmap_len = 16;
			break;
		case 32:
			xgi_video_info.DstColor = 0xC000;
			xgi_video_info.XGI310_AccelDepth = 0x00020000;
			xgi_video_info.video_cmap_len = 16;
#if defined(__powerpc__)
			inXGIIDXREG(XGICR, 0x4D, cr_data);
			outXGIIDXREG(XGICR, 0x4D, ((cr_data & 0xE0) | 0x15));
#endif
			break;
		default:
			xgi_video_info.video_cmap_len = 16;
			printk(KERN_ERR "XGIfb: Unsupported depth %d", xgi_video_info.video_bpp);
			break;
		}
	}
	XGIfb_bpp_to_var(var); /*update ARGB info*/
	DEBUGPRN("End of do_set_var");

	dumpVGAReg();
	return 0;
}

#ifdef XGIFB_PAN
static int XGIfb_pan_var(struct fb_var_screeninfo *var)
{
	unsigned int base;

	/* printk("Inside pan_var"); */

	if (var->xoffset > (var->xres_virtual - var->xres)) {
		/* printk("Pan: xo: %d xv %d xr %d\n",
			var->xoffset, var->xres_virtual, var->xres); */
		return -EINVAL;
	}
	if (var->yoffset > (var->yres_virtual - var->yres)) {
		/* printk("Pan: yo: %d yv %d yr %d\n",
			var->yoffset, var->yres_virtual, var->yres); */
		return -EINVAL;
	}
	base = var->yoffset * var->xres_virtual + var->xoffset;

	/* calculate base bpp dep. */
	switch (var->bits_per_pixel) {
	case 16:
		base >>= 1;
		break;
	case 32:
		break;
	case 8:
	default:
		base >>= 2;
		break;
	}

	outXGIIDXREG(XGISR, IND_XGI_PASSWORD, XGI_PASSWORD);

	outXGIIDXREG(XGICR, 0x0D, base & 0xFF);
	outXGIIDXREG(XGICR, 0x0C, (base >> 8) & 0xFF);
	outXGIIDXREG(XGISR, 0x0D, (base >> 16) & 0xFF);
	outXGIIDXREG(XGISR, 0x37, (base >> 24) & 0x03);
	setXGIIDXREG(XGISR, 0x37, 0xDF, (base >> 21) & 0x04);

	if (xgi_video_info.disp_state & DISPTYPE_DISP2) {
		orXGIIDXREG(XGIPART1, XGIfb_CRT2_write_enable, 0x01);
		outXGIIDXREG(XGIPART1, 0x06, (base & 0xFF));
		outXGIIDXREG(XGIPART1, 0x05, ((base >> 8) & 0xFF));
		outXGIIDXREG(XGIPART1, 0x04, ((base >> 16) & 0xFF));
		setXGIIDXREG(XGIPART1, 0x02, 0x7F, ((base >> 24) & 0x01) << 7);
	}
	/* printk("End of pan_var"); */
	return 0;
}
#endif

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

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

static int XGIfb_get_cmap_len(const struct fb_var_screeninfo *var)
{
	int rc = 16;

	switch (var->bits_per_pixel) {
	case 8:
		rc = 256;
		break;
	case 16:
		rc = 16;
		break;
	case 32:
		rc = 16;
		break;
	}
	return rc;
}

static int XGIfb_setcolreg(unsigned regno, unsigned red, unsigned green,
		unsigned blue, unsigned transp, struct fb_info *info)
{
	if (regno >= XGIfb_get_cmap_len(&info->var))
		return 1;

	switch (info->var.bits_per_pixel) {
	case 8:
		outXGIREG(XGIDACA, regno);
		outXGIREG(XGIDACD, (red >> 10));
		outXGIREG(XGIDACD, (green >> 10));
		outXGIREG(XGIDACD, (blue >> 10));
		if (xgi_video_info.disp_state & DISPTYPE_DISP2) {
			outXGIREG(XGIDAC2A, regno);
			outXGIREG(XGIDAC2D, (red >> 8));
			outXGIREG(XGIDAC2D, (green >> 8));
			outXGIREG(XGIDAC2D, (blue >> 8));
		}
		break;
	case 16:
		((u32 *) (info->pseudo_palette))[regno] = ((red & 0xf800))
				| ((green & 0xfc00) >> 5) | ((blue & 0xf800)
				>> 11);
		break;
	case 32:
		red >>= 8;
		green >>= 8;
		blue >>= 8;
		((u32 *) (info->pseudo_palette))[regno] = (red << 16) | (green
				<< 8) | (blue);
		break;
	}
	return 0;
}

static int XGIfb_set_par(struct fb_info *info)
{
	int err;

	/* printk("XGIfb: inside set_par\n"); */
	err = XGIfb_do_set_var(&info->var, 1, info);
	if (err)
		return err;
	XGIfb_get_fix(&info->fix, -1, info);
	/* printk("XGIfb: end of set_par\n"); */
	return 0;
}

static int XGIfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
	unsigned int htotal = var->left_margin + var->xres + var->right_margin
			+ var->hsync_len;
	unsigned int vtotal = 0;
	unsigned int drate = 0, hrate = 0;
	int found_mode = 0;
	int refresh_rate, search_idx;

	DEBUGPRN("Inside check_var");

	if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) {
		vtotal = var->upper_margin + var->yres + var->lower_margin
				+ var->vsync_len;
		vtotal <<= 1;
	} else if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) {
		vtotal = var->upper_margin + var->yres + var->lower_margin
				+ var->vsync_len;
		vtotal <<= 2;
	} else if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
		vtotal = var->upper_margin + (var->yres / 2)
				+ var->lower_margin + var->vsync_len;
	} else
		vtotal = var->upper_margin + var->yres + var->lower_margin
				+ var->vsync_len;

	if (!(htotal) || !(vtotal))
		XGIFAIL("XGIfb: no valid timing data");

	if (var->pixclock && htotal && vtotal) {
		drate = 1000000000 / var->pixclock;
		hrate = (drate * 1000) / htotal;
		xgi_video_info.refresh_rate = (unsigned int) (hrate * 2	/ vtotal);
		printk(KERN_DEBUG
			"%s: pixclock = %d ,htotal=%d, vtotal=%d\n"
			"%s: drate=%d, hrate=%d, refresh_rate=%d\n",
			__func__, var->pixclock, htotal, vtotal,
			__func__, drate, hrate, xgi_video_info.refresh_rate);
	} else {
		xgi_video_info.refresh_rate = 60;
	}

	/*
	if ((var->pixclock) && (htotal)) {
		drate = 1E12 / var->pixclock;
		hrate = drate / htotal;
		refresh_rate = (unsigned int) (hrate / vtotal * 2 + 0.5);
	} else {
		refresh_rate = 60;
	}
	*/
	/* TW: Calculation wrong for 1024x600 - force it to 60Hz */
	if ((var->xres == 1024) && (var->yres == 600))
		refresh_rate = 60;

	search_idx = 0;
	while ((XGIbios_mode[search_idx].mode_no != 0) &&
		(XGIbios_mode[search_idx].xres <= var->xres)) {
		if ((XGIbios_mode[search_idx].xres == var->xres) &&
			(XGIbios_mode[search_idx].yres == var->yres) &&
			(XGIbios_mode[search_idx].bpp == var->bits_per_pixel)) {
			if (XGIfb_validate_mode(search_idx) > 0) {
				found_mode = 1;
				break;
			}
		}
		search_idx++;
	}

	if (!found_mode) {

		printk(KERN_ERR "XGIfb: %dx%dx%d is no valid mode\n",
			var->xres, var->yres, var->bits_per_pixel);
		search_idx = 0;
		while (XGIbios_mode[search_idx].mode_no != 0) {

			if ((var->xres <= XGIbios_mode[search_idx].xres) &&
				(var->yres <= XGIbios_mode[search_idx].yres) &&
				(var->bits_per_pixel == XGIbios_mode[search_idx].bpp)) {
				if (XGIfb_validate_mode(search_idx) > 0) {
					found_mode = 1;
					break;
				}
			}
			search_idx++;
		}
		if (found_mode) {
			var->xres = XGIbios_mode[search_idx].xres;
			var->yres = XGIbios_mode[search_idx].yres;
			printk(KERN_DEBUG "XGIfb: Adapted to mode %dx%dx%d\n",
				var->xres, var->yres, var->bits_per_pixel);

		} else {
			printk(KERN_ERR "XGIfb: Failed to find similar mode to %dx%dx%d\n",
				var->xres, var->yres, var->bits_per_pixel);
			return -EINVAL;
		}
	}

	/* TW: TODO: Check the refresh rate */

	/* Adapt RGB settings */
	XGIfb_bpp_to_var(var);

	/* Sanity check for offsets */
	if (var->xoffset < 0)
		var->xoffset = 0;
	if (var->yoffset < 0)
		var->yoffset = 0;

	if (!XGIfb_ypan) {
		if (var->xres != var->xres_virtual)
			var->xres_virtual = var->xres;
		if (var->yres != var->yres_virtual)
			var->yres_virtual = var->yres;
	} /* else { */
		/* TW: Now patch yres_virtual if we use panning */
		/* May I do this? */
		/* var->yres_virtual = xgi_video_info.heapstart / (var->xres * (var->bits_per_pixel >> 3)); */
		/* if (var->yres_virtual <= var->yres) { */
		/* TW: Paranoia check */
		/* var->yres_virtual = var->yres; */
		/* } */
	/* } */

	/* Truncate offsets to maximum if too high */
	if (var->xoffset > var->xres_virtual - var->xres)
		var->xoffset = var->xres_virtual - var->xres - 1;

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

	/* Set everything else to 0 */
	var->red.msb_right =
	var->green.msb_right =
	var->blue.msb_right =
	var->transp.offset = var->transp.length = var->transp.msb_right = 0;

	DEBUGPRN("end of check_var");
	return 0;
}

#ifdef XGIFB_PAN
static int XGIfb_pan_display(struct fb_var_screeninfo *var,
		struct fb_info *info)
{
	int err;

	/* printk("\nInside pan_display:\n"); */

	if (var->xoffset > (var->xres_virtual - var->xres))
		return -EINVAL;
	if (var->yoffset > (var->yres_virtual - var->yres))
		return -EINVAL;

	if (var->vmode & FB_VMODE_YWRAP) {
		if (var->yoffset < 0 || var->yoffset >= info->var.yres_virtual
				|| var->xoffset)
			return -EINVAL;
	} else {
		if (var->xoffset + info->var.xres > info->var.xres_virtual
				|| var->yoffset + info->var.yres
						> info->var.yres_virtual)
			return -EINVAL;
	}
	err = XGIfb_pan_var(var);
	if (err < 0)
		return err;

	info->var.xoffset = var->xoffset;
	info->var.yoffset = var->yoffset;
	if (var->vmode & FB_VMODE_YWRAP)
		info->var.vmode |= FB_VMODE_YWRAP;
	else
		info->var.vmode &= ~FB_VMODE_YWRAP;

	/* printk("End of pan_display\n"); */
	return 0;
}
#endif

static int XGIfb_blank(int blank, struct fb_info *info)
{
	u8 reg;

	inXGIIDXREG(XGICR, 0x17, reg);

	if (blank > 0)
		reg &= 0x7f;
	else
		reg |= 0x80;

	outXGIIDXREG(XGICR, 0x17, reg);
	outXGIIDXREG(XGISR, 0x00, 0x01); /* Synchronous Reset */
	outXGIIDXREG(XGISR, 0x00, 0x03); /* End Reset */
	return 0;
}

/* ----------- FBDev related routines for all series ---------- */

static int XGIfb_get_fix(struct fb_fix_screeninfo *fix, int con,
		struct fb_info *info)
{
	DEBUGPRN("inside get_fix");
	memset(fix, 0, sizeof(struct fb_fix_screeninfo));

	strcpy(fix->id, myid);

	fix->smem_start = xgi_video_info.video_base;

	fix->smem_len = xgi_video_info.video_size;

	fix->type = video_type;
	fix->type_aux = 0;
	if (xgi_video_info.video_bpp == 8)
		fix->visual = FB_VISUAL_PSEUDOCOLOR;
	else
		fix->visual = FB_VISUAL_DIRECTCOLOR;
	fix->xpanstep = 0;
#ifdef XGIFB_PAN
	if (XGIfb_ypan)
		fix->ypanstep = 1;
#endif
	fix->ywrapstep = 0;
	fix->line_length = xgi_video_info.video_linelength;
	fix->mmio_start = xgi_video_info.mmio_base;
	fix->mmio_len = xgi_video_info.mmio_size;
	fix->accel = FB_ACCEL_XGI_XABRE;

	DEBUGPRN("end of get_fix");
	return 0;
}

static struct fb_ops XGIfb_ops = {
	.owner = THIS_MODULE,
	.fb_open = XGIfb_open,
	.fb_release = XGIfb_release,
	.fb_check_var = XGIfb_check_var,
	.fb_set_par = XGIfb_set_par,
	.fb_setcolreg = XGIfb_setcolreg,
#ifdef XGIFB_PAN
	.fb_pan_display = XGIfb_pan_display,
#endif
	.fb_blank = XGIfb_blank,
	.fb_fillrect = cfb_fillrect,
	.fb_copyarea = cfb_copyarea,
	.fb_imageblit = cfb_imageblit,
	/* .fb_mmap = XGIfb_mmap, */
};

/* ---------------- Chip generation dependent routines ---------------- */

/* for XGI 315/550/650/740/330 */

static int XGIfb_get_dram_size(void)
{

	u8 ChannelNum, tmp;
	u8 reg = 0;

	/* xorg driver sets 32MB * 1 channel */
	if (xgi_video_info.chip == XG27)
		outXGIIDXREG(XGISR, IND_XGI_DRAM_SIZE, 0x51);

	inXGIIDXREG(XGISR, IND_XGI_DRAM_SIZE, reg);
	switch ((reg & XGI_DRAM_SIZE_MASK) >> 4) {
	case XGI_DRAM_SIZE_1MB:
		xgi_video_info.video_size = 0x100000;
		break;
	case XGI_DRAM_SIZE_2MB:
		xgi_video_info.video_size = 0x200000;
		break;
	case XGI_DRAM_SIZE_4MB:
		xgi_video_info.video_size = 0x400000;
		break;
	case XGI_DRAM_SIZE_8MB:
		xgi_video_info.video_size = 0x800000;
		break;
	case XGI_DRAM_SIZE_16MB:
		xgi_video_info.video_size = 0x1000000;
		break;
	case XGI_DRAM_SIZE_32MB:
		xgi_video_info.video_size = 0x2000000;
		break;
	case XGI_DRAM_SIZE_64MB:
		xgi_video_info.video_size = 0x4000000;
		break;
	case XGI_DRAM_SIZE_128MB:
		xgi_video_info.video_size = 0x8000000;
		break;
	case XGI_DRAM_SIZE_256MB:
		xgi_video_info.video_size = 0x10000000;
		break;
	default:
		return -1;
	}

	tmp = (reg & 0x0c) >> 2;
	switch (xgi_video_info.chip) {
	case XG20:
	case XG21:
	case XG27:
		ChannelNum = 1;
		break;

	case XG42:
		if (reg & 0x04)
			ChannelNum = 2;
		else
			ChannelNum = 1;
		break;

	case XG45:
		if (tmp == 1)
			ChannelNum = 2;
		else if (tmp == 2)
			ChannelNum = 3;
		else if (tmp == 3)
			ChannelNum = 4;
		else
			ChannelNum = 1;
		break;

	case XG40:
	default:
		if (tmp == 2)
			ChannelNum = 2;
		else if (tmp == 3)
			ChannelNum = 3;
		else
			ChannelNum = 1;
		break;
	}

	xgi_video_info.video_size = xgi_video_info.video_size * ChannelNum;
	/* PLiad fixed for benchmarking and fb set */
	/* xgi_video_info.video_size = 0x200000; */ /* 1024x768x16 */
	/* xgi_video_info.video_size = 0x1000000; */ /* benchmark */

	printk("XGIfb: SR14=%x DramSzie %x ChannelNum %x\n", reg,
			xgi_video_info.video_size, ChannelNum);
	return 0;

}

static void XGIfb_detect_VB(void)
{
	u8 cr32, temp = 0;

	xgi_video_info.TV_plug = xgi_video_info.TV_type = 0;

	switch (xgi_video_info.hasVB) {
	case HASVB_LVDS_CHRONTEL:
	case HASVB_CHRONTEL:
		break;
	case HASVB_301:
	case HASVB_302:
		/* XGI_Sense30x(); */ /* Yi-Lin TV Sense? */
		break;
	}

	inXGIIDXREG(XGICR, IND_XGI_SCRATCH_REG_CR32, cr32);

	if ((cr32 & XGI_CRT1) && !XGIfb_crt1off)
		XGIfb_crt1off = 0;
	else {
		if (cr32 & 0x5F)
			XGIfb_crt1off = 1;
		else
			XGIfb_crt1off = 0;
	}

	if (XGIfb_crt2type != -1)
		/* TW: Override with option */
		xgi_video_info.disp_state = XGIfb_crt2type;
	else if (cr32 & XGI_VB_TV)
		xgi_video_info.disp_state = DISPTYPE_TV;
	else if (cr32 & XGI_VB_LCD)
		xgi_video_info.disp_state = DISPTYPE_LCD;
	else if (cr32 & XGI_VB_CRT2)
		xgi_video_info.disp_state = DISPTYPE_CRT2;
	else
		xgi_video_info.disp_state = 0;

	if (XGIfb_tvplug != -1)
		/* PR/TW: Override with option */
		xgi_video_info.TV_plug = XGIfb_tvplug;
	else if (cr32 & XGI_VB_HIVISION) {
		xgi_video_info.TV_type = TVMODE_HIVISION;
		xgi_video_info.TV_plug = TVPLUG_SVIDEO;
	} else if (cr32 & XGI_VB_SVIDEO)
		xgi_video_info.TV_plug = TVPLUG_SVIDEO;
	else if (cr32 & XGI_VB_COMPOSITE)
		xgi_video_info.TV_plug = TVPLUG_COMPOSITE;
	else if (cr32 & XGI_VB_SCART)
		xgi_video_info.TV_plug = TVPLUG_SCART;

	if (xgi_video_info.TV_type == 0) {
		inXGIIDXREG(XGICR, 0x38, temp);
		if (temp & 0x10)
			xgi_video_info.TV_type = TVMODE_PAL;
		else
			xgi_video_info.TV_type = TVMODE_NTSC;
	}

	/* TW: Copy forceCRT1 option to CRT1off if option is given */
	if (XGIfb_forcecrt1 != -1) {
		if (XGIfb_forcecrt1)
			XGIfb_crt1off = 0;
		else
			XGIfb_crt1off = 1;
	}
}

static void XGIfb_get_VB_type(void)
{
	u8 reg;

	if (!XGIfb_has_VB()) {
		inXGIIDXREG(XGICR, IND_XGI_SCRATCH_REG_CR37, reg);
		switch ((reg & XGI_EXTERNAL_CHIP_MASK) >> 1) {
		case XGI310_EXTERNAL_CHIP_LVDS:
			xgi_video_info.hasVB = HASVB_LVDS;
			break;
		case XGI310_EXTERNAL_CHIP_LVDS_CHRONTEL:
			xgi_video_info.hasVB = HASVB_LVDS_CHRONTEL;
			break;
		default:
			break;
		}
	}
}

static int XGIfb_has_VB(void)
{
	u8 vb_chipid;

	inXGIIDXREG(XGIPART4, 0x00, vb_chipid);
	switch (vb_chipid) {
	case 0x01:
		xgi_video_info.hasVB = HASVB_301;
		break;
	case 0x02:
		xgi_video_info.hasVB = HASVB_302;
		break;
	default:
		xgi_video_info.hasVB = HASVB_NONE;
		return 0;
	}
	return 1;
}

/* ------------------ Sensing routines ------------------ */

/* TW: Determine and detect attached devices on XGI30x */
int XGIDoSense(int tempbl, int tempbh, int tempcl, int tempch)
{
	int temp, i;

	outXGIIDXREG(XGIPART4, 0x11, tempbl);
	temp = tempbh | tempcl;
	setXGIIDXREG(XGIPART4, 0x10, 0xe0, temp);
	for (i = 0; i < 10; i++)
		XGI_LongWait(&XGI_Pr);
	tempch &= 0x7f;
	inXGIIDXREG(XGIPART4, 0x03, temp);
	temp ^= 0x0e;
	temp &= tempch;
	return temp;
}

void XGI_Sense30x(void)
{
	u8 backupP4_0d;
	u8 testsvhs_tempbl, testsvhs_tempbh;
	u8 testsvhs_tempcl, testsvhs_tempch;
	u8 testcvbs_tempbl, testcvbs_tempbh;
	u8 testcvbs_tempcl, testcvbs_tempch;
	u8 testvga2_tempbl, testvga2_tempbh;
	u8 testvga2_tempcl, testvga2_tempch;
	int myflag, result;

	inXGIIDXREG(XGIPART4, 0x0d, backupP4_0d);
	outXGIIDXREG(XGIPART4, 0x0d, (backupP4_0d | 0x04));

	testvga2_tempbh = 0x00;
	testvga2_tempbl = 0xd1;
	testsvhs_tempbh = 0x00;
	testsvhs_tempbl = 0xb9;
	testcvbs_tempbh = 0x00;
	testcvbs_tempbl = 0xb3;
	if ((XGIhw_ext.ujVBChipID != VB_CHIP_301) && (XGIhw_ext.ujVBChipID
			!= VB_CHIP_302)) {
		testvga2_tempbh = 0x01;
		testvga2_tempbl = 0x90;
		testsvhs_tempbh = 0x01;
		testsvhs_tempbl = 0x6b;
		testcvbs_tempbh = 0x01;
		testcvbs_tempbl = 0x74;
		if (XGIhw_ext.ujVBChipID == VB_CHIP_301LV
				|| XGIhw_ext.ujVBChipID == VB_CHIP_302LV) {
			testvga2_tempbh = 0x00;
			testvga2_tempbl = 0x00;
			testsvhs_tempbh = 0x02;
			testsvhs_tempbl = 0x00;
			testcvbs_tempbh = 0x01;
			testcvbs_tempbl = 0x00;
		}
	}
	if (XGIhw_ext.ujVBChipID != VB_CHIP_301LV && XGIhw_ext.ujVBChipID
			!= VB_CHIP_302LV) {
		inXGIIDXREG(XGIPART4, 0x01, myflag);
		if (myflag & 0x04) {
			testvga2_tempbh = 0x00;
			testvga2_tempbl = 0xfd;
			testsvhs_tempbh = 0x00;
			testsvhs_tempbl = 0xdd;
			testcvbs_tempbh = 0x00;
			testcvbs_tempbl = 0xee;
		}
	}
	if ((XGIhw_ext.ujVBChipID == VB_CHIP_301LV) || (XGIhw_ext.ujVBChipID
			== VB_CHIP_302LV)) {
		testvga2_tempbh = 0x00;
		testvga2_tempbl = 0x00;
		testvga2_tempch = 0x00;
		testvga2_tempcl = 0x00;
		testsvhs_tempch = 0x04;
		testsvhs_tempcl = 0x08;
		testcvbs_tempch = 0x08;
		testcvbs_tempcl = 0x08;
	} else {
		testvga2_tempch = 0x0e;
		testvga2_tempcl = 0x08;
		testsvhs_tempch = 0x06;
		testsvhs_tempcl = 0x04;
		testcvbs_tempch = 0x08;
		testcvbs_tempcl = 0x04;
	}

	if (testvga2_tempch || testvga2_tempcl || testvga2_tempbh
			|| testvga2_tempbl) {
		result = XGIDoSense(testvga2_tempbl, testvga2_tempbh,
				testvga2_tempcl, testvga2_tempch);
		if (result) {
			printk(KERN_INFO "XGIfb: Detected secondary VGA connection\n");
			orXGIIDXREG(XGICR, 0x32, 0x10);
		}
	}

	result = XGIDoSense(testsvhs_tempbl, testsvhs_tempbh, testsvhs_tempcl,
			testsvhs_tempch);
	if (result) {
		printk(KERN_INFO "XGIfb: Detected TV connected to SVHS output\n");
		/* TW: So we can be sure that there IS a SVHS output */
		xgi_video_info.TV_plug = TVPLUG_SVIDEO;
		orXGIIDXREG(XGICR, 0x32, 0x02);
	}

	if (!result) {
		result = XGIDoSense(testcvbs_tempbl, testcvbs_tempbh,
				testcvbs_tempcl, testcvbs_tempch);
		if (result) {
			printk(KERN_INFO "XGIfb: Detected TV connected to CVBS output\n");
			/* TW: So we can be sure that there IS a CVBS output */
			xgi_video_info.TV_plug = TVPLUG_COMPOSITE;
			orXGIIDXREG(XGICR, 0x32, 0x01);
		}
	}
	XGIDoSense(0, 0, 0, 0);

	outXGIIDXREG(XGIPART4, 0x0d, backupP4_0d);
}

/* --------------------- SetMode routines ------------------------- */

static void XGIfb_pre_setmode(void)
{
	u8 cr30 = 0, cr31 = 0;

	inXGIIDXREG(XGICR, 0x31, cr31);
	cr31 &= ~0x60;

	switch (xgi_video_info.disp_state & DISPTYPE_DISP2) {
	case DISPTYPE_CRT2:
		cr30 = (XGI_VB_OUTPUT_CRT2 | XGI_SIMULTANEOUS_VIEW_ENABLE);
		cr31 |= XGI_DRIVER_MODE;
		break;
	case DISPTYPE_LCD:
		cr30 = (XGI_VB_OUTPUT_LCD | XGI_SIMULTANEOUS_VIEW_ENABLE);
		cr31 |= XGI_DRIVER_MODE;
		break;
	case DISPTYPE_TV:
		if (xgi_video_info.TV_type == TVMODE_HIVISION)
			cr30 = (XGI_VB_OUTPUT_HIVISION
					| XGI_SIMULTANEOUS_VIEW_ENABLE);
		else if (xgi_video_info.TV_plug == TVPLUG_SVIDEO)
			cr30 = (XGI_VB_OUTPUT_SVIDEO
					| XGI_SIMULTANEOUS_VIEW_ENABLE);
		else if (xgi_video_info.TV_plug == TVPLUG_COMPOSITE)
			cr30 = (XGI_VB_OUTPUT_COMPOSITE
					| XGI_SIMULTANEOUS_VIEW_ENABLE);
		else if (xgi_video_info.TV_plug == TVPLUG_SCART)
			cr30 = (XGI_VB_OUTPUT_SCART
					| XGI_SIMULTANEOUS_VIEW_ENABLE);
		cr31 |= XGI_DRIVER_MODE;

		if (XGIfb_tvmode == 1 || xgi_video_info.TV_type == TVMODE_PAL)
			cr31 |= 0x01;
		else
			cr31 &= ~0x01;
		break;
	default: /* disable CRT2 */
		cr30 = 0x00;
		cr31 |= (XGI_DRIVER_MODE | XGI_VB_OUTPUT_DISABLE);
	}

	outXGIIDXREG(XGICR, IND_XGI_SCRATCH_REG_CR30, cr30);
	outXGIIDXREG(XGICR, IND_XGI_SCRATCH_REG_CR31, cr31);
	outXGIIDXREG(XGICR, IND_XGI_SCRATCH_REG_CR33, (XGIfb_rate_idx & 0x0F));
}

static void XGIfb_post_setmode(void)
{
	u8 reg;
	unsigned char doit = 1;
	/*
	outXGIIDXREG(XGISR,IND_XGI_PASSWORD,XGI_PASSWORD);
	outXGIIDXREG(XGICR, 0x13, 0x00);
	setXGIIDXREG(XGISR,0x0E, 0xF0, 0x01);
	*test*
	*/
	if (xgi_video_info.video_bpp == 8) {
		/* TW: We can't switch off CRT1 on LVDS/Chrontel in 8bpp Modes */
		if ((xgi_video_info.hasVB == HASVB_LVDS)
				|| (xgi_video_info.hasVB == HASVB_LVDS_CHRONTEL)) {
			doit = 0;
		}
		/* TW: We can't switch off CRT1 on 301B-DH in 8bpp Modes if using LCD */
		if (xgi_video_info.disp_state & DISPTYPE_LCD)
			doit = 0;
	}

	/* TW: We can't switch off CRT1 if bridge is in slave mode */
	if (xgi_video_info.hasVB != HASVB_NONE) {
		inXGIIDXREG(XGIPART1, 0x00, reg);

		if ((reg & 0x50) == 0x10)
			doit = 0;

	} else {
		XGIfb_crt1off = 0;
	}

	inXGIIDXREG(XGICR, 0x17, reg);
	if ((XGIfb_crt1off) && (doit))
		reg &= ~0x80;
	else
		reg |= 0x80;
	outXGIIDXREG(XGICR, 0x17, reg);

	andXGIIDXREG(XGISR, IND_XGI_RAMDAC_CONTROL, ~0x04);

	if ((xgi_video_info.disp_state & DISPTYPE_TV) && (xgi_video_info.hasVB
			== HASVB_301)) {

		inXGIIDXREG(XGIPART4, 0x01, reg);

		if (reg < 0xB0) { /* Set filter for XGI301 */

			switch (xgi_video_info.video_width) {
			case 320:
				filter_tb = (xgi_video_info.TV_type == TVMODE_NTSC) ? 4 : 12;
				break;
			case 640:
				filter_tb = (xgi_video_info.TV_type == TVMODE_NTSC) ? 5 : 13;
				break;
			case 720:
				filter_tb = (xgi_video_info.TV_type == TVMODE_NTSC) ? 6 : 14;
				break;
			case 800:
				filter_tb = (xgi_video_info.TV_type == TVMODE_NTSC) ? 7 : 15;
				break;
			default:
				filter = -1;
				break;
			}

			orXGIIDXREG(XGIPART1, XGIfb_CRT2_write_enable, 0x01);

			if (xgi_video_info.TV_type == TVMODE_NTSC) {

				andXGIIDXREG(XGIPART2, 0x3a, 0x1f);

				if (xgi_video_info.TV_plug == TVPLUG_SVIDEO) {

					andXGIIDXREG(XGIPART2, 0x30, 0xdf);

				} else if (xgi_video_info.TV_plug
						== TVPLUG_COMPOSITE) {

					orXGIIDXREG(XGIPART2, 0x30, 0x20);

					switch (xgi_video_info.video_width) {
					case 640:
						outXGIIDXREG(XGIPART2, 0x35, 0xEB);
						outXGIIDXREG(XGIPART2, 0x36, 0x04);
						outXGIIDXREG(XGIPART2, 0x37, 0x25);
						outXGIIDXREG(XGIPART2, 0x38, 0x18);
						break;
					case 720:
						outXGIIDXREG(XGIPART2, 0x35, 0xEE);
						outXGIIDXREG(XGIPART2, 0x36, 0x0C);
						outXGIIDXREG(XGIPART2, 0x37, 0x22);
						outXGIIDXREG(XGIPART2, 0x38, 0x08);
						break;
					case 800:
						outXGIIDXREG(XGIPART2, 0x35, 0xEB);
						outXGIIDXREG(XGIPART2, 0x36, 0x15);
						outXGIIDXREG(XGIPART2, 0x37, 0x25);
						outXGIIDXREG(XGIPART2, 0x38, 0xF6);
						break;
					}
				}

			} else if (xgi_video_info.TV_type == TVMODE_PAL) {

				andXGIIDXREG(XGIPART2, 0x3A, 0x1F);

				if (xgi_video_info.TV_plug == TVPLUG_SVIDEO) {

					andXGIIDXREG(XGIPART2, 0x30, 0xDF);

				} else if (xgi_video_info.TV_plug
						== TVPLUG_COMPOSITE) {

					orXGIIDXREG(XGIPART2, 0x30, 0x20);

					switch (xgi_video_info.video_width) {
					case 640:
						outXGIIDXREG(XGIPART2, 0x35, 0xF1);
						outXGIIDXREG(XGIPART2, 0x36, 0xF7);
						outXGIIDXREG(XGIPART2, 0x37, 0x1F);
						outXGIIDXREG(XGIPART2, 0x38, 0x32);
						break;
					case 720:
						outXGIIDXREG(XGIPART2, 0x35, 0xF3);
						outXGIIDXREG(XGIPART2, 0x36, 0x00);
						outXGIIDXREG(XGIPART2, 0x37, 0x1D);
						outXGIIDXREG(XGIPART2, 0x38, 0x20);
						break;
					case 800:
						outXGIIDXREG(XGIPART2, 0x35, 0xFC);
						outXGIIDXREG(XGIPART2, 0x36, 0xFB);
						outXGIIDXREG(XGIPART2, 0x37, 0x14);
						outXGIIDXREG(XGIPART2, 0x38, 0x2A);
						break;
					}
				}
			}

			if ((filter >= 0) && (filter <= 7)) {
				DPRINTK("FilterTable[%d]-%d: %02x %02x %02x %02x\n", filter_tb, filter,
						XGI_TV_filter[filter_tb].filter[filter][0],
						XGI_TV_filter[filter_tb].filter[filter][1],
						XGI_TV_filter[filter_tb].filter[filter][2],
						XGI_TV_filter[filter_tb].filter[filter][3]
				);
				outXGIIDXREG(
						XGIPART2,
						0x35,
						(XGI_TV_filter[filter_tb].filter[filter][0]));
				outXGIIDXREG(
						XGIPART2,
						0x36,
						(XGI_TV_filter[filter_tb].filter[filter][1]));
				outXGIIDXREG(
						XGIPART2,
						0x37,
						(XGI_TV_filter[filter_tb].filter[filter][2]));
				outXGIIDXREG(
						XGIPART2,
						0x38,
						(XGI_TV_filter[filter_tb].filter[filter][3]));
			}

		}

	}

}

XGIINITSTATIC int __init XGIfb_setup(char *options)
{
	char *this_opt;

	xgi_video_info.refresh_rate = 0;

	printk(KERN_INFO "XGIfb: Options %s\n", options);

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

	while ((this_opt = strsep(&options, ",")) != NULL) {

		if (!*this_opt)
			continue;

		if (!strncmp(this_opt, "mode:", 5)) {
			XGIfb_search_mode(this_opt + 5);
		} else if (!strncmp(this_opt, "vesa:", 5)) {
			XGIfb_search_vesamode(simple_strtoul(this_opt + 5, NULL, 0));
		} else if (!strncmp(this_opt, "mode:", 5)) {
			XGIfb_search_mode(this_opt + 5);
		} else if (!strncmp(this_opt, "vesa:", 5)) {
			XGIfb_search_vesamode(simple_strtoul(this_opt + 5, NULL, 0));
		} else if (!strncmp(this_opt, "vrate:", 6)) {
			xgi_video_info.refresh_rate = simple_strtoul(this_opt + 6, NULL, 0);
		} else if (!strncmp(this_opt, "rate:", 5)) {
			xgi_video_info.refresh_rate = simple_strtoul(this_opt + 5, NULL, 0);
		} else if (!strncmp(this_opt, "off", 3)) {
			XGIfb_off = 1;
		} else if (!strncmp(this_opt, "crt1off", 7)) {
			XGIfb_crt1off = 1;
		} else if (!strncmp(this_opt, "filter:", 7)) {
			filter = (int)simple_strtoul(this_opt + 7, NULL, 0);
		} else if (!strncmp(this_opt, "forcecrt2type:", 14)) {
			XGIfb_search_crt2type(this_opt + 14);
		} else if (!strncmp(this_opt, "forcecrt1:", 10)) {
			XGIfb_forcecrt1 = (int)simple_strtoul(this_opt + 10, NULL, 0);
		} else if (!strncmp(this_opt, "tvmode:", 7)) {
			XGIfb_search_tvstd(this_opt + 7);
		} else if (!strncmp(this_opt, "tvstandard:", 11)) {
			XGIfb_search_tvstd(this_opt + 7);
		} else if (!strncmp(this_opt, "dstn", 4)) {
			enable_dstn = 1;
			/* TW: DSTN overrules forcecrt2type */
			XGIfb_crt2type = DISPTYPE_LCD;
		} else if (!strncmp(this_opt, "pdc:", 4)) {
			XGIfb_pdc = simple_strtoul(this_opt + 4, NULL, 0);
			if (XGIfb_pdc & ~0x3c) {
				printk(KERN_INFO "XGIfb: Illegal pdc parameter\n");
				XGIfb_pdc = 0;
			}
		} else if (!strncmp(this_opt, "noypan", 6)) {
			XGIfb_ypan = 0;
		} else if (!strncmp(this_opt, "userom:", 7)) {
			XGIfb_userom = (int)simple_strtoul(this_opt + 7, NULL, 0);
			/* } else if (!strncmp(this_opt, "useoem:", 7)) { */
			/* XGIfb_useoem = (int)simple_strtoul(this_opt + 7, NULL, 0); */
		} else {
			XGIfb_search_mode(this_opt);
			/* printk(KERN_INFO "XGIfb: Invalid option %s\n", this_opt); */
		}

		/* TW: Panning only with acceleration */
		XGIfb_ypan = 0;

	}
	printk("\nxgifb: outa xgifb_setup 3450");
	return 0;
}

static unsigned char *xgifb_copy_rom(struct pci_dev *dev)
{
	void __iomem *rom_address;
	unsigned char *rom_copy;
	size_t rom_size;

	rom_address = pci_map_rom(dev, &rom_size);
	if (rom_address == NULL)
		return NULL;

	rom_copy = vzalloc(XGIFB_ROM_SIZE);
	if (rom_copy == NULL)
		goto done;

	rom_size = min_t(size_t, rom_size, XGIFB_ROM_SIZE);
	memcpy_fromio(rom_copy, rom_address, rom_size);

done:
	pci_unmap_rom(dev, rom_address);
	return rom_copy;
}

static int __devinit xgifb_probe(struct pci_dev *pdev,
		const struct pci_device_id *ent)
{
	u8 reg, reg1;
	u8 CR48, CR38;
	int ret;

	if (XGIfb_off)
		return -ENXIO;

	XGIfb_registered = 0;

	memset(&XGIhw_ext, 0, sizeof(struct xgi_hw_device_info));
	fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
	if (!fb_info)
		return -ENOMEM;

	xgi_video_info.chip_id = pdev->device;
	pci_read_config_byte(pdev, PCI_REVISION_ID, &xgi_video_info.revision_id);
	XGIhw_ext.jChipRevision = xgi_video_info.revision_id;

	xgi_video_info.pcibus = pdev->bus->number;
	xgi_video_info.pcislot = PCI_SLOT(pdev->devfn);
	xgi_video_info.pcifunc = PCI_FUNC(pdev->devfn);
	xgi_video_info.subsysvendor = pdev->subsystem_vendor;
	xgi_video_info.subsysdevice = pdev->subsystem_device;

	xgi_video_info.video_base = pci_resource_start(pdev, 0);
	xgi_video_info.mmio_base = pci_resource_start(pdev, 1);
	xgi_video_info.mmio_size = pci_resource_len(pdev, 1);
	xgi_video_info.vga_base = pci_resource_start(pdev, 2) + 0x30;
	XGIhw_ext.pjIOAddress = (unsigned char *)xgi_video_info.vga_base;
	/* XGI_Pr.RelIO  = ioremap(pci_resource_start(pdev, 2), 128) + 0x30; */
	printk("XGIfb: Relocate IO address: %lx [%08lx]\n",
			(unsigned long)pci_resource_start(pdev, 2), XGI_Pr.RelIO);

	if (pci_enable_device(pdev)) {
		ret = -EIO;
		goto error;
	}

	XGIRegInit(&XGI_Pr, (unsigned long)XGIhw_ext.pjIOAddress);

	outXGIIDXREG(XGISR, IND_XGI_PASSWORD, XGI_PASSWORD);
	inXGIIDXREG(XGISR, IND_XGI_PASSWORD, reg1);

	if (reg1 != 0xa1) { /*I/O error */
		printk("\nXGIfb: I/O error!!!");
		ret = -EIO;
		goto error;
	}

	switch (xgi_video_info.chip_id) {
	case PCI_DEVICE_ID_XG_20:
		orXGIIDXREG(XGICR, Index_CR_GPIO_Reg3, GPIOG_EN);
		inXGIIDXREG(XGICR, Index_CR_GPIO_Reg1, CR48);
		if (CR48&GPIOG_READ)
			xgi_video_info.chip = XG21;
		else
			xgi_video_info.chip = XG20;
		XGIfb_CRT2_write_enable = IND_XGI_CRT2_WRITE_ENABLE_315;
		break;
	case PCI_DEVICE_ID_XG_40:
		xgi_video_info.chip = XG40;
		XGIfb_CRT2_write_enable = IND_XGI_CRT2_WRITE_ENABLE_315;
		break;
	case PCI_DEVICE_ID_XG_41:
		xgi_video_info.chip = XG41;
		XGIfb_CRT2_write_enable = IND_XGI_CRT2_WRITE_ENABLE_315;
		break;
	case PCI_DEVICE_ID_XG_42:
		xgi_video_info.chip = XG42;
		XGIfb_CRT2_write_enable = IND_XGI_CRT2_WRITE_ENABLE_315;
		break;
	case PCI_DEVICE_ID_XG_27:
		xgi_video_info.chip = XG27;
		XGIfb_CRT2_write_enable = IND_XGI_CRT2_WRITE_ENABLE_315;
		break;
	default:
		ret = -ENODEV;
		goto error;
	}

	printk("XGIfb:chipid = %x\n", xgi_video_info.chip);
	XGIhw_ext.jChipType = xgi_video_info.chip;

	if ((xgi_video_info.chip == XG21) || (XGIfb_userom)) {
		XGIhw_ext.pjVirtualRomBase = xgifb_copy_rom(pdev);
		if (XGIhw_ext.pjVirtualRomBase)
			printk(KERN_INFO "XGIfb: Video ROM found and mapped to %p\n", XGIhw_ext.pjVirtualRomBase);
		else
			printk(KERN_INFO "XGIfb: Video ROM not found\n");
	} else {
		XGIhw_ext.pjVirtualRomBase = NULL;
		printk(KERN_INFO "XGIfb: Video ROM usage disabled\n");
	}
	XGIhw_ext.pQueryVGAConfigSpace = &XGIfb_query_VGA_config_space;

	if (XGIfb_get_dram_size()) {
		printk(KERN_INFO "XGIfb: Fatal error: Unable to determine RAM size.\n");
		ret = -ENODEV;
		goto error;
	}

	if ((xgifb_mode_idx < 0) || ((XGIbios_mode[xgifb_mode_idx].mode_no) != 0xFF)) {
		/* Enable PCI_LINEAR_ADDRESSING and MMIO_ENABLE  */
		orXGIIDXREG(XGISR, IND_XGI_PCI_ADDRESS_SET, (XGI_PCI_ADDR_ENABLE | XGI_MEM_MAP_IO_ENABLE));
		/* Enable 2D accelerator engine */
		orXGIIDXREG(XGISR, IND_XGI_MODULE_ENABLE, XGI_ENABLE_2D);
	}

	XGIhw_ext.ulVideoMemorySize = xgi_video_info.video_size;

	if (!request_mem_region(xgi_video_info.video_base, xgi_video_info.video_size, "XGIfb FB")) {
		printk("unable request memory size %x", xgi_video_info.video_size);
		printk(KERN_ERR "XGIfb: Fatal error: Unable to reserve frame buffer memory\n");
		printk(KERN_ERR "XGIfb: Is there another framebuffer driver active?\n");
		ret = -ENODEV;
		goto error;
	}

	if (!request_mem_region(xgi_video_info.mmio_base,
				xgi_video_info.mmio_size,
				"XGIfb MMIO")) {
		printk(KERN_ERR "XGIfb: Fatal error: Unable to reserve MMIO region\n");
		ret = -ENODEV;
		goto error_0;
	}

	xgi_video_info.video_vbase = XGIhw_ext.pjVideoMemoryAddress =
	ioremap(xgi_video_info.video_base, xgi_video_info.video_size);
	xgi_video_info.mmio_vbase = ioremap(xgi_video_info.mmio_base,
					    xgi_video_info.mmio_size);

	printk(KERN_INFO "XGIfb: Framebuffer at 0x%lx, mapped to 0x%p, size %dk\n",
			xgi_video_info.video_base, xgi_video_info.video_vbase, xgi_video_info.video_size / 1024);

	printk(KERN_INFO "XGIfb: MMIO at 0x%lx, mapped to 0x%p, size %ldk\n",
	       xgi_video_info.mmio_base, xgi_video_info.mmio_vbase,
	       xgi_video_info.mmio_size / 1024);
	printk("XGIfb: XGIInitNew() ...");
	if (XGIInitNew(&XGIhw_ext))
		printk("OK\n");
	else
		printk("Fail\n");

	xgi_video_info.mtrr = (unsigned int) 0;

	if ((xgifb_mode_idx < 0) || ((XGIbios_mode[xgifb_mode_idx].mode_no) != 0xFF)) {
		xgi_video_info.hasVB = HASVB_NONE;
		if ((xgi_video_info.chip == XG20) || (xgi_video_info.chip == XG27)) {
			xgi_video_info.hasVB = HASVB_NONE;
		} else if (xgi_video_info.chip == XG21) {
			inXGIIDXREG(XGICR, 0x38, CR38);
			if ((CR38&0xE0) == 0xC0) {
				xgi_video_info.disp_state = DISPTYPE_LCD;
				if (!XGIfb_GetXG21LVDSData()) {
					int m;
					for (m = 0; m < sizeof(XGI21_LCDCapList)/sizeof(struct XGI21_LVDSCapStruct); m++) {
						if ((XGI21_LCDCapList[m].LVDSHDE == XGIbios_mode[xgifb_mode_idx].xres) &&
								(XGI21_LCDCapList[m].LVDSVDE == XGIbios_mode[xgifb_mode_idx].yres)) {
							xgifb_reg_set(XGI_Pr.P3d4, 0x36, m);
						}
					}
				}
			} else if ((CR38&0xE0) == 0x60) {
				xgi_video_info.hasVB = HASVB_CHRONTEL;
			} else {
				xgi_video_info.hasVB = HASVB_NONE;
			}
		} else {
			XGIfb_get_VB_type();
		}

		XGIhw_ext.ujVBChipID = VB_CHIP_UNKNOWN;

		XGIhw_ext.ulExternalChip = 0;

		switch (xgi_video_info.hasVB) {
		case HASVB_301:
			inXGIIDXREG(XGIPART4, 0x01, reg);
			if (reg >= 0xE0) {
				XGIhw_ext.ujVBChipID = VB_CHIP_302LV;
				printk(KERN_INFO "XGIfb: XGI302LV bridge detected (revision 0x%02x)\n", reg);
			} else if (reg >= 0xD0) {
				XGIhw_ext.ujVBChipID = VB_CHIP_301LV;
				printk(KERN_INFO "XGIfb: XGI301LV bridge detected (revision 0x%02x)\n", reg);
			}
			/* else if (reg >= 0xB0) {
				XGIhw_ext.ujVBChipID = VB_CHIP_301B;
				inXGIIDXREG(XGIPART4, 0x23, reg1);
				printk("XGIfb: XGI301B bridge detected\n");
			} */
			else {
				XGIhw_ext.ujVBChipID = VB_CHIP_301;
				printk("XGIfb: XGI301 bridge detected\n");
			}
			break;
		case HASVB_302:
			inXGIIDXREG(XGIPART4, 0x01, reg);
			if (reg >= 0xE0) {
				XGIhw_ext.ujVBChipID = VB_CHIP_302LV;
				printk(KERN_INFO "XGIfb: XGI302LV bridge detected (revision 0x%02x)\n", reg);
			} else if (reg >= 0xD0) {
				XGIhw_ext.ujVBChipID = VB_CHIP_301LV;
				printk(KERN_INFO "XGIfb: XGI302LV bridge detected (revision 0x%02x)\n", reg);
			} else if (reg >= 0xB0) {
				inXGIIDXREG(XGIPART4, 0x23, reg1);

				XGIhw_ext.ujVBChipID = VB_CHIP_302B;

			} else {
				XGIhw_ext.ujVBChipID = VB_CHIP_302;
				printk(KERN_INFO "XGIfb: XGI302 bridge detected\n");
			}
			break;
		case HASVB_LVDS:
			XGIhw_ext.ulExternalChip = 0x1;
			printk(KERN_INFO "XGIfb: LVDS transmitter detected\n");
			break;
		case HASVB_TRUMPION:
			XGIhw_ext.ulExternalChip = 0x2;
			printk(KERN_INFO "XGIfb: Trumpion Zurac LVDS scaler detected\n");
			break;
		case HASVB_CHRONTEL:
			XGIhw_ext.ulExternalChip = 0x4;
			printk(KERN_INFO "XGIfb: Chrontel TV encoder detected\n");
			break;
		case HASVB_LVDS_CHRONTEL:
			XGIhw_ext.ulExternalChip = 0x5;
			printk(KERN_INFO "XGIfb: LVDS transmitter and Chrontel TV encoder detected\n");
			break;
		default:
			printk(KERN_INFO "XGIfb: No or unknown bridge type detected\n");
			break;
		}

		if (xgi_video_info.hasVB != HASVB_NONE)
			XGIfb_detect_VB();

		if (xgi_video_info.disp_state & DISPTYPE_DISP2) {
			if (XGIfb_crt1off)
				xgi_video_info.disp_state |= DISPMODE_SINGLE;
			else
				xgi_video_info.disp_state |= (DISPMODE_MIRROR | DISPTYPE_CRT1);
		} else {
			xgi_video_info.disp_state = DISPMODE_SINGLE | DISPTYPE_CRT1;
		}

		if (xgi_video_info.disp_state & DISPTYPE_LCD) {
			if (!enable_dstn) {
				inXGIIDXREG(XGICR, IND_XGI_LCD_PANEL, reg);
				reg &= 0x0f;
				XGIhw_ext.ulCRT2LCDType = XGI310paneltype[reg];

			} else {
				/* TW: FSTN/DSTN */
				XGIhw_ext.ulCRT2LCDType = LCD_320x480;
			}
		}

		XGIfb_detectedpdc = 0;

		XGIfb_detectedlcda = 0xff;

		/* TW: Try to find about LCDA */

		if ((XGIhw_ext.ujVBChipID == VB_CHIP_302B) ||
				(XGIhw_ext.ujVBChipID == VB_CHIP_301LV) ||
				(XGIhw_ext.ujVBChipID == VB_CHIP_302LV)) {
			int tmp;
			inXGIIDXREG(XGICR, 0x34, tmp);
			if (tmp <= 0x13) {
				/* Currently on LCDA? (Some BIOSes leave CR38) */
				inXGIIDXREG(XGICR, 0x38, tmp);
				if ((tmp & 0x03) == 0x03) {
					/* XGI_Pr.XGI_UseLCDA = 1; */
				} else {
					/* Currently on LCDA? (Some newer BIOSes set D0 in CR35) */
					inXGIIDXREG(XGICR, 0x35, tmp);
					if (tmp & 0x01) {
						/* XGI_Pr.XGI_UseLCDA = 1; */
					} else {
						inXGIIDXREG(XGICR, 0x30, tmp);
						if (tmp & 0x20) {
							inXGIIDXREG(XGIPART1, 0x13, tmp);
							if (tmp & 0x04) {
								/* XGI_Pr.XGI_UseLCDA = 1; */
							}
						}
					}
				}
			}

		}

		if (xgifb_mode_idx >= 0)
			xgifb_mode_idx = XGIfb_validate_mode(xgifb_mode_idx);

		if (xgifb_mode_idx < 0) {
			switch (xgi_video_info.disp_state & DISPTYPE_DISP2) {
			case DISPTYPE_LCD:
				xgifb_mode_idx = DEFAULT_LCDMODE;
				if (xgi_video_info.chip == XG21)
					xgifb_mode_idx = XGIfb_GetXG21DefaultLVDSModeIdx();
				break;
			case DISPTYPE_TV:
				xgifb_mode_idx = DEFAULT_TVMODE;
				break;
			default:
				xgifb_mode_idx = DEFAULT_MODE;
				break;
			}
		}

		XGIfb_mode_no = XGIbios_mode[xgifb_mode_idx].mode_no;

		if (xgi_video_info.refresh_rate == 0)
			xgi_video_info.refresh_rate = 60; /* yilin set default refresh rate */
		if (XGIfb_search_refresh_rate(xgi_video_info.refresh_rate) == 0) {
			XGIfb_rate_idx = XGIbios_mode[xgifb_mode_idx].rate_idx;
			xgi_video_info.refresh_rate = 60;
		}

		xgi_video_info.video_bpp = XGIbios_mode[xgifb_mode_idx].bpp;
		xgi_video_info.video_vwidth = xgi_video_info.video_width = XGIbios_mode[xgifb_mode_idx].xres;
		xgi_video_info.video_vheight = xgi_video_info.video_height = XGIbios_mode[xgifb_mode_idx].yres;
		xgi_video_info.org_x = xgi_video_info.org_y = 0;
		xgi_video_info.video_linelength = xgi_video_info.video_width * (xgi_video_info.video_bpp >> 3);
		switch (xgi_video_info.video_bpp) {
		case 8:
			xgi_video_info.DstColor = 0x0000;
			xgi_video_info.XGI310_AccelDepth = 0x00000000;
			xgi_video_info.video_cmap_len = 256;
			break;
		case 16:
			xgi_video_info.DstColor = 0x8000;
			xgi_video_info.XGI310_AccelDepth = 0x00010000;
			xgi_video_info.video_cmap_len = 16;
			break;
		case 32:
			xgi_video_info.DstColor = 0xC000;
			xgi_video_info.XGI310_AccelDepth = 0x00020000;
			xgi_video_info.video_cmap_len = 16;
			break;
		default:
			xgi_video_info.video_cmap_len = 16;
			printk(KERN_INFO "XGIfb: Unsupported depth %d", xgi_video_info.video_bpp);
			break;
		}

		printk(KERN_INFO "XGIfb: Default mode is %dx%dx%d (%dHz)\n",
				xgi_video_info.video_width, xgi_video_info.video_height, xgi_video_info.video_bpp,
				xgi_video_info.refresh_rate);

		default_var.xres = default_var.xres_virtual = xgi_video_info.video_width;
		default_var.yres = default_var.yres_virtual = xgi_video_info.video_height;
		default_var.bits_per_pixel = xgi_video_info.video_bpp;

		XGIfb_bpp_to_var(&default_var);

		default_var.pixclock = (u32) (1000000000 /
				XGIfb_mode_rate_to_dclock(&XGI_Pr, &XGIhw_ext,
						XGIfb_mode_no, XGIfb_rate_idx));

		if (XGIfb_mode_rate_to_ddata(&XGI_Pr, &XGIhw_ext,
			XGIfb_mode_no, XGIfb_rate_idx,
			&default_var.left_margin, &default_var.right_margin,
			&default_var.upper_margin, &default_var.lower_margin,
			&default_var.hsync_len, &default_var.vsync_len,
			&default_var.sync, &default_var.vmode)) {

			if ((default_var.vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
				default_var.yres <<= 1;
				default_var.yres_virtual <<= 1;
			} else if ((default_var.vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE) {
				default_var.pixclock >>= 1;
				default_var.yres >>= 1;
				default_var.yres_virtual >>= 1;
			}

		}

		fb_info->flags = FBINFO_FLAG_DEFAULT;
		fb_info->var = default_var;
		fb_info->fix = XGIfb_fix;
		fb_info->par = &xgi_video_info;
		fb_info->screen_base = xgi_video_info.video_vbase;
		fb_info->fbops = &XGIfb_ops;
		XGIfb_get_fix(&fb_info->fix, -1, fb_info);
		fb_info->pseudo_palette = pseudo_palette;

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

#ifdef CONFIG_MTRR
		xgi_video_info.mtrr = mtrr_add((unsigned int) xgi_video_info.video_base,
				(unsigned int) xgi_video_info.video_size,
				MTRR_TYPE_WRCOMB, 1);
		if (xgi_video_info.mtrr)
			printk(KERN_INFO "XGIfb: Added MTRRs\n");
#endif

		if (register_framebuffer(fb_info) < 0) {
			ret = -EINVAL;
			goto error_1;
		}

		XGIfb_registered = 1;

		printk(KERN_INFO "fb%d: %s frame buffer device, Version %d.%d.%02d\n",
				fb_info->node, myid, VER_MAJOR, VER_MINOR, VER_LEVEL);

	}

	dumpVGAReg();

	return 0;

error_1:
	iounmap(xgi_video_info.mmio_vbase);
	iounmap(xgi_video_info.video_vbase);
	release_mem_region(xgi_video_info.mmio_base, xgi_video_info.mmio_size);
error_0:
	release_mem_region(xgi_video_info.video_base,
			   xgi_video_info.video_size);
error:
	vfree(XGIhw_ext.pjVirtualRomBase);
	framebuffer_release(fb_info);
	return ret;
}

/*****************************************************/
/*                PCI DEVICE HANDLING                */
/*****************************************************/

static void __devexit xgifb_remove(struct pci_dev *pdev)
{
	unregister_framebuffer(fb_info);
	iounmap(xgi_video_info.mmio_vbase);
	iounmap(xgi_video_info.video_vbase);
	release_mem_region(xgi_video_info.mmio_base, xgi_video_info.mmio_size);
	release_mem_region(xgi_video_info.video_base,
			   xgi_video_info.video_size);
	vfree(XGIhw_ext.pjVirtualRomBase);
	framebuffer_release(fb_info);
	pci_set_drvdata(pdev, NULL);
}

static struct pci_driver xgifb_driver = {
	.name = "xgifb",
	.id_table = xgifb_pci_table,
	.probe = xgifb_probe,
	.remove = __devexit_p(xgifb_remove)
};

XGIINITSTATIC int __init xgifb_init(void)
{
	char *option = NULL;

	if (fb_get_options("xgifb", &option))
		return -ENODEV;
	XGIfb_setup(option);

	return pci_register_driver(&xgifb_driver);
}

#ifndef MODULE
module_init(xgifb_init);
#endif

/*****************************************************/
/*                      MODULE                       */
/*****************************************************/

#ifdef MODULE

static char *mode = NULL;
static int vesa = 0;
static unsigned int rate = 0;
static unsigned int mem = 0;
static char *forcecrt2type = NULL;
static int forcecrt1 = -1;
static int pdc = -1;
static int pdc1 = -1;
static int noypan = -1;
static int userom = -1;
static int useoem = -1;
static char *tvstandard = NULL;
static int nocrt2rate = 0;
static int scalelcd = -1;
static char *specialtiming = NULL;
static int lvdshl = -1;
static int tvxposoffset = 0, tvyposoffset = 0;
#if !defined(__i386__) && !defined(__x86_64__)
static int resetcard = 0;
static int videoram = 0;
#endif

MODULE_DESCRIPTION("Z7 Z9 Z9S Z11 framebuffer device driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XGITECH , Others");

module_param(mem, int, 0);
module_param(noypan, int, 0);
module_param(userom, int, 0);
module_param(useoem, int, 0);
module_param(mode, charp, 0);
module_param(vesa, int, 0);
module_param(rate, int, 0);
module_param(forcecrt1, int, 0);
module_param(forcecrt2type, charp, 0);
module_param(scalelcd, int, 0);
module_param(pdc, int, 0);
module_param(pdc1, int, 0);
module_param(specialtiming, charp, 0);
module_param(lvdshl, int, 0);
module_param(tvstandard, charp, 0);
module_param(tvxposoffset, int, 0);
module_param(tvyposoffset, int, 0);
module_param(filter, int, 0);
module_param(nocrt2rate, int, 0);
#if !defined(__i386__) && !defined(__x86_64__)
module_param(resetcard, int, 0);
module_param(videoram, int, 0);
#endif

MODULE_PARM_DESC(noypan,
		"\nIf set to anything other than 0, y-panning will be disabled and scrolling\n"
		"will be performed by redrawing the screen. (default: 0)\n");

MODULE_PARM_DESC(mode,
		"\nSelects the desired default display mode in the format XxYxDepth,\n"
		"eg. 1024x768x16. Other formats supported include XxY-Depth and\n"
		"XxY-Depth@Rate. If the parameter is only one (decimal or hexadecimal)\n"
		"number, it will be interpreted as a VESA mode number. (default: 800x600x8)\n");

MODULE_PARM_DESC(vesa,
		"\nSelects the desired default display mode by VESA defined mode number, eg.\n"
		"0x117 (default: 0x0103)\n");

MODULE_PARM_DESC(rate,
		"\nSelects the desired vertical refresh rate for CRT1 (external VGA) in Hz.\n"
		"If the mode is specified in the format XxY-Depth@Rate, this parameter\n"
		"will be ignored (default: 60)\n");

MODULE_PARM_DESC(forcecrt1,
		"\nNormally, the driver autodetects whether or not CRT1 (external VGA) is\n"
		"connected. With this option, the detection can be overridden (1=CRT1 ON,\n"
		"0=CRT1 OFF) (default: [autodetected])\n");

MODULE_PARM_DESC(forcecrt2type,
		"\nIf this option is omitted, the driver autodetects CRT2 output devices, such as\n"
		"LCD, TV or secondary VGA. With this option, this autodetection can be\n"
		"overridden. Possible parameters are LCD, TV, VGA or NONE. NONE disables CRT2.\n"
		"On systems with a SiS video bridge, parameters SVIDEO, COMPOSITE or SCART can\n"
		"be used instead of TV to override the TV detection. Furthermore, on systems\n"
		"with a SiS video bridge, SVIDEO+COMPOSITE, HIVISION, YPBPR480I, YPBPR480P,\n"
		"YPBPR720P and YPBPR1080I are understood. However, whether or not these work\n"
		"depends on the very hardware in use. (default: [autodetected])\n");

MODULE_PARM_DESC(scalelcd,
		"\nSetting this to 1 will force the driver to scale the LCD image to the panel's\n"
		"native resolution. Setting it to 0 will disable scaling; LVDS panels will\n"
		"show black bars around the image, TMDS panels will probably do the scaling\n"
		"themselves. Default: 1 on LVDS panels, 0 on TMDS panels\n");

MODULE_PARM_DESC(pdc,
		"\nThis is for manually selecting the LCD panel delay compensation. The driver\n"
		"should detect this correctly in most cases; however, sometimes this is not\n"
		"possible. If you see 'small waves' on the LCD, try setting this to 4, 32 or 24\n"
		"on a 300 series chipset; 6 on a 315 series chipset. If the problem persists,\n"
		"try other values (on 300 series: between 4 and 60 in steps of 4; on 315 series:\n"
		"any value from 0 to 31). (default: autodetected, if LCD is active during start)\n");

MODULE_PARM_DESC(pdc1,
		"\nThis is same as pdc, but for LCD-via CRT1. Hence, this is for the 315/330\n"
		"series only. (default: autodetected if LCD is in LCD-via-CRT1 mode during\n"
		"startup) - Note: currently, this has no effect because LCD-via-CRT1 is not\n"
		"implemented yet.\n");

MODULE_PARM_DESC(specialtiming,
		"\nPlease refer to documentation for more information on this option.\n");

MODULE_PARM_DESC(lvdshl,
		"\nPlease refer to documentation for more information on this option.\n");

MODULE_PARM_DESC(tvstandard,
		"\nThis allows overriding the BIOS default for the TV standard. Valid choices are\n"
		"pal, ntsc, palm and paln. (default: [auto; pal or ntsc only])\n");

MODULE_PARM_DESC(tvxposoffset,
		"\nRelocate TV output horizontally. Possible parameters: -32 through 32.\n"
		"Default: 0\n");

MODULE_PARM_DESC(tvyposoffset,
		"\nRelocate TV output vertically. Possible parameters: -32 through 32.\n"
		"Default: 0\n");

MODULE_PARM_DESC(filter,
		"\nSelects TV flicker filter type (only for systems with a SiS301 video bridge).\n"
		"(Possible values 0-7, default: [no filter])\n");

MODULE_PARM_DESC(nocrt2rate,
		"\nSetting this to 1 will force the driver to use the default refresh rate for\n"
		"CRT2 if CRT2 type is VGA. (default: 0, use same rate as CRT1)\n");

static int __init xgifb_init_module(void)
{
	printk("\nXGIfb_init_module");
	if (mode)
		XGIfb_search_mode(mode);
	else if (vesa != -1)
		XGIfb_search_vesamode(vesa);

	return xgifb_init();
}

static void __exit xgifb_remove_module(void)
{
	pci_unregister_driver(&xgifb_driver);
	printk(KERN_DEBUG "xgifb: Module unloaded\n");
}

module_init(xgifb_init_module);
module_exit(xgifb_remove_module);

#endif	/*  /MODULE  */
