#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 {
	PCIS3		= 0x5333,		/* PCI VID */

	SAVAGE3D	= 0x8A20,	/* PCI DID */
	SAVAGE3DMV	= 0x8A21,
	SAVAGE4		= 0x8A22,
	PROSAVAGEP	= 0x8A25,
	PROSAVAGEK	= 0x8A26,
	SAVAGEMXMV	= 0x8C10,
	SAVAGEMX	= 0x8C11,
	SAVAGEIXMV	= 0x8C12,
	SAVAGEIX	= 0x8C13,
	SUPERSAVAGEIXC16 = 0x8C2E,
	SAVAGE2000	= 0x9102,

	VIRGE		= 0x5631,
	VIRGEGX2	= 0x8A10,
	VIRGEDXGX	= 0x8A01,
	VIRGEVX		= 0x883D,
	VIRGEMX		= 0x8C01,
	VIRGEMXP	= 0x8C03,

	AURORA64VPLUS	= 0x8812,
};

/*
 * Savage4 et al. acceleration.
 *
 * This is based only on the Savage4 documentation.
 * It is expected to work on other Savage cards as well,
 * but has not been tried.
 * 
 * There are five ways to access the 2D graphics engine registers:
 * 	- Old MMIO non-packed format
 *	- Old MMIO packed format
 *	- New MMIO non-packed format
 *	- New MMIO packed format
 *	- Burst Command Interface (BCI)
 *
 * Of these, the manual hints that the first three are deprecated,
 * and it does not document any of those three well enough to use.
 * 
 * I have tried for many hours with no success to understand the BCI
 * interface well enough to use it.  It is not well documented, and the
 * XFree86 driver seems to completely contradict what little documentation
 * there is.
 *
 * This leaves the packed new MMIO.
 * The manual contradicts itself here, claming that the registers
 * start at 0x2008100 as well as at 0x0008100 from the base of the 
 * mmio segment.  Since the segment is only 512k, we assume that
 * the latter is the correct offset.
 *
 * According to the manual, only 16-bit reads of the 2D registers
 * are supported: 32-bit reads will return garbage in the upper word.
 * 32-bit writes must be enabled explicitly.
 * 
 * 32-bit reads of the status registers seem just fine.
 */

/* 2D graphics engine registers for Savage4; others appear to be mostly the same */
enum {
	SubsystemStatus = 0x8504,	/* Subsystem Status: read only */
	  /* read only: whether we get interrupts on various events */
	  VsyncInt		= 1<<0,		/* vertical sync */
	  GeBusyInt		= 1<<1,		/* 2D graphics engine busy */
	  BfifoFullInt	= 1<<2,		/* BIU FIFO full */
	  BfifoEmptyInt	= 1<<3,		/* BIU FIFO empty */
	  CfifoFullInt	= 1<<4,		/* command FIFO full */
	  CfifoEmptyInt	= 1<<5,		/* command FIFO empty */
	  BciInt		= 1<<6, 	/* BCI */
	  LpbInt		= 1<<7, 	/* LPB */
	  CbHiInt		= 1<<16,	/* COB upper threshold */
	  CbLoInt		= 1<<17,	/* COB lower threshold */

	SubsystemCtl 	= 0x8504,	/* Subsystem Control: write only */
	  /* clear interrupts for various events */
	  VsyncClr		= 1<<0,
	  GeBusyClr		= 1<<1,
	  BfifoFullClr	= 1<<2,
	  BfifoEmptyClr	= 1<<3,
	  CfifoFullClr	= 1<<4,
	  CfifoEmptyClr	= 1<<5,
	  BciClr		= 1<<6,
	  LpbClr		= 1<<7,
	  CbHiClr		= 1<<16,
	  CbLoClr		= 1<<17,

	  /* enable interrupts for various events */
	  VsyncEna		= 1<<8,
	  Busy2DEna		= 1<<9,
	  BfifoFullEna	= 1<<10,
	  BfifoEmptyEna	= 1<<11,
	  CfifoFullEna	= 1<<12,
	  CfifoEmptyEna	= 1<<13,
	  SubsysBciEna	= 1<<14,
	  CbHiEna		= 1<<24,
	  CbLoEna		= 1<<25,

	  /* 2D graphics engine software reset */
	  GeSoftReset	= 1<<15,

	FifoStatus		= 0x8508,	/* FIFO status: read only */
	  CwbEmpty		= 1<<0,		/* command write buffer empty */
	  CrbEmpty		= 1<<1,		/* command read buffer empty */
	  CobEmpty		= 1<<2,		/* command overflow buffer empty */
	  CfifoEmpty	= 1<<3,		/* command FIFO empty */
	  CwbFull		= 1<<8,		/* command write buffer full */
	  CrbFull		= 1<<9,		/* command read buffer full */
	  CobFull		= 1<<10,	/* command overflow buffer full */
	  CfifoFull		= 1<<11,	/* command FIFO full */

