/* (c) 1999 Symbol Technologies, all rights reserved. */
/* May be distributed under the terms of GNU's General Public License, the
   text of which can be found online at http://www.gun.org/copyleft/gpl.html */
#include "spectrum24_cs.h"

/* globals */

static dev_info_t dev_info = "spectrum24_cs";
static dev_link_t *dev_list = NULL;  /* a linked list of devices associated with this driver */

static char *essid = NULL;
static char *def_essid = DEFAULT_ESSID;
static int map_mode = FALSE;
static int psp_mode = 0;
static u_char map_dtim = 10;
static u_char map_hop_set = 0x01;
static u_char map_hop_pattern = 0xFF;
static int force_io_mode = FALSE;

MODULE_PARM(essid,"s");
MODULE_PARM(psp_mode,"i");
MODULE_PARM(map_mode,"i");
MODULE_PARM(map_dtim,"b");
MODULE_PARM(map_hop_set, "b");
MODULE_PARM(map_hop_pattern, "b");
MODULE_PARM(force_io_mode,"i");

static u_int irq_mask = 0xdeb8; /* standard irq mask */

int init_module(void)
{
  debug("init_module");
  
  EXPORT_NO_SYMBOLS;
  
  if (essid == NULL)
    essid = def_essid;
  else if (strlen(essid) > 32)
  {
    printk(KERN_NOTICE "%s: ESSID too long, using default.", dev_info);
    essid = def_essid;
  }
  register_pccard_driver(&dev_info,&s24_attach,&s24_detach);
  return 0;
}

void cleanup_module(void)
{
  debug("cleanup_module");
  unregister_pccard_driver(&dev_info);

  while (dev_list)
  {
    s24_detach(dev_list);
  }
}

static int s24_init(struct device *dev)
{
  return 0;
}

static int s24_ioctl(struct device *dev, struct ifreq *ifr, int cmd)
{
  u_long flags;
  local_info_t *info = dev->priv;
  struct iwreq *iwr = (struct iwreq *) ifr;
  int ret_val = 0;
  
  /* I'm not sure if this is necessary, but the sample code I looked at did it so ... */
  save_flags(flags);
  cli();

  switch(cmd)
  {
  case SIOCGIWNAME:
    strcpy(iwr->u.name,IF_NAME);
    break;
  case SIOCGIWESSID:
    if (copy_to_user(iwr->u.data.pointer,info->essid,info->essid_len))
    {
      ret_val = -EFAULT;
      break;
    }
    iwr->u.data.length = info->essid_len;
    iwr->u.data.flags = TRUE;
    break;
  case SIOCSIWESSID:
    if (copy_from_user(info->essid,iwr->u.data.pointer,iwr->u.data.length))
    { 
      ret_val = -EFAULT;
      break;
    }
    /* for some reason, it insists on including the nul character I'm not sure if this breaks down around IW_ESSID_MAX_SIZE */
    info->essid_len = iwr->u.data.length - 1;
    if (dev->start)
    {
      s24_reset(dev);
    }
    break;
  default:
    ret_val = -EOPNOTSUPP;
  }
  restore_flags(flags);
  return ret_val;
}

static void fill_stats(struct device *dev)
{
  
}

#ifdef HAS_WIRELESS_EXTENSIONS
static struct iw_statistics * s24_wireless_stats(struct device *dev)                                           
{
  debug("s24_wireless_stats");
  fill_stats(dev);
  return &((local_info_t *)dev->priv)->iw_stats;
}
#endif

/************************************************************************
* s24_attach								*
*									*
* sets up all the data types when a card is inserted			*
************************************************************************/

