/*
 * QEMU model of the WWDT Registers for the WWDT
 *
 * Copyright (c) 2020 Xilinx Inc.
 *
 * Autogenerated by xregqemu.py 2020-12-10.
 * Written by Joe Komlodi <komlodi@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 "qemu/timer.h"
#include "qapi/error.h"
#include "migration/vmstate.h"
#include "hw/qdev-properties.h"
#include "hw/fdt_generic_util.h"

#ifndef XILINX_WWDT_ERR_DEBUG
#define XILINX_WWDT_ERR_DEBUG 0
#endif

#define DB_PRINT_L(level, ...) do {         \
    if (XILINX_WWDT_ERR_DEBUG > (level)) {  \
        fprintf(stderr,  "%s: ", __func__); \
        fprintf(stderr, ## __VA_ARGS__);    \
    }                                       \
} while (0)

#define TYPE_XILINX_WWDT "xlnx,versal-wwdt"

/* Masks for LBE field in ENABLE_AND_STATUS_REG */
/* Basic WWDT mode */
#define LBE_BASIC_NO_BAD_EVENT_MASK 0x0
#define LBE_KICK_IN_FIRST_WINDOW_MASK 0x1
#define LBE_TSR_MISMATCH_MASK 0x2
#define LBE_SECOND_WINDOW_TIMEOUT_MASK 0x3
/* Q&A WWDT mode */
#define LBE_QA_NO_BAD_EVENT_MASK 0x4
#define LBE_TOKEN_EARLY_MASK 0x5
#define LBE_TOKEN_ERROR_MASK 0x6
#define LBE_TIMEOUT_MASK 0x7

#define INC_AND_ROLLOVER(num, max) do { \
    if (num == max) {                   \
        num = 0;                        \
    } else {                            \
        ++num;                          \
    }                                   \
} while (0)

#define XLNX_WWDT(obj) \
     OBJECT_CHECK(WWDT, (obj), TYPE_XILINX_WWDT)

REG32(MASTER_WRITE_CONTROL_REG, 0x0)
    FIELD(MASTER_WRITE_CONTROL_REG, MWC, 0, 1)
REG32(ENABLE_AND_STATUS_REG, 0x4)
    FIELD(ENABLE_AND_STATUS_REG, LBE, 24, 3)
    FIELD(ENABLE_AND_STATUS_REG, FCV, 20, 3)
    FIELD(ENABLE_AND_STATUS_REG, WRP, 17, 1)
    FIELD(ENABLE_AND_STATUS_REG, WINT, 16, 1)
    FIELD(ENABLE_AND_STATUS_REG, ACNT, 14, 2)
    FIELD(ENABLE_AND_STATUS_REG, TOUT, 12, 1)
    FIELD(ENABLE_AND_STATUS_REG, SERR, 11, 1)
    FIELD(ENABLE_AND_STATUS_REG, TERR, 10, 1)
    FIELD(ENABLE_AND_STATUS_REG, TERL, 9, 1)
    FIELD(ENABLE_AND_STATUS_REG, WSW, 8, 1)
    FIELD(ENABLE_AND_STATUS_REG, TVAL, 2, 4)
    FIELD(ENABLE_AND_STATUS_REG, WCFG, 1, 1)
    FIELD(ENABLE_AND_STATUS_REG, WEN, 0, 1)
REG32(FUNCTION_CONTROL_REG, 0x8)
    FIELD(FUNCTION_CONTROL_REG, SBC, 8, 8)
    FIELD(FUNCTION_CONTROL_REG, BSS, 6, 2)
    FIELD(FUNCTION_CONTROL_REG, SSTE, 4, 1)
    FIELD(FUNCTION_CONTROL_REG, PSME, 3, 1)
    FIELD(FUNCTION_CONTROL_REG, FCE, 2, 1)
    FIELD(FUNCTION_CONTROL_REG, WM, 1, 1)
    FIELD(FUNCTION_CONTROL_REG, WDP, 0, 1)
REG32(FIRST_WINDOW_CONFIG_REG, 0xc)
REG32(SECOND_WINDOW_CONFIG_REG, 0x10)
REG32(SST_WINDOW_CONFIG_REG, 0x14)
REG32(TASK_SIGNATURE_REG0, 0x18)
REG32(TASK_SIGNATURE_REG1, 0x1c)
REG32(SECOND_SEQ_TIMER_REG, 0x20)
REG32(TOKEN_FEEDBACK_REG, 0x24)
    FIELD(TOKEN_FEEDBACK_REG, FDBK, 8, 4)
    FIELD(TOKEN_FEEDBACK_REG, SEED, 0, 4)
REG32(TOKEN_RESPONSE_REG, 0x28)
    FIELD(TOKEN_RESPONSE_REG, ANS, 0, 8)
REG32(INTERRUPT_ENABLE_REG, 0x30)
    FIELD(INTERRUPT_ENABLE_REG, GWIEN, 2, 1)
    FIELD(INTERRUPT_ENABLE_REG, WRPEN, 1, 1)
    FIELD(INTERRUPT_ENABLE_REG, WINTEN, 0, 1)
REG32(INTERRUPT_DISABLE_REG, 0x34)
    FIELD(INTERRUPT_DISABLE_REG, GWID, 2, 1)
    FIELD(INTERRUPT_DISABLE_REG, WRPD, 1, 1)
    FIELD(INTERRUPT_DISABLE_REG, WINTD, 0, 1)
