• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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