diff mbox series

[v3,3/6] hw/timer: Adding watchdog for NPCM7XX Timer.

Message ID 20201023210637.351238-4-hskinnemoen@google.com
State Superseded
Headers show
Series Additional NPCM7xx features, devices and tests | expand

Commit Message

Xingtao Yao (Fujitsu)" via Oct. 23, 2020, 9:06 p.m. UTC
From: Hao Wu <wuhaotsh@google.com>

The watchdog is part of NPCM7XX's timer module. Its behavior is
controlled by the WTCR register in the timer.

When enabled, the watchdog issues an interrupt signal after a pre-set
amount of cycles, and issues a reset signal shortly after that.

Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
Signed-off-by: Havard Skinnemoen <hskinnemoen@google.com>
---
 include/hw/misc/npcm7xx_clk.h             |   2 +
 include/hw/timer/npcm7xx_timer.h          |  48 +++-
 hw/arm/npcm7xx.c                          |  12 +
 hw/misc/npcm7xx_clk.c                     |  28 ++
 hw/timer/npcm7xx_timer.c                  | 266 ++++++++++++++----
 tests/qtest/npcm7xx_watchdog_timer-test.c | 320 ++++++++++++++++++++++
 MAINTAINERS                               |   1 +
 tests/qtest/meson.build                   |   2 +-
 8 files changed, 625 insertions(+), 54 deletions(-)
 create mode 100644 tests/qtest/npcm7xx_watchdog_timer-test.c
diff mbox series

Patch

diff --git a/include/hw/misc/npcm7xx_clk.h b/include/hw/misc/npcm7xx_clk.h
index cdcc9e8534..2338fbbdb5 100644
--- a/include/hw/misc/npcm7xx_clk.h
+++ b/include/hw/misc/npcm7xx_clk.h
@@ -31,6 +31,8 @@ 
  */
 #define NPCM7XX_CLK_NR_REGS             (0x70 / sizeof(uint32_t))
 
+#define NPCM7XX_WATCHDOG_RESET_GPIO_IN "npcm7xx-clk-watchdog-reset-gpio-in"
+
 typedef struct NPCM7xxCLKState {
     SysBusDevice parent;
 
diff --git a/include/hw/timer/npcm7xx_timer.h b/include/hw/timer/npcm7xx_timer.h
index 878a365a79..6993fd723a 100644
--- a/include/hw/timer/npcm7xx_timer.h
+++ b/include/hw/timer/npcm7xx_timer.h
@@ -29,14 +29,31 @@ 
  */
 #define NPCM7XX_TIMER_NR_REGS (0x54 / sizeof(uint32_t))
 
+/* The basic watchdog timer period is 2^14 clock cycles. */
+#define NPCM7XX_WATCHDOG_BASETIME_SHIFT 14
+
+#define NPCM7XX_WATCHDOG_RESET_GPIO_OUT "npcm7xx-clk-watchdog-reset-gpio-out"
+
 typedef struct NPCM7xxTimerCtrlState NPCM7xxTimerCtrlState;
 
 /**
- * struct NPCM7xxTimer - Individual timer state.
- * @irq: GIC interrupt line to fire on expiration (if enabled).
+ * struct NPCM7xxBaseTimer - Basic functionality that both regular timer and
+ * watchdog timer use.
  * @qtimer: QEMU timer that notifies us on expiration.
  * @expires_ns: Absolute virtual expiration time.
  * @remaining_ns: Remaining time until expiration if timer is paused.
+ */
+typedef struct NPCM7xxBaseTimer {
+    QEMUTimer   qtimer;
+    int64_t     expires_ns;
+    int64_t     remaining_ns;
+} NPCM7xxBaseTimer;
+
+/**
+ * struct NPCM7xxTimer - Individual timer state.
+ * @ctrl: The timer module that owns this timer.
+ * @irq: GIC interrupt line to fire on expiration (if enabled).
+ * @base_timer: The basic timer functionality for this timer.
  * @tcsr: The Timer Control and Status Register.
  * @ticr: The Timer Initial Count Register.
  */
@@ -44,21 +61,38 @@  typedef struct NPCM7xxTimer {
     NPCM7xxTimerCtrlState *ctrl;
 
     qemu_irq    irq;
-    QEMUTimer   qtimer;
-    int64_t     expires_ns;
-    int64_t     remaining_ns;
+    NPCM7xxBaseTimer base_timer;
 
     uint32_t    tcsr;
     uint32_t    ticr;
 } NPCM7xxTimer;
 
+/**
+ * struct NPCM7xxWatchdogTimer - The watchdog timer state.
+ * @ctrl: The timer module that owns this timer.
+ * @irq: GIC interrupt line to fire on expiration (if enabled).
+ * @reset_signal: The GPIO used to send a reset signal.
+ * @base_timer: The basic timer functionality for this timer.
+ * @wtcr: The Watchdog Timer Control Register.
+ */
+typedef struct NPCM7xxWatchdogTimer {
+    NPCM7xxTimerCtrlState *ctrl;
+
+    qemu_irq            irq;
+    qemu_irq            reset_signal;
+    NPCM7xxBaseTimer base_timer;
+
+    uint32_t            wtcr;
+} NPCM7xxWatchdogTimer;
+
 /**
  * struct NPCM7xxTimerCtrlState - Timer Module device state.
  * @parent: System bus device.
  * @iomem: Memory region through which registers are accessed.
+ * @index: The index of this timer module.
  * @tisr: The Timer Interrupt Status Register.
- * @wtcr: The Watchdog Timer Control Register.
  * @timer: The five individual timers managed by this module.
+ * @watchdog_timer: The watchdog timer managed by this module.
  */
 struct NPCM7xxTimerCtrlState {
     SysBusDevice parent;
@@ -66,9 +100,9 @@  struct NPCM7xxTimerCtrlState {
     MemoryRegion iomem;
 
     uint32_t    tisr;
-    uint32_t    wtcr;
 
     NPCM7xxTimer timer[NPCM7XX_TIMERS_PER_CTRL];
+    NPCM7xxWatchdogTimer watchdog_timer;
 };
 
 #define TYPE_NPCM7XX_TIMER "npcm7xx-timer"
diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c
index 037f3a26f2..c341dcab8b 100644
--- a/hw/arm/npcm7xx.c
+++ b/hw/arm/npcm7xx.c
@@ -86,6 +86,9 @@  enum NPCM7xxInterrupt {
     NPCM7XX_TIMER12_IRQ,
     NPCM7XX_TIMER13_IRQ,
     NPCM7XX_TIMER14_IRQ,
+    NPCM7XX_WDG0_IRQ            = 47,   /* Timer Module 0 Watchdog */
+    NPCM7XX_WDG1_IRQ,                   /* Timer Module 1 Watchdog */
+    NPCM7XX_WDG2_IRQ,                   /* Timer Module 2 Watchdog */
 };
 
 /* Total number of GIC interrupts, including internal Cortex-A9 interrupts. */
@@ -353,6 +356,15 @@  static void npcm7xx_realize(DeviceState *dev, Error **errp)
             qemu_irq irq = npcm7xx_irq(s, first_irq + j);
             sysbus_connect_irq(sbd, j, irq);
         }
+
+        /* IRQ for watchdogs */
+        sysbus_connect_irq(sbd, NPCM7XX_TIMERS_PER_CTRL,
+                npcm7xx_irq(s, NPCM7XX_WDG0_IRQ + i));
+        /* GPIO that connects clk module with watchdog */
+        qdev_connect_gpio_out_named(DEVICE(&s->tim[i]),
+                NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 0,
+                qdev_get_gpio_in_named(DEVICE(&s->clk),
+                        NPCM7XX_WATCHDOG_RESET_GPIO_IN, i));
     }
 
     /* UART0..3 (16550 compatible) */
