/* de650.c: A non-shared-memory PCMCIA ethernet driver for linux. */
/*
    Written 1992,1993 by Donald Becker.
    Copyright 1993 United States Government as represented by the
    Director, National Security Agency.  This software may be used and
    distributed according to the terms of the GNU Public License,
    incorporated herein by reference.
    
    Based on the NE2000 driver, modified by David Hinds

    Donald Becker may be reached as becker@super.org or
    C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715
    
    David Hinds may be reached at dhinds@allegro.stanford.edu
*/

/* Routines for the NatSemi-based designs (NE[12]000). */

static char *version =
    "de650.c:v1.00-1 3/27/94 David Hinds (dhinds@allegro.stanford.edu)\n";

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/malloc.h>
#include <asm/io.h>
#include <asm/system.h>

#include "dev.h"
#include "8390.h"

#define DL_BASE	 (dev->base_addr)
#define DL_CMD	        0x00
#define DL_DATAPORT	0x10	/* NatSemi-defined port window offset. */
#define DL_RESET	0x1f	/* Issue a read to reset, a write to clear. */

#define SM_START_PG	0x40	/* First page of TX buffer */
#define SM_STOP_PG	0x80	/* Last page +1 of RX ring */

/*====================================================================*/

int dl_open(struct device *dev);
static int dl_close(struct device *dev);

static void dl_reset_8390(struct device *dev);
static int dl_block_input(struct device *dev, int count,
			  char *buf, int ring_offset);
static void dl_block_output(struct device *dev, const int count,
		const unsigned char *buf, const int start_page);

/*====================================================================*/

int dl_open(struct device *dev)
  {
  int i, ioaddr;

  ioaddr = dev->base_addr;

  /* Make sure we've been configured */
  if ((ioaddr < 0x1ff) || (dev->mem_start < 0xD0000) || (dev->irq < 2))
    return -ENODEV;
  
  /* Grab the hardware address, use to identify card */
  for (i = 0; i < ETHER_ADDR_LEN; i++)
    dev->dev_addr[i] = *(unsigned char *)(dev->mem_start+0x40+(i<<1));
  
  printk("%s: D-Link / Linksys PCMCIA probe: ", dev->name);
  if ((dev->dev_addr[0] != 0x00) || (dev->dev_addr[1] != 0x80) ||
      (dev->dev_addr[2] != 0xC8)) {
    printk("not found.\n");
    return -EAGAIN;
    }

  for (i = 0; i < ETHER_ADDR_LEN; i++)
    printk(" %02X", dev->dev_addr[i]);
  printk("\n");
  if (ei_debug > 1) printk(version);
  
  /* Not sure what this does, but it's important */
  *(char *)(dev->mem_start+8) = 0x41;

  ethdev_init(dev);
  
  /* Grab that interrupt */
  if (request_irq(dev->irq, &ei_interrupt) != 0) return -EAGAIN;
  
  ei_status.name = "D-Link / Linksys PCMCIA";
  ei_status.tx_start_page = SM_START_PG;
  ei_status.stop_page = SM_STOP_PG;
  ei_status.rx_start_page = ei_status.tx_start_page + TX_PAGES;
#ifdef PACKETBUF_MEMSIZE
  /* Allow the packet buffer size to be overridden by know-it-alls. */
  ei_status.stop_page = ei_status.tx_start_page + PACKETBUF_MEMSIZE;
#endif
  ei_status.reset_8390 = &dl_reset_8390;
  ei_status.block_input = &dl_block_input;
  ei_status.block_output = &dl_block_output;
  dev->stop = &dl_close;
  
  return ei_open(dev);
  } /* dl_open */

/*====================================================================*/

static int dl_close(struct device *dev)
  {
  printk("%s: Shutting down ethercard.\n", dev->name);
  irq2dev_map[dev->irq] = NULL;
  free_irq(dev->irq);
  if (dev->priv != NULL) {
    kfree_s(dev->priv, sizeof(struct ei_device));
    dev->priv = NULL;
    }
  dev->get_stats = NULL; dev->hard_start_xmit = NULL;
#ifdef HAVE_MULTICASE  
  dev->set_multicast_list = NULL;
#endif
  return 0;
  } /* dl_close */

/*======================================================================

    Hard reset the card.  This used to pause for the same period that
    a 8390 reset command required, but that shouldn't be necessary.
   
======================================================================*/
  
static void dl_reset_8390(struct device *dev)
  {
  int tmp = inb_p(DL_BASE + DL_RESET);
  int reset_start_time = jiffies;
  
  if (ei_debug > 1) printk("resetting the 8390 t=%ld...", jiffies);
  ei_status.txing = 0;

  outb_p(tmp, DL_BASE + DL_RESET);
  /* This check _should_not_ be necessary, omit eventually. */
  while ((inb_p(DL_BASE+EN0_ISR) & ENISR_RESET) == 0)
    if (jiffies - reset_start_time > 2) {
      printk("%s: dl_reset_8390() did not complete.\n", dev->name);
      break;
      }
  } /* dl_reset_8390 */

/*======================================================================

    Block input and output, similar to the Crynwr packet driver.

======================================================================*/

