/*
 * MediaTek Ethernet for the MT7688
 * thank you to the folks at NetBSD
 */

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


/* RX Descriptor Format */
#define RXD_LEN1(x)	(((x) >> 0) & 0x3fff)
#define RXD_LAST1	(1 << 14)
#define RXD_LEN0(x)	(((x) >> 16) & 0x3fff)
#define RXD_LAST0	(1 << 30)
#define RXD_DDONE	(1 << 31)
#define RXD_FOE(x)	(((x) >> 0) & 0x3fff)
#define RXD_FVLD	(1 << 14)
#define RXD_INFO(x)	(((x) >> 16) & 0xff)
#define RXD_PORT(x)	(((x) >> 24) & 0x7)
#define RXD_INFO_CPU	(1 << 27)
#define RXD_L4_FAIL	(1 << 28)
#define RXD_IP_FAIL	(1 << 29)
#define RXD_L4_VLD	(1 << 30)
#define RXD_IP_VLD	(1 << 31)


/* TX Descriptor Format */
#define TXD_LEN1(x)	(((x) & 0x3fff) << 0)
#define TXD_LAST1	(1 << 14)
#define TXD_BURST	(1 << 15)
#define TXD_LEN0(x)	(((x) & 0x3fff) << 16)
#define TXD_LAST0	(1 << 30)
#define TXD_DDONE	(1 << 31)
#define TXD_VIDX(x)	(((x) & 0xf) << 0)
#define TXD_VPRI(x)	(((x) & 0x7) << 4)
#define TXD_VEN		(1 << 7)
#define TXD_SIDX(x)	(((x) & 0xf) << 8)
#define TXD_SEN(x)	(1 << 13)
#define TXD_QN(x)	(((x) & 0x7) << 16)
#define TXD_PN(x)	(((x) & 0x7) << 24)
#define	TXD_PN_CPU	0
#define	TXD_PN_GDMA1	1
#define	TXD_PN_GDMA2	2
#define TXD_TCP_EN	(1 << 29)
#define TXD_UDP_EN	(1 << 30)
#define TXD_IP_EN	(1 << 31)


/* pdma global cfgs */
#define  GLO_CFG_TX_WB_DDONE	(1 << 6)
#define   GLO_CFG_BURST_SZ_4	(0 << 4)
#define   GLO_CFG_BURST_SZ_8	(1 << 4)
#define   GLO_CFG_BURST_SZ_16	(2 << 4)
#define  GLO_CFG_RX_DMA_EN		(1 << 2)
#define  GLO_CFG_TX_DMA_EN		(1 << 0)
#define	RX_DMA_BUSY				(1 << 3)
#define TX_DMA_BUSY				(1 << 1)


/* interupt masks */
#define  INT_RX_DONE_INT1		(1 << 17)
#define  INT_RX_DONE_INT0		(1 << 16)
#define  INT_TX_DONE_INT3		(1 << 3)
#define  INT_TX_DONE_INT2		(1 << 2)
#define  INT_TX_DONE_INT1		(1 << 1)
#define  INT_TX_DONE_INT0		(1 << 0)
#define  INT_RX_DUKKHA			(1 << 31)
#define  INT_TX_DUKKHA			(1 << 29)


/* mii stuff */
#define  PCTL0_WR_VAL(x)	(((x) & 0xffff) << 16)
#define  PCTL0_RD_CMD		(1 << 14)
#define  PCTL0_WR_CMD		(1 << 13)
#define  PCTL0_REG(x)		(((x) & 0x1f) << 8)
#define  PCTL0_ADDR(x)		(((x) & 0x1f) << 0)
#define  PCTL1_RD_VAL(x)	(((x) >> 16) & 0xffff)
#define  PCTL1_RD_DONE		(1 << 1)	/* read clear */
#define  PCTL1_WR_DONE		(1 << 0)	/* read clear */


/* Debugging options */
enum{
	Miidebug	=	0,
	Ethdebug	=	0,
	Attchbug	=	0,
};


enum{
	Nrd		= 256,	/* Number rx descriptors */
	Ntd		= 64,	/* Number tx descriptors */
	Rbsz	= 2048,	/* block size */
};


