/*
 * CX100 frame grabber device driver for Linux
 *
 * Create the device with:
 *
 * mknod /dev/cx100 c 42 0
 *
 * Written by: Danny Sung
 * Started: Spring 1995
 * Finished: 08/21/1995  19:13
 * 
 */

#include <linux/config.h>
#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#endif

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/mm.h>
#include <asm/segment.h>
#include <asm/io.h>
#include <asm/system.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/timer.h>
#include <linux/sched.h>

#include <unistd.h>

#include "cx100.h"

/* we should include the kernel idenfication string in the module. */
static char kernel_version[] = UTS_RELEASE;

/* HW Major code for this device. */
static int major = 42;


/* Frame Grabber specific */
static struct {
	unsigned int baseport;
	unsigned int address;
	unsigned int resolution;
	unsigned int mode;
	int fpos[FG_MODE_MAX];		/* fpos depends on current mode */
} FG;

#define PORT0 (FG.baseport)
#define PORT1 (FG.baseport+1)
#define PORT2 (FG.baseport+2)
#define PORT3 (FG.baseport+3)
#define PORT4 (FG.baseport+4)
#define PORT5 (FG.baseport+5)
#define PORT6 (FG.baseport+6)
#define PORT7 (FG.baseport+7)

#define SEGSIZE		0x10000		/* 64k segment */

static struct wait_queue * fg_timer_queue = NULL;

static unsigned long tvtojiffies(struct timeval *value);
static void fg_wake (unsigned long ignored);
static void fg_sleep( unsigned int ms, int interruptable);
static int fg_read(struct inode * node,struct file * filep,char * buf,int count);
static int fg_write(struct inode * node,struct file * filep,char * buf,int count);
static int fg_lseek(struct inode * inode, struct file * filep, off_t offset, int orig);
static int fg_open( struct inode* ino, struct file* filep);
static void fg_close( struct inode* ino, struct file* filep);
static int fg_ioctl( struct inode * ino, struct file *filep, unsigned int cmd, unsigned long arg);
static int cx_grab_field(void);
static int cx_grab(void);
static int cx_waitvb(void);
static int cx_command(int data);
static inline void cx_display(void);
static inline int cx_imagewidth(void);
static inline int cx_imageheight(void);
int init_module( void);
void cleanup_module(void);

#define cx_setpage(x)		outb( ((x)&3), PORT6 )
#define cx_getpage()			(inb(PORT0)&3)
#define cx_incpage()			(cx_setpage(cx_getpage()+1))
#define cx_decpage()			(cx_setpage(cx_getpage()-1))

#define delay()				outb(0xff, 0x80)		/* nonsense port */
					 		/* produces ~ 9.84*10^-7s (1us - 2us) delay */
/*
 * The driver.
 */



/*
 * Do a frame grab, and read in the image.  lseeks are accepted, and
 * are treated as a pixel index.
 */
static int fg_read(struct inode *node, struct file *filep, char *buf,int count)
{
	unsigned int p = FG.address;
	int cnt, maxbytes;
	int x;
	int fpos;

#ifdef DEBUG
   printk("fg_read()\n");
#endif

	if( (int)filep->f_pos == 0 )	/* If the seek position is 0, */
		cx_grab();			/* We'll automatically grab the frame */

	fpos = (int)filep->f_pos;

	maxbytes = cx_imagewidth() * cx_imageheight();
	if( (count + fpos) > maxbytes )
		count = maxbytes - fpos;

	if( !buf )
		return(count);

		/* Let's make sure the pointer we've got is valid */
	if( (x=verify_area(VERIFY_WRITE, buf, count)) )
		return(x);

	if( FG.resolution && count >= SEGSIZE )
	{		/* high resolution (512x486) */
		int i, j;

			/* page flip here ---v */
		i = ( fpos / SEGSIZE ) + ( (fpos%SEGSIZE) ? 1 : 0 );
		cx_setpage(i);
		cnt = (i * SEGSIZE) - fpos;
		memcpy_tofs(buf, (void *)(p + fpos), cnt );

		j = ( (fpos + count) / SEGSIZE );
		for(x=i; x<j; x++, cnt+=SEGSIZE)
		{
			cx_setpage(x);
			/* <--- page flip here */
			memcpy_tofs(buf+cnt, (void *)p, SEGSIZE);
		}

		cx_setpage(x);
			/* <--- page flip here */
		memcpy_tofs(buf+cnt, (void *)p,
			fpos + count - (x * SEGSIZE));
	}
	else	/* low resolution (256x243) */
	{
			/* don't need to set the page, as all pages *should* be the
				same in low resolution */

		memcpy_tofs(buf, (void *)(p + fpos), count);
		cnt = count;
	}

	return cnt;
}

