/*
 * DANG_BEGIN_MODULE
 *
 * Description:
 *   ARK driver for DOSEMU.  Code taken from SVGALIB. 
 * 
 *   Original driver Copyright (C) 1993 Harm Hanemaayer
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

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

#include <sys/mman.h>

#define CIRRUSREG_GR(i) (VGA_TOTAL_REGS + i - VGA_GRAPHICS_COUNT)
#define CIRRUSREG_SR(i) (VGA_TOTAL_REGS + 5 + i - VGA_SEQUENCER_COUNT)
#define CIRRUSREG_CR(i) (VGA_TOTAL_REGS + 5 + 27 + i - VGA_CRTC_COUNT)
#define CIRRUSREG_DAC (VGA_TOTAL_REGS + 5 + 27 + 15)
#define CIRRUS_TOTAL_REGS (VGA_TOTAL_REGS + 5 + 27 + 15 + 1)

/* Indices into mode register array. */
#define CIRRUS_GRAPHICSOFFSET1	CIRRUSREG_GR(0x09)
#define CIRRUS_GRAPHICSOFFSET2	CIRRUSREG_GR(0x0A)
#define CIRRUS_GRB		CIRRUSREG_GR(0x0B)
#define CIRRUS_SR7		CIRRUSREG_SR(0x07)
#define CIRRUS_VCLK3NUMERATOR	CIRRUSREG_SR(0x0E)
#define CIRRUS_DRAMCONTROL	CIRRUSREG_SR(0x0F)
#define CIRRUS_PERFTUNING	CIRRUSREG_SR(0x16)
#define CIRRUS_SR17		CIRRUSREG_SR(0x17)
#define CIRRUS_VCLK3DENOMINATOR CIRRUSREG_SR(0x1E)
#define CIRRUS_MCLKREGISTER	CIRRUSREG_SR(0x1F)
#define CIRRUS_CR19		CIRRUSREG_CR(0x19)
#define CIRRUS_CR1A		CIRRUSREG_CR(0x1A)
#define CIRRUS_CR1B		CIRRUSREG_CR(0x1B)
#define CIRRUS_CR1D 		CIRRUSREG_CR(0x1D)
#define CIRRUS_HIDDENDAC	CIRRUSREG_DAC


/* Efficient chip type checks. */

#define CHECKCHIP1(c1) ((1 << cirrus_chiptype) & (1 << c1))
#define CHECKCHIP2(c1, c2) ((1 << cirrus_chiptype) & ((1 << c1) | (1 << c2)))
#define CHECKCHIP3(c1, c2, c3) ((1 << cirrus_chiptype) & ((1 << c1) \
	| (1 << c2) | (1 << c3)))
#define CHECKCHIP4(c1, c2, c3, c4) ((1 << cirrus_chiptype) & ((1 << c1) \
	| (1 << c2) | (1 << c3) | (1 << c4)))
#define CHECKCHIP6(c1, c2, c3, c4, c5, c6) ((1 << cirrus_chiptype) & \
	((1 << c1) | (1 << c2) | (1 << c3) | (1 << c4) | (1 << c5) \
	| (1 << c6)))
#define CHECKCHIP8(c1, c2, c3, c4, c5, c6, c7, c8) ((1 << cirrus_chiptype) & \
	((1 << c1) | (1 << c2) | (1 << c3) | (1 << c4) | (1 << c5) \
	| (1 << c6) | (1 << c7) | (1 << c8)))
#define CHECKCHIPGREATEREQUAL(c) (cirrus_chiptype >= c)
#define CHECKCHIPNOTEQUAL(c) (cirrus_chiptype != c)

#define CHIP_HAS_CR1D() \
	CHECKCHIP3(CLGD5429, CLGD5430, CLGD5434)
#define CHIP_HAS_GRC_AND_GRD() \
	CHECKCHIP4(CLGD5424, CLGD5426, CLGD5428, CLGD5429)
