shithub: riscv

ref: f1a32c955360268a7bc901de057e6e451c979853
dir: /sys/src/9/imx8/etherimx.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 "../port/etherif.h"
#include "../port/ethermii.h"

enum {
	Ptpclk		= 100*Mhz,
	Busclk		= 266*Mhz,
	Txclk		= 125*Mhz,

	Maxtu		= 1518,

	R_BUF_SIZE	= ((Maxtu+BLOCKALIGN-1)&~BLOCKALIGN),
};

enum {
	ENET_EIR	= 0x004/4,	/* Interrupt Event Register */
	ENET_EIMR	= 0x008/4,	/* Interrupt Mask Register */
		INT_BABR	=1<<30,	/* Babbling Receive Error */
		INT_BABT	=1<<31,	/* Babbling Transmit Error */
		INT_GRA		=1<<28,	/* Graceful Stop Complete */
		INT_TXF		=1<<27,	/* Transmit Frame Interrupt */
		INT_TXB		=1<<26,	/* Transmit Buffer Interrupt */
		INT_RXF		=1<<25,	/* Receive Frame Interrupt */
		INT_RXB		=1<<24,	/* Receive Buffer Interrupt */
		INT_MII		=1<<23,	/* MII Interrupt */
		INT_EBERR	=1<<22,	/* Ethernet Bus Error */
		INT_LC		=1<<21,	/* Late Collision */
		INT_RL		=1<<20,	/* Collision Retry Limit */
		INT_UN		=1<<19,	/* Transmit FIFO Underrun */
		INT_PLR		=1<<18,	/* Payload Receive Error */
		INT_WAKEUP	=1<<17,	/* Node Wakeup Request Indication */
		INT_TS_AVAIL	=1<<16,	/* Transmit Timestamp Available */
		INT_TS_TIMER	=1<<15,	/* Timestamp Timer */
		INT_RXFLUSH_2	=1<<14,	/* RX DMA Ring 2 flush indication */
		INT_RXFLUSH_1	=1<<13,	/* RX DMA Ring 1 flush indication */
		INT_RXFLUSH_0	=1<<12,	/* RX DMA Ring 0 flush indication */
		INT_TXF2	=1<<7,	/* Transmit frame interrupt, class 2 */
		INT_TXB2	=1<<6,	/* Transmit buffer interrupt, class 2 */
		INT_RXF2	=1<<5,	/* Receive frame interrupt, class 2 */
		INT_RXB2	=1<<4,	/* Receive buffer interrupt, class 2 */
		INT_TXF1	=1<<3,	/* Transmit frame interrupt, class 1 */
		INT_TXB1	=1<<2,	/* Transmit buffer interrupt, class 1 */
		INT_RXF1	=1<<1,	/* Receive frame interrupt, class 1 */
		INT_RXB1	=1<<0,	/* Receive buffer interrupt, class 1 */

	ENET_RDAR	= 0x010/4,	/* Receive Descriptor Active Register */
		RDAR_ACTIVE	=1<<24,	/* Descriptor Active */
	ENET_TDAR	= 0x014/4,	/* Transmit Descriptor Active Register */
		TDAR_ACTIVE	=1<<24,	/* Descriptor Active */

	ENET_ECR	= 0x024/4,	/* Ethernet Control Register */
		ECR_RESERVED	=7<<28,
		ECR_SVLANDBL	=1<<11,	/* S-VLAN double tag */
		ECR_VLANUSE2ND	=1<<10,	/* VLAN use second tag */
		ECR_SVLANEN	=1<<9,	/* S-VLAN enable */
		ECR_DBSWP	=1<<8,	/* Descriptor Byte Swapping Enable */
		ECR_DBGEN	=1<<6,	/* Debug Enable */
		ECR_SPEED_100M	=0<<5,
		ECR_SPEED_1000M	=1<<5,
		ECR_EN1588	=1<<4,	/* Enables enhanced functionality of the MAC */
		ECR_SLEEP	=1<<3,	/* Sleep Mode Enable */
		ECR_MAGICEN	=1<<2,	/* Magic Packet Detection Enable */
		ECR_ETHEREN	=1<<1,	/* Ethernet Enable */
		ECR_RESET	=1<<0,	/* Ethernet MAC Reset */

