/*
 * QEMU model of the PSM_LOCAL_REG Power Management Unit Local Register File
 *
 * Copyright (c) 2018 Xilinx Inc.
 *
 * Partly autogenerated by xregqemu.py 2018-08-20.
 * Written by Edgar E. Iglesias <edgar.iglesias@xilinx.com>
 *
 * 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 "qemu/log.h"
#include "migration/vmstate.h"
#include "hw/qdev-properties.h"

#include "hw/fdt_generic_util.h"

#ifndef XILINX_PSM_LOCAL_REG_ERR_DEBUG
#define XILINX_PSM_LOCAL_REG_ERR_DEBUG 0
#endif

#define TYPE_XILINX_PSM_LOCAL_REG "xlnx.psm-local-reg"

#define XILINX_PSM_LOCAL_REG(obj) \
     OBJECT_CHECK(PSM_LOCAL_REG, (obj), TYPE_XILINX_PSM_LOCAL_REG)

REG32(ACPU0_PWR_CNTRL, 0x0)
    FIELD(ACPU0_PWR_CNTRL, ISOLATION, 4, 1)
    FIELD(ACPU0_PWR_CNTRL, PWR_GATES, 0, 4)
REG32(ACPU0_PWR_STATUS, 0x4)
    FIELD(ACPU0_PWR_STATUS, PWR_GATES, 0, 4)
REG32(ACPU1_PWR_CNTRL, 0x10)
    FIELD(ACPU1_PWR_CNTRL, ISOLATION, 4, 1)
    FIELD(ACPU1_PWR_CNTRL, PWR_GATES, 0, 4)
REG32(ACPU1_PWR_STATUS, 0x14)
    FIELD(ACPU1_PWR_STATUS, PWR_GATES, 0, 4)
REG32(RPU_PWR_CNTRL, 0x80)
    FIELD(RPU_PWR_CNTRL, ISOLATION, 4, 1)
    FIELD(RPU_PWR_CNTRL, PWR_GATES, 0, 4)
REG32(RPU_PWR_STATUS, 0x84)
    FIELD(RPU_PWR_STATUS, PWR_GATES, 0, 4)
REG32(L2_PWR_CNTRL, 0xb0)
    FIELD(L2_PWR_CNTRL, BANK0, 0, 1)
REG32(L2_CE_CNTRL, 0xb8)
    FIELD(L2_CE_CNTRL, BANK0, 0, 1)
REG32(L2_PWR_STATUS, 0xbc)
    FIELD(L2_PWR_STATUS, BANK0, 0, 1)
REG32(OCM_PWR_CNTRL, 0xc0)
    FIELD(OCM_PWR_CNTRL, BANK3, 24, 1)
    FIELD(OCM_PWR_CNTRL, BANK2, 16, 1)
    FIELD(OCM_PWR_CNTRL, BANK1, 8, 1)
    FIELD(OCM_PWR_CNTRL, BANK0, 0, 1)
REG32(OCM_CE_CNTRL, 0xc8)
    FIELD(OCM_CE_CNTRL, BANK3, 3, 1)
    FIELD(OCM_CE_CNTRL, BANK2, 2, 1)
    FIELD(OCM_CE_CNTRL, BANK1, 1, 1)
    FIELD(OCM_CE_CNTRL, BANK0, 0, 1)
REG32(OCM_PWR_STATUS, 0xcc)
    FIELD(OCM_PWR_STATUS, BANK3, 24, 1)
    FIELD(OCM_PWR_STATUS, BANK2, 16, 1)
    FIELD(OCM_PWR_STATUS, BANK1, 8, 1)
    FIELD(OCM_PWR_STATUS, BANK0, 0, 1)
REG32(TCM_PWR_CNTRL, 0xd0)
    FIELD(TCM_PWR_CNTRL, TCMB1, 24, 1)
    FIELD(TCM_PWR_CNTRL, TCMA1, 16, 1)
    FIELD(TCM_PWR_CNTRL, TCMB0, 8, 1)
    FIELD(TCM_PWR_CNTRL, TCMA0, 0, 1)
REG32(TCM_CE_CNTRL, 0xd8)
    FIELD(TCM_CE_CNTRL, TCMB1, 3, 1)
    FIELD(TCM_CE_CNTRL, TCMA1, 2, 1)
    FIELD(TCM_CE_CNTRL, TCMB0, 1, 1)
    FIELD(TCM_CE_CNTRL, TCMA0, 0, 1)
REG32(TCM_PWR_STATUS, 0xdc)
    FIELD(TCM_PWR_STATUS, TCMB1, 24, 1)
    FIELD(TCM_PWR_STATUS, TCMA1, 16, 1)
    FIELD(TCM_PWR_STATUS, TCMB0, 8, 1)
    FIELD(TCM_PWR_STATUS, TCMA0, 0, 1)
REG32(GEM_PWR_CNTRL, 0xe0)
    FIELD(GEM_PWR_CNTRL, GEM1, 8, 1)
    FIELD(GEM_PWR_CNTRL, GEM0, 0, 1)
REG32(GEM_CE_CNTRL, 0xe4)
    FIELD(GEM_CE_CNTRL, GEM1, 1, 1)
    FIELD(GEM_CE_CNTRL, GEM0, 0, 1)
REG32(GEM_PWR_STATUS, 0xe8)
    FIELD(GEM_PWR_STATUS, GEM1, 8, 1)
    FIELD(GEM_PWR_STATUS, GEM0, 0, 1)
REG32(DOMAIN_ISO_CNTRL, 0xf0)
    FIELD(DOMAIN_ISO_CNTRL, LPD_OCM2_DFX, 3, 1)
    FIELD(DOMAIN_ISO_CNTRL, LPD_OCM2, 2, 1)
    FIELD(DOMAIN_ISO_CNTRL, LPD_FPD_DFX, 1, 1)
    FIELD(DOMAIN_ISO_CNTRL, LPD_FPD, 0, 1)
REG32(LOC_PWR_STATE, 0x100)
    FIELD(LOC_PWR_STATE, FP, 22, 1)
    FIELD(LOC_PWR_STATE, GEM0, 21, 1)
    FIELD(LOC_PWR_STATE, GEM1, 20, 1)
    FIELD(LOC_PWR_STATE, OCM_BANK3, 19, 1)
    FIELD(LOC_PWR_STATE, OCM_BANK2, 18, 1)
    FIELD(LOC_PWR_STATE, OCM_BANK1, 17, 1)
    FIELD(LOC_PWR_STATE, OCM_BANK0, 16, 1)
    FIELD(LOC_PWR_STATE, TCM1B, 15, 1)
    FIELD(LOC_PWR_STATE, TCM1A, 14, 1)
    FIELD(LOC_PWR_STATE, TCM0B, 13, 1)
    FIELD(LOC_PWR_STATE, TCM0A, 12, 1)
    FIELD(LOC_PWR_STATE, R5_1, 11, 1)
    FIELD(LOC_PWR_STATE, R5_0, 10, 1)
    FIELD(LOC_PWR_STATE, L2_BANK0, 7, 1)
    FIELD(LOC_PWR_STATE, ACPU1, 1, 1)
    FIELD(LOC_PWR_STATE, ACPU0, 0, 1)
REG32(LOC_AUX_PWR_STATE, 0x104)
    FIELD(LOC_AUX_PWR_STATE, ACPU1_EMUL, 29, 1)
    FIELD(LOC_AUX_PWR_STATE, ACPU0_EMUL, 28, 1)
    FIELD(LOC_AUX_PWR_STATE, RPU_EMUL, 27, 1)
    FIELD(LOC_AUX_PWR_STATE, OCM_BANK3, 19, 1)
    FIELD(LOC_AUX_PWR_STATE, OCM_BANK2, 18, 1)
    FIELD(LOC_AUX_PWR_STATE, OCM_BANK1, 17, 1)
    FIELD(LOC_AUX_PWR_STATE, OCM_BANK0, 16, 1)
    FIELD(LOC_AUX_PWR_STATE, TCM1B, 15, 1)
    FIELD(LOC_AUX_PWR_STATE, TCM1A, 14, 1)
    FIELD(LOC_AUX_PWR_STATE, TCM0B, 13, 1)
    FIELD(LOC_AUX_PWR_STATE, TCM0A, 12, 1)
    FIELD(LOC_AUX_PWR_STATE, L2, 7, 1)
REG32(LOCAL_GEN_STORAGE0, 0x300)
REG32(LOCAL_GEN_STORAGE1, 0x304)
REG32(LOCAL_GEN_STORAGE2, 0x308)
REG32(LOCAL_GEN_STORAGE3, 0x30c)
REG32(PERS_LOC_GEN_STORAGE0, 0x310)
REG32(PERS_LOC_GEN_STORAGE1, 0x314)
REG32(PERS_LOC_GEN_STORAGE2, 0x318)
REG32(PERS_LOC_GEN_STORAGE3, 0x31c)
REG32(ADDR_ERROR_STATUS, 0x320)
    FIELD(ADDR_ERROR_STATUS, STATUS, 0, 1)
REG32(ADDR_ERROR_INT_MASK, 0x324)
    FIELD(ADDR_ERROR_INT_MASK, MASK, 0, 1)
REG32(ADDR_ERROR_INT_EN, 0x328)
    FIELD(ADDR_ERROR_INT_EN, EN, 0, 1)
REG32(ADDR_ERROR_INT_DIS, 0x32c)
    FIELD(ADDR_ERROR_INT_DIS, DIS, 0, 1)
REG32(APU_WFI_STATUS, 0x418)
    FIELD(APU_WFI_STATUS, L2_WFI, 16, 1)
    FIELD(APU_WFI_STATUS, ACPU1_WFI, 1, 1)
    FIELD(APU_WFI_STATUS, ACPU0_WFI, 0, 1)
REG32(SCAN_CLEAR_CPU0, 0x500)
    FIELD(SCAN_CLEAR_CPU0, PASS, 2, 1)
    FIELD(SCAN_CLEAR_CPU0, DONE, 1, 1)
    FIELD(SCAN_CLEAR_CPU0, TRIGGER, 0, 1)
REG32(SCAN_CLEAR_CPU1, 0x504)
    FIELD(SCAN_CLEAR_CPU1, PASS, 2, 1)
    FIELD(SCAN_CLEAR_CPU1, DONE, 1, 1)
    FIELD(SCAN_CLEAR_CPU1, TRIGGER, 0, 1)
REG32(SCAN_CLEAR_APU, 0x508)
    FIELD(SCAN_CLEAR_APU, PASS, 2, 1)
    FIELD(SCAN_CLEAR_APU, DONE, 1, 1)
    FIELD(SCAN_CLEAR_APU, TRIGGER, 0, 1)
REG32(SCAN_CLEAR_FPD, 0x50c)
    FIELD(SCAN_CLEAR_FPD, PASS, 2, 1)
    FIELD(SCAN_CLEAR_FPD, DONE, 1, 1)
    FIELD(SCAN_CLEAR_FPD, TRIGGER, 0, 1)
REG32(MBISR_CNTRL, 0x510)
    FIELD(MBISR_CNTRL, FPD_GROUP, 5, 1)
    FIELD(MBISR_CNTRL, ENABLE, 0, 1)
REG32(MBISR_STATUS, 0x514)
    FIELD(MBISR_STATUS, PASS, 4, 1)
    FIELD(MBISR_STATUS, DONE, 0, 1)
REG32(MBIST_RST, 0x518)
    FIELD(MBIST_RST, FPD, 1, 1)
    FIELD(MBIST_RST, LPD, 0, 1)
REG32(MBIST_PG_EN, 0x51c)
    FIELD(MBIST_PG_EN, FPD, 1, 1)
    FIELD(MBIST_PG_EN, LPD, 0, 1)
REG32(MBIST_SETUP, 0x520)
    FIELD(MBIST_SETUP, FPD, 1, 1)
    FIELD(MBIST_SETUP, LPD, 0, 1)
REG32(MBIST_DONE, 0x524)
    FIELD(MBIST_DONE, FPD, 1, 1)
    FIELD(MBIST_DONE, LPD, 0, 1)
REG32(MBIST_GOOD, 0x528)
    FIELD(MBIST_GOOD, FPD, 1, 1)
    FIELD(MBIST_GOOD, LPD, 0, 1)
REG32(PSM_GLOBAL_APB, 0x600)
    FIELD(PSM_GLOBAL_APB, RST_N, 4, 1)
    FIELD(PSM_GLOBAL_APB, POWER_IDLEREQ, 3, 1)
    FIELD(PSM_GLOBAL_APB, POWER_IDLEACK, 2, 1)
    FIELD(PSM_GLOBAL_APB, POWER_IDLE, 1, 1)
    FIELD(PSM_GLOBAL_APB, MAINEXTEN, 0, 1)

#define PSM_LOCAL_REG_R_MAX (R_PSM_GLOBAL_APB + 1)

typedef struct PSM_LOCAL_REG {
    SysBusDevice parent_obj;
    MemoryRegion iomem;
    qemu_irq irq_addr_error_int;

    qemu_irq pwr_acpu0[4];
    qemu_irq pwr_acpu1[4];
    qemu_irq pwr_rpu[4];
    qemu_irq pwr_l2;
    qemu_irq pwr_ocm[4];
    qemu_irq pwr_tcm_a[2];
    qemu_irq pwr_tcm_b[2];
    qemu_irq pwr_gem[2];

    qemu_irq loc_pwr_state[32];
    qemu_irq loc_aux_pwr_state[32];
    qemu_irq apu_sleep[4];
    qemu_irq apu_pwrdw_status[4];

    bool has_por;
    uint8_t apu_pwrdw_req;

    uint32_t regs[PSM_LOCAL_REG_R_MAX];
    RegisterInfo regs_info[PSM_LOCAL_REG_R_MAX];
} PSM_LOCAL_REG;

#define PROPAGATE_FIELD1(s, sreg, sf, dreg, df, irq) {           \
    unsigned int val = ARRAY_FIELD_EX32((s)->regs, sreg, sf);    \
    ARRAY_FIELD_DP32((s)->regs, dreg, df, val);                  \
    qemu_set_irq(irq, val);                                      \
}

#define PROPAGATE_FIELD(s, sreg, sf, dreg, df, irq) {            \
    unsigned int val = ARRAY_FIELD_EX32((s)->regs, sreg, sf);    \
    unsigned int i;                                              \
                                                                 \
    ARRAY_FIELD_DP32((s)->regs, dreg, df, val);                  \
    assert(ARRAY_SIZE(irq) == R_ ## sreg ## _ ## sf ## _LENGTH); \
    for (i = 0; i < R_ ## sreg ## _ ## sf ## _LENGTH; i++) {     \
        qemu_set_irq(irq[i], (val >> i) & 1);                    \
    }                                                            \
}

#define PROPAGATE_REG32(s, reg, irq) {                           \
    unsigned int val = (s)->regs[R_ ## reg];                     \
    unsigned int i;                                              \
                                                                 \
    assert(ARRAY_SIZE(irq) == sizeof((s)->regs[0]) * 8);         \
    for (i = 0; i < sizeof((s)->regs[0]) * 8; i++) {             \
        qemu_set_irq(irq[i], (val >> i) & 1);                    \
    }                                                            \
}

static void update_pwr_status(PSM_LOCAL_REG *s)
{
    PROPAGATE_FIELD(s, ACPU0_PWR_CNTRL, PWR_GATES,
                       ACPU0_PWR_STATUS, PWR_GATES, s->pwr_acpu0);
    PROPAGATE_FIELD(s, ACPU1_PWR_CNTRL, PWR_GATES,
                       ACPU1_PWR_STATUS, PWR_GATES, s->pwr_acpu1);
    PROPAGATE_FIELD(s, RPU_PWR_CNTRL, PWR_GATES, RPU_PWR_STATUS, PWR_GATES,
                    s->pwr_rpu);

    PROPAGATE_FIELD1(s, L2_PWR_CNTRL, BANK0, L2_PWR_STATUS, BANK0,
                     s->pwr_l2);

    PROPAGATE_FIELD1(s, OCM_PWR_CNTRL, BANK0, OCM_PWR_STATUS, BANK0,
                     s->pwr_ocm[0]);
    PROPAGATE_FIELD1(s, OCM_PWR_CNTRL, BANK1, OCM_PWR_STATUS, BANK1,
                     s->pwr_ocm[1]);
    PROPAGATE_FIELD1(s, OCM_PWR_CNTRL, BANK2, OCM_PWR_STATUS, BANK2,
                     s->pwr_ocm[2]);
    PROPAGATE_FIELD1(s, OCM_PWR_CNTRL, BANK3, OCM_PWR_STATUS, BANK3,
                     s->pwr_ocm[3]);

    PROPAGATE_FIELD1(s, TCM_PWR_CNTRL, TCMA0, TCM_PWR_STATUS, TCMA0,
                     s->pwr_tcm_a[0]);
    PROPAGATE_FIELD1(s, TCM_PWR_CNTRL, TCMB0, TCM_PWR_STATUS, TCMB0,
                     s->pwr_tcm_b[0]);
    PROPAGATE_FIELD1(s, TCM_PWR_CNTRL, TCMA1, TCM_PWR_STATUS, TCMA1,
                     s->pwr_tcm_a[1]);
    PROPAGATE_FIELD1(s, TCM_PWR_CNTRL, TCMB1, TCM_PWR_STATUS, TCMB1,
                     s->pwr_tcm_b[1]);
    PROPAGATE_FIELD1(s, GEM_PWR_CNTRL, GEM0, GEM_PWR_STATUS, GEM0,
                     s->pwr_gem[0]);
    PROPAGATE_FIELD1(s, GEM_PWR_CNTRL, GEM1, GEM_PWR_STATUS, GEM1,
                     s->pwr_gem[1]);
}

static void update_loc_pwr_state(PSM_LOCAL_REG *s)
{
    int i;
    PROPAGATE_REG32(s, LOC_PWR_STATE, s->loc_pwr_state);
    PROPAGATE_REG32(s, LOC_AUX_PWR_STATE, s->loc_aux_pwr_state);

    /*
     * Send apu powerdown signals to intc-redirect
     */
    for (i = 0; i < 4; i++) {
        qemu_set_irq(s->apu_pwrdw_status[i],
              !(s->regs[R_LOC_PWR_STATE] & (1 << i)));
    }
}

