/* VGAlib version 1.2 - (c) 1993 Tommy Frandsen                    */
/*                                                                 */
/* This library is free software; you can redistribute it and/or   */
/* modify it without any restrictions. This library is distributed */
/* in the hope that it will be useful, but without any warranty.   */

/* Multi-chipset support Copyright (c) 1993 Harm Hanemaayer */
/* partially copyrighted (C) 1993 by Hartmut Schirmer */

/* ET4000 code taken from VGAlib
 * ET4000 code modified to handle HiColor modes better by David Monro
 * Dynamic register loading by Hartmut Schirmer
 * HH: ET4000/W32 detection added and support for more than 1Mb (based on
 *     vgadoc3) (untested).
 * HH: Detect newer ET4000/W32p.
 */


/* Note that the clock detection stuff is currently not used. */

/* ET4000 registers description (from vgadoc2)
   **
   **
   **
   ** 102h: Microchannel Setup Control
   ** bit 0  Disable Card if set
   **
   ** 3BFh (R/W): Hercules Compatability register
   ** 
   ** 3C0h index 16h: ATC Miscellaneous
   **    (Write data to 3C0h, Read from 3C1h  (May not be needed))
   ** bit 4,5  High resolution timings.
   **       7  Bypass the internal palette if set
   **
   ** 3C3h (R/W): Microchannel Video Subsystem Enable Register:
   ** bit 0  Enable Microchannel VGA if set
   **
   ** 3C4h index  6  (R/W): TS State Control
   ** bit 1-2  dots per characters in text mode 
   **       (bit 0: 3c4 index 1, bit 0)
   **       bit <2:0>   ! dots/char
   **           111     !    16
   **           100     !    12
   **           011     !    11
   **           010     !    10
   **           001     !     8
   **           000     !     9
   **
   ** 3C4h index  7  (R/W): TS Auxiliary Mode
   ** bit 0  If set select MCLK/4 (if bit 6 is also set to 1)
   **     1  If set select SCLK input from MCLK
   **   3,5  Rom Bios Enable/Disable:
   **     0 0  C000-C3FF Enabled
   **     0 1  Rom disabled
   **     1 0  C000-C5FF,C680-C7FF Enabled
   **     1 1  C000-C7FF Enabled
   **     6  MCLK/2 if set
   **     7  VGA compatible if set EGA else.
   **
   ** 3CBh (R/W): PEL Address/Data Wd
   **  
   ** 3CDh (R/W): Segment Select
   **     0-3  64k Write bank nr (0..15)
   **     4-7  64k Read bank nr (0..15)
   **
   ** 3CEh index  Dh (R/W): Microsequencer Mode
   **
   ** 3CEh index  Eh (R/W): Microsequencer Reset 
   **
   ** 3d4h index 24h (R/W): Compatibility Control
   ** bit 0  Enable Clock Translate
   **     1  Additional Master Clock Select
   **     2  Enable tri-state for all output pins
   **     3  Enable input A8 of 1MB DRAMs
   **     4  Reserved
   **     5  Enable external ROM CRTC translation
   **     6  Enable Double Scan and Underline Attribute
   **     7  CGA/MDA/Hercules
   **
   ** 3d4h index 32h (R/W): RAS/CAS Video Config
   **       Ram timing, System clock and Ram type. Sample values:
   **      00h  VRAM  80nsec
   **      09h  VRAM 100nsec
   **      00h  VRAM  28MHz
   **      08h  VRAM  36MHz
   **      70h  DRAM  40MHz
   **
   ** 3d4h index 33h (R/W): Extended start ET4000 
   ** bit 0-1  Display start address bits 16-17
   **     2-3  Cursor start address bits 16-17
   **     Can be used to ID ET4000
   **
   ** 3d4h index 34h (R/W): Compatibility Control Register
   ** bit 2  bit 3 of clock select (bit 1-0 in misc output)
   **     3  if set Video Subsystem Enable Register at 46E8h
   **             else at 3C3h.
   **
   ** 3d4h index 35h (R/W): Overflow High ET4000
   ** bit 0  Vertical Blank Start Bit 10
   **     1  Vertical Total Bit 10
   **     2  Vertical Display End Bit 10
   **     3  Vertical Sync Start Bit 10
   **     4  Line Compare Bit 10
   **     5  Gen-Lock Enabled if set (External sync)
   **     6  Read/Modify/Write Enabled if set. Currently not implemented.
   **     7  Vertical interlace if set
   **
   ** 3d4h index 36h (R/W): Video System Configuration 1
   ** bit 0-2 Refresh count per line - 1
   **     3   16 bit wide fonts if set, else 8 bit wide
   **     4   Linear addressing if set. Video Memory is 
   **         mapped as a 1 Meg block above 1MB. (set
   **      GDC index 6 bits 3,2 to zero (128k))
   **     5   TLI addressing mode if set
   **     6   16 bit data path (video memory) if set
   **     7   16 bit data (I/O operations) if set
   **
   ** 3d4h index 37h (R/W): Video System Configuration 2
   ** bit 0-1  Display memory data bus width
   **          0,1=8bit, 2=16bit, 3=32bit; may be
   **       read as number of memory banks (1,2,4)
   **    2  Bus read data latch control. If set latches
   **       databus at end of CAS cycle else one clock delay
   **       3  Clear if 64kx4 RAMs                   ???
   **     if set RAM size = (bit 0-1)*256k
   **       else RAM size = (bit 0-1)* 64k
   **       4  16 bit ROM access if set
   **       5  Memory bandwidth (0 better than 1) ???
   **       6  TLI internal test mode if set 
   **       7  VRAM installed if set DRAM else.
   **
   ** 3d4h index 3Fh (R/W):
   ** bit   7  This bit seems to be bit 8 of the CRTC offset register (3d4h index 13h).
   **
   ** 3d8h (R/W): Display Mode Control
   **
   ** 46E8h (R):  Video Subsystem Enable Register
   ** bit   3  Enable VGA if set
   **
   **
   **       3C4h index 05 used.
   ** 
   **
   **  Bank Switching:
   **
   **     64k banks are selected by the Segment Select Register at 3CDh.
   **     Both a Read and a Write segment can be selected.
   **
   **  The sequence: 
   **
   **      port[$3BF]:=3;
   **      port[$3D8]:=$A0;
   **
   **  is apparently needed to enable the extensions in the Tseng 4000.
   **
   **
   **  Used extended ET4000 registers (EXT+xx) :
   **
   **   00 : CRT (3d4) index 30
   **   01 : CRT (3d4) index 31
   **   02 : CRT (3d4) index 32
   **   03 : CRT (3d4) index 33
   **   04 : CRT (3d4) index 34
   **   05 : CRT (3d4) index 35
   **   06 : CRT (3d4) index 36
   **   07 : CRT (3d4) index 37
   **   08 : CRT (3d4) index 3f
   **   09 : SEQ (3c4) index 07
   **   0A : Microchannel register (3c3)
   **   0B : Segment select (3cd)
   **   0C : ATT (3c0) index 16
   **
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include "vga.h"
#include "libvga.h"
#include "driver.h"
#include "ramdac/ramdac.h"

#define SEG_SELECT 0x3CD

#define CHIP_ET4000	0	/* Chip types. */
#define CHIP_ET4000W32	1
#define CHIP_ET4000W32i	2
#define CHIP_ET4000W32p	3

