/* 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 1993 Harm Hanemaayer */
/* partially copyrighted (C) 1993 by Hartmut Schirmer */

/* Oak support */
/* Basically ET4000 timings, with Oak bank switching */
/* Rewrite based on Steve Goldman's XFree86 linkkit driver */


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

/*
** Register array layout :
**
**  EXT+00 : ???  (unused)
**  EXT+01 : 3de/0d  OTI_MISC
**  EXT+02 : 3de/0e  OTI_BCOMPAT
**  EXT+03 : 3de/11  OTI_SEGMENT
**  EXT+04 : 3de/12  OTI_CONFIG
**  EXT+05 : 3de/14  OTI_OVERFLOW
**  EXT+06 : 3de/15  OTI_HSYNC2
**  EXT+07 : 3de/16  OTI_OVERFLOW2
*/

#include "oak.regs"


#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_HSYNC2  0x15		/* Oak hsync/2 start register */
#define OTI_OVERFLOW2  0x16		/* Oak overflow2 register */

static int oak_chiptype;
static int oak_memory;

static int oak_init(int, int, int);
static int oak_interlaced( int mode );
static int oak_unlock();

/* Mode table */

static ModeTable oak_modes[] = {
	OneModeEntry(640x480x256),
#if 0	
	OneModeEntry(800x600x16),
	OneModeEntry(1024x768x16),
	OneModeEntry(800x600x256),
	OneModeEntry(1024x768x256),
#endif
	END_OF_MODE_TABLE  
};


/* Fill in chipset specific mode information */

static int oak_getmodeinfo( int mode, vga_modeinfo *modeinfo ) {
	modeinfo->maxpixels = oak_memory * 1024 / modeinfo->bytesperpixel;
	modeinfo->maxlogicalwidth = 2040;
	modeinfo->startaddressrange = 0xfffff;
	modeinfo->haveblit = 0;
	modeinfo->flags |= HAVE_RWPAGE;
	if (oak_interlaced(mode))
	  modeinfo->flags |= IS_INTERLACED;
	
	return 0;	
}


/* Read and store chipset-specific registers */

static int oak_saveregs( unsigned char regs[] )
{
	oak_unlock();
	outb(0x3de, OTI_SEGMENT);
	regs[EXT + 3] = inb(0x3df);	/* bank register */
	outb(0x3de, OTI_MISC);
	regs[EXT + 1] = inb(0x3df);	/* misc. reg */
	outb(0x3de, OTI_BCOMPAT);
	regs[EXT + 2] = inb(0x3df);
	outb(0x3de, OTI_CONFIG);
	regs[EXT + 4] = inb(0x3df);
	outb(0x3de, OTI_OVERFLOW);
	regs[EXT + 5] = inb(0x3df);
	outb(0x3de, OTI_HSYNC2);
	regs[EXT + 6] = inb(0x3df);
	outb(0x3de, OTI_OVERFLOW2);
	regs[EXT + 7] = inb(0x3df);

	return 8; /* 8 additional register with oak chips */
}


/* Set chipset-specific registers */

static int oak_setregs( unsigned char regs[], int mode )
{
	int junk;

	oak_unlock();
	outb(0x3de, OTI_SEGMENT);
	outb(0x3df, regs[EXT + 3]);	/* bank register */

	/* 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 */
	
	outb(0x3de, OTI_MISC);
	outb(0x3df, regs[EXT + 1]);	/* misc. reg */

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

	outb(0x3de, OTI_BCOMPAT);
	outb(0x3df, regs[EXT + 2]);
	outb(0x3de, OTI_CONFIG);
	outb(0x3df, regs[EXT + 4]);
	outb(0x3de, OTI_OVERFLOW);
	outb(0x3df, regs[EXT + 5]);
	outb(0x3de, OTI_HSYNC2);
	outb(0x3df, regs[EXT + 6]);
	outb(0x3de, OTI_OVERFLOW2);
	outb(0x3df, regs[EXT + 7]);

	return 0;
}


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

static int oak_modeavailable( int mode ) {
	const unsigned char *regs;
	struct info *info;

	regs = LOOKUPMODE(oak_modes, mode);
	if (regs == NULL || mode == GPLANE16)
        	return vga_chipsetfunctions[CHIPSET_MODEAVAILABLE](mode);
	if (regs==DISABLE_MODE || mode<=TEXT || mode>GLASTMODE)
		return 0;

	info = &__svgalib_infotable[mode];
	if (oak_memory*1024 < info->ydim * info->xbytes)
		return 0;

	return SVGADRV;
}


/* Check if mode is interlaced */

static int oak_interlaced( int mode ) { 
	const unsigned char *regs;

	if (oak_modeavailable(mode) != SVGADRV)
	  return 0;
	regs = LOOKUPMODE(oak_modes, mode);
	if (regs == NULL || regs == DISABLE_MODE)
	  return 0;
	return regs[EXT+05]&0x80;  /* Bit 7 of OverFlow register */
}


/* Set a mode */