static void addr_error_int_update_irq(PSM_LOCAL_REG *s)
{
    bool pending;

    pending = s->regs[R_ADDR_ERROR_STATUS] & ~s->regs[R_ADDR_ERROR_INT_MASK];
    qemu_set_irq(s->irq_addr_error_int, pending);
}

static void addr_error_status_postw(RegisterInfo *reg, uint64_t val64)
{
    PSM_LOCAL_REG *s = XILINX_PSM_LOCAL_REG(reg->opaque);
    addr_error_int_update_irq(s);
}

static uint64_t addr_error_int_en_prew(RegisterInfo *reg, uint64_t val64)
{
    PSM_LOCAL_REG *s = XILINX_PSM_LOCAL_REG(reg->opaque);
    uint32_t val = val64;

    s->regs[R_ADDR_ERROR_INT_MASK] &= ~val;
    addr_error_int_update_irq(s);
    return 0;
}

static uint64_t addr_error_int_dis_prew(RegisterInfo *reg, uint64_t val64)
{
    PSM_LOCAL_REG *s = XILINX_PSM_LOCAL_REG(reg->opaque);
    uint32_t val = val64;

    s->regs[R_ADDR_ERROR_INT_MASK] |= val;
    addr_error_int_update_irq(s);
    return 0;
}

