/* 
 * Xircom Realport PCMCIA card support.
 *
 * Documentation sources:
 * 1. X1601-3 External Reference Specification, Revision C.  Xircom Inc.,
 *    Available for download from http://www.xircom.com/
 * 2. ML6692 100BASE-TX Physical Layer with MII specification.  MicroLinear
 *    Corp, Available for download from http://www.microlinear.com/
 * 3. Scott Mitchell's if_xe driver for FreeBSD.
 * 4. Werner Koch's xirc2ps driver for Linux.
 * 5. Plan9 drivers ethersmc, etherec2t, and ether82557.
 *
 * The mii routines were derived from those in the Linux driver, see
 * the mii section for the license.
 */

#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 "etherif.h"

#define PageSelect(c, x)		(outb((c)->port+Pr, (x)))
#define csr8r(c, r)				(inb((c)->port+(r)))
#define csr16r(c, r)			(ins((c)->port+(r)))
#define csr8sr(c, r, b, l)		(insb((c)->port+(r), (b), (int)(l)))
#define csr16sr(c, r, w, l)		(inss((c)->port+(r), (w), (int)(l)))
#define csr8w(c, r, b)			(outb((c)->port+(r), (int)(b)))
#define csr16w(c, r, w)			(outs((c)->port+(r), (ushort)(w)))
#define csr8sw(c, r, b, l)		(outsb((c)->port+(r), (b), (int)(l)))
#define csr16sw(c, r, w, l)		(outss((c)->port+(r), (w), (int)(l)))


typedef struct {
	Lock;
	ushort rev;
	int attached;
	Block *txbp;  /* transmit buffer */
	int srev;
	int port;
	ulong ccrport;
	int slot;
	int mohawk;
	int dingo;
	int speed;
	int duplex;
	ulong interrupts;
	ulong txover;
	ulong rxreject;
	ulong macintr;
	ulong rxbad;
	ulong longpacket;
	ulong rxwrap;
	ulong rxbadlen;
	ulong rxoklen;
} Ctlr;

static uint plength(Ether* ether);
static void config_dingo(Ether* ether);
static void config_mohawk(Ether* ether);

static void enable_intr(Ctlr* ctlr);
static void disable_intr(Ctlr* ctlr);

/* reset */
static void chipreset(Ctlr* ctlr);
static void hardreset(Ctlr* ctlr);
static void softreset(Ctlr* ctlr);
static void chipenable(Ether* ether);
static void mediaselect(Ctlr* ctrl);

/* CIS */
static void findmodel(Ether *ether,char *type);
static void findspeed(Ether *ether);
static int getslot(Ether *ether, char **type);
static int readnodeid(Ether* ether);
static void cis_scan(Ether* ether);

/* debug */
void printbits(uint x);
static void rxreg_dump(Ctlr *ctlr);
static void txreg_dump(Ctlr *ctlr);
static void reg_dump(Ctlr *ctlr);

/* MII */
static void mii_idle(Ctlr *ctlr);
static void mii_putbit(Ctlr *ctlr, uint data);
static int mii_getbit(Ctlr *ctlr);
static void mii_wbits(Ctlr *ctlr, uint data, int len);
static uint mii_rd(Ctlr *ctlr, uchar phyaddr, uchar phyreg);
static void mii_wr(Ctlr *ctlr, uchar phyaddr, uchar phyreg, uint data, int len);
static int mii_init(Ctlr *ctlr);

static char* xircompcmcia[] = {
	"CEM56",	   /* dingo/mohawk (not supported yet...) */			
	"CE3-10/100",  /* mohawk */
	"R2E-100BTX",  
	"PRO/100 M16A",		
	nil,
};

static char* mediatable[4] = {
	"10BASE-T",		
	"10BASE-TFD",
	"100BASE-TX",
	"100BASE-TXFD",
};


enum { 
	DEBUG 		= 0,
	IDEBUG 		= 0,
	RXDEBUG 		= 0,
	TXDEBUG 		= 0,
	MIIDEBUG		= 0,
};

enum {	/* CIS bits (from P9 ethersmc driver) */
	TupleFunce	= 0x22,
	TfNodeId		= 0x04,
};

enum {	/* Phys bits */
	Phy_Bmcr		= 0x00,		/* Basic mode control register */
	Phy_Bmsr		= 0x01,		/* Basic mode status register */
	Phy_Id1		= 0x02,		/* Phy Id 1 */
	Phy_Id2		= 0x03,		/* Phy Id 2 */
};

enum { /* Attribute memory registers (Xircom pg 28-35) */
	Dingo_Cis			= 0x0000,			/* Start of Card Info Stucture (CIS) tuples */
	Dingo_Ecor			= 0x0800,			/* Ethernet configuration option register */
	Dingo_Ecsr			= 0x0802,			/* Ethernet configuration status register */
	Dingo_Ebar0 			= 0x080a,			/* Ethernet base address register */
	Dingo_Ebar1 			= 0x080c,
	Dingo_Dcor0			= 0x0820,  		/* Configuration options registers */
	Dingo_Dcor1			= 0x0822,
	Dingo_Dcor2			= 0x0824,
	Dingo_Dcor3			= 0x0826,
	Dingo_Dcor4			= 0x0828,
	Dingo_Sfcor			= 0x0840,  		/* 2nd function configuration option register */
};

enum { /* Ecor bits (Xircom pg 28) */
	Dingo_Ecor_EthEnable	= 0x01,				
	Dingo_Ecor_IobEnable	= 0x02,		
	Dingo_Ecor_IntEnable	= 0x04,			
	Dingo_Ecor_IrqStschg	= 0x20,		
	Dingo_Ecor_IrqLevel	= 0x40,				
	Dingo_Ecor_Sreset		= 0x80,			
};


