/*
 * i2c.c
 *
 * routines more or less copied from WCH example code, then fucked around with to work.
 *
 * these routines have serious issues.
 * - any i2c issue may lock up the machine.
 * - timeout handlers are hastily added and may have problems.
 * - there's no error handling of any kind
 * - the library code makes some serious assumptions re: flags
 * - it's a shitpile of polling
 */

#include <ch32v00x.h>



#define I2C_TIMEOUT             0xffff
#define I2C_TIMEOUT_ACK_POLL    0x180

static uint32_t timeout;


void i2c_init()
{
    I2C_InitTypeDef i2c = {0};

    // ensure GPIO pins are configured before initializing

    i2c.I2C_ClockSpeed = 666666;
    i2c.I2C_Mode = I2C_Mode_I2C;
    i2c.I2C_DutyCycle = I2C_DutyCycle_2; // 16_9;
    i2c.I2C_Ack = I2C_Ack_Enable;
    i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_Init(I2C1, &i2c);

    I2C_Cmd(I2C1, ENABLE);
}

/*
 * reads data from devices which use a single-byte address register
 */
int8_t i2c_read_addr1b(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len)
{
    timeout = I2C_TIMEOUT;
    while((I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET) && timeout--);
    if (!timeout) return -1;

    I2C_GenerateSTART(I2C1, ENABLE);
    timeout = I2C_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && timeout--);
    if (!timeout) return -2;

    I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
    timeout = I2C_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) && timeout--);
    if (!timeout) return -3;

    I2C_AcknowledgeConfig(I2C1, DISABLE);
    I2C_SendData(I2C1, reg);
    timeout = I2C_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) && timeout--);
    if (!timeout) return -4;

    I2C_AcknowledgeConfig(I2C1, ENABLE);
    I2C_GenerateSTART(I2C1, ENABLE);
    timeout = I2C_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && timeout--);
    if (!timeout) return -5;

    I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);
    timeout = I2C_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) && timeout--);
    if (!timeout) return -6;

    while (len) {
       while(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET) {
           I2C_AcknowledgeConfig(I2C1, len);
       }

       *data++ = I2C_ReceiveData(I2C1);
       len--;

       if (!len) {
           I2C_GenerateSTOP(I2C1, ENABLE);
       }
    }

    return 0;
}

uint8_t i2c_read_reg_8b(uint8_t addr, uint8_t reg)
{
    uint8_t dat;

    i2c_read_addr1b(addr, reg, &dat, 1);
    return dat;
}

int8_t i2c_write_addr1b(uint8_t addr, uint8_t reg, const uint8_t *data, uint8_t len)
{
    timeout = I2C_TIMEOUT;
    while((I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET) && timeout--);
    if (!timeout) return -1;

    I2C_AcknowledgeConfig(I2C1, ENABLE);

    I2C_GenerateSTART(I2C1, ENABLE);
    timeout = I2C_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && timeout--);
    if (!timeout) return -2;

    I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
    timeout = I2C_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) && timeout--);
    if (!timeout) return -3;

    I2C_SendData(I2C1, reg);
    timeout = I2C_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) && timeout--);
    if (!timeout) return -4;

    while (len) {
        // fixme: can get stuck here if address isn't found.
        // somehow all the above passes but this will fail
        if (I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE) != RESET) {
            I2C_SendData(I2C1, *data++);
            len--;
            while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
            continue;
        }
        // failed to acknowledge...
        if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF)) {
            break;
        }
    }

    I2C_GenerateSTOP(I2C1, ENABLE);

    return 0;
}

void i2c_write_reg_8b(uint8_t addr, uint8_t reg, const uint8_t dat)
{
    i2c_write_addr1b(addr, reg, &dat, 1);
}

uint8_t i2c_addr_scan(uint8_t addr)
{
    uint8_t found = 1;
    uint32_t event;

    // no low addresses
    if ((addr >> 4) == 0) return 0;
    // no high addresses
    if ((addr & 0xf8) == 0xf8) return 0;

    timeout = I2C_TIMEOUT;
    while((I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET) && timeout--);
    if (!timeout) return -1;

    I2C_GenerateSTART(I2C1, ENABLE);
    timeout = I2C_TIMEOUT;
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && timeout--);
    if (!timeout) return -2;

    I2C_Send7bitAddress(I2C1, addr, (addr & 1));
    timeout = I2C_TIMEOUT_ACK_POLL;
    if (addr & 1) event = I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED;
    else          event = I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED;

    while (I2C_CheckEvent(I2C1, event) && timeout--) {
        if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF)) {
            found = 0;
            break;
        }

    }

    if (!timeout) {
        found = 0;
    }

    // reset flags; it might be in a fucked state
    I2C1->STAR1 = 0;

    // send a stop to make sure anything listening knows to stfu
    I2C_GenerateSTOP(I2C1, ENABLE);

    if (!found) {
        return 0;
    }

    return addr;
}
