/*******************************************************************************
*
* E M B E D D E D   W I Z A R D   P R O J E C T
*
*                                                Copyright (c) TARA Systems GmbH
*                                    written by Paul Banach and Manfred Schweyer
*
********************************************************************************
*
* This software is delivered "as is" and shows the usage of other software
* components. It is provided as an example software which is intended to be
* modified and extended according to particular requirements.
*
* TARA Systems hereby disclaims all warranties and conditions with regard to the
* software, including all implied warranties and conditions of merchantability
* and non-infringement of any third party IPR or other rights which may result
* from the use or the inability to use the software.
*
********************************************************************************
*
* DESCRIPTION:
*   This file is part of the interface (glue layer) between an Embedded Wizard
*   generated UI application and the board support package (BSP) of a dedicated
*   target.
*   This template is responsible to initialize the display hardware of the board
*   and to provide the necessary access to update the display content.
*
*******************************************************************************/

#include "fsl_lcdc.h"
#include "fsl_sctimer.h"

#include "ewconfig.h"
#include "ewrte.h"
#include "ewgfx.h"
#include "ewextgfx.h"
#include "ewgfxdefs.h"

#include "ew_bsp_display.h"
#include "ew_bsp_clock.h"

#if EW_USE_FREE_RTOS == 1

  #include "FreeRTOS.h"
  #include "task.h"
  #include "semphr.h"

  static SemaphoreHandle_t      LcdUpdateSemaphore;

#endif

#if EW_USE_DOUBLE_BUFFER == 1

  static volatile uint32_t      PendingFramebuffer = 0;

#else

  static volatile unsigned long VSyncTime          = 0;
  static volatile unsigned long VSyncDuration      = 0;

#endif

/* LCD */
#define APP_LCD LCD
#define LCD_PANEL_CLK 9000000
#define LCD_HSW 2
#define LCD_HFP 8
#define LCD_HBP 43
#define LCD_VSW 10
#define LCD_VFP 4
#define LCD_VBP 12
#define APP_LCD_IRQHandler LCD_IRQHandler
#define APP_LCD_IRQn LCD_IRQn


void LCD_IRQHandler( void )
{
  uint32_t intStatus;

  intStatus = LCDC_GetEnabledInterruptsPendingStatus( LCD );
  LCDC_ClearInterruptsStatus( LCD, intStatus );

  if ( intStatus & kLCDC_VerticalCompareInterrupt )
  {
    #if EW_USE_DOUBLE_BUFFER == 1

      if ( PendingFramebuffer )
      {
        #if EW_USE_FREE_RTOS == 1

          xSemaphoreGiveFromISR( LcdUpdateSemaphore, NULL );

        #endif

        PendingFramebuffer = 0;
      }

    #else

      unsigned long curTicks = DWT->CYCCNT;
      VSyncDuration = curTicks - VSyncTime;
      VSyncTime = curTicks;

    #endif
  }
  __DSB();
}


/* Helper function to sync on a certain line number updated by the display.
   This function is necessary when the system is used in single framebuffer mode. */
#if EW_USE_DOUBLE_BUFFER == 0
static void SyncOnLine( int aLine )
{
  unsigned long ticksTillRequestedLine;
  unsigned long localVSyncTime;

  if ( aLine == 0 )
    aLine += FRAME_BUFFER_HEIGHT;

  /* calculate time tick that corresponds to requested line */
  ticksTillRequestedLine = VSyncDuration * aLine / FRAME_BUFFER_HEIGHT;

  CPU_LOAD_SET_IDLE();

  /* if requested line is missed, wait for next display frame (next V-sync) */
  while (( DWT->CYCCNT - VSyncTime ) > ticksTillRequestedLine )
    ;

  /* wait for requested line in current display frame */
  localVSyncTime = VSyncTime;
  while (( DWT->CYCCNT - localVSyncTime ) < ticksTillRequestedLine )
    ;

  CPU_LOAD_SET_ACTIVE();
}
#endif


