[PATCH 1/5] hw/i2c: add K230 I2C controller model

Wang Zhongyu <[email protected]>
Newsgroups gmane.comp.emulators.qemu
Message-ID <[email protected]>
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]>
---
 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)
+{
+    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 */
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.