162306a36Sopenharmony_ci/* 3c574.c: A PCMCIA ethernet driver for the 3com 3c574 "RoadRunner".
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci	Written 1993-1998 by
462306a36Sopenharmony_ci	Donald Becker, becker@scyld.com, (driver core) and
562306a36Sopenharmony_ci	David Hinds, dahinds@users.sourceforge.net (from his PC card code).
662306a36Sopenharmony_ci	Locking fixes (C) Copyright 2003 Red Hat Inc
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci	This software may be used and distributed according to the terms of
962306a36Sopenharmony_ci	the GNU General Public License, incorporated herein by reference.
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci	This driver derives from Donald Becker's 3c509 core, which has the
1262306a36Sopenharmony_ci	following copyright:
1362306a36Sopenharmony_ci	Copyright 1993 United States Government as represented by the
1462306a36Sopenharmony_ci	Director, National Security Agency.
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci*/
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/*
2062306a36Sopenharmony_ci				Theory of Operation
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ciI. Board Compatibility
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ciThis device driver is designed for the 3Com 3c574 PC card Fast Ethernet
2562306a36Sopenharmony_ciAdapter.
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ciII. Board-specific settings
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ciNone -- PC cards are autoconfigured.
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ciIII. Driver operation
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ciThe 3c574 uses a Boomerang-style interface, without the bus-master capability.
3462306a36Sopenharmony_ciSee the Boomerang driver and documentation for most details.
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ciIV. Notes and chip documentation.
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ciTwo added registers are used to enhance PIO performance, RunnerRdCtrl and
3962306a36Sopenharmony_ciRunnerWrCtrl.  These are 11 bit down-counters that are preloaded with the
4062306a36Sopenharmony_cicount of word (16 bits) reads or writes the driver is about to do to the Rx
4162306a36Sopenharmony_cior Tx FIFO.  The chip is then able to hide the internal-PCI-bus to PC-card
4262306a36Sopenharmony_citranslation latency by buffering the I/O operations with an 8 word FIFO.
4362306a36Sopenharmony_ciNote: No other chip accesses are permitted when this buffer is used.
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ciA second enhancement is that both attribute and common memory space
4662306a36Sopenharmony_ci0x0800-0x0fff can translated to the PIO FIFO.  Thus memory operations (faster
4762306a36Sopenharmony_ciwith *some* PCcard bridges) may be used instead of I/O operations.
4862306a36Sopenharmony_ciThis is enabled by setting the 0x10 bit in the PCMCIA LAN COR.
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ciSome slow PC card bridges work better if they never see a WAIT signal.
5162306a36Sopenharmony_ciThis is configured by setting the 0x20 bit in the PCMCIA LAN COR.
5262306a36Sopenharmony_ciOnly do this after testing that it is reliable and improves performance.
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ciThe upper five bits of RunnerRdCtrl are used to window into PCcard
5562306a36Sopenharmony_ciconfiguration space registers.  Window 0 is the regular Boomerang/Odie
5662306a36Sopenharmony_ciregister set, 1-5 are various PC card control registers, and 16-31 are
5762306a36Sopenharmony_cithe (reversed!) CIS table.
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ciA final note: writing the InternalConfig register in window 3 with an
6062306a36Sopenharmony_ciinvalid ramWidth is Very Bad.
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ciV. References
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cihttp://www.scyld.com/expert/NWay.html
6562306a36Sopenharmony_cihttp://www.national.com/opf/DP/DP83840A.html
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ciThanks to Terry Murphy of 3Com for providing development information for
6862306a36Sopenharmony_ciearlier 3Com products.
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci*/
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci#include <linux/module.h>
7562306a36Sopenharmony_ci#include <linux/kernel.h>
7662306a36Sopenharmony_ci#include <linux/slab.h>
7762306a36Sopenharmony_ci#include <linux/string.h>
7862306a36Sopenharmony_ci#include <linux/timer.h>
7962306a36Sopenharmony_ci#include <linux/interrupt.h>
8062306a36Sopenharmony_ci#include <linux/in.h>
8162306a36Sopenharmony_ci#include <linux/delay.h>
8262306a36Sopenharmony_ci#include <linux/netdevice.h>
8362306a36Sopenharmony_ci#include <linux/etherdevice.h>
8462306a36Sopenharmony_ci#include <linux/skbuff.h>
8562306a36Sopenharmony_ci#include <linux/if_arp.h>
8662306a36Sopenharmony_ci#include <linux/ioport.h>
8762306a36Sopenharmony_ci#include <linux/bitops.h>
8862306a36Sopenharmony_ci#include <linux/mii.h>
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci#include <pcmcia/cistpl.h>
9162306a36Sopenharmony_ci#include <pcmcia/cisreg.h>
9262306a36Sopenharmony_ci#include <pcmcia/ciscode.h>
9362306a36Sopenharmony_ci#include <pcmcia/ds.h>
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci#include <linux/uaccess.h>
9662306a36Sopenharmony_ci#include <asm/io.h>
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci/*====================================================================*/
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci/* Module parameters */
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ciMODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>");
10362306a36Sopenharmony_ciMODULE_DESCRIPTION("3Com 3c574 series PCMCIA ethernet driver");
10462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0)
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/* Maximum events (Rx packets, etc.) to handle at each interrupt. */
10962306a36Sopenharmony_ciINT_MODULE_PARM(max_interrupt_work, 32);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci/* Force full duplex modes? */
11262306a36Sopenharmony_ciINT_MODULE_PARM(full_duplex, 0);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci/* Autodetect link polarity reversal? */
11562306a36Sopenharmony_ciINT_MODULE_PARM(auto_polarity, 1);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci/*====================================================================*/
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci/* Time in jiffies before concluding the transmitter is hung. */
12162306a36Sopenharmony_ci#define TX_TIMEOUT  ((800*HZ)/1000)
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci/* To minimize the size of the driver source and make the driver more
12462306a36Sopenharmony_ci   readable not all constants are symbolically defined.
12562306a36Sopenharmony_ci   You'll need the manual if you want to understand driver details anyway. */
12662306a36Sopenharmony_ci/* Offsets from base I/O address. */
12762306a36Sopenharmony_ci#define EL3_DATA	0x00
12862306a36Sopenharmony_ci#define EL3_CMD		0x0e
12962306a36Sopenharmony_ci#define EL3_STATUS	0x0e
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD)
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci/* The top five bits written to EL3_CMD are a command, the lower
13462306a36Sopenharmony_ci   11 bits are the parameter, if applicable. */
13562306a36Sopenharmony_cienum el3_cmds {
13662306a36Sopenharmony_ci	TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11,
13762306a36Sopenharmony_ci	RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11,
13862306a36Sopenharmony_ci	TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11,
13962306a36Sopenharmony_ci	FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11,
14062306a36Sopenharmony_ci	SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11,
14162306a36Sopenharmony_ci	SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11,
14262306a36Sopenharmony_ci	StatsDisable = 22<<11, StopCoax = 23<<11,
14362306a36Sopenharmony_ci};
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cienum elxl_status {
14662306a36Sopenharmony_ci	IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004,
14762306a36Sopenharmony_ci	TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020,
14862306a36Sopenharmony_ci	IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000 };
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci/* The SetRxFilter command accepts the following classes: */
15162306a36Sopenharmony_cienum RxFilter {
15262306a36Sopenharmony_ci	RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8
15362306a36Sopenharmony_ci};
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cienum Window0 {
15662306a36Sopenharmony_ci	Wn0EepromCmd = 10, Wn0EepromData = 12, /* EEPROM command/address, data. */
15762306a36Sopenharmony_ci	IntrStatus=0x0E,		/* Valid in all windows. */
15862306a36Sopenharmony_ci};
15962306a36Sopenharmony_ci/* These assumes the larger EEPROM. */
16062306a36Sopenharmony_cienum Win0_EEPROM_cmds {
16162306a36Sopenharmony_ci	EEPROM_Read = 0x200, EEPROM_WRITE = 0x100, EEPROM_ERASE = 0x300,
16262306a36Sopenharmony_ci	EEPROM_EWENB = 0x30,		/* Enable erasing/writing for 10 msec. */
16362306a36Sopenharmony_ci	EEPROM_EWDIS = 0x00,		/* Disable EWENB before 10 msec timeout. */
16462306a36Sopenharmony_ci};
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci/* Register window 1 offsets, the window used in normal operation.
16762306a36Sopenharmony_ci   On the "Odie" this window is always mapped at offsets 0x10-0x1f.
16862306a36Sopenharmony_ci   Except for TxFree, which is overlapped by RunnerWrCtrl. */
16962306a36Sopenharmony_cienum Window1 {
17062306a36Sopenharmony_ci	TX_FIFO = 0x10,  RX_FIFO = 0x10,  RxErrors = 0x14,
17162306a36Sopenharmony_ci	RxStatus = 0x18,  Timer=0x1A, TxStatus = 0x1B,
17262306a36Sopenharmony_ci	TxFree = 0x0C, /* Remaining free bytes in Tx buffer. */
17362306a36Sopenharmony_ci	RunnerRdCtrl = 0x16, RunnerWrCtrl = 0x1c,
17462306a36Sopenharmony_ci};
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cienum Window3 {			/* Window 3: MAC/config bits. */
17762306a36Sopenharmony_ci	Wn3_Config=0, Wn3_MAC_Ctrl=6, Wn3_Options=8,
17862306a36Sopenharmony_ci};
17962306a36Sopenharmony_cienum wn3_config {
18062306a36Sopenharmony_ci	Ram_size = 7,
18162306a36Sopenharmony_ci	Ram_width = 8,
18262306a36Sopenharmony_ci	Ram_speed = 0x30,
18362306a36Sopenharmony_ci	Rom_size = 0xc0,
18462306a36Sopenharmony_ci	Ram_split_shift = 16,
18562306a36Sopenharmony_ci	Ram_split = 3 << Ram_split_shift,
18662306a36Sopenharmony_ci	Xcvr_shift = 20,
18762306a36Sopenharmony_ci	Xcvr = 7 << Xcvr_shift,
18862306a36Sopenharmony_ci	Autoselect = 0x1000000,
18962306a36Sopenharmony_ci};
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cienum Window4 {		/* Window 4: Xcvr/media bits. */
19262306a36Sopenharmony_ci	Wn4_FIFODiag = 4, Wn4_NetDiag = 6, Wn4_PhysicalMgmt=8, Wn4_Media = 10,
19362306a36Sopenharmony_ci};
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci#define MEDIA_TP	0x00C0	/* Enable link beat and jabber for 10baseT. */
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistruct el3_private {
19862306a36Sopenharmony_ci	struct pcmcia_device	*p_dev;
19962306a36Sopenharmony_ci	u16 advertising, partner;		/* NWay media advertisement */
20062306a36Sopenharmony_ci	unsigned char phys;			/* MII device address */
20162306a36Sopenharmony_ci	unsigned int autoselect:1, default_media:3;	/* Read from the EEPROM/Wn3_Config. */
20262306a36Sopenharmony_ci	/* for transceiver monitoring */
20362306a36Sopenharmony_ci	struct timer_list media;
20462306a36Sopenharmony_ci	unsigned short media_status;
20562306a36Sopenharmony_ci	unsigned short fast_poll;
20662306a36Sopenharmony_ci	unsigned long last_irq;
20762306a36Sopenharmony_ci	spinlock_t window_lock;			/* Guards the Window selection */
20862306a36Sopenharmony_ci};
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci/* Set iff a MII transceiver on any interface requires mdio preamble.
21162306a36Sopenharmony_ci   This only set with the original DP83840 on older 3c905 boards, so the extra
21262306a36Sopenharmony_ci   code size of a per-interface flag is not worthwhile. */
21362306a36Sopenharmony_cistatic char mii_preamble_required = 0;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci/* Index of functions. */
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic int tc574_config(struct pcmcia_device *link);
21862306a36Sopenharmony_cistatic void tc574_release(struct pcmcia_device *link);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_cistatic void mdio_sync(unsigned int ioaddr, int bits);
22162306a36Sopenharmony_cistatic int mdio_read(unsigned int ioaddr, int phy_id, int location);
22262306a36Sopenharmony_cistatic void mdio_write(unsigned int ioaddr, int phy_id, int location,
22362306a36Sopenharmony_ci		       int value);
22462306a36Sopenharmony_cistatic unsigned short read_eeprom(unsigned int ioaddr, int index);
22562306a36Sopenharmony_cistatic void tc574_wait_for_completion(struct net_device *dev, int cmd);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic void tc574_reset(struct net_device *dev);
22862306a36Sopenharmony_cistatic void media_check(struct timer_list *t);
22962306a36Sopenharmony_cistatic int el3_open(struct net_device *dev);
23062306a36Sopenharmony_cistatic netdev_tx_t el3_start_xmit(struct sk_buff *skb,
23162306a36Sopenharmony_ci					struct net_device *dev);
23262306a36Sopenharmony_cistatic irqreturn_t el3_interrupt(int irq, void *dev_id);
23362306a36Sopenharmony_cistatic void update_stats(struct net_device *dev);
23462306a36Sopenharmony_cistatic struct net_device_stats *el3_get_stats(struct net_device *dev);
23562306a36Sopenharmony_cistatic int el3_rx(struct net_device *dev, int worklimit);
23662306a36Sopenharmony_cistatic int el3_close(struct net_device *dev);
23762306a36Sopenharmony_cistatic void el3_tx_timeout(struct net_device *dev, unsigned int txqueue);
23862306a36Sopenharmony_cistatic int el3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
23962306a36Sopenharmony_cistatic void set_rx_mode(struct net_device *dev);
24062306a36Sopenharmony_cistatic void set_multicast_list(struct net_device *dev);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cistatic void tc574_detach(struct pcmcia_device *p_dev);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci/*
24562306a36Sopenharmony_ci	tc574_attach() creates an "instance" of the driver, allocating
24662306a36Sopenharmony_ci	local data structures for one device.  The device is registered
24762306a36Sopenharmony_ci	with Card Services.
24862306a36Sopenharmony_ci*/
24962306a36Sopenharmony_cistatic const struct net_device_ops el3_netdev_ops = {
25062306a36Sopenharmony_ci	.ndo_open 		= el3_open,
25162306a36Sopenharmony_ci	.ndo_stop 		= el3_close,
25262306a36Sopenharmony_ci	.ndo_start_xmit		= el3_start_xmit,
25362306a36Sopenharmony_ci	.ndo_tx_timeout 	= el3_tx_timeout,
25462306a36Sopenharmony_ci	.ndo_get_stats		= el3_get_stats,
25562306a36Sopenharmony_ci	.ndo_eth_ioctl		= el3_ioctl,
25662306a36Sopenharmony_ci	.ndo_set_rx_mode	= set_multicast_list,
25762306a36Sopenharmony_ci	.ndo_set_mac_address 	= eth_mac_addr,
25862306a36Sopenharmony_ci	.ndo_validate_addr	= eth_validate_addr,
25962306a36Sopenharmony_ci};
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic int tc574_probe(struct pcmcia_device *link)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	struct el3_private *lp;
26462306a36Sopenharmony_ci	struct net_device *dev;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	dev_dbg(&link->dev, "3c574_attach()\n");
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	/* Create the PC card device object. */
26962306a36Sopenharmony_ci	dev = alloc_etherdev(sizeof(struct el3_private));
27062306a36Sopenharmony_ci	if (!dev)
27162306a36Sopenharmony_ci		return -ENOMEM;
27262306a36Sopenharmony_ci	lp = netdev_priv(dev);
27362306a36Sopenharmony_ci	link->priv = dev;
27462306a36Sopenharmony_ci	lp->p_dev = link;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	spin_lock_init(&lp->window_lock);
27762306a36Sopenharmony_ci	link->resource[0]->end = 32;
27862306a36Sopenharmony_ci	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_16;
27962306a36Sopenharmony_ci	link->config_flags |= CONF_ENABLE_IRQ;
28062306a36Sopenharmony_ci	link->config_index = 1;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	dev->netdev_ops = &el3_netdev_ops;
28362306a36Sopenharmony_ci	dev->watchdog_timeo = TX_TIMEOUT;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	return tc574_config(link);
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic void tc574_detach(struct pcmcia_device *link)
28962306a36Sopenharmony_ci{
29062306a36Sopenharmony_ci	struct net_device *dev = link->priv;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	dev_dbg(&link->dev, "3c574_detach()\n");
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	unregister_netdev(dev);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	tc574_release(link);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	free_netdev(dev);
29962306a36Sopenharmony_ci} /* tc574_detach */
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_cistatic const char *ram_split[] = {"5:3", "3:1", "1:1", "3:5"};
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic int tc574_config(struct pcmcia_device *link)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	struct net_device *dev = link->priv;
30662306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
30762306a36Sopenharmony_ci	int ret, i, j;
30862306a36Sopenharmony_ci	__be16 addr[ETH_ALEN / 2];
30962306a36Sopenharmony_ci	unsigned int ioaddr;
31062306a36Sopenharmony_ci	char *cardname;
31162306a36Sopenharmony_ci	__u32 config;
31262306a36Sopenharmony_ci	u8 *buf;
31362306a36Sopenharmony_ci	size_t len;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	dev_dbg(&link->dev, "3c574_config()\n");
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	link->io_lines = 16;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	for (i = j = 0; j < 0x400; j += 0x20) {
32062306a36Sopenharmony_ci		link->resource[0]->start = j ^ 0x300;
32162306a36Sopenharmony_ci		i = pcmcia_request_io(link);
32262306a36Sopenharmony_ci		if (i == 0)
32362306a36Sopenharmony_ci			break;
32462306a36Sopenharmony_ci	}
32562306a36Sopenharmony_ci	if (i != 0)
32662306a36Sopenharmony_ci		goto failed;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	ret = pcmcia_request_irq(link, el3_interrupt);
32962306a36Sopenharmony_ci	if (ret)
33062306a36Sopenharmony_ci		goto failed;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	ret = pcmcia_enable_device(link);
33362306a36Sopenharmony_ci	if (ret)
33462306a36Sopenharmony_ci		goto failed;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	dev->irq = link->irq;
33762306a36Sopenharmony_ci	dev->base_addr = link->resource[0]->start;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	ioaddr = dev->base_addr;
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	/* The 3c574 normally uses an EEPROM for configuration info, including
34262306a36Sopenharmony_ci	   the hardware address.  The future products may include a modem chip
34362306a36Sopenharmony_ci	   and put the address in the CIS. */
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	len = pcmcia_get_tuple(link, 0x88, &buf);
34662306a36Sopenharmony_ci	if (buf && len >= 6) {
34762306a36Sopenharmony_ci		for (i = 0; i < 3; i++)
34862306a36Sopenharmony_ci			addr[i] = htons(le16_to_cpu(buf[i * 2]));
34962306a36Sopenharmony_ci		kfree(buf);
35062306a36Sopenharmony_ci	} else {
35162306a36Sopenharmony_ci		kfree(buf); /* 0 < len < 6 */
35262306a36Sopenharmony_ci		EL3WINDOW(0);
35362306a36Sopenharmony_ci		for (i = 0; i < 3; i++)
35462306a36Sopenharmony_ci			addr[i] = htons(read_eeprom(ioaddr, i + 10));
35562306a36Sopenharmony_ci		if (addr[0] == htons(0x6060)) {
35662306a36Sopenharmony_ci			pr_notice("IO port conflict at 0x%03lx-0x%03lx\n",
35762306a36Sopenharmony_ci				  dev->base_addr, dev->base_addr+15);
35862306a36Sopenharmony_ci			goto failed;
35962306a36Sopenharmony_ci		}
36062306a36Sopenharmony_ci	}
36162306a36Sopenharmony_ci	eth_hw_addr_set(dev, (u8 *)addr);
36262306a36Sopenharmony_ci	if (link->prod_id[1])
36362306a36Sopenharmony_ci		cardname = link->prod_id[1];
36462306a36Sopenharmony_ci	else
36562306a36Sopenharmony_ci		cardname = "3Com 3c574";
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	{
36862306a36Sopenharmony_ci		u_char mcr;
36962306a36Sopenharmony_ci		outw(2<<11, ioaddr + RunnerRdCtrl);
37062306a36Sopenharmony_ci		mcr = inb(ioaddr + 2);
37162306a36Sopenharmony_ci		outw(0<<11, ioaddr + RunnerRdCtrl);
37262306a36Sopenharmony_ci		pr_info("  ASIC rev %d,", mcr>>3);
37362306a36Sopenharmony_ci		EL3WINDOW(3);
37462306a36Sopenharmony_ci		config = inl(ioaddr + Wn3_Config);
37562306a36Sopenharmony_ci		lp->default_media = (config & Xcvr) >> Xcvr_shift;
37662306a36Sopenharmony_ci		lp->autoselect = config & Autoselect ? 1 : 0;
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	timer_setup(&lp->media, media_check, 0);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	{
38262306a36Sopenharmony_ci		int phy;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci		/* Roadrunner only: Turn on the MII transceiver */
38562306a36Sopenharmony_ci		outw(0x8040, ioaddr + Wn3_Options);
38662306a36Sopenharmony_ci		mdelay(1);
38762306a36Sopenharmony_ci		outw(0xc040, ioaddr + Wn3_Options);
38862306a36Sopenharmony_ci		tc574_wait_for_completion(dev, TxReset);
38962306a36Sopenharmony_ci		tc574_wait_for_completion(dev, RxReset);
39062306a36Sopenharmony_ci		mdelay(1);
39162306a36Sopenharmony_ci		outw(0x8040, ioaddr + Wn3_Options);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci		EL3WINDOW(4);
39462306a36Sopenharmony_ci		for (phy = 1; phy <= 32; phy++) {
39562306a36Sopenharmony_ci			int mii_status;
39662306a36Sopenharmony_ci			mdio_sync(ioaddr, 32);
39762306a36Sopenharmony_ci			mii_status = mdio_read(ioaddr, phy & 0x1f, 1);
39862306a36Sopenharmony_ci			if (mii_status != 0xffff) {
39962306a36Sopenharmony_ci				lp->phys = phy & 0x1f;
40062306a36Sopenharmony_ci				dev_dbg(&link->dev, "  MII transceiver at "
40162306a36Sopenharmony_ci					"index %d, status %x.\n",
40262306a36Sopenharmony_ci					  phy, mii_status);
40362306a36Sopenharmony_ci				if ((mii_status & 0x0040) == 0)
40462306a36Sopenharmony_ci					mii_preamble_required = 1;
40562306a36Sopenharmony_ci				break;
40662306a36Sopenharmony_ci			}
40762306a36Sopenharmony_ci		}
40862306a36Sopenharmony_ci		if (phy > 32) {
40962306a36Sopenharmony_ci			pr_notice("  No MII transceivers found!\n");
41062306a36Sopenharmony_ci			goto failed;
41162306a36Sopenharmony_ci		}
41262306a36Sopenharmony_ci		i = mdio_read(ioaddr, lp->phys, 16) | 0x40;
41362306a36Sopenharmony_ci		mdio_write(ioaddr, lp->phys, 16, i);
41462306a36Sopenharmony_ci		lp->advertising = mdio_read(ioaddr, lp->phys, 4);
41562306a36Sopenharmony_ci		if (full_duplex) {
41662306a36Sopenharmony_ci			/* Only advertise the FD media types. */
41762306a36Sopenharmony_ci			lp->advertising &= ~0x02a0;
41862306a36Sopenharmony_ci			mdio_write(ioaddr, lp->phys, 4, lp->advertising);
41962306a36Sopenharmony_ci		}
42062306a36Sopenharmony_ci	}
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	SET_NETDEV_DEV(dev, &link->dev);
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	if (register_netdev(dev) != 0) {
42562306a36Sopenharmony_ci		pr_notice("register_netdev() failed\n");
42662306a36Sopenharmony_ci		goto failed;
42762306a36Sopenharmony_ci	}
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	netdev_info(dev, "%s at io %#3lx, irq %d, hw_addr %pM\n",
43062306a36Sopenharmony_ci		    cardname, dev->base_addr, dev->irq, dev->dev_addr);
43162306a36Sopenharmony_ci	netdev_info(dev, " %dK FIFO split %s Rx:Tx, %sMII interface.\n",
43262306a36Sopenharmony_ci		    8 << (config & Ram_size),
43362306a36Sopenharmony_ci		    ram_split[(config & Ram_split) >> Ram_split_shift],
43462306a36Sopenharmony_ci		    config & Autoselect ? "autoselect " : "");
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	return 0;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_cifailed:
43962306a36Sopenharmony_ci	tc574_release(link);
44062306a36Sopenharmony_ci	return -ENODEV;
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci} /* tc574_config */
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_cistatic void tc574_release(struct pcmcia_device *link)
44562306a36Sopenharmony_ci{
44662306a36Sopenharmony_ci	pcmcia_disable_device(link);
44762306a36Sopenharmony_ci}
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_cistatic int tc574_suspend(struct pcmcia_device *link)
45062306a36Sopenharmony_ci{
45162306a36Sopenharmony_ci	struct net_device *dev = link->priv;
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	if (link->open)
45462306a36Sopenharmony_ci		netif_device_detach(dev);
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	return 0;
45762306a36Sopenharmony_ci}
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_cistatic int tc574_resume(struct pcmcia_device *link)
46062306a36Sopenharmony_ci{
46162306a36Sopenharmony_ci	struct net_device *dev = link->priv;
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	if (link->open) {
46462306a36Sopenharmony_ci		tc574_reset(dev);
46562306a36Sopenharmony_ci		netif_device_attach(dev);
46662306a36Sopenharmony_ci	}
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	return 0;
46962306a36Sopenharmony_ci}
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_cistatic void dump_status(struct net_device *dev)
47262306a36Sopenharmony_ci{
47362306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
47462306a36Sopenharmony_ci	EL3WINDOW(1);
47562306a36Sopenharmony_ci	netdev_info(dev, "  irq status %04x, rx status %04x, tx status %02x, tx free %04x\n",
47662306a36Sopenharmony_ci		    inw(ioaddr+EL3_STATUS),
47762306a36Sopenharmony_ci		    inw(ioaddr+RxStatus), inb(ioaddr+TxStatus),
47862306a36Sopenharmony_ci		    inw(ioaddr+TxFree));
47962306a36Sopenharmony_ci	EL3WINDOW(4);
48062306a36Sopenharmony_ci	netdev_info(dev, "  diagnostics: fifo %04x net %04x ethernet %04x media %04x\n",
48162306a36Sopenharmony_ci		    inw(ioaddr+0x04), inw(ioaddr+0x06),
48262306a36Sopenharmony_ci		    inw(ioaddr+0x08), inw(ioaddr+0x0a));
48362306a36Sopenharmony_ci	EL3WINDOW(1);
48462306a36Sopenharmony_ci}
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci/*
48762306a36Sopenharmony_ci  Use this for commands that may take time to finish
48862306a36Sopenharmony_ci*/
48962306a36Sopenharmony_cistatic void tc574_wait_for_completion(struct net_device *dev, int cmd)
49062306a36Sopenharmony_ci{
49162306a36Sopenharmony_ci	int i = 1500;
49262306a36Sopenharmony_ci	outw(cmd, dev->base_addr + EL3_CMD);
49362306a36Sopenharmony_ci	while (--i > 0)
49462306a36Sopenharmony_ci		if (!(inw(dev->base_addr + EL3_STATUS) & 0x1000)) break;
49562306a36Sopenharmony_ci	if (i == 0)
49662306a36Sopenharmony_ci		netdev_notice(dev, "command 0x%04x did not complete!\n", cmd);
49762306a36Sopenharmony_ci}
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci/* Read a word from the EEPROM using the regular EEPROM access register.
50062306a36Sopenharmony_ci   Assume that we are in register window zero.
50162306a36Sopenharmony_ci */
50262306a36Sopenharmony_cistatic unsigned short read_eeprom(unsigned int ioaddr, int index)
50362306a36Sopenharmony_ci{
50462306a36Sopenharmony_ci	int timer;
50562306a36Sopenharmony_ci	outw(EEPROM_Read + index, ioaddr + Wn0EepromCmd);
50662306a36Sopenharmony_ci	/* Pause for at least 162 usec for the read to take place. */
50762306a36Sopenharmony_ci	for (timer = 1620; timer >= 0; timer--) {
50862306a36Sopenharmony_ci		if ((inw(ioaddr + Wn0EepromCmd) & 0x8000) == 0)
50962306a36Sopenharmony_ci			break;
51062306a36Sopenharmony_ci	}
51162306a36Sopenharmony_ci	return inw(ioaddr + Wn0EepromData);
51262306a36Sopenharmony_ci}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci/* MII transceiver control section.
51562306a36Sopenharmony_ci   Read and write the MII registers using software-generated serial
51662306a36Sopenharmony_ci   MDIO protocol.  See the MII specifications or DP83840A data sheet
51762306a36Sopenharmony_ci   for details.
51862306a36Sopenharmony_ci   The maxium data clock rate is 2.5 Mhz.  The timing is easily met by the
51962306a36Sopenharmony_ci   slow PC card interface. */
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci#define MDIO_SHIFT_CLK	0x01
52262306a36Sopenharmony_ci#define MDIO_DIR_WRITE	0x04
52362306a36Sopenharmony_ci#define MDIO_DATA_WRITE0 (0x00 | MDIO_DIR_WRITE)
52462306a36Sopenharmony_ci#define MDIO_DATA_WRITE1 (0x02 | MDIO_DIR_WRITE)
52562306a36Sopenharmony_ci#define MDIO_DATA_READ	0x02
52662306a36Sopenharmony_ci#define MDIO_ENB_IN		0x00
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci/* Generate the preamble required for initial synchronization and
52962306a36Sopenharmony_ci   a few older transceivers. */
53062306a36Sopenharmony_cistatic void mdio_sync(unsigned int ioaddr, int bits)
53162306a36Sopenharmony_ci{
53262306a36Sopenharmony_ci	unsigned int mdio_addr = ioaddr + Wn4_PhysicalMgmt;
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	/* Establish sync by sending at least 32 logic ones. */
53562306a36Sopenharmony_ci	while (-- bits >= 0) {
53662306a36Sopenharmony_ci		outw(MDIO_DATA_WRITE1, mdio_addr);
53762306a36Sopenharmony_ci		outw(MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr);
53862306a36Sopenharmony_ci	}
53962306a36Sopenharmony_ci}
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_cistatic int mdio_read(unsigned int ioaddr, int phy_id, int location)
54262306a36Sopenharmony_ci{
54362306a36Sopenharmony_ci	int i;
54462306a36Sopenharmony_ci	int read_cmd = (0xf6 << 10) | (phy_id << 5) | location;
54562306a36Sopenharmony_ci	unsigned int retval = 0;
54662306a36Sopenharmony_ci	unsigned int mdio_addr = ioaddr + Wn4_PhysicalMgmt;
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	if (mii_preamble_required)
54962306a36Sopenharmony_ci		mdio_sync(ioaddr, 32);
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci	/* Shift the read command bits out. */
55262306a36Sopenharmony_ci	for (i = 14; i >= 0; i--) {
55362306a36Sopenharmony_ci		int dataval = (read_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
55462306a36Sopenharmony_ci		outw(dataval, mdio_addr);
55562306a36Sopenharmony_ci		outw(dataval | MDIO_SHIFT_CLK, mdio_addr);
55662306a36Sopenharmony_ci	}
55762306a36Sopenharmony_ci	/* Read the two transition, 16 data, and wire-idle bits. */
55862306a36Sopenharmony_ci	for (i = 19; i > 0; i--) {
55962306a36Sopenharmony_ci		outw(MDIO_ENB_IN, mdio_addr);
56062306a36Sopenharmony_ci		retval = (retval << 1) | ((inw(mdio_addr) & MDIO_DATA_READ) ? 1 : 0);
56162306a36Sopenharmony_ci		outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr);
56262306a36Sopenharmony_ci	}
56362306a36Sopenharmony_ci	return (retval>>1) & 0xffff;
56462306a36Sopenharmony_ci}
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_cistatic void mdio_write(unsigned int ioaddr, int phy_id, int location, int value)
56762306a36Sopenharmony_ci{
56862306a36Sopenharmony_ci	int write_cmd = 0x50020000 | (phy_id << 23) | (location << 18) | value;
56962306a36Sopenharmony_ci	unsigned int mdio_addr = ioaddr + Wn4_PhysicalMgmt;
57062306a36Sopenharmony_ci	int i;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	if (mii_preamble_required)
57362306a36Sopenharmony_ci		mdio_sync(ioaddr, 32);
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	/* Shift the command bits out. */
57662306a36Sopenharmony_ci	for (i = 31; i >= 0; i--) {
57762306a36Sopenharmony_ci		int dataval = (write_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
57862306a36Sopenharmony_ci		outw(dataval, mdio_addr);
57962306a36Sopenharmony_ci		outw(dataval | MDIO_SHIFT_CLK, mdio_addr);
58062306a36Sopenharmony_ci	}
58162306a36Sopenharmony_ci	/* Leave the interface idle. */
58262306a36Sopenharmony_ci	for (i = 1; i >= 0; i--) {
58362306a36Sopenharmony_ci		outw(MDIO_ENB_IN, mdio_addr);
58462306a36Sopenharmony_ci		outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr);
58562306a36Sopenharmony_ci	}
58662306a36Sopenharmony_ci}
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci/* Reset and restore all of the 3c574 registers. */
58962306a36Sopenharmony_cistatic void tc574_reset(struct net_device *dev)
59062306a36Sopenharmony_ci{
59162306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
59262306a36Sopenharmony_ci	int i;
59362306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
59462306a36Sopenharmony_ci	unsigned long flags;
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci	tc574_wait_for_completion(dev, TotalReset|0x10);
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	spin_lock_irqsave(&lp->window_lock, flags);
59962306a36Sopenharmony_ci	/* Clear any transactions in progress. */
60062306a36Sopenharmony_ci	outw(0, ioaddr + RunnerWrCtrl);
60162306a36Sopenharmony_ci	outw(0, ioaddr + RunnerRdCtrl);
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	/* Set the station address and mask. */
60462306a36Sopenharmony_ci	EL3WINDOW(2);
60562306a36Sopenharmony_ci	for (i = 0; i < 6; i++)
60662306a36Sopenharmony_ci		outb(dev->dev_addr[i], ioaddr + i);
60762306a36Sopenharmony_ci	for (; i < 12; i+=2)
60862306a36Sopenharmony_ci		outw(0, ioaddr + i);
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	/* Reset config options */
61162306a36Sopenharmony_ci	EL3WINDOW(3);
61262306a36Sopenharmony_ci	outb((dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl);
61362306a36Sopenharmony_ci	outl((lp->autoselect ? 0x01000000 : 0) | 0x0062001b,
61462306a36Sopenharmony_ci		 ioaddr + Wn3_Config);
61562306a36Sopenharmony_ci	/* Roadrunner only: Turn on the MII transceiver. */
61662306a36Sopenharmony_ci	outw(0x8040, ioaddr + Wn3_Options);
61762306a36Sopenharmony_ci	mdelay(1);
61862306a36Sopenharmony_ci	outw(0xc040, ioaddr + Wn3_Options);
61962306a36Sopenharmony_ci	EL3WINDOW(1);
62062306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->window_lock, flags);
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci	tc574_wait_for_completion(dev, TxReset);
62362306a36Sopenharmony_ci	tc574_wait_for_completion(dev, RxReset);
62462306a36Sopenharmony_ci	mdelay(1);
62562306a36Sopenharmony_ci	spin_lock_irqsave(&lp->window_lock, flags);
62662306a36Sopenharmony_ci	EL3WINDOW(3);
62762306a36Sopenharmony_ci	outw(0x8040, ioaddr + Wn3_Options);
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	/* Switch to the stats window, and clear all stats by reading. */
63062306a36Sopenharmony_ci	outw(StatsDisable, ioaddr + EL3_CMD);
63162306a36Sopenharmony_ci	EL3WINDOW(6);
63262306a36Sopenharmony_ci	for (i = 0; i < 10; i++)
63362306a36Sopenharmony_ci		inb(ioaddr + i);
63462306a36Sopenharmony_ci	inw(ioaddr + 10);
63562306a36Sopenharmony_ci	inw(ioaddr + 12);
63662306a36Sopenharmony_ci	EL3WINDOW(4);
63762306a36Sopenharmony_ci	inb(ioaddr + 12);
63862306a36Sopenharmony_ci	inb(ioaddr + 13);
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_ci	/* .. enable any extra statistics bits.. */
64162306a36Sopenharmony_ci	outw(0x0040, ioaddr + Wn4_NetDiag);
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci	EL3WINDOW(1);
64462306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->window_lock, flags);
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	/* .. re-sync MII and re-fill what NWay is advertising. */
64762306a36Sopenharmony_ci	mdio_sync(ioaddr, 32);
64862306a36Sopenharmony_ci	mdio_write(ioaddr, lp->phys, 4, lp->advertising);
64962306a36Sopenharmony_ci	if (!auto_polarity) {
65062306a36Sopenharmony_ci		/* works for TDK 78Q2120 series MII's */
65162306a36Sopenharmony_ci		i = mdio_read(ioaddr, lp->phys, 16) | 0x20;
65262306a36Sopenharmony_ci		mdio_write(ioaddr, lp->phys, 16, i);
65362306a36Sopenharmony_ci	}
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	spin_lock_irqsave(&lp->window_lock, flags);
65662306a36Sopenharmony_ci	/* Switch to register set 1 for normal use, just for TxFree. */
65762306a36Sopenharmony_ci	set_rx_mode(dev);
65862306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->window_lock, flags);
65962306a36Sopenharmony_ci	outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */
66062306a36Sopenharmony_ci	outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */
66162306a36Sopenharmony_ci	outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */
66262306a36Sopenharmony_ci	/* Allow status bits to be seen. */
66362306a36Sopenharmony_ci	outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD);
66462306a36Sopenharmony_ci	/* Ack all pending events, and set active indicator mask. */
66562306a36Sopenharmony_ci	outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq,
66662306a36Sopenharmony_ci		 ioaddr + EL3_CMD);
66762306a36Sopenharmony_ci	outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull
66862306a36Sopenharmony_ci		 | AdapterFailure | RxEarly, ioaddr + EL3_CMD);
66962306a36Sopenharmony_ci}
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_cistatic int el3_open(struct net_device *dev)
67262306a36Sopenharmony_ci{
67362306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
67462306a36Sopenharmony_ci	struct pcmcia_device *link = lp->p_dev;
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	if (!pcmcia_dev_present(link))
67762306a36Sopenharmony_ci		return -ENODEV;
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	link->open++;
68062306a36Sopenharmony_ci	netif_start_queue(dev);
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	tc574_reset(dev);
68362306a36Sopenharmony_ci	lp->media.expires = jiffies + HZ;
68462306a36Sopenharmony_ci	add_timer(&lp->media);
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	dev_dbg(&link->dev, "%s: opened, status %4.4x.\n",
68762306a36Sopenharmony_ci		  dev->name, inw(dev->base_addr + EL3_STATUS));
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	return 0;
69062306a36Sopenharmony_ci}
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_cistatic void el3_tx_timeout(struct net_device *dev, unsigned int txqueue)
69362306a36Sopenharmony_ci{
69462306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
69562306a36Sopenharmony_ci
69662306a36Sopenharmony_ci	netdev_notice(dev, "Transmit timed out!\n");
69762306a36Sopenharmony_ci	dump_status(dev);
69862306a36Sopenharmony_ci	dev->stats.tx_errors++;
69962306a36Sopenharmony_ci	netif_trans_update(dev); /* prevent tx timeout */
70062306a36Sopenharmony_ci	/* Issue TX_RESET and TX_START commands. */
70162306a36Sopenharmony_ci	tc574_wait_for_completion(dev, TxReset);
70262306a36Sopenharmony_ci	outw(TxEnable, ioaddr + EL3_CMD);
70362306a36Sopenharmony_ci	netif_wake_queue(dev);
70462306a36Sopenharmony_ci}
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_cistatic void pop_tx_status(struct net_device *dev)
70762306a36Sopenharmony_ci{
70862306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
70962306a36Sopenharmony_ci	int i;
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	/* Clear the Tx status stack. */
71262306a36Sopenharmony_ci	for (i = 32; i > 0; i--) {
71362306a36Sopenharmony_ci		u_char tx_status = inb(ioaddr + TxStatus);
71462306a36Sopenharmony_ci		if (!(tx_status & 0x84))
71562306a36Sopenharmony_ci			break;
71662306a36Sopenharmony_ci		/* reset transmitter on jabber error or underrun */
71762306a36Sopenharmony_ci		if (tx_status & 0x30)
71862306a36Sopenharmony_ci			tc574_wait_for_completion(dev, TxReset);
71962306a36Sopenharmony_ci		if (tx_status & 0x38) {
72062306a36Sopenharmony_ci			pr_debug("%s: transmit error: status 0x%02x\n",
72162306a36Sopenharmony_ci				  dev->name, tx_status);
72262306a36Sopenharmony_ci			outw(TxEnable, ioaddr + EL3_CMD);
72362306a36Sopenharmony_ci			dev->stats.tx_aborted_errors++;
72462306a36Sopenharmony_ci		}
72562306a36Sopenharmony_ci		outb(0x00, ioaddr + TxStatus); /* Pop the status stack. */
72662306a36Sopenharmony_ci	}
72762306a36Sopenharmony_ci}
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_cistatic netdev_tx_t el3_start_xmit(struct sk_buff *skb,
73062306a36Sopenharmony_ci					struct net_device *dev)
73162306a36Sopenharmony_ci{
73262306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
73362306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
73462306a36Sopenharmony_ci	unsigned long flags;
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci	pr_debug("%s: el3_start_xmit(length = %ld) called, "
73762306a36Sopenharmony_ci		  "status %4.4x.\n", dev->name, (long)skb->len,
73862306a36Sopenharmony_ci		  inw(ioaddr + EL3_STATUS));
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	spin_lock_irqsave(&lp->window_lock, flags);
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci	dev->stats.tx_bytes += skb->len;
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_ci	/* Put out the doubleword header... */
74562306a36Sopenharmony_ci	outw(skb->len, ioaddr + TX_FIFO);
74662306a36Sopenharmony_ci	outw(0, ioaddr + TX_FIFO);
74762306a36Sopenharmony_ci	/* ... and the packet rounded to a doubleword. */
74862306a36Sopenharmony_ci	outsl(ioaddr + TX_FIFO, skb->data, (skb->len+3)>>2);
74962306a36Sopenharmony_ci
75062306a36Sopenharmony_ci	/* TxFree appears only in Window 1, not offset 0x1c. */
75162306a36Sopenharmony_ci	if (inw(ioaddr + TxFree) <= 1536) {
75262306a36Sopenharmony_ci		netif_stop_queue(dev);
75362306a36Sopenharmony_ci		/* Interrupt us when the FIFO has room for max-sized packet.
75462306a36Sopenharmony_ci		   The threshold is in units of dwords. */
75562306a36Sopenharmony_ci		outw(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD);
75662306a36Sopenharmony_ci	}
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	pop_tx_status(dev);
75962306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->window_lock, flags);
76062306a36Sopenharmony_ci	dev_kfree_skb(skb);
76162306a36Sopenharmony_ci	return NETDEV_TX_OK;
76262306a36Sopenharmony_ci}
76362306a36Sopenharmony_ci
76462306a36Sopenharmony_ci/* The EL3 interrupt handler. */
76562306a36Sopenharmony_cistatic irqreturn_t el3_interrupt(int irq, void *dev_id)
76662306a36Sopenharmony_ci{
76762306a36Sopenharmony_ci	struct net_device *dev = (struct net_device *) dev_id;
76862306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
76962306a36Sopenharmony_ci	unsigned int ioaddr;
77062306a36Sopenharmony_ci	unsigned status;
77162306a36Sopenharmony_ci	int work_budget = max_interrupt_work;
77262306a36Sopenharmony_ci	int handled = 0;
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ci	if (!netif_device_present(dev))
77562306a36Sopenharmony_ci		return IRQ_NONE;
77662306a36Sopenharmony_ci	ioaddr = dev->base_addr;
77762306a36Sopenharmony_ci
77862306a36Sopenharmony_ci	pr_debug("%s: interrupt, status %4.4x.\n",
77962306a36Sopenharmony_ci		  dev->name, inw(ioaddr + EL3_STATUS));
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	spin_lock(&lp->window_lock);
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	while ((status = inw(ioaddr + EL3_STATUS)) &
78462306a36Sopenharmony_ci		   (IntLatch | RxComplete | RxEarly | StatsFull)) {
78562306a36Sopenharmony_ci		if (!netif_device_present(dev) ||
78662306a36Sopenharmony_ci			((status & 0xe000) != 0x2000)) {
78762306a36Sopenharmony_ci			pr_debug("%s: Interrupt from dead card\n", dev->name);
78862306a36Sopenharmony_ci			break;
78962306a36Sopenharmony_ci		}
79062306a36Sopenharmony_ci
79162306a36Sopenharmony_ci		handled = 1;
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci		if (status & RxComplete)
79462306a36Sopenharmony_ci			work_budget = el3_rx(dev, work_budget);
79562306a36Sopenharmony_ci
79662306a36Sopenharmony_ci		if (status & TxAvailable) {
79762306a36Sopenharmony_ci			pr_debug("  TX room bit was handled.\n");
79862306a36Sopenharmony_ci			/* There's room in the FIFO for a full-sized packet. */
79962306a36Sopenharmony_ci			outw(AckIntr | TxAvailable, ioaddr + EL3_CMD);
80062306a36Sopenharmony_ci			netif_wake_queue(dev);
80162306a36Sopenharmony_ci		}
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci		if (status & TxComplete)
80462306a36Sopenharmony_ci			pop_tx_status(dev);
80562306a36Sopenharmony_ci
80662306a36Sopenharmony_ci		if (status & (AdapterFailure | RxEarly | StatsFull)) {
80762306a36Sopenharmony_ci			/* Handle all uncommon interrupts. */
80862306a36Sopenharmony_ci			if (status & StatsFull)
80962306a36Sopenharmony_ci				update_stats(dev);
81062306a36Sopenharmony_ci			if (status & RxEarly) {
81162306a36Sopenharmony_ci				work_budget = el3_rx(dev, work_budget);
81262306a36Sopenharmony_ci				outw(AckIntr | RxEarly, ioaddr + EL3_CMD);
81362306a36Sopenharmony_ci			}
81462306a36Sopenharmony_ci			if (status & AdapterFailure) {
81562306a36Sopenharmony_ci				u16 fifo_diag;
81662306a36Sopenharmony_ci				EL3WINDOW(4);
81762306a36Sopenharmony_ci				fifo_diag = inw(ioaddr + Wn4_FIFODiag);
81862306a36Sopenharmony_ci				EL3WINDOW(1);
81962306a36Sopenharmony_ci				netdev_notice(dev, "adapter failure, FIFO diagnostic register %04x\n",
82062306a36Sopenharmony_ci					      fifo_diag);
82162306a36Sopenharmony_ci				if (fifo_diag & 0x0400) {
82262306a36Sopenharmony_ci					/* Tx overrun */
82362306a36Sopenharmony_ci					tc574_wait_for_completion(dev, TxReset);
82462306a36Sopenharmony_ci					outw(TxEnable, ioaddr + EL3_CMD);
82562306a36Sopenharmony_ci				}
82662306a36Sopenharmony_ci				if (fifo_diag & 0x2000) {
82762306a36Sopenharmony_ci					/* Rx underrun */
82862306a36Sopenharmony_ci					tc574_wait_for_completion(dev, RxReset);
82962306a36Sopenharmony_ci					set_rx_mode(dev);
83062306a36Sopenharmony_ci					outw(RxEnable, ioaddr + EL3_CMD);
83162306a36Sopenharmony_ci				}
83262306a36Sopenharmony_ci				outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD);
83362306a36Sopenharmony_ci			}
83462306a36Sopenharmony_ci		}
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_ci		if (--work_budget < 0) {
83762306a36Sopenharmony_ci			pr_debug("%s: Too much work in interrupt, "
83862306a36Sopenharmony_ci				  "status %4.4x.\n", dev->name, status);
83962306a36Sopenharmony_ci			/* Clear all interrupts */
84062306a36Sopenharmony_ci			outw(AckIntr | 0xFF, ioaddr + EL3_CMD);
84162306a36Sopenharmony_ci			break;
84262306a36Sopenharmony_ci		}
84362306a36Sopenharmony_ci		/* Acknowledge the IRQ. */
84462306a36Sopenharmony_ci		outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD);
84562306a36Sopenharmony_ci	}
84662306a36Sopenharmony_ci
84762306a36Sopenharmony_ci	pr_debug("%s: exiting interrupt, status %4.4x.\n",
84862306a36Sopenharmony_ci		  dev->name, inw(ioaddr + EL3_STATUS));
84962306a36Sopenharmony_ci
85062306a36Sopenharmony_ci	spin_unlock(&lp->window_lock);
85162306a36Sopenharmony_ci	return IRQ_RETVAL(handled);
85262306a36Sopenharmony_ci}
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_ci/*
85562306a36Sopenharmony_ci    This timer serves two purposes: to check for missed interrupts
85662306a36Sopenharmony_ci	(and as a last resort, poll the NIC for events), and to monitor
85762306a36Sopenharmony_ci	the MII, reporting changes in cable status.
85862306a36Sopenharmony_ci*/
85962306a36Sopenharmony_cistatic void media_check(struct timer_list *t)
86062306a36Sopenharmony_ci{
86162306a36Sopenharmony_ci	struct el3_private *lp = from_timer(lp, t, media);
86262306a36Sopenharmony_ci	struct net_device *dev = lp->p_dev->priv;
86362306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
86462306a36Sopenharmony_ci	unsigned long flags;
86562306a36Sopenharmony_ci	unsigned short /* cable, */ media, partner;
86662306a36Sopenharmony_ci
86762306a36Sopenharmony_ci	if (!netif_device_present(dev))
86862306a36Sopenharmony_ci		goto reschedule;
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_ci	/* Check for pending interrupt with expired latency timer: with
87162306a36Sopenharmony_ci	   this, we can limp along even if the interrupt is blocked */
87262306a36Sopenharmony_ci	if ((inw(ioaddr + EL3_STATUS) & IntLatch) && (inb(ioaddr + Timer) == 0xff)) {
87362306a36Sopenharmony_ci		if (!lp->fast_poll)
87462306a36Sopenharmony_ci			netdev_info(dev, "interrupt(s) dropped!\n");
87562306a36Sopenharmony_ci
87662306a36Sopenharmony_ci		local_irq_save(flags);
87762306a36Sopenharmony_ci		el3_interrupt(dev->irq, dev);
87862306a36Sopenharmony_ci		local_irq_restore(flags);
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ci		lp->fast_poll = HZ;
88162306a36Sopenharmony_ci	}
88262306a36Sopenharmony_ci	if (lp->fast_poll) {
88362306a36Sopenharmony_ci		lp->fast_poll--;
88462306a36Sopenharmony_ci		lp->media.expires = jiffies + 2*HZ/100;
88562306a36Sopenharmony_ci		add_timer(&lp->media);
88662306a36Sopenharmony_ci		return;
88762306a36Sopenharmony_ci	}
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_ci	spin_lock_irqsave(&lp->window_lock, flags);
89062306a36Sopenharmony_ci	EL3WINDOW(4);
89162306a36Sopenharmony_ci	media = mdio_read(ioaddr, lp->phys, 1);
89262306a36Sopenharmony_ci	partner = mdio_read(ioaddr, lp->phys, 5);
89362306a36Sopenharmony_ci	EL3WINDOW(1);
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci	if (media != lp->media_status) {
89662306a36Sopenharmony_ci		if ((media ^ lp->media_status) & 0x0004)
89762306a36Sopenharmony_ci			netdev_info(dev, "%s link beat\n",
89862306a36Sopenharmony_ci				    (lp->media_status & 0x0004) ? "lost" : "found");
89962306a36Sopenharmony_ci		if ((media ^ lp->media_status) & 0x0020) {
90062306a36Sopenharmony_ci			lp->partner = 0;
90162306a36Sopenharmony_ci			if (lp->media_status & 0x0020) {
90262306a36Sopenharmony_ci				netdev_info(dev, "autonegotiation restarted\n");
90362306a36Sopenharmony_ci			} else if (partner) {
90462306a36Sopenharmony_ci				partner &= lp->advertising;
90562306a36Sopenharmony_ci				lp->partner = partner;
90662306a36Sopenharmony_ci				netdev_info(dev, "autonegotiation complete: "
90762306a36Sopenharmony_ci					    "%dbaseT-%cD selected\n",
90862306a36Sopenharmony_ci					    (partner & 0x0180) ? 100 : 10,
90962306a36Sopenharmony_ci					    (partner & 0x0140) ? 'F' : 'H');
91062306a36Sopenharmony_ci			} else {
91162306a36Sopenharmony_ci				netdev_info(dev, "link partner did not autonegotiate\n");
91262306a36Sopenharmony_ci			}
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_ci			EL3WINDOW(3);
91562306a36Sopenharmony_ci			outb((partner & 0x0140 ? 0x20 : 0) |
91662306a36Sopenharmony_ci				 (dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl);
91762306a36Sopenharmony_ci			EL3WINDOW(1);
91862306a36Sopenharmony_ci
91962306a36Sopenharmony_ci		}
92062306a36Sopenharmony_ci		if (media & 0x0010)
92162306a36Sopenharmony_ci			netdev_info(dev, "remote fault detected\n");
92262306a36Sopenharmony_ci		if (media & 0x0002)
92362306a36Sopenharmony_ci			netdev_info(dev, "jabber detected\n");
92462306a36Sopenharmony_ci		lp->media_status = media;
92562306a36Sopenharmony_ci	}
92662306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->window_lock, flags);
92762306a36Sopenharmony_ci
92862306a36Sopenharmony_cireschedule:
92962306a36Sopenharmony_ci	lp->media.expires = jiffies + HZ;
93062306a36Sopenharmony_ci	add_timer(&lp->media);
93162306a36Sopenharmony_ci}
93262306a36Sopenharmony_ci
93362306a36Sopenharmony_cistatic struct net_device_stats *el3_get_stats(struct net_device *dev)
93462306a36Sopenharmony_ci{
93562306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
93662306a36Sopenharmony_ci
93762306a36Sopenharmony_ci	if (netif_device_present(dev)) {
93862306a36Sopenharmony_ci		unsigned long flags;
93962306a36Sopenharmony_ci		spin_lock_irqsave(&lp->window_lock, flags);
94062306a36Sopenharmony_ci		update_stats(dev);
94162306a36Sopenharmony_ci		spin_unlock_irqrestore(&lp->window_lock, flags);
94262306a36Sopenharmony_ci	}
94362306a36Sopenharmony_ci	return &dev->stats;
94462306a36Sopenharmony_ci}
94562306a36Sopenharmony_ci
94662306a36Sopenharmony_ci/*  Update statistics.
94762306a36Sopenharmony_ci	Surprisingly this need not be run single-threaded, but it effectively is.
94862306a36Sopenharmony_ci	The counters clear when read, so the adds must merely be atomic.
94962306a36Sopenharmony_ci */
95062306a36Sopenharmony_cistatic void update_stats(struct net_device *dev)
95162306a36Sopenharmony_ci{
95262306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
95362306a36Sopenharmony_ci	u8 up;
95462306a36Sopenharmony_ci
95562306a36Sopenharmony_ci	pr_debug("%s: updating the statistics.\n", dev->name);
95662306a36Sopenharmony_ci
95762306a36Sopenharmony_ci	if (inw(ioaddr+EL3_STATUS) == 0xffff) /* No card. */
95862306a36Sopenharmony_ci		return;
95962306a36Sopenharmony_ci
96062306a36Sopenharmony_ci	/* Unlike the 3c509 we need not turn off stats updates while reading. */
96162306a36Sopenharmony_ci	/* Switch to the stats window, and read everything. */
96262306a36Sopenharmony_ci	EL3WINDOW(6);
96362306a36Sopenharmony_ci	dev->stats.tx_carrier_errors 		+= inb(ioaddr + 0);
96462306a36Sopenharmony_ci	dev->stats.tx_heartbeat_errors		+= inb(ioaddr + 1);
96562306a36Sopenharmony_ci	/* Multiple collisions. */	   	inb(ioaddr + 2);
96662306a36Sopenharmony_ci	dev->stats.collisions			+= inb(ioaddr + 3);
96762306a36Sopenharmony_ci	dev->stats.tx_window_errors		+= inb(ioaddr + 4);
96862306a36Sopenharmony_ci	dev->stats.rx_fifo_errors		+= inb(ioaddr + 5);
96962306a36Sopenharmony_ci	dev->stats.tx_packets			+= inb(ioaddr + 6);
97062306a36Sopenharmony_ci	up		 			 = inb(ioaddr + 9);
97162306a36Sopenharmony_ci	dev->stats.tx_packets			+= (up&0x30) << 4;
97262306a36Sopenharmony_ci	/* Rx packets   */			   inb(ioaddr + 7);
97362306a36Sopenharmony_ci	/* Tx deferrals */			   inb(ioaddr + 8);
97462306a36Sopenharmony_ci	/* rx */				   inw(ioaddr + 10);
97562306a36Sopenharmony_ci	/* tx */				   inw(ioaddr + 12);
97662306a36Sopenharmony_ci
97762306a36Sopenharmony_ci	EL3WINDOW(4);
97862306a36Sopenharmony_ci	/* BadSSD */				   inb(ioaddr + 12);
97962306a36Sopenharmony_ci	up					 = inb(ioaddr + 13);
98062306a36Sopenharmony_ci
98162306a36Sopenharmony_ci	EL3WINDOW(1);
98262306a36Sopenharmony_ci}
98362306a36Sopenharmony_ci
98462306a36Sopenharmony_cistatic int el3_rx(struct net_device *dev, int worklimit)
98562306a36Sopenharmony_ci{
98662306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
98762306a36Sopenharmony_ci	short rx_status;
98862306a36Sopenharmony_ci
98962306a36Sopenharmony_ci	pr_debug("%s: in rx_packet(), status %4.4x, rx_status %4.4x.\n",
99062306a36Sopenharmony_ci		  dev->name, inw(ioaddr+EL3_STATUS), inw(ioaddr+RxStatus));
99162306a36Sopenharmony_ci	while (!((rx_status = inw(ioaddr + RxStatus)) & 0x8000) &&
99262306a36Sopenharmony_ci			worklimit > 0) {
99362306a36Sopenharmony_ci		worklimit--;
99462306a36Sopenharmony_ci		if (rx_status & 0x4000) { /* Error, update stats. */
99562306a36Sopenharmony_ci			short error = rx_status & 0x3800;
99662306a36Sopenharmony_ci			dev->stats.rx_errors++;
99762306a36Sopenharmony_ci			switch (error) {
99862306a36Sopenharmony_ci			case 0x0000:	dev->stats.rx_over_errors++; break;
99962306a36Sopenharmony_ci			case 0x0800:	dev->stats.rx_length_errors++; break;
100062306a36Sopenharmony_ci			case 0x1000:	dev->stats.rx_frame_errors++; break;
100162306a36Sopenharmony_ci			case 0x1800:	dev->stats.rx_length_errors++; break;
100262306a36Sopenharmony_ci			case 0x2000:	dev->stats.rx_frame_errors++; break;
100362306a36Sopenharmony_ci			case 0x2800:	dev->stats.rx_crc_errors++; break;
100462306a36Sopenharmony_ci			}
100562306a36Sopenharmony_ci		} else {
100662306a36Sopenharmony_ci			short pkt_len = rx_status & 0x7ff;
100762306a36Sopenharmony_ci			struct sk_buff *skb;
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_ci			skb = netdev_alloc_skb(dev, pkt_len + 5);
101062306a36Sopenharmony_ci
101162306a36Sopenharmony_ci			pr_debug("  Receiving packet size %d status %4.4x.\n",
101262306a36Sopenharmony_ci				  pkt_len, rx_status);
101362306a36Sopenharmony_ci			if (skb != NULL) {
101462306a36Sopenharmony_ci				skb_reserve(skb, 2);
101562306a36Sopenharmony_ci				insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len),
101662306a36Sopenharmony_ci						((pkt_len+3)>>2));
101762306a36Sopenharmony_ci				skb->protocol = eth_type_trans(skb, dev);
101862306a36Sopenharmony_ci				netif_rx(skb);
101962306a36Sopenharmony_ci				dev->stats.rx_packets++;
102062306a36Sopenharmony_ci				dev->stats.rx_bytes += pkt_len;
102162306a36Sopenharmony_ci			} else {
102262306a36Sopenharmony_ci				pr_debug("%s: couldn't allocate a sk_buff of"
102362306a36Sopenharmony_ci					  " size %d.\n", dev->name, pkt_len);
102462306a36Sopenharmony_ci				dev->stats.rx_dropped++;
102562306a36Sopenharmony_ci			}
102662306a36Sopenharmony_ci		}
102762306a36Sopenharmony_ci		tc574_wait_for_completion(dev, RxDiscard);
102862306a36Sopenharmony_ci	}
102962306a36Sopenharmony_ci
103062306a36Sopenharmony_ci	return worklimit;
103162306a36Sopenharmony_ci}
103262306a36Sopenharmony_ci
103362306a36Sopenharmony_ci/* Provide ioctl() calls to examine the MII xcvr state. */
103462306a36Sopenharmony_cistatic int el3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
103562306a36Sopenharmony_ci{
103662306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
103762306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
103862306a36Sopenharmony_ci	struct mii_ioctl_data *data = if_mii(rq);
103962306a36Sopenharmony_ci	int phy = lp->phys & 0x1f;
104062306a36Sopenharmony_ci
104162306a36Sopenharmony_ci	pr_debug("%s: In ioct(%-.6s, %#4.4x) %4.4x %4.4x %4.4x %4.4x.\n",
104262306a36Sopenharmony_ci		  dev->name, rq->ifr_ifrn.ifrn_name, cmd,
104362306a36Sopenharmony_ci		  data->phy_id, data->reg_num, data->val_in, data->val_out);
104462306a36Sopenharmony_ci
104562306a36Sopenharmony_ci	switch(cmd) {
104662306a36Sopenharmony_ci	case SIOCGMIIPHY:		/* Get the address of the PHY in use. */
104762306a36Sopenharmony_ci		data->phy_id = phy;
104862306a36Sopenharmony_ci		fallthrough;
104962306a36Sopenharmony_ci	case SIOCGMIIREG:		/* Read the specified MII register. */
105062306a36Sopenharmony_ci		{
105162306a36Sopenharmony_ci			int saved_window;
105262306a36Sopenharmony_ci			unsigned long flags;
105362306a36Sopenharmony_ci
105462306a36Sopenharmony_ci			spin_lock_irqsave(&lp->window_lock, flags);
105562306a36Sopenharmony_ci			saved_window = inw(ioaddr + EL3_CMD) >> 13;
105662306a36Sopenharmony_ci			EL3WINDOW(4);
105762306a36Sopenharmony_ci			data->val_out = mdio_read(ioaddr, data->phy_id & 0x1f,
105862306a36Sopenharmony_ci						  data->reg_num & 0x1f);
105962306a36Sopenharmony_ci			EL3WINDOW(saved_window);
106062306a36Sopenharmony_ci			spin_unlock_irqrestore(&lp->window_lock, flags);
106162306a36Sopenharmony_ci			return 0;
106262306a36Sopenharmony_ci		}
106362306a36Sopenharmony_ci	case SIOCSMIIREG:		/* Write the specified MII register */
106462306a36Sopenharmony_ci		{
106562306a36Sopenharmony_ci			int saved_window;
106662306a36Sopenharmony_ci                       unsigned long flags;
106762306a36Sopenharmony_ci
106862306a36Sopenharmony_ci			spin_lock_irqsave(&lp->window_lock, flags);
106962306a36Sopenharmony_ci			saved_window = inw(ioaddr + EL3_CMD) >> 13;
107062306a36Sopenharmony_ci			EL3WINDOW(4);
107162306a36Sopenharmony_ci			mdio_write(ioaddr, data->phy_id & 0x1f,
107262306a36Sopenharmony_ci				   data->reg_num & 0x1f, data->val_in);
107362306a36Sopenharmony_ci			EL3WINDOW(saved_window);
107462306a36Sopenharmony_ci			spin_unlock_irqrestore(&lp->window_lock, flags);
107562306a36Sopenharmony_ci			return 0;
107662306a36Sopenharmony_ci		}
107762306a36Sopenharmony_ci	default:
107862306a36Sopenharmony_ci		return -EOPNOTSUPP;
107962306a36Sopenharmony_ci	}
108062306a36Sopenharmony_ci}
108162306a36Sopenharmony_ci
108262306a36Sopenharmony_ci/* The Odie chip has a 64 bin multicast filter, but the bit layout is not
108362306a36Sopenharmony_ci   documented.  Until it is we revert to receiving all multicast frames when
108462306a36Sopenharmony_ci   any multicast reception is desired.
108562306a36Sopenharmony_ci   Note: My other drivers emit a log message whenever promiscuous mode is
108662306a36Sopenharmony_ci   entered to help detect password sniffers.  This is less desirable on
108762306a36Sopenharmony_ci   typical PC card machines, so we omit the message.
108862306a36Sopenharmony_ci   */
108962306a36Sopenharmony_ci
109062306a36Sopenharmony_cistatic void set_rx_mode(struct net_device *dev)
109162306a36Sopenharmony_ci{
109262306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
109362306a36Sopenharmony_ci
109462306a36Sopenharmony_ci	if (dev->flags & IFF_PROMISC)
109562306a36Sopenharmony_ci		outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm,
109662306a36Sopenharmony_ci			 ioaddr + EL3_CMD);
109762306a36Sopenharmony_ci	else if (!netdev_mc_empty(dev) || (dev->flags & IFF_ALLMULTI))
109862306a36Sopenharmony_ci		outw(SetRxFilter|RxStation|RxMulticast|RxBroadcast, ioaddr + EL3_CMD);
109962306a36Sopenharmony_ci	else
110062306a36Sopenharmony_ci		outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD);
110162306a36Sopenharmony_ci}
110262306a36Sopenharmony_ci
110362306a36Sopenharmony_cistatic void set_multicast_list(struct net_device *dev)
110462306a36Sopenharmony_ci{
110562306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
110662306a36Sopenharmony_ci	unsigned long flags;
110762306a36Sopenharmony_ci
110862306a36Sopenharmony_ci	spin_lock_irqsave(&lp->window_lock, flags);
110962306a36Sopenharmony_ci	set_rx_mode(dev);
111062306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->window_lock, flags);
111162306a36Sopenharmony_ci}
111262306a36Sopenharmony_ci
111362306a36Sopenharmony_cistatic int el3_close(struct net_device *dev)
111462306a36Sopenharmony_ci{
111562306a36Sopenharmony_ci	unsigned int ioaddr = dev->base_addr;
111662306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
111762306a36Sopenharmony_ci	struct pcmcia_device *link = lp->p_dev;
111862306a36Sopenharmony_ci
111962306a36Sopenharmony_ci	dev_dbg(&link->dev, "%s: shutting down ethercard.\n", dev->name);
112062306a36Sopenharmony_ci
112162306a36Sopenharmony_ci	if (pcmcia_dev_present(link)) {
112262306a36Sopenharmony_ci		unsigned long flags;
112362306a36Sopenharmony_ci
112462306a36Sopenharmony_ci		/* Turn off statistics ASAP.  We update lp->stats below. */
112562306a36Sopenharmony_ci		outw(StatsDisable, ioaddr + EL3_CMD);
112662306a36Sopenharmony_ci
112762306a36Sopenharmony_ci		/* Disable the receiver and transmitter. */
112862306a36Sopenharmony_ci		outw(RxDisable, ioaddr + EL3_CMD);
112962306a36Sopenharmony_ci		outw(TxDisable, ioaddr + EL3_CMD);
113062306a36Sopenharmony_ci
113162306a36Sopenharmony_ci		/* Note: Switching to window 0 may disable the IRQ. */
113262306a36Sopenharmony_ci		EL3WINDOW(0);
113362306a36Sopenharmony_ci		spin_lock_irqsave(&lp->window_lock, flags);
113462306a36Sopenharmony_ci		update_stats(dev);
113562306a36Sopenharmony_ci		spin_unlock_irqrestore(&lp->window_lock, flags);
113662306a36Sopenharmony_ci
113762306a36Sopenharmony_ci		/* force interrupts off */
113862306a36Sopenharmony_ci		outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD);
113962306a36Sopenharmony_ci	}
114062306a36Sopenharmony_ci
114162306a36Sopenharmony_ci	link->open--;
114262306a36Sopenharmony_ci	netif_stop_queue(dev);
114362306a36Sopenharmony_ci	del_timer_sync(&lp->media);
114462306a36Sopenharmony_ci
114562306a36Sopenharmony_ci	return 0;
114662306a36Sopenharmony_ci}
114762306a36Sopenharmony_ci
114862306a36Sopenharmony_cistatic const struct pcmcia_device_id tc574_ids[] = {
114962306a36Sopenharmony_ci	PCMCIA_DEVICE_MANF_CARD(0x0101, 0x0574),
115062306a36Sopenharmony_ci	PCMCIA_MFC_DEVICE_CIS_MANF_CARD(0, 0x0101, 0x0556, "cis/3CCFEM556.cis"),
115162306a36Sopenharmony_ci	PCMCIA_DEVICE_NULL,
115262306a36Sopenharmony_ci};
115362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pcmcia, tc574_ids);
115462306a36Sopenharmony_ci
115562306a36Sopenharmony_cistatic struct pcmcia_driver tc574_driver = {
115662306a36Sopenharmony_ci	.owner		= THIS_MODULE,
115762306a36Sopenharmony_ci	.name		= "3c574_cs",
115862306a36Sopenharmony_ci	.probe		= tc574_probe,
115962306a36Sopenharmony_ci	.remove		= tc574_detach,
116062306a36Sopenharmony_ci	.id_table       = tc574_ids,
116162306a36Sopenharmony_ci	.suspend	= tc574_suspend,
116262306a36Sopenharmony_ci	.resume		= tc574_resume,
116362306a36Sopenharmony_ci};
116462306a36Sopenharmony_cimodule_pcmcia_driver(tc574_driver);
1165