//
// Copyright (c) Samsung Electronics CO., LTD.  All rights reserved.
//
/*++
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

Module Name:    ipc.cpp

Abstract:       implementation of controller for IPC

Functions:

Notes:

--*/

#include <bsp.h>
#include <drvlib_mem.h>
#include <assert.h>
#include <pmplatform.h>
#include "ipc.h"

#define MODULE_NAME _T("IPC")

#define IPC_MSG(x)    DBGMSG(CAMC_USR4 , x)

IPC::IPC()
{
	DWORD dwIPIndex, dwBytes;

    m_hPwrControl = CreateFile( L"PWC0:", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
    if (INVALID_HANDLE_VALUE == m_hPwrControl )
    {
        IPC_MSG((_T("[%s] %s() : PWC0 Open Device Failed!\r\n"), MODULE_NAME, _T(__FUNCTION__)));
    }
    
	dwIPIndex = CLK_IP0_IPC;
	if ( !DeviceIoControl(m_hPwrControl, IOCTL_PWRCON_SET_CLOCK_ON, &dwIPIndex, sizeof(DWORD), NULL, 0, &dwBytes, NULL) )
	{
        IPC_MSG((_T("[%s] %s() : IOCTL_PWRCON_SET_POWER_OFF Failed!\r\n"), MODULE_NAME, _T(__FUNCTION__)));       
	}    
    
    // Map Virtual address of IPC SFR from physical address
    m_pIPC_Reg = (IPC_REG *)DrvLib_MapIoSpace(BASE_REG_PA_IPC, sizeof(IPC_REG), FALSE);
    if (m_pIPC_Reg == NULL)
    {
        IPC_MSG((_T("[%s] %s() : m_pIPC_Reg Mapping Register Physical Base Address to Virtual Address Failed!\r\n"), MODULE_NAME, _T(__FUNCTION__)));       
    }

}


IPC::~IPC()
{
	DWORD dwIPIndex, dwBytes;

    if (m_pIPC_Reg != NULL)
    {
        DrvLib_UnmapIoSpace((PVOID)m_pIPC_Reg);
        m_pIPC_Reg = NULL;
    }
    
	dwIPIndex = CLK_IP0_IPC;
	if ( !DeviceIoControl(m_hPwrControl, IOCTL_PWRCON_SET_CLOCK_OFF, &dwIPIndex, sizeof(DWORD), NULL, 0, &dwBytes, NULL) )
	{
        IPC_MSG((_T("[%s] %s() : IOCTL_PWRCON_SET_POWER_OFF Failed!\r\n"), MODULE_NAME, _T(__FUNCTION__)));               
	}    
}


void IPC::InitIp(DWORD InHSz, DWORD InVSz, _2D_eIPC Ipc2d)
{   
	oIpc_SrcInf.uImgHsz = InHSz;
	oIpc_SrcInf.uImgVsz = InVSz;
	oIpc_SrcInf.uSrcHsz = InHSz;
	oIpc_SrcInf.uSrcVsz = InVSz;

	oIpc_DstInf.eScanMode = PROGRESSIVE_IPC;
			
	if(Ipc2d == IPC_2D)
	{
		oIpc_DstInf.uDstHsz = InHSz;
		oIpc_DstInf.uDstVsz = InVSz*2;		
        IPC_MSG((_T("[%s] %s() : IPC-2D Mode, IPC OUT IMAGE SIZE : %d X %d\r\n"), MODULE_NAME, _T(__FUNCTION__), InHSz, InVSz*2));               
	}
	else
	{
		oIpc_DstInf.uDstHsz = InHSz*2;
		oIpc_DstInf.uDstVsz = InVSz;		
        IPC_MSG((_T("[%s] %s() : IPC-HDS Mode, IPC OUT IMAGE SIZE : %d X %d\r\n"), MODULE_NAME, _T(__FUNCTION__), InHSz*2, InVSz));               
	}
	oIpc_ConVar.uModeVal = Ipc2d;
	
	Init(oIpc_SrcInf, oIpc_DstInf, oIpc_ConVar);
	
}


void IPC::Init(oIPC_Source oIpc_Src, oIPC_Destination oIpc_Dst, oIPC_ControlVariable oIpc_ConVar)
{
	SWReset();
	EnableIP(OFF);

	SetPostProcessingOnOff(OFF);
	SetModeAndImgSize(oIpc_Src, oIpc_Dst, oIpc_ConVar);

	//Initialize Enhancement 
	InitEnhancingParameter();
	SetContrast(oIpc_EnhVar.aContrast); 
	SetBrightness(oIpc_EnhVar.aBrightness);
	SetBrightOffset(oIpc_EnhVar.uBrightOffset);
	SetSaturation(oIpc_EnhVar.uSaturation);
	SetSharpness(oIpc_EnhVar.eSharpness, oIpc_EnhVar.uThHnoise);

	//Initialize the Poly-Phase Filter Coefficients
	SetFilter();

	m_pIPC_Reg->IPC_PEL_RATE_CTRL = 0x0<<0; // 1pixel/1cycle

	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1;	
	
}


//////////
// Function Name 	: 	SWReset
// Description		:	IPC Software Reset Command
// Input 			:	None
void IPC::SWReset(void)
{
	while(m_pIPC_Reg->IPC_SRESET & IPC_SOFT_RESET)
	{
		Sleep(1);
	}
	m_pIPC_Reg->IPC_SRESET |= IPC_SOFT_RESET;
}


//////////
// Function Name 	: 	EnableIP
// Description		:	IPC On
// Input 			:	dwOnOff - 1(On), 0(Off)
void IPC::EnableIP(DWORD dwOnOff)
{
	if (dwOnOff == ON)
	{
        IPC_MSG((_T("[%s] %s() : IPC ENABLE\r\n"), MODULE_NAME, _T(__FUNCTION__)));               
		m_pIPC_Reg->IPC_ENABLE |= 0x1;
	}
	else
	{
        IPC_MSG((_T("[%s] %s() : IPC DISABLE\r\n"), MODULE_NAME, _T(__FUNCTION__)));               
		m_pIPC_Reg->IPC_ENABLE &= ~(0x1);
	}
}


//////////
// Function Name 	: 	SetPostProcessingOnOff
// Description		:	IPC PostProcessing On/Off Selection
// Input 			:	uOnOff - 1(On), 0(Off)
void IPC::SetPostProcessingOnOff(DWORD dwOnOff)
{
	if (dwOnOff == ON)
    {
        IPC_MSG((_T("[%s] %s() : POST ENABLE\r\n"), MODULE_NAME, _T(__FUNCTION__)));               
        m_pIPC_Reg->IPC_BYPASS |= 0x1;
    }   
	else
    {
        IPC_MSG((_T("[%s] %s() : POST DISABLE\r\n"), MODULE_NAME, _T(__FUNCTION__)));                  
		m_pIPC_Reg->IPC_BYPASS &= ~(0x1);
    }
}


//////////
// Function Name 	: 	IPC_SetModeAndImgSize
// Description		:	IPC Mode Setting & Source Image Parameter Setting & Destination Image Parameter Setting.
// Input 			:	oIPC_Src - refer to oVP_Source
//					oIPC_Dst - refer to oVP_Destination
void IPC::SetModeAndImgSize(oIPC_Source oIpc_Src, oIPC_Destination oIpc_Dst, oIPC_ControlVariable oIpc_ConVar)
{
	DWORD uVRatio, uHRatio;
		
	FILED_ID_Control(IPC_BOTTOM_FIELD);
	FILED_ID_Mode(CAM_FIELD_SIG, AUTO);
//	FILED_ID_Mode(INTERNAL, AUTO);

	IPC2D_ENABLE(oIpc_ConVar.uModeVal);	// Enalbed : 2D IPC , Disabled : Horizon Double Scailing
	
	m_pIPC_Reg->IPC_SRC_WIDTH = (oIpc_Src.uSrcHsz & 0x7ff);
	m_pIPC_Reg->IPC_SRC_HEIGHT = (oIpc_Src.uSrcVsz & 0x7ff);
	
	// Set the Destination Image
	m_pIPC_Reg->IPC_DST_WIDTH = (oIpc_Dst.uDstHsz & 0x7ff);
	m_pIPC_Reg->IPC_DST_HEIGHT = (oIpc_Dst.uDstVsz & 0x7ff);


	uHRatio = (oIpc_ConVar.uModeVal== 1) ? (0x10000):(0x08000);
	m_pIPC_Reg->IPC_H_RATIO = uHRatio;

	uVRatio = 0x10000;
	m_pIPC_Reg->IPC_V_RATIO = uVRatio;

	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1;
}


//////////
// Function Name 	: 	FILED_ID_Set
// Description		:	IPC Field id top/bottom Selection
// Input 			:	uFieldIdReg - 0(top), 1(bottom)
void IPC::FILED_ID_Control(IPC_eFIELD_ID id )
{
	m_pIPC_Reg->IPC_FIELD_ID = id;

	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1;
}
	

//////////
// Function Name 	: 	FILED_ID_Mode
// Description		:	IPC Field id signal source Selection, auto toggling define
// Input 			:	sel - 0(Internal generation), 1(camif field signal accept) 
//					:	toggle - 0(define by user), 1(auto toggling)
void IPC::FILED_ID_Mode(IPC_eFIELD_ID_SEL sel,IPC_eFIELD_ID_TOGL toggle )
{
	m_pIPC_Reg->IPC_MODE = (sel<<6 | toggle<<2); 
	
	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1;
}


//////////
// Function Name 	: 	IPC2D_ENABLE
// Description		:	IPC 2D enable/disable Selection
// Input 			:	uOnOff - 0(disable), 1(enable)
void IPC::IPC2D_ENABLE(_2D_eIPC mode)
{
	if (mode == IPC_2D)
		m_pIPC_Reg->IPC_MODE |= (0x1<<1);
	else
		m_pIPC_Reg->IPC_MODE &= ~((0x1)<<1);	

	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1;
}


//////////
// Function Name 	: 	InitEnhancingParameter
// Description		:	Initialize the IPC Enhancing Parameter Variable(refer to oVP_EnhancingVariable Structure)
// Input 			:	None
void IPC::InitEnhancingParameter(void)
{
	DWORD i;
	
	for(i=0 ; i<8 ; i++)
	{
		oIpc_EnhVar.aBrightness[i] = 0x0;
		oIpc_EnhVar.aContrast[i] = 0x80;
	}

	oIpc_EnhVar.uSaturation = 0x80;
	oIpc_EnhVar.eSharpness = NO_EFFECT;
	oIpc_EnhVar.uThHnoise = 0x5;
	oIpc_EnhVar.uBrightOffset = 0x0;
}


//////////
// Function Name 	: 	SetContrast
// Description		:	IPC Contrast Setting
// Input 			:	uContrast - LINE_SLOPE Value(unsigned 1.7 format)
void IPC::SetContrast(DWORD *uContrast)
{
	DWORD i, uLineEq[8];

	for(i=0 ; i<8 ; i++)
	{
		uLineEq[i] = m_pIPC_Reg->IPC_PP_LINE_EQ[i];
		uLineEq[i] &= ~(0xFF<<0);
		uLineEq[i] |= ((uContrast[i]&0xFF)<<0);
		m_pIPC_Reg->IPC_PP_LINE_EQ[i] = uLineEq[i];
	}

	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1;
}


//////////
// Function Name 	: 	SetBrightness
// Description		:	IPC Brightness Setting
// Input 			:	uBrightness - LINE_INTC Value(unsigned 9.7 format)
void IPC::SetBrightness(DWORD *uBrightness)
{
	DWORD i, uLineEq[8];

	for(i=0 ; i<8 ; i++)
	{
		uLineEq[i] = m_pIPC_Reg->IPC_PP_LINE_EQ[i];
		uLineEq[i] &= ~(0xFFFF<<8);
		uLineEq[i] |= ((uBrightness[i]&0xFFFF)<<8);		//intercept, signed 9.7 format
		m_pIPC_Reg->IPC_PP_LINE_EQ[i] = uLineEq[i];
	}

	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1;				
}


//////////
// Function Name 	: 	SetBrightOffset
// Description		:	Set the Brightness Offset control register for Y
// Input 			:	uOffset - Offset for Y Brightness Value(signed 1.8 format)
void IPC::SetBrightOffset(DWORD uOffset)
{
	m_pIPC_Reg->IPC_PP_BRIGHT_OFFSET = (uOffset & 0x1FF);
	
	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1; 		
}


//////////
// Function Name 	: 	SetSaturation
// Description		:	Set the Color Saturation control register
// Input 			:	uSaturation - Color Saturation Factor Value(signed 1.7 format)
void IPC::SetSaturation(DWORD uSaturation)
{
	m_pIPC_Reg->IPC_PP_SATURATION = (uSaturation & 0xff);

	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1;	
}


//////////
// Function Name 	: 	SetSharpness
// Description		:	Set the Picture sharpness control register
// Input 			:	eSharpness - Control for the edge enhancement Value
//					uThreshold - Treshold value setting to decide minimum vertical edge value
void IPC::SetSharpness(IPC_eSHARPNESS eSharpness, DWORD uThreshold)
{
	DWORD uSharpVal;
	
	uSharpVal = 	(eSharpness == NO_EFFECT) ? (0x0<<0):
				(eSharpness == MIN_EDGE) ? (0x1<<0):
				(eSharpness == MODERATE_EDGE) ? (0x2<<0): 
				(0x3<<0); // MAX_EDGE

	m_pIPC_Reg->IPC_PP_SHARPNESS = (uSharpVal | ((uThreshold&0xff)<<8));

	//  Update shadow register
	m_pIPC_Reg->IPC_SHADOW_UPDATE = 0x1;	
}


//////////
// Function Name 	: 	SetFilter
// Description		:	Set the Poly-Phase Filter Coefficients
// Input 			:	None
void IPC::SetFilter(void)
{
	DWORD uHRatio, uVRatio;
	IPC_eFILTER_H_PP eHFilter;
	IPC_eFILTER_V_PP eVFilter;

	uHRatio = m_pIPC_Reg->IPC_H_RATIO;
	uVRatio = m_pIPC_Reg->IPC_V_RATIO;

	// 	For the real interlace mode, the vertical ratio should be used after divided by 2.
	//	Because in the interlace mode, all the IPC output is used for FIMD display
	//	and it should be the same as one field of the progressive mode. 
	//	Therefore the same filter coefficients should be used for the same the final output video.
	//	When half of the interlace V_RATIO is same as the progressive V_RATIO,
	//	the final output video scale is same. (20051104,ishan)

	//Horizontal Y 8tap 
	//Horizontal C 4tap	
	if (uHRatio <= (0x1<<16))			// 720->720 or zoom in
		eHFilter = IPC_PP_H_NORMAL;
	else if (uHRatio <= (0x9<<13))		// 720->640
		eHFilter = IPC_PP_H_8_9 ;
	else if(uHRatio <= (0x1<<17))		// 2->1
		eHFilter = IPC_PP_H_1_2;
	else if(uHRatio <= (0x3<<16))		// 2->1	
		eHFilter = IPC_PP_H_1_3;
	else // 0x800						// 4->1
		eHFilter = IPC_PP_H_1_4;

	// Vertical Y 4tap
	if (uVRatio <= (0x1<<16))			// 720->720 or zoom in
		eVFilter = IPC_PP_V_NORMAL;
	else if (uVRatio <= (0x3<<15))		//*6->5
		eVFilter = IPC_PP_V_5_6;
	else if(uVRatio <= (0x5<<14))		// 4->3
		eVFilter = IPC_PP_V_3_4;
	else if (uVRatio <= (0x1<<17))		// 2->1
		eVFilter = IPC_PP_V_1_2;
	else if (uVRatio <= (0x3<<16))		// 3->1
		eVFilter = IPC_PP_V_1_3;
	else // 0x400						// 4->1
		eVFilter = IPC_PP_V_1_4;

	SetPolyPhaseFilterSet(eHFilter, eVFilter);

}


//////////
// Function Name 	: 	SetPolyPhaseFilterSet
// Description		:	Set the Poly-Phase Filter Coefficients
// Input 			:	eHFilter - refer to IPC_FILTER_H_PP enum
//					eVFilter - refer to IPC_FILTER_V_PP enum
void IPC::SetPolyPhaseFilterSet(IPC_eFILTER_H_PP eHFilter, IPC_eFILTER_V_PP eVFilter)
{
	// Horizontal Y 8-tap
	SetPolyPhaseFilter(&m_pIPC_Reg->IPC_POLY8_Y0_LL, sIpc8tapCoef_Y_H+eHFilter*16*8, 8 );
	// Horizontal C 4-tap
	SetPolyPhaseFilter(&m_pIPC_Reg->IPC_POLY4_C0_LL, sIpc4tapCoef_C_H+eHFilter*16*4, 4 );
	// Vertical Y 4-tap
	SetPolyPhaseFilter(&m_pIPC_Reg->IPC_POLY4_Y0_LL, sIpc4tapCoef_Y_V+eVFilter*16*4, 4 );
}


//////////
// Function Name 	: 	SetPolyPhaseFilter
// Description		:	Set the Poly-Phase Filter Coefficients
// Input 			:	uFilterReg - the Base Address of Poly-Phase Filter Coefficient registers.
//					sFilterCoef - the Base Address of Coefficient value array
//					usTapSz - Tap Size
void IPC::SetPolyPhaseFilter(volatile DWORD *uFilterReg, const BYTE* sFilterCoef, WORD usTapSz)
{
	DWORD i,j;
	BYTE* ucpFilterCoef;
	WORD usTempTapSz;
	volatile DWORD * uFilterBaseAddr;
	
	uFilterBaseAddr = uFilterReg;
	ucpFilterCoef = (BYTE*)sFilterCoef;

	for (i=0; i<usTapSz; i++)
	{
		// VP_POLYx_Y0/1(/2/3)_xx Setting
		usTempTapSz = usTapSz-(WORD)i-1;
		
		for (j=0; j<4; j++)
		{
			// VP_POLYx_Yx_LL/LH/HL/HH Setting
			*uFilterBaseAddr = (DWORD)((ucpFilterCoef[4*j*usTapSz + usTempTapSz] <<24)
				| (ucpFilterCoef[(4*j+1)*usTapSz + usTempTapSz] <<16)
				| (ucpFilterCoef[(4*j+2)*usTapSz + usTempTapSz] <<8) 
				| (ucpFilterCoef[(4*j+3)*usTapSz + usTempTapSz]));
			uFilterBaseAddr ++;
		}
	}
}