#define CHIP_HAS_GRE() \
	CHECKCHIP4(CLGD5428, CLGD5429, CLGD5430, CLGD5434)
#define CHIP_HAS_GR10_AND_GR11() \
	CHECKCHIP6(CLGD5424, CLGD5426, CLGD5428, CLGD5429, CLGD5430, CLGD5434)
#define CHIP_HAS_BLTTRANSPARENTCOLOR() \
	CHECKCHIP2(CLGD5426, CLGD5428)
#define CHIP_HAS_PERFTUNINGREGISTER() \
	CHECKCHIP6(CLGD5424, CLGD5426, CLGD5428, CLGD5429, CLGD5430, CLGD5434)
#define CHIP_HAS_MCLK_REGISTER() \
	CHECKCHIP8(CLGD5420B, CLGD5422C, CLGD5424, CLGD5426, CLGD5428, \
		CLGD5429, CLGD5430, CLGD5434)
#define CHIP_HAS_32BIT_DRAM_BUS() \
	CHECKCHIPGREATEREQUAL(CLGD5420B)
#define CHIP_HAS_64BIT_DRAM_BUS() \
	CHECKCHIP1(CLGD5434)
#define CHIP_HAS_HIDDENDAC() \
	CHECKCHIPGREATEREQUAL(CLGD5420B)
#define CHIP_HAS_ACCELERATION() \
	CHECKCHIPNOTEQUAL(CLGD5420B)
#define CHIP_HAS_SR17() \
	CHECKCHIPGREATEREQUAL(CLGD5422)

enum { CLGD5420 = 0, CLGD5420B, CLGD5422, CLGD5422C, CLGD5424, CLGD5426,
	CLGD5428, CLGD5429, CLGD5430, CLGD5434};

static const char *cirrus_chipname[] = {
  "5420", "5420-75QC-B", "5422", "5422-80QC-C", "5424", "5426", "5428",
  "5429", "5430", "5434"
};

static int cirrus_memory;
static int cirrus_chiptype;
static int cirrus_chiprev;

/*
 * It looks like the 5434 palette clock doubling mode doensn't like
 * clocks like (0x7B, 0x20), whereas (0x53, 0x16) is OK.
 */

static void cirrus_setperm(void)
{
}

static void cirrus_releaseperm(void)
{
}

static void cirrus_setpage_2M(int page)
{
	/* Cirrus banking register has been set to 16K granularity */
	outw(0x3ce, (page << 10) + 0x09);
}

static void cirrus_setpage(int page)
{
	/* default 4K granularity */
	outw(0x3ce, (page << 12) + 0x09);
}

static void cirrus_unlock(void)
{
	int vgaIOBase, temp;
	
				/* Are we Mono or Color? */
       	vgaIOBase = (inb(0x3CC) & 0x01) ? 0x3D0 : 0x3B0;

       	outb(0x3C4,0x06);
       	outb(0x3C5,0x12);	/* unlock cirrus special */

				/* Put the Vert. Retrace End Reg in temp */

       	outb(vgaIOBase + 4, 0x11); temp = inb(vgaIOBase + 5);

				/* Put it back with PR bit set to 0 */
				/* This unprotects the 0-7 CRTC regs so */
				/* they can be modified, i.e. we can set */
				/* the timing. */

       	outb(vgaIOBase + 5, temp & 0x7F);
}

static void cirrus_lock(void)
{
	outb(0x3C4,0x06);
	outb(0x3C5,0x0F);	 /* relock cirrus special */
}


/* Read and save chipset-specific registers */

