/*
    Driver for NXP tda10048h OFDM Demodulator

    (c) 2008 Yacine Falek

    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.

*/
/*
 * This driver needs external firmware. Please use the commands
 * "<kerneldir>/Documentation/dvb/get_dvb_firmware tda10048",
 * download/extract them, and then copy them to /usr/lib/hotplug/firmware
 * or /lib/firmware (depending on configuration of firmware hotplug).
 */
#define TDA10048_DEFAULT_FIRMWARE      "dvb-fe-tda10048.fw"
#define TDA10048_DEFAULT_FIRMWARE_INFO "dvb-fe-tda10048.info"

#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/device.h>

#include "dvb_frontend.h"
#include "tda10048.h"

static int tda10048_init_on_startup;
module_param_named(init_on_startup, tda10048_init_on_startup, int, 0644);
MODULE_PARM_DESC(init_on_startup, "Load firmware on startup");

static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off).");

#define dprintk(args...) \
    do { \
        if (debug) printk(KERN_DEBUG "tda10048: " args); \
    } while (0)

/* TDA10048 registers */
#define TDA10048_CHIPID             0x00
#define TDA10048_RESERVED           0x02
#define TDA10048_IC_MODE            0x4E

#define TDA10048_AUTO               0x18
#define TDA10048_IN_CONF1           0x10
#define TDA10048_IN_CONF2           0x11
#define TDA10048_IN_CONF3           0x12
#define TDA10048_OUT_CONF1          0x14
#define TDA10048_OUT_CONF2          0x15
#define TDA10048_OUT_CONF3          0x16

#define TDA10048_CONFC4_1           0x1E
#define TDA10048_CONFC4_2           0x1F

#define TDA10048_DSP_ADR_MSB        0x3D
#define TDA10048_DSP_REG_MSB        0x3F
#define TDA10048_DSP_REG_LSB        0x3E
#define TDA10048_DSP_CODE_CPT       0x0C
#define TDA10048_DSP_CODE_IN        0x0E

#define TDA10048_CONF_TRISTATE1     0x44
#define TDA10048_CONF_TRISTATE2     0x45
#define TDA10048_CONF_POLARITY      0x46

#define TDA10048_CONF_PLL1          0x51
#define TDA10048_CONF_PLL2          0x52
#define TDA10048_CONF_PLL3          0x53
#define TDA10048_CONF_ADC           0x54
#define TDA10048_CONFC1_1           0x60
#define TDA10048_CONFC1_3           0x62
#define TDA10048_CONFX0             0x50

#define TDA10048_FREQ_PHY2_LSB      0x86

#define TDA10048_SYNC_STATUS        0x1A

#define TDA10048_AGC_CONF           0x70
#define TDA10048_AGC_THR            0x72
#define TDA10048_DIG_AGC_THR        0x75
#define TDA10048_AGC_GAINS          0x76
#define TDA10048_AGCTUN_MIN         0x78

#define TDA10048_CONFTS1            0xE1
#define TDA10048_CONFTS2            0xE0

#define TDA10048_CVBER_CTRL         0xC2
#define TDA10048_CBER_LSB           0xC6
#define TDA10048_CBER_MSB           0xC7

#define TDA10048_VBER_LSB           0xC8
#define TDA10048_VBER_MID           0xC9
#define TDA10048_VBER_MSB           0xCA

#define TDA10048_GPIO_SP_DS0        0x48
#define TDA10048_GPIO_SELECT        0x4D

#define TDA10048_TIME_INWREF_LSB    0x88
#define TDA10048_AGC_IFLEVEL        0x7F
#define TDA10048_DIG_AGC_LEVEL      0x81
#define TDA10048_NP_OUT             0xA2

#define TDA10048_UNCOR_CPTLSB       0xCE
#define TDA10048_UNCOR_CPTMSB       0xCF
#define TDA10048_UNCOR_CTRL         0xCD

#define TDA10048_SOFT_ITC3          0xD6
#define TDA10048_SCAN_CPT           0x1C

#define TDA10048_PVBER_MSK          0x02
#define TDA10048_AGC_GAINS_DEF      0x12


/* Bits mask */
#define TDA10048_AUTO_AUTOTPS                   0x01
#define TDA10048_AUTO_AUTOGI_MSK                0x02
#define TDA10048_AUTO_AUTO4K_MSK                0x80
#define TDA10048_AUTO_AUTO2K8K_MSK              0x04
#define TDA10048_AUTO_HPLP_MSK                  0x08
#define TDA10048_AUTO_AUTOOFFSET                0x10
#define TDA10048_AUTO_START_LOCK                0x40

#define TDA10048_IN_CONF1_CNF2K4K8K_MSK         0x03
#define TDA10048_IN_CONF1_DELTA_MSK             0x0C
#define TDA10048_IN_CONF1_OFFSET_MSK            0x70

#define TDA10048_IN_CONF2_RH_MSK                0x07
#define TDA10048_IN_CONF2_ALPHA_MSK             0x18
#define TDA10048_IN_CONF2_CONS_MSK              0x60

#define TDA10048_IN_CONF3_RL_MSK                0x07

#define TDA10048_CONF_PLL3_N_FACTOR_MSK         0x1F
#define TDA10048_CONF_PLL3_P_FACTOR_MSK         0x20
#define TDA10048_CONF_PLL3_START_MSK            0x40

#define TDA10048_CONFC1_USEPK_MSK               0x80
#define TDA10048_CONFC1_INVQ_MSK                0x20

#define TDA10048_CONFC1_3_POLA_AGC_IF_MSK       0x01
#define TDA10048_CONFC1_3_POLA_AGC_TUN_MSK      0x02

#define TDA10048_CONFC4_DSPWD_MSK               0x20

#define TDA10048_AGC_CONF_USE_AGC_TUN_MSK       0x20
#define TDA10048_AGC_CONF_AUTO_AGC_MSK          0x10
#define TDA10048_AGC_CONF_MSK                   0x0f
#define TDA10048_AGC_GAINS_MSK                  0x3f

#define TDA10048_CONFPOLA_AGCTUN_MSK            0x02
#define TDA10048_CONFPOLA_AGCIF_MSK             0x01

#define TDA10048_CVBER_CTRL_PVBER_MSK           0x0c
#define TDA10048_CLVBER_MSK                     0x02

#define TDA10048_CONFX0_CRYST100PPM_MSK         0x02

#define TDA10048_IC_MODE_MUX_CMD_MSK            0x78