diff --git a/hw/misc/npcm7xx_clk.c b/hw/misc/npcm7xx_clk.c
index 21ab4200d1..6732437fe2 100644
--- a/hw/misc/npcm7xx_clk.c
+++ b/hw/misc/npcm7xx_clk.c
@@ -17,6 +17,7 @@ 
 #include "qemu/osdep.h"
 
 #include "hw/misc/npcm7xx_clk.h"
+#include "hw/timer/npcm7xx_timer.h"
 #include "migration/vmstate.h"
 #include "qemu/error-report.h"
 #include "qemu/log.h"
@@ -24,6 +25,7 @@ 
 #include "qemu/timer.h"
 #include "qemu/units.h"
 #include "trace.h"
+#include "sysemu/watchdog.h"
 
 #define PLLCON_LOKI     BIT(31)
 #define PLLCON_LOKS     BIT(30)
@@ -87,6 +89,12 @@  static const uint32_t cold_reset_values[NPCM7XX_CLK_NR_REGS] = {
     [NPCM7XX_CLK_AHBCKFI]       = 0x000000c8,
 };
 
+/* Register Field Definitions */
+#define NPCM7XX_CLK_WDRCR_CA9C  BIT(0) /* Cortex A9 Cores */
+
+/* The number of watchdogs that can trigger a reset. */
+#define NPCM7XX_NR_WATCHDOGS    (3)
+
 static uint64_t npcm7xx_clk_read(void *opaque, hwaddr offset, unsigned size)
 {
     uint32_t reg = offset / sizeof(uint32_t);
@@ -187,6 +195,24 @@  static void npcm7xx_clk_write(void *opaque, hwaddr offset,
     s->regs[reg] = value;
 }
 
+/* Perform reset action triggered by a watchdog */
+static void npcm7xx_clk_perform_watchdog_reset(void *opaque, int n,
+        int level)
+{
+    NPCM7xxCLKState *clk = NPCM7XX_CLK(opaque);
+    uint32_t rcr;
+
+    g_assert(n >= 0 && n <= NPCM7XX_NR_WATCHDOGS);
+    rcr = clk->regs[NPCM7XX_CLK_WD0RCR + n];
+    if (rcr & NPCM7XX_CLK_WDRCR_CA9C) {
+        watchdog_perform_action();
+    } else {
+        qemu_log_mask(LOG_UNIMP,
+                "%s: only CPU reset is implemented. (requested 0x%" PRIx32")\n",
+                __func__, rcr);
+    }
+}
+
 static const struct MemoryRegionOps npcm7xx_clk_ops = {
     .read       = npcm7xx_clk_read,
     .write      = npcm7xx_clk_write,
@@ -226,6 +252,8 @@  static void npcm7xx_clk_init(Object *obj)
     memory_region_init_io(&s->iomem, obj, &npcm7xx_clk_ops, s,
                           TYPE_NPCM7XX_CLK, 4 * KiB);
     sysbus_init_mmio(&s->parent, &s->iomem);
+    qdev_init_gpio_in_named(DEVICE(s), npcm7xx_clk_perform_watchdog_reset,
+            NPCM7XX_WATCHDOG_RESET_GPIO_IN, NPCM7XX_NR_WATCHDOGS);
 }
 
 static const VMStateDescription vmstate_npcm7xx_clk = {
diff --git a/hw/timer/npcm7xx_timer.c b/hw/timer/npcm7xx_timer.c
index 2df9e3e496..d24445bd6e 100644
--- a/hw/timer/npcm7xx_timer.c
+++ b/hw/timer/npcm7xx_timer.c
@@ -17,6 +17,7 @@ 
 #include "qemu/osdep.h"
 
 #include "hw/irq.h"
+#include "hw/qdev-properties.h"
 #include "hw/misc/npcm7xx_clk.h"
 #include "hw/timer/npcm7xx_timer.h"
 #include "migration/vmstate.h"
@@ -60,6 +61,50 @@  enum NPCM7xxTimerRegisters {
 #define NPCM7XX_TCSR_PRESCALE_START     0
 #define NPCM7XX_TCSR_PRESCALE_LEN       8
 
+#define NPCM7XX_WTCR_WTCLK(rv)          extract32(rv, 10, 2)
+#define NPCM7XX_WTCR_FREEZE_EN          BIT(9)
+#define NPCM7XX_WTCR_WTE                BIT(7)
+#define NPCM7XX_WTCR_WTIE               BIT(6)
+#define NPCM7XX_WTCR_WTIS(rv)           extract32(rv, 4, 2)
+#define NPCM7XX_WTCR_WTIF               BIT(3)
+#define NPCM7XX_WTCR_WTRF               BIT(2)
+#define NPCM7XX_WTCR_WTRE               BIT(1)
+#define NPCM7XX_WTCR_WTR                BIT(0)
+
+/*
+ * The number of clock cycles between interrupt and reset in watchdog, used
+ * by the software to handle the interrupt before system is reset.
+ */
+#define NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES 1024
+
+/* Start or resume the timer. */
+static void npcm7xx_timer_start(NPCM7xxBaseTimer *t)
+{
+    int64_t now;
+
+    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    t->expires_ns = now + t->remaining_ns;
+    timer_mod(&t->qtimer, t->expires_ns);
+}
+
+/* Stop counting. Record the time remaining so we can continue later. */
+static void npcm7xx_timer_pause(NPCM7xxBaseTimer *t)
+{
+    int64_t now;
+
+    timer_del(&t->qtimer);
+    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    t->remaining_ns = t->expires_ns - now;
+}
+
+/* Delete the timer and reset it to default state. */
+static void npcm7xx_timer_clear(NPCM7xxBaseTimer *t)
+{
+    timer_del(&t->qtimer);
+    t->expires_ns = 0;
+    t->remaining_ns = 0;
+}
+
 /*
  * Returns the index of timer in the tc->timer array. This can be used to
  * locate the registers that belong to this timer.
@@ -102,6 +147,52 @@  static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns)
     return count;
 }
 
+static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t)
+{
+    switch (NPCM7XX_WTCR_WTCLK(t->wtcr)) {
+    case 0:
+        return 1;
+    case 1:
+        return 256;
+    case 2:
+        return 2048;
+    case 3:
+        return 65536;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static void npcm7xx_watchdog_timer_reset_cycles(NPCM7xxWatchdogTimer *t,
+        int64_t cycles)
+{
+    uint32_t prescaler = npcm7xx_watchdog_timer_prescaler(t);
+    int64_t ns = (NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ) * cycles;
+
+    /*
+     * The reset function always clears the current timer. The caller of the
+     * this needs to decide whether to start the watchdog timer based on
+     * specific flag in WTCR.
+     */
+    npcm7xx_timer_clear(&t->base_timer);
+
+    ns *= prescaler;
+    t->base_timer.remaining_ns = ns;
+}
+
+static void npcm7xx_watchdog_timer_reset(NPCM7xxWatchdogTimer *t)
+{
+    int64_t cycles = 1;
+    uint32_t s = NPCM7XX_WTCR_WTIS(t->wtcr);
+
+    g_assert(s <= 3);
+
+    cycles <<= NPCM7XX_WATCHDOG_BASETIME_SHIFT;
+    cycles <<= 2 * s;
+
+    npcm7xx_watchdog_timer_reset_cycles(t, cycles);
+}
+
 /*
  * Raise the interrupt line if there's a pending interrupt and interrupts are
  * enabled for this timer. If not, lower it.
@@ -116,16 +207,6 @@  static void npcm7xx_timer_check_interrupt(NPCM7xxTimer *t)
     trace_npcm7xx_timer_irq(DEVICE(tc)->canonical_path, index, pending);
 }
 
-/* Start or resume the timer. */
-static void npcm7xx_timer_start(NPCM7xxTimer *t)
-{
-    int64_t now;
-
-    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-    t->expires_ns = now + t->remaining_ns;
-    timer_mod(&t->qtimer, t->expires_ns);
-}
-
 /*
  * Called when the counter reaches zero. Sets the interrupt flag, and either
  * restarts or disables the timer.
@@ -138,9 +219,9 @@  static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t)
     tc->tisr |= BIT(index);
 
     if (t->tcsr & NPCM7XX_TCSR_PERIODIC) {
-        t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
+        t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
         if (t->tcsr & NPCM7XX_TCSR_CEN) {
-            npcm7xx_timer_start(t);
+            npcm7xx_timer_start(&t->base_timer);
         }
     } else {
         t->tcsr &= ~(NPCM7XX_TCSR_CEN | NPCM7XX_TCSR_CACT);
@@ -149,15 +230,6 @@  static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t)
     npcm7xx_timer_check_interrupt(t);
 }
 
-/* Stop counting. Record the time remaining so we can continue later. */
-static void npcm7xx_timer_pause(NPCM7xxTimer *t)
-{
-    int64_t now;
-
-    timer_del(&t->qtimer);
-    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-    t->remaining_ns = t->expires_ns - now;
-}
 
 /*
  * Restart the timer from its initial value. If the timer was enabled and stays
@@ -167,10 +239,10 @@  static void npcm7xx_timer_pause(NPCM7xxTimer *t)
  */
 static void npcm7xx_timer_restart(NPCM7xxTimer *t, uint32_t old_tcsr)
 {
-    t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
+    t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
 
     if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
-        npcm7xx_timer_start(t);
+        npcm7xx_timer_start(&t->base_timer);
     }
 }
 
@@ -181,10 +253,10 @@  static uint32_t npcm7xx_timer_read_tdr(NPCM7xxTimer *t)
     if (t->tcsr & NPCM7XX_TCSR_CEN) {
         int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
 
-        return npcm7xx_timer_ns_to_count(t, t->expires_ns - now);
+        return npcm7xx_timer_ns_to_count(t, t->base_timer.expires_ns - now);
     }
 
-    return npcm7xx_timer_ns_to_count(t, t->remaining_ns);
+    return npcm7xx_timer_ns_to_count(t, t->base_timer.remaining_ns);
 }
 
 static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr)
