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

    A cs89x0 PCMCIA client driver

    Written by Danilo Beuche, danilo@first.gmd.de 
    
    Newest version of this driver and additional information
    under http://www.first.gmd.de/~danilo

    This driver is under development und currently supports
    only basic rx/tx operations with CS8920 based PC-Card.
    
    It is based on the skeleton driver found in the pcmcia-cs
    package and other network drivers in this package.
    Additional information came from a crynwr packet driver
    source file supplied with my adapters driver package from
    IBM and the technical information for the CS8920 Chip 
    by Crystal Seminconductors Corp. ( http://www.crystal.com ) 
    
    It has been tested with a IBM EtherJet PC Card on
    10BaseT and BNC connections.
    

    BUGS: 
    - speaker goes off if the card is fully initialized, on succesfull
      initialization therefor only one beep is heard. Maybe be only
      a problem of my machine (SHARP PC-8800).
    - occasionally my machine freezes while the card is plugged in.
      Didn't traced the problem back yet.

    TODO:
    - checks for correct chip type have to be extend
    - configuration should be done by the driver instead of 
      relying on the eeprom configuration data
      o hardware address should be taken from CIS tuple
    - try to use more sophisticated rx/tx modes
      o memory mode packet page access 
	it looks like there is no access to the chips memory interface available
      o RxDMA mode
    - more statistics should be collected
    - document source file extensivly
    - remove every bug 
    
    This software may be used and distributed according to the terms
    of the GNU Public License.

CHANGES:
0.5     - fixed a serious bug which causes crashes on newer kernels
	- made it 2.2.x compatible
	- now compiles with pcmcia-cs 3.0.8
0.4
	- restructured file organization
0.3
	- fixed  connection type problem ( now CONN_HDX, CONN_FDX should work )
	(danilo)
	
	- workaround for IBM Thinkpad 760 Problems ( enable with #define THINKPADHACK 1 )       (danilo/emilio)
	- BNC/AUI works now (emilio)

0.2a	- some cleanup

0.1	- first release


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

//#include <pcmcia/config.h>
//#include <pcmcia/k_compat.h>

#include <linux/config.h>
#include <linux/module.h>

#define THINKPADHACK 1
// if you have an thinkpad 760  uncomment the statement above 
// look at README.Thinkpad for more information 

#ifdef MODULE
#define init_cs89x0 init_module
#endif

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/in.h>
#include <asm/io.h>
#include <asm/bitops.h>


#include <linux/netdevice.h>
#include <linux/etherdevice.h>

#include <linux/if_ether.h>
#include <linux/ioport.h>
#include <linux/skbuff.h>


#include <pcmcia/version.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ds.h>

#include "cs89x0_cs.h"

#define GET_PACKET(dev, skb, count)\
		skb_reserve((skb), 2); \
		BLOCK_INPUT(skb_put((skb), (count)), (count)); \
		(skb)->protocol = eth_type_trans((skb), (dev))

#define RUN_AT(x)		(jiffies+(x))

/*
   All the PCMCIA modules use PCMCIA_DEBUG to control debugging.  If
   you do not define PCMCIA_DEBUG at all, all the debug code will be
   left out.  If you compile with PCMCIA_DEBUG=0, the debug code will
   be present but disabled -- but it can then be enabled for specific
   modules at load time with a 'pc_debug=#' option to insmod.
*/

#define PCMCIA_DEBUG 0
#ifdef PCMCIA_DEBUG
static int pc_debug = PCMCIA_DEBUG;
static char *version =
"cs89x0.c 0.5 1999/02/18 15:23:29 (Danilo Beuche)";
static int cs89x0_loop = 0; // if 1 enable internal digital loopback mode
#endif


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

/* Parameters that can be set with 'insmod' */

/* Bit map of interrupts to choose from */
/* This means pick from 15, 14, 12, 11, 10, 9, 7, 5, 4, and 3 */
static u_long irq_mask = 0xdeb8;
static int irq_list[4] = { -1 };

static char *if_names[] = { "Auto", "10baseT", "10base2"};
static int if_port = 0; // default ist to autodetect connection 

static int conn_type = CONN_AUTO; 

// CONN_AUTO(0) = autonegotiate connection type 
// CONN_HDX(1) = halfduplex connection forced
// CONN_FDX(2) = fullduplex connection forced

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

/*
   The event() function is this driver's Card Services event handler.
   It will be called by Card Services when an appropriate card status
   event is received.  The config() and release() entry points are
   used to configure or release a socket, in response to card insertion
   and ejection events.  They are invoked from the cs89x0 event
   handler.
*/

static void cs89x0_config(dev_link_t *link);
static void cs89x0_release(u_long arg);
static int cs89x0_event(event_t event, int priority,
			event_callback_args_t *args);

/*
   The attach() and detach() entry points are used to create and destroy
   "instances" of the driver, where each instance represents everything
   needed to manage one actual PCMCIA card.
*/

static dev_link_t *cs89x0_attach(void);
static void cs89x0_detach(dev_link_t *);

/*
   You'll also need to prototype all the functions that will actually
   be used to talk to your device.  See 'pcmem_cs' for a good example
   of a fully self-sufficient driver; the other drivers rely more or
   less on other parts of the kernel.
*/

static void cs89x0_interrupt(int irq, void *dev_id, struct pt_regs *regs);

/*
   The dev_info variable is the "key" that is used to match up this
   device driver with appropriate cards, through the card configuration
   database.
*/

static dev_info_t dev_info = "cs89x0_cs";

/*
   A linked list of "instances" of the cs89x0 device.  Each actual
   PCMCIA card corresponds to one device instance, and is described
   by one dev_link_t structure (defined in ds.h).

   You may not want to use a linked list for this -- for example, the
   memory card driver uses an array of dev_link_t pointers, where minor
   device numbers are used to derive the corresponding array index.
*/

static dev_link_t *dev_list = NULL;

/*
   A dev_link_t structure has fields for most things that are needed
   to keep track of a socket, but there will usually be some device
   specific information that also needs to be kept track of.  The
   'priv' pointer in a dev_link_t structure can be used to point to
   a device-specific private data structure, like this.

   A driver needs to provide a dev_node_t structure for each device
   on a card.  In some cases, there is only one device per card (for
   example, ethernet cards, modems).  In other cases, there may be
   many actual or logical devices (SCSI adapters, memory cards with
   multiple partitions).  The dev_node_t structures need to be kept
   in a linked list starting at the 'dev' field of a dev_link_t
   structure.  We allocate them in the card's private data structure,
   because they generally can't be allocated dynamically.
*/
   
#define SET_PORT(a,base) outw((a),ADD_PORT + (base))
#define SET_DATA(d,base)  outw((d),DATA_PORT + (base))

// we don't know where the problem is but at least there is a solution
#ifndef THINKPADHACK
#define GET_DATA(base)   inw(DATA_PORT + (base))
#define GET_IDDATA(base)   inw(DATA_PORT + (base))
#else
#define GET_DATA(base)   ((inw(DATA_PORT + (base))& 0xff) | ((inw(DATA_PORT + (base)+ 1) & 0xff) <<8))
#define GET_IDDATA(base)   ((inw(DATA_PORT + (base))& 0xff) | ((inw(DATA_PORT + (base)+ 1) & 0xff) <<8))
#endif

static char *cs89x0_text[] = { "CS8900", "unknown", "CS8920", "CS8920M" };

typedef struct local_info_t {
  struct net_device dev;		// device infos
  dev_node_t node;
  struct sk_buff *tx_pending; 	// store next transmit frame if necessary
  int chip_id; 			//  0 = CS8900, 1 = CS8920
  int chip_rev;			//  0 = A, 1 = B, ...
  struct net_device_stats stats; // statistics e.g. used by ifconfig
} local_info_t;

static void set_dc_dc(struct net_device *dev, int on_not_off)
{
  int ioaddr = dev->base_addr;
  unsigned int selfcontrol = 0 ;
  int timenow = jiffies;

  if (on_not_off == 1)
    selfcontrol = HCB1_ENBL|HCB1 ;

  SET_PORT(PP_SelfCTL,ioaddr);
  SET_DATA((GET_DATA(ioaddr) & 0x0fff)|selfcontrol,ioaddr);

  while (jiffies-timenow <100)
    ;
}

static void cs89x0_reset(struct net_device *dev)
{
  int ioaddr = dev->base_addr;
  int i,x;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_reset('%s')\n", dev->name);
#endif

  current->state = TASK_INTERRUPTIBLE;
  schedule_timeout(HZ/10);

  for(i = 10000,x= 4711; i && x != 0x630e ; i--)
    {	SET_PORT(PP_ChipID,ioaddr); x = GET_IDDATA(ioaddr); }
  if (x!= 0x630e)
    printk(KERN_NOTICE "cs89x0_cs: doesn't looks like a cs89x0 based card at 0x%03x\n",ioaddr);

  // turn off interrupts
  SET_PORT(PP_BusCTL,ioaddr);
  SET_DATA(0,ioaddr);

  switch(dev->if_port)
    {
    case 1: // 10BaseT
      set_dc_dc(dev,0);
      SET_PORT(PP_LineCTL,ioaddr);
      SET_DATA(GET_DATA(ioaddr) & ~(AUI_ON + TENBASET_ON),ioaddr);
      break;
    case 2: // AUI / 10Base2
      set_dc_dc(dev,1);
      SET_PORT(PP_LineCTL,ioaddr);
      SET_DATA((GET_DATA(ioaddr)  & ~AUTO_AUI_10BASET)| AUI_ONLY,ioaddr);
      break;
    case 0: // autodetect
    default:
      set_dc_dc(dev,1);
      SET_PORT(PP_LineCTL,ioaddr);
      SET_DATA((GET_DATA(ioaddr)& ~AUI_ON)|TENBASET_ON,ioaddr);
      break;
    }
	
  // enable everything
  SET_PORT(PP_LineCTL,ioaddr);
  SET_DATA(GET_DATA(ioaddr)|(SERIAL_RX_ON + SERIAL_TX_ON),ioaddr);
  // turn on xmitter and receiver

  SET_PORT(PP_RxCFG,ioaddr);
  SET_DATA(RX_OK_ENBL + RX_CRC_ERROR_ENBL,ioaddr);
  // interrupt on rx of correct packets and packets with crc errors

  SET_PORT(PP_BufCFG,ioaddr);
  SET_DATA(READY_FOR_TX_ENBL | TX_COL_COUNT_OVRFLOW_ENBL | RX_MISS_COUNT_OVRFLOW_ENBL , ioaddr);

  SET_PORT(PP_TxCFG,ioaddr);
  SET_DATA(TX_OK_ENBL,ioaddr);
  // interrupt on successful tx of packet only

  outw(TX_FORCE,TX_CMD_PORT + ioaddr);   
  outw(0,TX_LEN_PORT + ioaddr);
  // if any tx is underway stop it now

#ifdef PCMCIA_DEBUG
  if (cs89x0_loop)
    {
      // digital loopback setup
      // we need full duplex for this, must turn off link pulse detection
      // and enable internal digital loopback

      conn_type = CONN_FDX;

      SET_PORT(PP_TestCTL,ioaddr);
      SET_DATA(GET_DATA(ioaddr)|(LINK_OFF + ENDEC_LOOPBACK),ioaddr);
    }
#endif PCMCIA_DEBUG

  SET_PORT(PP_RxCTL,ioaddr);
  SET_DATA(DEF_RX_ACCEPT|RX_BROADCAST_ACCEPT,ioaddr);
  // receive only broadcast frames and frames for own address
 
  switch (conn_type)
    {
    case CONN_AUTO:
      {
	int i,x;

	SET_PORT(PP_AutoNegCTL,ioaddr);
	SET_DATA(ALLOW_FDX|AUTO_NEG_ENABLE|RE_NEG_NOW,ioaddr);

	// wait for auto neg to complete
	SET_PORT(PP_AutoNegST,ioaddr);
	x = 1000;
	while((i = GET_DATA(ioaddr)) & AUTO_NEG_BUSY && x)
	  {
	    if ((i & 0x03f) != 0x01e) break;
	    udelay(10000);
	    x--;
	  }
#ifdef PCMCIA_DEBUG
	if (pc_debug) printk(KERN_DEBUG "cs89x0_cs: looped %d times for autoneg ready\n",x);
#endif PCMCIA_DEBUG

	SET_PORT(PP_AutoNegST,ioaddr);
	if (GET_DATA(ioaddr)& AUTO_NEG_BUSY)
	  {
	    printk("%s: autonegotiation did not complete successfully\n",dev->name);
	    printk("%s: maybe cable problem or transceiver mismatch\n",dev->name);
	  }
      }
    case CONN_FDX:
      SET_PORT(PP_AutoNegCTL,ioaddr);
      SET_DATA(FORCE_FDX,ioaddr);
      break;
    case CONN_HDX:
    default:
      if (conn_type != CONN_HDX) 
	printk("%s: invalid conn_type %d, using halfduplex\n",dev->name,conn_type);
      SET_PORT(PP_AutoNegCTL,ioaddr);
      SET_DATA(NLP_ENABLE,ioaddr);
  
    }

  
#ifdef PCMCIA_DEBUG
  if (pc_debug)
    {
      printk(KERN_DEBUG "cs89x0_reset('%s') done\n", dev->name);
      SET_PORT(PP_AutoNegST,ioaddr);
      printk(KERN_DEBUG "cs89x0:AutoNegST = 0x%04x\n", GET_DATA(ioaddr));
    }
#endif

  SET_PORT(PP_BusCTL,ioaddr);
  SET_DATA(ENABLE_IRQ,ioaddr);
  // let us start work now

}

static struct net_device_stats *cs89x0_get_stats(struct net_device *dev)
{

  struct local_info_t *lp = (struct local_info_t*) dev->priv;
  int ioaddr = dev->base_addr;
    
  /* If the card is stopped, just return the present stats. */
  if (!netif_running(dev)) return &lp->stats;

  /* Read the counter registers, assuming we are in page 0. */
  // lp->stats.rx_frame_errors += 0;
  SET_PORT(PP_TxCol,ioaddr);
  lp->stats.collisions+= GET_DATA(ioaddr) >> 6;
  printk(KERN_DEBUG "%s: Tx Col Packets = %ld\n",dev->name,lp->stats.collisions);
  SET_PORT(PP_RxMiss,ioaddr);
  lp->stats.rx_missed_errors+= GET_DATA(ioaddr) >> 6;
  printk(KERN_DEBUG "%s: Missed Packets = %ld\n",dev->name,lp->stats.rx_missed_errors);
    
  return &lp->stats;
	
}

static int cs89x0_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
  int ioaddr;
  local_info_t* lp;

#ifdef PCMCIA_DEBUG
  if (pc_debug) 
    printk(KERN_DEBUG "%s: cs89x0_start_xmit\n", dev->name);
#endif

  ioaddr = dev->base_addr;
  lp = (local_info_t*)dev->priv;

  if (netif_queue_stopped(dev))
    {
      int tickssofar = jiffies - dev->trans_start;

      if (tickssofar < 10) return 1;

      printk(KERN_NOTICE "%s: transmit timed out\n",dev->name);
      lp->stats.tx_errors++;

      dev->trans_start = jiffies;
      /* Issue TX_FORCE command. */
      outw(TX_FORCE,TX_CMD_PORT + ioaddr);
      outw(0,TX_LEN_PORT + ioaddr);

      
      if (lp->tx_pending)
	{ 
      	  printk("%s: freeing lp->tx_pending\n",dev->name);
	  dev_kfree_skb(lp->tx_pending); 
	  lp->tx_pending = NULL; 
	}
      netif_start_queue(dev);
    }

  {
    unsigned long flags;
    save_flags(flags);
    cli();
    // outw(TX_NOW,TX_CMD_PORT + ioaddr);
    outw(TX_AFTER_381,TX_CMD_PORT + ioaddr);
    outw(skb->len,TX_LEN_PORT + ioaddr);
    SET_PORT(PP_BusST,ioaddr);
    if ((GET_DATA(ioaddr) & READY_FOR_TX_NOW))
      {
	outsl(ioaddr + TX_FRAME_PORT, skb->data, (skb->len + 3) >> 2);
	
#ifdef PCMCIA_DEBUG
	if (pc_debug > 1)
	  {
	    int i;
	    printk(KERN_DEBUG "%s: Tx pkt of length %d: \n",
		   dev->name, skb->len);
	    for (i = 0; i < 32; i++)
	      printk(KERN_DEBUG " %02x", skb->data[i]);
	    printk(".\n");
	  }
#endif
	dev->trans_start = jiffies;
	lp->stats.tx_packets++;
	netif_start_queue(dev);

    	restore_flags(flags);

	dev_kfree_skb(skb);
      }
    else
      {
	lp->tx_pending = skb;
	restore_flags(flags);
	return 0;
      }
  }
  
  return 0;
}

static void cs89x0_RxEvent(local_info_t *lp,int isq)
{
  int ioaddr = lp->dev.base_addr;
  if (isq & RX_OK)
    {
      ushort pkt_len;
      struct sk_buff *skb;

      pkt_len = inw(ioaddr + RX_FRAME_PORT);
      // dummy read of rx status
      pkt_len = inw(ioaddr + RX_FRAME_PORT);
      // now get length of packet

      skb = dev_alloc_skb(pkt_len + 3);

      if (skb == NULL) {
	printk(KERN_NOTICE "%s: Memory squeeze, dropping "
	       "packet (len %d).\n", lp->dev.name, pkt_len);
	SET_PORT(RX_BUF_CFG,ioaddr);
	SET_DATA(GET_DATA(ioaddr)|SKIP_1,ioaddr);

	lp->stats.rx_dropped++;
      } 
      else 
	{
	  skb->dev = &lp->dev;
#define   BLOCK_INPUT(buf, len) \
	     insl((ioaddr + RX_FRAME_PORT), buf, (len + 3) >> 2)
	  GET_PACKET((&lp->dev), skb, pkt_len);

#ifdef PCMCIA_DEBUG
	  if (pc_debug > 1) 
	    {
	      int i;
	      printk(KERN_DEBUG "%s: Rx pkt of length %d: \n",
		     lp->dev.name, pkt_len);
	      for (i = 0; i < 32; i++)
		printk(KERN_DEBUG " %02x",((skb->data)-14)[i]);
	      printk(".\n");
	    }
#endif

	  lp->stats.rx_packets++;
	  netif_rx(skb);
	}
    }
  if (isq & RX_CRC_ERROR)
    {
      lp->stats.rx_crc_errors++;
      lp->stats.rx_errors++;
      lp->stats.rx_packets++;

      SET_PORT(RX_BUF_CFG,ioaddr);
      SET_DATA(GET_DATA(ioaddr)|SKIP_1,ioaddr);

      return;
    }
}
	
static void cs89x0_set_multicast_list(struct net_device *dev)
{
  short ioaddr = dev->base_addr;
  switch(dev->mc_count)
    {
    case -1:
      // promiscous mode , thats easy
      SET_PORT(PP_RxCTL,ioaddr);
      SET_DATA(GET_DATA(ioaddr)|RX_ALL_ACCEPT,ioaddr);
      break;
    case  0:
      // just broadcast and ia frames
      SET_PORT(PP_RxCTL,ioaddr);
      SET_DATA(DEF_RX_ACCEPT|RX_BROADCAST_ACCEPT,ioaddr);
      break;
    default:
      printk(KERN_INFO "%s: Multicast on cs89x0 for %d addrs NYI\n", 
	     dev->name, dev->mc_count);
    }
}
/*====================================================================*/

static void cs_error(client_handle_t handle, int func, int ret)
{
#if CS_RELEASE_CODE < 0x2911
  CardServices(ReportError, dev_info, (void *)func, (void *)ret);
#else
  error_info_t err = { func, ret };
  CardServices(ReportError, handle, &err);
#endif
}

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

    We never need to do anything when a pcnet device is "initialized"
    by the net software, because we only register already-found cards.

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

static int cs89x0_init(struct net_device *dev)
{
  return 0;
}

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

static int cs89x0_open(struct net_device *dev)
{
  dev_link_t *link;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_open('%s')\n", dev->name);
#endif

  for (link = dev_list; link; link = link->next)
    if (link->priv == dev) break;
  if (!DEV_OK(link))
    return -ENODEV;

  link->open++;
  MOD_INC_USE_COUNT;
    
#if CS_RELEASE_CODE <= 0x2911
  if (dev->irq != 0)
    irq2dev_map[dev->irq] = dev;
#endif
  
  cs89x0_reset(dev);
  netif_start_queue(dev);

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_open('%s') done\n", dev->name);
#endif
  return 0;

} /* cs89x0_open */

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

static int cs89x0_close(struct net_device *dev)
{
  dev_link_t *link;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_close('%s')\n", dev->name);
#endif

  for (link = dev_list; link; link = link->next)
    if (link->priv == dev) break;
  if (link == NULL)
    return -ENODEV;

  link->open--;
  netif_stop_queue(dev);
  if (link->state & DEV_STALE_CONFIG) {
    link->release.expires = RUN_AT(HZ/20);
    link->state |= DEV_RELEASE_PENDING;
    add_timer(&link->release);
  }

  MOD_DEC_USE_COUNT;

  return 0;
} /* cs89x0_close */

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

static int set_config(struct net_device *dev, struct ifmap *map)
{
  if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) {

    if (map->port == 1)
      set_dc_dc(dev,0);
    else
      set_dc_dc(dev,1);

    if ((map->port == 1) || (map->port == 2)) {
      dev->if_port = map->port;
      printk(KERN_INFO "%s: switched to %s port\n",
	     dev->name, if_names[dev->if_port]);
    }
    else
      return -EINVAL;
  }
  return 0;
}
/*======================================================================

    cs89x0_attach() creates an "instance" of the driver, allocating
    local data structures for one device.  The device is registered
    with Card Services.

    The dev_link structure is initialized, but we don't actually
    configure the card at this point -- we wait until we receive a
    card insertion event.
    
======================================================================*/