static void pwr_cntrl_postw(RegisterInfo *reg, uint64_t val64)
{
    PSM_LOCAL_REG *s = XILINX_PSM_LOCAL_REG(reg->opaque);
    update_pwr_status(s);
}

static void loc_pwr_state_postw(RegisterInfo *reg, uint64_t val64)
{
    PSM_LOCAL_REG *s = XILINX_PSM_LOCAL_REG(reg->opaque);
    update_loc_pwr_state(s);
}

static const RegisterAccessInfo psm_local_reg_regs_info[] = {
    {   .name = "ACPU0_PWR_CNTRL",  .addr = A_ACPU0_PWR_CNTRL,
        .reset = 0xf,
        .rsvd = 0xffffffe0,
        .ro = 0xffffffe0,
        .post_write = pwr_cntrl_postw,
    },{ .name = "ACPU0_PWR_STATUS",  .addr = A_ACPU0_PWR_STATUS,
        .reset = 0xf,
        .rsvd = 0xfffffff0,
        .ro = 0xffffffff,
    },{ .name = "ACPU1_PWR_CNTRL",  .addr = A_ACPU1_PWR_CNTRL,
        .reset = 0xf,
        .rsvd = 0xffffffe0,
        .ro = 0xffffffe0,
        .post_write = pwr_cntrl_postw,
    },{ .name = "ACPU1_PWR_STATUS",  .addr = A_ACPU1_PWR_STATUS,
        .reset = 0xf,
        .rsvd = 0xfffffff0,
        .ro = 0xffffffff,
        .post_write = pwr_cntrl_postw,
    },{ .name = "RPU_PWR_CNTRL",  .addr = A_RPU_PWR_CNTRL,
        .reset = 0xf,
        .rsvd = 0xffffffe0,
        .ro = 0xffffffe0,
        .post_write = pwr_cntrl_postw,
    },{ .name = "RPU_PWR_STATUS",  .addr = A_RPU_PWR_STATUS,
        .reset = 0xf,
        .rsvd = 0xfffffff0,
        .ro = 0xffffffff,
    },{ .name = "L2_PWR_CNTRL",  .addr = A_L2_PWR_CNTRL,
        .reset = 0x1,
        .rsvd = 0xfffffffe,
        .ro = 0xfffffffe,
        .post_write = pwr_cntrl_postw,
    },{ .name = "L2_CE_CNTRL",  .addr = A_L2_CE_CNTRL,
        .reset = 0x1,
        .rsvd = 0xfffffffe,
        .ro = 0xfffffffe,
    },{ .name = "L2_PWR_STATUS",  .addr = A_L2_PWR_STATUS,
        .reset = 0x1,
        .rsvd = 0xfffffffe,
        .ro = 0xffffffff,
    },{ .name = "OCM_PWR_CNTRL",  .addr = A_OCM_PWR_CNTRL,
        .reset = 0x1010101,
        .rsvd = 0xfefefefe,
        .ro = 0xfefefefe,
        .post_write = pwr_cntrl_postw,
    },{ .name = "OCM_CE_CNTRL",  .addr = A_OCM_CE_CNTRL,
        .reset = 0xf,
        .rsvd = 0xfffffff0,
        .ro = 0xfffffff0,
    },{ .name = "OCM_PWR_STATUS",  .addr = A_OCM_PWR_STATUS,
        .reset = 0x1010101,
        .rsvd = 0xfefefefe,
        .ro = 0xffffffff,
    },{ .name = "TCM_PWR_CNTRL",  .addr = A_TCM_PWR_CNTRL,
        .reset = 0x1010101,
        .rsvd = 0xfefefefe,
        .ro = 0xfefefefe,
        .post_write = pwr_cntrl_postw,
    },{ .name = "TCM_CE_CNTRL",  .addr = A_TCM_CE_CNTRL,
        .reset = 0xf,
        .rsvd = 0xfffffff0,
        .ro = 0xfffffff0,
    },{ .name = "TCM_PWR_STATUS",  .addr = A_TCM_PWR_STATUS,
        .reset = 0x1010101,
        .rsvd = 0xfefefefe,
        .ro = 0xffffffff,
    },{ .name = "GEM_PWR_CNTRL",  .addr = A_GEM_PWR_CNTRL,
        .reset = 0x101,
        .rsvd = 0xfffffefe,
        .ro = 0xfffffefe,
        .post_write = pwr_cntrl_postw,
    },{ .name = "GEM_CE_CNTRL",  .addr = A_GEM_CE_CNTRL,
        .reset = 0x3,
        .rsvd = 0xfffffffc,
        .ro = 0xfffffffc,
    },{ .name = "GEM_PWR_STATUS",  .addr = A_GEM_PWR_STATUS,
        .reset = 0x101,
        .rsvd = 0xfffffefe,
        .ro = 0xffffffff,
    },{ .name = "DOMAIN_ISO_CNTRL",  .addr = A_DOMAIN_ISO_CNTRL,
        .reset = 0x1f,
        .rsvd = 0xfffffff0,
        .ro = 0xfffffff0,
    },{ .name = "LOC_PWR_STATE",  .addr = A_LOC_PWR_STATE,
        .reset = 0xfffd87,
        .rsvd = 0xff80037c,
        .post_write = loc_pwr_state_postw,
    },{ .name = "LOC_AUX_PWR_STATE",  .addr = A_LOC_AUX_PWR_STATE,
        .reset = 0xff080,
        .rsvd = 0xc7f00f7f,
        .ro = 0xc7f00f7f,
        .post_write = loc_pwr_state_postw,
    },{ .name = "LOCAL_GEN_STORAGE0",  .addr = A_LOCAL_GEN_STORAGE0,
    },{ .name = "LOCAL_GEN_STORAGE1",  .addr = A_LOCAL_GEN_STORAGE1,
    },{ .name = "LOCAL_GEN_STORAGE2",  .addr = A_LOCAL_GEN_STORAGE2,
    },{ .name = "LOCAL_GEN_STORAGE3",  .addr = A_LOCAL_GEN_STORAGE3,
    },{ .name = "PERS_LOC_GEN_STORAGE0",  .addr = A_PERS_LOC_GEN_STORAGE0,
    },{ .name = "PERS_LOC_GEN_STORAGE1",  .addr = A_PERS_LOC_GEN_STORAGE1,
    },{ .name = "PERS_LOC_GEN_STORAGE2",  .addr = A_PERS_LOC_GEN_STORAGE2,
    },{ .name = "PERS_LOC_GEN_STORAGE3",  .addr = A_PERS_LOC_GEN_STORAGE3,
    },{ .name = "ADDR_ERROR_STATUS",  .addr = A_ADDR_ERROR_STATUS,
        .w1c = 0x1,
        .post_write = addr_error_status_postw,
    },{ .name = "ADDR_ERROR_INT_MASK",  .addr = A_ADDR_ERROR_INT_MASK,
        .reset = 0x1,
        .ro = 0x1,
    },{ .name = "ADDR_ERROR_INT_EN",  .addr = A_ADDR_ERROR_INT_EN,
        .pre_write = addr_error_int_en_prew,
    },{ .name = "ADDR_ERROR_INT_DIS",  .addr = A_ADDR_ERROR_INT_DIS,
        .pre_write = addr_error_int_dis_prew,
    },{ .name = "APU_WFI_STATUS",  .addr = A_APU_WFI_STATUS,
        .reset = 0x10003,
        .rsvd = 0xfffefffc,
        .ro = 0xffffffff,
    },{ .name = "SCAN_CLEAR_CPU0",  .addr = A_SCAN_CLEAR_CPU0,
        .rsvd = 0xfffffff8,
        .ro = 0xfffffffe,
        .reset = R_SCAN_CLEAR_CPU0_PASS_MASK | R_SCAN_CLEAR_CPU0_DONE_MASK,
    },{ .name = "SCAN_CLEAR_CPU1",  .addr = A_SCAN_CLEAR_CPU1,
        .rsvd = 0xfffffff8,
        .ro = 0xfffffffe,
        .reset = R_SCAN_CLEAR_CPU1_PASS_MASK | R_SCAN_CLEAR_CPU1_DONE_MASK,
    },{ .name = "SCAN_CLEAR_APU",  .addr = A_SCAN_CLEAR_APU,
        .rsvd = 0xfffffff8,
        .ro = 0xfffffffe,
        .reset = R_SCAN_CLEAR_APU_PASS_MASK | R_SCAN_CLEAR_APU_DONE_MASK,
    },{ .name = "SCAN_CLEAR_FPD",  .addr = A_SCAN_CLEAR_FPD,
        .rsvd = 0xfffffff8,
        .ro = 0xfffffffe,
        .reset = R_SCAN_CLEAR_FPD_PASS_MASK | R_SCAN_CLEAR_FPD_DONE_MASK,
    },{ .name = "MBISR_CNTRL",  .addr = A_MBISR_CNTRL,
        .rsvd = 0xffffffde,
        .ro = 0xffffffde,
    },{ .name = "MBISR_STATUS",  .addr = A_MBISR_STATUS,
        .rsvd = 0xffffffee,
        .ro = 0xffffffff,
        .reset = R_MBISR_STATUS_PASS_MASK | R_MBISR_STATUS_DONE_MASK,
    },{ .name = "MBIST_RST",  .addr = A_MBIST_RST,
    },{ .name = "MBIST_PG_EN",  .addr = A_MBIST_PG_EN,
    },{ .name = "MBIST_SETUP",  .addr = A_MBIST_SETUP,
    },{ .name = "MBIST_DONE",  .addr = A_MBIST_DONE,
        .ro = 0x3,
    },{ .name = "MBIST_GOOD",  .addr = A_MBIST_GOOD,
        .ro = 0x3,
    },{ .name = "PSM_GLOBAL_APB",  .addr = A_PSM_GLOBAL_APB,
        .reset = 0x16,
        .rsvd = 0xffffffe0,
        .ro = 0x6,
    }
};