/*
 * write out
 */
static int fg_write(struct inode * node,struct file * filep,char * buf,int count)
{
	int rtn=count, n;
	int p = FG.address;
	int fpos, maxbytes;

	fpos = (int)filep->f_pos;

	maxbytes = cx_imagewidth() * cx_imageheight();
	if( (count + fpos) > maxbytes )
		count = maxbytes - fpos;

	switch( FG.mode )
	{
		case FG_MODE_NORMAL:
			break;
		case FG_MODE_OVERLAY:
			if( !buf || !count )
				return(rtn);
			if( (n=verify_area(VERIFY_READ, buf, count)) )
				return(n);

			if( FG.resolution && count >= SEGSIZE )
			{
			}
			else
			{
					/* don't need to set the page, as all pages *should* be the
						same in low resolution */

				memcpy_fromfs((void *)(p + fpos), buf, count);
				rtn = count;
			}

			break;
#if 0		/* I'm not sure what exactly this does */
			/* I thought it would be able to handle pixel remapping in
				hardware, but it doesn't look like it. */

		case FG_WM_SET_INPUT_LUT:
			printk("write: set_input_lut\n");
			outb(0x14&1, PORT6);		/* go into INPUT LUT mode */

			outb(filep->f_pos, PORT2);		/* set initial LUT address */
			for(i=filep->f_pos; i<256 && i<count; i++)
				outb(get_fs_byte(buf+i), PORT3);
			outb(0x14, PORT6);		/* exit out of INPUT LUT mode */
			break;
#endif
	}
	return rtn;
}


/*
 * support seeks on the device
 */
static int fg_lseek(struct inode * inode, struct file * filep, off_t offset, int orig)
/* Ok, this may not be the convential usage of lseek, but it works.
	The usage is: lseek(cxfd, x, y).  Where (x,y) are the coordinates to
	which you wish to move the file pointer to.
	You can still use the "old" way of (x+(y*imagewidth)), but I find
	this a bit more elegant.  Besides, we've got a wasted parameter
	otherwise. */
{
#ifdef DEBUG
   printk("fg_lseek()\n");
#endif
	return(filep->f_pos = offset + (cx_imagewidth()*orig));
}


/*
 * Our special open code.
 * MOD_INC_USE_COUNT make sure that the driver memory is not freed
 * while the device is in use.
 */
static int fg_open( struct inode* ino, struct file* filep)
{
	int i;

#ifdef DEBUG
   printk("fg_open()\n");
#endif

	filep->f_pos = 0;
	FG.baseport = 0x230;		/* Default base address */

		/* Let's get the address */
	fg_sleep(5, 1);
	i = cx_command(0x0044);
	if( i == -1 )
		FG.address = 0;
	else
		FG.address = (i & 0x0F) << 16;

		/* Now, get the resolution */
	FG.resolution = !((inb(PORT0) & 0x20) >> 5);

	cx_display();

	FG.mode = 0;
	for(i=0; i<FG_MODE_MAX; i++)
		FG.fpos[i] = 0;

	MOD_INC_USE_COUNT;
	return 0;   
}

/*
 * Now decrement the use count.
 */
static void fg_close( struct inode* ino, struct file* filep)
{
#ifdef DEBUG
   printk("fg_close()\n");
#endif

	MOD_DEC_USE_COUNT;
}

