/* $Id: eexpress.c,v 1.1 1999/04/26 05:52:09 tb Exp $
 *
 * Intel EtherExpress device driver for Linux
 *
 * Original version written 1993 by Donald Becker
 * Modularized by Pauline Middelink <middelin@polyware.iaf.nl>
 * Changed to support io= irq= by Alan Cox <Alan.Cox@linux.org>
 * Reworked 1995 by John Sullivan <js10039@cam.ac.uk>
 * More fixes by Philip Blundell <pjb27@cam.ac.uk>
 * Added the Compaq LTE  Alan Cox <alan@redhat.com>
 *
 * Note - this driver is experimental still - it has problems on faster
 * machines. Someone needs to sit down and go through it line by line with
 * a databook...
 */

/*
 * The original EtherExpress driver was just about usable, but
 * suffered from a long startup delay, a hard limit of 16k memory
 * usage on the card (EtherExpress 16s have either 32k or 64k),
 * and random locks under load. The last was particularly annoying
 * and made running eXceed/W preferable to Linux/XFree. After hacking
 * through the driver for a couple of days, I had fixed most of the
 * card handling errors, at the expense of turning the code into
 * a complete jungle, but still hadn't tracked down the lock-ups.
 * I had hoped these would be an IP bug, but failed to reproduce them
 * under other drivers, so decided to start from scratch and rewrite
 * the driver cleanly. And here it is.
 *
 * It's still not quite there, but self-corrects a lot more problems.
 * the 'CU wedged, resetting...' message shouldn't happen at all, but
 * at least we recover. It still locks occasionally, any ideas welcome.
 *
 * The original startup delay experienced by some people was due to the
 * first ARP request for the address of the default router getting lost.
 * (mostly the reply we were getting back was arriving before our
 * hardware address was set up, or before the configuration sequence
 * had told the card NOT to strip of the frame header). If you a long
 * startup delay, you may have lost this ARP request/reply, although
 * the original cause has been fixed. However, it is more likely that
 * you've just locked under this version.
 *
 * The main changes are in the 586 initialization procedure (which was
 * just broken before - the EExp is a strange beasty and needs careful
 * handling) the receive buffer handling (we now use a non-terminating
 * circular list of buffers, which stops the card giving us out-of-
 * resources errors), and the transmit code. The driver is also more
 * structured, and I have tried to keep the kernel interface separate
 * from the hardware interface (although some routines naturally want
 * to do both).
 *
 * John Sullivan
 *
 * 18/5/95:
 *
 * The lock-ups seem to happen when you access card memory after a 586
 * reset. This happens only 1 in 12 resets, on a random basis, and
 * completely locks the machine. As far as I can see there is no
 * workaround possible - the only thing to be done is make sure we
 * never reset the card *after* booting the kernel - once at probe time
 * must be sufficient, and we'll just have to put up with that failing
 * occasionally (or buy a new NIC). By the way, this looks like a 
 * definite card bug, since Intel's own driver for DOS does exactly the
 * same.
 *
 * This bug makes switching in and out of promiscuous mode a risky
 * business, since we must do a 586 reset each time.
 */

/*
 * Sources:
 *
 * The original eexpress.c by Donald Becker
 *   Sources: the Crynwr EtherExpress driver source.
 *            the Intel Microcommunications Databook Vol.1 1990
 *
 * wavelan.c and i82586.h
 *   This was invaluable for the complete '586 configuration details
 *   and command format.
 *
 * The Crynwr sources (again)
 *   Not as useful as the Wavelan driver, but then I had eexpress.c to
 *   go off.
 *
 * The Intel EtherExpress 16 ethernet card
 *   Provided the only reason I want to see a working etherexpress driver.
 *   A lot of fixes came from just observing how the card (mis)behaves when
 *   you prod it.
 *
 */

static char version[] = 
"eexpress.c: v0.10 04-May-95 John Sullivan <js10039@cam.ac.uk>\n"
"            v0.14 19-May-96 Philip Blundell <phil@tazenda.demon.co.uk>\n"
"            v0.15 04-Aug-98 Alan Cox <alan@redhat.com>\n";

#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/string.h>
#include <linux/in.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/io.h>
#include <asm/dma.h>
#include <linux/delay.h>
#include <linux/errno.h>

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/malloc.h>

/*
 * Not actually used yet - may be implemented when the driver has
 * been debugged!
 *
 * Debug Level		Driver Status
 *	0		Final release
 *	1		Beta test
 *	2
 *	3
 * 	4		Report timeouts & 586 errors (normal debug level)
 *	5		Report all major events
 *	6		Dump sent/received packet contents
 *	7		Report function entry/exit
 */

#ifndef NET_DEBUG
#define NET_DEBUG 4
#endif
static unsigned int net_debug = NET_DEBUG;

#undef F_DEB

#include "eth82586.h"

#define PRIV(x)         ((struct net_local *)(x)->priv)
#define EEXP_IO_EXTENT  16

/*
 * Private data declarations
 */

struct net_local 
{
	struct enet_statistics stats;
	unsigned long init_time;        /* jiffies when eexp_hw_init586 called */
	unsigned short rx_first;        /* first rx buf, same as RX_BUF_START */
	unsigned short rx_last;         /* last rx buf */
	unsigned short tx_head;         /* next free tx buf */
	unsigned short tx_reap;         /* first in-use tx buf */
	unsigned short tx_tail;         /* previous tx buf to tx_head */
	unsigned short tx_link;         /* last known-executing tx buf */
	unsigned short last_tx_restart; /* set to tx_link when we restart the CU */
	unsigned char started;
	unsigned char promisc;
	unsigned short rx_buf_start;
	unsigned short rx_buf_end;
	unsigned short num_tx_bufs;
	unsigned short num_rx_bufs;
};

