#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"

typedef struct CursorNM CursorNM;
struct CursorNM {
	int	enable;
	int	x;
	int	y;
	int	colour1;
	int	colour2;
	int	addr;
};

static void
neomagicenable(VGAscr* scr)
{
	Pcidev *p;
	int curoff, vmsize;
	ulong ioaddr;
	ulong iosize;

	/*
	 * scr->mmio holds the virtual address of the cursor registers
	 * in the MMIO space. This may need to change for older chips
	 * which have the MMIO space offset in the framebuffer region.
	 *
	 * scr->io holds the offset into mmio of the CursorNM struct.
	 */
	if(scr->mmio)
		return;
	if(p = pcimatch(nil, 0x10C8, 0)){
		switch(p->did){
		case 0x0003:		/* MagicGraph 128ZV */
			curoff = 0x100;
			vmsize = 1152*1024;
			ioaddr = (p->mem[0].bar & ~0x0F) + 0x200000;
			iosize = 0x200000;
			break;
		case 0x0083:		/* MagicGraph 128ZV+ */
			curoff = 0x100;
			vmsize = 1152*1024;
			ioaddr = p->mem[1].bar & ~0x0F;
			iosize = p->mem[1].size;
			break;
		case 0x0004:		/* MagicGraph 128XD */
			curoff = 0x100;
			vmsize = 2048*1024;
			ioaddr = p->mem[1].bar & ~0x0F;
			iosize = p->mem[1].size;
			break;
		case 0x0005:		/* MagicMedia 256AV */
			curoff = 0x1000;
			vmsize = 2560*1024;
			ioaddr = p->mem[1].bar & ~0x0F;
			iosize = p->mem[1].size;
			break;
		case 0x0006:		/* MagicMedia 256ZX */
			curoff = 0x1000;
			vmsize = 4096*1024;
			ioaddr = p->mem[1].bar & ~0x0F;
			iosize = p->mem[1].size;
			break;
		case 0x0016:		/* MagicMedia 256XL+ */
			curoff = 0x1000;
			/* Vaio VESA BIOS says 6080, but then hwgc doesn't work */
			vmsize = 4096*1024;
			ioaddr = p->mem[1].bar & ~0x0F;
			iosize = p->mem[1].size;
			break;
		default:
			return;
		}
	}
	else
		return;
	scr->pci = p;

	scr->mmio = vmap(ioaddr, iosize);
	if(scr->mmio == nil)
		return;
	addvgaseg("neomagicmmio", ioaddr, iosize);

	/*
	 * Find a place for the cursor data in display memory.
	 * 2 cursor images might be needed, 1KB each so use the
	 * last 2KB of the framebuffer.
	 */
	scr->storage = vmsize-2*1024;
	scr->io = curoff;
	vgalinearpci(scr);
	if(scr->paddr)
		addvgaseg("neomagicscreen", scr->paddr, scr->apsize);
}

static void
neomagiccurdisable(VGAscr* scr)
{
	CursorNM *cursornm;

	if(scr->mmio == 0)
		return;
	cursornm = (void*)((char*)scr->mmio + scr->io);
	cursornm->enable = 0;
}

static void
neomagicinitcursor(VGAscr* scr, int xo, int yo, int index)
{
	uchar *p;
	uint p0, p1;
	int x, y;

	p = (uchar*)scr->vaddr;
	p += scr->storage + index*1024;

	for(y = yo; y < 16; y++){
		p0 = scr->set[2*y];
		p1 = scr->set[2*y+1];
		if(xo){
			p0 = (p0<<xo)|(p1>>(8-xo));
			p1 <<= xo;
		}
		*p++ = p0;
		*p++ = p1;

		for(x = 16; x < 64; x += 8)
			*p++ = 0x00;

		p0 = scr->clr[2*y]|scr->set[2*y];
		p1 = scr->clr[2*y+1]|scr->set[2*y+1];
		if(xo){
			p0 = (p0<<xo)|(p1>>(8-xo));
			p1 <<= xo;
		}
		*p++ = p0;
		*p++ = p1;

		for(x = 16; x < 64; x += 8)
			*p++ = 0x00;
	}
	while(y < 64+yo){
		for(x = 0; x < 64; x += 8){
			*p++ = 0x00;
			*p++ = 0x00;
		}
		y++;
	}
}

