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

/*
 * Aug 1995 (Stephen Lee):
 * Improved support for S3-801/805.
 * 15/16/24 bit colors works on the 805 + ATT20C490 I tested.
 * Newer chipsets are recognized (but no support coded in yet).
 * Should recognize memory size correctly on S3-924.
 * 
 * Dec 1994:
 * Partially rewritten using new SVGA-abstracted interface.
 * Based on XFree86 code (accel/s3/s3.c and s3init.c).
 * Goal is to have support for the S3-864 + S3-SDAC (which I can test).
 * 80x with GENDAC might also be supported.
 * Also, 640x480x256 should work on cards that have standard 25 and 28 MHz
 * clocks.
 *
 * XFree86-equivalent clock select is now supported plus some
 * industry-standard RAMDACs.
 *
 * Remaining problems:
 * * Some 864 problems are now fixed -- XF86_S3 seems to program the
 *   linewidth in bytes doubled for the S3-864 with > 1024K, which
 *   caused problems for this driver. There's still interference
 *   though when writing to video memory in the higher resolutions.
 *
 * * 256 colors doesn't work on at least one S3-805 + ATT20C490.
 */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "vga.h"
#include "libvga.h"
#include "driver.h"

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


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

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

/* Extended registers. */

#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

/* For debugging, these (non-)registers are read also (but never written). */

#define S3_CR36		VGA_TOTAL_REGS + 27
#define S3_CR37		VGA_TOTAL_REGS + 28
#define S3_CR38		VGA_TOTAL_REGS + 29
#define S3_CR39		VGA_TOTAL_REGS + 30
#define S3_CR3D		VGA_TOTAL_REGS + 31
#define S3_CR3E		VGA_TOTAL_REGS + 32
#define S3_CR3F		VGA_TOTAL_REGS + 33
#define S3_CR45		VGA_TOTAL_REGS + 34
#define S3_CR46		VGA_TOTAL_REGS + 35
#define S3_CR47		VGA_TOTAL_REGS + 36
#define S3_CR48		VGA_TOTAL_REGS + 37
#define S3_CR49		VGA_TOTAL_REGS + 38
#define S3_CR4A		VGA_TOTAL_REGS + 39
#define S3_CR4B		VGA_TOTAL_REGS + 40
#define S3_CR4C		VGA_TOTAL_REGS + 41
#define S3_CR4D		VGA_TOTAL_REGS + 42
#define S3_CR4E		VGA_TOTAL_REGS + 43
#define S3_CR4F		VGA_TOTAL_REGS + 44
#define S3_CR52		VGA_TOTAL_REGS + 45
#define S3_CR56		VGA_TOTAL_REGS + 46
#define S3_CR57		VGA_TOTAL_REGS + 47
#define S3_CR5B		VGA_TOTAL_REGS + 48
#define S3_CR5C		VGA_TOTAL_REGS + 49
#define S3_CR5F		VGA_TOTAL_REGS + 50
#define S3_CR63		VGA_TOTAL_REGS + 51
#define S3_CR64		VGA_TOTAL_REGS + 52
#define S3_CR65		VGA_TOTAL_REGS + 53
#define S3_CR66		VGA_TOTAL_REGS + 54

#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 CardSpecs *cardspecs;
static DacMethods *dac_used;

static int s3Localbus;

static int s3_init(int, int, int);

static void nothing() { }

/* Fill in chipset specific mode information */

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

static void s3_getmodeinfo( int mode, vga_modeinfo *modeinfo ) {
        switch (modeinfo->colors) {
                case 16 :       /* 4-plane 16 color mode */
                        modeinfo->maxpixels = 65536 * 8;
                        break;
                default :
                        modeinfo->maxpixels = s3_memory * 1024 /
                                        modeinfo->bytesperpixel;
                        break;
        }
	modeinfo->maxlogicalwidth = 2040;
	modeinfo->startaddressrange = 0xfffff;
	modeinfo->haveblit = 0;
	modeinfo->flags &= ~HAVE_RWPAGE;
}


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

