/*
 * IBM Smart Capture Card driver.
 *
 * Copyright(c) 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.50, Aug. 25, 1999.
 */

#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 <linux/types.h>
#include <linux/wrapper.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>

#ifdef MODVERSIONS
#include <linux/modsetver.h>
#include <linux/modules/videodev.ver>
#endif

#include <linux/videodev.h>
#include <linux/i2c.h>
#include <bttv.h>
#include <tuner.h>

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

// #define PCMCIA_DEBUG 1

#ifdef PCMCIA_DEBUG
static int pc_debug = PCMCIA_DEBUG;
static char *version =
"iscc.c 0.50 1999/8/25 (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 *);

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

static int bttv_num;			/* number of Bt848s in use */
static struct bttv bttvs[NISCC];

#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;

  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, "iscc0");
  dev->node.major = 81;
  dev->node.minor = 0;
  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_RGB565;
    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,
	    0);

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

  printk("iscc0: port %x, mem %x, size %d\n",
	 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 */

/*******************************/
/* Memory management functions */
/*******************************/

/* convert virtual user memory address to physical address */
/* (virt_to_phys only works for kmalloced kernel memory) */

static inline unsigned long uvirt_to_phys(unsigned long adr)
{
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *ptep, pte;
  
	pgd = pgd_offset(current->mm, adr);
	if (pgd_none(*pgd))
		return 0;
	pmd = pmd_offset(pgd, adr);
	if (pmd_none(*pmd))
		return 0;
	ptep = pte_offset(pmd, adr/*&(~PGDIR_MASK)*/);
	pte = *ptep;
	if(pte_present(pte))
		return 
		  virt_to_phys((void *)(pte_page(pte)|(adr&(PAGE_SIZE-1))));
	return 0;
}

static inline unsigned long uvirt_to_bus(unsigned long adr) 
{
	return virt_to_bus(phys_to_virt(uvirt_to_phys(adr)));
}

/* convert virtual kernel memory address to physical address */
/* (virt_to_phys only works for kmalloced kernel memory) */

static inline unsigned long kvirt_to_phys(unsigned long adr) 
{
	return uvirt_to_phys(VMALLOC_VMADDR(adr));
}

static inline unsigned long kvirt_to_bus(unsigned long adr) 
{
	return uvirt_to_bus(VMALLOC_VMADDR(adr));
}

static void * rvmalloc(unsigned long size)
{
	void * mem;
	unsigned long adr, page;
        
	mem=vmalloc(size);
	if (mem) 
	{
		memset(mem, 0, size); /* Clear the ram out, no junk to the user */
	        adr=(unsigned long) mem;
		while (size > 0) 
                {
	                page = kvirt_to_phys(adr);
			mem_map_reserve(MAP_NR(phys_to_virt(page)));
			adr+=PAGE_SIZE;
			size-=PAGE_SIZE;
		}
	}
	return mem;
}

static void rvfree(void * mem, unsigned long size)
{
        unsigned long adr, page;
        
	if (mem) 
	{
	        adr=(unsigned long) mem;
		while (size > 0) 
                {
	                page = kvirt_to_phys(adr);
			mem_map_unreserve(MAP_NR(phys_to_virt(page)));
			adr+=PAGE_SIZE;
			size-=PAGE_SIZE;
		}
		vfree(mem);
	}
}

/*
 *	Create the giant waste of buffer space we need for now
 *	until we get DMA to user space sorted out (probably 2.3.x)
 *
 *	We only create this as and when someone uses mmap
 */
 
static int fbuffer_alloc(struct bttv *btv)
{
	if(!btv->fbuffer)
		btv->fbuffer=(unsigned char *) rvmalloc(2*BTTV_MAX_FBUF);
	else
		printk(KERN_ERR "bttv%d: Double alloc of fbuffer!\n",
			btv->nr);
	if(!btv->fbuffer)
		return -ENOBUFS;
	return 0;
}

/* ---- System Calls ---- */

static int vgrab(struct bttv *btv, struct video_mmap *mp)
{
  dev_link_t *link;
  iscc_dev_t *dev;
  unsigned char *fbuffer;

  if(btv->fbuffer==NULL)
    {
      if(fbuffer_alloc(btv))
	return -ENOBUFS;
    }

  link = dev_table[0];
  dev = link->priv;

  {
    int changed=0;
    if(mp->width != dev->iscc.geomet.width){
      dev->iscc.geomet.width = mp->width;
      changed = 1;
    }
    if(mp->height != dev->iscc.geomet.height){

      if(mp->height <= 240)
	dev->iscc.geomet.height = mp->height;
      else if (dev->iscc.geomet.height != 240)
	dev->iscc.geomet.height = 240;

      changed = 1;

    }

    if(changed)
      SetVPXRect(
		 dev->iscc.geomet.width, 
		 dev->iscc.geomet.height, 
		 0);
  }

  {
    int changed=0;

//    printk("Format %d\n",mp->format);

    switch(mp->format){

    case VIDEO_PALETTE_RGB24:
      if(dev->iscc.format != ISCC_RGB565){
	dev->iscc.format = ISCC_RGB565;
	changed=1;
      }
      break;

    case VIDEO_PALETTE_RGB565:
      if(dev->iscc.format != ISCC_RGB565){
	dev->iscc.format = ISCC_RGB565;
	changed=1;
      }
      break;

    case VIDEO_PALETTE_YUV422:
      if(dev->iscc.format != ISCC_YUV422){
	dev->iscc.format = ISCC_YUV422;
	changed=1;
      }
      break;

    case VIDEO_PALETTE_YUV420P:
      if(dev->iscc.format != ISCC_YUV422){
	dev->iscc.format = ISCC_YUV422;
	changed=1;
      }
      break;
    }
    if(changed){
      SetVPXFmt(dev->iscc.format, 0);
//      printk("Format %d\n",dev->iscc.format);
    }

  }

  fbuffer = btv->fbuffer + BTTV_MAX_FBUF * mp->frame;

  {

    u_long read=0;
    u_long p=0, from, nb=0, ncp=0;
    int ret;
    memreq_t mem;
    int max_p;
    int count;

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

    if(p < max_p){

      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;

	memcpy(fbuffer+read, dev->Base+from, ncp); 

	read += ncp;
	from = 0;
	mem.CardOffset += ISCC_WINDOW_SIZE;
      }
    }
  }

  if(mp->height > 240){
    if(mp->height == 480){
      int i;
      for(i=240-1;i>=0;i--){
	memcpy(fbuffer+(i*2  )*mp->width*2, fbuffer+i*mp->width*2, mp->width*2);
	memcpy(fbuffer+(i*2+1)*mp->width*2, fbuffer+i*mp->width*2, mp->width*2);
      }
    } else {
      memset(fbuffer + mp->width * 240 * 2, 0, mp->width * (mp->height - 240) *2);
    }
  }

  {
    int i,j;
    switch(mp->format){

    case VIDEO_PALETTE_RGB24:

      for(i=mp->height-1;i>=0;i--)
	for(j=mp->width-1;j>=0;j--)

	  {
	    unsigned char raw0, raw1;
	    raw0 = *(fbuffer + (i*mp->width +j)*2   );
	    raw1 = *(fbuffer + (i*mp->width +j)*2 +1);
	    *(fbuffer + (i*mp->width +j)*3   ) = ((raw0 & 0x1f) << 3);
	    *(fbuffer + (i*mp->width +j)*3 +1) = ((raw1 & 0x07) << 5) | ((raw0 & 0xe0) >> 3);
	    *(fbuffer + (i*mp->width +j)*3 +2) = ((raw1 & 0xf8));
	  }

      break;

    case VIDEO_PALETTE_YUV422:

      for(i=0;i<mp->height;i++)
	for(j=0;j<mp->width;j++)
	  {
	    unsigned char t;
	    t = *(fbuffer + (i*mp->width +j)*2);
	    *(fbuffer + (i*mp->width +j)*2) = *(fbuffer + (i*mp->width +j)*2 +1);
	    *(fbuffer + (i*mp->width +j)*2 +1) = t^0x80;
	  }
      break;

    case VIDEO_PALETTE_YUV420P:
      {
	unsigned char *t;
	char *s, *y,*u,*v;
	int i,j;

	t = vmalloc(BTTV_MAX_FBUF);

	s = fbuffer;
	y = t;
	u = y + mp->width * mp->height;
	v = u + mp->width * mp->height / 4;

	for (i=0;i < mp->height; i+=2){
	  for (j=0 ; j<mp->width; j+=2){
	    
	    *(u++) = *(s++) ^ 0x80;
	    *(y++) = *(s++);
	    *(v++) = *(s++) ^ 0x80;
	    *(y++) = *(s++);
	  }
	  for (j=0 ; j<mp->width; j+=2){
	    s++;
	    *(y++) = *(s++);
	    s++;
	    *(y++) = *(s++);
	  }
	}

	memcpy(fbuffer, t, mp->width*mp->height*2);
	vfree(t);
	break;
      }
    }
  }

  btv->frame_stat[mp->frame] = GBUFFER_DONE;
  return 0;

} /* vgrab */

