
/*
 * This file contains RAMDAC definitions of type DacMethods for
 * various DACs.
 *
 * Note that the restoreState function is the only function that
 * should program the DAC registers; the initializeState function
 * should merely define the values that will be written in a
 * subsequent call of the restore funtion.
 */

#include <stdlib.h>
#include <stdio.h>
#include "libvga.h"

#include "timing.h"
#include "vgaregs.h"
#include "ramdac.h"


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

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

int normal_dac_map_clock( int bpp, int pixelclock ) {
	return pixelclock;
}

int normal_dac_map_horizontal_crtc( int bpp, int pixelclock, int htiming ) {
	return htiming;
}

void normal_dac_savestate( unsigned char *regs ) {
}

void normal_dac_restorestate( const unsigned char *regs ) {
}

void normal_dac_initializestate( unsigned char *regs, int bpp, int colormode,
int pixelclock ) {
	/* Nothing to do. */
}

void normal_dac_qualify_cardspecs( CardSpecs *cardspecs ) {
	cardspecs->maxPixelClock4bpp = 80000;
	cardspecs->maxPixelClock8bpp = 80000;
	cardspecs->maxPixelClock16bpp = 0;
	cardspecs->maxPixelClock24bpp = 0;
	cardspecs->maxPixelClock32bpp = 0;
	cardspecs->mapClock = normal_dac_map_clock;
	cardspecs->mapHorizontalCrtc = normal_dac_map_horizontal_crtc;
}