static dev_link_t *s24_attach(void)
{
  client_reg_t client_reg;
  dev_link_t *link;
  struct device *dev;
  local_info_t *info;
  int ret;
    
  debug("s24_attach");
  
  /* set up our link structure */
  link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL);
  
  if (link == NULL)
  {
    printk(KERN_NOTICE "%s: Unable to get free memory for link struct.\n", dev_info);
    return NULL;
  }
  
  /* initialize it to zero. */
  memset(link,0,sizeof(struct dev_link_t));
  
  /* populate it */
  link->release.function = &s24_release;
  link->release.data = (u_long) link;
  link->io.NumPorts1 = 16;
  link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO;
  link->io.IOAddrLines = 4;	/* I don't really know, and this isn't curently used, should be modified to be correct at some later date. */
  link->irq.Attributes = IRQ_HANDLE_PRESENT;
  link->irq.IRQInfo1 = IRQ_INFO2_VALID;
  link->irq.IRQInfo2 = irq_mask;
  link->irq.Handler = &s24_interrupt;
  link->conf.Attributes = CONF_ENABLE_IRQ;
  link->conf.Vcc = 50;		/* specified in tenths of a volt */
  link->conf.IntType = INT_MEMORY_AND_IO;
  link->conf.ConfigIndex = ENABLE_IO_MODE;
  link->conf.Present = PRESENT_OPTION;
  
  dev = kmalloc(sizeof(struct device),GFP_KERNEL);

  if (dev == NULL)
  {
    s24_detach(link);
    printk(KERN_NOTICE "%s: Unable to get free memory for dev struct.\n", dev_info);
    return NULL;
  }
  memset(dev,0,sizeof(struct device));
  dev->priv = kmalloc(sizeof(local_info_t),GFP_KERNEL);
  if (dev->priv == NULL)
  {
    s24_detach(link);
    printk(KERN_NOTICE "%s: Unable to get free memory for private struct.\n", dev_info);
    return NULL;
  }
  memset(dev->priv,0,sizeof(local_info_t));
  
  info = dev->priv;
  
  info->essid_len = strlen(essid);
  memcpy(info->essid,essid,info->essid_len);
  
  if (map_mode)
    info->adapter_mode = MAP;
  else
    info->adapter_mode = MU;

  info->map.hop_set = map_hop_set;
  info->map.hop_pattern = map_hop_pattern;
  info->map.dtim = map_dtim;
  
  info->psp = psp_mode;

  if (force_io_mode)
  {
    info->io_mode = TRUE;
  }
  ether_setup(dev);

  dev->hard_start_xmit = &s24_tx;
  dev->set_config = &s24_eth_config;
  dev->get_stats = &s24_stats;
  dev->set_multicast_list = &s24_multicast;
  dev->init = &s24_init;
  dev->open = &s24_open;
  dev->stop = &s24_stop;
  dev->do_ioctl = &s24_ioctl;

#ifdef HAS_WIRELESS_EXTENSIONS
  dev->get_wireless_stats = &s24_wireless_stats;
#endif

  dev->name = info->node.dev_name;
  dev->tbusy = 1;
  link->priv = link->irq.Instance = dev;
  
  link->next = dev_list;
  
  dev_list = link;
  
  client_reg.dev_info = &dev_info;
  client_reg.Attributes = INFO_IO_CLIENT;
  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;
  client_reg.event_handler = &s24_event;
  client_reg.Version = 0x0309; /* the version of card services? currently ignored, see comment about address lines */
  client_reg.event_callback_args.client_data = link;
  
  ret = CardServices(RegisterClient,&(link->handle), &client_reg);
  if (ret != CS_SUCCESS)
  {
    cs_error(link->handle,RegisterClient,ret);
    s24_detach(link);
    return NULL;
  }

  debug("Successfully attached");
  return link;
}

