shithub: sitara

ref: a7b1ab8236b7ccd41e96e9e4008cbc3213294724
dir: sitara/etherbbb.c

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

enum{
	CPSW_SS,
		SS_SOFT_RESET = 0x08/4,

	CPSW_CPDMA = 0x0800/4,
		TX_CONTROL = CPSW_CPDMA+0x04/4,
			TX_EN = 1<<0,
		RX_CONTROL = CPSW_CPDMA+0x14/4,
			RX_EN = 1<<0,
		CPDMA_SOFT_RESET = CPSW_CPDMA+0x1c/4,
		DMASTATUS = CPSW_CPDMA+0x24/4,
		RX_BUFFER_OFFSET = CPSW_CPDMA+0x28/4,
		TX_INTMASK_SET = CPSW_CPDMA+0x88/4,
		TX_INTMASK_CLEAR = CPSW_CPDMA+0x8c/4,
			TX_MASK_ALL = 0xff,
		CPDMA_EOI_VECTOR = CPSW_CPDMA+0x94/4,
		RX_INTMASK_SET = CPSW_CPDMA+0xa8/4,
			RX_MASK_ALL = 0xff,
		DMA_INTMASK_SET = CPSW_CPDMA+0xb8/4,
			STAT_INT_MASK = 1<<0,
			HOST_ERR_INT_MASK = 1<<1,

	CPSW_STATERAM = 0x0a00/4,
		TX0_HDP = CPSW_STATERAM+0x00/4,
		RX0_HDP = CPSW_STATERAM+0x20/4,
		TX0_CP	= CPSW_STATERAM+0x40/4,
		RX0_CP	= CPSW_STATERAM+0x60/4,

	CPSW_ALE = 0x0d00/4,
		ALE_CONTROL = CPSW_ALE+0x08/4,
			BYPASS = 1<<4,
			CLEAR_TABLE = 1<<30,
			ENABLE_ALE = 1<<31,
		PORTCTL0 = CPSW_ALE+0x40/4,
		PORTCTL1 = CPSW_ALE+0x44/4,
			PORT_STATE = 3,

	CPSW_SL1 = 0x0d80/4,
		MACCONTROL = CPSW_SL1+0x04/4,
			IFCTL_A = 1<<15,
			GIG = 1<<7,
			GMII_EN = 1<<5,
			FULLDUPLEX = 1<<0,
		MACSTATUS = CPSW_SL1+0x08/4,
		SL1_SOFT_RESET = CPSW_SL1+0x0c/4,

	CPSW_WR = 0x1200/4,
		WR_SOFT_RESET = CPSW_WR+0x04/4,
		C0_RX_THRESH_EN = CPSW_WR+0x10/4,
		C0_RX_EN = CPSW_WR+0x14/4,
		C0_TX_EN = CPSW_WR+0x18/4,
		C0_MISC_EN = CPSW_WR+0x1c/4,
			MDIO_USERINT = 1<<0,
			MDIO_LINKINT = 1<<1,
			HOST_PEND = 1<<2,
			STAT_PEND = 1<<3,
			EVNT_PEND = 1<<4,
		C0_RX_THRESH_STAT = CPSW_WR+0x40/4,
		C0_RX_STAT = CPSW_WR+0x44/4,
		C0_TX_STAT = CPSW_WR+0x48/4,
		C0_MISC_STAT = CPSW_WR+0x4c/4,
		RGMII_CTL = CPSW_WR+0x88/4,
			RGMII1_LINK = 1<<0,
			RGMII2_LINK = 1<<4,

	MDIO = 0x1000/4,
		MDIOCONTROL = MDIO+04/4,
		MDIOALIVE = MDIO+0x08/4,
		MDIOLINK = MDIO+0x0c/4,
		MDIOLINKINTRAW = MDIO+0x10/4,
		MDIOLINKINTMASKED = MDIO+0x14/4,
		MDIOUSERPHYSEL0 = MDIO+0x84/4,
		MDIOUSERPHYSEL1 = MDIO+0x88/4,
			PHYADDRMON = 0x1f,
			LINKINTENB = 1<<6,
			LINKSEL = 1<<7,
};

#define TX_HDP(i)	(TX0_HDP+i)
#define RX_HDP(i)	(RX0_HDP+i)
#define TX_CP(i)	(TX0_CP+i)
#define RX_CP(i)	(RX0_CP+i)