static dev_link_t *cs89x0_attach(void)
{
  client_reg_t client_reg;
  dev_link_t *link;
  local_info_t *local;
  int ret,i;
    
#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_attach()\n");
#endif

  /* Initialize the dev_link_t structure */
  link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL);
  memset(link, 0, sizeof(struct dev_link_t));
  link->release.function = &cs89x0_release;
  link->release.data = (u_long)link;


  /* The io structure describes IO port mapping */
  link->io.NumPorts1 = 16;
  link->io.Attributes1 = IO_DATA_PATH_WIDTH_16;
  link->io.NumPorts2 = 0;
  link->io.Attributes2 = IO_DATA_PATH_WIDTH_16;
  link->io.IOAddrLines = 4;

  /* Interrupt setup */
  link->irq.Attributes = IRQ_TYPE_EXCLUSIVE| IRQ_HANDLE_PRESENT;
  link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID;


  if (irq_list[0] == -1)
    link->irq.IRQInfo2 = irq_mask;
  else
    for (i = 0; i < 4; i++)
      link->irq.IRQInfo2 |= 1 << irq_list[i];
 
  link->irq.Handler = &cs89x0_interrupt;
    
  /* General socket configuration */
  link->conf.Attributes = CONF_ENABLE_IRQ;
  link->conf.Vcc = 50;
  link->conf.IntType = INT_MEMORY_AND_IO;
  link->conf.ConfigIndex = 1;
  link->conf.Present = PRESENT_OPTION;


  /* Allocate space for private device-specific data */
  local = kmalloc(sizeof(local_info_t), GFP_KERNEL);
  memset(local, 0, sizeof(local_info_t));
  link->priv = local;


  // ethdev_init(local->dev); // NYI
  strcpy(local->dev.name, local->node.dev_name);
  local->dev.init = &cs89x0_init;
  local->dev.open = &cs89x0_open;
  local->dev.stop = &cs89x0_close;
  local->dev.hard_start_xmit = &cs89x0_start_xmit;
  local->dev.get_stats = &cs89x0_get_stats;
  local->dev.set_config = &set_config;
  local->dev.set_multicast_list = &cs89x0_set_multicast_list;
  ether_setup(&(local->dev));
  netif_stop_queue(&local->dev);


