/*
 * 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"

enum {
	NSeqx		= 0x05,
	NCrtx		= 0x19,
	NGrx		= 0x09,
	NAttrx		= 0x15,
};

uint8_t
vgai(int32_t port)
{
	uint8_t data;

	switch(port){

	case MiscR:
	case Status0:
	case Status1:
	case FeatureR:
	case PaddrW:
	case Pdata:
	case Pixmask:
	case Pstatus:
		data = inportb(port);
		break;

	default:
		error("vgai(0x%4.4lX): unknown port\n", port);
		/*NOTREACHED*/
		data = 0xFF;
		break;
	}
	return data;
}

uint8_t
vgaxi(int32_t port, uint8_t index)
{
	uint8_t data;

	switch(port){

	case Seqx:
	case Crtx:
	case Grx:
		outportb(port, index);
		data = inportb(port+1);
		break;

	case Attrx:
		/*
		 * Allow processor access to the colour
		 * palette registers. Writes to Attrx must
		 * be preceded by a read from Status1 to
		 * initialise the register to point to the
		 * index register and not the data register.
		 * Processor access is allowed by turning
		 * off bit 0x20.
		 */
		inportb(Status1);
		if(index < 0x10){
			outportb(Attrx, index);
			data = inportb(Attrx+1);
			inportb(Status1);
			outportb(Attrx, 0x20|index);
		}
		else{
			outportb(Attrx, 0x20|index);
			data = inportb(Attrx+1);
		}
		break;

	default:
		error("vgaxi(0x%4.4lx, 0x%2.2uX): unknown port\n", port, index);
		/*NOTREACHED*/
		data = 0xFF;
		break;
	}
	return data;
}

void
vgao(int32_t port, uint8_t data)
{
	switch(port){

	case MiscW:
	case FeatureW:
	case PaddrW:
	case Pdata:
	case Pixmask:
	case PaddrR:
		outportb(port, data);
		break;

	default:
		error("vgao(0x%4.4lX, 0x%2.2uX): unknown port\n", port, data);
		/*NOTREACHED*/
		break;
	}
}

void
vgaxo(int32_t port, uint8_t index, uint8_t data)
{
	switch(port){

	case Seqx:
	case Crtx:
	case Grx:
		/*
		 * We could use an outport here, but some chips
		 * (e.g. 86C928) have trouble with that for some
		 * registers.
		 */
		outportb(port, index);
		outportb(port+1, data);
		break;

	case Attrx:
		inportb(Status1);
		if(index < 0x10){
			outportb(Attrx, index);
			outportb(Attrx, data);
			inportb(Status1);
			outportb(Attrx, 0x20|index);
		}
		else{
			outportb(Attrx, 0x20|index);
			outportb(Attrx, data);
		}
		break;

	default:
		error("vgaxo(0x%4.4lX, 0x%2.2uX, 0x%2.2uX): unknown port\n",
			port, index, data);
		break;
	}
}

static void
snarf(Vga* vga, Ctlr* ctlr)
{
	int i;

	/*
	 * Generic VGA registers:
	 * 	misc, feature;
	 *	sequencer;
	 *	crt;
	 *	graphics;
	 *	attribute;
	 *	palette.
	 */
	vga->misc = vgai(MiscR);
	vga->feature = vgai(FeatureR);

	for(i = 0; i < NSeqx; i++)
		vga->sequencer[i] = vgaxi(Seqx, i);

	for(i = 0; i < NCrtx; i++)
		vga->crt[i] = vgaxi(Crtx, i);

	for(i = 0; i < NGrx; i++)
		vga->graphics[i] = vgaxi(Grx, i);

	for(i = 0; i < NAttrx; i++)
		vga->attribute[i] = vgaxi(Attrx, i);

	if(dflag)
		palette.snarf(vga, ctlr);

	ctlr->flag |= Fsnarf;
}

