
/*
 * Matrox G200, G400 and G450.
 * Written by Philippe Anel <xigh@free.fr>
 *
 *  2006-08-07 : Minor fix to allow the G200 cards to work fine. YDSTORG is now initialized.
 *             : Also support for 16 and 24 bit modes is added.
 *             : by Leonardo Valencia <leoval@anixcorp.com>
 */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"

#define	Image IMAGE
#include <draw.h>
#include <memdraw.h>
#include <cursor.h>
#include "screen.h"

enum {
	MATROX			= 0x102B,
	MGA550			= 0x2527,
	MGA4xx			= 0x0525,
	MGA200			= 0x0521,

	FCOL			= 0x1c24,
	FXRIGHT			= 0x1cac,	
	FXLEFT			= 0x1ca8,
	YDST			= 0x1c90,
	YLEN			= 0x1c5c,
 	DWGCTL			= 0x1c00,
 		DWG_TRAP		= 0x04,
 		DWG_BITBLT		= 0x08,
 		DWG_ILOAD		= 0x09,
 		DWG_LINEAR		= 0x0080,
 		DWG_SOLID		= 0x0800,
 		DWG_ARZERO		= 0x1000,
 		DWG_SGNZERO		= 0x2000,
 		DWG_SHIFTZERO	= 0x4000,
		DWG_REPLACE		= 0x000C0000,
 		DWG_BFCOL		= 0x04000000,
 	SRCORG			= 0x2cb4,
	PITCH			= 0x1c8c,
	DSTORG			= 0x2cb8,
	YDSTORG			= 0x1c94,
	PLNWRT			= 0x1c1c,
	ZORG			= 0x1c0c,
	MACCESS			= 0x1c04,
	STATUS			= 0x1e14,
	FXBNDRY			= 0x1C84,
 	CXBNDRY			= 0x1C80,
	YTOP			= 0x1C98,
	YBOT			= 0x1C9C,
	YDSTLEN			= 0x1C88,
	AR0				= 0x1C60,
	AR1				= 0x1C64,
	AR2				= 0x1C68,
	AR3				= 0x1C6C,
	AR4				= 0x1C70,
	AR5				= 0x1C74,
	SGN				= 0x1C58,
		SGN_LEFT		= 1,
		SGN_UP			= 4,

	GO				= 0x0100,
 	FIFOSTATUS		= 0x1E10,
	CACHEFLUSH		= 0x1FFF,

	CRTCEXTIDX		= 0x1FDE,		/* CRTC Extension Index */
	CRTCEXTDATA		= 0x1FDF,		/* CRTC Extension Data */

	FILL_OPERAND	= 0x800c7804,
};

static Pcidev *
mgapcimatch(void)
{
	Pcidev *p;
	
	p = pcimatch(nil, MATROX, MGA4xx);
	if(p == nil)
		p = pcimatch(nil, MATROX, MGA550);
	if(p == nil)
		p = pcimatch(nil, MATROX, MGA200);
	return p;
}


static void
mgawrite8(VGAscr *scr, int index, uchar val)
{
	((uchar*)scr->mmio)[index] = val;
}

static uchar
mgaread8(VGAscr *scr, int index)
{
	return ((uchar*)scr->mmio)[index];
}

static uchar
crtcextset(VGAscr *scr, int index, uchar set, uchar clr)
{
	uchar tmp;

	mgawrite8(scr, CRTCEXTIDX, index);
	tmp = mgaread8(scr, CRTCEXTDATA);
	mgawrite8(scr, CRTCEXTIDX, index);
	mgawrite8(scr, CRTCEXTDATA, (tmp & ~clr) | set);

	return tmp;
}

