shithub: riscv

ref: 61cc0694649a4d4b109d168032f431751f57f083
dir: /sys/src/9/pc/etherdp83820.c/

View raw version
/*
 * National Semiconductor DP83820
 * 10/100/1000 Mb/s Ethernet Network Interface Controller
 * (Gig-NIC).
 * Driver assumes little-endian and 32-bit host throughout.
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/pci.h"
#include "../port/error.h"
#include "../port/netif.h"
#include "../port/etherif.h"
#include "../port/ethermii.h"

enum {					/* Registers */
	Cr		= 0x00,		/* Command */
	Cfg		= 0x04,		/* Configuration and Media Status */
	Mear		= 0x08,		/* MII/EEPROM Access */
	Ptscr		= 0x0C,		/* PCI Test Control */
	Isr		= 0x10,		/* Interrupt Status */
	Imr		= 0x14,		/* Interrupt Mask */
	Ier		= 0x18,		/* Interrupt Enable */
	Ihr		= 0x1C,		/* Interrupt Holdoff */
	Txdp		= 0x20,		/* Transmit Descriptor Pointer */
	Txdphi		= 0x24,		/* Transmit Descriptor Pointer Hi */
	Txcfg		= 0x28,		/* Transmit Configuration */
	Gpior		= 0x2C,		/* General Purpose I/O Control */
	Rxdp		= 0x30,		/* Receive Descriptor Pointer */
	Rxdphi		= 0x34,		/* Receive Descriptor Pointer Hi */
	Rxcfg		= 0x38,		/* Receive Configuration */
	Pqcr		= 0x3C,		/* Priority Queueing Control */
	Wcsr		= 0x40,		/* Wake on LAN Control/Status */
	Pcr		= 0x44,		/* Pause Control/Status */
	Rfcr		= 0x48,		/* Receive Filter/Match Control */
	Rfdr		= 0x4C,		/* Receive Filter/Match Data */
	Brar		= 0x50,		/* Boot ROM Address */
	Brdr		= 0x54,		/* Boot ROM Data */
	Srr		= 0x58,		/* Silicon Revision */
	Mibc		= 0x5C,		/* MIB Control */
	Mibd		= 0x60,		/* MIB Data */
	Txdp1		= 0xA0,		/* Txdp Priority 1 */
	Txdp2		= 0xA4,		/* Txdp Priority 2 */
	Txdp3		= 0xA8,		/* Txdp Priority 3 */
	Rxdp1		= 0xB0,		/* Rxdp Priority 1 */
	Rxdp2		= 0xB4,		/* Rxdp Priority 2 */
	Rxdp3		= 0xB8,		/* Rxdp Priority 3 */
	Vrcr		= 0xBC,		/* VLAN/IP Receive Control */
	Vtcr		= 0xC0,		/* VLAN/IP Transmit Control */
	Vdr		= 0xC4,		/* VLAN Data */
	Ccsr		= 0xCC,		/* Clockrun Control/Status */
	Tbicr		= 0xE0,		/* TBI Control */
	Tbisr		= 0xE4,		/* TBI Status */
	Tanar		= 0xE8,		/* TBI ANAR */
	Tanlpar		= 0xEC,		/* TBI ANLPAR */
	Taner		= 0xF0,		/* TBI ANER */
	Tesr		= 0xF4,		/* TBI ESR */
};

enum {					/* Cr */
	Txe		= 0x00000001,	/* Transmit Enable */
	Txd		= 0x00000002,	/* Transmit Disable */
	Rxe		= 0x00000004,	/* Receiver Enable */
	Rxd		= 0x00000008,	/* Receiver Disable */
	Txr		= 0x00000010,	/* Transmitter Reset */
	Rxr		= 0x00000020,	/* Receiver Reset */
	Swien		= 0x00000080,	/* Software Interrupt Enable */
	Rst		= 0x00000100,	/* Reset */
	TxpriSHFT	= 9,		/* Tx Priority Queue Select */
	TxpriMASK	= 0x00001E00,
	RxpriSHFT	= 13,		/* Rx Priority Queue Select */
	RxpriMASK	= 0x0001E000,
};

enum {					/* Configuration and Media Status */
	Bem		= 0x00000001,	/* Big Endian Mode */
	Ext125		= 0x00000002,	/* External 125MHz reference Select */
	Bromdis		= 0x00000004,	/* Disable Boot ROM interface */
	Pesel		= 0x00000008,	/* Parity Error Detection Action */
	Exd		= 0x00000010,	/* Excessive Deferral Abort */
	Pow		= 0x00000020,	/* Program Out of Window Timer */
	Sb		= 0x00000040,	/* Single Back-off */
	Reqalg		= 0x00000080,	/* PCI Bus Request Algorithm */
	Extstsen	= 0x00000100,	/* Extended Status Enable */
	Phydis		= 0x00000200,	/* Disable PHY */
	Phyrst		= 0x00000400,	/* Reset PHY */
	M64addren	= 0x00000800,	/* Master 64-bit Addressing Enable */
	Data64en	= 0x00001000,	/* 64-bit Data Enable */
	Pci64det	= 0x00002000,	/* PCI 64-bit Bus Detected */
	T64addren	= 0x00004000,	/* Target 64-bit Addressing Enable */
	Mwidis		= 0x00008000,	/* MWI Disable */
	Mrmdis		= 0x00010000,	/* MRM Disable */
	Tmrtest		= 0x00020000,	/* Timer Test Mode */
	Spdstsien	= 0x00040000,	/* PHY Spdsts Interrupt Enable */
	Lnkstsien	= 0x00080000,	/* PHY Lnksts Interrupt Enable */
	Dupstsien	= 0x00100000,	/* PHY Dupsts Interrupt Enable */
	Mode1000	= 0x00400000,	/* 1000Mb/s Mode Control */
	Tbien		= 0x01000000,	/* Ten-Bit Interface Enable */
	Dupsts		= 0x10000000,	/* Full Duplex Status */
	Spdsts100	= 0x20000000,	/* SPEED100 Input Pin Status */
	Spdsts1000	= 0x40000000,	/* SPEED1000 Input Pin Status */
	Lnksts		= 0x80000000,	/* Link Status */
};

