/*
 * DANG_BEGIN_MODULE
 *
 * Description:
 *   RAMDAC handler code unscrupulously copied from SVGALIB and hacked
 *   to support DOSEMU.
 *
 * -- previous comments --
 *   From XFree86, common_hw/S3gendac.c and S3gendac.h
 *   Progaming of the S3 gendac programable clocks, from the S3 Gendac
 *   programing documentation by S3 Inc. 
 *   Jon Tombs <jon@esix2.us.es>
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

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

#define inb(p)     port_in(p)
#define outp(p,v)  port_out(v,p)

/*
 * The following function probes the DACs in daclist, which must be
 * terminated by NULL. It returns the detected DAC if succesful, NULL
 * otherwise. The detected DAC is also initialized.
 */

DacMethods *probeDacs( DacMethods **dacs_to_probe) {
	/* Probe for a RAMDAC. */
	for (;;) {
		DacMethods *dac;
		dac = *dacs_to_probe;
		if (dac == NULL)
			/* None found. */
			return NULL;
	 	if (dac->probe()) {
	 		dac->initialize();
	 		return dac;
	 	}
	 	dacs_to_probe++;
	 }
}


/*
 * RAMDAC definition for normal VGA DAC.
 * Max dot clock is set at 80 MHz.
 */

static int normal_dac_probe(void) {
	return 1;
}
 
static void normal_dac_init(void) {
}

static void normal_dac_savestate(Bit16u *regs)
{
}

static void normal_dac_restorestate(Bit16u *regs)
{
}

DacMethods normal_dac_methods = {
	NORMAL_DAC,
	"Normal VGA DAC",
	0,
	normal_dac_probe,
	normal_dac_init,
	normal_dac_savestate,
	normal_dac_restorestate,
	0	/* State size. */
};


/*
 * RAMDAC definition for basic Sierra-type DAC
 * that can do 32K (5-5-5) color mode (16bpp) with doubled VCLK.
 * A value of 0x80 is written to the Hidden DAC register for this mode.
 */

static int Sierra_32K_probe(void) {
	/* Should return 1 for any Sierra-type DAC. */
	return 0;
}
 
static void Sierra_32K_init(void) {
	/* Should probe the exact DAC type. */
}

static void Sierra_32K_savestate(Bit16u *regs) {
	inb(0x3C8);
    	inb(0x3C6); inb(0x3C6); inb(0x3C6); inb(0x3C6);
    	regs[0] = inb(0x3C6);
}

static void Sierra_32K_restorestate(Bit16u *regs) {
	inb(0x3C8);
    	inb(0x3C6); inb(0x3C6); inb(0x3C6); inb(0x3C6);
    	outb(0x3C6, regs[0]);
}

DacMethods Sierra_32K_methods = {
	SIERRA_32K,
	"Sierra 32K colors VGA DAC",
	0,
	Sierra_32K_probe,
	Sierra_32K_init,
	Sierra_32K_savestate,
	Sierra_32K_restorestate,
	1	/* State size. */
};


/*
 * RAMDAC definition for industry-standard AT&T20C490 DAC with 8-bit pixel
 * port, and compatibles.
 * These RAMDACs can do 32K and 64K color mode (16bpp) with doubled VCLK
 * and 16M (8-8-8) truecolor with tripled VCLK.
 * 0xA0 is written to the Hidden DAC register for 32K, 0xC0 for 64K and
 * 0xE0 for 16M.
 */

static void dactocomm(void) {
	inb(0x3C8);
	inb(0x3C6);
	inb(0x3C6);
	inb(0x3C6);
	inb(0x3C6);
}

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

static unsigned char setcomm(unsigned char data) {
	dactocomm();
	outb (0x3c6, data);
	dactocomm();
	return inb (0x3c6);
}

static int att20c490_probe(void) {
	unsigned char oldcomm, notcomm, oldpel, v;
	int flag=0;

	dactocomm();
	oldcomm = inb (0x3c6);
	dactopel();
	oldpel = inb (0x3c6);

	notcomm = ~oldcomm;
	outb (0x3c6, notcomm);
	dactocomm();
	v = inb (0x3c6);
	if (v != notcomm) {
		if ((setcomm(0xe0) & 0xe0) == 0xe0) {
			if ((setcomm(0x60) & 0xe0) == 0) {
				if ((setcomm(2) & 2) > 0) flag = 1;
				else flag = 1;
			}
		}
	}
	dactocomm();
	outb (0x3c6, oldcomm);
	dactopel();
	outb (0x3c6, oldpel);
	return flag;
}
 
 
static void att20c490_init(void) {
	v_printf("svgalib: Using AT&T20C490-compatible truecolor DAC.\n");
#if 0
	dactocomm();
	inb(0x3C6);	/* Skip command register. */
	v_printf("svgalib: DAC Manufacturer ID = 0x%02X, ", inb(0x3C6));
	v_printf("Device ID = 0x%02X.\n", inb(0x3C6));
#endif
}

