/*
 *  Video4Linux driver for the 
 *      "Creative" Labs CT6000 Video Blaster 
 * 
 * Supports ONLY the CT6000 Video Blaster (not any of the later
 * varients of the Video Blaster name that Creative put out later).  The
 * newer boards are absolutly nothing like the original VB.
 *
 *    Copyright (C) 1999 Adam Fritzler (mid@auk.cx)
 *
 * History:
 *  16 Jun 1999 Adam Fritzler <mid@auk.cx>
 *              Started development
 */

#include <linux/module.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <linux/sched.h>
#include <linux/videodev.h>
#include <linux/version.h>
#include <asm/uaccess.h>

struct vb_device 
{
  struct video_device vid;
  struct video_picture pic;
  /* coordinates of the overlay window */
  int x;
  int y;
  /* size of the overlay and/or size of capture window */
  int height;
  int width;
};

/* ioports for 82c9001 */
#define CT82_ADDR_IDX 0x2ad6
#define CT82_ADDR_DATA CT82_ADDR_IDX+1

/*
 * ct82_read( reg )
 *
 *  return value of register reg on 82c9001a.
 *
 */
unsigned char ct82_read(unsigned char reg)
{
  outb(reg, CT82_ADDR_IDX);
  return inb(CT82_ADDR_DATA);
}

#define CT82_R18 0x18
#define CT82_R18_SCL 0x00
#define CT82_R18_SDAO 1
#define CT82_R18_SDAI 1<<1
/*
 * ct82_write( reg, data )
 *
 *  write byte data to register reg on 82c9001a.
 *
 */
void ct82_write(unsigned char reg, unsigned char data)
{
  outb(reg, CT82_ADDR_IDX);
  outb(data, CT82_ADDR_DATA);
}

void ct82_i2c_regwrite(unsigned char scl, unsigned char sda)
{
  unsigned char top = 0x00;

  top = ct82_read(CT82_R18) >> 3; /* take the top */
  printk(KERN_INFO "vb: ct82_i2c_regwrite: %d %d, top=0x%02x\n", scl, sda, top);
  ct82_write(CT82_R18, (top << 3) | (scl | (sda << 1)));
  printk(KERN_INFO "vb wrote 0x%02x\n", (top<<3)|(scl|(sda<<1)));

#if 0
  /* code before i killed it -- derived from Bernhard's code */
  ct82_write(0x18, (ct82_read(0x18) & (~(1 << y))) | ((x & 1) << y));
#endif

  /* insert rethought code here */

#if 0
  /* I wasn't even THINKING correctly for this stuff... */

  if (x == 0) /* set clock */
    {
      if (x == 0) /* set clock to zero (low) */
        {
          ct82_write(0x18, (ct82_read(0x18) & 0x01));
          printf("set clock low\n");
        }
      else /* set clock to on (high) */
        {
          ct82_write(0x18, (ct82_read(0x18) | 0x01));

          printf("set clock high\n");
        }
    }
  if (y == 1) /* set data */
    {
      if (x == 0) /* set data low */
        {
          ct82_write(0x18, (ct82_read(0x18) & 0x02));
          printf("set data low\n");
        }
      else /* set data high */
        {
          ct82_write(0x18, (ct82_read(0x18) | 0x02));
          printk(KERN_INFO "set data high\n");
        }
    }
#endif
}

/*
 * ct82_i2c_regread()
 *
 * Reads the "i2c Read Back" pin on the 82c9001a.  Bit three on RX18.
 *
 */
unsigned char ct82_i2c_regread(void)
{
  return ((ct82_read(CT82_R18) >> 2) & 1);
}

/*
 * ct82_i2c_ack()
 *
 * Uhm...lets wait till I know what I'm doing to explain this one...
 *
 */
unsigned char ct82_i2c_ack(void)
{
  /* doesn't match kernel! */
  unsigned char res;
  ct82_i2c_regwrite(0, 1); /* 1 1 */   /* set clock low, data high */
  ct82_i2c_regwrite(1, 1); /* 1 0 */   /* move clock high */
  res = ct82_i2c_regread();  /* see if clock really moved high */
  ct82_i2c_regwrite(0, 1); /* 0 0 */  /* move clock low */
  return res;
}

