/*
 *	8260 specific stuff:
 *		Interrupt handling
 */
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"io.h"
#include	"fns.h"
#include	"m8260.h"

enum {
	Pin4 = BIT(4),
};

static union {
	struct {
		ulong	hi;
		ulong	lo;
	};
	uvlong		val;
} ticks;

struct {
	ulong	hi;
	ulong	lo;
} vec2mask[64]	= {
[0]	= {0,		0	},	/* Error, No interrupt */
[1]	= {0,		BIT(16)	},	/* I2C */
[2]	= {0,		BIT(17)	},	/* SPI */
[3]	= {0,		BIT(18)	},	/* Risc Timers */
[4]	= {0,		BIT(19)	},	/* SMC1 */
[5]	= {0,		BIT(20)	},	/* SMC2 */
[6]	= {0,		BIT(21)	},	/* IDMA1 */
[7]	= {0,		BIT(22)	},	/* IDMA2 */
[8]	= {0,		BIT(23)	},	/* IDMA3 */
[9]	= {0,		BIT(24)	},	/* IDMA4 */
[10]	= {0,		BIT(25)	},	/* SDMA */
[11]	= {0,		0	},	/* Reserved */
[12]	= {0,		BIT(27)	},	/* Timer1 */
[13]	= {0,		BIT(28)	},	/* Timer2 */
[14]	= {0,		BIT(29)	},	/* Timer3 */
[15]	= {0,		BIT(30)	},	/* Timer4 */

[16]	= {BIT(29),	0	},	/* TMCNT */
[17]	= {BIT(30),	0	},	/* PIT */
[18]	= {0,		0	},	/* Reserved */
[19]	= {BIT(17),	0	},	/* IRQ1 */
[20]	= {BIT(18),	0	},	/* IRQ2 */
[21]	= {BIT(19),	0	},	/* IRQ3 */
[22]	= {BIT(20),	0	},	/* IRQ4 */
[23]	= {BIT(21),	0	},	/* IRQ5 */
[24]	= {BIT(22),	0	},	/* IRQ6 */
[25]	= {BIT(23),	0	},	/* IRQ7 */
[26]	= {0,		0	},	/* Reserved */
[27]	= {0,		0	},	/* Reserved */
[28]	= {0,		0	},	/* Reserved */
[29]	= {0,		0	},	/* Reserved */
[30]	= {0,		0	},	/* Reserved */
[31]	= {0,		0	},	/* Reserved */

[32]	= {0,		BIT(0)	},	/* FCC1 */
[33]	= {0,		BIT(1)	},	/* FCC2 */
[34]	= {0,		BIT(2)	},	/* FCC3 */
[35]	= {0,		0	},	/* Reserved */
[36]	= {0,		BIT(4)	},	/* MCC1 */
[37]	= {0,		BIT(5)	},	/* MCC2 */
[38]	= {0,		0	},	/* Reserved */
[39]	= {0,		0	},	/* Reserved */
[40]	= {0,		BIT(8)	},	/* SCC1 */
[41]	= {0,		BIT(9)	},	/* SCC2 */
[42]	= {0,		BIT(10)	},	/* SCC3 */
[43]	= {0,		BIT(11)	},	/* SCC4 */
[44]	= {0,		0	},	/* Reserved */
[45]	= {0,		0	},	/* Reserved */
[46]	= {0,		0	},	/* Reserved */
[47]	= {0,		0	},	/* Reserved */

[48]	= {BIT(15),	0	},	/* PC15 */
[49]	= {BIT(14),	0	},	/* PC14 */
[50]	= {BIT(13),	0	},	/* PC13 */
[51]	= {BIT(12),	0	},	/* PC12 */
[52]	= {BIT(11),	0	},	/* PC11 */
[53]	= {BIT(10),	0	},	/* PC10 */
[54]	= {BIT(9),	0	},	/* PC9 */
[55]	= {BIT(8),	0	},	/* PC8 */
[56]	= {BIT(7),	0	},	/* PC7 */
[57]	= {BIT(6),	0	},	/* PC6 */
[58]	= {BIT(5),	0	},	/* PC5 */
[59]	= {BIT(4),	0	},	/* PC4 */
[60]	= {BIT(3),	0	},	/* PC3 */
[61]	= {BIT(2),	0	},	/* PC2 */
[62]	= {BIT(1),	0	},	/* PC1 */
[63]	= {BIT(0),	0	},	/* PC0 */
};