#if CS_RELEASE_CODE > 0x2911
  link->irq.Instance = &(local->dev);
#endif
    
  /* Register with Card Services */
  link->next = dev_list;
  dev_list = link;
  client_reg.dev_info = &dev_info;
  client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE;
  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 | CS_EVENT_RESET_COMPLETE;
  client_reg.event_handler = &cs89x0_event;
  client_reg.Version = 0x0210;
  client_reg.event_callback_args.client_data = link;

  ret = CardServices(RegisterClient, &link->handle, &client_reg);
  if (ret != 0) {
    cs_error(link->handle, RegisterClient, ret);
    cs89x0_detach(link);
    return NULL;
  }

  return link;
} /* cs89x0_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 cs89x0_detach(dev_link_t *link)
{
  dev_link_t **linkp;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_detach(0x%p)\n", link);
#endif
    
  /* Locate device structure */
  printk(KERN_DEBUG "XLcs89x0_detach(0x%p)\n", link);
  for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next)
    if (*linkp == link) break;
  printk(KERN_DEBUG "XKcs89x0_detach(0x%p)\n", link);
  if (*linkp == NULL)
    return;
  printk(KERN_DEBUG "XJcs89x0_detach(0x%p)\n", link);

  /*
    If the device is currently configured and active, we won't
    actually delete it yet.  Instead, it is marked so that when
    the release() function is called, that will trigger a proper
    detach().
  */
  printk(KERN_DEBUG "XIcs89x0_detach(0x%p)\n", link);
  if (link->state & DEV_CONFIG) {
    printk(KERN_DEBUG "XHcs89x0_detach(0x%p)\n", link);
#ifdef PCMCIA_DEBUG
    printk(KERN_DEBUG "cs89x0_cs: detach postponed, '%s' "
	   "still locked\n", link->dev->dev_name);
#endif
    printk(KERN_DEBUG "XGcs89x0_detach(0x%p)\n", link);
    link->state |= DEV_STALE_LINK;
    printk(KERN_DEBUG "XFcs89x0_detach(0x%p)\n", link);
    return;
  }

  /* Break the link with Card Services */
  printk(KERN_DEBUG "XEcs89x0_detach(0x%p)\n", link);
  if (link->handle)
    CardServices(DeregisterClient, link->handle);
  printk(KERN_DEBUG "XDcs89x0_detach(0x%p)\n", link);
    
  /* Unlink device structure, free pieces */
  *linkp = link->next;
  printk(KERN_DEBUG "XCcs89x0_detach(0x%p)\n", link);
  kfree(link->priv);
  printk(KERN_DEBUG "XBcs89x0_detach(0x%p)\n", link);
  kfree(link);
  printk(KERN_DEBUG "XAcs89x0_detach(0x%p)\n", link);
    
} /* cs89x0_detach */

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

    cs89x0_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.
    