static void
neomagiccurload(VGAscr* scr, Cursor* curs)
{
	CursorNM *cursornm;

	if(scr->mmio == 0)
		return;
	cursornm = (void*)((char*)scr->mmio + scr->io);

	cursornm->enable = 0;
	memmove(&scr->Cursor, curs, sizeof(Cursor));
	neomagicinitcursor(scr, 0, 0, 0);
	cursornm->enable = 1;
}

static int
neomagiccurmove(VGAscr* scr, Point p)
{
	CursorNM *cursornm;
	int addr, index, x, xo, y, yo;

	if(scr->mmio == 0)
		return 1;
	cursornm = (void*)((char*)scr->mmio + scr->io);

	index = 0;
	if((x = p.x+scr->offset.x) < 0){
		xo = -x;
		x = 0;
	}
	else
		xo = 0;
	if((y = p.y+scr->offset.y) < 0){
		yo = -y;
		y = 0;
	}
	else
		yo = 0;

	if(xo || yo){
		index = 1;
		neomagicinitcursor(scr, xo, yo, index);
	}
	addr = ((scr->storage+(1024*index))>>10) & 0xFFF;
	addr = ((addr & 0x00F)<<8)|((addr>>4) & 0xFF);
	if(cursornm->addr != addr)
		cursornm->addr = addr;

	cursornm->x = x;
	cursornm->y = y;

	return 0;
}

static void
neomagiccurenable(VGAscr* scr)
{
	CursorNM *cursornm;

	neomagicenable(scr);
	if(scr->mmio == 0)
		return;
	cursornm = (void*)((char*)scr->mmio + scr->io);
	cursornm->enable = 0;

	/*
	 * Cursor colours.
	 */
	cursornm->colour1 = (Pblack<<16)|(Pblack<<8)|Pblack;
	cursornm->colour2 = (Pwhite<<16)|(Pwhite<<8)|Pwhite;

	/*
	 * Load, locate and enable the 64x64 cursor.
	 */
	neomagiccurload(scr, &arrow);
	neomagiccurmove(scr, ZP);
	cursornm->enable = 1;
}

static int neomagicbltflags;

/* registers */
enum {
	BltStat = 0,
	BltCntl = 1,
	XPColor = 2,
	FGColor = 3,
	BGColor = 4,
	Pitch = 5,
	ClipLT = 6,
	ClipRB = 7,
	SrcBitOff = 8,
	SrcStartOff = 9,

	DstStartOff = 11,
	XYExt = 12,

	PageCntl = 20,
	PageBase,
	PostBase,
	PostPtr,
	DataPtr,
};

/* flags */
enum {
	NEO_BS0_BLT_BUSY =	0x00000001,
	NEO_BS0_FIFO_AVAIL =	0x00000002,
	NEO_BS0_FIFO_PEND =	0x00000004,

	NEO_BC0_DST_Y_DEC =	0x00000001,
	NEO_BC0_X_DEC =		0x00000002,
	NEO_BC0_SRC_TRANS =	0x00000004,
	NEO_BC0_SRC_IS_FG =	0x00000008,
	NEO_BC0_SRC_Y_DEC =	0x00000010,
	NEO_BC0_FILL_PAT =	0x00000020,
	NEO_BC0_SRC_MONO =	0x00000040,
	NEO_BC0_SYS_TO_VID =	0x00000080,

	NEO_BC1_DEPTH8 =	0x00000100,
	NEO_BC1_DEPTH16 =	0x00000200,
	NEO_BC1_DEPTH24 =	0x00000300,
	NEO_BC1_X_320 =		0x00000400,
	NEO_BC1_X_640 =		0x00000800,
	NEO_BC1_X_800 =		0x00000c00,
	NEO_BC1_X_1024 =	0x00001000,
	NEO_BC1_X_1152 =	0x00001400,
	NEO_BC1_X_1280 =	0x00001800,
	NEO_BC1_X_1600 =	0x00001c00,
	NEO_BC1_DST_TRANS =	0x00002000,
	NEO_BC1_MSTR_BLT =	0x00004000,
	NEO_BC1_FILTER_Z =	0x00008000,

