/*
 * Copyright (c) 2021 HPMicro
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

#include "hpm_i2c_drv.h"

#ifndef HPM_I2C_DRV_DEFAULT_TPM
#define HPM_I2C_DRV_DEFAULT_TPM (0U)
#endif

#ifndef HPM_I2C_DRV_DEFAULT_RETRY_COUNT
#define HPM_I2C_DRV_DEFAULT_RETRY_COUNT (5000U)
#endif

#define period_in_100ps(freq) (10000000000UL / (freq))

typedef struct {
    uint32_t t_high;
    uint32_t t_low;
    uint16_t t_sp;
    uint16_t t_sudat;
    uint16_t t_hddat;
    uint16_t t_sclhi;
    uint16_t t_sclratio;
} i2c_timing_t;

static hpm_stat_t i2c_configure_timing(uint32_t src_clk_in_hz,
                                       i2c_mode_t i2c_mode,
                                       i2c_timing_t *timing)
{
    int32_t setup_time, hold_time, period;
    int32_t temp1, temp2, temp3;
    int32_t tpclk = period_in_100ps(src_clk_in_hz);

    switch (i2c_mode) {
    /*
     *          |Standard mode | Fast mode | Fast mode plus | Uint
     * ---------+--------------+-----------+----------------+-------
     *  t_high  |     4.0      |    0.6    |     0.26       |   us
     *  t_low   |     4.7      |    1.3    |     0.5        |   us
     *
     */
    /* time uint: 100ps */
    case i2c_mode_fast:
        timing->t_high = 6000;
        timing->t_low = 13000;
        timing->t_sclratio = 2;
        setup_time = 1000;
        hold_time = 3000;
        period = period_in_100ps(400000); /**< baudrate 400KHz */
        break;
    case i2c_mode_fast_plus:
        timing->t_high = 2600;
        timing->t_low = 5000;
        timing->t_sclratio = 2;
        setup_time = 500;
        hold_time = 0;
        period = period_in_100ps(1000000); /**< baudrate 1MHz */
        break;
    case i2c_mode_normal:
        timing->t_high = 40000;
        timing->t_low = 47000;
        timing->t_sclratio = 1;
        setup_time = 2500;
        hold_time = 3000;
        period = period_in_100ps(100000); /**< baudrate 100KHz */
        break;
    default:
        return status_i2c_not_supported;
    }

    /*
     * Spike Suppression | Standard | Fast mode | Fast mode plus | Uint
     *                   | mode     |           |                |
     * ------------------+----------+-----------+----------------+-------
     *    t_sp (min)     |    -     |  0 - 50   |    0 - 50      |   ns
     *
     * T_SP = 50ns / (tpclk * (TPM + 1))
     */
    timing->t_sp = 500 / period_in_100ps(src_clk_in_hz) / (HPM_I2C_DRV_DEFAULT_TPM + 1);

    /*
     * Setup time       |Standard mode | Fast mode | Fast mode plus | Uint
     * -----------------+--------------+-----------+----------------+-------
     *  t_sudat (min)   |     250      |    100    |     50         |   ns
     *
     * Setup time = (2 * tpclk) + (2 + T_SP + T_SUDAT) * tpclk * (TPM + 1)
     */
    temp1 = (setup_time - 2 * tpclk) / tpclk / (HPM_I2C_DRV_DEFAULT_TPM + 1) - 2 - timing->t_sp;
    timing->t_sudat = MAX(temp1, 0);
    /*
     * Hold time       |Standard mode | Fast mode | Fast mode plus | Uint
     * ----------------+--------------+-----------+----------------+-------
     *  t_hddata (min) |     300      |    300    |     0          |   ns
     *
     * Hold time = (2 * tpclk) + (2 + T_SP + T_HDDAT) * tpclk * (TPM + 1)
     */
    temp1 = (hold_time - 2 * tpclk) / tpclk / (HPM_I2C_DRV_DEFAULT_TPM + 1) - 2 - timing->t_sp;
    timing->t_hddat = MAX(temp1, 0);

    /*
     * SCLK High period = (2 * tpclk) + (2 + T_SP + T_SCLHi) * tpclk * (TPM + 1) > t_high;
     */
    temp1 = (timing->t_high - 2 * tpclk) / tpclk / (HPM_I2C_DRV_DEFAULT_TPM + 1) - 2 - timing->t_sp;

    /*
     * SCLK High period = (2 * tpclk) + (2 + T_SP + T_SCLHi) * tpclk * (TPM + 1) > period / (1 + ratio);
     */
    temp2 = (period / (1 + timing->t_sclratio) - 2 * tpclk) / tpclk / (HPM_I2C_DRV_DEFAULT_TPM + 1) - 2 - timing->t_sp;

    /*
     * SCLK Low period = (2 * tpclk) + (2 + T_SP + T_SCLHi * ratio) * tpclk * (TPM + 1) > t_low;
     */
    temp3 = ((timing->t_low - 2 * tpclk) / tpclk / (HPM_I2C_DRV_DEFAULT_TPM + 1) - 2 - timing->t_sp) / (timing->t_sclratio);

    timing->t_sclhi = MAX(MAX(temp1, temp2), temp3);

    /* update high_period and low_period to calculated value */
    timing->t_high = 2 * tpclk + (2 + timing->t_sp + timing->t_sclhi) * tpclk;
    timing->t_low = timing->t_high * timing->t_sclratio;

    return status_success;
}