#define TDA10048_CONFTS1_DEGIG_MSK              0x80
#define TDA10048_CONFTS2_SBUS_MSK               0x10
#define TDA10048_CONFTS2_POCLK_MSK              0x01
#define TDA10048_CONFTS2_SLSB_MSK               0X20
#define TDA10048_CONFTS2_PSYNC_LENGTH_MSK       0x40

#define TDA10048_TRISTATE1_ZTS_MSK              0x40
#define TDA10048_TRISTATE1_ZAGCIF_MSK           0x06
#define TDA10048_TRISTATE1_ZAGCTUN_MSK          0x18

#define TDA10048_CONF_POLARITY_GPIO0_MSK        0x01

#define TDA10048_GPIO_SELECT_OUT_SEL_MSK        0x07

#define FE_ALGO_HW        1

#define UInt32x32To64(a, b) ((unsigned long long)((unsigned long)(a)) * (unsigned long long)((unsigned long)(b)))

static int tda10048_write_byteI(struct tda10048_state* state, int reg, int data)
{
    int ret;
    u8 buf[] = { reg, data };
    struct i2c_msg msg = { .addr=0, .flags=0, .buf=buf, .len=2 };

    msg.addr = state->config.demod_address;
    ret = i2c_transfer(state->i2c, &msg, 1);

    if (ret != 1)
    {
        dprintk("%s: error reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__, reg, data, ret);
    }
    else
    {
        dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__, reg, data, ret);
    }

    return ( (ret != 1) ? -1 : 0 );
}


static int tda10048_read_byte(struct tda10048_state* state, 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}};

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

    msg[0].addr = state->config.demod_address;
    msg[1].addr = state->config.demod_address;
    ret = i2c_transfer(state->i2c, msg, 2);

    if (ret != 2)
    {
        dprintk("%s: error reg=0x%x, ret=%i\n", __FUNCTION__, reg, ret);
    }
    else
    {
        dprintk("%s: success reg=0x%x, data=0x%x, ret=%i\n", __FUNCTION__, reg, b1[0], ret);
    }

    return ( (ret != 2) ? -1 : (b1[0]) );
}


static int tda10048_write_mask(struct tda10048_state* state, int reg, int mask, int data)
{
    int val;

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

    /* Read a byte and check */
    val = tda10048_read_byte(state, reg);
    if (val < 0)
        return val;

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

    /* Write it out again */
    return tda10048_write_byteI(state, reg, val);
}


static int tda10048_write_buf(struct tda10048_state*  state, int reg, unsigned char* buf, int len)
{
    int i;
    int result;

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

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

    return result;
}


static int tda10048_enable_tuner_i2c(struct tda10048_state* state)
{
    int result;
    
    dprintk("%s\n", __FUNCTION__);

    result = tda10048_write_mask(state, TDA10048_CONFC4_1, 2, 2);
    msleep(1);
    return result;
}


static int tda10048_disable_tuner_i2c(struct tda10048_state* state)
{
    dprintk("%s\n", __FUNCTION__);

    return tda10048_write_mask(state, TDA10048_CONFC4_1, 2, 0);
}


static int tda10048h_set_bandwidth(struct tda10048_state* state, fe_bandwidth_t bandwidth)
{
    unsigned long   uInvWref;
    unsigned long   uWref;
    u8              regs[6];

    switch (bandwidth) 
    {
        case BANDWIDTH_6_MHZ:
            uInvWref = state->sampling.uInvWref6;
            uWref = state->sampling.uWref6;
            break;

        case BANDWIDTH_7_MHZ:
            uInvWref = state->sampling.uInvWref7;
            uWref = state->sampling.uWref7;
            break;

        case BANDWIDTH_8_MHZ:
            uInvWref = state->sampling.uInvWref8;
            uWref = state->sampling.uWref8;
            break;

        default:
            return -EINVAL;
    }

    regs[0] = uInvWref & 0xff;
    regs[1] = (uInvWref >> 8) & 0xff;
    regs[2] = uWref & 0xff;
    regs[3] = (uWref >>  8) & 0xff;
    regs[4] = (uWref >> 16) & 0xff;
    regs[5] = (uWref >> 24) & 0xff;

    /* Send value to chip */
    return (tda10048_write_buf(state, TDA10048_TIME_INWREF_LSB, regs, sizeof(regs)));
}


static int tda10048_do_upload(struct tda10048_state* state, unsigned char* mem, unsigned int len, u8 dspCodeInReg)
{
    u8 buf[0x200+3];
    struct i2c_msg fw_msg = {.addr = 0,.flags = 0,.buf = buf,.len = 0 };
    int tx_size;
    int pos = 0;

    fw_msg.addr = state->config.demod_address;

    buf[0] = dspCodeInReg;
    while (pos != len) {

        /* Work out how much to send this time */
        tx_size = len - pos;
        if (tx_size > 0x200) {
            tx_size = 0x200;
        }

        /* Send the chunk */
        memcpy(buf + 1, mem + pos, tx_size);
        fw_msg.len = tx_size + 1;
        if (i2c_transfer(state->i2c, &fw_msg, 1) != 1) {
            printk(KERN_ERR "tda10048: Error during firmware upload\n");
            return -EIO;
        }
        pos += tx_size;
        dprintk("%s: fw_pos=0x%x\n", __FUNCTION__, pos);
    }

    return 0;
}


static int tda10048_check_upload_ok(struct tda10048_state* state, u8 dspVersion, u8 dspCrc)
{
    u8 data1, data2;

    /* Check firmware version was OK */
    tda10048_write_byteI(state, TDA10048_DSP_ADR_MSB, 0x67);

    data1 = tda10048_read_byte(state, TDA10048_DSP_REG_MSB);
    data2 = tda10048_read_byte(state, TDA10048_DSP_REG_LSB);

    /* Check firmware CRC was OK */
    if ( (data1 == 0x67 ) && (data2 == dspVersion) )
    {        
        dprintk(KERN_INFO "tda10048: found firmware revision %x -- ok\n", data2);
        return 0;
        
 /*       tda10048_write_byteI(state, TDA10048_DSP_ADR_MSB, 0x6D);
        
        data1 = tda10048_read_byte(state, TDA10048_DSP_REG_MSB);
        data2 = tda10048_read_byte(state, TDA10048_DSP_REG_LSB);

        if ( (data1 == 0x6D ) && (data2 == dspCrc) )
        {
            printk(KERN_INFO "tda10048: found firmware CRC %x -- ok\n", data2);
            return 0;
        } 
        else 
        {
            printk(KERN_INFO "tda10048: found firmware CRC %x -- invalid\n", data2);
            return -EIO;
        }*/
    }
    else
    {
        dprintk(KERN_INFO "tda10048: found firmware revision %x -- invalid\n", data2);
        return -EIO;
    }

    return -EIO;
}


