/*
 * DANG_BEGIN_MODULE
 *
 * Description:
 *   ARK driver for DOSEMU.  Code taken from SVGALIB. 
 * 
 *   Original driver Copyright (c) 1993 Harm Hanemaayer and
 *         partially copyrighted (C) 1993 by Hartmut Schirmer
 *         Rewritten by Christopher Wiles (a0017097@wsuaix.csc.wsu.edu)
 *
 * DANG_END_MODULE
 *
 * $Date: $
 * $Source: $
 * $Revision: $
 * $State: $
 *
 * $Log: $
 */

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

#define OTI_MISC_DATA 1
#define OTI_BCOMPAT_DATA 2
#define OTI_SEGMENT_DATA 3
#define OTI_CONFIG_DATA	4
#define OTI_OVERFLOW_DATA 5
#define OTI_HSYNC2_DATA 6
#define OTI_OVERFLOW2_DATA 7

#define CLOCK_DATA 8
#define OVERFLOW_DATA 9
#define OTI87_HSYNC2_DATA 10
#define OTI87_XCRTC_DATA 11
#define OTI87_COLORS_DATA 12
#define OTI87_FIFO_DATA 13
#define OTI87_MODESELECT_DATA 14
#define OTI87_FEATURE_DATA 15
#define OTI87_XREADSEGMENT_DATA 16
#define OTI87_XWRITESEGMENT_DATA 17
#define OTI87_RAMDAC_DATA 18

#define OTI_INDEX 0x3DE			/* Oak extended index register */
#define OTI_R_W 0x3DF			/* Oak extended r/w register */
#define OTI_CRT_CNTL 0xC		/* Oak CRT COntrol Register */
#define OTI_MISC  0xD			/* Oak Misc register */
#define OTI_BCOMPAT  0xE		/* Oak Back compat register */
#define OTI_SEGMENT  0x11		/* Oak segment register */
#define OTI_CONFIG  0x12		/* Oak config register */
#define OTI_OVERFLOW  0x14		/* Oak overflow register */
#define OTI_OVERFLOW2  0x16		/* Oak overflow2 register */

#define OTI87_PRODUCT 0x00		/* Oak product register */
#define OTI87_STATUS 0x02		/* Oak status register */
#define OTI87_VIDMAP 0x05		/* Oak video memory register */
#define OTI87_CLOCK 0x06		/* Oak clock select register */
#define OTI87_OVERFLOW 0x14		/* Oak overflow register */
#define OTI87_HSYNC2 0x15		/* Oak hsynch/2 register */
#define OTI87_XCRTC 0x17		/* Oak extended CRTC register */
#define OTI87_COLORS 0x19		/* Oak color range register */
#define OTI87_FIFO 0x20			/* Oak FIFO depth register */
#define OTI87_MODESELECT 0x21		/* Oak mode select register */
#define OTI87_FEATURE 0x22		/* Oak feature select register */
#define OTI87_XREADSEGMENT 0x23		/* Oak extended read segment reg. */
#define OTI87_XWRITESEGMENT 0x24	/* Oak extended write seg. reg. */
#define OTI87_XCOMMONSEGMENT 0x25	/* Oak extended common register */

#define OTI87_OK_LINEAR 0		/* Set to 1 for linear addressing */
#define DAC_SC11487 0x3c6		/* Sierra DAC command register */
#define DAC_REVERT 0x3c8		/* Clears DAC from command mode */

static int oak_chiptype;
static int oak_lockreg_save;

static void OAKSetByte( int OTI_index, int OTI_value)
{
	outb(OTI_INDEX, OTI_index);
	outb(OTI_R_W, OTI_value);
}

static int OAKGetByte( int OTI_index)
{
	outb(OTI_INDEX, OTI_index);
	return inb(OTI_R_W);
}

static void OAK_WriteDAC ( int dac_value ) {
	inb(DAC_REVERT);		/* reset DAC r/w pointer */
	inb(DAC_SC11487);		/* do this four times to set register A */
	inb(DAC_SC11487);
	inb(DAC_SC11487);
	inb(DAC_SC11487);
	outb(DAC_SC11487, dac_value);
	inb(DAC_REVERT);		/* and reset DAC ... */
}

static void oak_getperm(void)
{
}

static void oak_releaseperm(void)
{
}

static void oak_unlock(void)
{
	int temp;
	/* Unprotect CRTC[0-7] */
	outb(CRT_I, 0x11);
	temp = inb(CRT_D);
	outb(CRT_D, temp & 0x7F);
	temp = OAKGetByte(OTI_CRT_CNTL);
	OAKSetByte(OTI_CRT_CNTL, temp & 0xf0);
        oak_lockreg_save = temp;
}

static void oak_lock(void)
{
	int temp;
	/* don't set the i/o write test bit, though we cleared it on entry */
	OAKSetByte(OTI_CRT_CNTL, oak_lockreg_save & 0xf7);
	/* Protect CRTC[0-7] */
	outb(CRT_I, 0x11); temp = inb(CRT_D);
	outb(CRT_D, (temp & 0x7F) | 0x80);
}

static void oak_setpage( int page )
{
	if (oak_chiptype == 87) {
		OAKSetByte(OTI87_XCOMMONSEGMENT, page);
	}
	else {
		OAKSetByte(OTI_SEGMENT, (page | (page << 4)) << 8);
	}
}

