/***************************************************************************
* Name:ssp.c                                                               *
* Description: SSP C library routine                                       *
* Author:                                                        *
****************************************************************************/
#include "common_include.h"
#include "Driver_Common.h"
#include "kdrv_i2s.h"


//fLib_ssp *SSPDevice[] ={0, (fLib_ssp *) CPE_SSP1_BASE,
// 						(fLib_ssp *) CPE_I2S_BASE,(fLib_ssp *)CPE_AC97_BASE,(fLib_ssp *) CPE_SSP2_BASE};

/*
 PLL=24.576M
 FPclk(For 6631)=24.576/2=12.288M
 FPclk(For AD73311)=24.576/8=3.072M
 
 FSclk=8K or 32K
 fBclk=FSclk*48 or FSclk*64 stuff_bit=48/2-16=8 or 64/2-16=16
 
 FBclk= Fsclk*48(64)=Fpclk/2*(SCLK_DIV+1)

 ex: Fsclk=8k Fpclk=3072K SCLK_DIV=(3072K/8K*48(64)/2)-1=3
*/
/*
 PLL=24.576M
 FMclk(For AC97 Winbond 83972d)=24.576M
 FBclk=12.288M
 
 FSclk=48K(AC97)
 
 FBclk= Fsclk*256=FMclk/2*(SCLK_DIV+1)
*/


int TxFIFOLen = 0;
int RxFIFOLen = 0;

void fLib_I2S_Init(u32 base_address, u32 codec, SSP_MODE_T mode)
{
	u32 data;
	unsigned int master_slave;
	
	
	if(mode == SSP_AS_MASTER)
	master_slave = SSP_OPM_MSST;	
    else
	master_slave = SSP_OPM_SLST;
  
	if( codec == W6631TS_I2S )
	{
	  	// FDIST = 1, 24 bits, padding 7 bits, word_length = 16 bits
	  	// SSP main clock input clock = source clock / 2
    	// outw(SSPCK_BASE,0x00);
		data = I2S_Format | 0x100;
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);
	  	fLib_SetSSPDataLen(base_address,15);
    	fLib_SetSSPpaddingDataLen(base_address,6);
	  	fLib_SetSSPClkdiv(base_address,3);
	}
	else if ( codec == UDA1345TS_I2S )
	{
	  	//UDA1345TS's distance between fram/sync and first data ==> FSDIST = 1
	  	// 16 bits, MSB first, SSP_DATAJUSTIFY set to 1 ==> pad data in the front of serial data
	  	// 16.9344MHz clock => 22.05K sample rate
    	//outw(SSPCK_BASE,0x00);  //dvided 2
		data = I2S_Format | 0x100 | SSP_DATAJUSTIFY;// frame sync must shift 1 bits when I2S mode
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);
	  	fLib_SetSSPDataLen(base_address,0xF);
	  	fLib_SetSSPClkdiv(base_address,0x3);
	}
  	else if ( codec == NORMAL_I2S )
  	{
    	// 16 bits, FDIST = 1, padding data in the back of serial data
    	// 16.9344MHz clock => 22.05K sample rate
		//    outw(SSPCK_BASE,0x00);  //dvided 2
		data = I2S_Format | 0x100;
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);		
	  	fLib_SetSSPDataLen(base_address,0xF);
	  	fLib_SetSSPClkdiv(base_address,0x5);    
  	}
  	else if ( codec == FSA0AC108_I2S )
  	{
    	// 16 data bits, 16 padding data bits
    	// FDIST = 1, padding data in the back of serial data
    	// 12.288MHz clock
		data = I2S_Format | 0x100 | SSP_FSPO_LOW;
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);	/* ssp as master */
	  	fLib_SetSSPDataLen(base_address,0xF);
	  	fLib_SetSSPpaddingDataLen(base_address, 0x10);
	  	fLib_SetSSPClkdiv(base_address,0xB);    
  	}
  	else if ( (codec == WM8510_I2S)||(codec == WM8731S_I2S) )
  	{
			/* If SSP is set as a master, enable SCU+0x330 to allow MCLK to be given by PLL */
		#if 0	
			if(mode == SSP_AS_MASTER)	
			vLib_LeWrite32(SCU_FTSCU100_PA_BASE + 0x330, 1);
			else
			vLib_LeWrite32(SCU_FTSCU100_PA_BASE + 0x330, 0);
		#endif	
    	// 16 data bits, 16 padding data bits
    	// FDIST = 1, padding data in the back of serial data
    	// 12.288MHz clock			
			data = I2S_Format | 0x100 | SSP_FSPO_LOW;
			outw(base_address + SSP_CONTROL0,data);
			fLib_SetSSPOPMode(base_address, master_slave);
	  	fLib_SetSSPDataLen(base_address,0xF);
		  
			if(mode == SSP_AS_MASTER)	
			{
			/* 
			As a master, SSP's MCLK is given by PLL (=12.288MHz) with SCU_FTSCU100_PA_BASE + 0x330 = 1.
			Then, SCLK/BCLK (=3.072MHz) is generated by setting divider (SCLKDIV) to 1 
			and outputs it to codec (WM8731) as a slave, so 16-bit padding is needed for 48KHz-16bit stereo data.
			48K (sample rate) * 32 (bit resolution + padding per channel) * 2 (left and right channels) = 3.072M			
			*/						
			fLib_SetSSPClkdiv(base_address,0x01);	
			fLib_SetSSPpaddingDataLen(base_address, 0x10);				
			}
			else
			{
			/* 
			As a slave, SSP whose SCLK/BCLK (=3.072MHz) are given by external codec (WM8731), 
			so 16-bit padding is needed for 48KHz-16bit stereo data.
			48K (sample rate) * 32 (bit resolution + padding per channel) * 2 (left and right channels) = 3.072M			
			*/						
			fLib_SetSSPpaddingDataLen(base_address, 0x10);			
  	}

  	}
	else if( codec == WM9081_I2S)
	{
		if(mode == SSP_AS_SLAVE)				
		{
			/* 
			SCLK (BCLK) and FS (LRCLK or sample rate) of SSP010, as SLAVE, are given by WM9081 codec as MASTER where 
			FS = 48KHz
			SCLK = FS * 16 (bit resolution per channel) * 2 (left and right channels) = 1.536MHz
			
			SSPCLK is input by internal PLL at 12.288MHz, which suits the minimum ratio of 
			SSPCLK/SCLK (12.288MHz/1.536MHz >= 6)
			*/		
			
			/* Set SCU+0x330 to 1 to enable PLL to generate 12.288 MHz for SSPCLK of SSP010 */		
			vLib_LeWrite32(SCU_FTSCU100_PA_BASE + 0x330, 1);		
			
			/* I2S format, FDIST = 1, frame sync active low */
			data = I2S_Format | 0x100 | SSP_FSPO_LOW;
			outw(base_address + SSP_CONTROL0,data);
			
			/* Slave stereo mode */
			fLib_SetSSPOPMode(base_address, master_slave);
			// 16 data bits, no padding
			fLib_SetSSPDataLen(base_address,0xF);
			fLib_SetSSPpaddingDataLen(base_address, 0x0);		
		}
	}
  	else if ( codec == PCM3793_I2S )
  	{
		data = I2S_Format | 0x100 | SSP_FSPO_LOW;
		outw(base_address + SSP_CONTROL0,data);
		fLib_SetSSPOPMode(base_address, master_slave);
  		fLib_SetSSPDataLen(base_address,0xF);
	  	fLib_SetSSPClkdiv(base_address,0x3);	  	
  	}
}

