diff options
author | Jeff Kirsher <jeffrey.t.kirsher@intel.com> | 2011-05-12 20:21:07 -0700 |
---|---|---|
committer | Jeff Kirsher <jeffrey.t.kirsher@intel.com> | 2011-08-10 20:04:03 -0700 |
commit | ae150435b59e68de00546330241727f2fec54517 (patch) | |
tree | 29a1cb71053306e8a8e0dff1f927d16a9a396917 /drivers/net/ethernet | |
parent | aa43c2158d5ae1dc76cccb08cd57a3ffd32c3825 (diff) |
smsc: Move the SMC (SMSC) drivers
Moves the SMC (SMSC) drivers into drivers/net/ethernet/smsc/ and the
necessary Kconfig and Makefile changes. Also did some cleanup
of NET_VENDOR_SMC Kconfig tag for the 8390 based drivers.
CC: Nicolas Pitre <nico@fluxnic.net>
CC: Donald Becker <becker@scyld.com>
CC: Erik Stahlman <erik@vt.edu>
CC: Dustin McIntire <dustin@sensoria.com>
CC: Steve Glendinning <steve.glendinning@smsc.com>
CC: David Hinds <dahinds@users.sourceforge.net>
Signed-off-by: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
Diffstat (limited to 'drivers/net/ethernet')
-rw-r--r-- | drivers/net/ethernet/8390/Kconfig | 21 | ||||
-rw-r--r-- | drivers/net/ethernet/Kconfig | 1 | ||||
-rw-r--r-- | drivers/net/ethernet/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/Kconfig | 131 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/Makefile | 11 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/epic100.c | 1609 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smc911x.c | 2210 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smc911x.h | 924 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smc9194.c | 1589 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smc9194.h | 241 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smc91c92_cs.c | 2070 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smc91x.c | 2431 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smc91x.h | 1180 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smsc911x.c | 2404 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smsc911x.h | 404 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smsc9420.c | 1763 | ||||
-rw-r--r-- | drivers/net/ethernet/smsc/smsc9420.h | 276 |
17 files changed, 17249 insertions, 17 deletions
diff --git a/drivers/net/ethernet/8390/Kconfig b/drivers/net/ethernet/8390/Kconfig index 5cd53f1b84d3..f1b9bddc1550 100644 --- a/drivers/net/ethernet/8390/Kconfig +++ b/drivers/net/ethernet/8390/Kconfig @@ -264,22 +264,9 @@ config STNIC If unsure, say N. -config NET_VENDOR_SMC - bool "Western Digital/SMC cards" - depends on (ISA || MCA || EISA || MAC) - ---help--- - If you have a network (Ethernet) card belonging to this class, say Y - and read the Ethernet-HOWTO, available from - <http://www.tldp.org/docs.html#howto>. - - Note that the answer to this question doesn't directly affect the - kernel: saying N will just cause the configurator to skip all - the questions about Western Digital cards. If you say Y, you will be - asked for your specific card in the following questions. - config ULTRAMCA tristate "SMC Ultra MCA support" - depends on NET_VENDOR_SMC && MCA + depends on MCA select CRC32 ---help--- If you have a network (Ethernet) card of this type and are running @@ -291,7 +278,7 @@ config ULTRAMCA config ULTRA tristate "SMC Ultra support" - depends on NET_VENDOR_SMC && ISA + depends on ISA select CRC32 ---help--- If you have a network (Ethernet) card of this type, say Y and read @@ -310,7 +297,7 @@ config ULTRA config ULTRA32 tristate "SMC Ultra32 EISA support" - depends on NET_VENDOR_SMC && EISA + depends on EISA select CRC32 ---help--- If you have a network (Ethernet) card of this type, say Y and read @@ -322,7 +309,7 @@ config ULTRA32 config WD80x3 tristate "WD80*3 support" - depends on NET_VENDOR_SMC && ISA + depends on ISA select CRC32 ---help--- If you have a network (Ethernet) card of this type, say Y and read diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index ab591bb96702..ed5836ccb8d6 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -18,5 +18,6 @@ source "drivers/net/ethernet/broadcom/Kconfig" source "drivers/net/ethernet/chelsio/Kconfig" source "drivers/net/ethernet/intel/Kconfig" source "drivers/net/ethernet/qlogic/Kconfig" +source "drivers/net/ethernet/smsc/Kconfig" endif # ETHERNET diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index d8cf120e3322..983fd2752151 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_NET_VENDOR_BROADCOM) += broadcom/ obj-$(CONFIG_NET_VENDOR_CHELSIO) += chelsio/ obj-$(CONFIG_NET_VENDOR_INTEL) += intel/ obj-$(CONFIG_NET_VENDOR_QLOGIC) += qlogic/ +obj-$(CONFIG_NET_VENDOR_SMSC) += smsc/ diff --git a/drivers/net/ethernet/smsc/Kconfig b/drivers/net/ethernet/smsc/Kconfig new file mode 100644 index 000000000000..702efe686c48 --- /dev/null +++ b/drivers/net/ethernet/smsc/Kconfig @@ -0,0 +1,131 @@ +# +# Western Digital/SMC network device configuration +# + +config NET_VENDOR_SMSC + bool "SMC (SMSC)/Western Digital devices" + depends on ARM || ISA || MAC || ARM || MIPS || M32R || SUPERH || \ + BLACKFIN || MN10300 || COLDFIRE || PCI || PCMCIA + ---help--- + If you have a network (Ethernet) card belonging to this class, say Y + and read the Ethernet-HOWTO, available from + <http://www.tldp.org/docs.html#howto>. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about SMC/Western Digital cards. If you say Y, you will + be asked for your specific card in the following questions. + +if NET_VENDOR_SMSC + +config SMC9194 + tristate "SMC 9194 support" + depends on (ISA || MAC && BROKEN) + select CRC32 + ---help--- + This is support for the SMC9xxx based Ethernet cards. Choose this + option if you have a DELL laptop with the docking station, or + another SMC9192/9194 based chipset. Say Y if you want it compiled + into the kernel, and read the file + <file:Documentation/networking/smc9.txt> and the Ethernet-HOWTO, + available from <http://www.tldp.org/docs.html#howto>. + + To compile this driver as a module, choose M here. The module + will be called smc9194. + +config SMC91X + tristate "SMC 91C9x/91C1xxx support" + select CRC32 + select MII + depends on (ARM || M32R || SUPERH || MIPS || BLACKFIN || \ + MN10300 || COLDFIRE) + ---help--- + This is a driver for SMC's 91x series of Ethernet chipsets, + including the SMC91C94 and the SMC91C111. Say Y if you want it + compiled into the kernel, and read the file + <file:Documentation/networking/smc9.txt> and the Ethernet-HOWTO, + available from <http://www.tldp.org/docs.html#howto>. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called smc91x. If you want to compile it as a + module, say M here and read <file:Documentation/kbuild/modules.txt>. + +config PCMCIA_SMC91C92 + tristate "SMC 91Cxx PCMCIA support" + depends on PCMCIA + select CRC32 + select MII + ---help--- + Say Y here if you intend to attach an SMC 91Cxx compatible PCMCIA + (PC-card) Ethernet or Fast Ethernet card to your computer. + + To compile this driver as a module, choose M here: the module will be + called smc91c92_cs. If unsure, say N. + +config EPIC100 + tristate "SMC EtherPower II" + depends on PCI + select CRC32 + select MII + ---help--- + This driver is for the SMC EtherPower II 9432 PCI Ethernet NIC, + which is based on the SMC83c17x (EPIC/100). + More specific information and updates are available from + <http://www.scyld.com/network/epic100.html>. + +config SMC911X + tristate "SMSC LAN911[5678] support" + select CRC32 + select MII + depends on (ARM || SUPERH || MN10300) + ---help--- + This is a driver for SMSC's LAN911x series of Ethernet chipsets + including the new LAN9115, LAN9116, LAN9117, and LAN9118. + Say Y if you want it compiled into the kernel, + and read the Ethernet-HOWTO, available from + <http://www.tldp.org/docs.html#howto>. + + This driver is also available as a module. The module will be + called smc911x. If you want to compile it as a module, say M + here and read <file:Documentation/kbuild/modules.txt> + +config SMSC911X + tristate "SMSC LAN911x/LAN921x families embedded ethernet support" + depends on (ARM || SUPERH || BLACKFIN || MIPS || MN10300) + select CRC32 + select MII + select PHYLIB + ---help--- + Say Y here if you want support for SMSC LAN911x and LAN921x families + of ethernet controllers. + + To compile this driver as a module, choose M here and read + <file:Documentation/networking/net-modules.txt>. The module + will be called smsc911x. + +config SMSC911X_ARCH_HOOKS + def_bool n + depends on SMSC911X + ---help--- + If the arch enables this, it allows the arch to implement various + hooks for more comprehensive interrupt control and also to override + the source of the MAC address. + +config SMSC9420 + tristate "SMSC LAN9420 PCI ethernet adapter support" + depends on PCI + select CRC32 + select PHYLIB + select SMSC_PHY + ---help--- + This is a driver for SMSC's LAN9420 PCI ethernet adapter. + Say Y if you want it compiled into the kernel, + and read the Ethernet-HOWTO, available from + <http://www.tldp.org/docs.html#howto>. + + This driver is also available as a module. The module will be + called smsc9420. If you want to compile it as a module, say M + here and read <file:Documentation/kbuild/modules.txt> + +endif # NET_VENDOR_SMSC diff --git a/drivers/net/ethernet/smsc/Makefile b/drivers/net/ethernet/smsc/Makefile new file mode 100644 index 000000000000..f3438dec9d90 --- /dev/null +++ b/drivers/net/ethernet/smsc/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for the SMSC network device drivers. +# + +obj-$(CONFIG_SMC9194) += smc9194.o +obj-$(CONFIG_SMC91X) += smc91x.o +obj-$(CONFIG_PCMCIA_SMC91C92) += smc91c92_cs.o +obj-$(CONFIG_EPIC100) += epic100.o +obj-$(CONFIG_SMSC9420) += smsc9420.o +obj-$(CONFIG_SMC911X) += smc911x.o +obj-$(CONFIG_SMSC911X) += smsc911x.o diff --git a/drivers/net/ethernet/smsc/epic100.c b/drivers/net/ethernet/smsc/epic100.c new file mode 100644 index 000000000000..814c187d5f95 --- /dev/null +++ b/drivers/net/ethernet/smsc/epic100.c @@ -0,0 +1,1609 @@ +/* epic100.c: A SMC 83c170 EPIC/100 Fast Ethernet driver for Linux. */ +/* + Written/copyright 1997-2001 by Donald Becker. + + This software may be used and distributed according to the terms of + the GNU General Public License (GPL), incorporated herein by reference. + Drivers based on or derived from this code fall under the GPL and must + retain the authorship, copyright and license notice. This file is not + a complete program and may only be used when the entire operating + system is licensed under the GPL. + + This driver is for the SMC83c170/175 "EPIC" series, as used on the + SMC EtherPower II 9432 PCI adapter, and several CardBus cards. + + The author may be reached as becker@scyld.com, or C/O + Scyld Computing Corporation + 410 Severn Ave., Suite 210 + Annapolis MD 21403 + + Information and updates available at + http://www.scyld.com/network/epic100.html + [this link no longer provides anything useful -jgarzik] + + --------------------------------------------------------------------- + +*/ + +#define DRV_NAME "epic100" +#define DRV_VERSION "2.1" +#define DRV_RELDATE "Sept 11, 2006" + +/* The user-configurable values. + These may be modified when a driver module is loaded.*/ + +static int debug = 1; /* 1 normal messages, 0 quiet .. 7 verbose. */ + +/* Used to pass the full-duplex flag, etc. */ +#define MAX_UNITS 8 /* More are supported, limit only on options */ +static int options[MAX_UNITS] = {-1, -1, -1, -1, -1, -1, -1, -1}; +static int full_duplex[MAX_UNITS] = {-1, -1, -1, -1, -1, -1, -1, -1}; + +/* Set the copy breakpoint for the copy-only-tiny-frames scheme. + Setting to > 1518 effectively disables this feature. */ +static int rx_copybreak; + +/* Operational parameters that are set at compile time. */ + +/* Keep the ring sizes a power of two for operational efficiency. + The compiler will convert <unsigned>'%'<2^N> into a bit mask. + Making the Tx ring too large decreases the effectiveness of channel + bonding and packet priority. + There are no ill effects from too-large receive rings. */ +#define TX_RING_SIZE 256 +#define TX_QUEUE_LEN 240 /* Limit ring entries actually used. */ +#define RX_RING_SIZE 256 +#define TX_TOTAL_SIZE TX_RING_SIZE*sizeof(struct epic_tx_desc) +#define RX_TOTAL_SIZE RX_RING_SIZE*sizeof(struct epic_rx_desc) + +/* Operational parameters that usually are not changed. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT (2*HZ) + +#define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/ + +/* Bytes transferred to chip before transmission starts. */ +/* Initial threshold, increased on underflow, rounded down to 4 byte units. */ +#define TX_FIFO_THRESH 256 +#define RX_FIFO_THRESH 1 /* 0-3, 0==32, 64,96, or 3==128 bytes */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/crc32.h> +#include <linux/bitops.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/byteorder.h> + +/* These identify the driver base version and may not be removed. */ +static char version[] __devinitdata = +DRV_NAME ".c:v1.11 1/7/2001 Written by Donald Becker <becker@scyld.com>\n"; +static char version2[] __devinitdata = +" (unofficial 2.4.x kernel port, version " DRV_VERSION ", " DRV_RELDATE ")\n"; + +MODULE_AUTHOR("Donald Becker <becker@scyld.com>"); +MODULE_DESCRIPTION("SMC 83c170 EPIC series Ethernet driver"); +MODULE_LICENSE("GPL"); + +module_param(debug, int, 0); +module_param(rx_copybreak, int, 0); +module_param_array(options, int, NULL, 0); +module_param_array(full_duplex, int, NULL, 0); +MODULE_PARM_DESC(debug, "EPIC/100 debug level (0-5)"); +MODULE_PARM_DESC(options, "EPIC/100: Bits 0-3: media type, bit 4: full duplex"); +MODULE_PARM_DESC(rx_copybreak, "EPIC/100 copy breakpoint for copy-only-tiny-frames"); +MODULE_PARM_DESC(full_duplex, "EPIC/100 full duplex setting(s) (1)"); + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the SMC "EPIC/100", the SMC +single-chip Ethernet controllers for PCI. This chip is used on +the SMC EtherPower II boards. + +II. Board-specific settings + +PCI bus devices are configured by the system at boot time, so no jumpers +need to be set on the board. The system BIOS will assign the +PCI INTA signal to a (preferably otherwise unused) system IRQ line. +Note: Kernel versions earlier than 1.3.73 do not support shared PCI +interrupt lines. + +III. Driver operation + +IIIa. Ring buffers + +IVb. References + +http://www.smsc.com/media/Downloads_Public/discontinued/83c171.pdf +http://www.smsc.com/media/Downloads_Public/discontinued/83c175.pdf +http://scyld.com/expert/NWay.html +http://www.national.com/pf/DP/DP83840A.html + +IVc. Errata + +*/ + + +enum chip_capability_flags { MII_PWRDWN=1, TYPE2_INTR=2, NO_MII=4 }; + +#define EPIC_TOTAL_SIZE 0x100 +#define USE_IO_OPS 1 + +typedef enum { + SMSC_83C170_0, + SMSC_83C170, + SMSC_83C175, +} chip_t; + + +struct epic_chip_info { + const char *name; + int drv_flags; /* Driver use, intended as capability flags. */ +}; + + +/* indexed by chip_t */ +static const struct epic_chip_info pci_id_tbl[] = { + { "SMSC EPIC/100 83c170", TYPE2_INTR | NO_MII | MII_PWRDWN }, + { "SMSC EPIC/100 83c170", TYPE2_INTR }, + { "SMSC EPIC/C 83c175", TYPE2_INTR | MII_PWRDWN }, +}; + + +static DEFINE_PCI_DEVICE_TABLE(epic_pci_tbl) = { + { 0x10B8, 0x0005, 0x1092, 0x0AB4, 0, 0, SMSC_83C170_0 }, + { 0x10B8, 0x0005, PCI_ANY_ID, PCI_ANY_ID, 0, 0, SMSC_83C170 }, + { 0x10B8, 0x0006, PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_NETWORK_ETHERNET << 8, 0xffff00, SMSC_83C175 }, + { 0,} +}; +MODULE_DEVICE_TABLE (pci, epic_pci_tbl); + + +#ifndef USE_IO_OPS +#undef inb +#undef inw +#undef inl +#undef outb +#undef outw +#undef outl +#define inb readb +#define inw readw +#define inl readl +#define outb writeb +#define outw writew +#define outl writel +#endif + +/* Offsets to registers, using the (ugh) SMC names. */ +enum epic_registers { + COMMAND=0, INTSTAT=4, INTMASK=8, GENCTL=0x0C, NVCTL=0x10, EECTL=0x14, + PCIBurstCnt=0x18, + TEST1=0x1C, CRCCNT=0x20, ALICNT=0x24, MPCNT=0x28, /* Rx error counters. */ + MIICtrl=0x30, MIIData=0x34, MIICfg=0x38, + LAN0=64, /* MAC address. */ + MC0=80, /* Multicast filter table. */ + RxCtrl=96, TxCtrl=112, TxSTAT=0x74, + PRxCDAR=0x84, RxSTAT=0xA4, EarlyRx=0xB0, PTxCDAR=0xC4, TxThresh=0xDC, +}; + +/* Interrupt register bits, using my own meaningful names. */ +enum IntrStatus { + TxIdle=0x40000, RxIdle=0x20000, IntrSummary=0x010000, + PCIBusErr170=0x7000, PCIBusErr175=0x1000, PhyEvent175=0x8000, + RxStarted=0x0800, RxEarlyWarn=0x0400, CntFull=0x0200, TxUnderrun=0x0100, + TxEmpty=0x0080, TxDone=0x0020, RxError=0x0010, + RxOverflow=0x0008, RxFull=0x0004, RxHeader=0x0002, RxDone=0x0001, +}; +enum CommandBits { + StopRx=1, StartRx=2, TxQueued=4, RxQueued=8, + StopTxDMA=0x20, StopRxDMA=0x40, RestartTx=0x80, +}; + +#define EpicRemoved 0xffffffff /* Chip failed or removed (CardBus) */ + +#define EpicNapiEvent (TxEmpty | TxDone | \ + RxDone | RxStarted | RxEarlyWarn | RxOverflow | RxFull) +#define EpicNormalEvent (0x0000ffff & ~EpicNapiEvent) + +static const u16 media2miictl[16] = { + 0, 0x0C00, 0x0C00, 0x2000, 0x0100, 0x2100, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + +/* + * The EPIC100 Rx and Tx buffer descriptors. Note that these + * really ARE host-endian; it's not a misannotation. We tell + * the card to byteswap them internally on big-endian hosts - + * look for #ifdef __BIG_ENDIAN in epic_open(). + */ + +struct epic_tx_desc { + u32 txstatus; + u32 bufaddr; + u32 buflength; + u32 next; +}; + +struct epic_rx_desc { + u32 rxstatus; + u32 bufaddr; + u32 buflength; + u32 next; +}; + +enum desc_status_bits { + DescOwn=0x8000, +}; + +#define PRIV_ALIGN 15 /* Required alignment mask */ +struct epic_private { + struct epic_rx_desc *rx_ring; + struct epic_tx_desc *tx_ring; + /* The saved address of a sent-in-place packet/buffer, for skfree(). */ + struct sk_buff* tx_skbuff[TX_RING_SIZE]; + /* The addresses of receive-in-place skbuffs. */ + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + + dma_addr_t tx_ring_dma; + dma_addr_t rx_ring_dma; + + /* Ring pointers. */ + spinlock_t lock; /* Group with Tx control cache line. */ + spinlock_t napi_lock; + struct napi_struct napi; + unsigned int reschedule_in_poll; + unsigned int cur_tx, dirty_tx; + + unsigned int cur_rx, dirty_rx; + u32 irq_mask; + unsigned int rx_buf_sz; /* Based on MTU+slack. */ + + struct pci_dev *pci_dev; /* PCI bus location. */ + int chip_id, chip_flags; + + struct timer_list timer; /* Media selection timer. */ + int tx_threshold; + unsigned char mc_filter[8]; + signed char phys[4]; /* MII device addresses. */ + u16 advertising; /* NWay media advertisement */ + int mii_phy_cnt; + struct mii_if_info mii; + unsigned int tx_full:1; /* The Tx queue is full. */ + unsigned int default_port:4; /* Last dev->if_port value. */ +}; + +static int epic_open(struct net_device *dev); +static int read_eeprom(long ioaddr, int location); +static int mdio_read(struct net_device *dev, int phy_id, int location); +static void mdio_write(struct net_device *dev, int phy_id, int loc, int val); +static void epic_restart(struct net_device *dev); +static void epic_timer(unsigned long data); +static void epic_tx_timeout(struct net_device *dev); +static void epic_init_ring(struct net_device *dev); +static netdev_tx_t epic_start_xmit(struct sk_buff *skb, + struct net_device *dev); +static int epic_rx(struct net_device *dev, int budget); +static int epic_poll(struct napi_struct *napi, int budget); +static irqreturn_t epic_interrupt(int irq, void *dev_instance); +static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static const struct ethtool_ops netdev_ethtool_ops; +static int epic_close(struct net_device *dev); +static struct net_device_stats *epic_get_stats(struct net_device *dev); +static void set_rx_mode(struct net_device *dev); + +static const struct net_device_ops epic_netdev_ops = { + .ndo_open = epic_open, + .ndo_stop = epic_close, + .ndo_start_xmit = epic_start_xmit, + .ndo_tx_timeout = epic_tx_timeout, + .ndo_get_stats = epic_get_stats, + .ndo_set_multicast_list = set_rx_mode, + .ndo_do_ioctl = netdev_ioctl, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static int __devinit epic_init_one (struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + static int card_idx = -1; + long ioaddr; + int chip_idx = (int) ent->driver_data; + int irq; + struct net_device *dev; + struct epic_private *ep; + int i, ret, option = 0, duplex = 0; + void *ring_space; + dma_addr_t ring_dma; + +/* when built into the kernel, we only print version if device is found */ +#ifndef MODULE + static int printed_version; + if (!printed_version++) + printk(KERN_INFO "%s%s", version, version2); +#endif + + card_idx++; + + ret = pci_enable_device(pdev); + if (ret) + goto out; + irq = pdev->irq; + + if (pci_resource_len(pdev, 0) < EPIC_TOTAL_SIZE) { + dev_err(&pdev->dev, "no PCI region space\n"); + ret = -ENODEV; + goto err_out_disable; + } + + pci_set_master(pdev); + + ret = pci_request_regions(pdev, DRV_NAME); + if (ret < 0) + goto err_out_disable; + + ret = -ENOMEM; + + dev = alloc_etherdev(sizeof (*ep)); + if (!dev) { + dev_err(&pdev->dev, "no memory for eth device\n"); + goto err_out_free_res; + } + SET_NETDEV_DEV(dev, &pdev->dev); + +#ifdef USE_IO_OPS + ioaddr = pci_resource_start (pdev, 0); +#else + ioaddr = pci_resource_start (pdev, 1); + ioaddr = (long) pci_ioremap_bar(pdev, 1); + if (!ioaddr) { + dev_err(&pdev->dev, "ioremap failed\n"); + goto err_out_free_netdev; + } +#endif + + pci_set_drvdata(pdev, dev); + ep = netdev_priv(dev); + ep->mii.dev = dev; + ep->mii.mdio_read = mdio_read; + ep->mii.mdio_write = mdio_write; + ep->mii.phy_id_mask = 0x1f; + ep->mii.reg_num_mask = 0x1f; + + ring_space = pci_alloc_consistent(pdev, TX_TOTAL_SIZE, &ring_dma); + if (!ring_space) + goto err_out_iounmap; + ep->tx_ring = ring_space; + ep->tx_ring_dma = ring_dma; + + ring_space = pci_alloc_consistent(pdev, RX_TOTAL_SIZE, &ring_dma); + if (!ring_space) + goto err_out_unmap_tx; + ep->rx_ring = ring_space; + ep->rx_ring_dma = ring_dma; + + if (dev->mem_start) { + option = dev->mem_start; + duplex = (dev->mem_start & 16) ? 1 : 0; + } else if (card_idx >= 0 && card_idx < MAX_UNITS) { + if (options[card_idx] >= 0) + option = options[card_idx]; + if (full_duplex[card_idx] >= 0) + duplex = full_duplex[card_idx]; + } + + dev->base_addr = ioaddr; + dev->irq = irq; + + spin_lock_init(&ep->lock); + spin_lock_init(&ep->napi_lock); + ep->reschedule_in_poll = 0; + + /* Bring the chip out of low-power mode. */ + outl(0x4200, ioaddr + GENCTL); + /* Magic?! If we don't set this bit the MII interface won't work. */ + /* This magic is documented in SMSC app note 7.15 */ + for (i = 16; i > 0; i--) + outl(0x0008, ioaddr + TEST1); + + /* Turn on the MII transceiver. */ + outl(0x12, ioaddr + MIICfg); + if (chip_idx == 1) + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + outl(0x0200, ioaddr + GENCTL); + + /* Note: the '175 does not have a serial EEPROM. */ + for (i = 0; i < 3; i++) + ((__le16 *)dev->dev_addr)[i] = cpu_to_le16(inw(ioaddr + LAN0 + i*4)); + + if (debug > 2) { + dev_printk(KERN_DEBUG, &pdev->dev, "EEPROM contents:\n"); + for (i = 0; i < 64; i++) + printk(" %4.4x%s", read_eeprom(ioaddr, i), + i % 16 == 15 ? "\n" : ""); + } + + ep->pci_dev = pdev; + ep->chip_id = chip_idx; + ep->chip_flags = pci_id_tbl[chip_idx].drv_flags; + ep->irq_mask = + (ep->chip_flags & TYPE2_INTR ? PCIBusErr175 : PCIBusErr170) + | CntFull | TxUnderrun | EpicNapiEvent; + + /* Find the connected MII xcvrs. + Doing this in open() would allow detecting external xcvrs later, but + takes much time and no cards have external MII. */ + { + int phy, phy_idx = 0; + for (phy = 1; phy < 32 && phy_idx < sizeof(ep->phys); phy++) { + int mii_status = mdio_read(dev, phy, MII_BMSR); + if (mii_status != 0xffff && mii_status != 0x0000) { + ep->phys[phy_idx++] = phy; + dev_info(&pdev->dev, + "MII transceiver #%d control " + "%4.4x status %4.4x.\n", + phy, mdio_read(dev, phy, 0), mii_status); + } + } + ep->mii_phy_cnt = phy_idx; + if (phy_idx != 0) { + phy = ep->phys[0]; + ep->mii.advertising = mdio_read(dev, phy, MII_ADVERTISE); + dev_info(&pdev->dev, + "Autonegotiation advertising %4.4x link " + "partner %4.4x.\n", + ep->mii.advertising, mdio_read(dev, phy, 5)); + } else if ( ! (ep->chip_flags & NO_MII)) { + dev_warn(&pdev->dev, + "***WARNING***: No MII transceiver found!\n"); + /* Use the known PHY address of the EPII. */ + ep->phys[0] = 3; + } + ep->mii.phy_id = ep->phys[0]; + } + + /* Turn off the MII xcvr (175 only!), leave the chip in low-power mode. */ + if (ep->chip_flags & MII_PWRDWN) + outl(inl(ioaddr + NVCTL) & ~0x483C, ioaddr + NVCTL); + outl(0x0008, ioaddr + GENCTL); + + /* The lower four bits are the media type. */ + if (duplex) { + ep->mii.force_media = ep->mii.full_duplex = 1; + dev_info(&pdev->dev, "Forced full duplex requested.\n"); + } + dev->if_port = ep->default_port = option; + + /* The Epic-specific entries in the device structure. */ + dev->netdev_ops = &epic_netdev_ops; + dev->ethtool_ops = &netdev_ethtool_ops; + dev->watchdog_timeo = TX_TIMEOUT; + netif_napi_add(dev, &ep->napi, epic_poll, 64); + + ret = register_netdev(dev); + if (ret < 0) + goto err_out_unmap_rx; + + printk(KERN_INFO "%s: %s at %#lx, IRQ %d, %pM\n", + dev->name, pci_id_tbl[chip_idx].name, ioaddr, dev->irq, + dev->dev_addr); + +out: + return ret; + +err_out_unmap_rx: + pci_free_consistent(pdev, RX_TOTAL_SIZE, ep->rx_ring, ep->rx_ring_dma); +err_out_unmap_tx: + pci_free_consistent(pdev, TX_TOTAL_SIZE, ep->tx_ring, ep->tx_ring_dma); +err_out_iounmap: +#ifndef USE_IO_OPS + iounmap(ioaddr); +err_out_free_netdev: +#endif + free_netdev(dev); +err_out_free_res: + pci_release_regions(pdev); +err_out_disable: + pci_disable_device(pdev); + goto out; +} + +/* Serial EEPROM section. */ + +/* EEPROM_Ctrl bits. */ +#define EE_SHIFT_CLK 0x04 /* EEPROM shift clock. */ +#define EE_CS 0x02 /* EEPROM chip select. */ +#define EE_DATA_WRITE 0x08 /* EEPROM chip data in. */ +#define EE_WRITE_0 0x01 +#define EE_WRITE_1 0x09 +#define EE_DATA_READ 0x10 /* EEPROM chip data out. */ +#define EE_ENB (0x0001 | EE_CS) + +/* Delay between EEPROM clock transitions. + This serves to flush the operation to the PCI bus. + */ + +#define eeprom_delay() inl(ee_addr) + +/* The EEPROM commands include the alway-set leading bit. */ +#define EE_WRITE_CMD (5 << 6) +#define EE_READ64_CMD (6 << 6) +#define EE_READ256_CMD (6 << 8) +#define EE_ERASE_CMD (7 << 6) + +static void epic_disable_int(struct net_device *dev, struct epic_private *ep) +{ + long ioaddr = dev->base_addr; + + outl(0x00000000, ioaddr + INTMASK); +} + +static inline void __epic_pci_commit(long ioaddr) +{ +#ifndef USE_IO_OPS + inl(ioaddr + INTMASK); +#endif +} + +static inline void epic_napi_irq_off(struct net_device *dev, + struct epic_private *ep) +{ + long ioaddr = dev->base_addr; + + outl(ep->irq_mask & ~EpicNapiEvent, ioaddr + INTMASK); + __epic_pci_commit(ioaddr); +} + +static inline void epic_napi_irq_on(struct net_device *dev, + struct epic_private *ep) +{ + long ioaddr = dev->base_addr; + + /* No need to commit possible posted write */ + outl(ep->irq_mask | EpicNapiEvent, ioaddr + INTMASK); +} + +static int __devinit read_eeprom(long ioaddr, int location) +{ + int i; + int retval = 0; + long ee_addr = ioaddr + EECTL; + int read_cmd = location | + (inl(ee_addr) & 0x40 ? EE_READ64_CMD : EE_READ256_CMD); + + outl(EE_ENB & ~EE_CS, ee_addr); + outl(EE_ENB, ee_addr); + + /* Shift the read command bits out. */ + for (i = 12; i >= 0; i--) { + short dataval = (read_cmd & (1 << i)) ? EE_WRITE_1 : EE_WRITE_0; + outl(EE_ENB | dataval, ee_addr); + eeprom_delay(); + outl(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr); + eeprom_delay(); + } + outl(EE_ENB, ee_addr); + + for (i = 16; i > 0; i--) { + outl(EE_ENB | EE_SHIFT_CLK, ee_addr); + eeprom_delay(); + retval = (retval << 1) | ((inl(ee_addr) & EE_DATA_READ) ? 1 : 0); + outl(EE_ENB, ee_addr); + eeprom_delay(); + } + + /* Terminate the EEPROM access. */ + outl(EE_ENB & ~EE_CS, ee_addr); + return retval; +} + +#define MII_READOP 1 +#define MII_WRITEOP 2 +static int mdio_read(struct net_device *dev, int phy_id, int location) +{ + long ioaddr = dev->base_addr; + int read_cmd = (phy_id << 9) | (location << 4) | MII_READOP; + int i; + + outl(read_cmd, ioaddr + MIICtrl); + /* Typical operation takes 25 loops. */ + for (i = 400; i > 0; i--) { + barrier(); + if ((inl(ioaddr + MIICtrl) & MII_READOP) == 0) { + /* Work around read failure bug. */ + if (phy_id == 1 && location < 6 && + inw(ioaddr + MIIData) == 0xffff) { + outl(read_cmd, ioaddr + MIICtrl); + continue; + } + return inw(ioaddr + MIIData); + } + } + return 0xffff; +} + +static void mdio_write(struct net_device *dev, int phy_id, int loc, int value) +{ + long ioaddr = dev->base_addr; + int i; + + outw(value, ioaddr + MIIData); + outl((phy_id << 9) | (loc << 4) | MII_WRITEOP, ioaddr + MIICtrl); + for (i = 10000; i > 0; i--) { + barrier(); + if ((inl(ioaddr + MIICtrl) & MII_WRITEOP) == 0) + break; + } +} + + +static int epic_open(struct net_device *dev) +{ + struct epic_private *ep = netdev_priv(dev); + long ioaddr = dev->base_addr; + int i; + int retval; + + /* Soft reset the chip. */ + outl(0x4001, ioaddr + GENCTL); + + napi_enable(&ep->napi); + if ((retval = request_irq(dev->irq, epic_interrupt, IRQF_SHARED, dev->name, dev))) { + napi_disable(&ep->napi); + return retval; + } + + epic_init_ring(dev); + + outl(0x4000, ioaddr + GENCTL); + /* This magic is documented in SMSC app note 7.15 */ + for (i = 16; i > 0; i--) + outl(0x0008, ioaddr + TEST1); + + /* Pull the chip out of low-power mode, enable interrupts, and set for + PCI read multiple. The MIIcfg setting and strange write order are + required by the details of which bits are reset and the transceiver + wiring on the Ositech CardBus card. + */ +#if 0 + outl(dev->if_port == 1 ? 0x13 : 0x12, ioaddr + MIICfg); +#endif + if (ep->chip_flags & MII_PWRDWN) + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + + /* Tell the chip to byteswap descriptors on big-endian hosts */ +#ifdef __BIG_ENDIAN + outl(0x4432 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); + inl(ioaddr + GENCTL); + outl(0x0432 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); +#else + outl(0x4412 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); + inl(ioaddr + GENCTL); + outl(0x0412 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); +#endif + + udelay(20); /* Looks like EPII needs that if you want reliable RX init. FIXME: pci posting bug? */ + + for (i = 0; i < 3; i++) + outl(le16_to_cpu(((__le16*)dev->dev_addr)[i]), ioaddr + LAN0 + i*4); + + ep->tx_threshold = TX_FIFO_THRESH; + outl(ep->tx_threshold, ioaddr + TxThresh); + + if (media2miictl[dev->if_port & 15]) { + if (ep->mii_phy_cnt) + mdio_write(dev, ep->phys[0], MII_BMCR, media2miictl[dev->if_port&15]); + if (dev->if_port == 1) { + if (debug > 1) + printk(KERN_INFO "%s: Using the 10base2 transceiver, MII " + "status %4.4x.\n", + dev->name, mdio_read(dev, ep->phys[0], MII_BMSR)); + } + } else { + int mii_lpa = mdio_read(dev, ep->phys[0], MII_LPA); + if (mii_lpa != 0xffff) { + if ((mii_lpa & LPA_100FULL) || (mii_lpa & 0x01C0) == LPA_10FULL) + ep->mii.full_duplex = 1; + else if (! (mii_lpa & LPA_LPACK)) + mdio_write(dev, ep->phys[0], MII_BMCR, BMCR_ANENABLE|BMCR_ANRESTART); + if (debug > 1) + printk(KERN_INFO "%s: Setting %s-duplex based on MII xcvr %d" + " register read of %4.4x.\n", dev->name, + ep->mii.full_duplex ? "full" : "half", + ep->phys[0], mii_lpa); + } + } + + outl(ep->mii.full_duplex ? 0x7F : 0x79, ioaddr + TxCtrl); + outl(ep->rx_ring_dma, ioaddr + PRxCDAR); + outl(ep->tx_ring_dma, ioaddr + PTxCDAR); + + /* Start the chip's Rx process. */ + set_rx_mode(dev); + outl(StartRx | RxQueued, ioaddr + COMMAND); + + netif_start_queue(dev); + + /* Enable interrupts by setting the interrupt mask. */ + outl((ep->chip_flags & TYPE2_INTR ? PCIBusErr175 : PCIBusErr170) + | CntFull | TxUnderrun + | RxError | RxHeader | EpicNapiEvent, ioaddr + INTMASK); + + if (debug > 1) + printk(KERN_DEBUG "%s: epic_open() ioaddr %lx IRQ %d status %4.4x " + "%s-duplex.\n", + dev->name, ioaddr, dev->irq, (int)inl(ioaddr + GENCTL), + ep->mii.full_duplex ? "full" : "half"); + + /* Set the timer to switch to check for link beat and perhaps switch + to an alternate media type. */ + init_timer(&ep->timer); + ep->timer.expires = jiffies + 3*HZ; + ep->timer.data = (unsigned long)dev; + ep->timer.function = epic_timer; /* timer handler */ + add_timer(&ep->timer); + + return 0; +} + +/* Reset the chip to recover from a PCI transaction error. + This may occur at interrupt time. */ +static void epic_pause(struct net_device *dev) +{ + long ioaddr = dev->base_addr; + + netif_stop_queue (dev); + + /* Disable interrupts by clearing the interrupt mask. */ + outl(0x00000000, ioaddr + INTMASK); + /* Stop the chip's Tx and Rx DMA processes. */ + outw(StopRx | StopTxDMA | StopRxDMA, ioaddr + COMMAND); + + /* Update the error counts. */ + if (inw(ioaddr + COMMAND) != 0xffff) { + dev->stats.rx_missed_errors += inb(ioaddr + MPCNT); + dev->stats.rx_frame_errors += inb(ioaddr + ALICNT); + dev->stats.rx_crc_errors += inb(ioaddr + CRCCNT); + } + + /* Remove the packets on the Rx queue. */ + epic_rx(dev, RX_RING_SIZE); +} + +static void epic_restart(struct net_device *dev) +{ + long ioaddr = dev->base_addr; + struct epic_private *ep = netdev_priv(dev); + int i; + + /* Soft reset the chip. */ + outl(0x4001, ioaddr + GENCTL); + + printk(KERN_DEBUG "%s: Restarting the EPIC chip, Rx %d/%d Tx %d/%d.\n", + dev->name, ep->cur_rx, ep->dirty_rx, ep->dirty_tx, ep->cur_tx); + udelay(1); + + /* This magic is documented in SMSC app note 7.15 */ + for (i = 16; i > 0; i--) + outl(0x0008, ioaddr + TEST1); + +#ifdef __BIG_ENDIAN + outl(0x0432 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); +#else + outl(0x0412 | (RX_FIFO_THRESH<<8), ioaddr + GENCTL); +#endif + outl(dev->if_port == 1 ? 0x13 : 0x12, ioaddr + MIICfg); + if (ep->chip_flags & MII_PWRDWN) + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + + for (i = 0; i < 3; i++) + outl(le16_to_cpu(((__le16*)dev->dev_addr)[i]), ioaddr + LAN0 + i*4); + + ep->tx_threshold = TX_FIFO_THRESH; + outl(ep->tx_threshold, ioaddr + TxThresh); + outl(ep->mii.full_duplex ? 0x7F : 0x79, ioaddr + TxCtrl); + outl(ep->rx_ring_dma + (ep->cur_rx%RX_RING_SIZE)* + sizeof(struct epic_rx_desc), ioaddr + PRxCDAR); + outl(ep->tx_ring_dma + (ep->dirty_tx%TX_RING_SIZE)* + sizeof(struct epic_tx_desc), ioaddr + PTxCDAR); + + /* Start the chip's Rx process. */ + set_rx_mode(dev); + outl(StartRx | RxQueued, ioaddr + COMMAND); + + /* Enable interrupts by setting the interrupt mask. */ + outl((ep->chip_flags & TYPE2_INTR ? PCIBusErr175 : PCIBusErr170) + | CntFull | TxUnderrun + | RxError | RxHeader | EpicNapiEvent, ioaddr + INTMASK); + + printk(KERN_DEBUG "%s: epic_restart() done, cmd status %4.4x, ctl %4.4x" + " interrupt %4.4x.\n", + dev->name, (int)inl(ioaddr + COMMAND), (int)inl(ioaddr + GENCTL), + (int)inl(ioaddr + INTSTAT)); +} + +static void check_media(struct net_device *dev) +{ + struct epic_private *ep = netdev_priv(dev); + long ioaddr = dev->base_addr; + int mii_lpa = ep->mii_phy_cnt ? mdio_read(dev, ep->phys[0], MII_LPA) : 0; + int negotiated = mii_lpa & ep->mii.advertising; + int duplex = (negotiated & 0x0100) || (negotiated & 0x01C0) == 0x0040; + + if (ep->mii.force_media) + return; + if (mii_lpa == 0xffff) /* Bogus read */ + return; + if (ep->mii.full_duplex != duplex) { + ep->mii.full_duplex = duplex; + printk(KERN_INFO "%s: Setting %s-duplex based on MII #%d link" + " partner capability of %4.4x.\n", dev->name, + ep->mii.full_duplex ? "full" : "half", ep->phys[0], mii_lpa); + outl(ep->mii.full_duplex ? 0x7F : 0x79, ioaddr + TxCtrl); + } +} + +static void epic_timer(unsigned long data) +{ + struct net_device *dev = (struct net_device *)data; + struct epic_private *ep = netdev_priv(dev); + long ioaddr = dev->base_addr; + int next_tick = 5*HZ; + + if (debug > 3) { + printk(KERN_DEBUG "%s: Media monitor tick, Tx status %8.8x.\n", + dev->name, (int)inl(ioaddr + TxSTAT)); + printk(KERN_DEBUG "%s: Other registers are IntMask %4.4x " + "IntStatus %4.4x RxStatus %4.4x.\n", + dev->name, (int)inl(ioaddr + INTMASK), + (int)inl(ioaddr + INTSTAT), (int)inl(ioaddr + RxSTAT)); + } + + check_media(dev); + + ep->timer.expires = jiffies + next_tick; + add_timer(&ep->timer); +} + +static void epic_tx_timeout(struct net_device *dev) +{ + struct epic_private *ep = netdev_priv(dev); + long ioaddr = dev->base_addr; + + if (debug > 0) { + printk(KERN_WARNING "%s: Transmit timeout using MII device, " + "Tx status %4.4x.\n", + dev->name, (int)inw(ioaddr + TxSTAT)); + if (debug > 1) { + printk(KERN_DEBUG "%s: Tx indices: dirty_tx %d, cur_tx %d.\n", + dev->name, ep->dirty_tx, ep->cur_tx); + } + } + if (inw(ioaddr + TxSTAT) & 0x10) { /* Tx FIFO underflow. */ + dev->stats.tx_fifo_errors++; + outl(RestartTx, ioaddr + COMMAND); + } else { + epic_restart(dev); + outl(TxQueued, dev->base_addr + COMMAND); + } + + dev->trans_start = jiffies; /* prevent tx timeout */ + dev->stats.tx_errors++; + if (!ep->tx_full) + netif_wake_queue(dev); +} + +/* Initialize the Rx and Tx rings, along with various 'dev' bits. */ +static void epic_init_ring(struct net_device *dev) +{ + struct epic_private *ep = netdev_priv(dev); + int i; + + ep->tx_full = 0; + ep->dirty_tx = ep->cur_tx = 0; + ep->cur_rx = ep->dirty_rx = 0; + ep->rx_buf_sz = (dev->mtu <= 1500 ? PKT_BUF_SZ : dev->mtu + 32); + + /* Initialize all Rx descriptors. */ + for (i = 0; i < RX_RING_SIZE; i++) { + ep->rx_ring[i].rxstatus = 0; + ep->rx_ring[i].buflength = ep->rx_buf_sz; + ep->rx_ring[i].next = ep->rx_ring_dma + + (i+1)*sizeof(struct epic_rx_desc); + ep->rx_skbuff[i] = NULL; + } + /* Mark the last entry as wrapping the ring. */ + ep->rx_ring[i-1].next = ep->rx_ring_dma; + + /* Fill in the Rx buffers. Handle allocation failure gracefully. */ + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb = dev_alloc_skb(ep->rx_buf_sz + 2); + ep->rx_skbuff[i] = skb; + if (skb == NULL) + break; + skb_reserve(skb, 2); /* 16 byte align the IP header. */ + ep->rx_ring[i].bufaddr = pci_map_single(ep->pci_dev, + skb->data, ep->rx_buf_sz, PCI_DMA_FROMDEVICE); + ep->rx_ring[i].rxstatus = DescOwn; + } + ep->dirty_rx = (unsigned int)(i - RX_RING_SIZE); + + /* The Tx buffer descriptor is filled in as needed, but we + do need to clear the ownership bit. */ + for (i = 0; i < TX_RING_SIZE; i++) { + ep->tx_skbuff[i] = NULL; + ep->tx_ring[i].txstatus = 0x0000; + ep->tx_ring[i].next = ep->tx_ring_dma + + (i+1)*sizeof(struct epic_tx_desc); + } + ep->tx_ring[i-1].next = ep->tx_ring_dma; +} + +static netdev_tx_t epic_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct epic_private *ep = netdev_priv(dev); + int entry, free_count; + u32 ctrl_word; + unsigned long flags; + + if (skb_padto(skb, ETH_ZLEN)) + return NETDEV_TX_OK; + + /* Caution: the write order is important here, set the field with the + "ownership" bit last. */ + + /* Calculate the next Tx descriptor entry. */ + spin_lock_irqsave(&ep->lock, flags); + free_count = ep->cur_tx - ep->dirty_tx; + entry = ep->cur_tx % TX_RING_SIZE; + + ep->tx_skbuff[entry] = skb; + ep->tx_ring[entry].bufaddr = pci_map_single(ep->pci_dev, skb->data, + skb->len, PCI_DMA_TODEVICE); + if (free_count < TX_QUEUE_LEN/2) {/* Typical path */ + ctrl_word = 0x100000; /* No interrupt */ + } else if (free_count == TX_QUEUE_LEN/2) { + ctrl_word = 0x140000; /* Tx-done intr. */ + } else if (free_count < TX_QUEUE_LEN - 1) { + ctrl_word = 0x100000; /* No Tx-done intr. */ + } else { + /* Leave room for an additional entry. */ + ctrl_word = 0x140000; /* Tx-done intr. */ + ep->tx_full = 1; + } + ep->tx_ring[entry].buflength = ctrl_word | skb->len; + ep->tx_ring[entry].txstatus = + ((skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN) << 16) + | DescOwn; + + ep->cur_tx++; + if (ep->tx_full) + netif_stop_queue(dev); + + spin_unlock_irqrestore(&ep->lock, flags); + /* Trigger an immediate transmit demand. */ + outl(TxQueued, dev->base_addr + COMMAND); + + if (debug > 4) + printk(KERN_DEBUG "%s: Queued Tx packet size %d to slot %d, " + "flag %2.2x Tx status %8.8x.\n", + dev->name, (int)skb->len, entry, ctrl_word, + (int)inl(dev->base_addr + TxSTAT)); + + return NETDEV_TX_OK; +} + +static void epic_tx_error(struct net_device *dev, struct epic_private *ep, + int status) +{ + struct net_device_stats *stats = &dev->stats; + +#ifndef final_version + /* There was an major error, log it. */ + if (debug > 1) + printk(KERN_DEBUG "%s: Transmit error, Tx status %8.8x.\n", + dev->name, status); +#endif + stats->tx_errors++; + if (status & 0x1050) + stats->tx_aborted_errors++; + if (status & 0x0008) + stats->tx_carrier_errors++; + if (status & 0x0040) + stats->tx_window_errors++; + if (status & 0x0010) + stats->tx_fifo_errors++; +} + +static void epic_tx(struct net_device *dev, struct epic_private *ep) +{ + unsigned int dirty_tx, cur_tx; + + /* + * Note: if this lock becomes a problem we can narrow the locked + * region at the cost of occasionally grabbing the lock more times. + */ + cur_tx = ep->cur_tx; + for (dirty_tx = ep->dirty_tx; cur_tx - dirty_tx > 0; dirty_tx++) { + struct sk_buff *skb; + int entry = dirty_tx % TX_RING_SIZE; + int txstatus = ep->tx_ring[entry].txstatus; + + if (txstatus & DescOwn) + break; /* It still hasn't been Txed */ + + if (likely(txstatus & 0x0001)) { + dev->stats.collisions += (txstatus >> 8) & 15; + dev->stats.tx_packets++; + dev->stats.tx_bytes += ep->tx_skbuff[entry]->len; + } else + epic_tx_error(dev, ep, txstatus); + + /* Free the original skb. */ + skb = ep->tx_skbuff[entry]; + pci_unmap_single(ep->pci_dev, ep->tx_ring[entry].bufaddr, + skb->len, PCI_DMA_TODEVICE); + dev_kfree_skb_irq(skb); + ep->tx_skbuff[entry] = NULL; + } + +#ifndef final_version + if (cur_tx - dirty_tx > TX_RING_SIZE) { + printk(KERN_WARNING + "%s: Out-of-sync dirty pointer, %d vs. %d, full=%d.\n", + dev->name, dirty_tx, cur_tx, ep->tx_full); + dirty_tx += TX_RING_SIZE; + } +#endif + ep->dirty_tx = dirty_tx; + if (ep->tx_full && cur_tx - dirty_tx < TX_QUEUE_LEN - 4) { + /* The ring is no longer full, allow new TX entries. */ + ep->tx_full = 0; + netif_wake_queue(dev); + } +} + +/* The interrupt handler does all of the Rx thread work and cleans up + after the Tx thread. */ +static irqreturn_t epic_interrupt(int irq, void *dev_instance) +{ + struct net_device *dev = dev_instance; + struct epic_private *ep = netdev_priv(dev); + long ioaddr = dev->base_addr; + unsigned int handled = 0; + int status; + + status = inl(ioaddr + INTSTAT); + /* Acknowledge all of the current interrupt sources ASAP. */ + outl(status & EpicNormalEvent, ioaddr + INTSTAT); + + if (debug > 4) { + printk(KERN_DEBUG "%s: Interrupt, status=%#8.8x new " + "intstat=%#8.8x.\n", dev->name, status, + (int)inl(ioaddr + INTSTAT)); + } + + if ((status & IntrSummary) == 0) + goto out; + + handled = 1; + + if ((status & EpicNapiEvent) && !ep->reschedule_in_poll) { + spin_lock(&ep->napi_lock); + if (napi_schedule_prep(&ep->napi)) { + epic_napi_irq_off(dev, ep); + __napi_schedule(&ep->napi); + } else + ep->reschedule_in_poll++; + spin_unlock(&ep->napi_lock); + } + status &= ~EpicNapiEvent; + + /* Check uncommon events all at once. */ + if (status & (CntFull | TxUnderrun | PCIBusErr170 | PCIBusErr175)) { + if (status == EpicRemoved) + goto out; + + /* Always update the error counts to avoid overhead later. */ + dev->stats.rx_missed_errors += inb(ioaddr + MPCNT); + dev->stats.rx_frame_errors += inb(ioaddr + ALICNT); + dev->stats.rx_crc_errors += inb(ioaddr + CRCCNT); + + if (status & TxUnderrun) { /* Tx FIFO underflow. */ + dev->stats.tx_fifo_errors++; + outl(ep->tx_threshold += 128, ioaddr + TxThresh); + /* Restart the transmit process. */ + outl(RestartTx, ioaddr + COMMAND); + } + if (status & PCIBusErr170) { + printk(KERN_ERR "%s: PCI Bus Error! status %4.4x.\n", + dev->name, status); + epic_pause(dev); + epic_restart(dev); + } + /* Clear all error sources. */ + outl(status & 0x7f18, ioaddr + INTSTAT); + } + +out: + if (debug > 3) { + printk(KERN_DEBUG "%s: exit interrupt, intr_status=%#4.4x.\n", + dev->name, status); + } + + return IRQ_RETVAL(handled); +} + +static int epic_rx(struct net_device *dev, int budget) +{ + struct epic_private *ep = netdev_priv(dev); + int entry = ep->cur_rx % RX_RING_SIZE; + int rx_work_limit = ep->dirty_rx + RX_RING_SIZE - ep->cur_rx; + int work_done = 0; + + if (debug > 4) + printk(KERN_DEBUG " In epic_rx(), entry %d %8.8x.\n", entry, + ep->rx_ring[entry].rxstatus); + + if (rx_work_limit > budget) + rx_work_limit = budget; + + /* If we own the next entry, it's a new packet. Send it up. */ + while ((ep->rx_ring[entry].rxstatus & DescOwn) == 0) { + int status = ep->rx_ring[entry].rxstatus; + + if (debug > 4) + printk(KERN_DEBUG " epic_rx() status was %8.8x.\n", status); + if (--rx_work_limit < 0) + break; + if (status & 0x2006) { + if (debug > 2) + printk(KERN_DEBUG "%s: epic_rx() error status was %8.8x.\n", + dev->name, status); + if (status & 0x2000) { + printk(KERN_WARNING "%s: Oversized Ethernet frame spanned " + "multiple buffers, status %4.4x!\n", dev->name, status); + dev->stats.rx_length_errors++; + } else if (status & 0x0006) + /* Rx Frame errors are counted in hardware. */ + dev->stats.rx_errors++; + } else { + /* Malloc up new buffer, compatible with net-2e. */ + /* Omit the four octet CRC from the length. */ + short pkt_len = (status >> 16) - 4; + struct sk_buff *skb; + + if (pkt_len > PKT_BUF_SZ - 4) { + printk(KERN_ERR "%s: Oversized Ethernet frame, status %x " + "%d bytes.\n", + dev->name, status, pkt_len); + pkt_len = 1514; + } + /* Check if the packet is long enough to accept without copying + to a minimally-sized skbuff. */ + if (pkt_len < rx_copybreak && + (skb = dev_alloc_skb(pkt_len + 2)) != NULL) { + skb_reserve(skb, 2); /* 16 byte align the IP header */ + pci_dma_sync_single_for_cpu(ep->pci_dev, + ep->rx_ring[entry].bufaddr, + ep->rx_buf_sz, + PCI_DMA_FROMDEVICE); + skb_copy_to_linear_data(skb, ep->rx_skbuff[entry]->data, pkt_len); + skb_put(skb, pkt_len); + pci_dma_sync_single_for_device(ep->pci_dev, + ep->rx_ring[entry].bufaddr, + ep->rx_buf_sz, + PCI_DMA_FROMDEVICE); + } else { + pci_unmap_single(ep->pci_dev, + ep->rx_ring[entry].bufaddr, + ep->rx_buf_sz, PCI_DMA_FROMDEVICE); + skb_put(skb = ep->rx_skbuff[entry], pkt_len); + ep->rx_skbuff[entry] = NULL; + } + skb->protocol = eth_type_trans(skb, dev); + netif_receive_skb(skb); + dev->stats.rx_packets++; + dev->stats.rx_bytes += pkt_len; + } + work_done++; + entry = (++ep->cur_rx) % RX_RING_SIZE; + } + + /* Refill the Rx ring buffers. */ + for (; ep->cur_rx - ep->dirty_rx > 0; ep->dirty_rx++) { + entry = ep->dirty_rx % RX_RING_SIZE; + if (ep->rx_skbuff[entry] == NULL) { + struct sk_buff *skb; + skb = ep->rx_skbuff[entry] = dev_alloc_skb(ep->rx_buf_sz + 2); + if (skb == NULL) + break; + skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */ + ep->rx_ring[entry].bufaddr = pci_map_single(ep->pci_dev, + skb->data, ep->rx_buf_sz, PCI_DMA_FROMDEVICE); + work_done++; + } + /* AV: shouldn't we add a barrier here? */ + ep->rx_ring[entry].rxstatus = DescOwn; + } + return work_done; +} + +static void epic_rx_err(struct net_device *dev, struct epic_private *ep) +{ + long ioaddr = dev->base_addr; + int status; + + status = inl(ioaddr + INTSTAT); + + if (status == EpicRemoved) + return; + if (status & RxOverflow) /* Missed a Rx frame. */ + dev->stats.rx_errors++; + if (status & (RxOverflow | RxFull)) + outw(RxQueued, ioaddr + COMMAND); +} + +static int epic_poll(struct napi_struct *napi, int budget) +{ + struct epic_private *ep = container_of(napi, struct epic_private, napi); + struct net_device *dev = ep->mii.dev; + int work_done = 0; + long ioaddr = dev->base_addr; + +rx_action: + + epic_tx(dev, ep); + + work_done += epic_rx(dev, budget); + + epic_rx_err(dev, ep); + + if (work_done < budget) { + unsigned long flags; + int more; + + /* A bit baroque but it avoids a (space hungry) spin_unlock */ + + spin_lock_irqsave(&ep->napi_lock, flags); + + more = ep->reschedule_in_poll; + if (!more) { + __napi_complete(napi); + outl(EpicNapiEvent, ioaddr + INTSTAT); + epic_napi_irq_on(dev, ep); + } else + ep->reschedule_in_poll--; + + spin_unlock_irqrestore(&ep->napi_lock, flags); + + if (more) + goto rx_action; + } + + return work_done; +} + +static int epic_close(struct net_device *dev) +{ + long ioaddr = dev->base_addr; + struct epic_private *ep = netdev_priv(dev); + struct sk_buff *skb; + int i; + + netif_stop_queue(dev); + napi_disable(&ep->napi); + + if (debug > 1) + printk(KERN_DEBUG "%s: Shutting down ethercard, status was %2.2x.\n", + dev->name, (int)inl(ioaddr + INTSTAT)); + + del_timer_sync(&ep->timer); + + epic_disable_int(dev, ep); + + free_irq(dev->irq, dev); + + epic_pause(dev); + + /* Free all the skbuffs in the Rx queue. */ + for (i = 0; i < RX_RING_SIZE; i++) { + skb = ep->rx_skbuff[i]; + ep->rx_skbuff[i] = NULL; + ep->rx_ring[i].rxstatus = 0; /* Not owned by Epic chip. */ + ep->rx_ring[i].buflength = 0; + if (skb) { + pci_unmap_single(ep->pci_dev, ep->rx_ring[i].bufaddr, + ep->rx_buf_sz, PCI_DMA_FROMDEVICE); + dev_kfree_skb(skb); + } + ep->rx_ring[i].bufaddr = 0xBADF00D0; /* An invalid address. */ + } + for (i = 0; i < TX_RING_SIZE; i++) { + skb = ep->tx_skbuff[i]; + ep->tx_skbuff[i] = NULL; + if (!skb) + continue; + pci_unmap_single(ep->pci_dev, ep->tx_ring[i].bufaddr, + skb->len, PCI_DMA_TODEVICE); + dev_kfree_skb(skb); + } + + /* Green! Leave the chip in low-power mode. */ + outl(0x0008, ioaddr + GENCTL); + + return 0; +} + +static struct net_device_stats *epic_get_stats(struct net_device *dev) +{ + long ioaddr = dev->base_addr; + + if (netif_running(dev)) { + /* Update the error counts. */ + dev->stats.rx_missed_errors += inb(ioaddr + MPCNT); + dev->stats.rx_frame_errors += inb(ioaddr + ALICNT); + dev->stats.rx_crc_errors += inb(ioaddr + CRCCNT); + } + + return &dev->stats; +} + +/* Set or clear the multicast filter for this adaptor. + Note that we only use exclusion around actually queueing the + new frame, not around filling ep->setup_frame. This is non-deterministic + when re-entered but still correct. */ + +static void set_rx_mode(struct net_device *dev) +{ + long ioaddr = dev->base_addr; + struct epic_private *ep = netdev_priv(dev); + unsigned char mc_filter[8]; /* Multicast hash filter */ + int i; + + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + outl(0x002C, ioaddr + RxCtrl); + /* Unconditionally log net taps. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + } else if ((!netdev_mc_empty(dev)) || (dev->flags & IFF_ALLMULTI)) { + /* There is apparently a chip bug, so the multicast filter + is never enabled. */ + /* Too many to filter perfectly -- accept all multicasts. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + outl(0x000C, ioaddr + RxCtrl); + } else if (netdev_mc_empty(dev)) { + outl(0x0004, ioaddr + RxCtrl); + return; + } else { /* Never executed, for now. */ + struct netdev_hw_addr *ha; + + memset(mc_filter, 0, sizeof(mc_filter)); + netdev_for_each_mc_addr(ha, dev) { + unsigned int bit_nr = + ether_crc_le(ETH_ALEN, ha->addr) & 0x3f; + mc_filter[bit_nr >> 3] |= (1 << bit_nr); + } + } + /* ToDo: perhaps we need to stop the Tx and Rx process here? */ + if (memcmp(mc_filter, ep->mc_filter, sizeof(mc_filter))) { + for (i = 0; i < 4; i++) + outw(((u16 *)mc_filter)[i], ioaddr + MC0 + i*4); + memcpy(ep->mc_filter, mc_filter, sizeof(mc_filter)); + } +} + +static void netdev_get_drvinfo (struct net_device *dev, struct ethtool_drvinfo *info) +{ + struct epic_private *np = netdev_priv(dev); + + strcpy (info->driver, DRV_NAME); + strcpy (info->version, DRV_VERSION); + strcpy (info->bus_info, pci_name(np->pci_dev)); +} + +static int netdev_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct epic_private *np = netdev_priv(dev); + int rc; + + spin_lock_irq(&np->lock); + rc = mii_ethtool_gset(&np->mii, cmd); + spin_unlock_irq(&np->lock); + + return rc; +} + +static int netdev_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct epic_private *np = netdev_priv(dev); + int rc; + + spin_lock_irq(&np->lock); + rc = mii_ethtool_sset(&np->mii, cmd); + spin_unlock_irq(&np->lock); + + return rc; +} + +static int netdev_nway_reset(struct net_device *dev) +{ + struct epic_private *np = netdev_priv(dev); + return mii_nway_restart(&np->mii); +} + +static u32 netdev_get_link(struct net_device *dev) +{ + struct epic_private *np = netdev_priv(dev); + return mii_link_ok(&np->mii); +} + +static u32 netdev_get_msglevel(struct net_device *dev) +{ + return debug; +} + +static void netdev_set_msglevel(struct net_device *dev, u32 value) +{ + debug = value; +} + +static int ethtool_begin(struct net_device *dev) +{ + unsigned long ioaddr = dev->base_addr; + /* power-up, if interface is down */ + if (! netif_running(dev)) { + outl(0x0200, ioaddr + GENCTL); + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + } + return 0; +} + +static void ethtool_complete(struct net_device *dev) +{ + unsigned long ioaddr = dev->base_addr; + /* power-down, if interface is down */ + if (! netif_running(dev)) { + outl(0x0008, ioaddr + GENCTL); + outl((inl(ioaddr + NVCTL) & ~0x483C) | 0x0000, ioaddr + NVCTL); + } +} + +static const struct ethtool_ops netdev_ethtool_ops = { + .get_drvinfo = netdev_get_drvinfo, + .get_settings = netdev_get_settings, + .set_settings = netdev_set_settings, + .nway_reset = netdev_nway_reset, + .get_link = netdev_get_link, + .get_msglevel = netdev_get_msglevel, + .set_msglevel = netdev_set_msglevel, + .begin = ethtool_begin, + .complete = ethtool_complete +}; + +static int netdev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct epic_private *np = netdev_priv(dev); + long ioaddr = dev->base_addr; + struct mii_ioctl_data *data = if_mii(rq); + int rc; + + /* power-up, if interface is down */ + if (! netif_running(dev)) { + outl(0x0200, ioaddr + GENCTL); + outl((inl(ioaddr + NVCTL) & ~0x003C) | 0x4800, ioaddr + NVCTL); + } + + /* all non-ethtool ioctls (the SIOC[GS]MIIxxx ioctls) */ + spin_lock_irq(&np->lock); + rc = generic_mii_ioctl(&np->mii, data, cmd, NULL); + spin_unlock_irq(&np->lock); + + /* power-down, if interface is down */ + if (! netif_running(dev)) { + outl(0x0008, ioaddr + GENCTL); + outl((inl(ioaddr + NVCTL) & ~0x483C) | 0x0000, ioaddr + NVCTL); + } + return rc; +} + + +static void __devexit epic_remove_one (struct pci_dev *pdev) +{ + struct net_device *dev = pci_get_drvdata(pdev); + struct epic_private *ep = netdev_priv(dev); + + pci_free_consistent(pdev, TX_TOTAL_SIZE, ep->tx_ring, ep->tx_ring_dma); + pci_free_consistent(pdev, RX_TOTAL_SIZE, ep->rx_ring, ep->rx_ring_dma); + unregister_netdev(dev); +#ifndef USE_IO_OPS + iounmap((void*) dev->base_addr); +#endif + pci_release_regions(pdev); + free_netdev(dev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + /* pci_power_off(pdev, -1); */ +} + + +#ifdef CONFIG_PM + +static int epic_suspend (struct pci_dev *pdev, pm_message_t state) +{ + struct net_device *dev = pci_get_drvdata(pdev); + long ioaddr = dev->base_addr; + + if (!netif_running(dev)) + return 0; + epic_pause(dev); + /* Put the chip into low-power mode. */ + outl(0x0008, ioaddr + GENCTL); + /* pci_power_off(pdev, -1); */ + return 0; +} + + +static int epic_resume (struct pci_dev *pdev) +{ + struct net_device *dev = pci_get_drvdata(pdev); + + if (!netif_running(dev)) + return 0; + epic_restart(dev); + /* pci_power_on(pdev); */ + return 0; +} + +#endif /* CONFIG_PM */ + + +static struct pci_driver epic_driver = { + .name = DRV_NAME, + .id_table = epic_pci_tbl, + .probe = epic_init_one, + .remove = __devexit_p(epic_remove_one), +#ifdef CONFIG_PM + .suspend = epic_suspend, + .resume = epic_resume, +#endif /* CONFIG_PM */ +}; + + +static int __init epic_init (void) +{ +/* when a module, this is printed whether or not devices are found in probe */ +#ifdef MODULE + printk (KERN_INFO "%s%s", + version, version2); +#endif + + return pci_register_driver(&epic_driver); +} + + +static void __exit epic_cleanup (void) +{ + pci_unregister_driver (&epic_driver); +} + + +module_init(epic_init); +module_exit(epic_cleanup); diff --git a/drivers/net/ethernet/smsc/smc911x.c b/drivers/net/ethernet/smsc/smc911x.c new file mode 100644 index 000000000000..a91fe1723020 --- /dev/null +++ b/drivers/net/ethernet/smsc/smc911x.c @@ -0,0 +1,2210 @@ +/* + * smc911x.c + * This is a driver for SMSC's LAN911{5,6,7,8} single-chip Ethernet devices. + * + * Copyright (C) 2005 Sensoria Corp + * Derived from the unified SMC91x driver by Nicolas Pitre + * and the smsc911x.c reference driver by SMSC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Arguments: + * watchdog = TX watchdog timeout + * tx_fifo_kb = Size of TX FIFO in KB + * + * History: + * 04/16/05 Dustin McIntire Initial version + */ +static const char version[] = + "smc911x.c: v1.0 04-16-2005 by Dustin McIntire <dustin@sensoria.com>\n"; + +/* Debugging options */ +#define ENABLE_SMC_DEBUG_RX 0 +#define ENABLE_SMC_DEBUG_TX 0 +#define ENABLE_SMC_DEBUG_DMA 0 +#define ENABLE_SMC_DEBUG_PKTS 0 +#define ENABLE_SMC_DEBUG_MISC 0 +#define ENABLE_SMC_DEBUG_FUNC 0 + +#define SMC_DEBUG_RX ((ENABLE_SMC_DEBUG_RX ? 1 : 0) << 0) +#define SMC_DEBUG_TX ((ENABLE_SMC_DEBUG_TX ? 1 : 0) << 1) +#define SMC_DEBUG_DMA ((ENABLE_SMC_DEBUG_DMA ? 1 : 0) << 2) +#define SMC_DEBUG_PKTS ((ENABLE_SMC_DEBUG_PKTS ? 1 : 0) << 3) +#define SMC_DEBUG_MISC ((ENABLE_SMC_DEBUG_MISC ? 1 : 0) << 4) +#define SMC_DEBUG_FUNC ((ENABLE_SMC_DEBUG_FUNC ? 1 : 0) << 5) + +#ifndef SMC_DEBUG +#define SMC_DEBUG ( SMC_DEBUG_RX | \ + SMC_DEBUG_TX | \ + SMC_DEBUG_DMA | \ + SMC_DEBUG_PKTS | \ + SMC_DEBUG_MISC | \ + SMC_DEBUG_FUNC \ + ) +#endif + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/crc32.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/workqueue.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <asm/io.h> + +#include "smc911x.h" + +/* + * Transmit timeout, default 5 seconds. + */ +static int watchdog = 5000; +module_param(watchdog, int, 0400); +MODULE_PARM_DESC(watchdog, "transmit timeout in milliseconds"); + +static int tx_fifo_kb=8; +module_param(tx_fifo_kb, int, 0400); +MODULE_PARM_DESC(tx_fifo_kb,"transmit FIFO size in KB (1<x<15)(default=8)"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:smc911x"); + +/* + * The internal workings of the driver. If you are changing anything + * here with the SMC stuff, you should have the datasheet and know + * what you are doing. + */ +#define CARDNAME "smc911x" + +/* + * Use power-down feature of the chip + */ +#define POWER_DOWN 1 + +#if SMC_DEBUG > 0 +#define DBG(n, args...) \ + do { \ + if (SMC_DEBUG & (n)) \ + printk(args); \ + } while (0) + +#define PRINTK(args...) printk(args) +#else +#define DBG(n, args...) do { } while (0) +#define PRINTK(args...) printk(KERN_DEBUG args) +#endif + +#if SMC_DEBUG_PKTS > 0 +static void PRINT_PKT(u_char *buf, int length) +{ + int i; + int remainder; + int lines; + + lines = length / 16; + remainder = length % 16; + + for (i = 0; i < lines ; i ++) { + int cur; + for (cur = 0; cur < 8; cur++) { + u_char a, b; + a = *buf++; + b = *buf++; + printk("%02x%02x ", a, b); + } + printk("\n"); + } + for (i = 0; i < remainder/2 ; i++) { + u_char a, b; + a = *buf++; + b = *buf++; + printk("%02x%02x ", a, b); + } + printk("\n"); +} +#else +#define PRINT_PKT(x...) do { } while (0) +#endif + + +/* this enables an interrupt in the interrupt mask register */ +#define SMC_ENABLE_INT(lp, x) do { \ + unsigned int __mask; \ + __mask = SMC_GET_INT_EN((lp)); \ + __mask |= (x); \ + SMC_SET_INT_EN((lp), __mask); \ +} while (0) + +/* this disables an interrupt from the interrupt mask register */ +#define SMC_DISABLE_INT(lp, x) do { \ + unsigned int __mask; \ + __mask = SMC_GET_INT_EN((lp)); \ + __mask &= ~(x); \ + SMC_SET_INT_EN((lp), __mask); \ +} while (0) + +/* + * this does a soft reset on the device + */ +static void smc911x_reset(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned int reg, timeout=0, resets=1, irq_cfg; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + /* Take out of PM setting first */ + if ((SMC_GET_PMT_CTRL(lp) & PMT_CTRL_READY_) == 0) { + /* Write to the bytetest will take out of powerdown */ + SMC_SET_BYTE_TEST(lp, 0); + timeout=10; + do { + udelay(10); + reg = SMC_GET_PMT_CTRL(lp) & PMT_CTRL_READY_; + } while (--timeout && !reg); + if (timeout == 0) { + PRINTK("%s: smc911x_reset timeout waiting for PM restore\n", dev->name); + return; + } + } + + /* Disable all interrupts */ + spin_lock_irqsave(&lp->lock, flags); + SMC_SET_INT_EN(lp, 0); + spin_unlock_irqrestore(&lp->lock, flags); + + while (resets--) { + SMC_SET_HW_CFG(lp, HW_CFG_SRST_); + timeout=10; + do { + udelay(10); + reg = SMC_GET_HW_CFG(lp); + /* If chip indicates reset timeout then try again */ + if (reg & HW_CFG_SRST_TO_) { + PRINTK("%s: chip reset timeout, retrying...\n", dev->name); + resets++; + break; + } + } while (--timeout && (reg & HW_CFG_SRST_)); + } + if (timeout == 0) { + PRINTK("%s: smc911x_reset timeout waiting for reset\n", dev->name); + return; + } + + /* make sure EEPROM has finished loading before setting GPIO_CFG */ + timeout=1000; + while (--timeout && (SMC_GET_E2P_CMD(lp) & E2P_CMD_EPC_BUSY_)) + udelay(10); + + if (timeout == 0){ + PRINTK("%s: smc911x_reset timeout waiting for EEPROM busy\n", dev->name); + return; + } + + /* Initialize interrupts */ + SMC_SET_INT_EN(lp, 0); + SMC_ACK_INT(lp, -1); + + /* Reset the FIFO level and flow control settings */ + SMC_SET_HW_CFG(lp, (lp->tx_fifo_kb & 0xF) << 16); +//TODO: Figure out what appropriate pause time is + SMC_SET_FLOW(lp, FLOW_FCPT_ | FLOW_FCEN_); + SMC_SET_AFC_CFG(lp, lp->afc_cfg); + + + /* Set to LED outputs */ + SMC_SET_GPIO_CFG(lp, 0x70070000); + + /* + * Deassert IRQ for 1*10us for edge type interrupts + * and drive IRQ pin push-pull + */ + irq_cfg = (1 << 24) | INT_CFG_IRQ_EN_ | INT_CFG_IRQ_TYPE_; +#ifdef SMC_DYNAMIC_BUS_CONFIG + if (lp->cfg.irq_polarity) + irq_cfg |= INT_CFG_IRQ_POL_; +#endif + SMC_SET_IRQ_CFG(lp, irq_cfg); + + /* clear anything saved */ + if (lp->pending_tx_skb != NULL) { + dev_kfree_skb (lp->pending_tx_skb); + lp->pending_tx_skb = NULL; + dev->stats.tx_errors++; + dev->stats.tx_aborted_errors++; + } +} + +/* + * Enable Interrupts, Receive, and Transmit + */ +static void smc911x_enable(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned mask, cfg, cr; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + spin_lock_irqsave(&lp->lock, flags); + + SMC_SET_MAC_ADDR(lp, dev->dev_addr); + + /* Enable TX */ + cfg = SMC_GET_HW_CFG(lp); + cfg &= HW_CFG_TX_FIF_SZ_ | 0xFFF; + cfg |= HW_CFG_SF_; + SMC_SET_HW_CFG(lp, cfg); + SMC_SET_FIFO_TDA(lp, 0xFF); + /* Update TX stats on every 64 packets received or every 1 sec */ + SMC_SET_FIFO_TSL(lp, 64); + SMC_SET_GPT_CFG(lp, GPT_CFG_TIMER_EN_ | 10000); + + SMC_GET_MAC_CR(lp, cr); + cr |= MAC_CR_TXEN_ | MAC_CR_HBDIS_; + SMC_SET_MAC_CR(lp, cr); + SMC_SET_TX_CFG(lp, TX_CFG_TX_ON_); + + /* Add 2 byte padding to start of packets */ + SMC_SET_RX_CFG(lp, (2<<8) & RX_CFG_RXDOFF_); + + /* Turn on receiver and enable RX */ + if (cr & MAC_CR_RXEN_) + DBG(SMC_DEBUG_RX, "%s: Receiver already enabled\n", dev->name); + + SMC_SET_MAC_CR(lp, cr | MAC_CR_RXEN_); + + /* Interrupt on every received packet */ + SMC_SET_FIFO_RSA(lp, 0x01); + SMC_SET_FIFO_RSL(lp, 0x00); + + /* now, enable interrupts */ + mask = INT_EN_TDFA_EN_ | INT_EN_TSFL_EN_ | INT_EN_RSFL_EN_ | + INT_EN_GPT_INT_EN_ | INT_EN_RXDFH_INT_EN_ | INT_EN_RXE_EN_ | + INT_EN_PHY_INT_EN_; + if (IS_REV_A(lp->revision)) + mask|=INT_EN_RDFL_EN_; + else { + mask|=INT_EN_RDFO_EN_; + } + SMC_ENABLE_INT(lp, mask); + + spin_unlock_irqrestore(&lp->lock, flags); +} + +/* + * this puts the device in an inactive state + */ +static void smc911x_shutdown(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned cr; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", CARDNAME, __func__); + + /* Disable IRQ's */ + SMC_SET_INT_EN(lp, 0); + + /* Turn of Rx and TX */ + spin_lock_irqsave(&lp->lock, flags); + SMC_GET_MAC_CR(lp, cr); + cr &= ~(MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_); + SMC_SET_MAC_CR(lp, cr); + SMC_SET_TX_CFG(lp, TX_CFG_STOP_TX_); + spin_unlock_irqrestore(&lp->lock, flags); +} + +static inline void smc911x_drop_pkt(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned int fifo_count, timeout, reg; + + DBG(SMC_DEBUG_FUNC | SMC_DEBUG_RX, "%s: --> %s\n", CARDNAME, __func__); + fifo_count = SMC_GET_RX_FIFO_INF(lp) & 0xFFFF; + if (fifo_count <= 4) { + /* Manually dump the packet data */ + while (fifo_count--) + SMC_GET_RX_FIFO(lp); + } else { + /* Fast forward through the bad packet */ + SMC_SET_RX_DP_CTRL(lp, RX_DP_CTRL_FFWD_BUSY_); + timeout=50; + do { + udelay(10); + reg = SMC_GET_RX_DP_CTRL(lp) & RX_DP_CTRL_FFWD_BUSY_; + } while (--timeout && reg); + if (timeout == 0) { + PRINTK("%s: timeout waiting for RX fast forward\n", dev->name); + } + } +} + +/* + * This is the procedure to handle the receipt of a packet. + * It should be called after checking for packet presence in + * the RX status FIFO. It must be called with the spin lock + * already held. + */ +static inline void smc911x_rcv(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned int pkt_len, status; + struct sk_buff *skb; + unsigned char *data; + + DBG(SMC_DEBUG_FUNC | SMC_DEBUG_RX, "%s: --> %s\n", + dev->name, __func__); + status = SMC_GET_RX_STS_FIFO(lp); + DBG(SMC_DEBUG_RX, "%s: Rx pkt len %d status 0x%08x\n", + dev->name, (status & 0x3fff0000) >> 16, status & 0xc000ffff); + pkt_len = (status & RX_STS_PKT_LEN_) >> 16; + if (status & RX_STS_ES_) { + /* Deal with a bad packet */ + dev->stats.rx_errors++; + if (status & RX_STS_CRC_ERR_) + dev->stats.rx_crc_errors++; + else { + if (status & RX_STS_LEN_ERR_) + dev->stats.rx_length_errors++; + if (status & RX_STS_MCAST_) + dev->stats.multicast++; + } + /* Remove the bad packet data from the RX FIFO */ + smc911x_drop_pkt(dev); + } else { + /* Receive a valid packet */ + /* Alloc a buffer with extra room for DMA alignment */ + skb=dev_alloc_skb(pkt_len+32); + if (unlikely(skb == NULL)) { + PRINTK( "%s: Low memory, rcvd packet dropped.\n", + dev->name); + dev->stats.rx_dropped++; + smc911x_drop_pkt(dev); + return; + } + /* Align IP header to 32 bits + * Note that the device is configured to add a 2 + * byte padding to the packet start, so we really + * want to write to the orignal data pointer */ + data = skb->data; + skb_reserve(skb, 2); + skb_put(skb,pkt_len-4); +#ifdef SMC_USE_DMA + { + unsigned int fifo; + /* Lower the FIFO threshold if possible */ + fifo = SMC_GET_FIFO_INT(lp); + if (fifo & 0xFF) fifo--; + DBG(SMC_DEBUG_RX, "%s: Setting RX stat FIFO threshold to %d\n", + dev->name, fifo & 0xff); + SMC_SET_FIFO_INT(lp, fifo); + /* Setup RX DMA */ + SMC_SET_RX_CFG(lp, RX_CFG_RX_END_ALGN16_ | ((2<<8) & RX_CFG_RXDOFF_)); + lp->rxdma_active = 1; + lp->current_rx_skb = skb; + SMC_PULL_DATA(lp, data, (pkt_len+2+15) & ~15); + /* Packet processing deferred to DMA RX interrupt */ + } +#else + SMC_SET_RX_CFG(lp, RX_CFG_RX_END_ALGN4_ | ((2<<8) & RX_CFG_RXDOFF_)); + SMC_PULL_DATA(lp, data, pkt_len+2+3); + + DBG(SMC_DEBUG_PKTS, "%s: Received packet\n", dev->name); + PRINT_PKT(data, ((pkt_len - 4) <= 64) ? pkt_len - 4 : 64); + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + dev->stats.rx_packets++; + dev->stats.rx_bytes += pkt_len-4; +#endif + } +} + +/* + * This is called to actually send a packet to the chip. + */ +static void smc911x_hardware_send_pkt(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + struct sk_buff *skb; + unsigned int cmdA, cmdB, len; + unsigned char *buf; + + DBG(SMC_DEBUG_FUNC | SMC_DEBUG_TX, "%s: --> %s\n", dev->name, __func__); + BUG_ON(lp->pending_tx_skb == NULL); + + skb = lp->pending_tx_skb; + lp->pending_tx_skb = NULL; + + /* cmdA {25:24] data alignment [20:16] start offset [10:0] buffer length */ + /* cmdB {31:16] pkt tag [10:0] length */ +#ifdef SMC_USE_DMA + /* 16 byte buffer alignment mode */ + buf = (char*)((u32)(skb->data) & ~0xF); + len = (skb->len + 0xF + ((u32)skb->data & 0xF)) & ~0xF; + cmdA = (1<<24) | (((u32)skb->data & 0xF)<<16) | + TX_CMD_A_INT_FIRST_SEG_ | TX_CMD_A_INT_LAST_SEG_ | + skb->len; +#else + buf = (char*)((u32)skb->data & ~0x3); + len = (skb->len + 3 + ((u32)skb->data & 3)) & ~0x3; + cmdA = (((u32)skb->data & 0x3) << 16) | + TX_CMD_A_INT_FIRST_SEG_ | TX_CMD_A_INT_LAST_SEG_ | + skb->len; +#endif + /* tag is packet length so we can use this in stats update later */ + cmdB = (skb->len << 16) | (skb->len & 0x7FF); + + DBG(SMC_DEBUG_TX, "%s: TX PKT LENGTH 0x%04x (%d) BUF 0x%p CMDA 0x%08x CMDB 0x%08x\n", + dev->name, len, len, buf, cmdA, cmdB); + SMC_SET_TX_FIFO(lp, cmdA); + SMC_SET_TX_FIFO(lp, cmdB); + + DBG(SMC_DEBUG_PKTS, "%s: Transmitted packet\n", dev->name); + PRINT_PKT(buf, len <= 64 ? len : 64); + + /* Send pkt via PIO or DMA */ +#ifdef SMC_USE_DMA + lp->current_tx_skb = skb; + SMC_PUSH_DATA(lp, buf, len); + /* DMA complete IRQ will free buffer and set jiffies */ +#else + SMC_PUSH_DATA(lp, buf, len); + dev->trans_start = jiffies; + dev_kfree_skb_irq(skb); +#endif + if (!lp->tx_throttle) { + netif_wake_queue(dev); + } + SMC_ENABLE_INT(lp, INT_EN_TDFA_EN_ | INT_EN_TSFL_EN_); +} + +/* + * Since I am not sure if I will have enough room in the chip's ram + * to store the packet, I call this routine which either sends it + * now, or set the card to generates an interrupt when ready + * for the packet. + */ +static int smc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned int free; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC | SMC_DEBUG_TX, "%s: --> %s\n", + dev->name, __func__); + + spin_lock_irqsave(&lp->lock, flags); + + BUG_ON(lp->pending_tx_skb != NULL); + + free = SMC_GET_TX_FIFO_INF(lp) & TX_FIFO_INF_TDFREE_; + DBG(SMC_DEBUG_TX, "%s: TX free space %d\n", dev->name, free); + + /* Turn off the flow when running out of space in FIFO */ + if (free <= SMC911X_TX_FIFO_LOW_THRESHOLD) { + DBG(SMC_DEBUG_TX, "%s: Disabling data flow due to low FIFO space (%d)\n", + dev->name, free); + /* Reenable when at least 1 packet of size MTU present */ + SMC_SET_FIFO_TDA(lp, (SMC911X_TX_FIFO_LOW_THRESHOLD)/64); + lp->tx_throttle = 1; + netif_stop_queue(dev); + } + + /* Drop packets when we run out of space in TX FIFO + * Account for overhead required for: + * + * Tx command words 8 bytes + * Start offset 15 bytes + * End padding 15 bytes + */ + if (unlikely(free < (skb->len + 8 + 15 + 15))) { + printk("%s: No Tx free space %d < %d\n", + dev->name, free, skb->len); + lp->pending_tx_skb = NULL; + dev->stats.tx_errors++; + dev->stats.tx_dropped++; + spin_unlock_irqrestore(&lp->lock, flags); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + +#ifdef SMC_USE_DMA + { + /* If the DMA is already running then defer this packet Tx until + * the DMA IRQ starts it + */ + if (lp->txdma_active) { + DBG(SMC_DEBUG_TX | SMC_DEBUG_DMA, "%s: Tx DMA running, deferring packet\n", dev->name); + lp->pending_tx_skb = skb; + netif_stop_queue(dev); + spin_unlock_irqrestore(&lp->lock, flags); + return NETDEV_TX_OK; + } else { + DBG(SMC_DEBUG_TX | SMC_DEBUG_DMA, "%s: Activating Tx DMA\n", dev->name); + lp->txdma_active = 1; + } + } +#endif + lp->pending_tx_skb = skb; + smc911x_hardware_send_pkt(dev); + spin_unlock_irqrestore(&lp->lock, flags); + + return NETDEV_TX_OK; +} + +/* + * This handles a TX status interrupt, which is only called when: + * - a TX error occurred, or + * - TX of a packet completed. + */ +static void smc911x_tx(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned int tx_status; + + DBG(SMC_DEBUG_FUNC | SMC_DEBUG_TX, "%s: --> %s\n", + dev->name, __func__); + + /* Collect the TX status */ + while (((SMC_GET_TX_FIFO_INF(lp) & TX_FIFO_INF_TSUSED_) >> 16) != 0) { + DBG(SMC_DEBUG_TX, "%s: Tx stat FIFO used 0x%04x\n", + dev->name, + (SMC_GET_TX_FIFO_INF(lp) & TX_FIFO_INF_TSUSED_) >> 16); + tx_status = SMC_GET_TX_STS_FIFO(lp); + dev->stats.tx_packets++; + dev->stats.tx_bytes+=tx_status>>16; + DBG(SMC_DEBUG_TX, "%s: Tx FIFO tag 0x%04x status 0x%04x\n", + dev->name, (tx_status & 0xffff0000) >> 16, + tx_status & 0x0000ffff); + /* count Tx errors, but ignore lost carrier errors when in + * full-duplex mode */ + if ((tx_status & TX_STS_ES_) && !(lp->ctl_rfduplx && + !(tx_status & 0x00000306))) { + dev->stats.tx_errors++; + } + if (tx_status & TX_STS_MANY_COLL_) { + dev->stats.collisions+=16; + dev->stats.tx_aborted_errors++; + } else { + dev->stats.collisions+=(tx_status & TX_STS_COLL_CNT_) >> 3; + } + /* carrier error only has meaning for half-duplex communication */ + if ((tx_status & (TX_STS_LOC_ | TX_STS_NO_CARR_)) && + !lp->ctl_rfduplx) { + dev->stats.tx_carrier_errors++; + } + if (tx_status & TX_STS_LATE_COLL_) { + dev->stats.collisions++; + dev->stats.tx_aborted_errors++; + } + } +} + + +/*---PHY CONTROL AND CONFIGURATION-----------------------------------------*/ +/* + * Reads a register from the MII Management serial interface + */ + +static int smc911x_phy_read(struct net_device *dev, int phyaddr, int phyreg) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned int phydata; + + SMC_GET_MII(lp, phyreg, phyaddr, phydata); + + DBG(SMC_DEBUG_MISC, "%s: phyaddr=0x%x, phyreg=0x%02x, phydata=0x%04x\n", + __func__, phyaddr, phyreg, phydata); + return phydata; +} + + +/* + * Writes a register to the MII Management serial interface + */ +static void smc911x_phy_write(struct net_device *dev, int phyaddr, int phyreg, + int phydata) +{ + struct smc911x_local *lp = netdev_priv(dev); + + DBG(SMC_DEBUG_MISC, "%s: phyaddr=0x%x, phyreg=0x%x, phydata=0x%x\n", + __func__, phyaddr, phyreg, phydata); + + SMC_SET_MII(lp, phyreg, phyaddr, phydata); +} + +/* + * Finds and reports the PHY address (115 and 117 have external + * PHY interface 118 has internal only + */ +static void smc911x_phy_detect(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + int phyaddr; + unsigned int cfg, id1, id2; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + lp->phy_type = 0; + + /* + * Scan all 32 PHY addresses if necessary, starting at + * PHY#1 to PHY#31, and then PHY#0 last. + */ + switch(lp->version) { + case CHIP_9115: + case CHIP_9117: + case CHIP_9215: + case CHIP_9217: + cfg = SMC_GET_HW_CFG(lp); + if (cfg & HW_CFG_EXT_PHY_DET_) { + cfg &= ~HW_CFG_PHY_CLK_SEL_; + cfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_; + SMC_SET_HW_CFG(lp, cfg); + udelay(10); /* Wait for clocks to stop */ + + cfg |= HW_CFG_EXT_PHY_EN_; + SMC_SET_HW_CFG(lp, cfg); + udelay(10); /* Wait for clocks to stop */ + + cfg &= ~HW_CFG_PHY_CLK_SEL_; + cfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_; + SMC_SET_HW_CFG(lp, cfg); + udelay(10); /* Wait for clocks to stop */ + + cfg |= HW_CFG_SMI_SEL_; + SMC_SET_HW_CFG(lp, cfg); + + for (phyaddr = 1; phyaddr < 32; ++phyaddr) { + + /* Read the PHY identifiers */ + SMC_GET_PHY_ID1(lp, phyaddr & 31, id1); + SMC_GET_PHY_ID2(lp, phyaddr & 31, id2); + + /* Make sure it is a valid identifier */ + if (id1 != 0x0000 && id1 != 0xffff && + id1 != 0x8000 && id2 != 0x0000 && + id2 != 0xffff && id2 != 0x8000) { + /* Save the PHY's address */ + lp->mii.phy_id = phyaddr & 31; + lp->phy_type = id1 << 16 | id2; + break; + } + } + if (phyaddr < 32) + /* Found an external PHY */ + break; + } + default: + /* Internal media only */ + SMC_GET_PHY_ID1(lp, 1, id1); + SMC_GET_PHY_ID2(lp, 1, id2); + /* Save the PHY's address */ + lp->mii.phy_id = 1; + lp->phy_type = id1 << 16 | id2; + } + + DBG(SMC_DEBUG_MISC, "%s: phy_id1=0x%x, phy_id2=0x%x phyaddr=0x%d\n", + dev->name, id1, id2, lp->mii.phy_id); +} + +/* + * Sets the PHY to a configuration as determined by the user. + * Called with spin_lock held. + */ +static int smc911x_phy_fixed(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + int phyaddr = lp->mii.phy_id; + int bmcr; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + /* Enter Link Disable state */ + SMC_GET_PHY_BMCR(lp, phyaddr, bmcr); + bmcr |= BMCR_PDOWN; + SMC_SET_PHY_BMCR(lp, phyaddr, bmcr); + + /* + * Set our fixed capabilities + * Disable auto-negotiation + */ + bmcr &= ~BMCR_ANENABLE; + if (lp->ctl_rfduplx) + bmcr |= BMCR_FULLDPLX; + + if (lp->ctl_rspeed == 100) + bmcr |= BMCR_SPEED100; + + /* Write our capabilities to the phy control register */ + SMC_SET_PHY_BMCR(lp, phyaddr, bmcr); + + /* Re-Configure the Receive/Phy Control register */ + bmcr &= ~BMCR_PDOWN; + SMC_SET_PHY_BMCR(lp, phyaddr, bmcr); + + return 1; +} + +/* + * smc911x_phy_reset - reset the phy + * @dev: net device + * @phy: phy address + * + * Issue a software reset for the specified PHY and + * wait up to 100ms for the reset to complete. We should + * not access the PHY for 50ms after issuing the reset. + * + * The time to wait appears to be dependent on the PHY. + * + */ +static int smc911x_phy_reset(struct net_device *dev, int phy) +{ + struct smc911x_local *lp = netdev_priv(dev); + int timeout; + unsigned long flags; + unsigned int reg; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s()\n", dev->name, __func__); + + spin_lock_irqsave(&lp->lock, flags); + reg = SMC_GET_PMT_CTRL(lp); + reg &= ~0xfffff030; + reg |= PMT_CTRL_PHY_RST_; + SMC_SET_PMT_CTRL(lp, reg); + spin_unlock_irqrestore(&lp->lock, flags); + for (timeout = 2; timeout; timeout--) { + msleep(50); + spin_lock_irqsave(&lp->lock, flags); + reg = SMC_GET_PMT_CTRL(lp); + spin_unlock_irqrestore(&lp->lock, flags); + if (!(reg & PMT_CTRL_PHY_RST_)) { + /* extra delay required because the phy may + * not be completed with its reset + * when PHY_BCR_RESET_ is cleared. 256us + * should suffice, but use 500us to be safe + */ + udelay(500); + break; + } + } + + return reg & PMT_CTRL_PHY_RST_; +} + +/* + * smc911x_phy_powerdown - powerdown phy + * @dev: net device + * @phy: phy address + * + * Power down the specified PHY + */ +static void smc911x_phy_powerdown(struct net_device *dev, int phy) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned int bmcr; + + /* Enter Link Disable state */ + SMC_GET_PHY_BMCR(lp, phy, bmcr); + bmcr |= BMCR_PDOWN; + SMC_SET_PHY_BMCR(lp, phy, bmcr); +} + +/* + * smc911x_phy_check_media - check the media status and adjust BMCR + * @dev: net device + * @init: set true for initialisation + * + * Select duplex mode depending on negotiation state. This + * also updates our carrier state. + */ +static void smc911x_phy_check_media(struct net_device *dev, int init) +{ + struct smc911x_local *lp = netdev_priv(dev); + int phyaddr = lp->mii.phy_id; + unsigned int bmcr, cr; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + if (mii_check_media(&lp->mii, netif_msg_link(lp), init)) { + /* duplex state has changed */ + SMC_GET_PHY_BMCR(lp, phyaddr, bmcr); + SMC_GET_MAC_CR(lp, cr); + if (lp->mii.full_duplex) { + DBG(SMC_DEBUG_MISC, "%s: Configuring for full-duplex mode\n", dev->name); + bmcr |= BMCR_FULLDPLX; + cr |= MAC_CR_RCVOWN_; + } else { + DBG(SMC_DEBUG_MISC, "%s: Configuring for half-duplex mode\n", dev->name); + bmcr &= ~BMCR_FULLDPLX; + cr &= ~MAC_CR_RCVOWN_; + } + SMC_SET_PHY_BMCR(lp, phyaddr, bmcr); + SMC_SET_MAC_CR(lp, cr); + } +} + +/* + * Configures the specified PHY through the MII management interface + * using Autonegotiation. + * Calls smc911x_phy_fixed() if the user has requested a certain config. + * If RPC ANEG bit is set, the media selection is dependent purely on + * the selection by the MII (either in the MII BMCR reg or the result + * of autonegotiation.) If the RPC ANEG bit is cleared, the selection + * is controlled by the RPC SPEED and RPC DPLX bits. + */ +static void smc911x_phy_configure(struct work_struct *work) +{ + struct smc911x_local *lp = container_of(work, struct smc911x_local, + phy_configure); + struct net_device *dev = lp->netdev; + int phyaddr = lp->mii.phy_id; + int my_phy_caps; /* My PHY capabilities */ + int my_ad_caps; /* My Advertised capabilities */ + int status; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s()\n", dev->name, __func__); + + /* + * We should not be called if phy_type is zero. + */ + if (lp->phy_type == 0) + return; + + if (smc911x_phy_reset(dev, phyaddr)) { + printk("%s: PHY reset timed out\n", dev->name); + return; + } + spin_lock_irqsave(&lp->lock, flags); + + /* + * Enable PHY Interrupts (for register 18) + * Interrupts listed here are enabled + */ + SMC_SET_PHY_INT_MASK(lp, phyaddr, PHY_INT_MASK_ENERGY_ON_ | + PHY_INT_MASK_ANEG_COMP_ | PHY_INT_MASK_REMOTE_FAULT_ | + PHY_INT_MASK_LINK_DOWN_); + + /* If the user requested no auto neg, then go set his request */ + if (lp->mii.force_media) { + smc911x_phy_fixed(dev); + goto smc911x_phy_configure_exit; + } + + /* Copy our capabilities from MII_BMSR to MII_ADVERTISE */ + SMC_GET_PHY_BMSR(lp, phyaddr, my_phy_caps); + if (!(my_phy_caps & BMSR_ANEGCAPABLE)) { + printk(KERN_INFO "Auto negotiation NOT supported\n"); + smc911x_phy_fixed(dev); + goto smc911x_phy_configure_exit; + } + + /* CSMA capable w/ both pauses */ + my_ad_caps = ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM; + + if (my_phy_caps & BMSR_100BASE4) + my_ad_caps |= ADVERTISE_100BASE4; + if (my_phy_caps & BMSR_100FULL) + my_ad_caps |= ADVERTISE_100FULL; + if (my_phy_caps & BMSR_100HALF) + my_ad_caps |= ADVERTISE_100HALF; + if (my_phy_caps & BMSR_10FULL) + my_ad_caps |= ADVERTISE_10FULL; + if (my_phy_caps & BMSR_10HALF) + my_ad_caps |= ADVERTISE_10HALF; + + /* Disable capabilities not selected by our user */ + if (lp->ctl_rspeed != 100) + my_ad_caps &= ~(ADVERTISE_100BASE4|ADVERTISE_100FULL|ADVERTISE_100HALF); + + if (!lp->ctl_rfduplx) + my_ad_caps &= ~(ADVERTISE_100FULL|ADVERTISE_10FULL); + + /* Update our Auto-Neg Advertisement Register */ + SMC_SET_PHY_MII_ADV(lp, phyaddr, my_ad_caps); + lp->mii.advertising = my_ad_caps; + + /* + * Read the register back. Without this, it appears that when + * auto-negotiation is restarted, sometimes it isn't ready and + * the link does not come up. + */ + udelay(10); + SMC_GET_PHY_MII_ADV(lp, phyaddr, status); + + DBG(SMC_DEBUG_MISC, "%s: phy caps=0x%04x\n", dev->name, my_phy_caps); + DBG(SMC_DEBUG_MISC, "%s: phy advertised caps=0x%04x\n", dev->name, my_ad_caps); + + /* Restart auto-negotiation process in order to advertise my caps */ + SMC_SET_PHY_BMCR(lp, phyaddr, BMCR_ANENABLE | BMCR_ANRESTART); + + smc911x_phy_check_media(dev, 1); + +smc911x_phy_configure_exit: + spin_unlock_irqrestore(&lp->lock, flags); +} + +/* + * smc911x_phy_interrupt + * + * Purpose: Handle interrupts relating to PHY register 18. This is + * called from the "hard" interrupt handler under our private spinlock. + */ +static void smc911x_phy_interrupt(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + int phyaddr = lp->mii.phy_id; + int status; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + if (lp->phy_type == 0) + return; + + smc911x_phy_check_media(dev, 0); + /* read to clear status bits */ + SMC_GET_PHY_INT_SRC(lp, phyaddr,status); + DBG(SMC_DEBUG_MISC, "%s: PHY interrupt status 0x%04x\n", + dev->name, status & 0xffff); + DBG(SMC_DEBUG_MISC, "%s: AFC_CFG 0x%08x\n", + dev->name, SMC_GET_AFC_CFG(lp)); +} + +/*--- END PHY CONTROL AND CONFIGURATION-------------------------------------*/ + +/* + * This is the main routine of the driver, to handle the device when + * it needs some attention. + */ +static irqreturn_t smc911x_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct smc911x_local *lp = netdev_priv(dev); + unsigned int status, mask, timeout; + unsigned int rx_overrun=0, cr, pkts; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + spin_lock_irqsave(&lp->lock, flags); + + /* Spurious interrupt check */ + if ((SMC_GET_IRQ_CFG(lp) & (INT_CFG_IRQ_INT_ | INT_CFG_IRQ_EN_)) != + (INT_CFG_IRQ_INT_ | INT_CFG_IRQ_EN_)) { + spin_unlock_irqrestore(&lp->lock, flags); + return IRQ_NONE; + } + + mask = SMC_GET_INT_EN(lp); + SMC_SET_INT_EN(lp, 0); + + /* set a timeout value, so I don't stay here forever */ + timeout = 8; + + + do { + status = SMC_GET_INT(lp); + + DBG(SMC_DEBUG_MISC, "%s: INT 0x%08x MASK 0x%08x OUTSIDE MASK 0x%08x\n", + dev->name, status, mask, status & ~mask); + + status &= mask; + if (!status) + break; + + /* Handle SW interrupt condition */ + if (status & INT_STS_SW_INT_) { + SMC_ACK_INT(lp, INT_STS_SW_INT_); + mask &= ~INT_EN_SW_INT_EN_; + } + /* Handle various error conditions */ + if (status & INT_STS_RXE_) { + SMC_ACK_INT(lp, INT_STS_RXE_); + dev->stats.rx_errors++; + } + if (status & INT_STS_RXDFH_INT_) { + SMC_ACK_INT(lp, INT_STS_RXDFH_INT_); + dev->stats.rx_dropped+=SMC_GET_RX_DROP(lp); + } + /* Undocumented interrupt-what is the right thing to do here? */ + if (status & INT_STS_RXDF_INT_) { + SMC_ACK_INT(lp, INT_STS_RXDF_INT_); + } + + /* Rx Data FIFO exceeds set level */ + if (status & INT_STS_RDFL_) { + if (IS_REV_A(lp->revision)) { + rx_overrun=1; + SMC_GET_MAC_CR(lp, cr); + cr &= ~MAC_CR_RXEN_; + SMC_SET_MAC_CR(lp, cr); + DBG(SMC_DEBUG_RX, "%s: RX overrun\n", dev->name); + dev->stats.rx_errors++; + dev->stats.rx_fifo_errors++; + } + SMC_ACK_INT(lp, INT_STS_RDFL_); + } + if (status & INT_STS_RDFO_) { + if (!IS_REV_A(lp->revision)) { + SMC_GET_MAC_CR(lp, cr); + cr &= ~MAC_CR_RXEN_; + SMC_SET_MAC_CR(lp, cr); + rx_overrun=1; + DBG(SMC_DEBUG_RX, "%s: RX overrun\n", dev->name); + dev->stats.rx_errors++; + dev->stats.rx_fifo_errors++; + } + SMC_ACK_INT(lp, INT_STS_RDFO_); + } + /* Handle receive condition */ + if ((status & INT_STS_RSFL_) || rx_overrun) { + unsigned int fifo; + DBG(SMC_DEBUG_RX, "%s: RX irq\n", dev->name); + fifo = SMC_GET_RX_FIFO_INF(lp); + pkts = (fifo & RX_FIFO_INF_RXSUSED_) >> 16; + DBG(SMC_DEBUG_RX, "%s: Rx FIFO pkts %d, bytes %d\n", + dev->name, pkts, fifo & 0xFFFF ); + if (pkts != 0) { +#ifdef SMC_USE_DMA + unsigned int fifo; + if (lp->rxdma_active){ + DBG(SMC_DEBUG_RX | SMC_DEBUG_DMA, + "%s: RX DMA active\n", dev->name); + /* The DMA is already running so up the IRQ threshold */ + fifo = SMC_GET_FIFO_INT(lp) & ~0xFF; + fifo |= pkts & 0xFF; + DBG(SMC_DEBUG_RX, + "%s: Setting RX stat FIFO threshold to %d\n", + dev->name, fifo & 0xff); + SMC_SET_FIFO_INT(lp, fifo); + } else +#endif + smc911x_rcv(dev); + } + SMC_ACK_INT(lp, INT_STS_RSFL_); + } + /* Handle transmit FIFO available */ + if (status & INT_STS_TDFA_) { + DBG(SMC_DEBUG_TX, "%s: TX data FIFO space available irq\n", dev->name); + SMC_SET_FIFO_TDA(lp, 0xFF); + lp->tx_throttle = 0; +#ifdef SMC_USE_DMA + if (!lp->txdma_active) +#endif + netif_wake_queue(dev); + SMC_ACK_INT(lp, INT_STS_TDFA_); + } + /* Handle transmit done condition */ +#if 1 + if (status & (INT_STS_TSFL_ | INT_STS_GPT_INT_)) { + DBG(SMC_DEBUG_TX | SMC_DEBUG_MISC, + "%s: Tx stat FIFO limit (%d) /GPT irq\n", + dev->name, (SMC_GET_FIFO_INT(lp) & 0x00ff0000) >> 16); + smc911x_tx(dev); + SMC_SET_GPT_CFG(lp, GPT_CFG_TIMER_EN_ | 10000); + SMC_ACK_INT(lp, INT_STS_TSFL_); + SMC_ACK_INT(lp, INT_STS_TSFL_ | INT_STS_GPT_INT_); + } +#else + if (status & INT_STS_TSFL_) { + DBG(SMC_DEBUG_TX, "%s: TX status FIFO limit (%d) irq\n", dev->name, ); + smc911x_tx(dev); + SMC_ACK_INT(lp, INT_STS_TSFL_); + } + + if (status & INT_STS_GPT_INT_) { + DBG(SMC_DEBUG_RX, "%s: IRQ_CFG 0x%08x FIFO_INT 0x%08x RX_CFG 0x%08x\n", + dev->name, + SMC_GET_IRQ_CFG(lp), + SMC_GET_FIFO_INT(lp), + SMC_GET_RX_CFG(lp)); + DBG(SMC_DEBUG_RX, "%s: Rx Stat FIFO Used 0x%02x " + "Data FIFO Used 0x%04x Stat FIFO 0x%08x\n", + dev->name, + (SMC_GET_RX_FIFO_INF(lp) & 0x00ff0000) >> 16, + SMC_GET_RX_FIFO_INF(lp) & 0xffff, + SMC_GET_RX_STS_FIFO_PEEK(lp)); + SMC_SET_GPT_CFG(lp, GPT_CFG_TIMER_EN_ | 10000); + SMC_ACK_INT(lp, INT_STS_GPT_INT_); + } +#endif + + /* Handle PHY interrupt condition */ + if (status & INT_STS_PHY_INT_) { + DBG(SMC_DEBUG_MISC, "%s: PHY irq\n", dev->name); + smc911x_phy_interrupt(dev); + SMC_ACK_INT(lp, INT_STS_PHY_INT_); + } + } while (--timeout); + + /* restore mask state */ + SMC_SET_INT_EN(lp, mask); + + DBG(SMC_DEBUG_MISC, "%s: Interrupt done (%d loops)\n", + dev->name, 8-timeout); + + spin_unlock_irqrestore(&lp->lock, flags); + + return IRQ_HANDLED; +} + +#ifdef SMC_USE_DMA +static void +smc911x_tx_dma_irq(int dma, void *data) +{ + struct net_device *dev = (struct net_device *)data; + struct smc911x_local *lp = netdev_priv(dev); + struct sk_buff *skb = lp->current_tx_skb; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + DBG(SMC_DEBUG_TX | SMC_DEBUG_DMA, "%s: TX DMA irq handler\n", dev->name); + /* Clear the DMA interrupt sources */ + SMC_DMA_ACK_IRQ(dev, dma); + BUG_ON(skb == NULL); + dma_unmap_single(NULL, tx_dmabuf, tx_dmalen, DMA_TO_DEVICE); + dev->trans_start = jiffies; + dev_kfree_skb_irq(skb); + lp->current_tx_skb = NULL; + if (lp->pending_tx_skb != NULL) + smc911x_hardware_send_pkt(dev); + else { + DBG(SMC_DEBUG_TX | SMC_DEBUG_DMA, + "%s: No pending Tx packets. DMA disabled\n", dev->name); + spin_lock_irqsave(&lp->lock, flags); + lp->txdma_active = 0; + if (!lp->tx_throttle) { + netif_wake_queue(dev); + } + spin_unlock_irqrestore(&lp->lock, flags); + } + + DBG(SMC_DEBUG_TX | SMC_DEBUG_DMA, + "%s: TX DMA irq completed\n", dev->name); +} +static void +smc911x_rx_dma_irq(int dma, void *data) +{ + struct net_device *dev = (struct net_device *)data; + unsigned long ioaddr = dev->base_addr; + struct smc911x_local *lp = netdev_priv(dev); + struct sk_buff *skb = lp->current_rx_skb; + unsigned long flags; + unsigned int pkts; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + DBG(SMC_DEBUG_RX | SMC_DEBUG_DMA, "%s: RX DMA irq handler\n", dev->name); + /* Clear the DMA interrupt sources */ + SMC_DMA_ACK_IRQ(dev, dma); + dma_unmap_single(NULL, rx_dmabuf, rx_dmalen, DMA_FROM_DEVICE); + BUG_ON(skb == NULL); + lp->current_rx_skb = NULL; + PRINT_PKT(skb->data, skb->len); + skb->protocol = eth_type_trans(skb, dev); + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + netif_rx(skb); + + spin_lock_irqsave(&lp->lock, flags); + pkts = (SMC_GET_RX_FIFO_INF(lp) & RX_FIFO_INF_RXSUSED_) >> 16; + if (pkts != 0) { + smc911x_rcv(dev); + }else { + lp->rxdma_active = 0; + } + spin_unlock_irqrestore(&lp->lock, flags); + DBG(SMC_DEBUG_RX | SMC_DEBUG_DMA, + "%s: RX DMA irq completed. DMA RX FIFO PKTS %d\n", + dev->name, pkts); +} +#endif /* SMC_USE_DMA */ + +#ifdef CONFIG_NET_POLL_CONTROLLER +/* + * Polling receive - used by netconsole and other diagnostic tools + * to allow network i/o with interrupts disabled. + */ +static void smc911x_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + smc911x_interrupt(dev->irq, dev); + enable_irq(dev->irq); +} +#endif + +/* Our watchdog timed out. Called by the networking layer */ +static void smc911x_timeout(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + int status, mask; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + spin_lock_irqsave(&lp->lock, flags); + status = SMC_GET_INT(lp); + mask = SMC_GET_INT_EN(lp); + spin_unlock_irqrestore(&lp->lock, flags); + DBG(SMC_DEBUG_MISC, "%s: INT 0x%02x MASK 0x%02x\n", + dev->name, status, mask); + + /* Dump the current TX FIFO contents and restart */ + mask = SMC_GET_TX_CFG(lp); + SMC_SET_TX_CFG(lp, mask | TX_CFG_TXS_DUMP_ | TX_CFG_TXD_DUMP_); + /* + * Reconfiguring the PHY doesn't seem like a bad idea here, but + * smc911x_phy_configure() calls msleep() which calls schedule_timeout() + * which calls schedule(). Hence we use a work queue. + */ + if (lp->phy_type != 0) + schedule_work(&lp->phy_configure); + + /* We can accept TX packets again */ + dev->trans_start = jiffies; /* prevent tx timeout */ + netif_wake_queue(dev); +} + +/* + * This routine will, depending on the values passed to it, + * either make it accept multicast packets, go into + * promiscuous mode (for TCPDUMP and cousins) or accept + * a select set of multicast packets + */ +static void smc911x_set_multicast_list(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned int multicast_table[2]; + unsigned int mcr, update_multicast = 0; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + spin_lock_irqsave(&lp->lock, flags); + SMC_GET_MAC_CR(lp, mcr); + spin_unlock_irqrestore(&lp->lock, flags); + + if (dev->flags & IFF_PROMISC) { + + DBG(SMC_DEBUG_MISC, "%s: RCR_PRMS\n", dev->name); + mcr |= MAC_CR_PRMS_; + } + /* + * Here, I am setting this to accept all multicast packets. + * I don't need to zero the multicast table, because the flag is + * checked before the table is + */ + else if (dev->flags & IFF_ALLMULTI || netdev_mc_count(dev) > 16) { + DBG(SMC_DEBUG_MISC, "%s: RCR_ALMUL\n", dev->name); + mcr |= MAC_CR_MCPAS_; + } + + /* + * This sets the internal hardware table to filter out unwanted + * multicast packets before they take up memory. + * + * The SMC chip uses a hash table where the high 6 bits of the CRC of + * address are the offset into the table. If that bit is 1, then the + * multicast packet is accepted. Otherwise, it's dropped silently. + * + * To use the 6 bits as an offset into the table, the high 1 bit is + * the number of the 32 bit register, while the low 5 bits are the bit + * within that register. + */ + else if (!netdev_mc_empty(dev)) { + struct netdev_hw_addr *ha; + + /* Set the Hash perfec mode */ + mcr |= MAC_CR_HPFILT_; + + /* start with a table of all zeros: reject all */ + memset(multicast_table, 0, sizeof(multicast_table)); + + netdev_for_each_mc_addr(ha, dev) { + u32 position; + + /* upper 6 bits are used as hash index */ + position = ether_crc(ETH_ALEN, ha->addr)>>26; + + multicast_table[position>>5] |= 1 << (position&0x1f); + } + + /* be sure I get rid of flags I might have set */ + mcr &= ~(MAC_CR_PRMS_ | MAC_CR_MCPAS_); + + /* now, the table can be loaded into the chipset */ + update_multicast = 1; + } else { + DBG(SMC_DEBUG_MISC, "%s: ~(MAC_CR_PRMS_|MAC_CR_MCPAS_)\n", + dev->name); + mcr &= ~(MAC_CR_PRMS_ | MAC_CR_MCPAS_); + + /* + * since I'm disabling all multicast entirely, I need to + * clear the multicast list + */ + memset(multicast_table, 0, sizeof(multicast_table)); + update_multicast = 1; + } + + spin_lock_irqsave(&lp->lock, flags); + SMC_SET_MAC_CR(lp, mcr); + if (update_multicast) { + DBG(SMC_DEBUG_MISC, + "%s: update mcast hash table 0x%08x 0x%08x\n", + dev->name, multicast_table[0], multicast_table[1]); + SMC_SET_HASHL(lp, multicast_table[0]); + SMC_SET_HASHH(lp, multicast_table[1]); + } + spin_unlock_irqrestore(&lp->lock, flags); +} + + +/* + * Open and Initialize the board + * + * Set up everything, reset the card, etc.. + */ +static int +smc911x_open(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + /* + * Check that the address is valid. If its not, refuse + * to bring the device up. The user must specify an + * address using ifconfig eth0 hw ether xx:xx:xx:xx:xx:xx + */ + if (!is_valid_ether_addr(dev->dev_addr)) { + PRINTK("%s: no valid ethernet hw addr\n", __func__); + return -EINVAL; + } + + /* reset the hardware */ + smc911x_reset(dev); + + /* Configure the PHY, initialize the link state */ + smc911x_phy_configure(&lp->phy_configure); + + /* Turn on Tx + Rx */ + smc911x_enable(dev); + + netif_start_queue(dev); + + return 0; +} + +/* + * smc911x_close + * + * this makes the board clean up everything that it can + * and not talk to the outside world. Caused by + * an 'ifconfig ethX down' + */ +static int smc911x_close(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + netif_stop_queue(dev); + netif_carrier_off(dev); + + /* clear everything */ + smc911x_shutdown(dev); + + if (lp->phy_type != 0) { + /* We need to ensure that no calls to + * smc911x_phy_configure are pending. + */ + cancel_work_sync(&lp->phy_configure); + smc911x_phy_powerdown(dev, lp->mii.phy_id); + } + + if (lp->pending_tx_skb) { + dev_kfree_skb(lp->pending_tx_skb); + lp->pending_tx_skb = NULL; + } + + return 0; +} + +/* + * Ethtool support + */ +static int +smc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smc911x_local *lp = netdev_priv(dev); + int ret, status; + unsigned long flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + cmd->maxtxpkt = 1; + cmd->maxrxpkt = 1; + + if (lp->phy_type != 0) { + spin_lock_irqsave(&lp->lock, flags); + ret = mii_ethtool_gset(&lp->mii, cmd); + spin_unlock_irqrestore(&lp->lock, flags); + } else { + cmd->supported = SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_TP | SUPPORTED_AUI; + + if (lp->ctl_rspeed == 10) + ethtool_cmd_speed_set(cmd, SPEED_10); + else if (lp->ctl_rspeed == 100) + ethtool_cmd_speed_set(cmd, SPEED_100); + + cmd->autoneg = AUTONEG_DISABLE; + if (lp->mii.phy_id==1) + cmd->transceiver = XCVR_INTERNAL; + else + cmd->transceiver = XCVR_EXTERNAL; + cmd->port = 0; + SMC_GET_PHY_SPECIAL(lp, lp->mii.phy_id, status); + cmd->duplex = + (status & (PHY_SPECIAL_SPD_10FULL_ | PHY_SPECIAL_SPD_100FULL_)) ? + DUPLEX_FULL : DUPLEX_HALF; + ret = 0; + } + + return ret; +} + +static int +smc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smc911x_local *lp = netdev_priv(dev); + int ret; + unsigned long flags; + + if (lp->phy_type != 0) { + spin_lock_irqsave(&lp->lock, flags); + ret = mii_ethtool_sset(&lp->mii, cmd); + spin_unlock_irqrestore(&lp->lock, flags); + } else { + if (cmd->autoneg != AUTONEG_DISABLE || + cmd->speed != SPEED_10 || + (cmd->duplex != DUPLEX_HALF && cmd->duplex != DUPLEX_FULL) || + (cmd->port != PORT_TP && cmd->port != PORT_AUI)) + return -EINVAL; + + lp->ctl_rfduplx = cmd->duplex == DUPLEX_FULL; + + ret = 0; + } + + return ret; +} + +static void +smc911x_ethtool_getdrvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + strncpy(info->driver, CARDNAME, sizeof(info->driver)); + strncpy(info->version, version, sizeof(info->version)); + strncpy(info->bus_info, dev_name(dev->dev.parent), sizeof(info->bus_info)); +} + +static int smc911x_ethtool_nwayreset(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + int ret = -EINVAL; + unsigned long flags; + + if (lp->phy_type != 0) { + spin_lock_irqsave(&lp->lock, flags); + ret = mii_nway_restart(&lp->mii); + spin_unlock_irqrestore(&lp->lock, flags); + } + + return ret; +} + +static u32 smc911x_ethtool_getmsglevel(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + return lp->msg_enable; +} + +static void smc911x_ethtool_setmsglevel(struct net_device *dev, u32 level) +{ + struct smc911x_local *lp = netdev_priv(dev); + lp->msg_enable = level; +} + +static int smc911x_ethtool_getregslen(struct net_device *dev) +{ + /* System regs + MAC regs + PHY regs */ + return (((E2P_CMD - ID_REV)/4 + 1) + + (WUCSR - MAC_CR)+1 + 32) * sizeof(u32); +} + +static void smc911x_ethtool_getregs(struct net_device *dev, + struct ethtool_regs* regs, void *buf) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned long flags; + u32 reg,i,j=0; + u32 *data = (u32*)buf; + + regs->version = lp->version; + for(i=ID_REV;i<=E2P_CMD;i+=4) { + data[j++] = SMC_inl(lp, i); + } + for(i=MAC_CR;i<=WUCSR;i++) { + spin_lock_irqsave(&lp->lock, flags); + SMC_GET_MAC_CSR(lp, i, reg); + spin_unlock_irqrestore(&lp->lock, flags); + data[j++] = reg; + } + for(i=0;i<=31;i++) { + spin_lock_irqsave(&lp->lock, flags); + SMC_GET_MII(lp, i, lp->mii.phy_id, reg); + spin_unlock_irqrestore(&lp->lock, flags); + data[j++] = reg & 0xFFFF; + } +} + +static int smc911x_ethtool_wait_eeprom_ready(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + unsigned int timeout; + int e2p_cmd; + + e2p_cmd = SMC_GET_E2P_CMD(lp); + for(timeout=10;(e2p_cmd & E2P_CMD_EPC_BUSY_) && timeout; timeout--) { + if (e2p_cmd & E2P_CMD_EPC_TIMEOUT_) { + PRINTK("%s: %s timeout waiting for EEPROM to respond\n", + dev->name, __func__); + return -EFAULT; + } + mdelay(1); + e2p_cmd = SMC_GET_E2P_CMD(lp); + } + if (timeout == 0) { + PRINTK("%s: %s timeout waiting for EEPROM CMD not busy\n", + dev->name, __func__); + return -ETIMEDOUT; + } + return 0; +} + +static inline int smc911x_ethtool_write_eeprom_cmd(struct net_device *dev, + int cmd, int addr) +{ + struct smc911x_local *lp = netdev_priv(dev); + int ret; + + if ((ret = smc911x_ethtool_wait_eeprom_ready(dev))!=0) + return ret; + SMC_SET_E2P_CMD(lp, E2P_CMD_EPC_BUSY_ | + ((cmd) & (0x7<<28)) | + ((addr) & 0xFF)); + return 0; +} + +static inline int smc911x_ethtool_read_eeprom_byte(struct net_device *dev, + u8 *data) +{ + struct smc911x_local *lp = netdev_priv(dev); + int ret; + + if ((ret = smc911x_ethtool_wait_eeprom_ready(dev))!=0) + return ret; + *data = SMC_GET_E2P_DATA(lp); + return 0; +} + +static inline int smc911x_ethtool_write_eeprom_byte(struct net_device *dev, + u8 data) +{ + struct smc911x_local *lp = netdev_priv(dev); + int ret; + + if ((ret = smc911x_ethtool_wait_eeprom_ready(dev))!=0) + return ret; + SMC_SET_E2P_DATA(lp, data); + return 0; +} + +static int smc911x_ethtool_geteeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + u8 eebuf[SMC911X_EEPROM_LEN]; + int i, ret; + + for(i=0;i<SMC911X_EEPROM_LEN;i++) { + if ((ret=smc911x_ethtool_write_eeprom_cmd(dev, E2P_CMD_EPC_CMD_READ_, i ))!=0) + return ret; + if ((ret=smc911x_ethtool_read_eeprom_byte(dev, &eebuf[i]))!=0) + return ret; + } + memcpy(data, eebuf+eeprom->offset, eeprom->len); + return 0; +} + +static int smc911x_ethtool_seteeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + int i, ret; + + /* Enable erase */ + if ((ret=smc911x_ethtool_write_eeprom_cmd(dev, E2P_CMD_EPC_CMD_EWEN_, 0 ))!=0) + return ret; + for(i=eeprom->offset;i<(eeprom->offset+eeprom->len);i++) { + /* erase byte */ + if ((ret=smc911x_ethtool_write_eeprom_cmd(dev, E2P_CMD_EPC_CMD_ERASE_, i ))!=0) + return ret; + /* write byte */ + if ((ret=smc911x_ethtool_write_eeprom_byte(dev, *data))!=0) + return ret; + if ((ret=smc911x_ethtool_write_eeprom_cmd(dev, E2P_CMD_EPC_CMD_WRITE_, i ))!=0) + return ret; + } + return 0; +} + +static int smc911x_ethtool_geteeprom_len(struct net_device *dev) +{ + return SMC911X_EEPROM_LEN; +} + +static const struct ethtool_ops smc911x_ethtool_ops = { + .get_settings = smc911x_ethtool_getsettings, + .set_settings = smc911x_ethtool_setsettings, + .get_drvinfo = smc911x_ethtool_getdrvinfo, + .get_msglevel = smc911x_ethtool_getmsglevel, + .set_msglevel = smc911x_ethtool_setmsglevel, + .nway_reset = smc911x_ethtool_nwayreset, + .get_link = ethtool_op_get_link, + .get_regs_len = smc911x_ethtool_getregslen, + .get_regs = smc911x_ethtool_getregs, + .get_eeprom_len = smc911x_ethtool_geteeprom_len, + .get_eeprom = smc911x_ethtool_geteeprom, + .set_eeprom = smc911x_ethtool_seteeprom, +}; + +/* + * smc911x_findirq + * + * This routine has a simple purpose -- make the SMC chip generate an + * interrupt, so an auto-detect routine can detect it, and find the IRQ, + */ +static int __devinit smc911x_findirq(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + int timeout = 20; + unsigned long cookie; + + DBG(SMC_DEBUG_FUNC, "--> %s\n", __func__); + + cookie = probe_irq_on(); + + /* + * Force a SW interrupt + */ + + SMC_SET_INT_EN(lp, INT_EN_SW_INT_EN_); + + /* + * Wait until positive that the interrupt has been generated + */ + do { + int int_status; + udelay(10); + int_status = SMC_GET_INT_EN(lp); + if (int_status & INT_EN_SW_INT_EN_) + break; /* got the interrupt */ + } while (--timeout); + + /* + * there is really nothing that I can do here if timeout fails, + * as autoirq_report will return a 0 anyway, which is what I + * want in this case. Plus, the clean up is needed in both + * cases. + */ + + /* and disable all interrupts again */ + SMC_SET_INT_EN(lp, 0); + + /* and return what I found */ + return probe_irq_off(cookie); +} + +static const struct net_device_ops smc911x_netdev_ops = { + .ndo_open = smc911x_open, + .ndo_stop = smc911x_close, + .ndo_start_xmit = smc911x_hard_start_xmit, + .ndo_tx_timeout = smc911x_timeout, + .ndo_set_multicast_list = smc911x_set_multicast_list, + .ndo_change_mtu = eth_change_mtu, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = eth_mac_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = smc911x_poll_controller, +#endif +}; + +/* + * Function: smc911x_probe(unsigned long ioaddr) + * + * Purpose: + * Tests to see if a given ioaddr points to an SMC911x chip. + * Returns a 0 on success + * + * Algorithm: + * (1) see if the endian word is OK + * (1) see if I recognize the chip ID in the appropriate register + * + * Here I do typical initialization tasks. + * + * o Initialize the structure if needed + * o print out my vanity message if not done so already + * o print out what type of hardware is detected + * o print out the ethernet address + * o find the IRQ + * o set up my private data + * o configure the dev structure with my subroutines + * o actually GRAB the irq. + * o GRAB the region + */ +static int __devinit smc911x_probe(struct net_device *dev) +{ + struct smc911x_local *lp = netdev_priv(dev); + int i, retval; + unsigned int val, chip_id, revision; + const char *version_string; + unsigned long irq_flags; + + DBG(SMC_DEBUG_FUNC, "%s: --> %s\n", dev->name, __func__); + + /* First, see if the endian word is recognized */ + val = SMC_GET_BYTE_TEST(lp); + DBG(SMC_DEBUG_MISC, "%s: endian probe returned 0x%04x\n", CARDNAME, val); + if (val != 0x87654321) { + printk(KERN_ERR "Invalid chip endian 0x%08x\n",val); + retval = -ENODEV; + goto err_out; + } + + /* + * check if the revision register is something that I + * recognize. These might need to be added to later, + * as future revisions could be added. + */ + chip_id = SMC_GET_PN(lp); + DBG(SMC_DEBUG_MISC, "%s: id probe returned 0x%04x\n", CARDNAME, chip_id); + for(i=0;chip_ids[i].id != 0; i++) { + if (chip_ids[i].id == chip_id) break; + } + if (!chip_ids[i].id) { + printk(KERN_ERR "Unknown chip ID %04x\n", chip_id); + retval = -ENODEV; + goto err_out; + } + version_string = chip_ids[i].name; + + revision = SMC_GET_REV(lp); + DBG(SMC_DEBUG_MISC, "%s: revision = 0x%04x\n", CARDNAME, revision); + + /* At this point I'll assume that the chip is an SMC911x. */ + DBG(SMC_DEBUG_MISC, "%s: Found a %s\n", CARDNAME, chip_ids[i].name); + + /* Validate the TX FIFO size requested */ + if ((tx_fifo_kb < 2) || (tx_fifo_kb > 14)) { + printk(KERN_ERR "Invalid TX FIFO size requested %d\n", tx_fifo_kb); + retval = -EINVAL; + goto err_out; + } + + /* fill in some of the fields */ + lp->version = chip_ids[i].id; + lp->revision = revision; + lp->tx_fifo_kb = tx_fifo_kb; + /* Reverse calculate the RX FIFO size from the TX */ + lp->tx_fifo_size=(lp->tx_fifo_kb<<10) - 512; + lp->rx_fifo_size= ((0x4000 - 512 - lp->tx_fifo_size) / 16) * 15; + + /* Set the automatic flow control values */ + switch(lp->tx_fifo_kb) { + /* + * AFC_HI is about ((Rx Data Fifo Size)*2/3)/64 + * AFC_LO is AFC_HI/2 + * BACK_DUR is about 5uS*(AFC_LO) rounded down + */ + case 2:/* 13440 Rx Data Fifo Size */ + lp->afc_cfg=0x008C46AF;break; + case 3:/* 12480 Rx Data Fifo Size */ + lp->afc_cfg=0x0082419F;break; + case 4:/* 11520 Rx Data Fifo Size */ + lp->afc_cfg=0x00783C9F;break; + case 5:/* 10560 Rx Data Fifo Size */ + lp->afc_cfg=0x006E374F;break; + case 6:/* 9600 Rx Data Fifo Size */ + lp->afc_cfg=0x0064328F;break; + case 7:/* 8640 Rx Data Fifo Size */ + lp->afc_cfg=0x005A2D7F;break; + case 8:/* 7680 Rx Data Fifo Size */ + lp->afc_cfg=0x0050287F;break; + case 9:/* 6720 Rx Data Fifo Size */ + lp->afc_cfg=0x0046236F;break; + case 10:/* 5760 Rx Data Fifo Size */ + lp->afc_cfg=0x003C1E6F;break; + case 11:/* 4800 Rx Data Fifo Size */ + lp->afc_cfg=0x0032195F;break; + /* + * AFC_HI is ~1520 bytes less than RX Data Fifo Size + * AFC_LO is AFC_HI/2 + * BACK_DUR is about 5uS*(AFC_LO) rounded down + */ + case 12:/* 3840 Rx Data Fifo Size */ + lp->afc_cfg=0x0024124F;break; + case 13:/* 2880 Rx Data Fifo Size */ + lp->afc_cfg=0x0015073F;break; + case 14:/* 1920 Rx Data Fifo Size */ + lp->afc_cfg=0x0006032F;break; + default: + PRINTK("%s: ERROR -- no AFC_CFG setting found", + dev->name); + break; + } + + DBG(SMC_DEBUG_MISC | SMC_DEBUG_TX | SMC_DEBUG_RX, + "%s: tx_fifo %d rx_fifo %d afc_cfg 0x%08x\n", CARDNAME, + lp->tx_fifo_size, lp->rx_fifo_size, lp->afc_cfg); + + spin_lock_init(&lp->lock); + + /* Get the MAC address */ + SMC_GET_MAC_ADDR(lp, dev->dev_addr); + + /* now, reset the chip, and put it into a known state */ + smc911x_reset(dev); + + /* + * If dev->irq is 0, then the device has to be banged on to see + * what the IRQ is. + * + * Specifying an IRQ is done with the assumption that the user knows + * what (s)he is doing. No checking is done!!!! + */ + if (dev->irq < 1) { + int trials; + + trials = 3; + while (trials--) { + dev->irq = smc911x_findirq(dev); + if (dev->irq) + break; + /* kick the card and try again */ + smc911x_reset(dev); + } + } + if (dev->irq == 0) { + printk("%s: Couldn't autodetect your IRQ. Use irq=xx.\n", + dev->name); + retval = -ENODEV; + goto err_out; + } + dev->irq = irq_canonicalize(dev->irq); + + /* Fill in the fields of the device structure with ethernet values. */ + ether_setup(dev); + + dev->netdev_ops = &smc911x_netdev_ops; + dev->watchdog_timeo = msecs_to_jiffies(watchdog); + dev->ethtool_ops = &smc911x_ethtool_ops; + + INIT_WORK(&lp->phy_configure, smc911x_phy_configure); + lp->mii.phy_id_mask = 0x1f; + lp->mii.reg_num_mask = 0x1f; + lp->mii.force_media = 0; + lp->mii.full_duplex = 0; + lp->mii.dev = dev; + lp->mii.mdio_read = smc911x_phy_read; + lp->mii.mdio_write = smc911x_phy_write; + + /* + * Locate the phy, if any. + */ + smc911x_phy_detect(dev); + + /* Set default parameters */ + lp->msg_enable = NETIF_MSG_LINK; + lp->ctl_rfduplx = 1; + lp->ctl_rspeed = 100; + +#ifdef SMC_DYNAMIC_BUS_CONFIG + irq_flags = lp->cfg.irq_flags; +#else + irq_flags = IRQF_SHARED | SMC_IRQ_SENSE; +#endif + + /* Grab the IRQ */ + retval = request_irq(dev->irq, smc911x_interrupt, + irq_flags, dev->name, dev); + if (retval) + goto err_out; + +#ifdef SMC_USE_DMA + lp->rxdma = SMC_DMA_REQUEST(dev, smc911x_rx_dma_irq); + lp->txdma = SMC_DMA_REQUEST(dev, smc911x_tx_dma_irq); + lp->rxdma_active = 0; + lp->txdma_active = 0; + dev->dma = lp->rxdma; +#endif + + retval = register_netdev(dev); + if (retval == 0) { + /* now, print out the card info, in a short format.. */ + printk("%s: %s (rev %d) at %#lx IRQ %d", + dev->name, version_string, lp->revision, + dev->base_addr, dev->irq); + +#ifdef SMC_USE_DMA + if (lp->rxdma != -1) + printk(" RXDMA %d ", lp->rxdma); + + if (lp->txdma != -1) + printk("TXDMA %d", lp->txdma); +#endif + printk("\n"); + if (!is_valid_ether_addr(dev->dev_addr)) { + printk("%s: Invalid ethernet MAC address. Please " + "set using ifconfig\n", dev->name); + } else { + /* Print the Ethernet address */ + printk("%s: Ethernet addr: %pM\n", + dev->name, dev->dev_addr); + } + + if (lp->phy_type == 0) { + PRINTK("%s: No PHY found\n", dev->name); + } else if ((lp->phy_type & ~0xff) == LAN911X_INTERNAL_PHY_ID) { + PRINTK("%s: LAN911x Internal PHY\n", dev->name); + } else { + PRINTK("%s: External PHY 0x%08x\n", dev->name, lp->phy_type); + } + } + +err_out: +#ifdef SMC_USE_DMA + if (retval) { + if (lp->rxdma != -1) { + SMC_DMA_FREE(dev, lp->rxdma); + } + if (lp->txdma != -1) { + SMC_DMA_FREE(dev, lp->txdma); + } + } +#endif + return retval; +} + +/* + * smc911x_init(void) + * + * Output: + * 0 --> there is a device + * anything else, error + */ +static int __devinit smc911x_drv_probe(struct platform_device *pdev) +{ + struct net_device *ndev; + struct resource *res; + struct smc911x_local *lp; + unsigned int *addr; + int ret; + + DBG(SMC_DEBUG_FUNC, "--> %s\n", __func__); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto out; + } + + /* + * Request the regions. + */ + if (!request_mem_region(res->start, SMC911X_IO_EXTENT, CARDNAME)) { + ret = -EBUSY; + goto out; + } + + ndev = alloc_etherdev(sizeof(struct smc911x_local)); + if (!ndev) { + printk("%s: could not allocate device.\n", CARDNAME); + ret = -ENOMEM; + goto release_1; + } + SET_NETDEV_DEV(ndev, &pdev->dev); + + ndev->dma = (unsigned char)-1; + ndev->irq = platform_get_irq(pdev, 0); + lp = netdev_priv(ndev); + lp->netdev = ndev; +#ifdef SMC_DYNAMIC_BUS_CONFIG + { + struct smc911x_platdata *pd = pdev->dev.platform_data; + if (!pd) { + ret = -EINVAL; + goto release_both; + } + memcpy(&lp->cfg, pd, sizeof(lp->cfg)); + } +#endif + + addr = ioremap(res->start, SMC911X_IO_EXTENT); + if (!addr) { + ret = -ENOMEM; + goto release_both; + } + + platform_set_drvdata(pdev, ndev); + lp->base = addr; + ndev->base_addr = res->start; + ret = smc911x_probe(ndev); + if (ret != 0) { + platform_set_drvdata(pdev, NULL); + iounmap(addr); +release_both: + free_netdev(ndev); +release_1: + release_mem_region(res->start, SMC911X_IO_EXTENT); +out: + printk("%s: not found (%d).\n", CARDNAME, ret); + } +#ifdef SMC_USE_DMA + else { + lp->physaddr = res->start; + lp->dev = &pdev->dev; + } +#endif + + return ret; +} + +static int __devexit smc911x_drv_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct smc911x_local *lp = netdev_priv(ndev); + struct resource *res; + + DBG(SMC_DEBUG_FUNC, "--> %s\n", __func__); + platform_set_drvdata(pdev, NULL); + + unregister_netdev(ndev); + + free_irq(ndev->irq, ndev); + +#ifdef SMC_USE_DMA + { + if (lp->rxdma != -1) { + SMC_DMA_FREE(dev, lp->rxdma); + } + if (lp->txdma != -1) { + SMC_DMA_FREE(dev, lp->txdma); + } + } +#endif + iounmap(lp->base); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, SMC911X_IO_EXTENT); + + free_netdev(ndev); + return 0; +} + +static int smc911x_drv_suspend(struct platform_device *dev, pm_message_t state) +{ + struct net_device *ndev = platform_get_drvdata(dev); + struct smc911x_local *lp = netdev_priv(ndev); + + DBG(SMC_DEBUG_FUNC, "--> %s\n", __func__); + if (ndev) { + if (netif_running(ndev)) { + netif_device_detach(ndev); + smc911x_shutdown(ndev); +#if POWER_DOWN + /* Set D2 - Energy detect only setting */ + SMC_SET_PMT_CTRL(lp, 2<<12); +#endif + } + } + return 0; +} + +static int smc911x_drv_resume(struct platform_device *dev) +{ + struct net_device *ndev = platform_get_drvdata(dev); + + DBG(SMC_DEBUG_FUNC, "--> %s\n", __func__); + if (ndev) { + struct smc911x_local *lp = netdev_priv(ndev); + + if (netif_running(ndev)) { + smc911x_reset(ndev); + if (lp->phy_type != 0) + smc911x_phy_configure(&lp->phy_configure); + smc911x_enable(ndev); + netif_device_attach(ndev); + } + } + return 0; +} + +static struct platform_driver smc911x_driver = { + .probe = smc911x_drv_probe, + .remove = __devexit_p(smc911x_drv_remove), + .suspend = smc911x_drv_suspend, + .resume = smc911x_drv_resume, + .driver = { + .name = CARDNAME, + .owner = THIS_MODULE, + }, +}; + +static int __init smc911x_init(void) +{ + return platform_driver_register(&smc911x_driver); +} + +static void __exit smc911x_cleanup(void) +{ + platform_driver_unregister(&smc911x_driver); +} + +module_init(smc911x_init); +module_exit(smc911x_cleanup); diff --git a/drivers/net/ethernet/smsc/smc911x.h b/drivers/net/ethernet/smsc/smc911x.h new file mode 100644 index 000000000000..3269292efecc --- /dev/null +++ b/drivers/net/ethernet/smsc/smc911x.h @@ -0,0 +1,924 @@ +/*------------------------------------------------------------------------ + . smc911x.h - macros for SMSC's LAN911{5,6,7,8} single-chip Ethernet device. + . + . Copyright (C) 2005 Sensoria Corp. + . Derived from the unified SMC91x driver by Nicolas Pitre + . + . This program is free software; you can redistribute it and/or modify + . it under the terms of the GNU General Public License as published by + . the Free Software Foundation; either version 2 of the License, or + . (at your option) any later version. + . + . This program is distributed in the hope that it will be useful, + . but WITHOUT ANY WARRANTY; without even the implied warranty of + . MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + . GNU General Public License for more details. + . + . You should have received a copy of the GNU General Public License + . along with this program; if not, write to the Free Software + . Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + . + . Information contained in this file was obtained from the LAN9118 + . manual from SMC. To get a copy, if you really want one, you can find + . information under www.smsc.com. + . + . Authors + . Dustin McIntire <dustin@sensoria.com> + . + ---------------------------------------------------------------------------*/ +#ifndef _SMC911X_H_ +#define _SMC911X_H_ + +#include <linux/smc911x.h> +/* + * Use the DMA feature on PXA chips + */ +#ifdef CONFIG_ARCH_PXA + #define SMC_USE_PXA_DMA 1 + #define SMC_USE_16BIT 0 + #define SMC_USE_32BIT 1 + #define SMC_IRQ_SENSE IRQF_TRIGGER_FALLING +#elif defined(CONFIG_SH_MAGIC_PANEL_R2) + #define SMC_USE_16BIT 0 + #define SMC_USE_32BIT 1 + #define SMC_IRQ_SENSE IRQF_TRIGGER_LOW +#elif defined(CONFIG_ARCH_OMAP3) + #define SMC_USE_16BIT 0 + #define SMC_USE_32BIT 1 + #define SMC_IRQ_SENSE IRQF_TRIGGER_LOW + #define SMC_MEM_RESERVED 1 +#elif defined(CONFIG_ARCH_OMAP2) + #define SMC_USE_16BIT 0 + #define SMC_USE_32BIT 1 + #define SMC_IRQ_SENSE IRQF_TRIGGER_LOW + #define SMC_MEM_RESERVED 1 +#else +/* + * Default configuration + */ + +#define SMC_DYNAMIC_BUS_CONFIG +#endif + +#ifdef SMC_USE_PXA_DMA +#define SMC_USE_DMA +#endif + +/* store this information for the driver.. */ +struct smc911x_local { + /* + * If I have to wait until the DMA is finished and ready to reload a + * packet, I will store the skbuff here. Then, the DMA will send it + * out and free it. + */ + struct sk_buff *pending_tx_skb; + + /* version/revision of the SMC911x chip */ + u16 version; + u16 revision; + + /* FIFO sizes */ + int tx_fifo_kb; + int tx_fifo_size; + int rx_fifo_size; + int afc_cfg; + + /* Contains the current active receive/phy mode */ + int ctl_rfduplx; + int ctl_rspeed; + + u32 msg_enable; + u32 phy_type; + struct mii_if_info mii; + + /* work queue */ + struct work_struct phy_configure; + + int tx_throttle; + spinlock_t lock; + + struct net_device *netdev; + +#ifdef SMC_USE_DMA + /* DMA needs the physical address of the chip */ + u_long physaddr; + int rxdma; + int txdma; + int rxdma_active; + int txdma_active; + struct sk_buff *current_rx_skb; + struct sk_buff *current_tx_skb; + struct device *dev; +#endif + void __iomem *base; +#ifdef SMC_DYNAMIC_BUS_CONFIG + struct smc911x_platdata cfg; +#endif +}; + +/* + * Define the bus width specific IO macros + */ + +#ifdef SMC_DYNAMIC_BUS_CONFIG +static inline unsigned int SMC_inl(struct smc911x_local *lp, int reg) +{ + void __iomem *ioaddr = lp->base + reg; + + if (lp->cfg.flags & SMC911X_USE_32BIT) + return readl(ioaddr); + + if (lp->cfg.flags & SMC911X_USE_16BIT) + return readw(ioaddr) | (readw(ioaddr + 2) << 16); + + BUG(); +} + +static inline void SMC_outl(unsigned int value, struct smc911x_local *lp, + int reg) +{ + void __iomem *ioaddr = lp->base + reg; + + if (lp->cfg.flags & SMC911X_USE_32BIT) { + writel(value, ioaddr); + return; + } + + if (lp->cfg.flags & SMC911X_USE_16BIT) { + writew(value & 0xffff, ioaddr); + writew(value >> 16, ioaddr + 2); + return; + } + + BUG(); +} + +static inline void SMC_insl(struct smc911x_local *lp, int reg, + void *addr, unsigned int count) +{ + void __iomem *ioaddr = lp->base + reg; + + if (lp->cfg.flags & SMC911X_USE_32BIT) { + readsl(ioaddr, addr, count); + return; + } + + if (lp->cfg.flags & SMC911X_USE_16BIT) { + readsw(ioaddr, addr, count * 2); + return; + } + + BUG(); +} + +static inline void SMC_outsl(struct smc911x_local *lp, int reg, + void *addr, unsigned int count) +{ + void __iomem *ioaddr = lp->base + reg; + + if (lp->cfg.flags & SMC911X_USE_32BIT) { + writesl(ioaddr, addr, count); + return; + } + + if (lp->cfg.flags & SMC911X_USE_16BIT) { + writesw(ioaddr, addr, count * 2); + return; + } + + BUG(); +} +#else +#if SMC_USE_16BIT +#define SMC_inl(lp, r) ((readw((lp)->base + (r)) & 0xFFFF) + (readw((lp)->base + (r) + 2) << 16)) +#define SMC_outl(v, lp, r) \ + do{ \ + writew(v & 0xFFFF, (lp)->base + (r)); \ + writew(v >> 16, (lp)->base + (r) + 2); \ + } while (0) +#define SMC_insl(lp, r, p, l) readsw((short*)((lp)->base + (r)), p, l*2) +#define SMC_outsl(lp, r, p, l) writesw((short*)((lp)->base + (r)), p, l*2) + +#elif SMC_USE_32BIT +#define SMC_inl(lp, r) readl((lp)->base + (r)) +#define SMC_outl(v, lp, r) writel(v, (lp)->base + (r)) +#define SMC_insl(lp, r, p, l) readsl((int*)((lp)->base + (r)), p, l) +#define SMC_outsl(lp, r, p, l) writesl((int*)((lp)->base + (r)), p, l) + +#endif /* SMC_USE_16BIT */ +#endif /* SMC_DYNAMIC_BUS_CONFIG */ + + +#ifdef SMC_USE_PXA_DMA + +#include <mach/dma.h> + +/* + * Define the request and free functions + * These are unfortunately architecture specific as no generic allocation + * mechanism exits + */ +#define SMC_DMA_REQUEST(dev, handler) \ + pxa_request_dma(dev->name, DMA_PRIO_LOW, handler, dev) + +#define SMC_DMA_FREE(dev, dma) \ + pxa_free_dma(dma) + +#define SMC_DMA_ACK_IRQ(dev, dma) \ +{ \ + if (DCSR(dma) & DCSR_BUSERR) { \ + printk("%s: DMA %d bus error!\n", dev->name, dma); \ + } \ + DCSR(dma) = DCSR_STARTINTR|DCSR_ENDINTR|DCSR_BUSERR; \ +} + +/* + * Use a DMA for RX and TX packets. + */ +#include <linux/dma-mapping.h> + +static dma_addr_t rx_dmabuf, tx_dmabuf; +static int rx_dmalen, tx_dmalen; + +#ifdef SMC_insl +#undef SMC_insl +#define SMC_insl(lp, r, p, l) \ + smc_pxa_dma_insl(lp, lp->physaddr, r, lp->rxdma, p, l) + +static inline void +smc_pxa_dma_insl(struct smc911x_local *lp, u_long physaddr, + int reg, int dma, u_char *buf, int len) +{ + /* 64 bit alignment is required for memory to memory DMA */ + if ((long)buf & 4) { + *((u32 *)buf) = SMC_inl(lp, reg); + buf += 4; + len--; + } + + len *= 4; + rx_dmabuf = dma_map_single(lp->dev, buf, len, DMA_FROM_DEVICE); + rx_dmalen = len; + DCSR(dma) = DCSR_NODESC; + DTADR(dma) = rx_dmabuf; + DSADR(dma) = physaddr + reg; + DCMD(dma) = (DCMD_INCTRGADDR | DCMD_BURST32 | + DCMD_WIDTH4 | DCMD_ENDIRQEN | (DCMD_LENGTH & rx_dmalen)); + DCSR(dma) = DCSR_NODESC | DCSR_RUN; +} +#endif + +#ifdef SMC_outsl +#undef SMC_outsl +#define SMC_outsl(lp, r, p, l) \ + smc_pxa_dma_outsl(lp, lp->physaddr, r, lp->txdma, p, l) + +static inline void +smc_pxa_dma_outsl(struct smc911x_local *lp, u_long physaddr, + int reg, int dma, u_char *buf, int len) +{ + /* 64 bit alignment is required for memory to memory DMA */ + if ((long)buf & 4) { + SMC_outl(*((u32 *)buf), lp, reg); + buf += 4; + len--; + } + + len *= 4; + tx_dmabuf = dma_map_single(lp->dev, buf, len, DMA_TO_DEVICE); + tx_dmalen = len; + DCSR(dma) = DCSR_NODESC; + DSADR(dma) = tx_dmabuf; + DTADR(dma) = physaddr + reg; + DCMD(dma) = (DCMD_INCSRCADDR | DCMD_BURST32 | + DCMD_WIDTH4 | DCMD_ENDIRQEN | (DCMD_LENGTH & tx_dmalen)); + DCSR(dma) = DCSR_NODESC | DCSR_RUN; +} +#endif +#endif /* SMC_USE_PXA_DMA */ + + +/* Chip Parameters and Register Definitions */ + +#define SMC911X_TX_FIFO_LOW_THRESHOLD (1536*2) + +#define SMC911X_IO_EXTENT 0x100 + +#define SMC911X_EEPROM_LEN 7 + +/* Below are the register offsets and bit definitions + * of the Lan911x memory space + */ +#define RX_DATA_FIFO (0x00) + +#define TX_DATA_FIFO (0x20) +#define TX_CMD_A_INT_ON_COMP_ (0x80000000) +#define TX_CMD_A_INT_BUF_END_ALGN_ (0x03000000) +#define TX_CMD_A_INT_4_BYTE_ALGN_ (0x00000000) +#define TX_CMD_A_INT_16_BYTE_ALGN_ (0x01000000) +#define TX_CMD_A_INT_32_BYTE_ALGN_ (0x02000000) +#define TX_CMD_A_INT_DATA_OFFSET_ (0x001F0000) +#define TX_CMD_A_INT_FIRST_SEG_ (0x00002000) +#define TX_CMD_A_INT_LAST_SEG_ (0x00001000) +#define TX_CMD_A_BUF_SIZE_ (0x000007FF) +#define TX_CMD_B_PKT_TAG_ (0xFFFF0000) +#define TX_CMD_B_ADD_CRC_DISABLE_ (0x00002000) +#define TX_CMD_B_DISABLE_PADDING_ (0x00001000) +#define TX_CMD_B_PKT_BYTE_LENGTH_ (0x000007FF) + +#define RX_STATUS_FIFO (0x40) +#define RX_STS_PKT_LEN_ (0x3FFF0000) +#define RX_STS_ES_ (0x00008000) +#define RX_STS_BCST_ (0x00002000) +#define RX_STS_LEN_ERR_ (0x00001000) +#define RX_STS_RUNT_ERR_ (0x00000800) +#define RX_STS_MCAST_ (0x00000400) +#define RX_STS_TOO_LONG_ (0x00000080) +#define RX_STS_COLL_ (0x00000040) +#define RX_STS_ETH_TYPE_ (0x00000020) +#define RX_STS_WDOG_TMT_ (0x00000010) +#define RX_STS_MII_ERR_ (0x00000008) +#define RX_STS_DRIBBLING_ (0x00000004) +#define RX_STS_CRC_ERR_ (0x00000002) +#define RX_STATUS_FIFO_PEEK (0x44) +#define TX_STATUS_FIFO (0x48) +#define TX_STS_TAG_ (0xFFFF0000) +#define TX_STS_ES_ (0x00008000) +#define TX_STS_LOC_ (0x00000800) +#define TX_STS_NO_CARR_ (0x00000400) +#define TX_STS_LATE_COLL_ (0x00000200) +#define TX_STS_MANY_COLL_ (0x00000100) +#define TX_STS_COLL_CNT_ (0x00000078) +#define TX_STS_MANY_DEFER_ (0x00000004) +#define TX_STS_UNDERRUN_ (0x00000002) +#define TX_STS_DEFERRED_ (0x00000001) +#define TX_STATUS_FIFO_PEEK (0x4C) +#define ID_REV (0x50) +#define ID_REV_CHIP_ID_ (0xFFFF0000) /* RO */ +#define ID_REV_REV_ID_ (0x0000FFFF) /* RO */ + +#define INT_CFG (0x54) +#define INT_CFG_INT_DEAS_ (0xFF000000) /* R/W */ +#define INT_CFG_INT_DEAS_CLR_ (0x00004000) +#define INT_CFG_INT_DEAS_STS_ (0x00002000) +#define INT_CFG_IRQ_INT_ (0x00001000) /* RO */ +#define INT_CFG_IRQ_EN_ (0x00000100) /* R/W */ +#define INT_CFG_IRQ_POL_ (0x00000010) /* R/W Not Affected by SW Reset */ +#define INT_CFG_IRQ_TYPE_ (0x00000001) /* R/W Not Affected by SW Reset */ + +#define INT_STS (0x58) +#define INT_STS_SW_INT_ (0x80000000) /* R/WC */ +#define INT_STS_TXSTOP_INT_ (0x02000000) /* R/WC */ +#define INT_STS_RXSTOP_INT_ (0x01000000) /* R/WC */ +#define INT_STS_RXDFH_INT_ (0x00800000) /* R/WC */ +#define INT_STS_RXDF_INT_ (0x00400000) /* R/WC */ +#define INT_STS_TX_IOC_ (0x00200000) /* R/WC */ +#define INT_STS_RXD_INT_ (0x00100000) /* R/WC */ +#define INT_STS_GPT_INT_ (0x00080000) /* R/WC */ +#define INT_STS_PHY_INT_ (0x00040000) /* RO */ +#define INT_STS_PME_INT_ (0x00020000) /* R/WC */ +#define INT_STS_TXSO_ (0x00010000) /* R/WC */ +#define INT_STS_RWT_ (0x00008000) /* R/WC */ +#define INT_STS_RXE_ (0x00004000) /* R/WC */ +#define INT_STS_TXE_ (0x00002000) /* R/WC */ +//#define INT_STS_ERX_ (0x00001000) /* R/WC */ +#define INT_STS_TDFU_ (0x00000800) /* R/WC */ +#define INT_STS_TDFO_ (0x00000400) /* R/WC */ +#define INT_STS_TDFA_ (0x00000200) /* R/WC */ +#define INT_STS_TSFF_ (0x00000100) /* R/WC */ +#define INT_STS_TSFL_ (0x00000080) /* R/WC */ +//#define INT_STS_RXDF_ (0x00000040) /* R/WC */ +#define INT_STS_RDFO_ (0x00000040) /* R/WC */ +#define INT_STS_RDFL_ (0x00000020) /* R/WC */ +#define INT_STS_RSFF_ (0x00000010) /* R/WC */ +#define INT_STS_RSFL_ (0x00000008) /* R/WC */ +#define INT_STS_GPIO2_INT_ (0x00000004) /* R/WC */ +#define INT_STS_GPIO1_INT_ (0x00000002) /* R/WC */ +#define INT_STS_GPIO0_INT_ (0x00000001) /* R/WC */ + +#define INT_EN (0x5C) +#define INT_EN_SW_INT_EN_ (0x80000000) /* R/W */ +#define INT_EN_TXSTOP_INT_EN_ (0x02000000) /* R/W */ +#define INT_EN_RXSTOP_INT_EN_ (0x01000000) /* R/W */ +#define INT_EN_RXDFH_INT_EN_ (0x00800000) /* R/W */ +//#define INT_EN_RXDF_INT_EN_ (0x00400000) /* R/W */ +#define INT_EN_TIOC_INT_EN_ (0x00200000) /* R/W */ +#define INT_EN_RXD_INT_EN_ (0x00100000) /* R/W */ +#define INT_EN_GPT_INT_EN_ (0x00080000) /* R/W */ +#define INT_EN_PHY_INT_EN_ (0x00040000) /* R/W */ +#define INT_EN_PME_INT_EN_ (0x00020000) /* R/W */ +#define INT_EN_TXSO_EN_ (0x00010000) /* R/W */ +#define INT_EN_RWT_EN_ (0x00008000) /* R/W */ +#define INT_EN_RXE_EN_ (0x00004000) /* R/W */ +#define INT_EN_TXE_EN_ (0x00002000) /* R/W */ +//#define INT_EN_ERX_EN_ (0x00001000) /* R/W */ +#define INT_EN_TDFU_EN_ (0x00000800) /* R/W */ +#define INT_EN_TDFO_EN_ (0x00000400) /* R/W */ +#define INT_EN_TDFA_EN_ (0x00000200) /* R/W */ +#define INT_EN_TSFF_EN_ (0x00000100) /* R/W */ +#define INT_EN_TSFL_EN_ (0x00000080) /* R/W */ +//#define INT_EN_RXDF_EN_ (0x00000040) /* R/W */ +#define INT_EN_RDFO_EN_ (0x00000040) /* R/W */ +#define INT_EN_RDFL_EN_ (0x00000020) /* R/W */ +#define INT_EN_RSFF_EN_ (0x00000010) /* R/W */ +#define INT_EN_RSFL_EN_ (0x00000008) /* R/W */ +#define INT_EN_GPIO2_INT_ (0x00000004) /* R/W */ +#define INT_EN_GPIO1_INT_ (0x00000002) /* R/W */ +#define INT_EN_GPIO0_INT_ (0x00000001) /* R/W */ + +#define BYTE_TEST (0x64) +#define FIFO_INT (0x68) +#define FIFO_INT_TX_AVAIL_LEVEL_ (0xFF000000) /* R/W */ +#define FIFO_INT_TX_STS_LEVEL_ (0x00FF0000) /* R/W */ +#define FIFO_INT_RX_AVAIL_LEVEL_ (0x0000FF00) /* R/W */ +#define FIFO_INT_RX_STS_LEVEL_ (0x000000FF) /* R/W */ + +#define RX_CFG (0x6C) +#define RX_CFG_RX_END_ALGN_ (0xC0000000) /* R/W */ +#define RX_CFG_RX_END_ALGN4_ (0x00000000) /* R/W */ +#define RX_CFG_RX_END_ALGN16_ (0x40000000) /* R/W */ +#define RX_CFG_RX_END_ALGN32_ (0x80000000) /* R/W */ +#define RX_CFG_RX_DMA_CNT_ (0x0FFF0000) /* R/W */ +#define RX_CFG_RX_DUMP_ (0x00008000) /* R/W */ +#define RX_CFG_RXDOFF_ (0x00001F00) /* R/W */ +//#define RX_CFG_RXBAD_ (0x00000001) /* R/W */ + +#define TX_CFG (0x70) +//#define TX_CFG_TX_DMA_LVL_ (0xE0000000) /* R/W */ +//#define TX_CFG_TX_DMA_CNT_ (0x0FFF0000) /* R/W Self Clearing */ +#define TX_CFG_TXS_DUMP_ (0x00008000) /* Self Clearing */ +#define TX_CFG_TXD_DUMP_ (0x00004000) /* Self Clearing */ +#define TX_CFG_TXSAO_ (0x00000004) /* R/W */ +#define TX_CFG_TX_ON_ (0x00000002) /* R/W */ +#define TX_CFG_STOP_TX_ (0x00000001) /* Self Clearing */ + +#define HW_CFG (0x74) +#define HW_CFG_TTM_ (0x00200000) /* R/W */ +#define HW_CFG_SF_ (0x00100000) /* R/W */ +#define HW_CFG_TX_FIF_SZ_ (0x000F0000) /* R/W */ +#define HW_CFG_TR_ (0x00003000) /* R/W */ +#define HW_CFG_PHY_CLK_SEL_ (0x00000060) /* R/W */ +#define HW_CFG_PHY_CLK_SEL_INT_PHY_ (0x00000000) /* R/W */ +#define HW_CFG_PHY_CLK_SEL_EXT_PHY_ (0x00000020) /* R/W */ +#define HW_CFG_PHY_CLK_SEL_CLK_DIS_ (0x00000040) /* R/W */ +#define HW_CFG_SMI_SEL_ (0x00000010) /* R/W */ +#define HW_CFG_EXT_PHY_DET_ (0x00000008) /* RO */ +#define HW_CFG_EXT_PHY_EN_ (0x00000004) /* R/W */ +#define HW_CFG_32_16_BIT_MODE_ (0x00000004) /* RO */ +#define HW_CFG_SRST_TO_ (0x00000002) /* RO */ +#define HW_CFG_SRST_ (0x00000001) /* Self Clearing */ + +#define RX_DP_CTRL (0x78) +#define RX_DP_CTRL_RX_FFWD_ (0x80000000) /* R/W */ +#define RX_DP_CTRL_FFWD_BUSY_ (0x80000000) /* RO */ + +#define RX_FIFO_INF (0x7C) +#define RX_FIFO_INF_RXSUSED_ (0x00FF0000) /* RO */ +#define RX_FIFO_INF_RXDUSED_ (0x0000FFFF) /* RO */ + +#define TX_FIFO_INF (0x80) +#define TX_FIFO_INF_TSUSED_ (0x00FF0000) /* RO */ +#define TX_FIFO_INF_TDFREE_ (0x0000FFFF) /* RO */ + +#define PMT_CTRL (0x84) +#define PMT_CTRL_PM_MODE_ (0x00003000) /* Self Clearing */ +#define PMT_CTRL_PHY_RST_ (0x00000400) /* Self Clearing */ +#define PMT_CTRL_WOL_EN_ (0x00000200) /* R/W */ +#define PMT_CTRL_ED_EN_ (0x00000100) /* R/W */ +#define PMT_CTRL_PME_TYPE_ (0x00000040) /* R/W Not Affected by SW Reset */ +#define PMT_CTRL_WUPS_ (0x00000030) /* R/WC */ +#define PMT_CTRL_WUPS_NOWAKE_ (0x00000000) /* R/WC */ +#define PMT_CTRL_WUPS_ED_ (0x00000010) /* R/WC */ +#define PMT_CTRL_WUPS_WOL_ (0x00000020) /* R/WC */ +#define PMT_CTRL_WUPS_MULTI_ (0x00000030) /* R/WC */ +#define PMT_CTRL_PME_IND_ (0x00000008) /* R/W */ +#define PMT_CTRL_PME_POL_ (0x00000004) /* R/W */ +#define PMT_CTRL_PME_EN_ (0x00000002) /* R/W Not Affected by SW Reset */ +#define PMT_CTRL_READY_ (0x00000001) /* RO */ + +#define GPIO_CFG (0x88) +#define GPIO_CFG_LED3_EN_ (0x40000000) /* R/W */ +#define GPIO_CFG_LED2_EN_ (0x20000000) /* R/W */ +#define GPIO_CFG_LED1_EN_ (0x10000000) /* R/W */ +#define GPIO_CFG_GPIO2_INT_POL_ (0x04000000) /* R/W */ +#define GPIO_CFG_GPIO1_INT_POL_ (0x02000000) /* R/W */ +#define GPIO_CFG_GPIO0_INT_POL_ (0x01000000) /* R/W */ +#define GPIO_CFG_EEPR_EN_ (0x00700000) /* R/W */ +#define GPIO_CFG_GPIOBUF2_ (0x00040000) /* R/W */ +#define GPIO_CFG_GPIOBUF1_ (0x00020000) /* R/W */ +#define GPIO_CFG_GPIOBUF0_ (0x00010000) /* R/W */ +#define GPIO_CFG_GPIODIR2_ (0x00000400) /* R/W */ +#define GPIO_CFG_GPIODIR1_ (0x00000200) /* R/W */ +#define GPIO_CFG_GPIODIR0_ (0x00000100) /* R/W */ +#define GPIO_CFG_GPIOD4_ (0x00000010) /* R/W */ +#define GPIO_CFG_GPIOD3_ (0x00000008) /* R/W */ +#define GPIO_CFG_GPIOD2_ (0x00000004) /* R/W */ +#define GPIO_CFG_GPIOD1_ (0x00000002) /* R/W */ +#define GPIO_CFG_GPIOD0_ (0x00000001) /* R/W */ + +#define GPT_CFG (0x8C) +#define GPT_CFG_TIMER_EN_ (0x20000000) /* R/W */ +#define GPT_CFG_GPT_LOAD_ (0x0000FFFF) /* R/W */ + +#define GPT_CNT (0x90) +#define GPT_CNT_GPT_CNT_ (0x0000FFFF) /* RO */ + +#define ENDIAN (0x98) +#define FREE_RUN (0x9C) +#define RX_DROP (0xA0) +#define MAC_CSR_CMD (0xA4) +#define MAC_CSR_CMD_CSR_BUSY_ (0x80000000) /* Self Clearing */ +#define MAC_CSR_CMD_R_NOT_W_ (0x40000000) /* R/W */ +#define MAC_CSR_CMD_CSR_ADDR_ (0x000000FF) /* R/W */ + +#define MAC_CSR_DATA (0xA8) +#define AFC_CFG (0xAC) +#define AFC_CFG_AFC_HI_ (0x00FF0000) /* R/W */ +#define AFC_CFG_AFC_LO_ (0x0000FF00) /* R/W */ +#define AFC_CFG_BACK_DUR_ (0x000000F0) /* R/W */ +#define AFC_CFG_FCMULT_ (0x00000008) /* R/W */ +#define AFC_CFG_FCBRD_ (0x00000004) /* R/W */ +#define AFC_CFG_FCADD_ (0x00000002) /* R/W */ +#define AFC_CFG_FCANY_ (0x00000001) /* R/W */ + +#define E2P_CMD (0xB0) +#define E2P_CMD_EPC_BUSY_ (0x80000000) /* Self Clearing */ +#define E2P_CMD_EPC_CMD_ (0x70000000) /* R/W */ +#define E2P_CMD_EPC_CMD_READ_ (0x00000000) /* R/W */ +#define E2P_CMD_EPC_CMD_EWDS_ (0x10000000) /* R/W */ +#define E2P_CMD_EPC_CMD_EWEN_ (0x20000000) /* R/W */ +#define E2P_CMD_EPC_CMD_WRITE_ (0x30000000) /* R/W */ +#define E2P_CMD_EPC_CMD_WRAL_ (0x40000000) /* R/W */ +#define E2P_CMD_EPC_CMD_ERASE_ (0x50000000) /* R/W */ +#define E2P_CMD_EPC_CMD_ERAL_ (0x60000000) /* R/W */ +#define E2P_CMD_EPC_CMD_RELOAD_ (0x70000000) /* R/W */ +#define E2P_CMD_EPC_TIMEOUT_ (0x00000200) /* RO */ +#define E2P_CMD_MAC_ADDR_LOADED_ (0x00000100) /* RO */ +#define E2P_CMD_EPC_ADDR_ (0x000000FF) /* R/W */ + +#define E2P_DATA (0xB4) +#define E2P_DATA_EEPROM_DATA_ (0x000000FF) /* R/W */ +/* end of LAN register offsets and bit definitions */ + +/* + **************************************************************************** + **************************************************************************** + * MAC Control and Status Register (Indirect Address) + * Offset (through the MAC_CSR CMD and DATA port) + **************************************************************************** + **************************************************************************** + * + */ +#define MAC_CR (0x01) /* R/W */ + +/* MAC_CR - MAC Control Register */ +#define MAC_CR_RXALL_ (0x80000000) +// TODO: delete this bit? It is not described in the data sheet. +#define MAC_CR_HBDIS_ (0x10000000) +#define MAC_CR_RCVOWN_ (0x00800000) +#define MAC_CR_LOOPBK_ (0x00200000) +#define MAC_CR_FDPX_ (0x00100000) +#define MAC_CR_MCPAS_ (0x00080000) +#define MAC_CR_PRMS_ (0x00040000) +#define MAC_CR_INVFILT_ (0x00020000) +#define MAC_CR_PASSBAD_ (0x00010000) +#define MAC_CR_HFILT_ (0x00008000) +#define MAC_CR_HPFILT_ (0x00002000) +#define MAC_CR_LCOLL_ (0x00001000) +#define MAC_CR_BCAST_ (0x00000800) +#define MAC_CR_DISRTY_ (0x00000400) +#define MAC_CR_PADSTR_ (0x00000100) +#define MAC_CR_BOLMT_MASK_ (0x000000C0) +#define MAC_CR_DFCHK_ (0x00000020) +#define MAC_CR_TXEN_ (0x00000008) +#define MAC_CR_RXEN_ (0x00000004) + +#define ADDRH (0x02) /* R/W mask 0x0000FFFFUL */ +#define ADDRL (0x03) /* R/W mask 0xFFFFFFFFUL */ +#define HASHH (0x04) /* R/W */ +#define HASHL (0x05) /* R/W */ + +#define MII_ACC (0x06) /* R/W */ +#define MII_ACC_PHY_ADDR_ (0x0000F800) +#define MII_ACC_MIIRINDA_ (0x000007C0) +#define MII_ACC_MII_WRITE_ (0x00000002) +#define MII_ACC_MII_BUSY_ (0x00000001) + +#define MII_DATA (0x07) /* R/W mask 0x0000FFFFUL */ + +#define FLOW (0x08) /* R/W */ +#define FLOW_FCPT_ (0xFFFF0000) +#define FLOW_FCPASS_ (0x00000004) +#define FLOW_FCEN_ (0x00000002) +#define FLOW_FCBSY_ (0x00000001) + +#define VLAN1 (0x09) /* R/W mask 0x0000FFFFUL */ +#define VLAN1_VTI1_ (0x0000ffff) + +#define VLAN2 (0x0A) /* R/W mask 0x0000FFFFUL */ +#define VLAN2_VTI2_ (0x0000ffff) + +#define WUFF (0x0B) /* WO */ + +#define WUCSR (0x0C) /* R/W */ +#define WUCSR_GUE_ (0x00000200) +#define WUCSR_WUFR_ (0x00000040) +#define WUCSR_MPR_ (0x00000020) +#define WUCSR_WAKE_EN_ (0x00000004) +#define WUCSR_MPEN_ (0x00000002) + +/* + **************************************************************************** + * Chip Specific MII Defines + **************************************************************************** + * + * Phy register offsets and bit definitions + * + */ + +#define PHY_MODE_CTRL_STS ((u32)17) /* Mode Control/Status Register */ +//#define MODE_CTRL_STS_FASTRIP_ ((u16)0x4000) +#define MODE_CTRL_STS_EDPWRDOWN_ ((u16)0x2000) +//#define MODE_CTRL_STS_LOWSQEN_ ((u16)0x0800) +//#define MODE_CTRL_STS_MDPREBP_ ((u16)0x0400) +//#define MODE_CTRL_STS_FARLOOPBACK_ ((u16)0x0200) +//#define MODE_CTRL_STS_FASTEST_ ((u16)0x0100) +//#define MODE_CTRL_STS_REFCLKEN_ ((u16)0x0010) +//#define MODE_CTRL_STS_PHYADBP_ ((u16)0x0008) +//#define MODE_CTRL_STS_FORCE_G_LINK_ ((u16)0x0004) +#define MODE_CTRL_STS_ENERGYON_ ((u16)0x0002) + +#define PHY_INT_SRC ((u32)29) +#define PHY_INT_SRC_ENERGY_ON_ ((u16)0x0080) +#define PHY_INT_SRC_ANEG_COMP_ ((u16)0x0040) +#define PHY_INT_SRC_REMOTE_FAULT_ ((u16)0x0020) +#define PHY_INT_SRC_LINK_DOWN_ ((u16)0x0010) +#define PHY_INT_SRC_ANEG_LP_ACK_ ((u16)0x0008) +#define PHY_INT_SRC_PAR_DET_FAULT_ ((u16)0x0004) +#define PHY_INT_SRC_ANEG_PGRX_ ((u16)0x0002) + +#define PHY_INT_MASK ((u32)30) +#define PHY_INT_MASK_ENERGY_ON_ ((u16)0x0080) +#define PHY_INT_MASK_ANEG_COMP_ ((u16)0x0040) +#define PHY_INT_MASK_REMOTE_FAULT_ ((u16)0x0020) +#define PHY_INT_MASK_LINK_DOWN_ ((u16)0x0010) +#define PHY_INT_MASK_ANEG_LP_ACK_ ((u16)0x0008) +#define PHY_INT_MASK_PAR_DET_FAULT_ ((u16)0x0004) +#define PHY_INT_MASK_ANEG_PGRX_ ((u16)0x0002) + +#define PHY_SPECIAL ((u32)31) +#define PHY_SPECIAL_ANEG_DONE_ ((u16)0x1000) +#define PHY_SPECIAL_RES_ ((u16)0x0040) +#define PHY_SPECIAL_RES_MASK_ ((u16)0x0FE1) +#define PHY_SPECIAL_SPD_ ((u16)0x001C) +#define PHY_SPECIAL_SPD_10HALF_ ((u16)0x0004) +#define PHY_SPECIAL_SPD_10FULL_ ((u16)0x0014) +#define PHY_SPECIAL_SPD_100HALF_ ((u16)0x0008) +#define PHY_SPECIAL_SPD_100FULL_ ((u16)0x0018) + +#define LAN911X_INTERNAL_PHY_ID (0x0007C000) + +/* Chip ID values */ +#define CHIP_9115 0x0115 +#define CHIP_9116 0x0116 +#define CHIP_9117 0x0117 +#define CHIP_9118 0x0118 +#define CHIP_9211 0x9211 +#define CHIP_9215 0x115A +#define CHIP_9217 0x117A +#define CHIP_9218 0x118A + +struct chip_id { + u16 id; + char *name; +}; + +static const struct chip_id chip_ids[] = { + { CHIP_9115, "LAN9115" }, + { CHIP_9116, "LAN9116" }, + { CHIP_9117, "LAN9117" }, + { CHIP_9118, "LAN9118" }, + { CHIP_9211, "LAN9211" }, + { CHIP_9215, "LAN9215" }, + { CHIP_9217, "LAN9217" }, + { CHIP_9218, "LAN9218" }, + { 0, NULL }, +}; + +#define IS_REV_A(x) ((x & 0xFFFF)==0) + +/* + * Macros to abstract register access according to the data bus + * capabilities. Please use those and not the in/out primitives. + */ +/* FIFO read/write macros */ +#define SMC_PUSH_DATA(lp, p, l) SMC_outsl( lp, TX_DATA_FIFO, p, (l) >> 2 ) +#define SMC_PULL_DATA(lp, p, l) SMC_insl ( lp, RX_DATA_FIFO, p, (l) >> 2 ) +#define SMC_SET_TX_FIFO(lp, x) SMC_outl( x, lp, TX_DATA_FIFO ) +#define SMC_GET_RX_FIFO(lp) SMC_inl( lp, RX_DATA_FIFO ) + + +/* I/O mapped register read/write macros */ +#define SMC_GET_TX_STS_FIFO(lp) SMC_inl( lp, TX_STATUS_FIFO ) +#define SMC_GET_RX_STS_FIFO(lp) SMC_inl( lp, RX_STATUS_FIFO ) +#define SMC_GET_RX_STS_FIFO_PEEK(lp) SMC_inl( lp, RX_STATUS_FIFO_PEEK ) +#define SMC_GET_PN(lp) (SMC_inl( lp, ID_REV ) >> 16) +#define SMC_GET_REV(lp) (SMC_inl( lp, ID_REV ) & 0xFFFF) +#define SMC_GET_IRQ_CFG(lp) SMC_inl( lp, INT_CFG ) +#define SMC_SET_IRQ_CFG(lp, x) SMC_outl( x, lp, INT_CFG ) +#define SMC_GET_INT(lp) SMC_inl( lp, INT_STS ) +#define SMC_ACK_INT(lp, x) SMC_outl( x, lp, INT_STS ) +#define SMC_GET_INT_EN(lp) SMC_inl( lp, INT_EN ) +#define SMC_SET_INT_EN(lp, x) SMC_outl( x, lp, INT_EN ) +#define SMC_GET_BYTE_TEST(lp) SMC_inl( lp, BYTE_TEST ) +#define SMC_SET_BYTE_TEST(lp, x) SMC_outl( x, lp, BYTE_TEST ) +#define SMC_GET_FIFO_INT(lp) SMC_inl( lp, FIFO_INT ) +#define SMC_SET_FIFO_INT(lp, x) SMC_outl( x, lp, FIFO_INT ) +#define SMC_SET_FIFO_TDA(lp, x) \ + do { \ + unsigned long __flags; \ + int __mask; \ + local_irq_save(__flags); \ + __mask = SMC_GET_FIFO_INT((lp)) & ~(0xFF<<24); \ + SMC_SET_FIFO_INT( (lp), __mask | (x)<<24 ); \ + local_irq_restore(__flags); \ + } while (0) +#define SMC_SET_FIFO_TSL(lp, x) \ + do { \ + unsigned long __flags; \ + int __mask; \ + local_irq_save(__flags); \ + __mask = SMC_GET_FIFO_INT((lp)) & ~(0xFF<<16); \ + SMC_SET_FIFO_INT( (lp), __mask | (((x) & 0xFF)<<16)); \ + local_irq_restore(__flags); \ + } while (0) +#define SMC_SET_FIFO_RSA(lp, x) \ + do { \ + unsigned long __flags; \ + int __mask; \ + local_irq_save(__flags); \ + __mask = SMC_GET_FIFO_INT((lp)) & ~(0xFF<<8); \ + SMC_SET_FIFO_INT( (lp), __mask | (((x) & 0xFF)<<8)); \ + local_irq_restore(__flags); \ + } while (0) +#define SMC_SET_FIFO_RSL(lp, x) \ + do { \ + unsigned long __flags; \ + int __mask; \ + local_irq_save(__flags); \ + __mask = SMC_GET_FIFO_INT((lp)) & ~0xFF; \ + SMC_SET_FIFO_INT( (lp),__mask | ((x) & 0xFF)); \ + local_irq_restore(__flags); \ + } while (0) +#define SMC_GET_RX_CFG(lp) SMC_inl( lp, RX_CFG ) +#define SMC_SET_RX_CFG(lp, x) SMC_outl( x, lp, RX_CFG ) +#define SMC_GET_TX_CFG(lp) SMC_inl( lp, TX_CFG ) +#define SMC_SET_TX_CFG(lp, x) SMC_outl( x, lp, TX_CFG ) +#define SMC_GET_HW_CFG(lp) SMC_inl( lp, HW_CFG ) +#define SMC_SET_HW_CFG(lp, x) SMC_outl( x, lp, HW_CFG ) +#define SMC_GET_RX_DP_CTRL(lp) SMC_inl( lp, RX_DP_CTRL ) +#define SMC_SET_RX_DP_CTRL(lp, x) SMC_outl( x, lp, RX_DP_CTRL ) +#define SMC_GET_PMT_CTRL(lp) SMC_inl( lp, PMT_CTRL ) +#define SMC_SET_PMT_CTRL(lp, x) SMC_outl( x, lp, PMT_CTRL ) +#define SMC_GET_GPIO_CFG(lp) SMC_inl( lp, GPIO_CFG ) +#define SMC_SET_GPIO_CFG(lp, x) SMC_outl( x, lp, GPIO_CFG ) +#define SMC_GET_RX_FIFO_INF(lp) SMC_inl( lp, RX_FIFO_INF ) +#define SMC_SET_RX_FIFO_INF(lp, x) SMC_outl( x, lp, RX_FIFO_INF ) +#define SMC_GET_TX_FIFO_INF(lp) SMC_inl( lp, TX_FIFO_INF ) +#define SMC_SET_TX_FIFO_INF(lp, x) SMC_outl( x, lp, TX_FIFO_INF ) +#define SMC_GET_GPT_CFG(lp) SMC_inl( lp, GPT_CFG ) +#define SMC_SET_GPT_CFG(lp, x) SMC_outl( x, lp, GPT_CFG ) +#define SMC_GET_RX_DROP(lp) SMC_inl( lp, RX_DROP ) +#define SMC_SET_RX_DROP(lp, x) SMC_outl( x, lp, RX_DROP ) +#define SMC_GET_MAC_CMD(lp) SMC_inl( lp, MAC_CSR_CMD ) +#define SMC_SET_MAC_CMD(lp, x) SMC_outl( x, lp, MAC_CSR_CMD ) +#define SMC_GET_MAC_DATA(lp) SMC_inl( lp, MAC_CSR_DATA ) +#define SMC_SET_MAC_DATA(lp, x) SMC_outl( x, lp, MAC_CSR_DATA ) +#define SMC_GET_AFC_CFG(lp) SMC_inl( lp, AFC_CFG ) +#define SMC_SET_AFC_CFG(lp, x) SMC_outl( x, lp, AFC_CFG ) +#define SMC_GET_E2P_CMD(lp) SMC_inl( lp, E2P_CMD ) +#define SMC_SET_E2P_CMD(lp, x) SMC_outl( x, lp, E2P_CMD ) +#define SMC_GET_E2P_DATA(lp) SMC_inl( lp, E2P_DATA ) +#define SMC_SET_E2P_DATA(lp, x) SMC_outl( x, lp, E2P_DATA ) + +/* MAC register read/write macros */ +#define SMC_GET_MAC_CSR(lp,a,v) \ + do { \ + while (SMC_GET_MAC_CMD((lp)) & MAC_CSR_CMD_CSR_BUSY_); \ + SMC_SET_MAC_CMD((lp),MAC_CSR_CMD_CSR_BUSY_ | \ + MAC_CSR_CMD_R_NOT_W_ | (a) ); \ + while (SMC_GET_MAC_CMD((lp)) & MAC_CSR_CMD_CSR_BUSY_); \ + v = SMC_GET_MAC_DATA((lp)); \ + } while (0) +#define SMC_SET_MAC_CSR(lp,a,v) \ + do { \ + while (SMC_GET_MAC_CMD((lp)) & MAC_CSR_CMD_CSR_BUSY_); \ + SMC_SET_MAC_DATA((lp), v); \ + SMC_SET_MAC_CMD((lp), MAC_CSR_CMD_CSR_BUSY_ | (a) ); \ + while (SMC_GET_MAC_CMD((lp)) & MAC_CSR_CMD_CSR_BUSY_); \ + } while (0) +#define SMC_GET_MAC_CR(lp, x) SMC_GET_MAC_CSR( (lp), MAC_CR, x ) +#define SMC_SET_MAC_CR(lp, x) SMC_SET_MAC_CSR( (lp), MAC_CR, x ) +#define SMC_GET_ADDRH(lp, x) SMC_GET_MAC_CSR( (lp), ADDRH, x ) +#define SMC_SET_ADDRH(lp, x) SMC_SET_MAC_CSR( (lp), ADDRH, x ) +#define SMC_GET_ADDRL(lp, x) SMC_GET_MAC_CSR( (lp), ADDRL, x ) +#define SMC_SET_ADDRL(lp, x) SMC_SET_MAC_CSR( (lp), ADDRL, x ) +#define SMC_GET_HASHH(lp, x) SMC_GET_MAC_CSR( (lp), HASHH, x ) +#define SMC_SET_HASHH(lp, x) SMC_SET_MAC_CSR( (lp), HASHH, x ) +#define SMC_GET_HASHL(lp, x) SMC_GET_MAC_CSR( (lp), HASHL, x ) +#define SMC_SET_HASHL(lp, x) SMC_SET_MAC_CSR( (lp), HASHL, x ) +#define SMC_GET_MII_ACC(lp, x) SMC_GET_MAC_CSR( (lp), MII_ACC, x ) +#define SMC_SET_MII_ACC(lp, x) SMC_SET_MAC_CSR( (lp), MII_ACC, x ) +#define SMC_GET_MII_DATA(lp, x) SMC_GET_MAC_CSR( (lp), MII_DATA, x ) +#define SMC_SET_MII_DATA(lp, x) SMC_SET_MAC_CSR( (lp), MII_DATA, x ) +#define SMC_GET_FLOW(lp, x) SMC_GET_MAC_CSR( (lp), FLOW, x ) +#define SMC_SET_FLOW(lp, x) SMC_SET_MAC_CSR( (lp), FLOW, x ) +#define SMC_GET_VLAN1(lp, x) SMC_GET_MAC_CSR( (lp), VLAN1, x ) +#define SMC_SET_VLAN1(lp, x) SMC_SET_MAC_CSR( (lp), VLAN1, x ) +#define SMC_GET_VLAN2(lp, x) SMC_GET_MAC_CSR( (lp), VLAN2, x ) +#define SMC_SET_VLAN2(lp, x) SMC_SET_MAC_CSR( (lp), VLAN2, x ) +#define SMC_SET_WUFF(lp, x) SMC_SET_MAC_CSR( (lp), WUFF, x ) +#define SMC_GET_WUCSR(lp, x) SMC_GET_MAC_CSR( (lp), WUCSR, x ) +#define SMC_SET_WUCSR(lp, x) SMC_SET_MAC_CSR( (lp), WUCSR, x ) + +/* PHY register read/write macros */ +#define SMC_GET_MII(lp,a,phy,v) \ + do { \ + u32 __v; \ + do { \ + SMC_GET_MII_ACC((lp), __v); \ + } while ( __v & MII_ACC_MII_BUSY_ ); \ + SMC_SET_MII_ACC( (lp), ((phy)<<11) | ((a)<<6) | \ + MII_ACC_MII_BUSY_); \ + do { \ + SMC_GET_MII_ACC( (lp), __v); \ + } while ( __v & MII_ACC_MII_BUSY_ ); \ + SMC_GET_MII_DATA((lp), v); \ + } while (0) +#define SMC_SET_MII(lp,a,phy,v) \ + do { \ + u32 __v; \ + do { \ + SMC_GET_MII_ACC((lp), __v); \ + } while ( __v & MII_ACC_MII_BUSY_ ); \ + SMC_SET_MII_DATA((lp), v); \ + SMC_SET_MII_ACC( (lp), ((phy)<<11) | ((a)<<6) | \ + MII_ACC_MII_BUSY_ | \ + MII_ACC_MII_WRITE_ ); \ + do { \ + SMC_GET_MII_ACC((lp), __v); \ + } while ( __v & MII_ACC_MII_BUSY_ ); \ + } while (0) +#define SMC_GET_PHY_BMCR(lp,phy,x) SMC_GET_MII( (lp), MII_BMCR, phy, x ) +#define SMC_SET_PHY_BMCR(lp,phy,x) SMC_SET_MII( (lp), MII_BMCR, phy, x ) +#define SMC_GET_PHY_BMSR(lp,phy,x) SMC_GET_MII( (lp), MII_BMSR, phy, x ) +#define SMC_GET_PHY_ID1(lp,phy,x) SMC_GET_MII( (lp), MII_PHYSID1, phy, x ) +#define SMC_GET_PHY_ID2(lp,phy,x) SMC_GET_MII( (lp), MII_PHYSID2, phy, x ) +#define SMC_GET_PHY_MII_ADV(lp,phy,x) SMC_GET_MII( (lp), MII_ADVERTISE, phy, x ) +#define SMC_SET_PHY_MII_ADV(lp,phy,x) SMC_SET_MII( (lp), MII_ADVERTISE, phy, x ) +#define SMC_GET_PHY_MII_LPA(lp,phy,x) SMC_GET_MII( (lp), MII_LPA, phy, x ) +#define SMC_SET_PHY_MII_LPA(lp,phy,x) SMC_SET_MII( (lp), MII_LPA, phy, x ) +#define SMC_GET_PHY_CTRL_STS(lp,phy,x) SMC_GET_MII( (lp), PHY_MODE_CTRL_STS, phy, x ) +#define SMC_SET_PHY_CTRL_STS(lp,phy,x) SMC_SET_MII( (lp), PHY_MODE_CTRL_STS, phy, x ) +#define SMC_GET_PHY_INT_SRC(lp,phy,x) SMC_GET_MII( (lp), PHY_INT_SRC, phy, x ) +#define SMC_SET_PHY_INT_SRC(lp,phy,x) SMC_SET_MII( (lp), PHY_INT_SRC, phy, x ) +#define SMC_GET_PHY_INT_MASK(lp,phy,x) SMC_GET_MII( (lp), PHY_INT_MASK, phy, x ) +#define SMC_SET_PHY_INT_MASK(lp,phy,x) SMC_SET_MII( (lp), PHY_INT_MASK, phy, x ) +#define SMC_GET_PHY_SPECIAL(lp,phy,x) SMC_GET_MII( (lp), PHY_SPECIAL, phy, x ) + + + +/* Misc read/write macros */ + +#ifndef SMC_GET_MAC_ADDR +#define SMC_GET_MAC_ADDR(lp, addr) \ + do { \ + unsigned int __v; \ + \ + SMC_GET_MAC_CSR((lp), ADDRL, __v); \ + addr[0] = __v; addr[1] = __v >> 8; \ + addr[2] = __v >> 16; addr[3] = __v >> 24; \ + SMC_GET_MAC_CSR((lp), ADDRH, __v); \ + addr[4] = __v; addr[5] = __v >> 8; \ + } while (0) +#endif + +#define SMC_SET_MAC_ADDR(lp, addr) \ + do { \ + SMC_SET_MAC_CSR((lp), ADDRL, \ + addr[0] | \ + (addr[1] << 8) | \ + (addr[2] << 16) | \ + (addr[3] << 24)); \ + SMC_SET_MAC_CSR((lp), ADDRH, addr[4]|(addr[5] << 8));\ + } while (0) + + +#define SMC_WRITE_EEPROM_CMD(lp, cmd, addr) \ + do { \ + while (SMC_GET_E2P_CMD((lp)) & MAC_CSR_CMD_CSR_BUSY_); \ + SMC_SET_MAC_CMD((lp), MAC_CSR_CMD_R_NOT_W_ | a ); \ + while (SMC_GET_MAC_CMD((lp)) & MAC_CSR_CMD_CSR_BUSY_); \ + } while (0) + +#endif /* _SMC911X_H_ */ diff --git a/drivers/net/ethernet/smsc/smc9194.c b/drivers/net/ethernet/smsc/smc9194.c new file mode 100644 index 000000000000..5b65ac4b3cef --- /dev/null +++ b/drivers/net/ethernet/smsc/smc9194.c @@ -0,0 +1,1589 @@ +/*------------------------------------------------------------------------ + . smc9194.c + . This is a driver for SMC's 9000 series of Ethernet cards. + . + . Copyright (C) 1996 by Erik Stahlman + . This software may be used and distributed according to the terms + . of the GNU General Public License, incorporated herein by reference. + . + . "Features" of the SMC chip: + . 4608 byte packet memory. ( for the 91C92. Others have more ) + . EEPROM for configuration + . AUI/TP selection ( mine has 10Base2/10BaseT select ) + . + . Arguments: + . io = for the base address + . irq = for the IRQ + . ifport = 0 for autodetect, 1 for TP, 2 for AUI ( or 10base2 ) + . + . author: + . Erik Stahlman ( erik@vt.edu ) + . contributors: + . Arnaldo Carvalho de Melo <acme@conectiva.com.br> + . + . Hardware multicast code from Peter Cammaert ( pc@denkart.be ) + . + . Sources: + . o SMC databook + . o skeleton.c by Donald Becker ( becker@scyld.com ) + . o ( a LOT of advice from Becker as well ) + . + . History: + . 12/07/95 Erik Stahlman written, got receive/xmit handled + . 01/03/96 Erik Stahlman worked out some bugs, actually usable!!! :-) + . 01/06/96 Erik Stahlman cleaned up some, better testing, etc + . 01/29/96 Erik Stahlman fixed autoirq, added multicast + . 02/01/96 Erik Stahlman 1. disabled all interrupts in smc_reset + . 2. got rid of post-decrementing bug -- UGH. + . 02/13/96 Erik Stahlman Tried to fix autoirq failure. Added more + . descriptive error messages. + . 02/15/96 Erik Stahlman Fixed typo that caused detection failure + . 02/23/96 Erik Stahlman Modified it to fit into kernel tree + . Added support to change hardware address + . Cleared stats on opens + . 02/26/96 Erik Stahlman Trial support for Kernel 1.2.13 + . Kludge for automatic IRQ detection + . 03/04/96 Erik Stahlman Fixed kernel 1.3.70 + + . Fixed bug reported by Gardner Buchanan in + . smc_enable, with outw instead of outb + . 03/06/96 Erik Stahlman Added hardware multicast from Peter Cammaert + . 04/14/00 Heiko Pruessing (SMA Regelsysteme) Fixed bug in chip memory + . allocation + . 08/20/00 Arnaldo Melo fix kfree(skb) in smc_hardware_send_packet + . 12/15/00 Christian Jullien fix "Warning: kfree_skb on hard IRQ" + . 11/08/01 Matt Domsch Use common crc32 function + ----------------------------------------------------------------------------*/ + +static const char version[] = + "smc9194.c:v0.14 12/15/00 by Erik Stahlman (erik@vt.edu)\n"; + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/crc32.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/bitops.h> + +#include <asm/io.h> + +#include "smc9194.h" + +#define DRV_NAME "smc9194" + +/*------------------------------------------------------------------------ + . + . Configuration options, for the experienced user to change. + . + -------------------------------------------------------------------------*/ + +/* + . Do you want to use 32 bit xfers? This should work on all chips, as + . the chipset is designed to accommodate them. +*/ +#ifdef __sh__ +#undef USE_32_BIT +#else +#define USE_32_BIT 1 +#endif + +#if defined(__H8300H__) || defined(__H8300S__) +#define NO_AUTOPROBE +#undef insl +#undef outsl +#define insl(a,b,l) io_insl_noswap(a,b,l) +#define outsl(a,b,l) io_outsl_noswap(a,b,l) +#endif + +/* + .the SMC9194 can be at any of the following port addresses. To change, + .for a slightly different card, you can add it to the array. Keep in + .mind that the array must end in zero. +*/ + +struct devlist { + unsigned int port; + unsigned int irq; +}; + +#if defined(CONFIG_H8S_EDOSK2674) +static struct devlist smc_devlist[] __initdata = { + {.port = 0xf80000, .irq = 16}, + {.port = 0, .irq = 0 }, +}; +#else +static struct devlist smc_devlist[] __initdata = { + {.port = 0x200, .irq = 0}, + {.port = 0x220, .irq = 0}, + {.port = 0x240, .irq = 0}, + {.port = 0x260, .irq = 0}, + {.port = 0x280, .irq = 0}, + {.port = 0x2A0, .irq = 0}, + {.port = 0x2C0, .irq = 0}, + {.port = 0x2E0, .irq = 0}, + {.port = 0x300, .irq = 0}, + {.port = 0x320, .irq = 0}, + {.port = 0x340, .irq = 0}, + {.port = 0x360, .irq = 0}, + {.port = 0x380, .irq = 0}, + {.port = 0x3A0, .irq = 0}, + {.port = 0x3C0, .irq = 0}, + {.port = 0x3E0, .irq = 0}, + {.port = 0, .irq = 0}, +}; +#endif +/* + . Wait time for memory to be free. This probably shouldn't be + . tuned that much, as waiting for this means nothing else happens + . in the system +*/ +#define MEMORY_WAIT_TIME 16 + +/* + . DEBUGGING LEVELS + . + . 0 for normal operation + . 1 for slightly more details + . >2 for various levels of increasingly useless information + . 2 for interrupt tracking, status flags + . 3 for packet dumps, etc. +*/ +#define SMC_DEBUG 0 + +#if (SMC_DEBUG > 2 ) +#define PRINTK3(x) printk x +#else +#define PRINTK3(x) +#endif + +#if SMC_DEBUG > 1 +#define PRINTK2(x) printk x +#else +#define PRINTK2(x) +#endif + +#ifdef SMC_DEBUG +#define PRINTK(x) printk x +#else +#define PRINTK(x) +#endif + + +/*------------------------------------------------------------------------ + . + . The internal workings of the driver. If you are changing anything + . here with the SMC stuff, you should have the datasheet and known + . what you are doing. + . + -------------------------------------------------------------------------*/ +#define CARDNAME "SMC9194" + + +/* store this information for the driver.. */ +struct smc_local { + /* + If I have to wait until memory is available to send + a packet, I will store the skbuff here, until I get the + desired memory. Then, I'll send it out and free it. + */ + struct sk_buff * saved_skb; + + /* + . This keeps track of how many packets that I have + . sent out. When an TX_EMPTY interrupt comes, I know + . that all of these have been sent. + */ + int packets_waiting; +}; + + +/*----------------------------------------------------------------- + . + . The driver can be entered at any of the following entry points. + . + .------------------------------------------------------------------ */ + +/* + . This is called by register_netdev(). It is responsible for + . checking the portlist for the SMC9000 series chipset. If it finds + . one, then it will initialize the device, find the hardware information, + . and sets up the appropriate device parameters. + . NOTE: Interrupts are *OFF* when this procedure is called. + . + . NB:This shouldn't be static since it is referred to externally. +*/ +struct net_device *smc_init(int unit); + +/* + . The kernel calls this function when someone wants to use the device, + . typically 'ifconfig ethX up'. +*/ +static int smc_open(struct net_device *dev); + +/* + . Our watchdog timed out. Called by the networking layer +*/ +static void smc_timeout(struct net_device *dev); + +/* + . This is called by the kernel in response to 'ifconfig ethX down'. It + . is responsible for cleaning up everything that the open routine + . does, and maybe putting the card into a powerdown state. +*/ +static int smc_close(struct net_device *dev); + +/* + . Finally, a call to set promiscuous mode ( for TCPDUMP and related + . programs ) and multicast modes. +*/ +static void smc_set_multicast_list(struct net_device *dev); + + +/*--------------------------------------------------------------- + . + . Interrupt level calls.. + . + ----------------------------------------------------------------*/ + +/* + . Handles the actual interrupt +*/ +static irqreturn_t smc_interrupt(int irq, void *); +/* + . This is a separate procedure to handle the receipt of a packet, to + . leave the interrupt code looking slightly cleaner +*/ +static inline void smc_rcv( struct net_device *dev ); +/* + . This handles a TX interrupt, which is only called when an error + . relating to a packet is sent. +*/ +static inline void smc_tx( struct net_device * dev ); + +/* + ------------------------------------------------------------ + . + . Internal routines + . + ------------------------------------------------------------ +*/ + +/* + . Test if a given location contains a chip, trying to cause as + . little damage as possible if it's not a SMC chip. +*/ +static int smc_probe(struct net_device *dev, int ioaddr); + +/* + . A rather simple routine to print out a packet for debugging purposes. +*/ +#if SMC_DEBUG > 2 +static void print_packet( byte *, int ); +#endif + +#define tx_done(dev) 1 + +/* this is called to actually send the packet to the chip */ +static void smc_hardware_send_packet( struct net_device * dev ); + +/* Since I am not sure if I will have enough room in the chip's ram + . to store the packet, I call this routine, which either sends it + . now, or generates an interrupt when the card is ready for the + . packet */ +static netdev_tx_t smc_wait_to_send_packet( struct sk_buff * skb, + struct net_device *dev ); + +/* this does a soft reset on the device */ +static void smc_reset( int ioaddr ); + +/* Enable Interrupts, Receive, and Transmit */ +static void smc_enable( int ioaddr ); + +/* this puts the device in an inactive state */ +static void smc_shutdown( int ioaddr ); + +/* This routine will find the IRQ of the driver if one is not + . specified in the input to the device. */ +static int smc_findirq( int ioaddr ); + +/* + . Function: smc_reset( int ioaddr ) + . Purpose: + . This sets the SMC91xx chip to its normal state, hopefully from whatever + . mess that any other DOS driver has put it in. + . + . Maybe I should reset more registers to defaults in here? SOFTRESET should + . do that for me. + . + . Method: + . 1. send a SOFT RESET + . 2. wait for it to finish + . 3. enable autorelease mode + . 4. reset the memory management unit + . 5. clear all interrupts + . +*/ +static void smc_reset( int ioaddr ) +{ + /* This resets the registers mostly to defaults, but doesn't + affect EEPROM. That seems unnecessary */ + SMC_SELECT_BANK( 0 ); + outw( RCR_SOFTRESET, ioaddr + RCR ); + + /* this should pause enough for the chip to be happy */ + SMC_DELAY( ); + + /* Set the transmit and receive configuration registers to + default values */ + outw( RCR_CLEAR, ioaddr + RCR ); + outw( TCR_CLEAR, ioaddr + TCR ); + + /* set the control register to automatically + release successfully transmitted packets, to make the best + use out of our limited memory */ + SMC_SELECT_BANK( 1 ); + outw( inw( ioaddr + CONTROL ) | CTL_AUTO_RELEASE , ioaddr + CONTROL ); + + /* Reset the MMU */ + SMC_SELECT_BANK( 2 ); + outw( MC_RESET, ioaddr + MMU_CMD ); + + /* Note: It doesn't seem that waiting for the MMU busy is needed here, + but this is a place where future chipsets _COULD_ break. Be wary + of issuing another MMU command right after this */ + + outb( 0, ioaddr + INT_MASK ); +} + +/* + . Function: smc_enable + . Purpose: let the chip talk to the outside work + . Method: + . 1. Enable the transmitter + . 2. Enable the receiver + . 3. Enable interrupts +*/ +static void smc_enable( int ioaddr ) +{ + SMC_SELECT_BANK( 0 ); + /* see the header file for options in TCR/RCR NORMAL*/ + outw( TCR_NORMAL, ioaddr + TCR ); + outw( RCR_NORMAL, ioaddr + RCR ); + + /* now, enable interrupts */ + SMC_SELECT_BANK( 2 ); + outb( SMC_INTERRUPT_MASK, ioaddr + INT_MASK ); +} + +/* + . Function: smc_shutdown + . Purpose: closes down the SMC91xxx chip. + . Method: + . 1. zero the interrupt mask + . 2. clear the enable receive flag + . 3. clear the enable xmit flags + . + . TODO: + . (1) maybe utilize power down mode. + . Why not yet? Because while the chip will go into power down mode, + . the manual says that it will wake up in response to any I/O requests + . in the register space. Empirical results do not show this working. +*/ +static void smc_shutdown( int ioaddr ) +{ + /* no more interrupts for me */ + SMC_SELECT_BANK( 2 ); + outb( 0, ioaddr + INT_MASK ); + + /* and tell the card to stay away from that nasty outside world */ + SMC_SELECT_BANK( 0 ); + outb( RCR_CLEAR, ioaddr + RCR ); + outb( TCR_CLEAR, ioaddr + TCR ); +#if 0 + /* finally, shut the chip down */ + SMC_SELECT_BANK( 1 ); + outw( inw( ioaddr + CONTROL ), CTL_POWERDOWN, ioaddr + CONTROL ); +#endif +} + + +/* + . Function: smc_setmulticast( int ioaddr, struct net_device *dev ) + . Purpose: + . This sets the internal hardware table to filter out unwanted multicast + . packets before they take up memory. + . + . The SMC chip uses a hash table where the high 6 bits of the CRC of + . address are the offset into the table. If that bit is 1, then the + . multicast packet is accepted. Otherwise, it's dropped silently. + . + . To use the 6 bits as an offset into the table, the high 3 bits are the + . number of the 8 bit register, while the low 3 bits are the bit within + . that register. + . + . This routine is based very heavily on the one provided by Peter Cammaert. +*/ + + +static void smc_setmulticast(int ioaddr, struct net_device *dev) +{ + int i; + unsigned char multicast_table[ 8 ]; + struct netdev_hw_addr *ha; + /* table for flipping the order of 3 bits */ + unsigned char invert3[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + + /* start with a table of all zeros: reject all */ + memset( multicast_table, 0, sizeof( multicast_table ) ); + + netdev_for_each_mc_addr(ha, dev) { + int position; + + /* only use the low order bits */ + position = ether_crc_le(6, ha->addr) & 0x3f; + + /* do some messy swapping to put the bit in the right spot */ + multicast_table[invert3[position&7]] |= + (1<<invert3[(position>>3)&7]); + + } + /* now, the table can be loaded into the chipset */ + SMC_SELECT_BANK( 3 ); + + for ( i = 0; i < 8 ; i++ ) { + outb( multicast_table[i], ioaddr + MULTICAST1 + i ); + } +} + +/* + . Function: smc_wait_to_send_packet( struct sk_buff * skb, struct net_device * ) + . Purpose: + . Attempt to allocate memory for a packet, if chip-memory is not + . available, then tell the card to generate an interrupt when it + . is available. + . + . Algorithm: + . + . o if the saved_skb is not currently null, then drop this packet + . on the floor. This should never happen, because of TBUSY. + . o if the saved_skb is null, then replace it with the current packet, + . o See if I can sending it now. + . o (NO): Enable interrupts and let the interrupt handler deal with it. + . o (YES):Send it now. +*/ +static netdev_tx_t smc_wait_to_send_packet(struct sk_buff *skb, + struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + word length; + unsigned short numPages; + word time_out; + + netif_stop_queue(dev); + /* Well, I want to send the packet.. but I don't know + if I can send it right now... */ + + if ( lp->saved_skb) { + /* THIS SHOULD NEVER HAPPEN. */ + dev->stats.tx_aborted_errors++; + printk(CARDNAME": Bad Craziness - sent packet while busy.\n" ); + return NETDEV_TX_BUSY; + } + lp->saved_skb = skb; + + length = skb->len; + + if (length < ETH_ZLEN) { + if (skb_padto(skb, ETH_ZLEN)) { + netif_wake_queue(dev); + return NETDEV_TX_OK; + } + length = ETH_ZLEN; + } + + /* + ** The MMU wants the number of pages to be the number of 256 bytes + ** 'pages', minus 1 ( since a packet can't ever have 0 pages :) ) + ** + ** Pkt size for allocating is data length +6 (for additional status words, + ** length and ctl!) If odd size last byte is included in this header. + */ + numPages = ((length & 0xfffe) + 6) / 256; + + if (numPages > 7 ) { + printk(CARDNAME": Far too big packet error.\n"); + /* freeing the packet is a good thing here... but should + . any packets of this size get down here? */ + dev_kfree_skb (skb); + lp->saved_skb = NULL; + /* this IS an error, but, i don't want the skb saved */ + netif_wake_queue(dev); + return NETDEV_TX_OK; + } + /* either way, a packet is waiting now */ + lp->packets_waiting++; + + /* now, try to allocate the memory */ + SMC_SELECT_BANK( 2 ); + outw( MC_ALLOC | numPages, ioaddr + MMU_CMD ); + /* + . Performance Hack + . + . wait a short amount of time.. if I can send a packet now, I send + . it now. Otherwise, I enable an interrupt and wait for one to be + . available. + . + . I could have handled this a slightly different way, by checking to + . see if any memory was available in the FREE MEMORY register. However, + . either way, I need to generate an allocation, and the allocation works + . no matter what, so I saw no point in checking free memory. + */ + time_out = MEMORY_WAIT_TIME; + do { + word status; + + status = inb( ioaddr + INTERRUPT ); + if ( status & IM_ALLOC_INT ) { + /* acknowledge the interrupt */ + outb( IM_ALLOC_INT, ioaddr + INTERRUPT ); + break; + } + } while ( -- time_out ); + + if ( !time_out ) { + /* oh well, wait until the chip finds memory later */ + SMC_ENABLE_INT( IM_ALLOC_INT ); + PRINTK2((CARDNAME": memory allocation deferred.\n")); + /* it's deferred, but I'll handle it later */ + return NETDEV_TX_OK; + } + /* or YES! I can send the packet now.. */ + smc_hardware_send_packet(dev); + netif_wake_queue(dev); + return NETDEV_TX_OK; +} + +/* + . Function: smc_hardware_send_packet(struct net_device * ) + . Purpose: + . This sends the actual packet to the SMC9xxx chip. + . + . Algorithm: + . First, see if a saved_skb is available. + . ( this should NOT be called if there is no 'saved_skb' + . Now, find the packet number that the chip allocated + . Point the data pointers at it in memory + . Set the length word in the chip's memory + . Dump the packet to chip memory + . Check if a last byte is needed ( odd length packet ) + . if so, set the control flag right + . Tell the card to send it + . Enable the transmit interrupt, so I know if it failed + . Free the kernel data if I actually sent it. +*/ +static void smc_hardware_send_packet( struct net_device * dev ) +{ + struct smc_local *lp = netdev_priv(dev); + byte packet_no; + struct sk_buff * skb = lp->saved_skb; + word length; + unsigned int ioaddr; + byte * buf; + + ioaddr = dev->base_addr; + + if ( !skb ) { + PRINTK((CARDNAME": In XMIT with no packet to send\n")); + return; + } + length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + buf = skb->data; + + /* If I get here, I _know_ there is a packet slot waiting for me */ + packet_no = inb( ioaddr + PNR_ARR + 1 ); + if ( packet_no & 0x80 ) { + /* or isn't there? BAD CHIP! */ + printk(KERN_DEBUG CARDNAME": Memory allocation failed.\n"); + dev_kfree_skb_any(skb); + lp->saved_skb = NULL; + netif_wake_queue(dev); + return; + } + + /* we have a packet address, so tell the card to use it */ + outb( packet_no, ioaddr + PNR_ARR ); + + /* point to the beginning of the packet */ + outw( PTR_AUTOINC , ioaddr + POINTER ); + + PRINTK3((CARDNAME": Trying to xmit packet of length %x\n", length )); +#if SMC_DEBUG > 2 + print_packet( buf, length ); +#endif + + /* send the packet length ( +6 for status, length and ctl byte ) + and the status word ( set to zeros ) */ +#ifdef USE_32_BIT + outl( (length +6 ) << 16 , ioaddr + DATA_1 ); +#else + outw( 0, ioaddr + DATA_1 ); + /* send the packet length ( +6 for status words, length, and ctl*/ + outb( (length+6) & 0xFF,ioaddr + DATA_1 ); + outb( (length+6) >> 8 , ioaddr + DATA_1 ); +#endif + + /* send the actual data + . I _think_ it's faster to send the longs first, and then + . mop up by sending the last word. It depends heavily + . on alignment, at least on the 486. Maybe it would be + . a good idea to check which is optimal? But that could take + . almost as much time as is saved? + */ +#ifdef USE_32_BIT + if ( length & 0x2 ) { + outsl(ioaddr + DATA_1, buf, length >> 2 ); +#if !defined(__H8300H__) && !defined(__H8300S__) + outw( *((word *)(buf + (length & 0xFFFFFFFC))),ioaddr +DATA_1); +#else + ctrl_outw( *((word *)(buf + (length & 0xFFFFFFFC))),ioaddr +DATA_1); +#endif + } + else + outsl(ioaddr + DATA_1, buf, length >> 2 ); +#else + outsw(ioaddr + DATA_1 , buf, (length ) >> 1); +#endif + /* Send the last byte, if there is one. */ + + if ( (length & 1) == 0 ) { + outw( 0, ioaddr + DATA_1 ); + } else { + outb( buf[length -1 ], ioaddr + DATA_1 ); + outb( 0x20, ioaddr + DATA_1); + } + + /* enable the interrupts */ + SMC_ENABLE_INT( (IM_TX_INT | IM_TX_EMPTY_INT) ); + + /* and let the chipset deal with it */ + outw( MC_ENQUEUE , ioaddr + MMU_CMD ); + + PRINTK2((CARDNAME": Sent packet of length %d\n", length)); + + lp->saved_skb = NULL; + dev_kfree_skb_any (skb); + + dev->trans_start = jiffies; + + /* we can send another packet */ + netif_wake_queue(dev); +} + +/*------------------------------------------------------------------------- + | + | smc_init(int unit) + | Input parameters: + | dev->base_addr == 0, try to find all possible locations + | dev->base_addr == 1, return failure code + | dev->base_addr == 2, always allocate space, and return success + | dev->base_addr == <anything else> this is the address to check + | + | Output: + | pointer to net_device or ERR_PTR(error) + | + --------------------------------------------------------------------------- +*/ +static int io; +static int irq; +static int ifport; + +struct net_device * __init smc_init(int unit) +{ + struct net_device *dev = alloc_etherdev(sizeof(struct smc_local)); + struct devlist *smcdev = smc_devlist; + int err = 0; + + if (!dev) + return ERR_PTR(-ENODEV); + + if (unit >= 0) { + sprintf(dev->name, "eth%d", unit); + netdev_boot_setup_check(dev); + io = dev->base_addr; + irq = dev->irq; + } + + if (io > 0x1ff) { /* Check a single specified location. */ + err = smc_probe(dev, io); + } else if (io != 0) { /* Don't probe at all. */ + err = -ENXIO; + } else { + for (;smcdev->port; smcdev++) { + if (smc_probe(dev, smcdev->port) == 0) + break; + } + if (!smcdev->port) + err = -ENODEV; + } + if (err) + goto out; + err = register_netdev(dev); + if (err) + goto out1; + return dev; +out1: + free_irq(dev->irq, dev); + release_region(dev->base_addr, SMC_IO_EXTENT); +out: + free_netdev(dev); + return ERR_PTR(err); +} + +/*---------------------------------------------------------------------- + . smc_findirq + . + . This routine has a simple purpose -- make the SMC chip generate an + . interrupt, so an auto-detect routine can detect it, and find the IRQ, + ------------------------------------------------------------------------ +*/ +static int __init smc_findirq(int ioaddr) +{ +#ifndef NO_AUTOPROBE + int timeout = 20; + unsigned long cookie; + + + cookie = probe_irq_on(); + + /* + * What I try to do here is trigger an ALLOC_INT. This is done + * by allocating a small chunk of memory, which will give an interrupt + * when done. + */ + + + SMC_SELECT_BANK(2); + /* enable ALLOCation interrupts ONLY */ + outb( IM_ALLOC_INT, ioaddr + INT_MASK ); + + /* + . Allocate 512 bytes of memory. Note that the chip was just + . reset so all the memory is available + */ + outw( MC_ALLOC | 1, ioaddr + MMU_CMD ); + + /* + . Wait until positive that the interrupt has been generated + */ + while ( timeout ) { + byte int_status; + + int_status = inb( ioaddr + INTERRUPT ); + + if ( int_status & IM_ALLOC_INT ) + break; /* got the interrupt */ + timeout--; + } + /* there is really nothing that I can do here if timeout fails, + as probe_irq_off will return a 0 anyway, which is what I + want in this case. Plus, the clean up is needed in both + cases. */ + + /* DELAY HERE! + On a fast machine, the status might change before the interrupt + is given to the processor. This means that the interrupt was + never detected, and probe_irq_off fails to report anything. + This should fix probe_irq_* problems. + */ + SMC_DELAY(); + SMC_DELAY(); + + /* and disable all interrupts again */ + outb( 0, ioaddr + INT_MASK ); + + /* and return what I found */ + return probe_irq_off(cookie); +#else /* NO_AUTOPROBE */ + struct devlist *smcdev; + for (smcdev = smc_devlist; smcdev->port; smcdev++) { + if (smcdev->port == ioaddr) + return smcdev->irq; + } + return 0; +#endif +} + +static const struct net_device_ops smc_netdev_ops = { + .ndo_open = smc_open, + .ndo_stop = smc_close, + .ndo_start_xmit = smc_wait_to_send_packet, + .ndo_tx_timeout = smc_timeout, + .ndo_set_multicast_list = smc_set_multicast_list, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +/*---------------------------------------------------------------------- + . Function: smc_probe( int ioaddr ) + . + . Purpose: + . Tests to see if a given ioaddr points to an SMC9xxx chip. + . Returns a 0 on success + . + . Algorithm: + . (1) see if the high byte of BANK_SELECT is 0x33 + . (2) compare the ioaddr with the base register's address + . (3) see if I recognize the chip ID in the appropriate register + . + .--------------------------------------------------------------------- + */ + +/*--------------------------------------------------------------- + . Here I do typical initialization tasks. + . + . o Initialize the structure if needed + . o print out my vanity message if not done so already + . o print out what type of hardware is detected + . o print out the ethernet address + . o find the IRQ + . o set up my private data + . o configure the dev structure with my subroutines + . o actually GRAB the irq. + . o GRAB the region + .----------------------------------------------------------------- +*/ +static int __init smc_probe(struct net_device *dev, int ioaddr) +{ + int i, memory, retval; + static unsigned version_printed; + unsigned int bank; + + const char *version_string; + const char *if_string; + + /* registers */ + word revision_register; + word base_address_register; + word configuration_register; + word memory_info_register; + word memory_cfg_register; + + /* Grab the region so that no one else tries to probe our ioports. */ + if (!request_region(ioaddr, SMC_IO_EXTENT, DRV_NAME)) + return -EBUSY; + + dev->irq = irq; + dev->if_port = ifport; + + /* First, see if the high byte is 0x33 */ + bank = inw( ioaddr + BANK_SELECT ); + if ( (bank & 0xFF00) != 0x3300 ) { + retval = -ENODEV; + goto err_out; + } + /* The above MIGHT indicate a device, but I need to write to further + test this. */ + outw( 0x0, ioaddr + BANK_SELECT ); + bank = inw( ioaddr + BANK_SELECT ); + if ( (bank & 0xFF00 ) != 0x3300 ) { + retval = -ENODEV; + goto err_out; + } +#if !defined(CONFIG_H8S_EDOSK2674) + /* well, we've already written once, so hopefully another time won't + hurt. This time, I need to switch the bank register to bank 1, + so I can access the base address register */ + SMC_SELECT_BANK(1); + base_address_register = inw( ioaddr + BASE ); + if ( ioaddr != ( base_address_register >> 3 & 0x3E0 ) ) { + printk(CARDNAME ": IOADDR %x doesn't match configuration (%x). " + "Probably not a SMC chip\n", + ioaddr, base_address_register >> 3 & 0x3E0 ); + /* well, the base address register didn't match. Must not have + been a SMC chip after all. */ + retval = -ENODEV; + goto err_out; + } +#else + (void)base_address_register; /* Warning suppression */ +#endif + + + /* check if the revision register is something that I recognize. + These might need to be added to later, as future revisions + could be added. */ + SMC_SELECT_BANK(3); + revision_register = inw( ioaddr + REVISION ); + if ( !chip_ids[ ( revision_register >> 4 ) & 0xF ] ) { + /* I don't recognize this chip, so... */ + printk(CARDNAME ": IO %x: Unrecognized revision register:" + " %x, Contact author.\n", ioaddr, revision_register); + + retval = -ENODEV; + goto err_out; + } + + /* at this point I'll assume that the chip is an SMC9xxx. + It might be prudent to check a listing of MAC addresses + against the hardware address, or do some other tests. */ + + if (version_printed++ == 0) + printk("%s", version); + + /* fill in some of the fields */ + dev->base_addr = ioaddr; + + /* + . Get the MAC address ( bank 1, regs 4 - 9 ) + */ + SMC_SELECT_BANK( 1 ); + for ( i = 0; i < 6; i += 2 ) { + word address; + + address = inw( ioaddr + ADDR0 + i ); + dev->dev_addr[ i + 1] = address >> 8; + dev->dev_addr[ i ] = address & 0xFF; + } + + /* get the memory information */ + + SMC_SELECT_BANK( 0 ); + memory_info_register = inw( ioaddr + MIR ); + memory_cfg_register = inw( ioaddr + MCR ); + memory = ( memory_cfg_register >> 9 ) & 0x7; /* multiplier */ + memory *= 256 * ( memory_info_register & 0xFF ); + + /* + Now, I want to find out more about the chip. This is sort of + redundant, but it's cleaner to have it in both, rather than having + one VERY long probe procedure. + */ + SMC_SELECT_BANK(3); + revision_register = inw( ioaddr + REVISION ); + version_string = chip_ids[ ( revision_register >> 4 ) & 0xF ]; + if ( !version_string ) { + /* I shouldn't get here because this call was done before.... */ + retval = -ENODEV; + goto err_out; + } + + /* is it using AUI or 10BaseT ? */ + if ( dev->if_port == 0 ) { + SMC_SELECT_BANK(1); + configuration_register = inw( ioaddr + CONFIG ); + if ( configuration_register & CFG_AUI_SELECT ) + dev->if_port = 2; + else + dev->if_port = 1; + } + if_string = interfaces[ dev->if_port - 1 ]; + + /* now, reset the chip, and put it into a known state */ + smc_reset( ioaddr ); + + /* + . If dev->irq is 0, then the device has to be banged on to see + . what the IRQ is. + . + . This banging doesn't always detect the IRQ, for unknown reasons. + . a workaround is to reset the chip and try again. + . + . Interestingly, the DOS packet driver *SETS* the IRQ on the card to + . be what is requested on the command line. I don't do that, mostly + . because the card that I have uses a non-standard method of accessing + . the IRQs, and because this _should_ work in most configurations. + . + . Specifying an IRQ is done with the assumption that the user knows + . what (s)he is doing. No checking is done!!!! + . + */ + if ( dev->irq < 2 ) { + int trials; + + trials = 3; + while ( trials-- ) { + dev->irq = smc_findirq( ioaddr ); + if ( dev->irq ) + break; + /* kick the card and try again */ + smc_reset( ioaddr ); + } + } + if (dev->irq == 0 ) { + printk(CARDNAME": Couldn't autodetect your IRQ. Use irq=xx.\n"); + retval = -ENODEV; + goto err_out; + } + + /* now, print out the card info, in a short format.. */ + + printk("%s: %s(r:%d) at %#3x IRQ:%d INTF:%s MEM:%db ", dev->name, + version_string, revision_register & 0xF, ioaddr, dev->irq, + if_string, memory ); + /* + . Print the Ethernet address + */ + printk("ADDR: %pM\n", dev->dev_addr); + + /* Grab the IRQ */ + retval = request_irq(dev->irq, smc_interrupt, 0, DRV_NAME, dev); + if (retval) { + printk("%s: unable to get IRQ %d (irqval=%d).\n", DRV_NAME, + dev->irq, retval); + goto err_out; + } + + dev->netdev_ops = &smc_netdev_ops; + dev->watchdog_timeo = HZ/20; + + return 0; + +err_out: + release_region(ioaddr, SMC_IO_EXTENT); + return retval; +} + +#if SMC_DEBUG > 2 +static void print_packet( byte * buf, int length ) +{ +#if 0 + int i; + int remainder; + int lines; + + printk("Packet of length %d\n", length); + lines = length / 16; + remainder = length % 16; + + for ( i = 0; i < lines ; i ++ ) { + int cur; + + for ( cur = 0; cur < 8; cur ++ ) { + byte a, b; + + a = *(buf ++ ); + b = *(buf ++ ); + printk("%02x%02x ", a, b ); + } + printk("\n"); + } + for ( i = 0; i < remainder/2 ; i++ ) { + byte a, b; + + a = *(buf ++ ); + b = *(buf ++ ); + printk("%02x%02x ", a, b ); + } + printk("\n"); +#endif +} +#endif + + +/* + * Open and Initialize the board + * + * Set up everything, reset the card, etc .. + * + */ +static int smc_open(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + + int i; /* used to set hw ethernet address */ + + /* clear out all the junk that was put here before... */ + memset(netdev_priv(dev), 0, sizeof(struct smc_local)); + + /* reset the hardware */ + + smc_reset( ioaddr ); + smc_enable( ioaddr ); + + /* Select which interface to use */ + + SMC_SELECT_BANK( 1 ); + if ( dev->if_port == 1 ) { + outw( inw( ioaddr + CONFIG ) & ~CFG_AUI_SELECT, + ioaddr + CONFIG ); + } + else if ( dev->if_port == 2 ) { + outw( inw( ioaddr + CONFIG ) | CFG_AUI_SELECT, + ioaddr + CONFIG ); + } + + /* + According to Becker, I have to set the hardware address + at this point, because the (l)user can set it with an + ioctl. Easily done... + */ + SMC_SELECT_BANK( 1 ); + for ( i = 0; i < 6; i += 2 ) { + word address; + + address = dev->dev_addr[ i + 1 ] << 8 ; + address |= dev->dev_addr[ i ]; + outw( address, ioaddr + ADDR0 + i ); + } + + netif_start_queue(dev); + return 0; +} + +/*-------------------------------------------------------- + . Called by the kernel to send a packet out into the void + . of the net. This routine is largely based on + . skeleton.c, from Becker. + .-------------------------------------------------------- +*/ + +static void smc_timeout(struct net_device *dev) +{ + /* If we get here, some higher level has decided we are broken. + There should really be a "kick me" function call instead. */ + printk(KERN_WARNING CARDNAME": transmit timed out, %s?\n", + tx_done(dev) ? "IRQ conflict" : + "network cable problem"); + /* "kick" the adaptor */ + smc_reset( dev->base_addr ); + smc_enable( dev->base_addr ); + dev->trans_start = jiffies; /* prevent tx timeout */ + /* clear anything saved */ + ((struct smc_local *)netdev_priv(dev))->saved_skb = NULL; + netif_wake_queue(dev); +} + +/*------------------------------------------------------------- + . + . smc_rcv - receive a packet from the card + . + . There is ( at least ) a packet waiting to be read from + . chip-memory. + . + . o Read the status + . o If an error, record it + . o otherwise, read in the packet + -------------------------------------------------------------- +*/ +static void smc_rcv(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + int packet_number; + word status; + word packet_length; + + /* assume bank 2 */ + + packet_number = inw( ioaddr + FIFO_PORTS ); + + if ( packet_number & FP_RXEMPTY ) { + /* we got called , but nothing was on the FIFO */ + PRINTK((CARDNAME ": WARNING: smc_rcv with nothing on FIFO.\n")); + /* don't need to restore anything */ + return; + } + + /* start reading from the start of the packet */ + outw( PTR_READ | PTR_RCV | PTR_AUTOINC, ioaddr + POINTER ); + + /* First two words are status and packet_length */ + status = inw( ioaddr + DATA_1 ); + packet_length = inw( ioaddr + DATA_1 ); + + packet_length &= 0x07ff; /* mask off top bits */ + + PRINTK2(("RCV: STATUS %4x LENGTH %4x\n", status, packet_length )); + /* + . the packet length contains 3 extra words : + . status, length, and an extra word with an odd byte . + */ + packet_length -= 6; + + if ( !(status & RS_ERRORS ) ){ + /* do stuff to make a new packet */ + struct sk_buff * skb; + byte * data; + + /* read one extra byte */ + if ( status & RS_ODDFRAME ) + packet_length++; + + /* set multicast stats */ + if ( status & RS_MULTICAST ) + dev->stats.multicast++; + + skb = dev_alloc_skb( packet_length + 5); + + if ( skb == NULL ) { + printk(KERN_NOTICE CARDNAME ": Low memory, packet dropped.\n"); + dev->stats.rx_dropped++; + goto done; + } + + /* + ! This should work without alignment, but it could be + ! in the worse case + */ + + skb_reserve( skb, 2 ); /* 16 bit alignment */ + + data = skb_put( skb, packet_length); + +#ifdef USE_32_BIT + /* QUESTION: Like in the TX routine, do I want + to send the DWORDs or the bytes first, or some + mixture. A mixture might improve already slow PIO + performance */ + PRINTK3((" Reading %d dwords (and %d bytes)\n", + packet_length >> 2, packet_length & 3 )); + insl(ioaddr + DATA_1 , data, packet_length >> 2 ); + /* read the left over bytes */ + insb( ioaddr + DATA_1, data + (packet_length & 0xFFFFFC), + packet_length & 0x3 ); +#else + PRINTK3((" Reading %d words and %d byte(s)\n", + (packet_length >> 1 ), packet_length & 1 )); + insw(ioaddr + DATA_1 , data, packet_length >> 1); + if ( packet_length & 1 ) { + data += packet_length & ~1; + *(data++) = inb( ioaddr + DATA_1 ); + } +#endif +#if SMC_DEBUG > 2 + print_packet( data, packet_length ); +#endif + + skb->protocol = eth_type_trans(skb, dev ); + netif_rx(skb); + dev->stats.rx_packets++; + dev->stats.rx_bytes += packet_length; + } else { + /* error ... */ + dev->stats.rx_errors++; + + if ( status & RS_ALGNERR ) dev->stats.rx_frame_errors++; + if ( status & (RS_TOOSHORT | RS_TOOLONG ) ) + dev->stats.rx_length_errors++; + if ( status & RS_BADCRC) dev->stats.rx_crc_errors++; + } + +done: + /* error or good, tell the card to get rid of this packet */ + outw( MC_RELEASE, ioaddr + MMU_CMD ); +} + + +/************************************************************************* + . smc_tx + . + . Purpose: Handle a transmit error message. This will only be called + . when an error, because of the AUTO_RELEASE mode. + . + . Algorithm: + . Save pointer and packet no + . Get the packet no from the top of the queue + . check if it's valid ( if not, is this an error??? ) + . read the status word + . record the error + . ( resend? Not really, since we don't want old packets around ) + . Restore saved values + ************************************************************************/ +static void smc_tx( struct net_device * dev ) +{ + int ioaddr = dev->base_addr; + struct smc_local *lp = netdev_priv(dev); + byte saved_packet; + byte packet_no; + word tx_status; + + + /* assume bank 2 */ + + saved_packet = inb( ioaddr + PNR_ARR ); + packet_no = inw( ioaddr + FIFO_PORTS ); + packet_no &= 0x7F; + + /* select this as the packet to read from */ + outb( packet_no, ioaddr + PNR_ARR ); + + /* read the first word from this packet */ + outw( PTR_AUTOINC | PTR_READ, ioaddr + POINTER ); + + tx_status = inw( ioaddr + DATA_1 ); + PRINTK3((CARDNAME": TX DONE STATUS: %4x\n", tx_status)); + + dev->stats.tx_errors++; + if ( tx_status & TS_LOSTCAR ) dev->stats.tx_carrier_errors++; + if ( tx_status & TS_LATCOL ) { + printk(KERN_DEBUG CARDNAME + ": Late collision occurred on last xmit.\n"); + dev->stats.tx_window_errors++; + } +#if 0 + if ( tx_status & TS_16COL ) { ... } +#endif + + if ( tx_status & TS_SUCCESS ) { + printk(CARDNAME": Successful packet caused interrupt\n"); + } + /* re-enable transmit */ + SMC_SELECT_BANK( 0 ); + outw( inw( ioaddr + TCR ) | TCR_ENABLE, ioaddr + TCR ); + + /* kill the packet */ + SMC_SELECT_BANK( 2 ); + outw( MC_FREEPKT, ioaddr + MMU_CMD ); + + /* one less packet waiting for me */ + lp->packets_waiting--; + + outb( saved_packet, ioaddr + PNR_ARR ); +} + +/*-------------------------------------------------------------------- + . + . This is the main routine of the driver, to handle the device when + . it needs some attention. + . + . So: + . first, save state of the chipset + . branch off into routines to handle each case, and acknowledge + . each to the interrupt register + . and finally restore state. + . + ---------------------------------------------------------------------*/ + +static irqreturn_t smc_interrupt(int irq, void * dev_id) +{ + struct net_device *dev = dev_id; + int ioaddr = dev->base_addr; + struct smc_local *lp = netdev_priv(dev); + + byte status; + word card_stats; + byte mask; + int timeout; + /* state registers */ + word saved_bank; + word saved_pointer; + int handled = 0; + + + PRINTK3((CARDNAME": SMC interrupt started\n")); + + saved_bank = inw( ioaddr + BANK_SELECT ); + + SMC_SELECT_BANK(2); + saved_pointer = inw( ioaddr + POINTER ); + + mask = inb( ioaddr + INT_MASK ); + /* clear all interrupts */ + outb( 0, ioaddr + INT_MASK ); + + + /* set a timeout value, so I don't stay here forever */ + timeout = 4; + + PRINTK2((KERN_WARNING CARDNAME ": MASK IS %x\n", mask)); + do { + /* read the status flag, and mask it */ + status = inb( ioaddr + INTERRUPT ) & mask; + if (!status ) + break; + + handled = 1; + + PRINTK3((KERN_WARNING CARDNAME + ": Handling interrupt status %x\n", status)); + + if (status & IM_RCV_INT) { + /* Got a packet(s). */ + PRINTK2((KERN_WARNING CARDNAME + ": Receive Interrupt\n")); + smc_rcv(dev); + } else if (status & IM_TX_INT ) { + PRINTK2((KERN_WARNING CARDNAME + ": TX ERROR handled\n")); + smc_tx(dev); + outb(IM_TX_INT, ioaddr + INTERRUPT ); + } else if (status & IM_TX_EMPTY_INT ) { + /* update stats */ + SMC_SELECT_BANK( 0 ); + card_stats = inw( ioaddr + COUNTER ); + /* single collisions */ + dev->stats.collisions += card_stats & 0xF; + card_stats >>= 4; + /* multiple collisions */ + dev->stats.collisions += card_stats & 0xF; + + /* these are for when linux supports these statistics */ + + SMC_SELECT_BANK( 2 ); + PRINTK2((KERN_WARNING CARDNAME + ": TX_BUFFER_EMPTY handled\n")); + outb( IM_TX_EMPTY_INT, ioaddr + INTERRUPT ); + mask &= ~IM_TX_EMPTY_INT; + dev->stats.tx_packets += lp->packets_waiting; + lp->packets_waiting = 0; + + } else if (status & IM_ALLOC_INT ) { + PRINTK2((KERN_DEBUG CARDNAME + ": Allocation interrupt\n")); + /* clear this interrupt so it doesn't happen again */ + mask &= ~IM_ALLOC_INT; + + smc_hardware_send_packet( dev ); + + /* enable xmit interrupts based on this */ + mask |= ( IM_TX_EMPTY_INT | IM_TX_INT ); + + /* and let the card send more packets to me */ + netif_wake_queue(dev); + + PRINTK2((CARDNAME": Handoff done successfully.\n")); + } else if (status & IM_RX_OVRN_INT ) { + dev->stats.rx_errors++; + dev->stats.rx_fifo_errors++; + outb( IM_RX_OVRN_INT, ioaddr + INTERRUPT ); + } else if (status & IM_EPH_INT ) { + PRINTK((CARDNAME ": UNSUPPORTED: EPH INTERRUPT\n")); + } else if (status & IM_ERCV_INT ) { + PRINTK((CARDNAME ": UNSUPPORTED: ERCV INTERRUPT\n")); + outb( IM_ERCV_INT, ioaddr + INTERRUPT ); + } + } while ( timeout -- ); + + + /* restore state register */ + SMC_SELECT_BANK( 2 ); + outb( mask, ioaddr + INT_MASK ); + + PRINTK3((KERN_WARNING CARDNAME ": MASK is now %x\n", mask)); + outw( saved_pointer, ioaddr + POINTER ); + + SMC_SELECT_BANK( saved_bank ); + + PRINTK3((CARDNAME ": Interrupt done\n")); + return IRQ_RETVAL(handled); +} + + +/*---------------------------------------------------- + . smc_close + . + . this makes the board clean up everything that it can + . and not talk to the outside world. Caused by + . an 'ifconfig ethX down' + . + -----------------------------------------------------*/ +static int smc_close(struct net_device *dev) +{ + netif_stop_queue(dev); + /* clear everything */ + smc_shutdown( dev->base_addr ); + + /* Update the statistics here. */ + return 0; +} + +/*----------------------------------------------------------- + . smc_set_multicast_list + . + . This routine will, depending on the values passed to it, + . either make it accept multicast packets, go into + . promiscuous mode ( for TCPDUMP and cousins ) or accept + . a select set of multicast packets +*/ +static void smc_set_multicast_list(struct net_device *dev) +{ + short ioaddr = dev->base_addr; + + SMC_SELECT_BANK(0); + if ( dev->flags & IFF_PROMISC ) + outw( inw(ioaddr + RCR ) | RCR_PROMISC, ioaddr + RCR ); + +/* BUG? I never disable promiscuous mode if multicasting was turned on. + Now, I turn off promiscuous mode, but I don't do anything to multicasting + when promiscuous mode is turned on. +*/ + + /* Here, I am setting this to accept all multicast packets. + I don't need to zero the multicast table, because the flag is + checked before the table is + */ + else if (dev->flags & IFF_ALLMULTI) + outw( inw(ioaddr + RCR ) | RCR_ALMUL, ioaddr + RCR ); + + /* We just get all multicast packets even if we only want them + . from one source. This will be changed at some future + . point. */ + else if (!netdev_mc_empty(dev)) { + /* support hardware multicasting */ + + /* be sure I get rid of flags I might have set */ + outw( inw( ioaddr + RCR ) & ~(RCR_PROMISC | RCR_ALMUL), + ioaddr + RCR ); + /* NOTE: this has to set the bank, so make sure it is the + last thing called. The bank is set to zero at the top */ + smc_setmulticast(ioaddr, dev); + } + else { + outw( inw( ioaddr + RCR ) & ~(RCR_PROMISC | RCR_ALMUL), + ioaddr + RCR ); + + /* + since I'm disabling all multicast entirely, I need to + clear the multicast list + */ + SMC_SELECT_BANK( 3 ); + outw( 0, ioaddr + MULTICAST1 ); + outw( 0, ioaddr + MULTICAST2 ); + outw( 0, ioaddr + MULTICAST3 ); + outw( 0, ioaddr + MULTICAST4 ); + } +} + +#ifdef MODULE + +static struct net_device *devSMC9194; +MODULE_LICENSE("GPL"); + +module_param(io, int, 0); +module_param(irq, int, 0); +module_param(ifport, int, 0); +MODULE_PARM_DESC(io, "SMC 99194 I/O base address"); +MODULE_PARM_DESC(irq, "SMC 99194 IRQ number"); +MODULE_PARM_DESC(ifport, "SMC 99194 interface port (0-default, 1-TP, 2-AUI)"); + +int __init init_module(void) +{ + if (io == 0) + printk(KERN_WARNING + CARDNAME": You shouldn't use auto-probing with insmod!\n" ); + + /* copy the parameters from insmod into the device structure */ + devSMC9194 = smc_init(-1); + if (IS_ERR(devSMC9194)) + return PTR_ERR(devSMC9194); + return 0; +} + +void __exit cleanup_module(void) +{ + unregister_netdev(devSMC9194); + free_irq(devSMC9194->irq, devSMC9194); + release_region(devSMC9194->base_addr, SMC_IO_EXTENT); + free_netdev(devSMC9194); +} + +#endif /* MODULE */ diff --git a/drivers/net/ethernet/smsc/smc9194.h b/drivers/net/ethernet/smsc/smc9194.h new file mode 100644 index 000000000000..cf69d0a5a1cb --- /dev/null +++ b/drivers/net/ethernet/smsc/smc9194.h @@ -0,0 +1,241 @@ +/*------------------------------------------------------------------------ + . smc9194.h + . Copyright (C) 1996 by Erik Stahlman + . + . This software may be used and distributed according to the terms + . of the GNU General Public License, incorporated herein by reference. + . + . This file contains register information and access macros for + . the SMC91xxx chipset. + . + . Information contained in this file was obtained from the SMC91C94 + . manual from SMC. To get a copy, if you really want one, you can find + . information under www.smc.com in the components division. + . ( this thanks to advice from Donald Becker ). + . + . Authors + . Erik Stahlman ( erik@vt.edu ) + . + . History + . 01/06/96 Erik Stahlman moved definitions here from main .c file + . 01/19/96 Erik Stahlman polished this up some, and added better + . error handling + . + ---------------------------------------------------------------------------*/ +#ifndef _SMC9194_H_ +#define _SMC9194_H_ + +/* I want some simple types */ + +typedef unsigned char byte; +typedef unsigned short word; +typedef unsigned long int dword; + + +/* Because of bank switching, the SMC91xxx uses only 16 I/O ports */ + +#define SMC_IO_EXTENT 16 + + +/*--------------------------------------------------------------- + . + . A description of the SMC registers is probably in order here, + . although for details, the SMC datasheet is invaluable. + . + . Basically, the chip has 4 banks of registers ( 0 to 3 ), which + . are accessed by writing a number into the BANK_SELECT register + . ( I also use a SMC_SELECT_BANK macro for this ). + . + . The banks are configured so that for most purposes, bank 2 is all + . that is needed for simple run time tasks. + -----------------------------------------------------------------------*/ + +/* + . Bank Select Register: + . + . yyyy yyyy 0000 00xx + . xx = bank number + . yyyy yyyy = 0x33, for identification purposes. +*/ +#define BANK_SELECT 14 + +/* BANK 0 */ + +#define TCR 0 /* transmit control register */ +#define TCR_ENABLE 0x0001 /* if this is 1, we can transmit */ +#define TCR_FDUPLX 0x0800 /* receive packets sent out */ +#define TCR_STP_SQET 0x1000 /* stop transmitting if Signal quality error */ +#define TCR_MON_CNS 0x0400 /* monitors the carrier status */ +#define TCR_PAD_ENABLE 0x0080 /* pads short packets to 64 bytes */ + +#define TCR_CLEAR 0 /* do NOTHING */ +/* the normal settings for the TCR register : */ +/* QUESTION: do I want to enable padding of short packets ? */ +#define TCR_NORMAL TCR_ENABLE + + +#define EPH_STATUS 2 +#define ES_LINK_OK 0x4000 /* is the link integrity ok ? */ + +#define RCR 4 +#define RCR_SOFTRESET 0x8000 /* resets the chip */ +#define RCR_STRIP_CRC 0x200 /* strips CRC */ +#define RCR_ENABLE 0x100 /* IFF this is set, we can receive packets */ +#define RCR_ALMUL 0x4 /* receive all multicast packets */ +#define RCR_PROMISC 0x2 /* enable promiscuous mode */ + +/* the normal settings for the RCR register : */ +#define RCR_NORMAL (RCR_STRIP_CRC | RCR_ENABLE) +#define RCR_CLEAR 0x0 /* set it to a base state */ + +#define COUNTER 6 +#define MIR 8 +#define MCR 10 +/* 12 is reserved */ + +/* BANK 1 */ +#define CONFIG 0 +#define CFG_AUI_SELECT 0x100 +#define BASE 2 +#define ADDR0 4 +#define ADDR1 6 +#define ADDR2 8 +#define GENERAL 10 +#define CONTROL 12 +#define CTL_POWERDOWN 0x2000 +#define CTL_LE_ENABLE 0x80 +#define CTL_CR_ENABLE 0x40 +#define CTL_TE_ENABLE 0x0020 +#define CTL_AUTO_RELEASE 0x0800 +#define CTL_EPROM_ACCESS 0x0003 /* high if Eprom is being read */ + +/* BANK 2 */ +#define MMU_CMD 0 +#define MC_BUSY 1 /* only readable bit in the register */ +#define MC_NOP 0 +#define MC_ALLOC 0x20 /* or with number of 256 byte packets */ +#define MC_RESET 0x40 +#define MC_REMOVE 0x60 /* remove the current rx packet */ +#define MC_RELEASE 0x80 /* remove and release the current rx packet */ +#define MC_FREEPKT 0xA0 /* Release packet in PNR register */ +#define MC_ENQUEUE 0xC0 /* Enqueue the packet for transmit */ + +#define PNR_ARR 2 +#define FIFO_PORTS 4 + +#define FP_RXEMPTY 0x8000 +#define FP_TXEMPTY 0x80 + +#define POINTER 6 +#define PTR_READ 0x2000 +#define PTR_RCV 0x8000 +#define PTR_AUTOINC 0x4000 +#define PTR_AUTO_INC 0x0040 + +#define DATA_1 8 +#define DATA_2 10 +#define INTERRUPT 12 + +#define INT_MASK 13 +#define IM_RCV_INT 0x1 +#define IM_TX_INT 0x2 +#define IM_TX_EMPTY_INT 0x4 +#define IM_ALLOC_INT 0x8 +#define IM_RX_OVRN_INT 0x10 +#define IM_EPH_INT 0x20 +#define IM_ERCV_INT 0x40 /* not on SMC9192 */ + +/* BANK 3 */ +#define MULTICAST1 0 +#define MULTICAST2 2 +#define MULTICAST3 4 +#define MULTICAST4 6 +#define MGMT 8 +#define REVISION 10 /* ( hi: chip id low: rev # ) */ + + +/* this is NOT on SMC9192 */ +#define ERCV 12 + +#define CHIP_9190 3 +#define CHIP_9194 4 +#define CHIP_9195 5 +#define CHIP_91100 7 + +static const char * chip_ids[ 15 ] = { + NULL, NULL, NULL, + /* 3 */ "SMC91C90/91C92", + /* 4 */ "SMC91C94", + /* 5 */ "SMC91C95", + NULL, + /* 7 */ "SMC91C100", + /* 8 */ "SMC91C100FD", + NULL, NULL, NULL, + NULL, NULL, NULL}; + +/* + . Transmit status bits +*/ +#define TS_SUCCESS 0x0001 +#define TS_LOSTCAR 0x0400 +#define TS_LATCOL 0x0200 +#define TS_16COL 0x0010 + +/* + . Receive status bits +*/ +#define RS_ALGNERR 0x8000 +#define RS_BADCRC 0x2000 +#define RS_ODDFRAME 0x1000 +#define RS_TOOLONG 0x0800 +#define RS_TOOSHORT 0x0400 +#define RS_MULTICAST 0x0001 +#define RS_ERRORS (RS_ALGNERR | RS_BADCRC | RS_TOOLONG | RS_TOOSHORT) + +static const char * interfaces[ 2 ] = { "TP", "AUI" }; + +/*------------------------------------------------------------------------- + . I define some macros to make it easier to do somewhat common + . or slightly complicated, repeated tasks. + --------------------------------------------------------------------------*/ + +/* select a register bank, 0 to 3 */ + +#define SMC_SELECT_BANK(x) { outw( x, ioaddr + BANK_SELECT ); } + +/* define a small delay for the reset */ +#define SMC_DELAY() { inw( ioaddr + RCR );\ + inw( ioaddr + RCR );\ + inw( ioaddr + RCR ); } + +/* this enables an interrupt in the interrupt mask register */ +#define SMC_ENABLE_INT(x) {\ + unsigned char mask;\ + SMC_SELECT_BANK(2);\ + mask = inb( ioaddr + INT_MASK );\ + mask |= (x);\ + outb( mask, ioaddr + INT_MASK ); \ +} + +/* this disables an interrupt from the interrupt mask register */ + +#define SMC_DISABLE_INT(x) {\ + unsigned char mask;\ + SMC_SELECT_BANK(2);\ + mask = inb( ioaddr + INT_MASK );\ + mask &= ~(x);\ + outb( mask, ioaddr + INT_MASK ); \ +} + +/*---------------------------------------------------------------------- + . Define the interrupts that I want to receive from the card + . + . I want: + . IM_EPH_INT, for nasty errors + . IM_RCV_INT, for happy received packets + . IM_RX_OVRN_INT, because I have to kick the receiver + --------------------------------------------------------------------------*/ +#define SMC_INTERRUPT_MASK (IM_EPH_INT | IM_RX_OVRN_INT | IM_RCV_INT) + +#endif /* _SMC_9194_H_ */ + diff --git a/drivers/net/ethernet/smsc/smc91c92_cs.c b/drivers/net/ethernet/smsc/smc91c92_cs.c new file mode 100644 index 000000000000..cffbc0373fa9 --- /dev/null +++ b/drivers/net/ethernet/smsc/smc91c92_cs.c @@ -0,0 +1,2070 @@ +/*====================================================================== + + A PCMCIA ethernet driver for SMC91c92-based cards. + + This driver supports Megahertz PCMCIA ethernet cards; and + Megahertz, Motorola, Ositech, and Psion Dacom ethernet/modem + multifunction cards. + + Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net + + smc91c92_cs.c 1.122 2002/10/25 06:26:39 + + This driver contains code written by Donald Becker + (becker@scyld.com), Rowan Hughes (x-csrdh@jcu.edu.au), + David Hinds (dahinds@users.sourceforge.net), and Erik Stahlman + (erik@vt.edu). Donald wrote the SMC 91c92 code using parts of + Erik's SMC 91c94 driver. Rowan wrote a similar driver, and I've + incorporated some parts of his driver here. I (Dave) wrote most + of the PCMCIA glue code, and the Ositech support code. Kelly + Stephens (kstephen@holli.com) added support for the Motorola + Mariner, with help from Allen Brost. + + This software may be used and distributed according to the terms of + the GNU General Public License, incorporated herein by reference. + +======================================================================*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/crc32.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/jiffies.h> +#include <linux/firmware.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/ss.h> + +#include <asm/io.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +/*====================================================================*/ + +static const char *if_names[] = { "auto", "10baseT", "10base2"}; + +/* Firmware name */ +#define FIRMWARE_NAME "ositech/Xilinx7OD.bin" + +/* Module parameters */ + +MODULE_DESCRIPTION("SMC 91c92 series PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(FIRMWARE_NAME); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +/* + Transceiver/media type. + 0 = auto + 1 = 10baseT (and autoselect if #define AUTOSELECT), + 2 = AUI/10base2, +*/ +INT_MODULE_PARM(if_port, 0); + + +#define DRV_NAME "smc91c92_cs" +#define DRV_VERSION "1.123" + +/*====================================================================*/ + +/* Operational parameter that usually are not changed. */ + +/* Time in jiffies before concluding Tx hung */ +#define TX_TIMEOUT ((400*HZ)/1000) + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +#define INTR_WORK 4 + +/* Times to check the check the chip before concluding that it doesn't + currently have room for another Tx packet. */ +#define MEMORY_WAIT_TIME 8 + +struct smc_private { + struct pcmcia_device *p_dev; + spinlock_t lock; + u_short manfid; + u_short cardid; + + struct sk_buff *saved_skb; + int packets_waiting; + void __iomem *base; + u_short cfg; + struct timer_list media; + int watchdog, tx_err; + u_short media_status; + u_short fast_poll; + u_short link_status; + struct mii_if_info mii_if; + int duplex; + int rx_ovrn; +}; + +/* Special definitions for Megahertz multifunction cards */ +#define MEGAHERTZ_ISR 0x0380 + +/* Special function registers for Motorola Mariner */ +#define MOT_LAN 0x0000 +#define MOT_UART 0x0020 +#define MOT_EEPROM 0x20 + +#define MOT_NORMAL \ +(COR_LEVEL_REQ | COR_FUNC_ENA | COR_ADDR_DECODE | COR_IREQ_ENA) + +/* Special function registers for Ositech cards */ +#define OSITECH_AUI_CTL 0x0c +#define OSITECH_PWRDOWN 0x0d +#define OSITECH_RESET 0x0e +#define OSITECH_ISR 0x0f +#define OSITECH_AUI_PWR 0x0c +#define OSITECH_RESET_ISR 0x0e + +#define OSI_AUI_PWR 0x40 +#define OSI_LAN_PWRDOWN 0x02 +#define OSI_MODEM_PWRDOWN 0x01 +#define OSI_LAN_RESET 0x02 +#define OSI_MODEM_RESET 0x01 + +/* Symbolic constants for the SMC91c9* series chips, from Erik Stahlman. */ +#define BANK_SELECT 14 /* Window select register. */ +#define SMC_SELECT_BANK(x) { outw(x, ioaddr + BANK_SELECT); } + +/* Bank 0 registers. */ +#define TCR 0 /* transmit control register */ +#define TCR_CLEAR 0 /* do NOTHING */ +#define TCR_ENABLE 0x0001 /* if this is 1, we can transmit */ +#define TCR_PAD_EN 0x0080 /* pads short packets to 64 bytes */ +#define TCR_MONCSN 0x0400 /* Monitor Carrier. */ +#define TCR_FDUPLX 0x0800 /* Full duplex mode. */ +#define TCR_NORMAL TCR_ENABLE | TCR_PAD_EN + +#define EPH 2 /* Ethernet Protocol Handler report. */ +#define EPH_TX_SUC 0x0001 +#define EPH_SNGLCOL 0x0002 +#define EPH_MULCOL 0x0004 +#define EPH_LTX_MULT 0x0008 +#define EPH_16COL 0x0010 +#define EPH_SQET 0x0020 +#define EPH_LTX_BRD 0x0040 +#define EPH_TX_DEFR 0x0080 +#define EPH_LAT_COL 0x0200 +#define EPH_LOST_CAR 0x0400 +#define EPH_EXC_DEF 0x0800 +#define EPH_CTR_ROL 0x1000 +#define EPH_RX_OVRN 0x2000 +#define EPH_LINK_OK 0x4000 +#define EPH_TX_UNRN 0x8000 +#define MEMINFO 8 /* Memory Information Register */ +#define MEMCFG 10 /* Memory Configuration Register */ + +/* Bank 1 registers. */ +#define CONFIG 0 +#define CFG_MII_SELECT 0x8000 /* 91C100 only */ +#define CFG_NO_WAIT 0x1000 +#define CFG_FULL_STEP 0x0400 +#define CFG_SET_SQLCH 0x0200 +#define CFG_AUI_SELECT 0x0100 +#define CFG_16BIT 0x0080 +#define CFG_DIS_LINK 0x0040 +#define CFG_STATIC 0x0030 +#define CFG_IRQ_SEL_1 0x0004 +#define CFG_IRQ_SEL_0 0x0002 +#define BASE_ADDR 2 +#define ADDR0 4 +#define GENERAL 10 +#define CONTROL 12 +#define CTL_STORE 0x0001 +#define CTL_RELOAD 0x0002 +#define CTL_EE_SELECT 0x0004 +#define CTL_TE_ENABLE 0x0020 +#define CTL_CR_ENABLE 0x0040 +#define CTL_LE_ENABLE 0x0080 +#define CTL_AUTO_RELEASE 0x0800 +#define CTL_POWERDOWN 0x2000 + +/* Bank 2 registers. */ +#define MMU_CMD 0 +#define MC_ALLOC 0x20 /* or with number of 256 byte packets */ +#define MC_RESET 0x40 +#define MC_RELEASE 0x80 /* remove and release the current rx packet */ +#define MC_FREEPKT 0xA0 /* Release packet in PNR register */ +#define MC_ENQUEUE 0xC0 /* Enqueue the packet for transmit */ +#define PNR_ARR 2 +#define FIFO_PORTS 4 +#define FP_RXEMPTY 0x8000 +#define POINTER 6 +#define PTR_AUTO_INC 0x0040 +#define PTR_READ 0x2000 +#define PTR_AUTOINC 0x4000 +#define PTR_RCV 0x8000 +#define DATA_1 8 +#define INTERRUPT 12 +#define IM_RCV_INT 0x1 +#define IM_TX_INT 0x2 +#define IM_TX_EMPTY_INT 0x4 +#define IM_ALLOC_INT 0x8 +#define IM_RX_OVRN_INT 0x10 +#define IM_EPH_INT 0x20 + +#define RCR 4 +enum RxCfg { RxAllMulti = 0x0004, RxPromisc = 0x0002, + RxEnable = 0x0100, RxStripCRC = 0x0200}; +#define RCR_SOFTRESET 0x8000 /* resets the chip */ +#define RCR_STRIP_CRC 0x200 /* strips CRC */ +#define RCR_ENABLE 0x100 /* IFF this is set, we can receive packets */ +#define RCR_ALMUL 0x4 /* receive all multicast packets */ +#define RCR_PROMISC 0x2 /* enable promiscuous mode */ + +/* the normal settings for the RCR register : */ +#define RCR_NORMAL (RCR_STRIP_CRC | RCR_ENABLE) +#define RCR_CLEAR 0x0 /* set it to a base state */ +#define COUNTER 6 + +/* BANK 3 -- not the same values as in smc9194! */ +#define MULTICAST0 0 +#define MULTICAST2 2 +#define MULTICAST4 4 +#define MULTICAST6 6 +#define MGMT 8 +#define REVISION 0x0a + +/* Transmit status bits. */ +#define TS_SUCCESS 0x0001 +#define TS_16COL 0x0010 +#define TS_LATCOL 0x0200 +#define TS_LOSTCAR 0x0400 + +/* Receive status bits. */ +#define RS_ALGNERR 0x8000 +#define RS_BADCRC 0x2000 +#define RS_ODDFRAME 0x1000 +#define RS_TOOLONG 0x0800 +#define RS_TOOSHORT 0x0400 +#define RS_MULTICAST 0x0001 +#define RS_ERRORS (RS_ALGNERR | RS_BADCRC | RS_TOOLONG | RS_TOOSHORT) + +#define set_bits(v, p) outw(inw(p)|(v), (p)) +#define mask_bits(v, p) outw(inw(p)&(v), (p)) + +/*====================================================================*/ + +static void smc91c92_detach(struct pcmcia_device *p_dev); +static int smc91c92_config(struct pcmcia_device *link); +static void smc91c92_release(struct pcmcia_device *link); + +static int smc_open(struct net_device *dev); +static int smc_close(struct net_device *dev); +static int smc_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static void smc_tx_timeout(struct net_device *dev); +static netdev_tx_t smc_start_xmit(struct sk_buff *skb, + struct net_device *dev); +static irqreturn_t smc_interrupt(int irq, void *dev_id); +static void smc_rx(struct net_device *dev); +static void set_rx_mode(struct net_device *dev); +static int s9k_config(struct net_device *dev, struct ifmap *map); +static void smc_set_xcvr(struct net_device *dev, int if_port); +static void smc_reset(struct net_device *dev); +static void media_check(u_long arg); +static void mdio_sync(unsigned int addr); +static int mdio_read(struct net_device *dev, int phy_id, int loc); +static void mdio_write(struct net_device *dev, int phy_id, int loc, int value); +static int smc_link_ok(struct net_device *dev); +static const struct ethtool_ops ethtool_ops; + +static const struct net_device_ops smc_netdev_ops = { + .ndo_open = smc_open, + .ndo_stop = smc_close, + .ndo_start_xmit = smc_start_xmit, + .ndo_tx_timeout = smc_tx_timeout, + .ndo_set_config = s9k_config, + .ndo_set_multicast_list = set_rx_mode, + .ndo_do_ioctl = smc_ioctl, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static int smc91c92_probe(struct pcmcia_device *link) +{ + struct smc_private *smc; + struct net_device *dev; + + dev_dbg(&link->dev, "smc91c92_attach()\n"); + + /* Create new ethernet device */ + dev = alloc_etherdev(sizeof(struct smc_private)); + if (!dev) + return -ENOMEM; + smc = netdev_priv(dev); + smc->p_dev = link; + link->priv = dev; + + spin_lock_init(&smc->lock); + + /* The SMC91c92-specific entries in the device structure. */ + dev->netdev_ops = &smc_netdev_ops; + SET_ETHTOOL_OPS(dev, ðtool_ops); + dev->watchdog_timeo = TX_TIMEOUT; + + smc->mii_if.dev = dev; + smc->mii_if.mdio_read = mdio_read; + smc->mii_if.mdio_write = mdio_write; + smc->mii_if.phy_id_mask = 0x1f; + smc->mii_if.reg_num_mask = 0x1f; + + return smc91c92_config(link); +} /* smc91c92_attach */ + +static void smc91c92_detach(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + + dev_dbg(&link->dev, "smc91c92_detach\n"); + + unregister_netdev(dev); + + smc91c92_release(link); + + free_netdev(dev); +} /* smc91c92_detach */ + +/*====================================================================*/ + +static int cvt_ascii_address(struct net_device *dev, char *s) +{ + int i, j, da, c; + + if (strlen(s) != 12) + return -1; + for (i = 0; i < 6; i++) { + da = 0; + for (j = 0; j < 2; j++) { + c = *s++; + da <<= 4; + da += ((c >= '0') && (c <= '9')) ? + (c - '0') : ((c & 0x0f) + 9); + } + dev->dev_addr[i] = da; + } + return 0; +} + +/*==================================================================== + + Configuration stuff for Megahertz cards + + mhz_3288_power() is used to power up a 3288's ethernet chip. + mhz_mfc_config() handles socket setup for multifunction (1144 + and 3288) cards. mhz_setup() gets a card's hardware ethernet + address. + +======================================================================*/ + +static int mhz_3288_power(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + u_char tmp; + + /* Read the ISR twice... */ + readb(smc->base+MEGAHERTZ_ISR); + udelay(5); + readb(smc->base+MEGAHERTZ_ISR); + + /* Pause 200ms... */ + mdelay(200); + + /* Now read and write the COR... */ + tmp = readb(smc->base + link->config_base + CISREG_COR); + udelay(5); + writeb(tmp, smc->base + link->config_base + CISREG_COR); + + return 0; +} + +static int mhz_mfc_config_check(struct pcmcia_device *p_dev, void *priv_data) +{ + int k; + p_dev->io_lines = 16; + p_dev->resource[1]->start = p_dev->resource[0]->start; + p_dev->resource[1]->end = 8; + p_dev->resource[1]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8; + p_dev->resource[0]->end = 16; + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; + for (k = 0; k < 0x400; k += 0x10) { + if (k & 0x80) + continue; + p_dev->resource[0]->start = k ^ 0x300; + if (!pcmcia_request_io(p_dev)) + return 0; + } + return -ENODEV; +} + +static int mhz_mfc_config(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + unsigned int offset; + int i; + + link->config_flags |= CONF_ENABLE_SPKR | CONF_ENABLE_IRQ | + CONF_AUTO_SET_IO; + + /* The Megahertz combo cards have modem-like CIS entries, so + we have to explicitly try a bunch of port combinations. */ + if (pcmcia_loop_config(link, mhz_mfc_config_check, NULL)) + return -ENODEV; + + dev->base_addr = link->resource[0]->start; + + /* Allocate a memory window, for accessing the ISR */ + link->resource[2]->flags = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + link->resource[2]->start = link->resource[2]->end = 0; + i = pcmcia_request_window(link, link->resource[2], 0); + if (i != 0) + return -ENODEV; + + smc->base = ioremap(link->resource[2]->start, + resource_size(link->resource[2])); + offset = (smc->manfid == MANFID_MOTOROLA) ? link->config_base : 0; + i = pcmcia_map_mem_page(link, link->resource[2], offset); + if ((i == 0) && + (smc->manfid == MANFID_MEGAHERTZ) && + (smc->cardid == PRODID_MEGAHERTZ_EM3288)) + mhz_3288_power(link); + + return 0; +} + +static int pcmcia_get_versmac(struct pcmcia_device *p_dev, + tuple_t *tuple, + void *priv) +{ + struct net_device *dev = priv; + cisparse_t parse; + u8 *buf; + + if (pcmcia_parse_tuple(tuple, &parse)) + return -EINVAL; + + buf = parse.version_1.str + parse.version_1.ofs[3]; + + if ((parse.version_1.ns > 3) && (cvt_ascii_address(dev, buf) == 0)) + return 0; + + return -EINVAL; +}; + +static int mhz_setup(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + size_t len; + u8 *buf; + int rc; + + /* Read the station address from the CIS. It is stored as the last + (fourth) string in the Version 1 Version/ID tuple. */ + if ((link->prod_id[3]) && + (cvt_ascii_address(dev, link->prod_id[3]) == 0)) + return 0; + + /* Workarounds for broken cards start here. */ + /* Ugh -- the EM1144 card has two VERS_1 tuples!?! */ + if (!pcmcia_loop_tuple(link, CISTPL_VERS_1, pcmcia_get_versmac, dev)) + return 0; + + /* Another possibility: for the EM3288, in a special tuple */ + rc = -1; + len = pcmcia_get_tuple(link, 0x81, &buf); + if (buf && len >= 13) { + buf[12] = '\0'; + if (cvt_ascii_address(dev, buf) == 0) + rc = 0; + } + kfree(buf); + + return rc; +}; + +/*====================================================================== + + Configuration stuff for the Motorola Mariner + + mot_config() writes directly to the Mariner configuration + registers because the CIS is just bogus. + +======================================================================*/ + +static void mot_config(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + unsigned int iouart = link->resource[1]->start; + + /* Set UART base address and force map with COR bit 1 */ + writeb(iouart & 0xff, smc->base + MOT_UART + CISREG_IOBASE_0); + writeb((iouart >> 8) & 0xff, smc->base + MOT_UART + CISREG_IOBASE_1); + writeb(MOT_NORMAL, smc->base + MOT_UART + CISREG_COR); + + /* Set SMC base address and force map with COR bit 1 */ + writeb(ioaddr & 0xff, smc->base + MOT_LAN + CISREG_IOBASE_0); + writeb((ioaddr >> 8) & 0xff, smc->base + MOT_LAN + CISREG_IOBASE_1); + writeb(MOT_NORMAL, smc->base + MOT_LAN + CISREG_COR); + + /* Wait for things to settle down */ + mdelay(100); +} + +static int mot_setup(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + unsigned int ioaddr = dev->base_addr; + int i, wait, loop; + u_int addr; + + /* Read Ethernet address from Serial EEPROM */ + + for (i = 0; i < 3; i++) { + SMC_SELECT_BANK(2); + outw(MOT_EEPROM + i, ioaddr + POINTER); + SMC_SELECT_BANK(1); + outw((CTL_RELOAD | CTL_EE_SELECT), ioaddr + CONTROL); + + for (loop = wait = 0; loop < 200; loop++) { + udelay(10); + wait = ((CTL_RELOAD | CTL_STORE) & inw(ioaddr + CONTROL)); + if (wait == 0) break; + } + + if (wait) + return -1; + + addr = inw(ioaddr + GENERAL); + dev->dev_addr[2*i] = addr & 0xff; + dev->dev_addr[2*i+1] = (addr >> 8) & 0xff; + } + + return 0; +} + +/*====================================================================*/ + +static int smc_configcheck(struct pcmcia_device *p_dev, void *priv_data) +{ + p_dev->resource[0]->end = 16; + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; + + return pcmcia_request_io(p_dev); +} + +static int smc_config(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + int i; + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + i = pcmcia_loop_config(link, smc_configcheck, NULL); + if (!i) + dev->base_addr = link->resource[0]->start; + + return i; +} + + +static int smc_setup(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + + /* Check for a LAN function extension tuple */ + if (!pcmcia_get_mac_from_cis(link, dev)) + return 0; + + /* Try the third string in the Version 1 Version/ID tuple. */ + if (link->prod_id[2]) { + if (cvt_ascii_address(dev, link->prod_id[2]) == 0) + return 0; + } + return -1; +} + +/*====================================================================*/ + +static int osi_config(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + static const unsigned int com[4] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 }; + int i, j; + + link->config_flags |= CONF_ENABLE_SPKR | CONF_ENABLE_IRQ; + link->resource[0]->end = 64; + link->resource[1]->flags |= IO_DATA_PATH_WIDTH_8; + link->resource[1]->end = 8; + + /* Enable Hard Decode, LAN, Modem */ + link->io_lines = 16; + link->config_index = 0x23; + + for (i = j = 0; j < 4; j++) { + link->resource[1]->start = com[j]; + i = pcmcia_request_io(link); + if (i == 0) + break; + } + if (i != 0) { + /* Fallback: turn off hard decode */ + link->config_index = 0x03; + link->resource[1]->end = 0; + i = pcmcia_request_io(link); + } + dev->base_addr = link->resource[0]->start + 0x10; + return i; +} + +static int osi_load_firmware(struct pcmcia_device *link) +{ + const struct firmware *fw; + int i, err; + + err = request_firmware(&fw, FIRMWARE_NAME, &link->dev); + if (err) { + pr_err("Failed to load firmware \"%s\"\n", FIRMWARE_NAME); + return err; + } + + /* Download the Seven of Diamonds firmware */ + for (i = 0; i < fw->size; i++) { + outb(fw->data[i], link->resource[0]->start + 2); + udelay(50); + } + release_firmware(fw); + return err; +} + +static int pcmcia_osi_mac(struct pcmcia_device *p_dev, + tuple_t *tuple, + void *priv) +{ + struct net_device *dev = priv; + int i; + + if (tuple->TupleDataLen < 8) + return -EINVAL; + if (tuple->TupleData[0] != 0x04) + return -EINVAL; + for (i = 0; i < 6; i++) + dev->dev_addr[i] = tuple->TupleData[i+2]; + return 0; +}; + + +static int osi_setup(struct pcmcia_device *link, u_short manfid, u_short cardid) +{ + struct net_device *dev = link->priv; + int rc; + + /* Read the station address from tuple 0x90, subtuple 0x04 */ + if (pcmcia_loop_tuple(link, 0x90, pcmcia_osi_mac, dev)) + return -1; + + if (((manfid == MANFID_OSITECH) && + (cardid == PRODID_OSITECH_SEVEN)) || + ((manfid == MANFID_PSION) && + (cardid == PRODID_PSION_NET100))) { + rc = osi_load_firmware(link); + if (rc) + return rc; + } else if (manfid == MANFID_OSITECH) { + /* Make sure both functions are powered up */ + set_bits(0x300, link->resource[0]->start + OSITECH_AUI_PWR); + /* Now, turn on the interrupt for both card functions */ + set_bits(0x300, link->resource[0]->start + OSITECH_RESET_ISR); + dev_dbg(&link->dev, "AUI/PWR: %4.4x RESET/ISR: %4.4x\n", + inw(link->resource[0]->start + OSITECH_AUI_PWR), + inw(link->resource[0]->start + OSITECH_RESET_ISR)); + } + return 0; +} + +static int smc91c92_suspend(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + + if (link->open) + netif_device_detach(dev); + + return 0; +} + +static int smc91c92_resume(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + int i; + + if ((smc->manfid == MANFID_MEGAHERTZ) && + (smc->cardid == PRODID_MEGAHERTZ_EM3288)) + mhz_3288_power(link); + if (smc->manfid == MANFID_MOTOROLA) + mot_config(link); + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) { + /* Power up the card and enable interrupts */ + set_bits(0x0300, dev->base_addr-0x10+OSITECH_AUI_PWR); + set_bits(0x0300, dev->base_addr-0x10+OSITECH_RESET_ISR); + } + if (((smc->manfid == MANFID_OSITECH) && + (smc->cardid == PRODID_OSITECH_SEVEN)) || + ((smc->manfid == MANFID_PSION) && + (smc->cardid == PRODID_PSION_NET100))) { + i = osi_load_firmware(link); + if (i) { + pr_err("smc91c92_cs: Failed to load firmware\n"); + return i; + } + } + if (link->open) { + smc_reset(dev); + netif_device_attach(dev); + } + + return 0; +} + + +/*====================================================================== + + This verifies that the chip is some SMC91cXX variant, and returns + the revision code if successful. Otherwise, it returns -ENODEV. + +======================================================================*/ + +static int check_sig(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + unsigned int ioaddr = dev->base_addr; + int width; + u_short s; + + SMC_SELECT_BANK(1); + if (inw(ioaddr + BANK_SELECT) >> 8 != 0x33) { + /* Try powering up the chip */ + outw(0, ioaddr + CONTROL); + mdelay(55); + } + + /* Try setting bus width */ + width = (link->resource[0]->flags == IO_DATA_PATH_WIDTH_AUTO); + s = inb(ioaddr + CONFIG); + if (width) + s |= CFG_16BIT; + else + s &= ~CFG_16BIT; + outb(s, ioaddr + CONFIG); + + /* Check Base Address Register to make sure bus width is OK */ + s = inw(ioaddr + BASE_ADDR); + if ((inw(ioaddr + BANK_SELECT) >> 8 == 0x33) && + ((s >> 8) != (s & 0xff))) { + SMC_SELECT_BANK(3); + s = inw(ioaddr + REVISION); + return s & 0xff; + } + + if (width) { + pr_info("using 8-bit IO window\n"); + + smc91c92_suspend(link); + pcmcia_fixup_iowidth(link); + smc91c92_resume(link); + return check_sig(link); + } + return -ENODEV; +} + +static int smc91c92_config(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + char *name; + int i, rev, j = 0; + unsigned int ioaddr; + u_long mir; + + dev_dbg(&link->dev, "smc91c92_config\n"); + + smc->manfid = link->manf_id; + smc->cardid = link->card_id; + + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) { + i = osi_config(link); + } else if ((smc->manfid == MANFID_MOTOROLA) || + ((smc->manfid == MANFID_MEGAHERTZ) && + ((smc->cardid == PRODID_MEGAHERTZ_VARIOUS) || + (smc->cardid == PRODID_MEGAHERTZ_EM3288)))) { + i = mhz_mfc_config(link); + } else { + i = smc_config(link); + } + if (i) + goto config_failed; + + i = pcmcia_request_irq(link, smc_interrupt); + if (i) + goto config_failed; + i = pcmcia_enable_device(link); + if (i) + goto config_failed; + + if (smc->manfid == MANFID_MOTOROLA) + mot_config(link); + + dev->irq = link->irq; + + if ((if_port >= 0) && (if_port <= 2)) + dev->if_port = if_port; + else + dev_notice(&link->dev, "invalid if_port requested\n"); + + switch (smc->manfid) { + case MANFID_OSITECH: + case MANFID_PSION: + i = osi_setup(link, smc->manfid, smc->cardid); break; + case MANFID_SMC: + case MANFID_NEW_MEDIA: + i = smc_setup(link); break; + case 0x128: /* For broken Megahertz cards */ + case MANFID_MEGAHERTZ: + i = mhz_setup(link); break; + case MANFID_MOTOROLA: + default: /* get the hw address from EEPROM */ + i = mot_setup(link); break; + } + + if (i != 0) { + dev_notice(&link->dev, "Unable to find hardware address.\n"); + goto config_failed; + } + + smc->duplex = 0; + smc->rx_ovrn = 0; + + rev = check_sig(link); + name = "???"; + if (rev > 0) + switch (rev >> 4) { + case 3: name = "92"; break; + case 4: name = ((rev & 15) >= 6) ? "96" : "94"; break; + case 5: name = "95"; break; + case 7: name = "100"; break; + case 8: name = "100-FD"; break; + case 9: name = "110"; break; + } + + ioaddr = dev->base_addr; + if (rev > 0) { + u_long mcr; + SMC_SELECT_BANK(0); + mir = inw(ioaddr + MEMINFO) & 0xff; + if (mir == 0xff) mir++; + /* Get scale factor for memory size */ + mcr = ((rev >> 4) > 3) ? inw(ioaddr + MEMCFG) : 0x0200; + mir *= 128 * (1<<((mcr >> 9) & 7)); + SMC_SELECT_BANK(1); + smc->cfg = inw(ioaddr + CONFIG) & ~CFG_AUI_SELECT; + smc->cfg |= CFG_NO_WAIT | CFG_16BIT | CFG_STATIC; + if (smc->manfid == MANFID_OSITECH) + smc->cfg |= CFG_IRQ_SEL_1 | CFG_IRQ_SEL_0; + if ((rev >> 4) >= 7) + smc->cfg |= CFG_MII_SELECT; + } else + mir = 0; + + if (smc->cfg & CFG_MII_SELECT) { + SMC_SELECT_BANK(3); + + for (i = 0; i < 32; i++) { + j = mdio_read(dev, i, 1); + if ((j != 0) && (j != 0xffff)) break; + } + smc->mii_if.phy_id = (i < 32) ? i : -1; + + SMC_SELECT_BANK(0); + } + + SET_NETDEV_DEV(dev, &link->dev); + + if (register_netdev(dev) != 0) { + dev_err(&link->dev, "register_netdev() failed\n"); + goto config_undo; + } + + netdev_info(dev, "smc91c%s rev %d: io %#3lx, irq %d, hw_addr %pM\n", + name, (rev & 0x0f), dev->base_addr, dev->irq, dev->dev_addr); + + if (rev > 0) { + if (mir & 0x3ff) + netdev_info(dev, " %lu byte", mir); + else + netdev_info(dev, " %lu kb", mir>>10); + pr_cont(" buffer, %s xcvr\n", + (smc->cfg & CFG_MII_SELECT) ? "MII" : if_names[dev->if_port]); + } + + if (smc->cfg & CFG_MII_SELECT) { + if (smc->mii_if.phy_id != -1) { + netdev_dbg(dev, " MII transceiver at index %d, status %x\n", + smc->mii_if.phy_id, j); + } else { + netdev_notice(dev, " No MII transceivers found!\n"); + } + } + return 0; + +config_undo: + unregister_netdev(dev); +config_failed: + smc91c92_release(link); + free_netdev(dev); + return -ENODEV; +} /* smc91c92_config */ + +static void smc91c92_release(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "smc91c92_release\n"); + if (link->resource[2]->end) { + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + iounmap(smc->base); + } + pcmcia_disable_device(link); +} + +/*====================================================================== + + MII interface support for SMC91cXX based cards +======================================================================*/ + +#define MDIO_SHIFT_CLK 0x04 +#define MDIO_DATA_OUT 0x01 +#define MDIO_DIR_WRITE 0x08 +#define MDIO_DATA_WRITE0 (MDIO_DIR_WRITE) +#define MDIO_DATA_WRITE1 (MDIO_DIR_WRITE | MDIO_DATA_OUT) +#define MDIO_DATA_READ 0x02 + +static void mdio_sync(unsigned int addr) +{ + int bits; + for (bits = 0; bits < 32; bits++) { + outb(MDIO_DATA_WRITE1, addr); + outb(MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, addr); + } +} + +static int mdio_read(struct net_device *dev, int phy_id, int loc) +{ + unsigned int addr = dev->base_addr + MGMT; + u_int cmd = (0x06<<10)|(phy_id<<5)|loc; + int i, retval = 0; + + mdio_sync(addr); + for (i = 13; i >= 0; i--) { + int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outb(dat, addr); + outb(dat | MDIO_SHIFT_CLK, addr); + } + for (i = 19; i > 0; i--) { + outb(0, addr); + retval = (retval << 1) | ((inb(addr) & MDIO_DATA_READ) != 0); + outb(MDIO_SHIFT_CLK, addr); + } + return (retval>>1) & 0xffff; +} + +static void mdio_write(struct net_device *dev, int phy_id, int loc, int value) +{ + unsigned int addr = dev->base_addr + MGMT; + u_int cmd = (0x05<<28)|(phy_id<<23)|(loc<<18)|(1<<17)|value; + int i; + + mdio_sync(addr); + for (i = 31; i >= 0; i--) { + int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outb(dat, addr); + outb(dat | MDIO_SHIFT_CLK, addr); + } + for (i = 1; i >= 0; i--) { + outb(0, addr); + outb(MDIO_SHIFT_CLK, addr); + } +} + +/*====================================================================== + + The driver core code, most of which should be common with a + non-PCMCIA implementation. + +======================================================================*/ + +#ifdef PCMCIA_DEBUG +static void smc_dump(struct net_device *dev) +{ + unsigned int ioaddr = dev->base_addr; + u_short i, w, save; + save = inw(ioaddr + BANK_SELECT); + for (w = 0; w < 4; w++) { + SMC_SELECT_BANK(w); + netdev_printk(KERN_DEBUG, dev, "bank %d: ", w); + for (i = 0; i < 14; i += 2) + pr_cont(" %04x", inw(ioaddr + i)); + pr_cont("\n"); + } + outw(save, ioaddr + BANK_SELECT); +} +#endif + +static int smc_open(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + struct pcmcia_device *link = smc->p_dev; + + dev_dbg(&link->dev, "%s: smc_open(%p), ID/Window %4.4x.\n", + dev->name, dev, inw(dev->base_addr + BANK_SELECT)); +#ifdef PCMCIA_DEBUG + smc_dump(dev); +#endif + + /* Check that the PCMCIA card is still here. */ + if (!pcmcia_dev_present(link)) + return -ENODEV; + /* Physical device present signature. */ + if (check_sig(link) < 0) { + netdev_info(dev, "Yikes! Bad chip signature!\n"); + return -ENODEV; + } + link->open++; + + netif_start_queue(dev); + smc->saved_skb = NULL; + smc->packets_waiting = 0; + + smc_reset(dev); + init_timer(&smc->media); + smc->media.function = media_check; + smc->media.data = (u_long) dev; + smc->media.expires = jiffies + HZ; + add_timer(&smc->media); + + return 0; +} /* smc_open */ + +/*====================================================================*/ + +static int smc_close(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + struct pcmcia_device *link = smc->p_dev; + unsigned int ioaddr = dev->base_addr; + + dev_dbg(&link->dev, "%s: smc_close(), status %4.4x.\n", + dev->name, inw(ioaddr + BANK_SELECT)); + + netif_stop_queue(dev); + + /* Shut off all interrupts, and turn off the Tx and Rx sections. + Don't bother to check for chip present. */ + SMC_SELECT_BANK(2); /* Nominally paranoia, but do no assume... */ + outw(0, ioaddr + INTERRUPT); + SMC_SELECT_BANK(0); + mask_bits(0xff00, ioaddr + RCR); + mask_bits(0xff00, ioaddr + TCR); + + /* Put the chip into power-down mode. */ + SMC_SELECT_BANK(1); + outw(CTL_POWERDOWN, ioaddr + CONTROL ); + + link->open--; + del_timer_sync(&smc->media); + + return 0; +} /* smc_close */ + +/*====================================================================== + + Transfer a packet to the hardware and trigger the packet send. + This may be called at either from either the Tx queue code + or the interrupt handler. + +======================================================================*/ + +static void smc_hardware_send_packet(struct net_device * dev) +{ + struct smc_private *smc = netdev_priv(dev); + struct sk_buff *skb = smc->saved_skb; + unsigned int ioaddr = dev->base_addr; + u_char packet_no; + + if (!skb) { + netdev_err(dev, "In XMIT with no packet to send\n"); + return; + } + + /* There should be a packet slot waiting. */ + packet_no = inw(ioaddr + PNR_ARR) >> 8; + if (packet_no & 0x80) { + /* If not, there is a hardware problem! Likely an ejected card. */ + netdev_warn(dev, "hardware Tx buffer allocation failed, status %#2.2x\n", + packet_no); + dev_kfree_skb_irq(skb); + smc->saved_skb = NULL; + netif_start_queue(dev); + return; + } + + dev->stats.tx_bytes += skb->len; + /* The card should use the just-allocated buffer. */ + outw(packet_no, ioaddr + PNR_ARR); + /* point to the beginning of the packet */ + outw(PTR_AUTOINC , ioaddr + POINTER); + + /* Send the packet length (+6 for status, length and ctl byte) + and the status word (set to zeros). */ + { + u_char *buf = skb->data; + u_int length = skb->len; /* The chip will pad to ethernet min. */ + + netdev_dbg(dev, "Trying to xmit packet of length %d\n", length); + + /* send the packet length: +6 for status word, length, and ctl */ + outw(0, ioaddr + DATA_1); + outw(length + 6, ioaddr + DATA_1); + outsw(ioaddr + DATA_1, buf, length >> 1); + + /* The odd last byte, if there is one, goes in the control word. */ + outw((length & 1) ? 0x2000 | buf[length-1] : 0, ioaddr + DATA_1); + } + + /* Enable the Tx interrupts, both Tx (TxErr) and TxEmpty. */ + outw(((IM_TX_INT|IM_TX_EMPTY_INT)<<8) | + (inw(ioaddr + INTERRUPT) & 0xff00), + ioaddr + INTERRUPT); + + /* The chip does the rest of the work. */ + outw(MC_ENQUEUE , ioaddr + MMU_CMD); + + smc->saved_skb = NULL; + dev_kfree_skb_irq(skb); + dev->trans_start = jiffies; + netif_start_queue(dev); +} + +/*====================================================================*/ + +static void smc_tx_timeout(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + + netdev_notice(dev, "transmit timed out, Tx_status %2.2x status %4.4x.\n", + inw(ioaddr)&0xff, inw(ioaddr + 2)); + dev->stats.tx_errors++; + smc_reset(dev); + dev->trans_start = jiffies; /* prevent tx timeout */ + smc->saved_skb = NULL; + netif_wake_queue(dev); +} + +static netdev_tx_t smc_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + u_short num_pages; + short time_out, ir; + unsigned long flags; + + netif_stop_queue(dev); + + netdev_dbg(dev, "smc_start_xmit(length = %d) called, status %04x\n", + skb->len, inw(ioaddr + 2)); + + if (smc->saved_skb) { + /* THIS SHOULD NEVER HAPPEN. */ + dev->stats.tx_aborted_errors++; + netdev_printk(KERN_DEBUG, dev, + "Internal error -- sent packet while busy\n"); + return NETDEV_TX_BUSY; + } + smc->saved_skb = skb; + + num_pages = skb->len >> 8; + + if (num_pages > 7) { + netdev_err(dev, "Far too big packet error: %d pages\n", num_pages); + dev_kfree_skb (skb); + smc->saved_skb = NULL; + dev->stats.tx_dropped++; + return NETDEV_TX_OK; /* Do not re-queue this packet. */ + } + /* A packet is now waiting. */ + smc->packets_waiting++; + + spin_lock_irqsave(&smc->lock, flags); + SMC_SELECT_BANK(2); /* Paranoia, we should always be in window 2 */ + + /* need MC_RESET to keep the memory consistent. errata? */ + if (smc->rx_ovrn) { + outw(MC_RESET, ioaddr + MMU_CMD); + smc->rx_ovrn = 0; + } + + /* Allocate the memory; send the packet now if we win. */ + outw(MC_ALLOC | num_pages, ioaddr + MMU_CMD); + for (time_out = MEMORY_WAIT_TIME; time_out >= 0; time_out--) { + ir = inw(ioaddr+INTERRUPT); + if (ir & IM_ALLOC_INT) { + /* Acknowledge the interrupt, send the packet. */ + outw((ir&0xff00) | IM_ALLOC_INT, ioaddr + INTERRUPT); + smc_hardware_send_packet(dev); /* Send the packet now.. */ + spin_unlock_irqrestore(&smc->lock, flags); + return NETDEV_TX_OK; + } + } + + /* Otherwise defer until the Tx-space-allocated interrupt. */ + pr_debug("%s: memory allocation deferred.\n", dev->name); + outw((IM_ALLOC_INT << 8) | (ir & 0xff00), ioaddr + INTERRUPT); + spin_unlock_irqrestore(&smc->lock, flags); + + return NETDEV_TX_OK; +} + +/*====================================================================== + + Handle a Tx anomalous event. Entered while in Window 2. + +======================================================================*/ + +static void smc_tx_err(struct net_device * dev) +{ + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + int saved_packet = inw(ioaddr + PNR_ARR) & 0xff; + int packet_no = inw(ioaddr + FIFO_PORTS) & 0x7f; + int tx_status; + + /* select this as the packet to read from */ + outw(packet_no, ioaddr + PNR_ARR); + + /* read the first word from this packet */ + outw(PTR_AUTOINC | PTR_READ | 0, ioaddr + POINTER); + + tx_status = inw(ioaddr + DATA_1); + + dev->stats.tx_errors++; + if (tx_status & TS_LOSTCAR) dev->stats.tx_carrier_errors++; + if (tx_status & TS_LATCOL) dev->stats.tx_window_errors++; + if (tx_status & TS_16COL) { + dev->stats.tx_aborted_errors++; + smc->tx_err++; + } + + if (tx_status & TS_SUCCESS) { + netdev_notice(dev, "Successful packet caused error interrupt?\n"); + } + /* re-enable transmit */ + SMC_SELECT_BANK(0); + outw(inw(ioaddr + TCR) | TCR_ENABLE | smc->duplex, ioaddr + TCR); + SMC_SELECT_BANK(2); + + outw(MC_FREEPKT, ioaddr + MMU_CMD); /* Free the packet memory. */ + + /* one less packet waiting for me */ + smc->packets_waiting--; + + outw(saved_packet, ioaddr + PNR_ARR); +} + +/*====================================================================*/ + +static void smc_eph_irq(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + u_short card_stats, ephs; + + SMC_SELECT_BANK(0); + ephs = inw(ioaddr + EPH); + pr_debug("%s: Ethernet protocol handler interrupt, status" + " %4.4x.\n", dev->name, ephs); + /* Could be a counter roll-over warning: update stats. */ + card_stats = inw(ioaddr + COUNTER); + /* single collisions */ + dev->stats.collisions += card_stats & 0xF; + card_stats >>= 4; + /* multiple collisions */ + dev->stats.collisions += card_stats & 0xF; +#if 0 /* These are for when linux supports these statistics */ + card_stats >>= 4; /* deferred */ + card_stats >>= 4; /* excess deferred */ +#endif + /* If we had a transmit error we must re-enable the transmitter. */ + outw(inw(ioaddr + TCR) | TCR_ENABLE | smc->duplex, ioaddr + TCR); + + /* Clear a link error interrupt. */ + SMC_SELECT_BANK(1); + outw(CTL_AUTO_RELEASE | 0x0000, ioaddr + CONTROL); + outw(CTL_AUTO_RELEASE | CTL_TE_ENABLE | CTL_CR_ENABLE, + ioaddr + CONTROL); + SMC_SELECT_BANK(2); +} + +/*====================================================================*/ + +static irqreturn_t smc_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr; + u_short saved_bank, saved_pointer, mask, status; + unsigned int handled = 1; + char bogus_cnt = INTR_WORK; /* Work we are willing to do. */ + + if (!netif_device_present(dev)) + return IRQ_NONE; + + ioaddr = dev->base_addr; + + pr_debug("%s: SMC91c92 interrupt %d at %#x.\n", dev->name, + irq, ioaddr); + + spin_lock(&smc->lock); + smc->watchdog = 0; + saved_bank = inw(ioaddr + BANK_SELECT); + if ((saved_bank & 0xff00) != 0x3300) { + /* The device does not exist -- the card could be off-line, or + maybe it has been ejected. */ + pr_debug("%s: SMC91c92 interrupt %d for non-existent" + "/ejected device.\n", dev->name, irq); + handled = 0; + goto irq_done; + } + + SMC_SELECT_BANK(2); + saved_pointer = inw(ioaddr + POINTER); + mask = inw(ioaddr + INTERRUPT) >> 8; + /* clear all interrupts */ + outw(0, ioaddr + INTERRUPT); + + do { /* read the status flag, and mask it */ + status = inw(ioaddr + INTERRUPT) & 0xff; + pr_debug("%s: Status is %#2.2x (mask %#2.2x).\n", dev->name, + status, mask); + if ((status & mask) == 0) { + if (bogus_cnt == INTR_WORK) + handled = 0; + break; + } + if (status & IM_RCV_INT) { + /* Got a packet(s). */ + smc_rx(dev); + } + if (status & IM_TX_INT) { + smc_tx_err(dev); + outw(IM_TX_INT, ioaddr + INTERRUPT); + } + status &= mask; + if (status & IM_TX_EMPTY_INT) { + outw(IM_TX_EMPTY_INT, ioaddr + INTERRUPT); + mask &= ~IM_TX_EMPTY_INT; + dev->stats.tx_packets += smc->packets_waiting; + smc->packets_waiting = 0; + } + if (status & IM_ALLOC_INT) { + /* Clear this interrupt so it doesn't happen again */ + mask &= ~IM_ALLOC_INT; + + smc_hardware_send_packet(dev); + + /* enable xmit interrupts based on this */ + mask |= (IM_TX_EMPTY_INT | IM_TX_INT); + + /* and let the card send more packets to me */ + netif_wake_queue(dev); + } + if (status & IM_RX_OVRN_INT) { + dev->stats.rx_errors++; + dev->stats.rx_fifo_errors++; + if (smc->duplex) + smc->rx_ovrn = 1; /* need MC_RESET outside smc_interrupt */ + outw(IM_RX_OVRN_INT, ioaddr + INTERRUPT); + } + if (status & IM_EPH_INT) + smc_eph_irq(dev); + } while (--bogus_cnt); + + pr_debug(" Restoring saved registers mask %2.2x bank %4.4x" + " pointer %4.4x.\n", mask, saved_bank, saved_pointer); + + /* restore state register */ + outw((mask<<8), ioaddr + INTERRUPT); + outw(saved_pointer, ioaddr + POINTER); + SMC_SELECT_BANK(saved_bank); + + pr_debug("%s: Exiting interrupt IRQ%d.\n", dev->name, irq); + +irq_done: + + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) { + /* Retrigger interrupt if needed */ + mask_bits(0x00ff, ioaddr-0x10+OSITECH_RESET_ISR); + set_bits(0x0300, ioaddr-0x10+OSITECH_RESET_ISR); + } + if (smc->manfid == MANFID_MOTOROLA) { + u_char cor; + cor = readb(smc->base + MOT_UART + CISREG_COR); + writeb(cor & ~COR_IREQ_ENA, smc->base + MOT_UART + CISREG_COR); + writeb(cor, smc->base + MOT_UART + CISREG_COR); + cor = readb(smc->base + MOT_LAN + CISREG_COR); + writeb(cor & ~COR_IREQ_ENA, smc->base + MOT_LAN + CISREG_COR); + writeb(cor, smc->base + MOT_LAN + CISREG_COR); + } + + if ((smc->base != NULL) && /* Megahertz MFC's */ + (smc->manfid == MANFID_MEGAHERTZ) && + (smc->cardid == PRODID_MEGAHERTZ_EM3288)) { + + u_char tmp; + tmp = readb(smc->base+MEGAHERTZ_ISR); + tmp = readb(smc->base+MEGAHERTZ_ISR); + + /* Retrigger interrupt if needed */ + writeb(tmp, smc->base + MEGAHERTZ_ISR); + writeb(tmp, smc->base + MEGAHERTZ_ISR); + } + + spin_unlock(&smc->lock); + return IRQ_RETVAL(handled); +} + +/*====================================================================*/ + +static void smc_rx(struct net_device *dev) +{ + unsigned int ioaddr = dev->base_addr; + int rx_status; + int packet_length; /* Caution: not frame length, rather words + to transfer from the chip. */ + + /* Assertion: we are in Window 2. */ + + if (inw(ioaddr + FIFO_PORTS) & FP_RXEMPTY) { + netdev_err(dev, "smc_rx() with nothing on Rx FIFO\n"); + return; + } + + /* Reset the read pointer, and read the status and packet length. */ + outw(PTR_READ | PTR_RCV | PTR_AUTOINC, ioaddr + POINTER); + rx_status = inw(ioaddr + DATA_1); + packet_length = inw(ioaddr + DATA_1) & 0x07ff; + + pr_debug("%s: Receive status %4.4x length %d.\n", + dev->name, rx_status, packet_length); + + if (!(rx_status & RS_ERRORS)) { + /* do stuff to make a new packet */ + struct sk_buff *skb; + + /* Note: packet_length adds 5 or 6 extra bytes here! */ + skb = dev_alloc_skb(packet_length+2); + + if (skb == NULL) { + pr_debug("%s: Low memory, packet dropped.\n", dev->name); + dev->stats.rx_dropped++; + outw(MC_RELEASE, ioaddr + MMU_CMD); + return; + } + + packet_length -= (rx_status & RS_ODDFRAME ? 5 : 6); + skb_reserve(skb, 2); + insw(ioaddr+DATA_1, skb_put(skb, packet_length), + (packet_length+1)>>1); + skb->protocol = eth_type_trans(skb, dev); + + netif_rx(skb); + dev->last_rx = jiffies; + dev->stats.rx_packets++; + dev->stats.rx_bytes += packet_length; + if (rx_status & RS_MULTICAST) + dev->stats.multicast++; + } else { + /* error ... */ + dev->stats.rx_errors++; + + if (rx_status & RS_ALGNERR) dev->stats.rx_frame_errors++; + if (rx_status & (RS_TOOSHORT | RS_TOOLONG)) + dev->stats.rx_length_errors++; + if (rx_status & RS_BADCRC) dev->stats.rx_crc_errors++; + } + /* Let the MMU free the memory of this packet. */ + outw(MC_RELEASE, ioaddr + MMU_CMD); +} + +/*====================================================================== + + Set the receive mode. + + This routine is used by both the protocol level to notify us of + promiscuous/multicast mode changes, and by the open/reset code to + initialize the Rx registers. We always set the multicast list and + leave the receiver running. + +======================================================================*/ + +static void set_rx_mode(struct net_device *dev) +{ + unsigned int ioaddr = dev->base_addr; + struct smc_private *smc = netdev_priv(dev); + unsigned char multicast_table[8]; + unsigned long flags; + u_short rx_cfg_setting; + int i; + + memset(multicast_table, 0, sizeof(multicast_table)); + + if (dev->flags & IFF_PROMISC) { + rx_cfg_setting = RxStripCRC | RxEnable | RxPromisc | RxAllMulti; + } else if (dev->flags & IFF_ALLMULTI) + rx_cfg_setting = RxStripCRC | RxEnable | RxAllMulti; + else { + if (!netdev_mc_empty(dev)) { + struct netdev_hw_addr *ha; + + netdev_for_each_mc_addr(ha, dev) { + u_int position = ether_crc(6, ha->addr); + multicast_table[position >> 29] |= 1 << ((position >> 26) & 7); + } + } + rx_cfg_setting = RxStripCRC | RxEnable; + } + + /* Load MC table and Rx setting into the chip without interrupts. */ + spin_lock_irqsave(&smc->lock, flags); + SMC_SELECT_BANK(3); + for (i = 0; i < 8; i++) + outb(multicast_table[i], ioaddr + MULTICAST0 + i); + SMC_SELECT_BANK(0); + outw(rx_cfg_setting, ioaddr + RCR); + SMC_SELECT_BANK(2); + spin_unlock_irqrestore(&smc->lock, flags); +} + +/*====================================================================== + + Senses when a card's config changes. Here, it's coax or TP. + +======================================================================*/ + +static int s9k_config(struct net_device *dev, struct ifmap *map) +{ + struct smc_private *smc = netdev_priv(dev); + if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { + if (smc->cfg & CFG_MII_SELECT) + return -EOPNOTSUPP; + else if (map->port > 2) + return -EINVAL; + dev->if_port = map->port; + netdev_info(dev, "switched to %s port\n", if_names[dev->if_port]); + smc_reset(dev); + } + return 0; +} + +/*====================================================================== + + Reset the chip, reloading every register that might be corrupted. + +======================================================================*/ + +/* + Set transceiver type, perhaps to something other than what the user + specified in dev->if_port. +*/ +static void smc_set_xcvr(struct net_device *dev, int if_port) +{ + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + u_short saved_bank; + + saved_bank = inw(ioaddr + BANK_SELECT); + SMC_SELECT_BANK(1); + if (if_port == 2) { + outw(smc->cfg | CFG_AUI_SELECT, ioaddr + CONFIG); + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) + set_bits(OSI_AUI_PWR, ioaddr - 0x10 + OSITECH_AUI_PWR); + smc->media_status = ((dev->if_port == 0) ? 0x0001 : 0x0002); + } else { + outw(smc->cfg, ioaddr + CONFIG); + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) + mask_bits(~OSI_AUI_PWR, ioaddr - 0x10 + OSITECH_AUI_PWR); + smc->media_status = ((dev->if_port == 0) ? 0x0012 : 0x4001); + } + SMC_SELECT_BANK(saved_bank); +} + +static void smc_reset(struct net_device *dev) +{ + unsigned int ioaddr = dev->base_addr; + struct smc_private *smc = netdev_priv(dev); + int i; + + pr_debug("%s: smc91c92 reset called.\n", dev->name); + + /* The first interaction must be a write to bring the chip out + of sleep mode. */ + SMC_SELECT_BANK(0); + /* Reset the chip. */ + outw(RCR_SOFTRESET, ioaddr + RCR); + udelay(10); + + /* Clear the transmit and receive configuration registers. */ + outw(RCR_CLEAR, ioaddr + RCR); + outw(TCR_CLEAR, ioaddr + TCR); + + /* Set the Window 1 control, configuration and station addr registers. + No point in writing the I/O base register ;-> */ + SMC_SELECT_BANK(1); + /* Automatically release successfully transmitted packets, + Accept link errors, counter and Tx error interrupts. */ + outw(CTL_AUTO_RELEASE | CTL_TE_ENABLE | CTL_CR_ENABLE, + ioaddr + CONTROL); + smc_set_xcvr(dev, dev->if_port); + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) + outw((dev->if_port == 2 ? OSI_AUI_PWR : 0) | + (inw(ioaddr-0x10+OSITECH_AUI_PWR) & 0xff00), + ioaddr - 0x10 + OSITECH_AUI_PWR); + + /* Fill in the physical address. The databook is wrong about the order! */ + for (i = 0; i < 6; i += 2) + outw((dev->dev_addr[i+1]<<8)+dev->dev_addr[i], + ioaddr + ADDR0 + i); + + /* Reset the MMU */ + SMC_SELECT_BANK(2); + outw(MC_RESET, ioaddr + MMU_CMD); + outw(0, ioaddr + INTERRUPT); + + /* Re-enable the chip. */ + SMC_SELECT_BANK(0); + outw(((smc->cfg & CFG_MII_SELECT) ? 0 : TCR_MONCSN) | + TCR_ENABLE | TCR_PAD_EN | smc->duplex, ioaddr + TCR); + set_rx_mode(dev); + + if (smc->cfg & CFG_MII_SELECT) { + SMC_SELECT_BANK(3); + + /* Reset MII */ + mdio_write(dev, smc->mii_if.phy_id, 0, 0x8000); + + /* Advertise 100F, 100H, 10F, 10H */ + mdio_write(dev, smc->mii_if.phy_id, 4, 0x01e1); + + /* Restart MII autonegotiation */ + mdio_write(dev, smc->mii_if.phy_id, 0, 0x0000); + mdio_write(dev, smc->mii_if.phy_id, 0, 0x1200); + } + + /* Enable interrupts. */ + SMC_SELECT_BANK(2); + outw((IM_EPH_INT | IM_RX_OVRN_INT | IM_RCV_INT) << 8, + ioaddr + INTERRUPT); +} + +/*====================================================================== + + Media selection timer routine + +======================================================================*/ + +static void media_check(u_long arg) +{ + struct net_device *dev = (struct net_device *) arg; + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + u_short i, media, saved_bank; + u_short link; + unsigned long flags; + + spin_lock_irqsave(&smc->lock, flags); + + saved_bank = inw(ioaddr + BANK_SELECT); + + if (!netif_device_present(dev)) + goto reschedule; + + SMC_SELECT_BANK(2); + + /* need MC_RESET to keep the memory consistent. errata? */ + if (smc->rx_ovrn) { + outw(MC_RESET, ioaddr + MMU_CMD); + smc->rx_ovrn = 0; + } + i = inw(ioaddr + INTERRUPT); + SMC_SELECT_BANK(0); + media = inw(ioaddr + EPH) & EPH_LINK_OK; + SMC_SELECT_BANK(1); + media |= (inw(ioaddr + CONFIG) & CFG_AUI_SELECT) ? 2 : 1; + + SMC_SELECT_BANK(saved_bank); + spin_unlock_irqrestore(&smc->lock, flags); + + /* Check for pending interrupt with watchdog flag set: with + this, we can limp along even if the interrupt is blocked */ + if (smc->watchdog++ && ((i>>8) & i)) { + if (!smc->fast_poll) + netdev_info(dev, "interrupt(s) dropped!\n"); + local_irq_save(flags); + smc_interrupt(dev->irq, dev); + local_irq_restore(flags); + smc->fast_poll = HZ; + } + if (smc->fast_poll) { + smc->fast_poll--; + smc->media.expires = jiffies + HZ/100; + add_timer(&smc->media); + return; + } + + spin_lock_irqsave(&smc->lock, flags); + + saved_bank = inw(ioaddr + BANK_SELECT); + + if (smc->cfg & CFG_MII_SELECT) { + if (smc->mii_if.phy_id < 0) + goto reschedule; + + SMC_SELECT_BANK(3); + link = mdio_read(dev, smc->mii_if.phy_id, 1); + if (!link || (link == 0xffff)) { + netdev_info(dev, "MII is missing!\n"); + smc->mii_if.phy_id = -1; + goto reschedule; + } + + link &= 0x0004; + if (link != smc->link_status) { + u_short p = mdio_read(dev, smc->mii_if.phy_id, 5); + netdev_info(dev, "%s link beat\n", link ? "found" : "lost"); + smc->duplex = (((p & 0x0100) || ((p & 0x1c0) == 0x40)) + ? TCR_FDUPLX : 0); + if (link) { + netdev_info(dev, "autonegotiation complete: " + "%dbaseT-%cD selected\n", + (p & 0x0180) ? 100 : 10, smc->duplex ? 'F' : 'H'); + } + SMC_SELECT_BANK(0); + outw(inw(ioaddr + TCR) | smc->duplex, ioaddr + TCR); + smc->link_status = link; + } + goto reschedule; + } + + /* Ignore collisions unless we've had no rx's recently */ + if (time_after(jiffies, dev->last_rx + HZ)) { + if (smc->tx_err || (smc->media_status & EPH_16COL)) + media |= EPH_16COL; + } + smc->tx_err = 0; + + if (media != smc->media_status) { + if ((media & smc->media_status & 1) && + ((smc->media_status ^ media) & EPH_LINK_OK)) + netdev_info(dev, "%s link beat\n", + smc->media_status & EPH_LINK_OK ? "lost" : "found"); + else if ((media & smc->media_status & 2) && + ((smc->media_status ^ media) & EPH_16COL)) + netdev_info(dev, "coax cable %s\n", + media & EPH_16COL ? "problem" : "ok"); + if (dev->if_port == 0) { + if (media & 1) { + if (media & EPH_LINK_OK) + netdev_info(dev, "flipped to 10baseT\n"); + else + smc_set_xcvr(dev, 2); + } else { + if (media & EPH_16COL) + smc_set_xcvr(dev, 1); + else + netdev_info(dev, "flipped to 10base2\n"); + } + } + smc->media_status = media; + } + +reschedule: + smc->media.expires = jiffies + HZ; + add_timer(&smc->media); + SMC_SELECT_BANK(saved_bank); + spin_unlock_irqrestore(&smc->lock, flags); +} + +static int smc_link_ok(struct net_device *dev) +{ + unsigned int ioaddr = dev->base_addr; + struct smc_private *smc = netdev_priv(dev); + + if (smc->cfg & CFG_MII_SELECT) { + return mii_link_ok(&smc->mii_if); + } else { + SMC_SELECT_BANK(0); + return inw(ioaddr + EPH) & EPH_LINK_OK; + } +} + +static int smc_netdev_get_ecmd(struct net_device *dev, struct ethtool_cmd *ecmd) +{ + u16 tmp; + unsigned int ioaddr = dev->base_addr; + + ecmd->supported = (SUPPORTED_TP | SUPPORTED_AUI | + SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full); + + SMC_SELECT_BANK(1); + tmp = inw(ioaddr + CONFIG); + ecmd->port = (tmp & CFG_AUI_SELECT) ? PORT_AUI : PORT_TP; + ecmd->transceiver = XCVR_INTERNAL; + ethtool_cmd_speed_set(ecmd, SPEED_10); + ecmd->phy_address = ioaddr + MGMT; + + SMC_SELECT_BANK(0); + tmp = inw(ioaddr + TCR); + ecmd->duplex = (tmp & TCR_FDUPLX) ? DUPLEX_FULL : DUPLEX_HALF; + + return 0; +} + +static int smc_netdev_set_ecmd(struct net_device *dev, struct ethtool_cmd *ecmd) +{ + u16 tmp; + unsigned int ioaddr = dev->base_addr; + + if (ethtool_cmd_speed(ecmd) != SPEED_10) + return -EINVAL; + if (ecmd->duplex != DUPLEX_HALF && ecmd->duplex != DUPLEX_FULL) + return -EINVAL; + if (ecmd->port != PORT_TP && ecmd->port != PORT_AUI) + return -EINVAL; + if (ecmd->transceiver != XCVR_INTERNAL) + return -EINVAL; + + if (ecmd->port == PORT_AUI) + smc_set_xcvr(dev, 1); + else + smc_set_xcvr(dev, 0); + + SMC_SELECT_BANK(0); + tmp = inw(ioaddr + TCR); + if (ecmd->duplex == DUPLEX_FULL) + tmp |= TCR_FDUPLX; + else + tmp &= ~TCR_FDUPLX; + outw(tmp, ioaddr + TCR); + + return 0; +} + +static int check_if_running(struct net_device *dev) +{ + if (!netif_running(dev)) + return -EINVAL; + return 0; +} + +static void smc_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + strcpy(info->driver, DRV_NAME); + strcpy(info->version, DRV_VERSION); +} + +static int smc_get_settings(struct net_device *dev, struct ethtool_cmd *ecmd) +{ + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + u16 saved_bank = inw(ioaddr + BANK_SELECT); + int ret; + unsigned long flags; + + spin_lock_irqsave(&smc->lock, flags); + SMC_SELECT_BANK(3); + if (smc->cfg & CFG_MII_SELECT) + ret = mii_ethtool_gset(&smc->mii_if, ecmd); + else + ret = smc_netdev_get_ecmd(dev, ecmd); + SMC_SELECT_BANK(saved_bank); + spin_unlock_irqrestore(&smc->lock, flags); + return ret; +} + +static int smc_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd) +{ + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + u16 saved_bank = inw(ioaddr + BANK_SELECT); + int ret; + unsigned long flags; + + spin_lock_irqsave(&smc->lock, flags); + SMC_SELECT_BANK(3); + if (smc->cfg & CFG_MII_SELECT) + ret = mii_ethtool_sset(&smc->mii_if, ecmd); + else + ret = smc_netdev_set_ecmd(dev, ecmd); + SMC_SELECT_BANK(saved_bank); + spin_unlock_irqrestore(&smc->lock, flags); + return ret; +} + +static u32 smc_get_link(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + unsigned int ioaddr = dev->base_addr; + u16 saved_bank = inw(ioaddr + BANK_SELECT); + u32 ret; + unsigned long flags; + + spin_lock_irqsave(&smc->lock, flags); + SMC_SELECT_BANK(3); + ret = smc_link_ok(dev); + SMC_SELECT_BANK(saved_bank); + spin_unlock_irqrestore(&smc->lock, flags); + return ret; +} + +static int smc_nway_reset(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + if (smc->cfg & CFG_MII_SELECT) { + unsigned int ioaddr = dev->base_addr; + u16 saved_bank = inw(ioaddr + BANK_SELECT); + int res; + + SMC_SELECT_BANK(3); + res = mii_nway_restart(&smc->mii_if); + SMC_SELECT_BANK(saved_bank); + + return res; + } else + return -EOPNOTSUPP; +} + +static const struct ethtool_ops ethtool_ops = { + .begin = check_if_running, + .get_drvinfo = smc_get_drvinfo, + .get_settings = smc_get_settings, + .set_settings = smc_set_settings, + .get_link = smc_get_link, + .nway_reset = smc_nway_reset, +}; + +static int smc_ioctl (struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct smc_private *smc = netdev_priv(dev); + struct mii_ioctl_data *mii = if_mii(rq); + int rc = 0; + u16 saved_bank; + unsigned int ioaddr = dev->base_addr; + unsigned long flags; + + if (!netif_running(dev)) + return -EINVAL; + + spin_lock_irqsave(&smc->lock, flags); + saved_bank = inw(ioaddr + BANK_SELECT); + SMC_SELECT_BANK(3); + rc = generic_mii_ioctl(&smc->mii_if, mii, cmd, NULL); + SMC_SELECT_BANK(saved_bank); + spin_unlock_irqrestore(&smc->lock, flags); + return rc; +} + +static const struct pcmcia_device_id smc91c92_ids[] = { + PCMCIA_PFC_DEVICE_MANF_CARD(0, 0x0109, 0x0501), + PCMCIA_PFC_DEVICE_MANF_CARD(0, 0x0140, 0x000a), + PCMCIA_PFC_DEVICE_PROD_ID123(0, "MEGAHERTZ", "CC/XJEM3288", "DATA/FAX/CELL ETHERNET MODEM", 0xf510db04, 0x04cd2988, 0x46a52d63), + PCMCIA_PFC_DEVICE_PROD_ID123(0, "MEGAHERTZ", "CC/XJEM3336", "DATA/FAX/CELL ETHERNET MODEM", 0xf510db04, 0x0143b773, 0x46a52d63), + PCMCIA_PFC_DEVICE_PROD_ID123(0, "MEGAHERTZ", "EM1144T", "PCMCIA MODEM", 0xf510db04, 0x856d66c8, 0xbd6c43ef), + PCMCIA_PFC_DEVICE_PROD_ID123(0, "MEGAHERTZ", "XJEM1144/CCEM1144", "PCMCIA MODEM", 0xf510db04, 0x52d21e1e, 0xbd6c43ef), + PCMCIA_PFC_DEVICE_PROD_ID12(0, "Gateway 2000", "XJEM3336", 0xdd9989be, 0x662c394c), + PCMCIA_PFC_DEVICE_PROD_ID12(0, "MEGAHERTZ", "XJEM1144/CCEM1144", 0xf510db04, 0x52d21e1e), + PCMCIA_PFC_DEVICE_PROD_ID12(0, "Ositech", "Trumpcard:Jack of Diamonds Modem+Ethernet", 0xc2f80cd, 0x656947b9), + PCMCIA_PFC_DEVICE_PROD_ID12(0, "Ositech", "Trumpcard:Jack of Hearts Modem+Ethernet", 0xc2f80cd, 0xdc9ba5ed), + PCMCIA_MFC_DEVICE_MANF_CARD(0, 0x016c, 0x0020), + PCMCIA_DEVICE_MANF_CARD(0x016c, 0x0023), + PCMCIA_DEVICE_PROD_ID123("BASICS by New Media Corporation", "Ethernet", "SMC91C94", 0x23c78a9d, 0x00b2e941, 0xcef397fb), + PCMCIA_DEVICE_PROD_ID12("ARGOSY", "Fast Ethernet PCCard", 0x78f308dc, 0xdcea68bc), + PCMCIA_DEVICE_PROD_ID12("dit Co., Ltd.", "PC Card-10/100BTX", 0xe59365c8, 0x6a2161d1), + PCMCIA_DEVICE_PROD_ID12("DYNALINK", "L100C", 0x6a26d1cf, 0xc16ce9c5), + PCMCIA_DEVICE_PROD_ID12("Farallon", "Farallon Enet", 0x58d93fc4, 0x244734e9), + PCMCIA_DEVICE_PROD_ID12("Megahertz", "CC10BT/2", 0x33234748, 0x3c95b953), + PCMCIA_DEVICE_PROD_ID12("MELCO/SMC", "LPC-TX", 0xa2cd8e6d, 0x42da662a), + PCMCIA_DEVICE_PROD_ID12("Ositech", "Trumpcard:Four of Diamonds Ethernet", 0xc2f80cd, 0xb3466314), + PCMCIA_DEVICE_PROD_ID12("Ositech", "Trumpcard:Seven of Diamonds Ethernet", 0xc2f80cd, 0x194b650a), + PCMCIA_DEVICE_PROD_ID12("PCMCIA", "Fast Ethernet PCCard", 0x281f1c5d, 0xdcea68bc), + PCMCIA_DEVICE_PROD_ID12("Psion", "10Mb Ethernet", 0x4ef00b21, 0x844be9e9), + PCMCIA_DEVICE_PROD_ID12("SMC", "EtherEZ Ethernet 8020", 0xc4f8b18b, 0x4a0eeb2d), + /* These conflict with other cards! */ + /* PCMCIA_DEVICE_MANF_CARD(0x0186, 0x0100), */ + /* PCMCIA_DEVICE_MANF_CARD(0x8a01, 0xc1ab), */ + PCMCIA_DEVICE_NULL, +}; +MODULE_DEVICE_TABLE(pcmcia, smc91c92_ids); + +static struct pcmcia_driver smc91c92_cs_driver = { + .owner = THIS_MODULE, + .name = "smc91c92_cs", + .probe = smc91c92_probe, + .remove = smc91c92_detach, + .id_table = smc91c92_ids, + .suspend = smc91c92_suspend, + .resume = smc91c92_resume, +}; + +static int __init init_smc91c92_cs(void) +{ + return pcmcia_register_driver(&smc91c92_cs_driver); +} + +static void __exit exit_smc91c92_cs(void) +{ + pcmcia_unregister_driver(&smc91c92_cs_driver); +} + +module_init(init_smc91c92_cs); +module_exit(exit_smc91c92_cs); diff --git a/drivers/net/ethernet/smsc/smc91x.c b/drivers/net/ethernet/smsc/smc91x.c new file mode 100644 index 000000000000..2b1d254d59af --- /dev/null +++ b/drivers/net/ethernet/smsc/smc91x.c @@ -0,0 +1,2431 @@ +/* + * smc91x.c + * This is a driver for SMSC's 91C9x/91C1xx single-chip Ethernet devices. + * + * Copyright (C) 1996 by Erik Stahlman + * Copyright (C) 2001 Standard Microsystems Corporation + * Developed by Simple Network Magic Corporation + * Copyright (C) 2003 Monta Vista Software, Inc. + * Unified SMC91x driver by Nicolas Pitre + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Arguments: + * io = for the base address + * irq = for the IRQ + * nowait = 0 for normal wait states, 1 eliminates additional wait states + * + * original author: + * Erik Stahlman <erik@vt.edu> + * + * hardware multicast code: + * Peter Cammaert <pc@denkart.be> + * + * contributors: + * Daris A Nevil <dnevil@snmc.com> + * Nicolas Pitre <nico@fluxnic.net> + * Russell King <rmk@arm.linux.org.uk> + * + * History: + * 08/20/00 Arnaldo Melo fix kfree(skb) in smc_hardware_send_packet + * 12/15/00 Christian Jullien fix "Warning: kfree_skb on hard IRQ" + * 03/16/01 Daris A Nevil modified smc9194.c for use with LAN91C111 + * 08/22/01 Scott Anderson merge changes from smc9194 to smc91111 + * 08/21/01 Pramod B Bhardwaj added support for RevB of LAN91C111 + * 12/20/01 Jeff Sutherland initial port to Xscale PXA with DMA support + * 04/07/03 Nicolas Pitre unified SMC91x driver, killed irq races, + * more bus abstraction, big cleanup, etc. + * 29/09/03 Russell King - add driver model support + * - ethtool support + * - convert to use generic MII interface + * - add link up/down notification + * - don't try to handle full negotiation in + * smc_phy_configure + * - clean up (and fix stack overrun) in PHY + * MII read/write functions + * 22/09/04 Nicolas Pitre big update (see commit log for details) + */ +static const char version[] = + "smc91x.c: v1.1, sep 22 2004 by Nicolas Pitre <nico@fluxnic.net>\n"; + +/* Debugging level */ +#ifndef SMC_DEBUG +#define SMC_DEBUG 0 +#endif + + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/crc32.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/workqueue.h> +#include <linux/of.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <asm/io.h> + +#include "smc91x.h" + +#ifndef SMC_NOWAIT +# define SMC_NOWAIT 0 +#endif +static int nowait = SMC_NOWAIT; +module_param(nowait, int, 0400); +MODULE_PARM_DESC(nowait, "set to 1 for no wait state"); + +/* + * Transmit timeout, default 5 seconds. + */ +static int watchdog = 1000; +module_param(watchdog, int, 0400); +MODULE_PARM_DESC(watchdog, "transmit timeout in milliseconds"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:smc91x"); + +/* + * The internal workings of the driver. If you are changing anything + * here with the SMC stuff, you should have the datasheet and know + * what you are doing. + */ +#define CARDNAME "smc91x" + +/* + * Use power-down feature of the chip + */ +#define POWER_DOWN 1 + +/* + * Wait time for memory to be free. This probably shouldn't be + * tuned that much, as waiting for this means nothing else happens + * in the system + */ +#define MEMORY_WAIT_TIME 16 + +/* + * The maximum number of processing loops allowed for each call to the + * IRQ handler. + */ +#define MAX_IRQ_LOOPS 8 + +/* + * This selects whether TX packets are sent one by one to the SMC91x internal + * memory and throttled until transmission completes. This may prevent + * RX overruns a litle by keeping much of the memory free for RX packets + * but to the expense of reduced TX throughput and increased IRQ overhead. + * Note this is not a cure for a too slow data bus or too high IRQ latency. + */ +#define THROTTLE_TX_PKTS 0 + +/* + * The MII clock high/low times. 2x this number gives the MII clock period + * in microseconds. (was 50, but this gives 6.4ms for each MII transaction!) + */ +#define MII_DELAY 1 + +#if SMC_DEBUG > 0 +#define DBG(n, args...) \ + do { \ + if (SMC_DEBUG >= (n)) \ + printk(args); \ + } while (0) + +#define PRINTK(args...) printk(args) +#else +#define DBG(n, args...) do { } while(0) +#define PRINTK(args...) printk(KERN_DEBUG args) +#endif + +#if SMC_DEBUG > 3 +static void PRINT_PKT(u_char *buf, int length) +{ + int i; + int remainder; + int lines; + + lines = length / 16; + remainder = length % 16; + + for (i = 0; i < lines ; i ++) { + int cur; + for (cur = 0; cur < 8; cur++) { + u_char a, b; + a = *buf++; + b = *buf++; + printk("%02x%02x ", a, b); + } + printk("\n"); + } + for (i = 0; i < remainder/2 ; i++) { + u_char a, b; + a = *buf++; + b = *buf++; + printk("%02x%02x ", a, b); + } + printk("\n"); +} +#else +#define PRINT_PKT(x...) do { } while(0) +#endif + + +/* this enables an interrupt in the interrupt mask register */ +#define SMC_ENABLE_INT(lp, x) do { \ + unsigned char mask; \ + unsigned long smc_enable_flags; \ + spin_lock_irqsave(&lp->lock, smc_enable_flags); \ + mask = SMC_GET_INT_MASK(lp); \ + mask |= (x); \ + SMC_SET_INT_MASK(lp, mask); \ + spin_unlock_irqrestore(&lp->lock, smc_enable_flags); \ +} while (0) + +/* this disables an interrupt from the interrupt mask register */ +#define SMC_DISABLE_INT(lp, x) do { \ + unsigned char mask; \ + unsigned long smc_disable_flags; \ + spin_lock_irqsave(&lp->lock, smc_disable_flags); \ + mask = SMC_GET_INT_MASK(lp); \ + mask &= ~(x); \ + SMC_SET_INT_MASK(lp, mask); \ + spin_unlock_irqrestore(&lp->lock, smc_disable_flags); \ +} while (0) + +/* + * Wait while MMU is busy. This is usually in the order of a few nanosecs + * if at all, but let's avoid deadlocking the system if the hardware + * decides to go south. + */ +#define SMC_WAIT_MMU_BUSY(lp) do { \ + if (unlikely(SMC_GET_MMU_CMD(lp) & MC_BUSY)) { \ + unsigned long timeout = jiffies + 2; \ + while (SMC_GET_MMU_CMD(lp) & MC_BUSY) { \ + if (time_after(jiffies, timeout)) { \ + printk("%s: timeout %s line %d\n", \ + dev->name, __FILE__, __LINE__); \ + break; \ + } \ + cpu_relax(); \ + } \ + } \ +} while (0) + + +/* + * this does a soft reset on the device + */ +static void smc_reset(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned int ctl, cfg; + struct sk_buff *pending_skb; + + DBG(2, "%s: %s\n", dev->name, __func__); + + /* Disable all interrupts, block TX tasklet */ + spin_lock_irq(&lp->lock); + SMC_SELECT_BANK(lp, 2); + SMC_SET_INT_MASK(lp, 0); + pending_skb = lp->pending_tx_skb; + lp->pending_tx_skb = NULL; + spin_unlock_irq(&lp->lock); + + /* free any pending tx skb */ + if (pending_skb) { + dev_kfree_skb(pending_skb); + dev->stats.tx_errors++; + dev->stats.tx_aborted_errors++; + } + + /* + * This resets the registers mostly to defaults, but doesn't + * affect EEPROM. That seems unnecessary + */ + SMC_SELECT_BANK(lp, 0); + SMC_SET_RCR(lp, RCR_SOFTRST); + + /* + * Setup the Configuration Register + * This is necessary because the CONFIG_REG is not affected + * by a soft reset + */ + SMC_SELECT_BANK(lp, 1); + + cfg = CONFIG_DEFAULT; + + /* + * Setup for fast accesses if requested. If the card/system + * can't handle it then there will be no recovery except for + * a hard reset or power cycle + */ + if (lp->cfg.flags & SMC91X_NOWAIT) + cfg |= CONFIG_NO_WAIT; + + /* + * Release from possible power-down state + * Configuration register is not affected by Soft Reset + */ + cfg |= CONFIG_EPH_POWER_EN; + + SMC_SET_CONFIG(lp, cfg); + + /* this should pause enough for the chip to be happy */ + /* + * elaborate? What does the chip _need_? --jgarzik + * + * This seems to be undocumented, but something the original + * driver(s) have always done. Suspect undocumented timing + * info/determined empirically. --rmk + */ + udelay(1); + + /* Disable transmit and receive functionality */ + SMC_SELECT_BANK(lp, 0); + SMC_SET_RCR(lp, RCR_CLEAR); + SMC_SET_TCR(lp, TCR_CLEAR); + + SMC_SELECT_BANK(lp, 1); + ctl = SMC_GET_CTL(lp) | CTL_LE_ENABLE; + + /* + * Set the control register to automatically release successfully + * transmitted packets, to make the best use out of our limited + * memory + */ + if(!THROTTLE_TX_PKTS) + ctl |= CTL_AUTO_RELEASE; + else + ctl &= ~CTL_AUTO_RELEASE; + SMC_SET_CTL(lp, ctl); + + /* Reset the MMU */ + SMC_SELECT_BANK(lp, 2); + SMC_SET_MMU_CMD(lp, MC_RESET); + SMC_WAIT_MMU_BUSY(lp); +} + +/* + * Enable Interrupts, Receive, and Transmit + */ +static void smc_enable(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + int mask; + + DBG(2, "%s: %s\n", dev->name, __func__); + + /* see the header file for options in TCR/RCR DEFAULT */ + SMC_SELECT_BANK(lp, 0); + SMC_SET_TCR(lp, lp->tcr_cur_mode); + SMC_SET_RCR(lp, lp->rcr_cur_mode); + + SMC_SELECT_BANK(lp, 1); + SMC_SET_MAC_ADDR(lp, dev->dev_addr); + + /* now, enable interrupts */ + mask = IM_EPH_INT|IM_RX_OVRN_INT|IM_RCV_INT; + if (lp->version >= (CHIP_91100 << 4)) + mask |= IM_MDINT; + SMC_SELECT_BANK(lp, 2); + SMC_SET_INT_MASK(lp, mask); + + /* + * From this point the register bank must _NOT_ be switched away + * to something else than bank 2 without proper locking against + * races with any tasklet or interrupt handlers until smc_shutdown() + * or smc_reset() is called. + */ +} + +/* + * this puts the device in an inactive state + */ +static void smc_shutdown(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + struct sk_buff *pending_skb; + + DBG(2, "%s: %s\n", CARDNAME, __func__); + + /* no more interrupts for me */ + spin_lock_irq(&lp->lock); + SMC_SELECT_BANK(lp, 2); + SMC_SET_INT_MASK(lp, 0); + pending_skb = lp->pending_tx_skb; + lp->pending_tx_skb = NULL; + spin_unlock_irq(&lp->lock); + if (pending_skb) + dev_kfree_skb(pending_skb); + + /* and tell the card to stay away from that nasty outside world */ + SMC_SELECT_BANK(lp, 0); + SMC_SET_RCR(lp, RCR_CLEAR); + SMC_SET_TCR(lp, TCR_CLEAR); + +#ifdef POWER_DOWN + /* finally, shut the chip down */ + SMC_SELECT_BANK(lp, 1); + SMC_SET_CONFIG(lp, SMC_GET_CONFIG(lp) & ~CONFIG_EPH_POWER_EN); +#endif +} + +/* + * This is the procedure to handle the receipt of a packet. + */ +static inline void smc_rcv(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned int packet_number, status, packet_len; + + DBG(3, "%s: %s\n", dev->name, __func__); + + packet_number = SMC_GET_RXFIFO(lp); + if (unlikely(packet_number & RXFIFO_REMPTY)) { + PRINTK("%s: smc_rcv with nothing on FIFO.\n", dev->name); + return; + } + + /* read from start of packet */ + SMC_SET_PTR(lp, PTR_READ | PTR_RCV | PTR_AUTOINC); + + /* First two words are status and packet length */ + SMC_GET_PKT_HDR(lp, status, packet_len); + packet_len &= 0x07ff; /* mask off top bits */ + DBG(2, "%s: RX PNR 0x%x STATUS 0x%04x LENGTH 0x%04x (%d)\n", + dev->name, packet_number, status, + packet_len, packet_len); + + back: + if (unlikely(packet_len < 6 || status & RS_ERRORS)) { + if (status & RS_TOOLONG && packet_len <= (1514 + 4 + 6)) { + /* accept VLAN packets */ + status &= ~RS_TOOLONG; + goto back; + } + if (packet_len < 6) { + /* bloody hardware */ + printk(KERN_ERR "%s: fubar (rxlen %u status %x\n", + dev->name, packet_len, status); + status |= RS_TOOSHORT; + } + SMC_WAIT_MMU_BUSY(lp); + SMC_SET_MMU_CMD(lp, MC_RELEASE); + dev->stats.rx_errors++; + if (status & RS_ALGNERR) + dev->stats.rx_frame_errors++; + if (status & (RS_TOOSHORT | RS_TOOLONG)) + dev->stats.rx_length_errors++; + if (status & RS_BADCRC) + dev->stats.rx_crc_errors++; + } else { + struct sk_buff *skb; + unsigned char *data; + unsigned int data_len; + + /* set multicast stats */ + if (status & RS_MULTICAST) + dev->stats.multicast++; + + /* + * Actual payload is packet_len - 6 (or 5 if odd byte). + * We want skb_reserve(2) and the final ctrl word + * (2 bytes, possibly containing the payload odd byte). + * Furthermore, we add 2 bytes to allow rounding up to + * multiple of 4 bytes on 32 bit buses. + * Hence packet_len - 6 + 2 + 2 + 2. + */ + skb = dev_alloc_skb(packet_len); + if (unlikely(skb == NULL)) { + printk(KERN_NOTICE "%s: Low memory, packet dropped.\n", + dev->name); + SMC_WAIT_MMU_BUSY(lp); + SMC_SET_MMU_CMD(lp, MC_RELEASE); + dev->stats.rx_dropped++; + return; + } + + /* Align IP header to 32 bits */ + skb_reserve(skb, 2); + + /* BUG: the LAN91C111 rev A never sets this bit. Force it. */ + if (lp->version == 0x90) + status |= RS_ODDFRAME; + + /* + * If odd length: packet_len - 5, + * otherwise packet_len - 6. + * With the trailing ctrl byte it's packet_len - 4. + */ + data_len = packet_len - ((status & RS_ODDFRAME) ? 5 : 6); + data = skb_put(skb, data_len); + SMC_PULL_DATA(lp, data, packet_len - 4); + + SMC_WAIT_MMU_BUSY(lp); + SMC_SET_MMU_CMD(lp, MC_RELEASE); + + PRINT_PKT(data, packet_len - 4); + + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + dev->stats.rx_packets++; + dev->stats.rx_bytes += data_len; + } +} + +#ifdef CONFIG_SMP +/* + * On SMP we have the following problem: + * + * A = smc_hardware_send_pkt() + * B = smc_hard_start_xmit() + * C = smc_interrupt() + * + * A and B can never be executed simultaneously. However, at least on UP, + * it is possible (and even desirable) for C to interrupt execution of + * A or B in order to have better RX reliability and avoid overruns. + * C, just like A and B, must have exclusive access to the chip and + * each of them must lock against any other concurrent access. + * Unfortunately this is not possible to have C suspend execution of A or + * B taking place on another CPU. On UP this is no an issue since A and B + * are run from softirq context and C from hard IRQ context, and there is + * no other CPU where concurrent access can happen. + * If ever there is a way to force at least B and C to always be executed + * on the same CPU then we could use read/write locks to protect against + * any other concurrent access and C would always interrupt B. But life + * isn't that easy in a SMP world... + */ +#define smc_special_trylock(lock, flags) \ +({ \ + int __ret; \ + local_irq_save(flags); \ + __ret = spin_trylock(lock); \ + if (!__ret) \ + local_irq_restore(flags); \ + __ret; \ +}) +#define smc_special_lock(lock, flags) spin_lock_irqsave(lock, flags) +#define smc_special_unlock(lock, flags) spin_unlock_irqrestore(lock, flags) +#else +#define smc_special_trylock(lock, flags) (flags == flags) +#define smc_special_lock(lock, flags) do { flags = 0; } while (0) +#define smc_special_unlock(lock, flags) do { flags = 0; } while (0) +#endif + +/* + * This is called to actually send a packet to the chip. + */ +static void smc_hardware_send_pkt(unsigned long data) +{ + struct net_device *dev = (struct net_device *)data; + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + struct sk_buff *skb; + unsigned int packet_no, len; + unsigned char *buf; + unsigned long flags; + + DBG(3, "%s: %s\n", dev->name, __func__); + + if (!smc_special_trylock(&lp->lock, flags)) { + netif_stop_queue(dev); + tasklet_schedule(&lp->tx_task); + return; + } + + skb = lp->pending_tx_skb; + if (unlikely(!skb)) { + smc_special_unlock(&lp->lock, flags); + return; + } + lp->pending_tx_skb = NULL; + + packet_no = SMC_GET_AR(lp); + if (unlikely(packet_no & AR_FAILED)) { + printk("%s: Memory allocation failed.\n", dev->name); + dev->stats.tx_errors++; + dev->stats.tx_fifo_errors++; + smc_special_unlock(&lp->lock, flags); + goto done; + } + + /* point to the beginning of the packet */ + SMC_SET_PN(lp, packet_no); + SMC_SET_PTR(lp, PTR_AUTOINC); + + buf = skb->data; + len = skb->len; + DBG(2, "%s: TX PNR 0x%x LENGTH 0x%04x (%d) BUF 0x%p\n", + dev->name, packet_no, len, len, buf); + PRINT_PKT(buf, len); + + /* + * Send the packet length (+6 for status words, length, and ctl. + * The card will pad to 64 bytes with zeroes if packet is too small. + */ + SMC_PUT_PKT_HDR(lp, 0, len + 6); + + /* send the actual data */ + SMC_PUSH_DATA(lp, buf, len & ~1); + + /* Send final ctl word with the last byte if there is one */ + SMC_outw(((len & 1) ? (0x2000 | buf[len-1]) : 0), ioaddr, DATA_REG(lp)); + + /* + * If THROTTLE_TX_PKTS is set, we stop the queue here. This will + * have the effect of having at most one packet queued for TX + * in the chip's memory at all time. + * + * If THROTTLE_TX_PKTS is not set then the queue is stopped only + * when memory allocation (MC_ALLOC) does not succeed right away. + */ + if (THROTTLE_TX_PKTS) + netif_stop_queue(dev); + + /* queue the packet for TX */ + SMC_SET_MMU_CMD(lp, MC_ENQUEUE); + smc_special_unlock(&lp->lock, flags); + + dev->trans_start = jiffies; + dev->stats.tx_packets++; + dev->stats.tx_bytes += len; + + SMC_ENABLE_INT(lp, IM_TX_INT | IM_TX_EMPTY_INT); + +done: if (!THROTTLE_TX_PKTS) + netif_wake_queue(dev); + + dev_kfree_skb(skb); +} + +/* + * Since I am not sure if I will have enough room in the chip's ram + * to store the packet, I call this routine which either sends it + * now, or set the card to generates an interrupt when ready + * for the packet. + */ +static int smc_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned int numPages, poll_count, status; + unsigned long flags; + + DBG(3, "%s: %s\n", dev->name, __func__); + + BUG_ON(lp->pending_tx_skb != NULL); + + /* + * The MMU wants the number of pages to be the number of 256 bytes + * 'pages', minus 1 (since a packet can't ever have 0 pages :)) + * + * The 91C111 ignores the size bits, but earlier models don't. + * + * Pkt size for allocating is data length +6 (for additional status + * words, length and ctl) + * + * If odd size then last byte is included in ctl word. + */ + numPages = ((skb->len & ~1) + (6 - 1)) >> 8; + if (unlikely(numPages > 7)) { + printk("%s: Far too big packet error.\n", dev->name); + dev->stats.tx_errors++; + dev->stats.tx_dropped++; + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + + smc_special_lock(&lp->lock, flags); + + /* now, try to allocate the memory */ + SMC_SET_MMU_CMD(lp, MC_ALLOC | numPages); + + /* + * Poll the chip for a short amount of time in case the + * allocation succeeds quickly. + */ + poll_count = MEMORY_WAIT_TIME; + do { + status = SMC_GET_INT(lp); + if (status & IM_ALLOC_INT) { + SMC_ACK_INT(lp, IM_ALLOC_INT); + break; + } + } while (--poll_count); + + smc_special_unlock(&lp->lock, flags); + + lp->pending_tx_skb = skb; + if (!poll_count) { + /* oh well, wait until the chip finds memory later */ + netif_stop_queue(dev); + DBG(2, "%s: TX memory allocation deferred.\n", dev->name); + SMC_ENABLE_INT(lp, IM_ALLOC_INT); + } else { + /* + * Allocation succeeded: push packet to the chip's own memory + * immediately. + */ + smc_hardware_send_pkt((unsigned long)dev); + } + + return NETDEV_TX_OK; +} + +/* + * This handles a TX interrupt, which is only called when: + * - a TX error occurred, or + * - CTL_AUTO_RELEASE is not set and TX of a packet completed. + */ +static void smc_tx(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned int saved_packet, packet_no, tx_status, pkt_len; + + DBG(3, "%s: %s\n", dev->name, __func__); + + /* If the TX FIFO is empty then nothing to do */ + packet_no = SMC_GET_TXFIFO(lp); + if (unlikely(packet_no & TXFIFO_TEMPTY)) { + PRINTK("%s: smc_tx with nothing on FIFO.\n", dev->name); + return; + } + + /* select packet to read from */ + saved_packet = SMC_GET_PN(lp); + SMC_SET_PN(lp, packet_no); + + /* read the first word (status word) from this packet */ + SMC_SET_PTR(lp, PTR_AUTOINC | PTR_READ); + SMC_GET_PKT_HDR(lp, tx_status, pkt_len); + DBG(2, "%s: TX STATUS 0x%04x PNR 0x%02x\n", + dev->name, tx_status, packet_no); + + if (!(tx_status & ES_TX_SUC)) + dev->stats.tx_errors++; + + if (tx_status & ES_LOSTCARR) + dev->stats.tx_carrier_errors++; + + if (tx_status & (ES_LATCOL | ES_16COL)) { + PRINTK("%s: %s occurred on last xmit\n", dev->name, + (tx_status & ES_LATCOL) ? + "late collision" : "too many collisions"); + dev->stats.tx_window_errors++; + if (!(dev->stats.tx_window_errors & 63) && net_ratelimit()) { + printk(KERN_INFO "%s: unexpectedly large number of " + "bad collisions. Please check duplex " + "setting.\n", dev->name); + } + } + + /* kill the packet */ + SMC_WAIT_MMU_BUSY(lp); + SMC_SET_MMU_CMD(lp, MC_FREEPKT); + + /* Don't restore Packet Number Reg until busy bit is cleared */ + SMC_WAIT_MMU_BUSY(lp); + SMC_SET_PN(lp, saved_packet); + + /* re-enable transmit */ + SMC_SELECT_BANK(lp, 0); + SMC_SET_TCR(lp, lp->tcr_cur_mode); + SMC_SELECT_BANK(lp, 2); +} + + +/*---PHY CONTROL AND CONFIGURATION-----------------------------------------*/ + +static void smc_mii_out(struct net_device *dev, unsigned int val, int bits) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned int mii_reg, mask; + + mii_reg = SMC_GET_MII(lp) & ~(MII_MCLK | MII_MDOE | MII_MDO); + mii_reg |= MII_MDOE; + + for (mask = 1 << (bits - 1); mask; mask >>= 1) { + if (val & mask) + mii_reg |= MII_MDO; + else + mii_reg &= ~MII_MDO; + + SMC_SET_MII(lp, mii_reg); + udelay(MII_DELAY); + SMC_SET_MII(lp, mii_reg | MII_MCLK); + udelay(MII_DELAY); + } +} + +static unsigned int smc_mii_in(struct net_device *dev, int bits) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned int mii_reg, mask, val; + + mii_reg = SMC_GET_MII(lp) & ~(MII_MCLK | MII_MDOE | MII_MDO); + SMC_SET_MII(lp, mii_reg); + + for (mask = 1 << (bits - 1), val = 0; mask; mask >>= 1) { + if (SMC_GET_MII(lp) & MII_MDI) + val |= mask; + + SMC_SET_MII(lp, mii_reg); + udelay(MII_DELAY); + SMC_SET_MII(lp, mii_reg | MII_MCLK); + udelay(MII_DELAY); + } + + return val; +} + +/* + * Reads a register from the MII Management serial interface + */ +static int smc_phy_read(struct net_device *dev, int phyaddr, int phyreg) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned int phydata; + + SMC_SELECT_BANK(lp, 3); + + /* Idle - 32 ones */ + smc_mii_out(dev, 0xffffffff, 32); + + /* Start code (01) + read (10) + phyaddr + phyreg */ + smc_mii_out(dev, 6 << 10 | phyaddr << 5 | phyreg, 14); + + /* Turnaround (2bits) + phydata */ + phydata = smc_mii_in(dev, 18); + + /* Return to idle state */ + SMC_SET_MII(lp, SMC_GET_MII(lp) & ~(MII_MCLK|MII_MDOE|MII_MDO)); + + DBG(3, "%s: phyaddr=0x%x, phyreg=0x%x, phydata=0x%x\n", + __func__, phyaddr, phyreg, phydata); + + SMC_SELECT_BANK(lp, 2); + return phydata; +} + +/* + * Writes a register to the MII Management serial interface + */ +static void smc_phy_write(struct net_device *dev, int phyaddr, int phyreg, + int phydata) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + + SMC_SELECT_BANK(lp, 3); + + /* Idle - 32 ones */ + smc_mii_out(dev, 0xffffffff, 32); + + /* Start code (01) + write (01) + phyaddr + phyreg + turnaround + phydata */ + smc_mii_out(dev, 5 << 28 | phyaddr << 23 | phyreg << 18 | 2 << 16 | phydata, 32); + + /* Return to idle state */ + SMC_SET_MII(lp, SMC_GET_MII(lp) & ~(MII_MCLK|MII_MDOE|MII_MDO)); + + DBG(3, "%s: phyaddr=0x%x, phyreg=0x%x, phydata=0x%x\n", + __func__, phyaddr, phyreg, phydata); + + SMC_SELECT_BANK(lp, 2); +} + +/* + * Finds and reports the PHY address + */ +static void smc_phy_detect(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + int phyaddr; + + DBG(2, "%s: %s\n", dev->name, __func__); + + lp->phy_type = 0; + + /* + * Scan all 32 PHY addresses if necessary, starting at + * PHY#1 to PHY#31, and then PHY#0 last. + */ + for (phyaddr = 1; phyaddr < 33; ++phyaddr) { + unsigned int id1, id2; + + /* Read the PHY identifiers */ + id1 = smc_phy_read(dev, phyaddr & 31, MII_PHYSID1); + id2 = smc_phy_read(dev, phyaddr & 31, MII_PHYSID2); + + DBG(3, "%s: phy_id1=0x%x, phy_id2=0x%x\n", + dev->name, id1, id2); + + /* Make sure it is a valid identifier */ + if (id1 != 0x0000 && id1 != 0xffff && id1 != 0x8000 && + id2 != 0x0000 && id2 != 0xffff && id2 != 0x8000) { + /* Save the PHY's address */ + lp->mii.phy_id = phyaddr & 31; + lp->phy_type = id1 << 16 | id2; + break; + } + } +} + +/* + * Sets the PHY to a configuration as determined by the user + */ +static int smc_phy_fixed(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + int phyaddr = lp->mii.phy_id; + int bmcr, cfg1; + + DBG(3, "%s: %s\n", dev->name, __func__); + + /* Enter Link Disable state */ + cfg1 = smc_phy_read(dev, phyaddr, PHY_CFG1_REG); + cfg1 |= PHY_CFG1_LNKDIS; + smc_phy_write(dev, phyaddr, PHY_CFG1_REG, cfg1); + + /* + * Set our fixed capabilities + * Disable auto-negotiation + */ + bmcr = 0; + + if (lp->ctl_rfduplx) + bmcr |= BMCR_FULLDPLX; + + if (lp->ctl_rspeed == 100) + bmcr |= BMCR_SPEED100; + + /* Write our capabilities to the phy control register */ + smc_phy_write(dev, phyaddr, MII_BMCR, bmcr); + + /* Re-Configure the Receive/Phy Control register */ + SMC_SELECT_BANK(lp, 0); + SMC_SET_RPC(lp, lp->rpc_cur_mode); + SMC_SELECT_BANK(lp, 2); + + return 1; +} + +/* + * smc_phy_reset - reset the phy + * @dev: net device + * @phy: phy address + * + * Issue a software reset for the specified PHY and + * wait up to 100ms for the reset to complete. We should + * not access the PHY for 50ms after issuing the reset. + * + * The time to wait appears to be dependent on the PHY. + * + * Must be called with lp->lock locked. + */ +static int smc_phy_reset(struct net_device *dev, int phy) +{ + struct smc_local *lp = netdev_priv(dev); + unsigned int bmcr; + int timeout; + + smc_phy_write(dev, phy, MII_BMCR, BMCR_RESET); + + for (timeout = 2; timeout; timeout--) { + spin_unlock_irq(&lp->lock); + msleep(50); + spin_lock_irq(&lp->lock); + + bmcr = smc_phy_read(dev, phy, MII_BMCR); + if (!(bmcr & BMCR_RESET)) + break; + } + + return bmcr & BMCR_RESET; +} + +/* + * smc_phy_powerdown - powerdown phy + * @dev: net device + * + * Power down the specified PHY + */ +static void smc_phy_powerdown(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + unsigned int bmcr; + int phy = lp->mii.phy_id; + + if (lp->phy_type == 0) + return; + + /* We need to ensure that no calls to smc_phy_configure are + pending. + */ + cancel_work_sync(&lp->phy_configure); + + bmcr = smc_phy_read(dev, phy, MII_BMCR); + smc_phy_write(dev, phy, MII_BMCR, bmcr | BMCR_PDOWN); +} + +/* + * smc_phy_check_media - check the media status and adjust TCR + * @dev: net device + * @init: set true for initialisation + * + * Select duplex mode depending on negotiation state. This + * also updates our carrier state. + */ +static void smc_phy_check_media(struct net_device *dev, int init) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + + if (mii_check_media(&lp->mii, netif_msg_link(lp), init)) { + /* duplex state has changed */ + if (lp->mii.full_duplex) { + lp->tcr_cur_mode |= TCR_SWFDUP; + } else { + lp->tcr_cur_mode &= ~TCR_SWFDUP; + } + + SMC_SELECT_BANK(lp, 0); + SMC_SET_TCR(lp, lp->tcr_cur_mode); + } +} + +/* + * Configures the specified PHY through the MII management interface + * using Autonegotiation. + * Calls smc_phy_fixed() if the user has requested a certain config. + * If RPC ANEG bit is set, the media selection is dependent purely on + * the selection by the MII (either in the MII BMCR reg or the result + * of autonegotiation.) If the RPC ANEG bit is cleared, the selection + * is controlled by the RPC SPEED and RPC DPLX bits. + */ +static void smc_phy_configure(struct work_struct *work) +{ + struct smc_local *lp = + container_of(work, struct smc_local, phy_configure); + struct net_device *dev = lp->dev; + void __iomem *ioaddr = lp->base; + int phyaddr = lp->mii.phy_id; + int my_phy_caps; /* My PHY capabilities */ + int my_ad_caps; /* My Advertised capabilities */ + int status; + + DBG(3, "%s:smc_program_phy()\n", dev->name); + + spin_lock_irq(&lp->lock); + + /* + * We should not be called if phy_type is zero. + */ + if (lp->phy_type == 0) + goto smc_phy_configure_exit; + + if (smc_phy_reset(dev, phyaddr)) { + printk("%s: PHY reset timed out\n", dev->name); + goto smc_phy_configure_exit; + } + + /* + * Enable PHY Interrupts (for register 18) + * Interrupts listed here are disabled + */ + smc_phy_write(dev, phyaddr, PHY_MASK_REG, + PHY_INT_LOSSSYNC | PHY_INT_CWRD | PHY_INT_SSD | + PHY_INT_ESD | PHY_INT_RPOL | PHY_INT_JAB | + PHY_INT_SPDDET | PHY_INT_DPLXDET); + + /* Configure the Receive/Phy Control register */ + SMC_SELECT_BANK(lp, 0); + SMC_SET_RPC(lp, lp->rpc_cur_mode); + + /* If the user requested no auto neg, then go set his request */ + if (lp->mii.force_media) { + smc_phy_fixed(dev); + goto smc_phy_configure_exit; + } + + /* Copy our capabilities from MII_BMSR to MII_ADVERTISE */ + my_phy_caps = smc_phy_read(dev, phyaddr, MII_BMSR); + + if (!(my_phy_caps & BMSR_ANEGCAPABLE)) { + printk(KERN_INFO "Auto negotiation NOT supported\n"); + smc_phy_fixed(dev); + goto smc_phy_configure_exit; + } + + my_ad_caps = ADVERTISE_CSMA; /* I am CSMA capable */ + + if (my_phy_caps & BMSR_100BASE4) + my_ad_caps |= ADVERTISE_100BASE4; + if (my_phy_caps & BMSR_100FULL) + my_ad_caps |= ADVERTISE_100FULL; + if (my_phy_caps & BMSR_100HALF) + my_ad_caps |= ADVERTISE_100HALF; + if (my_phy_caps & BMSR_10FULL) + my_ad_caps |= ADVERTISE_10FULL; + if (my_phy_caps & BMSR_10HALF) + my_ad_caps |= ADVERTISE_10HALF; + + /* Disable capabilities not selected by our user */ + if (lp->ctl_rspeed != 100) + my_ad_caps &= ~(ADVERTISE_100BASE4|ADVERTISE_100FULL|ADVERTISE_100HALF); + + if (!lp->ctl_rfduplx) + my_ad_caps &= ~(ADVERTISE_100FULL|ADVERTISE_10FULL); + + /* Update our Auto-Neg Advertisement Register */ + smc_phy_write(dev, phyaddr, MII_ADVERTISE, my_ad_caps); + lp->mii.advertising = my_ad_caps; + + /* + * Read the register back. Without this, it appears that when + * auto-negotiation is restarted, sometimes it isn't ready and + * the link does not come up. + */ + status = smc_phy_read(dev, phyaddr, MII_ADVERTISE); + + DBG(2, "%s: phy caps=%x\n", dev->name, my_phy_caps); + DBG(2, "%s: phy advertised caps=%x\n", dev->name, my_ad_caps); + + /* Restart auto-negotiation process in order to advertise my caps */ + smc_phy_write(dev, phyaddr, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + + smc_phy_check_media(dev, 1); + +smc_phy_configure_exit: + SMC_SELECT_BANK(lp, 2); + spin_unlock_irq(&lp->lock); +} + +/* + * smc_phy_interrupt + * + * Purpose: Handle interrupts relating to PHY register 18. This is + * called from the "hard" interrupt handler under our private spinlock. + */ +static void smc_phy_interrupt(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + int phyaddr = lp->mii.phy_id; + int phy18; + + DBG(2, "%s: %s\n", dev->name, __func__); + + if (lp->phy_type == 0) + return; + + for(;;) { + smc_phy_check_media(dev, 0); + + /* Read PHY Register 18, Status Output */ + phy18 = smc_phy_read(dev, phyaddr, PHY_INT_REG); + if ((phy18 & PHY_INT_INT) == 0) + break; + } +} + +/*--- END PHY CONTROL AND CONFIGURATION-------------------------------------*/ + +static void smc_10bt_check_media(struct net_device *dev, int init) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned int old_carrier, new_carrier; + + old_carrier = netif_carrier_ok(dev) ? 1 : 0; + + SMC_SELECT_BANK(lp, 0); + new_carrier = (SMC_GET_EPH_STATUS(lp) & ES_LINK_OK) ? 1 : 0; + SMC_SELECT_BANK(lp, 2); + + if (init || (old_carrier != new_carrier)) { + if (!new_carrier) { + netif_carrier_off(dev); + } else { + netif_carrier_on(dev); + } + if (netif_msg_link(lp)) + printk(KERN_INFO "%s: link %s\n", dev->name, + new_carrier ? "up" : "down"); + } +} + +static void smc_eph_interrupt(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned int ctl; + + smc_10bt_check_media(dev, 0); + + SMC_SELECT_BANK(lp, 1); + ctl = SMC_GET_CTL(lp); + SMC_SET_CTL(lp, ctl & ~CTL_LE_ENABLE); + SMC_SET_CTL(lp, ctl); + SMC_SELECT_BANK(lp, 2); +} + +/* + * This is the main routine of the driver, to handle the device when + * it needs some attention. + */ +static irqreturn_t smc_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + int status, mask, timeout, card_stats; + int saved_pointer; + + DBG(3, "%s: %s\n", dev->name, __func__); + + spin_lock(&lp->lock); + + /* A preamble may be used when there is a potential race + * between the interruptible transmit functions and this + * ISR. */ + SMC_INTERRUPT_PREAMBLE; + + saved_pointer = SMC_GET_PTR(lp); + mask = SMC_GET_INT_MASK(lp); + SMC_SET_INT_MASK(lp, 0); + + /* set a timeout value, so I don't stay here forever */ + timeout = MAX_IRQ_LOOPS; + + do { + status = SMC_GET_INT(lp); + + DBG(2, "%s: INT 0x%02x MASK 0x%02x MEM 0x%04x FIFO 0x%04x\n", + dev->name, status, mask, + ({ int meminfo; SMC_SELECT_BANK(lp, 0); + meminfo = SMC_GET_MIR(lp); + SMC_SELECT_BANK(lp, 2); meminfo; }), + SMC_GET_FIFO(lp)); + + status &= mask; + if (!status) + break; + + if (status & IM_TX_INT) { + /* do this before RX as it will free memory quickly */ + DBG(3, "%s: TX int\n", dev->name); + smc_tx(dev); + SMC_ACK_INT(lp, IM_TX_INT); + if (THROTTLE_TX_PKTS) + netif_wake_queue(dev); + } else if (status & IM_RCV_INT) { + DBG(3, "%s: RX irq\n", dev->name); + smc_rcv(dev); + } else if (status & IM_ALLOC_INT) { + DBG(3, "%s: Allocation irq\n", dev->name); + tasklet_hi_schedule(&lp->tx_task); + mask &= ~IM_ALLOC_INT; + } else if (status & IM_TX_EMPTY_INT) { + DBG(3, "%s: TX empty\n", dev->name); + mask &= ~IM_TX_EMPTY_INT; + + /* update stats */ + SMC_SELECT_BANK(lp, 0); + card_stats = SMC_GET_COUNTER(lp); + SMC_SELECT_BANK(lp, 2); + + /* single collisions */ + dev->stats.collisions += card_stats & 0xF; + card_stats >>= 4; + + /* multiple collisions */ + dev->stats.collisions += card_stats & 0xF; + } else if (status & IM_RX_OVRN_INT) { + DBG(1, "%s: RX overrun (EPH_ST 0x%04x)\n", dev->name, + ({ int eph_st; SMC_SELECT_BANK(lp, 0); + eph_st = SMC_GET_EPH_STATUS(lp); + SMC_SELECT_BANK(lp, 2); eph_st; })); + SMC_ACK_INT(lp, IM_RX_OVRN_INT); + dev->stats.rx_errors++; + dev->stats.rx_fifo_errors++; + } else if (status & IM_EPH_INT) { + smc_eph_interrupt(dev); + } else if (status & IM_MDINT) { + SMC_ACK_INT(lp, IM_MDINT); + smc_phy_interrupt(dev); + } else if (status & IM_ERCV_INT) { + SMC_ACK_INT(lp, IM_ERCV_INT); + PRINTK("%s: UNSUPPORTED: ERCV INTERRUPT\n", dev->name); + } + } while (--timeout); + + /* restore register states */ + SMC_SET_PTR(lp, saved_pointer); + SMC_SET_INT_MASK(lp, mask); + spin_unlock(&lp->lock); + +#ifndef CONFIG_NET_POLL_CONTROLLER + if (timeout == MAX_IRQ_LOOPS) + PRINTK("%s: spurious interrupt (mask = 0x%02x)\n", + dev->name, mask); +#endif + DBG(3, "%s: Interrupt done (%d loops)\n", + dev->name, MAX_IRQ_LOOPS - timeout); + + /* + * We return IRQ_HANDLED unconditionally here even if there was + * nothing to do. There is a possibility that a packet might + * get enqueued into the chip right after TX_EMPTY_INT is raised + * but just before the CPU acknowledges the IRQ. + * Better take an unneeded IRQ in some occasions than complexifying + * the code for all cases. + */ + return IRQ_HANDLED; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +/* + * Polling receive - used by netconsole and other diagnostic tools + * to allow network i/o with interrupts disabled. + */ +static void smc_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + smc_interrupt(dev->irq, dev); + enable_irq(dev->irq); +} +#endif + +/* Our watchdog timed out. Called by the networking layer */ +static void smc_timeout(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + int status, mask, eph_st, meminfo, fifo; + + DBG(2, "%s: %s\n", dev->name, __func__); + + spin_lock_irq(&lp->lock); + status = SMC_GET_INT(lp); + mask = SMC_GET_INT_MASK(lp); + fifo = SMC_GET_FIFO(lp); + SMC_SELECT_BANK(lp, 0); + eph_st = SMC_GET_EPH_STATUS(lp); + meminfo = SMC_GET_MIR(lp); + SMC_SELECT_BANK(lp, 2); + spin_unlock_irq(&lp->lock); + PRINTK( "%s: TX timeout (INT 0x%02x INTMASK 0x%02x " + "MEM 0x%04x FIFO 0x%04x EPH_ST 0x%04x)\n", + dev->name, status, mask, meminfo, fifo, eph_st ); + + smc_reset(dev); + smc_enable(dev); + + /* + * Reconfiguring the PHY doesn't seem like a bad idea here, but + * smc_phy_configure() calls msleep() which calls schedule_timeout() + * which calls schedule(). Hence we use a work queue. + */ + if (lp->phy_type != 0) + schedule_work(&lp->phy_configure); + + /* We can accept TX packets again */ + dev->trans_start = jiffies; /* prevent tx timeout */ + netif_wake_queue(dev); +} + +/* + * This routine will, depending on the values passed to it, + * either make it accept multicast packets, go into + * promiscuous mode (for TCPDUMP and cousins) or accept + * a select set of multicast packets + */ +static void smc_set_multicast_list(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + unsigned char multicast_table[8]; + int update_multicast = 0; + + DBG(2, "%s: %s\n", dev->name, __func__); + + if (dev->flags & IFF_PROMISC) { + DBG(2, "%s: RCR_PRMS\n", dev->name); + lp->rcr_cur_mode |= RCR_PRMS; + } + +/* BUG? I never disable promiscuous mode if multicasting was turned on. + Now, I turn off promiscuous mode, but I don't do anything to multicasting + when promiscuous mode is turned on. +*/ + + /* + * Here, I am setting this to accept all multicast packets. + * I don't need to zero the multicast table, because the flag is + * checked before the table is + */ + else if (dev->flags & IFF_ALLMULTI || netdev_mc_count(dev) > 16) { + DBG(2, "%s: RCR_ALMUL\n", dev->name); + lp->rcr_cur_mode |= RCR_ALMUL; + } + + /* + * This sets the internal hardware table to filter out unwanted + * multicast packets before they take up memory. + * + * The SMC chip uses a hash table where the high 6 bits of the CRC of + * address are the offset into the table. If that bit is 1, then the + * multicast packet is accepted. Otherwise, it's dropped silently. + * + * To use the 6 bits as an offset into the table, the high 3 bits are + * the number of the 8 bit register, while the low 3 bits are the bit + * within that register. + */ + else if (!netdev_mc_empty(dev)) { + struct netdev_hw_addr *ha; + + /* table for flipping the order of 3 bits */ + static const unsigned char invert3[] = {0, 4, 2, 6, 1, 5, 3, 7}; + + /* start with a table of all zeros: reject all */ + memset(multicast_table, 0, sizeof(multicast_table)); + + netdev_for_each_mc_addr(ha, dev) { + int position; + + /* only use the low order bits */ + position = crc32_le(~0, ha->addr, 6) & 0x3f; + + /* do some messy swapping to put the bit in the right spot */ + multicast_table[invert3[position&7]] |= + (1<<invert3[(position>>3)&7]); + } + + /* be sure I get rid of flags I might have set */ + lp->rcr_cur_mode &= ~(RCR_PRMS | RCR_ALMUL); + + /* now, the table can be loaded into the chipset */ + update_multicast = 1; + } else { + DBG(2, "%s: ~(RCR_PRMS|RCR_ALMUL)\n", dev->name); + lp->rcr_cur_mode &= ~(RCR_PRMS | RCR_ALMUL); + + /* + * since I'm disabling all multicast entirely, I need to + * clear the multicast list + */ + memset(multicast_table, 0, sizeof(multicast_table)); + update_multicast = 1; + } + + spin_lock_irq(&lp->lock); + SMC_SELECT_BANK(lp, 0); + SMC_SET_RCR(lp, lp->rcr_cur_mode); + if (update_multicast) { + SMC_SELECT_BANK(lp, 3); + SMC_SET_MCAST(lp, multicast_table); + } + SMC_SELECT_BANK(lp, 2); + spin_unlock_irq(&lp->lock); +} + + +/* + * Open and Initialize the board + * + * Set up everything, reset the card, etc.. + */ +static int +smc_open(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + + DBG(2, "%s: %s\n", dev->name, __func__); + + /* + * Check that the address is valid. If its not, refuse + * to bring the device up. The user must specify an + * address using ifconfig eth0 hw ether xx:xx:xx:xx:xx:xx + */ + if (!is_valid_ether_addr(dev->dev_addr)) { + PRINTK("%s: no valid ethernet hw addr\n", __func__); + return -EINVAL; + } + + /* Setup the default Register Modes */ + lp->tcr_cur_mode = TCR_DEFAULT; + lp->rcr_cur_mode = RCR_DEFAULT; + lp->rpc_cur_mode = RPC_DEFAULT | + lp->cfg.leda << RPC_LSXA_SHFT | + lp->cfg.ledb << RPC_LSXB_SHFT; + + /* + * If we are not using a MII interface, we need to + * monitor our own carrier signal to detect faults. + */ + if (lp->phy_type == 0) + lp->tcr_cur_mode |= TCR_MON_CSN; + + /* reset the hardware */ + smc_reset(dev); + smc_enable(dev); + + /* Configure the PHY, initialize the link state */ + if (lp->phy_type != 0) + smc_phy_configure(&lp->phy_configure); + else { + spin_lock_irq(&lp->lock); + smc_10bt_check_media(dev, 1); + spin_unlock_irq(&lp->lock); + } + + netif_start_queue(dev); + return 0; +} + +/* + * smc_close + * + * this makes the board clean up everything that it can + * and not talk to the outside world. Caused by + * an 'ifconfig ethX down' + */ +static int smc_close(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + + DBG(2, "%s: %s\n", dev->name, __func__); + + netif_stop_queue(dev); + netif_carrier_off(dev); + + /* clear everything */ + smc_shutdown(dev); + tasklet_kill(&lp->tx_task); + smc_phy_powerdown(dev); + return 0; +} + +/* + * Ethtool support + */ +static int +smc_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smc_local *lp = netdev_priv(dev); + int ret; + + cmd->maxtxpkt = 1; + cmd->maxrxpkt = 1; + + if (lp->phy_type != 0) { + spin_lock_irq(&lp->lock); + ret = mii_ethtool_gset(&lp->mii, cmd); + spin_unlock_irq(&lp->lock); + } else { + cmd->supported = SUPPORTED_10baseT_Half | + SUPPORTED_10baseT_Full | + SUPPORTED_TP | SUPPORTED_AUI; + + if (lp->ctl_rspeed == 10) + ethtool_cmd_speed_set(cmd, SPEED_10); + else if (lp->ctl_rspeed == 100) + ethtool_cmd_speed_set(cmd, SPEED_100); + + cmd->autoneg = AUTONEG_DISABLE; + cmd->transceiver = XCVR_INTERNAL; + cmd->port = 0; + cmd->duplex = lp->tcr_cur_mode & TCR_SWFDUP ? DUPLEX_FULL : DUPLEX_HALF; + + ret = 0; + } + + return ret; +} + +static int +smc_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smc_local *lp = netdev_priv(dev); + int ret; + + if (lp->phy_type != 0) { + spin_lock_irq(&lp->lock); + ret = mii_ethtool_sset(&lp->mii, cmd); + spin_unlock_irq(&lp->lock); + } else { + if (cmd->autoneg != AUTONEG_DISABLE || + cmd->speed != SPEED_10 || + (cmd->duplex != DUPLEX_HALF && cmd->duplex != DUPLEX_FULL) || + (cmd->port != PORT_TP && cmd->port != PORT_AUI)) + return -EINVAL; + +// lp->port = cmd->port; + lp->ctl_rfduplx = cmd->duplex == DUPLEX_FULL; + +// if (netif_running(dev)) +// smc_set_port(dev); + + ret = 0; + } + + return ret; +} + +static void +smc_ethtool_getdrvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + strncpy(info->driver, CARDNAME, sizeof(info->driver)); + strncpy(info->version, version, sizeof(info->version)); + strncpy(info->bus_info, dev_name(dev->dev.parent), sizeof(info->bus_info)); +} + +static int smc_ethtool_nwayreset(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + int ret = -EINVAL; + + if (lp->phy_type != 0) { + spin_lock_irq(&lp->lock); + ret = mii_nway_restart(&lp->mii); + spin_unlock_irq(&lp->lock); + } + + return ret; +} + +static u32 smc_ethtool_getmsglevel(struct net_device *dev) +{ + struct smc_local *lp = netdev_priv(dev); + return lp->msg_enable; +} + +static void smc_ethtool_setmsglevel(struct net_device *dev, u32 level) +{ + struct smc_local *lp = netdev_priv(dev); + lp->msg_enable = level; +} + +static int smc_write_eeprom_word(struct net_device *dev, u16 addr, u16 word) +{ + u16 ctl; + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + + spin_lock_irq(&lp->lock); + /* load word into GP register */ + SMC_SELECT_BANK(lp, 1); + SMC_SET_GP(lp, word); + /* set the address to put the data in EEPROM */ + SMC_SELECT_BANK(lp, 2); + SMC_SET_PTR(lp, addr); + /* tell it to write */ + SMC_SELECT_BANK(lp, 1); + ctl = SMC_GET_CTL(lp); + SMC_SET_CTL(lp, ctl | (CTL_EEPROM_SELECT | CTL_STORE)); + /* wait for it to finish */ + do { + udelay(1); + } while (SMC_GET_CTL(lp) & CTL_STORE); + /* clean up */ + SMC_SET_CTL(lp, ctl); + SMC_SELECT_BANK(lp, 2); + spin_unlock_irq(&lp->lock); + return 0; +} + +static int smc_read_eeprom_word(struct net_device *dev, u16 addr, u16 *word) +{ + u16 ctl; + struct smc_local *lp = netdev_priv(dev); + void __iomem *ioaddr = lp->base; + + spin_lock_irq(&lp->lock); + /* set the EEPROM address to get the data from */ + SMC_SELECT_BANK(lp, 2); + SMC_SET_PTR(lp, addr | PTR_READ); + /* tell it to load */ + SMC_SELECT_BANK(lp, 1); + SMC_SET_GP(lp, 0xffff); /* init to known */ + ctl = SMC_GET_CTL(lp); + SMC_SET_CTL(lp, ctl | (CTL_EEPROM_SELECT | CTL_RELOAD)); + /* wait for it to finish */ + do { + udelay(1); + } while (SMC_GET_CTL(lp) & CTL_RELOAD); + /* read word from GP register */ + *word = SMC_GET_GP(lp); + /* clean up */ + SMC_SET_CTL(lp, ctl); + SMC_SELECT_BANK(lp, 2); + spin_unlock_irq(&lp->lock); + return 0; +} + +static int smc_ethtool_geteeprom_len(struct net_device *dev) +{ + return 0x23 * 2; +} + +static int smc_ethtool_geteeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + int i; + int imax; + + DBG(1, "Reading %d bytes at %d(0x%x)\n", + eeprom->len, eeprom->offset, eeprom->offset); + imax = smc_ethtool_geteeprom_len(dev); + for (i = 0; i < eeprom->len; i += 2) { + int ret; + u16 wbuf; + int offset = i + eeprom->offset; + if (offset > imax) + break; + ret = smc_read_eeprom_word(dev, offset >> 1, &wbuf); + if (ret != 0) + return ret; + DBG(2, "Read 0x%x from 0x%x\n", wbuf, offset >> 1); + data[i] = (wbuf >> 8) & 0xff; + data[i+1] = wbuf & 0xff; + } + return 0; +} + +static int smc_ethtool_seteeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + int i; + int imax; + + DBG(1, "Writing %d bytes to %d(0x%x)\n", + eeprom->len, eeprom->offset, eeprom->offset); + imax = smc_ethtool_geteeprom_len(dev); + for (i = 0; i < eeprom->len; i += 2) { + int ret; + u16 wbuf; + int offset = i + eeprom->offset; + if (offset > imax) + break; + wbuf = (data[i] << 8) | data[i + 1]; + DBG(2, "Writing 0x%x to 0x%x\n", wbuf, offset >> 1); + ret = smc_write_eeprom_word(dev, offset >> 1, wbuf); + if (ret != 0) + return ret; + } + return 0; +} + + +static const struct ethtool_ops smc_ethtool_ops = { + .get_settings = smc_ethtool_getsettings, + .set_settings = smc_ethtool_setsettings, + .get_drvinfo = smc_ethtool_getdrvinfo, + + .get_msglevel = smc_ethtool_getmsglevel, + .set_msglevel = smc_ethtool_setmsglevel, + .nway_reset = smc_ethtool_nwayreset, + .get_link = ethtool_op_get_link, + .get_eeprom_len = smc_ethtool_geteeprom_len, + .get_eeprom = smc_ethtool_geteeprom, + .set_eeprom = smc_ethtool_seteeprom, +}; + +static const struct net_device_ops smc_netdev_ops = { + .ndo_open = smc_open, + .ndo_stop = smc_close, + .ndo_start_xmit = smc_hard_start_xmit, + .ndo_tx_timeout = smc_timeout, + .ndo_set_multicast_list = smc_set_multicast_list, + .ndo_change_mtu = eth_change_mtu, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = eth_mac_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = smc_poll_controller, +#endif +}; + +/* + * smc_findirq + * + * This routine has a simple purpose -- make the SMC chip generate an + * interrupt, so an auto-detect routine can detect it, and find the IRQ, + */ +/* + * does this still work? + * + * I just deleted auto_irq.c, since it was never built... + * --jgarzik + */ +static int __devinit smc_findirq(struct smc_local *lp) +{ + void __iomem *ioaddr = lp->base; + int timeout = 20; + unsigned long cookie; + + DBG(2, "%s: %s\n", CARDNAME, __func__); + + cookie = probe_irq_on(); + + /* + * What I try to do here is trigger an ALLOC_INT. This is done + * by allocating a small chunk of memory, which will give an interrupt + * when done. + */ + /* enable ALLOCation interrupts ONLY */ + SMC_SELECT_BANK(lp, 2); + SMC_SET_INT_MASK(lp, IM_ALLOC_INT); + + /* + * Allocate 512 bytes of memory. Note that the chip was just + * reset so all the memory is available + */ + SMC_SET_MMU_CMD(lp, MC_ALLOC | 1); + + /* + * Wait until positive that the interrupt has been generated + */ + do { + int int_status; + udelay(10); + int_status = SMC_GET_INT(lp); + if (int_status & IM_ALLOC_INT) + break; /* got the interrupt */ + } while (--timeout); + + /* + * there is really nothing that I can do here if timeout fails, + * as autoirq_report will return a 0 anyway, which is what I + * want in this case. Plus, the clean up is needed in both + * cases. + */ + + /* and disable all interrupts again */ + SMC_SET_INT_MASK(lp, 0); + + /* and return what I found */ + return probe_irq_off(cookie); +} + +/* + * Function: smc_probe(unsigned long ioaddr) + * + * Purpose: + * Tests to see if a given ioaddr points to an SMC91x chip. + * Returns a 0 on success + * + * Algorithm: + * (1) see if the high byte of BANK_SELECT is 0x33 + * (2) compare the ioaddr with the base register's address + * (3) see if I recognize the chip ID in the appropriate register + * + * Here I do typical initialization tasks. + * + * o Initialize the structure if needed + * o print out my vanity message if not done so already + * o print out what type of hardware is detected + * o print out the ethernet address + * o find the IRQ + * o set up my private data + * o configure the dev structure with my subroutines + * o actually GRAB the irq. + * o GRAB the region + */ +static int __devinit smc_probe(struct net_device *dev, void __iomem *ioaddr, + unsigned long irq_flags) +{ + struct smc_local *lp = netdev_priv(dev); + static int version_printed = 0; + int retval; + unsigned int val, revision_register; + const char *version_string; + + DBG(2, "%s: %s\n", CARDNAME, __func__); + + /* First, see if the high byte is 0x33 */ + val = SMC_CURRENT_BANK(lp); + DBG(2, "%s: bank signature probe returned 0x%04x\n", CARDNAME, val); + if ((val & 0xFF00) != 0x3300) { + if ((val & 0xFF) == 0x33) { + printk(KERN_WARNING + "%s: Detected possible byte-swapped interface" + " at IOADDR %p\n", CARDNAME, ioaddr); + } + retval = -ENODEV; + goto err_out; + } + + /* + * The above MIGHT indicate a device, but I need to write to + * further test this. + */ + SMC_SELECT_BANK(lp, 0); + val = SMC_CURRENT_BANK(lp); + if ((val & 0xFF00) != 0x3300) { + retval = -ENODEV; + goto err_out; + } + + /* + * well, we've already written once, so hopefully another + * time won't hurt. This time, I need to switch the bank + * register to bank 1, so I can access the base address + * register + */ + SMC_SELECT_BANK(lp, 1); + val = SMC_GET_BASE(lp); + val = ((val & 0x1F00) >> 3) << SMC_IO_SHIFT; + if (((unsigned int)ioaddr & (0x3e0 << SMC_IO_SHIFT)) != val) { + printk("%s: IOADDR %p doesn't match configuration (%x).\n", + CARDNAME, ioaddr, val); + } + + /* + * check if the revision register is something that I + * recognize. These might need to be added to later, + * as future revisions could be added. + */ + SMC_SELECT_BANK(lp, 3); + revision_register = SMC_GET_REV(lp); + DBG(2, "%s: revision = 0x%04x\n", CARDNAME, revision_register); + version_string = chip_ids[ (revision_register >> 4) & 0xF]; + if (!version_string || (revision_register & 0xff00) != 0x3300) { + /* I don't recognize this chip, so... */ + printk("%s: IO %p: Unrecognized revision register 0x%04x" + ", Contact author.\n", CARDNAME, + ioaddr, revision_register); + + retval = -ENODEV; + goto err_out; + } + + /* At this point I'll assume that the chip is an SMC91x. */ + if (version_printed++ == 0) + printk("%s", version); + + /* fill in some of the fields */ + dev->base_addr = (unsigned long)ioaddr; + lp->base = ioaddr; + lp->version = revision_register & 0xff; + spin_lock_init(&lp->lock); + + /* Get the MAC address */ + SMC_SELECT_BANK(lp, 1); + SMC_GET_MAC_ADDR(lp, dev->dev_addr); + + /* now, reset the chip, and put it into a known state */ + smc_reset(dev); + + /* + * If dev->irq is 0, then the device has to be banged on to see + * what the IRQ is. + * + * This banging doesn't always detect the IRQ, for unknown reasons. + * a workaround is to reset the chip and try again. + * + * Interestingly, the DOS packet driver *SETS* the IRQ on the card to + * be what is requested on the command line. I don't do that, mostly + * because the card that I have uses a non-standard method of accessing + * the IRQs, and because this _should_ work in most configurations. + * + * Specifying an IRQ is done with the assumption that the user knows + * what (s)he is doing. No checking is done!!!! + */ + if (dev->irq < 1) { + int trials; + + trials = 3; + while (trials--) { + dev->irq = smc_findirq(lp); + if (dev->irq) + break; + /* kick the card and try again */ + smc_reset(dev); + } + } + if (dev->irq == 0) { + printk("%s: Couldn't autodetect your IRQ. Use irq=xx.\n", + dev->name); + retval = -ENODEV; + goto err_out; + } + dev->irq = irq_canonicalize(dev->irq); + + /* Fill in the fields of the device structure with ethernet values. */ + ether_setup(dev); + + dev->watchdog_timeo = msecs_to_jiffies(watchdog); + dev->netdev_ops = &smc_netdev_ops; + dev->ethtool_ops = &smc_ethtool_ops; + + tasklet_init(&lp->tx_task, smc_hardware_send_pkt, (unsigned long)dev); + INIT_WORK(&lp->phy_configure, smc_phy_configure); + lp->dev = dev; + lp->mii.phy_id_mask = 0x1f; + lp->mii.reg_num_mask = 0x1f; + lp->mii.force_media = 0; + lp->mii.full_duplex = 0; + lp->mii.dev = dev; + lp->mii.mdio_read = smc_phy_read; + lp->mii.mdio_write = smc_phy_write; + + /* + * Locate the phy, if any. + */ + if (lp->version >= (CHIP_91100 << 4)) + smc_phy_detect(dev); + + /* then shut everything down to save power */ + smc_shutdown(dev); + smc_phy_powerdown(dev); + + /* Set default parameters */ + lp->msg_enable = NETIF_MSG_LINK; + lp->ctl_rfduplx = 0; + lp->ctl_rspeed = 10; + + if (lp->version >= (CHIP_91100 << 4)) { + lp->ctl_rfduplx = 1; + lp->ctl_rspeed = 100; + } + + /* Grab the IRQ */ + retval = request_irq(dev->irq, smc_interrupt, irq_flags, dev->name, dev); + if (retval) + goto err_out; + +#ifdef CONFIG_ARCH_PXA +# ifdef SMC_USE_PXA_DMA + lp->cfg.flags |= SMC91X_USE_DMA; +# endif + if (lp->cfg.flags & SMC91X_USE_DMA) { + int dma = pxa_request_dma(dev->name, DMA_PRIO_LOW, + smc_pxa_dma_irq, NULL); + if (dma >= 0) + dev->dma = dma; + } +#endif + + retval = register_netdev(dev); + if (retval == 0) { + /* now, print out the card info, in a short format.. */ + printk("%s: %s (rev %d) at %p IRQ %d", + dev->name, version_string, revision_register & 0x0f, + lp->base, dev->irq); + + if (dev->dma != (unsigned char)-1) + printk(" DMA %d", dev->dma); + + printk("%s%s\n", + lp->cfg.flags & SMC91X_NOWAIT ? " [nowait]" : "", + THROTTLE_TX_PKTS ? " [throttle_tx]" : ""); + + if (!is_valid_ether_addr(dev->dev_addr)) { + printk("%s: Invalid ethernet MAC address. Please " + "set using ifconfig\n", dev->name); + } else { + /* Print the Ethernet address */ + printk("%s: Ethernet addr: %pM\n", + dev->name, dev->dev_addr); + } + + if (lp->phy_type == 0) { + PRINTK("%s: No PHY found\n", dev->name); + } else if ((lp->phy_type & 0xfffffff0) == 0x0016f840) { + PRINTK("%s: PHY LAN83C183 (LAN91C111 Internal)\n", dev->name); + } else if ((lp->phy_type & 0xfffffff0) == 0x02821c50) { + PRINTK("%s: PHY LAN83C180\n", dev->name); + } + } + +err_out: +#ifdef CONFIG_ARCH_PXA + if (retval && dev->dma != (unsigned char)-1) + pxa_free_dma(dev->dma); +#endif + return retval; +} + +static int smc_enable_device(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct smc_local *lp = netdev_priv(ndev); + unsigned long flags; + unsigned char ecor, ecsr; + void __iomem *addr; + struct resource * res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-attrib"); + if (!res) + return 0; + + /* + * Map the attribute space. This is overkill, but clean. + */ + addr = ioremap(res->start, ATTRIB_SIZE); + if (!addr) + return -ENOMEM; + + /* + * Reset the device. We must disable IRQs around this + * since a reset causes the IRQ line become active. + */ + local_irq_save(flags); + ecor = readb(addr + (ECOR << SMC_IO_SHIFT)) & ~ECOR_RESET; + writeb(ecor | ECOR_RESET, addr + (ECOR << SMC_IO_SHIFT)); + readb(addr + (ECOR << SMC_IO_SHIFT)); + + /* + * Wait 100us for the chip to reset. + */ + udelay(100); + + /* + * The device will ignore all writes to the enable bit while + * reset is asserted, even if the reset bit is cleared in the + * same write. Must clear reset first, then enable the device. + */ + writeb(ecor, addr + (ECOR << SMC_IO_SHIFT)); + writeb(ecor | ECOR_ENABLE, addr + (ECOR << SMC_IO_SHIFT)); + + /* + * Set the appropriate byte/word mode. + */ + ecsr = readb(addr + (ECSR << SMC_IO_SHIFT)) & ~ECSR_IOIS8; + if (!SMC_16BIT(lp)) + ecsr |= ECSR_IOIS8; + writeb(ecsr, addr + (ECSR << SMC_IO_SHIFT)); + local_irq_restore(flags); + + iounmap(addr); + + /* + * Wait for the chip to wake up. We could poll the control + * register in the main register space, but that isn't mapped + * yet. We know this is going to take 750us. + */ + msleep(1); + + return 0; +} + +static int smc_request_attrib(struct platform_device *pdev, + struct net_device *ndev) +{ + struct resource * res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-attrib"); + struct smc_local *lp __maybe_unused = netdev_priv(ndev); + + if (!res) + return 0; + + if (!request_mem_region(res->start, ATTRIB_SIZE, CARDNAME)) + return -EBUSY; + + return 0; +} + +static void smc_release_attrib(struct platform_device *pdev, + struct net_device *ndev) +{ + struct resource * res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-attrib"); + struct smc_local *lp __maybe_unused = netdev_priv(ndev); + + if (res) + release_mem_region(res->start, ATTRIB_SIZE); +} + +static inline void smc_request_datacs(struct platform_device *pdev, struct net_device *ndev) +{ + if (SMC_CAN_USE_DATACS) { + struct resource * res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-data32"); + struct smc_local *lp = netdev_priv(ndev); + + if (!res) + return; + + if(!request_mem_region(res->start, SMC_DATA_EXTENT, CARDNAME)) { + printk(KERN_INFO "%s: failed to request datacs memory region.\n", CARDNAME); + return; + } + + lp->datacs = ioremap(res->start, SMC_DATA_EXTENT); + } +} + +static void smc_release_datacs(struct platform_device *pdev, struct net_device *ndev) +{ + if (SMC_CAN_USE_DATACS) { + struct smc_local *lp = netdev_priv(ndev); + struct resource * res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-data32"); + + if (lp->datacs) + iounmap(lp->datacs); + + lp->datacs = NULL; + + if (res) + release_mem_region(res->start, SMC_DATA_EXTENT); + } +} + +/* + * smc_init(void) + * Input parameters: + * dev->base_addr == 0, try to find all possible locations + * dev->base_addr > 0x1ff, this is the address to check + * dev->base_addr == <anything else>, return failure code + * + * Output: + * 0 --> there is a device + * anything else, error + */ +static int __devinit smc_drv_probe(struct platform_device *pdev) +{ + struct smc91x_platdata *pd = pdev->dev.platform_data; + struct smc_local *lp; + struct net_device *ndev; + struct resource *res, *ires; + unsigned int __iomem *addr; + unsigned long irq_flags = SMC_IRQ_FLAGS; + int ret; + + ndev = alloc_etherdev(sizeof(struct smc_local)); + if (!ndev) { + printk("%s: could not allocate device.\n", CARDNAME); + ret = -ENOMEM; + goto out; + } + SET_NETDEV_DEV(ndev, &pdev->dev); + + /* get configuration from platform data, only allow use of + * bus width if both SMC_CAN_USE_xxx and SMC91X_USE_xxx are set. + */ + + lp = netdev_priv(ndev); + + if (pd) { + memcpy(&lp->cfg, pd, sizeof(lp->cfg)); + lp->io_shift = SMC91X_IO_SHIFT(lp->cfg.flags); + } else { + lp->cfg.flags |= (SMC_CAN_USE_8BIT) ? SMC91X_USE_8BIT : 0; + lp->cfg.flags |= (SMC_CAN_USE_16BIT) ? SMC91X_USE_16BIT : 0; + lp->cfg.flags |= (SMC_CAN_USE_32BIT) ? SMC91X_USE_32BIT : 0; + lp->cfg.flags |= (nowait) ? SMC91X_NOWAIT : 0; + } + + if (!lp->cfg.leda && !lp->cfg.ledb) { + lp->cfg.leda = RPC_LSA_DEFAULT; + lp->cfg.ledb = RPC_LSB_DEFAULT; + } + + ndev->dma = (unsigned char)-1; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-regs"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto out_free_netdev; + } + + + if (!request_mem_region(res->start, SMC_IO_EXTENT, CARDNAME)) { + ret = -EBUSY; + goto out_free_netdev; + } + + ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!ires) { + ret = -ENODEV; + goto out_release_io; + } + + ndev->irq = ires->start; + + if (irq_flags == -1 || ires->flags & IRQF_TRIGGER_MASK) + irq_flags = ires->flags & IRQF_TRIGGER_MASK; + + ret = smc_request_attrib(pdev, ndev); + if (ret) + goto out_release_io; +#if defined(CONFIG_SA1100_ASSABET) + NCR_0 |= NCR_ENET_OSC_EN; +#endif + platform_set_drvdata(pdev, ndev); + ret = smc_enable_device(pdev); + if (ret) + goto out_release_attrib; + + addr = ioremap(res->start, SMC_IO_EXTENT); + if (!addr) { + ret = -ENOMEM; + goto out_release_attrib; + } + +#ifdef CONFIG_ARCH_PXA + { + struct smc_local *lp = netdev_priv(ndev); + lp->device = &pdev->dev; + lp->physaddr = res->start; + } +#endif + + ret = smc_probe(ndev, addr, irq_flags); + if (ret != 0) + goto out_iounmap; + + smc_request_datacs(pdev, ndev); + + return 0; + + out_iounmap: + platform_set_drvdata(pdev, NULL); + iounmap(addr); + out_release_attrib: + smc_release_attrib(pdev, ndev); + out_release_io: + release_mem_region(res->start, SMC_IO_EXTENT); + out_free_netdev: + free_netdev(ndev); + out: + printk("%s: not found (%d).\n", CARDNAME, ret); + + return ret; +} + +static int __devexit smc_drv_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct smc_local *lp = netdev_priv(ndev); + struct resource *res; + + platform_set_drvdata(pdev, NULL); + + unregister_netdev(ndev); + + free_irq(ndev->irq, ndev); + +#ifdef CONFIG_ARCH_PXA + if (ndev->dma != (unsigned char)-1) + pxa_free_dma(ndev->dma); +#endif + iounmap(lp->base); + + smc_release_datacs(pdev,ndev); + smc_release_attrib(pdev,ndev); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "smc91x-regs"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, SMC_IO_EXTENT); + + free_netdev(ndev); + + return 0; +} + +static int smc_drv_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct net_device *ndev = platform_get_drvdata(pdev); + + if (ndev) { + if (netif_running(ndev)) { + netif_device_detach(ndev); + smc_shutdown(ndev); + smc_phy_powerdown(ndev); + } + } + return 0; +} + +static int smc_drv_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct net_device *ndev = platform_get_drvdata(pdev); + + if (ndev) { + struct smc_local *lp = netdev_priv(ndev); + smc_enable_device(pdev); + if (netif_running(ndev)) { + smc_reset(ndev); + smc_enable(ndev); + if (lp->phy_type != 0) + smc_phy_configure(&lp->phy_configure); + netif_device_attach(ndev); + } + } + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id smc91x_match[] = { + { .compatible = "smsc,lan91c94", }, + { .compatible = "smsc,lan91c111", }, + {}, +}; +MODULE_DEVICE_TABLE(of, smc91x_match); +#else +#define smc91x_match NULL +#endif + +static struct dev_pm_ops smc_drv_pm_ops = { + .suspend = smc_drv_suspend, + .resume = smc_drv_resume, +}; + +static struct platform_driver smc_driver = { + .probe = smc_drv_probe, + .remove = __devexit_p(smc_drv_remove), + .driver = { + .name = CARDNAME, + .owner = THIS_MODULE, + .pm = &smc_drv_pm_ops, + .of_match_table = smc91x_match, + }, +}; + +static int __init smc_init(void) +{ + return platform_driver_register(&smc_driver); +} + +static void __exit smc_cleanup(void) +{ + platform_driver_unregister(&smc_driver); +} + +module_init(smc_init); +module_exit(smc_cleanup); diff --git a/drivers/net/ethernet/smsc/smc91x.h b/drivers/net/ethernet/smsc/smc91x.h new file mode 100644 index 000000000000..5f53fbbf67be --- /dev/null +++ b/drivers/net/ethernet/smsc/smc91x.h @@ -0,0 +1,1180 @@ +/*------------------------------------------------------------------------ + . smc91x.h - macros for SMSC's 91C9x/91C1xx single-chip Ethernet device. + . + . Copyright (C) 1996 by Erik Stahlman + . Copyright (C) 2001 Standard Microsystems Corporation + . Developed by Simple Network Magic Corporation + . Copyright (C) 2003 Monta Vista Software, Inc. + . Unified SMC91x driver by Nicolas Pitre + . + . This program is free software; you can redistribute it and/or modify + . it under the terms of the GNU General Public License as published by + . the Free Software Foundation; either version 2 of the License, or + . (at your option) any later version. + . + . This program is distributed in the hope that it will be useful, + . but WITHOUT ANY WARRANTY; without even the implied warranty of + . MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + . GNU General Public License for more details. + . + . You should have received a copy of the GNU General Public License + . along with this program; if not, write to the Free Software + . Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + . + . Information contained in this file was obtained from the LAN91C111 + . manual from SMC. To get a copy, if you really want one, you can find + . information under www.smsc.com. + . + . Authors + . Erik Stahlman <erik@vt.edu> + . Daris A Nevil <dnevil@snmc.com> + . Nicolas Pitre <nico@fluxnic.net> + . + ---------------------------------------------------------------------------*/ +#ifndef _SMC91X_H_ +#define _SMC91X_H_ + +#include <linux/smc91x.h> + +/* + * Define your architecture specific bus configuration parameters here. + */ + +#if defined(CONFIG_ARCH_LUBBOCK) ||\ + defined(CONFIG_MACH_MAINSTONE) ||\ + defined(CONFIG_MACH_ZYLONITE) ||\ + defined(CONFIG_MACH_LITTLETON) ||\ + defined(CONFIG_MACH_ZYLONITE2) ||\ + defined(CONFIG_ARCH_VIPER) ||\ + defined(CONFIG_MACH_STARGATE2) + +#include <asm/mach-types.h> + +/* Now the bus width is specified in the platform data + * pretend here to support all I/O access types + */ +#define SMC_CAN_USE_8BIT 1 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 1 +#define SMC_NOWAIT 1 + +#define SMC_IO_SHIFT (lp->io_shift) + +#define SMC_inb(a, r) readb((a) + (r)) +#define SMC_inw(a, r) readw((a) + (r)) +#define SMC_inl(a, r) readl((a) + (r)) +#define SMC_outb(v, a, r) writeb(v, (a) + (r)) +#define SMC_outl(v, a, r) writel(v, (a) + (r)) +#define SMC_insw(a, r, p, l) readsw((a) + (r), p, l) +#define SMC_outsw(a, r, p, l) writesw((a) + (r), p, l) +#define SMC_insl(a, r, p, l) readsl((a) + (r), p, l) +#define SMC_outsl(a, r, p, l) writesl((a) + (r), p, l) +#define SMC_IRQ_FLAGS (-1) /* from resource */ + +/* We actually can't write halfwords properly if not word aligned */ +static inline void SMC_outw(u16 val, void __iomem *ioaddr, int reg) +{ + if ((machine_is_mainstone() || machine_is_stargate2()) && reg & 2) { + unsigned int v = val << 16; + v |= readl(ioaddr + (reg & ~2)) & 0xffff; + writel(v, ioaddr + (reg & ~2)); + } else { + writew(val, ioaddr + reg); + } +} + +#elif defined(CONFIG_SA1100_PLEB) +/* We can only do 16-bit reads and writes in the static memory space. */ +#define SMC_CAN_USE_8BIT 1 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 0 +#define SMC_IO_SHIFT 0 +#define SMC_NOWAIT 1 + +#define SMC_inb(a, r) readb((a) + (r)) +#define SMC_insb(a, r, p, l) readsb((a) + (r), p, (l)) +#define SMC_inw(a, r) readw((a) + (r)) +#define SMC_insw(a, r, p, l) readsw((a) + (r), p, l) +#define SMC_outb(v, a, r) writeb(v, (a) + (r)) +#define SMC_outsb(a, r, p, l) writesb((a) + (r), p, (l)) +#define SMC_outw(v, a, r) writew(v, (a) + (r)) +#define SMC_outsw(a, r, p, l) writesw((a) + (r), p, l) + +#define SMC_IRQ_FLAGS (-1) + +#elif defined(CONFIG_SA1100_ASSABET) + +#include <mach/neponset.h> + +/* We can only do 8-bit reads and writes in the static memory space. */ +#define SMC_CAN_USE_8BIT 1 +#define SMC_CAN_USE_16BIT 0 +#define SMC_CAN_USE_32BIT 0 +#define SMC_NOWAIT 1 + +/* The first two address lines aren't connected... */ +#define SMC_IO_SHIFT 2 + +#define SMC_inb(a, r) readb((a) + (r)) +#define SMC_outb(v, a, r) writeb(v, (a) + (r)) +#define SMC_insb(a, r, p, l) readsb((a) + (r), p, (l)) +#define SMC_outsb(a, r, p, l) writesb((a) + (r), p, (l)) +#define SMC_IRQ_FLAGS (-1) /* from resource */ + +#elif defined(CONFIG_MACH_LOGICPD_PXA270) || \ + defined(CONFIG_MACH_NOMADIK_8815NHK) + +#define SMC_CAN_USE_8BIT 0 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 0 +#define SMC_IO_SHIFT 0 +#define SMC_NOWAIT 1 + +#define SMC_inw(a, r) readw((a) + (r)) +#define SMC_outw(v, a, r) writew(v, (a) + (r)) +#define SMC_insw(a, r, p, l) readsw((a) + (r), p, l) +#define SMC_outsw(a, r, p, l) writesw((a) + (r), p, l) + +#elif defined(CONFIG_ARCH_INNOKOM) || \ + defined(CONFIG_ARCH_PXA_IDP) || \ + defined(CONFIG_ARCH_RAMSES) || \ + defined(CONFIG_ARCH_PCM027) + +#define SMC_CAN_USE_8BIT 1 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 1 +#define SMC_IO_SHIFT 0 +#define SMC_NOWAIT 1 +#define SMC_USE_PXA_DMA 1 + +#define SMC_inb(a, r) readb((a) + (r)) +#define SMC_inw(a, r) readw((a) + (r)) +#define SMC_inl(a, r) readl((a) + (r)) +#define SMC_outb(v, a, r) writeb(v, (a) + (r)) +#define SMC_outl(v, a, r) writel(v, (a) + (r)) +#define SMC_insl(a, r, p, l) readsl((a) + (r), p, l) +#define SMC_outsl(a, r, p, l) writesl((a) + (r), p, l) +#define SMC_IRQ_FLAGS (-1) /* from resource */ + +/* We actually can't write halfwords properly if not word aligned */ +static inline void +SMC_outw(u16 val, void __iomem *ioaddr, int reg) +{ + if (reg & 2) { + unsigned int v = val << 16; + v |= readl(ioaddr + (reg & ~2)) & 0xffff; + writel(v, ioaddr + (reg & ~2)); + } else { + writew(val, ioaddr + reg); + } +} + +#elif defined(CONFIG_SH_SH4202_MICRODEV) + +#define SMC_CAN_USE_8BIT 0 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 0 + +#define SMC_inb(a, r) inb((a) + (r) - 0xa0000000) +#define SMC_inw(a, r) inw((a) + (r) - 0xa0000000) +#define SMC_inl(a, r) inl((a) + (r) - 0xa0000000) +#define SMC_outb(v, a, r) outb(v, (a) + (r) - 0xa0000000) +#define SMC_outw(v, a, r) outw(v, (a) + (r) - 0xa0000000) +#define SMC_outl(v, a, r) outl(v, (a) + (r) - 0xa0000000) +#define SMC_insl(a, r, p, l) insl((a) + (r) - 0xa0000000, p, l) +#define SMC_outsl(a, r, p, l) outsl((a) + (r) - 0xa0000000, p, l) +#define SMC_insw(a, r, p, l) insw((a) + (r) - 0xa0000000, p, l) +#define SMC_outsw(a, r, p, l) outsw((a) + (r) - 0xa0000000, p, l) + +#define SMC_IRQ_FLAGS (0) + +#elif defined(CONFIG_M32R) + +#define SMC_CAN_USE_8BIT 0 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 0 + +#define SMC_inb(a, r) inb(((u32)a) + (r)) +#define SMC_inw(a, r) inw(((u32)a) + (r)) +#define SMC_outb(v, a, r) outb(v, ((u32)a) + (r)) +#define SMC_outw(v, a, r) outw(v, ((u32)a) + (r)) +#define SMC_insw(a, r, p, l) insw(((u32)a) + (r), p, l) +#define SMC_outsw(a, r, p, l) outsw(((u32)a) + (r), p, l) + +#define SMC_IRQ_FLAGS (0) + +#define RPC_LSA_DEFAULT RPC_LED_TX_RX +#define RPC_LSB_DEFAULT RPC_LED_100_10 + +#elif defined(CONFIG_ARCH_VERSATILE) + +#define SMC_CAN_USE_8BIT 1 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 1 +#define SMC_NOWAIT 1 + +#define SMC_inb(a, r) readb((a) + (r)) +#define SMC_inw(a, r) readw((a) + (r)) +#define SMC_inl(a, r) readl((a) + (r)) +#define SMC_outb(v, a, r) writeb(v, (a) + (r)) +#define SMC_outw(v, a, r) writew(v, (a) + (r)) +#define SMC_outl(v, a, r) writel(v, (a) + (r)) +#define SMC_insl(a, r, p, l) readsl((a) + (r), p, l) +#define SMC_outsl(a, r, p, l) writesl((a) + (r), p, l) +#define SMC_IRQ_FLAGS (-1) /* from resource */ + +#elif defined(CONFIG_MN10300) + +/* + * MN10300/AM33 configuration + */ + +#include <unit/smc91111.h> + +#elif defined(CONFIG_ARCH_MSM) + +#define SMC_CAN_USE_8BIT 0 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 0 +#define SMC_NOWAIT 1 + +#define SMC_inw(a, r) readw((a) + (r)) +#define SMC_outw(v, a, r) writew(v, (a) + (r)) +#define SMC_insw(a, r, p, l) readsw((a) + (r), p, l) +#define SMC_outsw(a, r, p, l) writesw((a) + (r), p, l) + +#define SMC_IRQ_FLAGS IRQF_TRIGGER_HIGH + +#elif defined(CONFIG_COLDFIRE) + +#define SMC_CAN_USE_8BIT 0 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 0 +#define SMC_NOWAIT 1 + +static inline void mcf_insw(void *a, unsigned char *p, int l) +{ + u16 *wp = (u16 *) p; + while (l-- > 0) + *wp++ = readw(a); +} + +static inline void mcf_outsw(void *a, unsigned char *p, int l) +{ + u16 *wp = (u16 *) p; + while (l-- > 0) + writew(*wp++, a); +} + +#define SMC_inw(a, r) _swapw(readw((a) + (r))) +#define SMC_outw(v, a, r) writew(_swapw(v), (a) + (r)) +#define SMC_insw(a, r, p, l) mcf_insw(a + r, p, l) +#define SMC_outsw(a, r, p, l) mcf_outsw(a + r, p, l) + +#define SMC_IRQ_FLAGS (IRQF_DISABLED) + +#else + +/* + * Default configuration + */ + +#define SMC_CAN_USE_8BIT 1 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 1 +#define SMC_NOWAIT 1 + +#define SMC_IO_SHIFT (lp->io_shift) + +#define SMC_inb(a, r) readb((a) + (r)) +#define SMC_inw(a, r) readw((a) + (r)) +#define SMC_inl(a, r) readl((a) + (r)) +#define SMC_outb(v, a, r) writeb(v, (a) + (r)) +#define SMC_outw(v, a, r) writew(v, (a) + (r)) +#define SMC_outl(v, a, r) writel(v, (a) + (r)) +#define SMC_insw(a, r, p, l) readsw((a) + (r), p, l) +#define SMC_outsw(a, r, p, l) writesw((a) + (r), p, l) +#define SMC_insl(a, r, p, l) readsl((a) + (r), p, l) +#define SMC_outsl(a, r, p, l) writesl((a) + (r), p, l) + +#define RPC_LSA_DEFAULT RPC_LED_100_10 +#define RPC_LSB_DEFAULT RPC_LED_TX_RX + +#endif + + +/* store this information for the driver.. */ +struct smc_local { + /* + * If I have to wait until memory is available to send a + * packet, I will store the skbuff here, until I get the + * desired memory. Then, I'll send it out and free it. + */ + struct sk_buff *pending_tx_skb; + struct tasklet_struct tx_task; + + /* version/revision of the SMC91x chip */ + int version; + + /* Contains the current active transmission mode */ + int tcr_cur_mode; + + /* Contains the current active receive mode */ + int rcr_cur_mode; + + /* Contains the current active receive/phy mode */ + int rpc_cur_mode; + int ctl_rfduplx; + int ctl_rspeed; + + u32 msg_enable; + u32 phy_type; + struct mii_if_info mii; + + /* work queue */ + struct work_struct phy_configure; + struct net_device *dev; + int work_pending; + + spinlock_t lock; + +#ifdef CONFIG_ARCH_PXA + /* DMA needs the physical address of the chip */ + u_long physaddr; + struct device *device; +#endif + void __iomem *base; + void __iomem *datacs; + + /* the low address lines on some platforms aren't connected... */ + int io_shift; + + struct smc91x_platdata cfg; +}; + +#define SMC_8BIT(p) ((p)->cfg.flags & SMC91X_USE_8BIT) +#define SMC_16BIT(p) ((p)->cfg.flags & SMC91X_USE_16BIT) +#define SMC_32BIT(p) ((p)->cfg.flags & SMC91X_USE_32BIT) + +#ifdef CONFIG_ARCH_PXA +/* + * Let's use the DMA engine on the XScale PXA2xx for RX packets. This is + * always happening in irq context so no need to worry about races. TX is + * different and probably not worth it for that reason, and not as critical + * as RX which can overrun memory and lose packets. + */ +#include <linux/dma-mapping.h> +#include <mach/dma.h> + +#ifdef SMC_insl +#undef SMC_insl +#define SMC_insl(a, r, p, l) \ + smc_pxa_dma_insl(a, lp, r, dev->dma, p, l) +static inline void +smc_pxa_dma_insl(void __iomem *ioaddr, struct smc_local *lp, int reg, int dma, + u_char *buf, int len) +{ + u_long physaddr = lp->physaddr; + dma_addr_t dmabuf; + + /* fallback if no DMA available */ + if (dma == (unsigned char)-1) { + readsl(ioaddr + reg, buf, len); + return; + } + + /* 64 bit alignment is required for memory to memory DMA */ + if ((long)buf & 4) { + *((u32 *)buf) = SMC_inl(ioaddr, reg); + buf += 4; + len--; + } + + len *= 4; + dmabuf = dma_map_single(lp->device, buf, len, DMA_FROM_DEVICE); + DCSR(dma) = DCSR_NODESC; + DTADR(dma) = dmabuf; + DSADR(dma) = physaddr + reg; + DCMD(dma) = (DCMD_INCTRGADDR | DCMD_BURST32 | + DCMD_WIDTH4 | (DCMD_LENGTH & len)); + DCSR(dma) = DCSR_NODESC | DCSR_RUN; + while (!(DCSR(dma) & DCSR_STOPSTATE)) + cpu_relax(); + DCSR(dma) = 0; + dma_unmap_single(lp->device, dmabuf, len, DMA_FROM_DEVICE); +} +#endif + +#ifdef SMC_insw +#undef SMC_insw +#define SMC_insw(a, r, p, l) \ + smc_pxa_dma_insw(a, lp, r, dev->dma, p, l) +static inline void +smc_pxa_dma_insw(void __iomem *ioaddr, struct smc_local *lp, int reg, int dma, + u_char *buf, int len) +{ + u_long physaddr = lp->physaddr; + dma_addr_t dmabuf; + + /* fallback if no DMA available */ + if (dma == (unsigned char)-1) { + readsw(ioaddr + reg, buf, len); + return; + } + + /* 64 bit alignment is required for memory to memory DMA */ + while ((long)buf & 6) { + *((u16 *)buf) = SMC_inw(ioaddr, reg); + buf += 2; + len--; + } + + len *= 2; + dmabuf = dma_map_single(lp->device, buf, len, DMA_FROM_DEVICE); + DCSR(dma) = DCSR_NODESC; + DTADR(dma) = dmabuf; + DSADR(dma) = physaddr + reg; + DCMD(dma) = (DCMD_INCTRGADDR | DCMD_BURST32 | + DCMD_WIDTH2 | (DCMD_LENGTH & len)); + DCSR(dma) = DCSR_NODESC | DCSR_RUN; + while (!(DCSR(dma) & DCSR_STOPSTATE)) + cpu_relax(); + DCSR(dma) = 0; + dma_unmap_single(lp->device, dmabuf, len, DMA_FROM_DEVICE); +} +#endif + +static void +smc_pxa_dma_irq(int dma, void *dummy) +{ + DCSR(dma) = 0; +} +#endif /* CONFIG_ARCH_PXA */ + + +/* + * Everything a particular hardware setup needs should have been defined + * at this point. Add stubs for the undefined cases, mainly to avoid + * compilation warnings since they'll be optimized away, or to prevent buggy + * use of them. + */ + +#if ! SMC_CAN_USE_32BIT +#define SMC_inl(ioaddr, reg) ({ BUG(); 0; }) +#define SMC_outl(x, ioaddr, reg) BUG() +#define SMC_insl(a, r, p, l) BUG() +#define SMC_outsl(a, r, p, l) BUG() +#endif + +#if !defined(SMC_insl) || !defined(SMC_outsl) +#define SMC_insl(a, r, p, l) BUG() +#define SMC_outsl(a, r, p, l) BUG() +#endif + +#if ! SMC_CAN_USE_16BIT + +/* + * Any 16-bit access is performed with two 8-bit accesses if the hardware + * can't do it directly. Most registers are 16-bit so those are mandatory. + */ +#define SMC_outw(x, ioaddr, reg) \ + do { \ + unsigned int __val16 = (x); \ + SMC_outb( __val16, ioaddr, reg ); \ + SMC_outb( __val16 >> 8, ioaddr, reg + (1 << SMC_IO_SHIFT));\ + } while (0) +#define SMC_inw(ioaddr, reg) \ + ({ \ + unsigned int __val16; \ + __val16 = SMC_inb( ioaddr, reg ); \ + __val16 |= SMC_inb( ioaddr, reg + (1 << SMC_IO_SHIFT)) << 8; \ + __val16; \ + }) + +#define SMC_insw(a, r, p, l) BUG() +#define SMC_outsw(a, r, p, l) BUG() + +#endif + +#if !defined(SMC_insw) || !defined(SMC_outsw) +#define SMC_insw(a, r, p, l) BUG() +#define SMC_outsw(a, r, p, l) BUG() +#endif + +#if ! SMC_CAN_USE_8BIT +#define SMC_inb(ioaddr, reg) ({ BUG(); 0; }) +#define SMC_outb(x, ioaddr, reg) BUG() +#define SMC_insb(a, r, p, l) BUG() +#define SMC_outsb(a, r, p, l) BUG() +#endif + +#if !defined(SMC_insb) || !defined(SMC_outsb) +#define SMC_insb(a, r, p, l) BUG() +#define SMC_outsb(a, r, p, l) BUG() +#endif + +#ifndef SMC_CAN_USE_DATACS +#define SMC_CAN_USE_DATACS 0 +#endif + +#ifndef SMC_IO_SHIFT +#define SMC_IO_SHIFT 0 +#endif + +#ifndef SMC_IRQ_FLAGS +#define SMC_IRQ_FLAGS IRQF_TRIGGER_RISING +#endif + +#ifndef SMC_INTERRUPT_PREAMBLE +#define SMC_INTERRUPT_PREAMBLE +#endif + + +/* Because of bank switching, the LAN91x uses only 16 I/O ports */ +#define SMC_IO_EXTENT (16 << SMC_IO_SHIFT) +#define SMC_DATA_EXTENT (4) + +/* + . Bank Select Register: + . + . yyyy yyyy 0000 00xx + . xx = bank number + . yyyy yyyy = 0x33, for identification purposes. +*/ +#define BANK_SELECT (14 << SMC_IO_SHIFT) + + +// Transmit Control Register +/* BANK 0 */ +#define TCR_REG(lp) SMC_REG(lp, 0x0000, 0) +#define TCR_ENABLE 0x0001 // When 1 we can transmit +#define TCR_LOOP 0x0002 // Controls output pin LBK +#define TCR_FORCOL 0x0004 // When 1 will force a collision +#define TCR_PAD_EN 0x0080 // When 1 will pad tx frames < 64 bytes w/0 +#define TCR_NOCRC 0x0100 // When 1 will not append CRC to tx frames +#define TCR_MON_CSN 0x0400 // When 1 tx monitors carrier +#define TCR_FDUPLX 0x0800 // When 1 enables full duplex operation +#define TCR_STP_SQET 0x1000 // When 1 stops tx if Signal Quality Error +#define TCR_EPH_LOOP 0x2000 // When 1 enables EPH block loopback +#define TCR_SWFDUP 0x8000 // When 1 enables Switched Full Duplex mode + +#define TCR_CLEAR 0 /* do NOTHING */ +/* the default settings for the TCR register : */ +#define TCR_DEFAULT (TCR_ENABLE | TCR_PAD_EN) + + +// EPH Status Register +/* BANK 0 */ +#define EPH_STATUS_REG(lp) SMC_REG(lp, 0x0002, 0) +#define ES_TX_SUC 0x0001 // Last TX was successful +#define ES_SNGL_COL 0x0002 // Single collision detected for last tx +#define ES_MUL_COL 0x0004 // Multiple collisions detected for last tx +#define ES_LTX_MULT 0x0008 // Last tx was a multicast +#define ES_16COL 0x0010 // 16 Collisions Reached +#define ES_SQET 0x0020 // Signal Quality Error Test +#define ES_LTXBRD 0x0040 // Last tx was a broadcast +#define ES_TXDEFR 0x0080 // Transmit Deferred +#define ES_LATCOL 0x0200 // Late collision detected on last tx +#define ES_LOSTCARR 0x0400 // Lost Carrier Sense +#define ES_EXC_DEF 0x0800 // Excessive Deferral +#define ES_CTR_ROL 0x1000 // Counter Roll Over indication +#define ES_LINK_OK 0x4000 // Driven by inverted value of nLNK pin +#define ES_TXUNRN 0x8000 // Tx Underrun + + +// Receive Control Register +/* BANK 0 */ +#define RCR_REG(lp) SMC_REG(lp, 0x0004, 0) +#define RCR_RX_ABORT 0x0001 // Set if a rx frame was aborted +#define RCR_PRMS 0x0002 // Enable promiscuous mode +#define RCR_ALMUL 0x0004 // When set accepts all multicast frames +#define RCR_RXEN 0x0100 // IFF this is set, we can receive packets +#define RCR_STRIP_CRC 0x0200 // When set strips CRC from rx packets +#define RCR_ABORT_ENB 0x0200 // When set will abort rx on collision +#define RCR_FILT_CAR 0x0400 // When set filters leading 12 bit s of carrier +#define RCR_SOFTRST 0x8000 // resets the chip + +/* the normal settings for the RCR register : */ +#define RCR_DEFAULT (RCR_STRIP_CRC | RCR_RXEN) +#define RCR_CLEAR 0x0 // set it to a base state + + +// Counter Register +/* BANK 0 */ +#define COUNTER_REG(lp) SMC_REG(lp, 0x0006, 0) + + +// Memory Information Register +/* BANK 0 */ +#define MIR_REG(lp) SMC_REG(lp, 0x0008, 0) + + +// Receive/Phy Control Register +/* BANK 0 */ +#define RPC_REG(lp) SMC_REG(lp, 0x000A, 0) +#define RPC_SPEED 0x2000 // When 1 PHY is in 100Mbps mode. +#define RPC_DPLX 0x1000 // When 1 PHY is in Full-Duplex Mode +#define RPC_ANEG 0x0800 // When 1 PHY is in Auto-Negotiate Mode +#define RPC_LSXA_SHFT 5 // Bits to shift LS2A,LS1A,LS0A to lsb +#define RPC_LSXB_SHFT 2 // Bits to get LS2B,LS1B,LS0B to lsb + +#ifndef RPC_LSA_DEFAULT +#define RPC_LSA_DEFAULT RPC_LED_100 +#endif +#ifndef RPC_LSB_DEFAULT +#define RPC_LSB_DEFAULT RPC_LED_FD +#endif + +#define RPC_DEFAULT (RPC_ANEG | RPC_SPEED | RPC_DPLX) + + +/* Bank 0 0x0C is reserved */ + +// Bank Select Register +/* All Banks */ +#define BSR_REG 0x000E + + +// Configuration Reg +/* BANK 1 */ +#define CONFIG_REG(lp) SMC_REG(lp, 0x0000, 1) +#define CONFIG_EXT_PHY 0x0200 // 1=external MII, 0=internal Phy +#define CONFIG_GPCNTRL 0x0400 // Inverse value drives pin nCNTRL +#define CONFIG_NO_WAIT 0x1000 // When 1 no extra wait states on ISA bus +#define CONFIG_EPH_POWER_EN 0x8000 // When 0 EPH is placed into low power mode. + +// Default is powered-up, Internal Phy, Wait States, and pin nCNTRL=low +#define CONFIG_DEFAULT (CONFIG_EPH_POWER_EN) + + +// Base Address Register +/* BANK 1 */ +#define BASE_REG(lp) SMC_REG(lp, 0x0002, 1) + + +// Individual Address Registers +/* BANK 1 */ +#define ADDR0_REG(lp) SMC_REG(lp, 0x0004, 1) +#define ADDR1_REG(lp) SMC_REG(lp, 0x0006, 1) +#define ADDR2_REG(lp) SMC_REG(lp, 0x0008, 1) + + +// General Purpose Register +/* BANK 1 */ +#define GP_REG(lp) SMC_REG(lp, 0x000A, 1) + + +// Control Register +/* BANK 1 */ +#define CTL_REG(lp) SMC_REG(lp, 0x000C, 1) +#define CTL_RCV_BAD 0x4000 // When 1 bad CRC packets are received +#define CTL_AUTO_RELEASE 0x0800 // When 1 tx pages are released automatically +#define CTL_LE_ENABLE 0x0080 // When 1 enables Link Error interrupt +#define CTL_CR_ENABLE 0x0040 // When 1 enables Counter Rollover interrupt +#define CTL_TE_ENABLE 0x0020 // When 1 enables Transmit Error interrupt +#define CTL_EEPROM_SELECT 0x0004 // Controls EEPROM reload & store +#define CTL_RELOAD 0x0002 // When set reads EEPROM into registers +#define CTL_STORE 0x0001 // When set stores registers into EEPROM + + +// MMU Command Register +/* BANK 2 */ +#define MMU_CMD_REG(lp) SMC_REG(lp, 0x0000, 2) +#define MC_BUSY 1 // When 1 the last release has not completed +#define MC_NOP (0<<5) // No Op +#define MC_ALLOC (1<<5) // OR with number of 256 byte packets +#define MC_RESET (2<<5) // Reset MMU to initial state +#define MC_REMOVE (3<<5) // Remove the current rx packet +#define MC_RELEASE (4<<5) // Remove and release the current rx packet +#define MC_FREEPKT (5<<5) // Release packet in PNR register +#define MC_ENQUEUE (6<<5) // Enqueue the packet for transmit +#define MC_RSTTXFIFO (7<<5) // Reset the TX FIFOs + + +// Packet Number Register +/* BANK 2 */ +#define PN_REG(lp) SMC_REG(lp, 0x0002, 2) + + +// Allocation Result Register +/* BANK 2 */ +#define AR_REG(lp) SMC_REG(lp, 0x0003, 2) +#define AR_FAILED 0x80 // Alocation Failed + + +// TX FIFO Ports Register +/* BANK 2 */ +#define TXFIFO_REG(lp) SMC_REG(lp, 0x0004, 2) +#define TXFIFO_TEMPTY 0x80 // TX FIFO Empty + +// RX FIFO Ports Register +/* BANK 2 */ +#define RXFIFO_REG(lp) SMC_REG(lp, 0x0005, 2) +#define RXFIFO_REMPTY 0x80 // RX FIFO Empty + +#define FIFO_REG(lp) SMC_REG(lp, 0x0004, 2) + +// Pointer Register +/* BANK 2 */ +#define PTR_REG(lp) SMC_REG(lp, 0x0006, 2) +#define PTR_RCV 0x8000 // 1=Receive area, 0=Transmit area +#define PTR_AUTOINC 0x4000 // Auto increment the pointer on each access +#define PTR_READ 0x2000 // When 1 the operation is a read + + +// Data Register +/* BANK 2 */ +#define DATA_REG(lp) SMC_REG(lp, 0x0008, 2) + + +// Interrupt Status/Acknowledge Register +/* BANK 2 */ +#define INT_REG(lp) SMC_REG(lp, 0x000C, 2) + + +// Interrupt Mask Register +/* BANK 2 */ +#define IM_REG(lp) SMC_REG(lp, 0x000D, 2) +#define IM_MDINT 0x80 // PHY MI Register 18 Interrupt +#define IM_ERCV_INT 0x40 // Early Receive Interrupt +#define IM_EPH_INT 0x20 // Set by Ethernet Protocol Handler section +#define IM_RX_OVRN_INT 0x10 // Set by Receiver Overruns +#define IM_ALLOC_INT 0x08 // Set when allocation request is completed +#define IM_TX_EMPTY_INT 0x04 // Set if the TX FIFO goes empty +#define IM_TX_INT 0x02 // Transmit Interrupt +#define IM_RCV_INT 0x01 // Receive Interrupt + + +// Multicast Table Registers +/* BANK 3 */ +#define MCAST_REG1(lp) SMC_REG(lp, 0x0000, 3) +#define MCAST_REG2(lp) SMC_REG(lp, 0x0002, 3) +#define MCAST_REG3(lp) SMC_REG(lp, 0x0004, 3) +#define MCAST_REG4(lp) SMC_REG(lp, 0x0006, 3) + + +// Management Interface Register (MII) +/* BANK 3 */ +#define MII_REG(lp) SMC_REG(lp, 0x0008, 3) +#define MII_MSK_CRS100 0x4000 // Disables CRS100 detection during tx half dup +#define MII_MDOE 0x0008 // MII Output Enable +#define MII_MCLK 0x0004 // MII Clock, pin MDCLK +#define MII_MDI 0x0002 // MII Input, pin MDI +#define MII_MDO 0x0001 // MII Output, pin MDO + + +// Revision Register +/* BANK 3 */ +/* ( hi: chip id low: rev # ) */ +#define REV_REG(lp) SMC_REG(lp, 0x000A, 3) + + +// Early RCV Register +/* BANK 3 */ +/* this is NOT on SMC9192 */ +#define ERCV_REG(lp) SMC_REG(lp, 0x000C, 3) +#define ERCV_RCV_DISCRD 0x0080 // When 1 discards a packet being received +#define ERCV_THRESHOLD 0x001F // ERCV Threshold Mask + + +// External Register +/* BANK 7 */ +#define EXT_REG(lp) SMC_REG(lp, 0x0000, 7) + + +#define CHIP_9192 3 +#define CHIP_9194 4 +#define CHIP_9195 5 +#define CHIP_9196 6 +#define CHIP_91100 7 +#define CHIP_91100FD 8 +#define CHIP_91111FD 9 + +static const char * chip_ids[ 16 ] = { + NULL, NULL, NULL, + /* 3 */ "SMC91C90/91C92", + /* 4 */ "SMC91C94", + /* 5 */ "SMC91C95", + /* 6 */ "SMC91C96", + /* 7 */ "SMC91C100", + /* 8 */ "SMC91C100FD", + /* 9 */ "SMC91C11xFD", + NULL, NULL, NULL, + NULL, NULL, NULL}; + + +/* + . Receive status bits +*/ +#define RS_ALGNERR 0x8000 +#define RS_BRODCAST 0x4000 +#define RS_BADCRC 0x2000 +#define RS_ODDFRAME 0x1000 +#define RS_TOOLONG 0x0800 +#define RS_TOOSHORT 0x0400 +#define RS_MULTICAST 0x0001 +#define RS_ERRORS (RS_ALGNERR | RS_BADCRC | RS_TOOLONG | RS_TOOSHORT) + + +/* + * PHY IDs + * LAN83C183 == LAN91C111 Internal PHY + */ +#define PHY_LAN83C183 0x0016f840 +#define PHY_LAN83C180 0x02821c50 + +/* + * PHY Register Addresses (LAN91C111 Internal PHY) + * + * Generic PHY registers can be found in <linux/mii.h> + * + * These phy registers are specific to our on-board phy. + */ + +// PHY Configuration Register 1 +#define PHY_CFG1_REG 0x10 +#define PHY_CFG1_LNKDIS 0x8000 // 1=Rx Link Detect Function disabled +#define PHY_CFG1_XMTDIS 0x4000 // 1=TP Transmitter Disabled +#define PHY_CFG1_XMTPDN 0x2000 // 1=TP Transmitter Powered Down +#define PHY_CFG1_BYPSCR 0x0400 // 1=Bypass scrambler/descrambler +#define PHY_CFG1_UNSCDS 0x0200 // 1=Unscramble Idle Reception Disable +#define PHY_CFG1_EQLZR 0x0100 // 1=Rx Equalizer Disabled +#define PHY_CFG1_CABLE 0x0080 // 1=STP(150ohm), 0=UTP(100ohm) +#define PHY_CFG1_RLVL0 0x0040 // 1=Rx Squelch level reduced by 4.5db +#define PHY_CFG1_TLVL_SHIFT 2 // Transmit Output Level Adjust +#define PHY_CFG1_TLVL_MASK 0x003C +#define PHY_CFG1_TRF_MASK 0x0003 // Transmitter Rise/Fall time + + +// PHY Configuration Register 2 +#define PHY_CFG2_REG 0x11 +#define PHY_CFG2_APOLDIS 0x0020 // 1=Auto Polarity Correction disabled +#define PHY_CFG2_JABDIS 0x0010 // 1=Jabber disabled +#define PHY_CFG2_MREG 0x0008 // 1=Multiple register access (MII mgt) +#define PHY_CFG2_INTMDIO 0x0004 // 1=Interrupt signaled with MDIO pulseo + +// PHY Status Output (and Interrupt status) Register +#define PHY_INT_REG 0x12 // Status Output (Interrupt Status) +#define PHY_INT_INT 0x8000 // 1=bits have changed since last read +#define PHY_INT_LNKFAIL 0x4000 // 1=Link Not detected +#define PHY_INT_LOSSSYNC 0x2000 // 1=Descrambler has lost sync +#define PHY_INT_CWRD 0x1000 // 1=Invalid 4B5B code detected on rx +#define PHY_INT_SSD 0x0800 // 1=No Start Of Stream detected on rx +#define PHY_INT_ESD 0x0400 // 1=No End Of Stream detected on rx +#define PHY_INT_RPOL 0x0200 // 1=Reverse Polarity detected +#define PHY_INT_JAB 0x0100 // 1=Jabber detected +#define PHY_INT_SPDDET 0x0080 // 1=100Base-TX mode, 0=10Base-T mode +#define PHY_INT_DPLXDET 0x0040 // 1=Device in Full Duplex + +// PHY Interrupt/Status Mask Register +#define PHY_MASK_REG 0x13 // Interrupt Mask +// Uses the same bit definitions as PHY_INT_REG + + +/* + * SMC91C96 ethernet config and status registers. + * These are in the "attribute" space. + */ +#define ECOR 0x8000 +#define ECOR_RESET 0x80 +#define ECOR_LEVEL_IRQ 0x40 +#define ECOR_WR_ATTRIB 0x04 +#define ECOR_ENABLE 0x01 + +#define ECSR 0x8002 +#define ECSR_IOIS8 0x20 +#define ECSR_PWRDWN 0x04 +#define ECSR_INT 0x02 + +#define ATTRIB_SIZE ((64*1024) << SMC_IO_SHIFT) + + +/* + * Macros to abstract register access according to the data bus + * capabilities. Please use those and not the in/out primitives. + * Note: the following macros do *not* select the bank -- this must + * be done separately as needed in the main code. The SMC_REG() macro + * only uses the bank argument for debugging purposes (when enabled). + * + * Note: despite inline functions being safer, everything leading to this + * should preferably be macros to let BUG() display the line number in + * the core source code since we're interested in the top call site + * not in any inline function location. + */ + +#if SMC_DEBUG > 0 +#define SMC_REG(lp, reg, bank) \ + ({ \ + int __b = SMC_CURRENT_BANK(lp); \ + if (unlikely((__b & ~0xf0) != (0x3300 | bank))) { \ + printk( "%s: bank reg screwed (0x%04x)\n", \ + CARDNAME, __b ); \ + BUG(); \ + } \ + reg<<SMC_IO_SHIFT; \ + }) +#else +#define SMC_REG(lp, reg, bank) (reg<<SMC_IO_SHIFT) +#endif + +/* + * Hack Alert: Some setups just can't write 8 or 16 bits reliably when not + * aligned to a 32 bit boundary. I tell you that does exist! + * Fortunately the affected register accesses can be easily worked around + * since we can write zeroes to the preceding 16 bits without adverse + * effects and use a 32-bit access. + * + * Enforce it on any 32-bit capable setup for now. + */ +#define SMC_MUST_ALIGN_WRITE(lp) SMC_32BIT(lp) + +#define SMC_GET_PN(lp) \ + (SMC_8BIT(lp) ? (SMC_inb(ioaddr, PN_REG(lp))) \ + : (SMC_inw(ioaddr, PN_REG(lp)) & 0xFF)) + +#define SMC_SET_PN(lp, x) \ + do { \ + if (SMC_MUST_ALIGN_WRITE(lp)) \ + SMC_outl((x)<<16, ioaddr, SMC_REG(lp, 0, 2)); \ + else if (SMC_8BIT(lp)) \ + SMC_outb(x, ioaddr, PN_REG(lp)); \ + else \ + SMC_outw(x, ioaddr, PN_REG(lp)); \ + } while (0) + +#define SMC_GET_AR(lp) \ + (SMC_8BIT(lp) ? (SMC_inb(ioaddr, AR_REG(lp))) \ + : (SMC_inw(ioaddr, PN_REG(lp)) >> 8)) + +#define SMC_GET_TXFIFO(lp) \ + (SMC_8BIT(lp) ? (SMC_inb(ioaddr, TXFIFO_REG(lp))) \ + : (SMC_inw(ioaddr, TXFIFO_REG(lp)) & 0xFF)) + +#define SMC_GET_RXFIFO(lp) \ + (SMC_8BIT(lp) ? (SMC_inb(ioaddr, RXFIFO_REG(lp))) \ + : (SMC_inw(ioaddr, TXFIFO_REG(lp)) >> 8)) + +#define SMC_GET_INT(lp) \ + (SMC_8BIT(lp) ? (SMC_inb(ioaddr, INT_REG(lp))) \ + : (SMC_inw(ioaddr, INT_REG(lp)) & 0xFF)) + +#define SMC_ACK_INT(lp, x) \ + do { \ + if (SMC_8BIT(lp)) \ + SMC_outb(x, ioaddr, INT_REG(lp)); \ + else { \ + unsigned long __flags; \ + int __mask; \ + local_irq_save(__flags); \ + __mask = SMC_inw(ioaddr, INT_REG(lp)) & ~0xff; \ + SMC_outw(__mask | (x), ioaddr, INT_REG(lp)); \ + local_irq_restore(__flags); \ + } \ + } while (0) + +#define SMC_GET_INT_MASK(lp) \ + (SMC_8BIT(lp) ? (SMC_inb(ioaddr, IM_REG(lp))) \ + : (SMC_inw(ioaddr, INT_REG(lp)) >> 8)) + +#define SMC_SET_INT_MASK(lp, x) \ + do { \ + if (SMC_8BIT(lp)) \ + SMC_outb(x, ioaddr, IM_REG(lp)); \ + else \ + SMC_outw((x) << 8, ioaddr, INT_REG(lp)); \ + } while (0) + +#define SMC_CURRENT_BANK(lp) SMC_inw(ioaddr, BANK_SELECT) + +#define SMC_SELECT_BANK(lp, x) \ + do { \ + if (SMC_MUST_ALIGN_WRITE(lp)) \ + SMC_outl((x)<<16, ioaddr, 12<<SMC_IO_SHIFT); \ + else \ + SMC_outw(x, ioaddr, BANK_SELECT); \ + } while (0) + +#define SMC_GET_BASE(lp) SMC_inw(ioaddr, BASE_REG(lp)) + +#define SMC_SET_BASE(lp, x) SMC_outw(x, ioaddr, BASE_REG(lp)) + +#define SMC_GET_CONFIG(lp) SMC_inw(ioaddr, CONFIG_REG(lp)) + +#define SMC_SET_CONFIG(lp, x) SMC_outw(x, ioaddr, CONFIG_REG(lp)) + +#define SMC_GET_COUNTER(lp) SMC_inw(ioaddr, COUNTER_REG(lp)) + +#define SMC_GET_CTL(lp) SMC_inw(ioaddr, CTL_REG(lp)) + +#define SMC_SET_CTL(lp, x) SMC_outw(x, ioaddr, CTL_REG(lp)) + +#define SMC_GET_MII(lp) SMC_inw(ioaddr, MII_REG(lp)) + +#define SMC_GET_GP(lp) SMC_inw(ioaddr, GP_REG(lp)) + +#define SMC_SET_GP(lp, x) \ + do { \ + if (SMC_MUST_ALIGN_WRITE(lp)) \ + SMC_outl((x)<<16, ioaddr, SMC_REG(lp, 8, 1)); \ + else \ + SMC_outw(x, ioaddr, GP_REG(lp)); \ + } while (0) + +#define SMC_SET_MII(lp, x) SMC_outw(x, ioaddr, MII_REG(lp)) + +#define SMC_GET_MIR(lp) SMC_inw(ioaddr, MIR_REG(lp)) + +#define SMC_SET_MIR(lp, x) SMC_outw(x, ioaddr, MIR_REG(lp)) + +#define SMC_GET_MMU_CMD(lp) SMC_inw(ioaddr, MMU_CMD_REG(lp)) + +#define SMC_SET_MMU_CMD(lp, x) SMC_outw(x, ioaddr, MMU_CMD_REG(lp)) + +#define SMC_GET_FIFO(lp) SMC_inw(ioaddr, FIFO_REG(lp)) + +#define SMC_GET_PTR(lp) SMC_inw(ioaddr, PTR_REG(lp)) + +#define SMC_SET_PTR(lp, x) \ + do { \ + if (SMC_MUST_ALIGN_WRITE(lp)) \ + SMC_outl((x)<<16, ioaddr, SMC_REG(lp, 4, 2)); \ + else \ + SMC_outw(x, ioaddr, PTR_REG(lp)); \ + } while (0) + +#define SMC_GET_EPH_STATUS(lp) SMC_inw(ioaddr, EPH_STATUS_REG(lp)) + +#define SMC_GET_RCR(lp) SMC_inw(ioaddr, RCR_REG(lp)) + +#define SMC_SET_RCR(lp, x) SMC_outw(x, ioaddr, RCR_REG(lp)) + +#define SMC_GET_REV(lp) SMC_inw(ioaddr, REV_REG(lp)) + +#define SMC_GET_RPC(lp) SMC_inw(ioaddr, RPC_REG(lp)) + +#define SMC_SET_RPC(lp, x) \ + do { \ + if (SMC_MUST_ALIGN_WRITE(lp)) \ + SMC_outl((x)<<16, ioaddr, SMC_REG(lp, 8, 0)); \ + else \ + SMC_outw(x, ioaddr, RPC_REG(lp)); \ + } while (0) + +#define SMC_GET_TCR(lp) SMC_inw(ioaddr, TCR_REG(lp)) + +#define SMC_SET_TCR(lp, x) SMC_outw(x, ioaddr, TCR_REG(lp)) + +#ifndef SMC_GET_MAC_ADDR +#define SMC_GET_MAC_ADDR(lp, addr) \ + do { \ + unsigned int __v; \ + __v = SMC_inw(ioaddr, ADDR0_REG(lp)); \ + addr[0] = __v; addr[1] = __v >> 8; \ + __v = SMC_inw(ioaddr, ADDR1_REG(lp)); \ + addr[2] = __v; addr[3] = __v >> 8; \ + __v = SMC_inw(ioaddr, ADDR2_REG(lp)); \ + addr[4] = __v; addr[5] = __v >> 8; \ + } while (0) +#endif + +#define SMC_SET_MAC_ADDR(lp, addr) \ + do { \ + SMC_outw(addr[0]|(addr[1] << 8), ioaddr, ADDR0_REG(lp)); \ + SMC_outw(addr[2]|(addr[3] << 8), ioaddr, ADDR1_REG(lp)); \ + SMC_outw(addr[4]|(addr[5] << 8), ioaddr, ADDR2_REG(lp)); \ + } while (0) + +#define SMC_SET_MCAST(lp, x) \ + do { \ + const unsigned char *mt = (x); \ + SMC_outw(mt[0] | (mt[1] << 8), ioaddr, MCAST_REG1(lp)); \ + SMC_outw(mt[2] | (mt[3] << 8), ioaddr, MCAST_REG2(lp)); \ + SMC_outw(mt[4] | (mt[5] << 8), ioaddr, MCAST_REG3(lp)); \ + SMC_outw(mt[6] | (mt[7] << 8), ioaddr, MCAST_REG4(lp)); \ + } while (0) + +#define SMC_PUT_PKT_HDR(lp, status, length) \ + do { \ + if (SMC_32BIT(lp)) \ + SMC_outl((status) | (length)<<16, ioaddr, \ + DATA_REG(lp)); \ + else { \ + SMC_outw(status, ioaddr, DATA_REG(lp)); \ + SMC_outw(length, ioaddr, DATA_REG(lp)); \ + } \ + } while (0) + +#define SMC_GET_PKT_HDR(lp, status, length) \ + do { \ + if (SMC_32BIT(lp)) { \ + unsigned int __val = SMC_inl(ioaddr, DATA_REG(lp)); \ + (status) = __val & 0xffff; \ + (length) = __val >> 16; \ + } else { \ + (status) = SMC_inw(ioaddr, DATA_REG(lp)); \ + (length) = SMC_inw(ioaddr, DATA_REG(lp)); \ + } \ + } while (0) + +#define SMC_PUSH_DATA(lp, p, l) \ + do { \ + if (SMC_32BIT(lp)) { \ + void *__ptr = (p); \ + int __len = (l); \ + void __iomem *__ioaddr = ioaddr; \ + if (__len >= 2 && (unsigned long)__ptr & 2) { \ + __len -= 2; \ + SMC_outw(*(u16 *)__ptr, ioaddr, \ + DATA_REG(lp)); \ + __ptr += 2; \ + } \ + if (SMC_CAN_USE_DATACS && lp->datacs) \ + __ioaddr = lp->datacs; \ + SMC_outsl(__ioaddr, DATA_REG(lp), __ptr, __len>>2); \ + if (__len & 2) { \ + __ptr += (__len & ~3); \ + SMC_outw(*((u16 *)__ptr), ioaddr, \ + DATA_REG(lp)); \ + } \ + } else if (SMC_16BIT(lp)) \ + SMC_outsw(ioaddr, DATA_REG(lp), p, (l) >> 1); \ + else if (SMC_8BIT(lp)) \ + SMC_outsb(ioaddr, DATA_REG(lp), p, l); \ + } while (0) + +#define SMC_PULL_DATA(lp, p, l) \ + do { \ + if (SMC_32BIT(lp)) { \ + void *__ptr = (p); \ + int __len = (l); \ + void __iomem *__ioaddr = ioaddr; \ + if ((unsigned long)__ptr & 2) { \ + /* \ + * We want 32bit alignment here. \ + * Since some buses perform a full \ + * 32bit fetch even for 16bit data \ + * we can't use SMC_inw() here. \ + * Back both source (on-chip) and \ + * destination pointers of 2 bytes. \ + * This is possible since the call to \ + * SMC_GET_PKT_HDR() already advanced \ + * the source pointer of 4 bytes, and \ + * the skb_reserve(skb, 2) advanced \ + * the destination pointer of 2 bytes. \ + */ \ + __ptr -= 2; \ + __len += 2; \ + SMC_SET_PTR(lp, \ + 2|PTR_READ|PTR_RCV|PTR_AUTOINC); \ + } \ + if (SMC_CAN_USE_DATACS && lp->datacs) \ + __ioaddr = lp->datacs; \ + __len += 2; \ + SMC_insl(__ioaddr, DATA_REG(lp), __ptr, __len>>2); \ + } else if (SMC_16BIT(lp)) \ + SMC_insw(ioaddr, DATA_REG(lp), p, (l) >> 1); \ + else if (SMC_8BIT(lp)) \ + SMC_insb(ioaddr, DATA_REG(lp), p, l); \ + } while (0) + +#endif /* _SMC91X_H_ */ diff --git a/drivers/net/ethernet/smsc/smsc911x.c b/drivers/net/ethernet/smsc/smsc911x.c new file mode 100644 index 000000000000..75c08a55582c --- /dev/null +++ b/drivers/net/ethernet/smsc/smsc911x.c @@ -0,0 +1,2404 @@ +/*************************************************************************** + * + * Copyright (C) 2004-2008 SMSC + * Copyright (C) 2005-2008 ARM + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + *************************************************************************** + * Rewritten, heavily based on smsc911x simple driver by SMSC. + * Partly uses io macros from smc91x.c by Nicolas Pitre + * + * Supported devices: + * LAN9115, LAN9116, LAN9117, LAN9118 + * LAN9215, LAN9216, LAN9217, LAN9218 + * LAN9210, LAN9211 + * LAN9220, LAN9221 + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/bug.h> +#include <linux/bitops.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/swab.h> +#include <linux/phy.h> +#include <linux/smsc911x.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_net.h> +#include "smsc911x.h" + +#define SMSC_CHIPNAME "smsc911x" +#define SMSC_MDIONAME "smsc911x-mdio" +#define SMSC_DRV_VERSION "2008-10-21" + +MODULE_LICENSE("GPL"); +MODULE_VERSION(SMSC_DRV_VERSION); +MODULE_ALIAS("platform:smsc911x"); + +#if USE_DEBUG > 0 +static int debug = 16; +#else +static int debug = 3; +#endif + +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)"); + +struct smsc911x_data; + +struct smsc911x_ops { + u32 (*reg_read)(struct smsc911x_data *pdata, u32 reg); + void (*reg_write)(struct smsc911x_data *pdata, u32 reg, u32 val); + void (*rx_readfifo)(struct smsc911x_data *pdata, + unsigned int *buf, unsigned int wordcount); + void (*tx_writefifo)(struct smsc911x_data *pdata, + unsigned int *buf, unsigned int wordcount); +}; + +struct smsc911x_data { + void __iomem *ioaddr; + + unsigned int idrev; + + /* used to decide which workarounds apply */ + unsigned int generation; + + /* device configuration (copied from platform_data during probe) */ + struct smsc911x_platform_config config; + + /* This needs to be acquired before calling any of below: + * smsc911x_mac_read(), smsc911x_mac_write() + */ + spinlock_t mac_lock; + + /* spinlock to ensure register accesses are serialised */ + spinlock_t dev_lock; + + struct phy_device *phy_dev; + struct mii_bus *mii_bus; + int phy_irq[PHY_MAX_ADDR]; + unsigned int using_extphy; + int last_duplex; + int last_carrier; + + u32 msg_enable; + unsigned int gpio_setting; + unsigned int gpio_orig_setting; + struct net_device *dev; + struct napi_struct napi; + + unsigned int software_irq_signal; + +#ifdef USE_PHY_WORK_AROUND +#define MIN_PACKET_SIZE (64) + char loopback_tx_pkt[MIN_PACKET_SIZE]; + char loopback_rx_pkt[MIN_PACKET_SIZE]; + unsigned int resetcount; +#endif + + /* Members for Multicast filter workaround */ + unsigned int multicast_update_pending; + unsigned int set_bits_mask; + unsigned int clear_bits_mask; + unsigned int hashhi; + unsigned int hashlo; + + /* register access functions */ + const struct smsc911x_ops *ops; +}; + +/* Easy access to information */ +#define __smsc_shift(pdata, reg) ((reg) << ((pdata)->config.shift)) + +static inline u32 __smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) +{ + if (pdata->config.flags & SMSC911X_USE_32BIT) + return readl(pdata->ioaddr + reg); + + if (pdata->config.flags & SMSC911X_USE_16BIT) + return ((readw(pdata->ioaddr + reg) & 0xFFFF) | + ((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16)); + + BUG(); + return 0; +} + +static inline u32 +__smsc911x_reg_read_shift(struct smsc911x_data *pdata, u32 reg) +{ + if (pdata->config.flags & SMSC911X_USE_32BIT) + return readl(pdata->ioaddr + __smsc_shift(pdata, reg)); + + if (pdata->config.flags & SMSC911X_USE_16BIT) + return (readw(pdata->ioaddr + + __smsc_shift(pdata, reg)) & 0xFFFF) | + ((readw(pdata->ioaddr + + __smsc_shift(pdata, reg + 2)) & 0xFFFF) << 16); + + BUG(); + return 0; +} + +static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) +{ + u32 data; + unsigned long flags; + + spin_lock_irqsave(&pdata->dev_lock, flags); + data = pdata->ops->reg_read(pdata, reg); + spin_unlock_irqrestore(&pdata->dev_lock, flags); + + return data; +} + +static inline void __smsc911x_reg_write(struct smsc911x_data *pdata, u32 reg, + u32 val) +{ + if (pdata->config.flags & SMSC911X_USE_32BIT) { + writel(val, pdata->ioaddr + reg); + return; + } + + if (pdata->config.flags & SMSC911X_USE_16BIT) { + writew(val & 0xFFFF, pdata->ioaddr + reg); + writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2); + return; + } + + BUG(); +} + +static inline void +__smsc911x_reg_write_shift(struct smsc911x_data *pdata, u32 reg, u32 val) +{ + if (pdata->config.flags & SMSC911X_USE_32BIT) { + writel(val, pdata->ioaddr + __smsc_shift(pdata, reg)); + return; + } + + if (pdata->config.flags & SMSC911X_USE_16BIT) { + writew(val & 0xFFFF, + pdata->ioaddr + __smsc_shift(pdata, reg)); + writew((val >> 16) & 0xFFFF, + pdata->ioaddr + __smsc_shift(pdata, reg + 2)); + return; + } + + BUG(); +} + +static inline void smsc911x_reg_write(struct smsc911x_data *pdata, u32 reg, + u32 val) +{ + unsigned long flags; + + spin_lock_irqsave(&pdata->dev_lock, flags); + pdata->ops->reg_write(pdata, reg, val); + spin_unlock_irqrestore(&pdata->dev_lock, flags); +} + +/* Writes a packet to the TX_DATA_FIFO */ +static inline void +smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + unsigned long flags; + + spin_lock_irqsave(&pdata->dev_lock, flags); + + if (pdata->config.flags & SMSC911X_SWAP_FIFO) { + while (wordcount--) + __smsc911x_reg_write(pdata, TX_DATA_FIFO, + swab32(*buf++)); + goto out; + } + + if (pdata->config.flags & SMSC911X_USE_32BIT) { + writesl(pdata->ioaddr + TX_DATA_FIFO, buf, wordcount); + goto out; + } + + if (pdata->config.flags & SMSC911X_USE_16BIT) { + while (wordcount--) + __smsc911x_reg_write(pdata, TX_DATA_FIFO, *buf++); + goto out; + } + + BUG(); +out: + spin_unlock_irqrestore(&pdata->dev_lock, flags); +} + +/* Writes a packet to the TX_DATA_FIFO - shifted version */ +static inline void +smsc911x_tx_writefifo_shift(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + unsigned long flags; + + spin_lock_irqsave(&pdata->dev_lock, flags); + + if (pdata->config.flags & SMSC911X_SWAP_FIFO) { + while (wordcount--) + __smsc911x_reg_write_shift(pdata, TX_DATA_FIFO, + swab32(*buf++)); + goto out; + } + + if (pdata->config.flags & SMSC911X_USE_32BIT) { + writesl(pdata->ioaddr + __smsc_shift(pdata, + TX_DATA_FIFO), buf, wordcount); + goto out; + } + + if (pdata->config.flags & SMSC911X_USE_16BIT) { + while (wordcount--) + __smsc911x_reg_write_shift(pdata, + TX_DATA_FIFO, *buf++); + goto out; + } + + BUG(); +out: + spin_unlock_irqrestore(&pdata->dev_lock, flags); +} + +/* Reads a packet out of the RX_DATA_FIFO */ +static inline void +smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + unsigned long flags; + + spin_lock_irqsave(&pdata->dev_lock, flags); + + if (pdata->config.flags & SMSC911X_SWAP_FIFO) { + while (wordcount--) + *buf++ = swab32(__smsc911x_reg_read(pdata, + RX_DATA_FIFO)); + goto out; + } + + if (pdata->config.flags & SMSC911X_USE_32BIT) { + readsl(pdata->ioaddr + RX_DATA_FIFO, buf, wordcount); + goto out; + } + + if (pdata->config.flags & SMSC911X_USE_16BIT) { + while (wordcount--) + *buf++ = __smsc911x_reg_read(pdata, RX_DATA_FIFO); + goto out; + } + + BUG(); +out: + spin_unlock_irqrestore(&pdata->dev_lock, flags); +} + +/* Reads a packet out of the RX_DATA_FIFO - shifted version */ +static inline void +smsc911x_rx_readfifo_shift(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + unsigned long flags; + + spin_lock_irqsave(&pdata->dev_lock, flags); + + if (pdata->config.flags & SMSC911X_SWAP_FIFO) { + while (wordcount--) + *buf++ = swab32(__smsc911x_reg_read_shift(pdata, + RX_DATA_FIFO)); + goto out; + } + + if (pdata->config.flags & SMSC911X_USE_32BIT) { + readsl(pdata->ioaddr + __smsc_shift(pdata, + RX_DATA_FIFO), buf, wordcount); + goto out; + } + + if (pdata->config.flags & SMSC911X_USE_16BIT) { + while (wordcount--) + *buf++ = __smsc911x_reg_read_shift(pdata, + RX_DATA_FIFO); + goto out; + } + + BUG(); +out: + spin_unlock_irqrestore(&pdata->dev_lock, flags); +} + +/* waits for MAC not busy, with timeout. Only called by smsc911x_mac_read + * and smsc911x_mac_write, so assumes mac_lock is held */ +static int smsc911x_mac_complete(struct smsc911x_data *pdata) +{ + int i; + u32 val; + + SMSC_ASSERT_MAC_LOCK(pdata); + + for (i = 0; i < 40; i++) { + val = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (!(val & MAC_CSR_CMD_CSR_BUSY_)) + return 0; + } + SMSC_WARN(pdata, hw, "Timed out waiting for MAC not BUSY. " + "MAC_CSR_CMD: 0x%08X", val); + return -EIO; +} + +/* Fetches a MAC register value. Assumes mac_lock is acquired */ +static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset) +{ + unsigned int temp; + + SMSC_ASSERT_MAC_LOCK(pdata); + + temp = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) { + SMSC_WARN(pdata, hw, "MAC busy at entry"); + return 0xFFFFFFFF; + } + + /* Send the MAC cmd */ + smsc911x_reg_write(pdata, MAC_CSR_CMD, ((offset & 0xFF) | + MAC_CSR_CMD_CSR_BUSY_ | MAC_CSR_CMD_R_NOT_W_)); + + /* Workaround for hardware read-after-write restriction */ + temp = smsc911x_reg_read(pdata, BYTE_TEST); + + /* Wait for the read to complete */ + if (likely(smsc911x_mac_complete(pdata) == 0)) + return smsc911x_reg_read(pdata, MAC_CSR_DATA); + + SMSC_WARN(pdata, hw, "MAC busy after read"); + return 0xFFFFFFFF; +} + +/* Set a mac register, mac_lock must be acquired before calling */ +static void smsc911x_mac_write(struct smsc911x_data *pdata, + unsigned int offset, u32 val) +{ + unsigned int temp; + + SMSC_ASSERT_MAC_LOCK(pdata); + + temp = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) { + SMSC_WARN(pdata, hw, + "smsc911x_mac_write failed, MAC busy at entry"); + return; + } + + /* Send data to write */ + smsc911x_reg_write(pdata, MAC_CSR_DATA, val); + + /* Write the actual data */ + smsc911x_reg_write(pdata, MAC_CSR_CMD, ((offset & 0xFF) | + MAC_CSR_CMD_CSR_BUSY_)); + + /* Workaround for hardware read-after-write restriction */ + temp = smsc911x_reg_read(pdata, BYTE_TEST); + + /* Wait for the write to complete */ + if (likely(smsc911x_mac_complete(pdata) == 0)) + return; + + SMSC_WARN(pdata, hw, "smsc911x_mac_write failed, MAC busy after write"); +} + +/* Get a phy register */ +static int smsc911x_mii_read(struct mii_bus *bus, int phyaddr, int regidx) +{ + struct smsc911x_data *pdata = (struct smsc911x_data *)bus->priv; + unsigned long flags; + unsigned int addr; + int i, reg; + + spin_lock_irqsave(&pdata->mac_lock, flags); + + /* Confirm MII not busy */ + if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + SMSC_WARN(pdata, hw, "MII is busy in smsc911x_mii_read???"); + reg = -EIO; + goto out; + } + + /* Set the address, index & direction (read from PHY) */ + addr = ((phyaddr & 0x1F) << 11) | ((regidx & 0x1F) << 6); + smsc911x_mac_write(pdata, MII_ACC, addr); + + /* Wait for read to complete w/ timeout */ + for (i = 0; i < 100; i++) + if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + reg = smsc911x_mac_read(pdata, MII_DATA); + goto out; + } + + SMSC_WARN(pdata, hw, "Timed out waiting for MII read to finish"); + reg = -EIO; + +out: + spin_unlock_irqrestore(&pdata->mac_lock, flags); + return reg; +} + +/* Set a phy register */ +static int smsc911x_mii_write(struct mii_bus *bus, int phyaddr, int regidx, + u16 val) +{ + struct smsc911x_data *pdata = (struct smsc911x_data *)bus->priv; + unsigned long flags; + unsigned int addr; + int i, reg; + + spin_lock_irqsave(&pdata->mac_lock, flags); + + /* Confirm MII not busy */ + if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + SMSC_WARN(pdata, hw, "MII is busy in smsc911x_mii_write???"); + reg = -EIO; + goto out; + } + + /* Put the data to write in the MAC */ + smsc911x_mac_write(pdata, MII_DATA, val); + + /* Set the address, index & direction (write to PHY) */ + addr = ((phyaddr & 0x1F) << 11) | ((regidx & 0x1F) << 6) | + MII_ACC_MII_WRITE_; + smsc911x_mac_write(pdata, MII_ACC, addr); + + /* Wait for write to complete w/ timeout */ + for (i = 0; i < 100; i++) + if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + reg = 0; + goto out; + } + + SMSC_WARN(pdata, hw, "Timed out waiting for MII write to finish"); + reg = -EIO; + +out: + spin_unlock_irqrestore(&pdata->mac_lock, flags); + return reg; +} + +/* Switch to external phy. Assumes tx and rx are stopped. */ +static void smsc911x_phy_enable_external(struct smsc911x_data *pdata) +{ + unsigned int hwcfg = smsc911x_reg_read(pdata, HW_CFG); + + /* Disable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_; + smsc911x_reg_write(pdata, HW_CFG, hwcfg); + udelay(10); /* Enough time for clocks to stop */ + + /* Switch to external phy */ + hwcfg |= HW_CFG_EXT_PHY_EN_; + smsc911x_reg_write(pdata, HW_CFG, hwcfg); + + /* Enable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_; + smsc911x_reg_write(pdata, HW_CFG, hwcfg); + udelay(10); /* Enough time for clocks to restart */ + + hwcfg |= HW_CFG_SMI_SEL_; + smsc911x_reg_write(pdata, HW_CFG, hwcfg); +} + +/* Autodetects and enables external phy if present on supported chips. + * autodetection can be overridden by specifying SMSC911X_FORCE_INTERNAL_PHY + * or SMSC911X_FORCE_EXTERNAL_PHY in the platform_data flags. */ +static void smsc911x_phy_initialise_external(struct smsc911x_data *pdata) +{ + unsigned int hwcfg = smsc911x_reg_read(pdata, HW_CFG); + + if (pdata->config.flags & SMSC911X_FORCE_INTERNAL_PHY) { + SMSC_TRACE(pdata, hw, "Forcing internal PHY"); + pdata->using_extphy = 0; + } else if (pdata->config.flags & SMSC911X_FORCE_EXTERNAL_PHY) { + SMSC_TRACE(pdata, hw, "Forcing external PHY"); + smsc911x_phy_enable_external(pdata); + pdata->using_extphy = 1; + } else if (hwcfg & HW_CFG_EXT_PHY_DET_) { + SMSC_TRACE(pdata, hw, + "HW_CFG EXT_PHY_DET set, using external PHY"); + smsc911x_phy_enable_external(pdata); + pdata->using_extphy = 1; + } else { + SMSC_TRACE(pdata, hw, + "HW_CFG EXT_PHY_DET clear, using internal PHY"); + pdata->using_extphy = 0; + } +} + +/* Fetches a tx status out of the status fifo */ +static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata) +{ + unsigned int result = + smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_; + + if (result != 0) + result = smsc911x_reg_read(pdata, TX_STATUS_FIFO); + + return result; +} + +/* Fetches the next rx status */ +static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata) +{ + unsigned int result = + smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_; + + if (result != 0) + result = smsc911x_reg_read(pdata, RX_STATUS_FIFO); + + return result; +} + +#ifdef USE_PHY_WORK_AROUND +static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata) +{ + unsigned int tries; + u32 wrsz; + u32 rdsz; + ulong bufp; + + for (tries = 0; tries < 10; tries++) { + unsigned int txcmd_a; + unsigned int txcmd_b; + unsigned int status; + unsigned int pktlength; + unsigned int i; + + /* Zero-out rx packet memory */ + memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE); + + /* Write tx packet to 118 */ + txcmd_a = (u32)((ulong)pdata->loopback_tx_pkt & 0x03) << 16; + txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + txcmd_a |= MIN_PACKET_SIZE; + + txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE; + + smsc911x_reg_write(pdata, TX_DATA_FIFO, txcmd_a); + smsc911x_reg_write(pdata, TX_DATA_FIFO, txcmd_b); + + bufp = (ulong)pdata->loopback_tx_pkt & (~0x3); + wrsz = MIN_PACKET_SIZE + 3; + wrsz += (u32)((ulong)pdata->loopback_tx_pkt & 0x3); + wrsz >>= 2; + + pdata->ops->tx_writefifo(pdata, (unsigned int *)bufp, wrsz); + + /* Wait till transmit is done */ + i = 60; + do { + udelay(5); + status = smsc911x_tx_get_txstatus(pdata); + } while ((i--) && (!status)); + + if (!status) { + SMSC_WARN(pdata, hw, + "Failed to transmit during loopback test"); + continue; + } + if (status & TX_STS_ES_) { + SMSC_WARN(pdata, hw, + "Transmit encountered errors during loopback test"); + continue; + } + + /* Wait till receive is done */ + i = 60; + do { + udelay(5); + status = smsc911x_rx_get_rxstatus(pdata); + } while ((i--) && (!status)); + + if (!status) { + SMSC_WARN(pdata, hw, + "Failed to receive during loopback test"); + continue; + } + if (status & RX_STS_ES_) { + SMSC_WARN(pdata, hw, + "Receive encountered errors during loopback test"); + continue; + } + + pktlength = ((status & 0x3FFF0000UL) >> 16); + bufp = (ulong)pdata->loopback_rx_pkt; + rdsz = pktlength + 3; + rdsz += (u32)((ulong)pdata->loopback_rx_pkt & 0x3); + rdsz >>= 2; + + pdata->ops->rx_readfifo(pdata, (unsigned int *)bufp, rdsz); + + if (pktlength != (MIN_PACKET_SIZE + 4)) { + SMSC_WARN(pdata, hw, "Unexpected packet size " + "during loop back test, size=%d, will retry", + pktlength); + } else { + unsigned int j; + int mismatch = 0; + for (j = 0; j < MIN_PACKET_SIZE; j++) { + if (pdata->loopback_tx_pkt[j] + != pdata->loopback_rx_pkt[j]) { + mismatch = 1; + break; + } + } + if (!mismatch) { + SMSC_TRACE(pdata, hw, "Successfully verified " + "loopback packet"); + return 0; + } else { + SMSC_WARN(pdata, hw, "Data mismatch " + "during loop back test, will retry"); + } + } + } + + return -EIO; +} + +static int smsc911x_phy_reset(struct smsc911x_data *pdata) +{ + struct phy_device *phy_dev = pdata->phy_dev; + unsigned int temp; + unsigned int i = 100000; + + BUG_ON(!phy_dev); + BUG_ON(!phy_dev->bus); + + SMSC_TRACE(pdata, hw, "Performing PHY BCR Reset"); + smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_BMCR, BMCR_RESET); + do { + msleep(1); + temp = smsc911x_mii_read(phy_dev->bus, phy_dev->addr, + MII_BMCR); + } while ((i--) && (temp & BMCR_RESET)); + + if (temp & BMCR_RESET) { + SMSC_WARN(pdata, hw, "PHY reset failed to complete"); + return -EIO; + } + /* Extra delay required because the phy may not be completed with + * its reset when BMCR_RESET is cleared. Specs say 256 uS is + * enough delay but using 1ms here to be safe */ + msleep(1); + + return 0; +} + +static int smsc911x_phy_loopbacktest(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + struct phy_device *phy_dev = pdata->phy_dev; + int result = -EIO; + unsigned int i, val; + unsigned long flags; + + /* Initialise tx packet using broadcast destination address */ + memset(pdata->loopback_tx_pkt, 0xff, ETH_ALEN); + + /* Use incrementing source address */ + for (i = 6; i < 12; i++) + pdata->loopback_tx_pkt[i] = (char)i; + + /* Set length type field */ + pdata->loopback_tx_pkt[12] = 0x00; + pdata->loopback_tx_pkt[13] = 0x00; + + for (i = 14; i < MIN_PACKET_SIZE; i++) + pdata->loopback_tx_pkt[i] = (char)i; + + val = smsc911x_reg_read(pdata, HW_CFG); + val &= HW_CFG_TX_FIF_SZ_; + val |= HW_CFG_SF_; + smsc911x_reg_write(pdata, HW_CFG, val); + + smsc911x_reg_write(pdata, TX_CFG, TX_CFG_TX_ON_); + smsc911x_reg_write(pdata, RX_CFG, + (u32)((ulong)pdata->loopback_rx_pkt & 0x03) << 8); + + for (i = 0; i < 10; i++) { + /* Set PHY to 10/FD, no ANEG, and loopback mode */ + smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_BMCR, + BMCR_LOOPBACK | BMCR_FULLDPLX); + + /* Enable MAC tx/rx, FD */ + spin_lock_irqsave(&pdata->mac_lock, flags); + smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_ + | MAC_CR_TXEN_ | MAC_CR_RXEN_); + spin_unlock_irqrestore(&pdata->mac_lock, flags); + + if (smsc911x_phy_check_loopbackpkt(pdata) == 0) { + result = 0; + break; + } + pdata->resetcount++; + + /* Disable MAC rx */ + spin_lock_irqsave(&pdata->mac_lock, flags); + smsc911x_mac_write(pdata, MAC_CR, 0); + spin_unlock_irqrestore(&pdata->mac_lock, flags); + + smsc911x_phy_reset(pdata); + } + + /* Disable MAC */ + spin_lock_irqsave(&pdata->mac_lock, flags); + smsc911x_mac_write(pdata, MAC_CR, 0); + spin_unlock_irqrestore(&pdata->mac_lock, flags); + + /* Cancel PHY loopback mode */ + smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_BMCR, 0); + + smsc911x_reg_write(pdata, TX_CFG, 0); + smsc911x_reg_write(pdata, RX_CFG, 0); + + return result; +} +#endif /* USE_PHY_WORK_AROUND */ + +static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata) +{ + struct phy_device *phy_dev = pdata->phy_dev; + u32 afc = smsc911x_reg_read(pdata, AFC_CFG); + u32 flow; + unsigned long flags; + + if (phy_dev->duplex == DUPLEX_FULL) { + u16 lcladv = phy_read(phy_dev, MII_ADVERTISE); + u16 rmtadv = phy_read(phy_dev, MII_LPA); + u8 cap = mii_resolve_flowctrl_fdx(lcladv, rmtadv); + + if (cap & FLOW_CTRL_RX) + flow = 0xFFFF0002; + else + flow = 0; + + if (cap & FLOW_CTRL_TX) + afc |= 0xF; + else + afc &= ~0xF; + + SMSC_TRACE(pdata, hw, "rx pause %s, tx pause %s", + (cap & FLOW_CTRL_RX ? "enabled" : "disabled"), + (cap & FLOW_CTRL_TX ? "enabled" : "disabled")); + } else { + SMSC_TRACE(pdata, hw, "half duplex"); + flow = 0; + afc |= 0xF; + } + + spin_lock_irqsave(&pdata->mac_lock, flags); + smsc911x_mac_write(pdata, FLOW, flow); + spin_unlock_irqrestore(&pdata->mac_lock, flags); + + smsc911x_reg_write(pdata, AFC_CFG, afc); +} + +/* Update link mode if anything has changed. Called periodically when the + * PHY is in polling mode, even if nothing has changed. */ +static void smsc911x_phy_adjust_link(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + struct phy_device *phy_dev = pdata->phy_dev; + unsigned long flags; + int carrier; + + if (phy_dev->duplex != pdata->last_duplex) { + unsigned int mac_cr; + SMSC_TRACE(pdata, hw, "duplex state has changed"); + + spin_lock_irqsave(&pdata->mac_lock, flags); + mac_cr = smsc911x_mac_read(pdata, MAC_CR); + if (phy_dev->duplex) { + SMSC_TRACE(pdata, hw, + "configuring for full duplex mode"); + mac_cr |= MAC_CR_FDPX_; + } else { + SMSC_TRACE(pdata, hw, + "configuring for half duplex mode"); + mac_cr &= ~MAC_CR_FDPX_; + } + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + spin_unlock_irqrestore(&pdata->mac_lock, flags); + + smsc911x_phy_update_flowcontrol(pdata); + pdata->last_duplex = phy_dev->duplex; + } + + carrier = netif_carrier_ok(dev); + if (carrier != pdata->last_carrier) { + SMSC_TRACE(pdata, hw, "carrier state has changed"); + if (carrier) { + SMSC_TRACE(pdata, hw, "configuring for carrier OK"); + if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) && + (!pdata->using_extphy)) { + /* Restore original GPIO configuration */ + pdata->gpio_setting = pdata->gpio_orig_setting; + smsc911x_reg_write(pdata, GPIO_CFG, + pdata->gpio_setting); + } + } else { + SMSC_TRACE(pdata, hw, "configuring for no carrier"); + /* Check global setting that LED1 + * usage is 10/100 indicator */ + pdata->gpio_setting = smsc911x_reg_read(pdata, + GPIO_CFG); + if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_) && + (!pdata->using_extphy)) { + /* Force 10/100 LED off, after saving + * original GPIO configuration */ + pdata->gpio_orig_setting = pdata->gpio_setting; + + pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_; + pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_ + | GPIO_CFG_GPIODIR0_ + | GPIO_CFG_GPIOD0_); + smsc911x_reg_write(pdata, GPIO_CFG, + pdata->gpio_setting); + } + } + pdata->last_carrier = carrier; + } +} + +static int smsc911x_mii_probe(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + struct phy_device *phydev = NULL; + int ret; + + /* find the first phy */ + phydev = phy_find_first(pdata->mii_bus); + if (!phydev) { + netdev_err(dev, "no PHY found\n"); + return -ENODEV; + } + + SMSC_TRACE(pdata, probe, "PHY: addr %d, phy_id 0x%08X", + phydev->addr, phydev->phy_id); + + ret = phy_connect_direct(dev, phydev, + &smsc911x_phy_adjust_link, 0, + pdata->config.phy_interface); + + if (ret) { + netdev_err(dev, "Could not attach to PHY\n"); + return ret; + } + + netdev_info(dev, + "attached PHY driver [%s] (mii_bus:phy_addr=%s, irq=%d)\n", + phydev->drv->name, dev_name(&phydev->dev), phydev->irq); + + /* mask with MAC supported features */ + phydev->supported &= (PHY_BASIC_FEATURES | SUPPORTED_Pause | + SUPPORTED_Asym_Pause); + phydev->advertising = phydev->supported; + + pdata->phy_dev = phydev; + pdata->last_duplex = -1; + pdata->last_carrier = -1; + +#ifdef USE_PHY_WORK_AROUND + if (smsc911x_phy_loopbacktest(dev) < 0) { + SMSC_WARN(pdata, hw, "Failed Loop Back Test"); + return -ENODEV; + } + SMSC_TRACE(pdata, hw, "Passed Loop Back Test"); +#endif /* USE_PHY_WORK_AROUND */ + + SMSC_TRACE(pdata, hw, "phy initialised successfully"); + return 0; +} + +static int __devinit smsc911x_mii_init(struct platform_device *pdev, + struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + int err = -ENXIO, i; + + pdata->mii_bus = mdiobus_alloc(); + if (!pdata->mii_bus) { + err = -ENOMEM; + goto err_out_1; + } + + pdata->mii_bus->name = SMSC_MDIONAME; + snprintf(pdata->mii_bus->id, MII_BUS_ID_SIZE, "%x", pdev->id); + pdata->mii_bus->priv = pdata; + pdata->mii_bus->read = smsc911x_mii_read; + pdata->mii_bus->write = smsc911x_mii_write; + pdata->mii_bus->irq = pdata->phy_irq; + for (i = 0; i < PHY_MAX_ADDR; ++i) + pdata->mii_bus->irq[i] = PHY_POLL; + + pdata->mii_bus->parent = &pdev->dev; + + switch (pdata->idrev & 0xFFFF0000) { + case 0x01170000: + case 0x01150000: + case 0x117A0000: + case 0x115A0000: + /* External PHY supported, try to autodetect */ + smsc911x_phy_initialise_external(pdata); + break; + default: + SMSC_TRACE(pdata, hw, "External PHY is not supported, " + "using internal PHY"); + pdata->using_extphy = 0; + break; + } + + if (!pdata->using_extphy) { + /* Mask all PHYs except ID 1 (internal) */ + pdata->mii_bus->phy_mask = ~(1 << 1); + } + + if (mdiobus_register(pdata->mii_bus)) { + SMSC_WARN(pdata, probe, "Error registering mii bus"); + goto err_out_free_bus_2; + } + + if (smsc911x_mii_probe(dev) < 0) { + SMSC_WARN(pdata, probe, "Error registering mii bus"); + goto err_out_unregister_bus_3; + } + + return 0; + +err_out_unregister_bus_3: + mdiobus_unregister(pdata->mii_bus); +err_out_free_bus_2: + mdiobus_free(pdata->mii_bus); +err_out_1: + return err; +} + +/* Gets the number of tx statuses in the fifo */ +static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata) +{ + return (smsc911x_reg_read(pdata, TX_FIFO_INF) + & TX_FIFO_INF_TSUSED_) >> 16; +} + +/* Reads tx statuses and increments counters where necessary */ +static void smsc911x_tx_update_txcounters(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int tx_stat; + + while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) { + if (unlikely(tx_stat & 0x80000000)) { + /* In this driver the packet tag is used as the packet + * length. Since a packet length can never reach the + * size of 0x8000, this bit is reserved. It is worth + * noting that the "reserved bit" in the warning above + * does not reference a hardware defined reserved bit + * but rather a driver defined one. + */ + SMSC_WARN(pdata, hw, "Packet tag reserved bit is high"); + } else { + if (unlikely(tx_stat & TX_STS_ES_)) { + dev->stats.tx_errors++; + } else { + dev->stats.tx_packets++; + dev->stats.tx_bytes += (tx_stat >> 16); + } + if (unlikely(tx_stat & TX_STS_EXCESS_COL_)) { + dev->stats.collisions += 16; + dev->stats.tx_aborted_errors += 1; + } else { + dev->stats.collisions += + ((tx_stat >> 3) & 0xF); + } + if (unlikely(tx_stat & TX_STS_LOST_CARRIER_)) + dev->stats.tx_carrier_errors += 1; + if (unlikely(tx_stat & TX_STS_LATE_COL_)) { + dev->stats.collisions++; + dev->stats.tx_aborted_errors++; + } + } + } +} + +/* Increments the Rx error counters */ +static void +smsc911x_rx_counterrors(struct net_device *dev, unsigned int rxstat) +{ + int crc_err = 0; + + if (unlikely(rxstat & RX_STS_ES_)) { + dev->stats.rx_errors++; + if (unlikely(rxstat & RX_STS_CRC_ERR_)) { + dev->stats.rx_crc_errors++; + crc_err = 1; + } + } + if (likely(!crc_err)) { + if (unlikely((rxstat & RX_STS_FRAME_TYPE_) && + (rxstat & RX_STS_LENGTH_ERR_))) + dev->stats.rx_length_errors++; + if (rxstat & RX_STS_MCAST_) + dev->stats.multicast++; + } +} + +/* Quickly dumps bad packets */ +static void +smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int pktbytes) +{ + unsigned int pktwords = (pktbytes + NET_IP_ALIGN + 3) >> 2; + + if (likely(pktwords >= 4)) { + unsigned int timeout = 500; + unsigned int val; + smsc911x_reg_write(pdata, RX_DP_CTRL, RX_DP_CTRL_RX_FFWD_); + do { + udelay(1); + val = smsc911x_reg_read(pdata, RX_DP_CTRL); + } while ((val & RX_DP_CTRL_RX_FFWD_) && --timeout); + + if (unlikely(timeout == 0)) + SMSC_WARN(pdata, hw, "Timed out waiting for " + "RX FFWD to finish, RX_DP_CTRL: 0x%08X", val); + } else { + unsigned int temp; + while (pktwords--) + temp = smsc911x_reg_read(pdata, RX_DATA_FIFO); + } +} + +/* NAPI poll function */ +static int smsc911x_poll(struct napi_struct *napi, int budget) +{ + struct smsc911x_data *pdata = + container_of(napi, struct smsc911x_data, napi); + struct net_device *dev = pdata->dev; + int npackets = 0; + + while (npackets < budget) { + unsigned int pktlength; + unsigned int pktwords; + struct sk_buff *skb; + unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata); + + if (!rxstat) { + unsigned int temp; + /* We processed all packets available. Tell NAPI it can + * stop polling then re-enable rx interrupts */ + smsc911x_reg_write(pdata, INT_STS, INT_STS_RSFL_); + napi_complete(napi); + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_RSFL_EN_; + smsc911x_reg_write(pdata, INT_EN, temp); + break; + } + + /* Count packet for NAPI scheduling, even if it has an error. + * Error packets still require cycles to discard */ + npackets++; + + pktlength = ((rxstat & 0x3FFF0000) >> 16); + pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2; + smsc911x_rx_counterrors(dev, rxstat); + + if (unlikely(rxstat & RX_STS_ES_)) { + SMSC_WARN(pdata, rx_err, + "Discarding packet with error bit set"); + /* Packet has an error, discard it and continue with + * the next */ + smsc911x_rx_fastforward(pdata, pktwords); + dev->stats.rx_dropped++; + continue; + } + + skb = netdev_alloc_skb(dev, pktlength + NET_IP_ALIGN); + if (unlikely(!skb)) { + SMSC_WARN(pdata, rx_err, + "Unable to allocate skb for rx packet"); + /* Drop the packet and stop this polling iteration */ + smsc911x_rx_fastforward(pdata, pktwords); + dev->stats.rx_dropped++; + break; + } + + skb->data = skb->head; + skb_reset_tail_pointer(skb); + + /* Align IP on 16B boundary */ + skb_reserve(skb, NET_IP_ALIGN); + skb_put(skb, pktlength - 4); + pdata->ops->rx_readfifo(pdata, + (unsigned int *)skb->head, pktwords); + skb->protocol = eth_type_trans(skb, dev); + skb_checksum_none_assert(skb); + netif_receive_skb(skb); + + /* Update counters */ + dev->stats.rx_packets++; + dev->stats.rx_bytes += (pktlength - 4); + } + + /* Return total received packets */ + return npackets; +} + +/* Returns hash bit number for given MAC address + * Example: + * 01 00 5E 00 00 01 -> returns bit number 31 */ +static unsigned int smsc911x_hash(char addr[ETH_ALEN]) +{ + return (ether_crc(ETH_ALEN, addr) >> 26) & 0x3f; +} + +static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata) +{ + /* Performs the multicast & mac_cr update. This is called when + * safe on the current hardware, and with the mac_lock held */ + unsigned int mac_cr; + + SMSC_ASSERT_MAC_LOCK(pdata); + + mac_cr = smsc911x_mac_read(pdata, MAC_CR); + mac_cr |= pdata->set_bits_mask; + mac_cr &= ~(pdata->clear_bits_mask); + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + smsc911x_mac_write(pdata, HASHH, pdata->hashhi); + smsc911x_mac_write(pdata, HASHL, pdata->hashlo); + SMSC_TRACE(pdata, hw, "maccr 0x%08X, HASHH 0x%08X, HASHL 0x%08X", + mac_cr, pdata->hashhi, pdata->hashlo); +} + +static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata) +{ + unsigned int mac_cr; + + /* This function is only called for older LAN911x devices + * (revA or revB), where MAC_CR, HASHH and HASHL should not + * be modified during Rx - newer devices immediately update the + * registers. + * + * This is called from interrupt context */ + + spin_lock(&pdata->mac_lock); + + /* Check Rx has stopped */ + if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_) + SMSC_WARN(pdata, drv, "Rx not stopped"); + + /* Perform the update - safe to do now Rx has stopped */ + smsc911x_rx_multicast_update(pdata); + + /* Re-enable Rx */ + mac_cr = smsc911x_mac_read(pdata, MAC_CR); + mac_cr |= MAC_CR_RXEN_; + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + + pdata->multicast_update_pending = 0; + + spin_unlock(&pdata->mac_lock); +} + +static int smsc911x_soft_reset(struct smsc911x_data *pdata) +{ + unsigned int timeout; + unsigned int temp; + + /* Reset the LAN911x */ + smsc911x_reg_write(pdata, HW_CFG, HW_CFG_SRST_); + timeout = 10; + do { + udelay(10); + temp = smsc911x_reg_read(pdata, HW_CFG); + } while ((--timeout) && (temp & HW_CFG_SRST_)); + + if (unlikely(temp & HW_CFG_SRST_)) { + SMSC_WARN(pdata, drv, "Failed to complete reset"); + return -EIO; + } + return 0; +} + +/* Sets the device MAC address to dev_addr, called with mac_lock held */ +static void +smsc911x_set_hw_mac_address(struct smsc911x_data *pdata, u8 dev_addr[6]) +{ + u32 mac_high16 = (dev_addr[5] << 8) | dev_addr[4]; + u32 mac_low32 = (dev_addr[3] << 24) | (dev_addr[2] << 16) | + (dev_addr[1] << 8) | dev_addr[0]; + + SMSC_ASSERT_MAC_LOCK(pdata); + + smsc911x_mac_write(pdata, ADDRH, mac_high16); + smsc911x_mac_write(pdata, ADDRL, mac_low32); +} + +static int smsc911x_open(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int timeout; + unsigned int temp; + unsigned int intcfg; + + /* if the phy is not yet registered, retry later*/ + if (!pdata->phy_dev) { + SMSC_WARN(pdata, hw, "phy_dev is NULL"); + return -EAGAIN; + } + + if (!is_valid_ether_addr(dev->dev_addr)) { + SMSC_WARN(pdata, hw, "dev_addr is not a valid MAC address"); + return -EADDRNOTAVAIL; + } + + /* Reset the LAN911x */ + if (smsc911x_soft_reset(pdata)) { + SMSC_WARN(pdata, hw, "soft reset failed"); + return -EIO; + } + + smsc911x_reg_write(pdata, HW_CFG, 0x00050000); + smsc911x_reg_write(pdata, AFC_CFG, 0x006E3740); + + /* Increase the legal frame size of VLAN tagged frames to 1522 bytes */ + spin_lock_irq(&pdata->mac_lock); + smsc911x_mac_write(pdata, VLAN1, ETH_P_8021Q); + spin_unlock_irq(&pdata->mac_lock); + + /* Make sure EEPROM has finished loading before setting GPIO_CFG */ + timeout = 50; + while ((smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) && + --timeout) { + udelay(10); + } + + if (unlikely(timeout == 0)) + SMSC_WARN(pdata, ifup, + "Timed out waiting for EEPROM busy bit to clear"); + + smsc911x_reg_write(pdata, GPIO_CFG, 0x70070000); + + /* The soft reset above cleared the device's MAC address, + * restore it from local copy (set in probe) */ + spin_lock_irq(&pdata->mac_lock); + smsc911x_set_hw_mac_address(pdata, dev->dev_addr); + spin_unlock_irq(&pdata->mac_lock); + + /* Initialise irqs, but leave all sources disabled */ + smsc911x_reg_write(pdata, INT_EN, 0); + smsc911x_reg_write(pdata, INT_STS, 0xFFFFFFFF); + + /* Set interrupt deassertion to 100uS */ + intcfg = ((10 << 24) | INT_CFG_IRQ_EN_); + + if (pdata->config.irq_polarity) { + SMSC_TRACE(pdata, ifup, "irq polarity: active high"); + intcfg |= INT_CFG_IRQ_POL_; + } else { + SMSC_TRACE(pdata, ifup, "irq polarity: active low"); + } + + if (pdata->config.irq_type) { + SMSC_TRACE(pdata, ifup, "irq type: push-pull"); + intcfg |= INT_CFG_IRQ_TYPE_; + } else { + SMSC_TRACE(pdata, ifup, "irq type: open drain"); + } + + smsc911x_reg_write(pdata, INT_CFG, intcfg); + + SMSC_TRACE(pdata, ifup, "Testing irq handler using IRQ %d", dev->irq); + pdata->software_irq_signal = 0; + smp_wmb(); + + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_SW_INT_EN_; + smsc911x_reg_write(pdata, INT_EN, temp); + + timeout = 1000; + while (timeout--) { + if (pdata->software_irq_signal) + break; + msleep(1); + } + + if (!pdata->software_irq_signal) { + netdev_warn(dev, "ISR failed signaling test (IRQ %d)\n", + dev->irq); + return -ENODEV; + } + SMSC_TRACE(pdata, ifup, "IRQ handler passed test using IRQ %d", + dev->irq); + + netdev_info(dev, "SMSC911x/921x identified at %#08lx, IRQ: %d\n", + (unsigned long)pdata->ioaddr, dev->irq); + + /* Reset the last known duplex and carrier */ + pdata->last_duplex = -1; + pdata->last_carrier = -1; + + /* Bring the PHY up */ + phy_start(pdata->phy_dev); + + temp = smsc911x_reg_read(pdata, HW_CFG); + /* Preserve TX FIFO size and external PHY configuration */ + temp &= (HW_CFG_TX_FIF_SZ_|0x00000FFF); + temp |= HW_CFG_SF_; + smsc911x_reg_write(pdata, HW_CFG, temp); + + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp |= FIFO_INT_TX_AVAIL_LEVEL_; + temp &= ~(FIFO_INT_RX_STS_LEVEL_); + smsc911x_reg_write(pdata, FIFO_INT, temp); + + /* set RX Data offset to 2 bytes for alignment */ + smsc911x_reg_write(pdata, RX_CFG, (2 << 8)); + + /* enable NAPI polling before enabling RX interrupts */ + napi_enable(&pdata->napi); + + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_RXSTOP_INT_EN_); + smsc911x_reg_write(pdata, INT_EN, temp); + + spin_lock_irq(&pdata->mac_lock); + temp = smsc911x_mac_read(pdata, MAC_CR); + temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_); + smsc911x_mac_write(pdata, MAC_CR, temp); + spin_unlock_irq(&pdata->mac_lock); + + smsc911x_reg_write(pdata, TX_CFG, TX_CFG_TX_ON_); + + netif_start_queue(dev); + return 0; +} + +/* Entry point for stopping the interface */ +static int smsc911x_stop(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int temp; + + /* Disable all device interrupts */ + temp = smsc911x_reg_read(pdata, INT_CFG); + temp &= ~INT_CFG_IRQ_EN_; + smsc911x_reg_write(pdata, INT_CFG, temp); + + /* Stop Tx and Rx polling */ + netif_stop_queue(dev); + napi_disable(&pdata->napi); + + /* At this point all Rx and Tx activity is stopped */ + dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + smsc911x_tx_update_txcounters(dev); + + /* Bring the PHY down */ + if (pdata->phy_dev) + phy_stop(pdata->phy_dev); + + SMSC_TRACE(pdata, ifdown, "Interface stopped"); + return 0; +} + +/* Entry point for transmitting a packet */ +static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int freespace; + unsigned int tx_cmd_a; + unsigned int tx_cmd_b; + unsigned int temp; + u32 wrsz; + ulong bufp; + + freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_; + + if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD)) + SMSC_WARN(pdata, tx_err, + "Tx data fifo low, space available: %d", freespace); + + /* Word alignment adjustment */ + tx_cmd_a = (u32)((ulong)skb->data & 0x03) << 16; + tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + tx_cmd_a |= (unsigned int)skb->len; + + tx_cmd_b = ((unsigned int)skb->len) << 16; + tx_cmd_b |= (unsigned int)skb->len; + + smsc911x_reg_write(pdata, TX_DATA_FIFO, tx_cmd_a); + smsc911x_reg_write(pdata, TX_DATA_FIFO, tx_cmd_b); + + bufp = (ulong)skb->data & (~0x3); + wrsz = (u32)skb->len + 3; + wrsz += (u32)((ulong)skb->data & 0x3); + wrsz >>= 2; + + pdata->ops->tx_writefifo(pdata, (unsigned int *)bufp, wrsz); + freespace -= (skb->len + 32); + skb_tx_timestamp(skb); + dev_kfree_skb(skb); + + if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30)) + smsc911x_tx_update_txcounters(dev); + + if (freespace < TX_FIFO_LOW_THRESHOLD) { + netif_stop_queue(dev); + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp &= 0x00FFFFFF; + temp |= 0x32000000; + smsc911x_reg_write(pdata, FIFO_INT, temp); + } + + return NETDEV_TX_OK; +} + +/* Entry point for getting status counters */ +static struct net_device_stats *smsc911x_get_stats(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + smsc911x_tx_update_txcounters(dev); + dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + return &dev->stats; +} + +/* Entry point for setting addressing modes */ +static void smsc911x_set_multicast_list(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + + if (dev->flags & IFF_PROMISC) { + /* Enabling promiscuous mode */ + pdata->set_bits_mask = MAC_CR_PRMS_; + pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } else if (dev->flags & IFF_ALLMULTI) { + /* Enabling all multicast mode */ + pdata->set_bits_mask = MAC_CR_MCPAS_; + pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } else if (!netdev_mc_empty(dev)) { + /* Enabling specific multicast addresses */ + unsigned int hash_high = 0; + unsigned int hash_low = 0; + struct netdev_hw_addr *ha; + + pdata->set_bits_mask = MAC_CR_HPFILT_; + pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_); + + netdev_for_each_mc_addr(ha, dev) { + unsigned int bitnum = smsc911x_hash(ha->addr); + unsigned int mask = 0x01 << (bitnum & 0x1F); + + if (bitnum & 0x20) + hash_high |= mask; + else + hash_low |= mask; + } + + pdata->hashhi = hash_high; + pdata->hashlo = hash_low; + } else { + /* Enabling local MAC address only */ + pdata->set_bits_mask = 0; + pdata->clear_bits_mask = + (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } + + spin_lock_irqsave(&pdata->mac_lock, flags); + + if (pdata->generation <= 1) { + /* Older hardware revision - cannot change these flags while + * receiving data */ + if (!pdata->multicast_update_pending) { + unsigned int temp; + SMSC_TRACE(pdata, hw, "scheduling mcast update"); + pdata->multicast_update_pending = 1; + + /* Request the hardware to stop, then perform the + * update when we get an RX_STOP interrupt */ + temp = smsc911x_mac_read(pdata, MAC_CR); + temp &= ~(MAC_CR_RXEN_); + smsc911x_mac_write(pdata, MAC_CR, temp); + } else { + /* There is another update pending, this should now + * use the newer values */ + } + } else { + /* Newer hardware revision - can write immediately */ + smsc911x_rx_multicast_update(pdata); + } + + spin_unlock_irqrestore(&pdata->mac_lock, flags); +} + +static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct smsc911x_data *pdata = netdev_priv(dev); + u32 intsts = smsc911x_reg_read(pdata, INT_STS); + u32 inten = smsc911x_reg_read(pdata, INT_EN); + int serviced = IRQ_NONE; + u32 temp; + + if (unlikely(intsts & inten & INT_STS_SW_INT_)) { + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_SW_INT_EN_); + smsc911x_reg_write(pdata, INT_EN, temp); + smsc911x_reg_write(pdata, INT_STS, INT_STS_SW_INT_); + pdata->software_irq_signal = 1; + smp_wmb(); + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) { + /* Called when there is a multicast update scheduled and + * it is now safe to complete the update */ + SMSC_TRACE(pdata, intr, "RX Stop interrupt"); + smsc911x_reg_write(pdata, INT_STS, INT_STS_RXSTOP_INT_); + if (pdata->multicast_update_pending) + smsc911x_rx_multicast_update_workaround(pdata); + serviced = IRQ_HANDLED; + } + + if (intsts & inten & INT_STS_TDFA_) { + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp |= FIFO_INT_TX_AVAIL_LEVEL_; + smsc911x_reg_write(pdata, FIFO_INT, temp); + smsc911x_reg_write(pdata, INT_STS, INT_STS_TDFA_); + netif_wake_queue(dev); + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_RXE_)) { + SMSC_TRACE(pdata, intr, "RX Error interrupt"); + smsc911x_reg_write(pdata, INT_STS, INT_STS_RXE_); + serviced = IRQ_HANDLED; + } + + if (likely(intsts & inten & INT_STS_RSFL_)) { + if (likely(napi_schedule_prep(&pdata->napi))) { + /* Disable Rx interrupts */ + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_RSFL_EN_); + smsc911x_reg_write(pdata, INT_EN, temp); + /* Schedule a NAPI poll */ + __napi_schedule(&pdata->napi); + } else { + SMSC_WARN(pdata, rx_err, "napi_schedule_prep failed"); + } + serviced = IRQ_HANDLED; + } + + return serviced; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void smsc911x_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + smsc911x_irqhandler(0, dev); + enable_irq(dev->irq); +} +#endif /* CONFIG_NET_POLL_CONTROLLER */ + +static int smsc911x_set_mac_address(struct net_device *dev, void *p) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + struct sockaddr *addr = p; + + /* On older hardware revisions we cannot change the mac address + * registers while receiving data. Newer devices can safely change + * this at any time. */ + if (pdata->generation <= 1 && netif_running(dev)) + return -EBUSY; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN); + + spin_lock_irq(&pdata->mac_lock); + smsc911x_set_hw_mac_address(pdata, dev->dev_addr); + spin_unlock_irq(&pdata->mac_lock); + + netdev_info(dev, "MAC Address: %pM\n", dev->dev_addr); + + return 0; +} + +/* Standard ioctls for mii-tool */ +static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + if (!netif_running(dev) || !pdata->phy_dev) + return -EINVAL; + + return phy_mii_ioctl(pdata->phy_dev, ifr, cmd); +} + +static int +smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + cmd->maxtxpkt = 1; + cmd->maxrxpkt = 1; + return phy_ethtool_gset(pdata->phy_dev, cmd); +} + +static int +smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + return phy_ethtool_sset(pdata->phy_dev, cmd); +} + +static void smsc911x_ethtool_getdrvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver)); + strlcpy(info->version, SMSC_DRV_VERSION, sizeof(info->version)); + strlcpy(info->bus_info, dev_name(dev->dev.parent), + sizeof(info->bus_info)); +} + +static int smsc911x_ethtool_nwayreset(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + return phy_start_aneg(pdata->phy_dev); +} + +static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + return pdata->msg_enable; +} + +static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + pdata->msg_enable = level; +} + +static int smsc911x_ethtool_getregslen(struct net_device *dev) +{ + return (((E2P_DATA - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) * + sizeof(u32); +} + +static void +smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs, + void *buf) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + struct phy_device *phy_dev = pdata->phy_dev; + unsigned long flags; + unsigned int i; + unsigned int j = 0; + u32 *data = buf; + + regs->version = pdata->idrev; + for (i = ID_REV; i <= E2P_DATA; i += (sizeof(u32))) + data[j++] = smsc911x_reg_read(pdata, i); + + for (i = MAC_CR; i <= WUCSR; i++) { + spin_lock_irqsave(&pdata->mac_lock, flags); + data[j++] = smsc911x_mac_read(pdata, i); + spin_unlock_irqrestore(&pdata->mac_lock, flags); + } + + for (i = 0; i <= 31; i++) + data[j++] = smsc911x_mii_read(phy_dev->bus, phy_dev->addr, i); +} + +static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata) +{ + unsigned int temp = smsc911x_reg_read(pdata, GPIO_CFG); + temp &= ~GPIO_CFG_EEPR_EN_; + smsc911x_reg_write(pdata, GPIO_CFG, temp); + msleep(1); +} + +static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op) +{ + int timeout = 100; + u32 e2cmd; + + SMSC_TRACE(pdata, drv, "op 0x%08x", op); + if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) { + SMSC_WARN(pdata, drv, "Busy at start"); + return -EBUSY; + } + + e2cmd = op | E2P_CMD_EPC_BUSY_; + smsc911x_reg_write(pdata, E2P_CMD, e2cmd); + + do { + msleep(1); + e2cmd = smsc911x_reg_read(pdata, E2P_CMD); + } while ((e2cmd & E2P_CMD_EPC_BUSY_) && (--timeout)); + + if (!timeout) { + SMSC_TRACE(pdata, drv, "TIMED OUT"); + return -EAGAIN; + } + + if (e2cmd & E2P_CMD_EPC_TIMEOUT_) { + SMSC_TRACE(pdata, drv, "Error occurred during eeprom operation"); + return -EINVAL; + } + + return 0; +} + +static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata, + u8 address, u8 *data) +{ + u32 op = E2P_CMD_EPC_CMD_READ_ | address; + int ret; + + SMSC_TRACE(pdata, drv, "address 0x%x", address); + ret = smsc911x_eeprom_send_cmd(pdata, op); + + if (!ret) + data[address] = smsc911x_reg_read(pdata, E2P_DATA); + + return ret; +} + +static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata, + u8 address, u8 data) +{ + u32 op = E2P_CMD_EPC_CMD_ERASE_ | address; + u32 temp; + int ret; + + SMSC_TRACE(pdata, drv, "address 0x%x, data 0x%x", address, data); + ret = smsc911x_eeprom_send_cmd(pdata, op); + + if (!ret) { + op = E2P_CMD_EPC_CMD_WRITE_ | address; + smsc911x_reg_write(pdata, E2P_DATA, (u32)data); + + /* Workaround for hardware read-after-write restriction */ + temp = smsc911x_reg_read(pdata, BYTE_TEST); + + ret = smsc911x_eeprom_send_cmd(pdata, op); + } + + return ret; +} + +static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev) +{ + return SMSC911X_EEPROM_SIZE; +} + +static int smsc911x_ethtool_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + u8 eeprom_data[SMSC911X_EEPROM_SIZE]; + int len; + int i; + + smsc911x_eeprom_enable_access(pdata); + + len = min(eeprom->len, SMSC911X_EEPROM_SIZE); + for (i = 0; i < len; i++) { + int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data); + if (ret < 0) { + eeprom->len = 0; + return ret; + } + } + + memcpy(data, &eeprom_data[eeprom->offset], len); + eeprom->len = len; + return 0; +} + +static int smsc911x_ethtool_set_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + int ret; + struct smsc911x_data *pdata = netdev_priv(dev); + + smsc911x_eeprom_enable_access(pdata); + smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_); + ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data); + smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_); + + /* Single byte write, according to man page */ + eeprom->len = 1; + + return ret; +} + +static const struct ethtool_ops smsc911x_ethtool_ops = { + .get_settings = smsc911x_ethtool_getsettings, + .set_settings = smsc911x_ethtool_setsettings, + .get_link = ethtool_op_get_link, + .get_drvinfo = smsc911x_ethtool_getdrvinfo, + .nway_reset = smsc911x_ethtool_nwayreset, + .get_msglevel = smsc911x_ethtool_getmsglevel, + .set_msglevel = smsc911x_ethtool_setmsglevel, + .get_regs_len = smsc911x_ethtool_getregslen, + .get_regs = smsc911x_ethtool_getregs, + .get_eeprom_len = smsc911x_ethtool_get_eeprom_len, + .get_eeprom = smsc911x_ethtool_get_eeprom, + .set_eeprom = smsc911x_ethtool_set_eeprom, +}; + +static const struct net_device_ops smsc911x_netdev_ops = { + .ndo_open = smsc911x_open, + .ndo_stop = smsc911x_stop, + .ndo_start_xmit = smsc911x_hard_start_xmit, + .ndo_get_stats = smsc911x_get_stats, + .ndo_set_multicast_list = smsc911x_set_multicast_list, + .ndo_do_ioctl = smsc911x_do_ioctl, + .ndo_change_mtu = eth_change_mtu, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = smsc911x_set_mac_address, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = smsc911x_poll_controller, +#endif +}; + +/* copies the current mac address from hardware to dev->dev_addr */ +static void __devinit smsc911x_read_mac_address(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + u32 mac_high16 = smsc911x_mac_read(pdata, ADDRH); + u32 mac_low32 = smsc911x_mac_read(pdata, ADDRL); + + dev->dev_addr[0] = (u8)(mac_low32); + dev->dev_addr[1] = (u8)(mac_low32 >> 8); + dev->dev_addr[2] = (u8)(mac_low32 >> 16); + dev->dev_addr[3] = (u8)(mac_low32 >> 24); + dev->dev_addr[4] = (u8)(mac_high16); + dev->dev_addr[5] = (u8)(mac_high16 >> 8); +} + +/* Initializing private device structures, only called from probe */ +static int __devinit smsc911x_init(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int byte_test; + + SMSC_TRACE(pdata, probe, "Driver Parameters:"); + SMSC_TRACE(pdata, probe, "LAN base: 0x%08lX", + (unsigned long)pdata->ioaddr); + SMSC_TRACE(pdata, probe, "IRQ: %d", dev->irq); + SMSC_TRACE(pdata, probe, "PHY will be autodetected."); + + spin_lock_init(&pdata->dev_lock); + spin_lock_init(&pdata->mac_lock); + + if (pdata->ioaddr == 0) { + SMSC_WARN(pdata, probe, "pdata->ioaddr: 0x00000000"); + return -ENODEV; + } + + /* Check byte ordering */ + byte_test = smsc911x_reg_read(pdata, BYTE_TEST); + SMSC_TRACE(pdata, probe, "BYTE_TEST: 0x%08X", byte_test); + if (byte_test == 0x43218765) { + SMSC_TRACE(pdata, probe, "BYTE_TEST looks swapped, " + "applying WORD_SWAP"); + smsc911x_reg_write(pdata, WORD_SWAP, 0xffffffff); + + /* 1 dummy read of BYTE_TEST is needed after a write to + * WORD_SWAP before its contents are valid */ + byte_test = smsc911x_reg_read(pdata, BYTE_TEST); + + byte_test = smsc911x_reg_read(pdata, BYTE_TEST); + } + + if (byte_test != 0x87654321) { + SMSC_WARN(pdata, drv, "BYTE_TEST: 0x%08X", byte_test); + if (((byte_test >> 16) & 0xFFFF) == (byte_test & 0xFFFF)) { + SMSC_WARN(pdata, probe, + "top 16 bits equal to bottom 16 bits"); + SMSC_TRACE(pdata, probe, + "This may mean the chip is set " + "for 32 bit while the bus is reading 16 bit"); + } + return -ENODEV; + } + + /* Default generation to zero (all workarounds apply) */ + pdata->generation = 0; + + pdata->idrev = smsc911x_reg_read(pdata, ID_REV); + switch (pdata->idrev & 0xFFFF0000) { + case 0x01180000: + case 0x01170000: + case 0x01160000: + case 0x01150000: + /* LAN911[5678] family */ + pdata->generation = pdata->idrev & 0x0000FFFF; + break; + + case 0x118A0000: + case 0x117A0000: + case 0x116A0000: + case 0x115A0000: + /* LAN921[5678] family */ + pdata->generation = 3; + break; + + case 0x92100000: + case 0x92110000: + case 0x92200000: + case 0x92210000: + /* LAN9210/LAN9211/LAN9220/LAN9221 */ + pdata->generation = 4; + break; + + default: + SMSC_WARN(pdata, probe, "LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + } + + SMSC_TRACE(pdata, probe, + "LAN911x identified, idrev: 0x%08X, generation: %d", + pdata->idrev, pdata->generation); + + if (pdata->generation == 0) + SMSC_WARN(pdata, probe, + "This driver is not intended for this chip revision"); + + /* workaround for platforms without an eeprom, where the mac address + * is stored elsewhere and set by the bootloader. This saves the + * mac address before resetting the device */ + if (pdata->config.flags & SMSC911X_SAVE_MAC_ADDRESS) { + spin_lock_irq(&pdata->mac_lock); + smsc911x_read_mac_address(dev); + spin_unlock_irq(&pdata->mac_lock); + } + + /* Reset the LAN911x */ + if (smsc911x_soft_reset(pdata)) + return -ENODEV; + + /* Disable all interrupt sources until we bring the device up */ + smsc911x_reg_write(pdata, INT_EN, 0); + + ether_setup(dev); + dev->flags |= IFF_MULTICAST; + netif_napi_add(dev, &pdata->napi, smsc911x_poll, SMSC_NAPI_WEIGHT); + dev->netdev_ops = &smsc911x_netdev_ops; + dev->ethtool_ops = &smsc911x_ethtool_ops; + + return 0; +} + +static int __devexit smsc911x_drv_remove(struct platform_device *pdev) +{ + struct net_device *dev; + struct smsc911x_data *pdata; + struct resource *res; + + dev = platform_get_drvdata(pdev); + BUG_ON(!dev); + pdata = netdev_priv(dev); + BUG_ON(!pdata); + BUG_ON(!pdata->ioaddr); + BUG_ON(!pdata->phy_dev); + + SMSC_TRACE(pdata, ifdown, "Stopping driver"); + + phy_disconnect(pdata->phy_dev); + pdata->phy_dev = NULL; + mdiobus_unregister(pdata->mii_bus); + mdiobus_free(pdata->mii_bus); + + platform_set_drvdata(pdev, NULL); + unregister_netdev(dev); + free_irq(dev->irq, dev); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "smsc911x-memory"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + release_mem_region(res->start, resource_size(res)); + + iounmap(pdata->ioaddr); + + free_netdev(dev); + + return 0; +} + +/* standard register acces */ +static const struct smsc911x_ops standard_smsc911x_ops = { + .reg_read = __smsc911x_reg_read, + .reg_write = __smsc911x_reg_write, + .rx_readfifo = smsc911x_rx_readfifo, + .tx_writefifo = smsc911x_tx_writefifo, +}; + +/* shifted register access */ +static const struct smsc911x_ops shifted_smsc911x_ops = { + .reg_read = __smsc911x_reg_read_shift, + .reg_write = __smsc911x_reg_write_shift, + .rx_readfifo = smsc911x_rx_readfifo_shift, + .tx_writefifo = smsc911x_tx_writefifo_shift, +}; + +#ifdef CONFIG_OF +static int __devinit smsc911x_probe_config_dt( + struct smsc911x_platform_config *config, + struct device_node *np) +{ + const char *mac; + u32 width = 0; + + if (!np) + return -ENODEV; + + config->phy_interface = of_get_phy_mode(np); + + mac = of_get_mac_address(np); + if (mac) + memcpy(config->mac, mac, ETH_ALEN); + + of_property_read_u32(np, "reg-shift", &config->shift); + + of_property_read_u32(np, "reg-io-width", &width); + if (width == 4) + config->flags |= SMSC911X_USE_32BIT; + + if (of_get_property(np, "smsc,irq-active-high", NULL)) + config->irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_HIGH; + + if (of_get_property(np, "smsc,irq-push-pull", NULL)) + config->irq_type = SMSC911X_IRQ_TYPE_PUSH_PULL; + + if (of_get_property(np, "smsc,force-internal-phy", NULL)) + config->flags |= SMSC911X_FORCE_INTERNAL_PHY; + + if (of_get_property(np, "smsc,force-external-phy", NULL)) + config->flags |= SMSC911X_FORCE_EXTERNAL_PHY; + + if (of_get_property(np, "smsc,save-mac-address", NULL)) + config->flags |= SMSC911X_SAVE_MAC_ADDRESS; + + return 0; +} +#else +static inline int smsc911x_probe_config_dt( + struct smsc911x_platform_config *config, + struct device_node *np) +{ + return -ENODEV; +} +#endif /* CONFIG_OF */ + +static int __devinit smsc911x_drv_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct net_device *dev; + struct smsc911x_data *pdata; + struct smsc911x_platform_config *config = pdev->dev.platform_data; + struct resource *res, *irq_res; + unsigned int intcfg = 0; + int res_size, irq_flags; + int retval; + + pr_info("Driver version %s\n", SMSC_DRV_VERSION); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "smsc911x-memory"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + pr_warn("Could not allocate resource\n"); + retval = -ENODEV; + goto out_0; + } + res_size = resource_size(res); + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq_res) { + pr_warn("Could not allocate irq resource\n"); + retval = -ENODEV; + goto out_0; + } + + if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) { + retval = -EBUSY; + goto out_0; + } + + dev = alloc_etherdev(sizeof(struct smsc911x_data)); + if (!dev) { + pr_warn("Could not allocate device\n"); + retval = -ENOMEM; + goto out_release_io_1; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + + pdata = netdev_priv(dev); + + dev->irq = irq_res->start; + irq_flags = irq_res->flags & IRQF_TRIGGER_MASK; + pdata->ioaddr = ioremap_nocache(res->start, res_size); + + pdata->dev = dev; + pdata->msg_enable = ((1 << debug) - 1); + + if (pdata->ioaddr == NULL) { + SMSC_WARN(pdata, probe, "Error smsc911x base address invalid"); + retval = -ENOMEM; + goto out_free_netdev_2; + } + + retval = smsc911x_probe_config_dt(&pdata->config, np); + if (retval && config) { + /* copy config parameters across to pdata */ + memcpy(&pdata->config, config, sizeof(pdata->config)); + retval = 0; + } + + if (retval) { + SMSC_WARN(pdata, probe, "Error smsc911x config not found"); + goto out_unmap_io_3; + } + + /* assume standard, non-shifted, access to HW registers */ + pdata->ops = &standard_smsc911x_ops; + /* apply the right access if shifting is needed */ + if (pdata->config.shift) + pdata->ops = &shifted_smsc911x_ops; + + retval = smsc911x_init(dev); + if (retval < 0) + goto out_unmap_io_3; + + /* configure irq polarity and type before connecting isr */ + if (pdata->config.irq_polarity == SMSC911X_IRQ_POLARITY_ACTIVE_HIGH) + intcfg |= INT_CFG_IRQ_POL_; + + if (pdata->config.irq_type == SMSC911X_IRQ_TYPE_PUSH_PULL) + intcfg |= INT_CFG_IRQ_TYPE_; + + smsc911x_reg_write(pdata, INT_CFG, intcfg); + + /* Ensure interrupts are globally disabled before connecting ISR */ + smsc911x_reg_write(pdata, INT_EN, 0); + smsc911x_reg_write(pdata, INT_STS, 0xFFFFFFFF); + + retval = request_irq(dev->irq, smsc911x_irqhandler, + irq_flags | IRQF_SHARED, dev->name, dev); + if (retval) { + SMSC_WARN(pdata, probe, + "Unable to claim requested irq: %d", dev->irq); + goto out_unmap_io_3; + } + + platform_set_drvdata(pdev, dev); + + retval = register_netdev(dev); + if (retval) { + SMSC_WARN(pdata, probe, "Error %i registering device", retval); + goto out_unset_drvdata_4; + } else { + SMSC_TRACE(pdata, probe, + "Network interface: \"%s\"", dev->name); + } + + retval = smsc911x_mii_init(pdev, dev); + if (retval) { + SMSC_WARN(pdata, probe, "Error %i initialising mii", retval); + goto out_unregister_netdev_5; + } + + spin_lock_irq(&pdata->mac_lock); + + /* Check if mac address has been specified when bringing interface up */ + if (is_valid_ether_addr(dev->dev_addr)) { + smsc911x_set_hw_mac_address(pdata, dev->dev_addr); + SMSC_TRACE(pdata, probe, + "MAC Address is specified by configuration"); + } else if (is_valid_ether_addr(pdata->config.mac)) { + memcpy(dev->dev_addr, pdata->config.mac, 6); + SMSC_TRACE(pdata, probe, + "MAC Address specified by platform data"); + } else { + /* Try reading mac address from device. if EEPROM is present + * it will already have been set */ + smsc_get_mac(dev); + + if (is_valid_ether_addr(dev->dev_addr)) { + /* eeprom values are valid so use them */ + SMSC_TRACE(pdata, probe, + "Mac Address is read from LAN911x EEPROM"); + } else { + /* eeprom values are invalid, generate random MAC */ + random_ether_addr(dev->dev_addr); + smsc911x_set_hw_mac_address(pdata, dev->dev_addr); + SMSC_TRACE(pdata, probe, + "MAC Address is set to random_ether_addr"); + } + } + + spin_unlock_irq(&pdata->mac_lock); + + netdev_info(dev, "MAC Address: %pM\n", dev->dev_addr); + + return 0; + +out_unregister_netdev_5: + unregister_netdev(dev); +out_unset_drvdata_4: + platform_set_drvdata(pdev, NULL); + free_irq(dev->irq, dev); +out_unmap_io_3: + iounmap(pdata->ioaddr); +out_free_netdev_2: + free_netdev(dev); +out_release_io_1: + release_mem_region(res->start, resource_size(res)); +out_0: + return retval; +} + +#ifdef CONFIG_PM +/* This implementation assumes the devices remains powered on its VDDVARIO + * pins during suspend. */ + +/* TODO: implement freeze/thaw callbacks for hibernation.*/ + +static int smsc911x_suspend(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct smsc911x_data *pdata = netdev_priv(ndev); + + /* enable wake on LAN, energy detection and the external PME + * signal. */ + smsc911x_reg_write(pdata, PMT_CTRL, + PMT_CTRL_PM_MODE_D1_ | PMT_CTRL_WOL_EN_ | + PMT_CTRL_ED_EN_ | PMT_CTRL_PME_EN_); + + return 0; +} + +static int smsc911x_resume(struct device *dev) +{ + struct net_device *ndev = dev_get_drvdata(dev); + struct smsc911x_data *pdata = netdev_priv(ndev); + unsigned int to = 100; + + /* Note 3.11 from the datasheet: + * "When the LAN9220 is in a power saving state, a write of any + * data to the BYTE_TEST register will wake-up the device." + */ + smsc911x_reg_write(pdata, BYTE_TEST, 0); + + /* poll the READY bit in PMT_CTRL. Any other access to the device is + * forbidden while this bit isn't set. Try for 100ms and return -EIO + * if it failed. */ + while (!(smsc911x_reg_read(pdata, PMT_CTRL) & PMT_CTRL_READY_) && --to) + udelay(1000); + + return (to == 0) ? -EIO : 0; +} + +static const struct dev_pm_ops smsc911x_pm_ops = { + .suspend = smsc911x_suspend, + .resume = smsc911x_resume, +}; + +#define SMSC911X_PM_OPS (&smsc911x_pm_ops) + +#else +#define SMSC911X_PM_OPS NULL +#endif + +static const struct of_device_id smsc911x_dt_ids[] = { + { .compatible = "smsc,lan9115", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, smsc911x_dt_ids); + +static struct platform_driver smsc911x_driver = { + .probe = smsc911x_drv_probe, + .remove = __devexit_p(smsc911x_drv_remove), + .driver = { + .name = SMSC_CHIPNAME, + .owner = THIS_MODULE, + .pm = SMSC911X_PM_OPS, + .of_match_table = smsc911x_dt_ids, + }, +}; + +/* Entry point for loading the module */ +static int __init smsc911x_init_module(void) +{ + SMSC_INITIALIZE(); + return platform_driver_register(&smsc911x_driver); +} + +/* entry point for unloading the module */ +static void __exit smsc911x_cleanup_module(void) +{ + platform_driver_unregister(&smsc911x_driver); +} + +module_init(smsc911x_init_module); +module_exit(smsc911x_cleanup_module); diff --git a/drivers/net/ethernet/smsc/smsc911x.h b/drivers/net/ethernet/smsc/smsc911x.h new file mode 100644 index 000000000000..8d67aacf8867 --- /dev/null +++ b/drivers/net/ethernet/smsc/smsc911x.h @@ -0,0 +1,404 @@ +/*************************************************************************** + * + * Copyright (C) 2004-2008 SMSC + * Copyright (C) 2005-2008 ARM + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ***************************************************************************/ +#ifndef __SMSC911X_H__ +#define __SMSC911X_H__ + +#define TX_FIFO_LOW_THRESHOLD ((u32)1600) +#define SMSC911X_EEPROM_SIZE ((u32)128) +#define USE_DEBUG 0 + +/* This is the maximum number of packets to be received every + * NAPI poll */ +#define SMSC_NAPI_WEIGHT 16 + +/* implements a PHY loopback test at initialisation time, to ensure a packet + * can be successfully looped back */ +#define USE_PHY_WORK_AROUND + +#if USE_DEBUG >= 1 +#define SMSC_WARN(pdata, nlevel, fmt, args...) \ + netif_warn(pdata, nlevel, (pdata)->dev, \ + "%s: " fmt "\n", __func__, ##args) +#else +#define SMSC_WARN(pdata, nlevel, fmt, args...) \ + no_printk(fmt "\n", ##args) +#endif + +#if USE_DEBUG >= 2 +#define SMSC_TRACE(pdata, nlevel, fmt, args...) \ + netif_info(pdata, nlevel, pdata->dev, fmt "\n", ##args) +#else +#define SMSC_TRACE(pdata, nlevel, fmt, args...) \ + no_printk(fmt "\n", ##args) +#endif + +#ifdef CONFIG_DEBUG_SPINLOCK +#define SMSC_ASSERT_MAC_LOCK(pdata) \ + WARN_ON(!spin_is_locked(&pdata->mac_lock)) +#else +#define SMSC_ASSERT_MAC_LOCK(pdata) do {} while (0) +#endif /* CONFIG_DEBUG_SPINLOCK */ + +/* SMSC911x registers and bitfields */ +#define RX_DATA_FIFO 0x00 + +#define TX_DATA_FIFO 0x20 +#define TX_CMD_A_ON_COMP_ 0x80000000 +#define TX_CMD_A_BUF_END_ALGN_ 0x03000000 +#define TX_CMD_A_4_BYTE_ALGN_ 0x00000000 +#define TX_CMD_A_16_BYTE_ALGN_ 0x01000000 +#define TX_CMD_A_32_BYTE_ALGN_ 0x02000000 +#define TX_CMD_A_DATA_OFFSET_ 0x001F0000 +#define TX_CMD_A_FIRST_SEG_ 0x00002000 +#define TX_CMD_A_LAST_SEG_ 0x00001000 +#define TX_CMD_A_BUF_SIZE_ 0x000007FF +#define TX_CMD_B_PKT_TAG_ 0xFFFF0000 +#define TX_CMD_B_ADD_CRC_DISABLE_ 0x00002000 +#define TX_CMD_B_DISABLE_PADDING_ 0x00001000 +#define TX_CMD_B_PKT_BYTE_LENGTH_ 0x000007FF + +#define RX_STATUS_FIFO 0x40 +#define RX_STS_ES_ 0x00008000 +#define RX_STS_LENGTH_ERR_ 0x00001000 +#define RX_STS_MCAST_ 0x00000400 +#define RX_STS_FRAME_TYPE_ 0x00000020 +#define RX_STS_CRC_ERR_ 0x00000002 + +#define RX_STATUS_FIFO_PEEK 0x44 + +#define TX_STATUS_FIFO 0x48 +#define TX_STS_ES_ 0x00008000 +#define TX_STS_LOST_CARRIER_ 0x00000800 +#define TX_STS_NO_CARRIER_ 0x00000400 +#define TX_STS_LATE_COL_ 0x00000200 +#define TX_STS_EXCESS_COL_ 0x00000100 + +#define TX_STATUS_FIFO_PEEK 0x4C + +#define ID_REV 0x50 +#define ID_REV_CHIP_ID_ 0xFFFF0000 +#define ID_REV_REV_ID_ 0x0000FFFF + +#define INT_CFG 0x54 +#define INT_CFG_INT_DEAS_ 0xFF000000 +#define INT_CFG_INT_DEAS_CLR_ 0x00004000 +#define INT_CFG_INT_DEAS_STS_ 0x00002000 +#define INT_CFG_IRQ_INT_ 0x00001000 +#define INT_CFG_IRQ_EN_ 0x00000100 +#define INT_CFG_IRQ_POL_ 0x00000010 +#define INT_CFG_IRQ_TYPE_ 0x00000001 + +#define INT_STS 0x58 +#define INT_STS_SW_INT_ 0x80000000 +#define INT_STS_TXSTOP_INT_ 0x02000000 +#define INT_STS_RXSTOP_INT_ 0x01000000 +#define INT_STS_RXDFH_INT_ 0x00800000 +#define INT_STS_RXDF_INT_ 0x00400000 +#define INT_STS_TX_IOC_ 0x00200000 +#define INT_STS_RXD_INT_ 0x00100000 +#define INT_STS_GPT_INT_ 0x00080000 +#define INT_STS_PHY_INT_ 0x00040000 +#define INT_STS_PME_INT_ 0x00020000 +#define INT_STS_TXSO_ 0x00010000 +#define INT_STS_RWT_ 0x00008000 +#define INT_STS_RXE_ 0x00004000 +#define INT_STS_TXE_ 0x00002000 +#define INT_STS_TDFU_ 0x00000800 +#define INT_STS_TDFO_ 0x00000400 +#define INT_STS_TDFA_ 0x00000200 +#define INT_STS_TSFF_ 0x00000100 +#define INT_STS_TSFL_ 0x00000080 +#define INT_STS_RXDF_ 0x00000040 +#define INT_STS_RDFL_ 0x00000020 +#define INT_STS_RSFF_ 0x00000010 +#define INT_STS_RSFL_ 0x00000008 +#define INT_STS_GPIO2_INT_ 0x00000004 +#define INT_STS_GPIO1_INT_ 0x00000002 +#define INT_STS_GPIO0_INT_ 0x00000001 + +#define INT_EN 0x5C +#define INT_EN_SW_INT_EN_ 0x80000000 +#define INT_EN_TXSTOP_INT_EN_ 0x02000000 +#define INT_EN_RXSTOP_INT_EN_ 0x01000000 +#define INT_EN_RXDFH_INT_EN_ 0x00800000 +#define INT_EN_TIOC_INT_EN_ 0x00200000 +#define INT_EN_RXD_INT_EN_ 0x00100000 +#define INT_EN_GPT_INT_EN_ 0x00080000 +#define INT_EN_PHY_INT_EN_ 0x00040000 +#define INT_EN_PME_INT_EN_ 0x00020000 +#define INT_EN_TXSO_EN_ 0x00010000 +#define INT_EN_RWT_EN_ 0x00008000 +#define INT_EN_RXE_EN_ 0x00004000 +#define INT_EN_TXE_EN_ 0x00002000 +#define INT_EN_TDFU_EN_ 0x00000800 +#define INT_EN_TDFO_EN_ 0x00000400 +#define INT_EN_TDFA_EN_ 0x00000200 +#define INT_EN_TSFF_EN_ 0x00000100 +#define INT_EN_TSFL_EN_ 0x00000080 +#define INT_EN_RXDF_EN_ 0x00000040 +#define INT_EN_RDFL_EN_ 0x00000020 +#define INT_EN_RSFF_EN_ 0x00000010 +#define INT_EN_RSFL_EN_ 0x00000008 +#define INT_EN_GPIO2_INT_ 0x00000004 +#define INT_EN_GPIO1_INT_ 0x00000002 +#define INT_EN_GPIO0_INT_ 0x00000001 + +#define BYTE_TEST 0x64 + +#define FIFO_INT 0x68 +#define FIFO_INT_TX_AVAIL_LEVEL_ 0xFF000000 +#define FIFO_INT_TX_STS_LEVEL_ 0x00FF0000 +#define FIFO_INT_RX_AVAIL_LEVEL_ 0x0000FF00 +#define FIFO_INT_RX_STS_LEVEL_ 0x000000FF + +#define RX_CFG 0x6C +#define RX_CFG_RX_END_ALGN_ 0xC0000000 +#define RX_CFG_RX_END_ALGN4_ 0x00000000 +#define RX_CFG_RX_END_ALGN16_ 0x40000000 +#define RX_CFG_RX_END_ALGN32_ 0x80000000 +#define RX_CFG_RX_DMA_CNT_ 0x0FFF0000 +#define RX_CFG_RX_DUMP_ 0x00008000 +#define RX_CFG_RXDOFF_ 0x00001F00 + +#define TX_CFG 0x70 +#define TX_CFG_TXS_DUMP_ 0x00008000 +#define TX_CFG_TXD_DUMP_ 0x00004000 +#define TX_CFG_TXSAO_ 0x00000004 +#define TX_CFG_TX_ON_ 0x00000002 +#define TX_CFG_STOP_TX_ 0x00000001 + +#define HW_CFG 0x74 +#define HW_CFG_TTM_ 0x00200000 +#define HW_CFG_SF_ 0x00100000 +#define HW_CFG_TX_FIF_SZ_ 0x000F0000 +#define HW_CFG_TR_ 0x00003000 +#define HW_CFG_SRST_ 0x00000001 + +/* only available on 115/117 */ +#define HW_CFG_PHY_CLK_SEL_ 0x00000060 +#define HW_CFG_PHY_CLK_SEL_INT_PHY_ 0x00000000 +#define HW_CFG_PHY_CLK_SEL_EXT_PHY_ 0x00000020 +#define HW_CFG_PHY_CLK_SEL_CLK_DIS_ 0x00000040 +#define HW_CFG_SMI_SEL_ 0x00000010 +#define HW_CFG_EXT_PHY_DET_ 0x00000008 +#define HW_CFG_EXT_PHY_EN_ 0x00000004 +#define HW_CFG_SRST_TO_ 0x00000002 + +/* only available on 116/118 */ +#define HW_CFG_32_16_BIT_MODE_ 0x00000004 + +#define RX_DP_CTRL 0x78 +#define RX_DP_CTRL_RX_FFWD_ 0x80000000 + +#define RX_FIFO_INF 0x7C +#define RX_FIFO_INF_RXSUSED_ 0x00FF0000 +#define RX_FIFO_INF_RXDUSED_ 0x0000FFFF + +#define TX_FIFO_INF 0x80 +#define TX_FIFO_INF_TSUSED_ 0x00FF0000 +#define TX_FIFO_INF_TDFREE_ 0x0000FFFF + +#define PMT_CTRL 0x84 +#define PMT_CTRL_PM_MODE_ 0x00003000 +#define PMT_CTRL_PM_MODE_D0_ 0x00000000 +#define PMT_CTRL_PM_MODE_D1_ 0x00001000 +#define PMT_CTRL_PM_MODE_D2_ 0x00002000 +#define PMT_CTRL_PM_MODE_D3_ 0x00003000 +#define PMT_CTRL_PHY_RST_ 0x00000400 +#define PMT_CTRL_WOL_EN_ 0x00000200 +#define PMT_CTRL_ED_EN_ 0x00000100 +#define PMT_CTRL_PME_TYPE_ 0x00000040 +#define PMT_CTRL_WUPS_ 0x00000030 +#define PMT_CTRL_WUPS_NOWAKE_ 0x00000000 +#define PMT_CTRL_WUPS_ED_ 0x00000010 +#define PMT_CTRL_WUPS_WOL_ 0x00000020 +#define PMT_CTRL_WUPS_MULTI_ 0x00000030 +#define PMT_CTRL_PME_IND_ 0x00000008 +#define PMT_CTRL_PME_POL_ 0x00000004 +#define PMT_CTRL_PME_EN_ 0x00000002 +#define PMT_CTRL_READY_ 0x00000001 + +#define GPIO_CFG 0x88 +#define GPIO_CFG_LED3_EN_ 0x40000000 +#define GPIO_CFG_LED2_EN_ 0x20000000 +#define GPIO_CFG_LED1_EN_ 0x10000000 +#define GPIO_CFG_GPIO2_INT_POL_ 0x04000000 +#define GPIO_CFG_GPIO1_INT_POL_ 0x02000000 +#define GPIO_CFG_GPIO0_INT_POL_ 0x01000000 +#define GPIO_CFG_EEPR_EN_ 0x00700000 +#define GPIO_CFG_GPIOBUF2_ 0x00040000 +#define GPIO_CFG_GPIOBUF1_ 0x00020000 +#define GPIO_CFG_GPIOBUF0_ 0x00010000 +#define GPIO_CFG_GPIODIR2_ 0x00000400 +#define GPIO_CFG_GPIODIR1_ 0x00000200 +#define GPIO_CFG_GPIODIR0_ 0x00000100 +#define GPIO_CFG_GPIOD4_ 0x00000020 +#define GPIO_CFG_GPIOD3_ 0x00000010 +#define GPIO_CFG_GPIOD2_ 0x00000004 +#define GPIO_CFG_GPIOD1_ 0x00000002 +#define GPIO_CFG_GPIOD0_ 0x00000001 + +#define GPT_CFG 0x8C +#define GPT_CFG_TIMER_EN_ 0x20000000 +#define GPT_CFG_GPT_LOAD_ 0x0000FFFF + +#define GPT_CNT 0x90 +#define GPT_CNT_GPT_CNT_ 0x0000FFFF + +#define WORD_SWAP 0x98 + +#define FREE_RUN 0x9C + +#define RX_DROP 0xA0 + +#define MAC_CSR_CMD 0xA4 +#define MAC_CSR_CMD_CSR_BUSY_ 0x80000000 +#define MAC_CSR_CMD_R_NOT_W_ 0x40000000 +#define MAC_CSR_CMD_CSR_ADDR_ 0x000000FF + +#define MAC_CSR_DATA 0xA8 + +#define AFC_CFG 0xAC +#define AFC_CFG_AFC_HI_ 0x00FF0000 +#define AFC_CFG_AFC_LO_ 0x0000FF00 +#define AFC_CFG_BACK_DUR_ 0x000000F0 +#define AFC_CFG_FCMULT_ 0x00000008 +#define AFC_CFG_FCBRD_ 0x00000004 +#define AFC_CFG_FCADD_ 0x00000002 +#define AFC_CFG_FCANY_ 0x00000001 + +#define E2P_CMD 0xB0 +#define E2P_CMD_EPC_BUSY_ 0x80000000 +#define E2P_CMD_EPC_CMD_ 0x70000000 +#define E2P_CMD_EPC_CMD_READ_ 0x00000000 +#define E2P_CMD_EPC_CMD_EWDS_ 0x10000000 +#define E2P_CMD_EPC_CMD_EWEN_ 0x20000000 +#define E2P_CMD_EPC_CMD_WRITE_ 0x30000000 +#define E2P_CMD_EPC_CMD_WRAL_ 0x40000000 +#define E2P_CMD_EPC_CMD_ERASE_ 0x50000000 +#define E2P_CMD_EPC_CMD_ERAL_ 0x60000000 +#define E2P_CMD_EPC_CMD_RELOAD_ 0x70000000 +#define E2P_CMD_EPC_TIMEOUT_ 0x00000200 +#define E2P_CMD_MAC_ADDR_LOADED_ 0x00000100 +#define E2P_CMD_EPC_ADDR_ 0x000000FF + +#define E2P_DATA 0xB4 +#define E2P_DATA_EEPROM_DATA_ 0x000000FF +#define LAN_REGISTER_EXTENT 0x00000100 + +/* + * MAC Control and Status Register (Indirect Address) + * Offset (through the MAC_CSR CMD and DATA port) + */ +#define MAC_CR 0x01 +#define MAC_CR_RXALL_ 0x80000000 +#define MAC_CR_HBDIS_ 0x10000000 +#define MAC_CR_RCVOWN_ 0x00800000 +#define MAC_CR_LOOPBK_ 0x00200000 +#define MAC_CR_FDPX_ 0x00100000 +#define MAC_CR_MCPAS_ 0x00080000 +#define MAC_CR_PRMS_ 0x00040000 +#define MAC_CR_INVFILT_ 0x00020000 +#define MAC_CR_PASSBAD_ 0x00010000 +#define MAC_CR_HFILT_ 0x00008000 +#define MAC_CR_HPFILT_ 0x00002000 +#define MAC_CR_LCOLL_ 0x00001000 +#define MAC_CR_BCAST_ 0x00000800 +#define MAC_CR_DISRTY_ 0x00000400 +#define MAC_CR_PADSTR_ 0x00000100 +#define MAC_CR_BOLMT_MASK_ 0x000000C0 +#define MAC_CR_DFCHK_ 0x00000020 +#define MAC_CR_TXEN_ 0x00000008 +#define MAC_CR_RXEN_ 0x00000004 + +#define ADDRH 0x02 + +#define ADDRL 0x03 + +#define HASHH 0x04 + +#define HASHL 0x05 + +#define MII_ACC 0x06 +#define MII_ACC_PHY_ADDR_ 0x0000F800 +#define MII_ACC_MIIRINDA_ 0x000007C0 +#define MII_ACC_MII_WRITE_ 0x00000002 +#define MII_ACC_MII_BUSY_ 0x00000001 + +#define MII_DATA 0x07 + +#define FLOW 0x08 +#define FLOW_FCPT_ 0xFFFF0000 +#define FLOW_FCPASS_ 0x00000004 +#define FLOW_FCEN_ 0x00000002 +#define FLOW_FCBSY_ 0x00000001 + +#define VLAN1 0x09 + +#define VLAN2 0x0A + +#define WUFF 0x0B + +#define WUCSR 0x0C +#define WUCSR_GUE_ 0x00000200 +#define WUCSR_WUFR_ 0x00000040 +#define WUCSR_MPR_ 0x00000020 +#define WUCSR_WAKE_EN_ 0x00000004 +#define WUCSR_MPEN_ 0x00000002 + +/* + * Phy definitions (vendor-specific) + */ +#define LAN9118_PHY_ID 0x00C0001C + +#define MII_INTSTS 0x1D + +#define MII_INTMSK 0x1E +#define PHY_INTMSK_AN_RCV_ (1 << 1) +#define PHY_INTMSK_PDFAULT_ (1 << 2) +#define PHY_INTMSK_AN_ACK_ (1 << 3) +#define PHY_INTMSK_LNKDOWN_ (1 << 4) +#define PHY_INTMSK_RFAULT_ (1 << 5) +#define PHY_INTMSK_AN_COMP_ (1 << 6) +#define PHY_INTMSK_ENERGYON_ (1 << 7) +#define PHY_INTMSK_DEFAULT_ (PHY_INTMSK_ENERGYON_ | \ + PHY_INTMSK_AN_COMP_ | \ + PHY_INTMSK_RFAULT_ | \ + PHY_INTMSK_LNKDOWN_) + +#define ADVERTISE_PAUSE_ALL (ADVERTISE_PAUSE_CAP | \ + ADVERTISE_PAUSE_ASYM) + +#define LPA_PAUSE_ALL (LPA_PAUSE_CAP | \ + LPA_PAUSE_ASYM) + +/* + * Provide hooks to let the arch add to the initialisation procedure + * and to override the source of the MAC address. + */ +#define SMSC_INITIALIZE() do {} while (0) +#define smsc_get_mac(dev) smsc911x_read_mac_address((dev)) + +#ifdef CONFIG_SMSC911X_ARCH_HOOKS +#include <asm/smsc911x.h> +#endif + +#endif /* __SMSC911X_H__ */ diff --git a/drivers/net/ethernet/smsc/smsc9420.c b/drivers/net/ethernet/smsc/smsc9420.c new file mode 100644 index 000000000000..459726f54754 --- /dev/null +++ b/drivers/net/ethernet/smsc/smsc9420.c @@ -0,0 +1,1763 @@ + /*************************************************************************** + * + * Copyright (C) 2007,2008 SMSC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + *************************************************************************** + */ + +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/phy.h> +#include <linux/pci.h> +#include <linux/if_vlan.h> +#include <linux/dma-mapping.h> +#include <linux/crc32.h> +#include <linux/slab.h> +#include <asm/unaligned.h> +#include "smsc9420.h" + +#define DRV_NAME "smsc9420" +#define PFX DRV_NAME ": " +#define DRV_MDIONAME "smsc9420-mdio" +#define DRV_DESCRIPTION "SMSC LAN9420 driver" +#define DRV_VERSION "1.01" + +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); + +struct smsc9420_dma_desc { + u32 status; + u32 length; + u32 buffer1; + u32 buffer2; +}; + +struct smsc9420_ring_info { + struct sk_buff *skb; + dma_addr_t mapping; +}; + +struct smsc9420_pdata { + void __iomem *base_addr; + struct pci_dev *pdev; + struct net_device *dev; + + struct smsc9420_dma_desc *rx_ring; + struct smsc9420_dma_desc *tx_ring; + struct smsc9420_ring_info *tx_buffers; + struct smsc9420_ring_info *rx_buffers; + dma_addr_t rx_dma_addr; + dma_addr_t tx_dma_addr; + int tx_ring_head, tx_ring_tail; + int rx_ring_head, rx_ring_tail; + + spinlock_t int_lock; + spinlock_t phy_lock; + + struct napi_struct napi; + + bool software_irq_signal; + bool rx_csum; + u32 msg_enable; + + struct phy_device *phy_dev; + struct mii_bus *mii_bus; + int phy_irq[PHY_MAX_ADDR]; + int last_duplex; + int last_carrier; +}; + +static DEFINE_PCI_DEVICE_TABLE(smsc9420_id_table) = { + { PCI_VENDOR_ID_9420, PCI_DEVICE_ID_9420, PCI_ANY_ID, PCI_ANY_ID, }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, smsc9420_id_table); + +#define SMSC_MSG_DEFAULT (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK) + +static uint smsc_debug; +static uint debug = -1; +module_param(debug, uint, 0); +MODULE_PARM_DESC(debug, "debug level"); + +#define smsc_dbg(TYPE, f, a...) \ +do { if ((pd)->msg_enable & NETIF_MSG_##TYPE) \ + printk(KERN_DEBUG PFX f "\n", ## a); \ +} while (0) + +#define smsc_info(TYPE, f, a...) \ +do { if ((pd)->msg_enable & NETIF_MSG_##TYPE) \ + printk(KERN_INFO PFX f "\n", ## a); \ +} while (0) + +#define smsc_warn(TYPE, f, a...) \ +do { if ((pd)->msg_enable & NETIF_MSG_##TYPE) \ + printk(KERN_WARNING PFX f "\n", ## a); \ +} while (0) + +static inline u32 smsc9420_reg_read(struct smsc9420_pdata *pd, u32 offset) +{ + return ioread32(pd->base_addr + offset); +} + +static inline void +smsc9420_reg_write(struct smsc9420_pdata *pd, u32 offset, u32 value) +{ + iowrite32(value, pd->base_addr + offset); +} + +static inline void smsc9420_pci_flush_write(struct smsc9420_pdata *pd) +{ + /* to ensure PCI write completion, we must perform a PCI read */ + smsc9420_reg_read(pd, ID_REV); +} + +static int smsc9420_mii_read(struct mii_bus *bus, int phyaddr, int regidx) +{ + struct smsc9420_pdata *pd = (struct smsc9420_pdata *)bus->priv; + unsigned long flags; + u32 addr; + int i, reg = -EIO; + + spin_lock_irqsave(&pd->phy_lock, flags); + + /* confirm MII not busy */ + if ((smsc9420_reg_read(pd, MII_ACCESS) & MII_ACCESS_MII_BUSY_)) { + smsc_warn(DRV, "MII is busy???"); + goto out; + } + + /* set the address, index & direction (read from PHY) */ + addr = ((phyaddr & 0x1F) << 11) | ((regidx & 0x1F) << 6) | + MII_ACCESS_MII_READ_; + smsc9420_reg_write(pd, MII_ACCESS, addr); + + /* wait for read to complete with 50us timeout */ + for (i = 0; i < 5; i++) { + if (!(smsc9420_reg_read(pd, MII_ACCESS) & + MII_ACCESS_MII_BUSY_)) { + reg = (u16)smsc9420_reg_read(pd, MII_DATA); + goto out; + } + udelay(10); + } + + smsc_warn(DRV, "MII busy timeout!"); + +out: + spin_unlock_irqrestore(&pd->phy_lock, flags); + return reg; +} + +static int smsc9420_mii_write(struct mii_bus *bus, int phyaddr, int regidx, + u16 val) +{ + struct smsc9420_pdata *pd = (struct smsc9420_pdata *)bus->priv; + unsigned long flags; + u32 addr; + int i, reg = -EIO; + + spin_lock_irqsave(&pd->phy_lock, flags); + + /* confirm MII not busy */ + if ((smsc9420_reg_read(pd, MII_ACCESS) & MII_ACCESS_MII_BUSY_)) { + smsc_warn(DRV, "MII is busy???"); + goto out; + } + + /* put the data to write in the MAC */ + smsc9420_reg_write(pd, MII_DATA, (u32)val); + + /* set the address, index & direction (write to PHY) */ + addr = ((phyaddr & 0x1F) << 11) | ((regidx & 0x1F) << 6) | + MII_ACCESS_MII_WRITE_; + smsc9420_reg_write(pd, MII_ACCESS, addr); + + /* wait for write to complete with 50us timeout */ + for (i = 0; i < 5; i++) { + if (!(smsc9420_reg_read(pd, MII_ACCESS) & + MII_ACCESS_MII_BUSY_)) { + reg = 0; + goto out; + } + udelay(10); + } + + smsc_warn(DRV, "MII busy timeout!"); + +out: + spin_unlock_irqrestore(&pd->phy_lock, flags); + return reg; +} + +/* Returns hash bit number for given MAC address + * Example: + * 01 00 5E 00 00 01 -> returns bit number 31 */ +static u32 smsc9420_hash(u8 addr[ETH_ALEN]) +{ + return (ether_crc(ETH_ALEN, addr) >> 26) & 0x3f; +} + +static int smsc9420_eeprom_reload(struct smsc9420_pdata *pd) +{ + int timeout = 100000; + + BUG_ON(!pd); + + if (smsc9420_reg_read(pd, E2P_CMD) & E2P_CMD_EPC_BUSY_) { + smsc_dbg(DRV, "smsc9420_eeprom_reload: Eeprom busy"); + return -EIO; + } + + smsc9420_reg_write(pd, E2P_CMD, + (E2P_CMD_EPC_BUSY_ | E2P_CMD_EPC_CMD_RELOAD_)); + + do { + udelay(10); + if (!(smsc9420_reg_read(pd, E2P_CMD) & E2P_CMD_EPC_BUSY_)) + return 0; + } while (timeout--); + + smsc_warn(DRV, "smsc9420_eeprom_reload: Eeprom timed out"); + return -EIO; +} + +/* Standard ioctls for mii-tool */ +static int smsc9420_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + + if (!netif_running(dev) || !pd->phy_dev) + return -EINVAL; + + return phy_mii_ioctl(pd->phy_dev, ifr, cmd); +} + +static int smsc9420_ethtool_get_settings(struct net_device *dev, + struct ethtool_cmd *cmd) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + + if (!pd->phy_dev) + return -ENODEV; + + cmd->maxtxpkt = 1; + cmd->maxrxpkt = 1; + return phy_ethtool_gset(pd->phy_dev, cmd); +} + +static int smsc9420_ethtool_set_settings(struct net_device *dev, + struct ethtool_cmd *cmd) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + + if (!pd->phy_dev) + return -ENODEV; + + return phy_ethtool_sset(pd->phy_dev, cmd); +} + +static void smsc9420_ethtool_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *drvinfo) +{ + struct smsc9420_pdata *pd = netdev_priv(netdev); + + strcpy(drvinfo->driver, DRV_NAME); + strcpy(drvinfo->bus_info, pci_name(pd->pdev)); + strcpy(drvinfo->version, DRV_VERSION); +} + +static u32 smsc9420_ethtool_get_msglevel(struct net_device *netdev) +{ + struct smsc9420_pdata *pd = netdev_priv(netdev); + return pd->msg_enable; +} + +static void smsc9420_ethtool_set_msglevel(struct net_device *netdev, u32 data) +{ + struct smsc9420_pdata *pd = netdev_priv(netdev); + pd->msg_enable = data; +} + +static int smsc9420_ethtool_nway_reset(struct net_device *netdev) +{ + struct smsc9420_pdata *pd = netdev_priv(netdev); + + if (!pd->phy_dev) + return -ENODEV; + + return phy_start_aneg(pd->phy_dev); +} + +static int smsc9420_ethtool_getregslen(struct net_device *dev) +{ + /* all smsc9420 registers plus all phy registers */ + return 0x100 + (32 * sizeof(u32)); +} + +static void +smsc9420_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs, + void *buf) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + struct phy_device *phy_dev = pd->phy_dev; + unsigned int i, j = 0; + u32 *data = buf; + + regs->version = smsc9420_reg_read(pd, ID_REV); + for (i = 0; i < 0x100; i += (sizeof(u32))) + data[j++] = smsc9420_reg_read(pd, i); + + // cannot read phy registers if the net device is down + if (!phy_dev) + return; + + for (i = 0; i <= 31; i++) + data[j++] = smsc9420_mii_read(phy_dev->bus, phy_dev->addr, i); +} + +static void smsc9420_eeprom_enable_access(struct smsc9420_pdata *pd) +{ + unsigned int temp = smsc9420_reg_read(pd, GPIO_CFG); + temp &= ~GPIO_CFG_EEPR_EN_; + smsc9420_reg_write(pd, GPIO_CFG, temp); + msleep(1); +} + +static int smsc9420_eeprom_send_cmd(struct smsc9420_pdata *pd, u32 op) +{ + int timeout = 100; + u32 e2cmd; + + smsc_dbg(HW, "op 0x%08x", op); + if (smsc9420_reg_read(pd, E2P_CMD) & E2P_CMD_EPC_BUSY_) { + smsc_warn(HW, "Busy at start"); + return -EBUSY; + } + + e2cmd = op | E2P_CMD_EPC_BUSY_; + smsc9420_reg_write(pd, E2P_CMD, e2cmd); + + do { + msleep(1); + e2cmd = smsc9420_reg_read(pd, E2P_CMD); + } while ((e2cmd & E2P_CMD_EPC_BUSY_) && (--timeout)); + + if (!timeout) { + smsc_info(HW, "TIMED OUT"); + return -EAGAIN; + } + + if (e2cmd & E2P_CMD_EPC_TIMEOUT_) { + smsc_info(HW, "Error occurred during eeprom operation"); + return -EINVAL; + } + + return 0; +} + +static int smsc9420_eeprom_read_location(struct smsc9420_pdata *pd, + u8 address, u8 *data) +{ + u32 op = E2P_CMD_EPC_CMD_READ_ | address; + int ret; + + smsc_dbg(HW, "address 0x%x", address); + ret = smsc9420_eeprom_send_cmd(pd, op); + + if (!ret) + data[address] = smsc9420_reg_read(pd, E2P_DATA); + + return ret; +} + +static int smsc9420_eeprom_write_location(struct smsc9420_pdata *pd, + u8 address, u8 data) +{ + u32 op = E2P_CMD_EPC_CMD_ERASE_ | address; + int ret; + + smsc_dbg(HW, "address 0x%x, data 0x%x", address, data); + ret = smsc9420_eeprom_send_cmd(pd, op); + + if (!ret) { + op = E2P_CMD_EPC_CMD_WRITE_ | address; + smsc9420_reg_write(pd, E2P_DATA, (u32)data); + ret = smsc9420_eeprom_send_cmd(pd, op); + } + + return ret; +} + +static int smsc9420_ethtool_get_eeprom_len(struct net_device *dev) +{ + return SMSC9420_EEPROM_SIZE; +} + +static int smsc9420_ethtool_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + u8 eeprom_data[SMSC9420_EEPROM_SIZE]; + int len, i; + + smsc9420_eeprom_enable_access(pd); + + len = min(eeprom->len, SMSC9420_EEPROM_SIZE); + for (i = 0; i < len; i++) { + int ret = smsc9420_eeprom_read_location(pd, i, eeprom_data); + if (ret < 0) { + eeprom->len = 0; + return ret; + } + } + + memcpy(data, &eeprom_data[eeprom->offset], len); + eeprom->magic = SMSC9420_EEPROM_MAGIC; + eeprom->len = len; + return 0; +} + +static int smsc9420_ethtool_set_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + int ret; + + if (eeprom->magic != SMSC9420_EEPROM_MAGIC) + return -EINVAL; + + smsc9420_eeprom_enable_access(pd); + smsc9420_eeprom_send_cmd(pd, E2P_CMD_EPC_CMD_EWEN_); + ret = smsc9420_eeprom_write_location(pd, eeprom->offset, *data); + smsc9420_eeprom_send_cmd(pd, E2P_CMD_EPC_CMD_EWDS_); + + /* Single byte write, according to man page */ + eeprom->len = 1; + + return ret; +} + +static const struct ethtool_ops smsc9420_ethtool_ops = { + .get_settings = smsc9420_ethtool_get_settings, + .set_settings = smsc9420_ethtool_set_settings, + .get_drvinfo = smsc9420_ethtool_get_drvinfo, + .get_msglevel = smsc9420_ethtool_get_msglevel, + .set_msglevel = smsc9420_ethtool_set_msglevel, + .nway_reset = smsc9420_ethtool_nway_reset, + .get_link = ethtool_op_get_link, + .get_eeprom_len = smsc9420_ethtool_get_eeprom_len, + .get_eeprom = smsc9420_ethtool_get_eeprom, + .set_eeprom = smsc9420_ethtool_set_eeprom, + .get_regs_len = smsc9420_ethtool_getregslen, + .get_regs = smsc9420_ethtool_getregs, +}; + +/* Sets the device MAC address to dev_addr */ +static void smsc9420_set_mac_address(struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + u8 *dev_addr = dev->dev_addr; + u32 mac_high16 = (dev_addr[5] << 8) | dev_addr[4]; + u32 mac_low32 = (dev_addr[3] << 24) | (dev_addr[2] << 16) | + (dev_addr[1] << 8) | dev_addr[0]; + + smsc9420_reg_write(pd, ADDRH, mac_high16); + smsc9420_reg_write(pd, ADDRL, mac_low32); +} + +static void smsc9420_check_mac_address(struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + + /* Check if mac address has been specified when bringing interface up */ + if (is_valid_ether_addr(dev->dev_addr)) { + smsc9420_set_mac_address(dev); + smsc_dbg(PROBE, "MAC Address is specified by configuration"); + } else { + /* Try reading mac address from device. if EEPROM is present + * it will already have been set */ + u32 mac_high16 = smsc9420_reg_read(pd, ADDRH); + u32 mac_low32 = smsc9420_reg_read(pd, ADDRL); + dev->dev_addr[0] = (u8)(mac_low32); + dev->dev_addr[1] = (u8)(mac_low32 >> 8); + dev->dev_addr[2] = (u8)(mac_low32 >> 16); + dev->dev_addr[3] = (u8)(mac_low32 >> 24); + dev->dev_addr[4] = (u8)(mac_high16); + dev->dev_addr[5] = (u8)(mac_high16 >> 8); + + if (is_valid_ether_addr(dev->dev_addr)) { + /* eeprom values are valid so use them */ + smsc_dbg(PROBE, "Mac Address is read from EEPROM"); + } else { + /* eeprom values are invalid, generate random MAC */ + random_ether_addr(dev->dev_addr); + smsc9420_set_mac_address(dev); + smsc_dbg(PROBE, + "MAC Address is set to random_ether_addr"); + } + } +} + +static void smsc9420_stop_tx(struct smsc9420_pdata *pd) +{ + u32 dmac_control, mac_cr, dma_intr_ena; + int timeout = 1000; + + /* disable TX DMAC */ + dmac_control = smsc9420_reg_read(pd, DMAC_CONTROL); + dmac_control &= (~DMAC_CONTROL_ST_); + smsc9420_reg_write(pd, DMAC_CONTROL, dmac_control); + + /* Wait max 10ms for transmit process to stop */ + while (--timeout) { + if (smsc9420_reg_read(pd, DMAC_STATUS) & DMAC_STS_TS_) + break; + udelay(10); + } + + if (!timeout) + smsc_warn(IFDOWN, "TX DMAC failed to stop"); + + /* ACK Tx DMAC stop bit */ + smsc9420_reg_write(pd, DMAC_STATUS, DMAC_STS_TXPS_); + + /* mask TX DMAC interrupts */ + dma_intr_ena = smsc9420_reg_read(pd, DMAC_INTR_ENA); + dma_intr_ena &= ~(DMAC_INTR_ENA_TX_); + smsc9420_reg_write(pd, DMAC_INTR_ENA, dma_intr_ena); + smsc9420_pci_flush_write(pd); + + /* stop MAC TX */ + mac_cr = smsc9420_reg_read(pd, MAC_CR) & (~MAC_CR_TXEN_); + smsc9420_reg_write(pd, MAC_CR, mac_cr); + smsc9420_pci_flush_write(pd); +} + +static void smsc9420_free_tx_ring(struct smsc9420_pdata *pd) +{ + int i; + + BUG_ON(!pd->tx_ring); + + if (!pd->tx_buffers) + return; + + for (i = 0; i < TX_RING_SIZE; i++) { + struct sk_buff *skb = pd->tx_buffers[i].skb; + + if (skb) { + BUG_ON(!pd->tx_buffers[i].mapping); + pci_unmap_single(pd->pdev, pd->tx_buffers[i].mapping, + skb->len, PCI_DMA_TODEVICE); + dev_kfree_skb_any(skb); + } + + pd->tx_ring[i].status = 0; + pd->tx_ring[i].length = 0; + pd->tx_ring[i].buffer1 = 0; + pd->tx_ring[i].buffer2 = 0; + } + wmb(); + + kfree(pd->tx_buffers); + pd->tx_buffers = NULL; + + pd->tx_ring_head = 0; + pd->tx_ring_tail = 0; +} + +static void smsc9420_free_rx_ring(struct smsc9420_pdata *pd) +{ + int i; + + BUG_ON(!pd->rx_ring); + + if (!pd->rx_buffers) + return; + + for (i = 0; i < RX_RING_SIZE; i++) { + if (pd->rx_buffers[i].skb) + dev_kfree_skb_any(pd->rx_buffers[i].skb); + + if (pd->rx_buffers[i].mapping) + pci_unmap_single(pd->pdev, pd->rx_buffers[i].mapping, + PKT_BUF_SZ, PCI_DMA_FROMDEVICE); + + pd->rx_ring[i].status = 0; + pd->rx_ring[i].length = 0; + pd->rx_ring[i].buffer1 = 0; + pd->rx_ring[i].buffer2 = 0; + } + wmb(); + + kfree(pd->rx_buffers); + pd->rx_buffers = NULL; + + pd->rx_ring_head = 0; + pd->rx_ring_tail = 0; +} + +static void smsc9420_stop_rx(struct smsc9420_pdata *pd) +{ + int timeout = 1000; + u32 mac_cr, dmac_control, dma_intr_ena; + + /* mask RX DMAC interrupts */ + dma_intr_ena = smsc9420_reg_read(pd, DMAC_INTR_ENA); + dma_intr_ena &= (~DMAC_INTR_ENA_RX_); + smsc9420_reg_write(pd, DMAC_INTR_ENA, dma_intr_ena); + smsc9420_pci_flush_write(pd); + + /* stop RX MAC prior to stoping DMA */ + mac_cr = smsc9420_reg_read(pd, MAC_CR) & (~MAC_CR_RXEN_); + smsc9420_reg_write(pd, MAC_CR, mac_cr); + smsc9420_pci_flush_write(pd); + + /* stop RX DMAC */ + dmac_control = smsc9420_reg_read(pd, DMAC_CONTROL); + dmac_control &= (~DMAC_CONTROL_SR_); + smsc9420_reg_write(pd, DMAC_CONTROL, dmac_control); + smsc9420_pci_flush_write(pd); + + /* wait up to 10ms for receive to stop */ + while (--timeout) { + if (smsc9420_reg_read(pd, DMAC_STATUS) & DMAC_STS_RS_) + break; + udelay(10); + } + + if (!timeout) + smsc_warn(IFDOWN, "RX DMAC did not stop! timeout."); + + /* ACK the Rx DMAC stop bit */ + smsc9420_reg_write(pd, DMAC_STATUS, DMAC_STS_RXPS_); +} + +static irqreturn_t smsc9420_isr(int irq, void *dev_id) +{ + struct smsc9420_pdata *pd = dev_id; + u32 int_cfg, int_sts, int_ctl; + irqreturn_t ret = IRQ_NONE; + ulong flags; + + BUG_ON(!pd); + BUG_ON(!pd->base_addr); + + int_cfg = smsc9420_reg_read(pd, INT_CFG); + + /* check if it's our interrupt */ + if ((int_cfg & (INT_CFG_IRQ_EN_ | INT_CFG_IRQ_INT_)) != + (INT_CFG_IRQ_EN_ | INT_CFG_IRQ_INT_)) + return IRQ_NONE; + + int_sts = smsc9420_reg_read(pd, INT_STAT); + + if (likely(INT_STAT_DMAC_INT_ & int_sts)) { + u32 status = smsc9420_reg_read(pd, DMAC_STATUS); + u32 ints_to_clear = 0; + + if (status & DMAC_STS_TX_) { + ints_to_clear |= (DMAC_STS_TX_ | DMAC_STS_NIS_); + netif_wake_queue(pd->dev); + } + + if (status & DMAC_STS_RX_) { + /* mask RX DMAC interrupts */ + u32 dma_intr_ena = smsc9420_reg_read(pd, DMAC_INTR_ENA); + dma_intr_ena &= (~DMAC_INTR_ENA_RX_); + smsc9420_reg_write(pd, DMAC_INTR_ENA, dma_intr_ena); + smsc9420_pci_flush_write(pd); + + ints_to_clear |= (DMAC_STS_RX_ | DMAC_STS_NIS_); + napi_schedule(&pd->napi); + } + + if (ints_to_clear) + smsc9420_reg_write(pd, DMAC_STATUS, ints_to_clear); + + ret = IRQ_HANDLED; + } + + if (unlikely(INT_STAT_SW_INT_ & int_sts)) { + /* mask software interrupt */ + spin_lock_irqsave(&pd->int_lock, flags); + int_ctl = smsc9420_reg_read(pd, INT_CTL); + int_ctl &= (~INT_CTL_SW_INT_EN_); + smsc9420_reg_write(pd, INT_CTL, int_ctl); + spin_unlock_irqrestore(&pd->int_lock, flags); + + smsc9420_reg_write(pd, INT_STAT, INT_STAT_SW_INT_); + pd->software_irq_signal = true; + smp_wmb(); + + ret = IRQ_HANDLED; + } + + /* to ensure PCI write completion, we must perform a PCI read */ + smsc9420_pci_flush_write(pd); + + return ret; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +static void smsc9420_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + smsc9420_isr(0, dev); + enable_irq(dev->irq); +} +#endif /* CONFIG_NET_POLL_CONTROLLER */ + +static void smsc9420_dmac_soft_reset(struct smsc9420_pdata *pd) +{ + smsc9420_reg_write(pd, BUS_MODE, BUS_MODE_SWR_); + smsc9420_reg_read(pd, BUS_MODE); + udelay(2); + if (smsc9420_reg_read(pd, BUS_MODE) & BUS_MODE_SWR_) + smsc_warn(DRV, "Software reset not cleared"); +} + +static int smsc9420_stop(struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + u32 int_cfg; + ulong flags; + + BUG_ON(!pd); + BUG_ON(!pd->phy_dev); + + /* disable master interrupt */ + spin_lock_irqsave(&pd->int_lock, flags); + int_cfg = smsc9420_reg_read(pd, INT_CFG) & (~INT_CFG_IRQ_EN_); + smsc9420_reg_write(pd, INT_CFG, int_cfg); + spin_unlock_irqrestore(&pd->int_lock, flags); + + netif_tx_disable(dev); + napi_disable(&pd->napi); + + smsc9420_stop_tx(pd); + smsc9420_free_tx_ring(pd); + + smsc9420_stop_rx(pd); + smsc9420_free_rx_ring(pd); + + free_irq(dev->irq, pd); + + smsc9420_dmac_soft_reset(pd); + + phy_stop(pd->phy_dev); + + phy_disconnect(pd->phy_dev); + pd->phy_dev = NULL; + mdiobus_unregister(pd->mii_bus); + mdiobus_free(pd->mii_bus); + + return 0; +} + +static void smsc9420_rx_count_stats(struct net_device *dev, u32 desc_status) +{ + if (unlikely(desc_status & RDES0_ERROR_SUMMARY_)) { + dev->stats.rx_errors++; + if (desc_status & RDES0_DESCRIPTOR_ERROR_) + dev->stats.rx_over_errors++; + else if (desc_status & (RDES0_FRAME_TOO_LONG_ | + RDES0_RUNT_FRAME_ | RDES0_COLLISION_SEEN_)) + dev->stats.rx_frame_errors++; + else if (desc_status & RDES0_CRC_ERROR_) + dev->stats.rx_crc_errors++; + } + + if (unlikely(desc_status & RDES0_LENGTH_ERROR_)) + dev->stats.rx_length_errors++; + + if (unlikely(!((desc_status & RDES0_LAST_DESCRIPTOR_) && + (desc_status & RDES0_FIRST_DESCRIPTOR_)))) + dev->stats.rx_length_errors++; + + if (desc_status & RDES0_MULTICAST_FRAME_) + dev->stats.multicast++; +} + +static void smsc9420_rx_handoff(struct smsc9420_pdata *pd, const int index, + const u32 status) +{ + struct net_device *dev = pd->dev; + struct sk_buff *skb; + u16 packet_length = (status & RDES0_FRAME_LENGTH_MASK_) + >> RDES0_FRAME_LENGTH_SHFT_; + + /* remove crc from packet lendth */ + packet_length -= 4; + + if (pd->rx_csum) + packet_length -= 2; + + dev->stats.rx_packets++; + dev->stats.rx_bytes += packet_length; + + pci_unmap_single(pd->pdev, pd->rx_buffers[index].mapping, + PKT_BUF_SZ, PCI_DMA_FROMDEVICE); + pd->rx_buffers[index].mapping = 0; + + skb = pd->rx_buffers[index].skb; + pd->rx_buffers[index].skb = NULL; + + if (pd->rx_csum) { + u16 hw_csum = get_unaligned_le16(skb_tail_pointer(skb) + + NET_IP_ALIGN + packet_length + 4); + put_unaligned_le16(hw_csum, &skb->csum); + skb->ip_summed = CHECKSUM_COMPLETE; + } + + skb_reserve(skb, NET_IP_ALIGN); + skb_put(skb, packet_length); + + skb->protocol = eth_type_trans(skb, dev); + + netif_receive_skb(skb); +} + +static int smsc9420_alloc_rx_buffer(struct smsc9420_pdata *pd, int index) +{ + struct sk_buff *skb = netdev_alloc_skb(pd->dev, PKT_BUF_SZ); + dma_addr_t mapping; + + BUG_ON(pd->rx_buffers[index].skb); + BUG_ON(pd->rx_buffers[index].mapping); + + if (unlikely(!skb)) { + smsc_warn(RX_ERR, "Failed to allocate new skb!"); + return -ENOMEM; + } + + skb->dev = pd->dev; + + mapping = pci_map_single(pd->pdev, skb_tail_pointer(skb), + PKT_BUF_SZ, PCI_DMA_FROMDEVICE); + if (pci_dma_mapping_error(pd->pdev, mapping)) { + dev_kfree_skb_any(skb); + smsc_warn(RX_ERR, "pci_map_single failed!"); + return -ENOMEM; + } + + pd->rx_buffers[index].skb = skb; + pd->rx_buffers[index].mapping = mapping; + pd->rx_ring[index].buffer1 = mapping + NET_IP_ALIGN; + pd->rx_ring[index].status = RDES0_OWN_; + wmb(); + + return 0; +} + +static void smsc9420_alloc_new_rx_buffers(struct smsc9420_pdata *pd) +{ + while (pd->rx_ring_tail != pd->rx_ring_head) { + if (smsc9420_alloc_rx_buffer(pd, pd->rx_ring_tail)) + break; + + pd->rx_ring_tail = (pd->rx_ring_tail + 1) % RX_RING_SIZE; + } +} + +static int smsc9420_rx_poll(struct napi_struct *napi, int budget) +{ + struct smsc9420_pdata *pd = + container_of(napi, struct smsc9420_pdata, napi); + struct net_device *dev = pd->dev; + u32 drop_frame_cnt, dma_intr_ena, status; + int work_done; + + for (work_done = 0; work_done < budget; work_done++) { + rmb(); + status = pd->rx_ring[pd->rx_ring_head].status; + + /* stop if DMAC owns this dma descriptor */ + if (status & RDES0_OWN_) + break; + + smsc9420_rx_count_stats(dev, status); + smsc9420_rx_handoff(pd, pd->rx_ring_head, status); + pd->rx_ring_head = (pd->rx_ring_head + 1) % RX_RING_SIZE; + smsc9420_alloc_new_rx_buffers(pd); + } + + drop_frame_cnt = smsc9420_reg_read(pd, MISS_FRAME_CNTR); + dev->stats.rx_dropped += + (drop_frame_cnt & 0xFFFF) + ((drop_frame_cnt >> 17) & 0x3FF); + + /* Kick RXDMA */ + smsc9420_reg_write(pd, RX_POLL_DEMAND, 1); + smsc9420_pci_flush_write(pd); + + if (work_done < budget) { + napi_complete(&pd->napi); + + /* re-enable RX DMA interrupts */ + dma_intr_ena = smsc9420_reg_read(pd, DMAC_INTR_ENA); + dma_intr_ena |= (DMAC_INTR_ENA_RX_ | DMAC_INTR_ENA_NIS_); + smsc9420_reg_write(pd, DMAC_INTR_ENA, dma_intr_ena); + smsc9420_pci_flush_write(pd); + } + return work_done; +} + +static void +smsc9420_tx_update_stats(struct net_device *dev, u32 status, u32 length) +{ + if (unlikely(status & TDES0_ERROR_SUMMARY_)) { + dev->stats.tx_errors++; + if (status & (TDES0_EXCESSIVE_DEFERRAL_ | + TDES0_EXCESSIVE_COLLISIONS_)) + dev->stats.tx_aborted_errors++; + + if (status & (TDES0_LOSS_OF_CARRIER_ | TDES0_NO_CARRIER_)) + dev->stats.tx_carrier_errors++; + } else { + dev->stats.tx_packets++; + dev->stats.tx_bytes += (length & 0x7FF); + } + + if (unlikely(status & TDES0_EXCESSIVE_COLLISIONS_)) { + dev->stats.collisions += 16; + } else { + dev->stats.collisions += + (status & TDES0_COLLISION_COUNT_MASK_) >> + TDES0_COLLISION_COUNT_SHFT_; + } + + if (unlikely(status & TDES0_HEARTBEAT_FAIL_)) + dev->stats.tx_heartbeat_errors++; +} + +/* Check for completed dma transfers, update stats and free skbs */ +static void smsc9420_complete_tx(struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + + while (pd->tx_ring_tail != pd->tx_ring_head) { + int index = pd->tx_ring_tail; + u32 status, length; + + rmb(); + status = pd->tx_ring[index].status; + length = pd->tx_ring[index].length; + + /* Check if DMA still owns this descriptor */ + if (unlikely(TDES0_OWN_ & status)) + break; + + smsc9420_tx_update_stats(dev, status, length); + + BUG_ON(!pd->tx_buffers[index].skb); + BUG_ON(!pd->tx_buffers[index].mapping); + + pci_unmap_single(pd->pdev, pd->tx_buffers[index].mapping, + pd->tx_buffers[index].skb->len, PCI_DMA_TODEVICE); + pd->tx_buffers[index].mapping = 0; + + dev_kfree_skb_any(pd->tx_buffers[index].skb); + pd->tx_buffers[index].skb = NULL; + + pd->tx_ring[index].buffer1 = 0; + wmb(); + + pd->tx_ring_tail = (pd->tx_ring_tail + 1) % TX_RING_SIZE; + } +} + +static netdev_tx_t smsc9420_hard_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + dma_addr_t mapping; + int index = pd->tx_ring_head; + u32 tmp_desc1; + bool about_to_take_last_desc = + (((pd->tx_ring_head + 2) % TX_RING_SIZE) == pd->tx_ring_tail); + + smsc9420_complete_tx(dev); + + rmb(); + BUG_ON(pd->tx_ring[index].status & TDES0_OWN_); + BUG_ON(pd->tx_buffers[index].skb); + BUG_ON(pd->tx_buffers[index].mapping); + + mapping = pci_map_single(pd->pdev, skb->data, + skb->len, PCI_DMA_TODEVICE); + if (pci_dma_mapping_error(pd->pdev, mapping)) { + smsc_warn(TX_ERR, "pci_map_single failed, dropping packet"); + return NETDEV_TX_BUSY; + } + + pd->tx_buffers[index].skb = skb; + pd->tx_buffers[index].mapping = mapping; + + tmp_desc1 = (TDES1_LS_ | ((u32)skb->len & 0x7FF)); + if (unlikely(about_to_take_last_desc)) { + tmp_desc1 |= TDES1_IC_; + netif_stop_queue(pd->dev); + } + + /* check if we are at the last descriptor and need to set EOR */ + if (unlikely(index == (TX_RING_SIZE - 1))) + tmp_desc1 |= TDES1_TER_; + + pd->tx_ring[index].buffer1 = mapping; + pd->tx_ring[index].length = tmp_desc1; + wmb(); + + /* increment head */ + pd->tx_ring_head = (pd->tx_ring_head + 1) % TX_RING_SIZE; + + /* assign ownership to DMAC */ + pd->tx_ring[index].status = TDES0_OWN_; + wmb(); + + skb_tx_timestamp(skb); + + /* kick the DMA */ + smsc9420_reg_write(pd, TX_POLL_DEMAND, 1); + smsc9420_pci_flush_write(pd); + + return NETDEV_TX_OK; +} + +static struct net_device_stats *smsc9420_get_stats(struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + u32 counter = smsc9420_reg_read(pd, MISS_FRAME_CNTR); + dev->stats.rx_dropped += + (counter & 0x0000FFFF) + ((counter >> 17) & 0x000003FF); + return &dev->stats; +} + +static void smsc9420_set_multicast_list(struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + u32 mac_cr = smsc9420_reg_read(pd, MAC_CR); + + if (dev->flags & IFF_PROMISC) { + smsc_dbg(HW, "Promiscuous Mode Enabled"); + mac_cr |= MAC_CR_PRMS_; + mac_cr &= (~MAC_CR_MCPAS_); + mac_cr &= (~MAC_CR_HPFILT_); + } else if (dev->flags & IFF_ALLMULTI) { + smsc_dbg(HW, "Receive all Multicast Enabled"); + mac_cr &= (~MAC_CR_PRMS_); + mac_cr |= MAC_CR_MCPAS_; + mac_cr &= (~MAC_CR_HPFILT_); + } else if (!netdev_mc_empty(dev)) { + struct netdev_hw_addr *ha; + u32 hash_lo = 0, hash_hi = 0; + + smsc_dbg(HW, "Multicast filter enabled"); + netdev_for_each_mc_addr(ha, dev) { + u32 bit_num = smsc9420_hash(ha->addr); + u32 mask = 1 << (bit_num & 0x1F); + + if (bit_num & 0x20) + hash_hi |= mask; + else + hash_lo |= mask; + + } + smsc9420_reg_write(pd, HASHH, hash_hi); + smsc9420_reg_write(pd, HASHL, hash_lo); + + mac_cr &= (~MAC_CR_PRMS_); + mac_cr &= (~MAC_CR_MCPAS_); + mac_cr |= MAC_CR_HPFILT_; + } else { + smsc_dbg(HW, "Receive own packets only."); + smsc9420_reg_write(pd, HASHH, 0); + smsc9420_reg_write(pd, HASHL, 0); + + mac_cr &= (~MAC_CR_PRMS_); + mac_cr &= (~MAC_CR_MCPAS_); + mac_cr &= (~MAC_CR_HPFILT_); + } + + smsc9420_reg_write(pd, MAC_CR, mac_cr); + smsc9420_pci_flush_write(pd); +} + +static void smsc9420_phy_update_flowcontrol(struct smsc9420_pdata *pd) +{ + struct phy_device *phy_dev = pd->phy_dev; + u32 flow; + + if (phy_dev->duplex == DUPLEX_FULL) { + u16 lcladv = phy_read(phy_dev, MII_ADVERTISE); + u16 rmtadv = phy_read(phy_dev, MII_LPA); + u8 cap = mii_resolve_flowctrl_fdx(lcladv, rmtadv); + + if (cap & FLOW_CTRL_RX) + flow = 0xFFFF0002; + else + flow = 0; + + smsc_info(LINK, "rx pause %s, tx pause %s", + (cap & FLOW_CTRL_RX ? "enabled" : "disabled"), + (cap & FLOW_CTRL_TX ? "enabled" : "disabled")); + } else { + smsc_info(LINK, "half duplex"); + flow = 0; + } + + smsc9420_reg_write(pd, FLOW, flow); +} + +/* Update link mode if anything has changed. Called periodically when the + * PHY is in polling mode, even if nothing has changed. */ +static void smsc9420_phy_adjust_link(struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + struct phy_device *phy_dev = pd->phy_dev; + int carrier; + + if (phy_dev->duplex != pd->last_duplex) { + u32 mac_cr = smsc9420_reg_read(pd, MAC_CR); + if (phy_dev->duplex) { + smsc_dbg(LINK, "full duplex mode"); + mac_cr |= MAC_CR_FDPX_; + } else { + smsc_dbg(LINK, "half duplex mode"); + mac_cr &= ~MAC_CR_FDPX_; + } + smsc9420_reg_write(pd, MAC_CR, mac_cr); + + smsc9420_phy_update_flowcontrol(pd); + pd->last_duplex = phy_dev->duplex; + } + + carrier = netif_carrier_ok(dev); + if (carrier != pd->last_carrier) { + if (carrier) + smsc_dbg(LINK, "carrier OK"); + else + smsc_dbg(LINK, "no carrier"); + pd->last_carrier = carrier; + } +} + +static int smsc9420_mii_probe(struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + struct phy_device *phydev = NULL; + + BUG_ON(pd->phy_dev); + + /* Device only supports internal PHY at address 1 */ + if (!pd->mii_bus->phy_map[1]) { + pr_err("%s: no PHY found at address 1\n", dev->name); + return -ENODEV; + } + + phydev = pd->mii_bus->phy_map[1]; + smsc_info(PROBE, "PHY addr %d, phy_id 0x%08X", phydev->addr, + phydev->phy_id); + + phydev = phy_connect(dev, dev_name(&phydev->dev), + smsc9420_phy_adjust_link, 0, PHY_INTERFACE_MODE_MII); + + if (IS_ERR(phydev)) { + pr_err("%s: Could not attach to PHY\n", dev->name); + return PTR_ERR(phydev); + } + + pr_info("%s: attached PHY driver [%s] (mii_bus:phy_addr=%s, irq=%d)\n", + dev->name, phydev->drv->name, dev_name(&phydev->dev), phydev->irq); + + /* mask with MAC supported features */ + phydev->supported &= (PHY_BASIC_FEATURES | SUPPORTED_Pause | + SUPPORTED_Asym_Pause); + phydev->advertising = phydev->supported; + + pd->phy_dev = phydev; + pd->last_duplex = -1; + pd->last_carrier = -1; + + return 0; +} + +static int smsc9420_mii_init(struct net_device *dev) +{ + struct smsc9420_pdata *pd = netdev_priv(dev); + int err = -ENXIO, i; + + pd->mii_bus = mdiobus_alloc(); + if (!pd->mii_bus) { + err = -ENOMEM; + goto err_out_1; + } + pd->mii_bus->name = DRV_MDIONAME; + snprintf(pd->mii_bus->id, MII_BUS_ID_SIZE, "%x", + (pd->pdev->bus->number << 8) | pd->pdev->devfn); + pd->mii_bus->priv = pd; + pd->mii_bus->read = smsc9420_mii_read; + pd->mii_bus->write = smsc9420_mii_write; + pd->mii_bus->irq = pd->phy_irq; + for (i = 0; i < PHY_MAX_ADDR; ++i) + pd->mii_bus->irq[i] = PHY_POLL; + + /* Mask all PHYs except ID 1 (internal) */ + pd->mii_bus->phy_mask = ~(1 << 1); + + if (mdiobus_register(pd->mii_bus)) { + smsc_warn(PROBE, "Error registering mii bus"); + goto err_out_free_bus_2; + } + + if (smsc9420_mii_probe(dev) < 0) { + smsc_warn(PROBE, "Error probing mii bus"); + goto err_out_unregister_bus_3; + } + + return 0; + +err_out_unregister_bus_3: + mdiobus_unregister(pd->mii_bus); +err_out_free_bus_2: + mdiobus_free(pd->mii_bus); +err_out_1: + return err; +} + +static int smsc9420_alloc_tx_ring(struct smsc9420_pdata *pd) +{ + int i; + + BUG_ON(!pd->tx_ring); + + pd->tx_buffers = kmalloc((sizeof(struct smsc9420_ring_info) * + TX_RING_SIZE), GFP_KERNEL); + if (!pd->tx_buffers) { + smsc_warn(IFUP, "Failed to allocated tx_buffers"); + return -ENOMEM; + } + + /* Initialize the TX Ring */ + for (i = 0; i < TX_RING_SIZE; i++) { + pd->tx_buffers[i].skb = NULL; + pd->tx_buffers[i].mapping = 0; + pd->tx_ring[i].status = 0; + pd->tx_ring[i].length = 0; + pd->tx_ring[i].buffer1 = 0; + pd->tx_ring[i].buffer2 = 0; + } + pd->tx_ring[TX_RING_SIZE - 1].length = TDES1_TER_; + wmb(); + + pd->tx_ring_head = 0; + pd->tx_ring_tail = 0; + + smsc9420_reg_write(pd, TX_BASE_ADDR, pd->tx_dma_addr); + smsc9420_pci_flush_write(pd); + + return 0; +} + +static int smsc9420_alloc_rx_ring(struct smsc9420_pdata *pd) +{ + int i; + + BUG_ON(!pd->rx_ring); + + pd->rx_buffers = kmalloc((sizeof(struct smsc9420_ring_info) * + RX_RING_SIZE), GFP_KERNEL); + if (pd->rx_buffers == NULL) { + smsc_warn(IFUP, "Failed to allocated rx_buffers"); + goto out; + } + + /* initialize the rx ring */ + for (i = 0; i < RX_RING_SIZE; i++) { + pd->rx_ring[i].status = 0; + pd->rx_ring[i].length = PKT_BUF_SZ; + pd->rx_ring[i].buffer2 = 0; + pd->rx_buffers[i].skb = NULL; + pd->rx_buffers[i].mapping = 0; + } + pd->rx_ring[RX_RING_SIZE - 1].length = (PKT_BUF_SZ | RDES1_RER_); + + /* now allocate the entire ring of skbs */ + for (i = 0; i < RX_RING_SIZE; i++) { + if (smsc9420_alloc_rx_buffer(pd, i)) { + smsc_warn(IFUP, "failed to allocate rx skb %d", i); + goto out_free_rx_skbs; + } + } + + pd->rx_ring_head = 0; + pd->rx_ring_tail = 0; + + smsc9420_reg_write(pd, VLAN1, ETH_P_8021Q); + smsc_dbg(IFUP, "VLAN1 = 0x%08x", smsc9420_reg_read(pd, VLAN1)); + + if (pd->rx_csum) { + /* Enable RX COE */ + u32 coe = smsc9420_reg_read(pd, COE_CR) | RX_COE_EN; + smsc9420_reg_write(pd, COE_CR, coe); + smsc_dbg(IFUP, "COE_CR = 0x%08x", coe); + } + + smsc9420_reg_write(pd, RX_BASE_ADDR, pd->rx_dma_addr); + smsc9420_pci_flush_write(pd); + + return 0; + +out_free_rx_skbs: + smsc9420_free_rx_ring(pd); +out: + return -ENOMEM; +} + +static int smsc9420_open(struct net_device *dev) +{ + struct smsc9420_pdata *pd; + u32 bus_mode, mac_cr, dmac_control, int_cfg, dma_intr_ena, int_ctl; + unsigned long flags; + int result = 0, timeout; + + BUG_ON(!dev); + pd = netdev_priv(dev); + BUG_ON(!pd); + + if (!is_valid_ether_addr(dev->dev_addr)) { + smsc_warn(IFUP, "dev_addr is not a valid MAC address"); + result = -EADDRNOTAVAIL; + goto out_0; + } + + netif_carrier_off(dev); + + /* disable, mask and acknowledge all interrupts */ + spin_lock_irqsave(&pd->int_lock, flags); + int_cfg = smsc9420_reg_read(pd, INT_CFG) & (~INT_CFG_IRQ_EN_); + smsc9420_reg_write(pd, INT_CFG, int_cfg); + smsc9420_reg_write(pd, INT_CTL, 0); + spin_unlock_irqrestore(&pd->int_lock, flags); + smsc9420_reg_write(pd, DMAC_INTR_ENA, 0); + smsc9420_reg_write(pd, INT_STAT, 0xFFFFFFFF); + smsc9420_pci_flush_write(pd); + + if (request_irq(dev->irq, smsc9420_isr, IRQF_SHARED | IRQF_DISABLED, + DRV_NAME, pd)) { + smsc_warn(IFUP, "Unable to use IRQ = %d", dev->irq); + result = -ENODEV; + goto out_0; + } + + smsc9420_dmac_soft_reset(pd); + + /* make sure MAC_CR is sane */ + smsc9420_reg_write(pd, MAC_CR, 0); + + smsc9420_set_mac_address(dev); + + /* Configure GPIO pins to drive LEDs */ + smsc9420_reg_write(pd, GPIO_CFG, + (GPIO_CFG_LED_3_ | GPIO_CFG_LED_2_ | GPIO_CFG_LED_1_)); + + bus_mode = BUS_MODE_DMA_BURST_LENGTH_16; + +#ifdef __BIG_ENDIAN + bus_mode |= BUS_MODE_DBO_; +#endif + + smsc9420_reg_write(pd, BUS_MODE, bus_mode); + + smsc9420_pci_flush_write(pd); + + /* set bus master bridge arbitration priority for Rx and TX DMA */ + smsc9420_reg_write(pd, BUS_CFG, BUS_CFG_RXTXWEIGHT_4_1); + + smsc9420_reg_write(pd, DMAC_CONTROL, + (DMAC_CONTROL_SF_ | DMAC_CONTROL_OSF_)); + + smsc9420_pci_flush_write(pd); + + /* test the IRQ connection to the ISR */ + smsc_dbg(IFUP, "Testing ISR using IRQ %d", dev->irq); + pd->software_irq_signal = false; + + spin_lock_irqsave(&pd->int_lock, flags); + /* configure interrupt deassertion timer and enable interrupts */ + int_cfg = smsc9420_reg_read(pd, INT_CFG) | INT_CFG_IRQ_EN_; + int_cfg &= ~(INT_CFG_INT_DEAS_MASK); + int_cfg |= (INT_DEAS_TIME & INT_CFG_INT_DEAS_MASK); + smsc9420_reg_write(pd, INT_CFG, int_cfg); + + /* unmask software interrupt */ + int_ctl = smsc9420_reg_read(pd, INT_CTL) | INT_CTL_SW_INT_EN_; + smsc9420_reg_write(pd, INT_CTL, int_ctl); + spin_unlock_irqrestore(&pd->int_lock, flags); + smsc9420_pci_flush_write(pd); + + timeout = 1000; + while (timeout--) { + if (pd->software_irq_signal) + break; + msleep(1); + } + + /* disable interrupts */ + spin_lock_irqsave(&pd->int_lock, flags); + int_cfg = smsc9420_reg_read(pd, INT_CFG) & (~INT_CFG_IRQ_EN_); + smsc9420_reg_write(pd, INT_CFG, int_cfg); + spin_unlock_irqrestore(&pd->int_lock, flags); + + if (!pd->software_irq_signal) { + smsc_warn(IFUP, "ISR failed signaling test"); + result = -ENODEV; + goto out_free_irq_1; + } + + smsc_dbg(IFUP, "ISR passed test using IRQ %d", dev->irq); + + result = smsc9420_alloc_tx_ring(pd); + if (result) { + smsc_warn(IFUP, "Failed to Initialize tx dma ring"); + result = -ENOMEM; + goto out_free_irq_1; + } + + result = smsc9420_alloc_rx_ring(pd); + if (result) { + smsc_warn(IFUP, "Failed to Initialize rx dma ring"); + result = -ENOMEM; + goto out_free_tx_ring_2; + } + + result = smsc9420_mii_init(dev); + if (result) { + smsc_warn(IFUP, "Failed to initialize Phy"); + result = -ENODEV; + goto out_free_rx_ring_3; + } + + /* Bring the PHY up */ + phy_start(pd->phy_dev); + + napi_enable(&pd->napi); + + /* start tx and rx */ + mac_cr = smsc9420_reg_read(pd, MAC_CR) | MAC_CR_TXEN_ | MAC_CR_RXEN_; + smsc9420_reg_write(pd, MAC_CR, mac_cr); + + dmac_control = smsc9420_reg_read(pd, DMAC_CONTROL); + dmac_control |= DMAC_CONTROL_ST_ | DMAC_CONTROL_SR_; + smsc9420_reg_write(pd, DMAC_CONTROL, dmac_control); + smsc9420_pci_flush_write(pd); + + dma_intr_ena = smsc9420_reg_read(pd, DMAC_INTR_ENA); + dma_intr_ena |= + (DMAC_INTR_ENA_TX_ | DMAC_INTR_ENA_RX_ | DMAC_INTR_ENA_NIS_); + smsc9420_reg_write(pd, DMAC_INTR_ENA, dma_intr_ena); + smsc9420_pci_flush_write(pd); + + netif_wake_queue(dev); + + smsc9420_reg_write(pd, RX_POLL_DEMAND, 1); + + /* enable interrupts */ + spin_lock_irqsave(&pd->int_lock, flags); + int_cfg = smsc9420_reg_read(pd, INT_CFG) | INT_CFG_IRQ_EN_; + smsc9420_reg_write(pd, INT_CFG, int_cfg); + spin_unlock_irqrestore(&pd->int_lock, flags); + + return 0; + +out_free_rx_ring_3: + smsc9420_free_rx_ring(pd); +out_free_tx_ring_2: + smsc9420_free_tx_ring(pd); +out_free_irq_1: + free_irq(dev->irq, pd); +out_0: + return result; +} + +#ifdef CONFIG_PM + +static int smsc9420_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct net_device *dev = pci_get_drvdata(pdev); + struct smsc9420_pdata *pd = netdev_priv(dev); + u32 int_cfg; + ulong flags; + + /* disable interrupts */ + spin_lock_irqsave(&pd->int_lock, flags); + int_cfg = smsc9420_reg_read(pd, INT_CFG) & (~INT_CFG_IRQ_EN_); + smsc9420_reg_write(pd, INT_CFG, int_cfg); + spin_unlock_irqrestore(&pd->int_lock, flags); + + if (netif_running(dev)) { + netif_tx_disable(dev); + smsc9420_stop_tx(pd); + smsc9420_free_tx_ring(pd); + + napi_disable(&pd->napi); + smsc9420_stop_rx(pd); + smsc9420_free_rx_ring(pd); + + free_irq(dev->irq, pd); + + netif_device_detach(dev); + } + + pci_save_state(pdev); + pci_enable_wake(pdev, pci_choose_state(pdev, state), 0); + pci_disable_device(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + + return 0; +} + +static int smsc9420_resume(struct pci_dev *pdev) +{ + struct net_device *dev = pci_get_drvdata(pdev); + struct smsc9420_pdata *pd = netdev_priv(dev); + int err; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + + err = pci_enable_device(pdev); + if (err) + return err; + + pci_set_master(pdev); + + err = pci_enable_wake(pdev, 0, 0); + if (err) + smsc_warn(IFUP, "pci_enable_wake failed: %d", err); + + if (netif_running(dev)) { + err = smsc9420_open(dev); + netif_device_attach(dev); + } + return err; +} + +#endif /* CONFIG_PM */ + +static const struct net_device_ops smsc9420_netdev_ops = { + .ndo_open = smsc9420_open, + .ndo_stop = smsc9420_stop, + .ndo_start_xmit = smsc9420_hard_start_xmit, + .ndo_get_stats = smsc9420_get_stats, + .ndo_set_multicast_list = smsc9420_set_multicast_list, + .ndo_do_ioctl = smsc9420_do_ioctl, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = eth_mac_addr, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = smsc9420_poll_controller, +#endif /* CONFIG_NET_POLL_CONTROLLER */ +}; + +static int __devinit +smsc9420_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct net_device *dev; + struct smsc9420_pdata *pd; + void __iomem *virt_addr; + int result = 0; + u32 id_rev; + + printk(KERN_INFO DRV_DESCRIPTION " version " DRV_VERSION "\n"); + + /* First do the PCI initialisation */ + result = pci_enable_device(pdev); + if (unlikely(result)) { + printk(KERN_ERR "Cannot enable smsc9420\n"); + goto out_0; + } + + pci_set_master(pdev); + + dev = alloc_etherdev(sizeof(*pd)); + if (!dev) { + printk(KERN_ERR "ether device alloc failed\n"); + goto out_disable_pci_device_1; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + + if (!(pci_resource_flags(pdev, SMSC_BAR) & IORESOURCE_MEM)) { + printk(KERN_ERR "Cannot find PCI device base address\n"); + goto out_free_netdev_2; + } + + if ((pci_request_regions(pdev, DRV_NAME))) { + printk(KERN_ERR "Cannot obtain PCI resources, aborting.\n"); + goto out_free_netdev_2; + } + + if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { + printk(KERN_ERR "No usable DMA configuration, aborting.\n"); + goto out_free_regions_3; + } + + virt_addr = ioremap(pci_resource_start(pdev, SMSC_BAR), + pci_resource_len(pdev, SMSC_BAR)); + if (!virt_addr) { + printk(KERN_ERR "Cannot map device registers, aborting.\n"); + goto out_free_regions_3; + } + + /* registers are double mapped with 0 offset for LE and 0x200 for BE */ + virt_addr += LAN9420_CPSR_ENDIAN_OFFSET; + + dev->base_addr = (ulong)virt_addr; + + pd = netdev_priv(dev); + + /* pci descriptors are created in the PCI consistent area */ + pd->rx_ring = pci_alloc_consistent(pdev, + sizeof(struct smsc9420_dma_desc) * RX_RING_SIZE + + sizeof(struct smsc9420_dma_desc) * TX_RING_SIZE, + &pd->rx_dma_addr); + + if (!pd->rx_ring) + goto out_free_io_4; + + /* descriptors are aligned due to the nature of pci_alloc_consistent */ + pd->tx_ring = (struct smsc9420_dma_desc *) + (pd->rx_ring + RX_RING_SIZE); + pd->tx_dma_addr = pd->rx_dma_addr + + sizeof(struct smsc9420_dma_desc) * RX_RING_SIZE; + + pd->pdev = pdev; + pd->dev = dev; + pd->base_addr = virt_addr; + pd->msg_enable = smsc_debug; + pd->rx_csum = true; + + smsc_dbg(PROBE, "lan_base=0x%08lx", (ulong)virt_addr); + + id_rev = smsc9420_reg_read(pd, ID_REV); + switch (id_rev & 0xFFFF0000) { + case 0x94200000: + smsc_info(PROBE, "LAN9420 identified, ID_REV=0x%08X", id_rev); + break; + default: + smsc_warn(PROBE, "LAN9420 NOT identified"); + smsc_warn(PROBE, "ID_REV=0x%08X", id_rev); + goto out_free_dmadesc_5; + } + + smsc9420_dmac_soft_reset(pd); + smsc9420_eeprom_reload(pd); + smsc9420_check_mac_address(dev); + + dev->netdev_ops = &smsc9420_netdev_ops; + dev->ethtool_ops = &smsc9420_ethtool_ops; + dev->irq = pdev->irq; + + netif_napi_add(dev, &pd->napi, smsc9420_rx_poll, NAPI_WEIGHT); + + result = register_netdev(dev); + if (result) { + smsc_warn(PROBE, "error %i registering device", result); + goto out_free_dmadesc_5; + } + + pci_set_drvdata(pdev, dev); + + spin_lock_init(&pd->int_lock); + spin_lock_init(&pd->phy_lock); + + dev_info(&dev->dev, "MAC Address: %pM\n", dev->dev_addr); + + return 0; + +out_free_dmadesc_5: + pci_free_consistent(pdev, sizeof(struct smsc9420_dma_desc) * + (RX_RING_SIZE + TX_RING_SIZE), pd->rx_ring, pd->rx_dma_addr); +out_free_io_4: + iounmap(virt_addr - LAN9420_CPSR_ENDIAN_OFFSET); +out_free_regions_3: + pci_release_regions(pdev); +out_free_netdev_2: + free_netdev(dev); +out_disable_pci_device_1: + pci_disable_device(pdev); +out_0: + return -ENODEV; +} + +static void __devexit smsc9420_remove(struct pci_dev *pdev) +{ + struct net_device *dev; + struct smsc9420_pdata *pd; + + dev = pci_get_drvdata(pdev); + if (!dev) + return; + + pci_set_drvdata(pdev, NULL); + + pd = netdev_priv(dev); + unregister_netdev(dev); + + /* tx_buffers and rx_buffers are freed in stop */ + BUG_ON(pd->tx_buffers); + BUG_ON(pd->rx_buffers); + + BUG_ON(!pd->tx_ring); + BUG_ON(!pd->rx_ring); + + pci_free_consistent(pdev, sizeof(struct smsc9420_dma_desc) * + (RX_RING_SIZE + TX_RING_SIZE), pd->rx_ring, pd->rx_dma_addr); + + iounmap(pd->base_addr - LAN9420_CPSR_ENDIAN_OFFSET); + pci_release_regions(pdev); + free_netdev(dev); + pci_disable_device(pdev); +} + +static struct pci_driver smsc9420_driver = { + .name = DRV_NAME, + .id_table = smsc9420_id_table, + .probe = smsc9420_probe, + .remove = __devexit_p(smsc9420_remove), +#ifdef CONFIG_PM + .suspend = smsc9420_suspend, + .resume = smsc9420_resume, +#endif /* CONFIG_PM */ +}; + +static int __init smsc9420_init_module(void) +{ + smsc_debug = netif_msg_init(debug, SMSC_MSG_DEFAULT); + + return pci_register_driver(&smsc9420_driver); +} + +static void __exit smsc9420_exit_module(void) +{ + pci_unregister_driver(&smsc9420_driver); +} + +module_init(smsc9420_init_module); +module_exit(smsc9420_exit_module); diff --git a/drivers/net/ethernet/smsc/smsc9420.h b/drivers/net/ethernet/smsc/smsc9420.h new file mode 100644 index 000000000000..e441402f77a2 --- /dev/null +++ b/drivers/net/ethernet/smsc/smsc9420.h @@ -0,0 +1,276 @@ + /*************************************************************************** + * + * Copyright (C) 2007,2008 SMSC + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + *************************************************************************** + */ + +#ifndef _SMSC9420_H +#define _SMSC9420_H + +#define TX_RING_SIZE (32) +#define RX_RING_SIZE (128) + +/* interrupt deassertion in multiples of 10us */ +#define INT_DEAS_TIME (50) + +#define NAPI_WEIGHT (64) +#define SMSC_BAR (3) + +#ifdef __BIG_ENDIAN +/* Register set is duplicated for BE at an offset of 0x200 */ +#define LAN9420_CPSR_ENDIAN_OFFSET (0x200) +#else +#define LAN9420_CPSR_ENDIAN_OFFSET (0) +#endif + +#define PCI_VENDOR_ID_9420 (0x1055) +#define PCI_DEVICE_ID_9420 (0xE420) + +#define LAN_REGISTER_EXTENT (0x400) + +#define SMSC9420_EEPROM_SIZE ((u32)11) +#define SMSC9420_EEPROM_MAGIC (0x9420) + +#define PKT_BUF_SZ (VLAN_ETH_FRAME_LEN + NET_IP_ALIGN + 4) + +/***********************************************/ +/* DMA Controller Control and Status Registers */ +/***********************************************/ +#define BUS_MODE (0x00) +#define BUS_MODE_SWR_ (BIT(0)) +#define BUS_MODE_DMA_BURST_LENGTH_1 (BIT(8)) +#define BUS_MODE_DMA_BURST_LENGTH_2 (BIT(9)) +#define BUS_MODE_DMA_BURST_LENGTH_4 (BIT(10)) +#define BUS_MODE_DMA_BURST_LENGTH_8 (BIT(11)) +#define BUS_MODE_DMA_BURST_LENGTH_16 (BIT(12)) +#define BUS_MODE_DMA_BURST_LENGTH_32 (BIT(13)) +#define BUS_MODE_DBO_ (BIT(20)) + +#define TX_POLL_DEMAND (0x04) + +#define RX_POLL_DEMAND (0x08) + +#define RX_BASE_ADDR (0x0C) + +#define TX_BASE_ADDR (0x10) + +#define DMAC_STATUS (0x14) +#define DMAC_STS_TS_ (7 << 20) +#define DMAC_STS_RS_ (7 << 17) +#define DMAC_STS_NIS_ (BIT(16)) +#define DMAC_STS_AIS_ (BIT(15)) +#define DMAC_STS_RWT_ (BIT(9)) +#define DMAC_STS_RXPS_ (BIT(8)) +#define DMAC_STS_RXBU_ (BIT(7)) +#define DMAC_STS_RX_ (BIT(6)) +#define DMAC_STS_TXUNF_ (BIT(5)) +#define DMAC_STS_TXBU_ (BIT(2)) +#define DMAC_STS_TXPS_ (BIT(1)) +#define DMAC_STS_TX_ (BIT(0)) + +#define DMAC_CONTROL (0x18) +#define DMAC_CONTROL_TTM_ (BIT(22)) +#define DMAC_CONTROL_SF_ (BIT(21)) +#define DMAC_CONTROL_ST_ (BIT(13)) +#define DMAC_CONTROL_OSF_ (BIT(2)) +#define DMAC_CONTROL_SR_ (BIT(1)) + +#define DMAC_INTR_ENA (0x1C) +#define DMAC_INTR_ENA_NIS_ (BIT(16)) +#define DMAC_INTR_ENA_AIS_ (BIT(15)) +#define DMAC_INTR_ENA_RWT_ (BIT(9)) +#define DMAC_INTR_ENA_RXPS_ (BIT(8)) +#define DMAC_INTR_ENA_RXBU_ (BIT(7)) +#define DMAC_INTR_ENA_RX_ (BIT(6)) +#define DMAC_INTR_ENA_TXBU_ (BIT(2)) +#define DMAC_INTR_ENA_TXPS_ (BIT(1)) +#define DMAC_INTR_ENA_TX_ (BIT(0)) + +#define MISS_FRAME_CNTR (0x20) + +#define TX_BUFF_ADDR (0x50) + +#define RX_BUFF_ADDR (0x54) + +/* Transmit Descriptor Bit Defs */ +#define TDES0_OWN_ (0x80000000) +#define TDES0_ERROR_SUMMARY_ (0x00008000) +#define TDES0_LOSS_OF_CARRIER_ (0x00000800) +#define TDES0_NO_CARRIER_ (0x00000400) +#define TDES0_LATE_COLLISION_ (0x00000200) +#define TDES0_EXCESSIVE_COLLISIONS_ (0x00000100) +#define TDES0_HEARTBEAT_FAIL_ (0x00000080) +#define TDES0_COLLISION_COUNT_MASK_ (0x00000078) +#define TDES0_COLLISION_COUNT_SHFT_ (3) +#define TDES0_EXCESSIVE_DEFERRAL_ (0x00000004) +#define TDES0_DEFERRED_ (0x00000001) + +#define TDES1_IC_ 0x80000000 +#define TDES1_LS_ 0x40000000 +#define TDES1_FS_ 0x20000000 +#define TDES1_TXCSEN_ 0x08000000 +#define TDES1_TER_ (BIT(25)) +#define TDES1_TCH_ 0x01000000 + +/* Receive Descriptor 0 Bit Defs */ +#define RDES0_OWN_ (0x80000000) +#define RDES0_FRAME_LENGTH_MASK_ (0x07FF0000) +#define RDES0_FRAME_LENGTH_SHFT_ (16) +#define RDES0_ERROR_SUMMARY_ (0x00008000) +#define RDES0_DESCRIPTOR_ERROR_ (0x00004000) +#define RDES0_LENGTH_ERROR_ (0x00001000) +#define RDES0_RUNT_FRAME_ (0x00000800) +#define RDES0_MULTICAST_FRAME_ (0x00000400) +#define RDES0_FIRST_DESCRIPTOR_ (0x00000200) +#define RDES0_LAST_DESCRIPTOR_ (0x00000100) +#define RDES0_FRAME_TOO_LONG_ (0x00000080) +#define RDES0_COLLISION_SEEN_ (0x00000040) +#define RDES0_FRAME_TYPE_ (0x00000020) +#define RDES0_WATCHDOG_TIMEOUT_ (0x00000010) +#define RDES0_MII_ERROR_ (0x00000008) +#define RDES0_DRIBBLING_BIT_ (0x00000004) +#define RDES0_CRC_ERROR_ (0x00000002) + +/* Receive Descriptor 1 Bit Defs */ +#define RDES1_RER_ (0x02000000) + +/***********************************************/ +/* MAC Control and Status Registers */ +/***********************************************/ +#define MAC_CR (0x80) +#define MAC_CR_RXALL_ (0x80000000) +#define MAC_CR_DIS_RXOWN_ (0x00800000) +#define MAC_CR_LOOPBK_ (0x00200000) +#define MAC_CR_FDPX_ (0x00100000) +#define MAC_CR_MCPAS_ (0x00080000) +#define MAC_CR_PRMS_ (0x00040000) +#define MAC_CR_INVFILT_ (0x00020000) +#define MAC_CR_PASSBAD_ (0x00010000) +#define MAC_CR_HFILT_ (0x00008000) +#define MAC_CR_HPFILT_ (0x00002000) +#define MAC_CR_LCOLL_ (0x00001000) +#define MAC_CR_DIS_BCAST_ (0x00000800) +#define MAC_CR_DIS_RTRY_ (0x00000400) +#define MAC_CR_PADSTR_ (0x00000100) +#define MAC_CR_BOLMT_MSK (0x000000C0) +#define MAC_CR_MFCHK_ (0x00000020) +#define MAC_CR_TXEN_ (0x00000008) +#define MAC_CR_RXEN_ (0x00000004) + +#define ADDRH (0x84) + +#define ADDRL (0x88) + +#define HASHH (0x8C) + +#define HASHL (0x90) + +#define MII_ACCESS (0x94) +#define MII_ACCESS_MII_BUSY_ (0x00000001) +#define MII_ACCESS_MII_WRITE_ (0x00000002) +#define MII_ACCESS_MII_READ_ (0x00000000) +#define MII_ACCESS_INDX_MSK_ (0x000007C0) +#define MII_ACCESS_PHYADDR_MSK_ (0x0000F8C0) +#define MII_ACCESS_INDX_SHFT_CNT (6) +#define MII_ACCESS_PHYADDR_SHFT_CNT (11) + +#define MII_DATA (0x98) + +#define FLOW (0x9C) + +#define VLAN1 (0xA0) + +#define VLAN2 (0xA4) + +#define WUFF (0xA8) + +#define WUCSR (0xAC) + +#define COE_CR (0xB0) +#define TX_COE_EN (0x00010000) +#define RX_COE_MODE (0x00000002) +#define RX_COE_EN (0x00000001) + +/***********************************************/ +/* System Control and Status Registers */ +/***********************************************/ +#define ID_REV (0xC0) + +#define INT_CTL (0xC4) +#define INT_CTL_SW_INT_EN_ (0x00008000) +#define INT_CTL_SBERR_INT_EN_ (1 << 12) +#define INT_CTL_MBERR_INT_EN_ (1 << 13) +#define INT_CTL_GPT_INT_EN_ (0x00000008) +#define INT_CTL_PHY_INT_EN_ (0x00000004) +#define INT_CTL_WAKE_INT_EN_ (0x00000002) + +#define INT_STAT (0xC8) +#define INT_STAT_SW_INT_ (1 << 15) +#define INT_STAT_MBERR_INT_ (1 << 13) +#define INT_STAT_SBERR_INT_ (1 << 12) +#define INT_STAT_GPT_INT_ (1 << 3) +#define INT_STAT_PHY_INT_ (0x00000004) +#define INT_STAT_WAKE_INT_ (0x00000002) +#define INT_STAT_DMAC_INT_ (0x00000001) + +#define INT_CFG (0xCC) +#define INT_CFG_IRQ_INT_ (0x00080000) +#define INT_CFG_IRQ_EN_ (0x00040000) +#define INT_CFG_INT_DEAS_CLR_ (0x00000200) +#define INT_CFG_INT_DEAS_MASK (0x000000FF) + +#define GPIO_CFG (0xD0) +#define GPIO_CFG_LED_3_ (0x40000000) +#define GPIO_CFG_LED_2_ (0x20000000) +#define GPIO_CFG_LED_1_ (0x10000000) +#define GPIO_CFG_EEPR_EN_ (0x00700000) + +#define GPT_CFG (0xD4) +#define GPT_CFG_TIMER_EN_ (0x20000000) + +#define GPT_CNT (0xD8) + +#define BUS_CFG (0xDC) +#define BUS_CFG_RXTXWEIGHT_1_1 (0 << 25) +#define BUS_CFG_RXTXWEIGHT_2_1 (1 << 25) +#define BUS_CFG_RXTXWEIGHT_3_1 (2 << 25) +#define BUS_CFG_RXTXWEIGHT_4_1 (3 << 25) + +#define PMT_CTRL (0xE0) + +#define FREE_RUN (0xF4) + +#define E2P_CMD (0xF8) +#define E2P_CMD_EPC_BUSY_ (0x80000000) +#define E2P_CMD_EPC_CMD_ (0x70000000) +#define E2P_CMD_EPC_CMD_READ_ (0x00000000) +#define E2P_CMD_EPC_CMD_EWDS_ (0x10000000) +#define E2P_CMD_EPC_CMD_EWEN_ (0x20000000) +#define E2P_CMD_EPC_CMD_WRITE_ (0x30000000) +#define E2P_CMD_EPC_CMD_WRAL_ (0x40000000) +#define E2P_CMD_EPC_CMD_ERASE_ (0x50000000) +#define E2P_CMD_EPC_CMD_ERAL_ (0x60000000) +#define E2P_CMD_EPC_CMD_RELOAD_ (0x70000000) +#define E2P_CMD_EPC_TIMEOUT_ (0x00000200) +#define E2P_CMD_MAC_ADDR_LOADED_ (0x00000100) +#define E2P_CMD_EPC_ADDR_ (0x000000FF) + +#define E2P_DATA (0xFC) +#define E2P_DATA_EEPROM_DATA_ (0x000000FF) + +#endif /* _SMSC9420_H */ |