	AdvFunCtl		= 0x850C,	/* Advanced Function Control: read/write */
	  GeEna			= 1<<0,	/* enable 2D/3D engine */
	  /*
	   * according to the manual, BigPixel should be
	   * set when bpp >= 8 (bpp != 4), and then CR50_5-4 are
	   * used to figure out bpp example.  however, it does bad things
	   * to the screen in 8bpp mode.
	   */
	  BigPixel		= 1<<2,		/* 8 or more bpp enhanced mode */
	  LaEna			= 1<<3,		/* linear addressing ena: or'ed with CR58_4 */
	  Mclk_2		= 0<<8,		/* 2D engine clock divide: MCLK/2 */
	  Mclk_4		= 1<<8,		/* " MCLK/4 */
	  Mclk			= 2<<8,		/* " MCLK */
	/* Mclk			= 3<<8,		/* " MCLK */
	  Ic33mhz		= 1<<16,	/* Internal clock 33 MHz (instead of 66) */

	WakeupReg		= 0x8510,	/* Wakeup: read/write */
	  WakeupBit		= 1<<0,	/* wake up: or'ed with 3C3_0 */

	SourceY			= 0x8100,	/* UL corner of bitblt source */
	SourceX			= 0x8102,	/* " */
	RectY			= 0x8100,	/* UL corner of rectangle fill */
	RectX			= 0x8102,	/* " */
	DestY			= 0x8108,	/* UL corner of bitblt dest */
	DestX			= 0x810A,	/* " */
	Height			= 0x8148,	/* bitblt, image xfer rectangle height */
	Width			= 0x814A, 	/* bitblt, image xfer rectangle width */

	StartY			= 0x8100,	/* Line draw: first point*/
	StartX			= 0x8102,	/* " */
	/*
	 * For line draws, the following must be programmed:
	 * axial step constant = 2*min(|dx|,|dy|)
	 * diagonal step constant = 2*[min(|dx|,|dy|) - max(|dx|,|dy|)]
	 * error term = 2*min(|dx|,|dy|) - max(|dx|,|dy| - 1
	 *	[sic] when start X < end X
	 * error term = 2*min(|dx|,|dy|) - max(|dx|,|dy|
	 *  [sic] when start X >= end X
	 */
	AxialStep		= 0x8108,
	DiagonalStep	= 0x810A,
	LineError		= 0x8110,
	MinorLength		= 0x8148,	/* pixel count along minor axis */
	MajorLength		= 0x814A,	/* pixel count along major axis */

	DrawCmd			= 0x8118,	/* Drawing Command: write only */
	  CmdMagic		= 0<<1,
	  AcrossPlane	= 1<<1,		/* across the plane mode */
	  LastPixelOff	= 1<<2,		/* last pixel of line or vector draw not drawn */
	  Radial		= 1<<3,		/* enable radial direction (else axial) */
	  DoDraw		= 1<<4,		/* draw pixels (else only move current pos) */

	  DrawRight		= 1<<5,		/* axial drawing direction: left to right */
	  /* DrawLeft		= 0<<5, */
	  MajorY		= 1<<6,
	  /* MajorX		= 0<<6, */
	  DrawDown		= 1<<7,
	  /* DrawUp		= 0<<7, */
	  Degree0		= 0<<5,		/* drawing direction when Radial */
	  Degree45		= 1<<5,
		/* ... */
	  Degree315		= 7<<5,

	  UseCPUData	= 1<<8,

	  /* image write bus transfer width */
	  Bus8			= 0<<9,
	  Bus16			= 1<<9,
	  /*
	   * in Bus32 mode, doubleword bits beyond the image rect width are
	   * discarded.  each line starts on a new doubleword.
	   * Bus32AP is intended for across-the-plane mode and
	   * rounds to byte boundaries instead.
	   */
	  Bus32			= 2<<9,
	  Bus32AP		= 3<<9,

	  CmdNop		= 0<<13,	/* nop */
	  CmdLine		= 1<<13,	/* draw line */
	  CmdFill		= 2<<13,	/* fill rectangle */
	  CmdBitblt		= 6<<13,	/* bitblt */
	  CmdPatblt		= 7<<13,	/* 8x8 pattern blt */

	  SrcGBD		= 0<<16,
	  SrcPBD		= 1<<16,
	  SrcSBD		= 2<<16,

