/*  ============================================================================
 *   Copyright (c) Texas Instruments Inc 2015
 *
 *   Use of this software is controlled by the terms and conditions found in the
 *   license agreement under which this software has been supplied.
 *   ===========================================================================
 */
/** ============================================================================
 example for USIM initialization and read/write driver on KeyStone device
 only support mobile phone card (asynchronous, T = 0) for now
 * =============================================================================
 *  Revision History
 *  ===============
 *  Feb 27, 2015 Brighton Feng  file created
 * =============================================================================*/

#include <stdio.h>
#include <string.h>
#include "K2_common.h"
#include "K2_USIM_init_drv.h"

CSL_Usimocp__all__Regs * gpUSIM_regs = (CSL_Usimocp__all__Regs *)CSL_USIM_REGS;

USIM_Data usim_data;

/*Fi lookup table*/
Uint16 usFi_LUT[]=
{
	372 ,
	372 ,
	558 ,
	744 ,
	1116,
	1488,
	1860,
	372 ,
	372 ,
	512 ,
	768 ,
	1024,
	1536,
	2048,
	372 ,
	372 
};

/*Di lookup table*/
Uint8 ucDi_LUT[]=
{
	1 ,
	1 ,
	2 ,
	4 ,
	8 ,
	16,
	32,
	64,
	12,
	20,
	1 ,
	1 ,
	1 ,
	1 ,
	1 ,
	1  
};

/*configure the TX/RX FIFO of USIM*/
void K2_USIM_FIFO_cfg(USIM_FIFO_Config * fifo_cfg)
{
	/* Reset FIFO Pointer */
	gpUSIM_regs->USIM_FIFOS |= CSL_USIMOCP_USIM_FIFOS_FIFOTX_RESET_MASK
		|CSL_USIMOCP_USIM_FIFOS_FIFORX_RESET_MASK;
	gpUSIM_regs->USIM_FIFOS &= ~(CSL_USIMOCP_USIM_FIFOS_FIFOTX_RESET_MASK
		|CSL_USIMOCP_USIM_FIFOS_FIFORX_RESET_MASK);

	gpUSIM_regs->USIM_FIFOS &= ~(CSL_USIMOCP_USIM_FIFOS_RXDMA_TYPE_MASK
		|CSL_USIMOCP_USIM_FIFOS_FIFO_RX_TRIGGER_MASK
		|CSL_USIMOCP_USIM_FIFOS_FIFO_TX_TRIGGER_MASK);

	gpUSIM_regs->USIM_FIFOS |= (fifo_cfg->rx_DMA_type<<CSL_USIMOCP_USIM_FIFOS_RXDMA_TYPE_SHIFT)
		|(fifo_cfg->rx_FIFO_trigger_thresh<<CSL_USIMOCP_USIM_FIFOS_FIFO_RX_TRIGGER_SHIFT)
		|(fifo_cfg->tx_FIFO_trigger_thresh<<CSL_USIMOCP_USIM_FIFOS_FIFO_TX_TRIGGER_SHIFT)
		|CSL_USIMOCP_USIM_FIFOS_FIFO_ENABLE_MASK;

	if(fifo_cfg->bEDMA_event_en)
	{
		gpUSIM_regs->USIM_FIFOS |= CSL_USIMOCP_USIM_FIFOS_DMA_MODE_MASK;
	}
	else
	{
		gpUSIM_regs->USIM_FIFOS &= ~CSL_USIMOCP_USIM_FIFOS_DMA_MODE_MASK;
	}
	
	gpUSIM_regs->USIM_RXFIFO_BYTECNT= fifo_cfg->rx_FIFO_Byte_cnt;
};

