/**************************************************************************//**
* @file     lcd.c
* @brief    N9H30 LCD driver source file
*
* @note
* SPDX-License-Identifier: Apache-2.0
* Copyright (C) 2018 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "N9H30.h"
#include "nu_sys.h"
#include "nu_lcd.h"

/** @addtogroup N9H30_Device_Driver N9H30 Device Driver
  @{
*/

/** @addtogroup N9H30_LCD_Driver LCD Driver
  @{
*/

/** @addtogroup N9H30_LCD_EXPORTED_CONSTANTS LCD Exported Constants
  @{
*/

/// @cond HIDDEN_SYMBOLS

/* LCD attributes */
static VPOST_T DEF_E50A2V1 =
{
    800,                            /*!< Panel width */
    480,                            /*!< Panel height */
    0,                              /*!< MPU command line low indicator */
    0,                              /*!< MPU command width */
    0,                              /*!< MPU bus width */
    VPOSTB_DATA16or18,              /*!< Display bus width */
    0,                              /*!< MPU mode */
    VPOSTB_COLORTYPE_64K,           /*!< Display colors */
    VPOSTB_DEVICE_SYNC_HIGHCOLOR,   /*!< Type of display panel */
    0x020d03a0,                     /*!< CRTCSIZE register value */
    0x01e00320,                     /*!< CRTCDEND register value */
    0x03250321,                     /*!< CRTCHR register value */
    0x03780348,                     /*!< CRTCHSYNC register value */
    0x01f001ed                      /*!< CRTCVR register value */
};

static VPOST_T DEF_ILI9341_MPU80 =
{
    240,                            /*!< Panel width */
    320,                            /*!< Panel height */
    VPOSTB_CMDLOW,                  /*!< MPU command line low indicator */
    VPOSTB_CM16t18HIGH,             /*!< MPU command width */
    VPOSTB_CMD8,                    /*!< MPU bus width */
    VPOSTB_DATA16or18,              /*!< Display bus width */
    VPOSTB_MPU80,                   /*!< MPU mode */
    VPOSTB_COLORTYPE_64K,           /*!< Display colors */
    VPOSTB_DEVICE_MPU,              /*!< Type of display panel */
    0x01600100,                     /*!< CRTCSIZE register value */
    0x014000F0,                     /*!< CRTCDEND register value */
    0x00FA00F5,                     /*!< CRTCHR register value */
    0x00FC00FA,                     /*!< CRTCHSYNC register value */
    0x01500145                      /*!< CRTCVR register value */
};

static VPOST_T DEF_LSA40AT9001 =
{
    800,                            /*!< Panel width */
    600,                            /*!< Panel height */
    0,                              /*!< MPU command line low indicator */
    0,                              /*!< MPU command width */
    0,                              /*!< MPU bus width */
    VPOSTB_DATA16or18,              /*!< Display bus width */
    0,                              /*!< MPU mode */
    VPOSTB_COLORTYPE_64K,           /*!< Display colors */
    VPOSTB_DEVICE_SYNC_HIGHCOLOR,   /*!< Type of display panel */
    0x02800425,                     /*!< CRTCSIZE register value */
    0x02580320,                     /*!< CRTCDEND register value */
    0x032F032A,                     /*!< CRTCHR register value */
    0x0334032A,                     /*!< CRTCHSYNC register value */
    0x026C0262                      /*!< CRTCVR register value */
};


static VPOST_T DEF_FW070TFT =
{
    800,                            /*!< Panel width */
    480,                            /*!< Panel height */
    0,                              /*!< MPU command line low indicator */
    0,                              /*!< MPU command width */
    0,                              /*!< MPU bus width */
    VPOSTB_DATA16or18,              /*!< Display bus width */
    0,                              /*!< MPU mode */
    VPOSTB_COLORTYPE_16M,           /*!< Display colors */
    VPOSTB_DEVICE_SYNC_HIGHCOLOR,   /*!< Type of display panel */
    0x020d0420,                     /*!< CRTCSIZE register value */
    0x01e00320,                     /*!< CRTCDEND register value */
    0x033e0339,                     /*!< CRTCHR register value */
    0x040c03f8,                     /*!< CRTCHSYNC register value */
    0x020001f6                      /*!< CRTCVR register value */
};

