All of lore.kernel.org
 help / color / mirror / Atom feed
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 11/16] hw/misc: add i.MX 95 ANATOP/AONMIX/GPC/SRC power and clock blocks
Date: Wed, 19 Aug 2026 21:48:29 -0500	[thread overview]
Message-ID: <20260820024834.3286721-12-kylefoxaustin.github@gmail.com> (raw)
In-Reply-To: <20260820024834.3286721-1-kylefoxaustin.github@gmail.com>

The power and clock bring-up blocks that the System Manager firmware
programs before Linux starts: ANATOP (PLL lock / DFS status), SRC
(system reset controller, including the M7 mix-slice release), GPC
(general power controller mode requests) and the AONMIX block-control M7
CPU-WAIT gate (the SM's M7 hold/run control). These are register-level
models that return the lock, ack and status bits the firmware polls.

Signed-off-by: Kyle Fox <kylefoxaustin.github@gmail.com>
---
 hw/misc/Kconfig        |  12 +++
 hw/misc/imx95_anatop.c | 201 ++++++++++++++++++++++++++++++++++++
 hw/misc/imx95_aonmix.c | 160 +++++++++++++++++++++++++++++
 hw/misc/imx95_gpc.c    | 160 +++++++++++++++++++++++++++++
 hw/misc/imx95_src.c    | 228 +++++++++++++++++++++++++++++++++++++++++
 hw/misc/meson.build    |   4 +
 hw/misc/trace-events   |   8 ++
 7 files changed, 773 insertions(+)
 create mode 100644 hw/misc/imx95_anatop.c
 create mode 100644 hw/misc/imx95_aonmix.c
 create mode 100644 hw/misc/imx95_gpc.c
 create mode 100644 hw/misc/imx95_src.c

diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 2bae76b4ec1..1e1cae92f6f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -271,3 +271,15 @@ config IMX95_ELE_SERVER
 
 config IMX95_WDOG
     bool
+
+config IMX95_ANATOP
+    bool
+
+config IMX95_AONMIX
+    bool
+
+config IMX95_GPC
+    bool
+
+config IMX95_SRC
+    bool
diff --git a/hw/misc/imx95_anatop.c b/hw/misc/imx95_anatop.c
new file mode 100644
index 00000000000..b8b18d405d0
--- /dev/null
+++ b/hw/misc/imx95_anatop.c
@@ -0,0 +1,201 @@
+/*
+ * NXP i.MX 95 ANATOP / PLL stub model
+ *
+ * Copyright (c) 2026, Kyle Fox
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Covers the ANATOP block at 0x44480000. Its PLL instances live at
+ * 0x44481000..0x444819ff, one every 0x100 (SYS_PLL1, AUDIO_PLL1/2,
+ * VIDEO_PLL1, ARM_PLL, DRAM_PLL, HSIO_PLL, LDB_PLL). Each PLL register
+ * uses the i.MX RW/SET/CLR/TOG quad layout (a 16-byte group: write +0 =
+ * assign, +4 = set bits, +8 = clear bits, +C = toggle; reads return the
+ * accumulated value), and each PLL has two read-only status words:
+ * PLL_STATUS (+0xf0, bit0 = PLL_LOCK) and DFS_STATUS (+0xf4, bits[3:0] =
+ * per-DFS clock-ok).
+ *
+ * The System Manager's DVFS path (DEV_SM_PerfA55FreqUpdate via
+ * FRACTPLL_UpdateRate / FRACTPLL_UpdateDfsRate) powers a PLL up by setting
+ * CTRL.POWERUP and then polls PLL_STATUS.PLL_LOCK; it enables a DFS and
+ * polls DFS_STATUS.DFS_OK (an unbounded wait). QEMU has no analog PLLs, so
+ * the transition is instantaneous: PLL_LOCK mirrors CTRL.POWERUP and a
+ * DFS's DFS_OK bit mirrors its DFS_CTRL.ENABLE. Everything else is plain
+ * storage. Same status-mirrors-control idea as the GPC/SRC models.
+ *
+ * Non-PLL parts of ANATOP (e.g. TMPSNS at +0x2000) are plain RAM here -
+ * the quad/status behaviour is confined to the PLL window.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/core/sysbus.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define TYPE_IMX95_ANATOP "imx95.anatop"
+OBJECT_DECLARE_SIMPLE_TYPE(IMX95AnatopState, IMX95_ANATOP)
+
+#define IMX95_ANATOP_REG_SIZE   0x10000
+#define IMX95_ANATOP_NUM_WORDS  (IMX95_ANATOP_REG_SIZE / 4)
+
+/* PLL window within ANATOP: [0x1000, 0x1a00), one PLL every 0x100. */
+#define ANATOP_PLL_START        0x1000
+#define ANATOP_PLL_END          0x1a00
+#define PLL_BLOCK_SIZE          0x100
+
+/* Per-PLL register offsets. */
+#define PLL_CTRL_OFF            0x00    /* RW/SET/CLR/TOG, POWERUP in bit 0 */
+#define PLL_DFS0_OFF            0x70    /* DFS[0] DFS_CTRL; step 0x20 */
+#define PLL_DFS_STEP            0x20
+#define PLL_NUM_DFS             4
+#define PLL_STATUS_OFF          0xf0    /* RO: PLL_LOCK in bit 0 */
+#define PLL_DFS_STATUS_OFF      0xf4    /* RO: DFS_OK in bits [3:0] */
+
+#define PLL_CTRL_POWERUP        0x00000001u
+#define PLL_STATUS_LOCK         0x00000001u
+#define PLL_DFS_CTRL_ENABLE     0x80000000u
+
+struct IMX95AnatopState {
+    SysBusDevice    parent_obj;
+    MemoryRegion    iomem;
+    uint32_t        regs[IMX95_ANATOP_NUM_WORDS];
+};
+
+static bool anatop_in_pll_window(hwaddr offset)
+{
+    return offset >= ANATOP_PLL_START && offset < ANATOP_PLL_END;
+}
+
+static uint64_t imx95_anatop_read(void *opaque, hwaddr offset, unsigned size)
+{
+    IMX95AnatopState *s = opaque;
+
+    trace_imx95_anatop_read(offset);
+
+    if (anatop_in_pll_window(offset)) {
+        hwaddr blk = offset & ~(hwaddr)(PLL_BLOCK_SIZE - 1);
+        uint32_t reg = offset & (PLL_BLOCK_SIZE - 1);
+
+        if (reg == PLL_STATUS_OFF) {
+            /* PLL_LOCK mirrors CTRL.POWERUP (lock is instantaneous here). */
+            uint32_t ctrl = s->regs[(blk + PLL_CTRL_OFF) / 4];
+            return (ctrl & PLL_CTRL_POWERUP) ? PLL_STATUS_LOCK : 0;
+        }
+        if (reg == PLL_DFS_STATUS_OFF) {
+            /* Each DFS_OK bit mirrors that DFS's DFS_CTRL.ENABLE. */
+            uint32_t ok = 0;
+            for (unsigned n = 0; n < PLL_NUM_DFS; n++) {
+                hwaddr dfs = blk + PLL_DFS0_OFF + n * PLL_DFS_STEP;
+                if (s->regs[dfs / 4] & PLL_DFS_CTRL_ENABLE) {
+                    ok |= (1u << n);
+                }
+            }
+            return ok;
+        }
+    }
+    return s->regs[offset / 4];
+}
+
+static void imx95_anatop_write(void *opaque, hwaddr offset,
+                               uint64_t value, unsigned size)
+{
+    IMX95AnatopState *s = opaque;
+
+    trace_imx95_anatop_write(offset, value);
+
+    if (anatop_in_pll_window(offset)) {
+        uint32_t reg = offset & (PLL_BLOCK_SIZE - 1);
+
+        /* PLL_STATUS / DFS_STATUS are read-only. */
+        if (reg == PLL_STATUS_OFF || reg == PLL_DFS_STATUS_OFF) {
+            return;
+        }
+
+        /* RW/SET/CLR/TOG aliases of one logical register (16-byte group). */
+        hwaddr base = offset & ~(hwaddr)0xc;
+        switch (offset & 0xc) {
+        case 0x0:
+            s->regs[offset / 4] = value;
+            break;
+        case 0x4:
+            s->regs[base / 4] |= value;
+            break;
+        case 0x8:
+            s->regs[base / 4] &= ~(uint32_t)value;
+            break;
+        case 0xc:
+            s->regs[base / 4] ^= value;
+            break;
+        }
+        return;
+    }
+    s->regs[offset / 4] = value;
+}
+
+static const MemoryRegionOps imx95_anatop_ops = {
+    .read = imx95_anatop_read,
+    .write = imx95_anatop_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_anatop_reset_hold(Object *obj, ResetType type)
+{
+    IMX95AnatopState *s = IMX95_ANATOP(obj);
+
+    memset(s->regs, 0, sizeof(s->regs));
+}
+
+static void imx95_anatop_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    IMX95AnatopState *s = IMX95_ANATOP(obj);
+
+    memory_region_init_io(&s->iomem, obj, &imx95_anatop_ops, s,
+                          TYPE_IMX95_ANATOP, IMX95_ANATOP_REG_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription vmstate_imx95_anatop = {
+    .name = TYPE_IMX95_ANATOP,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, IMX95AnatopState, IMX95_ANATOP_NUM_WORDS),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void imx95_anatop_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &vmstate_imx95_anatop;
+    rc->phases.hold = imx95_anatop_reset_hold;
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    dc->desc = "NXP i.MX 95 ANATOP/PLL (stub)";
+}
+
+static const TypeInfo imx95_anatop_info = {
+    .name           = TYPE_IMX95_ANATOP,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(IMX95AnatopState),
+    .instance_init  = imx95_anatop_init,
+    .class_init     = imx95_anatop_class_init,
+};
+
+static void imx95_anatop_register_types(void)
+{
+    type_register_static(&imx95_anatop_info);
+}
+
+type_init(imx95_anatop_register_types)
diff --git a/hw/misc/imx95_aonmix.c b/hw/misc/imx95_aonmix.c
new file mode 100644
index 00000000000..d794440170e
--- /dev/null
+++ b/hw/misc/imx95_aonmix.c
@@ -0,0 +1,160 @@
+/*
+ * NXP i.MX 95 BLK_CTRL_S_AONMIX - minimal model for the M7 CPU-WAIT gate
+ *
+ * Copyright (c) 2026, Kyle Fox
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Covers BLK_CTRL_S_AONMIX at 0x444f0000. The block is mostly plain
+ * configuration storage, modelled here as a RAM-backed register file (an
+ * improvement over the previous logging stub, which read back as zero).
+ *
+ * The one load-bearing register is M7_CFG (offset 0x124). Its WAIT bit
+ * (bit 4) is the Cortex-M7 hold/run gate the System Manager uses to manage
+ * the M7's lifecycle: CPU_RunModeGet reads it (WAIT set => HOLD, clear =>
+ * START) and CPU_WaitSet sets/clears it. At reset WAIT is set, so the SM
+ * sees the M7 held and runs its full DEV_SM_CpuStart sequence - releasing
+ * CPUWAIT (which we surface as the m7-run gpio) and, crucially, enabling
+ * the CM7_SYSRESETREQ fault IRQ so the SM can later cold-reset the M7 LM.
+ * A WAIT 1->0 transition releases the M7; 0->1 holds it.
+ *
+ * INITVTOR (0x108) - the M7 boot vector the SM programs - is stored but
+ * unused: our M7 boots from its ITCM reset vector (init-svtor = 0).
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define TYPE_IMX95_AONMIX "imx95.aonmix"
+OBJECT_DECLARE_SIMPLE_TYPE(IMX95AonmixState, IMX95_AONMIX)
+
+#define IMX95_AONMIX_REG_SIZE   0x10000
+#define IMX95_AONMIX_NUM_WORDS  (IMX95_AONMIX_REG_SIZE / 4)
+
+#define AONMIX_M7_CFG           0x124       /* M7 configure register */
+#define AONMIX_M7_CFG_WAIT      0x10        /* bit 4: M7 CPU-WAIT (hold) */
+
+struct IMX95AonmixState {
+    SysBusDevice    parent_obj;
+    MemoryRegion    iomem;
+    uint32_t        regs[IMX95_AONMIX_NUM_WORDS];
+
+    /*
+     * M7 run gate, driven by M7_CFG.WAIT (level: 1 = released/run,
+     * 0 = held). The machine wires this to a handler that resets+resumes
+     * the M7 on release and halts it on hold.
+     */
+    qemu_irq        m7_run;
+};
+
+static uint64_t imx95_aonmix_read(void *opaque, hwaddr offset, unsigned size)
+{
+    IMX95AonmixState *s = opaque;
+
+    trace_imx95_aonmix_read(offset);
+    return s->regs[offset / 4];
+}
+
+static void imx95_aonmix_write(void *opaque, hwaddr offset,
+                               uint64_t value, unsigned size)
+{
+    IMX95AonmixState *s = opaque;
+
+    trace_imx95_aonmix_write(offset, value);
+
+    /*
+     * M7_CFG.WAIT toggling is the SM holding (set) or releasing (clear) the
+     * M7. Surface the released state on the m7-run line so the machine
+     * cycles the core. Only fires on an actual WAIT transition.
+     */
+    if (offset == AONMIX_M7_CFG) {
+        uint32_t old = s->regs[offset / 4];
+        uint32_t new = (uint32_t)value;
+
+        s->regs[offset / 4] = new;
+        if ((old ^ new) & AONMIX_M7_CFG_WAIT) {
+            int run = (new & AONMIX_M7_CFG_WAIT) ? 0 : 1;
+            trace_imx95_aonmix_m7_gate(run);
+            qemu_set_irq(s->m7_run, run);
+        }
+        return;
+    }
+
+    s->regs[offset / 4] = value;
+}
+
+static const MemoryRegionOps imx95_aonmix_ops = {
+    .read = imx95_aonmix_read,
+    .write = imx95_aonmix_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_aonmix_reset_hold(Object *obj, ResetType type)
+{
+    IMX95AonmixState *s = IMX95_AONMIX(obj);
+
+    memset(s->regs, 0, sizeof(s->regs));
+    /* M7 held at reset: the SM sees HOLD and runs its full CpuStart. */
+    s->regs[AONMIX_M7_CFG / 4] = AONMIX_M7_CFG_WAIT;
+}
+
+static void imx95_aonmix_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    IMX95AonmixState *s = IMX95_AONMIX(obj);
+
+    memory_region_init_io(&s->iomem, obj, &imx95_aonmix_ops, s,
+                          TYPE_IMX95_AONMIX, IMX95_AONMIX_REG_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+
+    qdev_init_gpio_out_named(DEVICE(obj), &s->m7_run, "m7-run", 1);
+}
+
+static const VMStateDescription vmstate_imx95_aonmix = {
+    .name = TYPE_IMX95_AONMIX,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, IMX95AonmixState, IMX95_AONMIX_NUM_WORDS),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void imx95_aonmix_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &vmstate_imx95_aonmix;
+    rc->phases.hold = imx95_aonmix_reset_hold;
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    dc->desc = "NXP i.MX 95 BLK_CTRL_S_AONMIX (M7 CPU-WAIT gate)";
+}
+
+static const TypeInfo imx95_aonmix_info = {
+    .name           = TYPE_IMX95_AONMIX,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(IMX95AonmixState),
+    .instance_init  = imx95_aonmix_init,
+    .class_init     = imx95_aonmix_class_init,
+};
+
+static void imx95_aonmix_register_types(void)
+{
+    type_register_static(&imx95_aonmix_info);
+}
+
+type_init(imx95_aonmix_register_types)
diff --git a/hw/misc/imx95_gpc.c b/hw/misc/imx95_gpc.c
new file mode 100644
index 00000000000..d18b6da13c0
--- /dev/null
+++ b/hw/misc/imx95_gpc.c
@@ -0,0 +1,160 @@
+/*
+ * NXP i.MX 95 GPC (General Power Controller) stub model
+ *
+ * Copyright (c) 2026, Kyle Fox
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Covers the GPC block at 0x44470000: the per-domain GPC_CPU_CTRL
+ * instances (CM33 @+0x0000, CM7 @+0x0800, CA55_0..5 @+0x1000..+0x3800,
+ * CA55_CLUSTER @+0x4000) and GPC_GLOBAL @+0x4800.
+ *
+ * The System Manager drives CPU power modes by writing CMC_MODE_CTRL
+ * (CPU_MODE_TARGET, offset 0x10 in a CPU_CTRL block) and polling
+ * CMC_MODE_STAT (CPU_MODE_CURRENT, offset 0x14) until the mode settles.
+ * It also uses the CMC_SLEEP_*_CTRL / *_STAT handshake pairs (a *_STAT
+ * at *_CTRL + 4). QEMU has no power hardware, so transitions are
+ * instantaneous: every status register reflects the value last written
+ * to its paired control register. Everything else is plain storage.
+ *
+ * This is the register-class triage used for XCACHE applied to a
+ * control/status block: control bits persist (RAM), status registers
+ * mirror their control so "request X, wait for X" loops converge.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/core/sysbus.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define TYPE_IMX95_GPC "imx95.gpc"
+OBJECT_DECLARE_SIMPLE_TYPE(IMX95GPCState, IMX95_GPC)
+
+#define IMX95_GPC_REG_SIZE      0x10000
+#define IMX95_GPC_NUM_WORDS     (IMX95_GPC_REG_SIZE / 4)
+
+/* GPC_CPU_CTRL per-domain block layout (the GLOBAL block lives at +0x4800). */
+#define GPC_GLOBAL_OFF          0x4800
+#define CPU_CTRL_BLOCK_SIZE     0x800
+#define CMC_MODE_CTRL           0x10    /* CPU_MODE_TARGET in [1:0] */
+#define CMC_MODE_STAT           0x14    /* CPU_MODE_CURRENT in [1:0] */
+
+struct IMX95GPCState {
+    SysBusDevice    parent_obj;
+    MemoryRegion    iomem;
+    uint32_t        regs[IMX95_GPC_NUM_WORDS];
+};
+
+/*
+ * Is `reg` (offset within a CPU_CTRL block) a status register that mirrors
+ * the control register 4 bytes below it? Covers CMC_MODE_STAT (0x14) and
+ * the CMC_SLEEP_*_STAT handshake registers (0x204, 0x20c, ... 0x24c), all
+ * of which sit at their paired *_CTRL + 4.
+ */
+static bool gpc_is_mirror_stat(uint32_t reg)
+{
+    if (reg == CMC_MODE_STAT) {
+        return true;
+    }
+    /* SLEEP/WAKEUP handshake STATs: odd-word in the 0x200..0x2ff range. */
+    if (reg >= 0x204 && reg <= 0x2fc && ((reg & 0x4) != 0)) {
+        return true;
+    }
+    return false;
+}
+
+static uint64_t imx95_gpc_read(void *opaque, hwaddr offset, unsigned size)
+{
+    IMX95GPCState *s = opaque;
+
+    if (offset < GPC_GLOBAL_OFF) {
+        uint32_t reg = offset & (CPU_CTRL_BLOCK_SIZE - 1);
+        if (gpc_is_mirror_stat(reg)) {
+            /* Status mirrors the paired control (transition is instant). */
+            return s->regs[(offset - 4) / 4];
+        }
+    }
+    return s->regs[offset / 4];
+}
+
+static void imx95_gpc_write(void *opaque, hwaddr offset,
+                            uint64_t value, unsigned size)
+{
+    IMX95GPCState *s = opaque;
+
+    if (offset < GPC_GLOBAL_OFF &&
+        (offset & (CPU_CTRL_BLOCK_SIZE - 1)) == CMC_MODE_CTRL) {
+        trace_imx95_gpc_mode(offset, (uint32_t)value & 0x3);
+    }
+
+    s->regs[offset / 4] = value;
+}
+
+static const MemoryRegionOps imx95_gpc_ops = {
+    .read = imx95_gpc_read,
+    .write = imx95_gpc_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_gpc_reset_hold(Object *obj, ResetType type)
+{
+    IMX95GPCState *s = IMX95_GPC(obj);
+
+    memset(s->regs, 0, sizeof(s->regs));
+}
+
+static void imx95_gpc_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    IMX95GPCState *s = IMX95_GPC(obj);
+
+    memory_region_init_io(&s->iomem, obj, &imx95_gpc_ops, s,
+                          TYPE_IMX95_GPC, IMX95_GPC_REG_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static const VMStateDescription vmstate_imx95_gpc = {
+    .name = TYPE_IMX95_GPC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, IMX95GPCState, IMX95_GPC_NUM_WORDS),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void imx95_gpc_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &vmstate_imx95_gpc;
+    rc->phases.hold = imx95_gpc_reset_hold;
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    dc->desc = "NXP i.MX 95 GPC (stub)";
+}
+
+static const TypeInfo imx95_gpc_info = {
+    .name           = TYPE_IMX95_GPC,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(IMX95GPCState),
+    .instance_init  = imx95_gpc_init,
+    .class_init     = imx95_gpc_class_init,
+};
+
+static void imx95_gpc_register_types(void)
+{
+    type_register_static(&imx95_gpc_info);
+}
+
+type_init(imx95_gpc_register_types)
diff --git a/hw/misc/imx95_src.c b/hw/misc/imx95_src.c
new file mode 100644
index 00000000000..8aa1d7d56b4
--- /dev/null
+++ b/hw/misc/imx95_src.c
@@ -0,0 +1,228 @@
+/*
+ * NXP i.MX 95 SRC (System Reset Controller) mix-slice stub model
+ *
+ * Copyright (c) 2026, Kyle Fox
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Covers the SRC block at 0x44460000: SRC_GEN at offset 0, then the
+ * per-power-domain "mix slice" register blocks (SRC_XSPR) starting at
+ * offset 0x400, one every 0x400 (ANAMIX, AONMIX, ..., CCMSRCGPCMIX, ...).
+ *
+ * The System Manager powers a mix down by setting SLICE_SW_CTRL.PDN_SOFT
+ * (bit 31, slice offset 0x20) and powers it up by clearing it, then polls
+ * the read-only FUNC_STAT (slice offset 0xb4) until the power state
+ * settles. QEMU has no power switches, so the transition is instantaneous:
+ * FUNC_STAT is derived from the slice's SLICE_SW_CTRL.PDN_SOFT -
+ *   powered up   -> RST_STAT released                  (0x00000004)
+ *   powered down -> PSW off, ISO on, handshakes done    (0x00005511)
+ * matching PWR_MIX_FUNC_STAT_PUP / _PDN in the SM's fsl_power.h. Everything
+ * else is plain storage. Same status-mirrors-control idea as the GPC model.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define TYPE_IMX95_SRC "imx95.src"
+OBJECT_DECLARE_SIMPLE_TYPE(IMX95SRCState, IMX95_SRC)
+
+#define IMX95_SRC_REG_SIZE      0x10000
+#define IMX95_SRC_NUM_WORDS     (IMX95_SRC_REG_SIZE / 4)
+
+/* Mix-slice (SRC_XSPR) layout. SRC_GEN occupies the first slice-sized block. */
+#define SRC_SLICE_STRIDE        0x400
+#define SRC_SLICE_SW_CTRL       0x20    /* PDN_SOFT in bit 31 */
+#define SRC_FUNC_STAT           0xb4    /* read-only power/reset status */
+#define SRC_SLICE_SW_CTRL_PDN_SOFT  0x80000000u
+
+/* FUNC_STAT values for the fully-up / fully-down states (SM fsl_power.h). */
+#define SRC_FUNC_STAT_PUP       0x00000004u
+#define SRC_FUNC_STAT_PDN       0x00005511u
+
+/*
+ * SRC_GEN.SCR - the boot-reset-release latch register at offset 0x10
+ * within SRC_GEN (which sits at the SRC block base). Bit 12 is the
+ * M7MIX release; per the i.MX 95 reference manual it is sticky:
+ * "M7MIX will be held under reset until boot core writes this bit to 1.
+ * Once this bit is set to 1, it will be locked." The SM's
+ * DEV_SM_CpuStart(M7) path writes this bit during the LMM_Boot phase;
+ * we use the 0->1 transition as a rising edge on the m7mix_release
+ * gpio-out so the machine wrapper can release the M7 CPU from
+ * start-powered-off (silicon-faithful M7 release).
+ */
+#define SRC_GEN_SCR_OFFSET                      0x10u
+#define SRC_GEN_SCR_BOOT_RESET_RELEASE_M7MIX    (1u << 12)
+
+/*
+ * Per-slice reset line. SLICE_SW_CTRL.RST_RSTR_0 (bit 20) asserts the
+ * slice's core reset; the read-only RSTR_STAT.RSTR_0_RST_STAT (bit 0, slice
+ * offset 0xb8) reports it back. The SM's SRC_MixSetResetLine writes RST_RSTR_0
+ * and SRC_MixGetResetLine polls RSTR_STAT until it matches, so - like
+ * FUNC_STAT mirroring PDN_SOFT - we derive RSTR_STAT from RST_RSTR_0 so the
+ * SM's assert/deassert wait loops converge.
+ */
+#define SRC_RSTR_STAT                           0xb8
+/*
+ * SLICE_SW_CTRL.RST_RSTR[3:0] live in bits [23:20]; RSTR_STAT.RST_STAT[3:0] in
+ * bits [3:0]. The reset lines (M7MIX + sub-resets) each use one of these.
+ */
+#define SRC_SLICE_SW_CTRL_RST_RSTR_SHIFT        20
+
+struct IMX95SRCState {
+    SysBusDevice    parent_obj;
+    MemoryRegion    iomem;
+    uint32_t        regs[IMX95_SRC_NUM_WORDS];
+
+    /*
+     * Rising edge when SRC_GEN.SCR.BOOT_RESET_RELEASE_M7MIX goes 0 -> 1.
+     * Wired by the machine to a handler that releases the M7 CPU,
+     * complementing the existing reset-time release path (which
+     * releases M7 when -device loader has staged firmware into ITCM,
+     * used by tests that boot the M7 standalone without the SM).
+     */
+    qemu_irq        m7mix_release;
+};
+
+static uint64_t imx95_src_read(void *opaque, hwaddr offset, unsigned size)
+{
+    IMX95SRCState *s = opaque;
+
+    /* FUNC_STAT in a mix slice mirrors that slice's SLICE_SW_CTRL.PDN_SOFT. */
+    if (offset >= SRC_SLICE_STRIDE &&
+        (offset & (SRC_SLICE_STRIDE - 1)) == SRC_FUNC_STAT) {
+        hwaddr slice = offset & ~(hwaddr)(SRC_SLICE_STRIDE - 1);
+        uint32_t ctrl = s->regs[(slice + SRC_SLICE_SW_CTRL) / 4];
+
+        return (ctrl & SRC_SLICE_SW_CTRL_PDN_SOFT) ? SRC_FUNC_STAT_PDN
+                                                   : SRC_FUNC_STAT_PUP;
+    }
+
+    /*
+     * RSTR_STAT mirrors that slice's SLICE_SW_CTRL reset lines: the SM
+     * asserts one of RST_RSTR[3:0] (SLICE_SW_CTRL bits [23:20]) and polls the
+     * matching RST_STAT[3:0] (RSTR_STAT bits [3:0]) until it tracks. The M7 LM
+     * reset walks several reset lines (M7MIX + associated sub-resets), each on
+     * a different RST_RSTR bit, so mirror all four.
+     */
+    if (offset >= SRC_SLICE_STRIDE &&
+        (offset & (SRC_SLICE_STRIDE - 1)) == SRC_RSTR_STAT) {
+        hwaddr slice = offset & ~(hwaddr)(SRC_SLICE_STRIDE - 1);
+        uint32_t ctrl = s->regs[(slice + SRC_SLICE_SW_CTRL) / 4];
+
+        return (ctrl >> SRC_SLICE_SW_CTRL_RST_RSTR_SHIFT) & 0xf;
+    }
+    return s->regs[offset / 4];
+}
+
+static void imx95_src_write(void *opaque, hwaddr offset,
+                            uint64_t value, unsigned size)
+{
+    IMX95SRCState *s = opaque;
+
+    /*
+     * SRC_GEN.SCR: bit 12 (M7MIX release) is sticky/locked once set per
+     * the RM. Track the 0->1 transition and pulse the m7mix_release out
+     * so the machine releases the M7 CPU.
+     */
+    if (offset == SRC_GEN_SCR_OFFSET) {
+        uint32_t old = s->regs[offset / 4];
+        uint32_t new = (uint32_t)value;
+
+        /* Lock any bit that was already 1: hardware-sticky behaviour. */
+        new |= old & SRC_GEN_SCR_BOOT_RESET_RELEASE_M7MIX;
+        s->regs[offset / 4] = new;
+
+        if (!(old & SRC_GEN_SCR_BOOT_RESET_RELEASE_M7MIX) &&
+             (new & SRC_GEN_SCR_BOOT_RESET_RELEASE_M7MIX)) {
+            trace_imx95_src_m7mix_release();
+            qemu_irq_raise(s->m7mix_release);
+        }
+        return;
+    }
+
+    if (offset >= SRC_SLICE_STRIDE &&
+        (offset & (SRC_SLICE_STRIDE - 1)) == SRC_SLICE_SW_CTRL) {
+        trace_imx95_src_slice_ctrl(offset, (uint32_t)value);
+    }
+
+    s->regs[offset / 4] = value;
+}
+
+static const MemoryRegionOps imx95_src_ops = {
+    .read = imx95_src_read,
+    .write = imx95_src_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_src_reset_hold(Object *obj, ResetType type)
+{
+    IMX95SRCState *s = IMX95_SRC(obj);
+
+    memset(s->regs, 0, sizeof(s->regs));
+}
+
+static void imx95_src_init(Object *obj)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    IMX95SRCState *s = IMX95_SRC(obj);
+
+    memory_region_init_io(&s->iomem, obj, &imx95_src_ops, s,
+                          TYPE_IMX95_SRC, IMX95_SRC_REG_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+
+    /*
+     * Named gpio-out for SRC_GEN.SCR.M7MIX rising edge. Connected by
+     * the machine wrapper to a handler that releases the M7 CPU.
+     */
+    qdev_init_gpio_out_named(DEVICE(obj), &s->m7mix_release,
+                             "m7mix-release", 1);
+}
+
+static const VMStateDescription vmstate_imx95_src = {
+    .name = TYPE_IMX95_SRC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, IMX95SRCState, IMX95_SRC_NUM_WORDS),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void imx95_src_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &vmstate_imx95_src;
+    rc->phases.hold = imx95_src_reset_hold;
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+    dc->desc = "NXP i.MX 95 SRC (stub)";
+}
+
+static const TypeInfo imx95_src_info = {
+    .name           = TYPE_IMX95_SRC,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(IMX95SRCState),
+    .instance_init  = imx95_src_init,
+    .class_init     = imx95_src_class_init,
+};
+
+static void imx95_src_register_types(void)
+{
+    type_register_static(&imx95_src_info);
+}
+
+type_init(imx95_src_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 3f8cd1d825e..0b7f5e4382d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -174,3 +174,7 @@ 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'))
+system_ss.add(when: 'CONFIG_IMX95_ANATOP', if_true: files('imx95_anatop.c'))
+system_ss.add(when: 'CONFIG_IMX95_AONMIX', if_true: files('imx95_aonmix.c'))
+system_ss.add(when: 'CONFIG_IMX95_GPC', if_true: files('imx95_gpc.c'))
+system_ss.add(when: 'CONFIG_IMX95_SRC', if_true: files('imx95_src.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 584c1f90963..15fab0596c2 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -450,3 +450,11 @@ imx95_ele_msg(uint8_t command, uint8_t tag, uint32_t size) "received cmd 0x%02x
 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"
+imx95_anatop_read(uint64_t offset) "read off 0x%" PRIx64
+imx95_anatop_write(uint64_t offset, uint64_t value) "write off 0x%" PRIx64 " <- 0x%08" PRIx64
+imx95_aonmix_read(uint64_t offset) "read off 0x%" PRIx64
+imx95_aonmix_write(uint64_t offset, uint64_t value) "write off 0x%" PRIx64 " <- 0x%08" PRIx64
+imx95_aonmix_m7_gate(int run) "M7 wait gate -> run=%d"
+imx95_gpc_mode(uint64_t offset, uint32_t target) "CMC_MODE_CTRL off 0x%" PRIx64 " target %u"
+imx95_src_m7mix_release(void) "SRC_GEN.SCR M7MIX reset released"
+imx95_src_slice_ctrl(uint64_t offset, uint32_t value) "SLICE_SW_CTRL off 0x%" PRIx64 " <- 0x%08x"
-- 
2.34.1



  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 ` [PATCH 10/16] hw/misc: add i.MX 95 watchdog Kyle Fox
2026-08-20  2:48 ` Kyle Fox [this message]
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-12-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.