root/drivers/net/wd.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. wd_probe
  2. wd_probe1
  3. wd_open
  4. wd_reset_8390
  5. wd_block_input
  6. wd_block_output
  7. wd_close_card
  8. init_module
  9. cleanup_module

   1 /* wd.c: A WD80x3 ethernet driver for linux. */
   2 /*
   3         Written 1993-94 by Donald Becker.
   4 
   5         Copyright 1993 United States Government as represented by the
   6         Director, National Security Agency.
   7 
   8         This software may be used and distributed according to the terms
   9         of the GNU Public License, incorporated herein by reference.
  10 
  11         The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O
  12         Center of Excellence in Space Data and Information Sciences
  13            Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771
  14 
  15         This is a driver for WD8003 and WD8013 "compatible" ethercards.
  16 
  17         Thanks to Russ Nelson (nelson@crnwyr.com) for loaning me a WD8013.
  18 */
  19 
  20 static const char *version =
  21         "wd.c:v1.10 9/23/94 Donald Becker (becker@cesdis.gsfc.nasa.gov)\n";
  22 
  23 #ifdef MODULE
  24 #include <linux/module.h>
  25 #include <linux/version.h>
  26 #endif
  27 
  28 #include <linux/kernel.h>
  29 #include <linux/sched.h>
  30 #include <linux/errno.h>
  31 #include <linux/string.h>
  32 #include <asm/io.h>
  33 #include <asm/system.h>
  34 
  35 #include <linux/netdevice.h>
  36 #include <linux/etherdevice.h>
  37 #include "8390.h"
  38 
  39 /* A zero-terminated list of I/O addresses to be probed. */
  40 static unsigned int wd_portlist[] =
  41 {0x300, 0x280, 0x380, 0x240, 0};
  42 
  43 int wd_probe(struct device *dev);
  44 int wd_probe1(struct device *dev, int ioaddr);
  45 
  46 static int wd_open(struct device *dev);
  47 static void wd_reset_8390(struct device *dev);
  48 static int wd_block_input(struct device *dev, int count,
  49                                                   char *buf, int ring_offset);
  50 static void wd_block_output(struct device *dev, int count,
  51                                                         const unsigned char *buf, const start_page);
  52 static int wd_close_card(struct device *dev);
  53 
  54 
  55 #define WD_START_PG             0x00    /* First page of TX buffer */
  56 #define WD03_STOP_PG    0x20    /* Last page +1 of RX ring */
  57 #define WD13_STOP_PG    0x40    /* Last page +1 of RX ring */
  58 
  59 #define WD_CMDREG               0               /* Offset to ASIC command register. */
  60 #define  WD_RESET               0x80    /* Board reset, in WD_CMDREG. */
  61 #define  WD_MEMENB              0x40    /* Enable the shared memory. */
  62 #define WD_CMDREG5              5               /* Offset to 16-bit-only ASIC register 5. */
  63 #define  ISA16                  0x80    /* Enable 16 bit access from the ISA bus. */
  64 #define  NIC16                  0x40    /* Enable 16 bit access from the 8390. */
  65 #define WD_NIC_OFFSET   16              /* Offset to the 8390 from the base_addr. */
  66 #define WD_IO_EXTENT    32
  67 
  68 
  69 /*      Probe for the WD8003 and WD8013.  These cards have the station
  70         address PROM at I/O ports <base>+8 to <base>+13, with a checksum
  71         following. A Soundblaster can have the same checksum as an WDethercard,
  72         so we have an extra exclusionary check for it.
  73 
  74         The wd_probe1() routine initializes the card and fills the
  75         station address field. */
  76 
  77 #ifdef HAVE_DEVLIST
  78 struct netdev_entry wd_drv =
  79 {"wd", wd_probe1, WD_IO_EXTENT, wd_portlist};
  80 #else
  81 
  82 int wd_probe(struct device *dev)
     /* [previous][next][first][last][top][bottom][index][help] */
  83 {
  84         int i;
  85         int base_addr = dev ? dev->base_addr : 0;
  86 
  87         if (base_addr > 0x1ff)          /* Check a single specified location. */
  88                 return wd_probe1(dev, base_addr);
  89         else if (base_addr != 0)        /* Don't probe at all. */
  90                 return ENXIO;
  91 
  92         for (i = 0; wd_portlist[i]; i++) {
  93                 int ioaddr = wd_portlist[i];
  94                 if (check_region(ioaddr, WD_IO_EXTENT))
  95                         continue;
  96                 if (wd_probe1(dev, ioaddr) == 0)
  97                         return 0;
  98         }
  99 
 100         return ENODEV;
 101 }
 102 #endif
 103 
 104 int wd_probe1(struct device *dev, int ioaddr)
     /* [previous][next][first][last][top][bottom][index][help] */
 105 {
 106         int i;
 107         int checksum = 0;
 108         int ancient = 0;                        /* An old card without config registers. */
 109         int word16 = 0;                         /* 0 = 8 bit, 1 = 16 bit */
 110         const char *model_name;
 111 
 112         for (i = 0; i < 8; i++)
 113                 checksum += inb(ioaddr + 8 + i);
 114         if (inb(ioaddr + 8) == 0xff     /* Extra check to avoid soundcard. */
 115                 || inb(ioaddr + 9) == 0xff
 116                 || (checksum & 0xff) != 0xFF)
 117                 return ENODEV;
 118 
 119         if (dev == NULL)
 120                 dev = init_etherdev(0, sizeof(struct ei_device), 0);
 121 
 122         printk("%s: WD80x3 at %#3x, ", dev->name, ioaddr);
 123         for (i = 0; i < 6; i++)
 124                 printk(" %2.2X", dev->dev_addr[i] = inb(ioaddr + 8 + i));
 125 
 126         /* The following PureData probe code was contributed by
 127            Mike Jagdis <jaggy@purplet.demon.co.uk>. Puredata does software
 128            configuration differently from others so we have to check for them.
 129            This detects an 8 bit, 16 bit or dumb (Toshiba, jumpered) card.
 130            */
 131         if (inb(ioaddr+0) == 'P' && inb(ioaddr+1) == 'D') {
 132                 unsigned char reg5 = inb(ioaddr+5);
 133 
 134                 switch (inb(ioaddr+2)) {
 135                 case 0x03: word16 = 0; model_name = "PDI8023-8";        break;
 136                 case 0x05: word16 = 0; model_name = "PDUC8023"; break;
 137                 case 0x0a: word16 = 1; model_name = "PDI8023-16"; break;
 138                         /* Either 0x01 (dumb) or they've released a new version. */
 139                 default:         word16 = 0; model_name = "PDI8023";    break;
 140                 }
 141                 dev->mem_start = ((reg5 & 0x1c) + 0xc0) << 12;
 142                 dev->irq = (reg5 & 0xe0) == 0xe0 ? 10 : (reg5 >> 5) + 1;
 143         } else {                                                                /* End of PureData probe */
 144                 /* This method of checking for a 16-bit board is borrowed from the
 145                    we.c driver.  A simpler method is just to look in ASIC reg. 0x03.
 146                    I'm comparing the two method in alpha test to make certain they
 147                    return the same result. */
 148                 /* Check for the old 8 bit board - it has register 0/8 aliasing.
 149                    Do NOT check i>=6 here -- it hangs the old 8003 boards! */
 150                 for (i = 0; i < 6; i++)
 151                         if (inb(ioaddr+i) != inb(ioaddr+8+i))
 152                                 break;
 153                 if (i >= 6) {
 154                         ancient = 1;
 155                         model_name = "WD8003-old";
 156                         word16 = 0;
 157                 } else {
 158                         int tmp = inb(ioaddr+1); /* fiddle with 16bit bit */
 159                         outb( tmp ^ 0x01, ioaddr+1 ); /* attempt to clear 16bit bit */
 160                         if (((inb( ioaddr+1) & 0x01) == 0x01) /* A 16 bit card */
 161                                 && (tmp & 0x01) == 0x01 ) {                             /* In a 16 slot. */
 162                                 int asic_reg5 = inb(ioaddr+WD_CMDREG5);
 163                                 /* Magic to set ASIC to word-wide mode. */
 164                                 outb( NIC16 | (asic_reg5&0x1f), ioaddr+WD_CMDREG5);
 165                                 outb(tmp, ioaddr+1);
 166                                 model_name = "WD8013";
 167                                 word16 = 1;             /* We have a 16bit board here! */
 168                         } else {
 169                                 model_name = "WD8003";
 170                                 word16 = 0;
 171                         }
 172                         outb(tmp, ioaddr+1);                    /* Restore original reg1 value. */
 173                 }
 174 #ifndef final_version
 175                 if ( !ancient && (inb(ioaddr+1) & 0x01) != (word16 & 0x01))
 176                         printk("\nWD80?3: Bus width conflict, %d (probe) != %d (reg report).",
 177                                    word16 ? 16 : 8, (inb(ioaddr+1) & 0x01) ? 16 : 8);
 178 #endif
 179         }
 180 
 181 #if defined(WD_SHMEM) && WD_SHMEM > 0x80000
 182         /* Allow a compile-time override.        */
 183         dev->mem_start = WD_SHMEM;
 184 #else
 185         if (dev->mem_start == 0) {
 186                 /* Sanity and old 8003 check */
 187                 int reg0 = inb(ioaddr);
 188                 if (reg0 == 0xff || reg0 == 0) {
 189                         /* Future plan: this could check a few likely locations first. */
 190                         dev->mem_start = 0xd0000;
 191                         printk(" assigning address %#lx", dev->mem_start);
 192                 } else {
 193                         int high_addr_bits = inb(ioaddr+WD_CMDREG5) & 0x1f;
 194                         /* Some boards don't have the register 5 -- it returns 0xff. */
 195                         if (high_addr_bits == 0x1f || word16 == 0)
 196                                 high_addr_bits = 0x01;
 197                         dev->mem_start = ((reg0&0x3f) << 13) + (high_addr_bits << 19);
 198                 }
 199         }
 200 #endif
 201 
 202         /* The 8390 isn't at the base address -- the ASIC regs are there! */
 203         dev->base_addr = ioaddr+WD_NIC_OFFSET;
 204 
 205         if (dev->irq < 2) {
 206                 int irqmap[] = {9,3,5,7,10,11,15,4};
 207                 int reg1 = inb(ioaddr+1);
 208                 int reg4 = inb(ioaddr+4);
 209                 if (ancient || reg1 == 0xff) {  /* Ack!! No way to read the IRQ! */
 210                         short nic_addr = ioaddr+WD_NIC_OFFSET;
 211 
 212                         /* We have an old-style ethercard that doesn't report its IRQ
 213                            line.  Do autoirq to find the IRQ line. Note that this IS NOT
 214                            a reliable way to trigger an interrupt. */
 215                         outb_p(E8390_NODMA + E8390_STOP, nic_addr);
 216                         outb(0x00, nic_addr+EN0_IMR);   /* Disable all intrs. */
 217                         autoirq_setup(0);
 218                         outb_p(0xff, nic_addr + EN0_IMR);       /* Enable all interrupts. */
 219                         outb_p(0x00, nic_addr + EN0_RCNTLO);
 220                         outb_p(0x00, nic_addr + EN0_RCNTHI);
 221                         outb(E8390_RREAD+E8390_START, nic_addr); /* Trigger it... */
 222                         dev->irq = autoirq_report(2);
 223                         outb_p(0x00, nic_addr+EN0_IMR); /* Mask all intrs. again. */
 224 
 225                         if (ei_debug > 2)
 226                                 printk(" autoirq is %d", dev->irq);
 227                         if (dev->irq < 2)
 228                                 dev->irq = word16 ? 10 : 5;
 229                 } else
 230                         dev->irq = irqmap[((reg4 >> 5) & 0x03) + (reg1 & 0x04)];
 231         } else if (dev->irq == 2)               /* Fixup bogosity: IRQ2 is really IRQ9 */
 232                 dev->irq = 9;
 233 
 234         /* Snarf the interrupt now.  There's no point in waiting since we cannot
 235            share and the board will usually be enabled. */
 236         if (request_irq(dev->irq, ei_interrupt, 0, "wd")) {
 237                 printk (" unable to get IRQ %d.\n", dev->irq);
 238                 return EAGAIN;
 239         }
 240 
 241         /* OK, were are certain this is going to work.  Setup the device. */
 242         request_region(ioaddr, WD_IO_EXTENT,"wd");
 243         ethdev_init(dev);
 244 
 245         ei_status.name = model_name;
 246         ei_status.word16 = word16;
 247         ei_status.tx_start_page = WD_START_PG;
 248         ei_status.rx_start_page = WD_START_PG + TX_PAGES;
 249         ei_status.stop_page = word16 ? WD13_STOP_PG : WD03_STOP_PG;
 250 
 251         /* Don't map in the shared memory until the board is actually opened. */
 252         dev->rmem_start = dev->mem_start + TX_PAGES*256;
 253         dev->mem_end = dev->rmem_end
 254                 = dev->mem_start + (ei_status.stop_page - WD_START_PG)*256;
 255 
 256         printk(" %s, IRQ %d, shared memory at %#lx-%#lx.\n",
 257                    model_name, dev->irq, dev->mem_start, dev->mem_end-1);
 258         if (ei_debug > 0)
 259                 printk(version);
 260 
 261         ei_status.reset_8390 = &wd_reset_8390;
 262         ei_status.block_input = &wd_block_input;
 263         ei_status.block_output = &wd_block_output;
 264         dev->open = &wd_open;
 265         dev->stop = &wd_close_card;
 266         NS8390_init(dev, 0);
 267 
 268 #if 1
 269         /* Enable interrupt generation on softconfig cards -- M.U */
 270         /* .. but possibly potentially unsafe - Donald */
 271         if (inb(ioaddr+14) & 0x20)
 272                 outb(inb(ioaddr+4)|0x80, ioaddr+4);
 273 #endif
 274 
 275         return 0;
 276 }
 277 
 278 static int
 279 wd_open(struct device *dev)
     /* [previous][next][first][last][top][bottom][index][help] */
 280 {
 281   int ioaddr = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */
 282   int rc;
 283 
 284   /* Map in the shared memory. Always set register 0 last to remain
 285          compatible with very old boards. */
 286   ei_status.reg0 = ((dev->mem_start>>13) & 0x3f) | WD_MEMENB;
 287   ei_status.reg5 = ((dev->mem_start>>19) & 0x1f) | NIC16;
 288 
 289   if (ei_status.word16)
 290           outb(ei_status.reg5, ioaddr+WD_CMDREG5);
 291   outb(ei_status.reg0, ioaddr); /* WD_CMDREG */
 292 
 293   rc = ei_open(dev);
 294   if (rc != 0) return rc;
 295 #ifdef MODULE
 296   MOD_INC_USE_COUNT;
 297 #endif
 298   return 0;
 299 }
 300 
 301 static void
 302 wd_reset_8390(struct device *dev)
     /* [previous][next][first][last][top][bottom][index][help] */
 303 {
 304         int wd_cmd_port = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */
 305 
 306         outb(WD_RESET, wd_cmd_port);
 307         if (ei_debug > 1) printk("resetting the WD80x3 t=%lu...", jiffies);
 308         ei_status.txing = 0;
 309 
 310         /* Set up the ASIC registers, just in case something changed them. */
 311         outb((((dev->mem_start>>13) & 0x3f)|WD_MEMENB), wd_cmd_port);
 312         if (ei_status.word16)
 313                 outb(NIC16 | ((dev->mem_start>>19) & 0x1f), wd_cmd_port+WD_CMDREG5);
 314 
 315         if (ei_debug > 1) printk("reset done\n");
 316         return;
 317 }
 318 
 319 /* Block input and output are easy on shared memory ethercards, and trivial
 320    on the Western digital card where there is no choice of how to do it.
 321    The only complications are that the ring buffer wraps, and need to map
 322    switch between 8- and 16-bit modes. */
 323 
 324 static int
 325 wd_block_input(struct device *dev, int count, char *buf, int ring_offset)
     /* [previous][next][first][last][top][bottom][index][help] */
 326 {
 327         int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */
 328         long xfer_start = dev->mem_start + ring_offset - (WD_START_PG<<8);
 329 
 330         /* We'll always get a 4 byte header read followed by a packet read, so
 331            we enable 16 bit mode before the header, and disable after the body. */
 332         if (count == 4) {
 333                 if (ei_status.word16)
 334                         outb(ISA16 | ei_status.reg5, wd_cmdreg+WD_CMDREG5);
 335                 ((int*)buf)[0] = ((int*)xfer_start)[0];
 336                 return 0;
 337         }
 338 
 339         if (xfer_start + count > dev->rmem_end) {
 340                 /* We must wrap the input move. */
 341                 int semi_count = dev->rmem_end - xfer_start;
 342                 memcpy(buf, (char *)xfer_start, semi_count);
 343                 count -= semi_count;
 344                 memcpy(buf + semi_count, (char *)dev->rmem_start, count);
 345         } else
 346                 memcpy(buf, (char *)xfer_start, count);
 347 
 348         /* Turn off 16 bit access so that reboot works.  ISA brain-damage */
 349         if (ei_status.word16)
 350                 outb(ei_status.reg5, wd_cmdreg+WD_CMDREG5);
 351 
 352         return 0;
 353 }
 354 
 355 static void
 356 wd_block_output(struct device *dev, int count, const unsigned char *buf,
     /* [previous][next][first][last][top][bottom][index][help] */
 357                                 int start_page)
 358 {
 359         int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */
 360         long shmem = dev->mem_start + ((start_page - WD_START_PG)<<8);
 361 
 362 
 363         if (ei_status.word16) {
 364                 /* Turn on and off 16 bit access so that reboot works. */
 365                 outb(ISA16 | ei_status.reg5, wd_cmdreg+WD_CMDREG5);
 366                 memcpy((char *)shmem, buf, count);
 367                 outb(ei_status.reg5, wd_cmdreg+WD_CMDREG5);
 368         } else
 369                 memcpy((char *)shmem, buf, count);
 370 }
 371 
 372 
 373 static int
 374 wd_close_card(struct device *dev)
     /* [previous][next][first][last][top][bottom][index][help] */
 375 {
 376         int wd_cmdreg = dev->base_addr - WD_NIC_OFFSET; /* WD_CMDREG */
 377 
 378         if (ei_debug > 1)
 379                 printk("%s: Shutting down ethercard.\n", dev->name);
 380         NS8390_init(dev, 0);
 381         dev->start = 0;
 382 
 383         /* Change from 16-bit to 8-bit shared memory so reboot works. */
 384         outb(ei_status.reg5, wd_cmdreg + WD_CMDREG5 );
 385 
 386         /* And disable the shared memory. */
 387         outb(ei_status.reg0 & ~WD_MEMENB, wd_cmdreg);
 388 
 389 #ifdef MODULE
 390         MOD_DEC_USE_COUNT;
 391 #endif
 392 
 393         return 0;
 394 }
 395 
 396 
 397 #ifdef MODULE
 398 char kernel_version[] = UTS_RELEASE;
 399 static char devicename[9] = { 0, };
 400 static struct device dev_wd80x3 = {
 401         devicename, /* device name is inserted by linux/drivers/net/net_init.c */
 402         0, 0, 0, 0,
 403         0, 0,
 404         0, 0, 0, NULL, wd_probe };
 405 
 406 int io = 0x300;
 407 int irq = 0;
 408 int mem = 0;
 409 
 410 int init_module(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 411 {
 412         if (io == 0)
 413                 printk("wd: You should not use auto-probing with insmod!\n");
 414         dev_wd80x3.base_addr = io;
 415         dev_wd80x3.irq       = irq;
 416         dev_wd80x3.mem_start = mem;
 417         if (register_netdev(&dev_wd80x3) != 0)
 418                 return -EIO;
 419         return 0;
 420 }
 421 
 422 void
 423 cleanup_module(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 424 {
 425         if (MOD_IN_USE)
 426                 printk("wd80x3: device busy, remove delayed\n");
 427         else
 428         {
 429                 int ioaddr = dev_wd80x3.base_addr - WD_NIC_OFFSET;
 430 
 431                 unregister_netdev(&dev_wd80x3);
 432 
 433                 /* If we don't do this, we can't re-insmod it later. */
 434                 free_irq(dev_wd80x3.irq);
 435                 release_region(ioaddr, WD_IO_EXTENT);
 436         }
 437 }
 438 #endif /* MODULE */
 439 
 440 
 441 /*
 442  * Local variables:
 443  *  compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c wd.c"
 444  *  version-control: t
 445  *  tab-width: 4
 446  *  kept-new-versions: 5
 447  * End:
 448  */

/* [previous][next][first][last][top][bottom][index][help] */