void i2c_reset(I2C_Type *ptr)
{
    ptr->CTRL = 0;
    ptr->CMD = I2C_CMD_RESET;
    ptr->SETUP &= ~I2C_SETUP_IICEN_MASK;
}

hpm_stat_t i2c_init_master(I2C_Type *ptr, uint32_t src_clk_in_hz, i2c_config_t *config)
{
    hpm_stat_t stat = status_success;
    i2c_timing_t timing = {0};

    i2c_reset(ptr);

    stat = i2c_configure_timing(src_clk_in_hz, config->i2c_mode, &timing);
    if (status_success != stat) {
        return stat;
    }

    ptr->TPM = I2C_TPM_TPM_SET(HPM_I2C_DRV_DEFAULT_TPM);

    ptr->SETUP = I2C_SETUP_T_SP_SET(timing.t_sp)
        | I2C_SETUP_T_SUDAT_SET(timing.t_sudat)
        | I2C_SETUP_T_HDDAT_SET(timing.t_hddat)
        | I2C_SETUP_T_SCLRADIO_SET(timing.t_sclratio - 1)
        | I2C_SETUP_T_SCLHI_SET(timing.t_sclhi)
        | I2C_SETUP_ADDRESSING_SET(config->is_10bit_addressing)
        | I2C_SETUP_IICEN_MASK
        | I2C_SETUP_MASTER_MASK;

    return status_success;
}