enum { /* Group A (Xircom pg 36) */
	Cr 		= 0x00, 		/* Command register  */
	Esr		= 0x00,		/* Ethernet status register */
	Pr		= 0x01, 		/* Page select register */
	Edp		= 0x04,		/* Ethernet data port */
	Isr0		= 0x06,		/* Ethernet interrupt status register */
	Gir		= 0x07,		/* Global interrupt register  */
	
};

enum {/* Cr bits (Xircom pg 41) */
	Cr_DisableIntr		= 0x00,
	Cr_TxPacket		= 0x01,	
	Cr_SoftReset		= 0x02,
	Cr_EnableIntr		= 0x04,
	Cr_ForceIntr		= 0x08,
	Cr_ClearFifo		= 0x10,	
	Cr_ClearOverrun	= 0x20,	
	Cr_RestartTx		= 0x40,	
};

enum {/* Esr bits (Xircom pg 42) */
	Esr_RxFull		= 0x01,	
	Esr_RxPartial		= 0x02,	
	Esr_RxReject		= 0x04,	
	Esr_TxPending		= 0x08,	
	Esr_BadPolarity	= 0x10,	
	Esr_MediaSelect	= 0x20,	
};

enum {/* Isr bits (Xircom pg 44) */
	Isr_TxOverflow	= 0x01,	
	Isr_TxPacket		= 0x02,	
	Isr_MacIntr		= 0x04,	
	Isr_RxEarly		= 0x10,
	Isr_RxFull			= 0x20,	
	Isr_RxReject		= 0x40,	
	Isr_ForceIntr		= 0x80,	
};

enum { /* Gir bits (Xircom pg 45) */
	Gir_EthIrq		= 0x01,	
	Gir_ErhMask	= 0x02,	
	Gir_SfIrq		= 0x04,	
	Gir_SfMask	= 0x08,	
};


enum { /* page 0 (Xircom pg 36)  */
	Tso0		= 0x08,			/* Transmit space open */
	Tso1		= 0x09,
	Tso2		= 0x0a,	
	Do0		= 0x0c,			/* Data offset register */
	Do1		= 0x0d,
	Rsr		= 0x0c,			/* Rx status register  */
	Tpr		= 0x0d,			/* Tx packets register */
	Rbc0		= 0x0e,			/* Rx byte count */
	Rbc1		= 0x0f,
};

enum { /* Do bits (Xircom pg 47) */
	Do_Offset			= 0x1fff,
	Do_ChangeOffset	= 0x2000,
	Do_Smem		= 0x4000,	
	Do_SkipPacket		= 0x8000,
};

enum { /* Rsr bits (Xircom pg 48) */
	Rsr_PhyPacket		= 0x01,	
	Rsr_BcastPacket	= 0x02,	
	Rsr_LongPacket	= 0x04,		
	Rsr_AddrMatch	= 0x08,	
	Rsr_AlignError		= 0x10,		/* CE2 only */
	Rsr_CrcError		= 0x20,	
	Rsr_RxOk			= 0x80,	
};

enum { /* page 1  (Xircom pg 36) */
	Imr0		= 0x0c,			/* Interrupt mask register 0 */
	Imr1		= 0x0d,			/* Interrupt mask register 1 (CE2 only) */
	Ecr		= 0x0e,			/* Ethernet configuration register */
};

enum { /* Imr bits (Xircom pg 46) */
	Imr_TxOverflow	= 0x01,	
	Imr_TxPacket		= 0x02,	
	Imr_MacIntr		= 0x04,	
	Imr_RxEarly		= 0x10,
	Imr_RxFull		= 0x20,	
	Imr_RxReject		= 0x40,	
	Imr_ForceIntr		= 0x80
};

enum { /* Ecr bits (Xircom pg 51) */
	Ecr_EarlyTx		= 0x01,
	Ecr_EarlyRx		= 0x02,
	Ecr_FullDuplex		= 0x04,
	Ecr_LongTpCable	= 0x08,		/* CE2 only */
	Ecr_NoPolCol		= 0x10,		/* CE2 only */
	Ecr_NoLinkPulse	= 0x20,
	Ecr_NoAutoTx		= 0x40,		/* CE2 only */
	Ecr_CompatMode	= 0x80,		/* Map ESR bits 2:0 to RBC bits 15:13 */
};

enum { /* page 2 (Xircom pg 37) */
	Rbs0		= 0x08,		/* Receive buffer start*/
	Rbs1		= 0x09,
	Led		= 0x0a,		/* LED control register */
	Led3		= 0x0b,		/* LED3 control register */
	Msr		= 0x0c,		/* Misc. setup register */
	Gp2		= 0x0d,		/* General purpose register 2 */
};

enum { /* Led bits (Xircom pg 52) */
	Led_Led0Mask		= 0x07,	
	Led_Led0Shift		= 0x00,
	Led_Led1Mask		= 0x38,	
	Led_Led1Shift		= 0x03,
	Led_Led0Rx		= 0x40,
	Led_Led1Rx		= 0x80,
};

enum { /* Led3 bits (Xircom pg 53) */
	Led3_Mask		= 0x07,	
	Led3_Shift		= 0x00,
	Led3_Rx			= 0x40,
};

enum { /* Msr bits (Xircom pg 55) */
	Msr_128K			= 0x01,
	Msr_RbsBit16		= 0x02,	
	Msr_MiiSelect		= 0x08,	
	Msr_HashTable	= 0x20,
};

enum {/* Gp2 bits (Xircom pg 57) */
	Gp2_Gp3Out		= 0x01,	
	Gp2_Gp4Out		= 0x02,
	Gp2_Gp3Select		= 0x04,	
	Gp2_Gp4Select		= 0x08,	
	Gp2_Gp3In		= 0x10,
	Gp2_Gp4In		= 0x20,	
};

enum { /* page 3 (Xircom pg 37) */
	Tpt0	= 0x0a,		/* Transmit packet threshold */
	Tpt1	= 0x0b,
};