/*******************************************************************************
* FUNCTION:
*   EwBspDisplayInit
*
* DESCRIPTION:
*   The function EwBspDisplayInit initializes the display hardware and returns
*   the display parameter.
*
* ARGUMENTS:
*   aDisplayInfo - Display info data structure.
*
* RETURN VALUE:
*   Returns 1 if successful, 0 otherwise.
*
*******************************************************************************/
int EwBspDisplayInit( XDisplayInfo* aDisplayInfo )
{
  sctimer_config_t config;
  sctimer_pwm_signal_param_t pwmParam;
  uint32_t event;
  lcdc_config_t lcdConfig;

  /* Route Main clock to LCD. */
  CLOCK_AttachClk(kMAIN_CLK_to_LCD_CLK);
  CLOCK_SetClkDiv(kCLOCK_DivLcdClk, 1, true);

  /* Init back light pwm */
  CLOCK_AttachClk(kMAIN_CLK_to_SCT_CLK);
  CLOCK_SetClkDiv(kCLOCK_DivSctClk, 2, true);
  SCTIMER_GetDefaultConfig(&config);
  SCTIMER_Init(SCT0, &config);

  pwmParam.output = kSCTIMER_Out_5;
  pwmParam.level = kSCTIMER_HighTrue;
  pwmParam.dutyCyclePercent = 5;

  SCTIMER_SetupPwm(SCT0, &pwmParam, kSCTIMER_CenterAlignedPwm, 1000U, CLOCK_GetSctClkFreq(), &event);

  #if EW_USE_FREE_RTOS == 1

    /* create semaphore for handling double-buffering on V-sync */
    LcdUpdateSemaphore = xSemaphoreCreateBinary();
    xSemaphoreGive( LcdUpdateSemaphore );

  #endif

  LCDC_GetDefaultConfig(&lcdConfig);

  lcdConfig.panelClock_Hz = LCD_PANEL_CLK;
  lcdConfig.ppl = FRAME_BUFFER_WIDTH;
  lcdConfig.hsw = LCD_HSW;
  lcdConfig.hfp = LCD_HFP;
  lcdConfig.hbp = LCD_HBP;
  lcdConfig.lpp = FRAME_BUFFER_HEIGHT;
  lcdConfig.vsw = LCD_VSW;
  lcdConfig.vfp = LCD_VFP;
  lcdConfig.vbp = LCD_VBP;
  lcdConfig.polarityFlags = kLCDC_InvertVsyncPolarity | kLCDC_InvertHsyncPolarity;
  lcdConfig.upperPanelAddr = (uint32_t)FRAME_BUFFER_ADDR;
  lcdConfig.display = kLCDC_DisplayTFT;
  lcdConfig.swapRedBlue = true;

  #if ( EW_FRAME_BUFFER_COLOR_FORMAT == EW_FRAME_BUFFER_COLOR_FORMAT_RGBA8888 )
    lcdConfig.bpp = kLCDC_24BPP;
  #elif ( EW_FRAME_BUFFER_COLOR_FORMAT == EW_FRAME_BUFFER_COLOR_FORMAT_RGB888 )
    lcdConfig.bpp = kLCDC_24BPP;
  #elif ( EW_FRAME_BUFFER_COLOR_FORMAT == EW_FRAME_BUFFER_COLOR_FORMAT_RGB565 )
    lcdConfig.bpp = kLCDC_16BPP565;
  #elif ( EW_FRAME_BUFFER_COLOR_FORMAT == EW_FRAME_BUFFER_COLOR_FORMAT_Index8 )
    lcdConfig.bpp = kLCDC_8BPP;
  #elif ( EW_FRAME_BUFFER_COLOR_FORMAT == EW_FRAME_BUFFER_COLOR_FORMAT_LumA44 )
    lcdConfig.bpp = kLCDC_8BPP;
  #endif

  LCDC_Init(LCD, &lcdConfig, CLOCK_GetLcdClkFreq());

#if EW_USE_DOUBLE_BUFFER == 1

  /* Trigger interrupt at start of every vertical back porch. */
  LCDC_SetVerticalInterruptMode(LCD, kLCDC_StartOfFrontPorch);

#else

  /* Trigger interrupt at start of active video. */
  LCDC_SetVerticalInterruptMode(LCD, kLCDC_StartOfActiveVideo);

#endif

  LCDC_EnableInterrupts(LCD, kLCDC_VerticalCompareInterrupt);

  /* lower interrupt priority needed with FreeRTOS */
  NVIC_SetPriority(APP_LCD_IRQn, 10);
  NVIC_EnableIRQ(APP_LCD_IRQn);

  LCDC_Start(LCD);
  LCDC_PowerUp(LCD);

  /* return the current display configuration */
  if ( aDisplayInfo )
  {
    memset( aDisplayInfo, 0, sizeof( XDisplayInfo ));
    aDisplayInfo->FrameBuffer   = (void*)FRAME_BUFFER_ADDR;
    aDisplayInfo->DoubleBuffer  = (void*)DOUBLE_BUFFER_ADDR;
    aDisplayInfo->BufferWidth   = FRAME_BUFFER_WIDTH;
    aDisplayInfo->BufferHeight  = FRAME_BUFFER_HEIGHT;
    aDisplayInfo->DisplayWidth  = FRAME_BUFFER_WIDTH;
    aDisplayInfo->DisplayHeight = FRAME_BUFFER_HEIGHT;

    #if EW_USE_DOUBLE_BUFFER == 1
      aDisplayInfo->UpdateMode  = EW_BSP_DISPLAY_UPDATE_NORMAL;
    #else
      aDisplayInfo->UpdateMode  = EW_BSP_DISPLAY_UPDATE_PARTIAL;
    #endif
  }
  return 1;
}


