Add a minimal emulation of the i.MX8MP General Power Controller (GPC)
and wire it into the i.MX8MP SoC model.
The GPC manages power domains and clock-gating behavior for cores on the 
i.MX8MP SoC.
This is needed to support AMP boot of the Cortex-M7 core from Linux.

Signed-off-by: Gaurav Sharma <[email protected]>
---
 docs/system/arm/imx8m.rst    |   1 +
 hw/arm/Kconfig               |   1 +
 hw/arm/fsl-imx8mp.c          |  10 +++
 hw/misc/Kconfig              |   3 +
 hw/misc/imx8mp_gpc.c         | 124 +++++++++++++++++++++++++++++++++++
 hw/misc/meson.build          |   1 +
 include/hw/arm/fsl-imx8mp.h  |   2 +
 include/hw/misc/imx8mp_gpc.h |  34 ++++++++++
 8 files changed, 176 insertions(+)
 create mode 100644 hw/misc/imx8mp_gpc.c
 create mode 100644 include/hw/misc/imx8mp_gpc.h

diff --git a/docs/system/arm/imx8m.rst b/docs/system/arm/imx8m.rst
index afbc33b2f4..00325c2413 100644
--- a/docs/system/arm/imx8m.rst
+++ b/docs/system/arm/imx8m.rst
@@ -25,6 +25,7 @@ following devices:
  * 6 General Purpose Timers
  * Secure Non-Volatile Storage (SNVS) including an RTC
  * Clock Tree
+ * General Power Controller (GPC)
 
 Boot options
 ------------
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 5b198402d5..83293ff8a7 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -600,6 +600,7 @@ config FSL_IMX8MP
     select ARM_GIC
     select FSL_IMX8MP_ANALOG
     select FSL_IMX8MP_CCM
+    select FSL_IMX8MP_GPC
     select IMX
     select IMX_FEC
     select IMX_I2C
diff --git a/hw/arm/fsl-imx8mp.c b/hw/arm/fsl-imx8mp.c
index 7c03ed3c34..d68d816bf1 100644
--- a/hw/arm/fsl-imx8mp.c
+++ b/hw/arm/fsl-imx8mp.c
@@ -204,6 +204,8 @@ static void fsl_imx8mp_init(Object *obj)
 
     object_initialize_child(obj, "snvs", &s->snvs, TYPE_IMX7_SNVS);
 
+    object_initialize_child(obj, "gpc", &s->gpc, TYPE_IMX8MP_GPC);
+
     for (i = 0; i < FSL_IMX8MP_NUM_UARTS; i++) {
         g_autofree char *name = g_strdup_printf("uart%d", i + 1);
         object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL);
@@ -422,6 +424,13 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error 
**errp)
                            qdev_get_gpio_in(gicdev, serial_table[i].irq));
     }
 
+    /* GPC */
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpc), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpc), 0,
+                    fsl_imx8mp_memmap[FSL_IMX8MP_GPC].addr);
+
     /* GPTs */
     object_property_set_int(OBJECT(&s->gpt5_gpt6_irq), "num-lines", 2,
                             &error_abort);
@@ -686,6 +695,7 @@ static void fsl_imx8mp_realize(DeviceState *dev, Error 
**errp)
         case FSL_IMX8MP_CCM:
         case FSL_IMX8MP_GIC_DIST:
         case FSL_IMX8MP_GIC_REDIST:
+        case FSL_IMX8MP_GPC:
         case FSL_IMX8MP_GPIO1 ... FSL_IMX8MP_GPIO5:
         case FSL_IMX8MP_GPT1 ... FSL_IMX8MP_GPT6:
         case FSL_IMX8MP_ECSPI1 ... FSL_IMX8MP_ECSPI3:
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 99bdf09219..e707a40f8c 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -101,6 +101,9 @@ config FSL_IMX8MP_ANALOG
 config FSL_IMX8MP_CCM
     bool
 
+config FSL_IMX8MP_GPC
+    bool
+
 config STM32_RCC
     bool
 