static int s3_modeavailable( int mode ) {
	struct info *info;
	ModeInfo *modeinfo;
	ModeTiming *modetiming;

	if (mode < G640x480x256 || mode == G720x348x2)
		return vga_driverspecs.modeavailable(mode);

	/* Enough memory? */
	info = &__svgalib_infotable[mode];
	if (s3_memory * 1024 < info->ydim * info->xbytes)
		return 0;

	modeinfo = createModeInfoStructureForSvgalibMode(mode);

	modetiming = malloc(sizeof(ModeTiming));
	if (getmodetiming(modetiming, modeinfo, cardspecs)) {
		free(modetiming);
		free(modeinfo);
		return 0;
	}
	free(modetiming);
	free(modeinfo);

	return SVGADRV;
}


static int s3_saveregs( unsigned char regs[] ) {

	s3_unlock();

 	/* 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_CR40] = inCR(0x40);
 	regs[S3_CR42] = inCR(0x42);
 	regs[S3_CR43] = inCR(0x43);
 	regs[S3_CR44] = inCR(0x44);

	/* Non-registers, for debugging. */
	regs[S3_CR36] = inCR(0x36);
	regs[S3_CR37] = inCR(0x37);
	regs[S3_CR38] = inCR(0x38);
	regs[S3_CR39] = inCR(0x39);
	regs[S3_CR3D] = inCR(0x3D);
	regs[S3_CR3E] = inCR(0x3E);
	regs[S3_CR3F] = inCR(0x3F);
	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);

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

		/* Non-registers, for debugging. */
		regs[S3_CR52] = inCR(0x52);
		regs[S3_CR56] = inCR(0x56);
		regs[S3_CR57] = inCR(0x57);
		regs[S3_CR5B] = inCR(0x5B);
		regs[S3_CR5C] = inCR(0x5C);
		regs[S3_CR5F] = inCR(0x5F);
		regs[S3_CR63] = inCR(0x63);
		regs[S3_CR64] = inCR(0x64);
		regs[S3_CR65] = inCR(0x65);
		regs[S3_CR66] = inCR(0x66);
 	}

	dac_used->saveState(regs + S3_DAC_OFFSET);
	return S3_DAC_OFFSET - VGA_TOTAL_REGS + dac_used->stateSize;
}


/* Set chipset-specific registers */

static void s3_setregs( const unsigned char regs[], int mode )
{
	s3_unlock(); /* May be locked again by other programs (eg. X) */

 	/* Write 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(0x40, regs[S3_CR40]);
 	outCR(0x42, regs[S3_CR42]);
 	outCR(0x43, regs[S3_CR43]);
 	outCR(0x44, regs[S3_CR44]);
 	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]);

		if (dac_used->id != NORMAL_DAC) {
			unsigned char CR1;
			/* Blank the screen.*/
			CR1 = inCR(0x01);
			outCR(0x01, CR1 | 0x20);

			outbCR(0x55, inCR(0x55) | 1);

			outCR(0x67, regs[S3_CR67]);	/* S3 pixmux. */

			dac_used->restoreState(regs + S3_DAC_OFFSET);

			outCR(0x6D, regs[S3_CR6D]);

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

			outCR(0x01, CR1);	/* Unblank screen. */
		}
	}
}


/*
 * Initialize register state for a mode.
 */

