/*
     Driver for Philips tda10046H OFDM Frontend with LG INNOTEK TdtpE001P Tuner,
	 based on Philips tda1004xh OFDM Frontend driver written by Andrew de Quincey 
	 & Robert Schlabbach

	 Written by Dany Salman -- salmandany@yahoo.fr
	 Copyright (c) 2004 TDF
     
     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.

   */

//====================================================================
// Standard Include files

#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/unistd.h>
#include <linux/fcntl.h>
#include <linux/syscalls.h>
#include <linux/i2c.h>

//====================================================================
// DVB Include files

#include "dvb_frontend.h"
#include "tmbsl10046DSPcode.h"
#include "tda1004x.h"

#define TDA1004X_DEMOD_TDA10045 0
#define TDA1004X_DEMOD_TDA10046 1

//====================================================================
// Defines

#define TDA1004X_CHIPID          0x00
#define TDA1004X_AUTO            0x01
#define TDA1004X_IN_CONF1        0x02
#define TDA1004X_IN_CONF2        0x03
#define TDA1004X_OUT_CONF1       0x04
#define TDA1004X_OUT_CONF2       0x05
#define TDA1004X_STATUS_CD       0x06
#define TDA1004X_CONFC4          0x07
#define TDA1004X_DSSPARE2        0x0C
#define TDA1004X_SCAN_CPT        0x10
#define TDA1004X_DSP_CMD         0x11
#define TDA1004X_DSP_ARG         0x12
#define TDA1004X_DSP_DATA1       0x13
#define TDA1004X_DSP_DATA2       0x14
#define TDA1004X_CONFADC1        0x15
#define TDA1004X_CONFC1          0x16
#define TDA10046H_AGC_TUN_LEVEL  0x1a
#define TDA1004X_SNR             0x1c
#define TDA1004X_CONF_TS1        0x1e
#define TDA1004X_CONF_TS2        0x1f
#define TDA1004X_CBER_RESET      0x20
#define TDA1004X_CBER_MSB        0x21
#define TDA1004X_CBER_LSB        0x22
#define TDA1004X_CVBER_LUT       0x23
#define TDA1004X_VBER_MSB        0x24
#define TDA1004X_VBER_MID        0x25
#define TDA1004X_VBER_LSB        0x26
#define TDA1004X_UNCOR           0x27
#define TDA1004X_IT_SEL			 0x29
#define TDA10046H_CONFPLL1       0x2D
#define TDA10046H_CONFPLL2       0x2F
#define TDA10046H_CONFPLL3       0x30
#define TDA10046H_TIME_WREF1     0x31
#define TDA10046H_TIME_WREF2     0x32
#define TDA10046H_TIME_WREF3     0x33
#define TDA10046H_TIME_WREF4     0x34
#define TDA10046H_TIME_WREF5     0x35
#define TDA1004X_CONFADC2        0x37
#define TDA10046H_CONF_TRISTATE1 0x3B
#define TDA10046H_CONF_TRISTATE2 0x3C
#define TDA10046H_CONF_POLARITY  0x3D
#define TDA10046H_FREQ_OFFSET    0x3E
#define TDA10046H_GPIO_SP_DS3    0x40
#define TDA10046H_GPIO_OUT_SEL   0x41
#define TDA10046H_GPIO_SELECT    0x42
#define TDA10046H_AGC_CONF       0x43
#define TDA10046H_AGC_GAINS      0x46
#define TDA10046H_AGC_TUN_MIN    0x47
#define TDA10046H_AGC_TUN_MAX    0x48
#define TDA10046H_AGC_IF_MIN     0x49
#define TDA10046H_AGC_IF_MAX     0x4A
#define TDA10046H_FREQ_PHY2_MSB  0x4D
#define TDA10046H_FREQ_PHY2_LSB  0x4E
#define TDA10046H_CVBER_CTRL     0x4F
#define TDA10046H_CHANNEL_INFO1	 0x50
#define TDA10046H_AGC_IF_LEVEL   0x52
#define TDA10046H_CODE_CPT       0x57
#define TDA10046H_CODE_IN        0x58

#define TDA10046_MAX_UNITS		 1
#define TDA10046_RF_MIN          170000
#define TDA10046_RF_MAX          887000000

#define TDA10046_CS_MIN          6000000
#define TDA10046_CS_MAX          8000000

#define DEMOD_I2C_ADDRESS   0x10 >> 1
#define TUNER_I2C_ADDRESS   0xC2 >> 1
#define EEPROM_I2C_ADDRESS  0xA0

//====================================================================
// Module parameters

MODULE_DESCRIPTION("Philips TDA10046H DVB-T Frontend");
MODULE_AUTHOR("Dany Salman <salmandany@yahoo.fr>");
MODULE_LICENSE("GPL");

