From: Wadim Mueller <wafgo01@gmail.com>
To: qemu-devel@nongnu.org
Cc: qemu-arm@nongnu.org, "Peter Maydell" <peter.maydell@linaro.org>,
"Philippe Mathieu-Daudé" <philmd@mailo.com>,
"Bin Meng" <bmeng.cn@gmail.com>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Fabiano Rosas" <farosas@suse.de>,
"Wadim Mueller" <wafgo01@gmail.com>
Subject: [RFC PATCH v2 02/14] hw/i2c/omap_i2c: implement soft reset and NACK reporting
Date: Thu, 20 Aug 2026 14:48:02 +0200 [thread overview]
Message-ID: <20260820124824.618671-3-wafgo01@gmail.com> (raw)
In-Reply-To: <20260820124824.618671-1-wafgo01@gmail.com>
Add the pieces of the controller that the TI K3 ROM/SPL and the Linux
omap-i2c driver actually exercise:
- OMAP_I2C_SYSC.SRST triggers a soft reset and SYSS.RDONE reports its
completion, instead of the register being a plain scratch value.
- A transfer to an address that nobody acknowledges raises STAT.NACK and
ends the transfer, rather than being silently completed.
This allows to let a guest probe an I2C bus and correctly conclude that a
device is absent.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/i2c/omap_i2c.c | 234 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 232 insertions(+), 2 deletions(-)
diff --git a/hw/i2c/omap_i2c.c b/hw/i2c/omap_i2c.c
index d6e28a1a89..e56f75d6f5 100644
--- a/hw/i2c/omap_i2c.c
+++ b/hw/i2c/omap_i2c.c
@@ -37,6 +37,7 @@ struct OMAPI2CState {
I2CBus *bus;
uint8_t revision;
+ uint8_t mmio_version;
void *iclk;
void *fclk;
@@ -58,6 +59,61 @@ struct OMAPI2CState {
#define OMAP2_INTR_REV 0x34
#define OMAP2_GC_REV 0x34
+/*
+ * MMIO register layout selector. The classic OMAP1/OMAP2 "IP V1" map is the
+ * default and is what every existing OMAP board relies on. "IP V2" is the
+ * OMAP4-and-later layout ("ti,omap4-i2c" / "ti,am64-i2c"), which relocates the
+ * registers and adds the IRQSTATUS_RAW / IRQENABLE_SET / IRQENABLE_CLR set.
+ * The transfer/reset/NACK engine is shared; only the address decode differs.
+ */
+#define OMAP_I2C_MMIO_V1 0
+#define OMAP_I2C_MMIO_V2 2
+
+/* IP V2 register offsets, as used from OMAP4 onwards. */
+#define OMAP_I2C_V2_REVNB_LO 0x00
+#define OMAP_I2C_V2_REVNB_HI 0x04
+#define OMAP_I2C_V2_SYSC 0x10
+#define OMAP_I2C_V2_IRQSTATUS_RAW 0x24
+#define OMAP_I2C_V2_IRQSTATUS 0x28
+#define OMAP_I2C_V2_IRQENABLE_SET 0x2c
+#define OMAP_I2C_V2_IRQENABLE_CLR 0x30
+#define OMAP_I2C_V2_WE 0x34
+#define OMAP_I2C_V2_SYSS 0x90
+#define OMAP_I2C_V2_BUF 0x94
+#define OMAP_I2C_V2_CNT 0x98
+#define OMAP_I2C_V2_DATA 0x9c
+#define OMAP_I2C_V2_CON 0xa4
+#define OMAP_I2C_V2_OA 0xa8
+#define OMAP_I2C_V2_SA 0xac
+#define OMAP_I2C_V2_PSC 0xb0
+#define OMAP_I2C_V2_SCLL 0xb4
+#define OMAP_I2C_V2_SCLH 0xb8
+#define OMAP_I2C_V2_SYSTEST 0xbc
+#define OMAP_I2C_V2_BUFSTAT 0xc0
+
+/*
+ * Translate an IP-V2 offset for a register whose semantics are identical to
+ * the V1 model into the V1 offset the shared read/write switch decodes.
+ * Returns -1 for offsets that have no direct V1 equivalent (those are handled
+ * inline by the V2 front-end).
+ */
+static int omap_i2c_v2_to_v1(int offset)
+{
+ switch (offset) {
+ case OMAP_I2C_V2_BUF: return 0x14;
+ case OMAP_I2C_V2_CNT: return 0x18;
+ /* DATA (0x9c) is handled inline byte-wise by the V2 front-end. */
+ case OMAP_I2C_V2_CON: return 0x24;
+ case OMAP_I2C_V2_OA: return 0x28;
+ case OMAP_I2C_V2_SA: return 0x2c;
+ case OMAP_I2C_V2_PSC: return 0x30;
+ case OMAP_I2C_V2_SCLL: return 0x34;
+ case OMAP_I2C_V2_SCLH: return 0x38;
+ case OMAP_I2C_V2_SYSTEST: return 0x3c;
+ default: return -1;
+ }
+}
+
static void omap_i2c_interrupts_update(OMAPI2CState *s)
{
qemu_set_irq(s->irq, s->stat & s->mask);
@@ -162,6 +218,83 @@ static uint32_t omap_i2c_read(void *opaque, hwaddr addr)
int offset = addr & OMAP_MPUI_REG_MASK;
uint16_t ret;
+ if (s->mmio_version == OMAP_I2C_MMIO_V2) {
+ switch (offset) {
+ case OMAP_I2C_V2_REVNB_LO:
+ return s->revision;
+ case OMAP_I2C_V2_REVNB_HI:
+ return 0;
+ case OMAP_I2C_V2_SYSC:
+ return 0;
+ case OMAP_I2C_V2_IRQSTATUS_RAW:
+ case OMAP_I2C_V2_IRQSTATUS: /* STAT mirrors IRQSTATUS_RAW */
+ return s->stat | (i2c_bus_busy(s->bus) << 12);
+ case OMAP_I2C_V2_IRQENABLE_SET:
+ case OMAP_I2C_V2_IRQENABLE_CLR:
+ return s->mask;
+ case OMAP_I2C_V2_WE:
+ return 0;
+ case OMAP_I2C_V2_SYSS:
+ /* reset is instantaneous in the model: RDONE always reads set */
+ return 1;
+ case OMAP_I2C_V2_BUFSTAT:
+ return 0;
+ case OMAP_I2C_V2_DATA: {
+ /*
+ * The OMAP4/AM64x driver accesses the DATA register one byte per
+ * MMIO access (readw of a single byte), unlike the classic V1
+ * 16-bit FIFO convention. Pop exactly one byte (oldest first,
+ * FIFO is filled LSB-first by omap_i2c_fifo_run()).
+ */
+ uint8_t b = s->fifo & 0xff;
+ if (s->rxlen > 0) {
+ s->fifo >>= 8;
+ s->rxlen--;
+ }
+ /*
+ * Refill from the slave while the transfer is still live (this
+ * may complete count_cur and issue the STOP, leaving the last
+ * few prefetched bytes buffered in the FIFO with the bus idle).
+ */
+ omap_i2c_fifo_run(s);
+ /*
+ * Drive the RRDY/ARDY handshake directly off the FIFO drain
+ * state so it keeps working after the bus has gone idle: RRDY
+ * stays asserted while buffered bytes remain, and ARDY is raised
+ * once the last byte has been consumed (master-receive).
+ */
+ if (s->rxlen > 0) {
+ s->stat |= 1 << 3; /* RRDY */
+ } else {
+ s->stat &= ~(1 << 3); /* RRDY */
+ if (((s->control >> 10) & 1) && /* MST */
+ ((~s->control >> 9) & 1)) { /* TRX (receive) */
+ s->stat |= 1 << 2; /* ARDY */
+ s->control &= ~(1 << 10); /* MST */
+ /*
+ * DCOUNT decrements to 0 on real hardware and stays
+ * there; leave it at 0 so a following address-only probe
+ * (which programs no CNT) starts from 0 and completes
+ * with ARDY instead of waiting for phantom TX bytes.
+ */
+ s->count = 0;
+ s->count_cur = 0;
+ }
+ }
+ s->stat &= ~(1 << 11); /* ROVR */
+ omap_i2c_interrupts_update(s);
+ return b;
+ }
+ default:
+ offset = omap_i2c_v2_to_v1(offset);
+ if (offset < 0) {
+ OMAP_BAD_REG(addr);
+ return 0;
+ }
+ break;
+ }
+ }
+
switch (offset) {
case 0x00: /* I2C_REV */
return s->revision; /* REV */
@@ -266,6 +399,83 @@ static void omap_i2c_write(void *opaque, hwaddr addr,
int offset = addr & OMAP_MPUI_REG_MASK;
int nack;
+ if (s->mmio_version == OMAP_I2C_MMIO_V2) {
+ switch (offset) {
+ case OMAP_I2C_V2_SYSC:
+ if (value & 2) { /* SRST */
+ omap_i2c_reset(DEVICE(s));
+ }
+ return;
+ case OMAP_I2C_V2_DATA:
+ /*
+ * The OMAP4/AM64x driver writes the DATA register one byte per
+ * MMIO access (writew of a single byte). Push exactly one byte,
+ * mirroring the classic 8-bit FIFO path (omap_i2c_writeb()).
+ */
+ if (s->txlen <= 2) {
+ s->fifo <<= 8;
+ s->txlen += 1;
+ s->fifo |= value & 0xff;
+ s->stat &= ~(1 << 10); /* XUDF */
+ if (s->txlen > 2) {
+ s->stat &= ~(1 << 4); /* XRDY */
+ }
+ omap_i2c_fifo_run(s);
+ /*
+ * If the transmit finished and issued its STOP, leave DCOUNT
+ * at 0 (see the DATA read path) so the next probe/transfer
+ * that does not reprogram CNT is not tricked into expecting
+ * stale phantom bytes.
+ */
+ if (!i2c_bus_busy(s->bus)) {
+ s->count = 0;
+ s->count_cur = 0;
+ }
+ omap_i2c_interrupts_update(s);
+ }
+ return;
+ case OMAP_I2C_V2_IRQSTATUS_RAW:
+ case OMAP_I2C_V2_IRQSTATUS: /* write-1-to-clear */
+ s->stat &= ~(value & 0x7fff);
+ /*
+ * XRDY/RRDY are level events: after the driver clears them it
+ * expects them to re-assert while the transfer still has room /
+ * data. Re-run the FIFO engine for a live transfer, then
+ * re-assert RRDY if bytes remain buffered even after the bus has
+ * gone idle (the tail of a receive drains from the FIFO).
+ */
+ omap_i2c_fifo_run(s);
+ if (s->rxlen > 0) {
+ s->stat |= 1 << 3; /* RRDY */
+ }
+ omap_i2c_interrupts_update(s);
+ return;
+ case OMAP_I2C_V2_IRQENABLE_SET:
+ s->mask |= value & 0xff;
+ omap_i2c_interrupts_update(s);
+ return;
+ case OMAP_I2C_V2_IRQENABLE_CLR:
+ s->mask &= ~(value & 0xff);
+ omap_i2c_interrupts_update(s);
+ return;
+ case OMAP_I2C_V2_WE:
+ return; /* wakeup enable: ignored */
+ case OMAP_I2C_V2_REVNB_LO:
+ case OMAP_I2C_V2_REVNB_HI:
+ case OMAP_I2C_V2_SYSS:
+ case OMAP_I2C_V2_BUFSTAT:
+ OMAP_RO_REG(addr);
+ return;
+ default:
+ offset = omap_i2c_v2_to_v1(offset);
+ if (offset < 0) {
+ OMAP_BAD_REG(addr);
+ return;
+ }
+ break;
+ }
+ }
+
switch (offset) {
case 0x00: /* I2C_REV */
case 0x0c: /* I2C_IV */
@@ -412,6 +622,10 @@ static void omap_i2c_writeb(void *opaque, hwaddr addr,
OMAPI2CState *s = opaque;
int offset = addr & OMAP_MPUI_REG_MASK;
+ if (s->mmio_version == OMAP_I2C_MMIO_V2 && offset == OMAP_I2C_V2_DATA) {
+ offset = 0x1c; /* I2C_DATA */
+ }
+
switch (offset) {
case 0x1c: /* I2C_DATA */
if (s->txlen > 2) {
@@ -489,10 +703,24 @@ static void omap_i2c_init(Object *obj)
static void omap_i2c_realize(DeviceState *dev, Error **errp)
{
OMAPI2CState *s = OMAP_I2C(dev);
+ uint64_t size;
+ if (s->mmio_version == OMAP_I2C_MMIO_V2) {
+ size = 0x100; /* AM64x main_i2c reg length */
+ } else {
+ size = (s->revision < OMAP2_INTR_REV) ? 0x800 : 0x1000;
+ }
memory_region_init_io(&s->iomem, OBJECT(dev), &omap_i2c_ops, s, "omap.i2c",
- (s->revision < OMAP2_INTR_REV) ? 0x800 : 0x1000);
-
+ size);
+
+ /*
+ * The IP-V2 wiring (e.g. TI AM64x) drives the module from the SoC clock
+ * tree rather than the legacy omap_clk pointer stubs, so the fclk/iclk
+ * requirement only applies to the classic OMAP boards.
+ */
+ if (s->mmio_version == OMAP_I2C_MMIO_V2) {
+ return;
+ }
if (!s->fclk) {
error_setg(errp, "omap_i2c: fclk not connected");
return;
@@ -516,6 +744,8 @@ void omap_i2c_set_fclk(OMAPI2CState *i2c, omap_clk clk)
static const Property omap_i2c_properties[] = {
DEFINE_PROP_UINT8("revision", OMAPI2CState, revision, 0),
+ DEFINE_PROP_UINT8("mmio-version", OMAPI2CState, mmio_version,
+ OMAP_I2C_MMIO_V1),
};
static void omap_i2c_class_init(ObjectClass *klass, const void *data)
--
2.43.0
next prev parent reply other threads:[~2026-08-20 12:48 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 01/14] hw/i2c/omap_i2c: add a dedicated CONFIG_OMAP_I2C symbol Wadim Mueller
2026-08-20 12:48 ` Wadim Mueller [this message]
2026-08-20 12:48 ` [RFC PATCH v2 03/14] hw/sd/sdhci: complete non-interrupt ADMA descriptor chains in one pass Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 04/14] hw/char: add TI AM64x UART model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 05/14] hw/timer: add TI K3 DMTimer model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 06/14] hw/misc: add TI K3 CTRL_MMR, GTC, DDRSS, SDHCI PHY and TRNG models Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 07/14] hw/misc: add TI RAT (region address translation) model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 08/14] hw/misc: add TI mailbox (IPC) model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 09/14] hw/misc: add TI K3 secure proxy model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 10/14] hw/misc: add TI DMSC (TI-SCI system controller) model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 11/14] hw/arm: add TI K3 combined boot image parser Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 12/14] hw/arm: add TI AM64x SoC model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 13/14] hw/arm: add the am64-virt machine Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 14/14] tests: add AM64x unit, qtest and functional tests Wadim Mueller
2026-08-20 16:25 ` Alex Bennée
2026-08-22 21:04 ` Wadim Mueller
2026-08-21 10:26 ` [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Alex Bennée
2026-08-22 21:06 ` Wadim Mueller
2026-08-21 16:35 ` Nick Huang
2026-08-22 21:07 ` Wadim Mueller
2026-08-22 5:22 ` Bin Meng
2026-08-22 21:13 ` Wadim Mueller
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=20260820124824.618671-3-wafgo01@gmail.com \
--to=wafgo01@gmail.com \
--cc=bmeng.cn@gmail.com \
--cc=farosas@suse.de \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@mailo.com \
--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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox