162306a36Sopenharmony_ci/* 3c509.c: A 3c509 EtherLink3 ethernet driver for linux. */
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci	Written 1993-2000 by Donald Becker.
462306a36Sopenharmony_ci
562306a36Sopenharmony_ci	Copyright 1994-2000 by Donald Becker.
662306a36Sopenharmony_ci	Copyright 1993 United States Government as represented by the
762306a36Sopenharmony_ci	Director, National Security Agency.	 This software may be used and
862306a36Sopenharmony_ci	distributed according to the terms of the GNU General Public License,
962306a36Sopenharmony_ci	incorporated herein by reference.
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci	This driver is for the 3Com EtherLinkIII series.
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci	The author may be reached as becker@scyld.com, or C/O
1462306a36Sopenharmony_ci	Scyld Computing Corporation
1562306a36Sopenharmony_ci	410 Severn Ave., Suite 210
1662306a36Sopenharmony_ci	Annapolis MD 21403
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci	Known limitations:
1962306a36Sopenharmony_ci	Because of the way 3c509 ISA detection works it's difficult to predict
2062306a36Sopenharmony_ci	a priori which of several ISA-mode cards will be detected first.
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	This driver does not use predictive interrupt mode, resulting in higher
2362306a36Sopenharmony_ci	packet latency but lower overhead.  If interrupts are disabled for an
2462306a36Sopenharmony_ci	unusually long time it could also result in missed packets, but in
2562306a36Sopenharmony_ci	practice this rarely happens.
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	FIXES:
2962306a36Sopenharmony_ci		Alan Cox:       Removed the 'Unexpected interrupt' bug.
3062306a36Sopenharmony_ci		Michael Meskes:	Upgraded to Donald Becker's version 1.07.
3162306a36Sopenharmony_ci		Alan Cox:	Increased the eeprom delay. Regardless of
3262306a36Sopenharmony_ci				what the docs say some people definitely
3362306a36Sopenharmony_ci				get problems with lower (but in card spec)
3462306a36Sopenharmony_ci				delays
3562306a36Sopenharmony_ci		v1.10 4/21/97 Fixed module code so that multiple cards may be detected,
3662306a36Sopenharmony_ci				other cleanups.  -djb
3762306a36Sopenharmony_ci		Andrea Arcangeli:	Upgraded to Donald Becker's version 1.12.
3862306a36Sopenharmony_ci		Rick Payne:	Fixed SMP race condition
3962306a36Sopenharmony_ci		v1.13 9/8/97 Made 'max_interrupt_work' an insmod-settable variable -djb
4062306a36Sopenharmony_ci		v1.14 10/15/97 Avoided waiting..discard message for fast machines -djb
4162306a36Sopenharmony_ci		v1.15 1/31/98 Faster recovery for Tx errors. -djb
4262306a36Sopenharmony_ci		v1.16 2/3/98 Different ID port handling to avoid sound cards. -djb
4362306a36Sopenharmony_ci		v1.18 12Mar2001 Andrew Morton
4462306a36Sopenharmony_ci			- Avoid bogus detect of 3c590's (Andrzej Krzysztofowicz)
4562306a36Sopenharmony_ci			- Reviewed against 1.18 from scyld.com
4662306a36Sopenharmony_ci		v1.18a 17Nov2001 Jeff Garzik <jgarzik@pobox.com>
4762306a36Sopenharmony_ci			- ethtool support
4862306a36Sopenharmony_ci		v1.18b 1Mar2002 Zwane Mwaikambo <zwane@commfireservices.com>
4962306a36Sopenharmony_ci			- Power Management support
5062306a36Sopenharmony_ci		v1.18c 1Mar2002 David Ruggiero <jdr@farfalle.com>
5162306a36Sopenharmony_ci			- Full duplex support
5262306a36Sopenharmony_ci		v1.19  16Oct2002 Zwane Mwaikambo <zwane@linuxpower.ca>
5362306a36Sopenharmony_ci			- Additional ethtool features
5462306a36Sopenharmony_ci		v1.19a 28Oct2002 Davud Ruggiero <jdr@farfalle.com>
5562306a36Sopenharmony_ci			- Increase *read_eeprom udelay to workaround oops with 2 cards.
5662306a36Sopenharmony_ci		v1.19b 08Nov2002 Marc Zyngier <maz@wild-wind.fr.eu.org>
5762306a36Sopenharmony_ci			- Introduce driver model for EISA cards.
5862306a36Sopenharmony_ci		v1.20  04Feb2008 Ondrej Zary <linux@rainbow-software.org>
5962306a36Sopenharmony_ci			- convert to isa_driver and pnp_driver and some cleanups
6062306a36Sopenharmony_ci*/
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci#define DRV_NAME	"3c509"
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci/* A few values that may be tweaked. */
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/* Time in jiffies before concluding the transmitter is hung. */
6762306a36Sopenharmony_ci#define TX_TIMEOUT  (400*HZ/1000)
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci#include <linux/module.h>
7062306a36Sopenharmony_ci#include <linux/isa.h>
7162306a36Sopenharmony_ci#include <linux/pnp.h>
7262306a36Sopenharmony_ci#include <linux/string.h>
7362306a36Sopenharmony_ci#include <linux/interrupt.h>
7462306a36Sopenharmony_ci#include <linux/errno.h>
7562306a36Sopenharmony_ci#include <linux/in.h>
7662306a36Sopenharmony_ci#include <linux/ioport.h>
7762306a36Sopenharmony_ci#include <linux/init.h>
7862306a36Sopenharmony_ci#include <linux/netdevice.h>
7962306a36Sopenharmony_ci#include <linux/etherdevice.h>
8062306a36Sopenharmony_ci#include <linux/pm.h>
8162306a36Sopenharmony_ci#include <linux/skbuff.h>
8262306a36Sopenharmony_ci#include <linux/delay.h>	/* for udelay() */
8362306a36Sopenharmony_ci#include <linux/spinlock.h>
8462306a36Sopenharmony_ci#include <linux/ethtool.h>
8562306a36Sopenharmony_ci#include <linux/device.h>
8662306a36Sopenharmony_ci#include <linux/eisa.h>
8762306a36Sopenharmony_ci#include <linux/bitops.h>
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci#include <linux/uaccess.h>
9062306a36Sopenharmony_ci#include <asm/io.h>
9162306a36Sopenharmony_ci#include <asm/irq.h>
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci#ifdef EL3_DEBUG
9462306a36Sopenharmony_cistatic int el3_debug = EL3_DEBUG;
9562306a36Sopenharmony_ci#else
9662306a36Sopenharmony_cistatic int el3_debug = 2;
9762306a36Sopenharmony_ci#endif
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci/* Used to do a global count of all the cards in the system.  Must be
10062306a36Sopenharmony_ci * a global variable so that the eisa probe routines can increment
10162306a36Sopenharmony_ci * it */
10262306a36Sopenharmony_cistatic int el3_cards = 0;
10362306a36Sopenharmony_ci#define EL3_MAX_CARDS 8
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci/* To minimize the size of the driver source I only define operating
10662306a36Sopenharmony_ci   constants if they are used several times.  You'll need the manual
10762306a36Sopenharmony_ci   anyway if you want to understand driver details. */
10862306a36Sopenharmony_ci/* Offsets from base I/O address. */
10962306a36Sopenharmony_ci#define EL3_DATA 0x00
11062306a36Sopenharmony_ci#define EL3_CMD 0x0e
11162306a36Sopenharmony_ci#define EL3_STATUS 0x0e
11262306a36Sopenharmony_ci#define	EEPROM_READ 0x80
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci#define EL3_IO_EXTENT	16
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD)
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci/* The top five bits written to EL3_CMD are a command, the lower
12062306a36Sopenharmony_ci   11 bits are the parameter, if applicable. */
12162306a36Sopenharmony_cienum c509cmd {
12262306a36Sopenharmony_ci	TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11,
12362306a36Sopenharmony_ci	RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11,
12462306a36Sopenharmony_ci	TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11,
12562306a36Sopenharmony_ci	FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11,
12662306a36Sopenharmony_ci	SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11,
12762306a36Sopenharmony_ci	SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11,
12862306a36Sopenharmony_ci	StatsDisable = 22<<11, StopCoax = 23<<11, PowerUp = 27<<11,
12962306a36Sopenharmony_ci	PowerDown = 28<<11, PowerAuto = 29<<11};
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cienum c509status {
13262306a36Sopenharmony_ci	IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004,
13362306a36Sopenharmony_ci	TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020,
13462306a36Sopenharmony_ci	IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000, };
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci/* The SetRxFilter command accepts the following classes: */
13762306a36Sopenharmony_cienum RxFilter {
13862306a36Sopenharmony_ci	RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 };
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci/* Register window 1 offsets, the window used in normal operation. */
14162306a36Sopenharmony_ci#define TX_FIFO		0x00
14262306a36Sopenharmony_ci#define RX_FIFO		0x00
14362306a36Sopenharmony_ci#define RX_STATUS 	0x08
14462306a36Sopenharmony_ci#define TX_STATUS 	0x0B
14562306a36Sopenharmony_ci#define TX_FREE		0x0C		/* Remaining free bytes in Tx buffer. */
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci#define WN0_CONF_CTRL	0x04		/* Window 0: Configuration control register */
14862306a36Sopenharmony_ci#define WN0_ADDR_CONF	0x06		/* Window 0: Address configuration register */
14962306a36Sopenharmony_ci#define WN0_IRQ		0x08		/* Window 0: Set IRQ line in bits 12-15. */
15062306a36Sopenharmony_ci#define WN4_MEDIA	0x0A		/* Window 4: Various transcvr/media bits. */
15162306a36Sopenharmony_ci#define	MEDIA_TP	0x00C0		/* Enable link beat and jabber for 10baseT. */
15262306a36Sopenharmony_ci#define WN4_NETDIAG	0x06		/* Window 4: Net diagnostic */
15362306a36Sopenharmony_ci#define FD_ENABLE	0x8000		/* Enable full-duplex ("external loopback") */
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci/*
15662306a36Sopenharmony_ci * Must be a power of two (we use a binary and in the
15762306a36Sopenharmony_ci * circular queue)
15862306a36Sopenharmony_ci */
15962306a36Sopenharmony_ci#define SKB_QUEUE_SIZE	64
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cienum el3_cardtype { EL3_ISA, EL3_PNP, EL3_EISA };
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistruct el3_private {
16462306a36Sopenharmony_ci	spinlock_t lock;
16562306a36Sopenharmony_ci	/* skb send-queue */
16662306a36Sopenharmony_ci	int head, size;
16762306a36Sopenharmony_ci	struct sk_buff *queue[SKB_QUEUE_SIZE];
16862306a36Sopenharmony_ci	enum el3_cardtype type;
16962306a36Sopenharmony_ci};
17062306a36Sopenharmony_cistatic int id_port;
17162306a36Sopenharmony_cistatic int current_tag;
17262306a36Sopenharmony_cistatic struct net_device *el3_devs[EL3_MAX_CARDS];
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci/* Parameters that may be passed into the module. */
17562306a36Sopenharmony_cistatic int debug = -1;
17662306a36Sopenharmony_cistatic int irq[] = {-1, -1, -1, -1, -1, -1, -1, -1};
17762306a36Sopenharmony_ci/* Maximum events (Rx packets, etc.) to handle at each interrupt. */
17862306a36Sopenharmony_cistatic int max_interrupt_work = 10;
17962306a36Sopenharmony_ci#ifdef CONFIG_PNP
18062306a36Sopenharmony_cistatic int nopnp;
18162306a36Sopenharmony_ci#endif
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic int el3_common_init(struct net_device *dev);
18462306a36Sopenharmony_cistatic void el3_common_remove(struct net_device *dev);
18562306a36Sopenharmony_cistatic ushort id_read_eeprom(int index);
18662306a36Sopenharmony_cistatic ushort read_eeprom(int ioaddr, int index);
18762306a36Sopenharmony_cistatic int el3_open(struct net_device *dev);
18862306a36Sopenharmony_cistatic netdev_tx_t el3_start_xmit(struct sk_buff *skb, struct net_device *dev);
18962306a36Sopenharmony_cistatic irqreturn_t el3_interrupt(int irq, void *dev_id);
19062306a36Sopenharmony_cistatic void update_stats(struct net_device *dev);
19162306a36Sopenharmony_cistatic struct net_device_stats *el3_get_stats(struct net_device *dev);
19262306a36Sopenharmony_cistatic int el3_rx(struct net_device *dev);
19362306a36Sopenharmony_cistatic int el3_close(struct net_device *dev);
19462306a36Sopenharmony_cistatic void set_multicast_list(struct net_device *dev);
19562306a36Sopenharmony_cistatic void el3_tx_timeout (struct net_device *dev, unsigned int txqueue);
19662306a36Sopenharmony_cistatic void el3_down(struct net_device *dev);
19762306a36Sopenharmony_cistatic void el3_up(struct net_device *dev);
19862306a36Sopenharmony_cistatic const struct ethtool_ops ethtool_ops;
19962306a36Sopenharmony_ci#ifdef CONFIG_PM
20062306a36Sopenharmony_cistatic int el3_suspend(struct device *, pm_message_t);
20162306a36Sopenharmony_cistatic int el3_resume(struct device *);
20262306a36Sopenharmony_ci#else
20362306a36Sopenharmony_ci#define el3_suspend NULL
20462306a36Sopenharmony_ci#define el3_resume NULL
20562306a36Sopenharmony_ci#endif
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci/* generic device remove for all device types */
20962306a36Sopenharmony_cistatic int el3_device_remove (struct device *device);
21062306a36Sopenharmony_ci#ifdef CONFIG_NET_POLL_CONTROLLER
21162306a36Sopenharmony_cistatic void el3_poll_controller(struct net_device *dev);
21262306a36Sopenharmony_ci#endif
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci/* Return 0 on success, 1 on error, 2 when found already detected PnP card */
21562306a36Sopenharmony_cistatic int el3_isa_id_sequence(__be16 *phys_addr)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	short lrs_state = 0xff;
21862306a36Sopenharmony_ci	int i;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	/* ISA boards are detected by sending the ID sequence to the
22162306a36Sopenharmony_ci	   ID_PORT.  We find cards past the first by setting the 'current_tag'
22262306a36Sopenharmony_ci	   on cards as they are found.  Cards with their tag set will not
22362306a36Sopenharmony_ci	   respond to subsequent ID sequences. */
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	outb(0x00, id_port);
22662306a36Sopenharmony_ci	outb(0x00, id_port);
22762306a36Sopenharmony_ci	for (i = 0; i < 255; i++) {
22862306a36Sopenharmony_ci		outb(lrs_state, id_port);
22962306a36Sopenharmony_ci		lrs_state <<= 1;
23062306a36Sopenharmony_ci		lrs_state = lrs_state & 0x100 ? lrs_state ^ 0xcf : lrs_state;
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci	/* For the first probe, clear all board's tag registers. */
23362306a36Sopenharmony_ci	if (current_tag == 0)
23462306a36Sopenharmony_ci		outb(0xd0, id_port);
23562306a36Sopenharmony_ci	else			/* Otherwise kill off already-found boards. */
23662306a36Sopenharmony_ci		outb(0xd8, id_port);
23762306a36Sopenharmony_ci	if (id_read_eeprom(7) != 0x6d50)
23862306a36Sopenharmony_ci		return 1;
23962306a36Sopenharmony_ci	/* Read in EEPROM data, which does contention-select.
24062306a36Sopenharmony_ci	   Only the lowest address board will stay "on-line".
24162306a36Sopenharmony_ci	   3Com got the byte order backwards. */
24262306a36Sopenharmony_ci	for (i = 0; i < 3; i++)
24362306a36Sopenharmony_ci		phys_addr[i] = htons(id_read_eeprom(i));
24462306a36Sopenharmony_ci#ifdef CONFIG_PNP
24562306a36Sopenharmony_ci	if (!nopnp) {
24662306a36Sopenharmony_ci		/* The ISA PnP 3c509 cards respond to the ID sequence too.
24762306a36Sopenharmony_ci		   This check is needed in order not to register them twice. */
24862306a36Sopenharmony_ci		for (i = 0; i < el3_cards; i++) {
24962306a36Sopenharmony_ci			struct el3_private *lp = netdev_priv(el3_devs[i]);
25062306a36Sopenharmony_ci			if (lp->type == EL3_PNP &&
25162306a36Sopenharmony_ci			    ether_addr_equal((u8 *)phys_addr, el3_devs[i]->dev_addr)) {
25262306a36Sopenharmony_ci				if (el3_debug > 3)
25362306a36Sopenharmony_ci					pr_debug("3c509 with address %02x %02x %02x %02x %02x %02x was found by ISAPnP\n",
25462306a36Sopenharmony_ci						phys_addr[0] & 0xff, phys_addr[0] >> 8,
25562306a36Sopenharmony_ci						phys_addr[1] & 0xff, phys_addr[1] >> 8,
25662306a36Sopenharmony_ci						phys_addr[2] & 0xff, phys_addr[2] >> 8);
25762306a36Sopenharmony_ci				/* Set the adaptor tag so that the next card can be found. */
25862306a36Sopenharmony_ci				outb(0xd0 + ++current_tag, id_port);
25962306a36Sopenharmony_ci				return 2;
26062306a36Sopenharmony_ci			}
26162306a36Sopenharmony_ci		}
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci#endif /* CONFIG_PNP */
26462306a36Sopenharmony_ci	return 0;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic void el3_dev_fill(struct net_device *dev, __be16 *phys_addr, int ioaddr,
26962306a36Sopenharmony_ci			 int irq, int if_port, enum el3_cardtype type)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	eth_hw_addr_set(dev, (u8 *)phys_addr);
27462306a36Sopenharmony_ci	dev->base_addr = ioaddr;
27562306a36Sopenharmony_ci	dev->irq = irq;
27662306a36Sopenharmony_ci	dev->if_port = if_port;
27762306a36Sopenharmony_ci	lp->type = type;
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic int el3_isa_match(struct device *pdev, unsigned int ndev)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	struct net_device *dev;
28362306a36Sopenharmony_ci	int ioaddr, isa_irq, if_port, err;
28462306a36Sopenharmony_ci	unsigned int iobase;
28562306a36Sopenharmony_ci	__be16 phys_addr[3];
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	while ((err = el3_isa_id_sequence(phys_addr)) == 2)
28862306a36Sopenharmony_ci		;	/* Skip to next card when PnP card found */
28962306a36Sopenharmony_ci	if (err == 1)
29062306a36Sopenharmony_ci		return 0;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	iobase = id_read_eeprom(8);
29362306a36Sopenharmony_ci	if_port = iobase >> 14;
29462306a36Sopenharmony_ci	ioaddr = 0x200 + ((iobase & 0x1f) << 4);
29562306a36Sopenharmony_ci	if (irq[el3_cards] > 1 && irq[el3_cards] < 16)
29662306a36Sopenharmony_ci		isa_irq = irq[el3_cards];
29762306a36Sopenharmony_ci	else
29862306a36Sopenharmony_ci		isa_irq = id_read_eeprom(9) >> 12;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	dev = alloc_etherdev(sizeof(struct el3_private));
30162306a36Sopenharmony_ci	if (!dev)
30262306a36Sopenharmony_ci		return -ENOMEM;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	SET_NETDEV_DEV(dev, pdev);
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	if (!request_region(ioaddr, EL3_IO_EXTENT, "3c509-isa")) {
30762306a36Sopenharmony_ci		free_netdev(dev);
30862306a36Sopenharmony_ci		return 0;
30962306a36Sopenharmony_ci	}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	/* Set the adaptor tag so that the next card can be found. */
31262306a36Sopenharmony_ci	outb(0xd0 + ++current_tag, id_port);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	/* Activate the adaptor at the EEPROM location. */
31562306a36Sopenharmony_ci	outb((ioaddr >> 4) | 0xe0, id_port);
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	EL3WINDOW(0);
31862306a36Sopenharmony_ci	if (inw(ioaddr) != 0x6d50) {
31962306a36Sopenharmony_ci		free_netdev(dev);
32062306a36Sopenharmony_ci		return 0;
32162306a36Sopenharmony_ci	}
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	/* Free the interrupt so that some other card can use it. */
32462306a36Sopenharmony_ci	outw(0x0f00, ioaddr + WN0_IRQ);
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	el3_dev_fill(dev, phys_addr, ioaddr, isa_irq, if_port, EL3_ISA);
32762306a36Sopenharmony_ci	dev_set_drvdata(pdev, dev);
32862306a36Sopenharmony_ci	if (el3_common_init(dev)) {
32962306a36Sopenharmony_ci		free_netdev(dev);
33062306a36Sopenharmony_ci		return 0;
33162306a36Sopenharmony_ci	}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	el3_devs[el3_cards++] = dev;
33462306a36Sopenharmony_ci	return 1;
33562306a36Sopenharmony_ci}
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_cistatic void el3_isa_remove(struct device *pdev,
33862306a36Sopenharmony_ci				    unsigned int ndev)
33962306a36Sopenharmony_ci{
34062306a36Sopenharmony_ci	el3_device_remove(pdev);
34162306a36Sopenharmony_ci	dev_set_drvdata(pdev, NULL);
34262306a36Sopenharmony_ci}
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci#ifdef CONFIG_PM
34562306a36Sopenharmony_cistatic int el3_isa_suspend(struct device *dev, unsigned int n,
34662306a36Sopenharmony_ci			   pm_message_t state)
34762306a36Sopenharmony_ci{
34862306a36Sopenharmony_ci	current_tag = 0;
34962306a36Sopenharmony_ci	return el3_suspend(dev, state);
35062306a36Sopenharmony_ci}
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_cistatic int el3_isa_resume(struct device *dev, unsigned int n)
35362306a36Sopenharmony_ci{
35462306a36Sopenharmony_ci	struct net_device *ndev = dev_get_drvdata(dev);
35562306a36Sopenharmony_ci	int ioaddr = ndev->base_addr, err;
35662306a36Sopenharmony_ci	__be16 phys_addr[3];
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	while ((err = el3_isa_id_sequence(phys_addr)) == 2)
35962306a36Sopenharmony_ci		;	/* Skip to next card when PnP card found */
36062306a36Sopenharmony_ci	if (err == 1)
36162306a36Sopenharmony_ci		return 0;
36262306a36Sopenharmony_ci	/* Set the adaptor tag so that the next card can be found. */
36362306a36Sopenharmony_ci	outb(0xd0 + ++current_tag, id_port);
36462306a36Sopenharmony_ci	/* Enable the card */
36562306a36Sopenharmony_ci	outb((ioaddr >> 4) | 0xe0, id_port);
36662306a36Sopenharmony_ci	EL3WINDOW(0);
36762306a36Sopenharmony_ci	if (inw(ioaddr) != 0x6d50)
36862306a36Sopenharmony_ci		return 1;
36962306a36Sopenharmony_ci	/* Free the interrupt so that some other card can use it. */
37062306a36Sopenharmony_ci	outw(0x0f00, ioaddr + WN0_IRQ);
37162306a36Sopenharmony_ci	return el3_resume(dev);
37262306a36Sopenharmony_ci}
37362306a36Sopenharmony_ci#endif
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_cistatic struct isa_driver el3_isa_driver = {
37662306a36Sopenharmony_ci	.match		= el3_isa_match,
37762306a36Sopenharmony_ci	.remove		= el3_isa_remove,
37862306a36Sopenharmony_ci#ifdef CONFIG_PM
37962306a36Sopenharmony_ci	.suspend	= el3_isa_suspend,
38062306a36Sopenharmony_ci	.resume		= el3_isa_resume,
38162306a36Sopenharmony_ci#endif
38262306a36Sopenharmony_ci	.driver		= {
38362306a36Sopenharmony_ci		.name	= "3c509"
38462306a36Sopenharmony_ci	},
38562306a36Sopenharmony_ci};
38662306a36Sopenharmony_cistatic int isa_registered;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci#ifdef CONFIG_PNP
38962306a36Sopenharmony_cistatic const struct pnp_device_id el3_pnp_ids[] = {
39062306a36Sopenharmony_ci	{ .id = "TCM5090" }, /* 3Com Etherlink III (TP) */
39162306a36Sopenharmony_ci	{ .id = "TCM5091" }, /* 3Com Etherlink III */
39262306a36Sopenharmony_ci	{ .id = "TCM5094" }, /* 3Com Etherlink III (combo) */
39362306a36Sopenharmony_ci	{ .id = "TCM5095" }, /* 3Com Etherlink III (TPO) */
39462306a36Sopenharmony_ci	{ .id = "TCM5098" }, /* 3Com Etherlink III (TPC) */
39562306a36Sopenharmony_ci	{ .id = "PNP80f7" }, /* 3Com Etherlink III compatible */
39662306a36Sopenharmony_ci	{ .id = "PNP80f8" }, /* 3Com Etherlink III compatible */
39762306a36Sopenharmony_ci	{ .id = "" }
39862306a36Sopenharmony_ci};
39962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pnp, el3_pnp_ids);
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_cistatic int el3_pnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *id)
40262306a36Sopenharmony_ci{
40362306a36Sopenharmony_ci	short i;
40462306a36Sopenharmony_ci	int ioaddr, irq, if_port;
40562306a36Sopenharmony_ci	__be16 phys_addr[3];
40662306a36Sopenharmony_ci	struct net_device *dev = NULL;
40762306a36Sopenharmony_ci	int err;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	ioaddr = pnp_port_start(pdev, 0);
41062306a36Sopenharmony_ci	if (!request_region(ioaddr, EL3_IO_EXTENT, "3c509-pnp"))
41162306a36Sopenharmony_ci		return -EBUSY;
41262306a36Sopenharmony_ci	irq = pnp_irq(pdev, 0);
41362306a36Sopenharmony_ci	EL3WINDOW(0);
41462306a36Sopenharmony_ci	for (i = 0; i < 3; i++)
41562306a36Sopenharmony_ci		phys_addr[i] = htons(read_eeprom(ioaddr, i));
41662306a36Sopenharmony_ci	if_port = read_eeprom(ioaddr, 8) >> 14;
41762306a36Sopenharmony_ci	dev = alloc_etherdev(sizeof(struct el3_private));
41862306a36Sopenharmony_ci	if (!dev) {
41962306a36Sopenharmony_ci		release_region(ioaddr, EL3_IO_EXTENT);
42062306a36Sopenharmony_ci		return -ENOMEM;
42162306a36Sopenharmony_ci	}
42262306a36Sopenharmony_ci	SET_NETDEV_DEV(dev, &pdev->dev);
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	el3_dev_fill(dev, phys_addr, ioaddr, irq, if_port, EL3_PNP);
42562306a36Sopenharmony_ci	pnp_set_drvdata(pdev, dev);
42662306a36Sopenharmony_ci	err = el3_common_init(dev);
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	if (err) {
42962306a36Sopenharmony_ci		pnp_set_drvdata(pdev, NULL);
43062306a36Sopenharmony_ci		free_netdev(dev);
43162306a36Sopenharmony_ci		return err;
43262306a36Sopenharmony_ci	}
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	el3_devs[el3_cards++] = dev;
43562306a36Sopenharmony_ci	return 0;
43662306a36Sopenharmony_ci}
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_cistatic void el3_pnp_remove(struct pnp_dev *pdev)
43962306a36Sopenharmony_ci{
44062306a36Sopenharmony_ci	el3_common_remove(pnp_get_drvdata(pdev));
44162306a36Sopenharmony_ci	pnp_set_drvdata(pdev, NULL);
44262306a36Sopenharmony_ci}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci#ifdef CONFIG_PM
44562306a36Sopenharmony_cistatic int el3_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
44662306a36Sopenharmony_ci{
44762306a36Sopenharmony_ci	return el3_suspend(&pdev->dev, state);
44862306a36Sopenharmony_ci}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_cistatic int el3_pnp_resume(struct pnp_dev *pdev)
45162306a36Sopenharmony_ci{
45262306a36Sopenharmony_ci	return el3_resume(&pdev->dev);
45362306a36Sopenharmony_ci}
45462306a36Sopenharmony_ci#endif
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_cistatic struct pnp_driver el3_pnp_driver = {
45762306a36Sopenharmony_ci	.name		= "3c509",
45862306a36Sopenharmony_ci	.id_table	= el3_pnp_ids,
45962306a36Sopenharmony_ci	.probe		= el3_pnp_probe,
46062306a36Sopenharmony_ci	.remove		= el3_pnp_remove,
46162306a36Sopenharmony_ci#ifdef CONFIG_PM
46262306a36Sopenharmony_ci	.suspend	= el3_pnp_suspend,
46362306a36Sopenharmony_ci	.resume		= el3_pnp_resume,
46462306a36Sopenharmony_ci#endif
46562306a36Sopenharmony_ci};
46662306a36Sopenharmony_cistatic int pnp_registered;
46762306a36Sopenharmony_ci#endif /* CONFIG_PNP */
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci#ifdef CONFIG_EISA
47062306a36Sopenharmony_cistatic const struct eisa_device_id el3_eisa_ids[] = {
47162306a36Sopenharmony_ci		{ "TCM5090" },
47262306a36Sopenharmony_ci		{ "TCM5091" },
47362306a36Sopenharmony_ci		{ "TCM5092" },
47462306a36Sopenharmony_ci		{ "TCM5093" },
47562306a36Sopenharmony_ci		{ "TCM5094" },
47662306a36Sopenharmony_ci		{ "TCM5095" },
47762306a36Sopenharmony_ci		{ "TCM5098" },
47862306a36Sopenharmony_ci		{ "" }
47962306a36Sopenharmony_ci};
48062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(eisa, el3_eisa_ids);
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cistatic int el3_eisa_probe (struct device *device);
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_cistatic struct eisa_driver el3_eisa_driver = {
48562306a36Sopenharmony_ci		.id_table = el3_eisa_ids,
48662306a36Sopenharmony_ci		.driver   = {
48762306a36Sopenharmony_ci				.name    = "3c579",
48862306a36Sopenharmony_ci				.probe   = el3_eisa_probe,
48962306a36Sopenharmony_ci				.remove  = el3_device_remove,
49062306a36Sopenharmony_ci				.suspend = el3_suspend,
49162306a36Sopenharmony_ci				.resume  = el3_resume,
49262306a36Sopenharmony_ci		}
49362306a36Sopenharmony_ci};
49462306a36Sopenharmony_cistatic int eisa_registered;
49562306a36Sopenharmony_ci#endif
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_cistatic const struct net_device_ops netdev_ops = {
49862306a36Sopenharmony_ci	.ndo_open 		= el3_open,
49962306a36Sopenharmony_ci	.ndo_stop	 	= el3_close,
50062306a36Sopenharmony_ci	.ndo_start_xmit 	= el3_start_xmit,
50162306a36Sopenharmony_ci	.ndo_get_stats 		= el3_get_stats,
50262306a36Sopenharmony_ci	.ndo_set_rx_mode	= set_multicast_list,
50362306a36Sopenharmony_ci	.ndo_tx_timeout 	= el3_tx_timeout,
50462306a36Sopenharmony_ci	.ndo_set_mac_address 	= eth_mac_addr,
50562306a36Sopenharmony_ci	.ndo_validate_addr	= eth_validate_addr,
50662306a36Sopenharmony_ci#ifdef CONFIG_NET_POLL_CONTROLLER
50762306a36Sopenharmony_ci	.ndo_poll_controller	= el3_poll_controller,
50862306a36Sopenharmony_ci#endif
50962306a36Sopenharmony_ci};
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_cistatic int el3_common_init(struct net_device *dev)
51262306a36Sopenharmony_ci{
51362306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
51462306a36Sopenharmony_ci	int err;
51562306a36Sopenharmony_ci	static const char * const if_names[] = {
51662306a36Sopenharmony_ci		"10baseT", "AUI", "undefined", "BNC"
51762306a36Sopenharmony_ci	};
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	spin_lock_init(&lp->lock);
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	if (dev->mem_start & 0x05) { /* xcvr codes 1/3/4/12 */
52262306a36Sopenharmony_ci		dev->if_port = (dev->mem_start & 0x0f);
52362306a36Sopenharmony_ci	} else { /* xcvr codes 0/8 */
52462306a36Sopenharmony_ci		/* use eeprom value, but save user's full-duplex selection */
52562306a36Sopenharmony_ci		dev->if_port |= (dev->mem_start & 0x08);
52662306a36Sopenharmony_ci	}
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci	/* The EL3-specific entries in the device structure. */
52962306a36Sopenharmony_ci	dev->netdev_ops = &netdev_ops;
53062306a36Sopenharmony_ci	dev->watchdog_timeo = TX_TIMEOUT;
53162306a36Sopenharmony_ci	dev->ethtool_ops = &ethtool_ops;
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	err = register_netdev(dev);
53462306a36Sopenharmony_ci	if (err) {
53562306a36Sopenharmony_ci		pr_err("Failed to register 3c5x9 at %#3.3lx, IRQ %d.\n",
53662306a36Sopenharmony_ci			dev->base_addr, dev->irq);
53762306a36Sopenharmony_ci		release_region(dev->base_addr, EL3_IO_EXTENT);
53862306a36Sopenharmony_ci		return err;
53962306a36Sopenharmony_ci	}
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	pr_info("%s: 3c5x9 found at %#3.3lx, %s port, address %pM, IRQ %d.\n",
54262306a36Sopenharmony_ci	       dev->name, dev->base_addr, if_names[(dev->if_port & 0x03)],
54362306a36Sopenharmony_ci	       dev->dev_addr, dev->irq);
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	return 0;
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci}
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_cistatic void el3_common_remove (struct net_device *dev)
55062306a36Sopenharmony_ci{
55162306a36Sopenharmony_ci	unregister_netdev (dev);
55262306a36Sopenharmony_ci	release_region(dev->base_addr, EL3_IO_EXTENT);
55362306a36Sopenharmony_ci	free_netdev (dev);
55462306a36Sopenharmony_ci}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci#ifdef CONFIG_EISA
55762306a36Sopenharmony_cistatic int el3_eisa_probe(struct device *device)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	short i;
56062306a36Sopenharmony_ci	int ioaddr, irq, if_port;
56162306a36Sopenharmony_ci	__be16 phys_addr[3];
56262306a36Sopenharmony_ci	struct net_device *dev = NULL;
56362306a36Sopenharmony_ci	struct eisa_device *edev;
56462306a36Sopenharmony_ci	int err;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	/* Yeepee, The driver framework is calling us ! */
56762306a36Sopenharmony_ci	edev = to_eisa_device (device);
56862306a36Sopenharmony_ci	ioaddr = edev->base_addr;
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	if (!request_region(ioaddr, EL3_IO_EXTENT, "3c579-eisa"))
57162306a36Sopenharmony_ci		return -EBUSY;
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	/* Change the register set to the configuration window 0. */
57462306a36Sopenharmony_ci	outw(SelectWindow | 0, ioaddr + 0xC80 + EL3_CMD);
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	irq = inw(ioaddr + WN0_IRQ) >> 12;
57762306a36Sopenharmony_ci	if_port = inw(ioaddr + 6)>>14;
57862306a36Sopenharmony_ci	for (i = 0; i < 3; i++)
57962306a36Sopenharmony_ci		phys_addr[i] = htons(read_eeprom(ioaddr, i));
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	/* Restore the "Product ID" to the EEPROM read register. */
58262306a36Sopenharmony_ci	read_eeprom(ioaddr, 3);
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	dev = alloc_etherdev(sizeof (struct el3_private));
58562306a36Sopenharmony_ci	if (dev == NULL) {
58662306a36Sopenharmony_ci		release_region(ioaddr, EL3_IO_EXTENT);
58762306a36Sopenharmony_ci		return -ENOMEM;
58862306a36Sopenharmony_ci	}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	SET_NETDEV_DEV(dev, device);
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	el3_dev_fill(dev, phys_addr, ioaddr, irq, if_port, EL3_EISA);
59362306a36Sopenharmony_ci	eisa_set_drvdata (edev, dev);
59462306a36Sopenharmony_ci	err = el3_common_init(dev);
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci	if (err) {
59762306a36Sopenharmony_ci		eisa_set_drvdata (edev, NULL);
59862306a36Sopenharmony_ci		free_netdev(dev);
59962306a36Sopenharmony_ci		return err;
60062306a36Sopenharmony_ci	}
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	el3_devs[el3_cards++] = dev;
60362306a36Sopenharmony_ci	return 0;
60462306a36Sopenharmony_ci}
60562306a36Sopenharmony_ci#endif
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci/* This remove works for all device types.
60862306a36Sopenharmony_ci *
60962306a36Sopenharmony_ci * The net dev must be stored in the driver data field */
61062306a36Sopenharmony_cistatic int el3_device_remove(struct device *device)
61162306a36Sopenharmony_ci{
61262306a36Sopenharmony_ci	struct net_device *dev;
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	dev = dev_get_drvdata(device);
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	el3_common_remove (dev);
61762306a36Sopenharmony_ci	return 0;
61862306a36Sopenharmony_ci}
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci/* Read a word from the EEPROM using the regular EEPROM access register.
62162306a36Sopenharmony_ci   Assume that we are in register window zero.
62262306a36Sopenharmony_ci */
62362306a36Sopenharmony_cistatic ushort read_eeprom(int ioaddr, int index)
62462306a36Sopenharmony_ci{
62562306a36Sopenharmony_ci	outw(EEPROM_READ + index, ioaddr + 10);
62662306a36Sopenharmony_ci	/* Pause for at least 162 us. for the read to take place.
62762306a36Sopenharmony_ci	   Some chips seem to require much longer */
62862306a36Sopenharmony_ci	mdelay(2);
62962306a36Sopenharmony_ci	return inw(ioaddr + 12);
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci/* Read a word from the EEPROM when in the ISA ID probe state. */
63362306a36Sopenharmony_cistatic ushort id_read_eeprom(int index)
63462306a36Sopenharmony_ci{
63562306a36Sopenharmony_ci	int bit, word = 0;
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	/* Issue read command, and pause for at least 162 us. for it to complete.
63862306a36Sopenharmony_ci	   Assume extra-fast 16Mhz bus. */
63962306a36Sopenharmony_ci	outb(EEPROM_READ + index, id_port);
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	/* Pause for at least 162 us. for the read to take place. */
64262306a36Sopenharmony_ci	/* Some chips seem to require much longer */
64362306a36Sopenharmony_ci	mdelay(4);
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	for (bit = 15; bit >= 0; bit--)
64662306a36Sopenharmony_ci		word = (word << 1) + (inb(id_port) & 0x01);
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	if (el3_debug > 3)
64962306a36Sopenharmony_ci		pr_debug("  3c509 EEPROM word %d %#4.4x.\n", index, word);
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	return word;
65262306a36Sopenharmony_ci}
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_cistatic int
65662306a36Sopenharmony_ciel3_open(struct net_device *dev)
65762306a36Sopenharmony_ci{
65862306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
65962306a36Sopenharmony_ci	int i;
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	outw(TxReset, ioaddr + EL3_CMD);
66262306a36Sopenharmony_ci	outw(RxReset, ioaddr + EL3_CMD);
66362306a36Sopenharmony_ci	outw(SetStatusEnb | 0x00, ioaddr + EL3_CMD);
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci	i = request_irq(dev->irq, el3_interrupt, 0, dev->name, dev);
66662306a36Sopenharmony_ci	if (i)
66762306a36Sopenharmony_ci		return i;
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci	EL3WINDOW(0);
67062306a36Sopenharmony_ci	if (el3_debug > 3)
67162306a36Sopenharmony_ci		pr_debug("%s: Opening, IRQ %d	 status@%x %4.4x.\n", dev->name,
67262306a36Sopenharmony_ci			   dev->irq, ioaddr + EL3_STATUS, inw(ioaddr + EL3_STATUS));
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	el3_up(dev);
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	if (el3_debug > 3)
67762306a36Sopenharmony_ci		pr_debug("%s: Opened 3c509  IRQ %d  status %4.4x.\n",
67862306a36Sopenharmony_ci			   dev->name, dev->irq, inw(ioaddr + EL3_STATUS));
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci	return 0;
68162306a36Sopenharmony_ci}
68262306a36Sopenharmony_ci
68362306a36Sopenharmony_cistatic void
68462306a36Sopenharmony_ciel3_tx_timeout (struct net_device *dev, unsigned int txqueue)
68562306a36Sopenharmony_ci{
68662306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	/* Transmitter timeout, serious problems. */
68962306a36Sopenharmony_ci	pr_warn("%s: transmit timed out, Tx_status %2.2x status %4.4x Tx FIFO room %d\n",
69062306a36Sopenharmony_ci		dev->name, inb(ioaddr + TX_STATUS), inw(ioaddr + EL3_STATUS),
69162306a36Sopenharmony_ci		inw(ioaddr + TX_FREE));
69262306a36Sopenharmony_ci	dev->stats.tx_errors++;
69362306a36Sopenharmony_ci	netif_trans_update(dev); /* prevent tx timeout */
69462306a36Sopenharmony_ci	/* Issue TX_RESET and TX_START commands. */
69562306a36Sopenharmony_ci	outw(TxReset, ioaddr + EL3_CMD);
69662306a36Sopenharmony_ci	outw(TxEnable, ioaddr + EL3_CMD);
69762306a36Sopenharmony_ci	netif_wake_queue(dev);
69862306a36Sopenharmony_ci}
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_cistatic netdev_tx_t
70262306a36Sopenharmony_ciel3_start_xmit(struct sk_buff *skb, struct net_device *dev)
70362306a36Sopenharmony_ci{
70462306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
70562306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
70662306a36Sopenharmony_ci	unsigned long flags;
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ci	netif_stop_queue (dev);
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci	dev->stats.tx_bytes += skb->len;
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	if (el3_debug > 4) {
71362306a36Sopenharmony_ci		pr_debug("%s: el3_start_xmit(length = %u) called, status %4.4x.\n",
71462306a36Sopenharmony_ci			   dev->name, skb->len, inw(ioaddr + EL3_STATUS));
71562306a36Sopenharmony_ci	}
71662306a36Sopenharmony_ci	/*
71762306a36Sopenharmony_ci	 *	We lock the driver against other processors. Note
71862306a36Sopenharmony_ci	 *	we don't need to lock versus the IRQ as we suspended
71962306a36Sopenharmony_ci	 *	that. This means that we lose the ability to take
72062306a36Sopenharmony_ci	 *	an RX during a TX upload. That sucks a bit with SMP
72162306a36Sopenharmony_ci	 *	on an original 3c509 (2K buffer)
72262306a36Sopenharmony_ci	 *
72362306a36Sopenharmony_ci	 *	Using disable_irq stops us crapping on other
72462306a36Sopenharmony_ci	 *	time sensitive devices.
72562306a36Sopenharmony_ci	 */
72662306a36Sopenharmony_ci
72762306a36Sopenharmony_ci	spin_lock_irqsave(&lp->lock, flags);
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci	/* Put out the doubleword header... */
73062306a36Sopenharmony_ci	outw(skb->len, ioaddr + TX_FIFO);
73162306a36Sopenharmony_ci	outw(0x00, ioaddr + TX_FIFO);
73262306a36Sopenharmony_ci	/* ... and the packet rounded to a doubleword. */
73362306a36Sopenharmony_ci	outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2);
73462306a36Sopenharmony_ci
73562306a36Sopenharmony_ci	if (inw(ioaddr + TX_FREE) > 1536)
73662306a36Sopenharmony_ci		netif_start_queue(dev);
73762306a36Sopenharmony_ci	else
73862306a36Sopenharmony_ci		/* Interrupt us when the FIFO has room for max-sized packet. */
73962306a36Sopenharmony_ci		outw(SetTxThreshold + 1536, ioaddr + EL3_CMD);
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->lock, flags);
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	dev_consume_skb_any (skb);
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	/* Clear the Tx status stack. */
74662306a36Sopenharmony_ci	{
74762306a36Sopenharmony_ci		short tx_status;
74862306a36Sopenharmony_ci		int i = 4;
74962306a36Sopenharmony_ci
75062306a36Sopenharmony_ci		while (--i > 0	&&	(tx_status = inb(ioaddr + TX_STATUS)) > 0) {
75162306a36Sopenharmony_ci			if (tx_status & 0x38) dev->stats.tx_aborted_errors++;
75262306a36Sopenharmony_ci			if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD);
75362306a36Sopenharmony_ci			if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD);
75462306a36Sopenharmony_ci			outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */
75562306a36Sopenharmony_ci		}
75662306a36Sopenharmony_ci	}
75762306a36Sopenharmony_ci	return NETDEV_TX_OK;
75862306a36Sopenharmony_ci}
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci/* The EL3 interrupt handler. */
76162306a36Sopenharmony_cistatic irqreturn_t
76262306a36Sopenharmony_ciel3_interrupt(int irq, void *dev_id)
76362306a36Sopenharmony_ci{
76462306a36Sopenharmony_ci	struct net_device *dev = dev_id;
76562306a36Sopenharmony_ci	struct el3_private *lp;
76662306a36Sopenharmony_ci	int ioaddr, status;
76762306a36Sopenharmony_ci	int i = max_interrupt_work;
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci	lp = netdev_priv(dev);
77062306a36Sopenharmony_ci	spin_lock(&lp->lock);
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci	ioaddr = dev->base_addr;
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ci	if (el3_debug > 4) {
77562306a36Sopenharmony_ci		status = inw(ioaddr + EL3_STATUS);
77662306a36Sopenharmony_ci		pr_debug("%s: interrupt, status %4.4x.\n", dev->name, status);
77762306a36Sopenharmony_ci	}
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	while ((status = inw(ioaddr + EL3_STATUS)) &
78062306a36Sopenharmony_ci		   (IntLatch | RxComplete | StatsFull)) {
78162306a36Sopenharmony_ci
78262306a36Sopenharmony_ci		if (status & RxComplete)
78362306a36Sopenharmony_ci			el3_rx(dev);
78462306a36Sopenharmony_ci
78562306a36Sopenharmony_ci		if (status & TxAvailable) {
78662306a36Sopenharmony_ci			if (el3_debug > 5)
78762306a36Sopenharmony_ci				pr_debug("	TX room bit was handled.\n");
78862306a36Sopenharmony_ci			/* There's room in the FIFO for a full-sized packet. */
78962306a36Sopenharmony_ci			outw(AckIntr | TxAvailable, ioaddr + EL3_CMD);
79062306a36Sopenharmony_ci			netif_wake_queue (dev);
79162306a36Sopenharmony_ci		}
79262306a36Sopenharmony_ci		if (status & (AdapterFailure | RxEarly | StatsFull | TxComplete)) {
79362306a36Sopenharmony_ci			/* Handle all uncommon interrupts. */
79462306a36Sopenharmony_ci			if (status & StatsFull)				/* Empty statistics. */
79562306a36Sopenharmony_ci				update_stats(dev);
79662306a36Sopenharmony_ci			if (status & RxEarly) {				/* Rx early is unused. */
79762306a36Sopenharmony_ci				el3_rx(dev);
79862306a36Sopenharmony_ci				outw(AckIntr | RxEarly, ioaddr + EL3_CMD);
79962306a36Sopenharmony_ci			}
80062306a36Sopenharmony_ci			if (status & TxComplete) {			/* Really Tx error. */
80162306a36Sopenharmony_ci				short tx_status;
80262306a36Sopenharmony_ci				int i = 4;
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci				while (--i>0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) {
80562306a36Sopenharmony_ci					if (tx_status & 0x38) dev->stats.tx_aborted_errors++;
80662306a36Sopenharmony_ci					if (tx_status & 0x30) outw(TxReset, ioaddr + EL3_CMD);
80762306a36Sopenharmony_ci					if (tx_status & 0x3C) outw(TxEnable, ioaddr + EL3_CMD);
80862306a36Sopenharmony_ci					outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */
80962306a36Sopenharmony_ci				}
81062306a36Sopenharmony_ci			}
81162306a36Sopenharmony_ci			if (status & AdapterFailure) {
81262306a36Sopenharmony_ci				/* Adapter failure requires Rx reset and reinit. */
81362306a36Sopenharmony_ci				outw(RxReset, ioaddr + EL3_CMD);
81462306a36Sopenharmony_ci				/* Set the Rx filter to the current state. */
81562306a36Sopenharmony_ci				outw(SetRxFilter | RxStation | RxBroadcast
81662306a36Sopenharmony_ci					 | (dev->flags & IFF_ALLMULTI ? RxMulticast : 0)
81762306a36Sopenharmony_ci					 | (dev->flags & IFF_PROMISC ? RxProm : 0),
81862306a36Sopenharmony_ci					 ioaddr + EL3_CMD);
81962306a36Sopenharmony_ci				outw(RxEnable, ioaddr + EL3_CMD); /* Re-enable the receiver. */
82062306a36Sopenharmony_ci				outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD);
82162306a36Sopenharmony_ci			}
82262306a36Sopenharmony_ci		}
82362306a36Sopenharmony_ci
82462306a36Sopenharmony_ci		if (--i < 0) {
82562306a36Sopenharmony_ci			pr_err("%s: Infinite loop in interrupt, status %4.4x.\n",
82662306a36Sopenharmony_ci				   dev->name, status);
82762306a36Sopenharmony_ci			/* Clear all interrupts. */
82862306a36Sopenharmony_ci			outw(AckIntr | 0xFF, ioaddr + EL3_CMD);
82962306a36Sopenharmony_ci			break;
83062306a36Sopenharmony_ci		}
83162306a36Sopenharmony_ci		/* Acknowledge the IRQ. */
83262306a36Sopenharmony_ci		outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); /* Ack IRQ */
83362306a36Sopenharmony_ci	}
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_ci	if (el3_debug > 4) {
83662306a36Sopenharmony_ci		pr_debug("%s: exiting interrupt, status %4.4x.\n", dev->name,
83762306a36Sopenharmony_ci			   inw(ioaddr + EL3_STATUS));
83862306a36Sopenharmony_ci	}
83962306a36Sopenharmony_ci	spin_unlock(&lp->lock);
84062306a36Sopenharmony_ci	return IRQ_HANDLED;
84162306a36Sopenharmony_ci}
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci
84462306a36Sopenharmony_ci#ifdef CONFIG_NET_POLL_CONTROLLER
84562306a36Sopenharmony_ci/*
84662306a36Sopenharmony_ci * Polling receive - used by netconsole and other diagnostic tools
84762306a36Sopenharmony_ci * to allow network i/o with interrupts disabled.
84862306a36Sopenharmony_ci */
84962306a36Sopenharmony_cistatic void el3_poll_controller(struct net_device *dev)
85062306a36Sopenharmony_ci{
85162306a36Sopenharmony_ci	disable_irq(dev->irq);
85262306a36Sopenharmony_ci	el3_interrupt(dev->irq, dev);
85362306a36Sopenharmony_ci	enable_irq(dev->irq);
85462306a36Sopenharmony_ci}
85562306a36Sopenharmony_ci#endif
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_cistatic struct net_device_stats *
85862306a36Sopenharmony_ciel3_get_stats(struct net_device *dev)
85962306a36Sopenharmony_ci{
86062306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
86162306a36Sopenharmony_ci	unsigned long flags;
86262306a36Sopenharmony_ci
86362306a36Sopenharmony_ci	/*
86462306a36Sopenharmony_ci	 *	This is fast enough not to bother with disable IRQ
86562306a36Sopenharmony_ci	 *	stuff.
86662306a36Sopenharmony_ci	 */
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci	spin_lock_irqsave(&lp->lock, flags);
86962306a36Sopenharmony_ci	update_stats(dev);
87062306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->lock, flags);
87162306a36Sopenharmony_ci	return &dev->stats;
87262306a36Sopenharmony_ci}
87362306a36Sopenharmony_ci
87462306a36Sopenharmony_ci/*  Update statistics.  We change to register window 6, so this should be run
87562306a36Sopenharmony_ci	single-threaded if the device is active. This is expected to be a rare
87662306a36Sopenharmony_ci	operation, and it's simpler for the rest of the driver to assume that
87762306a36Sopenharmony_ci	window 1 is always valid rather than use a special window-state variable.
87862306a36Sopenharmony_ci	*/
87962306a36Sopenharmony_cistatic void update_stats(struct net_device *dev)
88062306a36Sopenharmony_ci{
88162306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
88262306a36Sopenharmony_ci
88362306a36Sopenharmony_ci	if (el3_debug > 5)
88462306a36Sopenharmony_ci		pr_debug("   Updating the statistics.\n");
88562306a36Sopenharmony_ci	/* Turn off statistics updates while reading. */
88662306a36Sopenharmony_ci	outw(StatsDisable, ioaddr + EL3_CMD);
88762306a36Sopenharmony_ci	/* Switch to the stats window, and read everything. */
88862306a36Sopenharmony_ci	EL3WINDOW(6);
88962306a36Sopenharmony_ci	dev->stats.tx_carrier_errors 	+= inb(ioaddr + 0);
89062306a36Sopenharmony_ci	dev->stats.tx_heartbeat_errors	+= inb(ioaddr + 1);
89162306a36Sopenharmony_ci	/* Multiple collisions. */	   inb(ioaddr + 2);
89262306a36Sopenharmony_ci	dev->stats.collisions		+= inb(ioaddr + 3);
89362306a36Sopenharmony_ci	dev->stats.tx_window_errors	+= inb(ioaddr + 4);
89462306a36Sopenharmony_ci	dev->stats.rx_fifo_errors	+= inb(ioaddr + 5);
89562306a36Sopenharmony_ci	dev->stats.tx_packets		+= inb(ioaddr + 6);
89662306a36Sopenharmony_ci	/* Rx packets	*/		   inb(ioaddr + 7);
89762306a36Sopenharmony_ci	/* Tx deferrals */		   inb(ioaddr + 8);
89862306a36Sopenharmony_ci	inw(ioaddr + 10);	/* Total Rx and Tx octets. */
89962306a36Sopenharmony_ci	inw(ioaddr + 12);
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_ci	/* Back to window 1, and turn statistics back on. */
90262306a36Sopenharmony_ci	EL3WINDOW(1);
90362306a36Sopenharmony_ci	outw(StatsEnable, ioaddr + EL3_CMD);
90462306a36Sopenharmony_ci}
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_cistatic int
90762306a36Sopenharmony_ciel3_rx(struct net_device *dev)
90862306a36Sopenharmony_ci{
90962306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
91062306a36Sopenharmony_ci	short rx_status;
91162306a36Sopenharmony_ci
91262306a36Sopenharmony_ci	if (el3_debug > 5)
91362306a36Sopenharmony_ci		pr_debug("   In rx_packet(), status %4.4x, rx_status %4.4x.\n",
91462306a36Sopenharmony_ci			   inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS));
91562306a36Sopenharmony_ci	while ((rx_status = inw(ioaddr + RX_STATUS)) > 0) {
91662306a36Sopenharmony_ci		if (rx_status & 0x4000) { /* Error, update stats. */
91762306a36Sopenharmony_ci			short error = rx_status & 0x3800;
91862306a36Sopenharmony_ci
91962306a36Sopenharmony_ci			outw(RxDiscard, ioaddr + EL3_CMD);
92062306a36Sopenharmony_ci			dev->stats.rx_errors++;
92162306a36Sopenharmony_ci			switch (error) {
92262306a36Sopenharmony_ci			case 0x0000:		dev->stats.rx_over_errors++; break;
92362306a36Sopenharmony_ci			case 0x0800:		dev->stats.rx_length_errors++; break;
92462306a36Sopenharmony_ci			case 0x1000:		dev->stats.rx_frame_errors++; break;
92562306a36Sopenharmony_ci			case 0x1800:		dev->stats.rx_length_errors++; break;
92662306a36Sopenharmony_ci			case 0x2000:		dev->stats.rx_frame_errors++; break;
92762306a36Sopenharmony_ci			case 0x2800:		dev->stats.rx_crc_errors++; break;
92862306a36Sopenharmony_ci			}
92962306a36Sopenharmony_ci		} else {
93062306a36Sopenharmony_ci			short pkt_len = rx_status & 0x7ff;
93162306a36Sopenharmony_ci			struct sk_buff *skb;
93262306a36Sopenharmony_ci
93362306a36Sopenharmony_ci			skb = netdev_alloc_skb(dev, pkt_len + 5);
93462306a36Sopenharmony_ci			if (el3_debug > 4)
93562306a36Sopenharmony_ci				pr_debug("Receiving packet size %d status %4.4x.\n",
93662306a36Sopenharmony_ci					   pkt_len, rx_status);
93762306a36Sopenharmony_ci			if (skb != NULL) {
93862306a36Sopenharmony_ci				skb_reserve(skb, 2);     /* Align IP on 16 byte */
93962306a36Sopenharmony_ci
94062306a36Sopenharmony_ci				/* 'skb->data' points to the start of sk_buff data area. */
94162306a36Sopenharmony_ci				insl(ioaddr + RX_FIFO, skb_put(skb,pkt_len),
94262306a36Sopenharmony_ci					 (pkt_len + 3) >> 2);
94362306a36Sopenharmony_ci
94462306a36Sopenharmony_ci				outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */
94562306a36Sopenharmony_ci				skb->protocol = eth_type_trans(skb,dev);
94662306a36Sopenharmony_ci				netif_rx(skb);
94762306a36Sopenharmony_ci				dev->stats.rx_bytes += pkt_len;
94862306a36Sopenharmony_ci				dev->stats.rx_packets++;
94962306a36Sopenharmony_ci				continue;
95062306a36Sopenharmony_ci			}
95162306a36Sopenharmony_ci			outw(RxDiscard, ioaddr + EL3_CMD);
95262306a36Sopenharmony_ci			dev->stats.rx_dropped++;
95362306a36Sopenharmony_ci			if (el3_debug)
95462306a36Sopenharmony_ci				pr_debug("%s: Couldn't allocate a sk_buff of size %d.\n",
95562306a36Sopenharmony_ci					   dev->name, pkt_len);
95662306a36Sopenharmony_ci		}
95762306a36Sopenharmony_ci		inw(ioaddr + EL3_STATUS); 				/* Delay. */
95862306a36Sopenharmony_ci		while (inw(ioaddr + EL3_STATUS) & 0x1000)
95962306a36Sopenharmony_ci			pr_debug("	Waiting for 3c509 to discard packet, status %x.\n",
96062306a36Sopenharmony_ci				   inw(ioaddr + EL3_STATUS) );
96162306a36Sopenharmony_ci	}
96262306a36Sopenharmony_ci
96362306a36Sopenharmony_ci	return 0;
96462306a36Sopenharmony_ci}
96562306a36Sopenharmony_ci
96662306a36Sopenharmony_ci/*
96762306a36Sopenharmony_ci *     Set or clear the multicast filter for this adaptor.
96862306a36Sopenharmony_ci */
96962306a36Sopenharmony_cistatic void
97062306a36Sopenharmony_ciset_multicast_list(struct net_device *dev)
97162306a36Sopenharmony_ci{
97262306a36Sopenharmony_ci	unsigned long flags;
97362306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
97462306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
97562306a36Sopenharmony_ci	int mc_count = netdev_mc_count(dev);
97662306a36Sopenharmony_ci
97762306a36Sopenharmony_ci	if (el3_debug > 1) {
97862306a36Sopenharmony_ci		static int old;
97962306a36Sopenharmony_ci		if (old != mc_count) {
98062306a36Sopenharmony_ci			old = mc_count;
98162306a36Sopenharmony_ci			pr_debug("%s: Setting Rx mode to %d addresses.\n",
98262306a36Sopenharmony_ci				 dev->name, mc_count);
98362306a36Sopenharmony_ci		}
98462306a36Sopenharmony_ci	}
98562306a36Sopenharmony_ci	spin_lock_irqsave(&lp->lock, flags);
98662306a36Sopenharmony_ci	if (dev->flags&IFF_PROMISC) {
98762306a36Sopenharmony_ci		outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm,
98862306a36Sopenharmony_ci			 ioaddr + EL3_CMD);
98962306a36Sopenharmony_ci	}
99062306a36Sopenharmony_ci	else if (mc_count || (dev->flags&IFF_ALLMULTI)) {
99162306a36Sopenharmony_ci		outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast, ioaddr + EL3_CMD);
99262306a36Sopenharmony_ci	}
99362306a36Sopenharmony_ci	else
99462306a36Sopenharmony_ci		outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD);
99562306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->lock, flags);
99662306a36Sopenharmony_ci}
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_cistatic int
99962306a36Sopenharmony_ciel3_close(struct net_device *dev)
100062306a36Sopenharmony_ci{
100162306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
100262306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
100362306a36Sopenharmony_ci
100462306a36Sopenharmony_ci	if (el3_debug > 2)
100562306a36Sopenharmony_ci		pr_debug("%s: Shutting down ethercard.\n", dev->name);
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_ci	el3_down(dev);
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_ci	free_irq(dev->irq, dev);
101062306a36Sopenharmony_ci	/* Switching back to window 0 disables the IRQ. */
101162306a36Sopenharmony_ci	EL3WINDOW(0);
101262306a36Sopenharmony_ci	if (lp->type != EL3_EISA) {
101362306a36Sopenharmony_ci		/* But we explicitly zero the IRQ line select anyway. Don't do
101462306a36Sopenharmony_ci		 * it on EISA cards, it prevents the module from getting an
101562306a36Sopenharmony_ci		 * IRQ after unload+reload... */
101662306a36Sopenharmony_ci		outw(0x0f00, ioaddr + WN0_IRQ);
101762306a36Sopenharmony_ci	}
101862306a36Sopenharmony_ci
101962306a36Sopenharmony_ci	return 0;
102062306a36Sopenharmony_ci}
102162306a36Sopenharmony_ci
102262306a36Sopenharmony_cistatic int
102362306a36Sopenharmony_ciel3_link_ok(struct net_device *dev)
102462306a36Sopenharmony_ci{
102562306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
102662306a36Sopenharmony_ci	u16 tmp;
102762306a36Sopenharmony_ci
102862306a36Sopenharmony_ci	EL3WINDOW(4);
102962306a36Sopenharmony_ci	tmp = inw(ioaddr + WN4_MEDIA);
103062306a36Sopenharmony_ci	EL3WINDOW(1);
103162306a36Sopenharmony_ci	return tmp & (1<<11);
103262306a36Sopenharmony_ci}
103362306a36Sopenharmony_ci
103462306a36Sopenharmony_cistatic void
103562306a36Sopenharmony_ciel3_netdev_get_ecmd(struct net_device *dev, struct ethtool_link_ksettings *cmd)
103662306a36Sopenharmony_ci{
103762306a36Sopenharmony_ci	u16 tmp;
103862306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
103962306a36Sopenharmony_ci	u32 supported;
104062306a36Sopenharmony_ci
104162306a36Sopenharmony_ci	EL3WINDOW(0);
104262306a36Sopenharmony_ci	/* obtain current transceiver via WN4_MEDIA? */
104362306a36Sopenharmony_ci	tmp = inw(ioaddr + WN0_ADDR_CONF);
104462306a36Sopenharmony_ci	switch (tmp >> 14) {
104562306a36Sopenharmony_ci	case 0:
104662306a36Sopenharmony_ci		cmd->base.port = PORT_TP;
104762306a36Sopenharmony_ci		break;
104862306a36Sopenharmony_ci	case 1:
104962306a36Sopenharmony_ci		cmd->base.port = PORT_AUI;
105062306a36Sopenharmony_ci		break;
105162306a36Sopenharmony_ci	case 3:
105262306a36Sopenharmony_ci		cmd->base.port = PORT_BNC;
105362306a36Sopenharmony_ci		break;
105462306a36Sopenharmony_ci	default:
105562306a36Sopenharmony_ci		break;
105662306a36Sopenharmony_ci	}
105762306a36Sopenharmony_ci
105862306a36Sopenharmony_ci	cmd->base.duplex = DUPLEX_HALF;
105962306a36Sopenharmony_ci	supported = 0;
106062306a36Sopenharmony_ci	tmp = inw(ioaddr + WN0_CONF_CTRL);
106162306a36Sopenharmony_ci	if (tmp & (1<<13))
106262306a36Sopenharmony_ci		supported |= SUPPORTED_AUI;
106362306a36Sopenharmony_ci	if (tmp & (1<<12))
106462306a36Sopenharmony_ci		supported |= SUPPORTED_BNC;
106562306a36Sopenharmony_ci	if (tmp & (1<<9)) {
106662306a36Sopenharmony_ci		supported |= SUPPORTED_TP | SUPPORTED_10baseT_Half |
106762306a36Sopenharmony_ci				SUPPORTED_10baseT_Full;	/* hmm... */
106862306a36Sopenharmony_ci		EL3WINDOW(4);
106962306a36Sopenharmony_ci		tmp = inw(ioaddr + WN4_NETDIAG);
107062306a36Sopenharmony_ci		if (tmp & FD_ENABLE)
107162306a36Sopenharmony_ci			cmd->base.duplex = DUPLEX_FULL;
107262306a36Sopenharmony_ci	}
107362306a36Sopenharmony_ci
107462306a36Sopenharmony_ci	ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported,
107562306a36Sopenharmony_ci						supported);
107662306a36Sopenharmony_ci	cmd->base.speed = SPEED_10;
107762306a36Sopenharmony_ci	EL3WINDOW(1);
107862306a36Sopenharmony_ci}
107962306a36Sopenharmony_ci
108062306a36Sopenharmony_cistatic int
108162306a36Sopenharmony_ciel3_netdev_set_ecmd(struct net_device *dev,
108262306a36Sopenharmony_ci		    const struct ethtool_link_ksettings *cmd)
108362306a36Sopenharmony_ci{
108462306a36Sopenharmony_ci	u16 tmp;
108562306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
108662306a36Sopenharmony_ci
108762306a36Sopenharmony_ci	if (cmd->base.speed != SPEED_10)
108862306a36Sopenharmony_ci		return -EINVAL;
108962306a36Sopenharmony_ci	if ((cmd->base.duplex != DUPLEX_HALF) &&
109062306a36Sopenharmony_ci	    (cmd->base.duplex != DUPLEX_FULL))
109162306a36Sopenharmony_ci		return -EINVAL;
109262306a36Sopenharmony_ci
109362306a36Sopenharmony_ci	/* change XCVR type */
109462306a36Sopenharmony_ci	EL3WINDOW(0);
109562306a36Sopenharmony_ci	tmp = inw(ioaddr + WN0_ADDR_CONF);
109662306a36Sopenharmony_ci	switch (cmd->base.port) {
109762306a36Sopenharmony_ci	case PORT_TP:
109862306a36Sopenharmony_ci		tmp &= ~(3<<14);
109962306a36Sopenharmony_ci		dev->if_port = 0;
110062306a36Sopenharmony_ci		break;
110162306a36Sopenharmony_ci	case PORT_AUI:
110262306a36Sopenharmony_ci		tmp |= (1<<14);
110362306a36Sopenharmony_ci		dev->if_port = 1;
110462306a36Sopenharmony_ci		break;
110562306a36Sopenharmony_ci	case PORT_BNC:
110662306a36Sopenharmony_ci		tmp |= (3<<14);
110762306a36Sopenharmony_ci		dev->if_port = 3;
110862306a36Sopenharmony_ci		break;
110962306a36Sopenharmony_ci	default:
111062306a36Sopenharmony_ci		return -EINVAL;
111162306a36Sopenharmony_ci	}
111262306a36Sopenharmony_ci
111362306a36Sopenharmony_ci	outw(tmp, ioaddr + WN0_ADDR_CONF);
111462306a36Sopenharmony_ci	if (dev->if_port == 3) {
111562306a36Sopenharmony_ci		/* fire up the DC-DC convertor if BNC gets enabled */
111662306a36Sopenharmony_ci		tmp = inw(ioaddr + WN0_ADDR_CONF);
111762306a36Sopenharmony_ci		if (tmp & (3 << 14)) {
111862306a36Sopenharmony_ci			outw(StartCoax, ioaddr + EL3_CMD);
111962306a36Sopenharmony_ci			udelay(800);
112062306a36Sopenharmony_ci		} else
112162306a36Sopenharmony_ci			return -EIO;
112262306a36Sopenharmony_ci	}
112362306a36Sopenharmony_ci
112462306a36Sopenharmony_ci	EL3WINDOW(4);
112562306a36Sopenharmony_ci	tmp = inw(ioaddr + WN4_NETDIAG);
112662306a36Sopenharmony_ci	if (cmd->base.duplex == DUPLEX_FULL)
112762306a36Sopenharmony_ci		tmp |= FD_ENABLE;
112862306a36Sopenharmony_ci	else
112962306a36Sopenharmony_ci		tmp &= ~FD_ENABLE;
113062306a36Sopenharmony_ci	outw(tmp, ioaddr + WN4_NETDIAG);
113162306a36Sopenharmony_ci	EL3WINDOW(1);
113262306a36Sopenharmony_ci
113362306a36Sopenharmony_ci	return 0;
113462306a36Sopenharmony_ci}
113562306a36Sopenharmony_ci
113662306a36Sopenharmony_cistatic void el3_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
113762306a36Sopenharmony_ci{
113862306a36Sopenharmony_ci	strscpy(info->driver, DRV_NAME, sizeof(info->driver));
113962306a36Sopenharmony_ci}
114062306a36Sopenharmony_ci
114162306a36Sopenharmony_cistatic int el3_get_link_ksettings(struct net_device *dev,
114262306a36Sopenharmony_ci				  struct ethtool_link_ksettings *cmd)
114362306a36Sopenharmony_ci{
114462306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
114562306a36Sopenharmony_ci
114662306a36Sopenharmony_ci	spin_lock_irq(&lp->lock);
114762306a36Sopenharmony_ci	el3_netdev_get_ecmd(dev, cmd);
114862306a36Sopenharmony_ci	spin_unlock_irq(&lp->lock);
114962306a36Sopenharmony_ci	return 0;
115062306a36Sopenharmony_ci}
115162306a36Sopenharmony_ci
115262306a36Sopenharmony_cistatic int el3_set_link_ksettings(struct net_device *dev,
115362306a36Sopenharmony_ci				  const struct ethtool_link_ksettings *cmd)
115462306a36Sopenharmony_ci{
115562306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
115662306a36Sopenharmony_ci	int ret;
115762306a36Sopenharmony_ci
115862306a36Sopenharmony_ci	spin_lock_irq(&lp->lock);
115962306a36Sopenharmony_ci	ret = el3_netdev_set_ecmd(dev, cmd);
116062306a36Sopenharmony_ci	spin_unlock_irq(&lp->lock);
116162306a36Sopenharmony_ci	return ret;
116262306a36Sopenharmony_ci}
116362306a36Sopenharmony_ci
116462306a36Sopenharmony_cistatic u32 el3_get_link(struct net_device *dev)
116562306a36Sopenharmony_ci{
116662306a36Sopenharmony_ci	struct el3_private *lp = netdev_priv(dev);
116762306a36Sopenharmony_ci	u32 ret;
116862306a36Sopenharmony_ci
116962306a36Sopenharmony_ci	spin_lock_irq(&lp->lock);
117062306a36Sopenharmony_ci	ret = el3_link_ok(dev);
117162306a36Sopenharmony_ci	spin_unlock_irq(&lp->lock);
117262306a36Sopenharmony_ci	return ret;
117362306a36Sopenharmony_ci}
117462306a36Sopenharmony_ci
117562306a36Sopenharmony_cistatic u32 el3_get_msglevel(struct net_device *dev)
117662306a36Sopenharmony_ci{
117762306a36Sopenharmony_ci	return el3_debug;
117862306a36Sopenharmony_ci}
117962306a36Sopenharmony_ci
118062306a36Sopenharmony_cistatic void el3_set_msglevel(struct net_device *dev, u32 v)
118162306a36Sopenharmony_ci{
118262306a36Sopenharmony_ci	el3_debug = v;
118362306a36Sopenharmony_ci}
118462306a36Sopenharmony_ci
118562306a36Sopenharmony_cistatic const struct ethtool_ops ethtool_ops = {
118662306a36Sopenharmony_ci	.get_drvinfo = el3_get_drvinfo,
118762306a36Sopenharmony_ci	.get_link = el3_get_link,
118862306a36Sopenharmony_ci	.get_msglevel = el3_get_msglevel,
118962306a36Sopenharmony_ci	.set_msglevel = el3_set_msglevel,
119062306a36Sopenharmony_ci	.get_link_ksettings = el3_get_link_ksettings,
119162306a36Sopenharmony_ci	.set_link_ksettings = el3_set_link_ksettings,
119262306a36Sopenharmony_ci};
119362306a36Sopenharmony_ci
119462306a36Sopenharmony_cistatic void
119562306a36Sopenharmony_ciel3_down(struct net_device *dev)
119662306a36Sopenharmony_ci{
119762306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
119862306a36Sopenharmony_ci
119962306a36Sopenharmony_ci	netif_stop_queue(dev);
120062306a36Sopenharmony_ci
120162306a36Sopenharmony_ci	/* Turn off statistics ASAP.  We update lp->stats below. */
120262306a36Sopenharmony_ci	outw(StatsDisable, ioaddr + EL3_CMD);
120362306a36Sopenharmony_ci
120462306a36Sopenharmony_ci	/* Disable the receiver and transmitter. */
120562306a36Sopenharmony_ci	outw(RxDisable, ioaddr + EL3_CMD);
120662306a36Sopenharmony_ci	outw(TxDisable, ioaddr + EL3_CMD);
120762306a36Sopenharmony_ci
120862306a36Sopenharmony_ci	if (dev->if_port == 3)
120962306a36Sopenharmony_ci		/* Turn off thinnet power.  Green! */
121062306a36Sopenharmony_ci		outw(StopCoax, ioaddr + EL3_CMD);
121162306a36Sopenharmony_ci	else if (dev->if_port == 0) {
121262306a36Sopenharmony_ci		/* Disable link beat and jabber, if_port may change here next open(). */
121362306a36Sopenharmony_ci		EL3WINDOW(4);
121462306a36Sopenharmony_ci		outw(inw(ioaddr + WN4_MEDIA) & ~MEDIA_TP, ioaddr + WN4_MEDIA);
121562306a36Sopenharmony_ci	}
121662306a36Sopenharmony_ci
121762306a36Sopenharmony_ci	outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD);
121862306a36Sopenharmony_ci
121962306a36Sopenharmony_ci	update_stats(dev);
122062306a36Sopenharmony_ci}
122162306a36Sopenharmony_ci
122262306a36Sopenharmony_cistatic void
122362306a36Sopenharmony_ciel3_up(struct net_device *dev)
122462306a36Sopenharmony_ci{
122562306a36Sopenharmony_ci	int i, sw_info, net_diag;
122662306a36Sopenharmony_ci	int ioaddr = dev->base_addr;
122762306a36Sopenharmony_ci
122862306a36Sopenharmony_ci	/* Activating the board required and does no harm otherwise */
122962306a36Sopenharmony_ci	outw(0x0001, ioaddr + 4);
123062306a36Sopenharmony_ci
123162306a36Sopenharmony_ci	/* Set the IRQ line. */
123262306a36Sopenharmony_ci	outw((dev->irq << 12) | 0x0f00, ioaddr + WN0_IRQ);
123362306a36Sopenharmony_ci
123462306a36Sopenharmony_ci	/* Set the station address in window 2 each time opened. */
123562306a36Sopenharmony_ci	EL3WINDOW(2);
123662306a36Sopenharmony_ci
123762306a36Sopenharmony_ci	for (i = 0; i < 6; i++)
123862306a36Sopenharmony_ci		outb(dev->dev_addr[i], ioaddr + i);
123962306a36Sopenharmony_ci
124062306a36Sopenharmony_ci	if ((dev->if_port & 0x03) == 3) /* BNC interface */
124162306a36Sopenharmony_ci		/* Start the thinnet transceiver. We should really wait 50ms...*/
124262306a36Sopenharmony_ci		outw(StartCoax, ioaddr + EL3_CMD);
124362306a36Sopenharmony_ci	else if ((dev->if_port & 0x03) == 0) { /* 10baseT interface */
124462306a36Sopenharmony_ci		/* Combine secondary sw_info word (the adapter level) and primary
124562306a36Sopenharmony_ci			sw_info word (duplex setting plus other useless bits) */
124662306a36Sopenharmony_ci		EL3WINDOW(0);
124762306a36Sopenharmony_ci		sw_info = (read_eeprom(ioaddr, 0x14) & 0x400f) |
124862306a36Sopenharmony_ci			(read_eeprom(ioaddr, 0x0d) & 0xBff0);
124962306a36Sopenharmony_ci
125062306a36Sopenharmony_ci		EL3WINDOW(4);
125162306a36Sopenharmony_ci		net_diag = inw(ioaddr + WN4_NETDIAG);
125262306a36Sopenharmony_ci		net_diag = (net_diag | FD_ENABLE); /* temporarily assume full-duplex will be set */
125362306a36Sopenharmony_ci		pr_info("%s: ", dev->name);
125462306a36Sopenharmony_ci		switch (dev->if_port & 0x0c) {
125562306a36Sopenharmony_ci			case 12:
125662306a36Sopenharmony_ci				/* force full-duplex mode if 3c5x9b */
125762306a36Sopenharmony_ci				if (sw_info & 0x000f) {
125862306a36Sopenharmony_ci					pr_cont("Forcing 3c5x9b full-duplex mode");
125962306a36Sopenharmony_ci					break;
126062306a36Sopenharmony_ci				}
126162306a36Sopenharmony_ci				fallthrough;
126262306a36Sopenharmony_ci			case 8:
126362306a36Sopenharmony_ci				/* set full-duplex mode based on eeprom config setting */
126462306a36Sopenharmony_ci				if ((sw_info & 0x000f) && (sw_info & 0x8000)) {
126562306a36Sopenharmony_ci					pr_cont("Setting 3c5x9b full-duplex mode (from EEPROM configuration bit)");
126662306a36Sopenharmony_ci					break;
126762306a36Sopenharmony_ci				}
126862306a36Sopenharmony_ci				fallthrough;
126962306a36Sopenharmony_ci			default:
127062306a36Sopenharmony_ci				/* xcvr=(0 || 4) OR user has an old 3c5x9 non "B" model */
127162306a36Sopenharmony_ci				pr_cont("Setting 3c5x9/3c5x9B half-duplex mode");
127262306a36Sopenharmony_ci				net_diag = (net_diag & ~FD_ENABLE); /* disable full duplex */
127362306a36Sopenharmony_ci		}
127462306a36Sopenharmony_ci
127562306a36Sopenharmony_ci		outw(net_diag, ioaddr + WN4_NETDIAG);
127662306a36Sopenharmony_ci		pr_cont(" if_port: %d, sw_info: %4.4x\n", dev->if_port, sw_info);
127762306a36Sopenharmony_ci		if (el3_debug > 3)
127862306a36Sopenharmony_ci			pr_debug("%s: 3c5x9 net diag word is now: %4.4x.\n", dev->name, net_diag);
127962306a36Sopenharmony_ci		/* Enable link beat and jabber check. */
128062306a36Sopenharmony_ci		outw(inw(ioaddr + WN4_MEDIA) | MEDIA_TP, ioaddr + WN4_MEDIA);
128162306a36Sopenharmony_ci	}
128262306a36Sopenharmony_ci
128362306a36Sopenharmony_ci	/* Switch to the stats window, and clear all stats by reading. */
128462306a36Sopenharmony_ci	outw(StatsDisable, ioaddr + EL3_CMD);
128562306a36Sopenharmony_ci	EL3WINDOW(6);
128662306a36Sopenharmony_ci	for (i = 0; i < 9; i++)
128762306a36Sopenharmony_ci		inb(ioaddr + i);
128862306a36Sopenharmony_ci	inw(ioaddr + 10);
128962306a36Sopenharmony_ci	inw(ioaddr + 12);
129062306a36Sopenharmony_ci
129162306a36Sopenharmony_ci	/* Switch to register set 1 for normal use. */
129262306a36Sopenharmony_ci	EL3WINDOW(1);
129362306a36Sopenharmony_ci
129462306a36Sopenharmony_ci	/* Accept b-case and phys addr only. */
129562306a36Sopenharmony_ci	outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD);
129662306a36Sopenharmony_ci	outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */
129762306a36Sopenharmony_ci
129862306a36Sopenharmony_ci	outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */
129962306a36Sopenharmony_ci	outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */
130062306a36Sopenharmony_ci	/* Allow status bits to be seen. */
130162306a36Sopenharmony_ci	outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD);
130262306a36Sopenharmony_ci	/* Ack all pending events, and set active indicator mask. */
130362306a36Sopenharmony_ci	outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq,
130462306a36Sopenharmony_ci		 ioaddr + EL3_CMD);
130562306a36Sopenharmony_ci	outw(SetIntrEnb | IntLatch|TxAvailable|TxComplete|RxComplete|StatsFull,
130662306a36Sopenharmony_ci		 ioaddr + EL3_CMD);
130762306a36Sopenharmony_ci
130862306a36Sopenharmony_ci	netif_start_queue(dev);
130962306a36Sopenharmony_ci}
131062306a36Sopenharmony_ci
131162306a36Sopenharmony_ci/* Power Management support functions */
131262306a36Sopenharmony_ci#ifdef CONFIG_PM
131362306a36Sopenharmony_ci
131462306a36Sopenharmony_cistatic int
131562306a36Sopenharmony_ciel3_suspend(struct device *pdev, pm_message_t state)
131662306a36Sopenharmony_ci{
131762306a36Sopenharmony_ci	unsigned long flags;
131862306a36Sopenharmony_ci	struct net_device *dev;
131962306a36Sopenharmony_ci	struct el3_private *lp;
132062306a36Sopenharmony_ci	int ioaddr;
132162306a36Sopenharmony_ci
132262306a36Sopenharmony_ci	dev = dev_get_drvdata(pdev);
132362306a36Sopenharmony_ci	lp = netdev_priv(dev);
132462306a36Sopenharmony_ci	ioaddr = dev->base_addr;
132562306a36Sopenharmony_ci
132662306a36Sopenharmony_ci	spin_lock_irqsave(&lp->lock, flags);
132762306a36Sopenharmony_ci
132862306a36Sopenharmony_ci	if (netif_running(dev))
132962306a36Sopenharmony_ci		netif_device_detach(dev);
133062306a36Sopenharmony_ci
133162306a36Sopenharmony_ci	el3_down(dev);
133262306a36Sopenharmony_ci	outw(PowerDown, ioaddr + EL3_CMD);
133362306a36Sopenharmony_ci
133462306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->lock, flags);
133562306a36Sopenharmony_ci	return 0;
133662306a36Sopenharmony_ci}
133762306a36Sopenharmony_ci
133862306a36Sopenharmony_cistatic int
133962306a36Sopenharmony_ciel3_resume(struct device *pdev)
134062306a36Sopenharmony_ci{
134162306a36Sopenharmony_ci	unsigned long flags;
134262306a36Sopenharmony_ci	struct net_device *dev;
134362306a36Sopenharmony_ci	struct el3_private *lp;
134462306a36Sopenharmony_ci	int ioaddr;
134562306a36Sopenharmony_ci
134662306a36Sopenharmony_ci	dev = dev_get_drvdata(pdev);
134762306a36Sopenharmony_ci	lp = netdev_priv(dev);
134862306a36Sopenharmony_ci	ioaddr = dev->base_addr;
134962306a36Sopenharmony_ci
135062306a36Sopenharmony_ci	spin_lock_irqsave(&lp->lock, flags);
135162306a36Sopenharmony_ci
135262306a36Sopenharmony_ci	outw(PowerUp, ioaddr + EL3_CMD);
135362306a36Sopenharmony_ci	EL3WINDOW(0);
135462306a36Sopenharmony_ci	el3_up(dev);
135562306a36Sopenharmony_ci
135662306a36Sopenharmony_ci	if (netif_running(dev))
135762306a36Sopenharmony_ci		netif_device_attach(dev);
135862306a36Sopenharmony_ci
135962306a36Sopenharmony_ci	spin_unlock_irqrestore(&lp->lock, flags);
136062306a36Sopenharmony_ci	return 0;
136162306a36Sopenharmony_ci}
136262306a36Sopenharmony_ci
136362306a36Sopenharmony_ci#endif /* CONFIG_PM */
136462306a36Sopenharmony_ci
136562306a36Sopenharmony_cimodule_param(debug,int, 0);
136662306a36Sopenharmony_cimodule_param_hw_array(irq, int, irq, NULL, 0);
136762306a36Sopenharmony_cimodule_param(max_interrupt_work, int, 0);
136862306a36Sopenharmony_ciMODULE_PARM_DESC(debug, "debug level (0-6)");
136962306a36Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ number(s) (assigned)");
137062306a36Sopenharmony_ciMODULE_PARM_DESC(max_interrupt_work, "maximum events handled per interrupt");
137162306a36Sopenharmony_ci#ifdef CONFIG_PNP
137262306a36Sopenharmony_cimodule_param(nopnp, int, 0);
137362306a36Sopenharmony_ciMODULE_PARM_DESC(nopnp, "disable ISA PnP support (0-1)");
137462306a36Sopenharmony_ci#endif	/* CONFIG_PNP */
137562306a36Sopenharmony_ciMODULE_DESCRIPTION("3Com Etherlink III (3c509, 3c509B, 3c529, 3c579) ethernet driver");
137662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
137762306a36Sopenharmony_ci
137862306a36Sopenharmony_cistatic int __init el3_init_module(void)
137962306a36Sopenharmony_ci{
138062306a36Sopenharmony_ci	int ret = 0;
138162306a36Sopenharmony_ci
138262306a36Sopenharmony_ci	if (debug >= 0)
138362306a36Sopenharmony_ci		el3_debug = debug;
138462306a36Sopenharmony_ci
138562306a36Sopenharmony_ci#ifdef CONFIG_PNP
138662306a36Sopenharmony_ci	if (!nopnp) {
138762306a36Sopenharmony_ci		ret = pnp_register_driver(&el3_pnp_driver);
138862306a36Sopenharmony_ci		if (!ret)
138962306a36Sopenharmony_ci			pnp_registered = 1;
139062306a36Sopenharmony_ci	}
139162306a36Sopenharmony_ci#endif
139262306a36Sopenharmony_ci	/* Select an open I/O location at 0x1*0 to do ISA contention select. */
139362306a36Sopenharmony_ci	/* Start with 0x110 to avoid some sound cards.*/
139462306a36Sopenharmony_ci	for (id_port = 0x110 ; id_port < 0x200; id_port += 0x10) {
139562306a36Sopenharmony_ci		if (!request_region(id_port, 1, "3c509-control"))
139662306a36Sopenharmony_ci			continue;
139762306a36Sopenharmony_ci		outb(0x00, id_port);
139862306a36Sopenharmony_ci		outb(0xff, id_port);
139962306a36Sopenharmony_ci		if (inb(id_port) & 0x01)
140062306a36Sopenharmony_ci			break;
140162306a36Sopenharmony_ci		else
140262306a36Sopenharmony_ci			release_region(id_port, 1);
140362306a36Sopenharmony_ci	}
140462306a36Sopenharmony_ci	if (id_port >= 0x200) {
140562306a36Sopenharmony_ci		id_port = 0;
140662306a36Sopenharmony_ci		pr_err("No I/O port available for 3c509 activation.\n");
140762306a36Sopenharmony_ci	} else {
140862306a36Sopenharmony_ci		ret = isa_register_driver(&el3_isa_driver, EL3_MAX_CARDS);
140962306a36Sopenharmony_ci		if (!ret)
141062306a36Sopenharmony_ci			isa_registered = 1;
141162306a36Sopenharmony_ci	}
141262306a36Sopenharmony_ci#ifdef CONFIG_EISA
141362306a36Sopenharmony_ci	ret = eisa_driver_register(&el3_eisa_driver);
141462306a36Sopenharmony_ci	if (!ret)
141562306a36Sopenharmony_ci		eisa_registered = 1;
141662306a36Sopenharmony_ci#endif
141762306a36Sopenharmony_ci
141862306a36Sopenharmony_ci#ifdef CONFIG_PNP
141962306a36Sopenharmony_ci	if (pnp_registered)
142062306a36Sopenharmony_ci		ret = 0;
142162306a36Sopenharmony_ci#endif
142262306a36Sopenharmony_ci	if (isa_registered)
142362306a36Sopenharmony_ci		ret = 0;
142462306a36Sopenharmony_ci#ifdef CONFIG_EISA
142562306a36Sopenharmony_ci	if (eisa_registered)
142662306a36Sopenharmony_ci		ret = 0;
142762306a36Sopenharmony_ci#endif
142862306a36Sopenharmony_ci	return ret;
142962306a36Sopenharmony_ci}
143062306a36Sopenharmony_ci
143162306a36Sopenharmony_cistatic void __exit el3_cleanup_module(void)
143262306a36Sopenharmony_ci{
143362306a36Sopenharmony_ci#ifdef CONFIG_PNP
143462306a36Sopenharmony_ci	if (pnp_registered)
143562306a36Sopenharmony_ci		pnp_unregister_driver(&el3_pnp_driver);
143662306a36Sopenharmony_ci#endif
143762306a36Sopenharmony_ci	if (isa_registered)
143862306a36Sopenharmony_ci		isa_unregister_driver(&el3_isa_driver);
143962306a36Sopenharmony_ci	if (id_port)
144062306a36Sopenharmony_ci		release_region(id_port, 1);
144162306a36Sopenharmony_ci#ifdef CONFIG_EISA
144262306a36Sopenharmony_ci	if (eisa_registered)
144362306a36Sopenharmony_ci		eisa_driver_unregister(&el3_eisa_driver);
144462306a36Sopenharmony_ci#endif
144562306a36Sopenharmony_ci}
144662306a36Sopenharmony_ci
144762306a36Sopenharmony_cimodule_init (el3_init_module);
144862306a36Sopenharmony_cimodule_exit (el3_cleanup_module);
1449