/*
    
File name   : tua6039f.c

Description : Driver for tua6039f Tuner

COPYRIGHT (C) NXP Semiconductors

Date          Modification                              Name
----          ------------                              ----
06 Jun 2009   Created                                   Daniel Patrick
06 Jun 2009   Port to Linux DVB API			Bruno Farias / Daniel Patrick

*/

/******************************** Kernel Includes *******************************/

#include "tua6039f.h"
#include "dvb_frontend.h"

/************************************* Macros ***********************************/

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


/**
 * @brief utility function to debug
 */
#define dprintk(args...) \
    do { \
        if (debug) printk(KERN_WARNING "tda665xtt: " args); \
    } while (0)
    
/**
 * @brief utility function to protect the schannel scan operation
 */
#define PROTECT(parm)      \
  if (scan_channel < 0) {  \
    parm;		   \
  } else {		   \
    retval = -EACCES;	   \
  }                        \

/**
 * @brief utility function to check if the function is executed correctly
 */
#define EVALUATE(parm,err) retval = ((parm) < 0)? err : 0

/**
 * @brief utility function to check if the copy from user is executed correctly
 */
#define EVALUATE_COPY_FROM(oper,copy,err)	\
  if(copy) {                                    \
    retval = -EAGAIN;                           \
  } else {                                      \
    EVALUATE(oper,err);				\
  }                                             \

/**
 * @brief utility function to check if the copy to user is executed correctly
 */
#define EVALUATE_COPY_TO(var,oper,comp,copy,err)\
  var = oper;                                   \
  if (comp) {                                   \
    retval = err    ;                           \
  } else {                                      \
    retval = (copy) ? -EAGAIN : 0;              \
  }                                             \

/**************************** Type Definition ***********************************/

#define BANDWIDTH    (6)

//bruno-begin
#define STR_LEN       20
#define TRUE	     (1)
#define FALSE	     (0)
#define MEGA	     (1000000)
#define MAXNUMOFBYTE (7)
#define VHF	     (0x42)
#define UHF	     (0x68)//(0xC8)
#define CONTROLDATA1 (0XC1)
#define CONTROLDATA3 (0X81)
#define	UHFREQ 	     (389)
#define OFFSETVAL    (7)
#define CHFIRST	     (7)
#define CHLAST	     (69)
#define DLOCK 	     (2*HZ)
#define DELAY	     (HZ/10)
#define INITCHANUHF  (14)
#define INITVHFH     (174) 
#define ENDVHFH	     (216)
#define INITUHF      (470)
#define ENDUHF	     (806)
#define SLPMODE	     (0)
#define WKPMODE	     (1)
#define ONEMINUTE    (60)
#define MHZ 	     1000000

#define	IF	     (44)	
#define ENABLE_PLL   (0xFE)
#define PLL_ADDR     (0xC2)
#define AB_FASTGAINCONTROL	(0xA0) // Auxiliary byte (AB)
#define AB_SLOWGAINCONTROL	(0x20) // Auxiliary byte (AB)
#define NUM_TRIES    (20)
//bruno-end



/************************************ Enums *************************************/
enum BitMask { BIT0=0x01, BIT1=0x02, BIT2=0x04, BIT3=0x08, BIT4=0x10, BIT5=0x20,
				BIT6=0x40, BIT7=0x80 };

enum InitReg { ADDR01=0x01, ADDR03=0x03, ADDR04=0x04, ADDR05=0x05, ADDR06=0x06, ADDR13=0x013,
	       	   ADDR14=0x014,ADDR15=0x015,ADDR16=0x16, ADDR1C=0x1C, ADDR1D=0x1D, ADDR1E=0x1E, ADDR20=0x20, 
	           ADDR22=0x22, ADDR30=0x30, ADDR31=0x31, ADDR32=0x32, ADDR50=0x50, 
	           ADDR51=0x51, ADDR58=0x58, ADDR80=0x80, ADDR81=0x81, ADDRC2=0xC2, 
   		       ADDREF=0xEF, ADDRBC=0xBC, ADDRB0=0xB0, ADDR76=0x76, ADDR77=0x77,
		       ADDR9D=0x9D, ADDR9E=0x9E, ADDR9F=0x9F, ADDRA0=0xA0, ADDRA1=0xA1,
			   ADDRA2=0xA2, ADDRA3=0xA3, ADDRA4=0xA4, ADDRA5=0xA5, ADDRA6=0xA6,
	           ADDRA7=0xA7};

enum TunerState { INIT=0, IDLE, SETUP, STDBY, ACTIVE, TUNERERR };

//enum FFTSize { TRANSMISSION_MODE_2K=2048, TRANSMISSION_MODE_4K=4096, 
//				TRANSMISSION_MODE_8K=8192 };