REG32(INTERRUPT_MASK_REG, 0x38)
    FIELD(INTERRUPT_MASK_REG, GWIM, 2, 1)
    FIELD(INTERRUPT_MASK_REG, WRPM, 1, 1)
    FIELD(INTERRUPT_MASK_REG, WINTM, 0, 1)
REG32(ECO, 0x3c)
REG32(GWDT_REFRESH_REG, 0x1000)
    FIELD(GWDT_REFRESH_REG, GWRR, 0, 1)
REG32(GWDT_CNTRL_STATUS_REG, 0x2000)
    FIELD(GWDT_CNTRL_STATUS_REG, GWS, 1, 2)
    FIELD(GWDT_CNTRL_STATUS_REG, GWEN, 0, 1)
REG32(GWDT_OFFSET_REG, 0x2008)
REG32(GWDT_COMPARE_VALUE_REG0, 0x2010)
REG32(GWDT_COMPARE_VALUE_REG1, 0x2014)
REG32(GWDT_WARM_RESET_REG, 0x2fd0)
    FIELD(GWDT_WARM_RESET_REG, GWWRR, 0, 1)

#define WWDT_R_MAX (R_GWDT_WARM_RESET_REG + 1)

typedef struct WWDT {
    SysBusDevice parent_obj;
    MemoryRegion iomem;

    /* GWDT */
    QEMUTimer *gwdt_timer;
    qemu_irq gwdt_ws0;
    qemu_irq gwdt_ws1;

    /* WWDT */
    QEMUTimer *wwdt_timer;
    QEMUTimer *wwdt_irq_timer;
    QEMUTimer *sst_timer;
    qemu_irq wwdt_reset_pending;
    qemu_irq wwdt_irq;

    /* Both WWDT and GWDT */
    qemu_irq wwdt_reset;

    /* WWDT Q&A state */
    uint8_t lfsr;
    bool wwdt_qa_running;
    uint8_t token_cnt;
    /* WWDT and WWDT Q&A state */
    bool window_2;
    bool wen;

    uint64_t pclk;
    uint32_t regs[WWDT_R_MAX];
    RegisterInfo regs_info[WWDT_R_MAX];
} WWDT;

static void gwdt_update_irq(WWDT *s)
{
    bool irq;

    irq = ARRAY_FIELD_EX32(s->regs, GWDT_CNTRL_STATUS_REG, GWS) & 0x1;
    irq &= !ARRAY_FIELD_EX32(s->regs, INTERRUPT_MASK_REG, GWIM);
    qemu_set_irq(s->gwdt_ws0, irq);
    irq = ARRAY_FIELD_EX32(s->regs, GWDT_CNTRL_STATUS_REG, GWS) & 0x2;
    qemu_set_irq(s->gwdt_ws1, irq);
}

static uint32_t gwdt_reload_val(WWDT *s)
{
    return s->regs[R_GWDT_OFFSET_REG];
}

static uint64_t gwdt_next_trigger(WWDT *s)
{
    uint64_t next = muldiv64(1000000000,
                             gwdt_reload_val(s),
                             s->pclk);

    return next + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
}

static void gwdt_time_elapsed(void *opaque)
{
    WWDT *s = XLNX_WWDT(opaque);
    uint8_t window = ARRAY_FIELD_EX32(s->regs, GWDT_CNTRL_STATUS_REG, GWS);

    switch (window) {
    case 0:
        ++window;
        ARRAY_FIELD_DP32(s->regs, GWDT_CNTRL_STATUS_REG, GWS, 1);
        gwdt_update_irq(s);
        break;
    case 1:
        ++window;
        ARRAY_FIELD_DP32(s->regs, GWDT_CNTRL_STATUS_REG, GWS, 3);
        gwdt_update_irq(s);
        /* Reset on second window timeout */
        qemu_set_irq(s->wwdt_reset, 1);
        break;
    case 2:
    case 3:
        /* Lines stay the same, value in GWS field stays the same */
        break;
    default:
        g_assert_not_reached();
    }

    timer_mod(s->gwdt_timer, gwdt_next_trigger(s));
}

static void gwdt_refresh_reg_postw(RegisterInfo *reg, uint64_t val64)
{
    WWDT *s = XLNX_WWDT(reg->opaque);

    timer_mod(s->gwdt_timer, gwdt_next_trigger(s));
    /* Read as zero */
    s->regs[R_GWDT_REFRESH_REG] = 0;

    ARRAY_FIELD_DP32(s->regs, GWDT_CNTRL_STATUS_REG, GWS, 0);
    gwdt_update_irq(s);
}

static void gwdt_cntrl_status_reg_postw(RegisterInfo *reg, uint64_t val64)
{
    WWDT *s = XLNX_WWDT(reg->opaque);

    if (FIELD_EX64(val64, GWDT_CNTRL_STATUS_REG, GWEN)) {
        timer_mod(s->gwdt_timer, gwdt_next_trigger(s));
    } else {
        timer_del(s->gwdt_timer);
        ARRAY_FIELD_DP32(s->regs, GWDT_CNTRL_STATUS_REG, GWS, 0);
        gwdt_update_irq(s);
    }
}

static void gwdt_warm_reset_reg_postw(RegisterInfo *reg, uint64_t val64)
{
    WWDT *s = XLNX_WWDT(reg->opaque);

    register_reset(&s->regs_info[R_GWDT_REFRESH_REG]);
    register_reset(&s->regs_info[R_GWDT_CNTRL_STATUS_REG]);
    register_reset(&s->regs_info[R_GWDT_OFFSET_REG]);
    register_reset(&s->regs_info[R_GWDT_COMPARE_VALUE_REG0]);
    register_reset(&s->regs_info[R_GWDT_COMPARE_VALUE_REG1]);
    /* Read as 0 */
    s->regs[R_GWDT_WARM_RESET_REG] = 0;
    gwdt_update_irq(s);
}

