[PATCH] hw/misc/imx6_ccm: add the i.MX6SLL variant

Pablo Mazzini posted 1 patch 2 weeks ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260803193635.4153664-1-pmazzini@gmail.com
Maintainers: Peter Maydell <peter.maydell@linaro.org>, Jean-Christophe Dubois <jcd@tribudubois.net>
hw/misc/imx6_ccm.c         | 39 ++++++++++++++++++++++++++++++++++++--
include/hw/misc/imx6_ccm.h | 12 +++++++++++-
2 files changed, 48 insertions(+), 3 deletions(-)
[PATCH] hw/misc/imx6_ccm: add the i.MX6SLL variant
Posted by Pablo Mazzini 2 weeks ago
The i.MX6SLL shares the i.MX6Q clock tree, but reports its own chip id
in the ANATOP DIGPROG register and implements the PMU and XTALOSC24M
registers that the i.MX6Q does not.

Add TYPE_IMX6SLL_CCM as a subtype of TYPE_IMX6_CCM.  QOM runs the
parent's instance_init first, so the subtype only has to override
DIGPROG and widen the decoded ANALOG window to cover the extra
registers; the i.MX6Q window is unchanged.

DIGPROG is a hardwired chip identifier rather than a reset value, so
move its default out of imx6_ccm_reset() and into imx6_ccm_init().

The analog register array grows to cover the new registers.

Signed-off-by: Pablo Mazzini <pmazzini@gmail.com>
---
 hw/misc/imx6_ccm.c         | 39 ++++++++++++++++++++++++++++++++++++--
 include/hw/misc/imx6_ccm.h | 12 +++++++++++-
 2 files changed, 48 insertions(+), 3 deletions(-)

diff --git a/hw/misc/imx6_ccm.c b/hw/misc/imx6_ccm.c
index 45fdd0d5a8..6f48de5954 100644
--- a/hw/misc/imx6_ccm.c
+++ b/hw/misc/imx6_ccm.c
@@ -223,6 +223,14 @@ static const char *imx6_analog_reg_name(uint32_t reg)
         return "PMU_MISC1_TOG";
     case USB_ANALOG_DIGPROG:
         return "USB_ANALOG_DIGPROG";
+    case PMU_LOW_PWR_CTRL:
+        return "PMU_LOW_PWR_CTRL";
+    case XTALOSC24M_OSC_CONFIG0:
+        return "XTALOSC24M_OSC_CONFIG0";
+    case XTALOSC24M_OSC_CONFIG1:
+        return "XTALOSC24M_OSC_CONFIG1";
+    case XTALOSC24M_OSC_CONFIG2:
+        return "XTALOSC24M_OSC_CONFIG2";
     default:
         snprintf(unknown, sizeof(unknown), "%u ?", reg);
         return unknown;
@@ -237,7 +245,7 @@ static const VMStateDescription vmstate_imx6_ccm = {
     .minimum_version_id = 1,
     .fields = (const VMStateField[]) {
         VMSTATE_UINT32_ARRAY(ccm, IMX6CCMState, CCM_MAX),
-        VMSTATE_UINT32_ARRAY(analog, IMX6CCMState, CCM_ANALOG_MAX),
+        VMSTATE_UINT32_ARRAY(analog, IMX6CCMState, CCM_ANALOG_REGS),
         VMSTATE_END_OF_LIST()
     },
 };
@@ -474,7 +482,6 @@ static void imx6_ccm_reset(DeviceState *dev)
     s->analog[USB_ANALOG_USB2_VBUS_DETECT] = 0x00000004;
     s->analog[USB_ANALOG_USB2_CHRG_DETECT] = 0x00000000;
     s->analog[USB_ANALOG_USB2_MISC] = 0x00000002;
-    s->analog[USB_ANALOG_DIGPROG] = 0x00630000;
 
     /* all PLLs need to be locked */
     s->analog[CCM_ANALOG_PLL_ARM]   |= CCM_ANALOG_PLL_LOCK;
@@ -763,6 +770,12 @@ static void imx6_ccm_init(Object *obj)
     memory_region_add_subregion(&s->container, 0x4000, &s->ioanalog);
 
     sysbus_init_mmio(sd, &s->container);
+
+    /*
+     * DIGPROG is a hardwired chip identifier: it is not affected by reset.
+     * Default to i.MX6Q; variants sharing this CCM override it below.
+     */
+    s->analog[USB_ANALOG_DIGPROG] = 0x00630000;
 }
 
 static void imx6_ccm_class_init(ObjectClass *klass, const void *data)