#ifdef PCMCIA_DEBUG
INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG);
#define DEBUG(n, args...) if (pc_debug>(n)) DEBUG(0,KERN_DEBUG args)
static char *version = "pluto_cs.c 1.10 2004/07/22 (Dany Salman)";
#else
#define DEBUG(n, args...)
#endif

struct tda1004x_state {
	struct i2c_adapter* i2c;
	struct dvb_frontend_ops ops;
	const struct tda1004x_config* config;
	struct dvb_frontend frontend;
	u8 initialised:1;
	u8 demod_type;
};

// Information about frontend capabilities and frequencies limit
// called by ioctl FE_GET_INFO

//====================================================================
// Functions

// Write the byte <data> to the register <reg> of the demodulator
static int tda10046_write_byte(struct i2c_adapter *i2c, int reg, int data)
{
        int ret;
        u8 buf[] = { reg, data };
        struct i2c_msg msg = { .addr=0, .flags=0, .buf=buf, .len=2 };

        DEBUG(1,"%s: reg=0x%x, data=0x%x\n", __FUNCTION__, reg, data);

        msg.addr = DEMOD_I2C_ADDRESS;
        ret = i2c_transfer(i2c, &msg, 1);

        if (ret != 1)
                DEBUG(0,"%s: error reg=0x%x, data=0x%x, ret=%i\n",
                        __FUNCTION__, reg, data, ret);

        DEBUG(1,"%s: success reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__,
                reg, data, ret);
        return (ret != 1) ? -1 : 0;
}

// Read the byte contained in register <reg>
static int tda1004x_read_byte(struct i2c_adapter *i2c, int reg)
{
        int ret;
        u8 b0[] = { reg };
        u8 b1[] = { 0 };
        struct i2c_msg msg[] = {{ .addr=0, .flags=0, .buf=b0, .len=1},
                                { .addr=0, .flags=I2C_M_RD, .buf=b1, .len = 1}};

        DEBUG(1,"%s: reg=0x%x\n", __FUNCTION__, reg);

        msg[0].addr = DEMOD_I2C_ADDRESS;
        msg[1].addr = DEMOD_I2C_ADDRESS;
        ret = i2c_transfer(i2c, msg, 2);

        if (ret != 2) {
                DEBUG(0,"%s: error reg=0x%x, ret=%i\n", __FUNCTION__, reg,
                        ret);
                return -1;
        }

        DEBUG(1,"%s: success reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__,
                reg, b1[0], ret);
        return b1[0];
}

// Write byte data <data> masked by <mask> in register <reg>
static int tda1004x_write_mask(struct i2c_adapter *i2c, int reg, int mask, int data)
{
        int val;
        DEBUG(1,"%s: reg=0x%x, mask=0x%x, data=0x%x\n", __FUNCTION__, reg,
                mask, data);

        // read a byte and check
        val = tda1004x_read_byte(i2c, reg);
        if (val < 0)
                return val;

        // mask if off
        val = val & ~mask;
        val |= data & 0xff;

        // write it out again
        return tda10046_write_byte(i2c, reg, val);
}

// Write <len> bytes contained by <buf> in register <reg>
static int tda1004x_write_buf(struct i2c_adapter *i2c, int reg, unsigned char *buf, int len)
{
        int i;
        int result;

        DEBUG(1,"%s: reg=0x%x, len=0x%x\n", __FUNCTION__, reg, len);

        result = 0;
        for (i = 0; i < len; i++) {
                result = tda10046_write_byte(i2c, reg + i, buf[i]);
                if (result != 0)
                        break;
        }

        return result;
}

// Set bandwitdh
static int tda10046h_set_bandwidth(struct i2c_adapter *i2c, fe_bandwidth_t bandwidth)
{
        static u8 bandwidth_6mhz[] = { 0x80, 0x15, 0xfe, 0xab, 0x8e };
        static u8 bandwidth_7mhz[] = { 0x6e, 0x02, 0x53, 0xc8, 0x25 };
        static u8 bandwidth_8mhz[] = { 0x60, 0x12, 0xa8, 0xe4, 0xbd };

        switch (bandwidth) {
        case BANDWIDTH_6_MHZ:
                tda1004x_write_buf(i2c, TDA10046H_TIME_WREF1, bandwidth_6mhz, sizeof(bandwidth_6mhz));
                tda10046_write_byte(i2c, TDA1004X_DSSPARE2, 0);
                break;

        case BANDWIDTH_7_MHZ:
                tda1004x_write_buf(i2c, TDA10046H_TIME_WREF1, bandwidth_7mhz, sizeof(bandwidth_7mhz));
                tda10046_write_byte(i2c, TDA1004X_DSSPARE2, 0);
                break;

        case BANDWIDTH_8_MHZ:
                tda1004x_write_buf(i2c, TDA10046H_TIME_WREF1, bandwidth_8mhz, sizeof(bandwidth_8mhz));
                tda10046_write_byte(i2c, TDA1004X_DSSPARE2, 0xFF);
                break;

        default:
                return -EINVAL;
        }

        // done
        return 0;
}

