MAINTAINERS | 5 + hw/misc/Kconfig | 6 + hw/misc/meson.build | 1 + hw/misc/xiic_fpga_i2c.c | 651 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 663 insertions(+) create mode 100644 hw/misc/xiic_fpga_i2c.c
Hi all,
This RFC adds a QEMU device model, "xiic-fpga-i2c", for a Nexthop-style
PCIe FPGA that exposes Xilinx AXI-IIC (i2c-xiic) controllers. The goal
is to let the SONiC platform bring-up path (the multifpgapci + i2c-xiic
kernel drivers, plus the userspace platform code that walks the I2C
tree) be exercised under QEMU without access to real switch hardware.
The device presents a PCIe function with a BAR0 register window that
decodes the AXI-IIC register map, gives each channel its own QEMU I2C
bus (so standard slave models such as at24c-eeprom / tmp105 / tmp421 can
be attached from the command line), drives real transfers through the
i2c core in dynamic (DTR) mode, and delivers completion via MSI with a
legacy INTx fallback.
Open questions I would appreciate guidance on:
1. Is an emulation of a vendor-specific FPGA appropriate for hw/misc,
or would you prefer it live elsewhere / be structured differently?
It is modelled as a TEST_DEVICES-gated device alongside EDU and
pci-testdev, since its purpose is CI/bring-up rather than modelling
a shipping board.
2. The AXI-IIC register decode here is deliberately partial: it
implements the subset the Linux i2c-xiic driver actually touches in
dynamic mode. I can flesh out the static/manual mode if that is
preferred for a general-purpose model.
3. A documentation file under docs/specs/ can be added if this is
something you would consider merging; I left it out of the RFC to
keep the first posting focused on the device itself.
This series is based on current master (191489ba75). No documentation or
qtest is included yet (happy to add both if there is interest).
Thanks,
Nodoka
Nodoka Shibasaki (1):
hw/misc: add xiic-fpga-i2c, an emulated Nexthop PCIe FPGA I2C
controller
MAINTAINERS | 5 +
hw/misc/Kconfig | 6 +
hw/misc/meson.build | 1 +
hw/misc/xiic_fpga_i2c.c | 651 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 663 insertions(+)
create mode 100644 hw/misc/xiic_fpga_i2c.c
--
2.50.1
Hi all,
This is a follow-up to the single-patch RFC I sent last week ("[RFC
PATCH 1/1] hw/misc: add xiic-fpga-i2c, an emulated PCIe FPGA I2C
controller"). Based on the split suggested there between a
standardized controller and its carrier, this v2 breaks the device in
two:
1/2 - hw/i2c: a generic model of the AMD/Xilinx AXI IIC controller
(LogiCORE IP, PG090) as a SysBus device, independent of any
particular carrier.
2/2 - hw/misc: a PCIe function, gated behind TEST_DEVICES, that
embeds N of the above controllers behind one BAR0 window and
aggregates their interrupts onto a single MSI vector (with a
legacy INTx fallback). This models a specific FPGA carrier
rather than a standardized board, hence hw/misc + TEST_DEVICES
rather than hw/i2c proper.
Together they let the Linux i2c-xiic driver, and any I2C slave behind
it, be exercised under QEMU without real hardware.
Changes since v1:
- Split the single device into a generic hw/i2c controller model and
a separate hw/misc PCIe carrier that instantiates it.
- Added a register-level spec for the controller
(docs/specs/xlnx-axi-iic.rst).
- Added a qtest (tests/qtest/xiic-fpga-i2c-test.c) that drives a
transfer through BAR0 to a tmp105 slave and checks register,
transfer and NACK behavior.
- Reworded comments/commit messages to describe the hardware in
generic terms rather than referencing a specific downstream
project.
Open questions (carried over / updated from v1):
1. Is hw/misc + TEST_DEVICES the right home for the carrier, given
the controller itself is now a standalone hw/i2c device? I split
it this way since the carrier's register aggregation is specific
to one FPGA design rather than a standardized board.
2. The AXI-IIC register decode is deliberately partial: it
implements the subset the Linux i2c-xiic driver actually touches
in dynamic mode. I can flesh out static/manual mode if that is
preferred for a general-purpose model.
This series is based on current master. Happy to add more qtest
coverage or documentation if there's interest in merging either half.
Thanks
Nodoka Shibasaki (2):
hw/i2c: add xlnx-axi-iic, a Xilinx AXI IIC controller
hw/misc: add xiic-fpga-i2c, a PCIe FPGA embedding xlnx-axi-iic cores
MAINTAINERS | 12 +
configs/devices/x86_64-softmmu/default.mak | 5 +
docs/specs/index.rst | 1 +
docs/specs/xlnx-axi-iic.rst | 62 +++++
hw/i2c/Kconfig | 4 +
hw/i2c/meson.build | 1 +
hw/i2c/xlnx-axi-iic.c | 284 +++++++++++++++++++++
hw/misc/Kconfig | 6 +
hw/misc/meson.build | 1 +
hw/misc/xiic_fpga_i2c.c | 261 +++++++++++++++++++
include/hw/i2c/xlnx-axi-iic.h | 69 +++++
tests/qtest/meson.build | 2 +
tests/qtest/xiic-fpga-i2c-test.c | 159 ++++++++++++
13 files changed, 867 insertions(+)
create mode 100644 docs/specs/xlnx-axi-iic.rst
create mode 100644 hw/i2c/xlnx-axi-iic.c
create mode 100644 hw/misc/xiic_fpga_i2c.c
create mode 100644 include/hw/i2c/xlnx-axi-iic.h
create mode 100644 tests/qtest/xiic-fpga-i2c-test.c
--
2.50.1
Add a model of the AMD/Xilinx AXI IIC controller (LogiCORE IP, PG090),
an I2C bus controller. QEMU has no AXI IIC model today; this lets a guest
driver for it, and the I2C slaves behind it, be exercised without real
hardware.
The device is a SysBus device: it owns one I2C bus, decodes the AXI IIC
register map, implements the controller's dynamic transfer mode, and
drives a single level-triggered interrupt line while DGIER is enabled and
IISR & IIER is set. It has a reset handler and migration state, and a
register-level specification in docs/specs/xlnx-axi-iic.rst.
Signed-off-by: Nodoka Shibasaki <nodokaorganized@gmail.com>
---
MAINTAINERS | 10 ++
docs/specs/index.rst | 1 +
docs/specs/xlnx-axi-iic.rst | 62 ++++++++
hw/i2c/Kconfig | 4 +
hw/i2c/meson.build | 1 +
hw/i2c/xlnx-axi-iic.c | 284 ++++++++++++++++++++++++++++++++++
include/hw/i2c/xlnx-axi-iic.h | 69 +++++++++
7 files changed, 431 insertions(+)
create mode 100644 docs/specs/xlnx-axi-iic.rst
create mode 100644 hw/i2c/xlnx-axi-iic.c
create mode 100644 include/hw/i2c/xlnx-axi-iic.h
diff --git a/MAINTAINERS b/MAINTAINERS
index a28935c898..8339c9bf83 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2158,6 +2158,16 @@ S: Maintained
F: hw/misc/edu.c
F: docs/specs/edu.rst
+<<<<<<< Updated upstream
+=======
+xiic-fpga-i2c
+M: Nodoka Shibasaki <nodokaorganized@gmail.com>
+S: Maintained
+F: hw/i2c/xlnx-axi-iic.c
+F: include/hw/i2c/xlnx-axi-iic.h
+F: docs/specs/xlnx-axi-iic.rst
+
+>>>>>>> Stashed changes
IDE
M: John Snow <jsnow@redhat.com>
L: qemu-block@nongnu.org
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index b7909a108a..41b45a5be4 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -40,3 +40,4 @@ guest hardware that is specific to QEMU.
riscv-aia
aspeed-intc
iommu-testdev
+ xlnx-axi-iic
diff --git a/docs/specs/xlnx-axi-iic.rst b/docs/specs/xlnx-axi-iic.rst
new file mode 100644
index 0000000000..a19b479522
--- /dev/null
+++ b/docs/specs/xlnx-axi-iic.rst
@@ -0,0 +1,62 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Xilinx AXI IIC device
+======================
+
+``xlnx-axi-iic`` models the AMD/Xilinx AXI IIC (LogiCORE IP, documented in
+Xilinx PG090) I2C bus controller. It is a SysBus device: a board or a parent
+device maps its single MMIO region and connects its interrupt line, and I2C
+slave models are attached to the ``i2c`` bus it creates.
+
+The model implements the controller's *dynamic* transfer mode, which is what
+a guest driver uses by default.
+
+Properties
+----------
+
+``bus-name``
+ Name given to the I2C bus the controller creates (default ``i2c``). A parent
+ device that instantiates several controllers uses this to give each bus a
+ unique, user-referenceable name.
+
+MMIO register map
+-----------------
+
+Offsets are relative to the start of the controller's MMIO region.
+
+ 0x1C (RW) : DGIER, global interrupt enable (bit 31)
+ 0x20 (RW) : IISR, interrupt status; write-1-to-clear
+ 0x28 (RW) : IIER, interrupt enable
+ 0x40 (WO) : RESETR, soft reset (write 0xA)
+ 0x100 (RW) : CR, control
+ 0x104 (RO) : SR, status (computed)
+ 0x108 (WO) : DTR, tx data and dynamic START (bit 8) / STOP (bit 9)
+ 0x10C (RO) : DRR, rx data
+ 0x114 (RO) : TFO, tx FIFO occupancy (always 0; the FIFO drains immediately)
+ 0x118 (RO) : RFO, rx FIFO occupancy
+ 0x120 (RW) : RFD, rx FIFO programmable depth
+
+Status register (SR) bits: 0x04 bus busy, 0x20 rx FIFO full, 0x40 rx FIFO
+empty, 0x80 tx FIFO empty. Interrupt (IISR/IIER) bits: 0x01 arbitration lost,
+0x02 tx error / NACK, 0x04 tx FIFO empty, 0x08 rx FIFO full, 0x10 bus-not-busy.
+
+Dynamic-mode transfers
+----------------------
+
+The low 8 bits of a DTR write are the data byte; bit 8 (START) frames the
+8-bit address (bit 0 is the read/write flag) that opens a transfer, and bit 9
+(STOP) ends it.
+
+- Write: DTR <- addr|START, then each data byte, the last with STOP.
+- Read: DTR <- addr|START (read flag set), then DTR <- count|STOP. The
+ controller clocks ``count`` bytes from the slave into the rx FIFO, raises
+ IISR.RX_FULL, and raises IISR.BNB once the FIFO has been drained through DRR.
+
+A slave that does not acknowledge sets IISR.TX_ERROR and releases the bus.
+
+Interrupt
+---------
+
+The controller drives a single level-triggered output line, asserted while
+DGIER is enabled and ``IISR & IIER`` is non-zero, and deasserted when the guest
+clears the pending, enabled causes.
diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
index 0766130b59..4faf2bda99 100644
--- a/hw/i2c/Kconfig
+++ b/hw/i2c/Kconfig
@@ -43,6 +43,10 @@ config ALLWINNER_I2C
bool
select I2C
+config XLNX_AXI_IIC
+ bool
+ select I2C
+
config PCA954X
bool
select I2C
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index 88aea35662..939f02f03b 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -9,6 +9,7 @@ i2c_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_i2c.c'))
i2c_ss.add(when: 'CONFIG_IMX_I2C', if_true: files('imx_i2c.c'))
i2c_ss.add(when: 'CONFIG_MPC_I2C', if_true: files('mpc_i2c.c'))
i2c_ss.add(when: 'CONFIG_ALLWINNER_I2C', if_true: files('allwinner-i2c.c'))
+i2c_ss.add(when: 'CONFIG_XLNX_AXI_IIC', if_true: files('xlnx-axi-iic.c'))
i2c_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('microbit_i2c.c'))
i2c_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_smbus.c'))
i2c_ss.add(when: 'CONFIG_DESIGNWARE_I2C', if_true: files('designware_i2c.c'))
diff --git a/hw/i2c/xlnx-axi-iic.c b/hw/i2c/xlnx-axi-iic.c
new file mode 100644
index 0000000000..c27398c319
--- /dev/null
+++ b/hw/i2c/xlnx-axi-iic.c
@@ -0,0 +1,284 @@
+/*
+ * xlnx-axi-iic.c - QEMU model of the Xilinx AXI IIC (LogiCORE IP) controller.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "migration/vmstate.h"
+#include "hw/core/irq.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/i2c/xlnx-axi-iic.h"
+
+static bool xlnx_axi_iic_pending(XlnxAxiIicState *s)
+{
+ if (!(s->dgier & XLNX_AXI_IIC_GINTR_ENABLE_MASK)) {
+ return false;
+ }
+ return (s->isr & s->ier) != 0;
+}
+
+static void xlnx_axi_iic_update_irq(XlnxAxiIicState *s)
+{
+ qemu_set_irq(s->irq, xlnx_axi_iic_pending(s));
+}
+
+static void xlnx_axi_iic_reset_regs(XlnxAxiIicState *s)
+{
+ if (s->in_xfer) {
+ i2c_end_transfer(s->bus);
+ }
+ s->cr = 0;
+ s->isr = 0;
+ s->ier = 0;
+ s->dgier = 0;
+ s->rfd = 0;
+ s->in_xfer = false;
+ s->is_recv = false;
+ s->stop_pending = false;
+ s->rx_len = 0;
+ s->rx_pos = 0;
+ memset(s->rx_fifo, 0, sizeof(s->rx_fifo));
+}
+
+static uint32_t xlnx_axi_iic_status(XlnxAxiIicState *s)
+{
+ uint32_t sr = XLNX_AXI_IIC_SR_TX_FIFO_EMPTY_MASK;
+
+ sr |= (s->rx_pos >= s->rx_len) ? XLNX_AXI_IIC_SR_RX_FIFO_EMPTY_MASK
+ : XLNX_AXI_IIC_SR_RX_FIFO_FULL_MASK;
+ if (s->in_xfer) {
+ sr |= XLNX_AXI_IIC_SR_BUS_BUSY_MASK;
+ }
+ return sr;
+}
+
+static void xlnx_axi_iic_fail(XlnxAxiIicState *s)
+{
+ i2c_end_transfer(s->bus);
+ s->in_xfer = false;
+ s->isr |= XLNX_AXI_IIC_INTR_TX_ERROR_MASK | XLNX_AXI_IIC_INTR_BNB_MASK;
+ xlnx_axi_iic_update_irq(s);
+}
+
+static uint64_t xlnx_axi_iic_read(void *opaque, hwaddr addr, unsigned size)
+{
+ XlnxAxiIicState *s = opaque;
+ bool rx_empty = s->rx_pos >= s->rx_len;
+ uint64_t val = 0;
+
+ switch (addr) {
+ case XLNX_AXI_IIC_SR:
+ val = xlnx_axi_iic_status(s);
+ break;
+ case XLNX_AXI_IIC_IISR:
+ val = s->isr;
+ break;
+ case XLNX_AXI_IIC_IIER:
+ val = s->ier;
+ break;
+ case XLNX_AXI_IIC_DGIER:
+ val = s->dgier;
+ break;
+ case XLNX_AXI_IIC_CR:
+ val = s->cr;
+ break;
+ case XLNX_AXI_IIC_RFD:
+ val = s->rfd;
+ break;
+ case XLNX_AXI_IIC_RFO:
+ val = rx_empty ? 0 : (s->rx_len - s->rx_pos - 1);
+ break;
+ case XLNX_AXI_IIC_DRR:
+ if (!rx_empty) {
+ val = s->rx_fifo[s->rx_pos++];
+ if (s->rx_pos >= s->rx_len) {
+ s->isr &= ~(uint32_t)XLNX_AXI_IIC_INTR_RX_FULL_MASK;
+ if (s->stop_pending) {
+ s->stop_pending = false;
+ s->in_xfer = false;
+ s->isr |= XLNX_AXI_IIC_INTR_BNB_MASK;
+ }
+ xlnx_axi_iic_update_irq(s);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return val;
+}
+
+static void xlnx_axi_iic_dtr_write(XlnxAxiIicState *s, uint64_t val)
+{
+ uint16_t word = val & 0xFFFF;
+ bool stop = word & XLNX_AXI_IIC_TX_DYN_STOP_MASK;
+
+ if (word & XLNX_AXI_IIC_TX_DYN_START_MASK) {
+ uint8_t addr8 = word & 0xFF;
+ s->is_recv = addr8 & 1;
+ s->rx_len = 0;
+ s->rx_pos = 0;
+
+ int nack = s->is_recv ? i2c_start_recv(s->bus, addr8 >> 1)
+ : i2c_start_send(s->bus, addr8 >> 1);
+ s->in_xfer = true;
+ if (nack) {
+ xlnx_axi_iic_fail(s);
+ }
+ return;
+ }
+
+ if (!s->in_xfer) {
+ return;
+ }
+
+ if (s->is_recv) {
+ unsigned room = XLNX_AXI_IIC_RX_FIFO_MAX - s->rx_len;
+ unsigned n = MIN((unsigned)(word & 0xFF), room);
+
+ for (unsigned i = 0; i < n; i++) {
+ s->rx_fifo[s->rx_len++] = i2c_recv(s->bus);
+ }
+ if (stop) {
+ i2c_end_transfer(s->bus);
+ s->stop_pending = true;
+ }
+ if (s->rx_len > 0) {
+ s->isr |= XLNX_AXI_IIC_INTR_RX_FULL_MASK;
+ }
+ xlnx_axi_iic_update_irq(s);
+ return;
+ }
+
+ if (i2c_send(s->bus, word & 0xFF)) {
+ xlnx_axi_iic_fail(s);
+ return;
+ }
+ if (stop) {
+ i2c_end_transfer(s->bus);
+ s->in_xfer = false;
+ s->isr |= XLNX_AXI_IIC_INTR_TX_EMPTY_MASK | XLNX_AXI_IIC_INTR_BNB_MASK;
+ } else {
+ s->isr |= XLNX_AXI_IIC_INTR_TX_EMPTY_MASK;
+ }
+ xlnx_axi_iic_update_irq(s);
+}
+
+static void xlnx_axi_iic_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ XlnxAxiIicState *s = opaque;
+
+ switch (addr) {
+ case XLNX_AXI_IIC_RESETR:
+ if ((val & 0xf) == XLNX_AXI_IIC_RESET_MASK) {
+ device_cold_reset(DEVICE(s));
+ }
+ break;
+ case XLNX_AXI_IIC_CR:
+ s->cr = val;
+ break;
+ case XLNX_AXI_IIC_DGIER:
+ s->dgier = val;
+ xlnx_axi_iic_update_irq(s);
+ break;
+ case XLNX_AXI_IIC_IIER:
+ s->ier = val;
+ xlnx_axi_iic_update_irq(s);
+ break;
+ case XLNX_AXI_IIC_IISR:
+ s->isr &= ~(uint32_t)val;
+ xlnx_axi_iic_update_irq(s);
+ break;
+ case XLNX_AXI_IIC_RFD:
+ s->rfd = val;
+ break;
+ case XLNX_AXI_IIC_DTR:
+ xlnx_axi_iic_dtr_write(s, val);
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps xlnx_axi_iic_ops = {
+ .read = xlnx_axi_iic_read,
+ .write = xlnx_axi_iic_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = { .min_access_size = 1, .max_access_size = 4 },
+ .valid = { .min_access_size = 1, .max_access_size = 4 },
+};
+
+static void xlnx_axi_iic_realize(DeviceState *dev, Error **errp)
+{
+ XlnxAxiIicState *s = XLNX_AXI_IIC(dev);
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &xlnx_axi_iic_ops, s,
+ TYPE_XLNX_AXI_IIC, XLNX_AXI_IIC_REGS_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+ s->bus = i2c_init_bus(dev, s->bus_name ? s->bus_name : "i2c");
+}
+
+static void xlnx_axi_iic_reset_hold(Object *obj, ResetType type)
+{
+ XlnxAxiIicState *s = XLNX_AXI_IIC(obj);
+
+ xlnx_axi_iic_reset_regs(s);
+ xlnx_axi_iic_update_irq(s);
+}
+
+static const VMStateDescription vmstate_xlnx_axi_iic = {
+ .name = TYPE_XLNX_AXI_IIC,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(cr, XlnxAxiIicState),
+ VMSTATE_UINT32(isr, XlnxAxiIicState),
+ VMSTATE_UINT32(ier, XlnxAxiIicState),
+ VMSTATE_UINT32(dgier, XlnxAxiIicState),
+ VMSTATE_UINT32(rfd, XlnxAxiIicState),
+ VMSTATE_BOOL(in_xfer, XlnxAxiIicState),
+ VMSTATE_BOOL(is_recv, XlnxAxiIicState),
+ VMSTATE_BOOL(stop_pending, XlnxAxiIicState),
+ VMSTATE_UINT8_ARRAY(rx_fifo, XlnxAxiIicState, XLNX_AXI_IIC_RX_FIFO_MAX),
+ VMSTATE_INT32(rx_len, XlnxAxiIicState),
+ VMSTATE_INT32(rx_pos, XlnxAxiIicState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property xlnx_axi_iic_props[] = {
+ DEFINE_PROP_STRING("bus-name", XlnxAxiIicState, bus_name),
+};
+
+static void xlnx_axi_iic_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = xlnx_axi_iic_realize;
+ dc->vmsd = &vmstate_xlnx_axi_iic;
+ rc->phases.hold = xlnx_axi_iic_reset_hold;
+ dc->desc = "Xilinx AXI IIC controller";
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ device_class_set_props(dc, xlnx_axi_iic_props);
+}
+
+static const TypeInfo xlnx_axi_iic_info = {
+ .name = TYPE_XLNX_AXI_IIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XlnxAxiIicState),
+ .class_init = xlnx_axi_iic_class_init,
+};
+
+static void xlnx_axi_iic_register_types(void)
+{
+ type_register_static(&xlnx_axi_iic_info);
+}
+
+type_init(xlnx_axi_iic_register_types)
diff --git a/include/hw/i2c/xlnx-axi-iic.h b/include/hw/i2c/xlnx-axi-iic.h
new file mode 100644
index 0000000000..daa65c4140
--- /dev/null
+++ b/include/hw/i2c/xlnx-axi-iic.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef HW_I2C_XLNX_AXI_IIC_H
+#define HW_I2C_XLNX_AXI_IIC_H
+
+#include "hw/core/sysbus.h"
+#include "hw/i2c/i2c.h"
+#include "qom/object.h"
+
+#define TYPE_XLNX_AXI_IIC "xlnx-axi-iic"
+OBJECT_DECLARE_SIMPLE_TYPE(XlnxAxiIicState, XLNX_AXI_IIC)
+
+#define XLNX_AXI_IIC_REGS_SIZE 0x1000
+#define XLNX_AXI_IIC_RX_FIFO_MAX 256
+
+#define XLNX_AXI_IIC_DGIER 0x1C
+#define XLNX_AXI_IIC_IISR 0x20
+#define XLNX_AXI_IIC_IIER 0x28
+#define XLNX_AXI_IIC_RESETR 0x40
+#define XLNX_AXI_IIC_CR 0x100
+#define XLNX_AXI_IIC_SR 0x104
+#define XLNX_AXI_IIC_DTR 0x108
+#define XLNX_AXI_IIC_DRR 0x10C
+#define XLNX_AXI_IIC_TFO 0x114
+#define XLNX_AXI_IIC_RFO 0x118
+#define XLNX_AXI_IIC_RFD 0x120
+
+#define XLNX_AXI_IIC_RESET_MASK 0xA
+
+#define XLNX_AXI_IIC_SR_BUS_BUSY_MASK 0x04
+#define XLNX_AXI_IIC_SR_RX_FIFO_FULL_MASK 0x20
+#define XLNX_AXI_IIC_SR_RX_FIFO_EMPTY_MASK 0x40
+#define XLNX_AXI_IIC_SR_TX_FIFO_EMPTY_MASK 0x80
+
+#define XLNX_AXI_IIC_INTR_ARB_LOST_MASK 0x01
+#define XLNX_AXI_IIC_INTR_TX_ERROR_MASK 0x02
+#define XLNX_AXI_IIC_INTR_TX_EMPTY_MASK 0x04
+#define XLNX_AXI_IIC_INTR_RX_FULL_MASK 0x08
+#define XLNX_AXI_IIC_INTR_BNB_MASK 0x10
+
+#define XLNX_AXI_IIC_GINTR_ENABLE_MASK 0x80000000UL
+
+#define XLNX_AXI_IIC_TX_DYN_START_MASK 0x0100
+#define XLNX_AXI_IIC_TX_DYN_STOP_MASK 0x0200
+
+struct XlnxAxiIicState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ qemu_irq irq;
+ I2CBus *bus;
+ char *bus_name;
+
+ uint32_t cr;
+ uint32_t isr;
+ uint32_t ier;
+ uint32_t dgier;
+ uint32_t rfd;
+
+ bool in_xfer;
+ bool is_recv;
+ bool stop_pending;
+
+ uint8_t rx_fifo[XLNX_AXI_IIC_RX_FIFO_MAX];
+ int rx_len;
+ int rx_pos;
+};
+
+#endif
--
2.50.1
Add a PCIe function that embeds N xlnx-axi-iic controllers, maps each
core's register window into BAR0, and adds a top-level interrupt block
that aggregates the per-channel level lines into a bit-per-channel status
register delivered on a single MSI vector (with a legacy INTx fallback),
performing the level-to-edge translation a guest driver expects.
The aggregator's register layout models a specific FPGA carrier rather
than a standardized controller, so the device is gated behind
CONFIG_XIIC_FPGA_I2C (default y if TEST_DEVICES). A qtest drives a
transfer through BAR0 to a tmp105 slave and checks register, transfer and
NACK behavior; the test slaves it needs are enabled in the x86_64-softmmu
test config.
Signed-off-by: Nodoka Shibasaki <nodokaorganized@gmail.com>
---
MAINTAINERS | 8 +-
configs/devices/x86_64-softmmu/default.mak | 5 +
hw/misc/Kconfig | 6 +
hw/misc/meson.build | 1 +
hw/misc/xiic_fpga_i2c.c | 261 +++++++++++++++++++++
tests/qtest/meson.build | 2 +
tests/qtest/xiic-fpga-i2c-test.c | 159 +++++++++++++
7 files changed, 439 insertions(+), 3 deletions(-)
create mode 100644 hw/misc/xiic_fpga_i2c.c
create mode 100644 tests/qtest/xiic-fpga-i2c-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8339c9bf83..85f03cf4b6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2158,8 +2158,6 @@ S: Maintained
F: hw/misc/edu.c
F: docs/specs/edu.rst
-<<<<<<< Updated upstream
-=======
xiic-fpga-i2c
M: Nodoka Shibasaki <nodokaorganized@gmail.com>
S: Maintained
@@ -2167,7 +2165,11 @@ F: hw/i2c/xlnx-axi-iic.c
F: include/hw/i2c/xlnx-axi-iic.h
F: docs/specs/xlnx-axi-iic.rst
->>>>>>> Stashed changes
+xiic-fpga-i2c
+M: Nodoka Shibasaki <nodokaorganized@gmail.com>
+S: Maintained
+F: hw/misc/xiic_fpga_i2c.c
+
IDE
M: John Snow <jsnow@redhat.com>
L: qemu-block@nongnu.org
diff --git a/configs/devices/x86_64-softmmu/default.mak b/configs/devices/x86_64-softmmu/default.mak
index ddfc2ea626..d7a88deccc 100644
--- a/configs/devices/x86_64-softmmu/default.mak
+++ b/configs/devices/x86_64-softmmu/default.mak
@@ -1,3 +1,8 @@
# Default configuration for x86_64-softmmu
include ../i386-softmmu/default.mak
+
+# Enable I2C slave models used by the xiic-fpga-i2c qtest.
+CONFIG_AT24C=y
+CONFIG_TMP105=y
+CONFIG_TMP421=y
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6653..12acca66bb 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -25,6 +25,12 @@ config PCI_TESTDEV
default y if TEST_DEVICES
depends on PCI
+config XIIC_FPGA_I2C
+ bool
+ default y if TEST_DEVICES
+ depends on PCI && MSI_NONBROKEN
+ select XLNX_AXI_IIC
+
config IOMMU_TESTDEV
bool
default y if TEST_DEVICES
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6035..69d816cad9 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -4,6 +4,7 @@ system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
system_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
system_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
+system_ss.add(when: 'CONFIG_XIIC_FPGA_I2C', if_true: files('xiic_fpga_i2c.c'))
system_ss.add(when: 'CONFIG_IOMMU_TESTDEV', if_true: files('iommu-testdev.c'))
system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
diff --git a/hw/misc/xiic_fpga_i2c.c b/hw/misc/xiic_fpga_i2c.c
new file mode 100644
index 0000000000..c4425fa84d
--- /dev/null
+++ b/hw/misc/xiic_fpga_i2c.c
@@ -0,0 +1,261 @@
+/*
+ * xiic_fpga_i2c.c - QEMU model of a PCIe FPGA that embeds Xilinx AXI-IIC
+ * controllers behind a shared MSI interrupt aggregator.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/host-utils.h"
+#include "migration/vmstate.h"
+#include "hw/pci/pci_device.h"
+#include "hw/pci/msi.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/i2c/xlnx-axi-iic.h"
+
+#define TYPE_XIIC_FPGA_I2C "xiic-fpga-i2c"
+OBJECT_DECLARE_SIMPLE_TYPE(XiicFpgaI2cState, XIIC_FPGA_I2C)
+
+#define XIIC_FPGA_MAX_CHANNELS 32
+
+#define XIIC_FPGA_IRQ_STATUS_REG 0
+#define XIIC_FPGA_IRQ_UNMASK_REG 4
+#define XIIC_FPGA_IRQ_REGION_SIZE 8
+
+struct XiicFpgaI2cState {
+ PCIDevice parent_obj;
+
+ MemoryRegion bar0;
+ MemoryRegion irq_mmio;
+
+ uint32_t num_channels;
+ uint32_t ch_base_offset;
+ uint32_t ch_stride;
+ uint32_t bar_size;
+ uint32_t num_msi_vectors;
+
+ uint32_t irq_status_offset;
+ uint32_t irq_unmask_offset;
+ uint32_t irq_msi_vector;
+
+ uint32_t irq_status;
+ uint32_t irq_unmask;
+ bool msi_asserted;
+
+ XlnxAxiIicState chan[XIIC_FPGA_MAX_CHANNELS];
+};
+
+static void xiic_fpga_update_msi(XiicFpgaI2cState *s)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ uint32_t active = s->irq_status & s->irq_unmask;
+
+ if (msi_enabled(pci_dev)) {
+ if (active && !s->msi_asserted) {
+ msi_notify(pci_dev, s->irq_msi_vector);
+ s->msi_asserted = true;
+ } else if (!active) {
+ s->msi_asserted = false;
+ }
+ return;
+ }
+
+ pci_set_irq(pci_dev, active != 0);
+}
+
+static void xiic_fpga_irq_set(void *opaque, int n, int level)
+{
+ XiicFpgaI2cState *s = opaque;
+
+ if (level) {
+ s->irq_status |= (1u << n);
+ } else {
+ s->irq_status &= ~(1u << n);
+ }
+ xiic_fpga_update_msi(s);
+}
+
+static uint64_t xiic_fpga_irq_read(void *opaque, hwaddr addr, unsigned size)
+{
+ XiicFpgaI2cState *s = opaque;
+
+ switch (addr) {
+ case XIIC_FPGA_IRQ_STATUS_REG:
+ return s->irq_status;
+ case XIIC_FPGA_IRQ_UNMASK_REG:
+ return s->irq_unmask;
+ default:
+ return 0;
+ }
+}
+
+static void xiic_fpga_irq_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ XiicFpgaI2cState *s = opaque;
+
+ switch (addr) {
+ case XIIC_FPGA_IRQ_STATUS_REG:
+ s->msi_asserted = false;
+ xiic_fpga_update_msi(s);
+ break;
+ case XIIC_FPGA_IRQ_UNMASK_REG:
+ s->irq_unmask = val;
+ xiic_fpga_update_msi(s);
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps xiic_fpga_irq_ops = {
+ .read = xiic_fpga_irq_read,
+ .write = xiic_fpga_irq_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = { .min_access_size = 1, .max_access_size = 4 },
+ .valid = { .min_access_size = 1, .max_access_size = 4 },
+};
+
+static void xiic_fpga_i2c_realize(PCIDevice *pci_dev, Error **errp)
+{
+ XiicFpgaI2cState *s = XIIC_FPGA_I2C(pci_dev);
+
+ if (s->num_channels < 1 || s->num_channels > XIIC_FPGA_MAX_CHANNELS) {
+ error_setg(errp, "num-channels must be between 1 and %d",
+ XIIC_FPGA_MAX_CHANNELS);
+ return;
+ }
+ if (s->irq_unmask_offset != s->irq_status_offset + 4) {
+ error_setg(errp, "irq-unmask-offset must be irq-status-offset + 4");
+ return;
+ }
+
+ if (s->bar_size < s->ch_base_offset + s->num_channels * s->ch_stride) {
+ s->bar_size = s->ch_base_offset + s->num_channels * s->ch_stride;
+ }
+ if (s->bar_size < s->irq_status_offset + XIIC_FPGA_IRQ_REGION_SIZE) {
+ s->bar_size = s->irq_status_offset + XIIC_FPGA_IRQ_REGION_SIZE;
+ }
+ s->bar_size = pow2ceil(s->bar_size);
+
+ s->num_msi_vectors = pow2ceil(s->num_channels);
+ if (s->num_msi_vectors > 32) {
+ s->num_msi_vectors = 32;
+ }
+ if (s->irq_msi_vector >= s->num_msi_vectors) {
+ error_setg(errp, "irq-msi-vector %u out of range (0..%u)",
+ s->irq_msi_vector, s->num_msi_vectors - 1);
+ return;
+ }
+
+ memory_region_init(&s->bar0, OBJECT(s), "xiic-fpga-i2c-bar0", s->bar_size);
+
+ qdev_init_gpio_in(DEVICE(s), xiic_fpga_irq_set, s->num_channels);
+
+ for (unsigned i = 0; i < s->num_channels; i++) {
+ g_autofree char *name = g_strdup_printf("channel[%u]", i);
+ g_autofree char *bus_name = g_strdup_printf("xiic-fpga-i2c.%u", i);
+ object_initialize_child(OBJECT(s), name, &s->chan[i],
+ TYPE_XLNX_AXI_IIC);
+ qdev_prop_set_string(DEVICE(&s->chan[i]), "bus-name", bus_name);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->chan[i]), errp)) {
+ return;
+ }
+ memory_region_add_subregion(&s->bar0,
+ s->ch_base_offset + i * s->ch_stride,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->chan[i]), 0));
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->chan[i]), 0,
+ qdev_get_gpio_in(DEVICE(s), i));
+ }
+
+ memory_region_init_io(&s->irq_mmio, OBJECT(s), &xiic_fpga_irq_ops, s,
+ "xiic-fpga-i2c-irq", XIIC_FPGA_IRQ_REGION_SIZE);
+ memory_region_add_subregion(&s->bar0, s->irq_status_offset, &s->irq_mmio);
+
+ pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
+ pci_config_set_interrupt_pin(pci_dev->config, 1);
+
+ if (msi_init(pci_dev, 0, s->num_msi_vectors, true, false, errp) < 0) {
+ error_prepend(errp, "xiic-fpga-i2c: failed to init MSI: ");
+ return;
+ }
+}
+
+static void xiic_fpga_i2c_exit(PCIDevice *pci_dev)
+{
+ msi_uninit(pci_dev);
+}
+
+static void xiic_fpga_i2c_reset_hold(Object *obj, ResetType type)
+{
+ XiicFpgaI2cState *s = XIIC_FPGA_I2C(obj);
+
+ s->irq_status = 0;
+ s->irq_unmask = 0;
+ s->msi_asserted = false;
+}
+
+static const VMStateDescription vmstate_xiic_fpga_i2c = {
+ .name = "xiic-fpga-i2c",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, XiicFpgaI2cState),
+ VMSTATE_UINT32(irq_status, XiicFpgaI2cState),
+ VMSTATE_UINT32(irq_unmask, XiicFpgaI2cState),
+ VMSTATE_BOOL(msi_asserted, XiicFpgaI2cState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property xiic_fpga_i2c_props[] = {
+ DEFINE_PROP_UINT32("num-channels", XiicFpgaI2cState, num_channels, 4),
+ DEFINE_PROP_UINT32("ch-base-offset", XiicFpgaI2cState, ch_base_offset, 0x0),
+ DEFINE_PROP_UINT32("ch-stride", XiicFpgaI2cState, ch_stride, 0x1000),
+ DEFINE_PROP_UINT32("bar-size", XiicFpgaI2cState, bar_size, 0x8000),
+ DEFINE_PROP_UINT32("irq-status-offset", XiicFpgaI2cState,
+ irq_status_offset, 0x6000),
+ DEFINE_PROP_UINT32("irq-unmask-offset", XiicFpgaI2cState,
+ irq_unmask_offset, 0x6004),
+ DEFINE_PROP_UINT32("irq-msi-vector", XiicFpgaI2cState, irq_msi_vector, 0),
+};
+
+static void xiic_fpga_i2c_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ k->realize = xiic_fpga_i2c_realize;
+ k->exit = xiic_fpga_i2c_exit;
+ k->vendor_id = 0x10ee;
+ k->device_id = 0x7021;
+ k->revision = 0x01;
+ k->class_id = PCI_CLASS_OTHERS;
+
+ rc->phases.hold = xiic_fpga_i2c_reset_hold;
+ dc->vmsd = &vmstate_xiic_fpga_i2c;
+ dc->desc = "FPGA I2C (Xilinx AXI-IIC) emulation";
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ device_class_set_props(dc, xiic_fpga_i2c_props);
+}
+
+static const TypeInfo xiic_fpga_i2c_info = {
+ .name = TYPE_XIIC_FPGA_I2C,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(XiicFpgaI2cState),
+ .class_init = xiic_fpga_i2c_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void xiic_fpga_i2c_register_types(void)
+{
+ type_register_static(&xiic_fpga_i2c_info);
+}
+
+type_init(xiic_fpga_i2c_register_types)
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860e21..7abd2c7520 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -74,6 +74,8 @@ qtests_i386 = \
(config_all_devices.has_key('CONFIG_WDT_IB700') ? ['wdt_ib700-test'] : []) + \
(config_all_devices.has_key('CONFIG_PVPANIC_ISA') ? ['pvpanic-test'] : []) + \
(config_all_devices.has_key('CONFIG_PVPANIC_PCI') ? ['pvpanic-pci-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_XIIC_FPGA_I2C') and
+ config_all_devices.has_key('CONFIG_TMP105') ? ['xiic-fpga-i2c-test'] : []) + \
(config_all_devices.has_key('CONFIG_HDA') ? ['intel-hda-test'] : []) + \
(config_all_devices.has_key('CONFIG_I82801B11') ? ['i82801b11-test'] : []) + \
(config_all_devices.has_key('CONFIG_IOH3420') ? ['ioh3420-test'] : []) + \
diff --git a/tests/qtest/xiic-fpga-i2c-test.c b/tests/qtest/xiic-fpga-i2c-test.c
new file mode 100644
index 0000000000..8c5f862d7c
--- /dev/null
+++ b/tests/qtest/xiic-fpga-i2c-test.c
@@ -0,0 +1,159 @@
+/*
+ * QTest for the xiic-fpga-i2c PCIe FPGA and its embedded xlnx-axi-iic cores.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "libqos/pci.h"
+#include "libqos/pci-pc.h"
+
+#define XIIC_IISR 0x20
+#define XIIC_IIER 0x28
+#define XIIC_DGIER 0x1C
+#define XIIC_SR 0x104
+#define XIIC_DTR 0x108
+#define XIIC_DRR 0x10C
+
+#define XIIC_SR_RX_FIFO_EMPTY 0x40
+#define XIIC_SR_TX_FIFO_EMPTY 0x80
+#define XIIC_SR_BUS_BUSY 0x04
+
+#define XIIC_INTR_TX_ERROR 0x02
+#define XIIC_INTR_BNB 0x10
+
+#define XIIC_DYN_START 0x100
+#define XIIC_DYN_STOP 0x200
+
+#define TMP105_ADDR 0x4c
+#define TMP105_REG_CONFIG 0x01
+
+typedef struct {
+ QTestState *qts;
+ QPCIBus *pcibus;
+ QPCIDevice *dev;
+ QPCIBar bar;
+} XiicFixture;
+
+static void save_dev(QPCIDevice *dev, int devfn, void *data)
+{
+ QPCIDevice **out = data;
+
+ if (*out) {
+ g_free(dev);
+ } else {
+ *out = dev;
+ }
+}
+
+static void fixture_setup(XiicFixture *f)
+{
+ f->qts = qtest_init("-device xiic-fpga-i2c,num-channels=1 "
+ "-device tmp105,id=temp,bus=xiic-fpga-i2c.0,address=0x4c");
+ f->pcibus = qpci_new_pc(f->qts, NULL);
+ f->dev = NULL;
+ qpci_device_foreach(f->pcibus, 0x10ee, 0x7021, save_dev, &f->dev);
+ g_assert(f->dev != NULL);
+ qpci_device_enable(f->dev);
+ f->bar = qpci_iomap(f->dev, 0, NULL);
+}
+
+static void fixture_teardown(XiicFixture *f)
+{
+ qpci_iounmap(f->dev, f->bar);
+ g_free(f->dev);
+ qpci_free_pc(f->pcibus);
+ qtest_quit(f->qts);
+}
+
+static void wr(XiicFixture *f, uint64_t off, uint32_t val)
+{
+ qpci_io_writel(f->dev, f->bar, off, val);
+}
+
+static uint32_t rd(XiicFixture *f, uint64_t off)
+{
+ return qpci_io_readl(f->dev, f->bar, off);
+}
+
+static void test_registers(void)
+{
+ XiicFixture f;
+ uint32_t sr;
+
+ fixture_setup(&f);
+
+ sr = rd(&f, XIIC_SR);
+ g_assert_cmphex(sr & XIIC_SR_TX_FIFO_EMPTY, ==, XIIC_SR_TX_FIFO_EMPTY);
+ g_assert_cmphex(sr & XIIC_SR_RX_FIFO_EMPTY, ==, XIIC_SR_RX_FIFO_EMPTY);
+ g_assert_cmphex(sr & XIIC_SR_BUS_BUSY, ==, 0);
+
+ wr(&f, XIIC_IIER, 0x08);
+ g_assert_cmphex(rd(&f, XIIC_IIER), ==, 0x08);
+ wr(&f, XIIC_DGIER, 0x80000000);
+ g_assert_cmphex(rd(&f, XIIC_DGIER), ==, 0x80000000);
+
+ fixture_teardown(&f);
+}
+
+static void test_read(void)
+{
+ XiicFixture f;
+
+ fixture_setup(&f);
+
+ qtest_qmp_assert_success(f.qts,
+ "{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': '/machine/peripheral/temp',"
+ " 'property': 'temperature', 'value': 21000 } }");
+
+ wr(&f, XIIC_DTR, XIIC_DYN_START | (TMP105_ADDR << 1) | 1);
+ wr(&f, XIIC_DTR, XIIC_DYN_STOP | 2);
+
+ g_assert_cmphex(rd(&f, XIIC_DRR) & 0xff, ==, 0x15);
+
+ fixture_teardown(&f);
+}
+
+static void test_write(void)
+{
+ XiicFixture f;
+ uint32_t isr;
+
+ fixture_setup(&f);
+
+ wr(&f, XIIC_DTR, XIIC_DYN_START | (TMP105_ADDR << 1) | 0);
+ wr(&f, XIIC_DTR, TMP105_REG_CONFIG);
+ wr(&f, XIIC_DTR, XIIC_DYN_STOP | 0x00);
+
+ isr = rd(&f, XIIC_IISR);
+ g_assert_cmphex(isr & XIIC_INTR_TX_ERROR, ==, 0);
+ g_assert_cmphex(isr & XIIC_INTR_BNB, ==, XIIC_INTR_BNB);
+ g_assert_cmphex(rd(&f, XIIC_SR) & XIIC_SR_BUS_BUSY, ==, 0);
+
+ fixture_teardown(&f);
+}
+
+static void test_nack(void)
+{
+ XiicFixture f;
+
+ fixture_setup(&f);
+
+ wr(&f, XIIC_DTR, XIIC_DYN_START | (0x20 << 1) | 1);
+ g_assert_cmphex(rd(&f, XIIC_IISR) & XIIC_INTR_TX_ERROR, ==,
+ XIIC_INTR_TX_ERROR);
+
+ fixture_teardown(&f);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/xiic-fpga-i2c/registers", test_registers);
+ qtest_add_func("/xiic-fpga-i2c/read", test_read);
+ qtest_add_func("/xiic-fpga-i2c/write", test_write);
+ qtest_add_func("/xiic-fpga-i2c/nack", test_nack);
+ return g_test_run();
+}
--
2.50.1
© 2016 - 2026 Red Hat, Inc.