#define FW043TFT_WIDTH        480   /*!< XRES */
#define FW043TFT_HEIGHT       272   /*!< YRES */
#define FW043TFT_MARGIN_LEFT  30    /*!< HBP (Horizontal Back Porch) */
#define FW043TFT_MARGIN_RIGHT 5     /*!< HFP (Horizontal Front Porch) */
#define FW043TFT_MARGIN_UPPER 2     /*!< VBP (Vertical Back Porch) */
#define FW043TFT_MARGIN_LOWER 27    /*!< VFP (Vertical Front Porch) */
#define FW043TFT_HSYNC_LEN    41    /*!< HPW (HSYNC plus width) */
#define FW043TFT_VSYNC_LEN    10    /*!< VPW (VSYNC width) */
static VPOST_T DEF_FW043TFT =
{
    FW043TFT_WIDTH,                 /*!< Panel width */
    FW043TFT_HEIGHT,                /*!< Panel height */
    0,                              /*!< MPU command line low indicator */
    0,                              /*!< MPU command width */
    0,                              /*!< MPU bus width */
    VPOSTB_DATA16or18,              /*!< Display bus width */
    0,                              /*!< MPU mode */
    VPOSTB_COLORTYPE_16M,           /*!< Display colors */
    VPOSTB_DEVICE_SYNC_HIGHCOLOR,   /*!< Type of display panel */

    .sCRTCSIZE =
    {
        /*!< Horizontal Total */
        .HTT = FW043TFT_MARGIN_LEFT + FW043TFT_WIDTH + FW043TFT_MARGIN_RIGHT,

        /*!< Vertical Total */
        .VTT = FW043TFT_MARGIN_UPPER + FW043TFT_HEIGHT + FW043TFT_MARGIN_LOWER,
    },
    .sCRTCDEND =
    {
        /*!< Horizontal Display Enable End */
        .HDEND = FW043TFT_WIDTH,

        /*!< Vertical Display Enable End */
        .VDEND = FW043TFT_HEIGHT,
    },
    .sCRTCHR =
    {
        /*!< Internal Horizontal Retrace Start Timing */
        .HRS = FW043TFT_WIDTH + 1,

        /*!< Internal Horizontal Retrace End Low */
        .HRE = FW043TFT_WIDTH + 5,
    },
    .sCRTCHSYNC =
    {
        /*!< Horizontal Sync Start Timing */
        .HSYNC_S = FW043TFT_WIDTH + FW043TFT_MARGIN_LEFT,

        /*!< Horizontal Sync End Timing */
        .HSYNC_E = FW043TFT_WIDTH + FW043TFT_MARGIN_LEFT + FW043TFT_HSYNC_LEN,

        /*!< Hsync Signal Adjustment For Multi-Cycles Per Pixel Mode Of Sync-Based Unipac-LCD */
        .HSYNC_SHIFT = 0,
    },
    .sCRTCVR =
    {
        /*!< Vertical Internal Retrace Start Timing */
        .VRS = FW043TFT_HEIGHT + FW043TFT_MARGIN_UPPER,

        /*!< Vertical Internal Retrace End Low */
        .VRE = FW043TFT_HEIGHT + FW043TFT_MARGIN_UPPER + FW043TFT_VSYNC_LEN,
    }
};

#define FW070TFT_WSVGA_WIDTH        1024  /*!< XRES */
#define FW070TFT_WSVGA_HEIGHT       600   /*!< YRES */
#define FW070TFT_WSVGA_MARGIN_LEFT  160   /*!< HBP (Horizontal Back Porch) */
#define FW070TFT_WSVGA_MARGIN_RIGHT 160   /*!< HFP (Horizontal Front Porch) */
#define FW070TFT_WSVGA_MARGIN_UPPER 12    /*!< VBP (Vertical Back Porch) */
#define FW070TFT_WSVGA_MARGIN_LOWER 23    /*!< VFP (Vertical Front Porch) */
#define FW070TFT_WSVGA_HSYNC_LEN    1     /*!< HPW (HSYNC plus width) */
#define FW070TFT_WSVGA_VSYNC_LEN    1     /*!< VPW (VSYNC width) */
static VPOST_T DEF_FW070TFT_WSVGA  =
{
    FW070TFT_WSVGA_WIDTH,                 /*!< Panel width */
    FW070TFT_WSVGA_HEIGHT,                /*!< Panel height */
    0,                              /*!< MPU command line low indicator */
    0,                              /*!< MPU command width */
    0,                              /*!< MPU bus width */
    VPOSTB_DATA16or18,              /*!< Display bus width */
    0,                              /*!< MPU mode */
    VPOSTB_COLORTYPE_16M,           /*!< Display colors */
    VPOSTB_DEVICE_SYNC_HIGHCOLOR,   /*!< Type of display panel */

    .sCRTCSIZE =
    {
        /*!< Horizontal Total */
        .HTT = FW070TFT_WSVGA_MARGIN_LEFT + FW070TFT_WSVGA_WIDTH + FW070TFT_WSVGA_MARGIN_RIGHT,

        /*!< Vertical Total */
        .VTT = FW070TFT_WSVGA_MARGIN_UPPER + FW070TFT_WSVGA_HEIGHT + FW070TFT_WSVGA_MARGIN_LOWER,
    },
    .sCRTCDEND =
    {
        /*!< Horizontal Display Enable End */
        .HDEND = FW070TFT_WSVGA_WIDTH,

        /*!< Vertical Display Enable End */
        .VDEND = FW070TFT_WSVGA_HEIGHT,
    },
    .sCRTCHR =
    {
        /*!< Internal Horizontal Retrace Start Timing */
        .HRS = FW070TFT_WSVGA_WIDTH + 1,

        /*!< Internal Horizontal Retrace End Low */
        .HRE = FW070TFT_WSVGA_WIDTH + 5,
    },
    .sCRTCHSYNC =
    {
        /*!< Horizontal Sync Start Timing */
        .HSYNC_S = FW070TFT_WSVGA_WIDTH + FW070TFT_WSVGA_MARGIN_LEFT,

        /*!< Horizontal Sync End Timing */
        .HSYNC_E = FW070TFT_WSVGA_WIDTH + FW070TFT_WSVGA_MARGIN_LEFT + FW070TFT_WSVGA_HSYNC_LEN,

        /*!< Hsync Signal Adjustment For Multi-Cycles Per Pixel Mode Of Sync-Based Unipac-LCD */
        .HSYNC_SHIFT = 0,
    },
    .sCRTCVR =
    {
        /*!< Vertical Internal Retrace Start Timing */
        .VRS = FW070TFT_WSVGA_HEIGHT + FW070TFT_WSVGA_MARGIN_UPPER,

        /*!< Vertical Internal Retrace End Low */
        .VRE = FW070TFT_WSVGA_HEIGHT + FW070TFT_WSVGA_MARGIN_UPPER + FW070TFT_WSVGA_VSYNC_LEN,
    }
};