static void cirrus_saveregs(Bit16u *regs)
{
#if 0
    	int i;
#endif
	cirrus_unlock(); /* May be locked again by other programs (e.g. X) */

#if 0
	/* Save extended CRTC registers. */
	for (i = 0; i < 15; i++) {	/* was 4 */
		port_out(0x18 + i, CRT_I);
		regs[CIRRUSREG_CR(i)] = port_in(CRT_D);
	}
	
	/* Save extended graphics registers */
	for (i = 0; i < 5; i++) {	/* was 3 */
		port_out(0x09 + i, GRA_I);
		regs[CIRRUSREG_GR(i)] = port_in(GRA_D);
	}

    	/* Save extended sequencer registers. */
    	for (i = 0; i < 27; i++) {
	 	port_out(0x05 + i, SEQ_I); 
	 	regs[CIRRUSREG_SR(i)] = port_in(SEQ_D); 
    	}
#endif

 	/* Save extended CRTC registers. */
 	regs[CIRRUSREG_CR(0x19)] = inCR(0x19);
 	regs[CIRRUSREG_CR(0x1A)] = inCR(0x1A);
 	regs[CIRRUSREG_CR(0x1B)] = inCR(0x1B);
	if (CHIP_HAS_CR1D())
	 	regs[CIRRUSREG_CR(0x1D)] = inCR(0x1D);

	/* Save extended graphics registers. */
	regs[CIRRUSREG_GR(0x09)] = inGR(0x09);
	regs[CIRRUSREG_GR(0x0A)] = inGR(0x0A);
	regs[CIRRUSREG_GR(0x0B)] = inGR(0x0B);
 
    	/* Save extended sequencer registers. */
    	regs[CIRRUS_SR7] = inSR(0x07);
    	regs[CIRRUS_VCLK3NUMERATOR] = inSR(0x0E);
    	regs[CIRRUS_DRAMCONTROL] = inSR(0x0F);
    	if (CHIP_HAS_PERFTUNINGREGISTER())
	    	regs[CIRRUS_PERFTUNING] = inSR(0x16);
	if (CHIP_HAS_SR17())
		regs[CIRRUS_SR17] = inSR(0x17);
    	regs[CIRRUS_VCLK3DENOMINATOR] = inSR(0x1E);
    	if (CHIP_HAS_MCLK_REGISTER())
    		regs[CIRRUS_MCLKREGISTER] = inSR(0x1F);

    	/* Save Hicolor DAC register. */
    	if (CHIP_HAS_HIDDENDAC()) {
	    	outb(0x3c6, 0);	outb(0x3c6, 0xff);
	    	inb(0x3c6); inb(0x3c6); inb(0x3c6); inb(0x3c6);
	    	regs[CIRRUSREG_DAC] = inb(0x3c6);
	}
#if 0
	return CIRRUS_TOTAL_REGS - VGA_TOTAL_REGS;
#endif
}

static void writehicolordac( unsigned char c )
{
    	outb(0x3c6, 0);	outb(0x3c6, 0xff);
    	inb(0x3c6); inb(0x3c6); inb(0x3c6); inb(0x3c6);
    	outb(0x3c6, c);
    	inb(0x3c8);
}