static char *chipname[] =
{
    "ET4000",
    "ET4000/W32",
    "ET4000/W32i",
    "ET4000/W32p"
};

static int et4000_memory;
static int et4000_chiptype;

static int et4000_init(int, int, int);
static int et4000_interlaced(int mode);
static void et4000_unlock(void);

static void et4000_setlinear(int addr);

static int et4000_extdac;
static int pos_ext_settings, ext_settings;

/* Mode table */
#if defined(DYNAMIC)

static ModeTable *et4000_modes = NULL;
static ModeTable No_Modes = END_OF_MODE_TABLE;

#else				/* !defined(DYNAMIC) */

#include "config/et4000.regs"

#ifdef DAC_TYPE
static int et4000_dac = DAC_TYPE;
#endif

static ModeTable et4000_modes[] =
{
/* *INDENT-OFF* */
    OneModeEntry(320x200x32K),
    OneModeEntry(320x200x64K),
    OneModeEntry(320x200x16M),
    OneModeEntry(640x480x256),
    OneModeEntry(640x480x32K),
    OneModeEntry(640x480x64K),
    OneModeEntry(640x480x16M),
    OneModeEntry(800x600x16),
    OneModeEntry(800x600x256),
    OneModeEntry(800x600x32K),
    OneModeEntry(800x600x64K),
    OneModeEntry(1024x768x16),
    OneModeEntry(1024x768x256),
    OneModeEntry(1280x1024x16),
    END_OF_MODE_TABLE
/* *INDENT-ON* */
};

#endif				/* !defined(DYNAMIC) */

#ifndef DAC_TYPE
static int et4000_dac = -1;
#endif

/* Fill in chipset specific mode information */

static void et4000_getmodeinfo(int mode, vga_modeinfo * modeinfo)
{
    switch (modeinfo->colors) {
    case 16:			/* 4-plane 16 color mode */
	modeinfo->maxpixels = 65536 * 8;
	break;
    default:
	if (modeinfo->bytesperpixel > 0)
	    modeinfo->maxpixels = et4000_memory * 1024 /
		modeinfo->bytesperpixel;
	else
	    modeinfo->maxpixels = et4000_memory * 1024;
	break;
    }
    modeinfo->maxlogicalwidth = 4088;
    modeinfo->startaddressrange = 0xfffff;
    if (mode == G320x200x256)
	modeinfo->startaddressrange = 0;
    modeinfo->haveblit = 0;
    modeinfo->memory = et4000_memory * 1024;
    modeinfo->flags |= HAVE_RWPAGE | HAVE_EXT_SET;
    if (et4000_interlaced(mode))
	modeinfo->flags |= IS_INTERLACED;
    if (et4000_chiptype != CHIP_ET4000)
	modeinfo->flags |= EXT_INFO_AVAILABLE | CAPABLE_LINEAR;
}