/* Blast memory layout:
 *	CS0: FE000000 -> FFFFFFFF (Flash)
 *	CS1: FC000000 -> FCFFFFFF (DSP hpi)
 *	CS2: 00000000 -> 03FFFFFF (60x sdram)
 *	CS3: 04000000 -> 04FFFFFF (FPGA)
 *	CS4: 05000000 -> 06FFFFFF (local bus sdram)
 *	CS5: 07000000 -> 0700FFFF (eeprom - not populated)
 *	CS6: E0000000 -> E0FFFFFF (FPGA - 64bits)
 *
 * Main Board memory layout:
 *	CS0: FE000000 -> FEFFFFFF (16 M FLASH)
 *	CS1: FC000000 -> FCFFFFFF (16 M DSP1)
 *	CS2: 00000000 -> 03FFFFFF (64 M SDRAM)
 *	CS3: 04000000 -> 04FFFFFF (16M DSP2)
 *	CS4: 05000000 -> 06FFFFFF (32 M Local SDRAM)
 *	CS5: 07000000 -> 0700FFFF (eeprom - not populated)
 *	CS6: unused
 *	CS7: E0000000 -> E0FFFFFF (16 M FPGA)
 */

IMM* iomem = (IMM*)IOMEM;

static Lock cpmlock;

void
machinit(void)
{
	ulong scmr;
	int pllmf;
	extern char* plan9inistr;

	memset(m, 0, sizeof(*m));
	m->cputype = getpvr()>>16;	/* pvr = 0x00810101 for the 8260 */
	m->imap = (Imap*)INTMEM;

	m->loopconst = 1096;

	/* Make sure Ethernet is disabled (boot code may have buffers allocated anywhere in memory) */
	iomem->fcc[0].gfmr &= ~(BIT(27)|BIT(26));
	iomem->fcc[1].gfmr &= ~(BIT(27)|BIT(26));
	iomem->fcc[2].gfmr &= ~(BIT(27)|BIT(26));

	/* Flashed CS configuration is wrong for DSP2.  It's set to 64 bits, should be 16 */
	iomem->bank[3].br = 0x04001001;	/* Set 16-bit port */

	/*
	 * FPGA is capable of doing 64-bit transfers.  To use these, set br to 0xe0000001.
	 * Currently we use 32-bit transfers, because the 8260 does not easily do 64-bit operations.
	 */
	iomem->bank[6].br = 0xe0001801;
	iomem->bank[6].or = 0xff000830;	/* Was 0xff000816 */

/*
 * All systems with rev. A.1 (0K26N) silicon had serious problems when doing
 * DMA transfers with data cache enabled (usually this shows when  using
 * one of the FCC's with some traffic on the ethernet).  Allocating FCC buffer
 * descriptors in main memory instead of DP ram solves this problem.
 */

	/* Guess at clocks based upon the PLL configuration from the
	 * power-on reset.
	 */
	scmr = iomem->scmr;

	/* The EST8260 is typically run using either 33 or 66 MHz
	 * external clock.  The configuration byte in the Flash will
	 * tell us which is configured.  The blast appears to be slightly
	 * overclocked at 72 MHz (if set to 66 MHz, the uart runs too fast)
	 */

	m->clkin = CLKIN;

	pllmf = scmr & 0xfff;

	/* This is arithmetic from the 8260 manual, section 9.4.1. */

	/* Collect the bits from the scmr.
	*/
	m->vco_out = m->clkin * (pllmf + 1);
	if (scmr & BIT(19))	/* plldf (division factor is 1 or 2) */
		m->vco_out >>= 1;

	m->cpmhz = m->vco_out >> 1;	/* cpm hz is half of vco_out */
	m->brghz = m->vco_out >> (2 * ((iomem->sccr & 0x3) + 1));
	m->bushz = m->vco_out / (((scmr & 0x00f00000) >> 20) + 1);

	/* Serial init sets BRG clock....I don't know how to compute
	 * core clock from core configuration, but I think I know the
	 * mapping....
	 */
	switch(scmr >> (31-7)){
	case 0x0a:
		m->cpuhz = m->clkin * 2;
		break;
	case 0x0b:
		m->cpuhz = (m->clkin >> 1) * 5;
		break;
	default:
	case 0x0d:
		m->cpuhz = m->clkin * 3;
		break;
	case 0x14:
		m->cpuhz = (m->clkin >> 1) * 7;
		break;
	case 0x1c:
		m->cpuhz = m->clkin * 4;
		break;
	}

	m->cyclefreq = m->bushz / 4;

/*	Expect:
	intfreq	133		m->cpuhz
	busfreq	33		m->bushz
	cpmfreq	99		m->cpmhz
	brgfreq	49.5		m->brghz
	vco		198
*/

	active.machs = 1;
	active.exiting = 0;

	putmsr(getmsr() | MSR_ME);

	/*
	 * turn on data cache before instruction cache;
	 * for some reason which I don't understand,
	 * you can't turn on both caches at once
	 */
	icacheenb();
	dcacheenb();

	kfpinit();

	/* Plan9.ini location in flash is FLASHMEM+PLAN9INI
	 * if PLAN9INI == ~0, it's not stored in flash or there is no flash
	 * if *cp == 0xff, flash memory is not initialized
	 */
	if (PLAN9INI == ~0 || *(plan9inistr = (char*)(FLASHMEM+PLAN9INI)) == 0xff){
		/* No plan9.ini in flash */
		plan9inistr =
			"console=0\n"
			"ether0=type=fcc port=0 ea=00601d051dd8\n"
			"flash0=mem=0xfe000000\n"
			"fs=135.104.9.42\n"
			"auth=135.104.9.7\n"
			"authdom=cs.bell-labs.com\n"
			"sys=blast\n"
			"ntp=135.104.9.52\n";
	}
}

