]>
Commit | Line | Data |
---|---|---|
92eccc6e JCD |
1 | /* |
2 | * IMX25 Clock Control Module | |
3 | * | |
4 | * Copyright (C) 2012 NICTA | |
5 | * Updated by Jean-Christophe Dubois <[email protected]> | |
6 | * | |
7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
8 | * See the COPYING file in the top-level directory. | |
9 | * | |
10 | * To get the timer frequencies right, we need to emulate at least part of | |
11 | * the CCM. | |
12 | */ | |
13 | ||
14 | #include "hw/misc/imx25_ccm.h" | |
15 | ||
16 | #ifndef DEBUG_IMX25_CCM | |
17 | #define DEBUG_IMX25_CCM 0 | |
18 | #endif | |
19 | ||
20 | #define DPRINTF(fmt, args...) \ | |
21 | do { \ | |
22 | if (DEBUG_IMX25_CCM) { \ | |
23 | fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX25_CCM, \ | |
24 | __func__, ##args); \ | |
25 | } \ | |
26 | } while (0) | |
27 | ||
28 | static char const *imx25_ccm_reg_name(uint32_t reg) | |
29 | { | |
30 | static char unknown[20]; | |
31 | ||
32 | switch (reg) { | |
33 | case IMX25_CCM_MPCTL_REG: | |
34 | return "mpctl"; | |
35 | case IMX25_CCM_UPCTL_REG: | |
36 | return "upctl"; | |
37 | case IMX25_CCM_CCTL_REG: | |
38 | return "cctl"; | |
39 | case IMX25_CCM_CGCR0_REG: | |
40 | return "cgcr0"; | |
41 | case IMX25_CCM_CGCR1_REG: | |
42 | return "cgcr1"; | |
43 | case IMX25_CCM_CGCR2_REG: | |
44 | return "cgcr2"; | |
45 | case IMX25_CCM_PCDR0_REG: | |
46 | return "pcdr0"; | |
47 | case IMX25_CCM_PCDR1_REG: | |
48 | return "pcdr1"; | |
49 | case IMX25_CCM_PCDR2_REG: | |
50 | return "pcdr2"; | |
51 | case IMX25_CCM_PCDR3_REG: | |
52 | return "pcdr3"; | |
53 | case IMX25_CCM_RCSR_REG: | |
54 | return "rcsr"; | |
55 | case IMX25_CCM_CRDR_REG: | |
56 | return "crdr"; | |
57 | case IMX25_CCM_DCVR0_REG: | |
58 | return "dcvr0"; | |
59 | case IMX25_CCM_DCVR1_REG: | |
60 | return "dcvr1"; | |
61 | case IMX25_CCM_DCVR2_REG: | |
62 | return "dcvr2"; | |
63 | case IMX25_CCM_DCVR3_REG: | |
64 | return "dcvr3"; | |
65 | case IMX25_CCM_LTR0_REG: | |
66 | return "ltr0"; | |
67 | case IMX25_CCM_LTR1_REG: | |
68 | return "ltr1"; | |
69 | case IMX25_CCM_LTR2_REG: | |
70 | return "ltr2"; | |
71 | case IMX25_CCM_LTR3_REG: | |
72 | return "ltr3"; | |
73 | case IMX25_CCM_LTBR0_REG: | |
74 | return "ltbr0"; | |
75 | case IMX25_CCM_LTBR1_REG: | |
76 | return "ltbr1"; | |
77 | case IMX25_CCM_PMCR0_REG: | |
78 | return "pmcr0"; | |
79 | case IMX25_CCM_PMCR1_REG: | |
80 | return "pmcr1"; | |
81 | case IMX25_CCM_PMCR2_REG: | |
82 | return "pmcr2"; | |
83 | case IMX25_CCM_MCR_REG: | |
84 | return "mcr"; | |
85 | case IMX25_CCM_LPIMR0_REG: | |
86 | return "lpimr0"; | |
87 | case IMX25_CCM_LPIMR1_REG: | |
88 | return "lpimr1"; | |
89 | default: | |
90 | sprintf(unknown, "[%d ?]", reg); | |
91 | return unknown; | |
92 | } | |
93 | } | |
94 | #define CKIH_FREQ 24000000 /* 24MHz crystal input */ | |
95 | ||
96 | static const VMStateDescription vmstate_imx25_ccm = { | |
97 | .name = TYPE_IMX25_CCM, | |
98 | .version_id = 1, | |
99 | .minimum_version_id = 1, | |
100 | .fields = (VMStateField[]) { | |
101 | VMSTATE_UINT32_ARRAY(reg, IMX25CCMState, IMX25_CCM_MAX_REG), | |
102 | VMSTATE_END_OF_LIST() | |
103 | }, | |
104 | }; | |
105 | ||
106 | static uint32_t imx25_ccm_get_mpll_clk(IMXCCMState *dev) | |
107 | { | |
108 | uint32_t freq; | |
109 | IMX25CCMState *s = IMX25_CCM(dev); | |
110 | ||
111 | if (EXTRACT(s->reg[IMX25_CCM_CCTL_REG], MPLL_BYPASS)) { | |
112 | freq = CKIH_FREQ; | |
113 | } else { | |
114 | freq = imx_ccm_calc_pll(s->reg[IMX25_CCM_MPCTL_REG], CKIH_FREQ); | |
115 | } | |
116 | ||
117 | DPRINTF("freq = %d\n", freq); | |
118 | ||
119 | return freq; | |
120 | } | |
121 | ||
122 | static uint32_t imx25_ccm_get_upll_clk(IMXCCMState *dev) | |
123 | { | |
124 | uint32_t freq = 0; | |
125 | IMX25CCMState *s = IMX25_CCM(dev); | |
126 | ||
127 | if (!EXTRACT(s->reg[IMX25_CCM_CCTL_REG], UPLL_DIS)) { | |
128 | freq = imx_ccm_calc_pll(s->reg[IMX25_CCM_UPCTL_REG], CKIH_FREQ); | |
129 | } | |
130 | ||
131 | DPRINTF("freq = %d\n", freq); | |
132 | ||
133 | return freq; | |
134 | } | |
135 | ||
136 | static uint32_t imx25_ccm_get_mcu_clk(IMXCCMState *dev) | |
137 | { | |
138 | uint32_t freq; | |
139 | IMX25CCMState *s = IMX25_CCM(dev); | |
140 | ||
141 | freq = imx25_ccm_get_mpll_clk(dev); | |
142 | ||
143 | if (EXTRACT(s->reg[IMX25_CCM_CCTL_REG], ARM_SRC)) { | |
144 | freq = (freq * 3 / 4); | |
145 | } | |
146 | ||
147 | freq = freq / (1 + EXTRACT(s->reg[IMX25_CCM_CCTL_REG], ARM_CLK_DIV)); | |
148 | ||
149 | DPRINTF("freq = %d\n", freq); | |
150 | ||
151 | return freq; | |
152 | } | |
153 | ||
154 | static uint32_t imx25_ccm_get_ahb_clk(IMXCCMState *dev) | |
155 | { | |
156 | uint32_t freq; | |
157 | IMX25CCMState *s = IMX25_CCM(dev); | |
158 | ||
159 | freq = imx25_ccm_get_mcu_clk(dev) | |
160 | / (1 + EXTRACT(s->reg[IMX25_CCM_CCTL_REG], AHB_CLK_DIV)); | |
161 | ||
162 | DPRINTF("freq = %d\n", freq); | |
163 | ||
164 | return freq; | |
165 | } | |
166 | ||
167 | static uint32_t imx25_ccm_get_ipg_clk(IMXCCMState *dev) | |
168 | { | |
169 | uint32_t freq; | |
170 | ||
171 | freq = imx25_ccm_get_ahb_clk(dev) / 2; | |
172 | ||
173 | DPRINTF("freq = %d\n", freq); | |
174 | ||
175 | return freq; | |
176 | } | |
177 | ||
178 | static uint32_t imx25_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock) | |
179 | { | |
180 | uint32_t freq = 0; | |
181 | DPRINTF("Clock = %d)\n", clock); | |
182 | ||
183 | switch (clock) { | |
184 | case NOCLK: | |
185 | break; | |
186 | case CLK_MPLL: | |
187 | freq = imx25_ccm_get_mpll_clk(dev); | |
188 | break; | |
189 | case CLK_UPLL: | |
190 | freq = imx25_ccm_get_upll_clk(dev); | |
191 | break; | |
192 | case CLK_MCU: | |
193 | freq = imx25_ccm_get_mcu_clk(dev); | |
194 | break; | |
195 | case CLK_AHB: | |
196 | freq = imx25_ccm_get_ahb_clk(dev); | |
197 | break; | |
198 | case CLK_IPG: | |
199 | freq = imx25_ccm_get_ipg_clk(dev); | |
200 | break; | |
201 | case CLK_32k: | |
202 | freq = CKIL_FREQ; | |
203 | break; | |
204 | default: | |
205 | qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: unsupported clock %d\n", | |
206 | TYPE_IMX25_CCM, __func__, clock); | |
207 | break; | |
208 | } | |
209 | ||
210 | DPRINTF("Clock = %d) = %d\n", clock, freq); | |
211 | ||
212 | return freq; | |
213 | } | |
214 | ||
215 | static void imx25_ccm_reset(DeviceState *dev) | |
216 | { | |
217 | IMX25CCMState *s = IMX25_CCM(dev); | |
218 | ||
219 | DPRINTF("\n"); | |
220 | ||
221 | memset(s->reg, 0, IMX25_CCM_MAX_REG * sizeof(uint32_t)); | |
222 | s->reg[IMX25_CCM_MPCTL_REG] = 0x800b2c01; | |
223 | s->reg[IMX25_CCM_UPCTL_REG] = 0x84042800; | |
224 | /* | |
225 | * The value below gives: | |
226 | * CPU = 133 MHz, AHB = 66,5 MHz, IPG = 33 MHz. | |
227 | */ | |
228 | s->reg[IMX25_CCM_CCTL_REG] = 0xd0030000; | |
229 | s->reg[IMX25_CCM_CGCR0_REG] = 0x028A0100; | |
230 | s->reg[IMX25_CCM_CGCR1_REG] = 0x04008100; | |
231 | s->reg[IMX25_CCM_CGCR2_REG] = 0x00000438; | |
232 | s->reg[IMX25_CCM_PCDR0_REG] = 0x01010101; | |
233 | s->reg[IMX25_CCM_PCDR1_REG] = 0x01010101; | |
234 | s->reg[IMX25_CCM_PCDR2_REG] = 0x01010101; | |
235 | s->reg[IMX25_CCM_PCDR3_REG] = 0x01010101; | |
236 | s->reg[IMX25_CCM_PMCR0_REG] = 0x00A00000; | |
237 | s->reg[IMX25_CCM_PMCR1_REG] = 0x0000A030; | |
238 | s->reg[IMX25_CCM_PMCR2_REG] = 0x0000A030; | |
239 | s->reg[IMX25_CCM_MCR_REG] = 0x43000000; | |
240 | ||
241 | /* | |
242 | * default boot will change the reset values to allow: | |
243 | * CPU = 399 MHz, AHB = 133 MHz, IPG = 66,5 MHz. | |
244 | * For some reason, this doesn't work. With the value below, linux | |
245 | * detects a 88 MHz IPG CLK instead of 66,5 MHz. | |
246 | s->reg[IMX25_CCM_CCTL_REG] = 0x20032000; | |
247 | */ | |
248 | } | |
249 | ||
250 | static uint64_t imx25_ccm_read(void *opaque, hwaddr offset, unsigned size) | |
251 | { | |
252 | uint32 value = 0; | |
253 | IMX25CCMState *s = (IMX25CCMState *)opaque; | |
254 | ||
255 | if (offset < 0x70) { | |
256 | value = s->reg[offset >> 2]; | |
257 | } else { | |
258 | qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" | |
259 | HWADDR_PRIx "\n", TYPE_IMX25_CCM, __func__, offset); | |
260 | } | |
261 | ||
262 | DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx25_ccm_reg_name(offset >> 2), | |
263 | value); | |
264 | ||
265 | return value; | |
266 | } | |
267 | ||
268 | static void imx25_ccm_write(void *opaque, hwaddr offset, uint64_t value, | |
269 | unsigned size) | |
270 | { | |
271 | IMX25CCMState *s = (IMX25CCMState *)opaque; | |
272 | ||
273 | DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx25_ccm_reg_name(offset >> 2), | |
274 | (uint32_t)value); | |
275 | ||
276 | if (offset < 0x70) { | |
277 | /* | |
278 | * We will do a better implementation later. In particular some bits | |
279 | * cannot be written to. | |
280 | */ | |
281 | s->reg[offset >> 2] = value; | |
282 | } else { | |
283 | qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" | |
284 | HWADDR_PRIx "\n", TYPE_IMX25_CCM, __func__, offset); | |
285 | } | |
286 | } | |
287 | ||
288 | static const struct MemoryRegionOps imx25_ccm_ops = { | |
289 | .read = imx25_ccm_read, | |
290 | .write = imx25_ccm_write, | |
291 | .endianness = DEVICE_NATIVE_ENDIAN, | |
292 | .valid = { | |
293 | /* | |
294 | * Our device would not work correctly if the guest was doing | |
295 | * unaligned access. This might not be a limitation on the real | |
296 | * device but in practice there is no reason for a guest to access | |
297 | * this device unaligned. | |
298 | */ | |
299 | .min_access_size = 4, | |
300 | .max_access_size = 4, | |
301 | .unaligned = false, | |
302 | }, | |
303 | }; | |
304 | ||
305 | static void imx25_ccm_init(Object *obj) | |
306 | { | |
307 | DeviceState *dev = DEVICE(obj); | |
308 | SysBusDevice *sd = SYS_BUS_DEVICE(obj); | |
309 | IMX25CCMState *s = IMX25_CCM(obj); | |
310 | ||
311 | memory_region_init_io(&s->iomem, OBJECT(dev), &imx25_ccm_ops, s, | |
312 | TYPE_IMX25_CCM, 0x1000); | |
313 | sysbus_init_mmio(sd, &s->iomem); | |
314 | } | |
315 | ||
316 | static void imx25_ccm_class_init(ObjectClass *klass, void *data) | |
317 | { | |
318 | DeviceClass *dc = DEVICE_CLASS(klass); | |
319 | IMXCCMClass *ccm = IMX_CCM_CLASS(klass); | |
320 | ||
321 | dc->reset = imx25_ccm_reset; | |
322 | dc->vmsd = &vmstate_imx25_ccm; | |
323 | dc->desc = "i.MX25 Clock Control Module"; | |
324 | ||
325 | ccm->get_clock_frequency = imx25_ccm_get_clock_frequency; | |
326 | } | |
327 | ||
328 | static const TypeInfo imx25_ccm_info = { | |
329 | .name = TYPE_IMX25_CCM, | |
330 | .parent = TYPE_IMX_CCM, | |
331 | .instance_size = sizeof(IMX25CCMState), | |
332 | .instance_init = imx25_ccm_init, | |
333 | .class_init = imx25_ccm_class_init, | |
334 | }; | |
335 | ||
336 | static void imx25_ccm_register_types(void) | |
337 | { | |
338 | type_register_static(&imx25_ccm_info); | |
339 | } | |
340 | ||
341 | type_init(imx25_ccm_register_types) |