======================================================================*/

static void cs89x0_config(dev_link_t *link)
{
  client_handle_t handle;
  tuple_t tuple;
  cisparse_t parse;
  local_info_t *lp;
  int i, j;
  u_char buf[64];
  int ioaddr;
    
  handle = link->handle;
  lp = (local_info_t*)link->priv;



#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_config(0x%p)\n", link);
#endif

  /*
    This reads the card's CONFIG tuple to find its configuration
    registers.
  */
  do {
    tuple.DesiredTuple = CISTPL_CONFIG;
    i = CardServices(GetFirstTuple, handle, &tuple);
    if (i != CS_SUCCESS) break;
    tuple.TupleData = buf;
    tuple.TupleDataMax = 64;
    tuple.TupleOffset = 0;
    i = CardServices(GetTupleData, handle, &tuple);
    if (i != CS_SUCCESS) break;
    i = CardServices(ParseTuple, handle, &tuple, &parse);
    if (i != CS_SUCCESS) break;
    link->conf.ConfigBase = parse.config.base;
  } while (0);
  if (i != CS_SUCCESS) {
    cs_error(handle, ParseTuple, i);
    link->state &= ~DEV_CONFIG_PENDING;
    return;
  }
    
  /* Configure card */
  link->state |= DEV_CONFIG;

  do {
	
    /*
      Try allocating IO ports.  This tries a few fixed addresses.
      If you want, you can also read the card's config table to
      pick addresses -- see the serial driver for an example.
    */
    for (j = 0x290; j < 0x400; j += 0x10) {
      link->io.BasePort1 = j;
      link->io.BasePort2 = 0;
      i = CardServices(RequestIO, link->handle, &link->io);
      if (i == CS_SUCCESS) break;
    }
    if (i != CS_SUCCESS) {
      cs_error(handle, RequestIO, i);
      break;
    }
	
    /*
      Now allocate an interrupt line.  Note that this does not
      actually assign a handler to the interrupt.
    */
    i = CardServices(RequestIRQ, link->handle, &link->irq);
    if (i != CS_SUCCESS) {
      cs_error(handle, RequestIRQ, i);
      break;
    }
    // setup device entries 
    lp->dev.base_addr = link->io.BasePort1;
    lp->dev.irq = link->irq.AssignedIRQ;
    lp->dev.if_port = if_port; // defaults to auto
    netif_start_queue(&lp->dev);
    lp->dev.priv = link->priv;

#if CS_RELEASE_CODE <= 0x2911
    irq2dev_map[lp->dev.irq] = &lp->dev;
#endif

    ioaddr = lp->dev.base_addr;

    /*
      This actually configures the PCMCIA socket -- setting up
      the I/O windows and the interrupt mapping.
    */
    i = CardServices(RequestConfiguration, link->handle, &link->conf);
    if (i != CS_SUCCESS) {
      cs_error(handle, RequestConfiguration, i);
      break;
    }

    {
      // load ethernet hardware address from card 
      // this should be read from CIS instead !
      int x;

      current->state = TASK_INTERRUPTIBLE;
      schedule_timeout(HZ/10);
      // wait 100 ms

      for(i = 100000,x= 4711; i && x != 0x630e ; i--)
	// wait for card to get ready
	{	
	  SET_PORT(PP_ChipID,ioaddr);
	  x = GET_IDDATA(ioaddr);
	}
      i = (x != 0x630e);
      if (i != 0)
	{
	  printk(KERN_NOTICE "cs89x0_cs: doesn't looks like a cs89x0 based card at 0x%03x\n",ioaddr);
	  break;
	}	
      SET_PORT(PRODUCT_ID_ADD,ioaddr);
      x = GET_DATA(ioaddr);
      switch (x & REVISION_MASK)
	{
	case CS8900:
	  lp->chip_id = 0;
	  break;
	case CS8920:
	  lp->chip_id = 1;
	  break;
	case CS8920M:
	  lp->chip_id = 1;
	  break;
	default:
	  lp->chip_id = -1;
	}
      lp->chip_rev = (x & REVISION_BITS) >> 8;
   
#ifdef PCMCIA_DEBUG
      if (pc_debug)
	printk(KERN_DEBUG "cs89x0_cs:found a %s Rev. %c\n", cs89x0_text[x >> 13], lp->chip_rev + 'A');
#endif
      for (i = 0 ; i < 3 ; i++)
	{	
	  SET_PORT( PP_IA + 2*i,ioaddr);
	  ((short*)lp->dev.dev_addr)[i] = GET_DATA(ioaddr);
	}
     
    }

    i = register_netdev(&(lp->dev));

    if (i != 0) {
      printk(KERN_NOTICE "cs89x0_cs: register_netdev() failed\n");
      break;
    }

    link->dev = &lp->node;

  } while (0);

  /* At this point, the dev_node_t structure(s) should be
     initialized and arranged in a linked list at link->dev. */

    
  link->state &= ~DEV_CONFIG_PENDING;

  /* If any step failed, release any partially configured state */
  if (i != 0) {
    cs89x0_release((u_long)link);
    return;
  }
  printk(KERN_INFO "cs89x0 device loaded\n");
  return;

} /* cs89x0_config */

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

    After a card is removed, cs89x0_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 cs89x0_release(u_long arg)
{
  dev_link_t *link = (dev_link_t *)arg;
  local_info_t *local = link->priv;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_release(0x%p)\n", link);
#endif

  /*
    If the device is currently in use, we won't release until it
    is actually closed.
  */
  printk(KERN_DEBUG "cs89x0_release(0x%p)\n", link);
  if (link->open) {

#ifdef PCMCIA_DEBUG
    if (pc_debug)
      printk(KERN_INFO "cs89x0_cs: release postponed, '%s' "
	     "still open\n", link->dev->dev_name);
#endif

    link->state |= DEV_STALE_CONFIG;
    return;
  }

  if (link->dev != '\0')
    unregister_netdev(&(local->dev));
  /* Unlink the device chain */
  link->dev = NULL;
    
  /* Don't bother checking to see if these succeed or not */
  CardServices(ReleaseWindow, link->win);
  CardServices(ReleaseConfiguration, link->handle);
  CardServices(ReleaseIO, link->handle, &link->io);
  CardServices(ReleaseIRQ, link->handle, &link->irq);

#if CS_RELEASE_CODE <= 0x2911
  if (link->irq.AssignedIRQ != 0)
    irq2dev_map[link->irq.AssignedIRQ] = NULL;
#endif

  link->state &= ~DEV_CONFIG;
    
  if (link->state & DEV_STALE_LINK)
    cs89x0_detach(link);

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_release(0x%p) done\n", link);
#endif
    
} /* cs89x0_release */

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

    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.

    When a CARD_REMOVAL event is received, we immediately set a flag
    to block future accesses to this device.  All the functions that
    actually access the device should check this flag to make sure
    the card is still present.
    
