-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
376 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 */ |