hpm_stat_t i2c_master_address_read(I2C_Type *ptr, const uint16_t device_address,
                                   uint8_t *addr, uint32_t addr_size_in_byte,
                                   uint8_t *buf, const uint32_t size_in_byte)
{
    hpm_stat_t stat = status_success;
    uint32_t left;
    uint32_t retry;
    if (((addr_size_in_byte == 0) || (addr_size_in_byte > I2C_SOC_TRANSFER_COUNT_MAX)) ||
        ((size_in_byte == 0) || (size_in_byte > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }

    retry = 0;
    while (ptr->STATUS & I2C_STATUS_BUSBUSY_MASK) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    /* W1C, clear CMPL bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;

    ptr->CMD = I2C_CMD_CLEAR_FIFO;
    ptr->CTRL = I2C_CTRL_PHASE_START_MASK
        | I2C_CTRL_PHASE_ADDR_MASK
        | I2C_CTRL_PHASE_DATA_MASK
        | I2C_CTRL_DIR_SET(I2C_DIR_MASTER_WRITE)
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
        | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(addr_size_in_byte) >> 8U)
#endif
        | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(addr_size_in_byte));

    ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);

    left = addr_size_in_byte;
    while (left) {
        ptr->DATA = *(addr++);
        left--;
    }
    ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    /* Before starting to transmit data, judge addrhit to ensure that the slave address exists on the bus. */
    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_ADDRHIT_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        /* the address misses, a stop needs to be added to prevent the bus from being busy. */
        ptr->STATUS = I2C_STATUS_CMPL_MASK;
        ptr->CTRL = I2C_CTRL_PHASE_STOP_MASK;
        ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;
        return status_i2c_no_addr_hit;
    }
    ptr->STATUS = I2C_STATUS_ADDRHIT_MASK;

    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }
    /* W1C, clear CMPL bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;

    ptr->CMD = I2C_CMD_CLEAR_FIFO;
    ptr->CTRL = I2C_CTRL_PHASE_START_MASK
        | I2C_CTRL_PHASE_STOP_MASK
        | I2C_CTRL_PHASE_ADDR_MASK
        | I2C_CTRL_PHASE_DATA_MASK
        | I2C_CTRL_DIR_SET(I2C_DIR_MASTER_READ)
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
        | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size_in_byte) >> 8U)
#endif
        | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size_in_byte));
    ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    retry = 0;
    left = size_in_byte;
    while (left) {
        if (!(ptr->STATUS & I2C_STATUS_FIFOEMPTY_MASK)) {
            *(buf++) = ptr->DATA;
            left--;
            retry = 0;
        } else {
            if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                break;
            }
            retry++;
        }
    }

    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }
    return stat;
}

hpm_stat_t i2c_master_address_write(I2C_Type *ptr, const uint16_t device_address,
                                    uint8_t *addr, uint32_t addr_size_in_byte,
                                    uint8_t *buf, const uint32_t size_in_byte)
{
    hpm_stat_t stat = status_success;
    uint32_t left;
    uint32_t retry;

    if (((addr_size_in_byte == 0) || (addr_size_in_byte > I2C_SOC_TRANSFER_COUNT_MAX)) ||
        ((size_in_byte == 0) || (size_in_byte > I2C_SOC_TRANSFER_COUNT_MAX)) ||
        ((addr_size_in_byte + size_in_byte) > I2C_SOC_TRANSFER_COUNT_MAX)) {
        return status_invalid_argument;
    }

    retry = 0;
    while (ptr->STATUS & I2C_STATUS_BUSBUSY_MASK) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    /* W1C, clear CMPL bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;

    ptr->CMD = I2C_CMD_CLEAR_FIFO;
    ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);
    ptr->CTRL = I2C_CTRL_PHASE_START_MASK
        | I2C_CTRL_PHASE_STOP_MASK
        | I2C_CTRL_PHASE_ADDR_MASK
        | I2C_CTRL_PHASE_DATA_MASK
        | I2C_CTRL_DIR_SET(I2C_DIR_MASTER_WRITE)
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
        | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size_in_byte + addr_size_in_byte) >> 8U)
#endif
        | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size_in_byte + addr_size_in_byte));

    left = addr_size_in_byte;
    while (left) {
        ptr->DATA = *(addr++);
        left--;
    }
    ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    /* Before starting to transmit data, judge addrhit to ensure that the slave address exists on the bus. */
    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_ADDRHIT_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_i2c_no_addr_hit;
    }
    ptr->STATUS = I2C_STATUS_ADDRHIT_MASK;

    retry = 0;
    left = size_in_byte;
    while (left) {
        if (!(ptr->STATUS & I2C_STATUS_FIFOFULL_MASK)) {
            ptr->DATA = *(buf++);
            left--;
            retry = 0;
        } else {
            if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                break;
            }
            retry++;
        }
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        } else {
            retry++;
        }
    }

    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    return stat;
}