	ENET_MMFR	= 0x040/4,	/* MII Management Frame Register */
		MMFR_ST		=1<<30,
		MMFR_RD		=2<<28,
		MMFR_WR		=1<<28,
		MMFR_PA_SHIFT	=23,
		MMFR_TA		=2<<16,
		MMFR_RA_SHIFT	=18,

	ENET_MSCR	= 0x044/4,	/* MII Speed Control Register */
		MSCR_SPEED_SHIFT=1,	/* MII speed = module_clock/((SPEED+1)*2) */
		MSCR_DIS_PRE	=1<<7,	/* disable preamble */
		MSCR_HOLD_SHIFT	=8,	/* hold cycles in module_clock */

	ENET_MIBC	= 0x064/4,	/* MIB Control Register */
	ENET_RCR	= 0x084/4,	/* Receive Control Register */
		RCR_GRS		=1<<31,	/* Gracefull Receive Stopped */
		RCR_NLC		=1<<30,	/* Payload Length Check Disable */
		RCR_MAX_FL_SHIFT=16,	/* Maximum Frame Length */
		RCR_CFEN	=1<<15,	/* MAC Control Frame Enable */
		RCR_CRCFWD	=1<<14,	/* Forward Received CRC */
		RCR_PAUFWD	=1<<13,	/* Forward Pause Frames */
		RCR_PADEN	=1<<12,	/* Enable Frame Padding Remove */
		RCR_RMII_10T	=1<<9,	/* Enables 10-Mbit/s mode of the RMII/RGMII */
		RCR_RMII_MODE	=1<<8,	/* RMII Mode Enable */
		RCR_RGMII_EN	=1<<6,	/* RGMII Mode Enable */
		RCR_FCE		=1<<5,	/* Flow Control Enable */
		RCR_REJ		=1<<4,	/* Broadcast Frame Reject */
		RCR_PROM	=1<<3,	/* Promiscuous Mode */
		RCR_MII_MODE	=1<<2,	/* Media Independent Interface Mode (must always be set) */
		RCR_DRT		=1<<1,	/* Disable Receive On Timeout */
		RCR_LOOP	=1<<0,	/* Internal Loopback */

	ENET_TCR	= 0x0C4/4,	/* Transmit Control Register */
		TCR_CRCFWD	=1<<9,	/* Foward Frame From Application With CRC */
		TCR_ADDINS	=1<<8,	/* Set MAC Address on Transmit */
		TCR_RFC_PAUSE	=1<<4,	/* Receive Frame Control Pause */
		TCR_TFC_PAUSE	=1<<3,	/* Transmit Frame Control Pause */
		TCR_FDEN	=1<<2,	/* Full-Duplex Enable */
		TCR_GTS		=1<<0,	/* Graceful Transmit Stop */

	ENET_PALR	= 0x0E4/4,	/* Physical Address Lower Register */
	ENET_PAUR	= 0x0E8/4,	/* Physical Address Upper Register */

	ENET_OPD	= 0x0EC/4,	/* Opcode/Pause Duration Register */

	ENET_TXIC0	= 0x0F0/4,	/* Transmit Interrupt Coalescing Register */
	ENET_TXIC1	= 0x0F4/4,	/* Transmit Interrupt Coalescing Register */
	ENET_TXIC2	= 0x0F8/4,	/* Transmit Interrupt Coalescing Register */
	ENET_RXIC0	= 0x100/4,	/* Receive Interrupt Coalescing Register */
	ENET_RXIC1	= 0x104/4,	/* Receive Interrupt Coalescing Register */
	ENET_RXIC2	= 0x108/4,	/* Receive Interrupt Coalescing Register */
		IC_EN		= 1<<31,
		IC_CS		= 1<<30,
		IC_FT_SHIFT	= 20,
		IC_TT_SHIFT	= 0,