static int dl_block_input(struct device *dev, int count,
                          char *buf, int ring_offset)
  {
  int xfer_count = count;

  if (ei_debug > 4)
    if (count != 4) printk("%s: [bi=%d]\n", dev->name, count+4);
  if (ei_status.dmaing) {
    if (ei_debug > 0)
      printk("%s: DMAing conflict in dl_block_input."
             "[DMAstat:%1x][irqlock:%1x]\n",
             dev->name, ei_status.dmaing, ei_status.irqlock);
    return 0;
    }
  ei_status.dmaing |= 0x01;
  outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, DL_BASE+DL_CMD);
  outb_p(count & 0xff, DL_BASE + EN0_RCNTLO);
  outb_p(count >> 8, DL_BASE + EN0_RCNTHI);
  outb_p(ring_offset & 0xff, DL_BASE + EN0_RSARLO);
  outb_p(ring_offset >> 8, DL_BASE + EN0_RSARHI);
  outb_p(E8390_RREAD+E8390_START, DL_BASE+DL_CMD);
  if (ei_status.word16) {
    insw(DL_BASE + DL_DATAPORT,buf,count>>1);
    if (count & 0x01)
      buf[count-1] = inb(DL_BASE + DL_DATAPORT), xfer_count++;
  } else {
    insb(DL_BASE + DL_DATAPORT, buf, count);
    }
  
  /* This was for the ALPHA version only, but enough people have
     encountering problems that it is still here. */
  if (ei_debug > 1) {		/* DMA termination address check... */
    int addr, tries = 20;
    do {
      /* DON'T check for 'inb_p(EN0_ISR) & ENISR_RDC' here
         -- it's broken! Check the "DMA" address instead. */
      int high = inb_p(DL_BASE + EN0_RSARHI);
      int low = inb_p(DL_BASE + EN0_RSARLO);
      addr = (high << 8) + low;
      if (ei_status.word16) addr -= 8; else addr -= 4;
      if (((ring_offset + xfer_count) & 0xff) == (addr & 0xff))
        break;
      } while (--tries > 0);
    if (tries <= 0)
      printk("%s: RX transfer address mismatch,"
             "%#4.4x (expected) vs. %#4.4x (actual).\n",
             dev->name, ring_offset + xfer_count, addr);
    }
  ei_status.dmaing &= ~0x01;
  return ring_offset + count;
  } /* dl_block_input */
  
/*====================================================================*/

static void dl_block_output(struct device *dev, int count,
                            const unsigned char *buf, const int start_page)
  {
  int retries = 0;
  
  if (ei_debug > 4) printk("%s: [bo=%d]\n", dev->name, count);
  /* Round the count up for word writes.  Do we need to do this?
     What effect will an odd byte count have on the 8390?
     I should check someday. */
  if (ei_status.word16 && (count & 0x01))
    count++;
  if (ei_status.dmaing) {
    if (ei_debug > 0) 
      printk("%s: DMAing conflict in dl_block_output."
             "[DMAstat:%1x][irqlock:%1x]\n",
             dev->name, ei_status.dmaing, ei_status.irqlock);
    return;
    }
  ei_status.dmaing |= 0x02;
  /* We should already be in page 0, but to be safe... */
  outb_p(E8390_PAGE0+E8390_START+E8390_NODMA, DL_BASE+DL_CMD);
  
 retry:
#if defined(rw_bugfix)
  /* Handle the read-before-write bug the same way as the
     Crynwr packet driver -- the NatSemi method doesn't work.
     Actually this doesn't aways work either, but if you have
     problems with your NEx000 this is better than nothing! */
  outb_p(0x42, DL_BASE + EN0_RCNTLO);
  outb_p(0x00,   DL_BASE + EN0_RCNTHI);
  outb_p(0x42, DL_BASE + EN0_RSARLO);
  outb_p(0x00, DL_BASE + EN0_RSARHI);
  outb_p(E8390_RREAD+E8390_START, DL_BASE+DL_CMD);
  /* Make certain that the dummy read has occured. */
  SLOW_DOWN_IO;
  SLOW_DOWN_IO;
  SLOW_DOWN_IO;
#endif  /* rw_bugfix */
  
  /* Now the normal output. */
  outb_p(count & 0xff, DL_BASE + EN0_RCNTLO);
  outb_p(count >> 8,   DL_BASE + EN0_RCNTHI);
  outb_p(0x00, DL_BASE + EN0_RSARLO);
  outb_p(start_page, DL_BASE + EN0_RSARHI);
  
  outb_p(E8390_RWRITE+E8390_START, DL_BASE+DL_CMD);
  if (ei_status.word16) {
    outsw(DL_BASE + DL_DATAPORT, buf, count>>1);
  } else {
    outsb(DL_BASE + DL_DATAPORT, buf, count);
    }
  
  /* This was for the ALPHA version only, but enough people have
     encountering problems that it is still here. */
  if (ei_debug > 1) {		/* DMA termination address check... */
    int addr, tries = 20;
    do {
      /* DON'T check for 'inb_p(EN0_ISR) & ENISR_RDC' here
         -- it's broken! Check the "DMA" address instead. */
      int high = inb_p(DL_BASE + EN0_RSARHI);
      int low = inb_p(DL_BASE + EN0_RSARLO);
      addr = (high << 8) + low;
      if ((start_page << 8) + count == addr)
        break;
      } while (--tries > 0);
    if (tries <= 0) {
      printk("%s: Tx packet transfer address mismatch,"
             "%#4.4x (expected) vs. %#4.4x (actual).\n",
             dev->name, (start_page << 8) + count, addr);
      if (retries++ == 0)
        goto retry;
      }
    }
  ei_status.dmaing &= ~0x02;
  return;
  } /* dl_block_output */

/*====================================================================*/


/*
 * Local variables:
 *  compile-command: "gcc -DKERNEL -Wall -O6 -fomit-frame-pointer -I/usr/src/linux/net/tcp -c de650.c"
 *  version-control: t
 *  kept-new-versions: 5
 * End:
 */