/* ----------------------------------------------------------------- */
/* Set/get the actual clock frequency */

#if defined(USE_CLOCKS) || defined(DYNAMIC)
#ifndef CLOCKS
#define CLOCKS 24
#endif
#ifndef CLOCK_VALUES
#define CLOCK_VALUES { 0 }
#endif

static unsigned clocks[CLOCKS] = CLOCK_VALUES;
#endif


#ifdef USE_CLOCKS

/* Only include the rest of the clock stuff if USE_CLOCKS is defined. */

static int et4000_clocks(int clk)
{
    unsigned char temp;
    int res;

    /* get actual clock */
    res = (port_in(MIS_R) >> 2) & 3;	/* bit 0..1 */
    port_out_r(__svgalib_CRT_I, 0x34);
    res |= (port_in(__svgalib_CRT_D) & 2) << 1;	/* bit 2 */
    port_out_r(SEQ_I, 0x07);
    temp = port_in(SEQ_D);
    if (temp & 0x41 == 0x41)
	res |= 0x10;		/* bit 3..4 */
    else
	res |= (temp & 0x40) >> 3;	/* bit 3 */

    if (clk >= CLOCKS)
	clk = CLOCKS - 1;
    if (clk >= 0) {
	/* Set clock */
	temp = port_in(MIS_R) & 0xF3;
	port_out_r(MIS_W, temp | ((clk & 3) << 2));
	port_out_r(__svgalib_CRT_I, 0x34);
	temp = port_in(__svgalib_CRT_D) & 0xFD;
	port_out_r(__svgalib_CRT_D, temp | ((clk & 4) >> 1));
	port_out_r(SEQ_I, 0x07);
	temp = port_in(SEQ_D) & 0xBE;
	temp |= (clk & 0x10 ? 0x41 : 0x00);
	temp |= (clk & 0x08) << 3;
	port_out_r(SEQ_D, temp);
	usleep(5000);
    }
    return res;
}

#define FRAMES 2

/* I think the Xfree86 uses a similar BAD HACK ... */
static int measure(int frames)
{
    unsigned counter;
    __asm__ volatile (
			 "	xorl	%0,%0		\n"
			 "__W1:	port_in	%1,%%al		\n"
			 "	testb	$8,%%al		\n"
			 "	jne	__W1		\n"
			 "__W2:	port_in	%1,%%al		\n"
			 "	testb	$8,%%al		\n"
			 "	je	__W2		\n"
			 "__L1:	port_in	%1,%%al		\n"
			 "	incl	%0		\n"
			 "	testb	$8,%%al		\n"
			 "	jne	__L1		\n"
			 "__L2:	port_in	%1,%%al		\n"
			 "	incl	%0		\n"
			 "	testb	$8,%%al		\n"
			 "	je	__L2		\n"
			 "__L3:	decl	%2		\n"
			 "	jns	__L1		\n"
			 :"=b" (counter)
			 :"d"((unsigned short) (0x3da)), "c"(frames)
			 :"ax", "cx"
    );
    return counter;
}

#define BASIS_FREQUENCY 28322

static int measure_clk(int clk)
{
    unsigned new;
    int old_clk, state;
    static unsigned act = 0;

    old_clk = et4000_clocks(-1);
    if ((state = SCREENON))
	vga_screenoff();
    if (act == 0) {
	unsigned char save = port_in(MIS_R);
	port_out_r(MIS_W, (save & 0xF3) | 0x04);
	act = measure(FRAMES);
	port_out_r(MIS_W, save);
    }
    et4000_clocks(clk);
    new = measure(FRAMES);
    et4000_clocks(old_clk);
    if (state)
	vga_screenon();

    return (((long long) BASIS_FREQUENCY) * act) / new;
}

#define set1(f,v) ({ if ((f)==0) (f)=(v); })

static void set3(unsigned *f, int clk, unsigned base)
{
    if (clk - 16 >= 0)
	set1(f[clk - 16], 4 * base);
    if (clk - 8 >= 0)
	set1(f[clk - 8], 2 * base);
    set1(f[clk], base);
    if (clk + 8 < CLOCKS)
	set1(f[clk + 8], base / 2);
    if (clk + 16 < CLOCKS)
	set1(f[clk + 16], base / 4);
}

