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