/*****************************************************************************/
/* Copyright (c) 2009 NXP B.V.                                               */
/*                                                                           */
/* 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, using version 2 of the License.             */
/*                                                                           */
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/

/*****************************************************************************************
File name   : tdhl5.c

Description : TDHL5 ISDB-T Tuner Driver

Rev Date       Author      Comments
--------------------------------------------------------------------------------
  3 20090121   batelaan    Update author/license.
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
******************************************************************************************/

/******************************** Kernel Includes *******************************/
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/workqueue.h>

#include "tdhl5.h"
#include "i2c_client.h"

/********************************************************************************/

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

/**
 * @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;              \
  }                                             \

/********************************************************************************/

/************************** Forward declarations ********************************/
void TDHL5_channelscan_notify(struct work_struct *data);
void TDHL5_channelscan_check(struct work_struct *data);
void TDHL5_power_mang(struct work_struct *data);
/********************************************************************************/

/*********************************** Constants **********************************/
#define _MAJOR        210
#define _MINOR        1
#define STR_LEN       20
#define TRUE	     (1)
#define FALSE	     (0)
#define MEGA	     (1000000)
#define MAXNUMOFBYTE (7)
#define VHF1	     (0x22)
#define VHF2	     (0x42)
#define UHF1	     (0x68)
#define UHF2	     (0x88)
#define UHF3	     (0xA8)
#define UHF4	     (0xC8)
#define CONTROLDATA1 (0XC1)
#define CONTROLDATA3 (0X81)
#define OFFSETVAL    (7)
#define BANDWIDTH    (6)
#define	UHFREQ 	     (389)
#define	IF	     (44)	
#define CHFIRST	     (7)
#define CHLAST	     (69)
#define DLOCK 	     (2*HZ)
#define DELAY	     (HZ/10)
#define ENABLE_PLL   (0xFE)
#define PLL_ADDR     (0xC2)
#define NUM_TRIES    (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 MAX_NBITS_ERROR	(100000000)

/************************************ 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 ***************************/

typedef struct{
	struct cdev cdev;
	struct semaphore sema;
	struct fasync_struct *async_apps;
} tdhl5_device;

s32 IsoffsetEnabled, scan_cancel, scan_channel = -1, IsdemodStandby, sleepWakeMode, 
	power_delay, adc_counter, tState, poweroff_counter, lastState, wakeup_counter,
	slpwkp_ratio, tuner_timeout, transmission_mode, correction_mode, slot_mode;
dev_t major_minor;
static atomic_t tdhl5_available = ATOMIC_INIT(1);
struct file_operations tdhl5_fops;
tdhl5_device *device;
u32 current_channel = CHFIRST;

//struct work_struct manag;
struct workqueue_struct *tdhl5_queue;
DECLARE_DELAYED_WORK(notify,TDHL5_channelscan_notify);
DECLARE_DELAYED_WORK(check,TDHL5_channelscan_check);
DECLARE_DELAYED_WORK(manag,TDHL5_power_mang);

channels_table *ch_table;
channels ch;

/********************************************************************************/

/*********************** Methods for accessing I2C client ***********************/
/**
 * @brief This function is responsible for setting the TDHL5 registers value.
 *
 * @param[in] reg 	tdhl5 tuner register
 * @param[in] value register value to be loaded
 *
 * @retval This function returns 0 if the register is successfully set. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 set_register(u8 reg, u8 value){

	s32 err = 0;

	create_init_access_info(access);

	err += sm_lock(&access);	
	err += sm_write_value(reg, (u8) value, &access);
	err += sm_unlock(&access);
	
	if (err < 0)
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Error while writing in register %.2Xh\n", __FUNCTION__, __LINE__, reg);

	return (err < 0)?-1:0;
}

/**
 * @brief This function is responsible for retrieving the TDHL5 registers value.
 *
 * @param[in] reg 		tdhl5 tuner register
 * @param[out] value 	register value that is currently loaded
 *
 * @retval This function returns the register value. Otherwise, it returns -1 and 
 *         errno is set appropriately.
 */
s32 get_register(u8 reg, s32 *value){

	s32 err = 0;
	create_init_access_info(access);
	
	err += sm_lock(&access);
	err += sm_read_value (reg, value, &access);
	err += sm_unlock(&access);

	if (err < 0)
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Error while reading register %.2Xh\n", __FUNCTION__, __LINE__, reg);
	
	return (err < 0) ? -1 : 0;
}

/**
 * @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){

	s32 err = 0;

	create_init_access_info(access);

	err += sm_lock(&access);	
	err += sm_write_pll(length, data, &access);
	err += sm_unlock(&access);
	
	return (err < 0)?-1:0;
}

/********************************************************************************/

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

/**
 * @brief This IOCTL initializes the tdhn2 tuner. It must be called before 
 * 		  running any other tuner operations.
 *
 * @retval This IOCTL returns 0 if the tuner is initialized successfully. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_initreg(void) {

	s32 err = 0;

	debug("TDHL5->tdhl5.c:%s(%d), Initializing tuner...\n", __FUNCTION__, __LINE__);

	err = set_register(ADDR01, 0x40);
	err = set_register(ADDR13, 0x2C); //new
	err = set_register(ADDR14, 0x22); //new
	err = set_register(ADDR15, 0x00); //new

	err = set_register(ADDR16, 0x0A);
	err = set_register(ADDR1C, 0x20);
	err = set_register(ADDR1D, 0xA2);
	err = set_register(ADDR1E, 0xA0);
	err = set_register(ADDR20, 0x00);
	err = set_register(ADDR22, 0x80);
	err = set_register(ADDR30, 0x20);
	err = set_register(ADDR31, 0x11);
	err = set_register(ADDR32, 0x2B);
	err = set_register(ADDR50, 0x04);
	err = set_register(ADDR51, 0x58);
	err = set_register(ADDR58, 0x60);
	err = set_register(ADDRC2, 0x10);
	err = set_register(ADDREF, 0x01);

	return (err < 0) ? -1 : 0;

}

/**
 * @brief This IOCTL function sets the tdhl5 error correction decoder to
 *		  either after Viterbi decoding or Reed Solomon decoding. For both
 *		  decoder, the slot mode is selected which provides the number of
 *		  error packets.
 *
 * @param[in] mode 		Error correction decoder
 *
 * @retval This IOCTL returns 0 if the error correction decoder is set successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_setErrorCorrection(s32 decoder) {

	s32 err = 0, cor;

	err = get_register(ADDR77, &cor);

	if (decoder == VITERBI) {
		err = set_register(ADDR77, cor|0x01); 
		correction_mode = VITERBI;
	} else if (decoder == RS) {
		err = set_register(ADDR77, cor&0xFE);
		correction_mode = RS;
	} else {
		err = -1; /* error while setting error correction decoder */
	}

	err = get_register(ADDR77, &cor);

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), cor register: %d\n", __FUNCTION__, __LINE__, cor);

	return err;
}