static void wwdt_update_irq(WWDT *s)
{
    bool irq;

    irq = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, WINT);
    irq &= !ARRAY_FIELD_EX32(s->regs, INTERRUPT_MASK_REG, WINTM);
    qemu_set_irq(s->wwdt_irq, irq);
    irq = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, WRP);
    irq &= !ARRAY_FIELD_EX32(s->regs, INTERRUPT_MASK_REG, WRPM);
    qemu_set_irq(s->wwdt_reset_pending, irq);
}

static void wwdt_do_reset(WWDT *s)
{
    qemu_set_irq(s->wwdt_reset, 1);
     /* After generating wwdt_reset, WWDT stops running and WEN auto clears. */
    timer_del(s->wwdt_timer);
    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WEN, 0);
}

static void wwdt_sst_time_elapsed(void *opaque)
{
    WWDT *s = XLNX_WWDT(opaque);
    wwdt_do_reset(s);
}
static uint32_t wwdt_sst_reload_val(WWDT *s)
{
    return s->regs[R_SECOND_SEQ_TIMER_REG];
}

static uint64_t wwdt_sst_next_trigger(WWDT *s)
{
    uint64_t next = muldiv64(1000000000,
                             wwdt_sst_reload_val(s),
                             s->pclk);

    return next + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
}

static void wwdt_sst_start(WWDT *s)
{
    bool rp_en = !ARRAY_FIELD_EX32(s->regs, INTERRUPT_MASK_REG, WRPM);
    if (rp_en) {
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WRP, 1);
        wwdt_update_irq(s);
    }
    timer_mod(s->sst_timer, wwdt_sst_next_trigger(s));
}

static void wwdt_reset_event(WWDT *s)
{
    bool sst_en = ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, SSTE);
    if (sst_en) {
        wwdt_sst_start(s);
    } else {
        wwdt_do_reset(s);
    }
}

static void wwdt_good_event(WWDT *s)
{
    bool count_en = ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, FCE);
    bool is_qa = ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, WM);

    if (count_en || is_qa) {
        uint8_t count = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, FCV);
        if (count) {
            --count;
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, FCV, count);
        }
    }
}

static void wwdt_bad_event(WWDT *s)
{
    bool count_en = ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, FCE);
    bool is_qa = ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, WM);

    /* Fail counter is always enabled in Q&A mode */
    if (count_en || is_qa) {
        uint8_t count = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, FCV);
        /* Bad event counter maxed out, do a reset */
        if (count == 7) {
            wwdt_reset_event(s);
        } else {
            ++count;
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, FCV, count);
        }
    } else {
        wwdt_reset_event(s);
    }
}

static void wwdt_irq_time_elapsed(void *opaque)
{
    WWDT *s = XLNX_WWDT(opaque);

    DB_PRINT_L(0, "WWDT SBC and BSS mask matched, asserting IRQ.\n");
    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WINT, 1);
    wwdt_update_irq(s);
}

static uint32_t wwdt_irq_reload_val(WWDT *s, bool *success)
{
    uint32_t shift = 1 << (ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG,
                                            BSS) * 8);
    uint32_t val = ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, SBC) <<
                                    shift;
    /* Make sure WINT can actually trigger within the second window */
    if (val > s->regs[R_SECOND_WINDOW_CONFIG_REG]) {
        success = false;
        qemu_log_mask(LOG_GUEST_ERROR, "%s: WINT is set to trigger after "
                      "second window expires.\nSBC=0x%x BSS=0x%x window=0x%x\n",
                      object_get_canonical_path(OBJECT(s)),
                      ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, SBC),
                      ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, BSS),
                      s->regs[R_SECOND_WINDOW_CONFIG_REG]);
    } else {
        *success = true;
    }

    return val;
}

static uint64_t wwdt_irq_next_trigger(WWDT *s, bool *success)
{
    uint64_t next = muldiv64(1000000000,
                             wwdt_irq_reload_val(s, success),
                             s->pclk);

    return next + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
}

/*
 * Hardware triggers WINT when the WWDT timer has a certain amount of bits
 * masked, specified by BSS and SBC.
 * An equivalent way to do this is to create another timer with the value of
 * SBC shifted by BSS.
 * We use another timer so the WWDT timer can continue to tick while WINT
 * is set and the IRQ is asserted.
 */
static void wwdt_irq_timer_start(WWDT *s)
{   uint64_t trigger;
    bool success;

    trigger = wwdt_irq_next_trigger(s, &success);
    if (success) {
        DB_PRINT_L(0, "Starting interrupt timer\n");
        timer_mod(s->wwdt_irq_timer, trigger);
    }
}

static uint32_t wwdt_reload_val(WWDT *s)
{
    if (s->window_2) {
        return s->regs[R_SECOND_WINDOW_CONFIG_REG];
    } else {
        return s->regs[R_FIRST_WINDOW_CONFIG_REG];
    }
}

static uint64_t wwdt_next_trigger(WWDT *s)
{
    uint64_t next = muldiv64(1000000000,
                             wwdt_reload_val(s),
                             s->pclk);

    return next + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
}