@@ -777,6 +790,21 @@ static void imx6_ccm_class_init(ObjectClass *klass, const void *data)
     ccm->get_clock_frequency = imx6_ccm_get_clock_frequency;
 }
 
+static void imx6sll_ccm_init(Object *obj)
+{
+    IMX6CCMState *s = IMX6_CCM(obj);
+
+    /*
+     * Runs after imx6_ccm_init(), which installs the i.MX6Q default.  The
+     * clock tree is the same; only the chip identifier differs:
+     * MXC_CPU_IMX6SLL (0x67), revision 1.0.
+     */
+    s->analog[USB_ANALOG_DIGPROG] = 0x00670000;
+
+    /* Unlike the i.MX6Q, this one implements the PMU/XTALOSC24M registers. */
+    memory_region_set_size(&s->ioanalog, CCM_ANALOG_REGS * sizeof(uint32_t));
+}
+
 static const TypeInfo imx6_ccm_info = {
     .name          = TYPE_IMX6_CCM,
     .parent        = TYPE_IMX_CCM,
@@ -785,9 +813,16 @@ static const TypeInfo imx6_ccm_info = {
     .class_init    = imx6_ccm_class_init,
 };
 
+static const TypeInfo imx6sll_ccm_info = {
+    .name          = TYPE_IMX6SLL_CCM,
+    .parent        = TYPE_IMX6_CCM,
+    .instance_init = imx6sll_ccm_init,
+};
+
 static void imx6_ccm_register_types(void)
 {
     type_register_static(&imx6_ccm_info);
+    type_register_static(&imx6sll_ccm_info);
 }
 
 type_init(imx6_ccm_register_types)
diff --git a/include/hw/misc/imx6_ccm.h b/include/hw/misc/imx6_ccm.h
index a54b940686..ba8b9037ce 100644
--- a/include/hw/misc/imx6_ccm.h
+++ b/include/hw/misc/imx6_ccm.h
@@ -150,6 +150,13 @@
 #define USB_ANALOG_DIGPROG 152
 #define CCM_ANALOG_MAX 153
 
+/* PMU and XTALOSC24M registers; i.MX6SLL/SX/UL only */
+#define PMU_LOW_PWR_CTRL 156
+#define XTALOSC24M_OSC_CONFIG0 168
+#define XTALOSC24M_OSC_CONFIG1 172
+#define XTALOSC24M_OSC_CONFIG2 176
+#define CCM_ANALOG_REGS 177
+
 /* CCM_CBCMR */
 #define PRE_PERIPH_CLK_SEL_SHIFT  (18)
 #define PRE_PERIPH_CLK_SEL_LENGTH (2)
@@ -195,8 +202,11 @@ struct IMX6CCMState {
     MemoryRegion ioanalog;
 
     uint32_t ccm[CCM_MAX];
-    uint32_t analog[CCM_ANALOG_MAX];
+    uint32_t analog[CCM_ANALOG_REGS];
 
 };
 
+/* Same clock tree as the i.MX6Q, but its own chip id in ANATOP DIGPROG. */
+#define TYPE_IMX6SLL_CCM "imx6sll.ccm"
+
 #endif /* IMX6_CCM_H */
-- 
2.53.0
Re: [PATCH] hw/misc/imx6_ccm: add the i.MX6SLL variant
Posted by Peter Maydell 6 days, 11 hours ago
On Mon, 3 Aug 2026 at 20:36, Pablo Mazzini <pmazzini@gmail.com> wrote:
>
> The i.MX6SLL shares the i.MX6Q clock tree, but reports its own chip id
> in the ANATOP DIGPROG register and implements the PMU and XTALOSC24M
> registers that the i.MX6Q does not.
>
> Add TYPE_IMX6SLL_CCM as a subtype of TYPE_IMX6_CCM.  QOM runs the
> parent's instance_init first, so the subtype only has to override
> DIGPROG and widen the decoded ANALOG window to cover the extra
> registers; the i.MX6Q window is unchanged.

There's no user of this new type. We prefer patches adding new
devices to come in a patchset that includes support for whatever
their user is.

>
> DIGPROG is a hardwired chip identifier rather than a reset value, so
> move its default out of imx6_ccm_reset() and into imx6_ccm_init().
>
> The analog register array grows to cover the new registers.
>
> Signed-off-by: Pablo Mazzini <pmazzini@gmail.com>
> ---
>  hw/misc/imx6_ccm.c         | 39 ++++++++++++++++++++++++++++++++++++--
>  include/hw/misc/imx6_ccm.h | 12 +++++++++++-
>  2 files changed, 48 insertions(+), 3 deletions(-)
>
> diff --git a/hw/misc/imx6_ccm.c b/hw/misc/imx6_ccm.c
> index 45fdd0d5a8..6f48de5954 100644
> --- a/hw/misc/imx6_ccm.c
> +++ b/hw/misc/imx6_ccm.c
> @@ -223,6 +223,14 @@ static const char *imx6_analog_reg_name(uint32_t reg)
>          return "PMU_MISC1_TOG";
>      case USB_ANALOG_DIGPROG:
>          return "USB_ANALOG_DIGPROG";
> +    case PMU_LOW_PWR_CTRL:
> +        return "PMU_LOW_PWR_CTRL";
> +    case XTALOSC24M_OSC_CONFIG0:
> +        return "XTALOSC24M_OSC_CONFIG0";
> +    case XTALOSC24M_OSC_CONFIG1:
> +        return "XTALOSC24M_OSC_CONFIG1";
> +    case XTALOSC24M_OSC_CONFIG2:
> +        return "XTALOSC24M_OSC_CONFIG2";
>      default:
>          snprintf(unknown, sizeof(unknown), "%u ?", reg);
>          return unknown;
> @@ -237,7 +245,7 @@ static const VMStateDescription vmstate_imx6_ccm = {
>      .minimum_version_id = 1,
>      .fields = (const VMStateField[]) {
>          VMSTATE_UINT32_ARRAY(ccm, IMX6CCMState, CCM_MAX),
> -        VMSTATE_UINT32_ARRAY(analog, IMX6CCMState, CCM_ANALOG_MAX),
> +        VMSTATE_UINT32_ARRAY(analog, IMX6CCMState, CCM_ANALOG_REGS),
>          VMSTATE_END_OF_LIST()
>      },
>  };

Adding new elements to a vmstate is a migration bump, and at
minimum needs the version ID numbers to be incremented.

In this case I would favour having the new register state
in a vmstate subsection so that we only migrate it for the new
device type, and the existing device's migration info doesn't change.

> @@ -474,7 +482,6 @@ static void imx6_ccm_reset(DeviceState *dev)
>      s->analog[USB_ANALOG_USB2_VBUS_DETECT] = 0x00000004;
>      s->analog[USB_ANALOG_USB2_CHRG_DETECT] = 0x00000000;
>      s->analog[USB_ANALOG_USB2_MISC] = 0x00000002;
> -    s->analog[USB_ANALOG_DIGPROG] = 0x00630000;
>
>      /* all PLLs need to be locked */
>      s->analog[CCM_ANALOG_PLL_ARM]   |= CCM_ANALOG_PLL_LOCK;
> @@ -763,6 +770,12 @@ static void imx6_ccm_init(Object *obj)
>      memory_region_add_subregion(&s->container, 0x4000, &s->ioanalog);
>
>      sysbus_init_mmio(sd, &s->container);
> +
> +    /*
> +     * DIGPROG is a hardwired chip identifier: it is not affected by reset.
> +     * Default to i.MX6Q; variants sharing this CCM override it below.
> +     */
> +    s->analog[USB_ANALOG_DIGPROG] = 0x00630000;
>  }
>
>  static void imx6_ccm_class_init(ObjectClass *klass, const void *data)
> @@ -777,6 +790,21 @@ static void imx6_ccm_class_init(ObjectClass *klass, const void *data)
>      ccm->get_clock_frequency = imx6_ccm_get_clock_frequency;
>  }
>
> +static void imx6sll_ccm_init(Object *obj)
> +{
> +    IMX6CCMState *s = IMX6_CCM(obj);
> +
> +    /*
> +     * Runs after imx6_ccm_init(), which installs the i.MX6Q default.  The
> +     * clock tree is the same; only the chip identifier differs:
> +     * MXC_CPU_IMX6SLL (0x67), revision 1.0.
> +     */
> +    s->analog[USB_ANALOG_DIGPROG] = 0x00670000;
> +
> +    /* Unlike the i.MX6Q, this one implements the PMU/XTALOSC24M registers. */
> +    memory_region_set_size(&s->ioanalog, CCM_ANALOG_REGS * sizeof(uint32_t));

memory_region_set_size() is really for MRs that dynamically change size,
not for fixing up a size that we could have got right in the first place.

I recommend that you add a class struct to the TYPE_IMX6_CCM, and give
that struct suitable fields that define how the subclasses differ (DIGPROG
reset value, whether XTALOSC24M registers are present, etc). In the class_init
function for each subclass you set those fields in the class struct. Then in
instance_init you can look at the class struct to see how big you should
make the MemoryRegion, and in reset you can look to see what reset value to
use, and so on.

hw/misc/npcm_gcr.c is an example of this approach.

thanks
-- PMM