static int get_clock(int clk)
{
    static int first = 1;

    if (clk < 0 || clk >= CLOCKS)
	return 0;
    if (first) {
	int act_clk;
	first = 0;
	act_clk = et4000_clocks(-1);
	act_clk &= 0xFC;
	set3(clocks, act_clk, 25175);	/* act_clk  : 25175 KHz */
	set3(clocks, act_clk + 1, 28322);	/* act_clk+1: 28322 KHz */
	for (act_clk = 0; act_clk < CLOCKS; ++act_clk)
	    if (clocks[act_clk] != 0)
		set3(clocks, act_clk, clocks[act_clk]);
    }
    if (clocks[clk] == 0) {
	int c = clk & 7;
	clocks[16 + c] = measure_clk(16 + c);
	clocks[8 + c] = 2 * clocks[16 + c];
	clocks[c] = 2 * clocks[8 + c];
    }
    return clocks[clk];
}

#endif				/* defined(USE_CLOCKS) */

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


/* Read and store chipset-specific registers */

static int et4000_saveregs(unsigned char regs[])
{
    int i;

    et4000_unlock();
    /* save extended CRT registers */
    for (i = 0; i < 8; i++) {
	port_out(0x30 + i, __svgalib_CRT_I);
	regs[EXT + i] = port_in(__svgalib_CRT_D);
    }
    port_out(0x3f, __svgalib_CRT_I);
    regs[EXT + 8] = port_in(__svgalib_CRT_D);

    /* save extended sequencer register */
    port_out(7, SEQ_I);
    regs[EXT + 9] = port_in(SEQ_D);

    /* save some other ET4000 specific registers */
    regs[EXT + 10] = port_in(0x3c3);
    regs[EXT + 11] = port_in(0x3cd);

    /* save extended attribute register */
    port_in(__svgalib_IS1_R);		/* reset flip flop */
    port_out(0x16, ATT_IW);
    regs[EXT + 12] = port_in(ATT_R);

    if (et4000_extdac) {
	_ramdac_dactocomm();
	regs[EXT + 13] = port_in(PEL_MSK);
	_ramdac_dactocomm();
	port_out_r(PEL_MSK, regs[EXT + 13] | 0x10);
	_ramdac_dactocomm();
	port_in(PEL_MSK);
	port_out_r(PEL_MSK, 3);		/* write index low */
	port_out_r(PEL_MSK, 0);		/* write index high */
	regs[EXT + 14] = port_in(PEL_MSK);	/* primary ext. pixel select */
	regs[EXT + 15] = port_in(PEL_MSK);	/* secondary ext. pixel select */
	regs[EXT + 16] = port_in(PEL_MSK);	/* PLL control register */
	_ramdac_dactocomm();
	port_out_r(PEL_MSK, regs[EXT + 13]);
	return 17;
    }
    return 13;			/* ET4000 requires 13 additional registers */
}


/* Set chipset-specific registers */

static void et4000_setregs(const unsigned char regs[], int mode)
{
    int i;
    unsigned char save;

    /* make sure linear mode is forced off */
    et4000_setlinear(0);

    et4000_unlock();
    /* write some ET4000 specific registers */
    port_out(regs[EXT + 10], 0x3c3);
    port_out(regs[EXT + 11], 0x3cd);

    /* write extended sequencer register */
    port_out(7, SEQ_I);
    port_out(regs[EXT + 9], SEQ_D);

#if 0				/* This writes to registers we shouldn't write to. */
    /* write extended CRT registers */
    for (i = 0; i < 6; i++) {
	port_out(0x32 + i, __svgalib_CRT_I);
	port_out(regs[EXT + i], __svgalib_CRT_D);
    }
    port_out(0x3f, __svgalib_CRT_I);
    port_out(regs[EXT + 6], __svgalib_CRT_D);
#endif

    /* deprotect CRT register 0x35 */
    port_out(0x11, __svgalib_CRT_I);
    save = port_in(__svgalib_CRT_D);
    port_out(save & 0x7F, __svgalib_CRT_D);

    /* write extended CRT registers */
    for (i = 0; i < 6; i++) {
	port_out(0x30 + i, __svgalib_CRT_I);
	port_out(regs[EXT + i], __svgalib_CRT_D);
    }
    port_out(0x36, __svgalib_CRT_I);
    port_out((port_in(__svgalib_CRT_D) & 0x40) | (regs[EXT + 6] & 0xbf), __svgalib_CRT_D);
    port_out(0x37, __svgalib_CRT_I);
    port_out((port_in(__svgalib_CRT_D) & 0xbb) | (regs[EXT + 7] & 0x44), __svgalib_CRT_D);
    port_out(0x3f, __svgalib_CRT_I);
    port_out(regs[EXT + 8], __svgalib_CRT_D);

    /* set original CRTC 0x11 */
    port_out(0x11, __svgalib_CRT_I);
    port_out(save, __svgalib_CRT_D);

    /* write extended attribute register */
    port_in(__svgalib_IS1_R);		/* reset flip flop */
    port_out(0x16, ATT_IW);
    port_out(regs[EXT + 12], ATT_IW);

    if (et4000_extdac) {
	_ramdac_dactocomm();
	port_out_r(PEL_MSK, regs[EXT + 13] | 0x10);
	_ramdac_dactocomm();
	port_in(PEL_MSK);
	port_out_r(PEL_MSK, 3);		/* write index low */
	port_out_r(PEL_MSK, 0);		/* write index high */
	port_out_r(PEL_MSK, regs[EXT + 14]);	/* primary ext. pixel select */
	port_out_r(PEL_MSK, regs[EXT + 15]);	/* secondary ext. pixel select */
	port_out_r(PEL_MSK, regs[EXT + 16]);	/* PLL control register */
	_ramdac_dactocomm();
	port_out_r(PEL_MSK, regs[EXT + 13]);
    }
}