/* LCD build-in support list */
static VPOST_T *DisplayDevList[DIS_PANEL_CNT] =
{
    &DEF_E50A2V1,
    &DEF_ILI9341_MPU80,
    &DEF_LSA40AT9001,
    &DEF_FW070TFT,
    &DEF_FW043TFT,
    &DEF_FW070TFT_WSVGA
};

static VPOST_T curDisplayDev;
static OSDFORMATEX curOSDDev = {0};
static LCDFORMATEX curVADev = {0};

/// @endcond /* HIDDEN_SYMBOLS */

/*@}*/ /* end of group N9H30_I2C_EXPORTED_CONSTANTS */

/** @addtogroup N9H30_LCD_EXPORTED_FUNCTIONS LCD Exported Functions
  @{
*/
/// @cond HIDDEN_SYMBOLS

/* For align 32 */
static uint32_t shift_pointer(uint32_t ptr, uint32_t align)
{
    uint32_t alignedPTR;
    uint32_t remain;

    //printf("pointer position is %x\n",ptr);
    if ((ptr % align) != 0)
    {
        remain = ptr % align;
        alignedPTR = ptr + (align - remain);
        return alignedPTR;
    }
    return ptr;
}

/// @endcond /* HIDDEN_SYMBOLS */

/**
  * @brief Configure attributes of LCD panel,install interrupt handler and enable LCD engine clock
  * @param[in] u32DisplayPanelID is panel id to configure.
  * @return none
  */
void vpostLCMInit(uint32_t u32DisplayPanelID)
{
    // enable lcd engine clock
    outpw(REG_CLK_HCLKEN, inpw(REG_CLK_HCLKEN) | (1 << 25));

    memset((void *)&curDisplayDev, 0, sizeof(curDisplayDev));
    memcpy((void *)&curDisplayDev, DisplayDevList[u32DisplayPanelID], sizeof(curDisplayDev));

    outpw(REG_LCM_DEV_CTRL,   curDisplayDev.u32CmdLow
          | curDisplayDev.u32Cmd16t18
          | curDisplayDev.u32CmdBusWidth
          | curDisplayDev.u32DataBusWidth
          | curDisplayDev.u32MPU_Mode
          | curDisplayDev.u32DisplayColors
          | curDisplayDev.u32DevType);

    outpw(REG_LCM_CRTC_SIZE,    curDisplayDev.u32Reg_CRTCSIZE);
    outpw(REG_LCM_CRTC_DEND,    curDisplayDev.u32Reg_CRTCDEND);
    outpw(REG_LCM_CRTC_HR,      curDisplayDev.u32Reg_CRTCHR);
    outpw(REG_LCM_CRTC_HSYNC,   curDisplayDev.u32Reg_CRTCHSYNC);
    outpw(REG_LCM_CRTC_VR,      curDisplayDev.u32Reg_CRTCVR);

}

/**
  * @brief Query LCM capacity and configuration by ID
  * @param[in] u32DisplayPanelID is panel id to configure.
  * @return LCM instance
  */
VPOST_T *vpostLCMGetInstance(uint32_t u32DisplayPanelID)
{
    if (u32DisplayPanelID > (sizeof(DisplayDevList) / sizeof(VPOST_T *)))
        return NULL;

    return DisplayDevList[u32DisplayPanelID];
}

/**
  * @brief Disable LCD engine
  * @param none
  * @return none
  */
void vpostLCMDeinit(void)
{
    // disable lcd engine clock
    outpw(REG_CLK_HCLKEN, inpw(REG_CLK_HCLKEN) & ~(1 << 25));

    //sysDisableInterrupt(LCD_IRQn);
}

/**
  * @brief Get the pointer of frame buffer
  * @param none
  * @return pointer of frame buffer
  * @retval NULL fail.
  * @note before calling this function, display width, height and source format must be set first.
  */
