/*  Micronas vpx 3224D/3225D video processor i2c device driver. 
    Copyright Cherry George Mathew <cherry@freeshell.org>
    
    Based on Frodo Looijaard's <frodol@dds.nl> document on
    writing i2c clients. See Documentation/i2c/writing-clients.
    
    Special thanks to the i2c authors for excellent code templates and
    useful documentation.
    
*/

/*  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/pci.h>
#include <linux/i2c.h>
#include <linux/init.h> 
#include <linux/spinlock.h>   
#include <linux/byteorder/generic.h>
#include <linux/byteorder/swab.h>
#include <linux/sched.h> 
#include <linux/videodev.h>
#include <asm/io.h>
#include <asm/bitops.h>
#include "pvcard.h"
#include "pvproc.h"

/* Module params. */
static int debug;

/* Functional level debugging */
#define dprintk(fmt, args...) if (debug>=1) printk(KERN_DEBUG "pvcl-debug: " fmt, ## args);
/* Debugging single functions */
#define tprintk(fmt, args...) if (debug>=2) printk(KERN_DEBUG "pvcl-debug: " fmt, ## args);
/* Warning - too verbose. Debugging port conversations. */
#define vprintk(fmt, args...) if (debug>=3) printk(KERN_DEBUG "pvcl-debug:" fmt, ## args);


/* module parameters: 
 */

const char *client_name = "vpx i2c bus interface"; 
static int re_entry = 0;

/* currently, vmode is the only "sticky" status variable in vpx.
 * Oh, yes, and vpx_client.
 */
static __u16 vmode;

/* This client pointer contains the address of a detected vpx chip, if any.
 * Multiple chips, are unlikely on the same adapter, and are ignored. 
 * In such cases, the chip with a lower I2C address is detected.
 */

static struct i2c_client * vpx_client;


int vpx_init_client(struct i2c_client *client);
int vpx_attach_adapter(struct i2c_adapter *adapter);
int vpx_detach_client(struct i2c_client *client);
int vpx_command(struct i2c_client *client, unsigned int cmd, void *arg);
void vpx_inc_use(struct i2c_client *client);
void vpx_dec_use(struct i2c_client *client);
void vpx_poll_fp_busy(struct i2c_client *client);

/* TODO: Clearup I2C_DRIVERID_EXP0 issue on the v4l list. */

struct i2c_driver vpx_driver =
{  
	name:              "Micronas vpx 3224/5d i2c driver",
	id:                I2C_DRIVERID_EXP0,
	flags:             I2C_DF_NOTIFY,
	attach_adapter:    &vpx_attach_adapter,
	detach_client:     &vpx_detach_client,
	command:           &vpx_command,   
	inc_use:           &vpx_inc_use,   
	dec_use:           &vpx_dec_use    
};




/* Scan ports 0x43 - 0x47 (7-bit addresses) for the vpx */

static unsigned short normal_i2c[] = { I2C_CLIENT_END };

static unsigned short normal_i2c_range[] = { 0x43, 0x47, I2C_CLIENT_END };

/* Magic definition of all other variables and things */
I2C_CLIENT_INSMOD;


/* byte banging routines. i2c-algo-bit.c (lines 363 - 364 assumes data
   to be little endian. Am I right or have I read the code wrong ?
*/


s32 vpx_read_byte(struct i2c_client *client, u8 reg)
{
	  /* byte-sized register */
	return i2c_smbus_read_byte_data(client,reg);
}

s32 vpx_read_word(struct i2c_client *client, u8 reg)
{
	/* word-sized register */
	
#ifdef __LITTLE_ENDIAN
	/* The vpx takes MSB first. OK. topsy turvy! */
	
	return swab16(i2c_smbus_read_word_data(client,reg));

#endif

	return i2c_smbus_read_word_data(client,reg);
}

s32 vpx_write_byte(struct i2c_client *client, u8 reg, u16 value)
{
	
	/* byte-sized register */
	  return i2c_smbus_write_byte_data(client,reg,value);
	  
}

