/*
 * SMSC LAN95XX
 */

#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include "usb.h"
#include "usbfs.h"
#include "ether.h"

enum {
	Doburst		= 1,
	Resettime	= 1000,
	E2pbusytime	= 1000,
	Afcdefault	= 0xF830A1,
//	Hsburst		= 37,	/* from original linux driver */
	Hsburst		= 8,
	Fsburst		= 129,
	Defbulkdly	= 0x2000,

	Ethp8021q	= 0x8100,
	MACoffset 	= 1,
	PHYinternal	= 1,
	Rxerror		= 0x8000,
	Txfirst		= 0x2000,
	Txlast		= 0x1000,

	/* USB vendor requests */
	Writereg	= 0xA0,
	Readreg		= 0xA1,

	/* device registers */
	Intsts		= 0x08,
	Txcfg		= 0x10,
		Txon	= 1<<2,
	Hwcfg		= 0x14,
		Bir	= 1<<12,
		Rxdoff	= 3<<9,
		Mef	= 1<<5,
		Lrst	= 1<<3,
		Bce	= 1<<1,
	Pmctrl		= 0x20,
		Phyrst	= 1<<4,
	Ledgpio		= 0x24,
		Ledspd	= 1<<24,
		Ledlnk	= 1<<20,
		Ledfdx	= 1<<16,
	Afccfg		= 0x2C,
	E2pcmd		= 0x30,
		Busy	= 1<<31,
		Timeout	= 1<<10,
		Read	= 0,
	E2pdata		= 0x34,
	Burstcap	= 0x38,
	Intepctl	= 0x68,
		Phyint	= 1<<15,
	Bulkdelay	= 0x6C,
	Maccr		= 0x100,
		Mcpas	= 1<<19,
		Prms	= 1<<18,
		Hpfilt	= 1<<13,
		Txen	= 1<<3,
		Rxen	= 1<<2,
	Addrh		= 0x104,
	Addrl		= 0x108,
	Hashh		= 0x10C,
	Hashl		= 0x110,
	MIIaddr		= 0x114,
		MIIwrite= 1<<1,
		MIIread	= 0<<1,
		MIIbusy	= 1<<0,
	MIIdata		= 0x118,
	Flow		= 0x11C,
	Vlan1		= 0x120,
	Coecr		= 0x130,
		Txcoe	= 1<<16,
		Rxcoemd	= 1<<1,
		Rxcoe	= 1<<0,

	/* MII registers */
	Bmcr		= 0,
		Bmcrreset= 1<<15,
		Speed100= 1<<13,
		Anenable= 1<<12,
		Anrestart= 1<<9,
		Fulldpx	= 1<<8,
	Bmsr		= 1,
	Advertise	= 4,
		Adcsma	= 0x0001,
		Ad10h	= 0x0020,
		Ad10f	= 0x0040,
		Ad100h	= 0x0080,
		Ad100f	= 0x0100,
		Adpause	= 0x0400,
		Adpauseasym= 0x0800,
		Adall	= Ad10h|Ad10f|Ad100h|Ad100f,
	Phyintsrc	= 29,
	Phyintmask	= 30,
		Anegcomp= 1<<6,
		Linkdown= 1<<4,
};

static int
wr(Dev *d, int reg, int val)
{
	int ret;

	ret = usbcmd(d, Rh2d|Rvendor|Rdev, Writereg, 0, reg,
		(uchar*)&val, sizeof(val));
	if(ret < 0)
		deprint(2, "%s: wr(%x, %x): %r", argv0, reg, val);
	return ret;
}

static int
rr(Dev *d, int reg)
{
	int ret, rval;

	ret = usbcmd(d, Rd2h|Rvendor|Rdev, Readreg, 0, reg,
		(uchar*)&rval, sizeof(rval));
	if(ret < 0){
		fprint(2, "%s: rr(%x): %r", argv0, reg);
		return 0;
	}
	return rval;
}

static int
miird(Dev *d, int idx)
{
	while(rr(d, MIIaddr) & MIIbusy)
		;
	wr(d, MIIaddr, PHYinternal<<11 | idx<<6 | MIIread);
	while(rr(d, MIIaddr) & MIIbusy)
		;
	return rr(d, MIIdata);
}

