/*
 * QEMU model of the GPIO gpio Registers
 *
 * Copyright (c) 2017 Xilinx Inc.
 *
 * Partly autogenerated by xregqemu.py 2017-02-11.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "qemu/osdep.h"
#include "hw/sysbus.h"
#include "hw/register.h"
#include "qemu/bitops.h"
#include "migration/vmstate.h"
#include "hw/qdev-properties.h"
#include "qemu/log.h"

#include "hw/fdt_generic_util.h"

#ifndef XLNX_ZYNQMP_GPIO_ERR_DEBUG
#define XLNX_ZYNQMP_GPIO_ERR_DEBUG 0
#endif

#define TYPE_XLNX_ZYNQMP_GPIO "xlnx.zynqmp-gpio"

#define XLNX_ZYNQMP_GPIO(obj) \
     OBJECT_CHECK(XlnxZynqmpGPIO, (obj), TYPE_XLNX_ZYNQMP_GPIO)

#define DPRINT(...) \
    if (XLNX_ZYNQMP_GPIO_ERR_DEBUG) {  \
        qemu_log("%s:", __func__);     \
        qemu_log(__VA_ARGS__);         \
    }

REG32(MASK_DATA_0_LSW, 0x0)
    FIELD(MASK_DATA_0_LSW, MASK_0_LSW, 16, 16)
    FIELD(MASK_DATA_0_LSW, DATA_0_LSW, 0, 16)
REG32(MASK_DATA_0_MSW, 0x4)
    FIELD(MASK_DATA_0_MSW, MASK_0_MSW, 16, 10)
    FIELD(MASK_DATA_0_MSW, DATA_0_MSW, 0, 10)
REG32(MASK_DATA_1_LSW, 0x8)
    FIELD(MASK_DATA_1_LSW, MASK_1_LSW, 16, 16)
    FIELD(MASK_DATA_1_LSW, DATA_1_LSW, 0, 16)
REG32(MASK_DATA_1_MSW, 0xc)
    FIELD(MASK_DATA_1_MSW, MASK_1_MSW, 16, 10)
    FIELD(MASK_DATA_1_MSW, DATA_1_MSW, 0, 10)
REG32(MASK_DATA_2_LSW, 0x10)
    FIELD(MASK_DATA_2_LSW, MASK_2_LSW, 16, 16)
    FIELD(MASK_DATA_2_LSW, DATA_2_LSW, 0, 16)
REG32(MASK_DATA_2_MSW, 0x14)
    FIELD(MASK_DATA_2_MSW, MASK_2_MSW, 16, 10)
    FIELD(MASK_DATA_2_MSW, DATA_2_MSW, 0, 10)
REG32(MASK_DATA_3_LSW, 0x18)
    FIELD(MASK_DATA_3_LSW, MASK_3_LSW, 16, 16)
    FIELD(MASK_DATA_3_LSW, DATA_3_LSW, 0, 16)
REG32(MASK_DATA_3_MSW, 0x1c)
    FIELD(MASK_DATA_3_MSW, MASK_3_MSW, 16, 16)
    FIELD(MASK_DATA_3_MSW, DATA_3_MSW, 0, 16)
REG32(MASK_DATA_4_LSW, 0x20)
    FIELD(MASK_DATA_4_LSW, MASK_4_LSW, 16, 16)
    FIELD(MASK_DATA_4_LSW, DATA_4_LSW, 0, 16)
REG32(MASK_DATA_4_MSW, 0x24)
    FIELD(MASK_DATA_4_MSW, MASK_4_MSW, 16, 16)
    FIELD(MASK_DATA_4_MSW, DATA_4_MSW, 0, 16)
REG32(MASK_DATA_5_LSW, 0x28)
    FIELD(MASK_DATA_5_LSW, MASK_5_LSW, 16, 16)
    FIELD(MASK_DATA_5_LSW, DATA_5_LSW, 0, 16)
REG32(MASK_DATA_5_MSW, 0x2c)
    FIELD(MASK_DATA_5_MSW, MASK_5_MSW, 16, 16)
    FIELD(MASK_DATA_5_MSW, DATA_5_MSW, 0, 16)
REG32(DATA_0, 0x40)
    FIELD(DATA_0, DATA_0, 0, 26)
REG32(DATA_1, 0x44)
    FIELD(DATA_1, DATA_1, 0, 26)
REG32(DATA_2, 0x48)
    FIELD(DATA_2, DATA_2, 0, 26)
REG32(DATA_3, 0x4c)
REG32(DATA_4, 0x50)
REG32(DATA_5, 0x54)
REG32(DATA_0_RO, 0x60)
    FIELD(DATA_0_RO, DATA_0_RO, 0, 26)
REG32(DATA_1_RO, 0x64)
    FIELD(DATA_1_RO, DATA_1_RO, 0, 26)
REG32(DATA_2_RO, 0x68)
    FIELD(DATA_2_RO, DATA_2_RO, 0, 26)
REG32(DATA_3_RO, 0x6c)
REG32(DATA_4_RO, 0x70)
REG32(DATA_5_RO, 0x74)
REG32(DIRM_0, 0x204)
    FIELD(DIRM_0, DIRECTION_0, 0, 26)
REG32(OEN_0, 0x208)
    FIELD(OEN_0, OP_ENABLE_0, 0, 26)
REG32(INT_MASK_0, 0x20c)
    FIELD(INT_MASK_0, INT_MASK_0, 0, 26)
REG32(INT_EN_0, 0x210)
    FIELD(INT_EN_0, INT_ENABLE_0, 0, 26)
REG32(INT_DIS_0, 0x214)
    FIELD(INT_DIS_0, INT_DISABLE_0, 0, 26)
REG32(INT_STAT_0, 0x218)
    FIELD(INT_STAT_0, INT_STATUS_0, 0, 26)
REG32(INT_TYPE_0, 0x21c)
    FIELD(INT_TYPE_0, INT_TYPE_0, 0, 26)
REG32(INT_POLARITY_0, 0x220)
    FIELD(INT_POLARITY_0, INT_POL_0, 0, 26)
REG32(INT_ANY_0, 0x224)
    FIELD(INT_ANY_0, INT_ON_ANY_0, 0, 26)
REG32(DIRM_1, 0x244)
    FIELD(DIRM_1, DIRECTION_1, 0, 26)
REG32(OEN_1, 0x248)
    FIELD(OEN_1, OP_ENABLE_1, 0, 26)
REG32(INT_MASK_1, 0x24c)
    FIELD(INT_MASK_1, INT_MASK_1, 0, 26)
REG32(INT_EN_1, 0x250)
    FIELD(INT_EN_1, INT_ENABLE_1, 0, 26)
REG32(INT_DIS_1, 0x254)
    FIELD(INT_DIS_1, INT_DISABLE_1, 0, 26)
REG32(INT_STAT_1, 0x258)
    FIELD(INT_STAT_1, INT_STATUS_1, 0, 26)
REG32(INT_TYPE_1, 0x25c)
    FIELD(INT_TYPE_1, INT_TYPE_1, 0, 26)
REG32(INT_POLARITY_1, 0x260)
    FIELD(INT_POLARITY_1, INT_POL_1, 0, 26)
REG32(INT_ANY_1, 0x264)
    FIELD(INT_ANY_1, INT_ON_ANY_1, 0, 26)
REG32(DIRM_2, 0x284)
    FIELD(DIRM_2, DIRECTION_2, 0, 26)
REG32(OEN_2, 0x288)
    FIELD(OEN_2, OP_ENABLE_2, 0, 26)
REG32(INT_MASK_2, 0x28c)
    FIELD(INT_MASK_2, INT_MASK_2, 0, 26)
REG32(INT_EN_2, 0x290)
    FIELD(INT_EN_2, INT_ENABLE_2, 0, 26)
REG32(INT_DIS_2, 0x294)
    FIELD(INT_DIS_2, INT_DISABLE_2, 0, 26)
REG32(INT_STAT_2, 0x298)
    FIELD(INT_STAT_2, INT_STATUS_2, 0, 26)
REG32(INT_TYPE_2, 0x29c)
    FIELD(INT_TYPE_2, INT_TYPE_2, 0, 26)
REG32(INT_POLARITY_2, 0x2a0)
    FIELD(INT_POLARITY_2, INT_POL_2, 0, 26)
REG32(INT_ANY_2, 0x2a4)
    FIELD(INT_ANY_2, INT_ON_ANY_2, 0, 26)
REG32(DIRM_3, 0x2c4)
REG32(OEN_3, 0x2c8)
REG32(INT_MASK_3, 0x2cc)
REG32(INT_EN_3, 0x2d0)
REG32(INT_DIS_3, 0x2d4)
REG32(INT_STAT_3, 0x2d8)
REG32(INT_TYPE_3, 0x2dc)
REG32(INT_POLARITY_3, 0x2e0)
REG32(INT_ANY_3, 0x2e4)
REG32(DIRM_4, 0x304)
REG32(OEN_4, 0x308)
REG32(INT_MASK_4, 0x30c)
REG32(INT_EN_4, 0x310)
REG32(INT_DIS_4, 0x314)
REG32(INT_STAT_4, 0x318)
REG32(INT_TYPE_4, 0x31c)
REG32(INT_POLARITY_4, 0x320)
REG32(INT_ANY_4, 0x324)
REG32(DIRM_5, 0x344)
REG32(OEN_5, 0x348)
REG32(INT_MASK_5, 0x34c)
REG32(INT_EN_5, 0x350)
REG32(INT_DIS_5, 0x354)
REG32(INT_STAT_5, 0x358)
REG32(INT_TYPE_5, 0x35c)
REG32(INT_POLARITY_5, 0x360)
REG32(INT_ANY_5, 0x364)

#define R_MAX (R_INT_ANY_5 + 1)

#define ZYNQMP_GPIO_BANK0 0
#define ZYNQMP_GPIO_BANK1 26
#define ZYNQMP_GPIO_BANK2 52
#define ZYNQMP_GPIO_BANK3 78
#define ZYNQMP_GPIO_BANK4 110
#define ZYNQMP_GPIO_BANK5 142

#define ZYNQMP_NUM_GPIOS              174
#define ZYNQMP_NUM_OEN_SIGNALS        ZYNQMP_NUM_GPIOS
#define ZYNQMP_NUM_EMIO_PINS_PER_BANK  32
#define ZYNQMP_NUM_MIO_PINS_PER_BANK   26
#define ZYNQMP_GPIO_NUM_BANKS           6
#define ZYNQMP_GPIO_NUM_MIO_BANKS       3
#define ZYNQMP_GPIO_NUM_EMIO_BANKS      3
#define ZYNQMP_GPIO_EMIO_START_OFFSET  ZYNQMP_GPIO_BANK3
#define ZYNQMP_GPIO_MIO_START_OFFSET   ZYQNMP_GPIO_BANK0

#define R_GPIO_DATA_X(bank)      ((A_DATA_0 + (bank * 0x4)) / 4)

#define R_GPIO_DIRM_X(bank)      ((A_DIRM_0 + (bank * 0x40)) / 4)
#define R_GPIO_OEN_X(bank)       ((A_OEN_0 + (bank * 0x40)) / 4)
#define R_GPIO_INT_MASK_X(bank)  ((A_INT_MASK_0 + (bank * 0x40)) / 4)
#define R_GPIO_INT_STAT_X(bank)  ((A_INT_STAT_0 + (bank * 0x40)) / 4)
#define R_GPIO_INT_POL_X(bank)   ((A_INT_POLARITY_0 + (bank * 0x40)) / 4)

typedef struct XlnxZynqmpGPIO {
    SysBusDevice parent_obj;
    MemoryRegion iomem;

    qemu_irq irq;
    qemu_irq *gpio_out;
    qemu_irq *gpio_oen;

    bool por_done;
    uint32_t regs[R_MAX];
    RegisterInfo regs_info[R_MAX];
} XlnxZynqmpGPIO;

/* Returns Bank number based on addr passed
 */
