/*
 * DANG_BEGIN_MODULE
 *
 * Description:
 *   ARK driver for DOSEMU.  Code taken from SVGALIB. 
 * 
 *   Original driver written by Harm Hanemaayer.
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

#include "dosemu.h"
#include "base/port.h"
#include "base/detach.h"
#include "base/misc.h"
#include "env/libvga.h"
#include "vgaregs.h"
#include "vga-ramdac.h"


enum { S3_911, S3_924, S3_801, S3_805, S3_928, S3_864, S3_964, S3_TRIO32,
	S3_TRIO64 };

static const char *s3_chipname[] = { "911", "924", "801", "805", "928",
	"864", "964", "Trio32", "Trio64" };

#define S3_CR31		VGA_TOTAL_REGS + 0
#define S3_CR32		VGA_TOTAL_REGS + 1
#define S3_CR33		VGA_TOTAL_REGS + 2
#define S3_CR34		VGA_TOTAL_REGS + 3
#define S3_CR35		VGA_TOTAL_REGS + 4
#define S3_CR3A		VGA_TOTAL_REGS + 5
#define S3_CR3B		VGA_TOTAL_REGS + 6
#define S3_CR3C		VGA_TOTAL_REGS + 7
#define S3_CR40		VGA_TOTAL_REGS + 8
#define S3_CR42		VGA_TOTAL_REGS + 9
#define S3_CR43		VGA_TOTAL_REGS + 10
#define S3_CR44		VGA_TOTAL_REGS + 11
#define S3_CR50		VGA_TOTAL_REGS + 12	/* 801+ */
#define S3_CR51		VGA_TOTAL_REGS + 13
#define S3_CR53		VGA_TOTAL_REGS + 14
#define S3_CR54		VGA_TOTAL_REGS + 15
#define S3_CR55		VGA_TOTAL_REGS + 16
#define S3_CR58		VGA_TOTAL_REGS + 17
#define S3_CR59		VGA_TOTAL_REGS + 18
#define S3_CR5A		VGA_TOTAL_REGS + 19
#define S3_CR5D		VGA_TOTAL_REGS + 20
#define S3_CR5E		VGA_TOTAL_REGS + 21
#define S3_CR60		VGA_TOTAL_REGS + 22
#define S3_CR61		VGA_TOTAL_REGS + 23
#define S3_CR62		VGA_TOTAL_REGS + 24
#define S3_CR67		VGA_TOTAL_REGS + 25
#define S3_CR6D		VGA_TOTAL_REGS + 26
#define S3_CR38		VGA_TOTAL_REGS + 27
#define S3_CR39		VGA_TOTAL_REGS + 28
#define S3_CR41		VGA_TOTAL_REGS + 29
#define S3_CR45		VGA_TOTAL_REGS + 30
#define S3_CR46		VGA_TOTAL_REGS + 31
#define S3_CR47		VGA_TOTAL_REGS + 32
#define S3_CR48		VGA_TOTAL_REGS + 33
#define S3_CR49		VGA_TOTAL_REGS + 34
#define S3_CR4A		VGA_TOTAL_REGS + 35
#define S3_CR4B		VGA_TOTAL_REGS + 36
#define S3_CR4C		VGA_TOTAL_REGS + 37
#define S3_CR4D		VGA_TOTAL_REGS + 38
#define S3_CR4E		VGA_TOTAL_REGS + 39
#define S3_CR4F		VGA_TOTAL_REGS + 40
#define S3_CR63		VGA_TOTAL_REGS + 41
#define S3_CR64		VGA_TOTAL_REGS + 42
#define S3_CR65		VGA_TOTAL_REGS + 43
#define S3_FLIP		VGA_TOTAL_REGS + 44
#define S3_102		VGA_TOTAL_REGS + 45
#define S3_EXT1		VGA_TOTAL_REGS + 46
#define S3_EXT2		VGA_TOTAL_REGS + 47
#define S3_EXT3		VGA_TOTAL_REGS + 48
#define S3_EXT4		VGA_TOTAL_REGS + 49
#define S3_MISC		VGA_TOTAL_REGS + 50

