All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kyle Fox <kylefoxaustin.github@gmail.com>
To: qemu-devel@nongnu.org
Cc: Kyle Fox <kylefoxaustin.github@gmail.com>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Peter Maydell <peter.maydell@linaro.org>,
	qemu-arm@nongnu.org (open list:MCIMX95-19X19-EVK...)
Subject: [PATCH 06/16] hw/i2c: add i.MX LPI2C
Date: Wed, 19 Aug 2026 21:48:24 -0500	[thread overview]
Message-ID: <20260820024834.3286721-7-kylefoxaustin.github@gmail.com> (raw)
In-Reply-To: <20260820024834.3286721-1-kylefoxaustin.github@gmail.com>

The i.MX Low-Power I2C master. Models the polled master transfer path
(MCR/MSR, the MTDR command FIFO and the MRDR receive FIFO) that the
System Manager firmware uses to bring up the PMIC and IO expander. No
interrupts are modelled (MIER reads back 0). Slave devices attach on the
QEMU I2C bus.

Signed-off-by: Kyle Fox <kylefoxaustin.github@gmail.com>
---
 hw/i2c/Kconfig      |   4 +
 hw/i2c/imx_lpi2c.c  | 296 ++++++++++++++++++++++++++++++++++++++++++++
 hw/i2c/meson.build  |   1 +
 hw/i2c/trace-events |   4 +
 4 files changed, 305 insertions(+)
 create mode 100644 hw/i2c/imx_lpi2c.c

diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
index 0766130b596..ee31513529f 100644
--- a/hw/i2c/Kconfig
+++ b/hw/i2c/Kconfig
@@ -54,3 +54,7 @@ config PMBUS
 config BCM2835_I2C
     bool
     select I2C
