/*
 * Copyright  2018 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_video_common.h"
#include "fsl_display.h"
#include "fsl_it6263.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define IT6263_LVDS_ID (0x66 / 2)

#define IT6263_HDMI_VENDER_ID 0x01ca1376
#define IT6263_LVDS_VENDER_ID 0x15ca6162

#define IT6263_I2C_WriteReg(handle, addr, reg, value)                                 \
    VIDEO_I2C_WriteReg(addr, kVIDEO_RegAddr8Bit, (reg), kVIDEO_RegWidth8Bit, (value), \
                       ((it6263_resource_t *)(handle->resource))->i2cSendFunc)

#define IT6263_I2C_ReadReg(handle, addr, reg, value)                                 \
    VIDEO_I2C_ReadReg(addr, kVIDEO_RegAddr8Bit, (reg), kVIDEO_RegWidth8Bit, (value), \
                      ((it6263_resource_t *)(handle->resource))->i2cReceiveFunc)

#define IT6263_I2C_ModifyReg(handle, addr, reg, mask, value)                                 \
    VIDEO_I2C_ModifyReg(addr, kVIDEO_RegAddr8Bit, (reg), kVIDEO_RegWidth8Bit, mask, (value), \
                        ((it6263_resource_t *)(handle->resource))->i2cReceiveFunc,           \
                        ((it6263_resource_t *)(handle->resource))->i2cSendFunc)

#define IT6263_I2C_ReadRegs(handle, addr, reg, value, len)                              \
    VIDEO_I2C_ReadReg(addr, kVIDEO_RegAddr8Bit, (reg), (video_reg_width_t)len, (value), \
                      ((it6263_resource_t *)(handle->resource))->i2cReceiveFunc)

#define IT6263_SET_LVDS_ID_REG    0x1DU
#define IT6263_ENABLE_LVDS_ID_REG 0x1EU

#define IT6263_HDMI_VENDER_ID_REG 0
#define IT6263_LVDS_VENDER_ID_REG 0

#define IT6263_LVDS_COLOR_DEPTH(x)   ((x) << 0U)
#define IT6263_LVDS_COLOR_DEPTH_MASK (3U << 0U)
#define IT6263_LVDS_MAP(x)           ((x) << 4U)
#define IT6263_LVDS_MAP_MASK         (1U << 4U)
#define IT6263_LVDS_DUAL_MODE(x)     ((x) << 7U)
#define IT6263_LVDS_DUAL_MODE_MASK   (1U << 7U)

/* Color depth. */
#define IT6263_LVDS_COLOR_6BIT  IT6263_LVDS_COLOR_DEPTH(0)
#define IT6263_LVDS_COLOR_8BIT  IT6263_LVDS_COLOR_DEPTH(1)
#define IT6263_LVDS_COLOR_10BIT IT6263_LVDS_COLOR_DEPTH(2)
#define IT6263_LVDS_COLOR_12BIT IT6263_LVDS_COLOR_DEPTH(3)

/* Pixel map. */
#define IT6263_LVDS_JEIDA IT6263_LVDS_MAP(0)
#define IT6263_LVDS_VESA  IT6263_LVDS_MAP(1)

/* Dual mode. */
#define IT6263_LVDS_DISO   IT6263_LVDS_DUAL_MODE(1) /* Dual In / Single Out */
#define IT6263_LVDS_DIRECT IT6263_LVDS_DUAL_MODE(0) /* Single In / Single Out or Dual In / Dual Out */

/* HDMI register 0x04. */
#define HDMI_REG_SW_RST                0x04
#define IT6263_HDMI_04_SOFTREFRST_MASK (1U << 5U)
#define IT6263_HDMI_04_SOFTARST_MASK   (1U << 4U)
#define IT6263_HDMI_04_SOFTVRST_MASK   (1U << 3U)
#define IT6263_HDMI_04_AUDRST_MASK     (1U << 2U)
#define IT6263_HDMI_04_HDCPRST_MASK    (1U << 0U)
#define IT6263_HDMI_04_ALLRST_MASK     ((1U << 5U) | (1U << 4U) | (1U << 3U) | (1U << 2U) | (1U << 0U))

/* HDMI register 0x61. */
#define IT6263_HDMI_61_DRV_PWD_MASK (1U << 5U)
#define IT6263_HDMI_61_DRV_RST_MASK (1U << 4U)

/* HDMI register 0xC1. */
#define IT6263_HDMI_C1_AVMUTE_MASK (1U << 0U)

/* HDMI register 0xC6. */
#define IT6263_HDMI_C6_PKTGENCTRLRPT_MASK (1U << 1U)
#define IT6263_HDMI_C6_PKTGENCTRLEN_MASK  (1U << 0U)

/* LVDS register 0x05. */
#define IT6263_LVDS_05_SOFTREFRST_MASK (1U << 0U)
#define IT6263_LVDS_05_SOFTRST_MASK    (1U << 1U)

