#include "LCDControllerS6D04D2.hpp"
#include <touchgfx/hal/OSWrappers.hpp>

#define Swap(x) (((uint16_t)x<<8) | ((uint16_t)x>>8))

/**
* @fn 		void LCDController::init()
* @brief 	Initialization function for LCD controller S6D04D2 via 
* 				MIPI Controller SSD2805
*/
void LCDController::init()
{
	FMC_NORSRAMInitTypeDef hsram1;
	FMC_NORSRAMTimingInitTypeDef	hsram1_timing;
	STM32F4FMC fmc;
	
	//initialize FMC module for 8080 16-bit interface
	fmc.init();
	
	//Configure GPIO reset line and set default high
	GPIO_InitTypeDef GPIO_InitStruct;

	//VDDIO control for SSD2805 (RELEASE_3 valid), enable VDDIO for SSD2805
	#ifdef RELEASE_3
	GPIO_InitStruct.GPIO_Pin = LCD_VDDIOCTRL_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;	//output push-pull mode
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_LCD_VDDIOCTRL, ENABLE);
	GPIO_Init(LCD_VDDIOCTRL_BASE, &GPIO_InitStruct);
	GPIO_WriteBit(LCD_VDDIOCTRL_BASE, LCD_VDDIOCTRL_PIN, Bit_RESET);		
	#endif
	
	GPIO_InitStruct.GPIO_Pin = LCD_RST_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;	//output push-pull mode
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_LCD_RST, ENABLE);
	GPIO_Init(LCD_RST_BASE, &GPIO_InitStruct);
	GPIO_WriteBit(LCD_RST_BASE, LCD_RST_PIN, Bit_SET);	
	

	//Configure GPIO backlight control and set default low 
	GPIO_InitStruct.GPIO_Pin = LCD_BACKLIGHT_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;	//output push-pull mode
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_LCD_BACKLIGHT, ENABLE);
	GPIO_Init(LCD_BACKLIGHT_BASE, &GPIO_InitStruct);
	GPIO_WriteBit(LCD_BACKLIGHT_BASE, LCD_BACKLIGHT_PIN, Bit_RESET);	
	
	
	//low level reset
	hardwareReset(1);
	delayMs(300);
	hardwareReset(0);
	delayMs(10);
	hardwareReset(1);
	delayMs(10);				//This delay is necessary.
	
	/**
	* Set PLL. Before PLL of SSD2805 is started, the min WRX period 
	* is 6T with T=1/20MHz with an external TX_CLK = 20MHz OSC onboard. 
	* The min peiod for WRX = 300ns.
	* RCC set for HSI RC @16MHz to output such long WRX strobe pulse
	* with original OSC and CLK configuration saved
	*/
	RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI);
	while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY)!=SET)
		;
	
	hsram1.FMC_Bank 							= FMC_Bank1_NORSRAM2;
	hsram1.FMC_DataAddressMux 		= FMC_DataAddressMux_Disable;
	hsram1.FMC_MemoryType 				= FMC_MemoryType_SRAM ;
	hsram1.FMC_MemoryDataWidth 		= FMC_NORSRAM_MemoryDataWidth_16b;
	hsram1.FMC_BurstAccessMode 		= FMC_BurstAccessMode_Disable;
	hsram1.FMC_WaitSignalPolarity = FMC_WaitSignalPolarity_Low ;
	hsram1.FMC_WrapMode 					= FMC_WrapMode_Disable;
	hsram1.FMC_WaitSignalActive 	= FMC_WaitSignalActive_BeforeWaitState;
	hsram1.FMC_WriteOperation 		= FMC_WriteOperation_Enable;
	hsram1.FMC_WaitSignal 				= FMC_WaitSignal_Disable;
	hsram1.FMC_ExtendedMode 			= FMC_ExtendedMode_Disable;
	hsram1.FMC_AsynchronousWait 	= FMC_AsynchronousWait_Disable;
	hsram1.FMC_WriteBurst 				= FMC_WriteBurst_Disable ;
	hsram1.FMC_ContinousClock 		= FMC_CClock_SyncOnly;	
	
	/**
	*Timing measured in HCLK with MCU's SYSCLK=16MHz.
	*Before PLL of SSD2805 has been started, the min WRX period 
	*is 6T with T=1/20MHz. Therefore the min peiod for WRX = 300ns 
	*with an external TX_CLK = 20MHz OSC onboard	
	*/
	hsram1_timing.FMC_AccessMode = FMC_AccessMode_A;
	hsram1_timing.FMC_AddressHoldTime = 0;
	hsram1_timing.FMC_AddressSetupTime = 5;
	hsram1_timing.FMC_DataSetupTime = 5;
	hsram1_timing.FMC_BusTurnAroundDuration = 0;
	
	hsram1.FMC_ReadWriteTimingStruct = &hsram1_timing;	//It is FMC_ReadWriteTimingStruct if the Extended Mode is not used
	
	FMC_NORSRAMInit(&hsram1);
	FMC_NORSRAMCmd(LCD_FMC_BANK, ENABLE);
	
	/**
  *PLL 	= clock*MUL/(PDIV*DIV)
  *			= clock*(BAh[7:0]+1)/((BAh[15:12]+1)*(BAh[11:8]+1))
  *			= 20*(0x0f+1)/1*1 = 20*16 = 320MHz
  *Remark: 350MHz >= fvco >= 225MHz for SSD2805 because the max. speed per lane is 350Mbps
  *Set PLL by writing a value 0x000f to MCS register at 0xBA
	*/	
	writeReg(MCS_PLCR, 0x000F);
	writeReg(MCS_PCR,  0x0001);	//enable PLL
	delayMs(2);									//wait 2ms for PLL start.		

	//Now switch it back to PLL clock for 180MHz
	RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)!=SET)
		;

	/**
	* 
	* Now it is safe to set FMC running at higher speed.
	* Set clock control register for SYS_CLK & LP clock speed
	* SYS_CLK = TX_CLK/(BBh[7:6]+1), TX_CLK = external oscillator clock speed
	* In this case, SYS_CLK = 20MHz/(1+1)=10MHz. Measure SYS_CLK pin to verify it.
	* LP clock = PLL/(8*(BBh[5:0]+1)) = 320/(8*(4+1)) = 8MHz, conform to LG panel's spec, default LP = 8Mbps
	* S6D04D2 is the controller of LG 1.54" panel.
	*/
	
	/**
	* This is important for the func writeReg(uint8_t reg, uint16_t data)
	* to insert a min. 9.4ns for PWcsh time in WR# when SSD2805 running 
	* at 320MHz, especially when Compiler optimization applied.
	* PWcsl at 9.4ns is the min. for SSD2805 at 320MHz PLL clock (3*T with T=3.125ns),
	* PWcsh also min. at 9.4ns.
	* For now, AddressSetupTime set 4 with PWcsh set 22ns & PWcsl set 28ns for a stable result when
	* a while loop is implemented for writing constant word e.g. while(area--){writeData(0xff00);}.
	* Refresh time for a complete frame with 16-bit addressing for 240x240 pixels is calculated as :
	* Time = 240*240*(22+28)ns = 2.88ms. However, when it is required to transfer data from a frame buffer
	* like SDRAM, the looping time could be longer and hence these parameters could be further reduced
	* for an even higher optimization.
	*/
	/**
	* Remarks for internal SRAM as frame buffer
	* It has been demonstrated that when MCU's internal SRAM is used as the frame buffer, a slightly longer data and 
	* address setup time are required. These parameters will give a stable display for SRAM
	* hsram1_timing.FMC_DataSetupTime = 7;		
	*	hsram1_timing.FMC_AddressSetupTime = 6;	
	*/
	
	hsram1_timing.FMC_DataSetupTime = 5;		//5*HCLK = 5*(1/180) us = 28ns (PWcsl)
	hsram1_timing.FMC_AddressSetupTime = 4;	//4*HCLK=22ns	(PWcsh)
	//hsram1_timing.FMC_DataSetupTime = 2;	//2*HCLK = 12ns

	FMC_NORSRAMInit(&hsram1);
	FMC_NORSRAMCmd(LCD_FMC_BANK, ENABLE);
	
	writeReg(MCS_CCR, 0x0044);					//Set LP Clock here with divider = 5, i.e. LP clock = PLL/(8*(4+1)) = 8MHz
																			//SYSD_CLK divider set to 01 for SYS_CLK=TX_CLK/2=10MHz
																			//output sys_clk for debug. Now check sys_clk pin for 10MHz signal
	writeReg(MCS_TR,  0x0100);					//Enable SYS_CLK output with END and CO set 0 for MIPI lane data
																			//endianness and color order set here
	delayMs(100);												//Don't know why, this long delay is required.

	/**
	* Set MIPI packet format.
	* EOT packet enable, write operation, it is a DCS packet
	* HS clock is disabled, video mode disabled, in HS mode to send data
	*/
	writeReg(MCS_CFGR, 0x0243);			
	writeReg(MCS_PSCR3,0x0400);			//MCS_PSCR3:Packet size threshold, min=2, max=0x400
	//writeReg(MCS_PSCR3,240);			//MCS_PSCR3:Packet size threshold, min=2, max=0x400
	
	//Set Virtual Channel (VC) to use
	writeReg(MCS_VCR, 0x0000);

	//Now write DCS command to LG panel for system power-on upon reset
	writeTDCSize(0);
	writeCmd(DCS_SLPOUT);								//DCS sleep-out command	    	
	
	delayMs(100);												//wait for LG panel after sleep out	
	
	//Now configuration parameters sent to LG
  writeTDCSize(1);											//define TDC size to be 1
  writeReg(DCS_COLMOD, 0x0005);         //DCS command to set pixel format for 16-bit RGB

	//Turn on BSYNC pin (like TE), set to 5.6ms low pulse for frame update interval
	//Frame rate is 50Hz
	writeTDCSize(2);
	writeReg(0xF1, 0x5A5A);								//unlock MCS
	writeTDCSize(12);
	writeCmd(0xF2);
	writeData(0xF000);	//2nd param 0xF0, 1st param 0x00
	writeData(0x5603);	//4th param 0x56 (VBP), 3rd param 0x03
	writeData(0x0008);	//6th param 0x00, 5th param 0x08 (VFP)
	writeData(0x0001);	//8th param 0x00, 7th param 0x01 (TE_OUT_EN)
	writeData(0x0000);	//10th param 0x00, 9th param 0x00
	writeData(0x0156);	//12nd param 0x01 (BSYNC_POL active low), 11th param 0x56 (11th param=TE_ED[7:0] set the pulse width of TE)
	writeTDCSize(2);
	writeReg(0xF1, 0xA5A5);								//lock MCS
	/**
	* Frame frequency = INCLK (4MHz) / ((DisplayLines + VFP+VBP)*NHW)
	* = 4000000/((240+0x56+8)*240) 
	* = 4000000/80160 = 49.9Hz. Frame rate = 50Hz
	*/
	
	Rect rect;
	rect.x = 0;
	rect.y = 0;
	rect.width = DISPLAY_WIDTH;
	rect.height = DISPLAY_HEIGHT;	
	//direct write to display bypass a frame buffer to clear display in black
	fillRectDirect(rect, 0);
	
	//Turn on Display 
	writeTDCSize(0);	  
	writeCmd(DCS_DISPON);									//display ON DCS command to LG panel
	delayMs(1);	
	
	//backlight in full brightness
	backlightSet(255);		
}

