/*
 * This file is part of the UCB release of Plan 9. It is subject to the license
 * terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
 * part of the UCB release of Plan 9, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms contained
 * in the LICENSE file.
 */

#include <u.h>
#include <lib9.h>
#include <bio.h>

#include "pci.h"
#include "vga.h"

/*
 * Generic S3 GUI Accelerator.
 */
static void
snarf(Vga* vga, Ctlr* ctlr)
{
	int i;

	trace("%s->snarf->s3generic\n", ctlr->name);

	/*
	 * Unlock extended registers.
	 * 0xA5 ensures Crt36 and Crt37 are also unlocked
	 * (0xA0 unlocks everything else).
	 */
	vgaxo(Crtx, 0x38, 0x48);
	vgaxo(Crtx, 0x39, 0xA5);

	/*
	 * Not all registers exist on all chips.
	 * Crt3[EF] don't exist on any.
	 */
	for(i = 0x30; i < 0x70; i++)
		vga->crt[i] = vgaxi(Crtx, i);

	/*
	 * Memory size.
	 */
	switch((vga->crt[0x36]>>5) & 0x07){

	case 0x00:
		vga->vmz = 4*1024*1024;
		break;

	case 0x02:
		vga->vmz = 3*1024*1024;
		break;

	case 0x04:
		vga->vmz = 2*1024*1024;
		break;

	case 0x06:
		vga->vmz = 1*1024*1024;
		break;

	case 0x07:
		vga->vmz = 512*1024;
		break;
	}

	ctlr->flag |= Fsnarf;
}

static void
init(Vga* vga, Ctlr* ctlr)
{
	Mode *mode;
	uint32_t x;

	trace("%s->init->s3generic\n", ctlr->name);
	mode = vga->mode;

	/*
	 * Is enhanced mode is necessary?
	 */
	if((ctlr->flag & (Uenhanced|Henhanced)) == Henhanced){
		if(mode->z >= 8)
			resyncinit(vga, ctlr, Uenhanced, 0);
		else
			resyncinit(vga, ctlr, 0, Uenhanced|Henhanced);
	}
	if((ctlr->flag & Uenhanced) == 0 && mode->x > 1024)
		error("%s: no support for 1-bit mode > 1024x768x1\n", ctlr->name);

	vga->crt[0x31] = 0x85;
	vga->crt[0x6A] &= 0xC0;
	vga->crt[0x32] &= ~0x40;

	vga->crt[0x31] |= 0x08;
	vga->crt[0x32] |= 0x40;

	vga->crt[0x33] |= 0x20;
	if(mode->z >= 8)
		vga->crt[0x3A] |= 0x10;
	else
		vga->crt[0x3A] &= ~0x10;

	vga->crt[0x34] = 0x10;
	vga->crt[0x35] = 0x00;
	if(mode->interlace){
		vga->crt[0x3C] = vga->crt[0]/2;
		vga->crt[0x42] |= 0x20;
	}
	else{
		vga->crt[0x3C] = 0x00;
		vga->crt[0x42] &= ~0x20;
	}

	vga->crt[0x40] = (vga->crt[0x40] & 0xF2);
	vga->crt[0x43] = 0x00;
	vga->crt[0x45] = 0x00;

	vga->crt[0x50] &= 0x3E;
	if(mode->x <= 640)
		x = 0x40;
	else if(mode->x <= 800)
		x = 0x80;
	else if(mode->x <= 1024)
		x = 0x00;
	else if(mode->x <= 1152)
		x = 0x01;
	else if(mode->x <= 1280)
		x = 0xC0;
	else
		x = 0x81;
	vga->crt[0x50] |= x;

	vga->crt[0x51] = (vga->crt[0x51] & 0xC3)|((vga->crt[0x13]>>4) & 0x30);
	vga->crt[0x53] &= ~0x10;

	/*
	 * Set up linear aperture. For the moment it's 64K at 0xA0000.
	 * The real base address will be assigned before load is called.
	 */
	vga->crt[0x58] = 0x88;
	if(ctlr->flag & Uenhanced){
		vga->crt[0x58] |= 0x10;
		if(vga->linear && (ctlr->flag & Hlinear))
			ctlr->flag |= Ulinear;
		if(vga->vmz <= 1024*1024)
			vga->vma = 1024*1024;
		else if(vga->vmz <= 2*1024*1024)
			vga->vma = 2*1024*1024;
		else
			vga->vma = 8*1024*1024;
	}
	vga->crt[0x59] = 0x00;
	vga->crt[0x5A] = 0x0A;

	vga->crt[0x5D] &= 0x80;
	if(vga->crt[0x00] & 0x100)
		vga->crt[0x5D] |= 0x01;
	if(vga->crt[0x01] & 0x100)
		vga->crt[0x5D] |= 0x02;
	if(vga->crt[0x02] & 0x100)
		vga->crt[0x5D] |= 0x04;
	if(vga->crt[0x04] & 0x100)
		vga->crt[0x5D] |= 0x10;
	if(vga->crt[0x3B] & 0x100)
		vga->crt[0x5D] |= 0x40;

	vga->crt[0x5E] = 0x40;
	if(vga->crt[0x06] & 0x400)
		vga->crt[0x5E] |= 0x01;
	if(vga->crt[0x12] & 0x400)
		vga->crt[0x5E] |= 0x02;
	if(vga->crt[0x15] & 0x400)
		vga->crt[0x5E] |= 0x04;
	if(vga->crt[0x10] & 0x400)
		vga->crt[0x5E] |= 0x10;

	ctlr->type = s3generic.name;

	ctlr->flag |= Finit;
}