DacMethods ATT20C490_methods = {
	ATT20C490,
	"AT&T-compatible truecolor DAC, 80 MHz rated",
	0,
	att20c490_probe,
	att20c490_init,
	Sierra_32K_savestate,
	Sierra_32K_restorestate,
	1	/* State size. */
};


/*
 * RAMDAC definitions for the S3-SDAC (86C716) and S3-GENDAC.
 *
 * These contain S3-specific code.
 *
 * 16-bit DAC, 110 MHz raw clock limit. The 135 MHz version supports
 * pixel multiplexing in 8bpp modes with a halved raw clock.
 *
 * The state consists of 6 registers (same for SDAC and GENDAC).
 */

#define SDAC_COMMAND		0	/* Register offsets into state. */
#define GENDAC_COMMAND		0
#define SDAC_PLL_WRITEINDEX	1
#define SDAC_PLL_READINDEX	2
#define SDAC_PLL_M		3	/* f2 programmed clock */
#define SDAC_PLL_N1_N2		4
#define SDAC_PLL_CONTROL	5

#define SDAC_STATESIZE 6	/* 6 registers. */
#define GENDAC_STATESIZE 6

static int GENDAC_SDAC_probe(void) {
/* Taken from XFree86, accel/s3.c. */
/* Return 1 if GENDAC found, 2 if SDAC, 0 otherwise. */
   /* probe for S3 GENDAC or SDAC */
   /*
    * S3 GENDAC and SDAC have two fixed read only PLL clocks
    *     CLK0 f0: 25.255MHz   M-byte 0x28  N-byte 0x61
    *     CLK0 f1: 28.311MHz   M-byte 0x3d  N-byte 0x62
    * which can be used to detect GENDAC and SDAC since there is no chip-id
    * for the GENDAC.
    *
    * NOTE: for the GENDAC on a MIRO 10SD (805+GENDAC) reading PLL values
    * for CLK0 f0 and f1 always returns 0x7f (but is documented "read only")
    */
   
   unsigned char saveCR55, savelut[6];
   int i;
   long clock01, clock23;

   saveCR55 = inCR(0x55);
   outbCR(0x55, saveCR55 & ~1);

   outb(0x3c7,0);
   for(i=0; i<2*3; i++)		/* save first two LUT entries */
      savelut[i] = inb(0x3c9);
   outb(0x3c8,0);
   for(i=0; i<2*3; i++)		/* set first two LUT entries to zero */
      outb(0x3c9,0);

   outbCR(0x55, saveCR55 | 1);

   outb(0x3c7,0);
   for(i=clock01=0; i<4; i++)
      clock01 = (clock01 << 8) | (inb(0x3c9) & 0xff);
   for(i=clock23=0; i<4; i++)
      clock23 = (clock23 << 8) | (inb(0x3c9) & 0xff);

   outbCR(0x55, saveCR55 & ~1);

   outb(0x3c8,0);
   for(i=0; i<2*3; i++)		/* restore first two LUT entries */
      outb(0x3c9,savelut[i]);

   outbCR(0x55, saveCR55);

   if ( clock01 == 0x28613d62 ||
       (clock01 == 0x7f7f7f7f && clock23 != 0x7f7f7f7f)) {

      inb(0x3c8);	/* dactopel */

      inb(0x3c6);
      inb(0x3c6);
      inb(0x3c6);

      /* the forth read will show the SDAC chip ID and revision */
      if (((i=inb(0x3c6)) & 0xf0) == 0x70) {
	 return 2;	/* SDAC found. */
      }
      else {
	 return 1;	/* GENDAC found. */
      }
      inb(0x3c8);	/* dactopel */
   }
   return 0;
}

static void GENDAC_SDAC_init(void) {
	unsigned char val;
	int m, n, n1, n2, MCLK;
	val = inCR(0x55);
	outbCR(0x55, val | 0x01);

	outb(0x3C7, 10);	/* Read MCLK. */
	m = inb(0x3C9);
	n = inb(0x3C9);

	outbCR(0x55, val);	/* Restore CR55. */

	m &= 0x7f;
	n1 = n & 0x1f;
	n2 = (n >> 5) & 0x03;
	/* Calculate MCLK in kHz. */
	MCLK = 14318 * (m + 2) / (n1 + 2) / (1 << n2);
	v_printf("svgalib: S3-GENDAC/SDAC: MCLK = %d.%03d MHz\n",
		MCLK / 1000, MCLK % 1000);
}