enum {					/* MII/EEPROM Access */
	Eedi		= 0x00000001,	/* EEPROM Data In */
	Eedo		= 0x00000002,	/* EEPROM Data Out */
	Eeclk		= 0x00000004,	/* EEPROM Serial Clock */
	Eesel		= 0x00000008,	/* EEPROM Chip Select */
	Mdio		= 0x00000010,	/* MII Management Data */
	Mddir		= 0x00000020,	/* MII Management Direction */
	Mdc		= 0x00000040,	/* MII Management Clock */
};

enum {					/* Interrupts */
	Rxok		= 0x00000001,	/* Rx OK */
	Rxdesc		= 0x00000002,	/* Rx Descriptor */
	Rxerr		= 0x00000004,	/* Rx Packet Error */
	Rxearly		= 0x00000008,	/* Rx Early Threshold */
	Rxidle		= 0x00000010,	/* Rx Idle */
	Rxorn		= 0x00000020,	/* Rx Overrun */
	Txok		= 0x00000040,	/* Tx Packet OK */
	Txdesc		= 0x00000080,	/* Tx Descriptor */
	Txerr		= 0x00000100,	/* Tx Packet Error */
	Txidle		= 0x00000200,	/* Tx Idle */
	Txurn		= 0x00000400,	/* Tx Underrun */
	Mib		= 0x00000800,	/* MIB Service */
	Swi		= 0x00001000,	/* Software Interrupt */
	Pme		= 0x00002000,	/* Power Management Event */
	Phy		= 0x00004000,	/* PHY Interrupt */
	Hibint		= 0x00008000,	/* High Bits Interrupt Set */
	Rxsovr		= 0x00010000,	/* Rx Status FIFO Overrun */
	Rtabt		= 0x00020000,	/* Received Target Abort */
	Rmabt		= 0x00040000,	/* Received Master Abort */
	Sserr		= 0x00080000,	/* Signalled System Error */
	Dperr		= 0x00100000,	/* Detected Parity Error */
	Rxrcmp		= 0x00200000,	/* Receive Reset Complete */
	Txrcmp		= 0x00400000,	/* Transmit Reset Complete */
	Rxdesc0		= 0x00800000,	/* Rx Descriptor for Priority Queue 0 */
	Rxdesc1		= 0x01000000,	/* Rx Descriptor for Priority Queue 1 */
	Rxdesc2		= 0x02000000,	/* Rx Descriptor for Priority Queue 2 */
	Rxdesc3		= 0x04000000,	/* Rx Descriptor for Priority Queue 3 */
	Txdesc0		= 0x08000000,	/* Tx Descriptor for Priority Queue 0 */
	Txdesc1		= 0x10000000,	/* Tx Descriptor for Priority Queue 1 */
	Txdesc2		= 0x20000000,	/* Tx Descriptor for Priority Queue 2 */
	Txdesc3		= 0x40000000,	/* Tx Descriptor for Priority Queue 3 */
};

enum {					/* Interrupt Enable */
	Ien		= 0x00000001,	/* Interrupt Enable */
};

enum {					/* Interrupt Holdoff */
	IhSHFT		= 0,		/* Interrupt Holdoff */
	IhMASK		= 0x000000FF,
	Ihctl		= 0x00000100,	/* Interrupt Holdoff Control */
};

enum {					/* Transmit Configuration */
	TxdrthSHFT	= 0,		/* Tx Drain Threshold */
	TxdrthMASK	= 0x000000FF,
	FlthSHFT	= 16,		/* Tx Fill Threshold */
	FlthMASK	= 0x0000FF00,
	Brstdis		= 0x00080000,	/* 1000Mb/s Burst Disable */
	MxdmaSHFT	= 20,		/* Max Size per Tx DMA Burst */
	MxdmaMASK	= 0x00700000,
	Ecretryen	= 0x00800000,	/* Excessive Collision Retry Enable */
	Atp		= 0x10000000,	/* Automatic Transmit Padding */
	Mlb		= 0x20000000,	/* MAC Loopback */
	Hbi		= 0x40000000,	/* Heartbeat Ignore */
	Csi		= 0x80000000,	/* Carrier Sense Ignore */
};

enum {					/* Receive Configuration */
	RxdrthSHFT	= 1,		/* Rx Drain Threshold */
	RxdrthMASK	= 0x0000003E,
	Airl		= 0x04000000,	/* Accept In-Range Length Errored */
	Alp		= 0x08000000,	/* Accept Long Packets */
	Rxfd		= 0x10000000,	/* Receive Full Duplex */
	Stripcrc	= 0x20000000,	/* Strip CRC */
	Arp		= 0x40000000,	/* Accept Runt Packets */
	Aep		= 0x80000000,	/* Accept Errored Packets */
};

enum {					/* Priority Queueing Control */
	Txpqen		= 0x00000001,	/* Transmit Priority Queuing Enable */
	Txfairen	= 0x00000002,	/* Transmit Fairness Enable */
	RxpqenSHFT	= 2,		/* Receive Priority Queue Enable */
	RxpqenMASK	= 0x0000000C,
};