/*Initialize USIM controller*/
void K2_USIM_Init(USIM_Config * cfg)
{
	Uint32 N, CLKDIV, CLKMODE, ETU_DIV;
	
	/* perform softreset of IP */
	gpUSIM_regs->SYSCONFIG |= CSL_USIMOCP_SYSCONFIG_SOFTRESET_MASK;

	/* wait until reset get completed */
	do{
		delay_us(10);
	}while((gpUSIM_regs->SYSCONFIG & CSL_USIMOCP_SYSCONFIG_SOFTRESET_MASK)
		||(0==(gpUSIM_regs->SYSSTATUS & CSL_USIMOCP_SYSSTATUS_RESETDONE_MASK)));

	/*disable auto idle*/
	gpUSIM_regs->SYSCONFIG &= ~CSL_USIMOCP_SYSCONFIG_AUTOIDLE_MASK;

	//Set the output clock SIM_CLK for the Smart Card to the working frequency
	gpUSIM_regs->USIMCONF2 &= ~(CSL_USIMOCP_USIMCONF2_CONFSCLKMODE_MASK|CSL_USIMOCP_USIMCONF2_CONFSCLKDIV_MASK);
	gpUSIM_regs->USIMCONF2 |= cfg->sim_clk_div;
	CLKDIV= (cfg->sim_clk_div&CSL_USIMOCP_USIMCONF2_CONFSCLKDIV_MASK)>>CSL_USIMOCP_USIMCONF2_CONFSCLKDIV_SHIFT;
	CLKMODE= (cfg->sim_clk_div&CSL_USIMOCP_USIMCONF2_CONFSCLKMODE_MASK)>>CSL_USIMOCP_USIMCONF2_CONFSCLKMODE_SHIFT;
	N= CLKDIV*2+2 + 4*CLKMODE;

	usim_data.speed_Hz= gMain_Core_Speed_Hz/64/N;

	if(cfg->bSoftFiDi)
	{
		/*Set the FI, DI value */
		gpUSIM_regs->CONF5_REG= CSL_USIMOCP_CONF5_REG_SOFT_NHARD_FIDI_PROG_MASK|
			(cfg->Fi<<CSL_USIMOCP_CONF5_REG_CONFFI_SHIFT)|			
			(cfg->Di<<CSL_USIMOCP_CONF5_REG_DI_SHIFT);
		ETU_DIV= usFi_LUT[cfg->Fi]*N/ucDi_LUT[cfg->Di];
		
		/*Set the ETU clock period based on the functional high-frequency period*/
		gpUSIM_regs->CONF_ETU_DIV= ETU_DIV;

		/*Set the ETU over sample clock period*/
		gpUSIM_regs->CONF_SAM1_DIV= ETU_DIV/8;
	}
	else
		gpUSIM_regs->CONF5_REG &= ~CSL_USIMOCP_CONF5_REG_SOFT_NHARD_FIDI_PROG_MASK;
		//gpUSIM_regs->CONF5_REG = 0;

	/*Configure the Work Waiting Time (WWT= (WAITI+1)*960*ETU)*/
	gpUSIM_regs->CONF4_REG= cfg->WAITI;
	
	/*Configure the Character Guard Time (CGT)  */
	if(cfg->CGT>=11)
		gpUSIM_regs->USIM_CGT= cfg->CGT;

	/*Configure the Character Waiting Time (CWT)*/
	if(cfg->CWT>=12)
		gpUSIM_regs->USIM_CWT= cfg->CWT;
	
	/*Configure the Block Waiting Time (BWT)    */
	if(cfg->BWT>=12)
		gpUSIM_regs->USIM_BWT= cfg->BWT;

	/*set the protocol of the Smart Card*/
	if(cfg->protocol) 	//T=1
	{
		gpUSIM_regs->USIMCONF2 |= CSL_USIMOCP_USIMCONF2_CONFPROTOCOL_MASK;

		//set EDC type for T=1
		gpUSIM_regs->USIMCONF2 &= ~(CSL_USIMOCP_USIMCONF2_CONFEDC_MASK|
			CSL_USIMOCP_USIMCONF2_CONFLRCCHECK_MASK);
		gpUSIM_regs->USIMCONF2 |= cfg->t1Edc;
	}
	else //T=0
	{
		gpUSIM_regs->USIMCONF2 &= ~CSL_USIMOCP_USIMCONF2_CONFPROTOCOL_MASK;

		gpUSIM_regs->USIMCONF2 &= ~CSL_USIMOCP_USIMCONF2_CONFRESENT_MASK;
		gpUSIM_regs->USIMCONF2 |= (cfg->maxParityRetries
			<<CSL_USIMOCP_USIMCONF2_CONFRESENT_SHIFT);
	}

	if(cfg->bCheckParity)
	{
		gpUSIM_regs->USIMCONF2 |= CSL_USIMOCP_USIMCONF2_CONFCHKPAR_MASK
			|CSL_USIMOCP_USIMCONF2_NACKING_EN_MASK;
	}
	else
	{
		gpUSIM_regs->USIMCONF2 &= ~(CSL_USIMOCP_USIMCONF2_CONFCHKPAR_MASK
			|CSL_USIMOCP_USIMCONF2_NACKING_EN_MASK);
	}

	if(cfg->bPutErrInFifo)
	{
		gpUSIM_regs->USIMCONF2 |= CSL_USIMOCP_USIMCONF2_PUT_ERR_IN_FIFO_MASK;
	}
	else
	{
		gpUSIM_regs->USIMCONF2 &= ~CSL_USIMOCP_USIMCONF2_PUT_ERR_IN_FIFO_MASK;
	}

	/*generate interrupt based on a programmable number of consecutive 
	detected parity error*/
	gpUSIM_regs->USIMCONF2 &= ~CSL_USIMOCP_USIMCONF2_PAR_ERR_LEVEL_MASK;
	gpUSIM_regs->USIMCONF2 |= (cfg->parErrLevel<<CSL_USIMOCP_USIMCONF2_PAR_ERR_LEVEL_SHIFT);

	//Bypass ATR waiting sequence for synchronous card
	if(cfg->bBypassSyncATR)
	{
		gpUSIM_regs->USIMCONF2 |= CSL_USIMOCP_USIMCONF2_ATR_ASYN_BYPASS_MASK;
	}
	else
	{
		gpUSIM_regs->USIMCONF2 &= ~CSL_USIMOCP_USIMCONF2_ATR_ASYN_BYPASS_MASK;
	}

	gpUSIM_regs->IRQSTATUS = 0xFFFFFFFF; //clear interrupt flags
	gpUSIM_regs->IRQENABLE = cfg->interruptEnableMask; //enable masked interrupts

	if(cfg->fifo_cfg)
		K2_USIM_FIFO_cfg(cfg->fifo_cfg);
	
	//Start the clock module
	gpUSIM_regs->USIMCMD |= CSL_USIMOCP_USIMCMD_MODULE_CLK_EN_MASK;

	usim_data.stat= USIM_STATE_IDLE;
}

