qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 1/2] zynq_gpio: GPIO model for Zynq SoC


From: Colin Leitner
Subject: [Qemu-devel] [PATCH 1/2] zynq_gpio: GPIO model for Zynq SoC
Date: Tue, 30 Dec 2014 14:13:27 +0100

Based on the pl061 model. This model implements all four banks with 32 I/Os
each.

The I/Os are placed in four named groups:

 * mio_in/out[0..63], where mio_in/out[0..31] map to bank 0 and the rest to
   bank 1
 * emio_in/out[0..63], where emio_in/out[0..31] map to bank 2 and the rest to
   bank 3

Basic I/O tested with the Zynq GPIO driver in Linux 3.12.

Signed-off-by: Colin Leitner <address@hidden>
---
 hw/gpio/Makefile.objs |    1 +
 hw/gpio/zynq_gpio.c   |  441 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 442 insertions(+)
 create mode 100644 hw/gpio/zynq_gpio.c

diff --git a/hw/gpio/Makefile.objs b/hw/gpio/Makefile.objs
index 1abcf17..32b99e0 100644
--- a/hw/gpio/Makefile.objs
+++ b/hw/gpio/Makefile.objs
@@ -5,3 +5,4 @@ common-obj-$(CONFIG_ZAURUS) += zaurus.o
 common-obj-$(CONFIG_E500) += mpc8xxx.o
 
 obj-$(CONFIG_OMAP) += omap_gpio.o