	ENET_IAUR	= 0x118/4,	/* Descriptor Individual Upper Address Register */
	ENET_IALR	= 0x11C/4,	/* Descriptor Individual Lower Address Register */
	ENET_GAUR	= 0x120/4,	/* Descriptor Group Upper Address Register */
	ENET_GALR	= 0x124/4,	/* Descriptor Group Lower Address Register */
	ENET_TFWR	= 0x144/4,	/* Transmit FIFO Watermark Register */
		TFWR_STRFWD	= 1<<8,

	ENET_RDSR1	= 0x160/4,	/* Receive Descriptor Ring 1 Start Register */
	ENET_TDSR1	= 0x164/4,	/* Transmit Buffer Descriptor Ring 1 Start Register */
	ENET_MRBR1	= 0x168/4,	/* Maximum Receive Buffer Size Register Ring 1 */

	ENET_RDSR2	= 0x16C/4,	/* Receive Descriptor Ring 2 Start Register */
	ENET_TDSR2	= 0x170/4,	/* Transmit Buffer Descriptor Ring 2 Start Register */
	ENET_MRBR2	= 0x174/4,	/* Maximum Receive Buffer Size Register Ring 2 */

	ENET_RDSR	= 0x180/4,	/* Receive Descriptor Ring 0 Start Register */
	ENET_TDSR	= 0x184/4,	/* Transmit Buffer Descriptor Ring 0 Start Register */
	ENET_MRBR	= 0x188/4,	/* Maximum Receive Buffer Size Register Ring 0 */

	ENET_RSFL	= 0x190/4,	/* Receive FIFO Section Full Threshold */
	ENET_RSEM	= 0x194/4,	/* Receive FIFO Section Empty Threshold */
	ENET_RAEM	= 0x198/4,	/* Receive FIFO Almost Empty Threshold */
	ENET_RAFL	= 0x19C/4,	/* Receive FIFO Almost Full Threshold */

	ENET_TSEM	= 0x1A0/4,	/* Transmit FIFO Section Empty Threshold */
	ENET_TAEM	= 0x1A4/4,	/* Transmit FIFO Almost Empty Threshold */
	ENET_TAFL	= 0x1A8/4,	/* Transmit FIFO Almost Full Threshold */

	ENET_TIPG	= 0x1AC/4,	/* Transmit Inter-Packet Gap */
	ENET_FTRL	= 0x1B0/4,	/* Frame Truncation Length */
	ENET_TACC	= 0x1C0/4,	/* Transmit Accelerator Function Configuration */
	ENET_RACC	= 0x1C4/4,	/* Receive Accelerator Function Configuration */

	ENET_RCMR1	= 0x1C8/4,	/* Receive Classification Match Register */
	ENET_RCMR2	= 0x1CC/4,	/* Receive Classification Match Register */

	ENET_DMA1CFG	= 0x1D8/4,	/* DMA Class Based Configuration */
	ENET_DMA2CFG	= 0x1DC/4,	/* DMA Class Based Configuration */

	ENET_RDAR1	= 0x1E0/4,	/* Receive Descriptor Active Register - Ring 1 */
	ENET_TDAR1	= 0x1E4/4,	/* Transmit Descriptor Active Register - Ring 1 */
	ENET_RDAR2	= 0x1E8/4,	/* Receive Descriptor Active Register - Ring 2 */
	ENET_TDAR2	= 0x1EC/4,	/* Transmit Descriptor Active Register - Ring 2 */

	ENET_QOS	= 0x1F0/4,	/* QOS Scheme */
};