static void
miiwr(Dev *d, int idx, int val)
{
	while(rr(d, MIIaddr) & MIIbusy)
		;
	wr(d, MIIdata, val);
	wr(d, MIIaddr, PHYinternal<<11 | idx<<6 | MIIwrite);
	while(rr(d, MIIaddr) & MIIbusy)
		;
}

static int
eepromr(Dev *d, int off, uchar *buf, int len)
{
	int i, v;

	for(i = 0; i < E2pbusytime; i++)
		if((rr(d, E2pcmd) & Busy) == 0)
			break;
	if(i == E2pbusytime)
		return -1;
	for(i = 0; i < len; i++){
		wr(d, E2pcmd, Busy|Read|(i+off));
		while((v = rr(d, E2pcmd) & (Busy|Timeout)) == Busy)
			;
		if(v & Timeout)
			return -1;
		buf[i] = rr(d, E2pdata);
	}
	return 0;
}

static void
phyinit(Dev *d)
{
	int i;

	miiwr(d, Bmcr, Bmcrreset|Anenable);
	for(i = 0; i < Resettime/10; i++){
		if((miird(d, Bmcr) & Bmcrreset) == 0)
			break;
		sleep(10);
	}
	miiwr(d, Advertise, Adcsma|Adall|Adpause|Adpauseasym);
//	miiwr(d, Advertise, Adcsma|Ad10f|Ad10h|Adpause|Adpauseasym);
	miird(d, Phyintsrc);
	miiwr(d, Phyintmask, Anegcomp|Linkdown);
	miiwr(d, Bmcr, miird(d, Bmcr)|Anenable|Anrestart);
}


static int
doreset(Dev *d, int reg, int bit)
{
	int i;

	if(wr(d, reg, bit) < 0)
		return -1;
	for(i = 0; i < Resettime/10; i++){
		 if((rr(d, reg) & bit) == 0)
			return 1;
		sleep(10);
	}
	return 0;
}

static int
getmac(Dev *d, uchar buf[])
{
	int i;
	uchar ea[Eaddrlen];

	if(eepromr(d, MACoffset, ea, Eaddrlen) < 0)
		return -1;
	for(i = 0; i < Eaddrlen; i++)
		if(ea[i] != 0 && ea[i] != 0xFF){
			memmove(buf, ea, Eaddrlen);
			break;
		}
	return Eaddrlen;
}

static int
smscinit(Ether *ether)
{
	Dev *d;

	if(ether->cid != S95xx)
		return -1;
	d = ether->dev;
	deprint(2, "%s: setting up SMSC95XX\n", argv0);
	if(!doreset(d, Hwcfg, Lrst) || !doreset(d, Pmctrl, Phyrst))
		return -1;
	if(getmac(d, ether->addr) < 0)
		return -1;
	wr(d, Addrl, GET4(ether->addr));
	wr(d, Addrh, GET2(ether->addr+4));
	if(Doburst){
		wr(d, Hwcfg, (rr(d,Hwcfg)&~Rxdoff)|Bir|Mef|Bce);
		wr(d, Burstcap, Hsburst);
	}else{
		wr(d, Hwcfg, (rr(d,Hwcfg)&~(Rxdoff|Mef|Bce))|Bir);
		wr(d, Burstcap, 0);
	}
	wr(d, Bulkdelay, Defbulkdly);
	wr(d, Intsts, ~0);
	wr(d, Ledgpio, Ledspd|Ledlnk|Ledfdx);
	wr(d, Flow, 0);
	wr(d, Afccfg, Afcdefault);
	wr(d, Vlan1, Ethp8021q);
	wr(d, Coecr, rr(d,Coecr)&~(Txcoe|Rxcoe)); /* TODO could offload checksums? */

	wr(d, Hashh, 0);
	wr(d, Hashl, 0);
	wr(d, Maccr, rr(d,Maccr)&~(Prms|Mcpas|Hpfilt));

	phyinit(d);

	wr(d, Intepctl, rr(d, Intepctl)|Phyint);
	wr(d, Maccr, rr(d, Maccr)|Txen|Rxen);
	wr(d, Txcfg, Txon);

	return 0;
}