static void wwdt_qa_gen_token(WWDT *s)
{
    uint8_t fdbk = ARRAY_FIELD_EX32(s->regs, TOKEN_FEEDBACK_REG, FDBK);
    uint8_t token;

    switch (fdbk & 0x3) {
    case 0x0:
    case 0x3:
        token = !!((s->lfsr & 0x2) ^ (s->token_cnt & 0x2));
        token |= ((s->lfsr & 0x8) ^ (s->token_cnt & 0x8)) >> 2;
        token |= ((s->lfsr & 0x1) ^ (s->token_cnt & 0x1)) << 2;
        token |= ((s->lfsr & 0x4) ^ (s->token_cnt & 0x4)) << 1;
        break;
    case 0x1:
        token = !!((s->lfsr & 0x8) ^ (s->token_cnt & 0x8));
        token |= ((s->lfsr & 0x4) ^ (s->token_cnt & 0x4)) >> 1;
        token |= ((s->lfsr & 0x2) ^ (s->token_cnt & 0x2)) << 1;
        token |= ((s->lfsr & 0x1) ^ (s->token_cnt & 0x1)) << 3;
        break;
    case 0x2:
        token = !!((s->lfsr & 0x4) ^ (s->token_cnt & 0x4));
        token |= ((s->lfsr & 0x8) ^ (s->token_cnt & 0x8)) >> 2;
        token |= ((s->lfsr & 0x1) ^ (s->token_cnt & 0x1)) << 2;
        token |= ((s->lfsr & 0x2) ^ (s->token_cnt & 0x2)) << 2;
        break;
    }
    DB_PRINT_L(0, "Generated question token 0x%x\n", token);

    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, TVAL, token);
}

static void wwdt_qa_timer_start(WWDT *s)
{
    /* First time starting the timer, set initial values. */
    if (!s->wwdt_qa_running) {
        s->lfsr = ARRAY_FIELD_EX32(s->regs, TOKEN_FEEDBACK_REG, SEED);
        s->token_cnt = 1;
        s->wwdt_qa_running = true;
        wwdt_qa_gen_token(s);
    }

    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, ACNT, 3);

    DB_PRINT_L(0, "Starting first window timer\n");
    timer_mod(s->wwdt_timer, wwdt_next_trigger(s));
}

static void wwdt_qa_time_elapsed(WWDT *s)
{
    /* Second window timeout is always bad */
    if (s->window_2) {
        DB_PRINT_L(0, "Second window timeout\n");
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                         LBE_TIMEOUT_MASK);
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, TOUT, 1);
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, 0);
        s->window_2 = false;

        wwdt_bad_event(s);
        wwdt_qa_timer_start(s);
    } else {
        /* Error if we didn't get 3 correct answers in the first window. */
        if (ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, ACNT) != 2) {
            DB_PRINT_L(0, "First window timeout\n");
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                             LBE_TIMEOUT_MASK);
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, TOUT, 1);
            wwdt_bad_event(s);
            wwdt_qa_timer_start(s);
        } else {
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, 1);
            s->window_2 = true;

            wwdt_irq_timer_start(s);
            timer_mod(s->wwdt_timer, wwdt_next_trigger(s));
        }
    }
}

static void wwdt_basic_time_elapsed(WWDT *s)
{
    if (s->window_2) {
        DB_PRINT_L(0, "Second window timeout\n");
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                         LBE_SECOND_WINDOW_TIMEOUT_MASK);
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, TOUT, 1);
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, 0);
        wwdt_bad_event(s);
        s->window_2 = false;
    } else {
        DB_PRINT_L(0, "Starting second window timer\n");
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, 1);
        s->window_2 = true;

        wwdt_irq_timer_start(s);
    }

    timer_mod(s->wwdt_timer, wwdt_next_trigger(s));
}

static void wwdt_time_elapsed(void *opaque)
{
    WWDT *s = XLNX_WWDT(opaque);
    bool is_basic = !ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, WM);

    DB_PRINT_L(0, "Time elapsed\n");
    if (is_basic) {
        wwdt_basic_time_elapsed(s);
    } else {
        wwdt_qa_time_elapsed(s);
    }
}

static bool wwdt_basic_en(WWDT *s)
{
    bool success;

    if (!s->regs[R_SECOND_WINDOW_CONFIG_REG]) {
        qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot run WWDT in basic mode "
                      "with second window set to 0\n",
                      object_get_canonical_path(OBJECT(s)));
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WCFG, 1);
        success = false;
    } else {
        /* Block writes to WWDT when enabled. */
        ARRAY_FIELD_DP32(s->regs, MASTER_WRITE_CONTROL_REG, MWC, 0);
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                         LBE_BASIC_NO_BAD_EVENT_MASK);
        DB_PRINT_L(0, "Starting first window timer\n");
        timer_mod(s->wwdt_timer, wwdt_next_trigger(s));
        s->wen = true;
        success = true;
    }

    return success;
}

static bool wwdt_qa_en(WWDT *s)
{
    bool success;

    /*
     * Q&A mode timer isn't started until a separate write to TRR, however
     * we still block writes after WEN is set.
     */
    if (s->regs[R_SECOND_WINDOW_CONFIG_REG] &&
        s->regs[R_FIRST_WINDOW_CONFIG_REG]) {
        ARRAY_FIELD_DP32(s->regs, MASTER_WRITE_CONTROL_REG, MWC, 0);
        s->wen = true;
        success = true;
    } else {
        qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot run WWDT in Q&A mode "
                      "with either window set to 0\n",
                      object_get_canonical_path(OBJECT(s)));
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WCFG, 1);
        success = false;
    }

    return success;
}

