[RFC PATCH v2 02/14] hw/i2c/omap_i2c: implement soft reset and NACK reporting

Wadim Mueller <[email protected]>
Newsgroups org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
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 <[email protected]>
---
 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
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.