unsigned short start_code[] = {
	0x0000,                 /* SCP: set bus to 16 bits */
	0x0000,0x0000,          /* junk */
	0x0000,0x0000,          /* address of ISCP (lo,hi) */

	0x0001,                 /* ISCP: busy - cleared after reset */
	0x0008,0x0000,0x0000,   /* offset,address (lo,hi) of SCB */

	0x0000,0x0000,          /* SCB: status, commands */
	0x0000,0x0000,          /* links to first command block, first receive descriptor */
	0x0000,0x0000,          /* CRC error, alignment error counts */
	0x0000,0x0000,          /* out of resources, overrun error counts */

	0x0000,0x0000,          /* pad */
	0x0000,0x0000,

	0x0000,Cmd_Config,      /* startup configure sequence, at 0x0020 */
	0x0032,                 /* link to next command */
	0x080c,                 /* 12 bytes follow : fifo threshold=8 */
	0x2e40,                 /* don't rx bad frames : SRDY/ARDY => ext. sync. : preamble len=8
	                         * take addresses from data buffers : 6 bytes/address */
	0x6000,                 /* default backoff method & priority : interframe spacing = 0x60 */
	0xf200,                 /* slot time=0x200 : max collision retry = 0xf */
	0x0000,                 /* no HDLC : normal CRC : enable broadcast : disable promiscuous/multicast modes */
	0x003c,                 /* minimum frame length = 60 octets) */

	0x0000,Cmd_INT|Cmd_SetAddr,
	0x003e,                 /* link to next command */
	0x0000,0x0000,0x0000,   /* hardware address placed here, 0x0038 */
	0x0000,Cmd_END|Cmd_Nop, /* end of configure sequence */
	0x003e,

	0x0000

};

#define CONF_LINK 0x0020
#define CONF_HW_ADDR 0x0038

/* maps irq number to EtherExpress magic value */
static char irqrmap[] = { 0,0,1,2,3,4,0,0,0,1,5,6,0,0,0,0 };

/*
 * Prototypes for Linux interface
 */

extern int                  express_probe(struct device *dev);
static int                     eexp_open (struct device *dev);
static int                     eexp_close(struct device *dev);
static struct enet_statistics *eexp_stats(struct device *dev);
static int                     eexp_xmit (struct sk_buff *buf, struct device *dev);

static void                    eexp_irq  (int irq, void *dev_addr, struct pt_regs *regs);
static void                    eexp_set_multicast(struct device *dev);

/*
 * Prototypes for hardware access functions
 */

static void           eexp_hw_rx        (struct device *dev);
static void           eexp_hw_tx        (struct device *dev, unsigned short *buf, unsigned short len);
static int            eexp_hw_probe     (struct device *dev,unsigned short ioaddr);
static unsigned short eexp_hw_readeeprom(unsigned short ioaddr, unsigned char location);

static unsigned short eexp_hw_lasttxstat(struct device *dev);
static void           eexp_hw_txrestart (struct device *dev);

static void           eexp_hw_txinit    (struct device *dev);
static void           eexp_hw_rxinit    (struct device *dev);

static void           eexp_hw_init586   (struct device *dev);
static void           eexp_hw_ASICrst   (struct device *dev);

/*
 * Linux interface
 */

/*
 * checks for presence of EtherExpress card
 */

int express_probe(struct device *dev)
{
	unsigned short *port,ports[] = { 0x0300,0x0270,0x0320,0x0340,0 };
	unsigned short ioaddr = dev->base_addr;

	if (ioaddr&0xfe00)
		return eexp_hw_probe(dev,ioaddr);
	else if (ioaddr)
		return ENXIO;

	for ( port=&ports[0] ; *port ; port++ ) 
	{
		unsigned short sum = 0;
		int i;
		for ( i=0 ; i<4 ; i++ ) 
		{
			unsigned short t;
			t = inb(*port + ID_PORT);
			sum |= (t>>4) << ((t & 0x03)<<2);
		}
		if (sum==0xbaba && !eexp_hw_probe(dev,*port)) 
			return 0;
	}
	return ENODEV;
}

/*
 * open and initialize the adapter, ready for use
 */

static int eexp_open(struct device *dev)
{
	int irq = dev->irq;
	unsigned short ioaddr = dev->base_addr;

#if NET_DEBUG > 6
	printk(KERN_DEBUG "%s: eexp_open()\n", dev->name);
#endif

	if (!irq || !irqrmap[irq]) 
		return -ENXIO;

	if (irq2dev_map[irq] ||
	      /* more consistent, surely? */
	   ((irq2dev_map[irq]=dev),0) ||
	     request_irq(irq,&eexp_irq,0,"eexpress",NULL)) 
		return -EAGAIN;

	request_region(ioaddr, EEXP_IO_EXTENT, "eexpress");
	dev->tbusy = 0;
	dev->interrupt = 0;
	eexp_hw_init586(dev);
	dev->start = 1;
	MOD_INC_USE_COUNT;
#if NET_DEBUG > 6
	printk(KERN_DEBUG "%s: leaving eexp_open()\n", dev->name);
#endif
	return 0;
}

/*
 * close and disable the interface, leaving
 * the 586 in reset
 */
static int eexp_close(struct device *dev)
{
	unsigned short ioaddr = dev->base_addr;
	int irq = dev->irq;

	dev->tbusy = 1; 
	dev->start = 0;
  
	outb(SIRQ_dis|irqrmap[irq],ioaddr+SET_IRQ);
	PRIV(dev)->started = 0;
	outw(SCB_CUsuspend|SCB_RUsuspend,ioaddr+SCB_CMD);
	outb(0,ioaddr+SIGNAL_CA);
	free_irq(irq,NULL);
	irq2dev_map[irq] = NULL;
	outb(i586_RST,ioaddr+EEPROM_Ctrl);
	release_region(ioaddr,16);
	MOD_DEC_USE_COUNT;
	return 0;
}