// Upload the firmware
static int tda1004x_fwupload(struct i2c_adapter *i2c)
{
	u8 dsp_data1, dsp_data2;
	u32 totalsize = 0;
	struct i2c_msg fw_msg ;
	fw_msg.addr = DEMOD_I2C_ADDRESS;
	fw_msg.flags = 0 ;
	
	printk("Please wait while uploading firmware...\n");
	
	// First calculate the size of code included in the firmware
	
	totalsize += (u32)((DSPcode10046RAM[0] << 8) | DSPcode10046RAM[1]) * 2;
	totalsize += 4;
	totalsize += (u32)((DSPcode10046RAM[totalsize] << 8) | DSPcode10046RAM[totalsize + 1]) * 2;
	totalsize += 4;
	
	// Prepare the chip to accept a firmware uploading
	
	tda1004x_write_mask(i2c, TDA10046H_CONF_TRISTATE1, 1, 0);
	tda1004x_write_mask(i2c, TDA1004X_CONFC4, 8, 8);
	tda10046_write_byte(i2c, TDA10046H_CODE_CPT, 0x00);
	
	// Prepare the message 
	
	fw_msg.buf = vmalloc(totalsize + 1);
	fw_msg.len = totalsize + 1;
	fw_msg.buf[0] = TDA10046H_CODE_IN;
	
	// Copy the content of array DSPcode10046RAM into message buffer
	
	memcpy(fw_msg.buf + 1, DSPcode10046RAM, totalsize);
	
	// Upload firmware into TDA10046H_CODE_IN
	
	if (i2c_transfer(i2c, &fw_msg, 1) != 1) 
	{
       	printk("tda1004x: Error during firmware upload\n");
		vfree (fw_msg.buf);
		return -EIO;
    }
	vfree (fw_msg.buf);
	
	// First test to check if firmware has correctly been uploaded
	
	tda10046_write_byte(i2c, TDA1004X_DSP_CMD, 0x61); 
	dsp_data1 = tda1004x_read_byte(i2c, TDA1004X_DSP_DATA1);
	dsp_data2 = tda1004x_read_byte(i2c, TDA1004X_DSP_DATA2);
	if ((dsp_data1 != 0) || (dsp_data2 != 0))
		return -EIO;
		
	// Second test to check if firmware has correctly been uploaded
		
	tda10046_write_byte(i2c, TDA1004X_DSP_CMD, 0x67);
	dsp_data1 = tda1004x_read_byte(i2c, TDA1004X_DSP_DATA1);
	dsp_data2 = tda1004x_read_byte(i2c, TDA1004X_DSP_DATA2);
	if (dsp_data1 != 0x67)
		return -EIO;
	
	// So far, we consider all was fine
	
	printk("Firmware uploaded successfully !\n");
	
	return 0;	
}

// Process the FEC

static int tda1004x_encode_fec(int fec)
{
        // convert known FEC values
		
        switch (fec) {
        	case FEC_1_2:	return 0;
	        case FEC_2_3:	return 1;
    	    case FEC_3_4:	return 2;
	        case FEC_5_6:	return 3;
	        case FEC_7_8:	return 4;
        }

        // unsupported
		
        return -EINVAL;
}

static int tda1004x_decode_fec(int tdafec)
{
        // convert known FEC values
		
        switch (tdafec) {
        case 0:
                return FEC_1_2;
        case 1:
                return FEC_2_3;
        case 2:
                return FEC_3_4;
        case 3:
                return FEC_5_6;
        case 4:
                return FEC_7_8;
        }

        // unsupported
		
        return -1;
}

// Setup new frequency into tuner

