#include "LCDControllerSSD196x.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;
	
	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);
	
	/**
	*Set MN(multipliers) of PLL, VCO = crystal freq * (M+1)
	*PLL freq = VCO/(N+1) with 250MHz < VCO < 800MHz, PLL frequency <110MHz
	*/
	writeCmd(CMD_SET_PLL_MN);		// Set PLL with OSC = 10MHz (hardware)
															// Multiplier M = 35, VCO (>250MHz)= OSC*(M+1), VCO = 360MHz
	writeData(35);
	writeData(2);								// Divider N = 2, PLL = 360/(N+1) = 120MHz
	writeData(0x54);						// Validate M and N values
	
	writeCmd(CMD_PLL_START);		// Start PLL command
	writeData(0x01);						// enable PLL
	
	delayMs(1);									// wait 1ms for PLL start.		

	writeCmd(CMD_PLL_START);		// start PLL again
	writeData(0x03);						// now, use PLL output as system clock
	
	writeCmd(CMD_SOFT_RESET);		//Soft reset. All configuration register reset except 0xE0 to 0xE5
	delayMs(5);
	
	
	//Now switch it back to PLL clock for 180MHz
	RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)!=SET)
		;
	
	/**
	* This is important for the functions writeCmd() and writeData()
	* to insert a min. 13ns for PWcsh time in CS# when SSD196x running 
	* at 120MHz, especially when Compiler optimization applied.
	* PWcsl at 13ns is the min. value.
	* 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 480x272 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.
	*/
	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);
	
#if defined (USE_TY430TFT480272)
	//Set LSHIFT freq, i.e. the DCLK with PLL freq 120MHz set previously
	//Typical DCLK for TY430TFT480272 is 9MHz
	//9MHz = 120MHz*(LCDC_FPR+1)/2^20
	//LCDC_FPR = 78642 (0x13332)
	writeCmd(CMD_SET_PCLK);
	writeData(0x01);
	writeData(0x33);
	writeData(0x32);
#elif defined (USE_TY700TFT800480)
	//Set LSHIFT freq, i.e. the DCLK with PLL freq 120MHz set previously
	//Typical DCLK for TY500TFT800480 is 33MHz
	//30MHz = 120MHz*(LCDC_FPR+1)/2^20
	//LCDC_FPR = 262143 (0x3FFFF)
	writeCmd(CMD_SET_PCLK);
	writeData(0x03);
	writeData(0xff);
	writeData(0xff);
#endif

	//Set panel mode, varies from individual manufacturer
	writeCmd(CMD_SET_PANEL_MODE);
	
#if defined (USE_TY430TFT480272) || defined (USE_TY700TFT800480)
	writeData(0x20);				// set 24-bit for TY430TF480272 4.3" / TY700TFT800480_R3 panel, data latch in rising edge for LSHIFT
	writeData(0x00);				// set Hsync+Vsync mode
	writeData((DISPLAY_WIDTH-1)>>8); //Set panel size
	writeData(DISPLAY_WIDTH-1);
	writeData((DISPLAY_HEIGHT-1)>>8);
	writeData(DISPLAY_HEIGHT-1);
	writeData(0x00);				//RGB sequence
#endif
	
	//Set horizontal period
	writeCmd(CMD_SET_HOR_PERIOD);
	#define HT (DISP_HOR_RESOLUTION+DISP_HOR_PULSE_WIDTH+DISP_HOR_BACK_PORCH+DISP_HOR_FRONT_PORCH)
	writeData((HT-1)>>8);	
	writeData(HT-1);
	#define HPS (DISP_HOR_PULSE_WIDTH+DISP_HOR_BACK_PORCH)
	writeData((HPS-1)>>8);
	writeData(HPS-1);
	writeData(DISP_HOR_PULSE_WIDTH-1);
	writeData(0x00);
	writeData(0x00);
	writeData(0x00);

	//Set vertical period
	writeCmd(CMD_SET_VER_PERIOD);
	#define VT (DISP_VER_PULSE_WIDTH+DISP_VER_BACK_PORCH+DISP_VER_FRONT_PORCH+DISP_VER_RESOLUTION)
	writeData((VT-1)>>8);
	writeData(VT-1);
	#define VSP (DISP_VER_PULSE_WIDTH+DISP_VER_BACK_PORCH)
	writeData((VSP-1)>>8);
	writeData(VSP-1);
	writeData(DISP_VER_PULSE_WIDTH-1);
	writeData(0x00);
	writeData(0x00);

	//Set pixel format, i.e. the bpp
	writeCmd(CMD_SET_PIXEL_FORMAT);
	writeData(0x55); 				// set 16bpp

	//Set pixel data interface
	writeCmd(CMD_SET_DATA_INTERFACE);
	writeData(0x03);				//16-bit(565 format) data for 16bpp PIC32MX only