	  DstGBD		= 0<<18,
	  DstPBD		= 1<<18,
	  DstSBD		= 2<<18,

	/* color sources, controls */
	BgColor			= 0x8120,	/* Background Color: read/write */
	FgColor			= 0x8124,	/* Foreground Color: read/write */
	BitplaneWmask	= 0x8128,	/* Bitplane Write Mask: read/write */
	BitplaneRmask	= 0x812C,	/* Bitplane Read Mask: read/write */
	CmpColor		= 0x8130,	/* Color Compare: read/write */
	BgMix			= 0x8134,
	FgMix			= 0x8136,
	  MixNew		= 7,
	  SrcBg			= 0<<5,
	  SrcFg			= 1<<5,
	  SrcCPU		= 2<<5,
	  SrcDisp		= 3<<5,

	/* clipping rectangle */
	TopScissors		= 0x8138,	/* Top Scissors: write only */
	LeftScissors	= 0x813A,	/* Left Scissors: write only */
	BottomScissors	= 0x813C,	/* Bottom Scissors: write only */
	RightScissors	= 0x813E,	/* Right Scissors: write only */

	/*
	 * Registers with Magic were indirectly accessed in older modes.
	 * It is not clear whether the Magic is necessary.
	 * In the older modes, writes to these registers were pipelined,
	 * so that you had to issue an engine command and wait for engine
	 * idle before reading a write back.  It is not clear if this is
	 * still the case either.
	 */
	PixCtl			= 0x8140,	/* Pixel Control: write only */
	  PixMagic		= 0xA<<12,
	  PixMixFg		= 0<<6,		/* foreground mix register always */
	  PixMixCPU		= 2<<6,		/* CPU data determines mix register */
	  PixMixDisp	= 3<<6,		/* display data determines mix register */

	MfMisc2Ctl		= 0x8142,	/* Multifunction Control Misc. 2: write only */
	  MfMisc2Magic	= 0xD<<12,
	  DstShift		= 0,		/* 3 bits: destination base address in MB */
	  SrcShift		= 4,		/* 3 bits: source base address in MB */
	  WaitFifoEmpty	= 2<<8,		/* wait for write FIFO empty between draws */

	MfMiscCtl		= 0x8144,	/* Multifunction Control Misc: write only */
	  MfMiscMagic	= 0xE<<12,
	  UseHighBits	= 1<<4,		/* select upper 16 bits for 32-bit reg access */
	  ClipInvert	= 1<<5,		/* only touch pixels outside clip rectangle */
	  SkipSame		= 0<<6,		/* ignore pixels with color CmpColor */
	  SkipDifferent	= 1<<7,		/* ignore pixels not color CmpColor */
	  CmpEna		= 1<<8,		/* enable color compare */
	  W32Ena		= 1<<9,		/* enable 32-bit register write */
	  ClipDis		= 1<<11,	/* disable clipping */

	/*
	 * The bitmap descriptor 1 registers contain the starting
	 * address of the bitmap (in bytes).
	 * The bitmap descriptor 2 registesr contain stride (in pixels)
	 * in the lower 16 bits, depth (in bits) in the next 8 bits,
	 * and whether block write is disabled.
	 */
	GBD1			= 0x8168,	/* Global Bitmap Descriptor 1: read/write */
	GBD2			= 0x816C,	/* Global Bitmap Descriptor 2: read/write */
	  /* GBD2-only bits */
	  BDS64			= 1<<0,		/* bitmap descriptor size 64 bits */
	  GBDBciEna		= 1<<3,		/* BCI enable */
	  /* generic BD2 bits */
	  BlockWriteDis	= 1<<28,
	  StrideShift	= 0,
	  DepthShift	= 16,

	PBD1			= 0x8170,	/* Primary Bitmap Descriptor: read/write */
	PBD2			= 0x8174,
	SBD1			= 0x8178,	/* Secondary Bitmap Descriptor: read/write */
	SBD2			= 0x817C,
};

/* mastered data transfer registers */

/* configuration/status registers */
enum {
	XStatus0			= 0x48C00,	/* Status Word 0: read only */
	  /* rev. A silicon differs from rev. B; use AltStatus0 */
	  CBEMaskA		= 0x1FFFF,	/* filled command buffer entries */
	  CBEShiftA		= 0,
	  BciIdleA		= 1<<17,	/* BCI idle */
	  Ge3IdleA		= 1<<18,	/* 3D engine idle */
	  Ge2IdleA		= 1<<19,	/* 2D engine idle */
	  McpIdleA		= 1<<20,	/* motion compensation processor idle */
	  MeIdleA		= 1<<22,	/* master engine idle */
	  PfPendA		= 1<<23,	/* page flip pending */