	NEO_BC2_WR_TR_DST =	0x00800000,

	NEO_BC3_SRC_XY_ADDR =	0x01000000,
	NEO_BC3_DST_XY_ADDR =	0x02000000,
	NEO_BC3_CLIP_ON =	0x04000000,
	NEO_BC3_FIFO_EN =	0x08000000,
	NEO_BC3_BLT_ON_ADDR =	0x10000000,
	NEO_BC3_SKIP_MAPPING =	0x80000000,

	NEO_MODE1_DEPTH8 =	0x0100,
	NEO_MODE1_DEPTH16 =	0x0200,
	NEO_MODE1_DEPTH24 =	0x0300,
	NEO_MODE1_X_320 =	0x0400,
	NEO_MODE1_X_640 =	0x0800,
	NEO_MODE1_X_800 =	0x0c00,
	NEO_MODE1_X_1024 =	0x1000,
	NEO_MODE1_X_1152 =	0x1400,
	NEO_MODE1_X_1280 =	0x1800,
	NEO_MODE1_X_1600 =	0x1c00,
	NEO_MODE1_BLT_ON_ADDR =	0x2000,
};

/* Raster Operations */
enum {
	GXclear =		0x000000,	/* 0x0000 */
	GXand =			0x080000,	/* 0x1000 */
	GXandReverse =		0x040000,	/* 0x0100 */
	GXcopy =		0x0c0000,	/* 0x1100 */
	GXandInvert =		0x020000,	/* 0x0010 */
	GXnoop =		0x0a0000,	/* 0x1010 */
	GXxor =			0x060000,	/* 0x0110 */
	GXor =			0x0e0000,	/* 0x1110 */
	GXnor =			0x010000,	/* 0x0001 */
	GXequiv =		0x090000,	/* 0x1001 */
	GXinvert =		0x050000,	/* 0x0101 */
	GXorReverse =		0x0d0000,	/* 0x1101 */
	GXcopyInvert =		0x030000,	/* 0x0011 */
	GXorInverted =		0x0b0000,	/* 0x1011 */
	GXnand =		0x070000,	/* 0x0111 */
	GXset =			0x0f0000,	/* 0x1111 */
};

static void
waitforidle(VGAscr *scr)
{
	ulong *mmio;
	long x;

	mmio = scr->mmio;
	x = 0;
	while((mmio[BltStat] & NEO_BS0_BLT_BUSY) && x++ < 1000000)
		;
	//if(x >= 1000000)
	//	iprint("idle stat %lud scrmmio %#.8lux scr %#p pc %#p\n", mmio[BltStat], scr->mmio, scr, getcallerpc(&scr));
}

static void
waitforfifo(VGAscr *scr, int entries)
{
	ulong *mmio;
	long x;

	mmio = scr->mmio;
	x = 0;
	while(((mmio[BltStat]>>8) < entries) && x++ < 1000000)
		;
	//if(x >= 1000000)
	//	iprint("fifo stat %d scrmmio %#.8lux scr %#p pc %#p\n", mmio[BltStat]>>8, scr->mmio, scr, getcallerpc(&scr));
	/* DirectFB says the above doesn't work.  if so... */
	/* waitforidle(scr); */
}

static int
neomagichwfill(VGAscr *scr, Rectangle r, ulong sval)
{
	ulong *mmio;

	mmio = scr->mmio;

	waitforfifo(scr, 1);
	mmio[FGColor] = sval;
	waitforfifo(scr, 3);
	mmio[BltCntl] = neomagicbltflags
		| NEO_BC3_FIFO_EN
		| NEO_BC0_SRC_IS_FG
		| NEO_BC3_SKIP_MAPPING
		| GXcopy;
	mmio[DstStartOff] = scr->paddr
		+ r.min.y*scr->gscreen->width*BY2WD
		+ r.min.x*scr->gscreen->depth/BI2BY;
	mmio[XYExt] = (Dy(r) << 16) | (Dx(r) & 0xffff);
	waitforidle(scr);
	return 1;
}

