shithub: riscv

ref: fc0357b3decd0dc9848892a823d63070fde09a89
dir: /sys/src/9/omap/ether9221.c/

View raw version
/*
 * SMSC 9221 Ethernet driver
 * specifically for the ISEE IGEPv2 board,
 * where it is assigned to Chip Select 5,
 * its registers are at 0x2c000000 (inherited from u-boot),
 * and irq is 34 from gpio pin 176, thus gpio module 6.
 *
 * it's slow due to the use of fifos instead of buffer rings.
 * the slow system dma just makes it worse.
 *
 * igepv2 u-boot uses pin 64 on gpio 3 as an output pin to reset the 9221.
 */
#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"

/* currently using kprocs is a lot slower than not (87 s. to boot vs 60) */
#undef USE_KPROCS

enum {
	Vid9221	= 0x9221,
	Slop	= 4,			/* beyond ETHERMAXTU */
};

typedef struct Regs Regs;
struct Regs {
	/* fifo ports */
	ulong	rxdata;
	uchar	_pad0[0x20 - 4];
	ulong	txdata;
	uchar	_pad1[0x40 - 0x24];
	ulong	rxsts;
	ulong	rxstspeek;
	ulong	txsts;
	ulong	txstspeek;

	/* control & status */
	ushort	rev;			/* chip revision */
	ushort	id;			/* chip id, 0x9221 */
	ulong	irqcfg;
	ulong	intsts;
	ulong	inten;
	ulong	_pad2;
	ulong	bytetest;
	ulong	fifoint;		/* fifo level interrupts */
	ulong	rxcfg;
	ulong	txcfg;
	ulong	hwcfg;
	ulong	rxdpctl;		/* rx data path control */
	ulong	rxfifoinf;
	ulong	txfifoinf;
	ulong	pmtctl;			/* power mgmt. control */
	ulong	gpiocfg;
	ulong	gptcfg;			/* timer */
	ulong	gptcnt;
	ulong	_pad3;
	ulong	wordswap;
	ulong	freerun; 		/* counters */
	ulong	rxdrop;

	/*
	 * mac registers are accessed indirectly via the mac csr registers.
	 * phy registers are doubly indirect, via the mac csr mii_acc &
	 * mii_data mac csr registers.
	 */
	ulong	maccsrcmd;		/* mac csr synchronizer */
	ulong	maccsrdata;
	ulong	afccfg;			/* automatic flow control cfg. */
	ulong	eepcmd;			/* eeprom */
	ulong	eepdata;
	/* 0xb8 */
};

enum {
	Nstatistics	= 128,
};

enum {
	/* txcmda bits */
	Intcompl	= 1<<31,
	Bufendalign	= 3<<24,	/* mask */
	Datastoff	= 037<<16,	/* mask */
	Firstseg	= 1<<13,
	Lastseg		= 1<<12,
	Bufsize		= MASK(11),

	/* txcmdb bits */
	Pkttag		= MASK(16) << 16,
	Txcksumen	= 1<<14,
	Addcrcdis	= 1<<13,
	Framepaddis	= 1<<12,
	Pktlen		= (1<<1) - 1,	/* mask */

	/* txcfg bits */
	Txsdump		= 1<<15,	/* flush tx status fifo */
	Txddump		= 1<<14,	/* flush tx data fifo */
	Txon		= 1<<1,
	Stoptx		= 1<<0,

	/* hwcfg bits */
	Mbo		= 1<<20,	/* must be one */
	Srstto		= 1<<1,		/* soft reset time-out */
	Srst		= 1<<0,

	/* rxcfg bits */
	Rxdmacntshift	= 16,		/* ulong count, 12 bits wide */
	Rxdmacntmask	= MASK(12) << Rxdmacntshift,
	Rxdump		= 1<<15,	/* flush rx fifos */

	/* rxsts bits */
	Rxpktlenshift	= 16,		/* byte count */
	Rxpktlenmask	= MASK(14) << Rxpktlenshift,
	Rxerr		= 1<<15,