static void
mga4xxenable(VGAscr* scr)
{
	Pcidev *pci;
	int size;
	int i, n, k;
	uchar *p;
	uchar x[16];
	uchar crtcext3;

	if(scr->mmio)
		return;

	pci = mgapcimatch();
	if(pci == nil)
		return;

	scr->mmio = vmap(pci->mem[1].bar&~0x0F, 16*1024);
	if(scr->mmio == nil)
		return;
	
	addvgaseg("mga4xxmmio", pci->mem[1].bar&~0x0F, pci->mem[1].size);

	/* need to map frame buffer here too, so vga can find memory size */
	if(pci->did == MGA4xx || pci->did == MGA550)
		size = 32*MB;
	else
		size = 8*MB;
	vgalinearaddr(scr, pci->mem[0].bar&~0x0F, size);

	if(scr->paddr){

		/* Find out how much memory is here, some multiple of 2 MB */

		/* First Set MGA Mode ... */
		crtcext3 = crtcextset(scr, 3, 0x80, 0x00);

		p = scr->vaddr;
		n = (size / MB) / 2;
		for(i = 0; i < n; i++){
			k = (2*i+1)*MB;
			p[k] = 0;
			p[k] = i+1;
			*((uchar*)scr->mmio + CACHEFLUSH) = 0;
			x[i] = p[k];
 		}
		for(i = 1; i < n; i++)
			if(x[i] != i+1)
				break;
		scr->apsize = 2*i*MB;	/* sketchy */
		addvgaseg("mga4xxscreen", scr->paddr, scr->apsize);
		crtcextset(scr, 3, crtcext3, 0xff);
	}
}

enum{
	Index		= 0x00,		/* Index */
	Data			= 0x0A,		/* Data */

	Cxlsb		= 0x0C,		/* Cursor X LSB */
	Cxmsb		= 0x0D,		/* Cursor X MSB */
	Cylsb		= 0x0E,		/* Cursor Y LSB */
	Cymsb		= 0x0F,		/* Cursor Y MSB */

	Icuradrl		= 0x04,		/* Cursor Base Address Low */	
	Icuradrh		= 0x05,		/* Cursor Base Address High */
	Icctl			= 0x06,		/* Indirect Cursor Control */
};

static void
dac4xxdisable(VGAscr *scr)
{
	uchar *dac4xx;
	
	if(scr->mmio == 0)
		return;

	dac4xx = (uchar*)scr->mmio+0x3C00;
	
	*(dac4xx+Index) = Icctl;
	*(dac4xx+Data) = 0x00;
}

static void
dac4xxload(VGAscr *scr, Cursor *curs)
{
	int y;
	uchar *p;
	uchar *dac4xx;

	if(scr->mmio == 0)
		return;

	dac4xx = (uchar*)scr->mmio+0x3C00;
	
	dac4xxdisable(scr);

	p = (uchar*)scr->storage;
	for(y = 0; y < 64; y++){
		*p++ = 0; *p++ = 0; *p++ = 0;
		*p++ = 0; *p++ = 0; *p++ = 0;
		if(y < 16){
			*p++ = curs->set[1+y*2];
			*p++ = curs->set[y*2];
		} else{
			*p++ = 0; *p++ = 0;
		}

		*p++ = 0; *p++ = 0; *p++ = 0;
		*p++ = 0; *p++ = 0; *p++ = 0;
		if(y < 16){
			*p++ = curs->set[1+y*2]|curs->clr[1+2*y];
			*p++ = curs->set[y*2]|curs->clr[2*y];
		} else{
			*p++ = 0; *p++ = 0;
		}
	}
	scr->offset.x = 64 + curs->offset.x;
	scr->offset.y = 64 + curs->offset.y;

	*(dac4xx+Index) = Icctl;
	*(dac4xx+Data) = 0x03;
}

static int
dac4xxmove(VGAscr *scr, Point p)
{
	int x, y;
	uchar *dac4xx;

	if(scr->mmio == 0)
		return 1;

	dac4xx = (uchar*)scr->mmio + 0x3C00;

	x = p.x + scr->offset.x;
	y = p.y + scr->offset.y;

	*(dac4xx+Cxlsb) = x & 0xFF;
	*(dac4xx+Cxmsb) = (x>>8) & 0x0F;

	*(dac4xx+Cylsb) = y & 0xFF;
	*(dac4xx+Cymsb) = (y>>8) & 0x0F;

	return 0;
}