s32 vpx_write_word(struct i2c_client *client, u8 reg, u16 value)
{
	/* word-sized register */
	/* Do the byte swap */

#ifdef __LITTLE_ENDIAN

	value=swab16(value);

#endif
	return i2c_smbus_write_word_data(client,reg,value);
}


s32 vpx_fp_write(struct i2c_client *client, u16 fp_reg, u16 value)
{
	int status;
	vpx_poll_fp_busy(client);
	if((status=vpx_write_word(client, FPWR, fp_reg))) return status;
	vpx_poll_fp_busy(client);
 	return vpx_write_word(client, FPDAT, (value & 0x3fff)); 
}

s32 vpx_fp_read(struct i2c_client *client, u16 fp_reg)
{
        s32 status;
        vpx_poll_fp_busy(client);
        if((status=vpx_write_word(client, FPRD, fp_reg))) return status;
        vpx_poll_fp_busy(client);
         return (0x3fff & vpx_read_word(client, FPDAT)); 
}
 
void vpx_poll_fp_busy(struct i2c_client *client)
{

	unsigned long status;
	status = (unsigned long) vpx_read_byte(client, FPSTA);
	while(test_bit(2, &status))
		{
			status = (unsigned long) vpx_read_byte(client, FPSTA);
			
			/* This is a half hearted attempt to sleep 
			 * for 20 milliseconds. The vpx manual says that if 
			 * the onboard FP (FP stands for 
			 * "Fast Processor" :-O ) is busy, read/write
			 * to it needs to be retried after 20ms.
			 * The priority here is for other processes 
			 * to run. Not for us to encourage 
			 * couch potatoes... ;)
			 */
			
			set_current_state(TASK_UNINTERRUPTIBLE);
			schedule_timeout(VPX_BUSY_TIMEOUT);
		}
	
}	

				
/* vpx functional layer */

/* initialize the video processor */

void vpx_pinit(struct i2c_client *client, int model)
{
	int portword;
	/* I've gone to the trouble of customizing vpx parameters here
	 * with extensibility in mind. For each card, there should be a 
	 * special set of settings for the vpx.
	 */ 

	switch(model)
		{
		case PVCLPP_COMBO:
			/* The pvclpp combo card uses a philips TEA5582
			 * FM demodulator to demodulate sound.
			 * The TEA5582 has a mute control, which is connected
			 * via VPort B, bit 1. Therefore, we have to guess that
			 * the video capture takes place via the 8bits of
			 * VPort A. This is set via FPreg, 0x154. 
			 */

			portword = vpx_fp_read(client, 0x154) | 0x302;
			tprintk("Writing %04x to FPreg 0x154 \n.",
				portword);
			
			vpx_fp_write(client, 0x154, portword);
			

			/* Set VPort Driver strength. 
			 * Got this from the default setting of the pvclpp 
			 * application (tvtap) for win98. Booted into 
			 * windows, ran tvtap, killed it with softice,
			 * warm booted into linux through int 19, 
			 * and read the I2C 
			 * registers. 
			 */
			
			vpx_write_byte(client, 0xf9, 0x24);
			vpx_write_byte(client, 0xf8, 0x24);
			
			/* VREF pulse width = 6. 
			 * Guess how I found out ?? ;-)
			 */
			
			vpx_fp_write(client, 0x153, 0x20);

			/* Set the input source. There are 3 input 
			 * sources, VIN1, VIN2, VIN3.
			 * FPReg(0x21) [1:0] determines 
			 * hardcoding to television tuner input 
			 * for just now. */

			portword = vpx_fp_read(client, 0x21) & 0xfc;

			vpx_fp_write(client, 0x21, portword | 0x01);

		}

	/* Disable Winload table #2. We use Winload table #1 for both fields.*/

	vpx_fp_write(client, 0x12B, 0xc00);
	
	/* Latch current Values */
	portword = vpx_fp_read(client, 0x140);
	portword |= 0x40;
	vpx_fp_write(client, 0x140, portword);


	/* Switch off slicer. */
	vpx_write_byte(client, 0xaa, 0x40);
	
	tprintk("I2C reg. 0xAA reads %02x. \n", 
		vpx_read_byte(client, 0xaa));
	

	
	/* Set to PAL - B, G, H, I (50Hz) for testing on my VCR.
	 * Here is the vpx manual listing for setting various standards. 
	 *
	 * FPReg(0x20) [2:0]         Standard    Vert.       IF. (MHz)
	 *             0          PAL B,G,H,I    50Hz         4.433618
	 *             1               NTSC M    60Hz         3.579545
	 *             2                SECAM    50Hz         4.286
	 *             3               NTSC44    60Hz         4.433618
	 *             4                PAL M    60Hz         3.575611
	 *             5                PAL N    50Hz         3.582056
	 *             6               PAL 60    60Hz         4.433618
	 *             7            NTSC COMB    60Hz         3.579545
	 */


 	portword = vpx_fp_read(client, 0x20) & 0xff8; 

 	vpx_fp_write(client, 0x20, portword | 0x00); 
	
	/* There's some confusion in the ITU 601 format. Cr and Cb get
	 * Swapped. Let's re-swap them.
	 */

	portword = vpx_fp_read(client, 0x126) & 0xeff;

	vpx_fp_write(client, 0x126, portword | 0x100);


}