enum {
	/* transmit descriptor status bits */
	TD_R		= 1<<(15+16),	/* Ready */
	TD_OWN		= 1<<(14+16),	/* Ownership */
	TD_W		= 1<<(13+16),	/* Wrap */
	TD_L		= 1<<(11+16),	/* Last in a frame */

	TD_TC		= 1<<(10+16),	/* Transmit CRC */
	TD_ERR		= TD_TC,

	TD_LEN		= 0xFFFF,

	/* receive desctriptor status bits */
	RD_E		= 1<<(15+16),	/* Empty */
	RD_W		= 1<<(13+16),	/* Wrap */
	RD_L		= 1<<(11+16),	/* Last in a frame */

	RD_M		= 1<<(8+16),	/* Miss */
	RD_BC		= 1<<(7+16),	/* broadcast */
	RD_MC		= 1<<(6+16),	/* multicast */

	RD_LG		= 1<<(5+16),	/* length violation */
	RD_NO		= 1<<(4+16),	/* non octet aligned frame */
	RD_CR		= 1<<(2+16),	/* crc error */
	RD_OV		= 1<<(1+16),	/* overrun */
	RD_TR		= 1<<(0+16),	/* truncated */
	RD_ERR		= RD_LG | RD_NO | RD_CR | RD_OV | RD_TR,

	RD_LEN		= 0xFFFF,
};

typedef struct Descr Descr;
struct Descr
{
	u32int	status;
	u32int	addr;
};

typedef struct Ctlr Ctlr;
struct Ctlr
{
	u32int	*regs;
	u32int	intmask;

	struct {
		Block	*b[256];
		Descr	*d;
		Rendez;
	}	rx[1];

	struct {
		Block	*b[256];
		Descr	*d;
		Rendez;
	}	tx[1];

	struct {
		Rendez;
	}	free[1];

	struct {
		Mii;
		int done;
		Rendez;
	}	mii[1];

	int	attached;
	QLock;
};

#define rr(c, r)	((c)->regs[r])
#define wr(c, r, v)	((c)->regs[r] = (v))

static int
mdiodone(void *arg)
{
	Ctlr *ctlr = arg;
	return ctlr->mii->done || (rr(ctlr, ENET_EIR) & INT_MII) != 0;
}
static int
mdiowait(Ctlr *ctlr)
{
	int i;

	for(i = 0; i < 200; i++){
		tsleep(ctlr->mii, mdiodone, ctlr, 5);
		if(mdiodone(ctlr))
			return 0;
	}
	return -1;
}
static int
mdiow(Mii* mii, int phy, int addr, int data)
{
	Ctlr *ctlr = mii->ctlr;

	data &= 0xFFFF;

	wr(ctlr, ENET_EIR, INT_MII);
	ctlr->mii->done = 0;

	wr(ctlr, ENET_MMFR, MMFR_WR | MMFR_ST | MMFR_TA | phy<<MMFR_PA_SHIFT | addr<<MMFR_RA_SHIFT | data);
	if(mdiowait(ctlr) < 0)
		return -1;
	return data;
}
static int
mdior(Mii* mii, int phy, int addr)
{
	Ctlr *ctlr = mii->ctlr;

	wr(ctlr, ENET_EIR, INT_MII);
	ctlr->mii->done = 0;

	wr(ctlr, ENET_MMFR, MMFR_RD | MMFR_ST | MMFR_TA | phy<<MMFR_PA_SHIFT | addr<<MMFR_RA_SHIFT);
	if(mdiowait(ctlr) < 0)
		return -1;
	return rr(ctlr, ENET_MMFR) & 0xFFFF;
}

static void
interrupt(Ureg*, void *arg)
{
	Ether *edev = arg;
	Ctlr *ctlr = edev->ctlr;
	u32int e;

	e = rr(ctlr, ENET_EIR);
	if(e & INT_RXF) wakeup(ctlr->rx);
	if(e & INT_TXF) wakeup(ctlr->tx);
	if(e & INT_MII) {
		ctlr->mii->done = 1;
		wakeup(ctlr->mii);
	}
	wr(ctlr, ENET_EIR, e);
}