/**
 * @brief This IOCTL function gets the tdhl5 error correction mode.
 *
 * @retval If the error correction is set to after Viterbi decoding then 0.
 *		   is returned. Otherwise, it returns 1 for Reed Solomon.
 */
s32 TDHL5_getErrorCorrection(void) {

	return correction_mode;
}

/**
 * @brief This IOCTL function sets the tdhl5 slot mode to 512, 1024, 2048, 
 *		  4096, 8192, 16384, 32768 packets. The slot mode indicates the
 *		  measurement cycles for the packet mode.
 *
 * @param[in] mode 		Slot mode
 *
 * @retval This IOCTL returns 0 if the slot mode is set successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_setSlotMode(s32 mode) {

	s32 err = 0, pecya_high, pecya_low;

//	err = get_register(ADDR76, &cyc);


	switch(mode) {
		case PACKET512:
			err = set_register(ADDR76, 0x8F);
			slot_mode = PACKET512;
			break;
		case PACKET1024:
			err = set_register(ADDR76, 0x9F);
			slot_mode = PACKET1024;
			break;
		case PACKET2048:
			err = set_register(ADDR76, 0xAF);
			slot_mode = PACKET2048;
			break;
		case PACKET4096:
			err = set_register(ADDR76, 0xBF);
			slot_mode = PACKET4096;
			break;
		case PACKET8192:
			err = set_register(ADDR76, 0xCF);
			slot_mode = PACKET8192;
			break;
		case PACKET16384:
			err = set_register(ADDR76, 0xDF);
			slot_mode = PACKET16384;
			break;
		case PACKET32768:
			err = set_register(ADDR76, 0xEF);
			slot_mode = PACKET32768;
			break;
		default:
			err = -1; /* error while setting slot mode */
	}

	err = get_register(ADDRA6, &pecya_high);
	err = get_register(ADDRA7, &pecya_low);

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), slot mode: %d\n", __FUNCTION__, __LINE__, (pecya_high<<8)|pecya_low);

	return err;
}

/**
 * @brief This IOCTL function gets the tdhl5 slot mode.
 *
 * @retval This function returns the slot mode (512, 1024, 2048, 4096, 8192,
 *		   16384, 32768).
 */
s32 TDHL5_getSlotMode(void) {

	return slot_mode;
}

/**
 * @brief This IOCTL function gets the tdhl5 error count. The error count
 *		  is used to calculate the the packet error rate (PER) and bit error
 *		  rate (BER) after Viterbi and RS decoding.
 *
 * @param[in] layer		layer (A, B or C)
 *
 * @retval This IOCTL returns 0 if the error count is read successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 get_LayerErrorCount(s32 layer) {

	s32 err=0, perr1, perr2, perr3, error_count;

	switch(layer) {

		case LAYERA:
			err = get_register(ADDR9D, &perr1);
			err = get_register(ADDR9E, &perr2);
			err = get_register(ADDR9F, &perr3);
			break;
		case LAYERB:
			err = get_register(ADDRA0, &perr1);
			err = get_register(ADDRA1, &perr2);
			err = get_register(ADDRA2, &perr3);
			break;
		case LAYERC:
			err = get_register(ADDRA3, &perr1);
			err = get_register(ADDRA4, &perr2);
			err = get_register(ADDRA5, &perr3);
			break;
		default:
			return err = -1;
	}

		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), perr1: %d\n", __FUNCTION__, __LINE__, perr1);
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), perr2: %d\n", __FUNCTION__, __LINE__, perr2);
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), perr3: %d\n", __FUNCTION__, __LINE__, perr3);

		if (err != -1) {
			error_count = ((perr1<<16)|(perr2<<8)|(perr3))*MAX_NBITS_ERROR;
		} else {
			return err;
		}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Error count: %d\n", __FUNCTION__, __LINE__, error_count);

	return error_count;
}

/**
 * @brief This IOCTL function calculates approximately the PER after RS decoding
 *		  for layer A, B, or C.
 *
 * @param[in] layer		layer (A, B or C)
 *
 * @retval This IOCTL returns the PER. Otherwise, it returns -1 and errno 
 *         is set appropriately.
 */
s32 TDHL5_calculatePER(s32 layer) {

	s32 per=0;

	switch(layer) {
		case LAYERA:	per = get_LayerErrorCount(LAYERA);	break;
		case LAYERB:	per = get_LayerErrorCount(LAYERB);	break;
		case LAYERC:	per = get_LayerErrorCount(LAYERC);	break;
		default: per = -1; /* error while calculating the per */
	}

	if (per != -1) {
		per = (per/slot_mode);
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), PER: %d\n", __FUNCTION__, __LINE__, per);

	return per;
}

/**
 * @brief This IOCTL function calculates approximately the BER after Viterbi
 *		  and RS decoding for layer A, B, or C.
 *
 * @param[in] layer		layer (A/(0), B/(1) or C/(2))
 *
 * @retval This IOCTL returns the BER. Otherwise, it returns -1 and errno 
 *         is set appropriately.
 */