/************************************************************************
* s24_detach								*
*									*
* releases all the data structures allocated with s24_attach()		*
************************************************************************/
static void s24_detach(dev_link_t *link)
{
  dev_link_t **linkp;
  long flags;
  
  debug("s24_detach");
  
  /* look through the linked list until either we find our link, or we reach the end */
  for (linkp = &dev_list; *linkp != NULL && *linkp != link; linkp = &(*linkp)->next)
	;

  /* if we reached the end, return and don't do anything */	
  if (*linkp == NULL)
  {
    debug("couldn't find in linked list.");
    return;
  }
    
  save_flags(flags);
  cli();
  
  if (link->state & DEV_RELEASE_PENDING)
  {
    del_timer(&link->release);
    link->state &= ~DEV_RELEASE_PENDING;
  }
  
  restore_flags(flags);
  
  /* if the device is currently active, mark it as stale and clean it up when we get the release call */
  if (link->state & DEV_CONFIG)
  {
    s24_release((u_long)link);
    if (link->state & DEV_STALE_CONFIG)
    {
      debug("Got detach, but still busy.");
      link->state |= DEV_STALE_LINK;
      return;
    }
  }
  
  /* if the link was registered, deregister it */
  if (link->handle)
    CardServices(DeregisterClient, link->handle);
  
  /* Remove this from the linked list */
  *linkp = link->next;
  
  /* Free memory */
  if (link->priv != NULL)
  {
    struct device *dev = link->priv;
    if (dev->priv != NULL)
      kfree(dev->priv);
    kfree(link->priv);
  }
  
  kfree(link);
} /* s24_detach */

/************************************************************************
* s24_eth_config							*
*									*
* is called by ifconfig to change things like irq and ioport currently	*
* won't change any thing, in the future could be modifed to be more	*
* flexible.								*
************************************************************************/

static int s24_eth_config (struct device *dev, struct ifmap *map)
{
  debug("s24_eth_config");
  return 0;
}

/************************************************************************
* cold_reset								*
*									*
* will completely reset the adapter, and then bring it up in MU or MAP	*
* mode depending on the adapter_mode flag				*
************************************************************************/

static int cold_reset(struct device *dev)
{
  u_long i;
  local_info_t *info = dev->priv;
  unsigned ioaddr = dev->base_addr;

  put_byte(dev,0,WARM_START_REG);

  debug("register looks like %.2x", (u_char)inb(ioaddr + HSR));
  
  /* set the reset bit */
  outb(BIT_7,ioaddr + CMD);
  
  mdelay(2);
  
  /* clear the reset bit */
  outb(0,ioaddr + CMD);
  
  mdelay(1);

  debug("register looks like %.2x", (u_char)inb(ioaddr + HSR));

  switch(info->adapter_mode)
  {
  case MAP:
    outb(HREG_INIT_MAP, ioaddr + HREG);
    break;
  case MU:
    outb(HREG_INIT_MU, ioaddr + HREG);
    break;
  default:
    return 1;
  }

  outb(HCR_PWRUP,ioaddr + CMD);

  /* this should allow it to cycle through waiting for the adapter to wakeup,
   unfortunately it's a busy wait, but I don't know of a better solution since
   this can be called at interrupt time */
  for (i = jiffies + HZ*3/5 ; jiffies < i && !(inb(ioaddr) & 0x20) ; )
  {
    mdelay(1);
  }

  debug("register looks like %.2x", (u_char)inb(ioaddr + HSR));

  if (inb(ioaddr + HSR) & HSR_P188_ACTIVE)
    return 0;

  /* if it gets here, it didn't wake up. */
  return 1;
}

#define CS_CHECK(fn,args...)\
if ((last_ret = CardServices(fn,args)) != CS_SUCCESS) \
{ cs_error(handle,fn,last_ret); s24_release((u_long)link); return; }

/************************************************************************
* s24_config								*
*									*
* sets up all the card resources (memory windows, ioports and irqs)	*
************************************************************************/