void
fpgareset(void)
{
	print("fpga reset\n");

	ioplock();

	iomem->port[1].pdat &= ~Pin4;	/* force reset signal to 0 */
	delay(100);
	iomem->port[1].pdat |= Pin4;		/* force reset signal back to one */

	iopunlock();
}

void
hwintrinit(void)
{
	iomem->sicr = 2 << 8;
	/* Write ones into most bits of the interrupt pending registers to clear interrupts */
	iomem->sipnr_h = ~7;
	iomem->sipnr_h = ~1;
	/* Clear the interrupt masks, thereby disabling all interrupts */
	iomem->simr_h = 0;
	iomem->simr_l = 0;

	iomem->sypcr &= ~2;	/* cause a machine check interrupt on memory timeout */

	/* Initialize fpga reset pin */
	iomem->port[1].pdir |= Pin4;		/* 1 is an output */
	iomem->port[1].ppar &= ~Pin4;
	iomem->port[1].pdat |= Pin4;		/* force reset signal back to one */
}

int
vectorenable(Vctl *v)
{
	ulong hi, lo;

	if (v->irq & ~0x3f){
		print("m8260enable: interrupt vector %d out of range\n", v->irq);
		return -1;
	}
	hi = vec2mask[v->irq].hi;
	lo = vec2mask[v->irq].lo;
	if (hi == 0 && lo == 0){
		print("m8260enable: nonexistent vector %d\n", v->irq);
		return -1;
	}
	ioplock();
	/* Clear the interrupt before enabling */
	iomem->sipnr_h |= hi;
	iomem->sipnr_l |= lo;
	/* Enable */
	iomem->simr_h |= hi;
	iomem->simr_l |= lo;
	iopunlock();
	return v->irq;
}

void
vectordisable(Vctl *v)
{
	ulong hi, lo;

	if (v->irq & ~0x3f){
		print("m8260disable: interrupt vector %d out of range\n", v->irq);
		return;
	}
	hi = vec2mask[v->irq].hi;
	lo = vec2mask[v->irq].lo;
	if (hi == 0 && lo == 0){
		print("m8260disable: nonexistent vector %d\n", v->irq);
		return;
	}
	ioplock();
	iomem->simr_h &= ~hi;
	iomem->simr_l &= ~lo;
	iopunlock();
}

int
intvec(void)
{
	return iomem->sivec >> 26;
}

void
intend(int vno)
{
	/* Clear interrupt */
	ioplock();
	iomem->sipnr_h |= vec2mask[vno].hi;
	iomem->sipnr_l |= vec2mask[vno].lo;
	iopunlock();
}

int
m8260eoi(int)
{
	return 0;
}

int
m8260isr(int)
{
	return 0;
}

void
flashprogpower(int)
{
}