hpm_stat_t i2c_master_read(I2C_Type *ptr, const uint16_t device_address,
                           uint8_t *buf, const uint32_t size)
{
    hpm_stat_t stat = status_success;
    uint32_t left;
    uint32_t retry;
    if (size > I2C_SOC_TRANSFER_COUNT_MAX) {
        return status_invalid_argument;
    }

    retry = 0;
    while (ptr->STATUS & I2C_STATUS_BUSBUSY_MASK) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    /* W1C, clear CMPL bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;

    ptr->CMD = I2C_CMD_CLEAR_FIFO;
    ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);
    ptr->CTRL = I2C_CTRL_PHASE_START_MASK
        | I2C_CTRL_PHASE_STOP_MASK
        | I2C_CTRL_PHASE_ADDR_MASK
        | I2C_CTRL_DIR_SET(I2C_DIR_MASTER_READ);
    if (size > 0) {
        ptr->CTRL |= I2C_CTRL_PHASE_DATA_MASK
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
            | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size) >> 8U)
#endif
            | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));
    }
    ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    /* Before starting to transmit data, judge addrhit to ensure that the slave address exists on the bus. */
    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_ADDRHIT_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_i2c_no_addr_hit;
    }
    ptr->STATUS = I2C_STATUS_ADDRHIT_MASK;
    /* when size is zero, it's probe slave device, so directly return success */
    if (size == 0) {
        return status_success;
    }

    retry = 0;
    left = size;
    while (left) {
        if (!(ptr->STATUS & I2C_STATUS_FIFOEMPTY_MASK)) {
            *(buf++) = ptr->DATA;
            left--;
            retry = 0;
        } else {
            if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                break;
            }
            retry++;
        }
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    };
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    if (i2c_get_data_count(ptr) && (size)) {
        return status_i2c_transmit_not_completed;
    }

    return stat;
}

hpm_stat_t i2c_master_write(I2C_Type *ptr, const uint16_t device_address,
                            uint8_t *buf, const uint32_t size)
{
    hpm_stat_t stat = status_success;
    uint32_t retry;
    uint32_t left;

    if (size > I2C_SOC_TRANSFER_COUNT_MAX) {
        return status_invalid_argument;
    }

    retry = 0;
    while (ptr->STATUS & I2C_STATUS_BUSBUSY_MASK) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    /* W1C, clear CMPL bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;

    ptr->CMD = I2C_CMD_CLEAR_FIFO;
    ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);
    ptr->CTRL = I2C_CTRL_PHASE_START_MASK
        | I2C_CTRL_PHASE_STOP_MASK
        | I2C_CTRL_PHASE_ADDR_MASK
        | I2C_CTRL_DIR_SET(I2C_DIR_MASTER_WRITE);
    if (size > 0) {
        ptr->CTRL |= I2C_CTRL_PHASE_DATA_MASK
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
            | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size) >> 8U)
#endif
            | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));
    }
    ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    /* Before starting to transmit data, judge addrhit to ensure that the slave address exists on the bus. */
    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_ADDRHIT_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_i2c_no_addr_hit;
    }
    ptr->STATUS = I2C_STATUS_ADDRHIT_MASK;
    /* when size is zero, it's probe slave device, so directly return success */
    if (size == 0) {
        return status_success;
    }

    retry = 0;
    left = size;
    while (left) {
        if (!(ptr->STATUS & I2C_STATUS_FIFOFULL_MASK)) {
            ptr->DATA = *(buf++);
            left--;
            retry = 0;
        } else {
            if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                break;
            }
            retry++;
        }
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    if (i2c_get_data_count(ptr) && (size)) {
        return status_i2c_transmit_not_completed;
    }

    return stat;
}

hpm_stat_t i2c_init_slave(I2C_Type *ptr, uint32_t src_clk_in_hz,
                          i2c_config_t *config, const uint16_t slave_address)
{
    hpm_stat_t stat = status_success;
    i2c_timing_t timing = {0};

    i2c_reset(ptr);

    ptr->ADDR = I2C_ADDR_ADDR_SET(slave_address);

    stat = i2c_configure_timing(src_clk_in_hz, config->i2c_mode, &timing);
    if (status_success != stat) {
        return stat;
    }

    ptr->TPM = I2C_TPM_TPM_SET(HPM_I2C_DRV_DEFAULT_TPM);

    ptr->SETUP = I2C_SETUP_T_SP_SET(timing.t_sp)
        | I2C_SETUP_T_SUDAT_SET(timing.t_sudat)
        | I2C_SETUP_T_HDDAT_SET(timing.t_hddat)
        | I2C_SETUP_T_SCLRADIO_SET(timing.t_sclratio - 1)
        | I2C_SETUP_T_SCLHI_SET(timing.t_sclhi)
        | I2C_SETUP_ADDRESSING_SET(config->is_10bit_addressing)
        | I2C_SETUP_IICEN_MASK;

    return stat;
}