	/* rxfifoinf bits */
	Rxstsusedshift	= 16,		/* ulong count */
	Rxstsusedmask	= MASK(8) << Rxstsusedshift,
	Rxdatausedmask	= MASK(16),	/* byte count */

	/* txfifoinf bits */
	Txstsusedshift	= 16,		/* ulong count */
	Txstsusedmask	= MASK(8) << Txstsusedshift,
	Txdatafreemask	= MASK(16),	/* byte count */

	/* pmtctl bits */
	Dready		= 1<<0,

	/* maccsrcmd bits */
	Csrbusy		= 1<<31,
	Csrread		= 1<<30,	/* not write */
	Csraddrshift	= 0,
	Csraddrmask	= MASK(8) - 1,

	/* mac registers' indices */
	Maccr		= 1,
	Macaddrh,
	Macaddrl,
	Machashh,
	Machashl,
	Macmiiacc,			/* for doubly-indirect phy access */
	Macmiidata,
	Macflow,
	Macvlan1,
	Macvlan2,
	Macwuff,
	Macwucsr,
	Maccoe,

	/* Maccr bits */
	Rxall		= 1<<31,
	Rcvown		= 1<<23,	/* don't receive own transmissions */
	Fdpx		= 1<<20,	/* full duplex */
	Mcpas		= 1<<19,	/* pass all multicast */
	Prms		= 1<<18,	/* promiscuous */
	Ho		= 1<<15,	/* hash-only filtering */
	Hpfilt		= 1<<13,	/* hash/perfect filtering */
	Padstr		= 1<<8,		/* strip padding & fcs (crc) */
	Txen		= 1<<3,
	Rxen		= 1<<2,

	/* irqcfg bits */
	Irqdeasclr	= 1<<14,	/* deassertion intv'l clear */
	Irqdeassts	= 1<<13,	/* deassertion intv'l status */
	Irqint		= 1<<12,	/* intr being asserted? (ro) */
	Irqen		= 1<<8,
	Irqpol		= 1<<4,		/* irq output is active high */
	Irqpushpull	= 1<<0,		/* irq output is push/pull driver */

	/* intsts/inten bits */
	Swint		= 1<<31,	/* generate an interrupt */
	Txstop		= 1<<25,
	Rxstop		= 1<<24,
	Txioc		= 1<<21,
	Rxdma		= 1<<20,
	Gptimer		= 1<<19,
	Phy		= 1<<18,
	Rxe		= 1<<14,	/* errors */
	Txe		= 1<<13,
	Tdfo		= 1<<10,	/* tx data fifo overrun */
	Tdfa		= 1<<9,		/* tx data fifo available */
	Tsff		= 1<<8,		/* tx status fifo full */
	Tsfl		= 1<<7,		/* tx status fifo level */
	Rsff		= 1<<4,		/* rx status fifo full */
	Rsfl		= 1<<3,		/* rx status fifo level */

	/* eepcmd bits */
	Epcbusy		= 1<<31,
	Epccmdshift	= 28,		/* interesting one is Reload (7) */
	Epctimeout	= 1<<9,
	Epcmacloaded	= 1<<8,
	Epcaddrshift	= 0,
};

enum {
	Rxintrs		= Rsff | Rsfl | Rxe,
	Txintrs		= Tsff | Tsfl | Txe | Txioc,
};

/* wake-up frame filter */
struct Wakeup {
	ulong	bytemask[4];		/* index is filter # */
	uchar	filt0cmd;		/* filter 0 command */
	uchar	_pad0;
	uchar	filt1cmd;
	uchar	_pad1;
	uchar	filt2cmd;
	uchar	_pad2;
	uchar	filt3cmd;
	uchar	_pad3;
	uchar	offset[4];		/* index is filter # */
	ushort	crc16[4];		/* " */
};

typedef struct Ctlr Ctlr;
struct Ctlr {
	int	port;
	Ctlr*	next;
	Ether*	edev;
	Regs*	regs;
	int	active;
	int	started;
	int	inited;
	int	id;
	int	cls;
	ushort	eeprom[0x40];

	QLock	alock;			/* attach */
	int	nrb;			/* how many this Ctlr has in the pool */

