/*
 * (Fig.40 E-Fuse Program Flow)
 * Stage 10-9
 * Data=0xFF is set in Address=0xEC.
 * Data=0x03 is set in Address=0xEF.
 * Read out the data in Address=0x18 and Address=0x19.
 */
static void e_fuse_stage10_9(Serial *pc) {
    pc->printf("stage 10-9\r\n");
    _i2c_write(0xEC, 0xFF);
    _i2c_write(0xEF, 0x03);
    const uint8_t bit_replacemnt_18 = _i2c_read(0x18);
    const uint8_t bit_replacemnt_19 = _i2c_read(0x19);
    pc->printf("Check 0x18 => %d\r\n", bit_replacemnt_18);
    pc->printf("Check 0x19 => %d\r\n", bit_replacemnt_19);
    if (bit_replacemnt_18 == 0x82 && bit_replacemnt_19 == 0x00) {
        pc->printf("Bit Replacement (stage 10) is SUCCESSFUL\r\n");
    } else {
        pc->printf("Bit Replacement (stage 10) is FAILURE\r\n");
    }
}
/*
 * (Fig.40 E-Fuse Program Flow)
 * Stage 9
 * Data=0xFF is set in Address=0xEC.
 * Data=0x03 is set in Address=0xEF.
 * Read out the data in Address=0x27.
 * Data=0x00 is set in Address=0xEF.
 * Data=0x7F is set in Address=0xEC.
 *
 * @return 0 for success, 1 for failure : 0x27[4:0] & 0b10000(0x10)
 */
static uint8_t e_fuse_stage9(Serial *pc) {
    pc->printf("stage 9\r\n");
    // Table.20 List of E-Fuse program flow and setting value
    _i2c_write(0xEF, 0x00); // add this though it's missing in 12-6 Example of E-Fuse Programming
    _i2c_write(0xEC, 0xFF);
    _i2c_write(0xEF, 0x03);
    const uint8_t check_value = _i2c_read(0x27);
    const uint8_t check = check_value & 0x1f;
    pc->printf("Check 0x27[4:0] => %d\r\n", check);
    const uint8_t success = check & 0x10;
    // When lower 5bits data[4:0] is 00001, E-Fuse program is finished.
    // When lower 5bits data[4:0] is not 00001, go to stage10(bit replacement).
    _i2c_write(0xEF, 0x00);
    _i2c_write(0xEC, 0x7F);
    // Check Result
    return success;
}
static int32_t
_i2c_smbus_ioctl(int dev, uint8_t rw, uint8_t command, size_t size, union i2c_smbus_data *data)
{
    struct i2c_smbus_ioctl_data ioctldata = {
        .read_write = rw,
        .command = command,
        .size = 0,
        .data = data
    };

    switch (size) {
    case 1:
        ioctldata.size = I2C_SMBUS_BYTE_DATA;
        break;
    case 2:
        ioctldata.size = I2C_SMBUS_WORD_DATA;
        break;
    default:
        ioctldata.size = I2C_SMBUS_BLOCK_DATA;
    }

    if (ioctl(dev, I2C_SMBUS, &ioctldata) == -1) {
        return -errno;
    }

    return 0;
}

static void
_i2c_write_quick(struct sol_i2c *i2c, bool rw)
{
    struct i2c_smbus_ioctl_data ioctldata = {
        .read_write = rw,
        .command = 0,
        .size = I2C_SMBUS_QUICK,
        .data = NULL
    };

    if (ioctl(i2c->dev, I2C_SMBUS, &ioctldata) == -1) {
        SOL_WRN("Unable to perform I2C-SMBus write quick (bus = %u,"
            " device address = %u): %s", i2c->bus, i2c->addr,
            sol_util_strerrora(errno));
        return;
    }

    i2c->async.status = 1;
}

static void
_i2c_write_quick_dispatch(struct sol_i2c *i2c)
{
    if (!i2c->async.write_quick_cb.cb) return;
    i2c->async.write_quick_cb.cb((void *)i2c->async.cb_data, i2c,
        i2c->async.status);
}

#ifdef WORKER_THREAD
static bool
i2c_write_quick_worker_thread_iterate(void *data)
{
    struct sol_i2c *i2c = data;

    _i2c_write_quick(i2c, (bool)(intptr_t)i2c->async.data);
    return false;
}