s32 TDHL5_calculateBER(s32 layer) {

	s32 ber=0;

	switch(layer) {
		case LAYERA:	ber = get_LayerErrorCount(LAYERA);	break;
		case LAYERB:	ber = get_LayerErrorCount(LAYERB);	break;
		case LAYERC:	ber = get_LayerErrorCount(LAYERC);	break;
		default: return -1; /* error while calculating the ber */
	}

	if (correction_mode == VITERBI) {
		ber = (ber / (slot_mode*1632));
	} else if (correction_mode == RS) {
		ber = ((ber*36) / (slot_mode*1632));
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), BER: %d\n", __FUNCTION__, __LINE__, ber);

	return ber;
}

/**
 * @brief This IOCTL function sets the tdhl5 transmission mode to
 *		  either automatic decision or manual seeting.
 *
 * @param[in] mode 		Transmission mode
 *
 * @retval This IOCTL returns 0 if the transmission mode is set successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_setTransmissionMode(s32 mode) {

	s32 err = 0, mmode;

	err = get_register(ADDRBC, &mmode);

#if 0
	if(get_register(ADDRBC, &mmode)) {
		return -1; /*error while reading register mmode*/
	}
#endif

	if (mode == AUTOMATIC) {
		err = set_register(ADDRBC, 0xF7); 
		transmission_mode=FALSE;
	} else if (mode == MANUAL) {
		err = set_register(ADDRBC, 0xFF);	
		transmission_mode=TRUE;
	} else {
		err = -1; /* error while setting transmission mode */
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), mmode register: %d\n", __FUNCTION__, __LINE__, mmode);

	return err;
}

/**
 * @brief This IOCTL function checks the tdhl5 transmission mode.
 *
 * @retval If the tdhl5 is in automatic mode then 1 is returned.
 *		   Otherwise, it returns 0 for manual mode.
 */
s32 TDHL5_getTransmissionMode(void) {

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Transmission mode: %d\n", __FUNCTION__, __LINE__, transmission_mode);
	return transmission_mode;
}

/**
 * @brief This IOCTL function can set the tdhl5 fft size to 2048 (2K),
 *		  4096 (4K), 8192 (8K).
 *
 * @param[in] fftsize	FFT size
 *
 * @retval This IOCTL returns 0 if the fft size is set successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_setFFTSize(s32 fftsize) {

	s32 ffsize, err = 0;

	err = get_register(ADDRB0, &ffsize);

	if (fftsize == TRANSMISSION_MODE_2K) {
		err = set_register(ADDRBC, ffsize&0x3F);
	} else if (fftsize == TRANSMISSION_MODE_4K) {
		err = set_register(ADDRBC, ffsize&0x7F);
	} else if (fftsize == TRANSMISSION_MODE_8K) {
		err = set_register(ADDRBC, ffsize&0xBF);
	} else {
		err = -1;
	}

	return err;
}

/**
 * @brief This IOCTL function gets the tdhl5 fft size.
 *
 * @retval This function returns 2K, 4K and 8K according to
 *		   the fft size set to tdhl5. Otherwise, it returns -1
 *		   if an error occurs.
 */
s32 TDHL5_getFFTSize(void) {

	s32 err, ffsize, fftsize;

	err = get_register(ADDRB0, &ffsize);

	switch(ffsize&0xC0) {
		case 0:	fftsize = TRANSMISSION_MODE_2K;	break;
		case 1:	fftsize = TRANSMISSION_MODE_4K;	break;
		case 2: fftsize = TRANSMISSION_MODE_8K;	break;
		default: fftsize = -1;
	}

	return fftsize;
}

/**
 * @brief This IOCTL function can set the tdhl5 guard interval to 1/32,
 *		  1/16, 1/8, 1/4. This guard interval is used to avoid linear  
 *		  distortion such as multipath.
 *
 * @param[in] guard_ratio	Guard Interval
 *
 * @retval This IOCTL returns 0 if the guard interval is set successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_setGuardInterval(s32 guard_ratio) {

	s32 gdleng, err = 0;

	err = get_register(ADDRB0, &gdleng);

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Guard ratio gdleng: %d\n", __FUNCTION__, __LINE__, gdleng);

	if (guard_ratio == GI1_32) {
		err = set_register(ADDRB0, gdleng&0xE7);
	} else if (guard_ratio == GI1_16) {
		err = set_register(ADDRB0, gdleng&0xEF);
	} else if (guard_ratio == GI1_8) {
		err = set_register(ADDRB0, gdleng&0xF7);
	} else if (guard_ratio == GI1_4) {
		err = set_register(ADDRB0, gdleng&0xF7);
	} else {
		err = -1;
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Guard ratio: %d\n", __FUNCTION__, __LINE__, guard_ratio);

	return err;
}

/**
 * @brief This IOCTL function gets the tdhl5 guard interval.
 *
 * @retval This function returns 0 (1/32), 1 (1/16), 2 (1/8) or 3 (1/4) 
 *		   according to the guard interval set to tdhl5. Otherwise, it returns 
 *		   -1 if an error occurs.
 */
s32 TDHL5_getGuardInterval(void) {

	s32 gdleng, guard_interval, reg, err;

	err = get_register(ADDRB0, &gdleng);

	reg = (gdleng&0x18);

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Guard ratio reg: %d\n", __FUNCTION__, __LINE__, reg);
	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Guard ratio gdleng: %d\n", __FUNCTION__, __LINE__, gdleng);

	switch(reg) {
		case 0:	guard_interval = GI1_32;	break;
		case 8:	guard_interval = GI1_16;	break;
		case 16: guard_interval = GI1_8;	break;
		case 24: guard_interval = GI1_4;	break;
		default: guard_interval = -1;
	}

	return guard_interval;
}

/**
 * @brief This IOCTL function sets the value of the time to be spent in standby
 *		  mode before going to the idle state.
 *
 * @param[in] tout 		time out value
 *
 * @retval None
 */
void TDHL5_setTimeOut(s32 tout) {
	tuner_timeout = tout;
}

/**
 * @brief This IOCTL function gets the value of the time to be spent in standby
 *		  mode before going to the idle state.
 *
 * @retval The time out value
 */
s32 TDHL5_getTimeOut(void) {
	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), tuner_timeout: %d\n", __FUNCTION__, __LINE__, tuner_timeout);	
	return tuner_timeout;
}

