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

#define Rbsz		ROUNDUP(sizeof(Etherpkt)+16, 64)

enum {
	RXRING = 0x200,
	TXRING = 0x200,
	Linkdelay = 500,
	MDC_DIV = 6,
};

enum {
	NET_CTRL,
	NET_CFG,
	NET_STATUS,
	DMA_CFG = 4,
	TX_STATUS,
	RX_QBAR,
	TX_QBAR,
	RX_STATUS,
	INTR_STATUS,
	INTR_EN,
	INTR_DIS,
	INTR_MASK,
	PHY_MAINT,
	RX_PAUSEQ,
	TX_PAUSEQ,
	HASH_BOT = 32,
	HASH_TOP,
	SPEC_ADDR1_BOT,
	SPEC_ADDR1_TOP,
};

enum {
	MDCTRL,
	MDSTATUS,
	MDID1,
	MDID2,
	MDAUTOADV,
	MDAUTOPART,
	MDAUTOEX,
	MDAUTONEXT,
	MDAUTOLINK,
	MDGCTRL,
	MDGSTATUS,
	MDPHYCTRL = 0x1f,
};

enum {
	/* NET_CTRL */
	RXEN = 1<<2,
	TXEN = 1<<3,
	MDEN = 1<<4,
	STARTTX = 1<<9,
	/* NET_CFG */
	SPEED = 1<<0,
	FDEN = 1<<1,
	COPYALLEN = 1<<4,
	MCASTHASHEN = 1<<6,
	UCASTHASHEN = 1<<7,
	RX1536EN = 1<<8,
	GIGE_EN = 1<<10,
	RXCHKSUMEN = 1<<24,
	/* NET_STATUS */
	PHY_IDLE = 1<<2,
	/* DMA_CFG */
	TXCHKSUMEN  = 1<<11,
	/* TX_STATUS */
	TXCOMPL = 1<<5,
	/* INTR_{EN,DIS} */
	MGMTDONE = 1<<0,
	RXCOMPL = 1<<1,
	RXUSED = 1<<2,
	TXUNDER = 1<<4,
	RXOVER = 1<<10,
	/* MDCTRL */
	MDRESET = 1<<15,
	AUTONEG = 1<<12,
	FULLDUP = 1<<8,
	/* MDSTATUS */
	LINK = 1<<2,
	/* MDGSTATUS */
	RECVOK = 3<<12,
};

enum {
	RxUsed = 1,
	TxUsed = 1<<31,
	FrameEnd = 1<<15,
};

enum {
	GEM0_CLK_CTRL = 0x140/4,
};

typedef struct Ctlr Ctlr;

struct Ctlr {
	ulong *r;
	Rendez phy;
	int phyaddr;
	int rxconsi, rxprodi, txi;
	ulong *rxr, *txr;
	Block **rxs, **txs;
	Lock txlock;
	int attach;
};

static int
phyidle(void *v)
{
	return ((Ctlr*)v)->r[NET_STATUS] & PHY_IDLE;
}

static void
mdwrite(Ctlr *c, int r, u16int v)
{
	sleep(&c->phy, phyidle, c);
	c->r[PHY_MAINT] = 1<<30 | 1<<28 | 1<<17 | c->phyaddr << 23 | r << 18 | v;
	sleep(&c->phy, phyidle, c);
}

static u16int
mdread(Ctlr *c, int r)
{
	sleep(&c->phy, phyidle, c);
	c->r[PHY_MAINT] = 1<<30 | 1<< 29 | 1<<17 | c->phyaddr << 23 | r << 18;
	sleep(&c->phy, phyidle, c);
	return c->r[PHY_MAINT];
}

