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 >*/