static void oak_saveregs(Bit16u *regs)
{
	oak_unlock();
	regs[OTI_SEGMENT_DATA] = OAKGetByte(OTI_SEGMENT);
	regs[OTI_MISC_DATA] = OAKGetByte(OTI_MISC);
	regs[OTI_BCOMPAT_DATA] = OAKGetByte(OTI_BCOMPAT);
	regs[OTI_CONFIG_DATA] = OAKGetByte(OTI_CONFIG);
	regs[OTI_OVERFLOW_DATA] = OAKGetByte(OTI_OVERFLOW);
	regs[OTI_HSYNC2_DATA] = OAKGetByte(OTI87_HSYNC2);
	regs[OTI_OVERFLOW2_DATA] = OAKGetByte(OTI_OVERFLOW2);
	if (oak_chiptype == 87) {
		regs[8] = OAKGetByte(OTI87_CLOCK);
		regs[9] = OAKGetByte(OTI87_OVERFLOW);
		regs[OTI87_HSYNC2_DATA] = OAKGetByte(OTI87_HSYNC2);
		regs[OTI87_XCRTC_DATA] = OAKGetByte(OTI87_XCRTC);
		regs[OTI87_COLORS_DATA] = OAKGetByte(OTI87_COLORS);
		regs[OTI87_FIFO_DATA] = OAKGetByte(OTI87_FIFO_DATA);
		regs[OTI87_MODESELECT_DATA] = OAKGetByte(OTI87_MODESELECT);
		regs[OTI87_FEATURE_DATA] = OAKGetByte(OTI87_FEATURE_DATA);
		regs[OTI87_XREADSEGMENT_DATA] = OAKGetByte(OTI87_XREADSEGMENT);
		regs[OTI87_XWRITESEGMENT_DATA] = OAKGetByte(OTI87_XWRITESEGMENT);
	}
}

static void oak_setregs(const Bit16u *regs)
{
	int junk;

	oak_unlock();
	if (oak_chiptype == 87) {
		OAKSetByte(OTI87_XREADSEGMENT, regs[OTI87_XREADSEGMENT_DATA]);
		OAKSetByte(OTI87_XWRITESEGMENT, regs[OTI87_XWRITESEGMENT_DATA]);

		outb(0x3c4, 0);		/* reset sync. */
		junk = inb(0x3c5);
		outw(0x3c4, 0x00 + ((junk & 0xfd) << 8));
			
		OAKSetByte(OTI87_CLOCK, regs[8]);
		OAKSetByte(OTI87_FIFO, regs[OTI87_FIFO_DATA]);
		OAKSetByte(OTI87_MODESELECT, regs[OTI87_MODESELECT_DATA]);

		outw(0x3c4, 0x00 + (junk << 8)); /* set sync. */

		OAKSetByte(OTI87_XCRTC, regs[OTI87_XCRTC_DATA]);
		OAKSetByte(OTI87_OVERFLOW, regs[9]);
		OAKSetByte(OTI87_HSYNC2, regs[OTI87_HSYNC2_DATA]);
		OAK_WriteDAC(regs[OTI87_RAMDAC_DATA]);
	}
	else {
		OAKSetByte(OTI_SEGMENT, regs[OTI_SEGMENT_DATA]);

		/* doc says don't OTI-MISC unless sync. reset is off */
		outb(0x3c4, 0);
		junk = inb(0x3c5);
		outw(0x3C4, 0x00 + ((junk & 0xFD) << 8)); /* now disable the timing sequencer */
		
		OAKSetByte(OTI_MISC, regs[OTI_MISC_DATA]);

		/* put sequencer back */
		outw(0x3C4, 0x00 + (junk << 8));

		OAKSetByte(OTI_BCOMPAT, regs[OTI_BCOMPAT_DATA]);
		OAKSetByte(OTI_CONFIG, regs[OTI_CONFIG_DATA]);
		OAKSetByte(OTI_OVERFLOW, regs[OTI_OVERFLOW_DATA]);
		OAKSetByte(OTI87_HSYNC2, regs[OTI_HSYNC2_DATA]);
		OAKSetByte(OTI_OVERFLOW2, regs[OTI_OVERFLOW2_DATA]);
	}
}


/* oak_chipdetect()				Detect Oak chip type
 *
 * Returns chip id (67, 77, 87) if video chipset is a supported Oak type
 * (57 is _not_ supported).
 * Returns 0 otherwise
 */

static int oak_chipdetect(void)
{
	int  temp1;
	temp1 = OAKGetByte(OTI87_PRODUCT) & 0x01;
	if (temp1 == 0x01) {
		return 87;
	}
	else {
		temp1 = inb(OTI_INDEX);
		temp1 &= 0xE0;
		switch (temp1) {
		    case 0x40 : 
			return 67;
		    case 0xA0 :
			return 77;
		    default :
		    	printf("Oak driver: Unknown chipset (id = %2x)\n", temp1);
		    	oak_lock();
		    	return 0;
		}
	}
}

static Boolean oak_init(void)
{
  oak_chiptype = oak_chipdetect ();
  if (oak_chiptype == 0)
    return FALSE;			/* not supported */
  return TRUE;
}

static Boolean oak_test(void)
{
	int save, temp1;
	oak_unlock();	

	save = OAKGetByte(OTI_SEGMENT);
	OAKSetByte(OTI_SEGMENT, save ^ 0x11);
	temp1 = OAKGetByte(OTI_SEGMENT);
	OAKSetByte(OTI_SEGMENT, save);
	if (temp1 != ( save ^ 0x11 )) {
		oak_lock();			/* unsuccesful */
		return FALSE;
	}

	/* HH: Only allow 087, 077 support seems to be broken for at */
	/* least one 512K card. May be due to mode dump configuring */
	/* for 1024K. */
	if (oak_chipdetect() < 87)
		return FALSE;

	return oak_init();
}

DriverSpecs oak_driverspecs = {
	"OAK",
	oak_saveregs,
	oak_setregs,
	oak_test,
	oak_init,
	oak_setpage,
	oak_getperm,
	oak_releaseperm,
	NULL,
	NULL
};