static void psm_local_reg_reset(DeviceState *dev)
{
    PSM_LOCAL_REG *s = XILINX_PSM_LOCAL_REG(dev);
    unsigned int i;

    for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) {
        /*
         * Most registers in this blocks are preserved across warm resets.
         */
        switch (i) {
        case R_LOCAL_GEN_STORAGE0...R_LOCAL_GEN_STORAGE3:
        case R_ADDR_ERROR_STATUS...R_ADDR_ERROR_INT_DIS:
            if (!s->has_por) {
                register_reset(&s->regs_info[i]);
            }
            break;
        default:
            register_reset(&s->regs_info[i]);
            break;
        }
    }

    addr_error_int_update_irq(s);
    update_pwr_status(s);
    update_loc_pwr_state(s);

    s->has_por = true;
}

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

static void update_apu_sleep(PSM_LOCAL_REG *s)
{
    int i;

    for (i = 0; i < 4; i++) {
        if ((s->apu_pwrdw_req &
            s->regs[R_APU_WFI_STATUS]) &
            (1 << i)) {
            qemu_irq_pulse(s->apu_sleep[i]);
        }
    }
}

static void apu_wfi_h(void *opaque, int n, int level)
{
    PSM_LOCAL_REG *s = XILINX_PSM_LOCAL_REG(opaque);

    switch (n) {
    case 0:
        ARRAY_FIELD_DP32(s->regs, APU_WFI_STATUS, ACPU0_WFI, !!level);
        break;
    case 1:
        ARRAY_FIELD_DP32(s->regs, APU_WFI_STATUS, ACPU1_WFI, !!level);
        break;
    case 4:
        ARRAY_FIELD_DP32(s->regs, APU_WFI_STATUS, L2_WFI, !!level);
        break;
    default:
        g_assert_not_reached();
    }

    update_apu_sleep(s);
}

