qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

pca9552.c (11729B)


      1 /*
      2  * PCA9552 I2C LED blinker
      3  *
      4  *     https://www.nxp.com/docs/en/application-note/AN264.pdf
      5  *
      6  * Copyright (c) 2017-2018, IBM Corporation.
      7  * Copyright (c) 2020 Philippe Mathieu-Daudé
      8  *
      9  * This work is licensed under the terms of the GNU GPL, version 2 or
     10  * later. See the COPYING file in the top-level directory.
     11  */
     12 
     13 #include "qemu/osdep.h"
     14 #include "qemu/log.h"
     15 #include "qemu/module.h"
     16 #include "qemu/bitops.h"
     17 #include "hw/qdev-properties.h"
     18 #include "hw/misc/pca9552.h"
     19 #include "hw/misc/pca9552_regs.h"
     20 #include "hw/irq.h"
     21 #include "migration/vmstate.h"
     22 #include "qapi/error.h"
     23 #include "qapi/visitor.h"
     24 #include "trace.h"
     25 #include "qom/object.h"
     26 
     27 struct PCA955xClass {
     28     /*< private >*/
     29     I2CSlaveClass parent_class;
     30     /*< public >*/
     31 
     32     uint8_t pin_count;
     33     uint8_t max_reg;
     34 };
     35 typedef struct PCA955xClass PCA955xClass;
     36 
     37 DECLARE_CLASS_CHECKERS(PCA955xClass, PCA955X,
     38                        TYPE_PCA955X)
     39 
     40 #define PCA9552_LED_ON   0x0
     41 #define PCA9552_LED_OFF  0x1
     42 #define PCA9552_LED_PWM0 0x2
     43 #define PCA9552_LED_PWM1 0x3
     44 
     45 static const char *led_state[] = {"on", "off", "pwm0", "pwm1"};
     46 
     47 static uint8_t pca955x_pin_get_config(PCA955xState *s, int pin)
     48 {
     49     uint8_t reg   = PCA9552_LS0 + (pin / 4);
     50     uint8_t shift = (pin % 4) << 1;
     51 
     52     return extract32(s->regs[reg], shift, 2);
     53 }
     54 
     55 /* Return INPUT status (bit #N belongs to GPIO #N) */
     56 static uint16_t pca955x_pins_get_status(PCA955xState *s)
     57 {
     58     return (s->regs[PCA9552_INPUT1] << 8) | s->regs[PCA9552_INPUT0];
     59 }
     60 
     61 static void pca955x_display_pins_status(PCA955xState *s,
     62                                         uint16_t previous_pins_status)
     63 {
     64     PCA955xClass *k = PCA955X_GET_CLASS(s);
     65     uint16_t pins_status, pins_changed;
     66     int i;
     67 
     68     pins_status = pca955x_pins_get_status(s);
     69     pins_changed = previous_pins_status ^ pins_status;
     70     if (!pins_changed) {
     71         return;
     72     }
     73     if (trace_event_get_state_backends(TRACE_PCA955X_GPIO_STATUS)) {
     74         char *buf = g_newa(char, k->pin_count + 1);
     75 
     76         for (i = 0; i < k->pin_count; i++) {
     77             if (extract32(pins_status, i, 1)) {
     78                 buf[i] = '*';
     79             } else {
     80                 buf[i] = '.';
     81             }
     82         }
     83         buf[i] = '\0';
     84         trace_pca955x_gpio_status(s->description, buf);
     85     }
     86     if (trace_event_get_state_backends(TRACE_PCA955X_GPIO_CHANGE)) {
     87         for (i = 0; i < k->pin_count; i++) {
     88             if (extract32(pins_changed, i, 1)) {
     89                 unsigned new_state = extract32(pins_status, i, 1);
     90 
     91                 /*
     92                  * We display the state using the PCA logic ("active-high").
     93                  * This is not the state of the LED, which signal might be
     94                  * wired "active-low" on the board.
     95                  */
     96                 trace_pca955x_gpio_change(s->description, i,
     97                                           !new_state, new_state);
     98             }
     99         }
    100     }
    101 }
    102 
    103 static void pca955x_update_pin_input(PCA955xState *s)
    104 {
    105     PCA955xClass *k = PCA955X_GET_CLASS(s);
    106     int i;
    107 
    108     for (i = 0; i < k->pin_count; i++) {
    109         uint8_t input_reg = PCA9552_INPUT0 + (i / 8);
    110         uint8_t input_shift = (i % 8);
    111         uint8_t config = pca955x_pin_get_config(s, i);
    112 
    113         switch (config) {
    114         case PCA9552_LED_ON:
    115             qemu_set_irq(s->gpio[i], 1);
    116             s->regs[input_reg] |= 1 << input_shift;
    117             break;
    118         case PCA9552_LED_OFF:
    119             qemu_set_irq(s->gpio[i], 0);
    120             s->regs[input_reg] &= ~(1 << input_shift);
    121             break;
    122         case PCA9552_LED_PWM0:
    123         case PCA9552_LED_PWM1:
    124             /* TODO */
    125         default:
    126             break;
    127         }
    128     }
    129 }
    130 
    131 static uint8_t pca955x_read(PCA955xState *s, uint8_t reg)
    132 {
    133     switch (reg) {
    134     case PCA9552_INPUT0:
    135     case PCA9552_INPUT1:
    136     case PCA9552_PSC0:
    137     case PCA9552_PWM0:
    138     case PCA9552_PSC1:
    139     case PCA9552_PWM1:
    140     case PCA9552_LS0:
    141     case PCA9552_LS1:
    142     case PCA9552_LS2:
    143     case PCA9552_LS3:
    144         return s->regs[reg];
    145     default:
    146         qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n",
    147                       __func__, reg);
    148         return 0xFF;
    149     }
    150 }
    151 
    152 static void pca955x_write(PCA955xState *s, uint8_t reg, uint8_t data)
    153 {
    154     uint16_t pins_status;
    155 
    156     switch (reg) {
    157     case PCA9552_PSC0:
    158     case PCA9552_PWM0:
    159     case PCA9552_PSC1:
    160     case PCA9552_PWM1:
    161         s->regs[reg] = data;
    162         break;
    163 
    164     case PCA9552_LS0:
    165     case PCA9552_LS1:
    166     case PCA9552_LS2:
    167     case PCA9552_LS3:
    168         pins_status = pca955x_pins_get_status(s);
    169         s->regs[reg] = data;
    170         pca955x_update_pin_input(s);
    171         pca955x_display_pins_status(s, pins_status);
    172         break;
    173 
    174     case PCA9552_INPUT0:
    175     case PCA9552_INPUT1:
    176     default:
    177         qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n",
    178                       __func__, reg);
    179     }
    180 }
    181 
    182 /*
    183  * When Auto-Increment is on, the register address is incremented
    184  * after each byte is sent to or received by the device. The index
    185  * rollovers to 0 when the maximum register address is reached.
    186  */
    187 static void pca955x_autoinc(PCA955xState *s)
    188 {
    189     PCA955xClass *k = PCA955X_GET_CLASS(s);
    190 
    191     if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) {
    192         uint8_t reg = s->pointer & 0xf;
    193 
    194         reg = (reg + 1) % (k->max_reg + 1);
    195         s->pointer = reg | PCA9552_AUTOINC;
    196     }
    197 }
    198 
    199 static uint8_t pca955x_recv(I2CSlave *i2c)
    200 {
    201     PCA955xState *s = PCA955X(i2c);
    202     uint8_t ret;
    203 
    204     ret = pca955x_read(s, s->pointer & 0xf);
    205 
    206     /*
    207      * From the Specs:
    208      *
    209      *     Important Note: When a Read sequence is initiated and the
    210      *     AI bit is set to Logic Level 1, the Read Sequence MUST
    211      *     start by a register different from 0.
    212      *
    213      * I don't know what should be done in this case, so throw an
    214      * error.
    215      */
    216     if (s->pointer == PCA9552_AUTOINC) {
    217         qemu_log_mask(LOG_GUEST_ERROR,
    218                       "%s: Autoincrement read starting with register 0\n",
    219                       __func__);
    220     }
    221 
    222     pca955x_autoinc(s);
    223 
    224     return ret;
    225 }
    226 
    227 static int pca955x_send(I2CSlave *i2c, uint8_t data)
    228 {
    229     PCA955xState *s = PCA955X(i2c);
    230 
    231     /* First byte sent by is the register address */
    232     if (s->len == 0) {
    233         s->pointer = data;
    234         s->len++;
    235     } else {
    236         pca955x_write(s, s->pointer & 0xf, data);
    237 
    238         pca955x_autoinc(s);
    239     }
    240 
    241     return 0;
    242 }
    243 
    244 static int pca955x_event(I2CSlave *i2c, enum i2c_event event)
    245 {
    246     PCA955xState *s = PCA955X(i2c);
    247 
    248     s->len = 0;
    249     return 0;
    250 }
    251 
    252 static void pca955x_get_led(Object *obj, Visitor *v, const char *name,
    253                             void *opaque, Error **errp)
    254 {
    255     PCA955xClass *k = PCA955X_GET_CLASS(obj);
    256     PCA955xState *s = PCA955X(obj);
    257     int led, rc, reg;
    258     uint8_t state;
    259 
    260     rc = sscanf(name, "led%2d", &led);
    261     if (rc != 1) {
    262         error_setg(errp, "%s: error reading %s", __func__, name);
    263         return;
    264     }
    265     if (led < 0 || led > k->pin_count) {
    266         error_setg(errp, "%s invalid led %s", __func__, name);
    267         return;
    268     }
    269     /*
    270      * Get the LSx register as the qom interface should expose the device
    271      * state, not the modeled 'input line' behaviour which would come from
    272      * reading the INPUTx reg
    273      */
    274     reg = PCA9552_LS0 + led / 4;
    275     state = (pca955x_read(s, reg) >> ((led % 4) * 2)) & 0x3;
    276     visit_type_str(v, name, (char **)&led_state[state], errp);
    277 }
    278 
    279 /*
    280  * Return an LED selector register value based on an existing one, with
    281  * the appropriate 2-bit state value set for the given LED number (0-3).
    282  */
    283 static inline uint8_t pca955x_ledsel(uint8_t oldval, int led_num, int state)
    284 {
    285         return (oldval & (~(0x3 << (led_num << 1)))) |
    286                 ((state & 0x3) << (led_num << 1));
    287 }
    288 
    289 static void pca955x_set_led(Object *obj, Visitor *v, const char *name,
    290                             void *opaque, Error **errp)
    291 {
    292     PCA955xClass *k = PCA955X_GET_CLASS(obj);
    293     PCA955xState *s = PCA955X(obj);
    294     int led, rc, reg, val;
    295     uint8_t state;
    296     char *state_str;
    297 
    298     if (!visit_type_str(v, name, &state_str, errp)) {
    299         return;
    300     }
    301     rc = sscanf(name, "led%2d", &led);
    302     if (rc != 1) {
    303         error_setg(errp, "%s: error reading %s", __func__, name);
    304         return;
    305     }
    306     if (led < 0 || led > k->pin_count) {
    307         error_setg(errp, "%s invalid led %s", __func__, name);
    308         return;
    309     }
    310 
    311     for (state = 0; state < ARRAY_SIZE(led_state); state++) {
    312         if (!strcmp(state_str, led_state[state])) {
    313             break;
    314         }
    315     }
    316     if (state >= ARRAY_SIZE(led_state)) {
    317         error_setg(errp, "%s invalid led state %s", __func__, state_str);
    318         return;
    319     }
    320 
    321     reg = PCA9552_LS0 + led / 4;
    322     val = pca955x_read(s, reg);
    323     val = pca955x_ledsel(val, led % 4, state);
    324     pca955x_write(s, reg, val);
    325 }
    326 
    327 static const VMStateDescription pca9552_vmstate = {
    328     .name = "PCA9552",
    329     .version_id = 0,
    330     .minimum_version_id = 0,
    331     .fields = (VMStateField[]) {
    332         VMSTATE_UINT8(len, PCA955xState),
    333         VMSTATE_UINT8(pointer, PCA955xState),
    334         VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS),
    335         VMSTATE_I2C_SLAVE(i2c, PCA955xState),
    336         VMSTATE_END_OF_LIST()
    337     }
    338 };
    339 
    340 static void pca9552_reset(DeviceState *dev)
    341 {
    342     PCA955xState *s = PCA955X(dev);
    343 
    344     s->regs[PCA9552_PSC0] = 0xFF;
    345     s->regs[PCA9552_PWM0] = 0x80;
    346     s->regs[PCA9552_PSC1] = 0xFF;
    347     s->regs[PCA9552_PWM1] = 0x80;
    348     s->regs[PCA9552_LS0] = 0x55; /* all OFF */
    349     s->regs[PCA9552_LS1] = 0x55;
    350     s->regs[PCA9552_LS2] = 0x55;
    351     s->regs[PCA9552_LS3] = 0x55;
    352 
    353     pca955x_update_pin_input(s);
    354 
    355     s->pointer = 0xFF;
    356     s->len = 0;
    357 }
    358 
    359 static void pca955x_initfn(Object *obj)
    360 {
    361     PCA955xClass *k = PCA955X_GET_CLASS(obj);
    362     int led;
    363 
    364     assert(k->pin_count <= PCA955X_PIN_COUNT_MAX);
    365     for (led = 0; led < k->pin_count; led++) {
    366         char *name;
    367 
    368         name = g_strdup_printf("led%d", led);
    369         object_property_add(obj, name, "bool", pca955x_get_led, pca955x_set_led,
    370                             NULL, NULL);
    371         g_free(name);
    372     }
    373 }
    374 
    375 static void pca955x_realize(DeviceState *dev, Error **errp)
    376 {
    377     PCA955xClass *k = PCA955X_GET_CLASS(dev);
    378     PCA955xState *s = PCA955X(dev);
    379 
    380     if (!s->description) {
    381         s->description = g_strdup("pca-unspecified");
    382     }
    383 
    384     qdev_init_gpio_out(dev, s->gpio, k->pin_count);
    385 }
    386 
    387 static Property pca955x_properties[] = {
    388     DEFINE_PROP_STRING("description", PCA955xState, description),
    389     DEFINE_PROP_END_OF_LIST(),
    390 };
    391 
    392 static void pca955x_class_init(ObjectClass *klass, void *data)
    393 {
    394     DeviceClass *dc = DEVICE_CLASS(klass);
    395     I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
    396 
    397     k->event = pca955x_event;
    398     k->recv = pca955x_recv;
    399     k->send = pca955x_send;
    400     dc->realize = pca955x_realize;
    401     device_class_set_props(dc, pca955x_properties);
    402 }
    403 
    404 static const TypeInfo pca955x_info = {
    405     .name          = TYPE_PCA955X,
    406     .parent        = TYPE_I2C_SLAVE,
    407     .instance_init = pca955x_initfn,
    408     .instance_size = sizeof(PCA955xState),
    409     .class_init    = pca955x_class_init,
    410     .class_size    = sizeof(PCA955xClass),
    411     .abstract      = true,
    412 };
    413 
    414 static void pca9552_class_init(ObjectClass *oc, void *data)
    415 {
    416     DeviceClass *dc = DEVICE_CLASS(oc);
    417     PCA955xClass *pc = PCA955X_CLASS(oc);
    418 
    419     dc->reset = pca9552_reset;
    420     dc->vmsd = &pca9552_vmstate;
    421     pc->max_reg = PCA9552_LS3;
    422     pc->pin_count = 16;
    423 }
    424 
    425 static const TypeInfo pca9552_info = {
    426     .name          = TYPE_PCA9552,
    427     .parent        = TYPE_PCA955X,
    428     .class_init    = pca9552_class_init,
    429 };
    430 
    431 static void pca955x_register_types(void)
    432 {
    433     type_register_static(&pca955x_info);
    434     type_register_static(&pca9552_info);
    435 }
    436 
    437 type_init(pca955x_register_types)