static int tda1004x_set_frequency(struct i2c_adapter *i2c, struct dvb_frontend_parameters *fe_params)
{
        u8 tuner_buf[4];
        struct i2c_msg tuner_msg = {.addr=0, .flags=0, .buf=tuner_buf, .len=sizeof(tuner_buf) };
        u32 tuner_frequency = 0;
       	u8 tmp;
	   
        DEBUG(0,"%s\n", __FUNCTION__);

        // setup auto offset
		
	    tda1004x_write_mask(i2c, TDA1004X_AUTO, 0x10, 0x10);
	    tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x80, 0);		
	    tda1004x_write_mask(i2c, TDA1004X_IN_CONF2, 0xC0, 0);
		
	    // disable agc_conf[2]
		
	    tda1004x_write_mask(i2c, TDA10046H_AGC_CONF, 4, 0);
        
        //tda1004x_enable_tuner_i2c(i2c);
		
		tda1004x_write_mask(i2c, TDA1004X_CONFC4, 2, 2);
		
        // setup the frequency buffer
		
		tuner_frequency = (((fe_params->frequency / 1000) * 6) + 217502) / 1000;
		tuner_buf[0] = (tuner_frequency >> 8) & 0x7f;
		tuner_buf[1] = tuner_frequency & 0xff;
		
		if (fe_params->frequency < 611000000)
			tuner_buf[2] = 0xb4;
		else if (fe_params->frequency < 811000000)
			tuner_buf[2] = 0xbc;
		else
			tuner_buf[2] = 0xf4;
		if (fe_params->frequency < 470000000)
			tuner_buf[3] = 0x02;
		else if (fe_params->frequency < 823000000)
			tuner_buf[3] = 0x04;
		if (fe_params->u.ofdm.bandwidth == BANDWIDTH_8_MHZ) 
			tuner_buf[3] |= 0x08;
        tuner_msg.addr = TUNER_I2C_ADDRESS;
        tuner_msg.len = 4;
        if ((tmp = i2c_transfer(i2c, &tuner_msg, 1)) != 1) {
			printk("I2C Tuner error : tmp %d, addr 0x%02x, buf[0] 0x%02x, buf[1] 0x%02x, buf[2] 0x%02x, buf[3] 0x%02x\n",tmp,tuner_msg.addr,tuner_msg.buf[0],tuner_msg.buf[1],tuner_msg.buf[2],tuner_msg.buf[3]);
        	return -EIO;
        }   
		msleep(5);
		tda1004x_write_mask(i2c, TDA1004X_CONFC4, 2, 0);
        tda1004x_write_mask(i2c, TDA10046H_AGC_CONF, 4, 4);		
        
		DEBUG(0,"%s: success\n", __FUNCTION__);

        // done
        
		return 0;
}

