/*
 * This file is subject to the terms of the GFX License. If a copy of
 * the license was not distributed with this file, you can obtain one at:
 *
 *              http://ugfx.org/license.html
 */

#ifndef _GDISP_LLD_BOARD_H
#define _GDISP_LLD_BOARD_H

/* Avoid naming collisions with CubeHAL. */
#undef Red
#undef Green
#undef Blue

/* HAL drivers needed for configuration. */
#include "stm32f4xx_hal.h"
#include "stm32f4xx_hal_rcc.h"
#include "stm32f4xx_hal_gpio.h"
#include "stm32f4xx_hal_dsi.h"
/* sdram driver provided by ST. */
#include "stm32f469i_discovery_sdram.h"
/* OTM8009A driver provided by ST. */
#include "otm8009a.h"

/** Manually set the LTDC timing. */
#ifndef GFX_LTDC_TIMING_SET
  #define GFX_LTDC_TIMING_SET
#endif

/** Most boards will be revision A. */
#ifndef USE_STM32469I_DISCO_REVA
  #define USE_STM32469I_DISCO_REVA
#endif

/** @brief Panel parameters
 *
 * This panel is a KoD KM-040TMP-02-0621 DSI LCD Display.
 */

static const ltdcConfig driverCfg = {
	800, 480,								// Width, Height (pixels)
	1, 2,									  // Horizontal, Vertical sync (pixels)
	15, 34,									// Horizontal, Vertical back porch (pixels)
	16, 34,									// Horizontal, Vertical front porch (pixels)
	0,										  // Sync flags
	0x000000,								// Clear color (RGB888)

	{										                  // Background layer config
		(LLDCOLOR_TYPE *)SDRAM_DEVICE_ADDR, // Frame buffer address
		800, 480,							              // Width, Height (pixels)
		800 * LTDC_PIXELBYTES,				      // Line pitch (bytes)
		LTDC_PIXELFORMAT,					          // Pixel format
		0, 0,								                // Start pixel position (x, y)
		800, 480,							              // Size of virtual layer (cx, cy)
		LTDC_COLOR_FUCHSIA,					        // Default color (ARGB8888)
		0x980088,							              // Color key (RGB888)
		LTDC_BLEND_FIX1_FIX2,				        // Blending factors
		0,									                // Palette (RGB888, can be NULL)
		0,									                // Palette length
		0xFF,								                // Constant alpha factor
		LTDC_LEF_ENABLE						          // Layer configuration flags
	},

	LTDC_UNUSED_LAYER_CONFIG				      // Foreground layer config
};

/** Display timing setting */
#define KoD_FREQUENCY_DIVIDER 7

/** Global DSI handle to hold DSI parameters. */
DSI_HandleTypeDef dsiHandle;

static GFXINLINE void reset_lcd(GDisplay* g);

/**
 * @brief  Function to intialize the STM32F46i-DISCO board.
 *
 * @param  g: Structure holding display parameters.
 */
