/**
 * @file hv_cal_Tlc5971.c
 * @brief TLC5971 led driver cal module
 * @details This file provides the following functions: \n
 *          (1) Initialization and reset functions \n
 *
 * @author HiView SoC Software Team
 * @version 1.0.0
 * @date 2023-12-16
 * @copyright Copyright(c),2023-12, Hiview Software. All rights reserved.
 * @par History:
 * <table>
 * <tr><th>Author                        <th>Date         <th>Change Description
 * <tr><td>HiView SoC Software Team      <td>2023-12-16   <td>init
 * </table>
*/

#include "Common/hv_comm_DataType.h"
#include "Common/Assert/hv_comm_Assert.h"
#include "hv_comm_Define.h"
#include "hv_cal_Gpio.h"
#include "hv_vos_Comm.h"
#include "hv_cal_Tlc5971.h"

#define BITS_PER_LONG       32
/**
 * @brief Create a contiguous bitmask starting at bit position @p l
 *        and ending at position @p h.
 */
#define GENMASK(h, l) \
    (((~0UL) - (1UL << (l)) + 1) & (~0UL >> (BITS_PER_LONG - 1 - (h))))

/**
 * @brief Prepare a bitfield element using @p value with @p mask representing
 *      its field position and width. The result should be combined
 *      with other fields using a logical OR.
 */
//#define FIELD_PREP(mask, value) (((value) * LSB_GET(mask)) & (mask))
#define FIELD_PREP(mask, value) ((value) & (mask))

/** write command for writing control data and GS data to internal registers */
#define TLC5971_WRITE_COMMAND 0x25

/** GS reference clock edge select bit for OUTXn on-off timing control in FC data */
#define TLC5971_BYTE27_CTRL_BIT_OUTTMG BIT(1)

/** Auto display repeat mode enable bit in FC data (0 = disabled, 1 = enabled). */
#define TLC5971_BYTE26_CTRL_BIT_DSPRPT BIT(6)

/** Display timing reset mode enable bit in FC data (0 = disabled, 1 = enabled). */
#define TLC5971_BYTE26_CTRL_BIT_TMGRST BIT(7)

/** Bit mask for write cmd in data byte 27 */
#define TLC5971_BYTE27_WRITE_CMD_MASK GENMASK(7, 2)

/** Bit mask for control bits in data byte 27 */
#define TLC5971_BYTE27_CTRL_MASK GENMASK(1, 0)

/** Bit mask for control bits in data byte 26 */
#define TLC5971_BYTE26_CTRL_MASK GENMASK(7, 5)

/** Bit mask for global brightness control for color 1 in data byte 26, upper 5 bits of GBC */
#define TLC5971_BYTE26_GBC1_MASK GENMASK(4, 0)

/** Bit mask for global brightness control for color 1 in data byte 25, lower 2 bits of GBC */
#define TLC5971_BYTE25_GBC1_MASK GENMASK(7, 6)

/** Bit mask for global brightness control for color 2 in data byte 25, upper 6 bits of GBC */
#define TLC5971_BYTE25_GBC2_MASK GENMASK(5, 0)

/** Bit mask for global brightness control for color 2 in data byte 24, lower 1 bits of GBC */
#define TLC5971_BYTE24_GBC2_MASK BIT(7)

/** Bit mask for global brightness control for color 3 in data byte 24, all 7 bits of GBC */
#define TLC5971_BYTE24_GBC3_MASK GENMASK(6, 0)

/**
 * @brief create data byte 27 from control data
 * @param ucControlData control bits
 * @return UCHAR8 the serialized data byte 27
 */
static inline UCHAR8 Tlc5971_Data_Byte27(UCHAR8 ucControlData)
{
    return FIELD_PREP(TLC5971_BYTE27_WRITE_CMD_MASK, (TLC5971_WRITE_COMMAND << 2)) |
           FIELD_PREP(TLC5971_BYTE27_CTRL_MASK, ucControlData);
}