static void cirrus_setregs(const Bit16u *regs)
{
#if 0
    	int i;
#endif
    	
	cirrus_unlock(); /* May be locked again by other programs (eg. X) */

 	/* Write extended CRTC registers. */
 	outCR(0x19, regs[CIRRUSREG_CR(0x19)]);
 	outCR(0x1A, regs[CIRRUSREG_CR(0x1A)]);
 	outCR(0x1B, regs[CIRRUSREG_CR(0x1B)]);
	if (CHIP_HAS_CR1D())
	 	outCR(0x1D, regs[CIRRUSREG_CR(0x1D)]);

	/* Write extended graphics registers. */
	outGR(0x09, regs[CIRRUSREG_GR(0x09)]);
	outGR(0x0A, regs[CIRRUSREG_GR(0x0A)]);
	outGR(0x0B, regs[CIRRUSREG_GR(0x0B)]);
#ifdef SET_ALL
 	if (CHIP_HAS_GRC_AND_GRD()) {
 		outGR(0x0C, regs[CIRRUSREG_GR(0x0C)]);
 		outGR(0x0D, regs[CIRRUSREG_GR(0x0D)]);
 	}
 	if (CHIP_HAS_GRE())
 		outGR(0x0E, regs[CIRRUSREG_GR(0x0E)];
 	if (CHIP_HAS_GR10_AND_GR11() {
 		outGR(0x10, regs[CIRRUSREG_GR(0x10)]);
 		outGR(0x11, regs[CIRRUSREG_GR(0x11)]);
 	}
 	if (CHIP_HAS_BLT_REGISTERS()) {
		for (i = 0x20; i <= 0x2A; i++)
	 		outGR(i, regs[CIRRUSREG_GR(i)]);
		for (i = 0x2C; i < 0x2E; i++)
	 		outGR(i, regs[CIRRUSREG_GR(i)]);
	 	if (CHIP_HAS_BLTWRITEMASK())
	 		outGR(0x2D, regs[CIRRUSREG_GR(0x2D));
 		outGR(0x30, regs[CIRRUSREG_GR(0x30));
 		outGR(0x32, regs[CIRRUSREG_GR(0x32));
 		if (CHIP_HAS_BLTTRANSPARENTCOLOR()) {
	 		outGR(0x34, regs[CIRRUSREG_GR(0x34));
	 		outGR(0x35, regs[CIRRUSREG_GR(0x35));
	 		outGR(0x38, regs[CIRRUSREG_GR(0x38));
	 		outGR(0x39, regs[CIRRUSREG_GR(0x39));
 		}
 	}
 #endif
 
     	/* Write Truecolor DAC register. */
	if (CHIP_HAS_HIDDENDAC())
	    	writehicolordac(regs[CIRRUS_HIDDENDAC]);
 
    	/* Write extended sequencer registers. */

    	/* Be careful to put the sequencer clocking mode in a safe state. */
    	outSR(0x07, (regs[CIRRUS_SR7] & ~0x0F) | 0x01);

    	outSR(0x0E, regs[CIRRUS_VCLK3NUMERATOR]);
    	outSR(0x1E, regs[CIRRUS_VCLK3DENOMINATOR]);
    	outSR(0x07, regs[CIRRUS_SR7]);
    	outSR(0x0F, regs[CIRRUS_DRAMCONTROL]);
    	if (CHIP_HAS_PERFTUNINGREGISTER())
	    	outSR(0x16, regs[CIRRUS_PERFTUNING]);
	if (CHIP_HAS_SR17())
		outSR(0x17, regs[CIRRUS_SR17]);
    	if (CHIP_HAS_MCLK_REGISTER())
    		outSR(0x1F, regs[CIRRUS_MCLKREGISTER]);
}

static Boolean cirrus_init(void)
{
	unsigned char v;
		unsigned char partstatus;
	cirrus_unlock();
		outb(CRT_I, 0x27);
		cirrus_chiptype = inb(CRT_D) >> 2;
		cirrus_chiprev = 0;
		partstatus = inCR(0x25);
		switch (cirrus_chiptype) {
			case 0x22 :
				cirrus_chiptype = CLGD5420;
#if 0
				/* Check for CL-GD5420-75QC-B. */
				/* It has a Hidden-DAC register. */
				outb(0x3C6, 0x00);
				outb(0x3C6, 0xFF);
				inb(0x3C6); inb(0x3c6); inb(0x3C6); inb(0x3C6);
				if (inb(0x3C6) != 0xFF)
					cirrus_chiptype = CLGD5420B; break;
				break;
#endif
			case 0x23 : cirrus_chiptype = CLGD5422; break;
			case 0x24 : cirrus_chiptype = CLGD5426; break;
			case 0x25 :
				cirrus_chiptype = CLGD5424;
				/*
				 * Some CL-GD5422's ID as CL-GD5424.
				 * Check for writability of GRC.
				 */
				v = inGR(0x0C);
				outGR(0x0C, 0x55);
				if (inGR(0x0C) != 0x55)
					cirrus_chiptype = CLGD5422;
				outGR(0x0C, v);
				break;
			case 0x26 : cirrus_chiptype = CLGD5428; break;
			case 0x27 : cirrus_chiptype = CLGD5429; break;
			case 0x28 : cirrus_chiptype = CLGD5430; break;
			case 0x2A :
				cirrus_chiptype = CLGD5434;
				if ((partstatus & 0xC0) == 0xC0)
					/* Rev. E, can do 60 MHz MCLK. */
					cirrus_chiprev = 1;
				break;
			default :
				v_printf("VGA: Unknown Cirrus chip %2x.\n",
					cirrus_chiptype);
				return FALSE;
		}

#if 0
		if (cirrus_chiptype == CLGD5422) {
			/* Rev. C has programmable MCLK register; */
			/* check for it. */
			/* This check is wrong. */
			if (inSR(0x1F) != 0xFF)
				cirrus_chiptype = CLGD5422C;
		}
#endif

		/* Now determine the amount of memory. */
		outb(0x3c4, 0x0a);	/* read memory register */
		/* This depends on the BIOS having set a scratch register. */
		v = inb(0x3c5);
		cirrus_memory = 256 << ((v >> 3) & 3);

		/* Determine memory the correct way for the 543x, and
		 * for the 542x if the amount seems incorrect. */
		if (cirrus_chiptype >= CLGD5430 || (cirrus_memory <= 256
		&& cirrus_chiptype != CLGD5420)) {
			unsigned char SRF;
			cirrus_memory = 512;
			outb(0x3c4, 0x0f);
			SRF = inb(0x3c5);
			if (SRF & 0x10)
				/* 32-bit DRAM bus. */
				cirrus_memory *= 2;
		  	if ((SRF & 0x18) == 0x18)
	 	 		/* 64-bit DRAM data bus width; assume 2MB. */
	  			/* Also indicates 2MB memory on the 5430. */
	  			cirrus_memory *= 2;
		  	if (cirrus_chiptype != CLGD5430 && (SRF & 0x80))
		  		/* If DRAM bank switching is enabled, there */
		  		/* must be twice as much memory installed. */
		  		/* (4MB on the 5434) */
		  		cirrus_memory *= 2;
		}

	/* Set up the correct paging routine */
	if (cirrus_memory >= 2048)
		cirrus_driverspecs.setpage = cirrus_setpage_2M;

	return TRUE;
}

static Boolean cirrus_test(void)
{
  int oldlockreg;
  int lockreg;

  outb(0x3c4, 0x06);
  oldlockreg = inb(0x3c5);

  cirrus_unlock();

  /* If it's a Cirrus at all, we should be */
  /* able to read back the lock register */
	  
  outb(0x3C4,0x06);
  lockreg = inb(0x3C5);
	  
  /* Ok, if it's not 0x12, we're not a Cirrus542X. */
  if (lockreg != 0x12) {
    outb(0x3c4, 0x06);
    outb(0x3c5, oldlockreg);
    return FALSE;
  }
	
  /* The above check seems to be weak, so we also check the chip ID. */
	
  outb(CRT_I, 0x27);
  switch (inb(CRT_D) >> 2) {
  case 0x22 : 
  case 0x23 :
  case 0x24 :
  case 0x25 :
  case 0x26 :
  case 0x27 : 	/* 5429 */
  case 0x28 :	/* 5430 */
  case 0x2A :	/* 5434 */
    break;
  default :
    outb(0x3c4, 0x06);
    outb(0x3c5, oldlockreg);
    return FALSE;
  }

  return cirrus_init();
}

DriverSpecs cirrus_driverspecs = {
	"CIRRUS",
	cirrus_saveregs,
	cirrus_setregs,
	cirrus_test,
	cirrus_init,
	cirrus_setpage,
	cirrus_setperm,
	cirrus_releaseperm,
	NULL,
	NULL
};