enum{
	EOQ = 1<<12,
	OWNER = 1<<13,
	EOP = 1<<14,
	SOP = 1<<15,
};

typedef struct Desc Desc;
struct Desc
{
	uintptr next;
	uintptr bufptr;
	ushort buflen;
	ushort bufoff;
	ushort pktlen;
	ushort flags;
};

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

enum{
	RXRING = 0x200,
	TXRING = 0x200,
};

typedef struct Ctlr Ctlr;
struct Ctlr
{
	ulong *r;
	int attach;
	int rxconsi;
	int rxprodi;
	Desc *rxd;
	Block **rxb;
	int txconsi;
	int txprodi;
	Desc *txd;
	Block **txb;
	Lock txlock;
};

static int ethinit(Ether*);

static int
replenish(Ctlr *c)
{
	int n;
	Block *b;
	Desc *d;

	for(;;){
		n = (c->rxprodi+1) & (RXRING-1);
		if(n == c->rxconsi){
			c->rxd[c->rxprodi].next = 0;
			break;
		}
		b = iallocb(Rbsz);
		if(b == nil){
			iprint("cpsw: out of memory for rx buffers\n");
			return -1;
		}
		c->rxb[n] = b;
		d = &c->rxd[n];
		d->bufptr = PADDR(b->rp);
		d->bufoff = 0;
		d->buflen = BALLOC(b);
		if((d->buflen & 0x7ff) != d->buflen)
			d->buflen = 0x7ff;
		d->flags = OWNER;
		assert(d->buflen > 0);
		//cleandse(d, d+1);
		cleandse(b->base, b->lim);
		//coherence();
		assert(c->rxd[c->rxprodi].next == 0);
		d = &c->rxd[c->rxprodi];
		d->next = PADDR(&c->rxd[n]);
		//cleandse(d, d+1);
		c->rxprodi = n;
	}
	return 0;
}

static void
ethrxirq(Ureg*, void *arg)
{
	static int x;

	int n;
	Ether *edev;
	Ctlr *c;
	Desc *d, *d0;
	Block *b;

	edev = arg;
	c = edev->ctlr;

 	//iprint("rx enter (%d)!\n", x);
	n = 0;
	d0 = nil;
	for(;;){
		d = &c->rxd[c->rxconsi];
		//invaldse(d, d+1);
		if((d->flags & OWNER) != 0)
			break;
		if((d->flags & SOP) == 0 || (d->flags & EOP) == 0)
			iprint("cpsw: partial frame received -- shouldn't happen\n");
		if((d->flags & OWNER) == 0 && (d->flags & EOQ) != 0 && d->next != 0){
			//iprint("cpsw: rx misqueue detected\n");
			d0 = d;
		}
		b = c->rxb[c->rxconsi];
		b->wp = b->rp + (d->pktlen & 0x7ff);
		invaldse(b->rp, b->wp);
		//coherence();
		etheriq(edev, b, 1);
		c->r[RX0_CP] = PADDR(d);
		c->rxconsi = (c->rxconsi+1) & (RXRING-1);
		replenish(c);
		n++;
	}
	if(n == 0)
		iprint("cpsw: rx buffer full\n");
	if(d0 != nil){
		assert(d0->next == PADDR(d));
		c->r[RX0_HDP] = PADDR(d);
	}
	c->r[CPDMA_EOI_VECTOR] = 1;
	//iprint("rx leave (%d)!\n", x++);
}