// Setup new frontend parameters (FE_SET_FRONTEND ioctl)
static int tda1004x_set_fe(struct dvb_frontend *fe, struct dvb_frontend_parameters *fe_params)
{
	struct tda1004x_state *state = fe->demodulator_priv;
	struct i2c_adapter *i2c = state->i2c;
        int tmp;
        int inversion;

        DEBUG(0,"%s\n", __FUNCTION__);

        // set frequency
		
        if ((tmp = tda1004x_set_frequency(i2c, fe_params)) < 0)
                return tmp;

        // hardcoded to use auto as much as possible
		
        fe_params->u.ofdm.code_rate_HP = FEC_AUTO;
        fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_AUTO;
        fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO;

        // Set standard params.. or put them to auto
		
        if ((fe_params->u.ofdm.code_rate_HP == FEC_AUTO) ||
            (fe_params->u.ofdm.code_rate_LP == FEC_AUTO) ||
            (fe_params->u.ofdm.constellation == QAM_AUTO) ||
            (fe_params->u.ofdm.hierarchy_information == HIERARCHY_AUTO)) 
		{
                tda1004x_write_mask(i2c, TDA1004X_AUTO, 1, 1);	// enable auto
                tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x03, 0);	// turn off constellation bits
                tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x60, 0);	// turn off hierarchy bits
                tda1004x_write_mask(i2c, TDA1004X_IN_CONF2, 0x3f, 0);	// turn off FEC bits
        } 
		else
		{
                tda1004x_write_mask(i2c, TDA1004X_AUTO, 1, 0);	// disable auto

                // set HP FEC
				
                tmp = tda1004x_encode_fec(fe_params->u.ofdm.code_rate_HP);
                if (tmp < 0) return tmp;
                tda1004x_write_mask(i2c, TDA1004X_IN_CONF2, 7, tmp);

                // set LP FEC
				
                if (fe_params->u.ofdm.code_rate_LP != FEC_NONE) 
				{
                        tmp = tda1004x_encode_fec(fe_params->u.ofdm.code_rate_LP);
                        if (tmp < 0) return tmp;
                        tda1004x_write_mask(i2c, TDA1004X_IN_CONF2, 0x38, tmp << 3);
                }

                // set constellation
				
                switch (fe_params->u.ofdm.constellation) 
				{
                	case QPSK:	tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 3, 0);
		                        break;
		
        	        case QAM_16:	tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 3, 1);
			                        break;

            	    case QAM_64:	tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 3, 2);
			                        break;

            	    default:	return -EINVAL;
                }

                // set hierarchy
				
                switch (fe_params->u.ofdm.hierarchy_information) 
				{
					case HIERARCHY_NONE:	tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x60, 0 << 5);
					                        break;

	                case HIERARCHY_1:	tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x60, 1 << 5);
					                    break;

	                case HIERARCHY_2:	tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x60, 2 << 5);
					                    break;

	                case HIERARCHY_4:	tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x60, 3 << 5);
					                    break;

                	default:	return -EINVAL;
                }
        }

        // set bandwidth
		
        tda10046h_set_bandwidth(i2c, fe_params->u.ofdm.bandwidth);
        
        // need to invert the inversion for TT TDA10046H
		
        inversion = fe_params->inversion;
        inversion = inversion ? INVERSION_OFF : INVERSION_ON;

        // set inversion
		
        switch (inversion) 
		{
        	case INVERSION_OFF:	tda1004x_write_mask(i2c, TDA1004X_CONFC1, 0x20, 0);
				                break;

	        case INVERSION_ON:	tda1004x_write_mask(i2c, TDA1004X_CONFC1, 0x20, 0x20);
				                break;

	        default:	return -EINVAL;
        }

        // set guard interval
		
        switch (fe_params->u.ofdm.guard_interval) 
		{
        	case GUARD_INTERVAL_1_32:	tda1004x_write_mask(i2c, TDA1004X_AUTO, 2, 0);
										tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x0c, 0 << 2);
						                break;

	        case GUARD_INTERVAL_1_16:	tda1004x_write_mask(i2c, TDA1004X_AUTO, 2, 0);
						                tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x0c, 1 << 2);
						                break;

	        case GUARD_INTERVAL_1_8:	tda1004x_write_mask(i2c, TDA1004X_AUTO, 2, 0);
							            tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x0c, 2 << 2);
						                break;

	        case GUARD_INTERVAL_1_4:	tda1004x_write_mask(i2c, TDA1004X_AUTO, 2, 0);
						                tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x0c, 3 << 2);
						                break;

	        case GUARD_INTERVAL_AUTO:	tda1004x_write_mask(i2c, TDA1004X_AUTO, 2, 2);
						                tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x0c, 0 << 2);
						                break;

	        default:	return -EINVAL;
        }

        // set transmission mode
		
        switch (fe_params->u.ofdm.transmission_mode) 
		{
        	case TRANSMISSION_MODE_2K:	tda1004x_write_mask(i2c, TDA1004X_AUTO, 4, 0);
						                tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x10, 0 << 4);
										break;

	        case TRANSMISSION_MODE_8K:	tda1004x_write_mask(i2c, TDA1004X_AUTO, 4, 0);
						                tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x10, 1 << 4);
										break;

	        case TRANSMISSION_MODE_AUTO:	tda1004x_write_mask(i2c, TDA1004X_AUTO, 4, 4);
							                tda1004x_write_mask(i2c, TDA1004X_IN_CONF1, 0x10, 0);
											break;

	        default:	return -EINVAL;
        }

        // start the lock
		
        tda1004x_write_mask(i2c, TDA1004X_AUTO, 0x40, 0x40);
        msleep(10);
        
        // done
        return 0;
}

// Get frontend parameters (FE_GET_FRONTEND ioctl)
static int tda1004x_get_fe(struct dvb_frontend *fe, struct dvb_frontend_parameters *fe_params)
{
	struct tda1004x_state *state = fe->demodulator_priv;
	struct i2c_adapter *i2c = state->i2c;
        DEBUG(0,"%s\n", __FUNCTION__);

        // inversion status
		
        fe_params->inversion = INVERSION_OFF;
        if (tda1004x_read_byte(i2c, TDA1004X_CONFC1) & 0x20) 
	        fe_params->inversion = INVERSION_ON;

        // need to invert the inversion for TT TDA10046H
        
		fe_params->inversion = fe_params->inversion ? INVERSION_OFF : INVERSION_ON;
        
        // bandwidth
        
		switch (tda1004x_read_byte(i2c, TDA10046H_TIME_WREF1)) 
		{
        	case 0x60:	fe_params->u.ofdm.bandwidth = BANDWIDTH_8_MHZ;
						break;
	        case 0x6e:	fe_params->u.ofdm.bandwidth = BANDWIDTH_7_MHZ;
						break;
        	case 0x80:	fe_params->u.ofdm.bandwidth = BANDWIDTH_6_MHZ;
						break;
        }
        
        // FEC
		
        fe_params->u.ofdm.code_rate_HP = tda1004x_decode_fec(tda1004x_read_byte(i2c, TDA1004X_OUT_CONF2) & 7);
        fe_params->u.ofdm.code_rate_LP = tda1004x_decode_fec((tda1004x_read_byte(i2c, TDA1004X_OUT_CONF2) >> 3) & 7);

        // constellation
		
        switch (tda1004x_read_byte(i2c, TDA1004X_OUT_CONF1) & 3) 
		{
        	case 0:	fe_params->u.ofdm.constellation = QPSK;
	                break;
    	    case 1:	fe_params->u.ofdm.constellation = QAM_16;
	                break;
    	    case 2:	fe_params->u.ofdm.constellation = QAM_64;
	                break;
        }

        // transmission mode
    
	    fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_2K;
        if (tda1004x_read_byte(i2c, TDA1004X_OUT_CONF1) & 0x10) 
			fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K;

        // guard interval
		
        switch ((tda1004x_read_byte(i2c, TDA1004X_OUT_CONF1) & 0x0c) >> 2) 
		{
        case 0:	fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_32;
                break;
        case 1:	fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_16;
                break;
        case 2:	fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_8;
                break;
        case 3:	fe_params->u.ofdm.guard_interval = GUARD_INTERVAL_1_4;
                break;
        }

        // hierarchy
		
        switch ((tda1004x_read_byte(i2c, TDA1004X_OUT_CONF1) & 0x60) >> 5) 
		{
        	case 0:	fe_params->u.ofdm.hierarchy_information = HIERARCHY_NONE;
		            break;
        	case 1:	fe_params->u.ofdm.hierarchy_information = HIERARCHY_1;
	                break;
    	    case 2:	fe_params->u.ofdm.hierarchy_information = HIERARCHY_2;
	                break;
    	    case 3:	fe_params->u.ofdm.hierarchy_information = HIERARCHY_4;
	                break;
        }

        // done
    
	    return 0;
}