static void
i2c_worker_thread_finished(void *data)
{
    struct sol_i2c *i2c = data;

    i2c->async.worker = NULL;
    i2c->async.dispatch(i2c);
}
#else
static bool
i2c_write_quick_timeout_cb(void *data)
{
    struct sol_i2c *i2c = data;

    _i2c_write_quick(i2c, (bool)(intptr_t)i2c->async.data);
    i2c->async.timeout = NULL;
    i2c->async.dispatch(i2c);
    return false;
}
#endif

SOL_API struct sol_i2c_pending *
sol_i2c_write_quick(struct sol_i2c *i2c, bool rw, void (*write_quick_cb)(void *cb_data, struct sol_i2c *i2c, ssize_t status), const void *cb_data)
{
#ifdef WORKER_THREAD
    struct sol_worker_thread_spec spec = {
        .api_version = SOL_WORKER_THREAD_SPEC_API_VERSION,
        .setup = NULL,
        .cleanup = NULL,
        .iterate = i2c_write_quick_worker_thread_iterate,
        .finished = i2c_worker_thread_finished,
        .feedback = NULL,
        .data = i2c
    };
#endif

    SOL_NULL_CHECK(i2c, NULL);
    SOL_INT_CHECK(i2c->dev, == 0, NULL);
    BUSY_CHECK(i2c, NULL);

    i2c->async.data = (uint8_t *)(long)rw;
    i2c->async.status = -1;
    i2c->async.write_quick_cb.cb = write_quick_cb;
    i2c->async.dispatch = _i2c_write_quick_dispatch;
    i2c->async.cb_data = cb_data;

#ifdef WORKER_THREAD
    i2c->async.worker = sol_worker_thread_new(&spec);
    SOL_NULL_CHECK(i2c->async.worker, NULL);
    return (struct sol_i2c_pending *)i2c->async.worker;
#else
    i2c->async.timeout = sol_timeout_add(0, i2c_write_quick_timeout_cb, i2c);
    SOL_NULL_CHECK(i2c->async.timeout, NULL);
    return (struct sol_i2c_pending *)i2c->async.timeout;
#endif
}

static bool
write_byte(const struct sol_i2c *i2c, uint8_t byte)
{
    struct i2c_smbus_ioctl_data ioctldata = {
        .read_write = I2C_SMBUS_WRITE,
        .command = byte,
        .size = I2C_SMBUS_BYTE,
        .data = NULL
    };

    if (ioctl(i2c->dev, I2C_SMBUS, &ioctldata) == -1) {
        SOL_WRN("Unable to perform I2C-SMBus write byte (bus = %u,"
            " device address = %u): %s",
            i2c->bus, i2c->addr, sol_util_strerrora(errno));
        return false;
    }
    return true;
}

static bool
read_byte(const struct sol_i2c *i2c, uint8_t *byte)
{
    union i2c_smbus_data data;
    struct i2c_smbus_ioctl_data ioctldata = {
        .read_write = I2C_SMBUS_READ,
        .command = 0,
        .size = I2C_SMBUS_BYTE,
        .data = &data,
    };

    if (ioctl(i2c->dev, I2C_SMBUS, &ioctldata) == -1) {
        SOL_WRN("Unable to perform I2C-SMBus read byte (bus = %u,"
            " device address = %u): %s",
            i2c->bus, i2c->addr, sol_util_strerrora(errno));
        return false;
    }

    *byte = data.byte;

    return true;
}

static void
_i2c_read(struct sol_i2c *i2c, uint8_t *values)
{
    size_t i;

    for (i = 0; i < i2c->async.count; i++) {
        uint8_t byte;
        if (!read_byte(i2c, &byte))
            return;
        *values = byte;
        values++;
    }
    i2c->async.status = i2c->async.count;
}

static void
_i2c_read_write_dispatch(struct sol_i2c *i2c)
{
    if (!i2c->async.read_write_cb.cb) return;
    i2c->async.read_write_cb.cb((void *)i2c->async.cb_data, i2c,
        i2c->async.data, i2c->async.status);
}

#ifdef WORKER_THREAD
static bool
i2c_read_worker_thread_iterate(void *data)
{
    struct sol_i2c *i2c = data;

    _i2c_read(i2c, i2c->async.data);
    return false;
}
#else
static bool
i2c_read_timeout_cb(void *data)
{
    struct sol_i2c *i2c = data;

    _i2c_read(i2c, i2c->async.data);
    i2c->async.timeout = NULL;
    i2c->async.dispatch(i2c);
    return false;
}
#endif

