diff mbox

[v3,1/2] zynq_gpio: GPIO model for Zynq SoC

Message ID 1420583203-12404-2-git-send-email-colin.leitner@gmail.com
State New
Headers show

Commit Message

Colin Leitner Jan. 6, 2015, 10:26 p.m. UTC
Based on the pl061 model. This model implements all four banks with 32 I/Os
each.

The I/Os are placed in named groups:

 * bankX_in for the 32 inputs of each bank
 * bankX_out for the 32 outputs of each bank

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

Signed-off-by: Colin Leitner <colin.leitner@gmail.com>
---
 hw/gpio/Makefile.objs       |    1 +
 hw/gpio/zynq-gpio.c         |  402 +++++++++++++++++++++++++++++++++++++++++++
 include/hw/gpio/zynq-gpio.h |   79 +++++++++
 3 files changed, 482 insertions(+)
 create mode 100644 hw/gpio/zynq-gpio.c
 create mode 100644 include/hw/gpio/zynq-gpio.h

Comments

Peter Crosthwaite Jan. 20, 2015, 10:05 p.m. UTC | #1
Hi Colin,


Sorry about the delay, review below.

On Tue, Jan 6, 2015 at 2:26 PM, Colin Leitner
<colin.leitner@googlemail.com> wrote:
> Based on the pl061 model. This model implements all four banks with 32 I/Os
> each.
>
> The I/Os are placed in named groups:
>
>  * bankX_in for the 32 inputs of each bank
>  * bankX_out for the 32 outputs of each bank
>
> Basic I/O and IRQ support tested with the Zynq GPIO driver in Linux 3.12.
>
> Signed-off-by: Colin Leitner <colin.leitner@gmail.com>
> ---
>  hw/gpio/Makefile.objs       |    1 +
>  hw/gpio/zynq-gpio.c         |  402 +++++++++++++++++++++++++++++++++++++++++++
>  include/hw/gpio/zynq-gpio.h |   79 +++++++++
>  3 files changed, 482 insertions(+)
>  create mode 100644 hw/gpio/zynq-gpio.c
>  create mode 100644 include/hw/gpio/zynq-gpio.h
>
> diff --git a/hw/gpio/Makefile.objs b/hw/gpio/Makefile.objs
> index 1abcf17..c927c66 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..8cf4262
> --- /dev/null
> +++ b/hw/gpio/zynq-gpio.c
> @@ -0,0 +1,402 @@
> +/*
> + * Zynq General Purpose IO
> + *
> + * Copyright (C) 2014 Colin Leitner <colin.leitner@gmail.com>
> + *
> + * 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 noticeable 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/gpio/zynq-gpio.h"
> +#include "qemu/bitops.h"
> +
> +#ifndef ZYNQ_GPIO_ERR_DEBUG
> +#define ZYNQ_GPIO_ERR_DEBUG 0
> +#endif
> +
> +#define DB_PRINT_L(lvl, fmt, args...) do {\
> +    if (ZYNQ_GPIO_ERR_DEBUG >= lvl) {\
> +        qemu_log("zynq-gpio: %s:" fmt, __func__, ## args);\
> +    } \
> +} while (0);
> +
> +#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
> +
> +static void zynq_gpio_update_out(ZynqGPIOBank *bank)
> +{
> +    uint32_t changed;
> +    uint32_t mask;
> +    uint32_t out;
> +    int i;
> +
> +    DB_PRINT("dir = %d, data = %d\n", bank->dir, bank->out_data);
> +
> +    /*
> +     * We assume non-driven (DIRM = 0) outputs float high. On real hardware this
> +     * could be different, but here we have to decide which value to set the
> +     * output IRQ to if the direction register switches the I/O to an input.
> +     */
> +    /* FIXME: This is board dependent. */
> +    out = (bank->out_data & bank->dir) | ~bank->dir;
> +
> +    changed = bank->old_out_data ^ out;
> +    bank->old_out_data = out;
> +
> +    for (i = 0; i < ZYNQ_GPIO_IOS_PER_BANK; i++) {
> +        mask = 1 << i;
> +        if (changed & mask) {
> +            DB_PRINT("Set output %d = %d\n", i, (out & mask) != 0);
> +            qemu_set_irq(bank->out[i], (out & mask) != 0);
> +        }
> +    }
> +}
> +
> +static void zynq_gpio_update_in(ZynqGPIOBank *bank)
> +{
> +    uint32_t changed;
> +    uint32_t mask;
> +    int i;
> +
> +    changed = bank->old_in_data ^ bank->in_data;
> +    bank->old_in_data = bank->in_data;
> +
> +    for (i = 0; i < ZYNQ_GPIO_IOS_PER_BANK; i++) {
> +        mask = 1 << i;
> +        if (changed & mask) {
> +            DB_PRINT("Changed input %d = %d\n", i, (bank->in_data & mask) != 0);
> +
> +            if (bank->itype & mask) {
> +                /* Edge interrupt */
> +                if (bank->iany & mask) {
> +                    /* Any edge triggers the interrupt */
> +                    bank->istat |= mask;
> +                } else {
> +                    /* Edge is selected by INT_POLARITY */
> +                    bank->istat |= ~(bank->in_data ^ bank->ipolarity) & mask;
> +                }
> +            }
> +        }
> +    }
> +
> +    /* Level interrupt */
> +    bank->istat |= ~(bank->in_data ^ bank->ipolarity) & ~bank->itype;
> +
> +    DB_PRINT("istat = %08X\n", bank->istat);
> +}
> +
> +static void zynq_gpio_set_in_irq(ZynqGPIOState *s)
> +{
> +    int b;
> +    uint32_t istat = 0;
> +
> +    for (b = 0; b < ZYNQ_GPIO_BANKS; b++) {
> +        ZynqGPIOBank *bank = &s->banks[b];
> +
> +        istat |= bank->istat & ~bank->imask;
> +    }
> +
> +    DB_PRINT("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 < ZYNQ_GPIO_BANKS; b++) {
> +        ZynqGPIOBank *bank = &s->banks[b];
> +
> +        zynq_gpio_update_out(bank);
> +        zynq_gpio_update_in(bank);
> +    }
> +
> +    zynq_gpio_set_in_irq(s);
> +}
> +
> +static uint64_t zynq_gpio_read(void *opaque, hwaddr offset,
> +                               unsigned int size)
> +{
> +    ZynqGPIOState *s = opaque;
> +    int b;
> +    int shift;
> +    ZynqGPIOBank *bank;
> +
> +    switch (offset) {
> +    case ZYNQ_GPIO_REG_MASK_DATA_0_LSW ... ZYNQ_GPIO_REG_MASK_DATA_3_MSW:
> +        b = extract32(offset - ZYNQ_GPIO_REG_MASK_DATA_0_LSW, 3, 2);
> +        shift = (offset & 0x8) ? 16 : 0;
> +        return extract32(s->banks[b].mask_data, shift, 16);
> +
> +    case ZYNQ_GPIO_REG_DATA_0 ... ZYNQ_GPIO_REG_DATA_3:
> +        b = (offset - ZYNQ_GPIO_REG_DATA_0) / 4;
> +        return s->banks[b].out_data;
> +
> +    case ZYNQ_GPIO_REG_DATA_0_RO ... ZYNQ_GPIO_REG_DATA_3_RO:
> +        b = (offset - ZYNQ_GPIO_REG_DATA_0_RO) / 4;
> +        return s->banks[b].in_data;
> +    }
> +
> +    if (offset < 0x204 || offset > 0x2e4) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                     "zynq_gpio_read: Bad offset %" HWADDR_PRIx "\n", offset);
> +        return 0;
> +    }
> +
> +    b = (offset - 0x200) / 0x40;
> +    bank = &s->banks[b];
> +
> +    switch (offset - ZYNQ_GPIO_BANK_OFFSET(b)) {
> +    case ZYNQ_GPIO_BANK_REG_DIRM:
> +        return bank->dir;
> +    case ZYNQ_GPIO_BANK_REG_OEN:
> +        return bank->oen;
> +    case ZYNQ_GPIO_BANK_REG_INT_MASK:
> +        return bank->imask;
> +    case ZYNQ_GPIO_BANK_REG_INT_EN:
> +        return 0;
> +    case ZYNQ_GPIO_BANK_REG_INT_DIS:
> +        return 0;
> +    case ZYNQ_GPIO_BANK_REG_INT_STAT:
> +        return bank->istat;
> +    case ZYNQ_GPIO_BANK_REG_INT_TYPE:
> +        return bank->itype;
> +    case ZYNQ_GPIO_BANK_REG_INT_POLARITY:
> +        return bank->ipolarity;
> +    case ZYNQ_GPIO_BANK_REG_INT_ANY:
> +        return bank->iany;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                     "zynq_gpio_read: Bad offset %" HWADDR_PRIx "\n", offset);
> +        return 0;
> +    }
> +}
> +
> +static void zynq_gpio_mask_data(ZynqGPIOBank *bank, int bit_offset,
> +                                uint32_t mask_data)
> +{
> +    DB_PRINT("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 = deposit32(bank->mask_data, bit_offset, 16, mask_data);
> +    bank->out_data = deposit32(bank->out_data, bit_offset, 16, mask_data);
> +
> +    zynq_gpio_update_out(bank);
> +}
> +
> +static void zynq_gpio_data(ZynqGPIOBank *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 int size)
> +{
> +    ZynqGPIOState *s = opaque;
> +    int b;
> +    int shift;
> +    ZynqGPIOBank *bank;
> +
> +    switch (offset) {
> +    case ZYNQ_GPIO_REG_MASK_DATA_0_LSW ... ZYNQ_GPIO_REG_MASK_DATA_3_MSW:
> +        b = extract32(offset - ZYNQ_GPIO_REG_MASK_DATA_0_LSW, 3, 2);
> +        shift = (offset & 0x8) ? 16 : 0;
> +        zynq_gpio_mask_data(&s->banks[b], shift, value);
> +        return;
> +
> +    case ZYNQ_GPIO_REG_DATA_0 ... ZYNQ_GPIO_REG_DATA_3:
> +        b = (offset - ZYNQ_GPIO_REG_DATA_0) / 4;
> +        zynq_gpio_data(&s->banks[b], value);
> +        return;
> +
> +    case ZYNQ_GPIO_REG_DATA_0_RO ... ZYNQ_GPIO_REG_DATA_3_RO:
> +        return;
> +    }
> +
> +    if (offset < 0x204 || offset > 0x2e4) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                     "zynq_gpio_write: Bad offset %" HWADDR_PRIx "\n", offset);
> +        return;
> +    }
> +
> +    b = (offset - 0x200) / 0x40;
> +    bank = &s->banks[b];
> +
> +    switch (offset - ZYNQ_GPIO_BANK_OFFSET(b)) {
> +    case ZYNQ_GPIO_BANK_REG_DIRM:
> +        bank->dir = value;
> +        break;
> +    case ZYNQ_GPIO_BANK_REG_OEN:
> +        bank->oen = value;
> +        qemu_log_mask(LOG_UNIMP,
> +                     "zynq_gpio_write: Output enable lines not implemented\n");
> +        break;
> +    case ZYNQ_GPIO_BANK_REG_INT_MASK:
> +        return;
> +    case ZYNQ_GPIO_BANK_REG_INT_EN:
> +        bank->imask &= ~value;
> +        break;
> +    case ZYNQ_GPIO_BANK_REG_INT_DIS:
> +        bank->imask |= value;
> +        break;
> +    case ZYNQ_GPIO_BANK_REG_INT_STAT:
> +        bank->istat &= ~value;
> +        break;
> +    case ZYNQ_GPIO_BANK_REG_INT_TYPE:
> +        bank->itype = value;
> +        break;
> +    case ZYNQ_GPIO_BANK_REG_INT_POLARITY:
> +        bank->ipolarity = value;
> +        break;
> +    case ZYNQ_GPIO_BANK_REG_INT_ANY:
> +        bank->iany = value;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                     "zynq_gpio_write: Bad offset %" HWADDR_PRIx "\n", offset);
> +        return;
> +    }
> +
> +    zynq_gpio_update(s);
> +}
> +
> +static void zynq_gpio_reset(DeviceState *dev)
> +{
> +    ZynqGPIOState *s = ZYNQ_GPIO(dev);
> +    int b;
> +
> +    for (b = 0; b < ZYNQ_GPIO_BANKS; b++) {
> +        ZynqGPIOBank *bank = &s->banks[b];
> +        int i;
> +
> +        bank->mask_data = 0x00000000;
> +        bank->dir = 0x00000000;
> +        bank->oen = 0x00000000;
> +        bank->imask = 0x00000000;
> +        bank->istat = 0x00000000;
> +        bank->itype = 0xffffffff;
> +        bank->ipolarity = 0x00000000;
> +        bank->iany = 0x00000000;
> +
> +        bank->out_data = 0x00000000;
> +        bank->old_out_data = 0x00000000;
> +        for (i = 0; i < ZYNQ_GPIO_IOS_PER_BANK; i++) {
> +            qemu_set_irq(bank->out[i], 0);
> +        }
> +
> +        bank->old_in_data = bank->in_data;
> +    }
> +}
> +
> +static void zynq_gpio_set_irq(ZynqGPIOBank *bank, int irq, int level)
> +{
> +    uint32_t mask = 1 << irq;
> +
> +    bank->in_data &= ~mask;
> +    if (level)
> +        bank->in_data |= mask;
> +

You need braces {} around the if body. Checkpatch should catch this.

> +    zynq_gpio_update_in(bank);
> +
> +    zynq_gpio_set_in_irq(bank->parent);
> +}
> +
> +static void zynq_gpio_set_bank0_irq(void *opaque, int irq, int level)
> +{
> +    ZynqGPIOState *s = opaque;


Blank line here between declarations and function body.

> +    zynq_gpio_set_irq(&s->banks[0], irq, level);
> +}
> +
> +static void zynq_gpio_set_bank1_irq(void *opaque, int irq, int level)
> +{
> +    ZynqGPIOState *s = opaque;
> +    zynq_gpio_set_irq(&s->banks[1], irq, level);
> +}
> +
> +static void zynq_gpio_set_bank2_irq(void *opaque, int irq, int level)
> +{
> +    ZynqGPIOState *s = opaque;
> +    zynq_gpio_set_irq(&s->banks[2], irq, level);
> +}
> +
> +static void zynq_gpio_set_bank3_irq(void *opaque, int irq, int level)
> +{
> +    ZynqGPIOState *s = opaque;
> +    zynq_gpio_set_irq(&s->banks[3], irq, level);
> +}
> +
> +static const MemoryRegionOps zynq_gpio_ops = {
> +    .read = zynq_gpio_read,
> +    .write = zynq_gpio_write,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +};
> +
> +static void zynq_gpio_realize(DeviceState *dev, Error **errp)
> +{
> +    ZynqGPIOState *s = ZYNQ_GPIO(dev);
> +    int b;
> +
> +    memory_region_init_io(&s->iomem, OBJECT(s), &zynq_gpio_ops, s, "zynq-gpio", 0x1000);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
> +    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
> +
> +    for (b = 0; b < ZYNQ_GPIO_BANKS; b++) {
> +        ZynqGPIOBank *bank = &s->banks[b];
> +        char name[16];
> +
> +        memset(bank, 0, sizeof(*bank));
> +

Is this needed? QOM should init struct fields to 0 for you.

> +        bank->parent = s;
> +
> +        snprintf(name, sizeof(name), "bank%d_out", b);
> +        qdev_init_gpio_out_named(dev, bank->out, name, ZYNQ_GPIO_IOS_PER_BANK);
> +        /*
> +         * TODO: it would be nice if we could pass the bank to the handler. This
> +         * would allow us to remove the 4 callbacks and use zynq_gpio_set_irq
> +         * directly.
> +         */
> +#if 0
> +        snprintf(name, sizeof(name), "bank%d_in", b);
> +        qdev_init_gpio_in_named(dev, zynq_gpio_set_irq, name, ZYNQ_GPIO_IOS_PER_BANK, bank);
> +#endif

Yes I am seeing the problem here now. But this is a better solution
than V1 so I think we are good here, pending a rethink of the GPIO API
WRT this problem.

With the small style issues fixed and a checkpatch pass, please add

Reviewed-by: Peter Crosthwaite <peter.crosthwaite@xilinx.com>

To the next rev.

Regards,
Peter

> +    }
> +    qdev_init_gpio_in_named(dev, zynq_gpio_set_bank0_irq, "bank0_in", ZYNQ_GPIO_IOS_PER_BANK);
> +    qdev_init_gpio_in_named(dev, zynq_gpio_set_bank1_irq, "bank1_in", ZYNQ_GPIO_IOS_PER_BANK);
> +    qdev_init_gpio_in_named(dev, zynq_gpio_set_bank2_irq, "bank2_in", ZYNQ_GPIO_IOS_PER_BANK);
> +    qdev_init_gpio_in_named(dev, zynq_gpio_set_bank3_irq, "bank3_in", ZYNQ_GPIO_IOS_PER_BANK);
> +}
> +
> +static void zynq_gpio_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = zynq_gpio_realize;
> +    dc->reset = zynq_gpio_reset;
> +}
> +
> +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)
> diff --git a/include/hw/gpio/zynq-gpio.h b/include/hw/gpio/zynq-gpio.h
> new file mode 100644
> index 0000000..8146c1f
> --- /dev/null
> +++ b/include/hw/gpio/zynq-gpio.h
> @@ -0,0 +1,79 @@
> +/*
> + * Zynq General Purpose IO
> + *
> + * Copyright (C) 2014 Colin Leitner <colin.leitner@gmail.com>
> + *
> + * This code is licensed under the GPL.
> + */
> +
> +#ifndef HW_ZYNQ_GPIO_H
> +#define HW_ZYNQ_GPIO_H
> +
> +#include "hw/sysbus.h"
> +
> +#define TYPE_ZYNQ_GPIO "zynq-gpio"
> +#define ZYNQ_GPIO(obj) OBJECT_CHECK(ZynqGPIOState, (obj), TYPE_ZYNQ_GPIO)
> +
> +#define ZYNQ_GPIO_BANKS         4
> +#define ZYNQ_GPIO_IOS_PER_BANK  32
> +
> +typedef struct {
> +    struct ZynqGPIOState *parent;
> +
> +    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[ZYNQ_GPIO_IOS_PER_BANK];
> +} ZynqGPIOBank;
> +
> +typedef struct ZynqGPIOState {
> +    SysBusDevice parent_obj;
> +
> +    MemoryRegion iomem;
> +    ZynqGPIOBank banks[ZYNQ_GPIO_BANKS];
> +    qemu_irq irq;
> +} ZynqGPIOState;
> +
> +#define ZYNQ_GPIO_REG_MASK_DATA_0_LSW   0x000
> +#define ZYNQ_GPIO_REG_MASK_DATA_0_MSW   0x004
> +#define ZYNQ_GPIO_REG_MASK_DATA_1_LSW   0x008
> +#define ZYNQ_GPIO_REG_MASK_DATA_1_MSW   0x00C
> +#define ZYNQ_GPIO_REG_MASK_DATA_2_LSW   0x010
> +#define ZYNQ_GPIO_REG_MASK_DATA_2_MSW   0x014
> +#define ZYNQ_GPIO_REG_MASK_DATA_3_LSW   0x018
> +#define ZYNQ_GPIO_REG_MASK_DATA_3_MSW   0x01C
> +#define ZYNQ_GPIO_REG_DATA_0            0x040
> +#define ZYNQ_GPIO_REG_DATA_1            0x044
> +#define ZYNQ_GPIO_REG_DATA_2            0x048
> +#define ZYNQ_GPIO_REG_DATA_3            0x04C
> +#define ZYNQ_GPIO_REG_DATA_0_RO         0x060
> +#define ZYNQ_GPIO_REG_DATA_1_RO         0x064
> +#define ZYNQ_GPIO_REG_DATA_2_RO         0x068
> +#define ZYNQ_GPIO_REG_DATA_3_RO         0x06C
> +
> +/*
> + * Oddly enough these registers are neatly grouped per bank and not interleaved
> + * like the data registers
> + */
> +#define ZYNQ_GPIO_BANK_OFFSET(bank)     (0x200 + 0x40 * (bank))
> +#define ZYNQ_GPIO_BANK_REG_DIRM         0x04
> +#define ZYNQ_GPIO_BANK_REG_OEN          0x08
> +#define ZYNQ_GPIO_BANK_REG_INT_MASK     0x0C
> +#define ZYNQ_GPIO_BANK_REG_INT_EN       0x10
> +#define ZYNQ_GPIO_BANK_REG_INT_DIS      0x14
> +#define ZYNQ_GPIO_BANK_REG_INT_STAT     0x18
> +#define ZYNQ_GPIO_BANK_REG_INT_TYPE     0x1C
> +#define ZYNQ_GPIO_BANK_REG_INT_POLARITY 0x20
> +#define ZYNQ_GPIO_BANK_REG_INT_ANY      0x24
> +
> +#endif
> --
> 1.7.10.4
>
>
diff mbox

