On 10/12/26 16:31, Emmanuel Blot wrote:
The PCA9545 is a 4-channel I2C mux/switch. What sets it apart from the
plain PCA9546 is its interrupt logic: one INTn input per channel and a
shared INT output, both active low, asserted whenever any channel
interrupt is active.

Model this behind a new has_irq class flag. The PCA9545 gains four named
"interrupt" GPIO inputs and an "interrupt-out" output, and its control
read-back merges the latched status nibble; the plain PCA9546/PCA9548
switches keep their previous behaviour.

Signed-off-by: Emmanuel Blot <[email protected]>
---
  hw/i2c/i2c_mux_pca954x.c         | 71 ++++++++++++++++++++++++++++++++++++++--
  include/hw/i2c/i2c_mux_pca954x.h |  1 +
  2 files changed, 70 insertions(+), 2 deletions(-)

diff --git a/hw/i2c/i2c_mux_pca954x.c b/hw/i2c/i2c_mux_pca954x.c
index c8e75da8b2..4bb078a03c 100644
--- a/hw/i2c/i2c_mux_pca954x.c
+++ b/hw/i2c/i2c_mux_pca954x.c
@@ -12,12 +12,20 @@
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
   * for more details.
+ *
+ * Limitations:
+ * - A control write longer than one byte is rejected rather than retaining the
+ *   last received byte as the datasheet specifies.
+ * - On reset the cached per-channel interrupt state is cleared; a downstream
+ *   INT input that stays asserted across the reset is not re-sampled until it
+ *   next toggles.
   */
#include "qemu/osdep.h"
  #include "hw/i2c/i2c.h"
  #include "hw/i2c/i2c_mux_pca954x.h"
  #include "hw/i2c/smbus_slave.h"
+#include "hw/core/irq.h"
  #include "hw/core/qdev.h"
  #include "hw/core/qdev-properties.h"
  #include "qemu/log.h"
@@ -26,11 +34,15 @@
  #include "trace.h"
#define PCA9548_CHANNEL_COUNT 8
+#define PCA9545_CHANNEL_COUNT 4
  #define PCA9546_CHANNEL_COUNT 4
/*
   * struct Pca954xState - The pca954x state object.
   * @control: The value written to the mux control.
+ * @int_status: Per-channel interrupt status, for parts with interrupt logic.
+ * @int_out: The mux INT output, driven low while any channel interrupt is
+ * active.
   * @channel: The set of i2c channel buses that act as channels which own the
   * i2c children.
   */
