@samitouri / QOSamiQemu / commits / ed8e011b92

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 }