static int tda10048_fwupload(struct dvb_frontend* fe)
{
    struct tda10048_state*  state = fe->demodulator_priv;
    unsigned long           timeout;
    int                     ret;
    const struct firmware   *fw, *fw_info;
    
    /* wake up */
    ret = tda10048_write_mask(state, TDA10048_CONFC4_1, 1, 0);
   
    if (!ret) 
    {
        ret = tda10048_write_mask(state, TDA10048_CONF_TRISTATE1, 1, 1);
    }

    /* start boot */
    if (!ret)
    {
        ret = tda10048_write_mask(state, TDA10048_CONFC4_1, 0x0E, 8);
    }
    
    /* Load firmware info = version number + crc */
    request_firmware(&fw_info, TDA10048_DEFAULT_FIRMWARE_INFO, &state->i2c->dev);
    
    if (!ret) 
    {
        /* don't re-upload unless necessary: firmware version */
        if (tda10048_check_upload_ok(state, fw_info->data[0], fw_info->data[1]) != 0) {
            
            request_firmware(&fw, TDA10048_DEFAULT_FIRMWARE, &state->i2c->dev);
            ret = tda10048_do_upload(state, fw->data, fw->size, TDA10048_DSP_CODE_IN);
            if (!ret) 
            {

                /* wait for DSP ready */
                timeout = jiffies + HZ;
                
                while(!(tda10048_read_byte(state, TDA10048_SYNC_STATUS) & 0x40))
                {
                    if (time_after(jiffies, timeout))
                    {
                        printk(KERN_ERR "tda10048: boot DSP firmware, failed\n");
                        return -EIO;
                    }
                    msleep(1);
                }

                ret = tda10048_check_upload_ok(state, fw_info->data[0], fw_info->data[1]);
                if (!ret)
                {
                    dprintk("tda10048: firmware upload, ok\n");
                }
            }
            else 
            {
                printk(KERN_ERR "tda10048: firmware upload, failed\n");
            }
        }
        else
        {
            dprintk("tda10048: firmware upload, already loaded, ok\n");
        }
    }

    if (ret) 
    {
        printk(KERN_ERR "tda10048: firmware upload, failed\n");
    }

    return ret;
}


static int tda10048_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 tda10048_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;
}

static int tda10048_write_byte(struct dvb_frontend* fe, u8 *buf, int len)
{
    struct tda10048_state* state = fe->demodulator_priv;
    
    if (len != 2)
        return -EINVAL;

      return tda10048_write_byteI(state, buf[0], buf[1]);
}


long long tda10048_long_long_div_long(long long num, long div)
{
    long long   remain;
    long long   result;
    long long   sub_result;
    long long   inter_div;
    int         sign = 0;

    remain = num;
    result = 0;

    /* Extract sign */
    if (num < 0) {
        num = -num;
        if (div < 0) {
            sign = -sign;
            sign = 1;
        }
        else {
            sign = -1;
        }
    }
    else {
        if (div < 0) {
            sign = -sign;
            sign = -1;
        }
        else {
            sign = 1;
        }
    }

    if (0 == div) {
        return 0x7fffffffffffffffLL;
    }
    
    while (remain > div) {
        sub_result = 0x01;
        inter_div = div;

        while (remain > inter_div) {

            inter_div = inter_div << 1;
            sub_result = sub_result << 1;
        }

        result = result + (sub_result >> 1);
        remain = remain - (inter_div >> 1);
    }

    /* Set sign */
    if (sign == -1) {
        result = -result;
    }

    return (result);
}


