#include "i2c.h"

#define I2C_ERROR   1
#define I2C_SUCCESS 0

/* I2C wait loop - a timeout count is about 0.3 usec, */
/* so a 5 msec timout is about 16,666 counts.         */
/* note: interrupts will result in a timeout > 5 msec */
#define I2C_TIMEOUT_COUNT   16666

/***************************************************************************/
/* EEPROM functions                                                        */
/***************************************************************************/

int i2c_readEEPROM(unsigned char* data, int len)
{
    int i = 0;
    unsigned char b;

    if ( i2c_start() ) return I2C_ERROR;

    /* address with read bit clear */
    if ( i2c_write(0xA0) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    /* high byte address */
    if ( i2c_write(0x00) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    /* low byte address */
    if ( i2c_write(0x00) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_restart() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    /* address with read bit set */
    if ( i2c_write(0xA1) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    for (i = 0; i < len; ++i)
    {
        if ( i > 0x1FFF )   /* 8K max bytes */
        {
            i2c_stop();
            return I2C_ERROR;
        }

        if ( i2c_read(&b) ) return I2C_ERROR;
        if ( i2c_ack() ) return I2C_ERROR;
        if ( i2c_idle() ) return I2C_ERROR;

        data[i] = b;
    }

    if ( i2c_stop() ) return I2C_ERROR;

    return I2C_SUCCESS;
}

int i2c_writeEEPROM(unsigned char* data, int len)
{
    unsigned int i = 0;
    unsigned char add_msb;
    unsigned char add_lsb;

    for(i = 0; i < len; ++i)
    {
        add_msb = (i >> 8);
        add_lsb = (i & 0x00FF);

        if ( i > 0x1FFF )   /* 8K max bytes */
        {
            i2c_stop();
            return I2C_ERROR;
        }

        if ( i2c_start() ) return I2C_ERROR;

        /* address with read bit clear */
        if ( i2c_write(0xA0) ) return I2C_ERROR;
        if ( i2c_wait_ack() ) return I2C_ERROR;
        if ( i2c_idle() ) return I2C_ERROR;

        /* high byte address */
        if ( i2c_write(add_msb) ) return I2C_ERROR;
        if ( i2c_wait_ack() ) return I2C_ERROR;
        if ( i2c_idle() ) return I2C_ERROR;

        /* low byte address */
        if ( i2c_write(add_lsb) ) return I2C_ERROR;
        if ( i2c_wait_ack() ) return I2C_ERROR;
        if ( i2c_idle() ) return I2C_ERROR;

        /* data byte */
        if ( i2c_write(data[i]) ) return I2C_ERROR;
        if ( i2c_wait_ack() ) return I2C_ERROR;
        if ( i2c_idle() ) return I2C_ERROR;

        if ( i2c_stop() ) return I2C_ERROR;

        /* wait for write to complete */
        __delay_ms(8);

    }

    return I2C_SUCCESS;
}


/***************************************************************************/
/* HIH6130 function                                                        */
/***************************************************************************/

int i2c_getHumTemp(float* rh, float* temp)
{
    /* data bytes */
    unsigned char db1 = 0;
    unsigned char db2 = 0;
    unsigned char db3 = 0;
    unsigned char db4 = 0;

    /* register values */
    unsigned int rh_reg = 0;
    unsigned int temp_reg = 0;

    /* set args with error values */
    *rh = -1.0;
    *temp = -1.0;

    /* start conversion */
    if ( i2c_start() ) return I2C_ERROR;

    /* 0x4E is 0x27 shifted left by 1 */
    if ( i2c_write(0x4E) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_stop() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    /* wait for conversion */
    __delay_ms(50);

    /* read data */
    if ( i2c_start() ) return I2C_ERROR;

    /* 0x4F is 0x27 shifted left by 1 and write bit set */
    if ( i2c_write(0x4F) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_read(&db1) ) return I2C_ERROR;
    if ( i2c_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_read(&db2) ) return I2C_ERROR;
    if ( i2c_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_read(&db3) ) return I2C_ERROR;
    if ( i2c_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_read(&db4) ) return I2C_ERROR;
    if ( i2c_nack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_stop() ) return I2C_ERROR;

    /*************************************************************/
    /* convert the values to make it easy for the humans to read */
    /* conversion based on the following tech note,              */
    /* "I2C Comms HumidIcon TN_009061-2-EN_Final_07Jun12.pdf"    */
    /*************************************************************/

    /* humidity value */
    rh_reg = ((db1 << 8) | db2) & 0x3FFF;

    *rh = (float)rh_reg;

    *rh /= 16382.0;
    *rh *= 100.0;

    /* temperature value */
    temp_reg = ((db3 << 8) | db4) >> 2;

    *temp = (float)temp_reg;

    *temp /= 16382.0;
    *temp *= 165.0;
    *temp -= 40.0;

    return I2C_SUCCESS;
}


/***************************************************************************/
/* MPL115A2 functions                                                      */
/***************************************************************************/

int i2c_getMPL115A2Coeff(float* a0, float* b1, float* b2, float* c12)
{
    int i = 0;

    unsigned char buffer[8];

    /* set args with error values */
    *a0 = -1.0;
    *b1 = -1.0;
    *b2 = -1.0;
    *c12 = -1.0;

    /* grab the coefficient bytes */
    if ( i2c_start() ) return I2C_ERROR;

    if ( i2c_write(0xC0) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_write(0x04) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_restart() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_write(0xC1) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    for (i = 0; i < 8; ++i)
    {
        /* grab a byte */
        if ( i2c_read(&buffer[i]) ) return I2C_ERROR;

        if ( i < 7 )
        {
            if ( i2c_ack() ) return I2C_ERROR;
        }
        else
        {
            if ( i2c_nack() ) return I2C_ERROR;
        }

        if ( i2c_idle() ) return I2C_ERROR;
    }

    if ( i2c_stop() ) return I2C_ERROR;

    /* unpack the bytes into registers */
    *a0 =  (float) ((buffer[0] << 8) | buffer[1]);
    *b1 =  (float) ((buffer[2] << 8) | buffer[3]);
    *b2 =  (float) ((buffer[4] << 8) | buffer[5]);
    *c12 = (float)(((buffer[6] << 8) | buffer[7]) >> 2);

    /* calculate calibration coefficients */
    *a0 /= 8.0;

    *b1 /= 8192.0;

    *b2 /= 16384.0;

    *c12 /= 4194304.0;

    return I2C_SUCCESS;
}

int i2c_getPressTemp(float a0, float b1, float b2, float c12,
                     float* pressure, float* temp)
{
    int i = 0;
    float p, t, pc;
    unsigned char buffer[4];

    /* start the conversion */
    if ( i2c_start() ) return I2C_ERROR;

    if ( i2c_write(0xC0) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_write(0x12) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_write(0x00) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_stop() ) return I2C_ERROR;

    /* wait for the converison */
    __delay_ms(5);

    /* grab the datagram */
    if ( i2c_start() ) return I2C_ERROR;

    if ( i2c_write(0xC0) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_write(0x00) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_restart() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    if ( i2c_write(0xC1) ) return I2C_ERROR;
    if ( i2c_wait_ack() ) return I2C_ERROR;
    if ( i2c_idle() ) return I2C_ERROR;

    for (i = 0; i < 4; ++i)
    {
        /* grab a byte */
        i2c_read(&buffer[i]);

        if ( i < 3 )
        {
            if ( i2c_ack() ) return I2C_ERROR;
        }
        else
        {
            if ( i2c_nack() ) return I2C_ERROR;
        }

        if ( i2c_idle() ) return I2C_ERROR;
    }

    if ( i2c_stop() ) return I2C_ERROR;

    /* convert the bytes into pressure and temp */
    p = (float)(((buffer[0] << 8) | buffer[1]) >> 6);
    t = (float)(((buffer[2] << 8) | buffer[3]) >> 6);

    pc = a0 + (b1 + c12 * t) * p + b2 * t;

    *temp = (t - 498.0) / -5.35 + 25.0;         /* C    */
    *pressure = ((65.0 / 1023.0) * pc) + 50.0;  /* kPa  */

    return I2C_SUCCESS;
}

/***************************************************************************/
/* I2C functions                                                           */
/***************************************************************************/
/* If successful I2C functions will return 0, otherwise a value of 1 will  */
/* be returned                                                             */
/***************************************************************************/

void i2c_init()
{
    /* https://www.microchip.com/forums/m436598.aspx */
    I2C1BRG = ( (FCY/FSCL) - (FCY/1111111) ) - 1;

    I2C1CONbits.I2CEN = 0;      /* Disable I2C */
    I2C1CONbits.DISSLW = 1;     /* Disable slew rate control */
    I2C1CONbits.A10M = 0;       /* 7-bit slave addr */
    I2C1CONbits.SCLREL = 1;     /* SCL release control */

    /* diable interrupts */
    IEC1bits.MI2C1IE = 0;       /* Master I2C interrupt */
    IFS1bits.MI2C1IF = 0;       /* MI2C Flag */

    /* wait a few cycles for MI2C1Ix */
    Nop();
    Nop();

    I2C1CONbits.I2CEN = 1;      /* Enable I2C */
}

int i2c_start()
{
    int timeout = 0;

    I2C1CONbits.ACKDT = 0;      /* Reset any ACK */
    I2C1CONbits.SEN = 1;        /* Start */

    while ( I2C1CONbits.SEN )
    {
        if ( ++timeout > I2C_TIMEOUT_COUNT )
            return I2C_ERROR;
    }

    return I2C_SUCCESS;
}

int i2c_restart()
{
    int timeout = 0;

    I2C1CONbits.RSEN = 1;       /* Repeated Start Condition */

    while ( I2C1CONbits.RSEN )
    {
        if ( ++timeout > I2C_TIMEOUT_COUNT )
            return I2C_ERROR;
    }

    I2C1CONbits.ACKDT = 0;      /* Send ACK */
    I2C1STATbits.TBF = 0;       /* I2C1TRN is empty */

    return I2C_SUCCESS;
}

int i2c_stop()
{
    int timeout = 0;

    I2C1CONbits.RCEN = 0;       /* receive mode not in progress */
    I2C1CONbits.PEN = 1;        /* Stop condition */

    while ( I2C1CONbits.PEN )
    {
        if ( ++timeout > I2C_TIMEOUT_COUNT )
            return I2C_ERROR;
    }

    return I2C_SUCCESS;
}

int i2c_idle()
{
    int timeout = 0;

    while ( I2C1STATbits.TRSTAT )
    {
        if ( ++timeout > I2C_TIMEOUT_COUNT )
            return I2C_ERROR;
    }

    return I2C_SUCCESS;
}

int i2c_ack()
{
    int timeout = 0;

    I2C1CONbits.ACKDT = 0;      /* Send ACK */
    I2C1CONbits.ACKEN = 1;      /* Initiate Acknowledge and transmit ACKDT */

    while ( I2CCONbits.ACKEN )
    {
        if ( ++timeout > I2C_TIMEOUT_COUNT )
            return I2C_ERROR;
    }

    return I2C_SUCCESS;
}

int i2c_nack()
{
    int timeout = 0;

    I2C1CONbits.ACKDT = 1;      /* Send NACK */
    I2C1CONbits.ACKEN = 1;      /* Initiate Acknowledge and transmit ACKDT */

    while ( I2CCONbits.ACKEN )
    {
        if ( ++timeout > I2C_TIMEOUT_COUNT )
            return I2C_ERROR;
    }

    return I2C_SUCCESS;
}

int i2c_wait_ack()
{
    int timeout = 0;

    while ( I2C1STATbits.ACKSTAT )
    {
        if ( ++timeout > I2C_TIMEOUT_COUNT )
            return I2C_ERROR;
    }

    return I2C_SUCCESS;
}

int i2c_read(unsigned char* c)
{
    int timeout = 0;

    I2CCONbits.RCEN = 1;

    /* wait a bit to make sure RBF is clear */
    Nop();
    Nop();

    while ( !I2CSTATbits.RBF )
    {
        if ( ++timeout > I2C_TIMEOUT_COUNT )
            return I2C_ERROR;
    }

    /* Retrieve value from I2C register */
    *c = I2C1RCV;

    return I2C_SUCCESS;
}

int i2c_write(unsigned char c)
{
    int timeout = 0;

    I2C1TRN = c;

    while ( I2C1STATbits.TBF )
    {
        if ( ++timeout > I2C_TIMEOUT_COUNT )
            return I2C_ERROR;
    }

    return I2C_SUCCESS;
}