+
+config IMX_LPI2C
+    bool
+    select I2C
diff --git a/hw/i2c/imx_lpi2c.c b/hw/i2c/imx_lpi2c.c
new file mode 100644
index 00000000000..96180c02c8f
--- /dev/null
+++ b/hw/i2c/imx_lpi2c.c
@@ -0,0 +1,296 @@
+/*
+ * NXP i.MX LPI2C master controller
+ *
+ * Copyright (c) 2026, Kyle Fox
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Models the master side of the i.MX LPI2C used by the System Manager
+ * firmware to talk to the board PMIC / IO-expander. Only the polled
+ * blocking-transfer path the SM driver uses is modelled:
+ *
+ *   - MTDR is a command FIFO: the agent writes START (with address),
+ *     TX-data, RX-data (count) and STOP commands.
+ *   - Each command is executed synchronously against the QEMU I2C bus
+ *     (i2c_start_transfer / i2c_send / i2c_recv / i2c_end_transfer).
+ *   - MSR exposes the status flags the driver polls: TDF (tx ready,
+ *     always set since we drain the FIFO immediately), RDF (rx data
+ *     available), SDF (stop detected), NDF (NACK), MBF/BBF (busy).
+ *   - MRDR pops received bytes (RXEMPTY when the rx FIFO is empty).
+ *
+ * No interrupts, DMA, slave mode, clocking or timing are modelled.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/core/sysbus.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define TYPE_IMX_LPI2C "imx.lpi2c"
+OBJECT_DECLARE_SIMPLE_TYPE(IMXLPI2CState, IMX_LPI2C)
+
+#define IMX_LPI2C_REG_SIZE      0x10000
+
+/* Register offsets. */
+#define LPI2C_VERID             0x00
+#define LPI2C_PARAM             0x04
+#define LPI2C_MCR               0x10
+#define LPI2C_MSR               0x14
+#define LPI2C_MIER              0x18
+#define LPI2C_MCFGR1            0x24
+#define LPI2C_MFSR              0x5C
+#define LPI2C_MTDR              0x60
+#define LPI2C_MRDR              0x70
+
+/* MCR bits. */
+#define MCR_MEN                 (1u << 0)
+#define MCR_RST                 (1u << 1)
+#define MCR_RTF                 (1u << 8)   /* reset tx FIFO (self-clearing) */
+#define MCR_RRF                 (1u << 9)   /* reset rx FIFO (self-clearing) */
+
+/* MSR bits. */
+#define MSR_TDF                 (1u << 0)
+#define MSR_RDF                 (1u << 1)
+#define MSR_EPF                 (1u << 8)
+#define MSR_SDF                 (1u << 9)
+#define MSR_NDF                 (1u << 10)
+#define MSR_MBF                 (1u << 24)
+#define MSR_BBF                 (1u << 25)
+/* Write-1-to-clear status bits. */
+#define MSR_W1C                 (MSR_EPF | MSR_SDF | MSR_NDF | (0xfu << 11))
+
+/* MTDR command field [10:8]. */
+#define MTDR_CMD_TXDATA         0x0
+#define MTDR_CMD_RXDATA         0x1
+#define MTDR_CMD_STOP           0x2
+#define MTDR_CMD_START          0x4
+
+/* MRDR bits. */
+#define MRDR_RXEMPTY            (1u << 14)
+
+#define RXFIFO_LEN              256
+
+struct IMXLPI2CState {
+    SysBusDevice    parent_obj;
+
+    MemoryRegion    iomem;
+    I2CBus         *bus;
+
+    uint32_t        mcr;
+    uint32_t        msr;       /* sticky status bits (SDF/NDF/MBF/BBF/...) */
+    uint32_t        mcfgr1;
+
+    uint8_t         rxfifo[RXFIFO_LEN];
+    uint32_t        rx_head;
+    uint32_t        rx_count;
+};
+
+static void imx_lpi2c_rx_push(IMXLPI2CState *s, uint8_t b)
+{
+    if (s->rx_count < RXFIFO_LEN) {
+        unsigned tail = (s->rx_head + s->rx_count) % RXFIFO_LEN;
+        s->rxfifo[tail] = b;
+        s->rx_count++;
+    }
+}
+
+static void imx_lpi2c_do_command(IMXLPI2CState *s, uint32_t val)
+{
+    uint32_t cmd = (val >> 8) & 0x7;
+    uint8_t data = val & 0xff;
+
+    switch (cmd) {
+    case MTDR_CMD_START: {
+        /* DATA = (addr << 1) | rw. */
+        uint8_t addr = data >> 1;
+        bool recv = data & 1;
+        trace_imx_lpi2c_start(addr, recv);
+        if (i2c_start_transfer(s->bus, addr, recv) != 0) {
+            s->msr |= MSR_NDF;     /* no slave acked the address */
+        } else {
+            s->msr |= MSR_MBF | MSR_BBF;
+        }
+        break;
+    }
+    case MTDR_CMD_TXDATA:
+        trace_imx_lpi2c_send(data);
+        if (i2c_send(s->bus, data) != 0) {
+            s->msr |= MSR_NDF;
+        }
+        break;
+    case MTDR_CMD_RXDATA: {
+        /* Receive (DATA + 1) bytes. */
+        unsigned n = (unsigned)data + 1;
+        for (unsigned i = 0; i < n; i++) {
+            uint8_t b = i2c_recv(s->bus);
+            trace_imx_lpi2c_recv(b);
+            imx_lpi2c_rx_push(s, b);
+        }
+        break;
+    }
+    case MTDR_CMD_STOP:
+        trace_imx_lpi2c_stop();
+        i2c_end_transfer(s->bus);
+        s->msr |= MSR_SDF | MSR_EPF;
+        s->msr &= ~(MSR_MBF | MSR_BBF);
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP, "%s: unhandled MTDR cmd %u\n", __func__, cmd);
+        break;
+    }
+}
+
+static uint64_t imx_lpi2c_read(void *opaque, hwaddr offset, unsigned size)
+{
+    IMXLPI2CState *s = opaque;
+
+    switch (offset) {
+    case LPI2C_VERID:
+        return 0x01000003;     /* plausible LPI2C version */
+    case LPI2C_PARAM:
+        return 0x00000303;     /* 8-deep tx/rx FIFOs (not load-bearing) */
+    case LPI2C_MCR:
+        return s->mcr;
+    case LPI2C_MSR: {
+        /*
+         * TDF is always ready (we drain the tx command immediately);
+         * RDF reflects the rx FIFO.
+         */
+        uint32_t v = s->msr | MSR_TDF;
+        if (s->rx_count) {
+            v |= MSR_RDF;
+        }
+        return v;
+    }
+    case LPI2C_MIER:
+        return 0;
+    case LPI2C_MCFGR1:
+        return s->mcfgr1;
+    case LPI2C_MFSR:
+        /* rx FIFO count in [22:16]; tx always empty. */
+        return (s->rx_count & 0x7f) << 16;
+    case LPI2C_MRDR:
+        if (s->rx_count == 0) {
+            return MRDR_RXEMPTY;
+        } else {
+            uint8_t b = s->rxfifo[s->rx_head];
+            s->rx_head = (s->rx_head + 1) % RXFIFO_LEN;
+            s->rx_count--;
+            return b;
+        }
+    default:
+        return 0;
+    }
+}
+
+static void imx_lpi2c_write(void *opaque, hwaddr offset,
+                            uint64_t value, unsigned size)
+{
+    IMXLPI2CState *s = opaque;
+
+    switch (offset) {
+    case LPI2C_MCR:
+        if (value & MCR_RRF) {
+            s->rx_head = s->rx_count = 0;
+        }
+        /* RST / RTF / RRF are momentary; don't persist them. */
+        s->mcr = value & ~(MCR_RST | MCR_RTF | MCR_RRF);
+        break;
+    case LPI2C_MSR:
+        /* Write-1-to-clear the status flags. */
+        s->msr &= ~((uint32_t)value & MSR_W1C);
+        break;
+    case LPI2C_MIER:
+    case LPI2C_MCFGR1:
+        if (offset == LPI2C_MCFGR1) {
+            s->mcfgr1 = value;
+        }
+        break;
+    case LPI2C_MTDR:
+        if (s->mcr & MCR_MEN) {
+            imx_lpi2c_do_command(s, value);
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps imx_lpi2c_ops = {
+    .read = imx_lpi2c_read,
+    .write = imx_lpi2c_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void imx_lpi2c_reset_hold(Object *obj, ResetType type)
+{
+    IMXLPI2CState *s = IMX_LPI2C(obj);
+
+    s->mcr = 0;
+    s->msr = 0;
+    s->mcfgr1 = 0;
+    s->rx_head = s->rx_count = 0;
+}
+
+static void imx_lpi2c_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    IMXLPI2CState *s = IMX_LPI2C(obj);
+
+    memory_region_init_io(&s->iomem, obj, &imx_lpi2c_ops, s,
+                          TYPE_IMX_LPI2C, IMX_LPI2C_REG_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+    s->bus = i2c_init_bus(DEVICE(obj), "i2c");
+}
+
+static const VMStateDescription vmstate_imx_lpi2c = {
+    .name = TYPE_IMX_LPI2C,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(mcr, IMXLPI2CState),
+        VMSTATE_UINT32(msr, IMXLPI2CState),
+        VMSTATE_UINT32(mcfgr1, IMXLPI2CState),
+        VMSTATE_UINT8_ARRAY(rxfifo, IMXLPI2CState, RXFIFO_LEN),
+        VMSTATE_UINT32(rx_head, IMXLPI2CState),
+        VMSTATE_UINT32(rx_count, IMXLPI2CState),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void imx_lpi2c_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &vmstate_imx_lpi2c;
+    rc->phases.hold = imx_lpi2c_reset_hold;
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    dc->desc = "NXP i.MX LPI2C master";
+}
+
+static const TypeInfo imx_lpi2c_info = {
+    .name           = TYPE_IMX_LPI2C,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(IMXLPI2CState),
+    .instance_init  = imx_lpi2c_init,
+    .class_init     = imx_lpi2c_class_init,
+};
+
+static void imx_lpi2c_register_types(void)
+{
+    type_register_static(&imx_lpi2c_info);
+}
+
+type_init(imx_lpi2c_register_types)
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index 88aea35662d..ad6738f820e 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_IMX_LPI2C', if_true: files('imx_lpi2c.c'))
 system_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events
index 1ad0e95c0e6..88078e1c590 100644
--- a/hw/i2c/trace-events
+++ b/hw/i2c/trace-events
@@ -61,3 +61,7 @@ pca954x_read_data(uint8_t value) "PCA954X read data: 0x%02x"
 
 imx_i2c_read(const char *id, const char *reg, uint64_t ofs, uint64_t value) "%s:[%s (0x%" PRIx64 ")] -> 0x%02" PRIx64
 imx_i2c_write(const char *id, const char *reg, uint64_t ofs, uint64_t value) "%s:[%s (0x%" PRIx64 ")] <- 0x%02" PRIx64
+imx_lpi2c_start(uint8_t addr, int recv) "start addr 0x%02x recv %d"
+imx_lpi2c_send(uint8_t data) "tx 0x%02x"
+imx_lpi2c_recv(uint8_t data) "rx 0x%02x"
+imx_lpi2c_stop(void) "stop"
-- 
2.34.1



  parent reply	other threads:[~2026-08-20  2:51 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-20  2:48 [PATCH 00/16] hw/arm: add the NXP i.MX 95 EVK machine Kyle Fox
2026-08-20  2:48 ` [PATCH 01/16] hw/sd/sdhci: add i.MX uSDHC SDCLK_AUTO_GATE and NO_SDMA_BOUNDARY quirks Kyle Fox
2026-08-20  2:48 ` [PATCH 02/16] hw/arm/boot: let a board preset initrd_start Kyle Fox
2026-08-20  2:48 ` [PATCH 03/16] target/arm: opt-in align-down for a misaligned PMSAv7 MPU RBAR Kyle Fox
2026-08-20  2:48 ` [PATCH 04/16] hw/arm/armv7m: forward pmsav7-rbar-align-down to the CPU Kyle Fox
2026-08-20  2:48 ` [PATCH 05/16] hw/char: add i.MX LPUART Kyle Fox
2026-08-20  2:48 ` Kyle Fox [this message]
2026-08-20  2:48 ` [PATCH 07/16] hw/misc: add i.MX Messaging Unit (MU v2) Kyle Fox
2026-08-20  2:48 ` [PATCH 08/16] hw/misc: add NXP EdgeLock Enclave (ELE) responder Kyle Fox
2026-08-20  2:48 ` [PATCH 09/16] hw/timer: add i.MX 95 system counter Kyle Fox
2026-08-20  2:48 ` [PATCH 10/16] hw/misc: add i.MX 95 watchdog Kyle Fox
2026-08-20  2:48 ` [PATCH 11/16] hw/misc: add i.MX 95 ANATOP/AONMIX/GPC/SRC power and clock blocks Kyle Fox
2026-08-20  2:48 ` [PATCH 12/16] hw/misc: add i.MX 95 PMIC (PF09/PF53/PCAL6408A) and xcache controllers Kyle Fox
2026-08-20  2:48 ` [PATCH 13/16] hw/misc: add i.MX 95 DPU command-sequencer stub (headless) Kyle Fox
2026-08-20  2:48 ` [PATCH 14/16] hw/arm: add i.MX 95 SoC container (fsl-imx95) Kyle Fox
2026-08-20  2:48 ` [PATCH 15/16] hw/arm: add i.MX 95 19x19 EVK board Kyle Fox
2026-08-20  2:48 ` [PATCH 16/16] docs, MAINTAINERS, tests/functional: add i.MX 95 EVK Kyle Fox

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260820024834.3286721-7-kylefoxaustin.github@gmail.com \
    --to=kylefoxaustin.github@gmail.com \
    --cc=pbonzini@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.