static int fg_ioctl( struct inode * ino, struct file *filep, 
	unsigned int cmd, unsigned long arg)
{
	int i;
	int rtn=0;

#ifdef DEBUG
   printk("fg_ioctl()\n");
#endif
	switch(cmd)
	{
		case FG_NOP:
			if( arg )
				fg_sleep(arg, 1);
			break;
		case FG_WAITVB:
			rtn = cx_waitvb();
			break;
		case FG_BOARD_ON:
	 		fg_sleep(5, 1);
			inb(PORT6); 
			fg_sleep(15,1);		/* reading turns on power */
 		case FG_CX100:
	 		fg_sleep(5, 1);
			outb(0x47, PORT3); 
			fg_sleep(5,1);	/* put in CX100 mode */
			/* need to set address and erase ram? */


			/* We should do the stuff in open() here again... */

				/* Let's get the address */
			fg_sleep(5, 1);
			i = cx_command(0x0044);
			if( i == -1 )
				FG.address = 0;
			else
				FG.address = (i & 0x0F) << 16;

				/* Now, get the resolution */
			FG.resolution = !((inb(PORT0) & 0x20) >> 5);

			cx_display();

			break;
		case FG_BOARD_OFF:
			fg_sleep(10,1);
			outb(0x14, PORT6);
			outb(0x24, PORT6);
			outb(0x58, PORT3);
			fg_sleep(10,1);
			break;
		case FG_SET_BASE_PORT:
			if( arg == 0x230 || arg == 0x238 )
				switch(arg)
				{
					case 0x230: case 0x238:
					case 0x240: case 0x248:
					case 0x250: case 0x258:
					case 0x260: case 0x268:
						rtn = FG.baseport = arg;
						break;
					default:
						rtn = -EINVAL;
				}
			break;
		case FG_GET_BASE_PORT:
			rtn = FG.baseport;
			break;
		case FG_SET_ADDRESS:
	 		fg_sleep(5, 1);
			arg &= 0x0F;
			if( arg >= 0x08 && arg < 0x0D )
			{
				rtn = FG.address = (arg&0x0F)<<16;
				cx_command( (FG.address>>16) | 0x00D0 );
			}
			else
				rtn = -EINVAL;
			break;
		case FG_GET_ADDRESS:
	 		fg_sleep(5, 1);
			i = cx_command(0x0044);
			if( i == -1 )
				FG.address = 0;
			else
				FG.address = (i & 0x0F);
			rtn = i;
			break;
		case FG_ACQUIRE:
			cx_waitvb();
		case FG_ACQUIREF:
			outb(0x07, PORT6); /* ACQUIRE_RESET */
			break;

		case FG_IS_CCIR:
			rtn = (cx_command(0x43) & 0x04) ? 1 : 0;
			break;

		case FG_GRAB:
			rtn = cx_grab();
			break;
		case FG_GRAB_FIELD:
			rtn = cx_grab_field();
			break;
		case FG_DELAY:
			for(i=0; i<arg; i++)
				delay();
			break;
 		case FG_SET_RAM:
 			outb(0x1A | (arg&1), PORT6);
 			break;
 		case FG_SET_VERTICAL_ACCESS:
 			outb(0x04 | (arg&1), PORT6);
 			break;
 		case FG_SET_RESOLUTION:
 			cx_waitvb();
 			FG.resolution = arg&1;
 			outb(0x0A | !(FG.resolution), PORT6);

 			/* Low resolution  = 256x243		(1 64k segment) */
 			/* High resolution = 512x486		(4 64k segments) */

			/* No break here.  Let's return the newly set resolution */
		case FG_GET_RESOLUTION:
			FG.resolution = !((inb(PORT0) & 0x20) >> 5);
				/* The low-resolution flag is stored in bit 5 of port 0 */

			rtn = FG.resolution;
			break;
 		case FG_GET_RGB_LUT:
 			outb(0x00, PORT0);		/* initial LUT address */
 			break;
		case FG_SET_PAGE:
			cx_setpage(arg);
			break;
		case FG_GET_PAGE:
			rtn = cx_getpage();
			break;
		case FG_IMAGEWIDTH:
			rtn = cx_imagewidth();
			break;
		case FG_IMAGEHEIGHT:
			rtn = cx_imageheight();
			break;
		case FG_SET_MODE:
			if( arg < 0 || arg > FG_MODE_MAX )
				rtn = -EINVAL;
			else if( arg != FG.mode )
			{
				FG.fpos[FG.mode] = filep->f_pos;
				FG.mode = arg;
				filep->f_pos = FG.fpos[FG.mode];
			}
			break;
		default:
			return -ENOSYS;
	}

	return(rtn);
}


