/**
 * @file hv_drv_GpioSmutI2c.c
 * @brief gpio smulitaion i2c driver  layer file.
 * @details This file provides the following functions: \n
 *          (1) gpio simulate i2c master tx/rx \n
 *          (2) gpio simulate i2c init \n
 *
 * @author HiView SoC Software Team
 * @version 1.0.0
 * @date 2023-06-06
 * @copyright Copyright(c),2023-6, Hiview Software. All rights reserved.
 * @par History:
 * <table>
 * <tr><th>Author       <th>Date            <th>Change Description
 * </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_drv_GpioSimI2c.h"

static GpioSimI2c s_stgpiosimi2c[SIMI2CMAX-SIMI2C0] = {
                                        {INVALID_PARAM_UHCAR8, INVALID_PARAM_UHCAR8},
                                        {INVALID_PARAM_UHCAR8, INVALID_PARAM_UHCAR8},
                                        {INVALID_PARAM_UHCAR8, INVALID_PARAM_UHCAR8}
                                      };

#define SET_SCL_OUT(uiIndex)          Hv_Cal_Gpio_SetDirection(s_stgpiosimi2c[uiIndex].ucSclPin, GPIO_DIR_OUTPUT)
#define SET_SCL_IN(uiIndex)           Hv_Cal_Gpio_SetDirection(s_stgpiosimi2c[uiIndex].ucSclPin, GPIO_DIR_INPUT)
#define SET_SDA_OUT(uiIndex)          Hv_Cal_Gpio_SetDirection(s_stgpiosimi2c[uiIndex].ucSdaPin, GPIO_DIR_OUTPUT)
#define SET_SDA_IN(uiIndex)           Hv_Cal_Gpio_SetDirection(s_stgpiosimi2c[uiIndex].ucSdaPin, GPIO_DIR_INPUT)

#define SET_SCL_HIGH(uiIndex)         Hv_Cal_Gpio_SetPinLevel(s_stgpiosimi2c[uiIndex].ucSclPin, GPIO_LEVEL_HIGH)
#define SET_SCL_LOW(uiIndex)          Hv_Cal_Gpio_SetPinLevel(s_stgpiosimi2c[uiIndex].ucSclPin, GPIO_LEVEL_LOW)
#define SET_SDA_HIGH(uiIndex)         Hv_Cal_Gpio_SetPinLevel(s_stgpiosimi2c[uiIndex].ucSdaPin, GPIO_LEVEL_HIGH)
#define SET_SDA_LOW(uiIndex)          Hv_Cal_Gpio_SetPinLevel(s_stgpiosimi2c[uiIndex].ucSdaPin, GPIO_LEVEL_LOW)
#define GET_SDA_LEVEL(uiIndex)        Hv_Cal_Gpio_GetPinLevel(s_stgpiosimi2c[uiIndex].ucSdaPin)

#define GPIOSIMI2C_DELAY                2U
#define GPIOSIMI2C_WAIT_ACK_TIMES       200U
#define GPIOSIMI2C_ADDR_WRITE           0x00
#define GPIOSIMI2C_ADDR_READ            0x01

/** Initialize i2c
 *  @param pstInitParam pointer to i2c configuration parameters
 *  @return SUCCESS/FAILURE
 */
Status Hv_Drv_GpioSimI2c_Init(I2cBusID enBus, const GpioSimI2c *pstGpioSimI2c)
{
    HV_ASSERT_VALID_PTR_RET(pstGpioSimI2c, HV_FAILURE);
    if (enBus < SIMI2C0)
    {
        return HV_FAILURE;
    }

    UINT32 uiIndex = enBus - SIMI2C0;

    memcpy(&s_stgpiosimi2c[uiIndex], pstGpioSimI2c, sizeof(GpioSimI2c));
    SET_SCL_OUT(uiIndex);
    SET_SDA_OUT(uiIndex);
    SET_SCL_HIGH(uiIndex);
    SET_SDA_HIGH(uiIndex);
    return HV_SUCCESS;
}

/** I2c start signal
 *@SCL keep high, SDA change from high to low
 */