typedef struct Desc Desc;
typedef struct Ctlr Ctlr;


struct Desc
{
	u32int	ptr0;
	u32int	info1;
	u32int	ptr1;
	u32int	info2;
};


struct Ctlr
{
	int		attached;
	QLock;
	Ether	*edev;		/* point back */

	struct {
		Block	*b[Nrd];
		Desc	*d;
		Rendez;
		Lock;
	}	rx[1];

	struct {
		Block	*b[Ntd];
		Desc	*d;
		Rendez;
	}	tx[1];

	Mii	*mii;

	QLock	statlock;
	int		rxstat;
	int		rxintr;
	int		rxdmaerr;
	int		txstat;
	int		txintr;
	int		txdmaerr;
	int		nointr;
	int		badrx;
};


static u32int
sysrd(int offset)
{
	return *IO(u32int, (SYSCTLBASE + offset));
}


static void
syswr(int offset, u32int val)
{
	*IO(u32int, (SYSCTLBASE + offset)) = val;
}


static u32int
ethrd(int offset)
{
	return *IO(u32int, (ETHBASE + offset));
}


static void
ethwr(int offset, u32int val)
{
	*IO(u32int, (ETHBASE + offset)) = val;
}


static u32int
swrd(int offset)
{
	return *IO(u32int, (SWCHBASE + offset));
}


static void
swwr(int offset, u32int val)
{
	*IO(u32int, (SWCHBASE + offset)) = val;
}


static int
miird(Mii*, int pa, int ra)
{
	int val = 0;
	int	timeout;

	if(Miidebug)
		iprint("miird, phy_addr; %d phy_reg: %d\n", pa, ra);

	if(pa > 5)
		return -1;


	swwr(SW_PCTL0, PCTL0_RD_CMD | PCTL0_ADDR(pa) | PCTL0_REG(ra));
	delay(1);

	for(timeout = 0; timeout < 2000; timeout++){
		if((val = swrd(SW_PCTL1)) & PCTL1_RD_DONE)
			break;
		microdelay(100);
	}

	if(!(val & PCTL1_RD_DONE))
		return -1;

	return PCTL1_RD_VAL(val);
}


static int
miiwr(Mii*, int pa, int ra, int val)
{
	int timeout;

	if(Miidebug)
		iprint("miiwr, phy_addr; %d phy_reg: %d val: 0x%04X\n", pa, ra, val);

	if(pa > 5)
		return -1;

	swwr(SW_PCTL0, PCTL0_WR_CMD | PCTL0_WR_VAL(val) | PCTL0_ADDR(pa) | PCTL0_REG(ra));
	delay(1);

	for(timeout = 0; timeout < 2000; timeout++){
		if((val = swrd(SW_PCTL1)) & PCTL1_WR_DONE)
			break;
		microdelay(100);
	}

	if(!(val & PCTL1_WR_DONE))
		return -1;

	return 0;
}


static void
getmacaddr(Ether *edev)
{
	ulong msb, lsb;

	lsb = ethrd(GDMA1_MAC_LSB);
	msb = ethrd(GDMA1_MAC_MSB);

	edev->ea[0] = msb>>8;
	edev->ea[1] = msb>>0;
	edev->ea[2]	= lsb>>24;
	edev->ea[3] = lsb>>16;
	edev->ea[4] = lsb>>8;
	edev->ea[5] = lsb>>0;

	if(Attchbug){
		iprint("ether getmac: %04lX %08lX\n", (msb & 0xFFFF), lsb);
		delay(10);
	}
}