hpm_stat_t i2c_slave_write(I2C_Type *ptr, uint8_t *buf, const uint32_t size)
{
    volatile uint32_t status;
    uint32_t retry;
    uint32_t left;

    if (((size == 0) || (size > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }

    /* wait for address hit */
    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_ADDRHIT_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_fail;
    }

    /* W1C, clear CMPL bit and clear ADDRHIT bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK | I2C_STATUS_ADDRHIT_MASK;
    ptr->CMD = I2C_CMD_CLEAR_FIFO;

    retry = 0;
    left = size;
    while (left) {
        status = ptr->STATUS;
        if (!(status & I2C_STATUS_FIFOFULL_MASK)) {
            ptr->DATA = *(buf++);
            left--;
            retry = 0;
        } else {
            if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                break;
            }
            retry++;
        }
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }
    /* clear status, CMPL must to be cleared at slave mode before next transaction */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;

    if (i2c_get_data_count(ptr) != size) {
        return status_i2c_transmit_not_completed;
    }

    return status_success;
}

hpm_stat_t i2c_slave_read(I2C_Type *ptr,
                          uint8_t *buf,
                          const uint32_t size)
{
    volatile uint32_t status;
    uint32_t retry;
    uint32_t left;

    if (((size == 0) || (size > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }

    /* wait for address hit */
    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_ADDRHIT_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_fail;
    }

    /* W1C, clear CMPL bit and clear ADDRHIT bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK | I2C_STATUS_ADDRHIT_MASK;
    ptr->CMD = I2C_CMD_CLEAR_FIFO;

    retry = 0;
    left = size;
    while (left) {
        status = ptr->STATUS;
        if (!(status & I2C_STATUS_FIFOEMPTY_MASK)) {
            *(buf++) = ptr->DATA;
            left--;
            retry = 0;
        } else {
            if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                break;
            }
            retry++;
        }
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }
    /* clear status, CMPL must to be cleared at slave mode before next transaction */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;

    if (i2c_get_data_count(ptr) != size) {
        return status_i2c_transmit_not_completed;
    }

    return status_success;
}

hpm_stat_t i2c_master_start_dma_write(I2C_Type *i2c_ptr, const uint16_t device_address, uint32_t size)
{
    uint32_t retry = 0;
    if (((size == 0) || (size > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }

    while (i2c_ptr->STATUS & I2C_STATUS_BUSBUSY_MASK) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    /* W1C, clear CMPL bit to avoid blocking the transmission */
    i2c_ptr->STATUS = I2C_STATUS_CMPL_MASK;

    i2c_ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);
    i2c_ptr->CTRL = I2C_CTRL_PHASE_START_MASK
        | I2C_CTRL_PHASE_STOP_MASK
        | I2C_CTRL_PHASE_ADDR_MASK
        | I2C_CTRL_PHASE_DATA_MASK
        | I2C_CTRL_DIR_SET(I2C_DIR_MASTER_WRITE)
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
        | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size) >> 8U)
#endif
        | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));

    i2c_ptr->SETUP |= I2C_SETUP_DMAEN_MASK;

    i2c_ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    return status_success;
}

hpm_stat_t i2c_master_start_dma_read(I2C_Type *i2c_ptr, const uint16_t device_address, uint32_t size)
{
    uint32_t retry = 0;
    if (((size == 0) || (size > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }

    while (i2c_ptr->STATUS & I2C_STATUS_BUSBUSY_MASK) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }
    /* W1C, clear CMPL bit to avoid blocking the transmission */
    i2c_ptr->STATUS = I2C_STATUS_CMPL_MASK;

    i2c_ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);
    i2c_ptr->CTRL = I2C_CTRL_PHASE_START_MASK
        | I2C_CTRL_PHASE_STOP_MASK
        | I2C_CTRL_PHASE_ADDR_MASK
        | I2C_CTRL_PHASE_DATA_MASK
        | I2C_CTRL_DIR_SET(I2C_DIR_MASTER_READ)
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
        | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size) >> 8U)
