1 /*
2 * Copyright (c) 2025 Bernhard Beschow <shentey@gmail.com>
3 *
4 * i.MX 8M Plus CCM IP block emulation code
5 *
6 * Based on hw/misc/imx7_ccm.c
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11 #include "qemu/osdep.h"
12 #include "qemu/log.h"
13
14 #include "hw/misc/imx8mp_ccm.h"
15 #include "migration/vmstate.h"
16
17 #include "trace.h"
18
19 #define CKIH_FREQ 16000000 /* 16MHz crystal input */
20
imx8mp_ccm_reset(DeviceState * dev)21 static void imx8mp_ccm_reset(DeviceState *dev)
22 {
23 IMX8MPCCMState *s = IMX8MP_CCM(dev);
24
25 memset(s->ccm, 0, sizeof(s->ccm));
26 }
27
28 #define CCM_INDEX(offset) (((offset) & ~(hwaddr)0xF) / sizeof(uint32_t))
29 #define CCM_BITOP(offset) ((offset) & (hwaddr)0xF)
30
31 enum {
32 CCM_BITOP_NONE = 0x00,
33 CCM_BITOP_SET = 0x04,
34 CCM_BITOP_CLR = 0x08,
35 CCM_BITOP_TOG = 0x0C,
36 };
37
imx8mp_set_clr_tog_read(void * opaque,hwaddr offset,unsigned size)38 static uint64_t imx8mp_set_clr_tog_read(void *opaque, hwaddr offset,
39 unsigned size)
40 {
41 const uint32_t *mmio = opaque;
42
43 return mmio[CCM_INDEX(offset)];
44 }
45
imx8mp_set_clr_tog_write(void * opaque,hwaddr offset,uint64_t value,unsigned size)46 static void imx8mp_set_clr_tog_write(void *opaque, hwaddr offset,
47 uint64_t value, unsigned size)
48 {
49 const uint8_t bitop = CCM_BITOP(offset);
50 const uint32_t index = CCM_INDEX(offset);
51 uint32_t *mmio = opaque;
52
53 switch (bitop) {
54 case CCM_BITOP_NONE:
55 mmio[index] = value;
56 break;
57 case CCM_BITOP_SET:
58 mmio[index] |= value;
59 break;
60 case CCM_BITOP_CLR:
61 mmio[index] &= ~value;
62 break;
63 case CCM_BITOP_TOG:
64 mmio[index] ^= value;
65 break;
66 };
67 }
68
69 static const struct MemoryRegionOps imx8mp_set_clr_tog_ops = {
70 .read = imx8mp_set_clr_tog_read,
71 .write = imx8mp_set_clr_tog_write,
72 .endianness = DEVICE_NATIVE_ENDIAN,
73 .impl = {
74 /*
75 * Our device would not work correctly if the guest was doing
76 * unaligned access. This might not be a limitation on the real
77 * device but in practice there is no reason for a guest to access
78 * this device unaligned.
79 */
80 .min_access_size = 4,
81 .max_access_size = 4,
82 .unaligned = false,
83 },
84 };
85
imx8mp_ccm_init(Object * obj)86 static void imx8mp_ccm_init(Object *obj)
87 {
88 SysBusDevice *sd = SYS_BUS_DEVICE(obj);
89 IMX8MPCCMState *s = IMX8MP_CCM(obj);
90
91 memory_region_init_io(&s->iomem,
92 obj,
93 &imx8mp_set_clr_tog_ops,
94 s->ccm,
95 TYPE_IMX8MP_CCM ".ccm",
96 sizeof(s->ccm));
97
98 sysbus_init_mmio(sd, &s->iomem);
99 }
100
101 static const VMStateDescription imx8mp_ccm_vmstate = {
102 .name = TYPE_IMX8MP_CCM,
103 .version_id = 1,
104 .minimum_version_id = 1,
105 .fields = (const VMStateField[]) {
106 VMSTATE_UINT32_ARRAY(ccm, IMX8MPCCMState, CCM_MAX),
107 VMSTATE_END_OF_LIST()
108 },
109 };
110
imx8mp_ccm_get_clock_frequency(IMXCCMState * dev,IMXClk clock)111 static uint32_t imx8mp_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock)
112 {
113 /*
114 * This function is "consumed" by GPT emulation code. Some clocks
115 * have fixed frequencies and we can provide requested frequency
116 * easily. However for CCM provided clocks (like IPG) each GPT
117 * timer can have its own clock root.
118 * This means we need additional information when calling this
119 * function to know the requester's identity.
120 */
121 uint32_t freq = 0;
122
123 switch (clock) {
124 case CLK_NONE:
125 break;
126 case CLK_32k:
127 freq = CKIL_FREQ;
128 break;
129 case CLK_HIGH:
130 freq = CKIH_FREQ;
131 break;
132 case CLK_IPG:
133 case CLK_IPG_HIGH:
134 /*
135 * For now we don't have a way to figure out the device this
136 * function is called for. Until then the IPG derived clocks
137 * are left unimplemented.
138 */
139 qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Clock %d Not implemented\n",
140 TYPE_IMX8MP_CCM, __func__, clock);
141 break;
142 default:
143 qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: unsupported clock %d\n",
144 TYPE_IMX8MP_CCM, __func__, clock);
145 break;
146 }
147
148 trace_ccm_clock_freq(clock, freq);
149
150 return freq;
151 }
152
imx8mp_ccm_class_init(ObjectClass * klass,const void * data)153 static void imx8mp_ccm_class_init(ObjectClass *klass, const void *data)
154 {
155 DeviceClass *dc = DEVICE_CLASS(klass);
156 IMXCCMClass *ccm = IMX_CCM_CLASS(klass);
157
158 device_class_set_legacy_reset(dc, imx8mp_ccm_reset);
159 dc->vmsd = &imx8mp_ccm_vmstate;
160 dc->desc = "i.MX 8M Plus Clock Control Module";
161
162 ccm->get_clock_frequency = imx8mp_ccm_get_clock_frequency;
163 }
164
165 static const TypeInfo imx8mp_ccm_types[] = {
166 {
167 .name = TYPE_IMX8MP_CCM,
168 .parent = TYPE_IMX_CCM,
169 .instance_size = sizeof(IMX8MPCCMState),
170 .instance_init = imx8mp_ccm_init,
171 .class_init = imx8mp_ccm_class_init,
172 },
173 };
174
175 DEFINE_TYPES(imx8mp_ccm_types);
176