#define S3_DAC_OFFSET	VGA_TOTAL_REGS + 55

#define S3_TOTAL_REGS	VGA_TOTAL_REGS + 55 + 10

static int s3_chiptype;
static int s3_memory;
static DacMethods *dac_used = NULL;

static void s3_setperm(void)
{
  set_ioperm(0x102, 1, 1);
}

static void s3_releaseperm(void)
{
  set_ioperm(0x102, 1, 0);
}

static Bit8u s3_inb(Bit32u port)
{
  if (own_console) {
    return port_safe_inb(port);
  }
  else {
    return 0xff;
  }
}

static void s3_outb(Bit32u port, Bit8u val)
{
  if (own_console) {
    return port_safe_outb(port, val);
  }
  else {
  }
}

static Bit16u s3_inw(Bit32u port)
{
  if (own_console) {
    return port_safe_inw(port);
  }
  else {
    if (port < 0x3ff) {
      Bit16u result;
      result = s3_inb(port);
      result = result | (s3_inb(port+1) << 8);
      return result;
    }
    else {
      return 0xffff;
    }
  }
}

static void s3_outw(Bit32u port, Bit16u val)
{
  if (own_console) {
    return port_safe_outw(port, val);
  }
  else {
    if (port < 0x3ff) {
      s3_outb(port, val & 0xff);
      s3_outb(port+1, (val >> 8) & 0xff);
      return;
    }
    else {
      return;
    }
  }
}

static void s3_unlock(void)
{
  outCR(0x38, 0x48);	/* Unlock special regs. */
  outCR(0x39, 0xA5);	/* Unlock system control regs. */
  if (s3_chiptype >= S3_864)
    outCR(0x40, inCR(0x40) | 0x01);  /* Unlock enhanced command regs. */
}

static void s3_saveregs(Bit16u *regs)
{
  Bit32u ext_base;

  regs[S3_CR38] = inCR(0x38);
  regs[S3_CR39] = inCR(0x39);

  outCR(0x38, 0x48);	/* Unlock special regs. */
  outCR(0x39, 0xA5);	/* Unlock system control regs. */

  regs[S3_CR40] = inCR(0x40);

  if (s3_chiptype >= S3_864)
    outCR(0x40, inCR(0x40) | 0x01);  /* Unlock enhanced command regs. */

  regs[S3_MISC] = inb(MIS_R);

  /* Save extended CRTC registers. */
  regs[S3_CR31] = inCR(0x31);
  regs[S3_CR32] = inCR(0x32);
  regs[S3_CR33] = inCR(0x33);
  regs[S3_CR34] = inCR(0x34);
  regs[S3_CR35] = inCR(0x35);
  regs[S3_CR3A] = inCR(0x3A);
  regs[S3_CR3B] = inCR(0x3B);
  regs[S3_CR3C] = inCR(0x3C);
  regs[S3_CR41] = inCR(0x41);
  regs[S3_CR42] = inCR(0x42);
  regs[S3_CR43] = inCR(0x43);
  regs[S3_CR44] = inCR(0x44);
  regs[S3_CR45] = inCR(0x45);
  regs[S3_CR46] = inCR(0x46);
  regs[S3_CR47] = inCR(0x47);
  regs[S3_CR48] = inCR(0x48);
  regs[S3_CR49] = inCR(0x49);
  regs[S3_CR4A] = inCR(0x4A);
  regs[S3_CR4B] = inCR(0x4B);
  regs[S3_CR4C] = inCR(0x4C);
  regs[S3_CR4D] = inCR(0x4D);
  regs[S3_CR4E] = inCR(0x4E);
  regs[S3_CR4F] = inCR(0x4F);

  if (s3_chiptype >= S3_801) {
    regs[S3_CR50] = inCR(0x50);
    regs[S3_CR51] = inCR(0x51);
    regs[S3_CR53] = inCR(0x53);
    regs[S3_CR54] = inCR(0x54);
    regs[S3_CR55] = inCR(0x55);
    regs[S3_CR58] = inCR(0x58);
    regs[S3_CR59] = inCR(0x59);
    regs[S3_CR5A] = inCR(0x5A);
    regs[S3_CR5D] = inCR(0x5D);
    regs[S3_CR5E] = inCR(0x5E);
    regs[S3_CR60] = inCR(0x60);
    regs[S3_CR61] = inCR(0x61);
    regs[S3_CR62] = inCR(0x62);
    regs[S3_CR63] = inCR(0x63);
    regs[S3_CR64] = inCR(0x64);
    regs[S3_CR65] = inCR(0x65);

#if 0
    if (dac_used->id != NORMAL_DAC) {
      regs[S3_CR67] = inCR(0x67);
      regs[S3_CR6D] = inCR(0x6D);
    }
#endif
  }

  inb(0x3da);				/* reset flip-flop */
  outb(ATT_IW, 0x36);
  regs[S3_FLIP] = inb(ATT_R);
  outb(ATT_R, regs[S3_FLIP]);

  outCR(0x40, regs[S3_CR40]);
  outCR(0x38, regs[S3_CR38]);           /* restore the extension status */
  outCR(0x39, regs[S3_CR39]);

  ext_base = (regs[S3_CR43] & 0x10) ? 0x148 : 0x2e8;

#ifndef CONFIG_RUN_AS_ROOT
  priv_on();
#endif
  iopl(3);
  regs[S3_102] = inb(0x102);
  outb(0x102, 1);
  regs[S3_EXT1] = inw(0x8000 | ext_base);
  regs[S3_EXT2] = inw(0x8400 | ext_base);
  regs[S3_EXT3] = inw(0x8800 | ext_base);
  regs[S3_EXT4] = inw(0x8A00 | ext_base);
  outb(0x102, regs[S3_102]);
  iopl(0);
#ifndef CONFIG_RUN_AS_ROOT
  priv_off();
#endif
#if 0
  dac_used->saveState(regs + S3_DAC_OFFSET);
#endif
}