static void s3_initializemode( unsigned char *moderegs,
ModeTiming *modetiming, ModeInfo *modeinfo) {

	/* Get current values. */
	s3_saveregs(moderegs);

	/* Set up the standard VGA registers for a generic SVGA. */
	setup_VGA_registers(moderegs, modetiming, modeinfo);

	/* Set up the extended register values, including modifications */
	/* of standard VGA registers. */

	moderegs[VGA_SR0] = 0x03;
	moderegs[VGA_CR13] = modeinfo->lineWidth >> 3;
	moderegs[VGA_CR17] = 0xE3;

	if (modeinfo->lineWidth == 2048)
		moderegs[S3_CR31] = 0x8F;
	else
		moderegs[S3_CR31] = 0x8D;
#ifdef LINEAR_MODE_BANKING_864
	if (s3_chiptype >= S3_864) {
		moderegs[S3_ENHANCEDMODE] |= 0x01;
		/* Enable enhanced memory mode. */
		moderegs[S3_CR31] |= 0x04;
		/* Enable banking via CR6A in linear mode. */
		moderegs[S3_CR31] |= 0x01;
	}
#endif
	moderegs[S3_CR32] = 0;
	moderegs[S3_CR33] = 0x20;
	moderegs[S3_CR34] = 0x10;	/* 1024 */
	moderegs[S3_CR35] = 0;
	/* Call cebank() here when setting registers. */
	moderegs[S3_CR3A] = 0xB5;

	moderegs[S3_CR3B] = (moderegs[VGA_CR0] + moderegs[VGA_CR4] + 1) / 2;
	moderegs[S3_CR3C] = moderegs[VGA_CR0] / 2;
	if (s3_chiptype == S3_911) {
		moderegs[S3_CR40] &= 0xF2;
		moderegs[S3_CR40] |= 0x09;
	}
	else if (s3Localbus) {
		moderegs[S3_CR40] &= 0xF2;
		/* Pegasus wants 0x01 for zero wait states. */
		moderegs[S3_CR40] |= 0x05;
	}
	else {
		moderegs[S3_CR40] &= 0xF6;
		moderegs[S3_CR40] |= 0x01;
	}

    if (modeinfo->bitsPerPixel >= 15) {
		/* 15/16/24/32 bit color */
		if (s3_chiptype <= S3_864)
		    moderegs[S3_CR43] = 0x08; /* XXXX does this work on a 928? */
		else
		    /* XXXX some DAC might need this; XF86 source says... */
            moderegs[S3_CR43] = 0x09;
	} else {
		/* 4/8 bit color */
		moderegs[S3_CR43] = 0;
    }

	moderegs[S3_CR44] = 0;
	/* Skip CR45, 'hi/truecolor cursor color enable'. */

	if (s3_chiptype >= S3_801) {
		/* XXXX Not all chips support all widths. */
		int width;
		moderegs[S3_CR50] &= ~0xF1;
		width = modeinfo->lineWidth;
		if (modeinfo->bitsPerPixel == 16)
			moderegs[S3_CR50] |= 0x10;
		if (modeinfo->bitsPerPixel == 32) {
			moderegs[S3_CR50] |= 0x30;
			width /= 4;
		}
		switch (modeinfo->lineWidth) {
		case 640 : moderegs[S3_CR50] |= 0x40; break;
		case 800 : moderegs[S3_CR50] |= 0x80; break;
		case 1152: moderegs[S3_CR50] |= 0x01; break;
		case 1280 : moderegs[S3_CR50] |= 0xC0; break;
		case 1600 : moderegs[S3_CR50] |= 0x81; break;
		/* 1024/2048 no change. */
		}
		moderegs[S3_CR51] &= 0xC0;
		moderegs[S3_CR51] |= (modeinfo->lineWidth >> 7) & 0x30;
		/* moderegs[S3_CR53] |= 0x10; */	/* Enable MMIO. */
		/* For S3_805i with 2MB, OR with 0x20. */

		{
		int m, n;
		n = 0xFF;
		if (s3_chiptype == S3_864) {
			/* DRAM CRT FIFO balancing. */
			int clock, mclk;
			clock = modetiming->pixelClock *
				modeinfo->bytesPerPixel;
			if (s3_memory < 2048)
				clock *= 2;
			mclk = 60000;	/* Assumption. */
			m = (int)((mclk/1000.0*.72+16.867)*89.736/(clock/1000.0+39)-21.1543);
			if (s3_memory < 2048)
				m /= 2;
			if (m > 31)
				m = 31;
			else
			if (m < 0) {
				m = 0;
				n = 16;
			}
		}
		else if (s3_memory == 512 || modetiming->HDisplay > 1200)
			m = 0;
		else if (s3_memory == 1024)
			m = 2;
		else
			m = 20;
		moderegs[S3_CR54] = m << 3;
		moderegs[S3_CR60] = n;
		}

		moderegs[S3_CR55] &= 0x08;
		moderegs[S3_CR55] |= 0x40;

		moderegs[S3_CR58] = 0;		/* s3SAM256 */
#ifdef LINEAR_MODE_BANKING_864
		if (s3_chiptype >= S3_864) {
			/* Enable linear addressing. */
			moderegs[S3_CR58] |= 0x10;
			/* Set window size to 64K. */
			moderegs[S3_CR58] &= ~0x03;
			/* Assume CR59/5A are correctly set up for 0xA0000. */
			/* Set CR6A linear bank to zero. */
			moderegs[S3_CR6A] &= ~0x3F;
		}
#endif
		moderegs[S3_CR59] = 0;
		moderegs[S3_CR5A] = 0x0A;

		/* Extended CRTC timing. */
		moderegs[S3_CR5E] =
			(((modetiming->CrtcVTotal - 2) & 0x400) >> 10) |
			(((modetiming->CrtcVDisplay - 1) & 0x400) >> 9) |
			(((modetiming->CrtcVSyncStart) & 0x400) >> 8)  |
			(((modetiming->CrtcVSyncStart) & 0x400) >> 6) | 0x40;

		{
		int i, j;
		i = ((((modetiming->CrtcHTotal >> 3) - 5) & 0x100) >> 8) |
		    ((((modetiming->CrtcHDisplay >> 3) - 1) & 0x100) >> 7) |
		    ((((modetiming->CrtcHSyncStart >> 3) - 1) & 0x100) >> 6) |
		    ((modetiming->CrtcHSyncStart & 0x800) >> 7);
		j = ((moderegs[VGA_CR0] + ((i & 0x01) << 8) +
		    moderegs[VGA_CR4] + ((i & 0x10) << 4) + 1) / 2);
		moderegs[S3_CR3B] = j & 0xFF;
		i |= (j & 0x100) >> 2;
		/* Interlace mode frame offset. */
		moderegs[S3_CR3C] = (moderegs[VGA_CR0] + ((i & 0x01) << 8)) / 2;
		moderegs[S3_CR5D] &= ~0x57;
		moderegs[S3_CR5D] |= i;
		}

		{
		int i;
#if 0	/* This seems wrong. Why doesn't XF86_S3 have problems? */
		if (s3_chiptype == S3_864 && s3_memory > 1024)
			i = modetiming->HDisplay *
				modeinfo->bytesPerPixel / 8 + 1;
		else
#endif
			i = modetiming->HDisplay *
				modeinfo->bytesPerPixel / 4 + 1;
		moderegs[S3_CR61] = (i >> 8) | 0x80;
		moderegs[S3_CR62] = i & 0xFF;
		}
	} /* 801+ */

	if (modetiming->flags & INTERLACED)
		moderegs[S3_CR42] |= 0x20;

	/*
	 * Clock select works as follows:
	 * Clocks 0 and 1 (VGA 25 and 28 MHz) can be selected via the
	 * two VGA MiscOutput clock select bits.
	 * If 0x3 is written to these bits, the selected clock index
	 * is taken from the S3 clock select register at CR42. Clock
	 * indices 0 and 1 should correspond to the VGA ones above,
	 * and 3 is often 0 MHz, followed by extended clocks for a
	 * total of mostly 16.
	 */

	if (modetiming->flags & USEPROGRCLOCK)
		moderegs[VGA_MISCOUTPUT] |= 0x0C; /* External clock select. */
	else
	if (modetiming->selectedClockNo < 2) {
		/* Program clock select bits 0 and 1. */
		moderegs[VGA_MISCOUTPUT] &= ~0x0C;
		moderegs[VGA_MISCOUTPUT] |=
			(modetiming->selectedClockNo & 3) << 2;
	}
	else
	if (modetiming->selectedClockNo >= 2) {
		moderegs[VGA_MISCOUTPUT] |= 0x0C;
		/* Program S3 clock select bits. */
		moderegs[S3_CR42] &= ~0x1F;
		moderegs[S3_CR42] |=
			modetiming->selectedClockNo;
	}

	if (s3_chiptype == S3_TRIO64) {
		moderegs[S3_CR33] &= ~0x08;
		if (modeinfo->bitsPerPixel == 16)
			moderegs[S3_CR33] |= 0x08;
		/*
		 * The rest of the DAC/clocking is setup by the
		 * Trio64 code in the RAMDAC interface (ramdac.c).
		 */
	}
	else
	if (dac_used->id != NORMAL_DAC) {
		int colormode;
		colormode = colorbits_to_colormode(modeinfo->bitsPerPixel,
			modeinfo->colorBits);
		dac_used->initializeState(&moderegs[S3_DAC_OFFSET],
			modeinfo->bitsPerPixel, colormode,
			modetiming->pixelClock);

		if (dac_used->id == ATT20C490) {
			int pixmux, invert_vclk, blank_delay;
			pixmux = 0;
			invert_vclk = 0;
			blank_delay = 2;
			if (colormode == CLUT8_6
			&& modetiming->pixelClock >= 67500) {
				pixmux = 0x00;
				invert_vclk = 1;
			}
			else if (colormode == RGB16_555) pixmux = 0xa0;
			else if (colormode == RGB16_565) pixmux = 0xc0;
			else if (colormode == RGB24_888_B) pixmux = 0xe0;
			moderegs[S3_CR67] = pixmux | invert_vclk;
			moderegs[S3_CR6D] = blank_delay;
		}
		if (dac_used->id == S3_SDAC) {
			int pixmux, invert_vclk, blank_delay;
			pixmux = 0;
			invert_vclk = 0;
			blank_delay = 0;
			if (colormode == CLUT8_6
			&& modetiming->pixelClock >= 67500) {
				pixmux = 0x10;
				invert_vclk = 1;
				blank_delay = 2;
			}
			else
			if (colormode == RGB16_555) {
				pixmux = 0x30;
				blank_delay = 2;
			}
			else
			if (colormode == RGB16_565) {
				pixmux = 0x50;
				blank_delay = 2;
			}
			else
			if (colormode == RGB32_888_B) {
				pixmux = 0x70;
				blank_delay = 2;
			}
			moderegs[S3_CR67] = pixmux | invert_vclk;
			moderegs[S3_CR6D] = blank_delay;
			/* Clock select. */
			moderegs[S3_CR42] &= ~0x0F;
			moderegs[S3_CR42] |= 0x02;
		}
	}
}