======================================================================*/

static int cs89x0_event(event_t event, int priority,
			event_callback_args_t *args)
{
  dev_link_t *link = args->client_data;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_event(%d)\n",event);
#endif
    
  switch (event) {
#ifdef PCMCIA_DEBUG
  case CS_EVENT_REGISTRATION_COMPLETE:
    if (pc_debug)
      printk(KERN_DEBUG "cs89x0_cs: registration complete\n");
    break;
#endif
  case CS_EVENT_CARD_REMOVAL:
    link->state &= ~DEV_PRESENT;
    if (link->state & DEV_CONFIG) {
      // ((local_info_t *)link->priv)->block = 1;
      link->release.expires = RUN_AT(HZ/20);
      add_timer(&link->release);
    }
    break;
  case CS_EVENT_CARD_INSERTION:
    link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
    cs89x0_config(link);
    break;
  case CS_EVENT_PM_SUSPEND:
    link->state |= DEV_SUSPEND;
    /* Fall through... */
  case CS_EVENT_RESET_PHYSICAL:
    if (link->state & DEV_CONFIG)
      CardServices(ReleaseConfiguration, link->handle);
    break;
  case CS_EVENT_PM_RESUME:
    link->state &= ~DEV_SUSPEND;
    /* Fall through... */
    CardServices(ResetCard, link->handle);
    break;
  case CS_EVENT_CARD_RESET:
    if (link->state & DEV_CONFIG)
      CardServices(RequestConfiguration, link->handle, &link->conf);
    break;
  case CS_EVENT_RESET_COMPLETE:
    if (link->state & DEV_CONFIG)
      {
	if (link->open) 
	  {
	    struct net_device *dev = &((local_info_t*)link->priv)->dev;
// AKPM	    dev->start = 1;
	    cs89x0_reset(dev);
	  }
      }
  }
  return 0;
} /* cs89x0_event */

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

