/*
 * IBM Smart Capture Card driver.
 *
 * Copyright(c) 1996,1997,1998,1999 Koji OKAMURA.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer. 2.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Changed by Koji OKAMURA <oka@ec.kyushu-u.ac.jp>
 * Version 0.43, Aug. 17, 1999.
 * Version 0.42, Oct. 22, 1998.
 * Version 0.41, Sep. 28, 1997.
 * Version 0.32, Aug.  8, 1996.
 */

/* I got Major Number at Mon, 29 Sep 1997 15:49:32 -0700 (PDT) !!*/

#define ISCC_MAJOR 93

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

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/major.h>
#include <linux/ioctl.h> 

#include <asm/io.h>
#include <asm/system.h>

#if (LINUX_VERSION_CODE >= VERSION(2,2,0))
#include <asm/uaccess.h>
#endif
#include <asm/bitops.h>

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

#include "iscc_ioctl.h"
#include "iscc_cs.h"

/*
#define PCMCIA_DEBUG 
*/

#ifdef PCMCIA_DEBUG
static int pc_debug = PCMCIA_DEBUG;
static char *version =
"iscc.c 0.43 1999/8/17 (Koji OKAMURA)\n";
#endif

#define init_iscc init_module

static u_long irq_mask = 0xdeb8;
static int mem_speed = 0;

static void iscc_config(dev_link_t *link);
static void iscc_release(u_long arg);
static int iscc_event(event_t event, int priority,event_callback_args_t *args);

static dev_link_t *iscc_attach(void);
static void iscc_detach(dev_link_t *);

int iscc_major=ISCC_MAJOR;
int iscc_minor=0;

void iscc_interrupt(int reg);
static dev_info_t dev_info = "iscc_cs";
static dev_link_t *dev_table[NISCC] = { NULL, /* ... */ };

#define ISCC_WINDOW_SIZE 0x4000

typedef struct iscc_dev {
  dev_node_t	    node;
  caddr_t           Base;
  u_long	    flags;
  iscc_card_t       iscc;
} iscc_dev_t;

static void cs_error(int func, int ret)
{
  CardServices(ReportError, dev_info, (void *)func, (void *)ret);
}

static dev_link_t *iscc_attach(void)
{
  client_reg_t client_reg;
  dev_link_t *link;
  iscc_dev_t *dev;
  int ret;
  int i;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk("iscc_attach()\n");
#endif

  for (i = 0; i < NISCC; i++)
    if (dev_table[i] == NULL) break;

  if (i == NISCC) {
    printk(KERN_NOTICE "pcmem_cs: no devices available\n");
    return NULL;
  }

    /* Initialize the dev_link_t structure */
  link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL);
  dev_table[i] = link;
  memset(link, 0, sizeof(struct dev_link_t));
  link->release.function = &iscc_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.IOAddrLines = 5;

    /* Interrupt setup */
  link->irq.Attributes = IRQ_TYPE_EXCLUSIVE;
  link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID;
  link->irq.IRQInfo2 = irq_mask;
  link->irq.Handler = iscc_interrupt;
    
    /* General socket configuration */
  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 */
  dev = kmalloc(sizeof(iscc_dev_t), GFP_KERNEL);
  memset(dev, 0, sizeof(iscc_dev_t));
  link->priv = dev;
    
    /* Register with Card Services */
  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;
  client_reg.event_handler = &iscc_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(RegisterClient, ret);
    iscc_detach(link);
    return NULL;
  }
    return link;
} /* iscc_attach */

static void iscc_detach(dev_link_t *link)
{
  int i;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk("iscc_detach(0x%p)\n", link);
#endif
    
    /* Locate device structure */

  for (i = 0; i < NISCC; i++)
    if (dev_table[i] == link) break;

  if (i == NISCC)
    return;

  if (link->state & DEV_CONFIG) {
    printk("iscc_cs: detach postponed, '%s' still locked\n",
	   link->dev->dev_name);
    link->state |= DEV_STALE_LINK;
    return;
  }

  if (link->handle)
    CardServices(DeregisterClient, link->handle);
    
  kfree_s(link, sizeof(struct dev_link_t));
  dev_table[i] = NULL;
    
} /* iscc_detach */