SOL_API struct sol_i2c_pending *
sol_i2c_read(struct sol_i2c *i2c, uint8_t *values, size_t count, void (*read_cb)(void *cb_data, struct sol_i2c *i2c, uint8_t *data, ssize_t status), const void *cb_data)
{
#ifdef WORKER_THREAD
    struct sol_worker_thread_spec spec = {
        .api_version = SOL_WORKER_THREAD_SPEC_API_VERSION,
        .setup = NULL,
        .cleanup = NULL,
        .iterate = i2c_read_worker_thread_iterate,
        .finished = i2c_worker_thread_finished,
        .feedback = NULL,
        .data = i2c
    };
#endif

    SOL_NULL_CHECK(i2c, NULL);
    SOL_NULL_CHECK(values, NULL);
    SOL_INT_CHECK(count, == 0, NULL);
    SOL_INT_CHECK(i2c->dev, == 0, NULL);
    BUSY_CHECK(i2c, NULL);

    i2c->async.data = values;
    i2c->async.count = count;
    i2c->async.status = -1;
    i2c->async.read_write_cb.cb = read_cb;
    i2c->async.dispatch = _i2c_read_write_dispatch;
    i2c->async.cb_data = cb_data;

#ifdef WORKER_THREAD
    i2c->async.worker = sol_worker_thread_new(&spec);
    SOL_NULL_CHECK(i2c->async.worker, NULL);
    return (struct sol_i2c_pending *)i2c->async.worker;
#else
    i2c->async.timeout = sol_timeout_add(0, i2c_read_timeout_cb, i2c);
    SOL_NULL_CHECK(i2c->async.timeout, NULL);
    return (struct sol_i2c_pending *)i2c->async.timeout;
#endif
}

static void
_i2c_write(struct sol_i2c *i2c, uint8_t *values)
{
    size_t i;

    for (i = 0; i < i2c->async.count; i++) {
        if (!write_byte(i2c, *values))
            return;
        values++;
    }
    i2c->async.status = i2c->async.count;
}

#ifdef WORKER_THREAD
static bool
i2c_write_worker_thread_iterate(void *data)
{
    struct sol_i2c *i2c = data;

    _i2c_write(i2c, i2c->async.data);
    return false;
}
#else
static bool
i2c_write_timeout_cb(void *data)
{
    struct sol_i2c *i2c = data;

    _i2c_write(i2c, i2c->async.data);
    i2c->async.timeout = NULL;
    i2c->async.dispatch(i2c);
    return false;
}
#endif

SOL_API struct sol_i2c_pending *
sol_i2c_write(struct sol_i2c *i2c, uint8_t *values, size_t count, void (*write_cb)(void *cb_data, struct sol_i2c *i2c, uint8_t *data, ssize_t status), const void *cb_data)
{
#ifdef WORKER_THREAD
    struct sol_worker_thread_spec spec = {
        .api_version = SOL_WORKER_THREAD_SPEC_API_VERSION,
        .setup = NULL,
        .cleanup = NULL,
        .iterate = i2c_write_worker_thread_iterate,
        .finished = i2c_worker_thread_finished,
        .feedback = NULL,
        .data = i2c
    };
#endif

    SOL_NULL_CHECK(i2c, NULL);
    SOL_NULL_CHECK(values, NULL);
    SOL_INT_CHECK(count, == 0, NULL);
    SOL_INT_CHECK(i2c->dev, == 0, NULL);
    BUSY_CHECK(i2c, NULL);

    i2c->async.data = values;
    i2c->async.count = count;
    i2c->async.status = -1;
    i2c->async.read_write_cb.cb = write_cb;
    i2c->async.dispatch = _i2c_read_write_dispatch;
    i2c->async.cb_data = cb_data;

#ifdef WORKER_THREAD
    i2c->async.worker = sol_worker_thread_new(&spec);
    SOL_NULL_CHECK(i2c->async.worker, NULL);
    return (struct sol_i2c_pending *)i2c->async.worker;
#else
    i2c->async.timeout = sol_timeout_add(0, i2c_write_timeout_cb, i2c);
    SOL_NULL_CHECK(i2c->async.timeout, NULL);
    return (struct sol_i2c_pending *)i2c->async.timeout;
#endif
}

static int
sol_i2c_plain_read_register(const struct sol_i2c *i2c,
    uint8_t command,
    uint8_t *values,
    size_t count)
{
    struct i2c_msg msgs[] = {
        {
            .addr = i2c->addr,
            .flags = 0,
            .len = 1,
            .buf = &command
        },
        {
            .addr = i2c->addr,
            .flags = I2C_M_RD,
            .len = count,
            .buf = values,
        }
    };