/* 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 "vga.h"
#include "libvga.h"
#include "driver.h"
#include "config.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 );
static int et4000_interlaced( int mode );
static void et4000_unlock();

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 "et4000.regs" 

#ifdef DAC_TYPE
static int et4000_dac = DAC_TYPE;
#endif

static ModeTable et4000_modes[] = {
	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  
};

#endif /* !defined(DYNAMIC) */

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

static void dactocomm() {
	inb(0x3c8);	/* set PEL mode */
	inb(0x3c6);	/* read four times */
	inb(0x3c6);
	inb(0x3c6);
	inb(0x3c6);	
}

static void dactopel() {
	inb(0x3c8);
}

/* 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 */

#ifndef CLOCKS
#  define CLOCKS 24
#endif
#ifndef CLOCK_VALUES
#  define CLOCK_VALUES { 0 }
#endif

static unsigned clocks[CLOCKS] = CLOCK_VALUES;


#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 = (inb(MIS_R)>>2) & 3;    /* bit 0..1 */
	outb(CRT_I, 0x34); 
	res |= (inb(CRT_D) & 2) << 1; /* bit 2 */
	outb(SEQ_I, 0x07);
	temp = inb(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 = inb(MIS_R) & 0xF3;
	  outb(MIS_W, temp | ((clk&3)<<2)); 
	  outb(CRT_I, 0x34);
	  temp = inb(CRT_D) & 0xFD;
	  outb(CRT_D, temp | ((clk&4)>>1));
	  outb(SEQ_I, 0x07);
	  temp = inb(SEQ_D) & 0xBE;
	  temp |= (clk&0x10 ? 0x41 : 0x00);
	  temp |= (clk&0x08) << 3;
	  outb(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:	inb	%1,%%al		\n"
	"	testb	$8,%%al		\n"
	"	jne	__W1		\n"
	"__W2:	inb	%1,%%al		\n"
	"	testb	$8,%%al		\n"
	"	je	__W2		\n"
	"__L1:	inb	%1,%%al		\n"
	"	incl	%0		\n"
	"	testb	$8,%%al		\n"
	"	jne	__L1		\n"
	"__L2:	inb	%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 = inb( MIS_R);
    outb(MIS_W, (save&0xF3) | 0x04);
    act = measure(FRAMES);
    outb(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, CRT_I); 
	 regs[EXT+i] = port_in(CRT_D); 
    }
    port_out(0x3f, CRT_I);
    regs[EXT+8] = port_in(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(IS1_R);    /* reset flip flop */
    port_out(0x16, ATT_IW);
    regs[EXT+12] = port_in(ATT_R);

    if (et4000_extdac) {
    	    dactocomm();
	    regs[EXT+13] = inb(0x3c6);
	    dactocomm();
	    outb(0x3c6, regs[EXT+13] | 0x10);
	    dactocomm();
	    inb(0x3c6);
	    outb(0x3c6,3);	/* write index low */
	    outb(0x3c6,0);	/* write index high */
	    regs[EXT+14] = inb(0x3c6);	/* primary ext. pixel select */
	    regs[EXT+15] = inb(0x3c6);	/* secondary ext. pixel select */
	    regs[EXT+16] = inb(0x3c6);	/* PLL control register */
	    dactocomm();
	    outb(0x3c6, 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, CRT_I); 
	 port_out(regs[EXT+i], CRT_D); 
    }
    port_out(0x3f, CRT_I);
    port_out(regs[EXT+6], CRT_D);
#endif

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

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

    /* set original CRTC 0x11 */
    port_out(0x11, CRT_I);
    port_out(save, CRT_D);

    /* write extended attribute register */
    port_in(IS1_R);    /* reset flip flop */
    port_out(0x16, ATT_IW);
    port_out(regs[EXT+12], ATT_IW);
    
    if (et4000_extdac) {
    	    dactocomm();
    	    outb(0x3c6,regs[EXT+13] | 0x10);
	    dactocomm();
	    inb(0x3c6);
	    outb(0x3c6,3);	/* write index low */
	    outb(0x3c6,0);	/* write index high */
	    outb(0x3c6,regs[EXT+14]);	/* primary ext. pixel select */
	    outb(0x3c6,regs[EXT+15]);	/* secondary ext. pixel select */
	    outb(0x3c6,regs[EXT+16]);	/* PLL control register */
	    dactocomm();
	    outb(0x3c6, regs[EXT+13]);
    }
}


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

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

	regs = LOOKUPMODE(et4000_modes, mode);
	if (regs == NULL || mode == GPLANE16)
          return 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;

	if (et4000_dac)
		/* Standard dac behaviour */
		__vga_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). */
	  	outb(CRT_I, 0x34);
	  	if (inb(CRT_D) == 0xca)
	  		outb(CRT_D, 0xc8);
		return 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:
			__vga_hicolor(et4000_dac, HI15_DAC);
			break;
		case 1<<16:
			__vga_hicolor(et4000_dac, HI16_DAC);
			break;
		case 1<<24:
			__vga_hicolor(et4000_dac, TC24_DAC);
			break;
	    }

	__vga_setregs(regs);
	et4000_setregs(regs, mode);
        return 0;
}