/*******************************************************************************
* FUNCTION:
*   EwBspDisplayDone
*
* DESCRIPTION:
*   The function EwBspDisplayDone deinitializes the display hardware.
*
* ARGUMENTS:
*   None
*
* RETURN VALUE:
*   None
*
*******************************************************************************/
void EwBspDisplayDone( void )
{
}


/*******************************************************************************
* FUNCTION:
*   EwBspDisplayGetUpdateArea
*
* DESCRIPTION:
*   The function EwBspDisplayGetUpdateArea returns the next update area
*   depending on the selected display mode:
*   In case of a synchroneous single-buffer, the function has to return the
*   the rectangular areas that correspond to the horizontal stripes (fields)
*   of the framebuffer.
*   In case of a scratch-pad buffer, the function has to return the subareas
*   that fit into the provided update rectangle.
*   During each display update, this function is called until it returns 0.
*
* ARGUMENTS:
*   aUpdateRect - Rectangular area which should be updated (redrawn).
*
* RETURN VALUE:
*   Returns 1 if a further update area can be provided, 0 otherwise.
*
*******************************************************************************/
int EwBspDisplayGetUpdateArea( XRect* aUpdateRect )
{
  #if EW_USE_DOUBLE_BUFFER == 0

    static int field = 0;

    /* determine rectangular area of current field */
    #if EW_SURFACE_ROTATION == 0
      *aUpdateRect = EwNewRect( 0, field * FRAME_BUFFER_HEIGHT / NUMBER_OF_FIELDS,
        FRAME_BUFFER_WIDTH, ( field + 1 ) * FRAME_BUFFER_HEIGHT / NUMBER_OF_FIELDS );
    #endif

    #if EW_SURFACE_ROTATION == 90
      *aUpdateRect = EwNewRect( field * FRAME_BUFFER_HEIGHT / NUMBER_OF_FIELDS, 0,
        ( field + 1 ) * FRAME_BUFFER_HEIGHT / NUMBER_OF_FIELDS, FRAME_BUFFER_WIDTH );
    #endif

    #if EW_SURFACE_ROTATION == 180
      *aUpdateRect = EwNewRect( 0, FRAME_BUFFER_HEIGHT - ( field + 1 ) * FRAME_BUFFER_HEIGHT / NUMBER_OF_FIELDS,
        FRAME_BUFFER_WIDTH, FRAME_BUFFER_HEIGHT - field * FRAME_BUFFER_HEIGHT / NUMBER_OF_FIELDS );
    #endif

    #if EW_SURFACE_ROTATION == 270
      *aUpdateRect = EwNewRect( FRAME_BUFFER_HEIGHT - ( field + 1 ) * FRAME_BUFFER_HEIGHT / NUMBER_OF_FIELDS,
        0, FRAME_BUFFER_HEIGHT - field * FRAME_BUFFER_HEIGHT / NUMBER_OF_FIELDS, FRAME_BUFFER_WIDTH );
    #endif

    /* next field */
    field++;

    /* sync on start line of next field to ensure save drawing operation */
    SyncOnLine(( field % NUMBER_OF_FIELDS ) * FRAME_BUFFER_HEIGHT / NUMBER_OF_FIELDS );

    if ( field <= NUMBER_OF_FIELDS )
      return 1;

    field = 0;

  #endif

  return 0;
}