enum { /* page 4 (Xircom pg 37) */
	Gp0		= 0x08,		/* General purpose register 0 */
	Gp1		= 0x09,		/* General purpose register 1 */
	Bv		= 0x0a,		/* Bonding version register  */
	Ees		= 0x0b,		/* EEPROM control register */
	Lma		= 0x0c,		/* Local memory address (CE2 only) */
	Lmd		= 0x0e, 	/* Local memory data (CE2 only) */
};

/* Gp0 bits (Xircom pg 56) */
enum {
	Gp0_Gp1Out		= 0x01,
	Gp0_Gp2Out		= 0x02,
	Gp0_Gp1Select		= 0x04,
	Gp0_Gp2Select		= 0x08,	
	Gp0_Gp1In		= 0x10,
	Gp0_Gp2In		= 0x20,

};

enum { /* page 5 (Xircom pg 38)  */
	Crha0	= 0x08,		/* Current Rx host address */
	Crha1	= 0x09,
	Rhsa0 	= 0x0a,		/* Rx host start address */
	Rhsa1	= 0x0b,
	Rnsa0	= 0x0c,		/* Rx network start address */
	Rnsa1	= 0x0d,
	Crna0	= 0x0e,		/* Current Rx network address */
	Crna1	= 0x0f,
};

enum { /* page 6 (Xircom pg 38) */
	Ctha0	= 0x08,		/* Current Tx host address */
	Ctha1	= 0x09,
	Thsa0	= 0x0a,		/* Tx host start address */
	Thsa1	= 0x0b,
	Tnsa0	= 0x0c,		/* Tx network start address */
	Tnsa1	= 0x0d,
	Ctna0	= 0x0e,		/* Current Tx network address */
	Ctna1	= 0x0f,
};

enum { /* page 8 (Xircom pg 39) */
	Thbc0 	= 0x08,		/* Tx host byte count */
	Thbc1	= 0x09,
	Thps0	= 0x0a,		/* Tx host packet size */
	Thps1	= 0x0b,
	Tnbc0	= 0x0c,		/* Tx network byte count */
	Tnbc1	= 0x0d,
	Tnps0	= 0x0e,		/* Tx network packet size */
	Tnps1	= 0x0f,
};

enum { /* page 0x10 (Xircom pg 39, 58-59) */
	DingoID	= 0x08,		/* Dingo ID register */
	RevID	= 0x0a,		/* Dingo revision ID */
	VendorID 	= 0x0c,		/* Dingo vendor ID */
};

enum { /* page 0x40 (Xircom pg 40) */
	Cmd0	= 0x08,		/* Command register */
	Rst0		= 0x09,		/* Receive status register */
	Txst0	= 0x0b,		/* Transmit status register 0 */
	Txst1	= 0x0c,		/* Transmit status register 1 */
	Rx0Msk	= 0x0d,		/* Receive status mask register */
	Tx0Msk	= 0x0e,		/* Transmit status 0 mask register */
	Tx1Msk	= 0x0f,		/* Transmit status 1 mask register */
};

enum { /* Cmd0 bits (Xircom pg 68)*/
	Cmd0_Tx			= 0x01,		/* CE2 only */
	Cmd0_RxEnable	= 0x04,	
	Cmd0_RxDisable	= 0x08,
	Cmd0_Abort		= 0x10,		/* CE2 only */
	Cmd0_Online		= 0x20,
	Cmd0_AckIntr		= 0x40,		/* CE2 only */
	Cmd0_Offline		= 0x80,	
};


enum { /* page 0x42 (Xircom pg 40) */
	Swc0	= 0x08,		/* Software configuration 0 */
	Swc1	= 0x09,		/* Software configuration 1 */
	Boc		= 0x0a,		/* Back-off configuration */
	Tcd		= 0x0b,		/* Transmit collision deferral */
};

enum { /* Swc0 bits (Xircom pg 73)  */
	Swc0_LBEnable	= 0x01,	
	Swc0_LBSource	= 0x02,
	Swc0_AcceptError	= 0x04,
	Swc0_AcceprShort	= 0x08,	
	Swc0_NoCrc		= 0x40,	
};

enum { /* Swc1 bits (Xircom pg 74) */
	Swc1_AddrEnable		= 0x01,		/* Enable individual address filters */
	Swc1_PromiscuousMulti	= 0x02,		/* Accept all multicast packets */
	Swc1_Promiscuous		= 0x04,		/* Accept all non-multicast packets */
	Swc1_BcastDisable		= 0x08,		/* Reject broadcast packets */
	Swc1_MediaSelect		= 0x40,		/* media select (Mohawk only) */
	Swc1_AutoMedia		= 0x80,		/* Auto media select (Mohawk only) */
};

enum { /* page 50 bits */
	Etheraddr_start 	= 0x08,
	Etheraddr_end		= 0x0d,
};

static void
attach(Ether *ether)
{
	Ctlr* ctlr;

	ctlr = ether->ctlr;
	ilock(ctlr);

	if (DEBUG) print("xircom: attach\n");

	if (ctlr->attached) {
		iunlock(ctlr);
		return;
	}
	chipenable(ether);
	mediaselect(ctlr);

	ctlr->attached = 1;
	iunlock(ctlr);
}

static void
txstart(Ether* ether)
{
	Ctlr* ctlr;
	Block *bp; 
	long len, frees;
	
	ctlr = ether->ctlr;
	if (DEBUG) print("xircom: txstart\n");

	if (ctlr->txbp) {
		bp = ctlr->txbp;
		ctlr->txbp = 0;
	} else {
		bp = qget(ether->oq);
		if (bp == 0)
			return;
	}
	len = BLEN(bp);

	/* Check transmit buffer space */
	PageSelect(ctlr,0);
	csr16w(ctlr, Tso2, len+2);
	frees = csr16r(ctlr, Tso0) & 0x7fff;
	if (frees <= len + 2) 
		print("no buffer space! (%ld)\n",frees);

	/* Send packet length to card */
	csr16w(ctlr, Edp, len);

	/* Write packet to card  */
	csr8sw(ctlr, Edp, bp->rp, len);
	
	if(TXDEBUG > 1) txreg_dump(ctlr);

	/* mohawk only */
	csr8w(ctlr, Cr, Cr_TxPacket|Cr_EnableIntr);
	ether->outpackets++;

	if(TXDEBUG > 1) txreg_dump(ctlr);

	freeb(bp);
}