/* Return non-zero if mode is available */

static int et4000_modeavailable(int mode)
{
    const unsigned char *regs;
    struct vgainfo *info;

    regs = LOOKUPMODE(et4000_modes, mode);
    if (regs == NULL || mode == GPLANE16)
	return __svgalib_vga_driverspecs.modeavailable(mode);
    if (regs == DISABLE_MODE || mode <= TEXT || mode > GLASTMODE)
	return 0;

    info = &__svgalib_infotable[mode];
    if (et4000_memory * 1024 < info->ydim * info->xbytes)
	return 0;

    switch (info->colors) {
    case 1 << 15:
	/* All HiColor dacs handle 15 bit */
	if ((et4000_dac & 1) == 0)
	    return 0;
	break;
    case 1 << 16:
	/* Unfortunately, we can't tell the difference between a Sierra Mark 2 */
	/* and a Mark 3, and the mark 2 is only 15 bit. If this gives you trouble */
	/* change it to (8|16) rather than (2|8|16) */
	/* Currently allow Sierra Mark2/3, AT&T, STG-170x and AcuMos */
	if ((et4000_dac & (2 | 8 | 16 | 32 | 64)) == 0)
	    return 0;
	break;
    case 1 << 24:
	/* Don't know how to set dac command register for Diamond SS2410 Dac */
	/* Only allow AT&T, STG-170x and AcuMos */
	if ((et4000_dac & (8 | 16 | 32 | 64)) == 0)
	    return 0;
	break;
    }
    return SVGADRV;
}


/* Check if mode is interlaced */

static int et4000_interlaced(int mode)
{
    const unsigned char *regs;

    if (et4000_modeavailable(mode) != SVGADRV)
	return 0;
    regs = LOOKUPMODE(et4000_modes, mode);
    if (regs == NULL || regs == DISABLE_MODE)
	return 0;
    return (regs[EXT + 5] & 0x80) != 0;		/* CRTC 35H */
}


/* Set a mode */

static int et4000_setmode(int mode, int prv_mode)
{
    const unsigned char *regs;
    unsigned char i;

    if (et4000_dac)
	/* Standard dac behaviour */
	__svgalib_hicolor(et4000_dac, STD_DAC);
    switch (et4000_modeavailable(mode)) {
    case STDVGADRV:
	/* Reset extended register that is set to non-VGA */
	/* compatible value for 132-column textmodes (at */
	/* least on some cards). */
	et4000_unlock();
	port_out_r(__svgalib_CRT_I, 0x34);
	i = port_in(__svgalib_CRT_D);
	if ((i & 0x02) == 0x02)
	    port_out_r(__svgalib_CRT_D, (i & 0xfd)); /* DS */
	return __svgalib_vga_driverspecs.setmode(mode, prv_mode);
    case SVGADRV:
	regs = LOOKUPMODE(et4000_modes, mode);
	if (regs != NULL)
	    break;
    default:
	return 1;		/* mode not available */
    }

    if (et4000_dac && !et4000_extdac)
	switch (vga_getmodeinfo(mode)->colors) {
	case 1 << 15:
	    __svgalib_hicolor(et4000_dac, HI15_DAC);
	    break;
	case 1 << 16:
	    __svgalib_hicolor(et4000_dac, HI16_DAC);
	    break;
	case 1 << 24:
	    __svgalib_hicolor(et4000_dac, TC24_DAC);
	    break;
	}
    __svgalib_setregs(regs);
    et4000_setregs(regs, mode);
    return 0;
}

/* Unlock chipset-specific registers */

static void et4000_unlock(void)
{
    /* get access to extended registers */
    port_out(3, 0x3bf);
    if (port_in(0x3cc) & 1)
	port_out(0xa0, 0x3d8);
    else
	port_out(0xa0, 0x3b8);
}


/* Relock chipset-specific registers */

static void et4000_lock(void)
{
}

/* Enable linear mode at a particular 4M page (0 to turn off) */

