@@ -16,3 +16,8 @@ config GPIO_PWR
config SIFIVE_GPIO
bool
+
+config PCA_I2C_GPIO
+ bool
+ depends on I2C
+ default y if I2C_DEVICES
@@ -12,3 +12,4 @@ softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_gpio.c'))
softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_gpio.c'))
softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_gpio.c'))
softmmu_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c'))
+softmmu_ss.add(when: 'CONFIG_PCA_I2C_GPIO', if_true: files('pca_i2c_gpio.c'))
new file mode 100644
@@ -0,0 +1,392 @@
+/*
+ * NXP PCA I2C GPIO Expanders
+ *
+ * Low-voltage translating 16-bit I2C/SMBus GPIO expander with interrupt output,
+ * reset, and configuration registers
+ *
+ * Datasheet: https://www.nxp.com/docs/en/data-sheet/PCA6416A.pdf
+ *
+ * Copyright 2023 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * To assert some input pins before boot, use the following in the board file of
+ * the machine:
+ * object_property_set_uint(Object *obj, const char *name,
+ * uint64_t value, Error **errp);
+ * specifying name as "gpio_config" and the value as a bitfield of the inputs
+ * e.g. for the pca6416, a value of 0xFFF0, configures pins 0-3 as outputs and
+ * 4-15 as inputs.
+ * Then using name "gpio_input" with value "0x0F00" would raise GPIOs 8-11.
+ *
+ * This value can also be set at runtime through qmp externally, or by
+ * writing to the config register using i2c. The guest driver should generally
+ * control the config register, but exposing it via qmp allows external testing.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/pca_i2c_gpio.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/bitops.h"
+#include "trace.h"
+
+/*
+ * compare new_output to curr_output and update irq to match new_output
+ *
+ * The Input port registers (registers 0 and 1) reflect the incoming logic
+ * levels of the pins, regardless of whether the pin is defined as an input or
+ * an output by the Configuration register.
+ */
+static void pca_i2c_update_irqs(PCAGPIOState *ps)
+{
+ PCAGPIOClass *pc = PCA_I2C_GPIO_GET_CLASS(ps);
+ uint16_t out_diff = ps->new_output ^ ps->curr_output;
+ uint16_t in_diff = ps->new_input ^ ps->curr_input;
+ uint16_t mask, pin_i;
+
+ if (in_diff || out_diff) {
+ for (int i = 0; i < pc->num_pins; i++) {
+ mask = BIT(i);
+ /* pin must be configured as an output to be set here */
+ if (out_diff & ~ps->config & mask) {
+ pin_i = mask & ps->new_output;
+ qemu_set_irq(ps->output[i], pin_i > 0);
+ ps->curr_output &= ~mask;
+ ps->curr_output |= pin_i;
+ }
+
+ if (in_diff & mask) {
+ ps->curr_input &= ~mask;
+ ps->curr_input |= mask & ps->new_input;
+ }
+ }
+ /* make diff = 0 */
+ ps->new_input = ps->curr_input;
+ }
+}
+
+static void pca_i2c_irq_handler(void *opaque, int n, int level)
+{
+ PCAGPIOState *ps = opaque;
+ PCAGPIOClass *pc = PCA_I2C_GPIO_GET_CLASS(opaque);
+ uint16_t mask = BIT(n);
+
+ g_assert(n < pc->num_pins);
+ g_assert(n >= 0);
+
+ ps->new_input &= ~mask;
+
+ if (level > 0) {
+ ps->new_input |= BIT(n);
+ }
+
+ pca_i2c_update_irqs(ps);
+}
+
+/* slave to master */
+static uint8_t _pca953x_recv(I2CSlave *i2c, uint32_t shift)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(i2c);
+ uint8_t data;
+
+ switch (ps->command) {
+ case PCA953x_INPUT_PORT:
+ data = extract16(ps->curr_input, shift, 8);
+ break;
+ /*
+ * i2c reads to the output registers reflect the values written
+ * NOT the actual values of the gpios
+ */
+ case PCA953x_OUTPUT_PORT:
+ data = extract16(ps->new_output, shift, 8);
+ break;
+
+ case PCA953x_POLARITY_INVERSION_PORT:
+ data = extract16(ps->polarity_inv, shift, 8);
+ break;
+
+ case PCA953x_CONFIGURATION_PORT:
+ data = extract16(ps->config, shift, 8);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register 0x%02x",
+ __func__, ps->command);
+ data = 0xFF;
+ break;
+ }
+
+ trace_pca_i2c_recv(DEVICE(ps)->canonical_path, ps->command, shift, data);
+ return data;
+}
+
+static uint8_t pca6416_recv(I2CSlave *i2c)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(i2c);
+ uint32_t shift = ps->command & 1 ? 8 : 0;
+
+ /* Transform command into 4 port equivalent */
+ ps->command = ps->command >> 1;
+
+ return _pca953x_recv(i2c, shift);
+}
+
+static uint8_t pca953x_recv(I2CSlave *i2c)
+{
+ return _pca953x_recv(i2c, 0);
+}
+
+/* master to slave */
+static int _pca953x_send(I2CSlave *i2c, uint32_t shift, uint8_t data)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(i2c);
+
+ if (ps->i2c_cmd) {
+ ps->command = data;
+ ps->i2c_cmd = false;
+ return 0;
+ }
+
+ trace_pca_i2c_send(DEVICE(ps)->canonical_path, ps->command, shift, data);
+
+ switch (ps->command) {
+ case PCA953x_INPUT_PORT:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing to read only reg: 0x%02x",
+ __func__, ps->command);
+ break;
+ case PCA953x_OUTPUT_PORT:
+ ps->new_output = deposit16(ps->new_output, shift, 8, data);
+ break;
+
+ case PCA953x_POLARITY_INVERSION_PORT:
+ ps->polarity_inv = deposit16(ps->polarity_inv, shift, 8, data);
+ break;
+
+ case PCA953x_CONFIGURATION_PORT:
+ ps->config = deposit16(ps->config, shift, 8, data);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register\n",
+ __func__);
+ return -1;
+ }
+
+ pca_i2c_update_irqs(ps);
+ return 0;
+}
+
+static int pca6416_send(I2CSlave *i2c, uint8_t data)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(i2c);
+ uint32_t shift = ps->command & 1 ? 8 : 0;
+
+ /* Transform command into 4 port equivalent */
+ ps->command = ps->command >> 1;
+
+ return _pca953x_send(i2c, shift, data);
+}
+
+static int pca953x_send(I2CSlave *i2c, uint8_t data)
+{
+ return _pca953x_send(i2c, 0, data);
+}
+
+static int pca_i2c_event(I2CSlave *i2c, enum i2c_event event)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(i2c);
+
+ switch (event) {
+ case I2C_START_RECV:
+ trace_pca_i2c_event(DEVICE(ps)->canonical_path, "START_RECV");
+ break;
+
+ case I2C_START_SEND:
+ trace_pca_i2c_event(DEVICE(ps)->canonical_path, "START_SEND");
+ ps->i2c_cmd = true;
+ break;
+
+ case I2C_FINISH:
+ trace_pca_i2c_event(DEVICE(ps)->canonical_path, "FINISH");
+ break;
+
+ case I2C_NACK:
+ trace_pca_i2c_event(DEVICE(ps)->canonical_path, "NACK");
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: %s: unknown event 0x%x\n",
+ DEVICE(ps)->canonical_path, __func__, event);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void pca_i2c_config_get(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(obj);
+ visit_type_uint16(v, name, &ps->config, errp);
+}
+
+static void pca_i2c_config_set(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(obj);
+ if (!visit_type_uint16(v, name, &ps->config, errp)) {
+ return;
+ }
+ pca_i2c_update_irqs(ps);
+}
+
+
+static void pca_i2c_input_get(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(obj);
+ visit_type_uint16(v, name, &ps->curr_input, errp);
+}
+
+static void pca_i2c_input_set(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(obj);
+ if (!visit_type_uint16(v, name, &ps->new_input, errp)) {
+ return;
+ }
+ pca_i2c_update_irqs(ps);
+}
+
+static void pca_i2c_output_get(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(obj);
+ visit_type_uint16(v, name, &ps->curr_output, errp);
+}
+
+static void pca_i2c_output_set(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(obj);
+ if (!visit_type_uint16(v, name, &ps->new_output, errp)) {
+ return;
+ }
+ pca_i2c_update_irqs(ps);
+}
+
+static void pca_i2c_enter_reset(Object *obj, ResetType type)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(obj);
+
+ ps->polarity_inv = 0;
+ ps->config = 0;
+ ps->new_input = 0;
+ ps->new_output = 0;
+ ps->command = 0;
+
+ pca_i2c_update_irqs(ps);
+}
+
+
+static const VMStateDescription vmstate_pca_i2c_gpio = {
+ .name = TYPE_PCA_I2C_GPIO,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_I2C_SLAVE(parent, PCAGPIOState),
+ VMSTATE_UINT16(polarity_inv, PCAGPIOState),
+ VMSTATE_UINT16(config, PCAGPIOState),
+ VMSTATE_UINT16(curr_input, PCAGPIOState),
+ VMSTATE_UINT16(curr_output, PCAGPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pca6416_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+ PCAGPIOClass *pc = PCA_I2C_GPIO_CLASS(klass);
+
+ dc->desc = "PCA6416 16-bit I/O expander";
+ pc->num_pins = PCA6416_NUM_PINS;
+
+ k->recv = pca6416_recv;
+ k->send = pca6416_send;
+}
+
+static void pca9538_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+ PCAGPIOClass *pc = PCA_I2C_GPIO_CLASS(klass);
+
+ dc->desc = "PCA9538 8-bit I/O expander";
+ pc->num_pins = PCA9538_NUM_PINS;
+
+ k->recv = pca953x_recv;
+ k->send = pca953x_send;
+}
+
+static void pca_i2c_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->vmsd = &vmstate_pca_i2c_gpio;
+ rc->phases.enter = pca_i2c_enter_reset;
+ k->event = pca_i2c_event;
+}
+
+static void pca_i2c_gpio_init(Object *obj)
+{
+ PCAGPIOState *ps = PCA_I2C_GPIO(obj);
+ PCAGPIOClass *pc = PCA_I2C_GPIO_GET_CLASS(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ object_property_add(obj, "gpio_input", "uint16",
+ pca_i2c_input_get,
+ pca_i2c_input_set, NULL, NULL);
+ object_property_add(obj, "gpio_output", "uint16",
+ pca_i2c_output_get,
+ pca_i2c_output_set, NULL, NULL);
+ object_property_add(obj, "gpio_config", "uint16",
+ pca_i2c_config_get,
+ pca_i2c_config_set, NULL, NULL);
+ qdev_init_gpio_in(dev, pca_i2c_irq_handler, pc->num_pins);
+ qdev_init_gpio_out(dev, ps->output, pc->num_pins);
+}
+
+static const TypeInfo pca_gpio_types[] = {
+ {
+ .name = TYPE_PCA_I2C_GPIO,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(PCAGPIOState),
+ .instance_init = pca_i2c_gpio_init,
+ .class_size = sizeof(PCAGPIOClass),
+ .class_init = pca_i2c_gpio_class_init,
+ .abstract = true,
+ },
+ {
+ .name = TYPE_PCA6416_GPIO,
+ .parent = TYPE_PCA_I2C_GPIO,
+ .class_init = pca6416_gpio_class_init,
+ },
+ {
+ .name = TYPE_PCA9538_GPIO,
+ .parent = TYPE_PCA_I2C_GPIO,
+ .class_init = pca9538_gpio_class_init,
+ },
+};
+
+DEFINE_TYPES(pca_gpio_types);
@@ -31,3 +31,8 @@ sifive_gpio_update_output_irq(int64_t line, int64_t value) "line %" PRIi64 " val
# aspeed_gpio.c
aspeed_gpio_read(uint64_t offset, uint64_t value) "offset: 0x%" PRIx64 " value 0x%" PRIx64
aspeed_gpio_write(uint64_t offset, uint64_t value) "offset: 0x%" PRIx64 " value 0x%" PRIx64
+
+# pca_i2c_gpio.c
+pca_i2c_event(const char *id, const char *event) "%s: %s"
+pca_i2c_recv(const char *id, uint8_t cmd, uint32_t shift, uint8_t data) "%s cmd: 0x%" PRIx8 "shift: %" PRIi32 " data 0x%" PRIx8
+pca_i2c_send(const char *id, uint8_t cmd, uint32_t shift, uint8_t data) "%s cmd: 0x%" PRIx8 "shift: %" PRIi32 " data 0x%" PRIx8
new file mode 100644
@@ -0,0 +1,67 @@
+/*
+ * NXP PCA6416A
+ * Low-voltage translating 16-bit I2C/SMBus GPIO expander with interrupt output,
+ * reset, and configuration registers
+ *
+ * Datasheet: https://www.nxp.com/docs/en/data-sheet/PCA6416A.pdf
+ *
+ * Note: Polarity inversion emulation not implemented
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef PCA_I2C_GPIO_H
+#define PCA_I2C_GPIO_H
+
+#include "hw/i2c/i2c.h"
+#include "qom/object.h"
+
+#define PCA_I2C_MAX_PINS 16
+#define PCA6416_NUM_PINS 16
+#define PCA9538_NUM_PINS 8
+
+typedef struct PCAGPIOClass {
+ I2CSlaveClass parent;
+
+ uint8_t num_pins;
+} PCAGPIOClass;
+
+typedef struct PCAGPIOState {
+ I2CSlave parent;
+
+ uint16_t polarity_inv;
+ uint16_t config;
+
+ /* the values of the gpio pins are mirrored in these integers */
+ uint16_t curr_input;
+ uint16_t curr_output;
+ uint16_t new_input;
+ uint16_t new_output;
+
+ /*
+ * Note that these outputs need to be consumed by some other input
+ * to be useful, qemu ignores writes to disconnected gpio pins
+ */
+ qemu_irq output[PCA_I2C_MAX_PINS];
+
+ /* i2c transaction info */
+ uint8_t command;
+ bool i2c_cmd;
+
+} PCAGPIOState;
+
+#define TYPE_PCA_I2C_GPIO "pca_i2c_gpio"
+OBJECT_DECLARE_TYPE(PCAGPIOState, PCAGPIOClass, PCA_I2C_GPIO)
+
+#define PCA953x_INPUT_PORT 0x00 /* read */
+#define PCA953x_OUTPUT_PORT 0x01 /* read/write */
+#define PCA953x_POLARITY_INVERSION_PORT 0x02 /* read/write */
+#define PCA953x_CONFIGURATION_PORT 0x03 /* read/write */
+
+#define PCA_I2C_CONFIG_DEFAULT 0
+
+#define TYPE_PCA6416_GPIO "pca6416"
+#define TYPE_PCA9538_GPIO "pca9538"
+
+#endif
@@ -1 +1 @@
-Subproject commit f80f052277c88a67c55e107b550f504eeea947d3
+Subproject commit b24306f15daa2ff8510b06702114724b33895d3c
@@ -1 +1 @@
-Subproject commit af97fd7af5e7c18f591a7b987291d3db4ffb28b5
+Subproject commit 0e0afae6579c1efe9f0d85505b75ffe989554133
@@ -1 +1 @@
-Subproject commit 6b5188ca14e59ce7bf71afe4e7d3d557c3d31bf8
+Subproject commit 4489876e933d8ba0d8bc6c64bae71e295d45faac
@@ -1 +1 @@
-Subproject commit ea1b7a0733906b8425d948ae94fba63c32b1d425
+Subproject commit 3208b098f51a9ef96d0dfa71d5ec3a3eaec88f0a
@@ -1 +1 @@
-Subproject commit 232f41f160d4567b8c82dd52aa96c2bc3a5b75c1
+Subproject commit e3eb28cf2e17fbcf7fe7e19505ee432b8ec5bbb5
@@ -244,6 +244,7 @@ qos_test_ss.add(
'ne2000-test.c',
'tulip-test.c',
'nvme-test.c',
+ 'pca_i2c_gpio-test.c',
'pca9552-test.c',
'pci-test.c',
'pcnet-test.c',
new file mode 100644
@@ -0,0 +1,188 @@
+/*
+ * QTest for PCA I2C GPIO expanders
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/pca_i2c_gpio.h"
+#include "libqtest-single.h"
+#include "libqos/i2c.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qnum.h"
+#include "qemu/bitops.h"
+
+
+#define PCA6416_INPUT_PORT_0 0x00 /* read */
+#define PCA6416_INPUT_PORT_1 0x01 /* read */
+#define PCA6416_OUTPUT_PORT_0 0x02 /* read/write */
+#define PCA6416_OUTPUT_PORT_1 0x03 /* read/write */
+#define PCA6416_POLARITY_INVERSION_PORT_0 0x04 /* read/write */
+#define PCA6416_POLARITY_INVERSION_PORT_1 0x05 /* read/write */
+#define PCA6416_CONFIGURATION_PORT_0 0x06 /* read/write */
+#define PCA6416_CONFIGURATION_PORT_1 0x07 /* read/write */
+
+
+#define TEST_ID "pca_i2c_gpio-test"
+#define PCA_CONFIG_BYTE 0x55
+#define PCA_CONFIG_BYTE_UPPER 0xAA
+#define PCA_CONFIG_WORD 0xAA55
+
+static uint16_t qmp_pca_gpio_get(const char *id, const char *property)
+{
+ QDict *response;
+ uint16_t ret;
+ response = qmp("{ 'execute': 'qom-get', 'arguments': { 'path': %s, "
+ "'property': %s } }", id, property);
+ g_assert(qdict_haskey(response, "return"));
+ ret = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
+ qobject_unref(response);
+ return ret;
+}
+
+static void qmp_pca_gpio_set(const char *id, const char *property,
+ uint16_t value)
+{
+ QDict *response;
+
+ response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
+ "'property': %s, 'value': %u } }",
+ id, property, value);
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+}
+
+static void test_set_input(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t value;
+ uint16_t qmp_value;
+ /* configure pins to be inputs */
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_0, 0xFF);
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_1, 0xFF);
+
+ qmp_pca_gpio_set(TEST_ID, "gpio_input", 0xAAAA);
+ value = i2c_get8(i2cdev, PCA6416_INPUT_PORT_0);
+ g_assert_cmphex(value, ==, 0xAA);
+ value = i2c_get8(i2cdev, PCA6416_INPUT_PORT_1);
+ g_assert_cmphex(value, ==, 0xAA);
+
+ qmp_value = qmp_pca_gpio_get(TEST_ID, "gpio_input");
+ g_assert_cmphex(qmp_value, ==, 0xAAAA);
+}
+static void test_config(void *obj, void *data, QGuestAllocator *alloc)
+{
+
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t value;
+ uint16_t qmp_value;
+ /* configure half the pins to be inputs */
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_0, PCA_CONFIG_BYTE);
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_1, PCA_CONFIG_BYTE_UPPER);
+ value = i2c_get8(i2cdev, PCA6416_CONFIGURATION_PORT_0);
+ g_assert_cmphex(value, ==, PCA_CONFIG_BYTE);
+
+ value = i2c_get8(i2cdev, PCA6416_CONFIGURATION_PORT_1);
+ g_assert_cmphex(value, ==, PCA_CONFIG_BYTE_UPPER);
+
+ /* the pins that match the config should be set, the rest are undef */
+ qmp_pca_gpio_set(TEST_ID, "gpio_input", 0xFFFF);
+ value = i2c_get8(i2cdev, PCA6416_INPUT_PORT_0);
+ g_assert_cmphex(value & PCA_CONFIG_BYTE, ==, 0x55);
+ value = i2c_get8(i2cdev, PCA6416_INPUT_PORT_1);
+ g_assert_cmphex(value & PCA_CONFIG_BYTE_UPPER, ==, 0xAA);
+ qmp_value = qmp_pca_gpio_get(TEST_ID, "gpio_input");
+ g_assert_cmphex(qmp_value & PCA_CONFIG_WORD, ==, 0xAA55);
+
+ /*
+ * i2c will return the value written to the output register, not the values
+ * of the output pins, so we check only the configured pins
+ */
+ qmp_pca_gpio_set(TEST_ID, "gpio_output", 0xFFFF);
+ value = i2c_get8(i2cdev, PCA6416_OUTPUT_PORT_0);
+ g_assert_cmphex(value & ~PCA_CONFIG_BYTE, ==, 0xAA);
+ value = i2c_get8(i2cdev, PCA6416_OUTPUT_PORT_1);
+ g_assert_cmphex(value & ~PCA_CONFIG_BYTE_UPPER, ==, 0x55);
+
+ qmp_value = qmp_pca_gpio_get(TEST_ID, "gpio_output");
+ g_assert_cmphex(qmp_value & ~PCA_CONFIG_WORD, ==, 0x55AA);
+}
+
+static void test_set_output(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t value;
+ uint16_t qmp_value;
+ /* configure pins to be outputs */
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_0, 0);
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_1, 0);
+
+ qmp_pca_gpio_set(TEST_ID, "gpio_output", 0xBB55);
+ value = i2c_get8(i2cdev, PCA6416_OUTPUT_PORT_0);
+ g_assert_cmphex(value, ==, 0x55);
+ value = i2c_get8(i2cdev, PCA6416_OUTPUT_PORT_1);
+ g_assert_cmphex(value, ==, 0xBB);
+
+ qmp_value = qmp_pca_gpio_get(TEST_ID, "gpio_output");
+ g_assert_cmphex(qmp_value, ==, 0xBB55);
+}
+
+static void test_tx_rx(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ uint8_t value;
+
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_0, 0xFF);
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_1, 0xFF);
+ i2c_set8(i2cdev, PCA6416_POLARITY_INVERSION_PORT_0, 0);
+ i2c_set8(i2cdev, PCA6416_POLARITY_INVERSION_PORT_1, 0);
+
+ value = i2c_get8(i2cdev, PCA6416_CONFIGURATION_PORT_0);
+ g_assert_cmphex(value, ==, 0xFF);
+
+ value = i2c_get8(i2cdev, PCA6416_CONFIGURATION_PORT_1);
+ g_assert_cmphex(value, ==, 0xFF);
+
+ value = i2c_get8(i2cdev, PCA6416_POLARITY_INVERSION_PORT_0);
+ g_assert_cmphex(value, ==, 0);
+
+ value = i2c_get8(i2cdev, PCA6416_POLARITY_INVERSION_PORT_1);
+ g_assert_cmphex(value, ==, 0);
+
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_0, 0xAB);
+ value = i2c_get8(i2cdev, PCA6416_CONFIGURATION_PORT_0);
+ g_assert_cmphex(value, ==, 0xAB);
+
+ i2c_set8(i2cdev, PCA6416_CONFIGURATION_PORT_1, 0xBC);
+ value = i2c_get8(i2cdev, PCA6416_CONFIGURATION_PORT_1);
+ g_assert_cmphex(value, ==, 0xBC);
+
+ i2c_set8(i2cdev, PCA6416_POLARITY_INVERSION_PORT_0, 0xAB);
+ value = i2c_get8(i2cdev, PCA6416_POLARITY_INVERSION_PORT_0);
+ g_assert_cmphex(value, ==, 0xAB);
+
+ i2c_set8(i2cdev, PCA6416_POLARITY_INVERSION_PORT_1, 0x7C);
+ value = i2c_get8(i2cdev, PCA6416_POLARITY_INVERSION_PORT_1);
+ g_assert_cmphex(value, ==, 0x7C);
+
+}
+
+static void pca_i2c_gpio_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" TEST_ID ",address=0x78"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { 0x78 });
+ g_test_set_nonfatal_assertions();
+
+ qos_node_create_driver("pca6416", i2c_device_create);
+ qos_node_consumes("pca6416", "i2c-bus", &opts);
+
+ qos_add_test("tx-rx", "pca6416", test_tx_rx, NULL);
+ qos_add_test("set_output_gpio", "pca6416", test_set_output, NULL);
+ qos_add_test("set_input_gpio", "pca6416", test_set_input, NULL);
+ qos_add_test("follow_gpio_config", "pca6416", test_config, NULL);
+}
+libqos_init(pca_i2c_gpio_register_nodes);