static void
init(Vga* vga, Ctlr* ctlr)
{
	Mode *mode;
	int vt, vde, vrs, vre;
	uint32_t tmp;

	mode = vga->mode;

	memset(vga->sequencer, 0, NSeqx*sizeof(vga->sequencer[0]));
	memset(vga->crt, 0, NCrtx*sizeof(vga->crt[0]));
	memset(vga->graphics, 0, NGrx*sizeof(vga->graphics[0]));
	memset(vga->attribute, 0, NAttrx*sizeof(vga->attribute[0]));
	if(dflag)
		memset(vga->palette, 0, sizeof(vga->palette));

	/*
	 * Misc. If both the horizontal and vertical sync polarity
	 * options are set, use them. Otherwise use the defaults for
	 * the given vertical size.
	 */
	vga->misc = 0x23;
	if(mode->frequency == VgaFreq1)
		vga->misc |= 0x04;
	if(mode->hsync && mode->vsync){
		if(mode->hsync == '-')
			vga->misc |= 0x40;
		if(mode->vsync == '-')
			vga->misc |= 0x80;
	}
	else{
		if(mode->y < 480)
			vga->misc |= 0x40;
		else if(mode->y < 400)
			vga->misc |= 0x80;
		else if(mode->y < 768)
			vga->misc |= 0xC0;
	}

	/*
	 * Sequencer
	 */
	vga->sequencer[0x00] = 0x03;
	vga->sequencer[0x01] = 0x01;
	vga->sequencer[0x02] = 0x0F;
	vga->sequencer[0x03] = 0x00;
	if(mode->z >= 8)
		vga->sequencer[0x04] = 0x0A;
	else
		vga->sequencer[0x04] = 0x06;

	/*
	 * Crt. Most of the work here is in dealing
	 * with field overflow.
	 */
	memset(vga->crt, 0, NCrtx);

	vga->crt[0x00] = (mode->ht>>3)-5;
	vga->crt[0x01] = (mode->x>>3)-1;
	vga->crt[0x02] = (mode->shb>>3)-1;

	/*
	 * End Horizontal Blank is a 6-bit field, 5-bits
	 * in Crt3, high bit in Crt5.
	 */
	tmp = mode->ehb>>3;
	vga->crt[0x03] = 0x80|(tmp & 0x1F);
	if(tmp & 0x20)
		vga->crt[0x05] |= 0x80;

	if(mode->shs == 0)
		mode->shs = mode->shb;
	vga->crt[0x04] = mode->shs>>3;
	if(mode->ehs == 0)
		mode->ehs = mode->ehb;
	vga->crt[0x05] |= ((mode->ehs>>3) & 0x1F);

	/*
	 * Vertical Total is 10-bits, 8 in Crt6, the high
	 * two bits in Crt7. What if vt is >= 1024? We hope
	 * the specific controller has some more overflow
	 * bits.
	 *
	 * Interlace: if 'v',  divide the vertical timing
	 * values by 2.
	 */
	vt = mode->vt;
	vde = mode->y;
	vrs = mode->vrs;
	vre = mode->vre;

	if(mode->interlace == 'v'){
		vt /= 2;
		vde /= 2;
		vrs /= 2;
		vre /= 2;
	}

	tmp = vt-2;
	vga->crt[0x06] = tmp;
	if(tmp & 0x100)
		vga->crt[0x07] |= 0x01;
	if(tmp & 0x200)
		vga->crt[0x07] |= 0x20;

	tmp = vrs;
	vga->crt[0x10] = tmp;
	if(tmp & 0x100)
		vga->crt[0x07] |= 0x04;
	if(tmp & 0x200)
		vga->crt[0x07] |= 0x80;

	vga->crt[0x11] = 0x20|(vre & 0x0F);

	tmp = vde-1;
	vga->crt[0x12] = tmp;
	if(tmp & 0x100)
		vga->crt[0x07] |= 0x02;
	if(tmp & 0x200)
		vga->crt[0x07] |= 0x40;

	vga->crt[0x15] = vrs;
	if(vrs & 0x100)
		vga->crt[0x07] |= 0x08;
	if(vrs & 0x200)
		vga->crt[0x09] |= 0x20;

	vga->crt[0x16] = (vrs+1);

	vga->crt[0x17] = 0x83;
	tmp = ((vga->virtx*mode->z)/8);
	if(tmp >= 512){
		vga->crt[0x14] |= 0x60;
		tmp /= 8;
	}
	else if(tmp >= 256){
		vga->crt[0x17] |= 0x08;
		tmp /= 4;
	}
	else{
		vga->crt[0x17] |= 0x40;
		tmp /= 2;
	}
	vga->crt[0x13] = tmp;

	if(mode->x*mode->y*mode->z/8 > 64*1024)
		vga->crt[0x17] |= 0x20;

	vga->crt[0x18] = 0x7FF;
	if(vga->crt[0x18] & 0x100)
		vga->crt[0x07] |= 0x10;
	if(vga->crt[0x18] & 0x200)
		vga->crt[0x09] |= 0x40;

	/*
	 * Graphics
	 */
	memset(vga->graphics, 0, NGrx);
	if((vga->sequencer[0x04] & 0x04) == 0)
		vga->graphics[0x05] |= 0x10;
	if(mode->z >= 8)
		vga->graphics[0x05] |= 0x40;
	vga->graphics[0x06] = 0x05;
	vga->graphics[0x07] = 0x0F;
	vga->graphics[0x08] = 0xFF;

	/*
	 * Attribute
	 */
	memset(vga->attribute, 0, NAttrx);
	for(tmp = 0; tmp < 0x10; tmp++)
		vga->attribute[tmp] = tmp;
	vga->attribute[0x10] = 0x01;
	if(mode->z >= 8)
		vga->attribute[0x10] |= 0x40;
	vga->attribute[0x11] = 0xFF;
	vga->attribute[0x12] = 0x0F;

	/*
	 * Palette
	 */
	if(dflag)
		palette.init(vga, ctlr);

	ctlr->flag |= Finit;
}