/*
 * Return interface stats
 */

static struct enet_statistics *eexp_stats(struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;

	/* 
	 * Hmmm, this looks a little too easy... The card maintains
	 * some stats in the SCB, and I'm not convinced we're
	 * incrementing the most sensible statistics when the card
	 * returns an error (esp. slow DMA, out-of-resources)
	 */
	return &lp->stats;
}

/*
 * Called to transmit a packet, or to allow us to right ourselves
 * if the kernel thinks we've died.
 */

static int eexp_xmit(struct sk_buff *buf, struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	unsigned short ioaddr = dev->base_addr;

#if NET_DEBUG > 6
	printk(KERN_DEBUG "%s: eexp_xmit()\n", dev->name);
#endif

	outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
	if (dev->tbusy) 
	{
		/* This will happen, but hopefully not as often as when
		 * tbusy==0. If it happens too much, we probably ought
		 * to think about unwedging ourselves...
		 */
		if (test_bit(0,(void *)&PRIV(dev)->started)) 
		{
			if ((jiffies - dev->trans_start)>5) 
			{
				if (lp->tx_link==lp->last_tx_restart) 
				{
					unsigned short boguscount=200,rsst;
					printk(KERN_WARNING "%s: Retransmit timed out, status %04x, resetting...\n",
						dev->name,inw(ioaddr+SCB_STATUS));
					eexp_hw_txinit(dev);
					lp->last_tx_restart = 0;
					outw(lp->tx_link,ioaddr+SCB_CBL);
					outw(0,ioaddr+SCB_STATUS);
					outw(SCB_CUstart,ioaddr+SCB_CMD);
					outb(0,ioaddr+SIGNAL_CA);
					while (!SCB_complete(rsst=inw(ioaddr+SCB_STATUS))) 
					{
						if (!--boguscount) 
						{
							boguscount=200;
							printk(KERN_WARNING "%s: Reset timed out status %04x, retrying...\n",
								dev->name,rsst);
							outw(lp->tx_link,ioaddr+SCB_CBL);
							outw(0,ioaddr+SCB_STATUS);
							outw(SCB_CUstart,ioaddr+SCB_CMD);
							outb(0,ioaddr+SIGNAL_CA);
						}
					}
					dev->tbusy = 0;
					mark_bh(NET_BH);
				}
				else
				{
					unsigned short status = inw(ioaddr+SCB_STATUS);
					if (SCB_CUdead(status)) 
					{
						unsigned short txstatus = eexp_hw_lasttxstat(dev);
						printk(KERN_WARNING "%s: Transmit timed out, CU not active status %04x %04x, restarting...\n",
							dev->name, status, txstatus);
						eexp_hw_txrestart(dev);
					}
					else
					{
						unsigned short txstatus = eexp_hw_lasttxstat(dev);
						if (dev->tbusy && !txstatus) 
						{
							printk(KERN_WARNING "%s: CU wedged, status %04x %04x, resetting...\n",
								dev->name,status,txstatus);
							eexp_hw_init586(dev); 
							dev->tbusy = 0;
							mark_bh(NET_BH);
						}
					}
				}
			}
		}
		else
		{
			if ((jiffies-lp->init_time)>10)
			{
				unsigned short status = inw(ioaddr+SCB_STATUS);
				printk(KERN_WARNING "%s: i82586 startup timed out, status %04x, resetting...\n",
					dev->name, status);
				eexp_hw_init586(dev);
				dev->tbusy = 0;
				mark_bh(NET_BH);
			}
		}
	}

	if (buf==NULL) 
	{
		unsigned short status = inw(ioaddr+SCB_STATUS);
		unsigned short txstatus = eexp_hw_lasttxstat(dev);
		if (SCB_CUdead(status)) 
		{
			printk(KERN_WARNING "%s: CU has died! status %04x %04x, attempting to restart...\n",
				dev->name, status, txstatus);
			lp->stats.tx_errors++;
			eexp_hw_txrestart(dev);
		}
		dev_tint(dev);
		outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
		dev_kfree_skb(buf, FREE_WRITE);
		return 0;
	}

	if (set_bit(0,(void *)&dev->tbusy)) 
	{
		lp->stats.tx_dropped++;
	}
	else
	{
		unsigned short length = (ETH_ZLEN < buf->len) ? buf->len : ETH_ZLEN;
		unsigned short *data = (unsigned short *)buf->data;

		outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
		eexp_hw_tx(dev,data,length);
		outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
	}
	dev_kfree_skb(buf, FREE_WRITE);
	outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
	return 0;
}

/*
 * Handle an EtherExpress interrupt
 * If we've finished initializing, start the RU and CU up.
 * If we've already started, reap tx buffers, handle any received packets,
 * check to make sure we've not become wedged.
 */