static void s24_config(dev_link_t *link)
{
  client_handle_t handle;
  tuple_t tuple;
  cisparse_t parse;
  struct device *dev;
  local_info_t *info;
  int last_ret;
  u_char buf[64];
  win_req_t winrq;
  
  debug("s24_config");
  
  handle = link->handle;
  dev = link->priv;
  info = dev->priv;
  
  tuple.DesiredTuple = CISTPL_CONFIG;
  tuple.Attributes = 0;
  tuple.TupleData = buf;
  tuple.TupleDataMax = sizeof(buf);
  tuple.TupleOffset = 0;
  
  CS_CHECK(GetFirstTuple, handle, &tuple);
  CS_CHECK(GetTupleData, handle, &tuple);
  CS_CHECK(ParseTuple, handle, &tuple, &parse);
  link->conf.ConfigBase = parse.config.base;
  link->conf.Present = parse.config.rmask[0];
  
  link->state |= DEV_CONFIG;
  
  winrq.Attributes = WIN_MEMORY_TYPE_CM | WIN_DATA_WIDTH_8 | WIN_ENABLE;
  winrq.Base = 0;
  winrq.Size = MEM_WIN_SIZE;
  winrq.AccessSpeed = 100;
  
  link->win = (window_handle_t)handle;

  if (!info->io_mode && CardServices(RequestWindow,&link->win, &winrq) != CS_SUCCESS)
  {
    printk(KERN_NOTICE "%s: Unable to get a memory window, falling back to IO mode\n", dev_info);
    info->io_mode = TRUE;
  }

  if (!info->io_mode)
  {
    info->mem_area = ioremap(winrq.Base,MEM_WIN_SIZE);

    if (info->mem_area == NULL)
    {
      printk(KERN_NOTICE "%s: ioremap() failed, falling back to IO mode\n", dev_info);
      info->io_mode = TRUE;
    }
  }
  
  debug("info->io_mode = %d",info->io_mode);

  CS_CHECK(RequestIRQ,handle,&(link->irq));
  CS_CHECK(RequestIO,handle,&(link->io));
  CS_CHECK(RequestConfiguration,handle,&(link->conf));
  
  dev->irq = link->irq.AssignedIRQ;
  dev->base_addr = link->io.BasePort1;
  dev->tbusy = 0;

  if (cold_reset(dev))
  {
    printk(KERN_NOTICE "%s: cold_reset() failed!\n", dev_info);
    s24_release((u_long)link);
    return;
  }

  if (register_netdev(dev))
  {
    printk(KERN_NOTICE "%s: register_netdev() failed!\n", dev_info);
    s24_release((u_long)link);
    return;
  }
  
  get_bytes(dev, dev->dev_addr, ETH_ADDR_LOC, MAC_ADDR_LEN);

#if 0
  debug("memory dump:");
  print_buffer(0x100,0x160,dev);
#endif

  link->dev = &((local_info_t *)dev->priv)->node;
  link->state &= ~DEV_CONFIG_PENDING;
}

/************************************************************************
* s24_release								*
*									*
* Releases everything created with s24_config				*
************************************************************************/

static void s24_release(u_long arg)
{
  dev_link_t *link = (dev_link_t *)arg;
  local_info_t *info = ((struct device *)link->priv)->priv;
  
  debug("s24_release");
  
  if (link->open)
  {
    debug("release postponed, link still open.");
    link->state |= DEV_STALE_CONFIG;
    return;
  }

  CardServices(ReleaseConfiguration,link->handle);
  CardServices(ReleaseIO,link->handle, &link->io);
  CardServices(ReleaseIRQ,link->handle, &link->irq);

  CardServices(ReleaseWindow,link->win);

  if (link->dev != NULL)
  {
    unregister_netdev(link->priv);
    link->dev = NULL;
  }

  if (info->mem_area != NULL)
  {
    debug("iounmap %p",info->mem_area);
    iounmap((void *)info->mem_area);
    info->mem_area = NULL;
  }

  link->state &= ~(DEV_CONFIG | DEV_STALE_CONFIG);
  
  if (link->state & DEV_STALE_LINK)
    s24_detach(link);

}


/************************************************************************
* s24_tx								*
*									*
* the transmit function							*
************************************************************************/