static bool wwdt_en(WWDT *s)
{
    bool is_basic = !ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, WM);
    bool success;

    if (is_basic) {
        success = wwdt_basic_en(s);
    } else {
        success = wwdt_qa_en(s);
    }
    return success;
}

static bool wwdt_tsr_check(WWDT *s)
{
    bool match = s->regs[R_TASK_SIGNATURE_REG0] ==
                 s->regs[R_TASK_SIGNATURE_REG1];

    if (match) {
        wwdt_good_event(s);
    } else {
        wwdt_bad_event(s);
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                         LBE_TSR_MISMATCH_MASK);
        qemu_log_mask(LOG_GUEST_ERROR, "%s: TSR mistmatch: TSR0=0x%x, "
                      "TSR1=0x%x\n", object_get_canonical_path(OBJECT(s)),
                      s->regs[R_TASK_SIGNATURE_REG0],
                      s->regs[R_TASK_SIGNATURE_REG1]);
    }
    return match;
}

static void wwdt_basic_do_disable(WWDT *s)
{
    timer_del(s->wwdt_timer);
    s->wen = false;
    s->window_2 = false;
    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, 0);
}

static bool wwdt_basic_disable(WWDT *s)
{
    bool success;

    /* For basic WWDT, WEN can only be cleared in the second window */
    if (s->window_2) {
        bool psm_en = ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, PSME);
        if (psm_en) {
            if (wwdt_tsr_check(s)) {
                wwdt_basic_do_disable(s);
                success = true;
            } else {
                success = false;
            }
        } else {
            wwdt_basic_do_disable(s);
            success = true;
        }
    } else {
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                         LBE_KICK_IN_FIRST_WINDOW_MASK);
        wwdt_bad_event(s);
        qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot clear WEN in first "
                      "window\n", object_get_canonical_path(OBJECT(s)));
        success = false;
    }

    return success;
}

static bool wwdt_qa_disable(WWDT *s)
{
    uint8_t fail_count = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, FCV);
    bool success;

    /* In Q&A WWDT, WEN can be cleared as long as FCV == 0 */
    if (!fail_count) {
        s->wwdt_qa_running = false;
        s->window_2 = false;
        timer_del(s->wwdt_timer);
        success = true;
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, 0);
    } else {
        wwdt_bad_event(s);
        qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot clear WEN if FCV is "
                      "non-zero\n", object_get_canonical_path(OBJECT(s)));
        success = false;
    }
    return success;
}

static bool wwdt_disable(WWDT *s)
{
    bool is_basic = !ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, WM);
    bool success;

    if (is_basic) {
        success = wwdt_basic_disable(s);
    } else {
        success = wwdt_qa_disable(s);
    }
    return success;
}

static void wwdt_basic_do_kick(WWDT *s)
{
    DB_PRINT_L(0, "WDT kicked in second window\nRestarting in first window\n");
    wwdt_good_event(s);
    timer_mod(s->wwdt_timer, wwdt_next_trigger(s));
    s->window_2 = false;
    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, 0);
}

static void wwdt_basic_kick(WWDT *s)
{
    bool wsw_set = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, WSW);

    /* Kick in first window */
    if (!s->window_2 && wsw_set) {
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                         LBE_KICK_IN_FIRST_WINDOW_MASK);
        wwdt_bad_event(s);

        qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot reset WDT in first "
                      "window\n", object_get_canonical_path(OBJECT(s)));
    /* Kick in second window */
    } else if (s->window_2 && !wsw_set) {
        bool psm_en = ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, PSME);
        if (psm_en) {
            if (wwdt_tsr_check(s)) {
                wwdt_basic_do_kick(s);
            }
        } else {
            wwdt_basic_do_kick(s);
        }
    }
}