static int gpio_get_bank(hwaddr addr)
{
    switch (addr & 0xFFF) {
    case 0x0 ... 0x2c:
        return addr / 8;
    case 0x40 ... 0x54:
        addr -= 0x40;
        return addr / 4;
    case 0x60 ... 0x74:
        addr -= 0x60;
        return addr / 4;
    case 0x204 ... 0x364:
        return (addr >> 6) & 0x7;
    default:
        DPRINT("Register offset doesn't belong to any bank");
    };
    return 0;
}

static int gpio_get_bank_by_pin(int pin, int *offset)
{
    switch (pin) {
    case ZYNQMP_GPIO_BANK0 ... (ZYNQMP_GPIO_BANK1 - 1):
        *offset = pin - ZYNQMP_GPIO_BANK0;
        return 0;
    case ZYNQMP_GPIO_BANK1 ... (ZYNQMP_GPIO_BANK2 - 1):
        *offset = pin - ZYNQMP_GPIO_BANK1;
        return 1;
    case ZYNQMP_GPIO_BANK2 ... (ZYNQMP_GPIO_BANK3 - 1):
        *offset = pin - ZYNQMP_GPIO_BANK2;
        return 2;
    case ZYNQMP_GPIO_BANK3 ... (ZYNQMP_GPIO_BANK4 - 1):
        *offset = pin - ZYNQMP_GPIO_BANK3;
        return 3;
    case ZYNQMP_GPIO_BANK4 ... (ZYNQMP_GPIO_BANK5 - 1):
        *offset = pin - ZYNQMP_GPIO_BANK4;
        return 4;
    case ZYNQMP_GPIO_BANK5 ... (ZYNQMP_NUM_GPIOS - 1):
        *offset = pin - ZYNQMP_GPIO_BANK5;
        return 5;
    default:
        DPRINT("Pin number exceeded max gpios\n");
    };
    return 0;
}