enum {
	TgcrCas 			= 0x80,
	TgcrGm 			= 0x08,
	TgcrStp 			= 0x2,	/* There are two of these, timer-2 bits are bits << 4 */
	TgcrRst 			= 0x1,

	TmrIclkCasc		= 0x00<<1,
	TmrIclkIntclock	= 0x01<<1,
	TmrIclkIntclock16	= 0x02<<1,
	TmrIclkTin		= 0x03<<1,
	TmrCERising		= 0x1 << 6,
	TmrCEFalling		= 0x2 << 6,
	TmrCEAny		= 0x3 << 6,
	TmrFrr			= SBIT(12),
	TmrOri			= SBIT(11),

	TerRef			= SBIT(14),
	TerCap			= SBIT(15),
};

uvlong
fastticks(uvlong *hz)
{
	ulong count;
	static Lock fasttickslock;

	if (hz)
		*hz = m->clkin>>1;
	ilock(&fasttickslock);
	count = iomem->tcnl1;
	if (count < ticks.lo)
		ticks.hi += 1;
	ticks.lo = count;
	iunlock(&fasttickslock);
	return ticks.val;
}

void
timerset(uvlong next)
{
	long offset;
	uvlong now;
	static int cnt;

	now = fastticks(nil);
	offset = next - now;
	if (offset < 2500)
		next = now + 2500;	/* 10000 instructions */
	else if (offset > m->clkin / HZ){
		print("too far in the future: offset %llux, now %llux\n", next, now);
		next = now + m->clkin / HZ;
	}
	iomem->trrl1 = next;
}

void
m8260timerintr(Ureg *u, void*)
{
	iomem->ter2 |= TerRef | TerCap;		/* Clear interrupt */
	timerintr(u, 0);
}

void
timerinit(void)
{

	iomem->tgcr1 = TgcrCas | TgcrGm;		/* cascade timers 1 & 2, normal gate mode */
	iomem->tcnl1 = 0;
	iomem->trrl1 = m->clkin / HZ;		/* first capture in 1/HZ seconds */
	iomem->tmr1 = TmrIclkCasc;
	iomem->tmr2 = TmrIclkIntclock | TmrOri;
	intrenable(13, m8260timerintr, nil, "timer");	/* Timer 2 interrupt is on 13 */
	iomem->tgcr1 |= TgcrRst << 4;
}

static void
addseg(char *name, ulong start, ulong length)
{
	Physseg segbuf;

	memset(&segbuf, 0, sizeof(segbuf));
	segbuf.attr = SG_PHYSICAL;
	kstrdup(&segbuf.name, name);
	segbuf.pa = start;
	segbuf.size = length;
	if (addphysseg(&segbuf) == -1) {
		print("addphysseg: %s\n", name);
		return;
	}
}

void
sharedseginit(void)
{
	int i, j;
	ulong base, size;
	char name[16], *a, *b, *s;
	static char *segnames[] = {
		"fpga",
		"dsp",
	};

	for (j = 0; j < nelem(segnames); j++){
		for (i = 0; i < 8; i++){
			snprint(name, sizeof name, "%s%d", segnames[j], i);
			if ((a = getconf(name)) == nil)
				continue;
			if ((b = strstr(a, "mem=")) == nil){
				print("blastseginit: %s: no base\n", name);
				continue;
			}
			b += 4;
			base = strtoul(b, nil, 0);
			if (base == 0){
				print("blastseginit: %s: bad base: %s\n", name, b);
				continue;
			}
			if ((s = strstr(a, "size=")) == nil){
				print("blastseginit: %s: no size\n", name);
				continue;
			}
			s += 5;
			size = strtoul(s, nil, 0);
			if (size == 0){
				print("blastseginit: %s: bad size: %s\n", name, s);
				continue;
			}
			addseg(name, base, size);
		}
	}
}

void
cpmop(int op, int dev, int mcn)
{
	ioplock();
	eieio();
	while(iomem->cpcr & 0x10000)
		eieio();
	iomem->cpcr = dev<<(31-10) | mcn<<(31-25) | op | 0x10000;
	eieio();
	while(iomem->cpcr & 0x10000)
		eieio();
	iopunlock();
}

/*
 * connect SCCx clocks in NSMI mode (x=1 for USB)
 */
void
sccnmsi(int x, int rcs, int tcs)
{
	ulong v;
	int sh;

	sh = (x-1)*8;	/* each SCCx field in sicr is 8 bits */
	v = (((rcs&7)<<3) | (tcs&7)) << sh;
	iomem->sicr = (iomem->sicr & ~(0xFF<<sh)) | v;
}