static void s3_setregs(const Bit16u *regs)
{
  Bit32u ext_base;

  s3_unlock();

  outb(MIS_W, regs[S3_MISC]);

  /* Restore the extended CRTC registers. */
  outCR(0x31, regs[S3_CR31]);
  outCR(0x32, regs[S3_CR32]);
  outCR(0x33, regs[S3_CR33]);
  outCR(0x34, regs[S3_CR34]);
  outCR(0x35, regs[S3_CR35]);
  outCR(0x3A, regs[S3_CR3A]);
  outCR(0x3B, regs[S3_CR3B]);
  outCR(0x3C, regs[S3_CR3C]);
  outCR(0x41, regs[S3_CR41]);
  outCR(0x42, regs[S3_CR42]);
  outCR(0x43, regs[S3_CR43]);
  outCR(0x44, regs[S3_CR44]);
  outCR(0x45, regs[S3_CR45]);
  outCR(0x46, regs[S3_CR46]);
  outCR(0x47, regs[S3_CR47]);
  outCR(0x48, regs[S3_CR48]);
  outCR(0x49, regs[S3_CR49]);
  outCR(0x4A, regs[S3_CR4A]);
  outCR(0x4B, regs[S3_CR4B]);
  outCR(0x4C, regs[S3_CR4C]);
  outCR(0x4D, regs[S3_CR4D]);
  outCR(0x4E, regs[S3_CR4E]);
  outCR(0x4F, regs[S3_CR4F]);

  if (s3_chiptype >= S3_801) {
    outCR(0x50, regs[S3_CR50]);
    outCR(0x51, regs[S3_CR51]);
    outCR(0x53, regs[S3_CR53]);
    outCR(0x54, regs[S3_CR54]);
    outCR(0x55, regs[S3_CR55]);
    outCR(0x58, regs[S3_CR58]);
    outCR(0x59, regs[S3_CR59]);
    outCR(0x5A, regs[S3_CR5A]);
    outCR(0x5D, regs[S3_CR5D]);
    outCR(0x5E, regs[S3_CR5E]);
    outCR(0x60, regs[S3_CR60]);
    outCR(0x61, regs[S3_CR61]);
    outCR(0x62, regs[S3_CR62]);
    outCR(0x63, regs[S3_CR63]);
    outCR(0x64, regs[S3_CR64]);
    outCR(0x65, regs[S3_CR65]);

#if 0
    if (dac_used->id != NORMAL_DAC) {
      outCR(0x67, regs[S3_CR67]);	/* S3 pixmux. */
      dac_used->restoreState(regs + S3_DAC_OFFSET);
      outCR(0x6D, regs[S3_CR6D]);
    }
#endif
  }

  outCR(0x40, regs[S3_CR40]);

  port_in(0x3da);
  outb(ATT_IW, 0x36);
  outb(ATT_R, regs[S3_FLIP]);

  outCR(0x40, regs[S3_CR40]);
  outCR(0x38, regs[S3_CR38]);
  outCR(0x39, regs[S3_CR39]);

  ext_base = (regs[S3_CR43] & 0x10) ? 0x148 : 0x2e8;

#ifndef CONFIG_RUN_AS_ROOT
  priv_on();
#endif
  iopl(3);
  outb(0x102, 1);
  outw(0x8000 | ext_base, regs[S3_EXT1]);
  outw(0x8400 | ext_base, regs[S3_EXT2]);
  outw(0x8800 | ext_base, regs[S3_EXT3]);
  outw(0x8c00 | ext_base, regs[S3_EXT4]);
  outb(0x102, regs[S3_102]);
  iopl(0);
#ifndef CONFIG_RUN_AS_ROOT
  priv_off();
#endif
}


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