#if defined (USE_TY430TFT480272) || defined (USE_TY700TFT800480)
	writeCmd(CMD_SET_GPIO_CONF);// Set all GPIOs to output, controlled by host
	writeData(0x0f);				// Set GPIO0 as output
	writeData(0x01);				// GPIO[3:0] used as normal GPIOs

	// LL Reset to LCD
	gpioWrite(LCD_SPENA, 1);
	gpioWrite(LCD_SPCLK, 1);
	gpioWrite(LCD_SPDAT,1);
	gpioWrite(LCD_RESET,1);
	gpioWrite(LCD_RESET,0);
	delayMs(1);
	gpioWrite(LCD_RESET,1);
	
#endif
	
	//Turn on TE pin
	writeCmd(0x35);
	writeData(0x0000);
	
	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 
	writeCmd(CMD_ON_DISPLAY);
	
	//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)
{
	writeCmd(0xBE);				// Set PWM configuration for backlight control

	writeData(0x0E);			// PWMF[7:0] = 2, PWM base freq = PLL/(256*(1+5))/256 = 
												// 300Hz for a PLL freq = 120MHz
	writeData(intensity);	// Set duty cycle, from 0x00 (total pull-down) to 0xFF 
												// (99% pull-up , 255/256)
	writeData(0x01);			// PWM enabled and controlled by host (mcu)
	writeData(0x00);
	writeData(0x00);
	writeData(0x00);
}

/**
* @fn			void LCDController::sleepIn()
* @brief	SSD1963 enters sleep mode. Host must wait 5mS after sending before sending next command
*/
void LCDController::sleepIn()
{
	writeCmd(CMD_ENT_SLEEP);
}

/**
* @fn			void LCDController::sleepOut()
* @brief	SSD1963 exits sleep mode
*/
void LCDController::sleepOut()
{
	writeCmd(CMD_EXIT_SLEEP);
}

/**
* @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)
{
	uint32_t area=(uint32_t)rect.x*rect.y;
	
	LCDController::setRect(rect);

	writeCmd(CMD_WR_MEMSTART);					//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;
	Rect _rect;
	_rect.x=0;
	_rect.y=0;
	_rect.width = DISPLAY_WIDTH;
	_rect.height = DISPLAY_HEIGHT;
	
	LCDController::setRect(_rect);

	_area 	= (uint32_t)DISPLAY_WIDTH*DISPLAY_WIDTH;
	
	//set DCS write command (write command DCS_RAMWR)
	writeCmd(CMD_WR_MEMSTART);					//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 = (uint32_t)rect.width*rect.height;

	LCDController::setRect(rect);
	
	//set DCS write command (write command DCS_RAMWR)
	writeCmd(CMD_WR_MEMSTART);					//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;
	Rect _rect;
	_rect.x=0;
	_rect.y=0;
	_rect.width = DISPLAY_WIDTH;
	_rect.height = DISPLAY_HEIGHT;
	
	LCDController::setRect(_rect);

	_area 	= (uint32_t)DISPLAY_WIDTH*DISPLAY_WIDTH;
	
	//set DCS write command (write command DCS_RAMWR)
	writeCmd(CMD_WR_MEMSTART);					//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;
	}	

}

/**
* @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_InitStructure;
				
	// Enable clock for PA9
	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);	
				
	//Configure EXTI Line9
	EXTI_InitStructure.EXTI_Line = LCD_TE_EXTI_Line; 
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; 
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; 
	EXTI_InitStructure.EXTI_LineCmd = ENABLE; 
	EXTI_Init(&EXTI_InitStructure); 
}

/**
*
*/
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::setRect(const Rect& rect)
{
	uint16_t end;
	
	writeCmd(CMD_SET_COLUMN);
	writeData(rect.x>>8);
	writeData(rect.x);
	end = rect.x + rect.width - 1;
	writeData(end>>8);
	writeData(end);
	
	writeCmd(CMD_SET_PAGE);
	writeData(rect.y>>8);
	writeData(rect.y);
	end = rect.y + rect.height - 1;
	writeData(end>>8);
	writeData(end);
}

/**
* @fn			LCDController::gpioWrite(uint8_t pin, bool state)
* @brief	Set a GPIO pin to state high(1) or low(0)
*					PreCondition: Set the GPIO pin an output prior using this function
* @pararm	uint8_t pin defines the pin to use 
*							LCD_RESET
*							LCD_SPENA
*							LCD_SPCLK
*							LCD_SPDAT
*							
*			 		bool state - 	0 for low
*												1 for high
*/
void LCDController::gpioWrite(uint8_t pin, bool state)
{
	uint8_t _gpioStatus = 0;
	
		if(state==1)
		_gpioStatus = _gpioStatus|pin;
	else
		_gpioStatus = _gpioStatus&(~pin);

	writeCmd(0xBA);				// Set GPIO value
	writeData(_gpioStatus);
}

/**
* @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
        EXTI_ClearITPendingBit(LCD_TE_EXTI_Line);
    }
}


