/**
 * @file
 * @brief           Contains source code of i2c_drv.h.
 */

#include "xc.h"
#include "i2c_drv.h"

//----------------------------------------------------------------------------//
//private constants-----------------------------------------------------------//
//----------------------------------------------------------------------------//

/*
 * @version         : 	1.0
 * @description     :	Holds values to en-/disable i2c1 hardware.
 */
enum
{
    I2C_DRV_ENABLE = 1,
    I2C_DRV_DISABLE = 0,
} i2c_drv_enable;

/*
 * @version         : 	1.0
 * @description     :	Holds values to trigger i2c1 start sequence.
 */
enum
{
    I2C_DRV_ENABLE_START = 1,
    I2C_DRV_DISABLE_START = 0,
} i2c_drv_start_enable;

/*
 * @version         : 	1.0
 * @description     :	Holds values to trigger i2c1 restart sequence.
 */
enum
{
    I2C_DRV_ENABLE_RESTART = 1,
    I2C_DRV_DISABLE_RESTART = 0,
} i2c_drv_restart_enable;

/*
 * @version         : 	1.0
 * @description     :	Holds values to trigger i2c1 stop sequence.
 */
enum
{
    I2C_DRV_ENABLE_STOP = 1,
    I2C_DRV_DISABLE_STOP = 0,
} i2c_drv_stop_enable;

/*
 * @version         : 	1.0
 * @description     :	Holds values to indicate which acknowledge was received.
 */
enum
{
    I2C_DRV_GET_NACK = 1,
    I2C_DRV_GET_ACK = 0,
} i2c_drv_nack_ack_get;

/*
 * @version         : 	1.0
 * @description     :	Holds values to choose which acknowledge will be send 
 *                      during acknowledge sequence.
 */
enum
{
    I2C_DRV_SET_NACK = 1,
    I2C_DRV_SET_ACK = 0,
} i2c_drv_nack_ack_set;

/*
 * @version         : 	1.0
 * @description     :	Holds values to trigger i2c1 ack sequence.
 */
enum
{
    I2C_DRV_ENABLE_ACK = 1,
    I2C_DRV_DISABLE_ACK = 0,
} i2c_drv_ack_enable;

/*
 * @version         : 	1.0
 * @description     :	Holds values to trigger i2c1 nack sequence.
 */
enum
{
    I2C_DRV_ENABLE_NACK = 1,
    I2C_DRV_DISABLE_NACK = 0,
} i2c_drv_nack_enable;

/*
 * @version         : 	1.0
 * @description     :	Holds values to check if i2c1 transmission is finished.
 */
enum
{
    I2C_DRV_TRANSMIT_DONE = 0,
    I2C_DRV_TRANSMIT_DOING = 1,
} i2c_drv_transmit_done;

/*
 * @version         : 	1.0
 * @description     :	Holds values to check if i2c1 receive buffer is full.
 */
enum
{
    I2C_DRV_RECEIVE_BUF_FULL = 1,
    I2C_DRV_RECEIVE_BUF_EMPTY = 0,
} i2c_drv_receive_buf;

/*
 * @version         : 	1.0
 * @description     :	Holds values to trigger i2c1 receiving sequence.
 */
enum
{
    I2C_DRV_RECEIVE_ENABLE = 1,
    I2C_DRV_RECEIVE_DISABLE = 0,
} i2c_drv_receive_enable;

/*
 * @version         : 	1.0
 * @description     :	Holds values to check if i2c1 transmit buffer is full.
 */
enum
{
    I2C_DRV_TRANSMIT_BUF_FULL = 1,
    I2C_DRV_TRANSMIT_BUF_EMPTY = 0,
} i2c_drv_transmit_buf;

//----------------------------------------------------------------------------//
//private data----------------------------------------------------------------//
//----------------------------------------------------------------------------//



//----------------------------------------------------------------------------//
//private function declarations-----------------------------------------------//
//----------------------------------------------------------------------------//



//----------------------------------------------------------------------------//
//private function definitions------------------------------------------------//
//----------------------------------------------------------------------------//



//----------------------------------------------------------------------------//
//public functions------------------------------------------------------------//
//----------------------------------------------------------------------------//

exit_state i2c_drv_init(void)
{
    //init i2c module
    I2C1CON = 0x1000; //reset I2C1 controle register
    I2C1CONbits.SIDL = ENABLE; //turn of in idle
    I2C1CONbits.ON = DISABLE; //turn off I2C1 for safety
    I2C1CONbits.DISSLW = TRUE; // Disable slew rate for 100kHz
    I2C1BRG = 110; //sets SCL frequency to 100kHz
    I2C1CONbits.ON = ENABLE;

    return EXIT_SUCCESS;
}

exit_state i2c_drv_deinit(void)
{

    I2C1CONbits.I2CEN = I2C_DRV_DISABLE;

    return EXIT_SUCCESS;
}

//start-----------------------------------------------------------------------//

exit_state i2c_drv_tx_start(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    I2C1CONbits.SEN = SET;

    return EXIT_SUCCESS;
}