//active USIM card
void K2_USIM_activate_card(USIM_FIFO_Config * fifo_cfg)
{
	usim_data.uiART_len= 0;
	usim_data.stat= USIM_STATE_ACTING;

	/*Configure the direction of the SIM_IO line for reception*/
	gpUSIM_regs->USIMCONF2 &= ~CSL_USIMOCP_USIMCONF2_TXNRX_MASK;

	if(fifo_cfg)
		K2_USIM_FIFO_cfg(fifo_cfg);

	/* Activate using USIM */
	gpUSIM_regs->USIMCMD &= ~CSL_USIMOCP_USIMCMD_CMDSTOP_MASK;
	gpUSIM_regs->USIMCMD |= CSL_USIMOCP_USIMCMD_CMDSTART_MASK;

	usim_data.stat= USIM_STATE_WAIT_ATR;
}

//deactive USIM card
void K2_USIM_deactivate()
{
	/* Activate using USIM */
	gpUSIM_regs->USIMCMD &= ~CSL_USIMOCP_USIMCMD_CMDSTART_MASK;
	gpUSIM_regs->USIMCMD |= CSL_USIMOCP_USIMCMD_CMDSTOP_MASK;

	usim_data.stat= USIM_STATE_IDLE;
}

//read data
Uint32 K2_USIM_read(Uint8 * Rx_Buf, Uint32 uiBufLength)
{
	int i;
	Uint32 uiCount= gpUSIM_regs->USIM_RXFIFO_LEVEL;

	if(uiCount> uiBufLength)
		uiCount= uiBufLength;
		
	for(i=0; i< uiCount; i++)
		Rx_Buf[i]= gpUSIM_regs->USIM_DRX;

	return uiCount;
}