/**
 * @brief create data byte 26 from control data and color 1 GBC
 * @param ucControlData control bits
 * @param ucGbcColor1 global brightness control for color 1 LEDs
 * @return UCHAR8 the serialized data byte 26
 */
static inline UCHAR8 Tlc5971_Data_Byte26(UCHAR8 ucControlData, UCHAR8 ucGbcColor1)
{
    return FIELD_PREP(TLC5971_BYTE26_CTRL_MASK, ucControlData) |
           FIELD_PREP(TLC5971_BYTE26_GBC1_MASK, ucGbcColor1 >> 2);
}

/**
 * @brief create data byte 25 from color 1 and 2 GBC
 * @param ucGbcColor1 global brightness control for color 1 LEDs
 * @param ucGbcColor2 global brightness control for color 2 LEDs
 * @return UCHAR8 the serialized data byte 25
 */
static inline UCHAR8 Tlc5971_Data_Byte25(UCHAR8 ucGbcColor1, UCHAR8 ucGbcColor2)
{
    return FIELD_PREP(TLC5971_BYTE25_GBC1_MASK, ucGbcColor1 << 6) |
           FIELD_PREP(TLC5971_BYTE25_GBC2_MASK, ucGbcColor2 >> 1);
}

/**
 * @brief create data byte 24 from color 2 and 3 GBC
 * @param ucGbcColor2 global brightness control for color 2 LEDs
 * @param ucGbcColor3 global brightness control for color 3 LEDs
 * @return UCHAR8 the serialized data byte 24
 */
static inline UCHAR8 Tlc5971_Data_Byte24(UCHAR8 ucGbcColor2, UCHAR8 ucGbcColor3)
{
    return FIELD_PREP(TLC5971_BYTE24_GBC2_MASK, ucGbcColor2 << 7) |
           FIELD_PREP(TLC5971_BYTE24_GBC3_MASK, ucGbcColor3);
}

/**
 * @brief map user colors to tlc5971 color order
 * @param uiColorId color id from color mapping
 * @param pstPixelData rgb data to be mapped
 * @return the mapped color value
 */
static UCHAR8 Tlc5971_Map_Color(UINT32 uiColorId, const LedColor *pstPixelData)
{
    UCHAR8 ucTemp = 0;
    switch (uiColorId) 
    {
        case LED_COLOR_ID_RED:
        {
            ucTemp = (UCHAR8)pstPixelData->ucRed;
            break;
        }
        case LED_COLOR_ID_GREEN:
        {
            ucTemp = (UCHAR8)pstPixelData->ucGreen;
            break;
        }
        case LED_COLOR_ID_BLUE:
        {
            ucTemp = (UCHAR8)pstPixelData->ucBlue;
            break;
        }
        default:
        {
            ucTemp = 0;
            break;
        }
    }
    return ucTemp;
}

/**
 * @brief serialize control data and pixel data for device daisy chain
 * the serializer only supports "full" devices, meaning each device is expected
 * to be mounted with all 4 LEDs.
 * @param stDev device pointer
 * @param pstPixels pixel RGB data for daisy chain
 * @param uiPixelsNum number of pixels in daisy chain
 */
