#include "SSD1928_Cam.h"

/*********************************Local Functions*********************************/
static void SSD1928CamCommandWrite(BYTE reg, BYTE cmd);
static BYTE SSD1928CamRegisterRead(BYTE reg);
static SHORT  SSD1928CamTableWrite(const BYTE* ptab);

/*
*************************************************************************************
* Function: 	Write command 'cmd' to register at 'reg' in I2C protocol
*
* PreCondition: Call I2C_Init() prior to his function
*				CAM_DEVICE_ID must be known.
*
* Arguments:	BYTE reg	is the register to write
*				BYTE cmd	is the command to issue
*
* Return:		none
*
* Note:			One may refer to an application note released by OmniVision on
*				Functional Specification of OmniVision Serial Camera Control Bus(SCCB)
*				Document Version 2.1, last mofidied 26 February 2003
*				Communication is done in max. 3 phases.
*				The first step denotes the device ID, in our case it is _deviceID
*				The second phase denotes the sub-address to identify the register location
*				of the specified slave
*				The third phase denotes the data to be written
*************************************************************************************
*/
static void SSD1928CamCommandWrite(BYTE reg, BYTE cmd)
{
	ssd1928_I2C_Ctl(SSD1928_I2C_START|SSD1928_I2C_WRITE);		//low level I2C bits for I2C start condition and I2C write command
	ssd1928_I2C_Write(CAM_DEVICE_ID);							//device ID for OV9653/OV2640/OV2643
	ssd1928_I2C_Ctl(SSD1928_I2C_WRITE);
	ssd1928_I2C_Write(reg);

	ssd1928_I2C_Ctl(SSD1928_I2C_WRITE);
	ssd1928_I2C_Write(cmd);

	ssd1928_I2C_Ctl(SSD1928_I2C_STOP);							//I2C stop
	ssd1928_I2C_Write(0xFF);									//dummy data for I2C stop
}

/*
*************************************************************************************
* Function: 	Read a register from the I2C device
*
* PreCondition: Call I2C_Init() prior to his function
*				CAM_DEVICE_ID must be known
*
* Arguments:	BYTE reg	is the register to read
*				
* Return:		This function returns the byte read from a register 'reg' as the argument
*
* Note:			Optional timeout function may be implemented after if(ssd1928_I2C_Wait()!=0)
*				
*************************************************************************************
*/
static BYTE SSD1928CamRegisterRead(BYTE reg)
{
	BYTE data;

	ssd1928_I2C_Ctl(SSD1928_I2C_START|SSD1928_I2C_WRITE);		//low level I2C bits for I2C start condition and I2C write command
	ssd1928_I2C_Write(CAM_DEVICE_ID);							//device ID for I2C
	if(ssd1928_I2C_Wait()!=0) 
		;//debug camRegRd() line 1
	ssd1928_I2C_Ctl(SSD1928_I2C_WRITE);
	ssd1928_I2C_Write(reg);
	if(ssd1928_I2C_Wait()!=0) 
		;//debug camRegRd() line 2
	ssd1928_I2C_Ctl(SSD1928_I2C_STOP);
	ssd1928_I2C_Write(reg);
	if(ssd1928_I2C_Wait()!=0) 
		;//debug camRegRd() line 3	

	ssd1928_I2C_Ctl(SSD1928_I2C_START|SSD1928_I2C_WRITE);		//low level I2C bits for I2C start condition and I2C write command
	ssd1928_I2C_Write(CAM_DEVICE_ID|0x01);
	if(ssd1928_I2C_Wait()!=0) 
		;//debug camRegRd() line 4
	ssd1928_I2C_Ctl(SSD1928_I2C_READ_WO_ACK|0x10);
	data = ssd1928_I2C_Read();
	
	return data;
}

/*
*************************************************************************************
* This function write init table of OV9653/OV2640/OV2643
*************************************************************************************
*/
static SHORT  SSD1928CamTableWrite(const BYTE* ptab)
{
	BYTE address;
	BYTE value;
	const BYTE *pdata=ptab;
	
	if (pdata==NULL)
		return (-1);

	while(1)
	{
		address = *pdata++;
		value = *pdata++;
		if((address==0)&&(value==0))
		{
			break;
		}
		SSD1928CamCommandWrite(address, value);
	}
}

void SSD1928CamSetPreviewParam(PREVIEW_PARAM param)
{
	SetReg(REG_DV_VHDEC_RATIO, param.previewHRatio);		//set preview horizontal decimation ratio
	SetReg(REG_DV_VVDEC_RATIO, param.previewVRatio);		//set preview vertical decimation ratio
	SetRegW(REG_DV_HCROP_STR_0, param.previewCropHStart);	//set preview crop start
	SetRegW(REG_DV_VCROP_STR_0, param.previewCropVStart);
	SetRegW(REG_DV_HCROP_SIZE_0, param.previewCropHSize);	//set preview crop size
	SetRegW(REG_DV_VCROP_SIZE_0, param.previewCropVSize);
}	

