/*  Copyright (C) 2003 Cherry George Mathew <cherry@freeshell.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <linux/version.h> 
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/kdev_t.h>
#include "pvcard.h"
#include "pvcl.h" 
#include "pvproc.h"
#include <asm/io.h>
#include <asm/bitops.h>

/* const declarations and static allocations */

/* card ids ends with vendor id as marker. Number of cards is defined \
 * as MAX_CARDS. Will add PCI_DEVICE_ID_CIRRUS_5465 Later.
 */
const int clgd54xx_card_id[]={PCI_DEVICE_ID_CIRRUS_5480,\
			      PCI_DEVICE_ID_CIRRUS_5446,\
			      PCI_VENDOR_ID_CIRRUS};

/* Decided against kmalloc for the moment. Would probably be efficient
 * if we dealt with stuff like multiple cards, etc. not for the moment,
 * though.
 */


/* Default max screen size hardcoded to ???? */
static struct video_buffer vbuf_s = { 
};

static struct video_picture vpict_s;

/* TODO: Chroma keying.....Default _no_ chromakeying bcos xawtv has no
 *   support for it with V4L1. Someday.... Sigh ......*/

static struct video_window vwin_s = { 
/*  	chromakey:  GD_CHROMA_KEY,  */
/*   	flags:      VIDEO_WINDOW_CHROMAKEY   */
};

/* Tuner properties hardcoded. */
static struct video_tuner vtun_s = {
	name:        "Television",
	rangelow:    0,
	rangehigh:   0x7FFFFFFF,
	flags:       VIDEO_TUNER_PAL, 
	mode:        VIDEO_MODE_PAL
};

/* Single channel support at the moment. S-Video and Composite ignored. */
static struct video_channel vchan_s = {
	name:        "Television",
	tuners:      1,
	flags:       VIDEO_VC_TUNER | VIDEO_VC_AUDIO,
	type:        VIDEO_TYPE_TV,
	norm:        VIDEO_MODE_PAL
};

static struct gd_status_t dstat_s = { 
	vbuf_p:     &vbuf_s,
	vwin_p:     &vwin_s,
	vtun_p:     &vtun_s,
	vchan_p:    &vchan_s,
	vpict_p:    &vpict_s,
};

/* Initializing a new adapter named clgd54xx. Right now, we'll make do 
   with a static declaration. Not good enough for driving multiple cards.
*/


static struct clgd54xx_card clgd54xx_card_info = {
	spun_lock:    SPIN_LOCK_UNLOCKED,
	drv_stat_p:   &dstat_s,
	model:        PVCLPP_COMBO
};

static struct i2c_algo_bit_data clgd54xx_bitbang_adap = {
	data:         &clgd54xx_card_info,
	setsda:       gd54xx_setsda,
	setscl:       gd54xx_setscl,
	getsda:       gd54xx_getsda,
	getscl:       gd54xx_getscl,
	udelay:       16,
	mdelay:       10,
	timeout:      200,
};

static struct i2c_adapter clgd54xx_adap = {
	name:        "clgd54xx",

	/* Now make i2c_adapter->algo_data point to our 
	 * i2c_algo_bit_data , ie; link adapter and algo.
	 */

	id:          I2C_ALGO_BIT | I2C_HW_B_BT848,
	algo_data:   &clgd54xx_bitbang_adap,
};

/* variable declarations and initializations */

unsigned int debug = 0;
unsigned int re_entry = 0;

/* Adapter - Low level functions  */

/* Note:
 * 5480 and 5446B can use either MMIO or PIO
 * 5446A must use PIO
 * 5465 must use MMIO
 */

unsigned io_readb(unsigned port)
{
	vprintk("io_io_read on port %p\n", (char *) port);
	return inb(port);
}

void io_writeb(unsigned data, unsigned port)
{
	vprintk("io_io_write on port %p\n", (char *) port);
	outb(data, port);
} 

/***************  TODO: IMPLEMENT PCI MEM MAPPING *****************


unsigned m_io_readb_proc(unsigned port)
{
	vprintk("m_io_read on port %p\n", (char *) port);
	return readb(port);
}

void m_io_writeb_proc(unsigned data, unsigned port)
{
	vprintk("m_io_write on port %p\n", (char *) port);
	writeb(data, port);
}

*************************************************************************/
void gd_write_sr(struct clgd54xx_card *card_p,unsigned char datum,unsigned reg) 
{
	spin_lock_irqsave(&card_p->spun_lock, card_p->spinflags);
	io_writeb(reg, card_p->gd_io_base + GD_SR_OFFSET); 
	io_writeb(datum, card_p->gd_io_base + GD_SR_OFFSET + 1);
	spin_unlock_irqrestore(&card_p->spun_lock, card_p->spinflags);

}


void gd_write_gr(struct clgd54xx_card *card_p, unsigned char datum,unsigned reg) 
{ 
        spin_lock_irqsave(&card_p->spun_lock,card_p->spinflags);
	io_writeb(reg, card_p->gd_io_base + GD_GR_OFFSET); 
	io_writeb(datum, card_p->gd_io_base + GD_GR_OFFSET + 1);
	spin_unlock_irqrestore(&card_p->spun_lock,card_p->spinflags);

}

void gd_write_cr(struct clgd54xx_card *card_p, unsigned char datum,unsigned reg) 
{
        spin_lock_irqsave(&card_p->spun_lock, card_p->spinflags);
	io_writeb(reg, card_p->gd_io_base + GD_CR_OFFSET);
	io_writeb(datum, card_p->gd_io_base + GD_CR_OFFSET + 1);
	spin_unlock_irqrestore(&card_p->spun_lock,card_p->spinflags);

}

unsigned gd_read_sr(struct clgd54xx_card *card_p, unsigned reg) 
{
	unsigned value;
	spin_lock_irqsave(&card_p->spun_lock,card_p->spinflags);
	io_writeb(reg, card_p->gd_io_base + GD_SR_OFFSET);
	value = io_readb(card_p->gd_io_base + GD_SR_OFFSET + 1);
	spin_unlock_irqrestore(&card_p->spun_lock,card_p->spinflags);
	return value;
}