void fLib_SetSSPFrame(u32 base_address,u32 frame)
{
u32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= 0xFFF;
	data |= frame;
	outw(base_address + SSP_CONTROL0, data); 
}	


void fLib_SetSSPFramePolar(u32 base_address,u32 polar)
{
u32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= ~0x20;
	data |= polar;
  	outw(base_address + SSP_CONTROL0, data);
}	


void fLib_SetSSPOPMode(u32 base_address,u32 mode)
{
u32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= ~0xC;
	data |= mode;
  	outw(base_address + SSP_CONTROL0, data);
}	


void fLib_SetSSPDataLen(u32 base_address,u32 len)
{
u32 data; 

	data = inw(base_address + SSP_CONTROL1); 
	data &= ~ SSP_SDL;
	data |= len << 16;
  	outw(base_address + SSP_CONTROL1, data);
}  

void fLib_SetSSPpaddingDataLen(u32 base_address,u32 len)
{
	u32 data; 

	data = inw(base_address + SSP_CONTROL1);
	data &= ~ SSP_PDL;
	data |= len << 24;
  outw(base_address + SSP_CONTROL1, data);
}  
 
void fLib_SetSSPClkdiv(u32 base_address,u32 divider)
{
u32 data; 

	data = inw(base_address + SSP_CONTROL1); 
	data &= ~ SSP_CLKDIV;
	data |= divider;
  	outw(base_address + SSP_CONTROL1, data);
}  
  