/**
* @fn			LCDController::hardwareReset(bool state)
* @brief	hardware reset by toggle GPIO pin wired to RESET line of D2805
* @param	state : 1 for normal operation
*									0 for reset
*/
void LCDController::hardwareReset(bool state)
{
	if(state)
	{
		GPIO_WriteBit(LCD_RST_BASE, LCD_RST_PIN, Bit_SET);
	}
	else
	{
		GPIO_WriteBit(LCD_RST_BASE, LCD_RST_PIN, Bit_RESET);
	}
}

/**
* @fn			LCDController:: backlightSet(uint8_t intensity)
* @brief 	Set backlight intensity with this function. Only 2 states supported for now
*				 	On/Off
*	@param 	intensity > 0 (ON)
*									  = 0 (OFF)
*/
void LCDController:: backlightSet(uint8_t intensity)
{
	if(intensity)
	{
		GPIO_WriteBit(LCD_BACKLIGHT_BASE, LCD_BACKLIGHT_PIN, Bit_SET);
	}
	else
	{
		GPIO_WriteBit(LCD_BACKLIGHT_BASE, LCD_BACKLIGHT_PIN, Bit_RESET);
	}
}

/**
* @fn			LCDController::fillRectDirect(const Rect& rect, uint16_t color)
* @brief 	Fill LCD with a pure color bypassing frame buffer.
*					It is a direct draw.
* @param 	const Rect& rect is a reference to rectangle structure
*					uint16_t color indicates the color
*/
void LCDController::fillRectDirect(const Rect& rect, uint16_t color)
{
	uint16_t ec, ep;
	uint32_t area;
	
	writeTDCSize(4);
	writeCmd(DCS_CASET);				//column address set
	writeData(Swap(rect.x));		//set start column; swapping hibyte:lowbyte required
	ec = rect.x + rect.width - 1;
	writeData(Swap(ec));				//set end column

	writeCmd(DCS_PASET);				//page address set
	writeData(Swap(rect.y));		//set start page	
	ep = rect.y + rect.height - 1;
	writeData(Swap(ep));				//set end page
	
	area = (uint32_t)rect.width * rect.height;
	
	writeTDCSize(area*2);	//count in bytes for TDC size
	//set DCS write command (write command DCS_RAMWR)
	writeCmd(DCS_RAMWR);					//8080-IF with DCS_RAMWR to start writing to SRAM of SSD2805
	
	while(area--)
	{
		writeData(color);
	}
}

