@samitouri / QOSamiQemu / commits / 5eceb48bbe

hw/gpio: pca9554: add PCA9536 support

The PCA9536 is a 4-bit I/O expander that's register-compatible with the PCA9554 but only has 4 pins. Rather than duplicating the whole driver, make the existing PCA9554 model parameterizable and register PCA9536 as a subtype. Introduce a PCA9554Class with a pin_count property, and replace every hard-coded PCA9554_PIN_COUNT reference in the driver with the class value. The reset function now computes the correct pin mask from pin_count instead of assuming 0xFF. Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr> Reviewed-by: Glenn Miles <milesg@linux.ibm.com> Link: https://lore.kernel.org/qemu-devel/20260709-catalina-upgrade-v1-15-82a63fead90c@free.fr Signed-off-by: Cédric Le Goater <clg@redhat.com>

Emmanuel Blot committed Jul 9, 2026 at 17:23 UTC 5eceb48bbe86f4ef5fa59aa9896d2b09d5aae11a
2 files changed +54 -27
hw/gpio/pca9554.c
+52 -27
@@ -24,6 +24,8 @@ struct PCA9554Class {
24 /*< private >*/
25 I2CSlaveClass parent_class;
26 /*< public >*/
27 +
28 + uint8_t pin_count;
29 };
30 typedef struct PCA9554Class PCA9554Class;
31
@@ -37,12 +39,13 @@ static const char *pin_state[] = {"low", "high"};
39
40 static void pca9554_update_pin_input(PCA9554State *s)
41 {
42 + PCA9554Class *pc = PCA9554_GET_CLASS(s);
43 int i;
44 uint8_t config = s->regs[PCA9554_CONFIG];
45 uint8_t output = s->regs[PCA9554_OUTPUT];
46 uint8_t internal_state = config | output;
47
45 - for (i = 0; i < PCA9554_PIN_COUNT; i++) {
48 + for (i = 0; i < pc->pin_count; i++) {
49 uint8_t bit_mask = 1 << i;
50 uint8_t internal_pin_state = (internal_state >> i) & 0x1;
51 uint8_t old_value = s->regs[PCA9554_INPUT] & bit_mask;
@@ -67,7 +70,7 @@ static void pca9554_update_pin_input(PCA9554State *s)
70 break;
71 }
72
70 - /* update irq state only if pin state changed */
73 + /* drive the per-pin GPIO output only if the pin level changed */
74 new_value = s->regs[PCA9554_INPUT] & bit_mask;
75 if (new_value != old_value) {
76 if (new_value) {
@@ -99,6 +102,12 @@ static uint8_t pca9554_read(PCA9554State *s, uint8_t reg)
102
103 static void pca9554_write(PCA9554State *s, uint8_t reg, uint8_t data)
104 {
105 + PCA9554Class *pc = PCA9554_GET_CLASS(s);
106 + uint8_t pin_mask = (1 << pc->pin_count) - 1;
107 +
108 + /* Variants narrower than 8 bits ignore the unimplemented upper pins. */
109 + data &= pin_mask;
110 +
111 switch (reg) {
112 case PCA9554_OUTPUT:
113 case PCA9554_CONFIG:
@@ -157,7 +166,7 @@ static void pca9554_get_pin(Object *obj, Visitor *v, const char *name,
166 error_setg(errp, "%s: error reading %s", __func__, name);
167 return;
168 }
160 - if (pin < 0 || pin >= PCA9554_PIN_COUNT) {
169 + if (pin < 0 || pin >= PCA9554_GET_CLASS(s)->pin_count) {
170 error_setg(errp, "%s invalid pin %s", __func__, name);
171 return;
172 }
@@ -184,7 +193,7 @@ static void pca9554_set_pin(Object *obj, Visitor *v, const char *name,
193 error_setg(errp, "%s: error reading %s", __func__, name);
194 return;
195 }
187 - if (pin < 0 || pin >= PCA9554_PIN_COUNT) {
196 + if (pin < 0 || pin >= PCA9554_GET_CLASS(s)->pin_count) {
197 error_setg(errp, "%s invalid pin %s", __func__, name);
198 return;
199 }
@@ -232,13 +241,15 @@ static const VMStateDescription pca9554_vmstate = {
241 static void pca9554_reset(DeviceState *dev)
242 {
243 PCA9554State *s = PCA9554(dev);
244 + PCA9554Class *pc = PCA9554_GET_CLASS(s);
245 + uint8_t pin_mask = (1 << pc->pin_count) - 1;
246
236 - s->regs[PCA9554_INPUT] = 0xFF;
237 - s->regs[PCA9554_OUTPUT] = 0xFF;
247 + s->regs[PCA9554_INPUT] = pin_mask;
248 + s->regs[PCA9554_OUTPUT] = pin_mask;
249 s->regs[PCA9554_POLARITY] = 0x0; /* No pins are inverted */
239 - s->regs[PCA9554_CONFIG] = 0xFF; /* All pins are inputs */
250 + s->regs[PCA9554_CONFIG] = pin_mask; /* All pins are inputs */
251
241 - memset(s->ext_state, PCA9554_PIN_HIZ, PCA9554_PIN_COUNT);
252 + memset(s->ext_state, PCA9554_PIN_HIZ, pc->pin_count);
253 pca9554_update_pin_input(s);
254
255 s->pointer = 0x0;
@@ -247,9 +258,10 @@ static void pca9554_reset(DeviceState *dev)
258
259 static void pca9554_initfn(Object *obj)
260 {
261 + PCA9554Class *pc = PCA9554_GET_CLASS(obj);
262 int pin;
263
252 - for (pin = 0; pin < PCA9554_PIN_COUNT; pin++) {
264 + for (pin = 0; pin < pc->pin_count; pin++) {
265 char *name;
266
267 name = g_strdup_printf("pin%d", pin);
@@ -269,23 +281,24 @@ static void pca9554_set_ext_state(PCA9554State *s, int pin, int level)
281
282 static void pca9554_gpio_in_handler(void *opaque, int pin, int level)
283 {
272 -
284 PCA9554State *s = PCA9554(opaque);
285 + PCA9554Class *pc = PCA9554_GET_CLASS(s);
286
275 - assert((pin >= 0) && (pin < PCA9554_PIN_COUNT));
287 + assert((pin >= 0) && (pin < pc->pin_count));
288 pca9554_set_ext_state(s, pin, level);
289 }
290
291 static void pca9554_realize(DeviceState *dev, Error **errp)
292 {
293 PCA9554State *s = PCA9554(dev);
294 + PCA9554Class *pc = PCA9554_GET_CLASS(s);
295
296 if (!s->description) {
284 - s->description = g_strdup("pca9554");
297 + s->description = g_strdup(object_get_typename(OBJECT(dev)));
298 }
299
287 - qdev_init_gpio_out(dev, s->gpio_out, PCA9554_PIN_COUNT);
288 - qdev_init_gpio_in(dev, pca9554_gpio_in_handler, PCA9554_PIN_COUNT);
300 + qdev_init_gpio_out(dev, s->gpio_out, pc->pin_count);
301 + qdev_init_gpio_in(dev, pca9554_gpio_in_handler, pc->pin_count);
302 }
303
304 static const Property pca9554_properties[] = {
@@ -296,6 +309,7 @@ static void pca9554_class_init(ObjectClass *klass, const void *data)
309 {
310 DeviceClass *dc = DEVICE_CLASS(klass);
311 I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
312 + PCA9554Class *pc = PCA9554_CLASS(klass);
313
314 k->event = pca9554_event;
315 k->recv = pca9554_recv;
@@ -304,21 +318,32 @@ static void pca9554_class_init(ObjectClass *klass, const void *data)
318 device_class_set_legacy_reset(dc, pca9554_reset);
319 dc->vmsd = &pca9554_vmstate;
320 device_class_set_props(dc, pca9554_properties);
307 -}
321
309 -static const TypeInfo pca9554_info = {
310 - .name = TYPE_PCA9554,
311 - .parent = TYPE_I2C_SLAVE,
312 - .instance_init = pca9554_initfn,
313 - .instance_size = sizeof(PCA9554State),
314 - .class_init = pca9554_class_init,
315 - .class_size = sizeof(PCA9554Class),
316 - .abstract = false,
317 -};
322 + pc->pin_count = PCA9554_PIN_COUNT;
323 +}
324
319 -static void pca9554_register_types(void)
325 +static void pca9536_class_init(ObjectClass *klass, const void *data)
326 {
321 - type_register_static(&pca9554_info);
327 + PCA9554Class *pc = PCA9554_CLASS(klass);
328 +
329 + pc->pin_count = PCA9536_PIN_COUNT;
330 }
331
324 -type_init(pca9554_register_types)
332 +static const TypeInfo pca9554_types[] = {
333 + {
334 + .name = TYPE_PCA9554,
335 + .parent = TYPE_I2C_SLAVE,
336 + .instance_init = pca9554_initfn,
337 + .instance_size = sizeof(PCA9554State),
338 + .class_init = pca9554_class_init,
339 + .class_size = sizeof(PCA9554Class),
340 + .abstract = false,
341 + },
342 + {
343 + .name = TYPE_PCA9536,
344 + .parent = TYPE_PCA9554,
345 + .class_init = pca9536_class_init,
346 + }
347 +};
348 +
349 +DEFINE_TYPES(pca9554_types);
include/hw/gpio/pca9554.h
+2
@@ -12,12 +12,14 @@
12 #include "qom/object.h"
13
14 #define TYPE_PCA9554 "pca9554"
15 +#define TYPE_PCA9536 "pca9536"
16 typedef struct PCA9554State PCA9554State;
17 DECLARE_INSTANCE_CHECKER(PCA9554State, PCA9554,
18 TYPE_PCA9554)
19
20 #define PCA9554_NR_REGS 4
21 #define PCA9554_PIN_COUNT 8
22 +#define PCA9536_PIN_COUNT 4
23
24 struct PCA9554State {
25 /*< private >*/