@@ -38,6 +50,8 @@ typedef struct Pca954xState {
      SMBusDevice parent;
uint8_t control;
+    uint8_t int_status;
+    qemu_irq int_out;
bool enabled[PCA9548_CHANNEL_COUNT];
      I2CBus *bus[PCA9548_CHANNEL_COUNT];
@@ -48,11 +62,13 @@ typedef struct Pca954xState {
  /*
   * struct Pca954xClass - The pca954x class object.
   * @nchans: The number of i2c channels this device has.
+ * @has_irq: Whether the part implements interrupt logic.
   */
  typedef struct Pca954xClass {
      SMBusDeviceClass parent;
uint8_t nchans;
+    bool has_irq;
  } Pca954xClass;
#define TYPE_PCA954X "pca954x"
@@ -116,8 +132,10 @@ static void pca954x_enable_channel(Pca954xState *s, 
uint8_t enable_mask)
static void pca954x_write(Pca954xState *s, uint8_t data)
  {
-    s->control = data;
-    pca954x_enable_channel(s, data);
+    Pca954xClass *c = PCA954X_GET_CLASS(s);
+
+    s->control = c->has_irq ? data & ((1 << c->nchans) - 1) : data;
+    pca954x_enable_channel(s, s->control);
trace_pca954x_write_bytes(data);
  }
@@ -148,16 +166,45 @@ static int pca954x_write_data(SMBusDevice *d, uint8_t 
*buf, uint8_t len)
  static uint8_t pca954x_read_byte(SMBusDevice *d)
  {
      Pca954xState *s = PCA954X(d);
+    Pca954xClass *c = PCA954X_GET_CLASS(s);
      uint8_t data = s->control;
+
+    /*
+     * On parts with interrupt logic the read-back byte carries the per-channel
+     * interrupt status in the upper nibble.
+     */
+    if (c->has_irq) {
+        data = (s->int_status << 4) | (s->control & 0x0f);
+    }
+
      trace_pca954x_read_data(data);
      return data;
  }
+/*
+ * A downstream device drives one of the INTn inputs. INTn and INT are active
+ * low, and both carry the physical level: 0 is asserted, 1 is idle.
+ */
+static void pca954x_irq_handler(void *opaque, int n, int level)
+{
+    Pca954xState *s = PCA954X(opaque);
+
+    if (level) {
+        s->int_status &= ~(1 << n);
+    } else {
+        s->int_status |= (1 << n);
+    }
+
+    qemu_set_irq(s->int_out, s->int_status == 0);
+}
+
  static void pca954x_enter_reset(Object *obj, ResetType type)
  {
      Pca954xState *s = PCA954X(obj);
      /* Reset will disable all channels. */
      pca954x_write(s, 0);
+    s->int_status = 0;
+    qemu_set_irq(s->int_out, 1); /* INT released */
  }
I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
@@ -169,6 +216,13 @@ I2CBus *pca954x_i2c_get_bus(I2CSlave *mux, uint8_t channel)
      return pca954x->bus[channel];
  }
+static void pca9545_class_init(ObjectClass *klass, const void *data)
+{
+    Pca954xClass *s = PCA954X_CLASS(klass);
+    s->nchans = PCA9545_CHANNEL_COUNT;
+    s->has_irq = true;
+}
+
  static void pca9546_class_init(ObjectClass *klass, const void *data)
  {
      Pca954xClass *s = PCA954X_CLASS(klass);
@@ -184,12 +238,20 @@ static void pca9548_class_init(ObjectClass *klass, const 
void *data)
  static void pca954x_realize(DeviceState *dev, Error **errp)
  {
      Pca954xState *s = PCA954X(dev);
+    Pca954xClass *c = PCA954X_GET_CLASS(dev);
      DeviceState *d = DEVICE(s);
      if (s->name) {
          d->id = g_strdup(s->name);
      } else {
          d->id = g_strdup_printf("pca954x[%x]", s->parent.i2c.address);
      }
+
+    if (c->has_irq) {
+        /* One INTn input per channel, plus the shared INT output. */
+        qdev_init_gpio_in_named(dev, pca954x_irq_handler, "interrupt",
+                                c->nchans);
+        qdev_init_gpio_out_named(dev, &s->int_out, "interrupt-out", 1);
+    }
  }
static void pca954x_init(Object *obj)
@@ -242,6 +304,11 @@ static const TypeInfo pca954x_info[] = {
          .class_init    = pca954x_class_init,
          .abstract      = true,
      },
+    {
+        .name          = TYPE_PCA9545,
+        .parent        = TYPE_PCA954X,
+        .class_init    = pca9545_class_init,
+    },
      {
          .name          = TYPE_PCA9546,
          .parent        = TYPE_PCA954X,
diff --git a/include/hw/i2c/i2c_mux_pca954x.h b/include/hw/i2c/i2c_mux_pca954x.h
index 3dd25ec983..19a1050505 100644
--- a/include/hw/i2c/i2c_mux_pca954x.h
+++ b/include/hw/i2c/i2c_mux_pca954x.h
@@ -3,6 +3,7 @@
#include "hw/i2c/i2c.h" +#define TYPE_PCA9545 "pca9545"
  #define TYPE_PCA9546 "pca9546"
  #define TYPE_PCA9548 "pca9548"

Reviewed-by: Cédric Le Goater <[email protected]>

Thanks,

C.


Reply via email to