static int oak_setmode( int mode, int prv_mode ) {
	const unsigned char *rp;
	unsigned char regs[sizeof(g640x480x256_regs)];

	rp = LOOKUPMODE(oak_modes, mode);
	if (rp == NULL || mode == GPLANE16)
		return (int)(vga_chipsetfunctions[CHIPSET_SETMODE](mode, prv_mode));
	if (!oak_modeavailable(mode))
		return 1;	/* mode not available */

	/* Update the register information */
	memcpy(regs, rp, sizeof(regs));
	/*  Number of memory chips */
	regs[EXT+1] &= 0x3F;
	regs[EXT+1] |= (oak_memory==1024 ? 0x40 : 0x00);
	regs[EXT+1] |= (oak_memory>=512  ? 0x80 : 0x00);
	/* OTI 77 difference */
	if (oak_chiptype == 77) regs[EXT+4] |= 0x08;
			   else regs[EXT+4] &= 0xF7;
	if (__svgalib_infotable[mode].colors == 16) {
	  /* switch from 256 to 16 color mode (from XFree86) */
	  regs[SEQ+4] &= 0xf7; /* Switch off chain 4 mode */
	  regs[EXT+1] &= 0xf0; /* set 16 color high res */
	  regs[EXT+1] |= 0x18;
	}

	__vga_setregs(regs);
	oak_setregs(regs, mode);
	  
        return 0;
}


/* Unlock chipset-specific registers */

static int oak_lockreg_save;

static int oak_unlock() {
	int temp;
	/* Unprotect CRTC[0-7] */
	outb(CRT_I, 0x11); temp = inb(CRT_D);
	outb(CRT_D, temp & 0x7F);
	outb(OTI_INDEX, OTI_CRT_CNTL);
	temp = inb(OTI_R_W);
	outb(OTI_R_W, temp & 0xF0);
        oak_lockreg_save = temp;

	return 0;
}


/* Relock chipset-specific registers */

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

	return 0;
}


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

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

	/* First we see if the segment register is present */
	outb(OTI_INDEX, OTI_SEGMENT);
	save = inb(OTI_R_W);
	/* I assume that one I set the index I can r/w/r/w to
           my hearts content */
        outb(OTI_R_W, save ^ 0x11);
	temp1 = inb(OTI_R_W);
	outb(OTI_R_W, save);
	if (temp1 != ( save ^ 0x11 )) {
		oak_lock();	/* unsuccesful */
		return 0;
	}

	/* figure out which chipset */
	temp1 = inb(OTI_INDEX);
	temp1 &= 0xE0;
	switch (temp1) {
	    case 0xE0 : /* oti 57 don't know it */
	    	printf("Oak driver: OTI-057 not supported\n");
	    	oak_lock();
	    	return 0;
	    case 0x40 : /* oti 67 */
		oak_chiptype = 67;
		break;
	    case 0xA0 : /* oti 77 */
		oak_chiptype = 77;
		break;
	    default :
	    	printf("Oak driver: Unknown chipset (id = %2x)\n", temp1);
	    	oak_lock();
	    	return 0;
	}

	oak_init(1, oak_chiptype, 0);	/* force the chipset type */
    	return 1;
}


static unsigned char last_page = 0;

/* Bank switching function - set 64K bank number */
static void oak_setpage( int page ) {
	outw(0x3de, 0x11 + ((last_page = page | (page << 4)) << 8));
}

/* Bank switching function - set 64K read bank number */
static void oak_setrdpage( int page ) {
	last_page &= 0xF0;
	last_page |= page;
	outw(0x3de, 0x11 + (last_page << 8));
}

/* Bank switching function - set 64K write bank number */
static void oak_setwrpage( int page ) {
	last_page &= 0x0F;
	last_page |= page<<4;
	outw(0x3de, 0x11 + (last_page << 8));
}


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

static int oak_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(OTI_INDEX, OTI_OVERFLOW);
	outb(OTI_R_W, (inb(OTI_R_W) & 0xf7) | ((address & 0x40000) >> 15));
		/* write sa18 to bit 3 */
	if (oak_chiptype == 77) {
		outb(OTI_INDEX, OTI_OVERFLOW2);
		outb(OTI_R_W, (inb(OTI_R_W) & 0xf7) | ((address & 0x80000) >> 16));
			/* write sa19 to bit 3 */
	}

	return 0;
}


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

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

	return 0;
}


/* Function table (exported) */

int (*oak_chipsetfunctions[])() = {
	oak_saveregs,
	oak_setregs,
	oak_unlock,
	oak_lock,
	oak_test,
	oak_init,
	(int (*)()) oak_setpage,
	(int (*)()) oak_setrdpage,
	(int (*)()) oak_setwrpage,
	oak_setmode,
	oak_modeavailable,
	oak_setdisplaystart,
	oak_setlogicalwidth,
	oak_getmodeinfo
};


/* Initialize chipset (called after detection) */

static int oak_init( int force, int par1, int par2) {
	int temp1;
	if (force) {
		oak_chiptype = par1;	/* we already know the type */
	}
	else {
		/* figure out which chipset */
		int temp1 = inb(OTI_INDEX);
		temp1 &= 0xE0;
		switch (temp1) {
		    case 0x40 : /* oti 67 */
		    	oak_chiptype = 67;
			break;
		    case 0xA0 : /* oti 77 */
		    	oak_chiptype = 77;
			break;
		}
    	}

	/*
	 * Otherwise, do whatever chipset-specific things are 
	 * necessary to figure out how much memory (in kBytes) is 
	 * available.
	 */
	outb(OTI_INDEX, OTI_MISC);
	temp1 = inb(OTI_R_W);
	temp1 &= 0xC0;
	if (temp1 == 0xC0 )
		oak_memory = 1024;
	else if (temp1 == 0x80 )
		oak_memory = 512;
	else if (temp1 == 0x00 )
		oak_memory = 256;
	else {
		printf("Oak driver: Invalid amount of memory. Using 256K.\n");
		oak_memory = 256;
	}

	if (__svgalib_driver_report) {
		printf("Using Oak driver (OTI-0%d, %dK).\n", oak_chiptype, 
			oak_memory);
	}
	chipsetfunctions = oak_chipsetfunctions;

	return 0;
}