//transmit data
void K2_USIM_transmit(Uint8 * data, Uint32 uiLength)
{
	Uint32 uiByteCnt=0;

	printf("Transmit %d bytes: ", uiLength);

	/*Configure the direction of the SIM_IO line for transmission*/
	gpUSIM_regs->USIMCONF2 |= CSL_USIMOCP_USIMCONF2_TXNRX_MASK;
	
	while(uiByteCnt<uiLength)
	{
		//wait for room in FIFO
		if(0==(gpUSIM_regs->USIM_FIFOS & CSL_USIMOCP_USIM_FIFOS_FIFOTX_FULL_MASK))
		{
			gpUSIM_regs->USIM_DTX= data[uiByteCnt];
			printf("%02x ", data[uiByteCnt++]);
		}			
	}
	printf("\n");

	//wait for transmit complete
	while(0==(gpUSIM_regs->USIM_FIFOS & CSL_USIMOCP_USIM_FIFOS_FIFOTX_EMPTY_MASK));
}

//select file with its ID, return file size.
Uint32 K2_USIM_select_file(Uint32 file_ID)
{
	Uint8 buffer[8];

	/*-------send SELECT command-------*/
	buffer[0]= USIM_CLA;
	buffer[1]= USIM_INS_SELECT;
	buffer[2]= 0;
	buffer[3]= 0;
	buffer[4]= 2;

	//clear RX buffer for response
	usim_data.uiRX_len= 0;
	
	//send command
	K2_USIM_transmit(buffer, 5);

	//wait for response (updated by interrupt)
	while(0==usim_data.uiRX_len);
	if(USIM_INS_SELECT!=usim_data.Rx_Buf[0])
	{
		puts("received wrong response!");
		return 0;
	}

	/*---------send file ID---------*/
	buffer[0]= (file_ID>>8)&0xFF;
	buffer[1]= file_ID&0xFF;

	//clear RX buffer for response
	usim_data.uiRX_len= 0;
	
	//send data
	K2_USIM_transmit(buffer, 2);

	//wait for response (updated by interrupt)
	while(0==usim_data.uiRX_len);

	/*-------send GET RESPONSE command-------*/
	while((USIM_SW1_GET_RESPONSE==usim_data.Rx_Buf[0])||
		(USIM_SW1_CORRECT_COMMAND==usim_data.Rx_Buf[0]))
	{
		buffer[0]= USIM_CLA;
		buffer[1]= USIM_INS_GET_RESPONSE;
		buffer[2]= 0;
		buffer[3]= 0;
		buffer[4]= usim_data.Rx_Buf[1];

		//clear RX buffer for response
		usim_data.uiRX_len= 0;
		
		//send command
		K2_USIM_transmit(buffer, 5);

		//wait for response (updated by interrupt)
		while(0==usim_data.uiRX_len);
	}	

	if(USIM_SW1_SUCCESS!=usim_data.Rx_Buf[usim_data.uiRX_len-2])
	{
		puts("received wrong response!");
		return 0;
	}

	return usim_data.Rx_Buf[4];
}