static GFXINLINE void init_board(GDisplay *g) {

  // As we are not using multiple displays we set g->board to NULL as we don't use it
  g->board = 0;

#ifdef GFX_LTDC_TIMING_SET
  // KoD LCD clock configuration
  // PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz
  // PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 384 Mhz
  // PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 384/7 = 54.857 Mhz
  // LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_2 = 54.857/2 = 27.429Mhz
  #define STM32_SAISRC_NOCLOCK    (0 << 23)   /**< No clock.                  */
  #define STM32_SAISRC_PLL        (1 << 23)   /**< SAI_CKIN is PLL.           */
  #define STM32_SAIR_DIV2         (0 << 16)   /**< R divided by 2.            */
  #define STM32_SAIR_DIV4         (1 << 16)   /**< R divided by 4.            */
  #define STM32_SAIR_DIV8         (2 << 16)   /**< R divided by 8.            */
  #define STM32_SAIR_DIV16        (3 << 16)   /**< R divided by 16.           */

  #define STM32_PLLSAIN_VALUE                 384
  #define STM32_PLLSAIQ_VALUE                 4
  #define STM32_PLLSAIR_VALUE                 KoD_FREQUENCY_DIVIDER
  #define STM32_PLLSAIR_POST                  STM32_SAIR_DIV2

  RCC->PLLSAICFGR = (STM32_PLLSAIN_VALUE << 6) | (STM32_PLLSAIR_VALUE << 28) | (STM32_PLLSAIQ_VALUE << 24);
  RCC->DCKCFGR = (RCC->DCKCFGR & ~RCC_DCKCFGR_PLLSAIDIVR) | STM32_PLLSAIR_POST;
  RCC->CR |= RCC_CR_PLLSAION;
#endif
    
  __HAL_RCC_DSI_CLK_ENABLE();

  DSI_PLLInitTypeDef  dsiPllInit;
  DSI_CmdCfgTypeDef   dsiCmdMode;
  DSI_LPCmdTypeDef    dsiAPBCmd;
  /* Filling the DSI intialization struct. */
  dsiHandle.Instance                       = DSI;                            // There is only one DSI interface
  dsiHandle.Init.AutomaticClockLaneControl = DSI_AUTO_CLK_LANE_CTRL_ENABLE;  // Automatic clock lane control: powers down the clock lane when not in use
  /* Highest speed = 500MHz. */
  uint16_t laneByteClk_kHz = 62500; /* 500 MHz / 8 = 62.5 MHz = 62500 kHz */
  /* TXEscapeCkdiv = f(LaneByteClk)/15.62 = 4 -> 500MHz/4 = 25MHz datasheet says around 20MHz */
  dsiHandle.Init.TXEscapeCkdiv             = laneByteClk_kHz/15620;          // Low power clock relative to the laneByteClock
  dsiHandle.Init.NumberOfLanes             = DSI_TWO_DATA_LANES;             // Two data lines for the fastest transfer speed
  
  /* Fill in the command mode struct. */
  dsiCmdMode.VirtualChannelID              = 0;                              // The first virtual channel

  /* Select the appropriate color coding. */
#ifdef GDISP_PIXELFORMAT_RGB565
  dsiCmdMode.ColorCoding = DSI_RGB565;
#else
  dsiCmdMode.ColorCoding = DSI_RGB888;
#endif

  dsiCmdMode.CommandSize            = driverCfg.width;             // Amount of pixels sent at once -> one line at a time
  dsiCmdMode.TearingEffectSource    = DSI_TE_EXTERNAL;             // Use pin PJ2
  dsiCmdMode.TearingEffectPolarity  = DSI_TE_RISING_EDGE;
  dsiCmdMode.HSPolarity             = DSI_HSYNC_ACTIVE_HIGH;
  dsiCmdMode.VSPolarity             = DSI_VSYNC_ACTIVE_HIGH;
  dsiCmdMode.DEPolarity             = DSI_DATA_ENABLE_ACTIVE_HIGH;
  dsiCmdMode.VSyncPol               = DSI_VSYNC_FALLING;
  dsiCmdMode.AutomaticRefresh       = DSI_AR_ENABLE;               // Use the automatic refresh mode
  dsiCmdMode.TEAcknowledgeRequest   = DSI_TE_ACKNOWLEDGE_DISABLE;  // Not needed when using TE through GPIO
  
  /* GPIO configuration. */
  GPIO_InitTypeDef  gpioInit;
  /* PH7  LCD_RESET */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  gpioInit.Pin   = GPIO_PIN_7;
  gpioInit.Mode  = GPIO_MODE_OUTPUT_OD;
  gpioInit.Pull  = GPIO_NOPULL;
  gpioInit.Speed = GPIO_SPEED_HIGH;
  HAL_GPIO_Init(GPIOH, &gpioInit);
  
  /* PJ2  DSIHOST_TE */
  __HAL_RCC_GPIOJ_CLK_ENABLE();
  gpioInit.Pin        = GPIO_PIN_2;
  gpioInit.Mode       = GPIO_MODE_AF_PP;
  gpioInit.Alternate  = GPIO_AF13_DSI;
  HAL_GPIO_Init(GPIOJ, &gpioInit);
  
  /* PA3  LCD_BL_CTRL This pin is not physically connected. */
  __HAL_RCC_GPIOA_CLK_ENABLE();
  gpioInit.Pin        = GPIO_PIN_3;
  gpioInit.Mode       = GPIO_MODE_OUTPUT_OD;
  HAL_GPIO_Init(GPIOA, &gpioInit);

  /* Fvco = f(CLKin/IDF) * 2 * NDIV;  fPHI = Fvco/(2*ODF) */
#if !defined(USE_STM32469I_DISCO_REVA)
  /* fPHI = CLKin*62.5;  Fvco = CLKin*125 */
  dsiPllInit.PLLNDIV  = 125;
  dsiPllInit.PLLIDF   = DSI_PLL_IN_DIV2;
#else
  /* fPHI = CLKin*20;   Fvco = CLKin*40 */
  dsiPllInit.PLLNDIV  = 100;
  dsiPllInit.PLLIDF   = DSI_PLL_IN_DIV5;
#endif
  dsiPllInit.PLLODF   = DSI_PLL_OUT_DIV1;

  /* Initialize the DSI peripheral. */
  HAL_DSI_Init(&dsiHandle, &dsiPllInit);
  
  DSI_PHY_TimerTypeDef  PhyTimings;
  /* Configure DSI PHY HS2LP and LP2HS timings. Datasheet says 95ns max */
  PhyTimings.ClockLaneHS2LPTime   = 35;
  PhyTimings.ClockLaneLP2HSTime   = 35;
  PhyTimings.DataLaneHS2LPTime    = 35;
  PhyTimings.DataLaneLP2HSTime    = 35;
  PhyTimings.DataLaneMaxReadTime  = 0;
  PhyTimings.StopWaitTime         = 10;
  HAL_DSI_ConfigPhyTimer(&dsiHandle, &PhyTimings);

  /* Configure adapted command mode. */
  HAL_DSI_ConfigAdaptedCommandMode(&dsiHandle, &dsiCmdMode);  
  
  /* Hardware reset LCD */
  reset_lcd(g);

  /* Initialize the SDRAM */
  BSP_SDRAM_Init();
}