/* Some port I/O functions: */
static unsigned char rdinx( int port, unsigned char indx )
{
	outb(port, indx);
	return inb(port + 1);
}

static void wrinx( int port, unsigned char indx, unsigned char val )
{
	outb(port, indx);
	outb(port + 1, val);
}

/*
 * Returns true iff the bits in 'mask' of register 'port', index 'indx'
 * are read/write.
 */
static int testinx2( int port, unsigned char indx, unsigned char mask)
{
	unsigned char old, new1, new2;

	old = rdinx(port, indx);
	wrinx(port, indx, (old & ~mask));
	new1 = rdinx(port, indx) & mask;
	wrinx(port, indx, (old | mask));
	new2 = rdinx(port, indx) & mask;
	wrinx(port, indx, old);
	return (new1 == 0) && (new2 == mask);
}

static void s3_setpage(int page)
{
  outb(CRT_IC, 0x35);
  outb(CRT_DC, (inb(CRT_DC) & 0xF0) | (page & 0x0F));
  outb(CRT_IC, 0x51);
  outb(CRT_DC, (inb(CRT_DC) & ~0x0C) | ((page & 0x30) >> 2));
}

static DacMethods *dacs_to_probe[] = { &S3_SDAC_methods,
	&S3_GENDAC_methods, &ATT20C490_methods, NULL };