static void eexp_irq(int irq, void *dev_info, struct pt_regs *regs)
{
	struct device *dev = irq2dev_map[irq];
	struct net_local *lp;
	unsigned short ioaddr,status,ack_cmd;
	unsigned short old_rp,old_wp;

	if (dev==NULL) 
	{
		printk(KERN_WARNING "net_interrupt(): irq %d for unknown device caught by EExpress\n",irq);
		return;
	}

#if NET_DEBUG > 6
	printk(KERN_DEBUG "%s: interrupt\n", dev->name);
#endif

	dev->interrupt = 1; /* should this be reset on exit? */
  
	lp = (struct net_local *)dev->priv;
	ioaddr = dev->base_addr;

	outb(SIRQ_dis|irqrmap[irq],ioaddr+SET_IRQ);
	old_rp = inw(ioaddr+READ_PTR);
	old_wp = inw(ioaddr+WRITE_PTR);
	status = inw(ioaddr+SCB_STATUS);
	ack_cmd = SCB_ack(status);

	if (PRIV(dev)->started==0 && SCB_complete(status)) 
	{
#if NET_DEBUG > 4
		printk(KERN_DEBUG "%s: SCBcomplete event received\n", dev->name);
#endif
		while (SCB_CUstat(status)==2)
			status = inw_p(ioaddr+SCB_STATUS);
#if NET_DEBUG > 4
                printk(KERN_DEBUG "%s: CU went non-active (status = %08x)\n", dev->name, status);
#endif
		PRIV(dev)->started=1;
		outw_p(lp->tx_link,ioaddr+SCB_CBL);
		outw_p(PRIV(dev)->rx_buf_start,ioaddr+SCB_RFA);
		ack_cmd |= SCB_CUstart | SCB_RUstart;
	}
	else if (PRIV(dev)->started) 
	{
		unsigned short txstatus;
		txstatus = eexp_hw_lasttxstat(dev);
	}
  
	if (SCB_rxdframe(status)) 
	{
		eexp_hw_rx(dev);
	}

	if ((PRIV(dev)->started&2)!=0 && SCB_RUstat(status)!=4) 
	{
		printk(KERN_WARNING "%s: RU stopped status %04x, restarting...\n",
			dev->name,status);
		lp->stats.rx_errors++;
		eexp_hw_rxinit(dev);
		outw(PRIV(dev)->rx_buf_start,ioaddr+SCB_RFA);
		ack_cmd |= SCB_RUstart;
	} 
	else if (PRIV(dev)->started==1 && SCB_RUstat(status)==4) 
		PRIV(dev)->started|=2;

	outw(ack_cmd,ioaddr+SCB_CMD);
	outb(0,ioaddr+SIGNAL_CA);
	outw(old_rp,ioaddr+READ_PTR);
	outw(old_wp,ioaddr+WRITE_PTR);
	outb(SIRQ_en|irqrmap[irq],ioaddr+SET_IRQ);
	dev->interrupt = 0;
#if NET_DEBUG > 6
        printk(KERN_DEBUG "%s: leaving eexp_irq()\n", dev->name);
#endif
	return;
}

/*
 * Hardware access functions
 */

/*
 * Check all the receive buffers, and hand any received packets
 * to the upper levels. Basic sanity check on each frame
 * descriptor
 */
 
static void eexp_hw_rx(struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	unsigned short ioaddr = dev->base_addr;
	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
	unsigned short old_rp = inw(ioaddr+READ_PTR);
	unsigned short rx_block = lp->rx_first;
	unsigned short boguscount = lp->num_rx_bufs;

#if NET_DEBUG > 6
	printk(KERN_DEBUG "%s: eexp_hw_rx()\n", dev->name);
#endif

	while (outw(rx_block,ioaddr+READ_PTR),boguscount--) 
	{
		unsigned short status = inw(ioaddr);
		unsigned short rfd_cmd = inw(ioaddr);
		unsigned short rx_next = inw(ioaddr);
		unsigned short pbuf = inw(ioaddr);
		unsigned short pkt_len;

		if (FD_Done(status)) 
		{
			outw(pbuf,ioaddr+READ_PTR);
			pkt_len = inw(ioaddr);

			if (rfd_cmd!=0x0000 || pbuf!=rx_block+0x16
				|| (pkt_len & 0xc000)!=0xc000) 
			{
				printk(KERN_WARNING "%s: Rx frame at %04x corrupted, status %04x, cmd %04x, "
					"next %04x, pbuf %04x, len %04x\n",dev->name,rx_block,
					status,rfd_cmd,rx_next,pbuf,pkt_len);
				boguscount++;
				continue;
			}
			else if (!FD_OK(status)) 
			{
				lp->stats.rx_errors++;
				if (FD_CRC(status)) 
					lp->stats.rx_crc_errors++;
				if (FD_Align(status))
					lp->stats.rx_frame_errors++;
				if (FD_Resrc(status))
					lp->stats.rx_fifo_errors++;
				if (FD_DMA(status))
					lp->stats.rx_over_errors++;
				if (FD_Short(status))
					lp->stats.rx_length_errors++;
			}
			else
			{
				struct sk_buff *skb;
				pkt_len &= 0x3fff;
				skb = dev_alloc_skb(pkt_len+16);
				if (skb == NULL) 
				{
					printk(KERN_WARNING "%s: Memory squeeze, dropping packet\n",dev->name);
					lp->stats.rx_dropped++;
					break;
				}
				skb->dev = dev;
				skb_reserve(skb, 2);
				outw(pbuf+10,ioaddr+READ_PTR);
				insw(ioaddr,skb_put(skb,pkt_len),(pkt_len+1)>>1);
				skb->protocol = eth_type_trans(skb,dev);
				netif_rx(skb);
				lp->stats.rx_packets++;
			}
			outw(rx_block,ioaddr+WRITE_PTR);
			outw(0x0000,ioaddr);
			outw(0x0000,ioaddr);
		}
		rx_block = rx_next;
	}
	outw(old_rp,ioaddr+READ_PTR);
	outw(old_wp,ioaddr+WRITE_PTR);
}

/*
 * Hand a packet to the card for transmission
 * If we get here, we MUST have already checked
 * to make sure there is room in the transmit
 * buffer region
 */