@@ -216,9 +288,9 @@  static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr)
 
     if (npcm7xx_tcsr_prescaler(old_tcsr) != npcm7xx_tcsr_prescaler(new_tcsr)) {
         /* Recalculate time remaining based on the current TDR value. */
-        t->remaining_ns = npcm7xx_timer_count_to_ns(t, tdr);
+        t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, tdr);
         if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
-            npcm7xx_timer_start(t);
+            npcm7xx_timer_start(&t->base_timer);
         }
     }
 
@@ -232,11 +304,11 @@  static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr)
     if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_CEN) {
         if (new_tcsr & NPCM7XX_TCSR_CEN) {
             t->tcsr |= NPCM7XX_TCSR_CACT;
-            npcm7xx_timer_start(t);
+            npcm7xx_timer_start(&t->base_timer);
         } else {
             t->tcsr &= ~NPCM7XX_TCSR_CACT;
-            npcm7xx_timer_pause(t);
-            if (t->remaining_ns <= 0) {
+            npcm7xx_timer_pause(&t->base_timer);
+            if (t->base_timer.remaining_ns <= 0) {
                 npcm7xx_timer_reached_zero(t);
             }
         }
@@ -259,9 +331,47 @@  static void npcm7xx_timer_write_tisr(NPCM7xxTimerCtrlState *s, uint32_t value)
         if (value & (1U << i)) {
             npcm7xx_timer_check_interrupt(&s->timer[i]);
         }
+
     }
 }
 