static void
ethtx(Ether *edev)
{
	int i, n;
	Block *b;
	Ctlr *c;
	Desc *d0, *dn, *dp;

	c = edev->ctlr;
	ilock(&c->txlock);

	for(;;){
		dp = &c->txd[c->txconsi];
		if(c->txconsi == c->txprodi){
			dp->flags |= EOQ;
			break;
		}
		if((dp->flags & OWNER) != 0)
			break;
		c->txconsi = (c->txconsi+1) & (TXRING-1);
	}
	c->r[TX0_CP] = PADDR(&c->txd[c->txconsi]);
	n = 0;
	d0 = nil;
	for(;;){
		i = (c->txprodi+1) & (TXRING-1);
		dn = &c->txd[i];
		if((dn->flags & OWNER) != 0){
			iprint("cpsw: tx buffer full\n");
			break;
		}
		b = qget(edev->oq);
		if(b == nil)
			break;
		if(c->txb[c->txprodi] != nil)
			freeb(c->txb[c->txprodi]);
		c->txb[c->txprodi] = b;
		dp->next = PADDR(dn);
		dn->next = 0;
		dn->bufptr = PADDR(b->rp);
		dn->buflen = BLEN(b);
		dn->flags = SOP | EOP | OWNER;
		dn->pktlen = dn->buflen;
		if(d0 == nil)
			d0 = dp;
		dp = dn;
		c->txprodi = i;
		n++;
	}
	//iprint("tx: queued %d packets\n", n);
	if(d0 != nil){ // queued at least 1 packet
		if((d0->flags & OWNER) == 0 && (d0->flags & EOQ) != 0){
			//iprint("tx: misqueue detected!\n");
			c->r[TX0_HDP] = d0->next;
		}
	}
	iunlock(&c->txlock);
}

static void
ethtxirq(Ureg*, void *arg)
{
	Ether *edev;
	Ctlr *c;

	edev = arg;
	c = edev->ctlr;
	//assert(c->r[TX0_HDP] == 0);
	ethtx(edev);
	c->r[CPDMA_EOI_VECTOR] = 2;
}

static void
debugctlr(Ctlr *c)
{
	int i;
	uintptr hdp;
	Desc *d;

	hdp = c->r[RX0_HDP];
	if(hdp != 0){
		d = (Desc*)kaddr(hdp);
		iprint("pa(RX0_HDP)=%#p\n", hdp);
		iprint("va(RX0_HDP)=%#p\n", d);
		iprint("ii(RX0_HDP)=%ld\n",  d - &c->rxd[0]);	
		iprint("next=%#p\n", d->next);
	}
	for(i = 0; i < RXRING; i++){
		d = &c->rxd[i];
		if(d->buflen <= d->bufoff)
			iprint("i=%d -> {buflen=%d, bufoff=%d, pktlen=%d }\n", i, d->buflen, d->bufoff, d->pktlen);
	}
}

static void
ethmiscirq(Ureg*, void *arg)
{
	Ether *edev;
	Ctlr *c;
	ulong r;

	edev = arg;
	c = edev->ctlr;
	r = c->r[C0_MISC_STAT];

	if((r & MDIO_LINKINT) != 0){
		if((c->r[MDIOLINK] & 1) == 0){
			edev->link = 0;
			iprint("cpsw: no link\n");
		}else{
			edev->link = 1;
			edev->mbps = c->r[MACCONTROL] & IFCTL_A ? 100 : 10;
			iprint("cpsw: %dMbps %s duplex link\n", edev->mbps, c->r[MACCONTROL] & FULLDUPLEX ? "full" : "half");
		}
		c->r[MDIOLINKINTRAW] |= 1;
	}
	if((r & HOST_PEND) != 0){
		debugctlr(c);
		r = c->r[DMASTATUS];
		iprint("cpsw: dma host error (tx=0x%ulx, rx=0x%ulx)\n", (r>>20)&0xf, (r>>12)&0xf);
		ethinit(edev);
	}

	c->r[CPDMA_EOI_VECTOR] = 3;
}