static int tda10048_init(struct dvb_frontend* fe)
{
    struct tda10048_state*  state = fe->demodulator_priv;
    int                     err = 0;
    unsigned long           SamplingClk = 0;
    u8                      buff[4];
    u8                      uByte;
    unsigned long           start, stop;
    
    if (state->initialised)
    {
        return 0;
    }

    start = jiffies;
    /* Wake up the chip */
    if (!err) 
    {
        err = tda10048_write_mask(state, TDA10048_CONFC4_1, 1, 0);
    }

    /* With card running on quartz 16Mhz reduce to 4Mhz the I2C clock */
    if (state->config.xtal_freq == TDA10048_XTAL_16M )
    {
        err = tda10048_write_mask(state, TDA10048_CONFC4_2, 0x18, 0x10);
    }
    else if (state->config.xtal_freq == TDA10048_XTAL_25M )
    {
        err = tda10048_write_mask(state, TDA10048_CONFC4_2, 0x18, 0x11); // switch i2c to 3,125Mhz
    }

    /* Init the tuner */
    if (!err) 
    {
        if (state->config.pll_init)
        {
            tda10048_enable_tuner_i2c(state);
            state->config.pll_init(fe);
            tda10048_disable_tuner_i2c(state);
        }
    }

    /* Set PLL parameters */
    /* AUTO_PLL=1, PLL_CLK_EN=1, PLL_BYP=1, PLL_PWDWN=1 */
    if (!err)
    {
        err = tda10048_write_byteI(state, TDA10048_CONF_PLL1, 0x0f);
    }

    /* PLL factors M, N, P */
    if (!err)
    {
       err = tda10048_write_byteI(state, TDA10048_CONF_PLL2, state->config.PLL_M_Factor);
    }

    if (!err)
    {
        err = tda10048_write_mask(state, TDA10048_CONF_PLL3,
                TDA10048_CONF_PLL3_START_MSK | TDA10048_CONF_PLL3_P_FACTOR_MSK 
                | TDA10048_CONF_PLL3_N_FACTOR_MSK,
                TDA10048_CONF_PLL3_START_MSK | (state->config.PLL_P_Factor << 5) | state->config.PLL_N_Factor);
    }

    /* Calculate the sampling freqency */
    if (!err)
    {
        if (state->config.xtal_freq == TDA10048_XTAL_4M || state->config.xtal_freq == TDA10048_XTAL_16M || state->config.xtal_freq == TDA10048_XTAL_25M)
            SamplingClk = state->config.xtal_freq;
        else
        {
            printk(KERN_ERR "tda10048: xtall parameters, error\n");
            err = -1;
        }
    }
    
    if (!err) 
    {
        long long   if_freq_ll;
        u8          buff[2];
        bool        uPhy2negValue;

        SamplingClk = SamplingClk * (state->config.PLL_M_Factor + 45);
        SamplingClk = SamplingClk / (state->config.PLL_N_Factor + 1);
        SamplingClk = SamplingClk / (state->config.PLL_P_Factor + 4);

        /* Frequency correction */
        if_freq_ll = state->config.if_freq;

        uPhy2negValue = false;
        
        if (if_freq_ll > (SamplingClk)/2 )
        {
            if_freq_ll = if_freq_ll - SamplingClk;
            if (if_freq_ll < 0)
            {
                if_freq_ll = -if_freq_ll;
                uPhy2negValue = true;
            }
            else
                uPhy2negValue = false;
        }
        
        if_freq_ll = if_freq_ll << 15;
        if_freq_ll = tda10048_long_long_div_long(if_freq_ll, SamplingClk); 

        if (uPhy2negValue == true)
            if_freq_ll = -if_freq_ll;
    
        buff[0] = if_freq_ll & 0xff;
        buff[1] = (if_freq_ll >> 8) & 0xff;

        if (!err) 
            err = tda10048_write_buf(state, TDA10048_FREQ_PHY2_LSB, buff, 2);
    }

    /* Calculate WREF & INV_WREF samplig correction */
    if (!err)
    {
        unsigned long long  ref;

        /* 8 MHz */
        ref = (unsigned long long)0x80000000 * 8000000;
        ref = ref + (SamplingClk/2);        /* Rounding */
        ref = tda10048_long_long_div_long(ref, SamplingClk);
        ref = tda10048_long_long_div_long(ref, 7);
        state->sampling.uWref8 = ref;

        ref = SamplingClk * (unsigned long long)224;
        ref = ref + 4000000;                /* Rounding */
        state->sampling.uInvWref8 = tda10048_long_long_div_long(ref, 8000000);
        
        /* 7 MHz */
        ref = (unsigned long long)0x80000000 * 7000000;
        ref = ref + (SamplingClk/2);        /* Rounding */
        ref = tda10048_long_long_div_long(ref, SamplingClk);
        ref = tda10048_long_long_div_long(ref, 7);
        state->sampling.uWref7 = ref;

        ref = SamplingClk * (unsigned long long)224;
        ref = ref + 3500000;                /* Rounding */
        state->sampling.uInvWref7 = tda10048_long_long_div_long(ref, 7000000);

        /* 6 MHz */
        ref = (unsigned long long)0x80000000 * 6000000;
        ref = ref + (SamplingClk/2);        /* Rounding */
        ref = tda10048_long_long_div_long(ref, SamplingClk);
        ref = tda10048_long_long_div_long(ref, 7);
        state->sampling.uWref6 = ref;

        ref = SamplingClk * (unsigned long long)224;
        ref = ref + 3000000;                /* Rounding */
        state->sampling.uInvWref6 = tda10048_long_long_div_long(ref, 6000000);
    }

    /* Disable DSP watchdog timer */
    if (!err)
        err = tda10048_write_mask(state, TDA10048_CONFC4_1, TDA10048_CONFC4_DSPWD_MSK, 0);

    /* Use pulse killer algorithm */
    if (!err)
        err = tda10048_write_mask(state, TDA10048_CONFC1_1, TDA10048_CONFC1_USEPK_MSK,
                                  TDA10048_CONFC1_USEPK_MSK);

    /* Set default priority to HP */
    if (!err)
        err = tda10048_write_mask(state, TDA10048_AUTO, TDA10048_AUTO_HPLP_MSK, 0);

    /* AGC setup, TDA10048_AGC_TUN_UNUSED <=> RF AGC not managed */
    /* by TDA10048, AUTO_AGC=1, AGC_CONF=0x06                    */
    if (!err)
    {
        uByte = TDA10048_AGC_CONF_AUTO_AGC_MSK;

        if (state->config.AGC_TunUsed) 
        {
            /* Use the AGC TUN loop */
            uByte |= TDA10048_AGC_CONF_USE_AGC_TUN_MSK;
        }

        err = tda10048_write_mask(state, TDA10048_AGC_CONF, 
                                    TDA10048_AGC_CONF_USE_AGC_TUN_MSK
                                  | TDA10048_AGC_CONF_AUTO_AGC_MSK
                                  | TDA10048_AGC_CONF_MSK,
                                  (TDA10048_AGC_CONF_AUTO_AGC_MSK | 0x06) );

    }

    /* AGC Polarity */
    if (!err) 
    {
        uByte = 0;
        if (state->config.AGC_PolaTun)
        {
            /* AGC TUN level inverted */
            uByte |= TDA10048_CONFPOLA_AGCTUN_MSK;
        }
        if (state->config.AGC_PolaIf)
        {
            /* AGC IF level inverted */
            uByte |= TDA10048_CONFPOLA_AGCIF_MSK;
        }

        err = tda10048_write_mask(state,TDA10048_CONFC1_3,
            TDA10048_CONFC1_3_POLA_AGC_TUN_MSK | TDA10048_CONFC1_3_POLA_AGC_IF_MSK, (u8)uByte);
    }

    /* AGC levels */
    if (!err)
    {
        buff[0] = state->config.AGC_TunMin;
        buff[1] = state->config.AGC_TunMax;
        buff[2] = state->config.AGC_IfMin;
        buff[3] = state->config.AGC_IfMax;

        err = tda10048_write_buf(state, TDA10048_AGCTUN_MIN, buff, 4);
    }
    
    /* AGC threshold */
    /* -> XR: 9.05.08: TBC => NOT DONE IN BSL
    if (state->config.if_freq == TDA10048_IF_4M)
    {
        / Note: the if_freq is used to identify the silicon tuner 
           Only for use with Silicon Tuner *
        buff[0] = 0x70;
        buff[1] = 0x00;
    }
    else
    {
        * Default value *
        buff[0] = 0xCC;
        buff[1] = 0x00;
    }

    if (!err)
        err = tda10048_write_buf(state, TDA10048_AGC_THR, buff, 2);
    */

    /* Digital AGC_Threshold */
    if (!err)
    {
        buff[0] = 0x50;
        err = tda10048_write_buf(state, TDA10048_DIG_AGC_THR, buff, 1);
    }

    /* 10^7 VBER measurement bits */
    if (!err)
        err = tda10048_write_mask(state, TDA10048_CVBER_CTRL, TDA10048_CVBER_CTRL_PVBER_MSK,
               TDA10048_PVBER_MSK << 2);
    
    /* IF gain 4, TUN gain 4 */
    if (!err)
        err = tda10048_write_mask(state, TDA10048_AGC_GAINS, TDA10048_AGC_GAINS_MSK,
               TDA10048_AGC_GAINS_DEF);

    /* Crystal: 50ppm -> 0x00; 100ppm -> 0x02 */
    if (!err)
        err = tda10048_write_mask(state, TDA10048_CONFX0, 
                TDA10048_CONFX0_CRYST100PPM_MSK, 0x02);

    /***     Output TS mode :     ***/
    /*------------------------------*/
    /* Mode A or B - Division factor for parallel mode B : non jitter on Oclk */
    /* bOUT_ModeAB = 0 : Mode A */
    /* bOUT_ModeAB = 1 : Mode B */
    if (!err)
    {
        uByte = state->config.OUT_ParaDiv;
        if (state->config.OUT_ModeAB)
            uByte |= TDA10048_CONFTS1_DEGIG_MSK;

        err = tda10048_write_byteI(state, TDA10048_CONFTS1, uByte);
    }

    if (!err)
    {
        uByte = 0;
        
        /* Poclk */
        if (state->config.OUT_Poclk)
        {
            uByte |= TDA10048_CONFTS2_POCLK_MSK;
        }
        else 
        {
            uByte &= ~TDA10048_CONFTS2_POCLK_MSK;
        }

        /* Serial / Parallel */ 
        if (state->config.OUT_ParaSer)
        {
            /* serial */
            uByte |= TDA10048_CONFTS2_SBUS_MSK;
            err = tda10048_write_mask(state, TDA10048_IC_MODE, TDA10048_IC_MODE_MUX_CMD_MSK, 0x20);

            /* P_sync signal actif on whole the bits of the first byte 0x47 */
            uByte &= ~TDA10048_CONFTS2_PSYNC_LENGTH_MSK;
        }
        else
        {
            /* parallel */
            uByte &= ~TDA10048_CONFTS2_SBUS_MSK;
            err = tda10048_write_mask(state, TDA10048_IC_MODE, TDA10048_IC_MODE_MUX_CMD_MSK, 0x00);
        }
    }

    if (!err)
    {
        /* MSB / LSB : only used for MPEG-TS serial mode */
        if(state->config.OUT_ParaSer)
        {
            if (state->config.OUT_MsbLsb)
            {
                /* LSB first */
                uByte |= TDA10048_CONFTS2_SLSB_MSK;
            }
            else
            {
                /* MSB first */
                uByte &= ~TDA10048_CONFTS2_SLSB_MSK;
            }
        }

        err = tda10048_write_mask(state, 
            TDA10048_CONFTS2,
            TDA10048_CONFTS2_POCLK_MSK | TDA10048_CONFTS2_SBUS_MSK | TDA10048_CONFTS2_SLSB_MSK,
            (u8)uByte);
    }


    /* TRISTATE setup */
    if (!err)
    {
        if (state->config.AGC_TunUsed)
            err = tda10048_write_mask(state, TDA10048_CONF_TRISTATE1, TDA10048_TRISTATE1_ZTS_MSK |
                TDA10048_TRISTATE1_ZAGCIF_MSK | TDA10048_TRISTATE1_ZAGCTUN_MSK, 0x10);
        else
            err = tda10048_write_mask(state, TDA10048_CONF_TRISTATE1, TDA10048_TRISTATE1_ZTS_MSK |
                TDA10048_TRISTATE1_ZAGCIF_MSK | TDA10048_TRISTATE1_ZAGCTUN_MSK, 0x00);
    }

    /* set GPIO0 as 2state mode, all others as push-pull mode (note there are note:
       used in TS parallel mod */
    if (!err)
        err = tda10048_write_byteI(state, TDA10048_CONF_TRISTATE2, 0x02);

    /* Set 0 in PWM GPIO control register -> 0V */
    if (!err)
        err = tda10048_write_byteI(state, TDA10048_GPIO_SP_DS0, 0);

    /* Set GPIO0 not reversed */
    if (!err)
        err = tda10048_write_mask(state, TDA10048_CONF_POLARITY,
                TDA10048_CONF_POLARITY_GPIO0_MSK , 0);

    /* Set GPIO0 mode */
    if (!err)
    {
        if (state->config.if_freq == TDA10048_IF_4M)
        {
            /* Note: the if_freq is used to identify the silicon tuner 
               Only for use with Silicon Tuner -> Set GPIO0 mode = SigmaDelt (PWM)  */
            err = tda10048_write_mask(state, TDA10048_GPIO_SELECT,
                    TDA10048_GPIO_SELECT_OUT_SEL_MSK, 3);
        }
        else
        {
            /* default : GPIO used FEL indicator */
            err = tda10048_write_mask(state, TDA10048_GPIO_SELECT,
                    TDA10048_GPIO_SELECT_OUT_SEL_MSK, 0);
        }
    }

    /*** ADC reboot ***/
    /* Power down ADC */
    if (!err) 
    {
        buff[0] = 0x60;
        buff[1] = 0x10;
        err = tda10048_write_buf(state, TDA10048_CONF_ADC, buff, 2);
    }
    /* Power up ADC */
    if (!err)
    {
        buff[0] = 0x60;
        buff[1] = 0x00;
        err = tda10048_write_buf(state, TDA10048_CONF_ADC, buff, 2);
    }

    state->initialised = 1;

    if (tda10048_fwupload(fe))
    {
        printk(KERN_ERR  "%s: firmware upload failed\n", __FUNCTION__);       
        return -EIO;
    }

    stop = jiffies;
    printk(KERN_INFO "%s: init done in %ld ms\n", __FUNCTION__, (stop-start)*1000/HZ);
    return 0;
}

