xref: /qemu/hw/gpio/pca9554.c (revision e3d0814368d00e7985c31edf5d0cfce45972d4be)
1de0c7d54SGlenn Miles /*
2de0c7d54SGlenn Miles  * PCA9554 I/O port
3de0c7d54SGlenn Miles  *
4de0c7d54SGlenn Miles  * Copyright (c) 2023, IBM Corporation.
5de0c7d54SGlenn Miles  *
6de0c7d54SGlenn Miles  * SPDX-License-Identifier: GPL-2.0-or-later
7de0c7d54SGlenn Miles  */
8de0c7d54SGlenn Miles 
9de0c7d54SGlenn Miles #include "qemu/osdep.h"
10de0c7d54SGlenn Miles #include "qemu/log.h"
11de0c7d54SGlenn Miles #include "qemu/module.h"
12de0c7d54SGlenn Miles #include "qemu/bitops.h"
13de0c7d54SGlenn Miles #include "hw/qdev-properties.h"
146328d8ffSCédric Le Goater #include "hw/gpio/pca9554.h"
156328d8ffSCédric Le Goater #include "hw/gpio/pca9554_regs.h"
16de0c7d54SGlenn Miles #include "hw/irq.h"
17de0c7d54SGlenn Miles #include "migration/vmstate.h"
18de0c7d54SGlenn Miles #include "qapi/error.h"
19de0c7d54SGlenn Miles #include "qapi/visitor.h"
20de0c7d54SGlenn Miles #include "trace.h"
21de0c7d54SGlenn Miles #include "qom/object.h"
22de0c7d54SGlenn Miles 
23de0c7d54SGlenn Miles struct PCA9554Class {
24de0c7d54SGlenn Miles     /*< private >*/
25de0c7d54SGlenn Miles     I2CSlaveClass parent_class;
26de0c7d54SGlenn Miles     /*< public >*/
27de0c7d54SGlenn Miles };
28de0c7d54SGlenn Miles typedef struct PCA9554Class PCA9554Class;
29de0c7d54SGlenn Miles 
30de0c7d54SGlenn Miles DECLARE_CLASS_CHECKERS(PCA9554Class, PCA9554,
31de0c7d54SGlenn Miles                        TYPE_PCA9554)
32de0c7d54SGlenn Miles 
33de0c7d54SGlenn Miles #define PCA9554_PIN_LOW  0x0
34de0c7d54SGlenn Miles #define PCA9554_PIN_HIZ  0x1
35de0c7d54SGlenn Miles 
36de0c7d54SGlenn Miles static const char *pin_state[] = {"low", "high"};
37de0c7d54SGlenn Miles 
38de0c7d54SGlenn Miles static void pca9554_update_pin_input(PCA9554State *s)
39de0c7d54SGlenn Miles {
40de0c7d54SGlenn Miles     int i;
41de0c7d54SGlenn Miles     uint8_t config = s->regs[PCA9554_CONFIG];
42de0c7d54SGlenn Miles     uint8_t output = s->regs[PCA9554_OUTPUT];
43de0c7d54SGlenn Miles     uint8_t internal_state = config | output;
44de0c7d54SGlenn Miles 
45de0c7d54SGlenn Miles     for (i = 0; i < PCA9554_PIN_COUNT; i++) {
46de0c7d54SGlenn Miles         uint8_t bit_mask = 1 << i;
47de0c7d54SGlenn Miles         uint8_t internal_pin_state = (internal_state >> i) & 0x1;
48de0c7d54SGlenn Miles         uint8_t old_value = s->regs[PCA9554_INPUT] & bit_mask;
49de0c7d54SGlenn Miles         uint8_t new_value;
50de0c7d54SGlenn Miles 
51de0c7d54SGlenn Miles         switch (internal_pin_state) {
52de0c7d54SGlenn Miles         case PCA9554_PIN_LOW:
53de0c7d54SGlenn Miles             s->regs[PCA9554_INPUT] &= ~bit_mask;
54de0c7d54SGlenn Miles             break;
55de0c7d54SGlenn Miles         case PCA9554_PIN_HIZ:
56de0c7d54SGlenn Miles             /*
57de0c7d54SGlenn Miles              * pullup sets it to a logical 1 unless
58de0c7d54SGlenn Miles              * external device drives it low.
59de0c7d54SGlenn Miles              */
60de0c7d54SGlenn Miles             if (s->ext_state[i] == PCA9554_PIN_LOW) {
61de0c7d54SGlenn Miles                 s->regs[PCA9554_INPUT] &= ~bit_mask;
62de0c7d54SGlenn Miles             } else {
63de0c7d54SGlenn Miles                 s->regs[PCA9554_INPUT] |=  bit_mask;
64de0c7d54SGlenn Miles             }
65de0c7d54SGlenn Miles             break;
66de0c7d54SGlenn Miles         default:
67de0c7d54SGlenn Miles             break;
68de0c7d54SGlenn Miles         }
69de0c7d54SGlenn Miles 
70de0c7d54SGlenn Miles         /* update irq state only if pin state changed */
71de0c7d54SGlenn Miles         new_value = s->regs[PCA9554_INPUT] & bit_mask;
72de0c7d54SGlenn Miles         if (new_value != old_value) {
73de0c7d54SGlenn Miles             if (new_value) {
74de0c7d54SGlenn Miles                 /* changed from 0 to 1 */
75de0c7d54SGlenn Miles                 qemu_set_irq(s->gpio_out[i], 1);
76de0c7d54SGlenn Miles             } else {
77de0c7d54SGlenn Miles                 /* changed from 1 to 0 */
78de0c7d54SGlenn Miles                 qemu_set_irq(s->gpio_out[i], 0);
79de0c7d54SGlenn Miles             }
80de0c7d54SGlenn Miles         }
81de0c7d54SGlenn Miles     }
82de0c7d54SGlenn Miles }
83de0c7d54SGlenn Miles 
84de0c7d54SGlenn Miles static uint8_t pca9554_read(PCA9554State *s, uint8_t reg)
85de0c7d54SGlenn Miles {
86de0c7d54SGlenn Miles     switch (reg) {
87de0c7d54SGlenn Miles     case PCA9554_INPUT:
88de0c7d54SGlenn Miles         return s->regs[PCA9554_INPUT] ^ s->regs[PCA9554_POLARITY];
89de0c7d54SGlenn Miles     case PCA9554_OUTPUT:
90de0c7d54SGlenn Miles     case PCA9554_POLARITY:
91de0c7d54SGlenn Miles     case PCA9554_CONFIG:
92de0c7d54SGlenn Miles         return s->regs[reg];
93de0c7d54SGlenn Miles     default:
94de0c7d54SGlenn Miles         qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n",
95de0c7d54SGlenn Miles                       __func__, reg);
96de0c7d54SGlenn Miles         return 0xFF;
97de0c7d54SGlenn Miles     }
98de0c7d54SGlenn Miles }
99de0c7d54SGlenn Miles 
100de0c7d54SGlenn Miles static void pca9554_write(PCA9554State *s, uint8_t reg, uint8_t data)
101de0c7d54SGlenn Miles {
102de0c7d54SGlenn Miles     switch (reg) {
103de0c7d54SGlenn Miles     case PCA9554_OUTPUT:
104de0c7d54SGlenn Miles     case PCA9554_CONFIG:
105de0c7d54SGlenn Miles         s->regs[reg] = data;
106de0c7d54SGlenn Miles         pca9554_update_pin_input(s);
107de0c7d54SGlenn Miles         break;
108de0c7d54SGlenn Miles     case PCA9554_POLARITY:
109de0c7d54SGlenn Miles         s->regs[reg] = data;
110de0c7d54SGlenn Miles         break;
111de0c7d54SGlenn Miles     case PCA9554_INPUT:
112de0c7d54SGlenn Miles     default:
113de0c7d54SGlenn Miles         qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n",
114de0c7d54SGlenn Miles                       __func__, reg);
115de0c7d54SGlenn Miles     }
116de0c7d54SGlenn Miles }
117de0c7d54SGlenn Miles 
118de0c7d54SGlenn Miles static uint8_t pca9554_recv(I2CSlave *i2c)
119de0c7d54SGlenn Miles {
120de0c7d54SGlenn Miles     PCA9554State *s = PCA9554(i2c);
121de0c7d54SGlenn Miles     uint8_t ret;
122de0c7d54SGlenn Miles 
123de0c7d54SGlenn Miles     ret = pca9554_read(s, s->pointer & 0x3);
124de0c7d54SGlenn Miles 
125de0c7d54SGlenn Miles     return ret;
126de0c7d54SGlenn Miles }
127de0c7d54SGlenn Miles 
128de0c7d54SGlenn Miles static int pca9554_send(I2CSlave *i2c, uint8_t data)
129de0c7d54SGlenn Miles {
130de0c7d54SGlenn Miles     PCA9554State *s = PCA9554(i2c);
131de0c7d54SGlenn Miles 
132de0c7d54SGlenn Miles     /* First byte sent by is the register address */
133de0c7d54SGlenn Miles     if (s->len == 0) {
134de0c7d54SGlenn Miles         s->pointer = data;
135de0c7d54SGlenn Miles         s->len++;
136de0c7d54SGlenn Miles     } else {
137de0c7d54SGlenn Miles         pca9554_write(s, s->pointer & 0x3, data);
138de0c7d54SGlenn Miles     }
139de0c7d54SGlenn Miles 
140de0c7d54SGlenn Miles     return 0;
141de0c7d54SGlenn Miles }
142de0c7d54SGlenn Miles 
143de0c7d54SGlenn Miles static int pca9554_event(I2CSlave *i2c, enum i2c_event event)
144de0c7d54SGlenn Miles {
145de0c7d54SGlenn Miles     PCA9554State *s = PCA9554(i2c);
146de0c7d54SGlenn Miles 
147de0c7d54SGlenn Miles     s->len = 0;
148de0c7d54SGlenn Miles     return 0;
149de0c7d54SGlenn Miles }
150de0c7d54SGlenn Miles 
151de0c7d54SGlenn Miles static void pca9554_get_pin(Object *obj, Visitor *v, const char *name,
152de0c7d54SGlenn Miles                             void *opaque, Error **errp)
153de0c7d54SGlenn Miles {
154de0c7d54SGlenn Miles     PCA9554State *s = PCA9554(obj);
155de0c7d54SGlenn Miles     int pin, rc;
156de0c7d54SGlenn Miles     uint8_t state;
157de0c7d54SGlenn Miles 
158de0c7d54SGlenn Miles     rc = sscanf(name, "pin%2d", &pin);
159de0c7d54SGlenn Miles     if (rc != 1) {
160de0c7d54SGlenn Miles         error_setg(errp, "%s: error reading %s", __func__, name);
161de0c7d54SGlenn Miles         return;
162de0c7d54SGlenn Miles     }
163c67f7580SPeter Maydell     if (pin < 0 || pin >= PCA9554_PIN_COUNT) {
164de0c7d54SGlenn Miles         error_setg(errp, "%s invalid pin %s", __func__, name);
165de0c7d54SGlenn Miles         return;
166de0c7d54SGlenn Miles     }
167de0c7d54SGlenn Miles 
168de0c7d54SGlenn Miles     state = pca9554_read(s, PCA9554_CONFIG);
169de0c7d54SGlenn Miles     state |= pca9554_read(s, PCA9554_OUTPUT);
170de0c7d54SGlenn Miles     state = (state >> pin) & 0x1;
171de0c7d54SGlenn Miles     visit_type_str(v, name, (char **)&pin_state[state], errp);
172de0c7d54SGlenn Miles }
173de0c7d54SGlenn Miles 
174de0c7d54SGlenn Miles static void pca9554_set_pin(Object *obj, Visitor *v, const char *name,
175de0c7d54SGlenn Miles                             void *opaque, Error **errp)
176de0c7d54SGlenn Miles {
177de0c7d54SGlenn Miles     PCA9554State *s = PCA9554(obj);
178de0c7d54SGlenn Miles     int pin, rc, val;
179de0c7d54SGlenn Miles     uint8_t state, mask;
180de0c7d54SGlenn Miles     char *state_str;
181de0c7d54SGlenn Miles 
182de0c7d54SGlenn Miles     if (!visit_type_str(v, name, &state_str, errp)) {
183de0c7d54SGlenn Miles         return;
184de0c7d54SGlenn Miles     }
185de0c7d54SGlenn Miles     rc = sscanf(name, "pin%2d", &pin);
186de0c7d54SGlenn Miles     if (rc != 1) {
187de0c7d54SGlenn Miles         error_setg(errp, "%s: error reading %s", __func__, name);
188de0c7d54SGlenn Miles         return;
189de0c7d54SGlenn Miles     }
190c67f7580SPeter Maydell     if (pin < 0 || pin >= PCA9554_PIN_COUNT) {
191de0c7d54SGlenn Miles         error_setg(errp, "%s invalid pin %s", __func__, name);
192de0c7d54SGlenn Miles         return;
193de0c7d54SGlenn Miles     }
194de0c7d54SGlenn Miles 
195de0c7d54SGlenn Miles     for (state = 0; state < ARRAY_SIZE(pin_state); state++) {
196de0c7d54SGlenn Miles         if (!strcmp(state_str, pin_state[state])) {
197de0c7d54SGlenn Miles             break;
198de0c7d54SGlenn Miles         }
199de0c7d54SGlenn Miles     }
200de0c7d54SGlenn Miles     if (state >= ARRAY_SIZE(pin_state)) {
201de0c7d54SGlenn Miles         error_setg(errp, "%s invalid pin state %s", __func__, state_str);
202de0c7d54SGlenn Miles         return;
203de0c7d54SGlenn Miles     }
204de0c7d54SGlenn Miles 
205de0c7d54SGlenn Miles     /* First, modify the output register bit */
206de0c7d54SGlenn Miles     val = pca9554_read(s, PCA9554_OUTPUT);
207de0c7d54SGlenn Miles     mask = 0x1 << pin;
208de0c7d54SGlenn Miles     if (state == PCA9554_PIN_LOW) {
209de0c7d54SGlenn Miles         val &= ~(mask);
210de0c7d54SGlenn Miles     } else {
211de0c7d54SGlenn Miles         val |= mask;
212de0c7d54SGlenn Miles     }
213de0c7d54SGlenn Miles     pca9554_write(s, PCA9554_OUTPUT, val);
214de0c7d54SGlenn Miles 
215de0c7d54SGlenn Miles     /* Then, clear the config register bit for output mode */
216de0c7d54SGlenn Miles     val = pca9554_read(s, PCA9554_CONFIG);
217de0c7d54SGlenn Miles     val &= ~mask;
218de0c7d54SGlenn Miles     pca9554_write(s, PCA9554_CONFIG, val);
219de0c7d54SGlenn Miles }
220de0c7d54SGlenn Miles 
221de0c7d54SGlenn Miles static const VMStateDescription pca9554_vmstate = {
222de0c7d54SGlenn Miles     .name = "PCA9554",
223de0c7d54SGlenn Miles     .version_id = 0,
224de0c7d54SGlenn Miles     .minimum_version_id = 0,
225de0c7d54SGlenn Miles     .fields = (VMStateField[]) {
226de0c7d54SGlenn Miles         VMSTATE_UINT8(len, PCA9554State),
227de0c7d54SGlenn Miles         VMSTATE_UINT8(pointer, PCA9554State),
228de0c7d54SGlenn Miles         VMSTATE_UINT8_ARRAY(regs, PCA9554State, PCA9554_NR_REGS),
229de0c7d54SGlenn Miles         VMSTATE_UINT8_ARRAY(ext_state, PCA9554State, PCA9554_PIN_COUNT),
230de0c7d54SGlenn Miles         VMSTATE_I2C_SLAVE(i2c, PCA9554State),
231de0c7d54SGlenn Miles         VMSTATE_END_OF_LIST()
232de0c7d54SGlenn Miles     }
233de0c7d54SGlenn Miles };
234de0c7d54SGlenn Miles 
235de0c7d54SGlenn Miles static void pca9554_reset(DeviceState *dev)
236de0c7d54SGlenn Miles {
237de0c7d54SGlenn Miles     PCA9554State *s = PCA9554(dev);
238de0c7d54SGlenn Miles 
239de0c7d54SGlenn Miles     s->regs[PCA9554_INPUT] = 0xFF;
240de0c7d54SGlenn Miles     s->regs[PCA9554_OUTPUT] = 0xFF;
241de0c7d54SGlenn Miles     s->regs[PCA9554_POLARITY] = 0x0; /* No pins are inverted */
242de0c7d54SGlenn Miles     s->regs[PCA9554_CONFIG] = 0xFF; /* All pins are inputs */
243de0c7d54SGlenn Miles 
244de0c7d54SGlenn Miles     memset(s->ext_state, PCA9554_PIN_HIZ, PCA9554_PIN_COUNT);
245de0c7d54SGlenn Miles     pca9554_update_pin_input(s);
246de0c7d54SGlenn Miles 
247de0c7d54SGlenn Miles     s->pointer = 0x0;
248de0c7d54SGlenn Miles     s->len = 0;
249de0c7d54SGlenn Miles }
250de0c7d54SGlenn Miles 
251de0c7d54SGlenn Miles static void pca9554_initfn(Object *obj)
252de0c7d54SGlenn Miles {
253de0c7d54SGlenn Miles     int pin;
254de0c7d54SGlenn Miles 
255de0c7d54SGlenn Miles     for (pin = 0; pin < PCA9554_PIN_COUNT; pin++) {
256de0c7d54SGlenn Miles         char *name;
257de0c7d54SGlenn Miles 
258de0c7d54SGlenn Miles         name = g_strdup_printf("pin%d", pin);
259de0c7d54SGlenn Miles         object_property_add(obj, name, "bool", pca9554_get_pin, pca9554_set_pin,
260de0c7d54SGlenn Miles                             NULL, NULL);
261de0c7d54SGlenn Miles         g_free(name);
262de0c7d54SGlenn Miles     }
263de0c7d54SGlenn Miles }
264de0c7d54SGlenn Miles 
265de0c7d54SGlenn Miles static void pca9554_set_ext_state(PCA9554State *s, int pin, int level)
266de0c7d54SGlenn Miles {
267de0c7d54SGlenn Miles     if (s->ext_state[pin] != level) {
268de0c7d54SGlenn Miles         s->ext_state[pin] = level;
269de0c7d54SGlenn Miles         pca9554_update_pin_input(s);
270de0c7d54SGlenn Miles     }
271de0c7d54SGlenn Miles }
272de0c7d54SGlenn Miles 
273de0c7d54SGlenn Miles static void pca9554_gpio_in_handler(void *opaque, int pin, int level)
274de0c7d54SGlenn Miles {
275de0c7d54SGlenn Miles 
276de0c7d54SGlenn Miles     PCA9554State *s = PCA9554(opaque);
277de0c7d54SGlenn Miles 
278de0c7d54SGlenn Miles     assert((pin >= 0) && (pin < PCA9554_PIN_COUNT));
279de0c7d54SGlenn Miles     pca9554_set_ext_state(s, pin, level);
280de0c7d54SGlenn Miles }
281de0c7d54SGlenn Miles 
282de0c7d54SGlenn Miles static void pca9554_realize(DeviceState *dev, Error **errp)
283de0c7d54SGlenn Miles {
284de0c7d54SGlenn Miles     PCA9554State *s = PCA9554(dev);
285de0c7d54SGlenn Miles 
286de0c7d54SGlenn Miles     if (!s->description) {
287de0c7d54SGlenn Miles         s->description = g_strdup("pca9554");
288de0c7d54SGlenn Miles     }
289de0c7d54SGlenn Miles 
290de0c7d54SGlenn Miles     qdev_init_gpio_out(dev, s->gpio_out, PCA9554_PIN_COUNT);
291de0c7d54SGlenn Miles     qdev_init_gpio_in(dev, pca9554_gpio_in_handler, PCA9554_PIN_COUNT);
292de0c7d54SGlenn Miles }
293de0c7d54SGlenn Miles 
294de0c7d54SGlenn Miles static Property pca9554_properties[] = {
295de0c7d54SGlenn Miles     DEFINE_PROP_STRING("description", PCA9554State, description),
296de0c7d54SGlenn Miles     DEFINE_PROP_END_OF_LIST(),
297de0c7d54SGlenn Miles };
298de0c7d54SGlenn Miles 
299de0c7d54SGlenn Miles static void pca9554_class_init(ObjectClass *klass, void *data)
300de0c7d54SGlenn Miles {
301de0c7d54SGlenn Miles     DeviceClass *dc = DEVICE_CLASS(klass);
302de0c7d54SGlenn Miles     I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
303de0c7d54SGlenn Miles 
304de0c7d54SGlenn Miles     k->event = pca9554_event;
305de0c7d54SGlenn Miles     k->recv = pca9554_recv;
306de0c7d54SGlenn Miles     k->send = pca9554_send;
307de0c7d54SGlenn Miles     dc->realize = pca9554_realize;
308*e3d08143SPeter Maydell     device_class_set_legacy_reset(dc, pca9554_reset);
309de0c7d54SGlenn Miles     dc->vmsd = &pca9554_vmstate;
310de0c7d54SGlenn Miles     device_class_set_props(dc, pca9554_properties);
311de0c7d54SGlenn Miles }
312de0c7d54SGlenn Miles 
313de0c7d54SGlenn Miles static const TypeInfo pca9554_info = {
314de0c7d54SGlenn Miles     .name          = TYPE_PCA9554,
315de0c7d54SGlenn Miles     .parent        = TYPE_I2C_SLAVE,
316de0c7d54SGlenn Miles     .instance_init = pca9554_initfn,
317de0c7d54SGlenn Miles     .instance_size = sizeof(PCA9554State),
318de0c7d54SGlenn Miles     .class_init    = pca9554_class_init,
319de0c7d54SGlenn Miles     .class_size    = sizeof(PCA9554Class),
320de0c7d54SGlenn Miles     .abstract      = false,
321de0c7d54SGlenn Miles };
322de0c7d54SGlenn Miles 
323de0c7d54SGlenn Miles static void pca9554_register_types(void)
324de0c7d54SGlenn Miles {
325de0c7d54SGlenn Miles     type_register_static(&pca9554_info);
326de0c7d54SGlenn Miles }
327de0c7d54SGlenn Miles 
328de0c7d54SGlenn Miles type_init(pca9554_register_types)
329