static int s24_tx(struct sk_buff *skb, struct device *dev)
{
  u_short len, n, save_len;
  u_long flags;
  char *data;
  u_short rt_buff, tx_buffer;
  
  debug("s24_tx");

  /* I'm not sure which interrupt level this runs at, but in case it could be
     reentrant ... */
  save_flags(flags);
  cli();
  if (get_byte(dev,ASR) & TX_BUSY || dev->tbusy) /* theoretically shouldn't happen */
  {
    debug("TX_BUSY or tbusy, %.2x %.2lx", get_byte(dev, ASR) & TX_BUSY, dev->tbusy);
    if (get_byte(dev, ASR) & TX_BUSY)
    {
      restore_flags(flags);
      return -EBUSY;
    }
    printk(KERN_INFO "%s: failed to release tbusy.\n", dev_info);
  }
  
  if (!(get_byte(dev, ASR) & TX_FREE))
  {
    restore_flags(flags);
    debug("not enough space for a packet");
    return -EBUSY;
  }

  dev->tbusy = 1;
  restore_flags(flags);

  len = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN;
  data = skb->data;
  dev->trans_start = jiffies;
  
  /* actually write the data to the card */
  
  if (!wait_on_woc(dev,12))
  {
    debug("Timed out waiting for WOC");
    dev->tbusy = 0;
    return -EBUSY;
  }

  debug("got WOC, setting TX_BUSY");
  moon_cmd_byte(dev,CMD_ASR_SET,TX_BUSY);
  
  rt_buff = get_word(dev,TX_NEXT_BUFFER);
  
  put_bytes(dev, data, rt_buff + DEST_ADDR, 2 * MAC_ADDR_LEN);

  data += (2*MAC_ADDR_LEN);
  len -= (2*MAC_ADDR_LEN);
  
  /* save the length of the data portion of our buffer (minus the MAC addresses) */
  save_len = le16_to_cpu(len);
  
#if 0
  /* if the type/length field is a length, it is an 802.3 frame 
     in which case, it already has the header information, so we
     need to modify the transmit buffer registers to reflect that */
  if (htons(*(u_short *)data) <= 1500)
  {
    u_short data_size, data_off;

    data_off = get_word(dev,TX_BUFF_OFF);
    data_size = get_word(dev,TX_BUFF_SIZE);
    
    data_size += 8;
    data_off -= 8;
    
    put_word(dev,data_off,TX_BUFF_OFF);
    put_word(dev,data_size,TX_BUFF_SIZE);
  }
#endif

#if 0
  /* for some reason, TX_BUFF_OFF != rt_buff + DATA_OFF, and since using the first values give problems, I'm trying the second */
  tx_buffer = get_word(dev,TX_BUFF_OFF);
  debug("got %x for the buffer offset from tx_buff_off and %x from the buffer itself",tx_buffer,get_word(dev,rt_buff+DATA_OFF));
  n = MIN(get_word(dev,TX_BUFF_SIZE),len);
#endif

  while (len > 0 && rt_buff != 0)
  {
    tx_buffer = get_word(dev,rt_buff+DATA_OFF);
    n = MIN(get_word(dev,rt_buff+BUFF_LEN),len);
    put_bytes(dev,data,tx_buffer + rt_buff,n);
    data += n;
    len -= n;
    rt_buff = get_word(dev,rt_buff+LINK_PTR);
  }
  
  if (!wait_on_woc(dev,12))
  {
    debug("Timed out waiting for WOC");
    dev->tbusy = 0;
    return 1;
  }
  debug("Got WOC, sending tx_list_command.");

  moon_cmd(dev,CMD_TXL,(u_char *)&save_len,2);  

  dev_kfree_skb(skb);

  return 0;
}

/************************************************************************
* s24_open								*
*									*
* The callback that should bring the card up and start radio activity.	*
************************************************************************/

