162306a36Sopenharmony_ci/* ====================================================================== 262306a36Sopenharmony_ci * 362306a36Sopenharmony_ci * A PCMCIA ethernet driver for the 3com 3c589 card. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * 3c589_cs.c 1.162 2001/10/13 00:08:50 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * The network driver code is based on Donald Becker's 3c589 code: 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Written 1994 by Donald Becker. 1262306a36Sopenharmony_ci * Copyright 1993 United States Government as represented by the 1362306a36Sopenharmony_ci * Director, National Security Agency. This software may be used and 1462306a36Sopenharmony_ci * distributed according to the terms of the GNU General Public License, 1562306a36Sopenharmony_ci * incorporated herein by reference. 1662306a36Sopenharmony_ci * Donald Becker may be reached at becker@scyld.com 1762306a36Sopenharmony_ci * 1862306a36Sopenharmony_ci * Updated for 2.5.x by Alan Cox <alan@lxorguk.ukuu.org.uk> 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * ====================================================================== 2162306a36Sopenharmony_ci */ 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define DRV_NAME "3c589_cs" 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include <linux/module.h> 2862306a36Sopenharmony_ci#include <linux/kernel.h> 2962306a36Sopenharmony_ci#include <linux/ptrace.h> 3062306a36Sopenharmony_ci#include <linux/slab.h> 3162306a36Sopenharmony_ci#include <linux/string.h> 3262306a36Sopenharmony_ci#include <linux/timer.h> 3362306a36Sopenharmony_ci#include <linux/interrupt.h> 3462306a36Sopenharmony_ci#include <linux/in.h> 3562306a36Sopenharmony_ci#include <linux/delay.h> 3662306a36Sopenharmony_ci#include <linux/ethtool.h> 3762306a36Sopenharmony_ci#include <linux/netdevice.h> 3862306a36Sopenharmony_ci#include <linux/etherdevice.h> 3962306a36Sopenharmony_ci#include <linux/skbuff.h> 4062306a36Sopenharmony_ci#include <linux/if_arp.h> 4162306a36Sopenharmony_ci#include <linux/ioport.h> 4262306a36Sopenharmony_ci#include <linux/bitops.h> 4362306a36Sopenharmony_ci#include <linux/jiffies.h> 4462306a36Sopenharmony_ci#include <linux/uaccess.h> 4562306a36Sopenharmony_ci#include <linux/io.h> 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci#include <pcmcia/cistpl.h> 4862306a36Sopenharmony_ci#include <pcmcia/cisreg.h> 4962306a36Sopenharmony_ci#include <pcmcia/ciscode.h> 5062306a36Sopenharmony_ci#include <pcmcia/ds.h> 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci/* To minimize the size of the driver source I only define operating 5462306a36Sopenharmony_ci * constants if they are used several times. You'll need the manual 5562306a36Sopenharmony_ci * if you want to understand driver details. 5662306a36Sopenharmony_ci */ 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/* Offsets from base I/O address. */ 5962306a36Sopenharmony_ci#define EL3_DATA 0x00 6062306a36Sopenharmony_ci#define EL3_TIMER 0x0a 6162306a36Sopenharmony_ci#define EL3_CMD 0x0e 6262306a36Sopenharmony_ci#define EL3_STATUS 0x0e 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci#define EEPROM_READ 0x0080 6562306a36Sopenharmony_ci#define EEPROM_BUSY 0x8000 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/* The top five bits written to EL3_CMD are a command, the lower 7062306a36Sopenharmony_ci * 11 bits are the parameter, if applicable. 7162306a36Sopenharmony_ci */ 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cienum c509cmd { 7462306a36Sopenharmony_ci TotalReset = 0<<11, 7562306a36Sopenharmony_ci SelectWindow = 1<<11, 7662306a36Sopenharmony_ci StartCoax = 2<<11, 7762306a36Sopenharmony_ci RxDisable = 3<<11, 7862306a36Sopenharmony_ci RxEnable = 4<<11, 7962306a36Sopenharmony_ci RxReset = 5<<11, 8062306a36Sopenharmony_ci RxDiscard = 8<<11, 8162306a36Sopenharmony_ci TxEnable = 9<<11, 8262306a36Sopenharmony_ci TxDisable = 10<<11, 8362306a36Sopenharmony_ci TxReset = 11<<11, 8462306a36Sopenharmony_ci FakeIntr = 12<<11, 8562306a36Sopenharmony_ci AckIntr = 13<<11, 8662306a36Sopenharmony_ci SetIntrEnb = 14<<11, 8762306a36Sopenharmony_ci SetStatusEnb = 15<<11, 8862306a36Sopenharmony_ci SetRxFilter = 16<<11, 8962306a36Sopenharmony_ci SetRxThreshold = 17<<11, 9062306a36Sopenharmony_ci SetTxThreshold = 18<<11, 9162306a36Sopenharmony_ci SetTxStart = 19<<11, 9262306a36Sopenharmony_ci StatsEnable = 21<<11, 9362306a36Sopenharmony_ci StatsDisable = 22<<11, 9462306a36Sopenharmony_ci StopCoax = 23<<11 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cienum c509status { 9862306a36Sopenharmony_ci IntLatch = 0x0001, 9962306a36Sopenharmony_ci AdapterFailure = 0x0002, 10062306a36Sopenharmony_ci TxComplete = 0x0004, 10162306a36Sopenharmony_ci TxAvailable = 0x0008, 10262306a36Sopenharmony_ci RxComplete = 0x0010, 10362306a36Sopenharmony_ci RxEarly = 0x0020, 10462306a36Sopenharmony_ci IntReq = 0x0040, 10562306a36Sopenharmony_ci StatsFull = 0x0080, 10662306a36Sopenharmony_ci CmdBusy = 0x1000 10762306a36Sopenharmony_ci}; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci/* The SetRxFilter command accepts the following classes: */ 11062306a36Sopenharmony_cienum RxFilter { 11162306a36Sopenharmony_ci RxStation = 1, 11262306a36Sopenharmony_ci RxMulticast = 2, 11362306a36Sopenharmony_ci RxBroadcast = 4, 11462306a36Sopenharmony_ci RxProm = 8 11562306a36Sopenharmony_ci}; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci/* Register window 1 offsets, the window used in normal operation. */ 11862306a36Sopenharmony_ci#define TX_FIFO 0x00 11962306a36Sopenharmony_ci#define RX_FIFO 0x00 12062306a36Sopenharmony_ci#define RX_STATUS 0x08 12162306a36Sopenharmony_ci#define TX_STATUS 0x0B 12262306a36Sopenharmony_ci#define TX_FREE 0x0C /* Remaining free bytes in Tx buffer. */ 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci#define WN0_IRQ 0x08 /* Window 0: Set IRQ line in bits 12-15. */ 12562306a36Sopenharmony_ci#define WN4_MEDIA 0x0A /* Window 4: Various transcvr/media bits. */ 12662306a36Sopenharmony_ci#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */ 12762306a36Sopenharmony_ci#define MEDIA_LED 0x0001 /* Enable link light on 3C589E cards. */ 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci/* Time in jiffies before concluding Tx hung */ 13062306a36Sopenharmony_ci#define TX_TIMEOUT ((400*HZ)/1000) 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistruct el3_private { 13362306a36Sopenharmony_ci struct pcmcia_device *p_dev; 13462306a36Sopenharmony_ci /* For transceiver monitoring */ 13562306a36Sopenharmony_ci struct timer_list media; 13662306a36Sopenharmony_ci u16 media_status; 13762306a36Sopenharmony_ci u16 fast_poll; 13862306a36Sopenharmony_ci unsigned long last_irq; 13962306a36Sopenharmony_ci spinlock_t lock; 14062306a36Sopenharmony_ci}; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic const char *if_names[] = { "auto", "10baseT", "10base2", "AUI" }; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci/*====================================================================*/ 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci/* Module parameters */ 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ciMODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); 14962306a36Sopenharmony_ciMODULE_DESCRIPTION("3Com 3c589 series PCMCIA ethernet driver"); 15062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci/* Special hook for setting if_port when module is loaded */ 15562306a36Sopenharmony_ciINT_MODULE_PARM(if_port, 0); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci/*====================================================================*/ 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic int tc589_config(struct pcmcia_device *link); 16162306a36Sopenharmony_cistatic void tc589_release(struct pcmcia_device *link); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic u16 read_eeprom(unsigned int ioaddr, int index); 16462306a36Sopenharmony_cistatic void tc589_reset(struct net_device *dev); 16562306a36Sopenharmony_cistatic void media_check(struct timer_list *t); 16662306a36Sopenharmony_cistatic int el3_config(struct net_device *dev, struct ifmap *map); 16762306a36Sopenharmony_cistatic int el3_open(struct net_device *dev); 16862306a36Sopenharmony_cistatic netdev_tx_t el3_start_xmit(struct sk_buff *skb, 16962306a36Sopenharmony_ci struct net_device *dev); 17062306a36Sopenharmony_cistatic irqreturn_t el3_interrupt(int irq, void *dev_id); 17162306a36Sopenharmony_cistatic void update_stats(struct net_device *dev); 17262306a36Sopenharmony_cistatic struct net_device_stats *el3_get_stats(struct net_device *dev); 17362306a36Sopenharmony_cistatic int el3_rx(struct net_device *dev); 17462306a36Sopenharmony_cistatic int el3_close(struct net_device *dev); 17562306a36Sopenharmony_cistatic void el3_tx_timeout(struct net_device *dev, unsigned int txqueue); 17662306a36Sopenharmony_cistatic void set_rx_mode(struct net_device *dev); 17762306a36Sopenharmony_cistatic void set_multicast_list(struct net_device *dev); 17862306a36Sopenharmony_cistatic const struct ethtool_ops netdev_ethtool_ops; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic void tc589_detach(struct pcmcia_device *p_dev); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_cistatic const struct net_device_ops el3_netdev_ops = { 18362306a36Sopenharmony_ci .ndo_open = el3_open, 18462306a36Sopenharmony_ci .ndo_stop = el3_close, 18562306a36Sopenharmony_ci .ndo_start_xmit = el3_start_xmit, 18662306a36Sopenharmony_ci .ndo_tx_timeout = el3_tx_timeout, 18762306a36Sopenharmony_ci .ndo_set_config = el3_config, 18862306a36Sopenharmony_ci .ndo_get_stats = el3_get_stats, 18962306a36Sopenharmony_ci .ndo_set_rx_mode = set_multicast_list, 19062306a36Sopenharmony_ci .ndo_set_mac_address = eth_mac_addr, 19162306a36Sopenharmony_ci .ndo_validate_addr = eth_validate_addr, 19262306a36Sopenharmony_ci}; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic int tc589_probe(struct pcmcia_device *link) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci struct el3_private *lp; 19762306a36Sopenharmony_ci struct net_device *dev; 19862306a36Sopenharmony_ci int ret; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci dev_dbg(&link->dev, "3c589_attach()\n"); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci /* Create new ethernet device */ 20362306a36Sopenharmony_ci dev = alloc_etherdev(sizeof(struct el3_private)); 20462306a36Sopenharmony_ci if (!dev) 20562306a36Sopenharmony_ci return -ENOMEM; 20662306a36Sopenharmony_ci lp = netdev_priv(dev); 20762306a36Sopenharmony_ci link->priv = dev; 20862306a36Sopenharmony_ci lp->p_dev = link; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci spin_lock_init(&lp->lock); 21162306a36Sopenharmony_ci link->resource[0]->end = 16; 21262306a36Sopenharmony_ci link->resource[0]->flags |= IO_DATA_PATH_WIDTH_16; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci link->config_flags |= CONF_ENABLE_IRQ; 21562306a36Sopenharmony_ci link->config_index = 1; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci dev->netdev_ops = &el3_netdev_ops; 21862306a36Sopenharmony_ci dev->watchdog_timeo = TX_TIMEOUT; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci dev->ethtool_ops = &netdev_ethtool_ops; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci ret = tc589_config(link); 22362306a36Sopenharmony_ci if (ret) 22462306a36Sopenharmony_ci goto err_free_netdev; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci return 0; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cierr_free_netdev: 22962306a36Sopenharmony_ci free_netdev(dev); 23062306a36Sopenharmony_ci return ret; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic void tc589_detach(struct pcmcia_device *link) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci struct net_device *dev = link->priv; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci dev_dbg(&link->dev, "3c589_detach\n"); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci unregister_netdev(dev); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci tc589_release(link); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci free_netdev(dev); 24462306a36Sopenharmony_ci} /* tc589_detach */ 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic int tc589_config(struct pcmcia_device *link) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci struct net_device *dev = link->priv; 24962306a36Sopenharmony_ci int ret, i, j, multi = 0, fifo; 25062306a36Sopenharmony_ci __be16 addr[ETH_ALEN / 2]; 25162306a36Sopenharmony_ci unsigned int ioaddr; 25262306a36Sopenharmony_ci static const char * const ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; 25362306a36Sopenharmony_ci u8 *buf; 25462306a36Sopenharmony_ci size_t len; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci dev_dbg(&link->dev, "3c589_config\n"); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci /* Is this a 3c562? */ 25962306a36Sopenharmony_ci if (link->manf_id != MANFID_3COM) 26062306a36Sopenharmony_ci dev_info(&link->dev, "hmmm, is this really a 3Com card??\n"); 26162306a36Sopenharmony_ci multi = (link->card_id == PRODID_3COM_3C562); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci link->io_lines = 16; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci /* For the 3c562, the base address must be xx00-xx7f */ 26662306a36Sopenharmony_ci for (i = j = 0; j < 0x400; j += 0x10) { 26762306a36Sopenharmony_ci if (multi && (j & 0x80)) 26862306a36Sopenharmony_ci continue; 26962306a36Sopenharmony_ci link->resource[0]->start = j ^ 0x300; 27062306a36Sopenharmony_ci i = pcmcia_request_io(link); 27162306a36Sopenharmony_ci if (i == 0) 27262306a36Sopenharmony_ci break; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci if (i != 0) 27562306a36Sopenharmony_ci goto failed; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci ret = pcmcia_request_irq(link, el3_interrupt); 27862306a36Sopenharmony_ci if (ret) 27962306a36Sopenharmony_ci goto failed; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci ret = pcmcia_enable_device(link); 28262306a36Sopenharmony_ci if (ret) 28362306a36Sopenharmony_ci goto failed; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci dev->irq = link->irq; 28662306a36Sopenharmony_ci dev->base_addr = link->resource[0]->start; 28762306a36Sopenharmony_ci ioaddr = dev->base_addr; 28862306a36Sopenharmony_ci EL3WINDOW(0); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* The 3c589 has an extra EEPROM for configuration info, including 29162306a36Sopenharmony_ci * the hardware address. The 3c562 puts the address in the CIS. 29262306a36Sopenharmony_ci */ 29362306a36Sopenharmony_ci len = pcmcia_get_tuple(link, 0x88, &buf); 29462306a36Sopenharmony_ci if (buf && len >= 6) { 29562306a36Sopenharmony_ci for (i = 0; i < 3; i++) 29662306a36Sopenharmony_ci addr[i] = htons(le16_to_cpu(buf[i*2])); 29762306a36Sopenharmony_ci kfree(buf); 29862306a36Sopenharmony_ci } else { 29962306a36Sopenharmony_ci kfree(buf); /* 0 < len < 6 */ 30062306a36Sopenharmony_ci for (i = 0; i < 3; i++) 30162306a36Sopenharmony_ci addr[i] = htons(read_eeprom(ioaddr, i)); 30262306a36Sopenharmony_ci if (addr[0] == htons(0x6060)) { 30362306a36Sopenharmony_ci dev_err(&link->dev, "IO port conflict at 0x%03lx-0x%03lx\n", 30462306a36Sopenharmony_ci dev->base_addr, dev->base_addr+15); 30562306a36Sopenharmony_ci goto failed; 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci eth_hw_addr_set(dev, (u8 *)addr); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci /* The address and resource configuration register aren't loaded from 31162306a36Sopenharmony_ci * the EEPROM and *must* be set to 0 and IRQ3 for the PCMCIA version. 31262306a36Sopenharmony_ci */ 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci outw(0x3f00, ioaddr + 8); 31562306a36Sopenharmony_ci fifo = inl(ioaddr); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci /* The if_port symbol can be set when the module is loaded */ 31862306a36Sopenharmony_ci if ((if_port >= 0) && (if_port <= 3)) 31962306a36Sopenharmony_ci dev->if_port = if_port; 32062306a36Sopenharmony_ci else 32162306a36Sopenharmony_ci dev_err(&link->dev, "invalid if_port requested\n"); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci SET_NETDEV_DEV(dev, &link->dev); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci if (register_netdev(dev) != 0) { 32662306a36Sopenharmony_ci dev_err(&link->dev, "register_netdev() failed\n"); 32762306a36Sopenharmony_ci goto failed; 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci netdev_info(dev, "3Com 3c%s, io %#3lx, irq %d, hw_addr %pM\n", 33162306a36Sopenharmony_ci (multi ? "562" : "589"), dev->base_addr, dev->irq, 33262306a36Sopenharmony_ci dev->dev_addr); 33362306a36Sopenharmony_ci netdev_info(dev, " %dK FIFO split %s Rx:Tx, %s xcvr\n", 33462306a36Sopenharmony_ci (fifo & 7) ? 32 : 8, ram_split[(fifo >> 16) & 3], 33562306a36Sopenharmony_ci if_names[dev->if_port]); 33662306a36Sopenharmony_ci return 0; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_cifailed: 33962306a36Sopenharmony_ci tc589_release(link); 34062306a36Sopenharmony_ci return -ENODEV; 34162306a36Sopenharmony_ci} /* tc589_config */ 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_cistatic void tc589_release(struct pcmcia_device *link) 34462306a36Sopenharmony_ci{ 34562306a36Sopenharmony_ci pcmcia_disable_device(link); 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic int tc589_suspend(struct pcmcia_device *link) 34962306a36Sopenharmony_ci{ 35062306a36Sopenharmony_ci struct net_device *dev = link->priv; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci if (link->open) 35362306a36Sopenharmony_ci netif_device_detach(dev); 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci return 0; 35662306a36Sopenharmony_ci} 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_cistatic int tc589_resume(struct pcmcia_device *link) 35962306a36Sopenharmony_ci{ 36062306a36Sopenharmony_ci struct net_device *dev = link->priv; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci if (link->open) { 36362306a36Sopenharmony_ci tc589_reset(dev); 36462306a36Sopenharmony_ci netif_device_attach(dev); 36562306a36Sopenharmony_ci } 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci return 0; 36862306a36Sopenharmony_ci} 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci/*====================================================================*/ 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci/* Use this for commands that may take time to finish */ 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_cistatic void tc589_wait_for_completion(struct net_device *dev, int cmd) 37562306a36Sopenharmony_ci{ 37662306a36Sopenharmony_ci int i = 100; 37762306a36Sopenharmony_ci outw(cmd, dev->base_addr + EL3_CMD); 37862306a36Sopenharmony_ci while (--i > 0) 37962306a36Sopenharmony_ci if (!(inw(dev->base_addr + EL3_STATUS) & 0x1000)) 38062306a36Sopenharmony_ci break; 38162306a36Sopenharmony_ci if (i == 0) 38262306a36Sopenharmony_ci netdev_warn(dev, "command 0x%04x did not complete!\n", cmd); 38362306a36Sopenharmony_ci} 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci/* Read a word from the EEPROM using the regular EEPROM access register. 38662306a36Sopenharmony_ci * Assume that we are in register window zero. 38762306a36Sopenharmony_ci */ 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_cistatic u16 read_eeprom(unsigned int ioaddr, int index) 39062306a36Sopenharmony_ci{ 39162306a36Sopenharmony_ci int i; 39262306a36Sopenharmony_ci outw(EEPROM_READ + index, ioaddr + 10); 39362306a36Sopenharmony_ci /* Reading the eeprom takes 162 us */ 39462306a36Sopenharmony_ci for (i = 1620; i >= 0; i--) 39562306a36Sopenharmony_ci if ((inw(ioaddr + 10) & EEPROM_BUSY) == 0) 39662306a36Sopenharmony_ci break; 39762306a36Sopenharmony_ci return inw(ioaddr + 12); 39862306a36Sopenharmony_ci} 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci/* Set transceiver type, perhaps to something other than what the user 40162306a36Sopenharmony_ci * specified in dev->if_port. 40262306a36Sopenharmony_ci */ 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_cistatic void tc589_set_xcvr(struct net_device *dev, int if_port) 40562306a36Sopenharmony_ci{ 40662306a36Sopenharmony_ci struct el3_private *lp = netdev_priv(dev); 40762306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci EL3WINDOW(0); 41062306a36Sopenharmony_ci switch (if_port) { 41162306a36Sopenharmony_ci case 0: 41262306a36Sopenharmony_ci case 1: 41362306a36Sopenharmony_ci outw(0, ioaddr + 6); 41462306a36Sopenharmony_ci break; 41562306a36Sopenharmony_ci case 2: 41662306a36Sopenharmony_ci outw(3<<14, ioaddr + 6); 41762306a36Sopenharmony_ci break; 41862306a36Sopenharmony_ci case 3: 41962306a36Sopenharmony_ci outw(1<<14, ioaddr + 6); 42062306a36Sopenharmony_ci break; 42162306a36Sopenharmony_ci } 42262306a36Sopenharmony_ci /* On PCMCIA, this just turns on the LED */ 42362306a36Sopenharmony_ci outw((if_port == 2) ? StartCoax : StopCoax, ioaddr + EL3_CMD); 42462306a36Sopenharmony_ci /* 10baseT interface, enable link beat and jabber check. */ 42562306a36Sopenharmony_ci EL3WINDOW(4); 42662306a36Sopenharmony_ci outw(MEDIA_LED | ((if_port < 2) ? MEDIA_TP : 0), ioaddr + WN4_MEDIA); 42762306a36Sopenharmony_ci EL3WINDOW(1); 42862306a36Sopenharmony_ci if (if_port == 2) 42962306a36Sopenharmony_ci lp->media_status = ((dev->if_port == 0) ? 0x8000 : 0x4000); 43062306a36Sopenharmony_ci else 43162306a36Sopenharmony_ci lp->media_status = ((dev->if_port == 0) ? 0x4010 : 0x8800); 43262306a36Sopenharmony_ci} 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_cistatic void dump_status(struct net_device *dev) 43562306a36Sopenharmony_ci{ 43662306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 43762306a36Sopenharmony_ci EL3WINDOW(1); 43862306a36Sopenharmony_ci netdev_info(dev, " irq status %04x, rx status %04x, tx status %02x tx free %04x\n", 43962306a36Sopenharmony_ci inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS), 44062306a36Sopenharmony_ci inb(ioaddr+TX_STATUS), inw(ioaddr+TX_FREE)); 44162306a36Sopenharmony_ci EL3WINDOW(4); 44262306a36Sopenharmony_ci netdev_info(dev, " diagnostics: fifo %04x net %04x ethernet %04x media %04x\n", 44362306a36Sopenharmony_ci inw(ioaddr+0x04), inw(ioaddr+0x06), inw(ioaddr+0x08), 44462306a36Sopenharmony_ci inw(ioaddr+0x0a)); 44562306a36Sopenharmony_ci EL3WINDOW(1); 44662306a36Sopenharmony_ci} 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci/* Reset and restore all of the 3c589 registers. */ 44962306a36Sopenharmony_cistatic void tc589_reset(struct net_device *dev) 45062306a36Sopenharmony_ci{ 45162306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 45262306a36Sopenharmony_ci int i; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci EL3WINDOW(0); 45562306a36Sopenharmony_ci outw(0x0001, ioaddr + 4); /* Activate board. */ 45662306a36Sopenharmony_ci outw(0x3f00, ioaddr + 8); /* Set the IRQ line. */ 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci /* Set the station address in window 2. */ 45962306a36Sopenharmony_ci EL3WINDOW(2); 46062306a36Sopenharmony_ci for (i = 0; i < 6; i++) 46162306a36Sopenharmony_ci outb(dev->dev_addr[i], ioaddr + i); 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci tc589_set_xcvr(dev, dev->if_port); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci /* Switch to the stats window, and clear all stats by reading. */ 46662306a36Sopenharmony_ci outw(StatsDisable, ioaddr + EL3_CMD); 46762306a36Sopenharmony_ci EL3WINDOW(6); 46862306a36Sopenharmony_ci for (i = 0; i < 9; i++) 46962306a36Sopenharmony_ci inb(ioaddr+i); 47062306a36Sopenharmony_ci inw(ioaddr + 10); 47162306a36Sopenharmony_ci inw(ioaddr + 12); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci /* Switch to register set 1 for normal use. */ 47462306a36Sopenharmony_ci EL3WINDOW(1); 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci set_rx_mode(dev); 47762306a36Sopenharmony_ci outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ 47862306a36Sopenharmony_ci outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ 47962306a36Sopenharmony_ci outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ 48062306a36Sopenharmony_ci /* Allow status bits to be seen. */ 48162306a36Sopenharmony_ci outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); 48262306a36Sopenharmony_ci /* Ack all pending events, and set active indicator mask. */ 48362306a36Sopenharmony_ci outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, 48462306a36Sopenharmony_ci ioaddr + EL3_CMD); 48562306a36Sopenharmony_ci outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull 48662306a36Sopenharmony_ci | AdapterFailure, ioaddr + EL3_CMD); 48762306a36Sopenharmony_ci} 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_cistatic void netdev_get_drvinfo(struct net_device *dev, 49062306a36Sopenharmony_ci struct ethtool_drvinfo *info) 49162306a36Sopenharmony_ci{ 49262306a36Sopenharmony_ci strscpy(info->driver, DRV_NAME, sizeof(info->driver)); 49362306a36Sopenharmony_ci snprintf(info->bus_info, sizeof(info->bus_info), 49462306a36Sopenharmony_ci "PCMCIA 0x%lx", dev->base_addr); 49562306a36Sopenharmony_ci} 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_cistatic const struct ethtool_ops netdev_ethtool_ops = { 49862306a36Sopenharmony_ci .get_drvinfo = netdev_get_drvinfo, 49962306a36Sopenharmony_ci}; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_cistatic int el3_config(struct net_device *dev, struct ifmap *map) 50262306a36Sopenharmony_ci{ 50362306a36Sopenharmony_ci if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { 50462306a36Sopenharmony_ci if (map->port <= 3) { 50562306a36Sopenharmony_ci dev->if_port = map->port; 50662306a36Sopenharmony_ci netdev_info(dev, "switched to %s port\n", if_names[dev->if_port]); 50762306a36Sopenharmony_ci tc589_set_xcvr(dev, dev->if_port); 50862306a36Sopenharmony_ci } else { 50962306a36Sopenharmony_ci return -EINVAL; 51062306a36Sopenharmony_ci } 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci return 0; 51362306a36Sopenharmony_ci} 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_cistatic int el3_open(struct net_device *dev) 51662306a36Sopenharmony_ci{ 51762306a36Sopenharmony_ci struct el3_private *lp = netdev_priv(dev); 51862306a36Sopenharmony_ci struct pcmcia_device *link = lp->p_dev; 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci if (!pcmcia_dev_present(link)) 52162306a36Sopenharmony_ci return -ENODEV; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci link->open++; 52462306a36Sopenharmony_ci netif_start_queue(dev); 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci tc589_reset(dev); 52762306a36Sopenharmony_ci timer_setup(&lp->media, media_check, 0); 52862306a36Sopenharmony_ci mod_timer(&lp->media, jiffies + HZ); 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci dev_dbg(&link->dev, "%s: opened, status %4.4x.\n", 53162306a36Sopenharmony_ci dev->name, inw(dev->base_addr + EL3_STATUS)); 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci return 0; 53462306a36Sopenharmony_ci} 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_cistatic void el3_tx_timeout(struct net_device *dev, unsigned int txqueue) 53762306a36Sopenharmony_ci{ 53862306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci netdev_warn(dev, "Transmit timed out!\n"); 54162306a36Sopenharmony_ci dump_status(dev); 54262306a36Sopenharmony_ci dev->stats.tx_errors++; 54362306a36Sopenharmony_ci netif_trans_update(dev); /* prevent tx timeout */ 54462306a36Sopenharmony_ci /* Issue TX_RESET and TX_START commands. */ 54562306a36Sopenharmony_ci tc589_wait_for_completion(dev, TxReset); 54662306a36Sopenharmony_ci outw(TxEnable, ioaddr + EL3_CMD); 54762306a36Sopenharmony_ci netif_wake_queue(dev); 54862306a36Sopenharmony_ci} 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_cistatic void pop_tx_status(struct net_device *dev) 55162306a36Sopenharmony_ci{ 55262306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 55362306a36Sopenharmony_ci int i; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci /* Clear the Tx status stack. */ 55662306a36Sopenharmony_ci for (i = 32; i > 0; i--) { 55762306a36Sopenharmony_ci u_char tx_status = inb(ioaddr + TX_STATUS); 55862306a36Sopenharmony_ci if (!(tx_status & 0x84)) 55962306a36Sopenharmony_ci break; 56062306a36Sopenharmony_ci /* reset transmitter on jabber error or underrun */ 56162306a36Sopenharmony_ci if (tx_status & 0x30) 56262306a36Sopenharmony_ci tc589_wait_for_completion(dev, TxReset); 56362306a36Sopenharmony_ci if (tx_status & 0x38) { 56462306a36Sopenharmony_ci netdev_dbg(dev, "transmit error: status 0x%02x\n", tx_status); 56562306a36Sopenharmony_ci outw(TxEnable, ioaddr + EL3_CMD); 56662306a36Sopenharmony_ci dev->stats.tx_aborted_errors++; 56762306a36Sopenharmony_ci } 56862306a36Sopenharmony_ci outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci} 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_cistatic netdev_tx_t el3_start_xmit(struct sk_buff *skb, 57362306a36Sopenharmony_ci struct net_device *dev) 57462306a36Sopenharmony_ci{ 57562306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 57662306a36Sopenharmony_ci struct el3_private *priv = netdev_priv(dev); 57762306a36Sopenharmony_ci unsigned long flags; 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci netdev_dbg(dev, "el3_start_xmit(length = %ld) called, status %4.4x.\n", 58062306a36Sopenharmony_ci (long)skb->len, inw(ioaddr + EL3_STATUS)); 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci spin_lock_irqsave(&priv->lock, flags); 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci dev->stats.tx_bytes += skb->len; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci /* Put out the doubleword header... */ 58762306a36Sopenharmony_ci outw(skb->len, ioaddr + TX_FIFO); 58862306a36Sopenharmony_ci outw(0x00, ioaddr + TX_FIFO); 58962306a36Sopenharmony_ci /* ... and the packet rounded to a doubleword. */ 59062306a36Sopenharmony_ci outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci if (inw(ioaddr + TX_FREE) <= 1536) { 59362306a36Sopenharmony_ci netif_stop_queue(dev); 59462306a36Sopenharmony_ci /* Interrupt us when the FIFO has room for max-sized packet. */ 59562306a36Sopenharmony_ci outw(SetTxThreshold + 1536, ioaddr + EL3_CMD); 59662306a36Sopenharmony_ci } 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci pop_tx_status(dev); 59962306a36Sopenharmony_ci spin_unlock_irqrestore(&priv->lock, flags); 60062306a36Sopenharmony_ci dev_kfree_skb(skb); 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci return NETDEV_TX_OK; 60362306a36Sopenharmony_ci} 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci/* The EL3 interrupt handler. */ 60662306a36Sopenharmony_cistatic irqreturn_t el3_interrupt(int irq, void *dev_id) 60762306a36Sopenharmony_ci{ 60862306a36Sopenharmony_ci struct net_device *dev = (struct net_device *) dev_id; 60962306a36Sopenharmony_ci struct el3_private *lp = netdev_priv(dev); 61062306a36Sopenharmony_ci unsigned int ioaddr; 61162306a36Sopenharmony_ci __u16 status; 61262306a36Sopenharmony_ci int i = 0, handled = 1; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci if (!netif_device_present(dev)) 61562306a36Sopenharmony_ci return IRQ_NONE; 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci ioaddr = dev->base_addr; 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci netdev_dbg(dev, "interrupt, status %4.4x.\n", inw(ioaddr + EL3_STATUS)); 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_ci spin_lock(&lp->lock); 62262306a36Sopenharmony_ci while ((status = inw(ioaddr + EL3_STATUS)) & 62362306a36Sopenharmony_ci (IntLatch | RxComplete | StatsFull)) { 62462306a36Sopenharmony_ci if ((status & 0xe000) != 0x2000) { 62562306a36Sopenharmony_ci netdev_dbg(dev, "interrupt from dead card\n"); 62662306a36Sopenharmony_ci handled = 0; 62762306a36Sopenharmony_ci break; 62862306a36Sopenharmony_ci } 62962306a36Sopenharmony_ci if (status & RxComplete) 63062306a36Sopenharmony_ci el3_rx(dev); 63162306a36Sopenharmony_ci if (status & TxAvailable) { 63262306a36Sopenharmony_ci netdev_dbg(dev, " TX room bit was handled.\n"); 63362306a36Sopenharmony_ci /* There's room in the FIFO for a full-sized packet. */ 63462306a36Sopenharmony_ci outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); 63562306a36Sopenharmony_ci netif_wake_queue(dev); 63662306a36Sopenharmony_ci } 63762306a36Sopenharmony_ci if (status & TxComplete) 63862306a36Sopenharmony_ci pop_tx_status(dev); 63962306a36Sopenharmony_ci if (status & (AdapterFailure | RxEarly | StatsFull)) { 64062306a36Sopenharmony_ci /* Handle all uncommon interrupts. */ 64162306a36Sopenharmony_ci if (status & StatsFull) /* Empty statistics. */ 64262306a36Sopenharmony_ci update_stats(dev); 64362306a36Sopenharmony_ci if (status & RxEarly) { 64462306a36Sopenharmony_ci /* Rx early is unused. */ 64562306a36Sopenharmony_ci el3_rx(dev); 64662306a36Sopenharmony_ci outw(AckIntr | RxEarly, ioaddr + EL3_CMD); 64762306a36Sopenharmony_ci } 64862306a36Sopenharmony_ci if (status & AdapterFailure) { 64962306a36Sopenharmony_ci u16 fifo_diag; 65062306a36Sopenharmony_ci EL3WINDOW(4); 65162306a36Sopenharmony_ci fifo_diag = inw(ioaddr + 4); 65262306a36Sopenharmony_ci EL3WINDOW(1); 65362306a36Sopenharmony_ci netdev_warn(dev, "adapter failure, FIFO diagnostic register %04x.\n", 65462306a36Sopenharmony_ci fifo_diag); 65562306a36Sopenharmony_ci if (fifo_diag & 0x0400) { 65662306a36Sopenharmony_ci /* Tx overrun */ 65762306a36Sopenharmony_ci tc589_wait_for_completion(dev, TxReset); 65862306a36Sopenharmony_ci outw(TxEnable, ioaddr + EL3_CMD); 65962306a36Sopenharmony_ci } 66062306a36Sopenharmony_ci if (fifo_diag & 0x2000) { 66162306a36Sopenharmony_ci /* Rx underrun */ 66262306a36Sopenharmony_ci tc589_wait_for_completion(dev, RxReset); 66362306a36Sopenharmony_ci set_rx_mode(dev); 66462306a36Sopenharmony_ci outw(RxEnable, ioaddr + EL3_CMD); 66562306a36Sopenharmony_ci } 66662306a36Sopenharmony_ci outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); 66762306a36Sopenharmony_ci } 66862306a36Sopenharmony_ci } 66962306a36Sopenharmony_ci if (++i > 10) { 67062306a36Sopenharmony_ci netdev_err(dev, "infinite loop in interrupt, status %4.4x.\n", 67162306a36Sopenharmony_ci status); 67262306a36Sopenharmony_ci /* Clear all interrupts */ 67362306a36Sopenharmony_ci outw(AckIntr | 0xFF, ioaddr + EL3_CMD); 67462306a36Sopenharmony_ci break; 67562306a36Sopenharmony_ci } 67662306a36Sopenharmony_ci /* Acknowledge the IRQ. */ 67762306a36Sopenharmony_ci outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); 67862306a36Sopenharmony_ci } 67962306a36Sopenharmony_ci lp->last_irq = jiffies; 68062306a36Sopenharmony_ci spin_unlock(&lp->lock); 68162306a36Sopenharmony_ci netdev_dbg(dev, "exiting interrupt, status %4.4x.\n", 68262306a36Sopenharmony_ci inw(ioaddr + EL3_STATUS)); 68362306a36Sopenharmony_ci return IRQ_RETVAL(handled); 68462306a36Sopenharmony_ci} 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_cistatic void media_check(struct timer_list *t) 68762306a36Sopenharmony_ci{ 68862306a36Sopenharmony_ci struct el3_private *lp = from_timer(lp, t, media); 68962306a36Sopenharmony_ci struct net_device *dev = lp->p_dev->priv; 69062306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 69162306a36Sopenharmony_ci u16 media, errs; 69262306a36Sopenharmony_ci unsigned long flags; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci if (!netif_device_present(dev)) 69562306a36Sopenharmony_ci goto reschedule; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci /* Check for pending interrupt with expired latency timer: with 69862306a36Sopenharmony_ci * this, we can limp along even if the interrupt is blocked 69962306a36Sopenharmony_ci */ 70062306a36Sopenharmony_ci if ((inw(ioaddr + EL3_STATUS) & IntLatch) && 70162306a36Sopenharmony_ci (inb(ioaddr + EL3_TIMER) == 0xff)) { 70262306a36Sopenharmony_ci if (!lp->fast_poll) 70362306a36Sopenharmony_ci netdev_warn(dev, "interrupt(s) dropped!\n"); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci local_irq_save(flags); 70662306a36Sopenharmony_ci el3_interrupt(dev->irq, dev); 70762306a36Sopenharmony_ci local_irq_restore(flags); 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci lp->fast_poll = HZ; 71062306a36Sopenharmony_ci } 71162306a36Sopenharmony_ci if (lp->fast_poll) { 71262306a36Sopenharmony_ci lp->fast_poll--; 71362306a36Sopenharmony_ci lp->media.expires = jiffies + HZ/100; 71462306a36Sopenharmony_ci add_timer(&lp->media); 71562306a36Sopenharmony_ci return; 71662306a36Sopenharmony_ci } 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci /* lp->lock guards the EL3 window. Window should always be 1 except 71962306a36Sopenharmony_ci * when the lock is held 72062306a36Sopenharmony_ci */ 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci spin_lock_irqsave(&lp->lock, flags); 72362306a36Sopenharmony_ci EL3WINDOW(4); 72462306a36Sopenharmony_ci media = inw(ioaddr+WN4_MEDIA) & 0xc810; 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci /* Ignore collisions unless we've had no irq's recently */ 72762306a36Sopenharmony_ci if (time_before(jiffies, lp->last_irq + HZ)) { 72862306a36Sopenharmony_ci media &= ~0x0010; 72962306a36Sopenharmony_ci } else { 73062306a36Sopenharmony_ci /* Try harder to detect carrier errors */ 73162306a36Sopenharmony_ci EL3WINDOW(6); 73262306a36Sopenharmony_ci outw(StatsDisable, ioaddr + EL3_CMD); 73362306a36Sopenharmony_ci errs = inb(ioaddr + 0); 73462306a36Sopenharmony_ci outw(StatsEnable, ioaddr + EL3_CMD); 73562306a36Sopenharmony_ci dev->stats.tx_carrier_errors += errs; 73662306a36Sopenharmony_ci if (errs || (lp->media_status & 0x0010)) 73762306a36Sopenharmony_ci media |= 0x0010; 73862306a36Sopenharmony_ci } 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_ci if (media != lp->media_status) { 74162306a36Sopenharmony_ci if ((media & lp->media_status & 0x8000) && 74262306a36Sopenharmony_ci ((lp->media_status ^ media) & 0x0800)) 74362306a36Sopenharmony_ci netdev_info(dev, "%s link beat\n", 74462306a36Sopenharmony_ci (lp->media_status & 0x0800 ? "lost" : "found")); 74562306a36Sopenharmony_ci else if ((media & lp->media_status & 0x4000) && 74662306a36Sopenharmony_ci ((lp->media_status ^ media) & 0x0010)) 74762306a36Sopenharmony_ci netdev_info(dev, "coax cable %s\n", 74862306a36Sopenharmony_ci (lp->media_status & 0x0010 ? "ok" : "problem")); 74962306a36Sopenharmony_ci if (dev->if_port == 0) { 75062306a36Sopenharmony_ci if (media & 0x8000) { 75162306a36Sopenharmony_ci if (media & 0x0800) 75262306a36Sopenharmony_ci netdev_info(dev, "flipped to 10baseT\n"); 75362306a36Sopenharmony_ci else 75462306a36Sopenharmony_ci tc589_set_xcvr(dev, 2); 75562306a36Sopenharmony_ci } else if (media & 0x4000) { 75662306a36Sopenharmony_ci if (media & 0x0010) 75762306a36Sopenharmony_ci tc589_set_xcvr(dev, 1); 75862306a36Sopenharmony_ci else 75962306a36Sopenharmony_ci netdev_info(dev, "flipped to 10base2\n"); 76062306a36Sopenharmony_ci } 76162306a36Sopenharmony_ci } 76262306a36Sopenharmony_ci lp->media_status = media; 76362306a36Sopenharmony_ci } 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci EL3WINDOW(1); 76662306a36Sopenharmony_ci spin_unlock_irqrestore(&lp->lock, flags); 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_cireschedule: 76962306a36Sopenharmony_ci lp->media.expires = jiffies + HZ; 77062306a36Sopenharmony_ci add_timer(&lp->media); 77162306a36Sopenharmony_ci} 77262306a36Sopenharmony_ci 77362306a36Sopenharmony_cistatic struct net_device_stats *el3_get_stats(struct net_device *dev) 77462306a36Sopenharmony_ci{ 77562306a36Sopenharmony_ci struct el3_private *lp = netdev_priv(dev); 77662306a36Sopenharmony_ci unsigned long flags; 77762306a36Sopenharmony_ci struct pcmcia_device *link = lp->p_dev; 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci if (pcmcia_dev_present(link)) { 78062306a36Sopenharmony_ci spin_lock_irqsave(&lp->lock, flags); 78162306a36Sopenharmony_ci update_stats(dev); 78262306a36Sopenharmony_ci spin_unlock_irqrestore(&lp->lock, flags); 78362306a36Sopenharmony_ci } 78462306a36Sopenharmony_ci return &dev->stats; 78562306a36Sopenharmony_ci} 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci/* Update statistics. We change to register window 6, so this should be run 78862306a36Sopenharmony_ci* single-threaded if the device is active. This is expected to be a rare 78962306a36Sopenharmony_ci* operation, and it's simpler for the rest of the driver to assume that 79062306a36Sopenharmony_ci* window 1 is always valid rather than use a special window-state variable. 79162306a36Sopenharmony_ci* 79262306a36Sopenharmony_ci* Caller must hold the lock for this 79362306a36Sopenharmony_ci*/ 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_cistatic void update_stats(struct net_device *dev) 79662306a36Sopenharmony_ci{ 79762306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci netdev_dbg(dev, "updating the statistics.\n"); 80062306a36Sopenharmony_ci /* Turn off statistics updates while reading. */ 80162306a36Sopenharmony_ci outw(StatsDisable, ioaddr + EL3_CMD); 80262306a36Sopenharmony_ci /* Switch to the stats window, and read everything. */ 80362306a36Sopenharmony_ci EL3WINDOW(6); 80462306a36Sopenharmony_ci dev->stats.tx_carrier_errors += inb(ioaddr + 0); 80562306a36Sopenharmony_ci dev->stats.tx_heartbeat_errors += inb(ioaddr + 1); 80662306a36Sopenharmony_ci /* Multiple collisions. */ 80762306a36Sopenharmony_ci inb(ioaddr + 2); 80862306a36Sopenharmony_ci dev->stats.collisions += inb(ioaddr + 3); 80962306a36Sopenharmony_ci dev->stats.tx_window_errors += inb(ioaddr + 4); 81062306a36Sopenharmony_ci dev->stats.rx_fifo_errors += inb(ioaddr + 5); 81162306a36Sopenharmony_ci dev->stats.tx_packets += inb(ioaddr + 6); 81262306a36Sopenharmony_ci /* Rx packets */ 81362306a36Sopenharmony_ci inb(ioaddr + 7); 81462306a36Sopenharmony_ci /* Tx deferrals */ 81562306a36Sopenharmony_ci inb(ioaddr + 8); 81662306a36Sopenharmony_ci /* Rx octets */ 81762306a36Sopenharmony_ci inw(ioaddr + 10); 81862306a36Sopenharmony_ci /* Tx octets */ 81962306a36Sopenharmony_ci inw(ioaddr + 12); 82062306a36Sopenharmony_ci 82162306a36Sopenharmony_ci /* Back to window 1, and turn statistics back on. */ 82262306a36Sopenharmony_ci EL3WINDOW(1); 82362306a36Sopenharmony_ci outw(StatsEnable, ioaddr + EL3_CMD); 82462306a36Sopenharmony_ci} 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_cistatic int el3_rx(struct net_device *dev) 82762306a36Sopenharmony_ci{ 82862306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 82962306a36Sopenharmony_ci int worklimit = 32; 83062306a36Sopenharmony_ci short rx_status; 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ci netdev_dbg(dev, "in rx_packet(), status %4.4x, rx_status %4.4x.\n", 83362306a36Sopenharmony_ci inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS)); 83462306a36Sopenharmony_ci while (!((rx_status = inw(ioaddr + RX_STATUS)) & 0x8000) && 83562306a36Sopenharmony_ci worklimit > 0) { 83662306a36Sopenharmony_ci worklimit--; 83762306a36Sopenharmony_ci if (rx_status & 0x4000) { /* Error, update stats. */ 83862306a36Sopenharmony_ci short error = rx_status & 0x3800; 83962306a36Sopenharmony_ci dev->stats.rx_errors++; 84062306a36Sopenharmony_ci switch (error) { 84162306a36Sopenharmony_ci case 0x0000: 84262306a36Sopenharmony_ci dev->stats.rx_over_errors++; 84362306a36Sopenharmony_ci break; 84462306a36Sopenharmony_ci case 0x0800: 84562306a36Sopenharmony_ci dev->stats.rx_length_errors++; 84662306a36Sopenharmony_ci break; 84762306a36Sopenharmony_ci case 0x1000: 84862306a36Sopenharmony_ci dev->stats.rx_frame_errors++; 84962306a36Sopenharmony_ci break; 85062306a36Sopenharmony_ci case 0x1800: 85162306a36Sopenharmony_ci dev->stats.rx_length_errors++; 85262306a36Sopenharmony_ci break; 85362306a36Sopenharmony_ci case 0x2000: 85462306a36Sopenharmony_ci dev->stats.rx_frame_errors++; 85562306a36Sopenharmony_ci break; 85662306a36Sopenharmony_ci case 0x2800: 85762306a36Sopenharmony_ci dev->stats.rx_crc_errors++; 85862306a36Sopenharmony_ci break; 85962306a36Sopenharmony_ci } 86062306a36Sopenharmony_ci } else { 86162306a36Sopenharmony_ci short pkt_len = rx_status & 0x7ff; 86262306a36Sopenharmony_ci struct sk_buff *skb; 86362306a36Sopenharmony_ci 86462306a36Sopenharmony_ci skb = netdev_alloc_skb(dev, pkt_len + 5); 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci netdev_dbg(dev, " Receiving packet size %d status %4.4x.\n", 86762306a36Sopenharmony_ci pkt_len, rx_status); 86862306a36Sopenharmony_ci if (skb != NULL) { 86962306a36Sopenharmony_ci skb_reserve(skb, 2); 87062306a36Sopenharmony_ci insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len), 87162306a36Sopenharmony_ci (pkt_len+3)>>2); 87262306a36Sopenharmony_ci skb->protocol = eth_type_trans(skb, dev); 87362306a36Sopenharmony_ci netif_rx(skb); 87462306a36Sopenharmony_ci dev->stats.rx_packets++; 87562306a36Sopenharmony_ci dev->stats.rx_bytes += pkt_len; 87662306a36Sopenharmony_ci } else { 87762306a36Sopenharmony_ci netdev_dbg(dev, "couldn't allocate a sk_buff of size %d.\n", 87862306a36Sopenharmony_ci pkt_len); 87962306a36Sopenharmony_ci dev->stats.rx_dropped++; 88062306a36Sopenharmony_ci } 88162306a36Sopenharmony_ci } 88262306a36Sopenharmony_ci /* Pop the top of the Rx FIFO */ 88362306a36Sopenharmony_ci tc589_wait_for_completion(dev, RxDiscard); 88462306a36Sopenharmony_ci } 88562306a36Sopenharmony_ci if (worklimit == 0) 88662306a36Sopenharmony_ci netdev_warn(dev, "too much work in el3_rx!\n"); 88762306a36Sopenharmony_ci return 0; 88862306a36Sopenharmony_ci} 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_cistatic void set_rx_mode(struct net_device *dev) 89162306a36Sopenharmony_ci{ 89262306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 89362306a36Sopenharmony_ci u16 opts = SetRxFilter | RxStation | RxBroadcast; 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci if (dev->flags & IFF_PROMISC) 89662306a36Sopenharmony_ci opts |= RxMulticast | RxProm; 89762306a36Sopenharmony_ci else if (!netdev_mc_empty(dev) || (dev->flags & IFF_ALLMULTI)) 89862306a36Sopenharmony_ci opts |= RxMulticast; 89962306a36Sopenharmony_ci outw(opts, ioaddr + EL3_CMD); 90062306a36Sopenharmony_ci} 90162306a36Sopenharmony_ci 90262306a36Sopenharmony_cistatic void set_multicast_list(struct net_device *dev) 90362306a36Sopenharmony_ci{ 90462306a36Sopenharmony_ci struct el3_private *priv = netdev_priv(dev); 90562306a36Sopenharmony_ci unsigned long flags; 90662306a36Sopenharmony_ci 90762306a36Sopenharmony_ci spin_lock_irqsave(&priv->lock, flags); 90862306a36Sopenharmony_ci set_rx_mode(dev); 90962306a36Sopenharmony_ci spin_unlock_irqrestore(&priv->lock, flags); 91062306a36Sopenharmony_ci} 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_cistatic int el3_close(struct net_device *dev) 91362306a36Sopenharmony_ci{ 91462306a36Sopenharmony_ci struct el3_private *lp = netdev_priv(dev); 91562306a36Sopenharmony_ci struct pcmcia_device *link = lp->p_dev; 91662306a36Sopenharmony_ci unsigned int ioaddr = dev->base_addr; 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci dev_dbg(&link->dev, "%s: shutting down ethercard.\n", dev->name); 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci if (pcmcia_dev_present(link)) { 92162306a36Sopenharmony_ci /* Turn off statistics ASAP. We update dev->stats below. */ 92262306a36Sopenharmony_ci outw(StatsDisable, ioaddr + EL3_CMD); 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci /* Disable the receiver and transmitter. */ 92562306a36Sopenharmony_ci outw(RxDisable, ioaddr + EL3_CMD); 92662306a36Sopenharmony_ci outw(TxDisable, ioaddr + EL3_CMD); 92762306a36Sopenharmony_ci 92862306a36Sopenharmony_ci if (dev->if_port == 2) 92962306a36Sopenharmony_ci /* Turn off thinnet power. Green! */ 93062306a36Sopenharmony_ci outw(StopCoax, ioaddr + EL3_CMD); 93162306a36Sopenharmony_ci else if (dev->if_port == 1) { 93262306a36Sopenharmony_ci /* Disable link beat and jabber */ 93362306a36Sopenharmony_ci EL3WINDOW(4); 93462306a36Sopenharmony_ci outw(0, ioaddr + WN4_MEDIA); 93562306a36Sopenharmony_ci } 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ci /* Switching back to window 0 disables the IRQ. */ 93862306a36Sopenharmony_ci EL3WINDOW(0); 93962306a36Sopenharmony_ci /* But we explicitly zero the IRQ line select anyway. */ 94062306a36Sopenharmony_ci outw(0x0f00, ioaddr + WN0_IRQ); 94162306a36Sopenharmony_ci 94262306a36Sopenharmony_ci /* Check if the card still exists */ 94362306a36Sopenharmony_ci if ((inw(ioaddr+EL3_STATUS) & 0xe000) == 0x2000) 94462306a36Sopenharmony_ci update_stats(dev); 94562306a36Sopenharmony_ci } 94662306a36Sopenharmony_ci 94762306a36Sopenharmony_ci link->open--; 94862306a36Sopenharmony_ci netif_stop_queue(dev); 94962306a36Sopenharmony_ci del_timer_sync(&lp->media); 95062306a36Sopenharmony_ci 95162306a36Sopenharmony_ci return 0; 95262306a36Sopenharmony_ci} 95362306a36Sopenharmony_ci 95462306a36Sopenharmony_cistatic const struct pcmcia_device_id tc589_ids[] = { 95562306a36Sopenharmony_ci PCMCIA_MFC_DEVICE_MANF_CARD(0, 0x0101, 0x0562), 95662306a36Sopenharmony_ci PCMCIA_MFC_DEVICE_PROD_ID1(0, "Motorola MARQUIS", 0xf03e4e77), 95762306a36Sopenharmony_ci PCMCIA_DEVICE_MANF_CARD(0x0101, 0x0589), 95862306a36Sopenharmony_ci PCMCIA_DEVICE_PROD_ID12("Farallon", "ENet", 0x58d93fc4, 0x992c2202), 95962306a36Sopenharmony_ci PCMCIA_MFC_DEVICE_CIS_MANF_CARD(0, 0x0101, 0x0035, "cis/3CXEM556.cis"), 96062306a36Sopenharmony_ci PCMCIA_MFC_DEVICE_CIS_MANF_CARD(0, 0x0101, 0x003d, "cis/3CXEM556.cis"), 96162306a36Sopenharmony_ci PCMCIA_DEVICE_NULL, 96262306a36Sopenharmony_ci}; 96362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pcmcia, tc589_ids); 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_cistatic struct pcmcia_driver tc589_driver = { 96662306a36Sopenharmony_ci .owner = THIS_MODULE, 96762306a36Sopenharmony_ci .name = "3c589_cs", 96862306a36Sopenharmony_ci .probe = tc589_probe, 96962306a36Sopenharmony_ci .remove = tc589_detach, 97062306a36Sopenharmony_ci .id_table = tc589_ids, 97162306a36Sopenharmony_ci .suspend = tc589_suspend, 97262306a36Sopenharmony_ci .resume = tc589_resume, 97362306a36Sopenharmony_ci}; 97462306a36Sopenharmony_cimodule_pcmcia_driver(tc589_driver); 975