/* CX100 functions, adapted from ImageNation library */



/* Simply return number of bytes per line for the current resolution
*/
static inline int cx_imagewidth(void)
{
	return( FG.resolution ? 512 : 256 );
}

static inline int cx_imageheight(void)
{
	return( FG.resolution ? 486 : 243 );
}


/* Grabs one field in the current resolution.  In low resolution, the
	frame will be written to the current page.  In high resolution, the
	odd or even rows are written depending on the field grabbed.  Field
	0 writes the even rows beginning with row 0.  Field 1 writes the
	odd rows beginning with row 1.

	Functionally equivalent to grab_field() in CX100 DOS library.
*/
static int cx_grab_field(void)
{
	int field;

#ifdef DEBUG
   printk("cx_grabfield()\n");
#endif
	if( !cx_waitvb() )
		return -1;
	
	field = (inb(PORT6) & 0x04) ? 1 : 0;		/* status(FIELD) */

	outb(0x21, PORT6);		/* set FIELD_GRAB */
	cx_waitvb();
	return field;
}

/* Grabs one frame in the current resolution.  In high resolution, it
	grabs two fields.  In low resolution, it grabs one field.  In low
	resolution, the frame will be written to the current page.  In high
	resolution, the entire video RAM is written to extent of the
	incoming video.  NTSC video contains only 486 lines of data.  The
	rest of RAM is not written or cleared.

	Functionally equivalent to the grab() function in CX100 DOS library.
*/
static int cx_grab(void)
{
	int field;

#ifdef DEBUG
   printk("cx_grab()\n");
#endif
	if( !cx_waitvb() )
		return -1;

	field = (inb(PORT6) & 0x04) ? 1 : 0;		/* status(FIELD) */

/*	if( inb(PORT0) & 0x20 )*/		/* If in low res mode, grab only 1 field */
	if( !FG.resolution )
	{	/* low resolution */
		outb(0x21, PORT6);		/* set FIELD_GRAB */
		cx_waitvb();
	} else
	{	/* high resolution */
		outb(0x23, PORT6);		/* set FRAME_GRAB */
		cx_waitvb();
		cx_waitvb();
	}
	return field;
}


/* Waits until next vertical blank begins.  Vertical blank lasts 1.27 mS */
static int cx_waitvb(void)
/* Returns: 0 if failed, 1 if successful */
{
	int i;

#ifdef DEBUG
   printk("cx_waitvb()\n");
#endif
	i=0;
	while( inb(PORT6) & 0x08 )
	{
		delay();
		if( i++ > 550000 )		/* If it's been more than 1/2 a sec,
											we waited too long */
			return 0;

	}

	i=0;
	while( !(inb(PORT6) & 0x08) )
	{
		delay();
		if( i++ > 550000 )	/* If it's been more than 1/2 a sec,
										we waited too long */
			return 0;

	}

	return 1;
}


