/* 
 * saa7114 - Philips SAA7114H video decoder driver version 0.0.1
 *
 * Copyright (C) 2002 Maxim Yevtyushkin <max@linuxmedialabs.com>
 *
 * Based on saa7111 driver by Dave Perks
 *
 * Copyright (C) 1998 Dave Perks <dperks@ibm.net>
 *
 * Slight changes for video timing and attachment output by
 * Wolfgang Scherr <scherr@net4you.net>
 *
 * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net>
 *    - moved over to linux>=2.4.x i2c protocol (1/1/2003)
 *
 * Changes by Kevin Thayer <nufan_wfk at yahoo.com>
 *    - changed to saa7115. (2/17/2003)
 *
 * 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/major.h>
#include <linux/init.h>

#include <linux/slab.h>

#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/wrapper.h>

#include <linux/videodev.h>
#include <linux/version.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/page.h>

MODULE_DESCRIPTION("Philips SAA7115 video decoder driver");
MODULE_AUTHOR("Kevin Thayer");
MODULE_LICENSE("GPL");

#include <linux/i2c.h>
#include <linux/i2c-dev.h>

#ifndef I2C_DRIVERID_SAA7114
#warning Using temporary hack for missing I2C driver-ID for saa7114
#define I2C_DRIVERID_SAA7114 I2C_DRIVERID_EXP1
#endif

#include <linux/video_decoder.h>

static int debug = 1;
MODULE_PARM(debug, "i");
MODULE_PARM_DESC(debug, "Debug level (0-1)");

/* FIXME need to get this properly allocated 
 *   also defined in ivtv.h, so change it there too */
#define DECODER_SET_SIZE    76598
#define	DECODER_GET_PICTURE 76599

/* Need to be able to set the audio bitrates */
#define DECODER_SET_AUDIO	0xFFEE7707
#define DECODER_AUDIO_32_KHZ	0
#define DECODER_AUDIO_441_KHZ	1
#define DECODER_AUDIO_48_KHZ	2
 
#ifdef SAA7115_REGTEST
/* allow direct access to the saa7115 registers for testing */
#define SAA7115_GET_REG		0xFFEE7705
#define SAA7115_SET_REG		0xFFEE7706


struct saa7115_reg_t {
	u8 reg;
	u8 val;
};
#endif