diff --git a/hw/misc/imx8mp_gpc.c b/hw/misc/imx8mp_gpc.c
new file mode 100644
index 0000000000..bbe29d33e8
--- /dev/null
+++ b/hw/misc/imx8mp_gpc.c
@@ -0,0 +1,124 @@
+/*
+ * i.MX 8M Plus GPC(General Power Controller)
+ *
+ * Copyright (c) 2026, NXP Semiconductors
+ * Author: Gaurav Sharma <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#include "qemu/osdep.h"
+#include "hw/misc/imx8mp_gpc.h"
+#include "migration/vmstate.h"
+
+#define IMX8MP_GPC_ACK_BIT31 0x80000000u
+
+#define IMX8MP_GPC_STAT_BUSY_BIT (1u << 5)
+
+static uint64_t imx8mp_gpc_read(void *opaque, hwaddr offset, unsigned size)
+{
+    IMX8MPGPCState *s = opaque;
+    uint32_t idx = offset >> 2;
+
+    if (size != 4 || (offset & 3) || offset >= IMX8MP_GPC_MMIO_SIZE) {
+        return 0;
+    }
+
+    /*
+     * CM7 firmware polls GPC+0xD8 and loops while bit5 is 1.
+     * Emulate "ready" by forcing bit5 low on reads.
+     */
+    if (idx == IMX8MP_GPC_PU_PGC_SW_PUP_REQ) {
+        return s->regs[idx] & ~IMX8MP_GPC_STAT_BUSY_BIT;
+    }
+
+    return s->regs[idx];
+}
+
+static void imx8mp_gpc_write(void *opaque, hwaddr offset, uint64_t value, 
unsigned size)
+{
+    IMX8MPGPCState *s = opaque;
+    uint32_t idx = offset >> 2;
+
+    if (size != 4 || (offset & 3) || offset >= IMX8MP_GPC_MMIO_SIZE) {
+        return;
+    }
+
+    /*
+     * CM7 firmware sets bit15 in GPC+0x190 and then polls until the value
+     * becomes negative (bit31 set). Emulate the hardware ACK by forcing
+     * bit31 high on writes to this register.
+     */
+    if (idx == IMX8MP_GPC_POLL_REG) {
+        s->regs[idx] = ((uint32_t)value) | IMX8MP_GPC_ACK_BIT31;
+    } else {
+        s->regs[idx] = (uint32_t)value;
+    }
+
+}
+
+static const MemoryRegionOps imx8mp_gpc_ops = {
+    .read = imx8mp_gpc_read,
+    .write = imx8mp_gpc_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    .unaligned = false,
+    },
+};
+
+static void imx8mp_gpc_reset(DeviceState *dev)
+{
+    IMX8MPGPCState *s = IMX8MP_GPC(dev);
+    memset(s->regs, 0, sizeof(s->regs));
+
+    /*
+     * Unblock CM7 firmware polling loop: return a negative value at 0x190.
+     * Any value with bit31 set works.
+     */
+    s->regs[IMX8MP_GPC_POLL_REG] = IMX8MP_GPC_ACK_BIT31;
+
+    /* Default "ready" for the earlier poll at 0xD8: ensure busy bit is clear 
*/
+    s->regs[IMX8MP_GPC_PU_PGC_SW_PUP_REQ] = 0x00000000;
+
+}
+
+static const VMStateDescription imx8mp_gpc_vmstate = {
+    .name = TYPE_IMX8MP_GPC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, IMX8MPGPCState, IMX8MP_GPC_MMIO_SIZE / 4),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void imx8mp_gpc_init(Object *obj)
+{
+    IMX8MPGPCState *s = IMX8MP_GPC(obj);
+    memory_region_init_io(&s->iomem, obj, &imx8mp_gpc_ops, s,
+                          TYPE_IMX8MP_GPC, IMX8MP_GPC_MMIO_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void imx8mp_gpc_class_init(ObjectClass *oc, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+    device_class_set_legacy_reset(dc, imx8mp_gpc_reset);
+    dc->vmsd = &imx8mp_gpc_vmstate;
+    dc->desc = "i.MX8MP GPC(General Power Controller)";
+}
+
+static const TypeInfo imx8mp_gpc_info[] = {
+    {
+    .name = TYPE_IMX8MP_GPC,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(IMX8MPGPCState),
+    .instance_init = imx8mp_gpc_init,
+    .class_init = imx8mp_gpc_class_init,
+    }
+};
+
+DEFINE_TYPES(imx8mp_gpc_info);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index fa6a961ac9..512a539c6a 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -59,6 +59,7 @@ system_ss.add(when: 'CONFIG_ECCMEMCTL', if_true: 
files('eccmemctl.c'))
 system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pmu.c', 
'exynos4210_clk.c', 'exynos4210_rng.c'))
 system_ss.add(when: 'CONFIG_FSL_IMX8MP_ANALOG', if_true: 
files('imx8mp_analog.c'))
 system_ss.add(when: 'CONFIG_FSL_IMX8MP_CCM', if_true: files('imx8mp_ccm.c'))