void vpx_start_capture(struct i2c_client *client)
{
	int portword;
	

	/* Enable VPortA, VPortB, Pixclk, HREF, VREF, FIELD, VACT, LLC, LLC2 */
	
	portword = vpx_read_byte(client, 0xf2) & 0xf0;
	portword |= 0x0f;
	
	vpx_write_byte(client, 0xf2, portword);
}

void vpx_stop_capture(struct i2c_client *client)
{
	int portword;
	
	/* Disable VPortA, VPortB, Pixclk, 
	   HREF, VREF, FIELD, VACT, LLC, LLC2 */
	
	portword = vpx_read_byte(client, 0xf2) & 0xf0;
	
	vpx_write_byte(client, 0xf2, portword);
}

void vpx_set_window(struct i2c_client *client, struct video_window *vwin)
{
	/* Winloadtab1 is loaded here. It is active for both fields.
	 */

	int portword;
	
	switch(vmode)
		{
		case VIDEO_MODE_PAL: {
						
			/* Vertical Begin Scanline */
			vpx_fp_write(client, 0x120, 20);

			/* Horizontal Begin, Pixel */
			vpx_fp_write(client, 0x123, 5);

			/* Maximum Lines input 310:  PAL is a 625 line s/m. */
			vpx_fp_write(client, 0x121, 305);
			
			/* Scaled number of Vertical Lines. */
			vwin->height = 
				(vwin->height > 305 ? 
				 300 : vwin->height) & 0x1ff;

			vwin->height =
				vwin->height < 24 ?
				24 : vwin->height;

			/* Scaled Width of raster line. */
			vwin->width = 
				(vwin->width > 800 ? 
				 800 : vwin->width) & 0x7ff;

			vwin->width = vwin->width < 32 ? 
				32 : vwin->width;

			break;
		}			

		default:
			return;
		}

	/* Update No. of Vlines to vpx */

	vpx_fp_write(client, 0x122, vwin->height);

	/* Width of raster, upto 800 pixels */

	vpx_fp_write(client, 0x125, vwin->width);
	vpx_fp_write(client, 0x124, vwin->width);


	/* Latch current Values */
	portword = vpx_fp_read(client, 0x140);
	portword |= 0x20;
	vpx_fp_write(client, 0x140, portword);


}