void fLib_SSPClearTxFIFO(u32 base_address)
{
u32 data; 

	data = inw(base_address + SSP_CONTROL2); 
	data |= SSP_TXFCLR;
  	outw(base_address + SSP_CONTROL2, data);
} 


void fLib_SSPClearRxFIFO(u32 base_address)
{
  u32 data; 

	data = inw(base_address + SSP_CONTROL2); 
	data |= SSP_RXFCLR;
  outw(base_address + SSP_CONTROL2, data); 
} 


void fLib_WriteSSP(u32 base_address,u32 data)
{ 
 	outw(base_address + SSP_DATA, data);
} 


u32 fLib_ReadSSPStatus(u32 base_address)
{
u32 data; 

	data = inw(base_address + SSP_STATUS); 
   	return data;	
}

	
u32 fLib_ReadSSP(u32 base_address)
{
u32 data; 

	data = inw(base_address + SSP_DATA); 
 	return data;
} 

void fLib_SetSSPSCLKPO(u32 base_address,u32 spolarity)
{
u32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= ~0x2;
	data |= spolarity;
  	outw(base_address + SSP_CONTROL0, data);	
}	


void fLib_SetSSPSCLKPH(u32 base_address,u32 sphase)
{
u32 data; 

	data = inw(base_address + SSP_CONTROL0); 
	data &= ~0x1;
	data |= sphase;
  	outw(base_address + SSP_CONTROL0, data);
}	


u32 fLib_ReadSSPIntStatus(u32 base_address)
{
u32 data; 

	data = inw(base_address + SSP_INT_STATUS); 
   	return data;	
}


void fLib_SetSSP_TXFIFO(u32 base_address,u32 threshold,u32 underrun)
{
u32 data; 

	data = inw(base_address + SSP_INT_CONTROL); 
  		
	if (threshold)
  		data |= SSP_TFIEN; 	
  	else
  		data &= ~SSP_TFIEN;
  		
  	if (underrun)
  		data |= SSP_TFURIEN; 	
  	else
  		data &= ~SSP_TFURIEN; 
  		
  	outw(base_address + SSP_INT_CONTROL, data); 			  		
}	

void fLib_SetSSP_RXFIFO(u32 base_address,u32 threshold,u32 underrun)
{
u32 data; 

	data = inw(base_address + SSP_INT_CONTROL); 
  		
	if (threshold)
  		data |= SSP_RFIEN; 	
  	else
  		data &= ~SSP_RFIEN;
  		
  	if (underrun)
  		data |= SSP_RFORIEN; 	
  	else
  		data &= ~SSP_RFORIEN; 
  		
  	outw(base_address + SSP_INT_CONTROL, data);
}	

void fLib_SetSSP_DMA(u32 base_address,u32 trans,u32 rec)
{
u32 data; 

	data = inw(base_address + SSP_INT_CONTROL); 
  		
	if (trans)
  		data |= SSP_TXDMAEN;
  	else
  		data &= ~SSP_TXDMAEN;
  		
  	if (rec)
  		data |= SSP_RXDMAEN;
  	else
  		data &= ~SSP_RXDMAEN; 
  		
  	outw(base_address + SSP_INT_CONTROL, data); 		 			  		
}	


void fLib_SetSSP_FIFO_Threshold(u32 base_address,u32 trans_len,u32 rec_len)
{
u32 	ctrl;
	ctrl = inw(base_address + SSP_INT_CONTROL);	
	
	ctrl &= ~0x0001FF80; //bessel:todo:length start with 7th bit not 8bit
	//ctrl |= ((trans_len << 12) + (rec_len << 8)) & 0x0000FF00;
	ctrl |= ((trans_len << 12) + (rec_len << 7));
	
	outw(base_address + SSP_INT_CONTROL, ctrl);
}	


void fLib_SetSSP_WarmReset(u32 base_address)
{
u32 	ctrl;
	
	outw(base_address + SSP_CONTROL2, SSP_ACWRST | SSP_TXDOE);
	do
	{
		ctrl = inw(base_address + SSP_CONTROL2);
	}while((ctrl & SSP_ACWRST) != 0);
}

void fLib_SetSSP_ColdReset(u32 base_address)
{
u32 	ctrl;
	
	outw(base_address + SSP_CONTROL2, SSP_ACCRST | SSP_TXDOE); //
	do
	{
		ctrl = inw(base_address + SSP_CONTROL2);
	}while((ctrl & SSP_ACCRST) != 0);
}