static void
ethproc(void *ved)
{
	Ether *edev;
	Ctlr *c;
	u16int v;
	
	edev = ved;
	c = edev->ctlr;
	mdwrite(c, MDCTRL, AUTONEG);
	for(;;){
		if((mdread(c, MDSTATUS) & LINK) == 0){
			ethersetlink(edev, 0);
			while((mdread(c, MDSTATUS) & LINK) == 0)
				tsleep(&up->sleep, return0, nil, Linkdelay);
		}
		v = mdread(c, MDPHYCTRL);
		if((v & 0x40) != 0){
			while((mdread(c, MDGSTATUS) & RECVOK) != RECVOK)
				;
			c->r[NET_CFG] |= GIGE_EN;
			slcr[GEM0_CLK_CTRL] = 1 << 20 | 8 << 8 | 1;
			ethersetspeed(edev, 1000);
		}else if((v & 0x20) != 0){
			c->r[NET_CFG] = c->r[NET_CFG] & ~GIGE_EN | SPEED;
			slcr[GEM0_CLK_CTRL] = 5 << 20 | 8 << 8 | 1;
			ethersetspeed(edev, 100);
		}else if((v & 0x10) != 0){
			c->r[NET_CFG] = c->r[NET_CFG] & ~(GIGE_EN | SPEED);
			slcr[GEM0_CLK_CTRL] = 20 << 20 | 20 << 8 | 1;
			ethersetspeed(edev, 10);
		}
		if((v & 0x08) != 0){
			c->r[NET_CFG] |= FDEN;
		}else{
			c->r[NET_CFG] &= ~FDEN;
		}
		ethersetlink(edev, 1);
		while((mdread(c, MDSTATUS) & LINK) != 0)
			tsleep(&up->sleep, return0, nil, Linkdelay);
	}
}

static int
replenish(Ctlr *c)
{
	Block *bp;
	int i;
	ulong *r;
	
	while(c->rxprodi != c->rxconsi){
		i = c->rxprodi;
		bp = iallocb(Rbsz);
		if(bp == nil){
			print("eth: out of memory for receive buffers\n");
			return -1;
		}
		c->rxs[i] = bp;
		r = &c->rxr[2 * i];
		r[0] = RxUsed | PADDR(bp->rp);
		if(i == RXRING - 1)
			r[0] |= 2;
		r[1] = 0;
		cleandse(bp->base, bp->lim);
		clean2pa(PADDR(bp->base), PADDR(bp->lim));
		r[0] &= ~RxUsed;
		c->rxprodi = (c->rxprodi + 1) & (RXRING - 1);
	}
	return 0;
}

static void
ethrx(Ether *edev)
{
	Ctlr *c;
	ulong *r;
	Block *bp;

	c = edev->ctlr;
//	print("rx! %p %p\n", PADDR(&c->rxr[2 * c->rxconsi]), c->r[RX_QBAR]);
	for(;;){
		r = &c->rxr[2 * c->rxconsi];
		if((r[0] & RxUsed) == 0)
			break;
		if((r[1] & FrameEnd) == 0)
			print("eth: partial frame received -- shouldn't happen\n");
		bp = c->rxs[c->rxconsi];
		bp->wp = bp->rp + (r[1] & 0x1fff);
		invaldse(bp->rp, bp->wp);
		inval2pa(PADDR(bp->rp), PADDR(bp->wp));
		etheriq(edev, bp);
		c->rxconsi = (c->rxconsi + 1) & (RXRING - 1);
		replenish(c);
	}
}

static void
ethtx(Ether *edev)
{
	Ctlr *c;
	ulong *r;
	Block *bp;
	
	c = edev->ctlr;
	ilock(&c->txlock);
	for(;;){
		r = &c->txr[2 * c->txi];
		if((r[1] & TxUsed) == 0){
			print("eth: transmit buffer full\n");
			break;
		}
		bp = qget(edev->oq);
		if(bp == nil)
			break;
		if(c->txs[c->txi] != nil)
			freeb(c->txs[c->txi]);
		c->txs[c->txi] = bp;
		cleandse(bp->rp, bp->wp);
		clean2pa(PADDR(bp->rp), PADDR(bp->wp));
		r[0] = PADDR(bp->rp);
		r[1] = BLEN(bp) | FrameEnd | TxUsed;
		if(r == c->txr + 2 * (TXRING - 1))
			r[1] |= 1<<30;
		coherence();
		r[1] &= ~TxUsed;
		coherence();
		c->r[NET_CTRL] |= STARTTX;
		c->txi = (c->txi + 1) & (TXRING - 1);
	}
	iunlock(&c->txlock);
}

static void
ethirq(Ureg *, void *arg)
{
	Ether *edev;
	Ctlr *c;
	ulong fl;
	
	edev = arg;
	c = edev->ctlr;
	fl = c->r[INTR_STATUS];
	c->r[INTR_STATUS] = fl;
	if((fl & MGMTDONE) != 0)
		wakeup(&c->phy);
	if((fl & TXUNDER) != 0)
		ethtx(edev);
	if((fl & RXCOMPL) != 0)
		ethrx(edev);
	if((fl & RXUSED) != 0)
		print("eth: DMA read RX descriptor with used bit set, shouldn't happen\n");
	if((fl & RXOVER) != 0)
		print("eth: RX overrun, shouldn't happen\n");
}