static Boolean s3_init(void)
{
  emu_iodev_t  io_device;
  Bit32u       i;
  int          id, rev, tmp, cnfg;

  s3_unlock();

  id = inCR(0x30);	/* Get chip id. */
  rev = id & 0x0F;
  if (id >= 0xE0) {
      id |= inCR(0x2E) << 8;
      rev |= inCR(0x2F) << 4;
    }

    s3_chiptype = -1;
    switch (id & 0xfff0) {
    case 0x80 :
      if (rev == 1) {
	s3_chiptype = S3_911;
	break;
      }
      if (rev == 2) {
	s3_chiptype = S3_924;
	break;
      }
      break;
    case 0xa0 :
      outb(0x3d4, 0x36);
      tmp = inb(0x3d5);
      switch (tmp & 0x03) {
      case 0x00:
      case 0x01:
	/* EISA or VLB - 805 */
	s3_chiptype = S3_805;
	break;
      case 0x03:
	/* ISA - 801 */
	s3_chiptype = S3_801;
	break;
      }
      break;
    case 0x90:
      s3_chiptype = S3_928;
      break;
    case 0xB0 :
      /* 928P */
      s3_chiptype = S3_928;
      break;
    case 0xC0 :
      s3_chiptype = S3_864; break;
    case 0xD0 :
      s3_chiptype = S3_964; break;
    case 0x10E0 :
      s3_chiptype = S3_TRIO32; break;
    case 0x11E0 :
      s3_chiptype = S3_TRIO64; break;
    }
    if (s3_chiptype == -1) {
      error("VGA: S3: Unknown chip id %02x\n", id);
      return FALSE;
    }
    
    v_printf("S3: chip of type %s detected\n", s3_chipname[s3_chiptype]);

    outb(0x3d4, 0x36);	/* for register CR36 (CONFG_REG1), */
    cnfg = inCR(0x36);	/* get amount of ram installed */

    if ((cnfg & 0x20) != 0)
      s3_memory = 512;
    else
      if (s3_chiptype == S3_911)
	s3_memory = 1024;
      else
	/* look at bits 5, 6 and 7 */
	switch ((cnfg & 0xE0) >> 5) {
	case 0 : s3_memory = 4096; break;
	case 2 : s3_memory = 3072; break;
	case 3 : s3_memory = 8192; break;
	case 4 : s3_memory = 2048; break;
	case 5 : s3_memory = 6144; break;
	case 6 : s3_memory = 1024; break;
	}

  if (s3_chiptype == S3_TRIO64) {
    dac_used = &Trio64_methods;
    dac_used->initialize();
  }
  else
    dac_used = probeDacs(dacs_to_probe);

  if (dac_used == NULL) {
    /* Not supported. */
    v_printf("svgalib: s3: Assuming normal VGA DAC.\n");
    dac_used = &normal_dac_methods;
  }

  io_device.read_portb   = s3_inb;
  io_device.write_portb  = s3_outb;
  io_device.read_portw   = s3_inw;
  io_device.write_portw  = s3_outw;
  io_device.handler_name = "S3";
  io_device.irq          = EMU_NO_IRQ;

  for (i = 0x4000; i <= 0x4800; i += 0x0400) {
    io_device.start_addr   = i + 0x02e8;
    io_device.end_addr     = i + 0x02e9;
    port_register_handler(io_device);
  }

  for (i = 0x8000; i <= 0xbc00; i += 0x0400) {
    io_device.start_addr   = i + 0x02e8;
    io_device.end_addr     = i + 0x02e9;
    port_register_handler(io_device);
  }

  for (i = 0x4000; i <= 0x4800; i += 0x0400) {
    io_device.start_addr   = i + 0x0148;
    io_device.end_addr     = i + 0x0149;
    port_register_handler(io_device);
  }

  for (i = 0x8000; i <= 0xbc00; i += 0x0400) {
    io_device.start_addr   = i + 0x0148;
    io_device.end_addr     = i + 0x0149;
    port_register_handler(io_device);
  }

  return TRUE;
}

static Boolean s3_test(void)
{
  int vgaIOBase, vgaCRIndex, vgaCRReg;

  vgaIOBase = (inb(0x3CC) & 0x01) ? 0x3D0 : 0x3B0;
  vgaCRIndex = vgaIOBase + 4;
  vgaCRReg = vgaIOBase + 5;

  outb(vgaCRIndex, 0x11);	/* for register CR11, (Vertical Retrace End) */
  outb(vgaCRReg, 0x00);	/* set to 0 */

  outb(vgaCRIndex, 0x38);	/* check if we have an S3 */
  outb(vgaCRReg, 0x00);

  /* Make sure we can't write when locked */

  if (testinx2(vgaCRIndex, 0x35, 0x0f))
    return FALSE;

  outb(vgaCRIndex, 0x38);	/* for register CR38, (REG_LOCK1) */
  outb(vgaCRReg, 0x48);	/* unlock S3 register set for read/write */

  /* Make sure we can write when unlocked */

  if (!testinx2(vgaCRIndex, 0x35, 0x0f))
    return FALSE;

  return s3_init();
}

DriverSpecs s3_driverspecs = {
	"S3",
	s3_saveregs,		/* saveregs */
	s3_setregs,		/* setregs */
	s3_test,
	s3_init,
	s3_setpage,
	s3_setperm,		/* set_perm */
	s3_releaseperm,		/* relase_perm */
	s3_inb,
	s3_outb
};