static VOID Tlc5971_Fill_Data_Buffer(Tlc5971Dev *pstDev, LedColor *pstPixels, UINT32 uiPixelsNum)
{
    HV_ASSERT_VALID_PTR_VOID(pstDev);
    HV_ASSERT_VALID_PTR_VOID(pstPixels);
    Tlc5971Config *pstCfg = &pstDev->stConfig;
    Tlc5971Data *pstData = &pstDev->stData;
    UCHAR8 *pucDataBuf = pstData->pucDataBuffer;
    HV_ASSERT_VALID_PTR_VOID(pucDataBuf);
    UINT32 uiCount = 0;

    /*
     * tlc5971 device order is reversed as the rgb data for the last device in the daisy chain
     * should be transmitted first.
     */
    for (INT32 iDevId = (uiPixelsNum / TLC5971_PIXELS_PER_DEVICE) - 1; iDevId >= 0; iDevId--) 
    {
        /*
         * The frame format expects a BGR color order for the global brightness control
         * values, but since the led_strip API allows custom color mappings, we simply use
         * color_x terms to keep things generic.
         */
        pucDataBuf[uiCount++] = Tlc5971_Data_Byte27(pstData->ucControlData);
        pucDataBuf[uiCount++] = Tlc5971_Data_Byte26(pstData->ucControlData, pstData->ucGbcColor1);
        pucDataBuf[uiCount++] = Tlc5971_Data_Byte25(pstData->ucGbcColor1,  pstData->ucGbcColor2);
        pucDataBuf[uiCount++] = Tlc5971_Data_Byte24(pstData->ucGbcColor2,  pstData->ucGbcColor3);
        for (INT32 iPixel = (TLC5971_PIXELS_PER_DEVICE - 1); iPixel >= 0; iPixel--)
        {
            /* data is "reversed" so RGB0 comes last, i.e at byte 0 */
            LedColor *pPixelData = &pstPixels[(iDevId * TLC5971_PIXELS_PER_DEVICE) + iPixel];

            /*
             * Convert pixel data into frames, mapping user colors to tlc5971
             * data frame color order (BGR).
             */
            for (UINT32 uiColor = 0; uiColor < pstCfg->ucColorNum; uiColor++)
            {
                UCHAR8 ucTemp = Tlc5971_Map_Color(pstCfg->aucColorMapping[uiColor], pPixelData);
                /*
                 * The tlc5971 rgb values are 16 bit but zephyr's rgb values are
                 * 8 bit. Simply upscale to 16 bit by using the 8 bit value for both
                 * LSB and MSB of the 16 bit word.
                 */
                pucDataBuf[uiCount++] = ucTemp;
                pucDataBuf[uiCount++] = ucTemp;
            }
        }
    }
    return;
}

/**
 * @brief Transmit serialize control data and pixel data for device daisy chain
 * @param stDev device pointer
 * @param uiPixelsNum number of pixels in daisy chain
 * @return Status
 */
static Status Tlc5971_Transmit_Data(Tlc5971Dev *pstDev, UINT32 uiPixelsNum)
{
    HV_ASSERT_VALID_PTR(pstDev);
    Tlc5971Config *pstCfg = &pstDev->stConfig;
    Tlc5971Data *pstData = &pstDev->stData;
    UINT32 uiDataLen = (uiPixelsNum / TLC5971_PIXELS_PER_DEVICE) * TLC5971_PACKET_LEN;
    if (pstCfg->pfSendData)
    {
        return pstCfg->pfSendData(pstData->pucDataBuffer, uiDataLen);
    }
    else
    {
        return HV_FAILURE;
    }
}

/**
 * @brief Update serialize pixel data for device daisy chain
 * @param pstDev device pointer
 */
Status Hv_Cal_Led_Tlc5971_UpdateColor(Tlc5971Dev *pstDev)
{
    HV_ASSERT_VALID_PTR(pstDev);
    Tlc5971Config *pstCfg = &pstDev->stConfig;
    Tlc5971Data *pstData = &pstDev->stData;
    HV_ASSERT_VALID_PTR(pstData->pstPixels);
    if (!pstCfg->ucInitFlag)
    {
        HV_LOG_ERROR(LED, "the tlc5871 chain not init.");
        return HV_FAILURE;
    }

    Tlc5971_Fill_Data_Buffer(pstDev, pstData->pstPixels, pstCfg->ucPixelsNum);
    return Tlc5971_Transmit_Data(pstDev, pstCfg->ucPixelsNum);
}

