qemu/hw/misc/pca9552.c
<<
>>
Prefs
   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 *
   8 * This work is licensed under the terms of the GNU GPL, version 2 or
   9 * later. See the COPYING file in the top-level directory.
  10 */
  11
  12#include "qemu/osdep.h"
  13#include "qemu/log.h"
  14#include "qemu/module.h"
  15#include "hw/misc/pca9552.h"
  16#include "hw/misc/pca9552_regs.h"
  17#include "migration/vmstate.h"
  18
  19#define PCA9552_LED_ON   0x0
  20#define PCA9552_LED_OFF  0x1
  21#define PCA9552_LED_PWM0 0x2
  22#define PCA9552_LED_PWM1 0x3
  23
  24static uint8_t pca9552_pin_get_config(PCA9552State *s, int pin)
  25{
  26    uint8_t reg   = PCA9552_LS0 + (pin / 4);
  27    uint8_t shift = (pin % 4) << 1;
  28
  29    return extract32(s->regs[reg], shift, 2);
  30}
  31
  32static void pca9552_update_pin_input(PCA9552State *s)
  33{
  34    int i;
  35
  36    for (i = 0; i < s->nr_leds; i++) {
  37        uint8_t input_reg = PCA9552_INPUT0 + (i / 8);
  38        uint8_t input_shift = (i % 8);
  39        uint8_t config = pca9552_pin_get_config(s, i);
  40
  41        switch (config) {
  42        case PCA9552_LED_ON:
  43            s->regs[input_reg] |= 1 << input_shift;
  44            break;
  45        case PCA9552_LED_OFF:
  46            s->regs[input_reg] &= ~(1 << input_shift);
  47            break;
  48        case PCA9552_LED_PWM0:
  49        case PCA9552_LED_PWM1:
  50            /* TODO */
  51        default:
  52            break;
  53        }
  54    }
  55}
  56
  57static uint8_t pca9552_read(PCA9552State *s, uint8_t reg)
  58{
  59    switch (reg) {
  60    case PCA9552_INPUT0:
  61    case PCA9552_INPUT1:
  62    case PCA9552_PSC0:
  63    case PCA9552_PWM0:
  64    case PCA9552_PSC1:
  65    case PCA9552_PWM1:
  66    case PCA9552_LS0:
  67    case PCA9552_LS1:
  68    case PCA9552_LS2:
  69    case PCA9552_LS3:
  70        return s->regs[reg];
  71    default:
  72        qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected read to register %d\n",
  73                      __func__, reg);
  74        return 0xFF;
  75    }
  76}
  77
  78static void pca9552_write(PCA9552State *s, uint8_t reg, uint8_t data)
  79{
  80    switch (reg) {
  81    case PCA9552_PSC0:
  82    case PCA9552_PWM0:
  83    case PCA9552_PSC1:
  84    case PCA9552_PWM1:
  85        s->regs[reg] = data;
  86        break;
  87
  88    case PCA9552_LS0:
  89    case PCA9552_LS1:
  90    case PCA9552_LS2:
  91    case PCA9552_LS3:
  92        s->regs[reg] = data;
  93        pca9552_update_pin_input(s);
  94        break;
  95
  96    case PCA9552_INPUT0:
  97    case PCA9552_INPUT1:
  98    default:
  99        qemu_log_mask(LOG_GUEST_ERROR, "%s: unexpected write to register %d\n",
 100                      __func__, reg);
 101    }
 102}
 103
 104/*
 105 * When Auto-Increment is on, the register address is incremented
 106 * after each byte is sent to or received by the device. The index
 107 * rollovers to 0 when the maximum register address is reached.
 108 */
 109static void pca9552_autoinc(PCA9552State *s)
 110{
 111    if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) {
 112        uint8_t reg = s->pointer & 0xf;
 113
 114        reg = (reg + 1) % (s->max_reg + 1);
 115        s->pointer = reg | PCA9552_AUTOINC;
 116    }
 117}
 118
 119static uint8_t pca9552_recv(I2CSlave *i2c)
 120{
 121    PCA9552State *s = PCA9552(i2c);
 122    uint8_t ret;
 123
 124    ret = pca9552_read(s, s->pointer & 0xf);
 125
 126    /*
 127     * From the Specs:
 128     *
 129     *     Important Note: When a Read sequence is initiated and the
 130     *     AI bit is set to Logic Level 1, the Read Sequence MUST
 131     *     start by a register different from 0.
 132     *
 133     * I don't know what should be done in this case, so throw an
 134     * error.
 135     */
 136    if (s->pointer == PCA9552_AUTOINC) {
 137        qemu_log_mask(LOG_GUEST_ERROR,
 138                      "%s: Autoincrement read starting with register 0\n",
 139                      __func__);
 140    }
 141
 142    pca9552_autoinc(s);
 143
 144    return ret;
 145}
 146
 147static int pca9552_send(I2CSlave *i2c, uint8_t data)
 148{
 149    PCA9552State *s = PCA9552(i2c);
 150
 151    /* First byte sent by is the register address */
 152    if (s->len == 0) {
 153        s->pointer = data;
 154        s->len++;
 155    } else {
 156        pca9552_write(s, s->pointer & 0xf, data);
 157
 158        pca9552_autoinc(s);
 159    }
 160
 161    return 0;
 162}
 163
 164static int pca9552_event(I2CSlave *i2c, enum i2c_event event)
 165{
 166    PCA9552State *s = PCA9552(i2c);
 167
 168    s->len = 0;
 169    return 0;
 170}
 171
 172static const VMStateDescription pca9552_vmstate = {
 173    .name = "PCA9552",
 174    .version_id = 0,
 175    .minimum_version_id = 0,
 176    .fields = (VMStateField[]) {
 177        VMSTATE_UINT8(len, PCA9552State),
 178        VMSTATE_UINT8(pointer, PCA9552State),
 179        VMSTATE_UINT8_ARRAY(regs, PCA9552State, PCA9552_NR_REGS),
 180        VMSTATE_I2C_SLAVE(i2c, PCA9552State),
 181        VMSTATE_END_OF_LIST()
 182    }
 183};
 184
 185static void pca9552_reset(DeviceState *dev)
 186{
 187    PCA9552State *s = PCA9552(dev);
 188
 189    s->regs[PCA9552_PSC0] = 0xFF;
 190    s->regs[PCA9552_PWM0] = 0x80;
 191    s->regs[PCA9552_PSC1] = 0xFF;
 192    s->regs[PCA9552_PWM1] = 0x80;
 193    s->regs[PCA9552_LS0] = 0x55; /* all OFF */
 194    s->regs[PCA9552_LS1] = 0x55;
 195    s->regs[PCA9552_LS2] = 0x55;
 196    s->regs[PCA9552_LS3] = 0x55;
 197
 198    pca9552_update_pin_input(s);
 199
 200    s->pointer = 0xFF;
 201    s->len = 0;
 202}
 203
 204static void pca9552_initfn(Object *obj)
 205{
 206    PCA9552State *s = PCA9552(obj);
 207
 208    /* If support for the other PCA955X devices are implemented, these
 209     * constant values might be part of class structure describing the
 210     * PCA955X device
 211     */
 212    s->max_reg = PCA9552_LS3;
 213    s->nr_leds = 16;
 214}
 215
 216static void pca9552_class_init(ObjectClass *klass, void *data)
 217{
 218    DeviceClass *dc = DEVICE_CLASS(klass);
 219    I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
 220
 221    k->event = pca9552_event;
 222    k->recv = pca9552_recv;
 223    k->send = pca9552_send;
 224    dc->reset = pca9552_reset;
 225    dc->vmsd = &pca9552_vmstate;
 226}
 227
 228static const TypeInfo pca9552_info = {
 229    .name          = TYPE_PCA9552,
 230    .parent        = TYPE_I2C_SLAVE,
 231    .instance_init = pca9552_initfn,
 232    .instance_size = sizeof(PCA9552State),
 233    .class_init    = pca9552_class_init,
 234};
 235
 236static void pca9552_register_types(void)
 237{
 238    type_register_static(&pca9552_info);
 239}
 240
 241type_init(pca9552_register_types)
 242