Re: [PATCH 1/5] hw/i2c: add K230 I2C controller model
Daniel Henrique Barboza <[email protected]>
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
Hello, On 8/7/2026 12:28 PM, Wang Zhongyu wrote: > Add a model of the I2C controller used by the Kendryte K230 SoC. > > The model supports master-mode 7-bit transfers, TX and RX FIFOs, > interrupt status and masking, repeated START and STOP handling, > General Call writes, reset, and migration state. > > DMA, 10-bit addressing, bus clear, Device ID transactions, and raw > START BYTE transmission are not implemented. > > Signed-off-by: Wang Zhongyu <[email protected]> > --- LGTM. Some comments below: > MAINTAINERS | 2 + > hw/i2c/Kconfig | 4 + > hw/i2c/k230_i2c.c | 1083 +++++++++++++++++++++++++++++++++++++ > hw/i2c/meson.build | 1 + > include/hw/i2c/k230_i2c.h | 72 +++ > 5 files changed, 1162 insertions(+) > create mode 100644 hw/i2c/k230_i2c.c > create mode 100644 include/hw/i2c/k230_i2c.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 902db77218..ac61054dd1 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -1827,8 +1827,10 @@ M: Chao Liu <[email protected]> > L: [email protected] > S: Maintained > F: docs/system/riscv/k230.rst > +F: hw/i2c/k230_i2c.c > F: hw/riscv/k230.c > F: hw/watchdog/k230_wdt.c > +F: include/hw/i2c/k230_i2c.h > F: include/hw/riscv/k230.h > F: include/hw/watchdog/k230_wdt.h > F: tests/functional/riscv64/test_k230.py > diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig > index 0766130b59..f2a2ea6169 100644 > --- a/hw/i2c/Kconfig > +++ b/hw/i2c/Kconfig > @@ -54,3 +54,7 @@ config PMBUS > config BCM2835_I2C > bool > select I2C > + > +config K230_I2C > + bool > + select I2C > diff --git a/hw/i2c/k230_i2c.c b/hw/i2c/k230_i2c.c > new file mode 100644 > index 0000000000..cc328736a4 > --- /dev/null > +++ b/hw/i2c/k230_i2c.c > @@ -0,0 +1,1083 @@ > +/* > + * K230 DesignWare I2C controller > + * > + * Copyright (c) 2026 Wang Zhongyu <[email protected]> > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > +#include "qemu/osdep.h" > +#include "hw/i2c/i2c.h" > +#include "hw/i2c/k230_i2c.h" > +#include "migration/vmstate.h" > +#include "qemu/log.h" > +#include "qemu/module.h" > +#include "qemu/bitops.h" > +#include "hw/core/irq.h" > +#include "qemu/fifo8.h" > +#include "qemu/fifo32.h" > + > +#define K230_I2C_MMIO_SIZE 0x1000 > + > +#define K230_IC_CON 0x00 > +#define K230_IC_TAR 0x04 > +#define K230_IC_SAR 0x08 > +#define K230_IC_HS_MADDR 0x0c > +#define K230_IC_DATA_CMD 0x10 > +#define K230_IC_SS_SCL_HCNT 0x14 > +#define K230_IC_SS_SCL_LCNT 0x18 > +#define K230_IC_FS_SCL_HCNT 0x1c > +#define K230_IC_FS_SCL_LCNT 0x20 > +#define K230_IC_HS_SCL_HCNT 0x24 > +#define K230_IC_HS_SCL_LCNT 0x28 > +#define K230_IC_INTR_STAT 0x2c > +#define K230_IC_INTR_MASK 0x30 > +#define K230_IC_RAW_INTR_STAT 0x34 > +#define K230_IC_RX_TL 0x38 > +#define K230_IC_TX_TL 0x3c > +#define K230_IC_CLR_INTR 0x40 > +#define K230_IC_CLR_RX_UNDER 0x44 > +#define K230_IC_CLR_RX_OVER 0x48 > +#define K230_IC_CLR_TX_OVER 0x4c > +#define K230_IC_CLR_RD_REQ 0x50 > +#define K230_IC_CLR_TX_ABRT 0x54 > +#define K230_IC_CLR_RX_DONE 0x58 > +#define K230_IC_CLR_ACTIVITY 0x5c > +#define K230_IC_CLR_STOP_DET 0x60 > +#define K230_IC_CLR_START_DET 0x64 > +#define K230_IC_CLR_GEN_CALL 0x68 > +#define K230_IC_ENABLE 0x6c > +#define K230_IC_STATUS 0x70 > +#define K230_IC_TXFLR 0x74 > +#define K230_IC_RXFLR 0x78 > +/* 0x7C reserved */ > +#define K230_IC_TX_ABRT_SOURCE 0x80 > +#define K230_IC_SLV_DATA_NACK_ONLY 0x84 > +#define K230_IC_DMA_CR 0x88 > +#define K230_IC_DMA_TDLR 0x8c > +#define K230_IC_DMA_RDLR 0x90 > +#define K230_IC_SDA_SETUP 0x94 > +#define K230_IC_ACK_GENERAL_CALL 0x98 > +#define K230_IC_ENABLE_STATUS 0x9c > +#define K230_IC_COMP_PARAM_1 0xf4 > +#define K230_IC_COMP_VERSION 0xf8 > +#define K230_IC_COMP_TYPE 0xfc > +/* > + * Optional bus-clear support is outside the scope of the initial model. > + * Unimplemented register offsets read as zero and ignore writes. > + */ > + > +/* IC_CON bit definitions */ > +#define IC_CON_MASTER_MODE BIT(0) > +#define IC_CON_SPEED (0x3U << 1) > +#define IC_CON_10BITADDR_SLAVE BIT(3) > +#define IC_CON_10BITADDR_MASTER BIT(4) > +#define IC_CON_RESTART_EN BIT(5) > +#define IC_CON_SLAVE_DISABLE BIT(6) > +#define IC_CON_VALID_MASK \ > + (IC_CON_MASTER_MODE | \ > + IC_CON_SPEED | \ > + IC_CON_10BITADDR_SLAVE | \ > + IC_CON_10BITADDR_MASTER | \ > + IC_CON_RESTART_EN | \ > + IC_CON_SLAVE_DISABLE) > + > +/* IC_TAR bit definitions */ > +#define IC_TAR (0x3ffU) > +#define IC_TAR_GC_OR_START BIT(10) > +#define IC_TAR_SPECIAL BIT(11) > +/* > + * This implementation selects the master addressing mode through > + * IC_CON[4]. IC_TAR[12] is not modeled as writable. > + */ > +#define IC_TAR_VALID_MASK \ > + (IC_TAR | \ > + IC_TAR_GC_OR_START | \ > + IC_TAR_SPECIAL) > + > +/* IC_SAR bit definitions */ > +#define IC_SAR (0x3ffU) > +#define IC_SAR_VALID_MASK IC_SAR > + > +/* IC_HS_MADDR bit definitions */ > +#define IC_HS_MAR (0x7U) > +#define IC_HS_MAR_VALID_MASK IC_HS_MAR > + > +/* IC_DATA_CMD bit definitions */ > +#define IC_DAT (0xffU) > +#define IC_CMD BIT(8) > +#define IC_STOP BIT(9) > +#define IC_RESTART BIT(10) > +#define IC_DATA_CMD_VALID_MASK (IC_DAT | IC_CMD | IC_STOP | IC_RESTART) > + > +/* > + * The SCL count registers are retained for software compatibility. > + * QEMU's I2C bus API does not model cycle-accurate bus timing, so these > + * values do not change the duration of a transfer. > + */ > +#define IC_SS_SCL_HCNT_VALID_MASK (0xffffU) > +#define IC_SS_SCL_LCNT_VALID_MASK (0xffffU) > +#define IC_FS_SCL_HCNT_VALID_MASK (0xffffU) > +#define IC_FS_SCL_LCNT_VALID_MASK (0xffffU) > +#define IC_HS_SCL_HCNT_VALID_MASK (0xffffU) > +#define IC_HS_SCL_LCNT_VALID_MASK (0xffffU) > + > +/* IC_INTR bit definitions */ > +#define IC_INTR_RX_UNDER BIT(0) > +#define IC_INTR_RX_OVER BIT(1) > +#define IC_INTR_RX_FULL BIT(2) > +#define IC_INTR_TX_OVER BIT(3) > +#define IC_INTR_TX_EMPTY BIT(4) > +#define IC_INTR_RD_REQ BIT(5) > +#define IC_INTR_TX_ABRT BIT(6) > +#define IC_INTR_RX_DONE BIT(7) > +#define IC_INTR_ACTIVITY BIT(8) > +#define IC_INTR_STOP_DET BIT(9) > +#define IC_INTR_START_DET BIT(10) > +#define IC_INTR_GEN_CALL BIT(11) > +#define IC_INTR_VALID_MASK (0xfffU) > +#define IC_INTR_SW_CLEAR_MASK \ > + (IC_INTR_RX_UNDER | IC_INTR_RX_OVER | \ > + IC_INTR_TX_OVER | IC_INTR_RD_REQ | \ > + IC_INTR_TX_ABRT | IC_INTR_RX_DONE | \ > + IC_INTR_STOP_DET | IC_INTR_START_DET | \ > + IC_INTR_GEN_CALL | IC_INTR_ACTIVITY) > + > +/* IC_RX_TL and IC_TX_TL bit definitions */ > +#define IC_RX_TL (0xffU) > +#define IC_TX_TL (0xffU) > + > +/* CLR Reg bit definitions */ > +#define IC_CLR_INTR BIT(0) > +#define IC_CLR_RX_UNDER BIT(0) > +#define IC_CLR_RX_OVER BIT(0) > +#define IC_CLR_TX_OVER BIT(0) > +#define IC_CLR_RD_REQ BIT(0) > +#define IC_CLR_TX_ABRT BIT(0) > +#define IC_CLR_RX_DONE BIT(0) > +#define IC_CLR_ACTIVITY BIT(0) > +#define IC_CLR_STOP_DET BIT(0) > +#define IC_CLR_START_DET BIT(0) > +#define IC_CLR_GEN_CALL BIT(0) > + > +/* IC_ENABLE bit definitions */ > +#define IC_ENABLE BIT(0) > + > +/* IC_STATUS bit definitions */ > +#define IC_ACTIVITY BIT(0) > +#define IC_TFNF BIT(1) > +#define IC_TFE BIT(2) > +#define IC_RFNE BIT(3) > +#define IC_RFF BIT(4) > +#define IC_MST_ACTIVITY BIT(5) > +#define IC_SLV_ACTIVITY BIT(6) > +#define IC_STATUS_VALID_MASK \ > + (IC_ACTIVITY | \ > + IC_TFNF | \ > + IC_TFE | \ > + IC_RFNE | \ > + IC_RFF | \ > + IC_MST_ACTIVITY | \ > + IC_SLV_ACTIVITY) > + > +/* FIFO level register bit definitions */ > +#define IC_TXFLR (0x3fU) > +#define IC_RXFLR (0x7fU) > + > +/* IC_TX_ABRT_SOURCE bit definitions */ > +#define IC_ABRT_7B_ADDR_NOACK BIT(0) > +#define IC_ABRT_10ADDR1_NOACK BIT(1) > +#define IC_ABRT_10ADDR2_NOACK BIT(2) > +#define IC_ABRT_TXDATA_NOACK BIT(3) > +#define IC_ABRT_GCALL_NOACK BIT(4) > +#define IC_ABRT_GCALL_READ BIT(5) > +#define IC_ABRT_HS_ACKDET BIT(6) > +#define IC_ABRT_SBYTE_ACKDET BIT(7) > +#define IC_ABRT_HS_NORSTRT BIT(8) > +#define IC_ABRT_SBYTE_NORSTRT BIT(9) > +#define IC_ABRT_10B_RD_NORSTRT BIT(10) > +#define IC_ABRT_MASTER_DIS BIT(11) > +#define IC_ARB_LOST BIT(12) > +#define IC_ABRT_SLVFLUSH_TXFIFO BIT(13) > +#define IC_ABRT_SLV_ARBLOST BIT(14) > +#define IC_ABRT_SLVRD_INTX BIT(15) > +#define IC_ABRT_VALID_MASK 0xffffU > + > +/* IC_SLV_DATA_NACK_ONLY bit definitions */ > +#define IC_SLV_NACK BIT(0) > + > +/* > + * DMA support is not modeled by this implementation. DMA register writes > + * are ignored, the registers remain at their reset values, and > + * IC_COMP_PARAM_1 reports HAS_DMA as clear. > + */ > +#define IC_SDA_SETUP (0xffU) > +#define IC_ACK_GEN_CALL BIT(0) > + > +/* IC_ENABLE_STATUS bit definitions */ > +#define IC_EN BIT(0) > +#define SLV_DISABLED_WHILE_BUSY BIT(1) > +#define SLV_RX_DATA_LOST BIT(2) > + > +/* IC_COMP_PARAM_1 bit definitions */ > +#define IC_COMP_PARAM_APB_DATA_WIDTH (0x3U) > +#define IC_COMP_PARAM_MAX_SPEED_MODE (0x3U << 2) > +#define IC_COMP_PARAM_HC_COUNT_VALUES BIT(4) > +#define IC_COMP_PARAM_INTR_IO BIT(5) > +#define IC_COMP_PARAM_HAS_DMA BIT(6) > +#define IC_COMP_PARAM_ADD_ENCODED_PARAMS BIT(7) > +#define IC_COMP_PARAM_RX_BUFFER_DEPTH (0xffU << 8) > +#define IC_COMP_PARAM_TX_BUFFER_DEPTH (0xffU << 16) > + > +#define K230_IC_COMP_PARAM_1_VALUE \ > + (2U | \ > + (3U << 2) | \ > + IC_COMP_PARAM_INTR_IO | \ > + IC_COMP_PARAM_ADD_ENCODED_PARAMS | \ > + ((K230_I2C_RX_FIFO_SIZE - 1U) << 8) | \ > + ((K230_I2C_TX_FIFO_SIZE - 1U) << 16)) > + > +static void k230_i2c_update_irq(K230I2CState *s) > +{ > + uint32_t intr; > + intr = s->ic_raw_intr_stat & s->ic_intr_mask & IC_INTR_VALID_MASK; > + qemu_set_irq(s->irq, intr != 0); > +} > + > +static uint64_t k230_i2c_clear_intr(K230I2CState *s, uint32_t mask) > +{ > + s->ic_raw_intr_stat &= ~mask; > + k230_i2c_update_irq(s); > + > + return 0; > +} > + > +static bool k230_i2c_is_activity(K230I2CState *s) Nit: I think you want this to be named either k230_i2c_is_active() or k230_i2c_has_activity(). Both works fine. Reviewed-by: Daniel Henrique Barboza <[email protected]> > +{ > + return (s->transfer_state != K230_I2C_STATE_IDLE); > +} > + > +static uint32_t k230_i2c_get_status(K230I2CState *s) > +{ > + uint32_t status = 0; > + > + if (k230_i2c_is_activity(s)) { > + status |= IC_ACTIVITY; > + status |= IC_MST_ACTIVITY; > + } > + > + if (!fifo32_is_full(&s->tx_fifo)) { > + status |= IC_TFNF; > + } > + > + if (fifo32_is_empty(&s->tx_fifo)) { > + status |= IC_TFE; > + } > + > + if (!fifo8_is_empty(&s->rx_fifo)) { > + status |= IC_RFNE; > + } > + > + if (fifo8_is_full(&s->rx_fifo)) { > + status |= IC_RFF; > + } > + return status; > +} > + > +static bool k230_i2c_clear_tx_abrt_bit9(K230I2CState *s) > +{ > + return (s->ic_con & IC_CON_RESTART_EN) || > + !(s->ic_tar & IC_TAR_SPECIAL) || > + !(s->ic_tar & IC_TAR_GC_OR_START); > +} > + > +static void k230_i2c_update_fifo_intr(K230I2CState *s) > +{ > + uint32_t rx_level = fifo8_num_used(&s->rx_fifo); > + uint32_t tx_level = fifo32_num_used(&s->tx_fifo); > + > + if (!(s->ic_enable & IC_ENABLE)) { > + s->ic_raw_intr_stat &= ~(IC_INTR_RX_FULL | IC_INTR_TX_EMPTY); > + return; > + } > + > + if (rx_level > s->ic_rx_tl) { > + s->ic_raw_intr_stat |= IC_INTR_RX_FULL; > + } else { > + s->ic_raw_intr_stat &= ~IC_INTR_RX_FULL; > + } > + > + if (!(s->ic_raw_intr_stat & IC_INTR_TX_ABRT) && > + tx_level <= s->ic_tx_tl) { > + s->ic_raw_intr_stat |= IC_INTR_TX_EMPTY; > + } else { > + s->ic_raw_intr_stat &= ~IC_INTR_TX_EMPTY; > + } > +} > + > +static uint64_t k230_i2c_clear_tx_abrt(K230I2CState *s) > +{ > + bool clr_bit9 = k230_i2c_clear_tx_abrt_bit9(s); > + bool had_bit9 = !!(s->ic_tx_abrt_source & IC_ABRT_SBYTE_NORSTRT); > + > + s->ic_raw_intr_stat &= ~IC_INTR_TX_ABRT; > + s->ic_tx_abrt_source = 0; > + if (!clr_bit9 && had_bit9) { > + s->ic_tx_abrt_source |= IC_ABRT_SBYTE_NORSTRT; > + s->ic_raw_intr_stat |= IC_INTR_TX_ABRT; > + } > + k230_i2c_update_fifo_intr(s); > + k230_i2c_update_irq(s); > + return 0; > +} > + > +static uint64_t k230_i2c_clear_all_intr(K230I2CState *s) > +{ > + bool active = k230_i2c_is_activity(s); > + s->ic_raw_intr_stat &= ~IC_INTR_SW_CLEAR_MASK; > + if (active) { > + s->ic_raw_intr_stat |= IC_INTR_ACTIVITY; > + } > + return k230_i2c_clear_tx_abrt(s); > +} > + > +static uint64_t k230_i2c_read_data_cmd(K230I2CState *s) > +{ > + uint8_t data; > + > + if (fifo8_is_empty(&s->rx_fifo)) { > + s->ic_raw_intr_stat |= IC_INTR_RX_UNDER; > + k230_i2c_update_irq(s); > + return 0; > + } > + > + data = fifo8_pop(&s->rx_fifo); > + if (fifo8_num_used(&s->rx_fifo) >= s->ic_rx_tl + 1) { > + s->ic_raw_intr_stat |= IC_INTR_RX_FULL; > + } else { > + s->ic_raw_intr_stat &= ~IC_INTR_RX_FULL; > + } > + > + k230_i2c_update_irq(s); > + return data; > +} > + > +static void k230_i2c_write_enable(K230I2CState *s, uint32_t value) > +{ > + bool enable_flag = !!(value & IC_ENABLE); > + > + if (enable_flag) { > + s->ic_enable = enable_flag; > + s->ic_enable_status = enable_flag; > + > + k230_i2c_update_fifo_intr(s); > + k230_i2c_update_irq(s); > + return; > + } > + > + if (s->transfer_state == K230_I2C_STATE_RECEIVING) { > + i2c_nack(s->bus); > + } > + > + if (k230_i2c_is_activity(s)) { > + i2c_end_transfer(s->bus); > + } > + > + fifo8_reset(&s->rx_fifo); > + fifo32_reset(&s->tx_fifo); > + > + s->transfer_state = K230_I2C_STATE_IDLE; > + s->ic_enable_status = 0; > + s->ic_enable = 0; > + s->ic_raw_intr_stat &= ~(IC_INTR_TX_EMPTY | > + IC_INTR_RX_FULL | > + IC_INTR_ACTIVITY); > + k230_i2c_update_irq(s); > +} > + > +static void k230_i2c_abort(K230I2CState *s, uint32_t source) > +{ > + s->ic_tx_abrt_source |= source & IC_ABRT_VALID_MASK; > + s->ic_raw_intr_stat |= IC_INTR_TX_ABRT; > + > + if (k230_i2c_is_activity(s)) { > + i2c_end_transfer(s->bus); > + } > + > + fifo32_reset(&s->tx_fifo); > + fifo8_reset(&s->rx_fifo); > + > + s->transfer_state = K230_I2C_STATE_IDLE; > + > + s->ic_raw_intr_stat &= ~(IC_INTR_TX_EMPTY | IC_INTR_RX_FULL); > + > + k230_i2c_update_irq(s); > +} > + > +static bool k230_i2c_start_address_phase(K230I2CState *s, bool is_read) > +{ > + uint8_t address; > + uint32_t noack_source; > + bool initial_start; > + bool special; > + bool general_call; > + bool start_byte; > + > + initial_start = (s->transfer_state == K230_I2C_STATE_IDLE); > + > + if (!(s->ic_con & IC_CON_MASTER_MODE)) { > + k230_i2c_abort(s, IC_ABRT_MASTER_DIS); > + return false; > + } > + > + /* > + * This implementation handles 7-bit master transfers only. Requests for > + * 10-bit master mode are aborted rather than treated as 7-bit transfers. > + */ > + if (s->ic_con & IC_CON_10BITADDR_MASTER) { > + qemu_log_mask(LOG_UNIMP, > + "k230-i2c: 10-bit master addressing " > + "is not implemented\n"); > + k230_i2c_abort(s, 0); > + return false; > + } > + > + special = !!(s->ic_tar & IC_TAR_SPECIAL); > + > + general_call = (special && !(s->ic_tar & IC_TAR_GC_OR_START)); > + > + start_byte = (special && !!(s->ic_tar & IC_TAR_GC_OR_START)); > + > + /* > + * QEMU's I2C bus API cannot emit the raw START byte before the target > + * address. Preserve the DW_apb_i2c RESTART_EN requirement, then fall > + * back to the normal target-address phase. > + */ > + if (initial_start && start_byte && !(s->ic_con & IC_CON_RESTART_EN)) { > + k230_i2c_abort(s, IC_ABRT_SBYTE_NORSTRT); > + return false; > + } > + > + if (general_call) { > + if (is_read) { > + k230_i2c_abort(s, IC_ABRT_GCALL_READ); > + return false; > + } > + address = 0; > + noack_source = IC_ABRT_GCALL_NOACK; > + } else { > + address = s->ic_tar & 0x7fU; > + noack_source = IC_ABRT_7B_ADDR_NOACK; > + } > + > + s->ic_raw_intr_stat |= IC_INTR_START_DET | IC_INTR_ACTIVITY; > + > + if (i2c_start_transfer(s->bus, address, is_read)) { > + k230_i2c_abort(s, noack_source); > + return false; > + } > + > + s->transfer_state = is_read ? K230_I2C_STATE_RECEIVING : > + K230_I2C_STATE_SENDING; > + > + return true; > +} > + > +/* > + * DATA_CMD writes are executed synchronously. The TX FIFO acts as a > + * transient command staging buffer, while the RX FIFO retains received > + * data for guest reads and FIFO-level interrupt handling. > + */ > +static void k230_i2c_write_data_cmd(K230I2CState *s, uint32_t value) > +{ > + uint32_t command; > + uint32_t tx_entry; > + uint8_t data; > + bool is_read; > + bool restart; > + bool stop; > + bool direction_changed; > + bool need_restart; > + int ret; > + > + if (!(s->ic_enable & IC_ENABLE)) { > + return; > + } > + > + if (s->ic_raw_intr_stat & IC_INTR_TX_ABRT) { > + return; > + } > + > + if (fifo32_is_full(&s->tx_fifo)) { > + s->ic_raw_intr_stat |= IC_INTR_TX_OVER; > + k230_i2c_update_irq(s); > + return; > + } > + > + command = value & IC_DATA_CMD_VALID_MASK; > + > + fifo32_push(&s->tx_fifo, command); > + > + tx_entry = fifo32_pop(&s->tx_fifo); > + > + data = tx_entry & IC_DAT; > + is_read = !!(tx_entry & IC_CMD); > + restart = !!(tx_entry & IC_RESTART); > + stop = !!(tx_entry & IC_STOP); > + > + direction_changed = > + (s->transfer_state == K230_I2C_STATE_SENDING && is_read) || > + (s->transfer_state == K230_I2C_STATE_RECEIVING && !is_read); > + > + need_restart = restart || direction_changed; > + > + switch (s->transfer_state) { > + case K230_I2C_STATE_IDLE: > + if (!k230_i2c_start_address_phase(s, is_read)) { > + return; > + } > + break; > + case K230_I2C_STATE_SENDING: > + case K230_I2C_STATE_RECEIVING: > + if (need_restart) { > + if (!(s->ic_con & IC_CON_RESTART_EN)) { > + qemu_log_mask(LOG_GUEST_ERROR, > + "k230-i2c: restart requested while " > + "IC_RESTART_EN is disabled\n"); > + k230_i2c_abort(s, 0); > + return; > + } > + > + if (s->transfer_state == K230_I2C_STATE_RECEIVING) { > + i2c_nack(s->bus); > + } > + > + if (!k230_i2c_start_address_phase(s, is_read)) { > + return; > + } > + } > + break; > + default: > + g_assert_not_reached(); > + } > + > + if (is_read) { > + data = i2c_recv(s->bus); > + if (fifo8_is_full(&s->rx_fifo)) { > + s->ic_raw_intr_stat |= IC_INTR_RX_OVER; > + } else { > + fifo8_push(&s->rx_fifo, data); > + } > + } else { > + ret = i2c_send(s->bus, data); > + if (ret) { > + k230_i2c_abort(s, IC_ABRT_TXDATA_NOACK); > + return; > + } > + } > + > + if (stop) { > + if (is_read) { > + i2c_nack(s->bus); > + } > + > + i2c_end_transfer(s->bus); > + > + s->transfer_state = K230_I2C_STATE_IDLE; > + s->ic_raw_intr_stat |= IC_INTR_STOP_DET; > + } > + > + k230_i2c_update_fifo_intr(s); > + k230_i2c_update_irq(s); > +} > + > +static uint64_t k230_i2c_read(void *opaque, hwaddr offset, unsigned size) > +{ > + K230I2CState *s = K230_I2C(opaque); > + uint64_t activity_flag; > + switch (offset) { > + case K230_IC_CON: > + return s->ic_con; > + case K230_IC_TAR: > + return s->ic_tar; > + case K230_IC_SAR: > + return s->ic_sar; > + case K230_IC_HS_MADDR: > + return s->ic_hs_maddr; > + case K230_IC_DATA_CMD: > + return k230_i2c_read_data_cmd(s); > + case K230_IC_SS_SCL_HCNT: > + return s->ic_ss_scl_hcnt; > + case K230_IC_SS_SCL_LCNT: > + return s->ic_ss_scl_lcnt; > + case K230_IC_FS_SCL_HCNT: > + return s->ic_fs_scl_hcnt; > + case K230_IC_FS_SCL_LCNT: > + return s->ic_fs_scl_lcnt; > + case K230_IC_HS_SCL_HCNT: > + return s->ic_hs_scl_hcnt; > + case K230_IC_HS_SCL_LCNT: > + return s->ic_hs_scl_lcnt; > + case K230_IC_INTR_STAT: > + return s->ic_raw_intr_stat & s->ic_intr_mask; > + case K230_IC_INTR_MASK: > + return s->ic_intr_mask; > + case K230_IC_RAW_INTR_STAT: > + return s->ic_raw_intr_stat; > + case K230_IC_RX_TL: > + return s->ic_rx_tl; > + case K230_IC_TX_TL: > + return s->ic_tx_tl; > + case K230_IC_CLR_INTR: > + return k230_i2c_clear_all_intr(s); > + case K230_IC_CLR_RX_UNDER: > + return k230_i2c_clear_intr(s, IC_INTR_RX_UNDER); > + case K230_IC_CLR_RX_OVER: > + return k230_i2c_clear_intr(s, IC_INTR_RX_OVER); > + case K230_IC_CLR_TX_OVER: > + return k230_i2c_clear_intr(s, IC_INTR_TX_OVER); > + case K230_IC_CLR_RD_REQ: > + return k230_i2c_clear_intr(s, IC_INTR_RD_REQ); > + case K230_IC_CLR_TX_ABRT: > + return k230_i2c_clear_tx_abrt(s); > + case K230_IC_CLR_RX_DONE: > + return k230_i2c_clear_intr(s, IC_INTR_RX_DONE); > + case K230_IC_CLR_ACTIVITY: > + activity_flag = !!(s->ic_raw_intr_stat & IC_INTR_ACTIVITY); > + if (!k230_i2c_is_activity(s)) { > + k230_i2c_clear_intr(s, IC_INTR_ACTIVITY); > + } > + return activity_flag; > + case K230_IC_CLR_STOP_DET: > + return k230_i2c_clear_intr(s, IC_INTR_STOP_DET); > + case K230_IC_CLR_START_DET: > + return k230_i2c_clear_intr(s, IC_INTR_START_DET); > + case K230_IC_CLR_GEN_CALL: > + return k230_i2c_clear_intr(s, IC_INTR_GEN_CALL); > + case K230_IC_ENABLE: > + return s->ic_enable; > + case K230_IC_STATUS: > + return k230_i2c_get_status(s); > + case K230_IC_TXFLR: > + return fifo32_num_used(&s->tx_fifo); > + case K230_IC_RXFLR: > + return fifo8_num_used(&s->rx_fifo); > + case K230_IC_TX_ABRT_SOURCE: > + return s->ic_tx_abrt_source; > + case K230_IC_SLV_DATA_NACK_ONLY: > + return s->ic_slv_data_nack_only; > + case K230_IC_DMA_CR: > + return s->ic_dma_cr; > + case K230_IC_DMA_TDLR: > + return s->ic_dma_tdlr; > + case K230_IC_DMA_RDLR: > + return s->ic_dma_rdlr; > + case K230_IC_SDA_SETUP: > + return s->ic_sda_setup; > + case K230_IC_ACK_GENERAL_CALL: > + return s->ic_ack_general_call; > + case K230_IC_ENABLE_STATUS: > + return s->ic_enable_status; > + case K230_IC_COMP_PARAM_1: > + return K230_IC_COMP_PARAM_1_VALUE; > + case K230_IC_COMP_VERSION: > + return 0x3132302aU; > + case K230_IC_COMP_TYPE: > + return 0x44570140U; > + default: > + return 0; > + } > +} > + > +static void k230_i2c_write(void *opaque, hwaddr offset, > + uint64_t value, unsigned size) > +{ > + K230I2CState *s = K230_I2C(opaque); > + uint32_t val = value; > + > + switch (offset) { > + case K230_IC_CON: { > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + s->ic_con = val & IC_CON_VALID_MASK; > + > + if (!((s->ic_con & IC_CON_SPEED) >> 1)) { > + s->ic_con |= IC_CON_SPEED; > + } > + s->ic_con |= IC_CON_SLAVE_DISABLE; > + break; > + } > + case K230_IC_TAR: { > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + s->ic_tar = val & IC_TAR_VALID_MASK; > + break; > + } > + case K230_IC_SAR: { > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + s->ic_sar = val & IC_SAR_VALID_MASK; > + break; > + } > + case K230_IC_HS_MADDR: { > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + s->ic_hs_maddr = val & IC_HS_MAR_VALID_MASK; > + break; > + } > + case K230_IC_DATA_CMD: { > + k230_i2c_write_data_cmd(s, val); > + break; > + } > + case K230_IC_SS_SCL_HCNT: { > + uint16_t count; > + > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + > + count = val & IC_SS_SCL_HCNT_VALID_MASK; > + > + if (count < 6) { > + count = 6; > + } else if (count > 65525) { > + qemu_log_mask(LOG_GUEST_ERROR, > + "k230-i2c: invalid IC_SS_SCL_HCNT value %u\n", > + (unsigned int)count); > + break; > + } > + > + s->ic_ss_scl_hcnt = count; > + > + break; > + } > + case K230_IC_SS_SCL_LCNT: { > + uint16_t count; > + > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + > + count = val & IC_SS_SCL_LCNT_VALID_MASK; > + > + if (count < 8) { > + count = 8; > + } > + > + s->ic_ss_scl_lcnt = count; > + > + break; > + } > + case K230_IC_FS_SCL_HCNT: { > + uint16_t count; > + > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + > + count = val & IC_FS_SCL_HCNT_VALID_MASK; > + > + if (count < 6) { > + count = 6; > + } > + > + s->ic_fs_scl_hcnt = count; > + > + break; > + } > + case K230_IC_FS_SCL_LCNT: { > + uint16_t count; > + > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + > + count = val & IC_FS_SCL_LCNT_VALID_MASK; > + > + if (count < 8) { > + count = 8; > + } > + > + s->ic_fs_scl_lcnt = count; > + > + break; > + } > + case K230_IC_HS_SCL_HCNT: { > + uint16_t count; > + > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + > + count = val & IC_HS_SCL_HCNT_VALID_MASK; > + > + if (count < 6) { > + count = 6; > + } > + > + s->ic_hs_scl_hcnt = count; > + > + break; > + } > + case K230_IC_HS_SCL_LCNT: { > + uint16_t count; > + > + if (s->ic_enable & IC_ENABLE) { > + break; > + } > + > + count = val & IC_HS_SCL_LCNT_VALID_MASK; > + > + if (count < 8) { > + count = 8; > + } > + > + s->ic_hs_scl_lcnt = count; > + > + break; > + } > + case K230_IC_INTR_MASK: { > + s->ic_intr_mask = val & IC_INTR_VALID_MASK; > + > + k230_i2c_update_irq(s); > + break; > + } > + case K230_IC_RX_TL: { > + uint8_t threshold; > + > + threshold = val & IC_RX_TL; > + if (threshold > K230_I2C_RX_FIFO_SIZE - 1) { > + threshold = K230_I2C_RX_FIFO_SIZE - 1; > + } > + s->ic_rx_tl = threshold; > + > + k230_i2c_update_fifo_intr(s); > + k230_i2c_update_irq(s); > + > + break; > + } > + case K230_IC_TX_TL: { > + uint8_t threshold; > + > + threshold = val & IC_TX_TL; > + if (threshold > K230_I2C_TX_FIFO_SIZE) { > + threshold = K230_I2C_TX_FIFO_SIZE; > + } > + s->ic_tx_tl = threshold; > + > + k230_i2c_update_fifo_intr(s); > + k230_i2c_update_irq(s); > + > + break; > + } > + case K230_IC_ENABLE: { > + k230_i2c_write_enable(s, val); > + break; > + } > + case K230_IC_TX_ABRT_SOURCE: { > + s->ic_tx_abrt_source = val & IC_ABRT_VALID_MASK; > + break; > + } > + case K230_IC_SLV_DATA_NACK_ONLY: { > + /* Slave mode is not supported. */ > + break; > + } > + case K230_IC_DMA_CR: { > + break; > + } > + case K230_IC_DMA_TDLR: { > + break; > + } > + case K230_IC_DMA_RDLR: { > + break; > + } > + case K230_IC_SDA_SETUP: { > + s->ic_sda_setup = val & IC_SDA_SETUP; > + break; > + } > + case K230_IC_ACK_GENERAL_CALL: { > + s->ic_ack_general_call = val & IC_ACK_GEN_CALL; > + break; > + } > + default: > + return; > + } > +} > + > +static void k230_i2c_reset(Object *obj, ResetType type) > +{ > + K230I2CState *s = K230_I2C(obj); > + > + if (s->transfer_state == K230_I2C_STATE_RECEIVING) { > + i2c_nack(s->bus); > + } > + > + if (k230_i2c_is_activity(s)) { > + i2c_end_transfer(s->bus); > + } > + > + fifo8_reset(&s->rx_fifo); > + fifo32_reset(&s->tx_fifo); > + > + s->transfer_state = K230_I2C_STATE_IDLE; > + > + s->ic_con = 0x7f; > + s->ic_tar = 0x055; > + s->ic_sar = 0x055; > + s->ic_hs_maddr = 0x1; > + > + s->ic_ss_scl_hcnt = 0x0190; > + s->ic_ss_scl_lcnt = 0x01d6; > + s->ic_fs_scl_hcnt = 0x003c; > + s->ic_fs_scl_lcnt = 0x0082; > + s->ic_hs_scl_hcnt = 0x0006; > + s->ic_hs_scl_lcnt = 0x0010; > + > + s->ic_intr_mask = 0x8ff; > + s->ic_raw_intr_stat = 0; > + > + s->ic_rx_tl = 0; > + s->ic_tx_tl = 0; > + > + s->ic_enable = 0; > + s->ic_enable_status = 0; > + > + s->ic_tx_abrt_source = 0; > + s->ic_slv_data_nack_only = 0; > + > + s->ic_dma_cr = 0; > + s->ic_dma_tdlr = 0; > + s->ic_dma_rdlr = 0; > + > + s->ic_sda_setup = 0x64; > + s->ic_ack_general_call = 0x1; > + > + qemu_set_irq(s->irq, 0); > +} > + > +static const MemoryRegionOps k230_i2c_ops = { > + .read = k230_i2c_read, > + .write = k230_i2c_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > + .valid = { > + .min_access_size = 4, > + .max_access_size = 4, > + }, > + .impl = { > + .min_access_size = 4, > + .max_access_size = 4, > + }, > +}; > + > +static void k230_i2c_init(Object *obj) > +{ > + K230I2CState *s = K230_I2C(obj); > + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); > + > + fifo8_create(&s->rx_fifo, K230_I2C_RX_FIFO_SIZE); > + fifo32_create(&s->tx_fifo, K230_I2C_TX_FIFO_SIZE); > + > + s->bus = i2c_init_bus(DEVICE(obj), "i2c"); > + > + memory_region_init_io(&s->iomem, obj, > + &k230_i2c_ops, s, > + TYPE_K230_I2C, K230_I2C_MMIO_SIZE); > + > + sysbus_init_mmio(sbd, &s->iomem); > + sysbus_init_irq(sbd, &s->irq); > +} > + > +static void k230_i2c_finalize(Object *obj) > +{ > + K230I2CState *s = K230_I2C(obj); > + > + fifo8_destroy(&s->rx_fifo); > + fifo32_destroy(&s->tx_fifo); > +} > + > +static int k230_i2c_post_load(void *opaque, int version_id) > +{ > + K230I2CState *s = opaque; > + > + if (s->transfer_state > K230_I2C_STATE_RECEIVING) { > + return -EINVAL; > + } > + > + k230_i2c_update_fifo_intr(s); > + k230_i2c_update_irq(s); > + > + return 0; > +} > + > +static const VMStateDescription vmstate_k230_i2c = { > + .name = TYPE_K230_I2C, > + .version_id = 1, > + .minimum_version_id = 1, > + .post_load = k230_i2c_post_load, > + .fields = (const VMStateField[]) { > + VMSTATE_UINT32(ic_con, K230I2CState), > + VMSTATE_UINT32(ic_tar, K230I2CState), > + VMSTATE_UINT32(ic_sar, K230I2CState), > + VMSTATE_UINT32(ic_hs_maddr, K230I2CState), > + > + VMSTATE_UINT32(ic_ss_scl_hcnt, K230I2CState), > + VMSTATE_UINT32(ic_ss_scl_lcnt, K230I2CState), > + VMSTATE_UINT32(ic_fs_scl_hcnt, K230I2CState), > + VMSTATE_UINT32(ic_fs_scl_lcnt, K230I2CState), > + VMSTATE_UINT32(ic_hs_scl_hcnt, K230I2CState), > + VMSTATE_UINT32(ic_hs_scl_lcnt, K230I2CState), > + > + VMSTATE_UINT32(ic_intr_mask, K230I2CState), > + VMSTATE_UINT32(ic_raw_intr_stat, K230I2CState), > + > + VMSTATE_UINT32(ic_rx_tl, K230I2CState), > + VMSTATE_UINT32(ic_tx_tl, K230I2CState), > + > + VMSTATE_UINT32(ic_enable, K230I2CState), > + VMSTATE_UINT32(ic_tx_abrt_source, K230I2CState), > + > + VMSTATE_UINT32(ic_sda_setup, K230I2CState), > + VMSTATE_UINT32(ic_slv_data_nack_only, K230I2CState), > + > + VMSTATE_UINT32(ic_dma_cr, K230I2CState), > + VMSTATE_UINT32(ic_dma_tdlr, K230I2CState), > + VMSTATE_UINT32(ic_dma_rdlr, K230I2CState), > + VMSTATE_UINT32(ic_ack_general_call, K230I2CState), > + VMSTATE_UINT32(ic_enable_status, K230I2CState), > + > + VMSTATE_FIFO8(rx_fifo, K230I2CState), > + VMSTATE_FIFO32(tx_fifo, K230I2CState), > + > + VMSTATE_UINT32(transfer_state, K230I2CState), > + > + VMSTATE_END_OF_LIST() > + }, > +}; > + > +static void k230_i2c_class_init(ObjectClass *klass, const void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + ResettableClass *rc = RESETTABLE_CLASS(klass); > + > + dc->vmsd = &vmstate_k230_i2c; > + rc->phases.hold = k230_i2c_reset; > +} > + > +static const TypeInfo k230_i2c_info = { > + .name = TYPE_K230_I2C, > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(K230I2CState), > + .instance_init = k230_i2c_init, > + .instance_finalize = k230_i2c_finalize, > + .class_init = k230_i2c_class_init, > +}; > + > +static void k230_i2c_register_types(void) > +{ > + type_register_static(&k230_i2c_info); > +} > + > +type_init(k230_i2c_register_types) > diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build > index 88aea35662..10da237dd9 100644 > --- a/hw/i2c/meson.build > +++ b/hw/i2c/meson.build > @@ -19,4 +19,5 @@ i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: files('ppc4xx_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')) > +i2c_ss.add(when: 'CONFIG_K230_I2C', if_true: files('k230_i2c.c')) > system_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss) > diff --git a/include/hw/i2c/k230_i2c.h b/include/hw/i2c/k230_i2c.h > new file mode 100644 > index 0000000000..94a4128f46 > --- /dev/null > +++ b/include/hw/i2c/k230_i2c.h > @@ -0,0 +1,72 @@ > +/* > + * K230 DesignWare I2C controller > + * > + * Copyright (c) 2026 Wang Zhongyu <[email protected]> > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > +#ifndef HW_I2C_K230_I2C_H > +#define HW_I2C_K230_I2C_H > + > +#include <stdint.h> > + > +#include "hw/core/sysbus.h" > +#include "hw/i2c/i2c.h" > +#include "qemu/fifo32.h" > +#include "qemu/fifo8.h" > + > +#define K230_I2C_RX_FIFO_SIZE 64 > +#define K230_I2C_TX_FIFO_SIZE 32 > + > +#define TYPE_K230_I2C "k230-i2c" > +OBJECT_DECLARE_SIMPLE_TYPE(K230I2CState, K230_I2C) > + > +typedef enum K230I2CTransferState { > + K230_I2C_STATE_IDLE, > + K230_I2C_STATE_SENDING, > + K230_I2C_STATE_RECEIVING, > +} K230I2CTransferState; > + > +struct K230I2CState { > + SysBusDevice parent_obj; > + > + MemoryRegion iomem; > + > + I2CBus *bus; > + qemu_irq irq; > + > + /* K230 I2C register list */ > + uint32_t ic_con; > + uint32_t ic_tar; > + uint32_t ic_sar; > + uint32_t ic_hs_maddr; > + uint32_t ic_ss_scl_hcnt; > + uint32_t ic_ss_scl_lcnt; > + uint32_t ic_fs_scl_hcnt; > + uint32_t ic_fs_scl_lcnt; > + uint32_t ic_hs_scl_hcnt; > + uint32_t ic_hs_scl_lcnt; > + > + uint32_t ic_intr_mask; > + uint32_t ic_raw_intr_stat; > + > + uint32_t ic_rx_tl; > + uint32_t ic_tx_tl; > + > + uint32_t ic_enable; > + > + uint32_t ic_tx_abrt_source; > + uint32_t ic_sda_setup; > + uint32_t ic_slv_data_nack_only; > + uint32_t ic_dma_cr; > + uint32_t ic_dma_tdlr; > + uint32_t ic_dma_rdlr; > + uint32_t ic_ack_general_call; > + uint32_t ic_enable_status; > + > + Fifo8 rx_fifo; > + Fifo32 tx_fifo; > + uint32_t transfer_state; > +}; > + > +#endif /* HW_I2C_K230_I2C_H */