xref: /qemu/hw/gpio/pca9552.c (revision 12d1a768bdfea6e27a3a829228840d72507613a1)
15141d415SCédric Le Goater /*
25141d415SCédric Le Goater  * PCA9552 I2C LED blinker
35141d415SCédric Le Goater  *
45141d415SCédric Le Goater  *     https://www.nxp.com/docs/en/application-note/AN264.pdf
55141d415SCédric Le Goater  *
65141d415SCédric Le Goater  * Copyright (c) 2017-2018, IBM Corporation.
7736132e4SPhilippe Mathieu-Daudé  * Copyright (c) 2020 Philippe Mathieu-Daudé
85141d415SCédric Le Goater  *
95141d415SCédric Le Goater  * This work is licensed under the terms of the GNU GPL, version 2 or
105141d415SCédric Le Goater  * later. See the COPYING file in the top-level directory.
115141d415SCédric Le Goater  */
125141d415SCédric Le Goater 
135141d415SCédric Le Goater #include "qemu/osdep.h"
145141d415SCédric Le Goater #include "qemu/log.h"
150b8fa32fSMarkus Armbruster #include "qemu/module.h"
16b989b89fSPhilippe Mathieu-Daudé #include "qemu/bitops.h"
172df252d8SPhilippe Mathieu-Daudé #include "hw/qdev-properties.h"
186328d8ffSCédric Le Goater #include "hw/gpio/pca9552.h"
196328d8ffSCédric Le Goater #include "hw/gpio/pca9552_regs.h"
20586f495bSPhilippe Mathieu-Daudé #include "hw/irq.h"
21d6454270SMarkus Armbruster #include "migration/vmstate.h"
22a90d8f84SJoel Stanley #include "qapi/error.h"
23a90d8f84SJoel Stanley #include "qapi/visitor.h"
24b989b89fSPhilippe Mathieu-Daudé #include "trace.h"
25db1015e9SEduardo Habkost #include "qom/object.h"
265141d415SCédric Le Goater 
27db1015e9SEduardo Habkost struct PCA955xClass {
28736132e4SPhilippe Mathieu-Daudé     /*< private >*/
29736132e4SPhilippe Mathieu-Daudé     I2CSlaveClass parent_class;
30736132e4SPhilippe Mathieu-Daudé     /*< public >*/
31736132e4SPhilippe Mathieu-Daudé 
32736132e4SPhilippe Mathieu-Daudé     uint8_t pin_count;
33736132e4SPhilippe Mathieu-Daudé     uint8_t max_reg;
34db1015e9SEduardo Habkost };
35db1015e9SEduardo Habkost typedef struct PCA955xClass PCA955xClass;
36736132e4SPhilippe Mathieu-Daudé 
378110fa1dSEduardo Habkost DECLARE_CLASS_CHECKERS(PCA955xClass, PCA955X,
388110fa1dSEduardo Habkost                        TYPE_PCA955X)
397b99fb30SGlenn Miles /*
407b99fb30SGlenn Miles  * Note:  The LED_ON and LED_OFF configuration values for the PCA955X
417b99fb30SGlenn Miles  *        chips are the reverse of the PCA953X family of chips.
427b99fb30SGlenn Miles  */
435141d415SCédric Le Goater #define PCA9552_LED_ON   0x0
445141d415SCédric Le Goater #define PCA9552_LED_OFF  0x1
455141d415SCédric Le Goater #define PCA9552_LED_PWM0 0x2
465141d415SCédric Le Goater #define PCA9552_LED_PWM1 0x3
47ff557c27SGlenn Miles #define PCA9552_PIN_LOW  0x0
48ff557c27SGlenn Miles #define PCA9552_PIN_HIZ  0x1
495141d415SCédric Le Goater 
50a90d8f84SJoel Stanley static const char *led_state[] = {"on", "off", "pwm0", "pwm1"};
51a90d8f84SJoel Stanley 
pca955x_pin_get_config(PCA955xState * s,int pin)52ec17228aSPhilippe Mathieu-Daudé static uint8_t pca955x_pin_get_config(PCA955xState *s, int pin)
535141d415SCédric Le Goater {
545141d415SCédric Le Goater     uint8_t reg   = PCA9552_LS0 + (pin / 4);
555141d415SCédric Le Goater     uint8_t shift = (pin % 4) << 1;
565141d415SCédric Le Goater 
575141d415SCédric Le Goater     return extract32(s->regs[reg], shift, 2);
585141d415SCédric Le Goater }
595141d415SCédric Le Goater 
60b989b89fSPhilippe Mathieu-Daudé /* Return INPUT status (bit #N belongs to GPIO #N) */
pca955x_pins_get_status(PCA955xState * s)61b989b89fSPhilippe Mathieu-Daudé static uint16_t pca955x_pins_get_status(PCA955xState *s)
62b989b89fSPhilippe Mathieu-Daudé {
63b989b89fSPhilippe Mathieu-Daudé     return (s->regs[PCA9552_INPUT1] << 8) | s->regs[PCA9552_INPUT0];
64b989b89fSPhilippe Mathieu-Daudé }
65b989b89fSPhilippe Mathieu-Daudé 
pca955x_display_pins_status(PCA955xState * s,uint16_t previous_pins_status)66b989b89fSPhilippe Mathieu-Daudé static void pca955x_display_pins_status(PCA955xState *s,
67b989b89fSPhilippe Mathieu-Daudé                                         uint16_t previous_pins_status)
68b989b89fSPhilippe Mathieu-Daudé {
69b989b89fSPhilippe Mathieu-Daudé     PCA955xClass *k = PCA955X_GET_CLASS(s);
70b989b89fSPhilippe Mathieu-Daudé     uint16_t pins_status, pins_changed;
71b989b89fSPhilippe Mathieu-Daudé     int i;
72b989b89fSPhilippe Mathieu-Daudé 
73b989b89fSPhilippe Mathieu-Daudé     pins_status = pca955x_pins_get_status(s);
74b989b89fSPhilippe Mathieu-Daudé     pins_changed = previous_pins_status ^ pins_status;
75b989b89fSPhilippe Mathieu-Daudé     if (!pins_changed) {
76b989b89fSPhilippe Mathieu-Daudé         return;
77b989b89fSPhilippe Mathieu-Daudé     }
78b989b89fSPhilippe Mathieu-Daudé     if (trace_event_get_state_backends(TRACE_PCA955X_GPIO_STATUS)) {
79b989b89fSPhilippe Mathieu-Daudé         char *buf = g_newa(char, k->pin_count + 1);
80b989b89fSPhilippe Mathieu-Daudé 
81b989b89fSPhilippe Mathieu-Daudé         for (i = 0; i < k->pin_count; i++) {
82b989b89fSPhilippe Mathieu-Daudé             if (extract32(pins_status, i, 1)) {
83b989b89fSPhilippe Mathieu-Daudé                 buf[i] = '*';
84b989b89fSPhilippe Mathieu-Daudé             } else {
85b989b89fSPhilippe Mathieu-Daudé                 buf[i] = '.';
86b989b89fSPhilippe Mathieu-Daudé             }
87b989b89fSPhilippe Mathieu-Daudé         }
88b989b89fSPhilippe Mathieu-Daudé         buf[i] = '\0';
89b989b89fSPhilippe Mathieu-Daudé         trace_pca955x_gpio_status(s->description, buf);
90b989b89fSPhilippe Mathieu-Daudé     }
91d82ab293SPhilippe Mathieu-Daudé     if (trace_event_get_state_backends(TRACE_PCA955X_GPIO_CHANGE)) {
92d82ab293SPhilippe Mathieu-Daudé         for (i = 0; i < k->pin_count; i++) {
93d82ab293SPhilippe Mathieu-Daudé             if (extract32(pins_changed, i, 1)) {
94d82ab293SPhilippe Mathieu-Daudé                 unsigned new_state = extract32(pins_status, i, 1);
95d82ab293SPhilippe Mathieu-Daudé 
96d82ab293SPhilippe Mathieu-Daudé                 /*
97d82ab293SPhilippe Mathieu-Daudé                  * We display the state using the PCA logic ("active-high").
98d82ab293SPhilippe Mathieu-Daudé                  * This is not the state of the LED, which signal might be
99d82ab293SPhilippe Mathieu-Daudé                  * wired "active-low" on the board.
100d82ab293SPhilippe Mathieu-Daudé                  */
101d82ab293SPhilippe Mathieu-Daudé                 trace_pca955x_gpio_change(s->description, i,
102d82ab293SPhilippe Mathieu-Daudé                                           !new_state, new_state);
103d82ab293SPhilippe Mathieu-Daudé             }
104d82ab293SPhilippe Mathieu-Daudé         }
105d82ab293SPhilippe Mathieu-Daudé     }
106b989b89fSPhilippe Mathieu-Daudé }
107b989b89fSPhilippe Mathieu-Daudé 
pca955x_update_pin_input(PCA955xState * s)108ec17228aSPhilippe Mathieu-Daudé static void pca955x_update_pin_input(PCA955xState *s)
1095141d415SCédric Le Goater {
110736132e4SPhilippe Mathieu-Daudé     PCA955xClass *k = PCA955X_GET_CLASS(s);
1115141d415SCédric Le Goater     int i;
1125141d415SCédric Le Goater 
113736132e4SPhilippe Mathieu-Daudé     for (i = 0; i < k->pin_count; i++) {
1145141d415SCédric Le Goater         uint8_t input_reg = PCA9552_INPUT0 + (i / 8);
115ff557c27SGlenn Miles         uint8_t bit_mask = 1 << (i % 8);
116ec17228aSPhilippe Mathieu-Daudé         uint8_t config = pca955x_pin_get_config(s, i);
117ff557c27SGlenn Miles         uint8_t old_value = s->regs[input_reg] & bit_mask;
118ff557c27SGlenn Miles         uint8_t new_value;
1195141d415SCédric Le Goater 
1205141d415SCédric Le Goater         switch (config) {
1215141d415SCédric Le Goater         case PCA9552_LED_ON:
1227b99fb30SGlenn Miles             /* Pin is set to 0V to turn on LED */
123ff557c27SGlenn Miles             s->regs[input_reg] &= ~bit_mask;
1245141d415SCédric Le Goater             break;
1257b99fb30SGlenn Miles         case PCA9552_LED_OFF:
1267b99fb30SGlenn Miles             /*
1277b99fb30SGlenn Miles              * Pin is set to Hi-Z to turn off LED and
128ff557c27SGlenn Miles              * pullup sets it to a logical 1 unless
129ff557c27SGlenn Miles              * external device drives it low.
1307b99fb30SGlenn Miles              */
131ff557c27SGlenn Miles             if (s->ext_state[i] == PCA9552_PIN_LOW) {
132ff557c27SGlenn Miles                 s->regs[input_reg] &= ~bit_mask;
133ff557c27SGlenn Miles             } else {
134ff557c27SGlenn Miles                 s->regs[input_reg] |=  bit_mask;
135ff557c27SGlenn Miles             }
1367b99fb30SGlenn Miles             break;
1375141d415SCédric Le Goater         case PCA9552_LED_PWM0:
1385141d415SCédric Le Goater         case PCA9552_LED_PWM1:
1395141d415SCédric Le Goater             /* TODO */
1405141d415SCédric Le Goater         default:
1415141d415SCédric Le Goater             break;
1425141d415SCédric Le Goater         }
143ff557c27SGlenn Miles 
144ff557c27SGlenn Miles         /* update irq state only if pin state changed */
145ff557c27SGlenn Miles         new_value = s->regs[input_reg] & bit_mask;
146ff557c27SGlenn Miles         if (new_value != old_value) {
147ff557c27SGlenn Miles             qemu_set_irq(s->gpio_out[i], !!new_value);
148ff557c27SGlenn Miles         }
1495141d415SCédric Le Goater     }
1505141d415SCédric Le Goater }
1515141d415SCédric Le Goater 
pca955x_read(PCA955xState * s,uint8_t reg)152ec17228aSPhilippe Mathieu-Daudé static uint8_t pca955x_read(PCA955xState *s, uint8_t reg)
1535141d415SCédric Le Goater {
1545141d415SCédric Le Goater     switch (reg) {
1555141d415SCédric Le Goater     case PCA9552_INPUT0:
1565141d415SCédric Le Goater     case PCA9552_INPUT1:
1575141d415SCédric Le Goater     case PCA9552_PSC0:
1585141d415SCédric Le Goater     case PCA9552_PWM0:
1595141d415SCédric Le Goater     case PCA9552_PSC1:
1605141d415SCédric Le Goater     case PCA9552_PWM1:
1615141d415SCédric Le Goater     case PCA9552_LS0:
1625141d415SCédric Le Goater     case PCA9552_LS1:
1635141d415SCédric Le Goater     case PCA9552_LS2:
1645141d415SCédric Le Goater     case PCA9552_LS3:
1655141d415SCédric Le Goater         return s->regs[reg];
1665141d415SCédric Le Goater     default:
1675141d415SCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n",
1685141d415SCédric Le Goater                       __func__, reg);
1695141d415SCédric Le Goater         return 0xFF;
1705141d415SCédric Le Goater     }
1715141d415SCédric Le Goater }
1725141d415SCédric Le Goater 
pca955x_write(PCA955xState * s,uint8_t reg,uint8_t data)173ec17228aSPhilippe Mathieu-Daudé static void pca955x_write(PCA955xState *s, uint8_t reg, uint8_t data)
1745141d415SCédric Le Goater {
175b989b89fSPhilippe Mathieu-Daudé     uint16_t pins_status;
176b989b89fSPhilippe Mathieu-Daudé 
1775141d415SCédric Le Goater     switch (reg) {
1785141d415SCédric Le Goater     case PCA9552_PSC0:
1795141d415SCédric Le Goater     case PCA9552_PWM0:
1805141d415SCédric Le Goater     case PCA9552_PSC1:
1815141d415SCédric Le Goater     case PCA9552_PWM1:
1825141d415SCédric Le Goater         s->regs[reg] = data;
1835141d415SCédric Le Goater         break;
1845141d415SCédric Le Goater 
1855141d415SCédric Le Goater     case PCA9552_LS0:
1865141d415SCédric Le Goater     case PCA9552_LS1:
1875141d415SCédric Le Goater     case PCA9552_LS2:
1885141d415SCédric Le Goater     case PCA9552_LS3:
189b989b89fSPhilippe Mathieu-Daudé         pins_status = pca955x_pins_get_status(s);
1905141d415SCédric Le Goater         s->regs[reg] = data;
191ec17228aSPhilippe Mathieu-Daudé         pca955x_update_pin_input(s);
192b989b89fSPhilippe Mathieu-Daudé         pca955x_display_pins_status(s, pins_status);
1935141d415SCédric Le Goater         break;
1945141d415SCédric Le Goater 
1955141d415SCédric Le Goater     case PCA9552_INPUT0:
1965141d415SCédric Le Goater     case PCA9552_INPUT1:
1975141d415SCédric Le Goater     default:
1985141d415SCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n",
1995141d415SCédric Le Goater                       __func__, reg);
2005141d415SCédric Le Goater     }
2015141d415SCédric Le Goater }
2025141d415SCédric Le Goater 
2035141d415SCédric Le Goater /*
2045141d415SCédric Le Goater  * When Auto-Increment is on, the register address is incremented
2055141d415SCédric Le Goater  * after each byte is sent to or received by the device. The index
2065141d415SCédric Le Goater  * rollovers to 0 when the maximum register address is reached.
2075141d415SCédric Le Goater  */
pca955x_autoinc(PCA955xState * s)208ec17228aSPhilippe Mathieu-Daudé static void pca955x_autoinc(PCA955xState *s)
2095141d415SCédric Le Goater {
210736132e4SPhilippe Mathieu-Daudé     PCA955xClass *k = PCA955X_GET_CLASS(s);
211736132e4SPhilippe Mathieu-Daudé 
2125141d415SCédric Le Goater     if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) {
2135141d415SCédric Le Goater         uint8_t reg = s->pointer & 0xf;
2145141d415SCédric Le Goater 
215736132e4SPhilippe Mathieu-Daudé         reg = (reg + 1) % (k->max_reg + 1);
2165141d415SCédric Le Goater         s->pointer = reg | PCA9552_AUTOINC;
2175141d415SCédric Le Goater     }
2185141d415SCédric Le Goater }
2195141d415SCédric Le Goater 
pca955x_recv(I2CSlave * i2c)220ec17228aSPhilippe Mathieu-Daudé static uint8_t pca955x_recv(I2CSlave *i2c)
2215141d415SCédric Le Goater {
222ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(i2c);
2235141d415SCédric Le Goater     uint8_t ret;
2245141d415SCédric Le Goater 
225ec17228aSPhilippe Mathieu-Daudé     ret = pca955x_read(s, s->pointer & 0xf);
2265141d415SCédric Le Goater 
2275141d415SCédric Le Goater     /*
2285141d415SCédric Le Goater      * From the Specs:
2295141d415SCédric Le Goater      *
2305141d415SCédric Le Goater      *     Important Note: When a Read sequence is initiated and the
2315141d415SCédric Le Goater      *     AI bit is set to Logic Level 1, the Read Sequence MUST
2325141d415SCédric Le Goater      *     start by a register different from 0.
2335141d415SCédric Le Goater      *
2345141d415SCédric Le Goater      * I don't know what should be done in this case, so throw an
2355141d415SCédric Le Goater      * error.
2365141d415SCédric Le Goater      */
2375141d415SCédric Le Goater     if (s->pointer == PCA9552_AUTOINC) {
2385141d415SCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR,
2395141d415SCédric Le Goater                       "%s: Autoincrement read starting with register 0\n",
2405141d415SCédric Le Goater                       __func__);
2415141d415SCédric Le Goater     }
2425141d415SCédric Le Goater 
243ec17228aSPhilippe Mathieu-Daudé     pca955x_autoinc(s);
2445141d415SCédric Le Goater 
2455141d415SCédric Le Goater     return ret;
2465141d415SCédric Le Goater }
2475141d415SCédric Le Goater 
pca955x_send(I2CSlave * i2c,uint8_t data)248ec17228aSPhilippe Mathieu-Daudé static int pca955x_send(I2CSlave *i2c, uint8_t data)
2495141d415SCédric Le Goater {
250ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(i2c);
2515141d415SCédric Le Goater 
2525141d415SCédric Le Goater     /* First byte sent by is the register address */
2535141d415SCédric Le Goater     if (s->len == 0) {
2545141d415SCédric Le Goater         s->pointer = data;
2555141d415SCédric Le Goater         s->len++;
2565141d415SCédric Le Goater     } else {
257ec17228aSPhilippe Mathieu-Daudé         pca955x_write(s, s->pointer & 0xf, data);
2585141d415SCédric Le Goater 
259ec17228aSPhilippe Mathieu-Daudé         pca955x_autoinc(s);
2605141d415SCédric Le Goater     }
2615141d415SCédric Le Goater 
2625141d415SCédric Le Goater     return 0;
2635141d415SCédric Le Goater }
2645141d415SCédric Le Goater 
pca955x_event(I2CSlave * i2c,enum i2c_event event)265ec17228aSPhilippe Mathieu-Daudé static int pca955x_event(I2CSlave *i2c, enum i2c_event event)
2665141d415SCédric Le Goater {
267ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(i2c);
2685141d415SCédric Le Goater 
2695141d415SCédric Le Goater     s->len = 0;
2705141d415SCédric Le Goater     return 0;
2715141d415SCédric Le Goater }
2725141d415SCédric Le Goater 
pca955x_get_led(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)273ec17228aSPhilippe Mathieu-Daudé static void pca955x_get_led(Object *obj, Visitor *v, const char *name,
274a90d8f84SJoel Stanley                             void *opaque, Error **errp)
275a90d8f84SJoel Stanley {
276736132e4SPhilippe Mathieu-Daudé     PCA955xClass *k = PCA955X_GET_CLASS(obj);
277ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(obj);
278a90d8f84SJoel Stanley     int led, rc, reg;
279a90d8f84SJoel Stanley     uint8_t state;
280a90d8f84SJoel Stanley 
281a90d8f84SJoel Stanley     rc = sscanf(name, "led%2d", &led);
282a90d8f84SJoel Stanley     if (rc != 1) {
283a90d8f84SJoel Stanley         error_setg(errp, "%s: error reading %s", __func__, name);
284a90d8f84SJoel Stanley         return;
285a90d8f84SJoel Stanley     }
286736132e4SPhilippe Mathieu-Daudé     if (led < 0 || led > k->pin_count) {
287a90d8f84SJoel Stanley         error_setg(errp, "%s invalid led %s", __func__, name);
288a90d8f84SJoel Stanley         return;
289a90d8f84SJoel Stanley     }
290a90d8f84SJoel Stanley     /*
291a90d8f84SJoel Stanley      * Get the LSx register as the qom interface should expose the device
292a90d8f84SJoel Stanley      * state, not the modeled 'input line' behaviour which would come from
293a90d8f84SJoel Stanley      * reading the INPUTx reg
294a90d8f84SJoel Stanley      */
295a90d8f84SJoel Stanley     reg = PCA9552_LS0 + led / 4;
2960c33a48dSAndrew Jeffery     state = (pca955x_read(s, reg) >> ((led % 4) * 2)) & 0x3;
297a90d8f84SJoel Stanley     visit_type_str(v, name, (char **)&led_state[state], errp);
298a90d8f84SJoel Stanley }
299a90d8f84SJoel Stanley 
300a90d8f84SJoel Stanley /*
301a90d8f84SJoel Stanley  * Return an LED selector register value based on an existing one, with
302a90d8f84SJoel Stanley  * the appropriate 2-bit state value set for the given LED number (0-3).
303a90d8f84SJoel Stanley  */
pca955x_ledsel(uint8_t oldval,int led_num,int state)304a90d8f84SJoel Stanley static inline uint8_t pca955x_ledsel(uint8_t oldval, int led_num, int state)
305a90d8f84SJoel Stanley {
306a90d8f84SJoel Stanley         return (oldval & (~(0x3 << (led_num << 1)))) |
307a90d8f84SJoel Stanley                 ((state & 0x3) << (led_num << 1));
308a90d8f84SJoel Stanley }
309a90d8f84SJoel Stanley 
pca955x_set_led(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)310ec17228aSPhilippe Mathieu-Daudé static void pca955x_set_led(Object *obj, Visitor *v, const char *name,
311a90d8f84SJoel Stanley                             void *opaque, Error **errp)
312a90d8f84SJoel Stanley {
313736132e4SPhilippe Mathieu-Daudé     PCA955xClass *k = PCA955X_GET_CLASS(obj);
314ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(obj);
315a90d8f84SJoel Stanley     int led, rc, reg, val;
316a90d8f84SJoel Stanley     uint8_t state;
317a90d8f84SJoel Stanley     char *state_str;
318a90d8f84SJoel Stanley 
319668f62ecSMarkus Armbruster     if (!visit_type_str(v, name, &state_str, errp)) {
320a90d8f84SJoel Stanley         return;
321a90d8f84SJoel Stanley     }
322a90d8f84SJoel Stanley     rc = sscanf(name, "led%2d", &led);
323a90d8f84SJoel Stanley     if (rc != 1) {
324a90d8f84SJoel Stanley         error_setg(errp, "%s: error reading %s", __func__, name);
325a90d8f84SJoel Stanley         return;
326a90d8f84SJoel Stanley     }
327736132e4SPhilippe Mathieu-Daudé     if (led < 0 || led > k->pin_count) {
328a90d8f84SJoel Stanley         error_setg(errp, "%s invalid led %s", __func__, name);
329a90d8f84SJoel Stanley         return;
330a90d8f84SJoel Stanley     }
331a90d8f84SJoel Stanley 
332a90d8f84SJoel Stanley     for (state = 0; state < ARRAY_SIZE(led_state); state++) {
333a90d8f84SJoel Stanley         if (!strcmp(state_str, led_state[state])) {
334a90d8f84SJoel Stanley             break;
335a90d8f84SJoel Stanley         }
336a90d8f84SJoel Stanley     }
337a90d8f84SJoel Stanley     if (state >= ARRAY_SIZE(led_state)) {
338a90d8f84SJoel Stanley         error_setg(errp, "%s invalid led state %s", __func__, state_str);
339a90d8f84SJoel Stanley         return;
340a90d8f84SJoel Stanley     }
341a90d8f84SJoel Stanley 
342a90d8f84SJoel Stanley     reg = PCA9552_LS0 + led / 4;
343ec17228aSPhilippe Mathieu-Daudé     val = pca955x_read(s, reg);
344a90d8f84SJoel Stanley     val = pca955x_ledsel(val, led % 4, state);
345ec17228aSPhilippe Mathieu-Daudé     pca955x_write(s, reg, val);
346a90d8f84SJoel Stanley }
347a90d8f84SJoel Stanley 
3485141d415SCédric Le Goater static const VMStateDescription pca9552_vmstate = {
3495141d415SCédric Le Goater     .name = "PCA9552",
3505141d415SCédric Le Goater     .version_id = 0,
3515141d415SCédric Le Goater     .minimum_version_id = 0,
352e4ea952fSRichard Henderson     .fields = (const VMStateField[]) {
353ec17228aSPhilippe Mathieu-Daudé         VMSTATE_UINT8(len, PCA955xState),
354ec17228aSPhilippe Mathieu-Daudé         VMSTATE_UINT8(pointer, PCA955xState),
355ec17228aSPhilippe Mathieu-Daudé         VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS),
356ff557c27SGlenn Miles         VMSTATE_UINT8_ARRAY(ext_state, PCA955xState, PCA955X_PIN_COUNT_MAX),
357ec17228aSPhilippe Mathieu-Daudé         VMSTATE_I2C_SLAVE(i2c, PCA955xState),
3585141d415SCédric Le Goater         VMSTATE_END_OF_LIST()
3595141d415SCédric Le Goater     }
3605141d415SCédric Le Goater };
3615141d415SCédric Le Goater 
pca9552_reset(DeviceState * dev)3625141d415SCédric Le Goater static void pca9552_reset(DeviceState *dev)
3635141d415SCédric Le Goater {
364ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(dev);
3655141d415SCédric Le Goater 
3665141d415SCédric Le Goater     s->regs[PCA9552_PSC0] = 0xFF;
3675141d415SCédric Le Goater     s->regs[PCA9552_PWM0] = 0x80;
3685141d415SCédric Le Goater     s->regs[PCA9552_PSC1] = 0xFF;
3695141d415SCédric Le Goater     s->regs[PCA9552_PWM1] = 0x80;
3705141d415SCédric Le Goater     s->regs[PCA9552_LS0] = 0x55; /* all OFF */
3715141d415SCédric Le Goater     s->regs[PCA9552_LS1] = 0x55;
3725141d415SCédric Le Goater     s->regs[PCA9552_LS2] = 0x55;
3735141d415SCédric Le Goater     s->regs[PCA9552_LS3] = 0x55;
3745141d415SCédric Le Goater 
375ff557c27SGlenn Miles     memset(s->ext_state, PCA9552_PIN_HIZ, PCA955X_PIN_COUNT_MAX);
376ec17228aSPhilippe Mathieu-Daudé     pca955x_update_pin_input(s);
3775141d415SCédric Le Goater 
3785141d415SCédric Le Goater     s->pointer = 0xFF;
3795141d415SCédric Le Goater     s->len = 0;
3805141d415SCédric Le Goater }
3815141d415SCédric Le Goater 
pca955x_initfn(Object * obj)382ec17228aSPhilippe Mathieu-Daudé static void pca955x_initfn(Object *obj)
3835141d415SCédric Le Goater {
384736132e4SPhilippe Mathieu-Daudé     PCA955xClass *k = PCA955X_GET_CLASS(obj);
385a90d8f84SJoel Stanley     int led;
3865141d415SCédric Le Goater 
387736132e4SPhilippe Mathieu-Daudé     assert(k->pin_count <= PCA955X_PIN_COUNT_MAX);
388736132e4SPhilippe Mathieu-Daudé     for (led = 0; led < k->pin_count; led++) {
389a90d8f84SJoel Stanley         char *name;
390a90d8f84SJoel Stanley 
391a90d8f84SJoel Stanley         name = g_strdup_printf("led%d", led);
392ec17228aSPhilippe Mathieu-Daudé         object_property_add(obj, name, "bool", pca955x_get_led, pca955x_set_led,
393d2623129SMarkus Armbruster                             NULL, NULL);
394a90d8f84SJoel Stanley         g_free(name);
395a90d8f84SJoel Stanley     }
3965141d415SCédric Le Goater }
3975141d415SCédric Le Goater 
pca955x_set_ext_state(PCA955xState * s,int pin,int level)398ff557c27SGlenn Miles static void pca955x_set_ext_state(PCA955xState *s, int pin, int level)
399ff557c27SGlenn Miles {
400ff557c27SGlenn Miles     if (s->ext_state[pin] != level) {
401ff557c27SGlenn Miles         uint16_t pins_status = pca955x_pins_get_status(s);
402ff557c27SGlenn Miles         s->ext_state[pin] = level;
403ff557c27SGlenn Miles         pca955x_update_pin_input(s);
404ff557c27SGlenn Miles         pca955x_display_pins_status(s, pins_status);
405ff557c27SGlenn Miles     }
406ff557c27SGlenn Miles }
407ff557c27SGlenn Miles 
pca955x_gpio_in_handler(void * opaque,int pin,int level)408ff557c27SGlenn Miles static void pca955x_gpio_in_handler(void *opaque, int pin, int level)
409ff557c27SGlenn Miles {
410ff557c27SGlenn Miles 
411ff557c27SGlenn Miles     PCA955xState *s = PCA955X(opaque);
412ff557c27SGlenn Miles     PCA955xClass *k = PCA955X_GET_CLASS(s);
413ff557c27SGlenn Miles 
414ff557c27SGlenn Miles     assert((pin >= 0) && (pin < k->pin_count));
415ff557c27SGlenn Miles     pca955x_set_ext_state(s, pin, level);
416ff557c27SGlenn Miles }
417ff557c27SGlenn Miles 
pca955x_realize(DeviceState * dev,Error ** errp)4182df252d8SPhilippe Mathieu-Daudé static void pca955x_realize(DeviceState *dev, Error **errp)
4192df252d8SPhilippe Mathieu-Daudé {
420586f495bSPhilippe Mathieu-Daudé     PCA955xClass *k = PCA955X_GET_CLASS(dev);
4212df252d8SPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(dev);
4222df252d8SPhilippe Mathieu-Daudé 
4232df252d8SPhilippe Mathieu-Daudé     if (!s->description) {
4242df252d8SPhilippe Mathieu-Daudé         s->description = g_strdup("pca-unspecified");
4252df252d8SPhilippe Mathieu-Daudé     }
426586f495bSPhilippe Mathieu-Daudé 
427ff557c27SGlenn Miles     qdev_init_gpio_out(dev, s->gpio_out, k->pin_count);
428ff557c27SGlenn Miles     qdev_init_gpio_in(dev, pca955x_gpio_in_handler, k->pin_count);
4292df252d8SPhilippe Mathieu-Daudé }
4302df252d8SPhilippe Mathieu-Daudé 
431de531a6bSRichard Henderson static const Property pca955x_properties[] = {
4322df252d8SPhilippe Mathieu-Daudé     DEFINE_PROP_STRING("description", PCA955xState, description),
4332df252d8SPhilippe Mathieu-Daudé };
4342df252d8SPhilippe Mathieu-Daudé 
pca955x_class_init(ObjectClass * klass,const void * data)435*12d1a768SPhilippe Mathieu-Daudé static void pca955x_class_init(ObjectClass *klass, const void *data)
4365141d415SCédric Le Goater {
4372df252d8SPhilippe Mathieu-Daudé     DeviceClass *dc = DEVICE_CLASS(klass);
4385141d415SCédric Le Goater     I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
4395141d415SCédric Le Goater 
440ec17228aSPhilippe Mathieu-Daudé     k->event = pca955x_event;
441ec17228aSPhilippe Mathieu-Daudé     k->recv = pca955x_recv;
442ec17228aSPhilippe Mathieu-Daudé     k->send = pca955x_send;
4432df252d8SPhilippe Mathieu-Daudé     dc->realize = pca955x_realize;
4442df252d8SPhilippe Mathieu-Daudé     device_class_set_props(dc, pca955x_properties);
445736132e4SPhilippe Mathieu-Daudé }
446736132e4SPhilippe Mathieu-Daudé 
447736132e4SPhilippe Mathieu-Daudé static const TypeInfo pca955x_info = {
448736132e4SPhilippe Mathieu-Daudé     .name          = TYPE_PCA955X,
449736132e4SPhilippe Mathieu-Daudé     .parent        = TYPE_I2C_SLAVE,
450736132e4SPhilippe Mathieu-Daudé     .instance_init = pca955x_initfn,
451736132e4SPhilippe Mathieu-Daudé     .instance_size = sizeof(PCA955xState),
452736132e4SPhilippe Mathieu-Daudé     .class_init    = pca955x_class_init,
453fc1bff95SPhilippe Mathieu-Daudé     .class_size    = sizeof(PCA955xClass),
454736132e4SPhilippe Mathieu-Daudé     .abstract      = true,
455736132e4SPhilippe Mathieu-Daudé };
456736132e4SPhilippe Mathieu-Daudé 
pca9552_class_init(ObjectClass * oc,const void * data)457*12d1a768SPhilippe Mathieu-Daudé static void pca9552_class_init(ObjectClass *oc, const void *data)
458736132e4SPhilippe Mathieu-Daudé {
459736132e4SPhilippe Mathieu-Daudé     DeviceClass *dc = DEVICE_CLASS(oc);
460736132e4SPhilippe Mathieu-Daudé     PCA955xClass *pc = PCA955X_CLASS(oc);
461736132e4SPhilippe Mathieu-Daudé 
462e3d08143SPeter Maydell     device_class_set_legacy_reset(dc, pca9552_reset);
4635141d415SCédric Le Goater     dc->vmsd = &pca9552_vmstate;
464736132e4SPhilippe Mathieu-Daudé     pc->max_reg = PCA9552_LS3;
465736132e4SPhilippe Mathieu-Daudé     pc->pin_count = 16;
4665141d415SCédric Le Goater }
4675141d415SCédric Le Goater 
4685141d415SCédric Le Goater static const TypeInfo pca9552_info = {
4695141d415SCédric Le Goater     .name          = TYPE_PCA9552,
470736132e4SPhilippe Mathieu-Daudé     .parent        = TYPE_PCA955X,
4715141d415SCédric Le Goater     .class_init    = pca9552_class_init,
4725141d415SCédric Le Goater };
4735141d415SCédric Le Goater 
pca955x_register_types(void)474ec17228aSPhilippe Mathieu-Daudé static void pca955x_register_types(void)
4755141d415SCédric Le Goater {
476736132e4SPhilippe Mathieu-Daudé     type_register_static(&pca955x_info);
4775141d415SCédric Le Goater     type_register_static(&pca9552_info);
4785141d415SCédric Le Goater }
4795141d415SCédric Le Goater 
480ec17228aSPhilippe Mathieu-Daudé type_init(pca955x_register_types)
481