static void
receive(Ether* ether)
{
	int len;
	Ctlr* ctlr;
	Block *bp;

	ctlr = ether->ctlr;
	if (RXDEBUG) print("xircom: receive\n");
	
	PageSelect(ctlr,0);

	if (csr8r(ctlr, Rsr) & Rsr_RxOk) {

		len = plength(ether);

		bp = iallocb(len);
		if (RXDEBUG > 1) rxreg_dump(ctlr);

		csr8sr(ctlr, Edp, bp->rp, len);
       		bp->wp = bp->rp + len;

		etheriq(ether, bp, 1);
		ether->inpackets++;

		PageSelect(ctlr,0);
		csr16w(ctlr, Do0, Do_SkipPacket);
	
		delay(1); /* need to wait at least 500ns */
	} else {
		ctlr->rxbad++;
	}
	if ( csr8r(ctlr, Rsr) & Rsr_CrcError ) {
		ether->crcs++;
	}
	if ( csr8r(ctlr, Rsr) & Rsr_LongPacket ) {
		ctlr->longpacket++;
	}
}

static void
txerror(Ether* ether)
{
	USED(ether);
	print("xircom: txerror\n");
}

static void
transmit(Ether* ether)
{
	Ctlr *ctlr;
	if(TXDEBUG) print("xircom: transmit\n");
	ctlr = ether->ctlr;
	ilock(ctlr);
	txstart(ether);
	iunlock(ctlr);
}

static void
interrupt(Ureg*, void *arg)
{
	uint int_status, rx_status, tx_status;
	Ctlr* ctlr;
	Ether* ether;

	ether = arg;
	ctlr = ether->ctlr;

	int_status = csr8r(ctlr, Isr0);
	if (IDEBUG) print("xircom: interrupt %2.2ux\n",int_status);
	while(int_status) {
		ctlr->interrupts++;

		if(ctlr->mohawk) 
			csr8w(ctlr, Cr, Cr_DisableIntr);

		PageSelect(ctlr,0x40);
    		rx_status  = csr8r(ctlr, Rst0);
		USED(rx_status);
    		//csr8w(ctlr, Rst0, (~rx_status & 0xff));
    		tx_status = csr8r(ctlr, Txst0);
    		tx_status |= csr8r(ctlr, Txst1) << 8;
		USED(tx_status);
    		//csr8w(ctrl, Txst0, 0);
    		//csr8w(ctrl, Txst1, 0);

		/* transmit */
		if (int_status & Isr_TxPacket) {
			if(TXDEBUG) print("transmit intr\n");
		}

	
		/* receive */
		while(csr8r(ctlr, Esr) & Esr_RxFull ) 
			receive(ether);

		if (int_status & Isr_TxOverflow) {
			ctlr->txover++;
		}
		if (int_status & Isr_RxReject) {
			ctlr->rxreject++;
		}
		if (int_status & Isr_MacIntr) {
			ctlr->macintr++;
		}

		if(ctlr->mohawk) 
			csr8w(ctlr, Cr, Cr_EnableIntr); 
	
		 int_status = csr8r(ctlr, Isr0);
	}
}

static void
promiscuous(void* arg, int on)
{
	USED(arg);
	USED(on);
	print("xircom: promiscuous\n");
}

static void
multicast(void* arg, uchar *addr, int on)
{
	USED(arg);
	USED(addr);
	USED(on);
	print("xircom: multicast\n");
}

static void
statistics(Ether* ether)
{
	USED(ether);
}

static long
ifstat(Ether* ether, void* a, long n, ulong offset)
{
	char *p;
	int len;
	Ctlr *ctlr;

	if(n == 0)
		return 0;

	ctlr = ether->ctlr;

	ilock(ctlr);
	statistics(ether);
	iunlock(ctlr);

	p = malloc(READSTR);

	len = snprint(p, READSTR, "SiliconRev: %d\n", ctlr->srev);
	len += snprint(p+len, READSTR-len, "Port: %2.2ux\n", ctlr->port);
	PageSelect(ctlr,4);
	len += snprint(p+len, READSTR-len, "Bv: %2.2ux\n", csr8r(ctlr, Bv));
	PageSelect(ctlr, 0x10);
	len += snprint(p+len, READSTR-len, "DingoID: %2.2ux\n", csr16r(ctlr, DingoID));
	len += snprint(p+len, READSTR-len, "RevID: %2.2ux\n",csr16r(ctlr, RevID));
	len += snprint(p+len, READSTR-len, "VendorID: %2.2ux\n",csr8r(ctlr, VendorID));
	len += snprint(p+len, READSTR-len, "Speed: %d\n", ctlr->speed);
	len += snprint(p+len, READSTR-len, "Duplex: %d\n", ctlr->duplex);
	len += snprint(p+len, READSTR-len, "Interrupts: %uld\n", ctlr->interrupts);
	len += snprint(p+len, READSTR-len, "TxPackets: %d\n", ether->outpackets);
	len += snprint(p+len, READSTR-len, "RxPackets: %d\n", ether->inpackets);
	len += snprint(p+len, READSTR-len, "TxOverrun: %uld\n", ctlr->txover);
	len += snprint(p+len, READSTR-len, "RxReject: %uld\n", ctlr->rxreject);
	len += snprint(p+len, READSTR-len, "RxBad : %uld\n", ctlr->rxbad);
	len += snprint(p+len, READSTR-len, "MacIntr: %uld\n", ctlr->macintr);
	len += snprint(p+len, READSTR-len, "CrcErrors: %d\n", ether->crcs);
	len += snprint(p+len, READSTR-len, "RxLongPacket: %uld\n", ctlr->longpacket);
	len += snprint(p+len, READSTR-len, "RxBufferWrap: %uld\n", ctlr->rxwrap);
	len += snprint(p+len, READSTR-len, "RxBadLength: %uld\n", ctlr->rxbadlen);
	len += snprint(p+len, READSTR-len, "RxOkLength: %uld\n", ctlr->rxoklen);
	USED(len);
	n = readstr(offset, a, n, p);
	free(p);

	return n;
}