static int s24_open(struct device *dev)
{
  dev_link_t *link;
  debug("s24_open");

  for(link = dev_list; link != NULL && link->priv != dev; link = link->next)
  	;
  	
  if (link == NULL)
    return -ENODEV;
  
  link->open++;
  
  MOD_INC_USE_COUNT;
  
  dev->interrupt = 0;
  dev->tbusy = 0;
  dev->start = 1;
  
  s24_reset(dev);
  
  return 0;
}

/************************************************************************
* s24_interrupt								*
*									*
* The ISR. It deals with RX_COMPLETE, TX_COMPLETE, RX_ERROR and		*
* STATUS_CHANGE events							*
************************************************************************/

static void s24_interrupt IRQ(int irq, void *dev_id, struct pt_regs *regs)
{
  struct device *dev = dev_id;
  local_info_t *info;
  int ioaddr;
  int i;
  u_char status, tstat;
  u_short save;
  
  debug("int");
  
  if (dev == NULL || !dev->start)
    return;
  
  info = dev->priv;
  ioaddr = dev->base_addr;

  save = save_LMA(dev);
  
  for(i = 0;(status = get_byte(dev,ASR)) & ASR_INT_MASK && i < 100 ; i++)
  {
    debug("status = 0x%.2x, ASR = 0x%.4x",status, ASR);
    
    if (status & RX_COMPLETE)
    {
      struct sk_buff *skb;
      u_short pkt_offset, pkt_len, len;
      u_short data;
       
      pkt_offset = get_word(dev,RX_PKT_START);
      pkt_len = get_word(dev,RX_PKT_LEN);
      
      debug("Got packet! %x long at %x",pkt_len,pkt_offset);

      skb = dev_alloc_skb(pkt_len + (2 * MAC_ADDR_LEN) + 2);
      
      if (skb == NULL)
      {
        printk(KERN_NOTICE "Unable to get memory for skb!\n");
        goto end_receive;
      }
      
      /* aligns the ip header on a 16 byte boundry (2 + the 14 for the ethernet header) */
      skb_reserve(skb,2);

      get_bytes(dev,skb_put(skb,(2 * MAC_ADDR_LEN)),pkt_offset+DEST_ADDR,(2 * MAC_ADDR_LEN));

      while (pkt_len > 0)
      {
        if ((len = get_word(dev,pkt_offset+BUFF_LEN)) > pkt_len)
        {
          debug("buff size error, pkt_len = %x, buff_len = %x",pkt_len,len);
          len = pkt_len;
        }

        data = get_word(dev, pkt_offset + DATA_OFF) + pkt_offset;

        if (skb_tailroom(skb) < len)
        {
          printk(KERN_NOTICE "%s: serious error with receive socket buffer. Please report this to lkeyser@wpi.edu\n", dev_info);
          dev_kfree_skb(skb);
          goto end_receive;
        }

        get_bytes(dev,skb_put(skb,len),data,len);
        pkt_len -= len;
        pkt_offset = get_word(dev,pkt_offset+LINK_PTR);

        if (pkt_len && ! pkt_offset)
        {
          printk(KERN_NOTICE "packet buffer error\n");
          goto end_receive;
        }
      }
      
      skb->dev = dev;
      skb->protocol = eth_type_trans(skb,dev);

      netif_rx(skb);

      end_receive:

      if (!wait_on_woc(dev,12))
      {
        debug("Timed out waiting for WOC");
        break;
      }
      debug("Got WOC, clearing ASR");
      moon_cmd_byte(dev,CMD_ASR_CLR,RX_COMPLETE);

      if (!wait_on_woc(dev,12))
      {
        debug("Timed out waiting for WOC");
        break;
      }
      debug("Got WOC, issuing skip recieve");
      moon_cmd(dev,CMD_SKR,NULL,0);

      wait_on_woc(dev,12);
      debug("clear to cycle");
    }
    
    if (status & TX_COMPLETE)
    {
      debug("tx_done");
      if (!((tstat = get_byte(dev,TSR)) & TX_COMPLETE))
        info->stats.tx_errors++;
      
      if (!wait_on_woc(dev,12))
      {
        debug("Timed out waiting for WOC");
        break;
      }
      debug("Got WOC, clearing transmit status");
      
      moon_cmd_byte(dev,CMD_TSR_CLR,tstat);
      wait_on_woc(dev,12);
      debug("clear to cycle");

      if (!(get_byte(dev,ASR) & TX_BUSY))
      {
        debug("buffer free");
        dev->tbusy = 0;
        mark_bh(NET_BH);
      }
    }
    
    if (status & STATUS_CHANGE)
    {
      debug("Status change");

      if (!wait_on_woc(dev,12))
      {
        debug("Timed out waiting for WOC");
        break;
      }
      debug ("Got WOC, clearing status change bit in ASR");
      
      moon_cmd_byte(dev,CMD_ASR_CLR,STATUS_CHANGE);

      wait_on_woc(dev,12);
      debug("clear to cycle");
    }
    
    /* this is obsolete but it is included for completeness */
    
    if (status & RX_ERROR)
    {
      debug("Got rx error");
      
      info->stats.rx_errors++;

      if (!wait_on_woc(dev,12))
      {
        debug("Timed out waiting for WOC");
        break;
      }
      
      moon_cmd_byte(dev,CMD_ASR_CLR,RX_ERROR);

      if (!wait_on_woc(dev,12))
      {
        debug("Timed out waiting for WOC");
        break;
      }
      
      moon_cmd_byte(dev,CMD_ASR_CLR,RX_ERROR);
      wait_on_woc(dev,12);
      debug("clear to cycle");
    }
  }

  /* restore the LMA registers if need be */
  restore_LMA(dev,save);

  /* acknowledge the interrupt */
  outb(HSR_P188_INT,ioaddr + HSR);
}

