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