static int
reset(Ether* ether)
{
	int port, slot;
	char *type;
	Ctlr* ctlr;
	uchar ea[Eaddrlen];

	if (ether->irq == 0)
		ether->irq = 9;

	if (ether->port == 0)
		ether->port = 0x300;

	ether->mem = 0x0;

	if (ether->size== 0)
		ether->size = 0x1000;
	
	slot = getslot(ether, &type);

	ether->ctlr = malloc(sizeof(Ctlr));
	ctlr = ether->ctlr;
	port = ether->port;
	ctlr->port = ether->port;
	ctlr->slot = slot;

	findmodel(ether, type);
	findspeed(ether);

	if(ioalloc(port, 0x10, 0, "xircom") < 0)
		return -1;

	if(slot < 0){
		print(" no slot!\n");
		iofree(port);
		return -1;
	}

	if (ctlr == 0) {
		print(" no ctlr!\n");
		iofree(ether->port);
		pcmspecialclose(slot);
	}
	ilock(ctlr);

	memset(ea, 0, Eaddrlen);
	if (memcmp(ea, ether->ea, Eaddrlen) == 0) {
		if (readnodeid(ether) < 0) {
			print("xircom: cannot find ethernet address\n");
			iunlock(ctlr);
			iofree(port);
			pcmspecialclose(slot);
			return -1;
		}
	}

	chipreset(ctlr);

	/* extra stuff for dingo (someday...) */
	if (ctlr->dingo) {
		config_dingo(ether);
	} else {
		config_mohawk(ether);
	}


	ctlr->interrupts = 0;
	ctlr->txover = 0;
	ctlr->rxreject = 0;
	ctlr->macintr = 0;
	ctlr->rxbad= 0;
	ctlr->longpacket = 0;
	ctlr->rxwrap = 0;
	ctlr->rxbadlen = 0;
	ctlr->rxoklen = 0;
	ether->inpackets = 0;
	ether->outpackets = 0;
	ether->crcs = 0;
	ether->attach = attach;
	ether->transmit = transmit;
	ether->interrupt = interrupt;
	ether->ifstat = ifstat;
	ether->promiscuous = promiscuous;
	ether->multicast = multicast;
	ether->arg = ether;
	ether->mbps = ctlr->speed;
	iunlock(ctlr);

	return 0;
}

void
etherxircomlink(void)
{
	addethercard("xircom", reset);
}


/*============== support functions ===============*/

static void
config_mohawk(Ether *ether)
{
	Ctlr *ctlr;
	PCMmap *m;
	uchar *p;

	ctlr = ether->ctlr;

	m = pcmmap(ctlr->slot, Dingo_Ecor, 1 ,1);
	p = (uchar *) KADDR(m->isa + Dingo_Ecor - m->ca);
	*p = Dingo_Ecor_IrqLevel | Dingo_Ecor_IntEnable | Dingo_Ecor_IobEnable | Dingo_Ecor_EthEnable;
	*(p+2) = 0x0;
	pcmunmap(ctlr->slot, m);
}


static void
config_dingo(Ether *ether)
{
	Ctlr *ctlr;
	PCMmap *m;
	uchar *p;
	
	ctlr = ether->ctlr;

	m = pcmmap(ctlr->slot, 0xff80, 1 ,1);
	p = (uchar *) KADDR(m->isa + 0xff80 - m->ca);
	print("mio %2.2ux\n",*(p+0x0a));
	print("mio2 %2.2ux\n",*(p+0x0c));
	*(p+0x0a) = ctlr->port & 0xff;
	*(p+0x0c) = (ctlr->port >> 8) & 0xff;
	*p = 0x47;
	pcmunmap(ctlr->slot, m);
	

	m = pcmmap(ctlr->slot, Dingo_Ecor, 1 ,1);
	p = (uchar *) KADDR(m->isa + Dingo_Ecor - m->ca);
	*p = Dingo_Ecor_IrqLevel | Dingo_Ecor_IntEnable | Dingo_Ecor_IobEnable | Dingo_Ecor_EthEnable;
	print("ECOR %2.2ux\n",*p);
	*(p+2) = 0x0;
	print("ECSR %2.2ux\n",*(p+2));
	print("io %2.2ux\n",*(p+0x0a));
	print("io2 %2.2ux\n",*(p+0x0c));
	*(p+0x0a) = ctlr->port & 0xff;
	*(p+0x0c) = (ctlr->port >> 8) & 0xff;
	print("io %2.2ux\n",*(p+0x0a));
	print("io2 %2.2ux\n",*(p+0x0c));
	pcmunmap(ctlr->slot, m);

	m = pcmmap(ctlr->slot, Dingo_Dcor0, 1 ,1);
	p = (uchar *) KADDR(m->isa + Dingo_Dcor0 - m->ca);
	*p = 0x01;
	*(p+2) = 0x0c;
	*(p+4) = 0x0;
	*(p+6) = 0x0;
	*(p+8) = 0x0;
	pcmunmap(ctlr->slot, m);
	softreset(ctlr);
}