static void gpio_update_irq(XlnxZynqmpGPIO *s)
{
    bool line;
    int i;

    /* if mask high, interrupt disabled
     * if mask low, interrupt enabled
     */
    for (i = 0; i < ZYNQMP_GPIO_NUM_BANKS; i++) {
        line = !!(~s->regs[R_GPIO_INT_MASK_X(i)] &
                   s->regs[R_GPIO_INT_STAT_X(i)]);
        qemu_set_irq(s->irq, line);
    }
}

static void gpio_update_isr(XlnxZynqmpGPIO *s, int bank, int pin_offset,
                            uint32_t level, uint32_t level_old)
{
    uint32_t pol = s->regs[R_GPIO_INT_POL_X(bank)] & (1 << pin_offset);

    if ((level == 0 && level_old == 1 && !pol) || /* Falling Edge */
       (level == 1 && level_old == 0 && pol)) {   /* Raising Edge */
        s->regs[R_GPIO_INT_STAT_X(bank)] =
           deposit32(s->regs[R_GPIO_INT_STAT_X(bank)], pin_offset, 1, level);
    }
}

static void gpio_update_pins(XlnxZynqmpGPIO *s, int bank, int offset, int width,
                             uint32_t mask, uint32_t val, uint32_t val_old)
{
    int pin = 0;
    uint32_t i;
    /* Get the banks first pin number in s->gpio_out
     */
    switch (bank) {
    case 0:
        pin = ZYNQMP_GPIO_BANK0 + offset;
        break;
    case 1:
        pin = ZYNQMP_GPIO_BANK1 + offset;
        break;
    case 2:
        pin = ZYNQMP_GPIO_BANK2 + offset;
        break;
    case 3:
        pin = ZYNQMP_GPIO_BANK3 + offset;
        break;
    case 4:
        pin = ZYNQMP_GPIO_BANK4 + offset;
        break;
    case 5:
        pin = ZYNQMP_GPIO_BANK5 + offset;
        break;
    };

    for (i = pin; i < pin + width; i++) {
        /* 'i' is pin-offset of the s->gpio_out
         * i - pin should provice bank-pin-offset */
        gpio_update_isr(s, bank, i - pin, extract32(val, i - pin, 1),
                        extract32(val_old, i - pin, 1));
        if (mask & (1 << (i - pin))) {
            DPRINT("gpio out[%d] set to %d\n", i, extract32(val, i - pin, 1));
            qemu_set_irq(s->gpio_out[i], extract32(val, i - pin, 1));
        }
    }
}

