diff --git a/hw/i2c/esp32_i2c.c b/hw/i2c/esp32_i2c.c new file mode 100644 index 0000000000..ea0131c911 --- /dev/null +++ b/hw/i2c/esp32_i2c.c @@ -0,0 +1,283 @@ +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/error-report.h" +#include "hw/i2c/esp32_i2c.h" +#include "hw/irq.h" + +static void esp32_i2c_do_transaction(Esp32I2CState * s); +static void esp32_i2c_update_irq(Esp32I2CState * s); + +static void esp32_i2c_reset(DeviceState * dev) +{ + Esp32I2CState * s = Esp32_I2C(dev); + + fifo8_reset(&s->rx_fifo); + fifo8_reset(&s->tx_fifo); + s->trans_ongoing = false; + s->ctr_reg = 0; + s->timeout_reg = 0; + s->int_ena_reg = 0; + s->int_raw_reg = 0; + s->sda_hold_reg = 0; + s->sda_sample_reg = 0; + s->high_period_reg = 0; + s->low_period_reg = 0; + s->start_hold_reg = 0; + s->rstart_setup_reg = 0; + s->stop_hold_reg = 0; + s->stop_setup_reg = 0; + memset(s->cmd_reg, 0, sizeof(s->cmd_reg)); + + fifo8_reset(&s->tx_fifo); + fifo8_reset(&s->rx_fifo); +} + +static uint32_t esp32_i2c_get_status_reg(Esp32I2CState* s) +{ + uint32_t res = 0; + res = FIELD_DP32(res, I2C_STATUS, BUS_BUSY, s->trans_ongoing); + res = FIELD_DP32(res, I2C_STATUS, RXFIFO_CNT, fifo8_num_used(&s->rx_fifo)); + res = FIELD_DP32(res, I2C_STATUS, TXFIFO_CNT, fifo8_num_used(&s->tx_fifo)); + return res; +} + +static void esp32_i2c_update_irq(Esp32I2CState * s) +{ + int irq_state = !!(s->int_raw_reg & s->int_ena_reg); + qemu_set_irq(s->irq, irq_state); +} + +static uint64_t esp32_i2c_read(void * opaque, hwaddr addr, unsigned int size) +{ + Esp32I2CState * s = Esp32_I2C(opaque); + + switch(addr) { + case A_I2C_CTR: + return s->ctr_reg; + case A_I2C_STATUS: + return esp32_i2c_get_status_reg(s); + case A_I2C_FIFO_DATA: { + if (fifo8_num_used(&s->rx_fifo) == 0) { + error_report("esp32_i2c: read I2C FIFO while it is empty"); + return 0xee; + } + uint8_t res = fifo8_pop(&s->rx_fifo); + return res; + } + case A_I2C_INT_RAW: + return s->int_raw_reg; + case A_I2C_INT_ENA: + return s->int_ena_reg; + case A_I2C_INT_ST: + return s->int_raw_reg & s->int_ena_reg; + case A_I2C_CMD ... (A_I2C_CMD + ESP32_I2C_CMD_COUNT * 4): + return s->cmd_reg[(addr - A_I2C_CMD) / 4]; + case A_I2C_TIMEOUT: + return s->timeout_reg; + case A_I2C_SDA_HOLD: + return s->sda_hold_reg; + case A_I2C_SDA_SAMPLE: + return s->sda_sample_reg; + case A_I2C_HIGH_PERIOD: + return s->high_period_reg; + case A_I2C_LOW_PERIOD: + return s->low_period_reg; + case A_I2C_START_HOLD: + return s->start_hold_reg; + case A_I2C_RSTART_SETUP: + return s->rstart_setup_reg; + case A_I2C_STOP_HOLD: + return s->stop_hold_reg; + case A_I2C_STOP_SETUP: + return s->stop_setup_reg; + default: + return 0; + } +} + +static void esp32_i2c_write(void * opaque, hwaddr addr, uint64_t value, unsigned int size) +{ + Esp32I2CState * s = Esp32_I2C(opaque); + + switch(addr) { + case A_I2C_CTR: + if (FIELD_EX32(value, I2C_CTR, MS_MODE) != 1) { + error_report("esp32_i2c: slave mode not implemented"); + } + if (FIELD_EX32(value, I2C_CTR, TRANS_START)) { + esp32_i2c_do_transaction(s); + value &= ~ R_I2C_CTR_TRANS_START_MASK; + } + s->ctr_reg = value; + break; + case A_I2C_FIFO_CONF: + if (FIELD_EX32(value, I2C_FIFO_CONF, NONFIFO_EN)) { + error_report("esp32_i2c: APB mode not implemented"); + } + if (FIELD_EX32(value, I2C_FIFO_CONF, RX_FIFO_RST)) { + fifo8_reset(&s->rx_fifo); + } + if (FIELD_EX32(value, I2C_FIFO_CONF, TX_FIFO_RST)) { + fifo8_reset(&s->tx_fifo); + } + break; + case A_I2C_FIFO_DATA: + if (fifo8_num_free(&s->tx_fifo) == 0) { + error_report("esp32_i2c: write to I2C TX FIFO while it is full"); + } else { + fifo8_push(&s->tx_fifo, value); + } + break; + case A_I2C_INT_CLR: + s->int_raw_reg &= ~value; + esp32_i2c_update_irq(s); + break; + case A_I2C_INT_ENA: + s->int_ena_reg = value; + esp32_i2c_update_irq(s); + break; + case A_I2C_CMD ... (A_I2C_CMD + ESP32_I2C_CMD_COUNT * 4): + s->cmd_reg[(addr - A_I2C_CMD) / 4] = value; + break; + case A_I2C_TIMEOUT: + s->timeout_reg = value; + break; + case A_I2C_SDA_HOLD: + s->sda_hold_reg = value; + break; + case A_I2C_SDA_SAMPLE: + s->sda_sample_reg = value; + break; + case A_I2C_HIGH_PERIOD: + s->high_period_reg = value; + break; + case A_I2C_LOW_PERIOD: + s->low_period_reg = value; + break; + case A_I2C_START_HOLD: + s->start_hold_reg = value; + break; + case A_I2C_RSTART_SETUP: + s->rstart_setup_reg = value; + break; + case A_I2C_STOP_HOLD: + s->stop_hold_reg = value; + break; + case A_I2C_STOP_SETUP: + s->stop_setup_reg = value; + break; + default: + break; + } +} + +static void esp32_i2c_do_transaction(Esp32I2CState * s) +{ + bool stop_or_end = false; + for (int i_cmd = 0; i_cmd < ESP32_I2C_CMD_COUNT && !stop_or_end; ++i_cmd) { + uint32_t cmd = s->cmd_reg[i_cmd]; + char opcode = FIELD_EX32(cmd, I2C_CMD, OPCODE); + switch (opcode) { + case I2C_OPCODE_RSTART: + i2c_end_transfer(s->bus); + s->trans_ongoing = false; + break; + case I2C_OPCODE_WRITE: { + size_t length = FIELD_EX32(cmd, I2C_CMD, BYTE_NUM); + if (!s->trans_ongoing) { + s->trans_ongoing = true; + uint8_t data = fifo8_pop(&s->tx_fifo); + uint8_t addr = data >> 1; + uint8_t is_read = data & 0x1; + if (i2c_start_transfer(s->bus, addr, is_read) != 0) { + /* NACK */ + if (FIELD_EX32(cmd, I2C_CMD, ACK_CHECK_EN) + && FIELD_EX32(cmd, I2C_CMD, ACK_EXP) == 0) { + s->int_raw_reg = FIELD_DP32(s->int_raw_reg, I2C_INT_RAW, ACK_ERR, 1); + stop_or_end = true; + } + s->trans_ongoing = false; + break; + } + s->int_raw_reg = FIELD_DP32(s->int_raw_reg, I2C_INT_RAW, ACK_ERR, 0); + length -= 1; + } + for (size_t nbytes = 0; nbytes < length; ++nbytes) { + uint8_t data = fifo8_pop(&s->tx_fifo); + i2c_send(s->bus, data); + } + break; + } + case I2C_OPCODE_READ: { + size_t length = FIELD_EX32(cmd, I2C_CMD, BYTE_NUM); + for (size_t nbytes = 0; nbytes < length; ++nbytes) { + if (fifo8_num_free(&s->rx_fifo) == 0) { + error_report("esp32_i2c: RX FIFO overflow"); + } else { + uint8_t data = i2c_recv(s->bus); + fifo8_push(&s->rx_fifo, data); + } + } + break; + } + case I2C_OPCODE_STOP: + i2c_end_transfer(s->bus); + s->trans_ongoing = false; + s->int_raw_reg = FIELD_DP32(s->int_raw_reg, I2C_INT_RAW, TRANS_COMPLETE, 1); + stop_or_end = true; + break; + case I2C_OPCODE_END: + s->int_raw_reg = FIELD_DP32(s->int_raw_reg, I2C_INT_RAW, END_DETECT, 1); + stop_or_end = true; + break; + default: + error_report("esp32_i2c: Invalid command %d opcode %d", i_cmd, opcode); + break; + } + s->cmd_reg[i_cmd] = FIELD_DP32(s->cmd_reg[i_cmd], I2C_CMD, DONE, 1); + } + esp32_i2c_update_irq(s); +} + +static const MemoryRegionOps esp32_i2c_ops = { + .read = esp32_i2c_read, + .write = esp32_i2c_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void esp32_i2c_init(Object * obj) +{ + Esp32I2CState *s = Esp32_I2C(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &esp32_i2c_ops, s, TYPE_ESP32_I2C, ESP32_I2C_MEM_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + + s->bus = i2c_init_bus(DEVICE(s), "i2c"); + + fifo8_create(&s->tx_fifo, ESP32_I2C_FIFO_LENGTH); + fifo8_create(&s->rx_fifo, ESP32_I2C_FIFO_LENGTH); +} + +static void esp32_i2c_class_init(ObjectClass * klass, void * data) +{ + DeviceClass * dc = DEVICE_CLASS(klass); + dc->legacy_reset = esp32_i2c_reset; +} + +static const TypeInfo esp32_i2c_type_info = { + .name = TYPE_ESP32_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Esp32I2CState), + .instance_init = esp32_i2c_init, + .class_init = esp32_i2c_class_init, +}; + +static void esp32_i2c_register_types(void) +{ + type_register_static(&esp32_i2c_type_info); +} + +type_init(esp32_i2c_register_types) diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build index c459adcb59..286b9c63f9 100644 --- a/hw/i2c/meson.build +++ b/hw/i2c/meson.build @@ -15,6 +15,7 @@ i2c_ss.add(when: 'CONFIG_SMBUS_EEPROM', if_true: files('smbus_eeprom.c')) i2c_ss.add(when: 'CONFIG_ARM_SBCON_I2C', if_true: files('arm_sbcon_i2c.c')) i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c')) i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: files('ppc4xx_i2c.c')) +i2c_ss.add(when: 'CONFIG_XTENSA_ESP32', if_true: files('esp32_i2c.c')) i2c_ss.add(when: 'CONFIG_PCA954X', if_true: files('i2c_mux_pca954x.c')) i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c')) i2c_ss.add(when: 'CONFIG_BCM2835_I2C', if_true: files('bcm2835_i2c.c')) diff --git a/include/hw/i2c/esp32_i2c.h b/include/hw/i2c/esp32_i2c.h new file mode 100644 index 0000000000..b17dc75c8d --- /dev/null +++ b/include/hw/i2c/esp32_i2c.h @@ -0,0 +1,109 @@ +#ifndef ESP32_I2C_H +#define ESP32_I2C_H + +#include "hw/sysbus.h" +#include "qemu/fifo8.h" +#include "hw/i2c/i2c.h" +#include "hw/registerfields.h" + +#define TYPE_ESP32_I2C "esp32.i2c" +#define Esp32_I2C(obj) OBJECT_CHECK(Esp32I2CState, (obj), TYPE_ESP32_I2C) + + +#define ESP32_I2C_MEM_SIZE 0x100 +#define ESP32_I2C_FIFO_LENGTH 32 +#define ESP32_I2C_CMD_COUNT 16 + + +typedef struct Esp32I2CState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + I2CBus *bus; + Fifo8 rx_fifo; + Fifo8 tx_fifo; + bool trans_ongoing; + + uint32_t ctr_reg; + uint32_t timeout_reg; + uint32_t int_ena_reg; + uint32_t int_raw_reg; + uint32_t sda_hold_reg; + uint32_t sda_sample_reg; + uint32_t high_period_reg; + uint32_t low_period_reg; + uint32_t start_hold_reg; + uint32_t rstart_setup_reg; + uint32_t stop_hold_reg; + uint32_t stop_setup_reg; + uint32_t cmd_reg[ESP32_I2C_CMD_COUNT]; +} Esp32I2CState; + + +REG32(I2C_CTR, 0x04); + FIELD(I2C_CTR, MS_MODE, 4, 1); + FIELD(I2C_CTR, TRANS_START, 5, 1); + +REG32(I2C_STATUS, 0x08); + FIELD(I2C_STATUS, BUS_BUSY, 4, 1); + FIELD(I2C_STATUS, RXFIFO_CNT, 8, 6); + FIELD(I2C_STATUS, TXFIFO_CNT, 18, 6); + +REG32(I2C_TIMEOUT, 0x0c); + +REG32(I2C_FIFO_CONF, 0x18); + FIELD(I2C_FIFO_CONF, NONFIFO_EN, 10, 1); + FIELD(I2C_FIFO_CONF, RX_FIFO_RST, 12, 1); + FIELD(I2C_FIFO_CONF, TX_FIFO_RST, 13, 1); + +REG32(I2C_FIFO_DATA, 0x1c); + +REG32(I2C_INT_RAW, 0x20); + FIELD(I2C_INT_RAW, ACK_ERR, 10, 1); + FIELD(I2C_INT_RAW, TRANS_COMPLETE, 7, 1); + FIELD(I2C_INT_RAW, END_DETECT, 3, 1); + +REG32(I2C_INT_CLR, 0x24); + FIELD(I2C_INT_CLR, ACK_ERR, 10, 1); + FIELD(I2C_INT_CLR, TRANS_COMPLETE, 7, 1); + FIELD(I2C_INT_CLR, END_DETECT, 3, 1); + +REG32(I2C_INT_ENA, 0x28); + FIELD(I2C_INT_ENA, ACK_ERR, 10, 1); + FIELD(I2C_INT_ENA, TRANS_COMPLETE, 7, 1); + FIELD(I2C_INT_ENA, END_DETECT, 3, 1); + +REG32(I2C_INT_ST, 0x2c); + FIELD(I2C_INT_ST, ACK_ERR, 10, 1); + FIELD(I2C_INT_ST, TRANS_COMPLETE, 7, 1); + FIELD(I2C_INT_ST, END_DETECT, 3, 1); + +REG32(I2C_SDA_HOLD, 0x30); +REG32(I2C_SDA_SAMPLE, 0x34); +REG32(I2C_HIGH_PERIOD, 0x38); +REG32(I2C_LOW_PERIOD, 0x00); // 0x00 is not a typo +REG32(I2C_START_HOLD, 0x40); +REG32(I2C_RSTART_SETUP, 0x44); +REG32(I2C_STOP_HOLD, 0x48); +REG32(I2C_STOP_SETUP, 0x4c); + +REG32(I2C_CMD, 0x58); + FIELD(I2C_CMD, BYTE_NUM, 0, 8); + FIELD(I2C_CMD, ACK_CHECK_EN, 8, 1); + FIELD(I2C_CMD, ACK_EXP, 9, 1); + FIELD(I2C_CMD, ACK_VAL, 10, 1); + FIELD(I2C_CMD, OPCODE, 11, 3); + FIELD(I2C_CMD, DONE, 31, 1); +/* 15 more command registers omitted */ + +/* I2C_CMD.OPCODE values */ +typedef enum { + I2C_OPCODE_RSTART = 0, + I2C_OPCODE_WRITE = 1, + I2C_OPCODE_READ = 2, + I2C_OPCODE_STOP = 3, + I2C_OPCODE_END = 4, +} i2c_opcode_t; + +#endif /* ESP32_I2C_H */