enum {					/* Pause Control/Status */
	PscntSHFT	= 0,		/* Pause Counter Value */
	PscntMASK	= 0x0000FFFF,
	Pstx		= 0x00020000,	/* Transmit Pause Frame */
	PsffloSHFT	= 18,		/* Rx Data FIFO Lo Threshold */
	PsffloMASK	= 0x000C0000,
	PsffhiSHFT	= 20,		/* Rx Data FIFO Hi Threshold */
	PsffhiMASK	= 0x00300000,
	PsstloSHFT	= 22,		/* Rx Stat FIFO Hi Threshold */
	PsstloMASK	= 0x00C00000,
	PssthiSHFT	= 24,		/* Rx Stat FIFO Hi Threshold */
	PssthiMASK	= 0x03000000,
	Psrcvd		= 0x08000000,	/* Pause Frame Received */
	Psact		= 0x10000000,	/* Pause Active */
	Psda		= 0x20000000,	/* Pause on Destination Address */
	Psmcast		= 0x40000000,	/* Pause on Multicast */
	Psen		= 0x80000000,	/* Pause Enable */
};

enum {					/* Receive Filter/Match Control */
	RfaddrSHFT	= 0,		/* Extended Register Address */
	RfaddrMASK	= 0x000003FF,
	Ulm		= 0x00080000,	/* U/L bit mask */
	Uhen		= 0x00100000,	/* Unicast Hash Enable */
	Mhen		= 0x00200000,	/* Multicast Hash Enable */
	Aarp		= 0x00400000,	/* Accept ARP Packets */
	ApatSHFT	= 23,		/* Accept on Pattern Match */
	ApatMASK	= 0x07800000,
	Apm		= 0x08000000,	/* Accept on Perfect Match */
	Aau		= 0x10000000,	/* Accept All Unicast */
	Aam		= 0x20000000,	/* Accept All Multicast */
	Aab		= 0x40000000,	/* Accept All Broadcast */
	Rfen		= 0x80000000,	/* Rx Filter Enable */
};

enum {					/* Receive Filter/Match Data */
	RfdataSHFT	= 0,		/* Receive Filter Data */
	RfdataMASK	= 0x0000FFFF,
	BmaskSHFT	= 16,		/* Byte Mask */
	BmaskMASK	= 0x00030000,
};

enum {					/* MIB Control */
	Wrn		= 0x00000001,	/* Warning Test Indicator */
	Frz		= 0x00000002,	/* Freeze All Counters */
	Aclr		= 0x00000004,	/* Clear All Counters */
	Mibs		= 0x00000008,	/* MIB Counter Strobe */
};

enum {					/* MIB Data */
	Nmibd		= 11,		/* Number of MIB Data Registers */
};

enum {					/* VLAN/IP Receive Control */
	Vtden		= 0x00000001,	/* VLAN Tag Detection Enable */
	Vtren		= 0x00000002,	/* VLAN Tag Removal Enable */
	Dvtf		= 0x00000004,	/* Discard VLAN Tagged Frames */
	Dutf		= 0x00000008,	/* Discard Untagged Frames */
	Ipen		= 0x00000010,	/* IP Checksum Enable */
	Ripe		= 0x00000020,	/* Reject IP Checksum Errors */
	Rtcpe		= 0x00000040,	/* Reject TCP Checksum Errors */
	Rudpe		= 0x00000080,	/* Reject UDP Checksum Errors */
};

enum {					/* VLAN/IP Transmit Control */
	Vgti		= 0x00000001,	/* VLAN Global Tag Insertion */
	Vppti		= 0x00000002,	/* VLAN Per-Packet Tag Insertion */
	Gchk		= 0x00000004,	/* Global Checksum Generation */
	Ppchk		= 0x00000008,	/* Per-Packet Checksum Generation */
};

enum {					/* VLAN Data */
	VtypeSHFT	= 0,		/* VLAN Type Field */
	VtypeMASK	= 0x0000FFFF,
	VtciSHFT	= 16,		/* VLAN Tag Control Information */
	VtciMASK	= 0xFFFF0000,
};

enum {					/* Clockrun Control/Status */
	Clkrunen	= 0x00000001,	/* CLKRUN Enable */
	Pmeen		= 0x00000100,	/* PME Enable */
	Pmests		= 0x00008000,	/* PME Status */
};

typedef struct {
	u32int	link;			/* Link to the next descriptor */
	u32int	bufptr;			/* pointer to data Buffer */
	int	cmdsts;			/* Command/Status */
	int	extsts;			/* optional Extended Status */

	Block*	bp;			/* Block containing bufptr */
	u32int	unused;			/* pad to 64-bit */
} Desc;

enum {					/* Common cmdsts bits */
	SizeMASK	= 0x0000FFFF,	/* Descriptor Byte Count */
	SizeSHFT	= 0,
	Ok		= 0x08000000,	/* Packet OK */
	Crc		= 0x10000000,	/* Suppress/Include CRC */
	Intr		= 0x20000000,	/* Interrupt on ownership transfer */
	More		= 0x40000000,	/* not last descriptor in a packet */
	Own		= 0x80000000,	/* Descriptor Ownership */
};

enum {					/* Transmit cmdsts bits */
	CcntMASK	= 0x000F0000,	/* Collision Count */
	CcntSHFT	= 16,
	Ec		= 0x00100000,	/* Excessive Collisions */
	Owc		= 0x00200000,	/* Out of Window Collision */
	Ed		= 0x00400000,	/* Excessive Deferral */
	Td		= 0x00800000,	/* Transmit Deferred */
	Crs		= 0x01000000,	/* Carrier Sense Lost */
	Tfu		= 0x02000000,	/* Transmit FIFO Underrun */
	Txa		= 0x04000000,	/* Transmit Abort */
};