	int*	nic;
	Lock	imlock;
	int	im;			/* interrupt mask */

//	Mii*	mii;
//	Rendez	lrendez;
	int	lim;

	int	link;

	QLock	slock;
	uint	statistics[Nstatistics];
	uint	lsleep;
	uint	lintr;
	uint	rsleep;
	uint	rintr;
	int	tsleep;
	uint	tintr;

	uchar	ra[Eaddrlen];		/* receive address */
	ulong	mta[128];		/* multicast table array */

	Rendez	rrendez;
	int	gotinput;
	int	rdcpydone;

	Rendez	trendez;
	int	gotoutput;
	int	wrcpydone;

	Lock	tlock;
};

#define csr32r(c, r)	(*((c)->nic+((r)/4)))
#define csr32w(c, r, v)	(*((c)->nic+((r)/4)) = (v))

static Ctlr *smcctlrhead, *smcctlrtail;

static char* statistics[Nstatistics] = { "dummy", };

static uchar mymac[] = { 0xb0, 0x0f, 0xba, 0xbe, 0x00, 0x00, };

static void etherclock(void);
static void smcreceive(Ether *edev);
static void smcinterrupt(Ureg*, void* arg);

static Ether *thisether;
static int attached;

static void
smconce(Ether *edev)
{
	static int beenhere;
	static Lock l;

	ilock(&l);
	if (!beenhere && edev != nil) {
		beenhere = 1;
		/* simulate interrupts if we don't know the irq */
		if (edev->irq < 0) {		/* poll as backup */
			thisether = edev;
			addclock0link(etherclock, 1000/HZ);
			iprint(" polling");
		}
	}
	iunlock(&l);
}

/*
 * indirect (mac) register access
 */

static void
macwait(Regs *regs)
{
	long bound;

	for (bound = 400*Mhz; regs->maccsrcmd & Csrbusy && bound > 0; bound--)
		;
	if (bound <= 0)
		iprint("smc: mac registers didn't come ready\n");
}

static ulong
macrd(Regs *regs, uchar index)
{
	macwait(regs);
	regs->maccsrcmd = Csrbusy | Csrread | index;
	coherence();		/* back-to-back write/read delay per §6.2.1 */
	macwait(regs);
	return regs->maccsrdata;
}

static void
macwr(Regs *regs, uchar index, ulong val)
{
	macwait(regs);
	regs->maccsrdata = val;
	regs->maccsrcmd = Csrbusy | index;	/* fire */
	macwait(regs);
}


static long
smcifstat(Ether* edev, void* a, long n, ulong offset)
{
	Ctlr *ctlr;
	char *p, *s;
	int i, l, r;

	ctlr = edev->ctlr;
	qlock(&ctlr->slock);
	p = malloc(READSTR);
	l = 0;
	for(i = 0; i < Nstatistics; i++){
		// read regs->rxdrop TODO
		r = 0;
		if((s = statistics[i]) == nil)
			continue;
		switch(i){
		default:
			ctlr->statistics[i] += r;
			if(ctlr->statistics[i] == 0)
				continue;
			l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
				s, ctlr->statistics[i], r);
			break;
		}
	}

	l += snprint(p+l, READSTR-l, "lintr: %ud %ud\n",
		ctlr->lintr, ctlr->lsleep);
	l += snprint(p+l, READSTR-l, "rintr: %ud %ud\n",
		ctlr->rintr, ctlr->rsleep);
	l += snprint(p+l, READSTR-l, "tintr: %ud %ud\n",
		ctlr->tintr, ctlr->tsleep);

	l += snprint(p+l, READSTR-l, "eeprom:");
	for(i = 0; i < 0x40; i++){
		if(i && ((i & 0x07) == 0))
			l += snprint(p+l, READSTR-l, "\n       ");
		l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->eeprom[i]);
	}
	l += snprint(p+l, READSTR-l, "\n");
	USED(l);

	n = readstr(offset, a, n, p);
	free(p);
	qunlock(&ctlr->slock);

	return n;
}