+static void npcm7xx_timer_write_wtcr(NPCM7xxWatchdogTimer *t, uint32_t new_wtcr)
+{
+    uint32_t old_wtcr = t->wtcr;
+
+    /*
+     * WTIF and WTRF are cleared by writing 1. Writing 0 makes these bits
+     * unchanged.
+     */
+    if (new_wtcr & NPCM7XX_WTCR_WTIF) {
+        new_wtcr &= ~NPCM7XX_WTCR_WTIF;
+    } else if (old_wtcr & NPCM7XX_WTCR_WTIF) {
+        new_wtcr |= NPCM7XX_WTCR_WTIF;
+    }
+    if (new_wtcr & NPCM7XX_WTCR_WTRF) {
+        new_wtcr &= ~NPCM7XX_WTCR_WTRF;
+    } else if (old_wtcr & NPCM7XX_WTCR_WTRF) {
+        new_wtcr |= NPCM7XX_WTCR_WTRF;
+    }
+
+    t->wtcr = new_wtcr;
+
+    if (new_wtcr & NPCM7XX_WTCR_WTR) {
+        t->wtcr &= ~NPCM7XX_WTCR_WTR;
+        npcm7xx_watchdog_timer_reset(t);
+        if (new_wtcr & NPCM7XX_WTCR_WTE) {
+            npcm7xx_timer_start(&t->base_timer);
+        }
+    } else if ((old_wtcr ^ new_wtcr) & NPCM7XX_WTCR_WTE) {
+        if (new_wtcr & NPCM7XX_WTCR_WTE) {
+            npcm7xx_timer_start(&t->base_timer);
+        } else {
+            npcm7xx_timer_pause(&t->base_timer);
+        }
+    }
+
+}
+
 static hwaddr npcm7xx_tcsr_index(hwaddr reg)
 {
     switch (reg) {
@@ -353,7 +463,7 @@  static uint64_t npcm7xx_timer_read(void *opaque, hwaddr offset, unsigned size)
         break;
 
     case NPCM7XX_TIMER_WTCR:
-        value = s->wtcr;
+        value = s->watchdog_timer.wtcr;
         break;
 
     default:
@@ -409,8 +519,7 @@  static void npcm7xx_timer_write(void *opaque, hwaddr offset,
         return;
 
     case NPCM7XX_TIMER_WTCR:
-        qemu_log_mask(LOG_UNIMP, "%s: WTCR write not implemented: 0x%08x\n",
-                      __func__, value);
+        npcm7xx_timer_write_wtcr(&s->watchdog_timer, value);
         return;
     }
 
@@ -448,15 +557,42 @@  static void npcm7xx_timer_enter_reset(Object *obj, ResetType type)
     for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
         NPCM7xxTimer *t = &s->timer[i];
 
-        timer_del(&t->qtimer);
-        t->expires_ns = 0;
-        t->remaining_ns = 0;
+        npcm7xx_timer_clear(&t->base_timer);
         t->tcsr = 0x00000005;
         t->ticr = 0x00000000;
     }
 
     s->tisr = 0x00000000;
-    s->wtcr = 0x00000400;
+    /*
+     * Set WTCLK to 1(default) and reset all flags except WTRF.
+     * WTRF is not reset during a core domain reset.
+     */
+    s->watchdog_timer.wtcr = 0x00000400 | (s->watchdog_timer.wtcr &
+            NPCM7XX_WTCR_WTRF);
+}
+
+static void npcm7xx_watchdog_timer_expired(void *opaque)
+{
+    NPCM7xxWatchdogTimer *t = opaque;
+
+    if (t->wtcr & NPCM7XX_WTCR_WTE) {
+        if (t->wtcr & NPCM7XX_WTCR_WTIF) {
+            if (t->wtcr & NPCM7XX_WTCR_WTRE) {
+                t->wtcr |= NPCM7XX_WTCR_WTRF;
+                /* send reset signal to CLK module*/
+                qemu_irq_raise(t->reset_signal);
+            }
+        } else {
+            t->wtcr |= NPCM7XX_WTCR_WTIF;
+            if (t->wtcr & NPCM7XX_WTCR_WTIE) {
+                /* send interrupt */
+                qemu_irq_raise(t->irq);
+            }
+            npcm7xx_watchdog_timer_reset_cycles(t,
+                    NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES);
+            npcm7xx_timer_start(&t->base_timer);
+        }
+    }
 }
 
 static void npcm7xx_timer_hold_reset(Object *obj)
