1 /*
2 * Copyright (c) 2021 HPMicro
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 *
6 */
7
8 #ifndef HPM_PLLCTL_DRV_H
9 #define HPM_PLLCTL_DRV_H
10 #include "hpm_common.h"
11 #include "hpm_soc_feature.h"
12 #include "hpm_pllctl_regs.h"
13
14 /**
15 *
16 * @brief PLLCTL driver APIs
17 * @defgroup pllctl_interface PLLCTL driver APIs
18 * @{
19 */
20
21 #define PLLCTL_PLL_VCO_FREQ_MIN (375000000U)
22 #define PLLCTL_PLL_VCO_FREQ_MAX (2200000000U)
23
24 /*
25 * @brief PLL parts with lock
26 */
27 #define PLLCTL_PLL_LOCK_SS_RESET PLLCTL_PLL_LOCK_LOCK_SS_RSTPTR_MASK
28 #define PLLCTL_PLL_LOCK_REFDIV PLLCTL_PLL_LOCK_LOCK_REFDIV_MASK
29 #define PLLCTL_PLL_LOCK_POSTDIV1 PLLCTL_PLL_LOCK_LOCK_POSTDIV1_MASK
30 #define PLLCTL_PLL_LOCK_SS_SPREAD PLLCTL_PLL_LOCK_LOCK_SS_SPREAD_MASK
31 #define PLLCTL_PLL_LOCK_SS_DIVVAL PLLCTL_PLL_LOCK_LOCK_SS_DIVVAL_MASK
32 #define PLLCTL_PLL_LOCK_ALL (PLLCTL_PLL_LOCK_LOCK_SS_RSTPTR_MASK \
33 | PLLCTL_PLL_LOCK_LOCK_REFDIV_MASK \
34 | PLLCTL_PLL_LOCK_LOCK_POSTDIV1_MASK \
35 | PLLCTL_PLL_LOCK_LOCK_SS_SPREAD_MASK \
36 | PLLCTL_PLL_LOCK_LOCK_SS_DIVVAL_MASK \
37 | PLLCTL_PLL_LOCK_LOCK_SS_DIVVAL_MASK)
38
39 /*
40 * @brief PLLCTL specific status
41 */
42 enum {
43 status_pllctl_not_enabled = MAKE_STATUS(status_group_pllctl, 1),
44 status_pllctl_out_of_range = MAKE_STATUS(status_group_pllctl, 2),
45 };
46
47 #ifdef __cplusplus
48 extern "C" {
49 #endif
50
51 /**
52 * @brief Unlock pll
53 *
54 * @param[in] ptr PLLCTL base address
55 * @param[in] pll Target PLL index
56 * @param[in] lock_mask Mask of PLL parts to be unlocked
57 */
pllctl_pll_unlock(PLLCTL_Type * ptr,uint8_t pll,uint32_t lock_mask)58 static inline void pllctl_pll_unlock(PLLCTL_Type *ptr, uint8_t pll, uint32_t lock_mask)
59 {
60 ptr->PLL[pll].LOCK &= ~lock_mask;
61 }
62
63 /**
64 * @brief Lock pll
65 *
66 * @param[in] ptr PLLCTL base address
67 * @param[in] pll Target PLL index
68 * @param[in] lock_mask Mask of PLL parts to be locked
69 */
70
pllctl_pll_lock(PLLCTL_Type * ptr,uint8_t pll,uint32_t lock_mask)71 static inline void pllctl_pll_lock(PLLCTL_Type *ptr, uint8_t pll, uint32_t lock_mask)
72 {
73 ptr->PLL[pll].LOCK = lock_mask;
74 }
75
76 /**
77 * @brief Disable spread spectrum
78 *
79 * @param[in] ptr PLLCTL base address
80 * @param[in] pll Target PLL index
81 *
82 * @return status_success if everything is okay
83 */
pllctl_pll_ss_disable(PLLCTL_Type * ptr,uint8_t pll)84 static inline hpm_stat_t pllctl_pll_ss_disable(PLLCTL_Type *ptr, uint8_t pll)
85 {
86 if (pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1)) {
87 return status_invalid_argument;
88 }
89
90 ptr->PLL[pll].CFG0 |= (PLLCTL_PLL_CFG0_SS_RSTPTR_MASK
91 | PLLCTL_PLL_CFG0_SS_RESET_MASK);
92 ptr->PLL[pll].CFG0 |= PLLCTL_PLL_CFG0_SS_DISABLE_SSCG_MASK;
93 return status_success;
94 }
95
96 /**
97 * @brief Power down target PLL
98 *
99 * @param[in] ptr PLLCTL base address
100 * @param[in] pll Target PLL index
101 *
102 * @return status_success if everything is okay
103 */
pllctl_pll_powerdown(PLLCTL_Type * ptr,uint8_t pll)104 static inline hpm_stat_t pllctl_pll_powerdown(PLLCTL_Type *ptr, uint8_t pll)
105 {
106 if (pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1)) {
107 return status_invalid_argument;
108 }
109
110 ptr->PLL[pll].CFG1 = (ptr->PLL[pll].CFG1 &
111 ~(PLLCTL_PLL_CFG1_PLLCTRL_HW_EN_MASK | PLLCTL_PLL_CFG1_CLKEN_SW_MASK))
112 | PLLCTL_PLL_CFG1_PLLPD_SW_MASK;
113 return status_success;
114 }
115
116 /**
117 * @brief Power on target PLL
118 *
119 * @param[in] ptr PLLCTL base address
120 * @param[in] pll Target PLL index
121 *
122 * @return status_success if everything is okay
123 */
pllctl_pll_poweron(PLLCTL_Type * ptr,uint8_t pll)124 static inline hpm_stat_t pllctl_pll_poweron(PLLCTL_Type *ptr, uint8_t pll)
125 {
126 uint32_t cfg;
127 if (pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1)) {
128 return status_invalid_argument;
129 }
130
131 cfg = ptr->PLL[pll].CFG1;
132 if (!(cfg & PLLCTL_PLL_CFG1_PLLPD_SW_MASK)) {
133 return status_success;
134 }
135
136 if (cfg & PLLCTL_PLL_CFG1_PLLCTRL_HW_EN_MASK) {
137 ptr->PLL[pll].CFG1 &= ~PLLCTL_PLL_CFG1_PLLCTRL_HW_EN_MASK;
138 }
139
140 ptr->PLL[pll].CFG1 &= ~PLLCTL_PLL_CFG1_PLLPD_SW_MASK;
141
142 /*
143 * put back to hardware mode
144 */
145 ptr->PLL[pll].CFG1 |= PLLCTL_PLL_CFG1_PLLCTRL_HW_EN_MASK;
146 return status_success;
147 }
148
149 /**
150 * @brief Enable spread spectrum mode
151 *
152 * @param[in] ptr PLLCTL base address
153 * @param[in] pll Target PLL index
154 * @param[in] spread Spread spectrum depth (1-31, from 0.1% to 3.1%)
155 * @param[in] div Spread spectrum divider (1-63, divide by 1 to 63)
156 * @param[in] down_spread Set true if need down-spread, otherwise center-spread
157 *
158 * @return status_success if everything is okay
159 */
pllctl_pll_ss_enable(PLLCTL_Type * ptr,uint8_t pll,uint8_t spread,uint8_t div,bool down_spread)160 static inline hpm_stat_t pllctl_pll_ss_enable(PLLCTL_Type *ptr, uint8_t pll,
161 uint8_t spread, uint8_t div,
162 bool down_spread)
163 {
164 if ((pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1))
165 || (spread > (PLLCTL_PLL_CFG0_SS_SPREAD_MASK >> PLLCTL_PLL_CFG0_SS_SPREAD_SHIFT))
166 || (div > (PLLCTL_PLL_CFG0_SS_DIVVAL_MASK >> PLLCTL_PLL_CFG0_SS_DIVVAL_SHIFT))) {
167 return status_invalid_argument;
168 }
169 if (!(ptr->PLL[pll].CFG1 & PLLCTL_PLL_CFG1_PLLPD_SW_MASK)) {
170 pllctl_pll_powerdown(ptr, pll);
171 }
172
173 ptr->PLL[pll].CFG0 &= ~(PLLCTL_PLL_CFG0_SS_RSTPTR_MASK
174 | PLLCTL_PLL_CFG0_SS_RESET_MASK);
175 ptr->PLL[pll].CFG0 &= ~PLLCTL_PLL_CFG0_SS_DISABLE_SSCG_MASK;
176 ptr->PLL[pll].CFG0 = (ptr->PLL[pll].CFG0
177 & ~(PLLCTL_PLL_CFG0_SS_SPREAD_MASK | PLLCTL_PLL_CFG0_SS_DIVVAL_MASK))
178 | PLLCTL_PLL_CFG0_SS_SPREAD_SET(spread)
179 | PLLCTL_PLL_CFG0_SS_DIVVAL_SET(div)
180 | PLLCTL_PLL_CFG0_SS_DOWNSPREAD_SET(down_spread);
181
182 pllctl_pll_poweron(ptr, pll);
183 return status_success;
184 }
185
186 /**
187 * @brief Set postdiv1 for PLL
188 *
189 * @param[in] ptr PLLCTL base address
190 * @param[in] pll Target PLL index
191 * @param[in] div Postdiv1 value (0x1~0x7)
192 *
193 * @return status_success if everything is okay
194 */
pllctl_set_postdiv1(PLLCTL_Type * ptr,uint8_t pll,uint8_t div)195 static inline hpm_stat_t pllctl_set_postdiv1(PLLCTL_Type *ptr, uint8_t pll, uint8_t div)
196 {
197 if ((pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1))
198 || (!div)
199 || ((div) > (PLLCTL_PLL_CFG0_POSTDIV1_MASK >> PLLCTL_PLL_CFG0_POSTDIV1_SHIFT))) {
200 return status_invalid_argument;
201 }
202
203 ptr->PLL[pll].CFG0 = ((ptr->PLL[pll].CFG0 & ~(PLLCTL_PLL_CFG0_POSTDIV1_MASK))) | PLLCTL_PLL_CFG0_POSTDIV1_SET(div);
204 return status_success;
205 }
206
207 /**
208 * @brief Set fbdiv for PLL integer mode
209 *
210 * Fout = Fref/refdiv * fbdiv / postdiv1
211 *
212 * @param[in] ptr PLLCTL base address
213 * @param[in] pll Target PLL index
214 * @param[in] fbdiv Fbdiv value (0x1~0x1000)
215 * @note fbdiv value can not set too large, if Fref/refdiv * fbdiv > 2GHz, it might cause irrecoverable damage to that PLL
216 *
217 * @return status_success if everything is okay
218 */
pllctl_set_fbdiv_int(PLLCTL_Type * ptr,uint8_t pll,uint16_t fbdiv)219 static inline hpm_stat_t pllctl_set_fbdiv_int(PLLCTL_Type *ptr, uint8_t pll, uint16_t fbdiv)
220 {
221 if ((pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1))
222 || ((fbdiv - 1) > (PLLCTL_PLL_CFG2_FBDIV_INT_MASK >> PLLCTL_PLL_CFG2_FBDIV_INT_SHIFT))) {
223 return status_invalid_argument;
224 }
225
226 ptr->PLL[pll].CFG2 = ((ptr->PLL[pll].CFG2 & ~(PLLCTL_PLL_CFG2_FBDIV_INT_MASK))) | PLLCTL_PLL_CFG2_FBDIV_INT_SET(fbdiv - 1);
227 return status_success;
228 }
229
230 /**
231 * @brief Set fbdiv for PLL fraction mode
232 *
233 * Fout = Fref/refdive * (fbdiv + frac/2^24)/postdiv1
234 *
235 * @param[in] ptr PLLCTL base address
236 * @param[in] pll Target PLL index
237 * @param[in] fbdiv Fbdiv value (0x1~0x1000)
238 * @note fbdiv value can not set too large, if Fref/refdiv * fbdiv > 2GHz, it might cause irrecoverable damage to that PLL
239 *
240 * @return status_success if everything is okay
241 */
pllctl_set_fbdiv_frac(PLLCTL_Type * ptr,uint8_t pll,uint16_t fbdiv)242 static inline hpm_stat_t pllctl_set_fbdiv_frac(PLLCTL_Type *ptr, uint8_t pll, uint16_t fbdiv)
243 {
244 if ((pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1))
245 || ((fbdiv - 1) > (PLLCTL_PLL_FREQ_FBDIV_FRAC_MASK >> PLLCTL_PLL_FREQ_FBDIV_FRAC_SHIFT))) {
246 return status_invalid_argument;
247 }
248
249 ptr->PLL[pll].FREQ = (ptr->PLL[pll].FREQ & ~(PLLCTL_PLL_FREQ_FBDIV_FRAC_MASK))
250 | PLLCTL_PLL_FREQ_FBDIV_FRAC_SET(fbdiv - 1);
251 return status_success;
252 }
253
254 /**
255 * @brief Set fraction for PLL fraction mode
256 *
257 * @param[in] ptr PLLCTL base address
258 * @param[in] pll Target PLL index
259 * @param[in] frac 24-bit fixed float point value
260 *
261 * @return
262 */
pllctl_set_frac(PLLCTL_Type * ptr,uint8_t pll,uint32_t frac)263 static inline hpm_stat_t pllctl_set_frac(PLLCTL_Type *ptr, uint8_t pll, uint32_t frac)
264 {
265 if ((pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1))
266 || (frac > (PLLCTL_PLL_FREQ_FRAC_MASK >> PLLCTL_PLL_FREQ_FRAC_SHIFT))) {
267 return status_invalid_argument;
268 }
269 ptr->PLL[pll].FREQ = (ptr->PLL[pll].FREQ & ~(PLLCTL_PLL_FREQ_FRAC_MASK))
270 | PLLCTL_PLL_FREQ_FRAC_SET(frac);
271 return status_success;
272 }
273
274 /**
275 * @brief Get PLL divx value
276 *
277 * @param[in] ptr PLLCTL base address
278 * @param[in] pll Target PLL index
279 * @param[in] div_index Target DIV to query
280 *
281 * @return Divider value of target DIV
282 */
pllctl_get_div(PLLCTL_Type * ptr,uint8_t pll,uint8_t div_index)283 static inline hpm_stat_t pllctl_get_div(PLLCTL_Type *ptr, uint8_t pll, uint8_t div_index)
284 {
285 if ((pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1))
286 || !(PLLCTL_SOC_PLL_HAS_DIV0(pll))) {
287 return status_invalid_argument;
288 }
289 if (div_index) {
290 return PLLCTL_PLL_DIV0_DIV_GET(ptr->PLL[pll].DIV1) + 1;
291 } else {
292 return PLLCTL_PLL_DIV0_DIV_GET(ptr->PLL[pll].DIV0) + 1;
293 }
294 }
295
296 /**
297 * @brief Set divider
298 *
299 * @param[in] ptr PLLCTL base address
300 * @param[in] pll Target PLL index
301 * @param[in] div_index DIV index
302 * @param[in] div Divider value (starting from 1)
303 *
304 * @return status_success if everything is okay
305 */
pllctl_set_div(PLLCTL_Type * ptr,uint8_t pll,uint8_t div_index,uint16_t div)306 static inline hpm_stat_t pllctl_set_div(PLLCTL_Type *ptr, uint8_t pll, uint8_t div_index, uint16_t div)
307 {
308 if ((pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1))
309 || !(PLLCTL_SOC_PLL_HAS_DIV0(pll))
310 || ((div - 1) > (PLLCTL_PLL_DIV0_DIV_MASK >> PLLCTL_PLL_DIV0_DIV_SHIFT))) {
311 return status_invalid_argument;
312 }
313
314 if (div_index) {
315 ptr->PLL[pll].DIV1 = (ptr->PLL[pll].DIV1 & ~(PLLCTL_PLL_DIV1_DIV_MASK))
316 | PLLCTL_PLL_DIV1_DIV_SET(div - 1);
317 } else {
318 ptr->PLL[pll].DIV0 = (ptr->PLL[pll].DIV0 & ~(PLLCTL_PLL_DIV0_DIV_MASK))
319 | PLLCTL_PLL_DIV0_DIV_SET(div - 1);
320 }
321 return status_success;
322 }
323
324 /**
325 * @brief Check if specific PLL DIV is stable
326 *
327 * @param[in] ptr PLLCTL base address
328 * @param[in] pll Target PLL index
329 * @param[in] div_index Target DIV to check
330 *
331 * @return true if target PLL DIV is stable
332 */
pllctl_div_is_stable(PLLCTL_Type * ptr,uint8_t pll,uint8_t div_index)333 static inline bool pllctl_div_is_stable(PLLCTL_Type *ptr, uint8_t pll, uint8_t div_index)
334 {
335 if ((pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1)) || !(PLLCTL_SOC_PLL_HAS_DIV0(pll))) {
336 return false;
337 }
338 if (div_index) {
339 return ptr->PLL[pll].DIV1 & PLLCTL_PLL_DIV0_RESPONSE_MASK;
340 } else {
341 return ptr->PLL[pll].DIV0 & PLLCTL_PLL_DIV0_RESPONSE_MASK;
342 }
343 }
344
345 /**
346 * @brief Check if target PLL is enabled
347 *
348 * @param[in] ptr PLLCTL base address
349 * @param[in] pll Target PLL index
350 *
351 * @return true if target PLL is enabled
352 */
pllctl_pll_is_enabled(PLLCTL_Type * ptr,uint8_t pll)353 static inline bool pllctl_pll_is_enabled(PLLCTL_Type *ptr, uint8_t pll)
354 {
355 return (ptr->PLL[pll].STATUS & PLLCTL_PLL_STATUS_ENABLE_MASK);
356 }
357
358 /**
359 * @brief Check if XTAL is stable
360 *
361 * @param[in] ptr PLLCTL base address
362 *
363 * @return true if XTAL is stable
364 */
pllctl_xtal_is_stable(PLLCTL_Type * ptr)365 static inline bool pllctl_xtal_is_stable(PLLCTL_Type *ptr)
366 {
367 return ptr->XTAL & PLLCTL_XTAL_RESPONSE_MASK;
368 }
369
370 /**
371 * @brief Check if XTAL is enabled
372 *
373 * @param[in] ptr PLLCTL base address
374 *
375 * @return true if XTAL is enabled
376 */
pllctl_xtal_is_enabled(PLLCTL_Type * ptr)377 static inline bool pllctl_xtal_is_enabled(PLLCTL_Type *ptr)
378 {
379 return ptr->XTAL & PLLCTL_XTAL_ENABLE_MASK;
380 }
381
382 /*
383 * @brief set XTAL rampup time in cycles of IRC24M
384 *
385 * @param[in] ptr PLLCTL base address
386 */
pllctl_xtal_set_rampup_time(PLLCTL_Type * ptr,uint32_t cycles)387 static inline void pllctl_xtal_set_rampup_time(PLLCTL_Type *ptr, uint32_t cycles)
388 {
389 ptr->XTAL = (ptr->XTAL & ~PLLCTL_XTAL_RAMP_TIME_MASK) | PLLCTL_XTAL_RAMP_TIME_SET(cycles);
390 }
391
392 /**
393 * @brief Set refdiv
394 *
395 * @param[in] ptr PLLCTL base address
396 * @param[in] pll Target PLL index
397 * @param[in] div Divider value (0x1-0x3F)
398 *
399 * @return status_success if everything is okay
400 */
401 hpm_stat_t pllctl_set_refdiv(PLLCTL_Type *ptr, uint8_t pll, uint8_t div);
402
403 /**
404 * @brief Initialize PLL working at integer mode with specific frequency
405 *
406 * @param[in] ptr PLLCTL base address
407 * @param[in] pll Target PLL index
408 * @param[in] freq_in_hz Target frequency, expected >= 375000000Hz
409 *
410 * @return status_success if everything is okay
411 *
412 * @note The actual frequency might be slightly different from freq_in_hz due to calculation.
413 */
414 hpm_stat_t pllctl_init_int_pll_with_freq(PLLCTL_Type *ptr, uint8_t pll,
415 uint32_t freq_in_hz);
416
417 /**
418 * @brief Initialize PLL working at franction mode with specific frequency
419 *
420 * @param[in] ptr PLLCTL base address
421 * @param[in] pll Target PLL index
422 * @param[in] freq_in_hz Target frequency, expected >= 375000000Hz
423 *
424 * @return status_success if everything is okay
425 * @note The actual frequency might be slightly different from freq_in_hz due to calculation.
426 */
427 hpm_stat_t pllctl_init_frac_pll_with_freq(PLLCTL_Type *ptr, uint8_t pll,
428 uint32_t freq_in_hz);
429
430 /**
431 * @brief Get frequency of target PLL
432 *
433 * @param[in] ptr PLLCTL base address
434 * @param[in] pll Target PLL index
435 *
436 * @return current frequency of target PLL in Hz
437 */
438 uint32_t pllctl_get_pll_freq_in_hz(PLLCTL_Type *ptr, uint8_t pll);
439
440 #ifdef __cplusplus
441 }
442 #endif
443 /**
444 * @}
445 */
446 #endif /* HPM_PLLCTL_DRV_H */
447
448