/**
*
*/
void LCDController::flushFrameBuffer(const Rect& rect)
{

		__IO uint32_t write_pointer = 0;
	
	uint32_t _area;
	uint16_t ec, ep;						//ec=end column, ep=end page
	
	writeTDCSize(4);
	writeCmd(DCS_CASET);				//column address set
	writeData(0);								//set SC; swapping hibyte:lowbyte required

	ec = DISPLAY_WIDTH-1;
	writeData(Swap(ec));				//set EC
	
	writeCmd(DCS_PASET);				//page address set
	writeData(0);								//set SP
	
	ep = DISPLAY_HEIGHT-1;
	writeData(Swap(ep));	//set EP

	_area 	= (uint32_t)DISPLAY_WIDTH*DISPLAY_WIDTH;

	writeTDCSize(_area*2);				//SSD2805 specific, TDC Size in byte
	
	//set DCS write command (write command DCS_RAMWR)
	writeCmd(DCS_RAMWR);					//8080-IF with DCS_RAMWR to start writing to SRAM of SSD2805
	
	while(_area--)
	{
		writeData(*(__IO uint16_t *)(FRAME_BUFFER_START + write_pointer));
		write_pointer+=2;
	}	

/*
	__IO uint32_t write_pointer = (uint32_t)(DISPLAY_WIDTH*rect.y) + rect.x;
	
	uint32_t _area;
	uint16_t ec, ep;						//ec=end column, ep=end page
	
	writeTDCSize(4);
	writeCmd(DCS_CASET);				//column address set
	writeData(Swap(rect.x));		//set SC; swapping hibyte:lowbyte required

	ec = rect.x + rect.width -1;
	writeData(Swap(ec));				//set EC
	
	writeCmd(DCS_PASET);				//page address set
	writeData(Swap(rect.y));		//set SP
	
	ep = rect.y + rect.height - 1;
	writeData(Swap(ep));	//set EP

	_area 	= (uint32_t)rect.width*rect.height;

	writeTDCSize(_area*2);				//SSD2805 specific, count in bytes
	writeReg(MCS_PSCR3, _area);
	
	//set DCS write command (write command DCS_RAMWR)
	writeCmd(DCS_RAMWR);					//8080-IF with DCS_RAMWR to start writing to SRAM of SSD2805
	
	while(_area--)
	{
		writeData(*(__IO uint16_t *)(0xD0000000 + write_pointer));
		write_pointer+=2;
	}	
*/
}