static VOID Hv_GpioSimI2c_Start(UINT32 uiIndex)
{
    SET_SDA_OUT(uiIndex);
    SET_SCL_HIGH(uiIndex);
    SET_SDA_HIGH(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SDA_LOW(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SCL_LOW(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
}

/** I2c stop  signal
 *@SCL keep high, SDA change from low to high
 */
static VOID Hv_GpioSimI2c_Stop(UINT32 uiIndex)
{
    SET_SDA_OUT(uiIndex);
    SET_SDA_LOW(uiIndex);
    SET_SCL_LOW(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SCL_HIGH(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SDA_HIGH(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
}

/** master wait Ack signal
 *@Pull up SCL high, to read SDA level high or low
 */
static Status Hv_GpioSimI2c_Wait_Ack(UINT32 uiIndex)
{
    UINT32 uiWaitTime = 0;
    SET_SDA_IN(uiIndex);
    SET_SCL_LOW(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SCL_HIGH(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);

    while (GET_SDA_LEVEL(uiIndex))
    {
        if (uiWaitTime > GPIOSIMI2C_WAIT_ACK_TIMES)
        {
            Hv_GpioSimI2c_Stop(uiIndex);
            HV_LOGW("wait ack timeout!\n");
            return HV_FAILURE;
        }
        uiWaitTime++;
    }
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SCL_LOW(uiIndex);
    return HV_SUCCESS;
}

/** send Ack signal
 *@SCL keep high, SDA create a valid low level
 */
static VOID Hv_GpioSimI2c_Send_Ack(UINT32 uiIndex)
{
    SET_SDA_OUT(uiIndex);
    SET_SCL_LOW(uiIndex);
    SET_SDA_LOW(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SCL_HIGH(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SCL_LOW(uiIndex);
}

/** send NAck signal
 *@SCL keep high, SDA create a valid high level
 */
static VOID Hv_GpioSimI2c_Send_Nack(UINT32 uiIndex)
{
    SET_SDA_OUT(uiIndex);
    SET_SCL_LOW(uiIndex);
    SET_SDA_HIGH(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SCL_HIGH(uiIndex);
    Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    SET_SCL_LOW(uiIndex);
}

/** send data 1 byte
 *@ Send data bit by bit and MSB first;
 *@ When SCL keep high, data is valid;
 *@ when SCL change to low,  SDA permited to change.
 */
static VOID Hv_GpioSimI2c_Write_Byte(UINT32 uiIndex, UCHAR8 ucByte)
{
    UCHAR8 ucStep = 0;
    SET_SDA_OUT(uiIndex);
    SET_SCL_LOW(uiIndex);
    for (ucStep = 0;ucStep < 8; ucStep++)
    {
        if ((ucByte<<ucStep)&0x80)
        {
            SET_SDA_HIGH(uiIndex);
        }
        else
        {
            SET_SDA_LOW(uiIndex);
        }

        Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
        SET_SCL_HIGH(uiIndex);
        Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
        SET_SCL_LOW(uiIndex);
        Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    }
}

/** receive 1 byte
 *@Pull SCL high, to read SDA data from slave device  bit by bit
 */
static UCHAR8 Hv_GpioSimI2c_Read_Byte(UINT32 uiIndex)
{
    UCHAR8 ucByte = 0x00;
    UCHAR8 ucStep = 0;
    SET_SDA_IN(uiIndex);
    for (ucStep = 0; ucStep < 8; ucStep++)
    {
        SET_SCL_LOW(uiIndex);
        Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
        SET_SCL_HIGH(uiIndex);
        ucByte = ucByte<<1;
        if (GET_SDA_LEVEL(uiIndex))
        {
            ucByte = ucByte | 0x01;
        }
        Hv_Vos_Delayus(GPIOSIMI2C_DELAY);
    }
    SET_SCL_LOW(uiIndex);

    return ucByte;
}

/** Send in master mode an amount of data
 *  @param ucDevAddr target device address
 *  @param pucRegAddr pointer to register address
 *  @param usRegAddrSize register address length
 *  @param pucData pointer to data buffer to be transmited
 *  @param usDataSize amount of data to be sent
 *  @retval Status SUCESS/FAILURE
*/
Status Hv_Drv_GpioSimI2c_SendData(I2cBusID enBus, UCHAR8 ucDevAddr, UCHAR8 *pucRegAddr,
                                           USHORT16 usRegAddrSize, UCHAR8 *pcData, UINT32 uiDatasize)
{
    if (enBus < SIMI2C0)
    {
        return HV_FAILURE;
    }
    UINT32 uiSize = 0;
    UINT32 uiIndex = enBus - SIMI2C0;

    //Start
    Hv_GpioSimI2c_Start(uiIndex);

    //Write write device addr
    Hv_GpioSimI2c_Write_Byte(uiIndex, ucDevAddr + GPIOSIMI2C_ADDR_WRITE);
    HV_ASSERT_SUCCESS_RET(Hv_GpioSimI2c_Wait_Ack(uiIndex), HV_FAILURE);

    //Write register addr
    for (uiSize = 0; uiSize < usRegAddrSize; uiSize++)
    {
        Hv_GpioSimI2c_Write_Byte(uiIndex, pucRegAddr[uiSize]);
        HV_ASSERT_SUCCESS_RET(Hv_GpioSimI2c_Wait_Ack(uiIndex), HV_FAILURE);
    }

    //Write Data
    for (uiSize = 0; uiSize < uiDatasize; uiSize++)
    {
        Hv_GpioSimI2c_Write_Byte(uiIndex, pcData[uiSize]);
        HV_ASSERT_SUCCESS_RET(Hv_GpioSimI2c_Wait_Ack(uiIndex), HV_FAILURE);
    }

    //Stop
    Hv_GpioSimI2c_Stop(uiIndex);
    return HV_SUCCESS;
}

/** Receive in master mode an amount of data
*  @param ucDevAddr target device address
*  @param pucRegAddr pointer to register address
*  @param usRegAddrSize register address length
*  @param pucData pointer to data buffer to be transmited
*  @param usDataSize amount of data to be receive
*  @retval Status SUCESS/FAILURE
*/
Status Hv_Drv_GpioSimI2c_RecvData(I2cBusID enBus, UCHAR8 ucDevAddr, UCHAR8 *pucRegAddr,
                                           USHORT16 usRegAddrSize, UCHAR8 *pucData, USHORT16 usDataSize)
{
    if (enBus < SIMI2C0)
    {
        return HV_FAILURE;
    }
    UINT32 uiSize = 0;
    UINT32 uiIndex = enBus - SIMI2C0;
    //Start
    Hv_GpioSimI2c_Start(uiIndex);

    //Write write device addr
    Hv_GpioSimI2c_Write_Byte(uiIndex, ucDevAddr + GPIOSIMI2C_ADDR_WRITE);
    HV_ASSERT_SUCCESS_RET(Hv_GpioSimI2c_Wait_Ack(uiIndex), HV_FAILURE);

    //Write register addr
    for (uiSize = 0; uiSize < usRegAddrSize; uiSize++)
    {
        Hv_GpioSimI2c_Write_Byte(uiIndex, pucRegAddr[uiSize]);
        HV_ASSERT_SUCCESS_RET(Hv_GpioSimI2c_Wait_Ack(uiIndex), HV_FAILURE);
    }

    //Start
    Hv_GpioSimI2c_Start(uiIndex);

    //Write read device addr
    Hv_GpioSimI2c_Write_Byte(uiIndex, ucDevAddr + GPIOSIMI2C_ADDR_READ);
    HV_ASSERT_SUCCESS_RET(Hv_GpioSimI2c_Wait_Ack(uiIndex), HV_FAILURE);

    for (uiSize = 0; uiSize < usDataSize; uiSize++)
    {
        pucData[uiSize] = Hv_GpioSimI2c_Read_Byte(uiIndex);
        if (usDataSize && ((usDataSize - 1) == uiSize))//last one byte, Nack
        {
            Hv_GpioSimI2c_Send_Nack(uiIndex);
        }
        else
        {
            Hv_GpioSimI2c_Send_Ack(uiIndex);
        }
    }

    //Stop
    Hv_GpioSimI2c_Stop(uiIndex);
    return HV_SUCCESS;
}