
/* ====================================================================== *\

	fgrabber.c

	This is a Linux device driver for the WinVision frame grabber.
	The WinVision frame grabber is made by Quanta Corp. (1-800-682-1738)

	(C) Carlos Puchol, 1993
	    cpg@cs.utexas.edu
	    P.O. Box 7817
	    Austin, TX 78713-7817

	This piece of code may only be distributed, copied or modified under
	the terms of the GNU GENERAL PUBLIC LICENSE, Version 2, June 1991,
	attached in file COPYING in the distribution. This code comes as is,
	and I hope it is useful to you.

\* ====================================================================== */

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/timer.h>

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

typedef unsigned char byte;

typedef struct {
	int	HSize;
	int	VSize;
	int	HSkip;
	int	VSkip;
	} t_image_header;

/* ioctl command codes */

#define FG_HPOS_SET		0x0001
#define FG_HPOS_GET		0x0002
#define FG_VPOS_SET		0x0003
#define FG_VPOS_GET		0x0004
#define FG_BOARD_SET		0x0005
#define FG_BOARD_GET		0x0006
#define FG_CMAP_SET		0x0007
#define FG_EXPANSION_SET	0x0008

#define LSHORT		0
#define LHALF		1 
#define LNORMAL		2
#define LLONG		3

/* Queue for the driver sleeping on the timer */

struct wait_queue * fg_timer_queue = NULL;

static unsigned int		board = 0x268;
static byte	expansion = 1;
static int	HSize;
static int	VSize;
static int	HSkip, VSkip;
static byte	map[256];

#define NTSC_VIDEO	/* Default */

#ifdef NTSC_VIDEO
static byte	HPos = 17;
static byte	VPos = 16;
#define	HSizeMax	187
#define	VSizeMax	250
#else (* PAL *)
static byte	HPos = 21;
static byte	VPos = 17;
#define	HSizeMax	187
#define	VSizeMax	295
#endif

#define OUT	(outb(0x00, board))
#define IN	(inb(board))

#define CAPTURE_OVER		0x02
#define SYNC			0x01
#define	TOO_MANY		5000

static byte	*image;
static byte	line[(2*HSizeMax)+2];
static int	our_count=0;

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)
{
	/* The timer has just expired */
	wake_up(&fg_timer_queue);
}

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

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

static void expand(void)
{
	int     orig, dest;
	byte    a,b,c,d;

	for (orig=HSizeMax, dest=0; dest < (HSize - 4) ; orig+=3, dest +=5) {
		a = line [orig];
		b = line [orig+1];
		c = line [orig+2];
		d = line [orig+3];
		line[dest] = map[a & 0xFC];
		line[dest + 1] = map [((a+a+b+b+b) / 5) & 0xFC];
		line[dest + 2] = map [((b+b+b+b+c) / 5) & 0xFC];
		line[dest + 3] = map [((b+c+c+c+c) / 5) & 0xFC];
		line[dest + 4] = map [((c+c+c+d+d) / 5) & 0xFC];
        }
}


static void remap(void)
{
	int     orig, dest;

	for (orig=HSizeMax, dest=0; dest < HSize ; orig++, dest ++)
		line[dest] = map[line[orig]];
}

static int fg_linetype(void)
{
	byte    p;
	int     m;

	for ( m=0, p=0; !( p & SYNC) && m < 0xF1; m++)
		p = IN;
	if  (m > 0xF0) return LLONG;
	if  (m < 0x40) return LSHORT;
	if  (m < 0x90) return LHALF;
	return LNORMAL;
}

static void fg_rdline(void)
{
	byte    p;
	int     i;

	for (i=0; i < (HPos+HSkip); i++) (void)IN;

	for (i=HSizeMax, p=0; i < 2*HSizeMax && !(p & SYNC); i ++)
		line[i] = p = IN;
	for (; i < 2*HSizeMax; i ++) line[i] = 0;
	if (expansion)	expand();
	else		remap();
	memcpy_tofs (image + our_count, line, (unsigned long)HSize);
	for (i=0, p=0; !( p & SYNC ) &&  (i < 0xF1) ; i++ )
		p=IN;
}

static int fg_read(struct inode * inode, struct file * file,
			char * buffer, int count)
{
	int	y, l;
	t_image_header	frame;

	OUT;

	fg_sleep (30);
	if (IN & CAPTURE_OVER) {
		fg_sleep(13);
		if (IN & CAPTURE_OVER) {
			fg_sleep(100);
			if (IN & CAPTURE_OVER) {
				return -ENODATA;
			}
		}
	}

	memcpy_fromfs(&frame, buffer, sizeof (frame));
	image = buffer + sizeof(t_image_header);
	HSize =  frame.HSize;
	VSize =  frame.VSize;
	HSkip =  frame.HSkip;
	VSkip =  frame.VSkip;
	our_count=0;

	fg_linetype();
	fg_linetype();
	for (y=0, l=0; (l != LHALF) && (l != LLONG) && (y < TOO_MANY); y++)
		l = fg_linetype();
	if (y == TOO_MANY) return -ENODATA;
	for (y=0; y < (VPos+VSkip); y++) fg_linetype();
	for (y=0; y < VSize && y < VSizeMax; y++) {
		fg_rdline();
		our_count = (y + 1) * HSize;
	}
	return our_count;
}

static int fg_open(struct inode *inode, struct file *file)
{
	int	i;

	for (i=0; i < 256; i++)
		map[i] = (byte) i;
	return 0;
}

static void fg_release(struct inode *inode, struct file *file)
{
}

int fg_ioctl(struct inode * inode, struct file * file,
	unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
		case FG_HPOS_SET:
			HPos = (int) arg;
			break;
		case FG_HPOS_GET:
			memcpy_tofs ((int *) arg, &HPos, sizeof(HPos)); 
			break;
		case FG_VPOS_SET:
			VPos = (int) arg;
			break;
		case FG_VPOS_GET:
			memcpy_tofs ((int *) arg, &VPos, sizeof(VPos)); 
			break;
		case FG_BOARD_SET:
			board = (int) arg;
			break;
		case FG_BOARD_GET:
			memcpy_tofs ((int *) arg, &board, sizeof(board));
			break;
		case FG_CMAP_SET:
			memcpy_fromfs ((char *)map, (char *) arg, 256);
			break;
		case FG_EXPANSION_SET:
			expansion = (byte) arg;
			break;
		default:
			return -ENOSYS;
	}
	return 0;
}

struct file_operations fg_fops = {
	NULL,		/* fg_seek */
	fg_read,	/* fg_read */
	NULL,		/* fg_write */
	NULL,		/* fg_readdir */
	NULL,		/* fg_select */
	fg_ioctl,	/* fg_ioctl */
	NULL,		/* fg_mmap */
	fg_open,	/* fg_open */
	fg_release	/* fg_release */
};

unsigned long fg_init(unsigned long kmem_start)
{

	printk("Frame Grabber driver installed.\n");
	if (register_chrdev(27, "Frame Grabber", &fg_fops))
		printk("Frame Grabber driver error: Cannot register to major device 27!\n");
	return kmem_start;
}

