/* 
 * tveeprom - eeprom decoder for tvcard configuration eeproms
 *
 * Data and decoding routines shamelessly borrowed from bttv-cards.c
 * eeprom access routine shamelessly borrowed from bttv-if.c
 * which are:

    Copyright (C) 1996,97,98 Ralph  Metzler (rjkm@thp.uni-koeln.de)
                           & Marcus Metzler (mocm@thp.uni-koeln.de)
    (c) 1999-2001 Gerd Knorr <kraxel@goldbach.in-berlin.de>

 * Adjustments to fit a more general model and all bugs:
 
 	Copyright (C) 2003 John Klar <linpvr at projectplasma.com>

 * 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 <asm/io.h>
#include <asm/pgtable.h>
#include <asm/page.h>
#include <linux/sched.h>
#include <asm/segment.h>
#include <linux/types.h>
#include <linux/wrapper.h>

#include <linux/videodev.h>

#include <linux/version.h>
#include <asm/uaccess.h>

MODULE_DESCRIPTION("i2c eeprom decoder driver");
MODULE_AUTHOR("John Klar");
MODULE_LICENSE("GPL");

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

#include "tuner.h"

#ifndef I2C_DRIVERID_TVEEPROM
#warning Using temporary hack for missing I2C driver-ID for tveeprom
#define I2C_DRIVERID_TVEEPROM I2C_DRIVERID_EXP2
#endif

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

//static int verbose = 0;
MODULE_PARM(verbose, "i");
MODULE_PARM_DESC(verbose, "Verbose level (0-1)");

#define STRM(array,i) (i < sizeof(array)/sizeof(char*) ? array[i] : "unknown")

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

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

static unsigned char eeprom_buf[256];

struct tveeprom {
	u32 has_radio;

	u32 tuner_type;
	u32 tuner_formats;

	u32 digitizer;
	u32 digitizer_formats;

	u32 audio_processor;
	/* a_p_fmts? */

	u32 model;
	u32 revision;
	u32 serial_number;
	char rev_str[5];
};

#define   I2C_TVEEPROM        0xA0
#define   I2C_TVEEPROMA       0xA0

#define   I2C_DELAY   10


#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))

/* ----------------------------------------------------------------------- */
/* some hauppauge specific stuff                                           */

static struct HAUPPAUGE_TUNER_FMT
{
	int	id;
	char *name;
}
hauppauge_tuner_fmt[] __devinitdata =
{
	{ 0x00000000, "unknown1" },
	{ 0x00000000, "unknown2" },
	{ 0x00000007, "PAL(B/G)" },
	{ 0x00001000, "NTSC(M)" },
	{ 0x00000010, "PAL(I)" },
	{ 0x00400000, "SECAM(L/Lī)" },
	{ 0x00000e00, "PAL(D/K)" },
	{ 0x03000000, "ATSC Digital" },
};