/*
*************************************************************************************
* Function: 	Enable/Disable camera preview function
*************************************************************************************
*/
void SSD1928CamPreview(DWORD address, BOOL on)
{
	//Set Preview video memory start1 and start2 address to the same address base
	//In video preview mode, preview video data are fit starting from
	//Preview Video Memory Address 1 (REG_DV_VMEM_STR_ADDR1_0) or 
	//Preview Video Memory Address 2 (REG_DV_VMEM_STR_ADDR2_0) alternatively at a rate set
	//by Frame Sampling Register (REG_DV_FRAME_SAMPLING). When single preview window is used,
	//both value of Address 1 and Address 2 should be the same.
	SetRegL(REG_DV_VMEM_STR_ADDR1_0, address>>2);
	SetRegL(REG_DV_VMEM_STR_ADDR2_0, address>>2);

	//Set digital video window display start address0 and address1 to the same address base as Preview
	SetRegL(REG_DV_DV0_START_ADDR_0, address>>2);
	SetRegL(REG_DV_DV1_START_ADDR_0, address>>2);

	if(on)
	{
		SetReg(REG_DV_OP_MODE, GetReg(REG_DV_OP_MODE)&~(1<<3));
		SetReg(REG_DV_OP_MODE, GetReg(REG_DV_OP_MODE)|0x12);
	} 
		else
	{
		SetReg(REG_DV_OP_MODE, GetReg(REG_DV_OP_MODE)&~0x12);
	}
}

/*
*************************************************************************************
* Function: 	Enable video still function.
*
* PreCondition: Call SSD1928CamInit() and SSD1928CamPreview() prior to calling this function
*
* Arguments:	'DWORD' address is the starting address for preview image in the display buffer
*				'BOOL' 	on is the ON/OFF switch for video still.
*						TRUE->ON, that means the video will be freezed
*						FALSE->OFF, that means nothing will change for Preview
*				
* Return:		None
*
* Note:			The DV Mode Register at REG[160h] is an important register for this
*				function. JPEG Still Picture Enable and Video Still
*				Enable feature cannot be implemented without a bit-set on Video Start Enable bit.
*
*				One must watch out that, when we use FALSE as the argument for still,
*				only preview is maintained. Therefore if we want to pause the video, and 
*				then make the image "disappear" after a certain amount of time, we 
*				need to code a simple delay after the video still function and then
*				redraw the background by your application.
*************************************************************************************
*/
void SSD1928CamStill(DWORD address, BOOL on)
{
	if(on) 
		SetReg(REG_DV_OP_MODE,GetReg(REG_DV_OP_MODE)|0x01);
	else
		SetReg(REG_DV_OP_MODE,GetReg(REG_DV_OP_MODE)&~0x01);
}

void SSD1928CamInit(void)
{
	//initialize the display ratio, crop size, etc.

	//Low level init on the DV clock for SSD1928
	SetReg(REG_VCLK_CONFIG_0, 0x02); //VCLKO_SRC=PLL_CLK/(VCLKO_SRC Divide + 1) = 72MHz/(2+1) = 24MHz
	SetReg(REG_VCLK_CONFIG_1, 0x97); //VCLK Output Enable, etc.
	
	//init registers of SSD1928
	//Set digital video interface configuration registers for HValid, VValid etc
	SetReg(REG_DV_TV_0,0x12);
	SetReg(REG_DV_TV_1,0x01);
	SetReg(REG_DV_TV_2,0x04);
	SetReg(REG_DV_FRAME_PULSE_WIDTH, 0x08);

	SSD1928CamHardwareInit();
}