exit_state i2c_drv_tx_start_handler(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1CONbits.SEN == CLEAR)
    {
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

//restart---------------------------------------------------------------------//

exit_state i2c_drv_tx_restart(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    I2C1CONbits.RSEN = I2C_DRV_ENABLE_RESTART;

    return EXIT_SUCCESS;
}

exit_state i2c_drv_tx_restart_handler(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1CONbits.RSEN == I2C_DRV_DISABLE_RESTART)
    {
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

//stop------------------------------------------------------------------------//

exit_state i2c_drv_tx_stop(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    I2C1CONbits.PEN = I2C_DRV_ENABLE_STOP;

    return EXIT_SUCCESS;
}

exit_state i2c_drv_tx_stop_handler(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1CONbits.PEN == I2C_DRV_DISABLE_STOP)
    {
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

//ack-------------------------------------------------------------------------//

//set

exit_state i2c_drv_tx_ack(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    I2C1CONbits.ACKDT = I2C_DRV_SET_ACK;
    I2C1CONbits.ACKEN = I2C_DRV_ENABLE_ACK;

    return EXIT_SUCCESS;
}

exit_state i2c_drv_tx_ack_handler(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1CONbits.ACKEN == I2C_DRV_DISABLE_ACK)
    {
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

//get

exit_state i2c_drv_rx_ack(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1STATbits.ACKSTAT == I2C_DRV_GET_ACK)
    {
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

exit_state i2c_drv_rx_ack_handler(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1STATbits.TRSTAT == I2C_DRV_TRANSMIT_DONE)
    {
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

//nack------------------------------------------------------------------------//

//Set

exit_state i2c_drv_tx_nack(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    I2C1CONbits.ACKDT = I2C_DRV_SET_NACK;
    I2C1CONbits.ACKEN = I2C_DRV_ENABLE_NACK;

    return EXIT_SUCCESS;
}

exit_state i2c_drv_tx_nack_handler(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1CONbits.ACKEN == I2C_DRV_DISABLE_NACK)
    {
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

//get

exit_state i2c_drv_rx_nack(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1STATbits.ACKSTAT == I2C_DRV_GET_NACK)
    {
        return EXIT_SUCCESS;
    }
    return EXIT_FAILURE;
}

exit_state i2c_drv_rx_nack_handler(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1STATbits.TRSTAT == I2C_DRV_TRANSMIT_DONE)
    {
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

//transmit--------------------------------------------------------------------//

/**
 * @todo rewrite code to use ringbuffer.h
 */
exit_state i2c_drv_tx_data(uint8_t data)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    I2C1TRN = data;

    return EXIT_SUCCESS;
}

exit_state i2c_drv_tx_data_handler(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (I2C1STATbits.TBF == I2C_DRV_TRANSMIT_BUF_EMPTY)
    {
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

//receive---------------------------------------------------------------------//

/**
 * @bug Get caught in infty loop and breaks up message transmission
 */
exit_state i2c_drv_rx_data(uint8_t* data)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    *data = I2C1RCV;
}

exit_state i2c_drv_rx_data_handler(void)
{
    if (i2c_drv_module_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    static int rcen_flag = CLEAR;

    if (I2C1STATbits.RBF == I2C_DRV_RECEIVE_BUF_EMPTY && rcen_flag == CLEAR)
    {
        rcen_flag = SET;
        I2C1CONbits.RCEN = I2C_DRV_RECEIVE_ENABLE;
        return EXIT_FAILURE;
    }

    if (I2C1STATbits.RBF == I2C_DRV_RECEIVE_BUF_FULL && rcen_flag == SET)
    {
        rcen_flag = CLEAR;
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE; //buffer is not empty, read data from I2C1RCV first
}

//error management------------------------------------------------------------//

exit_state i2c_drv_module_check(void)
{

    if (i2c_drv_error_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    if (i2c_drv_busy_check() == EXIT_FAILURE)
    {
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

exit_state i2c_drv_error_check(void)
{
    if (
            IFS3bits.I2C1BIF == SET
            || I2C1STATbits.IWCOL == SET
            || I2C1STATbits.BCL == SET
            || I2C1STATbits.I2COV == SET
            )
    { //any bus collision occured
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

exit_state i2c_drv_busy_check(void)
{
    //according to datasheet no write to 5 lsb of I2C1CON allowed when atleast
    //one ist set
    if (
            I2C1CONbits.SEN == SET
            || I2C1CONbits.RSEN == SET
            || I2C1CONbits.PEN == SET
            || I2C1CONbits.RCEN == SET
            || I2C1CONbits.ACKEN == SET
            || I2C1STATbits.TRSTAT == SET
            || IFS3bits.I2C1MIF == SET //check if master interrupt is cleared (set by finishing any master event)
            || IFS3bits.I2C1SIF == SET //check if slave interrupt is cleared (set by receiving any slave event)
            )
    {
        IFS3bits.I2C1MIF = CLEAR;
        IFS3bits.I2C1SIF = CLEAR;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}