/* Set a mode */

static int s3_setmode( int mode, int prv_mode ) {
	ModeInfo *modeinfo;
	ModeTiming *modetiming;
	unsigned char *moderegs;

	if (mode < G640x480x256 || mode == G720x348x2)
		/* Let the standard VGA driver set standard VGA modes. */
		return vga_driverspecs.setmode(mode, prv_mode);
	if (!s3_modeavailable(mode))
		return 1;

	modeinfo = createModeInfoStructureForSvgalibMode(mode);

	modetiming = malloc(sizeof(ModeTiming));
	if (getmodetiming(modetiming, modeinfo, cardspecs)) {
		free(modetiming);
		free(modeinfo);
		return 1;
	}

	/* Adjust the display width. */
	if (s3_chiptype < S3_801)
		modeinfo->lineWidth = 1024;
	else {
		if (modeinfo->lineWidth <= 640)
			modeinfo->lineWidth = 640;
		else
		if (modeinfo->lineWidth <= 800)
			modeinfo->lineWidth = 800;
		else
		if (modeinfo->lineWidth <= 1024)
			modeinfo->lineWidth = 1024;
		else
		if (modeinfo->lineWidth <= 1152)
			modeinfo->lineWidth = 1152;
		else
		if (modeinfo->lineWidth <= 1280)
			modeinfo->lineWidth = 1280;
		else
		if (modeinfo->lineWidth <= 1600)
			modeinfo->lineWidth = 1600;
	}

	moderegs = malloc(S3_TOTAL_REGS);

	s3_initializemode(moderegs, modetiming, modeinfo);
	free(modeinfo);
	free(modetiming);

	__vga_setregs(moderegs);	/* Set standard regs. */
	s3_setregs(moderegs, mode);	/* Set extended regs. */
	free(moderegs);
	return 0;
}


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

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

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

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

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

