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 <[email protected]> --- 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 <[email protected]> +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 <[email protected]> L: [email protected] 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