+obj-$(CONFIG_ZYNQ) += zynq_gpio.o
diff --git a/hw/gpio/zynq_gpio.c b/hw/gpio/zynq_gpio.c
new file mode 100644
index 0000000..2119561
--- /dev/null
+++ b/hw/gpio/zynq_gpio.c
@@ -0,0 +1,441 @@
+/*
+ * Zynq General Purpose IO
+ *
+ * Copyright (C) 2014 Colin Leitner <address@hidden>
+ *
+ * Based on the PL061 model:
+ *   Copyright (c) 2007 CodeSourcery.
+ *   Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+/*
+ * We model all banks as if they were fully populated. MIO pins are usually
+ * limited to 54 pins, but this is probably device dependent and shouldn't
+ * cause too much trouble. One noticable difference is the reset value of
+ * INT_TYPE_1, which is 0x003fffff according to the TRM and 0xffffffff here.
+ *
+ * The output enable pins are not modeled.
+ */
+
+#include "hw/sysbus.h"
+
+//#define DEBUG_ZYNQ_GPIO 1
+
+#ifdef DEBUG_ZYNQ_GPIO
+#define DPRINTF(fmt, ...) \
+do { printf("zynq-gpio: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "zynq-gpio: error: " fmt , ## __VA_ARGS__); exit(1);} 
while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "zynq-gpio: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+#define TYPE_ZYNQ_GPIO "zynq-gpio"
+#define ZYNQ_GPIO(obj) OBJECT_CHECK(ZynqGPIOState, (obj), TYPE_ZYNQ_GPIO)
+
+typedef struct {
+    uint32_t mask_data;
+    uint32_t out_data;
+    uint32_t old_out_data;
+    uint32_t in_data;
+    uint32_t old_in_data;
+    uint32_t dir;
+    uint32_t oen;
+    uint32_t imask;
+    uint32_t istat;
+    uint32_t itype;
+    uint32_t ipolarity;
+    uint32_t iany;
+
+    qemu_irq *out;
+} GPIOBank;
+
+typedef struct ZynqGPIOState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    GPIOBank banks[4];
+    qemu_irq mio_out[64];
+    qemu_irq emio_out[64];
+    qemu_irq irq;
+} ZynqGPIOState;
+
+static void zynq_gpio_update_out(GPIOBank *b)
+{
+    uint32_t changed;
+    uint32_t mask;
+    uint32_t out;
+    int i;
+
+    DPRINTF("dir = %d, data = %d\n", b->dir, b->out_data);
+
+    /* Outputs float high.  */
+    /* FIXME: This is board dependent.  */
+    out = (b->out_data & b->dir) | ~b->dir;
+    changed = b->old_out_data ^ out;
+    if (changed) {
+        b->old_out_data = out;
+        for (i = 0; i < 32; i++) {
+            mask = 1 << i;
+            if (changed & mask) {
+                DPRINTF("Set output %d = %d\n", i, (out & mask) != 0);
+                qemu_set_irq(b->out[i], (out & mask) != 0);
+            }
+        }
+    }
+}
+
+static void zynq_gpio_update_in(GPIOBank *b)
+{
+    uint32_t changed;
+    uint32_t mask;
+    int i;
+
+    changed = b->old_in_data ^ b->in_data;
+    if (changed) {
+        b->old_in_data = b->in_data;
+        for (i = 0; i < 32; i++) {
+            mask = 1 << i;
+            if (changed & mask) {
+                DPRINTF("Changed input %d = %d\n", i, (b->in_data & mask) != 
0);
+
+                if (b->itype & mask) {
+                    /* Edge interrupt */
+                    if (b->iany & mask) {
+                        /* Any edge triggers the interrupt */
+                        b->istat |= mask;
+                    } else {
+                        /* Edge is selected by INT_POLARITY */
+                        b->istat |= ~(b->in_data ^ b->ipolarity) & mask;
+                    }
+                }
+            }
+        }
+    }
+
+    /* Level interrupt */
+    b->istat |= ~(b->in_data ^ b->ipolarity) & ~b->itype;
+
+    DPRINTF("istat = %08X\n", b->istat);
+}
+
+static void zynq_gpio_set_in_irq(ZynqGPIOState *s)
+{
+    int b;
+    uint32_t istat = 0;
+
+    for (b = 0; b < 4; b++) {
+        istat |= s->banks[b].istat & ~s->banks[b].imask;
+    }
+
+    DPRINTF("IRQ = %d\n", istat != 0);
+
+    qemu_set_irq(s->irq, istat != 0);
+}
+
+static void zynq_gpio_update(ZynqGPIOState *s)
+{
+    int b;
+
+    for (b = 0; b < 4; b++) {
+        zynq_gpio_update_out(&s->banks[b]);
+        zynq_gpio_update_in(&s->banks[b]);
+    }
+
+    zynq_gpio_set_in_irq(s);
+}
+
+static uint64_t zynq_gpio_read(void *opaque, hwaddr offset,
+                               unsigned size)
+{
+    ZynqGPIOState *s = (ZynqGPIOState *)opaque;
+    int b;
+    GPIOBank *bank;
+
+    switch (offset) {
+    case 0x000: /* MASK_DATA_0_LSW */
+        return (s->banks[0].mask_data >> 0) & 0xffff;
+    case 0x004: /* MASK_DATA_0_MSW */
+        return (s->banks[0].mask_data >> 16) & 0xffff;
+    case 0x008: /* MASK_DATA_1_LSW */
+        return (s->banks[1].mask_data >> 0) & 0xffff;
+    case 0x00C: /* MASK_DATA_1_MSW */
+        return (s->banks[1].mask_data >> 16) & 0xffff;
+    case 0x010: /* MASK_DATA_2_LSW */
+        return (s->banks[2].mask_data >> 0) & 0xffff;
+    case 0x014: /* MASK_DATA_2_MSW */
+        return (s->banks[2].mask_data >> 16) & 0xffff;
+    case 0x018: /* MASK_DATA_3_LSW */
+        return (s->banks[3].mask_data >> 0) & 0xffff;
+    case 0x01C: /* MASK_DATA_3_MSW */
+        return (s->banks[3].mask_data >> 16) & 0xffff;
+
+    case 0x040: /* DATA_0 */
+        return s->banks[0].out_data;
+    case 0x044: /* DATA_1 */
+        return s->banks[1].out_data;
+    case 0x048: /* DATA_2 */
+        return s->banks[2].out_data;
+    case 0x04C: /* DATA_3 */
+        return s->banks[3].out_data;
+
+    case 0x060: /* DATA_0_RO */
+        return s->banks[0].in_data;
+    case 0x064: /* DATA_1_RO */
+        return s->banks[1].in_data;
+    case 0x068: /* DATA_2_RO */
+        return s->banks[2].in_data;
+    case 0x06C: /* DATA_3_RO */
+        return s->banks[3].in_data;
+    }
+
+    if (offset < 0x204 || offset > 0x2e4) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                     "zynq_gpio_read: Bad offset %x\n", (int)offset);
+        return 0;
+    }
+
+    b = (offset - 0x200) / 0x40;
+    bank = &s->banks[b];
+
+    switch (offset - 0x200 - b * 0x40) {
+    case 0x04: /* DIRM_x */
+        return bank->dir;
+    case 0x08: /* OEN_x */
+        return bank->oen;
+    case 0x0C: /* INT_MASK_x */
+        return bank->imask;
+    case 0x10: /* INT_EN_x */
+        return 0;
+    case 0x14: /* INT_DIS_x */
+        return 0;
+    case 0x18: /* INT_STAT_x */
+        return bank->istat;
+    case 0x1C: /* INT_TYPE_x */
+        return bank->itype;
+    case 0x20: /* INT_POLARITY_x */
+        return bank->ipolarity;
+    case 0x24: /* INT_ANY_x */
+        return bank->iany;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                     "zynq_gpio_read: Bad offset %x\n", (int)offset);
+        return 0;
+    }
+}
+
+static void zynq_gpio_mask_data(GPIOBank *bank, int bit_offset,
+                                uint32_t mask_data)
+{
+    DPRINTF("mask data offset = %d, mask_data = %08X\n", bit_offset, 
mask_data);
+
+    /* MASK_DATA registers are R/W on their data part */
+    bank->mask_data = (bank->mask_data & ~(0xffff << bit_offset)) |
+                     ((mask_data & 0xffff) << bit_offset);
+    bank->out_data = (bank->out_data & ~((~mask_data >> 16) << bit_offset)) |
+                     ((mask_data & 0xffff) << bit_offset);
+    zynq_gpio_update_out(bank);
+}
+
+static void zynq_gpio_data(GPIOBank *bank, uint32_t data)
+{
+    bank->out_data = data;
+    zynq_gpio_update_out(bank);
+}
+
+static void zynq_gpio_write(void *opaque, hwaddr offset,
+                            uint64_t value, unsigned size)
+{
+    ZynqGPIOState *s = (ZynqGPIOState *)opaque;
+    int b;
+    GPIOBank *bank;
+
+    switch (offset) {
+    case 0x000: /* MASK_DATA_0_LSW */
+        zynq_gpio_mask_data(&s->banks[0], 0, value);
+        return;
+    case 0x004: /* MASK_DATA_0_MSW */
+        zynq_gpio_mask_data(&s->banks[0], 16, value);
+        return;
+    case 0x008: /* MASK_DATA_1_LSW */
+        zynq_gpio_mask_data(&s->banks[1], 0, value);
+        return;
+    case 0x00C: /* MASK_DATA_1_MSW */
+        zynq_gpio_mask_data(&s->banks[1], 16, value);
+        return;
+    case 0x010: /* MASK_DATA_2_LSW */
+        zynq_gpio_mask_data(&s->banks[2], 0, value);
+        return;
+    case 0x014: /* MASK_DATA_2_MSW */
+        zynq_gpio_mask_data(&s->banks[2], 16, value);
+        return;
+    case 0x018: /* MASK_DATA_3_LSW */
+        zynq_gpio_mask_data(&s->banks[3], 0, value);
+        return;
+    case 0x01C: /* MASK_DATA_3_MSW */
+        zynq_gpio_mask_data(&s->banks[3], 16, value);
+        return;
+
+    case 0x040: /* DATA_0 */
+        zynq_gpio_data(&s->banks[0], value);
+        return;
+    case 0x044: /* DATA_1 */
+        zynq_gpio_data(&s->banks[1], value);
+        return;
+    case 0x048: /* DATA_2 */
+        zynq_gpio_data(&s->banks[2], value);
+        return;
+    case 0x04C: /* DATA_3 */
+        zynq_gpio_data(&s->banks[3], value);
+        return;
+
+    case 0x060: /* DATA_0_RO */
+    case 0x064: /* DATA_1_RO */
+    case 0x068: /* DATA_2_RO */
+    case 0x06C: /* DATA_3_RO */
+        return;
+    }
+
+    if (offset < 0x204 || offset > 0x2e4) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                     "zynq_gpio_write: Bad offset %x\n", (int)offset);
+        return;
+    }
+
+    b = (offset - 0x200) / 0x40;
+    bank = &s->banks[b];
+
+    switch (offset - 0x200 - b * 0x40) {
+    case 0x04: /* DIRM_x */
+        bank->dir = value;
+        break;
+    case 0x08: /* OEN_x */
+        bank->oen = value;
+        break;
+    case 0x0C: /* INT_MASK_x */
+        return;
+    case 0x10: /* INT_EN_x */
+        bank->imask &= ~value;
+        break;
+    case 0x14: /* INT_DIS_x */
+        bank->imask |= value;
+        break;
+    case 0x18: /* INT_STAT_x */
+        bank->istat &= ~value;
+        break;
+    case 0x1C: /* INT_TYPE_x */
+        bank->itype = value;
+        break;
+    case 0x20: /* INT_POLARITY_x */
+        bank->ipolarity = value;
+        break;
+    case 0x24: /* INT_ANY_x */
+        bank->iany = value;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                     "zynq_gpio_write: Bad offset %x\n", (int)offset);
+        return;
+    }
+
+    zynq_gpio_update(s);
+}
+
+static void zynq_gpio_reset(ZynqGPIOState *s)
+{
+    int b;
+
+    for (b = 0; b < 4; b++) {
+        s->banks[b].mask_data = 0x00000000;
+        s->banks[b].out_data = 0x00000000;
+        s->banks[b].dir = 0x00000000;
+        s->banks[b].oen = 0x00000000;
+        s->banks[b].imask = 0x00000000;
+        s->banks[b].istat = 0x00000000;
+        s->banks[b].itype = 0xffffffff;
+        s->banks[b].ipolarity = 0x00000000;
+        s->banks[b].iany = 0x00000000;
+    }
+}
+
+static void zynq_gpio_set_irq(void * opaque, int irq, int level)
+{
+    ZynqGPIOState *s = (ZynqGPIOState *)opaque;
+
+    GPIOBank *bank = &s->banks[irq / 32];
+    uint32_t mask = 1 << (irq % 32);
+
+    bank->in_data &= ~mask;
+    if (level)
+        bank->in_data |= mask;
+
+    zynq_gpio_update_in(bank);
+
+    zynq_gpio_set_in_irq(s);
+}
+
+static void zynq_gpio_set_mio_irq(void * opaque, int irq, int level)
+{
+    zynq_gpio_set_irq(opaque, irq + 0, level);
+}
+
+static void zynq_gpio_set_emio_irq(void * opaque, int irq, int level)
+{
+    zynq_gpio_set_irq(opaque, irq + 64, level);
+}
+
+static const MemoryRegionOps zynq_gpio_ops = {
+    .read = zynq_gpio_read,
+    .write = zynq_gpio_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int zynq_gpio_initfn(SysBusDevice *sbd)
+{
+    DeviceState *dev = DEVICE(sbd);
+    ZynqGPIOState *s = ZYNQ_GPIO(dev);
+
+    s->banks[0].out = &s->mio_out[0];
+    s->banks[1].out = &s->mio_out[32];
+    s->banks[2].out = &s->emio_out[0];
+    s->banks[3].out = &s->emio_out[32];
+
+    memory_region_init_io(&s->iomem, OBJECT(s), &zynq_gpio_ops, s, 
"zynq_gpio", 0x1000);
+    sysbus_init_mmio(sbd, &s->iomem);
+    sysbus_init_irq(sbd, &s->irq);
+
+    qdev_init_gpio_in_named(dev, zynq_gpio_set_mio_irq, "mio_in", 64);
+    qdev_init_gpio_in_named(dev, zynq_gpio_set_emio_irq, "emio_in", 64);
+
+    qdev_init_gpio_out_named(dev, s->mio_out, "mio_out", 64);
+    qdev_init_gpio_out_named(dev, s->emio_out, "emio_out", 64);
+
+    zynq_gpio_reset(s);
+
+    return 0;
+}
+
+static void zynq_gpio_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = zynq_gpio_initfn;
+}
+
+static const TypeInfo zynq_gpio_info = {
+    .name          = TYPE_ZYNQ_GPIO,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ZynqGPIOState),
+    .class_init    = zynq_gpio_class_init,
+};
+
+static void zynq_gpio_register_type(void)
+{
+    type_register_static(&zynq_gpio_info);
+}
+
+type_init(zynq_gpio_register_type)
-- 
1.7.10.4




reply via email to

[Prev in Thread] Current Thread [Next in Thread]