static void
ethreset(Ether *edev)
{
	ulong buf;
	int i;
	Ctlr *ctlr = edev->ctlr;
	Mii *mii = ctlr->mii;

	iprint("reset eth and ephy\n");
	delay(10);




	buf = sysrd(SYSCTL_RST);
	buf |= (1<<24);
	syswr(SYSCTL_RST, buf);
	delay(1);
	buf ^= (1<<24);
	syswr(SYSCTL_RST, buf);


	miiwr(mii, 0, 31, 0x2000);
	miiwr(mii, 0, 26, 0x0020);

	for(i = 0; i < 5; i++){
		miiwr(mii, i, 31, 0x8000);
		miiwr(mii, i, 0, 0x3100);
		miiwr(mii, i, 30, 0xa000);
		miiwr(mii, i, 31, 0xa000);
		miiwr(mii, i, 16, 0x0606);
		miiwr(mii, i, 23, 0x0f0e);
		miiwr(mii, i, 24, 0x1610);
		miiwr(mii, i, 30, 0x1f15);
		miiwr(mii, i, 28, 0x6111);
	}

	miiwr(mii, 0, 31, 0x5000);
	miiwr(mii, 0, 19, 0x004a);
	miiwr(mii, 0, 20, 0x015a);
	miiwr(mii, 0, 21, 0x00ee);
	miiwr(mii, 0, 22, 0x0033);
	miiwr(mii, 0, 23, 0x020a);
	miiwr(mii, 0, 24, 0x0000);
	miiwr(mii, 0, 25, 0x024a);
	miiwr(mii, 0, 26, 0x035a);
	miiwr(mii, 0, 27, 0x02ee);
	miiwr(mii, 0, 28, 0x0233);
	miiwr(mii, 0, 29, 0x000a);
	miiwr(mii, 0, 30, 0x0000);
	miiwr(mii, 0, 31, 0x4000);
	miiwr(mii, 0, 29, 0x000d);
	miiwr(mii, 0, 30, 0x0500);


}

static void
doreset(Ether *edev)
{
	ulong buf;

	buf = sysrd(SYSCTL_RST);
	buf |= (1<<24);
	syswr(SYSCTL_RST, buf);
	delay(1);
	buf ^= (1<<24);
	syswr(SYSCTL_RST, buf);

	iprint("reset switch\n");
	delay(10);

	if(Attchbug){
		iprint("ether did a reset\n");
		delay(10);
	}

/* basic switch init */
	swwr(SW_FCT0, 0xC8A07850);
	swwr(SW_SGC2, 0x00000000);
	swwr(SW_PFC1, 0x00405555);	//vlan options
	swwr(SW_POC0, 0x00007f7f);
	swwr(SW_POC2, 0x00007f7f);
	swwr(SW_FCT2, 0x0002500c);
	swwr(SW_SWGC, 0x0008a301);
	swwr(SW_SOCPC, 0x02404040);
	swwr(SW_FPORT, 0x3f502b28);
	swwr(SW_FPA, 0x00000000);

	USED(edev);

}


static int
rdfull(void *arg)
{
	Desc *d = arg;
	return (d->info1 & RXD_DDONE) == 1;
}


static void
rxproc(void *arg)
{
	Ether	*edev = arg;
	Ctlr	*ctlr = edev->ctlr;
	Block	*b;
	Desc	*d;
	int		len, i;



	i = ethrd(PDMA_RX0_CPU_IDX);	/* get current index */

	while(waserror())
		;

	for(;;){
		ctlr->rxstat++;
		i = (i + 1) % Nrd;
		d = &ctlr->rx->d[i];

		if((d->info1 & RXD_DDONE) == 0)
			sleep(ctlr->rx, rdfull, d);

		len = RXD_LEN0(d->info1);	/* get length of packet */
		b = ctlr->rx->b[i];

		if(len > 0){
			b->wp = b->rp + len;
			dcflush(b->rp, BLEN(b));	/* move block to ram */
			etheriq(edev, b);			/* move block to ether input queue */

		if(Ethdebug)
			iprint("rxproc: (%d) len=%d | ", i, len);

		} else {
			ctlr->badrx++;
			freeb(b);
		}

			/* replenish */
			b = iallocb(Rbsz);
			if(b == nil)
				panic("NO RX BLOCKS");
			ctlr->rx->b[i] = b;
			dcflush(b->rp, Rbsz);
			d->ptr0 = PADDR(b->rp);	/* point to fresh block */
			d->info1 = 0;			/* clear out info1 & 2 */
			d->info2 = 0;
			d->info1 = RXD_LAST0;	/* clear ddone */

		ethwr(PDMA_RX0_CPU_IDX, i);	/* move to next index */
	}
}