enum {					/* Receive cmdsts bits */
	Irl		= 0x00010000,	/* In-Range Length Error */
	Lbp		= 0x00020000,	/* Loopback Packet */
	Fae		= 0x00040000,	/* Frame Alignment Error */
	Crce		= 0x00080000,	/* CRC Error */
	Ise		= 0x00100000,	/* Invalid Symbol Error */
	Runt		= 0x00200000,	/* Runt Packet Received */
	Long		= 0x00400000,	/* Too Long Packet Received */
	DestMASK	= 0x01800000,	/* Destination Class */
	DestSHFT	= 23,
	Rxo		= 0x02000000,	/* Receive Overrun */
	Rxa		= 0x04000000,	/* Receive Aborted */
};

enum {					/* extsts bits */
	EvtciMASK	= 0x0000FFFF,	/* VLAN Tag Control Information */
	EvtciSHFT	= 0,
	Vpkt		= 0x00010000,	/* VLAN Packet */
	Ippkt		= 0x00020000,	/* IP Packet */
	Iperr		= 0x00040000,	/* IP Checksum Error */
	Tcppkt		= 0x00080000,	/* TCP Packet */
	Tcperr		= 0x00100000,	/* TCP Checksum Error */
	Udppkt		= 0x00200000,	/* UDP Packet */
	Udperr		= 0x00400000,	/* UDP Checksum Error */
};

enum {
	Nrd		= 256,
	Nrb		= 4*Nrd,
	Rbsz		= ROUNDUP(sizeof(Etherpkt)+8, 8),
	Ntd		= 128,
};

typedef struct Ctlr Ctlr;
typedef struct Ctlr {
	uvlong	port;
	Pcidev*	pcidev;
	Ctlr*	next;
	int	active;
	int	id;

	int	eepromsz;		/* address size in bits */
	ushort*	eeprom;

	int*	nic;
	int	cfg;
	int	imr;

	QLock	alock;			/* attach */
	Lock	ilock;			/* init */
	void*	alloc;			/* base of per-Ctlr allocated data */

	Mii*	mii;

	Lock	rdlock;			/* receive */
	Desc*	rd;
	int	nrd;
	int	nrb;
	int	rdx;
	int	rxcfg;

	Lock	tlock;			/* transmit */
	Desc*	td;
	int	ntd;
	int	tdh;
	int	tdt;
	int	ntq;
	int	txcfg;

	int	rxidle;

	uint	mibd[Nmibd];

	int	ec;
	int	owc;
	int	ed;
	int	crs;
	int	tfu;
	int	txa;
} Ctlr;

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

static Ctlr* dp83820ctlrhead;
static Ctlr* dp83820ctlrtail;

static Lock dp83820rblock;		/* free receive Blocks */
static Block* dp83820rbpool;

static char* dp83820mibs[Nmibd] = {
	"RXErroredPkts",
	"RXFCSErrors",
	"RXMsdPktErrors",
	"RXFAErrors",
	"RXSymbolErrors",
	"RXFrameToLong",
	"RXIRLErrors",
	"RXBadOpcodes",
	"RXPauseFrames",
	"TXPauseFrames",
	"TXSQEErrors",
};

static int
mdior(Ctlr* ctlr, int n)
{
	int data, i, mear, r;

	mear = csr32r(ctlr, Mear);
	r = ~(Mdc|Mddir) & mear;
	data = 0;
	for(i = n-1; i >= 0; i--){
		if(csr32r(ctlr, Mear) & Mdio)
			data |= (1<<i);
		csr32w(ctlr, Mear, Mdc|r);
		csr32w(ctlr, Mear, r);
	}
	csr32w(ctlr, Mear, mear);

	return data;
}

static void
mdiow(Ctlr* ctlr, int bits, int n)
{
	int i, mear, r;

	mear = csr32r(ctlr, Mear);
	r = Mddir|(~Mdc & mear);
	for(i = n-1; i >= 0; i--){
		if(bits & (1<<i))
			r |= Mdio;
		else
			r &= ~Mdio;
		csr32w(ctlr, Mear, r);
		csr32w(ctlr, Mear, Mdc|r);
	}
	csr32w(ctlr, Mear, mear);
}

static int
dp83820miimir(Mii* mii, int pa, int ra)
{
	int data;
	Ctlr *ctlr;

	ctlr = mii->ctlr;

	/*
	 * MII Management Interface Read.
	 *
	 * Preamble;
	 * ST+OP+PA+RA;
	 * LT + 16 data bits.
	 */
	mdiow(ctlr, 0xFFFFFFFF, 32);
	mdiow(ctlr, 0x1800|(pa<<5)|ra, 14);
	data = mdior(ctlr, 18);

	if(data & 0x10000)
		return -1;

	return data & 0xFFFF;
}

static int
dp83820miimiw(Mii* mii, int pa, int ra, int data)
{
	Ctlr *ctlr;

	ctlr = mii->ctlr;

	/*
	 * MII Management Interface Write.
	 *
	 * Preamble;
	 * ST+OP+PA+RA+LT + 16 data bits;
	 * Z.
	 */
	mdiow(ctlr, 0xFFFFFFFF, 32);
	data &= 0xFFFF;
	data |= (0x05<<(5+5+2+16))|(pa<<(5+2+16))|(ra<<(2+16))|(0x02<<16);
	mdiow(ctlr, data, 32);

	return 0;
}

static Block *
dp83820rballoc(Desc* desc)
{
	Block *bp;

	if(desc->bp == nil){
		ilock(&dp83820rblock);
		if((bp = dp83820rbpool) == nil){
			iunlock(&dp83820rblock);
			desc->bp = nil;
			desc->cmdsts = Own;
			return nil;
		}
		dp83820rbpool = bp->next;
		bp->next = nil;
		iunlock(&dp83820rblock);
	
		desc->bufptr = PCIWADDR(bp->rp);
		desc->bp = bp;
	}
	else{
		bp = desc->bp;
		bp->rp = bp->lim - Rbsz;
		bp->wp = bp->rp;
	}

	coherence();
	desc->cmdsts = Intr|Rbsz;

	return bp;
}

