diff options
Diffstat (limited to 'drivers/net/ethernet/3com')
| -rw-r--r-- | drivers/net/ethernet/3com/3c509.c | 1454 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/3c515.c | 1583 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/3c574_cs.c | 1167 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/3c589_cs.c | 971 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/3c59x.c | 3328 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/Kconfig | 107 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/Makefile | 10 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/typhoon.c | 2556 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/typhoon.h | 624 | 
9 files changed, 11800 insertions, 0 deletions
diff --git a/drivers/net/ethernet/3com/3c509.c b/drivers/net/ethernet/3com/3c509.c new file mode 100644 index 00000000000..a968654b631 --- /dev/null +++ b/drivers/net/ethernet/3com/3c509.c @@ -0,0 +1,1454 @@ +/* 3c509.c: A 3c509 EtherLink3 ethernet driver for linux. */ +/* +	Written 1993-2000 by Donald Becker. + +	Copyright 1994-2000 by Donald Becker. +	Copyright 1993 United States Government as represented by the +	Director, National Security Agency.	 This software may be used and +	distributed according to the terms of the GNU General Public License, +	incorporated herein by reference. + +	This driver is for the 3Com EtherLinkIII series. + +	The author may be reached as becker@scyld.com, or C/O +	Scyld Computing Corporation +	410 Severn Ave., Suite 210 +	Annapolis MD 21403 + +	Known limitations: +	Because of the way 3c509 ISA detection works it's difficult to predict +	a priori which of several ISA-mode cards will be detected first. + +	This driver does not use predictive interrupt mode, resulting in higher +	packet latency but lower overhead.  If interrupts are disabled for an +	unusually long time it could also result in missed packets, but in +	practice this rarely happens. + + +	FIXES: +		Alan Cox:       Removed the 'Unexpected interrupt' bug. +		Michael Meskes:	Upgraded to Donald Becker's version 1.07. +		Alan Cox:	Increased the eeprom delay. Regardless of +				what the docs say some people definitely +				get problems with lower (but in card spec) +				delays +		v1.10 4/21/97 Fixed module code so that multiple cards may be detected, +				other cleanups.  -djb +		Andrea Arcangeli:	Upgraded to Donald Becker's version 1.12. +		Rick Payne:	Fixed SMP race condition +		v1.13 9/8/97 Made 'max_interrupt_work' an insmod-settable variable -djb +		v1.14 10/15/97 Avoided waiting..discard message for fast machines -djb +		v1.15 1/31/98 Faster recovery for Tx errors. -djb +		v1.16 2/3/98 Different ID port handling to avoid sound cards. -djb +		v1.18 12Mar2001 Andrew Morton +			- Avoid bogus detect of 3c590's (Andrzej Krzysztofowicz) +			- Reviewed against 1.18 from scyld.com +		v1.18a 17Nov2001 Jeff Garzik <jgarzik@pobox.com> +			- ethtool support +		v1.18b 1Mar2002 Zwane Mwaikambo <zwane@commfireservices.com> +			- Power Management support +		v1.18c 1Mar2002 David Ruggiero <jdr@farfalle.com> +			- Full duplex support +		v1.19  16Oct2002 Zwane Mwaikambo <zwane@linuxpower.ca> +			- Additional ethtool features +		v1.19a 28Oct2002 Davud Ruggiero <jdr@farfalle.com> +			- Increase *read_eeprom udelay to workaround oops with 2 cards. +		v1.19b 08Nov2002 Marc Zyngier <maz@wild-wind.fr.eu.org> +			- Introduce driver model for EISA cards. +		v1.20  04Feb2008 Ondrej Zary <linux@rainbow-software.org> +			- convert to isa_driver and pnp_driver and some cleanups +*/ + +#define DRV_NAME	"3c509" +#define DRV_VERSION	"1.20" +#define DRV_RELDATE	"04Feb2008" + +/* A few values that may be tweaked. */ + +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT  (400*HZ/1000) + +#include <linux/module.h> +#include <linux/isa.h> +#include <linux/pnp.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/pm.h> +#include <linux/skbuff.h> +#include <linux/delay.h>	/* for udelay() */ +#include <linux/spinlock.h> +#include <linux/ethtool.h> +#include <linux/device.h> +#include <linux/eisa.h> +#include <linux/bitops.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/irq.h> + +static char version[] = DRV_NAME ".c:" DRV_VERSION " " DRV_RELDATE " becker@scyld.com\n"; + +#ifdef EL3_DEBUG +static int el3_debug = EL3_DEBUG; +#else +static int el3_debug = 2; +#endif + +/* Used to do a global count of all the cards in the system.  Must be + * a global variable so that the eisa probe routines can increment + * it */ +static int el3_cards = 0; +#define EL3_MAX_CARDS 8 + +/* To minimize the size of the driver source I only define operating +   constants if they are used several times.  You'll need the manual +   anyway if you want to understand driver details. */ +/* Offsets from base I/O address. */ +#define EL3_DATA 0x00 +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e +#define	EEPROM_READ 0x80 + +#define EL3_IO_EXTENT	16 + +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) + + +/* The top five bits written to EL3_CMD are a command, the lower +   11 bits are the parameter, if applicable. */ +enum c509cmd { +	TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, +	RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11, +	TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, +	FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, +	SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, +	SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11, +	StatsDisable = 22<<11, StopCoax = 23<<11, PowerUp = 27<<11, +	PowerDown = 28<<11, PowerAuto = 29<<11}; + +enum c509status { +	IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, +	TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, +	IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000, }; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { +	RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 }; + +/* Register window 1 offsets, the window used in normal operation. */ +#define TX_FIFO		0x00 +#define RX_FIFO		0x00 +#define RX_STATUS 	0x08 +#define TX_STATUS 	0x0B +#define TX_FREE		0x0C		/* Remaining free bytes in Tx buffer. */ + +#define WN0_CONF_CTRL	0x04		/* Window 0: Configuration control register */ +#define WN0_ADDR_CONF	0x06		/* Window 0: Address configuration register */ +#define WN0_IRQ		0x08		/* Window 0: Set IRQ line in bits 12-15. */ +#define WN4_MEDIA	0x0A		/* Window 4: Various transcvr/media bits. */ +#define	MEDIA_TP	0x00C0		/* Enable link beat and jabber for 10baseT. */ +#define WN4_NETDIAG	0x06		/* Window 4: Net diagnostic */ +#define FD_ENABLE	0x8000		/* Enable full-duplex ("external loopback") */ + +/* + * Must be a power of two (we use a binary and in the + * circular queue) + */ +#define SKB_QUEUE_SIZE	64 + +enum el3_cardtype { EL3_ISA, EL3_PNP, EL3_EISA }; + +struct el3_private { +	spinlock_t lock; +	/* skb send-queue */ +	int head, size; +	struct sk_buff *queue[SKB_QUEUE_SIZE]; +	enum el3_cardtype type; +}; +static int id_port; +static int current_tag; +static struct net_device *el3_devs[EL3_MAX_CARDS]; + +/* Parameters that may be passed into the module. */ +static int debug = -1; +static int irq[] = {-1, -1, -1, -1, -1, -1, -1, -1}; +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 10; +#ifdef CONFIG_PNP +static int nopnp; +#endif + +static int el3_common_init(struct net_device *dev); +static void el3_common_remove(struct net_device *dev); +static ushort id_read_eeprom(int index); +static ushort read_eeprom(int ioaddr, int index); +static int el3_open(struct net_device *dev); +static netdev_tx_t el3_start_xmit(struct sk_buff *skb, struct net_device *dev); +static irqreturn_t el3_interrupt(int irq, void *dev_id); +static void update_stats(struct net_device *dev); +static struct net_device_stats *el3_get_stats(struct net_device *dev); +static int el3_rx(struct net_device *dev); +static int el3_close(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); +static void el3_tx_timeout (struct net_device *dev); +static void el3_down(struct net_device *dev); +static void el3_up(struct net_device *dev); +static const struct ethtool_ops ethtool_ops; +#ifdef CONFIG_PM +static int el3_suspend(struct device *, pm_message_t); +static int el3_resume(struct device *); +#else +#define el3_suspend NULL +#define el3_resume NULL +#endif + + +/* generic device remove for all device types */ +static int el3_device_remove (struct device *device); +#ifdef CONFIG_NET_POLL_CONTROLLER +static void el3_poll_controller(struct net_device *dev); +#endif + +/* Return 0 on success, 1 on error, 2 when found already detected PnP card */ +static int el3_isa_id_sequence(__be16 *phys_addr) +{ +	short lrs_state = 0xff; +	int i; + +	/* ISA boards are detected by sending the ID sequence to the +	   ID_PORT.  We find cards past the first by setting the 'current_tag' +	   on cards as they are found.  Cards with their tag set will not +	   respond to subsequent ID sequences. */ + +	outb(0x00, id_port); +	outb(0x00, id_port); +	for (i = 0; i < 255; i++) { +		outb(lrs_state, id_port); +		lrs_state <<= 1; +		lrs_state = lrs_state & 0x100 ? lrs_state ^ 0xcf : lrs_state; +	} +	/* For the first probe, clear all board's tag registers. */ +	if (current_tag == 0) +		outb(0xd0, id_port); +	else			/* Otherwise kill off already-found boards. */ +		outb(0xd8, id_port); +	if (id_read_eeprom(7) != 0x6d50) +		return 1; +	/* Read in EEPROM data, which does contention-select. +	   Only the lowest address board will stay "on-line". +	   3Com got the byte order backwards. */ +	for (i = 0; i < 3; i++) +		phys_addr[i] = htons(id_read_eeprom(i)); +#ifdef CONFIG_PNP +	if (!nopnp) { +		/* The ISA PnP 3c509 cards respond to the ID sequence too. +		   This check is needed in order not to register them twice. */ +		for (i = 0; i < el3_cards; i++) { +			struct el3_private *lp = netdev_priv(el3_devs[i]); +			if (lp->type == EL3_PNP && +			    ether_addr_equal((u8 *)phys_addr, el3_devs[i]->dev_addr)) { +				if (el3_debug > 3) +					pr_debug("3c509 with address %02x %02x %02x %02x %02x %02x was found by ISAPnP\n", +						phys_addr[0] & 0xff, phys_addr[0] >> 8, +						phys_addr[1] & 0xff, phys_addr[1] >> 8, +						phys_addr[2] & 0xff, phys_addr[2] >> 8); +				/* Set the adaptor tag so that the next card can be found. */ +				outb(0xd0 + ++current_tag, id_port); +				return 2; +			} +		} +	} +#endif /* CONFIG_PNP */ +	return 0; + +} + +static void el3_dev_fill(struct net_device *dev, __be16 *phys_addr, int ioaddr, +			 int irq, int if_port, enum el3_cardtype type) +{ +	struct el3_private *lp = netdev_priv(dev); + +	memcpy(dev->dev_addr, phys_addr, ETH_ALEN); +	dev->base_addr = ioaddr; +	dev->irq = irq; +	dev->if_port = if_port; +	lp->type = type; +} + +static int el3_isa_match(struct device *pdev, unsigned int ndev) +{ +	struct net_device *dev; +	int ioaddr, isa_irq, if_port, err; +	unsigned int iobase; +	__be16 phys_addr[3]; + +	while ((err = el3_isa_id_sequence(phys_addr)) == 2) +		;	/* Skip to next card when PnP card found */ +	if (err == 1) +		return 0; + +	iobase = id_read_eeprom(8); +	if_port = iobase >> 14; +	ioaddr = 0x200 + ((iobase & 0x1f) << 4); +	if (irq[el3_cards] > 1 && irq[el3_cards] < 16) +		isa_irq = irq[el3_cards]; +	else +		isa_irq = id_read_eeprom(9) >> 12; + +	dev = alloc_etherdev(sizeof(struct el3_private)); +	if (!dev) +		return -ENOMEM; + +	SET_NETDEV_DEV(dev, pdev); +	netdev_boot_setup_check(dev); + +	if (!request_region(ioaddr, EL3_IO_EXTENT, "3c509-isa")) { +		free_netdev(dev); +		return 0; +	} + +	/* Set the adaptor tag so that the next card can be found. */ +	outb(0xd0 + ++current_tag, id_port); + +	/* Activate the adaptor at the EEPROM location. */ +	outb((ioaddr >> 4) | 0xe0, id_port); + +	EL3WINDOW(0); +	if (inw(ioaddr) != 0x6d50) { +		free_netdev(dev); +		return 0; +	} + +	/* Free the interrupt so that some other card can use it. */ +	outw(0x0f00, ioaddr + WN0_IRQ); + +	el3_dev_fill(dev, phys_addr, ioaddr, isa_irq, if_port, EL3_ISA); +	dev_set_drvdata(pdev, dev); +	if (el3_common_init(dev)) { +		free_netdev(dev); +		return 0; +	} + +	el3_devs[el3_cards++] = dev; +	return 1; +} + +static int el3_isa_remove(struct device *pdev, +				    unsigned int ndev) +{ +	el3_device_remove(pdev); +	dev_set_drvdata(pdev, NULL); +	return 0; +} + +#ifdef CONFIG_PM +static int el3_isa_suspend(struct device *dev, unsigned int n, +			   pm_message_t state) +{ +	current_tag = 0; +	return el3_suspend(dev, state); +} + +static int el3_isa_resume(struct device *dev, unsigned int n) +{ +	struct net_device *ndev = dev_get_drvdata(dev); +	int ioaddr = ndev->base_addr, err; +	__be16 phys_addr[3]; + +	while ((err = el3_isa_id_sequence(phys_addr)) == 2) +		;	/* Skip to next card when PnP card found */ +	if (err == 1) +		return 0; +	/* Set the adaptor tag so that the next card can be found. */ +	outb(0xd0 + ++current_tag, id_port); +	/* Enable the card */ +	outb((ioaddr >> 4) | 0xe0, id_port); +	EL3WINDOW(0); +	if (inw(ioaddr) != 0x6d50) +		return 1; +	/* Free the interrupt so that some other card can use it. */ +	outw(0x0f00, ioaddr + WN0_IRQ); +	return el3_resume(dev); +} +#endif + +static struct isa_driver el3_isa_driver = { +	.match		= el3_isa_match, +	.remove		= el3_isa_remove, +#ifdef CONFIG_PM +	.suspend	= el3_isa_suspend, +	.resume		= el3_isa_resume, +#endif +	.driver		= { +		.name	= "3c509" +	}, +}; +static int isa_registered; + +#ifdef CONFIG_PNP +static struct pnp_device_id el3_pnp_ids[] = { +	{ .id = "TCM5090" }, /* 3Com Etherlink III (TP) */ +	{ .id = "TCM5091" }, /* 3Com Etherlink III */ +	{ .id = "TCM5094" }, /* 3Com Etherlink III (combo) */ +	{ .id = "TCM5095" }, /* 3Com Etherlink III (TPO) */ +	{ .id = "TCM5098" }, /* 3Com Etherlink III (TPC) */ +	{ .id = "PNP80f7" }, /* 3Com Etherlink III compatible */ +	{ .id = "PNP80f8" }, /* 3Com Etherlink III compatible */ +	{ .id = "" } +}; +MODULE_DEVICE_TABLE(pnp, el3_pnp_ids); + +static int el3_pnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *id) +{ +	short i; +	int ioaddr, irq, if_port; +	__be16 phys_addr[3]; +	struct net_device *dev = NULL; +	int err; + +	ioaddr = pnp_port_start(pdev, 0); +	if (!request_region(ioaddr, EL3_IO_EXTENT, "3c509-pnp")) +		return -EBUSY; +	irq = pnp_irq(pdev, 0); +	EL3WINDOW(0); +	for (i = 0; i < 3; i++) +		phys_addr[i] = htons(read_eeprom(ioaddr, i)); +	if_port = read_eeprom(ioaddr, 8) >> 14; +	dev = alloc_etherdev(sizeof(struct el3_private)); +	if (!dev) { +		release_region(ioaddr, EL3_IO_EXTENT); +		return -ENOMEM; +	} +	SET_NETDEV_DEV(dev, &pdev->dev); +	netdev_boot_setup_check(dev); + +	el3_dev_fill(dev, phys_addr, ioaddr, irq, if_port, EL3_PNP); +	pnp_set_drvdata(pdev, dev); +	err = el3_common_init(dev); + +	if (err) { +		pnp_set_drvdata(pdev, NULL); +		free_netdev(dev); +		return err; +	} + +	el3_devs[el3_cards++] = dev; +	return 0; +} + +static void el3_pnp_remove(struct pnp_dev *pdev) +{ +	el3_common_remove(pnp_get_drvdata(pdev)); +	pnp_set_drvdata(pdev, NULL); +} + +#ifdef CONFIG_PM +static int el3_pnp_suspend(struct pnp_dev *pdev, pm_message_t state) +{ +	return el3_suspend(&pdev->dev, state); +} + +static int el3_pnp_resume(struct pnp_dev *pdev) +{ +	return el3_resume(&pdev->dev); +} +#endif + +static struct pnp_driver el3_pnp_driver = { +	.name		= "3c509", +	.id_table	= el3_pnp_ids, +	.probe		= el3_pnp_probe, +	.remove		= el3_pnp_remove, +#ifdef CONFIG_PM +	.suspend	= el3_pnp_suspend, +	.resume		= el3_pnp_resume, +#endif +}; +static int pnp_registered; +#endif /* CONFIG_PNP */ + +#ifdef CONFIG_EISA +static struct eisa_device_id el3_eisa_ids[] = { +		{ "TCM5090" }, +		{ "TCM5091" }, +		{ "TCM5092" }, +		{ "TCM5093" }, +		{ "TCM5094" }, +		{ "TCM5095" }, +		{ "TCM5098" }, +		{ "" } +}; +MODULE_DEVICE_TABLE(eisa, el3_eisa_ids); + +static int el3_eisa_probe (struct device *device); + +static struct eisa_driver el3_eisa_driver = { +		.id_table = el3_eisa_ids, +		.driver   = { +				.name    = "3c579", +				.probe   = el3_eisa_probe, +				.remove  = el3_device_remove, +				.suspend = el3_suspend, +				.resume  = el3_resume, +		} +}; +static int eisa_registered; +#endif + +static const struct net_device_ops netdev_ops = { +	.ndo_open 		= el3_open, +	.ndo_stop	 	= el3_close, +	.ndo_start_xmit 	= el3_start_xmit, +	.ndo_get_stats 		= el3_get_stats, +	.ndo_set_rx_mode	= set_multicast_list, +	.ndo_tx_timeout 	= el3_tx_timeout, +	.ndo_change_mtu		= eth_change_mtu, +	.ndo_set_mac_address 	= eth_mac_addr, +	.ndo_validate_addr	= eth_validate_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER +	.ndo_poll_controller	= el3_poll_controller, +#endif +}; + +static int el3_common_init(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	int err; +	const char *if_names[] = {"10baseT", "AUI", "undefined", "BNC"}; + +	spin_lock_init(&lp->lock); + +	if (dev->mem_start & 0x05) { /* xcvr codes 1/3/4/12 */ +		dev->if_port = (dev->mem_start & 0x0f); +	} else { /* xcvr codes 0/8 */ +		/* use eeprom value, but save user's full-duplex selection */ +		dev->if_port |= (dev->mem_start & 0x08); +	} + +	/* The EL3-specific entries in the device structure. */ +	dev->netdev_ops = &netdev_ops; +	dev->watchdog_timeo = TX_TIMEOUT; +	dev->ethtool_ops = ðtool_ops; + +	err = register_netdev(dev); +	if (err) { +		pr_err("Failed to register 3c5x9 at %#3.3lx, IRQ %d.\n", +			dev->base_addr, dev->irq); +		release_region(dev->base_addr, EL3_IO_EXTENT); +		return err; +	} + +	pr_info("%s: 3c5x9 found at %#3.3lx, %s port, address %pM, IRQ %d.\n", +	       dev->name, dev->base_addr, if_names[(dev->if_port & 0x03)], +	       dev->dev_addr, dev->irq); + +	if (el3_debug > 0) +		pr_info("%s", version); +	return 0; + +} + +static void el3_common_remove (struct net_device *dev) +{ +	unregister_netdev (dev); +	release_region(dev->base_addr, EL3_IO_EXTENT); +	free_netdev (dev); +} + +#ifdef CONFIG_EISA +static int __init el3_eisa_probe (struct device *device) +{ +	short i; +	int ioaddr, irq, if_port; +	__be16 phys_addr[3]; +	struct net_device *dev = NULL; +	struct eisa_device *edev; +	int err; + +	/* Yeepee, The driver framework is calling us ! */ +	edev = to_eisa_device (device); +	ioaddr = edev->base_addr; + +	if (!request_region(ioaddr, EL3_IO_EXTENT, "3c579-eisa")) +		return -EBUSY; + +	/* Change the register set to the configuration window 0. */ +	outw(SelectWindow | 0, ioaddr + 0xC80 + EL3_CMD); + +	irq = inw(ioaddr + WN0_IRQ) >> 12; +	if_port = inw(ioaddr + 6)>>14; +	for (i = 0; i < 3; i++) +		phys_addr[i] = htons(read_eeprom(ioaddr, i)); + +	/* Restore the "Product ID" to the EEPROM read register. */ +	read_eeprom(ioaddr, 3); + +	dev = alloc_etherdev(sizeof (struct el3_private)); +	if (dev == NULL) { +		release_region(ioaddr, EL3_IO_EXTENT); +		return -ENOMEM; +	} + +	SET_NETDEV_DEV(dev, device); +	netdev_boot_setup_check(dev); + +	el3_dev_fill(dev, phys_addr, ioaddr, irq, if_port, EL3_EISA); +	eisa_set_drvdata (edev, dev); +	err = el3_common_init(dev); + +	if (err) { +		eisa_set_drvdata (edev, NULL); +		free_netdev(dev); +		return err; +	} + +	el3_devs[el3_cards++] = dev; +	return 0; +} +#endif + +/* This remove works for all device types. + * + * The net dev must be stored in the driver data field */ +static int el3_device_remove(struct device *device) +{ +	struct net_device *dev; + +	dev = dev_get_drvdata(device); + +	el3_common_remove (dev); +	return 0; +} + +/* Read a word from the EEPROM using the regular EEPROM access register. +   Assume that we are in register window zero. + */ +static ushort read_eeprom(int ioaddr, int index) +{ +	outw(EEPROM_READ + index, ioaddr + 10); +	/* Pause for at least 162 us. for the read to take place. +	   Some chips seem to require much longer */ +	mdelay(2); +	return inw(ioaddr + 12); +} + +/* Read a word from the EEPROM when in the ISA ID probe state. */ +static ushort id_read_eeprom(int index) +{ +	int bit, word = 0; + +	/* Issue read command, and pause for at least 162 us. for it to complete. +	   Assume extra-fast 16Mhz bus. */ +	outb(EEPROM_READ + index, id_port); + +	/* Pause for at least 162 us. for the read to take place. */ +	/* Some chips seem to require much longer */ +	mdelay(4); + +	for (bit = 15; bit >= 0; bit--) +		word = (word << 1) + (inb(id_port) & 0x01); + +	if (el3_debug > 3) +		pr_debug("  3c509 EEPROM word %d %#4.4x.\n", index, word); + +	return word; +} + + +static int +el3_open(struct net_device *dev) +{ +	int ioaddr = dev->base_addr; +	int i; + +	outw(TxReset, ioaddr + EL3_CMD); +	outw(RxReset, ioaddr + EL3_CMD); +	outw(SetStatusEnb | 0x00, ioaddr + EL3_CMD); + +	i = request_irq(dev->irq, el3_interrupt, 0, dev->name, dev); +	if (i) +		return i; + +	EL3WINDOW(0); +	if (el3_debug > 3) +		pr_debug("%s: Opening, IRQ %d	 status@%x %4.4x.\n", dev->name, +			   dev->irq, ioaddr + EL3_STATUS, inw(ioaddr + EL3_STATUS)); + +	el3_up(dev); + +	if (el3_debug > 3) +		pr_debug("%s: Opened 3c509  IRQ %d  status %4.4x.\n", +			   dev->name, dev->irq, inw(ioaddr + EL3_STATUS)); + +	return 0; +} + +static void +el3_tx_timeout (struct net_device *dev) +{ +	int ioaddr = dev->base_addr; + +	/* Transmitter timeout, serious problems. */ +	pr_warning("%s: transmit timed out, Tx_status %2.2x status %4.4x Tx FIFO room %d.\n", +		   dev->name, inb(ioaddr + TX_STATUS), inw(ioaddr + EL3_STATUS), +		   inw(ioaddr + TX_FREE)); +	dev->stats.tx_errors++; +	dev->trans_start = jiffies; /* prevent tx timeout */ +	/* Issue TX_RESET and TX_START commands. */ +	outw(TxReset, ioaddr + EL3_CMD); +	outw(TxEnable, ioaddr + EL3_CMD); +	netif_wake_queue(dev); +} + + +static netdev_tx_t +el3_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	int ioaddr = dev->base_addr; +	unsigned long flags; + +	netif_stop_queue (dev); + +	dev->stats.tx_bytes += skb->len; + +	if (el3_debug > 4) { +		pr_debug("%s: el3_start_xmit(length = %u) called, status %4.4x.\n", +			   dev->name, skb->len, inw(ioaddr + EL3_STATUS)); +	} +	/* +	 *	We lock the driver against other processors. Note +	 *	we don't need to lock versus the IRQ as we suspended +	 *	that. This means that we lose the ability to take +	 *	an RX during a TX upload. That sucks a bit with SMP +	 *	on an original 3c509 (2K buffer) +	 * +	 *	Using disable_irq stops us crapping on other +	 *	time sensitive devices. +	 */ + +	spin_lock_irqsave(&lp->lock, flags); + +	/* Put out the doubleword header... */ +	outw(skb->len, ioaddr + TX_FIFO); +	outw(0x00, ioaddr + TX_FIFO); +	/* ... and the packet rounded to a doubleword. */ +	outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); + +	if (inw(ioaddr + TX_FREE) > 1536) +		netif_start_queue(dev); +	else +		/* Interrupt us when the FIFO has room for max-sized packet. */ +		outw(SetTxThreshold + 1536, ioaddr + EL3_CMD); + +	spin_unlock_irqrestore(&lp->lock, flags); + +	dev_consume_skb_any (skb); + +	/* Clear the Tx status stack. */ +	{ +		short tx_status; +		int i = 4; + +		while (--i > 0	&&	(tx_status = inb(ioaddr + TX_STATUS)) > 0) { +			if (tx_status & 0x38) dev->stats.tx_aborted_errors++; +			if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD); +			if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD); +			outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ +		} +	} +	return NETDEV_TX_OK; +} + +/* The EL3 interrupt handler. */ +static irqreturn_t +el3_interrupt(int irq, void *dev_id) +{ +	struct net_device *dev = dev_id; +	struct el3_private *lp; +	int ioaddr, status; +	int i = max_interrupt_work; + +	lp = netdev_priv(dev); +	spin_lock(&lp->lock); + +	ioaddr = dev->base_addr; + +	if (el3_debug > 4) { +		status = inw(ioaddr + EL3_STATUS); +		pr_debug("%s: interrupt, status %4.4x.\n", dev->name, status); +	} + +	while ((status = inw(ioaddr + EL3_STATUS)) & +		   (IntLatch | RxComplete | StatsFull)) { + +		if (status & RxComplete) +			el3_rx(dev); + +		if (status & TxAvailable) { +			if (el3_debug > 5) +				pr_debug("	TX room bit was handled.\n"); +			/* There's room in the FIFO for a full-sized packet. */ +			outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); +			netif_wake_queue (dev); +		} +		if (status & (AdapterFailure | RxEarly | StatsFull | TxComplete)) { +			/* Handle all uncommon interrupts. */ +			if (status & StatsFull)				/* Empty statistics. */ +				update_stats(dev); +			if (status & RxEarly) {				/* Rx early is unused. */ +				el3_rx(dev); +				outw(AckIntr | RxEarly, ioaddr + EL3_CMD); +			} +			if (status & TxComplete) {			/* Really Tx error. */ +				short tx_status; +				int i = 4; + +				while (--i>0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) { +					if (tx_status & 0x38) dev->stats.tx_aborted_errors++; +					if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD); +					if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD); +					outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ +				} +			} +			if (status & AdapterFailure) { +				/* Adapter failure requires Rx reset and reinit. */ +				outw(RxReset, ioaddr + EL3_CMD); +				/* Set the Rx filter to the current state. */ +				outw(SetRxFilter | RxStation | RxBroadcast +					 | (dev->flags & IFF_ALLMULTI ? RxMulticast : 0) +					 | (dev->flags & IFF_PROMISC ? RxProm : 0), +					 ioaddr + EL3_CMD); +				outw(RxEnable, ioaddr + EL3_CMD); /* Re-enable the receiver. */ +				outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); +			} +		} + +		if (--i < 0) { +			pr_err("%s: Infinite loop in interrupt, status %4.4x.\n", +				   dev->name, status); +			/* Clear all interrupts. */ +			outw(AckIntr | 0xFF, ioaddr + EL3_CMD); +			break; +		} +		/* Acknowledge the IRQ. */ +		outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); /* Ack IRQ */ +	} + +	if (el3_debug > 4) { +		pr_debug("%s: exiting interrupt, status %4.4x.\n", dev->name, +			   inw(ioaddr + EL3_STATUS)); +	} +	spin_unlock(&lp->lock); +	return IRQ_HANDLED; +} + + +#ifdef CONFIG_NET_POLL_CONTROLLER +/* + * Polling receive - used by netconsole and other diagnostic tools + * to allow network i/o with interrupts disabled. + */ +static void el3_poll_controller(struct net_device *dev) +{ +	disable_irq(dev->irq); +	el3_interrupt(dev->irq, dev); +	enable_irq(dev->irq); +} +#endif + +static struct net_device_stats * +el3_get_stats(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	unsigned long flags; + +	/* +	 *	This is fast enough not to bother with disable IRQ +	 *	stuff. +	 */ + +	spin_lock_irqsave(&lp->lock, flags); +	update_stats(dev); +	spin_unlock_irqrestore(&lp->lock, flags); +	return &dev->stats; +} + +/*  Update statistics.  We change to register window 6, so this should be run +	single-threaded if the device is active. This is expected to be a rare +	operation, and it's simpler for the rest of the driver to assume that +	window 1 is always valid rather than use a special window-state variable. +	*/ +static void update_stats(struct net_device *dev) +{ +	int ioaddr = dev->base_addr; + +	if (el3_debug > 5) +		pr_debug("   Updating the statistics.\n"); +	/* Turn off statistics updates while reading. */ +	outw(StatsDisable, ioaddr + EL3_CMD); +	/* Switch to the stats window, and read everything. */ +	EL3WINDOW(6); +	dev->stats.tx_carrier_errors 	+= inb(ioaddr + 0); +	dev->stats.tx_heartbeat_errors	+= inb(ioaddr + 1); +	/* Multiple collisions. */	   inb(ioaddr + 2); +	dev->stats.collisions		+= inb(ioaddr + 3); +	dev->stats.tx_window_errors	+= inb(ioaddr + 4); +	dev->stats.rx_fifo_errors	+= inb(ioaddr + 5); +	dev->stats.tx_packets		+= inb(ioaddr + 6); +	/* Rx packets	*/		   inb(ioaddr + 7); +	/* Tx deferrals */		   inb(ioaddr + 8); +	inw(ioaddr + 10);	/* Total Rx and Tx octets. */ +	inw(ioaddr + 12); + +	/* Back to window 1, and turn statistics back on. */ +	EL3WINDOW(1); +	outw(StatsEnable, ioaddr + EL3_CMD); +} + +static int +el3_rx(struct net_device *dev) +{ +	int ioaddr = dev->base_addr; +	short rx_status; + +	if (el3_debug > 5) +		pr_debug("   In rx_packet(), status %4.4x, rx_status %4.4x.\n", +			   inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS)); +	while ((rx_status = inw(ioaddr + RX_STATUS)) > 0) { +		if (rx_status & 0x4000) { /* Error, update stats. */ +			short error = rx_status & 0x3800; + +			outw(RxDiscard, ioaddr + EL3_CMD); +			dev->stats.rx_errors++; +			switch (error) { +			case 0x0000:		dev->stats.rx_over_errors++; break; +			case 0x0800:		dev->stats.rx_length_errors++; break; +			case 0x1000:		dev->stats.rx_frame_errors++; break; +			case 0x1800:		dev->stats.rx_length_errors++; break; +			case 0x2000:		dev->stats.rx_frame_errors++; break; +			case 0x2800:		dev->stats.rx_crc_errors++; break; +			} +		} else { +			short pkt_len = rx_status & 0x7ff; +			struct sk_buff *skb; + +			skb = netdev_alloc_skb(dev, pkt_len + 5); +			if (el3_debug > 4) +				pr_debug("Receiving packet size %d status %4.4x.\n", +					   pkt_len, rx_status); +			if (skb != NULL) { +				skb_reserve(skb, 2);     /* Align IP on 16 byte */ + +				/* 'skb->data' points to the start of sk_buff data area. */ +				insl(ioaddr + RX_FIFO, skb_put(skb,pkt_len), +					 (pkt_len + 3) >> 2); + +				outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */ +				skb->protocol = eth_type_trans(skb,dev); +				netif_rx(skb); +				dev->stats.rx_bytes += pkt_len; +				dev->stats.rx_packets++; +				continue; +			} +			outw(RxDiscard, ioaddr + EL3_CMD); +			dev->stats.rx_dropped++; +			if (el3_debug) +				pr_debug("%s: Couldn't allocate a sk_buff of size %d.\n", +					   dev->name, pkt_len); +		} +		inw(ioaddr + EL3_STATUS); 				/* Delay. */ +		while (inw(ioaddr + EL3_STATUS) & 0x1000) +			pr_debug("	Waiting for 3c509 to discard packet, status %x.\n", +				   inw(ioaddr + EL3_STATUS) ); +	} + +	return 0; +} + +/* + *     Set or clear the multicast filter for this adaptor. + */ +static void +set_multicast_list(struct net_device *dev) +{ +	unsigned long flags; +	struct el3_private *lp = netdev_priv(dev); +	int ioaddr = dev->base_addr; +	int mc_count = netdev_mc_count(dev); + +	if (el3_debug > 1) { +		static int old; +		if (old != mc_count) { +			old = mc_count; +			pr_debug("%s: Setting Rx mode to %d addresses.\n", +				 dev->name, mc_count); +		} +	} +	spin_lock_irqsave(&lp->lock, flags); +	if (dev->flags&IFF_PROMISC) { +		outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm, +			 ioaddr + EL3_CMD); +	} +	else if (mc_count || (dev->flags&IFF_ALLMULTI)) { +		outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast, ioaddr + EL3_CMD); +	} +	else +		outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); +	spin_unlock_irqrestore(&lp->lock, flags); +} + +static int +el3_close(struct net_device *dev) +{ +	int ioaddr = dev->base_addr; +	struct el3_private *lp = netdev_priv(dev); + +	if (el3_debug > 2) +		pr_debug("%s: Shutting down ethercard.\n", dev->name); + +	el3_down(dev); + +	free_irq(dev->irq, dev); +	/* Switching back to window 0 disables the IRQ. */ +	EL3WINDOW(0); +	if (lp->type != EL3_EISA) { +		/* But we explicitly zero the IRQ line select anyway. Don't do +		 * it on EISA cards, it prevents the module from getting an +		 * IRQ after unload+reload... */ +		outw(0x0f00, ioaddr + WN0_IRQ); +	} + +	return 0; +} + +static int +el3_link_ok(struct net_device *dev) +{ +	int ioaddr = dev->base_addr; +	u16 tmp; + +	EL3WINDOW(4); +	tmp = inw(ioaddr + WN4_MEDIA); +	EL3WINDOW(1); +	return tmp & (1<<11); +} + +static int +el3_netdev_get_ecmd(struct net_device *dev, struct ethtool_cmd *ecmd) +{ +	u16 tmp; +	int ioaddr = dev->base_addr; + +	EL3WINDOW(0); +	/* obtain current transceiver via WN4_MEDIA? */ +	tmp = inw(ioaddr + WN0_ADDR_CONF); +	ecmd->transceiver = XCVR_INTERNAL; +	switch (tmp >> 14) { +	case 0: +		ecmd->port = PORT_TP; +		break; +	case 1: +		ecmd->port = PORT_AUI; +		ecmd->transceiver = XCVR_EXTERNAL; +		break; +	case 3: +		ecmd->port = PORT_BNC; +	default: +		break; +	} + +	ecmd->duplex = DUPLEX_HALF; +	ecmd->supported = 0; +	tmp = inw(ioaddr + WN0_CONF_CTRL); +	if (tmp & (1<<13)) +		ecmd->supported |= SUPPORTED_AUI; +	if (tmp & (1<<12)) +		ecmd->supported |= SUPPORTED_BNC; +	if (tmp & (1<<9)) { +		ecmd->supported |= SUPPORTED_TP | SUPPORTED_10baseT_Half | +				SUPPORTED_10baseT_Full;	/* hmm... */ +		EL3WINDOW(4); +		tmp = inw(ioaddr + WN4_NETDIAG); +		if (tmp & FD_ENABLE) +			ecmd->duplex = DUPLEX_FULL; +	} + +	ethtool_cmd_speed_set(ecmd, SPEED_10); +	EL3WINDOW(1); +	return 0; +} + +static int +el3_netdev_set_ecmd(struct net_device *dev, struct ethtool_cmd *ecmd) +{ +	u16 tmp; +	int ioaddr = dev->base_addr; + +	if (ecmd->speed != SPEED_10) +		return -EINVAL; +	if ((ecmd->duplex != DUPLEX_HALF) && (ecmd->duplex != DUPLEX_FULL)) +		return -EINVAL; +	if ((ecmd->transceiver != XCVR_INTERNAL) && (ecmd->transceiver != XCVR_EXTERNAL)) +		return -EINVAL; + +	/* change XCVR type */ +	EL3WINDOW(0); +	tmp = inw(ioaddr + WN0_ADDR_CONF); +	switch (ecmd->port) { +	case PORT_TP: +		tmp &= ~(3<<14); +		dev->if_port = 0; +		break; +	case PORT_AUI: +		tmp |= (1<<14); +		dev->if_port = 1; +		break; +	case PORT_BNC: +		tmp |= (3<<14); +		dev->if_port = 3; +		break; +	default: +		return -EINVAL; +	} + +	outw(tmp, ioaddr + WN0_ADDR_CONF); +	if (dev->if_port == 3) { +		/* fire up the DC-DC convertor if BNC gets enabled */ +		tmp = inw(ioaddr + WN0_ADDR_CONF); +		if (tmp & (3 << 14)) { +			outw(StartCoax, ioaddr + EL3_CMD); +			udelay(800); +		} else +			return -EIO; +	} + +	EL3WINDOW(4); +	tmp = inw(ioaddr + WN4_NETDIAG); +	if (ecmd->duplex == DUPLEX_FULL) +		tmp |= FD_ENABLE; +	else +		tmp &= ~FD_ENABLE; +	outw(tmp, ioaddr + WN4_NETDIAG); +	EL3WINDOW(1); + +	return 0; +} + +static void el3_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ +	strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); +	strlcpy(info->version, DRV_VERSION, sizeof(info->version)); +} + +static int el3_get_settings(struct net_device *dev, struct ethtool_cmd *ecmd) +{ +	struct el3_private *lp = netdev_priv(dev); +	int ret; + +	spin_lock_irq(&lp->lock); +	ret = el3_netdev_get_ecmd(dev, ecmd); +	spin_unlock_irq(&lp->lock); +	return ret; +} + +static int el3_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd) +{ +	struct el3_private *lp = netdev_priv(dev); +	int ret; + +	spin_lock_irq(&lp->lock); +	ret = el3_netdev_set_ecmd(dev, ecmd); +	spin_unlock_irq(&lp->lock); +	return ret; +} + +static u32 el3_get_link(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	u32 ret; + +	spin_lock_irq(&lp->lock); +	ret = el3_link_ok(dev); +	spin_unlock_irq(&lp->lock); +	return ret; +} + +static u32 el3_get_msglevel(struct net_device *dev) +{ +	return el3_debug; +} + +static void el3_set_msglevel(struct net_device *dev, u32 v) +{ +	el3_debug = v; +} + +static const struct ethtool_ops ethtool_ops = { +	.get_drvinfo = el3_get_drvinfo, +	.get_settings = el3_get_settings, +	.set_settings = el3_set_settings, +	.get_link = el3_get_link, +	.get_msglevel = el3_get_msglevel, +	.set_msglevel = el3_set_msglevel, +}; + +static void +el3_down(struct net_device *dev) +{ +	int ioaddr = dev->base_addr; + +	netif_stop_queue(dev); + +	/* Turn off statistics ASAP.  We update lp->stats below. */ +	outw(StatsDisable, ioaddr + EL3_CMD); + +	/* Disable the receiver and transmitter. */ +	outw(RxDisable, ioaddr + EL3_CMD); +	outw(TxDisable, ioaddr + EL3_CMD); + +	if (dev->if_port == 3) +		/* Turn off thinnet power.  Green! */ +		outw(StopCoax, ioaddr + EL3_CMD); +	else if (dev->if_port == 0) { +		/* Disable link beat and jabber, if_port may change here next open(). */ +		EL3WINDOW(4); +		outw(inw(ioaddr + WN4_MEDIA) & ~MEDIA_TP, ioaddr + WN4_MEDIA); +	} + +	outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD); + +	update_stats(dev); +} + +static void +el3_up(struct net_device *dev) +{ +	int i, sw_info, net_diag; +	int ioaddr = dev->base_addr; + +	/* Activating the board required and does no harm otherwise */ +	outw(0x0001, ioaddr + 4); + +	/* Set the IRQ line. */ +	outw((dev->irq << 12) | 0x0f00, ioaddr + WN0_IRQ); + +	/* Set the station address in window 2 each time opened. */ +	EL3WINDOW(2); + +	for (i = 0; i < 6; i++) +		outb(dev->dev_addr[i], ioaddr + i); + +	if ((dev->if_port & 0x03) == 3) /* BNC interface */ +		/* Start the thinnet transceiver. We should really wait 50ms...*/ +		outw(StartCoax, ioaddr + EL3_CMD); +	else if ((dev->if_port & 0x03) == 0) { /* 10baseT interface */ +		/* Combine secondary sw_info word (the adapter level) and primary +			sw_info word (duplex setting plus other useless bits) */ +		EL3WINDOW(0); +		sw_info = (read_eeprom(ioaddr, 0x14) & 0x400f) | +			(read_eeprom(ioaddr, 0x0d) & 0xBff0); + +		EL3WINDOW(4); +		net_diag = inw(ioaddr + WN4_NETDIAG); +		net_diag = (net_diag | FD_ENABLE); /* temporarily assume full-duplex will be set */ +		pr_info("%s: ", dev->name); +		switch (dev->if_port & 0x0c) { +			case 12: +				/* force full-duplex mode if 3c5x9b */ +				if (sw_info & 0x000f) { +					pr_cont("Forcing 3c5x9b full-duplex mode"); +					break; +				} +			case 8: +				/* set full-duplex mode based on eeprom config setting */ +				if ((sw_info & 0x000f) && (sw_info & 0x8000)) { +					pr_cont("Setting 3c5x9b full-duplex mode (from EEPROM configuration bit)"); +					break; +				} +			default: +				/* xcvr=(0 || 4) OR user has an old 3c5x9 non "B" model */ +				pr_cont("Setting 3c5x9/3c5x9B half-duplex mode"); +				net_diag = (net_diag & ~FD_ENABLE); /* disable full duplex */ +		} + +		outw(net_diag, ioaddr + WN4_NETDIAG); +		pr_cont(" if_port: %d, sw_info: %4.4x\n", dev->if_port, sw_info); +		if (el3_debug > 3) +			pr_debug("%s: 3c5x9 net diag word is now: %4.4x.\n", dev->name, net_diag); +		/* Enable link beat and jabber check. */ +		outw(inw(ioaddr + WN4_MEDIA) | MEDIA_TP, ioaddr + WN4_MEDIA); +	} + +	/* Switch to the stats window, and clear all stats by reading. */ +	outw(StatsDisable, ioaddr + EL3_CMD); +	EL3WINDOW(6); +	for (i = 0; i < 9; i++) +		inb(ioaddr + i); +	inw(ioaddr + 10); +	inw(ioaddr + 12); + +	/* Switch to register set 1 for normal use. */ +	EL3WINDOW(1); + +	/* Accept b-case and phys addr only. */ +	outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); +	outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + +	outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ +	outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ +	/* Allow status bits to be seen. */ +	outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); +	/* Ack all pending events, and set active indicator mask. */ +	outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, +		 ioaddr + EL3_CMD); +	outw(SetIntrEnb | IntLatch|TxAvailable|TxComplete|RxComplete|StatsFull, +		 ioaddr + EL3_CMD); + +	netif_start_queue(dev); +} + +/* Power Management support functions */ +#ifdef CONFIG_PM + +static int +el3_suspend(struct device *pdev, pm_message_t state) +{ +	unsigned long flags; +	struct net_device *dev; +	struct el3_private *lp; +	int ioaddr; + +	dev = dev_get_drvdata(pdev); +	lp = netdev_priv(dev); +	ioaddr = dev->base_addr; + +	spin_lock_irqsave(&lp->lock, flags); + +	if (netif_running(dev)) +		netif_device_detach(dev); + +	el3_down(dev); +	outw(PowerDown, ioaddr + EL3_CMD); + +	spin_unlock_irqrestore(&lp->lock, flags); +	return 0; +} + +static int +el3_resume(struct device *pdev) +{ +	unsigned long flags; +	struct net_device *dev; +	struct el3_private *lp; +	int ioaddr; + +	dev = dev_get_drvdata(pdev); +	lp = netdev_priv(dev); +	ioaddr = dev->base_addr; + +	spin_lock_irqsave(&lp->lock, flags); + +	outw(PowerUp, ioaddr + EL3_CMD); +	EL3WINDOW(0); +	el3_up(dev); + +	if (netif_running(dev)) +		netif_device_attach(dev); + +	spin_unlock_irqrestore(&lp->lock, flags); +	return 0; +} + +#endif /* CONFIG_PM */ + +module_param(debug,int, 0); +module_param_array(irq, int, NULL, 0); +module_param(max_interrupt_work, int, 0); +MODULE_PARM_DESC(debug, "debug level (0-6)"); +MODULE_PARM_DESC(irq, "IRQ number(s) (assigned)"); +MODULE_PARM_DESC(max_interrupt_work, "maximum events handled per interrupt"); +#ifdef CONFIG_PNP +module_param(nopnp, int, 0); +MODULE_PARM_DESC(nopnp, "disable ISA PnP support (0-1)"); +#endif	/* CONFIG_PNP */ +MODULE_DESCRIPTION("3Com Etherlink III (3c509, 3c509B, 3c529, 3c579) ethernet driver"); +MODULE_LICENSE("GPL"); + +static int __init el3_init_module(void) +{ +	int ret = 0; + +	if (debug >= 0) +		el3_debug = debug; + +#ifdef CONFIG_PNP +	if (!nopnp) { +		ret = pnp_register_driver(&el3_pnp_driver); +		if (!ret) +			pnp_registered = 1; +	} +#endif +	/* Select an open I/O location at 0x1*0 to do ISA contention select. */ +	/* Start with 0x110 to avoid some sound cards.*/ +	for (id_port = 0x110 ; id_port < 0x200; id_port += 0x10) { +		if (!request_region(id_port, 1, "3c509-control")) +			continue; +		outb(0x00, id_port); +		outb(0xff, id_port); +		if (inb(id_port) & 0x01) +			break; +		else +			release_region(id_port, 1); +	} +	if (id_port >= 0x200) { +		id_port = 0; +		pr_err("No I/O port available for 3c509 activation.\n"); +	} else { +		ret = isa_register_driver(&el3_isa_driver, EL3_MAX_CARDS); +		if (!ret) +			isa_registered = 1; +	} +#ifdef CONFIG_EISA +	ret = eisa_driver_register(&el3_eisa_driver); +	if (!ret) +		eisa_registered = 1; +#endif + +#ifdef CONFIG_PNP +	if (pnp_registered) +		ret = 0; +#endif +	if (isa_registered) +		ret = 0; +#ifdef CONFIG_EISA +	if (eisa_registered) +		ret = 0; +#endif +	return ret; +} + +static void __exit el3_cleanup_module(void) +{ +#ifdef CONFIG_PNP +	if (pnp_registered) +		pnp_unregister_driver(&el3_pnp_driver); +#endif +	if (isa_registered) +		isa_unregister_driver(&el3_isa_driver); +	if (id_port) +		release_region(id_port, 1); +#ifdef CONFIG_EISA +	if (eisa_registered) +		eisa_driver_unregister(&el3_eisa_driver); +#endif +} + +module_init (el3_init_module); +module_exit (el3_cleanup_module); diff --git a/drivers/net/ethernet/3com/3c515.c b/drivers/net/ethernet/3com/3c515.c new file mode 100644 index 00000000000..94c656f5a05 --- /dev/null +++ b/drivers/net/ethernet/3com/3c515.c @@ -0,0 +1,1583 @@ +/* +	Written 1997-1998 by Donald Becker. + +	This software may be used and distributed according to the terms +	of the GNU General Public License, incorporated herein by reference. + +	This driver is for the 3Com ISA EtherLink XL "Corkscrew" 3c515 ethercard. + +	The author may be reached as becker@scyld.com, or C/O +	Scyld Computing Corporation +	410 Severn Ave., Suite 210 +	Annapolis MD 21403 + + +	2000/2/2- Added support for kernel-level ISAPnP +		by Stephen Frost <sfrost@snowman.net> and Alessandro Zummo +	Cleaned up for 2.3.x/softnet by Jeff Garzik and Alan Cox. + +	2001/11/17 - Added ethtool support (jgarzik) + +	2002/10/28 - Locking updates for 2.5 (alan@lxorguk.ukuu.org.uk) + +*/ + +#define DRV_NAME		"3c515" +#define DRV_VERSION		"0.99t-ac" +#define DRV_RELDATE		"28-Oct-2002" + +static char *version = +DRV_NAME ".c:v" DRV_VERSION " " DRV_RELDATE " becker@scyld.com and others\n"; + +#define CORKSCREW 1 + +/* "Knobs" that adjust features and parameters. */ +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. +   Setting to > 1512 effectively disables this feature. */ +static int rx_copybreak = 200; + +/* Allow setting MTU to a larger size, bypassing the normal ethernet setup. */ +static const int mtu = 1500; + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 20; + +/* Enable the automatic media selection code -- usually set. */ +#define AUTOMEDIA 1 + +/* Allow the use of fragment bus master transfers instead of only +   programmed-I/O for Vortex cards.  Full-bus-master transfers are always +   enabled by default on Boomerang cards.  If VORTEX_BUS_MASTER is defined, +   the feature may be turned on using 'options'. */ +#define VORTEX_BUS_MASTER + +/* A few values that may be tweaked. */ +/* Keep the ring sizes a power of two for efficiency. */ +#define TX_RING_SIZE	16 +#define RX_RING_SIZE	16 +#define PKT_BUF_SZ		1536	/* Size of each temporary Rx buffer. */ + +#include <linux/module.h> +#include <linux/isapnp.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/ioport.h> +#include <linux/skbuff.h> +#include <linux/etherdevice.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/ethtool.h> +#include <linux/bitops.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/dma.h> + +#define NEW_MULTICAST +#include <linux/delay.h> + +#define MAX_UNITS 8 + +MODULE_AUTHOR("Donald Becker <becker@scyld.com>"); +MODULE_DESCRIPTION("3Com 3c515 Corkscrew driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); + +/* "Knobs" for adjusting internal parameters. */ +/* Put out somewhat more debugging messages. (0 - no msg, 1 minimal msgs). */ +#define DRIVER_DEBUG 1 +/* Some values here only for performance evaluation and path-coverage +   debugging. */ +static int rx_nocopy, rx_copy, queued_packet; + +/* Number of times to check to see if the Tx FIFO has space, used in some +   limited cases. */ +#define WAIT_TX_AVAIL 200 + +/* Operational parameter that usually are not changed. */ +#define TX_TIMEOUT  ((4*HZ)/10)	/* Time in jiffies before concluding Tx hung */ + +/* The size here is somewhat misleading: the Corkscrew also uses the ISA +   aliased registers at <base>+0x400. +   */ +#define CORKSCREW_TOTAL_SIZE 0x20 + +#ifdef DRIVER_DEBUG +static int corkscrew_debug = DRIVER_DEBUG; +#else +static int corkscrew_debug = 1; +#endif + +#define CORKSCREW_ID 10 + +/* +				Theory of Operation + +I. Board Compatibility + +This device driver is designed for the 3Com 3c515 ISA Fast EtherLink XL, +3Com's ISA bus adapter for Fast Ethernet.  Due to the unique I/O port layout, +it's not practical to integrate this driver with the other EtherLink drivers. + +II. Board-specific settings + +The Corkscrew has an EEPROM for configuration, but no special settings are +needed for Linux. + +III. Driver operation + +The 3c515 series use an interface that's very similar to the 3c900 "Boomerang" +PCI cards, with the bus master interface extensively modified to work with +the ISA bus. + +The card is capable of full-bus-master transfers with separate +lists of transmit and receive descriptors, similar to the AMD LANCE/PCnet, +DEC Tulip and Intel Speedo3. + +This driver uses a "RX_COPYBREAK" scheme rather than a fixed intermediate +receive buffer.  This scheme allocates full-sized skbuffs as receive +buffers.  The value RX_COPYBREAK is used as the copying breakpoint: it is +chosen to trade-off the memory wasted by passing the full-sized skbuff to +the queue layer for all frames vs. the copying cost of copying a frame to a +correctly-sized skbuff. + + +IIIC. Synchronization +The driver runs as two independent, single-threaded flows of control.  One +is the send-packet routine, which enforces single-threaded use by the netif +layer.  The other thread is the interrupt handler, which is single +threaded by the hardware and other software. + +IV. Notes + +Thanks to Terry Murphy of 3Com for providing documentation and a development +board. + +The names "Vortex", "Boomerang" and "Corkscrew" are the internal 3Com +project names.  I use these names to eliminate confusion -- 3Com product +numbers and names are very similar and often confused. + +The new chips support both ethernet (1.5K) and FDDI (4.5K) frame sizes! +This driver only supports ethernet frames because of the recent MTU limit +of 1.5K, but the changes to support 4.5K are minimal. +*/ + +/* Operational definitions. +   These are not used by other compilation units and thus are not +   exported in a ".h" file. + +   First the windows.  There are eight register windows, with the command +   and status registers available in each. +   */ +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e + +/* The top five bits written to EL3_CMD are a command, the lower +   11 bits are the parameter, if applicable. +   Note that 11 parameters bits was fine for ethernet, but the new chips +   can handle FDDI length frames (~4500 octets) and now parameters count +   32-bit 'Dwords' rather than octets. */ + +enum corkscrew_cmd { +	TotalReset = 0 << 11, SelectWindow = 1 << 11, StartCoax = 2 << 11, +	RxDisable = 3 << 11, RxEnable = 4 << 11, RxReset = 5 << 11, +	UpStall = 6 << 11, UpUnstall = (6 << 11) + 1, DownStall = (6 << 11) + 2, +	DownUnstall = (6 << 11) + 3, RxDiscard = 8 << 11, TxEnable = 9 << 11, +	TxDisable = 10 << 11, TxReset = 11 << 11, FakeIntr = 12 << 11, +	AckIntr = 13 << 11, SetIntrEnb = 14 << 11, SetStatusEnb = 15 << 11, +	SetRxFilter = 16 << 11, SetRxThreshold = 17 << 11, +	SetTxThreshold = 18 << 11, SetTxStart = 19 << 11, StartDMAUp = 20 << 11, +	StartDMADown = (20 << 11) + 1, StatsEnable = 21 << 11, +	StatsDisable = 22 << 11, StopCoax = 23 << 11, +}; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { +	RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 +}; + +/* Bits in the general status register. */ +enum corkscrew_status { +	IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, +	TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, +	IntReq = 0x0040, StatsFull = 0x0080, +	DMADone = 1 << 8, DownComplete = 1 << 9, UpComplete = 1 << 10, +	DMAInProgress = 1 << 11,	/* DMA controller is still busy. */ +	CmdInProgress = 1 << 12,	/* EL3_CMD is still busy. */ +}; + +/* Register window 1 offsets, the window used in normal operation. +   On the Corkscrew this window is always mapped at offsets 0x10-0x1f. */ +enum Window1 { +	TX_FIFO = 0x10, RX_FIFO = 0x10, RxErrors = 0x14, +	RxStatus = 0x18, Timer = 0x1A, TxStatus = 0x1B, +	TxFree = 0x1C,		/* Remaining free bytes in Tx buffer. */ +}; +enum Window0 { +	Wn0IRQ = 0x08, +#if defined(CORKSCREW) +	Wn0EepromCmd = 0x200A,	/* Corkscrew EEPROM command register. */ +	Wn0EepromData = 0x200C,	/* Corkscrew EEPROM results register. */ +#else +	Wn0EepromCmd = 10,	/* Window 0: EEPROM command register. */ +	Wn0EepromData = 12,	/* Window 0: EEPROM results register. */ +#endif +}; +enum Win0_EEPROM_bits { +	EEPROM_Read = 0x80, EEPROM_WRITE = 0x40, EEPROM_ERASE = 0xC0, +	EEPROM_EWENB = 0x30,	/* Enable erasing/writing for 10 msec. */ +	EEPROM_EWDIS = 0x00,	/* Disable EWENB before 10 msec timeout. */ +}; + +/* EEPROM locations. */ +enum eeprom_offset { +	PhysAddr01 = 0, PhysAddr23 = 1, PhysAddr45 = 2, ModelID = 3, +	EtherLink3ID = 7, +}; + +enum Window3 {			/* Window 3: MAC/config bits. */ +	Wn3_Config = 0, Wn3_MAC_Ctrl = 6, Wn3_Options = 8, +}; +enum wn3_config { +	Ram_size = 7, +	Ram_width = 8, +	Ram_speed = 0x30, +	Rom_size = 0xc0, +	Ram_split_shift = 16, +	Ram_split = 3 << Ram_split_shift, +	Xcvr_shift = 20, +	Xcvr = 7 << Xcvr_shift, +	Autoselect = 0x1000000, +}; + +enum Window4 { +	Wn4_NetDiag = 6, Wn4_Media = 10,	/* Window 4: Xcvr/media bits. */ +}; +enum Win4_Media_bits { +	Media_SQE = 0x0008,	/* Enable SQE error counting for AUI. */ +	Media_10TP = 0x00C0,	/* Enable link beat and jabber for 10baseT. */ +	Media_Lnk = 0x0080,	/* Enable just link beat for 100TX/100FX. */ +	Media_LnkBeat = 0x0800, +}; +enum Window7 {			/* Window 7: Bus Master control. */ +	Wn7_MasterAddr = 0, Wn7_MasterLen = 6, Wn7_MasterStatus = 12, +}; + +/* Boomerang-style bus master control registers.  Note ISA aliases! */ +enum MasterCtrl { +	PktStatus = 0x400, DownListPtr = 0x404, FragAddr = 0x408, FragLen = +	    0x40c, +	TxFreeThreshold = 0x40f, UpPktStatus = 0x410, UpListPtr = 0x418, +}; + +/* The Rx and Tx descriptor lists. +   Caution Alpha hackers: these types are 32 bits!  Note also the 8 byte +   alignment contraint on tx_ring[] and rx_ring[]. */ +struct boom_rx_desc { +	u32 next; +	s32 status; +	u32 addr; +	s32 length; +}; + +/* Values for the Rx status entry. */ +enum rx_desc_status { +	RxDComplete = 0x00008000, RxDError = 0x4000, +	/* See boomerang_rx() for actual error bits */ +}; + +struct boom_tx_desc { +	u32 next; +	s32 status; +	u32 addr; +	s32 length; +}; + +struct corkscrew_private { +	const char *product_name; +	struct list_head list; +	struct net_device *our_dev; +	/* The Rx and Tx rings are here to keep them quad-word-aligned. */ +	struct boom_rx_desc rx_ring[RX_RING_SIZE]; +	struct boom_tx_desc tx_ring[TX_RING_SIZE]; +	/* The addresses of transmit- and receive-in-place skbuffs. */ +	struct sk_buff *rx_skbuff[RX_RING_SIZE]; +	struct sk_buff *tx_skbuff[TX_RING_SIZE]; +	unsigned int cur_rx, cur_tx;	/* The next free ring entry */ +	unsigned int dirty_rx, dirty_tx;/* The ring entries to be free()ed. */ +	struct sk_buff *tx_skb;	/* Packet being eaten by bus master ctrl.  */ +	struct timer_list timer;	/* Media selection timer. */ +	int capabilities	;	/* Adapter capabilities word. */ +	int options;			/* User-settable misc. driver options. */ +	int last_rx_packets;		/* For media autoselection. */ +	unsigned int available_media:8,	/* From Wn3_Options */ +		media_override:3,	/* Passed-in media type. */ +		default_media:3,	/* Read from the EEPROM. */ +		full_duplex:1, autoselect:1, bus_master:1,	/* Vortex can only do a fragment bus-m. */ +		full_bus_master_tx:1, full_bus_master_rx:1,	/* Boomerang  */ +		tx_full:1; +	spinlock_t lock; +	struct device *dev; +}; + +/* The action to take with a media selection timer tick. +   Note that we deviate from the 3Com order by checking 10base2 before AUI. + */ +enum xcvr_types { +	XCVR_10baseT = 0, XCVR_AUI, XCVR_10baseTOnly, XCVR_10base2, XCVR_100baseTx, +	XCVR_100baseFx, XCVR_MII = 6, XCVR_Default = 8, +}; + +static struct media_table { +	char *name; +	unsigned int media_bits:16,	/* Bits to set in Wn4_Media register. */ +		mask:8,			/* The transceiver-present bit in Wn3_Config. */ +		next:8;			/* The media type to try next. */ +	short wait;			/* Time before we check media status. */ +} media_tbl[] = { +	{ "10baseT", Media_10TP, 0x08, XCVR_10base2, (14 * HZ) / 10 }, +	{ "10Mbs AUI", Media_SQE, 0x20, XCVR_Default, (1 * HZ) / 10}, +	{ "undefined", 0, 0x80, XCVR_10baseT, 10000}, +	{ "10base2", 0, 0x10, XCVR_AUI, (1 * HZ) / 10}, +	{ "100baseTX", Media_Lnk, 0x02, XCVR_100baseFx, (14 * HZ) / 10}, +	{ "100baseFX", Media_Lnk, 0x04, XCVR_MII, (14 * HZ) / 10}, +	{ "MII", 0, 0x40, XCVR_10baseT, 3 * HZ}, +	{ "undefined", 0, 0x01, XCVR_10baseT, 10000}, +	{ "Default", 0, 0xFF, XCVR_10baseT, 10000}, +}; + +#ifdef __ISAPNP__ +static struct isapnp_device_id corkscrew_isapnp_adapters[] = { +	{	ISAPNP_ANY_ID, ISAPNP_ANY_ID, +		ISAPNP_VENDOR('T', 'C', 'M'), ISAPNP_FUNCTION(0x5051), +		(long) "3Com Fast EtherLink ISA" }, +	{ }	/* terminate list */ +}; + +MODULE_DEVICE_TABLE(isapnp, corkscrew_isapnp_adapters); + +static int nopnp; +#endif /* __ISAPNP__ */ + +static struct net_device *corkscrew_scan(int unit); +static int corkscrew_setup(struct net_device *dev, int ioaddr, +			    struct pnp_dev *idev, int card_number); +static int corkscrew_open(struct net_device *dev); +static void corkscrew_timer(unsigned long arg); +static netdev_tx_t corkscrew_start_xmit(struct sk_buff *skb, +					struct net_device *dev); +static int corkscrew_rx(struct net_device *dev); +static void corkscrew_timeout(struct net_device *dev); +static int boomerang_rx(struct net_device *dev); +static irqreturn_t corkscrew_interrupt(int irq, void *dev_id); +static int corkscrew_close(struct net_device *dev); +static void update_stats(int addr, struct net_device *dev); +static struct net_device_stats *corkscrew_get_stats(struct net_device *dev); +static void set_rx_mode(struct net_device *dev); +static const struct ethtool_ops netdev_ethtool_ops; + + +/* +   Unfortunately maximizing the shared code between the integrated and +   module version of the driver results in a complicated set of initialization +   procedures. +   init_module() -- modules /  tc59x_init()  -- built-in +		The wrappers for corkscrew_scan() +   corkscrew_scan()  		 The common routine that scans for PCI and EISA cards +   corkscrew_found_device() Allocate a device structure when we find a card. +					Different versions exist for modules and built-in. +   corkscrew_probe1()		Fill in the device structure -- this is separated +					so that the modules code can put it in dev->init. +*/ +/* This driver uses 'options' to pass the media type, full-duplex flag, etc. */ +/* Note: this is the only limit on the number of cards supported!! */ +static int options[MAX_UNITS] = { -1, -1, -1, -1, -1, -1, -1, -1, }; + +#ifdef MODULE +static int debug = -1; + +module_param(debug, int, 0); +module_param_array(options, int, NULL, 0); +module_param(rx_copybreak, int, 0); +module_param(max_interrupt_work, int, 0); +MODULE_PARM_DESC(debug, "3c515 debug level (0-6)"); +MODULE_PARM_DESC(options, "3c515: Bits 0-2: media type, bit 3: full duplex, bit 4: bus mastering"); +MODULE_PARM_DESC(rx_copybreak, "3c515 copy breakpoint for copy-only-tiny-frames"); +MODULE_PARM_DESC(max_interrupt_work, "3c515 maximum events handled per interrupt"); + +/* A list of all installed Vortex devices, for removing the driver module. */ +/* we will need locking (and refcounting) if we ever use it for more */ +static LIST_HEAD(root_corkscrew_dev); + +int init_module(void) +{ +	int found = 0; +	if (debug >= 0) +		corkscrew_debug = debug; +	if (corkscrew_debug) +		pr_debug("%s", version); +	while (corkscrew_scan(-1)) +		found++; +	return found ? 0 : -ENODEV; +} + +#else +struct net_device *tc515_probe(int unit) +{ +	struct net_device *dev = corkscrew_scan(unit); +	static int printed; + +	if (!dev) +		return ERR_PTR(-ENODEV); + +	if (corkscrew_debug > 0 && !printed) { +		printed = 1; +		pr_debug("%s", version); +	} + +	return dev; +} +#endif				/* not MODULE */ + +static int check_device(unsigned ioaddr) +{ +	int timer; + +	if (!request_region(ioaddr, CORKSCREW_TOTAL_SIZE, "3c515")) +		return 0; +	/* Check the resource configuration for a matching ioaddr. */ +	if ((inw(ioaddr + 0x2002) & 0x1f0) != (ioaddr & 0x1f0)) { +		release_region(ioaddr, CORKSCREW_TOTAL_SIZE); +		return 0; +	} +	/* Verify by reading the device ID from the EEPROM. */ +	outw(EEPROM_Read + 7, ioaddr + Wn0EepromCmd); +	/* Pause for at least 162 us. for the read to take place. */ +	for (timer = 4; timer >= 0; timer--) { +		udelay(162); +		if ((inw(ioaddr + Wn0EepromCmd) & 0x0200) == 0) +			break; +	} +	if (inw(ioaddr + Wn0EepromData) != 0x6d50) { +		release_region(ioaddr, CORKSCREW_TOTAL_SIZE); +		return 0; +	} +	return 1; +} + +static void cleanup_card(struct net_device *dev) +{ +	struct corkscrew_private *vp = netdev_priv(dev); +	list_del_init(&vp->list); +	if (dev->dma) +		free_dma(dev->dma); +	outw(TotalReset, dev->base_addr + EL3_CMD); +	release_region(dev->base_addr, CORKSCREW_TOTAL_SIZE); +	if (vp->dev) +		pnp_device_detach(to_pnp_dev(vp->dev)); +} + +static struct net_device *corkscrew_scan(int unit) +{ +	struct net_device *dev; +	static int cards_found = 0; +	static int ioaddr; +	int err; +#ifdef __ISAPNP__ +	short i; +	static int pnp_cards; +#endif + +	dev = alloc_etherdev(sizeof(struct corkscrew_private)); +	if (!dev) +		return ERR_PTR(-ENOMEM); + +	if (unit >= 0) { +		sprintf(dev->name, "eth%d", unit); +		netdev_boot_setup_check(dev); +	} + +#ifdef __ISAPNP__ +	if(nopnp == 1) +		goto no_pnp; +	for(i=0; corkscrew_isapnp_adapters[i].vendor != 0; i++) { +		struct pnp_dev *idev = NULL; +		int irq; +		while((idev = pnp_find_dev(NULL, +					   corkscrew_isapnp_adapters[i].vendor, +					   corkscrew_isapnp_adapters[i].function, +					   idev))) { + +			if (pnp_device_attach(idev) < 0) +				continue; +			if (pnp_activate_dev(idev) < 0) { +				pr_warning("pnp activate failed (out of resources?)\n"); +				pnp_device_detach(idev); +				continue; +			} +			if (!pnp_port_valid(idev, 0) || !pnp_irq_valid(idev, 0)) { +				pnp_device_detach(idev); +				continue; +			} +			ioaddr = pnp_port_start(idev, 0); +			irq = pnp_irq(idev, 0); +			if (!check_device(ioaddr)) { +				pnp_device_detach(idev); +				continue; +			} +			if(corkscrew_debug) +				pr_debug("ISAPNP reports %s at i/o 0x%x, irq %d\n", +					(char*) corkscrew_isapnp_adapters[i].driver_data, ioaddr, irq); +			pr_info("3c515 Resource configuration register %#4.4x, DCR %4.4x.\n", +		     		inl(ioaddr + 0x2002), inw(ioaddr + 0x2000)); +			/* irq = inw(ioaddr + 0x2002) & 15; */ /* Use the irq from isapnp */ +			SET_NETDEV_DEV(dev, &idev->dev); +			pnp_cards++; +			err = corkscrew_setup(dev, ioaddr, idev, cards_found++); +			if (!err) +				return dev; +			cleanup_card(dev); +		} +	} +no_pnp: +#endif /* __ISAPNP__ */ + +	/* Check all locations on the ISA bus -- evil! */ +	for (ioaddr = 0x100; ioaddr < 0x400; ioaddr += 0x20) { +		if (!check_device(ioaddr)) +			continue; + +		pr_info("3c515 Resource configuration register %#4.4x, DCR %4.4x.\n", +		     inl(ioaddr + 0x2002), inw(ioaddr + 0x2000)); +		err = corkscrew_setup(dev, ioaddr, NULL, cards_found++); +		if (!err) +			return dev; +		cleanup_card(dev); +	} +	free_netdev(dev); +	return NULL; +} + + +static const struct net_device_ops netdev_ops = { +	.ndo_open		= corkscrew_open, +	.ndo_stop		= corkscrew_close, +	.ndo_start_xmit		= corkscrew_start_xmit, +	.ndo_tx_timeout		= corkscrew_timeout, +	.ndo_get_stats		= corkscrew_get_stats, +	.ndo_set_rx_mode	= set_rx_mode, +	.ndo_change_mtu		= eth_change_mtu, +	.ndo_set_mac_address 	= eth_mac_addr, +	.ndo_validate_addr	= eth_validate_addr, +}; + + +static int corkscrew_setup(struct net_device *dev, int ioaddr, +			    struct pnp_dev *idev, int card_number) +{ +	struct corkscrew_private *vp = netdev_priv(dev); +	unsigned int eeprom[0x40], checksum = 0;	/* EEPROM contents */ +	int i; +	int irq; + +#ifdef __ISAPNP__ +	if (idev) { +		irq = pnp_irq(idev, 0); +		vp->dev = &idev->dev; +	} else { +		irq = inw(ioaddr + 0x2002) & 15; +	} +#else +	irq = inw(ioaddr + 0x2002) & 15; +#endif + +	dev->base_addr = ioaddr; +	dev->irq = irq; +	dev->dma = inw(ioaddr + 0x2000) & 7; +	vp->product_name = "3c515"; +	vp->options = dev->mem_start; +	vp->our_dev = dev; + +	if (!vp->options) { +		 if (card_number >= MAX_UNITS) +			vp->options = -1; +		else +			vp->options = options[card_number]; +	} + +	if (vp->options >= 0) { +		vp->media_override = vp->options & 7; +		if (vp->media_override == 2) +			vp->media_override = 0; +		vp->full_duplex = (vp->options & 8) ? 1 : 0; +		vp->bus_master = (vp->options & 16) ? 1 : 0; +	} else { +		vp->media_override = 7; +		vp->full_duplex = 0; +		vp->bus_master = 0; +	} +#ifdef MODULE +	list_add(&vp->list, &root_corkscrew_dev); +#endif + +	pr_info("%s: 3Com %s at %#3x,", dev->name, vp->product_name, ioaddr); + +	spin_lock_init(&vp->lock); + +	/* Read the station address from the EEPROM. */ +	EL3WINDOW(0); +	for (i = 0; i < 0x18; i++) { +		__be16 *phys_addr = (__be16 *) dev->dev_addr; +		int timer; +		outw(EEPROM_Read + i, ioaddr + Wn0EepromCmd); +		/* Pause for at least 162 us. for the read to take place. */ +		for (timer = 4; timer >= 0; timer--) { +			udelay(162); +			if ((inw(ioaddr + Wn0EepromCmd) & 0x0200) == 0) +				break; +		} +		eeprom[i] = inw(ioaddr + Wn0EepromData); +		checksum ^= eeprom[i]; +		if (i < 3) +			phys_addr[i] = htons(eeprom[i]); +	} +	checksum = (checksum ^ (checksum >> 8)) & 0xff; +	if (checksum != 0x00) +		pr_cont(" ***INVALID CHECKSUM %4.4x*** ", checksum); +	pr_cont(" %pM", dev->dev_addr); +	if (eeprom[16] == 0x11c7) {	/* Corkscrew */ +		if (request_dma(dev->dma, "3c515")) { +			pr_cont(", DMA %d allocation failed", dev->dma); +			dev->dma = 0; +		} else +			pr_cont(", DMA %d", dev->dma); +	} +	pr_cont(", IRQ %d\n", dev->irq); +	/* Tell them about an invalid IRQ. */ +	if (corkscrew_debug && (dev->irq <= 0 || dev->irq > 15)) +		pr_warning(" *** Warning: this IRQ is unlikely to work! ***\n"); + +	{ +		static const char * const ram_split[] = { +			"5:3", "3:1", "1:1", "3:5" +		}; +		__u32 config; +		EL3WINDOW(3); +		vp->available_media = inw(ioaddr + Wn3_Options); +		config = inl(ioaddr + Wn3_Config); +		if (corkscrew_debug > 1) +			pr_info("  Internal config register is %4.4x, transceivers %#x.\n", +				config, inw(ioaddr + Wn3_Options)); +		pr_info("  %dK %s-wide RAM %s Rx:Tx split, %s%s interface.\n", +			8 << config & Ram_size, +			config & Ram_width ? "word" : "byte", +			ram_split[(config & Ram_split) >> Ram_split_shift], +			config & Autoselect ? "autoselect/" : "", +			media_tbl[(config & Xcvr) >> Xcvr_shift].name); +		vp->default_media = (config & Xcvr) >> Xcvr_shift; +		vp->autoselect = config & Autoselect ? 1 : 0; +		dev->if_port = vp->default_media; +	} +	if (vp->media_override != 7) { +		pr_info("  Media override to transceiver type %d (%s).\n", +		       vp->media_override, +		       media_tbl[vp->media_override].name); +		dev->if_port = vp->media_override; +	} + +	vp->capabilities = eeprom[16]; +	vp->full_bus_master_tx = (vp->capabilities & 0x20) ? 1 : 0; +	/* Rx is broken at 10mbps, so we always disable it. */ +	/* vp->full_bus_master_rx = 0; */ +	vp->full_bus_master_rx = (vp->capabilities & 0x20) ? 1 : 0; + +	/* The 3c51x-specific entries in the device structure. */ +	dev->netdev_ops = &netdev_ops; +	dev->watchdog_timeo = (400 * HZ) / 1000; +	dev->ethtool_ops = &netdev_ethtool_ops; + +	return register_netdev(dev); +} + + +static int corkscrew_open(struct net_device *dev) +{ +	int ioaddr = dev->base_addr; +	struct corkscrew_private *vp = netdev_priv(dev); +	__u32 config; +	int i; + +	/* Before initializing select the active media port. */ +	EL3WINDOW(3); +	if (vp->full_duplex) +		outb(0x20, ioaddr + Wn3_MAC_Ctrl);	/* Set the full-duplex bit. */ +	config = inl(ioaddr + Wn3_Config); + +	if (vp->media_override != 7) { +		if (corkscrew_debug > 1) +			pr_info("%s: Media override to transceiver %d (%s).\n", +				dev->name, vp->media_override, +				media_tbl[vp->media_override].name); +		dev->if_port = vp->media_override; +	} else if (vp->autoselect) { +		/* Find first available media type, starting with 100baseTx. */ +		dev->if_port = 4; +		while (!(vp->available_media & media_tbl[dev->if_port].mask)) +			dev->if_port = media_tbl[dev->if_port].next; + +		if (corkscrew_debug > 1) +			pr_debug("%s: Initial media type %s.\n", +			       dev->name, media_tbl[dev->if_port].name); + +		init_timer(&vp->timer); +		vp->timer.expires = jiffies + media_tbl[dev->if_port].wait; +		vp->timer.data = (unsigned long) dev; +		vp->timer.function = corkscrew_timer;	/* timer handler */ +		add_timer(&vp->timer); +	} else +		dev->if_port = vp->default_media; + +	config = (config & ~Xcvr) | (dev->if_port << Xcvr_shift); +	outl(config, ioaddr + Wn3_Config); + +	if (corkscrew_debug > 1) { +		pr_debug("%s: corkscrew_open() InternalConfig %8.8x.\n", +		       dev->name, config); +	} + +	outw(TxReset, ioaddr + EL3_CMD); +	for (i = 20; i >= 0; i--) +		if (!(inw(ioaddr + EL3_STATUS) & CmdInProgress)) +			break; + +	outw(RxReset, ioaddr + EL3_CMD); +	/* Wait a few ticks for the RxReset command to complete. */ +	for (i = 20; i >= 0; i--) +		if (!(inw(ioaddr + EL3_STATUS) & CmdInProgress)) +			break; + +	outw(SetStatusEnb | 0x00, ioaddr + EL3_CMD); + +	/* Use the now-standard shared IRQ implementation. */ +	if (vp->capabilities == 0x11c7) { +		/* Corkscrew: Cannot share ISA resources. */ +		if (dev->irq == 0 || +		    dev->dma == 0 || +		    request_irq(dev->irq, corkscrew_interrupt, 0, +				vp->product_name, dev)) +			return -EAGAIN; +		enable_dma(dev->dma); +		set_dma_mode(dev->dma, DMA_MODE_CASCADE); +	} else if (request_irq(dev->irq, corkscrew_interrupt, IRQF_SHARED, +			       vp->product_name, dev)) { +		return -EAGAIN; +	} + +	if (corkscrew_debug > 1) { +		EL3WINDOW(4); +		pr_debug("%s: corkscrew_open() irq %d media status %4.4x.\n", +		       dev->name, dev->irq, inw(ioaddr + Wn4_Media)); +	} + +	/* Set the station address and mask in window 2 each time opened. */ +	EL3WINDOW(2); +	for (i = 0; i < 6; i++) +		outb(dev->dev_addr[i], ioaddr + i); +	for (; i < 12; i += 2) +		outw(0, ioaddr + i); + +	if (dev->if_port == 3) +		/* Start the thinnet transceiver. We should really wait 50ms... */ +		outw(StartCoax, ioaddr + EL3_CMD); +	EL3WINDOW(4); +	outw((inw(ioaddr + Wn4_Media) & ~(Media_10TP | Media_SQE)) | +	     media_tbl[dev->if_port].media_bits, ioaddr + Wn4_Media); + +	/* Switch to the stats window, and clear all stats by reading. */ +	outw(StatsDisable, ioaddr + EL3_CMD); +	EL3WINDOW(6); +	for (i = 0; i < 10; i++) +		inb(ioaddr + i); +	inw(ioaddr + 10); +	inw(ioaddr + 12); +	/* New: On the Vortex we must also clear the BadSSD counter. */ +	EL3WINDOW(4); +	inb(ioaddr + 12); +	/* ..and on the Boomerang we enable the extra statistics bits. */ +	outw(0x0040, ioaddr + Wn4_NetDiag); + +	/* Switch to register set 7 for normal use. */ +	EL3WINDOW(7); + +	if (vp->full_bus_master_rx) {	/* Boomerang bus master. */ +		vp->cur_rx = vp->dirty_rx = 0; +		if (corkscrew_debug > 2) +			pr_debug("%s:  Filling in the Rx ring.\n", dev->name); +		for (i = 0; i < RX_RING_SIZE; i++) { +			struct sk_buff *skb; +			if (i < (RX_RING_SIZE - 1)) +				vp->rx_ring[i].next = +				    isa_virt_to_bus(&vp->rx_ring[i + 1]); +			else +				vp->rx_ring[i].next = 0; +			vp->rx_ring[i].status = 0;	/* Clear complete bit. */ +			vp->rx_ring[i].length = PKT_BUF_SZ | 0x80000000; +			skb = netdev_alloc_skb(dev, PKT_BUF_SZ); +			vp->rx_skbuff[i] = skb; +			if (skb == NULL) +				break;	/* Bad news!  */ +			skb_reserve(skb, 2);	/* Align IP on 16 byte boundaries */ +			vp->rx_ring[i].addr = isa_virt_to_bus(skb->data); +		} +		if (i != 0) +			vp->rx_ring[i - 1].next = +				isa_virt_to_bus(&vp->rx_ring[0]);	/* Wrap the ring. */ +		outl(isa_virt_to_bus(&vp->rx_ring[0]), ioaddr + UpListPtr); +	} +	if (vp->full_bus_master_tx) {	/* Boomerang bus master Tx. */ +		vp->cur_tx = vp->dirty_tx = 0; +		outb(PKT_BUF_SZ >> 8, ioaddr + TxFreeThreshold);	/* Room for a packet. */ +		/* Clear the Tx ring. */ +		for (i = 0; i < TX_RING_SIZE; i++) +			vp->tx_skbuff[i] = NULL; +		outl(0, ioaddr + DownListPtr); +	} +	/* Set receiver mode: presumably accept b-case and phys addr only. */ +	set_rx_mode(dev); +	outw(StatsEnable, ioaddr + EL3_CMD);	/* Turn on statistics. */ + +	netif_start_queue(dev); + +	outw(RxEnable, ioaddr + EL3_CMD);	/* Enable the receiver. */ +	outw(TxEnable, ioaddr + EL3_CMD);	/* Enable transmitter. */ +	/* Allow status bits to be seen. */ +	outw(SetStatusEnb | AdapterFailure | IntReq | StatsFull | +	     (vp->full_bus_master_tx ? DownComplete : TxAvailable) | +	     (vp->full_bus_master_rx ? UpComplete : RxComplete) | +	     (vp->bus_master ? DMADone : 0), ioaddr + EL3_CMD); +	/* Ack all pending events, and set active indicator mask. */ +	outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, +	     ioaddr + EL3_CMD); +	outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull +	     | (vp->bus_master ? DMADone : 0) | UpComplete | DownComplete, +	     ioaddr + EL3_CMD); + +	return 0; +} + +static void corkscrew_timer(unsigned long data) +{ +#ifdef AUTOMEDIA +	struct net_device *dev = (struct net_device *) data; +	struct corkscrew_private *vp = netdev_priv(dev); +	int ioaddr = dev->base_addr; +	unsigned long flags; +	int ok = 0; + +	if (corkscrew_debug > 1) +		pr_debug("%s: Media selection timer tick happened, %s.\n", +		       dev->name, media_tbl[dev->if_port].name); + +	spin_lock_irqsave(&vp->lock, flags); + +	{ +		int old_window = inw(ioaddr + EL3_CMD) >> 13; +		int media_status; +		EL3WINDOW(4); +		media_status = inw(ioaddr + Wn4_Media); +		switch (dev->if_port) { +		case 0: +		case 4: +		case 5:	/* 10baseT, 100baseTX, 100baseFX  */ +			if (media_status & Media_LnkBeat) { +				ok = 1; +				if (corkscrew_debug > 1) +					pr_debug("%s: Media %s has link beat, %x.\n", +						dev->name, +						media_tbl[dev->if_port].name, +						media_status); +			} else if (corkscrew_debug > 1) +				pr_debug("%s: Media %s is has no link beat, %x.\n", +					dev->name, +					media_tbl[dev->if_port].name, +					media_status); + +			break; +		default:	/* Other media types handled by Tx timeouts. */ +			if (corkscrew_debug > 1) +				pr_debug("%s: Media %s is has no indication, %x.\n", +					dev->name, +					media_tbl[dev->if_port].name, +					media_status); +			ok = 1; +		} +		if (!ok) { +			__u32 config; + +			do { +				dev->if_port = +				    media_tbl[dev->if_port].next; +			} +			while (!(vp->available_media & media_tbl[dev->if_port].mask)); + +			if (dev->if_port == 8) {	/* Go back to default. */ +				dev->if_port = vp->default_media; +				if (corkscrew_debug > 1) +					pr_debug("%s: Media selection failing, using default %s port.\n", +						dev->name, +						media_tbl[dev->if_port].name); +			} else { +				if (corkscrew_debug > 1) +					pr_debug("%s: Media selection failed, now trying %s port.\n", +						dev->name, +						media_tbl[dev->if_port].name); +				vp->timer.expires = jiffies + media_tbl[dev->if_port].wait; +				add_timer(&vp->timer); +			} +			outw((media_status & ~(Media_10TP | Media_SQE)) | +			     media_tbl[dev->if_port].media_bits, +			     ioaddr + Wn4_Media); + +			EL3WINDOW(3); +			config = inl(ioaddr + Wn3_Config); +			config = (config & ~Xcvr) | (dev->if_port << Xcvr_shift); +			outl(config, ioaddr + Wn3_Config); + +			outw(dev->if_port == 3 ? StartCoax : StopCoax, +			     ioaddr + EL3_CMD); +		} +		EL3WINDOW(old_window); +	} + +	spin_unlock_irqrestore(&vp->lock, flags); +	if (corkscrew_debug > 1) +		pr_debug("%s: Media selection timer finished, %s.\n", +		       dev->name, media_tbl[dev->if_port].name); + +#endif				/* AUTOMEDIA */ +} + +static void corkscrew_timeout(struct net_device *dev) +{ +	int i; +	struct corkscrew_private *vp = netdev_priv(dev); +	int ioaddr = dev->base_addr; + +	pr_warning("%s: transmit timed out, tx_status %2.2x status %4.4x.\n", +	       dev->name, inb(ioaddr + TxStatus), +	       inw(ioaddr + EL3_STATUS)); +	/* Slight code bloat to be user friendly. */ +	if ((inb(ioaddr + TxStatus) & 0x88) == 0x88) +		pr_warning("%s: Transmitter encountered 16 collisions --" +		       " network cable problem?\n", dev->name); +#ifndef final_version +	pr_debug("  Flags; bus-master %d, full %d; dirty %d current %d.\n", +	       vp->full_bus_master_tx, vp->tx_full, vp->dirty_tx, +	       vp->cur_tx); +	pr_debug("  Down list %8.8x vs. %p.\n", inl(ioaddr + DownListPtr), +	       &vp->tx_ring[0]); +	for (i = 0; i < TX_RING_SIZE; i++) { +		pr_debug("  %d: %p  length %8.8x status %8.8x\n", i, +		       &vp->tx_ring[i], +		       vp->tx_ring[i].length, vp->tx_ring[i].status); +	} +#endif +	/* Issue TX_RESET and TX_START commands. */ +	outw(TxReset, ioaddr + EL3_CMD); +	for (i = 20; i >= 0; i--) +		if (!(inw(ioaddr + EL3_STATUS) & CmdInProgress)) +			break; +	outw(TxEnable, ioaddr + EL3_CMD); +	dev->trans_start = jiffies; /* prevent tx timeout */ +	dev->stats.tx_errors++; +	dev->stats.tx_dropped++; +	netif_wake_queue(dev); +} + +static netdev_tx_t corkscrew_start_xmit(struct sk_buff *skb, +					struct net_device *dev) +{ +	struct corkscrew_private *vp = netdev_priv(dev); +	int ioaddr = dev->base_addr; + +	/* Block a timer-based transmit from overlapping. */ + +	netif_stop_queue(dev); + +	if (vp->full_bus_master_tx) {	/* BOOMERANG bus-master */ +		/* Calculate the next Tx descriptor entry. */ +		int entry = vp->cur_tx % TX_RING_SIZE; +		struct boom_tx_desc *prev_entry; +		unsigned long flags; +		int i; + +		if (vp->tx_full)	/* No room to transmit with */ +			return NETDEV_TX_BUSY; +		if (vp->cur_tx != 0) +			prev_entry = &vp->tx_ring[(vp->cur_tx - 1) % TX_RING_SIZE]; +		else +			prev_entry = NULL; +		if (corkscrew_debug > 3) +			pr_debug("%s: Trying to send a packet, Tx index %d.\n", +				dev->name, vp->cur_tx); +		/* vp->tx_full = 1; */ +		vp->tx_skbuff[entry] = skb; +		vp->tx_ring[entry].next = 0; +		vp->tx_ring[entry].addr = isa_virt_to_bus(skb->data); +		vp->tx_ring[entry].length = skb->len | 0x80000000; +		vp->tx_ring[entry].status = skb->len | 0x80000000; + +		spin_lock_irqsave(&vp->lock, flags); +		outw(DownStall, ioaddr + EL3_CMD); +		/* Wait for the stall to complete. */ +		for (i = 20; i >= 0; i--) +			if ((inw(ioaddr + EL3_STATUS) & CmdInProgress) == 0) +				break; +		if (prev_entry) +			prev_entry->next = isa_virt_to_bus(&vp->tx_ring[entry]); +		if (inl(ioaddr + DownListPtr) == 0) { +			outl(isa_virt_to_bus(&vp->tx_ring[entry]), +			     ioaddr + DownListPtr); +			queued_packet++; +		} +		outw(DownUnstall, ioaddr + EL3_CMD); +		spin_unlock_irqrestore(&vp->lock, flags); + +		vp->cur_tx++; +		if (vp->cur_tx - vp->dirty_tx > TX_RING_SIZE - 1) +			vp->tx_full = 1; +		else {		/* Clear previous interrupt enable. */ +			if (prev_entry) +				prev_entry->status &= ~0x80000000; +			netif_wake_queue(dev); +		} +		return NETDEV_TX_OK; +	} +	/* Put out the doubleword header... */ +	outl(skb->len, ioaddr + TX_FIFO); +	dev->stats.tx_bytes += skb->len; +#ifdef VORTEX_BUS_MASTER +	if (vp->bus_master) { +		/* Set the bus-master controller to transfer the packet. */ +		outl((int) (skb->data), ioaddr + Wn7_MasterAddr); +		outw((skb->len + 3) & ~3, ioaddr + Wn7_MasterLen); +		vp->tx_skb = skb; +		outw(StartDMADown, ioaddr + EL3_CMD); +		/* queue will be woken at the DMADone interrupt. */ +	} else { +		/* ... and the packet rounded to a doubleword. */ +		outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); +		dev_kfree_skb(skb); +		if (inw(ioaddr + TxFree) > 1536) { +			netif_wake_queue(dev); +		} else +			/* Interrupt us when the FIFO has room for max-sized packet. */ +			outw(SetTxThreshold + (1536 >> 2), +			     ioaddr + EL3_CMD); +	} +#else +	/* ... and the packet rounded to a doubleword. */ +	outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); +	dev_kfree_skb(skb); +	if (inw(ioaddr + TxFree) > 1536) { +		netif_wake_queue(dev); +	} else +		/* Interrupt us when the FIFO has room for max-sized packet. */ +		outw(SetTxThreshold + (1536 >> 2), ioaddr + EL3_CMD); +#endif				/* bus master */ + + +	/* Clear the Tx status stack. */ +	{ +		short tx_status; +		int i = 4; + +		while (--i > 0 && (tx_status = inb(ioaddr + TxStatus)) > 0) { +			if (tx_status & 0x3C) {	/* A Tx-disabling error occurred.  */ +				if (corkscrew_debug > 2) +					pr_debug("%s: Tx error, status %2.2x.\n", +						dev->name, tx_status); +				if (tx_status & 0x04) +					dev->stats.tx_fifo_errors++; +				if (tx_status & 0x38) +					dev->stats.tx_aborted_errors++; +				if (tx_status & 0x30) { +					int j; +					outw(TxReset, ioaddr + EL3_CMD); +					for (j = 20; j >= 0; j--) +						if (!(inw(ioaddr + EL3_STATUS) & CmdInProgress)) +							break; +				} +				outw(TxEnable, ioaddr + EL3_CMD); +			} +			outb(0x00, ioaddr + TxStatus);	/* Pop the status stack. */ +		} +	} +	return NETDEV_TX_OK; +} + +/* The interrupt handler does all of the Rx thread work and cleans up +   after the Tx thread. */ + +static irqreturn_t corkscrew_interrupt(int irq, void *dev_id) +{ +	/* Use the now-standard shared IRQ implementation. */ +	struct net_device *dev = dev_id; +	struct corkscrew_private *lp = netdev_priv(dev); +	int ioaddr, status; +	int latency; +	int i = max_interrupt_work; + +	ioaddr = dev->base_addr; +	latency = inb(ioaddr + Timer); + +	spin_lock(&lp->lock); + +	status = inw(ioaddr + EL3_STATUS); + +	if (corkscrew_debug > 4) +		pr_debug("%s: interrupt, status %4.4x, timer %d.\n", +			dev->name, status, latency); +	if ((status & 0xE000) != 0xE000) { +		static int donedidthis; +		/* Some interrupt controllers store a bogus interrupt from boot-time. +		   Ignore a single early interrupt, but don't hang the machine for +		   other interrupt problems. */ +		if (donedidthis++ > 100) { +			pr_err("%s: Bogus interrupt, bailing. Status %4.4x, start=%d.\n", +				   dev->name, status, netif_running(dev)); +			free_irq(dev->irq, dev); +			dev->irq = -1; +		} +	} + +	do { +		if (corkscrew_debug > 5) +			pr_debug("%s: In interrupt loop, status %4.4x.\n", +			       dev->name, status); +		if (status & RxComplete) +			corkscrew_rx(dev); + +		if (status & TxAvailable) { +			if (corkscrew_debug > 5) +				pr_debug("	TX room bit was handled.\n"); +			/* There's room in the FIFO for a full-sized packet. */ +			outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); +			netif_wake_queue(dev); +		} +		if (status & DownComplete) { +			unsigned int dirty_tx = lp->dirty_tx; + +			while (lp->cur_tx - dirty_tx > 0) { +				int entry = dirty_tx % TX_RING_SIZE; +				if (inl(ioaddr + DownListPtr) == isa_virt_to_bus(&lp->tx_ring[entry])) +					break;	/* It still hasn't been processed. */ +				if (lp->tx_skbuff[entry]) { +					dev_kfree_skb_irq(lp->tx_skbuff[entry]); +					lp->tx_skbuff[entry] = NULL; +				} +				dirty_tx++; +			} +			lp->dirty_tx = dirty_tx; +			outw(AckIntr | DownComplete, ioaddr + EL3_CMD); +			if (lp->tx_full && (lp->cur_tx - dirty_tx <= TX_RING_SIZE - 1)) { +				lp->tx_full = 0; +				netif_wake_queue(dev); +			} +		} +#ifdef VORTEX_BUS_MASTER +		if (status & DMADone) { +			outw(0x1000, ioaddr + Wn7_MasterStatus);	/* Ack the event. */ +			dev_kfree_skb_irq(lp->tx_skb);	/* Release the transferred buffer */ +			netif_wake_queue(dev); +		} +#endif +		if (status & UpComplete) { +			boomerang_rx(dev); +			outw(AckIntr | UpComplete, ioaddr + EL3_CMD); +		} +		if (status & (AdapterFailure | RxEarly | StatsFull)) { +			/* Handle all uncommon interrupts at once. */ +			if (status & RxEarly) {	/* Rx early is unused. */ +				corkscrew_rx(dev); +				outw(AckIntr | RxEarly, ioaddr + EL3_CMD); +			} +			if (status & StatsFull) {	/* Empty statistics. */ +				static int DoneDidThat; +				if (corkscrew_debug > 4) +					pr_debug("%s: Updating stats.\n", dev->name); +				update_stats(ioaddr, dev); +				/* DEBUG HACK: Disable statistics as an interrupt source. */ +				/* This occurs when we have the wrong media type! */ +				if (DoneDidThat == 0 && inw(ioaddr + EL3_STATUS) & StatsFull) { +					int win, reg; +					pr_notice("%s: Updating stats failed, disabling stats as an interrupt source.\n", +						dev->name); +					for (win = 0; win < 8; win++) { +						EL3WINDOW(win); +						pr_notice("Vortex window %d:", win); +						for (reg = 0; reg < 16; reg++) +							pr_cont(" %2.2x", inb(ioaddr + reg)); +						pr_cont("\n"); +					} +					EL3WINDOW(7); +					outw(SetIntrEnb | TxAvailable | +					     RxComplete | AdapterFailure | +					     UpComplete | DownComplete | +					     TxComplete, ioaddr + EL3_CMD); +					DoneDidThat++; +				} +			} +			if (status & AdapterFailure) { +				/* Adapter failure requires Rx reset and reinit. */ +				outw(RxReset, ioaddr + EL3_CMD); +				/* Set the Rx filter to the current state. */ +				set_rx_mode(dev); +				outw(RxEnable, ioaddr + EL3_CMD);	/* Re-enable the receiver. */ +				outw(AckIntr | AdapterFailure, +				     ioaddr + EL3_CMD); +			} +		} + +		if (--i < 0) { +			pr_err("%s: Too much work in interrupt, status %4.4x. Disabling functions (%4.4x).\n", +				dev->name, status, SetStatusEnb | ((~status) & 0x7FE)); +			/* Disable all pending interrupts. */ +			outw(SetStatusEnb | ((~status) & 0x7FE), ioaddr + EL3_CMD); +			outw(AckIntr | 0x7FF, ioaddr + EL3_CMD); +			break; +		} +		/* Acknowledge the IRQ. */ +		outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); + +	} while ((status = inw(ioaddr + EL3_STATUS)) & (IntLatch | RxComplete)); + +	spin_unlock(&lp->lock); + +	if (corkscrew_debug > 4) +		pr_debug("%s: exiting interrupt, status %4.4x.\n", dev->name, status); +	return IRQ_HANDLED; +} + +static int corkscrew_rx(struct net_device *dev) +{ +	int ioaddr = dev->base_addr; +	int i; +	short rx_status; + +	if (corkscrew_debug > 5) +		pr_debug("   In rx_packet(), status %4.4x, rx_status %4.4x.\n", +		     inw(ioaddr + EL3_STATUS), inw(ioaddr + RxStatus)); +	while ((rx_status = inw(ioaddr + RxStatus)) > 0) { +		if (rx_status & 0x4000) {	/* Error, update stats. */ +			unsigned char rx_error = inb(ioaddr + RxErrors); +			if (corkscrew_debug > 2) +				pr_debug(" Rx error: status %2.2x.\n", +				       rx_error); +			dev->stats.rx_errors++; +			if (rx_error & 0x01) +				dev->stats.rx_over_errors++; +			if (rx_error & 0x02) +				dev->stats.rx_length_errors++; +			if (rx_error & 0x04) +				dev->stats.rx_frame_errors++; +			if (rx_error & 0x08) +				dev->stats.rx_crc_errors++; +			if (rx_error & 0x10) +				dev->stats.rx_length_errors++; +		} else { +			/* The packet length: up to 4.5K!. */ +			short pkt_len = rx_status & 0x1fff; +			struct sk_buff *skb; + +			skb = netdev_alloc_skb(dev, pkt_len + 5 + 2); +			if (corkscrew_debug > 4) +				pr_debug("Receiving packet size %d status %4.4x.\n", +				     pkt_len, rx_status); +			if (skb != NULL) { +				skb_reserve(skb, 2);	/* Align IP on 16 byte boundaries */ +				/* 'skb_put()' points to the start of sk_buff data area. */ +				insl(ioaddr + RX_FIFO, +				     skb_put(skb, pkt_len), +				     (pkt_len + 3) >> 2); +				outw(RxDiscard, ioaddr + EL3_CMD);	/* Pop top Rx packet. */ +				skb->protocol = eth_type_trans(skb, dev); +				netif_rx(skb); +				dev->stats.rx_packets++; +				dev->stats.rx_bytes += pkt_len; +				/* Wait a limited time to go to next packet. */ +				for (i = 200; i >= 0; i--) +					if (! (inw(ioaddr + EL3_STATUS) & CmdInProgress)) +						break; +				continue; +			} else if (corkscrew_debug) +				pr_debug("%s: Couldn't allocate a sk_buff of size %d.\n", dev->name, pkt_len); +		} +		outw(RxDiscard, ioaddr + EL3_CMD); +		dev->stats.rx_dropped++; +		/* Wait a limited time to skip this packet. */ +		for (i = 200; i >= 0; i--) +			if (!(inw(ioaddr + EL3_STATUS) & CmdInProgress)) +				break; +	} +	return 0; +} + +static int boomerang_rx(struct net_device *dev) +{ +	struct corkscrew_private *vp = netdev_priv(dev); +	int entry = vp->cur_rx % RX_RING_SIZE; +	int ioaddr = dev->base_addr; +	int rx_status; + +	if (corkscrew_debug > 5) +		pr_debug("   In boomerang_rx(), status %4.4x, rx_status %4.4x.\n", +			inw(ioaddr + EL3_STATUS), inw(ioaddr + RxStatus)); +	while ((rx_status = vp->rx_ring[entry].status) & RxDComplete) { +		if (rx_status & RxDError) {	/* Error, update stats. */ +			unsigned char rx_error = rx_status >> 16; +			if (corkscrew_debug > 2) +				pr_debug(" Rx error: status %2.2x.\n", +				       rx_error); +			dev->stats.rx_errors++; +			if (rx_error & 0x01) +				dev->stats.rx_over_errors++; +			if (rx_error & 0x02) +				dev->stats.rx_length_errors++; +			if (rx_error & 0x04) +				dev->stats.rx_frame_errors++; +			if (rx_error & 0x08) +				dev->stats.rx_crc_errors++; +			if (rx_error & 0x10) +				dev->stats.rx_length_errors++; +		} else { +			/* The packet length: up to 4.5K!. */ +			short pkt_len = rx_status & 0x1fff; +			struct sk_buff *skb; + +			dev->stats.rx_bytes += pkt_len; +			if (corkscrew_debug > 4) +				pr_debug("Receiving packet size %d status %4.4x.\n", +				     pkt_len, rx_status); + +			/* Check if the packet is long enough to just accept without +			   copying to a properly sized skbuff. */ +			if (pkt_len < rx_copybreak && +			    (skb = netdev_alloc_skb(dev, pkt_len + 4)) != NULL) { +				skb_reserve(skb, 2);	/* Align IP on 16 byte boundaries */ +				/* 'skb_put()' points to the start of sk_buff data area. */ +				memcpy(skb_put(skb, pkt_len), +				       isa_bus_to_virt(vp->rx_ring[entry]. +						   addr), pkt_len); +				rx_copy++; +			} else { +				void *temp; +				/* Pass up the skbuff already on the Rx ring. */ +				skb = vp->rx_skbuff[entry]; +				vp->rx_skbuff[entry] = NULL; +				temp = skb_put(skb, pkt_len); +				/* Remove this checking code for final release. */ +				if (isa_bus_to_virt(vp->rx_ring[entry].addr) != temp) +					pr_warning("%s: Warning -- the skbuff addresses do not match" +					     " in boomerang_rx: %p vs. %p / %p.\n", +					     dev->name, +					     isa_bus_to_virt(vp-> +							 rx_ring[entry]. +							 addr), skb->head, +					     temp); +				rx_nocopy++; +			} +			skb->protocol = eth_type_trans(skb, dev); +			netif_rx(skb); +			dev->stats.rx_packets++; +		} +		entry = (++vp->cur_rx) % RX_RING_SIZE; +	} +	/* Refill the Rx ring buffers. */ +	for (; vp->cur_rx - vp->dirty_rx > 0; vp->dirty_rx++) { +		struct sk_buff *skb; +		entry = vp->dirty_rx % RX_RING_SIZE; +		if (vp->rx_skbuff[entry] == NULL) { +			skb = netdev_alloc_skb(dev, PKT_BUF_SZ); +			if (skb == NULL) +				break;	/* Bad news!  */ +			skb_reserve(skb, 2);	/* Align IP on 16 byte boundaries */ +			vp->rx_ring[entry].addr = isa_virt_to_bus(skb->data); +			vp->rx_skbuff[entry] = skb; +		} +		vp->rx_ring[entry].status = 0;	/* Clear complete bit. */ +	} +	return 0; +} + +static int corkscrew_close(struct net_device *dev) +{ +	struct corkscrew_private *vp = netdev_priv(dev); +	int ioaddr = dev->base_addr; +	int i; + +	netif_stop_queue(dev); + +	if (corkscrew_debug > 1) { +		pr_debug("%s: corkscrew_close() status %4.4x, Tx status %2.2x.\n", +		     dev->name, inw(ioaddr + EL3_STATUS), +		     inb(ioaddr + TxStatus)); +		pr_debug("%s: corkscrew close stats: rx_nocopy %d rx_copy %d tx_queued %d.\n", +			dev->name, rx_nocopy, rx_copy, queued_packet); +	} + +	del_timer(&vp->timer); + +	/* Turn off statistics ASAP.  We update lp->stats below. */ +	outw(StatsDisable, ioaddr + EL3_CMD); + +	/* Disable the receiver and transmitter. */ +	outw(RxDisable, ioaddr + EL3_CMD); +	outw(TxDisable, ioaddr + EL3_CMD); + +	if (dev->if_port == XCVR_10base2) +		/* Turn off thinnet power.  Green! */ +		outw(StopCoax, ioaddr + EL3_CMD); + +	free_irq(dev->irq, dev); + +	outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD); + +	update_stats(ioaddr, dev); +	if (vp->full_bus_master_rx) {	/* Free Boomerang bus master Rx buffers. */ +		outl(0, ioaddr + UpListPtr); +		for (i = 0; i < RX_RING_SIZE; i++) +			if (vp->rx_skbuff[i]) { +				dev_kfree_skb(vp->rx_skbuff[i]); +				vp->rx_skbuff[i] = NULL; +			} +	} +	if (vp->full_bus_master_tx) {	/* Free Boomerang bus master Tx buffers. */ +		outl(0, ioaddr + DownListPtr); +		for (i = 0; i < TX_RING_SIZE; i++) +			if (vp->tx_skbuff[i]) { +				dev_kfree_skb(vp->tx_skbuff[i]); +				vp->tx_skbuff[i] = NULL; +			} +	} + +	return 0; +} + +static struct net_device_stats *corkscrew_get_stats(struct net_device *dev) +{ +	struct corkscrew_private *vp = netdev_priv(dev); +	unsigned long flags; + +	if (netif_running(dev)) { +		spin_lock_irqsave(&vp->lock, flags); +		update_stats(dev->base_addr, dev); +		spin_unlock_irqrestore(&vp->lock, flags); +	} +	return &dev->stats; +} + +/*  Update statistics. +	Unlike with the EL3 we need not worry about interrupts changing +	the window setting from underneath us, but we must still guard +	against a race condition with a StatsUpdate interrupt updating the +	table.  This is done by checking that the ASM (!) code generated uses +	atomic updates with '+='. +	*/ +static void update_stats(int ioaddr, struct net_device *dev) +{ +	/* Unlike the 3c5x9 we need not turn off stats updates while reading. */ +	/* Switch to the stats window, and read everything. */ +	EL3WINDOW(6); +	dev->stats.tx_carrier_errors += inb(ioaddr + 0); +	dev->stats.tx_heartbeat_errors += inb(ioaddr + 1); +	/* Multiple collisions. */ inb(ioaddr + 2); +	dev->stats.collisions += inb(ioaddr + 3); +	dev->stats.tx_window_errors += inb(ioaddr + 4); +	dev->stats.rx_fifo_errors += inb(ioaddr + 5); +	dev->stats.tx_packets += inb(ioaddr + 6); +	dev->stats.tx_packets += (inb(ioaddr + 9) & 0x30) << 4; +						/* Rx packets   */ inb(ioaddr + 7); +						/* Must read to clear */ +	/* Tx deferrals */ inb(ioaddr + 8); +	/* Don't bother with register 9, an extension of registers 6&7. +	   If we do use the 6&7 values the atomic update assumption above +	   is invalid. */ +	inw(ioaddr + 10);	/* Total Rx and Tx octets. */ +	inw(ioaddr + 12); +	/* New: On the Vortex we must also clear the BadSSD counter. */ +	EL3WINDOW(4); +	inb(ioaddr + 12); + +	/* We change back to window 7 (not 1) with the Vortex. */ +	EL3WINDOW(7); +} + +/* This new version of set_rx_mode() supports v1.4 kernels. +   The Vortex chip has no documented multicast filter, so the only +   multicast setting is to receive all multicast frames.  At least +   the chip has a very clean way to set the mode, unlike many others. */ +static void set_rx_mode(struct net_device *dev) +{ +	int ioaddr = dev->base_addr; +	short new_mode; + +	if (dev->flags & IFF_PROMISC) { +		if (corkscrew_debug > 3) +			pr_debug("%s: Setting promiscuous mode.\n", +			       dev->name); +		new_mode = SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm; +	} else if (!netdev_mc_empty(dev) || dev->flags & IFF_ALLMULTI) { +		new_mode = SetRxFilter | RxStation | RxMulticast | RxBroadcast; +	} else +		new_mode = SetRxFilter | RxStation | RxBroadcast; + +	outw(new_mode, ioaddr + EL3_CMD); +} + +static void netdev_get_drvinfo(struct net_device *dev, +			       struct ethtool_drvinfo *info) +{ +	strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); +	strlcpy(info->version, DRV_VERSION, sizeof(info->version)); +	snprintf(info->bus_info, sizeof(info->bus_info), "ISA 0x%lx", +		 dev->base_addr); +} + +static u32 netdev_get_msglevel(struct net_device *dev) +{ +	return corkscrew_debug; +} + +static void netdev_set_msglevel(struct net_device *dev, u32 level) +{ +	corkscrew_debug = level; +} + +static const struct ethtool_ops netdev_ethtool_ops = { +	.get_drvinfo		= netdev_get_drvinfo, +	.get_msglevel		= netdev_get_msglevel, +	.set_msglevel		= netdev_set_msglevel, +}; + + +#ifdef MODULE +void cleanup_module(void) +{ +	while (!list_empty(&root_corkscrew_dev)) { +		struct net_device *dev; +		struct corkscrew_private *vp; + +		vp = list_entry(root_corkscrew_dev.next, +				struct corkscrew_private, list); +		dev = vp->our_dev; +		unregister_netdev(dev); +		cleanup_card(dev); +		free_netdev(dev); +	} +} +#endif				/* MODULE */ diff --git a/drivers/net/ethernet/3com/3c574_cs.c b/drivers/net/ethernet/3com/3c574_cs.c new file mode 100644 index 00000000000..b9948f00c5e --- /dev/null +++ b/drivers/net/ethernet/3com/3c574_cs.c @@ -0,0 +1,1167 @@ +/* 3c574.c: A PCMCIA ethernet driver for the 3com 3c574 "RoadRunner". + +	Written 1993-1998 by +	Donald Becker, becker@scyld.com, (driver core) and +	David Hinds, dahinds@users.sourceforge.net (from his PC card code). +	Locking fixes (C) Copyright 2003 Red Hat Inc + +	This software may be used and distributed according to the terms of +	the GNU General Public License, incorporated herein by reference. + +	This driver derives from Donald Becker's 3c509 core, which has the +	following copyright: +	Copyright 1993 United States Government as represented by the +	Director, National Security Agency. +	 + +*/ + +/* +				Theory of Operation + +I. Board Compatibility + +This device driver is designed for the 3Com 3c574 PC card Fast Ethernet +Adapter. + +II. Board-specific settings + +None -- PC cards are autoconfigured. + +III. Driver operation + +The 3c574 uses a Boomerang-style interface, without the bus-master capability. +See the Boomerang driver and documentation for most details. + +IV. Notes and chip documentation. + +Two added registers are used to enhance PIO performance, RunnerRdCtrl and +RunnerWrCtrl.  These are 11 bit down-counters that are preloaded with the +count of word (16 bits) reads or writes the driver is about to do to the Rx +or Tx FIFO.  The chip is then able to hide the internal-PCI-bus to PC-card +translation latency by buffering the I/O operations with an 8 word FIFO. +Note: No other chip accesses are permitted when this buffer is used. + +A second enhancement is that both attribute and common memory space +0x0800-0x0fff can translated to the PIO FIFO.  Thus memory operations (faster +with *some* PCcard bridges) may be used instead of I/O operations. +This is enabled by setting the 0x10 bit in the PCMCIA LAN COR. + +Some slow PC card bridges work better if they never see a WAIT signal. +This is configured by setting the 0x20 bit in the PCMCIA LAN COR. +Only do this after testing that it is reliable and improves performance. + +The upper five bits of RunnerRdCtrl are used to window into PCcard +configuration space registers.  Window 0 is the regular Boomerang/Odie +register set, 1-5 are various PC card control registers, and 16-31 are +the (reversed!) CIS table. + +A final note: writing the InternalConfig register in window 3 with an +invalid ramWidth is Very Bad. + +V. References + +http://www.scyld.com/expert/NWay.html +http://www.national.com/opf/DP/DP83840A.html + +Thanks to Terry Murphy of 3Com for providing development information for +earlier 3Com products. + +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> +#include <linux/bitops.h> +#include <linux/mii.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> + +#include <asm/uaccess.h> +#include <asm/io.h> + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("3Com 3c574 series PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +INT_MODULE_PARM(max_interrupt_work, 32); + +/* Force full duplex modes? */ +INT_MODULE_PARM(full_duplex, 0); + +/* Autodetect link polarity reversal? */ +INT_MODULE_PARM(auto_polarity, 1); + + +/*====================================================================*/ + +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT  ((800*HZ)/1000) + +/* To minimize the size of the driver source and make the driver more +   readable not all constants are symbolically defined. +   You'll need the manual if you want to understand driver details anyway. */ +/* Offsets from base I/O address. */ +#define EL3_DATA	0x00 +#define EL3_CMD		0x0e +#define EL3_STATUS	0x0e + +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) + +/* The top five bits written to EL3_CMD are a command, the lower +   11 bits are the parameter, if applicable. */ +enum el3_cmds { +	TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, +	RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11, +	TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, +	FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, +	SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, +	SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11, +	StatsDisable = 22<<11, StopCoax = 23<<11, +}; + +enum elxl_status { +	IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, +	TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, +	IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000 }; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { +	RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 +}; + +enum Window0 { +	Wn0EepromCmd = 10, Wn0EepromData = 12, /* EEPROM command/address, data. */ +	IntrStatus=0x0E,		/* Valid in all windows. */ +}; +/* These assumes the larger EEPROM. */ +enum Win0_EEPROM_cmds { +	EEPROM_Read = 0x200, EEPROM_WRITE = 0x100, EEPROM_ERASE = 0x300, +	EEPROM_EWENB = 0x30,		/* Enable erasing/writing for 10 msec. */ +	EEPROM_EWDIS = 0x00,		/* Disable EWENB before 10 msec timeout. */ +}; + +/* Register window 1 offsets, the window used in normal operation. +   On the "Odie" this window is always mapped at offsets 0x10-0x1f. +   Except for TxFree, which is overlapped by RunnerWrCtrl. */ +enum Window1 { +	TX_FIFO = 0x10,  RX_FIFO = 0x10,  RxErrors = 0x14, +	RxStatus = 0x18,  Timer=0x1A, TxStatus = 0x1B, +	TxFree = 0x0C, /* Remaining free bytes in Tx buffer. */ +	RunnerRdCtrl = 0x16, RunnerWrCtrl = 0x1c, +}; + +enum Window3 {			/* Window 3: MAC/config bits. */ +	Wn3_Config=0, Wn3_MAC_Ctrl=6, Wn3_Options=8, +}; +enum wn3_config { +	Ram_size = 7, +	Ram_width = 8, +	Ram_speed = 0x30, +	Rom_size = 0xc0, +	Ram_split_shift = 16, +	Ram_split = 3 << Ram_split_shift, +	Xcvr_shift = 20, +	Xcvr = 7 << Xcvr_shift, +	Autoselect = 0x1000000, +}; + +enum Window4 {		/* Window 4: Xcvr/media bits. */ +	Wn4_FIFODiag = 4, Wn4_NetDiag = 6, Wn4_PhysicalMgmt=8, Wn4_Media = 10, +}; + +#define MEDIA_TP	0x00C0	/* Enable link beat and jabber for 10baseT. */ + +struct el3_private { +	struct pcmcia_device	*p_dev; +	u16 advertising, partner;		/* NWay media advertisement */ +	unsigned char phys;			/* MII device address */ +	unsigned int autoselect:1, default_media:3;	/* Read from the EEPROM/Wn3_Config. */ +	/* for transceiver monitoring */ +	struct timer_list media; +	unsigned short media_status; +	unsigned short fast_poll; +	unsigned long last_irq; +	spinlock_t window_lock;			/* Guards the Window selection */ +}; + +/* Set iff a MII transceiver on any interface requires mdio preamble. +   This only set with the original DP83840 on older 3c905 boards, so the extra +   code size of a per-interface flag is not worthwhile. */ +static char mii_preamble_required = 0; + +/* Index of functions. */ + +static int tc574_config(struct pcmcia_device *link); +static void tc574_release(struct pcmcia_device *link); + +static void mdio_sync(unsigned int ioaddr, int bits); +static int mdio_read(unsigned int ioaddr, int phy_id, int location); +static void mdio_write(unsigned int ioaddr, int phy_id, int location, +		       int value); +static unsigned short read_eeprom(unsigned int ioaddr, int index); +static void tc574_wait_for_completion(struct net_device *dev, int cmd); + +static void tc574_reset(struct net_device *dev); +static void media_check(unsigned long arg); +static int el3_open(struct net_device *dev); +static netdev_tx_t el3_start_xmit(struct sk_buff *skb, +					struct net_device *dev); +static irqreturn_t el3_interrupt(int irq, void *dev_id); +static void update_stats(struct net_device *dev); +static struct net_device_stats *el3_get_stats(struct net_device *dev); +static int el3_rx(struct net_device *dev, int worklimit); +static int el3_close(struct net_device *dev); +static void el3_tx_timeout(struct net_device *dev); +static int el3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static void set_rx_mode(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); + +static void tc574_detach(struct pcmcia_device *p_dev); + +/* +	tc574_attach() creates an "instance" of the driver, allocating +	local data structures for one device.  The device is registered +	with Card Services. +*/ +static const struct net_device_ops el3_netdev_ops = { +	.ndo_open 		= el3_open, +	.ndo_stop 		= el3_close, +	.ndo_start_xmit		= el3_start_xmit, +	.ndo_tx_timeout 	= el3_tx_timeout, +	.ndo_get_stats		= el3_get_stats, +	.ndo_do_ioctl		= el3_ioctl, +	.ndo_set_rx_mode	= set_multicast_list, +	.ndo_change_mtu		= eth_change_mtu, +	.ndo_set_mac_address 	= eth_mac_addr, +	.ndo_validate_addr	= eth_validate_addr, +}; + +static int tc574_probe(struct pcmcia_device *link) +{ +	struct el3_private *lp; +	struct net_device *dev; + +	dev_dbg(&link->dev, "3c574_attach()\n"); + +	/* Create the PC card device object. */ +	dev = alloc_etherdev(sizeof(struct el3_private)); +	if (!dev) +		return -ENOMEM; +	lp = netdev_priv(dev); +	link->priv = dev; +	lp->p_dev = link; + +	spin_lock_init(&lp->window_lock); +	link->resource[0]->end = 32; +	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_16; +	link->config_flags |= CONF_ENABLE_IRQ; +	link->config_index = 1; + +	dev->netdev_ops = &el3_netdev_ops; +	dev->watchdog_timeo = TX_TIMEOUT; + +	return tc574_config(link); +} + +static void tc574_detach(struct pcmcia_device *link) +{ +	struct net_device *dev = link->priv; + +	dev_dbg(&link->dev, "3c574_detach()\n"); + +	unregister_netdev(dev); + +	tc574_release(link); + +	free_netdev(dev); +} /* tc574_detach */ + +static const char *ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; + +static int tc574_config(struct pcmcia_device *link) +{ +	struct net_device *dev = link->priv; +	struct el3_private *lp = netdev_priv(dev); +	int ret, i, j; +	unsigned int ioaddr; +	__be16 *phys_addr; +	char *cardname; +	__u32 config; +	u8 *buf; +	size_t len; + +	phys_addr = (__be16 *)dev->dev_addr; + +	dev_dbg(&link->dev, "3c574_config()\n"); + +	link->io_lines = 16; + +	for (i = j = 0; j < 0x400; j += 0x20) { +		link->resource[0]->start = j ^ 0x300; +		i = pcmcia_request_io(link); +		if (i == 0) +			break; +	} +	if (i != 0) +		goto failed; + +	ret = pcmcia_request_irq(link, el3_interrupt); +	if (ret) +		goto failed; + +	ret = pcmcia_enable_device(link); +	if (ret) +		goto failed; + +	dev->irq = link->irq; +	dev->base_addr = link->resource[0]->start; + +	ioaddr = dev->base_addr; + +	/* The 3c574 normally uses an EEPROM for configuration info, including +	   the hardware address.  The future products may include a modem chip +	   and put the address in the CIS. */ + +	len = pcmcia_get_tuple(link, 0x88, &buf); +	if (buf && len >= 6) { +		for (i = 0; i < 3; i++) +			phys_addr[i] = htons(le16_to_cpu(buf[i * 2])); +		kfree(buf); +	} else { +		kfree(buf); /* 0 < len < 6 */ +		EL3WINDOW(0); +		for (i = 0; i < 3; i++) +			phys_addr[i] = htons(read_eeprom(ioaddr, i + 10)); +		if (phys_addr[0] == htons(0x6060)) { +			pr_notice("IO port conflict at 0x%03lx-0x%03lx\n", +				  dev->base_addr, dev->base_addr+15); +			goto failed; +		} +	} +	if (link->prod_id[1]) +		cardname = link->prod_id[1]; +	else +		cardname = "3Com 3c574"; + +	{ +		u_char mcr; +		outw(2<<11, ioaddr + RunnerRdCtrl); +		mcr = inb(ioaddr + 2); +		outw(0<<11, ioaddr + RunnerRdCtrl); +		pr_info("  ASIC rev %d,", mcr>>3); +		EL3WINDOW(3); +		config = inl(ioaddr + Wn3_Config); +		lp->default_media = (config & Xcvr) >> Xcvr_shift; +		lp->autoselect = config & Autoselect ? 1 : 0; +	} + +	init_timer(&lp->media); + +	{ +		int phy; +		 +		/* Roadrunner only: Turn on the MII transceiver */ +		outw(0x8040, ioaddr + Wn3_Options); +		mdelay(1); +		outw(0xc040, ioaddr + Wn3_Options); +		tc574_wait_for_completion(dev, TxReset); +		tc574_wait_for_completion(dev, RxReset); +		mdelay(1); +		outw(0x8040, ioaddr + Wn3_Options); +		 +		EL3WINDOW(4); +		for (phy = 1; phy <= 32; phy++) { +			int mii_status; +			mdio_sync(ioaddr, 32); +			mii_status = mdio_read(ioaddr, phy & 0x1f, 1); +			if (mii_status != 0xffff) { +				lp->phys = phy & 0x1f; +				dev_dbg(&link->dev, "  MII transceiver at " +					"index %d, status %x.\n", +					  phy, mii_status); +				if ((mii_status & 0x0040) == 0) +					mii_preamble_required = 1; +				break; +			} +		} +		if (phy > 32) { +			pr_notice("  No MII transceivers found!\n"); +			goto failed; +		} +		i = mdio_read(ioaddr, lp->phys, 16) | 0x40; +		mdio_write(ioaddr, lp->phys, 16, i); +		lp->advertising = mdio_read(ioaddr, lp->phys, 4); +		if (full_duplex) { +			/* Only advertise the FD media types. */ +			lp->advertising &= ~0x02a0; +			mdio_write(ioaddr, lp->phys, 4, lp->advertising); +		} +	} + +	SET_NETDEV_DEV(dev, &link->dev); + +	if (register_netdev(dev) != 0) { +		pr_notice("register_netdev() failed\n"); +		goto failed; +	} + +	netdev_info(dev, "%s at io %#3lx, irq %d, hw_addr %pM\n", +		    cardname, dev->base_addr, dev->irq, dev->dev_addr); +	netdev_info(dev, " %dK FIFO split %s Rx:Tx, %sMII interface.\n", +		    8 << (config & Ram_size), +		    ram_split[(config & Ram_split) >> Ram_split_shift], +		    config & Autoselect ? "autoselect " : ""); + +	return 0; + +failed: +	tc574_release(link); +	return -ENODEV; + +} /* tc574_config */ + +static void tc574_release(struct pcmcia_device *link) +{ +	pcmcia_disable_device(link); +} + +static int tc574_suspend(struct pcmcia_device *link) +{ +	struct net_device *dev = link->priv; + +	if (link->open) +		netif_device_detach(dev); + +	return 0; +} + +static int tc574_resume(struct pcmcia_device *link) +{ +	struct net_device *dev = link->priv; + +	if (link->open) { +		tc574_reset(dev); +		netif_device_attach(dev); +	} + +	return 0; +} + +static void dump_status(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	EL3WINDOW(1); +	netdev_info(dev, "  irq status %04x, rx status %04x, tx status %02x, tx free %04x\n", +		    inw(ioaddr+EL3_STATUS), +		    inw(ioaddr+RxStatus), inb(ioaddr+TxStatus), +		    inw(ioaddr+TxFree)); +	EL3WINDOW(4); +	netdev_info(dev, "  diagnostics: fifo %04x net %04x ethernet %04x media %04x\n", +		    inw(ioaddr+0x04), inw(ioaddr+0x06), +		    inw(ioaddr+0x08), inw(ioaddr+0x0a)); +	EL3WINDOW(1); +} + +/* +  Use this for commands that may take time to finish +*/ +static void tc574_wait_for_completion(struct net_device *dev, int cmd) +{ +	int i = 1500; +	outw(cmd, dev->base_addr + EL3_CMD); +	while (--i > 0) +		if (!(inw(dev->base_addr + EL3_STATUS) & 0x1000)) break; +	if (i == 0) +		netdev_notice(dev, "command 0x%04x did not complete!\n", cmd); +} + +/* Read a word from the EEPROM using the regular EEPROM access register. +   Assume that we are in register window zero. + */ +static unsigned short read_eeprom(unsigned int ioaddr, int index) +{ +	int timer; +	outw(EEPROM_Read + index, ioaddr + Wn0EepromCmd); +	/* Pause for at least 162 usec for the read to take place. */ +	for (timer = 1620; timer >= 0; timer--) { +		if ((inw(ioaddr + Wn0EepromCmd) & 0x8000) == 0) +			break; +	} +	return inw(ioaddr + Wn0EepromData); +} + +/* MII transceiver control section. +   Read and write the MII registers using software-generated serial +   MDIO protocol.  See the MII specifications or DP83840A data sheet +   for details. +   The maxium data clock rate is 2.5 Mhz.  The timing is easily met by the +   slow PC card interface. */ + +#define MDIO_SHIFT_CLK	0x01 +#define MDIO_DIR_WRITE	0x04 +#define MDIO_DATA_WRITE0 (0x00 | MDIO_DIR_WRITE) +#define MDIO_DATA_WRITE1 (0x02 | MDIO_DIR_WRITE) +#define MDIO_DATA_READ	0x02 +#define MDIO_ENB_IN		0x00 + +/* Generate the preamble required for initial synchronization and +   a few older transceivers. */ +static void mdio_sync(unsigned int ioaddr, int bits) +{ +	unsigned int mdio_addr = ioaddr + Wn4_PhysicalMgmt; + +	/* Establish sync by sending at least 32 logic ones. */ +	while (-- bits >= 0) { +		outw(MDIO_DATA_WRITE1, mdio_addr); +		outw(MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr); +	} +} + +static int mdio_read(unsigned int ioaddr, int phy_id, int location) +{ +	int i; +	int read_cmd = (0xf6 << 10) | (phy_id << 5) | location; +	unsigned int retval = 0; +	unsigned int mdio_addr = ioaddr + Wn4_PhysicalMgmt; + +	if (mii_preamble_required) +		mdio_sync(ioaddr, 32); + +	/* Shift the read command bits out. */ +	for (i = 14; i >= 0; i--) { +		int dataval = (read_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; +		outw(dataval, mdio_addr); +		outw(dataval | MDIO_SHIFT_CLK, mdio_addr); +	} +	/* Read the two transition, 16 data, and wire-idle bits. */ +	for (i = 19; i > 0; i--) { +		outw(MDIO_ENB_IN, mdio_addr); +		retval = (retval << 1) | ((inw(mdio_addr) & MDIO_DATA_READ) ? 1 : 0); +		outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); +	} +	return (retval>>1) & 0xffff; +} + +static void mdio_write(unsigned int ioaddr, int phy_id, int location, int value) +{ +	int write_cmd = 0x50020000 | (phy_id << 23) | (location << 18) | value; +	unsigned int mdio_addr = ioaddr + Wn4_PhysicalMgmt; +	int i; + +	if (mii_preamble_required) +		mdio_sync(ioaddr, 32); + +	/* Shift the command bits out. */ +	for (i = 31; i >= 0; i--) { +		int dataval = (write_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; +		outw(dataval, mdio_addr); +		outw(dataval | MDIO_SHIFT_CLK, mdio_addr); +	} +	/* Leave the interface idle. */ +	for (i = 1; i >= 0; i--) { +		outw(MDIO_ENB_IN, mdio_addr); +		outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); +	} +} + +/* Reset and restore all of the 3c574 registers. */ +static void tc574_reset(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	int i; +	unsigned int ioaddr = dev->base_addr; +	unsigned long flags; + +	tc574_wait_for_completion(dev, TotalReset|0x10); + +	spin_lock_irqsave(&lp->window_lock, flags); +	/* Clear any transactions in progress. */ +	outw(0, ioaddr + RunnerWrCtrl); +	outw(0, ioaddr + RunnerRdCtrl); + +	/* Set the station address and mask. */ +	EL3WINDOW(2); +	for (i = 0; i < 6; i++) +		outb(dev->dev_addr[i], ioaddr + i); +	for (; i < 12; i+=2) +		outw(0, ioaddr + i); + +	/* Reset config options */ +	EL3WINDOW(3); +	outb((dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl); +	outl((lp->autoselect ? 0x01000000 : 0) | 0x0062001b, +		 ioaddr + Wn3_Config); +	/* Roadrunner only: Turn on the MII transceiver. */ +	outw(0x8040, ioaddr + Wn3_Options); +	mdelay(1); +	outw(0xc040, ioaddr + Wn3_Options); +	EL3WINDOW(1); +	spin_unlock_irqrestore(&lp->window_lock, flags); +	 +	tc574_wait_for_completion(dev, TxReset); +	tc574_wait_for_completion(dev, RxReset); +	mdelay(1); +	spin_lock_irqsave(&lp->window_lock, flags); +	EL3WINDOW(3); +	outw(0x8040, ioaddr + Wn3_Options); + +	/* Switch to the stats window, and clear all stats by reading. */ +	outw(StatsDisable, ioaddr + EL3_CMD); +	EL3WINDOW(6); +	for (i = 0; i < 10; i++) +		inb(ioaddr + i); +	inw(ioaddr + 10); +	inw(ioaddr + 12); +	EL3WINDOW(4); +	inb(ioaddr + 12); +	inb(ioaddr + 13); + +	/* .. enable any extra statistics bits.. */ +	outw(0x0040, ioaddr + Wn4_NetDiag); +	 +	EL3WINDOW(1); +	spin_unlock_irqrestore(&lp->window_lock, flags); +	 +	/* .. re-sync MII and re-fill what NWay is advertising. */ +	mdio_sync(ioaddr, 32); +	mdio_write(ioaddr, lp->phys, 4, lp->advertising); +	if (!auto_polarity) { +		/* works for TDK 78Q2120 series MII's */ +		i = mdio_read(ioaddr, lp->phys, 16) | 0x20; +		mdio_write(ioaddr, lp->phys, 16, i); +	} + +	spin_lock_irqsave(&lp->window_lock, flags); +	/* Switch to register set 1 for normal use, just for TxFree. */ +	set_rx_mode(dev); +	spin_unlock_irqrestore(&lp->window_lock, flags); +	outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ +	outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ +	outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ +	/* Allow status bits to be seen. */ +	outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); +	/* Ack all pending events, and set active indicator mask. */ +	outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, +		 ioaddr + EL3_CMD); +	outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull +		 | AdapterFailure | RxEarly, ioaddr + EL3_CMD); +} + +static int el3_open(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	struct pcmcia_device *link = lp->p_dev; + +	if (!pcmcia_dev_present(link)) +		return -ENODEV; +	 +	link->open++; +	netif_start_queue(dev); +	 +	tc574_reset(dev); +	lp->media.function = media_check; +	lp->media.data = (unsigned long) dev; +	lp->media.expires = jiffies + HZ; +	add_timer(&lp->media); +	 +	dev_dbg(&link->dev, "%s: opened, status %4.4x.\n", +		  dev->name, inw(dev->base_addr + EL3_STATUS)); +	 +	return 0; +} + +static void el3_tx_timeout(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	 +	netdev_notice(dev, "Transmit timed out!\n"); +	dump_status(dev); +	dev->stats.tx_errors++; +	dev->trans_start = jiffies; /* prevent tx timeout */ +	/* Issue TX_RESET and TX_START commands. */ +	tc574_wait_for_completion(dev, TxReset); +	outw(TxEnable, ioaddr + EL3_CMD); +	netif_wake_queue(dev); +} + +static void pop_tx_status(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	int i; +     +	/* Clear the Tx status stack. */ +	for (i = 32; i > 0; i--) { +		u_char tx_status = inb(ioaddr + TxStatus); +		if (!(tx_status & 0x84)) +			break; +		/* reset transmitter on jabber error or underrun */ +		if (tx_status & 0x30) +			tc574_wait_for_completion(dev, TxReset); +		if (tx_status & 0x38) { +			pr_debug("%s: transmit error: status 0x%02x\n", +				  dev->name, tx_status); +			outw(TxEnable, ioaddr + EL3_CMD); +			dev->stats.tx_aborted_errors++; +		} +		outb(0x00, ioaddr + TxStatus); /* Pop the status stack. */ +	} +} + +static netdev_tx_t el3_start_xmit(struct sk_buff *skb, +					struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	struct el3_private *lp = netdev_priv(dev); +	unsigned long flags; + +	pr_debug("%s: el3_start_xmit(length = %ld) called, " +		  "status %4.4x.\n", dev->name, (long)skb->len, +		  inw(ioaddr + EL3_STATUS)); + +	spin_lock_irqsave(&lp->window_lock, flags); + +	dev->stats.tx_bytes += skb->len; + +	/* Put out the doubleword header... */ +	outw(skb->len, ioaddr + TX_FIFO); +	outw(0, ioaddr + TX_FIFO); +	/* ... and the packet rounded to a doubleword. */ +	outsl(ioaddr + TX_FIFO, skb->data, (skb->len+3)>>2); + +	/* TxFree appears only in Window 1, not offset 0x1c. */ +	if (inw(ioaddr + TxFree) <= 1536) { +		netif_stop_queue(dev); +		/* Interrupt us when the FIFO has room for max-sized packet.  +		   The threshold is in units of dwords. */ +		outw(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); +	} + +	pop_tx_status(dev); +	spin_unlock_irqrestore(&lp->window_lock, flags); +	dev_kfree_skb(skb); +	return NETDEV_TX_OK; +} + +/* The EL3 interrupt handler. */ +static irqreturn_t el3_interrupt(int irq, void *dev_id) +{ +	struct net_device *dev = (struct net_device *) dev_id; +	struct el3_private *lp = netdev_priv(dev); +	unsigned int ioaddr; +	unsigned status; +	int work_budget = max_interrupt_work; +	int handled = 0; + +	if (!netif_device_present(dev)) +		return IRQ_NONE; +	ioaddr = dev->base_addr; + +	pr_debug("%s: interrupt, status %4.4x.\n", +		  dev->name, inw(ioaddr + EL3_STATUS)); + +	spin_lock(&lp->window_lock); +	 +	while ((status = inw(ioaddr + EL3_STATUS)) & +		   (IntLatch | RxComplete | RxEarly | StatsFull)) { +		if (!netif_device_present(dev) || +			((status & 0xe000) != 0x2000)) { +			pr_debug("%s: Interrupt from dead card\n", dev->name); +			break; +		} + +		handled = 1; + +		if (status & RxComplete) +			work_budget = el3_rx(dev, work_budget); + +		if (status & TxAvailable) { +			pr_debug("  TX room bit was handled.\n"); +			/* There's room in the FIFO for a full-sized packet. */ +			outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); +			netif_wake_queue(dev); +		} + +		if (status & TxComplete) +			pop_tx_status(dev); + +		if (status & (AdapterFailure | RxEarly | StatsFull)) { +			/* Handle all uncommon interrupts. */ +			if (status & StatsFull) +				update_stats(dev); +			if (status & RxEarly) { +				work_budget = el3_rx(dev, work_budget); +				outw(AckIntr | RxEarly, ioaddr + EL3_CMD); +			} +			if (status & AdapterFailure) { +				u16 fifo_diag; +				EL3WINDOW(4); +				fifo_diag = inw(ioaddr + Wn4_FIFODiag); +				EL3WINDOW(1); +				netdev_notice(dev, "adapter failure, FIFO diagnostic register %04x\n", +					      fifo_diag); +				if (fifo_diag & 0x0400) { +					/* Tx overrun */ +					tc574_wait_for_completion(dev, TxReset); +					outw(TxEnable, ioaddr + EL3_CMD); +				} +				if (fifo_diag & 0x2000) { +					/* Rx underrun */ +					tc574_wait_for_completion(dev, RxReset); +					set_rx_mode(dev); +					outw(RxEnable, ioaddr + EL3_CMD); +				} +				outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); +			} +		} + +		if (--work_budget < 0) { +			pr_debug("%s: Too much work in interrupt, " +				  "status %4.4x.\n", dev->name, status); +			/* Clear all interrupts */ +			outw(AckIntr | 0xFF, ioaddr + EL3_CMD); +			break; +		} +		/* Acknowledge the IRQ. */ +		outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); +	} + +	pr_debug("%s: exiting interrupt, status %4.4x.\n", +		  dev->name, inw(ioaddr + EL3_STATUS)); +		   +	spin_unlock(&lp->window_lock); +	return IRQ_RETVAL(handled); +} + +/* +    This timer serves two purposes: to check for missed interrupts +	(and as a last resort, poll the NIC for events), and to monitor +	the MII, reporting changes in cable status. +*/ +static void media_check(unsigned long arg) +{ +	struct net_device *dev = (struct net_device *) arg; +	struct el3_private *lp = netdev_priv(dev); +	unsigned int ioaddr = dev->base_addr; +	unsigned long flags; +	unsigned short /* cable, */ media, partner; + +	if (!netif_device_present(dev)) +		goto reschedule; +	 +	/* Check for pending interrupt with expired latency timer: with +	   this, we can limp along even if the interrupt is blocked */ +	if ((inw(ioaddr + EL3_STATUS) & IntLatch) && (inb(ioaddr + Timer) == 0xff)) { +		if (!lp->fast_poll) +			netdev_info(dev, "interrupt(s) dropped!\n"); + +		local_irq_save(flags); +		el3_interrupt(dev->irq, dev); +		local_irq_restore(flags); + +		lp->fast_poll = HZ; +	} +	if (lp->fast_poll) { +		lp->fast_poll--; +		lp->media.expires = jiffies + 2*HZ/100; +		add_timer(&lp->media); +		return; +	} + +	spin_lock_irqsave(&lp->window_lock, flags); +	EL3WINDOW(4); +	media = mdio_read(ioaddr, lp->phys, 1); +	partner = mdio_read(ioaddr, lp->phys, 5); +	EL3WINDOW(1); +	 +	if (media != lp->media_status) { +		if ((media ^ lp->media_status) & 0x0004) +			netdev_info(dev, "%s link beat\n", +				    (lp->media_status & 0x0004) ? "lost" : "found"); +		if ((media ^ lp->media_status) & 0x0020) { +			lp->partner = 0; +			if (lp->media_status & 0x0020) { +				netdev_info(dev, "autonegotiation restarted\n"); +			} else if (partner) { +				partner &= lp->advertising; +				lp->partner = partner; +				netdev_info(dev, "autonegotiation complete: " +					    "%dbaseT-%cD selected\n", +					    (partner & 0x0180) ? 100 : 10, +					    (partner & 0x0140) ? 'F' : 'H'); +			} else { +				netdev_info(dev, "link partner did not autonegotiate\n"); +			} + +			EL3WINDOW(3); +			outb((partner & 0x0140 ? 0x20 : 0) | +				 (dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl); +			EL3WINDOW(1); + +		} +		if (media & 0x0010) +			netdev_info(dev, "remote fault detected\n"); +		if (media & 0x0002) +			netdev_info(dev, "jabber detected\n"); +		lp->media_status = media; +	} +	spin_unlock_irqrestore(&lp->window_lock, flags); + +reschedule: +	lp->media.expires = jiffies + HZ; +	add_timer(&lp->media); +} + +static struct net_device_stats *el3_get_stats(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); + +	if (netif_device_present(dev)) { +		unsigned long flags; +		spin_lock_irqsave(&lp->window_lock, flags); +		update_stats(dev); +		spin_unlock_irqrestore(&lp->window_lock, flags); +	} +	return &dev->stats; +} + +/*  Update statistics. +	Surprisingly this need not be run single-threaded, but it effectively is. +	The counters clear when read, so the adds must merely be atomic. + */ +static void update_stats(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	u8 rx, tx, up; + +	pr_debug("%s: updating the statistics.\n", dev->name); + +	if (inw(ioaddr+EL3_STATUS) == 0xffff) /* No card. */ +		return; +		 +	/* Unlike the 3c509 we need not turn off stats updates while reading. */ +	/* Switch to the stats window, and read everything. */ +	EL3WINDOW(6); +	dev->stats.tx_carrier_errors 		+= inb(ioaddr + 0); +	dev->stats.tx_heartbeat_errors		+= inb(ioaddr + 1); +	/* Multiple collisions. */	   	inb(ioaddr + 2); +	dev->stats.collisions			+= inb(ioaddr + 3); +	dev->stats.tx_window_errors		+= inb(ioaddr + 4); +	dev->stats.rx_fifo_errors		+= inb(ioaddr + 5); +	dev->stats.tx_packets			+= inb(ioaddr + 6); +	up		 			 = inb(ioaddr + 9); +	dev->stats.tx_packets			+= (up&0x30) << 4; +	/* Rx packets   */			   inb(ioaddr + 7); +	/* Tx deferrals */			   inb(ioaddr + 8); +	rx		 			 = inw(ioaddr + 10); +	tx					 = inw(ioaddr + 12); + +	EL3WINDOW(4); +	/* BadSSD */				   inb(ioaddr + 12); +	up					 = inb(ioaddr + 13); + +	EL3WINDOW(1); +} + +static int el3_rx(struct net_device *dev, int worklimit) +{ +	unsigned int ioaddr = dev->base_addr; +	short rx_status; +	 +	pr_debug("%s: in rx_packet(), status %4.4x, rx_status %4.4x.\n", +		  dev->name, inw(ioaddr+EL3_STATUS), inw(ioaddr+RxStatus)); +	while (!((rx_status = inw(ioaddr + RxStatus)) & 0x8000) && +			worklimit > 0) { +		worklimit--; +		if (rx_status & 0x4000) { /* Error, update stats. */ +			short error = rx_status & 0x3800; +			dev->stats.rx_errors++; +			switch (error) { +			case 0x0000:	dev->stats.rx_over_errors++; break; +			case 0x0800:	dev->stats.rx_length_errors++; break; +			case 0x1000:	dev->stats.rx_frame_errors++; break; +			case 0x1800:	dev->stats.rx_length_errors++; break; +			case 0x2000:	dev->stats.rx_frame_errors++; break; +			case 0x2800:	dev->stats.rx_crc_errors++; break; +			} +		} else { +			short pkt_len = rx_status & 0x7ff; +			struct sk_buff *skb; + +			skb = netdev_alloc_skb(dev, pkt_len + 5); + +			pr_debug("  Receiving packet size %d status %4.4x.\n", +				  pkt_len, rx_status); +			if (skb != NULL) { +				skb_reserve(skb, 2); +				insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len), +						((pkt_len+3)>>2)); +				skb->protocol = eth_type_trans(skb, dev); +				netif_rx(skb); +				dev->stats.rx_packets++; +				dev->stats.rx_bytes += pkt_len; +			} else { +				pr_debug("%s: couldn't allocate a sk_buff of" +					  " size %d.\n", dev->name, pkt_len); +				dev->stats.rx_dropped++; +			} +		} +		tc574_wait_for_completion(dev, RxDiscard); +	} + +	return worklimit; +} + +/* Provide ioctl() calls to examine the MII xcvr state. */ +static int el3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ +	struct el3_private *lp = netdev_priv(dev); +	unsigned int ioaddr = dev->base_addr; +	struct mii_ioctl_data *data = if_mii(rq); +	int phy = lp->phys & 0x1f; + +	pr_debug("%s: In ioct(%-.6s, %#4.4x) %4.4x %4.4x %4.4x %4.4x.\n", +		  dev->name, rq->ifr_ifrn.ifrn_name, cmd, +		  data->phy_id, data->reg_num, data->val_in, data->val_out); + +	switch(cmd) { +	case SIOCGMIIPHY:		/* Get the address of the PHY in use. */ +		data->phy_id = phy; +	case SIOCGMIIREG:		/* Read the specified MII register. */ +		{ +			int saved_window; +			unsigned long flags; + +			spin_lock_irqsave(&lp->window_lock, flags); +			saved_window = inw(ioaddr + EL3_CMD) >> 13; +			EL3WINDOW(4); +			data->val_out = mdio_read(ioaddr, data->phy_id & 0x1f, +						  data->reg_num & 0x1f); +			EL3WINDOW(saved_window); +			spin_unlock_irqrestore(&lp->window_lock, flags); +			return 0; +		} +	case SIOCSMIIREG:		/* Write the specified MII register */ +		{ +			int saved_window; +                       unsigned long flags; + +			spin_lock_irqsave(&lp->window_lock, flags); +			saved_window = inw(ioaddr + EL3_CMD) >> 13; +			EL3WINDOW(4); +			mdio_write(ioaddr, data->phy_id & 0x1f, +				   data->reg_num & 0x1f, data->val_in); +			EL3WINDOW(saved_window); +			spin_unlock_irqrestore(&lp->window_lock, flags); +			return 0; +		} +	default: +		return -EOPNOTSUPP; +	} +} + +/* The Odie chip has a 64 bin multicast filter, but the bit layout is not +   documented.  Until it is we revert to receiving all multicast frames when +   any multicast reception is desired. +   Note: My other drivers emit a log message whenever promiscuous mode is +   entered to help detect password sniffers.  This is less desirable on +   typical PC card machines, so we omit the message. +   */ + +static void set_rx_mode(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; + +	if (dev->flags & IFF_PROMISC) +		outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm, +			 ioaddr + EL3_CMD); +	else if (!netdev_mc_empty(dev) || (dev->flags & IFF_ALLMULTI)) +		outw(SetRxFilter|RxStation|RxMulticast|RxBroadcast, ioaddr + EL3_CMD); +	else +		outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); +} + +static void set_multicast_list(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	unsigned long flags; + +	spin_lock_irqsave(&lp->window_lock, flags); +	set_rx_mode(dev); +	spin_unlock_irqrestore(&lp->window_lock, flags); +} + +static int el3_close(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	struct el3_private *lp = netdev_priv(dev); +	struct pcmcia_device *link = lp->p_dev; + +	dev_dbg(&link->dev, "%s: shutting down ethercard.\n", dev->name); +	 +	if (pcmcia_dev_present(link)) { +		unsigned long flags; + +		/* Turn off statistics ASAP.  We update lp->stats below. */ +		outw(StatsDisable, ioaddr + EL3_CMD); +		 +		/* Disable the receiver and transmitter. */ +		outw(RxDisable, ioaddr + EL3_CMD); +		outw(TxDisable, ioaddr + EL3_CMD); +		 +		/* Note: Switching to window 0 may disable the IRQ. */ +		EL3WINDOW(0); +		spin_lock_irqsave(&lp->window_lock, flags); +		update_stats(dev); +		spin_unlock_irqrestore(&lp->window_lock, flags); + +		/* force interrupts off */ +		outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD); +	} + +	link->open--; +	netif_stop_queue(dev); +	del_timer_sync(&lp->media); + +	return 0; +} + +static const struct pcmcia_device_id tc574_ids[] = { +	PCMCIA_DEVICE_MANF_CARD(0x0101, 0x0574), +	PCMCIA_MFC_DEVICE_CIS_MANF_CARD(0, 0x0101, 0x0556, "cis/3CCFEM556.cis"), +	PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, tc574_ids); + +static struct pcmcia_driver tc574_driver = { +	.owner		= THIS_MODULE, +	.name		= "3c574_cs", +	.probe		= tc574_probe, +	.remove		= tc574_detach, +	.id_table       = tc574_ids, +	.suspend	= tc574_suspend, +	.resume		= tc574_resume, +}; +module_pcmcia_driver(tc574_driver); diff --git a/drivers/net/ethernet/3com/3c589_cs.c b/drivers/net/ethernet/3com/3c589_cs.c new file mode 100644 index 00000000000..f18647c2355 --- /dev/null +++ b/drivers/net/ethernet/3com/3c589_cs.c @@ -0,0 +1,971 @@ +/* ====================================================================== + * + * A PCMCIA ethernet driver for the 3com 3c589 card. + * + * Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net + * + * 3c589_cs.c 1.162 2001/10/13 00:08:50 + * + * The network driver code is based on Donald Becker's 3c589 code: + * + * Written 1994 by Donald Becker. + * Copyright 1993 United States Government as represented by the + * Director, National Security Agency.  This software may be used and + * distributed according to the terms of the GNU General Public License, + * incorporated herein by reference. + * Donald Becker may be reached at becker@scyld.com + * + * Updated for 2.5.x by Alan Cox <alan@lxorguk.ukuu.org.uk> + * + * ====================================================================== + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define DRV_NAME	"3c589_cs" +#define DRV_VERSION	"1.162-ac" + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <linux/ethtool.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> +#include <linux/bitops.h> +#include <linux/jiffies.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> + + +/* To minimize the size of the driver source I only define operating + * constants if they are used several times. You'll need the manual + * if you want to understand driver details. + */ + +/* Offsets from base I/O address. */ +#define EL3_DATA	0x00 +#define EL3_TIMER	0x0a +#define EL3_CMD		0x0e +#define EL3_STATUS	0x0e + +#define EEPROM_READ	0x0080 +#define EEPROM_BUSY	0x8000 + +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) + +/* The top five bits written to EL3_CMD are a command, the lower + * 11 bits are the parameter, if applicable. + */ + +enum c509cmd { +	TotalReset	= 0<<11, +	SelectWindow	= 1<<11, +	StartCoax	= 2<<11, +	RxDisable	= 3<<11, +	RxEnable	= 4<<11, +	RxReset		= 5<<11, +	RxDiscard	= 8<<11, +	TxEnable	= 9<<11, +	TxDisable	= 10<<11, +	TxReset		= 11<<11, +	FakeIntr	= 12<<11, +	AckIntr		= 13<<11, +	SetIntrEnb	= 14<<11, +	SetStatusEnb	= 15<<11, +	SetRxFilter	= 16<<11, +	SetRxThreshold	= 17<<11, +	SetTxThreshold	= 18<<11, +	SetTxStart	= 19<<11, +	StatsEnable	= 21<<11, +	StatsDisable	= 22<<11, +	StopCoax	= 23<<11 +}; + +enum c509status { +	IntLatch	= 0x0001, +	AdapterFailure	= 0x0002, +	TxComplete	= 0x0004, +	TxAvailable	= 0x0008, +	RxComplete	= 0x0010, +	RxEarly		= 0x0020, +	IntReq		= 0x0040, +	StatsFull	= 0x0080, +	CmdBusy		= 0x1000 +}; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { +	RxStation	= 1, +	RxMulticast	= 2, +	RxBroadcast	= 4, +	RxProm		= 8 +}; + +/* Register window 1 offsets, the window used in normal operation. */ +#define TX_FIFO		0x00 +#define RX_FIFO		0x00 +#define RX_STATUS	0x08 +#define TX_STATUS	0x0B +#define TX_FREE		0x0C	/* Remaining free bytes in Tx buffer. */ + +#define WN0_IRQ		0x08	/* Window 0: Set IRQ line in bits 12-15. */ +#define WN4_MEDIA	0x0A	/* Window 4: Various transcvr/media bits. */ +#define MEDIA_TP	0x00C0	/* Enable link beat and jabber for 10baseT. */ +#define MEDIA_LED	0x0001	/* Enable link light on 3C589E cards. */ + +/* Time in jiffies before concluding Tx hung */ +#define TX_TIMEOUT	((400*HZ)/1000) + +struct el3_private { +	struct pcmcia_device	*p_dev; +	/* For transceiver monitoring */ +	struct timer_list	media; +	u16			media_status; +	u16			fast_poll; +	unsigned long		last_irq; +	spinlock_t		lock; +}; + +static const char *if_names[] = { "auto", "10baseT", "10base2", "AUI" }; + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("3Com 3c589 series PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +/* Special hook for setting if_port when module is loaded */ +INT_MODULE_PARM(if_port, 0); + + +/*====================================================================*/ + +static int tc589_config(struct pcmcia_device *link); +static void tc589_release(struct pcmcia_device *link); + +static u16 read_eeprom(unsigned int ioaddr, int index); +static void tc589_reset(struct net_device *dev); +static void media_check(unsigned long arg); +static int el3_config(struct net_device *dev, struct ifmap *map); +static int el3_open(struct net_device *dev); +static netdev_tx_t el3_start_xmit(struct sk_buff *skb, +					struct net_device *dev); +static irqreturn_t el3_interrupt(int irq, void *dev_id); +static void update_stats(struct net_device *dev); +static struct net_device_stats *el3_get_stats(struct net_device *dev); +static int el3_rx(struct net_device *dev); +static int el3_close(struct net_device *dev); +static void el3_tx_timeout(struct net_device *dev); +static void set_rx_mode(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); +static const struct ethtool_ops netdev_ethtool_ops; + +static void tc589_detach(struct pcmcia_device *p_dev); + +static const struct net_device_ops el3_netdev_ops = { +	.ndo_open		= el3_open, +	.ndo_stop		= el3_close, +	.ndo_start_xmit		= el3_start_xmit, +	.ndo_tx_timeout		= el3_tx_timeout, +	.ndo_set_config		= el3_config, +	.ndo_get_stats		= el3_get_stats, +	.ndo_set_rx_mode	= set_multicast_list, +	.ndo_change_mtu		= eth_change_mtu, +	.ndo_set_mac_address	= eth_mac_addr, +	.ndo_validate_addr	= eth_validate_addr, +}; + +static int tc589_probe(struct pcmcia_device *link) +{ +	struct el3_private *lp; +	struct net_device *dev; + +	dev_dbg(&link->dev, "3c589_attach()\n"); + +	/* Create new ethernet device */ +	dev = alloc_etherdev(sizeof(struct el3_private)); +	if (!dev) +		return -ENOMEM; +	lp = netdev_priv(dev); +	link->priv = dev; +	lp->p_dev = link; + +	spin_lock_init(&lp->lock); +	link->resource[0]->end = 16; +	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_16; + +	link->config_flags |= CONF_ENABLE_IRQ; +	link->config_index = 1; + +	dev->netdev_ops = &el3_netdev_ops; +	dev->watchdog_timeo = TX_TIMEOUT; + +	dev->ethtool_ops = &netdev_ethtool_ops; + +	return tc589_config(link); +} + +static void tc589_detach(struct pcmcia_device *link) +{ +	struct net_device *dev = link->priv; + +	dev_dbg(&link->dev, "3c589_detach\n"); + +	unregister_netdev(dev); + +	tc589_release(link); + +	free_netdev(dev); +} /* tc589_detach */ + +static int tc589_config(struct pcmcia_device *link) +{ +	struct net_device *dev = link->priv; +	__be16 *phys_addr; +	int ret, i, j, multi = 0, fifo; +	unsigned int ioaddr; +	static const char * const ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; +	u8 *buf; +	size_t len; + +	dev_dbg(&link->dev, "3c589_config\n"); + +	phys_addr = (__be16 *)dev->dev_addr; +	/* Is this a 3c562? */ +	if (link->manf_id != MANFID_3COM) +		dev_info(&link->dev, "hmmm, is this really a 3Com card??\n"); +	multi = (link->card_id == PRODID_3COM_3C562); + +	link->io_lines = 16; + +	/* For the 3c562, the base address must be xx00-xx7f */ +	for (i = j = 0; j < 0x400; j += 0x10) { +		if (multi && (j & 0x80)) +			continue; +		link->resource[0]->start = j ^ 0x300; +		i = pcmcia_request_io(link); +		if (i == 0) +			break; +	} +	if (i != 0) +		goto failed; + +	ret = pcmcia_request_irq(link, el3_interrupt); +	if (ret) +		goto failed; + +	ret = pcmcia_enable_device(link); +	if (ret) +		goto failed; + +	dev->irq = link->irq; +	dev->base_addr = link->resource[0]->start; +	ioaddr = dev->base_addr; +	EL3WINDOW(0); + +	/* The 3c589 has an extra EEPROM for configuration info, including +	 * the hardware address.  The 3c562 puts the address in the CIS. +	 */ +	len = pcmcia_get_tuple(link, 0x88, &buf); +	if (buf && len >= 6) { +		for (i = 0; i < 3; i++) +			phys_addr[i] = htons(le16_to_cpu(buf[i*2])); +		kfree(buf); +	} else { +		kfree(buf); /* 0 < len < 6 */ +		for (i = 0; i < 3; i++) +			phys_addr[i] = htons(read_eeprom(ioaddr, i)); +		if (phys_addr[0] == htons(0x6060)) { +			dev_err(&link->dev, "IO port conflict at 0x%03lx-0x%03lx\n", +					dev->base_addr, dev->base_addr+15); +			goto failed; +		} +	} + +	/* The address and resource configuration register aren't loaded from +	 * the EEPROM and *must* be set to 0 and IRQ3 for the PCMCIA version. +	 */ + +	outw(0x3f00, ioaddr + 8); +	fifo = inl(ioaddr); + +	/* The if_port symbol can be set when the module is loaded */ +	if ((if_port >= 0) && (if_port <= 3)) +		dev->if_port = if_port; +	else +		dev_err(&link->dev, "invalid if_port requested\n"); + +	SET_NETDEV_DEV(dev, &link->dev); + +	if (register_netdev(dev) != 0) { +		dev_err(&link->dev, "register_netdev() failed\n"); +		goto failed; +	} + +	netdev_info(dev, "3Com 3c%s, io %#3lx, irq %d, hw_addr %pM\n", +			(multi ? "562" : "589"), dev->base_addr, dev->irq, +			dev->dev_addr); +	netdev_info(dev, "  %dK FIFO split %s Rx:Tx, %s xcvr\n", +			(fifo & 7) ? 32 : 8, ram_split[(fifo >> 16) & 3], +			if_names[dev->if_port]); +	return 0; + +failed: +	tc589_release(link); +	return -ENODEV; +} /* tc589_config */ + +static void tc589_release(struct pcmcia_device *link) +{ +	pcmcia_disable_device(link); +} + +static int tc589_suspend(struct pcmcia_device *link) +{ +	struct net_device *dev = link->priv; + +	if (link->open) +		netif_device_detach(dev); + +	return 0; +} + +static int tc589_resume(struct pcmcia_device *link) +{ +	struct net_device *dev = link->priv; + +	if (link->open) { +		tc589_reset(dev); +		netif_device_attach(dev); +	} + +	return 0; +} + +/*====================================================================*/ + +/* Use this for commands that may take time to finish */ + +static void tc589_wait_for_completion(struct net_device *dev, int cmd) +{ +	int i = 100; +	outw(cmd, dev->base_addr + EL3_CMD); +	while (--i > 0) +		if (!(inw(dev->base_addr + EL3_STATUS) & 0x1000)) +			break; +	if (i == 0) +		netdev_warn(dev, "command 0x%04x did not complete!\n", cmd); +} + +/* Read a word from the EEPROM using the regular EEPROM access register. + * Assume that we are in register window zero. + */ + +static u16 read_eeprom(unsigned int ioaddr, int index) +{ +	int i; +	outw(EEPROM_READ + index, ioaddr + 10); +	/* Reading the eeprom takes 162 us */ +	for (i = 1620; i >= 0; i--) +		if ((inw(ioaddr + 10) & EEPROM_BUSY) == 0) +			break; +	return inw(ioaddr + 12); +} + +/* Set transceiver type, perhaps to something other than what the user + * specified in dev->if_port. + */ + +static void tc589_set_xcvr(struct net_device *dev, int if_port) +{ +	struct el3_private *lp = netdev_priv(dev); +	unsigned int ioaddr = dev->base_addr; + +	EL3WINDOW(0); +	switch (if_port) { +	case 0: +	case 1: +		outw(0, ioaddr + 6); +		break; +	case 2: +		outw(3<<14, ioaddr + 6); +		break; +	case 3: +		outw(1<<14, ioaddr + 6); +		break; +	} +	/* On PCMCIA, this just turns on the LED */ +	outw((if_port == 2) ? StartCoax : StopCoax, ioaddr + EL3_CMD); +	/* 10baseT interface, enable link beat and jabber check. */ +	EL3WINDOW(4); +	outw(MEDIA_LED | ((if_port < 2) ? MEDIA_TP : 0), ioaddr + WN4_MEDIA); +	EL3WINDOW(1); +	if (if_port == 2) +		lp->media_status = ((dev->if_port == 0) ? 0x8000 : 0x4000); +	else +		lp->media_status = ((dev->if_port == 0) ? 0x4010 : 0x8800); +} + +static void dump_status(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	EL3WINDOW(1); +	netdev_info(dev, "  irq status %04x, rx status %04x, tx status %02x  tx free %04x\n", +			inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS), +			inb(ioaddr+TX_STATUS), inw(ioaddr+TX_FREE)); +	EL3WINDOW(4); +	netdev_info(dev, "  diagnostics: fifo %04x net %04x ethernet %04x media %04x\n", +			inw(ioaddr+0x04), inw(ioaddr+0x06), inw(ioaddr+0x08), +			inw(ioaddr+0x0a)); +	EL3WINDOW(1); +} + +/* Reset and restore all of the 3c589 registers. */ +static void tc589_reset(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	int i; + +	EL3WINDOW(0); +	outw(0x0001, ioaddr + 4);			/* Activate board. */ +	outw(0x3f00, ioaddr + 8);			/* Set the IRQ line. */ + +	/* Set the station address in window 2. */ +	EL3WINDOW(2); +	for (i = 0; i < 6; i++) +		outb(dev->dev_addr[i], ioaddr + i); + +	tc589_set_xcvr(dev, dev->if_port); + +	/* Switch to the stats window, and clear all stats by reading. */ +	outw(StatsDisable, ioaddr + EL3_CMD); +	EL3WINDOW(6); +	for (i = 0; i < 9; i++) +		inb(ioaddr+i); +	inw(ioaddr + 10); +	inw(ioaddr + 12); + +	/* Switch to register set 1 for normal use. */ +	EL3WINDOW(1); + +	set_rx_mode(dev); +	outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ +	outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ +	outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ +	/* Allow status bits to be seen. */ +	outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); +	/* Ack all pending events, and set active indicator mask. */ +	outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, +	 ioaddr + EL3_CMD); +	outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull +	 | AdapterFailure, ioaddr + EL3_CMD); +} + +static void netdev_get_drvinfo(struct net_device *dev, +			       struct ethtool_drvinfo *info) +{ +	strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); +	strlcpy(info->version, DRV_VERSION, sizeof(info->version)); +	snprintf(info->bus_info, sizeof(info->bus_info), +		"PCMCIA 0x%lx", dev->base_addr); +} + +static const struct ethtool_ops netdev_ethtool_ops = { +	.get_drvinfo		= netdev_get_drvinfo, +}; + +static int el3_config(struct net_device *dev, struct ifmap *map) +{ +	if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { +		if (map->port <= 3) { +			dev->if_port = map->port; +			netdev_info(dev, "switched to %s port\n", if_names[dev->if_port]); +			tc589_set_xcvr(dev, dev->if_port); +		} else { +			return -EINVAL; +		} +	} +	return 0; +} + +static int el3_open(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	struct pcmcia_device *link = lp->p_dev; + +	if (!pcmcia_dev_present(link)) +		return -ENODEV; + +	link->open++; +	netif_start_queue(dev); + +	tc589_reset(dev); +	init_timer(&lp->media); +	lp->media.function = media_check; +	lp->media.data = (unsigned long) dev; +	lp->media.expires = jiffies + HZ; +	add_timer(&lp->media); + +	dev_dbg(&link->dev, "%s: opened, status %4.4x.\n", +	  dev->name, inw(dev->base_addr + EL3_STATUS)); + +	return 0; +} + +static void el3_tx_timeout(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; + +	netdev_warn(dev, "Transmit timed out!\n"); +	dump_status(dev); +	dev->stats.tx_errors++; +	dev->trans_start = jiffies; /* prevent tx timeout */ +	/* Issue TX_RESET and TX_START commands. */ +	tc589_wait_for_completion(dev, TxReset); +	outw(TxEnable, ioaddr + EL3_CMD); +	netif_wake_queue(dev); +} + +static void pop_tx_status(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	int i; + +	/* Clear the Tx status stack. */ +	for (i = 32; i > 0; i--) { +		u_char tx_status = inb(ioaddr + TX_STATUS); +		if (!(tx_status & 0x84)) +			break; +		/* reset transmitter on jabber error or underrun */ +		if (tx_status & 0x30) +			tc589_wait_for_completion(dev, TxReset); +		if (tx_status & 0x38) { +			netdev_dbg(dev, "transmit error: status 0x%02x\n", tx_status); +			outw(TxEnable, ioaddr + EL3_CMD); +			dev->stats.tx_aborted_errors++; +		} +		outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ +	} +} + +static netdev_tx_t el3_start_xmit(struct sk_buff *skb, +					struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	struct el3_private *priv = netdev_priv(dev); +	unsigned long flags; + +	netdev_dbg(dev, "el3_start_xmit(length = %ld) called, status %4.4x.\n", +	       (long)skb->len, inw(ioaddr + EL3_STATUS)); + +	spin_lock_irqsave(&priv->lock, flags); + +	dev->stats.tx_bytes += skb->len; + +	/* Put out the doubleword header... */ +	outw(skb->len, ioaddr + TX_FIFO); +	outw(0x00, ioaddr + TX_FIFO); +	/* ... and the packet rounded to a doubleword. */ +	outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); + +	if (inw(ioaddr + TX_FREE) <= 1536) { +		netif_stop_queue(dev); +		/* Interrupt us when the FIFO has room for max-sized packet. */ +		outw(SetTxThreshold + 1536, ioaddr + EL3_CMD); +	} + +	pop_tx_status(dev); +	spin_unlock_irqrestore(&priv->lock, flags); +	dev_kfree_skb(skb); + +	return NETDEV_TX_OK; +} + +/* The EL3 interrupt handler. */ +static irqreturn_t el3_interrupt(int irq, void *dev_id) +{ +	struct net_device *dev = (struct net_device *) dev_id; +	struct el3_private *lp = netdev_priv(dev); +	unsigned int ioaddr; +	__u16 status; +	int i = 0, handled = 1; + +	if (!netif_device_present(dev)) +		return IRQ_NONE; + +	ioaddr = dev->base_addr; + +	netdev_dbg(dev, "interrupt, status %4.4x.\n", inw(ioaddr + EL3_STATUS)); + +	spin_lock(&lp->lock); +	while ((status = inw(ioaddr + EL3_STATUS)) & +	(IntLatch | RxComplete | StatsFull)) { +		if ((status & 0xe000) != 0x2000) { +			netdev_dbg(dev, "interrupt from dead card\n"); +			handled = 0; +			break; +		} +		if (status & RxComplete) +			el3_rx(dev); +		if (status & TxAvailable) { +			netdev_dbg(dev, "    TX room bit was handled.\n"); +			/* There's room in the FIFO for a full-sized packet. */ +			outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); +			netif_wake_queue(dev); +		} +		if (status & TxComplete) +			pop_tx_status(dev); +		if (status & (AdapterFailure | RxEarly | StatsFull)) { +			/* Handle all uncommon interrupts. */ +			if (status & StatsFull)		/* Empty statistics. */ +				update_stats(dev); +			if (status & RxEarly) { +				/* Rx early is unused. */ +				el3_rx(dev); +				outw(AckIntr | RxEarly, ioaddr + EL3_CMD); +			} +			if (status & AdapterFailure) { +				u16 fifo_diag; +				EL3WINDOW(4); +				fifo_diag = inw(ioaddr + 4); +				EL3WINDOW(1); +				netdev_warn(dev, "adapter failure, FIFO diagnostic register %04x.\n", +			    fifo_diag); +				if (fifo_diag & 0x0400) { +					/* Tx overrun */ +					tc589_wait_for_completion(dev, TxReset); +					outw(TxEnable, ioaddr + EL3_CMD); +				} +				if (fifo_diag & 0x2000) { +					/* Rx underrun */ +					tc589_wait_for_completion(dev, RxReset); +					set_rx_mode(dev); +					outw(RxEnable, ioaddr + EL3_CMD); +				} +				outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); +			} +		} +		if (++i > 10) { +			netdev_err(dev, "infinite loop in interrupt, status %4.4x.\n", +					status); +			/* Clear all interrupts */ +			outw(AckIntr | 0xFF, ioaddr + EL3_CMD); +			break; +		} +		/* Acknowledge the IRQ. */ +		outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); +	} +	lp->last_irq = jiffies; +	spin_unlock(&lp->lock); +	netdev_dbg(dev, "exiting interrupt, status %4.4x.\n", +			inw(ioaddr + EL3_STATUS)); +	return IRQ_RETVAL(handled); +} + +static void media_check(unsigned long arg) +{ +	struct net_device *dev = (struct net_device *)(arg); +	struct el3_private *lp = netdev_priv(dev); +	unsigned int ioaddr = dev->base_addr; +	u16 media, errs; +	unsigned long flags; + +	if (!netif_device_present(dev)) +		goto reschedule; + +	/* Check for pending interrupt with expired latency timer: with +	 * this, we can limp along even if the interrupt is blocked +	 */ +	if ((inw(ioaddr + EL3_STATUS) & IntLatch) && +	(inb(ioaddr + EL3_TIMER) == 0xff)) { +		if (!lp->fast_poll) +			netdev_warn(dev, "interrupt(s) dropped!\n"); + +		local_irq_save(flags); +		el3_interrupt(dev->irq, dev); +		local_irq_restore(flags); + +		lp->fast_poll = HZ; +	} +	if (lp->fast_poll) { +		lp->fast_poll--; +		lp->media.expires = jiffies + HZ/100; +		add_timer(&lp->media); +		return; +	} + +	/* lp->lock guards the EL3 window. Window should always be 1 except +	 * when the lock is held +	 */ + +	spin_lock_irqsave(&lp->lock, flags); +	EL3WINDOW(4); +	media = inw(ioaddr+WN4_MEDIA) & 0xc810; + +	/* Ignore collisions unless we've had no irq's recently */ +	if (time_before(jiffies, lp->last_irq + HZ)) { +		media &= ~0x0010; +	} else { +		/* Try harder to detect carrier errors */ +		EL3WINDOW(6); +		outw(StatsDisable, ioaddr + EL3_CMD); +		errs = inb(ioaddr + 0); +		outw(StatsEnable, ioaddr + EL3_CMD); +		dev->stats.tx_carrier_errors += errs; +		if (errs || (lp->media_status & 0x0010)) +			media |= 0x0010; +	} + +	if (media != lp->media_status) { +		if ((media & lp->media_status & 0x8000) && +				((lp->media_status ^ media) & 0x0800)) +		netdev_info(dev, "%s link beat\n", +				(lp->media_status & 0x0800 ? "lost" : "found")); +		else if ((media & lp->media_status & 0x4000) && +		 ((lp->media_status ^ media) & 0x0010)) +		netdev_info(dev, "coax cable %s\n", +				(lp->media_status & 0x0010 ? "ok" : "problem")); +		if (dev->if_port == 0) { +			if (media & 0x8000) { +				if (media & 0x0800) +					netdev_info(dev, "flipped to 10baseT\n"); +				else +			tc589_set_xcvr(dev, 2); +			} else if (media & 0x4000) { +				if (media & 0x0010) +					tc589_set_xcvr(dev, 1); +				else +					netdev_info(dev, "flipped to 10base2\n"); +			} +		} +		lp->media_status = media; +	} + +	EL3WINDOW(1); +	spin_unlock_irqrestore(&lp->lock, flags); + +reschedule: +	lp->media.expires = jiffies + HZ; +	add_timer(&lp->media); +} + +static struct net_device_stats *el3_get_stats(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	unsigned long flags; +	struct pcmcia_device *link = lp->p_dev; + +	if (pcmcia_dev_present(link)) { +		spin_lock_irqsave(&lp->lock, flags); +		update_stats(dev); +		spin_unlock_irqrestore(&lp->lock, flags); +	} +	return &dev->stats; +} + +/* Update statistics.  We change to register window 6, so this should be run +* single-threaded if the device is active. This is expected to be a rare +* operation, and it's simpler for the rest of the driver to assume that +* window 1 is always valid rather than use a special window-state variable. +* +* Caller must hold the lock for this +*/ + +static void update_stats(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; + +	netdev_dbg(dev, "updating the statistics.\n"); +	/* Turn off statistics updates while reading. */ +	outw(StatsDisable, ioaddr + EL3_CMD); +	/* Switch to the stats window, and read everything. */ +	EL3WINDOW(6); +	dev->stats.tx_carrier_errors	+= inb(ioaddr + 0); +	dev->stats.tx_heartbeat_errors	+= inb(ioaddr + 1); +	/* Multiple collisions. */ +	inb(ioaddr + 2); +	dev->stats.collisions		+= inb(ioaddr + 3); +	dev->stats.tx_window_errors		+= inb(ioaddr + 4); +	dev->stats.rx_fifo_errors		+= inb(ioaddr + 5); +	dev->stats.tx_packets		+= inb(ioaddr + 6); +	/* Rx packets   */ +	inb(ioaddr + 7); +	/* Tx deferrals */ +	inb(ioaddr + 8); +	/* Rx octets */ +	inw(ioaddr + 10); +	/* Tx octets */ +	inw(ioaddr + 12); + +	/* Back to window 1, and turn statistics back on. */ +	EL3WINDOW(1); +	outw(StatsEnable, ioaddr + EL3_CMD); +} + +static int el3_rx(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	int worklimit = 32; +	short rx_status; + +	netdev_dbg(dev, "in rx_packet(), status %4.4x, rx_status %4.4x.\n", +	       inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS)); +	while (!((rx_status = inw(ioaddr + RX_STATUS)) & 0x8000) && +		    worklimit > 0) { +		worklimit--; +		if (rx_status & 0x4000) { /* Error, update stats. */ +			short error = rx_status & 0x3800; +			dev->stats.rx_errors++; +			switch (error) { +			case 0x0000: +				dev->stats.rx_over_errors++; +				break; +			case 0x0800: +				dev->stats.rx_length_errors++; +				break; +			case 0x1000: +				dev->stats.rx_frame_errors++; +				break; +			case 0x1800: +				dev->stats.rx_length_errors++; +				break; +			case 0x2000: +				dev->stats.rx_frame_errors++; +				break; +			case 0x2800: +				dev->stats.rx_crc_errors++; +				break; +			} +		} else { +			short pkt_len = rx_status & 0x7ff; +			struct sk_buff *skb; + +			skb = netdev_alloc_skb(dev, pkt_len + 5); + +			netdev_dbg(dev, "    Receiving packet size %d status %4.4x.\n", +		       pkt_len, rx_status); +			if (skb != NULL) { +				skb_reserve(skb, 2); +				insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len), +			(pkt_len+3)>>2); +				skb->protocol = eth_type_trans(skb, dev); +				netif_rx(skb); +				dev->stats.rx_packets++; +				dev->stats.rx_bytes += pkt_len; +			} else { +				netdev_dbg(dev, "couldn't allocate a sk_buff of size %d.\n", +			   pkt_len); +				dev->stats.rx_dropped++; +			} +		} +		/* Pop the top of the Rx FIFO */ +		tc589_wait_for_completion(dev, RxDiscard); +	} +	if (worklimit == 0) +		netdev_warn(dev, "too much work in el3_rx!\n"); +	return 0; +} + +static void set_rx_mode(struct net_device *dev) +{ +	unsigned int ioaddr = dev->base_addr; +	u16 opts = SetRxFilter | RxStation | RxBroadcast; + +	if (dev->flags & IFF_PROMISC) +		opts |= RxMulticast | RxProm; +	else if (!netdev_mc_empty(dev) || (dev->flags & IFF_ALLMULTI)) +		opts |= RxMulticast; +	outw(opts, ioaddr + EL3_CMD); +} + +static void set_multicast_list(struct net_device *dev) +{ +	struct el3_private *priv = netdev_priv(dev); +	unsigned long flags; + +	spin_lock_irqsave(&priv->lock, flags); +	set_rx_mode(dev); +	spin_unlock_irqrestore(&priv->lock, flags); +} + +static int el3_close(struct net_device *dev) +{ +	struct el3_private *lp = netdev_priv(dev); +	struct pcmcia_device *link = lp->p_dev; +	unsigned int ioaddr = dev->base_addr; + +	dev_dbg(&link->dev, "%s: shutting down ethercard.\n", dev->name); + +	if (pcmcia_dev_present(link)) { +		/* Turn off statistics ASAP.  We update dev->stats below. */ +		outw(StatsDisable, ioaddr + EL3_CMD); + +		/* Disable the receiver and transmitter. */ +		outw(RxDisable, ioaddr + EL3_CMD); +		outw(TxDisable, ioaddr + EL3_CMD); + +		if (dev->if_port == 2) +			/* Turn off thinnet power.  Green! */ +			outw(StopCoax, ioaddr + EL3_CMD); +		else if (dev->if_port == 1) { +			/* Disable link beat and jabber */ +			EL3WINDOW(4); +			outw(0, ioaddr + WN4_MEDIA); +		} + +		/* Switching back to window 0 disables the IRQ. */ +		EL3WINDOW(0); +		/* But we explicitly zero the IRQ line select anyway. */ +		outw(0x0f00, ioaddr + WN0_IRQ); + +		/* Check if the card still exists */ +		if ((inw(ioaddr+EL3_STATUS) & 0xe000) == 0x2000) +			update_stats(dev); +	} + +	link->open--; +	netif_stop_queue(dev); +	del_timer_sync(&lp->media); + +	return 0; +} + +static const struct pcmcia_device_id tc589_ids[] = { +	PCMCIA_MFC_DEVICE_MANF_CARD(0, 0x0101, 0x0562), +	PCMCIA_MFC_DEVICE_PROD_ID1(0, "Motorola MARQUIS", 0xf03e4e77), +	PCMCIA_DEVICE_MANF_CARD(0x0101, 0x0589), +	PCMCIA_DEVICE_PROD_ID12("Farallon", "ENet", 0x58d93fc4, 0x992c2202), +	PCMCIA_MFC_DEVICE_CIS_MANF_CARD(0, 0x0101, 0x0035, "cis/3CXEM556.cis"), +	PCMCIA_MFC_DEVICE_CIS_MANF_CARD(0, 0x0101, 0x003d, "cis/3CXEM556.cis"), +	PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, tc589_ids); + +static struct pcmcia_driver tc589_driver = { +	.owner		= THIS_MODULE, +	.name		= "3c589_cs", +	.probe		= tc589_probe, +	.remove		= tc589_detach, +	.id_table	= tc589_ids, +	.suspend	= tc589_suspend, +	.resume		= tc589_resume, +}; +module_pcmcia_driver(tc589_driver); diff --git a/drivers/net/ethernet/3com/3c59x.c b/drivers/net/ethernet/3com/3c59x.c new file mode 100644 index 00000000000..61477b8e8d2 --- /dev/null +++ b/drivers/net/ethernet/3com/3c59x.c @@ -0,0 +1,3328 @@ +/* EtherLinkXL.c: A 3Com EtherLink PCI III/XL ethernet driver for linux. */ +/* +	Written 1996-1999 by Donald Becker. + +	This software may be used and distributed according to the terms +	of the GNU General Public License, incorporated herein by reference. + +	This driver is for the 3Com "Vortex" and "Boomerang" series ethercards. +	Members of the series include Fast EtherLink 3c590/3c592/3c595/3c597 +	and the EtherLink XL 3c900 and 3c905 cards. + +	Problem reports and questions should be directed to +	vortex@scyld.com + +	The author may be reached as becker@scyld.com, or C/O +	Scyld Computing Corporation +	410 Severn Ave., Suite 210 +	Annapolis MD 21403 + +*/ + +/* + * FIXME: This driver _could_ support MTU changing, but doesn't.  See Don's hamachi.c implementation + * as well as other drivers + * + * NOTE: If you make 'vortex_debug' a constant (#define vortex_debug 0) the driver shrinks by 2k + * due to dead code elimination.  There will be some performance benefits from this due to + * elimination of all the tests and reduced cache footprint. + */ + + +#define DRV_NAME	"3c59x" + + + +/* A few values that may be tweaked. */ +/* Keep the ring sizes a power of two for efficiency. */ +#define TX_RING_SIZE	16 +#define RX_RING_SIZE	32 +#define PKT_BUF_SZ		1536			/* Size of each temporary Rx buffer.*/ + +/* "Knobs" that adjust features and parameters. */ +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. +   Setting to > 1512 effectively disables this feature. */ +#ifndef __arm__ +static int rx_copybreak = 200; +#else +/* ARM systems perform better by disregarding the bus-master +   transfer capability of these cards. -- rmk */ +static int rx_copybreak = 1513; +#endif +/* Allow setting MTU to a larger size, bypassing the normal ethernet setup. */ +static const int mtu = 1500; +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +static int max_interrupt_work = 32; +/* Tx timeout interval (millisecs) */ +static int watchdog = 5000; + +/* Allow aggregation of Tx interrupts.  Saves CPU load at the cost + * of possible Tx stalls if the system is blocking interrupts + * somewhere else.  Undefine this to disable. + */ +#define tx_interrupt_mitigation 1 + +/* Put out somewhat more debugging messages. (0: no msg, 1 minimal .. 6). */ +#define vortex_debug debug +#ifdef VORTEX_DEBUG +static int vortex_debug = VORTEX_DEBUG; +#else +static int vortex_debug = 1; +#endif + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/mii.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/ethtool.h> +#include <linux/highmem.h> +#include <linux/eisa.h> +#include <linux/bitops.h> +#include <linux/jiffies.h> +#include <linux/gfp.h> +#include <asm/irq.h>			/* For nr_irqs only. */ +#include <asm/io.h> +#include <asm/uaccess.h> + +/* Kernel compatibility defines, some common to David Hinds' PCMCIA package. +   This is only in the support-all-kernels source code. */ + +#define RUN_AT(x) (jiffies + (x)) + +#include <linux/delay.h> + + +static const char version[] = +	DRV_NAME ": Donald Becker and others.\n"; + +MODULE_AUTHOR("Donald Becker <becker@scyld.com>"); +MODULE_DESCRIPTION("3Com 3c59x/3c9xx ethernet driver "); +MODULE_LICENSE("GPL"); + + +/* Operational parameter that usually are not changed. */ + +/* The Vortex size is twice that of the original EtherLinkIII series: the +   runtime register window, window 1, is now always mapped in. +   The Boomerang size is twice as large as the Vortex -- it has additional +   bus master control registers. */ +#define VORTEX_TOTAL_SIZE 0x20 +#define BOOMERANG_TOTAL_SIZE 0x40 + +/* Set iff a MII transceiver on any interface requires mdio preamble. +   This only set with the original DP83840 on older 3c905 boards, so the extra +   code size of a per-interface flag is not worthwhile. */ +static char mii_preamble_required; + +#define PFX DRV_NAME ": " + + + +/* +				Theory of Operation + +I. Board Compatibility + +This device driver is designed for the 3Com FastEtherLink and FastEtherLink +XL, 3Com's PCI to 10/100baseT adapters.  It also works with the 10Mbs +versions of the FastEtherLink cards.  The supported product IDs are +  3c590, 3c592, 3c595, 3c597, 3c900, 3c905 + +The related ISA 3c515 is supported with a separate driver, 3c515.c, included +with the kernel source or available from +    cesdis.gsfc.nasa.gov:/pub/linux/drivers/3c515.html + +II. Board-specific settings + +PCI bus devices are configured by the system at boot time, so no jumpers +need to be set on the board.  The system BIOS should be set to assign the +PCI INTA signal to an otherwise unused system IRQ line. + +The EEPROM settings for media type and forced-full-duplex are observed. +The EEPROM media type should be left at the default "autoselect" unless using +10base2 or AUI connections which cannot be reliably detected. + +III. Driver operation + +The 3c59x series use an interface that's very similar to the previous 3c5x9 +series.  The primary interface is two programmed-I/O FIFOs, with an +alternate single-contiguous-region bus-master transfer (see next). + +The 3c900 "Boomerang" series uses a full-bus-master interface with separate +lists of transmit and receive descriptors, similar to the AMD LANCE/PCnet, +DEC Tulip and Intel Speedo3.  The first chip version retains a compatible +programmed-I/O interface that has been removed in 'B' and subsequent board +revisions. + +One extension that is advertised in a very large font is that the adapters +are capable of being bus masters.  On the Vortex chip this capability was +only for a single contiguous region making it far less useful than the full +bus master capability.  There is a significant performance impact of taking +an extra interrupt or polling for the completion of each transfer, as well +as difficulty sharing the single transfer engine between the transmit and +receive threads.  Using DMA transfers is a win only with large blocks or +with the flawed versions of the Intel Orion motherboard PCI controller. + +The Boomerang chip's full-bus-master interface is useful, and has the +currently-unused advantages over other similar chips that queued transmit +packets may be reordered and receive buffer groups are associated with a +single frame. + +With full-bus-master support, this driver uses a "RX_COPYBREAK" scheme. +Rather than a fixed intermediate receive buffer, this scheme allocates +full-sized skbuffs as receive buffers.  The value RX_COPYBREAK is used as +the copying breakpoint: it is chosen to trade-off the memory wasted by +passing the full-sized skbuff to the queue layer for all frames vs. the +copying cost of copying a frame to a correctly-sized skbuff. + +IIIC. Synchronization +The driver runs as two independent, single-threaded flows of control.  One +is the send-packet routine, which enforces single-threaded use by the +dev->tbusy flag.  The other thread is the interrupt handler, which is single +threaded by the hardware and other software. + +IV. Notes + +Thanks to Cameron Spitzer and Terry Murphy of 3Com for providing development +3c590, 3c595, and 3c900 boards. +The name "Vortex" is the internal 3Com project name for the PCI ASIC, and +the EISA version is called "Demon".  According to Terry these names come +from rides at the local amusement park. + +The new chips support both ethernet (1.5K) and FDDI (4.5K) packet sizes! +This driver only supports ethernet packets because of the skbuff allocation +limit of 4K. +*/ + +/* This table drives the PCI probe routines.  It's mostly boilerplate in all +   of the drivers, and will likely be provided by some future kernel. +*/ +enum pci_flags_bit { +	PCI_USES_MASTER=4, +}; + +enum {	IS_VORTEX=1, IS_BOOMERANG=2, IS_CYCLONE=4, IS_TORNADO=8, +	EEPROM_8BIT=0x10,	/* AKPM: Uses 0x230 as the base bitmaps for EEPROM reads */ +	HAS_PWR_CTRL=0x20, HAS_MII=0x40, HAS_NWAY=0x80, HAS_CB_FNS=0x100, +	INVERT_MII_PWR=0x200, INVERT_LED_PWR=0x400, MAX_COLLISION_RESET=0x800, +	EEPROM_OFFSET=0x1000, HAS_HWCKSM=0x2000, WNO_XCVR_PWR=0x4000, +	EXTRA_PREAMBLE=0x8000, EEPROM_RESET=0x10000, }; + +enum vortex_chips { +	CH_3C590 = 0, +	CH_3C592, +	CH_3C597, +	CH_3C595_1, +	CH_3C595_2, + +	CH_3C595_3, +	CH_3C900_1, +	CH_3C900_2, +	CH_3C900_3, +	CH_3C900_4, + +	CH_3C900_5, +	CH_3C900B_FL, +	CH_3C905_1, +	CH_3C905_2, +	CH_3C905B_TX, +	CH_3C905B_1, + +	CH_3C905B_2, +	CH_3C905B_FX, +	CH_3C905C, +	CH_3C9202, +	CH_3C980, +	CH_3C9805, + +	CH_3CSOHO100_TX, +	CH_3C555, +	CH_3C556, +	CH_3C556B, +	CH_3C575, + +	CH_3C575_1, +	CH_3CCFE575, +	CH_3CCFE575CT, +	CH_3CCFE656, +	CH_3CCFEM656, + +	CH_3CCFEM656_1, +	CH_3C450, +	CH_3C920, +	CH_3C982A, +	CH_3C982B, + +	CH_905BT4, +	CH_920B_EMB_WNM, +}; + + +/* note: this array directly indexed by above enums, and MUST + * be kept in sync with both the enums above, and the PCI device + * table below + */ +static struct vortex_chip_info { +	const char *name; +	int flags; +	int drv_flags; +	int io_size; +} vortex_info_tbl[] = { +	{"3c590 Vortex 10Mbps", +	 PCI_USES_MASTER, IS_VORTEX, 32, }, +	{"3c592 EISA 10Mbps Demon/Vortex",					/* AKPM: from Don's 3c59x_cb.c 0.49H */ +	 PCI_USES_MASTER, IS_VORTEX, 32, }, +	{"3c597 EISA Fast Demon/Vortex",					/* AKPM: from Don's 3c59x_cb.c 0.49H */ +	 PCI_USES_MASTER, IS_VORTEX, 32, }, +	{"3c595 Vortex 100baseTx", +	 PCI_USES_MASTER, IS_VORTEX, 32, }, +	{"3c595 Vortex 100baseT4", +	 PCI_USES_MASTER, IS_VORTEX, 32, }, + +	{"3c595 Vortex 100base-MII", +	 PCI_USES_MASTER, IS_VORTEX, 32, }, +	{"3c900 Boomerang 10baseT", +	 PCI_USES_MASTER, IS_BOOMERANG|EEPROM_RESET, 64, }, +	{"3c900 Boomerang 10Mbps Combo", +	 PCI_USES_MASTER, IS_BOOMERANG|EEPROM_RESET, 64, }, +	{"3c900 Cyclone 10Mbps TPO",						/* AKPM: from Don's 0.99M */ +	 PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, }, +	{"3c900 Cyclone 10Mbps Combo", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, }, + +	{"3c900 Cyclone 10Mbps TPC",						/* AKPM: from Don's 0.99M */ +	 PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, }, +	{"3c900B-FL Cyclone 10base-FL", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, }, +	{"3c905 Boomerang 100baseTx", +	 PCI_USES_MASTER, IS_BOOMERANG|HAS_MII|EEPROM_RESET, 64, }, +	{"3c905 Boomerang 100baseT4", +	 PCI_USES_MASTER, IS_BOOMERANG|HAS_MII|EEPROM_RESET, 64, }, +	{"3C905B-TX Fast Etherlink XL PCI", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, }, +	{"3c905B Cyclone 100baseTx", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, }, + +	{"3c905B Cyclone 10/100/BNC", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM, 128, }, +	{"3c905B-FX Cyclone 100baseFx", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, }, +	{"3c905C Tornado", +	PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, }, +	{"3c920B-EMB-WNM (ATI Radeon 9100 IGP)", +	 PCI_USES_MASTER, IS_TORNADO|HAS_MII|HAS_HWCKSM, 128, }, +	{"3c980 Cyclone", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM|EXTRA_PREAMBLE, 128, }, + +	{"3c980C Python-T", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM, 128, }, +	{"3cSOHO100-TX Hurricane", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, }, +	{"3c555 Laptop Hurricane", +	 PCI_USES_MASTER, IS_CYCLONE|EEPROM_8BIT|HAS_HWCKSM, 128, }, +	{"3c556 Laptop Tornado", +	 PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|EEPROM_8BIT|HAS_CB_FNS|INVERT_MII_PWR| +									HAS_HWCKSM, 128, }, +	{"3c556B Laptop Hurricane", +	 PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|EEPROM_OFFSET|HAS_CB_FNS|INVERT_MII_PWR| +	                                WNO_XCVR_PWR|HAS_HWCKSM, 128, }, + +	{"3c575 [Megahertz] 10/100 LAN 	CardBus", +	PCI_USES_MASTER, IS_BOOMERANG|HAS_MII|EEPROM_8BIT, 128, }, +	{"3c575 Boomerang CardBus", +	 PCI_USES_MASTER, IS_BOOMERANG|HAS_MII|EEPROM_8BIT, 128, }, +	{"3CCFE575BT Cyclone CardBus", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT| +									INVERT_LED_PWR|HAS_HWCKSM, 128, }, +	{"3CCFE575CT Tornado CardBus", +	 PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT|INVERT_MII_PWR| +									MAX_COLLISION_RESET|HAS_HWCKSM, 128, }, +	{"3CCFE656 Cyclone CardBus", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT|INVERT_MII_PWR| +									INVERT_LED_PWR|HAS_HWCKSM, 128, }, + +	{"3CCFEM656B Cyclone+Winmodem CardBus", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT|INVERT_MII_PWR| +									INVERT_LED_PWR|HAS_HWCKSM, 128, }, +	{"3CXFEM656C Tornado+Winmodem CardBus",			/* From pcmcia-cs-3.1.5 */ +	 PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT|INVERT_MII_PWR| +									MAX_COLLISION_RESET|HAS_HWCKSM, 128, }, +	{"3c450 HomePNA Tornado",						/* AKPM: from Don's 0.99Q */ +	 PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_HWCKSM, 128, }, +	{"3c920 Tornado", +	 PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_HWCKSM, 128, }, +	{"3c982 Hydra Dual Port A", +	 PCI_USES_MASTER, IS_TORNADO|HAS_HWCKSM|HAS_NWAY, 128, }, + +	{"3c982 Hydra Dual Port B", +	 PCI_USES_MASTER, IS_TORNADO|HAS_HWCKSM|HAS_NWAY, 128, }, +	{"3c905B-T4", +	 PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, }, +	{"3c920B-EMB-WNM Tornado", +	 PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_HWCKSM, 128, }, + +	{NULL,}, /* NULL terminated list. */ +}; + + +static DEFINE_PCI_DEVICE_TABLE(vortex_pci_tbl) = { +	{ 0x10B7, 0x5900, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C590 }, +	{ 0x10B7, 0x5920, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C592 }, +	{ 0x10B7, 0x5970, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C597 }, +	{ 0x10B7, 0x5950, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C595_1 }, +	{ 0x10B7, 0x5951, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C595_2 }, + +	{ 0x10B7, 0x5952, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C595_3 }, +	{ 0x10B7, 0x9000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C900_1 }, +	{ 0x10B7, 0x9001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C900_2 }, +	{ 0x10B7, 0x9004, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C900_3 }, +	{ 0x10B7, 0x9005, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C900_4 }, + +	{ 0x10B7, 0x9006, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C900_5 }, +	{ 0x10B7, 0x900A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C900B_FL }, +	{ 0x10B7, 0x9050, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C905_1 }, +	{ 0x10B7, 0x9051, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C905_2 }, +	{ 0x10B7, 0x9054, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C905B_TX }, +	{ 0x10B7, 0x9055, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C905B_1 }, + +	{ 0x10B7, 0x9058, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C905B_2 }, +	{ 0x10B7, 0x905A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C905B_FX }, +	{ 0x10B7, 0x9200, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C905C }, +	{ 0x10B7, 0x9202, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C9202 }, +	{ 0x10B7, 0x9800, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C980 }, +	{ 0x10B7, 0x9805, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C9805 }, + +	{ 0x10B7, 0x7646, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3CSOHO100_TX }, +	{ 0x10B7, 0x5055, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C555 }, +	{ 0x10B7, 0x6055, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C556 }, +	{ 0x10B7, 0x6056, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C556B }, +	{ 0x10B7, 0x5b57, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C575 }, + +	{ 0x10B7, 0x5057, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C575_1 }, +	{ 0x10B7, 0x5157, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3CCFE575 }, +	{ 0x10B7, 0x5257, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3CCFE575CT }, +	{ 0x10B7, 0x6560, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3CCFE656 }, +	{ 0x10B7, 0x6562, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3CCFEM656 }, + +	{ 0x10B7, 0x6564, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3CCFEM656_1 }, +	{ 0x10B7, 0x4500, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C450 }, +	{ 0x10B7, 0x9201, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C920 }, +	{ 0x10B7, 0x1201, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C982A }, +	{ 0x10B7, 0x1202, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_3C982B }, + +	{ 0x10B7, 0x9056, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_905BT4 }, +	{ 0x10B7, 0x9210, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_920B_EMB_WNM }, + +	{0,}						/* 0 terminated list. */ +}; +MODULE_DEVICE_TABLE(pci, vortex_pci_tbl); + + +/* Operational definitions. +   These are not used by other compilation units and thus are not +   exported in a ".h" file. + +   First the windows.  There are eight register windows, with the command +   and status registers available in each. +   */ +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e + +/* The top five bits written to EL3_CMD are a command, the lower +   11 bits are the parameter, if applicable. +   Note that 11 parameters bits was fine for ethernet, but the new chip +   can handle FDDI length frames (~4500 octets) and now parameters count +   32-bit 'Dwords' rather than octets. */ + +enum vortex_cmd { +	TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, +	RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, +	UpStall = 6<<11, UpUnstall = (6<<11)+1, +	DownStall = (6<<11)+2, DownUnstall = (6<<11)+3, +	RxDiscard = 8<<11, TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, +	FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, +	SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, +	SetTxThreshold = 18<<11, SetTxStart = 19<<11, +	StartDMAUp = 20<<11, StartDMADown = (20<<11)+1, StatsEnable = 21<<11, +	StatsDisable = 22<<11, StopCoax = 23<<11, SetFilterBit = 25<<11,}; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { +	RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 }; + +/* Bits in the general status register. */ +enum vortex_status { +	IntLatch = 0x0001, HostError = 0x0002, TxComplete = 0x0004, +	TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, +	IntReq = 0x0040, StatsFull = 0x0080, +	DMADone = 1<<8, DownComplete = 1<<9, UpComplete = 1<<10, +	DMAInProgress = 1<<11,			/* DMA controller is still busy.*/ +	CmdInProgress = 1<<12,			/* EL3_CMD is still busy.*/ +}; + +/* Register window 1 offsets, the window used in normal operation. +   On the Vortex this window is always mapped at offsets 0x10-0x1f. */ +enum Window1 { +	TX_FIFO = 0x10,  RX_FIFO = 0x10,  RxErrors = 0x14, +	RxStatus = 0x18,  Timer=0x1A, TxStatus = 0x1B, +	TxFree = 0x1C, /* Remaining free bytes in Tx buffer. */ +}; +enum Window0 { +	Wn0EepromCmd = 10,		/* Window 0: EEPROM command register. */ +	Wn0EepromData = 12,		/* Window 0: EEPROM results register. */ +	IntrStatus=0x0E,		/* Valid in all windows. */ +}; +enum Win0_EEPROM_bits { +	EEPROM_Read = 0x80, EEPROM_WRITE = 0x40, EEPROM_ERASE = 0xC0, +	EEPROM_EWENB = 0x30,		/* Enable erasing/writing for 10 msec. */ +	EEPROM_EWDIS = 0x00,		/* Disable EWENB before 10 msec timeout. */ +}; +/* EEPROM locations. */ +enum eeprom_offset { +	PhysAddr01=0, PhysAddr23=1, PhysAddr45=2, ModelID=3, +	EtherLink3ID=7, IFXcvrIO=8, IRQLine=9, +	NodeAddr01=10, NodeAddr23=11, NodeAddr45=12, +	DriverTune=13, Checksum=15}; + +enum Window2 {			/* Window 2. */ +	Wn2_ResetOptions=12, +}; +enum Window3 {			/* Window 3: MAC/config bits. */ +	Wn3_Config=0, Wn3_MaxPktSize=4, Wn3_MAC_Ctrl=6, Wn3_Options=8, +}; + +#define BFEXT(value, offset, bitcount)  \ +    ((((unsigned long)(value)) >> (offset)) & ((1 << (bitcount)) - 1)) + +#define BFINS(lhs, rhs, offset, bitcount)					\ +	(((lhs) & ~((((1 << (bitcount)) - 1)) << (offset))) |	\ +	(((rhs) & ((1 << (bitcount)) - 1)) << (offset))) + +#define RAM_SIZE(v)		BFEXT(v, 0, 3) +#define RAM_WIDTH(v)	BFEXT(v, 3, 1) +#define RAM_SPEED(v)	BFEXT(v, 4, 2) +#define ROM_SIZE(v)		BFEXT(v, 6, 2) +#define RAM_SPLIT(v)	BFEXT(v, 16, 2) +#define XCVR(v)			BFEXT(v, 20, 4) +#define AUTOSELECT(v)	BFEXT(v, 24, 1) + +enum Window4 {		/* Window 4: Xcvr/media bits. */ +	Wn4_FIFODiag = 4, Wn4_NetDiag = 6, Wn4_PhysicalMgmt=8, Wn4_Media = 10, +}; +enum Win4_Media_bits { +	Media_SQE = 0x0008,		/* Enable SQE error counting for AUI. */ +	Media_10TP = 0x00C0,	/* Enable link beat and jabber for 10baseT. */ +	Media_Lnk = 0x0080,		/* Enable just link beat for 100TX/100FX. */ +	Media_LnkBeat = 0x0800, +}; +enum Window7 {					/* Window 7: Bus Master control. */ +	Wn7_MasterAddr = 0, Wn7_VlanEtherType=4, Wn7_MasterLen = 6, +	Wn7_MasterStatus = 12, +}; +/* Boomerang bus master control registers. */ +enum MasterCtrl { +	PktStatus = 0x20, DownListPtr = 0x24, FragAddr = 0x28, FragLen = 0x2c, +	TxFreeThreshold = 0x2f, UpPktStatus = 0x30, UpListPtr = 0x38, +}; + +/* The Rx and Tx descriptor lists. +   Caution Alpha hackers: these types are 32 bits!  Note also the 8 byte +   alignment contraint on tx_ring[] and rx_ring[]. */ +#define LAST_FRAG 	0x80000000			/* Last Addr/Len pair in descriptor. */ +#define DN_COMPLETE	0x00010000			/* This packet has been downloaded */ +struct boom_rx_desc { +	__le32 next;					/* Last entry points to 0.   */ +	__le32 status; +	__le32 addr;					/* Up to 63 addr/len pairs possible. */ +	__le32 length;					/* Set LAST_FRAG to indicate last pair. */ +}; +/* Values for the Rx status entry. */ +enum rx_desc_status { +	RxDComplete=0x00008000, RxDError=0x4000, +	/* See boomerang_rx() for actual error bits */ +	IPChksumErr=1<<25, TCPChksumErr=1<<26, UDPChksumErr=1<<27, +	IPChksumValid=1<<29, TCPChksumValid=1<<30, UDPChksumValid=1<<31, +}; + +#ifdef MAX_SKB_FRAGS +#define DO_ZEROCOPY 1 +#else +#define DO_ZEROCOPY 0 +#endif + +struct boom_tx_desc { +	__le32 next;					/* Last entry points to 0.   */ +	__le32 status;					/* bits 0:12 length, others see below.  */ +#if DO_ZEROCOPY +	struct { +		__le32 addr; +		__le32 length; +	} frag[1+MAX_SKB_FRAGS]; +#else +		__le32 addr; +		__le32 length; +#endif +}; + +/* Values for the Tx status entry. */ +enum tx_desc_status { +	CRCDisable=0x2000, TxDComplete=0x8000, +	AddIPChksum=0x02000000, AddTCPChksum=0x04000000, AddUDPChksum=0x08000000, +	TxIntrUploaded=0x80000000,		/* IRQ when in FIFO, but maybe not sent. */ +}; + +/* Chip features we care about in vp->capabilities, read from the EEPROM. */ +enum ChipCaps { CapBusMaster=0x20, CapPwrMgmt=0x2000 }; + +struct vortex_extra_stats { +	unsigned long tx_deferred; +	unsigned long tx_max_collisions; +	unsigned long tx_multiple_collisions; +	unsigned long tx_single_collisions; +	unsigned long rx_bad_ssd; +}; + +struct vortex_private { +	/* The Rx and Tx rings should be quad-word-aligned. */ +	struct boom_rx_desc* rx_ring; +	struct boom_tx_desc* tx_ring; +	dma_addr_t rx_ring_dma; +	dma_addr_t tx_ring_dma; +	/* The addresses of transmit- and receive-in-place skbuffs. */ +	struct sk_buff* rx_skbuff[RX_RING_SIZE]; +	struct sk_buff* tx_skbuff[TX_RING_SIZE]; +	unsigned int cur_rx, cur_tx;		/* The next free ring entry */ +	unsigned int dirty_rx, dirty_tx;	/* The ring entries to be free()ed. */ +	struct vortex_extra_stats xstats;	/* NIC-specific extra stats */ +	struct sk_buff *tx_skb;				/* Packet being eaten by bus master ctrl.  */ +	dma_addr_t tx_skb_dma;				/* Allocated DMA address for bus master ctrl DMA.   */ + +	/* PCI configuration space information. */ +	struct device *gendev; +	void __iomem *ioaddr;			/* IO address space */ +	void __iomem *cb_fn_base;		/* CardBus function status addr space. */ + +	/* Some values here only for performance evaluation and path-coverage */ +	int rx_nocopy, rx_copy, queued_packet, rx_csumhits; +	int card_idx; + +	/* The remainder are related to chip state, mostly media selection. */ +	struct timer_list timer;			/* Media selection timer. */ +	struct timer_list rx_oom_timer;		/* Rx skb allocation retry timer */ +	int options;						/* User-settable misc. driver options. */ +	unsigned int media_override:4, 		/* Passed-in media type. */ +		default_media:4,				/* Read from the EEPROM/Wn3_Config. */ +		full_duplex:1, autoselect:1, +		bus_master:1,					/* Vortex can only do a fragment bus-m. */ +		full_bus_master_tx:1, full_bus_master_rx:2, /* Boomerang  */ +		flow_ctrl:1,					/* Use 802.3x flow control (PAUSE only) */ +		partner_flow_ctrl:1,			/* Partner supports flow control */ +		has_nway:1, +		enable_wol:1,					/* Wake-on-LAN is enabled */ +		pm_state_valid:1,				/* pci_dev->saved_config_space has sane contents */ +		open:1, +		medialock:1, +		large_frames:1,			/* accept large frames */ +		handling_irq:1;			/* private in_irq indicator */ +	/* {get|set}_wol operations are already serialized by rtnl. +	 * no additional locking is required for the enable_wol and acpi_set_WOL() +	 */ +	int drv_flags; +	u16 status_enable; +	u16 intr_enable; +	u16 available_media;				/* From Wn3_Options. */ +	u16 capabilities, info1, info2;		/* Various, from EEPROM. */ +	u16 advertising;					/* NWay media advertisement */ +	unsigned char phys[2];				/* MII device addresses. */ +	u16 deferred;						/* Resend these interrupts when we +										 * bale from the ISR */ +	u16 io_size;						/* Size of PCI region (for release_region) */ + +	/* Serialises access to hardware other than MII and variables below. +	 * The lock hierarchy is rtnl_lock > {lock, mii_lock} > window_lock. */ +	spinlock_t lock; + +	spinlock_t mii_lock;		/* Serialises access to MII */ +	struct mii_if_info mii;		/* MII lib hooks/info */ +	spinlock_t window_lock;		/* Serialises access to windowed regs */ +	int window;			/* Register window */ +}; + +static void window_set(struct vortex_private *vp, int window) +{ +	if (window != vp->window) { +		iowrite16(SelectWindow + window, vp->ioaddr + EL3_CMD); +		vp->window = window; +	} +} + +#define DEFINE_WINDOW_IO(size)						\ +static u ## size							\ +window_read ## size(struct vortex_private *vp, int window, int addr)	\ +{									\ +	unsigned long flags;						\ +	u ## size ret;							\ +	spin_lock_irqsave(&vp->window_lock, flags);			\ +	window_set(vp, window);						\ +	ret = ioread ## size(vp->ioaddr + addr);			\ +	spin_unlock_irqrestore(&vp->window_lock, flags);		\ +	return ret;							\ +}									\ +static void								\ +window_write ## size(struct vortex_private *vp, u ## size value,	\ +		     int window, int addr)				\ +{									\ +	unsigned long flags;						\ +	spin_lock_irqsave(&vp->window_lock, flags);			\ +	window_set(vp, window);						\ +	iowrite ## size(value, vp->ioaddr + addr);			\ +	spin_unlock_irqrestore(&vp->window_lock, flags);		\ +} +DEFINE_WINDOW_IO(8) +DEFINE_WINDOW_IO(16) +DEFINE_WINDOW_IO(32) + +#ifdef CONFIG_PCI +#define DEVICE_PCI(dev) ((dev_is_pci(dev)) ? to_pci_dev((dev)) : NULL) +#else +#define DEVICE_PCI(dev) NULL +#endif + +#define VORTEX_PCI(vp)							\ +	((struct pci_dev *) (((vp)->gendev) ? DEVICE_PCI((vp)->gendev) : NULL)) + +#ifdef CONFIG_EISA +#define DEVICE_EISA(dev) (((dev)->bus == &eisa_bus_type) ? to_eisa_device((dev)) : NULL) +#else +#define DEVICE_EISA(dev) NULL +#endif + +#define VORTEX_EISA(vp)							\ +	((struct eisa_device *) (((vp)->gendev) ? DEVICE_EISA((vp)->gendev) : NULL)) + +/* The action to take with a media selection timer tick. +   Note that we deviate from the 3Com order by checking 10base2 before AUI. + */ +enum xcvr_types { +	XCVR_10baseT=0, XCVR_AUI, XCVR_10baseTOnly, XCVR_10base2, XCVR_100baseTx, +	XCVR_100baseFx, XCVR_MII=6, XCVR_NWAY=8, XCVR_ExtMII=9, XCVR_Default=10, +}; + +static const struct media_table { +	char *name; +	unsigned int media_bits:16,		/* Bits to set in Wn4_Media register. */ +		mask:8,						/* The transceiver-present bit in Wn3_Config.*/ +		next:8;						/* The media type to try next. */ +	int wait;						/* Time before we check media status. */ +} media_tbl[] = { +  {	"10baseT",   Media_10TP,0x08, XCVR_10base2, (14*HZ)/10}, +  { "10Mbs AUI", Media_SQE, 0x20, XCVR_Default, (1*HZ)/10}, +  { "undefined", 0,			0x80, XCVR_10baseT, 10000}, +  { "10base2",   0,			0x10, XCVR_AUI,		(1*HZ)/10}, +  { "100baseTX", Media_Lnk, 0x02, XCVR_100baseFx, (14*HZ)/10}, +  { "100baseFX", Media_Lnk, 0x04, XCVR_MII,		(14*HZ)/10}, +  { "MII",		 0,			0x41, XCVR_10baseT, 3*HZ }, +  { "undefined", 0,			0x01, XCVR_10baseT, 10000}, +  { "Autonegotiate", 0,		0x41, XCVR_10baseT, 3*HZ}, +  { "MII-External",	 0,		0x41, XCVR_10baseT, 3*HZ }, +  { "Default",	 0,			0xFF, XCVR_10baseT, 10000}, +}; + +static struct { +	const char str[ETH_GSTRING_LEN]; +} ethtool_stats_keys[] = { +	{ "tx_deferred" }, +	{ "tx_max_collisions" }, +	{ "tx_multiple_collisions" }, +	{ "tx_single_collisions" }, +	{ "rx_bad_ssd" }, +}; + +/* number of ETHTOOL_GSTATS u64's */ +#define VORTEX_NUM_STATS    5 + +static int vortex_probe1(struct device *gendev, void __iomem *ioaddr, int irq, +				   int chip_idx, int card_idx); +static int vortex_up(struct net_device *dev); +static void vortex_down(struct net_device *dev, int final); +static int vortex_open(struct net_device *dev); +static void mdio_sync(struct vortex_private *vp, int bits); +static int mdio_read(struct net_device *dev, int phy_id, int location); +static void mdio_write(struct net_device *vp, int phy_id, int location, int value); +static void vortex_timer(unsigned long arg); +static void rx_oom_timer(unsigned long arg); +static netdev_tx_t vortex_start_xmit(struct sk_buff *skb, +				     struct net_device *dev); +static netdev_tx_t boomerang_start_xmit(struct sk_buff *skb, +					struct net_device *dev); +static int vortex_rx(struct net_device *dev); +static int boomerang_rx(struct net_device *dev); +static irqreturn_t vortex_interrupt(int irq, void *dev_id); +static irqreturn_t boomerang_interrupt(int irq, void *dev_id); +static int vortex_close(struct net_device *dev); +static void dump_tx_ring(struct net_device *dev); +static void update_stats(void __iomem *ioaddr, struct net_device *dev); +static struct net_device_stats *vortex_get_stats(struct net_device *dev); +static void set_rx_mode(struct net_device *dev); +#ifdef CONFIG_PCI +static int vortex_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +#endif +static void vortex_tx_timeout(struct net_device *dev); +static void acpi_set_WOL(struct net_device *dev); +static const struct ethtool_ops vortex_ethtool_ops; +static void set_8021q_mode(struct net_device *dev, int enable); + +/* This driver uses 'options' to pass the media type, full-duplex flag, etc. */ +/* Option count limit only -- unlimited interfaces are supported. */ +#define MAX_UNITS 8 +static int options[MAX_UNITS] = { [0 ... MAX_UNITS-1] = -1 }; +static int full_duplex[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 }; +static int hw_checksums[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 }; +static int flow_ctrl[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 }; +static int enable_wol[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 }; +static int use_mmio[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 }; +static int global_options = -1; +static int global_full_duplex = -1; +static int global_enable_wol = -1; +static int global_use_mmio = -1; + +/* Variables to work-around the Compaq PCI BIOS32 problem. */ +static int compaq_ioaddr, compaq_irq, compaq_device_id = 0x5900; +static struct net_device *compaq_net_device; + +static int vortex_cards_found; + +module_param(debug, int, 0); +module_param(global_options, int, 0); +module_param_array(options, int, NULL, 0); +module_param(global_full_duplex, int, 0); +module_param_array(full_duplex, int, NULL, 0); +module_param_array(hw_checksums, int, NULL, 0); +module_param_array(flow_ctrl, int, NULL, 0); +module_param(global_enable_wol, int, 0); +module_param_array(enable_wol, int, NULL, 0); +module_param(rx_copybreak, int, 0); +module_param(max_interrupt_work, int, 0); +module_param(compaq_ioaddr, int, 0); +module_param(compaq_irq, int, 0); +module_param(compaq_device_id, int, 0); +module_param(watchdog, int, 0); +module_param(global_use_mmio, int, 0); +module_param_array(use_mmio, int, NULL, 0); +MODULE_PARM_DESC(debug, "3c59x debug level (0-6)"); +MODULE_PARM_DESC(options, "3c59x: Bits 0-3: media type, bit 4: bus mastering, bit 9: full duplex"); +MODULE_PARM_DESC(global_options, "3c59x: same as options, but applies to all NICs if options is unset"); +MODULE_PARM_DESC(full_duplex, "3c59x full duplex setting(s) (1)"); +MODULE_PARM_DESC(global_full_duplex, "3c59x: same as full_duplex, but applies to all NICs if full_duplex is unset"); +MODULE_PARM_DESC(hw_checksums, "3c59x Hardware checksum checking by adapter(s) (0-1)"); +MODULE_PARM_DESC(flow_ctrl, "3c59x 802.3x flow control usage (PAUSE only) (0-1)"); +MODULE_PARM_DESC(enable_wol, "3c59x: Turn on Wake-on-LAN for adapter(s) (0-1)"); +MODULE_PARM_DESC(global_enable_wol, "3c59x: same as enable_wol, but applies to all NICs if enable_wol is unset"); +MODULE_PARM_DESC(rx_copybreak, "3c59x copy breakpoint for copy-only-tiny-frames"); +MODULE_PARM_DESC(max_interrupt_work, "3c59x maximum events handled per interrupt"); +MODULE_PARM_DESC(compaq_ioaddr, "3c59x PCI I/O base address (Compaq BIOS problem workaround)"); +MODULE_PARM_DESC(compaq_irq, "3c59x PCI IRQ number (Compaq BIOS problem workaround)"); +MODULE_PARM_DESC(compaq_device_id, "3c59x PCI device ID (Compaq BIOS problem workaround)"); +MODULE_PARM_DESC(watchdog, "3c59x transmit timeout in milliseconds"); +MODULE_PARM_DESC(global_use_mmio, "3c59x: same as use_mmio, but applies to all NICs if options is unset"); +MODULE_PARM_DESC(use_mmio, "3c59x: use memory-mapped PCI I/O resource (0-1)"); + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void poll_vortex(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	unsigned long flags; +	local_irq_save(flags); +	(vp->full_bus_master_rx ? boomerang_interrupt:vortex_interrupt)(dev->irq,dev); +	local_irq_restore(flags); +} +#endif + +#ifdef CONFIG_PM + +static int vortex_suspend(struct device *dev) +{ +	struct pci_dev *pdev = to_pci_dev(dev); +	struct net_device *ndev = pci_get_drvdata(pdev); + +	if (!ndev || !netif_running(ndev)) +		return 0; + +	netif_device_detach(ndev); +	vortex_down(ndev, 1); + +	return 0; +} + +static int vortex_resume(struct device *dev) +{ +	struct pci_dev *pdev = to_pci_dev(dev); +	struct net_device *ndev = pci_get_drvdata(pdev); +	int err; + +	if (!ndev || !netif_running(ndev)) +		return 0; + +	err = vortex_up(ndev); +	if (err) +		return err; + +	netif_device_attach(ndev); + +	return 0; +} + +static const struct dev_pm_ops vortex_pm_ops = { +	.suspend = vortex_suspend, +	.resume = vortex_resume, +	.freeze = vortex_suspend, +	.thaw = vortex_resume, +	.poweroff = vortex_suspend, +	.restore = vortex_resume, +}; + +#define VORTEX_PM_OPS (&vortex_pm_ops) + +#else /* !CONFIG_PM */ + +#define VORTEX_PM_OPS NULL + +#endif /* !CONFIG_PM */ + +#ifdef CONFIG_EISA +static struct eisa_device_id vortex_eisa_ids[] = { +	{ "TCM5920", CH_3C592 }, +	{ "TCM5970", CH_3C597 }, +	{ "" } +}; +MODULE_DEVICE_TABLE(eisa, vortex_eisa_ids); + +static int __init vortex_eisa_probe(struct device *device) +{ +	void __iomem *ioaddr; +	struct eisa_device *edev; + +	edev = to_eisa_device(device); + +	if (!request_region(edev->base_addr, VORTEX_TOTAL_SIZE, DRV_NAME)) +		return -EBUSY; + +	ioaddr = ioport_map(edev->base_addr, VORTEX_TOTAL_SIZE); + +	if (vortex_probe1(device, ioaddr, ioread16(ioaddr + 0xC88) >> 12, +					  edev->id.driver_data, vortex_cards_found)) { +		release_region(edev->base_addr, VORTEX_TOTAL_SIZE); +		return -ENODEV; +	} + +	vortex_cards_found++; + +	return 0; +} + +static int vortex_eisa_remove(struct device *device) +{ +	struct eisa_device *edev; +	struct net_device *dev; +	struct vortex_private *vp; +	void __iomem *ioaddr; + +	edev = to_eisa_device(device); +	dev = eisa_get_drvdata(edev); + +	if (!dev) { +		pr_err("vortex_eisa_remove called for Compaq device!\n"); +		BUG(); +	} + +	vp = netdev_priv(dev); +	ioaddr = vp->ioaddr; + +	unregister_netdev(dev); +	iowrite16(TotalReset|0x14, ioaddr + EL3_CMD); +	release_region(edev->base_addr, VORTEX_TOTAL_SIZE); + +	free_netdev(dev); +	return 0; +} + +static struct eisa_driver vortex_eisa_driver = { +	.id_table = vortex_eisa_ids, +	.driver   = { +		.name    = "3c59x", +		.probe   = vortex_eisa_probe, +		.remove  = vortex_eisa_remove +	} +}; + +#endif /* CONFIG_EISA */ + +/* returns count found (>= 0), or negative on error */ +static int __init vortex_eisa_init(void) +{ +	int eisa_found = 0; +	int orig_cards_found = vortex_cards_found; + +#ifdef CONFIG_EISA +	int err; + +	err = eisa_driver_register (&vortex_eisa_driver); +	if (!err) { +		/* +		 * Because of the way EISA bus is probed, we cannot assume +		 * any device have been found when we exit from +		 * eisa_driver_register (the bus root driver may not be +		 * initialized yet). So we blindly assume something was +		 * found, and let the sysfs magic happened... +		 */ +		eisa_found = 1; +	} +#endif + +	/* Special code to work-around the Compaq PCI BIOS32 problem. */ +	if (compaq_ioaddr) { +		vortex_probe1(NULL, ioport_map(compaq_ioaddr, VORTEX_TOTAL_SIZE), +			      compaq_irq, compaq_device_id, vortex_cards_found++); +	} + +	return vortex_cards_found - orig_cards_found + eisa_found; +} + +/* returns count (>= 0), or negative on error */ +static int vortex_init_one(struct pci_dev *pdev, +			   const struct pci_device_id *ent) +{ +	int rc, unit, pci_bar; +	struct vortex_chip_info *vci; +	void __iomem *ioaddr; + +	/* wake up and enable device */ +	rc = pci_enable_device(pdev); +	if (rc < 0) +		goto out; + +	rc = pci_request_regions(pdev, DRV_NAME); +	if (rc < 0) +		goto out_disable; + +	unit = vortex_cards_found; + +	if (global_use_mmio < 0 && (unit >= MAX_UNITS || use_mmio[unit] < 0)) { +		/* Determine the default if the user didn't override us */ +		vci = &vortex_info_tbl[ent->driver_data]; +		pci_bar = vci->drv_flags & (IS_CYCLONE | IS_TORNADO) ? 1 : 0; +	} else if (unit < MAX_UNITS && use_mmio[unit] >= 0) +		pci_bar = use_mmio[unit] ? 1 : 0; +	else +		pci_bar = global_use_mmio ? 1 : 0; + +	ioaddr = pci_iomap(pdev, pci_bar, 0); +	if (!ioaddr) /* If mapping fails, fall-back to BAR 0... */ +		ioaddr = pci_iomap(pdev, 0, 0); +	if (!ioaddr) { +		rc = -ENOMEM; +		goto out_release; +	} + +	rc = vortex_probe1(&pdev->dev, ioaddr, pdev->irq, +			   ent->driver_data, unit); +	if (rc < 0) +		goto out_iounmap; + +	vortex_cards_found++; +	goto out; + +out_iounmap: +	pci_iounmap(pdev, ioaddr); +out_release: +	pci_release_regions(pdev); +out_disable: +	pci_disable_device(pdev); +out: +	return rc; +} + +static const struct net_device_ops boomrang_netdev_ops = { +	.ndo_open		= vortex_open, +	.ndo_stop		= vortex_close, +	.ndo_start_xmit		= boomerang_start_xmit, +	.ndo_tx_timeout		= vortex_tx_timeout, +	.ndo_get_stats		= vortex_get_stats, +#ifdef CONFIG_PCI +	.ndo_do_ioctl 		= vortex_ioctl, +#endif +	.ndo_set_rx_mode	= set_rx_mode, +	.ndo_change_mtu		= eth_change_mtu, +	.ndo_set_mac_address 	= eth_mac_addr, +	.ndo_validate_addr	= eth_validate_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER +	.ndo_poll_controller	= poll_vortex, +#endif +}; + +static const struct net_device_ops vortex_netdev_ops = { +	.ndo_open		= vortex_open, +	.ndo_stop		= vortex_close, +	.ndo_start_xmit		= vortex_start_xmit, +	.ndo_tx_timeout		= vortex_tx_timeout, +	.ndo_get_stats		= vortex_get_stats, +#ifdef CONFIG_PCI +	.ndo_do_ioctl 		= vortex_ioctl, +#endif +	.ndo_set_rx_mode	= set_rx_mode, +	.ndo_change_mtu		= eth_change_mtu, +	.ndo_set_mac_address 	= eth_mac_addr, +	.ndo_validate_addr	= eth_validate_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER +	.ndo_poll_controller	= poll_vortex, +#endif +}; + +/* + * Start up the PCI/EISA device which is described by *gendev. + * Return 0 on success. + * + * NOTE: pdev can be NULL, for the case of a Compaq device + */ +static int vortex_probe1(struct device *gendev, void __iomem *ioaddr, int irq, +			 int chip_idx, int card_idx) +{ +	struct vortex_private *vp; +	int option; +	unsigned int eeprom[0x40], checksum = 0;		/* EEPROM contents */ +	int i, step; +	struct net_device *dev; +	static int printed_version; +	int retval, print_info; +	struct vortex_chip_info * const vci = &vortex_info_tbl[chip_idx]; +	const char *print_name = "3c59x"; +	struct pci_dev *pdev = NULL; +	struct eisa_device *edev = NULL; + +	if (!printed_version) { +		pr_info("%s", version); +		printed_version = 1; +	} + +	if (gendev) { +		if ((pdev = DEVICE_PCI(gendev))) { +			print_name = pci_name(pdev); +		} + +		if ((edev = DEVICE_EISA(gendev))) { +			print_name = dev_name(&edev->dev); +		} +	} + +	dev = alloc_etherdev(sizeof(*vp)); +	retval = -ENOMEM; +	if (!dev) +		goto out; + +	SET_NETDEV_DEV(dev, gendev); +	vp = netdev_priv(dev); + +	option = global_options; + +	/* The lower four bits are the media type. */ +	if (dev->mem_start) { +		/* +		 * The 'options' param is passed in as the third arg to the +		 * LILO 'ether=' argument for non-modular use +		 */ +		option = dev->mem_start; +	} +	else if (card_idx < MAX_UNITS) { +		if (options[card_idx] >= 0) +			option = options[card_idx]; +	} + +	if (option > 0) { +		if (option & 0x8000) +			vortex_debug = 7; +		if (option & 0x4000) +			vortex_debug = 2; +		if (option & 0x0400) +			vp->enable_wol = 1; +	} + +	print_info = (vortex_debug > 1); +	if (print_info) +		pr_info("See Documentation/networking/vortex.txt\n"); + +	pr_info("%s: 3Com %s %s at %p.\n", +	       print_name, +	       pdev ? "PCI" : "EISA", +	       vci->name, +	       ioaddr); + +	dev->base_addr = (unsigned long)ioaddr; +	dev->irq = irq; +	dev->mtu = mtu; +	vp->ioaddr = ioaddr; +	vp->large_frames = mtu > 1500; +	vp->drv_flags = vci->drv_flags; +	vp->has_nway = (vci->drv_flags & HAS_NWAY) ? 1 : 0; +	vp->io_size = vci->io_size; +	vp->card_idx = card_idx; +	vp->window = -1; + +	/* module list only for Compaq device */ +	if (gendev == NULL) { +		compaq_net_device = dev; +	} + +	/* PCI-only startup logic */ +	if (pdev) { +		/* enable bus-mastering if necessary */ +		if (vci->flags & PCI_USES_MASTER) +			pci_set_master(pdev); + +		if (vci->drv_flags & IS_VORTEX) { +			u8 pci_latency; +			u8 new_latency = 248; + +			/* Check the PCI latency value.  On the 3c590 series the latency timer +			   must be set to the maximum value to avoid data corruption that occurs +			   when the timer expires during a transfer.  This bug exists the Vortex +			   chip only. */ +			pci_read_config_byte(pdev, PCI_LATENCY_TIMER, &pci_latency); +			if (pci_latency < new_latency) { +				pr_info("%s: Overriding PCI latency timer (CFLT) setting of %d, new value is %d.\n", +					print_name, pci_latency, new_latency); +				pci_write_config_byte(pdev, PCI_LATENCY_TIMER, new_latency); +			} +		} +	} + +	spin_lock_init(&vp->lock); +	spin_lock_init(&vp->mii_lock); +	spin_lock_init(&vp->window_lock); +	vp->gendev = gendev; +	vp->mii.dev = dev; +	vp->mii.mdio_read = mdio_read; +	vp->mii.mdio_write = mdio_write; +	vp->mii.phy_id_mask = 0x1f; +	vp->mii.reg_num_mask = 0x1f; + +	/* Makes sure rings are at least 16 byte aligned. */ +	vp->rx_ring = pci_alloc_consistent(pdev, sizeof(struct boom_rx_desc) * RX_RING_SIZE +					   + sizeof(struct boom_tx_desc) * TX_RING_SIZE, +					   &vp->rx_ring_dma); +	retval = -ENOMEM; +	if (!vp->rx_ring) +		goto free_device; + +	vp->tx_ring = (struct boom_tx_desc *)(vp->rx_ring + RX_RING_SIZE); +	vp->tx_ring_dma = vp->rx_ring_dma + sizeof(struct boom_rx_desc) * RX_RING_SIZE; + +	/* if we are a PCI driver, we store info in pdev->driver_data +	 * instead of a module list */ +	if (pdev) +		pci_set_drvdata(pdev, dev); +	if (edev) +		eisa_set_drvdata(edev, dev); + +	vp->media_override = 7; +	if (option >= 0) { +		vp->media_override = ((option & 7) == 2)  ?  0  :  option & 15; +		if (vp->media_override != 7) +			vp->medialock = 1; +		vp->full_duplex = (option & 0x200) ? 1 : 0; +		vp->bus_master = (option & 16) ? 1 : 0; +	} + +	if (global_full_duplex > 0) +		vp->full_duplex = 1; +	if (global_enable_wol > 0) +		vp->enable_wol = 1; + +	if (card_idx < MAX_UNITS) { +		if (full_duplex[card_idx] > 0) +			vp->full_duplex = 1; +		if (flow_ctrl[card_idx] > 0) +			vp->flow_ctrl = 1; +		if (enable_wol[card_idx] > 0) +			vp->enable_wol = 1; +	} + +	vp->mii.force_media = vp->full_duplex; +	vp->options = option; +	/* Read the station address from the EEPROM. */ +	{ +		int base; + +		if (vci->drv_flags & EEPROM_8BIT) +			base = 0x230; +		else if (vci->drv_flags & EEPROM_OFFSET) +			base = EEPROM_Read + 0x30; +		else +			base = EEPROM_Read; + +		for (i = 0; i < 0x40; i++) { +			int timer; +			window_write16(vp, base + i, 0, Wn0EepromCmd); +			/* Pause for at least 162 us. for the read to take place. */ +			for (timer = 10; timer >= 0; timer--) { +				udelay(162); +				if ((window_read16(vp, 0, Wn0EepromCmd) & +				     0x8000) == 0) +					break; +			} +			eeprom[i] = window_read16(vp, 0, Wn0EepromData); +		} +	} +	for (i = 0; i < 0x18; i++) +		checksum ^= eeprom[i]; +	checksum = (checksum ^ (checksum >> 8)) & 0xff; +	if (checksum != 0x00) {		/* Grrr, needless incompatible change 3Com. */ +		while (i < 0x21) +			checksum ^= eeprom[i++]; +		checksum = (checksum ^ (checksum >> 8)) & 0xff; +	} +	if ((checksum != 0x00) && !(vci->drv_flags & IS_TORNADO)) +		pr_cont(" ***INVALID CHECKSUM %4.4x*** ", checksum); +	for (i = 0; i < 3; i++) +		((__be16 *)dev->dev_addr)[i] = htons(eeprom[i + 10]); +	if (print_info) +		pr_cont(" %pM", dev->dev_addr); +	/* Unfortunately an all zero eeprom passes the checksum and this +	   gets found in the wild in failure cases. Crypto is hard 8) */ +	if (!is_valid_ether_addr(dev->dev_addr)) { +		retval = -EINVAL; +		pr_err("*** EEPROM MAC address is invalid.\n"); +		goto free_ring;	/* With every pack */ +	} +	for (i = 0; i < 6; i++) +		window_write8(vp, dev->dev_addr[i], 2, i); + +	if (print_info) +		pr_cont(", IRQ %d\n", dev->irq); +	/* Tell them about an invalid IRQ. */ +	if (dev->irq <= 0 || dev->irq >= nr_irqs) +		pr_warning(" *** Warning: IRQ %d is unlikely to work! ***\n", +			   dev->irq); + +	step = (window_read8(vp, 4, Wn4_NetDiag) & 0x1e) >> 1; +	if (print_info) { +		pr_info("  product code %02x%02x rev %02x.%d date %02d-%02d-%02d\n", +			eeprom[6]&0xff, eeprom[6]>>8, eeprom[0x14], +			step, (eeprom[4]>>5) & 15, eeprom[4] & 31, eeprom[4]>>9); +	} + + +	if (pdev && vci->drv_flags & HAS_CB_FNS) { +		unsigned short n; + +		vp->cb_fn_base = pci_iomap(pdev, 2, 0); +		if (!vp->cb_fn_base) { +			retval = -ENOMEM; +			goto free_ring; +		} + +		if (print_info) { +			pr_info("%s: CardBus functions mapped %16.16llx->%p\n", +				print_name, +				(unsigned long long)pci_resource_start(pdev, 2), +				vp->cb_fn_base); +		} + +		n = window_read16(vp, 2, Wn2_ResetOptions) & ~0x4010; +		if (vp->drv_flags & INVERT_LED_PWR) +			n |= 0x10; +		if (vp->drv_flags & INVERT_MII_PWR) +			n |= 0x4000; +		window_write16(vp, n, 2, Wn2_ResetOptions); +		if (vp->drv_flags & WNO_XCVR_PWR) { +			window_write16(vp, 0x0800, 0, 0); +		} +	} + +	/* Extract our information from the EEPROM data. */ +	vp->info1 = eeprom[13]; +	vp->info2 = eeprom[15]; +	vp->capabilities = eeprom[16]; + +	if (vp->info1 & 0x8000) { +		vp->full_duplex = 1; +		if (print_info) +			pr_info("Full duplex capable\n"); +	} + +	{ +		static const char * const ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; +		unsigned int config; +		vp->available_media = window_read16(vp, 3, Wn3_Options); +		if ((vp->available_media & 0xff) == 0)		/* Broken 3c916 */ +			vp->available_media = 0x40; +		config = window_read32(vp, 3, Wn3_Config); +		if (print_info) { +			pr_debug("  Internal config register is %4.4x, transceivers %#x.\n", +				config, window_read16(vp, 3, Wn3_Options)); +			pr_info("  %dK %s-wide RAM %s Rx:Tx split, %s%s interface.\n", +				   8 << RAM_SIZE(config), +				   RAM_WIDTH(config) ? "word" : "byte", +				   ram_split[RAM_SPLIT(config)], +				   AUTOSELECT(config) ? "autoselect/" : "", +				   XCVR(config) > XCVR_ExtMII ? "<invalid transceiver>" : +				   media_tbl[XCVR(config)].name); +		} +		vp->default_media = XCVR(config); +		if (vp->default_media == XCVR_NWAY) +			vp->has_nway = 1; +		vp->autoselect = AUTOSELECT(config); +	} + +	if (vp->media_override != 7) { +		pr_info("%s:  Media override to transceiver type %d (%s).\n", +				print_name, vp->media_override, +				media_tbl[vp->media_override].name); +		dev->if_port = vp->media_override; +	} else +		dev->if_port = vp->default_media; + +	if ((vp->available_media & 0x40) || (vci->drv_flags & HAS_NWAY) || +		dev->if_port == XCVR_MII || dev->if_port == XCVR_NWAY) { +		int phy, phy_idx = 0; +		mii_preamble_required++; +		if (vp->drv_flags & EXTRA_PREAMBLE) +			mii_preamble_required++; +		mdio_sync(vp, 32); +		mdio_read(dev, 24, MII_BMSR); +		for (phy = 0; phy < 32 && phy_idx < 1; phy++) { +			int mii_status, phyx; + +			/* +			 * For the 3c905CX we look at index 24 first, because it bogusly +			 * reports an external PHY at all indices +			 */ +			if (phy == 0) +				phyx = 24; +			else if (phy <= 24) +				phyx = phy - 1; +			else +				phyx = phy; +			mii_status = mdio_read(dev, phyx, MII_BMSR); +			if (mii_status  &&  mii_status != 0xffff) { +				vp->phys[phy_idx++] = phyx; +				if (print_info) { +					pr_info("  MII transceiver found at address %d, status %4x.\n", +						phyx, mii_status); +				} +				if ((mii_status & 0x0040) == 0) +					mii_preamble_required++; +			} +		} +		mii_preamble_required--; +		if (phy_idx == 0) { +			pr_warning("  ***WARNING*** No MII transceivers found!\n"); +			vp->phys[0] = 24; +		} else { +			vp->advertising = mdio_read(dev, vp->phys[0], MII_ADVERTISE); +			if (vp->full_duplex) { +				/* Only advertise the FD media types. */ +				vp->advertising &= ~0x02A0; +				mdio_write(dev, vp->phys[0], 4, vp->advertising); +			} +		} +		vp->mii.phy_id = vp->phys[0]; +	} + +	if (vp->capabilities & CapBusMaster) { +		vp->full_bus_master_tx = 1; +		if (print_info) { +			pr_info("  Enabling bus-master transmits and %s receives.\n", +			(vp->info2 & 1) ? "early" : "whole-frame" ); +		} +		vp->full_bus_master_rx = (vp->info2 & 1) ? 1 : 2; +		vp->bus_master = 0;		/* AKPM: vortex only */ +	} + +	/* The 3c59x-specific entries in the device structure. */ +	if (vp->full_bus_master_tx) { +		dev->netdev_ops = &boomrang_netdev_ops; +		/* Actually, it still should work with iommu. */ +		if (card_idx < MAX_UNITS && +		    ((hw_checksums[card_idx] == -1 && (vp->drv_flags & HAS_HWCKSM)) || +				hw_checksums[card_idx] == 1)) { +			dev->features |= NETIF_F_IP_CSUM | NETIF_F_SG; +		} +	} else +		dev->netdev_ops =  &vortex_netdev_ops; + +	if (print_info) { +		pr_info("%s: scatter/gather %sabled. h/w checksums %sabled\n", +				print_name, +				(dev->features & NETIF_F_SG) ? "en":"dis", +				(dev->features & NETIF_F_IP_CSUM) ? "en":"dis"); +	} + +	dev->ethtool_ops = &vortex_ethtool_ops; +	dev->watchdog_timeo = (watchdog * HZ) / 1000; + +	if (pdev) { +		vp->pm_state_valid = 1; +		pci_save_state(pdev); + 		acpi_set_WOL(dev); +	} +	retval = register_netdev(dev); +	if (retval == 0) +		return 0; + +free_ring: +	pci_free_consistent(pdev, +						sizeof(struct boom_rx_desc) * RX_RING_SIZE +							+ sizeof(struct boom_tx_desc) * TX_RING_SIZE, +						vp->rx_ring, +						vp->rx_ring_dma); +free_device: +	free_netdev(dev); +	pr_err(PFX "vortex_probe1 fails.  Returns %d\n", retval); +out: +	return retval; +} + +static void +issue_and_wait(struct net_device *dev, int cmd) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	int i; + +	iowrite16(cmd, ioaddr + EL3_CMD); +	for (i = 0; i < 2000; i++) { +		if (!(ioread16(ioaddr + EL3_STATUS) & CmdInProgress)) +			return; +	} + +	/* OK, that didn't work.  Do it the slow way.  One second */ +	for (i = 0; i < 100000; i++) { +		if (!(ioread16(ioaddr + EL3_STATUS) & CmdInProgress)) { +			if (vortex_debug > 1) +				pr_info("%s: command 0x%04x took %d usecs\n", +					   dev->name, cmd, i * 10); +			return; +		} +		udelay(10); +	} +	pr_err("%s: command 0x%04x did not complete! Status=0x%x\n", +			   dev->name, cmd, ioread16(ioaddr + EL3_STATUS)); +} + +static void +vortex_set_duplex(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); + +	pr_info("%s:  setting %s-duplex.\n", +		dev->name, (vp->full_duplex) ? "full" : "half"); + +	/* Set the full-duplex bit. */ +	window_write16(vp, +		       ((vp->info1 & 0x8000) || vp->full_duplex ? 0x20 : 0) | +		       (vp->large_frames ? 0x40 : 0) | +		       ((vp->full_duplex && vp->flow_ctrl && vp->partner_flow_ctrl) ? +			0x100 : 0), +		       3, Wn3_MAC_Ctrl); +} + +static void vortex_check_media(struct net_device *dev, unsigned int init) +{ +	struct vortex_private *vp = netdev_priv(dev); +	unsigned int ok_to_print = 0; + +	if (vortex_debug > 3) +		ok_to_print = 1; + +	if (mii_check_media(&vp->mii, ok_to_print, init)) { +		vp->full_duplex = vp->mii.full_duplex; +		vortex_set_duplex(dev); +	} else if (init) { +		vortex_set_duplex(dev); +	} +} + +static int +vortex_up(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	unsigned int config; +	int i, mii_reg1, mii_reg5, err = 0; + +	if (VORTEX_PCI(vp)) { +		pci_set_power_state(VORTEX_PCI(vp), PCI_D0);	/* Go active */ +		if (vp->pm_state_valid) +			pci_restore_state(VORTEX_PCI(vp)); +		err = pci_enable_device(VORTEX_PCI(vp)); +		if (err) { +			pr_warning("%s: Could not enable device\n", +				dev->name); +			goto err_out; +		} +	} + +	/* Before initializing select the active media port. */ +	config = window_read32(vp, 3, Wn3_Config); + +	if (vp->media_override != 7) { +		pr_info("%s: Media override to transceiver %d (%s).\n", +			   dev->name, vp->media_override, +			   media_tbl[vp->media_override].name); +		dev->if_port = vp->media_override; +	} else if (vp->autoselect) { +		if (vp->has_nway) { +			if (vortex_debug > 1) +				pr_info("%s: using NWAY device table, not %d\n", +								dev->name, dev->if_port); +			dev->if_port = XCVR_NWAY; +		} else { +			/* Find first available media type, starting with 100baseTx. */ +			dev->if_port = XCVR_100baseTx; +			while (! (vp->available_media & media_tbl[dev->if_port].mask)) +				dev->if_port = media_tbl[dev->if_port].next; +			if (vortex_debug > 1) +				pr_info("%s: first available media type: %s\n", +					dev->name, media_tbl[dev->if_port].name); +		} +	} else { +		dev->if_port = vp->default_media; +		if (vortex_debug > 1) +			pr_info("%s: using default media %s\n", +				dev->name, media_tbl[dev->if_port].name); +	} + +	init_timer(&vp->timer); +	vp->timer.expires = RUN_AT(media_tbl[dev->if_port].wait); +	vp->timer.data = (unsigned long)dev; +	vp->timer.function = vortex_timer;		/* timer handler */ +	add_timer(&vp->timer); + +	init_timer(&vp->rx_oom_timer); +	vp->rx_oom_timer.data = (unsigned long)dev; +	vp->rx_oom_timer.function = rx_oom_timer; + +	if (vortex_debug > 1) +		pr_debug("%s: Initial media type %s.\n", +			   dev->name, media_tbl[dev->if_port].name); + +	vp->full_duplex = vp->mii.force_media; +	config = BFINS(config, dev->if_port, 20, 4); +	if (vortex_debug > 6) +		pr_debug("vortex_up(): writing 0x%x to InternalConfig\n", config); +	window_write32(vp, config, 3, Wn3_Config); + +	if (dev->if_port == XCVR_MII || dev->if_port == XCVR_NWAY) { +		mii_reg1 = mdio_read(dev, vp->phys[0], MII_BMSR); +		mii_reg5 = mdio_read(dev, vp->phys[0], MII_LPA); +		vp->partner_flow_ctrl = ((mii_reg5 & 0x0400) != 0); +		vp->mii.full_duplex = vp->full_duplex; + +		vortex_check_media(dev, 1); +	} +	else +		vortex_set_duplex(dev); + +	issue_and_wait(dev, TxReset); +	/* +	 * Don't reset the PHY - that upsets autonegotiation during DHCP operations. +	 */ +	issue_and_wait(dev, RxReset|0x04); + + +	iowrite16(SetStatusEnb | 0x00, ioaddr + EL3_CMD); + +	if (vortex_debug > 1) { +		pr_debug("%s: vortex_up() irq %d media status %4.4x.\n", +			   dev->name, dev->irq, window_read16(vp, 4, Wn4_Media)); +	} + +	/* Set the station address and mask in window 2 each time opened. */ +	for (i = 0; i < 6; i++) +		window_write8(vp, dev->dev_addr[i], 2, i); +	for (; i < 12; i+=2) +		window_write16(vp, 0, 2, i); + +	if (vp->cb_fn_base) { +		unsigned short n = window_read16(vp, 2, Wn2_ResetOptions) & ~0x4010; +		if (vp->drv_flags & INVERT_LED_PWR) +			n |= 0x10; +		if (vp->drv_flags & INVERT_MII_PWR) +			n |= 0x4000; +		window_write16(vp, n, 2, Wn2_ResetOptions); +	} + +	if (dev->if_port == XCVR_10base2) +		/* Start the thinnet transceiver. We should really wait 50ms...*/ +		iowrite16(StartCoax, ioaddr + EL3_CMD); +	if (dev->if_port != XCVR_NWAY) { +		window_write16(vp, +			       (window_read16(vp, 4, Wn4_Media) & +				~(Media_10TP|Media_SQE)) | +			       media_tbl[dev->if_port].media_bits, +			       4, Wn4_Media); +	} + +	/* Switch to the stats window, and clear all stats by reading. */ +	iowrite16(StatsDisable, ioaddr + EL3_CMD); +	for (i = 0; i < 10; i++) +		window_read8(vp, 6, i); +	window_read16(vp, 6, 10); +	window_read16(vp, 6, 12); +	/* New: On the Vortex we must also clear the BadSSD counter. */ +	window_read8(vp, 4, 12); +	/* ..and on the Boomerang we enable the extra statistics bits. */ +	window_write16(vp, 0x0040, 4, Wn4_NetDiag); + +	if (vp->full_bus_master_rx) { /* Boomerang bus master. */ +		vp->cur_rx = vp->dirty_rx = 0; +		/* Initialize the RxEarly register as recommended. */ +		iowrite16(SetRxThreshold + (1536>>2), ioaddr + EL3_CMD); +		iowrite32(0x0020, ioaddr + PktStatus); +		iowrite32(vp->rx_ring_dma, ioaddr + UpListPtr); +	} +	if (vp->full_bus_master_tx) { 		/* Boomerang bus master Tx. */ +		vp->cur_tx = vp->dirty_tx = 0; +		if (vp->drv_flags & IS_BOOMERANG) +			iowrite8(PKT_BUF_SZ>>8, ioaddr + TxFreeThreshold); /* Room for a packet. */ +		/* Clear the Rx, Tx rings. */ +		for (i = 0; i < RX_RING_SIZE; i++)	/* AKPM: this is done in vortex_open, too */ +			vp->rx_ring[i].status = 0; +		for (i = 0; i < TX_RING_SIZE; i++) +			vp->tx_skbuff[i] = NULL; +		iowrite32(0, ioaddr + DownListPtr); +	} +	/* Set receiver mode: presumably accept b-case and phys addr only. */ +	set_rx_mode(dev); +	/* enable 802.1q tagged frames */ +	set_8021q_mode(dev, 1); +	iowrite16(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + +	iowrite16(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ +	iowrite16(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ +	/* Allow status bits to be seen. */ +	vp->status_enable = SetStatusEnb | HostError|IntReq|StatsFull|TxComplete| +		(vp->full_bus_master_tx ? DownComplete : TxAvailable) | +		(vp->full_bus_master_rx ? UpComplete : RxComplete) | +		(vp->bus_master ? DMADone : 0); +	vp->intr_enable = SetIntrEnb | IntLatch | TxAvailable | +		(vp->full_bus_master_rx ? 0 : RxComplete) | +		StatsFull | HostError | TxComplete | IntReq +		| (vp->bus_master ? DMADone : 0) | UpComplete | DownComplete; +	iowrite16(vp->status_enable, ioaddr + EL3_CMD); +	/* Ack all pending events, and set active indicator mask. */ +	iowrite16(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, +		 ioaddr + EL3_CMD); +	iowrite16(vp->intr_enable, ioaddr + EL3_CMD); +	if (vp->cb_fn_base)			/* The PCMCIA people are idiots.  */ +		iowrite32(0x8000, vp->cb_fn_base + 4); +	netif_start_queue (dev); +err_out: +	return err; +} + +static int +vortex_open(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	int i; +	int retval; + +	/* Use the now-standard shared IRQ implementation. */ +	if ((retval = request_irq(dev->irq, vp->full_bus_master_rx ? +				boomerang_interrupt : vortex_interrupt, IRQF_SHARED, dev->name, dev))) { +		pr_err("%s: Could not reserve IRQ %d\n", dev->name, dev->irq); +		goto err; +	} + +	if (vp->full_bus_master_rx) { /* Boomerang bus master. */ +		if (vortex_debug > 2) +			pr_debug("%s:  Filling in the Rx ring.\n", dev->name); +		for (i = 0; i < RX_RING_SIZE; i++) { +			struct sk_buff *skb; +			vp->rx_ring[i].next = cpu_to_le32(vp->rx_ring_dma + sizeof(struct boom_rx_desc) * (i+1)); +			vp->rx_ring[i].status = 0;	/* Clear complete bit. */ +			vp->rx_ring[i].length = cpu_to_le32(PKT_BUF_SZ | LAST_FRAG); + +			skb = __netdev_alloc_skb(dev, PKT_BUF_SZ + NET_IP_ALIGN, +						 GFP_KERNEL); +			vp->rx_skbuff[i] = skb; +			if (skb == NULL) +				break;			/* Bad news!  */ + +			skb_reserve(skb, NET_IP_ALIGN);	/* Align IP on 16 byte boundaries */ +			vp->rx_ring[i].addr = cpu_to_le32(pci_map_single(VORTEX_PCI(vp), skb->data, PKT_BUF_SZ, PCI_DMA_FROMDEVICE)); +		} +		if (i != RX_RING_SIZE) { +			int j; +			pr_emerg("%s: no memory for rx ring\n", dev->name); +			for (j = 0; j < i; j++) { +				if (vp->rx_skbuff[j]) { +					dev_kfree_skb(vp->rx_skbuff[j]); +					vp->rx_skbuff[j] = NULL; +				} +			} +			retval = -ENOMEM; +			goto err_free_irq; +		} +		/* Wrap the ring. */ +		vp->rx_ring[i-1].next = cpu_to_le32(vp->rx_ring_dma); +	} + +	retval = vortex_up(dev); +	if (!retval) +		goto out; + +err_free_irq: +	free_irq(dev->irq, dev); +err: +	if (vortex_debug > 1) +		pr_err("%s: vortex_open() fails: returning %d\n", dev->name, retval); +out: +	return retval; +} + +static void +vortex_timer(unsigned long data) +{ +	struct net_device *dev = (struct net_device *)data; +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	int next_tick = 60*HZ; +	int ok = 0; +	int media_status; + +	if (vortex_debug > 2) { +		pr_debug("%s: Media selection timer tick happened, %s.\n", +			   dev->name, media_tbl[dev->if_port].name); +		pr_debug("dev->watchdog_timeo=%d\n", dev->watchdog_timeo); +	} + +	media_status = window_read16(vp, 4, Wn4_Media); +	switch (dev->if_port) { +	case XCVR_10baseT:  case XCVR_100baseTx:  case XCVR_100baseFx: +		if (media_status & Media_LnkBeat) { +			netif_carrier_on(dev); +			ok = 1; +			if (vortex_debug > 1) +				pr_debug("%s: Media %s has link beat, %x.\n", +					   dev->name, media_tbl[dev->if_port].name, media_status); +		} else { +			netif_carrier_off(dev); +			if (vortex_debug > 1) { +				pr_debug("%s: Media %s has no link beat, %x.\n", +					   dev->name, media_tbl[dev->if_port].name, media_status); +			} +		} +		break; +	case XCVR_MII: case XCVR_NWAY: +		{ +			ok = 1; +			vortex_check_media(dev, 0); +		} +		break; +	  default:					/* Other media types handled by Tx timeouts. */ +		if (vortex_debug > 1) +		  pr_debug("%s: Media %s has no indication, %x.\n", +				 dev->name, media_tbl[dev->if_port].name, media_status); +		ok = 1; +	} + +	if (dev->flags & IFF_SLAVE || !netif_carrier_ok(dev)) +		next_tick = 5*HZ; + +	if (vp->medialock) +		goto leave_media_alone; + +	if (!ok) { +		unsigned int config; + +		spin_lock_irq(&vp->lock); + +		do { +			dev->if_port = media_tbl[dev->if_port].next; +		} while ( ! (vp->available_media & media_tbl[dev->if_port].mask)); +		if (dev->if_port == XCVR_Default) { /* Go back to default. */ +		  dev->if_port = vp->default_media; +		  if (vortex_debug > 1) +			pr_debug("%s: Media selection failing, using default %s port.\n", +				   dev->name, media_tbl[dev->if_port].name); +		} else { +			if (vortex_debug > 1) +				pr_debug("%s: Media selection failed, now trying %s port.\n", +					   dev->name, media_tbl[dev->if_port].name); +			next_tick = media_tbl[dev->if_port].wait; +		} +		window_write16(vp, +			       (media_status & ~(Media_10TP|Media_SQE)) | +			       media_tbl[dev->if_port].media_bits, +			       4, Wn4_Media); + +		config = window_read32(vp, 3, Wn3_Config); +		config = BFINS(config, dev->if_port, 20, 4); +		window_write32(vp, config, 3, Wn3_Config); + +		iowrite16(dev->if_port == XCVR_10base2 ? StartCoax : StopCoax, +			 ioaddr + EL3_CMD); +		if (vortex_debug > 1) +			pr_debug("wrote 0x%08x to Wn3_Config\n", config); +		/* AKPM: FIXME: Should reset Rx & Tx here.  P60 of 3c90xc.pdf */ + +		spin_unlock_irq(&vp->lock); +	} + +leave_media_alone: +	if (vortex_debug > 2) +	  pr_debug("%s: Media selection timer finished, %s.\n", +			 dev->name, media_tbl[dev->if_port].name); + +	mod_timer(&vp->timer, RUN_AT(next_tick)); +	if (vp->deferred) +		iowrite16(FakeIntr, ioaddr + EL3_CMD); +} + +static void vortex_tx_timeout(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; + +	pr_err("%s: transmit timed out, tx_status %2.2x status %4.4x.\n", +		   dev->name, ioread8(ioaddr + TxStatus), +		   ioread16(ioaddr + EL3_STATUS)); +	pr_err("  diagnostics: net %04x media %04x dma %08x fifo %04x\n", +			window_read16(vp, 4, Wn4_NetDiag), +			window_read16(vp, 4, Wn4_Media), +			ioread32(ioaddr + PktStatus), +			window_read16(vp, 4, Wn4_FIFODiag)); +	/* Slight code bloat to be user friendly. */ +	if ((ioread8(ioaddr + TxStatus) & 0x88) == 0x88) +		pr_err("%s: Transmitter encountered 16 collisions --" +			   " network cable problem?\n", dev->name); +	if (ioread16(ioaddr + EL3_STATUS) & IntLatch) { +		pr_err("%s: Interrupt posted but not delivered --" +			   " IRQ blocked by another device?\n", dev->name); +		/* Bad idea here.. but we might as well handle a few events. */ +		{ +			/* +			 * Block interrupts because vortex_interrupt does a bare spin_lock() +			 */ +			unsigned long flags; +			local_irq_save(flags); +			if (vp->full_bus_master_tx) +				boomerang_interrupt(dev->irq, dev); +			else +				vortex_interrupt(dev->irq, dev); +			local_irq_restore(flags); +		} +	} + +	if (vortex_debug > 0) +		dump_tx_ring(dev); + +	issue_and_wait(dev, TxReset); + +	dev->stats.tx_errors++; +	if (vp->full_bus_master_tx) { +		pr_debug("%s: Resetting the Tx ring pointer.\n", dev->name); +		if (vp->cur_tx - vp->dirty_tx > 0  &&  ioread32(ioaddr + DownListPtr) == 0) +			iowrite32(vp->tx_ring_dma + (vp->dirty_tx % TX_RING_SIZE) * sizeof(struct boom_tx_desc), +				 ioaddr + DownListPtr); +		if (vp->cur_tx - vp->dirty_tx < TX_RING_SIZE) +			netif_wake_queue (dev); +		if (vp->drv_flags & IS_BOOMERANG) +			iowrite8(PKT_BUF_SZ>>8, ioaddr + TxFreeThreshold); +		iowrite16(DownUnstall, ioaddr + EL3_CMD); +	} else { +		dev->stats.tx_dropped++; +		netif_wake_queue(dev); +	} + +	/* Issue Tx Enable */ +	iowrite16(TxEnable, ioaddr + EL3_CMD); +	dev->trans_start = jiffies; /* prevent tx timeout */ +} + +/* + * Handle uncommon interrupt sources.  This is a separate routine to minimize + * the cache impact. + */ +static void +vortex_error(struct net_device *dev, int status) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	int do_tx_reset = 0, reset_mask = 0; +	unsigned char tx_status = 0; + +	if (vortex_debug > 2) { +		pr_err("%s: vortex_error(), status=0x%x\n", dev->name, status); +	} + +	if (status & TxComplete) {			/* Really "TxError" for us. */ +		tx_status = ioread8(ioaddr + TxStatus); +		/* Presumably a tx-timeout. We must merely re-enable. */ +		if (vortex_debug > 2 || +		    (tx_status != 0x88 && vortex_debug > 0)) { +			pr_err("%s: Transmit error, Tx status register %2.2x.\n", +				   dev->name, tx_status); +			if (tx_status == 0x82) { +				pr_err("Probably a duplex mismatch.  See " +						"Documentation/networking/vortex.txt\n"); +			} +			dump_tx_ring(dev); +		} +		if (tx_status & 0x14)  dev->stats.tx_fifo_errors++; +		if (tx_status & 0x38)  dev->stats.tx_aborted_errors++; +		if (tx_status & 0x08)  vp->xstats.tx_max_collisions++; +		iowrite8(0, ioaddr + TxStatus); +		if (tx_status & 0x30) {			/* txJabber or txUnderrun */ +			do_tx_reset = 1; +		} else if ((tx_status & 0x08) && (vp->drv_flags & MAX_COLLISION_RESET))  {	/* maxCollisions */ +			do_tx_reset = 1; +			reset_mask = 0x0108;		/* Reset interface logic, but not download logic */ +		} else {				/* Merely re-enable the transmitter. */ +			iowrite16(TxEnable, ioaddr + EL3_CMD); +		} +	} + +	if (status & RxEarly)				/* Rx early is unused. */ +		iowrite16(AckIntr | RxEarly, ioaddr + EL3_CMD); + +	if (status & StatsFull) {			/* Empty statistics. */ +		static int DoneDidThat; +		if (vortex_debug > 4) +			pr_debug("%s: Updating stats.\n", dev->name); +		update_stats(ioaddr, dev); +		/* HACK: Disable statistics as an interrupt source. */ +		/* This occurs when we have the wrong media type! */ +		if (DoneDidThat == 0  && +			ioread16(ioaddr + EL3_STATUS) & StatsFull) { +			pr_warning("%s: Updating statistics failed, disabling " +				   "stats as an interrupt source.\n", dev->name); +			iowrite16(SetIntrEnb | +				  (window_read16(vp, 5, 10) & ~StatsFull), +				  ioaddr + EL3_CMD); +			vp->intr_enable &= ~StatsFull; +			DoneDidThat++; +		} +	} +	if (status & IntReq) {		/* Restore all interrupt sources.  */ +		iowrite16(vp->status_enable, ioaddr + EL3_CMD); +		iowrite16(vp->intr_enable, ioaddr + EL3_CMD); +	} +	if (status & HostError) { +		u16 fifo_diag; +		fifo_diag = window_read16(vp, 4, Wn4_FIFODiag); +		pr_err("%s: Host error, FIFO diagnostic register %4.4x.\n", +			   dev->name, fifo_diag); +		/* Adapter failure requires Tx/Rx reset and reinit. */ +		if (vp->full_bus_master_tx) { +			int bus_status = ioread32(ioaddr + PktStatus); +			/* 0x80000000 PCI master abort. */ +			/* 0x40000000 PCI target abort. */ +			if (vortex_debug) +				pr_err("%s: PCI bus error, bus status %8.8x\n", dev->name, bus_status); + +			/* In this case, blow the card away */ +			/* Must not enter D3 or we can't legally issue the reset! */ +			vortex_down(dev, 0); +			issue_and_wait(dev, TotalReset | 0xff); +			vortex_up(dev);		/* AKPM: bug.  vortex_up() assumes that the rx ring is full. It may not be. */ +		} else if (fifo_diag & 0x0400) +			do_tx_reset = 1; +		if (fifo_diag & 0x3000) { +			/* Reset Rx fifo and upload logic */ +			issue_and_wait(dev, RxReset|0x07); +			/* Set the Rx filter to the current state. */ +			set_rx_mode(dev); +			/* enable 802.1q VLAN tagged frames */ +			set_8021q_mode(dev, 1); +			iowrite16(RxEnable, ioaddr + EL3_CMD); /* Re-enable the receiver. */ +			iowrite16(AckIntr | HostError, ioaddr + EL3_CMD); +		} +	} + +	if (do_tx_reset) { +		issue_and_wait(dev, TxReset|reset_mask); +		iowrite16(TxEnable, ioaddr + EL3_CMD); +		if (!vp->full_bus_master_tx) +			netif_wake_queue(dev); +	} +} + +static netdev_tx_t +vortex_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; + +	/* Put out the doubleword header... */ +	iowrite32(skb->len, ioaddr + TX_FIFO); +	if (vp->bus_master) { +		/* Set the bus-master controller to transfer the packet. */ +		int len = (skb->len + 3) & ~3; +		vp->tx_skb_dma = pci_map_single(VORTEX_PCI(vp), skb->data, len, +						PCI_DMA_TODEVICE); +		spin_lock_irq(&vp->window_lock); +		window_set(vp, 7); +		iowrite32(vp->tx_skb_dma, ioaddr + Wn7_MasterAddr); +		iowrite16(len, ioaddr + Wn7_MasterLen); +		spin_unlock_irq(&vp->window_lock); +		vp->tx_skb = skb; +		skb_tx_timestamp(skb); +		iowrite16(StartDMADown, ioaddr + EL3_CMD); +		/* netif_wake_queue() will be called at the DMADone interrupt. */ +	} else { +		/* ... and the packet rounded to a doubleword. */ +		skb_tx_timestamp(skb); +		iowrite32_rep(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); +		dev_consume_skb_any (skb); +		if (ioread16(ioaddr + TxFree) > 1536) { +			netif_start_queue (dev);	/* AKPM: redundant? */ +		} else { +			/* Interrupt us when the FIFO has room for max-sized packet. */ +			netif_stop_queue(dev); +			iowrite16(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); +		} +	} + + +	/* Clear the Tx status stack. */ +	{ +		int tx_status; +		int i = 32; + +		while (--i > 0	&&	(tx_status = ioread8(ioaddr + TxStatus)) > 0) { +			if (tx_status & 0x3C) {		/* A Tx-disabling error occurred.  */ +				if (vortex_debug > 2) +				  pr_debug("%s: Tx error, status %2.2x.\n", +						 dev->name, tx_status); +				if (tx_status & 0x04) dev->stats.tx_fifo_errors++; +				if (tx_status & 0x38) dev->stats.tx_aborted_errors++; +				if (tx_status & 0x30) { +					issue_and_wait(dev, TxReset); +				} +				iowrite16(TxEnable, ioaddr + EL3_CMD); +			} +			iowrite8(0x00, ioaddr + TxStatus); /* Pop the status stack. */ +		} +	} +	return NETDEV_TX_OK; +} + +static netdev_tx_t +boomerang_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	/* Calculate the next Tx descriptor entry. */ +	int entry = vp->cur_tx % TX_RING_SIZE; +	struct boom_tx_desc *prev_entry = &vp->tx_ring[(vp->cur_tx-1) % TX_RING_SIZE]; +	unsigned long flags; + +	if (vortex_debug > 6) { +		pr_debug("boomerang_start_xmit()\n"); +		pr_debug("%s: Trying to send a packet, Tx index %d.\n", +			   dev->name, vp->cur_tx); +	} + +	/* +	 * We can't allow a recursion from our interrupt handler back into the +	 * tx routine, as they take the same spin lock, and that causes +	 * deadlock.  Just return NETDEV_TX_BUSY and let the stack try again in +	 * a bit +	 */ +	if (vp->handling_irq) +		return NETDEV_TX_BUSY; + +	if (vp->cur_tx - vp->dirty_tx >= TX_RING_SIZE) { +		if (vortex_debug > 0) +			pr_warning("%s: BUG! Tx Ring full, refusing to send buffer.\n", +				   dev->name); +		netif_stop_queue(dev); +		return NETDEV_TX_BUSY; +	} + +	vp->tx_skbuff[entry] = skb; + +	vp->tx_ring[entry].next = 0; +#if DO_ZEROCOPY +	if (skb->ip_summed != CHECKSUM_PARTIAL) +			vp->tx_ring[entry].status = cpu_to_le32(skb->len | TxIntrUploaded); +	else +			vp->tx_ring[entry].status = cpu_to_le32(skb->len | TxIntrUploaded | AddTCPChksum | AddUDPChksum); + +	if (!skb_shinfo(skb)->nr_frags) { +		vp->tx_ring[entry].frag[0].addr = cpu_to_le32(pci_map_single(VORTEX_PCI(vp), skb->data, +										skb->len, PCI_DMA_TODEVICE)); +		vp->tx_ring[entry].frag[0].length = cpu_to_le32(skb->len | LAST_FRAG); +	} else { +		int i; + +		vp->tx_ring[entry].frag[0].addr = cpu_to_le32(pci_map_single(VORTEX_PCI(vp), skb->data, +										skb_headlen(skb), PCI_DMA_TODEVICE)); +		vp->tx_ring[entry].frag[0].length = cpu_to_le32(skb_headlen(skb)); + +		for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { +			skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + +			vp->tx_ring[entry].frag[i+1].addr = +					cpu_to_le32(pci_map_single( +						VORTEX_PCI(vp), +						(void *)skb_frag_address(frag), +						skb_frag_size(frag), PCI_DMA_TODEVICE)); + +			if (i == skb_shinfo(skb)->nr_frags-1) +					vp->tx_ring[entry].frag[i+1].length = cpu_to_le32(skb_frag_size(frag)|LAST_FRAG); +			else +					vp->tx_ring[entry].frag[i+1].length = cpu_to_le32(skb_frag_size(frag)); +		} +	} +#else +	vp->tx_ring[entry].addr = cpu_to_le32(pci_map_single(VORTEX_PCI(vp), skb->data, skb->len, PCI_DMA_TODEVICE)); +	vp->tx_ring[entry].length = cpu_to_le32(skb->len | LAST_FRAG); +	vp->tx_ring[entry].status = cpu_to_le32(skb->len | TxIntrUploaded); +#endif + +	spin_lock_irqsave(&vp->lock, flags); +	/* Wait for the stall to complete. */ +	issue_and_wait(dev, DownStall); +	prev_entry->next = cpu_to_le32(vp->tx_ring_dma + entry * sizeof(struct boom_tx_desc)); +	if (ioread32(ioaddr + DownListPtr) == 0) { +		iowrite32(vp->tx_ring_dma + entry * sizeof(struct boom_tx_desc), ioaddr + DownListPtr); +		vp->queued_packet++; +	} + +	vp->cur_tx++; +	if (vp->cur_tx - vp->dirty_tx > TX_RING_SIZE - 1) { +		netif_stop_queue (dev); +	} else {					/* Clear previous interrupt enable. */ +#if defined(tx_interrupt_mitigation) +		/* Dubious. If in boomeang_interrupt "faster" cyclone ifdef +		 * were selected, this would corrupt DN_COMPLETE. No? +		 */ +		prev_entry->status &= cpu_to_le32(~TxIntrUploaded); +#endif +	} +	skb_tx_timestamp(skb); +	iowrite16(DownUnstall, ioaddr + EL3_CMD); +	spin_unlock_irqrestore(&vp->lock, flags); +	return NETDEV_TX_OK; +} + +/* The interrupt handler does all of the Rx thread work and cleans up +   after the Tx thread. */ + +/* + * This is the ISR for the vortex series chips. + * full_bus_master_tx == 0 && full_bus_master_rx == 0 + */ + +static irqreturn_t +vortex_interrupt(int irq, void *dev_id) +{ +	struct net_device *dev = dev_id; +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr; +	int status; +	int work_done = max_interrupt_work; +	int handled = 0; + +	ioaddr = vp->ioaddr; +	spin_lock(&vp->lock); + +	status = ioread16(ioaddr + EL3_STATUS); + +	if (vortex_debug > 6) +		pr_debug("vortex_interrupt(). status=0x%4x\n", status); + +	if ((status & IntLatch) == 0) +		goto handler_exit;		/* No interrupt: shared IRQs cause this */ +	handled = 1; + +	if (status & IntReq) { +		status |= vp->deferred; +		vp->deferred = 0; +	} + +	if (status == 0xffff)		/* h/w no longer present (hotplug)? */ +		goto handler_exit; + +	if (vortex_debug > 4) +		pr_debug("%s: interrupt, status %4.4x, latency %d ticks.\n", +			   dev->name, status, ioread8(ioaddr + Timer)); + +	spin_lock(&vp->window_lock); +	window_set(vp, 7); + +	do { +		if (vortex_debug > 5) +				pr_debug("%s: In interrupt loop, status %4.4x.\n", +					   dev->name, status); +		if (status & RxComplete) +			vortex_rx(dev); + +		if (status & TxAvailable) { +			if (vortex_debug > 5) +				pr_debug("	TX room bit was handled.\n"); +			/* There's room in the FIFO for a full-sized packet. */ +			iowrite16(AckIntr | TxAvailable, ioaddr + EL3_CMD); +			netif_wake_queue (dev); +		} + +		if (status & DMADone) { +			if (ioread16(ioaddr + Wn7_MasterStatus) & 0x1000) { +				iowrite16(0x1000, ioaddr + Wn7_MasterStatus); /* Ack the event. */ +				pci_unmap_single(VORTEX_PCI(vp), vp->tx_skb_dma, (vp->tx_skb->len + 3) & ~3, PCI_DMA_TODEVICE); +				dev_kfree_skb_irq(vp->tx_skb); /* Release the transferred buffer */ +				if (ioread16(ioaddr + TxFree) > 1536) { +					/* +					 * AKPM: FIXME: I don't think we need this.  If the queue was stopped due to +					 * insufficient FIFO room, the TxAvailable test will succeed and call +					 * netif_wake_queue() +					 */ +					netif_wake_queue(dev); +				} else { /* Interrupt when FIFO has room for max-sized packet. */ +					iowrite16(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); +					netif_stop_queue(dev); +				} +			} +		} +		/* Check for all uncommon interrupts at once. */ +		if (status & (HostError | RxEarly | StatsFull | TxComplete | IntReq)) { +			if (status == 0xffff) +				break; +			if (status & RxEarly) +				vortex_rx(dev); +			spin_unlock(&vp->window_lock); +			vortex_error(dev, status); +			spin_lock(&vp->window_lock); +			window_set(vp, 7); +		} + +		if (--work_done < 0) { +			pr_warning("%s: Too much work in interrupt, status %4.4x.\n", +				dev->name, status); +			/* Disable all pending interrupts. */ +			do { +				vp->deferred |= status; +				iowrite16(SetStatusEnb | (~vp->deferred & vp->status_enable), +					 ioaddr + EL3_CMD); +				iowrite16(AckIntr | (vp->deferred & 0x7ff), ioaddr + EL3_CMD); +			} while ((status = ioread16(ioaddr + EL3_CMD)) & IntLatch); +			/* The timer will reenable interrupts. */ +			mod_timer(&vp->timer, jiffies + 1*HZ); +			break; +		} +		/* Acknowledge the IRQ. */ +		iowrite16(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); +	} while ((status = ioread16(ioaddr + EL3_STATUS)) & (IntLatch | RxComplete)); + +	spin_unlock(&vp->window_lock); + +	if (vortex_debug > 4) +		pr_debug("%s: exiting interrupt, status %4.4x.\n", +			   dev->name, status); +handler_exit: +	spin_unlock(&vp->lock); +	return IRQ_RETVAL(handled); +} + +/* + * This is the ISR for the boomerang series chips. + * full_bus_master_tx == 1 && full_bus_master_rx == 1 + */ + +static irqreturn_t +boomerang_interrupt(int irq, void *dev_id) +{ +	struct net_device *dev = dev_id; +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr; +	int status; +	int work_done = max_interrupt_work; + +	ioaddr = vp->ioaddr; + + +	/* +	 * It seems dopey to put the spinlock this early, but we could race against vortex_tx_timeout +	 * and boomerang_start_xmit +	 */ +	spin_lock(&vp->lock); +	vp->handling_irq = 1; + +	status = ioread16(ioaddr + EL3_STATUS); + +	if (vortex_debug > 6) +		pr_debug("boomerang_interrupt. status=0x%4x\n", status); + +	if ((status & IntLatch) == 0) +		goto handler_exit;		/* No interrupt: shared IRQs can cause this */ + +	if (status == 0xffff) {		/* h/w no longer present (hotplug)? */ +		if (vortex_debug > 1) +			pr_debug("boomerang_interrupt(1): status = 0xffff\n"); +		goto handler_exit; +	} + +	if (status & IntReq) { +		status |= vp->deferred; +		vp->deferred = 0; +	} + +	if (vortex_debug > 4) +		pr_debug("%s: interrupt, status %4.4x, latency %d ticks.\n", +			   dev->name, status, ioread8(ioaddr + Timer)); +	do { +		if (vortex_debug > 5) +				pr_debug("%s: In interrupt loop, status %4.4x.\n", +					   dev->name, status); +		if (status & UpComplete) { +			iowrite16(AckIntr | UpComplete, ioaddr + EL3_CMD); +			if (vortex_debug > 5) +				pr_debug("boomerang_interrupt->boomerang_rx\n"); +			boomerang_rx(dev); +		} + +		if (status & DownComplete) { +			unsigned int dirty_tx = vp->dirty_tx; + +			iowrite16(AckIntr | DownComplete, ioaddr + EL3_CMD); +			while (vp->cur_tx - dirty_tx > 0) { +				int entry = dirty_tx % TX_RING_SIZE; +#if 1	/* AKPM: the latter is faster, but cyclone-only */ +				if (ioread32(ioaddr + DownListPtr) == +					vp->tx_ring_dma + entry * sizeof(struct boom_tx_desc)) +					break;			/* It still hasn't been processed. */ +#else +				if ((vp->tx_ring[entry].status & DN_COMPLETE) == 0) +					break;			/* It still hasn't been processed. */ +#endif + +				if (vp->tx_skbuff[entry]) { +					struct sk_buff *skb = vp->tx_skbuff[entry]; +#if DO_ZEROCOPY +					int i; +					for (i=0; i<=skb_shinfo(skb)->nr_frags; i++) +							pci_unmap_single(VORTEX_PCI(vp), +											 le32_to_cpu(vp->tx_ring[entry].frag[i].addr), +											 le32_to_cpu(vp->tx_ring[entry].frag[i].length)&0xFFF, +											 PCI_DMA_TODEVICE); +#else +					pci_unmap_single(VORTEX_PCI(vp), +						le32_to_cpu(vp->tx_ring[entry].addr), skb->len, PCI_DMA_TODEVICE); +#endif +					dev_kfree_skb_irq(skb); +					vp->tx_skbuff[entry] = NULL; +				} else { +					pr_debug("boomerang_interrupt: no skb!\n"); +				} +				/* dev->stats.tx_packets++;  Counted below. */ +				dirty_tx++; +			} +			vp->dirty_tx = dirty_tx; +			if (vp->cur_tx - dirty_tx <= TX_RING_SIZE - 1) { +				if (vortex_debug > 6) +					pr_debug("boomerang_interrupt: wake queue\n"); +				netif_wake_queue (dev); +			} +		} + +		/* Check for all uncommon interrupts at once. */ +		if (status & (HostError | RxEarly | StatsFull | TxComplete | IntReq)) +			vortex_error(dev, status); + +		if (--work_done < 0) { +			pr_warning("%s: Too much work in interrupt, status %4.4x.\n", +				dev->name, status); +			/* Disable all pending interrupts. */ +			do { +				vp->deferred |= status; +				iowrite16(SetStatusEnb | (~vp->deferred & vp->status_enable), +					 ioaddr + EL3_CMD); +				iowrite16(AckIntr | (vp->deferred & 0x7ff), ioaddr + EL3_CMD); +			} while ((status = ioread16(ioaddr + EL3_CMD)) & IntLatch); +			/* The timer will reenable interrupts. */ +			mod_timer(&vp->timer, jiffies + 1*HZ); +			break; +		} +		/* Acknowledge the IRQ. */ +		iowrite16(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); +		if (vp->cb_fn_base)			/* The PCMCIA people are idiots.  */ +			iowrite32(0x8000, vp->cb_fn_base + 4); + +	} while ((status = ioread16(ioaddr + EL3_STATUS)) & IntLatch); + +	if (vortex_debug > 4) +		pr_debug("%s: exiting interrupt, status %4.4x.\n", +			   dev->name, status); +handler_exit: +	vp->handling_irq = 0; +	spin_unlock(&vp->lock); +	return IRQ_HANDLED; +} + +static int vortex_rx(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	int i; +	short rx_status; + +	if (vortex_debug > 5) +		pr_debug("vortex_rx(): status %4.4x, rx_status %4.4x.\n", +			   ioread16(ioaddr+EL3_STATUS), ioread16(ioaddr+RxStatus)); +	while ((rx_status = ioread16(ioaddr + RxStatus)) > 0) { +		if (rx_status & 0x4000) { /* Error, update stats. */ +			unsigned char rx_error = ioread8(ioaddr + RxErrors); +			if (vortex_debug > 2) +				pr_debug(" Rx error: status %2.2x.\n", rx_error); +			dev->stats.rx_errors++; +			if (rx_error & 0x01)  dev->stats.rx_over_errors++; +			if (rx_error & 0x02)  dev->stats.rx_length_errors++; +			if (rx_error & 0x04)  dev->stats.rx_frame_errors++; +			if (rx_error & 0x08)  dev->stats.rx_crc_errors++; +			if (rx_error & 0x10)  dev->stats.rx_length_errors++; +		} else { +			/* The packet length: up to 4.5K!. */ +			int pkt_len = rx_status & 0x1fff; +			struct sk_buff *skb; + +			skb = netdev_alloc_skb(dev, pkt_len + 5); +			if (vortex_debug > 4) +				pr_debug("Receiving packet size %d status %4.4x.\n", +					   pkt_len, rx_status); +			if (skb != NULL) { +				skb_reserve(skb, 2);	/* Align IP on 16 byte boundaries */ +				/* 'skb_put()' points to the start of sk_buff data area. */ +				if (vp->bus_master && +					! (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)) { +					dma_addr_t dma = pci_map_single(VORTEX_PCI(vp), skb_put(skb, pkt_len), +									   pkt_len, PCI_DMA_FROMDEVICE); +					iowrite32(dma, ioaddr + Wn7_MasterAddr); +					iowrite16((skb->len + 3) & ~3, ioaddr + Wn7_MasterLen); +					iowrite16(StartDMAUp, ioaddr + EL3_CMD); +					while (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000) +						; +					pci_unmap_single(VORTEX_PCI(vp), dma, pkt_len, PCI_DMA_FROMDEVICE); +				} else { +					ioread32_rep(ioaddr + RX_FIFO, +					             skb_put(skb, pkt_len), +						     (pkt_len + 3) >> 2); +				} +				iowrite16(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */ +				skb->protocol = eth_type_trans(skb, dev); +				netif_rx(skb); +				dev->stats.rx_packets++; +				/* Wait a limited time to go to next packet. */ +				for (i = 200; i >= 0; i--) +					if ( ! (ioread16(ioaddr + EL3_STATUS) & CmdInProgress)) +						break; +				continue; +			} else if (vortex_debug > 0) +				pr_notice("%s: No memory to allocate a sk_buff of size %d.\n", +					dev->name, pkt_len); +			dev->stats.rx_dropped++; +		} +		issue_and_wait(dev, RxDiscard); +	} + +	return 0; +} + +static int +boomerang_rx(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	int entry = vp->cur_rx % RX_RING_SIZE; +	void __iomem *ioaddr = vp->ioaddr; +	int rx_status; +	int rx_work_limit = vp->dirty_rx + RX_RING_SIZE - vp->cur_rx; + +	if (vortex_debug > 5) +		pr_debug("boomerang_rx(): status %4.4x\n", ioread16(ioaddr+EL3_STATUS)); + +	while ((rx_status = le32_to_cpu(vp->rx_ring[entry].status)) & RxDComplete){ +		if (--rx_work_limit < 0) +			break; +		if (rx_status & RxDError) { /* Error, update stats. */ +			unsigned char rx_error = rx_status >> 16; +			if (vortex_debug > 2) +				pr_debug(" Rx error: status %2.2x.\n", rx_error); +			dev->stats.rx_errors++; +			if (rx_error & 0x01)  dev->stats.rx_over_errors++; +			if (rx_error & 0x02)  dev->stats.rx_length_errors++; +			if (rx_error & 0x04)  dev->stats.rx_frame_errors++; +			if (rx_error & 0x08)  dev->stats.rx_crc_errors++; +			if (rx_error & 0x10)  dev->stats.rx_length_errors++; +		} else { +			/* The packet length: up to 4.5K!. */ +			int pkt_len = rx_status & 0x1fff; +			struct sk_buff *skb; +			dma_addr_t dma = le32_to_cpu(vp->rx_ring[entry].addr); + +			if (vortex_debug > 4) +				pr_debug("Receiving packet size %d status %4.4x.\n", +					   pkt_len, rx_status); + +			/* Check if the packet is long enough to just accept without +			   copying to a properly sized skbuff. */ +			if (pkt_len < rx_copybreak && +			    (skb = netdev_alloc_skb(dev, pkt_len + 2)) != NULL) { +				skb_reserve(skb, 2);	/* Align IP on 16 byte boundaries */ +				pci_dma_sync_single_for_cpu(VORTEX_PCI(vp), dma, PKT_BUF_SZ, PCI_DMA_FROMDEVICE); +				/* 'skb_put()' points to the start of sk_buff data area. */ +				memcpy(skb_put(skb, pkt_len), +					   vp->rx_skbuff[entry]->data, +					   pkt_len); +				pci_dma_sync_single_for_device(VORTEX_PCI(vp), dma, PKT_BUF_SZ, PCI_DMA_FROMDEVICE); +				vp->rx_copy++; +			} else { +				/* Pass up the skbuff already on the Rx ring. */ +				skb = vp->rx_skbuff[entry]; +				vp->rx_skbuff[entry] = NULL; +				skb_put(skb, pkt_len); +				pci_unmap_single(VORTEX_PCI(vp), dma, PKT_BUF_SZ, PCI_DMA_FROMDEVICE); +				vp->rx_nocopy++; +			} +			skb->protocol = eth_type_trans(skb, dev); +			{					/* Use hardware checksum info. */ +				int csum_bits = rx_status & 0xee000000; +				if (csum_bits && +					(csum_bits == (IPChksumValid | TCPChksumValid) || +					 csum_bits == (IPChksumValid | UDPChksumValid))) { +					skb->ip_summed = CHECKSUM_UNNECESSARY; +					vp->rx_csumhits++; +				} +			} +			netif_rx(skb); +			dev->stats.rx_packets++; +		} +		entry = (++vp->cur_rx) % RX_RING_SIZE; +	} +	/* Refill the Rx ring buffers. */ +	for (; vp->cur_rx - vp->dirty_rx > 0; vp->dirty_rx++) { +		struct sk_buff *skb; +		entry = vp->dirty_rx % RX_RING_SIZE; +		if (vp->rx_skbuff[entry] == NULL) { +			skb = netdev_alloc_skb_ip_align(dev, PKT_BUF_SZ); +			if (skb == NULL) { +				static unsigned long last_jif; +				if (time_after(jiffies, last_jif + 10 * HZ)) { +					pr_warning("%s: memory shortage\n", dev->name); +					last_jif = jiffies; +				} +				if ((vp->cur_rx - vp->dirty_rx) == RX_RING_SIZE) +					mod_timer(&vp->rx_oom_timer, RUN_AT(HZ * 1)); +				break;			/* Bad news!  */ +			} + +			vp->rx_ring[entry].addr = cpu_to_le32(pci_map_single(VORTEX_PCI(vp), skb->data, PKT_BUF_SZ, PCI_DMA_FROMDEVICE)); +			vp->rx_skbuff[entry] = skb; +		} +		vp->rx_ring[entry].status = 0;	/* Clear complete bit. */ +		iowrite16(UpUnstall, ioaddr + EL3_CMD); +	} +	return 0; +} + +/* + * If we've hit a total OOM refilling the Rx ring we poll once a second + * for some memory.  Otherwise there is no way to restart the rx process. + */ +static void +rx_oom_timer(unsigned long arg) +{ +	struct net_device *dev = (struct net_device *)arg; +	struct vortex_private *vp = netdev_priv(dev); + +	spin_lock_irq(&vp->lock); +	if ((vp->cur_rx - vp->dirty_rx) == RX_RING_SIZE)	/* This test is redundant, but makes me feel good */ +		boomerang_rx(dev); +	if (vortex_debug > 1) { +		pr_debug("%s: rx_oom_timer %s\n", dev->name, +			((vp->cur_rx - vp->dirty_rx) != RX_RING_SIZE) ? "succeeded" : "retrying"); +	} +	spin_unlock_irq(&vp->lock); +} + +static void +vortex_down(struct net_device *dev, int final_down) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; + +	netif_stop_queue (dev); + +	del_timer_sync(&vp->rx_oom_timer); +	del_timer_sync(&vp->timer); + +	/* Turn off statistics ASAP.  We update dev->stats below. */ +	iowrite16(StatsDisable, ioaddr + EL3_CMD); + +	/* Disable the receiver and transmitter. */ +	iowrite16(RxDisable, ioaddr + EL3_CMD); +	iowrite16(TxDisable, ioaddr + EL3_CMD); + +	/* Disable receiving 802.1q tagged frames */ +	set_8021q_mode(dev, 0); + +	if (dev->if_port == XCVR_10base2) +		/* Turn off thinnet power.  Green! */ +		iowrite16(StopCoax, ioaddr + EL3_CMD); + +	iowrite16(SetIntrEnb | 0x0000, ioaddr + EL3_CMD); + +	update_stats(ioaddr, dev); +	if (vp->full_bus_master_rx) +		iowrite32(0, ioaddr + UpListPtr); +	if (vp->full_bus_master_tx) +		iowrite32(0, ioaddr + DownListPtr); + +	if (final_down && VORTEX_PCI(vp)) { +		vp->pm_state_valid = 1; +		pci_save_state(VORTEX_PCI(vp)); +		acpi_set_WOL(dev); +	} +} + +static int +vortex_close(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	int i; + +	if (netif_device_present(dev)) +		vortex_down(dev, 1); + +	if (vortex_debug > 1) { +		pr_debug("%s: vortex_close() status %4.4x, Tx status %2.2x.\n", +			   dev->name, ioread16(ioaddr + EL3_STATUS), ioread8(ioaddr + TxStatus)); +		pr_debug("%s: vortex close stats: rx_nocopy %d rx_copy %d" +			   " tx_queued %d Rx pre-checksummed %d.\n", +			   dev->name, vp->rx_nocopy, vp->rx_copy, vp->queued_packet, vp->rx_csumhits); +	} + +#if DO_ZEROCOPY +	if (vp->rx_csumhits && +	    (vp->drv_flags & HAS_HWCKSM) == 0 && +	    (vp->card_idx >= MAX_UNITS || hw_checksums[vp->card_idx] == -1)) { +		pr_warning("%s supports hardware checksums, and we're not using them!\n", dev->name); +	} +#endif + +	free_irq(dev->irq, dev); + +	if (vp->full_bus_master_rx) { /* Free Boomerang bus master Rx buffers. */ +		for (i = 0; i < RX_RING_SIZE; i++) +			if (vp->rx_skbuff[i]) { +				pci_unmap_single(	VORTEX_PCI(vp), le32_to_cpu(vp->rx_ring[i].addr), +									PKT_BUF_SZ, PCI_DMA_FROMDEVICE); +				dev_kfree_skb(vp->rx_skbuff[i]); +				vp->rx_skbuff[i] = NULL; +			} +	} +	if (vp->full_bus_master_tx) { /* Free Boomerang bus master Tx buffers. */ +		for (i = 0; i < TX_RING_SIZE; i++) { +			if (vp->tx_skbuff[i]) { +				struct sk_buff *skb = vp->tx_skbuff[i]; +#if DO_ZEROCOPY +				int k; + +				for (k=0; k<=skb_shinfo(skb)->nr_frags; k++) +						pci_unmap_single(VORTEX_PCI(vp), +										 le32_to_cpu(vp->tx_ring[i].frag[k].addr), +										 le32_to_cpu(vp->tx_ring[i].frag[k].length)&0xFFF, +										 PCI_DMA_TODEVICE); +#else +				pci_unmap_single(VORTEX_PCI(vp), le32_to_cpu(vp->tx_ring[i].addr), skb->len, PCI_DMA_TODEVICE); +#endif +				dev_kfree_skb(skb); +				vp->tx_skbuff[i] = NULL; +			} +		} +	} + +	return 0; +} + +static void +dump_tx_ring(struct net_device *dev) +{ +	if (vortex_debug > 0) { +	struct vortex_private *vp = netdev_priv(dev); +		void __iomem *ioaddr = vp->ioaddr; + +		if (vp->full_bus_master_tx) { +			int i; +			int stalled = ioread32(ioaddr + PktStatus) & 0x04;	/* Possible racy. But it's only debug stuff */ + +			pr_err("  Flags; bus-master %d, dirty %d(%d) current %d(%d)\n", +					vp->full_bus_master_tx, +					vp->dirty_tx, vp->dirty_tx % TX_RING_SIZE, +					vp->cur_tx, vp->cur_tx % TX_RING_SIZE); +			pr_err("  Transmit list %8.8x vs. %p.\n", +				   ioread32(ioaddr + DownListPtr), +				   &vp->tx_ring[vp->dirty_tx % TX_RING_SIZE]); +			issue_and_wait(dev, DownStall); +			for (i = 0; i < TX_RING_SIZE; i++) { +				unsigned int length; + +#if DO_ZEROCOPY +				length = le32_to_cpu(vp->tx_ring[i].frag[0].length); +#else +				length = le32_to_cpu(vp->tx_ring[i].length); +#endif +				pr_err("  %d: @%p  length %8.8x status %8.8x\n", +					   i, &vp->tx_ring[i], length, +					   le32_to_cpu(vp->tx_ring[i].status)); +			} +			if (!stalled) +				iowrite16(DownUnstall, ioaddr + EL3_CMD); +		} +	} +} + +static struct net_device_stats *vortex_get_stats(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	unsigned long flags; + +	if (netif_device_present(dev)) {	/* AKPM: Used to be netif_running */ +		spin_lock_irqsave (&vp->lock, flags); +		update_stats(ioaddr, dev); +		spin_unlock_irqrestore (&vp->lock, flags); +	} +	return &dev->stats; +} + +/*  Update statistics. +	Unlike with the EL3 we need not worry about interrupts changing +	the window setting from underneath us, but we must still guard +	against a race condition with a StatsUpdate interrupt updating the +	table.  This is done by checking that the ASM (!) code generated uses +	atomic updates with '+='. +	*/ +static void update_stats(void __iomem *ioaddr, struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); + +	/* Unlike the 3c5x9 we need not turn off stats updates while reading. */ +	/* Switch to the stats window, and read everything. */ +	dev->stats.tx_carrier_errors		+= window_read8(vp, 6, 0); +	dev->stats.tx_heartbeat_errors		+= window_read8(vp, 6, 1); +	dev->stats.tx_window_errors		+= window_read8(vp, 6, 4); +	dev->stats.rx_fifo_errors		+= window_read8(vp, 6, 5); +	dev->stats.tx_packets			+= window_read8(vp, 6, 6); +	dev->stats.tx_packets			+= (window_read8(vp, 6, 9) & +						    0x30) << 4; +	/* Rx packets	*/			window_read8(vp, 6, 7);   /* Must read to clear */ +	/* Don't bother with register 9, an extension of registers 6&7. +	   If we do use the 6&7 values the atomic update assumption above +	   is invalid. */ +	dev->stats.rx_bytes 			+= window_read16(vp, 6, 10); +	dev->stats.tx_bytes 			+= window_read16(vp, 6, 12); +	/* Extra stats for get_ethtool_stats() */ +	vp->xstats.tx_multiple_collisions	+= window_read8(vp, 6, 2); +	vp->xstats.tx_single_collisions         += window_read8(vp, 6, 3); +	vp->xstats.tx_deferred			+= window_read8(vp, 6, 8); +	vp->xstats.rx_bad_ssd			+= window_read8(vp, 4, 12); + +	dev->stats.collisions = vp->xstats.tx_multiple_collisions +		+ vp->xstats.tx_single_collisions +		+ vp->xstats.tx_max_collisions; + +	{ +		u8 up = window_read8(vp, 4, 13); +		dev->stats.rx_bytes += (up & 0x0f) << 16; +		dev->stats.tx_bytes += (up & 0xf0) << 12; +	} +} + +static int vortex_nway_reset(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); + +	return mii_nway_restart(&vp->mii); +} + +static int vortex_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ +	struct vortex_private *vp = netdev_priv(dev); + +	return mii_ethtool_gset(&vp->mii, cmd); +} + +static int vortex_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ +	struct vortex_private *vp = netdev_priv(dev); + +	return mii_ethtool_sset(&vp->mii, cmd); +} + +static u32 vortex_get_msglevel(struct net_device *dev) +{ +	return vortex_debug; +} + +static void vortex_set_msglevel(struct net_device *dev, u32 dbg) +{ +	vortex_debug = dbg; +} + +static int vortex_get_sset_count(struct net_device *dev, int sset) +{ +	switch (sset) { +	case ETH_SS_STATS: +		return VORTEX_NUM_STATS; +	default: +		return -EOPNOTSUPP; +	} +} + +static void vortex_get_ethtool_stats(struct net_device *dev, +	struct ethtool_stats *stats, u64 *data) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	unsigned long flags; + +	spin_lock_irqsave(&vp->lock, flags); +	update_stats(ioaddr, dev); +	spin_unlock_irqrestore(&vp->lock, flags); + +	data[0] = vp->xstats.tx_deferred; +	data[1] = vp->xstats.tx_max_collisions; +	data[2] = vp->xstats.tx_multiple_collisions; +	data[3] = vp->xstats.tx_single_collisions; +	data[4] = vp->xstats.rx_bad_ssd; +} + + +static void vortex_get_strings(struct net_device *dev, u32 stringset, u8 *data) +{ +	switch (stringset) { +	case ETH_SS_STATS: +		memcpy(data, ðtool_stats_keys, sizeof(ethtool_stats_keys)); +		break; +	default: +		WARN_ON(1); +		break; +	} +} + +static void vortex_get_drvinfo(struct net_device *dev, +					struct ethtool_drvinfo *info) +{ +	struct vortex_private *vp = netdev_priv(dev); + +	strlcpy(info->driver, DRV_NAME, sizeof(info->driver)); +	if (VORTEX_PCI(vp)) { +		strlcpy(info->bus_info, pci_name(VORTEX_PCI(vp)), +			sizeof(info->bus_info)); +	} else { +		if (VORTEX_EISA(vp)) +			strlcpy(info->bus_info, dev_name(vp->gendev), +				sizeof(info->bus_info)); +		else +			snprintf(info->bus_info, sizeof(info->bus_info), +				"EISA 0x%lx %d", dev->base_addr, dev->irq); +	} +} + +static void vortex_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ +	struct vortex_private *vp = netdev_priv(dev); + +	if (!VORTEX_PCI(vp)) +		return; + +	wol->supported = WAKE_MAGIC; + +	wol->wolopts = 0; +	if (vp->enable_wol) +		wol->wolopts |= WAKE_MAGIC; +} + +static int vortex_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ +	struct vortex_private *vp = netdev_priv(dev); + +	if (!VORTEX_PCI(vp)) +		return -EOPNOTSUPP; + +	if (wol->wolopts & ~WAKE_MAGIC) +		return -EINVAL; + +	if (wol->wolopts & WAKE_MAGIC) +		vp->enable_wol = 1; +	else +		vp->enable_wol = 0; +	acpi_set_WOL(dev); + +	return 0; +} + +static const struct ethtool_ops vortex_ethtool_ops = { +	.get_drvinfo		= vortex_get_drvinfo, +	.get_strings            = vortex_get_strings, +	.get_msglevel           = vortex_get_msglevel, +	.set_msglevel           = vortex_set_msglevel, +	.get_ethtool_stats      = vortex_get_ethtool_stats, +	.get_sset_count		= vortex_get_sset_count, +	.get_settings           = vortex_get_settings, +	.set_settings           = vortex_set_settings, +	.get_link               = ethtool_op_get_link, +	.nway_reset             = vortex_nway_reset, +	.get_wol                = vortex_get_wol, +	.set_wol                = vortex_set_wol, +	.get_ts_info		= ethtool_op_get_ts_info, +}; + +#ifdef CONFIG_PCI +/* + *	Must power the device up to do MDIO operations + */ +static int vortex_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ +	int err; +	struct vortex_private *vp = netdev_priv(dev); +	pci_power_t state = 0; + +	if(VORTEX_PCI(vp)) +		state = VORTEX_PCI(vp)->current_state; + +	/* The kernel core really should have pci_get_power_state() */ + +	if(state != 0) +		pci_set_power_state(VORTEX_PCI(vp), PCI_D0); +	err = generic_mii_ioctl(&vp->mii, if_mii(rq), cmd, NULL); +	if(state != 0) +		pci_set_power_state(VORTEX_PCI(vp), state); + +	return err; +} +#endif + + +/* Pre-Cyclone chips have no documented multicast filter, so the only +   multicast setting is to receive all multicast frames.  At least +   the chip has a very clean way to set the mode, unlike many others. */ +static void set_rx_mode(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; +	int new_mode; + +	if (dev->flags & IFF_PROMISC) { +		if (vortex_debug > 3) +			pr_notice("%s: Setting promiscuous mode.\n", dev->name); +		new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast|RxProm; +	} else	if (!netdev_mc_empty(dev) || dev->flags & IFF_ALLMULTI) { +		new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast; +	} else +		new_mode = SetRxFilter | RxStation | RxBroadcast; + +	iowrite16(new_mode, ioaddr + EL3_CMD); +} + +#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE) +/* Setup the card so that it can receive frames with an 802.1q VLAN tag. +   Note that this must be done after each RxReset due to some backwards +   compatibility logic in the Cyclone and Tornado ASICs */ + +/* The Ethernet Type used for 802.1q tagged frames */ +#define VLAN_ETHER_TYPE 0x8100 + +static void set_8021q_mode(struct net_device *dev, int enable) +{ +	struct vortex_private *vp = netdev_priv(dev); +	int mac_ctrl; + +	if ((vp->drv_flags&IS_CYCLONE) || (vp->drv_flags&IS_TORNADO)) { +		/* cyclone and tornado chipsets can recognize 802.1q +		 * tagged frames and treat them correctly */ + +		int max_pkt_size = dev->mtu+14;	/* MTU+Ethernet header */ +		if (enable) +			max_pkt_size += 4;	/* 802.1Q VLAN tag */ + +		window_write16(vp, max_pkt_size, 3, Wn3_MaxPktSize); + +		/* set VlanEtherType to let the hardware checksumming +		   treat tagged frames correctly */ +		window_write16(vp, VLAN_ETHER_TYPE, 7, Wn7_VlanEtherType); +	} else { +		/* on older cards we have to enable large frames */ + +		vp->large_frames = dev->mtu > 1500 || enable; + +		mac_ctrl = window_read16(vp, 3, Wn3_MAC_Ctrl); +		if (vp->large_frames) +			mac_ctrl |= 0x40; +		else +			mac_ctrl &= ~0x40; +		window_write16(vp, mac_ctrl, 3, Wn3_MAC_Ctrl); +	} +} +#else + +static void set_8021q_mode(struct net_device *dev, int enable) +{ +} + + +#endif + +/* MII transceiver control section. +   Read and write the MII registers using software-generated serial +   MDIO protocol.  See the MII specifications or DP83840A data sheet +   for details. */ + +/* The maximum data clock rate is 2.5 Mhz.  The minimum timing is usually +   met by back-to-back PCI I/O cycles, but we insert a delay to avoid +   "overclocking" issues. */ +static void mdio_delay(struct vortex_private *vp) +{ +	window_read32(vp, 4, Wn4_PhysicalMgmt); +} + +#define MDIO_SHIFT_CLK	0x01 +#define MDIO_DIR_WRITE	0x04 +#define MDIO_DATA_WRITE0 (0x00 | MDIO_DIR_WRITE) +#define MDIO_DATA_WRITE1 (0x02 | MDIO_DIR_WRITE) +#define MDIO_DATA_READ	0x02 +#define MDIO_ENB_IN		0x00 + +/* Generate the preamble required for initial synchronization and +   a few older transceivers. */ +static void mdio_sync(struct vortex_private *vp, int bits) +{ +	/* Establish sync by sending at least 32 logic ones. */ +	while (-- bits >= 0) { +		window_write16(vp, MDIO_DATA_WRITE1, 4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +		window_write16(vp, MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, +			       4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +	} +} + +static int mdio_read(struct net_device *dev, int phy_id, int location) +{ +	int i; +	struct vortex_private *vp = netdev_priv(dev); +	int read_cmd = (0xf6 << 10) | (phy_id << 5) | location; +	unsigned int retval = 0; + +	spin_lock_bh(&vp->mii_lock); + +	if (mii_preamble_required) +		mdio_sync(vp, 32); + +	/* Shift the read command bits out. */ +	for (i = 14; i >= 0; i--) { +		int dataval = (read_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; +		window_write16(vp, dataval, 4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +		window_write16(vp, dataval | MDIO_SHIFT_CLK, +			       4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +	} +	/* Read the two transition, 16 data, and wire-idle bits. */ +	for (i = 19; i > 0; i--) { +		window_write16(vp, MDIO_ENB_IN, 4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +		retval = (retval << 1) | +			((window_read16(vp, 4, Wn4_PhysicalMgmt) & +			  MDIO_DATA_READ) ? 1 : 0); +		window_write16(vp, MDIO_ENB_IN | MDIO_SHIFT_CLK, +			       4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +	} + +	spin_unlock_bh(&vp->mii_lock); + +	return retval & 0x20000 ? 0xffff : retval>>1 & 0xffff; +} + +static void mdio_write(struct net_device *dev, int phy_id, int location, int value) +{ +	struct vortex_private *vp = netdev_priv(dev); +	int write_cmd = 0x50020000 | (phy_id << 23) | (location << 18) | value; +	int i; + +	spin_lock_bh(&vp->mii_lock); + +	if (mii_preamble_required) +		mdio_sync(vp, 32); + +	/* Shift the command bits out. */ +	for (i = 31; i >= 0; i--) { +		int dataval = (write_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; +		window_write16(vp, dataval, 4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +		window_write16(vp, dataval | MDIO_SHIFT_CLK, +			       4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +	} +	/* Leave the interface idle. */ +	for (i = 1; i >= 0; i--) { +		window_write16(vp, MDIO_ENB_IN, 4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +		window_write16(vp, MDIO_ENB_IN | MDIO_SHIFT_CLK, +			       4, Wn4_PhysicalMgmt); +		mdio_delay(vp); +	} + +	spin_unlock_bh(&vp->mii_lock); +} + +/* ACPI: Advanced Configuration and Power Interface. */ +/* Set Wake-On-LAN mode and put the board into D3 (power-down) state. */ +static void acpi_set_WOL(struct net_device *dev) +{ +	struct vortex_private *vp = netdev_priv(dev); +	void __iomem *ioaddr = vp->ioaddr; + +	device_set_wakeup_enable(vp->gendev, vp->enable_wol); + +	if (vp->enable_wol) { +		/* Power up on: 1==Downloaded Filter, 2==Magic Packets, 4==Link Status. */ +		window_write16(vp, 2, 7, 0x0c); +		/* The RxFilter must accept the WOL frames. */ +		iowrite16(SetRxFilter|RxStation|RxMulticast|RxBroadcast, ioaddr + EL3_CMD); +		iowrite16(RxEnable, ioaddr + EL3_CMD); + +		if (pci_enable_wake(VORTEX_PCI(vp), PCI_D3hot, 1)) { +			pr_info("%s: WOL not supported.\n", pci_name(VORTEX_PCI(vp))); + +			vp->enable_wol = 0; +			return; +		} + +		if (VORTEX_PCI(vp)->current_state < PCI_D3hot) +			return; + +		/* Change the power state to D3; RxEnable doesn't take effect. */ +		pci_set_power_state(VORTEX_PCI(vp), PCI_D3hot); +	} +} + + +static void vortex_remove_one(struct pci_dev *pdev) +{ +	struct net_device *dev = pci_get_drvdata(pdev); +	struct vortex_private *vp; + +	if (!dev) { +		pr_err("vortex_remove_one called for Compaq device!\n"); +		BUG(); +	} + +	vp = netdev_priv(dev); + +	if (vp->cb_fn_base) +		pci_iounmap(pdev, vp->cb_fn_base); + +	unregister_netdev(dev); + +	pci_set_power_state(pdev, PCI_D0);	/* Go active */ +	if (vp->pm_state_valid) +		pci_restore_state(pdev); +	pci_disable_device(pdev); + +	/* Should really use issue_and_wait() here */ +	iowrite16(TotalReset | ((vp->drv_flags & EEPROM_RESET) ? 0x04 : 0x14), +	     vp->ioaddr + EL3_CMD); + +	pci_iounmap(pdev, vp->ioaddr); + +	pci_free_consistent(pdev, +						sizeof(struct boom_rx_desc) * RX_RING_SIZE +							+ sizeof(struct boom_tx_desc) * TX_RING_SIZE, +						vp->rx_ring, +						vp->rx_ring_dma); + +	pci_release_regions(pdev); + +	free_netdev(dev); +} + + +static struct pci_driver vortex_driver = { +	.name		= "3c59x", +	.probe		= vortex_init_one, +	.remove		= vortex_remove_one, +	.id_table	= vortex_pci_tbl, +	.driver.pm	= VORTEX_PM_OPS, +}; + + +static int vortex_have_pci; +static int vortex_have_eisa; + + +static int __init vortex_init(void) +{ +	int pci_rc, eisa_rc; + +	pci_rc = pci_register_driver(&vortex_driver); +	eisa_rc = vortex_eisa_init(); + +	if (pci_rc == 0) +		vortex_have_pci = 1; +	if (eisa_rc > 0) +		vortex_have_eisa = 1; + +	return (vortex_have_pci + vortex_have_eisa) ? 0 : -ENODEV; +} + + +static void __exit vortex_eisa_cleanup(void) +{ +	void __iomem *ioaddr; + +#ifdef CONFIG_EISA +	/* Take care of the EISA devices */ +	eisa_driver_unregister(&vortex_eisa_driver); +#endif + +	if (compaq_net_device) { +		ioaddr = ioport_map(compaq_net_device->base_addr, +		                    VORTEX_TOTAL_SIZE); + +		unregister_netdev(compaq_net_device); +		iowrite16(TotalReset, ioaddr + EL3_CMD); +		release_region(compaq_net_device->base_addr, +		               VORTEX_TOTAL_SIZE); + +		free_netdev(compaq_net_device); +	} +} + + +static void __exit vortex_cleanup(void) +{ +	if (vortex_have_pci) +		pci_unregister_driver(&vortex_driver); +	if (vortex_have_eisa) +		vortex_eisa_cleanup(); +} + + +module_init(vortex_init); +module_exit(vortex_cleanup); diff --git a/drivers/net/ethernet/3com/Kconfig b/drivers/net/ethernet/3com/Kconfig new file mode 100644 index 00000000000..afaab4b2333 --- /dev/null +++ b/drivers/net/ethernet/3com/Kconfig @@ -0,0 +1,107 @@ +# +# 3Com Ethernet device configuration +# + +config NET_VENDOR_3COM +	bool "3Com devices" +	default y +	depends on ISA || EISA || PCI || PCMCIA +	---help--- +	  If you have a network (Ethernet) card belonging to this class, say Y +	  and read the Ethernet-HOWTO, available from +	  <http://www.tldp.org/docs.html#howto>. + +	  Note that the answer to this question doesn't directly affect the +	  kernel: saying N will just cause the configurator to skip all +	  the questions about 3Com cards. If you say Y, you will be asked for +	  your specific card in the following questions. + +if NET_VENDOR_3COM + +config EL3 +	tristate "3c509/3c579 \"EtherLink III\" support" +	depends on (ISA || EISA) +	---help--- +	  If you have a network (Ethernet) card belonging to the 3Com +	  EtherLinkIII series, say Y and read the Ethernet-HOWTO, available +	  from <http://www.tldp.org/docs.html#howto>. + +	  If your card is not working you may need to use the DOS +	  setup disk to disable Plug & Play mode, and to select the default +	  media type. + +	  To compile this driver as a module, choose M here. The module +	  will be called 3c509. + +config 3C515 +	tristate "3c515 ISA \"Fast EtherLink\"" +	depends on ISA && ISA_DMA_API +	---help--- +	  If you have a 3Com ISA EtherLink XL "Corkscrew" 3c515 Fast Ethernet +	  network card, say Y and read the Ethernet-HOWTO, available from +	  <http://www.tldp.org/docs.html#howto>. + +	  To compile this driver as a module, choose M here. The module +	  will be called 3c515. + +config PCMCIA_3C574 +	tristate "3Com 3c574 PCMCIA support" +	depends on PCMCIA +	---help--- +	  Say Y here if you intend to attach a 3Com 3c574 or compatible PCMCIA +	  (PC-card) Fast Ethernet card to your computer. + +	  To compile this driver as a module, choose M here: the module will be +	  called 3c574_cs.  If unsure, say N. + +config PCMCIA_3C589 +	tristate "3Com 3c589 PCMCIA support" +	depends on PCMCIA +	---help--- +	  Say Y here if you intend to attach a 3Com 3c589 or compatible PCMCIA +	  (PC-card) Ethernet card to your computer. + +	  To compile this driver as a module, choose M here: the module will be +	  called 3c589_cs.  If unsure, say N. + +config VORTEX +	tristate "3c590/3c900 series (592/595/597) \"Vortex/Boomerang\" support" +	depends on (PCI || EISA) && HAS_IOPORT_MAP +	select MII +	---help--- +	  This option enables driver support for a large number of 10Mbps and +	  10/100Mbps EISA, PCI and Cardbus 3Com network cards: + +	  "Vortex"    (Fast EtherLink 3c590/3c592/3c595/3c597) EISA and PCI +	  "Boomerang" (EtherLink XL 3c900 or 3c905)            PCI +	  "Cyclone"   (3c540/3c900/3c905/3c980/3c575/3c656)    PCI and Cardbus +	  "Tornado"   (3c905)                                  PCI +	  "Hurricane" (3c555/3cSOHO)                           PCI + +	  If you have such a card, say Y and read the Ethernet-HOWTO, +	  available from <http://www.tldp.org/docs.html#howto>. More +	  specific information is in +	  <file:Documentation/networking/vortex.txt> and in the comments at +	  the beginning of <file:drivers/net/ethernet/3com/3c59x.c>. + +	  To compile this support as a module, choose M here. + +config TYPHOON +	tristate "3cr990 series \"Typhoon\" support" +	depends on PCI +	select CRC32 +	---help--- +	  This option enables driver support for the 3cr990 series of cards: + +	  3C990-TX, 3CR990-TX-95, 3CR990-TX-97, 3CR990-FX-95, 3CR990-FX-97, +	  3CR990SVR, 3CR990SVR95, 3CR990SVR97, 3CR990-FX-95 Server, +	  3CR990-FX-97 Server, 3C990B-TX-M, 3C990BSVR + +	  If you have a network (Ethernet) card of this type, say Y and read +	  the Ethernet-HOWTO, available from +	  <http://www.tldp.org/docs.html#howto>. + +	  To compile this driver as a module, choose M here. The module +	  will be called typhoon. + +endif # NET_VENDOR_3COM diff --git a/drivers/net/ethernet/3com/Makefile b/drivers/net/ethernet/3com/Makefile new file mode 100644 index 00000000000..74046afab99 --- /dev/null +++ b/drivers/net/ethernet/3com/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the 3Com Ethernet device drivers +# + +obj-$(CONFIG_EL3) += 3c509.o +obj-$(CONFIG_3C515) += 3c515.o +obj-$(CONFIG_PCMCIA_3C589) += 3c589_cs.o +obj-$(CONFIG_PCMCIA_3C574) += 3c574_cs.o +obj-$(CONFIG_VORTEX) += 3c59x.o +obj-$(CONFIG_TYPHOON) += typhoon.o diff --git a/drivers/net/ethernet/3com/typhoon.c b/drivers/net/ethernet/3com/typhoon.c new file mode 100644 index 00000000000..e13b04624de --- /dev/null +++ b/drivers/net/ethernet/3com/typhoon.c @@ -0,0 +1,2556 @@ +/* typhoon.c: A Linux Ethernet device driver for 3Com 3CR990 family of NICs */ +/* +	Written 2002-2004 by David Dillow <dave@thedillows.org> +	Based on code written 1998-2000 by Donald Becker <becker@scyld.com> and +	Linux 2.2.x driver by David P. McLean <davidpmclean@yahoo.com>. + +	This software may be used and distributed according to the terms of +	the GNU General Public License (GPL), incorporated herein by reference. +	Drivers based on or derived from this code fall under the GPL and must +	retain the authorship, copyright and license notice.  This file is not +	a complete program and may only be used when the entire operating +	system is licensed under the GPL. + +	This software is available on a public web site. It may enable +	cryptographic capabilities of the 3Com hardware, and may be +	exported from the United States under License Exception "TSU" +	pursuant to 15 C.F.R. Section 740.13(e). + +	This work was funded by the National Library of Medicine under +	the Department of Energy project number 0274DD06D1 and NLM project +	number Y1-LM-2015-01. + +	This driver is designed for the 3Com 3CR990 Family of cards with the +	3XP Processor. It has been tested on x86 and sparc64. + +	KNOWN ISSUES: +	*) Cannot DMA Rx packets to a 2 byte aligned address. Also firmware +		issue. Hopefully 3Com will fix it. +	*) Waiting for a command response takes 8ms due to non-preemptable +		polling. Only significant for getting stats and creating +		SAs, but an ugly wart never the less. + +	TODO: +	*) Doesn't do IPSEC offloading. Yet. Keep yer pants on, it's coming. +	*) Add more support for ethtool (especially for NIC stats) +	*) Allow disabling of RX checksum offloading +	*) Fix MAC changing to work while the interface is up +		(Need to put commands on the TX ring, which changes +		the locking) +	*) Add in FCS to {rx,tx}_bytes, since the hardware doesn't. See +		http://oss.sgi.com/cgi-bin/mesg.cgi?a=netdev&i=20031215152211.7003fe8e.rddunlap%40osdl.org +*/ + +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. + * Setting to > 1518 effectively disables this feature. + */ +static int rx_copybreak = 200; + +/* Should we use MMIO or Port IO? + * 0: Port IO + * 1: MMIO + * 2: Try MMIO, fallback to Port IO + */ +static unsigned int use_mmio = 2; + +/* end user-configurable values */ + +/* Maximum number of multicast addresses to filter (vs. rx-all-multicast). + */ +static const int multicast_filter_limit = 32; + +/* Operational parameters that are set at compile time. */ + +/* Keep the ring sizes a power of two for compile efficiency. + * The compiler will convert <unsigned>'%'<2^N> into a bit mask. + * Making the Tx ring too large decreases the effectiveness of channel + * bonding and packet priority. + * There are no ill effects from too-large receive rings. + * + * We don't currently use the Hi Tx ring so, don't make it very big. + * + * Beware that if we start using the Hi Tx ring, we will need to change + * typhoon_num_free_tx() and typhoon_tx_complete() to account for that. + */ +#define TXHI_ENTRIES		2 +#define TXLO_ENTRIES		128 +#define RX_ENTRIES		32 +#define COMMAND_ENTRIES		16 +#define RESPONSE_ENTRIES	32 + +#define COMMAND_RING_SIZE	(COMMAND_ENTRIES * sizeof(struct cmd_desc)) +#define RESPONSE_RING_SIZE	(RESPONSE_ENTRIES * sizeof(struct resp_desc)) + +/* The 3XP will preload and remove 64 entries from the free buffer + * list, and we need one entry to keep the ring from wrapping, so + * to keep this a power of two, we use 128 entries. + */ +#define RXFREE_ENTRIES		128 +#define RXENT_ENTRIES		(RXFREE_ENTRIES - 1) + +/* Operational parameters that usually are not changed. */ + +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT  (2*HZ) + +#define PKT_BUF_SZ		1536 +#define FIRMWARE_NAME		"3com/typhoon.bin" + +#define pr_fmt(fmt)		KBUILD_MODNAME " " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/ethtool.h> +#include <linux/if_vlan.h> +#include <linux/crc32.h> +#include <linux/bitops.h> +#include <asm/processor.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/in6.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> + +#include "typhoon.h" + +MODULE_AUTHOR("David Dillow <dave@thedillows.org>"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(FIRMWARE_NAME); +MODULE_DESCRIPTION("3Com Typhoon Family (3C990, 3CR990, and variants)"); +MODULE_PARM_DESC(rx_copybreak, "Packets smaller than this are copied and " +			       "the buffer given back to the NIC. Default " +			       "is 200."); +MODULE_PARM_DESC(use_mmio, "Use MMIO (1) or PIO(0) to access the NIC. " +			   "Default is to try MMIO and fallback to PIO."); +module_param(rx_copybreak, int, 0); +module_param(use_mmio, int, 0); + +#if defined(NETIF_F_TSO) && MAX_SKB_FRAGS > 32 +#warning Typhoon only supports 32 entries in its SG list for TSO, disabling TSO +#undef NETIF_F_TSO +#endif + +#if TXLO_ENTRIES <= (2 * MAX_SKB_FRAGS) +#error TX ring too small! +#endif + +struct typhoon_card_info { +	const char *name; +	const int capabilities; +}; + +#define TYPHOON_CRYPTO_NONE		0x00 +#define TYPHOON_CRYPTO_DES		0x01 +#define TYPHOON_CRYPTO_3DES		0x02 +#define	TYPHOON_CRYPTO_VARIABLE		0x04 +#define TYPHOON_FIBER			0x08 +#define TYPHOON_WAKEUP_NEEDS_RESET	0x10 + +enum typhoon_cards { +	TYPHOON_TX = 0, TYPHOON_TX95, TYPHOON_TX97, TYPHOON_SVR, +	TYPHOON_SVR95, TYPHOON_SVR97, TYPHOON_TXM, TYPHOON_BSVR, +	TYPHOON_FX95, TYPHOON_FX97, TYPHOON_FX95SVR, TYPHOON_FX97SVR, +	TYPHOON_FXM, +}; + +/* directly indexed by enum typhoon_cards, above */ +static struct typhoon_card_info typhoon_card_info[] = { +	{ "3Com Typhoon (3C990-TX)", +		TYPHOON_CRYPTO_NONE}, +	{ "3Com Typhoon (3CR990-TX-95)", +		TYPHOON_CRYPTO_DES}, +	{ "3Com Typhoon (3CR990-TX-97)", +	 	TYPHOON_CRYPTO_DES | TYPHOON_CRYPTO_3DES}, +	{ "3Com Typhoon (3C990SVR)", +		TYPHOON_CRYPTO_NONE}, +	{ "3Com Typhoon (3CR990SVR95)", +		TYPHOON_CRYPTO_DES}, +	{ "3Com Typhoon (3CR990SVR97)", +	 	TYPHOON_CRYPTO_DES | TYPHOON_CRYPTO_3DES}, +	{ "3Com Typhoon2 (3C990B-TX-M)", +		TYPHOON_CRYPTO_VARIABLE}, +	{ "3Com Typhoon2 (3C990BSVR)", +		TYPHOON_CRYPTO_VARIABLE}, +	{ "3Com Typhoon (3CR990-FX-95)", +		TYPHOON_CRYPTO_DES | TYPHOON_FIBER}, +	{ "3Com Typhoon (3CR990-FX-97)", +	 	TYPHOON_CRYPTO_DES | TYPHOON_CRYPTO_3DES | TYPHOON_FIBER}, +	{ "3Com Typhoon (3CR990-FX-95 Server)", +	 	TYPHOON_CRYPTO_DES | TYPHOON_FIBER}, +	{ "3Com Typhoon (3CR990-FX-97 Server)", +	 	TYPHOON_CRYPTO_DES | TYPHOON_CRYPTO_3DES | TYPHOON_FIBER}, +	{ "3Com Typhoon2 (3C990B-FX-97)", +		TYPHOON_CRYPTO_VARIABLE | TYPHOON_FIBER}, +}; + +/* Notes on the new subsystem numbering scheme: + * bits 0-1 indicate crypto capabilities: (0) variable, (1) DES, or (2) 3DES + * bit 4 indicates if this card has secured firmware (we don't support it) + * bit 8 indicates if this is a (0) copper or (1) fiber card + * bits 12-16 indicate card type: (0) client and (1) server + */ +static DEFINE_PCI_DEVICE_TABLE(typhoon_pci_tbl) = { +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990, +	  PCI_ANY_ID, PCI_ANY_ID, 0, 0,TYPHOON_TX }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990_TX_95, +	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, TYPHOON_TX95 }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990_TX_97, +	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, TYPHOON_TX97 }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990B, +	  PCI_ANY_ID, 0x1000, 0, 0, TYPHOON_TXM }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990B, +	  PCI_ANY_ID, 0x1102, 0, 0, TYPHOON_FXM }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990B, +	  PCI_ANY_ID, 0x2000, 0, 0, TYPHOON_BSVR }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990_FX, +	  PCI_ANY_ID, 0x1101, 0, 0, TYPHOON_FX95 }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990_FX, +	  PCI_ANY_ID, 0x1102, 0, 0, TYPHOON_FX97 }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990_FX, +	  PCI_ANY_ID, 0x2101, 0, 0, TYPHOON_FX95SVR }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990_FX, +	  PCI_ANY_ID, 0x2102, 0, 0, TYPHOON_FX97SVR }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990SVR95, +	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, TYPHOON_SVR95 }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990SVR97, +	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, TYPHOON_SVR97 }, +	{ PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3CR990SVR, +	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, TYPHOON_SVR }, +	{ 0, } +}; +MODULE_DEVICE_TABLE(pci, typhoon_pci_tbl); + +/* Define the shared memory area + * Align everything the 3XP will normally be using. + * We'll need to move/align txHi if we start using that ring. + */ +#define __3xp_aligned	____cacheline_aligned +struct typhoon_shared { +	struct typhoon_interface	iface; +	struct typhoon_indexes		indexes			__3xp_aligned; +	struct tx_desc			txLo[TXLO_ENTRIES] 	__3xp_aligned; +	struct rx_desc			rxLo[RX_ENTRIES]	__3xp_aligned; +	struct rx_desc			rxHi[RX_ENTRIES]	__3xp_aligned; +	struct cmd_desc			cmd[COMMAND_ENTRIES]	__3xp_aligned; +	struct resp_desc		resp[RESPONSE_ENTRIES]	__3xp_aligned; +	struct rx_free			rxBuff[RXFREE_ENTRIES]	__3xp_aligned; +	u32				zeroWord; +	struct tx_desc			txHi[TXHI_ENTRIES]; +} __packed; + +struct rxbuff_ent { +	struct sk_buff *skb; +	dma_addr_t	dma_addr; +}; + +struct typhoon { +	/* Tx cache line section */ +	struct transmit_ring 	txLoRing	____cacheline_aligned; +	struct pci_dev *	tx_pdev; +	void __iomem		*tx_ioaddr; +	u32			txlo_dma_addr; + +	/* Irq/Rx cache line section */ +	void __iomem		*ioaddr		____cacheline_aligned; +	struct typhoon_indexes *indexes; +	u8			awaiting_resp; +	u8			duplex; +	u8			speed; +	u8			card_state; +	struct basic_ring	rxLoRing; +	struct pci_dev *	pdev; +	struct net_device *	dev; +	struct napi_struct	napi; +	struct basic_ring	rxHiRing; +	struct basic_ring	rxBuffRing; +	struct rxbuff_ent	rxbuffers[RXENT_ENTRIES]; + +	/* general section */ +	spinlock_t		command_lock	____cacheline_aligned; +	struct basic_ring	cmdRing; +	struct basic_ring	respRing; +	struct net_device_stats	stats; +	struct net_device_stats	stats_saved; +	struct typhoon_shared *	shared; +	dma_addr_t		shared_dma; +	__le16			xcvr_select; +	__le16			wol_events; +	__le32			offload; + +	/* unused stuff (future use) */ +	int			capabilities; +	struct transmit_ring 	txHiRing; +}; + +enum completion_wait_values { +	NoWait = 0, WaitNoSleep, WaitSleep, +}; + +/* These are the values for the typhoon.card_state variable. + * These determine where the statistics will come from in get_stats(). + * The sleep image does not support the statistics we need. + */ +enum state_values { +	Sleeping = 0, Running, +}; + +/* PCI writes are not guaranteed to be posted in order, but outstanding writes + * cannot pass a read, so this forces current writes to post. + */ +#define typhoon_post_pci_writes(x) \ +	do { if(likely(use_mmio)) ioread32(x+TYPHOON_REG_HEARTBEAT); } while(0) + +/* We'll wait up to six seconds for a reset, and half a second normally. + */ +#define TYPHOON_UDELAY			50 +#define TYPHOON_RESET_TIMEOUT_SLEEP	(6 * HZ) +#define TYPHOON_RESET_TIMEOUT_NOSLEEP	((6 * 1000000) / TYPHOON_UDELAY) +#define TYPHOON_WAIT_TIMEOUT		((1000000 / 2) / TYPHOON_UDELAY) + +#if defined(NETIF_F_TSO) +#define skb_tso_size(x)		(skb_shinfo(x)->gso_size) +#define TSO_NUM_DESCRIPTORS	2 +#define TSO_OFFLOAD_ON		TYPHOON_OFFLOAD_TCP_SEGMENT +#else +#define NETIF_F_TSO 		0 +#define skb_tso_size(x) 	0 +#define TSO_NUM_DESCRIPTORS	0 +#define TSO_OFFLOAD_ON		0 +#endif + +static inline void +typhoon_inc_index(u32 *index, const int count, const int num_entries) +{ +	/* Increment a ring index -- we can use this for all rings execept +	 * the Rx rings, as they use different size descriptors +	 * otherwise, everything is the same size as a cmd_desc +	 */ +	*index += count * sizeof(struct cmd_desc); +	*index %= num_entries * sizeof(struct cmd_desc); +} + +static inline void +typhoon_inc_cmd_index(u32 *index, const int count) +{ +	typhoon_inc_index(index, count, COMMAND_ENTRIES); +} + +static inline void +typhoon_inc_resp_index(u32 *index, const int count) +{ +	typhoon_inc_index(index, count, RESPONSE_ENTRIES); +} + +static inline void +typhoon_inc_rxfree_index(u32 *index, const int count) +{ +	typhoon_inc_index(index, count, RXFREE_ENTRIES); +} + +static inline void +typhoon_inc_tx_index(u32 *index, const int count) +{ +	/* if we start using the Hi Tx ring, this needs updating */ +	typhoon_inc_index(index, count, TXLO_ENTRIES); +} + +static inline void +typhoon_inc_rx_index(u32 *index, const int count) +{ +	/* sizeof(struct rx_desc) != sizeof(struct cmd_desc) */ +	*index += count * sizeof(struct rx_desc); +	*index %= RX_ENTRIES * sizeof(struct rx_desc); +} + +static int +typhoon_reset(void __iomem *ioaddr, int wait_type) +{ +	int i, err = 0; +	int timeout; + +	if(wait_type == WaitNoSleep) +		timeout = TYPHOON_RESET_TIMEOUT_NOSLEEP; +	else +		timeout = TYPHOON_RESET_TIMEOUT_SLEEP; + +	iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK); +	iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_STATUS); + +	iowrite32(TYPHOON_RESET_ALL, ioaddr + TYPHOON_REG_SOFT_RESET); +	typhoon_post_pci_writes(ioaddr); +	udelay(1); +	iowrite32(TYPHOON_RESET_NONE, ioaddr + TYPHOON_REG_SOFT_RESET); + +	if(wait_type != NoWait) { +		for(i = 0; i < timeout; i++) { +			if(ioread32(ioaddr + TYPHOON_REG_STATUS) == +			   TYPHOON_STATUS_WAITING_FOR_HOST) +				goto out; + +			if(wait_type == WaitSleep) +				schedule_timeout_uninterruptible(1); +			else +				udelay(TYPHOON_UDELAY); +		} + +		err = -ETIMEDOUT; +	} + +out: +	iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK); +	iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_STATUS); + +	/* The 3XP seems to need a little extra time to complete the load +	 * of the sleep image before we can reliably boot it. Failure to +	 * do this occasionally results in a hung adapter after boot in +	 * typhoon_init_one() while trying to read the MAC address or +	 * putting the card to sleep. 3Com's driver waits 5ms, but +	 * that seems to be overkill. However, if we can sleep, we might +	 * as well give it that much time. Otherwise, we'll give it 500us, +	 * which should be enough (I've see it work well at 100us, but still +	 * saw occasional problems.) +	 */ +	if(wait_type == WaitSleep) +		msleep(5); +	else +		udelay(500); +	return err; +} + +static int +typhoon_wait_status(void __iomem *ioaddr, u32 wait_value) +{ +	int i, err = 0; + +	for(i = 0; i < TYPHOON_WAIT_TIMEOUT; i++) { +		if(ioread32(ioaddr + TYPHOON_REG_STATUS) == wait_value) +			goto out; +		udelay(TYPHOON_UDELAY); +	} + +	err = -ETIMEDOUT; + +out: +	return err; +} + +static inline void +typhoon_media_status(struct net_device *dev, struct resp_desc *resp) +{ +	if(resp->parm1 & TYPHOON_MEDIA_STAT_NO_LINK) +		netif_carrier_off(dev); +	else +		netif_carrier_on(dev); +} + +static inline void +typhoon_hello(struct typhoon *tp) +{ +	struct basic_ring *ring = &tp->cmdRing; +	struct cmd_desc *cmd; + +	/* We only get a hello request if we've not sent anything to the +	 * card in a long while. If the lock is held, then we're in the +	 * process of issuing a command, so we don't need to respond. +	 */ +	if(spin_trylock(&tp->command_lock)) { +		cmd = (struct cmd_desc *)(ring->ringBase + ring->lastWrite); +		typhoon_inc_cmd_index(&ring->lastWrite, 1); + +		INIT_COMMAND_NO_RESPONSE(cmd, TYPHOON_CMD_HELLO_RESP); +		wmb(); +		iowrite32(ring->lastWrite, tp->ioaddr + TYPHOON_REG_CMD_READY); +		spin_unlock(&tp->command_lock); +	} +} + +static int +typhoon_process_response(struct typhoon *tp, int resp_size, +				struct resp_desc *resp_save) +{ +	struct typhoon_indexes *indexes = tp->indexes; +	struct resp_desc *resp; +	u8 *base = tp->respRing.ringBase; +	int count, len, wrap_len; +	u32 cleared; +	u32 ready; + +	cleared = le32_to_cpu(indexes->respCleared); +	ready = le32_to_cpu(indexes->respReady); +	while(cleared != ready) { +		resp = (struct resp_desc *)(base + cleared); +		count = resp->numDesc + 1; +		if(resp_save && resp->seqNo) { +			if(count > resp_size) { +				resp_save->flags = TYPHOON_RESP_ERROR; +				goto cleanup; +			} + +			wrap_len = 0; +			len = count * sizeof(*resp); +			if(unlikely(cleared + len > RESPONSE_RING_SIZE)) { +				wrap_len = cleared + len - RESPONSE_RING_SIZE; +				len = RESPONSE_RING_SIZE - cleared; +			} + +			memcpy(resp_save, resp, len); +			if(unlikely(wrap_len)) { +				resp_save += len / sizeof(*resp); +				memcpy(resp_save, base, wrap_len); +			} + +			resp_save = NULL; +		} else if(resp->cmd == TYPHOON_CMD_READ_MEDIA_STATUS) { +			typhoon_media_status(tp->dev, resp); +		} else if(resp->cmd == TYPHOON_CMD_HELLO_RESP) { +			typhoon_hello(tp); +		} else { +			netdev_err(tp->dev, +				   "dumping unexpected response 0x%04x:%d:0x%02x:0x%04x:%08x:%08x\n", +				   le16_to_cpu(resp->cmd), +				   resp->numDesc, resp->flags, +				   le16_to_cpu(resp->parm1), +				   le32_to_cpu(resp->parm2), +				   le32_to_cpu(resp->parm3)); +		} + +cleanup: +		typhoon_inc_resp_index(&cleared, count); +	} + +	indexes->respCleared = cpu_to_le32(cleared); +	wmb(); +	return resp_save == NULL; +} + +static inline int +typhoon_num_free(int lastWrite, int lastRead, int ringSize) +{ +	/* this works for all descriptors but rx_desc, as they are a +	 * different size than the cmd_desc -- everyone else is the same +	 */ +	lastWrite /= sizeof(struct cmd_desc); +	lastRead /= sizeof(struct cmd_desc); +	return (ringSize + lastRead - lastWrite - 1) % ringSize; +} + +static inline int +typhoon_num_free_cmd(struct typhoon *tp) +{ +	int lastWrite = tp->cmdRing.lastWrite; +	int cmdCleared = le32_to_cpu(tp->indexes->cmdCleared); + +	return typhoon_num_free(lastWrite, cmdCleared, COMMAND_ENTRIES); +} + +static inline int +typhoon_num_free_resp(struct typhoon *tp) +{ +	int respReady = le32_to_cpu(tp->indexes->respReady); +	int respCleared = le32_to_cpu(tp->indexes->respCleared); + +	return typhoon_num_free(respReady, respCleared, RESPONSE_ENTRIES); +} + +static inline int +typhoon_num_free_tx(struct transmit_ring *ring) +{ +	/* if we start using the Hi Tx ring, this needs updating */ +	return typhoon_num_free(ring->lastWrite, ring->lastRead, TXLO_ENTRIES); +} + +static int +typhoon_issue_command(struct typhoon *tp, int num_cmd, struct cmd_desc *cmd, +		      int num_resp, struct resp_desc *resp) +{ +	struct typhoon_indexes *indexes = tp->indexes; +	struct basic_ring *ring = &tp->cmdRing; +	struct resp_desc local_resp; +	int i, err = 0; +	int got_resp; +	int freeCmd, freeResp; +	int len, wrap_len; + +	spin_lock(&tp->command_lock); + +	freeCmd = typhoon_num_free_cmd(tp); +	freeResp = typhoon_num_free_resp(tp); + +	if(freeCmd < num_cmd || freeResp < num_resp) { +		netdev_err(tp->dev, "no descs for cmd, had (needed) %d (%d) cmd, %d (%d) resp\n", +			   freeCmd, num_cmd, freeResp, num_resp); +		err = -ENOMEM; +		goto out; +	} + +	if(cmd->flags & TYPHOON_CMD_RESPOND) { +		/* If we're expecting a response, but the caller hasn't given +		 * us a place to put it, we'll provide one. +		 */ +		tp->awaiting_resp = 1; +		if(resp == NULL) { +			resp = &local_resp; +			num_resp = 1; +		} +	} + +	wrap_len = 0; +	len = num_cmd * sizeof(*cmd); +	if(unlikely(ring->lastWrite + len > COMMAND_RING_SIZE)) { +		wrap_len = ring->lastWrite + len - COMMAND_RING_SIZE; +		len = COMMAND_RING_SIZE - ring->lastWrite; +	} + +	memcpy(ring->ringBase + ring->lastWrite, cmd, len); +	if(unlikely(wrap_len)) { +		struct cmd_desc *wrap_ptr = cmd; +		wrap_ptr += len / sizeof(*cmd); +		memcpy(ring->ringBase, wrap_ptr, wrap_len); +	} + +	typhoon_inc_cmd_index(&ring->lastWrite, num_cmd); + +	/* "I feel a presence... another warrior is on the mesa." +	 */ +	wmb(); +	iowrite32(ring->lastWrite, tp->ioaddr + TYPHOON_REG_CMD_READY); +	typhoon_post_pci_writes(tp->ioaddr); + +	if((cmd->flags & TYPHOON_CMD_RESPOND) == 0) +		goto out; + +	/* Ugh. We'll be here about 8ms, spinning our thumbs, unable to +	 * preempt or do anything other than take interrupts. So, don't +	 * wait for a response unless you have to. +	 * +	 * I've thought about trying to sleep here, but we're called +	 * from many contexts that don't allow that. Also, given the way +	 * 3Com has implemented irq coalescing, we would likely timeout -- +	 * this has been observed in real life! +	 * +	 * The big killer is we have to wait to get stats from the card, +	 * though we could go to a periodic refresh of those if we don't +	 * mind them getting somewhat stale. The rest of the waiting +	 * commands occur during open/close/suspend/resume, so they aren't +	 * time critical. Creating SAs in the future will also have to +	 * wait here. +	 */ +	got_resp = 0; +	for(i = 0; i < TYPHOON_WAIT_TIMEOUT && !got_resp; i++) { +		if(indexes->respCleared != indexes->respReady) +			got_resp = typhoon_process_response(tp, num_resp, +								resp); +		udelay(TYPHOON_UDELAY); +	} + +	if(!got_resp) { +		err = -ETIMEDOUT; +		goto out; +	} + +	/* Collect the error response even if we don't care about the +	 * rest of the response +	 */ +	if(resp->flags & TYPHOON_RESP_ERROR) +		err = -EIO; + +out: +	if(tp->awaiting_resp) { +		tp->awaiting_resp = 0; +		smp_wmb(); + +		/* Ugh. If a response was added to the ring between +		 * the call to typhoon_process_response() and the clearing +		 * of tp->awaiting_resp, we could have missed the interrupt +		 * and it could hang in the ring an indeterminate amount of +		 * time. So, check for it, and interrupt ourselves if this +		 * is the case. +		 */ +		if(indexes->respCleared != indexes->respReady) +			iowrite32(1, tp->ioaddr + TYPHOON_REG_SELF_INTERRUPT); +	} + +	spin_unlock(&tp->command_lock); +	return err; +} + +static inline void +typhoon_tso_fill(struct sk_buff *skb, struct transmit_ring *txRing, +			u32 ring_dma) +{ +	struct tcpopt_desc *tcpd; +	u32 tcpd_offset = ring_dma; + +	tcpd = (struct tcpopt_desc *) (txRing->ringBase + txRing->lastWrite); +	tcpd_offset += txRing->lastWrite; +	tcpd_offset += offsetof(struct tcpopt_desc, bytesTx); +	typhoon_inc_tx_index(&txRing->lastWrite, 1); + +	tcpd->flags = TYPHOON_OPT_DESC | TYPHOON_OPT_TCP_SEG; +	tcpd->numDesc = 1; +	tcpd->mss_flags = cpu_to_le16(skb_tso_size(skb)); +	tcpd->mss_flags |= TYPHOON_TSO_FIRST | TYPHOON_TSO_LAST; +	tcpd->respAddrLo = cpu_to_le32(tcpd_offset); +	tcpd->bytesTx = cpu_to_le32(skb->len); +	tcpd->status = 0; +} + +static netdev_tx_t +typhoon_start_tx(struct sk_buff *skb, struct net_device *dev) +{ +	struct typhoon *tp = netdev_priv(dev); +	struct transmit_ring *txRing; +	struct tx_desc *txd, *first_txd; +	dma_addr_t skb_dma; +	int numDesc; + +	/* we have two rings to choose from, but we only use txLo for now +	 * If we start using the Hi ring as well, we'll need to update +	 * typhoon_stop_runtime(), typhoon_interrupt(), typhoon_num_free_tx(), +	 * and TXHI_ENTRIES to match, as well as update the TSO code below +	 * to get the right DMA address +	 */ +	txRing = &tp->txLoRing; + +	/* We need one descriptor for each fragment of the sk_buff, plus the +	 * one for the ->data area of it. +	 * +	 * The docs say a maximum of 16 fragment descriptors per TCP option +	 * descriptor, then make a new packet descriptor and option descriptor +	 * for the next 16 fragments. The engineers say just an option +	 * descriptor is needed. I've tested up to 26 fragments with a single +	 * packet descriptor/option descriptor combo, so I use that for now. +	 * +	 * If problems develop with TSO, check this first. +	 */ +	numDesc = skb_shinfo(skb)->nr_frags + 1; +	if (skb_is_gso(skb)) +		numDesc++; + +	/* When checking for free space in the ring, we need to also +	 * account for the initial Tx descriptor, and we always must leave +	 * at least one descriptor unused in the ring so that it doesn't +	 * wrap and look empty. +	 * +	 * The only time we should loop here is when we hit the race +	 * between marking the queue awake and updating the cleared index. +	 * Just loop and it will appear. This comes from the acenic driver. +	 */ +	while(unlikely(typhoon_num_free_tx(txRing) < (numDesc + 2))) +		smp_rmb(); + +	first_txd = (struct tx_desc *) (txRing->ringBase + txRing->lastWrite); +	typhoon_inc_tx_index(&txRing->lastWrite, 1); + +	first_txd->flags = TYPHOON_TX_DESC | TYPHOON_DESC_VALID; +	first_txd->numDesc = 0; +	first_txd->len = 0; +	first_txd->tx_addr = (u64)((unsigned long) skb); +	first_txd->processFlags = 0; + +	if(skb->ip_summed == CHECKSUM_PARTIAL) { +		/* The 3XP will figure out if this is UDP/TCP */ +		first_txd->processFlags |= TYPHOON_TX_PF_TCP_CHKSUM; +		first_txd->processFlags |= TYPHOON_TX_PF_UDP_CHKSUM; +		first_txd->processFlags |= TYPHOON_TX_PF_IP_CHKSUM; +	} + +	if(vlan_tx_tag_present(skb)) { +		first_txd->processFlags |= +		    TYPHOON_TX_PF_INSERT_VLAN | TYPHOON_TX_PF_VLAN_PRIORITY; +		first_txd->processFlags |= +		    cpu_to_le32(htons(vlan_tx_tag_get(skb)) << +				TYPHOON_TX_PF_VLAN_TAG_SHIFT); +	} + +	if (skb_is_gso(skb)) { +		first_txd->processFlags |= TYPHOON_TX_PF_TCP_SEGMENT; +		first_txd->numDesc++; + +		typhoon_tso_fill(skb, txRing, tp->txlo_dma_addr); +	} + +	txd = (struct tx_desc *) (txRing->ringBase + txRing->lastWrite); +	typhoon_inc_tx_index(&txRing->lastWrite, 1); + +	/* No need to worry about padding packet -- the firmware pads +	 * it with zeros to ETH_ZLEN for us. +	 */ +	if(skb_shinfo(skb)->nr_frags == 0) { +		skb_dma = pci_map_single(tp->tx_pdev, skb->data, skb->len, +				       PCI_DMA_TODEVICE); +		txd->flags = TYPHOON_FRAG_DESC | TYPHOON_DESC_VALID; +		txd->len = cpu_to_le16(skb->len); +		txd->frag.addr = cpu_to_le32(skb_dma); +		txd->frag.addrHi = 0; +		first_txd->numDesc++; +	} else { +		int i, len; + +		len = skb_headlen(skb); +		skb_dma = pci_map_single(tp->tx_pdev, skb->data, len, +				         PCI_DMA_TODEVICE); +		txd->flags = TYPHOON_FRAG_DESC | TYPHOON_DESC_VALID; +		txd->len = cpu_to_le16(len); +		txd->frag.addr = cpu_to_le32(skb_dma); +		txd->frag.addrHi = 0; +		first_txd->numDesc++; + +		for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { +			const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; +			void *frag_addr; + +			txd = (struct tx_desc *) (txRing->ringBase + +						txRing->lastWrite); +			typhoon_inc_tx_index(&txRing->lastWrite, 1); + +			len = skb_frag_size(frag); +			frag_addr = skb_frag_address(frag); +			skb_dma = pci_map_single(tp->tx_pdev, frag_addr, len, +					 PCI_DMA_TODEVICE); +			txd->flags = TYPHOON_FRAG_DESC | TYPHOON_DESC_VALID; +			txd->len = cpu_to_le16(len); +			txd->frag.addr = cpu_to_le32(skb_dma); +			txd->frag.addrHi = 0; +			first_txd->numDesc++; +		} +	} + +	/* Kick the 3XP +	 */ +	wmb(); +	iowrite32(txRing->lastWrite, tp->tx_ioaddr + txRing->writeRegister); + +	/* If we don't have room to put the worst case packet on the +	 * queue, then we must stop the queue. We need 2 extra +	 * descriptors -- one to prevent ring wrap, and one for the +	 * Tx header. +	 */ +	numDesc = MAX_SKB_FRAGS + TSO_NUM_DESCRIPTORS + 1; + +	if(typhoon_num_free_tx(txRing) < (numDesc + 2)) { +		netif_stop_queue(dev); + +		/* A Tx complete IRQ could have gotten between, making +		 * the ring free again. Only need to recheck here, since +		 * Tx is serialized. +		 */ +		if(typhoon_num_free_tx(txRing) >= (numDesc + 2)) +			netif_wake_queue(dev); +	} + +	return NETDEV_TX_OK; +} + +static void +typhoon_set_rx_mode(struct net_device *dev) +{ +	struct typhoon *tp = netdev_priv(dev); +	struct cmd_desc xp_cmd; +	u32 mc_filter[2]; +	__le16 filter; + +	filter = TYPHOON_RX_FILTER_DIRECTED | TYPHOON_RX_FILTER_BROADCAST; +	if(dev->flags & IFF_PROMISC) { +		filter |= TYPHOON_RX_FILTER_PROMISCOUS; +	} else if ((netdev_mc_count(dev) > multicast_filter_limit) || +		  (dev->flags & IFF_ALLMULTI)) { +		/* Too many to match, or accept all multicasts. */ +		filter |= TYPHOON_RX_FILTER_ALL_MCAST; +	} else if (!netdev_mc_empty(dev)) { +		struct netdev_hw_addr *ha; + +		memset(mc_filter, 0, sizeof(mc_filter)); +		netdev_for_each_mc_addr(ha, dev) { +			int bit = ether_crc(ETH_ALEN, ha->addr) & 0x3f; +			mc_filter[bit >> 5] |= 1 << (bit & 0x1f); +		} + +		INIT_COMMAND_NO_RESPONSE(&xp_cmd, +					 TYPHOON_CMD_SET_MULTICAST_HASH); +		xp_cmd.parm1 = TYPHOON_MCAST_HASH_SET; +		xp_cmd.parm2 = cpu_to_le32(mc_filter[0]); +		xp_cmd.parm3 = cpu_to_le32(mc_filter[1]); +		typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); + +		filter |= TYPHOON_RX_FILTER_MCAST_HASH; +	} + +	INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_RX_FILTER); +	xp_cmd.parm1 = filter; +	typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +} + +static int +typhoon_do_get_stats(struct typhoon *tp) +{ +	struct net_device_stats *stats = &tp->stats; +	struct net_device_stats *saved = &tp->stats_saved; +	struct cmd_desc xp_cmd; +	struct resp_desc xp_resp[7]; +	struct stats_resp *s = (struct stats_resp *) xp_resp; +	int err; + +	INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_READ_STATS); +	err = typhoon_issue_command(tp, 1, &xp_cmd, 7, xp_resp); +	if(err < 0) +		return err; + +	/* 3Com's Linux driver uses txMultipleCollisions as it's +	 * collisions value, but there is some other collision info as well... +	 * +	 * The extra status reported would be a good candidate for +	 * ethtool_ops->get_{strings,stats}() +	 */ +	stats->tx_packets = le32_to_cpu(s->txPackets) + +			saved->tx_packets; +	stats->tx_bytes = le64_to_cpu(s->txBytes) + +			saved->tx_bytes; +	stats->tx_errors = le32_to_cpu(s->txCarrierLost) + +			saved->tx_errors; +	stats->tx_carrier_errors = le32_to_cpu(s->txCarrierLost) + +			saved->tx_carrier_errors; +	stats->collisions = le32_to_cpu(s->txMultipleCollisions) + +			saved->collisions; +	stats->rx_packets = le32_to_cpu(s->rxPacketsGood) + +			saved->rx_packets; +	stats->rx_bytes = le64_to_cpu(s->rxBytesGood) + +			saved->rx_bytes; +	stats->rx_fifo_errors = le32_to_cpu(s->rxFifoOverruns) + +			saved->rx_fifo_errors; +	stats->rx_errors = le32_to_cpu(s->rxFifoOverruns) + +			le32_to_cpu(s->BadSSD) + le32_to_cpu(s->rxCrcErrors) + +			saved->rx_errors; +	stats->rx_crc_errors = le32_to_cpu(s->rxCrcErrors) + +			saved->rx_crc_errors; +	stats->rx_length_errors = le32_to_cpu(s->rxOversized) + +			saved->rx_length_errors; +	tp->speed = (s->linkStatus & TYPHOON_LINK_100MBPS) ? +			SPEED_100 : SPEED_10; +	tp->duplex = (s->linkStatus & TYPHOON_LINK_FULL_DUPLEX) ? +			DUPLEX_FULL : DUPLEX_HALF; + +	return 0; +} + +static struct net_device_stats * +typhoon_get_stats(struct net_device *dev) +{ +	struct typhoon *tp = netdev_priv(dev); +	struct net_device_stats *stats = &tp->stats; +	struct net_device_stats *saved = &tp->stats_saved; + +	smp_rmb(); +	if(tp->card_state == Sleeping) +		return saved; + +	if(typhoon_do_get_stats(tp) < 0) { +		netdev_err(dev, "error getting stats\n"); +		return saved; +	} + +	return stats; +} + +static void +typhoon_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ +	struct typhoon *tp = netdev_priv(dev); +	struct pci_dev *pci_dev = tp->pdev; +	struct cmd_desc xp_cmd; +	struct resp_desc xp_resp[3]; + +	smp_rmb(); +	if(tp->card_state == Sleeping) { +		strlcpy(info->fw_version, "Sleep image", +			sizeof(info->fw_version)); +	} else { +		INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_READ_VERSIONS); +		if(typhoon_issue_command(tp, 1, &xp_cmd, 3, xp_resp) < 0) { +			strlcpy(info->fw_version, "Unknown runtime", +				sizeof(info->fw_version)); +		} else { +			u32 sleep_ver = le32_to_cpu(xp_resp[0].parm2); +			snprintf(info->fw_version, sizeof(info->fw_version), +				"%02x.%03x.%03x", sleep_ver >> 24, +				(sleep_ver >> 12) & 0xfff, sleep_ver & 0xfff); +		} +	} + +	strlcpy(info->driver, KBUILD_MODNAME, sizeof(info->driver)); +	strlcpy(info->bus_info, pci_name(pci_dev), sizeof(info->bus_info)); +} + +static int +typhoon_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ +	struct typhoon *tp = netdev_priv(dev); + +	cmd->supported = SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full | +				SUPPORTED_Autoneg; + +	switch (tp->xcvr_select) { +	case TYPHOON_XCVR_10HALF: +		cmd->advertising = ADVERTISED_10baseT_Half; +		break; +	case TYPHOON_XCVR_10FULL: +		cmd->advertising = ADVERTISED_10baseT_Full; +		break; +	case TYPHOON_XCVR_100HALF: +		cmd->advertising = ADVERTISED_100baseT_Half; +		break; +	case TYPHOON_XCVR_100FULL: +		cmd->advertising = ADVERTISED_100baseT_Full; +		break; +	case TYPHOON_XCVR_AUTONEG: +		cmd->advertising = ADVERTISED_10baseT_Half | +					    ADVERTISED_10baseT_Full | +					    ADVERTISED_100baseT_Half | +					    ADVERTISED_100baseT_Full | +					    ADVERTISED_Autoneg; +		break; +	} + +	if(tp->capabilities & TYPHOON_FIBER) { +		cmd->supported |= SUPPORTED_FIBRE; +		cmd->advertising |= ADVERTISED_FIBRE; +		cmd->port = PORT_FIBRE; +	} else { +		cmd->supported |= SUPPORTED_10baseT_Half | +		    			SUPPORTED_10baseT_Full | +					SUPPORTED_TP; +		cmd->advertising |= ADVERTISED_TP; +		cmd->port = PORT_TP; +	} + +	/* need to get stats to make these link speed/duplex valid */ +	typhoon_do_get_stats(tp); +	ethtool_cmd_speed_set(cmd, tp->speed); +	cmd->duplex = tp->duplex; +	cmd->phy_address = 0; +	cmd->transceiver = XCVR_INTERNAL; +	if(tp->xcvr_select == TYPHOON_XCVR_AUTONEG) +		cmd->autoneg = AUTONEG_ENABLE; +	else +		cmd->autoneg = AUTONEG_DISABLE; +	cmd->maxtxpkt = 1; +	cmd->maxrxpkt = 1; + +	return 0; +} + +static int +typhoon_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ +	struct typhoon *tp = netdev_priv(dev); +	u32 speed = ethtool_cmd_speed(cmd); +	struct cmd_desc xp_cmd; +	__le16 xcvr; +	int err; + +	err = -EINVAL; +	if (cmd->autoneg == AUTONEG_ENABLE) { +		xcvr = TYPHOON_XCVR_AUTONEG; +	} else { +		if (cmd->duplex == DUPLEX_HALF) { +			if (speed == SPEED_10) +				xcvr = TYPHOON_XCVR_10HALF; +			else if (speed == SPEED_100) +				xcvr = TYPHOON_XCVR_100HALF; +			else +				goto out; +		} else if (cmd->duplex == DUPLEX_FULL) { +			if (speed == SPEED_10) +				xcvr = TYPHOON_XCVR_10FULL; +			else if (speed == SPEED_100) +				xcvr = TYPHOON_XCVR_100FULL; +			else +				goto out; +		} else +			goto out; +	} + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_XCVR_SELECT); +	xp_cmd.parm1 = xcvr; +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) +		goto out; + +	tp->xcvr_select = xcvr; +	if(cmd->autoneg == AUTONEG_ENABLE) { +		tp->speed = 0xff;	/* invalid */ +		tp->duplex = 0xff;	/* invalid */ +	} else { +		tp->speed = speed; +		tp->duplex = cmd->duplex; +	} + +out: +	return err; +} + +static void +typhoon_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ +	struct typhoon *tp = netdev_priv(dev); + +	wol->supported = WAKE_PHY | WAKE_MAGIC; +	wol->wolopts = 0; +	if(tp->wol_events & TYPHOON_WAKE_LINK_EVENT) +		wol->wolopts |= WAKE_PHY; +	if(tp->wol_events & TYPHOON_WAKE_MAGIC_PKT) +		wol->wolopts |= WAKE_MAGIC; +	memset(&wol->sopass, 0, sizeof(wol->sopass)); +} + +static int +typhoon_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) +{ +	struct typhoon *tp = netdev_priv(dev); + +	if(wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC)) +		return -EINVAL; + +	tp->wol_events = 0; +	if(wol->wolopts & WAKE_PHY) +		tp->wol_events |= TYPHOON_WAKE_LINK_EVENT; +	if(wol->wolopts & WAKE_MAGIC) +		tp->wol_events |= TYPHOON_WAKE_MAGIC_PKT; + +	return 0; +} + +static void +typhoon_get_ringparam(struct net_device *dev, struct ethtool_ringparam *ering) +{ +	ering->rx_max_pending = RXENT_ENTRIES; +	ering->tx_max_pending = TXLO_ENTRIES - 1; + +	ering->rx_pending = RXENT_ENTRIES; +	ering->tx_pending = TXLO_ENTRIES - 1; +} + +static const struct ethtool_ops typhoon_ethtool_ops = { +	.get_settings		= typhoon_get_settings, +	.set_settings		= typhoon_set_settings, +	.get_drvinfo		= typhoon_get_drvinfo, +	.get_wol		= typhoon_get_wol, +	.set_wol		= typhoon_set_wol, +	.get_link		= ethtool_op_get_link, +	.get_ringparam		= typhoon_get_ringparam, +}; + +static int +typhoon_wait_interrupt(void __iomem *ioaddr) +{ +	int i, err = 0; + +	for(i = 0; i < TYPHOON_WAIT_TIMEOUT; i++) { +		if(ioread32(ioaddr + TYPHOON_REG_INTR_STATUS) & +		   TYPHOON_INTR_BOOTCMD) +			goto out; +		udelay(TYPHOON_UDELAY); +	} + +	err = -ETIMEDOUT; + +out: +	iowrite32(TYPHOON_INTR_BOOTCMD, ioaddr + TYPHOON_REG_INTR_STATUS); +	return err; +} + +#define shared_offset(x)	offsetof(struct typhoon_shared, x) + +static void +typhoon_init_interface(struct typhoon *tp) +{ +	struct typhoon_interface *iface = &tp->shared->iface; +	dma_addr_t shared_dma; + +	memset(tp->shared, 0, sizeof(struct typhoon_shared)); + +	/* The *Hi members of iface are all init'd to zero by the memset(). +	 */ +	shared_dma = tp->shared_dma + shared_offset(indexes); +	iface->ringIndex = cpu_to_le32(shared_dma); + +	shared_dma = tp->shared_dma + shared_offset(txLo); +	iface->txLoAddr = cpu_to_le32(shared_dma); +	iface->txLoSize = cpu_to_le32(TXLO_ENTRIES * sizeof(struct tx_desc)); + +	shared_dma = tp->shared_dma + shared_offset(txHi); +	iface->txHiAddr = cpu_to_le32(shared_dma); +	iface->txHiSize = cpu_to_le32(TXHI_ENTRIES * sizeof(struct tx_desc)); + +	shared_dma = tp->shared_dma + shared_offset(rxBuff); +	iface->rxBuffAddr = cpu_to_le32(shared_dma); +	iface->rxBuffSize = cpu_to_le32(RXFREE_ENTRIES * +					sizeof(struct rx_free)); + +	shared_dma = tp->shared_dma + shared_offset(rxLo); +	iface->rxLoAddr = cpu_to_le32(shared_dma); +	iface->rxLoSize = cpu_to_le32(RX_ENTRIES * sizeof(struct rx_desc)); + +	shared_dma = tp->shared_dma + shared_offset(rxHi); +	iface->rxHiAddr = cpu_to_le32(shared_dma); +	iface->rxHiSize = cpu_to_le32(RX_ENTRIES * sizeof(struct rx_desc)); + +	shared_dma = tp->shared_dma + shared_offset(cmd); +	iface->cmdAddr = cpu_to_le32(shared_dma); +	iface->cmdSize = cpu_to_le32(COMMAND_RING_SIZE); + +	shared_dma = tp->shared_dma + shared_offset(resp); +	iface->respAddr = cpu_to_le32(shared_dma); +	iface->respSize = cpu_to_le32(RESPONSE_RING_SIZE); + +	shared_dma = tp->shared_dma + shared_offset(zeroWord); +	iface->zeroAddr = cpu_to_le32(shared_dma); + +	tp->indexes = &tp->shared->indexes; +	tp->txLoRing.ringBase = (u8 *) tp->shared->txLo; +	tp->txHiRing.ringBase = (u8 *) tp->shared->txHi; +	tp->rxLoRing.ringBase = (u8 *) tp->shared->rxLo; +	tp->rxHiRing.ringBase = (u8 *) tp->shared->rxHi; +	tp->rxBuffRing.ringBase = (u8 *) tp->shared->rxBuff; +	tp->cmdRing.ringBase = (u8 *) tp->shared->cmd; +	tp->respRing.ringBase = (u8 *) tp->shared->resp; + +	tp->txLoRing.writeRegister = TYPHOON_REG_TX_LO_READY; +	tp->txHiRing.writeRegister = TYPHOON_REG_TX_HI_READY; + +	tp->txlo_dma_addr = le32_to_cpu(iface->txLoAddr); +	tp->card_state = Sleeping; + +	tp->offload = TYPHOON_OFFLOAD_IP_CHKSUM | TYPHOON_OFFLOAD_TCP_CHKSUM; +	tp->offload |= TYPHOON_OFFLOAD_UDP_CHKSUM | TSO_OFFLOAD_ON; +	tp->offload |= TYPHOON_OFFLOAD_VLAN; + +	spin_lock_init(&tp->command_lock); + +	/* Force the writes to the shared memory area out before continuing. */ +	wmb(); +} + +static void +typhoon_init_rings(struct typhoon *tp) +{ +	memset(tp->indexes, 0, sizeof(struct typhoon_indexes)); + +	tp->txLoRing.lastWrite = 0; +	tp->txHiRing.lastWrite = 0; +	tp->rxLoRing.lastWrite = 0; +	tp->rxHiRing.lastWrite = 0; +	tp->rxBuffRing.lastWrite = 0; +	tp->cmdRing.lastWrite = 0; +	tp->respRing.lastWrite = 0; + +	tp->txLoRing.lastRead = 0; +	tp->txHiRing.lastRead = 0; +} + +static const struct firmware *typhoon_fw; + +static int +typhoon_request_firmware(struct typhoon *tp) +{ +	const struct typhoon_file_header *fHdr; +	const struct typhoon_section_header *sHdr; +	const u8 *image_data; +	u32 numSections; +	u32 section_len; +	u32 remaining; +	int err; + +	if (typhoon_fw) +		return 0; + +	err = request_firmware(&typhoon_fw, FIRMWARE_NAME, &tp->pdev->dev); +	if (err) { +		netdev_err(tp->dev, "Failed to load firmware \"%s\"\n", +			   FIRMWARE_NAME); +		return err; +	} + +	image_data = (u8 *) typhoon_fw->data; +	remaining = typhoon_fw->size; +	if (remaining < sizeof(struct typhoon_file_header)) +		goto invalid_fw; + +	fHdr = (struct typhoon_file_header *) image_data; +	if (memcmp(fHdr->tag, "TYPHOON", 8)) +		goto invalid_fw; + +	numSections = le32_to_cpu(fHdr->numSections); +	image_data += sizeof(struct typhoon_file_header); +	remaining -= sizeof(struct typhoon_file_header); + +	while (numSections--) { +		if (remaining < sizeof(struct typhoon_section_header)) +			goto invalid_fw; + +		sHdr = (struct typhoon_section_header *) image_data; +		image_data += sizeof(struct typhoon_section_header); +		section_len = le32_to_cpu(sHdr->len); + +		if (remaining < section_len) +			goto invalid_fw; + +		image_data += section_len; +		remaining -= section_len; +	} + +	return 0; + +invalid_fw: +	netdev_err(tp->dev, "Invalid firmware image\n"); +	release_firmware(typhoon_fw); +	typhoon_fw = NULL; +	return -EINVAL; +} + +static int +typhoon_download_firmware(struct typhoon *tp) +{ +	void __iomem *ioaddr = tp->ioaddr; +	struct pci_dev *pdev = tp->pdev; +	const struct typhoon_file_header *fHdr; +	const struct typhoon_section_header *sHdr; +	const u8 *image_data; +	void *dpage; +	dma_addr_t dpage_dma; +	__sum16 csum; +	u32 irqEnabled; +	u32 irqMasked; +	u32 numSections; +	u32 section_len; +	u32 len; +	u32 load_addr; +	u32 hmac; +	int i; +	int err; + +	image_data = (u8 *) typhoon_fw->data; +	fHdr = (struct typhoon_file_header *) image_data; + +	/* Cannot just map the firmware image using pci_map_single() as +	 * the firmware is vmalloc()'d and may not be physically contiguous, +	 * so we allocate some consistent memory to copy the sections into. +	 */ +	err = -ENOMEM; +	dpage = pci_alloc_consistent(pdev, PAGE_SIZE, &dpage_dma); +	if(!dpage) { +		netdev_err(tp->dev, "no DMA mem for firmware\n"); +		goto err_out; +	} + +	irqEnabled = ioread32(ioaddr + TYPHOON_REG_INTR_ENABLE); +	iowrite32(irqEnabled | TYPHOON_INTR_BOOTCMD, +	       ioaddr + TYPHOON_REG_INTR_ENABLE); +	irqMasked = ioread32(ioaddr + TYPHOON_REG_INTR_MASK); +	iowrite32(irqMasked | TYPHOON_INTR_BOOTCMD, +	       ioaddr + TYPHOON_REG_INTR_MASK); + +	err = -ETIMEDOUT; +	if(typhoon_wait_status(ioaddr, TYPHOON_STATUS_WAITING_FOR_HOST) < 0) { +		netdev_err(tp->dev, "card ready timeout\n"); +		goto err_out_irq; +	} + +	numSections = le32_to_cpu(fHdr->numSections); +	load_addr = le32_to_cpu(fHdr->startAddr); + +	iowrite32(TYPHOON_INTR_BOOTCMD, ioaddr + TYPHOON_REG_INTR_STATUS); +	iowrite32(load_addr, ioaddr + TYPHOON_REG_DOWNLOAD_BOOT_ADDR); +	hmac = le32_to_cpu(fHdr->hmacDigest[0]); +	iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_0); +	hmac = le32_to_cpu(fHdr->hmacDigest[1]); +	iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_1); +	hmac = le32_to_cpu(fHdr->hmacDigest[2]); +	iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_2); +	hmac = le32_to_cpu(fHdr->hmacDigest[3]); +	iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_3); +	hmac = le32_to_cpu(fHdr->hmacDigest[4]); +	iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_4); +	typhoon_post_pci_writes(ioaddr); +	iowrite32(TYPHOON_BOOTCMD_RUNTIME_IMAGE, ioaddr + TYPHOON_REG_COMMAND); + +	image_data += sizeof(struct typhoon_file_header); + +	/* The ioread32() in typhoon_wait_interrupt() will force the +	 * last write to the command register to post, so +	 * we don't need a typhoon_post_pci_writes() after it. +	 */ +	for(i = 0; i < numSections; i++) { +		sHdr = (struct typhoon_section_header *) image_data; +		image_data += sizeof(struct typhoon_section_header); +		load_addr = le32_to_cpu(sHdr->startAddr); +		section_len = le32_to_cpu(sHdr->len); + +		while(section_len) { +			len = min_t(u32, section_len, PAGE_SIZE); + +			if(typhoon_wait_interrupt(ioaddr) < 0 || +			   ioread32(ioaddr + TYPHOON_REG_STATUS) != +			   TYPHOON_STATUS_WAITING_FOR_SEGMENT) { +				netdev_err(tp->dev, "segment ready timeout\n"); +				goto err_out_irq; +			} + +			/* Do an pseudo IPv4 checksum on the data -- first +			 * need to convert each u16 to cpu order before +			 * summing. Fortunately, due to the properties of +			 * the checksum, we can do this once, at the end. +			 */ +			csum = csum_fold(csum_partial_copy_nocheck(image_data, +								   dpage, len, +								   0)); + +			iowrite32(len, ioaddr + TYPHOON_REG_BOOT_LENGTH); +			iowrite32(le16_to_cpu((__force __le16)csum), +					ioaddr + TYPHOON_REG_BOOT_CHECKSUM); +			iowrite32(load_addr, +					ioaddr + TYPHOON_REG_BOOT_DEST_ADDR); +			iowrite32(0, ioaddr + TYPHOON_REG_BOOT_DATA_HI); +			iowrite32(dpage_dma, ioaddr + TYPHOON_REG_BOOT_DATA_LO); +			typhoon_post_pci_writes(ioaddr); +			iowrite32(TYPHOON_BOOTCMD_SEG_AVAILABLE, +					ioaddr + TYPHOON_REG_COMMAND); + +			image_data += len; +			load_addr += len; +			section_len -= len; +		} +	} + +	if(typhoon_wait_interrupt(ioaddr) < 0 || +	   ioread32(ioaddr + TYPHOON_REG_STATUS) != +	   TYPHOON_STATUS_WAITING_FOR_SEGMENT) { +		netdev_err(tp->dev, "final segment ready timeout\n"); +		goto err_out_irq; +	} + +	iowrite32(TYPHOON_BOOTCMD_DNLD_COMPLETE, ioaddr + TYPHOON_REG_COMMAND); + +	if(typhoon_wait_status(ioaddr, TYPHOON_STATUS_WAITING_FOR_BOOT) < 0) { +		netdev_err(tp->dev, "boot ready timeout, status 0x%0x\n", +			   ioread32(ioaddr + TYPHOON_REG_STATUS)); +		goto err_out_irq; +	} + +	err = 0; + +err_out_irq: +	iowrite32(irqMasked, ioaddr + TYPHOON_REG_INTR_MASK); +	iowrite32(irqEnabled, ioaddr + TYPHOON_REG_INTR_ENABLE); + +	pci_free_consistent(pdev, PAGE_SIZE, dpage, dpage_dma); + +err_out: +	return err; +} + +static int +typhoon_boot_3XP(struct typhoon *tp, u32 initial_status) +{ +	void __iomem *ioaddr = tp->ioaddr; + +	if(typhoon_wait_status(ioaddr, initial_status) < 0) { +		netdev_err(tp->dev, "boot ready timeout\n"); +		goto out_timeout; +	} + +	iowrite32(0, ioaddr + TYPHOON_REG_BOOT_RECORD_ADDR_HI); +	iowrite32(tp->shared_dma, ioaddr + TYPHOON_REG_BOOT_RECORD_ADDR_LO); +	typhoon_post_pci_writes(ioaddr); +	iowrite32(TYPHOON_BOOTCMD_REG_BOOT_RECORD, +				ioaddr + TYPHOON_REG_COMMAND); + +	if(typhoon_wait_status(ioaddr, TYPHOON_STATUS_RUNNING) < 0) { +		netdev_err(tp->dev, "boot finish timeout (status 0x%x)\n", +			   ioread32(ioaddr + TYPHOON_REG_STATUS)); +		goto out_timeout; +	} + +	/* Clear the Transmit and Command ready registers +	 */ +	iowrite32(0, ioaddr + TYPHOON_REG_TX_HI_READY); +	iowrite32(0, ioaddr + TYPHOON_REG_CMD_READY); +	iowrite32(0, ioaddr + TYPHOON_REG_TX_LO_READY); +	typhoon_post_pci_writes(ioaddr); +	iowrite32(TYPHOON_BOOTCMD_BOOT, ioaddr + TYPHOON_REG_COMMAND); + +	return 0; + +out_timeout: +	return -ETIMEDOUT; +} + +static u32 +typhoon_clean_tx(struct typhoon *tp, struct transmit_ring *txRing, +			volatile __le32 * index) +{ +	u32 lastRead = txRing->lastRead; +	struct tx_desc *tx; +	dma_addr_t skb_dma; +	int dma_len; +	int type; + +	while(lastRead != le32_to_cpu(*index)) { +		tx = (struct tx_desc *) (txRing->ringBase + lastRead); +		type = tx->flags & TYPHOON_TYPE_MASK; + +		if(type == TYPHOON_TX_DESC) { +			/* This tx_desc describes a packet. +			 */ +			unsigned long ptr = tx->tx_addr; +			struct sk_buff *skb = (struct sk_buff *) ptr; +			dev_kfree_skb_irq(skb); +		} else if(type == TYPHOON_FRAG_DESC) { +			/* This tx_desc describes a memory mapping. Free it. +			 */ +			skb_dma = (dma_addr_t) le32_to_cpu(tx->frag.addr); +			dma_len = le16_to_cpu(tx->len); +			pci_unmap_single(tp->pdev, skb_dma, dma_len, +				       PCI_DMA_TODEVICE); +		} + +		tx->flags = 0; +		typhoon_inc_tx_index(&lastRead, 1); +	} + +	return lastRead; +} + +static void +typhoon_tx_complete(struct typhoon *tp, struct transmit_ring *txRing, +			volatile __le32 * index) +{ +	u32 lastRead; +	int numDesc = MAX_SKB_FRAGS + 1; + +	/* This will need changing if we start to use the Hi Tx ring. */ +	lastRead = typhoon_clean_tx(tp, txRing, index); +	if(netif_queue_stopped(tp->dev) && typhoon_num_free(txRing->lastWrite, +				lastRead, TXLO_ENTRIES) > (numDesc + 2)) +		netif_wake_queue(tp->dev); + +	txRing->lastRead = lastRead; +	smp_wmb(); +} + +static void +typhoon_recycle_rx_skb(struct typhoon *tp, u32 idx) +{ +	struct typhoon_indexes *indexes = tp->indexes; +	struct rxbuff_ent *rxb = &tp->rxbuffers[idx]; +	struct basic_ring *ring = &tp->rxBuffRing; +	struct rx_free *r; + +	if((ring->lastWrite + sizeof(*r)) % (RXFREE_ENTRIES * sizeof(*r)) == +				le32_to_cpu(indexes->rxBuffCleared)) { +		/* no room in ring, just drop the skb +		 */ +		dev_kfree_skb_any(rxb->skb); +		rxb->skb = NULL; +		return; +	} + +	r = (struct rx_free *) (ring->ringBase + ring->lastWrite); +	typhoon_inc_rxfree_index(&ring->lastWrite, 1); +	r->virtAddr = idx; +	r->physAddr = cpu_to_le32(rxb->dma_addr); + +	/* Tell the card about it */ +	wmb(); +	indexes->rxBuffReady = cpu_to_le32(ring->lastWrite); +} + +static int +typhoon_alloc_rx_skb(struct typhoon *tp, u32 idx) +{ +	struct typhoon_indexes *indexes = tp->indexes; +	struct rxbuff_ent *rxb = &tp->rxbuffers[idx]; +	struct basic_ring *ring = &tp->rxBuffRing; +	struct rx_free *r; +	struct sk_buff *skb; +	dma_addr_t dma_addr; + +	rxb->skb = NULL; + +	if((ring->lastWrite + sizeof(*r)) % (RXFREE_ENTRIES * sizeof(*r)) == +				le32_to_cpu(indexes->rxBuffCleared)) +		return -ENOMEM; + +	skb = netdev_alloc_skb(tp->dev, PKT_BUF_SZ); +	if(!skb) +		return -ENOMEM; + +#if 0 +	/* Please, 3com, fix the firmware to allow DMA to a unaligned +	 * address! Pretty please? +	 */ +	skb_reserve(skb, 2); +#endif + +	dma_addr = pci_map_single(tp->pdev, skb->data, +				  PKT_BUF_SZ, PCI_DMA_FROMDEVICE); + +	/* Since no card does 64 bit DAC, the high bits will never +	 * change from zero. +	 */ +	r = (struct rx_free *) (ring->ringBase + ring->lastWrite); +	typhoon_inc_rxfree_index(&ring->lastWrite, 1); +	r->virtAddr = idx; +	r->physAddr = cpu_to_le32(dma_addr); +	rxb->skb = skb; +	rxb->dma_addr = dma_addr; + +	/* Tell the card about it */ +	wmb(); +	indexes->rxBuffReady = cpu_to_le32(ring->lastWrite); +	return 0; +} + +static int +typhoon_rx(struct typhoon *tp, struct basic_ring *rxRing, volatile __le32 * ready, +	   volatile __le32 * cleared, int budget) +{ +	struct rx_desc *rx; +	struct sk_buff *skb, *new_skb; +	struct rxbuff_ent *rxb; +	dma_addr_t dma_addr; +	u32 local_ready; +	u32 rxaddr; +	int pkt_len; +	u32 idx; +	__le32 csum_bits; +	int received; + +	received = 0; +	local_ready = le32_to_cpu(*ready); +	rxaddr = le32_to_cpu(*cleared); +	while(rxaddr != local_ready && budget > 0) { +		rx = (struct rx_desc *) (rxRing->ringBase + rxaddr); +		idx = rx->addr; +		rxb = &tp->rxbuffers[idx]; +		skb = rxb->skb; +		dma_addr = rxb->dma_addr; + +		typhoon_inc_rx_index(&rxaddr, 1); + +		if(rx->flags & TYPHOON_RX_ERROR) { +			typhoon_recycle_rx_skb(tp, idx); +			continue; +		} + +		pkt_len = le16_to_cpu(rx->frameLen); + +		if(pkt_len < rx_copybreak && +		   (new_skb = netdev_alloc_skb(tp->dev, pkt_len + 2)) != NULL) { +			skb_reserve(new_skb, 2); +			pci_dma_sync_single_for_cpu(tp->pdev, dma_addr, +						    PKT_BUF_SZ, +						    PCI_DMA_FROMDEVICE); +			skb_copy_to_linear_data(new_skb, skb->data, pkt_len); +			pci_dma_sync_single_for_device(tp->pdev, dma_addr, +						       PKT_BUF_SZ, +						       PCI_DMA_FROMDEVICE); +			skb_put(new_skb, pkt_len); +			typhoon_recycle_rx_skb(tp, idx); +		} else { +			new_skb = skb; +			skb_put(new_skb, pkt_len); +			pci_unmap_single(tp->pdev, dma_addr, PKT_BUF_SZ, +				       PCI_DMA_FROMDEVICE); +			typhoon_alloc_rx_skb(tp, idx); +		} +		new_skb->protocol = eth_type_trans(new_skb, tp->dev); +		csum_bits = rx->rxStatus & (TYPHOON_RX_IP_CHK_GOOD | +			TYPHOON_RX_UDP_CHK_GOOD | TYPHOON_RX_TCP_CHK_GOOD); +		if(csum_bits == +		   (TYPHOON_RX_IP_CHK_GOOD | TYPHOON_RX_TCP_CHK_GOOD) || +		   csum_bits == +		   (TYPHOON_RX_IP_CHK_GOOD | TYPHOON_RX_UDP_CHK_GOOD)) { +			new_skb->ip_summed = CHECKSUM_UNNECESSARY; +		} else +			skb_checksum_none_assert(new_skb); + +		if (rx->rxStatus & TYPHOON_RX_VLAN) +			__vlan_hwaccel_put_tag(new_skb, htons(ETH_P_8021Q), +					       ntohl(rx->vlanTag) & 0xffff); +		netif_receive_skb(new_skb); + +		received++; +		budget--; +	} +	*cleared = cpu_to_le32(rxaddr); + +	return received; +} + +static void +typhoon_fill_free_ring(struct typhoon *tp) +{ +	u32 i; + +	for(i = 0; i < RXENT_ENTRIES; i++) { +		struct rxbuff_ent *rxb = &tp->rxbuffers[i]; +		if(rxb->skb) +			continue; +		if(typhoon_alloc_rx_skb(tp, i) < 0) +			break; +	} +} + +static int +typhoon_poll(struct napi_struct *napi, int budget) +{ +	struct typhoon *tp = container_of(napi, struct typhoon, napi); +	struct typhoon_indexes *indexes = tp->indexes; +	int work_done; + +	rmb(); +	if(!tp->awaiting_resp && indexes->respReady != indexes->respCleared) +			typhoon_process_response(tp, 0, NULL); + +	if(le32_to_cpu(indexes->txLoCleared) != tp->txLoRing.lastRead) +		typhoon_tx_complete(tp, &tp->txLoRing, &indexes->txLoCleared); + +	work_done = 0; + +	if(indexes->rxHiCleared != indexes->rxHiReady) { +		work_done += typhoon_rx(tp, &tp->rxHiRing, &indexes->rxHiReady, +			   		&indexes->rxHiCleared, budget); +	} + +	if(indexes->rxLoCleared != indexes->rxLoReady) { +		work_done += typhoon_rx(tp, &tp->rxLoRing, &indexes->rxLoReady, +					&indexes->rxLoCleared, budget - work_done); +	} + +	if(le32_to_cpu(indexes->rxBuffCleared) == tp->rxBuffRing.lastWrite) { +		/* rxBuff ring is empty, try to fill it. */ +		typhoon_fill_free_ring(tp); +	} + +	if (work_done < budget) { +		napi_complete(napi); +		iowrite32(TYPHOON_INTR_NONE, +				tp->ioaddr + TYPHOON_REG_INTR_MASK); +		typhoon_post_pci_writes(tp->ioaddr); +	} + +	return work_done; +} + +static irqreturn_t +typhoon_interrupt(int irq, void *dev_instance) +{ +	struct net_device *dev = dev_instance; +	struct typhoon *tp = netdev_priv(dev); +	void __iomem *ioaddr = tp->ioaddr; +	u32 intr_status; + +	intr_status = ioread32(ioaddr + TYPHOON_REG_INTR_STATUS); +	if(!(intr_status & TYPHOON_INTR_HOST_INT)) +		return IRQ_NONE; + +	iowrite32(intr_status, ioaddr + TYPHOON_REG_INTR_STATUS); + +	if (napi_schedule_prep(&tp->napi)) { +		iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK); +		typhoon_post_pci_writes(ioaddr); +		__napi_schedule(&tp->napi); +	} else { +		netdev_err(dev, "Error, poll already scheduled\n"); +	} +	return IRQ_HANDLED; +} + +static void +typhoon_free_rx_rings(struct typhoon *tp) +{ +	u32 i; + +	for(i = 0; i < RXENT_ENTRIES; i++) { +		struct rxbuff_ent *rxb = &tp->rxbuffers[i]; +		if(rxb->skb) { +			pci_unmap_single(tp->pdev, rxb->dma_addr, PKT_BUF_SZ, +				       PCI_DMA_FROMDEVICE); +			dev_kfree_skb(rxb->skb); +			rxb->skb = NULL; +		} +	} +} + +static int +typhoon_sleep(struct typhoon *tp, pci_power_t state, __le16 events) +{ +	struct pci_dev *pdev = tp->pdev; +	void __iomem *ioaddr = tp->ioaddr; +	struct cmd_desc xp_cmd; +	int err; + +	INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_ENABLE_WAKE_EVENTS); +	xp_cmd.parm1 = events; +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) { +		netdev_err(tp->dev, "typhoon_sleep(): wake events cmd err %d\n", +			   err); +		return err; +	} + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_GOTO_SLEEP); +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) { +		netdev_err(tp->dev, "typhoon_sleep(): sleep cmd err %d\n", err); +		return err; +	} + +	if(typhoon_wait_status(ioaddr, TYPHOON_STATUS_SLEEPING) < 0) +		return -ETIMEDOUT; + +	/* Since we cannot monitor the status of the link while sleeping, +	 * tell the world it went away. +	 */ +	netif_carrier_off(tp->dev); + +	pci_enable_wake(tp->pdev, state, 1); +	pci_disable_device(pdev); +	return pci_set_power_state(pdev, state); +} + +static int +typhoon_wakeup(struct typhoon *tp, int wait_type) +{ +	struct pci_dev *pdev = tp->pdev; +	void __iomem *ioaddr = tp->ioaddr; + +	pci_set_power_state(pdev, PCI_D0); +	pci_restore_state(pdev); + +	/* Post 2.x.x versions of the Sleep Image require a reset before +	 * we can download the Runtime Image. But let's not make users of +	 * the old firmware pay for the reset. +	 */ +	iowrite32(TYPHOON_BOOTCMD_WAKEUP, ioaddr + TYPHOON_REG_COMMAND); +	if(typhoon_wait_status(ioaddr, TYPHOON_STATUS_WAITING_FOR_HOST) < 0 || +			(tp->capabilities & TYPHOON_WAKEUP_NEEDS_RESET)) +		return typhoon_reset(ioaddr, wait_type); + +	return 0; +} + +static int +typhoon_start_runtime(struct typhoon *tp) +{ +	struct net_device *dev = tp->dev; +	void __iomem *ioaddr = tp->ioaddr; +	struct cmd_desc xp_cmd; +	int err; + +	typhoon_init_rings(tp); +	typhoon_fill_free_ring(tp); + +	err = typhoon_download_firmware(tp); +	if(err < 0) { +		netdev_err(tp->dev, "cannot load runtime on 3XP\n"); +		goto error_out; +	} + +	if(typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_BOOT) < 0) { +		netdev_err(tp->dev, "cannot boot 3XP\n"); +		err = -EIO; +		goto error_out; +	} + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_MAX_PKT_SIZE); +	xp_cmd.parm1 = cpu_to_le16(PKT_BUF_SZ); +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) +		goto error_out; + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_MAC_ADDRESS); +	xp_cmd.parm1 = cpu_to_le16(ntohs(*(__be16 *)&dev->dev_addr[0])); +	xp_cmd.parm2 = cpu_to_le32(ntohl(*(__be32 *)&dev->dev_addr[2])); +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) +		goto error_out; + +	/* Disable IRQ coalescing -- we can reenable it when 3Com gives +	 * us some more information on how to control it. +	 */ +	INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_IRQ_COALESCE_CTRL); +	xp_cmd.parm1 = 0; +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) +		goto error_out; + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_XCVR_SELECT); +	xp_cmd.parm1 = tp->xcvr_select; +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) +		goto error_out; + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_VLAN_TYPE_WRITE); +	xp_cmd.parm1 = cpu_to_le16(ETH_P_8021Q); +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) +		goto error_out; + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_OFFLOAD_TASKS); +	xp_cmd.parm2 = tp->offload; +	xp_cmd.parm3 = tp->offload; +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) +		goto error_out; + +	typhoon_set_rx_mode(dev); + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_TX_ENABLE); +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) +		goto error_out; + +	INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_RX_ENABLE); +	err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); +	if(err < 0) +		goto error_out; + +	tp->card_state = Running; +	smp_wmb(); + +	iowrite32(TYPHOON_INTR_ENABLE_ALL, ioaddr + TYPHOON_REG_INTR_ENABLE); +	iowrite32(TYPHOON_INTR_NONE, ioaddr + TYPHOON_REG_INTR_MASK); +	typhoon_post_pci_writes(ioaddr); + +	return 0; + +error_out: +	typhoon_reset(ioaddr, WaitNoSleep); +	typhoon_free_rx_rings(tp); +	typhoon_init_rings(tp); +	return err; +} + +static int +typhoon_stop_runtime(struct typhoon *tp, int wait_type) +{ +	struct typhoon_indexes *indexes = tp->indexes; +	struct transmit_ring *txLo = &tp->txLoRing; +	void __iomem *ioaddr = tp->ioaddr; +	struct cmd_desc xp_cmd; +	int i; + +	/* Disable interrupts early, since we can't schedule a poll +	 * when called with !netif_running(). This will be posted +	 * when we force the posting of the command. +	 */ +	iowrite32(TYPHOON_INTR_NONE, ioaddr + TYPHOON_REG_INTR_ENABLE); + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_RX_DISABLE); +	typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); + +	/* Wait 1/2 sec for any outstanding transmits to occur +	 * We'll cleanup after the reset if this times out. +	 */ +	for(i = 0; i < TYPHOON_WAIT_TIMEOUT; i++) { +		if(indexes->txLoCleared == cpu_to_le32(txLo->lastWrite)) +			break; +		udelay(TYPHOON_UDELAY); +	} + +	if(i == TYPHOON_WAIT_TIMEOUT) +		netdev_err(tp->dev, "halt timed out waiting for Tx to complete\n"); + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_TX_DISABLE); +	typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); + +	/* save the statistics so when we bring the interface up again, +	 * the values reported to userspace are correct. +	 */ +	tp->card_state = Sleeping; +	smp_wmb(); +	typhoon_do_get_stats(tp); +	memcpy(&tp->stats_saved, &tp->stats, sizeof(struct net_device_stats)); + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_HALT); +	typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL); + +	if(typhoon_wait_status(ioaddr, TYPHOON_STATUS_HALTED) < 0) +		netdev_err(tp->dev, "timed out waiting for 3XP to halt\n"); + +	if(typhoon_reset(ioaddr, wait_type) < 0) { +		netdev_err(tp->dev, "unable to reset 3XP\n"); +		return -ETIMEDOUT; +	} + +	/* cleanup any outstanding Tx packets */ +	if(indexes->txLoCleared != cpu_to_le32(txLo->lastWrite)) { +		indexes->txLoCleared = cpu_to_le32(txLo->lastWrite); +		typhoon_clean_tx(tp, &tp->txLoRing, &indexes->txLoCleared); +	} + +	return 0; +} + +static void +typhoon_tx_timeout(struct net_device *dev) +{ +	struct typhoon *tp = netdev_priv(dev); + +	if(typhoon_reset(tp->ioaddr, WaitNoSleep) < 0) { +		netdev_warn(dev, "could not reset in tx timeout\n"); +		goto truly_dead; +	} + +	/* If we ever start using the Hi ring, it will need cleaning too */ +	typhoon_clean_tx(tp, &tp->txLoRing, &tp->indexes->txLoCleared); +	typhoon_free_rx_rings(tp); + +	if(typhoon_start_runtime(tp) < 0) { +		netdev_err(dev, "could not start runtime in tx timeout\n"); +		goto truly_dead; +        } + +	netif_wake_queue(dev); +	return; + +truly_dead: +	/* Reset the hardware, and turn off carrier to avoid more timeouts */ +	typhoon_reset(tp->ioaddr, NoWait); +	netif_carrier_off(dev); +} + +static int +typhoon_open(struct net_device *dev) +{ +	struct typhoon *tp = netdev_priv(dev); +	int err; + +	err = typhoon_request_firmware(tp); +	if (err) +		goto out; + +	err = typhoon_wakeup(tp, WaitSleep); +	if(err < 0) { +		netdev_err(dev, "unable to wakeup device\n"); +		goto out_sleep; +	} + +	err = request_irq(dev->irq, typhoon_interrupt, IRQF_SHARED, +				dev->name, dev); +	if(err < 0) +		goto out_sleep; + +	napi_enable(&tp->napi); + +	err = typhoon_start_runtime(tp); +	if(err < 0) { +		napi_disable(&tp->napi); +		goto out_irq; +	} + +	netif_start_queue(dev); +	return 0; + +out_irq: +	free_irq(dev->irq, dev); + +out_sleep: +	if(typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_HOST) < 0) { +		netdev_err(dev, "unable to reboot into sleep img\n"); +		typhoon_reset(tp->ioaddr, NoWait); +		goto out; +	} + +	if(typhoon_sleep(tp, PCI_D3hot, 0) < 0) +		netdev_err(dev, "unable to go back to sleep\n"); + +out: +	return err; +} + +static int +typhoon_close(struct net_device *dev) +{ +	struct typhoon *tp = netdev_priv(dev); + +	netif_stop_queue(dev); +	napi_disable(&tp->napi); + +	if(typhoon_stop_runtime(tp, WaitSleep) < 0) +		netdev_err(dev, "unable to stop runtime\n"); + +	/* Make sure there is no irq handler running on a different CPU. */ +	free_irq(dev->irq, dev); + +	typhoon_free_rx_rings(tp); +	typhoon_init_rings(tp); + +	if(typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_HOST) < 0) +		netdev_err(dev, "unable to boot sleep image\n"); + +	if(typhoon_sleep(tp, PCI_D3hot, 0) < 0) +		netdev_err(dev, "unable to put card to sleep\n"); + +	return 0; +} + +#ifdef CONFIG_PM +static int +typhoon_resume(struct pci_dev *pdev) +{ +	struct net_device *dev = pci_get_drvdata(pdev); +	struct typhoon *tp = netdev_priv(dev); + +	/* If we're down, resume when we are upped. +	 */ +	if(!netif_running(dev)) +		return 0; + +	if(typhoon_wakeup(tp, WaitNoSleep) < 0) { +		netdev_err(dev, "critical: could not wake up in resume\n"); +		goto reset; +	} + +	if(typhoon_start_runtime(tp) < 0) { +		netdev_err(dev, "critical: could not start runtime in resume\n"); +		goto reset; +	} + +	netif_device_attach(dev); +	return 0; + +reset: +	typhoon_reset(tp->ioaddr, NoWait); +	return -EBUSY; +} + +static int +typhoon_suspend(struct pci_dev *pdev, pm_message_t state) +{ +	struct net_device *dev = pci_get_drvdata(pdev); +	struct typhoon *tp = netdev_priv(dev); +	struct cmd_desc xp_cmd; + +	/* If we're down, we're already suspended. +	 */ +	if(!netif_running(dev)) +		return 0; + +	/* TYPHOON_OFFLOAD_VLAN is always on now, so this doesn't work */ +	if(tp->wol_events & TYPHOON_WAKE_MAGIC_PKT) +		netdev_warn(dev, "cannot do WAKE_MAGIC with VLAN offloading\n"); + +	netif_device_detach(dev); + +	if(typhoon_stop_runtime(tp, WaitNoSleep) < 0) { +		netdev_err(dev, "unable to stop runtime\n"); +		goto need_resume; +	} + +	typhoon_free_rx_rings(tp); +	typhoon_init_rings(tp); + +	if(typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_HOST) < 0) { +		netdev_err(dev, "unable to boot sleep image\n"); +		goto need_resume; +	} + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_MAC_ADDRESS); +	xp_cmd.parm1 = cpu_to_le16(ntohs(*(__be16 *)&dev->dev_addr[0])); +	xp_cmd.parm2 = cpu_to_le32(ntohl(*(__be32 *)&dev->dev_addr[2])); +	if(typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL) < 0) { +		netdev_err(dev, "unable to set mac address in suspend\n"); +		goto need_resume; +	} + +	INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_RX_FILTER); +	xp_cmd.parm1 = TYPHOON_RX_FILTER_DIRECTED | TYPHOON_RX_FILTER_BROADCAST; +	if(typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL) < 0) { +		netdev_err(dev, "unable to set rx filter in suspend\n"); +		goto need_resume; +	} + +	if(typhoon_sleep(tp, pci_choose_state(pdev, state), tp->wol_events) < 0) { +		netdev_err(dev, "unable to put card to sleep\n"); +		goto need_resume; +	} + +	return 0; + +need_resume: +	typhoon_resume(pdev); +	return -EBUSY; +} +#endif + +static int +typhoon_test_mmio(struct pci_dev *pdev) +{ +	void __iomem *ioaddr = pci_iomap(pdev, 1, 128); +	int mode = 0; +	u32 val; + +	if(!ioaddr) +		goto out; + +	if(ioread32(ioaddr + TYPHOON_REG_STATUS) != +				TYPHOON_STATUS_WAITING_FOR_HOST) +		goto out_unmap; + +	iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK); +	iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_STATUS); +	iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_ENABLE); + +	/* Ok, see if we can change our interrupt status register by +	 * sending ourselves an interrupt. If so, then MMIO works. +	 * The 50usec delay is arbitrary -- it could probably be smaller. +	 */ +	val = ioread32(ioaddr + TYPHOON_REG_INTR_STATUS); +	if((val & TYPHOON_INTR_SELF) == 0) { +		iowrite32(1, ioaddr + TYPHOON_REG_SELF_INTERRUPT); +		ioread32(ioaddr + TYPHOON_REG_INTR_STATUS); +		udelay(50); +		val = ioread32(ioaddr + TYPHOON_REG_INTR_STATUS); +		if(val & TYPHOON_INTR_SELF) +			mode = 1; +	} + +	iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK); +	iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_STATUS); +	iowrite32(TYPHOON_INTR_NONE, ioaddr + TYPHOON_REG_INTR_ENABLE); +	ioread32(ioaddr + TYPHOON_REG_INTR_STATUS); + +out_unmap: +	pci_iounmap(pdev, ioaddr); + +out: +	if(!mode) +		pr_info("%s: falling back to port IO\n", pci_name(pdev)); +	return mode; +} + +static const struct net_device_ops typhoon_netdev_ops = { +	.ndo_open		= typhoon_open, +	.ndo_stop		= typhoon_close, +	.ndo_start_xmit		= typhoon_start_tx, +	.ndo_set_rx_mode	= typhoon_set_rx_mode, +	.ndo_tx_timeout		= typhoon_tx_timeout, +	.ndo_get_stats		= typhoon_get_stats, +	.ndo_validate_addr	= eth_validate_addr, +	.ndo_set_mac_address	= eth_mac_addr, +	.ndo_change_mtu		= eth_change_mtu, +}; + +static int +typhoon_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +{ +	struct net_device *dev; +	struct typhoon *tp; +	int card_id = (int) ent->driver_data; +	void __iomem *ioaddr; +	void *shared; +	dma_addr_t shared_dma; +	struct cmd_desc xp_cmd; +	struct resp_desc xp_resp[3]; +	int err = 0; +	const char *err_msg; + +	dev = alloc_etherdev(sizeof(*tp)); +	if(dev == NULL) { +		err_msg = "unable to alloc new net device"; +		err = -ENOMEM; +		goto error_out; +	} +	SET_NETDEV_DEV(dev, &pdev->dev); + +	err = pci_enable_device(pdev); +	if(err < 0) { +		err_msg = "unable to enable device"; +		goto error_out_dev; +	} + +	err = pci_set_mwi(pdev); +	if(err < 0) { +		err_msg = "unable to set MWI"; +		goto error_out_disable; +	} + +	err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); +	if(err < 0) { +		err_msg = "No usable DMA configuration"; +		goto error_out_mwi; +	} + +	/* sanity checks on IO and MMIO BARs +	 */ +	if(!(pci_resource_flags(pdev, 0) & IORESOURCE_IO)) { +		err_msg = "region #1 not a PCI IO resource, aborting"; +		err = -ENODEV; +		goto error_out_mwi; +	} +	if(pci_resource_len(pdev, 0) < 128) { +		err_msg = "Invalid PCI IO region size, aborting"; +		err = -ENODEV; +		goto error_out_mwi; +	} +	if(!(pci_resource_flags(pdev, 1) & IORESOURCE_MEM)) { +		err_msg = "region #1 not a PCI MMIO resource, aborting"; +		err = -ENODEV; +		goto error_out_mwi; +	} +	if(pci_resource_len(pdev, 1) < 128) { +		err_msg = "Invalid PCI MMIO region size, aborting"; +		err = -ENODEV; +		goto error_out_mwi; +	} + +	err = pci_request_regions(pdev, KBUILD_MODNAME); +	if(err < 0) { +		err_msg = "could not request regions"; +		goto error_out_mwi; +	} + +	/* map our registers +	 */ +	if(use_mmio != 0 && use_mmio != 1) +		use_mmio = typhoon_test_mmio(pdev); + +	ioaddr = pci_iomap(pdev, use_mmio, 128); +	if (!ioaddr) { +		err_msg = "cannot remap registers, aborting"; +		err = -EIO; +		goto error_out_regions; +	} + +	/* allocate pci dma space for rx and tx descriptor rings +	 */ +	shared = pci_alloc_consistent(pdev, sizeof(struct typhoon_shared), +				      &shared_dma); +	if(!shared) { +		err_msg = "could not allocate DMA memory"; +		err = -ENOMEM; +		goto error_out_remap; +	} + +	dev->irq = pdev->irq; +	tp = netdev_priv(dev); +	tp->shared = shared; +	tp->shared_dma = shared_dma; +	tp->pdev = pdev; +	tp->tx_pdev = pdev; +	tp->ioaddr = ioaddr; +	tp->tx_ioaddr = ioaddr; +	tp->dev = dev; + +	/* Init sequence: +	 * 1) Reset the adapter to clear any bad juju +	 * 2) Reload the sleep image +	 * 3) Boot the sleep image +	 * 4) Get the hardware address. +	 * 5) Put the card to sleep. +	 */ +	if (typhoon_reset(ioaddr, WaitSleep) < 0) { +		err_msg = "could not reset 3XP"; +		err = -EIO; +		goto error_out_dma; +	} + +	/* Now that we've reset the 3XP and are sure it's not going to +	 * write all over memory, enable bus mastering, and save our +	 * state for resuming after a suspend. +	 */ +	pci_set_master(pdev); +	pci_save_state(pdev); + +	typhoon_init_interface(tp); +	typhoon_init_rings(tp); + +	if(typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_HOST) < 0) { +		err_msg = "cannot boot 3XP sleep image"; +		err = -EIO; +		goto error_out_reset; +	} + +	INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_READ_MAC_ADDRESS); +	if(typhoon_issue_command(tp, 1, &xp_cmd, 1, xp_resp) < 0) { +		err_msg = "cannot read MAC address"; +		err = -EIO; +		goto error_out_reset; +	} + +	*(__be16 *)&dev->dev_addr[0] = htons(le16_to_cpu(xp_resp[0].parm1)); +	*(__be32 *)&dev->dev_addr[2] = htonl(le32_to_cpu(xp_resp[0].parm2)); + +	if(!is_valid_ether_addr(dev->dev_addr)) { +		err_msg = "Could not obtain valid ethernet address, aborting"; +		goto error_out_reset; +	} + +	/* Read the Sleep Image version last, so the response is valid +	 * later when we print out the version reported. +	 */ +	INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_READ_VERSIONS); +	if(typhoon_issue_command(tp, 1, &xp_cmd, 3, xp_resp) < 0) { +		err_msg = "Could not get Sleep Image version"; +		goto error_out_reset; +	} + +	tp->capabilities = typhoon_card_info[card_id].capabilities; +	tp->xcvr_select = TYPHOON_XCVR_AUTONEG; + +	/* Typhoon 1.0 Sleep Images return one response descriptor to the +	 * READ_VERSIONS command. Those versions are OK after waking up +	 * from sleep without needing a reset. Typhoon 1.1+ Sleep Images +	 * seem to need a little extra help to get started. Since we don't +	 * know how to nudge it along, just kick it. +	 */ +	if(xp_resp[0].numDesc != 0) +		tp->capabilities |= TYPHOON_WAKEUP_NEEDS_RESET; + +	if(typhoon_sleep(tp, PCI_D3hot, 0) < 0) { +		err_msg = "cannot put adapter to sleep"; +		err = -EIO; +		goto error_out_reset; +	} + +	/* The chip-specific entries in the device structure. */ +	dev->netdev_ops		= &typhoon_netdev_ops; +	netif_napi_add(dev, &tp->napi, typhoon_poll, 16); +	dev->watchdog_timeo	= TX_TIMEOUT; + +	dev->ethtool_ops = &typhoon_ethtool_ops; + +	/* We can handle scatter gather, up to 16 entries, and +	 * we can do IP checksumming (only version 4, doh...) +	 * +	 * There's no way to turn off the RX VLAN offloading and stripping +	 * on the current 3XP firmware -- it does not respect the offload +	 * settings -- so we only allow the user to toggle the TX processing. +	 */ +	dev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO | +		NETIF_F_HW_VLAN_CTAG_TX; +	dev->features = dev->hw_features | +		NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_RXCSUM; + +	if(register_netdev(dev) < 0) { +		err_msg = "unable to register netdev"; +		goto error_out_reset; +	} + +	pci_set_drvdata(pdev, dev); + +	netdev_info(dev, "%s at %s 0x%llx, %pM\n", +		    typhoon_card_info[card_id].name, +		    use_mmio ? "MMIO" : "IO", +		    (unsigned long long)pci_resource_start(pdev, use_mmio), +		    dev->dev_addr); + +	/* xp_resp still contains the response to the READ_VERSIONS command. +	 * For debugging, let the user know what version he has. +	 */ +	if(xp_resp[0].numDesc == 0) { +		/* This is the Typhoon 1.0 type Sleep Image, last 16 bits +		 * of version is Month/Day of build. +		 */ +		u16 monthday = le32_to_cpu(xp_resp[0].parm2) & 0xffff; +		netdev_info(dev, "Typhoon 1.0 Sleep Image built %02u/%02u/2000\n", +			    monthday >> 8, monthday & 0xff); +	} else if(xp_resp[0].numDesc == 2) { +		/* This is the Typhoon 1.1+ type Sleep Image +		 */ +		u32 sleep_ver = le32_to_cpu(xp_resp[0].parm2); +		u8 *ver_string = (u8 *) &xp_resp[1]; +		ver_string[25] = 0; +		netdev_info(dev, "Typhoon 1.1+ Sleep Image version %02x.%03x.%03x %s\n", +			    sleep_ver >> 24, (sleep_ver >> 12) & 0xfff, +			    sleep_ver & 0xfff, ver_string); +	} else { +		netdev_warn(dev, "Unknown Sleep Image version (%u:%04x)\n", +			    xp_resp[0].numDesc, le32_to_cpu(xp_resp[0].parm2)); +	} + +	return 0; + +error_out_reset: +	typhoon_reset(ioaddr, NoWait); + +error_out_dma: +	pci_free_consistent(pdev, sizeof(struct typhoon_shared), +			    shared, shared_dma); +error_out_remap: +	pci_iounmap(pdev, ioaddr); +error_out_regions: +	pci_release_regions(pdev); +error_out_mwi: +	pci_clear_mwi(pdev); +error_out_disable: +	pci_disable_device(pdev); +error_out_dev: +	free_netdev(dev); +error_out: +	pr_err("%s: %s\n", pci_name(pdev), err_msg); +	return err; +} + +static void +typhoon_remove_one(struct pci_dev *pdev) +{ +	struct net_device *dev = pci_get_drvdata(pdev); +	struct typhoon *tp = netdev_priv(dev); + +	unregister_netdev(dev); +	pci_set_power_state(pdev, PCI_D0); +	pci_restore_state(pdev); +	typhoon_reset(tp->ioaddr, NoWait); +	pci_iounmap(pdev, tp->ioaddr); +	pci_free_consistent(pdev, sizeof(struct typhoon_shared), +			    tp->shared, tp->shared_dma); +	pci_release_regions(pdev); +	pci_clear_mwi(pdev); +	pci_disable_device(pdev); +	free_netdev(dev); +} + +static struct pci_driver typhoon_driver = { +	.name		= KBUILD_MODNAME, +	.id_table	= typhoon_pci_tbl, +	.probe		= typhoon_init_one, +	.remove		= typhoon_remove_one, +#ifdef CONFIG_PM +	.suspend	= typhoon_suspend, +	.resume		= typhoon_resume, +#endif +}; + +static int __init +typhoon_init(void) +{ +	return pci_register_driver(&typhoon_driver); +} + +static void __exit +typhoon_cleanup(void) +{ +	release_firmware(typhoon_fw); +	pci_unregister_driver(&typhoon_driver); +} + +module_init(typhoon_init); +module_exit(typhoon_cleanup); diff --git a/drivers/net/ethernet/3com/typhoon.h b/drivers/net/ethernet/3com/typhoon.h new file mode 100644 index 00000000000..88187fc84aa --- /dev/null +++ b/drivers/net/ethernet/3com/typhoon.h @@ -0,0 +1,624 @@ +/* typhoon.h:	chip info for the 3Com 3CR990 family of controllers */ +/* +	Written 2002-2003 by David Dillow <dave@thedillows.org> + +	This software may be used and distributed according to the terms of +	the GNU General Public License (GPL), incorporated herein by reference. +	Drivers based on or derived from this code fall under the GPL and must +	retain the authorship, copyright and license notice.  This file is not +	a complete program and may only be used when the entire operating +	system is licensed under the GPL. + +	This software is available on a public web site. It may enable +	cryptographic capabilities of the 3Com hardware, and may be +	exported from the United States under License Exception "TSU" +	pursuant to 15 C.F.R. Section 740.13(e). + +	This work was funded by the National Library of Medicine under +	the Department of Energy project number 0274DD06D1 and NLM project +	number Y1-LM-2015-01. +*/ + +/* All Typhoon ring positions are specificed in bytes, and point to the + * first "clean" entry in the ring -- ie the next entry we use for whatever + * purpose. + */ + +/* The Typhoon basic ring + * ringBase:  where this ring lives (our virtual address) + * lastWrite: the next entry we'll use + */ +struct basic_ring { +	u8 *ringBase; +	u32 lastWrite; +}; + +/* The Typoon transmit ring -- same as a basic ring, plus: + * lastRead:      where we're at in regard to cleaning up the ring + * writeRegister: register to use for writing (different for Hi & Lo rings) + */ +struct transmit_ring { +	u8 *ringBase; +	u32 lastWrite; +	u32 lastRead; +	int writeRegister; +}; + +/* The host<->Typhoon ring index structure + * This indicates the current positions in the rings + * + * All values must be in little endian format for the 3XP + * + * rxHiCleared:   entry we've cleared to in the Hi receive ring + * rxLoCleared:   entry we've cleared to in the Lo receive ring + * rxBuffReady:   next entry we'll put a free buffer in + * respCleared:   entry we've cleared to in the response ring + * + * txLoCleared:   entry the NIC has cleared to in the Lo transmit ring + * txHiCleared:   entry the NIC has cleared to in the Hi transmit ring + * rxLoReady:     entry the NIC has filled to in the Lo receive ring + * rxBuffCleared: entry the NIC has cleared in the free buffer ring + * cmdCleared:    entry the NIC has cleared in the command ring + * respReady:     entry the NIC has filled to in the response ring + * rxHiReady:     entry the NIC has filled to in the Hi receive ring + */ +struct typhoon_indexes { +	/* The first four are written by the host, and read by the NIC */ +	volatile __le32 rxHiCleared; +	volatile __le32 rxLoCleared; +	volatile __le32 rxBuffReady; +	volatile __le32 respCleared; + +	/* The remaining are written by the NIC, and read by the host */ +	volatile __le32 txLoCleared; +	volatile __le32 txHiCleared; +	volatile __le32 rxLoReady; +	volatile __le32 rxBuffCleared; +	volatile __le32 cmdCleared; +	volatile __le32 respReady; +	volatile __le32 rxHiReady; +} __packed; + +/* The host<->Typhoon interface + * Our means of communicating where things are + * + * All values must be in little endian format for the 3XP + * + * ringIndex:   64 bit bus address of the index structure + * txLoAddr:    64 bit bus address of the Lo transmit ring + * txLoSize:    size (in bytes) of the Lo transmit ring + * txHi*:       as above for the Hi priority transmit ring + * rxLo*:       as above for the Lo priority receive ring + * rxBuff*:     as above for the free buffer ring + * cmd*:        as above for the command ring + * resp*:       as above for the response ring + * zeroAddr:    64 bit bus address of a zero word (for DMA) + * rxHi*:       as above for the Hi Priority receive ring + * + * While there is room for 64 bit addresses, current versions of the 3XP + * only do 32 bit addresses, so the *Hi for each of the above will always + * be zero. + */ +struct typhoon_interface { +	__le32 ringIndex; +	__le32 ringIndexHi; +	__le32 txLoAddr; +	__le32 txLoAddrHi; +	__le32 txLoSize; +	__le32 txHiAddr; +	__le32 txHiAddrHi; +	__le32 txHiSize; +	__le32 rxLoAddr; +	__le32 rxLoAddrHi; +	__le32 rxLoSize; +	__le32 rxBuffAddr; +	__le32 rxBuffAddrHi; +	__le32 rxBuffSize; +	__le32 cmdAddr; +	__le32 cmdAddrHi; +	__le32 cmdSize; +	__le32 respAddr; +	__le32 respAddrHi; +	__le32 respSize; +	__le32 zeroAddr; +	__le32 zeroAddrHi; +	__le32 rxHiAddr; +	__le32 rxHiAddrHi; +	__le32 rxHiSize; +} __packed; + +/* The Typhoon transmit/fragment descriptor + * + * A packet is described by a packet descriptor, followed by option descriptors, + * if any, then one or more fragment descriptors. + * + * Packet descriptor: + * flags:	Descriptor type + * len:i	zero, or length of this packet + * addr*:	8 bytes of opaque data to the firmware -- for skb pointer + * processFlags: Determine offload tasks to perform on this packet. + * + * Fragment descriptor: + * flags:	Descriptor type + * len:i	length of this fragment + * addr:	low bytes of DMA address for this part of the packet + * addrHi:	hi bytes of DMA address for this part of the packet + * processFlags: must be zero + * + * TYPHOON_DESC_VALID is not mentioned in their docs, but their Linux + * driver uses it. + */ +struct tx_desc { +	u8  flags; +#define TYPHOON_TYPE_MASK	0x07 +#define 	TYPHOON_FRAG_DESC	0x00 +#define 	TYPHOON_TX_DESC		0x01 +#define 	TYPHOON_CMD_DESC	0x02 +#define 	TYPHOON_OPT_DESC	0x03 +#define 	TYPHOON_RX_DESC		0x04 +#define 	TYPHOON_RESP_DESC	0x05 +#define TYPHOON_OPT_TYPE_MASK	0xf0 +#define 	TYPHOON_OPT_IPSEC	0x00 +#define 	TYPHOON_OPT_TCP_SEG	0x10 +#define TYPHOON_CMD_RESPOND	0x40 +#define TYPHOON_RESP_ERROR	0x40 +#define TYPHOON_RX_ERROR	0x40 +#define TYPHOON_DESC_VALID	0x80 +	u8  numDesc; +	__le16 len; +	union { +		struct { +			__le32 addr; +			__le32 addrHi; +		} frag; +		u64 tx_addr;	/* opaque for hardware, for TX_DESC */ +	}; +	__le32 processFlags; +#define TYPHOON_TX_PF_NO_CRC		cpu_to_le32(0x00000001) +#define TYPHOON_TX_PF_IP_CHKSUM		cpu_to_le32(0x00000002) +#define TYPHOON_TX_PF_TCP_CHKSUM	cpu_to_le32(0x00000004) +#define TYPHOON_TX_PF_TCP_SEGMENT	cpu_to_le32(0x00000008) +#define TYPHOON_TX_PF_INSERT_VLAN	cpu_to_le32(0x00000010) +#define TYPHOON_TX_PF_IPSEC		cpu_to_le32(0x00000020) +#define TYPHOON_TX_PF_VLAN_PRIORITY	cpu_to_le32(0x00000040) +#define TYPHOON_TX_PF_UDP_CHKSUM	cpu_to_le32(0x00000080) +#define TYPHOON_TX_PF_PAD_FRAME		cpu_to_le32(0x00000100) +#define TYPHOON_TX_PF_RESERVED		cpu_to_le32(0x00000e00) +#define TYPHOON_TX_PF_VLAN_MASK		cpu_to_le32(0x0ffff000) +#define TYPHOON_TX_PF_INTERNAL		cpu_to_le32(0xf0000000) +#define TYPHOON_TX_PF_VLAN_TAG_SHIFT	12 +} __packed; + +/* The TCP Segmentation offload option descriptor + * + * flags:	descriptor type + * numDesc:	must be 1 + * mss_flags:	bits 0-11 (little endian) are MSS, 12 is first TSO descriptor + *			13 is list TSO descriptor, set both if only one TSO + * respAddrLo:	low bytes of address of the bytesTx field of this descriptor + * bytesTx:	total number of bytes in this TSO request + * status:	0 on completion + */ +struct tcpopt_desc { +	u8  flags; +	u8  numDesc; +	__le16 mss_flags; +#define TYPHOON_TSO_FIRST		cpu_to_le16(0x1000) +#define TYPHOON_TSO_LAST		cpu_to_le16(0x2000) +	__le32 respAddrLo; +	__le32 bytesTx; +	__le32 status; +} __packed; + +/* The IPSEC Offload descriptor + * + * flags:	descriptor type + * numDesc:	must be 1 + * ipsecFlags:	bit 0: 0 -- generate IV, 1 -- use supplied IV + * sa1, sa2:	Security Association IDs for this packet + * reserved:	set to 0 + */ +struct ipsec_desc { +	u8  flags; +	u8  numDesc; +	__le16 ipsecFlags; +#define TYPHOON_IPSEC_GEN_IV	cpu_to_le16(0x0000) +#define TYPHOON_IPSEC_USE_IV	cpu_to_le16(0x0001) +	__le32 sa1; +	__le32 sa2; +	__le32 reserved; +} __packed; + +/* The Typhoon receive descriptor (Updated by NIC) + * + * flags:         Descriptor type, error indication + * numDesc:       Always zero + * frameLen:      the size of the packet received + * addr:          low 32 bytes of the virtual addr passed in for this buffer + * addrHi:        high 32 bytes of the virtual addr passed in for this buffer + * rxStatus:      Error if set in flags, otherwise result of offload processing + * filterResults: results of filtering on packet, not used + * ipsecResults:  Results of IPSEC processing + * vlanTag:       the 801.2q TCI from the packet + */ +struct rx_desc { +	u8  flags; +	u8  numDesc; +	__le16 frameLen; +	u32 addr;	/* opaque, comes from virtAddr */ +	u32 addrHi;	/* opaque, comes from virtAddrHi */ +	__le32 rxStatus; +#define TYPHOON_RX_ERR_INTERNAL		cpu_to_le32(0x00000000) +#define TYPHOON_RX_ERR_FIFO_UNDERRUN	cpu_to_le32(0x00000001) +#define TYPHOON_RX_ERR_BAD_SSD		cpu_to_le32(0x00000002) +#define TYPHOON_RX_ERR_RUNT		cpu_to_le32(0x00000003) +#define TYPHOON_RX_ERR_CRC		cpu_to_le32(0x00000004) +#define TYPHOON_RX_ERR_OVERSIZE		cpu_to_le32(0x00000005) +#define TYPHOON_RX_ERR_ALIGN		cpu_to_le32(0x00000006) +#define TYPHOON_RX_ERR_DRIBBLE		cpu_to_le32(0x00000007) +#define TYPHOON_RX_PROTO_MASK		cpu_to_le32(0x00000003) +#define TYPHOON_RX_PROTO_UNKNOWN	cpu_to_le32(0x00000000) +#define TYPHOON_RX_PROTO_IP		cpu_to_le32(0x00000001) +#define TYPHOON_RX_PROTO_IPX		cpu_to_le32(0x00000002) +#define TYPHOON_RX_VLAN			cpu_to_le32(0x00000004) +#define TYPHOON_RX_IP_FRAG		cpu_to_le32(0x00000008) +#define TYPHOON_RX_IPSEC		cpu_to_le32(0x00000010) +#define TYPHOON_RX_IP_CHK_FAIL		cpu_to_le32(0x00000020) +#define TYPHOON_RX_TCP_CHK_FAIL		cpu_to_le32(0x00000040) +#define TYPHOON_RX_UDP_CHK_FAIL		cpu_to_le32(0x00000080) +#define TYPHOON_RX_IP_CHK_GOOD		cpu_to_le32(0x00000100) +#define TYPHOON_RX_TCP_CHK_GOOD		cpu_to_le32(0x00000200) +#define TYPHOON_RX_UDP_CHK_GOOD		cpu_to_le32(0x00000400) +	__le16 filterResults; +#define TYPHOON_RX_FILTER_MASK		cpu_to_le16(0x7fff) +#define TYPHOON_RX_FILTERED		cpu_to_le16(0x8000) +	__le16 ipsecResults; +#define TYPHOON_RX_OUTER_AH_GOOD	cpu_to_le16(0x0001) +#define TYPHOON_RX_OUTER_ESP_GOOD	cpu_to_le16(0x0002) +#define TYPHOON_RX_INNER_AH_GOOD	cpu_to_le16(0x0004) +#define TYPHOON_RX_INNER_ESP_GOOD	cpu_to_le16(0x0008) +#define TYPHOON_RX_OUTER_AH_FAIL	cpu_to_le16(0x0010) +#define TYPHOON_RX_OUTER_ESP_FAIL	cpu_to_le16(0x0020) +#define TYPHOON_RX_INNER_AH_FAIL	cpu_to_le16(0x0040) +#define TYPHOON_RX_INNER_ESP_FAIL	cpu_to_le16(0x0080) +#define TYPHOON_RX_UNKNOWN_SA		cpu_to_le16(0x0100) +#define TYPHOON_RX_ESP_FORMAT_ERR	cpu_to_le16(0x0200) +	__be32 vlanTag; +} __packed; + +/* The Typhoon free buffer descriptor, used to give a buffer to the NIC + * + * physAddr:    low 32 bits of the bus address of the buffer + * physAddrHi:  high 32 bits of the bus address of the buffer, always zero + * virtAddr:    low 32 bits of the skb address + * virtAddrHi:  high 32 bits of the skb address, always zero + * + * the virt* address is basically two 32 bit cookies, just passed back + * from the NIC + */ +struct rx_free { +	__le32 physAddr; +	__le32 physAddrHi; +	u32 virtAddr; +	u32 virtAddrHi; +} __packed; + +/* The Typhoon command descriptor, used for commands and responses + * + * flags:   descriptor type + * numDesc: number of descriptors following in this command/response, + *				ie, zero for a one descriptor command + * cmd:     the command + * seqNo:   sequence number (unused) + * parm1:   use varies by command + * parm2:   use varies by command + * parm3:   use varies by command + */ +struct cmd_desc { +	u8  flags; +	u8  numDesc; +	__le16 cmd; +#define TYPHOON_CMD_TX_ENABLE		cpu_to_le16(0x0001) +#define TYPHOON_CMD_TX_DISABLE		cpu_to_le16(0x0002) +#define TYPHOON_CMD_RX_ENABLE		cpu_to_le16(0x0003) +#define TYPHOON_CMD_RX_DISABLE		cpu_to_le16(0x0004) +#define TYPHOON_CMD_SET_RX_FILTER	cpu_to_le16(0x0005) +#define TYPHOON_CMD_READ_STATS		cpu_to_le16(0x0007) +#define TYPHOON_CMD_XCVR_SELECT		cpu_to_le16(0x0013) +#define TYPHOON_CMD_SET_MAX_PKT_SIZE	cpu_to_le16(0x001a) +#define TYPHOON_CMD_READ_MEDIA_STATUS	cpu_to_le16(0x001b) +#define TYPHOON_CMD_GOTO_SLEEP		cpu_to_le16(0x0023) +#define TYPHOON_CMD_SET_MULTICAST_HASH	cpu_to_le16(0x0025) +#define TYPHOON_CMD_SET_MAC_ADDRESS	cpu_to_le16(0x0026) +#define TYPHOON_CMD_READ_MAC_ADDRESS	cpu_to_le16(0x0027) +#define TYPHOON_CMD_VLAN_TYPE_WRITE	cpu_to_le16(0x002b) +#define TYPHOON_CMD_CREATE_SA		cpu_to_le16(0x0034) +#define TYPHOON_CMD_DELETE_SA		cpu_to_le16(0x0035) +#define TYPHOON_CMD_READ_VERSIONS	cpu_to_le16(0x0043) +#define TYPHOON_CMD_IRQ_COALESCE_CTRL	cpu_to_le16(0x0045) +#define TYPHOON_CMD_ENABLE_WAKE_EVENTS	cpu_to_le16(0x0049) +#define TYPHOON_CMD_SET_OFFLOAD_TASKS	cpu_to_le16(0x004f) +#define TYPHOON_CMD_HELLO_RESP		cpu_to_le16(0x0057) +#define TYPHOON_CMD_HALT		cpu_to_le16(0x005d) +#define TYPHOON_CMD_READ_IPSEC_INFO	cpu_to_le16(0x005e) +#define TYPHOON_CMD_GET_IPSEC_ENABLE	cpu_to_le16(0x0067) +#define TYPHOON_CMD_GET_CMD_LVL		cpu_to_le16(0x0069) +	u16 seqNo; +	__le16 parm1; +	__le32 parm2; +	__le32 parm3; +} __packed; + +/* The Typhoon response descriptor, see command descriptor for details + */ +struct resp_desc { +	u8  flags; +	u8  numDesc; +	__le16 cmd; +	__le16 seqNo; +	__le16 parm1; +	__le32 parm2; +	__le32 parm3; +} __packed; + +#define INIT_COMMAND_NO_RESPONSE(x, command)				\ +	do { struct cmd_desc *_ptr = (x);				\ +		memset(_ptr, 0, sizeof(struct cmd_desc));		\ +		_ptr->flags = TYPHOON_CMD_DESC | TYPHOON_DESC_VALID;	\ +		_ptr->cmd = command;					\ +	} while(0) + +/* We set seqNo to 1 if we're expecting a response from this command */ +#define INIT_COMMAND_WITH_RESPONSE(x, command)				\ +	do { struct cmd_desc *_ptr = (x);				\ +		memset(_ptr, 0, sizeof(struct cmd_desc));		\ +		_ptr->flags = TYPHOON_CMD_RESPOND | TYPHOON_CMD_DESC;	\ +		_ptr->flags |= TYPHOON_DESC_VALID; 			\ +		_ptr->cmd = command;					\ +		_ptr->seqNo = 1;					\ +	} while(0) + +/* TYPHOON_CMD_SET_RX_FILTER filter bits (cmd.parm1) + */ +#define TYPHOON_RX_FILTER_DIRECTED	cpu_to_le16(0x0001) +#define TYPHOON_RX_FILTER_ALL_MCAST	cpu_to_le16(0x0002) +#define TYPHOON_RX_FILTER_BROADCAST	cpu_to_le16(0x0004) +#define TYPHOON_RX_FILTER_PROMISCOUS	cpu_to_le16(0x0008) +#define TYPHOON_RX_FILTER_MCAST_HASH	cpu_to_le16(0x0010) + +/* TYPHOON_CMD_READ_STATS response format + */ +struct stats_resp { +	u8  flags; +	u8  numDesc; +	__le16 cmd; +	__le16 seqNo; +	__le16 unused; +	__le32 txPackets; +	__le64 txBytes; +	__le32 txDeferred; +	__le32 txLateCollisions; +	__le32 txCollisions; +	__le32 txCarrierLost; +	__le32 txMultipleCollisions; +	__le32 txExcessiveCollisions; +	__le32 txFifoUnderruns; +	__le32 txMulticastTxOverflows; +	__le32 txFiltered; +	__le32 rxPacketsGood; +	__le64 rxBytesGood; +	__le32 rxFifoOverruns; +	__le32 BadSSD; +	__le32 rxCrcErrors; +	__le32 rxOversized; +	__le32 rxBroadcast; +	__le32 rxMulticast; +	__le32 rxOverflow; +	__le32 rxFiltered; +	__le32 linkStatus; +#define TYPHOON_LINK_STAT_MASK		cpu_to_le32(0x00000001) +#define TYPHOON_LINK_GOOD		cpu_to_le32(0x00000001) +#define TYPHOON_LINK_BAD		cpu_to_le32(0x00000000) +#define TYPHOON_LINK_SPEED_MASK		cpu_to_le32(0x00000002) +#define TYPHOON_LINK_100MBPS		cpu_to_le32(0x00000002) +#define TYPHOON_LINK_10MBPS		cpu_to_le32(0x00000000) +#define TYPHOON_LINK_DUPLEX_MASK	cpu_to_le32(0x00000004) +#define TYPHOON_LINK_FULL_DUPLEX	cpu_to_le32(0x00000004) +#define TYPHOON_LINK_HALF_DUPLEX	cpu_to_le32(0x00000000) +	__le32 unused2; +	__le32 unused3; +} __packed; + +/* TYPHOON_CMD_XCVR_SELECT xcvr values (resp.parm1) + */ +#define TYPHOON_XCVR_10HALF	cpu_to_le16(0x0000) +#define TYPHOON_XCVR_10FULL	cpu_to_le16(0x0001) +#define TYPHOON_XCVR_100HALF	cpu_to_le16(0x0002) +#define TYPHOON_XCVR_100FULL	cpu_to_le16(0x0003) +#define TYPHOON_XCVR_AUTONEG	cpu_to_le16(0x0004) + +/* TYPHOON_CMD_READ_MEDIA_STATUS (resp.parm1) + */ +#define TYPHOON_MEDIA_STAT_CRC_STRIP_DISABLE	cpu_to_le16(0x0004) +#define TYPHOON_MEDIA_STAT_COLLISION_DETECT	cpu_to_le16(0x0010) +#define TYPHOON_MEDIA_STAT_CARRIER_SENSE	cpu_to_le16(0x0020) +#define TYPHOON_MEDIA_STAT_POLARITY_REV		cpu_to_le16(0x0400) +#define TYPHOON_MEDIA_STAT_NO_LINK		cpu_to_le16(0x0800) + +/* TYPHOON_CMD_SET_MULTICAST_HASH enable values (cmd.parm1) + */ +#define TYPHOON_MCAST_HASH_DISABLE	cpu_to_le16(0x0000) +#define TYPHOON_MCAST_HASH_ENABLE	cpu_to_le16(0x0001) +#define TYPHOON_MCAST_HASH_SET		cpu_to_le16(0x0002) + +/* TYPHOON_CMD_CREATE_SA descriptor and settings + */ +struct sa_descriptor { +	u8  flags; +	u8  numDesc; +	u16 cmd; +	u16 seqNo; +	u16 mode; +#define TYPHOON_SA_MODE_NULL		cpu_to_le16(0x0000) +#define TYPHOON_SA_MODE_AH		cpu_to_le16(0x0001) +#define TYPHOON_SA_MODE_ESP		cpu_to_le16(0x0002) +	u8  hashFlags; +#define TYPHOON_SA_HASH_ENABLE		0x01 +#define TYPHOON_SA_HASH_SHA1		0x02 +#define TYPHOON_SA_HASH_MD5		0x04 +	u8  direction; +#define TYPHOON_SA_DIR_RX		0x00 +#define TYPHOON_SA_DIR_TX		0x01 +	u8  encryptionFlags; +#define TYPHOON_SA_ENCRYPT_ENABLE	0x01 +#define TYPHOON_SA_ENCRYPT_DES		0x02 +#define TYPHOON_SA_ENCRYPT_3DES		0x00 +#define TYPHOON_SA_ENCRYPT_3DES_2KEY	0x00 +#define TYPHOON_SA_ENCRYPT_3DES_3KEY	0x04 +#define TYPHOON_SA_ENCRYPT_CBC		0x08 +#define TYPHOON_SA_ENCRYPT_ECB		0x00 +	u8  specifyIndex; +#define TYPHOON_SA_SPECIFY_INDEX	0x01 +#define TYPHOON_SA_GENERATE_INDEX	0x00 +	u32 SPI; +	u32 destAddr; +	u32 destMask; +	u8  integKey[20]; +	u8  confKey[24]; +	u32 index; +	u32 unused; +	u32 unused2; +} __packed; + +/* TYPHOON_CMD_SET_OFFLOAD_TASKS bits (cmd.parm2 (Tx) & cmd.parm3 (Rx)) + * This is all for IPv4. + */ +#define TYPHOON_OFFLOAD_TCP_CHKSUM	cpu_to_le32(0x00000002) +#define TYPHOON_OFFLOAD_UDP_CHKSUM	cpu_to_le32(0x00000004) +#define TYPHOON_OFFLOAD_IP_CHKSUM	cpu_to_le32(0x00000008) +#define TYPHOON_OFFLOAD_IPSEC		cpu_to_le32(0x00000010) +#define TYPHOON_OFFLOAD_BCAST_THROTTLE	cpu_to_le32(0x00000020) +#define TYPHOON_OFFLOAD_DHCP_PREVENT	cpu_to_le32(0x00000040) +#define TYPHOON_OFFLOAD_VLAN		cpu_to_le32(0x00000080) +#define TYPHOON_OFFLOAD_FILTERING	cpu_to_le32(0x00000100) +#define TYPHOON_OFFLOAD_TCP_SEGMENT	cpu_to_le32(0x00000200) + +/* TYPHOON_CMD_ENABLE_WAKE_EVENTS bits (cmd.parm1) + */ +#define TYPHOON_WAKE_MAGIC_PKT		cpu_to_le16(0x01) +#define TYPHOON_WAKE_LINK_EVENT		cpu_to_le16(0x02) +#define TYPHOON_WAKE_ICMP_ECHO		cpu_to_le16(0x04) +#define TYPHOON_WAKE_ARP		cpu_to_le16(0x08) + +/* These are used to load the firmware image on the NIC + */ +struct typhoon_file_header { +	u8  tag[8]; +	__le32 version; +	__le32 numSections; +	__le32 startAddr; +	__le32 hmacDigest[5]; +} __packed; + +struct typhoon_section_header { +	__le32 len; +	u16 checksum; +	u16 reserved; +	__le32 startAddr; +} __packed; + +/* The Typhoon Register offsets + */ +#define TYPHOON_REG_SOFT_RESET			0x00 +#define TYPHOON_REG_INTR_STATUS			0x04 +#define TYPHOON_REG_INTR_ENABLE			0x08 +#define TYPHOON_REG_INTR_MASK			0x0c +#define TYPHOON_REG_SELF_INTERRUPT		0x10 +#define TYPHOON_REG_HOST2ARM7			0x14 +#define TYPHOON_REG_HOST2ARM6			0x18 +#define TYPHOON_REG_HOST2ARM5			0x1c +#define TYPHOON_REG_HOST2ARM4			0x20 +#define TYPHOON_REG_HOST2ARM3			0x24 +#define TYPHOON_REG_HOST2ARM2			0x28 +#define TYPHOON_REG_HOST2ARM1			0x2c +#define TYPHOON_REG_HOST2ARM0			0x30 +#define TYPHOON_REG_ARM2HOST3			0x34 +#define TYPHOON_REG_ARM2HOST2			0x38 +#define TYPHOON_REG_ARM2HOST1			0x3c +#define TYPHOON_REG_ARM2HOST0			0x40 + +#define TYPHOON_REG_BOOT_DATA_LO		TYPHOON_REG_HOST2ARM5 +#define TYPHOON_REG_BOOT_DATA_HI		TYPHOON_REG_HOST2ARM4 +#define TYPHOON_REG_BOOT_DEST_ADDR		TYPHOON_REG_HOST2ARM3 +#define TYPHOON_REG_BOOT_CHECKSUM		TYPHOON_REG_HOST2ARM2 +#define TYPHOON_REG_BOOT_LENGTH			TYPHOON_REG_HOST2ARM1 + +#define TYPHOON_REG_DOWNLOAD_BOOT_ADDR		TYPHOON_REG_HOST2ARM1 +#define TYPHOON_REG_DOWNLOAD_HMAC_0		TYPHOON_REG_HOST2ARM2 +#define TYPHOON_REG_DOWNLOAD_HMAC_1		TYPHOON_REG_HOST2ARM3 +#define TYPHOON_REG_DOWNLOAD_HMAC_2		TYPHOON_REG_HOST2ARM4 +#define TYPHOON_REG_DOWNLOAD_HMAC_3		TYPHOON_REG_HOST2ARM5 +#define TYPHOON_REG_DOWNLOAD_HMAC_4		TYPHOON_REG_HOST2ARM6 + +#define TYPHOON_REG_BOOT_RECORD_ADDR_HI		TYPHOON_REG_HOST2ARM2 +#define TYPHOON_REG_BOOT_RECORD_ADDR_LO		TYPHOON_REG_HOST2ARM1 + +#define TYPHOON_REG_TX_LO_READY			TYPHOON_REG_HOST2ARM3 +#define TYPHOON_REG_CMD_READY			TYPHOON_REG_HOST2ARM2 +#define TYPHOON_REG_TX_HI_READY			TYPHOON_REG_HOST2ARM1 + +#define TYPHOON_REG_COMMAND			TYPHOON_REG_HOST2ARM0 +#define TYPHOON_REG_HEARTBEAT			TYPHOON_REG_ARM2HOST3 +#define TYPHOON_REG_STATUS			TYPHOON_REG_ARM2HOST0 + +/* 3XP Reset values (TYPHOON_REG_SOFT_RESET) + */ +#define TYPHOON_RESET_ALL	0x7f +#define TYPHOON_RESET_NONE	0x00 + +/* 3XP irq bits (TYPHOON_REG_INTR{STATUS,ENABLE,MASK}) + * + * Some of these came from OpenBSD, as the 3Com docs have it wrong + * (INTR_SELF) or don't list it at all (INTR_*_ABORT) + * + * Enabling irqs on the Heartbeat reg (ArmToHost3) gets you an irq + * about every 8ms, so don't do it. + */ +#define TYPHOON_INTR_HOST_INT		0x00000001 +#define TYPHOON_INTR_ARM2HOST0		0x00000002 +#define TYPHOON_INTR_ARM2HOST1		0x00000004 +#define TYPHOON_INTR_ARM2HOST2		0x00000008 +#define TYPHOON_INTR_ARM2HOST3		0x00000010 +#define TYPHOON_INTR_DMA0		0x00000020 +#define TYPHOON_INTR_DMA1		0x00000040 +#define TYPHOON_INTR_DMA2		0x00000080 +#define TYPHOON_INTR_DMA3		0x00000100 +#define TYPHOON_INTR_MASTER_ABORT	0x00000200 +#define TYPHOON_INTR_TARGET_ABORT	0x00000400 +#define TYPHOON_INTR_SELF		0x00000800 +#define TYPHOON_INTR_RESERVED		0xfffff000 + +#define TYPHOON_INTR_BOOTCMD		TYPHOON_INTR_ARM2HOST0 + +#define TYPHOON_INTR_ENABLE_ALL		0xffffffef +#define TYPHOON_INTR_ALL		0xffffffff +#define TYPHOON_INTR_NONE		0x00000000 + +/* The commands for the 3XP chip (TYPHOON_REG_COMMAND) + */ +#define TYPHOON_BOOTCMD_BOOT			0x00 +#define TYPHOON_BOOTCMD_WAKEUP			0xfa +#define TYPHOON_BOOTCMD_DNLD_COMPLETE		0xfb +#define TYPHOON_BOOTCMD_SEG_AVAILABLE		0xfc +#define TYPHOON_BOOTCMD_RUNTIME_IMAGE		0xfd +#define TYPHOON_BOOTCMD_REG_BOOT_RECORD		0xff + +/* 3XP Status values (TYPHOON_REG_STATUS) + */ +#define TYPHOON_STATUS_WAITING_FOR_BOOT		0x07 +#define TYPHOON_STATUS_SECOND_INIT		0x08 +#define TYPHOON_STATUS_RUNNING			0x09 +#define TYPHOON_STATUS_WAITING_FOR_HOST		0x0d +#define TYPHOON_STATUS_WAITING_FOR_SEGMENT	0x10 +#define TYPHOON_STATUS_SLEEPING			0x11 +#define TYPHOON_STATUS_HALTED			0x14  | 
