1 /*
2  * Copyright (c) 2021-2024 HPMicro
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  *
6  */
7 
8 #include "hpm_pllctl_drv.h"
9 
10 #define PLLCTL_INT_PLL_MAX_FBDIV (2400U)
11 #define PLLCTL_INT_PLL_MIN_FBDIV (16U)
12 
13 #define PLLCTL_FRAC_PLL_MAX_FBDIV (240U)
14 #define PLLCTL_FRAC_PLL_MIN_FBDIV (20U)
15 
16 #define PLLCTL_PLL_MAX_REFDIV (63U)
17 #define PLLCTL_PLL_MIN_REFDIV (1U)
18 
19 #define PLLCTL_PLL_MAX_POSTDIV1 (7U)
20 #define PLLCTL_PLL_MIN_POSTDIV1 (1U)
21 
22 #define PLLCTL_FRAC_PLL_MIN_REF (10000000U)
23 #define PLLCTL_INT_PLL_MIN_REF (1000000U)
24 
25 
pllctl_set_pll_work_mode(PLLCTL_Type * ptr,uint8_t pll,bool int_mode)26 hpm_stat_t pllctl_set_pll_work_mode(PLLCTL_Type *ptr, uint8_t pll, bool int_mode)
27 {
28     if ((ptr == NULL) || (pll >= PLLCTL_SOC_PLL_MAX_COUNT)) {
29         return status_invalid_argument;
30     }
31     if (int_mode) {
32         if (!(ptr->PLL[pll].CFG0 & PLLCTL_PLL_CFG0_DSMPD_MASK)) {
33             /* it was at frac mode, then it needs to be power down */
34             pllctl_pll_powerdown(ptr, pll);
35             ptr->PLL[pll].CFG0 |= PLLCTL_PLL_CFG0_DSMPD_MASK;
36             pllctl_pll_poweron(ptr, pll);
37         }
38     } else {
39         if (ptr->PLL[pll].CFG0 & PLLCTL_PLL_CFG0_DSMPD_MASK) {
40             /* pll has to be powered down to configure frac mode */
41             pllctl_pll_powerdown(ptr, pll);
42             ptr->PLL[pll].CFG0 &= ~PLLCTL_PLL_CFG0_DSMPD_MASK;
43             pllctl_pll_poweron(ptr, pll);
44         }
45     }
46 
47     return status_success;
48 }
49 
pllctl_set_refdiv(PLLCTL_Type * ptr,uint8_t pll,uint8_t div)50 hpm_stat_t pllctl_set_refdiv(PLLCTL_Type *ptr, uint8_t pll, uint8_t div)
51 {
52     uint32_t min_ref;
53 
54     if ((ptr == NULL)
55         || (pll > (PLLCTL_SOC_PLL_MAX_COUNT - 1))
56         || (div == 0U)
57         || (div > (PLLCTL_PLL_CFG0_REFDIV_MASK >> PLLCTL_PLL_CFG0_REFDIV_SHIFT))) {
58         return status_invalid_argument;
59     }
60 
61     if (ptr->PLL[pll].CFG0 & PLLCTL_PLL_CFG0_DSMPD_MASK) {
62         min_ref = PLLCTL_INT_PLL_MIN_REF;
63     } else {
64         min_ref = PLLCTL_FRAC_PLL_MIN_REF;
65     }
66 
67     if ((PLLCTL_SOC_PLL_REFCLK_FREQ / div) < min_ref) {
68         return status_pllctl_out_of_range;
69     }
70 
71     if (PLLCTL_PLL_CFG0_REFDIV_GET(ptr->PLL[pll].CFG0) != div) {
72         /* if div is different, it needs to be power down */
73         pllctl_pll_powerdown(ptr, pll);
74         ptr->PLL[pll].CFG0 = (ptr->PLL[pll].CFG0 & ~PLLCTL_PLL_CFG0_REFDIV_MASK)
75             | PLLCTL_PLL_CFG0_REFDIV_SET(div);
76         pllctl_pll_poweron(ptr, pll);
77     }
78     return status_success;
79 }
80 
pllctl_init_int_pll_with_freq(PLLCTL_Type * ptr,uint8_t pll,uint32_t freq_in_hz)81 hpm_stat_t pllctl_init_int_pll_with_freq(PLLCTL_Type *ptr, uint8_t pll,
82                                     uint32_t freq_in_hz)
83 {
84     if ((ptr == NULL) || (pll >= PLLCTL_SOC_PLL_MAX_COUNT)) {
85         return status_invalid_argument;
86     }
87     uint32_t freq, fbdiv, refdiv, postdiv;
88     if ((freq_in_hz < PLLCTL_PLL_VCO_FREQ_MIN)
89             || (freq_in_hz > PLLCTL_PLL_VCO_FREQ_MAX)) {
90         return status_invalid_argument;
91     }
92 
93     freq = freq_in_hz;
94     refdiv = PLLCTL_PLL_CFG0_REFDIV_GET(ptr->PLL[pll].CFG0);
95     postdiv = PLLCTL_PLL_CFG0_POSTDIV1_GET(ptr->PLL[pll].CFG0);
96     fbdiv = freq / (PLLCTL_SOC_PLL_REFCLK_FREQ / (refdiv * postdiv));
97     if (fbdiv > PLLCTL_INT_PLL_MAX_FBDIV) {
98         /* current refdiv can't be used for the given frequency */
99         refdiv--;
100         do {
101             fbdiv = freq / (PLLCTL_SOC_PLL_REFCLK_FREQ / (refdiv * postdiv));
102             if (fbdiv > PLLCTL_INT_PLL_MAX_FBDIV) {
103                 refdiv--;
104             } else {
105                 break;
106             }
107         } while (refdiv > PLLCTL_PLL_MIN_REFDIV);
108     } else if (fbdiv < PLLCTL_INT_PLL_MIN_FBDIV) {
109         /* current refdiv can't be used for the given frequency */
110         refdiv++;
111         do {
112             fbdiv = freq / (PLLCTL_SOC_PLL_REFCLK_FREQ / (refdiv * postdiv));
113             if (fbdiv < PLLCTL_INT_PLL_MIN_FBDIV) {
114                 refdiv++;
115             } else {
116                 break;
117             }
118         } while (refdiv < PLLCTL_PLL_MAX_REFDIV);
119     }
120 
121     if ((refdiv > PLLCTL_PLL_MAX_REFDIV)
122             || (refdiv < PLLCTL_PLL_MIN_REFDIV)
123             || (fbdiv > PLLCTL_INT_PLL_MAX_FBDIV)
124             || (fbdiv < PLLCTL_INT_PLL_MIN_FBDIV)
125             || (((PLLCTL_SOC_PLL_REFCLK_FREQ / refdiv) < PLLCTL_INT_PLL_MIN_REF))) {
126         return status_pllctl_out_of_range;
127     }
128 
129     if (!(ptr->PLL[pll].CFG0 & PLLCTL_PLL_CFG0_DSMPD_MASK)) {
130         /* it was at frac mode, then it needs to be power down */
131         pllctl_pll_powerdown(ptr, pll);
132         ptr->PLL[pll].CFG0 |= PLLCTL_PLL_CFG0_DSMPD_MASK;
133     }
134 
135     if (PLLCTL_PLL_CFG0_REFDIV_GET(ptr->PLL[pll].CFG0) != refdiv) {
136         /* if refdiv is different, it needs to be power down */
137         pllctl_pll_powerdown(ptr, pll);
138         ptr->PLL[pll].CFG0 = (ptr->PLL[pll].CFG0 & ~PLLCTL_PLL_CFG0_REFDIV_MASK)
139             | PLLCTL_PLL_CFG0_REFDIV_SET(refdiv);
140     }
141 
142     ptr->PLL[pll].CFG2 = (ptr->PLL[pll].CFG2 & ~(PLLCTL_PLL_CFG2_FBDIV_INT_MASK)) | PLLCTL_PLL_CFG2_FBDIV_INT_SET(fbdiv);
143 
144     pllctl_pll_poweron(ptr, pll);
145     return status_success;
146 }
147 
pllctl_init_frac_pll_with_freq(PLLCTL_Type * ptr,uint8_t pll,uint32_t freq_in_hz)148 hpm_stat_t pllctl_init_frac_pll_with_freq(PLLCTL_Type *ptr, uint8_t pll,
149                                     uint32_t freq_in_hz)
150 {
151     if ((ptr == NULL) || (pll >= PLLCTL_SOC_PLL_MAX_COUNT)) {
152         return status_invalid_argument;
153     }
154     uint32_t frac, refdiv, fbdiv, freq, postdiv;
155     double div;
156     if ((freq_in_hz < PLLCTL_PLL_VCO_FREQ_MIN)
157             || (freq_in_hz > PLLCTL_PLL_VCO_FREQ_MAX)) {
158         return status_invalid_argument;
159     }
160 
161     freq = freq_in_hz;
162     refdiv = PLLCTL_PLL_CFG0_REFDIV_GET(ptr->PLL[pll].CFG0);
163     postdiv = PLLCTL_PLL_CFG0_POSTDIV1_GET(ptr->PLL[pll].CFG0);
164     fbdiv = (freq / postdiv) / (PLLCTL_SOC_PLL_REFCLK_FREQ / refdiv);
165 
166     if (fbdiv > PLLCTL_FRAC_PLL_MAX_FBDIV) {
167         /* current refdiv can't be used for the given frequency */
168         refdiv--;
169         do {
170             fbdiv = freq / (PLLCTL_SOC_PLL_REFCLK_FREQ / (refdiv * postdiv));
171             if (fbdiv > PLLCTL_FRAC_PLL_MAX_FBDIV) {
172                 refdiv--;
173             } else {
174                 break;
175             }
176         } while (refdiv > PLLCTL_PLL_MIN_REFDIV);
177     } else if (fbdiv < PLLCTL_FRAC_PLL_MIN_FBDIV) {
178         /* current refdiv can't be used for the given frequency */
179         refdiv++;
180         do {
181             fbdiv = freq / (PLLCTL_SOC_PLL_REFCLK_FREQ / (refdiv * postdiv));
182             if (fbdiv < PLLCTL_FRAC_PLL_MIN_FBDIV) {
183                 refdiv++;
184             } else {
185                 break;
186             }
187         } while (refdiv < PLLCTL_PLL_MAX_REFDIV);
188     }
189 
190     if ((refdiv > PLLCTL_PLL_MAX_REFDIV)
191             || (refdiv < PLLCTL_PLL_MIN_REFDIV)
192             || (fbdiv > PLLCTL_FRAC_PLL_MAX_FBDIV)
193             || (fbdiv < PLLCTL_FRAC_PLL_MIN_FBDIV)
194             || (((PLLCTL_SOC_PLL_REFCLK_FREQ / refdiv) < PLLCTL_FRAC_PLL_MIN_REF))) {
195         return status_pllctl_out_of_range;
196     }
197 
198     div = (double) freq / PLLCTL_SOC_PLL_REFCLK_FREQ * (refdiv * postdiv);
199     fbdiv = freq / (PLLCTL_SOC_PLL_REFCLK_FREQ / (refdiv * postdiv));
200     frac = (uint32_t)((div - fbdiv) * (1 << 24));
201 
202     /*
203      * pll has to be powered down to configure frac mode
204      */
205     pllctl_pll_powerdown(ptr, pll);
206 
207     ptr->PLL[pll].CFG0 = (ptr->PLL[pll].CFG0
208             & ~(PLLCTL_PLL_CFG0_REFDIV_MASK | PLLCTL_PLL_CFG0_DSMPD_MASK))
209         | PLLCTL_PLL_CFG0_REFDIV_SET(refdiv);
210 
211     pllctl_pll_ss_disable(ptr, pll);
212     ptr->PLL[pll].FREQ = (ptr->PLL[pll].FREQ
213             & ~(PLLCTL_PLL_FREQ_FRAC_MASK | PLLCTL_PLL_FREQ_FBDIV_FRAC_MASK))
214         | PLLCTL_PLL_FREQ_FBDIV_FRAC_SET(fbdiv) | PLLCTL_PLL_FREQ_FRAC_SET(frac);
215 
216     pllctl_pll_poweron(ptr, pll);
217     return status_success;
218 }
219 
pllctl_get_pll_freq_in_hz(PLLCTL_Type * ptr,uint8_t pll)220 uint32_t pllctl_get_pll_freq_in_hz(PLLCTL_Type *ptr, uint8_t pll)
221 {
222     if ((ptr == NULL) || (pll >= PLLCTL_SOC_PLL_MAX_COUNT)) {
223         return status_invalid_argument;
224     }
225     uint32_t fbdiv, frac, refdiv, postdiv, refclk, freq;
226     if (ptr->PLL[pll].CFG1 & PLLCTL_PLL_CFG1_PLLPD_SW_MASK) {
227         /* pll is powered down */
228         return 0;
229     }
230 
231     refdiv = PLLCTL_PLL_CFG0_REFDIV_GET(ptr->PLL[pll].CFG0);
232     postdiv = PLLCTL_PLL_CFG0_POSTDIV1_GET(ptr->PLL[pll].CFG0);
233     refclk = PLLCTL_SOC_PLL_REFCLK_FREQ / (refdiv * postdiv);
234 
235     if (ptr->PLL[pll].CFG0 & PLLCTL_PLL_CFG0_DSMPD_MASK) {
236         /* pll int mode */
237         fbdiv = PLLCTL_PLL_CFG2_FBDIV_INT_GET(ptr->PLL[pll].CFG2);
238         freq = refclk * fbdiv;
239     } else {
240         /* pll frac mode */
241         fbdiv = PLLCTL_PLL_FREQ_FBDIV_FRAC_GET(ptr->PLL[pll].FREQ);
242         frac = PLLCTL_PLL_FREQ_FRAC_GET(ptr->PLL[pll].FREQ);
243         freq = (uint32_t)((refclk * (fbdiv + ((double) frac / (1 << 24)))) + 0.5);
244     }
245     return freq;
246 }
247 
248