static void cs89x0_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
#if CS_RELEASE_CODE > 0x2911
  struct net_device *dev = (struct net_device *)dev_id;
#else
  struct net_device *dev = (struct net_device *)irq2dev_map[irq];
#endif
 
  struct local_info_t *lp;
  int ioaddr, isq;

  if (dev == NULL || !netif_running(dev)) {
    printk(KERN_DEBUG "cs89x0_interrupt(): irq %d for unknown"
	   " device.\n", irq);
    return;
  }
  lp = (struct local_info_t*)dev->priv;
    
  ioaddr = dev->base_addr;
  while ((isq = inw(ioaddr + ISQ_PORT )) && (isq & ISQ_EVENT_MASK) != ISQ_EVENT_MASK )
    {
      switch(isq & ISQ_EVENT_MASK)
	{
	case ISQ_RECEIVER_EVENT:
#ifdef PCMCIA_DEBUG
	  if (pc_debug > 1)
	    printk(KERN_DEBUG "%s: cs89x0_cs: rx ev (isq = 0x%04x)\n",lp->dev.name,isq);
#endif
	  cs89x0_RxEvent(lp,isq);
	  break;
	case ISQ_BUFFER_EVENT:
#ifdef PCMCIA_DEBUG
	  if (pc_debug > 1) printk(KERN_DEBUG "cs89x0_cs: buff ev\n");
#endif
	  if ((isq & READY_FOR_TX) && netif_queue_stopped(dev) && lp->tx_pending)
	    {
	      outsl(ioaddr + TX_FRAME_PORT, lp->tx_pending->data, (lp->tx_pending->len + 3) >> 2);
	      dev_kfree_skb(lp->tx_pending);
	      lp->tx_pending = NULL;
	      netif_wake_queue(dev);
	    }
	  break;   
	case ISQ_TRANSMITTER_EVENT:
#ifdef PCMCIA_DEBUG
	  if (pc_debug > 1) printk(KERN_DEBUG "cs89x0_cs: tx ev\n");
#endif
	  break;
	case ISQ_RX_MISS_EVENT:
#ifdef PCMCIA_DEBUG
	  if (pc_debug > 1) printk(KERN_DEBUG "cs89x0_cs: rx_miss ev\n");
#endif
	  lp->stats.rx_missed_errors+= isq >> 6;
	  break;
	case ISQ_TX_COL_EVENT:
	  lp->stats.collisions+= isq >> 6;
#ifdef PCMCIA_DEBUG
	  if (pc_debug > 1) printk(KERN_DEBUG "cs89x0_cs: tx_col ev\n");
#endif
	  break;
	default:
	  printk(KERN_DEBUG "cs89x0_cs: unknown event (isq = 0x%04x) \n",isq);
	  break;
	}
    }
} /* cs89x0_interrupt */

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