static void
ethprom(void *arg, int on)
{
	Ether *edev;
	Ctlr *c;

	edev = arg;
	c = edev->ctlr;
	if(on)
		c->r[NET_CFG] |= COPYALLEN;
	else
		c->r[NET_CFG] &= ~COPYALLEN;
}

static void
ethmcast(void *arg, uchar *ea, int on)
{
	Ether *edev;
	Ctlr *c;
	u64int a;
	uchar x;

	edev = arg;
	c = edev->ctlr;
	if(edev->nmaddr == 0){
		c->r[NET_CFG] &= ~MCASTHASHEN;
		c->r[HASH_BOT] = 0;
		c->r[HASH_TOP] = 0;
	}
	if(!on)
		return;
	a = (u64int)ea[0]     | (u64int)ea[1]<<8  | (u64int)ea[2]<<16 |
	    (u64int)ea[3]<<24 | (u64int)ea[4]<<32 | (u64int)ea[5]<<40;
	x = a ^ (a>>6) ^ (a>>12) ^ (a>>18) ^ (a>>24) ^ (a>>30) ^ (a>>36) ^ (a>>42);
	x &= 63;
	if(x < 32)
		c->r[HASH_BOT] |= 1<<x;
	else
		c->r[HASH_TOP] |= 1<<(x-32);
	c->r[NET_CFG] |= MCASTHASHEN;
}

static int
ethinit(Ether *edev)
{
	Ctlr *c;
	int i;
	
	c = edev->ctlr;
	c->r[NET_CTRL] = 0;
	c->r[RX_STATUS] = 0xf;
	c->r[TX_STATUS] = 0xff;
	c->r[INTR_DIS] = 0x7FFFEFF;
	c->r[NET_CFG] = MDC_DIV << 18 | FDEN | SPEED | RX1536EN | GIGE_EN | RXCHKSUMEN;
	c->r[SPEC_ADDR1_BOT] = edev->ea[0] | edev->ea[1] << 8 | edev->ea[2] << 16 | edev->ea[3] << 24;
	c->r[SPEC_ADDR1_TOP] = edev->ea[4] | edev->ea[5] << 8;
	c->r[DMA_CFG] = TXCHKSUMEN | (Rbsz/64) << 16 | 1 << 10 | 3 << 8 | 0x10;

	c->rxr = ucalloc(8 * RXRING);
	c->txr = ucalloc(8 * TXRING);
	c->rxs = xspanalloc(4 * RXRING, 4, 0);
	c->txs = xspanalloc(4 * TXRING, 4, 0);
	for(i = 0; i < 2 * RXRING; ){
		c->rxr[i++] = 1;
		c->rxr[i++] = 0;
	}
	c->rxconsi = 1;
	replenish(c);
	c->rxconsi = 0;
	replenish(c);
	for(i = 0; i < 2 * TXRING; ){
		c->txr[i++] = 0;
		c->txr[i++] = 1<<31;
	}
	c->txr[2 * (TXRING - 1)] |= 1<<30;
	c->r[RX_QBAR] = PADDR(c->rxr);
	c->r[TX_QBAR] = PADDR(c->txr);
	
	c->r[NET_CTRL] = MDEN | TXEN | RXEN;
	c->r[INTR_EN] = MGMTDONE | TXUNDER | RXCOMPL | RXUSED | RXOVER;
	return 0;
}

static void
ethattach(Ether *edev)
{
	Ctlr *c;

	c = edev->ctlr;
	if(c->attach)
		return;
	c->attach = 1;
	kproc("ethproc", ethproc, edev);
}

static int
etherpnp(Ether *edev)
{
	static Ctlr ct;
	static uchar mac[] = {0x0e, 0xa7, 0xde, 0xad, 0xbe, 0xef};
	
	if(ct.r != nil)
		return -1;

	memmove(edev->ea, mac, 6);
	edev->ctlr = &ct;
	edev->port = ETH0_BASE;
	ct.r = vmap(edev->port, BY2PG);
	edev->irq = ETH0IRQ;
	edev->ctlr = &ct;
	edev->transmit = ethtx;
	edev->attach = ethattach;
	edev->promiscuous = ethprom;
	edev->multicast = ethmcast;
	edev->arg = edev;
	edev->mbps = 1000;
	
	if(ethinit(edev) < 0){
		edev->ctlr = nil;
		return -1;
	}

	intrenable(edev->irq, ethirq, edev, LEVEL, edev->name);
	return 0;
}

void
etherzynqlink(void)
{
	addethercard("eth", etherpnp);
}
