hw/gpio: pca9554: reflect push-pull outputs in the input register
pca9554_update_pin_input() derived the pin level from CONFIG | OUTPUT, which treated an output driven high as Hi-Z and let ext_state pull it low. The PCA9554/PCA9536 output stage is push-pull, so a pin configured as an output drives the OUTPUT register level regardless of any external agent. Reflect the output value directly for output pins and keep the pull-up/ext_state behaviour for input pins, matching the PCA9555 GPIO variant. 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-17-82a63fead90c@free.fr Signed-off-by: Cédric Le Goater <clg@redhat.com>
Emmanuel Blot committed
Jul 9, 2026 at 17:23 UTC
ed8e011b922c5ae23804d6dccbde1152af3a5381
1 file changed
+9
-20
hw/gpio/pca9554.c
+9
-20
@@ -43,43 +43,32 @@ static void pca9554_update_pin_input(PCA9554State *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;
46
47
for (i = 0; i < pc->pin_count; i++) {
48
uint8_t bit_mask = 1 << i;
50
- uint8_t internal_pin_state = (internal_state >> i) & 0x1;
49
uint8_t old_value = s->regs[PCA9554_INPUT] & bit_mask;
50
uint8_t new_value;
51
54
- switch (internal_pin_state) {
55
- case PCA9554_PIN_LOW:
56
- s->regs[PCA9554_INPUT] &= ~bit_mask;
57
- break;
58
- case PCA9554_PIN_HIZ:
52
+ if (config & bit_mask) {
53
/*
60
- * pullup sets it to a logical 1 unless
61
- * external device drives it low.
54
+ * Input: the pin is Hi-Z with a pull-up, so it reads high
55
+ * unless an external device drives it low.
56
*/
57
if (s->ext_state[i] == PCA9554_PIN_LOW) {
58
s->regs[PCA9554_INPUT] &= ~bit_mask;
59
} else {
66
- s->regs[PCA9554_INPUT] |= bit_mask;
60
+ s->regs[PCA9554_INPUT] |= bit_mask;
61
}
68
- break;
69
- default:
70
- break;
62
+ } else {
63
+ /* Output: the push-pull stage drives the output register level. */
64
+ s->regs[PCA9554_INPUT] = (s->regs[PCA9554_INPUT] & ~bit_mask) |
65
+ (output & bit_mask);
66
}
67
68
/* drive the per-pin GPIO output only if the pin level changed */
69
new_value = s->regs[PCA9554_INPUT] & bit_mask;
70
if (new_value != old_value) {
76
- if (new_value) {
77
- /* changed from 0 to 1 */
78
- qemu_set_irq(s->gpio_out[i], 1);
79
- } else {
80
- /* changed from 1 to 0 */
81
- qemu_set_irq(s->gpio_out[i], 0);
82
- }
71
+ qemu_set_irq(s->gpio_out[i], !!new_value);
72
}
73
}
74
}