static void et4000_setlinear(int addr)
{
    et4000_unlock();
    port_out_r(CRT_IC, 0x36);
    if (addr)
	port_out_r(CRT_DC, port_in(CRT_DC) | 16);	/* enable linear mode */
    else
	port_out_r(CRT_DC, port_in(CRT_DC) & ~16);	/* disable linear mode */
    port_out_r(CRT_IC, 0x30);
    port_out_r(CRT_DC, addr);
    et4000_lock();
}


/* Indentify chipset; return non-zero if detected */

static int et4000_test(void)
{
    unsigned char new, old, val;
    int base;

    et4000_unlock();

    /* test for Tseng clues */
    old = port_in(0x3cd);
    port_out(0x55, 0x3cd);
    new = port_in(0x3cd);
    port_out(old, 0x3cd);

    /* return false if not Tseng */
    if (new != 0x55)
	return 0;

    /* test for ET4000 clues */
    if (port_in(0x3cc) & 1)
	base = CRT_IC;
    else
	base = 0x3b4;
    port_out(0x33, base);
    old = port_in(base + 1);
    new = old ^ 0xf;
    port_out(new, base + 1);
    val = port_in(base + 1);
    port_out(old, base + 1);

    /* return true if ET4000 */
    if (val == new) {
	et4000_init(0, 0, 0);
	return 1;
    }
    return 0;
}



static unsigned char last_page = 0;

/* Bank switching function - set 64K bank number */
static void et4000_setpage(int page)
{
    /* Set both read and write bank. */
    port_out(last_page = (page | ((page & 15) << 4)), SEG_SELECT);
    if (et4000_chiptype >= CHIP_ET4000W32i) {
	/* Write page4-5 to bits 0-1 of ext. bank register, */
	/* and to bits 4-5. */
	port_out_r(0x3cb, (port_in(0x3cb) & ~0x33) | (page >> 4) | (page & 0x30));
    }
}


/* Bank switching function - set 64K read bank number */
static void et4000_setrdpage(int page)
{
    last_page &= 0x0F;
    last_page |= (page << 4);
    port_out(last_page, SEG_SELECT);
    if (et4000_chiptype >= CHIP_ET4000W32i) {
	/* Write page4-5 to bits 4-5 of ext. bank register. */
	port_out_r(0x3cb, (port_in(0x3cb) & ~0x30) | (page & 0x30));
    }
}

/* Bank switching function - set 64K write bank number */
static void et4000_setwrpage(int page)
{
    last_page &= 0xF0;
    last_page |= page;
    port_out(last_page, SEG_SELECT);
    if (et4000_chiptype >= CHIP_ET4000W32i) {
	/* Write page4-5 to bits 0-1 of ext. bank register. */
	port_out_r(0x3cb, (port_in(0x3cb) & ~0x03) | (page >> 4));
    }
}


/* Set display start address (not for 16 color modes) */
/* ET4000 supports any address in video memory (up to 1Mb) */
/* ET4000/W32i/p supports up to 4Mb */

static void et4000_setdisplaystart(int address)
{
    port_outw_r(CRT_IC, 0x0d + ((address >> 2) & 0x00ff) * 256);	/* sa2-sa9 */
    port_outw_r(CRT_IC, 0x0c + ((address >> 2) & 0xff00));	/* sa10-sa17 */
    port_in(0x3da);			/* set ATC to addressing mode */
    port_out_r(ATT_IW, 0x13 + 0x20);	/* select ATC reg 0x13 */
    port_out_r(ATT_IW, (port_in(ATT_R) & 0xf0) | ((address & 3) << 1));
    /* write sa0-1 to bits 1-2 */
    port_out_r(CRT_IC, 0x33);
    if (et4000_chiptype >= CHIP_ET4000W32i)
	/* write sa18-21 to bit 0-3 */
	port_out_r(CRT_DC, (port_in(CRT_DC) & 0xf0) | ((address & 0x3c0000) >> 18));
    else
	/* write sa18-19 to bit 0-3 */
	port_out_r(CRT_DC, (port_in(CRT_DC) & 0xfc) | ((address & 0xc0000) >> 18));
}


/* Set logical scanline length (usually multiple of 8) */
/* ET4000 supports multiples of 8 to 4088 */

static void et4000_setlogicalwidth(int width)
{
    port_outw_r(CRT_IC, 0x13 + (width >> 3) * 256);	/* lw3-lw11 */
    port_out_r(CRT_IC, 0x3f);
    port_out_r(CRT_DC, (port_in(CRT_DC) & 0x7f)
	 | ((width & 0x800) >> 5));	/* write lw12 to bit 7 */
}