static void
load(Vga* vga, Ctlr* ctlr)
{
	uint32_t l;

	trace("%s->load->s3generic\n", ctlr->name);

	vgaxo(Crtx, 0x31, vga->crt[0x31]);
	vgaxo(Crtx, 0x32, vga->crt[0x32]);
	vgaxo(Crtx, 0x33, vga->crt[0x33]);
	vgaxo(Crtx, 0x34, vga->crt[0x34]);
	vgaxo(Crtx, 0x35, vga->crt[0x35]);
	vgaxo(Crtx, 0x3A, vga->crt[0x3A]);
	vgaxo(Crtx, 0x3B, vga->crt[0x3B]);
	vgaxo(Crtx, 0x3C, vga->crt[0x3C]);

	vgaxo(Crtx, 0x40, vga->crt[0x40]|0x01);
	vgaxo(Crtx, 0x42, vga->crt[0x42]);
	vgaxo(Crtx, 0x43, vga->crt[0x43]);
	vgaxo(Crtx, 0x45, vga->crt[0x45]);

	vgaxo(Crtx, 0x50, vga->crt[0x50]);
	vgaxo(Crtx, 0x51, vga->crt[0x51]);
	vgaxo(Crtx, 0x53, vga->crt[0x53]);
	vgaxo(Crtx, 0x54, vga->crt[0x54]);
	vgaxo(Crtx, 0x55, vga->crt[0x55]);

	if(ctlr->flag & Ulinear){
		l = vga->vmb>>16;
		vga->crt[0x59] = (l>>8) & 0xFF;
		vga->crt[0x5A] = l & 0xFF;
		if(vga->vmz <= 1024*1024)
			vga->crt[0x58] |= 0x01;
		else if(vga->vmz <= 2*1024*1024)
			vga->crt[0x58] |= 0x02;
		else
			vga->crt[0x58] |= 0x03;
	}
	vgaxo(Crtx, 0x59, vga->crt[0x59]);
	vgaxo(Crtx, 0x5A, vga->crt[0x5A]);
	vgaxo(Crtx, 0x58, vga->crt[0x58]);

	vgaxo(Crtx, 0x5D, vga->crt[0x5D]);
	vgaxo(Crtx, 0x5E, vga->crt[0x5E]);

	vgaxo(Crtx, 0x6A, vga->crt[0x6A]);

	ctlr->flag |= Fload;
}