unsigned gd_read_gr(struct clgd54xx_card *card_p, unsigned reg)
{
	unsigned value;
        spin_lock_irqsave(&card_p->spun_lock, card_p->spinflags);
	io_writeb(reg, card_p->gd_io_base + GD_GR_OFFSET);
	value = io_readb(card_p->gd_io_base + GD_GR_OFFSET + 1);
	spin_unlock_irqrestore(&card_p->spun_lock,card_p->spinflags);
	return value;
}

unsigned gd_read_cr(struct clgd54xx_card *card_p, unsigned reg)
{
	unsigned value;
	spin_lock_irqsave(&card_p->spun_lock, card_p->spinflags);
	io_writeb(reg, card_p->gd_io_base + GD_CR_OFFSET); 
	value = io_readb(card_p->gd_io_base + GD_CR_OFFSET + 1);
	spin_unlock_irqrestore(&card_p->spun_lock, card_p->spinflags);
	return value;
}
 

void gd54xx_setsda (void *bit_adap_dat, int state)
{
	struct clgd54xx_card *data=bit_adap_dat;

	/* Switch on I2C interface */
	set_bit(6, &data->i2c_state);

	/* Set/Clear bit */ 
	state ? set_bit(1, &(data->i2c_state)) : clear_bit(1, &(data->i2c_state));

	gd_write_sr(data, data->i2c_state, 0x8);
}

void gd54xx_setscl (void *bit_adap_dat, int state)
{
	struct clgd54xx_card *data=bit_adap_dat;

	set_bit(6, &data->i2c_state);
	state ? set_bit(0, &(data->i2c_state)) : clear_bit(0, &(data->i2c_state));

	gd_write_sr(data, data->i2c_state, 0x8);
}

int gd54xx_getsda (void *bit_adap_dat)
{
	struct clgd54xx_card *data=bit_adap_dat;

	return (((data->i2c_state = gd_read_sr(data, 0x8)) >>7)&0x1);

	return 0;
}

int gd54xx_getscl (void *bit_adap_dat)
{
	struct clgd54xx_card *data=bit_adap_dat;
	return (((data->i2c_state = gd_read_sr(data, 0x8)) >>2)&0x1);
	return 0;
}

/* Adapter functions - high level */

int i2c_clgd54xx_init_adapter(struct clgd54xx_card *card_p, 
			      struct i2c_adapter * adap,
			      struct i2c_algo_bit_data * bitadap)
{

	/* Let's initialize some of the data structures.
	 * First plug in card adapter and card bit bang algo
	 * into card info.
	 */

	card_p->clgd54xx_adapter_p = adap;
	card_p->clgd54xx_bitbang_adapter_p = bitadap;

	return 	i2c_bit_add_bus(card_p->clgd54xx_adapter_p);

}

int i2c_clgd54xx_cleanup_adapter(struct clgd54xx_card *card_p){

	return i2c_bit_del_bus(card_p->clgd54xx_adapter_p);
}

int i2c_clgd54xx_probe_card(struct clgd54xx_card *card_p)
{
	card_p->gd_io_base=0;
	
	/* Memory Mapped IO not supported at the moment.
	 * This driver won't work for multiple cards.
	 */
	return 0;
}

int i2c_clgd54xx_find_card(struct clgd54xx_card *card_p){
	
        struct pci_dev *dev = NULL;
	int loop_count=0;
	do{
		if ((dev = pci_find_device(clgd54xx_card_id[MAX_CARDS],clgd54xx_card_id[loop_count], dev))) {

			printk(KERN_INFO
			       "pvcl: Found %s\n", dev->name);
			card_p->clgd54xx_pci_dev_p = dev;
			card_p->clgd54xx_pci_dev_id =
				clgd54xx_card_id[loop_count];
			
			printk(KERN_INFO
			       "pvcl: Detected %dMB video ram.\n",
			       gd_count_ram(card_p));
			
			return 0;
		}
		loop_count++;
	} while(clgd54xx_card_id[loop_count]!=clgd54xx_card_id[MAX_CARDS]);

	printk(KERN_WARNING
	       "pvcl: Sorry. Could not find a Cirrus Logic Chip.\n");
	return -ENODEV;

}


/*
 *  gd_count_ram() is hacked from gd5480.c from the xtv package.
 *  xtv is copyrighted to Itai Nahshon <nahshon@actcom.co.il>.
 *  Used with permission from the author.
 */


int
gd_count_ram(struct clgd54xx_card *card_p)
{
	int videoram = 1;
	int SR0F, SR17;

	unsigned short chip_type = card_p->clgd54xx_pci_dev_id;

	switch (chip_type) {
        case PCI_DEVICE_ID_CIRRUS_5446:
		videoram = 1;
	
		SR0F = gd_read_sr(card_p, 0x0F);
		SR17 = gd_read_sr(card_p, 0x17);
		if ((SR0F & 0x18) == 0x18) {
			if(SR0F & 0x80) {
				if(SR17 & 0x80)
					videoram = 2;
				else if(SR17 & 0x02)
					videoram = 3;
				else
					videoram = 4;
			}
			else {
				if((SR17 & 80) == 0)
					videoram = 2;
			}
		}
		break;
		
	case PCI_DEVICE_ID_CIRRUS_5480:
		videoram = 1;
		SR0F = gd_read_sr(card_p, 0x0F);
		if ((SR0F & 0x18) == 0x18) {	/* 2 or 4 MB */
			videoram = 2;
			if (SR0F & 0x80)	/* Second bank enable */
				videoram = 4;
		}
		break;
		
	case PCI_DEVICE_ID_CIRRUS_5465:
		videoram = 4;
		break;
	}

	card_p->vram = videoram;
	return videoram;
}


int __init i2c_clgd54xx_init(struct clgd54xx_card *card_p,
			     struct i2c_adapter *adap,
			     struct i2c_algo_bit_data *bitadap)
{
	int ret_val;