static void
smcpromiscuous(void* arg, int on)
{
	int rctl;
	Ctlr *ctlr;
	Ether *edev;
	Regs *regs;

	edev = arg;
	ctlr = edev->ctlr;
	regs = ctlr->regs;
	rctl = macrd(regs, Maccr);
	if(on)
		rctl |= Prms;
	else
		rctl &= ~Prms;
	macwr(regs, Maccr, rctl);
}

static void
smcmulticast(void*, uchar*, int)
{
	/* nothing to do, we allow all multicast packets in */
}

static int
iswrcpydone(void *arg)
{
	return ((Ctlr *)arg)->wrcpydone;
}

static int
smctxstart(Ctlr *ctlr, uchar *ubuf, uint len)
{
	uint wds, ruplen;
	ulong *wdp, *txdp;
	Regs *regs;
	static ulong buf[ROUNDUP(ETHERMAXTU, sizeof(ulong)) / sizeof(ulong)];

	if (!ctlr->inited) {
		iprint("smctxstart: too soon to send\n");
		return -1;		/* toss it */
	}
	regs = ctlr->regs;

	/* is there room for a packet in the tx data fifo? */
	if (len < ETHERMINTU)
		iprint("sending too-short (%d) pkt\n", len);
	else if (len > ETHERMAXTU)
		iprint("sending jumbo (%d) pkt\n", len);

	ruplen = ROUNDUP(len, sizeof(ulong));
	coherence();	/* back-to-back read/read delay per §6.2.2 */
	if ((regs->txfifoinf & Txdatafreemask) < ruplen + 2*sizeof(ulong))
		return -1;	/* not enough room for data + command words */

	if ((uintptr)ubuf & MASK(2)) {		/* ensure word alignment */
		memmove(buf, ubuf, len);
		ubuf = (uchar *)buf;
	}

	/* tx cmd a: length is bytes in this buffer */
	txdp = &regs->txdata;
	*txdp = Intcompl | Firstseg | Lastseg | len;
	/* tx cmd b: length is bytes in this packet (could be multiple buf.s) */
	*txdp = len;

	/* shovel pkt into tx fifo, which triggers transmission due to Txon */
	wdp = (ulong *)ubuf;
	for (wds = ruplen / sizeof(ulong) + 1; --wds > 0; )
		*txdp = *wdp++;

	regs->intsts = Txintrs;		/* dismiss intr */
	coherence();
	regs->inten |= Txintrs;
	coherence();		/* back-to-back write/read delay per §6.2.1 */
	return 0;
}

static void
smctransmit(Ether* edev)
{
	Block *bp;
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	if (ctlr == nil)
		panic("smctransmit: nil ctlr");
	ilock(&ctlr->tlock);
	/*
	 * Try to fill the chip's buffers back up, via the tx fifo.
	 */
	while ((bp = qget(edev->oq)) != nil)
		if (smctxstart(ctlr, bp->rp, BLEN(bp)) < 0) {
			qputback(edev->oq, bp);	/* retry the block later */
			iprint("smctransmit: tx data fifo full\n");
			break;
		} else
			freeb(bp);
	iunlock(&ctlr->tlock);
}

static void
smctransmitcall(Ether *edev)		/* called from devether.c */
{
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	ctlr->gotoutput = 1;
#ifdef USE_KPROCS
	wakeup(&ctlr->trendez);
#else
	smctransmit(edev);
#endif
}

static int
smcrim(void* ctlr)
{
	return ((Ctlr*)ctlr)->gotinput;
}

static void
smcrproc(void* arg)
{
	Ctlr *ctlr;
	Ether *edev;

	edev = arg;
	ctlr = edev->ctlr;
	for(;;){
		ctlr->rsleep++;
		sleep(&ctlr->rrendez, smcrim, ctlr);

		/* process any newly-arrived packets and pass to etheriq */
		ctlr->gotinput = 0;
		smcreceive(edev);
	}
}

static int
smcgotout(void* ctlr)
{
	return ((Ctlr*)ctlr)->gotoutput;
}

