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 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 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 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 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 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 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 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 device_class_set_legacy_reset(dc, 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