void fLib_SetSSP_Enable(u32 base_address,int enable)
{
u32 	ctrl;
	
	ctrl = inw(base_address + SSP_CONTROL2);
	if(enable)
		ctrl |= (SSP_SSPEN | SSP_TXDOE | SSP_TXEN | SSP_RXEN);//TXDOE is a must; otherwise, no sound at all to output		
	else
		ctrl &= ~(SSP_SSPEN + SSP_TXDOE | SSP_TXEN | SSP_RXEN);
	
	outw(base_address + SSP_CONTROL2, ctrl);
}

void fLib_SetSSP_IntMask(u32 base_address,int Mask)
{
u32 	ctrl;
	ctrl = inw(base_address + SSP_INT_CONTROL);
	ctrl &= ~0x3F;
	ctrl |= Mask;	
	outw(base_address + SSP_INT_CONTROL, ctrl);
}

u32 fLib_SSP_GetTxFIFOLen(u32 base_address)
{
	u32 len;
	len = (inw(base_address + SSP_INFO)&SSP_TXFIFO_DEPTH);	
	TxFIFOLen = (len >> 16) + 1;
	return TxFIFOLen;
}

u32 fLib_SSP_GetRxFIFOLen(u32 base_address)
{
	u32 len;
	len = (inw(base_address + SSP_INFO)&SSP_RXFIFO_DEPTH);	
	RxFIFOLen = (len >> 8) + 1;
	
	return RxFIFOLen;
}


u32 fLib_SSP_GetRxFIFOValidEntries(u32 base_addr)
{
	return ((fLib_ReadSSPStatus(base_addr) & SSP_RFVE) >> 4);
}


void fLib_AC97_SetSlotValidReg(u32 base_address,u32 SlotValid)
{
	outw(base_address + SSP_ACLINK_SLOT_VALID, SlotValid);	
}

void fLib_InitAC97(u32 base_address)
{
//	outw(SSPCK_BASE, 0xF00);

	fLib_SetSSPFrame(base_address,AC97_Format);

	fLib_SSP_GetTxFIFOLen(base_address);
	fLib_SSP_GetRxFIFOLen(base_address);
		  
	fLib_SetSSP_ColdReset(base_address);

	fLib_SetSSP_Enable(base_address,false);
	   
	fLib_SetSSP_IntMask(base_address,0);

	fLib_SetSSP_FIFO_Threshold(base_address,4,4);

}

void fLib_AC97_WriteData(u32 base_address,u32 *data, u32 Len)
{
u32 	tx_cnt;
	
	while(Len != 0)
	{
		tx_cnt = (inw(base_address + SSP_STATUS) & SSP_TFVE) >> 12;		
				
		for(; tx_cnt < TxFIFOLen; tx_cnt++, data++, Len--)
		{
			if(Len == 0)
				return ;
				
			outw(base_address + SSP_DATA, *data);
		}
	}
	return;
}


void fLib_AC97_ReadData(u32 base_address,u32 *data, u32 Len)
{
	u32 	rx_cnt;

	while(Len != 0)
	{
		rx_cnt = (inw(base_address + SSP_STATUS) & SSP_RFVE) >> 4;
		
		for(; rx_cnt > 0; rx_cnt--, data++, Len--)
		{
			if(Len == 0)
				return;
				
			*data = inw(base_address + SSP_DATA);
		}
	}	
} 
u32 fLib_ReadSSP32Bit(u32 base_address)
{
	u32 data; 

	data = inw(base_address + SSP_DATA); 
 	return data;
} 
u32 fLib_AC97_ReadOneWordData(u32 base_address)
{
	while(!(inw(base_address + SSP_STATUS) & SSP_RFVE))
		;
				
	return inw(base_address + SSP_DATA);
}


int fLib_AC97_ReadRegister(u32 base_address,u32 Reg_Index, u16 *data)
{
u32 TxBuf[3], RxBuf[3];
	
 	TxBuf[0] = 0xE000;
	TxBuf[1] = 0x80000+(Reg_Index<<12);
	TxBuf[2] = 0;

	fLib_SSPClearTxFIFO(base_address);
	fLib_SSPClearRxFIFO(base_address);
	
	fLib_AC97_SetSlotValidReg(base_address,0x7);
	
	fLib_AC97_WriteData(base_address,TxBuf, 3);
	
	fLib_SetSSP_Enable(base_address,true);
	
	while(1)
	{
		fLib_AC97_ReadData(base_address,RxBuf, 1);
		//check reg index is match
		if((RxBuf[0] & 0xE000) == 0xE000) 
		{
			fLib_AC97_ReadData(base_address,RxBuf, 1);
			if(((RxBuf[0] >> 12) & 0x7f) == Reg_Index)
			{
				fLib_AC97_ReadData(base_address,RxBuf, 1);
				*data = (RxBuf[0] >> 4) & 0x0000ffff;
			}
			fLib_SetSSP_Enable(base_address,false);
			break;
		}
	}
	return true;
}