uint8_t *vpostGetFrameBuffer(void)
{
    uint8_t *u8BufPtr;
    uint8_t u32BytePerPixel;

    if ((curDisplayDev.u32DevWidth == 0) || (curDisplayDev.u32DevHeight == 0))
        return NULL;

    switch (curVADev.ucVASrcFormat)
    {
    case VA_SRC_YUV422:
    case VA_SRC_YCBCR422:
    case VA_SRC_RGB565:
        u32BytePerPixel = 2;
        break;

    case VA_SRC_RGB666:
    case VA_SRC_RGB888:
        u32BytePerPixel = 4;
        break;

    default:
        u32BytePerPixel = 2;
    }

    u8BufPtr = (uint8_t *)malloc((curDisplayDev.u32DevWidth * curDisplayDev.u32DevHeight * u32BytePerPixel) + 32);
    if (u8BufPtr == NULL)
        return NULL;
    u8BufPtr = (uint8_t *)shift_pointer((uint32_t)u8BufPtr, 32);

    outpw(REG_LCM_VA_BADDR0, (uint32_t)((uint32_t)u8BufPtr | 0x80000000));
    outpw(REG_LCM_VA_FBCTRL, inpw(REG_LCM_VA_FBCTRL) & ~(1 << 30) & ~VPOSTB_DB_EN);

    return (uint8_t *)((uint32_t)u8BufPtr | 0x80000000);
}

void vpostSetFrameBuffer(uint8_t *pu8BufPtr)
{
    outpw(REG_LCM_VA_BADDR0, (uint32_t)((uint32_t)pu8BufPtr | 0x80000000));
    outpw(REG_LCM_VA_FBCTRL, inpw(REG_LCM_VA_FBCTRL) & ~(1 << 30) & ~VPOSTB_DB_EN);
}


/**
  * @brief Get the pointer of frame buffer
  * @param[in] u32Cnt is the frame buffer count to allocate. Min value is 1.
  * @return pointer of frame buffer
  * @retval NULL fail.
  * @note before calling this function, display width, height and source format must be set first.
  */
uint8_t *vpostGetMultiFrameBuffer(uint32_t u32Cnt)
{
    uint8_t *u8BufPtr;
    uint8_t u32BytePerPixel;

    if ((curDisplayDev.u32DevWidth == 0) || (curDisplayDev.u32DevHeight == 0) || (u32Cnt == 0))
        return NULL;

    switch (curVADev.ucVASrcFormat)
    {
    case VA_SRC_YUV422:
    case VA_SRC_YCBCR422:
    case VA_SRC_RGB565:
        u32BytePerPixel = 2;
        break;

    case VA_SRC_RGB666:
    case VA_SRC_RGB888:
        u32BytePerPixel = 4;
        break;

    default:
        u32BytePerPixel = 2;
    }

    u8BufPtr = (uint8_t *)malloc((curDisplayDev.u32DevWidth * curDisplayDev.u32DevHeight * u32BytePerPixel) * u32Cnt + 32);
    if (u8BufPtr == NULL)
        return NULL;
    u8BufPtr = (uint8_t *)shift_pointer((uint32_t)u8BufPtr, 32);

    outpw(REG_LCM_VA_BADDR0, (uint32_t)((uint32_t)u8BufPtr | 0x80000000));
    outpw(REG_LCM_VA_FBCTRL, inpw(REG_LCM_VA_FBCTRL) & ~(1 << 30) & ~VPOSTB_DB_EN);

    return (uint8_t *)((uint32_t)u8BufPtr | 0x80000000);
}

/**
  * @brief Set active display window
  * @param[in] u16StartY is y start position
  * @param[in] u16EndY is y end position
  * @param[in] u8BGColorR is background R color
  * @param[in] u8BGColorG is background G color
  * @param[in] u8BGColorB is background B color
  * @return none
  */
void vpostSetActiveWindow(uint16_t u16StartY, uint16_t u16EndY, uint8_t u8BGColorR, uint8_t u8BGColorG, uint8_t u8BGColorB)
{
    outpw(REG_LCM_VA_WIN, (u16StartY << 16) | u16EndY);
    outpw(REG_LCM_VA_STUFF, (u8BGColorR << 16) | (u8BGColorG << 8) | u8BGColorB);
}

/**
  * @brief Configure LCD display mode
  * @param[in] u8DisplayMode is display mode, value could be
  *                                         - \ref VPOST_DISPLAY_SINGLE
  *                                         - \ref VPOST_DISPLAY_CONTINUOUS
  * @return none
  */
void vpostSetDisplayMode(uint8_t u8DisplayMode)
{
    if (u8DisplayMode == 0)
        outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) & ~(1 << 7)); //clear setting
    else
        outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) | (u8DisplayMode) << 7);
}

/**
  * @brief Configure display attributes of video interface,
  * @param[in] u32VASrcType is display type, value could be
  *                                         - \ref VA_SRC_YUV422
  *                                         - \ref VA_SRC_YCBCR422
  *                                         - \ref VA_SRC_RGB888
  *                                         - \ref VA_SRC_RGB666
  *                                         - \ref VA_SRC_RGB565
  *                                         - \ref VA_SRC_RGB444_LOW
  *                                         - \ref VA_SRC_RGB444_HIGH
  * @return none
  */
