/**************************************************************************************************
*******
**************************************************************************************************/

/*******************************************************************************
    @file     i2c.c
    @brief    Contains all functions support for i2c driver
    @version  0.0
    @date     25. Oct. 2017
    @author   qing.han



*******************************************************************************/

/*******************************************************************************
    @ Module           :  pre-compiler
    @ Description    :  NULL
*******************************************************************************/
#define _I2C_CMD_

/*******************************************************************************
    @ Module               :  Includes
    @ Description    :  None
*******************************************************************************/
#include "rom_sym_def.h"

#include "types.h"

#include "gpio.h"

#include "i2c.h"

#include "clock.h"
#include "log.h"
#include "error.h"
#include "OSAL.h"
#include "pwrmgr.h"

#define I2C_OP_TIMEOUT  100   //100ms for an Byte operation
extern  uint32_t pclk;


/**************************************************************************************
    @fn          hal_master_send_read_cmd

    @brief       This function process for master send read command;It's vaild when the chip act as master

    input parameters

    @param       uint8_t len: read length

    output parameters

    @param       None.

    @return      None.
 **************************************************************************************/
static void hal_master_send_read_cmd(void* pi2c, uint8_t len)
{
    uint8_t i;
    AP_I2C_TypeDef* pi2cdev = (AP_I2C_TypeDef*)pi2c;

    for(i=0; i<len; i++)
    {
        I2C_READ_CMD(pi2cdev);
    }
}


static void _hal_i2c_send_byte(void* pi2c, uint8_t data)
{
    AP_I2C_TypeDef* pi2cdev = (AP_I2C_TypeDef*)pi2c;
    pi2cdev->IC_DATA_CMD = data;  //data
}

/**************************************************************************************
    @fn          hal_i2c_send

    @brief       This function process for send a serial(programe length) data by i2c interface

    input parameters

    @param       unsigned char* str: send data(string)
                uint32_t len: send length

    output parameters

    @param       None.

    @return      None.
 **************************************************************************************/
int hal_i2c_send(void* pi2c, uint8_t* str,uint8_t len)
{
    uint8_t i;
    AP_I2C_TypeDef* pi2cdev = (AP_I2C_TypeDef*)pi2c;

    if(pi2cdev != AP_I2C0 && pi2cdev != AP_I2C1)
    {
        return PPlus_ERR_INVALID_PARAM;
    }

    for(i=0; i<len; i++)
    {
        _hal_i2c_send_byte(pi2c, str[i]);
    }

    return PPlus_SUCCESS;
}


int hal_i2c_wait_tx_completed(void* pi2c)
{
    int cnt = 0;
    //uint32 st0, st;
    AP_I2C_TypeDef* pi2cdev = (AP_I2C_TypeDef*)pi2c;
    I2C_INIT_TOUT(to);

    if(pi2cdev != AP_I2C0 && pi2cdev != AP_I2C1)
    {
        return PPlus_ERR_INVALID_PARAM;
    }

    //st0 = pi2cdev->IC_INTR_STAT;
    //st = pi2cdev->IC_RAW_INTR_STAT;
    while(1)
    {
        cnt++;

        if(pi2cdev->IC_RAW_INTR_STAT&0x200)//check tx empty
            break;

        I2C_CHECK_TOUT(to, I2C_OP_TIMEOUT, "hal_i2c_wait_tx_completed TO\n");
    }

    //LOG("I2c Tx empty:%x, %x\n", st0,st);
    return PPlus_SUCCESS;
}