/************************************************************************
* s24_multicast								*
*									*
* This deals with the instances in which we are thrown changes to our	*
* interface flags.							*
************************************************************************/

static void s24_multicast(struct device *dev)
{
  debug("set_multicast");
}

/************************************************************************
* s24_stats								*
*									*
* Does statistics reporting						*
************************************************************************/

static struct net_device_stats *s24_stats(struct device *dev)
{
  debug("s24_stats");
  fill_stats(dev);
  return &((local_info_t *)dev->priv)->stats;
}

/************************************************************************
* s24_stop								*
*									*
* The callback in charge of bringing the card down.			*
************************************************************************/

static int s24_stop(struct device *dev)
{
  dev_link_t *link;
  
  debug("s24_stop");
  
  for (link = dev_list; link != NULL && link->priv != dev; link = link->next)
  	;
  
  if (link == NULL)
    return -ENODEV;

  link->open--;
  
  dev->tbusy = 1;
  dev->start = 0;

  cold_reset(dev);

  MOD_DEC_USE_COUNT;
  
  return 0;
}

/************************************************************************
* s24_event								*
*									*
* Handles pcmcia events							*
************************************************************************/

static int s24_event(event_t event, int priority, event_callback_args_t *args)
{
  dev_link_t *link = args->client_data;
  struct device *dev = link->priv;

  debug("s24_event");
  
  switch(event)
  {
  case CS_EVENT_CARD_INSERTION:
    debug("Got event card insertion");
    link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
    s24_config(link);
    break;
  case CS_EVENT_CARD_REMOVAL:
    debug("Got event card removal");
    link->state &= ~DEV_PRESENT;
    if (link->state & DEV_CONFIG)
    {
      dev->tbusy = 1;
      dev->start = 0;
      link->release.expires = RUN_AT(HZ/20);
      add_timer(&(link->release));
    }
    break;
  case CS_EVENT_PM_SUSPEND:
    debug("Got event pm suspend");
    link->state |= DEV_SUSPEND;
    debug("falling through to event reset physical...");
  case CS_EVENT_RESET_PHYSICAL:
    debug("Got event reset physical");
    if (link->state & DEV_CONFIG)
    {
      if (link->open)
      {
      	dev->tbusy = 1;
      	dev->start = 0;
      }
//      CardServices(ReleaseConfiguration, link->handle);
    }
    break;
  case CS_EVENT_PM_RESUME:
    debug("Got event pm resume");
    link->state &= ~DEV_SUSPEND;
    debug("falling through to event card reset...");
  case CS_EVENT_CARD_RESET:
    debug("Got event card reset");
    if (link->state & DEV_CONFIG)
    {
//      CardServices(RequestConfiguration, link->handle, &(link->conf));
      if (link->open)
      {
        cold_reset(dev);
        s24_reset(dev);
        dev->tbusy = 0;
        dev->start = 1;
      }
    }
    break;
  default:
    debug("Got unknown event");
  }

  return 0;
}