static void
load(Vga* vga, Ctlr* ctlr)
{
	int i;

	/*
	 * Reset the sequencer and leave it off.
	 * Load the generic VGA registers:
	 *	misc;
	 *	sequencer (but not seq01, display enable);
	 *	take the sequencer out of reset;
	 *	take off write-protect on crt[0x00-0x07];
	 *	crt;
	 *	graphics;
	 *	attribute;
	 *	palette.
	vgaxo(Seqx, 0x00, 0x00);
	 */

	vgao(MiscW, vga->misc);

	for(i = 2; i < NSeqx; i++)
		vgaxo(Seqx, i, vga->sequencer[i]);
	/*vgaxo(Seqx, 0x00, 0x03);*/

	vgaxo(Crtx, 0x11, vga->crt[0x11] & ~0x80);
	for(i = 0; i < NCrtx; i++)
		vgaxo(Crtx, i, vga->crt[i]);

	for(i = 0; i < NGrx; i++)
		vgaxo(Grx, i, vga->graphics[i]);

	for(i = 0; i < NAttrx; i++)
		vgaxo(Attrx, i, vga->attribute[i]);

	if(dflag)
		palette.load(vga, ctlr);

	ctlr->flag |= Fload;
}

static void
dump(Vga* vga, Ctlr* ctlr)
{
	int i;

	printitem(ctlr->name, "misc");
	printreg(vga->misc);
	printitem(ctlr->name, "feature");
	printreg(vga->feature);

	printitem(ctlr->name, "sequencer");
	for(i = 0; i < NSeqx; i++)
		printreg(vga->sequencer[i]);

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

	printitem(ctlr->name, "graphics");
	for(i = 0; i < NGrx; i++)
		printreg(vga->graphics[i]);

	printitem(ctlr->name, "attribute");
	for(i = 0; i < NAttrx; i++)
		printreg(vga->attribute[i]);

	if(dflag)
		palette.dump(vga, ctlr);

	printitem(ctlr->name, "virtual");
	Bprint(&stdout, "%ld %ld\n", vga->virtx, vga->virty);
	printitem(ctlr->name, "panning");
	Bprint(&stdout, "%s\n", vga->panning ? "on" : "off");
	if(vga->f[0]){
		printitem(ctlr->name, "clock[0] f");
		Bprint(&stdout, "%9ld\n", vga->f[0]);
		printitem(ctlr->name, "clock[0] d i m");
		Bprint(&stdout, "%9ld %8ld       - %8ld\n",
			vga->d[0], vga->i[0], vga->m[0]);
		printitem(ctlr->name, "clock[0] n p q r");
		Bprint(&stdout, "%9ld %8ld       - %8ld %8ld\n",
			vga->n[0], vga->p[0], vga->q[0], vga->r[0]);
	}
	if(vga->f[1]){
		printitem(ctlr->name, "clock[1] f");
		Bprint(&stdout, "%9ld\n", vga->f[1]);
		printitem(ctlr->name, "clock[1] d i m");
		Bprint(&stdout, "%9ld %8ld       - %8ld\n",
			vga->d[1], vga->i[1], vga->m[1]);
		printitem(ctlr->name, "clock[1] n p q r");
		Bprint(&stdout, "%9ld %8ld       - %8ld %8ld\n",
			vga->n[1], vga->p[1], vga->q[1], vga->r[1]);
	}

	if(vga->vma || vga->vmb){
		printitem(ctlr->name, "vm a b");
		Bprint(&stdout, "%9lud %8lud\n", vga->vma, vga->vmb);
	}
	if(vga->vmz){
		printitem(ctlr->name, "vmz");
		Bprint(&stdout, "%9lud\n", vga->vmz);
	}
	printitem(ctlr->name, "apz");
	Bprint(&stdout, "%9lud\n", vga->apz);

	printitem(ctlr->name, "linear");
	Bprint(&stdout, "%9d\n", vga->linear);
}

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