/*******************************************************************************
 * Variables
 ******************************************************************************/
const display_operations_t it6263_ops = {
    .init   = IT6263_Init,
    .deinit = IT6263_Deinit,
    .start  = IT6263_Start,
    .stop   = IT6263_Stop,
};

/*******************************************************************************
 * Code
 ******************************************************************************/

static void IT6263_HardwareReset(display_handle_t *handle)
{
    it6263_resource_t *resource = (it6263_resource_t *)(handle->resource);

    resource->pullResetPin(true);

    VIDEO_DelayMs(1);

    resource->pullResetPin(false);

    VIDEO_DelayMs(40);

    resource->pullResetPin(true);

    VIDEO_DelayMs(10);
}

static void IT6263_LVDSReset(display_handle_t *handle)
{
    /* Reset the AFE PLL. */
    IT6263_I2C_ModifyReg(handle, IT6263_LVDS_ID, 0x3C, 0x1, 0x0);
    VIDEO_DelayMs(1);
    IT6263_I2C_ModifyReg(handle, IT6263_LVDS_ID, 0x3C, 0x1, 0x1);

    /* Reset the PCLK. */
    IT6263_I2C_ModifyReg(handle, IT6263_LVDS_ID, 0x05, IT6263_LVDS_05_SOFTRST_MASK, IT6263_LVDS_05_SOFTRST_MASK);
    VIDEO_DelayMs(1);
    IT6263_I2C_ModifyReg(handle, IT6263_LVDS_ID, 0x05, IT6263_LVDS_05_SOFTRST_MASK, 0);

    VIDEO_DelayMs(1);
}

static status_t IT6263_InitLVDS_ID(display_handle_t *handle)
{
    status_t status;

    uint8_t i2cAddr = (((it6263_resource_t *)(handle->resource))->i2cAddr) / 2;

    status = IT6263_I2C_WriteReg(handle, i2cAddr, IT6263_SET_LVDS_ID_REG, IT6263_LVDS_ID * 2);

    if (kStatus_Success == status)
    {
        status = IT6263_I2C_WriteReg(handle, i2cAddr, IT6263_ENABLE_LVDS_ID_REG, 1);
    }

    return status;
}

static status_t IT6263_Identify(display_handle_t *handle)
{
    uint32_t hdmiVendorID;
    uint32_t lvdsVendorID;

    uint8_t i2cAddr = (((it6263_resource_t *)(handle->resource))->i2cAddr) / 2;

    IT6263_I2C_ReadRegs(handle, i2cAddr, IT6263_HDMI_VENDER_ID_REG, &hdmiVendorID, sizeof(hdmiVendorID));
    IT6263_I2C_ReadRegs(handle, IT6263_LVDS_ID, IT6263_LVDS_VENDER_ID_REG, &lvdsVendorID, sizeof(lvdsVendorID));

    if ((hdmiVendorID != IT6263_HDMI_VENDER_ID) || (lvdsVendorID != IT6263_LVDS_VENDER_ID))
    {
        return kStatus_Fail;
    }
    else
    {
        return kStatus_Success;
    }
}

static void IT6263_InitLVDS(display_handle_t *handle)
{
    /* Configure LVDS. */
    IT6263_I2C_ModifyReg(handle, IT6263_LVDS_ID, 0x2C,
                         IT6263_LVDS_COLOR_DEPTH_MASK | IT6263_LVDS_MAP_MASK | IT6263_LVDS_DUAL_MODE_MASK,
                         IT6263_LVDS_COLOR_8BIT | IT6263_LVDS_JEIDA | IT6263_LVDS_DIRECT);

    IT6263_I2C_ModifyReg(handle, IT6263_LVDS_ID, 0x52, 0x02, 0x00);

    /* AFE */
    IT6263_I2C_WriteReg(handle, IT6263_LVDS_ID, 0x3E, 0xAA);
    IT6263_I2C_WriteReg(handle, IT6263_LVDS_ID, 0x3F, 0x02);
    IT6263_I2C_WriteReg(handle, IT6263_LVDS_ID, 0x47, 0xAA);
    IT6263_I2C_WriteReg(handle, IT6263_LVDS_ID, 0x48, 0x02);
    IT6263_I2C_WriteReg(handle, IT6263_LVDS_ID, 0x4F, 0x11);

    IT6263_I2C_ModifyReg(handle, IT6263_LVDS_ID, 0x3C, 0x07, 0);
}

