/*
 * i2c_device.c
 * started Oct 12, 2024
 *
 * ended up using some code from 003fun project, adapting it to my
 * needs. it isn't quite what I want, but the emulated register file
 * can be made to work in a time crunch.
 *
 * some notes:
 * - writing does not loop; if writing to the end then new writes will cause an error interrupt.
 * - added write protection for first 16 bytes.
 * - the code don't send NACKs on invalid writes.
 * - write registers will only commit to action upon valid I2C stop received.
 *
 *
 * register map:
 * see i2c_slave.h
 *
 */

#include "i2c_slave.h"

#include "device/btn.h"
#include "device/intr_out.h"
#include "device/irda.h"
#include "device/misc_gpo.h"



volatile uint8_t i2cs_reg[256*3] = {0};

i2c_slave_state i2cs_state;

static const uint8_t write_cb_trig[] = {
    REG_INTR_FLAGS_CLEAR,
    REG_RGB_HWEN,
    REG_IRDA_MODE,
    REG_BTN1_HOLD_LO,
    REG_BTN2_HOLD_LO,
    REG_BTN3_HOLD_LO,
    REG_BTN4_HOLD_LO,
    REG_BTN5_HOLD_LO,
    REG_BTN1_REPEAT_LO,
    REG_BTN2_REPEAT_LO,
    REG_BTN3_REPEAT_LO,
    REG_BTN4_REPEAT_LO,
    REG_BTN5_REPEAT_LO,
};

static void i2cs_int_check()
{
    if (i2cs_reg[REG_INTR_FLAGS] & i2cs_reg[REG_INTR_ENABLE]) {
        INT_OUT_SET();
    } else {
        INT_OUT_CLEAR();
    }
}


// called on a stop event.
// reg  - first register sent by master
// last - last register written by master - if less than reg, nothing was written
void i2cs_write_cb(uint16_t initial_reg, uint16_t last_reg)
{
    uint8_t i;
    uint8_t trig;
    uint16_t w;

    // nothing written?
    if (initial_reg > last_reg) return;

    // other single registers
    for (i = 0; i < sizeof(write_cb_trig); i++) {
        if ((initial_reg <= write_cb_trig[i]) && (last_reg >= write_cb_trig[i])) {
            trig = write_cb_trig[i];
        } else continue;

        switch (trig) {
            case REG_INTR_FLAGS_CLEAR: {
                i2cs_reg[REG_INTR_FLAGS] &= ~i2cs_reg[REG_INTR_FLAGS_CLEAR];
                i2cs_int_check();

                break;
            }

            case REG_RGB_HWEN: {
                if (i2cs_reg[REG_RGB_HWEN]) GPO_RGB_HWEN_PORT->BSHR = GPO_RGB_HWEN_PIN;
                else                        GPO_RGB_HWEN_PORT->BCR  = GPO_RGB_HWEN_PIN;
                break;
            }

            case REG_IRDA_MODE: {
                w = i2cs_reg[REG_IRDA_TIMER] * 20;
                switch (i2cs_reg[trig]) {
                    case 0: irda_mode(IRDA_STATE_IDLE, 0); break;
                    case 1: irda_mode(IRDA_STATE_TX_HEADER, w); break;
                    case 2: irda_mode(IRDA_STATE_RX_DATA, w); break;
                }
                break;
            }

            case REG_BTN1_HOLD_LO:
            case REG_BTN2_HOLD_LO:
            case REG_BTN3_HOLD_LO:
            case REG_BTN4_HOLD_LO:
            case REG_BTN5_HOLD_LO: {
                w = (trig - REG_BTN1_HOLD_LO) >> 1;
                btn[w].hold = (i2cs_reg[trig - 1] << 8) | i2cs_reg[trig];
                break;
            }

            case REG_BTN1_REPEAT_LO:
            case REG_BTN2_REPEAT_LO:
            case REG_BTN3_REPEAT_LO:
            case REG_BTN4_REPEAT_LO:
            case REG_BTN5_REPEAT_LO: {
                w = (trig - REG_BTN1_REPEAT_LO) >> 1;
                btn[w].repeat = (i2cs_reg[trig - 1] << 8) | i2cs_reg[trig];
                break;
            }
        }
    }
}

// called when writing to a read-only register,
// writing past the end of the register file,
// or writing when read-only.
// reg   - register written to
// data  - data attempted to write to register
// error - see i2c_slave.h for error types
void i2cs_werr_cb(uint16_t reg, uint8_t data, uint8_t error)
{
    i2cs_reg[REG_WERR_LO]++;
    if (!i2cs_reg[REG_WERR_LO]) i2cs_reg[REG_WERR_HI]++;
}

// called upon every register read
void i2cs_read_cb(uint16_t reg)
{
    switch (reg) {
        case REG_BTN_PUSHED:
        case REG_BTN_HELD:
        case REG_BTN_RELEASED: {
            i2cs_reg[reg] = 0;
            break;
        }
    }
}

