| /* |
| * 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); |