static void eexp_hw_tx(struct device *dev, unsigned short *buf, unsigned short len)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	unsigned short ioaddr = dev->base_addr;
	unsigned short old_wp = inw(ioaddr+WRITE_PTR);

	outw(lp->tx_head,ioaddr+WRITE_PTR);
	outw(0x0000,ioaddr);
	outw(Cmd_INT|Cmd_Xmit,ioaddr);
	outw(lp->tx_head+0x08,ioaddr);
	outw(lp->tx_head+0x0e,ioaddr);
	outw(0x0000,ioaddr);
	outw(0x0000,ioaddr);
	outw(lp->tx_head+0x08,ioaddr);
	outw(0x8000|len,ioaddr);
	outw(-1,ioaddr);
	outw(lp->tx_head+0x16,ioaddr);
	outw(0,ioaddr);
	outsw(ioaddr,buf,(len+1)>>1);
	outw(lp->tx_tail+0x0c,ioaddr+WRITE_PTR);
	outw(lp->tx_head,ioaddr);
	dev->trans_start = jiffies;
	lp->tx_tail = lp->tx_head;
	if (lp->tx_head==TX_BUF_START+((lp->num_tx_bufs-1)*TX_BUF_SIZE)) 
		lp->tx_head = TX_BUF_START;
	else 
		lp->tx_head += TX_BUF_SIZE;
	if (lp->tx_head != lp->tx_reap) 
		dev->tbusy = 0;
	outw(old_wp,ioaddr+WRITE_PTR);
}

/*
 * Sanity check the suspected EtherExpress card
 * Read hardware address, reset card, size memory and
 * initialize buffer memory pointers. These should
 * probably be held in dev->priv, in case someone has 2
 * differently configured cards in their box (Arghhh!)
 */

static int eexp_hw_probe(struct device *dev, unsigned short ioaddr)
{
	unsigned short hw_addr[3];
	int i;
	unsigned char *chw_addr = (unsigned char *)hw_addr;

	printk("%s: EtherExpress at %#x, ",dev->name,ioaddr);

	hw_addr[0] = eexp_hw_readeeprom(ioaddr,2);
	hw_addr[1] = eexp_hw_readeeprom(ioaddr,3);
	hw_addr[2] = eexp_hw_readeeprom(ioaddr,4);

	/* Standard Address or Compaq LTE Address */
	if (!((hw_addr[2]==0x00aa && ((hw_addr[1] & 0xff00)==0x0000)) ||
	      (hw_addr[2]==0x0080 && ((hw_addr[1] & 0xff00)==0x5F00)))) 
	{
		printk("rejected: invalid address %04x%04x%04x\n",
			hw_addr[2],hw_addr[1],hw_addr[0]);
		return -ENODEV;
	}

	dev->base_addr = ioaddr;
	for ( i=0 ; i<6 ; i++ ) 
		dev->dev_addr[i] = chw_addr[5-i];

	{
		char irqmap[]={0, 9, 3, 4, 5, 10, 11, 0};
		char *ifmap[]={"AUI", "BNC", "10baseT"};
		enum iftype {AUI=0, BNC=1, TP=2};
		unsigned short setupval = eexp_hw_readeeprom(ioaddr,0);

		dev->irq = irqmap[setupval>>13];
		dev->if_port = !(setupval & 0x1000) ? AUI :
			eexp_hw_readeeprom(ioaddr,5) & 0x1 ? TP : BNC;

		printk("IRQ %d, Interface %s, ",dev->irq,ifmap[dev->if_port]);

		outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
		outb(0,ioaddr+SET_IRQ);
	}

	dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);
	if (!dev->priv) 
		return -ENOMEM;

	memset(dev->priv, 0, sizeof(struct net_local));

	eexp_hw_ASICrst(dev);

	{
		unsigned short i586mso = 0x023e;
		unsigned short old_wp,old_rp,old_a0,old_a1;
		unsigned short a0_0,a1_0,a0_1,a1_1;

		old_wp = inw(ioaddr+WRITE_PTR);
		old_rp = inw(ioaddr+READ_PTR);
		outw(0x8000+i586mso,ioaddr+READ_PTR);
		old_a1 = inw(ioaddr);
		outw(i586mso,ioaddr+READ_PTR);
		old_a0 = inw(ioaddr);
		outw(i586mso,ioaddr+WRITE_PTR);
		outw(0x55aa,ioaddr);
		outw(i586mso,ioaddr+READ_PTR);
		a0_0 = inw(ioaddr);
		outw(0x8000+i586mso,ioaddr+WRITE_PTR);
		outw(0x5a5a,ioaddr);
		outw(0x8000+i586mso,ioaddr+READ_PTR);
		a1_0 = inw(ioaddr);
		outw(i586mso,ioaddr+READ_PTR);
		a0_1 = inw(ioaddr);
		outw(i586mso,ioaddr+WRITE_PTR);
		outw(0x1234,ioaddr);
		outw(0x8000+i586mso,ioaddr+READ_PTR);
		a1_1 = inw(ioaddr);

		if ((a0_0 != a0_1) || (a1_0 != a1_1) ||
			(a1_0 != 0x5a5a) || (a0_0 != 0x55aa)) 
		{
			printk("32k\n");
			PRIV(dev)->rx_buf_end = 0x7ff6;
			PRIV(dev)->num_tx_bufs = 4;
		}
		else
		{
			printk("64k\n");
			PRIV(dev)->num_tx_bufs = 8;
			PRIV(dev)->rx_buf_start = TX_BUF_START + (PRIV(dev)->num_tx_bufs*TX_BUF_SIZE);
			PRIV(dev)->rx_buf_end = 0xfff6;
		}

		outw(0x8000+i586mso,ioaddr+WRITE_PTR);
		outw(old_a1,ioaddr);
		outw(i586mso,ioaddr+WRITE_PTR);
		outw(old_a0,ioaddr);
		outw(old_wp,ioaddr+WRITE_PTR);
		outw(old_rp,ioaddr+READ_PTR);
	}
  
	if (net_debug) 
		printk(version);
	dev->open = eexp_open;
	dev->stop = eexp_close;
	dev->hard_start_xmit = eexp_xmit;
	dev->get_stats = eexp_stats;
	dev->set_multicast_list = &eexp_set_multicast;
	ether_setup(dev);
	return 0;
}