static int
neomagichwscroll(VGAscr *scr, Rectangle r, Rectangle sr)
{
	ulong *mmio;
	int pitch, pixel;

	mmio = scr->mmio;

	pitch = scr->gscreen->width*BY2WD;
	pixel = scr->gscreen->depth/BI2BY;

	waitforfifo(scr, 4);
	if (r.min.y < sr.min.y || (r.min.y == sr.min.y && r.min.x < sr.min.x)) {
		/* start from upper-left */
		mmio[BltCntl] = neomagicbltflags
			| NEO_BC3_FIFO_EN
			| NEO_BC3_SKIP_MAPPING
			| GXcopy;
		mmio[SrcStartOff] = scr->paddr
			+ sr.min.y*pitch + sr.min.x*pixel;
		mmio[DstStartOff] = scr->paddr
			+ r.min.y*pitch + r.min.x*pixel;
	} else {
		/* start from lower-right */
		mmio[BltCntl] = neomagicbltflags
			| NEO_BC0_X_DEC
			| NEO_BC0_DST_Y_DEC
			| NEO_BC0_SRC_Y_DEC
			| NEO_BC3_FIFO_EN
			| NEO_BC3_SKIP_MAPPING
			| GXcopy;
		mmio[SrcStartOff] = scr->paddr
			+ (sr.max.y-1)*pitch + (sr.max.x-1)*pixel;
		mmio[DstStartOff] = scr->paddr
			+ (r.max.y-1)*pitch + (r.max.x-1)*pixel;
	}
	mmio[XYExt] = (Dy(r) << 16) | (Dx(r) & 0xffff);
	waitforidle(scr);
	return 1;
}

static void
neomagicdrawinit(VGAscr *scr)
{
	ulong *mmio;
	uint bltmode, pitch;

	mmio = scr->mmio;

	pitch = scr->gscreen->width*BY2WD;

	neomagicbltflags = bltmode = 0;

	switch(scr->gscreen->depth) {
	case 8:
		bltmode |= NEO_MODE1_DEPTH8;
		neomagicbltflags |= NEO_BC1_DEPTH8;
		break;
	case 16:
		bltmode |= NEO_MODE1_DEPTH16;
		neomagicbltflags |= NEO_BC1_DEPTH16;
		break;
	case 24:	/* I can't get it to work, and XFree86 doesn't either. */
	default:	/* give up */
		return;
	}

	switch(Dx(scr->gscreen->r)) {
	case 320:
		bltmode |= NEO_MODE1_X_320;
		neomagicbltflags |= NEO_BC1_X_320;
		break;
	case 640:
		bltmode |= NEO_MODE1_X_640;
		neomagicbltflags |= NEO_BC1_X_640;
		break;
	case 800:
		bltmode |= NEO_MODE1_X_800;
		neomagicbltflags |= NEO_BC1_X_800;
		break;
	case 1024:
		bltmode |= NEO_MODE1_X_1024;
		neomagicbltflags |= NEO_BC1_X_1024;
		break;
	case 1152:
		bltmode |= NEO_MODE1_X_1152;
		neomagicbltflags |= NEO_BC1_X_1152;
		break;
	case 1280:
		bltmode |= NEO_MODE1_X_1280;
		neomagicbltflags |= NEO_BC1_X_1280;
		break;
	case 1600:
		bltmode |= NEO_MODE1_X_1600;
		neomagicbltflags |= NEO_BC1_X_1600;
		break;
	default:
		/* don't worry about it */
		break;
	}

	waitforidle(scr);
	mmio[BltStat] = bltmode << 16;
	mmio[Pitch] = (pitch << 16) | (pitch & 0xffff);

	scr->fill = neomagichwfill;
	scr->scroll = neomagichwscroll;
}

VGAdev vganeomagicdev = {
	"neomagic",

	neomagicenable,
	nil,
	nil,
	nil,
	neomagicdrawinit,
};

VGAcur vganeomagiccur = {
	"neomagichwgc",

	neomagiccurenable,
	neomagiccurdisable,
	neomagiccurload,
	neomagiccurmove,
};

