[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