diff options
Diffstat (limited to 'drivers/net/pcmcia')
-rw-r--r-- | drivers/net/pcmcia/3c574_cs.c | 1307 | ||||
-rw-r--r-- | drivers/net/pcmcia/3c589_cs.c | 1081 | ||||
-rw-r--r-- | drivers/net/pcmcia/Kconfig | 132 | ||||
-rw-r--r-- | drivers/net/pcmcia/Makefile | 16 | ||||
-rw-r--r-- | drivers/net/pcmcia/axnet_cs.c | 1864 | ||||
-rw-r--r-- | drivers/net/pcmcia/com20020_cs.c | 509 | ||||
-rw-r--r-- | drivers/net/pcmcia/fmvj18x_cs.c | 1286 | ||||
-rw-r--r-- | drivers/net/pcmcia/ibmtr_cs.c | 535 | ||||
-rw-r--r-- | drivers/net/pcmcia/nmclan_cs.c | 1699 | ||||
-rw-r--r-- | drivers/net/pcmcia/ositech.h | 358 | ||||
-rw-r--r-- | drivers/net/pcmcia/pcnet_cs.c | 1659 | ||||
-rw-r--r-- | drivers/net/pcmcia/smc91c92_cs.c | 2260 | ||||
-rw-r--r-- | drivers/net/pcmcia/xirc2ps_cs.c | 2031 |
13 files changed, 14737 insertions, 0 deletions
diff --git a/drivers/net/pcmcia/3c574_cs.c b/drivers/net/pcmcia/3c574_cs.c new file mode 100644 index 00000000000..41e51711480 --- /dev/null +++ b/drivers/net/pcmcia/3c574_cs.c @@ -0,0 +1,1307 @@ +/* 3c574.c: A PCMCIA ethernet driver for the 3com 3c574 "RoadRunner". + + Written 1993-1998 by + Donald Becker, becker@scyld.com, (driver core) and + David Hinds, dahinds@users.sourceforge.net (from his PC card code). + Locking fixes (C) Copyright 2003 Red Hat Inc + + This software may be used and distributed according to the terms of + the GNU General Public License, incorporated herein by reference. + + This driver derives from Donald Becker's 3c509 core, which has the + following copyright: + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + +*/ + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the 3Com 3c574 PC card Fast Ethernet +Adapter. + +II. Board-specific settings + +None -- PC cards are autoconfigured. + +III. Driver operation + +The 3c574 uses a Boomerang-style interface, without the bus-master capability. +See the Boomerang driver and documentation for most details. + +IV. Notes and chip documentation. + +Two added registers are used to enhance PIO performance, RunnerRdCtrl and +RunnerWrCtrl. These are 11 bit down-counters that are preloaded with the +count of word (16 bits) reads or writes the driver is about to do to the Rx +or Tx FIFO. The chip is then able to hide the internal-PCI-bus to PC-card +translation latency by buffering the I/O operations with an 8 word FIFO. +Note: No other chip accesses are permitted when this buffer is used. + +A second enhancement is that both attribute and common memory space +0x0800-0x0fff can translated to the PIO FIFO. Thus memory operations (faster +with *some* PCcard bridges) may be used instead of I/O operations. +This is enabled by setting the 0x10 bit in the PCMCIA LAN COR. + +Some slow PC card bridges work better if they never see a WAIT signal. +This is configured by setting the 0x20 bit in the PCMCIA LAN COR. +Only do this after testing that it is reliable and improves performance. + +The upper five bits of RunnerRdCtrl are used to window into PCcard +configuration space registers. Window 0 is the regular Boomerang/Odie +register set, 1-5 are various PC card control registers, and 16-31 are +the (reversed!) CIS table. + +A final note: writing the InternalConfig register in window 3 with an +invalid ramWidth is Very Bad. + +V. References + +http://www.scyld.com/expert/NWay.html +http://www.national.com/pf/DP/DP83840.html + +Thanks to Terry Murphy of 3Com for providing development information for +earlier 3Com products. + +*/ + +#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/in.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> +#include <linux/ethtool.h> +#include <linux/bitops.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/mem_op.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/system.h> + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("3Com 3c574 series PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +INT_MODULE_PARM(max_interrupt_work, 32); + +/* Force full duplex modes? */ +INT_MODULE_PARM(full_duplex, 0); + +/* Autodetect link polarity reversal? */ +INT_MODULE_PARM(auto_polarity, 1); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"3c574_cs.c 1.65ac1 2003/04/07 Donald Becker/David Hinds, becker@scyld.com.\n"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT ((800*HZ)/1000) + +/* To minimize the size of the driver source and make the driver more + readable not all constants are symbolically defined. + You'll need the manual if you want to understand driver details anyway. */ +/* Offsets from base I/O address. */ +#define EL3_DATA 0x00 +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e + +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) + +/* The top five bits written to EL3_CMD are a command, the lower + 11 bits are the parameter, if applicable. */ +enum el3_cmds { + TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, + RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11, + TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, + FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, + SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, + SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11, + StatsDisable = 22<<11, StopCoax = 23<<11, +}; + +enum elxl_status { + IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, + TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, + IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000 }; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { + RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 +}; + +enum Window0 { + Wn0EepromCmd = 10, Wn0EepromData = 12, /* EEPROM command/address, data. */ + IntrStatus=0x0E, /* Valid in all windows. */ +}; +/* These assumes the larger EEPROM. */ +enum Win0_EEPROM_cmds { + EEPROM_Read = 0x200, EEPROM_WRITE = 0x100, EEPROM_ERASE = 0x300, + EEPROM_EWENB = 0x30, /* Enable erasing/writing for 10 msec. */ + EEPROM_EWDIS = 0x00, /* Disable EWENB before 10 msec timeout. */ +}; + +/* Register window 1 offsets, the window used in normal operation. + On the "Odie" this window is always mapped at offsets 0x10-0x1f. + Except for TxFree, which is overlapped by RunnerWrCtrl. */ +enum Window1 { + TX_FIFO = 0x10, RX_FIFO = 0x10, RxErrors = 0x14, + RxStatus = 0x18, Timer=0x1A, TxStatus = 0x1B, + TxFree = 0x0C, /* Remaining free bytes in Tx buffer. */ + RunnerRdCtrl = 0x16, RunnerWrCtrl = 0x1c, +}; + +enum Window3 { /* Window 3: MAC/config bits. */ + Wn3_Config=0, Wn3_MAC_Ctrl=6, Wn3_Options=8, +}; +union wn3_config { + int i; + struct w3_config_fields { + unsigned int ram_size:3, ram_width:1, ram_speed:2, rom_size:2; + int pad8:8; + unsigned int ram_split:2, pad18:2, xcvr:3, pad21:1, autoselect:1; + int pad24:7; + } u; +}; + +enum Window4 { /* Window 4: Xcvr/media bits. */ + Wn4_FIFODiag = 4, Wn4_NetDiag = 6, Wn4_PhysicalMgmt=8, Wn4_Media = 10, +}; + +#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */ + +struct el3_private { + dev_link_t link; + dev_node_t node; + struct net_device_stats stats; + u16 advertising, partner; /* NWay media advertisement */ + unsigned char phys; /* MII device address */ + unsigned int autoselect:1, default_media:3; /* Read from the EEPROM/Wn3_Config. */ + /* for transceiver monitoring */ + struct timer_list media; + unsigned short media_status; + unsigned short fast_poll; + unsigned long last_irq; + spinlock_t window_lock; /* Guards the Window selection */ +}; + +/* Set iff a MII transceiver on any interface requires mdio preamble. + This only set with the original DP83840 on older 3c905 boards, so the extra + code size of a per-interface flag is not worthwhile. */ +static char mii_preamble_required = 0; + +/* Index of functions. */ + +static void tc574_config(dev_link_t *link); +static void tc574_release(dev_link_t *link); +static int tc574_event(event_t event, int priority, + event_callback_args_t *args); + +static void mdio_sync(kio_addr_t ioaddr, int bits); +static int mdio_read(kio_addr_t ioaddr, int phy_id, int location); +static void mdio_write(kio_addr_t ioaddr, int phy_id, int location, int value); +static unsigned short read_eeprom(kio_addr_t ioaddr, int index); +static void tc574_wait_for_completion(struct net_device *dev, int cmd); + +static void tc574_reset(struct net_device *dev); +static void media_check(unsigned long arg); +static int el3_open(struct net_device *dev); +static int el3_start_xmit(struct sk_buff *skb, struct net_device *dev); +static irqreturn_t el3_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void update_stats(struct net_device *dev); +static struct net_device_stats *el3_get_stats(struct net_device *dev); +static int el3_rx(struct net_device *dev, int worklimit); +static int el3_close(struct net_device *dev); +static void el3_tx_timeout(struct net_device *dev); +static int el3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static struct ethtool_ops netdev_ethtool_ops; +static void set_rx_mode(struct net_device *dev); + +static dev_info_t dev_info = "3c574_cs"; + +static dev_link_t *tc574_attach(void); +static void tc574_detach(dev_link_t *); + +static dev_link_t *dev_list; + +/* + tc574_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. +*/ + +static dev_link_t *tc574_attach(void) +{ + struct el3_private *lp; + client_reg_t client_reg; + dev_link_t *link; + struct net_device *dev; + int ret; + + DEBUG(0, "3c574_attach()\n"); + + /* Create the PC card device object. */ + dev = alloc_etherdev(sizeof(struct el3_private)); + if (!dev) + return NULL; + lp = netdev_priv(dev); + link = &lp->link; + link->priv = dev; + + spin_lock_init(&lp->window_lock); + link->io.NumPorts1 = 32; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->irq.Handler = &el3_interrupt; + link->irq.Instance = dev; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.ConfigIndex = 1; + link->conf.Present = PRESENT_OPTION; + + /* The EL3-specific entries in the device structure. */ + dev->hard_start_xmit = &el3_start_xmit; + dev->get_stats = &el3_get_stats; + dev->do_ioctl = &el3_ioctl; + SET_ETHTOOL_OPS(dev, &netdev_ethtool_ops); + dev->set_multicast_list = &set_rx_mode; + dev->open = &el3_open; + dev->stop = &el3_close; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = el3_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &tc574_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + tc574_detach(link); + return NULL; + } + + return link; +} /* tc574_attach */ + +/* + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +*/ + +static void tc574_detach(dev_link_t *link) +{ + struct net_device *dev = link->priv; + dev_link_t **linkp; + + DEBUG(0, "3c574_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->dev) + unregister_netdev(dev); + + if (link->state & DEV_CONFIG) + tc574_release(link); + + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + free_netdev(dev); +} /* tc574_detach */ + +/* + tc574_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + ethernet device available to the system. +*/ + +#define CS_CHECK(fn, ret) \ + do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static char *ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; + +static void tc574_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + struct el3_private *lp = netdev_priv(dev); + tuple_t tuple; + cisparse_t parse; + unsigned short buf[32]; + int last_fn, last_ret, i, j; + kio_addr_t ioaddr; + u16 *phys_addr; + char *cardname; + union wn3_config config; + + phys_addr = (u16 *)dev->dev_addr; + + DEBUG(0, "3c574_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + link->io.IOAddrLines = 16; + for (i = j = 0; j < 0x400; j += 0x20) { + link->io.BasePort1 = j ^ 0x300; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIO, i); + goto failed; + } + CS_CHECK(RequestIRQ, pcmcia_request_irq(link->handle, &link->irq)); + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf)); + + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + + ioaddr = dev->base_addr; + + /* The 3c574 normally uses an EEPROM for configuration info, including + the hardware address. The future products may include a modem chip + and put the address in the CIS. */ + tuple.DesiredTuple = 0x88; + if (pcmcia_get_first_tuple(handle, &tuple) == CS_SUCCESS) { + pcmcia_get_tuple_data(handle, &tuple); + for (i = 0; i < 3; i++) + phys_addr[i] = htons(buf[i]); + } else { + EL3WINDOW(0); + for (i = 0; i < 3; i++) + phys_addr[i] = htons(read_eeprom(ioaddr, i + 10)); + if (phys_addr[0] == 0x6060) { + printk(KERN_NOTICE "3c574_cs: IO port conflict at 0x%03lx" + "-0x%03lx\n", dev->base_addr, dev->base_addr+15); + goto failed; + } + } + tuple.DesiredTuple = CISTPL_VERS_1; + if (pcmcia_get_first_tuple(handle, &tuple) == CS_SUCCESS && + pcmcia_get_tuple_data(handle, &tuple) == CS_SUCCESS && + pcmcia_parse_tuple(handle, &tuple, &parse) == CS_SUCCESS) { + cardname = parse.version_1.str + parse.version_1.ofs[1]; + } else + cardname = "3Com 3c574"; + + { + u_char mcr; + outw(2<<11, ioaddr + RunnerRdCtrl); + mcr = inb(ioaddr + 2); + outw(0<<11, ioaddr + RunnerRdCtrl); + printk(KERN_INFO " ASIC rev %d,", mcr>>3); + EL3WINDOW(3); + config.i = inl(ioaddr + Wn3_Config); + lp->default_media = config.u.xcvr; + lp->autoselect = config.u.autoselect; + } + + init_timer(&lp->media); + + { + int phy; + + /* Roadrunner only: Turn on the MII transceiver */ + outw(0x8040, ioaddr + Wn3_Options); + mdelay(1); + outw(0xc040, ioaddr + Wn3_Options); + tc574_wait_for_completion(dev, TxReset); + tc574_wait_for_completion(dev, RxReset); + mdelay(1); + outw(0x8040, ioaddr + Wn3_Options); + + EL3WINDOW(4); + for (phy = 1; phy <= 32; phy++) { + int mii_status; + mdio_sync(ioaddr, 32); + mii_status = mdio_read(ioaddr, phy & 0x1f, 1); + if (mii_status != 0xffff) { + lp->phys = phy & 0x1f; + DEBUG(0, " MII transceiver at index %d, status %x.\n", + phy, mii_status); + if ((mii_status & 0x0040) == 0) + mii_preamble_required = 1; + break; + } + } + if (phy > 32) { + printk(KERN_NOTICE " No MII transceivers found!\n"); + goto failed; + } + i = mdio_read(ioaddr, lp->phys, 16) | 0x40; + mdio_write(ioaddr, lp->phys, 16, i); + lp->advertising = mdio_read(ioaddr, lp->phys, 4); + if (full_duplex) { + /* Only advertise the FD media types. */ + lp->advertising &= ~0x02a0; + mdio_write(ioaddr, lp->phys, 4, lp->advertising); + } + } + + link->state &= ~DEV_CONFIG_PENDING; + link->dev = &lp->node; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + if (register_netdev(dev) != 0) { + printk(KERN_NOTICE "3c574_cs: register_netdev() failed\n"); + link->dev = NULL; + goto failed; + } + + strcpy(lp->node.dev_name, dev->name); + + printk(KERN_INFO "%s: %s at io %#3lx, irq %d, hw_addr ", + dev->name, cardname, dev->base_addr, dev->irq); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : ".\n")); + printk(" %dK FIFO split %s Rx:Tx, %sMII interface.\n", + 8 << config.u.ram_size, ram_split[config.u.ram_split], + config.u.autoselect ? "autoselect " : ""); + + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + tc574_release(link); + return; + +} /* tc574_config */ + +/* + After a card is removed, tc574_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. +*/ + +static void tc574_release(dev_link_t *link) +{ + DEBUG(0, "3c574_release(0x%p)\n", link); + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + +/* + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. +*/ + +static int tc574_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct net_device *dev = link->priv; + + DEBUG(1, "3c574_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + tc574_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) + netif_device_detach(dev); + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + tc574_reset(dev); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* tc574_event */ + +static void dump_status(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + EL3WINDOW(1); + printk(KERN_INFO " irq status %04x, rx status %04x, tx status " + "%02x, tx free %04x\n", inw(ioaddr+EL3_STATUS), + inw(ioaddr+RxStatus), inb(ioaddr+TxStatus), + inw(ioaddr+TxFree)); + EL3WINDOW(4); + printk(KERN_INFO " diagnostics: fifo %04x net %04x ethernet %04x" + " media %04x\n", inw(ioaddr+0x04), inw(ioaddr+0x06), + inw(ioaddr+0x08), inw(ioaddr+0x0a)); + EL3WINDOW(1); +} + +/* + Use this for commands that may take time to finish +*/ +static void tc574_wait_for_completion(struct net_device *dev, int cmd) +{ + int i = 1500; + outw(cmd, dev->base_addr + EL3_CMD); + while (--i > 0) + if (!(inw(dev->base_addr + EL3_STATUS) & 0x1000)) break; + if (i == 0) + printk(KERN_NOTICE "%s: command 0x%04x did not complete!\n", dev->name, cmd); +} + +/* Read a word from the EEPROM using the regular EEPROM access register. + Assume that we are in register window zero. + */ +static unsigned short read_eeprom(kio_addr_t ioaddr, int index) +{ + int timer; + outw(EEPROM_Read + index, ioaddr + Wn0EepromCmd); + /* Pause for at least 162 usec for the read to take place. */ + for (timer = 1620; timer >= 0; timer--) { + if ((inw(ioaddr + Wn0EepromCmd) & 0x8000) == 0) + break; + } + return inw(ioaddr + Wn0EepromData); +} + +/* MII transceiver control section. + Read and write the MII registers using software-generated serial + MDIO protocol. See the MII specifications or DP83840A data sheet + for details. + The maxium data clock rate is 2.5 Mhz. The timing is easily met by the + slow PC card interface. */ + +#define MDIO_SHIFT_CLK 0x01 +#define MDIO_DIR_WRITE 0x04 +#define MDIO_DATA_WRITE0 (0x00 | MDIO_DIR_WRITE) +#define MDIO_DATA_WRITE1 (0x02 | MDIO_DIR_WRITE) +#define MDIO_DATA_READ 0x02 +#define MDIO_ENB_IN 0x00 + +/* Generate the preamble required for initial synchronization and + a few older transceivers. */ +static void mdio_sync(kio_addr_t ioaddr, int bits) +{ + kio_addr_t mdio_addr = ioaddr + Wn4_PhysicalMgmt; + + /* Establish sync by sending at least 32 logic ones. */ + while (-- bits >= 0) { + outw(MDIO_DATA_WRITE1, mdio_addr); + outw(MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr); + } +} + +static int mdio_read(kio_addr_t ioaddr, int phy_id, int location) +{ + int i; + int read_cmd = (0xf6 << 10) | (phy_id << 5) | location; + unsigned int retval = 0; + kio_addr_t mdio_addr = ioaddr + Wn4_PhysicalMgmt; + + if (mii_preamble_required) + mdio_sync(ioaddr, 32); + + /* Shift the read command bits out. */ + for (i = 14; i >= 0; i--) { + int dataval = (read_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outw(dataval, mdio_addr); + outw(dataval | MDIO_SHIFT_CLK, mdio_addr); + } + /* Read the two transition, 16 data, and wire-idle bits. */ + for (i = 19; i > 0; i--) { + outw(MDIO_ENB_IN, mdio_addr); + retval = (retval << 1) | ((inw(mdio_addr) & MDIO_DATA_READ) ? 1 : 0); + outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); + } + return (retval>>1) & 0xffff; +} + +static void mdio_write(kio_addr_t ioaddr, int phy_id, int location, int value) +{ + int write_cmd = 0x50020000 | (phy_id << 23) | (location << 18) | value; + kio_addr_t mdio_addr = ioaddr + Wn4_PhysicalMgmt; + int i; + + if (mii_preamble_required) + mdio_sync(ioaddr, 32); + + /* Shift the command bits out. */ + for (i = 31; i >= 0; i--) { + int dataval = (write_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outw(dataval, mdio_addr); + outw(dataval | MDIO_SHIFT_CLK, mdio_addr); + } + /* Leave the interface idle. */ + for (i = 1; i >= 0; i--) { + outw(MDIO_ENB_IN, mdio_addr); + outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); + } + + return; +} + +/* Reset and restore all of the 3c574 registers. */ +static void tc574_reset(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + int i; + kio_addr_t ioaddr = dev->base_addr; + unsigned long flags; + + tc574_wait_for_completion(dev, TotalReset|0x10); + + spin_lock_irqsave(&lp->window_lock, flags); + /* Clear any transactions in progress. */ + outw(0, ioaddr + RunnerWrCtrl); + outw(0, ioaddr + RunnerRdCtrl); + + /* Set the station address and mask. */ + EL3WINDOW(2); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + i); + for (; i < 12; i+=2) + outw(0, ioaddr + i); + + /* Reset config options */ + EL3WINDOW(3); + outb((dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl); + outl((lp->autoselect ? 0x01000000 : 0) | 0x0062001b, + ioaddr + Wn3_Config); + /* Roadrunner only: Turn on the MII transceiver. */ + outw(0x8040, ioaddr + Wn3_Options); + mdelay(1); + outw(0xc040, ioaddr + Wn3_Options); + EL3WINDOW(1); + spin_unlock_irqrestore(&lp->window_lock, flags); + + tc574_wait_for_completion(dev, TxReset); + tc574_wait_for_completion(dev, RxReset); + mdelay(1); + spin_lock_irqsave(&lp->window_lock, flags); + EL3WINDOW(3); + outw(0x8040, ioaddr + Wn3_Options); + + /* Switch to the stats window, and clear all stats by reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + EL3WINDOW(6); + for (i = 0; i < 10; i++) + inb(ioaddr + i); + inw(ioaddr + 10); + inw(ioaddr + 12); + EL3WINDOW(4); + inb(ioaddr + 12); + inb(ioaddr + 13); + + /* .. enable any extra statistics bits.. */ + outw(0x0040, ioaddr + Wn4_NetDiag); + + EL3WINDOW(1); + spin_unlock_irqrestore(&lp->window_lock, flags); + + /* .. re-sync MII and re-fill what NWay is advertising. */ + mdio_sync(ioaddr, 32); + mdio_write(ioaddr, lp->phys, 4, lp->advertising); + if (!auto_polarity) { + /* works for TDK 78Q2120 series MII's */ + int i = mdio_read(ioaddr, lp->phys, 16) | 0x20; + mdio_write(ioaddr, lp->phys, 16, i); + } + + spin_lock_irqsave(&lp->window_lock, flags); + /* Switch to register set 1 for normal use, just for TxFree. */ + set_rx_mode(dev); + spin_unlock_irqrestore(&lp->window_lock, flags); + outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ + outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ + /* Allow status bits to be seen. */ + outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); + /* Ack all pending events, and set active indicator mask. */ + outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, + ioaddr + EL3_CMD); + outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull + | AdapterFailure | RxEarly, ioaddr + EL3_CMD); +} + +static int el3_open(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + netif_start_queue(dev); + + tc574_reset(dev); + lp->media.function = &media_check; + lp->media.data = (unsigned long) dev; + lp->media.expires = jiffies + HZ; + add_timer(&lp->media); + + DEBUG(2, "%s: opened, status %4.4x.\n", + dev->name, inw(dev->base_addr + EL3_STATUS)); + + return 0; +} + +static void el3_tx_timeout(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + + printk(KERN_NOTICE "%s: Transmit timed out!\n", dev->name); + dump_status(dev); + lp->stats.tx_errors++; + dev->trans_start = jiffies; + /* Issue TX_RESET and TX_START commands. */ + tc574_wait_for_completion(dev, TxReset); + outw(TxEnable, ioaddr + EL3_CMD); + netif_wake_queue(dev); +} + +static void pop_tx_status(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + int i; + + /* Clear the Tx status stack. */ + for (i = 32; i > 0; i--) { + u_char tx_status = inb(ioaddr + TxStatus); + if (!(tx_status & 0x84)) + break; + /* reset transmitter on jabber error or underrun */ + if (tx_status & 0x30) + tc574_wait_for_completion(dev, TxReset); + if (tx_status & 0x38) { + DEBUG(1, "%s: transmit error: status 0x%02x\n", + dev->name, tx_status); + outw(TxEnable, ioaddr + EL3_CMD); + lp->stats.tx_aborted_errors++; + } + outb(0x00, ioaddr + TxStatus); /* Pop the status stack. */ + } +} + +static int el3_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + struct el3_private *lp = netdev_priv(dev); + unsigned long flags; + + DEBUG(3, "%s: el3_start_xmit(length = %ld) called, " + "status %4.4x.\n", dev->name, (long)skb->len, + inw(ioaddr + EL3_STATUS)); + + spin_lock_irqsave(&lp->window_lock, flags); + outw(skb->len, ioaddr + TX_FIFO); + outw(0, ioaddr + TX_FIFO); + outsl(ioaddr + TX_FIFO, skb->data, (skb->len+3)>>2); + + dev->trans_start = jiffies; + + /* TxFree appears only in Window 1, not offset 0x1c. */ + if (inw(ioaddr + TxFree) <= 1536) { + netif_stop_queue(dev); + /* Interrupt us when the FIFO has room for max-sized packet. + The threshold is in units of dwords. */ + outw(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); + } + + pop_tx_status(dev); + spin_unlock_irqrestore(&lp->window_lock, flags); + dev_kfree_skb(skb); + return 0; +} + +/* The EL3 interrupt handler. */ +static irqreturn_t el3_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *) dev_id; + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr; + unsigned status; + int work_budget = max_interrupt_work; + int handled = 0; + + if (!netif_device_present(dev)) + return IRQ_NONE; + ioaddr = dev->base_addr; + + DEBUG(3, "%s: interrupt, status %4.4x.\n", + dev->name, inw(ioaddr + EL3_STATUS)); + + spin_lock(&lp->window_lock); + + while ((status = inw(ioaddr + EL3_STATUS)) & + (IntLatch | RxComplete | RxEarly | StatsFull)) { + if (!netif_device_present(dev) || + ((status & 0xe000) != 0x2000)) { + DEBUG(1, "%s: Interrupt from dead card\n", dev->name); + break; + } + + handled = 1; + + if (status & RxComplete) + work_budget = el3_rx(dev, work_budget); + + if (status & TxAvailable) { + DEBUG(3, " TX room bit was handled.\n"); + /* There's room in the FIFO for a full-sized packet. */ + outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); + netif_wake_queue(dev); + } + + if (status & TxComplete) + pop_tx_status(dev); + + if (status & (AdapterFailure | RxEarly | StatsFull)) { + /* Handle all uncommon interrupts. */ + if (status & StatsFull) + update_stats(dev); + if (status & RxEarly) { + work_budget = el3_rx(dev, work_budget); + outw(AckIntr | RxEarly, ioaddr + EL3_CMD); + } + if (status & AdapterFailure) { + u16 fifo_diag; + EL3WINDOW(4); + fifo_diag = inw(ioaddr + Wn4_FIFODiag); + EL3WINDOW(1); + printk(KERN_NOTICE "%s: adapter failure, FIFO diagnostic" + " register %04x.\n", dev->name, fifo_diag); + if (fifo_diag & 0x0400) { + /* Tx overrun */ + tc574_wait_for_completion(dev, TxReset); + outw(TxEnable, ioaddr + EL3_CMD); + } + if (fifo_diag & 0x2000) { + /* Rx underrun */ + tc574_wait_for_completion(dev, RxReset); + set_rx_mode(dev); + outw(RxEnable, ioaddr + EL3_CMD); + } + outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); + } + } + + if (--work_budget < 0) { + DEBUG(0, "%s: Too much work in interrupt, " + "status %4.4x.\n", dev->name, status); + /* Clear all interrupts */ + outw(AckIntr | 0xFF, ioaddr + EL3_CMD); + break; + } + /* Acknowledge the IRQ. */ + outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); + } + + DEBUG(3, "%s: exiting interrupt, status %4.4x.\n", + dev->name, inw(ioaddr + EL3_STATUS)); + + spin_unlock(&lp->window_lock); + return IRQ_RETVAL(handled); +} + +/* + This timer serves two purposes: to check for missed interrupts + (and as a last resort, poll the NIC for events), and to monitor + the MII, reporting changes in cable status. +*/ +static void media_check(unsigned long arg) +{ + struct net_device *dev = (struct net_device *) arg; + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + unsigned long flags; + unsigned short /* cable, */ media, partner; + + if (!netif_device_present(dev)) + goto reschedule; + + /* Check for pending interrupt with expired latency timer: with + this, we can limp along even if the interrupt is blocked */ + if ((inw(ioaddr + EL3_STATUS) & IntLatch) && (inb(ioaddr + Timer) == 0xff)) { + if (!lp->fast_poll) + printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); + el3_interrupt(dev->irq, lp, NULL); + lp->fast_poll = HZ; + } + if (lp->fast_poll) { + lp->fast_poll--; + lp->media.expires = jiffies + 2*HZ/100; + add_timer(&lp->media); + return; + } + + spin_lock_irqsave(&lp->window_lock, flags); + EL3WINDOW(4); + media = mdio_read(ioaddr, lp->phys, 1); + partner = mdio_read(ioaddr, lp->phys, 5); + EL3WINDOW(1); + + if (media != lp->media_status) { + if ((media ^ lp->media_status) & 0x0004) + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (lp->media_status & 0x0004) ? "lost" : "found"); + if ((media ^ lp->media_status) & 0x0020) { + lp->partner = 0; + if (lp->media_status & 0x0020) { + printk(KERN_INFO "%s: autonegotiation restarted\n", + dev->name); + } else if (partner) { + partner &= lp->advertising; + lp->partner = partner; + printk(KERN_INFO "%s: autonegotiation complete: " + "%sbaseT-%cD selected\n", dev->name, + ((partner & 0x0180) ? "100" : "10"), + ((partner & 0x0140) ? 'F' : 'H')); + } else { + printk(KERN_INFO "%s: link partner did not autonegotiate\n", + dev->name); + } + + EL3WINDOW(3); + outb((partner & 0x0140 ? 0x20 : 0) | + (dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl); + EL3WINDOW(1); + + } + if (media & 0x0010) + printk(KERN_INFO "%s: remote fault detected\n", + dev->name); + if (media & 0x0002) + printk(KERN_INFO "%s: jabber detected\n", dev->name); + lp->media_status = media; + } + spin_unlock_irqrestore(&lp->window_lock, flags); + +reschedule: + lp->media.expires = jiffies + HZ; + add_timer(&lp->media); +} + +static struct net_device_stats *el3_get_stats(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + + if (netif_device_present(dev)) { + unsigned long flags; + spin_lock_irqsave(&lp->window_lock, flags); + update_stats(dev); + spin_unlock_irqrestore(&lp->window_lock, flags); + } + return &lp->stats; +} + +/* Update statistics. + Suprisingly this need not be run single-threaded, but it effectively is. + The counters clear when read, so the adds must merely be atomic. + */ +static void update_stats(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + u8 rx, tx, up; + + DEBUG(2, "%s: updating the statistics.\n", dev->name); + + if (inw(ioaddr+EL3_STATUS) == 0xffff) /* No card. */ + return; + + /* Unlike the 3c509 we need not turn off stats updates while reading. */ + /* Switch to the stats window, and read everything. */ + EL3WINDOW(6); + lp->stats.tx_carrier_errors += inb(ioaddr + 0); + lp->stats.tx_heartbeat_errors += inb(ioaddr + 1); + /* Multiple collisions. */ inb(ioaddr + 2); + lp->stats.collisions += inb(ioaddr + 3); + lp->stats.tx_window_errors += inb(ioaddr + 4); + lp->stats.rx_fifo_errors += inb(ioaddr + 5); + lp->stats.tx_packets += inb(ioaddr + 6); + up = inb(ioaddr + 9); + lp->stats.tx_packets += (up&0x30) << 4; + /* Rx packets */ inb(ioaddr + 7); + /* Tx deferrals */ inb(ioaddr + 8); + rx = inw(ioaddr + 10); + tx = inw(ioaddr + 12); + + EL3WINDOW(4); + /* BadSSD */ inb(ioaddr + 12); + up = inb(ioaddr + 13); + + lp->stats.tx_bytes += tx + ((up & 0xf0) << 12); + + EL3WINDOW(1); +} + +static int el3_rx(struct net_device *dev, int worklimit) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + short rx_status; + + DEBUG(3, "%s: in rx_packet(), status %4.4x, rx_status %4.4x.\n", + dev->name, inw(ioaddr+EL3_STATUS), inw(ioaddr+RxStatus)); + while (!((rx_status = inw(ioaddr + RxStatus)) & 0x8000) && + (--worklimit >= 0)) { + if (rx_status & 0x4000) { /* Error, update stats. */ + short error = rx_status & 0x3800; + lp->stats.rx_errors++; + switch (error) { + case 0x0000: lp->stats.rx_over_errors++; break; + case 0x0800: lp->stats.rx_length_errors++; break; + case 0x1000: lp->stats.rx_frame_errors++; break; + case 0x1800: lp->stats.rx_length_errors++; break; + case 0x2000: lp->stats.rx_frame_errors++; break; + case 0x2800: lp->stats.rx_crc_errors++; break; + } + } else { + short pkt_len = rx_status & 0x7ff; + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len+5); + + DEBUG(3, " Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + if (skb != NULL) { + skb->dev = dev; + skb_reserve(skb, 2); + insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len), + ((pkt_len+3)>>2)); + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + dev->last_rx = jiffies; + lp->stats.rx_packets++; + lp->stats.rx_bytes += pkt_len; + } else { + DEBUG(1, "%s: couldn't allocate a sk_buff of" + " size %d.\n", dev->name, pkt_len); + lp->stats.rx_dropped++; + } + } + tc574_wait_for_completion(dev, RxDiscard); + } + + return worklimit; +} + +static void netdev_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, "3c574_cs"); +} + +static struct ethtool_ops netdev_ethtool_ops = { + .get_drvinfo = netdev_get_drvinfo, +}; + +/* Provide ioctl() calls to examine the MII xcvr state. */ +static int el3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + u16 *data = (u16 *)&rq->ifr_ifru; + int phy = lp->phys & 0x1f; + + DEBUG(2, "%s: In ioct(%-.6s, %#4.4x) %4.4x %4.4x %4.4x %4.4x.\n", + dev->name, rq->ifr_ifrn.ifrn_name, cmd, + data[0], data[1], data[2], data[3]); + + switch(cmd) { + case SIOCGMIIPHY: /* Get the address of the PHY in use. */ + data[0] = phy; + case SIOCGMIIREG: /* Read the specified MII register. */ + { + int saved_window; + unsigned long flags; + + spin_lock_irqsave(&lp->window_lock, flags); + saved_window = inw(ioaddr + EL3_CMD) >> 13; + EL3WINDOW(4); + data[3] = mdio_read(ioaddr, data[0] & 0x1f, data[1] & 0x1f); + EL3WINDOW(saved_window); + spin_unlock_irqrestore(&lp->window_lock, flags); + return 0; + } + case SIOCSMIIREG: /* Write the specified MII register */ + { + int saved_window; + unsigned long flags; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + spin_lock_irqsave(&lp->window_lock, flags); + saved_window = inw(ioaddr + EL3_CMD) >> 13; + EL3WINDOW(4); + mdio_write(ioaddr, data[0] & 0x1f, data[1] & 0x1f, data[2]); + EL3WINDOW(saved_window); + spin_unlock_irqrestore(&lp->window_lock, flags); + return 0; + } + default: + return -EOPNOTSUPP; + } +} + +/* The Odie chip has a 64 bin multicast filter, but the bit layout is not + documented. Until it is we revert to receiving all multicast frames when + any multicast reception is desired. + Note: My other drivers emit a log message whenever promiscuous mode is + entered to help detect password sniffers. This is less desirable on + typical PC card machines, so we omit the message. + */ + +static void set_rx_mode(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + + if (dev->flags & IFF_PROMISC) + outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm, + ioaddr + EL3_CMD); + else if (dev->mc_count || (dev->flags & IFF_ALLMULTI)) + outw(SetRxFilter|RxStation|RxMulticast|RxBroadcast, ioaddr + EL3_CMD); + else + outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); +} + +static int el3_close(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + struct el3_private *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + + DEBUG(2, "%s: shutting down ethercard.\n", dev->name); + + if (DEV_OK(link)) { + unsigned long flags; + + /* Turn off statistics ASAP. We update lp->stats below. */ + outw(StatsDisable, ioaddr + EL3_CMD); + + /* Disable the receiver and transmitter. */ + outw(RxDisable, ioaddr + EL3_CMD); + outw(TxDisable, ioaddr + EL3_CMD); + + /* Note: Switching to window 0 may disable the IRQ. */ + EL3WINDOW(0); + spin_lock_irqsave(&lp->window_lock, flags); + update_stats(dev); + spin_unlock_irqrestore(&lp->window_lock, flags); + } + + link->open--; + netif_stop_queue(dev); + del_timer_sync(&lp->media); + + return 0; +} + +static struct pcmcia_driver tc574_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "3c574_cs", + }, + .attach = tc574_attach, + .detach = tc574_detach, +}; + +static int __init init_tc574(void) +{ + return pcmcia_register_driver(&tc574_driver); +} + +static void __exit exit_tc574(void) +{ + pcmcia_unregister_driver(&tc574_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_tc574); +module_exit(exit_tc574); diff --git a/drivers/net/pcmcia/3c589_cs.c b/drivers/net/pcmcia/3c589_cs.c new file mode 100644 index 00000000000..89abdda1d34 --- /dev/null +++ b/drivers/net/pcmcia/3c589_cs.c @@ -0,0 +1,1081 @@ +/*====================================================================== + + A PCMCIA ethernet driver for the 3com 3c589 card. + + Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net + + 3c589_cs.c 1.162 2001/10/13 00:08:50 + + The network driver code is based on Donald Becker's 3c589 code: + + Written 1994 by Donald Becker. + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU General Public License, + incorporated herein by reference. + Donald Becker may be reached at becker@scyld.com + + Updated for 2.5.x by Alan Cox <alan@redhat.com> + +======================================================================*/ + +#define DRV_NAME "3c589_cs" +#define DRV_VERSION "1.162-ac" + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <linux/ethtool.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> +#include <linux/bitops.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/system.h> + +/* To minimize the size of the driver source I only define operating + constants if they are used several times. You'll need the manual + if you want to understand driver details. */ +/* Offsets from base I/O address. */ +#define EL3_DATA 0x00 +#define EL3_TIMER 0x0a +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e + +#define EEPROM_READ 0x0080 +#define EEPROM_BUSY 0x8000 + +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) + +/* The top five bits written to EL3_CMD are a command, the lower + 11 bits are the parameter, if applicable. */ +enum c509cmd { + TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, + RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11, + TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, + FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, + SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, + SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11, + StatsDisable = 22<<11, StopCoax = 23<<11, +}; + +enum c509status { + IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, + TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, + IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000 +}; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { + RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 +}; + +/* Register window 1 offsets, the window used in normal operation. */ +#define TX_FIFO 0x00 +#define RX_FIFO 0x00 +#define RX_STATUS 0x08 +#define TX_STATUS 0x0B +#define TX_FREE 0x0C /* Remaining free bytes in Tx buffer. */ + +#define WN0_IRQ 0x08 /* Window 0: Set IRQ line in bits 12-15. */ +#define WN4_MEDIA 0x0A /* Window 4: Various transcvr/media bits. */ +#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */ +#define MEDIA_LED 0x0001 /* Enable link light on 3C589E cards. */ + +/* Time in jiffies before concluding Tx hung */ +#define TX_TIMEOUT ((400*HZ)/1000) + +struct el3_private { + dev_link_t link; + dev_node_t node; + struct net_device_stats stats; + /* For transceiver monitoring */ + struct timer_list media; + u16 media_status; + u16 fast_poll; + unsigned long last_irq; + spinlock_t lock; +}; + +static char *if_names[] = { "auto", "10baseT", "10base2", "AUI" }; + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("3Com 3c589 series PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +/* Special hook for setting if_port when module is loaded */ +INT_MODULE_PARM(if_port, 0); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +DRV_NAME ".c " DRV_VERSION " 2001/10/13 00:08:50 (David Hinds)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +static void tc589_config(dev_link_t *link); +static void tc589_release(dev_link_t *link); +static int tc589_event(event_t event, int priority, + event_callback_args_t *args); + +static u16 read_eeprom(kio_addr_t ioaddr, int index); +static void tc589_reset(struct net_device *dev); +static void media_check(unsigned long arg); +static int el3_config(struct net_device *dev, struct ifmap *map); +static int el3_open(struct net_device *dev); +static int el3_start_xmit(struct sk_buff *skb, struct net_device *dev); +static irqreturn_t el3_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void update_stats(struct net_device *dev); +static struct net_device_stats *el3_get_stats(struct net_device *dev); +static int el3_rx(struct net_device *dev); +static int el3_close(struct net_device *dev); +static void el3_tx_timeout(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); +static struct ethtool_ops netdev_ethtool_ops; + +static dev_info_t dev_info = "3c589_cs"; + +static dev_link_t *tc589_attach(void); +static void tc589_detach(dev_link_t *); + +static dev_link_t *dev_list; + +/*====================================================================== + + tc589_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *tc589_attach(void) +{ + struct el3_private *lp; + client_reg_t client_reg; + dev_link_t *link; + struct net_device *dev; + int ret; + + DEBUG(0, "3c589_attach()\n"); + + /* Create new ethernet device */ + dev = alloc_etherdev(sizeof(struct el3_private)); + if (!dev) + return NULL; + lp = netdev_priv(dev); + link = &lp->link; + link->priv = dev; + + spin_lock_init(&lp->lock); + link->io.NumPorts1 = 16; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->irq.Handler = &el3_interrupt; + link->irq.Instance = dev; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.ConfigIndex = 1; + link->conf.Present = PRESENT_OPTION; + + /* The EL3-specific entries in the device structure. */ + SET_MODULE_OWNER(dev); + dev->hard_start_xmit = &el3_start_xmit; + dev->set_config = &el3_config; + dev->get_stats = &el3_get_stats; + dev->set_multicast_list = &set_multicast_list; + dev->open = &el3_open; + dev->stop = &el3_close; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = el3_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + SET_ETHTOOL_OPS(dev, &netdev_ethtool_ops); + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &tc589_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + tc589_detach(link); + return NULL; + } + + return link; +} /* tc589_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void tc589_detach(dev_link_t *link) +{ + struct net_device *dev = link->priv; + dev_link_t **linkp; + + DEBUG(0, "3c589_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->dev) + unregister_netdev(dev); + + if (link->state & DEV_CONFIG) + tc589_release(link); + + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + free_netdev(dev); +} /* tc589_detach */ + +/*====================================================================== + + tc589_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + ethernet device available to the system. + +======================================================================*/ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static void tc589_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + struct el3_private *lp = netdev_priv(dev); + tuple_t tuple; + cisparse_t parse; + u16 buf[32], *phys_addr; + int last_fn, last_ret, i, j, multi = 0, fifo; + kio_addr_t ioaddr; + char *ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; + + DEBUG(0, "3c589_config(0x%p)\n", link); + + phys_addr = (u16 *)dev->dev_addr; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Is this a 3c562? */ + tuple.DesiredTuple = CISTPL_MANFID; + tuple.Attributes = TUPLE_RETURN_COMMON; + if ((pcmcia_get_first_tuple(handle, &tuple) == CS_SUCCESS) && + (pcmcia_get_tuple_data(handle, &tuple) == CS_SUCCESS)) { + if (le16_to_cpu(buf[0]) != MANFID_3COM) + printk(KERN_INFO "3c589_cs: hmmm, is this really a " + "3Com card??\n"); + multi = (le16_to_cpu(buf[1]) == PRODID_3COM_3C562); + } + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* For the 3c562, the base address must be xx00-xx7f */ + link->io.IOAddrLines = 16; + for (i = j = 0; j < 0x400; j += 0x10) { + if (multi && (j & 0x80)) continue; + link->io.BasePort1 = j ^ 0x300; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIO, i); + goto failed; + } + CS_CHECK(RequestIRQ, pcmcia_request_irq(link->handle, &link->irq)); + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf)); + + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + ioaddr = dev->base_addr; + EL3WINDOW(0); + + /* The 3c589 has an extra EEPROM for configuration info, including + the hardware address. The 3c562 puts the address in the CIS. */ + tuple.DesiredTuple = 0x88; + if (pcmcia_get_first_tuple(handle, &tuple) == CS_SUCCESS) { + pcmcia_get_tuple_data(handle, &tuple); + for (i = 0; i < 3; i++) + phys_addr[i] = htons(buf[i]); + } else { + for (i = 0; i < 3; i++) + phys_addr[i] = htons(read_eeprom(ioaddr, i)); + if (phys_addr[0] == 0x6060) { + printk(KERN_ERR "3c589_cs: IO port conflict at 0x%03lx" + "-0x%03lx\n", dev->base_addr, dev->base_addr+15); + goto failed; + } + } + + /* The address and resource configuration register aren't loaded from + the EEPROM and *must* be set to 0 and IRQ3 for the PCMCIA version. */ + outw(0x3f00, ioaddr + 8); + fifo = inl(ioaddr); + + /* The if_port symbol can be set when the module is loaded */ + if ((if_port >= 0) && (if_port <= 3)) + dev->if_port = if_port; + else + printk(KERN_ERR "3c589_cs: invalid if_port requested\n"); + + link->dev = &lp->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + if (register_netdev(dev) != 0) { + printk(KERN_ERR "3c589_cs: register_netdev() failed\n"); + link->dev = NULL; + goto failed; + } + + strcpy(lp->node.dev_name, dev->name); + + printk(KERN_INFO "%s: 3Com 3c%s, io %#3lx, irq %d, hw_addr ", + dev->name, (multi ? "562" : "589"), dev->base_addr, + dev->irq); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + printk(KERN_INFO " %dK FIFO split %s Rx:Tx, %s xcvr\n", + (fifo & 7) ? 32 : 8, ram_split[(fifo >> 16) & 3], + if_names[dev->if_port]); + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + tc589_release(link); + return; + +} /* tc589_config */ + +/*====================================================================== + + After a card is removed, tc589_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void tc589_release(dev_link_t *link) +{ + DEBUG(0, "3c589_release(0x%p)\n", link); + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + +======================================================================*/ + +static int tc589_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct net_device *dev = link->priv; + + DEBUG(1, "3c589_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + tc589_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) + netif_device_detach(dev); + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + tc589_reset(dev); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* tc589_event */ + +/*====================================================================*/ + +/* + Use this for commands that may take time to finish +*/ +static void tc589_wait_for_completion(struct net_device *dev, int cmd) +{ + int i = 100; + outw(cmd, dev->base_addr + EL3_CMD); + while (--i > 0) + if (!(inw(dev->base_addr + EL3_STATUS) & 0x1000)) break; + if (i == 0) + printk(KERN_WARNING "%s: command 0x%04x did not complete!\n", + dev->name, cmd); +} + +/* + Read a word from the EEPROM using the regular EEPROM access register. + Assume that we are in register window zero. +*/ +static u16 read_eeprom(kio_addr_t ioaddr, int index) +{ + int i; + outw(EEPROM_READ + index, ioaddr + 10); + /* Reading the eeprom takes 162 us */ + for (i = 1620; i >= 0; i--) + if ((inw(ioaddr + 10) & EEPROM_BUSY) == 0) + break; + return inw(ioaddr + 12); +} + +/* + Set transceiver type, perhaps to something other than what the user + specified in dev->if_port. +*/ +static void tc589_set_xcvr(struct net_device *dev, int if_port) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + + EL3WINDOW(0); + switch (if_port) { + case 0: case 1: outw(0, ioaddr + 6); break; + case 2: outw(3<<14, ioaddr + 6); break; + case 3: outw(1<<14, ioaddr + 6); break; + } + /* On PCMCIA, this just turns on the LED */ + outw((if_port == 2) ? StartCoax : StopCoax, ioaddr + EL3_CMD); + /* 10baseT interface, enable link beat and jabber check. */ + EL3WINDOW(4); + outw(MEDIA_LED | ((if_port < 2) ? MEDIA_TP : 0), ioaddr + WN4_MEDIA); + EL3WINDOW(1); + if (if_port == 2) + lp->media_status = ((dev->if_port == 0) ? 0x8000 : 0x4000); + else + lp->media_status = ((dev->if_port == 0) ? 0x4010 : 0x8800); +} + +static void dump_status(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + EL3WINDOW(1); + printk(KERN_INFO " irq status %04x, rx status %04x, tx status " + "%02x tx free %04x\n", inw(ioaddr+EL3_STATUS), + inw(ioaddr+RX_STATUS), inb(ioaddr+TX_STATUS), + inw(ioaddr+TX_FREE)); + EL3WINDOW(4); + printk(KERN_INFO " diagnostics: fifo %04x net %04x ethernet %04x" + " media %04x\n", inw(ioaddr+0x04), inw(ioaddr+0x06), + inw(ioaddr+0x08), inw(ioaddr+0x0a)); + EL3WINDOW(1); +} + +/* Reset and restore all of the 3c589 registers. */ +static void tc589_reset(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + int i; + + EL3WINDOW(0); + outw(0x0001, ioaddr + 4); /* Activate board. */ + outw(0x3f00, ioaddr + 8); /* Set the IRQ line. */ + + /* Set the station address in window 2. */ + EL3WINDOW(2); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + i); + + tc589_set_xcvr(dev, dev->if_port); + + /* Switch to the stats window, and clear all stats by reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + EL3WINDOW(6); + for (i = 0; i < 9; i++) + inb(ioaddr+i); + inw(ioaddr + 10); + inw(ioaddr + 12); + + /* Switch to register set 1 for normal use. */ + EL3WINDOW(1); + + /* Accept b-cast and phys addr only. */ + outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); + outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ + outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ + /* Allow status bits to be seen. */ + outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); + /* Ack all pending events, and set active indicator mask. */ + outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, + ioaddr + EL3_CMD); + outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull + | AdapterFailure, ioaddr + EL3_CMD); +} + +static void netdev_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, DRV_NAME); + strcpy(info->version, DRV_VERSION); + sprintf(info->bus_info, "PCMCIA 0x%lx", dev->base_addr); +} + +#ifdef PCMCIA_DEBUG +static u32 netdev_get_msglevel(struct net_device *dev) +{ + return pc_debug; +} + +static void netdev_set_msglevel(struct net_device *dev, u32 level) +{ + pc_debug = level; +} +#endif /* PCMCIA_DEBUG */ + +static struct ethtool_ops netdev_ethtool_ops = { + .get_drvinfo = netdev_get_drvinfo, +#ifdef PCMCIA_DEBUG + .get_msglevel = netdev_get_msglevel, + .set_msglevel = netdev_set_msglevel, +#endif /* PCMCIA_DEBUG */ +}; + +static int el3_config(struct net_device *dev, struct ifmap *map) +{ + if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { + if (map->port <= 3) { + dev->if_port = map->port; + printk(KERN_INFO "%s: switched to %s port\n", + dev->name, if_names[dev->if_port]); + tc589_set_xcvr(dev, dev->if_port); + } else + return -EINVAL; + } + return 0; +} + +static int el3_open(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + netif_start_queue(dev); + + tc589_reset(dev); + init_timer(&lp->media); + lp->media.function = &media_check; + lp->media.data = (unsigned long) dev; + lp->media.expires = jiffies + HZ; + add_timer(&lp->media); + + DEBUG(1, "%s: opened, status %4.4x.\n", + dev->name, inw(dev->base_addr + EL3_STATUS)); + + return 0; +} + +static void el3_tx_timeout(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + + printk(KERN_WARNING "%s: Transmit timed out!\n", dev->name); + dump_status(dev); + lp->stats.tx_errors++; + dev->trans_start = jiffies; + /* Issue TX_RESET and TX_START commands. */ + tc589_wait_for_completion(dev, TxReset); + outw(TxEnable, ioaddr + EL3_CMD); + netif_wake_queue(dev); +} + +static void pop_tx_status(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + int i; + + /* Clear the Tx status stack. */ + for (i = 32; i > 0; i--) { + u_char tx_status = inb(ioaddr + TX_STATUS); + if (!(tx_status & 0x84)) break; + /* reset transmitter on jabber error or underrun */ + if (tx_status & 0x30) + tc589_wait_for_completion(dev, TxReset); + if (tx_status & 0x38) { + DEBUG(1, "%s: transmit error: status 0x%02x\n", + dev->name, tx_status); + outw(TxEnable, ioaddr + EL3_CMD); + lp->stats.tx_aborted_errors++; + } + outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ + } +} + +static int el3_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + struct el3_private *priv = netdev_priv(dev); + + DEBUG(3, "%s: el3_start_xmit(length = %ld) called, " + "status %4.4x.\n", dev->name, (long)skb->len, + inw(ioaddr + EL3_STATUS)); + + priv->stats.tx_bytes += skb->len; + + /* Put out the doubleword header... */ + outw(skb->len, ioaddr + TX_FIFO); + outw(0x00, ioaddr + TX_FIFO); + /* ... and the packet rounded to a doubleword. */ + outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); + + dev->trans_start = jiffies; + if (inw(ioaddr + TX_FREE) <= 1536) { + netif_stop_queue(dev); + /* Interrupt us when the FIFO has room for max-sized packet. */ + outw(SetTxThreshold + 1536, ioaddr + EL3_CMD); + } + + dev_kfree_skb(skb); + pop_tx_status(dev); + + return 0; +} + +/* The EL3 interrupt handler. */ +static irqreturn_t el3_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *) dev_id; + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr; + __u16 status; + int i = 0, handled = 1; + + if (!netif_device_present(dev)) + return IRQ_NONE; + + ioaddr = dev->base_addr; + + DEBUG(3, "%s: interrupt, status %4.4x.\n", + dev->name, inw(ioaddr + EL3_STATUS)); + + spin_lock(&lp->lock); + while ((status = inw(ioaddr + EL3_STATUS)) & + (IntLatch | RxComplete | StatsFull)) { + if ((status & 0xe000) != 0x2000) { + DEBUG(1, "%s: interrupt from dead card\n", dev->name); + handled = 0; + break; + } + + if (status & RxComplete) + el3_rx(dev); + + if (status & TxAvailable) { + DEBUG(3, " TX room bit was handled.\n"); + /* There's room in the FIFO for a full-sized packet. */ + outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); + netif_wake_queue(dev); + } + + if (status & TxComplete) + pop_tx_status(dev); + + if (status & (AdapterFailure | RxEarly | StatsFull)) { + /* Handle all uncommon interrupts. */ + if (status & StatsFull) /* Empty statistics. */ + update_stats(dev); + if (status & RxEarly) { /* Rx early is unused. */ + el3_rx(dev); + outw(AckIntr | RxEarly, ioaddr + EL3_CMD); + } + if (status & AdapterFailure) { + u16 fifo_diag; + EL3WINDOW(4); + fifo_diag = inw(ioaddr + 4); + EL3WINDOW(1); + printk(KERN_WARNING "%s: adapter failure, FIFO diagnostic" + " register %04x.\n", dev->name, fifo_diag); + if (fifo_diag & 0x0400) { + /* Tx overrun */ + tc589_wait_for_completion(dev, TxReset); + outw(TxEnable, ioaddr + EL3_CMD); + } + if (fifo_diag & 0x2000) { + /* Rx underrun */ + tc589_wait_for_completion(dev, RxReset); + set_multicast_list(dev); + outw(RxEnable, ioaddr + EL3_CMD); + } + outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); + } + } + + if (++i > 10) { + printk(KERN_ERR "%s: infinite loop in interrupt, " + "status %4.4x.\n", dev->name, status); + /* Clear all interrupts */ + outw(AckIntr | 0xFF, ioaddr + EL3_CMD); + break; + } + /* Acknowledge the IRQ. */ + outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); + } + + lp->last_irq = jiffies; + spin_unlock(&lp->lock); + DEBUG(3, "%s: exiting interrupt, status %4.4x.\n", + dev->name, inw(ioaddr + EL3_STATUS)); + return IRQ_RETVAL(handled); +} + +static void media_check(unsigned long arg) +{ + struct net_device *dev = (struct net_device *)(arg); + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + u16 media, errs; + unsigned long flags; + + if (!netif_device_present(dev)) goto reschedule; + + EL3WINDOW(1); + /* Check for pending interrupt with expired latency timer: with + this, we can limp along even if the interrupt is blocked */ + if ((inw(ioaddr + EL3_STATUS) & IntLatch) && + (inb(ioaddr + EL3_TIMER) == 0xff)) { + if (!lp->fast_poll) + printk(KERN_WARNING "%s: interrupt(s) dropped!\n", dev->name); + el3_interrupt(dev->irq, lp, NULL); + lp->fast_poll = HZ; + } + if (lp->fast_poll) { + lp->fast_poll--; + lp->media.expires = jiffies + HZ/100; + add_timer(&lp->media); + return; + } + + /* lp->lock guards the EL3 window. Window should always be 1 except + when the lock is held */ + spin_lock_irqsave(&lp->lock, flags); + EL3WINDOW(4); + media = inw(ioaddr+WN4_MEDIA) & 0xc810; + + /* Ignore collisions unless we've had no irq's recently */ + if (jiffies - lp->last_irq < HZ) { + media &= ~0x0010; + } else { + /* Try harder to detect carrier errors */ + EL3WINDOW(6); + outw(StatsDisable, ioaddr + EL3_CMD); + errs = inb(ioaddr + 0); + outw(StatsEnable, ioaddr + EL3_CMD); + lp->stats.tx_carrier_errors += errs; + if (errs || (lp->media_status & 0x0010)) media |= 0x0010; + } + + if (media != lp->media_status) { + if ((media & lp->media_status & 0x8000) && + ((lp->media_status ^ media) & 0x0800)) + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (lp->media_status & 0x0800 ? "lost" : "found")); + else if ((media & lp->media_status & 0x4000) && + ((lp->media_status ^ media) & 0x0010)) + printk(KERN_INFO "%s: coax cable %s\n", dev->name, + (lp->media_status & 0x0010 ? "ok" : "problem")); + if (dev->if_port == 0) { + if (media & 0x8000) { + if (media & 0x0800) + printk(KERN_INFO "%s: flipped to 10baseT\n", + dev->name); + else + tc589_set_xcvr(dev, 2); + } else if (media & 0x4000) { + if (media & 0x0010) + tc589_set_xcvr(dev, 1); + else + printk(KERN_INFO "%s: flipped to 10base2\n", + dev->name); + } + } + lp->media_status = media; + } + + EL3WINDOW(1); + spin_unlock_irqrestore(&lp->lock, flags); + +reschedule: + lp->media.expires = jiffies + HZ; + add_timer(&lp->media); +} + +static struct net_device_stats *el3_get_stats(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + unsigned long flags; + dev_link_t *link = &lp->link; + + if (DEV_OK(link)) { + spin_lock_irqsave(&lp->lock, flags); + update_stats(dev); + spin_unlock_irqrestore(&lp->lock, flags); + } + return &lp->stats; +} + +/* + Update statistics. We change to register window 6, so this should be run + single-threaded if the device is active. This is expected to be a rare + operation, and it's simpler for the rest of the driver to assume that + window 1 is always valid rather than use a special window-state variable. + + Caller must hold the lock for this +*/ +static void update_stats(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + + DEBUG(2, "%s: updating the statistics.\n", dev->name); + /* Turn off statistics updates while reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + /* Switch to the stats window, and read everything. */ + EL3WINDOW(6); + lp->stats.tx_carrier_errors += inb(ioaddr + 0); + lp->stats.tx_heartbeat_errors += inb(ioaddr + 1); + /* Multiple collisions. */ inb(ioaddr + 2); + lp->stats.collisions += inb(ioaddr + 3); + lp->stats.tx_window_errors += inb(ioaddr + 4); + lp->stats.rx_fifo_errors += inb(ioaddr + 5); + lp->stats.tx_packets += inb(ioaddr + 6); + /* Rx packets */ inb(ioaddr + 7); + /* Tx deferrals */ inb(ioaddr + 8); + /* Rx octets */ inw(ioaddr + 10); + /* Tx octets */ inw(ioaddr + 12); + + /* Back to window 1, and turn statistics back on. */ + EL3WINDOW(1); + outw(StatsEnable, ioaddr + EL3_CMD); +} + +static int el3_rx(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + int worklimit = 32; + short rx_status; + + DEBUG(3, "%s: in rx_packet(), status %4.4x, rx_status %4.4x.\n", + dev->name, inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS)); + while (!((rx_status = inw(ioaddr + RX_STATUS)) & 0x8000) && + (--worklimit >= 0)) { + if (rx_status & 0x4000) { /* Error, update stats. */ + short error = rx_status & 0x3800; + lp->stats.rx_errors++; + switch (error) { + case 0x0000: lp->stats.rx_over_errors++; break; + case 0x0800: lp->stats.rx_length_errors++; break; + case 0x1000: lp->stats.rx_frame_errors++; break; + case 0x1800: lp->stats.rx_length_errors++; break; + case 0x2000: lp->stats.rx_frame_errors++; break; + case 0x2800: lp->stats.rx_crc_errors++; break; + } + } else { + short pkt_len = rx_status & 0x7ff; + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len+5); + + DEBUG(3, " Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + if (skb != NULL) { + skb->dev = dev; + skb_reserve(skb, 2); + insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len), + (pkt_len+3)>>2); + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + dev->last_rx = jiffies; + lp->stats.rx_packets++; + lp->stats.rx_bytes += pkt_len; + } else { + DEBUG(1, "%s: couldn't allocate a sk_buff of" + " size %d.\n", dev->name, pkt_len); + lp->stats.rx_dropped++; + } + } + /* Pop the top of the Rx FIFO */ + tc589_wait_for_completion(dev, RxDiscard); + } + if (worklimit == 0) + printk(KERN_WARNING "%s: too much work in el3_rx!\n", dev->name); + return 0; +} + +static void set_multicast_list(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + kio_addr_t ioaddr = dev->base_addr; + u16 opts = SetRxFilter | RxStation | RxBroadcast; + + if (!(DEV_OK(link))) return; + if (dev->flags & IFF_PROMISC) + opts |= RxMulticast | RxProm; + else if (dev->mc_count || (dev->flags & IFF_ALLMULTI)) + opts |= RxMulticast; + outw(opts, ioaddr + EL3_CMD); +} + +static int el3_close(struct net_device *dev) +{ + struct el3_private *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + kio_addr_t ioaddr = dev->base_addr; + + DEBUG(1, "%s: shutting down ethercard.\n", dev->name); + + if (DEV_OK(link)) { + /* Turn off statistics ASAP. We update lp->stats below. */ + outw(StatsDisable, ioaddr + EL3_CMD); + + /* Disable the receiver and transmitter. */ + outw(RxDisable, ioaddr + EL3_CMD); + outw(TxDisable, ioaddr + EL3_CMD); + + if (dev->if_port == 2) + /* Turn off thinnet power. Green! */ + outw(StopCoax, ioaddr + EL3_CMD); + else if (dev->if_port == 1) { + /* Disable link beat and jabber */ + EL3WINDOW(4); + outw(0, ioaddr + WN4_MEDIA); + } + + /* Switching back to window 0 disables the IRQ. */ + EL3WINDOW(0); + /* But we explicitly zero the IRQ line select anyway. */ + outw(0x0f00, ioaddr + WN0_IRQ); + + /* Check if the card still exists */ + if ((inw(ioaddr+EL3_STATUS) & 0xe000) == 0x2000) + update_stats(dev); + } + + link->open--; + netif_stop_queue(dev); + del_timer_sync(&lp->media); + + return 0; +} + +static struct pcmcia_driver tc589_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "3c589_cs", + }, + .attach = tc589_attach, + .detach = tc589_detach, +}; + +static int __init init_tc589(void) +{ + return pcmcia_register_driver(&tc589_driver); +} + +static void __exit exit_tc589(void) +{ + pcmcia_unregister_driver(&tc589_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_tc589); +module_exit(exit_tc589); diff --git a/drivers/net/pcmcia/Kconfig b/drivers/net/pcmcia/Kconfig new file mode 100644 index 00000000000..74f86200124 --- /dev/null +++ b/drivers/net/pcmcia/Kconfig @@ -0,0 +1,132 @@ +# +# PCMCIA Network device configuration +# + +menu "PCMCIA network device support" + depends on NETDEVICES && PCMCIA!=n + +config NET_PCMCIA + bool "PCMCIA network device support" + ---help--- + Say Y if you would like to include support for any PCMCIA or CardBus + network adapters, then say Y to the driver for your particular card + below. PCMCIA- or PC-cards are credit-card size devices often used + with laptops computers; CardBus is the newer and faster version of + PCMCIA. + + To use your PC-cards, you will need supporting software from David + Hinds' pcmcia-cs package (see the file <file:Documentation/Changes> + for location). You also want to check out the PCMCIA-HOWTO, + available from <http://www.tldp.org/docs.html#howto>. + + If unsure, say N. + +config PCMCIA_3C589 + tristate "3Com 3c589 PCMCIA support" + depends on NET_PCMCIA && PCMCIA + help + Say Y here if you intend to attach a 3Com 3c589 or compatible PCMCIA + (PC-card) Ethernet card to your computer. + + To compile this driver as a module, choose M here: the module will be + called 3c589_cs. If unsure, say N. + +config PCMCIA_3C574 + tristate "3Com 3c574 PCMCIA support" + depends on NET_PCMCIA && PCMCIA + help + Say Y here if you intend to attach a 3Com 3c574 or compatible PCMCIA + (PC-card) Fast Ethernet card to your computer. + + To compile this driver as a module, choose M here: the module will be + called 3c574_cs. If unsure, say N. + +config PCMCIA_FMVJ18X + tristate "Fujitsu FMV-J18x PCMCIA support" + depends on NET_PCMCIA && PCMCIA + select CRC32 + help + Say Y here if you intend to attach a Fujitsu FMV-J18x or compatible + PCMCIA (PC-card) Ethernet card to your computer. + + To compile this driver as a module, choose M here: the module will be + called fmvj18x_cs. If unsure, say N. + +config PCMCIA_PCNET + tristate "NE2000 compatible PCMCIA support" + depends on NET_PCMCIA && PCMCIA + select CRC32 + help + Say Y here if you intend to attach an NE2000 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 pcnet_cs. If unsure, say N. + +config PCMCIA_NMCLAN + tristate "New Media PCMCIA support" + depends on NET_PCMCIA && PCMCIA + help + Say Y here if you intend to attach a New Media Ethernet or LiveWire + PCMCIA (PC-card) Ethernet card to your computer. + + To compile this driver as a module, choose M here: the module will be + called nmclan_cs. If unsure, say N. + +config PCMCIA_SMC91C92 + tristate "SMC 91Cxx PCMCIA support" + depends on NET_PCMCIA && 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 PCMCIA_XIRC2PS + tristate "Xircom 16-bit PCMCIA support" + depends on NET_PCMCIA && PCMCIA + help + Say Y here if you intend to attach a Xircom 16-bit 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 xirc2ps_cs. If unsure, say N. + +config PCMCIA_AXNET + tristate "Asix AX88190 PCMCIA support" + depends on NET_PCMCIA && PCMCIA + ---help--- + Say Y here if you intend to attach an Asix AX88190-based PCMCIA + (PC-card) Fast Ethernet card to your computer. These cards are + nearly NE2000 compatible but need a separate driver due to a few + misfeatures. + + To compile this driver as a module, choose M here: the module will be + called axnet_cs. If unsure, say N. + +config ARCNET_COM20020_CS + tristate "COM20020 ARCnet PCMCIA support" + depends on NET_PCMCIA && ARCNET_COM20020 && PCMCIA + help + Say Y here if you intend to attach this type of ARCnet PCMCIA card + to your computer. + + To compile this driver as a module, choose M here: the module will be + called com20020_cs. If unsure, say N. + +config PCMCIA_IBMTR + tristate "IBM PCMCIA tokenring adapter support" + depends on NET_PCMCIA && IBMTR!=y && TR && PCMCIA && !64BIT + help + Say Y here if you intend to attach this type of Token Ring PCMCIA + card to your computer. You then also need to say Y to "Token Ring + driver support". + + To compile this driver as a module, choose M here: the module will be + called ibmtr_cs. + +endmenu + diff --git a/drivers/net/pcmcia/Makefile b/drivers/net/pcmcia/Makefile new file mode 100644 index 00000000000..87d2d99f4c1 --- /dev/null +++ b/drivers/net/pcmcia/Makefile @@ -0,0 +1,16 @@ +# +# Makefile for the Linux PCMCIA network device drivers. +# + +# 16-bit client drivers +obj-$(CONFIG_PCMCIA_3C589) += 3c589_cs.o +obj-$(CONFIG_PCMCIA_3C574) += 3c574_cs.o +obj-$(CONFIG_PCMCIA_FMVJ18X) += fmvj18x_cs.o +obj-$(CONFIG_PCMCIA_NMCLAN) += nmclan_cs.o +obj-$(CONFIG_PCMCIA_PCNET) += pcnet_cs.o +obj-$(CONFIG_PCMCIA_SMC91C92) += smc91c92_cs.o +obj-$(CONFIG_PCMCIA_XIRC2PS) += xirc2ps_cs.o +obj-$(CONFIG_ARCNET_COM20020_CS)+= com20020_cs.o +obj-$(CONFIG_PCMCIA_AXNET) += axnet_cs.o + +obj-$(CONFIG_PCMCIA_IBMTR) += ibmtr_cs.o diff --git a/drivers/net/pcmcia/axnet_cs.c b/drivers/net/pcmcia/axnet_cs.c new file mode 100644 index 00000000000..853b586e481 --- /dev/null +++ b/drivers/net/pcmcia/axnet_cs.c @@ -0,0 +1,1864 @@ +/*====================================================================== + + A PCMCIA ethernet driver for Asix AX88190-based cards + + The Asix AX88190 is a NS8390-derived chipset with a few nasty + idiosyncracies that make it very inconvenient to support with a + standard 8390 driver. This driver is based on pcnet_cs, with the + tweaked 8390 code grafted on the end. Much of what I did was to + clean up and update a similar driver supplied by Asix, which was + adapted by William Lee, william@asix.com.tw. + + Copyright (C) 2001 David A. Hinds -- dahinds@users.sourceforge.net + + axnet_cs.c 1.28 2002/06/29 06:27:37 + + The network driver code is based on Donald Becker's NE2000 code: + + Written 1992,1993 by Donald Becker. + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU General Public License, + incorporated herein by reference. + Donald Becker may be reached at becker@scyld.com + +======================================================================*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/ethtool.h> +#include <linux/netdevice.h> +#include "../8390.h" + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <asm/io.h> +#include <asm/system.h> +#include <asm/byteorder.h> +#include <asm/uaccess.h> + +#define AXNET_CMD 0x00 +#define AXNET_DATAPORT 0x10 /* NatSemi-defined port window offset. */ +#define AXNET_RESET 0x1f /* Issue a read to reset, a write to clear. */ +#define AXNET_MII_EEP 0x14 /* Offset of MII access port */ +#define AXNET_TEST 0x15 /* Offset of TEST Register port */ +#define AXNET_GPIO 0x17 /* Offset of General Purpose Register Port */ + +#define AXNET_START_PG 0x40 /* First page of TX buffer */ +#define AXNET_STOP_PG 0x80 /* Last page +1 of RX ring */ + +#define AXNET_RDC_TIMEOUT 0x02 /* Max wait in jiffies for Tx RDC */ + +#define IS_AX88190 0x0001 +#define IS_AX88790 0x0002 + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("Asix AX88190 PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#ifdef PCMCIA_DEBUG +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"axnet_cs.c 1.28 2002/06/29 06:27:37 (David Hinds)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +static void axnet_config(dev_link_t *link); +static void axnet_release(dev_link_t *link); +static int axnet_event(event_t event, int priority, + event_callback_args_t *args); +static int axnet_open(struct net_device *dev); +static int axnet_close(struct net_device *dev); +static int axnet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static struct ethtool_ops netdev_ethtool_ops; +static irqreturn_t ei_irq_wrapper(int irq, void *dev_id, struct pt_regs *regs); +static void ei_watchdog(u_long arg); +static void axnet_reset_8390(struct net_device *dev); + +static int mdio_read(kio_addr_t addr, int phy_id, int loc); +static void mdio_write(kio_addr_t addr, int phy_id, int loc, int value); + +static void get_8390_hdr(struct net_device *, + struct e8390_pkt_hdr *, int); +static void block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void block_output(struct net_device *dev, int count, + const u_char *buf, const int start_page); + +static dev_link_t *axnet_attach(void); +static void axnet_detach(dev_link_t *); + +static dev_info_t dev_info = "axnet_cs"; +static dev_link_t *dev_list; + +static void axdev_setup(struct net_device *dev); +static void AX88190_init(struct net_device *dev, int startp); +static int ax_open(struct net_device *dev); +static int ax_close(struct net_device *dev); +static irqreturn_t ax_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +/*====================================================================*/ + +typedef struct axnet_dev_t { + dev_link_t link; + dev_node_t node; + caddr_t base; + struct timer_list watchdog; + int stale, fast_poll; + u_short link_status; + u_char duplex_flag; + int phy_id; + int flags; +} axnet_dev_t; + +static inline axnet_dev_t *PRIV(struct net_device *dev) +{ + void *p = (char *)netdev_priv(dev) + sizeof(struct ei_device); + return p; +} + +/*====================================================================== + + axnet_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *axnet_attach(void) +{ + axnet_dev_t *info; + dev_link_t *link; + struct net_device *dev; + client_reg_t client_reg; + int ret; + + DEBUG(0, "axnet_attach()\n"); + + dev = alloc_netdev(sizeof(struct ei_device) + sizeof(axnet_dev_t), + "eth%d", axdev_setup); + + if (!dev) + return NULL; + + info = PRIV(dev); + link = &info->link; + link->priv = dev; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.IntType = INT_MEMORY_AND_IO; + + dev->open = &axnet_open; + dev->stop = &axnet_close; + dev->do_ioctl = &axnet_ioctl; + SET_ETHTOOL_OPS(dev, &netdev_ethtool_ops); + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &axnet_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + axnet_detach(link); + return NULL; + } + + return link; +} /* axnet_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void axnet_detach(dev_link_t *link) +{ + struct net_device *dev = link->priv; + dev_link_t **linkp; + + DEBUG(0, "axnet_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->dev) + unregister_netdev(dev); + + if (link->state & DEV_CONFIG) + axnet_release(link); + + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + free_netdev(dev); +} /* axnet_detach */ + +/*====================================================================== + + This probes for a card's hardware address by reading the PROM. + +======================================================================*/ + +static int get_prom(dev_link_t *link) +{ + struct net_device *dev = link->priv; + kio_addr_t ioaddr = dev->base_addr; + int i, j; + + /* This is based on drivers/net/ne.c */ + struct { + u_char value, offset; + } program_seq[] = { + {E8390_NODMA+E8390_PAGE0+E8390_STOP, E8390_CMD}, /* Select page 0*/ + {0x01, EN0_DCFG}, /* Set word-wide access. */ + {0x00, EN0_RCNTLO}, /* Clear the count regs. */ + {0x00, EN0_RCNTHI}, + {0x00, EN0_IMR}, /* Mask completion irq. */ + {0xFF, EN0_ISR}, + {E8390_RXOFF|0x40, EN0_RXCR}, /* 0x60 Set to monitor */ + {E8390_TXOFF, EN0_TXCR}, /* 0x02 and loopback mode. */ + {0x10, EN0_RCNTLO}, + {0x00, EN0_RCNTHI}, + {0x00, EN0_RSARLO}, /* DMA starting at 0x0400. */ + {0x04, EN0_RSARHI}, + {E8390_RREAD+E8390_START, E8390_CMD}, + }; + + /* Not much of a test, but the alternatives are messy */ + if (link->conf.ConfigBase != 0x03c0) + return 0; + + axnet_reset_8390(dev); + mdelay(10); + + for (i = 0; i < sizeof(program_seq)/sizeof(program_seq[0]); i++) + outb_p(program_seq[i].value, ioaddr + program_seq[i].offset); + + for (i = 0; i < 6; i += 2) { + j = inw(ioaddr + AXNET_DATAPORT); + dev->dev_addr[i] = j & 0xff; + dev->dev_addr[i+1] = j >> 8; + } + return 1; +} /* get_prom */ + +/*====================================================================== + + axnet_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + ethernet device available to the system. + +======================================================================*/ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static int try_io_port(dev_link_t *link) +{ + int j, ret; + if (link->io.NumPorts1 == 32) { + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if (link->io.NumPorts2 > 0) { + /* for master/slave multifunction cards */ + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; + } + } else { + /* This should be two 16-port windows */ + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_16; + } + if (link->io.BasePort1 == 0) { + link->io.IOAddrLines = 16; + for (j = 0; j < 0x400; j += 0x20) { + link->io.BasePort1 = j ^ 0x300; + link->io.BasePort2 = (j ^ 0x300) + 0x10; + ret = pcmcia_request_io(link->handle, &link->io); + if (ret == CS_SUCCESS) return ret; + } + return ret; + } else { + return pcmcia_request_io(link->handle, &link->io); + } +} + +static void axnet_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + axnet_dev_t *info = PRIV(dev); + tuple_t tuple; + cisparse_t parse; + int i, j, last_ret, last_fn; + u_short buf[64]; + config_info_t conf; + + DEBUG(0, "axnet_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + /* don't trust the CIS on this; Linksys got it wrong */ + link->conf.Present = 0x63; + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* Look up current Vcc */ + CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(handle, &conf)); + link->conf.Vcc = conf.Vcc; + + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + tuple.Attributes = 0; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + while (last_ret == CS_SUCCESS) { + cistpl_cftable_entry_t *cfg = &(parse.cftable_entry); + cistpl_io_t *io = &(parse.cftable_entry.io); + + if (pcmcia_get_tuple_data(handle, &tuple) != 0 || + pcmcia_parse_tuple(handle, &tuple, &parse) != 0 || + cfg->index == 0 || cfg->io.nwin == 0) + goto next_entry; + + link->conf.ConfigIndex = 0x05; + /* For multifunction cards, by convention, we configure the + network function with window 0, and serial with window 1 */ + if (io->nwin > 1) { + i = (io->win[1].len > io->win[0].len); + link->io.BasePort2 = io->win[1-i].base; + link->io.NumPorts2 = io->win[1-i].len; + } else { + i = link->io.NumPorts2 = 0; + } + link->io.BasePort1 = io->win[i].base; + link->io.NumPorts1 = io->win[i].len; + link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; + if (link->io.NumPorts1 + link->io.NumPorts2 >= 32) { + last_ret = try_io_port(link); + if (last_ret == CS_SUCCESS) break; + } + next_entry: + last_ret = pcmcia_get_next_tuple(handle, &tuple); + } + if (last_ret != CS_SUCCESS) { + cs_error(handle, RequestIO, last_ret); + goto failed; + } + + CS_CHECK(RequestIRQ, pcmcia_request_irq(handle, &link->irq)); + + if (link->io.NumPorts2 == 8) { + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + } + + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(handle, &link->conf)); + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + + if (!get_prom(link)) { + printk(KERN_NOTICE "axnet_cs: this is not an AX88190 card!\n"); + printk(KERN_NOTICE "axnet_cs: use pcnet_cs instead.\n"); + goto failed; + } + + ei_status.name = "AX88190"; + ei_status.word16 = 1; + ei_status.tx_start_page = AXNET_START_PG; + ei_status.rx_start_page = AXNET_START_PG + TX_PAGES; + ei_status.stop_page = AXNET_STOP_PG; + ei_status.reset_8390 = &axnet_reset_8390; + ei_status.get_8390_hdr = &get_8390_hdr; + ei_status.block_input = &block_input; + ei_status.block_output = &block_output; + + if (inb(dev->base_addr + AXNET_TEST) != 0) + info->flags |= IS_AX88790; + else + info->flags |= IS_AX88190; + + if (info->flags & IS_AX88790) + outb(0x10, dev->base_addr + AXNET_GPIO); /* select Internal PHY */ + + for (i = 0; i < 32; i++) { + j = mdio_read(dev->base_addr + AXNET_MII_EEP, i, 1); + if ((j != 0) && (j != 0xffff)) break; + } + + /* Maybe PHY is in power down mode. (PPD_SET = 1) + Bit 2 of CCSR is active low. */ + if (i == 32) { + conf_reg_t reg = { 0, CS_WRITE, CISREG_CCSR, 0x04 }; + pcmcia_access_configuration_register(link->handle, ®); + for (i = 0; i < 32; i++) { + j = mdio_read(dev->base_addr + AXNET_MII_EEP, i, 1); + if ((j != 0) && (j != 0xffff)) break; + } + } + + info->phy_id = (i < 32) ? i : -1; + link->dev = &info->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + if (register_netdev(dev) != 0) { + printk(KERN_NOTICE "axnet_cs: register_netdev() failed\n"); + link->dev = NULL; + goto failed; + } + + strcpy(info->node.dev_name, dev->name); + + printk(KERN_INFO "%s: Asix AX88%d90: io %#3lx, irq %d, hw_addr ", + dev->name, ((info->flags & IS_AX88790) ? 7 : 1), + dev->base_addr, dev->irq); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + if (info->phy_id != -1) { + DEBUG(0, " MII transceiver at index %d, status %x.\n", info->phy_id, j); + } else { + printk(KERN_NOTICE " No MII transceivers found!\n"); + } + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + axnet_release(link); + link->state &= ~DEV_CONFIG_PENDING; + return; +} /* axnet_config */ + +/*====================================================================== + + After a card is removed, axnet_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void axnet_release(dev_link_t *link) +{ + DEBUG(0, "axnet_release(0x%p)\n", link); + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + +======================================================================*/ + +static int axnet_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct net_device *dev = link->priv; + + DEBUG(2, "axnet_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + axnet_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) + netif_device_detach(dev); + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + axnet_reset_8390(dev); + AX88190_init(dev, 1); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* axnet_event */ + +/*====================================================================== + + MII interface support + +======================================================================*/ + +#define MDIO_SHIFT_CLK 0x01 +#define MDIO_DATA_WRITE0 0x00 +#define MDIO_DATA_WRITE1 0x08 +#define MDIO_DATA_READ 0x04 +#define MDIO_MASK 0x0f +#define MDIO_ENB_IN 0x02 + +static void mdio_sync(kio_addr_t addr) +{ + int bits; + for (bits = 0; bits < 32; bits++) { + outb_p(MDIO_DATA_WRITE1, addr); + outb_p(MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, addr); + } +} + +static int mdio_read(kio_addr_t addr, int phy_id, int loc) +{ + u_int cmd = (0xf6<<10)|(phy_id<<5)|loc; + int i, retval = 0; + + mdio_sync(addr); + for (i = 14; i >= 0; i--) { + int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outb_p(dat, addr); + outb_p(dat | MDIO_SHIFT_CLK, addr); + } + for (i = 19; i > 0; i--) { + outb_p(MDIO_ENB_IN, addr); + retval = (retval << 1) | ((inb_p(addr) & MDIO_DATA_READ) != 0); + outb_p(MDIO_ENB_IN | MDIO_SHIFT_CLK, addr); + } + return (retval>>1) & 0xffff; +} + +static void mdio_write(kio_addr_t addr, int phy_id, int loc, int value) +{ + 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_p(dat, addr); + outb_p(dat | MDIO_SHIFT_CLK, addr); + } + for (i = 1; i >= 0; i--) { + outb_p(MDIO_ENB_IN, addr); + outb_p(MDIO_ENB_IN | MDIO_SHIFT_CLK, addr); + } +} + +/*====================================================================*/ + +static int axnet_open(struct net_device *dev) +{ + axnet_dev_t *info = PRIV(dev); + dev_link_t *link = &info->link; + + DEBUG(2, "axnet_open('%s')\n", dev->name); + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + + request_irq(dev->irq, ei_irq_wrapper, SA_SHIRQ, dev_info, dev); + + info->link_status = 0x00; + init_timer(&info->watchdog); + info->watchdog.function = &ei_watchdog; + info->watchdog.data = (u_long)dev; + info->watchdog.expires = jiffies + HZ; + add_timer(&info->watchdog); + + return ax_open(dev); +} /* axnet_open */ + +/*====================================================================*/ + +static int axnet_close(struct net_device *dev) +{ + axnet_dev_t *info = PRIV(dev); + dev_link_t *link = &info->link; + + DEBUG(2, "axnet_close('%s')\n", dev->name); + + ax_close(dev); + free_irq(dev->irq, dev); + + link->open--; + netif_stop_queue(dev); + del_timer_sync(&info->watchdog); + + return 0; +} /* axnet_close */ + +/*====================================================================== + + Hard reset the card. This used to pause for the same period that + a 8390 reset command required, but that shouldn't be necessary. + +======================================================================*/ + +static void axnet_reset_8390(struct net_device *dev) +{ + kio_addr_t nic_base = dev->base_addr; + int i; + + ei_status.txing = ei_status.dmaing = 0; + + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, nic_base + E8390_CMD); + + outb(inb(nic_base + AXNET_RESET), nic_base + AXNET_RESET); + + for (i = 0; i < 100; i++) { + if ((inb_p(nic_base+EN0_ISR) & ENISR_RESET) != 0) + break; + udelay(100); + } + outb_p(ENISR_RESET, nic_base + EN0_ISR); /* Ack intr. */ + + if (i == 100) + printk(KERN_ERR "%s: axnet_reset_8390() did not complete.\n", + dev->name); + +} /* axnet_reset_8390 */ + +/*====================================================================*/ + +static irqreturn_t ei_irq_wrapper(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = dev_id; + PRIV(dev)->stale = 0; + return ax_interrupt(irq, dev_id, regs); +} + +static void ei_watchdog(u_long arg) +{ + struct net_device *dev = (struct net_device *)(arg); + axnet_dev_t *info = PRIV(dev); + kio_addr_t nic_base = dev->base_addr; + kio_addr_t mii_addr = nic_base + AXNET_MII_EEP; + u_short link; + + if (!netif_device_present(dev)) goto reschedule; + + /* Check for pending interrupt with expired latency timer: with + this, we can limp along even if the interrupt is blocked */ + if (info->stale++ && (inb_p(nic_base + EN0_ISR) & ENISR_ALL)) { + if (!info->fast_poll) + printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); + ei_irq_wrapper(dev->irq, dev, NULL); + info->fast_poll = HZ; + } + if (info->fast_poll) { + info->fast_poll--; + info->watchdog.expires = jiffies + 1; + add_timer(&info->watchdog); + return; + } + + if (info->phy_id < 0) + goto reschedule; + link = mdio_read(mii_addr, info->phy_id, 1); + if (!link || (link == 0xffff)) { + printk(KERN_INFO "%s: MII is missing!\n", dev->name); + info->phy_id = -1; + goto reschedule; + } + + link &= 0x0004; + if (link != info->link_status) { + u_short p = mdio_read(mii_addr, info->phy_id, 5); + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (link) ? "found" : "lost"); + if (link) { + info->duplex_flag = (p & 0x0140) ? 0x80 : 0x00; + if (p) + printk(KERN_INFO "%s: autonegotiation complete: " + "%sbaseT-%cD selected\n", dev->name, + ((p & 0x0180) ? "100" : "10"), + ((p & 0x0140) ? 'F' : 'H')); + else + printk(KERN_INFO "%s: link partner did not autonegotiate\n", + dev->name); + AX88190_init(dev, 1); + } + info->link_status = link; + } + +reschedule: + info->watchdog.expires = jiffies + HZ; + add_timer(&info->watchdog); +} + +static void netdev_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, "axnet_cs"); +} + +static struct ethtool_ops netdev_ethtool_ops = { + .get_drvinfo = netdev_get_drvinfo, +}; + +/*====================================================================*/ + +static int axnet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + axnet_dev_t *info = PRIV(dev); + u16 *data = (u16 *)&rq->ifr_ifru; + kio_addr_t mii_addr = dev->base_addr + AXNET_MII_EEP; + switch (cmd) { + case SIOCGMIIPHY: + data[0] = info->phy_id; + case SIOCGMIIREG: /* Read MII PHY register. */ + data[3] = mdio_read(mii_addr, data[0], data[1] & 0x1f); + return 0; + case SIOCSMIIREG: /* Write MII PHY register. */ + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + mdio_write(mii_addr, data[0], data[1] & 0x1f, data[2]); + return 0; + } + return -EOPNOTSUPP; +} + +/*====================================================================*/ + +static void get_8390_hdr(struct net_device *dev, + struct e8390_pkt_hdr *hdr, + int ring_page) +{ + kio_addr_t nic_base = dev->base_addr; + + outb_p(0, nic_base + EN0_RSARLO); /* On page boundary */ + outb_p(ring_page, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + AXNET_CMD); + + insw(nic_base + AXNET_DATAPORT, hdr, + sizeof(struct e8390_pkt_hdr)>>1); + /* Fix for big endian systems */ + hdr->count = le16_to_cpu(hdr->count); + +} + +/*====================================================================*/ + +static void block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset) +{ + kio_addr_t nic_base = dev->base_addr; + int xfer_count = count; + char *buf = skb->data; + +#ifdef PCMCIA_DEBUG + if ((ei_debug > 4) && (count != 4)) + printk(KERN_DEBUG "%s: [bi=%d]\n", dev->name, count+4); +#endif + outb_p(ring_offset & 0xff, nic_base + EN0_RSARLO); + outb_p(ring_offset >> 8, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + AXNET_CMD); + + insw(nic_base + AXNET_DATAPORT,buf,count>>1); + if (count & 0x01) + buf[count-1] = inb(nic_base + AXNET_DATAPORT), xfer_count++; + +} + +/*====================================================================*/ + +static void block_output(struct net_device *dev, int count, + const u_char *buf, const int start_page) +{ + kio_addr_t nic_base = dev->base_addr; + +#ifdef PCMCIA_DEBUG + if (ei_debug > 4) + printk(KERN_DEBUG "%s: [bo=%d]\n", dev->name, count); +#endif + + /* Round the count up for word writes. Do we need to do this? + What effect will an odd byte count have on the 8390? + I should check someday. */ + if (count & 0x01) + count++; + + outb_p(0x00, nic_base + EN0_RSARLO); + outb_p(start_page, nic_base + EN0_RSARHI); + outb_p(E8390_RWRITE+E8390_START, nic_base + AXNET_CMD); + outsw(nic_base + AXNET_DATAPORT, buf, count>>1); +} + +static struct pcmcia_driver axnet_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "axnet_cs", + }, + .attach = axnet_attach, + .detach = axnet_detach, +}; + +static int __init init_axnet_cs(void) +{ + return pcmcia_register_driver(&axnet_cs_driver); +} + +static void __exit exit_axnet_cs(void) +{ + pcmcia_unregister_driver(&axnet_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_axnet_cs); +module_exit(exit_axnet_cs); + +/*====================================================================*/ + +/* 8390.c: A general NS8390 ethernet driver core for linux. */ +/* + Written 1992-94 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU General Public License, incorporated herein by reference. + + The author may be reached as becker@scyld.com, or C/O + Scyld Computing Corporation + 410 Severn Ave., Suite 210 + Annapolis MD 21403 + + This is the chip-specific code for many 8390-based ethernet adaptors. + This is not a complete driver, it must be combined with board-specific + code such as ne.c, wd.c, 3c503.c, etc. + + Seeing how at least eight drivers use this code, (not counting the + PCMCIA ones either) it is easy to break some card by what seems like + a simple innocent change. Please contact me or Donald if you think + you have found something that needs changing. -- PG + + Changelog: + + Paul Gortmaker : remove set_bit lock, other cleanups. + Paul Gortmaker : add ei_get_8390_hdr() so we can pass skb's to + ei_block_input() for eth_io_copy_and_sum(). + Paul Gortmaker : exchange static int ei_pingpong for a #define, + also add better Tx error handling. + Paul Gortmaker : rewrite Rx overrun handling as per NS specs. + Alexey Kuznetsov : use the 8390's six bit hash multicast filter. + Paul Gortmaker : tweak ANK's above multicast changes a bit. + Paul Gortmaker : update packet statistics for v2.1.x + Alan Cox : support arbitary stupid port mappings on the + 68K Macintosh. Support >16bit I/O spaces + Paul Gortmaker : add kmod support for auto-loading of the 8390 + module by all drivers that require it. + Alan Cox : Spinlocking work, added 'BUG_83C690' + Paul Gortmaker : Separate out Tx timeout code from Tx path. + + Sources: + The National Semiconductor LAN Databook, and the 3Com 3c503 databook. + + */ + +static const char *version_8390 = + "8390.c:v1.10cvs 9/23/94 Donald Becker (becker@scyld.com)\n"; + +#include <linux/bitops.h> +#include <asm/irq.h> +#include <linux/fcntl.h> +#include <linux/in.h> +#include <linux/interrupt.h> + +#include <linux/etherdevice.h> + +#define BUG_83C690 + +/* These are the operational function interfaces to board-specific + routines. + void reset_8390(struct net_device *dev) + Resets the board associated with DEV, including a hardware reset of + the 8390. This is only called when there is a transmit timeout, and + it is always followed by 8390_init(). + void block_output(struct net_device *dev, int count, const unsigned char *buf, + int start_page) + Write the COUNT bytes of BUF to the packet buffer at START_PAGE. The + "page" value uses the 8390's 256-byte pages. + void get_8390_hdr(struct net_device *dev, struct e8390_hdr *hdr, int ring_page) + Read the 4 byte, page aligned 8390 header. *If* there is a + subsequent read, it will be of the rest of the packet. + void block_input(struct net_device *dev, int count, struct sk_buff *skb, int ring_offset) + Read COUNT bytes from the packet buffer into the skb data area. Start + reading from RING_OFFSET, the address as the 8390 sees it. This will always + follow the read of the 8390 header. +*/ +#define ei_reset_8390 (ei_local->reset_8390) +#define ei_block_output (ei_local->block_output) +#define ei_block_input (ei_local->block_input) +#define ei_get_8390_hdr (ei_local->get_8390_hdr) + +/* use 0 for production, 1 for verification, >2 for debug */ +#ifndef ei_debug +int ei_debug = 1; +#endif + +/* Index to functions. */ +static void ei_tx_intr(struct net_device *dev); +static void ei_tx_err(struct net_device *dev); +static void ei_tx_timeout(struct net_device *dev); +static void ei_receive(struct net_device *dev); +static void ei_rx_overrun(struct net_device *dev); + +/* Routines generic to NS8390-based boards. */ +static void NS8390_trigger_send(struct net_device *dev, unsigned int length, + int start_page); +static void set_multicast_list(struct net_device *dev); +static void do_set_multicast_list(struct net_device *dev); + +/* + * SMP and the 8390 setup. + * + * The 8390 isnt exactly designed to be multithreaded on RX/TX. There is + * a page register that controls bank and packet buffer access. We guard + * this with ei_local->page_lock. Nobody should assume or set the page other + * than zero when the lock is not held. Lock holders must restore page 0 + * before unlocking. Even pure readers must take the lock to protect in + * page 0. + * + * To make life difficult the chip can also be very slow. We therefore can't + * just use spinlocks. For the longer lockups we disable the irq the device + * sits on and hold the lock. We must hold the lock because there is a dual + * processor case other than interrupts (get stats/set multicast list in + * parallel with each other and transmit). + * + * Note: in theory we can just disable the irq on the card _but_ there is + * a latency on SMP irq delivery. So we can easily go "disable irq" "sync irqs" + * enter lock, take the queued irq. So we waddle instead of flying. + * + * Finally by special arrangement for the purpose of being generally + * annoying the transmit function is called bh atomic. That places + * restrictions on the user context callers as disable_irq won't save + * them. + */ + +/** + * ax_open - Open/initialize the board. + * @dev: network device to initialize + * + * This routine goes all-out, setting everything + * up anew at each open, even though many of these registers should only + * need to be set once at boot. + */ +static int ax_open(struct net_device *dev) +{ + unsigned long flags; + struct ei_device *ei_local = (struct ei_device *) netdev_priv(dev); + +#ifdef HAVE_TX_TIMEOUT + /* The card I/O part of the driver (e.g. 3c503) can hook a Tx timeout + wrapper that does e.g. media check & then calls ei_tx_timeout. */ + if (dev->tx_timeout == NULL) + dev->tx_timeout = ei_tx_timeout; + if (dev->watchdog_timeo <= 0) + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* + * Grab the page lock so we own the register set, then call + * the init function. + */ + + spin_lock_irqsave(&ei_local->page_lock, flags); + AX88190_init(dev, 1); + /* Set the flag before we drop the lock, That way the IRQ arrives + after its set and we get no silly warnings */ + netif_start_queue(dev); + spin_unlock_irqrestore(&ei_local->page_lock, flags); + ei_local->irqlock = 0; + return 0; +} + +#define dev_lock(dev) (((struct ei_device *)netdev_priv(dev))->page_lock) + +/** + * ax_close - shut down network device + * @dev: network device to close + * + * Opposite of ax_open(). Only used when "ifconfig <devname> down" is done. + */ +int ax_close(struct net_device *dev) +{ + unsigned long flags; + + /* + * Hold the page lock during close + */ + + spin_lock_irqsave(&dev_lock(dev), flags); + AX88190_init(dev, 0); + spin_unlock_irqrestore(&dev_lock(dev), flags); + netif_stop_queue(dev); + return 0; +} + +/** + * ei_tx_timeout - handle transmit time out condition + * @dev: network device which has apparently fallen asleep + * + * Called by kernel when device never acknowledges a transmit has + * completed (or failed) - i.e. never posted a Tx related interrupt. + */ + +void ei_tx_timeout(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) netdev_priv(dev); + int txsr, isr, tickssofar = jiffies - dev->trans_start; + unsigned long flags; + + ei_local->stat.tx_errors++; + + spin_lock_irqsave(&ei_local->page_lock, flags); + txsr = inb(e8390_base+EN0_TSR); + isr = inb(e8390_base+EN0_ISR); + spin_unlock_irqrestore(&ei_local->page_lock, flags); + + printk(KERN_DEBUG "%s: Tx timed out, %s TSR=%#2x, ISR=%#2x, t=%d.\n", + dev->name, (txsr & ENTSR_ABT) ? "excess collisions." : + (isr) ? "lost interrupt?" : "cable problem?", txsr, isr, tickssofar); + + if (!isr && !ei_local->stat.tx_packets) + { + /* The 8390 probably hasn't gotten on the cable yet. */ + ei_local->interface_num ^= 1; /* Try a different xcvr. */ + } + + /* Ugly but a reset can be slow, yet must be protected */ + + disable_irq_nosync(dev->irq); + spin_lock(&ei_local->page_lock); + + /* Try to restart the card. Perhaps the user has fixed something. */ + ei_reset_8390(dev); + AX88190_init(dev, 1); + + spin_unlock(&ei_local->page_lock); + enable_irq(dev->irq); + netif_wake_queue(dev); +} + +/** + * ei_start_xmit - begin packet transmission + * @skb: packet to be sent + * @dev: network device to which packet is sent + * + * Sends a packet to an 8390 network device. + */ + +static int ei_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) netdev_priv(dev); + int length, send_length, output_page; + unsigned long flags; + u8 packet[ETH_ZLEN]; + + netif_stop_queue(dev); + + length = skb->len; + + /* Mask interrupts from the ethercard. + SMP: We have to grab the lock here otherwise the IRQ handler + on another CPU can flip window and race the IRQ mask set. We end + up trashing the mcast filter not disabling irqs if we don't lock */ + + spin_lock_irqsave(&ei_local->page_lock, flags); + outb_p(0x00, e8390_base + EN0_IMR); + spin_unlock_irqrestore(&ei_local->page_lock, flags); + + /* + * Slow phase with lock held. + */ + + disable_irq_nosync(dev->irq); + + spin_lock(&ei_local->page_lock); + + ei_local->irqlock = 1; + + send_length = ETH_ZLEN < length ? length : ETH_ZLEN; + + /* + * We have two Tx slots available for use. Find the first free + * slot, and then perform some sanity checks. With two Tx bufs, + * you get very close to transmitting back-to-back packets. With + * only one Tx buf, the transmitter sits idle while you reload the + * card, leaving a substantial gap between each transmitted packet. + */ + + if (ei_local->tx1 == 0) + { + output_page = ei_local->tx_start_page; + ei_local->tx1 = send_length; + if (ei_debug && ei_local->tx2 > 0) + printk(KERN_DEBUG "%s: idle transmitter tx2=%d, lasttx=%d, txing=%d.\n", + dev->name, ei_local->tx2, ei_local->lasttx, ei_local->txing); + } + else if (ei_local->tx2 == 0) + { + output_page = ei_local->tx_start_page + TX_PAGES/2; + ei_local->tx2 = send_length; + if (ei_debug && ei_local->tx1 > 0) + printk(KERN_DEBUG "%s: idle transmitter, tx1=%d, lasttx=%d, txing=%d.\n", + dev->name, ei_local->tx1, ei_local->lasttx, ei_local->txing); + } + else + { /* We should never get here. */ + if (ei_debug) + printk(KERN_DEBUG "%s: No Tx buffers free! tx1=%d tx2=%d last=%d\n", + dev->name, ei_local->tx1, ei_local->tx2, ei_local->lasttx); + ei_local->irqlock = 0; + netif_stop_queue(dev); + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + spin_unlock(&ei_local->page_lock); + enable_irq(dev->irq); + ei_local->stat.tx_errors++; + return 1; + } + + /* + * Okay, now upload the packet and trigger a send if the transmitter + * isn't already sending. If it is busy, the interrupt handler will + * trigger the send later, upon receiving a Tx done interrupt. + */ + + if (length == skb->len) + ei_block_output(dev, length, skb->data, output_page); + else { + memset(packet, 0, ETH_ZLEN); + memcpy(packet, skb->data, skb->len); + ei_block_output(dev, length, packet, output_page); + } + + if (! ei_local->txing) + { + ei_local->txing = 1; + NS8390_trigger_send(dev, send_length, output_page); + dev->trans_start = jiffies; + if (output_page == ei_local->tx_start_page) + { + ei_local->tx1 = -1; + ei_local->lasttx = -1; + } + else + { + ei_local->tx2 = -1; + ei_local->lasttx = -2; + } + } + else ei_local->txqueue++; + + if (ei_local->tx1 && ei_local->tx2) + netif_stop_queue(dev); + else + netif_start_queue(dev); + + /* Turn 8390 interrupts back on. */ + ei_local->irqlock = 0; + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + + spin_unlock(&ei_local->page_lock); + enable_irq(dev->irq); + + dev_kfree_skb (skb); + ei_local->stat.tx_bytes += send_length; + + return 0; +} + +/** + * ax_interrupt - handle the interrupts from an 8390 + * @irq: interrupt number + * @dev_id: a pointer to the net_device + * @regs: unused + * + * Handle the ether interface interrupts. We pull packets from + * the 8390 via the card specific functions and fire them at the networking + * stack. We also handle transmit completions and wake the transmit path if + * necessary. We also update the counters and do other housekeeping as + * needed. + */ + +static irqreturn_t ax_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct net_device *dev = dev_id; + long e8390_base; + int interrupts, nr_serviced = 0, i; + struct ei_device *ei_local; + int handled = 0; + + if (dev == NULL) + { + printk ("net_interrupt(): irq %d for unknown device.\n", irq); + return IRQ_NONE; + } + + e8390_base = dev->base_addr; + ei_local = (struct ei_device *) netdev_priv(dev); + + /* + * Protect the irq test too. + */ + + spin_lock(&ei_local->page_lock); + + if (ei_local->irqlock) + { +#if 1 /* This might just be an interrupt for a PCI device sharing this line */ + /* The "irqlock" check is only for testing. */ + printk(ei_local->irqlock + ? "%s: Interrupted while interrupts are masked! isr=%#2x imr=%#2x.\n" + : "%s: Reentering the interrupt handler! isr=%#2x imr=%#2x.\n", + dev->name, inb_p(e8390_base + EN0_ISR), + inb_p(e8390_base + EN0_IMR)); +#endif + spin_unlock(&ei_local->page_lock); + return IRQ_NONE; + } + + if (ei_debug > 3) + printk(KERN_DEBUG "%s: interrupt(isr=%#2.2x).\n", dev->name, + inb_p(e8390_base + EN0_ISR)); + + outb_p(0x00, e8390_base + EN0_ISR); + ei_local->irqlock = 1; + + /* !!Assumption!! -- we stay in page 0. Don't break this. */ + while ((interrupts = inb_p(e8390_base + EN0_ISR)) != 0 + && ++nr_serviced < MAX_SERVICE) + { + if (!netif_running(dev) || (interrupts == 0xff)) { + if (ei_debug > 1) + printk(KERN_WARNING "%s: interrupt from stopped card\n", dev->name); + outb_p(interrupts, e8390_base + EN0_ISR); + interrupts = 0; + break; + } + handled = 1; + + /* AX88190 bug fix. */ + outb_p(interrupts, e8390_base + EN0_ISR); + for (i = 0; i < 10; i++) { + if (!(inb(e8390_base + EN0_ISR) & interrupts)) + break; + outb_p(0, e8390_base + EN0_ISR); + outb_p(interrupts, e8390_base + EN0_ISR); + } + if (interrupts & ENISR_OVER) + ei_rx_overrun(dev); + else if (interrupts & (ENISR_RX+ENISR_RX_ERR)) + { + /* Got a good (?) packet. */ + ei_receive(dev); + } + /* Push the next to-transmit packet through. */ + if (interrupts & ENISR_TX) + ei_tx_intr(dev); + else if (interrupts & ENISR_TX_ERR) + ei_tx_err(dev); + + if (interrupts & ENISR_COUNTERS) + { + ei_local->stat.rx_frame_errors += inb_p(e8390_base + EN0_COUNTER0); + ei_local->stat.rx_crc_errors += inb_p(e8390_base + EN0_COUNTER1); + ei_local->stat.rx_missed_errors+= inb_p(e8390_base + EN0_COUNTER2); + } + } + + if (interrupts && ei_debug) + { + handled = 1; + if (nr_serviced >= MAX_SERVICE) + { + /* 0xFF is valid for a card removal */ + if(interrupts!=0xFF) + printk(KERN_WARNING "%s: Too much work at interrupt, status %#2.2x\n", + dev->name, interrupts); + outb_p(ENISR_ALL, e8390_base + EN0_ISR); /* Ack. most intrs. */ + } else { + printk(KERN_WARNING "%s: unknown interrupt %#2x\n", dev->name, interrupts); + outb_p(0xff, e8390_base + EN0_ISR); /* Ack. all intrs. */ + } + } + + /* Turn 8390 interrupts back on. */ + ei_local->irqlock = 0; + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + + spin_unlock(&ei_local->page_lock); + return IRQ_RETVAL(handled); +} + +/** + * ei_tx_err - handle transmitter error + * @dev: network device which threw the exception + * + * A transmitter error has happened. Most likely excess collisions (which + * is a fairly normal condition). If the error is one where the Tx will + * have been aborted, we try and send another one right away, instead of + * letting the failed packet sit and collect dust in the Tx buffer. This + * is a much better solution as it avoids kernel based Tx timeouts, and + * an unnecessary card reset. + * + * Called with lock held. + */ + +static void ei_tx_err(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) netdev_priv(dev); + unsigned char txsr = inb_p(e8390_base+EN0_TSR); + unsigned char tx_was_aborted = txsr & (ENTSR_ABT+ENTSR_FU); + +#ifdef VERBOSE_ERROR_DUMP + printk(KERN_DEBUG "%s: transmitter error (%#2x): ", dev->name, txsr); + if (txsr & ENTSR_ABT) + printk("excess-collisions "); + if (txsr & ENTSR_ND) + printk("non-deferral "); + if (txsr & ENTSR_CRS) + printk("lost-carrier "); + if (txsr & ENTSR_FU) + printk("FIFO-underrun "); + if (txsr & ENTSR_CDH) + printk("lost-heartbeat "); + printk("\n"); +#endif + + if (tx_was_aborted) + ei_tx_intr(dev); + else + { + ei_local->stat.tx_errors++; + if (txsr & ENTSR_CRS) ei_local->stat.tx_carrier_errors++; + if (txsr & ENTSR_CDH) ei_local->stat.tx_heartbeat_errors++; + if (txsr & ENTSR_OWC) ei_local->stat.tx_window_errors++; + } +} + +/** + * ei_tx_intr - transmit interrupt handler + * @dev: network device for which tx intr is handled + * + * We have finished a transmit: check for errors and then trigger the next + * packet to be sent. Called with lock held. + */ + +static void ei_tx_intr(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) netdev_priv(dev); + int status = inb(e8390_base + EN0_TSR); + + /* + * There are two Tx buffers, see which one finished, and trigger + * the send of another one if it exists. + */ + ei_local->txqueue--; + + if (ei_local->tx1 < 0) + { + if (ei_local->lasttx != 1 && ei_local->lasttx != -1) + printk(KERN_ERR "%s: bogus last_tx_buffer %d, tx1=%d.\n", + ei_local->name, ei_local->lasttx, ei_local->tx1); + ei_local->tx1 = 0; + if (ei_local->tx2 > 0) + { + ei_local->txing = 1; + NS8390_trigger_send(dev, ei_local->tx2, ei_local->tx_start_page + 6); + dev->trans_start = jiffies; + ei_local->tx2 = -1, + ei_local->lasttx = 2; + } + else ei_local->lasttx = 20, ei_local->txing = 0; + } + else if (ei_local->tx2 < 0) + { + if (ei_local->lasttx != 2 && ei_local->lasttx != -2) + printk("%s: bogus last_tx_buffer %d, tx2=%d.\n", + ei_local->name, ei_local->lasttx, ei_local->tx2); + ei_local->tx2 = 0; + if (ei_local->tx1 > 0) + { + ei_local->txing = 1; + NS8390_trigger_send(dev, ei_local->tx1, ei_local->tx_start_page); + dev->trans_start = jiffies; + ei_local->tx1 = -1; + ei_local->lasttx = 1; + } + else + ei_local->lasttx = 10, ei_local->txing = 0; + } +// else printk(KERN_WARNING "%s: unexpected TX-done interrupt, lasttx=%d.\n", +// dev->name, ei_local->lasttx); + + /* Minimize Tx latency: update the statistics after we restart TXing. */ + if (status & ENTSR_COL) + ei_local->stat.collisions++; + if (status & ENTSR_PTX) + ei_local->stat.tx_packets++; + else + { + ei_local->stat.tx_errors++; + if (status & ENTSR_ABT) + { + ei_local->stat.tx_aborted_errors++; + ei_local->stat.collisions += 16; + } + if (status & ENTSR_CRS) + ei_local->stat.tx_carrier_errors++; + if (status & ENTSR_FU) + ei_local->stat.tx_fifo_errors++; + if (status & ENTSR_CDH) + ei_local->stat.tx_heartbeat_errors++; + if (status & ENTSR_OWC) + ei_local->stat.tx_window_errors++; + } + netif_wake_queue(dev); +} + +/** + * ei_receive - receive some packets + * @dev: network device with which receive will be run + * + * We have a good packet(s), get it/them out of the buffers. + * Called with lock held. + */ + +static void ei_receive(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) netdev_priv(dev); + unsigned char rxing_page, this_frame, next_frame; + unsigned short current_offset; + int rx_pkt_count = 0; + struct e8390_pkt_hdr rx_frame; + + while (++rx_pkt_count < 10) + { + int pkt_len, pkt_stat; + + /* Get the rx page (incoming packet pointer). */ + rxing_page = inb_p(e8390_base + EN1_CURPAG -1); + + /* Remove one frame from the ring. Boundary is always a page behind. */ + this_frame = inb_p(e8390_base + EN0_BOUNDARY) + 1; + if (this_frame >= ei_local->stop_page) + this_frame = ei_local->rx_start_page; + + /* Someday we'll omit the previous, iff we never get this message. + (There is at least one clone claimed to have a problem.) + + Keep quiet if it looks like a card removal. One problem here + is that some clones crash in roughly the same way. + */ + if (ei_debug > 0 && this_frame != ei_local->current_page && (this_frame!=0x0 || rxing_page!=0xFF)) + printk(KERN_ERR "%s: mismatched read page pointers %2x vs %2x.\n", + dev->name, this_frame, ei_local->current_page); + + if (this_frame == rxing_page) /* Read all the frames? */ + break; /* Done for now */ + + current_offset = this_frame << 8; + ei_get_8390_hdr(dev, &rx_frame, this_frame); + + pkt_len = rx_frame.count - sizeof(struct e8390_pkt_hdr); + pkt_stat = rx_frame.status; + + next_frame = this_frame + 1 + ((pkt_len+4)>>8); + + if (pkt_len < 60 || pkt_len > 1518) + { + if (ei_debug) + printk(KERN_DEBUG "%s: bogus packet size: %d, status=%#2x nxpg=%#2x.\n", + dev->name, rx_frame.count, rx_frame.status, + rx_frame.next); + ei_local->stat.rx_errors++; + ei_local->stat.rx_length_errors++; + } + else if ((pkt_stat & 0x0F) == ENRSR_RXOK) + { + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len+2); + if (skb == NULL) + { + if (ei_debug > 1) + printk(KERN_DEBUG "%s: Couldn't allocate a sk_buff of size %d.\n", + dev->name, pkt_len); + ei_local->stat.rx_dropped++; + break; + } + else + { + skb_reserve(skb,2); /* IP headers on 16 byte boundaries */ + skb->dev = dev; + skb_put(skb, pkt_len); /* Make room */ + ei_block_input(dev, pkt_len, skb, current_offset + sizeof(rx_frame)); + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + dev->last_rx = jiffies; + ei_local->stat.rx_packets++; + ei_local->stat.rx_bytes += pkt_len; + if (pkt_stat & ENRSR_PHY) + ei_local->stat.multicast++; + } + } + else + { + if (ei_debug) + printk(KERN_DEBUG "%s: bogus packet: status=%#2x nxpg=%#2x size=%d\n", + dev->name, rx_frame.status, rx_frame.next, + rx_frame.count); + ei_local->stat.rx_errors++; + /* NB: The NIC counts CRC, frame and missed errors. */ + if (pkt_stat & ENRSR_FO) + ei_local->stat.rx_fifo_errors++; + } + next_frame = rx_frame.next; + + /* This _should_ never happen: it's here for avoiding bad clones. */ + if (next_frame >= ei_local->stop_page) { + printk("%s: next frame inconsistency, %#2x\n", dev->name, + next_frame); + next_frame = ei_local->rx_start_page; + } + ei_local->current_page = next_frame; + outb_p(next_frame-1, e8390_base+EN0_BOUNDARY); + } + + return; +} + +/** + * ei_rx_overrun - handle receiver overrun + * @dev: network device which threw exception + * + * We have a receiver overrun: we have to kick the 8390 to get it started + * again. Problem is that you have to kick it exactly as NS prescribes in + * the updated datasheets, or "the NIC may act in an unpredictable manner." + * This includes causing "the NIC to defer indefinitely when it is stopped + * on a busy network." Ugh. + * Called with lock held. Don't call this with the interrupts off or your + * computer will hate you - it takes 10ms or so. + */ + +static void ei_rx_overrun(struct net_device *dev) +{ + axnet_dev_t *info = (axnet_dev_t *)dev; + long e8390_base = dev->base_addr; + unsigned char was_txing, must_resend = 0; + struct ei_device *ei_local = (struct ei_device *) netdev_priv(dev); + + /* + * Record whether a Tx was in progress and then issue the + * stop command. + */ + was_txing = inb_p(e8390_base+E8390_CMD) & E8390_TRANS; + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base+E8390_CMD); + + if (ei_debug > 1) + printk(KERN_DEBUG "%s: Receiver overrun.\n", dev->name); + ei_local->stat.rx_over_errors++; + + /* + * Wait a full Tx time (1.2ms) + some guard time, NS says 1.6ms total. + * Early datasheets said to poll the reset bit, but now they say that + * it "is not a reliable indicator and subsequently should be ignored." + * We wait at least 10ms. + */ + + mdelay(10); + + /* + * Reset RBCR[01] back to zero as per magic incantation. + */ + outb_p(0x00, e8390_base+EN0_RCNTLO); + outb_p(0x00, e8390_base+EN0_RCNTHI); + + /* + * See if any Tx was interrupted or not. According to NS, this + * step is vital, and skipping it will cause no end of havoc. + */ + + if (was_txing) + { + unsigned char tx_completed = inb_p(e8390_base+EN0_ISR) & (ENISR_TX+ENISR_TX_ERR); + if (!tx_completed) + must_resend = 1; + } + + /* + * Have to enter loopback mode and then restart the NIC before + * you are allowed to slurp packets up off the ring. + */ + outb_p(E8390_TXOFF, e8390_base + EN0_TXCR); + outb_p(E8390_NODMA + E8390_PAGE0 + E8390_START, e8390_base + E8390_CMD); + + /* + * Clear the Rx ring of all the debris, and ack the interrupt. + */ + ei_receive(dev); + + /* + * Leave loopback mode, and resend any packet that got stopped. + */ + outb_p(E8390_TXCONFIG | info->duplex_flag, e8390_base + EN0_TXCR); + if (must_resend) + outb_p(E8390_NODMA + E8390_PAGE0 + E8390_START + E8390_TRANS, e8390_base + E8390_CMD); +} + +/* + * Collect the stats. This is called unlocked and from several contexts. + */ + +static struct net_device_stats *get_stats(struct net_device *dev) +{ + long ioaddr = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) netdev_priv(dev); + unsigned long flags; + + /* If the card is stopped, just return the present stats. */ + if (!netif_running(dev)) + return &ei_local->stat; + + spin_lock_irqsave(&ei_local->page_lock,flags); + /* Read the counter registers, assuming we are in page 0. */ + ei_local->stat.rx_frame_errors += inb_p(ioaddr + EN0_COUNTER0); + ei_local->stat.rx_crc_errors += inb_p(ioaddr + EN0_COUNTER1); + ei_local->stat.rx_missed_errors+= inb_p(ioaddr + EN0_COUNTER2); + spin_unlock_irqrestore(&ei_local->page_lock, flags); + + return &ei_local->stat; +} + +/** + * do_set_multicast_list - set/clear multicast filter + * @dev: net device for which multicast filter is adjusted + * + * Set or clear the multicast filter for this adaptor. May be called + * from a BH in 2.1.x. Must be called with lock held. + */ + +static void do_set_multicast_list(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + + if(dev->flags&IFF_PROMISC) + outb_p(E8390_RXCONFIG | 0x58, e8390_base + EN0_RXCR); + else if(dev->flags&IFF_ALLMULTI || dev->mc_list) + outb_p(E8390_RXCONFIG | 0x48, e8390_base + EN0_RXCR); + else + outb_p(E8390_RXCONFIG | 0x40, e8390_base + EN0_RXCR); +} + +/* + * Called without lock held. This is invoked from user context and may + * be parallel to just about everything else. Its also fairly quick and + * not called too often. Must protect against both bh and irq users + */ + +static void set_multicast_list(struct net_device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev_lock(dev), flags); + do_set_multicast_list(dev); + spin_unlock_irqrestore(&dev_lock(dev), flags); +} + +/** + * axdev_setup - init rest of 8390 device struct + * @dev: network device structure to init + * + * Initialize the rest of the 8390 device structure. Do NOT __init + * this, as it is used by 8390 based modular drivers too. + */ + +static void axdev_setup(struct net_device *dev) +{ + struct ei_device *ei_local; + if (ei_debug > 1) + printk(version_8390); + + SET_MODULE_OWNER(dev); + + + ei_local = (struct ei_device *)netdev_priv(dev); + spin_lock_init(&ei_local->page_lock); + + dev->hard_start_xmit = &ei_start_xmit; + dev->get_stats = get_stats; + dev->set_multicast_list = &set_multicast_list; + + ether_setup(dev); +} + +/* This page of functions should be 8390 generic */ +/* Follow National Semi's recommendations for initializing the "NIC". */ + +/** + * AX88190_init - initialize 8390 hardware + * @dev: network device to initialize + * @startp: boolean. non-zero value to initiate chip processing + * + * Must be called with lock held. + */ + +static void AX88190_init(struct net_device *dev, int startp) +{ + axnet_dev_t *info = PRIV(dev); + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) netdev_priv(dev); + int i; + int endcfg = ei_local->word16 ? (0x48 | ENDCFG_WTS) : 0x48; + + if(sizeof(struct e8390_pkt_hdr)!=4) + panic("8390.c: header struct mispacked\n"); + /* Follow National Semi's recommendations for initing the DP83902. */ + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base+E8390_CMD); /* 0x21 */ + outb_p(endcfg, e8390_base + EN0_DCFG); /* 0x48 or 0x49 */ + /* Clear the remote byte count registers. */ + outb_p(0x00, e8390_base + EN0_RCNTLO); + outb_p(0x00, e8390_base + EN0_RCNTHI); + /* Set to monitor and loopback mode -- this is vital!. */ + outb_p(E8390_RXOFF|0x40, e8390_base + EN0_RXCR); /* 0x60 */ + outb_p(E8390_TXOFF, e8390_base + EN0_TXCR); /* 0x02 */ + /* Set the transmit page and receive ring. */ + outb_p(ei_local->tx_start_page, e8390_base + EN0_TPSR); + ei_local->tx1 = ei_local->tx2 = 0; + outb_p(ei_local->rx_start_page, e8390_base + EN0_STARTPG); + outb_p(ei_local->stop_page-1, e8390_base + EN0_BOUNDARY); /* 3c503 says 0x3f,NS0x26*/ + ei_local->current_page = ei_local->rx_start_page; /* assert boundary+1 */ + outb_p(ei_local->stop_page, e8390_base + EN0_STOPPG); + /* Clear the pending interrupts and mask. */ + outb_p(0xFF, e8390_base + EN0_ISR); + outb_p(0x00, e8390_base + EN0_IMR); + + /* Copy the station address into the DS8390 registers. */ + + outb_p(E8390_NODMA + E8390_PAGE1 + E8390_STOP, e8390_base+E8390_CMD); /* 0x61 */ + for(i = 0; i < 6; i++) + { + outb_p(dev->dev_addr[i], e8390_base + EN1_PHYS_SHIFT(i)); + if(inb_p(e8390_base + EN1_PHYS_SHIFT(i))!=dev->dev_addr[i]) + printk(KERN_ERR "Hw. address read/write mismap %d\n",i); + } + /* + * Initialize the multicast list to accept-all. If we enable multicast + * the higher levels can do the filtering. + */ + for (i = 0; i < 8; i++) + outb_p(0xff, e8390_base + EN1_MULT + i); + + outb_p(ei_local->rx_start_page, e8390_base + EN1_CURPAG); + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base+E8390_CMD); + + netif_start_queue(dev); + ei_local->tx1 = ei_local->tx2 = 0; + ei_local->txing = 0; + + if (startp) + { + outb_p(0xff, e8390_base + EN0_ISR); + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, e8390_base+E8390_CMD); + outb_p(E8390_TXCONFIG | info->duplex_flag, + e8390_base + EN0_TXCR); /* xmit on. */ + /* 3c503 TechMan says rxconfig only after the NIC is started. */ + outb_p(E8390_RXCONFIG | 0x40, e8390_base + EN0_RXCR); /* rx on, */ + do_set_multicast_list(dev); /* (re)load the mcast table */ + } +} + +/* Trigger a transmit start, assuming the length is valid. + Always called with the page lock held */ + +static void NS8390_trigger_send(struct net_device *dev, unsigned int length, + int start_page) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local __attribute((unused)) = (struct ei_device *) netdev_priv(dev); + + if (inb_p(e8390_base) & E8390_TRANS) + { + printk(KERN_WARNING "%s: trigger_send() called with the transmitter busy.\n", + dev->name); + return; + } + outb_p(length & 0xff, e8390_base + EN0_TCNTLO); + outb_p(length >> 8, e8390_base + EN0_TCNTHI); + outb_p(start_page, e8390_base + EN0_TPSR); + outb_p(E8390_NODMA+E8390_TRANS+E8390_START, e8390_base+E8390_CMD); +} diff --git a/drivers/net/pcmcia/com20020_cs.c b/drivers/net/pcmcia/com20020_cs.c new file mode 100644 index 00000000000..4294e1e3f15 --- /dev/null +++ b/drivers/net/pcmcia/com20020_cs.c @@ -0,0 +1,509 @@ +/* + * Linux ARCnet driver - COM20020 PCMCIA support + * + * Written 1994-1999 by Avery Pennarun, + * based on an ISA version by David Woodhouse. + * Derived from ibmtr_cs.c by Steve Kipisz (pcmcia-cs 3.1.4) + * which was derived from pcnet_cs.c by David Hinds. + * Some additional portions derived from skeleton.c by Donald Becker. + * + * Special thanks to Contemporary Controls, Inc. (www.ccontrols.com) + * for sponsoring the further development of this driver. + * + * ********************** + * + * The original copyright of skeleton.c was as follows: + * + * skeleton.c Written 1993 by Donald Becker. + * Copyright 1993 United States Government as represented by the + * Director, National Security Agency. This software may only be used + * and distributed according to the terms of the GNU General Public License as + * modified by SRC, incorporated herein by reference. + * + * ********************** + * Changes: + * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 08/08/2000 + * - reorganize kmallocs in com20020_attach, checking all for failure + * and releasing the previous allocations if one fails + * ********************** + * + * For more details, see drivers/net/arcnet.c + * + * ********************** + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/arcdevice.h> +#include <linux/com20020.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#include <asm/io.h> +#include <asm/system.h> + +#define VERSION "arcnet: COM20020 PCMCIA support loaded.\n" + +#ifdef PCMCIA_DEBUG + +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) + +static void regdump(struct net_device *dev) +{ + int ioaddr = dev->base_addr; + int count; + + printk("com20020 register dump:\n"); + for (count = ioaddr; count < ioaddr + 16; count++) + { + if (!(count % 16)) + printk("\n%04X: ", count); + printk("%02X ", inb(count)); + } + printk("\n"); + + printk("buffer0 dump:\n"); + /* set up the address register */ + count = 0; + outb((count >> 8) | RDDATAflag | AUTOINCflag, _ADDR_HI); + outb(count & 0xff, _ADDR_LO); + + for (count = 0; count < 256+32; count++) + { + if (!(count % 16)) + printk("\n%04X: ", count); + + /* copy the data */ + printk("%02X ", inb(_MEMDATA)); + } + printk("\n"); +} + +#else + +#define DEBUG(n, args...) do { } while (0) +static inline void regdump(struct net_device *dev) { } + +#endif + + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int node; +static int timeout = 3; +static int backplane; +static int clockp; +static int clockm; + +module_param(node, int, 0); +module_param(timeout, int, 0); +module_param(backplane, int, 0); +module_param(clockp, int, 0); +module_param(clockm, int, 0); + +MODULE_LICENSE("GPL"); + +/*====================================================================*/ + +static void com20020_config(dev_link_t *link); +static void com20020_release(dev_link_t *link); +static int com20020_event(event_t event, int priority, + event_callback_args_t *args); + +static dev_info_t dev_info = "com20020_cs"; + +static dev_link_t *com20020_attach(void); +static void com20020_detach(dev_link_t *); + +static dev_link_t *dev_list; + +/*====================================================================*/ + +typedef struct com20020_dev_t { + struct net_device *dev; + dev_node_t node; +} com20020_dev_t; + +/*====================================================================== + + com20020_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *com20020_attach(void) +{ + client_reg_t client_reg; + dev_link_t *link; + com20020_dev_t *info; + struct net_device *dev; + int ret; + struct arcnet_local *lp; + + DEBUG(0, "com20020_attach()\n"); + + /* Create new network device */ + link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL); + if (!link) + return NULL; + + info = kmalloc(sizeof(struct com20020_dev_t), GFP_KERNEL); + if (!info) + goto fail_alloc_info; + + dev = alloc_arcdev(""); + if (!dev) + goto fail_alloc_dev; + + memset(info, 0, sizeof(struct com20020_dev_t)); + memset(link, 0, sizeof(struct dev_link_t)); + lp = dev->priv; + lp->timeout = timeout; + lp->backplane = backplane; + lp->clockp = clockp; + lp->clockm = clockm & 3; + lp->hw.owner = THIS_MODULE; + + /* fill in our module parameters as defaults */ + dev->dev_addr[0] = node; + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts1 = 16; + link->io.IOAddrLines = 16; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.Present = PRESENT_OPTION; + + + link->irq.Instance = info->dev = dev; + link->priv = info; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &com20020_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + com20020_detach(link); + return NULL; + } + + return link; + +fail_alloc_dev: + kfree(info); +fail_alloc_info: + kfree(link); + return NULL; +} /* com20020_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void com20020_detach(dev_link_t *link) +{ + struct com20020_dev_t *info = link->priv; + dev_link_t **linkp; + struct net_device *dev; + + DEBUG(1,"detach...\n"); + + DEBUG(0, "com20020_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + dev = info->dev; + + if (link->dev) { + DEBUG(1,"unregister...\n"); + + unregister_netdev(dev); + + /* + * this is necessary because we register our IRQ separately + * from card services. + */ + if (dev->irq) + free_irq(dev->irq, dev); + } + + if (link->state & DEV_CONFIG) + com20020_release(link); + + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free bits */ + DEBUG(1,"unlinking...\n"); + *linkp = link->next; + if (link->priv) + { + dev = info->dev; + if (dev) + { + DEBUG(1,"kfree...\n"); + free_netdev(dev); + } + DEBUG(1,"kfree2...\n"); + kfree(info); + } + DEBUG(1,"kfree3...\n"); + kfree(link); + +} /* com20020_detach */ + +/*====================================================================== + + com20020_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + device available to the system. + +======================================================================*/ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static void com20020_config(dev_link_t *link) +{ + struct arcnet_local *lp; + client_handle_t handle; + tuple_t tuple; + cisparse_t parse; + com20020_dev_t *info; + struct net_device *dev; + int i, last_ret, last_fn; + u_char buf[64]; + int ioaddr; + + handle = link->handle; + info = link->priv; + dev = info->dev; + + DEBUG(1,"config...\n"); + + DEBUG(0, "com20020_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + + /* Configure card */ + link->state |= DEV_CONFIG; + + DEBUG(1,"arcnet: baseport1 is %Xh\n", link->io.BasePort1); + i = !CS_SUCCESS; + if (!link->io.BasePort1) + { + for (ioaddr = 0x100; ioaddr < 0x400; ioaddr += 0x10) + { + link->io.BasePort1 = ioaddr; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) + break; + } + } + else + i = pcmcia_request_io(link->handle, &link->io); + + if (i != CS_SUCCESS) + { + DEBUG(1,"arcnet: requestIO failed totally!\n"); + goto failed; + } + + ioaddr = dev->base_addr = link->io.BasePort1; + DEBUG(1,"arcnet: got ioaddr %Xh\n", ioaddr); + + DEBUG(1,"arcnet: request IRQ %d (%Xh/%Xh)\n", + link->irq.AssignedIRQ, + link->irq.IRQInfo1, link->irq.IRQInfo2); + i = pcmcia_request_irq(link->handle, &link->irq); + if (i != CS_SUCCESS) + { + DEBUG(1,"arcnet: requestIRQ failed totally!\n"); + goto failed; + } + + dev->irq = link->irq.AssignedIRQ; + + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf)); + + if (com20020_check(dev)) + { + regdump(dev); + goto failed; + } + + lp = dev->priv; + lp->card_name = "PCMCIA COM20020"; + lp->card_flags = ARC_CAN_10MBIT; /* pretend all of them can 10Mbit */ + + link->dev = &info->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + i = com20020_found(dev, 0); /* calls register_netdev */ + + if (i != 0) { + DEBUG(1,KERN_NOTICE "com20020_cs: com20020_found() failed\n"); + link->dev = NULL; + goto failed; + } + + strcpy(info->node.dev_name, dev->name); + + DEBUG(1,KERN_INFO "%s: port %#3lx, irq %d\n", + dev->name, dev->base_addr, dev->irq); + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + DEBUG(1,"com20020_config failed...\n"); + com20020_release(link); +} /* com20020_config */ + +/*====================================================================== + + After a card is removed, com20020_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void com20020_release(dev_link_t *link) +{ + + DEBUG(1,"release...\n"); + + DEBUG(0, "com20020_release(0x%p)\n", link); + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~(DEV_CONFIG | DEV_RELEASE_PENDING); +} + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + +======================================================================*/ + +static int com20020_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + com20020_dev_t *info = link->priv; + struct net_device *dev = info->dev; + + DEBUG(1, "com20020_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT; + com20020_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) { + netif_device_detach(dev); + } + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + int ioaddr = dev->base_addr; + struct arcnet_local *lp = dev->priv; + ARCRESET; + } + } + break; + } + return 0; +} /* com20020_event */ + + + +static struct pcmcia_driver com20020_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "com20020_cs", + }, + .attach = com20020_attach, + .detach = com20020_detach, +}; + +static int __init init_com20020_cs(void) +{ + return pcmcia_register_driver(&com20020_cs_driver); +} + +static void __exit exit_com20020_cs(void) +{ + pcmcia_unregister_driver(&com20020_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_com20020_cs); +module_exit(exit_com20020_cs); diff --git a/drivers/net/pcmcia/fmvj18x_cs.c b/drivers/net/pcmcia/fmvj18x_cs.c new file mode 100644 index 00000000000..0424865e809 --- /dev/null +++ b/drivers/net/pcmcia/fmvj18x_cs.c @@ -0,0 +1,1286 @@ +/*====================================================================== + fmvj18x_cs.c 2.8 2002/03/23 + + A fmvj18x (and its compatibles) PCMCIA client driver + + Contributed by Shingo Fujimoto, shingo@flab.fujitsu.co.jp + + TDK LAK-CD021 and CONTEC C-NET(PC)C support added by + Nobuhiro Katayama, kata-n@po.iijnet.or.jp + + The PCMCIA client code is based on code written by David Hinds. + Network code is based on the "FMV-18x driver" by Yutaka TAMIYA + but is actually largely Donald Becker's AT1700 driver, which + carries the following attribution: + + Written 1993-94 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU General Public License, incorporated herein by reference. + + The author may be reached as becker@scyld.com, or C/O + Scyld Computing Corporation + 410 Severn Ave., Suite 210 + Annapolis MD 21403 + +======================================================================*/ + +#define DRV_NAME "fmvj18x_cs" +#define DRV_VERSION "2.8" + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <linux/ethtool.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> +#include <linux/crc32.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/system.h> + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_DESCRIPTION("fmvj18x and compatible PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +/* SRAM configuration */ +/* 0:4KB*2 TX buffer else:8KB*2 TX buffer */ +INT_MODULE_PARM(sram_config, 0); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = DRV_NAME ".c " DRV_VERSION " 2002/03/23"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ +/* + PCMCIA event handlers + */ +static void fmvj18x_config(dev_link_t *link); +static int fmvj18x_get_hwinfo(dev_link_t *link, u_char *node_id); +static int fmvj18x_setup_mfc(dev_link_t *link); +static void fmvj18x_release(dev_link_t *link); +static int fmvj18x_event(event_t event, int priority, + event_callback_args_t *args); +static dev_link_t *fmvj18x_attach(void); +static void fmvj18x_detach(dev_link_t *); + +/* + LAN controller(MBH86960A) specific routines + */ +static int fjn_config(struct net_device *dev, struct ifmap *map); +static int fjn_open(struct net_device *dev); +static int fjn_close(struct net_device *dev); +static int fjn_start_xmit(struct sk_buff *skb, struct net_device *dev); +static irqreturn_t fjn_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void fjn_rx(struct net_device *dev); +static void fjn_reset(struct net_device *dev); +static struct net_device_stats *fjn_get_stats(struct net_device *dev); +static void set_rx_mode(struct net_device *dev); +static void fjn_tx_timeout(struct net_device *dev); +static struct ethtool_ops netdev_ethtool_ops; + +static dev_info_t dev_info = "fmvj18x_cs"; +static dev_link_t *dev_list; + +/* + card type + */ +typedef enum { MBH10302, MBH10304, TDK, CONTEC, LA501, UNGERMANN, + XXX10304 +} cardtype_t; + +/* + driver specific data structure +*/ +typedef struct local_info_t { + dev_link_t link; + dev_node_t node; + struct net_device_stats stats; + long open_time; + uint tx_started:1; + uint tx_queue; + u_short tx_queue_len; + cardtype_t cardtype; + u_short sent; + u_char mc_filter[8]; +} local_info_t; + +#define MC_FILTERBREAK 64 + +/*====================================================================*/ +/* + ioport offset from the base address + */ +#define TX_STATUS 0 /* transmit status register */ +#define RX_STATUS 1 /* receive status register */ +#define TX_INTR 2 /* transmit interrupt mask register */ +#define RX_INTR 3 /* receive interrupt mask register */ +#define TX_MODE 4 /* transmit mode register */ +#define RX_MODE 5 /* receive mode register */ +#define CONFIG_0 6 /* configuration register 0 */ +#define CONFIG_1 7 /* configuration register 1 */ + +#define NODE_ID 8 /* node ID register (bank 0) */ +#define MAR_ADR 8 /* multicast address registers (bank 1) */ + +#define DATAPORT 8 /* buffer mem port registers (bank 2) */ +#define TX_START 10 /* transmit start register */ +#define COL_CTRL 11 /* 16 collision control register */ +#define BMPR12 12 /* reserved */ +#define BMPR13 13 /* reserved */ +#define RX_SKIP 14 /* skip received packet register */ + +#define LAN_CTRL 16 /* LAN card control register */ + +#define MAC_ID 0x1a /* hardware address */ +#define UNGERMANN_MAC_ID 0x18 /* UNGERMANN-BASS hardware address */ + +/* + control bits + */ +#define ENA_TMT_OK 0x80 +#define ENA_TMT_REC 0x20 +#define ENA_COL 0x04 +#define ENA_16_COL 0x02 +#define ENA_TBUS_ERR 0x01 + +#define ENA_PKT_RDY 0x80 +#define ENA_BUS_ERR 0x40 +#define ENA_LEN_ERR 0x08 +#define ENA_ALG_ERR 0x04 +#define ENA_CRC_ERR 0x02 +#define ENA_OVR_FLO 0x01 + +/* flags */ +#define F_TMT_RDY 0x80 /* can accept new packet */ +#define F_NET_BSY 0x40 /* carrier is detected */ +#define F_TMT_OK 0x20 /* send packet successfully */ +#define F_SRT_PKT 0x10 /* short packet error */ +#define F_COL_ERR 0x04 /* collision error */ +#define F_16_COL 0x02 /* 16 collision error */ +#define F_TBUS_ERR 0x01 /* bus read error */ + +#define F_PKT_RDY 0x80 /* packet(s) in buffer */ +#define F_BUS_ERR 0x40 /* bus read error */ +#define F_LEN_ERR 0x08 /* short packet */ +#define F_ALG_ERR 0x04 /* frame error */ +#define F_CRC_ERR 0x02 /* CRC error */ +#define F_OVR_FLO 0x01 /* overflow error */ + +#define F_BUF_EMP 0x40 /* receive buffer is empty */ + +#define F_SKP_PKT 0x05 /* drop packet in buffer */ + +/* default bitmaps */ +#define D_TX_INTR ( ENA_TMT_OK ) +#define D_RX_INTR ( ENA_PKT_RDY | ENA_LEN_ERR \ + | ENA_ALG_ERR | ENA_CRC_ERR | ENA_OVR_FLO ) +#define TX_STAT_M ( F_TMT_RDY ) +#define RX_STAT_M ( F_PKT_RDY | F_LEN_ERR \ + | F_ALG_ERR | F_CRC_ERR | F_OVR_FLO ) + +/* commands */ +#define D_TX_MODE 0x06 /* no tests, detect carrier */ +#define ID_MATCHED 0x02 /* (RX_MODE) */ +#define RECV_ALL 0x03 /* (RX_MODE) */ +#define CONFIG0_DFL 0x5a /* 16bit bus, 4K x 2 Tx queues */ +#define CONFIG0_DFL_1 0x5e /* 16bit bus, 8K x 2 Tx queues */ +#define CONFIG0_RST 0xda /* Data Link Controller off (CONFIG_0) */ +#define CONFIG0_RST_1 0xde /* Data Link Controller off (CONFIG_0) */ +#define BANK_0 0xa0 /* bank 0 (CONFIG_1) */ +#define BANK_1 0xa4 /* bank 1 (CONFIG_1) */ +#define BANK_2 0xa8 /* bank 2 (CONFIG_1) */ +#define CHIP_OFF 0x80 /* contrl chip power off (CONFIG_1) */ +#define DO_TX 0x80 /* do transmit packet */ +#define SEND_PKT 0x81 /* send a packet */ +#define AUTO_MODE 0x07 /* Auto skip packet on 16 col detected */ +#define MANU_MODE 0x03 /* Stop and skip packet on 16 col */ +#define TDK_AUTO_MODE 0x47 /* Auto skip packet on 16 col detected */ +#define TDK_MANU_MODE 0x43 /* Stop and skip packet on 16 col */ +#define INTR_OFF 0x0d /* LAN controller ignores interrupts */ +#define INTR_ON 0x1d /* LAN controller will catch interrupts */ + +#define TX_TIMEOUT ((400*HZ)/1000) + +#define BANK_0U 0x20 /* bank 0 (CONFIG_1) */ +#define BANK_1U 0x24 /* bank 1 (CONFIG_1) */ +#define BANK_2U 0x28 /* bank 2 (CONFIG_1) */ + +static dev_link_t *fmvj18x_attach(void) +{ + local_info_t *lp; + dev_link_t *link; + struct net_device *dev; + client_reg_t client_reg; + int ret; + + DEBUG(0, "fmvj18x_attach()\n"); + + /* Make up a FMVJ18x specific data structure */ + dev = alloc_etherdev(sizeof(local_info_t)); + if (!dev) + return NULL; + lp = netdev_priv(dev); + link = &lp->link; + link->priv = dev; + + /* The io structure describes IO port mapping */ + link->io.NumPorts1 = 32; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.IOAddrLines = 5; + + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->irq.Handler = &fjn_interrupt; + link->irq.Instance = dev; + + /* General socket configuration */ + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* The FMVJ18x specific entries in the device structure. */ + SET_MODULE_OWNER(dev); + dev->hard_start_xmit = &fjn_start_xmit; + dev->set_config = &fjn_config; + dev->get_stats = &fjn_get_stats; + dev->set_multicast_list = &set_rx_mode; + dev->open = &fjn_open; + dev->stop = &fjn_close; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = fjn_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + SET_ETHTOOL_OPS(dev, &netdev_ethtool_ops); + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &fmvj18x_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + fmvj18x_detach(link); + return NULL; + } + + return link; +} /* fmvj18x_attach */ + +/*====================================================================*/ + +static void fmvj18x_detach(dev_link_t *link) +{ + struct net_device *dev = link->priv; + dev_link_t **linkp; + + DEBUG(0, "fmvj18x_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->dev) + unregister_netdev(dev); + + if (link->state & DEV_CONFIG) + fmvj18x_release(link); + + /* Break the link with Card Services */ + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free pieces */ + *linkp = link->next; + free_netdev(dev); +} /* fmvj18x_detach */ + +/*====================================================================*/ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static int mfc_try_io_port(dev_link_t *link) +{ + int i, ret; + static kio_addr_t serial_base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; + + for (i = 0; i < 5; i++) { + link->io.BasePort2 = serial_base[i]; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + if (link->io.BasePort2 == 0) { + link->io.NumPorts2 = 0; + printk(KERN_NOTICE "fmvj18x_cs: out of resource for serial\n"); + } + ret = pcmcia_request_io(link->handle, &link->io); + if (ret == CS_SUCCESS) return ret; + } + return ret; +} + +static int ungermann_try_io_port(dev_link_t *link) +{ + int ret; + kio_addr_t ioaddr; + /* + Ungermann-Bass Access/CARD accepts 0x300,0x320,0x340,0x360 + 0x380,0x3c0 only for ioport. + */ + for (ioaddr = 0x300; ioaddr < 0x3e0; ioaddr += 0x20) { + link->io.BasePort1 = ioaddr; + ret = pcmcia_request_io(link->handle, &link->io); + if (ret == CS_SUCCESS) { + /* calculate ConfigIndex value */ + link->conf.ConfigIndex = + ((link->io.BasePort1 & 0x0f0) >> 3) | 0x22; + return ret; + } + } + return ret; /* RequestIO failed */ +} + +static void fmvj18x_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + local_info_t *lp = netdev_priv(dev); + tuple_t tuple; + cisparse_t parse; + u_short buf[32]; + int i, last_fn, last_ret, ret; + kio_addr_t ioaddr; + cardtype_t cardtype; + char *card_name = "unknown"; + u_char *node_id; + + DEBUG(0, "fmvj18x_config(0x%p)\n", link); + + /* + This reads the card's CONFIG tuple to find its configuration + registers. + */ + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + tuple.TupleData = (u_char *)buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + + /* Configure card */ + link->state |= DEV_CONFIG; + + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + tuple.DesiredTuple = CISTPL_FUNCE; + tuple.TupleOffset = 0; + if (pcmcia_get_first_tuple(handle, &tuple) == CS_SUCCESS) { + /* Yes, I have CISTPL_FUNCE. Let's check CISTPL_MANFID */ + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigIndex = parse.cftable_entry.index; + tuple.DesiredTuple = CISTPL_MANFID; + if (pcmcia_get_first_tuple(handle, &tuple) == CS_SUCCESS) + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + else + buf[0] = 0xffff; + switch (le16_to_cpu(buf[0])) { + case MANFID_TDK: + cardtype = TDK; + if (le16_to_cpu(buf[1]) == PRODID_TDK_CF010) { + cs_status_t status; + pcmcia_get_status(handle, &status); + if (status.CardState & CS_EVENT_3VCARD) + link->conf.Vcc = 33; /* inserted in 3.3V slot */ + } else if (le16_to_cpu(buf[1]) == PRODID_TDK_GN3410) { + /* MultiFunction Card */ + link->conf.ConfigBase = 0x800; + link->conf.ConfigIndex = 0x47; + link->io.NumPorts2 = 8; + } + break; + case MANFID_CONTEC: + cardtype = CONTEC; + break; + case MANFID_FUJITSU: + if (le16_to_cpu(buf[1]) == PRODID_FUJITSU_MBH10302) + /* RATOC REX-5588/9822/4886's PRODID are 0004(=MBH10302), + but these are MBH10304 based card. */ + cardtype = MBH10304; + else if (le16_to_cpu(buf[1]) == PRODID_FUJITSU_MBH10304) + cardtype = MBH10304; + else + cardtype = LA501; + break; + default: + cardtype = MBH10304; + } + } else { + /* old type card */ + tuple.DesiredTuple = CISTPL_MANFID; + if (pcmcia_get_first_tuple(handle, &tuple) == CS_SUCCESS) + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + else + buf[0] = 0xffff; + switch (le16_to_cpu(buf[0])) { + case MANFID_FUJITSU: + if (le16_to_cpu(buf[1]) == PRODID_FUJITSU_MBH10304) { + cardtype = XXX10304; /* MBH10304 with buggy CIS */ + link->conf.ConfigIndex = 0x20; + } else { + cardtype = MBH10302; /* NextCom NC5310, etc. */ + link->conf.ConfigIndex = 1; + } + break; + case MANFID_UNGERMANN: + cardtype = UNGERMANN; + break; + default: + cardtype = MBH10302; + link->conf.ConfigIndex = 1; + } + } + + if (link->io.NumPorts2 != 0) { + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED|IRQ_HANDLE_PRESENT; + ret = mfc_try_io_port(link); + if (ret != CS_SUCCESS) goto cs_failed; + } else if (cardtype == UNGERMANN) { + ret = ungermann_try_io_port(link); + if (ret != CS_SUCCESS) goto cs_failed; + } else { + CS_CHECK(RequestIO, pcmcia_request_io(link->handle, &link->io)); + } + CS_CHECK(RequestIRQ, pcmcia_request_irq(link->handle, &link->irq)); + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf)); + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + + if (link->io.BasePort2 != 0) + fmvj18x_setup_mfc(link); + + ioaddr = dev->base_addr; + + /* Reset controller */ + if (sram_config == 0) + outb(CONFIG0_RST, ioaddr + CONFIG_0); + else + outb(CONFIG0_RST_1, ioaddr + CONFIG_0); + + /* Power On chip and select bank 0 */ + if (cardtype == MBH10302) + outb(BANK_0, ioaddr + CONFIG_1); + else + outb(BANK_0U, ioaddr + CONFIG_1); + + /* Set hardware address */ + switch (cardtype) { + case MBH10304: + case TDK: + case LA501: + case CONTEC: + tuple.DesiredTuple = CISTPL_FUNCE; + tuple.TupleOffset = 0; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + tuple.TupleOffset = 0; + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + if (cardtype == MBH10304) { + /* MBH10304's CIS_FUNCE is corrupted */ + node_id = &(tuple.TupleData[5]); + card_name = "FMV-J182"; + } else { + while (tuple.TupleData[0] != CISTPL_FUNCE_LAN_NODE_ID ) { + CS_CHECK(GetNextTuple, pcmcia_get_next_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + } + node_id = &(tuple.TupleData[2]); + if( cardtype == TDK ) { + card_name = "TDK LAK-CD021"; + } else if( cardtype == LA501 ) { + card_name = "LA501"; + } else { + card_name = "C-NET(PC)C"; + } + } + /* Read MACID from CIS */ + for (i = 0; i < 6; i++) + dev->dev_addr[i] = node_id[i]; + break; + case UNGERMANN: + /* Read MACID from register */ + for (i = 0; i < 6; i++) + dev->dev_addr[i] = inb(ioaddr + UNGERMANN_MAC_ID + i); + card_name = "Access/CARD"; + break; + case XXX10304: + /* Read MACID from Buggy CIS */ + if (fmvj18x_get_hwinfo(link, tuple.TupleData) == -1) { + printk(KERN_NOTICE "fmvj18x_cs: unable to read hardware net address.\n"); + goto failed; + } + for (i = 0 ; i < 6; i++) { + dev->dev_addr[i] = tuple.TupleData[i]; + } + card_name = "FMV-J182"; + break; + case MBH10302: + default: + /* Read MACID from register */ + for (i = 0; i < 6; i++) + dev->dev_addr[i] = inb(ioaddr + MAC_ID + i); + card_name = "FMV-J181"; + break; + } + + lp->cardtype = cardtype; + link->dev = &lp->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + if (register_netdev(dev) != 0) { + printk(KERN_NOTICE "fmvj18x_cs: register_netdev() failed\n"); + link->dev = NULL; + goto failed; + } + + strcpy(lp->node.dev_name, dev->name); + + /* print current configuration */ + printk(KERN_INFO "%s: %s, sram %s, port %#3lx, irq %d, hw_addr ", + dev->name, card_name, sram_config == 0 ? "4K TX*2" : "8K TX*2", + dev->base_addr, dev->irq); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + + return; + +cs_failed: + /* All Card Services errors end up here */ + cs_error(link->handle, last_fn, last_ret); +failed: + fmvj18x_release(link); + link->state &= ~DEV_CONFIG_PENDING; + +} /* fmvj18x_config */ +/*====================================================================*/ + +static int fmvj18x_get_hwinfo(dev_link_t *link, u_char *node_id) +{ + win_req_t req; + memreq_t mem; + u_char __iomem *base; + int i, j; + + /* Allocate a small memory window */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = 0; req.Size = 0; + req.AccessSpeed = 0; + i = pcmcia_request_window(&link->handle, &req, &link->win); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestWindow, i); + return -1; + } + + base = ioremap(req.Base, req.Size); + mem.Page = 0; + mem.CardOffset = 0; + pcmcia_map_mem_page(link->win, &mem); + + /* + * MBH10304 CISTPL_FUNCE_LAN_NODE_ID format + * 22 0d xx xx xx 04 06 yy yy yy yy yy yy ff + * 'xx' is garbage. + * 'yy' is MAC address. + */ + for (i = 0; i < 0x200; i++) { + if (readb(base+i*2) == 0x22) { + if (readb(base+(i-1)*2) == 0xff + && readb(base+(i+5)*2) == 0x04 + && readb(base+(i+6)*2) == 0x06 + && readb(base+(i+13)*2) == 0xff) + break; + } + } + + if (i != 0x200) { + for (j = 0 ; j < 6; j++,i++) { + node_id[j] = readb(base+(i+7)*2); + } + } + + iounmap(base); + j = pcmcia_release_window(link->win); + if (j != CS_SUCCESS) + cs_error(link->handle, ReleaseWindow, j); + return (i != 0x200) ? 0 : -1; + +} /* fmvj18x_get_hwinfo */ +/*====================================================================*/ + +static int fmvj18x_setup_mfc(dev_link_t *link) +{ + win_req_t req; + memreq_t mem; + u_char __iomem *base; + int i, j; + struct net_device *dev = link->priv; + kio_addr_t ioaddr; + + /* Allocate a small memory window */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = 0; req.Size = 0; + req.AccessSpeed = 0; + i = pcmcia_request_window(&link->handle, &req, &link->win); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestWindow, i); + return -1; + } + + base = ioremap(req.Base, req.Size); + mem.Page = 0; + mem.CardOffset = 0; + pcmcia_map_mem_page(link->win, &mem); + + ioaddr = dev->base_addr; + writeb(0x47, base+0x800); /* Config Option Register of LAN */ + writeb(0x0, base+0x802); /* Config and Status Register */ + + writeb(ioaddr & 0xff, base+0x80a); /* I/O Base(Low) of LAN */ + writeb((ioaddr >> 8) & 0xff, base+0x80c); /* I/O Base(High) of LAN */ + + writeb(0x45, base+0x820); /* Config Option Register of Modem */ + writeb(0x8, base+0x822); /* Config and Status Register */ + + iounmap(base); + j = pcmcia_release_window(link->win); + if (j != CS_SUCCESS) + cs_error(link->handle, ReleaseWindow, j); + return 0; + +} +/*====================================================================*/ + +static void fmvj18x_release(dev_link_t *link) +{ + + DEBUG(0, "fmvj18x_release(0x%p)\n", link); + + /* Don't bother checking to see if these succeed or not */ + pcmcia_release_window(link->win); + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + +/*====================================================================*/ + +static int fmvj18x_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct net_device *dev = link->priv; + + DEBUG(1, "fmvj18x_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + fmvj18x_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) + netif_device_detach(dev); + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + fjn_reset(dev); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* fmvj18x_event */ + +static struct pcmcia_driver fmvj18x_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "fmvj18x_cs", + }, + .attach = fmvj18x_attach, + .detach = fmvj18x_detach, +}; + +static int __init init_fmvj18x_cs(void) +{ + return pcmcia_register_driver(&fmvj18x_cs_driver); +} + +static void __exit exit_fmvj18x_cs(void) +{ + pcmcia_unregister_driver(&fmvj18x_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_fmvj18x_cs); +module_exit(exit_fmvj18x_cs); + +/*====================================================================*/ + +static irqreturn_t fjn_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = dev_id; + local_info_t *lp = netdev_priv(dev); + kio_addr_t ioaddr; + unsigned short tx_stat, rx_stat; + + if (lp == NULL) { + printk(KERN_NOTICE "fjn_interrupt(): irq %d for " + "unknown device.\n", irq); + return IRQ_NONE; + } + ioaddr = dev->base_addr; + + /* avoid multiple interrupts */ + outw(0x0000, ioaddr + TX_INTR); + + /* wait for a while */ + udelay(1); + + /* get status */ + tx_stat = inb(ioaddr + TX_STATUS); + rx_stat = inb(ioaddr + RX_STATUS); + + /* clear status */ + outb(tx_stat, ioaddr + TX_STATUS); + outb(rx_stat, ioaddr + RX_STATUS); + + DEBUG(4, "%s: interrupt, rx_status %02x.\n", dev->name, rx_stat); + DEBUG(4, " tx_status %02x.\n", tx_stat); + + if (rx_stat || (inb(ioaddr + RX_MODE) & F_BUF_EMP) == 0) { + /* there is packet(s) in rx buffer */ + fjn_rx(dev); + } + if (tx_stat & F_TMT_RDY) { + lp->stats.tx_packets += lp->sent ; + lp->sent = 0 ; + if (lp->tx_queue) { + outb(DO_TX | lp->tx_queue, ioaddr + TX_START); + lp->sent = lp->tx_queue ; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + dev->trans_start = jiffies; + } else { + lp->tx_started = 0; + } + netif_wake_queue(dev); + } + DEBUG(4, "%s: exiting interrupt,\n", dev->name); + DEBUG(4, " tx_status %02x, rx_status %02x.\n", tx_stat, rx_stat); + + outb(D_TX_INTR, ioaddr + TX_INTR); + outb(D_RX_INTR, ioaddr + RX_INTR); + return IRQ_HANDLED; + +} /* fjn_interrupt */ + +/*====================================================================*/ + +static void fjn_tx_timeout(struct net_device *dev) +{ + struct local_info_t *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + + printk(KERN_NOTICE "%s: transmit timed out with status %04x, %s?\n", + dev->name, htons(inw(ioaddr + TX_STATUS)), + inb(ioaddr + TX_STATUS) & F_TMT_RDY + ? "IRQ conflict" : "network cable problem"); + printk(KERN_NOTICE "%s: timeout registers: %04x %04x %04x " + "%04x %04x %04x %04x %04x.\n", + dev->name, htons(inw(ioaddr + 0)), + htons(inw(ioaddr + 2)), htons(inw(ioaddr + 4)), + htons(inw(ioaddr + 6)), htons(inw(ioaddr + 8)), + htons(inw(ioaddr +10)), htons(inw(ioaddr +12)), + htons(inw(ioaddr +14))); + lp->stats.tx_errors++; + /* ToDo: We should try to restart the adaptor... */ + local_irq_disable(); + fjn_reset(dev); + + lp->tx_started = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + lp->sent = 0; + lp->open_time = jiffies; + local_irq_enable(); + netif_wake_queue(dev); +} + +static int fjn_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct local_info_t *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + short length = skb->len; + + if (length < ETH_ZLEN) + { + skb = skb_padto(skb, ETH_ZLEN); + if (skb == NULL) + return 0; + length = ETH_ZLEN; + } + + netif_stop_queue(dev); + + { + unsigned char *buf = skb->data; + + if (length > ETH_FRAME_LEN) { + printk(KERN_NOTICE "%s: Attempting to send a large packet" + " (%d bytes).\n", dev->name, length); + return 1; + } + + DEBUG(4, "%s: Transmitting a packet of length %lu.\n", + dev->name, (unsigned long)skb->len); + lp->stats.tx_bytes += skb->len; + + /* Disable both interrupts. */ + outw(0x0000, ioaddr + TX_INTR); + + /* wait for a while */ + udelay(1); + + outw(length, ioaddr + DATAPORT); + outsw(ioaddr + DATAPORT, buf, (length + 1) >> 1); + + lp->tx_queue++; + lp->tx_queue_len += ((length+3) & ~1); + + if (lp->tx_started == 0) { + /* If the Tx is idle, always trigger a transmit. */ + outb(DO_TX | lp->tx_queue, ioaddr + TX_START); + lp->sent = lp->tx_queue ; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + dev->trans_start = jiffies; + lp->tx_started = 1; + netif_start_queue(dev); + } else { + if( sram_config == 0 ) { + if (lp->tx_queue_len < (4096 - (ETH_FRAME_LEN +2)) ) + /* Yes, there is room for one more packet. */ + netif_start_queue(dev); + } else { + if (lp->tx_queue_len < (8192 - (ETH_FRAME_LEN +2)) && + lp->tx_queue < 127 ) + /* Yes, there is room for one more packet. */ + netif_start_queue(dev); + } + } + + /* Re-enable interrupts */ + outb(D_TX_INTR, ioaddr + TX_INTR); + outb(D_RX_INTR, ioaddr + RX_INTR); + } + dev_kfree_skb (skb); + + return 0; +} /* fjn_start_xmit */ + +/*====================================================================*/ + +static void fjn_reset(struct net_device *dev) +{ + struct local_info_t *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + int i; + + DEBUG(4, "fjn_reset(%s) called.\n",dev->name); + + /* Reset controller */ + if( sram_config == 0 ) + outb(CONFIG0_RST, ioaddr + CONFIG_0); + else + outb(CONFIG0_RST_1, ioaddr + CONFIG_0); + + /* Power On chip and select bank 0 */ + if (lp->cardtype == MBH10302) + outb(BANK_0, ioaddr + CONFIG_1); + else + outb(BANK_0U, ioaddr + CONFIG_1); + + /* Set Tx modes */ + outb(D_TX_MODE, ioaddr + TX_MODE); + /* set Rx modes */ + outb(ID_MATCHED, ioaddr + RX_MODE); + + /* Set hardware address */ + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + NODE_ID + i); + + /* Switch to bank 1 */ + if (lp->cardtype == MBH10302) + outb(BANK_1, ioaddr + CONFIG_1); + else + outb(BANK_1U, ioaddr + CONFIG_1); + + /* set the multicast table to accept none. */ + for (i = 0; i < 6; i++) + outb(0x00, ioaddr + MAR_ADR + i); + + /* Switch to bank 2 (runtime mode) */ + if (lp->cardtype == MBH10302) + outb(BANK_2, ioaddr + CONFIG_1); + else + outb(BANK_2U, ioaddr + CONFIG_1); + + /* set 16col ctrl bits */ + if( lp->cardtype == TDK || lp->cardtype == CONTEC) + outb(TDK_AUTO_MODE, ioaddr + COL_CTRL); + else + outb(AUTO_MODE, ioaddr + COL_CTRL); + + /* clear Reserved Regs */ + outb(0x00, ioaddr + BMPR12); + outb(0x00, ioaddr + BMPR13); + + /* reset Skip packet reg. */ + outb(0x01, ioaddr + RX_SKIP); + + /* Enable Tx and Rx */ + if( sram_config == 0 ) + outb(CONFIG0_DFL, ioaddr + CONFIG_0); + else + outb(CONFIG0_DFL_1, ioaddr + CONFIG_0); + + /* Init receive pointer ? */ + inw(ioaddr + DATAPORT); + inw(ioaddr + DATAPORT); + + /* Clear all status */ + outb(0xff, ioaddr + TX_STATUS); + outb(0xff, ioaddr + RX_STATUS); + + if (lp->cardtype == MBH10302) + outb(INTR_OFF, ioaddr + LAN_CTRL); + + /* Turn on Rx interrupts */ + outb(D_TX_INTR, ioaddr + TX_INTR); + outb(D_RX_INTR, ioaddr + RX_INTR); + + /* Turn on interrupts from LAN card controller */ + if (lp->cardtype == MBH10302) + outb(INTR_ON, ioaddr + LAN_CTRL); +} /* fjn_reset */ + +/*====================================================================*/ + +static void fjn_rx(struct net_device *dev) +{ + struct local_info_t *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + int boguscount = 10; /* 5 -> 10: by agy 19940922 */ + + DEBUG(4, "%s: in rx_packet(), rx_status %02x.\n", + dev->name, inb(ioaddr + RX_STATUS)); + + while ((inb(ioaddr + RX_MODE) & F_BUF_EMP) == 0) { + u_short status = inw(ioaddr + DATAPORT); + + DEBUG(4, "%s: Rxing packet mode %02x status %04x.\n", + dev->name, inb(ioaddr + RX_MODE), status); +#ifndef final_version + if (status == 0) { + outb(F_SKP_PKT, ioaddr + RX_SKIP); + break; + } +#endif + if ((status & 0xF0) != 0x20) { /* There was an error. */ + lp->stats.rx_errors++; + if (status & F_LEN_ERR) lp->stats.rx_length_errors++; + if (status & F_ALG_ERR) lp->stats.rx_frame_errors++; + if (status & F_CRC_ERR) lp->stats.rx_crc_errors++; + if (status & F_OVR_FLO) lp->stats.rx_over_errors++; + } else { + u_short pkt_len = inw(ioaddr + DATAPORT); + /* Malloc up new buffer. */ + struct sk_buff *skb; + + if (pkt_len > 1550) { + printk(KERN_NOTICE "%s: The FMV-18x claimed a very " + "large packet, size %d.\n", dev->name, pkt_len); + outb(F_SKP_PKT, ioaddr + RX_SKIP); + lp->stats.rx_errors++; + break; + } + skb = dev_alloc_skb(pkt_len+2); + if (skb == NULL) { + printk(KERN_NOTICE "%s: Memory squeeze, dropping " + "packet (len %d).\n", dev->name, pkt_len); + outb(F_SKP_PKT, ioaddr + RX_SKIP); + lp->stats.rx_dropped++; + break; + } + skb->dev = dev; + + skb_reserve(skb, 2); + insw(ioaddr + DATAPORT, skb_put(skb, pkt_len), + (pkt_len + 1) >> 1); + skb->protocol = eth_type_trans(skb, dev); + +#ifdef PCMCIA_DEBUG + if (pc_debug > 5) { + int i; + printk(KERN_DEBUG "%s: Rxed packet of length %d: ", + dev->name, pkt_len); + for (i = 0; i < 14; i++) + printk(" %02x", skb->data[i]); + printk(".\n"); + } +#endif + + netif_rx(skb); + dev->last_rx = jiffies; + lp->stats.rx_packets++; + lp->stats.rx_bytes += pkt_len; + } + if (--boguscount <= 0) + break; + } + + /* If any worth-while packets have been received, dev_rint() + has done a netif_wake_queue() for us and will work on them + when we get to the bottom-half routine. */ +/* + if (lp->cardtype != TDK) { + int i; + for (i = 0; i < 20; i++) { + if ((inb(ioaddr + RX_MODE) & F_BUF_EMP) == F_BUF_EMP) + break; + (void)inw(ioaddr + DATAPORT); /+ dummy status read +/ + outb(F_SKP_PKT, ioaddr + RX_SKIP); + } + + if (i > 0) + DEBUG(5, "%s: Exint Rx packet with mode %02x after " + "%d ticks.\n", dev->name, inb(ioaddr + RX_MODE), i); + } +*/ + + return; +} /* fjn_rx */ + +/*====================================================================*/ + +static void netdev_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, DRV_NAME); + strcpy(info->version, DRV_VERSION); + sprintf(info->bus_info, "PCMCIA 0x%lx", dev->base_addr); +} + +#ifdef PCMCIA_DEBUG +static u32 netdev_get_msglevel(struct net_device *dev) +{ + return pc_debug; +} + +static void netdev_set_msglevel(struct net_device *dev, u32 level) +{ + pc_debug = level; +} +#endif /* PCMCIA_DEBUG */ + +static struct ethtool_ops netdev_ethtool_ops = { + .get_drvinfo = netdev_get_drvinfo, +#ifdef PCMCIA_DEBUG + .get_msglevel = netdev_get_msglevel, + .set_msglevel = netdev_set_msglevel, +#endif /* PCMCIA_DEBUG */ +}; + +static int fjn_config(struct net_device *dev, struct ifmap *map){ + return 0; +} + +static int fjn_open(struct net_device *dev) +{ + struct local_info_t *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + + DEBUG(4, "fjn_open('%s').\n", dev->name); + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + + fjn_reset(dev); + + lp->tx_started = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + lp->open_time = jiffies; + netif_start_queue(dev); + + return 0; +} /* fjn_open */ + +/*====================================================================*/ + +static int fjn_close(struct net_device *dev) +{ + struct local_info_t *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + kio_addr_t ioaddr = dev->base_addr; + + DEBUG(4, "fjn_close('%s').\n", dev->name); + + lp->open_time = 0; + netif_stop_queue(dev); + + /* Set configuration register 0 to disable Tx and Rx. */ + if( sram_config == 0 ) + outb(CONFIG0_RST ,ioaddr + CONFIG_0); + else + outb(CONFIG0_RST_1 ,ioaddr + CONFIG_0); + + /* Update the statistics -- ToDo. */ + + /* Power-down the chip. Green, green, green! */ + outb(CHIP_OFF ,ioaddr + CONFIG_1); + + /* Set the ethernet adaptor disable IRQ */ + if (lp->cardtype == MBH10302) + outb(INTR_OFF, ioaddr + LAN_CTRL); + + link->open--; + + return 0; +} /* fjn_close */ + +/*====================================================================*/ + +static struct net_device_stats *fjn_get_stats(struct net_device *dev) +{ + local_info_t *lp = netdev_priv(dev); + return &lp->stats; +} /* fjn_get_stats */ + +/*====================================================================*/ + +/* + Set the multicast/promiscuous mode for this adaptor. +*/ + +static void set_rx_mode(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + struct local_info_t *lp = netdev_priv(dev); + u_char mc_filter[8]; /* Multicast hash filter */ + u_long flags; + int i; + + if (dev->flags & IFF_PROMISC) { + /* Unconditionally log net taps. */ + printk("%s: Promiscuous mode enabled.\n", dev->name); + memset(mc_filter, 0xff, sizeof(mc_filter)); + outb(3, ioaddr + RX_MODE); /* Enable promiscuous mode */ + } else if (dev->mc_count > MC_FILTERBREAK + || (dev->flags & IFF_ALLMULTI)) { + /* Too many to filter perfectly -- accept all multicasts. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + outb(2, ioaddr + RX_MODE); /* Use normal mode. */ + } else if (dev->mc_count == 0) { + memset(mc_filter, 0x00, sizeof(mc_filter)); + outb(1, ioaddr + RX_MODE); /* Ignore almost all multicasts. */ + } else { + struct dev_mc_list *mclist; + int i; + + memset(mc_filter, 0, sizeof(mc_filter)); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) { + unsigned int bit = + ether_crc_le(ETH_ALEN, mclist->dmi_addr) & 0x3f; + mc_filter[bit >> 3] |= (1 << bit); + } + } + + local_irq_save(flags); + if (memcmp(mc_filter, lp->mc_filter, sizeof(mc_filter))) { + int saved_bank = inb(ioaddr + CONFIG_1); + /* Switch to bank 1 and set the multicast table. */ + outb(0xe4, ioaddr + CONFIG_1); + for (i = 0; i < 8; i++) + outb(mc_filter[i], ioaddr + 8 + i); + memcpy(lp->mc_filter, mc_filter, sizeof(mc_filter)); + outb(saved_bank, ioaddr + CONFIG_1); + } + local_irq_restore(flags); +} diff --git a/drivers/net/pcmcia/ibmtr_cs.c b/drivers/net/pcmcia/ibmtr_cs.c new file mode 100644 index 00000000000..3107ccfe8f3 --- /dev/null +++ b/drivers/net/pcmcia/ibmtr_cs.c @@ -0,0 +1,535 @@ +/*====================================================================== + + A PCMCIA token-ring driver for IBM-based cards + + This driver supports the IBM PCMCIA Token-Ring Card. + Written by Steve Kipisz, kipisz@vnet.ibm.com or + bungy@ibm.net + + Written 1995,1996. + + This code is based on pcnet_cs.c from David Hinds. + + V2.2.0 February 1999 - Mike Phillips phillim@amtrak.com + + Linux V2.2.x presented significant changes to the underlying + ibmtr.c code. Mainly the code became a lot more organized and + modular. + + This caused the old PCMCIA Token Ring driver to give up and go + home early. Instead of just patching the old code to make it + work, the PCMCIA code has been streamlined, updated and possibly + improved. + + This code now only contains code required for the Card Services. + All we do here is set the card up enough so that the real ibmtr.c + driver can find it and work with it properly. + + i.e. We set up the io port, irq, mmio memory and shared ram + memory. This enables ibmtr_probe in ibmtr.c to find the card and + configure it as though it was a normal ISA and/or PnP card. + + CHANGES + + v2.2.5 April 1999 Mike Phillips (phillim@amtrak.com) + Obscure bug fix, required changed to ibmtr.c not ibmtr_cs.c + + v2.2.7 May 1999 Mike Phillips (phillim@amtrak.com) + Updated to version 2.2.7 to match the first version of the kernel + that the modification to ibmtr.c were incorporated into. + + v2.2.17 July 2000 Burt Silverman (burts@us.ibm.com) + Address translation feature of PCMCIA controller is usable so + memory windows can be placed in High memory (meaning above + 0xFFFFF.) + +======================================================================*/ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/module.h> +#include <linux/ethtool.h> +#include <linux/netdevice.h> +#include <linux/trdevice.h> +#include <linux/ibmtr.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/system.h> + +#define PCMCIA +#include "../tokenring/ibmtr.c" + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"ibmtr_cs.c 1.10 1996/01/06 05:19:00 (Steve Kipisz)\n" +" 2.2.7 1999/05/03 12:00:00 (Mike Phillips)\n" +" 2.4.2 2001/30/28 Midnight (Burt Silverman)\n"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +/* MMIO base address */ +static u_long mmiobase = 0xce000; + +/* SRAM base address */ +static u_long srambase = 0xd0000; + +/* SRAM size 8,16,32,64 */ +static u_long sramsize = 64; + +/* Ringspeed 4,16 */ +static int ringspeed = 16; + +module_param(mmiobase, ulong, 0); +module_param(srambase, ulong, 0); +module_param(sramsize, ulong, 0); +module_param(ringspeed, int, 0); +MODULE_LICENSE("GPL"); + +/*====================================================================*/ + +static void ibmtr_config(dev_link_t *link); +static void ibmtr_hw_setup(struct net_device *dev, u_int mmiobase); +static void ibmtr_release(dev_link_t *link); +static int ibmtr_event(event_t event, int priority, + event_callback_args_t *args); + +static dev_info_t dev_info = "ibmtr_cs"; + +static dev_link_t *ibmtr_attach(void); +static void ibmtr_detach(dev_link_t *); + +static dev_link_t *dev_list; + +extern int ibmtr_probe_card(struct net_device *dev); +extern irqreturn_t tok_interrupt (int irq, void *dev_id, struct pt_regs *regs); + +/*====================================================================*/ + +typedef struct ibmtr_dev_t { + dev_link_t link; + struct net_device *dev; + dev_node_t node; + window_handle_t sram_win_handle; + struct tok_info *ti; +} ibmtr_dev_t; + +static void netdev_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, "ibmtr_cs"); +} + +static struct ethtool_ops netdev_ethtool_ops = { + .get_drvinfo = netdev_get_drvinfo, +}; + +/*====================================================================== + + ibmtr_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *ibmtr_attach(void) +{ + ibmtr_dev_t *info; + dev_link_t *link; + struct net_device *dev; + client_reg_t client_reg; + int ret; + + DEBUG(0, "ibmtr_attach()\n"); + + /* Create new token-ring device */ + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) return NULL; + memset(info,0,sizeof(*info)); + dev = alloc_trdev(sizeof(struct tok_info)); + if (!dev) { + kfree(info); + return NULL; + } + + link = &info->link; + link->priv = info; + info->ti = netdev_priv(dev); + + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts1 = 4; + link->io.IOAddrLines = 16; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->irq.Handler = &tok_interrupt; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.Present = PRESENT_OPTION; + + link->irq.Instance = info->dev = dev; + + SET_ETHTOOL_OPS(dev, &netdev_ethtool_ops); + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &ibmtr_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + goto out_detach; + } + +out: + return link; + +out_detach: + ibmtr_detach(link); + link = NULL; + goto out; +} /* ibmtr_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void ibmtr_detach(dev_link_t *link) +{ + struct ibmtr_dev_t *info = link->priv; + dev_link_t **linkp; + struct net_device *dev; + + DEBUG(0, "ibmtr_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + dev = info->dev; + + if (link->dev) + unregister_netdev(dev); + + { + struct tok_info *ti = netdev_priv(dev); + del_timer_sync(&(ti->tr_timer)); + } + if (link->state & DEV_CONFIG) + ibmtr_release(link); + + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + free_netdev(dev); + kfree(info); +} /* ibmtr_detach */ + +/*====================================================================== + + ibmtr_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + token-ring device available to the system. + +======================================================================*/ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static void ibmtr_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + ibmtr_dev_t *info = link->priv; + struct net_device *dev = info->dev; + struct tok_info *ti = netdev_priv(dev); + tuple_t tuple; + cisparse_t parse; + win_req_t req; + memreq_t mem; + int i, last_ret, last_fn; + u_char buf[64]; + + DEBUG(0, "ibmtr_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + + /* Configure card */ + link->state |= DEV_CONFIG; + + link->conf.ConfigIndex = 0x61; + + /* Determine if this is PRIMARY or ALTERNATE. */ + + /* Try PRIMARY card at 0xA20-0xA23 */ + link->io.BasePort1 = 0xA20; + i = pcmcia_request_io(link->handle, &link->io); + if (i != CS_SUCCESS) { + /* Couldn't get 0xA20-0xA23. Try ALTERNATE at 0xA24-0xA27. */ + link->io.BasePort1 = 0xA24; + CS_CHECK(RequestIO, pcmcia_request_io(link->handle, &link->io)); + } + dev->base_addr = link->io.BasePort1; + + CS_CHECK(RequestIRQ, pcmcia_request_irq(link->handle, &link->irq)); + dev->irq = link->irq.AssignedIRQ; + ti->irq = link->irq.AssignedIRQ; + ti->global_int_enable=GLOBAL_INT_ENABLE+((dev->irq==9) ? 2 : dev->irq); + + /* Allocate the MMIO memory window */ + req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM|WIN_ENABLE; + req.Attributes |= WIN_USE_WAIT; + req.Base = 0; + req.Size = 0x2000; + req.AccessSpeed = 250; + CS_CHECK(RequestWindow, pcmcia_request_window(&link->handle, &req, &link->win)); + + mem.CardOffset = mmiobase; + mem.Page = 0; + CS_CHECK(MapMemPage, pcmcia_map_mem_page(link->win, &mem)); + ti->mmio = ioremap(req.Base, req.Size); + + /* Allocate the SRAM memory window */ + req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM|WIN_ENABLE; + req.Attributes |= WIN_USE_WAIT; + req.Base = 0; + req.Size = sramsize * 1024; + req.AccessSpeed = 250; + CS_CHECK(RequestWindow, pcmcia_request_window(&link->handle, &req, &info->sram_win_handle)); + + mem.CardOffset = srambase; + mem.Page = 0; + CS_CHECK(MapMemPage, pcmcia_map_mem_page(info->sram_win_handle, &mem)); + + ti->sram_base = mem.CardOffset >> 12; + ti->sram_virt = ioremap(req.Base, req.Size); + ti->sram_phys = req.Base; + + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf)); + + /* Set up the Token-Ring Controller Configuration Register and + turn on the card. Check the "Local Area Network Credit Card + Adapters Technical Reference" SC30-3585 for this info. */ + ibmtr_hw_setup(dev, mmiobase); + + link->dev = &info->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + i = ibmtr_probe_card(dev); + if (i != 0) { + printk(KERN_NOTICE "ibmtr_cs: register_netdev() failed\n"); + link->dev = NULL; + goto failed; + } + + strcpy(info->node.dev_name, dev->name); + + printk(KERN_INFO "%s: port %#3lx, irq %d,", + dev->name, dev->base_addr, dev->irq); + printk (" mmio %#5lx,", (u_long)ti->mmio); + printk (" sram %#5lx,", (u_long)ti->sram_base << 12); + printk ("\n" KERN_INFO " hwaddr="); + for (i = 0; i < TR_ALEN; i++) + printk("%02X", dev->dev_addr[i]); + printk("\n"); + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + ibmtr_release(link); +} /* ibmtr_config */ + +/*====================================================================== + + After a card is removed, ibmtr_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void ibmtr_release(dev_link_t *link) +{ + ibmtr_dev_t *info = link->priv; + struct net_device *dev = info->dev; + + DEBUG(0, "ibmtr_release(0x%p)\n", link); + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + if (link->win) { + struct tok_info *ti = netdev_priv(dev); + iounmap(ti->mmio); + pcmcia_release_window(link->win); + pcmcia_release_window(info->sram_win_handle); + } + + link->state &= ~DEV_CONFIG; +} + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + +======================================================================*/ + +static int ibmtr_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + ibmtr_dev_t *info = link->priv; + struct net_device *dev = info->dev; + + DEBUG(1, "ibmtr_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + /* set flag to bypass normal interrupt code */ + struct tok_info *priv = netdev_priv(dev); + priv->sram_phys |= 1; + netif_device_detach(dev); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT; + ibmtr_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) + netif_device_detach(dev); + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + ibmtr_probe(dev); /* really? */ + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* ibmtr_event */ + +/*====================================================================*/ + +static void ibmtr_hw_setup(struct net_device *dev, u_int mmiobase) +{ + int i; + + /* Bizarre IBM behavior, there are 16 bits of information we + need to set, but the card only allows us to send 4 bits at a + time. For each byte sent to base_addr, bits 7-4 tell the + card which part of the 16 bits we are setting, bits 3-0 contain + the actual information */ + + /* First nibble provides 4 bits of mmio */ + i = (mmiobase >> 16) & 0x0F; + outb(i, dev->base_addr); + + /* Second nibble provides 3 bits of mmio */ + i = 0x10 | ((mmiobase >> 12) & 0x0E); + outb(i, dev->base_addr); + + /* Third nibble, hard-coded values */ + i = 0x26; + outb(i, dev->base_addr); + + /* Fourth nibble sets shared ram page size */ + + /* 8 = 00, 16 = 01, 32 = 10, 64 = 11 */ + i = (sramsize >> 4) & 0x07; + i = ((i == 4) ? 3 : i) << 2; + i |= 0x30; + + if (ringspeed == 16) + i |= 2; + if (dev->base_addr == 0xA24) + i |= 1; + outb(i, dev->base_addr); + + /* 0x40 will release the card for use */ + outb(0x40, dev->base_addr); + + return; +} + +static struct pcmcia_driver ibmtr_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "ibmtr_cs", + }, + .attach = ibmtr_attach, + .detach = ibmtr_detach, +}; + +static int __init init_ibmtr_cs(void) +{ + return pcmcia_register_driver(&ibmtr_cs_driver); +} + +static void __exit exit_ibmtr_cs(void) +{ + pcmcia_unregister_driver(&ibmtr_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_ibmtr_cs); +module_exit(exit_ibmtr_cs); diff --git a/drivers/net/pcmcia/nmclan_cs.c b/drivers/net/pcmcia/nmclan_cs.c new file mode 100644 index 00000000000..4603807fcaf --- /dev/null +++ b/drivers/net/pcmcia/nmclan_cs.c @@ -0,0 +1,1699 @@ +/* ---------------------------------------------------------------------------- +Linux PCMCIA ethernet adapter driver for the New Media Ethernet LAN. + nmclan_cs.c,v 0.16 1995/07/01 06:42:17 rpao Exp rpao + + The Ethernet LAN uses the Advanced Micro Devices (AMD) Am79C940 Media + Access Controller for Ethernet (MACE). It is essentially the Am2150 + PCMCIA Ethernet card contained in the Am2150 Demo Kit. + +Written by Roger C. Pao <rpao@paonet.org> + Copyright 1995 Roger C. Pao + Linux 2.5 cleanups Copyright Red Hat 2003 + + This software may be used and distributed according to the terms of + the GNU General Public License. + +Ported to Linux 1.3.* network driver environment by + Matti Aarnio <mea@utu.fi> + +References + + Am2150 Technical Reference Manual, Revision 1.0, August 17, 1993 + Am79C940 (MACE) Data Sheet, 1994 + Am79C90 (C-LANCE) Data Sheet, 1994 + Linux PCMCIA Programmer's Guide v1.17 + /usr/src/linux/net/inet/dev.c, Linux kernel 1.2.8 + + Eric Mears, New Media Corporation + Tom Pollard, New Media Corporation + Dean Siasoyco, New Media Corporation + Ken Lesniak, Silicon Graphics, Inc. <lesniak@boston.sgi.com> + Donald Becker <becker@scyld.com> + David Hinds <dahinds@users.sourceforge.net> + + The Linux client driver is based on the 3c589_cs.c client driver by + David Hinds. + + The Linux network driver outline is based on the 3c589_cs.c driver, + the 8390.c driver, and the example skeleton.c kernel code, which are + by Donald Becker. + + The Am2150 network driver hardware interface code is based on the + OS/9000 driver for the New Media Ethernet LAN by Eric Mears. + + Special thanks for testing and help in debugging this driver goes + to Ken Lesniak. + +------------------------------------------------------------------------------- +Driver Notes and Issues +------------------------------------------------------------------------------- + +1. Developed on a Dell 320SLi + PCMCIA Card Services 2.6.2 + Linux dell 1.2.10 #1 Thu Jun 29 20:23:41 PDT 1995 i386 + +2. rc.pcmcia may require loading pcmcia_core with io_speed=300: + 'insmod pcmcia_core.o io_speed=300'. + This will avoid problems with fast systems which causes rx_framecnt + to return random values. + +3. If hot extraction does not work for you, use 'ifconfig eth0 down' + before extraction. + +4. There is a bad slow-down problem in this driver. + +5. Future: Multicast processing. In the meantime, do _not_ compile your + kernel with multicast ip enabled. + +------------------------------------------------------------------------------- +History +------------------------------------------------------------------------------- +Log: nmclan_cs.c,v + * 2.5.75-ac1 2003/07/11 Alan Cox <alan@redhat.com> + * Fixed hang on card eject as we probe it + * Cleaned up to use new style locking. + * + * Revision 0.16 1995/07/01 06:42:17 rpao + * Bug fix: nmclan_reset() called CardServices incorrectly. + * + * Revision 0.15 1995/05/24 08:09:47 rpao + * Re-implement MULTI_TX dev->tbusy handling. + * + * Revision 0.14 1995/05/23 03:19:30 rpao + * Added, in nmclan_config(), "tuple.Attributes = 0;". + * Modified MACE ID check to ignore chip revision level. + * Avoid tx_free_frames race condition between _start_xmit and _interrupt. + * + * Revision 0.13 1995/05/18 05:56:34 rpao + * Statistics changes. + * Bug fix: nmclan_reset did not enable TX and RX: call restore_multicast_list. + * Bug fix: mace_interrupt checks ~MACE_IMR_DEFAULT. Fixes driver lockup. + * + * Revision 0.12 1995/05/14 00:12:23 rpao + * Statistics overhaul. + * + +95/05/13 rpao V0.10a + Bug fix: MACE statistics counters used wrong I/O ports. + Bug fix: mace_interrupt() needed to allow statistics to be + processed without RX or TX interrupts pending. +95/05/11 rpao V0.10 + Multiple transmit request processing. + Modified statistics to use MACE counters where possible. +95/05/10 rpao V0.09 Bug fix: Must use IO_DATA_PATH_WIDTH_AUTO. + *Released +95/05/10 rpao V0.08 + Bug fix: Make all non-exported functions private by using + static keyword. + Bug fix: Test IntrCnt _before_ reading MACE_IR. +95/05/10 rpao V0.07 Statistics. +95/05/09 rpao V0.06 Fix rx_framecnt problem by addition of PCIC wait states. + +---------------------------------------------------------------------------- */ + +#define DRV_NAME "nmclan_cs" +#define DRV_VERSION "0.16" + + +/* ---------------------------------------------------------------------------- +Conditional Compilation Options +---------------------------------------------------------------------------- */ + +#define MULTI_TX 0 +#define RESET_ON_TIMEOUT 1 +#define TX_INTERRUPTABLE 1 +#define RESET_XILINX 0 + +/* ---------------------------------------------------------------------------- +Include Files +---------------------------------------------------------------------------- */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <linux/ethtool.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> +#include <linux/bitops.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/system.h> + +/* ---------------------------------------------------------------------------- +Defines +---------------------------------------------------------------------------- */ + +#define ETHER_ADDR_LEN ETH_ALEN + /* 6 bytes in an Ethernet Address */ +#define MACE_LADRF_LEN 8 + /* 8 bytes in Logical Address Filter */ + +/* Loop Control Defines */ +#define MACE_MAX_IR_ITERATIONS 10 +#define MACE_MAX_RX_ITERATIONS 12 + /* + TBD: Dean brought this up, and I assumed the hardware would + handle it: + + If MACE_MAX_RX_ITERATIONS is > 1, rx_framecnt may still be + non-zero when the isr exits. We may not get another interrupt + to process the remaining packets for some time. + */ + +/* +The Am2150 has a Xilinx XC3042 field programmable gate array (FPGA) +which manages the interface between the MACE and the PCMCIA bus. It +also includes buffer management for the 32K x 8 SRAM to control up to +four transmit and 12 receive frames at a time. +*/ +#define AM2150_MAX_TX_FRAMES 4 +#define AM2150_MAX_RX_FRAMES 12 + +/* Am2150 Ethernet Card I/O Mapping */ +#define AM2150_RCV 0x00 +#define AM2150_XMT 0x04 +#define AM2150_XMT_SKIP 0x09 +#define AM2150_RCV_NEXT 0x0A +#define AM2150_RCV_FRAME_COUNT 0x0B +#define AM2150_MACE_BANK 0x0C +#define AM2150_MACE_BASE 0x10 + +/* MACE Registers */ +#define MACE_RCVFIFO 0 +#define MACE_XMTFIFO 1 +#define MACE_XMTFC 2 +#define MACE_XMTFS 3 +#define MACE_XMTRC 4 +#define MACE_RCVFC 5 +#define MACE_RCVFS 6 +#define MACE_FIFOFC 7 +#define MACE_IR 8 +#define MACE_IMR 9 +#define MACE_PR 10 +#define MACE_BIUCC 11 +#define MACE_FIFOCC 12 +#define MACE_MACCC 13 +#define MACE_PLSCC 14 +#define MACE_PHYCC 15 +#define MACE_CHIPIDL 16 +#define MACE_CHIPIDH 17 +#define MACE_IAC 18 +/* Reserved */ +#define MACE_LADRF 20 +#define MACE_PADR 21 +/* Reserved */ +/* Reserved */ +#define MACE_MPC 24 +/* Reserved */ +#define MACE_RNTPC 26 +#define MACE_RCVCC 27 +/* Reserved */ +#define MACE_UTR 29 +#define MACE_RTR1 30 +#define MACE_RTR2 31 + +/* MACE Bit Masks */ +#define MACE_XMTRC_EXDEF 0x80 +#define MACE_XMTRC_XMTRC 0x0F + +#define MACE_XMTFS_XMTSV 0x80 +#define MACE_XMTFS_UFLO 0x40 +#define MACE_XMTFS_LCOL 0x20 +#define MACE_XMTFS_MORE 0x10 +#define MACE_XMTFS_ONE 0x08 +#define MACE_XMTFS_DEFER 0x04 +#define MACE_XMTFS_LCAR 0x02 +#define MACE_XMTFS_RTRY 0x01 + +#define MACE_RCVFS_RCVSTS 0xF000 +#define MACE_RCVFS_OFLO 0x8000 +#define MACE_RCVFS_CLSN 0x4000 +#define MACE_RCVFS_FRAM 0x2000 +#define MACE_RCVFS_FCS 0x1000 + +#define MACE_FIFOFC_RCVFC 0xF0 +#define MACE_FIFOFC_XMTFC 0x0F + +#define MACE_IR_JAB 0x80 +#define MACE_IR_BABL 0x40 +#define MACE_IR_CERR 0x20 +#define MACE_IR_RCVCCO 0x10 +#define MACE_IR_RNTPCO 0x08 +#define MACE_IR_MPCO 0x04 +#define MACE_IR_RCVINT 0x02 +#define MACE_IR_XMTINT 0x01 + +#define MACE_MACCC_PROM 0x80 +#define MACE_MACCC_DXMT2PD 0x40 +#define MACE_MACCC_EMBA 0x20 +#define MACE_MACCC_RESERVED 0x10 +#define MACE_MACCC_DRCVPA 0x08 +#define MACE_MACCC_DRCVBC 0x04 +#define MACE_MACCC_ENXMT 0x02 +#define MACE_MACCC_ENRCV 0x01 + +#define MACE_PHYCC_LNKFL 0x80 +#define MACE_PHYCC_DLNKTST 0x40 +#define MACE_PHYCC_REVPOL 0x20 +#define MACE_PHYCC_DAPC 0x10 +#define MACE_PHYCC_LRT 0x08 +#define MACE_PHYCC_ASEL 0x04 +#define MACE_PHYCC_RWAKE 0x02 +#define MACE_PHYCC_AWAKE 0x01 + +#define MACE_IAC_ADDRCHG 0x80 +#define MACE_IAC_PHYADDR 0x04 +#define MACE_IAC_LOGADDR 0x02 + +#define MACE_UTR_RTRE 0x80 +#define MACE_UTR_RTRD 0x40 +#define MACE_UTR_RPA 0x20 +#define MACE_UTR_FCOLL 0x10 +#define MACE_UTR_RCVFCSE 0x08 +#define MACE_UTR_LOOP_INCL_MENDEC 0x06 +#define MACE_UTR_LOOP_NO_MENDEC 0x04 +#define MACE_UTR_LOOP_EXTERNAL 0x02 +#define MACE_UTR_LOOP_NONE 0x00 +#define MACE_UTR_RESERVED 0x01 + +/* Switch MACE register bank (only 0 and 1 are valid) */ +#define MACEBANK(win_num) outb((win_num), ioaddr + AM2150_MACE_BANK) + +#define MACE_IMR_DEFAULT \ + (0xFF - \ + ( \ + MACE_IR_CERR | \ + MACE_IR_RCVCCO | \ + MACE_IR_RNTPCO | \ + MACE_IR_MPCO | \ + MACE_IR_RCVINT | \ + MACE_IR_XMTINT \ + ) \ + ) +#undef MACE_IMR_DEFAULT +#define MACE_IMR_DEFAULT 0x00 /* New statistics handling: grab everything */ + +#define TX_TIMEOUT ((400*HZ)/1000) + +/* ---------------------------------------------------------------------------- +Type Definitions +---------------------------------------------------------------------------- */ + +typedef struct _mace_statistics { + /* MACE_XMTFS */ + int xmtsv; + int uflo; + int lcol; + int more; + int one; + int defer; + int lcar; + int rtry; + + /* MACE_XMTRC */ + int exdef; + int xmtrc; + + /* RFS1--Receive Status (RCVSTS) */ + int oflo; + int clsn; + int fram; + int fcs; + + /* RFS2--Runt Packet Count (RNTPC) */ + int rfs_rntpc; + + /* RFS3--Receive Collision Count (RCVCC) */ + int rfs_rcvcc; + + /* MACE_IR */ + int jab; + int babl; + int cerr; + int rcvcco; + int rntpco; + int mpco; + + /* MACE_MPC */ + int mpc; + + /* MACE_RNTPC */ + int rntpc; + + /* MACE_RCVCC */ + int rcvcc; +} mace_statistics; + +typedef struct _mace_private { + dev_link_t link; + dev_node_t node; + struct net_device_stats linux_stats; /* Linux statistics counters */ + mace_statistics mace_stats; /* MACE chip statistics counters */ + + /* restore_multicast_list() state variables */ + int multicast_ladrf[MACE_LADRF_LEN]; /* Logical address filter */ + int multicast_num_addrs; + + char tx_free_frames; /* Number of free transmit frame buffers */ + char tx_irq_disabled; /* MACE TX interrupt disabled */ + + spinlock_t bank_lock; /* Must be held if you step off bank 0 */ +} mace_private; + +/* ---------------------------------------------------------------------------- +Private Global Variables +---------------------------------------------------------------------------- */ + +#ifdef PCMCIA_DEBUG +static char rcsid[] = +"nmclan_cs.c,v 0.16 1995/07/01 06:42:17 rpao Exp rpao"; +static char *version = +DRV_NAME " " DRV_VERSION " (Roger C. Pao)"; +#endif + +static dev_info_t dev_info="nmclan_cs"; +static dev_link_t *dev_list; + +static char *if_names[]={ + "Auto", "10baseT", "BNC", +}; + +/* ---------------------------------------------------------------------------- +Parameters + These are the parameters that can be set during loading with + 'insmod'. +---------------------------------------------------------------------------- */ + +MODULE_DESCRIPTION("New Media PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +/* 0=auto, 1=10baseT, 2 = 10base2, default=auto */ +INT_MODULE_PARM(if_port, 0); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +#else +#define DEBUG(n, args...) +#endif + +/* ---------------------------------------------------------------------------- +Function Prototypes +---------------------------------------------------------------------------- */ + +static void nmclan_config(dev_link_t *link); +static void nmclan_release(dev_link_t *link); +static int nmclan_event(event_t event, int priority, + event_callback_args_t *args); + +static void nmclan_reset(struct net_device *dev); +static int mace_config(struct net_device *dev, struct ifmap *map); +static int mace_open(struct net_device *dev); +static int mace_close(struct net_device *dev); +static int mace_start_xmit(struct sk_buff *skb, struct net_device *dev); +static void mace_tx_timeout(struct net_device *dev); +static irqreturn_t mace_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static struct net_device_stats *mace_get_stats(struct net_device *dev); +static int mace_rx(struct net_device *dev, unsigned char RxCnt); +static void restore_multicast_list(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); +static struct ethtool_ops netdev_ethtool_ops; + + +static dev_link_t *nmclan_attach(void); +static void nmclan_detach(dev_link_t *); + +/* ---------------------------------------------------------------------------- +nmclan_attach + Creates an "instance" of the driver, allocating local data + structures for one device. The device is registered with Card + Services. +---------------------------------------------------------------------------- */ + +static dev_link_t *nmclan_attach(void) +{ + mace_private *lp; + dev_link_t *link; + struct net_device *dev; + client_reg_t client_reg; + int ret; + + DEBUG(0, "nmclan_attach()\n"); + DEBUG(1, "%s\n", rcsid); + + /* Create new ethernet device */ + dev = alloc_etherdev(sizeof(mace_private)); + if (!dev) + return NULL; + lp = netdev_priv(dev); + link = &lp->link; + link->priv = dev; + + spin_lock_init(&lp->bank_lock); + link->io.NumPorts1 = 32; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.IOAddrLines = 5; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->irq.Handler = &mace_interrupt; + link->irq.Instance = dev; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.ConfigIndex = 1; + link->conf.Present = PRESENT_OPTION; + + lp->tx_free_frames=AM2150_MAX_TX_FRAMES; + + SET_MODULE_OWNER(dev); + dev->hard_start_xmit = &mace_start_xmit; + dev->set_config = &mace_config; + dev->get_stats = &mace_get_stats; + dev->set_multicast_list = &set_multicast_list; + SET_ETHTOOL_OPS(dev, &netdev_ethtool_ops); + dev->open = &mace_open; + dev->stop = &mace_close; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = mace_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &nmclan_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + nmclan_detach(link); + return NULL; + } + + return link; +} /* nmclan_attach */ + +/* ---------------------------------------------------------------------------- +nmclan_detach + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. +---------------------------------------------------------------------------- */ + +static void nmclan_detach(dev_link_t *link) +{ + struct net_device *dev = link->priv; + dev_link_t **linkp; + + DEBUG(0, "nmclan_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->dev) + unregister_netdev(dev); + + if (link->state & DEV_CONFIG) + nmclan_release(link); + + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + free_netdev(dev); +} /* nmclan_detach */ + +/* ---------------------------------------------------------------------------- +mace_read + Reads a MACE register. This is bank independent; however, the + caller must ensure that this call is not interruptable. We are + assuming that during normal operation, the MACE is always in + bank 0. +---------------------------------------------------------------------------- */ +static int mace_read(mace_private *lp, kio_addr_t ioaddr, int reg) +{ + int data = 0xFF; + unsigned long flags; + + switch (reg >> 4) { + case 0: /* register 0-15 */ + data = inb(ioaddr + AM2150_MACE_BASE + reg); + break; + case 1: /* register 16-31 */ + spin_lock_irqsave(&lp->bank_lock, flags); + MACEBANK(1); + data = inb(ioaddr + AM2150_MACE_BASE + (reg & 0x0F)); + MACEBANK(0); + spin_unlock_irqrestore(&lp->bank_lock, flags); + break; + } + return (data & 0xFF); +} /* mace_read */ + +/* ---------------------------------------------------------------------------- +mace_write + Writes to a MACE register. This is bank independent; however, + the caller must ensure that this call is not interruptable. We + are assuming that during normal operation, the MACE is always in + bank 0. +---------------------------------------------------------------------------- */ +static void mace_write(mace_private *lp, kio_addr_t ioaddr, int reg, int data) +{ + unsigned long flags; + + switch (reg >> 4) { + case 0: /* register 0-15 */ + outb(data & 0xFF, ioaddr + AM2150_MACE_BASE + reg); + break; + case 1: /* register 16-31 */ + spin_lock_irqsave(&lp->bank_lock, flags); + MACEBANK(1); + outb(data & 0xFF, ioaddr + AM2150_MACE_BASE + (reg & 0x0F)); + MACEBANK(0); + spin_unlock_irqrestore(&lp->bank_lock, flags); + break; + } +} /* mace_write */ + +/* ---------------------------------------------------------------------------- +mace_init + Resets the MACE chip. +---------------------------------------------------------------------------- */ +static int mace_init(mace_private *lp, kio_addr_t ioaddr, char *enet_addr) +{ + int i; + int ct = 0; + + /* MACE Software reset */ + mace_write(lp, ioaddr, MACE_BIUCC, 1); + while (mace_read(lp, ioaddr, MACE_BIUCC) & 0x01) { + /* Wait for reset bit to be cleared automatically after <= 200ns */; + if(++ct > 500) + { + printk(KERN_ERR "mace: reset failed, card removed ?\n"); + return -1; + } + udelay(1); + } + mace_write(lp, ioaddr, MACE_BIUCC, 0); + + /* The Am2150 requires that the MACE FIFOs operate in burst mode. */ + mace_write(lp, ioaddr, MACE_FIFOCC, 0x0F); + + mace_write(lp,ioaddr, MACE_RCVFC, 0); /* Disable Auto Strip Receive */ + mace_write(lp, ioaddr, MACE_IMR, 0xFF); /* Disable all interrupts until _open */ + + /* + * Bit 2-1 PORTSEL[1-0] Port Select. + * 00 AUI/10Base-2 + * 01 10Base-T + * 10 DAI Port (reserved in Am2150) + * 11 GPSI + * For this card, only the first two are valid. + * So, PLSCC should be set to + * 0x00 for 10Base-2 + * 0x02 for 10Base-T + * Or just set ASEL in PHYCC below! + */ + switch (if_port) { + case 1: + mace_write(lp, ioaddr, MACE_PLSCC, 0x02); + break; + case 2: + mace_write(lp, ioaddr, MACE_PLSCC, 0x00); + break; + default: + mace_write(lp, ioaddr, MACE_PHYCC, /* ASEL */ 4); + /* ASEL Auto Select. When set, the PORTSEL[1-0] bits are overridden, + and the MACE device will automatically select the operating media + interface port. */ + break; + } + + mace_write(lp, ioaddr, MACE_IAC, MACE_IAC_ADDRCHG | MACE_IAC_PHYADDR); + /* Poll ADDRCHG bit */ + ct = 0; + while (mace_read(lp, ioaddr, MACE_IAC) & MACE_IAC_ADDRCHG) + { + if(++ ct > 500) + { + printk(KERN_ERR "mace: ADDRCHG timeout, card removed ?\n"); + return -1; + } + } + /* Set PADR register */ + for (i = 0; i < ETHER_ADDR_LEN; i++) + mace_write(lp, ioaddr, MACE_PADR, enet_addr[i]); + + /* MAC Configuration Control Register should be written last */ + /* Let set_multicast_list set this. */ + /* mace_write(lp, ioaddr, MACE_MACCC, MACE_MACCC_ENXMT | MACE_MACCC_ENRCV); */ + mace_write(lp, ioaddr, MACE_MACCC, 0x00); + return 0; +} /* mace_init */ + +/* ---------------------------------------------------------------------------- +nmclan_config + This routine is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + ethernet device available to the system. +---------------------------------------------------------------------------- */ + +#define CS_CHECK(fn, ret) \ + do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static void nmclan_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + mace_private *lp = netdev_priv(dev); + tuple_t tuple; + cisparse_t parse; + u_char buf[64]; + int i, last_ret, last_fn; + kio_addr_t ioaddr; + + DEBUG(0, "nmclan_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + + /* Configure card */ + link->state |= DEV_CONFIG; + + CS_CHECK(RequestIO, pcmcia_request_io(handle, &link->io)); + CS_CHECK(RequestIRQ, pcmcia_request_irq(handle, &link->irq)); + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(handle, &link->conf)); + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + + ioaddr = dev->base_addr; + + /* Read the ethernet address from the CIS. */ + tuple.DesiredTuple = 0x80 /* CISTPL_CFTABLE_ENTRY_MISC */; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + memcpy(dev->dev_addr, tuple.TupleData, ETHER_ADDR_LEN); + + /* Verify configuration by reading the MACE ID. */ + { + char sig[2]; + + sig[0] = mace_read(lp, ioaddr, MACE_CHIPIDL); + sig[1] = mace_read(lp, ioaddr, MACE_CHIPIDH); + if ((sig[0] == 0x40) && ((sig[1] & 0x0F) == 0x09)) { + DEBUG(0, "nmclan_cs configured: mace id=%x %x\n", + sig[0], sig[1]); + } else { + printk(KERN_NOTICE "nmclan_cs: mace id not found: %x %x should" + " be 0x40 0x?9\n", sig[0], sig[1]); + link->state &= ~DEV_CONFIG_PENDING; + return; + } + } + + if(mace_init(lp, ioaddr, dev->dev_addr) == -1) + goto failed; + + /* The if_port symbol can be set when the module is loaded */ + if (if_port <= 2) + dev->if_port = if_port; + else + printk(KERN_NOTICE "nmclan_cs: invalid if_port requested\n"); + + link->dev = &lp->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + i = register_netdev(dev); + if (i != 0) { + printk(KERN_NOTICE "nmclan_cs: register_netdev() failed\n"); + link->dev = NULL; + goto failed; + } + + strcpy(lp->node.dev_name, dev->name); + + printk(KERN_INFO "%s: nmclan: port %#3lx, irq %d, %s port, hw_addr ", + dev->name, dev->base_addr, dev->irq, if_names[dev->if_port]); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + nmclan_release(link); + return; + +} /* nmclan_config */ + +/* ---------------------------------------------------------------------------- +nmclan_release + After a card is removed, nmclan_release() will unregister the + net device, and release the PCMCIA configuration. If the device + is still open, this will be postponed until it is closed. +---------------------------------------------------------------------------- */ +static void nmclan_release(dev_link_t *link) +{ + + DEBUG(0, "nmclan_release(0x%p)\n", link); + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + +/* ---------------------------------------------------------------------------- +nmclan_event + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. +---------------------------------------------------------------------------- */ +static int nmclan_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct net_device *dev = link->priv; + + DEBUG(1, "nmclan_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + nmclan_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) + netif_device_detach(dev); + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + nmclan_reset(dev); + netif_device_attach(dev); + } + } + break; + case CS_EVENT_RESET_REQUEST: + return 1; + break; + } + return 0; +} /* nmclan_event */ + +/* ---------------------------------------------------------------------------- +nmclan_reset + Reset and restore all of the Xilinx and MACE registers. +---------------------------------------------------------------------------- */ +static void nmclan_reset(struct net_device *dev) +{ + mace_private *lp = netdev_priv(dev); + +#if RESET_XILINX + dev_link_t *link = &lp->link; + conf_reg_t reg; + u_long OrigCorValue; + + /* Save original COR value */ + reg.Function = 0; + reg.Action = CS_READ; + reg.Offset = CISREG_COR; + reg.Value = 0; + pcmcia_access_configuration_register(link->handle, ®); + OrigCorValue = reg.Value; + + /* Reset Xilinx */ + reg.Action = CS_WRITE; + reg.Offset = CISREG_COR; + DEBUG(1, "nmclan_reset: OrigCorValue=0x%lX, resetting...\n", + OrigCorValue); + reg.Value = COR_SOFT_RESET; + pcmcia_access_configuration_register(link->handle, ®); + /* Need to wait for 20 ms for PCMCIA to finish reset. */ + + /* Restore original COR configuration index */ + reg.Value = COR_LEVEL_REQ | (OrigCorValue & COR_CONFIG_MASK); + pcmcia_access_configuration_register(link->handle, ®); + /* Xilinx is now completely reset along with the MACE chip. */ + lp->tx_free_frames=AM2150_MAX_TX_FRAMES; + +#endif /* #if RESET_XILINX */ + + /* Xilinx is now completely reset along with the MACE chip. */ + lp->tx_free_frames=AM2150_MAX_TX_FRAMES; + + /* Reinitialize the MACE chip for operation. */ + mace_init(lp, dev->base_addr, dev->dev_addr); + mace_write(lp, dev->base_addr, MACE_IMR, MACE_IMR_DEFAULT); + + /* Restore the multicast list and enable TX and RX. */ + restore_multicast_list(dev); +} /* nmclan_reset */ + +/* ---------------------------------------------------------------------------- +mace_config + [Someone tell me what this is supposed to do? Is if_port a defined + standard? If so, there should be defines to indicate 1=10Base-T, + 2=10Base-2, etc. including limited automatic detection.] +---------------------------------------------------------------------------- */ +static int mace_config(struct net_device *dev, struct ifmap *map) +{ + if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { + if (map->port <= 2) { + dev->if_port = map->port; + printk(KERN_INFO "%s: switched to %s port\n", dev->name, + if_names[dev->if_port]); + } else + return -EINVAL; + } + return 0; +} /* mace_config */ + +/* ---------------------------------------------------------------------------- +mace_open + Open device driver. +---------------------------------------------------------------------------- */ +static int mace_open(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + mace_private *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + + MACEBANK(0); + + netif_start_queue(dev); + nmclan_reset(dev); + + return 0; /* Always succeed */ +} /* mace_open */ + +/* ---------------------------------------------------------------------------- +mace_close + Closes device driver. +---------------------------------------------------------------------------- */ +static int mace_close(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + mace_private *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + + DEBUG(2, "%s: shutting down ethercard.\n", dev->name); + + /* Mask off all interrupts from the MACE chip. */ + outb(0xFF, ioaddr + AM2150_MACE_BASE + MACE_IMR); + + link->open--; + netif_stop_queue(dev); + + return 0; +} /* mace_close */ + +static void netdev_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, DRV_NAME); + strcpy(info->version, DRV_VERSION); + sprintf(info->bus_info, "PCMCIA 0x%lx", dev->base_addr); +} + +#ifdef PCMCIA_DEBUG +static u32 netdev_get_msglevel(struct net_device *dev) +{ + return pc_debug; +} + +static void netdev_set_msglevel(struct net_device *dev, u32 level) +{ + pc_debug = level; +} +#endif /* PCMCIA_DEBUG */ + +static struct ethtool_ops netdev_ethtool_ops = { + .get_drvinfo = netdev_get_drvinfo, +#ifdef PCMCIA_DEBUG + .get_msglevel = netdev_get_msglevel, + .set_msglevel = netdev_set_msglevel, +#endif /* PCMCIA_DEBUG */ +}; + +/* ---------------------------------------------------------------------------- +mace_start_xmit + This routine begins the packet transmit function. When completed, + it will generate a transmit interrupt. + + According to /usr/src/linux/net/inet/dev.c, if _start_xmit + returns 0, the "packet is now solely the responsibility of the + driver." If _start_xmit returns non-zero, the "transmission + failed, put skb back into a list." +---------------------------------------------------------------------------- */ + +static void mace_tx_timeout(struct net_device *dev) +{ + mace_private *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + + printk(KERN_NOTICE "%s: transmit timed out -- ", dev->name); +#if RESET_ON_TIMEOUT + printk("resetting card\n"); + pcmcia_reset_card(link->handle, NULL); +#else /* #if RESET_ON_TIMEOUT */ + printk("NOT resetting card\n"); +#endif /* #if RESET_ON_TIMEOUT */ + dev->trans_start = jiffies; + netif_wake_queue(dev); +} + +static int mace_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + mace_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + + netif_stop_queue(dev); + + DEBUG(3, "%s: mace_start_xmit(length = %ld) called.\n", + dev->name, (long)skb->len); + +#if (!TX_INTERRUPTABLE) + /* Disable MACE TX interrupts. */ + outb(MACE_IMR_DEFAULT | MACE_IR_XMTINT, + ioaddr + AM2150_MACE_BASE + MACE_IMR); + lp->tx_irq_disabled=1; +#endif /* #if (!TX_INTERRUPTABLE) */ + + { + /* This block must not be interrupted by another transmit request! + mace_tx_timeout will take care of timer-based retransmissions from + the upper layers. The interrupt handler is guaranteed never to + service a transmit interrupt while we are in here. + */ + + lp->linux_stats.tx_bytes += skb->len; + lp->tx_free_frames--; + + /* WARNING: Write the _exact_ number of bytes written in the header! */ + /* Put out the word header [must be an outw()] . . . */ + outw(skb->len, ioaddr + AM2150_XMT); + /* . . . and the packet [may be any combination of outw() and outb()] */ + outsw(ioaddr + AM2150_XMT, skb->data, skb->len >> 1); + if (skb->len & 1) { + /* Odd byte transfer */ + outb(skb->data[skb->len-1], ioaddr + AM2150_XMT); + } + + dev->trans_start = jiffies; + +#if MULTI_TX + if (lp->tx_free_frames > 0) + netif_start_queue(dev); +#endif /* #if MULTI_TX */ + } + +#if (!TX_INTERRUPTABLE) + /* Re-enable MACE TX interrupts. */ + lp->tx_irq_disabled=0; + outb(MACE_IMR_DEFAULT, ioaddr + AM2150_MACE_BASE + MACE_IMR); +#endif /* #if (!TX_INTERRUPTABLE) */ + + dev_kfree_skb(skb); + + return 0; +} /* mace_start_xmit */ + +/* ---------------------------------------------------------------------------- +mace_interrupt + The interrupt handler. +---------------------------------------------------------------------------- */ +static irqreturn_t mace_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *) dev_id; + mace_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + int status; + int IntrCnt = MACE_MAX_IR_ITERATIONS; + + if (dev == NULL) { + DEBUG(2, "mace_interrupt(): irq 0x%X for unknown device.\n", + irq); + return IRQ_NONE; + } + + if (lp->tx_irq_disabled) { + printk( + (lp->tx_irq_disabled? + KERN_NOTICE "%s: Interrupt with tx_irq_disabled " + "[isr=%02X, imr=%02X]\n": + KERN_NOTICE "%s: Re-entering the interrupt handler " + "[isr=%02X, imr=%02X]\n"), + dev->name, + inb(ioaddr + AM2150_MACE_BASE + MACE_IR), + inb(ioaddr + AM2150_MACE_BASE + MACE_IMR) + ); + /* WARNING: MACE_IR has been read! */ + return IRQ_NONE; + } + + if (!netif_device_present(dev)) { + DEBUG(2, "%s: interrupt from dead card\n", dev->name); + return IRQ_NONE; + } + + do { + /* WARNING: MACE_IR is a READ/CLEAR port! */ + status = inb(ioaddr + AM2150_MACE_BASE + MACE_IR); + + DEBUG(3, "mace_interrupt: irq 0x%X status 0x%X.\n", irq, status); + + if (status & MACE_IR_RCVINT) { + mace_rx(dev, MACE_MAX_RX_ITERATIONS); + } + + if (status & MACE_IR_XMTINT) { + unsigned char fifofc; + unsigned char xmtrc; + unsigned char xmtfs; + + fifofc = inb(ioaddr + AM2150_MACE_BASE + MACE_FIFOFC); + if ((fifofc & MACE_FIFOFC_XMTFC)==0) { + lp->linux_stats.tx_errors++; + outb(0xFF, ioaddr + AM2150_XMT_SKIP); + } + + /* Transmit Retry Count (XMTRC, reg 4) */ + xmtrc = inb(ioaddr + AM2150_MACE_BASE + MACE_XMTRC); + if (xmtrc & MACE_XMTRC_EXDEF) lp->mace_stats.exdef++; + lp->mace_stats.xmtrc += (xmtrc & MACE_XMTRC_XMTRC); + + if ( + (xmtfs = inb(ioaddr + AM2150_MACE_BASE + MACE_XMTFS)) & + MACE_XMTFS_XMTSV /* Transmit Status Valid */ + ) { + lp->mace_stats.xmtsv++; + + if (xmtfs & ~MACE_XMTFS_XMTSV) { + if (xmtfs & MACE_XMTFS_UFLO) { + /* Underflow. Indicates that the Transmit FIFO emptied before + the end of frame was reached. */ + lp->mace_stats.uflo++; + } + if (xmtfs & MACE_XMTFS_LCOL) { + /* Late Collision */ + lp->mace_stats.lcol++; + } + if (xmtfs & MACE_XMTFS_MORE) { + /* MORE than one retry was needed */ + lp->mace_stats.more++; + } + if (xmtfs & MACE_XMTFS_ONE) { + /* Exactly ONE retry occurred */ + lp->mace_stats.one++; + } + if (xmtfs & MACE_XMTFS_DEFER) { + /* Transmission was defered */ + lp->mace_stats.defer++; + } + if (xmtfs & MACE_XMTFS_LCAR) { + /* Loss of carrier */ + lp->mace_stats.lcar++; + } + if (xmtfs & MACE_XMTFS_RTRY) { + /* Retry error: transmit aborted after 16 attempts */ + lp->mace_stats.rtry++; + } + } /* if (xmtfs & ~MACE_XMTFS_XMTSV) */ + + } /* if (xmtfs & MACE_XMTFS_XMTSV) */ + + lp->linux_stats.tx_packets++; + lp->tx_free_frames++; + netif_wake_queue(dev); + } /* if (status & MACE_IR_XMTINT) */ + + if (status & ~MACE_IMR_DEFAULT & ~MACE_IR_RCVINT & ~MACE_IR_XMTINT) { + if (status & MACE_IR_JAB) { + /* Jabber Error. Excessive transmit duration (20-150ms). */ + lp->mace_stats.jab++; + } + if (status & MACE_IR_BABL) { + /* Babble Error. >1518 bytes transmitted. */ + lp->mace_stats.babl++; + } + if (status & MACE_IR_CERR) { + /* Collision Error. CERR indicates the absence of the + Signal Quality Error Test message after a packet + transmission. */ + lp->mace_stats.cerr++; + } + if (status & MACE_IR_RCVCCO) { + /* Receive Collision Count Overflow; */ + lp->mace_stats.rcvcco++; + } + if (status & MACE_IR_RNTPCO) { + /* Runt Packet Count Overflow */ + lp->mace_stats.rntpco++; + } + if (status & MACE_IR_MPCO) { + /* Missed Packet Count Overflow */ + lp->mace_stats.mpco++; + } + } /* if (status & ~MACE_IMR_DEFAULT & ~MACE_IR_RCVINT & ~MACE_IR_XMTINT) */ + + } while ((status & ~MACE_IMR_DEFAULT) && (--IntrCnt)); + + return IRQ_HANDLED; +} /* mace_interrupt */ + +/* ---------------------------------------------------------------------------- +mace_rx + Receives packets. +---------------------------------------------------------------------------- */ +static int mace_rx(struct net_device *dev, unsigned char RxCnt) +{ + mace_private *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + unsigned char rx_framecnt; + unsigned short rx_status; + + while ( + ((rx_framecnt = inb(ioaddr + AM2150_RCV_FRAME_COUNT)) > 0) && + (rx_framecnt <= 12) && /* rx_framecnt==0xFF if card is extracted. */ + (RxCnt--) + ) { + rx_status = inw(ioaddr + AM2150_RCV); + + DEBUG(3, "%s: in mace_rx(), framecnt 0x%X, rx_status" + " 0x%X.\n", dev->name, rx_framecnt, rx_status); + + if (rx_status & MACE_RCVFS_RCVSTS) { /* Error, update stats. */ + lp->linux_stats.rx_errors++; + if (rx_status & MACE_RCVFS_OFLO) { + lp->mace_stats.oflo++; + } + if (rx_status & MACE_RCVFS_CLSN) { + lp->mace_stats.clsn++; + } + if (rx_status & MACE_RCVFS_FRAM) { + lp->mace_stats.fram++; + } + if (rx_status & MACE_RCVFS_FCS) { + lp->mace_stats.fcs++; + } + } else { + short pkt_len = (rx_status & ~MACE_RCVFS_RCVSTS) - 4; + /* Auto Strip is off, always subtract 4 */ + struct sk_buff *skb; + + lp->mace_stats.rfs_rntpc += inb(ioaddr + AM2150_RCV); + /* runt packet count */ + lp->mace_stats.rfs_rcvcc += inb(ioaddr + AM2150_RCV); + /* rcv collision count */ + + DEBUG(3, " receiving packet size 0x%X rx_status" + " 0x%X.\n", pkt_len, rx_status); + + skb = dev_alloc_skb(pkt_len+2); + + if (skb != NULL) { + skb->dev = dev; + + skb_reserve(skb, 2); + insw(ioaddr + AM2150_RCV, skb_put(skb, pkt_len), pkt_len>>1); + if (pkt_len & 1) + *(skb->tail-1) = inb(ioaddr + AM2150_RCV); + skb->protocol = eth_type_trans(skb, dev); + + netif_rx(skb); /* Send the packet to the upper (protocol) layers. */ + + dev->last_rx = jiffies; + lp->linux_stats.rx_packets++; + lp->linux_stats.rx_bytes += skb->len; + outb(0xFF, ioaddr + AM2150_RCV_NEXT); /* skip to next frame */ + continue; + } else { + DEBUG(1, "%s: couldn't allocate a sk_buff of size" + " %d.\n", dev->name, pkt_len); + lp->linux_stats.rx_dropped++; + } + } + outb(0xFF, ioaddr + AM2150_RCV_NEXT); /* skip to next frame */ + } /* while */ + + return 0; +} /* mace_rx */ + +/* ---------------------------------------------------------------------------- +pr_linux_stats +---------------------------------------------------------------------------- */ +static void pr_linux_stats(struct net_device_stats *pstats) +{ + DEBUG(2, "pr_linux_stats\n"); + DEBUG(2, " rx_packets=%-7ld tx_packets=%ld\n", + (long)pstats->rx_packets, (long)pstats->tx_packets); + DEBUG(2, " rx_errors=%-7ld tx_errors=%ld\n", + (long)pstats->rx_errors, (long)pstats->tx_errors); + DEBUG(2, " rx_dropped=%-7ld tx_dropped=%ld\n", + (long)pstats->rx_dropped, (long)pstats->tx_dropped); + DEBUG(2, " multicast=%-7ld collisions=%ld\n", + (long)pstats->multicast, (long)pstats->collisions); + + DEBUG(2, " rx_length_errors=%-7ld rx_over_errors=%ld\n", + (long)pstats->rx_length_errors, (long)pstats->rx_over_errors); + DEBUG(2, " rx_crc_errors=%-7ld rx_frame_errors=%ld\n", + (long)pstats->rx_crc_errors, (long)pstats->rx_frame_errors); + DEBUG(2, " rx_fifo_errors=%-7ld rx_missed_errors=%ld\n", + (long)pstats->rx_fifo_errors, (long)pstats->rx_missed_errors); + + DEBUG(2, " tx_aborted_errors=%-7ld tx_carrier_errors=%ld\n", + (long)pstats->tx_aborted_errors, (long)pstats->tx_carrier_errors); + DEBUG(2, " tx_fifo_errors=%-7ld tx_heartbeat_errors=%ld\n", + (long)pstats->tx_fifo_errors, (long)pstats->tx_heartbeat_errors); + DEBUG(2, " tx_window_errors=%ld\n", + (long)pstats->tx_window_errors); +} /* pr_linux_stats */ + +/* ---------------------------------------------------------------------------- +pr_mace_stats +---------------------------------------------------------------------------- */ +static void pr_mace_stats(mace_statistics *pstats) +{ + DEBUG(2, "pr_mace_stats\n"); + + DEBUG(2, " xmtsv=%-7d uflo=%d\n", + pstats->xmtsv, pstats->uflo); + DEBUG(2, " lcol=%-7d more=%d\n", + pstats->lcol, pstats->more); + DEBUG(2, " one=%-7d defer=%d\n", + pstats->one, pstats->defer); + DEBUG(2, " lcar=%-7d rtry=%d\n", + pstats->lcar, pstats->rtry); + + /* MACE_XMTRC */ + DEBUG(2, " exdef=%-7d xmtrc=%d\n", + pstats->exdef, pstats->xmtrc); + + /* RFS1--Receive Status (RCVSTS) */ + DEBUG(2, " oflo=%-7d clsn=%d\n", + pstats->oflo, pstats->clsn); + DEBUG(2, " fram=%-7d fcs=%d\n", + pstats->fram, pstats->fcs); + + /* RFS2--Runt Packet Count (RNTPC) */ + /* RFS3--Receive Collision Count (RCVCC) */ + DEBUG(2, " rfs_rntpc=%-7d rfs_rcvcc=%d\n", + pstats->rfs_rntpc, pstats->rfs_rcvcc); + + /* MACE_IR */ + DEBUG(2, " jab=%-7d babl=%d\n", + pstats->jab, pstats->babl); + DEBUG(2, " cerr=%-7d rcvcco=%d\n", + pstats->cerr, pstats->rcvcco); + DEBUG(2, " rntpco=%-7d mpco=%d\n", + pstats->rntpco, pstats->mpco); + + /* MACE_MPC */ + DEBUG(2, " mpc=%d\n", pstats->mpc); + + /* MACE_RNTPC */ + DEBUG(2, " rntpc=%d\n", pstats->rntpc); + + /* MACE_RCVCC */ + DEBUG(2, " rcvcc=%d\n", pstats->rcvcc); + +} /* pr_mace_stats */ + +/* ---------------------------------------------------------------------------- +update_stats + Update statistics. We change to register window 1, so this + should be run single-threaded if the device is active. This is + expected to be a rare operation, and it's simpler for the rest + of the driver to assume that window 0 is always valid rather + than use a special window-state variable. + + oflo & uflo should _never_ occur since it would mean the Xilinx + was not able to transfer data between the MACE FIFO and the + card's SRAM fast enough. If this happens, something is + seriously wrong with the hardware. +---------------------------------------------------------------------------- */ +static void update_stats(kio_addr_t ioaddr, struct net_device *dev) +{ + mace_private *lp = netdev_priv(dev); + + lp->mace_stats.rcvcc += mace_read(lp, ioaddr, MACE_RCVCC); + lp->mace_stats.rntpc += mace_read(lp, ioaddr, MACE_RNTPC); + lp->mace_stats.mpc += mace_read(lp, ioaddr, MACE_MPC); + /* At this point, mace_stats is fully updated for this call. + We may now update the linux_stats. */ + + /* The MACE has no equivalent for linux_stats field which are commented + out. */ + + /* lp->linux_stats.multicast; */ + lp->linux_stats.collisions = + lp->mace_stats.rcvcco * 256 + lp->mace_stats.rcvcc; + /* Collision: The MACE may retry sending a packet 15 times + before giving up. The retry count is in XMTRC. + Does each retry constitute a collision? + If so, why doesn't the RCVCC record these collisions? */ + + /* detailed rx_errors: */ + lp->linux_stats.rx_length_errors = + lp->mace_stats.rntpco * 256 + lp->mace_stats.rntpc; + /* lp->linux_stats.rx_over_errors */ + lp->linux_stats.rx_crc_errors = lp->mace_stats.fcs; + lp->linux_stats.rx_frame_errors = lp->mace_stats.fram; + lp->linux_stats.rx_fifo_errors = lp->mace_stats.oflo; + lp->linux_stats.rx_missed_errors = + lp->mace_stats.mpco * 256 + lp->mace_stats.mpc; + + /* detailed tx_errors */ + lp->linux_stats.tx_aborted_errors = lp->mace_stats.rtry; + lp->linux_stats.tx_carrier_errors = lp->mace_stats.lcar; + /* LCAR usually results from bad cabling. */ + lp->linux_stats.tx_fifo_errors = lp->mace_stats.uflo; + lp->linux_stats.tx_heartbeat_errors = lp->mace_stats.cerr; + /* lp->linux_stats.tx_window_errors; */ + + return; +} /* update_stats */ + +/* ---------------------------------------------------------------------------- +mace_get_stats + Gathers ethernet statistics from the MACE chip. +---------------------------------------------------------------------------- */ +static struct net_device_stats *mace_get_stats(struct net_device *dev) +{ + mace_private *lp = netdev_priv(dev); + + update_stats(dev->base_addr, dev); + + DEBUG(1, "%s: updating the statistics.\n", dev->name); + pr_linux_stats(&lp->linux_stats); + pr_mace_stats(&lp->mace_stats); + + return &lp->linux_stats; +} /* net_device_stats */ + +/* ---------------------------------------------------------------------------- +updateCRC + Modified from Am79C90 data sheet. +---------------------------------------------------------------------------- */ + +#ifdef BROKEN_MULTICAST + +static void updateCRC(int *CRC, int bit) +{ + int poly[]={ + 1,1,1,0, 1,1,0,1, + 1,0,1,1, 1,0,0,0, + 1,0,0,0, 0,0,1,1, + 0,0,1,0, 0,0,0,0 + }; /* CRC polynomial. poly[n] = coefficient of the x**n term of the + CRC generator polynomial. */ + + int j; + + /* shift CRC and control bit (CRC[32]) */ + for (j = 32; j > 0; j--) + CRC[j] = CRC[j-1]; + CRC[0] = 0; + + /* If bit XOR(control bit) = 1, set CRC = CRC XOR polynomial. */ + if (bit ^ CRC[32]) + for (j = 0; j < 32; j++) + CRC[j] ^= poly[j]; +} /* updateCRC */ + +/* ---------------------------------------------------------------------------- +BuildLAF + Build logical address filter. + Modified from Am79C90 data sheet. + +Input + ladrf: logical address filter (contents initialized to 0) + adr: ethernet address +---------------------------------------------------------------------------- */ +static void BuildLAF(int *ladrf, int *adr) +{ + int CRC[33]={1}; /* CRC register, 1 word/bit + extra control bit */ + + int i, byte; /* temporary array indices */ + int hashcode; /* the output object */ + + CRC[32]=0; + + for (byte = 0; byte < 6; byte++) + for (i = 0; i < 8; i++) + updateCRC(CRC, (adr[byte] >> i) & 1); + + hashcode = 0; + for (i = 0; i < 6; i++) + hashcode = (hashcode << 1) + CRC[i]; + + byte = hashcode >> 3; + ladrf[byte] |= (1 << (hashcode & 7)); + +#ifdef PCMCIA_DEBUG + if (pc_debug > 2) { + printk(KERN_DEBUG " adr ="); + for (i = 0; i < 6; i++) + printk(" %02X", adr[i]); + printk("\n" KERN_DEBUG " hashcode = %d(decimal), ladrf[0:63]" + " =", hashcode); + for (i = 0; i < 8; i++) + printk(" %02X", ladrf[i]); + printk("\n"); + } +#endif +} /* BuildLAF */ + +/* ---------------------------------------------------------------------------- +restore_multicast_list + Restores the multicast filter for MACE chip to the last + set_multicast_list() call. + +Input + multicast_num_addrs + multicast_ladrf[] +---------------------------------------------------------------------------- */ +static void restore_multicast_list(struct net_device *dev) +{ + mace_private *lp = netdev_priv(dev); + int num_addrs = lp->multicast_num_addrs; + int *ladrf = lp->multicast_ladrf; + kio_addr_t ioaddr = dev->base_addr; + int i; + + DEBUG(2, "%s: restoring Rx mode to %d addresses.\n", + dev->name, num_addrs); + + if (num_addrs > 0) { + + DEBUG(1, "Attempt to restore multicast list detected.\n"); + + mace_write(lp, ioaddr, MACE_IAC, MACE_IAC_ADDRCHG | MACE_IAC_LOGADDR); + /* Poll ADDRCHG bit */ + while (mace_read(lp, ioaddr, MACE_IAC) & MACE_IAC_ADDRCHG) + ; + /* Set LADRF register */ + for (i = 0; i < MACE_LADRF_LEN; i++) + mace_write(lp, ioaddr, MACE_LADRF, ladrf[i]); + + mace_write(lp, ioaddr, MACE_UTR, MACE_UTR_RCVFCSE | MACE_UTR_LOOP_EXTERNAL); + mace_write(lp, ioaddr, MACE_MACCC, MACE_MACCC_ENXMT | MACE_MACCC_ENRCV); + + } else if (num_addrs < 0) { + + /* Promiscuous mode: receive all packets */ + mace_write(lp, ioaddr, MACE_UTR, MACE_UTR_LOOP_EXTERNAL); + mace_write(lp, ioaddr, MACE_MACCC, + MACE_MACCC_PROM | MACE_MACCC_ENXMT | MACE_MACCC_ENRCV + ); + + } else { + + /* Normal mode */ + mace_write(lp, ioaddr, MACE_UTR, MACE_UTR_LOOP_EXTERNAL); + mace_write(lp, ioaddr, MACE_MACCC, MACE_MACCC_ENXMT | MACE_MACCC_ENRCV); + + } +} /* restore_multicast_list */ + +/* ---------------------------------------------------------------------------- +set_multicast_list + Set or clear the multicast filter for this adaptor. + +Input + num_addrs == -1 Promiscuous mode, receive all packets + num_addrs == 0 Normal mode, clear multicast list + num_addrs > 0 Multicast mode, receive normal and MC packets, and do + best-effort filtering. +Output + multicast_num_addrs + multicast_ladrf[] +---------------------------------------------------------------------------- */ + +static void set_multicast_list(struct net_device *dev) +{ + mace_private *lp = netdev_priv(dev); + int adr[ETHER_ADDR_LEN] = {0}; /* Ethernet address */ + int i; + struct dev_mc_list *dmi = dev->mc_list; + +#ifdef PCMCIA_DEBUG + if (pc_debug > 1) { + static int old; + if (dev->mc_count != old) { + old = dev->mc_count; + DEBUG(0, "%s: setting Rx mode to %d addresses.\n", + dev->name, old); + } + } +#endif + + /* Set multicast_num_addrs. */ + lp->multicast_num_addrs = dev->mc_count; + + /* Set multicast_ladrf. */ + if (num_addrs > 0) { + /* Calculate multicast logical address filter */ + memset(lp->multicast_ladrf, 0, MACE_LADRF_LEN); + for (i = 0; i < dev->mc_count; i++) { + memcpy(adr, dmi->dmi_addr, ETHER_ADDR_LEN); + dmi = dmi->next; + BuildLAF(lp->multicast_ladrf, adr); + } + } + + restore_multicast_list(dev); + +} /* set_multicast_list */ + +#endif /* BROKEN_MULTICAST */ + +static void restore_multicast_list(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + mace_private *lp = netdev_priv(dev); + + DEBUG(2, "%s: restoring Rx mode to %d addresses.\n", dev->name, + lp->multicast_num_addrs); + + if (dev->flags & IFF_PROMISC) { + /* Promiscuous mode: receive all packets */ + mace_write(lp,ioaddr, MACE_UTR, MACE_UTR_LOOP_EXTERNAL); + mace_write(lp, ioaddr, MACE_MACCC, + MACE_MACCC_PROM | MACE_MACCC_ENXMT | MACE_MACCC_ENRCV + ); + } else { + /* Normal mode */ + mace_write(lp, ioaddr, MACE_UTR, MACE_UTR_LOOP_EXTERNAL); + mace_write(lp, ioaddr, MACE_MACCC, MACE_MACCC_ENXMT | MACE_MACCC_ENRCV); + } +} /* restore_multicast_list */ + +static void set_multicast_list(struct net_device *dev) +{ + mace_private *lp = netdev_priv(dev); + +#ifdef PCMCIA_DEBUG + if (pc_debug > 1) { + static int old; + if (dev->mc_count != old) { + old = dev->mc_count; + DEBUG(0, "%s: setting Rx mode to %d addresses.\n", + dev->name, old); + } + } +#endif + + lp->multicast_num_addrs = dev->mc_count; + restore_multicast_list(dev); + +} /* set_multicast_list */ + +static struct pcmcia_driver nmclan_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "nmclan_cs", + }, + .attach = nmclan_attach, + .detach = nmclan_detach, +}; + +static int __init init_nmclan_cs(void) +{ + return pcmcia_register_driver(&nmclan_cs_driver); +} + +static void __exit exit_nmclan_cs(void) +{ + pcmcia_unregister_driver(&nmclan_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_nmclan_cs); +module_exit(exit_nmclan_cs); diff --git a/drivers/net/pcmcia/ositech.h b/drivers/net/pcmcia/ositech.h new file mode 100644 index 00000000000..4126efc355b --- /dev/null +++ b/drivers/net/pcmcia/ositech.h @@ -0,0 +1,358 @@ +/* + This file contains the firmware of Seven of Diamonds from OSITECH. + (Special thanks to Kevin MacPherson of OSITECH) + + This software may be used and distributed according to the terms of + the GNU General Public License, incorporated herein by reference. +*/ + + static const u_char __Xilinx7OD[] = { + 0xFF, 0x04, 0xA0, 0x36, 0xF3, 0xEC, 0xFF, 0xFF, 0xFF, 0xDF, 0xFB, 0xFF, + 0xF3, 0xFF, 0xFF, 0xFF, + 0xEF, 0x3F, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F, 0xFE, 0xFF, + 0xCE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xDE, 0xBD, 0xDD, 0xFD, 0xFF, 0xFD, 0xCF, 0xF7, 0xBF, 0x7F, 0xFF, + 0x7F, 0x3F, 0xFE, 0xBF, + 0xFF, 0xFF, 0xFF, 0xBC, 0xFF, 0xFF, 0xBD, 0xB5, 0x7F, 0x7F, 0xBF, 0xBF, + 0x7F, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFB, 0xFF, 0xF7, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xDE, + 0xFE, 0xFE, 0xFA, 0xDE, + 0xBD, 0xFD, 0xED, 0xFD, 0xFD, 0xCF, 0xEF, 0xEF, 0xEF, 0xEF, 0xC7, 0xDF, + 0xDF, 0xDF, 0xDF, 0xDF, + 0xFF, 0x7E, 0xFE, 0xFD, 0x7D, 0x6D, 0xEE, 0xFE, 0x7C, 0xFB, 0xF4, 0xFB, + 0xCF, 0xDB, 0xDF, 0xFF, + 0xFF, 0xBB, 0x7F, 0xFF, 0x7F, 0xFF, 0xF7, 0xFF, 0x9E, 0xBF, 0x3B, 0xBF, + 0xBF, 0x7F, 0x7F, 0x7F, + 0x7E, 0x6F, 0xDF, 0xEF, 0xF5, 0xF6, 0xFD, 0xF6, 0xF5, 0xED, 0xEB, 0xFF, + 0xEF, 0xEF, 0xEF, 0x7E, + 0x7F, 0x7F, 0x6F, 0x7F, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xEF, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0x1F, 0x1F, 0xEE, 0xFF, 0xBC, + 0xB7, 0xFF, 0xDF, 0xFF, + 0xDF, 0xEF, 0x3B, 0xE3, 0xD3, 0xFF, 0xFB, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, + 0xFF, 0xBA, 0xBF, 0x2D, + 0xDB, 0xBD, 0xFD, 0xDB, 0xDF, 0xFA, 0xFB, 0xFF, 0xEF, 0xFB, 0xDB, 0xF3, + 0xFF, 0xDF, 0xFD, 0x7F, + 0xEF, 0xFB, 0xFF, 0xFF, 0xBE, 0xBF, 0x27, 0xBA, 0xFE, 0xFB, 0xDF, 0xFF, + 0xF6, 0xFF, 0xFF, 0xEF, + 0xFB, 0xDB, 0xF3, 0xD9, 0x9A, 0x3F, 0xFF, 0xAF, 0xBF, 0xFF, 0xFF, 0xBE, + 0x3F, 0x37, 0xBD, 0x96, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAE, 0xFB, 0xF3, 0xF3, 0xEB, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF7, 0xFA, 0xBC, 0xAE, 0xFE, 0xBE, 0xFE, 0xBB, 0x7F, 0xFD, 0xFF, + 0x7F, 0xEF, 0xF7, 0xFB, + 0xBB, 0xD7, 0xF7, 0x7F, 0xFF, 0xF7, 0xFF, 0xFF, 0xF7, 0xBC, 0xED, 0xFD, + 0xBD, 0x9D, 0x7D, 0x7B, + 0xFB, 0x7B, 0x7B, 0xFB, 0xAF, 0xFF, 0xFE, 0xFD, 0xFD, 0xFE, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF7, + 0xAA, 0xB9, 0xBF, 0x8F, 0xBF, 0xDF, 0xFF, 0x7F, 0xFF, 0xFF, 0x7F, 0xCF, + 0xFB, 0xEB, 0xCB, 0xEB, + 0xEE, 0xFF, 0xFF, 0xD7, 0xFF, 0xFF, 0xFF, 0x3E, 0x33, 0x3F, 0x1C, 0x7C, + 0xFC, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xCF, 0xD3, 0xF3, 0xE3, 0xF3, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEB, 0xFE, 0x35, + 0x3F, 0x3D, 0xFD, 0xFD, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xEF, 0x6F, 0xE3, + 0xE3, 0xE3, 0xEF, 0xFF, + 0xFF, 0xDF, 0xFF, 0xFF, 0xF7, 0xFE, 0x3E, 0x5E, 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFD, 0xFF, 0xFF, + 0xAF, 0xCF, 0xF2, 0xCB, 0xCF, 0x8E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, + 0xFC, 0x3E, 0x1F, 0x9E, + 0xAD, 0xFD, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xEF, 0xFF, 0xB3, 0xF7, 0xE7, + 0xF7, 0xFA, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xEE, 0xEB, 0xAB, 0xAF, 0x9F, 0xE3, 0x7F, 0xFF, 0xDE, + 0xFF, 0x7F, 0xEE, 0xFF, + 0xFF, 0xFB, 0x3A, 0xFA, 0xFF, 0xF2, 0x77, 0xFF, 0xFF, 0xF7, 0xFE, 0xFF, + 0xFE, 0xBD, 0xAE, 0xDE, + 0x7D, 0x7D, 0xFD, 0xFF, 0xBF, 0xEE, 0xFF, 0xFD, 0xFF, 0xDB, 0xFB, 0xFF, + 0xF7, 0xEF, 0xFB, 0xFF, + 0xFF, 0xFE, 0xFF, 0x2D, 0xAF, 0xB9, 0xFD, 0x79, 0xFB, 0xFA, 0xFF, 0xBF, + 0xEF, 0xFF, 0xFF, 0x91, + 0xFA, 0xFB, 0xDF, 0xF7, 0xF7, 0xFF, 0xFF, 0xFF, 0xFC, 0xCF, 0x37, 0xBF, + 0xBF, 0xFF, 0x7F, 0x7F, + 0xFF, 0xFF, 0xFF, 0xAF, 0xFF, 0xFF, 0xF3, 0xFB, 0xFB, 0xFF, 0xF5, 0xEF, + 0xFF, 0xFF, 0xF7, 0xFA, + 0xFF, 0xFF, 0xEE, 0xFA, 0xFE, 0xFB, 0x55, 0xDD, 0xFF, 0x7F, 0xAF, 0xFE, + 0xFF, 0xFB, 0xFB, 0xF5, + 0xFF, 0xF7, 0xEF, 0xFF, 0xFF, 0xFF, 0xBE, 0xBD, 0xBD, 0xBD, 0xBD, 0x7D, + 0x7B, 0x7B, 0x7B, 0x7B, + 0xFB, 0xAE, 0xFF, 0xFD, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0xDA, 0xB7, 0x61, + 0xFF, 0xB9, 0x59, 0xF3, 0x73, 0xF3, 0xDF, 0x7F, 0x6F, 0xDF, 0xEF, 0xF7, + 0xEB, 0xEB, 0xD7, 0xFF, + 0xD7, 0xFF, 0xFF, 0xF7, 0xFE, 0x7F, 0xFB, 0x3E, 0x38, 0x73, 0xF6, 0x7F, + 0xFC, 0xFF, 0xFF, 0xCF, + 0xFF, 0xB7, 0xFB, 0xB3, 0xB3, 0x67, 0xFF, 0xE7, 0xFD, 0xFF, 0xEF, 0xF6, + 0x7F, 0xB7, 0xBC, 0xF5, + 0x7B, 0xF6, 0xF7, 0xF5, 0xFF, 0xFF, 0xEF, 0xFF, 0xF7, 0xFF, 0xF7, 0xCE, + 0xE7, 0xFF, 0x9F, 0xFF, + 0xFF, 0xF5, 0xFE, 0x7D, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xFF, 0xF6, + 0xCB, 0xDB, 0xEE, 0xFE, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xBE, + 0x1E, 0x3E, 0xFE, 0xFF, + 0x7D, 0xFE, 0xFF, 0xFF, 0xEF, 0xBF, 0xE7, 0xFF, 0xE3, 0xE3, 0xFF, 0xDF, + 0xE7, 0xFF, 0xFF, 0xFF, + 0xB8, 0xEF, 0xB7, 0x2F, 0xEE, 0xFF, 0xDF, 0xFF, 0xBF, 0xFF, 0x7F, 0xEF, + 0xEB, 0xBF, 0xA3, 0xD3, + 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xBE, 0xFD, 0x3F, 0xCF, 0xFD, + 0xFB, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xAF, 0xFB, 0xBF, 0xBB, 0xBF, 0xDB, 0xFD, 0xFB, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3E, 0xFE, + 0x3F, 0xBA, 0xBA, 0xFE, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xEF, 0xC3, 0x7F, + 0xB2, 0x9B, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0x3C, 0xFF, 0x3F, 0x3C, 0xFF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xAF, 0xF3, 0xFE, 0xF3, 0xE3, 0xEB, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF7, + 0x9A, 0xFE, 0xAF, 0x9E, + 0xBE, 0xFE, 0xFF, 0xDF, 0xFF, 0xFF, 0x7B, 0xEF, 0xF7, 0xBF, 0xFB, 0xFB, + 0xFB, 0xFF, 0xFF, 0x7F, + 0xFF, 0xFF, 0xFF, 0xBC, 0xBD, 0xFD, 0xBD, 0xDD, 0x7D, 0x7B, 0x7B, 0x7B, + 0x7B, 0xFB, 0xAE, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFE, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xF7, 0x9A, 0xFF, + 0x9F, 0xFF, 0xAF, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xCF, 0xF3, 0xFF, 0xEB, 0xFF, 0xEB, 0xFF, + 0xFF, 0xBF, 0xFF, 0xFF, + 0xEF, 0xFE, 0xFF, 0x37, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xCF, 0xEF, 0xFD, 0xF3, + 0xFF, 0xEE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6E, 0xFD, 0x2F, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xEF, 0xCF, 0xFF, 0xF3, 0xBF, 0x69, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, + 0xFB, 0x9F, 0xFF, 0xBF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x87, + 0xFE, 0xDA, 0xEF, 0xCF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xEF, 0xBF, 0xEF, 0xEF, 0xFD, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xFD, 0xFF, 0x7B, 0xFF, 0xEB, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xEB, 0xF8, 0xFF, 0xEF, + 0xAF, 0xFF, 0xFF, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F, 0xEE, 0x7F, 0xEF, 0xFF, + 0xBB, 0xFF, 0xBF, 0xFB, + 0xFF, 0xFF, 0xFF, 0xF7, 0xF6, 0xFB, 0xBD, 0xFD, 0xDD, 0xF5, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xAF, + 0xFF, 0x5F, 0xF5, 0xDF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, + 0xF3, 0xFF, 0xDE, 0xFE, + 0xEF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xDE, 0xDF, 0x5F, 0xDF, + 0xFD, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFE, 0xFE, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, + 0xFF, 0xAF, 0xFF, 0xFF, + 0xEF, 0xED, 0xFF, 0xDF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xDA, 0xBD, 0xBE, + 0xAE, 0xFE, 0x7F, 0xFD, + 0xDF, 0xFF, 0xFF, 0x7F, 0xEF, 0xFF, 0xFB, 0xFB, 0xFB, 0x7F, 0xF7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF7, + 0xBC, 0xFD, 0xBD, 0xBD, 0xBD, 0xFD, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xAE, + 0xFF, 0xFF, 0xFD, 0xFF, + 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x9F, 0xBF, 0xBF, 0xCF, + 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xAF, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, 0xD7, 0xFE, 0xFF, 0xFF, + 0xBF, 0xE7, 0xFE, 0xBF, + 0x7F, 0xFC, 0xFF, 0xFF, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFB, + 0xFB, 0xFF, 0xFF, 0xDD, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBD, 0xDF, 0x9D, 0xFD, 0xDF, 0xB9, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xEF, 0xFF, 0xFB, 0xEF, 0xEB, 0xFF, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF6, 0x9F, 0xFF, 0xFC, + 0xFE, 0xFB, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xDF, 0xFA, 0xCD, 0xCF, + 0xBF, 0x9F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF7, 0xFE, 0xBF, 0xFF, 0xDF, 0xEF, 0x5F, 0xFF, 0xFF, 0xFF, + 0xFF, 0x7F, 0x6F, 0xFF, + 0xBB, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0xFF, + 0x5F, 0xFF, 0xBF, 0xBF, + 0xF9, 0xFF, 0xFF, 0xFF, 0x7F, 0x6E, 0x7B, 0xFF, 0xEF, 0xFD, 0xEB, 0xDF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0xB6, 0x3E, 0xFC, 0xFD, 0xBF, 0x7E, 0xFB, 0xFF, 0xFF, 0xFF, 0xF7, + 0xEF, 0xF7, 0xF3, 0xF7, + 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6E, 0x35, 0x79, 0xFF, + 0xBF, 0xFC, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xEF, 0xFB, 0x53, 0xDF, 0xFF, 0xEB, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xBC, + 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xF5, + 0xFF, 0xF7, 0xFF, 0xFB, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0xAA, 0xEE, 0xFE, 0x3F, 0x7D, + 0xFD, 0xFF, 0xFF, 0xFF, + 0x7F, 0xAF, 0x77, 0xFB, 0xFB, 0xFF, 0xFB, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0xBE, 0xBD, 0xBD, + 0xBD, 0xBD, 0xFD, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xAE, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, + 0xFF, 0xFF, 0xFF, 0xFF, 0x9A, 0xD9, 0xB8, 0xFF, 0xFF, 0x79, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xCF, + 0xFB, 0xFF, 0xEB, 0xFF, 0xEB, 0xD7, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xDE, + 0xF8, 0xFB, 0xFE, 0x3F, + 0xFB, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xAD, 0xBF, 0xFA, 0xFF, 0x73, + 0xDF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3A, 0xF5, 0xB7, 0xFC, 0x3F, 0xF9, 0xFD, 0xFF, 0xFF, 0xFF, + 0x7F, 0xEF, 0xF3, 0xFF, + 0xBF, 0xFE, 0xF3, 0x9F, 0xFE, 0xFF, 0xFF, 0xFF, 0xF7, 0x3E, 0xFF, 0xFF, + 0xFF, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xD3, 0xFE, 0xDB, 0xFF, 0xDB, 0xDF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x3E, 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, + 0xF3, 0xFF, 0xED, 0xFF, + 0xF7, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xF6, 0x3C, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x9F, 0xEF, 0xEF, 0xD1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x7E, 0xBF, + 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBB, 0xEF, 0xDF, 0xF1, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x3E, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xBF, + 0xEF, 0xFD, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, + 0xFC, 0x3E, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2E, 0xEF, 0xF3, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF7, 0xBA, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x7F, 0xAF, 0xFB, + 0xFB, 0xFD, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xF2, 0xD6, 0xED, + 0xBD, 0xBD, 0xBD, 0x7D, + 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xAF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x92, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, + 0xAF, 0xEB, 0xEB, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFE, 0x2E, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x4F, 0xEF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, + 0x3C, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xCE, + 0xC3, 0xFD, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x5D, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xCF, 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0xEE, 0x3E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xEF, 0xDF, 0xE2, 0xFF, + 0xFF, 0xFF, 0xFB, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0xBE, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x7F, 0xEE, + 0x5F, 0xE6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, + 0x7D, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xF3, 0xFB, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0xF7, 0x36, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xD3, 0xF6, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x7F, 0xEE, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xEF, 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xBA, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, + 0xFB, 0xFA, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xD6, 0xFD, 0xBD, 0xBD, 0xBD, + 0x7D, 0x7B, 0x7B, 0x7B, + 0x7B, 0xFB, 0xAE, 0xFF, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF7, 0xBA, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xEF, 0xEB, 0x6B, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFE, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x4F, 0xEF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, + 0x3E, 0x6E, 0xFC, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xC3, 0xC9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x3E, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xEF, 0xFB, + 0xD5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, + 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6F, 0xEF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFB, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF6, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFE, + 0xEF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFF, 0xFE, 0xFF, 0xF7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x7F, 0xFA, 0xEF, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE7, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFE, 0xEF, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xA7, 0xFF, 0xFC, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x7F, + 0xFE, 0xAE, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, + 0xF7, 0xFA, 0xFF, 0xFD, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xAF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF7, 0xBE, 0xBD, 0xBD, 0xBD, 0xBD, 0x7D, 0x7B, 0x7B, + 0x7B, 0x7B, 0xFB, 0xAF, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCA, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE7, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xCF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xDF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE7, 0xF2, 0xFC, + 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xAE, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x7E, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xEF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, + 0xFE, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xEF, 0xDD, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xAF, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFA, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xF6, 0x9C, 0xBD, 0xBD, 0xBD, 0xBD, 0x7D, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, + 0xAE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x7A, 0xFF, 0xFF, 0xFF, + 0xFF, 0xDF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x6F, 0xEF, 0xF7, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF7, 0xFE, + 0xFE, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xEB, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x9E, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xEF, 0xCB, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFD, + 0xFF, 0xFF, 0xFF, 0xFF, 0xBE, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xEF, + 0xEF, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFB, 0xAF, 0x7F, 0xFF, + 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xBF, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAE, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF7, 0xBC, 0xBD, + 0xBD, 0xBD, 0xBD, 0x7D, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xAF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x7F, + 0xAF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, + 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, + 0xFF, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xEF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFE, 0xFF, 0x9F, 0x9F, + 0x9F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0xFF, 0xEF, 0xDF, 0xDF, 0xDF, 0xDF, 0xCF, 0xB7, 0xBF, 0xBF, + 0xBF, 0xBF, 0xFF, 0xBC, + 0xB9, 0x9D, 0xBD, 0xBD, 0x7D, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xEF, 0xD7, + 0xF5, 0xF3, 0xF1, 0xD1, + 0x65, 0xE3, 0xE3, 0xE3, 0xA3, 0xFF, 0xFE, 0x7F, 0xFE, 0xDE, 0xDE, 0xFF, + 0xBD, 0xBD, 0xBD, 0xBD, + 0xDF, 0xEF, 0xFB, 0xF7, 0xF3, 0xF3, 0xF3, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, + 0xFB, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + + }; diff --git a/drivers/net/pcmcia/pcnet_cs.c b/drivers/net/pcmcia/pcnet_cs.c new file mode 100644 index 00000000000..b0126304ca0 --- /dev/null +++ b/drivers/net/pcmcia/pcnet_cs.c @@ -0,0 +1,1659 @@ +/*====================================================================== + + A PCMCIA ethernet driver for NS8390-based cards + + This driver supports the D-Link DE-650 and Linksys EthernetCard + cards, the newer D-Link and Linksys combo cards, Accton EN2212 + cards, the RPTI EP400, and the PreMax PE-200 in non-shared-memory + mode, and the IBM Credit Card Adapter, the NE4100, the Thomas + Conrad ethernet card, and the Kingston KNE-PCM/x in shared-memory + mode. It will also handle the Socket EA card in either mode. + + Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net + + pcnet_cs.c 1.153 2003/11/09 18:53:09 + + The network driver code is based on Donald Becker's NE2000 code: + + Written 1992,1993 by Donald Becker. + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU General Public License, + incorporated herein by reference. + Donald Becker may be reached at becker@scyld.com + + Based also on Keith Moore's changes to Don Becker's code, for IBM + CCAE support. Drivers merged back together, and shared-memory + Socket EA support added, by Ken Raeburn, September 1995. + +======================================================================*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/ethtool.h> +#include <linux/netdevice.h> +#include <../drivers/net/8390.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <asm/io.h> +#include <asm/system.h> +#include <asm/byteorder.h> +#include <asm/uaccess.h> + +#define PCNET_CMD 0x00 +#define PCNET_DATAPORT 0x10 /* NatSemi-defined port window offset. */ +#define PCNET_RESET 0x1f /* Issue a read to reset, a write to clear. */ +#define PCNET_MISC 0x18 /* For IBM CCAE and Socket EA cards */ + +#define PCNET_START_PG 0x40 /* First page of TX buffer */ +#define PCNET_STOP_PG 0x80 /* Last page +1 of RX ring */ + +/* Socket EA cards have a larger packet buffer */ +#define SOCKET_START_PG 0x01 +#define SOCKET_STOP_PG 0xff + +#define PCNET_RDC_TIMEOUT (2*HZ/100) /* Max wait in jiffies for Tx RDC */ + +static char *if_names[] = { "auto", "10baseT", "10base2"}; + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"pcnet_cs.c 1.153 2003/11/09 18:53:09 (David Hinds)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("NE2000 compatible PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +INT_MODULE_PARM(if_port, 1); /* Transceiver type */ +INT_MODULE_PARM(use_big_buf, 1); /* use 64K packet buffer? */ +INT_MODULE_PARM(mem_speed, 0); /* shared mem speed, in ns */ +INT_MODULE_PARM(delay_output, 0); /* pause after xmit? */ +INT_MODULE_PARM(delay_time, 4); /* in usec */ +INT_MODULE_PARM(use_shmem, -1); /* use shared memory? */ +INT_MODULE_PARM(full_duplex, 0); /* full duplex? */ + +/* Ugh! Let the user hardwire the hardware address for queer cards */ +static int hw_addr[6] = { 0, /* ... */ }; +module_param_array(hw_addr, int, NULL, 0); + +/*====================================================================*/ + +static void mii_phy_probe(struct net_device *dev); +static void pcnet_config(dev_link_t *link); +static void pcnet_release(dev_link_t *link); +static int pcnet_event(event_t event, int priority, + event_callback_args_t *args); +static int pcnet_open(struct net_device *dev); +static int pcnet_close(struct net_device *dev); +static int ei_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static struct ethtool_ops netdev_ethtool_ops; +static irqreturn_t ei_irq_wrapper(int irq, void *dev_id, struct pt_regs *regs); +static void ei_watchdog(u_long arg); +static void pcnet_reset_8390(struct net_device *dev); +static int set_config(struct net_device *dev, struct ifmap *map); +static int setup_shmem_window(dev_link_t *link, int start_pg, + int stop_pg, int cm_offset); +static int setup_dma_config(dev_link_t *link, int start_pg, + int stop_pg); + +static dev_link_t *pcnet_attach(void); +static void pcnet_detach(dev_link_t *); + +static dev_info_t dev_info = "pcnet_cs"; +static dev_link_t *dev_list; + +/*====================================================================*/ + +typedef struct hw_info_t { + u_int offset; + u_char a0, a1, a2; + u_int flags; +} hw_info_t; + +#define DELAY_OUTPUT 0x01 +#define HAS_MISC_REG 0x02 +#define USE_BIG_BUF 0x04 +#define HAS_IBM_MISC 0x08 +#define IS_DL10019 0x10 +#define IS_DL10022 0x20 +#define HAS_MII 0x40 +#define USE_SHMEM 0x80 /* autodetected */ + +#define AM79C9XX_HOME_PHY 0x00006B90 /* HomePNA PHY */ +#define AM79C9XX_ETH_PHY 0x00006B70 /* 10baseT PHY */ +#define MII_PHYID_REV_MASK 0xfffffff0 +#define MII_PHYID_REG1 0x02 +#define MII_PHYID_REG2 0x03 + +static hw_info_t hw_info[] = { + { /* Accton EN2212 */ 0x0ff0, 0x00, 0x00, 0xe8, DELAY_OUTPUT }, + { /* Allied Telesis LA-PCM */ 0x0ff0, 0x00, 0x00, 0xf4, 0 }, + { /* APEX MultiCard */ 0x03f4, 0x00, 0x20, 0xe5, 0 }, + { /* ASANTE FriendlyNet */ 0x4910, 0x00, 0x00, 0x94, + DELAY_OUTPUT | HAS_IBM_MISC }, + { /* Danpex EN-6200P2 */ 0x0110, 0x00, 0x40, 0xc7, 0 }, + { /* DataTrek NetCard */ 0x0ff0, 0x00, 0x20, 0xe8, 0 }, + { /* Dayna CommuniCard E */ 0x0110, 0x00, 0x80, 0x19, 0 }, + { /* D-Link DE-650 */ 0x0040, 0x00, 0x80, 0xc8, 0 }, + { /* EP-210 Ethernet */ 0x0110, 0x00, 0x40, 0x33, 0 }, + { /* EP4000 Ethernet */ 0x01c0, 0x00, 0x00, 0xb4, 0 }, + { /* Epson EEN10B */ 0x0ff0, 0x00, 0x00, 0x48, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* ELECOM Laneed LD-CDWA */ 0xb8, 0x08, 0x00, 0x42, 0 }, + { /* Hypertec Ethernet */ 0x01c0, 0x00, 0x40, 0x4c, 0 }, + { /* IBM CCAE */ 0x0ff0, 0x08, 0x00, 0x5a, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* IBM CCAE */ 0x0ff0, 0x00, 0x04, 0xac, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* IBM CCAE */ 0x0ff0, 0x00, 0x06, 0x29, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* IBM FME */ 0x0374, 0x08, 0x00, 0x5a, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* IBM FME */ 0x0374, 0x00, 0x04, 0xac, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* Kansai KLA-PCM/T */ 0x0ff0, 0x00, 0x60, 0x87, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* NSC DP83903 */ 0x0374, 0x08, 0x00, 0x17, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* NSC DP83903 */ 0x0374, 0x00, 0xc0, 0xa8, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* NSC DP83903 */ 0x0374, 0x00, 0xa0, 0xb0, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* NSC DP83903 */ 0x0198, 0x00, 0x20, 0xe0, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* I-O DATA PCLA/T */ 0x0ff0, 0x00, 0xa0, 0xb0, 0 }, + { /* Katron PE-520 */ 0x0110, 0x00, 0x40, 0xf6, 0 }, + { /* Kingston KNE-PCM/x */ 0x0ff0, 0x00, 0xc0, 0xf0, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* Kingston KNE-PCM/x */ 0x0ff0, 0xe2, 0x0c, 0x0f, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* Kingston KNE-PC2 */ 0x0180, 0x00, 0xc0, 0xf0, 0 }, + { /* Maxtech PCN2000 */ 0x5000, 0x00, 0x00, 0xe8, 0 }, + { /* NDC Instant-Link */ 0x003a, 0x00, 0x80, 0xc6, 0 }, + { /* NE2000 Compatible */ 0x0ff0, 0x00, 0xa0, 0x0c, 0 }, + { /* Network General Sniffer */ 0x0ff0, 0x00, 0x00, 0x65, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* Panasonic VEL211 */ 0x0ff0, 0x00, 0x80, 0x45, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* PreMax PE-200 */ 0x07f0, 0x00, 0x20, 0xe0, 0 }, + { /* RPTI EP400 */ 0x0110, 0x00, 0x40, 0x95, 0 }, + { /* SCM Ethernet */ 0x0ff0, 0x00, 0x20, 0xcb, 0 }, + { /* Socket EA */ 0x4000, 0x00, 0xc0, 0x1b, + DELAY_OUTPUT | HAS_MISC_REG | USE_BIG_BUF }, + { /* Socket LP-E CF+ */ 0x01c0, 0x00, 0xc0, 0x1b, 0 }, + { /* SuperSocket RE450T */ 0x0110, 0x00, 0xe0, 0x98, 0 }, + { /* Volktek NPL-402CT */ 0x0060, 0x00, 0x40, 0x05, 0 }, + { /* NEC PC-9801N-J12 */ 0x0ff0, 0x00, 0x00, 0x4c, 0 }, + { /* PCMCIA Technology OEM */ 0x01c8, 0x00, 0xa0, 0x0c, 0 } +}; + +#define NR_INFO (sizeof(hw_info)/sizeof(hw_info_t)) + +static hw_info_t default_info = { 0, 0, 0, 0, 0 }; +static hw_info_t dl10019_info = { 0, 0, 0, 0, IS_DL10019|HAS_MII }; +static hw_info_t dl10022_info = { 0, 0, 0, 0, IS_DL10022|HAS_MII }; + +typedef struct pcnet_dev_t { + dev_link_t link; + dev_node_t node; + u_int flags; + void __iomem *base; + struct timer_list watchdog; + int stale, fast_poll; + u_char phy_id; + u_char eth_phy, pna_phy; + u_short link_status; + u_long mii_reset; +} pcnet_dev_t; + +static inline pcnet_dev_t *PRIV(struct net_device *dev) +{ + char *p = netdev_priv(dev); + return (pcnet_dev_t *)(p + sizeof(struct ei_device)); +} + +/*====================================================================== + + pcnet_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *pcnet_attach(void) +{ + pcnet_dev_t *info; + dev_link_t *link; + struct net_device *dev; + client_reg_t client_reg; + int ret; + + DEBUG(0, "pcnet_attach()\n"); + + /* Create new ethernet device */ + dev = __alloc_ei_netdev(sizeof(pcnet_dev_t)); + if (!dev) return NULL; + info = PRIV(dev); + link = &info->link; + link->priv = dev; + + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.IntType = INT_MEMORY_AND_IO; + + SET_MODULE_OWNER(dev); + dev->open = &pcnet_open; + dev->stop = &pcnet_close; + dev->set_config = &set_config; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &pcnet_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + pcnet_detach(link); + return NULL; + } + + return link; +} /* pcnet_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void pcnet_detach(dev_link_t *link) +{ + struct net_device *dev = link->priv; + dev_link_t **linkp; + + DEBUG(0, "pcnet_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->dev) + unregister_netdev(dev); + + if (link->state & DEV_CONFIG) + pcnet_release(link); + + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + free_netdev(dev); +} /* pcnet_detach */ + +/*====================================================================== + + This probes for a card's hardware address, for card types that + encode this information in their CIS. + +======================================================================*/ + +static hw_info_t *get_hwinfo(dev_link_t *link) +{ + struct net_device *dev = link->priv; + win_req_t req; + memreq_t mem; + u_char __iomem *base, *virt; + int i, j; + + /* Allocate a small memory window */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = 0; req.Size = 0; + req.AccessSpeed = 0; + i = pcmcia_request_window(&link->handle, &req, &link->win); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestWindow, i); + return NULL; + } + + virt = ioremap(req.Base, req.Size); + mem.Page = 0; + for (i = 0; i < NR_INFO; i++) { + mem.CardOffset = hw_info[i].offset & ~(req.Size-1); + pcmcia_map_mem_page(link->win, &mem); + base = &virt[hw_info[i].offset & (req.Size-1)]; + if ((readb(base+0) == hw_info[i].a0) && + (readb(base+2) == hw_info[i].a1) && + (readb(base+4) == hw_info[i].a2)) + break; + } + if (i < NR_INFO) { + for (j = 0; j < 6; j++) + dev->dev_addr[j] = readb(base + (j<<1)); + } + + iounmap(virt); + j = pcmcia_release_window(link->win); + if (j != CS_SUCCESS) + cs_error(link->handle, ReleaseWindow, j); + return (i < NR_INFO) ? hw_info+i : NULL; +} /* get_hwinfo */ + +/*====================================================================== + + This probes for a card's hardware address by reading the PROM. + It checks the address against a list of known types, then falls + back to a simple NE2000 clone signature check. + +======================================================================*/ + +static hw_info_t *get_prom(dev_link_t *link) +{ + struct net_device *dev = link->priv; + kio_addr_t ioaddr = dev->base_addr; + u_char prom[32]; + int i, j; + + /* This is lifted straight from drivers/net/ne.c */ + struct { + u_char value, offset; + } program_seq[] = { + {E8390_NODMA+E8390_PAGE0+E8390_STOP, E8390_CMD}, /* Select page 0*/ + {0x48, EN0_DCFG}, /* Set byte-wide (0x48) access. */ + {0x00, EN0_RCNTLO}, /* Clear the count regs. */ + {0x00, EN0_RCNTHI}, + {0x00, EN0_IMR}, /* Mask completion irq. */ + {0xFF, EN0_ISR}, + {E8390_RXOFF, EN0_RXCR}, /* 0x20 Set to monitor */ + {E8390_TXOFF, EN0_TXCR}, /* 0x02 and loopback mode. */ + {32, EN0_RCNTLO}, + {0x00, EN0_RCNTHI}, + {0x00, EN0_RSARLO}, /* DMA starting at 0x0000. */ + {0x00, EN0_RSARHI}, + {E8390_RREAD+E8390_START, E8390_CMD}, + }; + + pcnet_reset_8390(dev); + mdelay(10); + + for (i = 0; i < sizeof(program_seq)/sizeof(program_seq[0]); i++) + outb_p(program_seq[i].value, ioaddr + program_seq[i].offset); + + for (i = 0; i < 32; i++) + prom[i] = inb(ioaddr + PCNET_DATAPORT); + for (i = 0; i < NR_INFO; i++) { + if ((prom[0] == hw_info[i].a0) && + (prom[2] == hw_info[i].a1) && + (prom[4] == hw_info[i].a2)) + break; + } + if ((i < NR_INFO) || ((prom[28] == 0x57) && (prom[30] == 0x57))) { + for (j = 0; j < 6; j++) + dev->dev_addr[j] = prom[j<<1]; + return (i < NR_INFO) ? hw_info+i : &default_info; + } + return NULL; +} /* get_prom */ + +/*====================================================================== + + For DL10019 based cards, like the Linksys EtherFast + +======================================================================*/ + +static hw_info_t *get_dl10019(dev_link_t *link) +{ + struct net_device *dev = link->priv; + int i; + u_char sum; + + for (sum = 0, i = 0x14; i < 0x1c; i++) + sum += inb_p(dev->base_addr + i); + if (sum != 0xff) + return NULL; + for (i = 0; i < 6; i++) + dev->dev_addr[i] = inb_p(dev->base_addr + 0x14 + i); + i = inb(dev->base_addr + 0x1f); + return ((i == 0x91)||(i == 0x99)) ? &dl10022_info : &dl10019_info; +} + +/*====================================================================== + + For Asix AX88190 based cards + +======================================================================*/ + +static hw_info_t *get_ax88190(dev_link_t *link) +{ + struct net_device *dev = link->priv; + kio_addr_t ioaddr = dev->base_addr; + int i, j; + + /* Not much of a test, but the alternatives are messy */ + if (link->conf.ConfigBase != 0x03c0) + return NULL; + + outb_p(0x01, ioaddr + EN0_DCFG); /* Set word-wide access. */ + outb_p(0x00, ioaddr + EN0_RSARLO); /* DMA starting at 0x0400. */ + outb_p(0x04, ioaddr + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, ioaddr + E8390_CMD); + + for (i = 0; i < 6; i += 2) { + j = inw(ioaddr + PCNET_DATAPORT); + dev->dev_addr[i] = j & 0xff; + dev->dev_addr[i+1] = j >> 8; + } + printk(KERN_NOTICE "pcnet_cs: this is an AX88190 card!\n"); + printk(KERN_NOTICE "pcnet_cs: use axnet_cs instead.\n"); + return NULL; +} + +/*====================================================================== + + This should be totally unnecessary... but when we can't figure + out the hardware address any other way, we'll let the user hard + wire it when the module is initialized. + +======================================================================*/ + +static hw_info_t *get_hwired(dev_link_t *link) +{ + struct net_device *dev = link->priv; + int i; + + for (i = 0; i < 6; i++) + if (hw_addr[i] != 0) break; + if (i == 6) + return NULL; + + for (i = 0; i < 6; i++) + dev->dev_addr[i] = hw_addr[i]; + + return &default_info; +} /* get_hwired */ + +/*====================================================================== + + pcnet_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + ethernet device available to the system. + +======================================================================*/ + +#define CS_CHECK(fn, ret) \ +do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0) + +static int try_io_port(dev_link_t *link) +{ + int j, ret; + if (link->io.NumPorts1 == 32) { + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if (link->io.NumPorts2 > 0) { + /* for master/slave multifunction cards */ + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; + } + } else { + /* This should be two 16-port windows */ + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_16; + } + if (link->io.BasePort1 == 0) { + link->io.IOAddrLines = 16; + for (j = 0; j < 0x400; j += 0x20) { + link->io.BasePort1 = j ^ 0x300; + link->io.BasePort2 = (j ^ 0x300) + 0x10; + ret = pcmcia_request_io(link->handle, &link->io); + if (ret == CS_SUCCESS) return ret; + } + return ret; + } else { + return pcmcia_request_io(link->handle, &link->io); + } +} + +static void pcnet_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + pcnet_dev_t *info = PRIV(dev); + tuple_t tuple; + cisparse_t parse; + int i, last_ret, last_fn, start_pg, stop_pg, cm_offset; + int manfid = 0, prodid = 0, has_shmem = 0; + u_short buf[64]; + config_info_t conf; + hw_info_t *hw_info; + + DEBUG(0, "pcnet_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple)); + CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse)); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* Look up current Vcc */ + CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(handle, &conf)); + link->conf.Vcc = conf.Vcc; + + tuple.DesiredTuple = CISTPL_MANFID; + tuple.Attributes = TUPLE_RETURN_COMMON; + if ((pcmcia_get_first_tuple(handle, &tuple) == CS_SUCCESS) && + (pcmcia_get_tuple_data(handle, &tuple) == CS_SUCCESS)) { + manfid = le16_to_cpu(buf[0]); + prodid = le16_to_cpu(buf[1]); + } + + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + tuple.Attributes = 0; + CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple)); + while (last_ret == CS_SUCCESS) { + cistpl_cftable_entry_t *cfg = &(parse.cftable_entry); + cistpl_io_t *io = &(parse.cftable_entry.io); + + if (pcmcia_get_tuple_data(handle, &tuple) != 0 || + pcmcia_parse_tuple(handle, &tuple, &parse) != 0 || + cfg->index == 0 || cfg->io.nwin == 0) + goto next_entry; + + link->conf.ConfigIndex = cfg->index; + /* For multifunction cards, by convention, we configure the + network function with window 0, and serial with window 1 */ + if (io->nwin > 1) { + i = (io->win[1].len > io->win[0].len); + link->io.BasePort2 = io->win[1-i].base; + link->io.NumPorts2 = io->win[1-i].len; + } else { + i = link->io.NumPorts2 = 0; + } + has_shmem = ((cfg->mem.nwin == 1) && + (cfg->mem.win[0].len >= 0x4000)); + link->io.BasePort1 = io->win[i].base; + link->io.NumPorts1 = io->win[i].len; + link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; + if (link->io.NumPorts1 + link->io.NumPorts2 >= 32) { + last_ret = try_io_port(link); + if (last_ret == CS_SUCCESS) break; + } + next_entry: + last_ret = pcmcia_get_next_tuple(handle, &tuple); + } + if (last_ret != CS_SUCCESS) { + cs_error(handle, RequestIO, last_ret); + goto failed; + } + + CS_CHECK(RequestIRQ, pcmcia_request_irq(handle, &link->irq)); + + if (link->io.NumPorts2 == 8) { + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + } + if ((manfid == MANFID_IBM) && + (prodid == PRODID_IBM_HOME_AND_AWAY)) + link->conf.ConfigIndex |= 0x10; + + CS_CHECK(RequestConfiguration, pcmcia_request_configuration(handle, &link->conf)); + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + if (info->flags & HAS_MISC_REG) { + if ((if_port == 1) || (if_port == 2)) + dev->if_port = if_port; + else + printk(KERN_NOTICE "pcnet_cs: invalid if_port requested\n"); + } else { + dev->if_port = 0; + } + + hw_info = get_hwinfo(link); + if (hw_info == NULL) + hw_info = get_prom(link); + if (hw_info == NULL) + hw_info = get_dl10019(link); + if (hw_info == NULL) + hw_info = get_ax88190(link); + if (hw_info == NULL) + hw_info = get_hwired(link); + + if (hw_info == NULL) { + printk(KERN_NOTICE "pcnet_cs: unable to read hardware net" + " address for io base %#3lx\n", dev->base_addr); + goto failed; + } + + info->flags = hw_info->flags; + /* Check for user overrides */ + info->flags |= (delay_output) ? DELAY_OUTPUT : 0; + if ((manfid == MANFID_SOCKET) && + ((prodid == PRODID_SOCKET_LPE) || + (prodid == PRODID_SOCKET_LPE_CF) || + (prodid == PRODID_SOCKET_EIO))) + info->flags &= ~USE_BIG_BUF; + if (!use_big_buf) + info->flags &= ~USE_BIG_BUF; + + if (info->flags & USE_BIG_BUF) { + start_pg = SOCKET_START_PG; + stop_pg = SOCKET_STOP_PG; + cm_offset = 0x10000; + } else { + start_pg = PCNET_START_PG; + stop_pg = PCNET_STOP_PG; + cm_offset = 0; + } + + /* has_shmem is ignored if use_shmem != -1 */ + if ((use_shmem == 0) || (!has_shmem && (use_shmem == -1)) || + (setup_shmem_window(link, start_pg, stop_pg, cm_offset) != 0)) + setup_dma_config(link, start_pg, stop_pg); + + ei_status.name = "NE2000"; + ei_status.word16 = 1; + ei_status.reset_8390 = &pcnet_reset_8390; + + SET_ETHTOOL_OPS(dev, &netdev_ethtool_ops); + + if (info->flags & (IS_DL10019|IS_DL10022)) { + u_char id = inb(dev->base_addr + 0x1a); + dev->do_ioctl = &ei_ioctl; + mii_phy_probe(dev); + if ((id == 0x30) && !info->pna_phy && (info->eth_phy == 4)) + info->eth_phy = 0; + } + + link->dev = &info->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + +#ifdef CONFIG_NET_POLL_CONTROLLER + dev->poll_controller = ei_poll; +#endif + + if (register_netdev(dev) != 0) { + printk(KERN_NOTICE "pcnet_cs: register_netdev() failed\n"); + link->dev = NULL; + goto failed; + } + + strcpy(info->node.dev_name, dev->name); + + if (info->flags & (IS_DL10019|IS_DL10022)) { + u_char id = inb(dev->base_addr + 0x1a); + printk(KERN_INFO "%s: NE2000 (DL100%d rev %02x): ", + dev->name, ((info->flags & IS_DL10022) ? 22 : 19), id); + if (info->pna_phy) + printk("PNA, "); + } else { + printk(KERN_INFO "%s: NE2000 Compatible: ", dev->name); + } + printk("io %#3lx, irq %d,", dev->base_addr, dev->irq); + if (info->flags & USE_SHMEM) + printk (" mem %#5lx,", dev->mem_start); + if (info->flags & HAS_MISC_REG) + printk(" %s xcvr,", if_names[dev->if_port]); + printk(" hw_addr "); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + pcnet_release(link); + link->state &= ~DEV_CONFIG_PENDING; + return; +} /* pcnet_config */ + +/*====================================================================== + + After a card is removed, pcnet_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void pcnet_release(dev_link_t *link) +{ + pcnet_dev_t *info = PRIV(link->priv); + + DEBUG(0, "pcnet_release(0x%p)\n", link); + + if (info->flags & USE_SHMEM) { + iounmap(info->base); + pcmcia_release_window(link->win); + } + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + +======================================================================*/ + +static int pcnet_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct net_device *dev = link->priv; + + DEBUG(2, "pcnet_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + pcnet_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) + netif_device_detach(dev); + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + pcnet_reset_8390(dev); + NS8390_init(dev, 1); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* pcnet_event */ + +/*====================================================================== + + MII interface support for DL10019 and DL10022 based cards + + On the DL10019, the MII IO direction bit is 0x10; on the DL10022 + it is 0x20. Setting both bits seems to work on both card types. + +======================================================================*/ + +#define DLINK_GPIO 0x1c +#define DLINK_DIAG 0x1d +#define DLINK_EEPROM 0x1e + +#define MDIO_SHIFT_CLK 0x80 +#define MDIO_DATA_OUT 0x40 +#define MDIO_DIR_WRITE 0x30 +#define MDIO_DATA_WRITE0 (MDIO_DIR_WRITE) +#define MDIO_DATA_WRITE1 (MDIO_DIR_WRITE | MDIO_DATA_OUT) +#define MDIO_DATA_READ 0x10 +#define MDIO_MASK 0x0f + +static void mdio_sync(kio_addr_t addr) +{ + int bits, mask = inb(addr) & MDIO_MASK; + for (bits = 0; bits < 32; bits++) { + outb(mask | MDIO_DATA_WRITE1, addr); + outb(mask | MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, addr); + } +} + +static int mdio_read(kio_addr_t addr, int phy_id, int loc) +{ + u_int cmd = (0x06<<10)|(phy_id<<5)|loc; + int i, retval = 0, mask = inb(addr) & MDIO_MASK; + + mdio_sync(addr); + for (i = 13; i >= 0; i--) { + int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outb(mask | dat, addr); + outb(mask | dat | MDIO_SHIFT_CLK, addr); + } + for (i = 19; i > 0; i--) { + outb(mask, addr); + retval = (retval << 1) | ((inb(addr) & MDIO_DATA_READ) != 0); + outb(mask | MDIO_SHIFT_CLK, addr); + } + return (retval>>1) & 0xffff; +} + +static void mdio_write(kio_addr_t addr, int phy_id, int loc, int value) +{ + u_int cmd = (0x05<<28)|(phy_id<<23)|(loc<<18)|(1<<17)|value; + int i, mask = inb(addr) & MDIO_MASK; + + mdio_sync(addr); + for (i = 31; i >= 0; i--) { + int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outb(mask | dat, addr); + outb(mask | dat | MDIO_SHIFT_CLK, addr); + } + for (i = 1; i >= 0; i--) { + outb(mask, addr); + outb(mask | MDIO_SHIFT_CLK, addr); + } +} + +static void mdio_reset(kio_addr_t addr, int phy_id) +{ + outb_p(0x08, addr); + outb_p(0x0c, addr); + outb_p(0x08, addr); + outb_p(0x0c, addr); + outb_p(0x00, addr); +} + +/*====================================================================== + + EEPROM access routines for DL10019 and DL10022 based cards + +======================================================================*/ + +#define EE_EEP 0x40 +#define EE_ASIC 0x10 +#define EE_CS 0x08 +#define EE_CK 0x04 +#define EE_DO 0x02 +#define EE_DI 0x01 +#define EE_ADOT 0x01 /* DataOut for ASIC */ +#define EE_READ_CMD 0x06 + +#define DL19FDUPLX 0x0400 /* DL10019 Full duplex mode */ + +static int read_eeprom(kio_addr_t ioaddr, int location) +{ + int i, retval = 0; + kio_addr_t ee_addr = ioaddr + DLINK_EEPROM; + int read_cmd = location | (EE_READ_CMD << 8); + + outb(0, ee_addr); + outb(EE_EEP|EE_CS, ee_addr); + + /* Shift the read command bits out. */ + for (i = 10; i >= 0; i--) { + short dataval = (read_cmd & (1 << i)) ? EE_DO : 0; + outb_p(EE_EEP|EE_CS|dataval, ee_addr); + outb_p(EE_EEP|EE_CS|dataval|EE_CK, ee_addr); + } + outb(EE_EEP|EE_CS, ee_addr); + + for (i = 16; i > 0; i--) { + outb_p(EE_EEP|EE_CS | EE_CK, ee_addr); + retval = (retval << 1) | ((inb(ee_addr) & EE_DI) ? 1 : 0); + outb_p(EE_EEP|EE_CS, ee_addr); + } + + /* Terminate the EEPROM access. */ + outb(0, ee_addr); + return retval; +} + +/* + The internal ASIC registers can be changed by EEPROM READ access + with EE_ASIC bit set. + In ASIC mode, EE_ADOT is used to output the data to the ASIC. +*/ + +static void write_asic(kio_addr_t ioaddr, int location, short asic_data) +{ + int i; + kio_addr_t ee_addr = ioaddr + DLINK_EEPROM; + short dataval; + int read_cmd = location | (EE_READ_CMD << 8); + + asic_data |= read_eeprom(ioaddr, location); + + outb(0, ee_addr); + outb(EE_ASIC|EE_CS|EE_DI, ee_addr); + + read_cmd = read_cmd >> 1; + + /* Shift the read command bits out. */ + for (i = 9; i >= 0; i--) { + dataval = (read_cmd & (1 << i)) ? EE_DO : 0; + outb_p(EE_ASIC|EE_CS|EE_DI|dataval, ee_addr); + outb_p(EE_ASIC|EE_CS|EE_DI|dataval|EE_CK, ee_addr); + outb_p(EE_ASIC|EE_CS|EE_DI|dataval, ee_addr); + } + // sync + outb(EE_ASIC|EE_CS, ee_addr); + outb(EE_ASIC|EE_CS|EE_CK, ee_addr); + outb(EE_ASIC|EE_CS, ee_addr); + + for (i = 15; i >= 0; i--) { + dataval = (asic_data & (1 << i)) ? EE_ADOT : 0; + outb_p(EE_ASIC|EE_CS|dataval, ee_addr); + outb_p(EE_ASIC|EE_CS|dataval|EE_CK, ee_addr); + outb_p(EE_ASIC|EE_CS|dataval, ee_addr); + } + + /* Terminate the ASIC access. */ + outb(EE_ASIC|EE_DI, ee_addr); + outb(EE_ASIC|EE_DI| EE_CK, ee_addr); + outb(EE_ASIC|EE_DI, ee_addr); + + outb(0, ee_addr); +} + +/*====================================================================*/ + +static void set_misc_reg(struct net_device *dev) +{ + kio_addr_t nic_base = dev->base_addr; + pcnet_dev_t *info = PRIV(dev); + u_char tmp; + + if (info->flags & HAS_MISC_REG) { + tmp = inb_p(nic_base + PCNET_MISC) & ~3; + if (dev->if_port == 2) + tmp |= 1; + if (info->flags & USE_BIG_BUF) + tmp |= 2; + if (info->flags & HAS_IBM_MISC) + tmp |= 8; + outb_p(tmp, nic_base + PCNET_MISC); + } + if (info->flags & IS_DL10022) { + if (info->flags & HAS_MII) { + mdio_reset(nic_base + DLINK_GPIO, info->eth_phy); + /* Restart MII autonegotiation */ + mdio_write(nic_base + DLINK_GPIO, info->eth_phy, 0, 0x0000); + mdio_write(nic_base + DLINK_GPIO, info->eth_phy, 0, 0x1200); + info->mii_reset = jiffies; + } else { + outb(full_duplex ? 4 : 0, nic_base + DLINK_DIAG); + } + } +} + +/*====================================================================*/ + +static void mii_phy_probe(struct net_device *dev) +{ + pcnet_dev_t *info = PRIV(dev); + kio_addr_t mii_addr = dev->base_addr + DLINK_GPIO; + int i; + u_int tmp, phyid; + + for (i = 31; i >= 0; i--) { + tmp = mdio_read(mii_addr, i, 1); + if ((tmp == 0) || (tmp == 0xffff)) + continue; + tmp = mdio_read(mii_addr, i, MII_PHYID_REG1); + phyid = tmp << 16; + phyid |= mdio_read(mii_addr, i, MII_PHYID_REG2); + phyid &= MII_PHYID_REV_MASK; + DEBUG(0, "%s: MII at %d is 0x%08x\n", dev->name, i, phyid); + if (phyid == AM79C9XX_HOME_PHY) { + info->pna_phy = i; + } else if (phyid != AM79C9XX_ETH_PHY) { + info->eth_phy = i; + } + } +} + +static int pcnet_open(struct net_device *dev) +{ + pcnet_dev_t *info = PRIV(dev); + dev_link_t *link = &info->link; + + DEBUG(2, "pcnet_open('%s')\n", dev->name); + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + + set_misc_reg(dev); + request_irq(dev->irq, ei_irq_wrapper, SA_SHIRQ, dev_info, dev); + + info->phy_id = info->eth_phy; + info->link_status = 0x00; + init_timer(&info->watchdog); + info->watchdog.function = &ei_watchdog; + info->watchdog.data = (u_long)dev; + info->watchdog.expires = jiffies + HZ; + add_timer(&info->watchdog); + + return ei_open(dev); +} /* pcnet_open */ + +/*====================================================================*/ + +static int pcnet_close(struct net_device *dev) +{ + pcnet_dev_t *info = PRIV(dev); + dev_link_t *link = &info->link; + + DEBUG(2, "pcnet_close('%s')\n", dev->name); + + ei_close(dev); + free_irq(dev->irq, dev); + + link->open--; + netif_stop_queue(dev); + del_timer_sync(&info->watchdog); + + return 0; +} /* pcnet_close */ + +/*====================================================================== + + Hard reset the card. This used to pause for the same period that + a 8390 reset command required, but that shouldn't be necessary. + +======================================================================*/ + +static void pcnet_reset_8390(struct net_device *dev) +{ + kio_addr_t nic_base = dev->base_addr; + int i; + + ei_status.txing = ei_status.dmaing = 0; + + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, nic_base + E8390_CMD); + + outb(inb(nic_base + PCNET_RESET), nic_base + PCNET_RESET); + + for (i = 0; i < 100; i++) { + if ((inb_p(nic_base+EN0_ISR) & ENISR_RESET) != 0) + break; + udelay(100); + } + outb_p(ENISR_RESET, nic_base + EN0_ISR); /* Ack intr. */ + + if (i == 100) + printk(KERN_ERR "%s: pcnet_reset_8390() did not complete.\n", + dev->name); + set_misc_reg(dev); + +} /* pcnet_reset_8390 */ + +/*====================================================================*/ + +static int set_config(struct net_device *dev, struct ifmap *map) +{ + pcnet_dev_t *info = PRIV(dev); + if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { + if (!(info->flags & HAS_MISC_REG)) + return -EOPNOTSUPP; + else if ((map->port < 1) || (map->port > 2)) + return -EINVAL; + dev->if_port = map->port; + printk(KERN_INFO "%s: switched to %s port\n", + dev->name, if_names[dev->if_port]); + NS8390_init(dev, 1); + } + return 0; +} + +/*====================================================================*/ + +static irqreturn_t ei_irq_wrapper(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = dev_id; + pcnet_dev_t *info = PRIV(dev); + irqreturn_t ret = ei_interrupt(irq, dev_id, regs); + + if (ret == IRQ_HANDLED) + info->stale = 0; + return ret; +} + +static void ei_watchdog(u_long arg) +{ + struct net_device *dev = (struct net_device *)arg; + pcnet_dev_t *info = PRIV(dev); + kio_addr_t nic_base = dev->base_addr; + kio_addr_t mii_addr = nic_base + DLINK_GPIO; + u_short link; + + if (!netif_device_present(dev)) goto reschedule; + + /* Check for pending interrupt with expired latency timer: with + this, we can limp along even if the interrupt is blocked */ + outb_p(E8390_NODMA+E8390_PAGE0, nic_base + E8390_CMD); + if (info->stale++ && (inb_p(nic_base + EN0_ISR) & ENISR_ALL)) { + if (!info->fast_poll) + printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); + ei_irq_wrapper(dev->irq, dev, NULL); + info->fast_poll = HZ; + } + if (info->fast_poll) { + info->fast_poll--; + info->watchdog.expires = jiffies + 1; + add_timer(&info->watchdog); + return; + } + + if (!(info->flags & HAS_MII)) + goto reschedule; + + mdio_read(mii_addr, info->phy_id, 1); + link = mdio_read(mii_addr, info->phy_id, 1); + if (!link || (link == 0xffff)) { + if (info->eth_phy) { + info->phy_id = info->eth_phy = 0; + } else { + printk(KERN_INFO "%s: MII is missing!\n", dev->name); + info->flags &= ~HAS_MII; + } + goto reschedule; + } + + link &= 0x0004; + if (link != info->link_status) { + u_short p = mdio_read(mii_addr, info->phy_id, 5); + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (link) ? "found" : "lost"); + if (link && (info->flags & IS_DL10022)) { + /* Disable collision detection on full duplex links */ + outb((p & 0x0140) ? 4 : 0, nic_base + DLINK_DIAG); + } else if (link && (info->flags & IS_DL10019)) { + /* Disable collision detection on full duplex links */ + write_asic(dev->base_addr, 4, (p & 0x140) ? DL19FDUPLX : 0); + } + if (link) { + if (info->phy_id == info->eth_phy) { + if (p) + printk(KERN_INFO "%s: autonegotiation complete: " + "%sbaseT-%cD selected\n", dev->name, + ((p & 0x0180) ? "100" : "10"), + ((p & 0x0140) ? 'F' : 'H')); + else + printk(KERN_INFO "%s: link partner did not " + "autonegotiate\n", dev->name); + } + NS8390_init(dev, 1); + } + info->link_status = link; + } + if (info->pna_phy && time_after(jiffies, info->mii_reset + 6*HZ)) { + link = mdio_read(mii_addr, info->eth_phy, 1) & 0x0004; + if (((info->phy_id == info->pna_phy) && link) || + ((info->phy_id != info->pna_phy) && !link)) { + /* isolate this MII and try flipping to the other one */ + mdio_write(mii_addr, info->phy_id, 0, 0x0400); + info->phy_id ^= info->pna_phy ^ info->eth_phy; + printk(KERN_INFO "%s: switched to %s transceiver\n", dev->name, + (info->phy_id == info->eth_phy) ? "ethernet" : "PNA"); + mdio_write(mii_addr, info->phy_id, 0, + (info->phy_id == info->eth_phy) ? 0x1000 : 0); + info->link_status = 0; + info->mii_reset = jiffies; + } + } + +reschedule: + info->watchdog.expires = jiffies + HZ; + add_timer(&info->watchdog); +} + +/*====================================================================*/ + +static void netdev_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, "pcnet_cs"); +} + +static struct ethtool_ops netdev_ethtool_ops = { + .get_drvinfo = netdev_get_drvinfo, +}; + +/*====================================================================*/ + + +static int ei_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + pcnet_dev_t *info = PRIV(dev); + u16 *data = (u16 *)&rq->ifr_ifru; + kio_addr_t mii_addr = dev->base_addr + DLINK_GPIO; + switch (cmd) { + case SIOCGMIIPHY: + data[0] = info->phy_id; + case SIOCGMIIREG: /* Read MII PHY register. */ + data[3] = mdio_read(mii_addr, data[0], data[1] & 0x1f); + return 0; + case SIOCSMIIREG: /* Write MII PHY register. */ + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + mdio_write(mii_addr, data[0], data[1] & 0x1f, data[2]); + return 0; + } + return -EOPNOTSUPP; +} + +/*====================================================================*/ + +static void dma_get_8390_hdr(struct net_device *dev, + struct e8390_pkt_hdr *hdr, + int ring_page) +{ + kio_addr_t nic_base = dev->base_addr; + + if (ei_status.dmaing) { + printk(KERN_NOTICE "%s: DMAing conflict in dma_block_input." + "[DMAstat:%1x][irqlock:%1x]\n", + dev->name, ei_status.dmaing, ei_status.irqlock); + return; + } + + ei_status.dmaing |= 0x01; + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base + PCNET_CMD); + outb_p(sizeof(struct e8390_pkt_hdr), nic_base + EN0_RCNTLO); + outb_p(0, nic_base + EN0_RCNTHI); + outb_p(0, nic_base + EN0_RSARLO); /* On page boundary */ + outb_p(ring_page, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + PCNET_CMD); + + insw(nic_base + PCNET_DATAPORT, hdr, + sizeof(struct e8390_pkt_hdr)>>1); + /* Fix for big endian systems */ + hdr->count = le16_to_cpu(hdr->count); + + outb_p(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; +} + +/*====================================================================*/ + +static void dma_block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset) +{ + kio_addr_t nic_base = dev->base_addr; + int xfer_count = count; + char *buf = skb->data; + +#ifdef PCMCIA_DEBUG + if ((ei_debug > 4) && (count != 4)) + printk(KERN_DEBUG "%s: [bi=%d]\n", dev->name, count+4); +#endif + if (ei_status.dmaing) { + printk(KERN_NOTICE "%s: DMAing conflict in dma_block_input." + "[DMAstat:%1x][irqlock:%1x]\n", + dev->name, ei_status.dmaing, ei_status.irqlock); + return; + } + ei_status.dmaing |= 0x01; + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base + PCNET_CMD); + outb_p(count & 0xff, nic_base + EN0_RCNTLO); + outb_p(count >> 8, nic_base + EN0_RCNTHI); + outb_p(ring_offset & 0xff, nic_base + EN0_RSARLO); + outb_p(ring_offset >> 8, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + PCNET_CMD); + + insw(nic_base + PCNET_DATAPORT,buf,count>>1); + if (count & 0x01) + buf[count-1] = inb(nic_base + PCNET_DATAPORT), xfer_count++; + + /* This was for the ALPHA version only, but enough people have + encountering problems that it is still here. */ +#ifdef PCMCIA_DEBUG + if (ei_debug > 4) { /* DMA termination address check... */ + int addr, tries = 20; + do { + /* DON'T check for 'inb_p(EN0_ISR) & ENISR_RDC' here + -- it's broken for Rx on some cards! */ + int high = inb_p(nic_base + EN0_RSARHI); + int low = inb_p(nic_base + EN0_RSARLO); + addr = (high << 8) + low; + if (((ring_offset + xfer_count) & 0xff) == (addr & 0xff)) + break; + } while (--tries > 0); + if (tries <= 0) + printk(KERN_NOTICE "%s: RX transfer address mismatch," + "%#4.4x (expected) vs. %#4.4x (actual).\n", + dev->name, ring_offset + xfer_count, addr); + } +#endif + outb_p(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; +} /* dma_block_input */ + +/*====================================================================*/ + +static void dma_block_output(struct net_device *dev, int count, + const u_char *buf, const int start_page) +{ + kio_addr_t nic_base = dev->base_addr; + pcnet_dev_t *info = PRIV(dev); +#ifdef PCMCIA_DEBUG + int retries = 0; +#endif + u_long dma_start; + +#ifdef PCMCIA_DEBUG + if (ei_debug > 4) + printk(KERN_DEBUG "%s: [bo=%d]\n", dev->name, count); +#endif + + /* Round the count up for word writes. Do we need to do this? + What effect will an odd byte count have on the 8390? + I should check someday. */ + if (count & 0x01) + count++; + if (ei_status.dmaing) { + printk(KERN_NOTICE "%s: DMAing conflict in dma_block_output." + "[DMAstat:%1x][irqlock:%1x]\n", + dev->name, ei_status.dmaing, ei_status.irqlock); + return; + } + ei_status.dmaing |= 0x01; + /* We should already be in page 0, but to be safe... */ + outb_p(E8390_PAGE0+E8390_START+E8390_NODMA, nic_base+PCNET_CMD); + +#ifdef PCMCIA_DEBUG + retry: +#endif + + outb_p(ENISR_RDC, nic_base + EN0_ISR); + + /* Now the normal output. */ + outb_p(count & 0xff, nic_base + EN0_RCNTLO); + outb_p(count >> 8, nic_base + EN0_RCNTHI); + outb_p(0x00, nic_base + EN0_RSARLO); + outb_p(start_page, nic_base + EN0_RSARHI); + + outb_p(E8390_RWRITE+E8390_START, nic_base + PCNET_CMD); + outsw(nic_base + PCNET_DATAPORT, buf, count>>1); + + dma_start = jiffies; + +#ifdef PCMCIA_DEBUG + /* This was for the ALPHA version only, but enough people have + encountering problems that it is still here. */ + if (ei_debug > 4) { /* DMA termination address check... */ + int addr, tries = 20; + do { + int high = inb_p(nic_base + EN0_RSARHI); + int low = inb_p(nic_base + EN0_RSARLO); + addr = (high << 8) + low; + if ((start_page << 8) + count == addr) + break; + } while (--tries > 0); + if (tries <= 0) { + printk(KERN_NOTICE "%s: Tx packet transfer address mismatch," + "%#4.4x (expected) vs. %#4.4x (actual).\n", + dev->name, (start_page << 8) + count, addr); + if (retries++ == 0) + goto retry; + } + } +#endif + + while ((inb_p(nic_base + EN0_ISR) & ENISR_RDC) == 0) + if (time_after(jiffies, dma_start + PCNET_RDC_TIMEOUT)) { + printk(KERN_NOTICE "%s: timeout waiting for Tx RDC.\n", + dev->name); + pcnet_reset_8390(dev); + NS8390_init(dev, 1); + break; + } + + outb_p(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + if (info->flags & DELAY_OUTPUT) + udelay((long)delay_time); + ei_status.dmaing &= ~0x01; +} + +/*====================================================================*/ + +static int setup_dma_config(dev_link_t *link, int start_pg, + int stop_pg) +{ + struct net_device *dev = link->priv; + + ei_status.tx_start_page = start_pg; + ei_status.rx_start_page = start_pg + TX_PAGES; + ei_status.stop_page = stop_pg; + + /* set up block i/o functions */ + ei_status.get_8390_hdr = &dma_get_8390_hdr; + ei_status.block_input = &dma_block_input; + ei_status.block_output = &dma_block_output; + + return 0; +} + +/*====================================================================*/ + +static void copyin(void *dest, void __iomem *src, int c) +{ + u_short *d = dest; + u_short __iomem *s = src; + int odd; + + if (c <= 0) + return; + odd = (c & 1); c >>= 1; + + if (c) { + do { *d++ = __raw_readw(s++); } while (--c); + } + /* get last byte by fetching a word and masking */ + if (odd) + *((u_char *)d) = readw(s) & 0xff; +} + +static void copyout(void __iomem *dest, const void *src, int c) +{ + u_short __iomem *d = dest; + const u_short *s = src; + int odd; + + if (c <= 0) + return; + odd = (c & 1); c >>= 1; + + if (c) { + do { __raw_writew(*s++, d++); } while (--c); + } + /* copy last byte doing a read-modify-write */ + if (odd) + writew((readw(d) & 0xff00) | *(u_char *)s, d); +} + +/*====================================================================*/ + +static void shmem_get_8390_hdr(struct net_device *dev, + struct e8390_pkt_hdr *hdr, + int ring_page) +{ + void __iomem *xfer_start = ei_status.mem + (TX_PAGES<<8) + + (ring_page << 8) + - (ei_status.rx_start_page << 8); + + copyin(hdr, xfer_start, sizeof(struct e8390_pkt_hdr)); + /* Fix for big endian systems */ + hdr->count = le16_to_cpu(hdr->count); +} + +/*====================================================================*/ + +static void shmem_block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset) +{ + void __iomem *xfer_start = ei_status.mem + (TX_PAGES<<8) + + ring_offset + - (ei_status.rx_start_page << 8); + char *buf = skb->data; + + if (xfer_start + count > (void __iomem *)ei_status.rmem_end) { + /* We must wrap the input move. */ + int semi_count = (void __iomem *)ei_status.rmem_end - xfer_start; + copyin(buf, xfer_start, semi_count); + buf += semi_count; + xfer_start = ei_status.mem + (TX_PAGES<<8); + count -= semi_count; + } + copyin(buf, xfer_start, count); +} + +/*====================================================================*/ + +static void shmem_block_output(struct net_device *dev, int count, + const u_char *buf, const int start_page) +{ + void __iomem *shmem = ei_status.mem + (start_page << 8); + shmem -= ei_status.tx_start_page << 8; + copyout(shmem, buf, count); +} + +/*====================================================================*/ + +static int setup_shmem_window(dev_link_t *link, int start_pg, + int stop_pg, int cm_offset) +{ + struct net_device *dev = link->priv; + pcnet_dev_t *info = PRIV(dev); + win_req_t req; + memreq_t mem; + int i, window_size, offset, last_ret, last_fn; + + window_size = (stop_pg - start_pg) << 8; + if (window_size > 32 * 1024) + window_size = 32 * 1024; + + /* Make sure it's a power of two. */ + while ((window_size & (window_size - 1)) != 0) + window_size += window_size & ~(window_size - 1); + + /* Allocate a memory window */ + req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM|WIN_ENABLE; + req.Attributes |= WIN_USE_WAIT; + req.Base = 0; req.Size = window_size; + req.AccessSpeed = mem_speed; + CS_CHECK(RequestWindow, pcmcia_request_window(&link->handle, &req, &link->win)); + + mem.CardOffset = (start_pg << 8) + cm_offset; + offset = mem.CardOffset % window_size; + mem.CardOffset -= offset; + mem.Page = 0; + CS_CHECK(MapMemPage, pcmcia_map_mem_page(link->win, &mem)); + + /* Try scribbling on the buffer */ + info->base = ioremap(req.Base, window_size); + for (i = 0; i < (TX_PAGES<<8); i += 2) + __raw_writew((i>>1), info->base+offset+i); + udelay(100); + for (i = 0; i < (TX_PAGES<<8); i += 2) + if (__raw_readw(info->base+offset+i) != (i>>1)) break; + pcnet_reset_8390(dev); + if (i != (TX_PAGES<<8)) { + iounmap(info->base); + pcmcia_release_window(link->win); + info->base = NULL; link->win = NULL; + goto failed; + } + + ei_status.mem = info->base + offset; + dev->mem_start = (u_long)ei_status.mem; + dev->mem_end = ei_status.rmem_end = (u_long)info->base + req.Size; + + ei_status.tx_start_page = start_pg; + ei_status.rx_start_page = start_pg + TX_PAGES; + ei_status.stop_page = start_pg + ((req.Size - offset) >> 8); + + /* set up block i/o functions */ + ei_status.get_8390_hdr = &shmem_get_8390_hdr; + ei_status.block_input = &shmem_block_input; + ei_status.block_output = &shmem_block_output; + + info->flags |= USE_SHMEM; + return 0; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + return 1; +} + +/*====================================================================*/ + +static struct pcmcia_driver pcnet_driver = { + .drv = { + .name = "pcnet_cs", + }, + .attach = pcnet_attach, + .detach = pcnet_detach, + .owner = THIS_MODULE, +}; + +static int __init init_pcnet_cs(void) +{ + return pcmcia_register_driver(&pcnet_driver); +} + +static void __exit exit_pcnet_cs(void) +{ + DEBUG(0, "pcnet_cs: unloading\n"); + pcmcia_unregister_driver(&pcnet_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_pcnet_cs); +module_exit(exit_pcnet_cs); diff --git a/drivers/net/pcmcia/smc91c92_cs.c b/drivers/net/pcmcia/smc91c92_cs.c new file mode 100644 index 00000000000..85a15217314 --- /dev/null +++ b/drivers/net/pcmcia/smc91c92_cs.c @@ -0,0 +1,2260 @@ +/*====================================================================== + + 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. + +======================================================================*/ + +#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 <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> + +#include <asm/io.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +/* Ositech Seven of Diamonds firmware */ +#include "ositech.h" + +/*====================================================================*/ + +static char *if_names[] = { "auto", "10baseT", "10base2"}; + +/* Module parameters */ + +MODULE_DESCRIPTION("SMC 91c92 series PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#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); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +static const char *version = +"smc91c92_cs.c 0.09 1996/8/4 Donald Becker, becker@scyld.com.\n"; +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +#else +#define DEBUG(n, args...) +#endif + +#define DRV_NAME "smc91c92_cs" +#define DRV_VERSION "1.122" + +/*====================================================================*/ + +/* 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 + +static dev_info_t dev_info = "smc91c92_cs"; + +static dev_link_t *dev_list; + +struct smc_private { + dev_link_t link; + spinlock_t lock; + u_short manfid; + u_short cardid; + struct net_device_stats stats; + dev_node_t node; + 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 dev_link_t *smc91c92_attach(void); +static void smc91c92_detach(dev_link_t *); +static void smc91c92_config(dev_link_t *link); +static void smc91c92_release(dev_link_t *link); +static int smc91c92_event(event_t event, int priority, + event_callback_args_t *args); + +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 int smc_start_xmit(struct sk_buff *skb, struct net_device *dev); +static irqreturn_t smc_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void smc_rx(struct net_device *dev); +static struct net_device_stats *smc_get_stats(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(kio_addr_t 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 struct ethtool_ops ethtool_ops; + +/*====================================================================== + + smc91c92_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *smc91c92_attach(void) +{ + client_reg_t client_reg; + struct smc_private *smc; + dev_link_t *link; + struct net_device *dev; + int ret; + + DEBUG(0, "smc91c92_attach()\n"); + + /* Create new ethernet device */ + dev = alloc_etherdev(sizeof(struct smc_private)); + if (!dev) + return NULL; + smc = netdev_priv(dev); + link = &smc->link; + link->priv = dev; + + spin_lock_init(&smc->lock); + link->io.NumPorts1 = 16; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.IOAddrLines = 4; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + link->irq.Handler = &smc_interrupt; + link->irq.Instance = dev; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* The SMC91c92-specific entries in the device structure. */ + SET_MODULE_OWNER(dev); + dev->hard_start_xmit = &smc_start_xmit; + dev->get_stats = &smc_get_stats; + dev->set_config = &s9k_config; + dev->set_multicast_list = &set_rx_mode; + dev->open = &smc_open; + dev->stop = &smc_close; + dev->do_ioctl = &smc_ioctl; + SET_ETHTOOL_OPS(dev, ðtool_ops); +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = smc_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + 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; + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &smc91c92_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = pcmcia_register_client(&link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + smc91c92_detach(link); + return NULL; + } + + return link; +} /* smc91c92_attach */ + +/*====================================================================== + + This deletes a driver "instance". The device is de-registered + with Card Services. If it has been released, all local data + structures are freed. Otherwise, the structures will be freed + when the device is released. + +======================================================================*/ + +static void smc91c92_detach(dev_link_t *link) +{ + struct net_device *dev = link->priv; + dev_link_t **linkp; + + DEBUG(0, "smc91c92_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->dev) + unregister_netdev(dev); + + if (link->state & DEV_CONFIG) + smc91c92_release(link); + + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + 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; +} + +/*====================================================================*/ + +static int first_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i; + + if ((i = pcmcia_get_first_tuple(handle, tuple)) != CS_SUCCESS || + (i = pcmcia_get_tuple_data(handle, tuple)) != CS_SUCCESS) + return i; + return pcmcia_parse_tuple(handle, tuple, parse); +} + +static int next_tuple(client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i; + + if ((i = pcmcia_get_next_tuple(handle, tuple)) != CS_SUCCESS || + (i = pcmcia_get_tuple_data(handle, tuple)) != CS_SUCCESS) + return i; + return pcmcia_parse_tuple(handle, tuple, parse); +} + +/*====================================================================== + + 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(dev_link_t *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->conf.ConfigBase + CISREG_COR); + udelay(5); + writeb(tmp, smc->base + link->conf.ConfigBase + CISREG_COR); + + return 0; +} + +static int mhz_mfc_config(dev_link_t *link) +{ + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + tuple_t tuple; + cisparse_t parse; + u_char buf[255]; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + win_req_t req; + memreq_t mem; + int i, k; + + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED|IRQ_HANDLE_PRESENT; + link->io.IOAddrLines = 16; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts2 = 8; + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + + i = first_tuple(link->handle, &tuple, &parse); + /* The Megahertz combo cards have modem-like CIS entries, so + we have to explicitly try a bunch of port combinations. */ + while (i == CS_SUCCESS) { + link->conf.ConfigIndex = cf->index; + link->io.BasePort2 = cf->io.win[0].base; + for (k = 0; k < 0x400; k += 0x10) { + if (k & 0x80) continue; + link->io.BasePort1 = k ^ 0x300; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + if (i == CS_SUCCESS) break; + i = next_tuple(link->handle, &tuple, &parse); + } + if (i != CS_SUCCESS) + return i; + dev->base_addr = link->io.BasePort1; + + /* Allocate a memory window, for accessing the ISR */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = req.Size = 0; + req.AccessSpeed = 0; + i = pcmcia_request_window(&link->handle, &req, &link->win); + if (i != CS_SUCCESS) + return i; + smc->base = ioremap(req.Base, req.Size); + mem.CardOffset = mem.Page = 0; + if (smc->manfid == MANFID_MOTOROLA) + mem.CardOffset = link->conf.ConfigBase; + i = pcmcia_map_mem_page(link->win, &mem); + + if ((i == CS_SUCCESS) + && (smc->manfid == MANFID_MEGAHERTZ) + && (smc->cardid == PRODID_MEGAHERTZ_EM3288)) + mhz_3288_power(link); + + return i; +} + +static int mhz_setup(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + tuple_t tuple; + cisparse_t parse; + u_char buf[255], *station_addr; + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + + /* Read the station address from the CIS. It is stored as the last + (fourth) string in the Version 1 Version/ID tuple. */ + tuple.DesiredTuple = CISTPL_VERS_1; + if (first_tuple(handle, &tuple, &parse) != CS_SUCCESS) + return -1; + /* Ugh -- the EM1144 card has two VERS_1 tuples!?! */ + if (next_tuple(handle, &tuple, &parse) != CS_SUCCESS) + first_tuple(handle, &tuple, &parse); + if (parse.version_1.ns > 3) { + station_addr = parse.version_1.str + parse.version_1.ofs[3]; + if (cvt_ascii_address(dev, station_addr) == 0) + return 0; + } + + /* Another possibility: for the EM3288, in a special tuple */ + tuple.DesiredTuple = 0x81; + if (pcmcia_get_first_tuple(handle, &tuple) != CS_SUCCESS) + return -1; + if (pcmcia_get_tuple_data(handle, &tuple) != CS_SUCCESS) + return -1; + buf[12] = '\0'; + if (cvt_ascii_address(dev, buf) == 0) + return 0; + + return -1; +} + +/*====================================================================== + + 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(dev_link_t *link) +{ + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + kio_addr_t iouart = link->io.BasePort2; + + /* 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(dev_link_t *link) +{ + struct net_device *dev = link->priv; + kio_addr_t 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_config(dev_link_t *link) +{ + struct net_device *dev = link->priv; + tuple_t tuple; + cisparse_t parse; + u_char buf[255]; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + int i; + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + + link->io.NumPorts1 = 16; + i = first_tuple(link->handle, &tuple, &parse); + while (i != CS_NO_MORE_ITEMS) { + if (i == CS_SUCCESS) { + link->conf.ConfigIndex = cf->index; + link->io.BasePort1 = cf->io.win[0].base; + link->io.IOAddrLines = cf->io.flags & CISTPL_IO_LINES_MASK; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + i = next_tuple(link->handle, &tuple, &parse); + } + if (i == CS_SUCCESS) + dev->base_addr = link->io.BasePort1; + return i; +} + +static int smc_setup(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + tuple_t tuple; + cisparse_t parse; + cistpl_lan_node_id_t *node_id; + u_char buf[255], *station_addr; + int i; + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + + /* Check for a LAN function extension tuple */ + tuple.DesiredTuple = CISTPL_FUNCE; + i = first_tuple(handle, &tuple, &parse); + while (i == CS_SUCCESS) { + if (parse.funce.type == CISTPL_FUNCE_LAN_NODE_ID) + break; + i = next_tuple(handle, &tuple, &parse); + } + if (i == CS_SUCCESS) { + node_id = (cistpl_lan_node_id_t *)parse.funce.data; + if (node_id->nb == 6) { + for (i = 0; i < 6; i++) + dev->dev_addr[i] = node_id->id[i]; + return 0; + } + } + /* Try the third string in the Version 1 Version/ID tuple. */ + tuple.DesiredTuple = CISTPL_VERS_1; + if (first_tuple(handle, &tuple, &parse) != CS_SUCCESS) + return -1; + station_addr = parse.version_1.str + parse.version_1.ofs[2]; + if (cvt_ascii_address(dev, station_addr) == 0) + return 0; + + return -1; +} + +/*====================================================================*/ + +static int osi_config(dev_link_t *link) +{ + struct net_device *dev = link->priv; + static kio_addr_t com[4] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 }; + int i, j; + + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED|IRQ_HANDLE_PRESENT; + link->io.NumPorts1 = 64; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts2 = 8; + link->io.IOAddrLines = 16; + + /* Enable Hard Decode, LAN, Modem */ + link->conf.ConfigIndex = 0x23; + + for (i = j = 0; j < 4; j++) { + link->io.BasePort2 = com[j]; + i = pcmcia_request_io(link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + if (i != CS_SUCCESS) { + /* Fallback: turn off hard decode */ + link->conf.ConfigIndex = 0x03; + link->io.NumPorts2 = 0; + i = pcmcia_request_io(link->handle, &link->io); + } + dev->base_addr = link->io.BasePort1 + 0x10; + return i; +} + +static int osi_setup(dev_link_t *link, u_short manfid, u_short cardid) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + tuple_t tuple; + u_char buf[255]; + int i; + + tuple.Attributes = TUPLE_RETURN_COMMON; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + + /* Read the station address from tuple 0x90, subtuple 0x04 */ + tuple.DesiredTuple = 0x90; + i = pcmcia_get_first_tuple(handle, &tuple); + while (i == CS_SUCCESS) { + i = pcmcia_get_tuple_data(handle, &tuple); + if ((i != CS_SUCCESS) || (buf[0] == 0x04)) + break; + i = pcmcia_get_next_tuple(handle, &tuple); + } + if (i != CS_SUCCESS) + return -1; + for (i = 0; i < 6; i++) + dev->dev_addr[i] = buf[i+2]; + + if (((manfid == MANFID_OSITECH) && + (cardid == PRODID_OSITECH_SEVEN)) || + ((manfid == MANFID_PSION) && + (cardid == PRODID_PSION_NET100))) { + /* Download the Seven of Diamonds firmware */ + for (i = 0; i < sizeof(__Xilinx7OD); i++) { + outb(__Xilinx7OD[i], link->io.BasePort1+2); + udelay(50); + } + } else if (manfid == MANFID_OSITECH) { + /* Make sure both functions are powered up */ + set_bits(0x300, link->io.BasePort1 + OSITECH_AUI_PWR); + /* Now, turn on the interrupt for both card functions */ + set_bits(0x300, link->io.BasePort1 + OSITECH_RESET_ISR); + DEBUG(2, "AUI/PWR: %4.4x RESET/ISR: %4.4x\n", + inw(link->io.BasePort1 + OSITECH_AUI_PWR), + inw(link->io.BasePort1 + OSITECH_RESET_ISR)); + } + + 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(dev_link_t *link) +{ + struct net_device *dev = link->priv; + kio_addr_t 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->io.Attributes1 == 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) { + event_callback_args_t args; + printk(KERN_INFO "smc91c92_cs: using 8-bit IO window.\n"); + args.client_data = link; + smc91c92_event(CS_EVENT_RESET_PHYSICAL, 0, &args); + pcmcia_release_io(link->handle, &link->io); + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + pcmcia_request_io(link->handle, &link->io); + smc91c92_event(CS_EVENT_CARD_RESET, 0, &args); + return check_sig(link); + } + return -ENODEV; +} + +/*====================================================================== + + smc91c92_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + ethernet device available to the system. + +======================================================================*/ + +#define CS_EXIT_TEST(ret, svc, label) \ +if (ret != CS_SUCCESS) { cs_error(link->handle, svc, ret); goto label; } + +static void smc91c92_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + tuple_t tuple; + cisparse_t parse; + u_short buf[32]; + char *name; + int i, j, rev; + kio_addr_t ioaddr; + u_long mir; + + DEBUG(0, "smc91c92_config(0x%p)\n", link); + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + + tuple.DesiredTuple = CISTPL_CONFIG; + i = first_tuple(handle, &tuple, &parse); + CS_EXIT_TEST(i, ParseTuple, config_failed); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + tuple.DesiredTuple = CISTPL_MANFID; + tuple.Attributes = TUPLE_RETURN_COMMON; + if (first_tuple(handle, &tuple, &parse) == CS_SUCCESS) { + smc->manfid = parse.manfid.manf; + smc->cardid = parse.manfid.card; + } + + /* Configure card */ + link->state |= DEV_CONFIG; + + 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); + } + CS_EXIT_TEST(i, RequestIO, config_failed); + + i = pcmcia_request_irq(link->handle, &link->irq); + CS_EXIT_TEST(i, RequestIRQ, config_failed); + i = pcmcia_request_configuration(link->handle, &link->conf); + CS_EXIT_TEST(i, RequestConfiguration, config_failed); + + if (smc->manfid == MANFID_MOTOROLA) + mot_config(link); + + dev->irq = link->irq.AssignedIRQ; + + if ((if_port >= 0) && (if_port <= 2)) + dev->if_port = if_port; + else + printk(KERN_NOTICE "smc91c92_cs: 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) { + printk(KERN_NOTICE "smc91c92_cs: Unable to find hardware address.\n"); + goto config_undo; + } + + 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); + } + + link->dev = &smc->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + if (register_netdev(dev) != 0) { + printk(KERN_ERR "smc91c92_cs: register_netdev() failed\n"); + link->dev = NULL; + goto config_undo; + } + + strcpy(smc->node.dev_name, dev->name); + + printk(KERN_INFO "%s: smc91c%s rev %d: io %#3lx, irq %d, " + "hw_addr ", dev->name, name, (rev & 0x0f), dev->base_addr, + dev->irq); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + + if (rev > 0) { + if (mir & 0x3ff) + printk(KERN_INFO " %lu byte", mir); + else + printk(KERN_INFO " %lu kb", mir>>10); + printk(" 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) { + DEBUG(0, " MII transceiver at index %d, status %x.\n", + smc->mii_if.phy_id, j); + } else { + printk(KERN_NOTICE " No MII transceivers found!\n"); + } + } + + return; + +config_undo: + unregister_netdev(dev); +config_failed: /* CS_EXIT_TEST() calls jump to here... */ + smc91c92_release(link); + link->state &= ~DEV_CONFIG_PENDING; + +} /* smc91c92_config */ + +/*====================================================================== + + After a card is removed, smc91c92_release() will unregister the net + device, and release the PCMCIA configuration. If the device is + still open, this will be postponed until it is closed. + +======================================================================*/ + +static void smc91c92_release(dev_link_t *link) +{ + + DEBUG(0, "smc91c92_release(0x%p)\n", link); + + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + if (link->win) { + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + iounmap(smc->base); + pcmcia_release_window(link->win); + } + + link->state &= ~DEV_CONFIG; +} + +/*====================================================================== + + The card status event handler. Mostly, this schedules other + stuff to run after an event is received. A CARD_REMOVAL event + also sets some flags to discourage the net drivers from trying + to talk to the card any more. + +======================================================================*/ + +static int smc91c92_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct net_device *dev = link->priv; + struct smc_private *smc = netdev_priv(dev); + int i; + + DEBUG(1, "smc91c92_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + smc91c92_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) + netif_device_detach(dev); + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + if ((smc->manfid == MANFID_MEGAHERTZ) && + (smc->cardid == PRODID_MEGAHERTZ_EM3288)) + mhz_3288_power(link); + pcmcia_request_configuration(link->handle, &link->conf); + 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))) { + /* Download the Seven of Diamonds firmware */ + for (i = 0; i < sizeof(__Xilinx7OD); i++) { + outb(__Xilinx7OD[i], link->io.BasePort1+2); + udelay(50); + } + } + if (link->open) { + smc_reset(dev); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* smc91c92_event */ + +/*====================================================================== + + 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(kio_addr_t 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) +{ + kio_addr_t 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) +{ + kio_addr_t 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) +{ + kio_addr_t ioaddr = dev->base_addr; + u_short i, w, save; + save = inw(ioaddr + BANK_SELECT); + for (w = 0; w < 4; w++) { + SMC_SELECT_BANK(w); + printk(KERN_DEBUG "bank %d: ", w); + for (i = 0; i < 14; i += 2) + printk(" %04x", inw(ioaddr + i)); + printk("\n"); + } + outw(save, ioaddr + BANK_SELECT); +} +#endif + +static int smc_open(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + dev_link_t *link = &smc->link; + +#ifdef PCMCIA_DEBUG + DEBUG(0, "%s: smc_open(%p), ID/Window %4.4x.\n", + dev->name, dev, inw(dev->base_addr + BANK_SELECT)); + if (pc_debug > 1) smc_dump(dev); +#endif + + /* Check that the PCMCIA card is still here. */ + if (!DEV_OK(link)) + return -ENODEV; + /* Physical device present signature. */ + if (check_sig(link) < 0) { + printk("smc91c92_cs: 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); + dev_link_t *link = &smc->link; + kio_addr_t ioaddr = dev->base_addr; + + DEBUG(0, "%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; + kio_addr_t ioaddr = dev->base_addr; + u_char packet_no; + + if (!skb) { + printk(KERN_ERR "%s: In XMIT with no packet to send.\n", dev->name); + 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. */ + printk(KERN_WARNING "%s: 91c92 hardware Tx buffer allocation" + " failed, status %#2.2x.\n", dev->name, packet_no); + dev_kfree_skb_irq(skb); + smc->saved_skb = NULL; + netif_start_queue(dev); + return; + } + + smc->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. */ + + DEBUG(2, "%s: Trying to xmit packet of length %d.\n", + dev->name, 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); + return; +} + +/*====================================================================*/ + +static void smc_tx_timeout(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + + printk(KERN_NOTICE "%s: SMC91c92 transmit timed out, " + "Tx_status %2.2x status %4.4x.\n", + dev->name, inw(ioaddr)&0xff, inw(ioaddr + 2)); + smc->stats.tx_errors++; + smc_reset(dev); + dev->trans_start = jiffies; + smc->saved_skb = NULL; + netif_wake_queue(dev); +} + +static int smc_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + u_short num_pages; + short time_out, ir; + + netif_stop_queue(dev); + + DEBUG(2, "%s: smc_start_xmit(length = %d) called," + " status %4.4x.\n", dev->name, skb->len, inw(ioaddr + 2)); + + if (smc->saved_skb) { + /* THIS SHOULD NEVER HAPPEN. */ + smc->stats.tx_aborted_errors++; + printk(KERN_DEBUG "%s: Internal error -- sent packet while busy.\n", + dev->name); + return 1; + } + smc->saved_skb = skb; + + num_pages = skb->len >> 8; + + if (num_pages > 7) { + printk(KERN_ERR "%s: Far too big packet error.\n", dev->name); + dev_kfree_skb (skb); + smc->saved_skb = NULL; + smc->stats.tx_dropped++; + return 0; /* Do not re-queue this packet. */ + } + /* A packet is now waiting. */ + smc->packets_waiting++; + + 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.. */ + return 0; + } + } + + /* Otherwise defer until the Tx-space-allocated interrupt. */ + DEBUG(2, "%s: memory allocation deferred.\n", dev->name); + outw((IM_ALLOC_INT << 8) | (ir & 0xff00), ioaddr + INTERRUPT); + + return 0; +} + +/*====================================================================== + + Handle a Tx anomolous event. Entered while in Window 2. + +======================================================================*/ + +static void smc_tx_err(struct net_device * dev) +{ + struct smc_private *smc = netdev_priv(dev); + kio_addr_t 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); + + smc->stats.tx_errors++; + if (tx_status & TS_LOSTCAR) smc->stats.tx_carrier_errors++; + if (tx_status & TS_LATCOL) smc->stats.tx_window_errors++; + if (tx_status & TS_16COL) { + smc->stats.tx_aborted_errors++; + smc->tx_err++; + } + + if (tx_status & TS_SUCCESS) { + printk(KERN_NOTICE "%s: Successful packet caused error " + "interrupt?\n", dev->name); + } + /* 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); + return; +} + +/*====================================================================*/ + +static void smc_eph_irq(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + u_short card_stats, ephs; + + SMC_SELECT_BANK(0); + ephs = inw(ioaddr + EPH); + DEBUG(2, "%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 */ + smc->stats.collisions += card_stats & 0xF; + card_stats >>= 4; + /* multiple collisions */ + smc->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 pt_regs *regs) +{ + struct net_device *dev = dev_id; + struct smc_private *smc = netdev_priv(dev); + kio_addr_t 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; + + DEBUG(3, "%s: SMC91c92 interrupt %d at %#x.\n", dev->name, + irq, ioaddr); + + 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. */ + DEBUG(1, "%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; + DEBUG(3, "%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; + smc->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) { + smc->stats.rx_errors++; + smc->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); + + DEBUG(3, " 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); + + DEBUG(3, "%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); + } +#ifdef DOES_NOT_WORK + if (smc->base != NULL) { /* Megahertz MFC's */ + readb(smc->base+MEGAHERTZ_ISR); + readb(smc->base+MEGAHERTZ_ISR); + } +#endif + return IRQ_RETVAL(handled); +} + +/*====================================================================*/ + +static void smc_rx(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + kio_addr_t 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) { + printk(KERN_ERR "%s: smc_rx() with nothing on Rx FIFO.\n", + dev->name); + 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; + + DEBUG(2, "%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) { + DEBUG(1, "%s: Low memory, packet dropped.\n", dev->name); + smc->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); + + skb->dev = dev; + netif_rx(skb); + dev->last_rx = jiffies; + smc->stats.rx_packets++; + smc->stats.rx_bytes += packet_length; + if (rx_status & RS_MULTICAST) + smc->stats.multicast++; + } else { + /* error ... */ + smc->stats.rx_errors++; + + if (rx_status & RS_ALGNERR) smc->stats.rx_frame_errors++; + if (rx_status & (RS_TOOSHORT | RS_TOOLONG)) + smc->stats.rx_length_errors++; + if (rx_status & RS_BADCRC) smc->stats.rx_crc_errors++; + } + /* Let the MMU free the memory of this packet. */ + outw(MC_RELEASE, ioaddr + MMU_CMD); + + return; +} + +/*====================================================================*/ + +static struct net_device_stats *smc_get_stats(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + /* Nothing to update - the 91c92 is a pretty primative chip. */ + return &smc->stats; +} + +/*====================================================================== + + Calculate values for the hardware multicast filter hash table. + +======================================================================*/ + +static void fill_multicast_tbl(int count, struct dev_mc_list *addrs, + u_char *multicast_table) +{ + struct dev_mc_list *mc_addr; + + for (mc_addr = addrs; mc_addr && --count > 0; mc_addr = mc_addr->next) { + u_int position = ether_crc(6, mc_addr->dmi_addr); +#ifndef final_version /* Verify multicast address. */ + if ((mc_addr->dmi_addr[0] & 1) == 0) + continue; +#endif + multicast_table[position >> 29] |= 1 << ((position >> 26) & 7); + } +} + +/*====================================================================== + + 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) +{ + kio_addr_t ioaddr = dev->base_addr; + struct smc_private *smc = netdev_priv(dev); + u_int multicast_table[ 2 ] = { 0, }; + unsigned long flags; + u_short rx_cfg_setting; + + if (dev->flags & IFF_PROMISC) { + printk(KERN_NOTICE "%s: setting Rx mode to promiscuous.\n", dev->name); + rx_cfg_setting = RxStripCRC | RxEnable | RxPromisc | RxAllMulti; + } else if (dev->flags & IFF_ALLMULTI) + rx_cfg_setting = RxStripCRC | RxEnable | RxAllMulti; + else { + if (dev->mc_count) { + fill_multicast_tbl(dev->mc_count, dev->mc_list, + (u_char *)multicast_table); + } + 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); + outl(multicast_table[0], ioaddr + MULTICAST0); + outl(multicast_table[1], ioaddr + MULTICAST4); + SMC_SELECT_BANK(0); + outw(rx_cfg_setting, ioaddr + RCR); + SMC_SELECT_BANK(2); + spin_unlock_irqrestore(&smc->lock, flags); + + return; +} + +/*====================================================================== + + 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; + printk(KERN_INFO "%s: switched to %s port\n", + dev->name, 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); + kio_addr_t 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) +{ + kio_addr_t ioaddr = dev->base_addr; + struct smc_private *smc = netdev_priv(dev); + int i; + + DEBUG(0, "%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 succesfully 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); + kio_addr_t ioaddr = dev->base_addr; + u_short i, media, saved_bank; + u_short link; + + 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; + + /* 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) + printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); + smc_interrupt(dev->irq, smc, NULL); + smc->fast_poll = HZ; + } + if (smc->fast_poll) { + smc->fast_poll--; + smc->media.expires = jiffies + HZ/100; + add_timer(&smc->media); + SMC_SELECT_BANK(saved_bank); + return; + } + + 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)) { + printk(KERN_INFO "%s: MII is missing!\n", dev->name); + 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); + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (link) ? "found" : "lost"); + smc->duplex = (((p & 0x0100) || ((p & 0x1c0) == 0x40)) + ? TCR_FDUPLX : 0); + if (link) { + printk(KERN_INFO "%s: autonegotiation complete: " + "%sbaseT-%cD selected\n", dev->name, + ((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 (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)) + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (smc->media_status & EPH_LINK_OK ? "lost" : "found")); + else if ((media & smc->media_status & 2) && + ((smc->media_status ^ media) & EPH_16COL)) + printk(KERN_INFO "%s: coax cable %s\n", dev->name, + (media & EPH_16COL ? "problem" : "ok")); + if (dev->if_port == 0) { + if (media & 1) { + if (media & EPH_LINK_OK) + printk(KERN_INFO "%s: flipped to 10baseT\n", + dev->name); + else + smc_set_xcvr(dev, 2); + } else { + if (media & EPH_16COL) + smc_set_xcvr(dev, 1); + else + printk(KERN_INFO "%s: flipped to 10base2\n", + dev->name); + } + } + smc->media_status = media; + } + +reschedule: + smc->media.expires = jiffies + HZ; + add_timer(&smc->media); + SMC_SELECT_BANK(saved_bank); +} + +static int smc_link_ok(struct net_device *dev) +{ + kio_addr_t 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; + kio_addr_t 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; + ecmd->speed = 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; + kio_addr_t ioaddr = dev->base_addr; + + if (ecmd->speed != 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); + kio_addr_t ioaddr = dev->base_addr; + u16 saved_bank = inw(ioaddr + BANK_SELECT); + int ret; + + SMC_SELECT_BANK(3); + spin_lock_irq(&smc->lock); + if (smc->cfg & CFG_MII_SELECT) + ret = mii_ethtool_gset(&smc->mii_if, ecmd); + else + ret = smc_netdev_get_ecmd(dev, ecmd); + spin_unlock_irq(&smc->lock); + SMC_SELECT_BANK(saved_bank); + return ret; +} + +static int smc_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd) +{ + struct smc_private *smc = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + u16 saved_bank = inw(ioaddr + BANK_SELECT); + int ret; + + SMC_SELECT_BANK(3); + spin_lock_irq(&smc->lock); + if (smc->cfg & CFG_MII_SELECT) + ret = mii_ethtool_sset(&smc->mii_if, ecmd); + else + ret = smc_netdev_set_ecmd(dev, ecmd); + spin_unlock_irq(&smc->lock); + SMC_SELECT_BANK(saved_bank); + return ret; +} + +static u32 smc_get_link(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + u16 saved_bank = inw(ioaddr + BANK_SELECT); + u32 ret; + + SMC_SELECT_BANK(3); + spin_lock_irq(&smc->lock); + ret = smc_link_ok(dev); + spin_unlock_irq(&smc->lock); + SMC_SELECT_BANK(saved_bank); + return ret; +} + +#ifdef PCMCIA_DEBUG +static u32 smc_get_msglevel(struct net_device *dev) +{ + return pc_debug; +} + +static void smc_set_msglevel(struct net_device *dev, u32 val) +{ + pc_debug = val; +} +#endif + +static int smc_nway_reset(struct net_device *dev) +{ + struct smc_private *smc = netdev_priv(dev); + if (smc->cfg & CFG_MII_SELECT) { + kio_addr_t 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 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, +#ifdef PCMCIA_DEBUG + .get_msglevel = smc_get_msglevel, + .set_msglevel = smc_set_msglevel, +#endif + .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; + kio_addr_t ioaddr = dev->base_addr; + + if (!netif_running(dev)) + return -EINVAL; + + spin_lock_irq(&smc->lock); + 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_irq(&smc->lock); + return rc; +} + +static struct pcmcia_driver smc91c92_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "smc91c92_cs", + }, + .attach = smc91c92_attach, + .detach = smc91c92_detach, +}; + +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); + BUG_ON(dev_list != NULL); +} + +module_init(init_smc91c92_cs); +module_exit(exit_smc91c92_cs); diff --git a/drivers/net/pcmcia/xirc2ps_cs.c b/drivers/net/pcmcia/xirc2ps_cs.c new file mode 100644 index 00000000000..58177d67ea1 --- /dev/null +++ b/drivers/net/pcmcia/xirc2ps_cs.c @@ -0,0 +1,2031 @@ +/* [xirc2ps_cs.c wk 03.11.99] (1.40 1999/11/18 00:06:03) + * Xircom CreditCard Ethernet Adapter IIps driver + * Xircom Realport 10/100 (RE-100) driver + * + * This driver supports various Xircom CreditCard Ethernet adapters + * including the CE2, CE IIps, RE-10, CEM28, CEM33, CE33, CEM56, + * CE3-100, CE3B, RE-100, REM10BT, and REM56G-100. + * + * 2000-09-24 <psheer@icon.co.za> The Xircom CE3B-100 may not + * autodetect the media properly. In this case use the + * if_port=1 (for 10BaseT) or if_port=4 (for 100BaseT) options + * to force the media type. + * + * Written originally by Werner Koch based on David Hinds' skeleton of the + * PCMCIA driver. + * + * Copyright (c) 1997,1998 Werner Koch (dd9jn) + * + * This driver 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. + * + * It 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 + * + * + * ALTERNATIVELY, this driver may be distributed under the terms of + * the following license, in which case the provisions of this license + * are required INSTEAD OF the GNU General Public License. (This clause + * is necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <linux/ethtool.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> +#include <linux/bitops.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> + +#include <asm/io.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +#ifndef MANFID_COMPAQ + #define MANFID_COMPAQ 0x0138 + #define MANFID_COMPAQ2 0x0183 /* is this correct? */ +#endif + +#include <pcmcia/ds.h> + +/* Time in jiffies before concluding Tx hung */ +#define TX_TIMEOUT ((400*HZ)/1000) + +/**************** + * Some constants used to access the hardware + */ + +/* Register offsets and value constans */ +#define XIRCREG_CR 0 /* Command register (wr) */ +enum xirc_cr { + TransmitPacket = 0x01, + SoftReset = 0x02, + EnableIntr = 0x04, + ForceIntr = 0x08, + ClearTxFIFO = 0x10, + ClearRxOvrun = 0x20, + RestartTx = 0x40 +}; +#define XIRCREG_ESR 0 /* Ethernet status register (rd) */ +enum xirc_esr { + FullPktRcvd = 0x01, /* full packet in receive buffer */ + PktRejected = 0x04, /* a packet has been rejected */ + TxPktPend = 0x08, /* TX Packet Pending */ + IncorPolarity = 0x10, + MediaSelect = 0x20 /* set if TP, clear if AUI */ +}; +#define XIRCREG_PR 1 /* Page Register select */ +#define XIRCREG_EDP 4 /* Ethernet Data Port Register */ +#define XIRCREG_ISR 6 /* Ethernet Interrupt Status Register */ +enum xirc_isr { + TxBufOvr = 0x01, /* TX Buffer Overflow */ + PktTxed = 0x02, /* Packet Transmitted */ + MACIntr = 0x04, /* MAC Interrupt occurred */ + TxResGrant = 0x08, /* Tx Reservation Granted */ + RxFullPkt = 0x20, /* Rx Full Packet */ + RxPktRej = 0x40, /* Rx Packet Rejected */ + ForcedIntr= 0x80 /* Forced Interrupt */ +}; +#define XIRCREG1_IMR0 12 /* Ethernet Interrupt Mask Register (on page 1)*/ +#define XIRCREG1_IMR1 13 +#define XIRCREG0_TSO 8 /* Transmit Space Open Register (on page 0)*/ +#define XIRCREG0_TRS 10 /* Transmit reservation Size Register (page 0)*/ +#define XIRCREG0_DO 12 /* Data Offset Register (page 0) (wr) */ +#define XIRCREG0_RSR 12 /* Receive Status Register (page 0) (rd) */ +enum xirc_rsr { + PhyPkt = 0x01, /* set:physical packet, clear: multicast packet */ + BrdcstPkt = 0x02, /* set if it is a broadcast packet */ + PktTooLong = 0x04, /* set if packet length > 1518 */ + AlignErr = 0x10, /* incorrect CRC and last octet not complete */ + CRCErr = 0x20, /* incorrect CRC and last octet is complete */ + PktRxOk = 0x80 /* received ok */ +}; +#define XIRCREG0_PTR 13 /* packets transmitted register (rd) */ +#define XIRCREG0_RBC 14 /* receive byte count regsister (rd) */ +#define XIRCREG1_ECR 14 /* ethernet configurationn register */ +enum xirc_ecr { + FullDuplex = 0x04, /* enable full duplex mode */ + LongTPMode = 0x08, /* adjust for longer lengths of TP cable */ + DisablePolCor = 0x10,/* disable auto polarity correction */ + DisableLinkPulse = 0x20, /* disable link pulse generation */ + DisableAutoTx = 0x40, /* disable auto-transmit */ +}; +#define XIRCREG2_RBS 8 /* receive buffer start register */ +#define XIRCREG2_LED 10 /* LED Configuration register */ +/* values for the leds: Bits 2-0 for led 1 + * 0 disabled Bits 5-3 for led 2 + * 1 collision + * 2 noncollision + * 3 link_detected + * 4 incor_polarity + * 5 jabber + * 6 auto_assertion + * 7 rx_tx_activity + */ +#define XIRCREG2_MSR 12 /* Mohawk specific register */ + +#define XIRCREG4_GPR0 8 /* General Purpose Register 0 */ +#define XIRCREG4_GPR1 9 /* General Purpose Register 1 */ +#define XIRCREG2_GPR2 13 /* General Purpose Register 2 (page2!)*/ +#define XIRCREG4_BOV 10 /* Bonding Version Register */ +#define XIRCREG4_LMA 12 /* Local Memory Address Register */ +#define XIRCREG4_LMD 14 /* Local Memory Data Port */ +/* MAC register can only by accessed with 8 bit operations */ +#define XIRCREG40_CMD0 8 /* Command Register (wr) */ +enum xirc_cmd { /* Commands */ + Transmit = 0x01, + EnableRecv = 0x04, + DisableRecv = 0x08, + Abort = 0x10, + Online = 0x20, + IntrAck = 0x40, + Offline = 0x80 +}; +#define XIRCREG5_RHSA0 10 /* Rx Host Start Address */ +#define XIRCREG40_RXST0 9 /* Receive Status Register */ +#define XIRCREG40_TXST0 11 /* Transmit Status Register 0 */ +#define XIRCREG40_TXST1 12 /* Transmit Status Register 10 */ +#define XIRCREG40_RMASK0 13 /* Receive Mask Register */ +#define XIRCREG40_TMASK0 14 /* Transmit Mask Register 0 */ +#define XIRCREG40_TMASK1 15 /* Transmit Mask Register 0 */ +#define XIRCREG42_SWC0 8 /* Software Configuration 0 */ +#define XIRCREG42_SWC1 9 /* Software Configuration 1 */ +#define XIRCREG42_BOC 10 /* Back-Off Configuration */ +#define XIRCREG44_TDR0 8 /* Time Domain Reflectometry 0 */ +#define XIRCREG44_TDR1 9 /* Time Domain Reflectometry 1 */ +#define XIRCREG44_RXBC_LO 10 /* Rx Byte Count 0 (rd) */ +#define XIRCREG44_RXBC_HI 11 /* Rx Byte Count 1 (rd) */ +#define XIRCREG45_REV 15 /* Revision Register (rd) */ +#define XIRCREG50_IA 8 /* Individual Address (8-13) */ + +static char *if_names[] = { "Auto", "10BaseT", "10Base2", "AUI", "100BaseT" }; + +/**************** + * All the PCMCIA modules use PCMCIA_DEBUG to control debugging. If + * you do not define PCMCIA_DEBUG at all, all the debug code will be + * left out. If you compile with PCMCIA_DEBUG=0, the debug code will + * be present but disabled -- but it can then be enabled for specific + * modules at load time with a 'pc_debug=#' option to insmod. + */ +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +module_param(pc_debug, int, 0); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KDBG_XIRC args) +#else +#define DEBUG(n, args...) +#endif + +#define KDBG_XIRC KERN_DEBUG "xirc2ps_cs: " +#define KERR_XIRC KERN_ERR "xirc2ps_cs: " +#define KWRN_XIRC KERN_WARNING "xirc2ps_cs: " +#define KNOT_XIRC KERN_NOTICE "xirc2ps_cs: " +#define KINF_XIRC KERN_INFO "xirc2ps_cs: " + +/* card types */ +#define XIR_UNKNOWN 0 /* unknown: not supported */ +#define XIR_CE 1 /* (prodid 1) different hardware: not supported */ +#define XIR_CE2 2 /* (prodid 2) */ +#define XIR_CE3 3 /* (prodid 3) */ +#define XIR_CEM 4 /* (prodid 1) different hardware: not supported */ +#define XIR_CEM2 5 /* (prodid 2) */ +#define XIR_CEM3 6 /* (prodid 3) */ +#define XIR_CEM33 7 /* (prodid 4) */ +#define XIR_CEM56M 8 /* (prodid 5) */ +#define XIR_CEM56 9 /* (prodid 6) */ +#define XIR_CM28 10 /* (prodid 3) modem only: not supported here */ +#define XIR_CM33 11 /* (prodid 4) modem only: not supported here */ +#define XIR_CM56 12 /* (prodid 5) modem only: not supported here */ +#define XIR_CG 13 /* (prodid 1) GSM modem only: not supported */ +#define XIR_CBE 14 /* (prodid 1) cardbus ethernet: not supported */ +/*====================================================================*/ + +/* Module parameters */ + +MODULE_DESCRIPTION("Xircom PCMCIA ethernet driver"); +MODULE_LICENSE("Dual MPL/GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0) + +INT_MODULE_PARM(if_port, 0); +INT_MODULE_PARM(full_duplex, 0); +INT_MODULE_PARM(do_sound, 1); +INT_MODULE_PARM(lockup_hack, 0); /* anti lockup hack */ + +/*====================================================================*/ + +/* We do not process more than these number of bytes during one + * interrupt. (Of course we receive complete packets, so this is not + * an exact value). + * Something between 2000..22000; first value gives best interrupt latency, + * the second enables the usage of the complete on-chip buffer. We use the + * high value as the initial value. + */ +static unsigned maxrx_bytes = 22000; + +/* MII management prototypes */ +static void mii_idle(kio_addr_t ioaddr); +static void mii_putbit(kio_addr_t ioaddr, unsigned data); +static int mii_getbit(kio_addr_t ioaddr); +static void mii_wbits(kio_addr_t ioaddr, unsigned data, int len); +static unsigned mii_rd(kio_addr_t ioaddr, u_char phyaddr, u_char phyreg); +static void mii_wr(kio_addr_t ioaddr, u_char phyaddr, u_char phyreg, + unsigned data, int len); + +/* + * The event() function is this driver's Card Services event handler. + * It will be called by Card Services when an appropriate card status + * event is received. The config() and release() entry points are + * used to configure or release a socket, in response to card insertion + * and ejection events. They are invoked from the event handler. + */ + +static int has_ce2_string(dev_link_t * link); +static void xirc2ps_config(dev_link_t * link); +static void xirc2ps_release(dev_link_t * link); +static int xirc2ps_event(event_t event, int priority, + event_callback_args_t * args); + +/**************** + * The attach() and detach() entry points are used to create and destroy + * "instances" of the driver, where each instance represents everything + * needed to manage one actual PCMCIA card. + */ + +static dev_link_t *xirc2ps_attach(void); +static void xirc2ps_detach(dev_link_t *); + +/**************** + * You'll also need to prototype all the functions that will actually + * be used to talk to your device. See 'pcmem_cs' for a good example + * of a fully self-sufficient driver; the other drivers rely more or + * less on other parts of the kernel. + */ + +static irqreturn_t xirc2ps_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +/* + * The dev_info variable is the "key" that is used to match up this + * device driver with appropriate cards, through the card configuration + * database. + */ + +static dev_info_t dev_info = "xirc2ps_cs"; + +/**************** + * A linked list of "instances" of the device. Each actual + * PCMCIA card corresponds to one device instance, and is described + * by one dev_link_t structure (defined in ds.h). + * + * You may not want to use a linked list for this -- for example, the + * memory card driver uses an array of dev_link_t pointers, where minor + * device numbers are used to derive the corresponding array index. + */ + +static dev_link_t *dev_list; + +/**************** + * A dev_link_t structure has fields for most things that are needed + * to keep track of a socket, but there will usually be some device + * specific information that also needs to be kept track of. The + * 'priv' pointer in a dev_link_t structure can be used to point to + * a device-specific private data structure, like this. + * + * A driver needs to provide a dev_node_t structure for each device + * on a card. In some cases, there is only one device per card (for + * example, ethernet cards, modems). In other cases, there may be + * many actual or logical devices (SCSI adapters, memory cards with + * multiple partitions). The dev_node_t structures need to be kept + * in a linked list starting at the 'dev' field of a dev_link_t + * structure. We allocate them in the card's private data structure, + * because they generally can't be allocated dynamically. + */ + +typedef struct local_info_t { + dev_link_t link; + dev_node_t node; + struct net_device_stats stats; + int card_type; + int probe_port; + int silicon; /* silicon revision. 0=old CE2, 1=Scipper, 4=Mohawk */ + int mohawk; /* a CE3 type card */ + int dingo; /* a CEM56 type card */ + int new_mii; /* has full 10baseT/100baseT MII */ + int modem; /* is a multi function card (i.e with a modem) */ + void __iomem *dingo_ccr; /* only used for CEM56 cards */ + unsigned last_ptr_value; /* last packets transmitted value */ + const char *manf_str; +} local_info_t; + +/**************** + * Some more prototypes + */ +static int do_start_xmit(struct sk_buff *skb, struct net_device *dev); +static void do_tx_timeout(struct net_device *dev); +static struct net_device_stats *do_get_stats(struct net_device *dev); +static void set_addresses(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); +static int set_card_type(dev_link_t *link, const void *s); +static int do_config(struct net_device *dev, struct ifmap *map); +static int do_open(struct net_device *dev); +static int do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static struct ethtool_ops netdev_ethtool_ops; +static void hardreset(struct net_device *dev); +static void do_reset(struct net_device *dev, int full); +static int init_mii(struct net_device *dev); +static void do_powerdown(struct net_device *dev); +static int do_stop(struct net_device *dev); + +/*=============== Helper functions =========================*/ +static int +first_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int err; + + if ((err = pcmcia_get_first_tuple(handle, tuple)) == 0 && + (err = pcmcia_get_tuple_data(handle, tuple)) == 0) + err = pcmcia_parse_tuple(handle, tuple, parse); + return err; +} + +static int +next_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int err; + + if ((err = pcmcia_get_next_tuple(handle, tuple)) == 0 && + (err = pcmcia_get_tuple_data(handle, tuple)) == 0) + err = pcmcia_parse_tuple(handle, tuple, parse); + return err; +} + +#define SelectPage(pgnr) outb((pgnr), ioaddr + XIRCREG_PR) +#define GetByte(reg) ((unsigned)inb(ioaddr + (reg))) +#define GetWord(reg) ((unsigned)inw(ioaddr + (reg))) +#define PutByte(reg,value) outb((value), ioaddr+(reg)) +#define PutWord(reg,value) outw((value), ioaddr+(reg)) + +/*====== Functions used for debugging =================================*/ +#if defined(PCMCIA_DEBUG) && 0 /* reading regs may change system status */ +static void +PrintRegisters(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + + if (pc_debug > 1) { + int i, page; + + printk(KDBG_XIRC "Register common: "); + for (i = 0; i < 8; i++) + printk(" %2.2x", GetByte(i)); + printk("\n"); + for (page = 0; page <= 8; page++) { + printk(KDBG_XIRC "Register page %2x: ", page); + SelectPage(page); + for (i = 8; i < 16; i++) + printk(" %2.2x", GetByte(i)); + printk("\n"); + } + for (page=0x40 ; page <= 0x5f; page++) { + if (page == 0x43 || (page >= 0x46 && page <= 0x4f) + || (page >= 0x51 && page <=0x5e)) + continue; + printk(KDBG_XIRC "Register page %2x: ", page); + SelectPage(page); + for (i = 8; i < 16; i++) + printk(" %2.2x", GetByte(i)); + printk("\n"); + } + } +} +#endif /* PCMCIA_DEBUG */ + +/*============== MII Management functions ===============*/ + +/**************** + * Turn around for read + */ +static void +mii_idle(kio_addr_t ioaddr) +{ + PutByte(XIRCREG2_GPR2, 0x04|0); /* drive MDCK low */ + udelay(1); + PutByte(XIRCREG2_GPR2, 0x04|1); /* and drive MDCK high */ + udelay(1); +} + +/**************** + * Write a bit to MDI/O + */ +static void +mii_putbit(kio_addr_t ioaddr, unsigned data) +{ + #if 1 + if (data) { + PutByte(XIRCREG2_GPR2, 0x0c|2|0); /* set MDIO */ + udelay(1); + PutByte(XIRCREG2_GPR2, 0x0c|2|1); /* and drive MDCK high */ + udelay(1); + } else { + PutByte(XIRCREG2_GPR2, 0x0c|0|0); /* clear MDIO */ + udelay(1); + PutByte(XIRCREG2_GPR2, 0x0c|0|1); /* and drive MDCK high */ + udelay(1); + } + #else + if (data) { + PutWord(XIRCREG2_GPR2-1, 0x0e0e); + udelay(1); + PutWord(XIRCREG2_GPR2-1, 0x0f0f); + udelay(1); + } else { + PutWord(XIRCREG2_GPR2-1, 0x0c0c); + udelay(1); + PutWord(XIRCREG2_GPR2-1, 0x0d0d); + udelay(1); + } + #endif +} + +/**************** + * Get a bit from MDI/O + */ +static int +mii_getbit(kio_addr_t ioaddr) +{ + unsigned d; + + PutByte(XIRCREG2_GPR2, 4|0); /* drive MDCK low */ + udelay(1); + d = GetByte(XIRCREG2_GPR2); /* read MDIO */ + PutByte(XIRCREG2_GPR2, 4|1); /* drive MDCK high again */ + udelay(1); + return d & 0x20; /* read MDIO */ +} + +static void +mii_wbits(kio_addr_t ioaddr, unsigned data, int len) +{ + unsigned m = 1 << (len-1); + for (; m; m >>= 1) + mii_putbit(ioaddr, data & m); +} + +static unsigned +mii_rd(kio_addr_t ioaddr, u_char phyaddr, u_char phyreg) +{ + int i; + unsigned data=0, m; + + SelectPage(2); + for (i=0; i < 32; i++) /* 32 bit preamble */ + mii_putbit(ioaddr, 1); + mii_wbits(ioaddr, 0x06, 4); /* Start and opcode for read */ + mii_wbits(ioaddr, phyaddr, 5); /* PHY address to be accessed */ + mii_wbits(ioaddr, phyreg, 5); /* PHY register to read */ + mii_idle(ioaddr); /* turn around */ + mii_getbit(ioaddr); + + for (m = 1<<15; m; m >>= 1) + if (mii_getbit(ioaddr)) + data |= m; + mii_idle(ioaddr); + return data; +} + +static void +mii_wr(kio_addr_t ioaddr, u_char phyaddr, u_char phyreg, unsigned data, int len) +{ + int i; + + SelectPage(2); + for (i=0; i < 32; i++) /* 32 bit preamble */ + mii_putbit(ioaddr, 1); + mii_wbits(ioaddr, 0x05, 4); /* Start and opcode for write */ + mii_wbits(ioaddr, phyaddr, 5); /* PHY address to be accessed */ + mii_wbits(ioaddr, phyreg, 5); /* PHY Register to write */ + mii_putbit(ioaddr, 1); /* turn around */ + mii_putbit(ioaddr, 0); + mii_wbits(ioaddr, data, len); /* And write the data */ + mii_idle(ioaddr); +} + +/*============= Main bulk of functions =========================*/ + +/**************** + * xirc2ps_attach() creates an "instance" of the driver, allocating + * local data structures for one device. The device is registered + * with Card Services. + * + * The dev_link structure is initialized, but we don't actually + * configure the card at this point -- we wait until we receive a + * card insertion event. + */ + +static dev_link_t * +xirc2ps_attach(void) +{ + client_reg_t client_reg; + dev_link_t *link; + struct net_device *dev; + local_info_t *local; + int err; + + DEBUG(0, "attach()\n"); + + /* Allocate the device structure */ + dev = alloc_etherdev(sizeof(local_info_t)); + if (!dev) + return NULL; + local = netdev_priv(dev); + link = &local->link; + link->priv = dev; + + /* General socket configuration */ + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + link->conf.ConfigIndex = 1; + link->conf.Present = PRESENT_OPTION; + link->irq.Handler = xirc2ps_interrupt; + link->irq.Instance = dev; + + /* Fill in card specific entries */ + SET_MODULE_OWNER(dev); + dev->hard_start_xmit = &do_start_xmit; + dev->set_config = &do_config; + dev->get_stats = &do_get_stats; + dev->do_ioctl = &do_ioctl; + SET_ETHTOOL_OPS(dev, &netdev_ethtool_ops); + dev->set_multicast_list = &set_multicast_list; + dev->open = &do_open; + dev->stop = &do_stop; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = do_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* Register with Card Services */ + link->next = dev_list; + dev_list = link; + client_reg.dev_info = &dev_info; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &xirc2ps_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + if ((err = pcmcia_register_client(&link->handle, &client_reg))) { + cs_error(link->handle, RegisterClient, err); + xirc2ps_detach(link); + return NULL; + } + + return link; +} /* xirc2ps_attach */ + +/**************** + * This deletes a driver "instance". The device is de-registered + * with Card Services. If it has been released, all local data + * structures are freed. Otherwise, the structures will be freed + * when the device is released. + */ + +static void +xirc2ps_detach(dev_link_t * link) +{ + struct net_device *dev = link->priv; + dev_link_t **linkp; + + DEBUG(0, "detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) + break; + if (!*linkp) { + DEBUG(0, "detach(0x%p): dev_link lost\n", link); + return; + } + + if (link->dev) + unregister_netdev(dev); + + /* + * If the device is currently configured and active, we won't + * actually delete it yet. Instead, it is marked so that when + * the release() function is called, that will trigger a proper + * detach(). + */ + if (link->state & DEV_CONFIG) + xirc2ps_release(link); + + /* Break the link with Card Services */ + if (link->handle) + pcmcia_deregister_client(link->handle); + + /* Unlink device structure, free it */ + *linkp = link->next; + free_netdev(dev); +} /* xirc2ps_detach */ + +/**************** + * Detect the type of the card. s is the buffer with the data of tuple 0x20 + * Returns: 0 := not supported + * mediaid=11 and prodid=47 + * Media-Id bits: + * Ethernet 0x01 + * Tokenring 0x02 + * Arcnet 0x04 + * Wireless 0x08 + * Modem 0x10 + * GSM only 0x20 + * Prod-Id bits: + * Pocket 0x10 + * External 0x20 + * Creditcard 0x40 + * Cardbus 0x80 + * + */ +static int +set_card_type(dev_link_t *link, const void *s) +{ + struct net_device *dev = link->priv; + local_info_t *local = netdev_priv(dev); + #ifdef PCMCIA_DEBUG + unsigned cisrev = ((const unsigned char *)s)[2]; + #endif + unsigned mediaid= ((const unsigned char *)s)[3]; + unsigned prodid = ((const unsigned char *)s)[4]; + + DEBUG(0, "cisrev=%02x mediaid=%02x prodid=%02x\n", + cisrev, mediaid, prodid); + + local->mohawk = 0; + local->dingo = 0; + local->modem = 0; + local->card_type = XIR_UNKNOWN; + if (!(prodid & 0x40)) { + printk(KNOT_XIRC "Ooops: Not a creditcard\n"); + return 0; + } + if (!(mediaid & 0x01)) { + printk(KNOT_XIRC "Not an Ethernet card\n"); + return 0; + } + if (mediaid & 0x10) { + local->modem = 1; + switch(prodid & 15) { + case 1: local->card_type = XIR_CEM ; break; + case 2: local->card_type = XIR_CEM2 ; break; + case 3: local->card_type = XIR_CEM3 ; break; + case 4: local->card_type = XIR_CEM33 ; break; + case 5: local->card_type = XIR_CEM56M; + local->mohawk = 1; + break; + case 6: + case 7: /* 7 is the RealPort 10/56 */ + local->card_type = XIR_CEM56 ; + local->mohawk = 1; + local->dingo = 1; + break; + } + } else { + switch(prodid & 15) { + case 1: local->card_type = has_ce2_string(link)? XIR_CE2 : XIR_CE ; + break; + case 2: local->card_type = XIR_CE2; break; + case 3: local->card_type = XIR_CE3; + local->mohawk = 1; + break; + } + } + if (local->card_type == XIR_CE || local->card_type == XIR_CEM) { + printk(KNOT_XIRC "Sorry, this is an old CE card\n"); + return 0; + } + if (local->card_type == XIR_UNKNOWN) + printk(KNOT_XIRC "unknown card (mediaid=%02x prodid=%02x)\n", + mediaid, prodid); + + return 1; +} + +/**************** + * There are some CE2 cards out which claim to be a CE card. + * This function looks for a "CE2" in the 3rd version field. + * Returns: true if this is a CE2 + */ +static int +has_ce2_string(dev_link_t * link) +{ + client_handle_t handle = link->handle; + tuple_t tuple; + cisparse_t parse; + u_char buf[256]; + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 254; + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_VERS_1; + if (!first_tuple(handle, &tuple, &parse) && parse.version_1.ns > 2) { + if (strstr(parse.version_1.str + parse.version_1.ofs[2], "CE2")) + return 1; + } + return 0; +} + +/**************** + * xirc2ps_config() is scheduled to run after a CARD_INSERTION event + * is received, to configure the PCMCIA socket, and to make the + * ethernet device available to the system. + */ +static void +xirc2ps_config(dev_link_t * link) +{ + client_handle_t handle = link->handle; + struct net_device *dev = link->priv; + local_info_t *local = netdev_priv(dev); + tuple_t tuple; + cisparse_t parse; + kio_addr_t ioaddr; + int err, i; + u_char buf[64]; + cistpl_lan_node_id_t *node_id = (cistpl_lan_node_id_t*)parse.funce.data; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + + local->dingo_ccr = NULL; + + DEBUG(0, "config(0x%p)\n", link); + + /* + * This reads the card's CONFIG tuple to find its configuration + * registers. + */ + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + + /* Is this a valid card */ + tuple.DesiredTuple = CISTPL_MANFID; + if ((err=first_tuple(handle, &tuple, &parse))) { + printk(KNOT_XIRC "manfid not found in CIS\n"); + goto failure; + } + + switch(parse.manfid.manf) { + case MANFID_XIRCOM: + local->manf_str = "Xircom"; + break; + case MANFID_ACCTON: + local->manf_str = "Accton"; + break; + case MANFID_COMPAQ: + case MANFID_COMPAQ2: + local->manf_str = "Compaq"; + break; + case MANFID_INTEL: + local->manf_str = "Intel"; + break; + case MANFID_TOSHIBA: + local->manf_str = "Toshiba"; + break; + default: + printk(KNOT_XIRC "Unknown Card Manufacturer ID: 0x%04x\n", + (unsigned)parse.manfid.manf); + goto failure; + } + DEBUG(0, "found %s card\n", local->manf_str); + + if (!set_card_type(link, buf)) { + printk(KNOT_XIRC "this card is not supported\n"); + goto failure; + } + + /* get configuration stuff */ + tuple.DesiredTuple = CISTPL_CONFIG; + if ((err=first_tuple(handle, &tuple, &parse))) + goto cis_error; + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* get the ethernet address from the CIS */ + tuple.DesiredTuple = CISTPL_FUNCE; + for (err = first_tuple(handle, &tuple, &parse); !err; + err = next_tuple(handle, &tuple, &parse)) { + /* Once I saw two CISTPL_FUNCE_LAN_NODE_ID entries: + * the first one with a length of zero the second correct - + * so I skip all entries with length 0 */ + if (parse.funce.type == CISTPL_FUNCE_LAN_NODE_ID + && ((cistpl_lan_node_id_t *)parse.funce.data)->nb) + break; + } + if (err) { /* not found: try to get the node-id from tuple 0x89 */ + tuple.DesiredTuple = 0x89; /* data layout looks like tuple 0x22 */ + if ((err = pcmcia_get_first_tuple(handle, &tuple)) == 0 && + (err = pcmcia_get_tuple_data(handle, &tuple)) == 0) { + if (tuple.TupleDataLen == 8 && *buf == CISTPL_FUNCE_LAN_NODE_ID) + memcpy(&parse, buf, 8); + else + err = -1; + } + } + if (err) { /* another try (James Lehmer's CE2 version 4.1)*/ + tuple.DesiredTuple = CISTPL_FUNCE; + for (err = first_tuple(handle, &tuple, &parse); !err; + err = next_tuple(handle, &tuple, &parse)) { + if (parse.funce.type == 0x02 && parse.funce.data[0] == 1 + && parse.funce.data[1] == 6 && tuple.TupleDataLen == 13) { + buf[1] = 4; + memcpy(&parse, buf+1, 8); + break; + } + } + } + if (err) { + printk(KNOT_XIRC "node-id not found in CIS\n"); + goto failure; + } + node_id = (cistpl_lan_node_id_t *)parse.funce.data; + if (node_id->nb != 6) { + printk(KNOT_XIRC "malformed node-id in CIS\n"); + goto failure; + } + for (i=0; i < 6; i++) + dev->dev_addr[i] = node_id->id[i]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + link->io.IOAddrLines =10; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + link->irq.Attributes = IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_LEVEL_ID; + if (local->modem) { + int pass; + + if (do_sound) { + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status |= CCSR_AUDIO_ENA; + } + link->irq.Attributes |= IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED ; + link->io.NumPorts2 = 8; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + if (local->dingo) { + /* Take the Modem IO port from the CIS and scan for a free + * Ethernet port */ + link->io.NumPorts1 = 16; /* no Mako stuff anymore */ + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + for (err = first_tuple(handle, &tuple, &parse); !err; + err = next_tuple(handle, &tuple, &parse)) { + if (cf->io.nwin > 0 && (cf->io.win[0].base & 0xf) == 8) { + for (ioaddr = 0x300; ioaddr < 0x400; ioaddr += 0x10) { + link->conf.ConfigIndex = cf->index ; + link->io.BasePort2 = cf->io.win[0].base; + link->io.BasePort1 = ioaddr; + if (!(err=pcmcia_request_io(link->handle, &link->io))) + goto port_found; + } + } + } + } else { + link->io.NumPorts1 = 18; + /* We do 2 passes here: The first one uses the regular mapping and + * the second tries again, thereby considering that the 32 ports are + * mirrored every 32 bytes. Actually we use a mirrored port for + * the Mako if (on the first pass) the COR bit 5 is set. + */ + for (pass=0; pass < 2; pass++) { + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + for (err = first_tuple(handle, &tuple, &parse); !err; + err = next_tuple(handle, &tuple, &parse)){ + if (cf->io.nwin > 0 && (cf->io.win[0].base & 0xf) == 8){ + link->conf.ConfigIndex = cf->index ; + link->io.BasePort2 = cf->io.win[0].base; + link->io.BasePort1 = link->io.BasePort2 + + (pass ? (cf->index & 0x20 ? -24:8) + : (cf->index & 0x20 ? 8:-24)); + if (!(err=pcmcia_request_io(link->handle, &link->io))) + goto port_found; + } + } + } + /* if special option: + * try to configure as Ethernet only. + * .... */ + } + printk(KNOT_XIRC "no ports available\n"); + } else { + link->irq.Attributes |= IRQ_TYPE_EXCLUSIVE; + link->io.NumPorts1 = 16; + for (ioaddr = 0x300; ioaddr < 0x400; ioaddr += 0x10) { + link->io.BasePort1 = ioaddr; + if (!(err=pcmcia_request_io(link->handle, &link->io))) + goto port_found; + } + link->io.BasePort1 = 0; /* let CS decide */ + if ((err=pcmcia_request_io(link->handle, &link->io))) { + cs_error(link->handle, RequestIO, err); + goto config_error; + } + } + port_found: + if (err) + goto config_error; + + /**************** + * Now allocate an interrupt line. Note that this does not + * actually assign a handler to the interrupt. + */ + if ((err=pcmcia_request_irq(link->handle, &link->irq))) { + cs_error(link->handle, RequestIRQ, err); + goto config_error; + } + + /**************** + * This actually configures the PCMCIA socket -- setting up + * the I/O windows and the interrupt mapping. + */ + if ((err=pcmcia_request_configuration(link->handle, &link->conf))) { + cs_error(link->handle, RequestConfiguration, err); + goto config_error; + } + + if (local->dingo) { + conf_reg_t reg; + win_req_t req; + memreq_t mem; + + /* Reset the modem's BAR to the correct value + * This is necessary because in the RequestConfiguration call, + * the base address of the ethernet port (BasePort1) is written + * to the BAR registers of the modem. + */ + reg.Action = CS_WRITE; + reg.Offset = CISREG_IOBASE_0; + reg.Value = link->io.BasePort2 & 0xff; + if ((err = pcmcia_access_configuration_register(link->handle, ®))) { + cs_error(link->handle, AccessConfigurationRegister, err); + goto config_error; + } + reg.Action = CS_WRITE; + reg.Offset = CISREG_IOBASE_1; + reg.Value = (link->io.BasePort2 >> 8) & 0xff; + if ((err = pcmcia_access_configuration_register(link->handle, ®))) { + cs_error(link->handle, AccessConfigurationRegister, err); + goto config_error; + } + + /* There is no config entry for the Ethernet part which + * is at 0x0800. So we allocate a window into the attribute + * memory and write direct to the CIS registers + */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = req.Size = 0; + req.AccessSpeed = 0; + if ((err = pcmcia_request_window(&link->handle, &req, &link->win))) { + cs_error(link->handle, RequestWindow, err); + goto config_error; + } + local->dingo_ccr = ioremap(req.Base,0x1000) + 0x0800; + mem.CardOffset = 0x0; + mem.Page = 0; + if ((err = pcmcia_map_mem_page(link->win, &mem))) { + cs_error(link->handle, MapMemPage, err); + goto config_error; + } + + /* Setup the CCRs; there are no infos in the CIS about the Ethernet + * part. + */ + writeb(0x47, local->dingo_ccr + CISREG_COR); + ioaddr = link->io.BasePort1; + writeb(ioaddr & 0xff , local->dingo_ccr + CISREG_IOBASE_0); + writeb((ioaddr >> 8)&0xff , local->dingo_ccr + CISREG_IOBASE_1); + + #if 0 + { + u_char tmp; + printk(KERN_INFO "ECOR:"); + for (i=0; i < 7; i++) { + tmp = readb(local->dingo_ccr + i*2); + printk(" %02x", tmp); + } + printk("\n"); + printk(KERN_INFO "DCOR:"); + for (i=0; i < 4; i++) { + tmp = readb(local->dingo_ccr + 0x20 + i*2); + printk(" %02x", tmp); + } + printk("\n"); + printk(KERN_INFO "SCOR:"); + for (i=0; i < 10; i++) { + tmp = readb(local->dingo_ccr + 0x40 + i*2); + printk(" %02x", tmp); + } + printk("\n"); + } + #endif + + writeb(0x01, local->dingo_ccr + 0x20); + writeb(0x0c, local->dingo_ccr + 0x22); + writeb(0x00, local->dingo_ccr + 0x24); + writeb(0x00, local->dingo_ccr + 0x26); + writeb(0x00, local->dingo_ccr + 0x28); + } + + /* The if_port symbol can be set when the module is loaded */ + local->probe_port=0; + if (!if_port) { + local->probe_port = dev->if_port = 1; + } else if ((if_port >= 1 && if_port <= 2) || + (local->mohawk && if_port==4)) + dev->if_port = if_port; + else + printk(KNOT_XIRC "invalid if_port requested\n"); + + /* we can now register the device with the net subsystem */ + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + + if (local->dingo) + do_reset(dev, 1); /* a kludge to make the cem56 work */ + + link->dev = &local->node; + link->state &= ~DEV_CONFIG_PENDING; + SET_NETDEV_DEV(dev, &handle_to_dev(handle)); + + if ((err=register_netdev(dev))) { + printk(KNOT_XIRC "register_netdev() failed\n"); + link->dev = NULL; + goto config_error; + } + + strcpy(local->node.dev_name, dev->name); + + /* give some infos about the hardware */ + printk(KERN_INFO "%s: %s: port %#3lx, irq %d, hwaddr", + dev->name, local->manf_str,(u_long)dev->base_addr, (int)dev->irq); + for (i = 0; i < 6; i++) + printk("%c%02X", i?':':' ', dev->dev_addr[i]); + printk("\n"); + + return; + + config_error: + link->state &= ~DEV_CONFIG_PENDING; + xirc2ps_release(link); + return; + + cis_error: + printk(KNOT_XIRC "unable to parse CIS\n"); + failure: + link->state &= ~DEV_CONFIG_PENDING; +} /* xirc2ps_config */ + +/**************** + * After a card is removed, xirc2ps_release() will unregister the net + * device, and release the PCMCIA configuration. If the device is + * still open, this will be postponed until it is closed. + */ +static void +xirc2ps_release(dev_link_t *link) +{ + + DEBUG(0, "release(0x%p)\n", link); + + if (link->win) { + struct net_device *dev = link->priv; + local_info_t *local = netdev_priv(dev); + if (local->dingo) + iounmap(local->dingo_ccr - 0x0800); + pcmcia_release_window(link->win); + } + pcmcia_release_configuration(link->handle); + pcmcia_release_io(link->handle, &link->io); + pcmcia_release_irq(link->handle, &link->irq); + link->state &= ~DEV_CONFIG; + +} /* xirc2ps_release */ + +/*====================================================================*/ + +/**************** + * The card status event handler. Mostly, this schedules other + * stuff to run after an event is received. A CARD_REMOVAL event + * also sets some flags to discourage the net drivers from trying + * to talk to the card any more. + * + * When a CARD_REMOVAL event is received, we immediately set a flag + * to block future accesses to this device. All the functions that + * actually access the device should check this flag to make sure + * the card is still present. + */ + +static int +xirc2ps_event(event_t event, int priority, + event_callback_args_t * args) +{ + dev_link_t *link = args->client_data; + struct net_device *dev = link->priv; + + DEBUG(0, "event(%d)\n", (int)event); + + switch (event) { + case CS_EVENT_REGISTRATION_COMPLETE: + DEBUG(0, "registration complete\n"); + break; + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + netif_device_detach(dev); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + xirc2ps_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (link->open) { + netif_device_detach(dev); + do_powerdown(dev); + } + pcmcia_release_configuration(link->handle); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + pcmcia_request_configuration(link->handle, &link->conf); + if (link->open) { + do_reset(dev,1); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* xirc2ps_event */ + +/*====================================================================*/ + +/**************** + * This is the Interrupt service route. + */ +static irqreturn_t +xirc2ps_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *)dev_id; + local_info_t *lp = netdev_priv(dev); + kio_addr_t ioaddr; + u_char saved_page; + unsigned bytes_rcvd; + unsigned int_status, eth_status, rx_status, tx_status; + unsigned rsr, pktlen; + ulong start_ticks = jiffies; /* fixme: jiffies rollover every 497 days + * is this something to worry about? + * -- on a laptop? + */ + + if (!netif_device_present(dev)) + return IRQ_HANDLED; + + ioaddr = dev->base_addr; + if (lp->mohawk) { /* must disable the interrupt */ + PutByte(XIRCREG_CR, 0); + } + + DEBUG(6, "%s: interrupt %d at %#x.\n", dev->name, irq, ioaddr); + + saved_page = GetByte(XIRCREG_PR); + /* Read the ISR to see whats the cause for the interrupt. + * This also clears the interrupt flags on CE2 cards + */ + int_status = GetByte(XIRCREG_ISR); + bytes_rcvd = 0; + loop_entry: + if (int_status == 0xff) { /* card may be ejected */ + DEBUG(3, "%s: interrupt %d for dead card\n", dev->name, irq); + goto leave; + } + eth_status = GetByte(XIRCREG_ESR); + + SelectPage(0x40); + rx_status = GetByte(XIRCREG40_RXST0); + PutByte(XIRCREG40_RXST0, (~rx_status & 0xff)); + tx_status = GetByte(XIRCREG40_TXST0); + tx_status |= GetByte(XIRCREG40_TXST1) << 8; + PutByte(XIRCREG40_TXST0, 0); + PutByte(XIRCREG40_TXST1, 0); + + DEBUG(3, "%s: ISR=%#2.2x ESR=%#2.2x RSR=%#2.2x TSR=%#4.4x\n", + dev->name, int_status, eth_status, rx_status, tx_status); + + /***** receive section ******/ + SelectPage(0); + while (eth_status & FullPktRcvd) { + rsr = GetByte(XIRCREG0_RSR); + if (bytes_rcvd > maxrx_bytes && (rsr & PktRxOk)) { + /* too many bytes received during this int, drop the rest of the + * packets */ + lp->stats.rx_dropped++; + DEBUG(2, "%s: RX drop, too much done\n", dev->name); + } else if (rsr & PktRxOk) { + struct sk_buff *skb; + + pktlen = GetWord(XIRCREG0_RBC); + bytes_rcvd += pktlen; + + DEBUG(5, "rsr=%#02x packet_length=%u\n", rsr, pktlen); + + skb = dev_alloc_skb(pktlen+3); /* 1 extra so we can use insw */ + if (!skb) { + printk(KNOT_XIRC "low memory, packet dropped (size=%u)\n", + pktlen); + lp->stats.rx_dropped++; + } else { /* okay get the packet */ + skb_reserve(skb, 2); + if (lp->silicon == 0 ) { /* work around a hardware bug */ + unsigned rhsa; /* receive start address */ + + SelectPage(5); + rhsa = GetWord(XIRCREG5_RHSA0); + SelectPage(0); + rhsa += 3; /* skip control infos */ + if (rhsa >= 0x8000) + rhsa = 0; + if (rhsa + pktlen > 0x8000) { + unsigned i; + u_char *buf = skb_put(skb, pktlen); + for (i=0; i < pktlen ; i++, rhsa++) { + buf[i] = GetByte(XIRCREG_EDP); + if (rhsa == 0x8000) { + rhsa = 0; + i--; + } + } + } else { + insw(ioaddr+XIRCREG_EDP, + skb_put(skb, pktlen), (pktlen+1)>>1); + } + } + #if 0 + else if (lp->mohawk) { + /* To use this 32 bit access we should use + * a manual optimized loop + * Also the words are swapped, we can get more + * performance by using 32 bit access and swapping + * the words in a register. Will need this for cardbus + * + * Note: don't forget to change the ALLOC_SKB to .. +3 + */ + unsigned i; + u_long *p = skb_put(skb, pktlen); + register u_long a; + kio_addr_t edpreg = ioaddr+XIRCREG_EDP-2; + for (i=0; i < len ; i += 4, p++) { + a = inl(edpreg); + __asm__("rorl $16,%0\n\t" + :"=q" (a) + : "0" (a)); + *p = a; + } + } + #endif + else { + insw(ioaddr+XIRCREG_EDP, skb_put(skb, pktlen), + (pktlen+1)>>1); + } + skb->protocol = eth_type_trans(skb, dev); + skb->dev = dev; + netif_rx(skb); + dev->last_rx = jiffies; + lp->stats.rx_packets++; + lp->stats.rx_bytes += pktlen; + if (!(rsr & PhyPkt)) + lp->stats.multicast++; + } + } else { /* bad packet */ + DEBUG(5, "rsr=%#02x\n", rsr); + } + if (rsr & PktTooLong) { + lp->stats.rx_frame_errors++; + DEBUG(3, "%s: Packet too long\n", dev->name); + } + if (rsr & CRCErr) { + lp->stats.rx_crc_errors++; + DEBUG(3, "%s: CRC error\n", dev->name); + } + if (rsr & AlignErr) { + lp->stats.rx_fifo_errors++; /* okay ? */ + DEBUG(3, "%s: Alignment error\n", dev->name); + } + + /* clear the received/dropped/error packet */ + PutWord(XIRCREG0_DO, 0x8000); /* issue cmd: skip_rx_packet */ + + /* get the new ethernet status */ + eth_status = GetByte(XIRCREG_ESR); + } + if (rx_status & 0x10) { /* Receive overrun */ + lp->stats.rx_over_errors++; + PutByte(XIRCREG_CR, ClearRxOvrun); + DEBUG(3, "receive overrun cleared\n"); + } + + /***** transmit section ******/ + if (int_status & PktTxed) { + unsigned n, nn; + + n = lp->last_ptr_value; + nn = GetByte(XIRCREG0_PTR); + lp->last_ptr_value = nn; + if (nn < n) /* rollover */ + lp->stats.tx_packets += 256 - n; + else if (n == nn) { /* happens sometimes - don't know why */ + DEBUG(0, "PTR not changed?\n"); + } else + lp->stats.tx_packets += lp->last_ptr_value - n; + netif_wake_queue(dev); + } + if (tx_status & 0x0002) { /* Execessive collissions */ + DEBUG(0, "tx restarted due to execssive collissions\n"); + PutByte(XIRCREG_CR, RestartTx); /* restart transmitter process */ + } + if (tx_status & 0x0040) + lp->stats.tx_aborted_errors++; + + /* recalculate our work chunk so that we limit the duration of this + * ISR to about 1/10 of a second. + * Calculate only if we received a reasonable amount of bytes. + */ + if (bytes_rcvd > 1000) { + u_long duration = jiffies - start_ticks; + + if (duration >= HZ/10) { /* if more than about 1/10 second */ + maxrx_bytes = (bytes_rcvd * (HZ/10)) / duration; + if (maxrx_bytes < 2000) + maxrx_bytes = 2000; + else if (maxrx_bytes > 22000) + maxrx_bytes = 22000; + DEBUG(1, "set maxrx=%u (rcvd=%u ticks=%lu)\n", + maxrx_bytes, bytes_rcvd, duration); + } else if (!duration && maxrx_bytes < 22000) { + /* now much faster */ + maxrx_bytes += 2000; + if (maxrx_bytes > 22000) + maxrx_bytes = 22000; + DEBUG(1, "set maxrx=%u\n", maxrx_bytes); + } + } + + leave: + if (lockup_hack) { + if (int_status != 0xff && (int_status = GetByte(XIRCREG_ISR)) != 0) + goto loop_entry; + } + SelectPage(saved_page); + PutByte(XIRCREG_CR, EnableIntr); /* re-enable interrupts */ + /* Instead of dropping packets during a receive, we could + * force an interrupt with this command: + * PutByte(XIRCREG_CR, EnableIntr|ForceIntr); + */ + return IRQ_HANDLED; +} /* xirc2ps_interrupt */ + +/*====================================================================*/ + +static void +do_tx_timeout(struct net_device *dev) +{ + local_info_t *lp = netdev_priv(dev); + printk(KERN_NOTICE "%s: transmit timed out\n", dev->name); + lp->stats.tx_errors++; + /* reset the card */ + do_reset(dev,1); + dev->trans_start = jiffies; + netif_wake_queue(dev); +} + +static int +do_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + local_info_t *lp = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + int okay; + unsigned freespace; + unsigned pktlen = skb? skb->len : 0; + + DEBUG(1, "do_start_xmit(skb=%p, dev=%p) len=%u\n", + skb, dev, pktlen); + + + /* adjust the packet length to min. required + * and hope that the buffer is large enough + * to provide some random data. + * fixme: For Mohawk we can change this by sending + * a larger packetlen than we actually have; the chip will + * pad this in his buffer with random bytes + */ + if (pktlen < ETH_ZLEN) + { + skb = skb_padto(skb, ETH_ZLEN); + if (skb == NULL) + return 0; + pktlen = ETH_ZLEN; + } + + netif_stop_queue(dev); + SelectPage(0); + PutWord(XIRCREG0_TRS, (u_short)pktlen+2); + freespace = GetWord(XIRCREG0_TSO); + okay = freespace & 0x8000; + freespace &= 0x7fff; + /* TRS doesn't work - (indeed it is eliminated with sil-rev 1) */ + okay = pktlen +2 < freespace; + DEBUG(2 + (okay ? 2 : 0), "%s: avail. tx space=%u%s\n", + dev->name, freespace, okay ? " (okay)":" (not enough)"); + if (!okay) { /* not enough space */ + return 1; /* upper layer may decide to requeue this packet */ + } + /* send the packet */ + PutWord(XIRCREG_EDP, (u_short)pktlen); + outsw(ioaddr+XIRCREG_EDP, skb->data, pktlen>>1); + if (pktlen & 1) + PutByte(XIRCREG_EDP, skb->data[pktlen-1]); + + if (lp->mohawk) + PutByte(XIRCREG_CR, TransmitPacket|EnableIntr); + + dev_kfree_skb (skb); + dev->trans_start = jiffies; + lp->stats.tx_bytes += pktlen; + netif_start_queue(dev); + return 0; +} + +static struct net_device_stats * +do_get_stats(struct net_device *dev) +{ + local_info_t *lp = netdev_priv(dev); + + /* lp->stats.rx_missed_errors = GetByte(?) */ + return &lp->stats; +} + +/**************** + * Set all addresses: This first one is the individual address, + * the next 9 addresses are taken from the multicast list and + * the rest is filled with the individual address. + */ +static void +set_addresses(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + local_info_t *lp = netdev_priv(dev); + struct dev_mc_list *dmi = dev->mc_list; + char *addr; + int i,j,k,n; + + SelectPage(k=0x50); + for (i=0,j=8,n=0; ; i++, j++) { + if (i > 5) { + if (++n > 9) + break; + i = 0; + } + if (j > 15) { + j = 8; + k++; + SelectPage(k); + } + + if (n && n <= dev->mc_count && dmi) { + addr = dmi->dmi_addr; + dmi = dmi->next; + } else + addr = dev->dev_addr; + + if (lp->mohawk) + PutByte(j, addr[5-i]); + else + PutByte(j, addr[i]); + } + SelectPage(0); +} + +/**************** + * Set or clear the multicast filter for this adaptor. + * We can filter up to 9 addresses, if more are requested we set + * multicast promiscuous mode. + */ + +static void +set_multicast_list(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + + SelectPage(0x42); + if (dev->flags & IFF_PROMISC) { /* snoop */ + PutByte(XIRCREG42_SWC1, 0x06); /* set MPE and PME */ + } else if (dev->mc_count > 9 || (dev->flags & IFF_ALLMULTI)) { + PutByte(XIRCREG42_SWC1, 0x06); /* set MPE */ + } else if (dev->mc_count) { + /* the chip can filter 9 addresses perfectly */ + PutByte(XIRCREG42_SWC1, 0x00); + SelectPage(0x40); + PutByte(XIRCREG40_CMD0, Offline); + set_addresses(dev); + SelectPage(0x40); + PutByte(XIRCREG40_CMD0, EnableRecv | Online); + } else { /* standard usage */ + PutByte(XIRCREG42_SWC1, 0x00); + } + SelectPage(0); +} + +static int +do_config(struct net_device *dev, struct ifmap *map) +{ + local_info_t *local = netdev_priv(dev); + + DEBUG(0, "do_config(%p)\n", dev); + if (map->port != 255 && map->port != dev->if_port) { + if (map->port > 4) + return -EINVAL; + if (!map->port) { + local->probe_port = 1; + dev->if_port = 1; + } else { + local->probe_port = 0; + dev->if_port = map->port; + } + printk(KERN_INFO "%s: switching to %s port\n", + dev->name, if_names[dev->if_port]); + do_reset(dev,1); /* not the fine way :-) */ + } + return 0; +} + +/**************** + * Open the driver + */ +static int +do_open(struct net_device *dev) +{ + local_info_t *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + + DEBUG(0, "do_open(%p)\n", dev); + + /* Check that the PCMCIA card is still here. */ + /* Physical device present signature. */ + if (!DEV_OK(link)) + return -ENODEV; + + /* okay */ + link->open++; + + netif_start_queue(dev); + do_reset(dev,1); + + return 0; +} + +static void netdev_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, "xirc2ps_cs"); + sprintf(info->bus_info, "PCMCIA 0x%lx", dev->base_addr); +} + +static struct ethtool_ops netdev_ethtool_ops = { + .get_drvinfo = netdev_get_drvinfo, +}; + +static int +do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + local_info_t *local = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + u16 *data = (u16 *)&rq->ifr_ifru; + + DEBUG(1, "%s: ioctl(%-.6s, %#04x) %04x %04x %04x %04x\n", + dev->name, rq->ifr_ifrn.ifrn_name, cmd, + data[0], data[1], data[2], data[3]); + + if (!local->mohawk) + return -EOPNOTSUPP; + + switch(cmd) { + case SIOCGMIIPHY: /* Get the address of the PHY in use. */ + data[0] = 0; /* we have only this address */ + /* fall trough */ + case SIOCGMIIREG: /* Read the specified MII register. */ + data[3] = mii_rd(ioaddr, data[0] & 0x1f, data[1] & 0x1f); + break; + case SIOCSMIIREG: /* Write the specified MII register */ + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + mii_wr(ioaddr, data[0] & 0x1f, data[1] & 0x1f, data[2], 16); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static void +hardreset(struct net_device *dev) +{ + local_info_t *local = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + + SelectPage(4); + udelay(1); + PutByte(XIRCREG4_GPR1, 0); /* clear bit 0: power down */ + msleep(40); /* wait 40 msec */ + if (local->mohawk) + PutByte(XIRCREG4_GPR1, 1); /* set bit 0: power up */ + else + PutByte(XIRCREG4_GPR1, 1 | 4); /* set bit 0: power up, bit 2: AIC */ + msleep(20); /* wait 20 msec */ +} + +static void +do_reset(struct net_device *dev, int full) +{ + local_info_t *local = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + unsigned value; + + DEBUG(0, "%s: do_reset(%p,%d)\n", dev? dev->name:"eth?", dev, full); + + hardreset(dev); + PutByte(XIRCREG_CR, SoftReset); /* set */ + msleep(20); /* wait 20 msec */ + PutByte(XIRCREG_CR, 0); /* clear */ + msleep(40); /* wait 40 msec */ + if (local->mohawk) { + SelectPage(4); + /* set pin GP1 and GP2 to output (0x0c) + * set GP1 to low to power up the ML6692 (0x00) + * set GP2 to high to power up the 10Mhz chip (0x02) + */ + PutByte(XIRCREG4_GPR0, 0x0e); + } + + /* give the circuits some time to power up */ + msleep(500); /* about 500ms */ + + local->last_ptr_value = 0; + local->silicon = local->mohawk ? (GetByte(XIRCREG4_BOV) & 0x70) >> 4 + : (GetByte(XIRCREG4_BOV) & 0x30) >> 4; + + if (local->probe_port) { + if (!local->mohawk) { + SelectPage(4); + PutByte(XIRCREG4_GPR0, 4); + local->probe_port = 0; + } + } else if (dev->if_port == 2) { /* enable 10Base2 */ + SelectPage(0x42); + PutByte(XIRCREG42_SWC1, 0xC0); + } else { /* enable 10BaseT */ + SelectPage(0x42); + PutByte(XIRCREG42_SWC1, 0x80); + } + msleep(40); /* wait 40 msec to let it complete */ + + #ifdef PCMCIA_DEBUG + if (pc_debug) { + SelectPage(0); + value = GetByte(XIRCREG_ESR); /* read the ESR */ + printk(KERN_DEBUG "%s: ESR is: %#02x\n", dev->name, value); + } + #endif + + /* setup the ECR */ + SelectPage(1); + PutByte(XIRCREG1_IMR0, 0xff); /* allow all ints */ + PutByte(XIRCREG1_IMR1, 1 ); /* and Set TxUnderrunDetect */ + value = GetByte(XIRCREG1_ECR); + #if 0 + if (local->mohawk) + value |= DisableLinkPulse; + PutByte(XIRCREG1_ECR, value); + #endif + DEBUG(0, "%s: ECR is: %#02x\n", dev->name, value); + + SelectPage(0x42); + PutByte(XIRCREG42_SWC0, 0x20); /* disable source insertion */ + + if (local->silicon != 1) { + /* set the local memory dividing line. + * The comments in the sample code say that this is only + * settable with the scipper version 2 which is revision 0. + * Always for CE3 cards + */ + SelectPage(2); + PutWord(XIRCREG2_RBS, 0x2000); + } + + if (full) + set_addresses(dev); + + /* Hardware workaround: + * The receive byte pointer after reset is off by 1 so we need + * to move the offset pointer back to 0. + */ + SelectPage(0); + PutWord(XIRCREG0_DO, 0x2000); /* change offset command, off=0 */ + + /* setup MAC IMRs and clear status registers */ + SelectPage(0x40); /* Bit 7 ... bit 0 */ + PutByte(XIRCREG40_RMASK0, 0xff); /* ROK, RAB, rsv, RO, CRC, AE, PTL, MP */ + PutByte(XIRCREG40_TMASK0, 0xff); /* TOK, TAB, SQE, LL, TU, JAB, EXC, CRS */ + PutByte(XIRCREG40_TMASK1, 0xb0); /* rsv, rsv, PTD, EXT, rsv,rsv,rsv, rsv*/ + PutByte(XIRCREG40_RXST0, 0x00); /* ROK, RAB, REN, RO, CRC, AE, PTL, MP */ + PutByte(XIRCREG40_TXST0, 0x00); /* TOK, TAB, SQE, LL, TU, JAB, EXC, CRS */ + PutByte(XIRCREG40_TXST1, 0x00); /* TEN, rsv, PTD, EXT, retry_counter:4 */ + + if (full && local->mohawk && init_mii(dev)) { + if (dev->if_port == 4 || local->dingo || local->new_mii) { + printk(KERN_INFO "%s: MII selected\n", dev->name); + SelectPage(2); + PutByte(XIRCREG2_MSR, GetByte(XIRCREG2_MSR) | 0x08); + msleep(20); + } else { + printk(KERN_INFO "%s: MII detected; using 10mbs\n", + dev->name); + SelectPage(0x42); + if (dev->if_port == 2) /* enable 10Base2 */ + PutByte(XIRCREG42_SWC1, 0xC0); + else /* enable 10BaseT */ + PutByte(XIRCREG42_SWC1, 0x80); + msleep(40); /* wait 40 msec to let it complete */ + } + if (full_duplex) + PutByte(XIRCREG1_ECR, GetByte(XIRCREG1_ECR | FullDuplex)); + } else { /* No MII */ + SelectPage(0); + value = GetByte(XIRCREG_ESR); /* read the ESR */ + dev->if_port = (value & MediaSelect) ? 1 : 2; + } + + /* configure the LEDs */ + SelectPage(2); + if (dev->if_port == 1 || dev->if_port == 4) /* TP: Link and Activity */ + PutByte(XIRCREG2_LED, 0x3b); + else /* Coax: Not-Collision and Activity */ + PutByte(XIRCREG2_LED, 0x3a); + + if (local->dingo) + PutByte(0x0b, 0x04); /* 100 Mbit LED */ + + /* enable receiver and put the mac online */ + if (full) { + SelectPage(0x40); + PutByte(XIRCREG40_CMD0, EnableRecv | Online); + } + + /* setup Ethernet IMR and enable interrupts */ + SelectPage(1); + PutByte(XIRCREG1_IMR0, 0xff); + udelay(1); + SelectPage(0); + PutByte(XIRCREG_CR, EnableIntr); + if (local->modem && !local->dingo) { /* do some magic */ + if (!(GetByte(0x10) & 0x01)) + PutByte(0x10, 0x11); /* unmask master-int bit */ + } + + if (full) + printk(KERN_INFO "%s: media %s, silicon revision %d\n", + dev->name, if_names[dev->if_port], local->silicon); + /* We should switch back to page 0 to avoid a bug in revision 0 + * where regs with offset below 8 can't be read after an access + * to the MAC registers */ + SelectPage(0); +} + +/**************** + * Initialize the Media-Independent-Interface + * Returns: True if we have a good MII + */ +static int +init_mii(struct net_device *dev) +{ + local_info_t *local = netdev_priv(dev); + kio_addr_t ioaddr = dev->base_addr; + unsigned control, status, linkpartner; + int i; + + if (if_port == 4 || if_port == 1) { /* force 100BaseT or 10BaseT */ + dev->if_port = if_port; + local->probe_port = 0; + return 1; + } + + status = mii_rd(ioaddr, 0, 1); + if ((status & 0xff00) != 0x7800) + return 0; /* No MII */ + + local->new_mii = (mii_rd(ioaddr, 0, 2) != 0xffff); + + if (local->probe_port) + control = 0x1000; /* auto neg */ + else if (dev->if_port == 4) + control = 0x2000; /* no auto neg, 100mbs mode */ + else + control = 0x0000; /* no auto neg, 10mbs mode */ + mii_wr(ioaddr, 0, 0, control, 16); + udelay(100); + control = mii_rd(ioaddr, 0, 0); + + if (control & 0x0400) { + printk(KERN_NOTICE "%s can't take PHY out of isolation mode\n", + dev->name); + local->probe_port = 0; + return 0; + } + + if (local->probe_port) { + /* according to the DP83840A specs the auto negotiation process + * may take up to 3.5 sec, so we use this also for our ML6692 + * Fixme: Better to use a timer here! + */ + for (i=0; i < 35; i++) { + msleep(100); /* wait 100 msec */ + status = mii_rd(ioaddr, 0, 1); + if ((status & 0x0020) && (status & 0x0004)) + break; + } + + if (!(status & 0x0020)) { + printk(KERN_INFO "%s: autonegotiation failed;" + " using 10mbs\n", dev->name); + if (!local->new_mii) { + control = 0x0000; + mii_wr(ioaddr, 0, 0, control, 16); + udelay(100); + SelectPage(0); + dev->if_port = (GetByte(XIRCREG_ESR) & MediaSelect) ? 1 : 2; + } + } else { + linkpartner = mii_rd(ioaddr, 0, 5); + printk(KERN_INFO "%s: MII link partner: %04x\n", + dev->name, linkpartner); + if (linkpartner & 0x0080) { + dev->if_port = 4; + } else + dev->if_port = 1; + } + } + + return 1; +} + +static void +do_powerdown(struct net_device *dev) +{ + + kio_addr_t ioaddr = dev->base_addr; + + DEBUG(0, "do_powerdown(%p)\n", dev); + + SelectPage(4); + PutByte(XIRCREG4_GPR1, 0); /* clear bit 0: power down */ + SelectPage(0); +} + +static int +do_stop(struct net_device *dev) +{ + kio_addr_t ioaddr = dev->base_addr; + local_info_t *lp = netdev_priv(dev); + dev_link_t *link = &lp->link; + + DEBUG(0, "do_stop(%p)\n", dev); + + if (!link) + return -ENODEV; + + netif_stop_queue(dev); + + SelectPage(0); + PutByte(XIRCREG_CR, 0); /* disable interrupts */ + SelectPage(0x01); + PutByte(XIRCREG1_IMR0, 0x00); /* forbid all ints */ + SelectPage(4); + PutByte(XIRCREG4_GPR1, 0); /* clear bit 0: power down */ + SelectPage(0); + + link->open--; + return 0; +} + +static struct pcmcia_driver xirc2ps_cs_driver = { + .owner = THIS_MODULE, + .drv = { + .name = "xirc2ps_cs", + }, + .attach = xirc2ps_attach, + .detach = xirc2ps_detach, +}; + +static int __init +init_xirc2ps_cs(void) +{ + return pcmcia_register_driver(&xirc2ps_cs_driver); +} + +static void __exit +exit_xirc2ps_cs(void) +{ + pcmcia_unregister_driver(&xirc2ps_cs_driver); + BUG_ON(dev_list != NULL); +} + +module_init(init_xirc2ps_cs); +module_exit(exit_xirc2ps_cs); + +#ifndef MODULE +static int __init setup_xirc2ps_cs(char *str) +{ + /* if_port, full_duplex, do_sound, lockup_hack + */ + int ints[10] = { -1 }; + + str = get_options(str, 9, ints); + +#define MAYBE_SET(X,Y) if (ints[0] >= Y && ints[Y] != -1) { X = ints[Y]; } + MAYBE_SET(if_port, 3); + MAYBE_SET(full_duplex, 4); + MAYBE_SET(do_sound, 5); + MAYBE_SET(lockup_hack, 6); +#undef MAYBE_SET + + return 0; +} + +__setup("xirc2ps_cs=", setup_xirc2ps_cs); +#endif |