void vpostSetVASrc(uint32_t u32VASrcType)
{
    uint32_t u32BytePerPixel, VA_FF, VA_Sride;

    curVADev.ucVASrcFormat = u32VASrcType;

    outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) & ~(7 << 8));
    if (u32VASrcType != 0)
        outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) | u32VASrcType);
    else
        outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) & ~(7 << 8));

    if ((u32VASrcType == VA_SRC_RGB888) || (u32VASrcType == VA_SRC_RGB666))
        outpw(REG_LCM_VA_FBCTRL, inpw(REG_LCM_VA_FBCTRL) & ~0x7ff07ff | (curDisplayDev.u32DevWidth << 16) | curDisplayDev.u32DevWidth);
    else
        outpw(REG_LCM_VA_FBCTRL, inpw(REG_LCM_VA_FBCTRL) & ~0x7ff07ff | ((curDisplayDev.u32DevWidth / 2) << 16) | (curDisplayDev.u32DevWidth / 2));

    switch (u32VASrcType)
    {
    case VA_SRC_YUV422:
    case VA_SRC_YCBCR422:
    case VA_SRC_RGB565:
        u32BytePerPixel = 2;
        break;

    case VA_SRC_RGB666:
    case VA_SRC_RGB888:
        u32BytePerPixel = 4;
        break;

    default:
        u32BytePerPixel = 2;
    }

    /* set video stream frame buffer control */
    VA_FF = curDisplayDev.u32DevWidth * u32BytePerPixel / 4;
    VA_Sride = curDisplayDev.u32DevWidth * u32BytePerPixel / 4;
    outpw(REG_LCM_VA_FBCTRL, inpw(REG_LCM_VA_FBCTRL) & ~0x7ff07ff | (VA_FF << 16) | VA_Sride);
}

/**
  * @brief Start to display
  * @param none
  * @return none
  */
void vpostVAStartTrigger(void)
{
    if ((inpw(REG_LCM_DCCS) & VPOSTB_SINGLE) == VPOSTB_SINGLE)
        while ((inpw(REG_LCM_DCCS) & VPOSTB_VA_EN) == VPOSTB_VA_EN); //wait VA_EN low
    outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) | VPOSTB_DISP_OUT_EN); //display_out-enable
    outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) | VPOSTB_VA_EN); //va-enable
}

/**
  * @brief Stop to display
  * @param none
  * @return none
  */
void vpostVAStopTrigger(void)
{
    outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) & ~(VPOSTB_DISP_OUT_EN | VPOSTB_VA_EN)); //OSD disable
}

/**
  * @brief Configure LCD scaling attribute
  * @param[in] u8HIntegral is horizontal integral
  * @param[in] u16HDecimal is horizontal decimal
  * @param[in] u8VIntegral is vertical integral
  * @param[in] u16VDecimal is vertical decimal
  * @param[in] u32Mode is scale mode, value could be
  *                                      - \ref VA_SCALE_INTERPOLATION
  *                                      - \ref VA_SCALE_DUPLICATION
  * @return none
  */
void vpostVAScalingCtrl(uint8_t u8HIntegral, uint16_t u16HDecimal, uint8_t u8VIntegral, uint16_t u16VDecimal, uint32_t u32Mode)
{
    outpw(REG_LCM_VA_SCALE, ((((uint32_t)u8VIntegral << 10) + ((uint32_t)ceil((double)1024 / 10)*u16VDecimal)) << 16)
          | (((uint32_t)u8HIntegral << 10) + ((uint32_t)ceil((double)1024 / 10)*u16HDecimal)) | u32Mode);
}

/**
  * @brief Set OSD color key
  * @param[in] u8CKeyColorR is color key R color
  * @param[in] u8CKeyColorG is color key G color
  * @param[in] u8CKeyColorB is color key B color
  * @return none
  */
void vpostOSDSetColKey(uint8_t u8CKeyColorR, uint8_t u8CKeyColorG, uint8_t u8CKeyColorB)
{
    outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) & ~(VPOSTB_BLI_ON | VPOSTB_CKEY_ON)); //blinking disable, color-key disable
    outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) | VPOSTB_CKEY_ON);//color-key enable
    outpw(REG_LCM_OSD_CKEY, ((uint32_t)(u8CKeyColorR << 16) | (uint32_t)(u8CKeyColorG << 8) | u8CKeyColorB));
}

/**
  * @brief Set OSD color mask, OSD data only will be displayed if the mask bit is set as 1.
  * @param[in] u8MaskColorR is color key R color
  * @param[in] u8MaskColorG is color key G color
  * @param[in] u8MaskColorB is color key B color
  * @return none
  */
void vpostOSDSetColMask(uint8_t u8MaskColorR, uint8_t u8MaskColorG, uint8_t u8MaskColorB)
{
    outpw(REG_LCM_OSD_CMASK, ((u8MaskColorR << 16) | (u8MaskColorG << 8) | u8MaskColorB));
}

/**
  * @brief Set OSD blinking function
  * @param[in] u8OSDBlinkVcnt is blinking cycle time, unit is VSync
  * @return none
  */
void vpostOSDSetBlinking(uint8_t u8OSDBlinkVcnt)
{
    outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) & ~(VPOSTB_BLI_ON | VPOSTB_CKEY_ON));  //blinking disable, color-key disable
    outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) | VPOSTB_BLI_ON);
    outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) | ((uint32_t)(u8OSDBlinkVcnt) << 16));
}

/**
  * @brief Disable OSD blinking function
  * @param none
  * @return none
  */