static void
smctproc(void* arg)
{
	Ctlr *ctlr;
	Ether *edev;

	edev = arg;
	ctlr = edev->ctlr;
	for(;;){
		ctlr->tsleep++;
		sleep(&ctlr->trendez, smcgotout, ctlr);

		/* process any newly-arrived packets and pass to etheriq */
		ctlr->gotoutput = 0;
		smctransmit(edev);
	}
}

void	gpioirqclr(void);

static void
smcattach(Ether* edev)
{
#ifdef USE_KPROCS
	char name[KNAMELEN];
#endif
	Ctlr *ctlr;

	ctlr = edev->ctlr;
	qlock(&ctlr->alock);
	if(waserror()){
		qunlock(&ctlr->alock);
		nexterror();
	}
	if (!ctlr->inited) {
		ctlr->inited = 1;
#ifdef USE_KPROCS
		snprint(name, KNAMELEN, "#l%drproc", edev->ctlrno);
		kproc(name, smcrproc, edev);

		snprint(name, KNAMELEN, "#l%dtproc", edev->ctlrno);
		kproc(name, smctproc, edev);
#endif

iprint("smcattach:");
#ifdef USE_KPROCS
iprint(" with kprocs");
#else
iprint(" no kprocs");
#endif
iprint(", no dma");
		/* can now accept real or simulated interrupts */

		smconce(edev);
		attached = 1;
iprint("\n");
	}
	qunlock(&ctlr->alock);
	poperror();
}

static int
isrdcpydone(void *arg)
{
	return ((Ctlr *)arg)->rdcpydone;
}

static void
smcreceive(Ether *edev)
{
	uint wds, len, sts;
	ulong *wdp, *rxdp;
	Block *bp;
	Ctlr *ctlr;
	Regs *regs;

	ctlr = edev->ctlr;
	regs = ctlr->regs;
	coherence();		/* back-to-back read/read delay per §6.2.2 */
	/*
	 * is there a full packet in the rx data fifo?
	 */
	while (((regs->rxfifoinf & Rxstsusedmask) >> Rxstsusedshift) != 0) {
		coherence();
		sts = regs->rxsts;		/* pop rx status */
		if(sts & Rxerr)
			iprint("smcreceive: rx error\n");
		len = (sts & Rxpktlenmask) >> Rxpktlenshift;
		if (len > ETHERMAXTU + Slop)
			iprint("smcreceive: oversized rx pkt (%d)\n", len);
		else if (len < ETHERMINTU)
			iprint("smcreceive: too-short (%d) pkt\n", len);
		wds = ROUNDUP(len, sizeof(ulong)) / sizeof(ulong);
		if (wds > 0) {
			/* copy aligned words from rx fifo into a Block */
			bp = iallocb(len + sizeof(ulong) /* - 1 */);
			if (bp == nil)
				panic("smcreceive: nil Block*");

			/* bp->rp should be 32-byte aligned, more than we need */
			assert(((uintptr)bp->rp & (sizeof(ulong) - 1)) == 0);
			wdp = (ulong *)bp->rp;
			rxdp = &regs->rxdata;
			wds = ROUNDUP(len, sizeof(ulong)) / sizeof(ulong) + 1;
			while (--wds > 0)
				*wdp++ = *rxdp;
			bp->wp = bp->rp + len;

			/* and push the Block upstream */
			if (ctlr->inited)
				etheriq(edev, bp);
			else
				freeb(bp);

			regs->intsts = Rxintrs;		/* dismiss intr */
			coherence();
			regs->inten |= Rxintrs;
		}
		coherence();
	}
	regs->inten |= Rxintrs;
	coherence();
}

/*
 * disable the stsclr bits in inten and write them to intsts to ack and dismiss
 * the interrupt source.
 */
void
ackintr(Regs *regs, ulong stsclr)
{
	if (stsclr == 0)
		return;

	regs->inten &= ~stsclr;
	coherence();

//	regs->intsts = stsclr;		/* acknowledge & clear intr(s) */
//	coherence();
}