static void apu_pwrdw_h(void *opaque, int n, int level)
{
    PSM_LOCAL_REG *s = XILINX_PSM_LOCAL_REG(opaque);

    s->apu_pwrdw_req &= ~(1 << n);
    s->apu_pwrdw_req |= (level << n);

    update_apu_sleep(s);
}

static void psm_local_reg_init(Object *obj)
{
    PSM_LOCAL_REG *s = XILINX_PSM_LOCAL_REG(obj);
    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
    RegisterInfoArray *reg_array;

    memory_region_init(&s->iomem, obj, TYPE_XILINX_PSM_LOCAL_REG,
                       PSM_LOCAL_REG_R_MAX * 4);
    reg_array =
        register_init_block32(DEVICE(obj), psm_local_reg_regs_info,
                              ARRAY_SIZE(psm_local_reg_regs_info),
                              s->regs_info, s->regs,
                              &psm_local_reg_ops,
                              XILINX_PSM_LOCAL_REG_ERR_DEBUG,
                              PSM_LOCAL_REG_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_addr_error_int);

    qdev_init_gpio_out_named(DEVICE(obj), s->pwr_acpu0, "pwr-acpu0", 4);
    qdev_init_gpio_out_named(DEVICE(obj), s->pwr_acpu1, "pwr-acpu1", 4);
    qdev_init_gpio_out_named(DEVICE(obj), s->pwr_rpu, "pwr-rpu", 4);

    qdev_init_gpio_out_named(DEVICE(obj), &s->pwr_l2, "pwr-l2", 1);
    qdev_init_gpio_out_named(DEVICE(obj), s->pwr_ocm, "pwr-ocm", 4);

    qdev_init_gpio_out_named(DEVICE(obj), &s->pwr_tcm_a[0], "pwr-tcm-a0", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->pwr_tcm_b[0], "pwr-tcm-b0", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->pwr_tcm_a[1], "pwr-tcm-a1", 1);
    qdev_init_gpio_out_named(DEVICE(obj), &s->pwr_tcm_b[1], "pwr-tcm-b1", 1);

    qdev_init_gpio_out_named(DEVICE(obj), s->pwr_gem, "pwr-gem", 2);

    qdev_init_gpio_out_named(DEVICE(obj), s->loc_pwr_state,
                             "loc-pwr-state", 32);
    qdev_init_gpio_out_named(DEVICE(obj), s->loc_aux_pwr_state,
                             "loc-aux-pwr-state", 32);

    qdev_init_gpio_in_named(DEVICE(obj), apu_wfi_h, "apu-wfi", 5);
    qdev_init_gpio_in_named(DEVICE(obj), apu_pwrdw_h, "apu-pwrdw", 4);
    qdev_init_gpio_out_named(DEVICE(obj), s->apu_sleep, "apu-sleep", 4);
    qdev_init_gpio_out_named(DEVICE(obj), s->apu_pwrdw_status,
                             "apu-pwrdw-status", 4);
}