void i2cs_init(uint8_t address, uint8_t read_only_mode)
{
    I2C_InitTypeDef i2c = {0};

    i2cs_state.reg_file = i2cs_reg;
    i2cs_state.reg_len = sizeof(i2cs_reg);
    i2cs_state.write_callback = i2cs_write_cb;
    i2cs_state.werr_callback = i2cs_werr_cb;
    i2cs_state.read_callback = i2cs_read_cb;
    i2cs_state.read_only = read_only_mode;

    i2c.I2C_ClockSpeed  = 600000;
    i2c.I2C_Mode        = I2C_Mode_I2C;
    i2c.I2C_DutyCycle   = I2C_DutyCycle_2;
    i2c.I2C_OwnAddress1 = address;
    i2c.I2C_Ack         = I2C_Ack_Enable;
    i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;

    I2C_Init(I2C1, &i2c);

    // enable interrupts
    I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN | I2C_CTLR2_ITERREN;

    NVIC_SetPriority(I2C1_EV_IRQn, 0 << 6);
    NVIC_EnableIRQ(I2C1_EV_IRQn); // Event interrupt
    NVIC_SetPriority(I2C1_ER_IRQn, 3 << 6);
    NVIC_EnableIRQ(I2C1_ER_IRQn); // Error interrupt

    // enable I2C
    I2C_Cmd(I2C1, ENABLE);
}

void i2cs_append_flag(uint8_t flag)
{
    if (flag) {
        i2cs_reg[REG_INTR_FLAGS] |= flag;
        i2cs_int_check();
    }
}

void i2cs_poll()
{

}

__attribute__((interrupt("WCH-Interrupt-fast")))
void I2C1_EV_IRQHandler(void)
{
    uint16_t STAR1;
    uint16_t STAR2 __attribute__((unused));

    STAR1 = I2C1->STAR1;
    STAR2 = I2C1->STAR2;

    if (STAR1 & I2C_STAR1_ADDR) { // Start event
        i2cs_state.addr_write = 1;              // Next write will be the 16-bit register address
        i2cs_state.addr_req = 0;                // reset this address
        i2cs_state.writing = 0;                 // not yet writing data from master
    }

    if (STAR1 & I2C_STAR1_RXNE) { // Write from master event
        if (i2cs_state.addr_write == 2) {       // low address byte written
            i2cs_state.addr_req |= I2C1->DATAR;

            i2cs_state.position = i2cs_state.addr_req;  // position set to requested index
            i2cs_state.addr_write = 0;                  // not writing the address anymore
        } else
        if (i2cs_state.addr_write == 1) {       // high address byte written
            i2cs_state.addr_req = I2C1->DATAR << 8;
            i2cs_state.addr_write++;
        } else
        {                                       // Normal register write
            i2cs_state.writing = 1;

            if (i2cs_state.position < i2cs_state.reg_len && !i2cs_state.read_only) {
                // read only registers
                if (i2cs_state.position < 16) {
                    if (i2cs_state.werr_callback != 0) {
                        i2cs_state.werr_callback(i2cs_state.position, I2C1->DATAR, I2CS_WRITE_READ_ONLY_REGISTER);
                    }
                } else {
                    i2cs_state.reg_file[i2cs_state.position] = I2C1->DATAR;
                }
                i2cs_state.position++;
            } else {
                if (i2cs_state.werr_callback != 0) {
                    i2cs_state.werr_callback(i2cs_state.position, I2C1->DATAR,
                                             i2cs_state.read_only ? I2CS_WRITE_WHEN_READ_ONLY : I2CS_WRITE_BEYOND_END);
                }
            }
        }
    }

    if (STAR1 & I2C_STAR1_TXE) { // Read to master event
        if (i2cs_state.position < i2cs_state.reg_len) {
            I2C1->DATAR = i2cs_state.reg_file[i2cs_state.position];
            if (i2cs_state.read_callback != 0) {
                i2cs_state.read_callback(i2cs_state.position);
            }
            i2cs_state.position++;
        } else {
            I2C1->DATAR = 0x00;
        }
    }

    if (STAR1 & I2C_STAR1_STOPF) { // Stop event
        I2C1->CTLR1 &= ~(I2C_CTLR1_STOP); // Clear stop

        if (i2cs_state.write_callback != 0 && i2cs_state.writing) {
            i2cs_state.write_callback(i2cs_state.addr_req, i2cs_state.position - 1);
        }
    }
}

__attribute__((interrupt()))
void I2C1_ER_IRQHandler(void)
{
    uint8_t err = 0;
    uint16_t STAR1 = I2C1->STAR1;

    if (STAR1 & I2C_STAR1_BERR) { // Bus error
        err++;
        I2C1->STAR1 &= ~(I2C_STAR1_BERR); // Clear error
    }

    if (STAR1 & I2C_STAR1_ARLO) { // Arbitration lost error
        err++;
        I2C1->STAR1 &= ~(I2C_STAR1_ARLO); // Clear error
    }

    if (STAR1 & I2C_STAR1_AF) { // Acknowledge failure
        err++;
        I2C1->STAR1 &= ~(I2C_STAR1_AF); // Clear error
    }

    if (err) {
        I2C1->CTLR1 &= ~(I2C_CTLR1_STOP); // Clear stop
    }
}