static int s3_test()
{
	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 0;

	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 0;

	if (s3_init(0, 0, 0))	/* type not OK */
		return 0;
	return 1;
}


/* Bank switching function - set 64K bank number */

static void s3_setpage( int page ) {
#ifdef LINEAR_MODE_BANKING_864
	if (s3_chiptype >= S3_864) {
		/* "Linear" mode banking. */
		outb(0x3D4, 0x6A);
		outb(0x3D5, (inb(0x3D5) & ~0x3F) | page);
		return;
	}
#endif
	outb(0x3d4, 0x35);
	outb(0x3d5, (inb(0x3d5) & 0xF0) | (page & 0x0F));
	outb(0x3d4, 0x51);
	outb(0x3d5, (inb(0x3d5) & ~0x0C) | ((page & 0x30) >> 2));
}


/* Set display start address (not for 16 color modes) */

/* This works up to 1Mb (should be able to go higher). */
static void s3_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, 0x31);
	/* XXXX This destroys bits for 2048 linewidth. */
	outb(0x3d5, ((address & 0xc0000) >> 14) | 0x8d);
}


/* Set logical scanline length (usually multiple of 8) */
/* Multiples of 8 to 2040 */

static void s3_setlogicalwidth( int width ) { 
	outw(0x3d4, 0x13 + (width >> 3) * 256);	/* lw3-lw11 */
}


