162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-1.0+ 262306a36Sopenharmony_ci/* wd.c: A WD80x3 ethernet driver for linux. */ 362306a36Sopenharmony_ci/* 462306a36Sopenharmony_ci Written 1993-94 by Donald Becker. 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci Copyright 1993 United States Government as represented by the 762306a36Sopenharmony_ci Director, National Security Agency. 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci The author may be reached as becker@scyld.com, or C/O 1062306a36Sopenharmony_ci Scyld Computing Corporation 1162306a36Sopenharmony_ci 410 Severn Ave., Suite 210 1262306a36Sopenharmony_ci Annapolis MD 21403 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci This is a driver for WD8003 and WD8013 "compatible" ethercards. 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci Thanks to Russ Nelson (nelson@crnwyr.com) for loaning me a WD8013. 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci Changelog: 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci Paul Gortmaker : multiple card support for module users, support 2162306a36Sopenharmony_ci for non-standard memory sizes. 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci*/ 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic const char version[] = 2762306a36Sopenharmony_ci "wd.c:v1.10 9/23/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n"; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include <linux/module.h> 3062306a36Sopenharmony_ci#include <linux/kernel.h> 3162306a36Sopenharmony_ci#include <linux/errno.h> 3262306a36Sopenharmony_ci#include <linux/string.h> 3362306a36Sopenharmony_ci#include <linux/init.h> 3462306a36Sopenharmony_ci#include <linux/interrupt.h> 3562306a36Sopenharmony_ci#include <linux/delay.h> 3662306a36Sopenharmony_ci#include <linux/netdevice.h> 3762306a36Sopenharmony_ci#include <linux/etherdevice.h> 3862306a36Sopenharmony_ci#include <net/Space.h> 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#include <asm/io.h> 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#include "8390.h" 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#define DRV_NAME "wd" 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/* A zero-terminated list of I/O addresses to be probed. */ 4762306a36Sopenharmony_cistatic unsigned int wd_portlist[] __initdata = 4862306a36Sopenharmony_ci{0x300, 0x280, 0x380, 0x240, 0}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic int wd_probe1(struct net_device *dev, int ioaddr); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic int wd_open(struct net_device *dev); 5362306a36Sopenharmony_cistatic void wd_reset_8390(struct net_device *dev); 5462306a36Sopenharmony_cistatic void wd_get_8390_hdr(struct net_device *dev, struct e8390_pkt_hdr *hdr, 5562306a36Sopenharmony_ci int ring_page); 5662306a36Sopenharmony_cistatic void wd_block_input(struct net_device *dev, int count, 5762306a36Sopenharmony_ci struct sk_buff *skb, int ring_offset); 5862306a36Sopenharmony_cistatic void wd_block_output(struct net_device *dev, int count, 5962306a36Sopenharmony_ci const unsigned char *buf, int start_page); 6062306a36Sopenharmony_cistatic int wd_close(struct net_device *dev); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic u32 wd_msg_enable; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci#define WD_START_PG 0x00 /* First page of TX buffer */ 6562306a36Sopenharmony_ci#define WD03_STOP_PG 0x20 /* Last page +1 of RX ring */ 6662306a36Sopenharmony_ci#define WD13_STOP_PG 0x40 /* Last page +1 of RX ring */ 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci#define WD_CMDREG 0 /* Offset to ASIC command register. */ 6962306a36Sopenharmony_ci#define WD_RESET 0x80 /* Board reset, in WD_CMDREG. */ 7062306a36Sopenharmony_ci#define WD_MEMENB 0x40 /* Enable the shared memory. */ 7162306a36Sopenharmony_ci#define WD_CMDREG5 5 /* Offset to 16-bit-only ASIC register 5. */ 7262306a36Sopenharmony_ci#define ISA16 0x80 /* Enable 16 bit access from the ISA bus. */ 7362306a36Sopenharmony_ci#define NIC16 0x40 /* Enable 16 bit access from the 8390. */ 7462306a36Sopenharmony_ci#define WD_NIC_OFFSET 16 /* Offset to the 8390 from the base_addr. */ 7562306a36Sopenharmony_ci#define WD_IO_EXTENT 32 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci/* Probe for the WD8003 and WD8013. These cards have the station 7962306a36Sopenharmony_ci address PROM at I/O ports <base>+8 to <base>+13, with a checksum 8062306a36Sopenharmony_ci following. A Soundblaster can have the same checksum as an WDethercard, 8162306a36Sopenharmony_ci so we have an extra exclusionary check for it. 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci The wd_probe1() routine initializes the card and fills the 8462306a36Sopenharmony_ci station address field. */ 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic int __init do_wd_probe(struct net_device *dev) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci int i; 8962306a36Sopenharmony_ci struct resource *r; 9062306a36Sopenharmony_ci int base_addr = dev->base_addr; 9162306a36Sopenharmony_ci int irq = dev->irq; 9262306a36Sopenharmony_ci int mem_start = dev->mem_start; 9362306a36Sopenharmony_ci int mem_end = dev->mem_end; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci if (base_addr > 0x1ff) { /* Check a user specified location. */ 9662306a36Sopenharmony_ci r = request_region(base_addr, WD_IO_EXTENT, "wd-probe"); 9762306a36Sopenharmony_ci if ( r == NULL) 9862306a36Sopenharmony_ci return -EBUSY; 9962306a36Sopenharmony_ci i = wd_probe1(dev, base_addr); 10062306a36Sopenharmony_ci if (i != 0) 10162306a36Sopenharmony_ci release_region(base_addr, WD_IO_EXTENT); 10262306a36Sopenharmony_ci else 10362306a36Sopenharmony_ci r->name = dev->name; 10462306a36Sopenharmony_ci return i; 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci else if (base_addr != 0) /* Don't probe at all. */ 10762306a36Sopenharmony_ci return -ENXIO; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci for (i = 0; wd_portlist[i]; i++) { 11062306a36Sopenharmony_ci int ioaddr = wd_portlist[i]; 11162306a36Sopenharmony_ci r = request_region(ioaddr, WD_IO_EXTENT, "wd-probe"); 11262306a36Sopenharmony_ci if (r == NULL) 11362306a36Sopenharmony_ci continue; 11462306a36Sopenharmony_ci if (wd_probe1(dev, ioaddr) == 0) { 11562306a36Sopenharmony_ci r->name = dev->name; 11662306a36Sopenharmony_ci return 0; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci release_region(ioaddr, WD_IO_EXTENT); 11962306a36Sopenharmony_ci dev->irq = irq; 12062306a36Sopenharmony_ci dev->mem_start = mem_start; 12162306a36Sopenharmony_ci dev->mem_end = mem_end; 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci return -ENODEV; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci#ifndef MODULE 12862306a36Sopenharmony_cistruct net_device * __init wd_probe(int unit) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct net_device *dev = alloc_ei_netdev(); 13162306a36Sopenharmony_ci int err; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (!dev) 13462306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci sprintf(dev->name, "eth%d", unit); 13762306a36Sopenharmony_ci netdev_boot_setup_check(dev); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci err = do_wd_probe(dev); 14062306a36Sopenharmony_ci if (err) 14162306a36Sopenharmony_ci goto out; 14262306a36Sopenharmony_ci return dev; 14362306a36Sopenharmony_ciout: 14462306a36Sopenharmony_ci free_netdev(dev); 14562306a36Sopenharmony_ci return ERR_PTR(err); 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci#endif 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic const struct net_device_ops wd_netdev_ops = { 15062306a36Sopenharmony_ci .ndo_open = wd_open, 15162306a36Sopenharmony_ci .ndo_stop = wd_close, 15262306a36Sopenharmony_ci .ndo_start_xmit = ei_start_xmit, 15362306a36Sopenharmony_ci .ndo_tx_timeout = ei_tx_timeout, 15462306a36Sopenharmony_ci .ndo_get_stats = ei_get_stats, 15562306a36Sopenharmony_ci .ndo_set_rx_mode = ei_set_multicast_list, 15662306a36Sopenharmony_ci .ndo_validate_addr = eth_validate_addr, 15762306a36Sopenharmony_ci .ndo_set_mac_address = eth_mac_addr, 15862306a36Sopenharmony_ci#ifdef CONFIG_NET_POLL_CONTROLLER 15962306a36Sopenharmony_ci .ndo_poll_controller = ei_poll, 16062306a36Sopenharmony_ci#endif 16162306a36Sopenharmony_ci}; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int __init wd_probe1(struct net_device *dev, int ioaddr) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci int i; 16662306a36Sopenharmony_ci int err; 16762306a36Sopenharmony_ci int checksum = 0; 16862306a36Sopenharmony_ci int ancient = 0; /* An old card without config registers. */ 16962306a36Sopenharmony_ci int word16 = 0; /* 0 = 8 bit, 1 = 16 bit */ 17062306a36Sopenharmony_ci u8 addr[ETH_ALEN]; 17162306a36Sopenharmony_ci const char *model_name; 17262306a36Sopenharmony_ci static unsigned version_printed; 17362306a36Sopenharmony_ci struct ei_device *ei_local = netdev_priv(dev); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci for (i = 0; i < 8; i++) 17662306a36Sopenharmony_ci checksum += inb(ioaddr + 8 + i); 17762306a36Sopenharmony_ci if (inb(ioaddr + 8) == 0xff /* Extra check to avoid soundcard. */ 17862306a36Sopenharmony_ci || inb(ioaddr + 9) == 0xff 17962306a36Sopenharmony_ci || (checksum & 0xff) != 0xFF) 18062306a36Sopenharmony_ci return -ENODEV; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* Check for semi-valid mem_start/end values if supplied. */ 18362306a36Sopenharmony_ci if ((dev->mem_start % 0x2000) || (dev->mem_end % 0x2000)) { 18462306a36Sopenharmony_ci netdev_warn(dev, 18562306a36Sopenharmony_ci "wd.c: user supplied mem_start or mem_end not on 8kB boundary - ignored.\n"); 18662306a36Sopenharmony_ci dev->mem_start = 0; 18762306a36Sopenharmony_ci dev->mem_end = 0; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if ((wd_msg_enable & NETIF_MSG_DRV) && (version_printed++ == 0)) 19162306a36Sopenharmony_ci netdev_info(dev, version); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci for (i = 0; i < 6; i++) 19462306a36Sopenharmony_ci addr[i] = inb(ioaddr + 8 + i); 19562306a36Sopenharmony_ci eth_hw_addr_set(dev, addr); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci netdev_info(dev, "WD80x3 at %#3x, %pM", ioaddr, dev->dev_addr); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* The following PureData probe code was contributed by 20062306a36Sopenharmony_ci Mike Jagdis <jaggy@purplet.demon.co.uk>. Puredata does software 20162306a36Sopenharmony_ci configuration differently from others so we have to check for them. 20262306a36Sopenharmony_ci This detects an 8 bit, 16 bit or dumb (Toshiba, jumpered) card. 20362306a36Sopenharmony_ci */ 20462306a36Sopenharmony_ci if (inb(ioaddr+0) == 'P' && inb(ioaddr+1) == 'D') { 20562306a36Sopenharmony_ci unsigned char reg5 = inb(ioaddr+5); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci switch (inb(ioaddr+2)) { 20862306a36Sopenharmony_ci case 0x03: word16 = 0; model_name = "PDI8023-8"; break; 20962306a36Sopenharmony_ci case 0x05: word16 = 0; model_name = "PDUC8023"; break; 21062306a36Sopenharmony_ci case 0x0a: word16 = 1; model_name = "PDI8023-16"; break; 21162306a36Sopenharmony_ci /* Either 0x01 (dumb) or they've released a new version. */ 21262306a36Sopenharmony_ci default: word16 = 0; model_name = "PDI8023"; break; 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci dev->mem_start = ((reg5 & 0x1c) + 0xc0) << 12; 21562306a36Sopenharmony_ci dev->irq = (reg5 & 0xe0) == 0xe0 ? 10 : (reg5 >> 5) + 1; 21662306a36Sopenharmony_ci } else { /* End of PureData probe */ 21762306a36Sopenharmony_ci /* This method of checking for a 16-bit board is borrowed from the 21862306a36Sopenharmony_ci we.c driver. A simpler method is just to look in ASIC reg. 0x03. 21962306a36Sopenharmony_ci I'm comparing the two method in alpha test to make certain they 22062306a36Sopenharmony_ci return the same result. */ 22162306a36Sopenharmony_ci /* Check for the old 8 bit board - it has register 0/8 aliasing. 22262306a36Sopenharmony_ci Do NOT check i>=6 here -- it hangs the old 8003 boards! */ 22362306a36Sopenharmony_ci for (i = 0; i < 6; i++) 22462306a36Sopenharmony_ci if (inb(ioaddr+i) != inb(ioaddr+8+i)) 22562306a36Sopenharmony_ci break; 22662306a36Sopenharmony_ci if (i >= 6) { 22762306a36Sopenharmony_ci ancient = 1; 22862306a36Sopenharmony_ci model_name = "WD8003-old"; 22962306a36Sopenharmony_ci word16 = 0; 23062306a36Sopenharmony_ci } else { 23162306a36Sopenharmony_ci int tmp = inb(ioaddr+1); /* fiddle with 16bit bit */ 23262306a36Sopenharmony_ci outb( tmp ^ 0x01, ioaddr+1 ); /* attempt to clear 16bit bit */ 23362306a36Sopenharmony_ci if (((inb( ioaddr+1) & 0x01) == 0x01) /* A 16 bit card */ 23462306a36Sopenharmony_ci && (tmp & 0x01) == 0x01 ) { /* In a 16 slot. */ 23562306a36Sopenharmony_ci int asic_reg5 = inb(ioaddr+WD_CMDREG5); 23662306a36Sopenharmony_ci /* Magic to set ASIC to word-wide mode. */ 23762306a36Sopenharmony_ci outb( NIC16 | (asic_reg5&0x1f), ioaddr+WD_CMDREG5); 23862306a36Sopenharmony_ci outb(tmp, ioaddr+1); 23962306a36Sopenharmony_ci model_name = "WD8013"; 24062306a36Sopenharmony_ci word16 = 1; /* We have a 16bit board here! */ 24162306a36Sopenharmony_ci } else { 24262306a36Sopenharmony_ci model_name = "WD8003"; 24362306a36Sopenharmony_ci word16 = 0; 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci outb(tmp, ioaddr+1); /* Restore original reg1 value. */ 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci#ifndef final_version 24862306a36Sopenharmony_ci if ( !ancient && (inb(ioaddr+1) & 0x01) != (word16 & 0x01)) 24962306a36Sopenharmony_ci pr_cont("\nWD80?3: Bus width conflict, %d (probe) != %d (reg report).", 25062306a36Sopenharmony_ci word16 ? 16 : 8, 25162306a36Sopenharmony_ci (inb(ioaddr+1) & 0x01) ? 16 : 8); 25262306a36Sopenharmony_ci#endif 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci#if defined(WD_SHMEM) && WD_SHMEM > 0x80000 25662306a36Sopenharmony_ci /* Allow a compile-time override. */ 25762306a36Sopenharmony_ci dev->mem_start = WD_SHMEM; 25862306a36Sopenharmony_ci#else 25962306a36Sopenharmony_ci if (dev->mem_start == 0) { 26062306a36Sopenharmony_ci /* Sanity and old 8003 check */ 26162306a36Sopenharmony_ci int reg0 = inb(ioaddr); 26262306a36Sopenharmony_ci if (reg0 == 0xff || reg0 == 0) { 26362306a36Sopenharmony_ci /* Future plan: this could check a few likely locations first. */ 26462306a36Sopenharmony_ci dev->mem_start = 0xd0000; 26562306a36Sopenharmony_ci pr_cont(" assigning address %#lx", dev->mem_start); 26662306a36Sopenharmony_ci } else { 26762306a36Sopenharmony_ci int high_addr_bits = inb(ioaddr+WD_CMDREG5) & 0x1f; 26862306a36Sopenharmony_ci /* Some boards don't have the register 5 -- it returns 0xff. */ 26962306a36Sopenharmony_ci if (high_addr_bits == 0x1f || word16 == 0) 27062306a36Sopenharmony_ci high_addr_bits = 0x01; 27162306a36Sopenharmony_ci dev->mem_start = ((reg0&0x3f) << 13) + (high_addr_bits << 19); 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci#endif 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci /* The 8390 isn't at the base address -- the ASIC regs are there! */ 27762306a36Sopenharmony_ci dev->base_addr = ioaddr+WD_NIC_OFFSET; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (dev->irq < 2) { 28062306a36Sopenharmony_ci static const int irqmap[] = {9, 3, 5, 7, 10, 11, 15, 4}; 28162306a36Sopenharmony_ci int reg1 = inb(ioaddr+1); 28262306a36Sopenharmony_ci int reg4 = inb(ioaddr+4); 28362306a36Sopenharmony_ci if (ancient || reg1 == 0xff) { /* Ack!! No way to read the IRQ! */ 28462306a36Sopenharmony_ci short nic_addr = ioaddr+WD_NIC_OFFSET; 28562306a36Sopenharmony_ci unsigned long irq_mask; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* We have an old-style ethercard that doesn't report its IRQ 28862306a36Sopenharmony_ci line. Do autoirq to find the IRQ line. Note that this IS NOT 28962306a36Sopenharmony_ci a reliable way to trigger an interrupt. */ 29062306a36Sopenharmony_ci outb_p(E8390_NODMA + E8390_STOP, nic_addr); 29162306a36Sopenharmony_ci outb(0x00, nic_addr+EN0_IMR); /* Disable all intrs. */ 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci irq_mask = probe_irq_on(); 29462306a36Sopenharmony_ci outb_p(0xff, nic_addr + EN0_IMR); /* Enable all interrupts. */ 29562306a36Sopenharmony_ci outb_p(0x00, nic_addr + EN0_RCNTLO); 29662306a36Sopenharmony_ci outb_p(0x00, nic_addr + EN0_RCNTHI); 29762306a36Sopenharmony_ci outb(E8390_RREAD+E8390_START, nic_addr); /* Trigger it... */ 29862306a36Sopenharmony_ci mdelay(20); 29962306a36Sopenharmony_ci dev->irq = probe_irq_off(irq_mask); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci outb_p(0x00, nic_addr+EN0_IMR); /* Mask all intrs. again. */ 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (wd_msg_enable & NETIF_MSG_PROBE) 30462306a36Sopenharmony_ci pr_cont(" autoirq is %d", dev->irq); 30562306a36Sopenharmony_ci if (dev->irq < 2) 30662306a36Sopenharmony_ci dev->irq = word16 ? 10 : 5; 30762306a36Sopenharmony_ci } else 30862306a36Sopenharmony_ci dev->irq = irqmap[((reg4 >> 5) & 0x03) + (reg1 & 0x04)]; 30962306a36Sopenharmony_ci } else if (dev->irq == 2) /* Fixup bogosity: IRQ2 is really IRQ9 */ 31062306a36Sopenharmony_ci dev->irq = 9; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* Snarf the interrupt now. There's no point in waiting since we cannot 31362306a36Sopenharmony_ci share and the board will usually be enabled. */ 31462306a36Sopenharmony_ci i = request_irq(dev->irq, ei_interrupt, 0, DRV_NAME, dev); 31562306a36Sopenharmony_ci if (i) { 31662306a36Sopenharmony_ci pr_cont(" unable to get IRQ %d.\n", dev->irq); 31762306a36Sopenharmony_ci return i; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci /* OK, were are certain this is going to work. Setup the device. */ 32162306a36Sopenharmony_ci ei_status.name = model_name; 32262306a36Sopenharmony_ci ei_status.word16 = word16; 32362306a36Sopenharmony_ci ei_status.tx_start_page = WD_START_PG; 32462306a36Sopenharmony_ci ei_status.rx_start_page = WD_START_PG + TX_PAGES; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci /* Don't map in the shared memory until the board is actually opened. */ 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci /* Some cards (eg WD8003EBT) can be jumpered for more (32k!) memory. */ 32962306a36Sopenharmony_ci if (dev->mem_end != 0) { 33062306a36Sopenharmony_ci ei_status.stop_page = (dev->mem_end - dev->mem_start)/256; 33162306a36Sopenharmony_ci ei_status.priv = dev->mem_end - dev->mem_start; 33262306a36Sopenharmony_ci } else { 33362306a36Sopenharmony_ci ei_status.stop_page = word16 ? WD13_STOP_PG : WD03_STOP_PG; 33462306a36Sopenharmony_ci dev->mem_end = dev->mem_start + (ei_status.stop_page - WD_START_PG)*256; 33562306a36Sopenharmony_ci ei_status.priv = (ei_status.stop_page - WD_START_PG)*256; 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci ei_status.mem = ioremap(dev->mem_start, ei_status.priv); 33962306a36Sopenharmony_ci if (!ei_status.mem) { 34062306a36Sopenharmony_ci free_irq(dev->irq, dev); 34162306a36Sopenharmony_ci return -ENOMEM; 34262306a36Sopenharmony_ci } 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci pr_cont(" %s, IRQ %d, shared memory at %#lx-%#lx.\n", 34562306a36Sopenharmony_ci model_name, dev->irq, dev->mem_start, dev->mem_end-1); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci ei_status.reset_8390 = wd_reset_8390; 34862306a36Sopenharmony_ci ei_status.block_input = wd_block_input; 34962306a36Sopenharmony_ci ei_status.block_output = wd_block_output; 35062306a36Sopenharmony_ci ei_status.get_8390_hdr = wd_get_8390_hdr; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci dev->netdev_ops = &wd_netdev_ops; 35362306a36Sopenharmony_ci NS8390_init(dev, 0); 35462306a36Sopenharmony_ci ei_local->msg_enable = wd_msg_enable; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci#if 1 35762306a36Sopenharmony_ci /* Enable interrupt generation on softconfig cards -- M.U */ 35862306a36Sopenharmony_ci /* .. but possibly potentially unsafe - Donald */ 35962306a36Sopenharmony_ci if (inb(ioaddr+14) & 0x20) 36062306a36Sopenharmony_ci outb(inb(ioaddr+4)|0x80, ioaddr+4); 36162306a36Sopenharmony_ci#endif 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci err = register_netdev(dev); 36462306a36Sopenharmony_ci if (err) { 36562306a36Sopenharmony_ci free_irq(dev->irq, dev); 36662306a36Sopenharmony_ci iounmap(ei_status.mem); 36762306a36Sopenharmony_ci } 36862306a36Sopenharmony_ci return err; 36962306a36Sopenharmony_ci} 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_cistatic int 37262306a36Sopenharmony_ciwd_open(struct net_device *dev) 37362306a36Sopenharmony_ci{ 37462306a36Sopenharmony_ci int ioaddr = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci /* Map in the shared memory. Always set register 0 last to remain 37762306a36Sopenharmony_ci compatible with very old boards. */ 37862306a36Sopenharmony_ci ei_status.reg0 = ((dev->mem_start>>13) & 0x3f) | WD_MEMENB; 37962306a36Sopenharmony_ci ei_status.reg5 = ((dev->mem_start>>19) & 0x1f) | NIC16; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci if (ei_status.word16) 38262306a36Sopenharmony_ci outb(ei_status.reg5, ioaddr+WD_CMDREG5); 38362306a36Sopenharmony_ci outb(ei_status.reg0, ioaddr); /* WD_CMDREG */ 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci return ei_open(dev); 38662306a36Sopenharmony_ci} 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_cistatic void 38962306a36Sopenharmony_ciwd_reset_8390(struct net_device *dev) 39062306a36Sopenharmony_ci{ 39162306a36Sopenharmony_ci int wd_cmd_port = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ 39262306a36Sopenharmony_ci struct ei_device *ei_local = netdev_priv(dev); 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci outb(WD_RESET, wd_cmd_port); 39562306a36Sopenharmony_ci netif_dbg(ei_local, hw, dev, "resetting the WD80x3 t=%lu...\n", 39662306a36Sopenharmony_ci jiffies); 39762306a36Sopenharmony_ci ei_status.txing = 0; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci /* Set up the ASIC registers, just in case something changed them. */ 40062306a36Sopenharmony_ci outb((((dev->mem_start>>13) & 0x3f)|WD_MEMENB), wd_cmd_port); 40162306a36Sopenharmony_ci if (ei_status.word16) 40262306a36Sopenharmony_ci outb(NIC16 | ((dev->mem_start>>19) & 0x1f), wd_cmd_port+WD_CMDREG5); 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci netif_dbg(ei_local, hw, dev, "reset done\n"); 40562306a36Sopenharmony_ci} 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci/* Grab the 8390 specific header. Similar to the block_input routine, but 40862306a36Sopenharmony_ci we don't need to be concerned with ring wrap as the header will be at 40962306a36Sopenharmony_ci the start of a page, so we optimize accordingly. */ 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_cistatic void 41262306a36Sopenharmony_ciwd_get_8390_hdr(struct net_device *dev, struct e8390_pkt_hdr *hdr, int ring_page) 41362306a36Sopenharmony_ci{ 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ 41662306a36Sopenharmony_ci void __iomem *hdr_start = ei_status.mem + ((ring_page - WD_START_PG)<<8); 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci /* We'll always get a 4 byte header read followed by a packet read, so 41962306a36Sopenharmony_ci we enable 16 bit mode before the header, and disable after the body. */ 42062306a36Sopenharmony_ci if (ei_status.word16) 42162306a36Sopenharmony_ci outb(ISA16 | ei_status.reg5, wd_cmdreg+WD_CMDREG5); 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci#ifdef __BIG_ENDIAN 42462306a36Sopenharmony_ci /* Officially this is what we are doing, but the readl() is faster */ 42562306a36Sopenharmony_ci /* unfortunately it isn't endian aware of the struct */ 42662306a36Sopenharmony_ci memcpy_fromio(hdr, hdr_start, sizeof(struct e8390_pkt_hdr)); 42762306a36Sopenharmony_ci hdr->count = le16_to_cpu(hdr->count); 42862306a36Sopenharmony_ci#else 42962306a36Sopenharmony_ci ((unsigned int*)hdr)[0] = readl(hdr_start); 43062306a36Sopenharmony_ci#endif 43162306a36Sopenharmony_ci} 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci/* Block input and output are easy on shared memory ethercards, and trivial 43462306a36Sopenharmony_ci on the Western digital card where there is no choice of how to do it. 43562306a36Sopenharmony_ci The only complications are that the ring buffer wraps, and need to map 43662306a36Sopenharmony_ci switch between 8- and 16-bit modes. */ 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_cistatic void 43962306a36Sopenharmony_ciwd_block_input(struct net_device *dev, int count, struct sk_buff *skb, int ring_offset) 44062306a36Sopenharmony_ci{ 44162306a36Sopenharmony_ci int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ 44262306a36Sopenharmony_ci unsigned long offset = ring_offset - (WD_START_PG<<8); 44362306a36Sopenharmony_ci void __iomem *xfer_start = ei_status.mem + offset; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci if (offset + count > ei_status.priv) { 44662306a36Sopenharmony_ci /* We must wrap the input move. */ 44762306a36Sopenharmony_ci int semi_count = ei_status.priv - offset; 44862306a36Sopenharmony_ci memcpy_fromio(skb->data, xfer_start, semi_count); 44962306a36Sopenharmony_ci count -= semi_count; 45062306a36Sopenharmony_ci memcpy_fromio(skb->data + semi_count, ei_status.mem + TX_PAGES * 256, count); 45162306a36Sopenharmony_ci } else { 45262306a36Sopenharmony_ci /* Packet is in one chunk -- we can copy + cksum. */ 45362306a36Sopenharmony_ci memcpy_fromio(skb->data, xfer_start, count); 45462306a36Sopenharmony_ci } 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci /* Turn off 16 bit access so that reboot works. ISA brain-damage */ 45762306a36Sopenharmony_ci if (ei_status.word16) 45862306a36Sopenharmony_ci outb(ei_status.reg5, wd_cmdreg+WD_CMDREG5); 45962306a36Sopenharmony_ci} 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_cistatic void 46262306a36Sopenharmony_ciwd_block_output(struct net_device *dev, int count, const unsigned char *buf, 46362306a36Sopenharmony_ci int start_page) 46462306a36Sopenharmony_ci{ 46562306a36Sopenharmony_ci int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ 46662306a36Sopenharmony_ci void __iomem *shmem = ei_status.mem + ((start_page - WD_START_PG)<<8); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci if (ei_status.word16) { 47062306a36Sopenharmony_ci /* Turn on and off 16 bit access so that reboot works. */ 47162306a36Sopenharmony_ci outb(ISA16 | ei_status.reg5, wd_cmdreg+WD_CMDREG5); 47262306a36Sopenharmony_ci memcpy_toio(shmem, buf, count); 47362306a36Sopenharmony_ci outb(ei_status.reg5, wd_cmdreg+WD_CMDREG5); 47462306a36Sopenharmony_ci } else 47562306a36Sopenharmony_ci memcpy_toio(shmem, buf, count); 47662306a36Sopenharmony_ci} 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_cistatic int 48062306a36Sopenharmony_ciwd_close(struct net_device *dev) 48162306a36Sopenharmony_ci{ 48262306a36Sopenharmony_ci int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */ 48362306a36Sopenharmony_ci struct ei_device *ei_local = netdev_priv(dev); 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci netif_dbg(ei_local, ifdown, dev, "Shutting down ethercard.\n"); 48662306a36Sopenharmony_ci ei_close(dev); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci /* Change from 16-bit to 8-bit shared memory so reboot works. */ 48962306a36Sopenharmony_ci if (ei_status.word16) 49062306a36Sopenharmony_ci outb(ei_status.reg5, wd_cmdreg + WD_CMDREG5 ); 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci /* And disable the shared memory. */ 49362306a36Sopenharmony_ci outb(ei_status.reg0 & ~WD_MEMENB, wd_cmdreg); 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci return 0; 49662306a36Sopenharmony_ci} 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci#ifdef MODULE 50062306a36Sopenharmony_ci#define MAX_WD_CARDS 4 /* Max number of wd cards per module */ 50162306a36Sopenharmony_cistatic struct net_device *dev_wd[MAX_WD_CARDS]; 50262306a36Sopenharmony_cistatic int io[MAX_WD_CARDS]; 50362306a36Sopenharmony_cistatic int irq[MAX_WD_CARDS]; 50462306a36Sopenharmony_cistatic int mem[MAX_WD_CARDS]; 50562306a36Sopenharmony_cistatic int mem_end[MAX_WD_CARDS]; /* for non std. mem size */ 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_cimodule_param_hw_array(io, int, ioport, NULL, 0); 50862306a36Sopenharmony_cimodule_param_hw_array(irq, int, irq, NULL, 0); 50962306a36Sopenharmony_cimodule_param_hw_array(mem, int, iomem, NULL, 0); 51062306a36Sopenharmony_cimodule_param_hw_array(mem_end, int, iomem, NULL, 0); 51162306a36Sopenharmony_cimodule_param_named(msg_enable, wd_msg_enable, uint, 0444); 51262306a36Sopenharmony_ciMODULE_PARM_DESC(io, "I/O base address(es)"); 51362306a36Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ number(s) (ignored for PureData boards)"); 51462306a36Sopenharmony_ciMODULE_PARM_DESC(mem, "memory base address(es)(ignored for PureData boards)"); 51562306a36Sopenharmony_ciMODULE_PARM_DESC(mem_end, "memory end address(es)"); 51662306a36Sopenharmony_ciMODULE_PARM_DESC(msg_enable, "Debug message level (see linux/netdevice.h for bitmap)"); 51762306a36Sopenharmony_ciMODULE_DESCRIPTION("ISA Western Digital wd8003/wd8013 ; SMC Elite, Elite16 ethernet driver"); 51862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci/* This is set up so that only a single autoprobe takes place per call. 52162306a36Sopenharmony_ciISA device autoprobes on a running machine are not recommended. */ 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_cistatic int __init wd_init_module(void) 52462306a36Sopenharmony_ci{ 52562306a36Sopenharmony_ci struct net_device *dev; 52662306a36Sopenharmony_ci int this_dev, found = 0; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci for (this_dev = 0; this_dev < MAX_WD_CARDS; this_dev++) { 52962306a36Sopenharmony_ci if (io[this_dev] == 0) { 53062306a36Sopenharmony_ci if (this_dev != 0) break; /* only autoprobe 1st one */ 53162306a36Sopenharmony_ci printk(KERN_NOTICE "wd.c: Presently autoprobing (not recommended) for a single card.\n"); 53262306a36Sopenharmony_ci } 53362306a36Sopenharmony_ci dev = alloc_ei_netdev(); 53462306a36Sopenharmony_ci if (!dev) 53562306a36Sopenharmony_ci break; 53662306a36Sopenharmony_ci dev->irq = irq[this_dev]; 53762306a36Sopenharmony_ci dev->base_addr = io[this_dev]; 53862306a36Sopenharmony_ci dev->mem_start = mem[this_dev]; 53962306a36Sopenharmony_ci dev->mem_end = mem_end[this_dev]; 54062306a36Sopenharmony_ci if (do_wd_probe(dev) == 0) { 54162306a36Sopenharmony_ci dev_wd[found++] = dev; 54262306a36Sopenharmony_ci continue; 54362306a36Sopenharmony_ci } 54462306a36Sopenharmony_ci free_netdev(dev); 54562306a36Sopenharmony_ci printk(KERN_WARNING "wd.c: No wd80x3 card found (i/o = 0x%x).\n", io[this_dev]); 54662306a36Sopenharmony_ci break; 54762306a36Sopenharmony_ci } 54862306a36Sopenharmony_ci if (found) 54962306a36Sopenharmony_ci return 0; 55062306a36Sopenharmony_ci return -ENXIO; 55162306a36Sopenharmony_ci} 55262306a36Sopenharmony_cimodule_init(wd_init_module); 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_cistatic void cleanup_card(struct net_device *dev) 55562306a36Sopenharmony_ci{ 55662306a36Sopenharmony_ci free_irq(dev->irq, dev); 55762306a36Sopenharmony_ci release_region(dev->base_addr - WD_NIC_OFFSET, WD_IO_EXTENT); 55862306a36Sopenharmony_ci iounmap(ei_status.mem); 55962306a36Sopenharmony_ci} 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_cistatic void __exit wd_cleanup_module(void) 56262306a36Sopenharmony_ci{ 56362306a36Sopenharmony_ci int this_dev; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci for (this_dev = 0; this_dev < MAX_WD_CARDS; this_dev++) { 56662306a36Sopenharmony_ci struct net_device *dev = dev_wd[this_dev]; 56762306a36Sopenharmony_ci if (dev) { 56862306a36Sopenharmony_ci unregister_netdev(dev); 56962306a36Sopenharmony_ci cleanup_card(dev); 57062306a36Sopenharmony_ci free_netdev(dev); 57162306a36Sopenharmony_ci } 57262306a36Sopenharmony_ci } 57362306a36Sopenharmony_ci} 57462306a36Sopenharmony_cimodule_exit(wd_cleanup_module); 57562306a36Sopenharmony_ci#endif /* MODULE */ 576