From: Kyle Fox <kylefoxaustin.github@gmail.com>
To: qemu-devel@nongnu.org
Cc: Kyle Fox <kylefoxaustin.github@gmail.com>,
Paolo Bonzini <pbonzini@redhat.com>,
qemu-arm@nongnu.org (open list:MCIMX95-19X19-EVK...)
Subject: [PATCH 10/16] hw/misc: add i.MX 95 watchdog
Date: Wed, 19 Aug 2026 21:48:28 -0500 [thread overview]
Message-ID: <20260820024834.3286721-11-kylefoxaustin.github@gmail.com> (raw)
In-Reply-To: <20260820024834.3286721-1-kylefoxaustin.github@gmail.com>
The i.MX 95 watchdog. A register-level model of the CS/CNT/TOVAL/WIN
registers and the unlock sequence, enough for the SM and Linux to
configure and refresh it. It has no live timer and does not reset the
machine on timeout; it exists so the watchdog node probes and is
programmable rather than faulting on first access.
Signed-off-by: Kyle Fox <kylefoxaustin.github@gmail.com>
---
hw/misc/Kconfig | 3 +
hw/misc/imx95_wdog.c | 198 +++++++++++++++++++++++++++++++++++++++++++
hw/misc/meson.build | 1 +
hw/misc/trace-events | 2 +
4 files changed, 204 insertions(+)
create mode 100644 hw/misc/imx95_wdog.c
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index ded60e21a6c..2bae76b4ec1 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -268,3 +268,6 @@ config IMX_MU
config IMX95_ELE_SERVER
bool
select IMX_MU
+
+config IMX95_WDOG
+ bool
diff --git a/hw/misc/imx95_wdog.c b/hw/misc/imx95_wdog.c
new file mode 100644
index 00000000000..4fe91a92b6d
--- /dev/null
+++ b/hw/misc/imx95_wdog.c
@@ -0,0 +1,198 @@
+/*
+ * NXP i.MX 95 ULP Watchdog stub model
+ *
+ * Copyright (c) 2026, Kyle Fox
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Minimal stub of the ULP watchdog (compatible "fsl,imx93-wdt") used
+ * by U-Boot SPL's arch_cpu_init() to disable WDG3/4/5 before the
+ * console comes up. SPL's disable_wdog() reads CS @ 0x00 and either
+ * early-exits if the enable bit (0x80) is clear or runs an unlock +
+ * disable handshake that polls for CS.ULK (0x800) and CS.RCS (0x400).
+ *
+ * The stub leaves the watchdog disabled at reset, so the first CS
+ * read returns 0 and disable_wdog() returns immediately. The unlock
+ * sequence is implemented for the cases where the guest forces the
+ * unlock anyway: a write of 0xD928C520 to CNT @ 0x04 sets CS.ULK,
+ * any subsequent write to CS sets CS.RCS. No timer behaviour is
+ * modelled.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define TYPE_IMX95_WDOG "imx95.wdog"
+OBJECT_DECLARE_SIMPLE_TYPE(IMX95WDogState, IMX95_WDOG)
+
+#define IMX95_WDOG_REG_SIZE 0x10000
+
+/* Register offsets. */
+#define WDOG_CS 0x00
+#define WDOG_CNT 0x04
+#define WDOG_TOVAL 0x08
+#define WDOG_WIN 0x0C
+
+/* CS bit fields exercised by U-Boot. */
+#define CS_EN 0x00000080
+#define CS_RCS 0x00000400 /* reconfig complete */
+#define CS_ULK 0x00000800 /* unlocked */
+
+#define UNLOCK_WORD 0xD928C520
+
+struct IMX95WDogState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t cs;
+ uint32_t cnt;
+ uint32_t toval;
+ uint32_t win;
+};
+
+static uint64_t imx95_wdog_read(void *opaque, hwaddr offset, unsigned size)
+{
+ IMX95WDogState *s = opaque;
+
+ switch (offset) {
+ case WDOG_CS:
+ return s->cs;
+ case WDOG_CNT:
+ return s->cnt;
+ case WDOG_TOVAL:
+ return s->toval;
+ case WDOG_WIN:
+ return s->win;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+ __func__, offset);
+ return 0;
+ }
+}
+
+static void imx95_wdog_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ IMX95WDogState *s = opaque;
+
+ switch (offset) {
+ case WDOG_CS:
+ /*
+ * SPL writes CS to update timeout + window and to clear EN.
+ * Acknowledge by setting RCS (reconfig complete) so the
+ * "wait for RCS" loop at the tail of disable_wdog() exits.
+ */
+ s->cs = (value & ~CS_RCS) | CS_RCS;
+ trace_imx95_wdog_config(s->cs);
+ break;
+
+ case WDOG_CNT:
+ s->cnt = value;
+ /*
+ * A 32-bit UNLOCK_WORD write to CNT puts the watchdog into
+ * the unlocked state. Any other value (e.g. REFRESH_WORD)
+ * is just a refresh ping; ignore.
+ */
+ if ((uint32_t)value == UNLOCK_WORD) {
+ s->cs |= CS_ULK;
+ trace_imx95_wdog_unlock();
+ }
+ break;
+
+ case WDOG_TOVAL:
+ s->toval = value;
+ break;
+
+ case WDOG_WIN:
+ s->win = value;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad write offset 0x%" HWADDR_PRIx
+ " value 0x%" PRIx64 "\n",
+ __func__, offset, value);
+ break;
+ }
+}
+
+static const MemoryRegionOps imx95_wdog_ops = {
+ .read = imx95_wdog_read,
+ .write = imx95_wdog_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void imx95_wdog_reset_hold(Object *obj, ResetType type)
+{
+ IMX95WDogState *s = IMX95_WDOG(obj);
+
+ /* Watchdog disabled at reset: CS = 0 makes disable_wdog() early-exit. */
+ s->cs = 0;
+ s->cnt = 0;
+ s->toval = 0x400;
+ s->win = 0;
+}
+
+static void imx95_wdog_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ IMX95WDogState *s = IMX95_WDOG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &imx95_wdog_ops, s,
+ TYPE_IMX95_WDOG, IMX95_WDOG_REG_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription vmstate_imx95_wdog = {
+ .name = TYPE_IMX95_WDOG,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(cs, IMX95WDogState),
+ VMSTATE_UINT32(cnt, IMX95WDogState),
+ VMSTATE_UINT32(toval, IMX95WDogState),
+ VMSTATE_UINT32(win, IMX95WDogState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void imx95_wdog_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->vmsd = &vmstate_imx95_wdog;
+ rc->phases.hold = imx95_wdog_reset_hold;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->desc = "NXP i.MX 95 ULP watchdog (stub)";
+}
+
+static const TypeInfo imx95_wdog_info = {
+ .name = TYPE_IMX95_WDOG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMX95WDogState),
+ .instance_init = imx95_wdog_init,
+ .class_init = imx95_wdog_class_init,
+};
+
+static void imx95_wdog_register_types(void)
+{
+ type_register_static(&imx95_wdog_info);
+}
+
+type_init(imx95_wdog_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index b0dade72c6c..3f8cd1d825e 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -173,3 +173,4 @@ system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_AXIADO_CLK', if_true: files('axiado_clk.c'))
system_ss.add(when: 'CONFIG_IMX_MU', if_true: files('imx_mu.c'))
system_ss.add(when: 'CONFIG_IMX95_ELE_SERVER', if_true: files('imx95_ele_server.c'))
+system_ss.add(when: 'CONFIG_IMX95_WDOG', if_true: files('imx95_wdog.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 6b3cff74549..584c1f90963 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -448,3 +448,5 @@ imx_mu_doorbell(unsigned idx) "doorbell GIR channel %u"
imx_mu_gip(unsigned idx) "GIP assert channel %u"
imx95_ele_msg(uint8_t command, uint8_t tag, uint32_t size) "received cmd 0x%02x tag 0x%02x size %u"
imx95_ele_response(uint8_t command) "response cmd 0x%02x"
+imx95_wdog_config(uint32_t cs) "CS <- 0x%08x"
+imx95_wdog_unlock(void) "unlock word written"
--
2.34.1
next prev parent reply other threads:[~2026-08-20 2:51 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-20 2:48 [PATCH 00/16] hw/arm: add the NXP i.MX 95 EVK machine Kyle Fox
2026-08-20 2:48 ` [PATCH 01/16] hw/sd/sdhci: add i.MX uSDHC SDCLK_AUTO_GATE and NO_SDMA_BOUNDARY quirks Kyle Fox
2026-08-20 11:50 ` Bin Meng
2026-08-20 2:48 ` [PATCH 02/16] hw/arm/boot: let a board preset initrd_start Kyle Fox
2026-08-20 2:48 ` [PATCH 03/16] target/arm: opt-in align-down for a misaligned PMSAv7 MPU RBAR Kyle Fox
2026-08-20 2:48 ` [PATCH 04/16] hw/arm/armv7m: forward pmsav7-rbar-align-down to the CPU Kyle Fox
2026-08-20 2:48 ` [PATCH 05/16] hw/char: add i.MX LPUART Kyle Fox
2026-08-20 2:48 ` [PATCH 06/16] hw/i2c: add i.MX LPI2C Kyle Fox
2026-08-20 2:48 ` [PATCH 07/16] hw/misc: add i.MX Messaging Unit (MU v2) Kyle Fox
2026-08-20 2:48 ` [PATCH 08/16] hw/misc: add NXP EdgeLock Enclave (ELE) responder Kyle Fox
2026-08-20 2:48 ` [PATCH 09/16] hw/timer: add i.MX 95 system counter Kyle Fox
2026-08-20 2:48 ` Kyle Fox [this message]
2026-08-20 2:48 ` [PATCH 11/16] hw/misc: add i.MX 95 ANATOP/AONMIX/GPC/SRC power and clock blocks Kyle Fox
2026-08-20 2:48 ` [PATCH 12/16] hw/misc: add i.MX 95 PMIC (PF09/PF53/PCAL6408A) and xcache controllers Kyle Fox
2026-08-20 2:48 ` [PATCH 13/16] hw/misc: add i.MX 95 DPU command-sequencer stub (headless) Kyle Fox
2026-08-20 2:48 ` [PATCH 14/16] hw/arm: add i.MX 95 SoC container (fsl-imx95) Kyle Fox
2026-08-20 2:48 ` [PATCH 15/16] hw/arm: add i.MX 95 19x19 EVK board Kyle Fox
2026-08-20 2:48 ` [PATCH 16/16] docs, MAINTAINERS, tests/functional: add i.MX 95 EVK Kyle Fox
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260820024834.3286721-11-kylefoxaustin.github@gmail.com \
--to=kylefoxaustin.github@gmail.com \
--cc=pbonzini@redhat.com \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.