	/* This adapter is non-re-entrant at the moment */

	if(re_entry) return -EBUSY;

	re_entry = 1;

	if( (ret_val = i2c_clgd54xx_find_card(card_p)) ){
		return ret_val;
	}
	
	if( (ret_val = i2c_clgd54xx_probe_card(card_p)) ){
		return ret_val;
	}
	
	if( (ret_val = i2c_clgd54xx_init_adapter(card_p, adap, bitadap)) ){
		return ret_val;
	}
	
	return ret_val;
}

int __init i2c_clgd54xx_cleanup(struct clgd54xx_card *card_p)
{
	re_entry=0;

	return i2c_clgd54xx_cleanup_adapter( card_p );
	
}

/* i2c client support */
void do_client_ioctl(struct file *file, unsigned int cmd, void *arg)
{
	struct clgd54xx_card  *card_p = file->private_data;
	struct i2c_adapter *adap = card_p->clgd54xx_adapter_p;
	
	int i;
	
        for (i = 0; i < I2C_CLIENT_MAX; i++) {
                if (NULL == adap->clients[i])
                        continue;
                if (NULL == adap->clients[i]->driver->command)
                        continue;
                adap->clients[i]->driver->command(
						     adap->clients[i],cmd,arg);
        }

	
}

/* VGA support functions. */

/* gd_bit_copy() - used for copying data to/from VGA registers */

void gd_bit_copy(unsigned long * dest, int dest_start,  
		 unsigned long * src, int src_start, int src_stop)
{
	for(; src_start<= src_stop; src_start++, dest_start++)
		{
			if(test_bit(src_start, src))
				set_bit(dest_start, dest);
			else
				clear_bit(dest_start, dest);
		}
				   
}

void gd_enable_window(struct clgd54xx_card * card_p)
{
	unsigned long CR3E, CR50, CR51, CR58, CR5C;

	CR3E = gd_read_cr(card_p, 0x3e);
	CR50 = gd_read_cr(card_p, 0x50);
	CR51 = gd_read_cr(card_p, 0x51);
	CR58 = gd_read_cr(card_p, 0x58);
	CR5C = gd_read_cr(card_p, 0x5c);

	/* Disable Teletext CR5C[7] = 0 */
 	clear_bit(7,  &CR5C); 

/* 	Capture all frames CR50[7], CR50[2], CR58[6] = 0,0,0 */

 	clear_bit(7, &CR50); 
 	clear_bit(2, &CR50); 
	clear_bit(6, &CR58); 
	
	/*	Set Capture input to VPORT CR50[1:0] = 11  */

	set_bit(0, &CR50);           /* Falling edge of HREF ends  */  
	set_bit(1, &CR50);           /* capture line. */
	
	/* Enable Capture CR51[3] = 1 */
  	set_bit(3, &CR51); 
	
	/* Enable Video Window CR3E[0] = 1 */
	set_bit(0, &CR3E);

	gd_write_cr(card_p, CR5C, 0x5c);
	gd_write_cr(card_p, CR58, 0x58);
	gd_write_cr(card_p, CR51, 0x51);
	gd_write_cr(card_p, CR50, 0x50);
	gd_write_cr(card_p, CR3E, 0x3e);

}

void gd_disable_window(struct clgd54xx_card * card_p)
{
	unsigned long CR3E, CR50, CR51;
	 
	CR3E = gd_read_cr(card_p, 0x3e);
	CR50 = gd_read_cr(card_p, 0x50);
	CR51 = gd_read_cr(card_p, 0x51);


	/* 	Disable Capture CR51[3] = 0 */
  	clear_bit(3, &CR51);  

	/*	Reset Capture input to standard feature 
		connector CR50[1:0] = 11  */

	clear_bit(0, &CR50);           /* Falling edge of HREF ends capture */
	clear_bit(1, &CR50);  

	/* Disable Video Window CR3E[0] = 0 */
	clear_bit(0, &CR3E);


	gd_write_cr(card_p, CR51, 0x51);
	gd_write_cr(card_p, CR50, 0x50);
	gd_write_cr(card_p, CR3E, 0x3e);
}

void gd_set_vbuf1(struct clgd54xx_card * card_p, unsigned long ptr)
{
        unsigned long CR3A, CR3B, CR3C, CR5D;


	CR3A = gd_read_cr(card_p, 0x3a);
	CR3B = gd_read_cr(card_p, 0x3b);
	CR3C = gd_read_cr(card_p, 0x3c);
	CR5D = gd_read_cr(card_p, 0x5d);

	/* CR5D[3:2] = ptr[1:0]  */

	gd_bit_copy( &CR5D, 2, &ptr, 0, 1);

	/* CR3A = ptr[9:2] */

	gd_bit_copy( &CR3A, 0, &ptr, 2, 9);

	
	/* CR3B = ptr[17:10] */

	gd_bit_copy(&CR3B, 0, &ptr, 10, 17);

	/* CR3C[3:0] = ptr[21:18] */

	gd_bit_copy(&CR3C, 0, &ptr, 18, 21);

	gd_write_cr(card_p, CR3A, 0x3a);
	gd_write_cr(card_p, CR3B, 0x3b);
	gd_write_cr(card_p, CR3C, 0x3c);
	gd_write_cr(card_p, CR5D, 0x5d);

}

void gd_set_vbuf2(struct clgd54xx_card * card_p, unsigned long ptr)
{
        unsigned long CR59, CR5A, CR58, CR5D;

	CR59 = gd_read_cr(card_p, 0x59);
	CR5A = gd_read_cr(card_p, 0x5a);
	CR58 = gd_read_cr(card_p, 0x58);
	CR5D = gd_read_cr(card_p, 0x5d);

	/* CR5D[3:2] = ptr[1:0] */

	gd_bit_copy(&CR5D, 2, &ptr, 0, 1);

	/* CR59 = ptr[9:2]  */

	gd_bit_copy(&CR59, 0, &ptr, 2, 9);
	
	/* CR5A = ptr[17:10] */

	gd_bit_copy(&CR5A, 0, &ptr, 10, 17);

	/* CR58[3:0] = ptr[21:18] */

	gd_bit_copy(&CR58, 0, &ptr, 18, 21);

	gd_write_cr(card_p, CR59, 0x59);
	gd_write_cr(card_p, CR5A, 0x5a);
	gd_write_cr(card_p, CR58, 0x58);
	gd_write_cr(card_p, CR5D, 0x5d);

}