static int tda10048_get_frontend_algo(struct dvb_frontend *fe)
{
    struct tda10048_state*  state = fe->demodulator_priv;

    if (state->config.tuning_algo == 1)
    {
        return FE_ALGO_HW;
    }
    return 0;	    
}

static int tda10048_set_fe(struct dvb_frontend* fe, struct dvb_frontend_parameters* fe_params)
{
    struct tda10048_state*  state = fe->demodulator_priv;
    int                     reg = 0;
    u8                      autoX = 0;
    int                     err = 0;

    dprintk("%s\n", __FUNCTION__);

    if (!state->initialised) 
    {
        /* Error tda10048 not initialized */
        err = -1;
    }
    else 
    {

        /* Set TPS usage */
        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) )
        {
            err = tda10048_write_mask(state, TDA10048_AUTO, TDA10048_AUTO_AUTOTPS,
                    TDA10048_AUTO_AUTOTPS);     /* Enable auto TPS */
        } 
        else 
        {
            /* If TPS not used set all manuel param */
            err = tda10048_write_mask(state, TDA10048_AUTO, TDA10048_AUTO_AUTOTPS, 0);      /* Disable auto TPS */

            /* Set hierarchy */
            switch (fe_params->u.ofdm.hierarchy_information) 
            {
                case HIERARCHY_NONE:
                    reg = 0;
                    break;
                case HIERARCHY_1:
                    reg = 1 << 3;
                    break;
                case HIERARCHY_2:
                    reg = 2 << 3;
                    break;
                case HIERARCHY_4:
                    reg = 3 << 3;
                    break;
                default:
                    err = -EINVAL;
                    break;
            }
            if (!err)
                err = tda10048_write_mask(state, TDA10048_IN_CONF2,
                        TDA10048_IN_CONF2_ALPHA_MSK, reg);


            /* Set LP FEC */
            reg = tda10048_encode_fec(fe_params->u.ofdm.code_rate_LP);
            if (reg >= 0) 
            {
                if (!err)
                    err = tda10048_write_mask(state, TDA10048_IN_CONF3,
                            TDA10048_IN_CONF3_RL_MSK, reg);

            }
            else
                err = -1;

            /* Set HP FEC */
            reg = tda10048_encode_fec(fe_params->u.ofdm.code_rate_HP);
            if (reg >= 0) 
            {
                if (!err)
                    err = tda10048_write_mask(state, TDA10048_IN_CONF2,
                            TDA10048_IN_CONF2_RH_MSK, reg);
            }
            else
                err = -1;
            
            /* Set constellation */
            switch (fe_params->u.ofdm.constellation)
            {
                case QPSK:
                    reg = 0;
                    break;
                case QAM_16:
                    reg = 1<<5;
                    break;
                case QAM_64:
                    reg = 2<<5;
                    break;
                default:
                    err = -EINVAL;
                    break;
            }
            if (!err)
                err = tda10048_write_mask(state, TDA10048_IN_CONF2, TDA10048_IN_CONF2_CONS_MSK, reg);
        }

        /* Set transmission mode (FFT) */
        switch (fe_params->u.ofdm.transmission_mode)
        {
            case TRANSMISSION_MODE_2K:
                autoX = 0;
                reg = 0;
                break;
            case TRANSMISSION_MODE_8K:
                autoX = 0;
                reg = 1;
                break;
            case TRANSMISSION_MODE_4K:
                autoX = 0;
                reg = 2;
                break;
            case TRANSMISSION_MODE_AUTO:
                autoX = TDA10048_AUTO_AUTO2K8K_MSK;
                break;
            default:
                err = -EINVAL;
                break;
        }
        if (!err)
            err = tda10048_write_mask(state, TDA10048_AUTO, 
                  TDA10048_AUTO_AUTO2K8K_MSK | TDA10048_AUTO_AUTO4K_MSK, autoX);

        if ((!err) && (!autoX))
            err = tda10048_write_mask(state, TDA10048_IN_CONF1, TDA10048_IN_CONF1_CNF2K4K8K_MSK, reg);

        /* Set guard interval */
        switch (fe_params->u.ofdm.guard_interval)
        {
            case GUARD_INTERVAL_1_32:
                autoX = 0;
                reg = 0;
                break;
            case GUARD_INTERVAL_1_16:
                autoX = 0;
                reg = 1 << 2;
                break;
            case GUARD_INTERVAL_1_8:
                autoX = 0;
                reg = 2 << 2;
                break;
            case GUARD_INTERVAL_1_4:
                autoX = 0;
                reg = 3 << 2;
                break;
            case GUARD_INTERVAL_AUTO:
                autoX = TDA10048_AUTO_AUTOGI_MSK;
                break;
            default:
                err = -EINVAL;
                break;
        }
        if (!err)
            err = tda10048_write_mask(state, TDA10048_AUTO, TDA10048_AUTO_AUTOGI_MSK, autoX);

        if ((!err) && (!autoX))
            err = tda10048_write_mask(state, TDA10048_IN_CONF1, TDA10048_IN_CONF1_DELTA_MSK, reg);

        /* Set signal inversion */
        switch (fe_params->inversion)
        {
            case INVERSION_OFF:
                reg = (state->config.spectral_inv) ? TDA10048_CONFC1_INVQ_MSK : 0;
                break;
            case INVERSION_ON:
                reg = (state->config.spectral_inv) ? 0 : TDA10048_CONFC1_INVQ_MSK;
                break;

            case INVERSION_AUTO:
                /* Auto inversion is not supported by the hardware */
            default:
                err = -EINVAL;
                break;
        }
        
        if (!err)
            err = tda10048_write_mask(state, TDA10048_CONFC1_1, TDA10048_CONFC1_INVQ_MSK, reg);

        /* Set bandwidth */
        if (!err)
            err = tda10048h_set_bandwidth(state, fe_params->u.ofdm.bandwidth);

        /* Setup auto frequency offset */
        if (!err)
            err = tda10048_write_mask(state, TDA10048_AUTO, TDA10048_AUTO_AUTOOFFSET,
                    TDA10048_AUTO_AUTOOFFSET);
        if (!err)
            err = tda10048_write_mask(state, TDA10048_IN_CONF1, TDA10048_IN_CONF1_OFFSET_MSK,
                    TDA10048_IN_CONF1_OFFSET_MSK);

        /* Set tuner frequency */
        if (!err)
            err = tda10048_enable_tuner_i2c(state);

        if ((!err) && (fe->ops.tuner_ops.set_params))
        {
            err = fe->ops.tuner_ops.set_params(fe, fe_params);
        }
        else
            printk(KERN_ERR "tda10048: tuner access, failed\n");

        /* Wait for AGC convergence */
        msleep(5); 

        if (!err)
            err = tda10048_disable_tuner_i2c(state);

        /* Start the lock */
        if (!err)
        {
            err = tda10048_write_mask(state, TDA10048_AUTO, TDA10048_AUTO_START_LOCK, TDA10048_AUTO_START_LOCK);
        }
    }

    if (err)
        printk(KERN_ERR "%s: failed\n", __FUNCTION__);

    return (err);
}