status_t IT6263_Init(display_handle_t *handle, const display_config_t *config)
{
    assert(handle);

    status_t status;

    uint8_t i2cAddr = ((it6263_resource_t *)(handle->resource))->i2cAddr / 2;

    IT6263_HardwareReset(handle);

    /* Software reset. */
    status = IT6263_I2C_WriteReg(handle, i2cAddr, 0x04, IT6263_HDMI_04_ALLRST_MASK);
    if (kStatus_Success != status)
    {
        return status;
    }

    VIDEO_DelayMs(1);

    status = IT6263_InitLVDS_ID(handle);
    if (kStatus_Success != status)
    {
        return status;
    }

    status = IT6263_Identify(handle);
    if (kStatus_Success != status)
    {
        return status;
    }

    IT6263_LVDSReset(handle);

    IT6263_InitLVDS(handle);

    /*
     * Set color:
     * 0 RGB
     * 1 YUV422
     * 2 YUV444
     */
    IT6263_I2C_ModifyReg(handle, i2cAddr, 0x70, 0x03 << 6U, 0);

    /*
     * No color space conversion.
     * 0: No conversion
     * 1: RGB -> YUV
     * 2: YUV -> RGB
     */
    IT6263_I2C_ModifyReg(handle, i2cAddr, 0x72, 0x03 << 0U, 0);

    /*
     * HDMI color dpeth:
     * 000 - color depth is not indicate (as 8bit)
     * 100 - 8/8/8 bit color
     * 101 - 10/10/10 bit color
     * 110 - 12/12/12 bit color)
     */
    IT6263_I2C_ModifyReg(handle, i2cAddr, 0xC1, 0x07U << 4U, 0x04U << 0U);

    /* Select HDMI. */
    IT6263_I2C_WriteReg(handle, i2cAddr, 0xC0, 0x01);

    /* Set AFE */
    if (config->pixelClock_Hz > 80000000)
    {
        IT6263_I2C_WriteReg(handle, i2cAddr, 0x62, 0x88);
        IT6263_I2C_WriteReg(handle, i2cAddr, 0x64, 0x84);
    }
    else
    {
        IT6263_I2C_WriteReg(handle, i2cAddr, 0x62, 0x18);
        IT6263_I2C_WriteReg(handle, i2cAddr, 0x64, 0x0C);
    }
    IT6263_I2C_WriteReg(handle, i2cAddr, 0x63, 0x10);

    /*
     * Output Color Mode
     * 00 - RGB444 mode
     * 01 - YCbCr422 mode
     * 10 - YCbCr444 mode
     */
    IT6263_I2C_ModifyReg(handle, i2cAddr, 0x0F, 0x03U << 0U, 0x01U << 0U); /* Bank 0 */
    IT6263_I2C_ModifyReg(handle, i2cAddr, 0x58, 0x03U << 5U, 0x00U << 5U);
    IT6263_I2C_ModifyReg(handle, i2cAddr, 0x0F, 0x03U << 0U, 0x00U << 0U); /* Bank 1 */

    /* software video reset */
    IT6263_I2C_ModifyReg(handle, i2cAddr, 0x04, IT6263_HDMI_04_SOFTVRST_MASK, IT6263_HDMI_04_SOFTVRST_MASK);
    VIDEO_DelayMs(100);

    return kStatus_Success;
}

status_t IT6263_Deinit(display_handle_t *handle)
{
    it6263_resource_t *resource = (it6263_resource_t *)(handle->resource);

    resource->pullResetPin(false);

    return kStatus_Success;
}

status_t IT6263_Start(display_handle_t *handle)
{
    uint8_t i2cAddr = ((it6263_resource_t *)(handle->resource))->i2cAddr / 2;

    IT6263_I2C_ModifyReg(handle, i2cAddr, 0x04, IT6263_HDMI_04_SOFTVRST_MASK, 0);
    IT6263_I2C_WriteReg(handle, i2cAddr, 0x61, 0);
    IT6263_I2C_ModifyReg(handle, i2cAddr, 0xC1, IT6263_HDMI_C1_AVMUTE_MASK, 0);
    IT6263_I2C_WriteReg(handle, i2cAddr, 0xc6, IT6263_HDMI_C6_PKTGENCTRLRPT_MASK | IT6263_HDMI_C6_PKTGENCTRLEN_MASK);

    return kStatus_Success;
}

status_t IT6263_Stop(display_handle_t *handle)
{
    uint8_t i2cAddr = ((it6263_resource_t *)(handle->resource))->i2cAddr / 2;

    IT6263_I2C_ModifyReg(handle, i2cAddr, 0xC1, IT6263_HDMI_C1_AVMUTE_MASK, IT6263_HDMI_C1_AVMUTE_MASK);
    IT6263_I2C_WriteReg(handle, i2cAddr, 0xC6, 0);
    IT6263_I2C_ModifyReg(handle, i2cAddr, 0x04, IT6263_HDMI_04_SOFTVRST_MASK, IT6263_HDMI_04_SOFTVRST_MASK);
    IT6263_I2C_WriteReg(handle, i2cAddr, 0x61, IT6263_HDMI_61_DRV_PWD_MASK | IT6263_HDMI_61_DRV_RST_MASK);

    return kStatus_Success;
}