/* this is a horrible hack because Rbc1 is sometimes bogus */ 
static uint
plength(Ether* ether)
{	
	int len;
	Ctlr *ctlr;
	ctlr = ether->ctlr;

	PageSelect(ctlr, 0);

	if( csr8r(ctlr, Rbc1) && 0xff) {
		/* high byte is garbage, try sick workaround */
		PageSelect(ctlr, 5);

		/* use Rnsa0 and Rhsa0 pointers to find length */
		len = csr16r(ctlr, Rnsa0) - csr16r(ctlr, Rhsa0) - 3;
		PageSelect(ctlr, 0);

		/* ick, try to deal with wraparound */
		if ( len < 0) {
			ctlr->rxwrap++;
			len = 1024*24+len; /* ugly we set 24k for rx buffer */
			if (RXDEBUG) print("bad packet length, trying %d\n",len);
		}
		if ( len < 0 || len > 8*1024) {
			ctlr->rxbadlen++;	
			len =  csr8r(ctlr, Rbc0); /* really ugly */
			if (RXDEBUG) print("double bad packet length, trying %d\n",len);
		}
	} else {
		ctlr->rxoklen++;
		len = csr8r(ctlr, Rbc0);
 		len |= (csr8r(ctlr, Rbc1) & 0x1f) << 8;
	}
	return(len);
}



/*============== intr enable/disable functions ===============*/

static void
enable_intr(Ctlr* ctlr)
{
	PageSelect(ctlr,1);
	csr8w(ctlr, Imr0, 0xff);			/* Unmask everything */
  	csr8w(ctlr, Imr1, 0x01);			/* Unmask TX underrun detection */
  	delay(1);

	PageSelect(ctlr,0);
	csr8w(ctlr, Cr, Cr_EnableIntr);	/* Enable interrupts */
}

static void
disable_intr(Ctlr* ctlr)
{
	PageSelect(ctlr, 0);
	csr8w(ctlr, Cr, Cr_DisableIntr);	/* disable intrrupts */
	PageSelect(ctlr,1);
	csr8w(ctlr, Imr0, 0);		 		/* forbid interrupts */
	csr8w(ctlr, Imr1, 0);	
	PageSelect(ctlr,0);
}


/*============== chip reset/enable functions ===============*/

static void
chipreset(Ctlr* ctlr)
{
	hardreset(ctlr);
	softreset(ctlr);
	if (DEBUG) reg_dump(ctlr);
}


static void
hardreset(Ctlr* ctlr)
{
	PageSelect(ctlr, 4);
	delay(1);
	csr8w(ctlr, Gp1, 0);	/* clear bit 0: power down */
	delay(40);		    
	csr8w(ctlr, Gp1, 1);	/* set bit 0: power up */
	delay(20);		   
	PageSelect(ctlr, 0);
}

static void
softreset(Ctlr* ctlr)
{
	PageSelect(ctlr, 0);
	csr8w(ctlr, Cr, Cr_SoftReset); 
	delay(20);  			
	csr8w(ctlr, Cr, 0);     /* clear reset */
	delay(40);
	PageSelect(ctlr, 4);
  
	if (ctlr->mohawk) {
		/* set GP1 and GP2 as outputs
		* set GP1 low to power on the ML6692
		* set GP2 high to power on the 10Mhz chip
		*/
		csr8w(ctlr, Gp0, Gp0_Gp1Select|Gp0_Gp2Select|Gp0_Gp2Out);
	}
	delay(500);	/* circuits need time to power up */
	
	/* get rev info */
	if (ctlr->mohawk)
		ctlr->srev = (csr8r(ctlr, Bv) & 0x70) >> 4;
	else 
		ctlr->srev =  (csr8r(ctlr, Bv) & 0x30) >> 4;

	/* turn off 2nd function interrupt */
	csr8w(ctlr, Gir, Gir_SfMask);

	/* disable_intr(ctlr);  why? */

	PageSelect(ctlr, 0);

}

/* very simple, no auto detection yet */
static void
mediaselect(Ctlr* ctlr)
{
	if (ctlr->speed == 10) {
		print("media: 10BT ");
	  	PageSelect(ctlr, 0x42);
		csr8w(ctlr, Swc1, Swc1_MediaSelect); //enable 10BT
	} else if ( ctlr->speed == 100) {
		print("media: 100BT ");
	} else {
		print("bad media");
	}
	
	delay(40);
	if (ctlr->duplex) {
		PageSelect(ctlr, 1);
		print("full duplex\n");
 		csr8w(ctlr, Ecr, csr8r(ctlr, Ecr | Ecr_FullDuplex));
		delay(40);
	} else {
		print("half duplex\n");
	}
	PageSelect(ctlr, 0);
}

static void
setaddrs(Ether* ether)
{
	int i, j = 0;
	Ctlr *ctlr;

	ctlr = ether->ctlr;

	PageSelect(ctlr,0x50);
	for(i = Etheraddr_end; i >= Etheraddr_start; i--) {
		csr8w(ctlr,i,ether->ea[j]);
		j++;
	}
}	