static int tda10048_get_fe(struct dvb_frontend* fe, struct dvb_frontend_parameters* fe_params)
{
    struct tda10048_state* state = fe->demodulator_priv;
    unsigned long   uInvWref;

    dprintk("%s\n", __FUNCTION__);

    /* Inversion status */
    fe_params->inversion = INVERSION_OFF;
    if (tda10048_read_byte(state, TDA10048_CONFC1_1) & 0x20)
    {
        fe_params->inversion = INVERSION_ON;
    }

    if (state->config.spectral_inv)
    {
        fe_params->inversion = fe_params->inversion ? INVERSION_OFF : INVERSION_ON;
    }

    /* Bandwidth */
    uInvWref = tda10048_read_byte(state, TDA10048_TIME_INWREF_LSB);
    
    if ( uInvWref == (state->sampling.uInvWref6 & 0xff ) )
        fe_params->u.ofdm.bandwidth = BANDWIDTH_6_MHZ;

    if ( uInvWref == (state->sampling.uInvWref7 & 0xff ) )
        fe_params->u.ofdm.bandwidth = BANDWIDTH_7_MHZ;
    
    if ( uInvWref == (state->sampling.uInvWref8 & 0xff ) )
        fe_params->u.ofdm.bandwidth = BANDWIDTH_8_MHZ;

    /* FEC */
    fe_params->u.ofdm.code_rate_HP =
        tda10048_decode_fec(tda10048_read_byte(state, TDA10048_OUT_CONF2) & 7);
    fe_params->u.ofdm.code_rate_LP =
        tda10048_decode_fec(tda10048_read_byte(state, TDA10048_OUT_CONF3) & 7);

    /* Constellation */
    switch ((tda10048_read_byte(state, TDA10048_OUT_CONF2) & 0x60)>>5)
    {
        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 */
    switch (tda10048_read_byte(state, TDA10048_OUT_CONF1) & 3) 
    {
        case 0:
            fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_2K;
            break;
        case 1:
            fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K;
            break;
        case 2:
            fe_params->u.ofdm.transmission_mode = TRANSMISSION_MODE_4K;
            break;
    }

    /* Guard interval */
    switch ((tda10048_read_byte(state, TDA10048_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 ((tda10048_read_byte(state, TDA10048_OUT_CONF2) & 0x18) >> 3)
    {
        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;
    }

    return 0;
}

static int tda10048_read_offset(struct dvb_frontend* fe, int* fe_offset)
{
    struct tda10048_state* state = fe->demodulator_priv;
    int out_conf_1;

    out_conf_1 = tda10048_read_byte(state, TDA10048_OUT_CONF1);

    if (out_conf_1 < 0)
    {
        return -1;
    }
    
    *fe_offset = (out_conf_1& 0x70) >> 4;

    return 0;
}

static int tda10048_read_status(struct dvb_frontend* fe, fe_status_t* fe_status)
{
    struct tda10048_state* state = fe->demodulator_priv;
    int status;
    int counter;
    int tmp;

    dprintk("%s\n", __FUNCTION__);

    /* Read status */
    status = tda10048_read_byte(state, TDA10048_SYNC_STATUS);
    if (status == -1)
        return -EIO;

    *fe_status = 0;

    /* Read scanCPT */
    counter = tda10048_read_byte(state, TDA10048_SCAN_CPT);

    if ((counter > 8) && (status & 0x1F) != 0x1F)
    {
        /* Demodulator cannot be locked */
        *fe_status |= FE_TIMEDOUT;
        return 0;
    }

    /* Decode */
    if (status & 0x04) {
        *fe_status |= FE_HAS_SIGNAL;
    }
    if (status & 0x02) {
        *fe_status |= FE_HAS_CARRIER;
    }
    if (status & 0x01) {
        *fe_status |= FE_HAS_SYNC;
    }
    if (status & 0x08) {
        *fe_status |= FE_HAS_VITERBI;
    }
    if (status & 0x10) {
        *fe_status |= FE_HAS_LOCK;
    }

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

static int tda10048_tune(struct dvb_frontend* fe, struct dvb_frontend_parameters* params,
                        unsigned int mode_flags, unsigned int *delay, fe_status_t *status)
{
    struct tda10048_state* state = fe->demodulator_priv;
    fe_status_t s;
    int err = 0;
    int fe_offset;

    if (params == NULL)
    {
        /* dvb-core is not in FESTATE_RETUNE state */
        /* may be we can check lock state here */
        *status = 0;
        return 0;
    }

    if (tda10048_set_fe(fe, params) < 0)
        return -EIO;

    /* wait for lock or timeout */
    do
    {
        err = tda10048_read_status(fe, &s);
        msleep(6);
    }while( !( (s&FE_TIMEDOUT) || (s&FE_HAS_LOCK) || (err<0) ) );

    if (err < 0)
        return err;

    if (s&FE_HAS_LOCK)
    {
        /* locked on central frequency. Exit now. */
        *status = s;
        return 0;
    }

    /* else, check for a possible offset */
    if (tda10048_read_offset(fe, &fe_offset) < 0)
        return -EIO;
    
    if (fe_offset == TDA10048_NO_OFFSET)
    {
        /* No offset detected. Exit now. */
        *status = FE_TIMEDOUT;
        return 0;
    }

    switch(fe_offset)
    {
        case TDA10048_OFFSET_PLUS_166:
            params->frequency += 166000;
            break;
        case TDA10048_OFFSET_MINUS_166:
            params->frequency -= 166000;
            break;
        case TDA10048_OFFSET_PLUS_333:
            params->frequency += 333000;
            break;
        case TDA10048_OFFSET_MINUS_333:
            params->frequency -= 333000;
            break;
        case TDA10048_OFFSET_PLUS_500:
            params->frequency += 500000;
            break;
        case TDA10048_OFFSET_MINUS_500:
            params->frequency -= 500000;
            break;
        default:
            return -EIO;
            break;
    }

    /* Set tuner frequency */
    if (tda10048_enable_tuner_i2c(state) < 0)
        return -EIO;

    if (fe->ops.tuner_ops.set_params)
    {
        err = fe->ops.tuner_ops.set_params(fe, params);
    }

    if (err < 0)
        return err;

    /* Wait for AGC convergence */
    msleep(5); 

    if (tda10048_disable_tuner_i2c(state) < 0)
        return -EIO;

    /* start lock */
    if (tda10048_write_mask(state, TDA10048_AUTO, TDA10048_AUTO_START_LOCK, TDA10048_AUTO_START_LOCK) < 0)
        return -EIO;

    /* wait for lock or timeout */
    do
    {
        err = tda10048_read_status(fe, &s);
        msleep(6);
    }while( !( (s&FE_TIMEDOUT) || (s&FE_HAS_LOCK) || (err<0) ) );

    if (err < 0)
        return err;

    if (s&FE_HAS_LOCK)
    {
        /* locked on frequency + offset. Exit now. */
        *status = s;
        return 0;
    }
    else
    {
        /* has detected an offset but not locked (it can happens on analog carrier) */
        *status = FE_TIMEDOUT;
        return 0;
    }
}

static int tda10048_read_signal_strength(struct dvb_frontend* fe, u16* signal)
{
    struct tda10048_state* state = fe->demodulator_priv;
    int tmp, tmp2;

    dprintk("%s\n", __FUNCTION__);

    /* Read Agc if level */
    tmp = tda10048_read_byte(state, TDA10048_AGC_IFLEVEL);
    if (tmp < 0)
        return -EIO;

    /* Read Digital Agc */
    tmp2 = tda10048_read_byte(state, TDA10048_DIG_AGC_LEVEL);
    if (tmp2 < 0)
        return -EIO;        

    *signal = (tmp2 << 8) | tmp;
    dprintk("%s: signal=0x%x\n", __FUNCTION__, *signal);

    return 0;
}

static int tda10048_read_snr(struct dvb_frontend* fe, u16* snr)
{
    struct tda10048_state* state = fe->demodulator_priv;
    int tmp;

    dprintk("%s\n", __FUNCTION__);

    /* Read it */
    tmp = tda10048_read_byte(state, TDA10048_NP_OUT);
    if (tmp < 0)
        return -EIO;
    if (tmp) {
        tmp = 255 - tmp;
    }

    *snr = ((tmp << 8) | tmp);
    dprintk("%s: snr=0x%x\n", __FUNCTION__, *snr);

    return 0;
}

static int tda10048_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks)
{
    struct tda10048_state* state = fe->demodulator_priv;
    int tmp;
    int tmp2;
    int counter;

    dprintk("%s\n", __FUNCTION__);

    /* Read the UCBLOCKS and reset */
    counter = 0;
    tmp = tda10048_read_byte(state, TDA10048_UNCOR_CPTMSB)<<8 |
          tda10048_read_byte(state, TDA10048_UNCOR_CPTLSB);
    if (tmp < 0)
        return -EIO;

    tmp &= 0xffff;

    while (counter++ < 5)
    {
        tda10048_write_mask(state, TDA10048_UNCOR_CTRL, 0x80, 0x80);

        tmp2 = tda10048_read_byte(state, TDA10048_UNCOR_CPTMSB)<<8 |
               tda10048_read_byte(state, TDA10048_UNCOR_CPTLSB);
        if (tmp2 < 0)
            return -EIO;
        tmp2 &= 0xffff;
        if ((tmp2 < tmp) || (tmp2 == 0))
            break;
    }

    if (tmp != 0xffff) 
        *ucblocks = tmp;
    else
        *ucblocks = 0xffffffff;

    dprintk("%s: ucblocks=0x%x\n", __FUNCTION__, *ucblocks);

    return 0;
}

static int tda10048_read_ber(struct dvb_frontend* fe, u32* vber)
{
    struct tda10048_state*  state = fe->demodulator_priv;
    int                     tmp;

    dprintk("%s\n", __FUNCTION__);

    if(tda10048_read_byte(state, TDA10048_SOFT_ITC3) & 2) {
        tmp = tda10048_read_byte(state, TDA10048_VBER_LSB);
        if (tmp < 0) return -EIO;
        *vber = tmp;
        tmp = tda10048_read_byte(state, TDA10048_VBER_MID);
        if (tmp < 0) return -EIO;
        *vber |= (tmp << 8);
        tmp = tda10048_read_byte(state, TDA10048_VBER_MSB);
        if (tmp < 0) return -EIO;
        *vber |= (tmp << 16);
    }
    else{
        *vber = 0xffffffff; 
    }
    dprintk("%s: vber=%d\n", __FUNCTION__, *vber);

    return 0;
}

static int tda10048_sleep(struct dvb_frontend* fe)
{
    struct tda10048_state* state = fe->demodulator_priv;

    tda10048_write_mask(state, TDA10048_CONFC4_1, 1, 1);
    state->initialised = 0;
    return 0;
}

static int tda10048_i2c_gate_ctrl(struct dvb_frontend* fe, int enable)
{
    struct tda10048_state* state = fe->demodulator_priv;

    if (enable) {
        return tda10048_enable_tuner_i2c(state);
    } else {
        return tda10048_disable_tuner_i2c(state);
    }
}

static int tda10048_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*3;
    return 0;
}

static void tda10048_release(struct dvb_frontend* fe)
{
    struct tda10048_state* state = (struct tda10048_state*) fe->demodulator_priv;
    kfree(state);
    fe->demodulator_priv = NULL;
    
    return;
}

static struct dvb_frontend_ops tda10048_ops = {

    .info  = {
        .name = "NXP TDA10048C2 DVB-T/H",
        .type = FE_OFDM,
        .frequency_min = 174000000,
        .frequency_max = 862000000,
        .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 = tda10048_release,

    .init = tda10048_init,
    .sleep = tda10048_sleep,
    .write = tda10048_write_byte,
    .i2c_gate_ctrl = tda10048_i2c_gate_ctrl,

    .tune = tda10048_tune,
    .get_frontend_algo = tda10048_get_frontend_algo,

    .set_frontend = tda10048_set_fe,
    .get_frontend = tda10048_get_fe,
    .get_tune_settings = tda10048_get_tune_settings,

    .read_status = tda10048_read_status,
    .read_ber = tda10048_read_ber,
    .read_signal_strength = tda10048_read_signal_strength,
    .read_snr = tda10048_read_snr,
    .read_ucblocks = tda10048_read_ucblocks,
};

struct dvb_frontend* tda10048_attach(const struct tda10048_config* config, struct i2c_adapter* i2c)
{
    struct tda10048_state*  state = NULL;
    struct dvb_frontend*    returned_fe;

    /* Allocate memory for the internal state */
    state = (struct tda10048_state*) kmalloc(sizeof(struct tda10048_state), GFP_KERNEL);

    if (state != NULL) {
        /* Setup the state */
        memcpy((void*)&state->config, config, sizeof(struct tda10048_config));
        state->i2c = i2c;
        memcpy(&state->ops, &tda10048_ops, sizeof(struct dvb_frontend_ops));
        state->initialised = 0;
        

        /* Check if the demod is there */
        if (tda10048_read_byte(state, TDA10048_CHIPID) == 0x48) {
            /* Create dvb_frontend */
            memcpy(&state->frontend.ops, &state->ops, sizeof(struct dvb_frontend_ops));
            state->frontend.demodulator_priv = state;

            returned_fe = &state->frontend;

            /* Init + Load Firmware */
            if (tda10048_init_on_startup) {
            	tda10048_init(returned_fe);
            }
        }
        else {
            kfree(state);
            returned_fe = NULL;
        }
    }
    else {
        returned_fe = NULL;
    }

    return (returned_fe);
}

MODULE_DESCRIPTION("Philips TDA10048C2 DVB-T/H Demodulator");
MODULE_AUTHOR("Yacine FALEK & Baptiste GUILLOT & Cedric BOUDY");
MODULE_LICENSE("GPL");

EXPORT_SYMBOL(tda10048_attach);