static int et4000_ext_set(unsigned what, va_list params)
{
    int param2, old_values;

    switch (what) {
    case VGA_EXT_AVAILABLE:
	param2 = va_arg(params, int);
	switch (param2) {
	case VGA_AVAIL_SET:
	    return VGA_EXT_AVAILABLE | VGA_EXT_SET | VGA_EXT_CLEAR | VGA_EXT_RESET;
	case VGA_AVAIL_ACCEL:
	    return 0;
	case VGA_AVAIL_FLAGS:
	    return pos_ext_settings;
	}
	return 0;
    case VGA_EXT_SET:
	old_values = ext_settings;
	ext_settings |= (va_arg(params, int)) & pos_ext_settings;
      params_changed:
	if (((old_values ^ ext_settings) & pos_ext_settings)) {
	    int cmd;
	    CRITICAL = 1;
	    vga_lockvc();
	    _ramdac_dactocomm();
	    cmd = port_in(PEL_MSK);
	    _ramdac_dactocomm();
	    if (ext_settings & VGA_CLUT8)
		cmd |= 2;
	    else
		cmd &= ~2;
	    port_out_r(PEL_MSK, cmd);
	    vga_unlockvc();
	    CRITICAL = 0;
	}
	return old_values;
    case VGA_EXT_CLEAR:
	old_values = ext_settings;
	ext_settings &= ~((va_arg(params, int)) & pos_ext_settings);
	goto params_changed;
    case VGA_EXT_RESET:
	old_values = ext_settings;
	ext_settings = (va_arg(params, int)) & pos_ext_settings;
	goto params_changed;
    default:
	return 0;
    }
}

static int et4000_linear(int op, int param)
{
    /* linear mode not supported on original chipset */
    if (et4000_chiptype == CHIP_ET4000)
	return -1;
    else if (op == LINEAR_QUERY_GRANULARITY)
	return 4 * 1024 * 1024;
    else if (op == LINEAR_QUERY_RANGE)
	return 256;
    else if (op == LINEAR_ENABLE) {
	et4000_setlinear(param / (4 * 1024 * 1024));
	return 0;
    } else if (op == LINEAR_DISABLE) {
	et4000_setlinear(0);
	return 0;
    } else
	return -1;
}

/* Function table (exported) */

DriverSpecs __svgalib_et4000_driverspecs =
{
    et4000_saveregs,
    et4000_setregs,
    et4000_unlock,
    et4000_lock,
    et4000_test,
    et4000_init,
    et4000_setpage,
    et4000_setrdpage,
    et4000_setwrpage,
    et4000_setmode,
    et4000_modeavailable,
    et4000_setdisplaystart,
    et4000_setlogicalwidth,
    et4000_getmodeinfo,
    0,				/* bitblt */
    0,				/* imageblt */
    0,				/* fillblt */
    0,				/* hlinelistblt */
    0,				/* bltwait */
    et4000_ext_set,
    0,
    et4000_linear,
    NULL,                       /* Accelspecs */
    NULL,                       /* Emulation */
};


/* Hicolor DAC detection derived from vgadoc (looks tricky) */


#ifndef DAC_TYPE
static int testdac(void)
{
    int x, y, z, v, oldcommreg, oldpelreg, retval;

    /* Use the following return values:
     *      0: Ordinary DAC
     *      1: Sierra SC11486 (32k)
     *      3: Sierra 32k/64k
     *      5: SS2410 15/24bit DAC
     *      9: AT&T 20c491/2
     *     10: STG-170x
     */

    retval = 0;			/* default to no fancy dac */
    _ramdac_dactopel();
    x = port_in(PEL_MSK);
    do {			/* wait for repeat of value */
	y = x;
	x = port_in(PEL_MSK);
    } while (x != y);
    z = x;
    port_in(PEL_IW);
    port_in(PEL_MSK);
    port_in(PEL_MSK);
    port_in(PEL_MSK);
    x = port_in(PEL_MSK);
    y = 8;
    while ((x != 0x8e) && (y > 0)) {
	x = port_in(PEL_MSK);
	y--;
    }
    if (x == 0x8e) {
	/* We have an SS2410 */
	retval = 1 | 4;
	_ramdac_dactopel();
    } else {
	_ramdac_dactocomm();
	oldcommreg = port_in(PEL_MSK);
	_ramdac_dactopel();
	oldpelreg = port_in(PEL_MSK);
	x = oldcommreg ^ 0xFF;
	port_out_r(PEL_MSK, x);
	_ramdac_dactocomm();
	v = port_in(PEL_MSK);
	if (v != x) {
	    v = _ramdac_setcomm(x = oldcommreg ^ 0x60);
	    /* We have a Sierra SC11486 */
	    retval = 1;

	    if ((x & 0xe0) == (v & 0xe0)) {
		/* We have a Sierra 32k/64k */
		x = port_in(PEL_MSK);
		_ramdac_dactopel();
		retval = 1 | 2;

		if (x == port_in(PEL_MSK)) {
		    /* We have an ATT 20c491 or 20c492 */
		    retval = 1 | 8;
		    if (_ramdac_setcomm(0xFF) != 0xFF) {
			/* We have a weird dac, AcuMos ADAC1 */
			retval = 1 | 16;
		    } else {	/* maybe an STG-170x */
			/* try to enable ext. registers */
			_ramdac_setcomm(oldcommreg | 0x10);
			port_out_r(PEL_MSK, 0);
			port_out_r(PEL_MSK, 0);
			if (port_in(PEL_MSK) == 0x44) {
			    /* it's a 170x */
			    /* Another read from PEL_MSK gets the chiptype, 0x02 == 1702. */
			    retval = 1 | 64;
			    et4000_extdac = 1;
			}
		    }
		}
	    }
	    _ramdac_dactocomm();
	    port_out_r(PEL_MSK, oldcommreg);
	}
	_ramdac_dactopel();
	port_out_r(PEL_MSK, oldpelreg);
    }
    return retval;
}
#endif				/* !defined(DAC_TYPE) */