static uint8_t wwdt_qa_gen_ans(WWDT *s)
{
    uint8_t acnt = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, ACNT);
    uint8_t token = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, TVAL);
    uint8_t fdbk = ARRAY_FIELD_EX32(s->regs, TOKEN_FEEDBACK_REG, FDBK);
    uint8_t ans;

    INC_AND_ROLLOVER(acnt, 3);

    fdbk >>= 2;
    switch (fdbk) {
    case 0x0:
        ans = (token & 0x1) ^ !!(acnt & 0x2) ^ !!(token & 0x8);
        ans |= ((token & 0x1) ^ !!(acnt & 0x2) ^ !!(token & 0x4) ^
               !!(token & 0x2)) << 1;
        ans |= ((token & 0x1) ^ !!(acnt & 0x2) ^ !!(token & 0x8) ^
               !!(token & 0x2)) << 2;
        ans |= (!!(token & 0x4) ^ !!(acnt & 0x2) ^ (token & 0x1) ^
               !!(token & 0x8)) << 3;
        ans |= ((token & 0x2) ^ (acnt & 0x1)) << 4;
        ans |= (!!(token & 0x8) ^ (acnt & 0x1)) << 5;
        ans |= ((token & 0x1) ^ (acnt & 0x1)) << 6;
        ans |= (!!(token & 0x4) ^ (acnt & 0x1)) << 7;
        break;
    case 0x1:
        ans = !!(token & 0x2) ^ !!(acnt & 0x2) ^ (token & 0x1);
        ans |= (!!(token & 0x2) ^ !!(acnt & 0x2) ^ (token & 0x1) ^
               !!(token & 0x2)) << 1;
        ans |= (!!(token & 0x2) ^ !!(acnt & 0x2) ^ (token & 0x1) ^
               !!(token & 0x2)) << 2;
        ans |= ((token & 0x1) ^ !!(acnt & 0x2) ^ !!(token & 0x2) ^
              !!(token & 0x8)) << 3;
        ans |= ((token & 0x1) ^ (acnt & 0x1)) << 4;
        ans |= ((token & 0x1) ^ (acnt & 0x1)) << 5;
        ans |= (!!(token & 0x2) ^ (acnt & 0x1)) << 6;
        ans |= ((token & 0x1) ^ (acnt & 0x1)) << 7;
        break;
    case 0x2:
        ans = !!(token & 0x4) ^ !!(acnt & 0x2) ^ !!(token & 0x2);
        ans |= (!!(token & 0x4) ^ !!(acnt & 0x2) ^ !!(token & 0x2) ^
               !!(token & 0x2)) << 1;
        ans |= (!!(token & 0x4) ^ !!(acnt & 0x2) ^ !!(token & 0x2) ^
              !!(token & 0x2)) << 2;
        ans |= (!!(token & 0x2) ^ !!(acnt & 0x2) ^ !!(token & 0x4) ^
              !!(token & 0x8)) << 3;
        ans |= (!!(token & 0x4) ^ (acnt & 0x1)) << 4;
        ans |= (!!(token & 0x2) ^ (acnt & 0x1)) << 5;
        ans |= (!!(token & 0x4) ^ (acnt & 0x1)) << 6;
        ans |= (!!(token & 0x2) ^ (acnt & 0x1)) << 7;
        break;
    case 0x3:
        ans = !!(token & 0x8) ^ !!(acnt & 0x2) ^ !!(token & 0x4);
        ans |= (!!(token & 0x8) ^ !!(acnt & 0x2) ^ !!(token & 0x8) ^
               !!(token & 0x2)) << 1;
        ans |= (!!(token & 0x8) ^ !!(acnt & 0x2) ^ !!(token & 0x4) ^
               !!(token & 0x2)) << 2;
        ans |= (!!(token & 0x8) ^ !!(acnt & 0x2) ^ !!(token & 0x8) ^
               !!(token & 0x8)) << 3;
        ans |= (!!(token & 0x8) ^ (acnt & 0x1)) << 4;
        ans |= (!!(token & 0x4) ^ (acnt & 0x1)) << 5;
        ans |= (!!(token & 0x8) ^ (acnt & 0x1)) << 6;
        ans |= (!!(token & 0x4) ^ (acnt & 0x1)) << 7;
        break;
    }

    DB_PRINT_L(0, "Generated answer 0x%x\n", ans);

    return ans;
}

static void wwdt_qa_next_token(WWDT *s)
{
    s->lfsr = ((!!((s->lfsr & 0x4) | (s->lfsr & 0x2))) |
              ((s->lfsr & 0x1) << 2) |
              (((s->lfsr & 0x8) ^ (s->lfsr & 0x4)) << 3));
    INC_AND_ROLLOVER(s->token_cnt, 0x0f);
    wwdt_qa_gen_token(s);
}

static void wwdt_qa_answer_match(WWDT *s)
{
    uint8_t acnt = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, ACNT);

    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, TERR, 0);
    /* If this is the 4th write, it's only valid in the 2nd window */
    if (acnt == 2) {
        if (s->window_2) {
            /* Restart WWDT to first window. LBE status is also reset. */
            s->window_2 = false;
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, 0);
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, SERR, 0);
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, TERL, 0);
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                             LBE_QA_NO_BAD_EVENT_MASK);
            wwdt_good_event(s);
            wwdt_qa_next_token(s);
            wwdt_qa_timer_start(s);
        } else {
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                             LBE_TOKEN_EARLY_MASK);
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, SERR, 1);
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, TERL, 1);
            wwdt_bad_event(s);
        }
    } else {
        INC_AND_ROLLOVER(acnt, 3);

        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, ACNT, acnt);
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, SERR, 0);
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, TERL, 0);
    }
}

static void wwdt_qa_answer_mismatch(WWDT *s)
{
    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, SERR, 1);
    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, TERR, 1);
    ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                     LBE_TOKEN_ERROR_MASK);
    wwdt_bad_event(s);
}

static void wwdt_enable_and_status_reg_postw(RegisterInfo *reg, uint64_t val64)
{
    bool success;
    WWDT *s = XLNX_WWDT(reg->opaque);
    uint32_t val = (uint32_t)val64;
    bool wen_set = ARRAY_FIELD_EX32(s->regs, ENABLE_AND_STATUS_REG, WEN);
    bool is_basic = !ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, WM);
    bool wsw_clear = FIELD_EX32(val, ENABLE_AND_STATUS_REG, WSW);

    /*
     * WSW is W1C, but the register API zeroes the write before prew or postw.
     * We need to know if the user is writing 1 or 0 to WSW to know if it's a
     * kick or not, so we make the field RW and W1C it manually.
     */
    if (wsw_clear) {
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, 0);
    } else {
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WSW, s->window_2);
    }

    if (!s->wen && wen_set) {
        success = wwdt_en(s);
        /* WEN is not set on a misconfiguration. */
        if (!success) {
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WEN, 0);
        }
    } else if (s->wen && !wen_set) {
        success = wwdt_disable(s);
        /* WEN is not cleared if done in the wrong situation. */
        if (!success) {
            ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, WEN, 1);
        }
    }

    if (is_basic && wsw_clear) {
        wwdt_basic_kick(s);
    } else if (wsw_clear) {
        qemu_log_mask(LOG_GUEST_ERROR, "%s: WSW is RO in Q&A mode.\n",
                      object_get_canonical_path(OBJECT(s)));
    }

    wwdt_update_irq(s);
}