/*
 *	Read a word from eeprom location (0-63?)
 */
static unsigned short eexp_hw_readeeprom(unsigned short ioaddr, unsigned char location)
{
	unsigned short cmd = 0x180|(location&0x7f);
	unsigned short rval = 0,wval = EC_CS|i586_RST;
	int i;
 
	outb(EC_CS|i586_RST,ioaddr+EEPROM_Ctrl);
	for ( i=0x100 ; i ; i>>=1 ) 
	{
		if (cmd&i) 
			wval |= EC_Wr;
		else 
			wval &= ~EC_Wr;

		outb(wval,ioaddr+EEPROM_Ctrl);
		outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
		eeprom_delay();
		outb(wval,ioaddr+EEPROM_Ctrl);
		eeprom_delay();
	}	
	wval &= ~EC_Wr;
	outb(wval,ioaddr+EEPROM_Ctrl);
	for ( i=0x8000 ; i ; i>>=1 ) 
	{
		outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
		eeprom_delay();
		if (inb(ioaddr+EEPROM_Ctrl)&EC_Rd) 
			rval |= i;
		outb(wval,ioaddr+EEPROM_Ctrl);
		eeprom_delay();
	}
	wval &= ~EC_CS;
	outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
	eeprom_delay();
	outb(wval,ioaddr+EEPROM_Ctrl);
	eeprom_delay();
	return rval;
}

/*
 * Reap tx buffers and return last transmit status.
 * if ==0 then either:
 *    a) we're not transmitting anything, so why are we here?
 *    b) we've died.
 * otherwise, Stat_Busy(return) means we've still got some packets
 * to transmit, Stat_Done(return) means our buffers should be empty
 * again
 */

static unsigned short eexp_hw_lasttxstat(struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	unsigned short ioaddr = dev->base_addr;
	unsigned short old_rp = inw(ioaddr+READ_PTR);
	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
	unsigned short tx_block = lp->tx_reap;
	unsigned short status;
  
	if (!test_bit(0,(void *)&dev->tbusy) && lp->tx_head==lp->tx_reap) 
		return 0x0000;

	do
	{
		outw(tx_block,ioaddr+READ_PTR);
		status = inw(ioaddr);
		if (!Stat_Done(status)) 
		{
			lp->tx_link = tx_block;
			outw(old_rp,ioaddr+READ_PTR);
			outw(old_wp,ioaddr+WRITE_PTR);
			return status;
		}
		else 
		{
			lp->last_tx_restart = 0;
			lp->stats.collisions += Stat_NoColl(status);
			if (!Stat_OK(status)) 
			{
				if (Stat_Abort(status)) 
					lp->stats.tx_aborted_errors++;
				if (Stat_TNoCar(status) || Stat_TNoCTS(status)) 
					lp->stats.tx_carrier_errors++;
				if (Stat_TNoDMA(status)) 
					lp->stats.tx_fifo_errors++;
			}
			else
				lp->stats.tx_packets++;
		}
		if (tx_block == TX_BUF_START+((lp->num_tx_bufs-1)*TX_BUF_SIZE)) 
			lp->tx_reap = tx_block = TX_BUF_START;
		else
			lp->tx_reap = tx_block += TX_BUF_SIZE;
		dev->tbusy = 0;
		mark_bh(NET_BH);
	}
	while (lp->tx_reap != lp->tx_head);

	lp->tx_link = lp->tx_tail + 0x08;
	outw(old_rp,ioaddr+READ_PTR);
	outw(old_wp,ioaddr+WRITE_PTR);

	return status;
}

/* 
 * This should never happen. It is called when some higher
 * routine detects the CU has stopped, to try to restart
 * it from the last packet we knew we were working on,
 * or the idle loop if we had finished for the time.
 */

static void eexp_hw_txrestart(struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	unsigned short ioaddr = dev->base_addr;
  
	lp->last_tx_restart = lp->tx_link;
	outw(lp->tx_link,ioaddr+SCB_CBL);
	outw(SCB_CUstart,ioaddr+SCB_CMD);
	outw(0,ioaddr+SCB_STATUS);
	outb(0,ioaddr+SIGNAL_CA);

	{
		unsigned short boguscount=50,failcount=5;
		while (!inw(ioaddr+SCB_STATUS)) 
		{
			if (!--boguscount) 
			{
				if (--failcount) 
				{
					printk(KERN_WARNING "%s: CU start timed out, status %04x, cmd %04x\n",
						dev->name, inw(ioaddr+SCB_STATUS), inw(ioaddr+SCB_CMD));
					outw(lp->tx_link,ioaddr+SCB_CBL);
					outw(0,ioaddr+SCB_STATUS);
					outw(SCB_CUstart,ioaddr+SCB_CMD);
					outb(0,ioaddr+SIGNAL_CA);
					boguscount = 100;
				}
				else
				{
					printk(KERN_WARNING "%s: Failed to restart CU, resetting board...\n",dev->name);
					eexp_hw_init586(dev);
					dev->tbusy = 0;
					mark_bh(NET_BH);
					return;
				}
			}
		}
	}
}