@@ -467,6 +603,7 @@  static void npcm7xx_timer_hold_reset(Object *obj)
     for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
         qemu_irq_lower(s->timer[i].irq);
     }
+    qemu_irq_lower(s->watchdog_timer.irq);
 }
 
 static void npcm7xx_timer_realize(DeviceState *dev, Error **errp)
@@ -474,43 +611,80 @@  static void npcm7xx_timer_realize(DeviceState *dev, Error **errp)
     NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(dev);
     SysBusDevice *sbd = &s->parent;
     int i;
+    NPCM7xxWatchdogTimer *w;
 
     for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
         NPCM7xxTimer *t = &s->timer[i];
         t->ctrl = s;
-        timer_init_ns(&t->qtimer, QEMU_CLOCK_VIRTUAL, npcm7xx_timer_expired, t);
+        timer_init_ns(&t->base_timer.qtimer, QEMU_CLOCK_VIRTUAL,
+                npcm7xx_timer_expired, t);
         sysbus_init_irq(sbd, &t->irq);
     }
 
+    w = &s->watchdog_timer;
+    w->ctrl = s;
+    timer_init_ns(&w->base_timer.qtimer, QEMU_CLOCK_VIRTUAL,
+            npcm7xx_watchdog_timer_expired, w);
+    sysbus_init_irq(sbd, &w->irq);
+
     memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_timer_ops, s,
                           TYPE_NPCM7XX_TIMER, 4 * KiB);
     sysbus_init_mmio(sbd, &s->iomem);