static void zynqmp_gpio_in_handler(void *opaque, int n, int level)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(opaque);
    int offset = 0;
    int bank = gpio_get_bank_by_pin(n, &offset);
    uint32_t mask;
    uint32_t data_old = s->regs[R_GPIO_DATA_X(bank)];

    /* Accept the data only if gpio is set as input */
    mask = ~(s->regs[R_GPIO_DIRM_X(bank)]);

    if (mask & (1 << offset)) {
        DPRINT("gpio in[%d] set to %d\n", n, level);
        s->regs[R_GPIO_DATA_X(bank)] =
            deposit32(s->regs[R_GPIO_DATA_X(bank)], offset, 1, level);

        gpio_update_isr(s, bank, offset, level,
                        extract32(data_old, offset, 1));
    }
    gpio_update_irq(s);
}

static uint64_t gpio_data_reg_prew(RegisterInfo *reg, uint64_t val)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(reg->opaque);
    uint32_t data = (uint32_t) val;
    int bank = gpio_get_bank(reg->access->addr);
    int width;
    uint32_t data_old = s->regs[R_GPIO_DATA_X(bank)];
    uint32_t mask;

    /* BANK 0,1,2 have 26 pins each
     * BANK 3,4,5 have 32 pins each
     */
    width = bank < 3 ? 26 : 32;

    mask = (s->regs[R_GPIO_OEN_X(bank)] &
            s->regs[R_GPIO_DIRM_X(bank)]);

    gpio_update_pins(s, bank, 0, width, mask, data, data_old);
    return val;
}

static uint64_t gpio_mask_data_lsw_prew(RegisterInfo *reg, uint64_t val)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(reg->opaque);
    uint32_t data = (uint32_t) val;
    uint16_t maskw = data >> 16;
    uint32_t data_old = 0;
    int bank = gpio_get_bank(reg->access->addr);
    uint32_t data_reg = R_GPIO_DATA_X(bank);
    uint32_t mask;

    /* Clear the mask value from data*/
    data &= (1 << 16) - 1;
    data_old = s->regs[data_reg] & 0x0000FFFF;
    /* Pins corresponding to unmasked bits of maskw are allowed to update
     * i.e high bit in ~maskw */
    s->regs[data_reg] = deposit32(s->regs[data_reg], 0, 16,
                                  (data_old & maskw) | (~maskw & data));

    mask = (s->regs[R_GPIO_OEN_X(bank)] &
            s->regs[R_GPIO_DIRM_X(bank)]);
    gpio_update_pins(s, bank, 0, 16, (mask & 0x0000FFFF),
                     (s->regs[data_reg] & 0x0000FFFF),
                      data_old);
    return val;
}

static uint64_t gpio_mask_data_msw_prew(RegisterInfo *reg, uint64_t val)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(reg->opaque);
    uint32_t data = (uint32_t) val;
    uint16_t maskw = data >> 16;
    uint32_t data_old = 0;
    int width;
    int bank = gpio_get_bank(reg->access->addr);
    uint32_t data_reg = R_GPIO_DATA_X(bank);
    uint32_t mask;

    /* BANK 0,1,2 have 10 pins and
     * BANK 3,4,5 have 16 pins in
     * the upper-half of the bank
     */
    width = bank < 3 ? 10 : 16;

    data &= (1 << width) - 1;
    data_old = s->regs[data_reg] >> 16;

    s->regs[data_reg] = deposit32(s->regs[data_reg], 16, width,
                                  (data_old & maskw) | (~maskw & data));

    mask = (s->regs[R_GPIO_OEN_X(bank)] &
            s->regs[R_GPIO_DIRM_X(bank)]);
    gpio_update_pins(s, bank, 16, width, (mask >> 16),
                     (s->regs[data_reg] >> 16), data_old);
    return val;
}