/* Function table (exported) */

DriverSpecs s3_driverspecs = {
	s3_saveregs,		/* saveregs */
	s3_setregs,		/* setregs */
	(void (*)()) nothing,	/* unlock */
	(void (*)()) nothing,	/* lock */
	s3_test,
	s3_init,
	s3_setpage,
	(void (*)()) nothing,
	(void (*)()) nothing,
	s3_setmode,
	s3_modeavailable,
	s3_setdisplaystart,
	s3_setlogicalwidth,
	s3_getmodeinfo,
	0,	/* bitblt */
	0,	/* imageblt */
	0,	/* fillblt */
	0,	/* hlinelistblt */
	0,	/* bltwait */
	0,	/* extset */
	0,	/* accel */
	0,	/* linear */
	NULL	/* accelspecs */
};


/* S3-specific config file options. */

/*
 * Currently this only handles Clocks. It would a good idea to have
 * higher-level code process options like Clocks that are valid for
 * more than one driver driver (with better error detection etc.).
 */

static char *s3_config_options[] = {
	"clocks", "ramdac", NULL
};

static char *s3_process_option( int option, int mode ) {
/*
 * option is the number of the option string in s3_config_options,
 * mode seems to be a 'hardness' indicator for security.
 */
	if (option == 0) {	/* "Clocks" */
		/* Process at most 16 specified clocks. */
		cardspecs->clocks = malloc(sizeof(int) * 16);
		/* cardspecs->nClocks should be already be 0. */
		for (;;) {
			char *ptr;
			int freq;
			ptr = strtok(NULL, " ");
			if (ptr == NULL)
				break;
			/*
			 * This doesn't protect against bad characters
			 * (atof() doesn't detect errors).
			 */
			freq = atof(ptr) * 1000;
			cardspecs->clocks[cardspecs->nClocks] = freq;
			cardspecs->nClocks++;
			if (cardspecs->nClocks == 16)
				break;
		}
	}
	if (option == 1) {	/* "Ramdac" */
		char *ptr;
		ptr = strtok(NULL, " ");
		if (strcasecmp(ptr, "Sierra32K") == 0)
			dac_used = &Sierra_32K_methods;
		if (strcasecmp(ptr, "SDAC") == 0)
			dac_used = &S3_SDAC_methods;
		if (strcasecmp(ptr, "GenDAC") == 0)
			dac_used = &S3_GENDAC_methods;
		if (strcasecmp(ptr, "ATT20C490") == 0)
			dac_used = &ATT20C490_methods;
		if (strcasecmp(ptr, "ATT20C498") == 0)
			dac_used = &ATT20C498_methods;
	}
	return strtok(NULL, " ");
}


/* Initialize driver (called after detection) */
/* Derived from XFree86 SuperProbe and s3 driver. */

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

