/*
 * Example I2C device using asynchronous I2C send.
 *
 * Copyright (C) 2023 Samsung Electronics Co., Ltd. All Rights Reserved.
 *
 * This work is licensed under the terms of the GNU GPL, version 2.  See
 * the COPYING file in the top-level directory.
 *
 */

#include "qemu/osdep.h"
#include "qemu/timer.h"
#include "qemu/main-loop.h"
#include "block/aio.h"
#include "hw/i2c/i2c.h"

#define TYPE_I2C_ECHO "i2c-echo"
OBJECT_DECLARE_SIMPLE_TYPE(I2CEchoState, I2C_ECHO)

enum i2c_echo_state {
    I2C_ECHO_STATE_IDLE,
    I2C_ECHO_STATE_START_SEND,
    I2C_ECHO_STATE_ACK,
};

typedef struct I2CEchoState {
    I2CSlave parent_obj;

    I2CBus *bus;

    enum i2c_echo_state state;
    QEMUBH *bh;

    unsigned int pos;
    uint8_t data[3];
} I2CEchoState;

static void i2c_echo_bh(void *opaque)
{
    I2CEchoState *state = opaque;

    switch (state->state) {
    case I2C_ECHO_STATE_IDLE:
        return;

    case I2C_ECHO_STATE_START_SEND:
        if (i2c_start_send_async(state->bus, state->data[0])) {
            goto release_bus;
        }

        state->pos++;
        state->state = I2C_ECHO_STATE_ACK;
        return;

    case I2C_ECHO_STATE_ACK:
        if (state->pos > 2) {
            break;
        }

        if (i2c_send_async(state->bus, state->data[state->pos++])) {
            break;
        }

        return;
    }


    i2c_end_transfer(state->bus);
release_bus:
    i2c_bus_release(state->bus);

    state->state = I2C_ECHO_STATE_IDLE;
}

static int i2c_echo_event(I2CSlave *s, enum i2c_event event)
{
    I2CEchoState *state = I2C_ECHO(s);

    switch (event) {
    case I2C_START_RECV:
        state->pos = 0;

        break;

    case I2C_START_SEND:
        state->pos = 0;

        break;

    case I2C_FINISH:
        state->pos = 0;
        state->state = I2C_ECHO_STATE_START_SEND;
        i2c_bus_master(state->bus, state->bh);

        break;

    case I2C_NACK:
        break;

    default:
        return -1;
    }

    return 0;
}

static uint8_t i2c_echo_recv(I2CSlave *s)
{
    I2CEchoState *state = I2C_ECHO(s);

    if (state->pos > 2) {
        return 0xff;
    }

    return state->data[state->pos++];
}

static int i2c_echo_send(I2CSlave *s, uint8_t data)
{
    I2CEchoState *state = I2C_ECHO(s);

    if (state->pos > 2) {
        return -1;
    }

    state->data[state->pos++] = data;

    return 0;
}

static void i2c_echo_realize(DeviceState *dev, Error **errp)
{
    I2CEchoState *state = I2C_ECHO(dev);
    BusState *bus = qdev_get_parent_bus(dev);

    state->bus = I2C_BUS(bus);
    state->bh = qemu_bh_new(i2c_echo_bh, state);

    return;
}

static void i2c_echo_class_init(ObjectClass *oc, void *data)
{
    I2CSlaveClass *sc = I2C_SLAVE_CLASS(oc);
    DeviceClass *dc = DEVICE_CLASS(oc);

    dc->realize = i2c_echo_realize;

    sc->event = i2c_echo_event;
    sc->recv = i2c_echo_recv;
    sc->send = i2c_echo_send;
}

static const TypeInfo i2c_echo = {
    .name = TYPE_I2C_ECHO,
    .parent = TYPE_I2C_SLAVE,
    .instance_size = sizeof(I2CEchoState),
    .class_init = i2c_echo_class_init,
};

static void register_types(void)
{
    type_register_static(&i2c_echo);
}

type_init(register_types);