static int
tdfree(void *arg)
{
	Desc *d = arg;
	return (d->info1 & (TXD_LAST0  | TXD_DDONE)) == (TXD_LAST0  | TXD_DDONE);
}


static void
txproc(void *arg)
{
	Ether *edev = arg;
	Ctlr *ctlr = edev->ctlr;
	Block *b;
	Desc *d;
	int i, len, Δlen;

	i = ethrd(PDMA_TX0_CPU_IDX);	/* get current index */

	while(waserror())
		;

	for(;;){
		ctlr->txstat++;
		if((b = qbread(edev->oq, 100000)) == nil)	/* fetch packet from queue */
			break;
		

		d = &ctlr->tx->d[i];
		while(!tdfree(d))
			sleep(ctlr->tx, tdfree, d);

		ilock(ctlr->tx);	/* helps with packet loss */
		if(ctlr->tx->b[i] != nil)
			freeb(ctlr->tx->b[i]);

		ctlr->tx->b[i] = b;
		len = BLEN(b);

		if(len < 64){	/* tx needs at least 64 per packet */
			Δlen = 64 - len;
			b = padblock(b, -Δlen);
			len = BLEN(b) + Δlen;
		}

		if(Ethdebug)
			iprint("txproc: (%d) len=%d | ", i, len);

		dcflush(b->rp, Rbsz);	/* move packet to ram */
		d->ptr0 = PADDR(b->rp);
	//	d->info2 = TXD_QN(3) | TXD_PN(TXD_PN_GDMA1);
		d->info1 = TXD_LEN0(len) | TXD_LAST0;

		i = (i + 1) % Ntd;
		ethwr(PDMA_TX0_CPU_IDX, i);
		iunlock(ctlr->tx);
	}
}


static void
etherinterrupt(Ureg*, void *arg)
{
	Ether *edev = arg;
	Ctlr *ctlr = edev->ctlr;
	u32int irq;
	int rxintΔ, txintΔ;


	rxintΔ = ctlr->rxintr;
	txintΔ = ctlr->txintr;

	irq = ethrd(INT_STATUS);	/* get interrupt requests */

	if(Ethdebug){
		iprint("ether interrupt: %08uX |", irq);
		delay(10);
	}


	if(irq & (INT_RX_DONE_INT0 | INT_RX_DONE_INT1)){
		ctlr->rxintr++;
		wakeup(ctlr->rx);
	}

	if(irq & (INT_TX_DONE_INT0 | INT_TX_DONE_INT1 | 
		INT_TX_DONE_INT2 | INT_TX_DONE_INT3)){
		ctlr->txintr++;
		wakeup(ctlr->tx);
	}

	if((rxintΔ == ctlr->rxintr) && (txintΔ == ctlr->txintr)){
		ctlr->nointr++;
		iprint("etherinterrupt: spurious %X\n", irq);
	}

	if(irq & INT_TX_DUKKHA)
		ctlr->txdmaerr++;

	if(irq & INT_RX_DUKKHA)
		ctlr->rxdmaerr++;

	ethwr(INT_STATUS, irq);	/* writing back 1's clears irqs */
}


static int
initmii(Ctlr *ctlr)
{

/*
 *	since the ethernet is wired right into 
 *	a 7? port switch, much of the mii stuff
 * 	is handled by the switch start up
 */

	Ether	*edev = ctlr->edev;	

	if((ctlr->mii = malloc(sizeof(Mii))) == nil)
		return -1;

	ctlr->mii->name = edev->name;
	ctlr->mii->ctlr	= ctlr;
	ctlr->mii->mir	= miird;
	ctlr->mii->miw	= miiwr;

	if(mii(ctlr->mii, ~0) == 0 || ctlr->mii->curphy == nil){
		iprint("#l%d: init mii failure\n", edev->ctlrno);
		free(ctlr->mii);
		ctlr->mii = nil;
		return -1;
	}
	addmiibus(ctlr->mii);

	iprint("#l%d: phy%d id %.8ux oui %x\n", 
		edev->ctlrno, ctlr->mii->curphy->phyno, 
		ctlr->mii->curphy->id, ctlr->mii->curphy->oui);

	miireset(ctlr->mii->curphy);

	miiane(ctlr->mii->curphy, ~0, ~0, ~0);

	return 0;
}