static void
dp83820rbfree(Block *bp)
{
	bp->rp = bp->lim - Rbsz;
	bp->wp = bp->rp;

	ilock(&dp83820rblock);
	bp->next = dp83820rbpool;
	dp83820rbpool = bp;
	iunlock(&dp83820rblock);
}

static void
dp83820halt(Ctlr* ctlr)
{
	int i, timeo;

	ilock(&ctlr->ilock);
	csr32w(ctlr, Imr, 0);
	csr32w(ctlr, Ier, 0);
	csr32w(ctlr, Cr, Rxd|Txd);
	for(timeo = 0; timeo < 1000; timeo++){
		if(!(csr32r(ctlr, Cr) & (Rxe|Txe)))
			break;
		microdelay(1);
	}
	csr32w(ctlr, Mibc, Frz);
	iunlock(&ctlr->ilock);

	if(ctlr->rd != nil){
		for(i = 0; i < ctlr->nrd; i++){
			if(ctlr->rd[i].bp == nil)
				continue;
			freeb(ctlr->rd[i].bp);
			ctlr->rd[i].bp = nil;
		}
	}
	if(ctlr->td != nil){
		for(i = 0; i < ctlr->ntd; i++){
			if(ctlr->td[i].bp == nil)
				continue;
			freeb(ctlr->td[i].bp);
			ctlr->td[i].bp = nil;
		}
	}
}

static void
dp83820cfg(Ctlr* ctlr)
{
	int cfg;

	/*
	 * Don't know how to deal with a TBI yet.
	 */
	if(ctlr->mii == nil)
		return;

	/*
	 * The polarity of these bits is at the mercy
	 * of the board designer.
	 * The correct answer for all speed and duplex questions
	 * should be to query the phy.
	 */
	cfg = csr32r(ctlr, Cfg);
	if(!(cfg & Dupsts)){
		ctlr->rxcfg |= Rxfd;
		ctlr->txcfg |= Csi|Hbi;
		iprint("83820: full duplex, ");
	}
	else{
		ctlr->rxcfg &= ~Rxfd;
		ctlr->txcfg &= ~(Csi|Hbi);
		iprint("83820: half duplex, ");
	}
	csr32w(ctlr, Rxcfg, ctlr->rxcfg);
	csr32w(ctlr, Txcfg, ctlr->txcfg);

	switch(cfg & (Spdsts1000|Spdsts100)){
	case Spdsts1000:		/* 100Mbps */
	default:			/* 10Mbps */
		ctlr->cfg &= ~Mode1000;
		if((cfg & (Spdsts1000|Spdsts100)) == Spdsts1000)
			iprint("100Mb/s\n");
		else
			iprint("10Mb/s\n");
		break;
	case Spdsts100:			/* 1Gbps */
		ctlr->cfg |= Mode1000;
		iprint("1Gb/s\n");
		break;
	}
	csr32w(ctlr, Cfg, ctlr->cfg);
}

static void
dp83820init(Ether* edev)
{
	int i;
	Ctlr *ctlr;
	Desc *desc;
	uchar *alloc;

	ctlr = edev->ctlr;

	dp83820halt(ctlr);

	/*
	 * Receiver
	 */
	alloc = (uchar*)ROUNDUP((ulong)ctlr->alloc, 8);
	ctlr->rd = (Desc*)alloc;
	alloc += ctlr->nrd*sizeof(Desc);
	memset(ctlr->rd, 0, ctlr->nrd*sizeof(Desc));
	ctlr->rdx = 0;
	for(i = 0; i < ctlr->nrd; i++){
		desc = &ctlr->rd[i];
		desc->link = PCIWADDR(&ctlr->rd[NEXT(i, ctlr->nrd)]);
		if(dp83820rballoc(desc) == nil)
			continue;
	}
	csr32w(ctlr, Rxdphi, 0);
	csr32w(ctlr, Rxdp, PCIWADDR(ctlr->rd));

	for(i = 0; i < Eaddrlen; i += 2){
		csr32w(ctlr, Rfcr, i);
		csr32w(ctlr, Rfdr, (edev->ea[i+1]<<8)|edev->ea[i]);
	}
	csr32w(ctlr, Rfcr, Rfen|Aab|Aam|Apm);

	ctlr->rxcfg = Stripcrc|(((2*(ETHERMINTU+4))/8)<<RxdrthSHFT);
	ctlr->imr |= Rxorn|Rxidle|Rxearly|Rxdesc|Rxok;

	/*
	 * Transmitter.
	 */
	ctlr->td = (Desc*)alloc;
	memset(ctlr->td, 0, ctlr->ntd*sizeof(Desc));
	ctlr->tdh = ctlr->tdt = ctlr->ntq = 0;
	for(i = 0; i < ctlr->ntd; i++){
		desc = &ctlr->td[i];
		desc->link = PCIWADDR(&ctlr->td[NEXT(i, ctlr->ntd)]);
	}
	csr32w(ctlr, Txdphi, 0);
	csr32w(ctlr, Txdp, PCIWADDR(ctlr->td));

	ctlr->txcfg = Atp|(((2*(ETHERMINTU+4))/32)<<FlthSHFT)|((4096/32)<<TxdrthSHFT);
	ctlr->imr |= Txurn|Txidle|Txdesc|Txok;

	ilock(&ctlr->ilock);

	dp83820cfg(ctlr);

	csr32w(ctlr, Mibc, Aclr);
	ctlr->imr |= Mib;

	csr32w(ctlr, Imr, ctlr->imr);

	/* try coalescing adjacent interrupts; use hold-off interval of 100µs */
	csr32w(ctlr, Ihr, Ihctl|(1<<IhSHFT));

	csr32w(ctlr, Ier, Ien);
	csr32w(ctlr, Cr, Rxe|Txe);

	iunlock(&ctlr->ilock);
}

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

	ctlr = edev->ctlr;
	qlock(&ctlr->alock);
	if(ctlr->alloc != nil){
		qunlock(&ctlr->alock);
		return;
	}

	if(waserror()){
		if(ctlr->mii != nil){
			free(ctlr->mii);
			ctlr->mii = nil;
		}
		if(ctlr->alloc != nil){
			free(ctlr->alloc);
			ctlr->alloc = nil;
		}
		qunlock(&ctlr->alock);
		nexterror();
	}

	if(!(ctlr->cfg & Tbien)){
		if((ctlr->mii = malloc(sizeof(Mii))) == nil)
			error(Enomem);
		ctlr->mii->ctlr = ctlr;
		ctlr->mii->mir = dp83820miimir;
		ctlr->mii->miw = dp83820miimiw;
		if(mii(ctlr->mii, ~0) == 0)
			error("no PHY");
		ctlr->cfg |= Dupstsien|Lnkstsien|Spdstsien;
		ctlr->imr |= Phy;
	}

	ctlr->nrd = Nrd;
	ctlr->nrb = Nrb;
	ctlr->ntd = Ntd;
	ctlr->alloc = mallocz((ctlr->nrd+ctlr->ntd)*sizeof(Desc) + 7, 0);
	if(ctlr->alloc == nil)
		error(Enomem);

	for(ctlr->nrb = 0; ctlr->nrb < Nrb; ctlr->nrb++){
		if((bp = allocb(Rbsz)) == nil)
			break;
		bp->free = dp83820rbfree;
		dp83820rbfree(bp);
	}

	dp83820init(edev);

	qunlock(&ctlr->alock);
	poperror();
}