void ct82_i2c_start(void)
{
  ct82_i2c_regwrite(0, 1); /* 1 1 */  /* set clock low, data high */
  ct82_i2c_regwrite(1, 1); /* 1 0 */  /* move clock up */
  ct82_i2c_regwrite(1, 0); /* 0 1 */  /* move data low while clock is up */
  ct82_i2c_regwrite(0, 0); /* set both low (to start data transfer) */
}

void ct82_i2c_stop(void)
{
  ct82_i2c_regwrite(0, 0); /* 0 1 */ /* set both low */
  ct82_i2c_regwrite(1, 0); /* set clock high */
  ct82_i2c_regwrite(1, 1); /* move data high with clock up */
}

void ct82_i2c_one(void)
{
  ct82_i2c_regwrite(0, 1);
  ct82_i2c_regwrite(1, 1);
  ct82_i2c_regwrite(0, 1);
}

void ct82_i2c_zero(void)
{
  ct82_i2c_regwrite(0, 0);
  ct82_i2c_regwrite(1, 0);
  ct82_i2c_regwrite(0, 0);
}

unsigned char ct82_i2c_sendbyte(unsigned char x, unsigned char y)
{
  char j;
  unsigned char i;

#if 0
  /* this is bernhard's code...ya, it works when i started */
  if (!y)
    ct82_i2c_start();
  y = x;
  for (i = 0; i < 8; i++)
    {
      ct82_i2c_regwrite(x >> 7, 1);
      ct82_i2c_regwrite(1, 0);
      ct82_i2c_regwrite(0, 0);
      x <<= 1;
    }
#endif

#if 1
  /* this is derived from Gerd's i2c module... */
  y = x;
  ct82_i2c_regwrite(0, 0);
  for (j=7; j >= 0; j--)
    (x & (1<<j)) ? ct82_i2c_one() : ct82_i2c_zero();
#endif

  i = ct82_i2c_ack();
  printk(KERN_INFO "\t0x%02x: %c\n", y, i?'-':'+');
  return i;
}
unsigned char ct82_i2c_write(unsigned char mad,
                             unsigned char sad,
                             unsigned char data)
{

  /* MAD goes first... */
  if (ct82_i2c_sendbyte(mad, 0) != 0)
    {
      ct82_i2c_stop(); /* error: send STOP and abort */
      return 0;
    }

  /* then the SAD... */
  if (ct82_i2c_sendbyte(sad, 1) != 0)
    {
      ct82_i2c_stop(); /* error: send STOP and abort */
      return 0;
    }

  /* then the data... */
  if (ct82_i2c_sendbyte(data, 2) != 0)
    {
      ct82_i2c_stop(); /* error: send STOP and abort */
      return 0;
    }
  else
    {
      /* successful */
      ct82_i2c_stop(); /* send STOP */
      return 1;
    }
}

unsigned char ct82_i2c_read(void)
{
  unsigned char i, j;

  i = 0;
  for (j = 0; j < 8; j++)
    {
      ct82_i2c_regwrite(1, 0);
      i = (i << 1) | ct82_i2c_regread();
      ct82_i2c_regwrite(0, 0);
    }

  return i;
}

int ct82_i2c_busscan(void)
{
  int i,j;
  unsigned char ack;

  j = 0;
  for (i = 0; i < 256; i += 2)
    {
      ct82_i2c_start();
      ack = ct82_i2c_sendbyte(i, 0);
      ct82_i2c_stop();
      if (!ack)
        {
          printk(KERN_INFO "vb: i2c: found device at 0x%02x\n", i);
          j++;
        }
    }
  return j;
}

#define CT82_RFF 0xFF
#define CT82_RFF_GENABLE 0x00