/*
 * lock the shared IO memory and return a reference to it
 */
void
ioplock(void)
{
	ilock(&cpmlock);
}

/*
 * release the lock on the shared IO memory
 */
void
iopunlock(void)
{
	eieio();
	iunlock(&cpmlock);
}

BD*
bdalloc(int n)
{
	static BD *palloc = ((Imap*)INTMEM)->bd;
	BD *p;
	
	p = palloc;
	if (palloc > ((Imap*)INTMEM)->bd + nelem(((Imap*)INTMEM)->bd)){
		print("bdalloc: out of BDs\n");
		return nil;
	}
	palloc += n;
	return p;
}

/*
 * Initialise receive and transmit buffer rings.  Only used for FCC
 * Ethernet now.
 *
 * Ioringinit will allocate the buffer descriptors in normal memory
 * and NOT in Dual-Ported Ram, as prescribed by the MPC8260
 * PowerQUICC II manual (Section 28.6).  When they are allocated
 * in DPram and the Dcache is enabled, the processor will hang.
 * This has been observed for the FCCs, it may or may not be true
 * for SCCs or DMA.
 * The SMC Uart buffer descriptors are not allocated here; (1) they
 * can ONLY be in DPram and (2) they are not configured as a ring.
 */
int
ioringinit(Ring* r, int nrdre, int ntdre, int bufsize)
{
	int i, x;
	static uchar *dpmallocaddr;
	static uchar *dpmallocend;

	if (dpmallocaddr == nil){
		dpmallocaddr = m->imap->dpram1;
		dpmallocend = dpmallocaddr + sizeof(m->imap->dpram1);
	}
	/* the ring entries must be aligned on sizeof(BD) boundaries */
	r->nrdre = nrdre;
	if(r->rdr == nil)
		r->rdr = xspanalloc(nrdre*sizeof(BD), 0, 8);
	if(r->rdr == nil)
		return -1;
	if(r->rrb == nil && bufsize){
		r->rrb = xspanalloc(nrdre*bufsize, 0, CACHELINESZ);
		if(r->rrb == nil)
			return -1;
	}
	x = bufsize ? PADDR(r->rrb) : 0;
	for(i = 0; i < nrdre; i++){
		r->rdr[i].length = 0;
		r->rdr[i].addr = x;
		r->rdr[i].status = BDEmpty|BDInt;
		x += bufsize;
	}
	r->rdr[i-1].status |= BDWrap;
	r->rdrx = 0;

	r->ntdre = ntdre;
	if(r->tdr == nil)
		r->tdr = xspanalloc(ntdre*sizeof(BD), 0, 8);
	if(r->txb == nil)
		r->txb = xspanalloc(ntdre*sizeof(Block*), 0, CACHELINESZ);
	if(r->tdr == nil || r->txb == nil)
		return -1;
	for(i = 0; i < ntdre; i++){
		r->txb[i] = nil;
		r->tdr[i].addr = 0;
		r->tdr[i].length = 0;
		r->tdr[i].status = 0;
	}
	r->tdr[i-1].status |= BDWrap;
	r->tdrh = 0;
	r->tdri = 0;
	r->ntq = 0;
	return 0;
}

void
trapinit(void)
{
	int i;

	/*
	 * set all exceptions to trap
	 */
	for(i = 0x0; i < 0x2000; i += 0x100)
		sethvec(i, trapvec);

	setmvec(0x1000, imiss, tlbvec);
	setmvec(0x1100, dmiss, tlbvec);
	setmvec(0x1200, dmiss, tlbvec);

/*	Useful for avoiding assembler miss handling:
	sethvec(0x1000, tlbvec);
	sethvec(0x1100, tlbvec);
	sethvec(0x1200, tlbvec);
/* */
	dcflush(KADDR(0), 0x2000);
	icflush(KADDR(0), 0x2000);

	putmsr(getmsr() & ~MSR_IP);
}

void
reboot(void*, void*, ulong)
{
	ulong *p;
	int x;

	p = (ulong*)0x90000000;
	x = splhi();
	iomem->sypcr |= 0xc0;
	print("iomem->sypcr = 0x%lux\n", iomem->sypcr);
	*p = 0;
	print("still alive\n");
	splx(x);
}