static void GENDAC_SDAC_savestate(Bit16u *regs) {
	unsigned char tmp;
	tmp = inCR(0x55);
	outbCR(0x55, tmp | 1);

	regs[SDAC_COMMAND] = inb(0x3c6);
	regs[SDAC_PLL_WRITEINDEX] = inb(0x3c8);	/* PLL write index */
	regs[SDAC_PLL_READINDEX] = inb(0x3c7);	/* PLL read index */
	outb(0x3c7, 2);				/* index to f2 reg */
	regs[SDAC_PLL_M] = inb(0x3c9);		/* f2 PLL M divider */
	regs[SDAC_PLL_N1_N2] = inb(0x3c9);	/* f2 PLL N1/N2 divider */
	outb(0x3c7, 0x0e);			/* index to PLL control */
	regs[SDAC_PLL_CONTROL] = inb(0x3c9);	/* PLL control */

	outbCR(0x55, tmp & ~1);
}

static void GENDAC_SDAC_restorestate(Bit16u *regs) {
	unsigned char tmp;

	/* set RS2 via CR55, yuck */
	tmp = inCR(0x55) & 0xFC;
	outbCR(0x55, tmp | 0x01);

	v_printf("SDAC.restorestate, setting clock 0x%02X 0x%02X\n",
		regs[SDAC_PLL_M],
		regs[SDAC_PLL_N1_N2]);

	outb(0x3c6, regs[SDAC_COMMAND]);
	outb(0x3c8, 2);				/* index to f2 reg */
	outb(0x3c9, regs[SDAC_PLL_M]);		/* f2 PLL M divider */
	outb(0x3c9, regs[SDAC_PLL_N1_N2]);	/* f2 PLL N1/N2 divider */
	outb(0x3c8, 0x0e);			/* index to PLL control */
	outb(0x3c9, regs[SDAC_PLL_CONTROL]);	/* PLL control */
	outb(0x3c8, regs[SDAC_PLL_WRITEINDEX]);	/* PLL write index */
	outb(0x3c7, regs[SDAC_PLL_READINDEX]);	/* PLL read index */

	outbCR(0x55, tmp);
}


/* SDAC */

static int SDAC_probe(void) {
	return GENDAC_SDAC_probe() == 2;
}
 
DacMethods S3_SDAC_methods = {
	S3_SDAC,
	"S3-SDAC (86C716)",
	DAC_HAS_PROGRAMMABLE_CLOCKS,
	SDAC_probe,
	GENDAC_SDAC_init,
	GENDAC_SDAC_savestate,
	GENDAC_SDAC_restorestate,
	SDAC_STATESIZE
};


/* S3-GENDAC, 8-bit DAC. */

static int GENDAC_probe(void) {
	return GENDAC_SDAC_probe() == 1;
}

DacMethods S3_GENDAC_methods = {
	S3_GENDAC,
	"S3-GENDAC (86C708)",
	DAC_HAS_PROGRAMMABLE_CLOCKS,
	GENDAC_probe,
	GENDAC_SDAC_init,
	GENDAC_SDAC_savestate,
	GENDAC_SDAC_restorestate,
	GENDAC_STATESIZE
};


/* S3-Trio64, 16-bit integrated DAC. */

#define	TRIO64_SR15		0
#define TRIO64_SR18		1
#define TRIO64_PLL_N1_N2	2
#define TRIO64_PLL_M		3
#define TRIO64_CR67		4
#define TRIO64_STATESIZE	5

/* Note: s3.c also defines CR67, but doesn't use it for the Trio64. */

static void Trio64_init(void) {
	unsigned char sr8;
	int m, n, n1, n2, mclk;

	outb(0x3c4, 0x08);
	sr8 = inb(0x3c5);
	outb(0x3c5, 0x06);
	 
	outb(0x3c4, 0x11);
	m = inb(0x3c5);
	outb(0x3c4, 0x10);
	n = inb(0x3c5);
	 
	outb(0x3c4, 0x08);
	outb(0x3c5, sr8);

	m &= 0x7f;
	n1 = n & 0x1f;
	n2 = (n>>5) & 0x03;
	/* Calculate MCLK in kHz. */
	mclk = ((1431818 * (m+2)) / (n1+2) / (1 << n2) + 50) / 100;
	v_printf("svgalib: Trio64: MCLK = %d.%03d MHz\n",
		mclk / 1000, mclk % 1000);
}