static int iscc_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
{

  struct bttv *btv=(struct bttv *)dev;
  int i;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("iscc_ioctl(%d)\n", btv->video_dev.minor);
#endif
  	
    switch (cmd)
      {	
      case VIDIOCGCAP:
	{
	  struct video_capability b;
	  strcpy(b.name,btv->video_dev.name);
	  b.type = VID_TYPE_CAPTURE|
	    VID_TYPE_TUNER|
	    VID_TYPE_TELETEXT|
	    VID_TYPE_OVERLAY|
	    VID_TYPE_CLIPPING|
	    VID_TYPE_FRAMERAM|
	    VID_TYPE_SCALES;
	  b.channels = 1;
	  b.maxwidth = 320;
	  b.maxheight = 240;
	  b.minwidth = 32;
	  b.minheight = 32;
	  if(copy_to_user(arg,&b,sizeof(b)))
	    return -EFAULT;
	  return 0;
	}

      case VIDIOCGCHAN:
	{
	  struct video_channel v;
	  if(copy_from_user(&v, arg,sizeof(v)))
	    return -EFAULT;
	  v.flags=VIDEO_VC_AUDIO;
	  v.tuners=0;
	  v.type=VIDEO_TYPE_CAMERA;
	  v.norm = btv->win.norm;
	  strcpy(v.name,"Composite");

	  if(copy_to_user(arg,&v,sizeof(v)))
	    return -EFAULT;
	  return 0;
	}
      /*
       *	Each channel has 1 tuner
       */
      case VIDIOCSCHAN:
	{
	  struct video_channel v;
	  if(copy_from_user(&v, arg,sizeof(v)))
	    return -EFAULT;

	  v.norm=VIDEO_MODE_NTSC;

	  if(v.norm!=VIDEO_MODE_PAL&&v.norm!=VIDEO_MODE_NTSC
	     &&v.norm!=VIDEO_MODE_SECAM)
	    return -EOPNOTSUPP;
	  btv->win.norm = v.norm;

	  return 0;
	}

      case VIDIOCGPICT:
	{
	  struct video_picture p=btv->picture;
	  if(btv->win.depth==8)
	    p.palette=VIDEO_PALETTE_HI240;
	  if(btv->win.depth==15)
	    p.palette=VIDEO_PALETTE_RGB555;
	  if(btv->win.depth==16)
	    p.palette=VIDEO_PALETTE_RGB565;
	  if(btv->win.depth==24)
	    p.palette=VIDEO_PALETTE_RGB24;
	  if(btv->win.depth==32)
	    p.palette=VIDEO_PALETTE_RGB32;
			
	  if(copy_to_user(arg, &p, sizeof(p)))
	    return -EFAULT;
	  return 0;
	}
      case VIDIOCSPICT:
	{
	  struct video_picture p;
//	  int format;
	  if(copy_from_user(&p, arg,sizeof(p)))
	    return -EFAULT;
	  /* We want -128 to 127 we get 0-65535 */
//	  bt848_bright(btv, (p.brightness>>8)-128);
	  /* 0-511 for the colour */
//	  bt848_sat_u(btv, p.colour>>7);
//	  bt848_sat_v(btv, ((p.colour>>7)*201L)/237);
	  /* -128 to 127 */
//	  bt848_hue(btv, (p.hue>>8)-128);
	  /* 0-511 */
//	  bt848_contrast(btv, p.contrast>>7);
	  btv->picture = p;

	  /* set palette if bpp matches */
	  /*
	  if (p.palette < sizeof(palette2fmt)/sizeof(int)) {
	    format = palette2fmt[p.palette];
	    if (fmtbppx2[format&0x0f]/2 == btv->win.bpp)
	      btv->win.color_fmt = format;
	  }
	  */
	  return 0;
	}

      case VIDIOCCAPTURE:
	{
	  return 0;
	}

      case VIDIOCGFBUF:
	{
	  struct video_buffer v;
	  v.base=(void *)btv->win.vidadr;
	  v.height=btv->win.sheight;
	  v.width=btv->win.swidth;
	  v.depth=btv->win.depth;
	  v.bytesperline=btv->win.bpl;
	  if(copy_to_user(arg, &v,sizeof(v)))
	    return -EFAULT;
	  return 0;
			
	}
      case VIDIOCSFBUF:
	{
	  return 0;
	}

      case VIDIOCSYNC:
	{

	  if(copy_from_user((void *)&i,arg,sizeof(int)))
	    return -EFAULT;

/*                        if(i>1 || i<0)
                                return -EINVAL;
*/

	  switch (btv->frame_stat[i]) {

	  case GBUFFER_UNUSED:
	    return -EINVAL;
	  case GBUFFER_GRABBING:
	    /*
	    while(btv->frame_stat[i]==GBUFFER_GRABBING) {
	      interruptible_sleep_on(&btv->capq);
	      if(signal_pending(current))
		return -EINTR;
	    }
	    */

	    /* Do Capture */

	  /* fall */
	  case GBUFFER_DONE:
	    btv->frame_stat[i] = GBUFFER_UNUSED;
	    break;
	  }
	  return 0;
	}

      case VIDIOCMCAPTURE:
	{
	  struct video_mmap vm;

	  if(copy_from_user((void *) &vm, (void *) arg, sizeof(vm)))
	    return -EFAULT;

	  if (btv->frame_stat[vm.frame] == GBUFFER_GRABBING)
	    return -EBUSY;

	  return vgrab(btv, &vm); 
	}
      
      case VIDIOCGMBUF:
	{
	  struct video_mbuf vm;
	  memset(&vm, 0 , sizeof(vm));
	  vm.size=BTTV_MAX_FBUF*2;
	  vm.frames=1;
	  vm.offsets[0]=0;
	  vm.offsets[1]=BTTV_MAX_FBUF;
	  if(copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
	    return -EFAULT;
	  return 0;
	}

      case VIDIOCGTUNER:
      case VIDIOCSTUNER:
      case VIDIOCSWIN:
      case VIDIOCGWIN:
      case VIDIOCKEY:
      case VIDIOCGFREQ:
      case VIDIOCSFREQ:
      case VIDIOCGAUDIO:
      case VIDIOCSAUDIO:
      case VIDIOCGUNIT:

      case BTTV_WRITEE:
      case BTTV_READEE:
      case BTTV_FIELDNR: 
      case BTTV_PLLSET: 
      case BTTV_BURST_ON:
      case BTTV_BURST_OFF:
      case BTTV_PICNR:
	{
	  return 0;
	}

      case BTTV_VERSION:
	{
	  return BTTV_VERSION_CODE;
	}

      default:
	return -ENOIOCTLCMD;
      }

	return 0;
} /* iscc_ioctl */

/*
 *	This maps the vmalloced and reserved fbuffer to user space.
 *
 *  FIXME: 
 *  - PAGE_READONLY should suffice!?
 *  - remap_page_range is kind of inefficient for page by page remapping.
 *    But e.g. pte_alloc() does not work in modules ... :-(
 */
 
static int iscc_mmap(struct video_device *dev, const char *adr, unsigned long size)
{
  struct bttv *btv=(struct bttv *)dev;
  unsigned long start=(unsigned long) adr;
  unsigned long page,pos;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("iscc_mmap(%d)\n", btv->video_dev.minor);
#endif

  if (size>2*BTTV_MAX_FBUF)
    return -EINVAL;
  if (!btv->fbuffer)
    {
      if(fbuffer_alloc(btv))
	return -EINVAL;
    }
  pos=(unsigned long) btv->fbuffer;
  while (size > 0) 
    {
      page = kvirt_to_phys(pos);
      if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED))
	return -EAGAIN;
      start+=PAGE_SIZE;
      pos+=PAGE_SIZE;
      size-=PAGE_SIZE;    
    }
  return 0;

} /* iscc_mmap */