//read binary data from current file
Uint32 K2_USIM_read_binary_file(Uint32 uiOffset, Uint8 ucByteCnt)
{
	Uint8 buffer[8];

	buffer[0]= USIM_CLA;
	buffer[1]= USIM_INS_READ_BINARY;
	buffer[2]= (uiOffset>>8)&0xFF;
	buffer[3]= uiOffset&0xFF;
	buffer[4]= ucByteCnt;

	//clear RX buffer for response
	usim_data.uiRX_len= 0;
	
	//send command
	K2_USIM_transmit(buffer, 5);

	//wait for response (updated by interrupt)
	while(0==usim_data.uiRX_len);
	while(USIM_SW1_SUCCESS!=usim_data.Rx_Buf[usim_data.uiRX_len-2])
	{
		if(USIM_SW1_REPEAT_COMMAND==usim_data.Rx_Buf[usim_data.uiRX_len-2])
		{
			buffer[4]= usim_data.Rx_Buf[usim_data.uiRX_len-1];

			//clear RX buffer for response
			usim_data.uiRX_len= 0;
			
			//send command
			K2_USIM_transmit(buffer, 5);

			//wait for response (updated by interrupt)
			while(0==usim_data.uiRX_len);

			continue;
		}
		else
		{
			puts("received wrong response!");
			return 0;
		}
	}
	return usim_data.uiRX_len;
}

//read ICCID from USIM
void K2_USIM_read_ICCID()
{
	int i;
	Uint32 uiFileSize, uiByteCnt;
	Uint8 data;
	
	puts("Select ICCID file...");
	uiFileSize= K2_USIM_select_file(USIM_EF_ICCID);

	puts("Read ICCID...");
	if(uiFileSize)
	{
		uiByteCnt= K2_USIM_read_binary_file(0, uiFileSize);
		if(uiByteCnt>=uiFileSize)
		{
			printf("ICCID = ");
			for(i= 0; i< 10; i++)
			{
				data= usim_data.Rx_Buf[i+1];
				printf("%x%x", data&0xF, data>>4);
			}
			printf("\n");
		}
	}
}

//return the pointer of ATR string, and parser ATR contents
Uint8 * K2_USIM_get_ATR()
{
	int i;
	Uint8 TS, T0, TA, TB, TC, TD;
	if((usim_data.stat < USIM_STATE_ATR_DONE)||
		(usim_data.uiART_len==0))
	{
		puts("Do not receive ATR yet!");
		return NULL;
	}

	printf("Get %d bytes in ATR: ", usim_data.uiART_len);
	for(i=0; i< usim_data.uiART_len; i++)
		printf("0x%02x ", usim_data.ATR[i]);
	
	TS= usim_data.ATR[0];
	printf("\n TS= 0x%02x, ", TS);
	if((TS != 0x3B)&&(TS != 0x03)&&(TS != 0x3F))
	{
		printf("Error: invalid ATR!!!\n");
		return NULL;
	}
	
	if(TS==0x3B)
		printf("direct ");
	else if((TS==0x03)||(TS==0x3F))
		printf("inverse ");
	printf("coding convention.\n");

	T0= usim_data.ATR[1];
	if(T0&(1<<4))
	{
		TA= usim_data.ATR[2];
		printf(" TA= 0x%02x, Fi= %d, Di= %d\n", 
			TA, usFi_LUT[TA>>4], ucDi_LUT[TA&0xF]);
	}
	
	if(T0&(1<<5))
	{
		TB= usim_data.ATR[3];
		printf(" TB= 0x%02x\n", TB);
	}
	
	if(T0&(1<<6))
	{
		TC= usim_data.ATR[4];
		printf(" TC, extra Guard Time integer (N) = %d\n", TC);
	}
	
	if(T0&(1<<7))
	{
		TD= usim_data.ATR[5];
		printf(" TD= 0x%02x\n", TD);
	}
	
	printf(" number of Ti in ATR = %d.\n", T0&0xF);

	return usim_data.ATR;
}