static uint64_t gpio_reg_wo(RegisterInfo *reg, uint64_t val)
{
    return 0;
}

static uint64_t gpio_data_ro_postr(RegisterInfo *reg, uint64_t val)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(reg->opaque);
    int bank = gpio_get_bank(reg->access->addr);

    return s->regs[R_GPIO_DATA_X(bank)];
}

static void gpio_int_en_postw(RegisterInfo *reg, uint64_t val)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(reg->opaque);
    uint32_t data = (uint32_t) val;
    int bank = gpio_get_bank(reg->access->addr);

    s->regs[R_GPIO_INT_MASK_X(bank)] &= ~data;
}

static void gpio_int_dis_postw(RegisterInfo *reg, uint64_t val)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(reg->opaque);
    uint32_t data = (uint32_t) val;
    int bank = gpio_get_bank(reg->access->addr);

    s->regs[R_GPIO_INT_MASK_X(bank)] |= data;
}

static uint64_t gpio_oen_prew(RegisterInfo *reg, uint64_t val)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(reg->opaque);
    uint32_t data = (uint32_t) val;
    uint32_t data_old = *(uint32_t *)reg->data;
    int bank = gpio_get_bank(reg->access->addr);
    int oen_pin;
    int width;
    int i;

    if (bank < ZYNQMP_GPIO_NUM_MIO_BANKS) {
        /* Calculate the oen signal offset for pins mapped to mio */
        oen_pin = bank * ZYNQMP_NUM_MIO_PINS_PER_BANK;
        width = ZYNQMP_NUM_MIO_PINS_PER_BANK;
    } else if (bank < ZYNQMP_GPIO_NUM_BANKS) {
        /* Calculate the oen signal offset for emios.
         * EMIO's would start from bank 3 */
        oen_pin = ZYNQMP_GPIO_EMIO_START_OFFSET +
                  (bank - ZYNQMP_GPIO_NUM_MIO_BANKS) *
                   ZYNQMP_NUM_EMIO_PINS_PER_BANK;
        width = ZYNQMP_NUM_EMIO_PINS_PER_BANK;
    } else {
        DPRINT("bank %d not available\n", bank);
        return val;
    }

    for (i = 0; i < width; i++) {
        /* Change in the oen value should update the signal */
        if ((data_old ^ data) & (1 << i)) {
            qemu_set_irq(s->gpio_oen[oen_pin + i], extract32(data, i, 1));
        }
    }

    return val;
}