// Read chip status (FE_READ_STATUS ioctl)
static int tda1004x_read_status(struct dvb_frontend *fe, fe_status_t * fe_status)
{
	struct tda1004x_state *state = fe->demodulator_priv;
	struct i2c_adapter *i2c = state->i2c;
        int status;
        int cber;
       
        DEBUG(0,"%s\n", __FUNCTION__);

        // read status
		
        status = tda1004x_read_byte(i2c, TDA1004X_STATUS_CD);
        if (status == -1) 
			return -EIO;

        // decode

        *fe_status = 0;
        if (status & 4) *fe_status |= FE_HAS_SIGNAL;
        if (status & 2) *fe_status |= FE_HAS_CARRIER;
        if (status & 8) *fe_status |= FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK;

        // if we don't already have VITERBI (i.e. not LOCKED), see if the viterbi
        // is getting anything valid

        if (!(*fe_status & FE_HAS_VITERBI)) 
		{
                // read the CBER
        
		        cber = tda1004x_read_byte(i2c, TDA1004X_CBER_LSB);
                if (cber == -1) return -EIO;
                status = tda1004x_read_byte(i2c, TDA1004X_CBER_MSB);
                if (status == -1) return -EIO;
                cber |= (status << 8);
                tda1004x_read_byte(i2c, TDA1004X_CBER_RESET);
                if (cber != 65535) 
	                *fe_status |= FE_HAS_VITERBI;
		}

        /*// if we DO have some valid VITERBI output, but don't already have SYNC
        // bytes (i.e. not LOCKED), see if the RS decoder is getting anything valid.
        
		if ((*fe_status & FE_HAS_VITERBI) && (!(*fe_status & FE_HAS_SYNC))) {
  
        	    // read the VBER
  
                vber = tda1004x_read_byte(i2c, TDA1004X_VBER_LSB);
                if (vber == -1) return -EIO;
                status = tda1004x_read_byte(i2c, TDA1004X_VBER_MID);
                if (status == -1) return -EIO;
                vber |= (status << 8);
                status = tda1004x_read_byte(i2c, TDA1004X_VBER_MSB);
                if (status == -1) return -EIO;
                vber |= ((status << 16) & 0x0f);
                tda1004x_read_byte(i2c, TDA1004X_CVBER_LUT);

                // if RS has passed some valid TS packets, then we must be
                // getting some SYNC bytes
  
                if (vber < 16632) 
					*fe_status |= FE_HAS_SYNC;
        }*/

        // success
		
        DEBUG(0,"%s: fe_status=0x%x\n", __FUNCTION__, *fe_status);
        return 0;
}

// Read the signal strength (FE_READ_SIGNAL_STRENGTH ioctl)
static int tda1004x_read_signal_strength(struct dvb_frontend *fe, u16 *signal)
{
	struct tda1004x_state *state = fe->demodulator_priv;
	struct i2c_adapter *i2c = state->i2c;
        int tmp;
        int reg = 0;

	DEBUG(0,"%s\n", __FUNCTION__);

        reg = TDA10046H_AGC_IF_LEVEL;

        // read it
        tmp = tda1004x_read_byte(i2c, reg);
        if (tmp < 0)
                return -EIO;

        // done
        *signal = (tmp << 8) | tmp;
        DEBUG(0,"%s: signal=0x%x\n", __FUNCTION__, *signal);
        return 0;
}