static int vb_probe(void)
{
  unsigned char revision = 0x00;

  revision = ct82_read(CT82_RFF) >> 4;
  printk(KERN_INFO "vb: Board Revision %d\n", revision);
  //ct82_write(CT82_RFF, 1<<CT82_RFF_GENABLE); /* set Global Enable */

  outb(CT82_ADDR_IDX & 0xff, CT82_ADDR_IDX);
  outw(0x3ff, CT82_ADDR_IDX+1);
  outw(0x7a4e, CT82_ADDR_IDX+1);
  inb(CT82_ADDR_DATA);
  
  if (inb(CT82_ADDR_DATA) != 122) {
    printk(KERN_INFO "vb: board not detected\n");
  }

  printk(KERN_INFO "vb: R4D: %02x\n", ct82_read(0x4d));
  ct82_write(0x4d, 9);
  printk(KERN_INFO "vb: R4D: %02x\n", ct82_read(0x4d));


  //printk(KERN_INFO "vb: found %d devices\n", ct82_i2c_busscan());
  return -NODEV;
}

static int vb_open(struct video_device *dev, int flags)
{
  /* MOD_INC_USE_COUNT; */
  printk(KERN_INFO "vb: open\n");
  return 0;
}

static void vb_close(struct video_device *dev)
{
  /* MOD_DEC_USE_COUNT; */
  printk(KERN_INFO "vb: closed\n");
}

static int vb_init(struct video_device *dev)
{
  printk(KERN_INFO "vb:init\n");
  return 0;
}


