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 c8e75da8b2e..4bb078a03c4 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 3dd25ec9830..19a10505056 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" -- 2.50.1