static void
attach(Ether *edev)	//keep it minimal
{
	int i;
	ulong	buf;
	Ctlr *ctlr;
	Desc *d;

	ctlr = edev->ctlr;

	if(Attchbug){
		iprint("ether attach called\n");
		delay(10);
	}

	qlock(ctlr);
	if(ctlr->attached){
		qunlock(ctlr);

		if(Attchbug){
			iprint("ether attach already?\n");
			delay(10);
		}

		return;
	}

	if(waserror()){
		qunlock(ctlr);

		if(Attchbug){
			iprint("ether attach waserror?\n");
			delay(10);
		}

		free(ctlr->rx->d);
		free(ctlr->tx->d);
		nexterror();
	}

	doreset(edev);

	ethreset(edev);

	if(initmii(ctlr) < 0)
		error("mii failed");

	/* Allocate Rx/Tx ring KSEG1, is uncached memmory */
	ctlr->tx->d = (Desc *)KSEG1ADDR(xspanalloc(sizeof(Desc) * Ntd, CACHELINESZ, 0));
	ctlr->rx->d = (Desc *)KSEG1ADDR(xspanalloc(sizeof(Desc) * Nrd, CACHELINESZ, 0));

	if(ctlr->tx->d == nil || ctlr->rx->d == nil)
		error(Enomem);

	/* Allocate Rx blocks, initialize Rx ring. */
	for(i = 0; i < Nrd; i++){
		Block *b = iallocb(Rbsz);
		if(b == nil)
			error("rxblock");
		ctlr->rx->b[i] = b;
		dcflush(b->rp, Rbsz);
		d = &ctlr->rx->d[i];
		d->ptr0 = PADDR(b->rp);
		d->info1 = RXD_LAST0;
	}

	/* Initialize Tx ring */
	for(i = 0; i < Ntd; i++){
		ctlr->tx->b[i] = nil;
		ctlr->tx->d[i].info1 = TXD_LAST0 | TXD_DDONE;
		ctlr->tx->d[i].info2 = TXD_QN(3) | TXD_PN(TXD_PN_GDMA1);
	}

	if(Attchbug){
		iprint("ether attach clear nic\n");
		delay(10);
	}

	/* turn off and clear defaults */
	buf = ethrd(PDMA_GLOBAL_CFG);
	buf &= 0xFF;
	ethwr(PDMA_GLOBAL_CFG, buf); 
	delay(1);

	/* give Tx ring to nic */
	ethwr(PDMA_TX0_PTR, PADDR(ctlr->tx->d));
	ethwr(PDMA_TX0_COUNT, Ntd);
	ethwr(PDMA_TX0_CPU_IDX, 0);
	ethwr(PDMA_IDX_RST, 1 << 0);
	coherence();

	/* give Rx ring to nic */
	ethwr(PDMA_RX0_PTR, PADDR(ctlr->rx->d));
	ethwr(PDMA_RX0_COUNT, Nrd);
	ethwr(PDMA_RX0_CPU_IDX, (Nrd - 1));
	ethwr(PDMA_IDX_RST, 1 << 16);
	coherence();


	/* clear pending irqs */
	buf = ethrd(INT_STATUS);
	ethwr(INT_STATUS, buf);

	/* setup interupts */
	ethwr(INT_MASK,
		INT_RX_DONE_INT1 |
		INT_RX_DONE_INT0 |
		INT_TX_DONE_INT3 |
		INT_TX_DONE_INT2 |
		INT_TX_DONE_INT1 |
		INT_TX_DONE_INT0);

	if(Attchbug){
		iprint("ether attach start\n");
		delay(10);
	}


	/* start dma */
	ethwr(PDMA_GLOBAL_CFG, GLO_CFG_TX_WB_DDONE | GLO_CFG_RX_DMA_EN | GLO_CFG_TX_DMA_EN); 


	if(Attchbug){
		iprint("ether attach vlan\n");
		delay(10);
	}

	/* outer vlan id */
	ethwr(SDM_CON, 0x8100);

	edev->link = 1;
	ctlr->attached = 1;

	if(Attchbug){
		iprint("ether attach kprocs\n");
		delay(10);
	}

	kproc("rxproc", rxproc, edev);
	kproc("txproc", txproc, edev);

	qunlock(ctlr);
	poperror();

	if(Attchbug)
		iprint("ether attach done\n");
}


