/*!
 * @file        gpio_i2c_slave.c
 *
 * @brief       This file contains all the functions for the GPIO I2C peripheral
 *
 * @version     V1.0.0
 *
 * @date        2026-02-25
 *
 * @attention
 *
 *  Copyright (C) 2026 Geehy Semiconductor
 *
 *  You may not use this file except in compliance with the
 *  GEEHY COPYRIGHT NOTICE (Geehy Semiconductor Software License Agreement).
 *
 *  The program is only for reference, which is distributed in the hope
 *  that it will be useful and instructional for customers to develop
 *  their software. Unless required by applicable law or agreed to in
 *  writing, the program is distributed on an "AS IS" BASIS, WITHOUT
 *  ANY WARRANTY OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the Geehy Semiconductor Software License Agreement for the governing permissions
 *  and limitations under the License.
 */

/* Includes */
#include "gpio_i2c_slave.h"
#include "Board.h"
#include "user_i2c_config.h"

/** @addtogroup Examples
  @{
  */

/** @addtogroup I2C_Examples
  @{
  */

/** @addtogroup GPIO_I2C_SLAVE
  @{
  */

/** @defgroup GPIO_I2C_SLAVE_Variables Variables
  @{
  */
static volatile I2c_SlaveStateType slaveState = I2C_SLAVE_WAIT_START;
volatile uint8_t recevieBuffer[256] = {0};
volatile uint8_t recevieBufLen = 0;
volatile uint8_t sendBuffer[256] = {0};
volatile uint8_t sendBufLen = 0;

/**@} end of group GPIO_I2C_SLAVE_Variables */

/** @defgroup GPIO_I2C_SLAVE_Functions Functions
  @{
  */

/*!
 * @brief       Set slave SCL pin to input mode
 *
 * @param       None
 *
 * @retval      None
 *
 * @note
 */
static void Slave_Scl_InputMode(void)
{
    Rcm_EnableAhbPeriphClock(I2C_SLAVE_SCL_CLK);   
    Gpio_Config(I2C_SLAVE_SCL_PORT, &g_sclGpioConfig);
}

/*!
 * @brief       Set slave SDA pin to output mode
 *
 * @param       None
 *
 * @retval      None
 *
 * @note
 */
static void Slave_Sda_OutputMode(void)
{
    Rcm_EnableAhbPeriphClock(I2C_SLAVE_SDA_CLK);
    Gpio_Config(I2C_SLAVE_SDA_PORT, &g_sdaGpioConfig);
    /* set default status */
    I2C_SDA_HIGH();
}


/*!
 * @brief       I2C wait start signal
 *
 * @param       None
 *
 * @retval      0 is not start,1 is start
 *
 * @note
 */
static uint8_t I2c_Slave_WaitStart(void)
{
    uint8_t ret = 0U;
    /* when SCL is high,then SDA from high to low, it is start signal */
    if(I2C_SCL_READ() == 1U)
    {
        if(I2C_SDA_READ() == 1U)
        {
            while(I2C_SDA_READ() == 1U)
            {
                /* nothing */
            }
            if(I2C_SDA_READ() == 0U)
            {
                if(I2C_SCL_READ() == 1U)
                {
                    while(I2C_SCL_READ() == 1U)
                    {
                        /* nothing */
                    }
                    /* start signal */
                    ret = 1U;
                }
                else
                {
                    /* nothing */
                }
            }
            else
            {
                /* nothing */
            }
        }
        else
        {
            /* nothing */
        }
    }
    else
    {
        /* nothing */
    }
    /* no start signal */
    return ret;
}

/*!
 * @brief       send one byte to host
 *
 * @param       sendByte :byte data to send
 *
 * @retval      None
 *
 * @note
 */
static uint8_t I2c_Slave_WriteByte(uint8_t sendByte)
{
    uint8_t ack=0;
    uint8_t n=0;
    uint8_t sendByteData = sendByte;
    /* send bit one by one */
    for(n=0; n<8U; n++)
    {
        if((sendByteData & 0x80U) != 0U)
        {
            I2C_SDA_HIGH();
        }
        else
        {
            I2C_SDA_LOW();
        }
        sendByteData <<= 1;
        /* wait SCL high */
        while(I2C_SCL_READ() == 0U)
        {
            /* nothing */
        }
        /* wait SCL low */
        while(I2C_SCL_READ() == 1U)
        {
            /* nothing */
        }
    }
    /* set sda free */
    I2C_SDA_HIGH();
    /* wait SCL high */
    while(I2C_SCL_READ() == 0U)
    {
        /* nothing */
    }
    if(I2C_SDA_READ() == 0U)
    {
        /* ack */
        ack = 1;
    }
    else
    {
        /* no ack */
        ack = 0;
    }
    /* wait SCL low */
    while(I2C_SCL_READ() == 1U)
    {
        /* nothing */
    }
    
    return ack;
}

/*!
 * @brief       receive one byte from host
 *
 * @param       None
 *
 * @retval      return data 0-255 is receive normal data,0xFFFF is stop signal.
 *
 * @note
 */