static void
chipenable(Ether* ether)
{
	Ctlr *ctlr;

	ctlr = ether->ctlr;

#ifdef _ONLYTEST_
	/* compat mode hack */
	PageSelect(ctlr, 1);
 	csr8w(ctlr, Ecr, Ecr_CompatMode); 
	print("Ecr register ");
	printbits(csr8r(ctlr, Ecr));
	print("\n");
#endif

 	PageSelect(ctlr, 0x42);
 	csr8w(ctlr, Swc0, 0x20);	/* Disable source insertion  */
  
  /*
   * Set the 'local memory dividing line' -- splits the 32K card memory into
   * 8K for transmit buffers and 24K for receive.  This is done automatically
   * on newer revision cards.
   */
  	if (ctlr->srev != 1) {
		PageSelect(ctlr, 2);
		csr16w(ctlr, Rbs0, 0x2000);
 	}
	setaddrs(ether);

	/* Fix the data offset register -- reset leaves it off-by-one */
  	PageSelect(ctlr, 0);
  	csr16w(ctlr, Do0, Do_ChangeOffset); 


 	/* Set MAC interrupt masks and clear status regs */
 	PageSelect(ctlr, 0x40);	
	csr8w(ctlr, Rx0Msk, 0xff);
	csr8w(ctlr, Tx0Msk, 0xff);
	if (!(ctlr->dingo)) 
		csr8w(ctlr, Tx1Msk, 0xb0);
	csr8w(ctlr, Rst0, 0x00);
	csr8w(ctlr, Txst0, 0x00);
	csr8w(ctlr, Txst1, 0x00);

	if(mii_init(ctlr)) { 
		if (MIIDEBUG) print("MII detected\n");
		PageSelect(ctlr, 2);   
		csr8w(ctlr, Msr, csr8r(ctlr, Msr) | Msr_MiiSelect);	/* use mii */
		delay(20);
	} else {
		if (MIIDEBUG) print("no MII detected\n");
		if (ctlr->mohawk) {
			print("MII error: trying to continue\n");
			PageSelect(ctlr, 2);   
			csr8w(ctlr, Msr, Msr_MiiSelect); /* try to use mii anyhow */
			delay(20);
	  		//PageSelect(ctlr, 0x42);
			//csr8w(ctlr,Swc1, Swc1_AutoMedia); /* auto media detect: not working yet */
		} else {
			print("card not supported\n");
		}
		delay(50);
	}
		
	if (ctlr->dingo) {
 		PageSelect(ctlr,2);
		csr8w(ctlr, Led, 0x3b); 
		csr8w(ctlr, Led3, 0x04);  /* dingo 100 Mbit LED */
	}

  	/* Enable receiver, put MAC online */
 	PageSelect(ctlr, 0x40);
    	csr8w(ctlr, Cmd0, Cmd0_RxEnable|Cmd0_Online);
	
	enable_intr(ctlr);
	PageSelect(ctlr, 0);

	/* dingo modem magic */
	if(ctlr->dingo) {
		if(!(csr8r(ctlr,0x10) & 0x01)) {
			print("dingo magic\n");
			csr8w(ctlr, 0x10, 0x11);
		}
	}

	PageSelect(ctlr, 0);
}





/*============== PCMCIA/CIS functions ===============*/

static void
findmodel(Ether *ether, char *type)
{
	Ctlr* ctlr;	

	ctlr = ether->ctlr;
	if(cistrcmp(type, "CEM56") == 0){
		print("type: dingo, watch out for baby\n");
		ctlr->dingo = 1; 
		ctlr->mohawk = 1;
	} else {
		print("type: mohawk\n");
		ctlr->dingo = 0;
		ctlr->mohawk = 1;
	}
}

static void
findspeed(Ether *ether)
{
	Ctlr* ctlr;	
	int i, k, media;
	char *p;

	ctlr = ether->ctlr;

	media = 2;  /* 100BT half duplex default */
	for(i = 0; i < ether->nopt; i++) {
		if(cistrncmp(ether->opt[i], "media=", 6) != 0)
			continue;
		p = ether->opt[i]+6;
		for(k = 0; k< nelem(mediatable); k++) {
			if(cistrcmp(p, mediatable[k]) == 0)
				media = k;	
		}
	}
	print("media type: %d\n",media);
	switch(media){
	default:
		break;

	case 0x00:			/* 10BASE-T */
		ctlr->speed=10;
		ctlr->duplex=0;
		break;
	case 0x01:			/* 10BASE-TFD */
		ctlr->speed=10;
		ctlr->duplex=1;
		break;
	case 0x02:			/* 100BASE-TX */
		ctlr->speed=100;
		ctlr->duplex=0;
		break;
	case 0x03:			/* 100BASE-TXFD */
		ctlr->speed=100;
		ctlr->duplex=1;
		break;
	}
}

static int
getslot(Ether *ether, char **type)
{
	int i, slot;

	slot = -1;
	*type = nil;
	for(i = 0; xircompcmcia[i] != nil; i++){
		*type = xircompcmcia[i];
		if((slot = pcmspecial(*type, ether)) >= 0)
			break;
	}
	if(xircompcmcia[i] == nil){
		for(i = 0; i < ether->nopt; i++){
			if(cistrncmp(ether->opt[i], "id=", 3))
				continue;
			*type = &ether->opt[i][3];
			if((slot = pcmspecial(*type, ether)) >= 0)
				break;
		}
	}
	return slot;
}


/* get MAC addr from CIS */
static int
readnodeid(Ether* ether)
{
	Ctlr* ctlr;
	uchar data[Eaddrlen + 1];
	int len;
	ctlr = ether->ctlr;

	len = sizeof(data);
	if (pcmcistuple(ctlr->slot, TupleFunce, 0x4, data, len) != len)
		return -1;

	if (data[0] != Eaddrlen)
		return -1;

	memmove(ether->ea, &data[1], Eaddrlen);
	return 0;
}

/*============== Debugging functions ===============*/


void
printbits(uint x) 
{
	int i;
	for(i = 7; i >=0 ; i--) {
		print("%d",(x>>i) & ~(~0<<1));
	}
}


static void
reg_dump(Ctlr *ctlr) 
{
	print("port=%2.2ux\n",ctlr->port);
	PageSelect(ctlr,4);
	print("Bv=%2.2ux (should be 0x45/0x55) ",csr8r(ctlr, Bv));
	printbits(csr8r(ctlr, Bv));
	print("\n");
	print("page=%2.2ux (should be 4)\n",csr8r(ctlr, Pr));
	PageSelect(ctlr, 0x10);
	print("DingoID=%2.2ux (should be 0x444B)\n",csr16r(ctlr, DingoID));
	print("page=%2.2ux (should be 10)\n",csr8r(ctlr, Pr));
	print("RevID=%2.2ux (should be 0x0001)\n",csr16r(ctlr, RevID));
	print("VendorID=%2.2ux (0 for X1601-2)\n",csr8r(ctlr, VendorID));
}