/**
 * @brief This function provides the current status of the tuner. The following
 *		  states are possible: Initialize, Idle, Setup, Standby, and Active.
 *		  In the idle state, the ADC is turned off and the clock frequency is
 *		  is reduced while in the standby state only the clock frequency is reduced.
 *
 * @retval This IOCTL returns the current status of the tuner.
 */
s32 TDHL5_getTunerState(void) {
	return tState;
}

/**
 * @brief This function is responsible for reducing power consumption 
 *		  of the TDHL5 tuner when the receiver is in standby mode.
 *		  In order to set the tuner to sleep mode then 0 must be passed
 *		  to the function call. To set ther tuner to wakeup mode then 1
 *		  must be passed.
 *
 * @param[in] slpwkp 		Sleep(0)/Wakeup(1) mode
 *
 * @retval This IOCTL returns 0 if the sleep/wakeup mode is set successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_setswmode(s32 slpwkp){

	s32 err = 0;

	if (slpwkp == FALSE) {
		err = set_register(ADDR03, BIT5);
		lastState = tState;
		tState = STDBY;
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Sleep mode...\n", __FUNCTION__, __LINE__);	
	} else if (slpwkp == TRUE) {
		err = set_register(ADDR03, BIT3);
		lastState = tState;
		tState = SETUP;
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Wakeup mode...\n", __FUNCTION__, __LINE__);	
	} else {
		err=-1;
	}

	return (err < 0) ? -1 : 0;
}

/**
 * @brief If this function is called four timers after leaving 
 *		  the standby state then the tuner goes to IDLE state.
 *
 * @retval This function returns the amount of times that the TMCC 
 *         register is equal to 0 (i.e., active).
 */
s32 checkWakupState(void) {

	if (lastState == STDBY && wakeup_counter == 4) {
		wakeup_counter = 0;
		lastState = tState;
		tState = IDLE;
	} else if (lastState == STDBY) {
		wakeup_counter++;
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), wakeup_counter: %d\n", __FUNCTION__, __LINE__, wakeup_counter);

	return wakeup_counter;

}

/**
 * @brief This function sets the tuner to standby mode according to
 *        the parameter passed in the function call. If "TRUE" is passed
 *		  then the ADC is turned off. Otherwise, the ADC is turned on.
 *
 * @param[in] stby 		Standby on(1)/Standby off(0)
 *
 * @retval This IOCTL returns 0 if the tuner is set successfully. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_setstandby(s32 stby){

	s32 err = 0;

	if (stby == TRUE) {
		err = TDHL5_setswmode(SLPMODE);
		err = set_register(ADDR04, BIT5);
		lastState = tState;
		tState = IDLE;
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Turn off ADC\n", __FUNCTION__, __LINE__);	
	} else if (stby == FALSE) {
		err = TDHL5_setswmode(TRUE);
		err = set_register(ADDR04, 0x00);
		lastState = tState;
		tState = SETUP;
		checkWakupState();
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Turn on ADC\n", __FUNCTION__, __LINE__);	
	} else {
		err=-1;
	}

	return (err < 0) ? -1 : 0;
}

/**
 * @brief This function switches from sleep to wakeup and vice-versa
 *        with the purpose of reducing energy consumption.
 *
 * @retval This function returns 0 if the TMCC register is set successfully. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 switchSlpWkp(void) {

	s32 tmunvld, err = 0;

	/* get tmunvld (TMCC non-detection flag) register value */
	err = get_register(ADDR80, &tmunvld);

	power_delay=HZ;

	if (slpwkp_ratio <= 2) {
		TDHL5_setswmode(SLPMODE);  /* Set the tuner to sleep mode */
		tState = STDBY;
		poweroff_counter++;
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), poweroff_counter: %d...\n", __FUNCTION__, __LINE__, poweroff_counter);
		slpwkp_ratio++;
		if (poweroff_counter == tuner_timeout) {
			poweroff_counter = 0;
			TDHL5_setstandby(TRUE);
			lastState = tState;
			tState = IDLE;
		}
	} else {
		slpwkp_ratio = 0;
		TDHL5_setswmode(WKPMODE);  /* Set the tuner to wakeup mode */
		if (!(tmunvld & BIT5)) {
			lastState = tState;
			tState = SETUP;
			poweroff_counter = 0;
		} else {
			tState = STDBY;
		}
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), slpwkp_ratio: %d\n", __FUNCTION__, __LINE__, slpwkp_ratio);	

	return err;
}

/**
 * @brief This function checks if the tuner is active according to the value
 *		  of the TMCC register. If the tuner is not active then it is set to
 *        sleep mode. Otherwise, it is set to wakeup mode.
 *
 * @retval This function returns 0 if the TMCC register is read successfully. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 checkActive(void) {

	s32 tmunvld, err = 0;

	/* get tmunvld (TMCC non-detection flag) register value */
	err = get_register(ADDR80, &tmunvld);

	if (tmunvld & BIT5) {
		power_delay=(HZ/4);
		adc_counter++;
		if (adc_counter == 8) {
			adc_counter=0;
			power_delay=HZ;
			TDHL5_setswmode(SLPMODE);  /* Set the tuner to sleep mode */
			lastState = tState;
			tState = STDBY;
		}
	} else {
		TDHL5_setswmode(WKPMODE);  /* Set the tuner to wakeup mode */
		lastState = tState;
		tState = ACTIVE;
	}

	return err;

}

/**
 * @brief This IOCTL checks if the current channel has been locked or not.
 *
 * @retval If the channel is locked then the integer 1 is returned as parameter. 
 *         Otherwise, 0 is returned. If an error occurs during the IOCTL operation 
 *         then -1 is returned and errno is set appropriately.
 */
s32 TDHL5_getlock(void) {

	s32 value, tuner_locked = FALSE, i;
	
	if (get_register(ADDR80, &value)) {	/* fulock status */
		return -1; 		/* Error */
	}

	for(i=0; i< NUM_TRIES; i++)	{
		if ((~value) & BIT3) {
			tuner_locked = TRUE;
			lastState = tState;
			tState = ACTIVE;
			debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Channel %d was locked...\n", __FUNCTION__, __LINE__, current_channel);
		} else {
			tuner_locked = FALSE;
			lastState = tState;
			tState = STDBY;
			debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Channel %d unlocked...\n", __FUNCTION__, __LINE__, current_channel);
		}
	}
	
	return tuner_locked;
}