int init_cs89x0(void)
{
  servinfo_t serv;
#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "%s\n", version);
#endif
  CardServices(GetCardServicesInfo, &serv);
  if (serv.Revision != CS_RELEASE_CODE) {
    printk(KERN_NOTICE "cs89x0: Card Services release "
	   "does not match!\n");
    return -1;
  }
  register_pcmcia_driver(&dev_info, &cs89x0_attach, &cs89x0_detach);
  return 0;
}

#ifdef MODULE
void cleanup_module(void)
{
#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(KERN_DEBUG "cs89x0_cs: unloading\n");
#endif
  unregister_pcmcia_driver(&dev_info);
  while (dev_list != NULL) {
    if (dev_list->state & DEV_CONFIG)
      cs89x0_release((u_long)dev_list);
    cs89x0_detach(dev_list);
  }
}
#endif /* MODULE */
/*

  SET_PORT(PP_AutoNegST,ioaddr);
  printk("AutoNegST = 0x%04x\n",GET_DATA(ioaddr));

  SET_PORT(PP_BusST,ioaddr);
  printk("BusST = 0x%04x\n",GET_DATA(ioaddr));

  SET_PORT(PP_LineST,ioaddr);
  printk("LineST = 0x%04x\n",GET_DATA(ioaddr));

  SET_PORT(PP_RxEvent,ioaddr);
  printk("RxEvent = 0x%04x\n",GET_DATA(ioaddr));

  SET_PORT(PP_TxEvent,ioaddr);
  printk("TxEvent = 0x%04x\n",GET_DATA(ioaddr));
*/