static void
dp83820transmit(Ether* edev)
{
	Block *bp;
	Ctlr *ctlr;
	Desc *desc;
	int cmdsts, r, x;

	ctlr = edev->ctlr;

	ilock(&ctlr->tlock);

	bp = nil;
	for(x = ctlr->tdh; ctlr->ntq; x = NEXT(x, ctlr->ntd)){
		desc = &ctlr->td[x];
		if((cmdsts = desc->cmdsts) & Own)
			break;
		if(!(cmdsts & Ok)){
			if(cmdsts & Ec)
				ctlr->ec++;
			if(cmdsts & Owc)
				ctlr->owc++;
			if(cmdsts & Ed)
				ctlr->ed++;
			if(cmdsts & Crs)
				ctlr->crs++;
			if(cmdsts & Tfu)
				ctlr->tfu++;
			if(cmdsts & Txa)
				ctlr->txa++;
			edev->oerrs++;
		}
		desc->bp->next = bp;
		bp = desc->bp;
		desc->bp = nil;

		ctlr->ntq--;
	}
	ctlr->tdh = x;
	if(bp != nil)
		freeblist(bp);

	x = ctlr->tdt;
	while(ctlr->ntq < (ctlr->ntd-1)){
		if((bp = qget(edev->oq)) == nil)
			break;

		desc = &ctlr->td[x];
		desc->bufptr = PCIWADDR(bp->rp);
		desc->bp = bp;
		ctlr->ntq++;
		coherence();
		desc->cmdsts = Own|Intr|BLEN(bp);

		x = NEXT(x, ctlr->ntd);
	}
	if(x != ctlr->tdt){
		ctlr->tdt = x;
		r = csr32r(ctlr, Cr);
		csr32w(ctlr, Cr, Txe|r);
	}

	iunlock(&ctlr->tlock);
}

