QEMU-Devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
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



  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