#endif
        | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));

    i2c_ptr->SETUP |= I2C_SETUP_DMAEN_MASK;

    i2c_ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    return status_success;
}

hpm_stat_t i2c_slave_dma_transfer(I2C_Type *i2c_ptr, uint32_t size)
{
    if (((size == 0) || (size > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }

    /* W1C, clear CMPL bit to avoid blocking the transmission */
    i2c_ptr->STATUS = I2C_STATUS_CMPL_MASK;

#ifdef I2C_CTRL_DATACNT_HIGH_MASK
    i2c_ptr->CTRL &= ~(I2C_CTRL_DATACNT_HIGH_MASK | I2C_CTRL_DATACNT_MASK);
    i2c_ptr->CTRL |= I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size) >> 8U) | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));
#else
    i2c_ptr->CTRL &= ~I2C_CTRL_DATACNT_MASK;
    i2c_ptr->CTRL |= I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));
#endif

    i2c_ptr->SETUP |= I2C_SETUP_DMAEN_MASK;

    return status_success;
}

hpm_stat_t i2c_master_configure_transfer(I2C_Type *i2c_ptr, const uint16_t device_address, uint32_t size, bool read)
{
    uint32_t retry = 0;
    if (((size == 0) || (size > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }

    while (i2c_ptr->STATUS & I2C_STATUS_BUSBUSY_MASK) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }
    /* W1C, clear CMPL bit to avoid blocking the transmission */
    i2c_ptr->STATUS = I2C_STATUS_CMPL_MASK;
    i2c_ptr->CMD = I2C_CMD_CLEAR_FIFO;
    i2c_ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);
    i2c_ptr->CTRL = I2C_CTRL_PHASE_START_MASK
                | I2C_CTRL_PHASE_STOP_MASK
                | I2C_CTRL_PHASE_ADDR_MASK
                | I2C_CTRL_PHASE_DATA_MASK
                | I2C_CTRL_DIR_SET(read)
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
                | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size) >> 8U)
#endif
                | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));

    i2c_ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    return status_success;
}

hpm_stat_t i2c_master_seq_transmit_check_ack(I2C_Type *ptr, const uint16_t device_address,
                                    uint8_t *buf, const uint32_t size, i2c_seq_transfer_opt_t opt, bool ack_check)
{
    uint32_t ctrl;
    uint32_t retry = 0;
    uint32_t left = 0;
    volatile uint32_t status;
    hpm_stat_t stat = status_success;
    if (((size == 0) || (size > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }

    /* W1C, clear CMPL bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;
    ptr->CMD = I2C_CMD_CLEAR_FIFO;
    ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);

    switch (opt) {
    case i2c_frist_frame:
        ctrl = I2C_CTRL_PHASE_START_SET(true) | I2C_CTRL_PHASE_STOP_SET(false) \
               | I2C_CTRL_PHASE_ADDR_SET(true);
        break;
    case i2c_next_frame:
        ctrl = I2C_CTRL_PHASE_START_SET(false) | I2C_CTRL_PHASE_STOP_SET(false) \
               | I2C_CTRL_PHASE_ADDR_SET(false);
        break;
    case i2c_last_frame:
        ctrl = I2C_CTRL_PHASE_START_SET(false) | I2C_CTRL_PHASE_STOP_SET(true) \
               | I2C_CTRL_PHASE_ADDR_SET(false);
        break;
    default:
        return status_invalid_argument;
    }

    ptr->CTRL = ctrl | I2C_CTRL_PHASE_DATA_SET(true)
                | I2C_CTRL_DIR_SET(I2C_DIR_MASTER_WRITE)
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
                | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size) >> 8U)
#endif
                | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));
    /* enable auto ack */
    ptr->INTEN &= ~I2C_EVENT_BYTE_RECEIVED;
    ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    retry = 0;
    left = size;
    if (ack_check == true) {
        ptr->STATUS = I2C_STATUS_BYTETRANS_MASK;
    }
    while (left) {
        if (ack_check == true) {
            ptr->DATA = *(buf++);
            left--;
            retry = 0;
            while (!(ptr->STATUS & I2C_STATUS_BYTETRANS_MASK)) {
                if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                    break;
                }
                retry++;
            }
            ptr->STATUS = I2C_STATUS_BYTETRANS_MASK;
            /* NACK request */
            if (!(I2C_STATUS_ACK_GET(ptr->STATUS))) {
                ptr->CTRL = I2C_CTRL_PHASE_STOP_SET(true);
                ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;
                stat = status_i2c_no_ack;
                break;
            }
        } else {
            status = ptr->STATUS;
            if (!(status & I2C_STATUS_FIFOFULL_MASK)) {
                ptr->DATA = *(buf++);
                left--;
                retry = 0;
            } else {
                if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                    break;
                }
                retry++;
            }
        }

    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }
    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }
    /* clear status, CMPL must to be cleared at slave mode before next transaction */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;
    return stat;
}

