/* 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 ATI Driver	January 1995, Scott D. Heavner (sdh@po.cwru.edu)
 */

/* ==== SO FAR THE ONLY SPECIAL THING THIS ATI DRIVER DOES IS ALLOW 
 * ==== GRAPHICS MODES TO FUNCTION WITH A 132col TERMINAL, THERE ARE NO
 * ==== NEW MODES YET (there will be).  No the XF86 driver won't help as
 * ==== XF863.1 doesn't work properly with my ATI Graphics Ultra, 
 * ==== I will be having a look at the XF86 drivers now that I've found
 * ==== the problem an implemented a fix here.
 *
 * ---- If this driver detects a mach32 chip, it does not init the ATI
 * ---- driver, hopefully, the autodetect will find the Mach32 driver.
 * ---- We should probably change this to run the Mach32 as an ATI SVGA
 * ---- board, but probe for the Mach32 first, this would allow the user
 * ---- to force ATI mode in a config file.
 */

#define ATI_UNKNOWN	0
#define	ATI_18800	1
#define	ATI_18800_1	2
#define	ATI_28800_2	3
#define	ATI_28800_4	4
#define	ATI_28800_5	5
#define ATI_68800	'a'

#define ATIREG(s)	(EXT+(s)-0xa0) 

#define MIN(a,b)	(((a)<(b))?(a):(b))
#define ABS(a)		(((a)<0)?(-(a)):(a))

#include <stdio.h>
#include <stdlib.h>     /* for NULL */
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

#include "vga.h"
#include "libvga.h"
#include "driver.h"

static unsigned char ati_flags_42, ati_flags_44, ati_gate;
static short ati_base=0;

static void nothing() { }

/* Initialize chipset (called after detection) */
static int ati_init( int force, int par1, int par2) {
        if (__svgalib_driver_report)
                printf("Using ATI (mostly VGA) driver.\n");
        driverspecs = &ati_driverspecs;

	/* Have to give ourselves some more permissions -- last port currently used is 0x3df */
	if (ioperm(MIN(ati_base,0x3df), ABS(0x3df-ati_base), 1) ) {
			printf("IOPERM FAILED IN ATI\n");
			exit(-2);
	}

        return 1;
}

/* Read and store chipset-specific registers */
static int ati_saveregs(unsigned char regs[])
{
	outb(ati_base,0xb8);
	regs[ATIREG(0xb8)]=inb(1+ati_base);

	if (ati_gate>ATI_18800) {
		outb(ati_base,0xbe);
		regs[ATIREG(0xbe)]=inb(1+ati_base);
	}

	return 0;
}

/* Set chipset-specific registers */
static void ati_setregs( const unsigned char regs[], int mode )
{
	unsigned char c;

        if (mode==TEXT) {    /* Death request */
                outb(ati_base,0xb8);
		outb(1+ati_base, regs[ATIREG(0xb8)]);

		if (ati_gate>ATI_18800) {
       			outb(ati_base,0xbe);
			outb(1+ati_base, regs[ATIREG(0xbe)]);
		}
	} else {
                /* Mess with the timings before passing it off to the vga driver */
                outb(ati_base,0xb8);
		c = inb(1+ati_base) | 0x40;
		outb(1+ati_base, c);

		if (ati_gate>ATI_18800) {
	                outb(ati_base,0xbe);
			c = inb(1+ati_base) | 0x10;
			outb(1+ati_base, c);
		}

        }
}

/* Fill in chipset specific mode information */
static void ati_getmodeinfo( int mode, vga_modeinfo *modeinfo ) {
	vga_driverspecs.getmodeinfo(mode, modeinfo);
}


/* Return non-zero if mode is available */
static int ati_modeavailable( int mode ) {
	return vga_driverspecs.modeavailable(mode);
}


/* Set a mode */
static int ati_setmode( int mode, int prv_mode ) {
	const unsigned char *regs = NULL;
	ati_setregs(regs, mode);
	return vga_driverspecs.setmode(mode, prv_mode);
}

/* Lifted from the genoa driver, maybe we should let everyone do the checks while we have
 * the vbios mmapped */
static unsigned char *map_vbios()
{
	unsigned char *vga_bios;

        /* Changed to use valloc(). */
        if ((vga_bios = valloc(4096)) == NULL)
        {
                fprintf (stderr, "svgalib: malloc error\n");
                exit (-1);
        }

        vga_bios = (unsigned char *) mmap
        (
                (caddr_t) vga_bios,
                4096,
                PROT_READ,
                MAP_SHARED | MAP_FIXED,
                __svgalib_mem_fd,
                0xc0000
        );

        if ((long) vga_bios < 0)
        {
                fprintf (stderr, "svgalib: mmap error\n");
                exit (-1);
        }

	return vga_bios;
}

static int ati_test()
{
        int  result;
	unsigned char *vga_bios;

	vga_bios = map_vbios();

	if ( strncmp("761295520",(vga_bios+0x31),9)||     /* Identify as an ATI product */
	     strncmp("31",(vga_bios+0x40),2)) {           /* Identify as an ATI super vga (vs EGA/Basic-16) */
		result=0;
	} else {
		result=1;

		memcpy(&ati_base,(vga_bios+0x10),2);

		switch (vga_bios[0x43]) {		/* Identify Gate revision */
		case '1':
			ati_gate = ATI_18800;
			break;
		case '2':
			ati_gate = ATI_18800_1;
			break;
		case '3':
			ati_gate = ATI_28800_2;
			break;
		case '4':
			ati_gate = ATI_28800_4;
			break;
		case '5':
			ati_gate = ATI_28800_5;
			break;
		case 'a':
			ati_gate = ATI_68800;		/* Mach 32, should someone else handle this ? */
			return 0;
		default:
			ati_gate = ATI_UNKNOWN;
		}

		ati_flags_42 = vga_bios[0x42];
		ati_flags_44 = vga_bios[0x44];
		
	}

	if (result)
		result = ati_init(0,0,0);

        munmap ((caddr_t) vga_bios, 4096);

        return (result);

}


/* Set display start */
static void ati_setdisplaystart( int address ) {
	vga_driverspecs.setdisplaystart(address);
}


/* Set logical scanline length (usually multiple of 8) */
static void ati_setlogicalwidth( int width ) { 
	vga_driverspecs.setlogicalwidth(width);
}


/* Function table (exported) */
DriverSpecs ati_driverspecs = {
        ati_saveregs,
        ati_setregs,
        nothing, /* unlock */
        nothing, /* lock */
        ati_test,
        ati_init,
        nothing, /* setpage */
        nothing, /* setrdpage */
        nothing, /* setwrpage */
        ati_setmode,
        ati_modeavailable,
        ati_setdisplaystart,
        ati_setlogicalwidth,
        ati_getmodeinfo,
	0,	/* bitblt */
	0,	/* imageblt */
	0,	/* fillblt */
	0,	/* hlinelistblt */
	0,	/* bltwait */
	0,	/* extset */
	0,
	0,	/* linear */
	NULL	/* accelspecs */
};