/**
 * @brief This function is responsible for controlling the tuner states.
 *
 * @retval None.
 */
void TDHL5_power_mang(struct work_struct *data){

	s32 err = 0;

	switch(tState) {
		case IDLE:
			err = TDHL5_setstandby(TRUE);
			debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), IDLE state...\n", __FUNCTION__, __LINE__);	
			break;
		case SETUP:
			err = TDHL5_setstandby(FALSE);
			err = TDHL5_getlock();
			debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), SETUP state...\n", __FUNCTION__, __LINE__);	
			break;
		case STDBY:
			err = switchSlpWkp();
			debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), STDBY state...\n", __FUNCTION__, __LINE__);	
			break;
		case ACTIVE:
			err = checkActive();
			debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), ACTIVE state...\n", __FUNCTION__, __LINE__);	
			break;
		default:
			err = -1;
	}

	queue_delayed_work(tdhl5_queue, &manag, power_delay);

}

/**
 * @brief This IOCTL sets the frequency of offset value of the tuner. 
 * 		  According to the ISDB-T specification, the 1/7 Mhz should be 
 *		  added to the center frequency of the channel. 
 *
 * @param[in] Value	The offset value
 *
 * @retval This IOCTL returns 0 if the offset is adjusted successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
void TDHL5_setoffset(s32 Value) {

	IsoffsetEnabled = Value;
}

/**
 * @brief This IOCTL sets the tuner to standby or not. 
 *
 * @retval This IOCTL returns 0 if the TMCC flag is read successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_getstandby(void) {

	s32 tmunvld;

	/* get tmunvld (TMCC non-detection flag) register value */
	get_register(ADDR80, &tmunvld);

	if (tmunvld & BIT5) {
		IsdemodStandby=TRUE;
	} else {
		IsdemodStandby=FALSE;
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Standby = %d\n", __FUNCTION__, __LINE__, IsdemodStandby);

	return IsdemodStandby;
}

/**
 * @brief This function calculates the channel programmable data that must be loaded
 *		  into the TDHL5 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 TDHL5 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 IOCTL sets the desired channel on the ISDB-T tuner. 
 *
 * @param[in] channel	The channel number
 *
 * @retval This IOCTL returns 0 if the channel is set successfully. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_setchannel(s32 channel){

	u8 *pllRegData; 
	s32 desiredCenterFreq;

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Setting channel... eddie\n", __FUNCTION__, __LINE__);

	desiredCenterFreq = chan2freq(channel);
	if(desiredCenterFreq > 0) {
		current_channel = channel;
	  	pllRegData = setPllData(desiredCenterFreq);
	  	set_pll(MAXNUMOFBYTE, pllRegData);

		set_register(ADDR01, 0x44);
		tState = SETUP;
	}

	return desiredCenterFreq;
}

/**
 * @brief This IOCTL allocates memory to the channel data sctructure 
 *        according to the channels range.
 *
 * @param[in] fch	The first channel
 * @param[in] lch	The last channel
 *
 * @retval This IOCTL returns 0 if the TMCC flag is read successfully. 
 * 		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 TDHL5_setchannels(s32 fch, s32 lch) {

	kfree(ch_table);
	ch.first_channel = fch;
	ch.last_channel = lch;
	ch_table = (channels_table *)kmalloc(sizeof(channels_table)*(ch.last_channel-ch.first_channel+1),GFP_KERNEL);

	if((current_channel < ch.first_channel) || (current_channel > ch.last_channel)) {
	  TDHL5_setchannel(ch.first_channel);
	}

	if(ch_table) {
	        debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), First channel: %d, Last channel: %d \n", __FUNCTION__, __LINE__, ch.first_channel,ch.last_channel);
	        return 0;
	} else {
	        debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Could not allocate Memory...\n", __FUNCTION__, __LINE__);
	        return -1;
	}
}

/**
 * @brief This IOCTL returns the status of the tuner offset.
 *
 * @retval This IOCTL returns the status of the offset that is currently set in the tuner. 
 *		   If an error occurs while retrieving the offset status then “-1” is returned by the 
 *		   IOCTL and errno is set appropriately.
 */
s32 TDHL5_getoffset(void){

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), OffSet = %d\n", __FUNCTION__, __LINE__, IsoffsetEnabled);
	return IsoffsetEnabled;

}

s32 TDHL5_getswmode(void){

	s32 err=0, slpen=0;

	err = get_register(ADDR81, &slpen);

	if (slpen&BIT4){
		sleepWakeMode=TRUE;
	} else {
		sleepWakeMode=FALSE;
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Sleep(0)/Wakeup(1) Mode = %d\n", __FUNCTION__, __LINE__, sleepWakeMode);

	return sleepWakeMode;

}

/**
 * @brief This function provides the channel that is currently set.
 *
 * @retval This functions returns the current channel is an integer.
 */
s32 TDHL5_getCurrChan(void){

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Channel = %d\n", __FUNCTION__, __LINE__, current_channel);
	return current_channel;

}

/**
 * @brief This function provides the channels range that is currently set.
 *
 * @retval This functions returns the struct that contains the first and last channel.
 */
channels TDHL5_getchannels(void){

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), First Channel = %d, Last Channel = %d\n", __FUNCTION__, __LINE__, ch.first_channel, ch.last_channel);
	return ch;

}

/**
 * @brief This IOCTL provides the bandwidth that is currently set in the TDHL5 driver.
 *
 * @retval This IOCTL returns the bandwidth as an integer. If an error occurs while
 * 		   retrieving the bandwidth then -1 is returned and errno is set appropriately.	   
 */