static struct HAUPPAUGE_TUNER 
{
	int  id;
	char *name;
} 
hauppauge_tuner[] __devinitdata = 
{
	{ TUNER_ABSENT,        "" },
	{ TUNER_ABSENT,        "External" },
	{ TUNER_ABSENT,        "Unspecified" },
	{ TUNER_PHILIPS_PAL,   "Philips FI1216" },
	{ TUNER_PHILIPS_SECAM, "Philips FI1216MF" },
	{ TUNER_PHILIPS_NTSC,  "Philips FI1236" },
	{ TUNER_PHILIPS_PAL_I, "Philips FI1246" },
	{ TUNER_PHILIPS_PAL_DK,"Philips FI1256" },
	{ TUNER_PHILIPS_PAL,   "Philips FI1216 MK2" },
	{ TUNER_PHILIPS_SECAM, "Philips FI1216MF MK2" },
	{ TUNER_PHILIPS_NTSC,  "Philips FI1236 MK2" },
	{ TUNER_PHILIPS_PAL_I, "Philips FI1246 MK2" },
	{ TUNER_PHILIPS_PAL_DK,"Philips FI1256 MK2" },
	{ TUNER_TEMIC_NTSC,    "Temic 4032FY5" },
	{ TUNER_TEMIC_PAL,     "Temic 4002FH5" },
	{ TUNER_TEMIC_PAL_I,   "Temic 4062FY5" },
	{ TUNER_PHILIPS_PAL,   "Philips FR1216 MK2" },
	{ TUNER_PHILIPS_SECAM, "Philips FR1216MF MK2" },
	{ TUNER_PHILIPS_NTSC,  "Philips FR1236 MK2" },
	{ TUNER_PHILIPS_PAL_I, "Philips FR1246 MK2" },
	{ TUNER_PHILIPS_PAL_DK,"Philips FR1256 MK2" },
	{ TUNER_PHILIPS_PAL,   "Philips FM1216" },
	{ TUNER_PHILIPS_SECAM, "Philips FM1216MF" },
	{ TUNER_PHILIPS_NTSC,  "Philips FM1236" },
	{ TUNER_PHILIPS_PAL_I, "Philips FM1246" },
	{ TUNER_PHILIPS_PAL_DK,"Philips FM1256" },
	{ TUNER_TEMIC_4036FY5_NTSC, "Temic 4036FY5" },
	{ TUNER_ABSENT,        "Samsung TCPN9082D" },
	{ TUNER_ABSENT,        "Samsung TCPM9092P" },
	{ TUNER_TEMIC_4006FH5_PAL, "Temic 4006FH5" },
	{ TUNER_ABSENT,        "Samsung TCPN9085D" },
	{ TUNER_ABSENT,        "Samsung TCPB9085P" },
	{ TUNER_ABSENT,        "Samsung TCPL9091P" },
	{ TUNER_TEMIC_4039FR5_NTSC, "Temic 4039FR5" },
	{ TUNER_PHILIPS_FQ1216ME,   "Philips FQ1216 ME" },
	{ TUNER_TEMIC_4066FY5_PAL_I, "Temic 4066FY5" },
	{ TUNER_ABSENT,        "Philips TD1536" },
	{ TUNER_ABSENT,        "Philips TD1536D" },
	{ TUNER_PHILIPS_NTSC,  "Philips FMR1236" }, /* mono radio */
	{ TUNER_ABSENT,        "Philips FI1256MP" },
	{ TUNER_ABSENT,        "Samsung TCPQ9091P" },
	{ TUNER_TEMIC_4006FN5_MULTI_PAL, "Temic 4006FN5" },
	{ TUNER_TEMIC_4009FR5_PAL, "Temic 4009FR5" },
	{ TUNER_TEMIC_4046FM5,     "Temic 4046FM5" },
	{ TUNER_TEMIC_4009FN5_MULTI_PAL_FM, "Temic 4009FN5" },
	{ TUNER_ABSENT,        "Philips TD1536D_FH_44"},
	{ TUNER_LG_NTSC_FM,    "LG TP18NSR01F"},
	{ TUNER_LG_PAL_FM,     "LG TP18PSB01D"},
	{ TUNER_LG_PAL,        "LG TP18PSB11D"},	
	{ TUNER_LG_PAL_I_FM,   "LG TAPC-I001D"},
	{ TUNER_LG_PAL_I,      "LG TAPC-I701D"}
};

static char *sndtype[] = {
	"None", "TEA6300", "TEA6320", "TDA9850", "MSP3400C", "MSP3410D",
	"MSP3415", "MSP3430", "MSP3438", "CS5331", "MSP3435", "MSP3440",
	"MSP3445", "MSP3411", "MSP3416", "MSP3425",

	"Type 0x10","Type 0x11","Type 0x12","Type 0x13",
	"Type 0x14","Type 0x15","Type 0x16","Type 0x17",
	"Type 0x18","MSP4418","Type 0x1a","MSP4448",
	"Type 0x1c","Type 0x1d","Type 0x1e","Type 0x1f",
};

