/*
    
File name   : tda665xtt.c

Description : Driver for tda665xtt Tuner

COPYRIGHT (C) NXP Semiconductors

Date          Modification                              Name
----          ------------                              ----
01 Jun 2007   Created                                   Anastacio Cavalcante - Lucas Cordeiro
23 Jul 2007   Changed driver name to TDHL5				Lucas Cordeiro
24 Jul 2007	  Added Doxygen comments					Lucas Cordeiro
17 Aug 2007	  Added power management functionality	    Lucas Cordeiro
29 Aug 2007	  Added fft size, guard interval functions	Lucas Cordeiro
10 Sep 2007   Added error correction decoder functions  Lucas Cordeiro
20 Jan 2009   Port to Linux DVB API						Daniel Patrick

*/

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

#include "tda665xtt.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 MAXNUMOFBYTE (7)
#define BANDWIDTH    (6)
#define OFFSETVAL    (7)
#define CONTROLDATA1 (0XC1)
#define CONTROLDATA3 (0X81)
#define ENABLE_PLL   (0xFE)
#define PLL_ADDR     (0xC2)
#define	IF	         (44)
#define VHF1	     (0x22)
#define VHF2	     (0x42)
#define UHF1	     (0x68)
#define UHF2	     (0x88)
#define UHF3	     (0xA8)
#define UHF4	     (0xC8)
#define	UHFREQ 	     (389)
#define INITCHANUHF  (14)
#define INITVHFH     (174) 
#define ENDVHFH	     (216)
#define INITUHF      (470)
#define ENDUHF	     (806)
#define MHZ (1000000)


/************************************ Enums *************************************/

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

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

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};

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

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

	u32 frequency;
};

s32 IsoffsetEnabled, tState;

u32 current_channel;

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

/**
 * @brief This function calculates the channel programmable data that must be loaded
 *		  into the TDA665xTT register. The channel programmable data is calculated based  
 *		  on the channel frequency and the offset values.
 *
 * @param[in] freq 	The channel frequency
 *
 * @retval This function returns the register value of the programmable data.
 */
s32 calculateRegVal(s32 freq)
{

	s32 progData;

	progData = (OFFSETVAL*freq + IsoffsetEnabled + OFFSETVAL*IF);
    
	return progData;
}

/**
 * @brief This function calculates the control data register which indicates
 *		  the range of channels frequency (VHF or UHF).
 *
 * @param[in] freq 	The channel frequency
 *
 * @retval This function returns the value that must be loaded into the control data register. 
 *		   If an error occurs while calculating the control data register value then -1 is 
 *	       returned and errno is set appropriately.	   
 */
s32 calculateCD2(s32 freq)
{

	s32 controlData2;

	if (freq == 177) { // channel 7
		controlData2 = VHF1;
	} else if ((freq >= 183) && (freq <= 213)) { // channels 8 - 13
		controlData2 = VHF2;
	} else if ((freq >= 473) && (freq <= 557)) { // channels 14 - 28
		controlData2 = UHF1;
	} else if ((freq >= 563) && (freq <= 629)) { // channels 29 - 40
		controlData2 = UHF2;
	} else if ((freq >= 635) && (freq <= 707)) { // channels 41 - 53
		controlData2 = UHF3;
	} else if ((freq >= 713) && (freq <= 803)) { // channels 54 - 69
		controlData2 = UHF4;
	} else {
		controlData2 = -1; //error! out of range.
	}

	return controlData2;
}

/**
 * @brief This function is responsible for filling in the pll register of the TDA665xTT tuner.
 *		  The pll register contains all information about the channel programming.
 *
 * @param[in] freq 	The channel frequency
 *
 * @retval This function returns the value that must be loaded into the pll register. 
 *		   If an error occurs while filling in the pll data then -1 is returned and
 *	       and errno is set appropriately.	   
 */