/*
*************************************************************************************
* Function: 	Low level initialization on the cmos camera hardware
*
* PreCondition: DV clock enabled. 
*
* Arguments:	None
*				
* Return:		None
*
* Note:			1. The DV clock should be enabled prior to running this function
*					SetReg(REG_VCLK_CONFIG_0, 0x02); 
*					SetReg(REG_VCLK_CONFIG_1, 0x97); 
*				2. Reset signal for OV965X is different from that of OV264X.
*				   A twist to reset signal is performed to make check if I2C returns
*				   0xFFFF, 	DV_ENB pin is set high -> low for OV965X.
*************************************************************************************
*/
void SSD1928CamHardwareInit(void)
{
	WORD_VAL _productID;
	

	//There are two ways for a sensor reset
	//1. Hardware reset - pulling the RESET pin low and keep it for at least 3ms.
	//					  after AVDD is ON
	//2. Software reset - writing 0x80 to register 0x12 for a software reset. If a software reset
	//		is used, a reset operation done twice is recommended to make sure the sensor is stable.
	//For Sensor OV264X:
	//This is a low level camera module hardware reset by a bit set action on 
	//RESET pin 	1:normal operating mode 	0:Reset mode
	//PWDN pin 		0:normal mode 				1:power down mode
	//We need to use REG_DV_ENB register REG[1AFh] to control both PWDN and RESET pins
	SetReg(REG_DV_ENB, 0x00);	//hold in reset
	DelayMs(3);					//keep hardware reset pin low after AVDD is ON for at least 3ms
	SetReg(REG_DV_ENB, 0x01);	//RESET pin brought high
	DelayMs(2);				
		
	ssd1928_I2C_Init();		//Init I2C module of SSD1928

	//read product ID
	_productID.v[1] = SSD1928CamRegisterRead(0x0A);
	_productID.v[0] = SSD1928CamRegisterRead(0x0B);

	if(_productID.Val==0xFFFF)	//reset signal for OV965X is different from ov264x
	{
		//Twist the reset signal for OV965X
		SetReg(REG_DV_ENB, 0x01);	//hold in reset
		DelayMs(3);					//keep hardware reset pin high for 3ms
		SetReg(REG_DV_ENB, 0x00);	//normal operation to bring RESET pin low
		DelayMs(2);

		//read product ID
		_productID.v[1] = SSD1928CamRegisterRead(0x0A);
		_productID.v[0] = SSD1928CamRegisterRead(0x0B);
		_productID.Val&=0x9650;	//The actual product ID can be 0x9650, 0x9652, 0x9653 etc.
	}

	switch (_productID.Val)
	{
		case (OV2643_PRODUCT_ID):
				#if defined (OV2643_USE_720P)
						SSD1928CamTableWrite(OV2643_REGS_720p);	//1280*720
						_param.previewHRatio = 2;	//It is 1280*720 image displayed on 320*240 QVGA TFT
						_param.previewVRatio = 2;	//Ratio = 1:2^bit[2-0] = 1:4
						_param.previewCropHStart = 0;
						_param.previewCropVStart = 0;
						_param.previewCropHSize = OV2643_HOR_SIZE;
						_param.previewCropVSize = OV2643_VER_SIZE;
					
				#elif defined (OV2643_USE_SUB)
						SSD1928CamTableWrite(OV2643_REGS_SUB);	//400*300 sub-sampling
						_param.previewHRatio = 0;	//It is 400*300 image displayed on 320*240 QVGA TFT
						_param.previewVRatio = 0;	//Ratio = 1:2^bit[2-0] = 1:4
						_param.previewCropHStart = 40;
						_param.previewCropVStart = 30;
						_param.previewCropHSize = OV2643_HOR_SIZE;
						_param.previewCropVSize = OV2643_VER_SIZE;
				#endif
			break;
		case (OV2640_PRODUCT_ID):
				#if defined (OV2640_USE_VGA)				//640*480
					SSD1928CamTableWrite(OV2640_REGS_VGA);
					_param.previewHRatio = 1;	//It is 640*480 image displayed on 320*240 QVGA TFT
					_param.previewVRatio = 1;	//Ratio = 1:2^bit[2-0] = 1:2
					_param.previewCropHStart = 0;
					_param.previewCropVStart = 0;
					_param.previewCropHSize = OV2640_HOR_SIZE;
					_param.previewCropVSize = OV2640_VER_SIZE;
				#endif
			break;
		case (OV965X_PRODUCT_ID):
					SSD1928CamTableWrite(OV965X_REGS_TBL);
				#if defined (OV965X_USE_QVGA)	//320*240
					_param.previewHRatio = 0;	//It is 320x240 displayed on QVGA TFT
					_param.previewVRatio = 0;
				#elif defined OV965X_USE_VGA	//640*480
					_param.previewHRatio = 1;	
					_param.previewVRatio = 1;
				#elif defined OV965X_USE_SXGA	//1280*1024
					_param.previewHRatio = 2;	
					_param.previewVRatio = 2;					
				#endif	
					_param.previewCropHStart = 0;
					_param.previewCropVStart = 0;
					_param.previewCropHSize = OV965X_HOR_SIZE;
					_param.previewCropVSize = OV965X_VER_SIZE;
			break;	
	}

	SSD1928CamSetPreviewParam(_param);	//set preview parameters
}