static void
smcinterrupt(Ureg*, void* arg)
{
	int junk;
	unsigned intsts, intr;
	Ctlr *ctlr;
	Ether *edev;
	Regs *regs;

	edev = arg;
	ctlr = edev->ctlr;
	ilock(&ctlr->imlock);
	regs = ctlr->regs;

	gpioirqclr();

	coherence();		/* back-to-back read/read delay per §6.2.2 */
	intsts = regs->intsts;
	coherence();

	intsts &= ~MASK(3);		/* ignore gpio bits */
	if (0 && intsts == 0) {
		coherence();
		iprint("smc: interrupt without a cause; insts %#ux (vs inten %#lux)\n",
			intsts, regs->inten);
	}

	intr = intsts & Rxintrs;
	if(intr) {
		/* disable interrupt sources; kproc/smcreceive will reenable */
		ackintr(regs, intr);

		ctlr->rintr++;
		ctlr->gotinput = 1;
#ifdef USE_KPROCS
		wakeup(&ctlr->rrendez);
#else
		smcreceive(edev);
#endif
	}

	while(((regs->txfifoinf & Txstsusedmask) >> Txstsusedshift) != 0) {
		/* probably indicates tx completion, just toss it */
		junk = regs->txsts;		/* pop tx sts */
		USED(junk);
		coherence();
	}

	intr = intsts & Txintrs;
	if (ctlr->gotoutput || intr) {
		/* disable interrupt sources; kproc/smctransmit will reenable */
		ackintr(regs, intr);

		ctlr->tintr++;
		ctlr->gotoutput = 1;
#ifdef USE_KPROCS
		wakeup(&ctlr->trendez);
#else
		smctransmit(edev);
#endif
	}

	iunlock(&ctlr->imlock);
}

static void
etherclock(void)
{
	smcinterrupt(nil, thisether);
}

static int
smcmii(Ctlr *)
{
	return 0;
}

static int
smcdetach(Ctlr* ctlr)
{
	Regs *regs;

	if (ctlr == nil || ctlr->regs == nil)
		return -1;
	regs = ctlr->regs;
	/* verify that it's real by reading a few registers */
	switch (regs->id) {
	case Vid9221:
		break;
	default:
		print("smc: unknown chip id %#ux\n", regs->id);
		return -1;
	}
	regs->inten = 0;		/* no interrupts */
	regs->intsts = ~0;		/* clear any pending */
	regs->gptcfg = 0;
	coherence();
	regs->rxcfg = Rxdump;
	regs->txcfg = Txsdump | Txddump;
	regs->irqcfg &= ~Irqen;
	coherence();
	return 0;
}

static void
smcshutdown(Ether* ether)
{
	smcdetach(ether->ctlr);
}

static void
powerwait(Regs *regs)
{
	long bound;

	regs->bytetest = 0;			/* bring power on */
	for (bound = 400*Mhz; !(regs->pmtctl & Dready) && bound > 0; bound--)
		;
	if (bound <= 0)
		iprint("smc: pmtctl didn't come ready\n");
}