static int s3_init( int force, int par1, int par2) {
	int id, rev, tmp, config;

	s3_unlock();

	if (force) {
		s3_chiptype = par1;	/* we already know the type */
		s3_memory = par2;
	}
	else {
		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 & 0xf0) {
		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 0xE0 :
			switch (id & 0xFFF0) {
			case 0x10E0 :
				s3_chiptype = S3_TRIO32; break;
			case 0x11E0 :
				s3_chiptype = S3_TRIO64; break;
			case 0x80E0 :
				s3_chiptype = S3_866; break;
			case 0x90E0 :
				s3_chiptype = S3_868; break;
			case 0xF0E0 : /* XXXX From data book; XF86 says 0xB0E0? */
				s3_chiptype = S3_968; break;
			}
		}
		if (s3_chiptype == -1) {
			printf("svgalib: S3: Unknown chip id %02x\n",
				id);
			return -1;
		}

		outb(0x3d4, 0x36);	/* for register CR36 (CONFG_REG1), */
		config = inCR(0x36);	/* get configuration info */

		if (s3_chiptype <= S3_924) {
			if ((config & 0x20) != 0)
			    s3_memory = 512;
			else
				s3_memory = 1024;
		} else {
			/* look at bits 5, 6 and 7 */
			switch ((config & 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;
			case 7 : s3_memory = 512; break;  /* Trio32 */
			}
		}

		s3Localbus = ((config & 0x03) < 3); /* XXXX 928P is ignored */
	}

#if 0
	if (s3_chiptype != S3_864) {
		printf("svgalib: s3: Sorry, only S3-864 is currently supported.\n");
		return -1;
	}
#endif

 
/* begin: Initialize cardspecs. */
	cardspecs = malloc(sizeof(CardSpecs));
	cardspecs->videoMemory = s3_memory;
	cardspecs->nClocks = 0;
	cardspecs->maxHorizontalCrtc = 4088;
	cardspecs->flags = INTERLACE_DIVIDE_VERT;

	/* Process S3-specific config file options. */
	__svgalib_read_options(s3_config_options, s3_process_option);

 	if (s3_chiptype == S3_TRIO64 && dac_used == NULL) {
 		dac_used = &Trio64_methods;
 		dac_used->initialize();
 	}

 	if (dac_used == NULL)
 		dac_used = probeDacs(dacs_to_probe);

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

	dac_used->qualifyCardSpecs(cardspecs);

	/* Initialize standard clocks for unknown DAC. */
	if ((!(cardspecs->flags & CLOCK_PROGRAMMABLE))
	&& cardspecs->nClocks == 0) {
		/*
		 * Almost all cards have 25 and 28 MHz on VGA clocks 0 and 1,
		 * so use these for an unknown DAC, yielding 640x480x256.
		 */
		cardspecs->nClocks = 2;
		cardspecs->clocks = malloc(sizeof(int) * 2);
		cardspecs->clocks[0] = 25175;
		cardspecs->clocks[1] = 28322;
	}

	/* Limit pixel clocks according to chip specifications. */
	if (s3_chiptype == S3_864) {
		/* Limit max clocks according to 95 MHz DCLK spec. */
		LIMIT(cardspecs->maxPixelClock4bpp, 95000);
		LIMIT(cardspecs->maxPixelClock8bpp, 95000 * 2);
		LIMIT(cardspecs->maxPixelClock16bpp, 95000);
		/*
		 * The official 32bpp limit is 47500, but we allow
		 * 50 MHz for VESA 800x600 timing (actually the
		 * S3-864 doesn't have the horizontal timing range
		 * to run unmodified VESA 800x600 72 Hz timings).
		 */
		LIMIT(cardspecs->maxPixelClock32bpp, 50000);
	}
	cardspecs->maxPixelClock4bpp = 0;	/* 16-color doesn't work. */
/* end: Initialize cardspecs. */

	if (__svgalib_driver_report) {
		printf("Using S3 driver (%s, %dK).\n", s3_chipname[s3_chiptype],
			s3_memory);
	}
#if 0
	if (getenv("SVGALIB_TRY_S3") == NULL) {
		printf("The S3 driver in svgalib probably doesn't work. If you want to try it anyway,\n"
		       "define the environment variable SVGALIB_TRY_S3.\n");
		return 1;
	}
#endif
	driverspecs = &s3_driverspecs;

	return 0;
}