/* Initialize chipset (called after detection) */

static int et4000_init(int force, int par1, int par2)
{
    int value;
    int old, new, val;
#ifdef USE_CLOCKS
    int i;
#endif

#ifdef DYNAMIC
    if (et4000_modes == NULL) {
	FILE *regs;

	regs = fopen(ET4000_REGS, "r");
	if (regs != 0) {
	    et4000_modes = NULL;
	    __svgalib_readmodes(regs, &et4000_modes, &et4000_dac, &clocks[0]);
	    fclose(regs);
	} else
	    et4000_modes = &No_Modes;
    }
#endif

    /* Determine the ET4000 chip type. */

    /* Test for ET4000/W32. */
    old = port_in(0x3cb);
    port_out_r(0x3cb, 0x33);
    new = port_in(0x3cb);
    port_out_r(0x3cb, old);
    if (new != 0x33)
	et4000_chiptype = CHIP_ET4000;
    else {
	    port_out_r(0x217a, 0xec);
	    val = port_in(0x217b) >> 4;
	    switch (val) {
	    case 0:
		et4000_chiptype = CHIP_ET4000W32;
		break;
	    case 1:
	    case 3:
		et4000_chiptype = CHIP_ET4000W32i;
		break;
	    case 2:
	    case 5:
	    default:
		et4000_chiptype = CHIP_ET4000W32p;
	    }
    }

    if (force)
	et4000_memory = par1;
    else {
	port_out_r(CRT_IC, 0x37);
	value = port_in(CRT_DC);
	et4000_memory = (value & 0x08) ? 256 : 64;
	switch (value & 3) {
	case 2:
	    et4000_memory *= 2;
	    break;
	case 3:
	    et4000_memory *= 4;
	    break;
	}
	if (value & 0x80)
	    et4000_memory *= 2;
	port_out_r(CRT_IC, 0x32);
	if (port_in(CRT_DC) & 0x80) {
	    /* Interleaved memory on ET4000/W32i/p: */
	    /* Multiply by 2. */
	    et4000_memory *= 2;
	}
    }
#ifndef DAC_TYPE
    if (et4000_dac < 0)
	et4000_dac = testdac();
#endif

#ifdef USE_CLOCKS
    /* Measure clock frequencies */
    for (i = 0; i < CLOCKS; ++i)
	get_clock(i);
#endif

    if (__svgalib_driver_report) {
	char dacname[60];
	switch (et4000_dac & ~1) {
	case 0:
	    strcpy(dacname, ", Hicolor Dac: Sierra SC11486");
	    break;
	case 2:
	    strcpy(dacname, ", Hicolor Dac: Sierra 32k/64k");
	    break;
	case 4:
	    strcpy(dacname, ", Hicolor Dac: SS2410");
	    break;
	case 8:
	    strcpy(dacname, ", Hicolor Dac: ATT20c491/2");
	    break;
	case 16:
	    strcpy(dacname, ", Hicolor Dac: ACUMOS ADAC1");
	    break;
	case 32:
	    strcpy(dacname, ", Hicolor Dac: Sierra 15025/6 (24-bit)");
	    break;
	case 64:
	    strcpy(dacname, ", Hicolor Dac: STG-170x (24-bit)");
	    break;
	default:
	    strcpy(dacname, ", Hicolor Dac: Unknown");
	    break;
	}

	fprintf(stderr,"Using Tseng ET4000 driver (%s %d%s).",
	       chipname[et4000_chiptype], et4000_memory,
	       et4000_dac & 1 ? dacname : "");
#ifdef USE_CLOCKS
	fprintf(stderr," Clocks:");
	for (i = 0; i < 8; ++i)
	    fprintf(stderr," %d", (clocks[i] + 500) / 1000);
#endif
	fprintf(stderr,"\n");
    }
    __svgalib_driverspecs = &__svgalib_et4000_driverspecs;

    __svgalib_banked_mem_base=0xa0000;
    __svgalib_banked_mem_size=0x10000;
/*    __svgalib_linear_mem_base=LINEARBASE;
    __svgalib_linear_mem_size=nv3_memory*0x400; */

    pos_ext_settings = 0;
    if (et4000_dac & (8 | 16 | 32 | 64))
	pos_ext_settings = VGA_CLUT8;

    return 0;
}