hpm_stat_t i2c_master_seq_receive(I2C_Type *ptr, const uint16_t device_address,
                                    uint8_t *buf, const uint32_t size, i2c_seq_transfer_opt_t opt)
{
    uint32_t ctrl;
    uint32_t retry = 0;
    uint32_t left = 0;

    if (((size == 0) || (size > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }

    /* W1C, clear CMPL bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;
    ptr->CMD = I2C_CMD_CLEAR_FIFO;
    ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);

    switch (opt) {
    case i2c_frist_frame:
        ctrl = I2C_CTRL_PHASE_START_SET(true) | I2C_CTRL_PHASE_STOP_SET(false) \
               | I2C_CTRL_PHASE_ADDR_SET(true);
        break;
    case i2c_next_frame:
        ctrl = I2C_CTRL_PHASE_START_SET(false) | I2C_CTRL_PHASE_STOP_SET(false) \
               | I2C_CTRL_PHASE_ADDR_SET(false);
        break;
    case i2c_last_frame:
        ctrl = I2C_CTRL_PHASE_START_SET(false) | I2C_CTRL_PHASE_STOP_SET(true) \
               | I2C_CTRL_PHASE_ADDR_SET(false);
        break;
    default:
        return status_invalid_argument;
    }

    ptr->CTRL = ctrl | I2C_CTRL_PHASE_DATA_SET(true)
                | I2C_CTRL_DIR_SET(I2C_DIR_MASTER_READ)
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
                | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size) >> 8U)
#endif
                | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));

    /* disable auto ack */
    ptr->INTEN |= I2C_EVENT_BYTE_RECEIVED;
    ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;

    retry = 0;
    left = size;
    while (left) {
        if (!(ptr->STATUS & I2C_STATUS_FIFOEMPTY_MASK)) {
            *(buf++) = ptr->DATA;
            left--;
            if ((left == 0) && (opt == i2c_last_frame)) {
                ptr->CMD = I2C_CMD_NACK;
            } else {
                ptr->CMD = I2C_CMD_ACK;
            }
            retry = 0;
        } else {
            if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                break;
            }
            retry++;
        }
    }

    /* default auto ack */
    ptr->INTEN &= ~I2C_EVENT_BYTE_RECEIVED;

    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }

    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        return status_timeout;
    }
    if (opt  == i2c_last_frame) {
        retry = 0;
        while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
            if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                break;
            }
            retry++;
        }
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            return status_timeout;
        }
    }
    /* clear status, CMPL must to be cleared at slave mode before next transaction */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;

    return status_success;
}

