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

/*
	Initial S3 911 driver. Doesn't work.
	Untested. Supports 640x480x256, 800x600x256, 1024x768x256, and
	possibly 640x480x64K.

	I don't know if any 924, 801, 805 and 928 should work with this.
	Probably need to support whole range of DACs/clock chips.
	Providing support for 24-bit truecolor modes would be useful.
	Doesn't use saveregs/setregs.
	Uses __svgalib_monitortype properly.

	If it is seems to do at least some things right, do report!
	Also, SVPMI may be a route to get S3 cards working -- sort of
	pseudo C code setting all registers. Check it out, and report.
*/


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


/* Standard VGA ATC/Graphics/Sequencer registers */
static const unsigned char generic_STDVGA[36] = {
	/* ATC */
	0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,
	0x0c,0x0d,0x0e,0x0f,0x01,0x00,0x0f,0x00,0x00,
	/* Graphics */
	0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x0f,0xff,
	/* Sequencer */
	0x01,0x01,0x0f,0x02,0x0e,
	/* Misc. output */
	0xE3
};

/* 800x600x256, 56/60/72Hz */
static const unsigned char g800x600x256_CRTCVGA[24] = {
	/* CRTC */
	0x7F,0x63,0x64,0x82,0x66,0x00,0x8E,0xF0,0x00,0x60,0x00,0x00,
	0x00,0x00,0x00,0x00,0x66,0x8C,0x57,0x80,0x00,0x63,0x80,0xE3,
};

/* 1024x768x256, 47Hz interlaced */
static const unsigned char g1024x768x256i_CRTCVGA[24] = {
	/* CRTC */
	0x99,0x7F,0x7F,0x9C,0x83,0x19,0x97,0x1F,0x00,0x40,0x00,0x00,
	0x00,0x00,0x00,0x00,0x81,0x83,0x7F,0x80,0x00,0x80,0x96,0xE3,
};

/* 1024x768x256, 60/72Hz */
static const unsigned char g1024x768x256_CRTCVGA[24] = {
	/* CRTC */
	0xA1,0x7F,0x80,0x84,0x83,0x94,0x24,0xFD,0x00,0x60,0x00,0x00,
	0x00,0x00,0x00,0x00,0x04,0x8A,0xFF,0x80,0x60,0x02,0x22,0xAB,
};

/* 640x480x256/64K */
/* Taken from Cirrus driver. */
static const unsigned char g640x480x256_CRTCVGA[24] = {
	/* CRTC */
	0x5F,0x4F,0x50,0x82,0x54,0x80,0x0B,0x3E,0x00,0x40,0x00,0x00,
	0x00,0x00,0x00,0x00,0xEA,0x8C,0xDF,0x50,0x60,0xE7,0x04,0xAB,
};


enum { S3_911, S3_924, S3_801, S3_805, S3_928 };

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


static int s3_chiptype;
static int s3_memory;

static int s3_init(int, int, int);


static void nothing() { }


/* Fill in chipset specific mode information */

static int s3_interlaced( int mode ) { 
	return (mode == G1024x768x256 && __svgalib_monitortype < MON1024_60);
}

static int 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->maxpixels = s3_memory * 1024 / modeinfo->bytesperpixel; */
	modeinfo->maxlogicalwidth = 2040;
	modeinfo->startaddressrange = 0xfffff;
	modeinfo->haveblit = 0;
	modeinfo->flags &= ~HAVE_RWPAGE;
	if (s3_interlaced(mode))
	  modeinfo->flags |= IS_INTERLACED;
	return 0;
}


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

static int s3_modeavailable( int mode ) {
	struct info *info;

        if (mode < 10)
          return vga_chipsetfunctions[CHIPSET_MODEAVAILABLE](mode);
        if (mode == 32)
          return 0;

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

	/* Monitor considerations. */
	if (mode == G800x600x256 && __svgalib_monitortype < MON800_56)
		return 0;
	if (mode == G1024x768x256 && __svgalib_monitortype < MON1024_43I)
		return 0;

	return SVGADRV;
}


/* Set a mode */