DacMethods normal_dac_methods = {
	NORMAL_DAC,
	"Normal VGA DAC",
	0,
	normal_dac_probe,
	normal_dac_init,
	normal_dac_qualify_cardspecs,
	normal_dac_savestate,
	normal_dac_restorestate,
	normal_dac_initializestate,
	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.
 */

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

int Sierra_32K_map_clock( int bpp, int pixelclock ) {
	if (bpp == 16)
		return pixelclock * 2;
	return pixelclock;
}

int Sierra_32K_map_horizontal_crtc( int bpp, int pixelclock, int htiming ) {
	if (bpp == 16)
		return htiming * 2;
	return htiming;
}

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

void Sierra_32K_restorestate( const unsigned char *regs ) {
	inb(0x3C8);
    	inb(0x3C6); inb(0x3C6); inb(0x3C6); inb(0x3C6);
    	outb(0x3C6, regs[0]);
}

void Sierra_32K_initializestate( unsigned char *regs, int bpp, int colormode,
int pixelclock ) {
	regs[0] = 0;
	if (colormode == RGB16_555)
		regs[0] = 0x80;
}

void Sierra_32K_qualify_cardspecs( CardSpecs *cardspecs ) {
	cardspecs->maxPixelClock4bpp = 80000;
	cardspecs->maxPixelClock8bpp = 80000;
	cardspecs->maxPixelClock16bpp = 40000;
	cardspecs->maxPixelClock24bpp = 0;
	cardspecs->maxPixelClock32bpp = 0;
	cardspecs->mapClock = Sierra_32K_map_clock;
	cardspecs->mapHorizontalCrtc = Sierra_32K_map_horizontal_crtc;
	cardspecs->flags |= NO_RGB16_565;
}

DacMethods Sierra_32K_methods = {
	SIERRA_32K,
	"Sierra 32K colors VGA DAC",
	0,
	Sierra_32K_probe,
	Sierra_32K_init,
	Sierra_32K_qualify_cardspecs,
	Sierra_32K_savestate,
	Sierra_32K_restorestate,
	Sierra_32K_initializestate,
	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() {
	inb(0x3C8);
	inb(0x3C6);
	inb(0x3C6);
	inb(0x3C6);
	inb(0x3C6);
}

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

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

static int att20c490_probe() {
	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;
}
 
 
void att20c490_init() {
	printf("svgalib: Using AT&T20C490-compatible truecolor DAC.\n");
#if 0
	dactocomm();
	inb(0x3C6);	/* Skip command register. */
	printf("svgalib: DAC Manufacturer ID = 0x%02X, ", inb(0x3C6));
	printf("Device ID = 0x%02X.\n", inb(0x3C6));
#endif
}

int att20c490_map_clock( int bpp, int pixelclock ) {
	if (bpp == 16)
		return pixelclock * 2;
	if (bpp == 24)
		return pixelclock * 3;
	return pixelclock;
}

int att20c490_map_horizontal_crtc( int bpp, int pixelclock, int htiming ) {
	if (bpp == 16)
		return htiming * 2;
	if (bpp == 24)
		return htiming * 3;
	return htiming;
}


void att20c490_initializestate( unsigned char *regs, int bpp, int colormode,
int pixelclock ) {
	regs[0] = 0;
	if (colormode == RGB16_555)
		regs[0] = 0xA0;
	if (colormode == RGB16_565)
		regs[0] = 0xC0;
	if (colormode == RGB24_888_B)
		regs[0] = 0xE0;
}

void att20c490_qualify_cardspecs( CardSpecs *cardspecs ) {
	/* Assume 80 MHz version to be safe. */
	cardspecs->maxPixelClock4bpp = 80000;
	cardspecs->maxPixelClock8bpp = 80000;
	cardspecs->maxPixelClock16bpp = 80000 / 2;
	cardspecs->maxPixelClock24bpp = 80000 / 3;
	cardspecs->maxPixelClock32bpp = 0;
	cardspecs->mapClock = att20c490_map_clock;
	cardspecs->mapHorizontalCrtc = att20c490_map_horizontal_crtc;
}

DacMethods ATT20C490_methods = {
	ATT20C490,
	"AT&T-compatible truecolor DAC, 80 MHz rated",
	0,
	att20c490_probe,
	att20c490_init,
	att20c490_qualify_cardspecs,
	Sierra_32K_savestate,
	Sierra_32K_restorestate,
	att20c490_initializestate,
	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



int GENDAC_SDAC_probe() {
/* 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;
}

void GENDAC_SDAC_init() {
	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);
	printf("svgalib: S3-GENDAC/SDAC: MCLK = %d.%03d MHz\n",
		MCLK / 1000, MCLK % 1000);
}


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

#include <math.h>

#define GENDAC_INDEX	     0x3C8
#define GENDAC_DATA	     0x3C9
#define BASE_FREQ	     14.31818   /* MHz */


static void
S3gendacFindClock(int freq, int min_n2, int freq_min, int freq_max,
int *best_m_out, int *best_n1_out, int *best_n2_out)
{
   double ffreq, ffreq_min, ffreq_max;
   double div, diff, best_diff;
   unsigned int m;
   unsigned char n, n1, n2;
   unsigned char best_n1=16+2, best_n2=2, best_m=125+2;

   ffreq     = freq     / 1000.0 / BASE_FREQ;
   ffreq_min = freq_min / 1000.0 / BASE_FREQ;
   ffreq_max = freq_max / 1000.0 / BASE_FREQ;

   if (ffreq < ffreq_min/8) {
      printf("invalid frequency %1.3f MHz  [freq >= %1.3f MHz]\n", 
	     ffreq*BASE_FREQ, ffreq_min*BASE_FREQ/8);
      ffreq = ffreq_min/8;
   }
   if (ffreq > ffreq_max / (1<<min_n2)) {
      printf("invalid frequency %1.3f MHz  [freq <= %1.3f MHz]\n", 
	     ffreq*BASE_FREQ, ffreq_max*BASE_FREQ / (1<<min_n2));
      ffreq = ffreq_max / (1<<min_n2);
   }

   /* work out suitable timings */

   best_diff = ffreq;
   
   for (n2=min_n2; n2<=3; n2++) {
      for (n1 = 1+2; n1 <= 31+2; n1++) {
	 m = (int)(ffreq * n1 * (1<<n2) + 0.5) ;
	 if (m < 1+2 || m > 127+2) 
	    continue;
	 div = (double)(m) / (double)(n1);	 
	 if ((div >= ffreq_min) &&
	     (div <= ffreq_max)) {
	    diff = ffreq - div / (1<<n2);
	    if (diff < 0.0) 
	       diff = -diff;
	    if (diff < best_diff) {
	       best_diff = diff;
	       best_m    = m;
	       best_n1   = n1;
	       best_n2   = n2;
	    }
	 }
      }
   }
   
#if 0
   ErrorF("clk %d, setting to %1.6f MHz (m %d, n1 %d, n2 %d)\n", clk,
	  ((double)(best_m) / (double)(best_n1) / (1 << best_n2)) * BASE_FREQ
	  ,best_m-2 ,best_n1-2 ,best_n2
	  );
#endif

   n = (best_n1 - 2) | (best_n2 << 5);
   m = best_m - 2;

   *best_m_out = best_m;
   *best_n1_out = best_n1;
   *best_n2_out = best_n2;
}

int SDAC_GENDAC_match_programmable_clock( int desiredclock ) {
	int min_m, min_n1, n2;
	S3gendacFindClock(desiredclock, 0, 100000, 250000,
		&min_m, &min_n1, &n2);
/*
	For ICS5342, min_n2 parameter should be one.
	For Trio, min_n2 = 0, freq_min = 130000 and freq_max = 270000
*/
	 
	return ((float)(min_m) / (float)(min_n1) / (1 << n2))
		* BASE_FREQ;
}

int Trio64_match_programmable_clock( int desiredclock ) {
	int min_m, min_n1, n2;
	S3gendacFindClock(desiredclock, 0, 130000, 270000,
		&min_m, &min_n1, &n2);
	 
	return ((float)(min_m) / (float)(min_n1) / (1 << n2))
		* BASE_FREQ;
}


#if 0	/* Retain for reference. */
static void
setdacpll(reg, data1, data2)
int reg;
unsigned char data1;
unsigned char data2;
{
   unsigned char tmp, tmp1;
   int vgaCRIndex = vgaIOBase + 4;
   int vgaCRReg = vgaIOBase + 5;
		
   /* set RS2 via CR55, yuck */
   tmp = inCR(0x55) & 0xFC;
   outCR(tmp | 0x01);
   tmp1 = inb(GENDAC_INDEX);

   outb(GENDAC_INDEX, reg);
   outb(GENDAC_DATA, data1);
   outb(GENDAC_DATA, data2);

   /* Now clean up our mess */
   outb(GENDAC_INDEX, tmp1);  
   outbCR(0x55, tmp);
}
#endif

void SDAC_GENDAC_initialize_clock_state(unsigned char *regs, int freq ) {
	int min_m, min_n1, n2;
	int n, m;
	S3gendacFindClock(freq, 0, 100000, 250000, &min_m, &min_n1, &n2);
	n = (min_n1 - 2) | (n2 << 5);
	m = min_m - 2;
	regs[SDAC_PLL_M] = m;
	regs[SDAC_PLL_N1_N2] = n;
	printf("Initializing DAC values; 0x%02X, 0x%02X.\n", m, n);
}

void GENDAC_SDAC_savestate( unsigned char *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);
}

void GENDAC_SDAC_restorestate( const unsigned char *regs ) {
	unsigned char tmp;

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

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

int SDAC_probe() {
	return GENDAC_SDAC_probe() == 2;
}
 
int SDAC_map_clock( int bpp, int pixelclock ) {
	if (bpp == 8 && pixelclock >= 67500)
		/* Use pixel multiplexing. */
		return pixelclock / 2;
	if (bpp == 32)
		return pixelclock * 2;
	return pixelclock;
}

int SDAC_map_horizontal_crtc( int bpp, int pixelclock, int htiming ) {
	if (bpp == 16)
		return htiming * 2;
	if (bpp == 32)
		return htiming * 4;
	return htiming;
}

void SDAC_initializestate( unsigned char *regs, int bpp, int colormode,
int pixelclock ) {
	int pixmux;		/* SDAC command register. */
	pixmux = 0;
	if (colormode == CLUT8_6 && pixelclock >= 67500)
		pixmux = 0x10;
	else
	if (colormode == RGB16_555)
		pixmux = 0x30;
	else
	if (colormode == RGB16_565)
		pixmux = 0x50;
	else
	if (colormode == RGB32_888_B)
		pixmux = 0x70;
	regs[SDAC_COMMAND] = pixmux;
	SDAC_GENDAC_initialize_clock_state(regs,
		SDAC_map_clock(bpp, pixelclock));
}

void SDAC_qualify_cardspecs( CardSpecs *cardspecs ) {
	/* Conservative values for 110 MHz version. */
	cardspecs->maxPixelClock4bpp = 110000;
	cardspecs->maxPixelClock8bpp = 110000;	/* Most can do 135 MHz. */
	cardspecs->maxPixelClock16bpp = 110000;	/* Raw clock limit. */
	cardspecs->maxPixelClock24bpp = 0;	/* How to program? */
	cardspecs->maxPixelClock32bpp = 110000 / 2;
	cardspecs->mapClock = SDAC_map_clock;
	cardspecs->matchProgrammableClock = SDAC_GENDAC_match_programmable_clock;
	cardspecs->mapHorizontalCrtc = SDAC_map_horizontal_crtc;
	cardspecs->flags |= CLOCK_PROGRAMMABLE;
}

DacMethods S3_SDAC_methods = {
	S3_SDAC,
	"S3-SDAC (86C716)",
	DAC_HAS_PROGRAMMABLE_CLOCKS,
	SDAC_probe,
	GENDAC_SDAC_init,
	SDAC_qualify_cardspecs,
	GENDAC_SDAC_savestate,
	GENDAC_SDAC_restorestate,
	SDAC_initializestate,
	SDAC_STATESIZE
};


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

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

int GENDAC_map_clock( int bpp, int pixelclock ) {
	if (bpp == 16)
		return pixelclock * 2;
	if (bpp == 32)
		return pixelclock * 4;
	return pixelclock;
}

int GENDAC_map_horizontal_crtc( int bpp, int pixelclock, int htiming ) {
	/* XXXX Not sure. */
	if (bpp == 24)
		return htiming * 3;
	if (bpp == 16)
		return htiming * 2;
	return htiming;
}

void GENDAC_initializestate( unsigned char *regs, int bpp, int colormode,
int pixelclock ) {
	int daccomm;		/* DAC command register. */
	daccomm = 0;
	if (colormode == RGB16_555)
		daccomm = 0x20;
	else
	if (colormode == RGB16_565)
		daccomm = 0x60;
	else
	if (colormode == RGB32_888_B)
		daccomm = 0x40;
	regs[GENDAC_COMMAND] = daccomm;
	SDAC_GENDAC_initialize_clock_state(regs,
		GENDAC_map_clock(bpp, pixelclock));
}

void GENDAC_qualify_cardspecs( CardSpecs *cardspecs ) {
	/* Conservative values for 110 MHz version. */
	cardspecs->maxPixelClock4bpp = 110000;
	cardspecs->maxPixelClock8bpp = 110000;
	cardspecs->maxPixelClock16bpp = 110000 / 2;
	cardspecs->maxPixelClock24bpp = 0;		/* How to program? */
	cardspecs->maxPixelClock32bpp = 0;
	cardspecs->mapClock = GENDAC_map_clock;
	cardspecs->matchProgrammableClock = SDAC_GENDAC_match_programmable_clock;
	cardspecs->mapHorizontalCrtc = GENDAC_map_horizontal_crtc;
	cardspecs->flags |= CLOCK_PROGRAMMABLE;
}

DacMethods S3_GENDAC_methods = {
	S3_GENDAC,
	"S3-GENDAC (86C708)",
	DAC_HAS_PROGRAMMABLE_CLOCKS,
	GENDAC_probe,
	GENDAC_SDAC_init,
	GENDAC_qualify_cardspecs,
	GENDAC_SDAC_savestate,
	GENDAC_SDAC_restorestate,
	GENDAC_initializestate,
	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. */

void Trio64_init() {
	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;
	printf("svgalib: Trio64: MCLK = %d.%03d MHz\n",
		mclk / 1000, mclk % 1000);
}


int Trio64_map_clock( int bpp, int pixelclock ) {
	if (bpp == 8 && pixelclock >= 80000)
		return pixelclock / 2;
	if (bpp == 32)
		return pixelclock * 2;
	return pixelclock;
}

int Trio64_map_horizontal_crtc( int bpp, int pixelclock, int htiming ) {
	if (bpp == 16)
		return htiming * 2;
	/* Normal mapping for 8bpp and 32bpp. */
	return htiming;
}

void Trio64_initialize_clock_state(unsigned char *regs, int freq ) {
	int min_m, min_n1, n2;
	int n, m;
	S3gendacFindClock(freq, 0, 130000, 270000, &min_m, &min_n1, &n2);
	n = (min_n1 - 2) | (n2 << 5);
	m = min_m - 2;
	regs[TRIO64_PLL_M] = m;
	regs[TRIO64_PLL_N1_N2] = n;
}

void Trio64_initializestate( unsigned char *regs, int bpp, int colormode,
int pixelclock ) {
	int pixmux, invert_vclk;
	regs[TRIO64_SR15] &= ~0x10;
	regs[TRIO64_SR18] &= ~0x80;
	pixmux = 0;
	invert_vclk = 0;
	if (colormode == CLUT8_6 && pixelclock >= 80000) {
		pixmux = 0x10;
		invert_vclk = 2;
		regs[TRIO64_SR15] |= 0x10;
		regs[TRIO64_SR18] |= 0x80;
	}
	else
	if (bpp == 16) {
		/* moderegs[S3_CR33] |= 0x08; */ /* done in s3.c. */
		if (colormode == RGB16_555)
			pixmux = 0x30;
		else
			pixmux = 0x50;
	}
	if (colormode == RGB32_888_B) {
		pixmux = 0xD0;	/* 32-bit color, 2 VCLKs/pixel. */
	}
	regs[TRIO64_CR67] = pixmux | invert_vclk;

	Trio64_initialize_clock_state(regs, pixelclock);
}

void Trio64_savestate( unsigned char *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);
}

void Trio64_restorestate( const unsigned char *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);
}


void Trio64_qualify_cardspecs( CardSpecs *cardspecs ) {
	cardspecs->maxPixelClock4bpp = 80000;
	cardspecs->maxPixelClock8bpp = 135000;
	cardspecs->maxPixelClock16bpp = 80000;
	cardspecs->maxPixelClock24bpp = 0;		/* How to program? */
	cardspecs->maxPixelClock32bpp = 50000;
	cardspecs->mapClock = Trio64_map_clock;
	cardspecs->matchProgrammableClock = Trio64_match_programmable_clock;
	cardspecs->mapHorizontalCrtc = Trio64_map_horizontal_crtc;
	cardspecs->flags |= CLOCK_PROGRAMMABLE;
}

DacMethods Trio64_methods = {
	TRIO64,
	"S3-Trio64 internal DAC",
	DAC_HAS_PROGRAMMABLE_CLOCKS,
	NULL,	/* probe */
	Trio64_init,
	Trio64_qualify_cardspecs,
	Trio64_savestate,
	Trio64_restorestate,
	Trio64_initializestate,
	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() {
	return 0;
}
 
static void att20c498_init() {
	printf("svgalib: Using AT&T20C498-compatible DAC, 80 MHz rated.\n");
}

static int att20c498_map_clock( int bpp, int pixelclock ) {
	if (bpp == 8 && pixelclock > 80000)
		/* Use 16-bit path, clock doubling at RAMDAC. */
		return pixelclock / 2;
	if (bpp == 16)
		return pixelclock;
	if (bpp == 32)
		return pixelclock * 2;
	return pixelclock;
}

static int att20c498_map_horizontal_crtc( int bpp, int pixelclock, int htiming ) {
	/* Not sure. */
	if (bpp == 8 && pixelclock > 80000)
		/* Use 16-bit path, clock doubling at RAMDAC. */
		return htiming / 2;
	if (bpp == 32)
		return htiming * 2;
	return htiming;
}

static void att20c498_initializestate( unsigned char *regs, int bpp, int colormode,
int pixelclock ) {
	regs[0] = 0;
	if (colormode == CLUT8_8)
		regs[0] = 0x02;
	if (colormode == RGB16_555)
		regs[0] = 0x10;
	if (colormode == RGB16_565)
		regs[0] = 0x30;
	if (colormode == RGB32_888_B)
		regs[0] = 0x50;
}

static void att20c498_qualify_cardspecs( CardSpecs *cardspecs ) {
	/* Based on 110 MHz rated version. */
	cardspecs->maxPixelClock4bpp = 0;
	cardspecs->maxPixelClock8bpp = 110000;
	cardspecs->maxPixelClock16bpp = 110000;
	cardspecs->maxPixelClock24bpp = 0;
	cardspecs->maxPixelClock32bpp = 110000 / 2;
	cardspecs->mapClock = att20c498_map_clock;
	cardspecs->mapHorizontalCrtc = att20c498_map_horizontal_crtc;
}

DacMethods ATT20C498_methods = {
	ATT20C498,
	"AT&T20C498 DAC",
	0,
	att20c498_probe,
	att20c498_init,
	att20c498_qualify_cardspecs,
	Sierra_32K_savestate,
	Sierra_32K_restorestate,
	att20c498_initializestate,
	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() {
	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;
		printf("svgalib: ICW_probe: Unknown IC Works DAC.\n");
	}
	return 0;
}

static void ICW_init() {
	printf("svgalib: Using IC Works DAC (AT&T20C498-compatible).\n");
}
 
static int ICW_map_clock( int bpp, int pixelclock ) {
	if (bpp == 8 && pixelclock > 80000)
		/* Use 16-bit path, clock doubling at RAMDAC. */
		return pixelclock / 2;
	if (bpp == 16)
		return pixelclock;
	if (bpp == 24)
		/* Use the packed 24-bit mode. */
		return pixelclock * 3 / 2;
	if (bpp == 32)
		return pixelclock * 2;
	return pixelclock;
}

static int ICW_map_horizontal_crtc( int bpp, int pixelclock, int htiming ) {
	/* Not sure. */
	if (bpp == 8 && pixelclock > 80000)
		/* Use 16-bit path, clock doubling at RAMDAC. */
		return htiming / 2;
	if (bpp == 24)
		return htiming * 3 / 2;
	if (bpp == 32)
		return htiming * 2;
	return htiming;
}

static void ICW_initializestate( unsigned char *regs, int bpp, int colormode,
int pixelclock ) {
	regs[0] = 0;
	if (colormode == CLUT8_8)
		regs[0] = 0x02;
	if (colormode == RGB16_555)
		regs[0] = 0x10;
	if (colormode == RGB16_565)
		regs[0] = 0x30;
	if (colormode == RGB24_888_B)
		/* Packed mode. */
		regs[0] = 0xB0;
	if (colormode == RGB32_888_B)
		regs[0] = 0x50;
}

static void ICW_qualify_cardspecs( CardSpecs *cardspecs ) {
	/* Based on 110 MHz rated version. */
	cardspecs->maxPixelClock4bpp = 0;
	cardspecs->maxPixelClock8bpp = 110000;
	cardspecs->maxPixelClock16bpp = 110000;
	cardspecs->maxPixelClock24bpp = 110000 * 2 / 3;
	cardspecs->maxPixelClock32bpp = 110000 / 2;
	cardspecs->mapClock = ICW_map_clock;
	cardspecs->mapHorizontalCrtc = ICW_map_horizontal_crtc;
}

DacMethods ICW_methods = {
	IC_WORKS,
	"IC Works DAC",
	0,
	ICW_probe,
	ICW_init,
	ICW_qualify_cardspecs,
	Sierra_32K_savestate,
	Sierra_32K_restorestate,
	ICW_initializestate,
	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
};