//return the pointer of data string, and print the contents
Uint8 * K2_USIM_get_Rx_data()
{
	int i;

	if((usim_data.stat < USIM_STATE_RX_DONE)||
		(usim_data.uiRX_len==0))
	{
		puts("Do not receive data yet!");
		return NULL;
	}

	printf("Received %d bytes: ", usim_data.uiRX_len);
	for(i=0; i< usim_data.uiRX_len; i++)
		printf("%02x ", usim_data.Rx_Buf[i]);
	printf("\n");

	return usim_data.Rx_Buf;

}

char * USIM_status_str[]=
{
	"no ATR!",
	"Work Waiting Time (WWT) timeout.",
	"receive FIFO full!",
	"FIFO_TX_TRIGGER+1 data can be written into FIFO.",
	"RX_FIFO threshold is reached.",
	"USIM card insertion/extraction.",
	"End Of Block receive (T = 1).",
	"Time Out Character (CWT) (T = 1)!",
	"Time Out Block (BWT) (T = 1)!",
	"the same word has been sent (USIM_CONF2.CONFRESENT) times (T = 0)!",
	"TS_decode error!",
	"EMV_ATR_LENGTH_TIME_OUT!",
	"SIM clock stopped.",
	"Number of consecutive frames received with parity error has reached the level defined by the PAR_ERR_LEVEL field of USIMCONF2!",
	"Frame error occurred during frame transmission/reception!",
	"received bytes reaches RXFIFO_BYTECNT (T=0) or the last byte of the block has been received (T = 1)."
};

//return IQRSTATUS and parser it
Uint32 K2_USIM_IRQ_handler()
{
	int i;
	Uint32 IQRSTATUS= gpUSIM_regs->IRQSTATUS;

	if(IQRSTATUS&CSL_USIMOCP_IRQSTATUS_USIM_NATR_MASK)
		puts("USIM IRQ: no ATR!");
#if 0
	for(i=1; i<=16; i++)
	{
		if(IQRSTATUS&(1<<i))
			printf("USIM IRQ: %s\n", USIM_status_str[i]);
	}
#endif

	if(IQRSTATUS&(CSL_USIMOCP_IRQENABLE_TOB_EN_MASK                    
		|CSL_USIMOCP_IRQENABLE_TOC_EN_MASK                    
		|CSL_USIMOCP_IRQENABLE_EOB_EN_MASK                    
		|CSL_USIMOCP_IRQENABLE_RX_EN_MASK                     
		|CSL_USIMOCP_IRQENABLE_RXFULL_EN_MASK                 
		|CSL_USIMOCP_IRQENABLE_WT_EN_MASK                     
		|USIM_IRQ_RXDMA_RDY_MASK  
		|CSL_USIMOCP_IRQENABLE_PAR_ERR_LEVEL_REACHED_EN_MASK))
	{
		if(usim_data.stat==USIM_STATE_WAIT_ATR)
		{
			usim_data.uiART_len= K2_USIM_read(usim_data.ATR, USIM_MAX_ATR_LENGTH);
			if(usim_data.uiART_len)
			{
				usim_data.stat=USIM_STATE_ATR_DONE;
				K2_USIM_get_ATR();
			}
		}
		else if(usim_data.stat>=USIM_STATE_ATR_DONE)
		{
			usim_data.uiRX_len= K2_USIM_read(usim_data.Rx_Buf, USIM_RX_BUF_BYTE_SIZE);
			if(usim_data.uiRX_len)
			{
				usim_data.stat=USIM_STATE_RX_DONE;
				K2_USIM_get_Rx_data();
			}
		}
	}
	
	//clear interrupts
	gpUSIM_regs->IRQSTATUS= IQRSTATUS;

	return IQRSTATUS;
}
