Skip to content

Commit

Permalink
hw/i2c: add ESP32 I2C
Browse files Browse the repository at this point in the history
  • Loading branch information
peterus authored and igrr committed Feb 19, 2021
1 parent 3797dc3 commit 75e0baf
Show file tree
Hide file tree
Showing 3 changed files with 376 additions and 0 deletions.
266 changes: 266 additions & 0 deletions hw/i2c/esp32_i2c.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
#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;
case A_I2C_TIMEOUT:
s->timeout_reg = value;
case A_I2C_SDA_HOLD:
s->sda_hold_reg = value;
case A_I2C_SDA_SAMPLE:
s->sda_sample_reg = value;
case A_I2C_HIGH_PERIOD:
s->high_period_reg = value;
case A_I2C_LOW_PERIOD:
s->low_period_reg = value;
case A_I2C_START_HOLD:
s->start_hold_reg = value;
case A_I2C_RSTART_SETUP:
s->rstart_setup_reg = value;
case A_I2C_STOP_HOLD:
s->stop_hold_reg = value;
case A_I2C_STOP_SETUP:
s->stop_setup_reg = value;
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 (uint nbytes = 0; nbytes < length; ++nbytes) {
uint8_t data = fifo8_pop(&s->tx_fifo);
i2c_send(s->bus, data);
}
break;
}
case I2C_OPCODE_READ: {
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->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)
1 change: 1 addition & 0 deletions hw/i2c/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ i2c_ss.add(when: 'CONFIG_SMBUS_EEPROM', if_true: files('smbus_eeprom.c'))
i2c_ss.add(when: 'CONFIG_VERSATILE_I2C', if_true: files('versatile_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'))
softmmu_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
109 changes: 109 additions & 0 deletions include/hw/i2c/esp32_i2c.h
Original file line number Diff line number Diff line change
@@ -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 */

0 comments on commit 75e0baf

Please sign in to comment.