static void
dump(Vga* vga, Ctlr* ctlr)
{
	int i, id, interlace, mul, div;
	char *name;
	uint16_t shb, vrs, x;

	name = ctlr->name;

	printitem(name, "Crt30");
	for(i = 0x30; i < 0x3E; i++)
		printreg(vga->crt[i]);

	printitem(name, "Crt40");
	for(i = 0x40; i < 0x50; i++)
		printreg(vga->crt[i]);

	printitem(name, "Crt50");
	for(i = 0x50; i < 0x60; i++)
		printreg(vga->crt[i]);

	printitem(name, "Crt60");
	for(i = 0x60; i < 0x70; i++)
		printreg(vga->crt[i]);

	/*
	 * Try to disassemble the snarfed values into
	 * understandable numbers.
	 * Only do this if we weren't called after Finit.
	 */
	if(ctlr->flag & Finit)
		return;


	/*
	 * If hde <= 400, assume this is a 928 or Vision964
	 * and the horizontal values have been divided by 4.
	 *
	 * if ViRGE/[DG]X and 15 or 16bpp, horizontal values have
	 * been multiplied by 2.
	 */
	mul = 1;
	div = 1;

	if(strcmp(name, "virge") == 0){
		id = (vga->crt[0x2D]<<8)|vga->crt[0x2E];
		/* S3 ViRGE/[DG]X */
		if(id==0x8A01 && ((vga->crt[0x67] & 0x30) || (vga->crt[0x67] & 0x50))){
			mul = 1;
			div = 2;
		}
	}

	x = vga->crt[0x01];
	if(vga->crt[0x5D] & 0x02)
		x |= 0x100;
	x = (x+1)<<3;

	if(x <= 400){
		mul = 4;
		div = 1;
	}

	x = (x * mul) / div;
	printitem(name, "hde");
	printreg(x);
	Bprint(&stdout, "%6ud", x);

	shb = vga->crt[0x02];
	if(vga->crt[0x5D] & 0x04)
		shb |= 0x100;
	shb = (shb+1)<<3;
	shb = (shb * mul) / div;
	printitem(name, "shb");
	printreg(shb);
	Bprint(&stdout, "%6ud", shb);

	x = vga->crt[0x03] & 0x1F;
	if(vga->crt[0x05] & 0x80)
		x |= 0x20;
	x = (x * mul) / div;
	x = shb|x;					/* ???? */
	if(vga->crt[0x5D] & 0x08)
		x += 64;
	printitem(name, "ehb");
	printreg(x);
	Bprint(&stdout, "%6ud", x);

	x = vga->crt[0x00];
	if(vga->crt[0x5D] & 0x01)
		x |= 0x100;
	x = (x+5)<<3;
	x = (x * mul) / div;
	printitem(name, "ht");
	printreg(x);
	Bprint(&stdout, "%6ud", x);

	interlace = vga->crt[0x42] & 0x20;
	x = vga->crt[0x12];
	if(vga->crt[0x07] & 0x02)
		x |= 0x100;
	if(vga->crt[0x07] & 0x40)
		x |= 0x200;
	if(vga->crt[0x5E] & 0x02)
		x |= 0x400;
	x += 1;
	if(interlace)
		x *= 2;
	printitem(name, "vde");
	printreg(x);
	Bprint(&stdout, "%6ud", x);

	vrs = vga->crt[0x10];
	if(vga->crt[0x07] & 0x04)
		vrs |= 0x100;
	if(vga->crt[0x07] & 0x80)
		vrs |= 0x200;
	if(vga->crt[0x5E] & 0x10)
		vrs |= 0x400;
	if(interlace)
		vrs *= 2;
	printitem(name, "vrs");
	printreg(vrs);
	Bprint(&stdout, "%6ud", vrs);

	if(interlace)
		vrs /= 2;
	x = (vrs & ~0x0F)|(vga->crt[0x11] & 0x0F);
	if(interlace)
		x *= 2;
	printitem(name, "vre");
	printreg(x);
	Bprint(&stdout, "%6ud", x);

	x = vga->crt[0x06];
	if(vga->crt[0x07] & 0x01)
		x |= 0x100;
	if(vga->crt[0x07] & 0x20)
		x |= 0x200;
	if(vga->crt[0x5E] & 0x01)
		x |= 0x400;
	x += 2;
	if(interlace)
		x *= 2;
	printitem(name, "vt");
	printreg(x);
	Bprint(&stdout, "%6ud", x);
}

Ctlr s3generic = {
	"s3",				/* name */
	snarf,				/* snarf */
	0,				/* options */
	init,				/* init */
	load,				/* load */
	dump,				/* dump */
};