void vpx_set_picture(struct i2c_client *client, struct video_picture *vpict)
{
	
	int portword;
	
	tprintk("Brightness: %d, Contrast: %d, Colour: %d, Hue %d \n",
	       vpict->brightness, vpict->contrast, vpict->colour,
	       vpict->hue);

	/* set contrast FPReg 0x132 [5].[4:0] = contrast ratio */	

       	portword = vpx_fp_read(client, 0x132) & 0xfd0;

	portword |= (vpict->contrast >> 10) & 0x3f;

	vpx_fp_write(client, 0x132, portword);

	tprintk("H/W contrast set to %d.%d \n", vpict->contrast >> 15,
		vpict->contrast >> 11 & 0x1f);

	/* Set Brightness */
	portword = vpx_fp_read(client, 0x131) & 0xf00;

	portword |= vpict->brightness >> 8;

	vpx_fp_write(client, 0x131, portword);
	
	


}

int vpx_detect_client(struct i2c_adapter *adapter, int address, 
		      unsigned short flags, int kind)
{
	int err = 0;

	struct i2c_client *new_client;

	dprintk("in vpx_detect_client() \n");
	
	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_EMUL)){
		return 0;
	}
		
       
	if(! (new_client = 
		    kmalloc(sizeof(struct i2c_client),GFP_KERNEL))) {
			
		return -ENOMEM;
	}

	new_client->addr = address;
	new_client->adapter = adapter;
	new_client->driver = &vpx_driver;
	new_client->flags = 0;
	
	/* Now, we do the remaining detection. No `force' parameter is used. */
			
	if (vpx_read_byte(new_client, VPX_REG_JEDEC) != VPX_JEDEC_ID)
		{
			goto BAILOUT;
		}
	if (vpx_read_byte(new_client, VPX_REG_PARTNUM1) != VPX_PARTNUM1_ID)
		{
			goto BAILOUT;
		}
	
	if ((vpx_read_byte(new_client, VPX_REG_PARTNUM0) == VPX_3224D_ID)){
		
		printk( KERN_INFO 
			"vpx: Found: vpx 3224d chip @ 0x%02x \n", 
			(new_client->addr << 1) );
	
		if((err=vpx_init_client(new_client))) goto BAILOUT;
		return 0;
	}
	
	if (vpx_read_byte(new_client, 
			  VPX_REG_PARTNUM0) == VPX_3225D_ID){
		printk( KERN_INFO
			"vpx: Found: vpx 3225d chip @ 0x%02x \n", 
			(new_client->addr << 1));

		if((err=vpx_init_client(new_client))) goto BAILOUT;
		return 0;
	}
	else{
		printk( KERN_WARNING
			"vpx:  Can't find supported vpx chip 
                               on this adapter. Sorry.\n");		


		goto BAILOUT;
	}
	

 BAILOUT:	
	kfree(new_client);
	return err; 
	
}



int vpx_init_client(struct i2c_client *client)
{
	
	int err=0;


	/* This driver is non re-entrant; ie; can only support one chip 
	   at a time.
	*/

	if(re_entry) 
		{
			printk(KERN_INFO
			       "Sorry, only one client allowed per adapter. \n");
			return -EBUSY;
		}
	
	re_entry = 1;

	/* Fill in the remaining client fields. */
	strcpy(client->name, client_name);
	
	
	/* Tell the i2c layer a new client has arrived */
	
	if ((err = i2c_attach_client(client)))
		{
			return err;	
		}

	/* Finally link this one client with our global client pointer */
	vpx_client = client;

	return 0;
}

	

int vpx_detach_client(struct i2c_client *client)
{
	int err;
	
	
	/* Try to detach the client from i2c space */
	if ((err = i2c_detach_client(client))) {
		printk("vpx322xd: Client deregistration failed, client not detached.\n");

		return err;
	}

	re_entry=0;

	MOD_DEC_USE_COUNT;

	/* Frees client data too, if allocated at the same time */
	kfree(client); 
	return 0;
}