/**
*
*/
void LCDController::flushFrameBuffer()
{
	__IO uint32_t write_pointer = 0;
	
	uint32_t _area;
	uint16_t ec, ep;						//ec=end column, ep=end page
	
	writeTDCSize(4);
	writeCmd(DCS_CASET);				//column address set
	writeData(0);								//set SC; swapping hibyte:lowbyte required

	ec = DISPLAY_WIDTH-1;
	writeData(Swap(ec));				//set EC
	
	writeCmd(DCS_PASET);				//page address set
	writeData(0);								//set SP
	
	ep = DISPLAY_HEIGHT-1;
	writeData(Swap(ep));	//set EP

	_area 	= (uint32_t)DISPLAY_WIDTH*DISPLAY_WIDTH;

	writeTDCSize(_area*2);				//SSD2805 specific, TDC Size in byte
	
	//set DCS write command (write command DCS_RAMWR)
	writeCmd(DCS_RAMWR);					//8080-IF with DCS_RAMWR to start writing to SRAM of SSD2805
	
	while(_area--)
	{
		writeData(*(__IO uint16_t *)(FRAME_BUFFER_START + write_pointer));
		write_pointer+=2;
	}	
	
	/*
	Rect rect;
	
	rect.x=0;
	rect.y=0;
	rect.width = DISPLAY_WIDTH;
	rect.height = DISPLAY_HEIGHT;
	
	LCDController::flushFrameBuffer(rect);
	*/
}