void* hal_i2c_init(i2c_dev_t dev, I2C_CLOCK_e i2c_clock_rate)
{
    int pclk = clk_get_pclk();
    AP_I2C_TypeDef* pi2cdev = NULL;

    if(dev == I2C_0)
    {
        pi2cdev = AP_I2C0;
        hal_clk_gate_enable(MOD_I2C0);
    }
    else if(dev == I2C_1)
    {
        pi2cdev = AP_I2C1;
        hal_clk_gate_enable(MOD_I2C1);
    }
    else
    {
        return NULL;
    }

    pi2cdev->IC_ENABLE=0;
    pi2cdev->IC_CON=0x61;

    if(i2c_clock_rate==I2C_CLOCK_100K)
    {
        pi2cdev->IC_CON= ((pi2cdev->IC_CON) & 0xfffffff9)|(0x01 << 1);

        if(pclk==16000000)
        {
            pi2cdev->IC_SS_SCL_HCNT=70;  //16
            pi2cdev->IC_SS_SCL_LCNT=76;  //32)
        }
        else if(pclk==32000000)
        {
            pi2cdev->IC_SS_SCL_HCNT=148;  //16
            pi2cdev->IC_SS_SCL_LCNT=154;  //32)
        }
        else if(pclk==48000000)
        {
            pi2cdev->IC_SS_SCL_HCNT=230;  //16
            pi2cdev->IC_SS_SCL_LCNT=236;  //32)
        }
        else if(pclk==64000000)
        {
            pi2cdev->IC_SS_SCL_HCNT=307;  //16
            pi2cdev->IC_SS_SCL_LCNT=320;  //32)
        }
        else if(pclk==96000000)
        {
            pi2cdev->IC_SS_SCL_HCNT=460;  //16
            pi2cdev->IC_SS_SCL_LCNT=470;  //32)
        }
    }
    else if(i2c_clock_rate==I2C_CLOCK_400K)
    {
        pi2cdev->IC_CON= ((pi2cdev->IC_CON) & 0xfffffff9)|(0x02 << 1);

        if(pclk==16000000)
        {
            pi2cdev->IC_FS_SCL_HCNT=10;  //16
            pi2cdev->IC_FS_SCL_LCNT=17;  //32)
        }
        else if(pclk==32000000)
        {
            pi2cdev->IC_FS_SCL_HCNT=30;  //16
            pi2cdev->IC_FS_SCL_LCNT=35;  //32)
        }
        else if(pclk==48000000)
        {
            pi2cdev->IC_FS_SCL_HCNT=48;  //16
            pi2cdev->IC_FS_SCL_LCNT=54;  //32)
        }
        else if(pclk==64000000)
        {
            pi2cdev->IC_FS_SCL_HCNT=67;  //16
            pi2cdev->IC_FS_SCL_LCNT=75;  //32)
        }
        else if(pclk==96000000)
        {
            pi2cdev->IC_FS_SCL_HCNT=105;  //16
            pi2cdev->IC_FS_SCL_LCNT=113;  //32)
        }
    }

    pi2cdev->IC_TAR = I2C_MASTER_ADDR_DEF;
    pi2cdev->IC_INTR_MASK=0;
    pi2cdev->IC_RX_TL=0x0;
    pi2cdev->IC_TX_TL=0x1;
    pi2cdev->IC_ENABLE=1;
    return (void*)pi2cdev;
}


int hal_i2c_deinit(void* pi2c)
{
    AP_I2C_TypeDef* pi2cdev = (AP_I2C_TypeDef*)pi2c;

    if(pi2cdev == AP_I2C0)
    {
        pi2cdev->IC_ENABLE=0;
        hal_clk_gate_disable(MOD_I2C0);
    }
    else if(pi2cdev == AP_I2C1)
    {
        pi2cdev->IC_ENABLE=0;
        hal_clk_gate_enable(MOD_I2C1);
    }
    else
    {
        return PPlus_ERR_INVALID_PARAM;
    }

    return PPlus_SUCCESS;
}


/**************************************************************************************
    @fn          hal_i2c_pin_init

    @brief       This function process for i2c pin initial(2 lines);You can use two i2c,i2c0 and i2c1,should programe by USE_AP_I2CX

    input parameters

    @param       gpio_pin_e pin_sda: define sda_pin
                gpio_pin_e pin_clk: define clk_pin

    output parameters

    @param       None.

    @return      None.
 **************************************************************************************/
