/* pcmcia.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 as 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
    
    Marc E. Fiuczynski updated driver for pl14 and added support :) for DL650T.
    Marc may be reached at mef@cs.washington.edu

    Robert C. Bedichek update driver for pl14 to support the
    Databook Inc. TCIC/2 PCMCIA interface chip.
    Robert may be reached at robertb@cs.washington.edu
*/

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

static char *version = 
  "de650.c:v0.99-14 based on 12/5/93 pcmcia.c:v0.99-13 D.Hinds (dhinds@allegro.stanford.edu)\n";

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <asm/system.h>
#include <asm/io.h>
#ifndef port_read
#include "iow.h"
#endif /* !port_read */

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

#ifndef CONFIG_82365
#ifndef CONFIG_TCIC2
#error "DE650 is a PCMCIA device, requires either 82365 or TCIC2 support\n"
#endif
#endif

#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 */

#define DL650C_IDENT    0x000E0001L /* contents of memory start address for 650C */
#define DL650T_IDENT    0x00260001L /* contents of memory start address for 650T */

/* For some reason, my identification word has a 4 in the top byte.  The
   really odd thing, is that it didn't used to have a 4 there! 
   robertb@cs.washington.edu */

#define DL650R_IDENT    0x04260001L /* contents of memory start address for 650T */

int         de650_probe(struct device *dev);

int probe_for_82365(struct device *dev);
int probe_for_tcic2(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);


/*
  Probe for Linksys and D-Link 650 PCMCIA ethernet adapters
*/

void delay(int len)
{
    sti();
    {
        int start = jiffies;
	int boguscount = 150000;
	while(jiffies - start < len)
	    if (boguscount-- < 0) {
		printk("jiffy failure (t=%d)...", jiffies);
		break;
	    }
    }
}

int de650_probe(struct device *dev)
{
    short ioaddr = dev->base_addr;
    int i, irqval ;

#ifdef HAVE_PORTRESERVE
    if (check_region(ioaddr, 32)) return ENODEV;
#endif

/* We support two kinds of PCMCIA interface chips.  One or the other
   must be confiured. */

#ifdef CONFIG_82365
    if (probe_for_82365(dev)) return ENODEV;
#endif
#ifdef CONFIG_TCIC2
    if (probe_for_tcic2(dev)) return ENODEV;
#endif
    switch(*(long *)dev->mem_start){
    case DL650C_IDENT:
      printk(" detected DE-650C\n");
      break;
    case DL650T_IDENT:
      printk(" detected DE-650T\n");
      break;
    case DL650R_IDENT:
      printk(" detected Rob's DE-650T\n");
      break;
    default:
      printk(" Ethernet card not found (found 0x%x).\n", *(long *)dev->mem_start);
      return ENODEV;
    }
    
    /* Not sure what this does, but it's important */
    *(char *)(dev->mem_start+8) = 0x41;
    
    /* Set up the rest of the parameters. */
    printk("Ethernet address:");
    for (i = 0; i < ETHER_ADDR_LEN; i++) {
      dev->dev_addr[i] = *(unsigned char *)(dev->mem_start+0x40+(i<<1));
      printk(" %02X", dev->dev_addr[i]);
    }
    printk("\n");

    /* Get the interrupt now.  There's no point in waiting 
     * since we cannot share and the board will usually be
     * enabled. 
     */
    if((irqval = irqaction (dev->irq, &ei_sigaction))!=0){
      printk ("Unable to get IRQ %d (irqval=%d) for DE-650 ethernet card\n",
	      dev->irq, irqval);
      return ENODEV;
    }

#ifdef HAVE_PORTRESERVE
    snarf_region(ioaddr, 32);
#endif

    ethdev_init(dev);
    ei_status.name = "D-Link DE-650 PCMCIA Ethernet Card";
    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
    printk("\n%s: %s found at %#x, using IRQ %d, mem window at %#5x.\n",
	   dev->name, ei_status.name, ioaddr, dev->irq, dev->mem_start);

    if (ei_debug > 0)
	printk(version);

    ei_status.reset_8390 = &dl_reset_8390;
    ei_status.block_input = &dl_block_input;
    ei_status.block_output = &dl_block_output;
    NS8390_init(dev, 0);
    return 0;
}

/* 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=%d...", 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;
	}
}

/* Block input and output, similar to the Crynwr packet driver.  If you
   porting to a new ethercard look at the packet driver source for hints.
   The NEx000 doesn't share it on-board packet memory -- you have to put
   the packet out through the "remote DMA" dataport using outb. */

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

    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, nic_base + EN0_RCNTLO);
    outb_p(count >> 8, nic_base + EN0_RCNTHI);
    outb_p(ring_offset & 0xff, nic_base + EN0_RSARLO);
    outb_p(ring_offset >> 8, nic_base + EN0_RSARHI);
    outb_p(E8390_RREAD+E8390_START, DL_BASE + DL_CMD);
    if (ei_status.word16) {
      port_read(DL_BASE + DL_DATAPORT,buf,count>>1);
      if (count & 0x01)
	buf[count-1] = inb(DL_BASE + DL_DATAPORT), xfer_count++;
    } else {
	port_read_b(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 you see
       this message you either 1) have an slightly incompatible clone
       or 2) have noise/speed problems with your bus. */
    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(nic_base + EN0_RSARHI);
	    int low = inb_p(nic_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;
}

static void
dl_block_output(struct device *dev, int count,
		const unsigned char *buf, const int start_page)
{
    int retries = 0;
    int nic_base = DL_BASE;

    /* 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, nic_base + EN0_RCNTLO);
    outb_p(0x00,   nic_base + EN0_RCNTHI);
    outb_p(0x42, nic_base + EN0_RSARLO);
    outb_p(0x00, nic_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, nic_base + EN0_RCNTLO);
    outb_p(count >> 8,   nic_base + EN0_RCNTHI);
    outb_p(0x00, nic_base + EN0_RSARLO);
    outb_p(start_page, nic_base + EN0_RSARHI);

    outb_p(E8390_RWRITE+E8390_START, DL_BASE + DL_CMD);
    if (ei_status.word16) {
	port_write(DL_BASE + DL_DATAPORT, buf, count>>1);
    } else {
	port_write_b(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(nic_base + EN0_RSARHI);
	    int low = inb_p(nic_base + EN0_RSARLO);
	    addr = (high << 8) + low;
	    if ((start_page << 8) + count == addr)
		break;
	} while (--tries > 0);
	if (tries <= 0) {
	    printk("%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;
}

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