int vpx_command(struct i2c_client *client, unsigned int cmd, void *arg)
{
	/* Please note that it is the caller's responsibility to 
	 * maintain state information about the video processor. 
	 * The only VPROC_GET command is
	 * used to query the video standard. ie; PAL, NTSC and such.
	 */

	switch(cmd)
		{
		case VPROC_INIT:
			{
				int model = * ((int *) arg);

				vpx_pinit(client, model);
				dprintk("VPROC_INIT called. \n");
				break;
			}

		case VPROC_START_CAPTURE:
			
			vpx_start_capture(client);

			dprintk("VPROC_START_CAPTURE called. \n");
			break;
			
			
		case VPROC_STOP_CAPTURE:
			
			vpx_stop_capture(client);
			
			dprintk("VPROC_STOP_CAPTURE called. \n");
			break;
			
			
		case VPROC_SET_CAP_MODE:
			
			/* SET to PAL/NTSC/SECAM via struct */
			vmode = *((int *)(arg));
			dprintk("VPROC_SET_CAP_MODE called. \n");	
			break;
			

		case VPROC_GET_CAP_MODE:
			memcpy(arg, &vmode, sizeof(int));
			break;
			
		case VPROC_SET_WINDOW:
			{
				struct video_window * vwin = arg;
				vpx_set_window(client, vwin);
				dprintk("VPROC_SET_WINDOW called. \n");
				break;
			}
			
		case VPROC_SET_PICTURE:
			{
				struct video_picture * vpict = arg;
				vpx_set_picture(client, vpict);
				dprintk("VPROC_SET_PICTURE called. \n");
				break;
			}
		}
	
	
	return 0;
}

int vpx_attach_adapter(struct i2c_adapter *adapter)
{
	int retval;
	switch ((adapter->id & I2C_ALGO_BIT)) {
	case I2C_ALGO_BIT:

		printk(KERN_INFO "vpx: probing i2c adapter %s ... \n",
		       adapter->name);
		retval = i2c_probe(adapter,&addr_data,&vpx_detect_client);

		break;

	default:

		printk("vpx: skipping adapter %s, 
                            adapter type not supported.\n",
		       adapter->name);
		retval = 0;
	}

	/* Had to put this here because of messy code in vpx_detect_client()
	 * TODO: Cleanup the chip detect code in vpx_detect_client().
	 */

	if(!retval) 
		MOD_INC_USE_COUNT;

	return retval;

}

void vpx_inc_use(struct i2c_client *client)
{
	MOD_INC_USE_COUNT;
}

void vpx_dec_use(struct i2c_client *client)
{
	MOD_DEC_USE_COUNT;
}




int __init vpx_init(void)
{
    int res;
    printk("vpx version %s (%s)\n", VPX_VERSION, VPX_DATE);
    
    if ((res = i2c_add_driver(&vpx_driver))) {
	    printk("vpx: Driver registration failed, module not inserted.\n");
	    return res;
    }

    return 0;
}

int __init vpx_cleanup(void)
{
	int res;
	if ((res = i2c_del_driver(&vpx_driver))) {
		printk("vpx: Driver de-registration failed, module not removed.\n");
		return res;
	}
	

	return 0;
}


EXPORT_NO_SYMBOLS;
MODULE_PARM(debug, "i");


MODULE_PARM_DESC(debug,
	   "debug level - 0 off; 1 normal; 2 verbose; ");

#ifdef MODULE
MODULE_AUTHOR("Cherry George Mathew <cherry@freeshell.org>");
MODULE_DESCRIPTION("i2c driver for the micronas vpx322xd 
                    video processor family");
MODULE_LICENSE("GPL");


int init_module(void)
{
	return vpx_init();
}


int cleanup_module(void)
{
	return vpx_cleanup();
}

#endif                        /* MODULE */




/*
 * Overrides for Emacs so that we follow Linus's tabbing style.
 * ---------------------------------------------------------------------------
 * Local variables:
 * c-basic-offset: 8
 * End:
 */