	  CBEMaskB		= 0x1FFFFF,
	  CBEShiftB		= 0,
	  BciIdleB		= 1<<25,
	  Ge3IdleB		= 1<<26,
	  Ge2IdleB		= 1<<27,
	  McpIdleB		= 1<<28,
	  MeIdleB		= 1<<30,
	  PfPendB		= 1<<31,

	AltStatus0		= 0x48C60,	/* Alternate Status Word 0: read only */
	  CBEMask		= 0x1FFFF,
	  CBEShift		= 0,
	  /* the Savage4 manual says bits 17..23 for these, like Status0 */
	  /* empirically, they are bits 21..26 */
	  BciIdle		= 1<<21,
	  Ge3Idle		= 1<<22,
	  Ge2Idle		= 1<<23,
	  McpIdle		= 1<<24,
	  MeIdle		= 1<<25,
	  PfPend		= 1<<26,

	XStatus1			= 0x48C04,	/* Status Word 1: read only */
	  /* contains event tag 1, event tag 0, both 16 bits */

	XStatus2			= 0x48C08,	/* Status Word 2: read only */
	  ScanMask		= 0x3FF,	/* current scan line */
	  ScanShift		= 0,
	  VRTMask		= 0x7F100,	/* vert retrace count */
	  VRTShift		= 11,

	CbThresh		= 0x48C10,	/* Command Buffer Thresholds: read/write */
	CobOff			= 0x48C14,	/* Command Overflow Buffer: read/write */

	CobPtr			= 0x48C18,	/* Command Overflow Buffer Pointers: read/write */
	  CobEna		= 1<<2,		/* command overflow buffer enable */
	  CobBciEna		= 1<<3,		/* BCI function enable */
	  CbeMask		= 0xFFFF8000,	/* no. of entries in command buffer */
	  CbeShift		= 15,

	AltStatus1		= 0x48C64,	/* Alternate Status Word 1: read onnly */
	  /* contains current texture surface tag, vertex buffer tag */

};

struct {
	ulong idletimeout;
	ulong tostatw[16];
} savagestats;

enum {
	Maxloop = 1<<20
};

static void
savagewaitidle(VGAscr *scr)
{
	long x;
	ulong *statw, mask, goal;

	switch(scr->id){
	case SAVAGE4:
	case PROSAVAGEP:
	case PROSAVAGEK:
		/* wait for engine idle and FIFO empty */
		statw = (ulong*)((uchar*)scr->mmio+AltStatus0);
		mask = CBEMask | Ge2Idle;
		goal = Ge2Idle;
		break;
	/* case SAVAGEMXMV: ? */
	/* case SAVAGEMX: ? */
	/* case SAVAGEIX: ? */
	case SUPERSAVAGEIXC16:
	case SAVAGEIXMV:
	case SAVAGEMXMV:
		/* wait for engine idle and FIFO empty */
		statw = (ulong*)((uchar*)scr->mmio+XStatus0);
		mask = CBEMaskA | Ge2IdleA;
		goal = Ge2IdleA;
		break;
	default:
		/* 
		 * best we can do: can't print or we'll call ourselves.
		 * savageinit is supposed to not let this happen.
		 */	
		return;
	}

	for(x=0; x<Maxloop; x++)
		if((*statw & mask) == goal)
			return;

	savagestats.tostatw[savagestats.idletimeout++&15] = *statw;
	savagestats.tostatw[savagestats.idletimeout++&15] = (ulong)statw;
}

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

	mmio = (uchar*)scr->mmio;

	*(ulong*)(mmio+FgColor) = sval;
	*(ulong*)(mmio+BgColor) = sval;
	*(ulong*)(mmio+BgMix) = SrcFg|MixNew;
	*(ulong*)(mmio+FgMix) = SrcFg|MixNew;
	*(ushort*)(mmio+RectY) = r.min.y;
	*(ushort*)(mmio+RectX) = r.min.x;
	*(ushort*)(mmio+Width) = Dx(r)-1;
	*(ushort*)(mmio+Height) = Dy(r)-1;
	*(ulong*)(mmio+DrawCmd) = CmdMagic | DoDraw | CmdFill | DrawRight | DrawDown;
	savagewaitidle(scr);
	return 1;
}