enum GuardInterval { GI1_32=0, GI1_16, GI1_8, GI1_4 };

enum ErrorCorrection { VITERBI=0, RS=1, AUTOMATIC=0, MANUAL=1 };

enum SlotMode { PACKET512=512, PACKET1024=1024, PACKET2048=2048, PACKET4096=4096, 
				PACKET8192=8192, PACKET16384=16384, PACKET32768=32768 };

enum LayersSelection { LAYERA=0, LAYERB, LAYERC };

/*************************** Structures and Variables ***************************/

struct tua6039f_priv {
	u8 i2c_addr;
	struct i2c_adapter *i2c;
	short init;

	u32 frequency;
};

typedef struct {
	s32				freq;

	u8				DividerByte1,
					DividerByte2,
   					
   					ControlByte,		
					BandswitchByte;		
					
	s32				channel;
	
	u8				lock_status;
	
} tua6039f_tuner;

tua6039f_tuner tuner_cfg;

s32 IsoffsetEnabled, tState;

u32 current_channel;

/********************************** ISDB-T Methods ******************************/

//bruno-begin


/**
 * @brief This function is responsible for filling in the pll register of the TUA6039F tuner.
 *		  The pll register contains all information about the channel programming.
 *
 * @param[in] freq 	The channel frequency
 *	   
 */
void setPllData(s32 freq) {

   	s32 bs  = 0x82;   // Bandswitch
   	s32 cp  = 0x01;   // Charge Pump
   	s32 tb  = 0x07;   // Test Bits
   	s32 rs  = 0x01 ;  // Reference Divider Bits: 1/7 MHz ... TODO: FIXO, MUDAR PARA DEFINE
   	s32 fosc;         // Oscilator frequency
   	s32 N;            // Program Divider Value

   	fosc = freq + IF; // IF = 44 MHz

   	/* VHF L */
   	if     ( fosc <= 150 ) { tb=7; cp=0; }
   	else if( fosc <= 180 ) { tb=6; }
   	else if( fosc <= 188 ) { tb=7; }

   	/* VHF H */
   	else if( fosc <= 320 ) { tb=6; cp=0; }
   	else if( fosc <= 420 ) { tb=7; cp=0; }
   	else if( fosc <= 460 ) { tb=6; }
   	else if( fosc <= 470 ) { tb=7; }

   	/* UHF */
   	else if( fosc <= 515 ) { tb=6; bs=0x84; cp=0; }
   	else if( fosc <= 745 ) { tb=7; bs=0x84; cp=0; }
   	else if( fosc <= 845 ) { tb=6; bs=0x84; }
   	else if( fosc <= 905 ) { tb=7; bs=0x84; }

   	N = 7 * fosc;
   	
   	// Divider byte DB
   	tuner_cfg.DividerByte2 = N;
   	tuner_cfg.DividerByte1 = N >> 8;
   	
	// Control byte CB
	tuner_cfg.ControlByte = 0x80;
	tuner_cfg.ControlByte |= (cp  << 6) | (tb  << 3) | (rs << 1);
	
	// Band switch byte BB
	tuner_cfg.BandswitchByte = bs;

}



/**
 * @brief This function is responsible for setting the pll register. A set of bytes 
 *	      is sent via I2C which contains information about the TV channels.
 *		  The chip address is changed before to send data to tuner address (0xC2).
 *
 * @param[in] length	The length of the pll data
 * @param[in] data 		The pll register data to be loaded
 *
 * @retval This function returns 0 if the bytes are sent successfully. Otherwise, it returns -1 
 *		   and errno is set appropriately.
 */
s32 set_pll(u8 length, u8 *data, struct dvb_frontend* fe){

    struct tua6039f_priv* priv = (struct tua6039f_priv*) fe->tuner_priv;
    struct i2c_msg tuner_write_msg = {.addr = 0x61, .flags = 0};
    
    tuner_write_msg.buf = data;
    tuner_write_msg.len = length;
    
    if (i2c_transfer(priv->i2c, &tuner_write_msg, 1) != 1)
    {
        return -EIO;
    }
    
    return 0;


}


/**
 * @brief This function is responsible for filling in the pll register of the TUA6039F tuner 
 *		  and search for digital reception.
 *
 * @param[in] freq 	The channel frequency
 *	   
 */
