xref: /qemu/hw/gpio/pca9552.c (revision ec17228a25e7fd46287842322f8dc4923eccca92)
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.
75141d415SCédric Le Goater  *
85141d415SCédric Le Goater  * This work is licensed under the terms of the GNU GPL, version 2 or
95141d415SCédric Le Goater  * later. See the COPYING file in the top-level directory.
105141d415SCédric Le Goater  */
115141d415SCédric Le Goater 
125141d415SCédric Le Goater #include "qemu/osdep.h"
135141d415SCédric Le Goater #include "qemu/log.h"
140b8fa32fSMarkus Armbruster #include "qemu/module.h"
155141d415SCédric Le Goater #include "hw/misc/pca9552.h"
165141d415SCédric Le Goater #include "hw/misc/pca9552_regs.h"
17d6454270SMarkus Armbruster #include "migration/vmstate.h"
18a90d8f84SJoel Stanley #include "qapi/error.h"
19a90d8f84SJoel Stanley #include "qapi/visitor.h"
205141d415SCédric Le Goater 
215141d415SCédric Le Goater #define PCA9552_LED_ON   0x0
225141d415SCédric Le Goater #define PCA9552_LED_OFF  0x1
235141d415SCédric Le Goater #define PCA9552_LED_PWM0 0x2
245141d415SCédric Le Goater #define PCA9552_LED_PWM1 0x3
255141d415SCédric Le Goater 
26a90d8f84SJoel Stanley static const char *led_state[] = {"on", "off", "pwm0", "pwm1"};
27a90d8f84SJoel Stanley 
28*ec17228aSPhilippe Mathieu-Daudé static uint8_t pca955x_pin_get_config(PCA955xState *s, int pin)
295141d415SCédric Le Goater {
305141d415SCédric Le Goater     uint8_t reg   = PCA9552_LS0 + (pin / 4);
315141d415SCédric Le Goater     uint8_t shift = (pin % 4) << 1;
325141d415SCédric Le Goater 
335141d415SCédric Le Goater     return extract32(s->regs[reg], shift, 2);
345141d415SCédric Le Goater }
355141d415SCédric Le Goater 
36*ec17228aSPhilippe Mathieu-Daudé static void pca955x_update_pin_input(PCA955xState *s)
375141d415SCédric Le Goater {
385141d415SCédric Le Goater     int i;
395141d415SCédric Le Goater 
408208335bSPhilippe Mathieu-Daudé     for (i = 0; i < s->pin_count; i++) {
415141d415SCédric Le Goater         uint8_t input_reg = PCA9552_INPUT0 + (i / 8);
425141d415SCédric Le Goater         uint8_t input_shift = (i % 8);
43*ec17228aSPhilippe Mathieu-Daudé         uint8_t config = pca955x_pin_get_config(s, i);
445141d415SCédric Le Goater 
455141d415SCédric Le Goater         switch (config) {
465141d415SCédric Le Goater         case PCA9552_LED_ON:
475141d415SCédric Le Goater             s->regs[input_reg] |= 1 << input_shift;
485141d415SCédric Le Goater             break;
495141d415SCédric Le Goater         case PCA9552_LED_OFF:
505141d415SCédric Le Goater             s->regs[input_reg] &= ~(1 << input_shift);
515141d415SCédric Le Goater             break;
525141d415SCédric Le Goater         case PCA9552_LED_PWM0:
535141d415SCédric Le Goater         case PCA9552_LED_PWM1:
545141d415SCédric Le Goater             /* TODO */
555141d415SCédric Le Goater         default:
565141d415SCédric Le Goater             break;
575141d415SCédric Le Goater         }
585141d415SCédric Le Goater     }
595141d415SCédric Le Goater }
605141d415SCédric Le Goater 
61*ec17228aSPhilippe Mathieu-Daudé static uint8_t pca955x_read(PCA955xState *s, uint8_t reg)
625141d415SCédric Le Goater {
635141d415SCédric Le Goater     switch (reg) {
645141d415SCédric Le Goater     case PCA9552_INPUT0:
655141d415SCédric Le Goater     case PCA9552_INPUT1:
665141d415SCédric Le Goater     case PCA9552_PSC0:
675141d415SCédric Le Goater     case PCA9552_PWM0:
685141d415SCédric Le Goater     case PCA9552_PSC1:
695141d415SCédric Le Goater     case PCA9552_PWM1:
705141d415SCédric Le Goater     case PCA9552_LS0:
715141d415SCédric Le Goater     case PCA9552_LS1:
725141d415SCédric Le Goater     case PCA9552_LS2:
735141d415SCédric Le Goater     case PCA9552_LS3:
745141d415SCédric Le Goater         return s->regs[reg];
755141d415SCédric Le Goater     default:
765141d415SCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n",
775141d415SCédric Le Goater                       __func__, reg);
785141d415SCédric Le Goater         return 0xFF;
795141d415SCédric Le Goater     }
805141d415SCédric Le Goater }
815141d415SCédric Le Goater 
82*ec17228aSPhilippe Mathieu-Daudé static void pca955x_write(PCA955xState *s, uint8_t reg, uint8_t data)
835141d415SCédric Le Goater {
845141d415SCédric Le Goater     switch (reg) {
855141d415SCédric Le Goater     case PCA9552_PSC0:
865141d415SCédric Le Goater     case PCA9552_PWM0:
875141d415SCédric Le Goater     case PCA9552_PSC1:
885141d415SCédric Le Goater     case PCA9552_PWM1:
895141d415SCédric Le Goater         s->regs[reg] = data;
905141d415SCédric Le Goater         break;
915141d415SCédric Le Goater 
925141d415SCédric Le Goater     case PCA9552_LS0:
935141d415SCédric Le Goater     case PCA9552_LS1:
945141d415SCédric Le Goater     case PCA9552_LS2:
955141d415SCédric Le Goater     case PCA9552_LS3:
965141d415SCédric Le Goater         s->regs[reg] = data;
97*ec17228aSPhilippe Mathieu-Daudé         pca955x_update_pin_input(s);
985141d415SCédric Le Goater         break;
995141d415SCédric Le Goater 
1005141d415SCédric Le Goater     case PCA9552_INPUT0:
1015141d415SCédric Le Goater     case PCA9552_INPUT1:
1025141d415SCédric Le Goater     default:
1035141d415SCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n",
1045141d415SCédric Le Goater                       __func__, reg);
1055141d415SCédric Le Goater     }
1065141d415SCédric Le Goater }
1075141d415SCédric Le Goater 
1085141d415SCédric Le Goater /*
1095141d415SCédric Le Goater  * When Auto-Increment is on, the register address is incremented
1105141d415SCédric Le Goater  * after each byte is sent to or received by the device. The index
1115141d415SCédric Le Goater  * rollovers to 0 when the maximum register address is reached.
1125141d415SCédric Le Goater  */
113*ec17228aSPhilippe Mathieu-Daudé static void pca955x_autoinc(PCA955xState *s)
1145141d415SCédric Le Goater {
1155141d415SCédric Le Goater     if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) {
1165141d415SCédric Le Goater         uint8_t reg = s->pointer & 0xf;
1175141d415SCédric Le Goater 
1185141d415SCédric Le Goater         reg = (reg + 1) % (s->max_reg + 1);
1195141d415SCédric Le Goater         s->pointer = reg | PCA9552_AUTOINC;
1205141d415SCédric Le Goater     }
1215141d415SCédric Le Goater }
1225141d415SCédric Le Goater 
123*ec17228aSPhilippe Mathieu-Daudé static uint8_t pca955x_recv(I2CSlave *i2c)
1245141d415SCédric Le Goater {
125*ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(i2c);
1265141d415SCédric Le Goater     uint8_t ret;
1275141d415SCédric Le Goater 
128*ec17228aSPhilippe Mathieu-Daudé     ret = pca955x_read(s, s->pointer & 0xf);
1295141d415SCédric Le Goater 
1305141d415SCédric Le Goater     /*
1315141d415SCédric Le Goater      * From the Specs:
1325141d415SCédric Le Goater      *
1335141d415SCédric Le Goater      *     Important Note: When a Read sequence is initiated and the
1345141d415SCédric Le Goater      *     AI bit is set to Logic Level 1, the Read Sequence MUST
1355141d415SCédric Le Goater      *     start by a register different from 0.
1365141d415SCédric Le Goater      *
1375141d415SCédric Le Goater      * I don't know what should be done in this case, so throw an
1385141d415SCédric Le Goater      * error.
1395141d415SCédric Le Goater      */
1405141d415SCédric Le Goater     if (s->pointer == PCA9552_AUTOINC) {
1415141d415SCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR,
1425141d415SCédric Le Goater                       "%s: Autoincrement read starting with register 0\n",
1435141d415SCédric Le Goater                       __func__);
1445141d415SCédric Le Goater     }
1455141d415SCédric Le Goater 
146*ec17228aSPhilippe Mathieu-Daudé     pca955x_autoinc(s);
1475141d415SCédric Le Goater 
1485141d415SCédric Le Goater     return ret;
1495141d415SCédric Le Goater }
1505141d415SCédric Le Goater 
151*ec17228aSPhilippe Mathieu-Daudé static int pca955x_send(I2CSlave *i2c, uint8_t data)
1525141d415SCédric Le Goater {
153*ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(i2c);
1545141d415SCédric Le Goater 
1555141d415SCédric Le Goater     /* First byte sent by is the register address */
1565141d415SCédric Le Goater     if (s->len == 0) {
1575141d415SCédric Le Goater         s->pointer = data;
1585141d415SCédric Le Goater         s->len++;
1595141d415SCédric Le Goater     } else {
160*ec17228aSPhilippe Mathieu-Daudé         pca955x_write(s, s->pointer & 0xf, data);
1615141d415SCédric Le Goater 
162*ec17228aSPhilippe Mathieu-Daudé         pca955x_autoinc(s);
1635141d415SCédric Le Goater     }
1645141d415SCédric Le Goater 
1655141d415SCédric Le Goater     return 0;
1665141d415SCédric Le Goater }
1675141d415SCédric Le Goater 
168*ec17228aSPhilippe Mathieu-Daudé static int pca955x_event(I2CSlave *i2c, enum i2c_event event)
1695141d415SCédric Le Goater {
170*ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(i2c);
1715141d415SCédric Le Goater 
1725141d415SCédric Le Goater     s->len = 0;
1735141d415SCédric Le Goater     return 0;
1745141d415SCédric Le Goater }
1755141d415SCédric Le Goater 
176*ec17228aSPhilippe Mathieu-Daudé static void pca955x_get_led(Object *obj, Visitor *v, const char *name,
177a90d8f84SJoel Stanley                             void *opaque, Error **errp)
178a90d8f84SJoel Stanley {
179*ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(obj);
180a90d8f84SJoel Stanley     int led, rc, reg;
181a90d8f84SJoel Stanley     uint8_t state;
182a90d8f84SJoel Stanley 
183a90d8f84SJoel Stanley     rc = sscanf(name, "led%2d", &led);
184a90d8f84SJoel Stanley     if (rc != 1) {
185a90d8f84SJoel Stanley         error_setg(errp, "%s: error reading %s", __func__, name);
186a90d8f84SJoel Stanley         return;
187a90d8f84SJoel Stanley     }
1888208335bSPhilippe Mathieu-Daudé     if (led < 0 || led > s->pin_count) {
189a90d8f84SJoel Stanley         error_setg(errp, "%s invalid led %s", __func__, name);
190a90d8f84SJoel Stanley         return;
191a90d8f84SJoel Stanley     }
192a90d8f84SJoel Stanley     /*
193a90d8f84SJoel Stanley      * Get the LSx register as the qom interface should expose the device
194a90d8f84SJoel Stanley      * state, not the modeled 'input line' behaviour which would come from
195a90d8f84SJoel Stanley      * reading the INPUTx reg
196a90d8f84SJoel Stanley      */
197a90d8f84SJoel Stanley     reg = PCA9552_LS0 + led / 4;
198*ec17228aSPhilippe Mathieu-Daudé     state = (pca955x_read(s, reg) >> (led % 8)) & 0x3;
199a90d8f84SJoel Stanley     visit_type_str(v, name, (char **)&led_state[state], errp);
200a90d8f84SJoel Stanley }
201a90d8f84SJoel Stanley 
202a90d8f84SJoel Stanley /*
203a90d8f84SJoel Stanley  * Return an LED selector register value based on an existing one, with
204a90d8f84SJoel Stanley  * the appropriate 2-bit state value set for the given LED number (0-3).
205a90d8f84SJoel Stanley  */
206a90d8f84SJoel Stanley static inline uint8_t pca955x_ledsel(uint8_t oldval, int led_num, int state)
207a90d8f84SJoel Stanley {
208a90d8f84SJoel Stanley         return (oldval & (~(0x3 << (led_num << 1)))) |
209a90d8f84SJoel Stanley                 ((state & 0x3) << (led_num << 1));
210a90d8f84SJoel Stanley }
211a90d8f84SJoel Stanley 
212*ec17228aSPhilippe Mathieu-Daudé static void pca955x_set_led(Object *obj, Visitor *v, const char *name,
213a90d8f84SJoel Stanley                             void *opaque, Error **errp)
214a90d8f84SJoel Stanley {
215*ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(obj);
216a90d8f84SJoel Stanley     Error *local_err = NULL;
217a90d8f84SJoel Stanley     int led, rc, reg, val;
218a90d8f84SJoel Stanley     uint8_t state;
219a90d8f84SJoel Stanley     char *state_str;
220a90d8f84SJoel Stanley 
221a90d8f84SJoel Stanley     visit_type_str(v, name, &state_str, &local_err);
222a90d8f84SJoel Stanley     if (local_err) {
223a90d8f84SJoel Stanley         error_propagate(errp, local_err);
224a90d8f84SJoel Stanley         return;
225a90d8f84SJoel Stanley     }
226a90d8f84SJoel Stanley     rc = sscanf(name, "led%2d", &led);
227a90d8f84SJoel Stanley     if (rc != 1) {
228a90d8f84SJoel Stanley         error_setg(errp, "%s: error reading %s", __func__, name);
229a90d8f84SJoel Stanley         return;
230a90d8f84SJoel Stanley     }
2318208335bSPhilippe Mathieu-Daudé     if (led < 0 || led > s->pin_count) {
232a90d8f84SJoel Stanley         error_setg(errp, "%s invalid led %s", __func__, name);
233a90d8f84SJoel Stanley         return;
234a90d8f84SJoel Stanley     }
235a90d8f84SJoel Stanley 
236a90d8f84SJoel Stanley     for (state = 0; state < ARRAY_SIZE(led_state); state++) {
237a90d8f84SJoel Stanley         if (!strcmp(state_str, led_state[state])) {
238a90d8f84SJoel Stanley             break;
239a90d8f84SJoel Stanley         }
240a90d8f84SJoel Stanley     }
241a90d8f84SJoel Stanley     if (state >= ARRAY_SIZE(led_state)) {
242a90d8f84SJoel Stanley         error_setg(errp, "%s invalid led state %s", __func__, state_str);
243a90d8f84SJoel Stanley         return;
244a90d8f84SJoel Stanley     }
245a90d8f84SJoel Stanley 
246a90d8f84SJoel Stanley     reg = PCA9552_LS0 + led / 4;
247*ec17228aSPhilippe Mathieu-Daudé     val = pca955x_read(s, reg);
248a90d8f84SJoel Stanley     val = pca955x_ledsel(val, led % 4, state);
249*ec17228aSPhilippe Mathieu-Daudé     pca955x_write(s, reg, val);
250a90d8f84SJoel Stanley }
251a90d8f84SJoel Stanley 
2525141d415SCédric Le Goater static const VMStateDescription pca9552_vmstate = {
2535141d415SCédric Le Goater     .name = "PCA9552",
2545141d415SCédric Le Goater     .version_id = 0,
2555141d415SCédric Le Goater     .minimum_version_id = 0,
2565141d415SCédric Le Goater     .fields = (VMStateField[]) {
257*ec17228aSPhilippe Mathieu-Daudé         VMSTATE_UINT8(len, PCA955xState),
258*ec17228aSPhilippe Mathieu-Daudé         VMSTATE_UINT8(pointer, PCA955xState),
259*ec17228aSPhilippe Mathieu-Daudé         VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS),
260*ec17228aSPhilippe Mathieu-Daudé         VMSTATE_I2C_SLAVE(i2c, PCA955xState),
2615141d415SCédric Le Goater         VMSTATE_END_OF_LIST()
2625141d415SCédric Le Goater     }
2635141d415SCédric Le Goater };
2645141d415SCédric Le Goater 
2655141d415SCédric Le Goater static void pca9552_reset(DeviceState *dev)
2665141d415SCédric Le Goater {
267*ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(dev);
2685141d415SCédric Le Goater 
2695141d415SCédric Le Goater     s->regs[PCA9552_PSC0] = 0xFF;
2705141d415SCédric Le Goater     s->regs[PCA9552_PWM0] = 0x80;
2715141d415SCédric Le Goater     s->regs[PCA9552_PSC1] = 0xFF;
2725141d415SCédric Le Goater     s->regs[PCA9552_PWM1] = 0x80;
2735141d415SCédric Le Goater     s->regs[PCA9552_LS0] = 0x55; /* all OFF */
2745141d415SCédric Le Goater     s->regs[PCA9552_LS1] = 0x55;
2755141d415SCédric Le Goater     s->regs[PCA9552_LS2] = 0x55;
2765141d415SCédric Le Goater     s->regs[PCA9552_LS3] = 0x55;
2775141d415SCédric Le Goater 
278*ec17228aSPhilippe Mathieu-Daudé     pca955x_update_pin_input(s);
2795141d415SCédric Le Goater 
2805141d415SCédric Le Goater     s->pointer = 0xFF;
2815141d415SCédric Le Goater     s->len = 0;
2825141d415SCédric Le Goater }
2835141d415SCédric Le Goater 
284*ec17228aSPhilippe Mathieu-Daudé static void pca955x_initfn(Object *obj)
2855141d415SCédric Le Goater {
286*ec17228aSPhilippe Mathieu-Daudé     PCA955xState *s = PCA955X(obj);
287a90d8f84SJoel Stanley     int led;
2885141d415SCédric Le Goater 
2895141d415SCédric Le Goater     /* If support for the other PCA955X devices are implemented, these
2905141d415SCédric Le Goater      * constant values might be part of class structure describing the
2915141d415SCédric Le Goater      * PCA955X device
2925141d415SCédric Le Goater      */
2935141d415SCédric Le Goater     s->max_reg = PCA9552_LS3;
2948208335bSPhilippe Mathieu-Daudé     s->pin_count = 16;
295a90d8f84SJoel Stanley 
2968208335bSPhilippe Mathieu-Daudé     for (led = 0; led < s->pin_count; led++) {
297a90d8f84SJoel Stanley         char *name;
298a90d8f84SJoel Stanley 
299a90d8f84SJoel Stanley         name = g_strdup_printf("led%d", led);
300*ec17228aSPhilippe Mathieu-Daudé         object_property_add(obj, name, "bool", pca955x_get_led, pca955x_set_led,
301d2623129SMarkus Armbruster                             NULL, NULL);
302a90d8f84SJoel Stanley         g_free(name);
303a90d8f84SJoel Stanley     }
3045141d415SCédric Le Goater }
3055141d415SCédric Le Goater 
3065141d415SCédric Le Goater static void pca9552_class_init(ObjectClass *klass, void *data)
3075141d415SCédric Le Goater {
3085141d415SCédric Le Goater     DeviceClass *dc = DEVICE_CLASS(klass);
3095141d415SCédric Le Goater     I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
3105141d415SCédric Le Goater 
311*ec17228aSPhilippe Mathieu-Daudé     k->event = pca955x_event;
312*ec17228aSPhilippe Mathieu-Daudé     k->recv = pca955x_recv;
313*ec17228aSPhilippe Mathieu-Daudé     k->send = pca955x_send;
3145141d415SCédric Le Goater     dc->reset = pca9552_reset;
3155141d415SCédric Le Goater     dc->vmsd = &pca9552_vmstate;
3165141d415SCédric Le Goater }
3175141d415SCédric Le Goater 
3185141d415SCédric Le Goater static const TypeInfo pca9552_info = {
3195141d415SCédric Le Goater     .name          = TYPE_PCA9552,
3205141d415SCédric Le Goater     .parent        = TYPE_I2C_SLAVE,
321*ec17228aSPhilippe Mathieu-Daudé     .instance_init = pca955x_initfn,
322*ec17228aSPhilippe Mathieu-Daudé     .instance_size = sizeof(PCA955xState),
3235141d415SCédric Le Goater     .class_init    = pca9552_class_init,
3245141d415SCédric Le Goater };
3255141d415SCédric Le Goater 
326*ec17228aSPhilippe Mathieu-Daudé static void pca955x_register_types(void)
3275141d415SCédric Le Goater {
3285141d415SCédric Le Goater     type_register_static(&pca9552_info);
3295141d415SCédric Le Goater }
3305141d415SCédric Le Goater 
331*ec17228aSPhilippe Mathieu-Daudé type_init(pca955x_register_types)
332