]>
Commit | Line | Data |
---|---|---|
fc14176b LM |
1 | /* |
2 | * BCM2835 CPRMAN clock manager | |
3 | * | |
4 | * Copyright (c) 2020 Luc Michel <luc@lmichel.fr> | |
5 | * | |
6 | * SPDX-License-Identifier: GPL-2.0-or-later | |
7 | */ | |
8 | ||
9 | /* | |
10 | * This peripheral is roughly divided into 3 main parts: | |
11 | * - the PLLs | |
12 | * - the PLL channels | |
13 | * - the clock muxes | |
14 | * | |
15 | * A main oscillator (xosc) feeds all the PLLs. Each PLLs has one or more | |
16 | * channels. Those channel are then connected to the clock muxes. Each mux has | |
17 | * multiples sources (usually the xosc, some of the PLL channels and some "test | |
18 | * debug" clocks). A mux is configured to select a given source through its | |
19 | * control register. Each mux has one output clock that also goes out of the | |
20 | * CPRMAN. This output clock usually connects to another peripheral in the SoC | |
21 | * (so a given mux is dedicated to a peripheral). | |
22 | * | |
23 | * At each level (PLL, channel and mux), the clock can be altered through | |
24 | * dividers (and multipliers in case of the PLLs), and can be disabled (in this | |
25 | * case, the next levels see no clock). | |
26 | * | |
27 | * This can be sum-up as follows (this is an example and not the actual BCM2835 | |
28 | * clock tree): | |
29 | * | |
30 | * /-->[PLL]-|->[PLL channel]--... [mux]--> to peripherals | |
31 | * | |->[PLL channel] muxes takes [mux] | |
32 | * | \->[PLL channel] inputs from [mux] | |
33 | * | some channels [mux] | |
34 | * [xosc]---|-->[PLL]-|->[PLL channel] and other srcs [mux] | |
35 | * | \->[PLL channel] ...-->[mux] | |
36 | * | [mux] | |
37 | * \-->[PLL]--->[PLL channel] [mux] | |
38 | * | |
39 | * The page at https://elinux.org/The_Undocumented_Pi gives the actual clock | |
40 | * tree configuration. | |
72813624 LM |
41 | * |
42 | * The CPRMAN exposes clock outputs with the name of the clock mux suffixed | |
43 | * with "-out" (e.g. "uart-out", "h264-out", ...). | |
fc14176b LM |
44 | */ |
45 | ||
46 | #include "qemu/osdep.h" | |
47 | #include "qemu/log.h" | |
48 | #include "migration/vmstate.h" | |
49 | #include "hw/qdev-properties.h" | |
50 | #include "hw/misc/bcm2835_cprman.h" | |
51 | #include "hw/misc/bcm2835_cprman_internals.h" | |
52 | #include "trace.h" | |
53 | ||
1e986e25 LM |
54 | /* PLL */ |
55 | ||
83ad4695 LM |
56 | static void pll_reset(DeviceState *dev) |
57 | { | |
58 | CprmanPllState *s = CPRMAN_PLL(dev); | |
59 | const PLLResetInfo *info = &PLL_RESET_INFO[s->id]; | |
60 | ||
61 | *s->reg_cm = info->cm; | |
62 | *s->reg_a2w_ctrl = info->a2w_ctrl; | |
63 | memcpy(s->reg_a2w_ana, info->a2w_ana, sizeof(info->a2w_ana)); | |
64 | *s->reg_a2w_frac = info->a2w_frac; | |
65 | } | |
66 | ||
6d2b874c LM |
67 | static bool pll_is_locked(const CprmanPllState *pll) |
68 | { | |
69 | return !FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, PWRDN) | |
70 | && !FIELD_EX32(*pll->reg_cm, CM_PLLx, ANARST); | |
71 | } | |
72 | ||
1e986e25 LM |
73 | static void pll_update(CprmanPllState *pll) |
74 | { | |
6d2b874c LM |
75 | uint64_t freq, ndiv, fdiv, pdiv; |
76 | ||
77 | if (!pll_is_locked(pll)) { | |
78 | clock_update(pll->out, 0); | |
79 | return; | |
80 | } | |
81 | ||
82 | pdiv = FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, PDIV); | |
83 | ||
84 | if (!pdiv) { | |
85 | clock_update(pll->out, 0); | |
86 | return; | |
87 | } | |
88 | ||
89 | ndiv = FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, NDIV); | |
90 | fdiv = FIELD_EX32(*pll->reg_a2w_frac, A2W_PLLx_FRAC, FRAC); | |
91 | ||
92 | if (pll->reg_a2w_ana[1] & pll->prediv_mask) { | |
93 | /* The prescaler doubles the parent frequency */ | |
94 | ndiv *= 2; | |
95 | fdiv *= 2; | |
96 | } | |
97 | ||
98 | /* | |
99 | * We have a multiplier with an integer part (ndiv) and a fractional part | |
100 | * (fdiv), and a divider (pdiv). | |
101 | */ | |
102 | freq = clock_get_hz(pll->xosc_in) * | |
103 | ((ndiv << R_A2W_PLLx_FRAC_FRAC_LENGTH) + fdiv); | |
104 | freq /= pdiv; | |
105 | freq >>= R_A2W_PLLx_FRAC_FRAC_LENGTH; | |
106 | ||
107 | clock_update_hz(pll->out, freq); | |
1e986e25 LM |
108 | } |
109 | ||
5ee0abed | 110 | static void pll_xosc_update(void *opaque, ClockEvent event) |
1e986e25 LM |
111 | { |
112 | pll_update(CPRMAN_PLL(opaque)); | |
113 | } | |
114 | ||
115 | static void pll_init(Object *obj) | |
116 | { | |
117 | CprmanPllState *s = CPRMAN_PLL(obj); | |
118 | ||
5ee0abed PM |
119 | s->xosc_in = qdev_init_clock_in(DEVICE(s), "xosc-in", pll_xosc_update, |
120 | s, ClockUpdate); | |
1e986e25 LM |
121 | s->out = qdev_init_clock_out(DEVICE(s), "out"); |
122 | } | |
123 | ||
124 | static const VMStateDescription pll_vmstate = { | |
125 | .name = TYPE_CPRMAN_PLL, | |
126 | .version_id = 1, | |
127 | .minimum_version_id = 1, | |
128 | .fields = (VMStateField[]) { | |
129 | VMSTATE_CLOCK(xosc_in, CprmanPllState), | |
130 | VMSTATE_END_OF_LIST() | |
131 | } | |
132 | }; | |
133 | ||
134 | static void pll_class_init(ObjectClass *klass, void *data) | |
135 | { | |
136 | DeviceClass *dc = DEVICE_CLASS(klass); | |
137 | ||
83ad4695 | 138 | dc->reset = pll_reset; |
1e986e25 LM |
139 | dc->vmsd = &pll_vmstate; |
140 | } | |
141 | ||
142 | static const TypeInfo cprman_pll_info = { | |
143 | .name = TYPE_CPRMAN_PLL, | |
144 | .parent = TYPE_DEVICE, | |
145 | .instance_size = sizeof(CprmanPllState), | |
146 | .class_init = pll_class_init, | |
147 | .instance_init = pll_init, | |
148 | }; | |
149 | ||
150 | ||
09d56bbc LM |
151 | /* PLL channel */ |
152 | ||
83ad4695 LM |
153 | static void pll_channel_reset(DeviceState *dev) |
154 | { | |
155 | CprmanPllChannelState *s = CPRMAN_PLL_CHANNEL(dev); | |
156 | const PLLChannelResetInfo *info = &PLL_CHANNEL_RESET_INFO[s->id]; | |
157 | ||
158 | *s->reg_a2w_ctrl = info->a2w_ctrl; | |
159 | } | |
160 | ||
95745811 LM |
161 | static bool pll_channel_is_enabled(CprmanPllChannelState *channel) |
162 | { | |
163 | /* | |
164 | * XXX I'm not sure of the purpose of the LOAD field. The Linux driver does | |
165 | * not set it when enabling the channel, but does clear it when disabling | |
166 | * it. | |
167 | */ | |
168 | return !FIELD_EX32(*channel->reg_a2w_ctrl, A2W_PLLx_CHANNELy, DISABLE) | |
169 | && !(*channel->reg_cm & channel->hold_mask); | |
170 | } | |
171 | ||
09d56bbc LM |
172 | static void pll_channel_update(CprmanPllChannelState *channel) |
173 | { | |
95745811 LM |
174 | uint64_t freq, div; |
175 | ||
176 | if (!pll_channel_is_enabled(channel)) { | |
177 | clock_update(channel->out, 0); | |
178 | return; | |
179 | } | |
180 | ||
181 | div = FIELD_EX32(*channel->reg_a2w_ctrl, A2W_PLLx_CHANNELy, DIV); | |
182 | ||
183 | if (!div) { | |
184 | /* | |
185 | * It seems that when the divider value is 0, it is considered as | |
186 | * being maximum by the hardware (see the Linux driver). | |
187 | */ | |
188 | div = R_A2W_PLLx_CHANNELy_DIV_MASK; | |
189 | } | |
190 | ||
191 | /* Some channels have an additional fixed divider */ | |
192 | freq = clock_get_hz(channel->pll_in) / (div * channel->fixed_divider); | |
193 | ||
194 | clock_update_hz(channel->out, freq); | |
09d56bbc LM |
195 | } |
196 | ||
197 | /* Update a PLL and all its channels */ | |
198 | static void pll_update_all_channels(BCM2835CprmanState *s, | |
199 | CprmanPllState *pll) | |
200 | { | |
201 | size_t i; | |
202 | ||
203 | pll_update(pll); | |
204 | ||
205 | for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { | |
206 | CprmanPllChannelState *channel = &s->channels[i]; | |
207 | if (channel->parent == pll->id) { | |
208 | pll_channel_update(channel); | |
209 | } | |
210 | } | |
211 | } | |
212 | ||
5ee0abed | 213 | static void pll_channel_pll_in_update(void *opaque, ClockEvent event) |
09d56bbc LM |
214 | { |
215 | pll_channel_update(CPRMAN_PLL_CHANNEL(opaque)); | |
216 | } | |
217 | ||
218 | static void pll_channel_init(Object *obj) | |
219 | { | |
220 | CprmanPllChannelState *s = CPRMAN_PLL_CHANNEL(obj); | |
221 | ||
222 | s->pll_in = qdev_init_clock_in(DEVICE(s), "pll-in", | |
5ee0abed PM |
223 | pll_channel_pll_in_update, s, |
224 | ClockUpdate); | |
09d56bbc LM |
225 | s->out = qdev_init_clock_out(DEVICE(s), "out"); |
226 | } | |
227 | ||
228 | static const VMStateDescription pll_channel_vmstate = { | |
229 | .name = TYPE_CPRMAN_PLL_CHANNEL, | |
230 | .version_id = 1, | |
231 | .minimum_version_id = 1, | |
232 | .fields = (VMStateField[]) { | |
233 | VMSTATE_CLOCK(pll_in, CprmanPllChannelState), | |
234 | VMSTATE_END_OF_LIST() | |
235 | } | |
236 | }; | |
237 | ||
238 | static void pll_channel_class_init(ObjectClass *klass, void *data) | |
239 | { | |
240 | DeviceClass *dc = DEVICE_CLASS(klass); | |
241 | ||
83ad4695 | 242 | dc->reset = pll_channel_reset; |
09d56bbc LM |
243 | dc->vmsd = &pll_channel_vmstate; |
244 | } | |
245 | ||
246 | static const TypeInfo cprman_pll_channel_info = { | |
247 | .name = TYPE_CPRMAN_PLL_CHANNEL, | |
248 | .parent = TYPE_DEVICE, | |
249 | .instance_size = sizeof(CprmanPllChannelState), | |
250 | .class_init = pll_channel_class_init, | |
251 | .instance_init = pll_channel_init, | |
252 | }; | |
253 | ||
254 | ||
72813624 LM |
255 | /* clock mux */ |
256 | ||
fc984085 LM |
257 | static bool clock_mux_is_enabled(CprmanClockMuxState *mux) |
258 | { | |
259 | return FIELD_EX32(*mux->reg_ctl, CM_CLOCKx_CTL, ENABLE); | |
260 | } | |
261 | ||
72813624 LM |
262 | static void clock_mux_update(CprmanClockMuxState *mux) |
263 | { | |
fc984085 LM |
264 | uint64_t freq; |
265 | uint32_t div, src = FIELD_EX32(*mux->reg_ctl, CM_CLOCKx_CTL, SRC); | |
266 | bool enabled = clock_mux_is_enabled(mux); | |
267 | ||
268 | *mux->reg_ctl = FIELD_DP32(*mux->reg_ctl, CM_CLOCKx_CTL, BUSY, enabled); | |
269 | ||
270 | if (!enabled) { | |
271 | clock_update(mux->out, 0); | |
272 | return; | |
273 | } | |
274 | ||
275 | freq = clock_get_hz(mux->srcs[src]); | |
276 | ||
277 | if (mux->int_bits == 0 && mux->frac_bits == 0) { | |
278 | clock_update_hz(mux->out, freq); | |
279 | return; | |
280 | } | |
281 | ||
282 | /* | |
283 | * The divider has an integer and a fractional part. The size of each part | |
284 | * varies with the muxes (int_bits and frac_bits). Both parts are | |
285 | * concatenated, with the integer part always starting at bit 12. | |
286 | * | |
287 | * 31 12 11 0 | |
288 | * ------------------------------ | |
289 | * CM_DIV | | int | frac | | | |
290 | * ------------------------------ | |
291 | * <-----> <------> | |
292 | * int_bits frac_bits | |
293 | */ | |
294 | div = extract32(*mux->reg_div, | |
295 | R_CM_CLOCKx_DIV_FRAC_LENGTH - mux->frac_bits, | |
296 | mux->int_bits + mux->frac_bits); | |
297 | ||
298 | if (!div) { | |
299 | clock_update(mux->out, 0); | |
300 | return; | |
301 | } | |
302 | ||
303 | freq = muldiv64(freq, 1 << mux->frac_bits, div); | |
304 | ||
305 | clock_update_hz(mux->out, freq); | |
72813624 LM |
306 | } |
307 | ||
5ee0abed | 308 | static void clock_mux_src_update(void *opaque, ClockEvent event) |
72813624 LM |
309 | { |
310 | CprmanClockMuxState **backref = opaque; | |
311 | CprmanClockMuxState *s = *backref; | |
fc984085 LM |
312 | CprmanClockMuxSource src = backref - s->backref; |
313 | ||
314 | if (FIELD_EX32(*s->reg_ctl, CM_CLOCKx_CTL, SRC) != src) { | |
315 | return; | |
316 | } | |
72813624 LM |
317 | |
318 | clock_mux_update(s); | |
319 | } | |
320 | ||
83ad4695 LM |
321 | static void clock_mux_reset(DeviceState *dev) |
322 | { | |
323 | CprmanClockMuxState *clock = CPRMAN_CLOCK_MUX(dev); | |
324 | const ClockMuxResetInfo *info = &CLOCK_MUX_RESET_INFO[clock->id]; | |
325 | ||
326 | *clock->reg_ctl = info->cm_ctl; | |
327 | *clock->reg_div = info->cm_div; | |
328 | } | |
329 | ||
72813624 LM |
330 | static void clock_mux_init(Object *obj) |
331 | { | |
332 | CprmanClockMuxState *s = CPRMAN_CLOCK_MUX(obj); | |
333 | size_t i; | |
334 | ||
335 | for (i = 0; i < CPRMAN_NUM_CLOCK_MUX_SRC; i++) { | |
336 | char *name = g_strdup_printf("srcs[%zu]", i); | |
337 | s->backref[i] = s; | |
338 | s->srcs[i] = qdev_init_clock_in(DEVICE(s), name, | |
339 | clock_mux_src_update, | |
5ee0abed PM |
340 | &s->backref[i], |
341 | ClockUpdate); | |
72813624 LM |
342 | g_free(name); |
343 | } | |
344 | ||
345 | s->out = qdev_init_clock_out(DEVICE(s), "out"); | |
346 | } | |
347 | ||
348 | static const VMStateDescription clock_mux_vmstate = { | |
349 | .name = TYPE_CPRMAN_CLOCK_MUX, | |
350 | .version_id = 1, | |
351 | .minimum_version_id = 1, | |
352 | .fields = (VMStateField[]) { | |
353 | VMSTATE_ARRAY_CLOCK(srcs, CprmanClockMuxState, | |
354 | CPRMAN_NUM_CLOCK_MUX_SRC), | |
355 | VMSTATE_END_OF_LIST() | |
356 | } | |
357 | }; | |
358 | ||
359 | static void clock_mux_class_init(ObjectClass *klass, void *data) | |
360 | { | |
361 | DeviceClass *dc = DEVICE_CLASS(klass); | |
362 | ||
83ad4695 | 363 | dc->reset = clock_mux_reset; |
72813624 LM |
364 | dc->vmsd = &clock_mux_vmstate; |
365 | } | |
366 | ||
367 | static const TypeInfo cprman_clock_mux_info = { | |
368 | .name = TYPE_CPRMAN_CLOCK_MUX, | |
369 | .parent = TYPE_DEVICE, | |
370 | .instance_size = sizeof(CprmanClockMuxState), | |
371 | .class_init = clock_mux_class_init, | |
372 | .instance_init = clock_mux_init, | |
373 | }; | |
374 | ||
375 | ||
502960ca LM |
376 | /* DSI0HSCK mux */ |
377 | ||
378 | static void dsi0hsck_mux_update(CprmanDsi0HsckMuxState *s) | |
379 | { | |
380 | bool src_is_plld = FIELD_EX32(*s->reg_cm, CM_DSI0HSCK, SELPLLD); | |
381 | Clock *src = src_is_plld ? s->plld_in : s->plla_in; | |
382 | ||
383 | clock_update(s->out, clock_get(src)); | |
384 | } | |
385 | ||
5ee0abed | 386 | static void dsi0hsck_mux_in_update(void *opaque, ClockEvent event) |
502960ca LM |
387 | { |
388 | dsi0hsck_mux_update(CPRMAN_DSI0HSCK_MUX(opaque)); | |
389 | } | |
390 | ||
391 | static void dsi0hsck_mux_init(Object *obj) | |
392 | { | |
393 | CprmanDsi0HsckMuxState *s = CPRMAN_DSI0HSCK_MUX(obj); | |
394 | DeviceState *dev = DEVICE(obj); | |
395 | ||
5ee0abed PM |
396 | s->plla_in = qdev_init_clock_in(dev, "plla-in", dsi0hsck_mux_in_update, |
397 | s, ClockUpdate); | |
398 | s->plld_in = qdev_init_clock_in(dev, "plld-in", dsi0hsck_mux_in_update, | |
399 | s, ClockUpdate); | |
502960ca LM |
400 | s->out = qdev_init_clock_out(DEVICE(s), "out"); |
401 | } | |
402 | ||
403 | static const VMStateDescription dsi0hsck_mux_vmstate = { | |
404 | .name = TYPE_CPRMAN_DSI0HSCK_MUX, | |
405 | .version_id = 1, | |
406 | .minimum_version_id = 1, | |
407 | .fields = (VMStateField[]) { | |
408 | VMSTATE_CLOCK(plla_in, CprmanDsi0HsckMuxState), | |
409 | VMSTATE_CLOCK(plld_in, CprmanDsi0HsckMuxState), | |
410 | VMSTATE_END_OF_LIST() | |
411 | } | |
412 | }; | |
413 | ||
414 | static void dsi0hsck_mux_class_init(ObjectClass *klass, void *data) | |
415 | { | |
416 | DeviceClass *dc = DEVICE_CLASS(klass); | |
417 | ||
418 | dc->vmsd = &dsi0hsck_mux_vmstate; | |
419 | } | |
420 | ||
421 | static const TypeInfo cprman_dsi0hsck_mux_info = { | |
422 | .name = TYPE_CPRMAN_DSI0HSCK_MUX, | |
423 | .parent = TYPE_DEVICE, | |
424 | .instance_size = sizeof(CprmanDsi0HsckMuxState), | |
425 | .class_init = dsi0hsck_mux_class_init, | |
426 | .instance_init = dsi0hsck_mux_init, | |
427 | }; | |
428 | ||
429 | ||
fc14176b LM |
430 | /* CPRMAN "top level" model */ |
431 | ||
6d2b874c LM |
432 | static uint32_t get_cm_lock(const BCM2835CprmanState *s) |
433 | { | |
434 | static const int CM_LOCK_MAPPING[CPRMAN_NUM_PLL] = { | |
435 | [CPRMAN_PLLA] = R_CM_LOCK_FLOCKA_SHIFT, | |
436 | [CPRMAN_PLLC] = R_CM_LOCK_FLOCKC_SHIFT, | |
437 | [CPRMAN_PLLD] = R_CM_LOCK_FLOCKD_SHIFT, | |
438 | [CPRMAN_PLLH] = R_CM_LOCK_FLOCKH_SHIFT, | |
439 | [CPRMAN_PLLB] = R_CM_LOCK_FLOCKB_SHIFT, | |
440 | }; | |
441 | ||
442 | uint32_t r = 0; | |
443 | size_t i; | |
444 | ||
445 | for (i = 0; i < CPRMAN_NUM_PLL; i++) { | |
446 | r |= pll_is_locked(&s->plls[i]) << CM_LOCK_MAPPING[i]; | |
447 | } | |
448 | ||
449 | return r; | |
450 | } | |
451 | ||
fc14176b LM |
452 | static uint64_t cprman_read(void *opaque, hwaddr offset, |
453 | unsigned size) | |
454 | { | |
455 | BCM2835CprmanState *s = CPRMAN(opaque); | |
456 | uint64_t r = 0; | |
457 | size_t idx = offset / sizeof(uint32_t); | |
458 | ||
459 | switch (idx) { | |
6d2b874c LM |
460 | case R_CM_LOCK: |
461 | r = get_cm_lock(s); | |
462 | break; | |
463 | ||
fc14176b LM |
464 | default: |
465 | r = s->regs[idx]; | |
466 | } | |
467 | ||
468 | trace_bcm2835_cprman_read(offset, r); | |
469 | return r; | |
470 | } | |
471 | ||
09d56bbc LM |
472 | static inline void update_pll_and_channels_from_cm(BCM2835CprmanState *s, |
473 | size_t idx) | |
474 | { | |
475 | size_t i; | |
476 | ||
477 | for (i = 0; i < CPRMAN_NUM_PLL; i++) { | |
478 | if (PLL_INIT_INFO[i].cm_offset == idx) { | |
479 | pll_update_all_channels(s, &s->plls[i]); | |
480 | return; | |
481 | } | |
482 | } | |
483 | } | |
484 | ||
485 | static inline void update_channel_from_a2w(BCM2835CprmanState *s, size_t idx) | |
486 | { | |
487 | size_t i; | |
488 | ||
489 | for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { | |
490 | if (PLL_CHANNEL_INIT_INFO[i].a2w_ctrl_offset == idx) { | |
491 | pll_channel_update(&s->channels[i]); | |
492 | return; | |
493 | } | |
494 | } | |
495 | } | |
496 | ||
72813624 LM |
497 | static inline void update_mux_from_cm(BCM2835CprmanState *s, size_t idx) |
498 | { | |
499 | size_t i; | |
500 | ||
501 | for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) { | |
502 | if ((CLOCK_MUX_INIT_INFO[i].cm_offset == idx) || | |
503 | (CLOCK_MUX_INIT_INFO[i].cm_offset + 4 == idx)) { | |
504 | /* matches CM_CTL or CM_DIV mux register */ | |
505 | clock_mux_update(&s->clock_muxes[i]); | |
506 | return; | |
507 | } | |
508 | } | |
509 | } | |
510 | ||
09d56bbc | 511 | #define CASE_PLL_A2W_REGS(pll_) \ |
1e986e25 LM |
512 | case R_A2W_ ## pll_ ## _CTRL: \ |
513 | case R_A2W_ ## pll_ ## _ANA0: \ | |
514 | case R_A2W_ ## pll_ ## _ANA1: \ | |
515 | case R_A2W_ ## pll_ ## _ANA2: \ | |
516 | case R_A2W_ ## pll_ ## _ANA3: \ | |
517 | case R_A2W_ ## pll_ ## _FRAC | |
518 | ||
fc14176b LM |
519 | static void cprman_write(void *opaque, hwaddr offset, |
520 | uint64_t value, unsigned size) | |
521 | { | |
522 | BCM2835CprmanState *s = CPRMAN(opaque); | |
523 | size_t idx = offset / sizeof(uint32_t); | |
524 | ||
525 | if (FIELD_EX32(value, CPRMAN, PASSWORD) != CPRMAN_PASSWORD) { | |
526 | trace_bcm2835_cprman_write_invalid_magic(offset, value); | |
527 | return; | |
528 | } | |
529 | ||
530 | value &= ~R_CPRMAN_PASSWORD_MASK; | |
531 | ||
532 | trace_bcm2835_cprman_write(offset, value); | |
533 | s->regs[idx] = value; | |
534 | ||
1e986e25 | 535 | switch (idx) { |
09d56bbc LM |
536 | case R_CM_PLLA ... R_CM_PLLH: |
537 | case R_CM_PLLB: | |
538 | /* | |
539 | * A given CM_PLLx register is shared by both the PLL and the channels | |
540 | * of this PLL. | |
541 | */ | |
542 | update_pll_and_channels_from_cm(s, idx); | |
543 | break; | |
544 | ||
545 | CASE_PLL_A2W_REGS(PLLA) : | |
1e986e25 LM |
546 | pll_update(&s->plls[CPRMAN_PLLA]); |
547 | break; | |
548 | ||
09d56bbc | 549 | CASE_PLL_A2W_REGS(PLLC) : |
1e986e25 LM |
550 | pll_update(&s->plls[CPRMAN_PLLC]); |
551 | break; | |
552 | ||
09d56bbc | 553 | CASE_PLL_A2W_REGS(PLLD) : |
1e986e25 LM |
554 | pll_update(&s->plls[CPRMAN_PLLD]); |
555 | break; | |
556 | ||
09d56bbc | 557 | CASE_PLL_A2W_REGS(PLLH) : |
1e986e25 LM |
558 | pll_update(&s->plls[CPRMAN_PLLH]); |
559 | break; | |
560 | ||
09d56bbc | 561 | CASE_PLL_A2W_REGS(PLLB) : |
1e986e25 LM |
562 | pll_update(&s->plls[CPRMAN_PLLB]); |
563 | break; | |
09d56bbc LM |
564 | |
565 | case R_A2W_PLLA_DSI0: | |
566 | case R_A2W_PLLA_CORE: | |
567 | case R_A2W_PLLA_PER: | |
568 | case R_A2W_PLLA_CCP2: | |
569 | case R_A2W_PLLC_CORE2: | |
570 | case R_A2W_PLLC_CORE1: | |
571 | case R_A2W_PLLC_PER: | |
572 | case R_A2W_PLLC_CORE0: | |
573 | case R_A2W_PLLD_DSI0: | |
574 | case R_A2W_PLLD_CORE: | |
575 | case R_A2W_PLLD_PER: | |
576 | case R_A2W_PLLD_DSI1: | |
577 | case R_A2W_PLLH_AUX: | |
578 | case R_A2W_PLLH_RCAL: | |
579 | case R_A2W_PLLH_PIX: | |
580 | case R_A2W_PLLB_ARM: | |
581 | update_channel_from_a2w(s, idx); | |
582 | break; | |
72813624 LM |
583 | |
584 | case R_CM_GNRICCTL ... R_CM_SMIDIV: | |
585 | case R_CM_TCNTCNT ... R_CM_VECDIV: | |
586 | case R_CM_PULSECTL ... R_CM_PULSEDIV: | |
587 | case R_CM_SDCCTL ... R_CM_ARMCTL: | |
588 | case R_CM_AVEOCTL ... R_CM_EMMCDIV: | |
589 | case R_CM_EMMC2CTL ... R_CM_EMMC2DIV: | |
590 | update_mux_from_cm(s, idx); | |
591 | break; | |
502960ca LM |
592 | |
593 | case R_CM_DSI0HSCK: | |
594 | dsi0hsck_mux_update(&s->dsi0hsck_mux); | |
595 | break; | |
1e986e25 | 596 | } |
fc14176b LM |
597 | } |
598 | ||
09d56bbc | 599 | #undef CASE_PLL_A2W_REGS |
1e986e25 | 600 | |
fc14176b LM |
601 | static const MemoryRegionOps cprman_ops = { |
602 | .read = cprman_read, | |
603 | .write = cprman_write, | |
604 | .endianness = DEVICE_LITTLE_ENDIAN, | |
605 | .valid = { | |
606 | /* | |
607 | * Although this hasn't been checked against real hardware, nor the | |
608 | * information can be found in a datasheet, it seems reasonable because | |
609 | * of the "PASSWORD" magic value found in every registers. | |
610 | */ | |
611 | .min_access_size = 4, | |
612 | .max_access_size = 4, | |
613 | .unaligned = false, | |
614 | }, | |
615 | .impl = { | |
616 | .max_access_size = 4, | |
617 | }, | |
618 | }; | |
619 | ||
620 | static void cprman_reset(DeviceState *dev) | |
621 | { | |
622 | BCM2835CprmanState *s = CPRMAN(dev); | |
1e986e25 | 623 | size_t i; |
fc14176b LM |
624 | |
625 | memset(s->regs, 0, sizeof(s->regs)); | |
626 | ||
1e986e25 LM |
627 | for (i = 0; i < CPRMAN_NUM_PLL; i++) { |
628 | device_cold_reset(DEVICE(&s->plls[i])); | |
629 | } | |
630 | ||
09d56bbc LM |
631 | for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { |
632 | device_cold_reset(DEVICE(&s->channels[i])); | |
633 | } | |
634 | ||
502960ca LM |
635 | device_cold_reset(DEVICE(&s->dsi0hsck_mux)); |
636 | ||
72813624 LM |
637 | for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) { |
638 | device_cold_reset(DEVICE(&s->clock_muxes[i])); | |
639 | } | |
640 | ||
fc14176b LM |
641 | clock_update_hz(s->xosc, s->xosc_freq); |
642 | } | |
643 | ||
644 | static void cprman_init(Object *obj) | |
645 | { | |
646 | BCM2835CprmanState *s = CPRMAN(obj); | |
1e986e25 LM |
647 | size_t i; |
648 | ||
649 | for (i = 0; i < CPRMAN_NUM_PLL; i++) { | |
650 | object_initialize_child(obj, PLL_INIT_INFO[i].name, | |
651 | &s->plls[i], TYPE_CPRMAN_PLL); | |
652 | set_pll_init_info(s, &s->plls[i], i); | |
653 | } | |
fc14176b | 654 | |
09d56bbc LM |
655 | for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { |
656 | object_initialize_child(obj, PLL_CHANNEL_INIT_INFO[i].name, | |
657 | &s->channels[i], | |
658 | TYPE_CPRMAN_PLL_CHANNEL); | |
659 | set_pll_channel_init_info(s, &s->channels[i], i); | |
660 | } | |
661 | ||
502960ca LM |
662 | object_initialize_child(obj, "dsi0hsck-mux", |
663 | &s->dsi0hsck_mux, TYPE_CPRMAN_DSI0HSCK_MUX); | |
664 | s->dsi0hsck_mux.reg_cm = &s->regs[R_CM_DSI0HSCK]; | |
665 | ||
72813624 LM |
666 | for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) { |
667 | char *alias; | |
668 | ||
669 | object_initialize_child(obj, CLOCK_MUX_INIT_INFO[i].name, | |
670 | &s->clock_muxes[i], | |
671 | TYPE_CPRMAN_CLOCK_MUX); | |
672 | set_clock_mux_init_info(s, &s->clock_muxes[i], i); | |
673 | ||
674 | /* Expose muxes output as CPRMAN outputs */ | |
675 | alias = g_strdup_printf("%s-out", CLOCK_MUX_INIT_INFO[i].name); | |
676 | qdev_alias_clock(DEVICE(&s->clock_muxes[i]), "out", DEVICE(obj), alias); | |
677 | g_free(alias); | |
678 | } | |
679 | ||
fc14176b | 680 | s->xosc = clock_new(obj, "xosc"); |
72813624 LM |
681 | s->gnd = clock_new(obj, "gnd"); |
682 | ||
683 | clock_set(s->gnd, 0); | |
fc14176b LM |
684 | |
685 | memory_region_init_io(&s->iomem, obj, &cprman_ops, | |
686 | s, "bcm2835-cprman", 0x2000); | |
687 | sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); | |
688 | } | |
689 | ||
72813624 LM |
690 | static void connect_mux_sources(BCM2835CprmanState *s, |
691 | CprmanClockMuxState *mux, | |
692 | const CprmanPllChannel *clk_mapping) | |
693 | { | |
694 | size_t i; | |
695 | Clock *td0 = s->clock_muxes[CPRMAN_CLOCK_TD0].out; | |
696 | Clock *td1 = s->clock_muxes[CPRMAN_CLOCK_TD1].out; | |
697 | ||
698 | /* For sources from 0 to 3. Source 4 to 9 are mux specific */ | |
699 | Clock * const CLK_SRC_MAPPING[] = { | |
700 | [CPRMAN_CLOCK_SRC_GND] = s->gnd, | |
701 | [CPRMAN_CLOCK_SRC_XOSC] = s->xosc, | |
702 | [CPRMAN_CLOCK_SRC_TD0] = td0, | |
703 | [CPRMAN_CLOCK_SRC_TD1] = td1, | |
704 | }; | |
705 | ||
706 | for (i = 0; i < CPRMAN_NUM_CLOCK_MUX_SRC; i++) { | |
707 | CprmanPllChannel mapping = clk_mapping[i]; | |
708 | Clock *src; | |
709 | ||
710 | if (mapping == CPRMAN_CLOCK_SRC_FORCE_GROUND) { | |
711 | src = s->gnd; | |
712 | } else if (mapping == CPRMAN_CLOCK_SRC_DSI0HSCK) { | |
502960ca | 713 | src = s->dsi0hsck_mux.out; |
72813624 LM |
714 | } else if (i < CPRMAN_CLOCK_SRC_PLLA) { |
715 | src = CLK_SRC_MAPPING[i]; | |
716 | } else { | |
717 | src = s->channels[mapping].out; | |
718 | } | |
719 | ||
720 | clock_set_source(mux->srcs[i], src); | |
721 | } | |
722 | } | |
723 | ||
1e986e25 LM |
724 | static void cprman_realize(DeviceState *dev, Error **errp) |
725 | { | |
726 | BCM2835CprmanState *s = CPRMAN(dev); | |
727 | size_t i; | |
728 | ||
729 | for (i = 0; i < CPRMAN_NUM_PLL; i++) { | |
730 | CprmanPllState *pll = &s->plls[i]; | |
731 | ||
732 | clock_set_source(pll->xosc_in, s->xosc); | |
733 | ||
734 | if (!qdev_realize(DEVICE(pll), NULL, errp)) { | |
735 | return; | |
736 | } | |
737 | } | |
09d56bbc LM |
738 | |
739 | for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) { | |
740 | CprmanPllChannelState *channel = &s->channels[i]; | |
741 | CprmanPll parent = PLL_CHANNEL_INIT_INFO[i].parent; | |
742 | Clock *parent_clk = s->plls[parent].out; | |
743 | ||
744 | clock_set_source(channel->pll_in, parent_clk); | |
745 | ||
746 | if (!qdev_realize(DEVICE(channel), NULL, errp)) { | |
747 | return; | |
748 | } | |
749 | } | |
72813624 | 750 | |
502960ca LM |
751 | clock_set_source(s->dsi0hsck_mux.plla_in, |
752 | s->channels[CPRMAN_PLLA_CHANNEL_DSI0].out); | |
753 | clock_set_source(s->dsi0hsck_mux.plld_in, | |
754 | s->channels[CPRMAN_PLLD_CHANNEL_DSI0].out); | |
755 | ||
756 | if (!qdev_realize(DEVICE(&s->dsi0hsck_mux), NULL, errp)) { | |
757 | return; | |
758 | } | |
759 | ||
72813624 LM |
760 | for (i = 0; i < CPRMAN_NUM_CLOCK_MUX; i++) { |
761 | CprmanClockMuxState *clock_mux = &s->clock_muxes[i]; | |
762 | ||
763 | connect_mux_sources(s, clock_mux, CLOCK_MUX_INIT_INFO[i].src_mapping); | |
764 | ||
765 | if (!qdev_realize(DEVICE(clock_mux), NULL, errp)) { | |
766 | return; | |
767 | } | |
768 | } | |
1e986e25 LM |
769 | } |
770 | ||
fc14176b LM |
771 | static const VMStateDescription cprman_vmstate = { |
772 | .name = TYPE_BCM2835_CPRMAN, | |
773 | .version_id = 1, | |
774 | .minimum_version_id = 1, | |
775 | .fields = (VMStateField[]) { | |
776 | VMSTATE_UINT32_ARRAY(regs, BCM2835CprmanState, CPRMAN_NUM_REGS), | |
777 | VMSTATE_END_OF_LIST() | |
778 | } | |
779 | }; | |
780 | ||
781 | static Property cprman_properties[] = { | |
782 | DEFINE_PROP_UINT32("xosc-freq-hz", BCM2835CprmanState, xosc_freq, 19200000), | |
783 | DEFINE_PROP_END_OF_LIST() | |
784 | }; | |
785 | ||
786 | static void cprman_class_init(ObjectClass *klass, void *data) | |
787 | { | |
788 | DeviceClass *dc = DEVICE_CLASS(klass); | |
789 | ||
1e986e25 | 790 | dc->realize = cprman_realize; |
fc14176b LM |
791 | dc->reset = cprman_reset; |
792 | dc->vmsd = &cprman_vmstate; | |
793 | device_class_set_props(dc, cprman_properties); | |
794 | } | |
795 | ||
796 | static const TypeInfo cprman_info = { | |
797 | .name = TYPE_BCM2835_CPRMAN, | |
798 | .parent = TYPE_SYS_BUS_DEVICE, | |
799 | .instance_size = sizeof(BCM2835CprmanState), | |
800 | .class_init = cprman_class_init, | |
801 | .instance_init = cprman_init, | |
802 | }; | |
803 | ||
804 | static void cprman_register_types(void) | |
805 | { | |
806 | type_register_static(&cprman_info); | |
1e986e25 | 807 | type_register_static(&cprman_pll_info); |
09d56bbc | 808 | type_register_static(&cprman_pll_channel_info); |
72813624 | 809 | type_register_static(&cprman_clock_mux_info); |
502960ca | 810 | type_register_static(&cprman_dsi0hsck_mux_info); |
fc14176b LM |
811 | } |
812 | ||
813 | type_init(cprman_register_types); |