s32 TDHL5_getbandwidth(void){

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Band Width = %d MHz\n", __FUNCTION__, __LINE__, BANDWIDTH);
	return BANDWIDTH;

}

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

	s32 freq = 0;

	if((channel <= ch.first_channel) || (channel >= ch.last_channel)){
		freq = -1;
	}
	/* UHF */
	else if(channel >= INITCHANUHF){  	
           	freq = ((channel*BANDWIDTH) + UHFREQ);
	}
	else{
		
	   	switch (channel){
		   	/* High VHF */
		   	case 7:	 freq = 177;	break;
		   	case 8:	 freq = 183;	break;
		   	case 9:	 freq = 189;	break;
		   	case 10: freq = 195; 	break;
		   	case 11: freq = 201; 	break;
		   	case 12: freq = 207; 	break;
		   	case 13: freq = 213; 	break;

		}
   	}

	debug("\nTDHL5->tdhl5.c:%s(%d), Channel Frequency %d\n", __FUNCTION__, __LINE__, freq);

   	return freq;
}

/**
 * @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;

		}
   	}

	debug("TDHL5->tdhl5.c:%s(%d), Channel %d\n", __FUNCTION__, __LINE__, channel);

   	return channel;	

}

/**
 * @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 TDHL5_setfrequency(s32 freq){

	s32 ret;
	u8 *pllRegData;

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Setting frequency... eddie\n", __FUNCTION__, __LINE__);

	ret = freq2chan(freq);
	if(ret > 0) {
	  	current_channel = ret;
	  	pllRegData = setPllData(freq);
	  	set_pll(MAXNUMOFBYTE, pllRegData);
		//udelay(100);
		set_register(ADDR01, 0x44);
		tState = SETUP;
	}

	return ret;
}

/**
 * @brief This IOCTL increments the current channel and sets it into the ISDB-T tuner.
 *
 * @retval This IOCTL returns the number of channel that is set as an integer. 
 *		   If an error occurs while setting the channel then -1 is returned and 
 *		   errno is set appropriately.   
 */