static int
ethinit(Ether *edev)
{
	int i;
	Ctlr *c;

	c = edev->ctlr;

	c->r[SS_SOFT_RESET] |= 1;
	c->r[SL1_SOFT_RESET] |= 1;
	c->r[CPDMA_SOFT_RESET] |= 1;

	while((c->r[SS_SOFT_RESET] & 1) != 0);
	while((c->r[SL1_SOFT_RESET] & 1) != 0);
	while((c->r[CPDMA_SOFT_RESET] & 1) != 0);

	for(i = 0; i < 8; i++){
		c->r[TX_HDP(i)] = 0;
		c->r[RX_HDP(i)] = 0;
		c->r[TX_CP(i)] = 0;
		c->r[RX_CP(i)] = 0;
	}

	c->r[ALE_CONTROL] |= ENABLE_ALE | CLEAR_TABLE | BYPASS;
	c->r[PORTCTL0] |= PORT_STATE;
	c->r[PORTCTL1] |= PORT_STATE;

	//intrenable(ETHIRQRXTHR, ethrxthrirq, edev, edev->name);
	intrenable(ETHIRQRX, ethrxirq, edev, edev->name);
	intrenable(ETHIRQTX, ethtxirq, edev, edev->name);
	intrenable(ETHIRQMISC, ethmiscirq, edev, edev->name);

	c->r[TX_INTMASK_SET] |= TX_MASK_ALL;
	c->r[RX_INTMASK_SET] |= RX_MASK_ALL;
	c->r[DMA_INTMASK_SET] |= STAT_INT_MASK | HOST_ERR_INT_MASK;

	c->r[MDIOUSERPHYSEL0] |= LINKINTENB;

	c->r[C0_RX_THRESH_EN] |= 1;
	c->r[C0_RX_EN] |= 1;
	c->r[C0_TX_EN] |= 1;
	c->r[C0_MISC_EN] |= HOST_PEND | MDIO_LINKINT;

	c->r[RX_CONTROL] |= RX_EN;
	c->r[TX_CONTROL] |= TX_EN;

	c->r[MACCONTROL] &= ~GIG;
	c->r[MACCONTROL] |= GMII_EN | FULLDUPLEX | IFCTL_A;

	if(c->rxd == nil)
		c->rxd = ucalloc(RXRING*sizeof(Desc));
	memset(c->rxd, 0, RXRING*sizeof(Desc));
	if(c->rxb == nil)
		c->rxb = ucalloc(RXRING*sizeof(Block*));
	memset(c->rxb, 0, RXRING*sizeof(Block*));

	if(c->txd == nil)
		c->txd = ucalloc(TXRING*sizeof(Desc));
	memset(c->txd, 0, TXRING*sizeof(Desc));
	if(c->txb == nil)
		c->txb = ucalloc(TXRING*sizeof(Block*));
	memset(c->txb, 0, TXRING*sizeof(Block*));

#ifdef nope
	ulong x;
	x = va2pa(c->txd);
	if((x&1) != 0)
		iprint("ethinit: va2pa failed\n");
	else
		iprint("ethinit: %#p, SH=%ulx, OUTER=%ulx, INNER=%ulx\n", x, (x>>7)&1, (x>>2)&3, (x>>4)&7);

	x = va2pa(c->txb);
	if((x&1) != 0)
		iprint("ethinit: va2pa failed\n");
	else
		iprint("ethinit: %#p, SH=%ulx, OUTER=%ulx, INNER=%ulx\n", x, (x>>7)&1, (x>>2)&3, (x>>4)&7);
#endif

	c->rxprodi = RXRING-1;
	c->rxconsi = RXRING-1;
	replenish(c);
	c->rxconsi = 0;
	replenish(c);

	c->r[RX0_HDP] = PADDR(&c->rxd[c->rxconsi]);

	return 0;
}

static void
ethprom(void*, int)
{
}

static void
ethmcast(void*, uchar*, int)
{
}

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

	c = edev->ctlr;
	if(c->attach)
		return;
	c->attach = 1;
}

static int
etherpnp(Ether *edev)
{
	static Ctlr ct;
	static uchar mac[] = {0x6c, 0xec, 0xeb, 0xaf, 0x1c, 0xed};

	ulong x;

	if(ct.r != nil)
		return -1;
	memmove(edev->ea, mac, 6);
	edev->ctlr = &ct;
	edev->port = ETH_BASE;
	ct.r = vmap(edev->port, 2*BY2PG);

#ifdef nope
	x = va2pa(ct.r);
	if((x&1) != 0)
		iprint("etherpnp: va2pa failed\n");
	else
		iprint("etherpnp: %#p, SH=%ulx, OUTER=%ulx, INNER=%ulx\n", x, (x>>7)&1, (x>>2)&3, (x>>4)&7);
#endif

	edev->irq0 = ETHIRQRXTHR;
	edev->irqn = ETHIRQMISC;
	edev->transmit = ethtx;
	edev->attach = ethattach;
	edev->promiscuous = ethprom;
	edev->multicast = ethmcast;
	edev->mbps = 100;
	edev->arg = edev;
	if(ethinit(edev) < 0){
		edev->ctlr = nil;
		return -1;
	}
	return 0;
}

void
etherbbblink(void)
{
	addethercard("cpsw", etherpnp);
}