static void __devinit hauppauge_eeprom(struct tveeprom *tvee, 
									   unsigned char *eeprom_data)
{

	/* ----------------------------------------------
	** The hauppauge eeprom format is tagged
	**
	** if packet[0] == 0x84, then packet[0..1] == length
	** else length = packet[0] & 3f;
	** if packet[0] & f8 == f8, then EOD and packet[1] == checksum
	**
	** In our (ivtv) case we're interested in the following:
	** tuner type: tag [00].05 or [0a].01 (index into hauppauge_tuners)
	** tuner fmts: tag [00].04 or [0a].00 (bitmask index into hauppauge_fmts)
	** radio:      tag [00].{last} or [0e].00  (bitmask.  bit2=FM)
	** audio proc: tag [02].01 or [05].00 (lower nibble indexes lut?)

	** Fun info:
	** model:      tag [00].07-08 or [06].00-01
	** revision:   tag [00].09-0b or [06].04-06
	** serial#:    tag [01].05-07 or [04].04-06

	** # of inputs/outputs ???
	*/

	int i,j,len,done,tag,tuner=0,t_format=0;
	char *t_name = NULL, *t_fmt_name = NULL;
	
	tvee->revision = done = len = 0;
	for(i=0; !done && i<256; i+=len) {

		dprintk(2,
				KERN_INFO 
				"tvee: processing pos=%02x (%02x,%02x)\n",
				i,eeprom_data[i],eeprom_data[i+1]);

		if(eeprom_data[i] == 0x84) {
			len = eeprom_data[i+1] + (eeprom_data[i+2] << 8);
			i+=3;
		} else
		if((eeprom_data[i] & 0xf0) == 0x70) {
			if((eeprom_data[i] & 0x08)) {
				/* verify checksum! */
				done = 1;
				break;
			}
			len = eeprom_data[i] & 0x07;
			++i;
		} else {
			printk(KERN_WARNING "Encountered bad packet header [%02x].  "
				   "Corrupt or not a Hauppauge eeprom.\n",eeprom_data[i]);
			return;
		}

		dprintk(1,
				KERN_INFO 
				"%3d [%02x] ",
				len,eeprom_data[i]);
		for(j=1; j<len; j++) {
			dprintk(1,
					"%02x ",eeprom_data[i+j]);
		}
		dprintk(1,"\n");

		/* process by tag */
		tag = eeprom_data[i];
		switch( tag ) {
		case 0x00:
			tuner = eeprom_data[i+6];
			t_format = eeprom_data[i+5];
			tvee->has_radio = eeprom_data[i+len-1];
			tvee->model = 
				eeprom_data[i+8] + 
				(eeprom_data[i+9] << 8);
			tvee->revision = eeprom_data[i+10] +
				(eeprom_data[i+11] << 8) +
				(eeprom_data[i+12] << 16);
			break;
		case 0x01:
			tvee->serial_number = 
				eeprom_data[i+6] + 
				(eeprom_data[i+7] << 8) + 
				(eeprom_data[i+8] << 16);
			break;
		case 0x02:
			tvee->audio_processor = eeprom_data[i+2] & 0x0f;
			break;
		case 0x04:
			tvee->serial_number = 
				eeprom_data[i+5] + 
				(eeprom_data[i+6] << 8) + 
				(eeprom_data[i+7] << 16);
			break;
		case 0x05:
			tvee->audio_processor = eeprom_data[i+1] & 0x0f;
			break;
		case 0x06:
			tvee->model = 
				eeprom_data[i+1] + 
				(eeprom_data[i+2] << 8);
			tvee->revision = eeprom_data[i+5] +
				(eeprom_data[i+6] << 8) +
				(eeprom_data[i+7] << 16);
			break;
		case 0x0a:
			tuner = eeprom_data[i+2];
			t_format = eeprom_data[i+1];
			break;
		case 0x0e:
			tvee->has_radio = eeprom_data[i+1];
			break;
		default:
			printk(KERN_WARNING "Not sure what to do with tag [%02x]\n",tag);
			/* dump the rest of the packet? */
		}

	}

	if(!done) {
		printk(KERN_WARNING "Ran out of data!\n");
		return;
	}

	if(tvee->revision != 0) {
		tvee->rev_str[0] = 32 + ((tvee->revision >> 18) & 0x3f);
		tvee->rev_str[1] = 32 + ((tvee->revision >> 12) & 0x3f);
		tvee->rev_str[2] = 32 + ((tvee->revision >>  6) & 0x3f);
		tvee->rev_str[3] = 32 + ( tvee->revision        & 0x3f);
		tvee->rev_str[4] = 0;
	}

	if (tuner < sizeof(hauppauge_tuner)/sizeof(struct HAUPPAUGE_TUNER)) {
		tvee->tuner_type = hauppauge_tuner[tuner].id;
		t_name = hauppauge_tuner[tuner].name;
	} else {
		t_name = "<unknown>";
	}

	tvee->tuner_formats = 0;
	t_fmt_name = "<none>";
	for(i=0; i<8; i++) {
		if( (t_format & (1<<i)) ) {
			tvee->tuner_formats |= hauppauge_tuner_fmt[i].id;
			/* yuck */
			t_fmt_name = hauppauge_tuner_fmt[i].name;
		}
	}

#if 0
	if (t_format < sizeof(hauppauge_tuner_fmt)/sizeof(struct HAUPPAUGE_TUNER_FMT)) {
		tvee->tuner_formats = hauppauge_tuner_fmt[t_format].id;
		t_fmt_name = hauppauge_tuner_fmt[t_format].name;
	} else {
		t_fmt_name = "<unknown>";
	}
#endif

	printk(KERN_INFO "tvee: Hauppauge: model=%d, rev=%s, serial#=%d\n",
		   tvee->model,
		   tvee->rev_str,
		   tvee->serial_number);
	printk(KERN_INFO "tvee: tuner=%s (idx=%d, type=%d)\n",
		   t_name,
		   tuner,
		   tvee->tuner_type);
	printk(KERN_INFO "tvee: tuner fmt=%s (eeprom=0x%02x, v4l2=0x%08x)\n",
		   t_fmt_name,
		   t_format,
		   tvee->tuner_formats);

	printk(KERN_INFO "tvee: audio_processor=%s (type=%d)\n",
		   STRM(sndtype,tvee->audio_processor),
		   tvee->audio_processor);

}

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