s32 TDHL5_channelup(void){
        s32 ret = current_channel;

	if (ret < ch.last_channel){
		ret++;
	}
	else {
		ret = ch.first_channel;
	}

	if(TDHL5_setchannel(ret) > 0) {
	  current_channel = ret;
	} else {
	  ret = -1;
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Current channel: %d\n", __FUNCTION__, __LINE__, ret);

	return ret;

}

/**
 * @brief This IOCTL decrements channel and sets it into the ISDB-T tuner. 
 *		  The number of channel that was set is returned as an integer number.
 *
 * @retval This IOCTL returns the number of channel that is set as an integer. 
 * 		   If an error occurs while setting the channel then -1 is returned 
 *		   and errno is set appropriately.
 */
s32 TDHL5_channeldown(void){
        s32 ret = current_channel;

	if (ret > ch.first_channel){
		ret--;
	}
	else {
		ret = ch.last_channel;
	}

	if(TDHL5_setchannel(ret) > 0) {
	  current_channel = ret;
	} else {
	  ret = -1;
	}

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Current channel: %d\n", __FUNCTION__, __LINE__, ret);

	return ret;
}

/**
 * @brief This IOCTL is called after the driver user starts the auto scan 
 *        functionality and then decides to interrupt the operation at a given moment.
 *
 * @retval None
 */
void TDHL5_scancancel(void){

	scan_cancel = TRUE;
	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Scan operation was canceled...\n", __FUNCTION__, __LINE__);

}

/**
 * @brief This IOCTL automatically scans all TV channels and verify the
 * 	      lock status if there is a signal at the input of the ISDB-T tuner.
 *
 * @retval None
 */
void TDHL5_channelscan(void){

	debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Programming autoscan...\n", __FUNCTION__, __LINE__);
	scan_channel = ch.first_channel;
	scan_cancel  = FALSE;
	schedule_delayed_work(&notify,DELAY);

}

/**
 * @brief This function notifies the application as the channel is programmed.
 *		  If the application cancels the auto scan operattion then a message is
 *		  sent to the kernel log.
 *
 * @param[in] data	General data used in the scheduling process by the kernel
 *
 * @retval None
 */
void TDHL5_channelscan_notify(struct work_struct *data) {

	if (TDHL5_setchannel(scan_channel) > 0) {
	        //schedule_delayed_work(&check,DLOCK);
		queue_delayed_work(tdhl5_queue, &check, DLOCK);
	} else {
	        debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Autoscan canceled...\n", __FUNCTION__, __LINE__);
	}
}

/**
 * @brief This function fills in the channels table as the channel is programmed.
 *
 * @param[in] data	General data used in the scheduling process by the kernel
 *
 * @retval None
 */
void TDHL5_channelscan_check(struct work_struct *data){

    s32 ret,i;
	
	for(i = 0;i < NUM_TRIES; i++) {
		ret = TDHL5_getlock();
	  	if(ret >= 0) {
	    	break;
	  	}
	}

	if(ret < 0) {
		debug(KERN_WARNING "TDHL5->tdhl5.c:%s(%d), Autoscan canceled...\n", __FUNCTION__, __LINE__);
		scan_cancel=FALSE;
	  	scan_channel=-1;
	} else {
	  if (ret){
	      ch_table[scan_channel-ch.first_channel].IsChannelLocked = TRUE;
	  }
	  else{
	  	  ch_table[scan_channel-ch.first_channel].IsChannelLocked = FALSE;
	  }

	  ch_table[scan_channel-ch.first_channel].channel = scan_channel;
	  
	  kill_fasync(&device->async_apps,SIGIO,POLL_IN);

	  if((scan_channel<ch.last_channel) && (!scan_cancel)) {
	      queue_delayed_work(tdhl5_queue, &notify, DELAY);
	      scan_channel++;
	  } else {
	      scan_cancel=FALSE;
	      scan_channel=-1;
	      TDHL5_setchannel(ch.first_channel);
	  }
	}
}

/********************************************************************************/

/*********************************Driver Methods*********************************/

/**
 * @brief This method is used to send a signal to the application
 *
 * @param[in] fd	file descriptor
 * @param[in] *arq	file
 * @param[in] mode	mode
 *
 * @retval This function returns a signal to the application
 */
static int tdhl5_fasync(int fd,struct file *arq,int mode){

  tdhl5_device *dev = arq->private_data;

  return fasync_helper(fd,arq,mode,&dev->async_apps);

}

/**
 * @brief This method is called when opening the TDHL5 driver
 *
 * @param[in] *node	inode
 * @param[in] *arq	file
 *
 * @retval This function returns 0 if the tdhl5 driver is successfully open. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 tdhl5_open(struct inode *node, struct file *arq)
{
	tdhl5_device *dev;
	
	if(!atomic_dec_and_test (&tdhl5_available))
	{
	      atomic_inc(&tdhl5_available);

	      return -EBUSY;
	}

	dev=container_of(node->i_cdev, tdhl5_device, cdev);
	arq->private_data = dev;
	
	return 0;
}


/**
 * @brief This method called when closing the TDHL5 driver
 *
 * @param[in] *node	inode
 * @param[in] *arq	file
 *
 * @retval 0
 */
s32 tdhl5_release(struct inode *node, struct file *arq){

	tdhl5_fasync(-1, arq, 0);
	arq->private_data = NULL;
	atomic_inc(&tdhl5_available);
	
	return 0;
}

/**
 * @brief This method is called when using the ioctl system call
 *
 * @param[in] 	*node	inode
 * @param[in] 	*arq	file
 * @param[in] 	cmd		command
 * @param[out] 	arg		argument
 *
 * @retval This function returns 0 if the IOCTL call is successfully executed. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 tdhl5_ioctl(struct inode *node, struct file *arq, u32 cmd, unsigned long arg){

	tdhl5_device *dev = arq->private_data;
	s32 retval = 0, chkval, layer = 0;
	channels channelfl;

	if(down_interruptible(&dev->sema))
		return -EFAULT;
	
	switch(cmd){

		case TDHL5_IOC_INIT:
		  	PROTECT(EVALUATE(TDHL5_initreg(),-EPERM));
		  	break;

		case TDHL5_IOC_SETOFFSET:
		  	PROTECT(TDHL5_setoffset(arg));
		  	break;

		case TDHL5_IOC_SETCHANNEL:
		  	PROTECT(EVALUATE(TDHL5_setchannel(arg),-EPERM));
		  	break;

		case TDHL5_IOC_SETFREQUENCY:
		  	PROTECT(EVALUATE(TDHL5_setfrequency(arg),-EPERM));
		  	break;

		case TDHL5_IOC_CHANNELSCAN:
		  	PROTECT(TDHL5_channelscan());
		  	break;

		case TDHL5_IOC_SETCHANNELS:
		  	PROTECT(EVALUATE_COPY_FROM(TDHL5_setchannels(channelfl.first_channel, channelfl.last_channel),copy_from_user(&channelfl,(channels *)arg,sizeof(channels)),-ENOMEM));				  
			break;

		case TDHL5_IOC_CHANNELUP:
		  	PROTECT(EVALUATE_COPY_TO(chkval,TDHL5_channelup(),chkval < 0,copy_to_user((s32 *)arg,&chkval,sizeof(chkval)),-EPERM));
		  	break;

		case TDHL5_IOC_CHANNELDOWN:
		  	PROTECT(EVALUATE_COPY_TO(chkval,TDHL5_channeldown(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM));
		  	break;

		case TDHL5_IOC_GETOFFSET:
		  	EVALUATE_COPY_TO(chkval,TDHL5_getoffset(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;	

		case TDHL5_IOC_GETLOCKSTATUS:
		  	EVALUATE_COPY_TO(chkval,TDHL5_getlock(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_SCANCANCEL:
		  	TDHL5_scancancel();
		  	break;

		case TDHL5_IOC_GETCHANNELS:
		  	EVALUATE_COPY_TO(channelfl,TDHL5_getchannels(),(channelfl.first_channel < 0) || (channelfl.last_channel < 0),(copy_to_user((channels *)arg,&channelfl,sizeof(channelfl))),-EPERM);
			break;

		case TDHL5_IOC_GETBANDWIDTH:
		  	EVALUATE_COPY_TO(chkval,TDHL5_getbandwidth(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);	
		  	break;
			
		case TDHL5_IOC_GETCHANNELSTABLE:
		  	PROTECT(retval = (copy_to_user((channels_table *)arg,ch_table,sizeof(channels_table)*(ch.last_channel-ch.first_channel+1))) ? -EAGAIN : 0);
			break;

		case TDHL5_IOC_GETCURRCHAN:
		   	EVALUATE_COPY_TO(chkval,TDHL5_getCurrChan(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
			break;

		case TDHL5_IOC_GETSTANDBY:
		  	EVALUATE_COPY_TO(chkval, TDHL5_getstandby(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
			break;

		case TDHL5_IOC_SETSLEEPWAKEUP:
		  	PROTECT(TDHL5_setswmode(arg));
		  	break;

		case TDHL5_IOC_GETSLEEPWAKEUP:
		  	EVALUATE_COPY_TO(chkval,TDHL5_getswmode(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_GETTUNERSTATUS:
		  	EVALUATE_COPY_TO(chkval,TDHL5_getTunerState(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_SETSTANDBYMODE:
		  	PROTECT(TDHL5_setstandby(arg));
		  	break;

		case TDHL5_IOC_GETSTANDBYMODE:
		  	EVALUATE_COPY_TO(chkval,TDHL5_getstandby(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_SETTIMEOUT:
		  	PROTECT(TDHL5_setTimeOut(arg));
		  	break;

		case TDHL5_IOC_GETTIMEOUT:
		  	EVALUATE_COPY_TO(chkval,TDHL5_getTimeOut(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_SETTRANSMISSIONMODE:
		  	PROTECT(TDHL5_setTransmissionMode(arg));
		  	break;

		case TDHL5_IOC_GETTRANSMISSIONMODE:
		  	EVALUATE_COPY_TO(chkval,TDHL5_getTransmissionMode(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
			break;

		case TDHL5_IOC_SETGUARDINTERVAL:
		  	PROTECT(TDHL5_setGuardInterval(arg));
		  	break;

		case TDHL5_IOC_GETGUARDINTERVAL:
		  	EVALUATE_COPY_TO(chkval,TDHL5_getGuardInterval(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_SETFFTSIZE:
		  	PROTECT(TDHL5_setFFTSize(arg));
		  	break;

		case TDHL5_IOC_GETFFTSIZE:
			EVALUATE_COPY_TO(chkval,TDHL5_getFFTSize(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_SETERRORCORRECTION:
		  	PROTECT(TDHL5_setErrorCorrection(arg));
		  	break;

		case TDHL5_IOC_SETSLOTMODE:
		  	PROTECT(TDHL5_setSlotMode(arg));
		  	break;

		case TDHL5_IOC_GETPER:
			copy_from_user(&layer,(s32 *)arg,sizeof(s32));
//			EVALUATE_COPY_FROM(TDHL5_calculatePER(layer),copy_from_user(&layer,(s32 *)arg,sizeof(s32)),-ENOMEM);				  
		  	EVALUATE_COPY_TO(chkval,TDHL5_calculatePER(layer),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_GETERRORCORRECTION:
			EVALUATE_COPY_TO(chkval,TDHL5_getErrorCorrection(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_GETSLOTMODE:
			EVALUATE_COPY_TO(chkval,TDHL5_getSlotMode(),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		case TDHL5_IOC_GETBER:
			copy_from_user(&layer,(s32 *)arg,sizeof(s32));
		  	EVALUATE_COPY_TO(chkval,TDHL5_calculateBER(layer),chkval < 0,(copy_to_user((s32 *)arg,&chkval,sizeof(chkval))),-EPERM);
		  	break;

		default:
			retval = -ENOTTY;
	}

	up(&dev->sema);
	
	return retval;
}



/**************************** Init and Exit methods *****************************/

/**
 * @brief This method is called by insmod and initializes the TDHL5 driver
 *
 * @retval This function returns 0 if the tdhl5 driver is successfully initialized. 
 *		   Otherwise, it returns -1 and errno is set appropriately.
 */
s32 tdhl5_init(void) {

    s32 resp;
	
	/* initialise the I2C Client Driver that is used by the TDHL5 ISDB-T Tuner Driver */
	sm_init();
	
	tdhl5_queue = create_workqueue("TDHL5_QUEUE");

	transmission_mode=FALSE;
	scan_cancel = FALSE;
	ch.first_channel = CHFIRST;
	ch.last_channel = CHLAST;
	IsdemodStandby = FALSE;
	sleepWakeMode = TRUE;
	TDHL5_setchannels(ch.first_channel, ch.last_channel);
	TDHL5_setoffset(TRUE);
	power_delay = HZ;
	adc_counter = 0;
	poweroff_counter = 0;
	tState = IDLE;
	lastState = INIT;
	wakeup_counter = 0;
	slpwkp_ratio = 0;
	tuner_timeout = ONEMINUTE;
	correction_mode = TRUE;
	slot_mode = PACKET512;

	ch_table = (channels_table *)kmalloc(sizeof(channels_table)*(ch.last_channel-ch.first_channel+1),GFP_KERNEL);

	if (!ch_table) {
		resp = -ENOMEM;
		goto err_2;
	}

	major_minor = MKDEV(_MAJOR,_MINOR);

	debug(KERN_WARNING "TDHL5->tdhn.c:%s(%d), Loading Device Driver...\n", __FUNCTION__, __LINE__);

	resp = register_chrdev_region(major_minor, 1, "tdhl5");

	debug(KERN_WARNING "TDHL5->tdhn.c:%s(%d), Alloc for major and minnor = %s\n", __FUNCTION__, __LINE__, (resp < 0) ? "NOK" : "OK");

	if (resp) {
	        return -EFAULT;
	}

	debug(KERN_WARNING "TDHL5->tdhn.c:%s(%d), Major=%i Minor=%i\n", __FUNCTION__, __LINE__, _MAJOR, _MINOR);
	
	tdhl5_fops.open = tdhl5_open;
	tdhl5_fops.release = tdhl5_release;
	tdhl5_fops.ioctl = tdhl5_ioctl;
	tdhl5_fops.owner = THIS_MODULE;
	tdhl5_fops.fasync  = tdhl5_fasync;
	
	device = (tdhl5_device *)kmalloc(sizeof(tdhl5_device), GFP_KERNEL);

	if (!device){
		resp = -ENOMEM;
		goto err_1;
	}

	memset(device, 0, sizeof(tdhl5_device));
	cdev_init(&device->cdev, &tdhl5_fops);
	
	device->cdev.ops = &tdhl5_fops;
	device->cdev.owner = THIS_MODULE;
		
	init_MUTEX(&device->sema);

	resp = cdev_add(&device->cdev, major_minor, 1);

	debug(KERN_WARNING "TDHL5->tdhn.c:%s(%d), Module added to kernel = %s\n", __FUNCTION__, __LINE__, (resp !=0) ? "NOK" : "OK");

	schedule_delayed_work(&manag,power_delay);
	return 0;

 err_1:
	
	unregister_chrdev_region(major_minor,1);

 err_2:
	return resp;
}


/**
 * @brief This method is called when rmmoding the TDHL5 driver
 *
 * @retval void
 */
void tdhl5_exit(void){

	debug(KERN_WARNING "TDHL5->tdhn.c:%s(%d), TDHL5 is being unloaded!!!\n", __FUNCTION__, __LINE__);

    /* unload the i2c client driver */
	
	cancel_delayed_work(&notify);
	cancel_delayed_work(&check);
	cancel_delayed_work(&manag);
	flush_scheduled_work();
	
	if(device) {
		cdev_del(&device->cdev);
		kfree(device);
	}
	
	unregister_chrdev_region(major_minor, 1);

	if(ch_table) {
		kfree(ch_table);
	}

	sm_exit();
}


/*************************** Module Description **************************/
MODULE_AUTHOR("NXP B.V.");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Driver for controlling the TDHL5 ISDB-T Tuner through the I2C Client");

/**************************** Init and Exit Methods *****************************/

module_init(tdhl5_init); /* Init method - Called by insmod */
module_exit(tdhl5_exit); /* Exit method - Called by rmmod */