+system_ss.add(when: 'CONFIG_FSL_IMX8MP_GPC', if_true: files('imx8mp_gpc.c'))
 system_ss.add(when: 'CONFIG_IMX', if_true: files(
   'imx25_ccm.c',
   'imx31_ccm.c',
diff --git a/include/hw/arm/fsl-imx8mp.h b/include/hw/arm/fsl-imx8mp.h
index 3b6183ed1d..51630cfd3b 100644
--- a/include/hw/arm/fsl-imx8mp.h
+++ b/include/hw/arm/fsl-imx8mp.h
@@ -17,6 +17,7 @@
 #include "hw/misc/imx7_snvs.h"
 #include "hw/misc/imx8mp_analog.h"
 #include "hw/misc/imx8mp_ccm.h"
+#include "hw/misc/imx8mp_gpc.h"
 #include "hw/net/imx_fec.h"
 #include "hw/core/or-irq.h"
 #include "hw/pci-host/designware.h"
@@ -54,6 +55,7 @@ struct FslImx8mpState {
 
     ARMCPU             cpu[FSL_IMX8MP_NUM_CPUS];
     GICv3State         gic;
+    IMX8MPGPCState     gpc;
     IMXGPTState        gpt[FSL_IMX8MP_NUM_GPTS];
     IMXGPIOState       gpio[FSL_IMX8MP_NUM_GPIOS];
     IMX8MPCCMState     ccm;
diff --git a/include/hw/misc/imx8mp_gpc.h b/include/hw/misc/imx8mp_gpc.h
new file mode 100644
index 0000000000..49a040e0c9
--- /dev/null
+++ b/include/hw/misc/imx8mp_gpc.h
@@ -0,0 +1,34 @@
+/*
+ * i.MX 8M Plus GPC(General Power Controller)
+ *
+ * Copyright (c) 2026, NXP Semiconductors
+ * Author: Gaurav Sharma <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#ifndef IMX8MP_GPC_H
+#define IMX8MP_GPC_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define IMX8MP_GPC_MMIO_SIZE 0x1000
+
+#define TYPE_IMX8MP_GPC "imx8mp.gpc"
+
+OBJECT_DECLARE_SIMPLE_TYPE(IMX8MPGPCState, IMX8MP_GPC)
+
+enum IMX8MPGPCRegisters {
+    IMX8MP_GPC_PU_PGC_SW_PUP_REQ = 0x0D8 / 4,
+    IMX8MP_GPC_POLL_REG          = 0x190 / 4,
+};
+
+struct IMX8MPGPCState {
+    SysBusDevice parent_obj;
+    MemoryRegion iomem;
+    uint32_t regs[IMX8MP_GPC_MMIO_SIZE / 4];
+};
+
+#endif /* IMX8MP_GPC_H */
-- 
2.34.1


Reply via email to