static long
smscbread(Ether *e, Buf *bp)
{
	uint hd;
	int n, m;
	Buf *rbp;

	rbp = e->aux;
	if(rbp->ndata < 4){
		rbp->rp = rbp->data;
		rbp->ndata = read(e->epin->dfd, rbp->rp, Doburst? Hsburst*512:
			Maxpkt);
		if(rbp->ndata < 0)
			return -1;
	}
	if(rbp->ndata < 4){
		werrstr("short frame");
		fprint(2, "smsc short frame %d bytes\n", rbp->ndata);
		return 0;
	}
	hd = GET4(rbp->rp);
	n = hd >> 16;
	m = (n + 4 + 3) & ~3;
	if(n < 6 || m > rbp->ndata){
		werrstr("frame length");
		fprint(2, "smsc length error packet %d buf %d\n", n, rbp->ndata);
		rbp->ndata = 0;
		return 0;
	}
	if(hd & Rxerror){
		fprint(2, "smsc rx error %8.8ux\n", hd);
		n = 0;
	}else{
		bp->rp = bp->data + Hdrsize;
		memmove(bp->rp, rbp->rp+4, n);
	}
	bp->ndata = n;
	rbp->rp += m;
	rbp->ndata -= m;
	return n;
}

static long
smscbwrite(Ether *e, Buf *bp)
{
	int n;

	n = bp->ndata & 0x7FF;
	bp->rp -= 8;
	bp->ndata += 8;
	PUT4(bp->rp, n | Txfirst | Txlast);
	PUT4(bp->rp+4, n);
	n = write(e->epout->dfd, bp->rp, bp->ndata);
	return n;
}

static int
smscpromiscuous(Ether *e, int on)
{
	USED(on, e);
#ifdef TODO		/* copied from asix */
	int rxctl;

	deprint(2, "%s: smscpromiscuous %d\n", argv0, on);
	rxctl = getrxctl(e->dev);
	if(on != 0)
		rxctl |= Rxctlprom;
	else
		rxctl &= ~Rxctlprom;
	return wr(e->dev, Cwrxctl, rxctl);
#endif
	return -1;
}

static int
smscmulticast(Ether *e, uchar *addr, int on)
{
	USED(addr, on, e);
#ifdef TODO		/* needed for ipv6; copied from asix */
	int rxctl;

	/* BUG: should write multicast filter */
	rxctl = getrxctl(e->dev);
	if(e->nmcasts != 0)
		rxctl |= Rxctlamall;
	else
		rxctl &= ~Rxctlamall;
	deprint(2, "%s: smscmulticast %d\n", argv0, e->nmcasts);
	return wr(e->dev, Cwrxctl, rxctl);
#endif
	return -1;
}

static void
smscfree(Ether *ether)
{
	free(ether->aux);
	ether->aux = nil;
}

int
smscreset(Ether *ether)
{
	Cinfo *ip;
	Dev *dev;

	dev = ether->dev;
	for(ip = cinfo; ip->vid != 0; ip++)
		if(ip->vid == dev->usb->vid && ip->did == dev->usb->did){
			ether->cid = ip->cid;
			if(smscinit(ether) < 0){
				deprint(2, "%s: smsc init failed: %r\n", argv0);
				return -1;
			}
			deprint(2, "%s: smsc reset done\n", argv0);
			ether->name = "smsc";
			if(Doburst){
				ether->bufsize = Hsburst*512;
				ether->aux = emallocz(sizeof(Buf) +
					ether->bufsize - Maxpkt, 1);
			}else{
				ether->bufsize = Maxpkt;
				ether->aux = emallocz(sizeof(Buf), 1);
			}
			ether->free = smscfree;
			ether->bread = smscbread;
			ether->bwrite = smscbwrite;
			ether->promiscuous = smscpromiscuous;
			ether->multicast = smscmulticast;
			ether->mbps = 100;	/* BUG */
			return 0;
		}
	return -1;
}