static int iscc_open(struct video_device *dev, int flags)
{
  struct bttv *btv = (struct bttv *)dev;
  int users, i;

#ifdef PCMCIA_DEBUG
    if (pc_debug)
	printk("iscc_open(%d)\n", btv->video_dev.minor);
#endif

  if (btv->user)
    return -EBUSY;
  for (i=users=0; i<bttv_num; i++)
    users+=bttvs[i].user;

  btv->fbuffer=NULL;
  if (!btv->fbuffer)
    btv->fbuffer=(unsigned char *) rvmalloc(2*BTTV_MAX_FBUF);
  if (!btv->fbuffer)
    return -EINVAL;
  btv->grabbing = 0;
  btv->grab = 0;
  btv->lastgrab = 0;
  for (i = 0; i < MAX_GBUFFERS; i++)
    btv->frame_stat[i] = GBUFFER_UNUSED;

  btv->user++;

  return 0;   

} /* iscc_open */

static void iscc_close(struct video_device *dev)
{
  struct bttv *btv=(struct bttv *)dev;

#ifdef PCMCIA_DEBUG
  if (pc_debug)
    printk("iscc_close(%d)\n", btv->video_dev.minor);
#endif

  btv->user--;
  btv->cap&=~3;

  if(btv->fbuffer)
    rvfree((void *) btv->fbuffer, 2*BTTV_MAX_FBUF);
  btv->fbuffer=0;

} /* iscc_close */