Patch

diff --git a/hw/gpio/Makefile.objs b/hw/gpio/Makefile.objs
index 1abcf17..c927c66 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..8cf4262
--- /dev/null
+++ b/hw/gpio/zynq-gpio.c
@@ -0,0 +1,402 @@ 
+/*
+ * Zynq General Purpose IO
+ *
+ * Copyright (C) 2014 Colin Leitner <colin.leitner@gmail.com>
+ *
+ * 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 noticeable 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/gpio/zynq-gpio.h"
+#include "qemu/bitops.h"
+
+#ifndef ZYNQ_GPIO_ERR_DEBUG
+#define ZYNQ_GPIO_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(lvl, fmt, args...) do {\
+    if (ZYNQ_GPIO_ERR_DEBUG >= lvl) {\
+        qemu_log("zynq-gpio: %s:" fmt, __func__, ## args);\
+    } \
+} while (0);
+
+#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
+
+static void zynq_gpio_update_out(ZynqGPIOBank *bank)
+{
+    uint32_t changed;
+    uint32_t mask;
+    uint32_t out;
+    int i;
+
+    DB_PRINT("dir = %d, data = %d\n", bank->dir, bank->out_data);
+
+    /*
+     * We assume non-driven (DIRM = 0) outputs float high. On real hardware this
+     * could be different, but here we have to decide which value to set the
+     * output IRQ to if the direction register switches the I/O to an input.
+     */
+    /* FIXME: This is board dependent. */
+    out = (bank->out_data & bank->dir) | ~bank->dir;
+
+    changed = bank->old_out_data ^ out;
+    bank->old_out_data = out;
+
+    for (i = 0; i < ZYNQ_GPIO_IOS_PER_BANK; i++) {
+        mask = 1 << i;
+        if (changed & mask) {
+            DB_PRINT("Set output %d = %d\n", i, (out & mask) != 0);
+            qemu_set_irq(bank->out[i], (out & mask) != 0);
+        }
+    }
+}
+
+static void zynq_gpio_update_in(ZynqGPIOBank *bank)
+{
+    uint32_t changed;
+    uint32_t mask;
+    int i;
+
+    changed = bank->old_in_data ^ bank->in_data;
+    bank->old_in_data = bank->in_data;
+
+    for (i = 0; i < ZYNQ_GPIO_IOS_PER_BANK; i++) {
+        mask = 1 << i;
+        if (changed & mask) {
+            DB_PRINT("Changed input %d = %d\n", i, (bank->in_data & mask) != 0);
+
+            if (bank->itype & mask) {
+                /* Edge interrupt */
+                if (bank->iany & mask) {
+                    /* Any edge triggers the interrupt */
+                    bank->istat |= mask;
+                } else {
+                    /* Edge is selected by INT_POLARITY */
+                    bank->istat |= ~(bank->in_data ^ bank->ipolarity) & mask;
+                }
+            }
+        }
+    }
+
+    /* Level interrupt */
+    bank->istat |= ~(bank->in_data ^ bank->ipolarity) & ~bank->itype;
+
+    DB_PRINT("istat = %08X\n", bank->istat);
+}
+
+static void zynq_gpio_set_in_irq(ZynqGPIOState *s)
+{
+    int b;
+    uint32_t istat = 0;
+
+    for (b = 0; b < ZYNQ_GPIO_BANKS; b++) {
+        ZynqGPIOBank *bank = &s->banks[b];
+
+        istat |= bank->istat & ~bank->imask;
+    }
+
+    DB_PRINT("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 < ZYNQ_GPIO_BANKS; b++) {
+        ZynqGPIOBank *bank = &s->banks[b];
+
+        zynq_gpio_update_out(bank);
+        zynq_gpio_update_in(bank);
+    }
+
+    zynq_gpio_set_in_irq(s);
+}
+
+static uint64_t zynq_gpio_read(void *opaque, hwaddr offset,
+                               unsigned int size)
+{
+    ZynqGPIOState *s = opaque;
+    int b;
+    int shift;
+    ZynqGPIOBank *bank;
+
+    switch (offset) {
+    case ZYNQ_GPIO_REG_MASK_DATA_0_LSW ... ZYNQ_GPIO_REG_MASK_DATA_3_MSW:
+        b = extract32(offset - ZYNQ_GPIO_REG_MASK_DATA_0_LSW, 3, 2);
+        shift = (offset & 0x8) ? 16 : 0;
+        return extract32(s->banks[b].mask_data, shift, 16);
+
+    case ZYNQ_GPIO_REG_DATA_0 ... ZYNQ_GPIO_REG_DATA_3:
+        b = (offset - ZYNQ_GPIO_REG_DATA_0) / 4;
+        return s->banks[b].out_data;
+
+    case ZYNQ_GPIO_REG_DATA_0_RO ... ZYNQ_GPIO_REG_DATA_3_RO:
+        b = (offset - ZYNQ_GPIO_REG_DATA_0_RO) / 4;
+        return s->banks[b].in_data;
+    }
+
+    if (offset < 0x204 || offset > 0x2e4) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                     "zynq_gpio_read: Bad offset %" HWADDR_PRIx "\n", offset);
+        return 0;
+    }
+
+    b = (offset - 0x200) / 0x40;
+    bank = &s->banks[b];
+
+    switch (offset - ZYNQ_GPIO_BANK_OFFSET(b)) {
+    case ZYNQ_GPIO_BANK_REG_DIRM:
+        return bank->dir;
+    case ZYNQ_GPIO_BANK_REG_OEN:
+        return bank->oen;
+    case ZYNQ_GPIO_BANK_REG_INT_MASK:
+        return bank->imask;
+    case ZYNQ_GPIO_BANK_REG_INT_EN:
+        return 0;
+    case ZYNQ_GPIO_BANK_REG_INT_DIS:
+        return 0;
+    case ZYNQ_GPIO_BANK_REG_INT_STAT:
+        return bank->istat;
+    case ZYNQ_GPIO_BANK_REG_INT_TYPE:
+        return bank->itype;
+    case ZYNQ_GPIO_BANK_REG_INT_POLARITY:
+        return bank->ipolarity;
+    case ZYNQ_GPIO_BANK_REG_INT_ANY:
+        return bank->iany;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                     "zynq_gpio_read: Bad offset %" HWADDR_PRIx "\n", offset);
+        return 0;
+    }
+}
+
+static void zynq_gpio_mask_data(ZynqGPIOBank *bank, int bit_offset,
+                                uint32_t mask_data)
+{
+    DB_PRINT("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 = deposit32(bank->mask_data, bit_offset, 16, mask_data);
+    bank->out_data = deposit32(bank->out_data, bit_offset, 16, mask_data);
+
+    zynq_gpio_update_out(bank);
+}
+
+static void zynq_gpio_data(ZynqGPIOBank *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 int size)
+{
+    ZynqGPIOState *s = opaque;
+    int b;
+    int shift;
+    ZynqGPIOBank *bank;
+
+    switch (offset) {
+    case ZYNQ_GPIO_REG_MASK_DATA_0_LSW ... ZYNQ_GPIO_REG_MASK_DATA_3_MSW:
+        b = extract32(offset - ZYNQ_GPIO_REG_MASK_DATA_0_LSW, 3, 2);
+        shift = (offset & 0x8) ? 16 : 0;
+        zynq_gpio_mask_data(&s->banks[b], shift, value);
+        return;
+
+    case ZYNQ_GPIO_REG_DATA_0 ... ZYNQ_GPIO_REG_DATA_3:
+        b = (offset - ZYNQ_GPIO_REG_DATA_0) / 4;
+        zynq_gpio_data(&s->banks[b], value);
+        return;
+
+    case ZYNQ_GPIO_REG_DATA_0_RO ... ZYNQ_GPIO_REG_DATA_3_RO:
+        return;
+    }
+
+    if (offset < 0x204 || offset > 0x2e4) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                     "zynq_gpio_write: Bad offset %" HWADDR_PRIx "\n", offset);
+        return;
+    }
+
+    b = (offset - 0x200) / 0x40;
+    bank = &s->banks[b];
+
+    switch (offset - ZYNQ_GPIO_BANK_OFFSET(b)) {
+    case ZYNQ_GPIO_BANK_REG_DIRM:
+        bank->dir = value;
+        break;
+    case ZYNQ_GPIO_BANK_REG_OEN:
+        bank->oen = value;
+        qemu_log_mask(LOG_UNIMP,
+                     "zynq_gpio_write: Output enable lines not implemented\n");
+        break;
+    case ZYNQ_GPIO_BANK_REG_INT_MASK:
+        return;
+    case ZYNQ_GPIO_BANK_REG_INT_EN:
+        bank->imask &= ~value;
+        break;
+    case ZYNQ_GPIO_BANK_REG_INT_DIS:
+        bank->imask |= value;
+        break;
+    case ZYNQ_GPIO_BANK_REG_INT_STAT:
+        bank->istat &= ~value;
+        break;
+    case ZYNQ_GPIO_BANK_REG_INT_TYPE:
+        bank->itype = value;
+        break;
+    case ZYNQ_GPIO_BANK_REG_INT_POLARITY:
+        bank->ipolarity = value;
+        break;
+    case ZYNQ_GPIO_BANK_REG_INT_ANY:
+        bank->iany = value;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                     "zynq_gpio_write: Bad offset %" HWADDR_PRIx "\n", offset);
+        return;
+    }
+
+    zynq_gpio_update(s);
+}
+
+static void zynq_gpio_reset(DeviceState *dev)
+{
+    ZynqGPIOState *s = ZYNQ_GPIO(dev);
+    int b;
+
+    for (b = 0; b < ZYNQ_GPIO_BANKS; b++) {
+        ZynqGPIOBank *bank = &s->banks[b];
+        int i;
+
+        bank->mask_data = 0x00000000;
+        bank->dir = 0x00000000;
+        bank->oen = 0x00000000;
+        bank->imask = 0x00000000;
+        bank->istat = 0x00000000;
+        bank->itype = 0xffffffff;
+        bank->ipolarity = 0x00000000;
+        bank->iany = 0x00000000;
+
+        bank->out_data = 0x00000000;
+        bank->old_out_data = 0x00000000;
+        for (i = 0; i < ZYNQ_GPIO_IOS_PER_BANK; i++) {
+            qemu_set_irq(bank->out[i], 0);
+        }
+
+        bank->old_in_data = bank->in_data;
+    }
+}
+
+static void zynq_gpio_set_irq(ZynqGPIOBank *bank, int irq, int level)
+{
+    uint32_t mask = 1 << irq;
+
+    bank->in_data &= ~mask;
+    if (level)
+        bank->in_data |= mask;
+
+    zynq_gpio_update_in(bank);
+
+    zynq_gpio_set_in_irq(bank->parent);
+}
+
+static void zynq_gpio_set_bank0_irq(void *opaque, int irq, int level)
+{
+    ZynqGPIOState *s = opaque;
+    zynq_gpio_set_irq(&s->banks[0], irq, level);
+}
+
+static void zynq_gpio_set_bank1_irq(void *opaque, int irq, int level)
+{
+    ZynqGPIOState *s = opaque;
+    zynq_gpio_set_irq(&s->banks[1], irq, level);
+}
+
+static void zynq_gpio_set_bank2_irq(void *opaque, int irq, int level)
+{
+    ZynqGPIOState *s = opaque;
+    zynq_gpio_set_irq(&s->banks[2], irq, level);
+}
+
+static void zynq_gpio_set_bank3_irq(void *opaque, int irq, int level)
+{
+    ZynqGPIOState *s = opaque;
+    zynq_gpio_set_irq(&s->banks[3], irq, level);
+}
+
+static const MemoryRegionOps zynq_gpio_ops = {
+    .read = zynq_gpio_read,
+    .write = zynq_gpio_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void zynq_gpio_realize(DeviceState *dev, Error **errp)
+{
+    ZynqGPIOState *s = ZYNQ_GPIO(dev);
+    int b;
+
+    memory_region_init_io(&s->iomem, OBJECT(s), &zynq_gpio_ops, s, "zynq-gpio", 0x1000);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+
+    for (b = 0; b < ZYNQ_GPIO_BANKS; b++) {
+        ZynqGPIOBank *bank = &s->banks[b];
+        char name[16];
+
+        memset(bank, 0, sizeof(*bank));
+
+        bank->parent = s;
+
+        snprintf(name, sizeof(name), "bank%d_out", b);
+        qdev_init_gpio_out_named(dev, bank->out, name, ZYNQ_GPIO_IOS_PER_BANK);
+        /*
+         * TODO: it would be nice if we could pass the bank to the handler. This
+         * would allow us to remove the 4 callbacks and use zynq_gpio_set_irq
+         * directly.
+         */
+#if 0
+        snprintf(name, sizeof(name), "bank%d_in", b);
+        qdev_init_gpio_in_named(dev, zynq_gpio_set_irq, name, ZYNQ_GPIO_IOS_PER_BANK, bank);
+#endif
+    }
+    qdev_init_gpio_in_named(dev, zynq_gpio_set_bank0_irq, "bank0_in", ZYNQ_GPIO_IOS_PER_BANK);
+    qdev_init_gpio_in_named(dev, zynq_gpio_set_bank1_irq, "bank1_in", ZYNQ_GPIO_IOS_PER_BANK);
+    qdev_init_gpio_in_named(dev, zynq_gpio_set_bank2_irq, "bank2_in", ZYNQ_GPIO_IOS_PER_BANK);
+    qdev_init_gpio_in_named(dev, zynq_gpio_set_bank3_irq, "bank3_in", ZYNQ_GPIO_IOS_PER_BANK);
+}
+
+static void zynq_gpio_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = zynq_gpio_realize;
+    dc->reset = zynq_gpio_reset;
+}
+
+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)
diff --git a/include/hw/gpio/zynq-gpio.h b/include/hw/gpio/zynq-gpio.h
new file mode 100644
index 0000000..8146c1f
--- /dev/null
+++ b/include/hw/gpio/zynq-gpio.h
@@ -0,0 +1,79 @@ 
+/*
+ * Zynq General Purpose IO
+ *
+ * Copyright (C) 2014 Colin Leitner <colin.leitner@gmail.com>
+ *
+ * This code is licensed under the GPL.
+ */
+
+#ifndef HW_ZYNQ_GPIO_H
+#define HW_ZYNQ_GPIO_H
+
+#include "hw/sysbus.h"
+
+#define TYPE_ZYNQ_GPIO "zynq-gpio"
+#define ZYNQ_GPIO(obj) OBJECT_CHECK(ZynqGPIOState, (obj), TYPE_ZYNQ_GPIO)
+
+#define ZYNQ_GPIO_BANKS         4
+#define ZYNQ_GPIO_IOS_PER_BANK  32
+
+typedef struct {
+    struct ZynqGPIOState *parent;
+
+    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[ZYNQ_GPIO_IOS_PER_BANK];
+} ZynqGPIOBank;
+
+typedef struct ZynqGPIOState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    ZynqGPIOBank banks[ZYNQ_GPIO_BANKS];
+    qemu_irq irq;
+} ZynqGPIOState;
+
+#define ZYNQ_GPIO_REG_MASK_DATA_0_LSW   0x000
+#define ZYNQ_GPIO_REG_MASK_DATA_0_MSW   0x004
+#define ZYNQ_GPIO_REG_MASK_DATA_1_LSW   0x008
+#define ZYNQ_GPIO_REG_MASK_DATA_1_MSW   0x00C
+#define ZYNQ_GPIO_REG_MASK_DATA_2_LSW   0x010
+#define ZYNQ_GPIO_REG_MASK_DATA_2_MSW   0x014
+#define ZYNQ_GPIO_REG_MASK_DATA_3_LSW   0x018
+#define ZYNQ_GPIO_REG_MASK_DATA_3_MSW   0x01C
+#define ZYNQ_GPIO_REG_DATA_0            0x040
+#define ZYNQ_GPIO_REG_DATA_1            0x044
+#define ZYNQ_GPIO_REG_DATA_2            0x048
+#define ZYNQ_GPIO_REG_DATA_3            0x04C
+#define ZYNQ_GPIO_REG_DATA_0_RO         0x060
+#define ZYNQ_GPIO_REG_DATA_1_RO         0x064
+#define ZYNQ_GPIO_REG_DATA_2_RO         0x068
+#define ZYNQ_GPIO_REG_DATA_3_RO         0x06C
+
+/*
+ * Oddly enough these registers are neatly grouped per bank and not interleaved
+ * like the data registers
+ */
+#define ZYNQ_GPIO_BANK_OFFSET(bank)     (0x200 + 0x40 * (bank))
+#define ZYNQ_GPIO_BANK_REG_DIRM         0x04
+#define ZYNQ_GPIO_BANK_REG_OEN          0x08
+#define ZYNQ_GPIO_BANK_REG_INT_MASK     0x0C
+#define ZYNQ_GPIO_BANK_REG_INT_EN       0x10
+#define ZYNQ_GPIO_BANK_REG_INT_DIS      0x14
+#define ZYNQ_GPIO_BANK_REG_INT_STAT     0x18
+#define ZYNQ_GPIO_BANK_REG_INT_TYPE     0x1C
+#define ZYNQ_GPIO_BANK_REG_INT_POLARITY 0x20
+#define ZYNQ_GPIO_BANK_REG_INT_ANY      0x24
+
+#endif