static void
dac4xxenable(VGAscr *scr)
{
	uchar *dac4xx;
	ulong storage;
	
	if(scr->mmio == 0)
		return;
	dac4xx = (uchar*)scr->mmio+0x3C00;

	dac4xxdisable(scr);

	storage = (scr->apsize-4096)&~0x3ff;

	*(dac4xx+Index) = Icuradrl;
	*(dac4xx+Data) = 0xff & (storage >> 10);
	*(dac4xx+Index) = Icuradrh;
	*(dac4xx+Data) = 0xff & (storage >> 18);		

	scr->storage = (ulong)scr->vaddr + storage;

	/* Show X11-Like Cursor */
	*(dac4xx+Index) = Icctl;
	*(dac4xx+Data) = 0x03;

	/* Cursor Color 0 : White */
	*(dac4xx+Index) = 0x08;
	*(dac4xx+Data)  = 0xff;
	*(dac4xx+Index) = 0x09;
	*(dac4xx+Data)  = 0xff;
	*(dac4xx+Index) = 0x0a;
	*(dac4xx+Data)  = 0xff;

	/* Cursor Color 1 : Black */
	*(dac4xx+Index) = 0x0c;
	*(dac4xx+Data)  = 0x00;
	*(dac4xx+Index) = 0x0d;
	*(dac4xx+Data)  = 0x00;
	*(dac4xx+Index) = 0x0e;
	*(dac4xx+Data)  = 0x00;

	/* Cursor Color 2 : Red */
	*(dac4xx+Index) = 0x10;
	*(dac4xx+Data)  = 0xff;
	*(dac4xx+Index) = 0x11;
	*(dac4xx+Data)  = 0x00;
	*(dac4xx+Index) = 0x12;
	*(dac4xx+Data)  = 0x00;

	/*
	 * Load, locate and enable the
	 * 64x64 cursor in X11 mode.
	 */
	dac4xxload(scr, &arrow);
	dac4xxmove(scr, ZP);
}

static void
mga4xxblank(VGAscr *scr, int blank)
{
	char *cp;
	uchar *mga;
	uchar seq1, crtcext1;
	
	/* blank = 0 -> turn screen on */
	/* blank = 1 -> turn screen off */

	if(scr->mmio == 0)
		return;
	mga = (uchar*)scr->mmio;	

	if(blank == 0){
		seq1 = 0x00;
		crtcext1 = 0x00;
	} else {
		seq1 = 0x20;
		crtcext1 = 0x10;			/* Default value ... : standby */
		cp = getconf("*dpms");
		if(cp){
			if(cistrcmp(cp, "standby") == 0)
				crtcext1 = 0x10;
			else if(cistrcmp(cp, "suspend") == 0)
				crtcext1 = 0x20;
			else if(cistrcmp(cp, "off") == 0)
				crtcext1 = 0x30;
		}
	}

	*(mga + 0x1fc4) = 1;
	seq1 |= *(mga + 0x1fc5) & ~0x20;
	*(mga + 0x1fc5) = seq1;

	*(mga + 0x1fde) = 1;
	crtcext1 |= *(mga + 0x1fdf) & ~0x30;
	*(mga + 0x1fdf) = crtcext1;
}

static void
mgawrite32(uchar *mga, ulong reg, ulong val)
{
	*((ulong*)(&mga[reg])) = val;
}

static ulong
mgaread32(uchar *mga, ulong reg)
{
	return *((ulong*)(&mga[reg]));
}

static void
mga_fifo(uchar *mga, uchar n)
{
	ulong t;

#define Timeout 100
	for (t = 0; t < Timeout; t++)
		if ((mgaread32(mga, FIFOSTATUS) & 0xff) >= n)
			break;
	if (t >= Timeout)
		print("mga4xx: fifo timeout");
}