static void iscc_config(dev_link_t *link)
{
  client_handle_t handle;
  tuple_t tuple;
  cisparse_t parse;
  iscc_dev_t *dev;
  int i, j, ret;
  u_char buf[64];
  win_req_t req;

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

  for (i = 0; i < NISCC; i++)
    if (dev_table[i] == link) break;
  iscc_minor = i;

  handle = link->handle;
  dev = link->priv;

  do {
    tuple.DesiredTuple = CISTPL_CONFIG;
    ret = CardServices(GetFirstTuple, handle, &tuple);
    if (ret != CS_SUCCESS) break;
    tuple.TupleData = buf;
    tuple.TupleDataMax = 64;
    tuple.TupleOffset = 0;
    ret = CardServices(GetTupleData, handle, &tuple);
    if (ret != CS_SUCCESS) break;
    ret = CardServices(ParseTuple, handle, &tuple, &parse);
    if (ret != CS_SUCCESS) break;
    link->conf.ConfigBase = parse.config.base;
  } while (0);

  if (ret != CS_SUCCESS) {
    cs_error(ParseTuple, ret);
    link->state &= ~DEV_CONFIG_PENDING;
    return;
  }
    
    /* Configure card */
  link->state |= DEV_CONFIG;

  do {
    for (j = 0x300; j < 0x400; j += 0x20) {
      link->io.BasePort1 = j;
      link->io.BasePort2 = j+0x10;
      ret = CardServices(RequestIO, link->handle, &link->io);
      if (ret == CS_SUCCESS) break;
    }
    if (ret != CS_SUCCESS) {
      cs_error(RequestIO, ret);
      break;
    }
	
    ret = CardServices(RequestConfiguration, link->handle, &link->conf);
    if (ret != CS_SUCCESS) {
      cs_error(RequestConfiguration, ret);
      break;
    }

    req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM|WIN_ENABLE;
    req.Base = 0;
    req.Size = ISCC_WINDOW_SIZE;
    req.AccessSpeed = mem_speed ; 
    link->win = (window_handle_t)link->handle;
    ret = CardServices(RequestWindow, &link->win, &req);
    if (ret != 0) {
      cs_error(RequestWindow, ret);
      break;
    }

  } while (0);

  sprintf(dev->node.dev_name, "iscc%d",iscc_minor);
  dev->node.major = iscc_major;
  dev->node.minor = iscc_minor;
  dev->Base = ioremap(req.Base, req.Size);

  link->dev = &dev->node;
  link->state &= ~DEV_CONFIG_PENDING;

  if (ret != 0) {
    iscc_release((u_long)link);
    return;
  }

  {
    dev->iscc.mode             = ISCC_NTSC; 
    dev->iscc.geomet.width     = ISCC_NTSCWIDTH;
    dev->iscc.geomet.height    = ISCC_NTSCHEIGHT;
    dev->iscc.format           = ISCC_YUV422;
    dev->iscc.spdmode          = ISCC_FAST;
    dev->iscc.color.brightness = ISCC_DEFBRIGHTNESS; 
    dev->iscc.color.contrast   = ISCC_DEFCONTRAST;
    dev->iscc.color.saturation = ISCC_DEFSATURATION;
    dev->iscc.color.hue        = ISCC_DEFHUE;
    dev->iscc.tv.tuntype       = 0x61;

    InitVPX(link->io.BasePort1,
	    dev->iscc.mode,0,
	    dev->iscc.geomet.width,
	    dev->iscc.geomet.height,1,
	    dev->iscc.format,
	    iscc_minor);

    SetVPXColor(dev->iscc.color.brightness,
		dev->iscc.color.contrast,
		dev->iscc.color.saturation,
		dev->iscc.color.hue,
		iscc_minor);
      
    SetTVChannel(dev->iscc.tv.tuntype,
		 dev->iscc.tv.channel,
		 dev->iscc.tv.fine,
		 dev->iscc.tv.country,
		 iscc_minor);
  }

  printk("iscc%d: port %x, mem %x, size %d\n",
	 iscc_minor,link->io.BasePort1,(int)req.Base,(int)req.Size);

  printk("iscc device loaded\n");

} /* iscc_config */

