1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* NXP PCF8574 8-port I2C GPIO expansion chip.
* Copyright (c) 2024 KNS Group (YADRO).
* Written by Dmitrii Sharikhin <d.sharikhin@yadro.com>
*/
#include "qemu/osdep.h"
#include "hw/i2c/i2c.h"
#include "hw/gpio/pcf8574.h"
#include "hw/irq.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qom/object.h"
/*
* PCF8574 and compatible chips incorporate quasi-bidirectional
* IO. Electrically it means that device sustain pull-up to line
* unless IO port is configured as output _and_ driven low.
*
* IO access is implemented as simple I2C single-byte read
* or write operation. So, to configure line to input user write 1
* to corresponding bit. To configure line to output and drive it low
* user write 0 to corresponding bit.
*
* In essence, user can think of quasi-bidirectional IO as
* open-drain line, except presence of builtin rising edge acceleration
* embedded in PCF8574 IC
*
* PCF8574 has interrupt request line, which is being pulled down when
* port line state differs from last read. Port read operation clears
* state and INT line returns to high state via pullup.
*/
OBJECT_DECLARE_SIMPLE_TYPE(PCF8574State, PCF8574)
#define PORTS_COUNT (8)
struct PCF8574State {
I2CSlave parent_obj;
uint8_t lastrq; /* Last requested state. If changed - assert irq */
uint8_t input; /* external electrical line state */
uint8_t output; /* Pull-up (1) or drive low (0) on bit */
qemu_irq handler[PORTS_COUNT];
qemu_irq intrq; /* External irq request */
};
static void pcf8574_reset(DeviceState *dev)
{
PCF8574State *s = PCF8574(dev);
s->lastrq = MAKE_64BIT_MASK(0, PORTS_COUNT);
s->input = MAKE_64BIT_MASK(0, PORTS_COUNT);
s->output = MAKE_64BIT_MASK(0, PORTS_COUNT);
}
static inline uint8_t pcf8574_line_state(PCF8574State *s)
{
/* we driving line low or external circuit does that */
return s->input & s->output;
}
static uint8_t pcf8574_rx(I2CSlave *i2c)
{
PCF8574State *s = PCF8574(i2c);
uint8_t linestate = pcf8574_line_state(s);
if (s->lastrq != linestate) {
s->lastrq = linestate;
if (s->intrq) {
qemu_set_irq(s->intrq, 1);
}
}
return linestate;
}
static int pcf8574_tx(I2CSlave *i2c, uint8_t data)
{
PCF8574State *s = PCF8574(i2c);
uint8_t prev;
uint8_t diff;
uint8_t actual;
int line = 0;
prev = pcf8574_line_state(s);
s->output = data;
actual = pcf8574_line_state(s);
for (diff = (actual ^ prev); diff; diff &= ~(1 << line)) {
line = ctz32(diff);
if (s->handler[line]) {
qemu_set_irq(s->handler[line], (actual >> line) & 1);
}
}
if (s->intrq) {
qemu_set_irq(s->intrq, actual == s->lastrq);
}
return 0;
}
static const VMStateDescription vmstate_pcf8574 = {
.name = "pcf8574",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
VMSTATE_I2C_SLAVE(parent_obj, PCF8574State),
VMSTATE_UINT8(lastrq, PCF8574State),
VMSTATE_UINT8(input, PCF8574State),
VMSTATE_UINT8(output, PCF8574State),
VMSTATE_END_OF_LIST()
}
};
static void pcf8574_gpio_set(void *opaque, int line, int level)
{
PCF8574State *s = (PCF8574State *) opaque;
assert(line >= 0 && line < ARRAY_SIZE(s->handler));
if (level) {
s->input |= (1 << line);
} else {
s->input &= ~(1 << line);
}
if (pcf8574_line_state(s) != s->lastrq && s->intrq) {
qemu_set_irq(s->intrq, 0);
}
}
static void pcf8574_realize(DeviceState *dev, Error **errp)
{
PCF8574State *s = PCF8574(dev);
qdev_init_gpio_in(dev, pcf8574_gpio_set, ARRAY_SIZE(s->handler));
qdev_init_gpio_out(dev, s->handler, ARRAY_SIZE(s->handler));
qdev_init_gpio_out_named(dev, &s->intrq, "nINT", 1);
}
static void pcf8574_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
k->recv = pcf8574_rx;
k->send = pcf8574_tx;
dc->realize = pcf8574_realize;
device_class_set_legacy_reset(dc, pcf8574_reset);
dc->vmsd = &vmstate_pcf8574;
}
static const TypeInfo pcf8574_infos[] = {
{
.name = TYPE_PCF8574,
.parent = TYPE_I2C_SLAVE,
.instance_size = sizeof(PCF8574State),
.class_init = pcf8574_class_init,
}
};
DEFINE_TYPES(pcf8574_infos);
|