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