unsigned long gd_get_vbuf1(struct clgd54xx_card * card_p)
{
        unsigned long CR3A, CR3B, CR3C, CR5D;
	unsigned long ptr;

	CR3A = gd_read_cr(card_p, 0x3a);
	CR3B = gd_read_cr(card_p, 0x3b);
	CR3C = gd_read_cr(card_p, 0x3c);
	CR5D = gd_read_cr(card_p, 0x5d);

	/*  ptr[1:0]  = CR5D[3:2] */
	ptr = 0;
	gd_bit_copy( &ptr, 0, &CR5D, 2, 3);

	/*  ptr[9:2] = CR3A */

	gd_bit_copy( &ptr, 2, &CR3A, 0, 7);

	
	/* ptr[17:10] = CR3B */

	gd_bit_copy(&ptr, 10, &CR3B, 0, 7);

	/* ptr[21:18] = CR3C[3:0] */

	gd_bit_copy(&ptr, 18, &CR3C, 0, 3);

	return ptr;

}


unsigned long gd_get_vbuf2(struct clgd54xx_card * card_p)
{
        unsigned long CR59, CR5A, CR58, CR5D;
	unsigned long ptr;

	CR59 = gd_read_cr(card_p, 0x59);
	CR5A = gd_read_cr(card_p, 0x5a);
	CR58 = gd_read_cr(card_p, 0x58);
	CR5D = gd_read_cr(card_p, 0x5d);

	/* ptr[1:0] = CR5D[3:2] */
	ptr = 0;
	gd_bit_copy(&ptr, 0, &CR5D, 2, 3);

	/* ptr[9:2] = CR59 */

	gd_bit_copy(&ptr, 2, &CR59, 0, 7);

	/* ptr[17:10] = CR5A */

	gd_bit_copy(&ptr, 10, &CR5A, 0, 7);

	/* ptr[21:18] = CR58[3:0] */
	
	gd_bit_copy(&ptr, 18, &CR58, 0, 3);
	return ptr;

}

void gd_set_pitch(struct clgd54xx_card * card_p, unsigned long offset)
{
	unsigned long CR3C, CR3D;
	
	CR3C = gd_read_cr(card_p, 0x3c);
	CR3D = gd_read_cr(card_p, 0x3d);

	/* CR3C[5] = offset[11], CR3D = offset[10:3] */

	gd_bit_copy(&CR3C, 5, &offset ,11, 11);

	gd_bit_copy(&CR3D, 0, &offset, 3, 10);

	gd_write_cr(card_p, CR3C, 0x3c);
	gd_write_cr(card_p, CR3D, 0x3d);

}


unsigned long gd_get_pitch(struct clgd54xx_card * card_p)
{

	unsigned long CR3C, CR3D;
	unsigned long offset ;


        CR3C = gd_read_cr(card_p, 0x3c);
        CR3D = gd_read_cr(card_p, 0x3d);
	
	/* offset[11] = CR3C[5] , offset[10:3] = CR3D */
	offset = 0;
	gd_bit_copy(&offset, 11, &CR3C, 5, 5);
	gd_bit_copy(&offset, 3, &CR3D, 0, 7);

	return offset;
}



void gd_init_video( struct clgd54xx_card * card_p )
{
	unsigned long temp_reg;
	unsigned long  CR31, CR32, CR3E, CR3F, CR50, CR51, CR56, 
		CR57, CR58, CR5C, CR5E;

/* 	unsigned long GRC, GRD; */
 
	CR31 = gd_read_cr(card_p, 0x31);
	CR32 = gd_read_cr(card_p, 0x32);
	CR3E = gd_read_cr(card_p, 0x3e);
	CR3F = gd_read_cr(card_p, 0x3f);
	CR50 = gd_read_cr(card_p, 0x50);
	CR51 = gd_read_cr(card_p, 0x51);
	CR56 = gd_read_cr(card_p, 0x56);
	CR57 = gd_read_cr(card_p, 0x57);
	CR58 = gd_read_cr(card_p, 0x58); /* Dependancy only on maxheight */
	CR5C = gd_read_cr(card_p, 0x5c);
	CR5E = gd_read_cr(card_p, 0x5e);


	/* 	GRC = gd_read_gr(card_p, 0x0c); */
	/* 	GRD = gd_read_gr(card_p, 0x0d); */

	/* Set the maximum scanlines to 512, we don't want 
	 *  wanton clipping. */

	temp_reg = 0xff;
	gd_bit_copy(&CR57, 0, &temp_reg, 0, 7);
	set_bit(5, &CR58);

	/* Luminance only capture disabled */
	/* CR5C[5] = 0 */
	
	clear_bit(5, &CR5C);

	/* Count lines upto capture, set to zero */
	/* CR56[4:0] = 0 */

	temp_reg = 0;
	gd_bit_copy(&CR56, 0, &temp_reg, 0, 4);

	/* Non - Interlaced capture */
	/* CR50[6] = 0 */

	clear_bit(6, &CR50);

	/* 8bits, VPort width */
	/* CR50[4] = 0; */

	clear_bit(4, &CR50);

	/* Double clock capture */
	/* CR50[3] = 1; */
	
	set_bit(3, &CR50);
	  

	/* Video Capture (Not Teletext) */
	/* CR5C[7] = 0 */

	clear_bit(7, &CR5C);

	/* Capture Data Format - YUV 4:2:2 */
	/* CR51[2:0] = 000 */

	clear_bit(0, &CR51);
	clear_bit(1, &CR51);
	clear_bit(2, &CR51);

	/* Display Data Format - YUV 4:2:2 */
	/* CR3E[3:1] = 000 */
	/* CR3F[4] = 0 */

	clear_bit(1, &CR3E);
	clear_bit(2, &CR3E);
	clear_bit(3, &CR3E);
	clear_bit(4, &CR3F);

	/* Double Buffer Control - Autoswitch */
	/* CR5E[5:4] = 10 */

	clear_bit(4, &CR5E);
	set_bit(5, &CR5E);


	/* Set Zoom = 1:1 ie., ZoomX = ZoomY = 0 */
	/* ZoomX = CR31, ZoomY = CR32 */
	CR31 = CR32 = 0;
	
	/* Also, Zoom mode set to 1; ie; line replication, 
	 * in anticipation of chromakeying..
	 */
	set_bit(4, &CR3E);

	/* Occlusion set to Colour Key, tagged compare (16 bit). */
	/* CR1D[5:3] = 011 */

	/* 	set_bit(3, &CR1D); */
	/* 	clear_bit(4, &CR1D); */
	/* 	clear_bit(5, &CR1D); */
       


	/* Set the Chroma key. */

	/* 	GRC = 0; */
	/* 	GRD = GD_CHROMA_KEY; */
       
	/* Switch on Chromakeying */
	/* 	set_bit(7, &CR3E); */

	/* 	gd_write_gr(card_p, GRC, 0x0c); */
	/* 	gd_write_gr(card_p, GRD, 0x0d); */

	gd_write_cr(card_p, CR50, 0x50);
	gd_write_cr(card_p, CR51, 0x51);
	gd_write_cr(card_p, CR56, 0x56);
	gd_write_cr(card_p, CR57, 0x57);
	gd_write_cr(card_p, CR58, 0x58); /* May be safely removed with Mx ht.*/
	gd_write_cr(card_p, CR5C, 0x5c);
	gd_write_cr(card_p, CR5E, 0x5e);
	gd_write_cr(card_p, CR31, 0x31);
	gd_write_cr(card_p, CR32, 0x32);
	gd_write_cr(card_p, CR3E, 0x3e);
	gd_write_cr(card_p, CR3F, 0x3f);
}