static int cx_command(int data)
{
	int ret;

#ifdef DEBUG
   printk("cx_command()\n");
#endif
	outb((char)data, PORT3);
	if( (inb(PORT1) & 0x00C0) != 0x00C0 )
	{
		fg_sleep(55, 1);
		if( (inb(PORT1) & 0x00C0) != 0x00C0 )
			return -1;
	}

		/* commmands 40 and 41 don't get data to port fast enough */
		/* Don't ask me, I didn't make the thing */
	if( data == 0x40 || data == 0x41 )
		/* Note: These aren't time critical requests anyway, so who
			cares if we waste a couple milleseconds here or there.
			(Which is why sleep is set to interruptible */
	{
		fg_sleep(55, 1);
		if( (inb(PORT1) & 0x00C0) != 0x00C0 )
			return -1;
	}

	ret = inb(PORT3) & 0x00FF;

		/* more waiting... */
	outb(0x0045, PORT3);
	if( (inb(PORT1) & 0x00C0) != 0x00C0 )
	{
		fg_sleep(55, 1);
		if( (inb(PORT1) & 0x00C0) != 0x00C0 )
			return -1;
	}

	inb(PORT3);
	return ret;
}

static inline void cx_display(void)
/* This just makes it so that we can read from the CX100's address
	space, if I understand the docs correctly.  Whoever wrote the
	notes for this thing should be shot. */
{
	int i;
#ifdef DEBUG
   printk("cx_display()\n");
#endif
	outb( 0x06, PORT6 );		/* clr(AQUIRE_REQUEST); */

	for(i=0; i<5; i++)		/* one loop seems to be enough */
		outb( 0xff, 0x80 );	/* but we'll do 5 just in case */

	outb( 0x09, PORT6 );		/* set(DISPLAY_RAM); */
}







/*
 * And now the modules code and kernel interface.
 */

static struct file_operations fg_fops = {
	fg_lseek,
	fg_read,
	fg_write,
	NULL,		/* fg_readdir */
	NULL,		/* fg_select */
	fg_ioctl,	/* fg_ioctl */
	NULL,		/* fg_mmap */
	fg_open,
	fg_close,
	NULL		/* fsync */
};


#ifdef __cplusplus
extern "C" {
#endif

extern int printk(const char* fmt, ...);


int init_module( void)
{
#ifdef DEBUG
   printk("cx100: init_module()\n");
#endif
	printk( "cx100.c: init_module called\n");
	if (register_chrdev(major, "fg", &fg_fops)) {
		printk("cx100.c: register_chrdev failed: goodbye world :-(\n");
		return -EIO;
	}

	return 0;
}

void cleanup_module(void)
{
#ifdef DEBUG
   printk("cx100: cleanup_module()\n");
#endif

	if (MOD_IN_USE)
		printk("cx100.c: device busy, remove delayed\n");

	if (unregister_chrdev(major, "fg") != 0)
		printk("cx100.c: cleanup_module failed\n");
	else
		printk("cx100.c: cleanup_module succeeded\n");
}



/*
 * Sleep functions taken from fgrabber.c  for WinVision frame grabber
 * Written by: Carlos Puchol, 1993
 */
 
static unsigned long tvtojiffies(struct timeval *value)
{
	return((unsigned long )value->tv_sec * HZ +
		(unsigned long )(value->tv_usec +
		(1000000 / HZ - 1)) / (1000000 / HZ));
}

static void fg_wake (unsigned long ignored)
{
#ifdef DEBUG
   printk("cx100: fg_wake()\n");
#endif
   /* The timer has just expired */
   wake_up(&fg_timer_queue);
}

static void fg_sleep( unsigned int ms, int interruptable)
{
   struct timeval millisecs;
   static struct timer_list fg_timer = { NULL, NULL, 0, 0, fg_wake };

#ifdef DEBUG
   printk("cx100: fg_sleep()\n");
#endif

   millisecs.tv_sec = 0;
   millisecs.tv_usec = ms * 1000 ;
   del_timer(&fg_timer);
   if (ms) {
      fg_timer.expires = tvtojiffies(&millisecs);
      add_timer(&fg_timer);

		if( interruptable )
					/* shouldn't hurt to make it interruptible */
      	interruptible_sleep_on(&fg_timer_queue);
		else
			sleep_on(&fg_timer_queue);
   }
}
/* End Sleep functions */


#ifdef __cplusplus
}
#endif