static void Trio64_savestate(Bit16u *regs) {
	unsigned char SR8;
	outb(0x3C4, 0x08);
	SR8 = inb(0x3C5);

	regs[TRIO64_SR15] = inSR(0x15);
	regs[TRIO64_SR18] = inSR(0x18);
	regs[TRIO64_PLL_N1_N2] = inSR(0x12);
	regs[TRIO64_PLL_M] = inSR(0x13);
	regs[TRIO64_CR67] = inCR(0x67);

	outSR(0x08, SR8);
}

static void Trio64_restorestate(Bit16u *regs) {
	unsigned char SR8;

	outb(0x3C4, 0x08);
	SR8 = inb(0x3C5);
	outb(0x3C5, 0x06);	/* Unlock. */

	outCR(0x67, regs[TRIO64_CR67]);

	outSR(0x15, regs[TRIO64_SR15]);
	outSR(0x18, regs[TRIO64_SR18]);

	/* Clock. */
	outSR(0x12, regs[TRIO64_PLL_N1_N2]);
	outSR(0x13, regs[TRIO64_PLL_M]);

#if 0
	/*
	 * XFree86 XF86_S3 (common_hw/gendac.c) has this, but it looks
	 * incorrect, it should flip the bit by writing to 0x3c5, not
	 * 0x3c4.
	 */
      outb(0x3c4, 0x15);
      tmp = inb(0x3c5);
      outb(0x3c4, tmp & ~0x20);
      outb(0x3c4, tmp |  0x20);
      outb(0x3c4, tmp & ~0x20);
#endif

	outSR(0x08, SR8);
}


DacMethods Trio64_methods = {
	TRIO64,
	"S3-Trio64 internal DAC",
	DAC_HAS_PROGRAMMABLE_CLOCKS,
	NULL,	/* probe */
	Trio64_init,
	Trio64_savestate,
	Trio64_restorestate,
	TRIO64_STATESIZE
};


/*
 * RAMDAC definition for industry-standard AT&T20C498 DAC with 16-bit
 * pixel port, and compatibles.
 * Differently rated versions exist, such as 80, 110, 135 and 170 MHz.
 * This code assumes the DAC is actually connected with a 16-bit path.
 * (an example of a 498-compatible DAC being used with a 8-bit path
 * is the Hercules Stingray Pro/V with the IC Works ZoomDAC).
 */

static int att20c498_probe(void) {
	return 0;
}
 
static void att20c498_init(void) {
	v_printf("svgalib: Using AT&T20C498-compatible DAC, 80 MHz rated.\n");
}

DacMethods ATT20C498_methods = {
	ATT20C498,
	"AT&T20C498 DAC",
	0,
	att20c498_probe,
	att20c498_init,
	Sierra_32K_savestate,
	Sierra_32K_restorestate,
	1	/* State size. */
};


/*
 * RAMDAC definition for IC Works DACs.
 * This version only supports the 16-bit ZoomDAC (w30C516), which 
 * is compatible with the AT&T 20C498.
 * This DAC exists in 110, 135 and 170 MHz versions.
 * It can do packed 24-bit color (BG-RB-GR).
 * The 170 MHz version has a PCLK limit of 135 MHz
 * (170 pixel clock for 16-bit path for 8bpp LUT).
 */

static int ICW_probe(void) {
	unsigned char mi, di;
	inb(0x3C8);
	inb(0x3C6); inb(0x3C6); inb(0x3C6); inb(0x3C6);
	inb(0x3C6);	/* Control register 0. */
	mi = inb(0x3C6);	/* Manufacturer ID. */
	di = inb(0x3C6);	/* Device ID. */
	if (mi == 0x84) {
		if (di == 0x98)
			return 1;
		v_printf("svgalib: ICW_probe: Unknown IC Works DAC.\n");
	}
	return 0;
}

static void ICW_init(void) {
	v_printf("svgalib: Using IC Works DAC (AT&T20C498-compatible).\n");
}
 
DacMethods ICW_methods = {
	IC_WORKS,
	"IC Works DAC",
	0,
	ICW_probe,
	ICW_init,
	Sierra_32K_savestate,
	Sierra_32K_restorestate,
	1	/* State size. */
};


/*
 * List of all DACs.
 */

DacMethods *all_dacs[] = {
	&normal_dac_methods,
	&S3_SDAC_methods,
	&S3_GENDAC_methods,
	&Trio64_methods,
	&Sierra_32K_methods,
 	&ATT20C490_methods,
	&ATT20C498_methods,
	NULL
};