hpm_stat_t i2c_master_transfer(I2C_Type *ptr, const uint16_t device_address,
                                    uint8_t *buf, const uint32_t size,  uint16_t flags)
{
    volatile uint32_t status;
    uint32_t ctrl = 0;
    uint32_t retry = 0;
    uint32_t left = 0;
    hpm_stat_t stat = status_success;
    if (((size == 0) || (size > I2C_SOC_TRANSFER_COUNT_MAX))) {
        return status_invalid_argument;
    }
    if (flags & I2C_ADDR_10BIT) {
        i2c_enable_10bit_address_mode(ptr, true);
    } else {
        i2c_enable_10bit_address_mode(ptr, false);
    }
    /* W1C, clear CMPL bit to avoid blocking the transmission */
    ptr->STATUS = I2C_STATUS_CMPL_MASK;
    ptr->CMD = I2C_CMD_CLEAR_FIFO;
    ptr->ADDR = I2C_ADDR_ADDR_SET(device_address);

    if (flags & I2C_RD) {
        ctrl |= I2C_CTRL_DIR_SET(I2C_DIR_MASTER_READ);
    } else {
        ctrl |= I2C_CTRL_DIR_SET(I2C_DIR_MASTER_WRITE);/* is write flag */
    }
    /* start signal */
    if (flags & I2C_NO_START) {
        ctrl |= I2C_CTRL_PHASE_START_SET(false);
    } else {
        ctrl |= I2C_CTRL_PHASE_START_SET(true);
    }
    /* end signal*/
    if (flags & I2C_NO_STOP) {
        ctrl |= I2C_CTRL_PHASE_STOP_SET(false);
    } else {
        ctrl |= I2C_CTRL_PHASE_STOP_SET(true);
    }

    ptr->CTRL = ctrl | I2C_CTRL_PHASE_DATA_SET(true)
                | I2C_CTRL_PHASE_ADDR_SET(true)
#ifdef I2C_CTRL_DATACNT_HIGH_MASK
                | I2C_CTRL_DATACNT_HIGH_SET(I2C_DATACNT_MAP(size) >> 8U)
#endif
                | I2C_CTRL_DATACNT_SET(I2C_DATACNT_MAP(size));
    /* disable auto ack */
    ptr->INTEN |= I2C_EVENT_BYTE_RECEIVED;
    ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;
    retry = 0;
    left = size;
    if (flags & I2C_RD) {
        while (left) {
            if (!(ptr->STATUS & I2C_STATUS_FIFOEMPTY_MASK)) {
                *(buf++) = ptr->DATA;
                left--;
                if (left == 0) {
                    ptr->CMD = I2C_CMD_NACK;
                } else {
                    /* ACK is sent when reading */
                    if (!(flags & I2C_NO_READ_ACK)) {
                        ptr->CMD = I2C_CMD_ACK;
                    }
                }
                retry = 0;
            } else {
                if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                    break;
                }
                retry++;
            }
        }
    } else {
        if (flags & I2C_WRITE_CHECK_ACK) {
            ptr->STATUS = I2C_STATUS_BYTETRANS_MASK;
        }
        while (left) {
            if (flags & I2C_WRITE_CHECK_ACK) {
                ptr->DATA = *(buf++);
                left--;
                retry = 0;
                while (!(ptr->STATUS & I2C_STATUS_BYTETRANS_MASK)) {
                    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                        break;
                    }
                    retry++;
                }
                ptr->STATUS = I2C_STATUS_BYTETRANS_MASK;
                /* NACK request */
                if (!(I2C_STATUS_ACK_GET(ptr->STATUS))) {
                    ptr->CTRL = I2C_CTRL_PHASE_STOP_SET(true);
                    ptr->CMD = I2C_CMD_ISSUE_DATA_TRANSMISSION;
                    stat = status_i2c_no_ack;
                    break;
                }
            } else {
                status = ptr->STATUS;
                if (!(status & I2C_STATUS_FIFOFULL_MASK)) {
                    ptr->DATA = *(buf++);
                    left--;
                    retry = 0;
                } else {
                    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
                        break;
                    }
                    retry++;
                }
            }
        }
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        /* default auto ack */
        ptr->INTEN &= ~I2C_EVENT_BYTE_RECEIVED;
        return status_timeout;
    }
    retry = 0;
    while (!(ptr->STATUS & I2C_STATUS_CMPL_MASK)) {
        if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
            break;
        }
        retry++;
    }
    if (retry > HPM_I2C_DRV_DEFAULT_RETRY_COUNT) {
        /* default auto ack */
        ptr->INTEN &= ~I2C_EVENT_BYTE_RECEIVED;
        return status_timeout;
    }

    if (i2c_get_data_count(ptr) && (size)) {
        /* default auto ack */
        ptr->INTEN &= ~I2C_EVENT_BYTE_RECEIVED;
        return status_i2c_transmit_not_completed;
    }
    /* default auto ack */
    ptr->INTEN &= ~I2C_EVENT_BYTE_RECEIVED;
    return stat;
}