static GFXINLINE void set_backlight(GDisplay* g, uint8_t percent)
{
	(void)g;
	(void)percent;
}

/**
 * @brief  Perform a hardware reset on the LCD.
 *
 * @param  g: Display parameter structure.
 */
static GFXINLINE void reset_lcd(GDisplay* g)
{
  (void)g;
  /* Hardware display reset. */
  HAL_GPIO_WritePin(GPIOH, GPIO_PIN_7, GPIO_PIN_RESET);
  gfxSleepMilliseconds(20);
  HAL_GPIO_WritePin(GPIOH, GPIO_PIN_7, GPIO_PIN_SET);
  gfxSleepMilliseconds(10);
  /* Turn on backlight. */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);
}

/**
 * @brief  Extra initialization that is performed after the LTDC intialization.
 *
 * @param  g: Display paramter structure.
 */
static GFXINLINE void post_init_board(GDisplay* g)
{
  (void)g;
  DSI_LPCmdTypeDef dsiAPBCmd;
  
  /* Enable the DSI host and wrapper after the LTDC initialization
  To avoid any synchronization issue, the DSI shall be started after enabling the LTDC */
  HAL_DSI_Start(&dsiHandle);

  /* The configuration commands for the LCD have to be send through the dsiAPBCmd
  * interface as the adapted command mode only supports DCS, WMS and WMC commands. */
  dsiAPBCmd.LPGenShortWriteNoP    = DSI_LP_GSW0P_ENABLE;  // Put everything in low power mode
  dsiAPBCmd.LPGenShortWriteOneP   = DSI_LP_GSW1P_ENABLE;
  dsiAPBCmd.LPGenShortWriteTwoP   = DSI_LP_GSW2P_ENABLE;
  dsiAPBCmd.LPGenShortReadNoP     = DSI_LP_GSR0P_ENABLE;
  dsiAPBCmd.LPGenShortReadOneP    = DSI_LP_GSR1P_ENABLE;
  dsiAPBCmd.LPGenShortReadTwoP    = DSI_LP_GSR2P_ENABLE;
  dsiAPBCmd.LPGenLongWrite        = DSI_LP_GLW_ENABLE;
  dsiAPBCmd.LPDcsShortWriteNoP    = DSI_LP_DSW0P_ENABLE;
  dsiAPBCmd.LPDcsShortWriteOneP   = DSI_LP_DSW1P_ENABLE;
  dsiAPBCmd.LPDcsShortReadNoP     = DSI_LP_DSR0P_ENABLE;
  dsiAPBCmd.LPDcsLongWrite        = DSI_LP_DLW_ENABLE;
  HAL_DSI_ConfigCommand(&dsiHandle, &dsiAPBCmd);
  
  /* Configure the LCD. */
#ifdef GDISP_PIXELFORMAT_RGB565
  OTM8009A_Init(OTM8009A_FORMAT_RBG565, 1);
#else
  OTM8009A_Init(OTM8009A_FORMAT_RGB888, 1);
#endif

  /* Enable the tearing effect line. */
  HAL_DSI_ShortWrite(&dsiHandle, 0, DSI_DCS_SHORT_PKT_WRITE_P1, DSI_SET_TEAR_ON, 0);  // Only V-Blanking info
  
  /* Disable the APB command mode again to go into adapted command mode. (going into high speed mode) */
  dsiAPBCmd.LPGenShortWriteNoP    = DSI_LP_GSW0P_DISABLE;
  dsiAPBCmd.LPGenShortWriteOneP   = DSI_LP_GSW1P_DISABLE;
  dsiAPBCmd.LPGenShortWriteTwoP   = DSI_LP_GSW2P_DISABLE;
  dsiAPBCmd.LPGenShortReadNoP     = DSI_LP_GSR0P_DISABLE;
  dsiAPBCmd.LPGenShortReadOneP    = DSI_LP_GSR1P_DISABLE;
  dsiAPBCmd.LPGenShortReadTwoP    = DSI_LP_GSR2P_DISABLE;
  dsiAPBCmd.LPGenLongWrite        = DSI_LP_GLW_DISABLE;
  dsiAPBCmd.LPDcsShortWriteNoP    = DSI_LP_DSW0P_DISABLE;
  dsiAPBCmd.LPDcsShortWriteOneP   = DSI_LP_DSW1P_DISABLE;
  dsiAPBCmd.LPDcsShortReadNoP     = DSI_LP_DSR0P_DISABLE;
  dsiAPBCmd.LPDcsLongWrite        = DSI_LP_DLW_DISABLE;
  HAL_DSI_ConfigCommand(&dsiHandle, &dsiAPBCmd);
    
  HAL_DSI_Refresh(&dsiHandle);
}

/**
  * @brief  DCS or Generic short/long write command
  * @param  NbrParams: Number of parameters. It indicates the write command mode:
  *                 If inferior to 2, a long write command is performed else short.
  * @param  pParams: Pointer to parameter values table.
  * @retval HAL status
  */
void DSI_IO_WriteCmd(uint32_t NbrParams, uint8_t *pParams)
{
  if(NbrParams <= 1)
  {
   HAL_DSI_ShortWrite(&dsiHandle, 0, DSI_DCS_SHORT_PKT_WRITE_P1, pParams[0], pParams[1]);
  }
  else
  {
   HAL_DSI_LongWrite(&dsiHandle,  0, DSI_DCS_LONG_PKT_WRITE, NbrParams, pParams[NbrParams], pParams);
  }
}

/**
 * @brief  Delay function for the OTM8009A driver.
 *
 * @param  Delay: The requested delay in ms.
 */
void OTM8009A_IO_Delay(uint32_t Delay)
{
  gfxSleepMilliseconds(Delay);
}

#endif /* _GDISP_LLD_BOARD_H */