/**
* @fn			void disableInterrupts()
* @brief	This function disable interrupt for TE pin edge detect like Vsync
*/
void LCDController::disableInterrupts()
{
	NVIC_DisableIRQ(LCD_TE_IRQn);
}


/**
* @fn			void LCDController::enableInterrupts()
* @brief	This function enables interrupt for TE pin edge detect like Vsync
*/
void LCDController::enableInterrupts()
{
	NVIC_EnableIRQ(LCD_TE_IRQn);
}

/**
* @fn			void LCDController::configureInterrupts()
* @brief	This function configures interrupt priority for TE pin detect
*/
void LCDController::configureInterrupts()
{
	NVIC_SetPriority(LCD_TE_IRQn, 9);
}

/**
*
*/
void LCDController::enableLCDControllerInterrupt()
{
	//enable EXTI irq for TE pin simulate a VSYNC
	GPIO_InitTypeDef 	GPIO_InitStruct;
	EXTI_InitTypeDef	EXTI_InitStruct;
	//NVIC_InitTypeDef	NVIC_InitStruct;
	
	// Enable clock for TE interrupt input
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_TE, ENABLE);
	// Enable clock for SYSCFG
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

	GPIO_InitStruct.GPIO_Pin = LCD_TE_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;	//input mode
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;	//weak pullup enable
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(LCD_TE_BASE, &GPIO_InitStruct);	
	
	// Connect EXTI line to TE pin
	SYSCFG_EXTILineConfig(LCD_TE_PortSourceGPIO, LCD_TE_PinSource);
	
	//Configure EXTI linex
	EXTI_InitStruct.EXTI_Line = LCD_TE_EXTI_Line; 
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; 
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; 
	EXTI_InitStruct.EXTI_LineCmd = ENABLE; 
	EXTI_Init(&EXTI_InitStruct); 
	
	/**
	//These are not necessary as NVIC is configured by 
	//LCDController::configureInterrupts() & LCDController::enableInterrupts()
	NVIC_InitStruct.NVIC_IRQChannel = EXTI4_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x09;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x00;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);
	*/
}

/**
*
*/
uint16_t*  LCDController::getTFTFrameBuffer() const
{
	return (uint16_t*)FRAME_BUFFER_START;	
}

/**
*
*/
void LCDController::writeReg(uint8_t reg, uint16_t data)
{
	writeCmd(reg);
	//insert wait here for slow clock & 320MHz, this is set with Timing.AddressSetupTime = 1 with FMC init
	//because address hold time = 1 HCLK. Especially important when MCU running at 168MHz (1 HCLK = 5.9ns!)
	writeData(data);
}

/**
*
*/
void LCDController::writeTDCSize(uint32_t byteSize)
{
	writeReg(MCS_PSCR1, (uint16_t)byteSize);
	writeReg(MCS_PSCR2, (uint16_t)(byteSize>>16));
}

/**
* @fn			void LCDController::delayMs(__IO uint32_t ms)
* @brief 	Software delay tuned for only 180MHz system clock
* @param	ms		delay in milli seconds
*/
void LCDController::delayMs(__IO uint32_t ms)
{
  __IO uint32_t index = 0; 
  for(index = (45000 * ms); index != 0; index--)
  {
		;
  }
}

/**
*
*/
extern "C"
__irq void LCD_EXTI_IRQHandler(void)
{
	  //Make sure that interrupt flag is set
    if (EXTI_GetITStatus(LCD_TE_EXTI_Line) != RESET) {  
				OSWrappers::signalVSync();
        //Clear interrupt flag
				//GPIO_ToggleBits(GPIOG, GPIO_Pin_2);
        EXTI_ClearITPendingBit(LCD_TE_EXTI_Line);
    }
}