int hal_i2c_pin_init(i2c_dev_t dev, gpio_pin_e pin_sda, gpio_pin_e pin_clk)
{
    if(dev == I2C_0)
    {
        hal_gpio_fmux_set(pin_clk, FMUX_IIC0_SCL);
        hal_gpio_fmux_set(pin_sda, FMUX_IIC0_SDA);
    }
    else if(dev == I2C_1)
    {
        hal_gpio_fmux_set(pin_clk, FMUX_IIC1_SCL);
        hal_gpio_fmux_set(pin_sda, FMUX_IIC1_SDA);
    }
    else
    {
        return PPlus_ERR_INVALID_PARAM;
    }

    hal_gpio_pull_set(pin_sda,GPIO_PULL_UP_S);
    hal_gpio_pull_set(pin_clk,GPIO_PULL_UP_S);

    if(dev == I2C_0)
    {
        hal_clk_gate_enable(MOD_I2C0);
    }
    else if(dev == I2C_1)
    {
        hal_clk_gate_enable(MOD_I2C1);
    }

    return PPlus_SUCCESS;
}

int hal_i2c_tx_start(void* pi2c)
{
    AP_I2C_TypeDef* pi2cdev = (AP_I2C_TypeDef*)pi2c;

    if(pi2cdev != AP_I2C0 && pi2cdev != AP_I2C1)
    {
        return PPlus_ERR_INVALID_PARAM;
    }

    pi2cdev->IC_ENABLE=1;
    return PPlus_SUCCESS;
}

/**************************************************************************************
    @fn          hal_i2c_addr_update

    @brief       This function process for tar update

    input parameters

    @param       uint8_t addr: address

    output parameters

    @param       None.

    @return      None.
 **************************************************************************************/
int hal_i2c_addr_update(void* pi2c, uint8_t addr)
{
    AP_I2C_TypeDef* pi2cdev = (AP_I2C_TypeDef*)pi2c;

    if(pi2cdev != AP_I2C0 && pi2cdev != AP_I2C1)
    {
        return PPlus_ERR_INVALID_PARAM;
    }

    pi2cdev->IC_ENABLE=0;
    pi2cdev->IC_TAR = addr;
    pi2cdev->IC_ENABLE=0;
    return PPlus_SUCCESS;
}


int _hal_i2c_read_s(void* pi2c, uint8_t slave_addr, uint8_t reg, uint8_t* data, uint8_t size)
{
    I2C_INIT_TOUT(to);
    AP_I2C_TypeDef* pi2cdev = (AP_I2C_TypeDef*)pi2c;

    if(pi2cdev != AP_I2C0 && pi2cdev != AP_I2C1)
    {
        return PPlus_ERR_INVALID_PARAM;
    }

    hal_i2c_addr_update(pi2c, slave_addr);
    HAL_ENTER_CRITICAL_SECTION();
    hal_i2c_tx_start(pi2c);
    _hal_i2c_send_byte(pi2c, reg);
    hal_master_send_read_cmd(pi2c, size);
    HAL_EXIT_CRITICAL_SECTION();

    while(1)
    {
        if(I2C_RX_FIFO_NOT_EMPTY(pi2cdev))
        {
            *data = (pi2cdev->IC_DATA_CMD&0xff);
            data++;
            size --;

            if(size == 0)
                break;
        }

        I2C_CHECK_TOUT(to, I2C_OP_TIMEOUT*size, "I2C RD TO\n");
    }

    return PPlus_SUCCESS;
}



int hal_i2c_read(
    void* pi2c,
    uint8_t slave_addr,
    uint8_t reg,
    uint8_t* data,
    uint8_t size)
{
    uint8_t cnt;
    int ret = PPlus_SUCCESS;
    AP_I2C_TypeDef* pi2cdev = (AP_I2C_TypeDef*)pi2c;

    if(pi2cdev != AP_I2C0 && pi2cdev != AP_I2C1)
    {
        return PPlus_ERR_INVALID_PARAM;
    }

    while(size)
    {
        cnt = (size >7) ? 7 : size;
        size -= cnt;
        ret = _hal_i2c_read_s(pi2c, slave_addr, reg, data, cnt);

        if(ret != PPlus_SUCCESS)
            break;

        data += cnt;
    }

    return ret;
}



