diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/net/pcmcia/smc91c92_cs.c |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/net/pcmcia/smc91c92_cs.c')
-rw-r--r-- | drivers/net/pcmcia/smc91c92_cs.c | 2260 |
1 files changed, 2260 insertions, 0 deletions
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); |