+    qdev_init_gpio_out_named(dev, &w->reset_signal,
+            NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 1);
 }
 
-static const VMStateDescription vmstate_npcm7xx_timer = {
-    .name = "npcm7xx-timer",
+static const VMStateDescription vmstate_npcm7xx_base_timer = {
+    .name = "npcm7xx-base-timer",
     .version_id = 0,
     .minimum_version_id = 0,
     .fields = (VMStateField[]) {
-        VMSTATE_TIMER(qtimer, NPCM7xxTimer),
-        VMSTATE_INT64(expires_ns, NPCM7xxTimer),
-        VMSTATE_INT64(remaining_ns, NPCM7xxTimer),
+        VMSTATE_TIMER(qtimer, NPCM7xxBaseTimer),
+        VMSTATE_INT64(expires_ns, NPCM7xxBaseTimer),
+        VMSTATE_INT64(remaining_ns, NPCM7xxBaseTimer),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_npcm7xx_timer = {
+    .name = "npcm7xx-timer",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_STRUCT(base_timer, NPCM7xxTimer,
+                             0, vmstate_npcm7xx_base_timer,
+                             NPCM7xxBaseTimer),
         VMSTATE_UINT32(tcsr, NPCM7xxTimer),
         VMSTATE_UINT32(ticr, NPCM7xxTimer),
         VMSTATE_END_OF_LIST(),
     },
 };
 
-static const VMStateDescription vmstate_npcm7xx_timer_ctrl = {
-    .name = "npcm7xx-timer-ctrl",
+static const VMStateDescription vmstate_npcm7xx_watchdog_timer = {
+    .name = "npcm7xx-watchdog-timer",
     .version_id = 0,
     .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_STRUCT(base_timer, NPCM7xxWatchdogTimer,
+                             0, vmstate_npcm7xx_base_timer,
+                             NPCM7xxBaseTimer),
+        VMSTATE_UINT32(wtcr, NPCM7xxWatchdogTimer),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_npcm7xx_timer_ctrl = {
+    .name = "npcm7xx-timer-ctrl",
+    .version_id = 1,
+    .minimum_version_id = 1,
     .fields = (VMStateField[]) {
         VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState),
-        VMSTATE_UINT32(wtcr, NPCM7xxTimerCtrlState),
         VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState,
                              NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_timer,
                              NPCM7xxTimer),
+        VMSTATE_STRUCT(watchdog_timer, NPCM7xxTimerCtrlState,
+                             0, vmstate_npcm7xx_watchdog_timer,
+                             NPCM7xxWatchdogTimer),
         VMSTATE_END_OF_LIST(),
     },
 };
diff --git a/tests/qtest/npcm7xx_watchdog_timer-test.c b/tests/qtest/npcm7xx_watchdog_timer-test.c
new file mode 100644
index 0000000000..226a213527
--- /dev/null
+++ b/tests/qtest/npcm7xx_watchdog_timer-test.c
@@ -0,0 +1,320 @@ 
+/*
+ * QTests for Nuvoton NPCM7xx Timer Watchdog Modules.
+ *
+ * Copyright 2020 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+
+#include "libqos/libqtest.h"
+#include "qapi/qmp/qdict.h"
+
+#define WTCR_OFFSET     0x1c
+#define REF_HZ          (25000000)
+
+/* WTCR bit fields */
+#define WTCLK(rv)       ((rv) << 10)
+#define WTE             BIT(7)
+#define WTIE            BIT(6)
+#define WTIS(rv)        ((rv) << 4)
+#define WTIF            BIT(3)
+#define WTRF            BIT(2)
+#define WTRE            BIT(1)
+#define WTR             BIT(0)
+
+typedef struct Watchdog {
+    int irq;
+    uint64_t base_addr;
+} Watchdog;
+
+static const Watchdog watchdog_list[] = {
+    {
+        .irq        = 47,
+        .base_addr  = 0xf0008000
+    },
+    {
+        .irq        = 48,
+        .base_addr  = 0xf0009000
+    },
+    {
+        .irq        = 49,
+        .base_addr  = 0xf000a000
+    }
+};
+
+static int watchdog_index(const Watchdog *wd)
+{
+    ptrdiff_t diff = wd - watchdog_list;
+
+    g_assert(diff >= 0 && diff < ARRAY_SIZE(watchdog_list));
+
+    return diff;
+}
+
+static uint32_t watchdog_read_wtcr(QTestState *qts, const Watchdog *wd)
+{
+    return qtest_readl(qts, wd->base_addr + WTCR_OFFSET);
+}
+
+static void watchdog_write_wtcr(QTestState *qts, const Watchdog *wd,
+        uint32_t value)
+{
+    qtest_writel(qts, wd->base_addr + WTCR_OFFSET, value);
+}
+
+static uint32_t watchdog_prescaler(QTestState *qts, const Watchdog *wd)
+{
+    switch (extract32(watchdog_read_wtcr(qts, wd), 10, 2)) {
+    case 0:
+        return 1;
+    case 1:
+        return 256;
+    case 2:
+        return 2048;
+    case 3:
+        return 65536;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static QDict *get_watchdog_action(QTestState *qts)
+{
+    QDict *ev = qtest_qmp_eventwait_ref(qts, "WATCHDOG");
+    QDict *data;
+
+    data = qdict_get_qdict(ev, "data");
+    qobject_ref(data);
+    qobject_unref(ev);
+    return data;
+}
+
+#define RESET_CYCLES 1024
+static uint32_t watchdog_interrupt_cycles(QTestState *qts, const Watchdog *wd)
+{
+    uint32_t wtis = extract32(watchdog_read_wtcr(qts, wd), 4, 2);
+    return 1 << (14 + 2 * wtis);
+}
+
+static int64_t watchdog_calculate_steps(uint32_t count, uint32_t prescale)
+{
+    return (NANOSECONDS_PER_SECOND / REF_HZ) * count * prescale;
+}
+
+static int64_t watchdog_interrupt_steps(QTestState *qts, const Watchdog *wd)
+{
+    return watchdog_calculate_steps(watchdog_interrupt_cycles(qts, wd),
+            watchdog_prescaler(qts, wd));
+}
+
+/* Check wtcr can be reset to default value */
+static void test_init(gconstpointer watchdog)
+{
+    const Watchdog *wd = watchdog;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+
+    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+
+    watchdog_write_wtcr(qts, wd, WTCLK(1) | WTRF | WTIF | WTR);
+    g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(1));
+
+    qtest_quit(qts);
+}
+
+/* Check a watchdog can generate interrupt and reset actions */
+static void test_reset_action(gconstpointer watchdog)
+{
+    const Watchdog *wd = watchdog;
+    QTestState *qts = qtest_init("-machine quanta-gsj");
+    QDict *ad;
+
+    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+
+    watchdog_write_wtcr(qts, wd,
+            WTCLK(0) | WTE | WTRF | WTRE | WTIF | WTIE | WTR);
+    g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==,
+            WTCLK(0) | WTE | WTRE | WTIE);
+
+    /* Check a watchdog can generate an interrupt */
+    qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
+    g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==,
+            WTCLK(0) | WTE | WTIF | WTIE | WTRE);
+    g_assert_true(qtest_get_irq(qts, wd->irq));
+
+    /* Check a watchdog can generate a reset signal */
+    qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
+                watchdog_prescaler(qts, wd)));
+    ad = get_watchdog_action(qts);
+    /* The signal is a reset signal */
+    g_assert_false(strcmp(qdict_get_str(ad, "action"), "reset"));
+    qobject_unref(ad);
+    qtest_qmp_eventwait(qts, "RESET");
+    /*
+     * Make sure WTCR is reset to default except for WTRF bit which shouldn't
+     * be reset.
+     */
+    g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(1) | WTRF);
+    qtest_quit(qts);
+}
+
+/* Check a watchdog works with all possible WTCLK prescalers and WTIS cycles */
+static void test_prescaler(gconstpointer watchdog)
+{
+    const Watchdog *wd = watchdog;
+
+    for (int wtclk = 0; wtclk < 4; ++wtclk) {
+        for (int wtis = 0; wtis < 4; ++wtis) {
+            QTestState *qts = qtest_init("-machine quanta-gsj");
+
+            qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+            watchdog_write_wtcr(qts, wd,
+                    WTCLK(wtclk) | WTE | WTIF | WTIS(wtis) | WTIE | WTR);
+            /*
+             * The interrupt doesn't fire until watchdog_interrupt_steps()
+             * cycles passed
+             */
+            qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd) - 1);
+            g_assert_false(watchdog_read_wtcr(qts, wd) & WTIF);
+            g_assert_false(qtest_get_irq(qts, wd->irq));
+            qtest_clock_step(qts, 1);
+            g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+            g_assert_true(qtest_get_irq(qts, wd->irq));
+
+            qtest_quit(qts);
+        }
+    }
+}
+
+/*
+ * Check a watchdog doesn't fire if corresponding flags (WTIE and WTRE) are not
+ * set.
+ */
+static void test_enabling_flags(gconstpointer watchdog)
+{
+    const Watchdog *wd = watchdog;
+    QTestState *qts;
+
+    /* Neither WTIE or WTRE is set, no interrupt or reset should happen */
+    qts = qtest_init("-machine quanta-gsj");
+    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+    watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTRF | WTR);
+    qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
+    g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+    g_assert_false(qtest_get_irq(qts, wd->irq));
+    qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
+                watchdog_prescaler(qts, wd)));
+    g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+    g_assert_false(watchdog_read_wtcr(qts, wd) & WTRF);
+    qtest_quit(qts);
+
+    /* Only WTIE is set, interrupt is triggered but reset should not happen */
+    qts = qtest_init("-machine quanta-gsj");
+    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+    watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTIE | WTRF | WTR);
+    qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
+    g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+    g_assert_true(qtest_get_irq(qts, wd->irq));
+    qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
+                watchdog_prescaler(qts, wd)));
+    g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+    g_assert_false(watchdog_read_wtcr(qts, wd) & WTRF);
+    qtest_quit(qts);
+
+    /* Only WTRE is set, interrupt is triggered but reset should not happen */
+    qts = qtest_init("-machine quanta-gsj");
+    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+    watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTRE | WTRF | WTR);
+    qtest_clock_step(qts, watchdog_interrupt_steps(qts, wd));
+    g_assert_true(watchdog_read_wtcr(qts, wd) & WTIF);
+    g_assert_false(qtest_get_irq(qts, wd->irq));
+    qtest_clock_step(qts, watchdog_calculate_steps(RESET_CYCLES,
+                watchdog_prescaler(qts, wd)));
+    g_assert_false(strcmp(qdict_get_str(get_watchdog_action(qts), "action"),
+                "reset"));
+    qtest_qmp_eventwait(qts, "RESET");
+    qtest_quit(qts);
+
+    /*
+     * The case when both flags are set is already tested in
+     * test_reset_action().
+     */
+}
+
+/* Check a watchdog can pause and resume by setting WTE bits */
+static void test_pause(gconstpointer watchdog)
+{
+    const Watchdog *wd = watchdog;
+    QTestState *qts;
+    int64_t remaining_steps, steps;
+
+    qts = qtest_init("-machine quanta-gsj");
+    qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic");
+    watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIF | WTIE | WTRF | WTR);
+    remaining_steps = watchdog_interrupt_steps(qts, wd);
+    g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTE | WTIE);
+
+    /* Run for half of the execution period. */
+    steps = remaining_steps / 2;
+    remaining_steps -= steps;
+    qtest_clock_step(qts, steps);
+
+    /* Pause the watchdog */
+    watchdog_write_wtcr(qts, wd, WTCLK(0) | WTIE);
+    g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTIE);
+
+    /* Run for a long period of time, the watchdog shouldn't fire */
+    qtest_clock_step(qts, steps << 4);
+    g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTIE);
+    g_assert_false(qtest_get_irq(qts, wd->irq));
+
+    /* Resume the watchdog */
+    watchdog_write_wtcr(qts, wd, WTCLK(0) | WTE | WTIE);
+    g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==, WTCLK(0) | WTE | WTIE);
+
+    /* Run for the reset of the execution period, the watchdog should fire */
+    qtest_clock_step(qts, remaining_steps);
+    g_assert_cmphex(watchdog_read_wtcr(qts, wd), ==,
+            WTCLK(0) | WTE | WTIF | WTIE);
+    g_assert_true(qtest_get_irq(qts, wd->irq));
+
+    qtest_quit(qts);
+}
+
+static void watchdog_add_test(const char *name, const Watchdog* wd,
+        GTestDataFunc fn)
+{
+    g_autofree char *full_name = g_strdup_printf(
+            "npcm7xx_watchdog_timer[%d]/%s", watchdog_index(wd), name);
+    qtest_add_data_func(full_name, wd, fn);
+}
+#define add_test(name, td) watchdog_add_test(#name, td, test_##name)
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+    g_test_set_nonfatal_assertions();
+
+    for (int i = 0; i < ARRAY_SIZE(watchdog_list); ++i) {
+        const Watchdog *wd = &watchdog_list[i];
+
+        add_test(init, wd);
+        add_test(reset_action, wd);
+        add_test(prescaler, wd);
+        add_test(enabling_flags, wd);
+        add_test(pause, wd);
+    }
+
+    return g_test_run();
+}
+
diff --git a/MAINTAINERS b/MAINTAINERS
index 6a197bd358..0fa74b9bf3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -755,6 +755,7 @@  L: qemu-arm@nongnu.org
 S: Supported
 F: hw/*/npcm7xx*
 F: include/hw/*/npcm7xx*
+F: tests/qtest/npcm7xx*
 F: pc-bios/npcm7xx_bootrom.bin
 F: roms/vbootrom
 
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 7e0ecaa2c5..b160c6f787 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -133,7 +133,7 @@  qtests_sparc64 = \
   (config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) +            \
   ['prom-env-test', 'boot-serial-test']
 
-qtests_npcm7xx = ['npcm7xx_timer-test']
+qtests_npcm7xx = ['npcm7xx_timer-test', 'npcm7xx_watchdog_timer-test']
 qtests_arm = \
   (config_all_devices.has_key('CONFIG_PFLASH_CFI02') ? ['pflash-cfi02-test'] : []) +         \
   (config_all_devices.has_key('CONFIG_NPCM7XX') ? qtests_npcm7xx : []) + \