/*
 * Writes down the list of transmit buffers into card
 * memory. Initial separate, repeated transmits link
 * them into a circular list, such that the CU can
 * be constantly active, and unlink them as we reap
 * transmitted packet buffers, so the CU doesn't loop
 * and endlessly transmit packets. (Try hacking the driver
 * to send continuous broadcast messages, say ARP requests
 * on a subnet with Windows boxes running on Novell and
 * LAN Workplace with EMM386. Amusing to watch them all die
 * horribly leaving the Linux boxes up!)
 */

static void eexp_hw_txinit(struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	unsigned short ioaddr = dev->base_addr;
	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
	unsigned short tx_block = TX_BUF_START;
	unsigned short curtbuf;

	for ( curtbuf=0 ; curtbuf<lp->num_tx_bufs ; curtbuf++ ) 
	{
		outw(tx_block,ioaddr+WRITE_PTR);
		outw(0x0000,ioaddr);
		outw(Cmd_INT|Cmd_Xmit,ioaddr);
		outw(tx_block+0x08,ioaddr);
		outw(tx_block+0x0e,ioaddr);
		outw(0x0000,ioaddr);
		outw(0x0000,ioaddr);
		outw(tx_block+0x08,ioaddr);
		outw(0x8000,ioaddr);
		outw(-1,ioaddr);
		outw(tx_block+0x16,ioaddr);
		outw(0x0000,ioaddr);
		tx_block += TX_BUF_SIZE;
	}
	lp->tx_head = TX_BUF_START;
	lp->tx_reap = TX_BUF_START;
	lp->tx_tail = tx_block - TX_BUF_SIZE;
	lp->tx_link = lp->tx_tail + 0x08;
	lp->rx_buf_start = tx_block;
	outw(old_wp,ioaddr+WRITE_PTR);
}

/* is this a standard test pattern, or dbecker randomness? */

unsigned short rx_words[] = 
{
	0xfeed,0xf00d,0xf001,0x0505,0x2424,0x6565,0xdeaf
};

/*
 * Write the circular list of receive buffer descriptors to
 * card memory. Note, we no longer mark the end of the list,
 * so if all the buffers fill up, the 82586 will loop until
 * we free one. This may sound dodgy, but it works, and
 * it makes the error detection in the interrupt handler
 * a lot simpler.
 */

static void eexp_hw_rxinit(struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	unsigned short ioaddr = dev->base_addr;
	unsigned short old_wp = inw(ioaddr+WRITE_PTR);
	unsigned short rx_block = lp->rx_buf_start;

	lp->num_rx_bufs = 0;
	lp->rx_first = rx_block;
	do 
	{
		lp->num_rx_bufs++;
		outw(rx_block,ioaddr+WRITE_PTR);
		outw(0x0000,ioaddr);
		outw(0x0000,ioaddr);
		outw(rx_block+RX_BUF_SIZE,ioaddr);
		outw(rx_block+0x16,ioaddr);
		outsw(ioaddr, rx_words, sizeof(rx_words)>>1);
		outw(0x8000,ioaddr);
		outw(-1,ioaddr);
		outw(rx_block+0x20,ioaddr);
		outw(0x0000,ioaddr);
		outw(0x8000|(RX_BUF_SIZE-0x20),ioaddr);
		lp->rx_last = rx_block;
		rx_block += RX_BUF_SIZE;
	} while (rx_block <= lp->rx_buf_end-RX_BUF_SIZE);

	outw(lp->rx_last+4,ioaddr+WRITE_PTR);
	outw(lp->rx_first,ioaddr);

	outw(old_wp,ioaddr+WRITE_PTR);
}

/*
 * Reset the 586, fill memory (including calls to
 * eexp_hw_[(rx)(tx)]init()) unreset, and start
 * the configuration sequence. We don't wait for this
 * to finish, but allow the interrupt handler to start
 * the CU and RU for us. We can't start the receive/
 * transmission system up before we know that the
 * hardware is configured correctly
 */