static uint64_t wwdt_enable_and_status_reg_prew(RegisterInfo *reg,
                                                uint64_t val64)
{
    WWDT *s = XLNX_WWDT(reg->opaque);
    uint32_t val = (uint64_t)val64;
    bool wen_set = FIELD_EX32(val, ENABLE_AND_STATUS_REG, WEN);
    bool dis_prot = ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, WDP);

    if (dis_prot) {
        if (s->wen && !wen_set) {
            qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot clear WEN when WDS is "
                          "set\n", object_get_canonical_path(OBJECT(s)));
            FIELD_DP32(val, ENABLE_AND_STATUS_REG, WEN, 1);
        }
    }
    return val;
}

static void wwdt_token_response_reg_postw(RegisterInfo *reg, uint64_t val)
{
    WWDT *s = XLNX_WWDT(reg->opaque);
    bool is_basic = !ARRAY_FIELD_EX32(s->regs, FUNCTION_CONTROL_REG, WM);
    uint8_t expected_ans;

    if (is_basic) {
        return;
    }

    /* Start WWDT timer on first write. */
    if (!s->wwdt_qa_running) {
        ARRAY_FIELD_DP32(s->regs, ENABLE_AND_STATUS_REG, LBE,
                         LBE_QA_NO_BAD_EVENT_MASK);
        wwdt_qa_timer_start(s);
    /* Future writes to this register should be checked if they're correct. */
    } else {
        expected_ans = wwdt_qa_gen_ans(s);
        if (expected_ans == val) {
            wwdt_qa_answer_match(s);
        } else {
            wwdt_qa_answer_mismatch(s);
        }
    }
}

static void wwdt_interrupt_enable_reg_postw(RegisterInfo *reg, uint64_t val64)
{
    WWDT *s = XLNX_WWDT(reg->opaque);
    uint32_t val = (uint32_t)val64;

    val = ~val;
    s->regs[R_INTERRUPT_MASK_REG] &= ~val;
}

static void wwdt_interrupt_disable_reg_postw(RegisterInfo *reg, uint64_t val64)
{
    WWDT *s = XLNX_WWDT(reg->opaque);
    uint32_t val = (uint32_t)val64;

    s->regs[R_INTERRUPT_MASK_REG] |= val;
}

static const RegisterAccessInfo wwdt_regs_info[] = {
    {   .name = "MASTER_WRITE_CONTROL_REG",  .addr = A_MASTER_WRITE_CONTROL_REG,
        .reset = 0x1,
        .rsvd = 0xfffffffe,
    },{ .name = "ENABLE_AND_STATUS_REG",  .addr = A_ENABLE_AND_STATUS_REG,
        .reset = 0x500000,
        .rsvd = 0xf88c20c0,
        .ro = 0x70de3c,
        .w1c = 0x7030002,
        .post_write = wwdt_enable_and_status_reg_postw,
        .pre_write = wwdt_enable_and_status_reg_prew,
    },{ .name = "FUNCTION_CONTROL_REG",  .addr = A_FUNCTION_CONTROL_REG,
        .rsvd = 0xffff0020,
    },{ .name = "FIRST_WINDOW_CONFIG_REG",  .addr = A_FIRST_WINDOW_CONFIG_REG,
    },{ .name = "SECOND_WINDOW_CONFIG_REG",  .addr = A_SECOND_WINDOW_CONFIG_REG,
    },{ .name = "SST_WINDOW_CONFIG_REG",  .addr = A_SST_WINDOW_CONFIG_REG,
    },{ .name = "TASK_SIGNATURE_REG0",  .addr = A_TASK_SIGNATURE_REG0,
    },{ .name = "TASK_SIGNATURE_REG1",  .addr = A_TASK_SIGNATURE_REG1,
    },{ .name = "SECOND_SEQ_TIMER_REG",  .addr = A_SECOND_SEQ_TIMER_REG,
        .ro = 0xffffffff,
    },{ .name = "TOKEN_FEEDBACK_REG",  .addr = A_TOKEN_FEEDBACK_REG,
        .rsvd = 0xfffff0f0,
    },{ .name = "TOKEN_RESPONSE_REG",  .addr = A_TOKEN_RESPONSE_REG,
        .rsvd = 0xffffff00,
        .post_write = wwdt_token_response_reg_postw,
    },{ .name = "INTERRUPT_ENABLE_REG",  .addr = A_INTERRUPT_ENABLE_REG,
        .rsvd = 0xfffffff8,
        .post_write = wwdt_interrupt_enable_reg_postw,
    },{ .name = "INTERRUPT_DISABLE_REG",  .addr = A_INTERRUPT_DISABLE_REG,
        .rsvd = 0xfffffff8,
        .post_write = wwdt_interrupt_disable_reg_postw,
    },{ .name = "INTERRUPT_MASK_REG",  .addr = A_INTERRUPT_MASK_REG,
        .reset = 0x2,
        .rsvd = 0xfffffff8,
        .ro = 0x7,
    },{ .name = "ECO",  .addr = A_ECO,
    },{ .name = "GWDT_REFRESH_REG",  .addr = A_GWDT_REFRESH_REG,
        .rsvd = 0xfffffffe,
        .post_write = gwdt_refresh_reg_postw,
    },{ .name = "GWDT_CNTRL_STATUS_REG",  .addr = A_GWDT_CNTRL_STATUS_REG,
        .rsvd = 0xfffffff8,
        .ro = 0x6,
        .post_write = gwdt_cntrl_status_reg_postw,
    },{ .name = "GWDT_OFFSET_REG",  .addr = A_GWDT_OFFSET_REG,
    },{ .name = "GWDT_COMPARE_VALUE_REG0",  .addr = A_GWDT_COMPARE_VALUE_REG0,
    },{ .name = "GWDT_COMPARE_VALUE_REG1",  .addr = A_GWDT_COMPARE_VALUE_REG1,
    },{ .name = "GWDT_WARM_RESET_REG",  .addr = A_GWDT_WARM_RESET_REG,
        .rsvd = 0xfffffffe,
        .post_write = gwdt_warm_reset_reg_postw,
    }
};

