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

#define fpga ((ulong*) FPGAMGR_BASE)

enum { REMAP = 0x0 / 4 };

enum { Timeout = 3000 };

enum {
	FPGASTAT,
	FPGACTRL,
	FPGAINTEN = 0x830/4,
	FPGAINTTYPE = 0x838/4,
	FPGAINTPOL = 0x83C/4,
	FPGAINTSTATUS = 0x840/4,
	FPGAEOI = 0x84C/4,
	FPGAPINS = 0x850/4,
};

enum {
	/*FPGACTRL*/
	HPSCONFIG = 1<<0,
	NCONFIGPULL = 1<<2,
	AXICFGEN = 1<<8,
	/*FPGAPINS*/
	NSTATUS = 1<<0,
	CONF_DONE = 1<<1,
	INIT_DONE = 1<<2,
	CRC_ERROR = 1<<3,
	CVP_CONF_DONE = 1<<4,
	PR_READY = 1<<5,
	PR_ERROR = 1<<6,
	PR_DONE = 1<<7,
	NCONFIG_PIN = 1<<8,
	NSTATUS_PIN = 1<<9,
	CONF_DONE_PIN = 1<<10,
	FPGA_POWER_ON = 1<<11
};

enum {
	Qdir = 0,
	Qfpga,
	Qbase,

	Qmax = 16,
};

static Dirtab archdir[Qmax] = {
	".",		{ Qdir, 0, QTDIR },	0,	0555,
	"fpga",		{ Qfpga, 0 }, 		0,	0660,
};
static int narchdir = Qbase;

static Physseg *axi;

static Ref fpgawopen;
enum { FPGABUFSIZ = 65536 };
static uchar *fpgabuf;
static int fpgabufp;
static int fpgaok;

static Rendez fpgarend;
static u32int fpgawaitset, fpgawaitclr;
static int
donewaiting(void *)
{
	u32int s;
	
	s = fpga[FPGAPINS];
	return (s & fpgawaitset | ~s & fpgawaitclr) != 0;
	
}
static void
fpgairq(Ureg *, void *)
{
	fpga[FPGAEOI] = -1;
	if(donewaiting(nil))
		wakeup(&fpgarend);
}
static int
fpgawait(u32int set, u32int clr, int timeout)
{
	int s;

	fpgawaitset = set;
	fpgawaitclr = clr;
	if(donewaiting(nil)) return 0;
	s = spllo();
	fpga[FPGAINTEN] = 0;
	fpga[FPGAEOI] = -1;
	fpga[FPGAINTPOL] = set;
	fpga[FPGAINTEN] = set | clr;
	tsleep(&fpgarend, donewaiting, nil, timeout);
	fpga[FPGAINTEN] = 0;
	fpga[FPGAEOI] = -1;
	splx(s);
	return donewaiting(nil) ? 0 : -1;
}

static void
fpgaconf(void)
{
	int msel;
	enum { PORFAST = 1, AES = 2, AESMAYBE = 4, COMP = 8, FPP32 = 16 };
	static uchar mseltab[16][3] = {
		[0] {0, 1, PORFAST},
		[4] {0, 1, 0},
		[1] {0, 2, PORFAST|AES},
		[5] {0, 2, AES},
		[2] {0, 3, COMP|AESMAYBE|PORFAST},
		[6] {0, 3, COMP|AESMAYBE},
		[8] {1, 1, PORFAST|FPP32},
		[12] {1, 1, FPP32},
		[9] {1, 2, AES|FPP32|PORFAST},
		[13] {1, 2, AES|FPP32},
		[10] {1, 4, COMP|AESMAYBE|PORFAST|FPP32},
		[14] {1, 4, COMP|AESMAYBE|FPP32}
	};

	axi->attr |= SG_FAULT;
	procflushpseg(axi);
	flushmmu();

	if((fpga[FPGAPINS] & FPGA_POWER_ON) == 0)
		error("FPGA powered off");
	msel = fpga[FPGASTAT] >> 3 & 0x1f;
	if(msel >= 16 || mseltab[msel][1] == 0){
		print("MSEL set to invalid setting %#.2x\n", msel);
		error("MSEL set to invalid setting");
	}
	fpga[FPGACTRL] = fpga[FPGACTRL] & ~0x3ff
		| mseltab[msel][0] << 9 /* cfgwdth */
		| mseltab[msel][1]-1 << 6 /* cdratio */
		| NCONFIGPULL | HPSCONFIG;
	if(fpgawait(0, NSTATUS, Timeout) < 0 || fpgawait(0, CONF_DONE, Timeout) < 0)
		error("FPGA won't enter reset phase");
	fpga[FPGACTRL] &= ~NCONFIGPULL;
	if(fpgawait(NSTATUS, 0, Timeout) < 0)
		error("FPGA won't enter configuration phase");
	fpga[FPGACTRL] |= AXICFGEN;
}