/* Unlock chipset-specific registers */

static void et4000_unlock() {
	/* 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() {
}

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

static void et4000_setlinear(int addr) {
	et4000_unlock();
	outb(0x3D4,0x36);
	if (addr)
      		outb(0x3D5,inb(0x3D5) | 16);    /* enable linear mode */
	else
		outb(0x3D5,inb(0x3D5) & ~16);   /* disable linear mode */
	outb(0x3D4,0x30);
	outb(0x3D5,addr);
	et4000_lock();
}


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

static int et4000_test()
{
    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 = 0x3d4;
    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);
    	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. */
		outb(0x3cb, (inb(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. */
		outb(0x3cb, (inb(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. */
		outb(0x3cb, (inb(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 ) {
	outw(0x3d4, 0x0d + ((address >> 2) & 0x00ff) * 256);	/* sa2-sa9 */
	outw(0x3d4, 0x0c + ((address >> 2) & 0xff00));		/* sa10-sa17 */
	inb(0x3da);			/* set ATC to addressing mode */
	outb(0x3c0, 0x13 + 0x20);	/* select ATC reg 0x13 */
	outb(0x3c0, (inb(0x3c1) & 0xf0) | ((address & 3) << 1));
		/* write sa0-1 to bits 1-2 */
	outb(0x3d4, 0x33);
	if (et4000_chiptype >= CHIP_ET4000W32i)
		/* write sa18-21 to bit 0-3 */
		outb(0x3d5, (inb(0x3d5) & 0xf0) | ((address & 0x3c0000) >> 18));
	else
		/* write sa18-19 to bit 0-3 */
		outb(0x3d5, (inb(0x3d5) & 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 ) { 
	outw(0x3d4, 0x13 + (width >> 3) * 256);	/* lw3-lw11 */
	outb(0x3d4, 0x3f);
	outb(0x3d5, (inb(0x3d5) & 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();
			dactocomm();
			cmd = inb(0x3c6);
			dactocomm();
			if(ext_settings&VGA_CLUT8)
				cmd |= 2;
			else
				cmd &= ~2;
			outb(0x3c6,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 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 */
};


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


#ifndef DAC_TYPE
static int testdac()
{
	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 */
	dactopel();
	x=inb(0x3c6);
	do {		/* wait for repeat of value */
		y = x;
		x = inb(0x3c6);
	} while (x != y);
	z=x;
	inb(0x3c8);
	inb(0x3c6);
	inb(0x3c6);
	inb(0x3c6);
	x=inb(0x3c6);
	y=8;
	while ( (x!=0x8e) && (y>0) )
	{
		x=inb(0x3c6);
		y--;
	}
	if (x==0x8e)
	{
		/* We have an SS2410 */
		retval = 1 | 4;
		dactopel();
	}
	else
	{
		dactocomm();
		oldcommreg = inb(0x3c6);
		dactopel();
		oldpelreg = inb(0x3c6);
		x = oldcommreg ^ 0xFF;
		outb(0x3c6, x);
		dactocomm();
		v=inb(0x3c6);
		if (v != x)
		{
			dactocomm();
			x = oldcommreg ^ 0x60;
			outb(0x3c6, x);
			dactocomm();
			v = inb(0x3c6);
			/* We have a Sierra SC11486 */
			retval = 1;

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

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


/* Initialize chipset (called after detection) */

static int et4000_init( int force, int par1 ) {
	int value;
	int old, new, val;

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

	  regs = fopen(ET4000_REGS,"r");
	  if (regs != 0) {
	    et4000_modes = NULL;
	    __vga_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 = inb(0x3cb);
        outb(0x3cb, 0x33);
        new = inb(0x3cb);
        outb(0x3cb, old);
        if (new != 0x33)
        	et4000_chiptype = CHIP_ET4000;
        else {
        	/* ET4000/W32. */
        	if (getenv("IOPERM") == NULL && iopl(3) < 0)
        		/* Can't get further I/O permissions -- */
        		/* assume ET4000/W32. */
        		et4000_chiptype = CHIP_ET4000W32;
        	else {
	        	outb(0x217a, 0xec);
	        	val = inb(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;
        		}
	        	iopl(0);
	        }
        }

	if (force)
		et4000_memory = par1;
	else {
		outb(0x3d4,0x37);
		value = inb(0x3d5);
		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;
		outb(0x3d4, 0x32);
		if (inb(0x3d5) & 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;
		}

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

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

	return 0;
}