u8 *setPllData(s32 freq)
{
	u8 controlData2;
	static u8 pllData[MAXNUMOFBYTE];
	u32 progDivider;

	controlData2 = calculateCD2(freq);
	progDivider = calculateRegVal(freq);

	pllData[0] = ENABLE_PLL;
	pllData[1] = PLL_ADDR;
	pllData[2] = (progDivider & 0xFF00) >> 8;
	pllData[3] = (progDivider & 0x00FF);
	pllData[4] = CONTROLDATA1;
	pllData[5] = controlData2;
	pllData[6] = CONTROLDATA3;
	
	return pllData;
}

/**
 * @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(KERN_WARNING "tda665xtt.c:%s(%d), Channel %d , Freq %d\n", __FUNCTION__, __LINE__, channel, freq);

   	return channel;	

}


/**
 * @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.
 *
 * @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 tda665xtt_priv* priv = (struct tda665xtt_priv*) fe->tuner_priv;
    struct i2c_msg tuner_write_msg = {.addr = priv->i2c_addr, .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 IOCTL sets the desired frequency on the ISDB-T tuner.
 *
 * @param[in] freq 	The channel frequency
 *
 * @retval This IOCTL returns 0 if the frequency is set successfully. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDA665xTT_setfrequency(s32 freq, struct dvb_frontend* fe)
{

	s32 ret;
	u8 *pllRegData;

    u8 buf[] = { ADDR01, 0x44 };
    struct tda665xtt_priv*  priv = (struct tda665xtt_priv*) fe->tuner_priv;
    struct i2c_msg msg = { .addr=0, .flags=0, .buf=buf, .len=2 };


	dprintk(KERN_WARNING "tda665xtt.c:%s(%d), Setting frequency (%d)...\n", __FUNCTION__, __LINE__, freq);

	ret = freq2chan(freq);
	if(ret > 0) {
	  	current_channel = ret;
	  	pllRegData = setPllData(freq);
	  	
	  	ret = set_pll(MAXNUMOFBYTE, pllRegData, fe);
	  	
	  	if (ret < 0)
			dprintk("tda665xtt.c:%s(%d) Error set pll\n", __FUNCTION__, __LINE__);
	  	
		// write 0x44 in ADDR01
		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__);
		
		tState = SETUP;
	}

	return ret;
}


static int tda665xtt_init(struct dvb_frontend* fe)
{
	dprintk("tda665xtt->%s(%d) initialising\n", __FUNCTION__, __LINE__);
	// registers are initialised by Demodulator (tc90507xbg)
	
	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 tda665xtt_set_params(struct dvb_frontend* fe, struct dvb_frontend_parameters* params)
{
	struct tda665xtt_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;
    }
    
    TDA665xTT_setfrequency(params->frequency/MHZ, fe);
    
    // save settings
    priv->frequency = params->frequency;
    
    return 0;

}

/**
 * Driver release
 * @param fe frontend
 * @return 0:success
 */
static int tda665xtt_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 tda665xtt_get_frequency(struct dvb_frontend *fe, u32 *frequency)
{
	struct tda665xtt_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 tda665xtt_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 tda665xtt_tuner_ops = {
	.info =
	{
		.name = "NXP TDA665XTT Tuner - ISDB",
		.frequency_min  = 470000000,
		.frequency_max  = 800000000,
		.frequency_step = 6000000
	},
 .set_params        = tda665xtt_set_params,
 .get_frequency     = tda665xtt_get_frequency,
 .get_bandwidth     = tda665xtt_get_bandwidth,
 .release           = tda665xtt_release,
 .init				= tda665xtt_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 * tda665xtt_attach(struct dvb_frontend * fe, u8 i2c_addr, struct i2c_adapter * i2c_adap)
{
	struct tda665xtt_priv * priv;

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

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

	fe->tuner_priv = priv;

	return fe;
}

EXPORT_SYMBOL(tda665xtt_attach);