static const VMStateDescription vmstate_psm_local_reg = {
    .name = TYPE_XILINX_PSM_LOCAL_REG,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32_ARRAY(regs, PSM_LOCAL_REG, PSM_LOCAL_REG_R_MAX),
        VMSTATE_END_OF_LIST(),
    }
};

static const FDTGenericGPIOSet psm_local_reg_gpios[] = {
    {
      .names = &fdt_generic_gpio_name_set_gpio,
      .gpios = (FDTGenericGPIOConnection[]) {
        { .name = "pwr-acpu0", .fdt_index = 0, .range = 4 },
        { .name = "pwr-acpu1", .fdt_index = 4, .range = 4 },
        { .name = "pwr-rpu", .fdt_index = 8, .range = 4 },
        { .name = "pwr-l2", .fdt_index = 12, .range = 1 },
        { .name = "pwr-ocm", .fdt_index = 13, .range = 4 },
        { .name = "pwr-tcm-a0", .fdt_index = 17, .range = 1 },
        { .name = "pwr-tcm-b0", .fdt_index = 18, .range = 1 },
        { .name = "pwr-tcm-a1", .fdt_index = 19, .range = 1 },
        { .name = "pwr-tcm-b1", .fdt_index = 20, .range = 1 },
        /* These control the GEM RAMs.  */
        { .name = "pwr-gem", .fdt_index = 21, .range = 2 },
        { .name = "apu-wfi", .fdt_index = 24, .range = 5 },
        { .name = "loc-pwr-state", .fdt_index = 32, .range = 32 },
        { .name = "loc-aux-pwr-state", .fdt_index = 64, .range = 32 },
        { .name = "apu-sleep", .fdt_index = 96, .range = 4 },
        { .name = "apu-pwrdw-status", .fdt_index = 100, .range = 4 },
        { },
      },
    },
    { },
};

static const FDTGenericGPIOSet psm_local_reg_client_gpios[] = {
    {
      .names = &fdt_generic_gpio_name_set_gpio,
      .gpios = (FDTGenericGPIOConnection[]) {
        { .name = "apu-pwrdw", .fdt_index = 0, .range = 4 },
        { },
      },
    },
    { },
};

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

    dc->reset = psm_local_reg_reset;
    dc->vmsd = &vmstate_psm_local_reg;
    fggc->controller_gpios = psm_local_reg_gpios;
    fggc->client_gpios = psm_local_reg_client_gpios;
}

static const TypeInfo psm_local_reg_info = {
    .name          = TYPE_XILINX_PSM_LOCAL_REG,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(PSM_LOCAL_REG),
    .class_init    = psm_local_reg_class_init,
    .instance_init = psm_local_reg_init,
    .interfaces    = (InterfaceInfo[]) {
        { TYPE_FDT_GENERIC_GPIO },
        { }
    },
};

static void psm_local_reg_register_types(void)
{
    type_register_static(&psm_local_reg_info);
}

type_init(psm_local_reg_register_types)