static int vb_ioctl(struct video_device *dev, unsigned int cmd, void *arg)
{
  struct vb_device *vb=(struct vb_device *)dev;
  printk(KERN_INFO "vb: ioctl\n");
  switch (cmd)
    {
    case VIDIOCGCAP:
      {
	struct video_capability cap;
	strcpy(cap.name, "Creative Video Blaster");
	cap.type = VID_TYPE_CAPTURE|VID_TYPE_OVERLAY|VID_TYPE_SCALES;
	cap.channels = 3;
	cap.audios = 0; /* FIXME */
	cap.maxwidth = 640; /* FIXME: these are exactly correct */
	cap.maxheight = 480; 
	cap.minwidth = 16; /* FIXME: not sure about these */
	cap.minheight = 16;
	if (copy_to_user(arg, &cap, sizeof(cap)))
	  return -EFAULT;
	return 0;
      }
    case VIDIOCGCHAN:
      {
	struct video_channel chan;
	if (copy_from_user(&chan, arg, sizeof(chan)))
	  return -EFAULT;
	if ( (chan.channel < 0) || (chan.channel > 2) )
	  return -EINVAL;
	chan.flags = 0;
	chan.tuners = 0; /* I think this is how this should be done */
	switch (chan.channel)
	  {
	  case 0:
	    strcpy(chan.name, "Video 0");
	    break;
	  case 1:
	    strcpy(chan.name, "Video 1");
	    break;
	  case 2:
	    strcpy(chan.name, "Video 2");
	    break;
	  }
	if (copy_to_user(arg, &chan, sizeof(chan)))
	  return -EFAULT;
	return 0;
      }
    case VIDIOCSCHAN:
      {
	struct video_channel chan;
	if (copy_from_user(&chan, arg, sizeof(chan)))
	  return -EFAULT;
	if ( (chan.channel < 0) || (chan.channel > 2) )
	  return -EINVAL;
	/* FIXME...
	   vb_setsource(chan.channel);
	 */
	return 0;
      }
    case VIDIOCSWIN:
      {
	struct video_window vw;
	if (copy_from_user(&vw, arg, sizeof(vw)))
	  return -EFAULT;

	/* VB doesn't support this arbitrary clipping */
	if (vw.clipcount) 
	  return -EINVAL;

	if (vw.flags)
	  return -EINVAL;  /* no flags...yet */

	/* the 82c9001 can only overlay on 800x600 or smaller displays */
	if ((vw.x > 800) || (vw.y > 600))
	  return -EINVAL;
	vb->x = vw.x;
	vb->y = vw.y;
      
	/* FIXME: not sure on mins here */
	if ((vw.width < 16) || (vw.width > 640))
	  return -EINVAL;
	vb->width = vw.width;
	if ((vw.height < 16) || (vw.height > 480))
	  return -EINVAL;
	vb->height = vw.height;
	/* FIXME: vb_setcapsize(vb->width, vb->height) */
	/* FIXME: vb_setwindow(vb->x, vb->y, vb->x+vb->width, vb->y+vb->height) */
	return 0;
      }
    case VIDIOCGWIN:
      {
	struct video_window vw;
	vw.x = vb->x;
	vw.y = vb->y;
	vw.width = vb->width;
	vw.height = vb->height;
	vw.chromakey = 0;
	vw.flags = 0;
	if (copy_to_user(arg, &vw,sizeof(vw)))
	  return -EFAULT;
	return 0;
      }
    case VIDIOCCAPTURE:
      {
	if ((int)arg)
	  ; /* vb_startoverlay(); */
	else
	  ; /* vb_stopoverlay(); */

	return 0;
      }
    case VIDIOCSPICT:
      {
	struct video_picture pic;
	if (copy_from_user(&pic, arg, sizeof(pic)))
	  return -EFAULT;
	/* FIXME: do palette checking here...dunno whats supported */
	vb->pic = pic;
	/* FIXME: ...
	   vb_setbrightness(pic.brightness);
	   vb_sethue(pic.hue);
	   vb_setcolour(pic.colour);
	   vb_setcontrast(pic.contrast);
	 */
	return 0;
      }
    case VIDIOCGPICT:
      {
	struct video_picture pic;
	if (copy_to_user(arg, &pic, sizeof(pic)))
	  return -EFAULT;
	return 0;
      }
    case VIDIOCGTUNER: /* No VB has a tuner */ /* TODO: Support ROMBO tuner? */
    case VIDIOCSTUNER: 
    case VIDIOCGFBUF: /* VB doesn't do FB overlays, only analog */
    case VIDIOCSFBUF:
    case VIDIOCKEY: /* FIXME: support colorkeying */
    case VIDIOCGFREQ: /* No tuners */
    case VIDIOCSFREQ:
    case VIDIOCGAUDIO: /* FIXME: fiddle with VB's audio fader */
    case VIDIOCSAUDIO:
      /* FIXME: Once we get freeze-frame going, we should support mmap() */
    case VIDIOCGMBUF:
    case VIDIOCMCAPTURE: 
      /* TODO: implement VIDIOCPRIVATE ioctl()s for VB-specific features */
    default:
      return -EINVAL;
    }
  return 0;
}
struct video_device vb_template = {
  "Creative Video Blaster",
  VID_TYPE_CAPTURE,
  23, /* VID_HARDWARE_VB */
  vb_open, /* open */
  vb_close, /* close */
  NULL, /* read */
  NULL, /* write */
  NULL, /* poll */  /* not sure if the VB uses the 82c9001 IRQ or not */
  vb_ioctl, /* ioctl */
  NULL, /* mmap */
  NULL, /* init */
  NULL, /* priv */
  0,
  0
};

struct vb_device vbdev;

#ifdef MODULE

MODULE_PARM(io_port, "i");
MODULE_PARM(mem_base, "i");

int init_module(void)
#else
int vb_initcards(struct video_init *v)
#endif
{
  printk(KERN_INFO "Creative Labs Video Blaster driver 0.00\n");
  vb_probe();
  memcpy(&vbdev.vid, &vb_template, sizeof(vb_template));
  vbdev.x = vbdev.y = 0;
  vbdev.height = 240;
  vbdev.width = 320;
  /* vb_setwinsize(vbdev.width, vbdev.height) */
  return video_register_device((struct video_device *)&vbdev, VFL_TYPE_GRABBER); 
}

#ifdef MODULE
void cleanup_module(void)
{
  /* vb_shutdown(); */
  video_unregister_device((struct video_device *)&vbdev);
}
#endif