static void
txreg_dump(Ctlr *ctlr)
{	
	PageSelect(ctlr, 6);
	print("Ctha=%4.4ux ", csr16r(ctlr, Ctha0));
	print("Thsa=%4.4ux ", csr16r(ctlr, Thsa0));
	print("Tnsa=%4.4ux ", csr16r(ctlr, Tnsa0));
	print("Ctna=%4.4ux\n", csr16r(ctlr, Ctna0));
	PageSelect(ctlr, 8);
	print("Thbc=%4.4ux ", csr16r(ctlr, Thbc0));
	print("Thps=%4.4ux ", csr16r(ctlr, Thps0));
	print("Tnbc=%4.4ux ", csr16r(ctlr, Tnbc0));
	print("Tnps=%4.4ux\n", csr16r(ctlr, Tnps0));
	PageSelect(ctlr, 0);
}

static void
rxreg_dump(Ctlr *ctlr)
{	
	PageSelect(ctlr, 5);
	print("Crha=%4.4ux ", csr16r(ctlr, Crha0));
	print("Rhsa=%4.4ux ", csr16r(ctlr, Rhsa0));
	print("Rnsa=%4.4ux ", csr16r(ctlr, Rnsa0));
	print("Crna=%4.4ux ", csr16r(ctlr, Crna0));
	print("Rnsa-Rhsa=%d ", csr16r(ctlr, Rnsa0)-csr16r(ctlr, Rhsa0));
	print("Rnsa-Crha=%d\n", csr16r(ctlr, Rnsa0)-csr16r(ctlr, Crha0));
	PageSelect(ctlr, 0);
}


/*============== MII Management functions ===============*/

/*
 * The mmi functions were derived from Werner Koch's xirc2ps driver
 * for Linux under the following license:
 *
 * Copyright (c) 1997, 1998 by Werner Koch (dd9jn)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.	IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

static void
mii_idle(Ctlr *ctlr)
{
	PageSelect(ctlr, 2);
	csr8w(ctlr, Gp2, 0x04|0); /* drive MDCK low */
	delay(1);
	csr8w(ctlr, Gp2, 0x04|1); /* and drive MDCK high */
	delay(1);
}


static void
mii_putbit(Ctlr *ctlr, uint data)
{
	PageSelect(ctlr, 2);
	if (data) {
		csr8w(ctlr, Gp2, 0x0c|2|0); /* set MDIO */
		delay(1);
		csr8w(ctlr, Gp2, 0x0c|2|1); /* and drive MDCK high */
		delay(1);
	} else {
		csr8w(ctlr, Gp2, 0x0c|0|0); /* clear MDIO */
		delay(1);
		csr8w(ctlr, Gp2, 0x0c|0|1); /* and drive MDCK high */
		delay(1);
    	}
}


static int
mii_getbit(Ctlr *ctlr)
{
	uint d;

	PageSelect(ctlr, 2);
    	csr8w(ctlr, Gp2, 4|0); /* drive MDCK low */
    	delay(1);
    	d = csr8r(ctlr, Gp2); /* read MDIO */
    	csr8w(ctlr, Gp2, 4|1); /* drive MDCK high again */
    	delay(1);
    	return d & 0x20; /* read MDIO */
}

static void
mii_wbits(Ctlr *ctlr, uint data, int len)
{
	uint m = 1 << (len-1);
	for (; m; m >>= 1)
		mii_putbit(ctlr, data & m);
}

static uint
mii_rd(Ctlr *ctlr, uchar phyaddr, uchar phyreg)
{
	int i;
	uint data = 0, m;

	PageSelect(ctlr, 2);

    	for (i=0; i < 32; i++)		/* 32 bit preamble */
		mii_putbit(ctlr, 1);
	mii_wbits(ctlr, 0x06, 4); 	/* Start and opcode for read */
	mii_wbits(ctlr, phyaddr, 5);	/* PHY address to be accessed */
	mii_wbits(ctlr, phyreg, 5);	/* PHY register to read */
	mii_idle(ctlr);			/* turn around */
	mii_getbit(ctlr);

	for (m = 1<<15; m; m >>= 1)
		if (mii_getbit(ctlr))
			data |= m;
	mii_idle(ctlr);
	return data;
}

static void
mii_wr(Ctlr *ctlr, uchar phyaddr, uchar phyreg, uint data, int len)
{
	int i;

	PageSelect(ctlr, 2);
	for (i=0; i < 32; i++)		/* 32 bit preamble */
		mii_putbit(ctlr, 1);
	mii_wbits(ctlr, 0x05, 4); 	/* Start and opcode for write */
	mii_wbits(ctlr, phyaddr, 5);	/* PHY address to be accessed */
	mii_wbits(ctlr, phyreg, 5);	/* PHY Register to write */
	mii_putbit(ctlr, 1);		/* turn around */
	mii_putbit(ctlr, 0);
	mii_wbits(ctlr, data, len);	/* And write the data */
	mii_idle(ctlr);
}

static int
mii_init(Ctlr *ctlr)
{
	uint val = 1;
	uint new_mii, control, status;

    	status = mii_rd(ctlr,  0, 1);
    	if ((status & 0xff00) != 0x7800) {
		print("%ux no MII!\n",(status & 0xff00));
		val = 0; /* No MII */
	}

   	new_mii = (mii_rd(ctlr, 0, 2) != 0xffff);
	print("new mii %ud\n",new_mii);

	if (ctlr->speed == 10) {
		control = 0x0000; /* no auto neg, 10mbs mode */
	} else if (ctlr->speed == 100) {
		control = 0x2000; /* no auto neg, 100mbs mode */
	} else {
		control = 0x0000;
		print("bad media\n");
	}
 
	mii_wr(ctlr,  0, 0, control, 16);
	delay(100);
	control = mii_rd(ctlr, 0, 0);
	
	if (control & 0x0400) {
		print("can't take PHY out of isolation mode %ux\n",control);
		val = 0;
   	 }

	PageSelect(ctlr, 0);
    	return val;
}