static void
dp83820interrupt(Ureg*, void* arg)
{
	Block *bp;
	Ctlr *ctlr;
	Desc *desc;
	Ether *edev;
	int cmdsts, i, isr, r, x;

	edev = arg;
	ctlr = edev->ctlr;

	for(isr = csr32r(ctlr, Isr); isr & ctlr->imr; isr = csr32r(ctlr, Isr)){
		if(isr & (Rxorn|Rxidle|Rxearly|Rxerr|Rxdesc|Rxok)){
			x = ctlr->rdx;
			desc = &ctlr->rd[x];
			while((cmdsts = desc->cmdsts) & Own){
				if((cmdsts & Ok) && desc->bp != nil){
					bp = desc->bp;
					desc->bp = nil;
					bp->wp += cmdsts & SizeMASK;
					etheriq(edev, bp);
				}
				else if(0 && !(cmdsts & Ok)){
					iprint("dp83820: rx %8.8uX:", cmdsts);
					bp = desc->bp;
					for(i = 0; i < 20; i++)
						iprint(" %2.2uX", bp->rp[i]);
					iprint("\n");
				}
				dp83820rballoc(desc);

				x = NEXT(x, ctlr->nrd);
				desc = &ctlr->rd[x];
			}
			ctlr->rdx = x;

			if(isr & Rxidle){
				r = csr32r(ctlr, Cr);
				csr32w(ctlr, Cr, Rxe|r);
				ctlr->rxidle++;
			}

			isr &= ~(Rxorn|Rxidle|Rxearly|Rxerr|Rxdesc|Rxok);
		}

		if(isr & Txurn){
			x = (ctlr->txcfg & TxdrthMASK)>>TxdrthSHFT;
			r = (ctlr->txcfg & FlthMASK)>>FlthSHFT;
			if(x < ((TxdrthMASK)>>TxdrthSHFT)
			&& x < (2048/32 - r)){
				ctlr->txcfg &= ~TxdrthMASK;
				x++;
				ctlr->txcfg |= x<<TxdrthSHFT;
				csr32w(ctlr, Txcfg, ctlr->txcfg);
			}
		}

		if(isr & (Txurn|Txidle|Txdesc|Txok)){
			dp83820transmit(edev);
			isr &= ~(Txurn|Txidle|Txdesc|Txok);
		}

		if(isr & Mib){
			for(i = 0; i < Nmibd; i++){
				r = csr32r(ctlr, Mibd+(i*sizeof(int)));
				ctlr->mibd[i] += r & 0xFFFF;
			}
			isr &= ~Mib;
		}

		if((isr & Phy) && ctlr->mii != nil){
			ctlr->mii->mir(ctlr->mii, 1, Bmsr);
			print("phy: cfg %8.8uX bmsr %4.4uX\n",
				csr32r(ctlr, Cfg),
				ctlr->mii->mir(ctlr->mii, 1, Bmsr));
			dp83820cfg(ctlr);
			isr &= ~Phy;
		}
		if(isr)
			iprint("dp83820: isr %8.8uX\n", isr);
	}
}

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

	ctlr = edev->ctlr;

	edev->crcs = ctlr->mibd[Mibd+(1*sizeof(int))];
	edev->frames = ctlr->mibd[Mibd+(3*sizeof(int))];
	edev->buffs = ctlr->mibd[Mibd+(5*sizeof(int))];
	edev->overflows = ctlr->mibd[Mibd+(2*sizeof(int))];

	if(n == 0)
		return 0;

	p = smalloc(READSTR);
	l = 0;
	for(i = 0; i < Nmibd; i++){
		r = csr32r(ctlr, Mibd+(i*sizeof(int)));
		ctlr->mibd[i] += r & 0xFFFF;
		if(ctlr->mibd[i] != 0 && dp83820mibs[i] != nil)
			l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
				dp83820mibs[i], ctlr->mibd[i], r);
	}
	l += snprint(p+l, READSTR-l, "rxidle %d\n", ctlr->rxidle);
	l += snprint(p+l, READSTR-l, "ec %d\n", ctlr->ec);
	l += snprint(p+l, READSTR-l, "owc %d\n", ctlr->owc);
	l += snprint(p+l, READSTR-l, "ed %d\n", ctlr->ed);
	l += snprint(p+l, READSTR-l, "crs %d\n", ctlr->crs);
	l += snprint(p+l, READSTR-l, "tfu %d\n", ctlr->tfu);
	l += snprint(p+l, READSTR-l, "txa %d\n", ctlr->txa);

	l += snprint(p+l, READSTR-l, "rom:");
	for(i = 0; i < 0x10; 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");

	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
		l += snprint(p+l, READSTR-l, "phy:");
		for(i = 0; i < NMiiPhyr; i++){
			if(i && ((i & 0x07) == 0))
				l += snprint(p+l, READSTR-l, "\n    ");
			r = miimir(ctlr->mii, i);
			l += snprint(p+l, READSTR-l, " %4.4uX", r);
		}
		snprint(p+l, READSTR-l, "\n");
	}

	n = readstr(offset, a, n, p);
	free(p);

	return n;
}

static void
dp83820promiscuous(void* arg, int on)
{
	USED(arg, on);
}

/* multicast already on, don't need to do anything */
static void
dp83820multicast(void*, uchar*, int)
{
}

static int
dp83820detach(Ctlr* ctlr)
{
	/*
	 * Soft reset the controller.
	 */
	csr32w(ctlr, Cr, Rst);
	delay(1);
	while(csr32r(ctlr, Cr) & Rst)
		delay(1);
	return 0;
}

static void
dp83820shutdown(Ether* ether)
{
print("dp83820shutdown\n");
	dp83820detach(ether->ctlr);
}

static int
atc93c46r(Ctlr* ctlr, int address)
{
	int data, i, mear, r, size;

	/*
	 * Analog Technology, Inc. ATC93C46
	 * or equivalent serial EEPROM.
	 */
	mear = csr32r(ctlr, Mear);
	mear &= ~(Eesel|Eeclk|Eedo|Eedi);
	r = Eesel|mear;

reread:
	csr32w(ctlr, Mear, r);
	data = 0x06;
	for(i = 3-1; i >= 0; i--){
		if(data & (1<<i))
			r |= Eedi;
		else
			r &= ~Eedi;
		csr32w(ctlr, Mear, r);
		csr32w(ctlr, Mear, Eeclk|r);
		microdelay(1);
		csr32w(ctlr, Mear, r);
		microdelay(1);
	}

	/*
	 * First time through must work out the EEPROM size.
	 */
	if((size = ctlr->eepromsz) == 0)
		size = 8;

	for(size = size-1; size >= 0; size--){
		if(address & (1<<size))
			r |= Eedi;
		else
			r &= ~Eedi;
		csr32w(ctlr, Mear, r);
		microdelay(1);
		csr32w(ctlr, Mear, Eeclk|r);
		microdelay(1);
		csr32w(ctlr, Mear, r);
		microdelay(1);
		if(!(csr32r(ctlr, Mear) & Eedo))
			break;
	}
	r &= ~Eedi;

	data = 0;
	for(i = 16-1; i >= 0; i--){
		csr32w(ctlr, Mear, Eeclk|r);
		microdelay(1);
		if(csr32r(ctlr, Mear) & Eedo)
			data |= (1<<i);
		csr32w(ctlr, Mear, r);
		microdelay(1);
	}

	csr32w(ctlr, Mear, mear);

	if(ctlr->eepromsz == 0){
		ctlr->eepromsz = 8-size;
		ctlr->eeprom = malloc((1<<ctlr->eepromsz)*sizeof(ushort));
		goto reread;
	}

	return data;
}