// Read the signal noise ratio (FE_READ_SNR ioctl)
static int tda1004x_read_snr(struct dvb_frontend *fe, u16 *snr)
{
	struct tda1004x_state *state = fe->demodulator_priv;
	struct i2c_adapter *i2c = state->i2c;
        int tmp;

	DEBUG(0,"%s\n", __FUNCTION__);

         // read it
        tmp = tda1004x_read_byte(i2c, TDA1004X_SNR);
        if (tmp < 0)
                return -EIO;
        if (tmp) {
                tmp = 255 - tmp;
        }

        // done
        *snr = ((tmp << 8) | tmp);
        DEBUG(0,"%s: snr=0x%x\n", __FUNCTION__, *snr);
        return 0;
}

// Read the number of uncorrected blocks (FE_READ_UNCORRECTED_BLOCKS ioctl)
static int tda1004x_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
{
	struct tda1004x_state *state = fe->demodulator_priv;
	struct i2c_adapter *i2c = state->i2c;
        int tmp;
        int tmp2;
        int counter;

        DEBUG(0,"%s\n", __FUNCTION__);

        // read the UCBLOCKS and reset
        counter = 0;
        tmp = tda1004x_read_byte(i2c, TDA1004X_UNCOR);
        if (tmp < 0)
        	return -EIO;
        tmp &= 0x7f;
        while (counter++ < 5) 
		{
        	tda1004x_write_mask(i2c, TDA1004X_UNCOR, 0x80, 0);
            tda1004x_write_mask(i2c, TDA1004X_UNCOR, 0x80, 0);
            tda1004x_write_mask(i2c, TDA1004X_UNCOR, 0x80, 0);
            tmp2 = tda1004x_read_byte(i2c, TDA1004X_UNCOR);
            if (tmp2 < 0)
            	return -EIO;
            tmp2 &= 0x7f;
            if ((tmp2 < tmp) || (tmp2 == 0))
            	break;
        }

        // done
        if (tmp != 0x7f) 
        	*ucblocks = tmp;
        else
        	*ucblocks = 0xffffffff;
        DEBUG(0,"%s: ucblocks=0x%x\n", __FUNCTION__, *ucblocks);
        return 0;
}

// Read the Bit Error Rate (FE_READ_BER ioctl)
static int tda1004x_read_ber(struct dvb_frontend *fe, u32* ber)
{
	struct tda1004x_state *state = fe->demodulator_priv;
	struct i2c_adapter *i2c = state->i2c;
        int tmp;

        DEBUG(0,"%s\n", __FUNCTION__);

		// read it in
        tmp = tda1004x_read_byte(i2c, TDA1004X_CBER_LSB);
        if (tmp < 0) return -EIO;
        *ber = tmp << 1;
        tmp = tda1004x_read_byte(i2c, TDA1004X_CBER_MSB);
        if (tmp < 0) return -EIO;
        *ber |= (tmp << 9);
        tda1004x_read_byte(i2c, TDA1004X_CBER_RESET);

        // done
        DEBUG(0,"%s: ber=0x%x\n", __FUNCTION__, *ber);
        return 0;
}