static int
mga4xxfill(VGAscr *scr, Rectangle r, ulong color)
{
	uchar *mga;

	if(scr->mmio == 0)
		return 0;
	mga = (uchar*)scr->mmio;

	mga_fifo(mga, 7);
	mgawrite32(mga, DWGCTL, 0);
	mgawrite32(mga, FCOL, color);
	mgawrite32(mga, FXLEFT, r.min.x);
	mgawrite32(mga, FXRIGHT, r.max.x);
	mgawrite32(mga, YDST, r.min.y);
	mgawrite32(mga, YLEN, Dy(r));
	mgawrite32(mga, DWGCTL + GO, FILL_OPERAND);

	while(mgaread32(mga, STATUS) & 0x00010000)
		;

	return 1;
}

static int
mga4xxscroll(VGAscr *scr, Rectangle dr, Rectangle sr)
{
	uchar * mga;
	int pitch;
 	int width, height;
	ulong start, end, sgn;
	Point sp, dp;
 
	if(scr->mmio == 0)
		return 0;
	mga = (uchar*)scr->mmio;

	assert(Dx(sr) == Dx(dr) && Dy(sr) == Dy(dr));

	sp = sr.min;
	dp = dr.min;
	if(eqpt(sp, dp))
		return 1;

	pitch = Dx(scr->gscreen->r);
	width = Dx(sr);
	height = Dy(sr);
	sgn = 0;

	if(dp.y > sp.y && dp.y < sp.y + height){
		sp.y += height - 1;
		dp.y += height - 1;
		sgn |= SGN_UP;
	}

	width--;
	start = end = sp.x + (sp.y * pitch);

	if(dp.x > sp.x && dp.x < sp.x + width){
		start += width;
		sgn |= SGN_LEFT;
	}
	else
		end += width;

	mga_fifo(mga, 8);
	mgawrite32(mga, DWGCTL, 0);
	mgawrite32(mga, SGN, sgn);
	mgawrite32(mga, AR5, sgn & SGN_UP ? -pitch : pitch);
	mgawrite32(mga, AR0, end);
	mgawrite32(mga, AR3, start);
	mgawrite32(mga, FXBNDRY, ((dp.x + width) << 16) | dp.x);
	mgawrite32(mga, YDSTLEN, (dp.y << 16) | height);
	mgawrite32(mga, DWGCTL + GO, DWG_BITBLT | DWG_SHIFTZERO | DWG_BFCOL | DWG_REPLACE);

	while(mgaread32(mga, STATUS) & 0x00010000)
		;

	return 1;
}

static void
mga4xxdrawinit(VGAscr *scr)
{
	uchar *mga;

	if(scr->mmio == 0)
		return;

	mga = (uchar*)scr->mmio;

	mgawrite32(mga, SRCORG, 0);
	mgawrite32(mga, DSTORG, 0);
	mgawrite32(mga, YDSTORG, 0);
	mgawrite32(mga, ZORG, 0);
	mgawrite32(mga, PLNWRT, ~0);
	mgawrite32(mga, FCOL, 0xffff0000);
	mgawrite32(mga, CXBNDRY, 0xFFFF0000);
	mgawrite32(mga, YTOP, 0);
	mgawrite32(mga, YBOT, 0x01FFFFFF);
	mgawrite32(mga, PITCH, Dx(scr->gscreen->r) & ((1 << 13) - 1));
	switch(scr->gscreen->depth){
	case 8:
		mgawrite32(mga, MACCESS, 0);
		break;
	case 16:
		mgawrite32(mga, MACCESS, 1);
		break;
	case 24:
		mgawrite32(mga, MACCESS, 3);
		break;
	case 32:
		mgawrite32(mga, MACCESS, 2);
		break;
	default:
		return;		/* depth not supported ! */
	}
	scr->fill = mga4xxfill;
	scr->scroll = mga4xxscroll;
	scr->blank = mga4xxblank;
}

VGAdev vgamga4xxdev = {
	"mga4xx",
	mga4xxenable,		/* enable */
	0,					/* disable */
	0,					/* page */
	0,					/* linear */
	mga4xxdrawinit,
};

VGAcur vgamga4xxcur = {
	"mga4xxhwgc",
	dac4xxenable,
	dac4xxdisable,
	dac4xxload,
	dac4xxmove,
};
