blob: 3c18798c706120039a96a388e458162a4aa2881e [file] [log] [blame]
developer80159062021-11-08 16:37:39 +08001/*
2 * Copyright (c) 2021, ARM Limited and Contributors. All rights reserved.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7#include <errno.h>
8
9#include <arch_helpers.h>
10#include <common/debug.h>
11#include <drivers/delay_timer.h>
12#include <lib/spinlock.h>
13
14#include <apupwr_clkctl.h>
15#include <apupwr_clkctl_def.h>
16#include <mtk_plat_common.h>
17#include <platform_def.h>
18
19uint32_t mixed_con0_addr[APUPLL_MAX] = {
20 APU_PLL4H_PLL1_CON0,
21 APU_PLL4H_PLL2_CON0,
22 APU_PLL4H_PLL3_CON0,
23 APU_PLL4H_PLL4_CON0,
24};
25
26uint32_t mixed_con1_addr[APUPLL_MAX] = {
27 APU_PLL4H_PLL1_CON1,
28 APU_PLL4H_PLL2_CON1,
29 APU_PLL4H_PLL3_CON1,
30 APU_PLL4H_PLL4_CON1,
31};
32
33uint32_t mixed_con3_addr[APUPLL_MAX] = {
34 APU_PLL4H_PLL1_CON3,
35 APU_PLL4H_PLL2_CON3,
36 APU_PLL4H_PLL3_CON3,
37 APU_PLL4H_PLL4_CON3,
38};
39
40uint32_t fhctl_dds_addr[APUPLL_MAX] = {
41 APU_PLL4H_FHCTL0_DDS,
42 APU_PLL4H_FHCTL1_DDS,
43 APU_PLL4H_FHCTL2_DDS,
44 APU_PLL4H_FHCTL3_DDS,
45};
46
47uint32_t fhctl_dvfs_addr[APUPLL_MAX] = {
48 APU_PLL4H_FHCTL0_DVFS,
49 APU_PLL4H_FHCTL1_DVFS,
50 APU_PLL4H_FHCTL2_DVFS,
51 APU_PLL4H_FHCTL3_DVFS,
52};
53
54uint32_t fhctl_mon_addr[APUPLL_MAX] = {
55 APU_PLL4H_FHCTL0_MON,
56 APU_PLL4H_FHCTL1_MON,
57 APU_PLL4H_FHCTL2_MON,
58 APU_PLL4H_FHCTL3_MON,
59};
60
61uint32_t fhctl_cfg_addr[APUPLL_MAX] = {
62 APU_PLL4H_FHCTL0_CFG,
63 APU_PLL4H_FHCTL1_CFG,
64 APU_PLL4H_FHCTL2_CFG,
65 APU_PLL4H_FHCTL3_CFG,
66};
67
68static spinlock_t apupll_lock;
69static spinlock_t npupll_lock;
70static spinlock_t apupll_1_lock;
71static spinlock_t apupll_2_lock;
72static uint32_t pll_cnt[APUPLL_MAX];
73/**
74 * vd2pllidx() - voltage domain to pll idx.
75 * @domain: the voltage domain for getting pll index.
76 *
77 * Caller will get correspond pll index by different voltage domain.
78 * pll_idx[0] --> APUPLL (MDLA0/1)
79 * pll_idx[1] --> NPUPLL (VPU0/1)
80 * pll_idx[2] --> APUPLL1(CONN)
81 * pll_idx[3] --> APUPLL2(IOMMU)
82 * The longer description may have multiple paragraphs.
83 *
84 * Context: Any context.
85 * Return:
86 * * 0 ~ 3 - return the corresponding pll index
87 * * -EEXIST - cannot find pll idex of the specific voltage domain
88 *
89 */
90static int32_t vd2pllidx(enum dvfs_voltage_domain domain)
91{
92 int32_t ret;
93
94 switch (domain) {
95 case V_VPU0:
96 case V_VPU1:
97 ret = NPUPLL;
98 break;
99 case V_MDLA0:
100 case V_MDLA1:
101 ret = APUPLL;
102 break;
103 case V_TOP_IOMMU:
104 ret = APUPLL2;
105 break;
106 case V_APU_CONN:
107 ret = APUPLL1;
108 break;
109 default:
110 ERROR("%s wrong voltage domain: %d\n", __func__, domain);
111 ret = -EEXIST; /* non-exist */
112 break;
113 }
114
115 return ret;
116}
117
118/**
119 * pllidx2name() - return names of specific pll index.
120 * @pll_idx: input for specific pll index.
121 *
122 * Given pll index, this function will return name of it.
123 *
124 * Context: Any context.
125 * Return: Names of pll_idx, if found, otherwise will return "NULL"
126 */
127static const char *pllidx2name(int32_t pll_idx)
128{
129 static const char *const names[] = {
130 [APUPLL] = "PLL4H_PLL1",
131 [NPUPLL] = "PLL4H_PLL2",
132 [APUPLL1] = "PLL4H_PLL3",
133 [APUPLL2] = "PLL4H_PLL4",
134 [APUPLL_MAX] = "NULL",
135 };
136
137 if (pll_idx >= APUPLL_MAX) {
138 pll_idx = APUPLL_MAX;
139 }
140
141 return names[pll_idx];
142}
143
144/**
145 * _fhctl_mon_done() - poll whether fhctl HW mode is done.
146 * @pll_idx: input for specific pll index.
147 * @tar_dds: target dds for fhctl_mon to be.
148 *
149 * Given pll index, this function will continue to poll whether fhctl_mon
150 * has reached the expected value within 80us.
151 *
152 * Context: Any context.
153 * Return:
154 * * 0 - OK for fhctl_mon == tar_dds
155 * * -ETIMEDOUT - fhctl_mon not reach tar_dds
156 */
157static int32_t _fhctl_mon_done(uint32_t pll_idx, unsigned long tar_dds)
158{
159 unsigned long mon_dds;
160 uint64_t timeout = timeout_init_us(PLL_READY_TIME_20US);
161 int32_t ret = 0;
162
163 tar_dds &= DDS_MASK;
164 do {
165 mon_dds = apupwr_readl(fhctl_mon_addr[pll_idx]) & DDS_MASK;
166 if (mon_dds == tar_dds) {
167 break;
168 }
169
170 if (timeout_elapsed(timeout)) {
171 ERROR("%s monitor DDS 0x%08lx != expect 0x%08lx\n",
172 pllidx2name(pll_idx), mon_dds, tar_dds);
173 ret = -ETIMEDOUT;
174 break;
175 }
176 } while (mon_dds != tar_dds);
177
178 return ret;
179}
180
181/**
182 * _pll_get_postdiv_reg() - return current post dividor of pll_idx
183 * @pll_idx: input for specific pll index.
184 *
185 * Given pll index, this function will return its current post dividor.
186 *
187 * Context: Any context.
188 * Return: post dividor of current pll_idx.
189 *
190 */
191static uint32_t _pll_get_postdiv_reg(uint32_t pll_idx)
192{
193 int32_t pll_postdiv_reg = 0;
194 uint32_t val;
195
196 val = apupwr_readl(mixed_con1_addr[pll_idx]);
197 pll_postdiv_reg = (val >> POSDIV_SHIFT) & POSDIV_MASK;
198 return pll_postdiv_reg;
199}
200
201/**
202 * _set_postdiv_reg() - set pll_idx's post dividor.
203 * @pll_idx: Which PLL to enable/disable
204 * @post_div: the register value of post dividor to be wrtten.
205 *
206 * Below are lists of post dividor register value and its meaning:
207 * [31] APUPLL_SDM_PCW_CHG
208 * [26:24] APUPLL_POSDIV
209 * [21:0] APUPLL_SDM_PCW (8bit integer + 14bit fraction)
210 * expected freq range ----- divider-------post divider in reg:
211 * >1500M (1500/ 1) -> 1 -> 0(2 to the zero power)
212 * > 750M (1500/ 2) -> 2 -> 1(2 to the 1st power)
213 * > 375M (1500/ 4) -> 4 -> 2(2 to the 2nd power)
214 * > 187.5M (1500/ 8) -> 8 -> 3(2 to the 3rd power)
215 * > 93.75M (1500/16) -> 16 -> 4(2 to the 4th power)
216 *
217 * Context: Any context.
218 */
219static void _set_postdiv_reg(uint32_t pll_idx, uint32_t post_div)
220{
221 apupwr_clrbits(POSDIV_MASK << POSDIV_SHIFT, mixed_con1_addr[pll_idx]);
222 apupwr_setbits((post_div & POSDIV_MASK) << POSDIV_SHIFT,
223 mixed_con1_addr[pll_idx]);
224}
225
226/**
227 * _cal_pll_data() - input freq, calculate correspond post dividor and dds.
228 * @pd: address of output post dividor.
229 * @dds: address of output dds.
230 * @freq: input frequency.
231 *
232 * Given freq, this function will calculate correspond post dividor and dds.
233 *
234 * Context: Any context.
235 * Return:
236 * * 0 - done for calculating post dividor and dds.
237 */
238static int32_t _cal_pll_data(uint32_t *pd, uint32_t *dds, uint32_t freq)
239{
240 uint32_t vco, postdiv_val = 1, postdiv_reg = 0;
241 uint32_t pcw_val;
242
243 vco = freq;
244 postdiv_val = 1;
245 postdiv_reg = 0;
246 while (vco <= FREQ_VCO_MIN) {
247 postdiv_val = postdiv_val << 1;
248 postdiv_reg = postdiv_reg + 1;
249 vco = vco << 1;
250 }
251
252 pcw_val = vco * (1 << PCW_FRACTIONAL_SHIFT);
253 pcw_val = pcw_val / FREQ_FIN;
254
255 if (postdiv_reg == 0) { /* Fvco * 2 with post_divider = 2 */
256 pcw_val = pcw_val * 2;
257 postdiv_val = postdiv_val << 1;
258 postdiv_reg = postdiv_reg + 1;
259 } /* Post divider is 1 is not available */
260 *pd = postdiv_reg;
261 *dds = pcw_val | RG_PLL_SDM_PCW_CHG;
262
263 return 0;
264}
265
266/**
267 * _pll_en() - enable/disable RG_PLL_EN of CON1 for pll[pll_idx]
268 * @pll_idx: Which PLL to enable/disable
269 * @on: 1 -> enable, 0 -> disable.
270 *
Elyes Haouas2be03c02023-02-13 09:14:48 +0100271 * This function will only change RG_PLL_EN of CON1 for pll[pll_idx].
developer80159062021-11-08 16:37:39 +0800272 *
273 * Context: Any context.
274 */
275static void _pll_en(uint32_t pll_idx, bool on)
276{
277 if (on) {
278 apupwr_setbits(RG_PLL_EN, mixed_con0_addr[pll_idx]);
279 } else {
280 apupwr_clrbits(RG_PLL_EN, mixed_con0_addr[pll_idx]);
281 }
282}
283
284/**
285 * _pll_pwr() - enable/disable PLL_SDM_PWR_ON of CON3 for pll[pll_idx]
286 * @pll_idx: Which PLL to enable/disable
287 * @on: 1 -> enable, 0 -> disable.
288 *
Elyes Haouas2be03c02023-02-13 09:14:48 +0100289 * This function will only change PLL_SDM_PWR_ON of CON3 for pll[pll_idx].
developer80159062021-11-08 16:37:39 +0800290 *
291 * Context: Any context.
292 */
293static void _pll_pwr(uint32_t pll_idx, bool on)
294{
295 if (on) {
296 apupwr_setbits(DA_PLL_SDM_PWR_ON, mixed_con3_addr[pll_idx]);
297 } else {
298 apupwr_clrbits(DA_PLL_SDM_PWR_ON, mixed_con3_addr[pll_idx]);
299 }
300}
301
302/**
303 * _pll_iso() - enable/disable PLL_SDM_ISO_EN of CON3 for pll[pll_idx]
304 * @pll_idx: Which PLL to enable/disable
305 * @enable: 1 -> turn on isolation, 0 -> turn off isolation.
306 *
Elyes Haouas2be03c02023-02-13 09:14:48 +0100307 * This function will turn on/off pll isolation by
developer80159062021-11-08 16:37:39 +0800308 * changing PLL_SDM_PWR_ON of CON3 for pll[pll_idx].
309 *
310 * Context: Any context.
311 */
312static void _pll_iso(uint32_t pll_idx, bool enable)
313{
314 if (enable) {
315 apupwr_setbits(DA_PLL_SDM_ISO_EN, mixed_con3_addr[pll_idx]);
316 } else {
317 apupwr_clrbits(DA_PLL_SDM_ISO_EN, mixed_con3_addr[pll_idx]);
318 }
319}
320
321/**
322 * _pll_switch() - entry point to turn whole PLL on/off
323 * @pll_idx: Which PLL to enable/disable
324 * @on: 1 -> enable, 0 -> disable.
325 * @fhctl_en: enable or disable fhctl function
326 *
Elyes Haouas2be03c02023-02-13 09:14:48 +0100327 * This is the entry poing for controlling pll and fhctl function on/off.
developer80159062021-11-08 16:37:39 +0800328 * Caller can chose only enable pll instead of fhctl function.
329 *
330 * Context: Any context.
331 * Return:
332 * * 0 - done for enable pll or fhctl as well.
333 */
334static int32_t _pll_switch(uint32_t pll_idx, bool on, bool fhctl_en)
335{
336 int32_t ret = 0;
337
338 if (pll_idx >= APUPLL_MAX) {
339 ERROR("%s wrong pll_idx: %d\n", __func__, pll_idx);
340 ret = -EINVAL;
341 goto err;
342 }
343
344 if (on) {
345 _pll_pwr(pll_idx, true);
346 udelay(PLL_CMD_READY_TIME_1US);
347 _pll_iso(pll_idx, false);
348 udelay(PLL_CMD_READY_TIME_1US);
349 _pll_en(pll_idx, true);
350 udelay(PLL_READY_TIME_20US);
351 } else {
352 _pll_en(pll_idx, false);
353 _pll_iso(pll_idx, true);
354 _pll_pwr(pll_idx, false);
355 }
356
357err:
358 return ret;
359}
360
361/**
362 * apu_pll_enable() - API for smc function to enable/disable pll
363 * @pll_idx: Which pll to enable/disable.
364 * @enable: 1 -> enable, 0 -> disable.
365 * @fhctl_en: enable or disable fhctl function
366 *
367 * pll_idx[0] --> APUPLL (MDLA0/1)
368 * pll_idx[1] --> NPUPLL (VPU0/1)
369 * pll_idx[2] --> APUPLL1(CONN)
370 * pll_idx[3] --> APUPLL2(IOMMU)
371 * The differences between _pll_switch are:
372 * 1. Atomic update pll reference cnt to protect double enable pll &
373 * close pll during user is not zero.
374 *
375 * Context: Any context.
376 * Return:
377 * * 0 - done for enable pll or fhctl as well.
378 */
379int32_t apu_pll_enable(int32_t pll_idx, bool enable, bool fhctl_en)
380{
381 int32_t ret = 0;
382
383 if (pll_idx >= APUPLL_MAX) {
384 ERROR("%s wrong pll_idx: %d\n", __func__, pll_idx);
385 ret = -EINVAL;
386 goto err;
387 }
388
389 if (enable) {
390 switch (pll_idx) {
391 case APUPLL:
392 spin_lock(&apupll_lock);
393 if (pll_cnt[APUPLL] == 0) {
394 _pll_switch(pll_idx, enable, fhctl_en);
395 }
396 pll_cnt[APUPLL]++;
397 spin_unlock(&apupll_lock);
398 break;
399 case NPUPLL:
400 spin_lock(&npupll_lock);
401 if (pll_cnt[NPUPLL] == 0) {
402 _pll_switch(pll_idx, enable, fhctl_en);
403 }
404 pll_cnt[NPUPLL]++;
405 spin_unlock(&npupll_lock);
406 break;
407 case APUPLL1:
408 spin_lock(&apupll_1_lock);
409 if (pll_cnt[APUPLL1] == 0) {
410 _pll_switch(pll_idx, enable, fhctl_en);
411 }
412 pll_cnt[APUPLL1]++;
413 spin_unlock(&apupll_1_lock);
414 break;
415 case APUPLL2:
416 spin_lock(&apupll_2_lock);
417 if (pll_cnt[APUPLL2] == 0) {
418 _pll_switch(pll_idx, enable, fhctl_en);
419 }
420 pll_cnt[APUPLL2]++;
421 spin_unlock(&apupll_2_lock);
422 break;
423 default:
424 ERROR("%s invalid pll_idx: %d\n", __func__, pll_idx);
425 ret = -EINVAL;
426 break;
427 }
428 } else {
429 switch (pll_idx) {
430 case APUPLL:
431 spin_lock(&apupll_lock);
432 if (pll_cnt[APUPLL]) {
433 pll_cnt[APUPLL]--;
434 }
435 if (pll_cnt[APUPLL] == 0) {
436 _pll_switch(pll_idx, enable, fhctl_en);
437 }
438 spin_unlock(&apupll_lock);
439 break;
440 case NPUPLL:
441 spin_lock(&npupll_lock);
442 if (pll_cnt[NPUPLL]) {
443 pll_cnt[NPUPLL]--;
444 }
445 if (pll_cnt[NPUPLL] == 0) {
446 _pll_switch(pll_idx, enable, fhctl_en);
447 }
448 spin_unlock(&npupll_lock);
449 break;
450 case APUPLL1:
451 spin_lock(&apupll_1_lock);
452 if (pll_cnt[APUPLL1]) {
453 pll_cnt[APUPLL1]--;
454 }
455 if (pll_cnt[APUPLL1] == 0) {
456 _pll_switch(pll_idx, enable, fhctl_en);
457 }
458 spin_unlock(&apupll_1_lock);
459 break;
460 case APUPLL2:
461 spin_lock(&apupll_2_lock);
462 if (pll_cnt[APUPLL2]) {
463 pll_cnt[APUPLL2]--;
464 }
465 if (pll_cnt[APUPLL2] == 0) {
466 _pll_switch(pll_idx, enable, fhctl_en);
467 }
468 spin_unlock(&apupll_2_lock);
469 break;
470 default:
471 ERROR("%s invalid pll_idx: %d\n", __func__, pll_idx);
472 ret = -EINVAL;
473 break;
474 }
475 }
476
477err:
478 return ret;
479}
480
481/**
482 * anpu_pll_set_rate() - API for smc function to set rate of voltage domain.
483 * @domain: Which pll of correspond voltage domain to change rate.
484 * @mode: which mode to use when set_rate
485 * @freq: which frequency to set.
486 *
487 * For V_VPU0/1, it will only allow 1 of them to modify NPUPLL
488 * such that there will be no race condition happen.
489 *
490 * For V_MDLA0/1, it will only allow 1 of them to modify APUPLL1
491 * such that there will be no race condition happen.
492 *
493 * There are 3 kinds of modes to set pll's rate.
494 * 1. pure sw mode: (CON0_PCW)
495 * fhctl function is off and change rate by programming CON1_PCW.
496 * 2. fhctl sw mode: (FHCTL_SW)
497 * fhctl function is on and change rate by programming fhctl_dds.
498 * (post dividor is still need to program CON1_PCW)
499 * 3. fhctl hw mode: (FHCTL_HW)
500 * fhctl function is on and change rate by programming fhctl_dvfs.
501 * (post dividor is still need to program CON1_PCW)
502 *
503 * Context: Any context.
504 * Return:
505 * * 0 - done for set rate of voltage domain.
506 */
507int32_t anpu_pll_set_rate(enum dvfs_voltage_domain domain,
508 enum pll_set_rate_mode mode, int32_t freq)
509{
510 uint32_t pd, old_pd, dds;
511 int32_t pll_idx, ret = 0;
512
513 pll_idx = vd2pllidx(domain);
514 if (pll_idx < 0) {
515 ret = pll_idx;
516 goto err;
517 }
518
519 _cal_pll_data(&pd, &dds, freq / 1000);
520
521 INFO("%s %s new post_div=%d, target dds=0x%08x(%dMhz) mode = %d\n",
522 __func__, pllidx2name(pll_idx), pd, dds, freq / 1000, mode);
523
524 /* spin_lock for NPULL, since vpu0/1 share npupll */
525 if (domain == V_VPU0 || domain == V_VPU1) {
526 spin_lock(&npupll_lock);
527 }
528
529 /* spin_lock for APUPLL, since mdla0/1 shate apupll */
530 if (domain == V_MDLA0 || domain == V_MDLA1) {
531 spin_lock(&apupll_lock);
532 }
533
534 switch (mode) {
535 case CON0_PCW:
536 pd = RG_PLL_SDM_PCW_CHG |
537 (pd & POSDIV_MASK) << POSDIV_SHIFT | dds;
538 apupwr_writel(pd, mixed_con1_addr[pll_idx]);
539 udelay(PLL_READY_TIME_20US);
540 break;
541 case FHCTL_SW:
542 /* pll con0 disable */
543 _pll_en(pll_idx, false);
544 apupwr_writel(dds, fhctl_dds_addr[pll_idx]);
545 _set_postdiv_reg(pll_idx, pd);
546 apupwr_setbits(PLL_TGL_ORG, fhctl_dds_addr[pll_idx]);
547 udelay(PLL_CMD_READY_TIME_1US);
548 /* pll con0 enable */
549 _pll_en(pll_idx, true);
550 udelay(PLL_READY_TIME_20US);
551 break;
552 case FHCTL_HW:
553 old_pd = _pll_get_postdiv_reg(pll_idx);
554 if (pd > old_pd) {
555 _set_postdiv_reg(pll_idx, pd);
556 apupwr_writel(dds, fhctl_dvfs_addr[pll_idx]);
557 } else {
558 apupwr_writel(dds, fhctl_dvfs_addr[pll_idx]);
559 _set_postdiv_reg(pll_idx, pd);
560 }
561 ret = _fhctl_mon_done(pll_idx, dds);
562 break;
563 default:
564 ERROR("%s input wrong mode: %d\n", __func__, mode);
565 ret = -EINVAL;
566 break;
567 }
568
569 /* spin_lock for NPULL, since vpu0/1 share npupll */
570 if (domain == V_VPU0 || domain == V_VPU1) {
571 spin_unlock(&npupll_lock);
572 }
573
574 /* spin_lock for APUPLL, since mdla0/1 share apupll */
575 if (domain == V_MDLA0 || domain == V_MDLA1) {
576 spin_unlock(&apupll_lock);
577 }
578
579err:
580 return ret;
581}