static void wwdt_reset(DeviceState *dev)
{
    WWDT *s = XLNX_WWDT(dev);
    uint32_t i;

    for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) {
        register_reset(&s->regs_info[i]);
    }
    s->lfsr = 0;
    s->token_cnt = 0;
    s->wwdt_qa_running = false;
}

static WWDT *wwdt_from_mr(void *mr_accessor)
{
    RegisterInfoArray *reg_array = mr_accessor;
    Object *obj;

    assert(reg_array != NULL);

    obj = reg_array->mem.owner;
    assert(obj);

    return XLNX_WWDT(obj);
}

static MemTxResult wwdt_write(void *opaque, hwaddr addr, uint64_t val,
                              unsigned size, MemTxAttrs attrs)
{
    WWDT *s = wwdt_from_mr(opaque);
    bool locked = !ARRAY_FIELD_EX32(s->regs, MASTER_WRITE_CONTROL_REG, MWC);

    /* MWC reg and GWDT address space cannot be locked */
    if (locked && (addr > A_MASTER_WRITE_CONTROL_REG &&
                   addr < A_GWDT_REFRESH_REG)) {
        qemu_log_mask(LOG_GUEST_ERROR, "%s: Accessing locked register "
                      "0x%"HWADDR_PRIx"\n",
                      object_get_canonical_path(OBJECT(s)), addr);
        return MEMTX_ERROR;
    }

    register_write_memory(opaque, addr, val, size);
    return MEMTX_OK;
}

static const MemoryRegionOps wwdt_ops = {
    .read = register_read_memory,
    .write_with_attrs = wwdt_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = {
        .min_access_size = 4,
        .max_access_size = 4,
    },
};

static void wwdt_realize(DeviceState *dev, Error **errp)
{
    WWDT *s = XLNX_WWDT(dev);

    if (!s->pclk) {
        error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, "pclk");
    }
}

static void wwdt_init(Object *obj)
{
    WWDT *s = XLNX_WWDT(obj);
    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
    RegisterInfoArray *reg_array;

    memory_region_init(&s->iomem, obj, TYPE_XILINX_WWDT, WWDT_R_MAX * 4);
    reg_array =
        register_init_block32(DEVICE(obj), wwdt_regs_info,
                              ARRAY_SIZE(wwdt_regs_info),
                              s->regs_info, s->regs,
                              &wwdt_ops,
                              XILINX_WWDT_ERR_DEBUG,
                              WWDT_R_MAX * 4);
    memory_region_add_subregion(&s->iomem,
                                0x0,
                                &reg_array->mem);
    sysbus_init_mmio(sbd, &s->iomem);

    /* Order must match the order in DTS */
    sysbus_init_irq(sbd, &s->wwdt_irq);
    sysbus_init_irq(sbd, &s->wwdt_reset_pending);
    sysbus_init_irq(sbd, &s->gwdt_ws0);
    sysbus_init_irq(sbd, &s->gwdt_ws1);

    qdev_init_gpio_out_named(DEVICE(obj), &s->wwdt_reset, "reset-out", 1);

    s->gwdt_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, gwdt_time_elapsed, s);
    s->wwdt_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, wwdt_time_elapsed, s);
    s->wwdt_irq_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, wwdt_irq_time_elapsed,
                                     s);
    s->sst_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, wwdt_sst_time_elapsed, s);
}

static Property wwdt_properties[] = {
    DEFINE_PROP_UINT64("pclk", WWDT, pclk, 0),
    DEFINE_PROP_END_OF_LIST(),
};

static const FDTGenericGPIOSet wwdt_client_gpios[] = {
    {
        .names = &fdt_generic_gpio_name_set_gpio,
        .gpios = (FDTGenericGPIOConnection[]) {
            { .name = "reset-out", .fdt_index = 0, .range = 1 },
            { },
        }
    },
    { },
};

static const VMStateDescription vmstate_wwdt = {
    .name = TYPE_XILINX_WWDT,
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32_ARRAY(regs, WWDT, WWDT_R_MAX),
        VMSTATE_END_OF_LIST(),
    }
};

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

    dc->reset = wwdt_reset;
    dc->realize = wwdt_realize;
    dc->vmsd = &vmstate_wwdt;
    device_class_set_props(dc, wwdt_properties);
    fggc->client_gpios = wwdt_client_gpios;
}

static const TypeInfo wwdt_info = {
    .name          = TYPE_XILINX_WWDT,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(WWDT),
    .class_init    = wwdt_class_init,
    .instance_init = wwdt_init,
    .interfaces = (InterfaceInfo[]) {
        { TYPE_FDT_GENERIC_GPIO },
        { }
    }
};

static void wwdt_register_types(void)
{
    type_register_static(&wwdt_info);
}

type_init(wwdt_register_types)