static RegisterAccessInfo gpio_regs_info[] = {
    {   .name = "MASK_DATA_0_LSW",
        .addr = A_MASK_DATA_0_LSW,
        .pre_write = gpio_mask_data_lsw_prew,
        .post_read = gpio_reg_wo,
    },{ .name = "MASK_DATA_0_MSW",
        .addr = A_MASK_DATA_0_MSW,
        .rsvd = 0xfc00fc00,
        .pre_write = gpio_mask_data_msw_prew,
        .post_read = gpio_reg_wo,
    },{   .name = "MASK_DATA_1_LSW",
        .addr = A_MASK_DATA_1_LSW,
        .pre_write = gpio_mask_data_lsw_prew,
        .post_read = gpio_reg_wo,
    },{ .name = "MASK_DATA_1_MSW",
        .addr = A_MASK_DATA_1_MSW,
        .rsvd = 0xfc00fc00,
        .pre_write = gpio_mask_data_msw_prew,
        .post_read = gpio_reg_wo,
    },{   .name = "MASK_DATA_2_LSW",
        .addr = A_MASK_DATA_2_LSW,
        .pre_write = gpio_mask_data_lsw_prew,
        .post_read = gpio_reg_wo,
    },{ .name = "MASK_DATA_2_MSW",
        .addr = A_MASK_DATA_2_MSW,
        .rsvd = 0xfc00fc00,
        .pre_write = gpio_mask_data_msw_prew,
        .post_read = gpio_reg_wo,
    },{   .name = "MASK_DATA_3_LSW",
        .addr = A_MASK_DATA_3_LSW,
        .pre_write = gpio_mask_data_lsw_prew,
        .post_read = gpio_reg_wo,
    },{ .name = "MASK_DATA_3_MSW",
        .addr = A_MASK_DATA_3_MSW,
        .pre_write = gpio_mask_data_msw_prew,
        .post_read = gpio_reg_wo,
    },{   .name = "MASK_DATA_4_LSW",
        .addr = A_MASK_DATA_4_LSW,
        .pre_write = gpio_mask_data_lsw_prew,
        .post_read = gpio_reg_wo,
    },{ .name = "MASK_DATA_4_MSW",
        .addr = A_MASK_DATA_4_MSW,
        .pre_write = gpio_mask_data_msw_prew,
        .post_read = gpio_reg_wo,
    },{   .name = "MASK_DATA_5_LSW",
        .addr = A_MASK_DATA_5_LSW,
        .pre_write = gpio_mask_data_lsw_prew,
        .post_read = gpio_reg_wo,
    },{ .name = "MASK_DATA_5_MSW",
        .addr = A_MASK_DATA_5_MSW,
        .pre_write = gpio_mask_data_msw_prew,
        .post_read = gpio_reg_wo,
    },{ .name = "DATA_0",  .addr = A_DATA_0,
        .pre_write = gpio_data_reg_prew,
        .rsvd = 0xfc000000,
    },{ .name = "DATA_1",  .addr = A_DATA_1,
        .pre_write = gpio_data_reg_prew,
        .rsvd = 0xfc000000,
    },{ .name = "DATA_2",  .addr = A_DATA_2,
        .pre_write = gpio_data_reg_prew,
        .rsvd = 0xfc000000,
    },{ .name = "DATA_3",  .addr = A_DATA_3,
        .pre_write = gpio_data_reg_prew,
    },{ .name = "DATA_4",  .addr = A_DATA_4,
        .pre_write = gpio_data_reg_prew,
    },{ .name = "DATA_5",  .addr = A_DATA_5,
        .pre_write = gpio_data_reg_prew,
    },{ .name = "DATA_0_RO",
        .addr = A_DATA_0_RO,
        .rsvd = 0xfc000000,
        .ro = 0xffffffff,
        .post_read = gpio_data_ro_postr,
    },{ .name = "DATA_1_RO",
        .addr = A_DATA_1_RO,
        .rsvd = 0xfc000000,
        .ro = 0xffffffff,
        .post_read = gpio_data_ro_postr,
    },{ .name = "DATA_2_RO",
        .addr = A_DATA_2_RO,
        .rsvd = 0xfc000000,
        .ro = 0xffffffff,
        .post_read = gpio_data_ro_postr,
    },{ .name = "DATA_3_RO",
        .addr = A_DATA_3_RO,
        .ro = 0xffffffff,
        .post_read = gpio_data_ro_postr,
    },{ .name = "DATA_4_RO",
        .addr = A_DATA_4_RO,
        .ro = 0xffffffff,
        .post_read = gpio_data_ro_postr,
    },{ .name = "DATA_5_RO",
        .addr = A_DATA_5_RO,
        .ro = 0xffffffff,
        .post_read = gpio_data_ro_postr,
    },{ .name = "DIRM_0",  .addr = A_DIRM_0,
        .rsvd = 0xfc000000,
    },{ .name = "OEN_0",  .addr = A_OEN_0,
        .rsvd = 0xfc000000,
        .pre_write = gpio_oen_prew,
    },{ .name = "INT_MASK_0",
        .addr = A_INT_MASK_0,
        .reset = 0x3ffffff,
        .rsvd = 0xfc000000,
        .ro = 0x3ffffff,
    },{ .name = "INT_EN_0",  .addr = A_INT_EN_0,
        .post_write = gpio_int_en_postw,
        .rsvd = 0xfc000000,
    },{ .name = "INT_DIS_0",
        .addr = A_INT_DIS_0,
        .rsvd = 0xfc000000,
        .post_write = gpio_int_dis_postw,
    },{ .name = "INT_STAT_0",
        .addr = A_INT_STAT_0,
        .w1c = 0x3ffffff,
        .rsvd = 0xfc000000,
    },{ .name = "INT_TYPE_0",
        .addr = A_INT_TYPE_0,
        .rsvd = 0xfc000000,
        .reset = 0x3ffffff,
    },{ .name = "INT_POLARITY_0",
        .addr = A_INT_POLARITY_0,
        .rsvd = 0xfc000000,
    },{ .name = "INT_ANY_0",
        .addr = A_INT_ANY_0,
        .rsvd = 0xfc000000,
    },{ .name = "DIRM_1", .addr = A_DIRM_1,
        .rsvd = 0xfc000000,
    },{ .name = "OEN_1",  .addr = A_OEN_1,
        .rsvd = 0xfc000000,
        .pre_write = gpio_oen_prew,
    },{ .name = "INT_MASK_1",
        .addr = A_INT_MASK_1,
        .reset = 0x3ffffff,
        .rsvd = 0xfc000000,
        .ro = 0x3ffffff,
    },{ .name = "INT_EN_1",  .addr = A_INT_EN_1,
        .rsvd = 0xfc000000,
        .post_write = gpio_int_en_postw,
    },{ .name = "INT_DIS_1",
        .rsvd = 0xfc000000,
        .addr = A_INT_DIS_1,
        .post_write = gpio_int_dis_postw,
    },{ .name = "INT_STAT_1",
        .addr = A_INT_STAT_1,
        .rsvd = 0xfc000000,
        .w1c = 0x3ffffff,
    },{ .name = "INT_TYPE_1",
        .addr = A_INT_TYPE_1,
        .rsvd = 0xfc000000,
        .reset = 0x3ffffff,
    },{ .name = "INT_POLARITY_1",
        .addr = A_INT_POLARITY_1,
        .rsvd = 0xfc000000,
    },{ .name = "INT_ANY_1",
        .addr = A_INT_ANY_1,
        .rsvd = 0xfc000000,
    },{ .name = "DIRM_2",  .addr = A_DIRM_2,
        .rsvd = 0xfc000000,
    },{ .name = "OEN_2",  .addr = A_OEN_2,
        .rsvd = 0xfc000000,
        .pre_write = gpio_oen_prew,
    },{ .name = "INT_MASK_2",
        .addr = A_INT_MASK_2,
        .ro = 0x3ffffff,
        .rsvd = 0xfc000000,
        .reset = 0x3ffffff,
    },{ .name = "INT_EN_2",  .addr = A_INT_EN_2,
        .post_write = gpio_int_en_postw,
        .rsvd = 0xfc000000,
    },{ .name = "INT_DIS_2",
        .addr = A_INT_DIS_2,
        .rsvd = 0xfc000000,
        .post_write = gpio_int_dis_postw,
    },{ .name = "INT_STAT_2",
        .addr = A_INT_STAT_2,
        .rsvd = 0xfc000000,
        .w1c = 0x3ffffff,
    },{ .name = "INT_TYPE_2",
        .addr = A_INT_TYPE_2,
        .rsvd = 0xfc000000,
        .reset = 0x3ffffff,
    },{ .name = "INT_POLARITY_2",
        .addr = A_INT_POLARITY_2,
        .rsvd = 0xfc000000,
    },{ .name = "INT_ANY_2",
        .addr = A_INT_ANY_2,
        .rsvd = 0xfc000000,
    },{ .name = "DIRM_3",  .addr = A_DIRM_3,
    },{ .name = "OEN_3",  .addr = A_OEN_3,
        .pre_write = gpio_oen_prew,
    },{ .name = "INT_MASK_3",
        .addr = A_INT_MASK_3,
        .ro = 0xffffffff,
        .reset = 0xffffffff,
    },{ .name = "INT_EN_3",  .addr = A_INT_EN_3,
        .post_write = gpio_int_en_postw,
    },{ .name = "INT_DIS_3",
        .addr = A_INT_DIS_3,
        .post_write = gpio_int_dis_postw,
    },{ .name = "INT_STAT_3",
        .addr = A_INT_STAT_3,
        .w1c = 0xffffffff,
    },{ .name = "INT_TYPE_3",
        .addr = A_INT_TYPE_3,
        .reset = 0xffffffff,
    },{ .name = "INT_POLARITY_3",
        .addr = A_INT_POLARITY_3,
    },{ .name = "INT_ANY_3",
        .addr = A_INT_ANY_3,
    },{ .name = "DIRM_4",  .addr = A_DIRM_4,
    },{ .name = "OEN_4",  .addr = A_OEN_4,
        .pre_write = gpio_oen_prew,
    },{ .name = "INT_MASK_4",
        .addr = A_INT_MASK_4,
        .ro = 0xffffffff,
        .reset = 0xffffffff,
    },{ .name = "INT_EN_4",  .addr = A_INT_EN_4,
        .post_write = gpio_int_en_postw,
    },{ .name = "INT_DIS_4",
        .addr = A_INT_DIS_4,
        .post_write = gpio_int_dis_postw,
    },{ .name = "INT_STAT_4",
        .addr = A_INT_STAT_4,
        .w1c = 0xffffffff,
    },{ .name = "INT_TYPE_4",
        .addr = A_INT_TYPE_4,
        .reset = 0xffffffff,
    },{ .name = "INT_POLARITY_4",
        .addr = A_INT_POLARITY_4,
    },{ .name = "INT_ANY_4",
        .addr = A_INT_ANY_4,
    },{ .name = "DIRM_5",  .addr = A_DIRM_5,
    },{ .name = "OEN_5",  .addr = A_OEN_5,
        .pre_write = gpio_oen_prew,
    },{ .name = "INT_MASK_5",
        .addr = A_INT_MASK_5,
        .ro = 0xffffffff,
        .reset = 0xffffffff,
    },{ .name = "INT_EN_5",  .addr = A_INT_EN_5,
        .post_write = gpio_int_en_postw,
    },{ .name = "INT_DIS_5",
        .addr = A_INT_DIS_5,
        .post_write = gpio_int_dis_postw,
    },{ .name = "INT_STAT_5",
        .addr = A_INT_STAT_5,
        .w1c = 0xffffffff,
    },{ .name = "INT_TYPE_5",
        .addr = A_INT_TYPE_5,
        .reset = 0xffffffff,
    },{ .name = "INT_POLARITY_5",
        .addr = A_INT_POLARITY_5,
    },{ .name = "INT_ANY_5",
        .addr = A_INT_ANY_5,
    },
};

