#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>

/* Scan for i2c addresses 0x5d and 0x5c. Only 0x5d is present on my system.  */
static unsigned short normal_i2c[] = { 0x5d, 0x5c, I2C_CLIENT_END }; 

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

static int tvp5150_attach_adapter(struct i2c_adapter *adapter);
static int tvp5150_detach_client(struct i2c_client *client);

static struct i2c_driver tvp5150 = {
    .owner          = THIS_MODULE,
    .name           = "tvp5150 CC decoder",
    .flags          = I2C_DF_NOTIFY,
    .attach_adapter = tvp5150_attach_adapter,
    .detach_client  = tvp5150_detach_client,
};

static int tvp5150_detect_client(struct i2c_adapter *adapter, int address, int kind)
{
	printk( KERN_INFO "detect_client %d\n", address); 
	return -ENODEV;
}

static struct i2c_client tvp5150_client = 
{
    .addr = 0x5d,
    .adapter = NULL,
    .driver = &tvp5150,
    .flags = 0,
    .name = "tvp5510"	
};

static void read_vdp() {
    int i, b;
    i2c_smbus_write_byte_data(&tvp5150_client, 0xC5, 0x00);
    i2c_smbus_write_byte_data(&tvp5150_client, 0xC4, 0x00);
    printk(KERN_INFO "conf: \n");
    for(i=0; i<0x1C; i++) {
        printk(KERN_INFO "%1x: ", i);
        for(b=0; b<16; b++) {
            printk(" %1x", i2c_smbus_read_byte_data(&tvp5150_client, 0xC3));
        }
        printk("\n");
    }
}

static void prepare_to_program(void) {
    int i;
    // Write 0xFF into registers D0h to FBh
    for(i=0xD0; i<=0xFB; i++) {
        i2c_smbus_write_byte_data(&tvp5150_client, i, 0xFF);
    }
    // Disable full field mode
    i2c_smbus_write_byte_data(&tvp5150_client, 0xCF, 0x00);
}

static void program_cc(void)
{
    static unsigned char cc_code[] = {
        0xAA, 0x2A, 0xFF, 0x3F, 0x04, 0x51, 0x6E, 0x02, 0x69, 0x8C, 0x09,
        0x00, 0x00, 0x00, 0x27, 0x00
    };
    int i;
    prepare_to_program();
    i2c_smbus_write_byte_data(&tvp5150_client, 0xC5, 0x00);
    i2c_smbus_write_byte_data(&tvp5150_client, 0xC4, 0xF0);
    for(i=0; i<sizeof(cc_code)/sizeof(*cc_code); i++) {
        i2c_smbus_write_byte_data(&tvp5150_client, 0xC3, cc_code[i]); 
    }
}

static int tvp5150_attach_adapter(struct i2c_adapter *adapter)
{
    printk( KERN_INFO "attach adapter\n");	
    tvp5150_client.adapter = adapter;
    
    i2c_attach_client(&tvp5150_client);
    program_cc();
    //read_vdp();
    i2c_smbus_write_byte_data(&tvp5150_client, 0xCE, 0xFF); // auto init
    i2c_smbus_write_byte_data(&tvp5150_client, 0xFC, 0xF7);
    i2c_smbus_write_byte_data(&tvp5150_client, 0xCF, 0xFF); // Enable FFM
    return 0;//return i2c_probe(adapter, &addr_data, &tvp5150_detect_client); 
}

static int tvp5150_detach_client(struct i2c_client *client)
{
    i2c_detach_client(client);
    return 0;
}

struct proc_dir_entry *proc_file = NULL;

static int tvp5150cc_open(struct inode *inode, struct file *file)
{
    return 0;
}

static int tvp5150cc_release(struct inode *inode, struct file *file)
{
    return 0;
}

static int tvp5150cc_read(struct file *filp, char *buffer, size_t length, loff_t *offset)
{
    unsigned char temp;
    int i = 0;
    static char *hex = "0123456789ABCDEF";
    if( length < 6 ) { return 0; }
    temp = i2c_smbus_read_byte_data(&tvp5150_client, 0xC6);
    if( !(temp & 0x10) ) { buffer[0] = '\0'; return 1; }
    //buffer[i++] = hex[(temp&0xF0)>>4];
    //buffer[i++] = hex[(temp&0x0F)];
    buffer[i++] = 0x7F & i2c_smbus_read_byte_data(&tvp5150_client, 0x90);
    buffer[i++] = 0x7F & i2c_smbus_read_byte_data(&tvp5150_client, 0x91);
    //buffer[i++] = i2c_smbus_read_byte_data(&tvp5150_client, 0x92);
    //buffer[i++] = i2c_smbus_read_byte_data(&tvp5150_client, 0x93);
    
    // reset FIFO
    i2c_smbus_write_byte_data(&tvp5150_client, 0xC9, 0);
    
    return i;
}
 
static struct file_operations tvp5150_fops = {
    .open = tvp5150cc_open,
    .release = tvp5150cc_release,
    .read = tvp5150cc_read
};

static int __init cc_init_module(void)
{
    printk(KERN_INFO "attaching...\n");	
    register_chrdev(/*0x66*/0, "tvp5150cc", &tvp5150_fops);
    proc_file = create_proc_entry("tvp5150cc", S_IFREG | S_IWUSR, &proc_root);
    proc_file->proc_fops = &tvp5150_fops;
    i2c_add_driver(&tvp5150);
    printk(KERN_INFO "tvp5510 Close Caption Driver -- Paul Ingemi \n");
    return 0;
}

static void __exit cc_cleanup_module(void)
{
    unregister_chrdev(0x66, "tvp5150cc");
    remove_proc_entry("tvp5150cc", &proc_root);
    i2c_del_driver(&tvp5150);
    printk(KERN_INFO "bye!\n");
}

module_init(cc_init_module);
module_exit(cc_cleanup_module);