void gd_get_window(struct clgd54xx_card * card_p,
		   struct video_window *winstruct,
		   struct video_buffer * vbuf)
{
	int depth;
	unsigned long R1SZ, R1Adjust, R2SZ, R2Adjust, R2DSZ, WVS, WVE, WAO;
 	unsigned long CR33, CR34, CR35, CR36, CR37, CR38, CR39, CR5D;

	depth = (vbuf->depth == 32 ? 24 : vbuf->depth);
        CR33 = gd_read_cr(card_p, 0x33);
	CR34 = gd_read_cr(card_p, 0x34);
	CR35 = gd_read_cr(card_p, 0x35);
	CR36 = gd_read_cr(card_p, 0x36);
	CR37 = gd_read_cr(card_p, 0x37);
	CR38 = gd_read_cr(card_p, 0x38);
	CR39 = gd_read_cr(card_p, 0x39);
	CR5D = gd_read_cr(card_p, 0x5d);


	/* R1SZ = CR36[1:0] CR33 */
	R1SZ = 0;
	gd_bit_copy(&R1SZ, 8, &CR36, 0, 1);
	gd_bit_copy(&R1SZ, 0, &CR33, 0, 7);
	
	/* R1Adjust = CR5D[1:0] */

	R1Adjust = 0;
	gd_bit_copy(&R1Adjust, 0, &CR5D, 0, 1);

	/* R2SZ = CR36[3:2] CR34 */
	R2SZ = 0;
	gd_bit_copy(&R2SZ, 8, &CR36, 2, 3);
	gd_bit_copy(&R2SZ, 0, &CR34, 0, 7);

	/* R2Adjust = CR5D[5:4] */
	R2Adjust = 0;
	gd_bit_copy(&R2Adjust, 0, &CR5D, 4, 5);

	/* R2DSZ = CR36[5:4] CR35 */
	R2DSZ = 0;
	gd_bit_copy(&R2DSZ, 8, &CR36, 4, 5);
	gd_bit_copy(&R2DSZ, 0, &CR35, 0, 7);

	/* WVS = CR39[1:0] CR37 */
	WVS = 0;
	gd_bit_copy(&WVS, 8, &CR39, 0, 1);
	gd_bit_copy(&WVS, 0, &CR37, 0, 7);

	/* WVE = CR39[3:2] CR38 */
	WVE = 0;
	gd_bit_copy(&WVE, 8, &CR39, 2, 3);
	gd_bit_copy(&WVE, 0, &CR38, 0, 7);

	WAO = 0;

	WAO = gd_get_pitch(card_p);


	tprintk("gd_get_window() hardware registers read:\n
                depth = %d bpp \n
                R1SZ = %ld\n
                R1Adjust = %ld\n
                R2SZ = %ld\n 
                R2Adjust = %ld\n 
                R2DSZ = %ld\n 
                WVS=%ld\n WVE=%ld\n 
                WAO=%ld\n", depth,
		R1SZ, R1Adjust, R2SZ, R2Adjust, R2DSZ, WVS, WVE, WAO);

	winstruct->x = R1SZ * 32 / depth + R1Adjust * 8 / depth;

	winstruct->width = R2SZ * 32 / depth + R2Adjust * 8 / depth;

	winstruct->y = WVS;
	winstruct->height = WVE - WVS;

}


void gd_set_window(struct clgd54xx_card * card_p,
		   struct video_window *winasked,
		   struct video_window *wingiven,
		   struct video_buffer *vbuf)
{
	int depth;
	unsigned long R1SZ, R1Adjust, R2SZ, R2Adjust, R2DSZ, WVS, WVE, WAO;
	unsigned ZOOMY, CR32;
	unsigned long CR33, CR34, CR35, CR36, CR37, CR38, CR39, CR5D;

	depth = (vbuf->depth == 32 ? 24 : vbuf->depth);