static int
savagescroll(VGAscr *scr, Rectangle r, Rectangle sr)
{
	uchar *mmio;
	ulong cmd;
	Point dp, sp;

	cmd = CmdMagic | DoDraw | CmdBitblt | SrcPBD | DstGBD;

	if(r.min.x <= sr.min.x){
		cmd |= DrawRight;
		dp.x = r.min.x;
		sp.x = sr.min.x;
	}else{
		dp.x = r.max.x-1;
		sp.x = sr.max.x-1;
	}

	if(r.min.y <= sr.min.y){
		cmd |= DrawDown;
		dp.y = r.min.y;
		sp.y = sr.min.y;
	}else{
		dp.y = r.max.y-1;
		sp.y = sr.max.y-1;
	}

	mmio = (uchar*)scr->mmio;

	*(ushort*)(mmio+SourceX) = sp.x;
	*(ushort*)(mmio+SourceY) = sp.y;
	*(ushort*)(mmio+DestX) = dp.x;
	*(ushort*)(mmio+DestY) = dp.y;
	*(ushort*)(mmio+Width) = Dx(r)-1;
	*(ushort*)(mmio+Height) = Dy(r)-1;
	*(ulong*)(mmio+BgMix) = SrcDisp|MixNew;
	*(ulong*)(mmio+FgMix) = SrcDisp|MixNew;
	*(ulong*)(mmio+DrawCmd) = cmd;
	savagewaitidle(scr);
	return 1;
}

static void
savageblank(VGAscr*, int blank)
{
	uchar seqD;

	/*
	 * Will handle DPMS to monitor
	 */
	vgaxo(Seqx, 8, vgaxi(Seqx,8)|0x06);
	seqD = vgaxi(Seqx, 0xD);
	seqD &= 0x03;
	if(blank)
		seqD |= 0x50;
	vgaxo(Seqx, 0xD, seqD);

	/*
	 * Will handle LCD
	 */
	if(blank)
		vgaxo(Seqx, 0x31, vgaxi(Seqx, 0x31) & ~0x10);
	else
		vgaxo(Seqx, 0x31, vgaxi(Seqx, 0x31) | 0x10);
}


void
savageinit(VGAscr *scr)
{
	uchar *mmio;
	ulong bd;

	/* if you add chip IDs here be sure to update savagewaitidle */
	switch(scr->id){
	case SAVAGE4:
	case PROSAVAGEP:
	case PROSAVAGEK:
	case SAVAGEIXMV:
	case SUPERSAVAGEIXC16:
	case SAVAGEMXMV:
		break;
	default:
		print("unknown savage %.4lux\n", scr->id);
		return;
	}

	mmio = (uchar*)scr->mmio;
	if(mmio == nil) {
		print("savageinit: no mmio\n");
		return;
	}

	/* 2D graphics engine software reset */
	*(ushort*)(mmio+SubsystemCtl) = GeSoftReset;
	delay(2);
	*(ushort*)(mmio+SubsystemCtl) = 0;
	savagewaitidle(scr);

	/* disable BCI as much as possible */
	*(ushort*)(mmio+CobPtr) &= ~CobBciEna;
	*(ushort*)(mmio+GBD2) &= ~GBDBciEna;
	savagewaitidle(scr);

	/* enable 32-bit writes, disable clipping */
	*(ushort*)(mmio+MfMiscCtl) = MfMiscMagic|W32Ena|ClipDis;
	savagewaitidle(scr);

	/* enable all read, write planes */
	*(ulong*)(mmio+BitplaneRmask) = ~0;
	*(ulong*)(mmio+BitplaneWmask) = ~0;
	savagewaitidle(scr);

	/* turn on linear access, 2D engine */
	*(ulong*)(mmio+AdvFunCtl) |= GeEna|LaEna;
	savagewaitidle(scr);

	/* set bitmap descriptors */
	bd = (scr->gscreen->depth<<DepthShift) |
		(Dx(scr->gscreen->r)<<StrideShift) | BlockWriteDis
		| BDS64;

	*(ulong*)(mmio+GBD1) = 0;
	*(ulong*)(mmio+GBD2) = bd;

	*(ulong*)(mmio+PBD1) = 0;
	*(ulong*)(mmio+PBD2) = bd;

	*(ulong*)(mmio+SBD1) = 0;
	*(ulong*)(mmio+SBD2) = bd;

	/*
	 * For some reason, the GBD needs to get programmed twice,
	 * once before the PBD, SBD, and once after.
	 * This empirically makes it get set right.
	 * I would like to better understand the ugliness
	 * going on here.
	 */
	*(ulong*)(mmio+GBD1) = 0;
	*(ulong*)(mmio+GBD2) = bd;
	*(ushort*)(mmio+GBD2+2) = bd>>16;
	savagewaitidle(scr);

	scr->fill = savagefill;
	scr->scroll = savagescroll;
	scr->blank = savageblank;
	hwblank = 0;
}