static void
shutdown(Ether *edev)
{
	Ctlr *ctlr = edev->ctlr;
	coherence();

	wr(ctlr, ENET_ECR, ECR_RESERVED | ECR_RESET);
	while(rr(ctlr, ENET_ECR) & ECR_RESET) delay(1);

	/* mask and clear interrupt events */
	wr(ctlr, ENET_EIMR, 0);
	wr(ctlr, ENET_EIR, ~0);
}

static int
tdfree(void *arg)
{
	Descr *d = arg;
	return (d->status & (TD_OWN|TD_R)) == 0;
}

static void
txproc(void *arg)
{
	Ether *edev = arg;
	Ctlr *ctlr = edev->ctlr;
	Block *b;
	Descr *d;
	uint i = 0;

	while(waserror())
		;

	for(;;){
		if((b = qbread(edev->oq, 100000)) == nil)
			break;

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

		ctlr->tx->b[i] = b;

		dmaflush(1, b->rp, BLEN(b));
		d->addr = PADDR(b->rp);
		coherence();
		if(i == nelem(ctlr->tx->b)-1){
			d->status = BLEN(b) | TD_OWN | TD_R | TD_L | TD_TC | TD_W;
			i = 0;
		} else {
			d->status = BLEN(b) | TD_OWN | TD_R | TD_L | TD_TC;
			i++;
		}
		wr(ctlr, ENET_TDAR, TDAR_ACTIVE);
	}
}

static int
tddone(void *arg)
{
	Descr *d = arg;
	return (d->status & (TD_OWN|TD_R)) == TD_OWN;
}

static void
frproc(void *arg)
{
	Ether *edev = arg;
	Ctlr *ctlr = edev->ctlr;
	Block *b;
	Descr *d;
	uint i = 0;

	while(waserror())
		;

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

		b = ctlr->tx->b[i];
		ctlr->tx->b[i] = nil;
		coherence();

		if(i == nelem(ctlr->tx->b)-1){
			d->status = TD_W;
			i = 0;
		} else {
			d->status = 0;
			i++;
		}

		wakeup(ctlr->free);
		freeb(b);
	}
}

static int
rdfull(void *arg)
{
	Descr *d = arg;
	return (d->status & RD_E) == 0;
}

static void
rxproc(void *arg)
{
	Ether *edev = arg;
	Ctlr *ctlr = edev->ctlr;
	Block *b;
	Descr *d;
	uint s, i = 0;

	while(waserror())
		;

	for(;;){
		d = &ctlr->rx->d[i];
		s = d->status;
		if(s & RD_E){
			sleep(ctlr->rx, rdfull, d);
			continue;
		}
		if(((s^RD_L) & (RD_L|RD_ERR)) == 0){
			b = ctlr->rx->b[i];
			b->wp = b->rp + (s & RD_LEN);
			dmaflush(0, b->rp, BLEN(b));
			etheriq(edev, b);

			/* replenish */
			b = allocb(R_BUF_SIZE);
			ctlr->rx->b[i] = b;
			dmaflush(1, b->rp, R_BUF_SIZE);
			d->addr = PADDR(b->rp); 
			coherence();
		}
		if(i == nelem(ctlr->rx->b)-1) {
			d->status = RD_E | RD_W;
			i = 0;
		} else {
			d->status = RD_E;
			i++;
		}
		wr(ctlr, ENET_RDAR, RDAR_ACTIVE);
	}
}