	/* Safety Checks and clipping, for window borders. */

	winasked->x = winasked->x < 0 ?
		0 : winasked->x;

	winasked->y = winasked->y < 0 ?
		0 : winasked->y;
		
	winasked->width = (vbuf->width - winasked->x) < winasked->width ?
		(vbuf->width - winasked->x) : winasked->width;

	winasked->height = (vbuf->height - winasked->y) < winasked->height ?
		(vbuf->height - winasked->y) : winasked->height;


	R1SZ = (winasked->x * depth) / 32;
	R1Adjust = (winasked->x * depth - R1SZ * 32) / 8;
	
	R2SZ = (winasked->width * depth) / 32;
	R2Adjust = (winasked->width * depth - R2SZ * 32) / 8;

	/*ugly hack. only tried on 24bpp and 16bpp eqn is. vbpp/gbpp * .5 */

	R2DSZ = R2SZ * 16 / depth + R2Adjust * 4 / depth;

	WVS = winasked->y;

	WVE = WVS + winasked->height;

	WAO = R2SZ * 4; 

	/* Zoom treatment. Doing zoom just for Vlines.
	 * Notes: Occlusion, ie; chromakeying and line interpolation 
	 * (in the case of zooming) are mutually exclusive. 
	 */
	
	if(wingiven->height < winasked->height)
		{
			ZOOMY = (wingiven->height * 256) / winasked->height - 5;
			CR32 = ZOOMY;
		}
	else
		CR32 = 0;      /* Don't want old Zoom values lying about. */
			
			

	/* CR36[1:0] CR33 = R1SZ */
	gd_bit_copy(&CR36, 0, &R1SZ, 8, 9);
	gd_bit_copy(&CR33, 0, &R1SZ, 0, 7);
	
	/* CR5D[1:0] = R1Adjust */
	gd_bit_copy(&CR5D, 0, &R1Adjust, 0, 1);

	/* CR36[3:2] CR34 = R2SZ */
	gd_bit_copy(&CR36, 2, &R2SZ, 8, 9);
	gd_bit_copy(&CR34, 0, &R2SZ, 0, 7);

	/* CR5D[5:4] = R2Adjust */
	
	gd_bit_copy(&CR5D, 4, &R2Adjust, 0, 1);

	/* CR36[5:4] CR35 = R2DSZ */
	
	gd_bit_copy(&CR36, 4, &R2DSZ, 8, 9);
	gd_bit_copy(&CR35, 0, &R2DSZ, 0, 7);

	/* CR39[1:0] CR37 = WVS */
	
	gd_bit_copy(&CR39, 0, &WVS, 8, 9);
	gd_bit_copy(&CR37, 0, &WVS, 0, 7);

	/* CR39[3:2] CR38 = WVE */
	
	gd_bit_copy(&CR39, 2, &WVE, 8, 9);
	gd_bit_copy(&CR38, 0, &WVE, 0, 7);

	/* CR36[5:4] CR35 = R2DSZ */

	gd_bit_copy(&CR36, 4, &R2DSZ, 8, 9);
	gd_bit_copy(&CR35, 0, &R2DSZ, 0, 7);

	gd_set_pitch(card_p, WAO);

	tprintk("gd_set_window() hardware registers set to:\n
                R1SZ = %ld\n
                R1Adjust = %ld\n
                R2SZ = %ld\n 
                R2Adjust = %ld\n 
                R2DSZ = %ld\n 
                WVS=%ld\n WVE=%ld\n 
                WAO=%ld\n", 
		R1SZ, R1Adjust, R2SZ, R2Adjust, R2DSZ, WVS, WVE, WAO);

	  	gd_write_cr(card_p, CR5D, 0x5d);  
		gd_write_cr(card_p, CR39, 0x39);  
		gd_write_cr(card_p, CR38, 0x38);  
		gd_write_cr(card_p, CR37, 0x37);  
		gd_write_cr(card_p, CR36, 0x36);  
		gd_write_cr(card_p, CR35, 0x35);  
		gd_write_cr(card_p, CR34, 0x34);  
		gd_write_cr(card_p, CR33, 0x33);  
		gd_write_cr(card_p, CR32, 0x32);  
}

/* returns clipped windows, according to available ram, with
 * video buffer starting as close to the end of video ram as possible.
 */

long gd_window_init(struct clgd54xx_card * card_p)
{

	int scrn_depth;
	unsigned long vbuf1_ptr, vbuf2_ptr, scrn_ht, scrn_wt, vram;

	vram = card_p->vram = gd_count_ram(card_p);

	scrn_ht = card_p->drv_stat_p->vbuf_p->height;
	scrn_wt = card_p->drv_stat_p->vbuf_p->width;
	scrn_depth = card_p->drv_stat_p->vbuf_p->depth;
	scrn_depth = scrn_depth == 32 ? 24 :scrn_depth;

	vbuf1_ptr = vram * 1048576 - 16384 - scrn_ht * scrn_wt * 2;

	/* Eeeks!!! No check for video data spilling over into framebuffer
	 * space. OK folk. Get ready for full screen video! 

	if(vbuf1_ptr < (scrn_ht * scrn_wt * scrn_depth / 8)) return -1;
	
	*/

	vbuf2_ptr = vbuf1_ptr;

	gd_init_video(card_p);

	printk("Marker \n");

	gd_set_vbuf1(card_p, vbuf1_ptr);
	gd_set_vbuf2(card_p, vbuf2_ptr);

	return vbuf2_ptr;

}

/* V4L Driver Callback functions */

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

	unsigned int minor = minor(inode->i_rdev);
	struct clgd54xx_card * card_p;


	file->private_data = &clgd54xx_card_info;
	card_p = file->private_data;

	do_client_ioctl(file, VPROC_INIT, &card_p->model);

	dprintk("pvcl_open called on minor %d\n", minor);

	return 0;
}

static int pvcl_release(struct inode *inode, struct file *file)
{
	file->private_data = NULL;
	
	dprintk("pvcl_release called.\n");
	return 0;
}

