All of 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: 15+ 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

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 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.