static void
linkproc(void *arg)
{
	Ether *edev = arg;
	Ctlr *ctlr = edev->ctlr;
	MiiPhy *phy;
	int link = 0;

	while(waserror())
		;
	miiane(ctlr->mii, ~0, ~0, ~0);
	for(;;){
		miistatus(ctlr->mii);
		phy = ctlr->mii->curphy;
		if(phy->link == link){
			tsleep(ctlr->mii, return0, nil, 5000);
			continue;
		}
		link = phy->link;
		if(link){
			u32int ecr = rr(ctlr, ENET_ECR) & ~ECR_SPEED_1000M;
			u32int rcr = rr(ctlr, ENET_RCR) & ~(RCR_RMII_10T|RCR_FCE);
			u32int tcr = rr(ctlr, ENET_TCR) & ~(TCR_RFC_PAUSE|TCR_TFC_PAUSE|TCR_FDEN);

			switch(phy->speed){
			case 1000:
				ecr |= ECR_SPEED_1000M;
				rcr |= RCR_FCE;

				/* receive fifo thresholds */
				wr(ctlr, ENET_RSFL, 16);
				wr(ctlr, ENET_RSEM, 132);
				wr(ctlr, ENET_RAEM, 8);
				wr(ctlr, ENET_RAFL, 8);

				/* opcode/pause duration */
				wr(ctlr, ENET_OPD, 0xFFF0);
				break;
			case 100:
				ecr |= ECR_SPEED_100M;
				break;
			case 10:
				rcr |= RCR_RMII_10T;
				break;
			}
			if(phy->fd)
				tcr |= TCR_FDEN;
			if(phy->rfc)
				tcr |= TCR_RFC_PAUSE;
			if(phy->tfc)
				tcr |= TCR_TFC_PAUSE;

			wr(ctlr, ENET_ECR, ecr);
			wr(ctlr, ENET_RCR, rcr);
			wr(ctlr, ENET_TCR, tcr);

			edev->mbps = phy->speed;

			wr(ctlr, ENET_RDAR, RDAR_ACTIVE);
		} 
		edev->link = link;
		print("#l%d: link %d speed %d\n", edev->ctlrno, edev->link, edev->mbps);
	}
}