static uint16_t I2c_Slave_ReadByte(void)
{
    uint16_t data = 0;
    uint8_t sdaBit1 = 0;
    uint8_t sdaBit2 = 0;
    uint8_t i=1;
    uint8_t stopDetected = 0U;

    while(I2C_SCL_READ() == 0U)
    {
        /* nothing */
    }
    /* read sda when scl is high */
    sdaBit1 = I2C_SDA_READ();
    /* poll the sda status when scl is high */
    while(I2C_SCL_READ() == 1U)
    {
        sdaBit2 = I2C_SDA_READ();
        if((sdaBit1 == 0U)&&(sdaBit2 == 1U))
        {
            /* stop signal */
            data = 0xFFFFU;
            stopDetected = 1U;
        }
        else
        {
            /* nothing */
        }

        if(stopDetected == 1U)
        {
            break;
        }
        else
        {
            /* nothing */
        }
    }

    if(stopDetected == 0U)
    {
        /* normal data */
        if(sdaBit1 == sdaBit2)
        {
            /* read bit one by one */
            data = (data << 1) | sdaBit1;
            for(i=1; i<8U; i++)
            {
                while(I2C_SCL_READ() == 0U)
                {
                    /* Nothing */
                }
                data = (data << 1) | I2C_SDA_READ();
                while(I2C_SCL_READ() == 1U)
                {
                    /* Nothing */
                }
            }
        }
        else
        {
            /* nothing */
        }
    }
    else
    {
        /* nothing */
    }

    return data;
}

/*!
 * @brief       send ack
 *
 * @param       None
 *
 * @retval      None
 *
 * @note
 */
static void I2c_Slave_SendAck(void)
{
    /* While SCL is high and SDA remains low, it is ACK signal */
    I2C_SDA_LOW();
    while(I2C_SCL_READ() == 0U)
    {
        /* Nothing */
    }
    while(I2C_SCL_READ() == 1U)
    {
        /* Nothing */
    }
    I2C_SDA_HIGH();
}


/*!
 * @brief       Initialize the I2C gpio
 *
 * @param       None
 *
 * @retval      None
 *
 * @note
 */
void I2c_InitSlaveGpio(void)
{
    Slave_Scl_InputMode();
    /*Set SDA Open-drain output mode, and not pull up,it can read and write */
    Slave_Sda_OutputMode();
}

/*!
 * @brief       slave device polling
 *
 * @param       None
 *
 * @retval      None
 *
 * @note
 */
void I2c_Slave_Poll(void)
{
    uint16_t rxData;
    uint8_t ackData=0;
    uint8_t addr_byte;
    uint8_t addr;
    uint8_t stopDetected = 0U;
    uint8_t breakflag0 = 0U, breakflag1 = 0U;
    uint8_t idx;
    uint8_t tx;
    /* wait start signal */
    if(I2c_Slave_WaitStart() == 0U)
    {
        slaveState = I2C_SLAVE_WAIT_START;
        /* no start signal */
        stopDetected = 1U;
    }
    else
    {
        slaveState = I2C_SLAVE_START;
    }

    if(stopDetected == 0U)
    {
        /* recevie or send data */
        while(slaveState != I2C_SLAVE_WAIT_START)
        {
            /* read address */
            addr_byte = (uint8_t)I2c_Slave_ReadByte();
            /* ignore LSB of device address */
            addr = addr_byte >> 1;
        
            /* check address */
            if(addr != (I2C_SLAVE_ADDR>>1))
            {
                break;
            }
            else
            {
                /* nothing */
            }
            /* send ack */
            I2c_Slave_SendAck();
        
            /* host device write data to slave device */
            if((addr_byte & 0x01U) == 0U)
            {
                recevieBufLen = 0;
                /* recevie data until data len is I2C_SLAVE_PAGE_SIZE+I2C_SLAVE_REGADDR_SIZE, or stop break */
                while(1)
                {
                    /* recevie one byte */
                    rxData = I2c_Slave_ReadByte(); 
                    /* recevie normal data */
                    if(rxData <= 0xFFU)
                    {
                        recevieBuffer[recevieBufLen] = (uint8_t)(rxData&0xFFU);
                        recevieBufLen += 1U;
                        I2c_Slave_SendAck();
                        if(recevieBufLen >= (I2C_SLAVE_PAGE_SIZE + I2C_SLAVE_REGADDR_SIZE))
                        {
                            /* recevie length overflow */
                            breakflag0 = 1U;
                        }
                        else
                        {
                            /* nothing */
                        }
                    }
                    /* recevie stop signal */
                    else if(rxData == 0xFFFFU)
                    {
                        slaveState = I2C_SLAVE_WAIT_START;
                        breakflag0 = 1U;
                    }
                    else
                    {
                        /* nothing */
                    }
                    
                    if(breakflag0 == 1U)
                    {
                        break;
                    }
                    else
                    {
                        /* nothing */
                    }
                }
            }
            /* host device read data from slave device */
            else
            {
                sendBufLen = I2C_SLAVE_REGADDR_SIZE;
                /* send data until data len is I2C_SLAVE_PAGE_SIZE+I2C_SLAVE_REGADDR_SIZE,or stop break */
                while(1)
                {
                    /* send one byte */
                    idx = sendBufLen;
                    tx  = sendBuffer[idx];
                    ackData = I2c_Slave_WriteByte(tx);
                    /* recevie ack */
                    if(ackData == 1U)
                    {
                        sendBufLen += 1U;
                        if(sendBufLen >= (I2C_SLAVE_PAGE_SIZE + I2C_SLAVE_REGADDR_SIZE))
                        {
                            /* send length overflow */
                            slaveState = I2C_SLAVE_WAIT_START;
                            breakflag1 = 1U;
                        }
                        else
                        {
                            /* nothing */
                        }
                    }
                    /* recevie no ack */
                    else
                    {
                        slaveState = I2C_SLAVE_WAIT_START;
                        breakflag1 = 1U;
                    }
                    
                    if(breakflag1 == 1U)
                    {
                        break;
                    }
                    else
                    {
                        /* nothing */
                    }
                }
            }
        }
    }
    else
    {
        /* nothing */
    }
}


/**@} end of group GPIO_I2C_SLAVE_Functions */
/**@} end of group GPIO_I2C_SLAVE */
/**@} end of group I2C_Examples */
/**@} end of group Examples */