void vpostOSDDisableBlinking(void)
{
    outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) & ~ VPOSTB_BLI_ON);
}

/**
  * @brief Configure display attributes of OSD
  * @param[in] u32OSDSrcType is display type, value could be
  *                                         - \ref OSD_SRC_YUV422
  *                                         - \ref OSD_SRC_YCBCR422
  *                                         - \ref OSD_SRC_RGB888
  *                                         - \ref OSD_SRC_RGB666
  *                                         - \ref OSD_SRC_RGB565
  *                                         - \ref OSD_SRC_RGB444_LOW
  *                                         - \ref OSD_SRC_RGB444_HIGH
  *                                         - \ref OSD_SRC_RGB332
  * @return none
  */
void vpostSetOSDSrc(uint32_t u32OSDSrcType)
{
    uint32_t u32BytePerPixel, VA_FF, VA_Sride;

    outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) & ~(7 << 12) | u32OSDSrcType);
    curOSDDev.ucOSDSrcFormat = u32OSDSrcType;

    switch (u32OSDSrcType)
    {
    case OSD_SRC_YUV422:
    case OSD_SRC_YCBCR422:
    case OSD_SRC_RGB565:
        u32BytePerPixel = 2;
        break;

    case OSD_SRC_RGB666:
    case OSD_SRC_RGB888:
        u32BytePerPixel = 4;
        break;

    default:
        u32BytePerPixel = 2;
    }

    /* set video stream frame buffer control */
    VA_FF = curOSDDev.nOSDWidth * u32BytePerPixel / 4;
    VA_Sride = curOSDDev.nOSDWidth * u32BytePerPixel / 4;
    outpw(REG_LCM_OSD_FBCTRL, inpw(REG_LCM_OSD_FBCTRL) & ~0x7ff07ff | (VA_FF << 16) | VA_Sride);
}

/**
  * @brief Get the pointer of OSD frame buffer
  * @param none
  * @return pointer of OSD frame buffer
  * @retval NULL fail.
  * @note Must call \ref vpostOSDSetWindow and \ref vpostSetOSDSrc before calling this function
  */
uint8_t *vpostGetOSDBuffer(void)
{
    uint32_t u32BytePerPixel;
    uint8_t *u8BufPtr;

    if ((curOSDDev.nOSDWidth == 0) || (curOSDDev.nOSDHeight == 0))
    {
        return NULL;
    }

    switch (curOSDDev.ucOSDSrcFormat)
    {
    case OSD_SRC_YUV422:
    case OSD_SRC_YCBCR422:
    case OSD_SRC_RGB565:
        u32BytePerPixel = 2;
        break;

    case OSD_SRC_RGB666:
    case OSD_SRC_RGB888:
        u32BytePerPixel = 4;
        break;

    default:
        u32BytePerPixel = 2;
    }

    u8BufPtr = (uint8_t *)malloc((curOSDDev.nOSDWidth * curOSDDev.nOSDHeight * u32BytePerPixel) + 32);
    if (u8BufPtr == NULL)
        return NULL;
    u8BufPtr = (uint8_t *)shift_pointer((uint32_t)u8BufPtr, 32);

    outpw(REG_LCM_OSD_BADDR, (uint32_t)((uint32_t)u8BufPtr | 0x80000000));

    return (uint8_t *)((uint32_t)u8BufPtr | 0x80000000);
}

/**
  * @brief Get the pointer of OSD buffer
  * @param[in] u32Cnt is the frame buffer count to allocate. Min value is 1.
  * @return pointer of frame buffer
  * @retval NULL fail.
  * @note before calling this function, display width, height and source format must be set first.
  */
uint8_t *vpostGetMultiOSDBuffer(uint32_t u32Cnt)
{
    uint32_t u32BytePerPixel;
    uint8_t *u8BufPtr;

    if ((curOSDDev.nOSDWidth == 0) || (curOSDDev.nOSDHeight == 0))
    {
        return NULL;
    }

    switch (curOSDDev.ucOSDSrcFormat)
    {
    case OSD_SRC_YUV422:
    case OSD_SRC_YCBCR422:
    case OSD_SRC_RGB565:
        u32BytePerPixel = 2;
        break;

    case OSD_SRC_RGB666:
    case OSD_SRC_RGB888:
        u32BytePerPixel = 4;
        break;

    default:
        u32BytePerPixel = 2;
    }

    u8BufPtr = (uint8_t *)malloc((curOSDDev.nOSDWidth * curOSDDev.nOSDHeight * u32BytePerPixel) * u32Cnt + 32);
    if (u8BufPtr == NULL)
        return NULL;
    u8BufPtr = (uint8_t *)shift_pointer((uint32_t)u8BufPtr, 32);

    outpw(REG_LCM_OSD_BADDR, (uint32_t)((uint32_t)u8BufPtr | 0x80000000));

    return (uint8_t *)((uint32_t)u8BufPtr | 0x80000000);

}

void vpostSetOSDBuffer(uint8_t *u8BufPtr)
{
    outpw(REG_LCM_OSD_BADDR, (uint32_t)((uint32_t)u8BufPtr | 0x80000000));
}

/**
  * @brief Enable OSD function
  * @param none
  * @return none
  */