static void
shutdown(Ether *edev)
{
	USED(edev);
}

/* promiscuous stub */
static void
prom(void*, int)
{
}

/* multicast stub */
static void
multi(void*, uchar*, int)
{
}


static char*
ifstat(void *arg, char *p, char *e)
{
	Ether *edev = arg;
	Ctlr* ctlr = edev->ctlr;

	if(p >= e)
		return p;

	qlock(ctlr);
	p = seprint(p, e, "tx: %d\n", ctlr->txstat);
	p = seprint(p, e, "rx: %d\n", ctlr->rxstat);
	p = seprint(p, e, "txintr: %d\n", ctlr->txintr);
	p = seprint(p, e, "rxintr: %d\n", ctlr->rxintr);
	p = seprint(p, e, "nointr: %d\n", ctlr->nointr);
	p = seprint(p, e, "bad rx: %d\n", ctlr->badrx);
	p = seprint(p, e, "\n");
	p = seprint(p, e, "dma errs: tx: %d rx: %d\n", ctlr->txdmaerr, ctlr->rxdmaerr);
	p = seprint(p, e, "\n");
	p = seprint(p, e, "txptr: %08uX\n", ethrd(PDMA_TX0_PTR));
	p = seprint(p, e, "txcnt: %uX\n", ethrd(PDMA_TX0_COUNT));
	p = seprint(p, e, "txidx: %uX\n", ethrd(PDMA_TX0_CPU_IDX));
	p = seprint(p, e, "txdtx: %uX\n", ethrd(PDMA_TX0_DMA_IDX));
	p = seprint(p, e, "\n");
	p = seprint(p, e, "rxptr: %08uX\n", ethrd(PDMA_RX0_PTR));
	p = seprint(p, e, "rxcnt: %uX\n", ethrd(PDMA_RX0_COUNT));
	p = seprint(p, e, "rxidx: %uX\n", ethrd(PDMA_RX0_CPU_IDX));
	p = seprint(p, e, "rxdtx: %uX\n", ethrd(PDMA_RX0_DMA_IDX));
	p = seprint(p, e, "\n");
	p = seprint(p, e, "GLOBAL CFG: %08uX\n", ethrd(PDMA_GLOBAL_CFG));
	p = seprint(p, e, "INT STATUS: %08uX\n", ethrd(INT_STATUS));
	p = seprint(p, e, "INT   MASK: %08uX\n", ethrd(INT_MASK));
	p = seprint(p, e, "\n");
	qunlock(ctlr);

	return p;
}

/* set Ether and Ctlr */
static int
pnp(Ether *edev)
{
	static Ctlr ctlr[1];

	if(Attchbug)
		iprint("ether pnp called\n");

	if(edev->ctlr != nil)
		return -1;

	/* only one controller */
	if(edev->ctlrno != 0)
		return -1;


	ctlr->edev	=	edev;

	edev->port	= (uintptr)(KSEG1|ETHBASE);
	edev->ctlr	= ctlr;
	edev->irq	= IRQethr;
	edev->mbps	= 100;
	edev->maxmtu = 1536;
	edev->arg	= edev;

	edev->attach = attach;
	edev->shutdown = shutdown;
//	edev->ctl = ctl;
	edev->ifstat = ifstat;
	edev->promiscuous = prom;
	edev->multicast = multi;

	getmacaddr(edev);

	intrenable(edev->irq, etherinterrupt, edev, 0, edev->name);

	if(Attchbug)
		iprint("ether pnp done\n");

	return 0;
}


void
ether7688link(void)
{
	addethercard("ether7688", pnp);
}