static int pvcl_do_ioctl(struct inode *inode, struct file *file,
			   unsigned int cmd, void *arg)
{

	struct clgd54xx_card * card_p = file->private_data;

	switch(cmd) {
		
	case VIDIOCGCAP:
		{
			struct video_capability *cap = arg; 
			struct video_buffer *bufstat = 
				card_p->drv_stat_p->vbuf_p;

			sprintf(cap->name, "pvcl");
			
			cap->type = VID_TYPE_TUNER | VID_TYPE_OVERLAY | 
				VID_TYPE_FRAMERAM  | VID_TYPE_SCALES;
			
			cap->channels = 1; /* Just one channel for now. */
			cap->audios = 1;

			/* Max Screen Size reported by X11 via v4l-conf */
			cap->maxwidth = bufstat->width;    
			cap->maxheight = bufstat->height;  
			cap->minwidth = 32;  /* Hardcoded vpx 3225D specific */
			cap->minheight = 24;
			
			/* Hard Code PAL for the moment. */

			do_client_ioctl(file, VPROC_SET_CAP_MODE,
					&card_p->drv_stat_p->vtun_p->mode);

			dprintk("VIDIOCGCAP called\n");
		
			return 0;
		}

	case VIDIOCGFBUF:
		{
			struct video_buffer *buf = arg;
			struct video_buffer *bufstat = 
				card_p->drv_stat_p->vbuf_p;

			memcpy(buf, bufstat, sizeof(*buf));

			dprintk("VIDIOCGFBUF called. \n");

			return 0;

		}
		
	case VIDIOCSFBUF:
		{
			
			struct video_buffer *buf = arg;
			struct video_buffer *bufstat = 
				card_p->drv_stat_p->vbuf_p;


			/* HEEEEEEEEEEEEELOOOOOOOOOOOOOOOO, Careful. Security 
			   hole here!!!!!!!!!!! 
			*/
			/* Security check disabled for now.
			   if(!capable(CAP_SYS_ADMIN) &&
			   !capable(CAP_SYS_RAWIO))
			   return -EPERM;
			*/

			memcpy(bufstat, buf, sizeof(*bufstat));
			
			dprintk("VIDIOCSFBUF called. \n");
			printk(KERN_INFO "client reports:\n
                              screen size: %dx%d @ %dbpp. 
                              %d bytesperline.\n.", 
			       bufstat->width, 
			       bufstat->height, 
			       bufstat->depth, 
			       bufstat->bytesperline);

			dprintk("Capture Buffer set to 0x%p", bufstat->base);

			/* Now that we've got the Screen Parms, 
			 * we can inititialize the window */

			gd_window_init(card_p); 

			return 0;
		}
		
		
	case VIDIOCCAPTURE:
		{
			int *on = arg;
			struct video_buffer *bufstat = 
				card_p->drv_stat_p->vbuf_p;
			
			if (*on) {
				/* verify args */
				if (NULL == bufstat->base)
					return -EINVAL;
				if (bufstat->width <32 ||
				    bufstat->height <24 ||
				    bufstat->width >800 ||
				    bufstat->height >600)
				    
				    return -EINVAL;
			}

			if(*on) {
				

				/* Tell vpx to switch on. `on' is dummy here.*/

				do_client_ioctl(file, VPROC_START_CAPTURE, on);
				gd_enable_window(card_p); 
				
				dprintk("VIDIOCCAPTURE: video window switched on. \n");
				return 0;
			}

			else {

				/* Tell vpx to stop capture. `on'  is dummy.*/

				do_client_ioctl(file, VPROC_STOP_CAPTURE, on);

				gd_disable_window(card_p); 

				dprintk("VIDIOCCAPTURE: video window switched off. \n");
				return 0;
			}
				
		}
		
	case VIDIOCGWIN:
		{
			struct video_window *winarg = arg;
			struct video_window *winstat = 
				card_p->drv_stat_p->vwin_p;
			
			memcpy(winarg, winstat, sizeof(*winarg));

			/* TODO: Need to get actual window parms from
			 * hardware. Use gd_get_video_window()
			 */

			dprintk("VIDIOCGWIN called. \n");
			return 0;
		}


	case VIDIOCSWIN:
		{
			struct video_window *winarg = arg;
                        struct video_window *winstat = 
				card_p->drv_stat_p->vwin_p;
			struct video_buffer *bufstat = 
				card_p->drv_stat_p->vbuf_p;

 			memcpy(winstat, winarg, sizeof(*winstat));


			tprintk("VIDIOCSWIN recieved:\n 
                                \t position:   %d, %d \n 
                                \t dimensions: %d, %d \n",
                                winstat->x,
				winstat->y, 
				winstat->width, 
				winstat->height); 

			do_client_ioctl(file, VPROC_SET_WINDOW, winarg); 

			/* gd_set_window(card_p, app_wants, vproc_gives....) */

			gd_set_window(card_p, winstat, winarg, bufstat); 

			tprintk("gd_set_window() recieved:\n 
                                \t position:   %d, %d \n 
                                \t dimensions: %d, %d \n",
                                winstat->x,
				winstat->y, 
				winstat->width, 
				winstat->height); 

   			gd_get_window(card_p, winstat, bufstat);   

			tprintk("gd_get_window returned: \n 
                                \t position:   %d, %d \n 
                                \t dimensions: %d, %d \n",
                                winstat->x,
				winstat->y, 
				winstat->width, 
				winstat->height); 


                        dprintk("VIDIOCSWIN called. \n");
                        return 0;
                }

		/* Wrappers for tuner.c - hacked from bttv-driver.c 
		   which is copyrighted to :
		 
		   Copyright (C) 1996,97,98 
		   Ralph  Metzler <rjkm@thp.uni-koeln.de>,
		   Marcus Metzler <mocm@thp.uni-koeln.de>,
		   &
		   (c) 1999-2002 Gerd Knorr <kraxel@bytesex.org>
 
		*/
		
        case VIDIOCGTUNER:
		{
			struct video_tuner *v = arg;
			struct video_tuner *vstat = 
				card_p->drv_stat_p->vtun_p;

			if (v->tuner) /* Only tuner 0 */
				return -EINVAL;

			do_client_ioctl(file,cmd,v);

			/* tuner.c returns v->signal, ie; signal strength. */
			memcpy(v, vstat, sizeof(*v));

			dprintk("VIDIOCGTUNER has been called. \n");
			return 0;

		}					
		
        case VIDIOCSTUNER:
		{
			struct video_tuner *v = arg;
			struct video_tuner *vstat = 
				card_p->drv_stat_p->vtun_p;

			if (v->tuner) /* Only tuner 0 */
				return -EINVAL;
			if (v->mode > VIDEO_MODE_PAL)
				return -EINVAL;
			
			do_client_ioctl(file,cmd,v);

			vstat->mode = v->mode;

			dprintk("VIDIOCSTUNER has been called. \n");

			return 0;
		}

        case VIDIOCGFREQ:
		{
			unsigned long *freq = arg;
			*freq = card_p->drv_stat_p->freq;
			dprintk("VIDIOCGFREQ called.\n");
		
			return 0;
		}
        case VIDIOCSFREQ:
		{
			unsigned long *freq = arg;

			do_client_ioctl(file,cmd,freq);
			
			card_p->drv_stat_p->freq=*freq;
			dprintk("VIDIOCSFREQ called.\n");

			return 0;
		}

        case VIDIOCSCHAN:
		{
			struct video_channel *v = arg;
			struct video_tuner *vstat = card_p->drv_stat_p->vtun_p;

			if (v->channel >  0)
				return -EINVAL;

			if(v->norm > VIDEO_MODE_PAL)
				return -EINVAL;

			/* TODO: need to implement tv norms. 
			 * Currently hardcoded
			 * to PAL. See struct video_channel 
			 * vchan_s decl above. */

			do_client_ioctl(file,cmd,v);

			memcpy(vstat, v , sizeof(*v));
			dprintk("VIDIOCSCHAN has been called. \n");
			return 0;
		}
		
        case VIDIOCGCHAN:
		{
			struct video_channel *v = arg;
			struct video_channel *chanstat = 
				card_p->drv_stat_p->vchan_p;

			memcpy(v, chanstat, sizeof(*v));

			dprintk("VIDIOCGCHAN has been called. \n");

			return 0;
		}

		/* End bttv-driver.c wrappers. */


	case VIDIOCGPICT:
		{
			struct video_picture *vpict = arg;
			struct video_picture *vpictstat =
				card_p->drv_stat_p->vpict_p;

			memcpy(vpict, vpictstat, sizeof(*vpict));

			dprintk("VIDIOCGPICT has been called. \n");
		}


	case VIDIOCSPICT:
		{
			struct video_picture *vpict = arg;
			struct video_picture *vpictstat =
				card_p->drv_stat_p->vpict_p;

			memcpy(vpictstat, vpict, sizeof(*vpict));

			do_client_ioctl(file, VPROC_SET_PICTURE, vpictstat);
			
			dprintk("VIDIOCGPICT has been called. \n");
		}
	}	
	
	return -ENOIOCTLCMD;
	

	
}