static void eexp_hw_init586(struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	unsigned short ioaddr = dev->base_addr;

#if NET_DEBUG > 6
        printk("%s: eexp_hw_init586()\n", dev->name);
#endif

	lp->started = 0;
	set_loopback;

	outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
	outb_p(i586_RST,ioaddr+EEPROM_Ctrl);
	udelay(2000);  /* delay 20ms */
        {
		unsigned long ofs;
		for (ofs = 0; ofs < lp->rx_buf_end; ofs += 32) {
			unsigned long i;
			outw_p(ofs, ioaddr+SM_PTR);
			for (i = 0; i < 16; i++) {
				outw_p(0, ioaddr+SM_ADDR(i<<1));
			}
		}
	}

	outw_p(lp->rx_buf_end,ioaddr+WRITE_PTR);
	start_code[28] = (dev->flags & IFF_PROMISC)?(start_code[28] | 1):(start_code[28] & ~1);
	lp->promisc = dev->flags & IFF_PROMISC;
	/* We may die here */
	outsw(ioaddr, start_code, sizeof(start_code)>>1);
	outw(CONF_HW_ADDR,ioaddr+WRITE_PTR);
	outsw(ioaddr,dev->dev_addr,3);
	eexp_hw_txinit(dev);
	eexp_hw_rxinit(dev);
	outw(0,ioaddr+WRITE_PTR);
	outw(1,ioaddr);
	outb(0,ioaddr+EEPROM_Ctrl);
	outw(0,ioaddr+SCB_CMD);
	outb(0,ioaddr+SIGNAL_CA);
	{
		unsigned short rboguscount=50,rfailcount=5;
		while (outw(0,ioaddr+READ_PTR),inw(ioaddr)) 
		{
			if (!--rboguscount) 
			{
				printk(KERN_WARNING "%s: i82586 reset timed out, kicking...\n",
					dev->name);
				outw(0,ioaddr+SCB_CMD);
				outb(0,ioaddr+SIGNAL_CA);
				rboguscount = 100;
				if (!--rfailcount) 
				{
					printk(KERN_WARNING "%s: i82586 not responding, giving up.\n",
						dev->name);
					return;
				}
			}
		}
	}

	outw(CONF_LINK,ioaddr+SCB_CBL);
	outw(0,ioaddr+SCB_STATUS);
	outw(0xf000|SCB_CUstart,ioaddr+SCB_CMD);
	outb(0,ioaddr+SIGNAL_CA);
	{
		unsigned short iboguscount=50,ifailcount=5;
		while (!inw(ioaddr+SCB_STATUS)) 
		{
			if (!--iboguscount) 
			{
				if (--ifailcount) 
				{
					printk(KERN_WARNING "%s: i82586 initialization timed out, status %04x, cmd %04x\n",
						dev->name, inw(ioaddr+SCB_STATUS), inw(ioaddr+SCB_CMD));
					outw(CONF_LINK,ioaddr+SCB_CBL);
					outw(0,ioaddr+SCB_STATUS);
					outw(0xf000|SCB_CUstart,ioaddr+SCB_CMD);
					outb(0,ioaddr+SIGNAL_CA);
					iboguscount = 100;
				}
				else 
				{
					printk(KERN_WARNING "%s: Failed to initialize i82586, giving up.\n",dev->name);
					return;
				}
			}
		}
	}
  
	outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
	clear_loopback;
	lp->init_time = jiffies;
#if NET_DEBUG > 6
        printk("%s: leaving eexp_hw_init586()\n", dev->name);
#endif
	return;
}

/* 
 * completely reset the EtherExpress hardware. We will most likely get
 * an interrupt during this whether we want one or not. It is best,
 * therefore, to call this while we don't have a request_irq() on.
 */

static void eexp_hw_ASICrst(struct device *dev)
{
	unsigned short ioaddr = dev->base_addr;
	unsigned short wrval = 0x0001,succount=0,boguscount=500;

	outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);

	PRIV(dev)->started = 0;
	outb(ASIC_RST|i586_RST,ioaddr+EEPROM_Ctrl);
	while (succount<20) 
	{
		if (wrval == 0xffff) 
			wrval = 0x0001;
		outw(0,ioaddr+WRITE_PTR);
		outw(wrval,ioaddr);
		outw(0,ioaddr+READ_PTR);
		if (wrval++ == inw(ioaddr)) 
			succount++;
		else 
		{
			succount = 0;
			if (!boguscount--) 
			{
				boguscount = 500;
				printk("%s: Having problems resetting EtherExpress ASIC, continuing...\n",
					dev->name);
				wrval = 0x0001;
				outb(ASIC_RST|i586_RST,ioaddr+EEPROM_Ctrl);
			}
		}
	}
	outb(i586_RST,ioaddr+EEPROM_Ctrl);
}


/*
 * Set or clear the multicast filter for this adaptor.
 * We have to do a complete 586 restart for this to take effect.
 * At the moment only promiscuous mode is supported.
 */
static void
eexp_set_multicast(struct device *dev)
{
	if ((dev->flags & IFF_PROMISC) != PRIV(dev)->promisc)
		eexp_hw_init586(dev);
}


/*
 * MODULE stuff
 */
#ifdef MODULE

#define EEXP_MAX_CARDS     4    /* max number of cards to support */
#define NAMELEN            8    /* max length of dev->name (inc null) */

static char namelist[NAMELEN * EEXP_MAX_CARDS] = { 0, };

static struct device dev_eexp[EEXP_MAX_CARDS] = 
{
        { NULL,         /* will allocate dynamically */
	  0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, express_probe },  
};

int irq[EEXP_MAX_CARDS] = {0, };
int io[EEXP_MAX_CARDS] = {0, };

/* Ideally the user would give us io=, irq= for every card.  If any parameters
 * are specified, we verify and then use them.  If no parameters are given, we
 * autoprobe for one card only.
 */
int init_module(void)
{
	int this_dev, found = 0;

	for (this_dev = 0; this_dev < EEXP_MAX_CARDS; this_dev++) {
		struct device *dev = &dev_eexp[this_dev];
		dev->name = namelist + (NAMELEN*this_dev);
		dev->irq = irq[this_dev];
		dev->base_addr = io[this_dev];
		if (io[this_dev] == 0) {
			if (this_dev) break;
			printk(KERN_NOTICE "eexpress.c: Module autoprobe not recommended, give io=xx.\n");
		}
		if (register_netdev(dev) != 0) {
			printk(KERN_WARNING "eexpress.c: Failed to register card at 0x%x.\n", io[this_dev]);
			if (found != 0) return 0;
			return -ENXIO;
		}
		found++;
	}
	return 0;
}

void cleanup_module(void)
{
	int this_dev;
        
	for (this_dev = 0; this_dev < EEXP_MAX_CARDS; this_dev++) {
		struct device *dev = &dev_eexp[this_dev];
		if (dev->priv != NULL) {
			kfree(dev->priv);
			dev->priv = NULL;
			release_region(dev->base_addr, EEXP_IO_EXTENT);
			unregister_netdev(dev);
		}
	}
}
#endif

/*
 * Local Variables:
 *  c-file-style: "linux"
 *  tab-width: 8
 *  compile-command: "gcc -D__KERNEL__ -I/discs/bibble/src/linux-1.3.69/include  -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strength-reduce -pipe -m486 -DCPU=486 -DMODULE  -c 3c505.c"
 * End:
 */
