xref: /qemu/hw/misc/imx8mp_ccm.c (revision 96adf9b404e51b9acdf9592595ad935905de1f4e)
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 
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 
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 
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 
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 
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 
153 static void imx8mp_ccm_class_init(ObjectClass *klass, 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