s32 set_pll_registers( s32 freq,  struct dvb_frontend* fe) {

   	u8 tu_val[4];
	s32 err;
	int ret;

	//  search	
	u8 buf[] = { 0x08, 0x01 };
    struct tua6039f_priv*  priv = (struct tua6039f_priv*) fe->tuner_priv;
    struct i2c_msg msg = { .addr=0x61, .flags=0, .buf=buf, .len=2 };

    setPllData(freq);
	
	// write 0x01 in 0x08
	msg.addr = priv->i2c_addr;
	ret = i2c_transfer(priv->i2c, &msg, 1);		
	if (ret < 0)
		dprintk("tda665xtt.c:%s(%d) Error set pll\n", __FUNCTION__, __LINE__);

	buf[1] = 0x00;
	msg.buf = buf;
	
	ret = i2c_transfer(priv->i2c, &msg, 1);		
	if (ret < 0)
		dprintk("tda665xtt.c:%s(%d) Error set pll\n", __FUNCTION__, __LINE__);

	/* Step 1: */
   	/* Open SCL2 and SDA2 */
	//err = set_register(ENABLE_PLL, 0x00);
	buf[0] = ENABLE_PLL;	
	msg.buf = buf;
	
	// write 0x00 in ENABLE_PLL
	ret = i2c_transfer(priv->i2c, &msg, 1);		
	if (ret < 0){
		dprintk("tda665xtt.c:%s(%d) Error set pll\n", __FUNCTION__, __LINE__);
		return -1;
	}

   	tu_val[0] = 0x9A;
   	tu_val[1] = AB_FASTGAINCONTROL;

   	err = set_pll(2, tu_val, fe);
   	if (err){
	  dprintk("tda665xtt.c:%s(%d) Error set pll\n", __FUNCTION__, __LINE__);
	  return -1;
	}

   	dprintk("%s(%d) Step1: %X %X %X %X \n",
   		__FUNCTION__, __LINE__, tu_val[0], tu_val[1], tu_val[2], tu_val[3]);
	
   	/* Step 2: */
   	/* Open SCL2 and SDA2 */
	// write 0x00 in ENABLE_PLL
	ret = i2c_transfer(priv->i2c, &msg, 1);		
	if (ret < 0){ 
	  dprintk("tda665xtt.c:%s(%d) Error set pll\n", __FUNCTION__, __LINE__);
      return -1;
	}

   	tu_val[0] = tuner_cfg.DividerByte1;
   	tu_val[1] = tuner_cfg.DividerByte2;
   	tu_val[2] = tuner_cfg.ControlByte;
   	tu_val[3] = tuner_cfg.BandswitchByte;

	err = set_pll(4, tu_val, fe);
   	if (err){
	  dprintk("tda665xtt.c:%s(%d) Error set pll\n", __FUNCTION__, __LINE__);
	  return -1;
	}

   	dprintk("%s(%d) Step2: %X %X %X %X \n",
   		__FUNCTION__, __LINE__, tu_val[0], tu_val[1], tu_val[2], tu_val[3]);
	
	// delay
   	udelay(1);

   	/* Step 3: */
   	/* Open SCL2 and SDA2 */
	// write 0x00 in ENABLE_PLL
	ret = i2c_transfer(priv->i2c, &msg, 1);		
	if (ret < 0){
	  dprintk("tda665xtt.c:%s(%d) Error set pll\n", __FUNCTION__, __LINE__);	
	  return -1;
	}

   	tu_val[0] = 0x9A;
   	tu_val[1] = AB_SLOWGAINCONTROL;

   	err = set_pll(2, tu_val, fe);
   	
   	dprintk("%s(%d) Step3: %X %X %X %X \n",
   		__FUNCTION__, __LINE__, tu_val[0], tu_val[1], tu_val[2], tu_val[3]);

	tState = SETUP;

   return err;
}


/**
 * @brief This function changes the channel frequency to the respective channel number 
 * 		  according to the ISDTV.
 *
 * @param[in] freq	The channel frequency
 *
 * @retval This function returns the channel number as an integer. If an error occurs while
 * 		   converting the frequency then -1 is returned and errno is set appropriately.	   
 */
s32 freq2chan(s32 freq){

	s32 channel=0;

	if (!(((freq >= (INITVHFH+(BANDWIDTH/2))) && (freq <= (ENDVHFH-(BANDWIDTH/2)))) || 
		((freq >= (INITUHF+(BANDWIDTH/2))) && (freq <= (ENDUHF-(BANDWIDTH/2))))) ){
		channel = -1;
	}
	/* UHF */
	else if (freq >= (INITUHF+(BANDWIDTH/2))){
		channel = ((freq - UHFREQ)/BANDWIDTH);
	}
	else{	

	   	switch (freq){
		   	/* High VHF */
		   	case 177: channel = 7;	break;
		   	case 183: channel = 8;	break;
		   	case 189: channel = 9;	break;
		   	case 195: channel = 10;	break;
		   	case 201: channel = 11;	break;
		   	case 207: channel = 12;	break;
		   	case 213: channel = 13;	break;

		}
   	}

	dprintk("TUA6039F->tua6039f.c:%s(%d), Channel %d\n", __FUNCTION__, __LINE__, channel);

   	return channel;	

}