static int
dp83820reset(Ctlr* ctlr)
{
	int i, r;
	unsigned char sum;

	/*
	 * Soft reset the controller;
	 * read the EEPROM to get the initial settings
	 * of the Cfg and Gpior bits which should be cleared by
	 * the reset.
	 */
	dp83820detach(ctlr);

	atc93c46r(ctlr, 0);
	if(ctlr->eeprom == nil) {
		print("dp83820reset: no eeprom\n");
		return -1;
	}
	sum = 0;
	for(i = 0; i < 0x0E; i++){
		r = atc93c46r(ctlr, i);
		ctlr->eeprom[i] = r;
		sum += r;
		sum += r>>8;
	}

	if(sum != 0){
		print("dp83820reset: bad EEPROM checksum\n");
		return -1;
	}

#ifdef notdef
	csr32w(ctlr, Gpior, ctlr->eeprom[4]);

	cfg = Extstsen|Exd;
	r = csr32r(ctlr, Cfg);
	if(ctlr->eeprom[5] & 0x0001)
		cfg |= Ext125;
	if(ctlr->eeprom[5] & 0x0002)
		cfg |= M64addren;
	if((ctlr->eeprom[5] & 0x0004) && (r & Pci64det))
		cfg |= Data64en;
	if(ctlr->eeprom[5] & 0x0008)
		cfg |= T64addren;
	if(!(pcicfgr16(ctlr->pcidev, PciPCR) & 0x10))
		cfg |= Mwidis;
	if(ctlr->eeprom[5] & 0x0020)
		cfg |= Mrmdis;
	if(ctlr->eeprom[5] & 0x0080)
		cfg |= Mode1000;
	if(ctlr->eeprom[5] & 0x0200)
		cfg |= Tbien|Mode1000;
	/*
	 * What about RO bits we might have destroyed with Rst?
	 * What about Exd, Tmrtest, Extstsen, Pintctl?
	 * Why does it think it has detected a 64-bit bus when
	 * it hasn't?
	 */
#else
	// r = csr32r(ctlr, Cfg);
	// r &= ~(Mode1000|T64addren|Data64en|M64addren);
	// csr32w(ctlr, Cfg, r);
	// csr32w(ctlr, Cfg, 0x2000);
#endif						/* notdef */
	ctlr->cfg = csr32r(ctlr, Cfg);
print("cfg %8.8uX pcicfg %8.8uX\n", ctlr->cfg, pcicfgr32(ctlr->pcidev, PciPCR));
	ctlr->cfg &= ~(T64addren|Data64en|M64addren);
	csr32w(ctlr, Cfg, ctlr->cfg);
	csr32w(ctlr, Mibc, Aclr|Frz);

	return 0;
}

static void
dp83820pci(void)
{
	void *mem;
	Pcidev *p;
	Ctlr *ctlr;

	p = nil;
	while(p = pcimatch(p, 0, 0)){
		if(p->ccrb != Pcibcnet || p->ccru != Pciscether)
			continue;
		if(p->mem[1].bar & 1)
			continue;

		switch((p->did<<16)|p->vid){
		default:
			continue;
		case (0x0022<<16)|0x100B:	/* DP83820 (Gig-NIC) */
			break;
		}

		mem = vmap(p->mem[1].bar & ~0xF, p->mem[1].size);
		if(mem == nil){
			print("DP83820: can't map %llux\n", p->mem[1].bar & ~0xF);
			continue;
		}

		ctlr = malloc(sizeof(Ctlr));
		if(ctlr == nil){
			print("DP83820: can't allocate memory\n");
			continue;
		}
		ctlr->port = p->mem[1].bar & ~0xF;
		ctlr->pcidev = p;
		pcienable(p);
		ctlr->id = (p->did<<16)|p->vid;

		ctlr->nic = mem;
		if(dp83820reset(ctlr)){
			free(ctlr);
			continue;
		}
		pcisetbme(p);

		if(dp83820ctlrhead != nil)
			dp83820ctlrtail->next = ctlr;
		else
			dp83820ctlrhead = ctlr;
		dp83820ctlrtail = ctlr;
	}
}

static int
dp83820pnp(Ether* edev)
{
	int i;
	Ctlr *ctlr;
	uchar ea[Eaddrlen];

	if(dp83820ctlrhead == nil)
		dp83820pci();

	/*
	 * Any adapter matches if no edev->port is supplied,
	 * otherwise the ports must match.
	 */
	for(ctlr = dp83820ctlrhead; 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;
	edev->port = ctlr->port;
	edev->irq = ctlr->pcidev->intl;
	edev->tbdf = ctlr->pcidev->tbdf;
	edev->mbps = 1000;

	/*
	 * Check if the adapter's station address is to be overridden.
	 * If not, read it from the EEPROM and set in ether->ea prior to
	 * loading the station address in the hardware.
	 */
	memset(ea, 0, Eaddrlen);
	if(memcmp(ea, edev->ea, Eaddrlen) == 0)
		for(i = 0; i < Eaddrlen/2; i++){
			edev->ea[2*i] = ctlr->eeprom[0x0C-i];
			edev->ea[2*i+1] = ctlr->eeprom[0x0C-i]>>8;
		}

	edev->attach = dp83820attach;
	edev->transmit = dp83820transmit;
	edev->ifstat = dp83820ifstat;

	edev->arg = edev;
	edev->promiscuous = dp83820promiscuous;
	edev->multicast = dp83820multicast;
	edev->shutdown = dp83820shutdown;

	intrenable(edev->irq, dp83820interrupt, edev, edev->tbdf, edev->name);

	return 0;
}

void
etherdp83820link(void)
{
	addethercard("DP83820", dp83820pnp);
}