static void iscc_release(u_long arg)
{
  dev_link_t *link ;
  iscc_dev_t *info ;

  link = (dev_link_t *)arg;
  info = link->priv;

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

  if (link->open) {
    printk("iscc_cs: release postponed, '%s' still open\n",
	   link->dev->dev_name);
    link->state |= DEV_STALE_CONFIG;
    return;
  }

    /* 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);
  link->state &= ~DEV_CONFIG;
    
  if (link->state & DEV_STALE_LINK)
    iscc_detach(link);
    
} /* iscc_release */

static int iscc_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("iscc_event()\n");
#endif
    
  switch (event) {
#ifdef PCMCIA_DEBUG
  case CS_EVENT_REGISTRATION_COMPLETE:
    if (pc_debug)
      printk("iscc_cs: registration complete\n");
    break;
#endif
  case CS_EVENT_CARD_REMOVAL:
    link->state &= ~DEV_PRESENT;
    if (link->state & DEV_CONFIG) {
      link->release.expires = RUN_AT(5);
      add_timer(&link->release);
    }
    break;
  case CS_EVENT_CARD_INSERTION:
    link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
    iscc_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... */
  case CS_EVENT_CARD_RESET:
    if (link->state & DEV_CONFIG)
      CardServices(RequestConfiguration, link->handle, &link->conf);
    break;
  }
  return 0;
} /* iscc_event */

void iscc_interrupt(int reg)
{
  printk("iscc_cs: interrupt\n");
} /* iscc_interrupt */

#if (LINUX_VERSION_CODE >= VERSION(2,2,0))
static ssize_t iscc_read(struct file *file, 
			 char *buf, size_t count,loff_t *ppos)
#else
static int iscc_read(struct inode *inode, struct file *file, 
		     char *buf, int count)
#endif
{
  dev_link_t *link;
  iscc_dev_t *dev;
  u_long read=0;
  u_long p, from, nb=0, ncp=0;
  int ret;
  memreq_t mem;
  int max_p;
  int adhoc_p;
  int nminor;

#if (LINUX_VERSION_CODE >= VERSION(2,2,0))
  nminor = MINOR(file->f_dentry->d_inode->i_rdev);
#else
  nminor = MINOR(inode->i_rdev);
#endif

#ifdef PCMCIA_DEBUG
  if (pc_debug) {
    printk("iscc_read(%d) : read request of %d bytes (fpos=%d).\n",
	   nminor,count, (int)file->f_pos);
  }
#endif

  if(nminor & 0x80) /* 128 >= minor -> ctl */
    return 0;

  link = dev_table[(nminor & 0x7f)];
  dev = link->priv;

  adhoc_p = dev->iscc.geomet.width*2*8; /* ad hoc */
  max_p = adhoc_p + dev->iscc.geomet.width*dev->iscc.geomet.height*2;

  if(file->f_pos==0)
    file->f_pos += adhoc_p;

  p  = file->f_pos ;
 
  if(p < max_p){

    if(dev->iscc.spdmode == ISCC_SLOW)
      SetVPXRect(20, 0, (nminor & 0x7f));

    {

      mem.CardOffset = p & ~(ISCC_WINDOW_SIZE-1);
      mem.Page = 0;
      from = p & (ISCC_WINDOW_SIZE-1);

      for ( ; count > 0; count -= nb) {

	ret = CardServices(MapMemPage, link->win, &mem);
	if (ret != CS_SUCCESS) {
	  cs_error(MapMemPage, ret);
	  return -EIO;
	}

	nb = (from+count > ISCC_WINDOW_SIZE) ? ISCC_WINDOW_SIZE-from : count;

	ncp = nb;
	if(p + nb > max_p)
	  ncp = max_p - p;

	copy_to_user(buf+read,dev->Base+from, ncp); 
	read += ncp;
	from = 0;
	mem.CardOffset += ISCC_WINDOW_SIZE;

      }
      file->f_pos += read;  
    }

    if(dev->iscc.spdmode == ISCC_SLOW)
      SetVPXRect(dev->iscc.geomet.width, dev->iscc.geomet.height,
		 (nminor&0x7f));
  }

  return read; 	/* success */

} /* iscc_read */