/* write I2C */
int tvee_I2CWrite(struct i2c_client *client, 
				  unsigned char b1,
                  unsigned char b2, int both)
{
	unsigned char buffer[2];
	int bytes = both ? 2 : 1;

	buffer[0] = b1;
	buffer[1] = b2;
	if (bytes != i2c_master_send(client, buffer, bytes))
		return -1;
	return 0;
}

void __devinit tvee_readee(struct i2c_client *client, 
						   unsigned char *eedata)
{
	int i;
        
	if (tvee_I2CWrite(client, 0, -1, 0)<0) {
		printk(KERN_WARNING "tvee: readee error\n");
		return;
	}

	for (i=0; i<256; i+=16) {
		if (16 != i2c_master_recv(client,eedata+i,16)) {
			printk(KERN_WARNING "tvee: readee error\n");
			break;
		}
	}
}

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

static int
tveeprom_command (struct i2c_client *client,
		 unsigned int       cmd,
		 void              *arg)
{

	struct tveeprom *eeprom = client->data;
	u32 *eeprom_props = arg;

	switch (cmd) {

	case 0:
		eeprom_props[0] = eeprom->tuner_type;
		eeprom_props[1] = eeprom->tuner_formats;
		eeprom_props[2] = eeprom->model;
		eeprom_props[3] = eeprom->revision;
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

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

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

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

static void
tveeprom_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_TVEEPROM >> 1, I2C_TVEEPROMA >> 1, I2C_CLIENT_END };
static unsigned short normal_i2c_range[] = { I2C_CLIENT_END };

I2C_CLIENT_INSMOD;

static int tveeprom_i2c_id = 0;
struct i2c_driver i2c_driver_tveeprom;

static int
tveeprom_detect_client (struct i2c_adapter *adapter,
		       int                 address,
		       unsigned short      flags,
		       int                 kind)
{
	struct i2c_client *client;
	struct tveeprom *eeprom;

	dprintk(1,
		KERN_INFO
		"tveeprom.c: detecting tveeprom 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_tveeprom;
	client->flags = I2C_CLIENT_ALLOW_USE;
	client->id = tveeprom_i2c_id++;
	snprintf(client->name, sizeof(client->name) - 1, "tveeprom[%d]",
		 client->id);

	client->data = eeprom =
	    kmalloc(sizeof(struct tveeprom), GFP_KERNEL);
	if (eeprom == NULL) {
		kfree(client);
		return -ENOMEM;
	}
	memset(eeprom, 0, sizeof(struct tveeprom));
	eeprom->tuner_type = -1;
	eeprom->has_radio = 0;
	eeprom->model = 0;

	tvee_readee(client,eeprom_buf);
	hauppauge_eeprom(eeprom,eeprom_buf);

	return 0;
}

static int
tveeprom_attach_adapter (struct i2c_adapter *adapter)
{
	if(adapter->id != (I2C_ALGO_BIT | I2C_HW_B_BT848))
		return 0;
	dprintk(1,
		KERN_INFO
		"tveeprom.c: starting probe for adapter %s (0x%x)\n",
		adapter->name, adapter->id);
	return i2c_probe(adapter, &addr_data, tveeprom_detect_client);
}

static int
tveeprom_detach_client (struct i2c_client *client)
{
	struct tveeprom *eeprom = client->data;
	int err;

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

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

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

/* i2c implementation */
struct i2c_driver i2c_driver_tveeprom = {
	.name = "tveeprom",

	.id = I2C_DRIVERID_TVEEPROM,
	.flags = I2C_DF_NOTIFY,

	.attach_adapter = tveeprom_attach_adapter,
	.detach_client = tveeprom_detach_client,
	.command = tveeprom_command,
#ifndef NEW_I2C
/* pre i2c-2.8.0 */
	.inc_use = tveeprom_inc_use,
	.dec_use = tveeprom_dec_use,
#else
/* i2c-2.8.0 and later */
	.owner = THIS_MODULE,
#endif
};

EXPORT_NO_SYMBOLS;

static int __init
tveeprom_init (void)
{
	return i2c_add_driver(&i2c_driver_tveeprom);
}

static void __exit
tveeprom_exit (void)
{
	i2c_del_driver(&i2c_driver_tveeprom);
}

module_init(tveeprom_init);
module_exit(tveeprom_exit);
