/*
 * DANG_BEGIN_MODULE
 *
 * Description:
 *   ARK driver for DOSEMU.  Code taken from SVGALIB. 
 * 
 *   Original driver Copyright (c) 1993 Harm Hanemaayer and
 *         partially copyrighted (C) 1993 by Hartmut Schirmer
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

#include "dosemu.h"
#include "base/port.h"
#include "env/libvga.h"

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

#define SEG_SELECT 0x3CD

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

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

static int et4000_memory;
static int et4000_chiptype;
static int et4000_extdac;
static int et4000_dac = -1;

static void et4000_getperm(void)
{
   if (ioperm(0x3bf,1,1)) {
      error("ET4000: can't get I/O permissions\n");
      leavedos(1);
   }
}

static void et4000_releaseperm(void)
{
   if (ioperm(0x3bf,1,0)) {
      error("ET4000: can't release I/O permissions\n");
      leavedos(1);
   }
}

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

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

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

static void et4000_saveregs(Bit16u *xregs)
{
  int i;
  Bit32u disp_mode_cont;
  Bit16u *crtregs = xregs;
  Bit16u *seqregs = xregs+10;
  Bit16u *attregs = xregs+12;
  Bit16u *graregs = xregs+14;
  Bit16u *miscregs = xregs+16;
  Bit16u *dacregs = xregs+21;
  
  disp_mode_cont = (port_in(MIS_R)&1) ? 0x3d8 : 0x3b8;
 
  /* locking regs - these must be saved before unlocking */
  miscregs[3] = port_in(0x3bf);           /* Hercules compatibility mode */
  miscregs[4] = port_in(disp_mode_cont);  /* display mode control */
  
  et4000_unlock();
 
  /* save extended CRT registers (index 24, 30..37, 3f) */
  for (i = 0; i < 8; i++) {
    port_out(0x30+i, CRT_I); 
    crtregs[1] = port_in(CRT_D); 
  }
  port_out(0x3f, CRT_I);
  crtregs[8] = port_in(CRT_D);
  
  /* save extended sequencer register */
  port_out(7, SEQ_I); 
  seqregs[1] = port_in(SEQ_D); 

  /* save extended attribute register */
  port_in(IS1_R);
  port_out(0x16, ATT_IW);
  attregs[1] = port_in(ATT_R);
 
  /* save extended graphics controller regs */
  port_out(0x0d, GRA_I);
  graregs[0] = port_in(GRA_D);
  port_out(0x0E, GRA_I);
  graregs[1] = port_in(GRA_D);
 
  /* save some other ET4000 specific registers */
  miscregs[0] = port_in(0x3c3); /* microchannel video subsystem enable */
  miscregs[1] = port_in(0x3cb); /* PEL address/data wd */
  miscregs[2] = port_in(0x3cd); /* Segment select */
 
  /* locking regs: already saved above, now restore their values */
  port_out(miscregs[3], 0x3bf);
  port_out(miscregs[4], disp_mode_cont);
  
  if (et4000_extdac) {
    dactocomm();
    dacregs[0] = inb(0x3c6);
    dactocomm();
    outb(0x3c6, dacregs[0] | 0x10);
    dactocomm();
    inb(0x3c6);
    outb(0x3c6,3);	/* write index low */
    outb(0x3c6,0);	/* write index high */
    dacregs[1] = inb(0x3c6);	/* primary ext. pixel select */
    dacregs[2] = inb(0x3c6);	/* secondary ext. pixel select */
    dacregs[3] = inb(0x3c6);	/* PLL control register */
    dactocomm();
    outb(0x3c6, dacregs[0]);
  }
}

static void et4000_setregs(const Bit16u *xregs)
{
  int i;
  unsigned char save;
  Bit32u disp_mode_cont;
  const Bit16u *crtregs = xregs;
  const Bit16u *seqregs = xregs+10;
  const Bit16u *attregs = xregs+12;
  const Bit16u *graregs = xregs+14;
  const Bit16u *miscregs = xregs+16;
  const Bit16u *dacregs = xregs+21;
  
  disp_mode_cont = (port_in(MIS_R)&1) ? 0x3d8 : 0x3b8;
  
  et4000_unlock();
  
  /* write extended CRTC registers */

  /* 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(crtregs[i], CRT_D); 
  }

#if 0
  /* these are rather dangerous ?! */
  port_out(0x36, CRT_I);
  port_out( (port_in(CRT_D) & 0x40)  | (crtregs[6] & 0xbf), CRT_D);
  port_out(0x37, CRT_I);
  port_out( (port_in(CRT_D) & 0xbb)  | (crtregs[7] & 0x44), CRT_D);
#endif
 
  port_out(0x3f, CRT_I);
  port_out( (port_in(CRT_D) & 0x7f)  | (crtregs[8] & 0x80), CRT_D);
  
  /* set original CRTC 0x11 */
  port_out(0x11, CRT_I);
  port_out(save, CRT_D);
 
  /* write extended sequencer register */
  port_out(7, SEQ_I); 
  port_out( (port_in(SEQ_D) & 0x4f) | (seqregs[1] & 0xb0), SEQ_D); 
 
  /* write extended attribute registers */
  port_in(IS1_R);    /* reset flip flop */
  port_out(0x16, ATT_IW);
  port_out(attregs[1], ATT_IW);
 
  /* write extended graphics controller regs */
  port_out(0x0d, GRA_I);
  port_out(graregs[0], GRA_D);
  port_out(0x0e, GRA_I);
  port_out(graregs[1], GRA_D);
 
  /* write some ET4000 specific registers */
#if 0
  port_out(miscregs[0], 0x3c3);
#endif
  port_out(miscregs[1], 0x3cb);
  port_out(miscregs[2], 0x3cd);
 
  /* locking regs! (don't access any extended ET4000 regs after this) */
  port_out(miscregs[3], 0x3bf);
  port_out(miscregs[4], disp_mode_cont );
 
  if (et4000_extdac) {
    dactocomm();
    outb(0x3c6,dacregs[0] | 0x10);
    dactocomm();
    inb(0x3c6);
    outb(0x3c6,3);	/* write index low */
    outb(0x3c6,0);	/* write index high */
    outb(0x3c6,dacregs[1]);	/* primary ext. pixel select */
    outb(0x3c6,dacregs[2]);	/* secondary ext. pixel select */
    outb(0x3c6,dacregs[3]);	/* PLL control register */
    dactocomm();
    outb(0x3c6, dacregs[0]);
  }
}

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 */
  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 ((Bit8u)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;
}

static Boolean et4000_init(void)
{
  int value;
  char dacname[60];

  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;
  }
  if (et4000_dac < 0)
    et4000_dac = testdac();
  
  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;
  }
	
  v_printf("Using Tseng ET4000 driver (%s %d%s).\n", chipname[et4000_chiptype],
	   et4000_memory, et4000_dac & 1 ? dacname : "");

  return TRUE;
}

static Boolean et4000_test(void)
{
  Bit8u 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 FALSE;

  /* test for ET4000 clues */
  if (port_in(MIS_R) & 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) {
    /* 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. */
      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;
      }
    }
    return et4000_init();
  }
  return FALSE;
}

static void et4000_setpage(int page)
{
  /* Set both read and write bank. */
  port_out(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));
  }
}

DriverSpecs et4000_driverspecs = {
	"ET4000",
	et4000_saveregs,
	et4000_setregs,
	et4000_test,
	et4000_init,
	et4000_setpage,
	et4000_getperm,
	et4000_releaseperm,
	NULL,
	NULL
};