static int s3_setmode( int mode, int prv_mode ) {
	static int s3reg0x31, s3reg0x35;
	unsigned char c;
	unsigned char regs[60];

	/* Restore extended registers to a state suitable for setting */
	/* standard VGA modes (including textmode). */
	if (prv_mode == G640x480x64K) {
		outw(0x3D4, 0x0243);		/* set RS2 */
		outb(0x3C6, 0x00);		/* clear DAC MODE */
		outw(0x3D4, 0x0D43);		/* clear RS2 */
	}
	if (!SVGAMODE(mode) && SVGAMODE(prv_mode)) {
		/* Restore extended registers. */
		outb(0x3d4, 0x31);
		outb(0x3d5, s3reg0x31);
		outb(0x3d4, 0x35);
		outb(0x3d5, s3reg0x35);
	}

	if (!SVGAMODE(mode))
		/* Let the standard VGA driver set standard VGA modes. */
		return vga_chipsetfunctions[CHIPSET_SETMODE](mode);
	if (!s3_modeavailable(mode))
		return 1;

	/* Set up standard VGA registers. */
	memcpy(regs + 24, generic_STDVGA, 36);
	switch (mode) {
	case G640x480x256 :
	case G640x480x64K :
		memcpy(regs, g640x480x256_CRTCVGA, 24);
		break;
	case G800x600x256 :
		memcpy(regs, g800x600x256_CRTCVGA, 24);
		break;
	case G1024x768x256 :
		if (__svgalib_monitortype >= MON1024_60)
			/* Non-interlaced. */
			memcpy(regs, g1024x768x256_CRTCVGA, 24);
		else
			/* Interlaced. */
			memcpy(regs, g1024x768x256i_CRTCVGA, 24);
		break;
	}
	__vga_setregs(regs);

	/* Set up extended registers. */
	outw(0x3d4, 0xa539);		/* unlock system control regs */
	if (!SVGAMODE(prv_mode)) {
		/* Save extended registers. */
		outb(0x3d4, 0x31);
		s3reg0x31 = inb(0x3d5);
		outb(0x3d4, 0x35);
		s3reg0x35 = inb(0x3d5);
	}
	/* [the following needs to be corrected; wh] */
	outw(0x3d4, 0x8931);		/* enable banking and vga256? */
	outw(0x3d4, 0x9535);		/* bus? */
	switch (mode) {
	case G640x480x256 :
		if (__svgalib_monitortype >= MON1024_60)
			/* 78 Hz */
			outw(0x3d4, 0x0b42);	/* set S3R0B */
		break;
	case G640x480x64K :
		outw(0x3D4, 0x0243);		/* set RS2 */
		outb(0x3C6, 0xC0);		/* set DAC MODE	*/
		outw(0x3D4, 0x0D43);		/* clear RS2 */
		break;
	case G800x600x256 :
		outw(0x3d4, 0x7a3b);	/* set S3R0B */
		outb(0x3d4, 0x42);	/* set S3R12 */
		c = 0x06;		/* default 56Hz */
		if (__svgalib_monitortype >= MON800_60)
			c = 0x02;	/* 60hz */
		if (__svgalib_monitortype >= MON1024_60)
			c = 0x04;	/* 72hz */
		outb(0x3d5, c);
		break;
	case G1024x768x256 :
		if (__svgalib_monitortype >= MON1024_60) {
			/* Non-interlaced. */
			c = 0x0d;		/* default 60Hz */
			if (__svgalib_monitortype >= MON1024_72)	
				c = 0x0e;	/* 72Hz */
			outw(0x3d4, 0x9c3b);
			outb(0x3d4, 0x42);
			outb(0x3d5, c);
		}
		else {
			/* Interlaced. */
			outw(0x3d4, 0x943b);	/* set S30D */
			outw(0x3d4, 0x603c);	/* set S3R0C */
			outw(0x3d4, 0x2742);	/* set S3R12 */
		}
		break;
	}
#if 0
	if (mode == G800x600x256 || mode == G1024x768x256)
		outw(0x4ae8, 0x000f);		/* set ADVFUNC */
#endif		
        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 ) {
	outb(0x3d4, 0x35);
	outb(0x3d5, page);
	outb(0x3d4, 0x51);
	outb(0x3d5, (page & 0x30) >> 2);
}


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

/* This works up to 1Mb (should be able to go higher). */
static int 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);
	outb(0x3d5, ((address & 0xc0000) >> 14) | 0x8d);
	return 0;
}


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

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


/* Function table (exported) */

int (*s3_chipsetfunctions[])() = {
	(int (*)()) nothing,	/* saveregs */
	(int (*)()) nothing,	/* setregs */
	(int (*)()) nothing,	/* unlock */
	(int (*)()) nothing,	/* lock */
	s3_test,
	s3_init,
	(int (*)()) s3_setpage,
	(int (*)()) nothing,
	(int (*)()) nothing,
	s3_setmode,
	s3_modeavailable,
	s3_setdisplaystart,
	s3_setlogicalwidth,
	s3_getmodeinfo
};


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

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

	if (force) {
		s3_chiptype = par1;	/* we already know the type */
		s3_memory = par2;
	}
	else {
		outb(0x3d4, 0x30);
		rev = inb(0x3d5);	/* get chip id */
		
		switch (rev & 0xf0) {
		case 0x80 :
			switch (rev & 0x0f) {
			case 1 : s3_chiptype = S3_911; break;
			case 2 : s3_chiptype = S3_924; break;
			default :
				printf("Unknown S3 chipset (id = %d).\n", rev);
				return 1;
			}
		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;
			default:
				printf("Unknown S3 chipset (id = %d).\n", rev);
				return 1;
			}
			break;
		case 0x90:
			switch (rev & 0x0F) {
			case 0x02:
			case 0x03:
				printf("Unknown S3 chipset (id = %d).\n", rev);
				return 1;
			default :
				/* 0x00, 0x01: 928D */
				/* 0x04, 0x05: 928E */
				s3_chiptype = S3_928;
				break;
			}
			break;
		case 0xB0:
			/* 928P */
			s3_chiptype = S3_928;
			break;
		}

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

		if ((config & 0x20) != 0)
			s3_memory = 512;
		else
			if (s3_chiptype == S3_911)
				s3_memory = 1024;
			else
				/* look at bits 6 and 7 */
				switch ((config & 0xC0) >> 6) {
				case 0 : s3_memory = 4096; break;
				case 1 : s3_memory = 3072; break;
				case 2 : s3_memory = 2048; break;
				case 3 : s3_memory = 1024; break;
				}
	}

	if (__svgalib_driver_report) {
		printf("Using S3 driver (%s, %dK).\n", s3_chipname[s3_chiptype],
			s3_memory);
	}
	chipsetfunctions = s3_chipsetfunctions;

	return 0;
}