void vpostOSDEnable(void)
{
    outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) | VPOSTB_OSD_EN); //OSD enable
}

/**
  * @brief Disable OSD function
  * @param none
  * @return none
  */
void vpostOSDDisable(void)
{
    outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) & ~VPOSTB_OSD_EN); //OSD disable
}

/**
  * @brief Configure OSD scaling attribute
  * @param[in] u8HIntegral is horizontal integral
  * @param[in] u16HDecimal is horizontal decimal
  * @param[in] u8VScall is scale mode, value could be
  *                                      - \ref VPOSTB_OSD_VUP_1X
  *                                      - \ref VPOSTB_OSD_VUP_2X
  *                                      - \ref VPOSTB_OSD_VUP_4X
  * @return none
  */
void vpostOSDScalingCtrl(uint8_t u8HIntegral, uint16_t u16HDecimal, uint8_t u8VScall)
{
    outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) & 0xfff0ffff); //clear OSD scaling setting
    if (u8VScall != 0)
        outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) | (u8VScall << 16));
    outpw(REG_LCM_OSD_SCALE, ((uint32_t)u8HIntegral << 10) | ((uint32_t)ceil((double)1024 / 10 * u16HDecimal)) << 6);
}

/**
  * @brief Set OSD display window, including start position, width and height.
  * @param[in] u32XStart is X start position
  * @param[in] u32YStart is Y start position
  * @param[in] u32Width is OSD display width
  * @param[in] u32Height is OSD display height
  * @return none
  */
void vpostOSDSetWindow(uint32_t u32XStart, uint32_t u32YStart, uint32_t u32Width, uint32_t u32Height)
{
    outpw(REG_LCM_OSD_WINS, ((u32YStart + 1) << 16) | (u32XStart + 1));
    outpw(REG_LCM_OSD_WINE, ((u32YStart + u32Height) << 16) | (u32XStart + u32Width));

    curOSDDev.nOSDWidth = u32Width;
    curOSDDev.nOSDHeight = u32Height;
}

/**
  * @brief Initialize hardware cursor function
  * @param[in] u32CursorBMPBuff is pointer of hardware cursor image
  * @param[in] ucMode is hardware cursor mode, value could be
  *                                              - \ref HC_MODE0
  *                                              - \ref HC_MODE1
  *                                              - \ref HC_MODE2
  *                                              - \ref HC_MODE3
  *                                              - \ref HC_MODE4
  *                                              - \ref HC_MODE5
  * @return none
  */
void vpostHCInit(uint32_t *u32CursorBMPBuff, VA_HCMODE_E ucMode)
{
    int bpp = 2;
    int BlockWidth = 32;
    int bpw = 32;

    outpw(REG_LCM_HC_CTRL, inpw(REG_LCM_HC_CTRL) & ~0x003f3f00 | (0x00 << 8) | (0x00 << 16)); //set TIP
    if (ucMode == HC_MODE0)
    {
        bpp = 2;
        BlockWidth = 32;
        outpw(REG_LCM_HC_CTRL, inpw(REG_LCM_HC_CTRL) & ~0x7); //set mode 0 32X32X2bpp 4 color

    }
    else if (ucMode == HC_MODE1)
    {
        bpp = 2;
        BlockWidth = 32;
        outpw(REG_LCM_HC_CTRL, inpw(REG_LCM_HC_CTRL) & ~0x7 | 0x1); //set mode 1 32X32X2bpp 3 color and 1 transparent
    }
    else if (ucMode == HC_MODE2)
    {
        bpp = 2;
        BlockWidth = 64;
        outpw(REG_LCM_HC_CTRL, inpw(REG_LCM_HC_CTRL) & ~0x7 | 0x2); //set mode 2 64X64X2bpp 4 color
    }
    else if (ucMode == HC_MODE3)
    {
        bpp = 2;
        BlockWidth = 64;
        outpw(REG_LCM_HC_CTRL, inpw(REG_LCM_HC_CTRL) & ~0x7 | 0x3); //set mode 3 64X64X2bpp 3 color and 1 transparent
    }
    else if (ucMode == HC_MODE4)
    {
        bpp = 1;
        BlockWidth = 128;
        outpw(REG_LCM_HC_CTRL, inpw(REG_LCM_HC_CTRL) & ~0x7 | 0x4); //set mode 4 128X128X1bpp 2 color
    }
    else if (ucMode == HC_MODE5)
    {
        bpp = 1;
        BlockWidth = 128;
        outpw(REG_LCM_HC_CTRL, inpw(REG_LCM_HC_CTRL) & ~0x7 | 0x5); //set mode 5 128X128X1bpp 1 color and 1 transparent
    }

    outpw(REG_LCM_HC_WBCTRL, ((bpp * BlockWidth / bpw) << 16) | (bpp * BlockWidth / bpw));
    outpw(REG_LCM_HC_BADDR, (uint32_t)u32CursorBMPBuff);
    outpw(REG_LCM_HC_COLOR0, 0x00ff0000);       // RED color
    outpw(REG_LCM_HC_COLOR1, 0x0000ff00);       // GREEN color
    outpw(REG_LCM_HC_COLOR2, 0x000000ff);       // BLUE color
    outpw(REG_LCM_HC_COLOR3, 0x00ffff00);       // YELLOW color
    outpw(REG_LCM_DCCS, inpw(REG_LCM_DCCS) | VPOSTB_HC_EN);
}