s32 tua6039f_setfrequency(s32 freq, struct dvb_frontend* fe){

    s32 ret, err;
    ret = freq2chan(freq);
	
    if(ret > 0) {
      tuner_cfg.freq = freq;
      tuner_cfg.channel = ret;

      err = set_pll_registers(freq, fe);
      if (err) printk("TUA6039F: %s(%d), PLL writing error...\n", __FUNCTION__, __LINE__);
    }

    return ret;
}


//bruno-end


static int tua6039f_init(struct dvb_frontend* fe)
{
	dprintk("tua6039f->%s(%d) initialising\n", __FUNCTION__, __LINE__);
	// registers are initialised by Demodulator
	
	return 0;
}


/**
 * Set frequency, operating mode
 *  
 * @param fe frontend
 * @param params ptr to structure with settings
 * @return 0:success >0:invalid value or operation
 */
static int tua6039f_set_params(struct dvb_frontend* fe, struct dvb_frontend_parameters* params)
{
	struct tua6039f_priv *priv = fe->tuner_priv;

    // First-time initialisation
	if (!priv->init) {
        //TDA665xTT_initreg();
        priv->init = 1;
    }
    
    // Check parameters
    if (priv == NULL || params == NULL)
    {
    	dprintk("%s: %s NULL\n", __FUNCTION__, (priv == NULL) ? "fe->priv" : "params");
    	return -EINVAL;
    }
    
    tua6039f_setfrequency(params->frequency/MHZ, fe);
    
    // save settings
    priv->frequency = params->frequency;
    
    return 0;

}

/**
 * Driver release
 * @param fe frontend
 * @return 0:success
 */
static int tua6039f_release(struct dvb_frontend *fe)
{

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

	if (fe != NULL)
	{
		kfree(fe->tuner_priv);
		fe->tuner_priv = NULL;
	}

	return 0;
}

/**
 * Report tuned frequency
 * @param fe frontend
 * @param frequency location in which tuned frequency is returned (non-NULL)
 * @return 0:success
 */
int tua6039f_get_frequency(struct dvb_frontend *fe, u32 *frequency)
{
	struct tua6039f_priv *priv = fe->tuner_priv;

    dprintk("%s(%d): frequency %d\n", __FUNCTION__, __LINE__, priv->frequency);

	*frequency = priv->frequency;
	return 0;
}

/**
 * Report set bandwidth
 * @param fe frontend
 * @param bandwidth location in which configured bandwidth is returned (non-NULL)
 * @return 0:success
 */
static int tua6039f_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
{
	dprintk("%s(%d)\n", __FUNCTION__, __LINE__);

	*bandwidth = BANDWIDTH;
	return 0;
}

/**
 * Standard tuner operations
 */
static struct dvb_tuner_ops tua6039f_tuner_ops = {
	.info =
	{
		.name = "NXP TUA6039F Tuner - ISDB-T",
		.frequency_min  = 470000000,
		.frequency_max  = 800000000,
		.frequency_step = 6000000
	},
 .set_params        = tua6039f_set_params,
 .get_frequency     = tua6039f_get_frequency,
 .get_bandwidth     = tua6039f_get_bandwidth,
 .release           = tua6039f_release,
 .init				= tua6039f_init,
};

/**
 * Allocate memory for tuner private data and ops, attach to frontend
 * 
 * @param fe frontend to attach to
 * @param addr I2C address 
 * @param i2c I2C bus
 * @param is_master true if 'master' tuner, false if 'slave'
 */
struct dvb_frontend * tua6039f_attach(struct dvb_frontend * fe, u8 i2c_addr, struct i2c_adapter * i2c_adap)
{
	struct tua6039f_priv * priv;

	dprintk("tua6039f->%s(%d) \n", __FUNCTION__, __LINE__);
 
	priv = kzalloc(sizeof(struct tua6039f_priv), GFP_KERNEL);
	if (priv == NULL)
	{
		dprintk("tua6039f->%s(%d) - kzalloc\n", __FUNCTION__, __LINE__);
		return NULL;
	}
 
	priv->i2c_addr = i2c_addr;
	priv->i2c = i2c_adap;

	memcpy(&fe->ops.tuner_ops, &tua6039f_tuner_ops, sizeof(tua6039f_tuner_ops));

	fe->tuner_priv = priv;

	return fe;
}

EXPORT_SYMBOL(tua6039f_attach);