static int pvcl_ioctl(struct inode *inode, struct file *file,
			unsigned int cmd, unsigned long arg)
{
	return video_usercopy(inode, file, cmd, arg, pvcl_do_ioctl);
}

static ssize_t pvcl_read(struct file *file, char *data,
			   size_t count, loff_t *ppos)
{
	return 0;
}

static struct file_operations pvcl_fops =
{
	owner:      THIS_MODULE,
	open:       pvcl_open,
	release:    pvcl_release,
	ioctl:      pvcl_ioctl,
	llseek:     no_llseek,
	read:       pvcl_read
};

static struct video_device pvcl_video_dev =
{
	name:      "PVCL ver 0.1",
	type:       VID_TYPE_TUNER | VID_TYPE_OVERLAY |
                    VID_TYPE_FRAMERAM | VID_TYPE_SCALES,
	fops:       &pvcl_fops,
	minor:      -1
};


/* Device Registration */
static int __devinit pvcl_register_video(struct video_device * video_dev )
{
	if(video_register_device(video_dev, VFL_TYPE_GRABBER, 0)<0)
		return -1;
	printk(KERN_INFO "pvcl: registered device video\n");

	return 0;


}

static void __devexit pvcl_unregister_video(struct video_device * video_dev)
{
	video_unregister_device(video_dev);
}

static int pvcl_init_module(void)
{
	int retval;

	if( (retval = pvcl_register_video( &pvcl_video_dev)) ) return retval;
	if( (retval = i2c_clgd54xx_init(&clgd54xx_card_info, 
					&clgd54xx_adap,
					&clgd54xx_bitbang_adap)) )
		return retval;

	switch(debug)
		{
		case 1:
			{
				printk(KERN_INFO "pvcl: Functional Debug messages enabled.\n");
				break;
			}
		case 2:
			{
				printk(KERN_INFO "pvcl: Debug messages enabled per function.\n");
				break;
			}
		case 3:
			printk(KERN_INFO "pvcl: WARNING: Verbose Debugging enabled - kernel printk buffers may overflow.\n");
			break;
		}


	return retval;

}

static void pvcl_cleanup_module(void)
{
	i2c_clgd54xx_cleanup(&clgd54xx_card_info);
	pvcl_unregister_video(&pvcl_video_dev);
}

module_init(pvcl_init_module);
module_exit(pvcl_cleanup_module);

MODULE_PARM(debug,"i");
MODULE_PARM_DESC(debug,"debug messages, default 0, is don't debug.");

MODULE_DESCRIPTION("pvcl - v4l driver module for the pixelview combo tv plus");
MODULE_AUTHOR("Cherry George Mathew");
MODULE_LICENSE("GPL");

/*
 * Local variables:
 * c-basic-offset: 8
 * End:
 */