static void
fpgawrite(uchar *a, int n)
{
	int m;

	if(waserror()){
		fpgaok = 0;
		nexterror();
	}
	if((fpga[FPGAPINS] & NSTATUS) == 0)
		error("FPGA reports configuration error");
	fpgaok = 1;
	while(fpgabufp + n >= FPGABUFSIZ){
		m = FPGABUFSIZ - fpgabufp;
		memmove(&fpgabuf[fpgabufp], a, m);
		cleandse(fpgabuf, fpgabuf + FPGABUFSIZ);
		dmacopy((void *) FPGAMGRDATA, fpgabuf, FPGABUFSIZ, SRC_INC);
		a += m;
		n -= m;
		fpgabufp = 0;
	}
	memmove(&fpgabuf[fpgabufp], a, n);
	fpgabufp += n;
	poperror();
}

static void
fpgafinish(void)
{
	if(!fpgaok) return;
	while((fpgabufp & 3) != 0)
		fpgabuf[fpgabufp++] = 0;
	cleandse(fpgabuf, fpgabuf + fpgabufp);
	dmacopy((void *) FPGAMGRDATA, fpgabuf, fpgabufp, SRC_INC);
	fpga[FPGACTRL] &= ~AXICFGEN;
	if(fpgawait(CONF_DONE, NSTATUS, Timeout) < 0){
		print("FPGA stuck in configuration phase -- truncated file?\n");
		return;
	}
	if((fpga[FPGAPINS] & NSTATUS) == 0){
		print("FPGA reports configuration error\n");
		return;
	}
	if(fpgawait(INIT_DONE, 0, Timeout) < 0){
		print("FPGA stuck in initialization phase\n");
		return;
	}
	fpga[FPGACTRL] &= ~HPSCONFIG;
	
	axi->attr &= ~SG_FAULT;
	procflushpseg(axi);
	flushmmu();
}

static long
archread(Chan *c, void *a, long n, vlong)
{
	switch((ulong)c->qid.path){
	case Qdir:
		return devdirread(c, a, n, archdir, narchdir, devgen);
	default:
		error(Egreg);
	}
}

static long
archwrite(Chan *c, void *a, long n, vlong)
{
	switch((ulong)c->qid.path){
	case Qfpga:
		fpgawrite(a, n);
		return n;
	default:
		error(Egreg);
	}
}

Walkqid*
archwalk(Chan* c, Chan *nc, char** name, int nname)
{
	return devwalk(c, nc, name, nname, archdir, narchdir, devgen);
}

static int
archstat(Chan* c, uchar* dp, int n)
{
	return devstat(c, dp, n, archdir, narchdir, devgen);
}

static Chan*
archopen(Chan* c, int omode)
{
	devopen(c, omode, archdir, narchdir, devgen);
	if((ulong)c->qid.path == Qfpga && (c->mode == OWRITE || c->mode == ORDWR)){
		if(incref(&fpgawopen) != 1){
			c->flag &= ~COPEN;
			decref(&fpgawopen);
			error(Einuse);
		}
		fpgaok = 0;
		fpgaconf();
		fpgabuf = xspanalloc(FPGABUFSIZ, 64, 0);
		fpgabufp = 0;
	}
	return c;
}

static void
archclose(Chan* c)
{
	if((c->flag & COPEN) != 0)
	if((ulong)c->qid.path == Qfpga && (c->mode == OWRITE || c->mode == ORDWR)){
		fpgafinish();
		//xfree(fpgabuf);
		fpgabuf = nil;
		decref(&fpgawopen);
	}
}

void
archinit(void)
{
	Physseg seg;

	fpga[FPGAINTEN] = 0;
	fpga[FPGAEOI] = -1;
	fpga[FPGAINTTYPE] = -1;
	intrenable(FPGAMGRIRQ, fpgairq, nil, LEVEL, "fpgamgr");
	
	resetmgr[BRGMODRST] &= ~7;
	l3[REMAP] = 0x18;

	memset(&seg, 0, sizeof seg);
	seg.attr = SG_PHYSICAL | SG_DEVICE | SG_NOEXEC | SG_FAULT;
	seg.name = "axi";
	seg.pa = 0xFF200000;
	seg.size = 0x200000;
	axi = addphysseg(&seg);

}

static Chan*
archattach(char* spec)
{
	return devattach('P', spec);
}

Dev archdevtab = {
	'P',
	"arch",
	
	devreset,
	devinit,
	devshutdown,
	archattach,
	archwalk,
	archstat,
	archopen,
	devcreate,
	archclose,
	archread,
	devbread,
	archwrite,
	devbwrite,
	devremove,
	devwstat,
};