static void gpio_reset(DeviceState *dev)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(dev);
    unsigned int i;

    for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) {
        if (s->por_done && s->regs_info[i].access) {
            switch (s->regs_info[i].access->addr) {
            case A_DATA_0 ... A_DATA_5:
                /* No update to gpio pins after POR */
                continue;
            default:
                break;
            }
        }
        register_reset(&s->regs_info[i]);
    }

    s->por_done = true;
}

static const MemoryRegionOps gpio_ops = {
    .read = register_read_memory,
    .write = register_write_memory,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = {
        .min_access_size = 4,
        .max_access_size = 4,
    },
};

static void gpio_realize(DeviceState *dev, Error **errp)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(dev);
    const char *gpios_name;

    s->gpio_out = g_new0(qemu_irq, ZYNQMP_NUM_GPIOS);
    s->gpio_oen = g_new0(qemu_irq, ZYNQMP_NUM_OEN_SIGNALS);

    gpios_name = g_strdup("zynqmp_gpio_out");
    qdev_init_gpio_out_named(dev, s->gpio_out, gpios_name, ZYNQMP_NUM_GPIOS);
    g_free((gpointer) gpios_name);

    gpios_name = g_strdup("zynqmp_gpio_oen");
    qdev_init_gpio_out_named(dev, s->gpio_oen, gpios_name,
                             ZYNQMP_NUM_OEN_SIGNALS);
    g_free((gpointer) gpios_name);

    gpios_name = g_strdup("zynqmp_gpio_in");
    qdev_init_gpio_in_named(dev, zynqmp_gpio_in_handler, gpios_name,
                            ZYNQMP_NUM_GPIOS);
    g_free((gpointer)gpios_name);
}