int fLib_ReturnTxFIFO_Count(u32 base_address)
{
	int count = 0;
	
	count = (inw(base_address+SSP_STATUS)&SSP_TFVE)>>12;
	
	return count;
}

int fLib_ReturnRxFIFO_Count(u32 base_address)
{
	int count = 0;
	
	count = (inw(base_address+SSP_STATUS)&SSP_RFVE)>>4;
	
	return count;
}

int fLib_AC97_ReadRegisterEx(u32 base_address,u32 Reg_Index, u16 *data)
{
	u32 TxBuf[2], RxBuf[2];
	
	*data = 0xFFFF;
	
	TxBuf[0] = 0x80000+(Reg_Index<<12);			// Command address slot (slot1)
	TxBuf[1] = 0;									// Command Data port slot (slot2)

	fLib_SSPClearTxFIFO(base_address);
	fLib_SSPClearRxFIFO(base_address);
	
	fLib_AC97_SetSlotValidReg(base_address,0xE000);	// slot0, slot1, slot2
	
	//##########################################################################
	// when enable SSP controler, data will send immediately at the first frame,
	// but receive data will be at next frame
	// to make sure second frame will not send wrong data,
	// we must fill 2 set of command to prevent second data to be wrong
	//##########################################################################
	fLib_AC97_WriteData(base_address,TxBuf, 2);
	fLib_AC97_WriteData(base_address,TxBuf, 2);
	
	fLib_SetSSP_Enable(base_address,true);
	
	while(fLib_ReturnTxFIFO_Count(base_address)>0)
		;
		
	fLib_SetSSP_Enable(base_address,false);
		
	// data will be in the second frame, so pump 2 fifo data first
	fLib_AC97_ReadData(base_address,RxBuf, 2);			// read first frame (no use frame)
	
	// real valid data
	fLib_AC97_ReadData(base_address,RxBuf, 1);			// read second frame - slot1(ECHO of Command address)
	if(((RxBuf[0] >> 12) & 0x7f) == Reg_Index)
	{
		fLib_AC97_ReadData(base_address,RxBuf, 1);		// read second frame - slot2 (status data port)
		*data = (RxBuf[0] >> 4) & 0x0000ffff;				
	}
			
	return true;
}

void fLib_AC97_WriteRegister(u32 base_address,u32 Reg_Index, u32 data)
{
	u32 TxBuf[3];

	TxBuf[0] = 0xE000;
	TxBuf[1] = 0x00000+(Reg_Index<<12);
	TxBuf[2] = data << 4;
	 
	fLib_SSPClearTxFIFO(base_address);
	fLib_SSPClearRxFIFO(base_address);	
	fLib_AC97_SetSlotValidReg(base_address,0x7);
		
	fLib_AC97_WriteData(base_address,TxBuf, 3);	
	fLib_SetSSP_Enable(base_address,true);
	while(((inw(base_address+SSP_STATUS)&SSP_TFVE)>>12)>0)
		;
		
	fLib_SetSSP_Enable(base_address,false);		
}

void fLib_AC97_WriteRegisterEx(u32 base_address,u32 Reg_Index, u32 data)
{
	u32 TxBuf[2];

	TxBuf[0] = 0x00000+(Reg_Index<<12);
	TxBuf[1] = data << 4;
	 
	fLib_SSPClearTxFIFO(base_address);
	fLib_SSPClearRxFIFO(base_address);	
	fLib_AC97_SetSlotValidReg(base_address,0xE000);	// slot0, slot1, slot2
		
	fLib_AC97_WriteData(base_address,TxBuf, 2);
	fLib_SetSSP_Enable(base_address,true);

	while(fLib_ReturnTxFIFO_Count(base_address)>0)
		;

	fLib_SetSSP_Enable(base_address,false);

}

u32 fLib_SSP_busy(u32 base_addr)
{
	return (((fLib_ReadSSPStatus(base_addr) ) >> 2) & 0x1);
}

void fLib_SetSSP_SDL_Write(u32 base_address,u32 len,u32 w_data)
{
	while (fLib_SSP_busy(base_address))
	;
	fLib_SetSSPDataLen(base_address,len);
	fLib_WriteSSP(base_address,w_data);
}