/**
 * @brief Fill GS Data Api for TLC5971
 * @param pstDev device pointer
 * @param uiDevId TLC5971 device id of daisy chain
 * @param enChId TLC5971 channel id
 * @param pstPixel pixel RGB data for daisy chain
 */
Status Hv_Cal_Led_Tlc5971_FillGsPixels(Tlc5971Dev *pstDev, LedDeviceId enDevId, LedChannelId enChId, LedColor *pstPixel)
{
    HV_ASSERT_VALID_PTR(pstPixel);
    HV_ASSERT_VALID_PTR(pstDev);
    HV_ASSERT_TRUE(enDevId < HV_BOARD_CONFIG_LED_IC_NUM);
    HV_ASSERT_TRUE(enChId < TLC5971_PIXELS_PER_DEVICE);
    Tlc5971Data *pstData = &pstDev->stData;
    HV_ASSERT_VALID_PTR(pstData->pstPixels);
    if (!pstDev->stConfig.ucInitFlag)
    {
        HV_LOG_ERROR(LED, "the tlc5871 chain not init.");
        return HV_FAILURE;
    }

    LedColor *pstPixelData = &pstData->pstPixels[(enDevId * TLC5971_PIXELS_PER_DEVICE) + enChId];
    Hv_Vos_Memcpy(pstPixelData, pstPixel, sizeof(LedColor));
    return HV_SUCCESS;
}

/**
 * @brief Clear GS Data Api for TLC5971
 * @param pstDev device pointer
 */
Status Hv_Cal_Led_Tlc5971_ClearAllGsPixels(Tlc5971Dev *pstDev)
{
    HV_ASSERT_VALID_PTR(pstDev);
    Tlc5971Config *pstCfg = &pstDev->stConfig;
    Tlc5971Data *pstData = &pstDev->stData;
    HV_ASSERT_VALID_PTR(pstData->pstPixels);
    if (!pstCfg->ucInitFlag)
    {
        HV_LOG_ERROR(LED, "the tlc5871 chain not init.");
        return HV_FAILURE;
    }
    Hv_Vos_Memset(pstData->pstPixels, 0, pstCfg->ucPixelsNum * sizeof(LedColor));
    return HV_SUCCESS;
}

/**
 * @brief dump serialize control data and pixel data for device daisy chain
 * the serializer only supports "full" devices, meaning each device is expected
 * to be mounted with all 4 LEDs.
 * @param pstDev device pointer
 */
VOID Hv_Cal_Led_Tlc5971_Dump_Data_Buffer(Tlc5971Dev *pstDev)
{
    HV_ASSERT_VALID_PTR_VOID(pstDev);
    Tlc5971Config *pstCfg = &pstDev->stConfig;
    Tlc5971Data *pstData = &pstDev->stData;
    UCHAR8 *pucDataBuf = pstData->pucDataBuffer;
    HV_ASSERT_VALID_PTR_VOID(pucDataBuf);
    UINT32 uiCount = 0;
    if (!pstCfg->ucInitFlag)
    {
        HV_LOG_ERROR(LED, "the tlc5871 chain not init.");
        return;
    }

    /*
     * tlc5971 device order is reversed as the rgb data for the last device in the daisy chain
     * should be transmitted first.
     */
    for (INT32 iDevId = (pstCfg->ucPixelsNum / TLC5971_PIXELS_PER_DEVICE) - 1; iDevId >= 0; iDevId--) 
    {
        HV_LOGI("Dev[%u]: ", iDevId);
        HV_LOGI("[%d] 0x%0x", uiCount, pucDataBuf[uiCount]);
        uiCount++;
        HV_LOGI("Ctrl[%u]: ");
        HV_LOGI("[%d] 0x%0x", uiCount, pucDataBuf[uiCount]);
        uiCount++;
        HV_LOGI("[%d] 0x%0x", uiCount, pucDataBuf[uiCount]);
        uiCount++;
        HV_LOGI("[%d] 0x%0x", uiCount, pucDataBuf[uiCount]);
        uiCount++;
        HV_LOGI("RGB: ");
        for (INT32 iPixel = (TLC5971_PIXELS_PER_DEVICE - 1); iPixel >= 0; iPixel--)
        {
            for (UINT32 uiColor = 0; uiColor < pstCfg->ucColorNum; uiColor++)
            {
                /* GS data 16bits*/
                HV_LOGI("[%d] 0x%0x", uiCount, pucDataBuf[uiCount]);
                uiCount++;
                HV_LOGI("[%d] 0x%0x", uiCount, pucDataBuf[uiCount]);
                uiCount++;
            }
        }
    }
    return;
}