static struct video_device iscc_template=
{
        "IBM Smart Capture Card",
	VID_TYPE_TUNER|VID_TYPE_CAPTURE|VID_TYPE_OVERLAY|VID_TYPE_TELETEXT,
	VID_HARDWARE_BT848,
	iscc_open,
	iscc_close,
	NULL,
	NULL,
#if LINUX_VERSION_CODE >= 0x020100
	NULL,			/* poll */
#endif
	iscc_ioctl,
	iscc_mmap,
	NULL,
	NULL,
	0,
	-1
};

int init_iscc(void)
{

  servinfo_t serv;
  struct bttv *btv = &bttvs[0];

#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);

  /* --- */

  btv->user=0; 

  /* default setup for max. PAL size in a 1024xXXX hicolor framebuffer */
  btv->win.norm=1; /* change this to 0 for PAL, 2 for SECAM */
  btv->win.interlace=1;
  btv->win.x=0;
  btv->win.y=0;
  btv->win.width=320; /* 640 */
  btv->win.height=240; /* 480 */
  btv->win.cropwidth=320; /* 640 */
  btv->win.cropheight=240; /* 480 */
  btv->win.cropx=0;
  btv->win.cropy=0;
  btv->win.bpp=2;
  btv->win.depth=16;
  btv->win.color_fmt=BT848_COLOR_FMT_RGB16;
  btv->win.bpl=1024*btv->win.bpp;
  btv->win.swidth=1024;
  btv->win.sheight=768;
  btv->cap=0;

  btv->gmode=0;
  btv->risc_odd=0;
  btv->risc_even=0;
  btv->risc_jmp=0;
  btv->vbibuf=0;
  btv->grisc=0;
  btv->grabbing=0;
  btv->grabcount=0;
  btv->grab=0;
  btv->lastgrab=0;
  btv->field=btv->last_field=0;
  /* cevans - prevents panic if initialization bails due to memory
   * alloc failures!
   */
  btv->video_dev.minor = -1;
  btv->vbi_dev.minor = -1;
  btv->radio_dev.minor = -1;

  btv->fbuffer=NULL;

  btv->picture.colour=254<<7;
  btv->picture.brightness=128<<8;
  btv->picture.hue=128<<8;
  btv->picture.contrast=0xd8<<7;
  
  /*
   *	Now add the template and register the device unit.
   */

  memcpy(&btv->video_dev,&iscc_template, sizeof(iscc_template));
  if(video_register_device(&btv->video_dev,VFL_TYPE_GRABBER)<0)
    return -1;
  return 0;
}

void cleanup_module(void)
{
  dev_link_t *link;
  int i;
  struct bttv *btv;

  btv=&bttvs[0];

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

  if(btv->video_dev.minor!=-1)
    video_unregister_device(&btv->video_dev);

  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);
    }
  }
}