static void
attach(Ether *edev)
{
	Ctlr *ctlr = edev->ctlr;
	Descr *d;
	int i;

	eqlock(ctlr);
	if(ctlr->attached){
		qunlock(ctlr);
		return;
	}
	if(waserror()){
		qunlock(ctlr);
		nexterror();
	}

	/* RGMII mode, max frame length */
	wr(ctlr, ENET_RCR, RCR_MII_MODE | RCR_RGMII_EN | Maxtu<<RCR_MAX_FL_SHIFT);

	/* set MII clock to 2.5Mhz, 10ns hold time */
	wr(ctlr, ENET_MSCR, ((Busclk/(2*2500000))-1)<<MSCR_SPEED_SHIFT | ((Busclk/1000000)-1)<<MSCR_HOLD_SHIFT);

	ctlr->intmask |= INT_MII;
	wr(ctlr, ENET_EIMR, ctlr->intmask);
	mii(ctlr->mii, ~0);

	if(ctlr->mii->curphy == nil)
		error("no phy");

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

	/* clear mac filter hash table */
	wr(ctlr, ENET_IALR, 0);
	wr(ctlr, ENET_IAUR, 0);
	wr(ctlr, ENET_GALR, 0);
	wr(ctlr, ENET_GAUR, 0);

	/* set MAC address */
	wr(ctlr, ENET_PALR, (u32int)edev->ea[0]<<24 | (u32int)edev->ea[1]<<16 | (u32int)edev->ea[2]<<8 | edev->ea[3]<<0);
	wr(ctlr, ENET_PAUR, (u32int)edev->ea[4]<<24 | (u32int)edev->ea[5]<<16);

	if(ctlr->rx->d == nil)
		ctlr->rx->d = ucalloc(sizeof(Descr) * nelem(ctlr->rx->b));
	for(i=0; i<nelem(ctlr->rx->b); i++){
		Block *b = allocb(R_BUF_SIZE);
		ctlr->rx->b[i] = b;
		d = &ctlr->rx->d[i];
		dmaflush(1, b->rp, R_BUF_SIZE);
		d->addr = PADDR(b->rp);
		d->status = RD_E;
	}
	ctlr->rx->d[nelem(ctlr->rx->b)-1].status = RD_E | RD_W;
	wr(ctlr, ENET_MRBR, R_BUF_SIZE);
	coherence();
	wr(ctlr, ENET_RDSR, PADDR(ctlr->rx->d));

	if(ctlr->tx->d == nil)
		ctlr->tx->d = ucalloc(sizeof(Descr) * nelem(ctlr->tx->b));
	for(i=0; i<nelem(ctlr->tx->b); i++){
		ctlr->tx->b[i] = nil;
		d = &ctlr->tx->d[i];
		d->addr = 0;
		d->status = 0;
	}
	ctlr->tx->d[nelem(ctlr->tx->b)-1].status = TD_W;
	coherence();
	wr(ctlr, ENET_TDSR, PADDR(ctlr->tx->d));

	/* store and forward tx fifo */
	wr(ctlr, ENET_TFWR, TFWR_STRFWD);

	/* interrupt coalescing: 200 pkts, 1000 µs */
	wr(ctlr, ENET_RXIC0, IC_EN | 200<<IC_FT_SHIFT | ((1000*Txclk)/64000000)<<IC_TT_SHIFT);
	wr(ctlr, ENET_TXIC0, IC_EN | 200<<IC_FT_SHIFT | ((1000*Txclk)/64000000)<<IC_TT_SHIFT);

	ctlr->intmask |= INT_TXF | INT_RXF;
	wr(ctlr, ENET_EIMR, ctlr->intmask);

	/* enable ethernet */
	wr(ctlr, ENET_ECR, rr(ctlr, ENET_ECR) | ECR_ETHEREN | ECR_DBSWP);

	ctlr->attached = 1;

	kproc("ether-rx", rxproc, edev);
	kproc("ether-tx", txproc, edev);
	kproc("ether-fr", frproc, edev);

	kproc("ether-link", linkproc, edev);

	qunlock(ctlr);
	poperror();
}

static void
prom(void *arg, int on)
{
	Ether *edev = arg;
	Ctlr *ctlr = edev->ctlr;

	if(on)
		wr(ctlr, ENET_RCR, rr(ctlr, ENET_RCR) | RCR_PROM);
	else
		wr(ctlr, ENET_RCR, rr(ctlr, ENET_RCR) & ~RCR_PROM);
}

static void
multi(void *arg, uchar*, int)
{
	Ether *edev = arg;
	Ctlr *ctlr = edev->ctlr;
	Netaddr *a;
	u64int hash;

	hash = 0;
	for(a = edev->maddr; a != nil; a = a->next)
		hash |= 1ULL << ((ethercrc(a->addr, edev->alen) >> (32 - 6)) & 0x3F);

	wr(ctlr, ENET_GALR, hash & 0xFFFFFFFF);
	wr(ctlr, ENET_GAUR, hash >> 32);
}

static long
ctl(Ether*, void*, long len)
{
	return len;
}