/*******************************************************************************
* FUNCTION:
*   EwBspDisplayWaitForCompletion
*
* DESCRIPTION:
*   The function EwBspDisplayWaitForCompletion is called from the Graphics Engine
*   to ensure that all pending activities of the display system are completed, so
*   that the rendering of the next frame can start.
*   In case of a double-buffering system, the function has to wait until the
*   V-sync has occured and the pending buffer is used by the display controller.
*   In case of an external display controller, the function has to wait until
*   the transfer (update) of the graphics data has been completed and there are
*   no pending buffers.
*
* ARGUMENTS:
*   None
*
* RETURN VALUE:
*   None
*
*******************************************************************************/
void EwBspDisplayWaitForCompletion( void )
{
  #if EW_USE_DOUBLE_BUFFER == 1

    CPU_LOAD_SET_IDLE();

    #if EW_USE_FREE_RTOS == 1

      /* wait until pending framebuffer is used as current framebuffer and
         use CPU time for other tasks */
      while ( PendingFramebuffer )
      {
        if ( xSemaphoreTake( LcdUpdateSemaphore, 1000 / portTICK_PERIOD_MS ) == pdTRUE )
          xSemaphoreGive( LcdUpdateSemaphore );
      }

    #else

      /* wait until pending framebuffer is used as current framebuffer */
      while( PendingFramebuffer )
        ;

    #endif

    CPU_LOAD_SET_ACTIVE();

  #endif
}


/*******************************************************************************
* FUNCTION:
*   EwBspDisplayCommitBuffer
*
* DESCRIPTION:
*   The function EwBspDisplayCommitBuffer is called from the Graphics Engine
*   when the rendering of a certain buffer has been completed.
*   The type of buffer depends on the selected framebuffer concept.
*   If the display is running in a double-buffering mode, the function is called
*   after each buffer update in order to change the currently active framebuffer
*   address. Changing the framebuffer address should be synchronized with V-sync.
*   If the system is using an external graphics controller, this function is
*   responsible to start the transfer of the framebuffer content.
*
* ARGUMENTS:
*   aAddress - Address of the framebuffer to be shown on the display.
*   aX,
*   aY       - Origin of the area which has been updated by the Graphics Engine.
*   aWidth,
*   aHeight  - Size of the area which has been updated by the Graphics Engine.
*
* RETURN VALUE:
*   None
*
*******************************************************************************/
void EwBspDisplayCommitBuffer( void* aAddress, int aX, int aY, int aWidth, int aHeight )
{
  #if EW_USE_DOUBLE_BUFFER == 1

    NVIC_DisableIRQ( APP_LCD_IRQn );

    /* set pending framebuffer address to be used on next V-sync */
    PendingFramebuffer = (uint32_t)aAddress;
    LCDC_SetPanelAddr( LCD, kLCDC_UpperPanel, (uint32_t)aAddress );

    #if EW_USE_FREE_RTOS == 1
      xSemaphoreTake( LcdUpdateSemaphore, 0 );
    #endif

    NVIC_EnableIRQ( APP_LCD_IRQn );

  #endif
}


/*******************************************************************************
* FUNCTION:
*   EwBspDisplaySetClut
*
* DESCRIPTION:
*   The function EwBspDisplaySetClut is called from the Graphics Engine
*   in order to update the hardware CLUT of the current framebuffer.
*   The function is only called when the color format of the framebuffer is
*   Index8 or LumA44.
*
* ARGUMENTS:
*   aClut - Pointer to a color lookup table with 256 entries.
*
* RETURN VALUE:
*   None
*
*******************************************************************************/
void EwBspDisplaySetClut( unsigned long* aClut )
{
  int i;
  static uint16_t clut[ 256 ];


  for ( i = 0; i < 256; i++ )
    clut[i] = (( aClut[ i ] & 0x000000F8 ) >> 3 ) |
              (( aClut[ i ] & 0x0000F800 ) >> 6 ) |
              (( aClut[ i ] & 0x00F80000 ) >> 9 );

  LCDC_SetPalette( LCD, ( uint32_t*)clut, 128 );
}


/* mli, msy */