/************************************************************************
* s24_reset								*
*									*
* Resets the on-card configuration parameters				*
************************************************************************/

static void s24_reset(struct device *dev)
{
  local_info_t *info = dev->priv;
  int ioaddr = dev->base_addr;
  
  debug("s24_reset");

  if (!wait_on_woc(dev,12))
  {
    debug("s24_reset couldn't get WOC, bailing out");
    return;
  }
  debug("Got WOC, sending reset");

  moon_cmd(dev,CMD_RESET,NULL,0);
  
  if (!wait_on_woc(dev,12))
  {
    debug("s24_reset couldn't get WOC, bailing out");
    return;
  }
  debug("got WOC, initializing receive");
  
  /* turn on the reciever (Bit 7) enable broadcast (bit 2)	*
   * disable antenna diversity (bit 0)				*
   * the other options are enable multicast (bit 3)		*
   * and promiscuous mode (bit 4)				*/

  moon_cmd_byte(dev, CMD_SRC, BIT_7 | BIT_3 | BIT_2 | BIT_0);

  debug("waiting for WOC");

  if (!wait_on_woc(dev,12))
  {
    debug("s24_reset couldn't get WOC, bailing out");
    return;
  }
  debug("got WOC, initializing transmit");
  
  /* bit 7 is enable transmit and bit 0 is loop back packets */
  moon_cmd_byte(dev, CMD_STC, BIT_7);
 
  /* set up the map if that's our goal */
  if (info->adapter_mode == MAP)
  {
    if (!wait_on_woc(dev,12))
    {
      debug("s24_reset couldn't get WOC, bailing out");
      return;
    }
    debug("got WOC, setting MAP mode");

    moon_cmd(dev, CMD_MAP_CONFIG, (u_char *)&info->map, 3);
  }
  
  /* PSP stuff -- needs to be completed */
  if (!wait_on_woc(dev,12))
  {
    debug("s24_reset couldn't get WOC, bailing out");
    return;
  }
  debug("got WOC, setting PSP mode");
  
  moon_cmd_byte(dev, CMD_PMG, info->psp ? BIT_0 : 0);

  /* make the essid go */
  if (!wait_on_woc(dev,12))
  {
    debug("s24_reset couldn't get WOC, bailing out");
    return;
  }
  debug("got WOC, initializing essID ... essid_len = %d", info->essid_len);
  
  /* put the essid in the extra command args buffer assuming it exists */
  if (info->essid_len)
    put_bytes(dev,info->essid, 0xE0, info->essid_len);
  
  /* length of essid 0 means broadcast */
  put_word(dev, info->essid_len, HCB+1);
  
  moon_cmd(dev, CMD_ESSID, NULL,0);

  if (!wait_on_woc(dev,12))
  {
    debug("Unable to get WOC, bailing out.");
    return;
  }
  debug("got WOC, clear to set interrupts");

  outb(ENABLE_P188_INT | inb(ioaddr+CMD),CMD +ioaddr);  
}