static int
reset(Ether *edev)
{
	enum {
		OCOTP_HW_OCOTP_MAC_ADDR0 = 0x640/4,
		OCOTP_HW_OCOTP_MAC_ADDR1 = 0x650/4,
	};
	static u32int *ocotp = (u32int*)(VIRTIO + 0x350000);
	u32int a0, a1;

	a0 = ocotp[OCOTP_HW_OCOTP_MAC_ADDR0];
	a1 = ocotp[OCOTP_HW_OCOTP_MAC_ADDR1];

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

	shutdown(edev);

	return 0;
}

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

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

	ctlr->regs = (u32int*)(VIRTIO + 0xbe0000);

	ctlr->mii->ctlr = ctlr;
	ctlr->mii->mir = mdior;
	ctlr->mii->miw = mdiow;

	edev->port = (uintptr)ctlr->regs - KZERO;
	edev->irq = IRQenet1;
	edev->ctlr = ctlr;
	edev->attach = attach;
	edev->shutdown = shutdown;
	edev->promiscuous = prom;
	edev->multicast = multi;
	edev->ctl = ctl;
	edev->arg = edev;
	edev->mbps = 1000;
	edev->maxmtu = Maxtu;

	iomuxpad("pad_enet_mdc", "enet1_mdc", "~LVTTL ~HYS ~PUE ~ODE SLOW 75_OHM");
	iomuxpad("pad_enet_mdio", "enet1_mdio", "~LVTTL ~HYS ~PUE ODE SLOW 75_OHM");

	iomuxpad("pad_enet_td3", "enet1_rgmii_td3", "~LVTTL ~HYS ~PUE ~ODE MAX 40_OHM");
	iomuxpad("pad_enet_td2", "enet1_rgmii_td2", "~LVTTL ~HYS ~PUE ~ODE MAX 40_OHM");
	iomuxpad("pad_enet_td1", "enet1_rgmii_td1", "~LVTTL ~HYS ~PUE ~ODE MAX 40_OHM");
	iomuxpad("pad_enet_td0", "enet1_rgmii_td0", "~LVTTL ~HYS ~PUE ~ODE MAX 40_OHM");
	iomuxpad("pad_enet_tx_ctl", "enet1_rgmii_tx_ctl",  "~LVTTL ~HYS ~PUE ~ODE MAX 40_OHM VSEL_0");
	iomuxpad("pad_enet_txc", "enet1_rgmii_txc",  "~LVTTL ~HYS ~PUE ~ODE MAX 40_OHM");

	iomuxpad("pad_enet_rxc", "enet1_rgmii_rxc", "~LVTTL HYS ~PUE ~ODE FAST 255_OHM");
	iomuxpad("pad_enet_rx_ctl", "enet1_rgmii_rx_ctl", "~LVTTL HYS ~PUE ~ODE FAST 255_OHM");
	iomuxpad("pad_enet_rd0", "enet1_rgmii_rd0", "~LVTTL HYS ~PUE ~ODE FAST 255_OHM");
	iomuxpad("pad_enet_rd1", "enet1_rgmii_rd1", "~LVTTL HYS ~PUE ~ODE FAST 255_OHM");
	iomuxpad("pad_enet_rd2", "enet1_rgmii_rd2", "~LVTTL HYS ~PUE ~ODE FAST 255_OHM");
	iomuxpad("pad_enet_rd3", "enet1_rgmii_rd3", "~LVTTL HYS ~PUE ~ODE FAST 255_OHM");

	setclkgate("enet1.ipp_ind_mac0_txclk", 0);
	setclkgate("sim_enet.mainclk", 0);

	setclkrate("enet1.ipg_clk", "system_pll1_div3", Busclk);
	setclkrate("enet1.ipp_ind_mac0_txclk", "system_pll2_div8", Txclk);
	setclkrate("enet1.ipg_clk_time", "system_pll2_div10", Ptpclk);

	setclkgate("enet1.ipp_ind_mac0_txclk", 1);
	setclkgate("sim_enet.mainclk", 1);

	if(reset(edev) < 0)
		return -1;

	intrenable(edev->irq+0, interrupt, edev, BUSUNKNOWN, edev->name);
	intrenable(edev->irq+1, interrupt, edev, BUSUNKNOWN, edev->name);
	intrenable(edev->irq+2, interrupt, edev, BUSUNKNOWN, edev->name);
	intrenable(edev->irq+3, interrupt, edev, BUSUNKNOWN, edev->name);

	return 0;
}

void
etherimxlink(void)
{
	addethercard("imx", pnp);
}