#define dprintk(num, format, args...) \
	do { \
		if (debug >= num) \
			printk(format, ##args); \
	} while (0)

/* ----------------------------------------------------------------------- */

static u8 readreg(struct i2c_client *client, unsigned char reg) {
        struct i2c_adapter *adap=client->adapter;
        unsigned char mm1[] = {0x1e};
        unsigned char mm2[] = {0x00};
        struct i2c_msg msgs[2];
        
        msgs[0].flags=0;
        msgs[1].flags=I2C_M_RD;
        msgs[0].addr=msgs[1].addr=client->addr;
        mm1[0]=reg;
        msgs[0].len=1; msgs[1].len=1;
        msgs[0].buf=mm1; msgs[1].buf=mm2;
        i2c_transfer(adap, msgs, 2);
        
        return mm2[0];
}

struct saa7114 {
	int norm;
	int input;
	int enable;
	int bright;
	int contrast;
	int hue;
	int sat;
	int playback;
	int audio;
};

#define   I2C_SAA7114        0x42
#define   I2C_SAA7114A       0x40

#define   I2C_DELAY   10


//#define SAA_7114_NTSC_HSYNC_START       (-3)
//#define SAA_7114_NTSC_HSYNC_STOP        (-18)

#define SAA_7114_NTSC_HSYNC_START  (-17)
#define SAA_7114_NTSC_HSYNC_STOP   (-32)

//#define SAA_7114_NTSC_HOFFSET           (5)
#define SAA_7114_NTSC_HOFFSET		(6)
#define SAA_7114_NTSC_VOFFSET           (10)
#define SAA_7114_NTSC_WIDTH             (720)
#define SAA_7114_NTSC_HEIGHT            (480) /* was 250*/

#define SAA_7114_SECAM_HSYNC_START      (-17)
#define SAA_7114_SECAM_HSYNC_STOP       (-32)

#define SAA_7114_SECAM_HOFFSET          (2)
#define SAA_7114_SECAM_VOFFSET          (10)
#define SAA_7114_SECAM_WIDTH            (720)
#define SAA_7114_SECAM_HEIGHT           (300)

#define SAA_7114_PAL_HSYNC_START        (-17)
#define SAA_7114_PAL_HSYNC_STOP         (-32)

#define SAA_7114_PAL_HOFFSET            (2)
#define SAA_7114_PAL_VOFFSET            (10)
#define SAA_7114_PAL_WIDTH              (720)
#define SAA_7114_PAL_HEIGHT             (300)

#define SAA_7114_VERTICAL_CHROMA_OFFSET         0	//0x50504040
#define SAA_7114_VERTICAL_LUMA_OFFSET           0

#define REG_ADDR(x) (((x) << 1) + 1)
#define LOBYTE(x) ((unsigned char)((x) & 0xff))
#define HIBYTE(x) ((unsigned char)(((x) >> 8) & 0xff))
#define LOWORD(x) ((unsigned short int)((x) & 0xffff))
#define HIWORD(x) ((unsigned short int)(((x) >> 16) & 0xffff))


/* ----------------------------------------------------------------------- */

static inline int
saa7114_write (struct i2c_client *client,
	       u8                 reg,
	       u8                 value)
{
//	struct saa7114 *decoder = client->data;
	return i2c_smbus_write_byte_data(client, reg, value);
}


static int writeregs(struct i2c_client *client, const unsigned char *regs)
{
        unsigned char reg, data;

        while (*regs!=0x00) {
                reg =*(regs++);
                data=*(regs++);
                if (saa7114_write(client, reg, data) < 0)
                        return -1;
        }
        return 0;
}

static inline int
saa7114_read (struct i2c_client *client,
	      u8                 reg)
{
	return i2c_smbus_read_byte_data(client, reg);
}

/* ----------------------------------------------------------------------- */


static const unsigned char init_saa7115_auto_input[] = {
	0x01, 0x08, //(was 0x48)   // 0x08: white peak control enabled, 0x48: white peak control disabled
	0x03, 0x2C, //was 0x20     // 0x20: automatic gain control, 0x2c: user programmable gain
	0x04, 0x90, // analog gain set to 0
	0x05, 0x90, // analog gain set to 0
	0x06, 0xEB, // horiz sync begin = -21
	0x07, 0xE0, // horiz sync stop = -17

	// done in misc
//	0x09, 0x40, //80 for svideo  // 0x40: use luminance comb filter, 0x80: don't use ...

	0x0A, 0x80, //i2c dump ends up at 96 (was 80) // decoder brightness, 0x80 is itu standard
	0x0B, 0x44, // decoder contrast, 0x44 is itu standard
	0x0C, 0x40, // decoder saturation, 0x40 is itu standard
	0x0D, 0x00, //i2c dump ends up at 04 (was 00) // chrominance hue control
	0x0F, 0x24, //i2c dump says 0x30 (was 0x2A) // chrominance gain control , should be 0x00 for agc, otherwise 0x80+0x24: 0xA4
	0x10, 0x06,
	0x11, 0x00,
	0x12, 0x9D, //i2c dump says 0x9D (was 0x00)
	0x13, 0x80, //" 0x80 (was 0x00)
	0x14, 0x01, //" 0x01 (was 0x01)
	0x15, 0x04, //" 0x00 (was 0x11) //should also be moved to NTSC/PAL VGATE start
	/* moved to NTSC/PAL sections
	0x16, 0x11, //" 0x11 (was 0xFE) // VGATE stop
	*/
	0x17, 0x98, //" 0x98 (was 0xD8) //may set to 98 // VGATE MSB and other values
	0x18, 0x40, // raw data gain 0x00 = nominal
	0x19, 0x80, // raw data offset 0x80 = 0 LSB
	0x1A, 0x77, // color killer level control 0x77 = recommended
	0x1B, 0x42, // misc chroma control 0x42 = recommended
	0x1C, 0xA9, // combfilter control 0xA9 = recommended
	0x1D, 0x01, // combfilter control 0x01 = recommended
	0x88, 0xD0, //reset device // set programmed, reset
	0x88, 0xF0, //Set device programmed, all in operational mode // set programmed, active ?programmed should be 0?
	0x00, 0x00 // ? not necessary, version readback register
};

/* ============== SAA7715 VIDEO templates =============  */

static const unsigned char cfg_saa7115_reset_scaler[] = {
	0x87, 0x00, //Disable I-port output
	0x88, 0x0B, //reset scaler (was 0xD0) // ?should be 0xD0
	0x88, 0xF0, //activate scaler
	0x87, 0x01, //Enable I-port output  // what about high bits? how is ICLK used?
	0x00, 0x00
};
static const unsigned char cfg_saa7115_NTSC_fullres_x[] = {
	0xCC, 0xD0, //hsize low (output) //hor output window size = 0x2d0 = 720
	0xCD, 0x02, //hsize hi (output)

	0xD0, 0x01, // down scale = 1
	0xD1, 0x00, // prescale accumulation length = 1
	0xD2, 0x00, // dc gain and fir prefilter control
	0xD4, 0x80, //Lum Brightness // nominal value = 0x80
	0xD5, 0x40, //Lum contrast // nominal value = 0x40
	0xD6, 0x40, //Chroma satur. // nominal value = 0x80
	0xD8, 0x00, // hor lum scaling 0x0400 = 1
	0xD9, 0x04,
	0xDA, 0x00, //H-phase offset Luma = 0
	0xDC, 0x00, // hor chrom scaling 0x0200. must be hor lum scaling /2
	0xDD, 0x02, //H-scaling incr chroma 
	0xDE, 0x00, //H-phase offset chroma // must be offset luma /2
	
	0x00, 0x00
};
static const unsigned char cfg_saa7115_NTSC_fullres_y[] = {
	0xCE, 0xFD, //vsize low (output) was FD // ver output window size = 253 ??240
	0xCF, 0x00, //vsize hi (output)

	0xE0, 0x00, //V-scaling incr luma low 0x0400 = 1
	0xE1, 0x04, //" hi
	0xE2, 0x00, //V-scaling incr chroma low // must be same as luma
	0xE3, 0x04, //" hi
	0xE4, 0x01, //V-scaling mode control // no mirroring, higher order accumulation
	0xE8, 0x00, //V-phase offset chroma 00 //?only regs E8 and EC necessary?
	0xE9, 0x00, //V-phase offset chroma 01
	0xEA, 0x00, //V-phase offset chroma 10
	0xEB, 0x00, //V-phase offset chroma 11
	0xEC, 0x00, //V-phase offset luma 00
	0xED, 0x00, //V-phase offset luma 01
	0xEE, 0x00, //V-phase offset luma 10

	0x00, 0x00
};

static const unsigned char cfg_saa7115_NTSC_video[] = {
	0x80, 0x00, //reset tasks
	0x88, 0x0B, //reset scaler (was 0xD0)
	
	0x16, 0x11, //" 0x11 (was 0xFE) //VGATE pulse stop

	0x08, 0x68, //i2c dump says 0x68 (was 0xB0) NTSC ONLY  // 0xBO: auto detection, 0x68 = NTSC
	0x0E, 0x07, //i2c dump says 0x0d (was 0x07) // lots of different stuff... video autodetection is on

	0xC0, 0x00, //Task Handling Control (was 0x00)
	0xC1, 0x08, //X-port formats/config
	0xC2, 0x00, //Input Ref. signal Def.
	0xC3, 0x80, //I-port config (was 0x80)
	0xC4, 0x02, //hoffset low (input) // 0x0002 is minimum
	0xC5, 0x00, //hoffset hi (input)
	0xC6, 0xD0, //hsize low (input) // 0x02d0 = 720
	0xC7, 0x02, //hsize hi (input)
	0xC8, 0x14, //voff low was 0x14, changing to 0x0E (14) // 0x0014 = 20
	0xC9, 0x00, //voff hi
	0xCA, 0xFD, //vsize low (input) was FD // 0x00fd = 253
	0xCB, 0x00, //vsize hi (input)

	0xF0, 0xAD, //Set PLL Register. NTSC 525 lines per frame, 27 MHz
	0xF1, 0x05, //low bit with 0xF0, (was 0x05)
	0xF5, 0xAD, //Set pulse generator register
	0xF6, 0x01,

	0x87, 0x00, //Disable I-port output
	0x88, 0x0B, //reset scaler (was 0xD0)
	0x80, 0x20, //Activate only task "B", continuous mode (was 0xA0)
	0x88, 0xF0, //activate scaler
	0x87, 0x01, //Enable I-port output
	0x00, 0x00
};
 
static const unsigned char cfg_saa7115_PAL_fullres_x[] = {
 	0xCC, 0xD0, //hsize low (output) //720 same as NTSC
 	0xCD, 0x02, //hsize hi (output)

 	0xD0, 0x01,
 	0xD1, 0x00,
 	0xD2, 0x00,
 	0xD4, 0x80, //Lum Brightness
 	0xD5, 0x40, //Lum contrast
 	0xD6, 0x40, //Chroma satur.
 	0xD8, 0x00,
 	0xD9, 0x04,
 	0xDA, 0x00, //H-phase offset Luma
 	0xDC, 0x00,
 	0xDD, 0x02, //H-scaling incr chroma
 	0xDE, 0x00, //H-phase offset chroma

	0x00, 0x00
};
static const unsigned char cfg_saa7115_PAL_fullres_y[] = {
 	0xCE, 0x20, //vsize low (output) // 0x0120 = 288
 	0xCF, 0x01, //vsize hi (output)
 
 	0xE0, 0x00, //V-scaling incr luma low
 	0xE1, 0x04, //" hi
 	0xE2, 0x00, //V-scaling incr chroma low
 	0xE3, 0x04, //" hi
 	0xE4, 0x01, //V-scaling mode control
 	0xE8, 0x00, //V-phase offset chroma 00
 	0xE9, 0x00, //V-phase offset chroma 01
 	0xEA, 0x00, //V-phase offset chroma 10
 	0xEB, 0x00, //V-phase offset chroma 11
 	0xEC, 0x00, //V-phase offset luma 00
 	0xED, 0x00, //V-phase offset luma 01
 	0xEE, 0x00, //V-phase offset luma 10
 	0xEF, 0x00, //V-phase offset luma 11

	0x00, 0x00
};

/* FIXME need to input proper height/width */
static const unsigned char cfg_saa7115_PAL_video[] = {
 	0x80, 0x00, //reset tasks
 	0x88, 0x0B, //reset scaler (was 0xD0)

	0x16, 0x15, //" 0x11 (was 0xFE)

	0x08, 0x28, //i2c dump says 0x28 (was 0xB0) PAL ONLY // 0x28 = PAL
	0x0E, 0x07, //i2c dump says 0x0d (was 0x07)

 	0xC0, 0x00, //Task Handling Control (was 0x00)
 	0xC1, 0x08, //X-port formats/config
 	0xC2, 0x00, //Input Ref. signal Def.
 	0xC3, 0x80, //I-port config (was 0x80)
 	0xC4, 0x00, //hoffset low (input)
 	0xC5, 0x00, //hoffset hi (input)
 	0xC6, 0xD0, //hsize low (input) // 0x02D0 = 720
 	0xC7, 0x02, //hsize hi (input)
 	0xC8, 0x14, //voffset low (input) low was 0x14, changing to 0x0E (14)
 	0xC9, 0x00, //voffset hi (input)
 	0xCA, 0x20, //vsize low (input) // 288
 	0xCB, 0x01, //vsize hi (input)

	0xF0, 0xB0, //Set PLL Register. PAL 625 lines per frame, 27 MHz
	0xF1, 0x05, //low bit with 0xF0, (was 0x05)
	0xF5, 0xB0, //Set pulse generator register
	0xF6, 0x01,

	0x87, 0x00, //Disable I-port output
 	0x88, 0x0B, //reset scaler (was 0xD0)
 	0x80, 0xA0, //Activate only task "B", continuous mode (was 0xA0)
 	0x88, 0xF0, //activate scaler
 	0x87, 0x01, //Enable I-port output
 	0x00, 0x00
};
/* ============== SAA7715 VIDEO templates (end) =======  */


static const unsigned char init_saa7115_misc[] = {
	0x38, 0x03, // audio stuff
	0x39, 0x10,
	0x3A, 0x00,

//	0x80, 0x00, // set below
	0x81, 0x01, //reg 0x15,0x16 define blanking window
	0x82, 0x00,
	0x83, 0x01, //was 0x01 // I port settings
	0x84, 0x20,
	0x85, 0x21,
	0x86, 0xC5,
	0x87, 0x01,
//	0x88, 0xD0, // unnecessary

//	0xF0, 0xAD, //this goes in PAL/NTSC video
//	0xF1, 0x05,
	0xF2, 0x50, // crystal clock = 24.576 MHz, target = 27MHz
	0xF3, 0x46,
	0xF4, 0x00,
//	0xF5, 0xAD, //this goes in PAL/NTSC video
//	0xF6, 0x01,
	0xF7, 0x4B, // not the recommended settings!
	0xF8, 0x00,
	0xF9, 0x4B,
	0xFA, 0x00,
	0xFB, 0x4B,
//	0xFC, 0x00, // unused
//	0xFD, 0x00,
//	0xFE, 0x00,
	0xFF, 0x88, // PLL2 lock detection settings: 71 lines 50% phase error

//	0x88, 0xF0, // unnecessary

//	0x0D, 0x04, // already set in auto_input
//	0x0C, 0x40,
//	0x0A, 0x96,
//	0x0B, 0x41,
//	0x98, 0x05, // belongs to task A; unnecessary
/* Turn off VBI */
	0x40, 0x00,
	0x41, 0xFF,
	0x42, 0xFF,
	0x43, 0xFF,
	0x44, 0xFF,
	0x45, 0xFF,
	0x46, 0xFF,
	0x47, 0xFF,
	0x48, 0xFF,
	0x49, 0xFF,
	0x4A, 0xFF,
	0x4B, 0xFF,
	0x4C, 0xFF,
	0x4D, 0xFF,
	0x4E, 0xFF,
	0x4F, 0xFF,
	0x50, 0xFF,
	0x51, 0xFF,
	0x52, 0xFF,
	0x53, 0xFF,
	0x54, 0xFF,
	0x55, 0xFF,
	0x56, 0xFF,
	0x57, 0xFF,
	0x58, 0x00,
	0x59, 0x47,
	0x5A, 0x06,
	0x5B, 0x88,
	0x5D, 0xBF,
	0x5E, 0x35,

	0x02, 0x84, //input tuner -> input 4, amplifier active
	0x09, 0x53, //chrom trap for tuner // special tuner stuff?

	0x80, 0x20, //was 0x30 // 0x20 clock from PLL2, 0x30 clock from ICLK
	0x88, 0xD0,
	0x88, 0xF0,
	0x00, 0x00 
};

/* ============== SAA7715 AUDIO settings =============  */
static const unsigned char cfg_saa7115_48_audio[] = {
	0x34, 0xCE, // 48khz
	0x35, 0xFB, // "
	0x36, 0x30, // "
	0x00, 0x00
};

static const unsigned char cfg_saa7115_441_audio[] = {
	0x34, 0xF2, // 44.1khz
	0x35, 0x00, // "
	0x36, 0x2D, // "
	0x00, 0x00
};

static const unsigned char cfg_saa7115_32_audio[] = {
	0x34, 0xDF, // 32.0khz
	0x35, 0xA7, // "
	0x36, 0x20, // "
	0x00, 0x00
};

static const unsigned char cfg_saa7115_NTSC_48_audio[] = {
	0x30, 0xCD, // 48.0khz NTSC
	0x31, 0x20, // "
	0x32, 0x03, // "
	0x00, 0x00
};

static const unsigned char cfg_saa7115_PAL_48_audio[] = {
	0x30, 0x00, // 48.0khz PAL
	0x31, 0xC0, // "
	0x32, 0x03, // "
	0x00, 0x00
};

static const unsigned char cfg_saa7115_NTSC_441_audio[] = {
	0x30, 0xBC, // 44.1khz NTSC
	0x31, 0xDF, // "
	0x32, 0x02, // "
	0x00, 0x00
};

static const unsigned char cfg_saa7115_PAL_441_audio[] = {
	0x30, 0x00, // 44.1khz PAL
	0x31, 0x72, // "
	0x32, 0x03, // "
	0x00, 0x00
};

static const unsigned char cfg_saa7115_NTSC_32_audio[] = {
	0x30, 0xDE, // 32.0khz NTSC
	0x31, 0x15, // "
	0x32, 0x02, // "
	0x00, 0x00
};

static const unsigned char cfg_saa7115_PAL_32_audio[] = {
	0x30, 0x00, // 32.0khz PAL
	0x31, 0x80, // "
	0x32, 0x02, // "
	0x00, 0x00
};

/* ============ SAA7715 AUDIO settings (end) ============= */

static int
saa7114_command (struct i2c_client *client,
		 unsigned int       cmd,
		 void              *arg)
{
	struct saa7114 *decoder = client->data;

	switch (cmd) {

	case 0:
		//dprintk(1, KERN_INFO "%s: writing init\n", client->name);
		//saa7114_write_block(client, init, sizeof(init));
		break;
#ifdef SAA7115_REGTEST
	/* ioctls to allow direct access to the saa7115 registers for testing */
	case SAA7115_GET_REG:
	{
		struct saa7115_reg_t *saa7115_reg = (struct saa7115_reg_t *)arg;

		saa7115_reg->val = saa7114_read(client, saa7115_reg->reg);
		break;
	}
	case SAA7115_SET_REG:
	{
		struct saa7115_reg_t *saa7115_reg = (struct saa7115_reg_t *)arg;

		saa7114_write(client, saa7115_reg->reg, saa7115_reg->val);
		break;
	}
#endif
	case DECODER_SET_SIZE:
	{
		/* Used video_window because it has height/width and is
		 * already defined */
		struct video_window *wind = arg;
		int HPSC, HFSC;
		int VSCY, Vsrc;

		dprintk(1, KERN_INFO "%s: decoder set size\n", client->name);

		/* FIXME need better bounds checking here */
		if ( (wind->width < 1) || (wind->width > 1440))
			return -EINVAL;
		if ( (wind->height < 1) || (wind->height > 960))
			return -EINVAL;
			
		/* probably have a valid size, let's set it */
/* Set output width/height */
		/* width */
		saa7114_write (client, 0xCC, (u8)(wind->width & 0xFF));
		saa7114_write (client, 0xCD, (u8)((wind->width >> 8)&0xFF));
		/* height */
		saa7114_write (client, 0xCE, (u8)(wind->height & 0xFF));
		saa7114_write (client, 0xCF, (u8)((wind->height >> 8)&0xFF));

/* Scaling settings */
		/* Hprescaler is floor(inres/outres) */
		/* FIXME hardcoding input res */
		if (wind->width != 720) {
			HPSC = (int) (720/wind->width);
			HFSC = (int) ((1024*720)/(HPSC*wind->width));

			printk("Hpsc: 0x%05x, Hfsc: 0x%05x\n", HPSC, HFSC);
			/* FIXME hardcodes to "Task B" 
			 * write H prescaler integer */
			saa7114_write (client, 0xD0, (u8)(HPSC & 0x3F));

			/* write H fine-scaling (luminance)*/
			saa7114_write (client, 0xD8, (u8)(HFSC & 0xFF));
			saa7114_write (client, 0xD9, (u8)((HFSC >> 8) & 0xFF) );
			/* write H fine-scaling (chrominance)
			 * must be lum/2, so i'll just bitshift :) */
			saa7114_write (client, 0xDC, (u8)((HFSC >> 1) & 0xFF) );
			saa7114_write (client, 0xDD, (u8)((HFSC >> 9) & 0xFF) );
		} else {
			if (decoder->norm != VIDEO_MODE_NTSC) {
				printk("Setting full PAL width\n");
				writeregs(client, cfg_saa7115_PAL_fullres_x);
			} else {
				printk("Setting full NTSC width\n");
				writeregs(client, cfg_saa7115_NTSC_fullres_x);
			}
		}
		
		Vsrc = 480;
		if (decoder->norm != VIDEO_MODE_NTSC) Vsrc = 576;

		if (wind->height != Vsrc) {
			VSCY = (int) ((1024*Vsrc)/wind->height);
			printk("Vsrc: %d, Vscy: 0x%05x\n", Vsrc, VSCY);
			/* write V fine-scaling (luminance)*/
			saa7114_write (client, 0xE0, (u8)(VSCY & 0xFF));
			saa7114_write (client, 0xE1, (u8)((VSCY >> 8) & 0xFF) );
			/* write V fine-scaling (chrominance)*/
			saa7114_write (client, 0xE2, (u8)(VSCY & 0xFF));
			saa7114_write (client, 0xE3, (u8)((VSCY >> 8) & 0xFF) );
		} else {
			if (decoder->norm != VIDEO_MODE_NTSC) {
				printk("Setting full PAL height\n");
				writeregs(client, cfg_saa7115_PAL_fullres_y);
			} else {
				printk("Setting full NTSC height\n");
				writeregs(client, cfg_saa7115_NTSC_fullres_y);
			}
		}

		writeregs(client, cfg_saa7115_reset_scaler);
		break;
	}
	case DECODER_DUMP:
	{
		int i;

		dprintk(1, KERN_INFO "%s: decoder dump\n", client->name);

		for (i = 0; i < 32; i += 16) {
			int j;

			printk(KERN_DEBUG "%s: %03x", client->name, i);
			for (j = 0; j < 16; ++j) {
				printk(" %02x",
				       saa7114_read(client, i + j));
			}
			printk("\n");
		}
	}
		break;

	case DECODER_GET_CAPABILITIES:
	{
		struct video_decoder_capability *cap = arg;

		dprintk(1, KERN_DEBUG "%s: decoder get capabilities\n",
			client->name);

		cap->flags = VIDEO_DECODER_PAL |
			     VIDEO_DECODER_NTSC |
			     VIDEO_DECODER_SECAM |
			     VIDEO_DECODER_AUTO |
			     VIDEO_DECODER_CCIR;
		cap->inputs = 8;
		cap->outputs = 1;
	}
		break;

	case DECODER_SET_AUDIO:
	{
		int *iarg = arg;
		dprintk(1, KERN_DEBUG "%s set audio: 0x%02x\n", client->name, *iarg);
		switch (*iarg) {
			case DECODER_AUDIO_32_KHZ:
				writeregs(client, cfg_saa7115_32_audio);
				if (decoder->norm == VIDEO_MODE_NTSC) {
					writeregs(client, cfg_saa7115_NTSC_32_audio);
				} else {
					writeregs(client, cfg_saa7115_PAL_32_audio);
				}
				break;
			case DECODER_AUDIO_441_KHZ:
				writeregs(client, cfg_saa7115_441_audio);
				if (decoder->norm == VIDEO_MODE_NTSC) {
					writeregs(client, cfg_saa7115_NTSC_441_audio);
				} else {
					writeregs(client, cfg_saa7115_PAL_441_audio);
				}
				break;
			case DECODER_AUDIO_48_KHZ:
				writeregs(client, cfg_saa7115_48_audio);
				if (decoder->norm == VIDEO_MODE_NTSC) {
					writeregs(client, cfg_saa7115_NTSC_48_audio);
				} else {
					writeregs(client, cfg_saa7115_PAL_48_audio);
				}
				break;
			default:
				printk(KERN_DEBUG "%s invalid audio setting 0x%02x\n", client->name, *iarg);
		}

		/*FIXME digitizer reset needed? 
		 *   if so, uncomment this line */
		//writeregs(client, cfg_saa7115_reset_scaler);

		decoder->audio = *iarg;
		
	}
		break;
	case DECODER_GET_STATUS:
	{
		int *iarg = arg;
		int status;
		int res;

		status = saa7114_read(client, 0x1f);

		dprintk(1, KERN_DEBUG "%s status: 0x%02x\n", client->name,
			status);
		res = 0;
		if ((status & (1 << 6)) == 0) {
			res |= DECODER_STATUS_GOOD;
		}
		switch (decoder->norm) {
		case VIDEO_MODE_NTSC:
			res |= DECODER_STATUS_NTSC;
			break;
		case VIDEO_MODE_PAL:
			res |= DECODER_STATUS_PAL;
			break;
		case VIDEO_MODE_SECAM:
			res |= DECODER_STATUS_SECAM;
			break;
		default:
		case VIDEO_MODE_AUTO:
			if ((status & (1 << 5)) != 0) {
				res |= DECODER_STATUS_NTSC;
			} else {
				res |= DECODER_STATUS_PAL;
			}
			break;
		}
		if ((status & (1 << 0)) != 0) {
			res |= DECODER_STATUS_COLOR;
		}
		*iarg = res;
	}
		break;

	case DECODER_SET_NORM:
	{
		u16 *iarg = arg;

		dprintk(1, KERN_DEBUG "%s: decoder set norm ",
			client->name);

		switch (*iarg) {

		case VIDEO_MODE_NTSC:
			dprintk(1, "NTSC\n");
			writeregs(client, cfg_saa7115_NTSC_video);
			break;

		case VIDEO_MODE_PAL:
			dprintk(1, "PAL\n");
			writeregs(client, cfg_saa7115_PAL_video);
			break;

		case VIDEO_MODE_SECAM:
			dprintk(1, "SECAM\n");
			writeregs(client, cfg_saa7115_PAL_video);
			break;

		default:
			dprintk(1, " Unknown video mode!!!\n");
			return -EINVAL;

		}

		decoder->norm = *iarg;

		/* switch audio mode too! */
		saa7114_command(client, DECODER_SET_AUDIO, &decoder->audio);

	}
		break;

	case DECODER_SET_INPUT:
	{
		int *iarg = arg;

		dprintk(1, KERN_DEBUG "%s: decoder set input (%d)\n",
			client->name, *iarg);
		/* inputs from 0-9 are available*/
		if (*iarg < 0 || *iarg > 9) { 
			return -EINVAL;
		}

		if (decoder->input != *iarg) {
			dprintk(1, KERN_DEBUG "%s: now setting %s input\n",
				client->name,
				*iarg >= 6 ? "S-Video" : "Composite");
			decoder->input = *iarg;

			/* select mode */
			saa7114_write(
				client,
				0x02,
				(saa7114_read(client, 0x02) & 0xf0)|decoder->input);

			/* bypass chrominance trap for modes 6..9 */
			saa7114_write(client, 0x09,
				(saa7114_read(client,0x09) & 0x7f)|
				(decoder->input < 6 ? 0x0 : 0x80));
		}
	}
		break;

	case DECODER_SET_OUTPUT:
	{
		int *iarg = arg;

		dprintk(1, KERN_DEBUG "%s: decoder set output\n",
			client->name);

		/* not much choice of outputs */
		if (*iarg != 0) {
			return -EINVAL;
		}
	}
		break;

	case DECODER_ENABLE_OUTPUT:
	{
		int *iarg = arg;
		int enable = (*iarg != 0);

		dprintk(1, KERN_DEBUG "%s: decoder %s output\n",
			client->name, enable ? "enable" : "disable");

		decoder->playback = !enable;

		if (decoder->enable != enable) {
			decoder->enable = enable;

			if (decoder->enable) {
				saa7114_write(client,0x87,0x01);
			} else {
				saa7114_write(client,0x87,0x00);
			}
		}
	}
		break;

	case DECODER_GET_PICTURE:
	{
		struct saa7114 *pic = arg;

		pic->bright = decoder->bright;
		pic->contrast = decoder->contrast;
		pic->sat = decoder->sat;
		pic->hue = decoder->hue;
	}
		break;

	case DECODER_SET_PICTURE:
	{
		struct saa7114 *pic = arg;

		dprintk(1,
			KERN_DEBUG
			"%s: decoder set picture bright=%d contrast=%d saturation=%d hue=%d\n",
			client->name, pic->bright, pic->contrast,
			pic->sat, pic->hue);

		if (decoder->bright != pic->bright) {
			/* We want 0 to 255 */
			if (pic->bright < 0 || pic->bright > 255) {
				dprintk(0, KERN_ERR "%s: invalid brightness setting %d", client->name, pic->bright);
				return -EINVAL;
			}
			decoder->bright = pic->bright;
			saa7114_write(client, 0x0a, decoder->bright);
		}
		if (decoder->contrast != pic->contrast) {
			/* We want 0 to 127 */
			if (pic->contrast < 0 || pic->contrast > 127) {
				dprintk(0, KERN_ERR "%s: invalid contrast setting %d", client->name, pic->contrast);
				return -EINVAL;
			}
			decoder->contrast = pic->contrast;
			saa7114_write(client, 0x0b, decoder->contrast);
		}
		if (decoder->sat != pic->sat) {
			/* We want 0 to 127 */
			if (pic->sat < 0 || pic->sat > 127) {
				dprintk(0, KERN_ERR "%s: invalid saturation setting %d", client->name, pic->sat);
				return -EINVAL;
			}
			decoder->sat = pic->sat;
			saa7114_write(client, 0x0c, decoder->sat);
		}
		if (decoder->hue != pic->hue) {
			/* We want -128 to 127 */
			if (pic->hue < -128 || pic->hue > 127) {
				dprintk(0, KERN_ERR "%s: invalid hue setting %d", client->name, pic->hue);
				return -EINVAL;
			}
			decoder->hue = pic->hue;
			saa7114_write(client, 0x0d, decoder->hue);
		}
	}
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

/* ----------------------------------------------------------------------- */


/* i2c implementation */
#ifndef NEW_I2C
  /* pre i2c-2.8.0 */

static void
saa7114_inc_use (struct i2c_client *client)
{
  #ifdef MODULE
	MOD_INC_USE_COUNT;
  #endif
}

static void
saa7114_dec_use (struct i2c_client *client)
{
  #ifdef MODULE
	MOD_DEC_USE_COUNT;
  #endif
}

#else
	/* i2c-2.8.0 and later */
#endif

/*
 * Generic i2c probe
 * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
 */
static unsigned short normal_i2c[] =
    { I2C_SAA7114 >> 1, I2C_SAA7114A >> 1, I2C_CLIENT_END };
static unsigned short normal_i2c_range[] = { I2C_CLIENT_END };

I2C_CLIENT_INSMOD;

static int saa7114_i2c_id = 0;
struct i2c_driver i2c_driver_saa7114;

static int
saa7114_detect_client (struct i2c_adapter *adapter,
		       int                 address,
		       unsigned short      flags,
		       int                 kind)
{
//	int i, err[30];
//	short int hoff = SAA_7114_NTSC_HOFFSET;
//	short int voff = SAA_7114_NTSC_VOFFSET;
//	short int w = SAA_7114_NTSC_WIDTH;
//	short int h = SAA_7114_NTSC_HEIGHT;
	struct i2c_client *client;
	struct saa7114 *decoder;

	dprintk(1,
		KERN_INFO
		"saa7114.c: detecting saa7114 client on address 0x%x\n",
		address << 1);

	/* Check if the adapter supports the needed features */
	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
		return 0;

	client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (client == 0)
		return -ENOMEM;
	memset(client, 0, sizeof(struct i2c_client));
	client->addr = address;
	client->adapter = adapter;
	client->driver = &i2c_driver_saa7114;
	client->flags = I2C_CLIENT_ALLOW_USE;
	client->id = saa7114_i2c_id++;
	snprintf(client->name, sizeof(client->name) - 1, "saa7115[%d]",
		 client->id);

	client->data = decoder =
	    kmalloc(sizeof(struct saa7114), GFP_KERNEL);
	if (decoder == NULL) {
		kfree(client);
		return -ENOMEM;
	}
	memset(decoder, 0, sizeof(struct saa7114));
	decoder->norm = VIDEO_MODE_NTSC;
	decoder->input = -1;
	decoder->enable = 1;
	decoder->bright = 128;
	decoder->contrast = 64;
	decoder->hue = 0;
	decoder->sat = 64;
	decoder->playback = 0;	// initially capture mode used
	decoder->audio = DECODER_AUDIO_48_KHZ;

	dprintk(1,
		KERN_INFO
		"saa7115.c: writing init values\n");

	/* init to NTSC/48khz */
	writeregs(client, init_saa7115_auto_input);
	writeregs(client, init_saa7115_misc);
	writeregs(client, cfg_saa7115_NTSC_fullres_x);
	writeregs(client, cfg_saa7115_NTSC_fullres_y);
	writeregs(client, cfg_saa7115_NTSC_video);
	writeregs(client, cfg_saa7115_48_audio);
	writeregs(client, cfg_saa7115_NTSC_48_audio);
	writeregs(client, cfg_saa7115_reset_scaler);

	set_current_state(TASK_INTERRUPTIBLE);
	schedule_timeout(2*HZ);
	
	printk("status: (1E) 0x%02x, (1F) 0x%02x\n",
		readreg(client, 0x1e),
		readreg(client, 0x1f));

	i2c_attach_client(client);

	return 0;
}

static int
saa7114_attach_adapter (struct i2c_adapter *adapter)
{
	dprintk(1,
		KERN_INFO
		"saa7114.c: starting probe for adapter %s (0x%x)\n",
		adapter->name, adapter->id);
	return i2c_probe(adapter, &addr_data, &saa7114_detect_client);
}

static int
saa7114_detach_client (struct i2c_client *client)
{
	struct saa7114 *decoder = client->data;
	int err;

	err = i2c_detach_client(client);
	if (err) {
		return err;
	}

	kfree(decoder);
	kfree(client);
	return 0;
}

/* ----------------------------------------------------------------------- */

/* i2c implementation */
struct i2c_driver i2c_driver_saa7114 = {
	.name = "saa7115",

	.id = I2C_DRIVERID_SAA7114,
	.flags = I2C_DF_NOTIFY,

	.attach_adapter = saa7114_attach_adapter,
	.detach_client = saa7114_detach_client,
	.command = saa7114_command,
#ifndef NEW_I2C
/* pre i2c-2.8.0 */
	.inc_use = saa7114_inc_use,
	.dec_use = saa7114_dec_use,
#else
/* i2c-2.8.0 and later */
	.owner = THIS_MODULE,
#endif
};

EXPORT_NO_SYMBOLS;

static int __init
saa7114_init (void)
{
	return i2c_add_driver(&i2c_driver_saa7114);
}

static void __exit
saa7114_exit (void)
{
	i2c_del_driver(&i2c_driver_saa7114);
}

module_init(saa7114_init);
module_exit(saa7114_exit);