/**
  * @brief Set the position of hardware cursor
  * @param[in] u32CursorX is X position
  * @param[in] u32CursorY is Y position
  * @return none
  */
void vpostHCPosCtrl(uint32_t u32CursorX, uint32_t u32CursorY)
{
    outpw(REG_LCM_HC_POS, (u32CursorY << 16) | u32CursorX); //set Cursor position
}

/**
  * @brief Set OSD overlay condition
  * @param[in] u8OSDDisplayMatch is display method when mask bit is matched, value could be
  *                                                         - \ref DISPLAY_VIDEO
  *                                                         - \ref DISPLAY_OSD
  *                                                         - \ref DISPLAY_SYNTHESIZED
  * @param[in] u8OSDDisplayUnMatch is display method when mask bit is unmatched
  *                                                         - \ref DISPLAY_VIDEO
  *                                                         - \ref DISPLAY_OSD
  *                                                         - \ref DISPLAY_SYNTHESIZED
  * @param[in] u8OSDSynW is synthesis video weighting, based on match condition
  * @return none
  */
void vpostOSDSetOverlay(uint8_t u8OSDDisplayMatch, uint8_t u8OSDDisplayUnMatch, uint8_t u8OSDSynW)
{
    /* clear OCR0 and OCR1 */
    outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) & 0xfffffff0);

    /* match condition */
    if (u8OSDDisplayMatch != 0)
    {
        outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) | (u8OSDDisplayMatch << 2));
    }

    /* unmatch condition */
    if (u8OSDDisplayUnMatch != 0)
    {
        outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) | (u8OSDDisplayUnMatch));
    }

    /* synthesized weight */
    if (u8OSDDisplayMatch == DISPLAY_SYNTHESIZED || u8OSDDisplayUnMatch == DISPLAY_SYNTHESIZED)
    {
        outpw(REG_LCM_OSD_OVERLAY, inpw(REG_LCM_OSD_OVERLAY) | (u8OSDSynW << 4));
    }
}

/**
  * @brief Write MPU command
  * @param[in] uscmd MPU command code
  * @return none
  */
void vpostMPUWriteAddr(uint16_t uscmd)
{
    outpw(REG_LCM_MPU_CMD, inpw(REG_LCM_MPU_CMD) & ~(1 << 30));     //RS=0
    outpw(REG_LCM_MPU_CMD, inpw(REG_LCM_MPU_CMD) & ~(1 << 29));     //w

    outpw(REG_LCM_DCCS, (inpw(REG_LCM_DCCS) | (1 << 5)));           //CMD ON
    outpw(REG_LCM_MPU_CMD, (inpw(REG_LCM_MPU_CMD) & 0xffff0000 | uscmd));
    while (inpw(REG_LCM_MPU_CMD) & (1UL << 31));
    outpw(REG_LCM_DCCS, (inpw(REG_LCM_DCCS) & ~(1 << 5)));          //CMD OFF
}

/**
  * @brief Write MPU data
  * @param[in] usdata MPU data
  * @return none
  */
void vpostMPUWriteData(uint16_t usdata)
{
    outpw(REG_LCM_MPU_CMD, inpw(REG_LCM_MPU_CMD) | (1 << 30));      //RS=1
    outpw(REG_LCM_MPU_CMD, inpw(REG_LCM_MPU_CMD) & ~(1 << 29));     //w
    outpw(REG_LCM_DCCS, (inpw(REG_LCM_DCCS) | (1 << 5)));           //CMD ON
    outpw(REG_LCM_MPU_CMD, inpw(REG_LCM_MPU_CMD) & 0xffff0000 | usdata);
    while (inpw(REG_LCM_MPU_CMD) & (1UL << 31));
    outpw(REG_LCM_DCCS, (inpw(REG_LCM_DCCS) & ~(1 << 5)));          //CMD OFF
}

/**
  * @brief Read MPU data
  * @param none
  * @return MPU data
  */
uint32_t vpostMPUReadData(void)
{
    uint32_t udata;

    outpw(REG_LCM_MPU_CMD, inpw(REG_LCM_MPU_CMD) | (1 << 30));      //RS=1
    outpw(REG_LCM_DCCS, (inpw(REG_LCM_DCCS) | (1 << 5)));           //CMD ON
    outpw(REG_LCM_MPU_CMD, inpw(REG_LCM_MPU_CMD) | (1 << 29));      //r
    while (inpw(REG_LCM_MPU_CMD) & (1UL << 31));
    udata = inpw(REG_LCM_MPU_CMD) & 0xffff;
    outpw(REG_LCM_DCCS, (inpw(REG_LCM_DCCS) & ~(1 << 5)));          //CMD OFF

    return udata;
}

/*@}*/ /* end of group N9H30_LCD_EXPORTED_FUNCTIONS */

/*@}*/ /* end of group N9H30_LCD_Driver */

/*@}*/ /* end of group N9H30_Device_Driver */

/*** (C) COPYRIGHT 2018 Nuvoton Technology Corp. ***/