static void gpio_init(Object *obj)
{
    XlnxZynqmpGPIO *s = XLNX_ZYNQMP_GPIO(obj);
    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
    RegisterInfoArray *reg_array;

    memory_region_init(&s->iomem, obj, TYPE_XLNX_ZYNQMP_GPIO, R_MAX * 4);
    reg_array =
        register_init_block32(DEVICE(obj), gpio_regs_info,
                              ARRAY_SIZE(gpio_regs_info),
                              s->regs_info, s->regs,
                              &gpio_ops,
                              XLNX_ZYNQMP_GPIO_ERR_DEBUG,
                              R_MAX * 4);
    memory_region_add_subregion(&s->iomem,
                                0x0,
                                &reg_array->mem);
    sysbus_init_mmio(sbd, &s->iomem);
    sysbus_init_irq(sbd, &s->irq);
}

static const VMStateDescription vmstate_gpio = {
    .name = TYPE_XLNX_ZYNQMP_GPIO,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32_ARRAY(regs, XlnxZynqmpGPIO, R_MAX),
        VMSTATE_BOOL(por_done, XlnxZynqmpGPIO),
        VMSTATE_END_OF_LIST(),
    }
};

static const FDTGenericGPIOSet xlnx_gpio_controller_gpios[] = {
    {
        .names = &fdt_generic_gpio_name_set_gpio,
        .gpios = (FDTGenericGPIOConnection []) {
            { .name = "zynqmp_gpio_out", .fdt_index = 0,
               .range = ZYNQMP_NUM_GPIOS},
            { .name = "zynqmp_gpio_oen", .fdt_index = ZYNQMP_NUM_GPIOS,
              .range = ZYNQMP_NUM_OEN_SIGNALS},
            { },
        },
    },
    { },
};

static const FDTGenericGPIOSet xlnx_gpio_client_gpios[] = {
    {
        .names = &fdt_generic_gpio_name_set_gpio,
        .gpios = (FDTGenericGPIOConnection []) {
           { .name = "zynqmp_gpio_in", .fdt_index = 0,
             .range = ZYNQMP_NUM_GPIOS },
           { },
        },
    },
    { },
};

static void gpio_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    FDTGenericGPIOClass *fggc = FDT_GENERIC_GPIO_CLASS(klass);

    dc->reset = gpio_reset;
    dc->realize = gpio_realize;
    dc->vmsd = &vmstate_gpio;
    fggc->controller_gpios = xlnx_gpio_controller_gpios;
    fggc->client_gpios = xlnx_gpio_client_gpios;
}

static const TypeInfo gpio_info = {
    .name          = TYPE_XLNX_ZYNQMP_GPIO,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(XlnxZynqmpGPIO),
    .class_init    = gpio_class_init,
    .instance_init = gpio_init,
    .interfaces    = (InterfaceInfo[]) {
        { TYPE_FDT_GENERIC_GPIO },
        { }
    },
};

static void gpio_register_types(void)
{
    type_register_static(&gpio_info);
}

type_init(gpio_register_types)