static int
smcreset(Ctlr* ctlr)
{
	int r;
	Regs *regs;
	static char zea[Eaddrlen];

	regs = ctlr->regs;
	powerwait(regs);

	if(smcdetach(ctlr))
		return -1;

	/* verify that it's real by reading a few registers */
	switch (regs->id) {
	case Vid9221:
		break;
	default:
		print("smc: unknown chip id %#ux\n", regs->id);
		return -1;
	}
	if (regs->bytetest != 0x87654321) {
		print("smc: bytetest reg %#p (%#lux) != 0x87654321\n",
			&regs->bytetest, regs->bytetest);
		return -1;
	}

#ifdef TODO			/* read MAC from EEPROM */
//	int ctrl, i, pause, swdpio, txcw;
	/*
	 * Snarf and set up the receive addresses.
	 * There are 16 addresses. The first should be the MAC address.
	 * The others are cleared and not marked valid (MS bit of Rah).
	 */
	for(i = Ea; i < Eaddrlen/2; i++){
		ctlr->ra[2*i] = ctlr->eeprom[i];
		ctlr->ra[2*i+1] = ctlr->eeprom[i]>>8;
	}

	/*
	 * Clear the Multicast Table Array.
	 * It's a 4096 bit vector accessed as 128 32-bit registers.
	 */
	memset(ctlr->mta, 0, sizeof(ctlr->mta));
	for(i = 0; i < 128; i++)
		csr32w(ctlr, Mta+i*4, 0);
#endif
	regs->hwcfg |= Mbo;

	/* don't overwrite existing ea */
//	if (memcmp(edev->ea, zea, Eaddrlen) == 0)
//		memmove(edev->ea, ctlr->ra, Eaddrlen);

	r = ctlr->ra[3]<<24 | ctlr->ra[2]<<16 | ctlr->ra[1]<<8 | ctlr->ra[0];
	macwr(regs, Macaddrl, r);
	macwr(regs, Macaddrh, ctlr->ra[5]<<8 | ctlr->ra[4]);

	/* turn on the controller */
	macwr(regs, Maccoe, 0);
	regs->inten = 0;		/* no interrupts yet */
	regs->intsts = ~0;		/* clear any pending */
	regs->gptcfg = 0;
	coherence();
	regs->rxcfg = Rxdump;
	regs->txcfg = Txsdump | Txddump | Txon;
	regs->fifoint = 72<<24;		/* default values */
	macwr(regs, Maccr, Rxall | Rcvown | Fdpx | Mcpas | Txen | Rxen);
	coherence();		/* back-to-back write/read delay per §6.2.1 */
	regs->irqcfg = 1<<24 | Irqen | Irqpushpull;  /* deas for 10µs (linux) */
	coherence();		/* back-to-back write/read delay per §6.2.1 */
	regs->inten = Rxintrs | Txintrs;
	coherence();

	if(smcmii(ctlr) < 0)
		return -1;
	return 0;
}

static void
smcpci(void)
{
	Ctlr *ctlr;
	static int beenhere;

	if (beenhere)
		return;
	beenhere = 1;

	if (probeaddr(PHYSETHER) < 0)
		return;
	ctlr = malloc(sizeof(Ctlr));
	ctlr->id = Vid9221<<16 | 0x0424;	/* smsc 9221 */
	ctlr->port = PHYSETHER;
	ctlr->nic = (int *)PHYSETHER;
	ctlr->regs = (Regs *)PHYSETHER;

	if(smcreset(ctlr)){
		free(ctlr);
		return;
	}
	if(smcctlrhead != nil)
		smcctlrtail->next = ctlr;
	else
		smcctlrhead = ctlr;
	smcctlrtail = ctlr;
}

static int
smcpnp(Ether* edev)
{
	Ctlr *ctlr;
	static char zea[Eaddrlen];

	if(smcctlrhead == nil)
		smcpci();

	/*
	 * Any adapter matches if no edev->port is supplied,
	 * otherwise the ports must match.
	 */
	for(ctlr = smcctlrhead; ctlr != nil; ctlr = ctlr->next){
		if(ctlr->active)
			continue;
		if(edev->port == 0 || edev->port == ctlr->port){
			ctlr->active = 1;
			break;
		}
	}
	if(ctlr == nil)
		return -1;

	edev->ctlr = ctlr;
	ctlr->edev = edev;			/* point back to Ether* */
	edev->port = ctlr->port;
	edev->irq = 34;
// TODO: verify speed (100Mb/s) and duplicity (full-duplex)
	edev->mbps = 100;

	/* don't overwrite existing ea */
	if (memcmp(edev->ea, zea, Eaddrlen) == 0)
		memmove(edev->ea, ctlr->ra, Eaddrlen);

	/*
	 * Linkage to the generic ethernet driver.
	 */
	edev->attach = smcattach;
	edev->transmit = smctransmitcall;
	edev->ifstat = smcifstat;
/*	edev->ctl = smcctl;			/* no ctl msgs supported */

	edev->arg = edev;
	edev->promiscuous = smcpromiscuous;
	edev->multicast = smcmulticast;
	edev->shutdown = smcshutdown;

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

	return 0;
}

void
ether9221link(void)
{
	addethercard("9221", smcpnp);
}