/**
 * @brief Set serialize global brightness for device daisy chain
 * @param stDev device pointer
 * @param pstPixel pixel RGB data for daisy chain
 */
Status Hv_Cal_Led_Tlc5971_SetGlobalBrightness(Tlc5971Dev *pstDev, LedGlBc *pstGbc)
{
    HV_ASSERT_VALID_PTR(pstDev);
    HV_ASSERT_VALID_PTR(pstGbc);
    Tlc5971Config *pstCfg = &pstDev->stConfig;
    Tlc5971Data *pstData = &pstDev->stData;
    Status uiRet = HV_FAILURE;
    if (!pstCfg->ucInitFlag)
    {
        HV_LOG_ERROR(LED, "the tlc5871 chain not init.");
        return HV_FAILURE;
    }

    if ((pstGbc->ucRedGbc <= TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX)
         && (pstGbc->ucGreenGbc <= TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX)
         && (pstGbc->ucBlueGbc <= TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX)) 
    {
        LedColor *pstPixelData = (LedColor *)pstGbc;
        pstData->ucGbcColor1 = Tlc5971_Map_Color(pstCfg->aucColorMapping[0], pstPixelData);
        pstData->ucGbcColor2 = Tlc5971_Map_Color(pstCfg->aucColorMapping[1], pstPixelData);
        pstData->ucGbcColor3 = Tlc5971_Map_Color(pstCfg->aucColorMapping[2], pstPixelData);
        uiRet = HV_SUCCESS;
    }
    else
    {
        HV_LOG_ERROR(LED, "The brightness value > tlc5971 Max Th(%u)", TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX);
        uiRet = HV_FAILURE;
    }

    return uiRet;
}

/**
 * @brief Initialize serialize device daisy chain
 * @param pstDev device pointer
 * @param pvParams paramas for init
 */
Status Hv_Cal_Led_Tlc5971_Init(Tlc5971Dev *pstDev, VOID* pvParams)
{
    HV_ASSERT_VALID_PTR(pstDev);
    Tlc5971Config *pstCfg = &pstDev->stConfig;
    Tlc5971Data *pstData = &pstDev->stData;

    if ((pstCfg->ucPixelsNum % TLC5971_PIXELS_PER_DEVICE) != 0)
    {
        HV_LOG_ERROR(LED, "The tlc5871 chain length must be multiple of 4");
        return HV_FAILURE;
    }

    if (pstCfg->ucColorNum != TLC5971_NUMBER_OF_COLORS)
    {
        HV_LOG_ERROR(LED, "The tlc5971 only supports %i colors", TLC5971_NUMBER_OF_COLORS);
        return HV_FAILURE;
    }

    /*
     * set up sane defaults for control data.
     * unblanks leds, enables auto display repeat, enables timing resetm uses internal clock for
     * PWM generation and sets the GS reference clock edge select to rising edge
     */
    pstData->ucControlData = TLC5971_BYTE27_CTRL_BIT_OUTTMG | TLC5971_BYTE26_CTRL_BIT_DSPRPT
                             | TLC5971_BYTE26_CTRL_BIT_TMGRST;

    return HV_SUCCESS;
}