static int iscc_ioctl(struct inode *inode, struct file *file,
		       u_int cmd, u_long arg)
{

  dev_link_t *link;
  iscc_dev_t *dev;
  int retval;

#ifdef PCMCIA_DEBUG
  if (pc_debug) {
    printk("iscc(%d) : trying ioctl %u\n", 
	   MINOR(inode->i_rdev), cmd );
  }
#endif

  link = dev_table[(MINOR(inode->i_rdev) & 0x7f)];
  dev = link->priv;

  if (!arg) return -EINVAL; 

  switch (cmd) {

  case ISCCSETMODE:
    retval = verify_area(VERIFY_READ, (void *) arg, sizeof(int));
    if (retval) 
      return retval;

    { 
      int  modetmp;

      copy_from_user(&modetmp, (void *) arg, sizeof(int));
      if(modetmp != ISCC_NTSC && modetmp != ISCC_PAL )
	return -EINVAL;
      
      dev->iscc.mode = modetmp;
    }

    break;

  case ISCCGETMODE:

    retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(int));
    if (retval)	
      return retval;

    copy_to_user((int *) arg, &dev->iscc.mode, sizeof(int));

    break;

  case ISCCSETGEO:

    retval = verify_area(VERIFY_READ, (void *) arg, sizeof(iscc_geomet_t));
    if (retval) 
      return retval;

    { 
      iscc_geomet_t iscctmp;

      copy_from_user(&iscctmp, (void *) arg, sizeof(iscc_geomet_t));
      if(dev->iscc.mode == ISCC_NTSC){
	if(iscctmp.width <= 0             ||
	   iscctmp.width > ISCC_NTSCWIDTH ||
	   iscctmp.height<= 0             ||
	   iscctmp.height> ISCC_NTSCHEIGHT)
	  return -EINVAL;
      }else {
	if(iscctmp.width <= 0            ||
	   iscctmp.width > ISCC_PALWIDTH ||
	   iscctmp.height<= 0            ||
	   iscctmp.height> ISCC_PALHEIGHT)
	  return -EINVAL;
      }
      
      dev->iscc.geomet = iscctmp;
    }

    SetVPXRect(
	       dev->iscc.geomet.width, 
	       dev->iscc.geomet.height, 
	       (MINOR(inode->i_rdev)&0x7f));
    break;

  case ISCCGETGEO:

    retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(iscc_geomet_t));
    if (retval)	
      return retval;

    copy_to_user((int *) arg, &dev->iscc.geomet, sizeof(iscc_geomet_t));

    break;

  case ISCCSETFMT :

    retval = verify_area(VERIFY_READ, (void *) arg, sizeof(int));
    if (retval) 
      return retval;

    {
      int fmttmp;
      copy_from_user(&fmttmp, (void *) arg, sizeof(int));
      if(fmttmp < ISCC_YUV422 || fmttmp > ISCC_RGB555)
		return -EINVAL;
      dev->iscc.format = fmttmp;
    }

    SetVPXFmt(dev->iscc.format, (MINOR(inode->i_rdev)&0x7f));
    break;

  case ISCCGETFMT :

    retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(int));
    if (retval)	
      return retval;

    copy_to_user((int *) arg, &dev->iscc.format, sizeof(int));

    break;

  case ISCCSETSPDMODE :

    retval = verify_area(VERIFY_READ, (void *) arg, sizeof(int));
    if (retval) 
      return retval;

    {
      int spdtmp;
      copy_from_user(&spdtmp, (void *) arg, sizeof(int));
      if(spdtmp != ISCC_SLOW && spdtmp != ISCC_FAST)
		return -EINVAL;
      dev->iscc.spdmode = spdtmp;
    }

    break;

  case ISCCGETSPDMODE :

    retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(int));
    if (retval)	
      return retval;

    copy_to_user((int *) arg, &dev->iscc.spdmode, sizeof(int));

    break;

  case ISCCSETCOLOR:

    retval = verify_area(VERIFY_READ, (void *) arg, sizeof(iscc_color_t));
    if (retval) 
      return retval;

    { 
      iscc_color_t clrtmp;

      copy_from_user(&clrtmp, (void *) arg, sizeof(iscc_color_t));
      
      dev->iscc.color = clrtmp;
    }

    SetVPXColor(dev->iscc.color.brightness,
		dev->iscc.color.contrast,
		dev->iscc.color.saturation,
		dev->iscc.color.hue,
		(MINOR(inode->i_rdev)&0x7f));

    break;

  case ISCCGETCOLOR:

    retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(iscc_color_t));
    if (retval)	
      return retval;

    copy_to_user((int *) arg, &dev->iscc.color, sizeof(iscc_color_t));

    break;

  case ISCCSETTVCHANNEL:

    retval = verify_area(VERIFY_READ, (void *) arg, sizeof(iscc_tv_t));
    if (retval) 
      return retval;

    { 
      iscc_tv_t tvtmp;

      copy_from_user(&tvtmp, (void *) arg, sizeof(iscc_tv_t));
      
      dev->iscc.tv = tvtmp;
    }

    SetTVChannel(dev->iscc.tv.tuntype,
		 dev->iscc.tv.channel,
		 dev->iscc.tv.fine,
		 dev->iscc.tv.country,
		 (MINOR(inode->i_rdev)&0x7f));
    
    break;

  case ISCCGETTVCHANNEL:

    retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(iscc_tv_t));
    if (retval)	
      return retval;

    copy_to_user((int *) arg, &dev->iscc.tv, sizeof(iscc_tv_t));

    break;

  default:

#ifdef PCMCIA_DEBUG
    if (pc_debug) 
      printk("iscc: unsupported ioctl (%d).\n",cmd);
#endif
    return -EINVAL;
  }

  return 0;

} /* iscc_ioctl */

static int iscc_open(struct inode *inode, struct file *file)
{

  dev_link_t *link;
  iscc_dev_t *dev;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("iscc_open(%d)\n", MINOR(inode->i_rdev));
#endif

  if(MINOR(inode->i_rdev) & 0x80) /* 128 >= minor -> ctl */
    return 0;

  link = dev_table[(MINOR(inode->i_rdev) & 0x7f)];
  dev = link->priv;

  if (dev->flags & ISCC_OPEN) 
    return -EBUSY;

  dev->flags |= ISCC_OPEN;
  return 0;

} /* iscc_open */

#if (LINUX_VERSION_CODE >= VERSION(2,2,0))
static int iscc_close(struct inode *inode, struct file *file)
#else
static void iscc_close(struct inode *inode, struct file *file)
#endif
{

  dev_link_t *link;
  iscc_dev_t *dev;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk("iscc_close(%d)\n", MINOR(inode->i_rdev));
#endif

  if(MINOR(inode->i_rdev) & 0x80) /* 128 >= minor -> ctl */
#if (LINUX_VERSION_CODE >= VERSION(2,2,0))
    return 0;
#else
    return;
#endif

  link = dev_table[(MINOR(inode->i_rdev) & 0x7f)];
  dev = link->priv;

  dev->flags &= ~ISCC_OPEN;

#if (LINUX_VERSION_CODE >= VERSION(2,2,0))
    return 0;
#else
    return;
#endif

} /* iscc_close */

static struct file_operations iscc_fops = {
  NULL,		/* lseek */
  iscc_read,	/* read */
  NULL,		/* write */
  NULL,		/* readdir */
  NULL,		/* select */
  iscc_ioctl,	/* ioctl */
  NULL,		/* mmap */
  iscc_open,	/* open */
#if (LINUX_VERSION_CODE >= VERSION(2,2,0))
  NULL,         /* flush               */
#endif
  iscc_close,   /* release */
  NULL,         /* fsync               */
  NULL,         /* fasync              */
  NULL,         /* check_media_change  */
  NULL          /* revalidate          */
#if (LINUX_VERSION_CODE >= VERSION(2,2,0))
  ,NULL	         /* lock                */
#endif
};

int init_iscc(void)
{
  int i=0;

  servinfo_t serv;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk(version);
#endif

  CardServices(GetCardServicesInfo, &serv);
  if (serv.Revision != CS_RELEASE_CODE) {
    printk("iscc: Card Services release does not match!\n");
    return -1;
  }

  register_pcmcia_driver(&dev_info, &iscc_attach, &iscc_detach);

/*
  for (i = MAX_CHRDEV-1; i > 0; i--) {
    if (register_chrdev(i, "iscc_cs", &iscc_fops) == 0){
      iscc_major=i;
      break;
    } else
      unregister_chrdev(i, "iscc_cs");
  }
*/

  if (register_chrdev(iscc_major, "iscc_cs", &iscc_fops)) {
    unregister_chrdev(i, "iscc_cs");
  }

  return 0;

}

void cleanup_module(void)
{
  dev_link_t *link;
  int i;

  printk("iscc_cs: unloading\n");
  unregister_pcmcia_driver(&dev_info);
  unregister_chrdev(iscc_major, "iscc_cs");

  for (i = 0; i < NISCC; i++) {
    link = dev_table[i];
    if (link) {
      if (link->state & DEV_CONFIG)
	iscc_release((u_long)link);
      iscc_detach(link);
    }
  }
}