// Initialize demodulator (FE_INIT ioctl)
static int tda10046_init(struct dvb_frontend *fe)
{
	struct tda1004x_state *state = fe->demodulator_priv;
	struct i2c_adapter *i2c = state->i2c;
	int status = 0;

	status |= tda10046_write_byte(i2c, TDA10046H_CONFPLL2, 0xa);
	status |= tda10046_write_byte(i2c, TDA10046H_CONFPLL3, 0x03);
	status |= tda10046_write_byte(i2c, TDA10046H_FREQ_OFFSET, 0x64);
	status |= tda10046_write_byte(i2c, TDA10046H_FREQ_PHY2_MSB, 0xd4);
	status |= tda10046_write_byte(i2c, TDA10046H_FREQ_PHY2_LSB, 0x2a); 
	status |= tda10046_write_byte(i2c, TDA10046H_TIME_WREF1, 0x60); 
	status |= tda10046_write_byte(i2c, TDA10046H_TIME_WREF2, 0x12); 
	status |= tda10046_write_byte(i2c, TDA10046H_TIME_WREF3, 0xa8); 
	status |= tda10046_write_byte(i2c, TDA10046H_TIME_WREF4, 0xe4); 
	status |= tda10046_write_byte(i2c, TDA10046H_TIME_WREF5, 0xbd); 
	status |= tda1004x_write_mask(i2c, TDA1004X_CONFC4, 0x20, 0);
	status |= tda1004x_write_mask(i2c, TDA1004X_CONFC1, 0xa0, 0x20); 
	status |= tda10046_write_byte(i2c, TDA10046H_AGC_CONF, 0);
	status |= tda1004x_write_mask(i2c, TDA10046H_CONF_POLARITY, 0x60, 0x60);
	status |= tda10046_write_byte(i2c, TDA10046H_AGC_TUN_MIN, 0);
	status |= tda10046_write_byte(i2c, TDA10046H_AGC_TUN_MAX, 0xff);
	status |= tda10046_write_byte(i2c, TDA10046H_AGC_IF_MIN, 0); 
	status |= tda10046_write_byte(i2c, TDA10046H_AGC_IF_MAX, 0xff);
	status |= tda1004x_write_mask(i2c, TDA10046H_CVBER_CTRL, 0x30, 0x20); 
	status |= tda1004x_write_mask(i2c, TDA1004X_IT_SEL, 0x08, 0x08);
	status |= tda10046_write_byte(i2c, TDA10046H_AGC_GAINS, 0x1);
	status |= tda1004x_write_mask(i2c, TDA1004X_AUTO, 0x80, 0);
	status |= tda10046_write_byte(i2c, TDA1004X_CONF_TS1, 0x7);
	status |= tda1004x_write_mask(i2c, TDA1004X_CONF_TS2, 0x31, 0); 
	status |= tda1004x_write_mask(i2c, TDA10046H_CONF_TRISTATE1, 0x9e, 0);
	status |= tda10046_write_byte(i2c, TDA10046H_CONF_TRISTATE2, 0xbc); 
	status |= tda10046_write_byte(i2c, TDA10046H_GPIO_OUT_SEL, 0xfc); 
	status |= tda10046_write_byte(i2c, TDA10046H_GPIO_SP_DS3, 0); 	
	status |= tda10046_write_byte(i2c, TDA10046H_CHANNEL_INFO1, 0); 
	status |= tda10046_write_byte(i2c, TDA1004X_CONFADC2, 0x74); 
	status |= tda10046_write_byte(i2c, TDA1004X_CONFADC2, 0x34); 

	// upload firmware
	DEBUG(0,"%s: Uploading firmware",__FUNCTION__);
        status |= tda1004x_fwupload(i2c);

	return status;
}

// Disable tuner activity (FE_SLEEP ioctl)
static int tda1004x_sleep(struct dvb_frontend *fe)
{
	// Do nothing... for the moment
	//tda1004x_write_mask(i2c, TDA1004X_CONFC4, 1, 1);
	return 0;
}

static int tda1004x_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings)
{
	fesettings->min_delay_ms = 800;
	fesettings->step_size = 166667;
	fesettings->max_drift = 166667*2;

	return 0;
}

static void tda1004x_release(struct dvb_frontend *fe)
{
	struct tda1004x_state *state = fe->demodulator_priv;
	kfree(state);
}

static struct dvb_frontend_ops tda10046_ops = {
	.info = {
		.name = "Philips TDA10046H DVB-T",
		.type = FE_OFDM,
		.frequency_min = TDA10046_RF_MIN,
		.frequency_max = TDA10046_RF_MAX,
		.frequency_stepsize = 166667,
		.caps =
			FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
			FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
			FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO |
			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO
	},
	.release = tda1004x_release,
	.init = tda10046_init,
	.sleep = tda1004x_sleep,
	.set_frontend = tda1004x_set_fe,
	.get_frontend = tda1004x_get_fe,
	.get_tune_settings = tda1004x_get_tune_settings,
	.read_status = tda1004x_read_status,
	.read_ber = tda1004x_read_ber,
	.read_signal_strength = tda1004x_read_signal_strength,
	.read_snr = tda1004x_read_snr,
	.read_ucblocks = tda1004x_read_ucblocks,
};

struct dvb_frontend *tda10046_attach(const struct tda1004x_config *config, struct i2c_adapter *i2c)
{
	struct tda1004x_state* state;

	DEBUG(0,"%s\n", __FUNCTION__);

	state = kmalloc(sizeof(struct tda1004x_state), GFP_KERNEL);
	if (!state)
		goto error;

	state->config = config;
	state->i2c = i2c;
	memcpy(&state->ops, &tda10046_ops, sizeof(struct dvb_frontend_ops));
	state->initialised = 0;
	state->demod_type = TDA1004X_DEMOD_TDA10046;

	if (tda1004x_read_byte(i2c, TDA1004X_CHIPID) != 0x46)
		goto error;

	state->frontend.ops = &state->ops;
	state->frontend.demodulator_priv = state;
	return &state->frontend;

error:
	kfree(state);
	return NULL;
}

EXPORT_SYMBOL(tda10046_attach);