blob: 78c6ac550cd7b8a71f07fd965091ff19e711b9d1 [file] [log] [blame]
xjb04a4022021-11-25 15:01:52 +08001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2019 MediaTek Inc.
4 * Author: Pierre Lee <pierre.lee@mediatek.com>
5 */
6
7#include <linux/delay.h>
8#include <linux/iopoll.h>
9#include "clk-fhctl.h"
10
11#define PERCENT_TO_DDSLMT(dds, percent_m10) \
12 ((((dds) * (percent_m10)) >> 5) / 100)
13
14
15#define POSDIV_LSB 24
16#define POSDIV_MASK 0x7
17
18
19static int mt_fh_ipi_init(void)
20{
21 return 0;
22}
23
24
25static int __mt_fh_hw_hopping(struct clk_mt_fhctl *fh,
26 int pll_id, unsigned int new_dds,
27 int postdiv)
28{
29 unsigned int dds_mask, con_temp;
30 unsigned int mon_dds = 0;
31 int ret;
32 struct clk_mt_fhctl_regs *fh_regs;
33 struct clk_mt_fhctl_pll_data *pll_data;
34
35 fh_regs = fh->fh_regs;
36 pll_data = fh->pll_data;
37 dds_mask = fh->pll_data->dds_mask;
38
39 if (new_dds > dds_mask)
40 return -EINVAL;
41
42 /* 1. sync ncpo to DDS of FHCTL */
43 writel((readl(fh_regs->reg_con_pcw) & pll_data->dds_mask) |
44 FH_FHCTLX_PLL_TGL_ORG, fh_regs->reg_dds);
45
46 /* 2. enable DVFS and Hopping control */
47
48 /* enable dvfs mode */
49 fh_set_field(fh_regs->reg_cfg, FH_SFSTRX_EN, 1);
50 /* enable hopping */
51 fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 1);
52
53 /* for slope setting. */
54
55 writel(pll_data->slope0_value, fh_regs->reg_slope0);
56 /* SLOPE1 is for MEMPLL */
57 writel(pll_data->slope1_value, fh_regs->reg_slope1);
58
59 /* 3. switch to hopping control */
60 fh_set_field(fh_regs->reg_hp_en, (0x1U << pll_id),
61 REG_HP_EN_FHCTL_CTR);
62
63 /* 4. set DFS DDS */
64 writel((new_dds) | (FH_FHCTLX_PLL_DVFS_TRI), fh_regs->reg_dvfs);
65
66 /* 4.1 ensure jump to target DDS */
67 /* Wait 1000 us until DDS stable */
68 ret = readl_poll_timeout_atomic(fh_regs->reg_mon, mon_dds,
69 (mon_dds&pll_data->dds_mask) == new_dds, 10, 1000);
70 if (ret)
71 pr_info("ERROR %s: target_dds=0x%x, mon_dds=0x%x",
72 __func__, new_dds, (mon_dds&pll_data->dds_mask));
73
74 if (postdiv == -1) {
75 /* Don't change DIV for fhctl UT */
76 con_temp = readl(fh_regs->reg_con_pcw) & (~dds_mask);
77 con_temp = (con_temp |
78 (readl(fh_regs->reg_mon) & dds_mask) |
79 FH_XXPLL_CON_PCWCHG);
80 } else {
81 con_temp = readl(fh_regs->reg_con_pcw) & (~dds_mask);
82 con_temp = con_temp & ~(POSDIV_MASK << POSDIV_LSB);
83 con_temp = (con_temp |
84 (readl(fh_regs->reg_mon) & dds_mask) |
85 (postdiv & POSDIV_MASK) << POSDIV_LSB |
86 FH_XXPLL_CON_PCWCHG);
87 }
88
89 /* 5. write back to ncpo */
90 writel(con_temp, fh_regs->reg_con_pcw);
91
92 /* 6. switch to APMIXEDSYS control */
93 fh_set_field(fh_regs->reg_hp_en, BIT(pll_id),
94 REG_HP_EN_APMIXEDSYS_CTR);
95
96
97 return 0;
98}
99
100
101static int clk_mt_fh_hw_pll_init(struct clk_mt_fhctl *fh)
102{
103 struct clk_mt_fhctl_regs *fh_regs;
104 struct clk_mt_fhctl_pll_data *pll_data;
105 int pll_id;
106 unsigned int mask;
107
108 pr_debug("mt_fh_pll_init() start ");
109
110 pll_id = fh->pll_data->pll_id;
111 fh_regs = fh->fh_regs;
112 pll_data = fh->pll_data;
113
114 mask = 1 << pll_id;
115
116 if (fh_regs == NULL) {
117 pr_info("ERROR fh_reg (%d) is NULL", pll_id);
118 return -EFAULT;
119 }
120
121 if (pll_data == NULL) {
122 pr_info("ERROR pll_data (%d) is NULL", pll_id);
123 return -EFAULT;
124 }
125
126 fh_set_field(fh_regs->reg_clk_con, mask, 1);
127
128 /* Release software-reset to reset */
129 fh_set_field(fh_regs->reg_rst_con, mask, 0);
130 fh_set_field(fh_regs->reg_rst_con, mask, 1);
131
132 writel(0x00000000, fh_regs->reg_cfg); /* No SSC/FH enabled */
133 writel(0x00000000, fh_regs->reg_updnlmt); /* clr all setting */
134 writel(0x00000000, fh_regs->reg_dds); /* clr all settings */
135
136 /* Check default enable SSC */
137 if (pll_data->pll_default_ssc_rate != 0) {
138 // Default Enable SSC to 0~-N%;
139 fh->hal_ops->pll_ssc_enable(fh, pll_data->pll_default_ssc_rate);
140 }
141
142 return 0;
143}
144
145static int clk_mt_fh_hw_pll_unpause(struct clk_mt_fhctl *fh)
146{
147 int pll_id;
148 struct clk_mt_fhctl_regs *fh_regs;
149 unsigned long flags = 0;
150
151
152 pll_id = fh->pll_data->pll_id;
153 fh_regs = fh->fh_regs;
154
155 if (fh->pll_data->pll_type != FH_PLL_TYPE_CPU) {
156 pr_info("%s not support unpause.", fh->pll_data->pll_name);
157 return -EFAULT;
158 }
159
160 pr_debug("%s fh_pll_id:%d", __func__, pll_id);
161
162 spin_lock_irqsave(fh->lock, flags);
163
164 /* unpause */
165 fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_CFG_PAUSE, 0);
166
167 spin_unlock_irqrestore(fh->lock, flags);
168
169 return 0;
170}
171
172static int clk_mt_fh_hw_pll_pause(struct clk_mt_fhctl *fh)
173{
174 int pll_id;
175 struct clk_mt_fhctl_regs *fh_regs;
176 unsigned long flags = 0;
177
178 pll_id = fh->pll_data->pll_id;
179 fh_regs = fh->fh_regs;
180
181 if (fh->pll_data->pll_type != FH_PLL_TYPE_CPU) {
182 pr_info("%s not support pause.", fh->pll_data->pll_name);
183 return -EFAULT;
184 }
185
186 pr_debug("%s fh_pll_id:%d", __func__, pll_id);
187
188 spin_lock_irqsave(fh->lock, flags);
189
190 /* pause */
191 fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_CFG_PAUSE, 1);
192
193 spin_unlock_irqrestore(fh->lock, flags);
194
195 return 0;
196}
197
198static int clk_mt_fh_hw_pll_ssc_disable(struct clk_mt_fhctl *fh)
199{
200 int pll_id;
201 unsigned long flags = 0;
202 struct clk_mt_fhctl_regs *fh_regs;
203 struct clk_mt_fhctl_pll_data *pll_data;
204
205
206 pll_id = fh->pll_data->pll_id;
207 fh_regs = fh->fh_regs;
208 pll_data = fh->pll_data;
209
210 if (pll_data->pll_type == FH_PLL_TYPE_NOT_SUPPORT) {
211 pr_info("%s not support SSC.", pll_data->pll_name);
212 return -EPERM;
213 }
214
215 pr_debug("fh_pll_id:%d", pll_id);
216
217 spin_lock_irqsave(fh->lock, flags);
218
219 /* Set the relative registers */
220 fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 0);
221 fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 0);
222
223 /* Switch to APMIXEDSYS control */
224 fh_set_field(fh_regs->reg_hp_en, BIT(pll_id),
225 REG_HP_EN_APMIXEDSYS_CTR);
226
227 spin_unlock_irqrestore(fh->lock, flags);
228
229 /* Wait for DDS to be stable */
230 udelay(30);
231
232 return 0;
233}
234
235static int clk_mt_fh_hw_pll_ssc_enable(struct clk_mt_fhctl *fh, int ssc_rate)
236{
237 int pll_id;
238 unsigned long flags = 0;
239 unsigned int dds_mask;
240 unsigned int updnlmt_val;
241 struct clk_mt_fhctl_regs *fh_regs;
242 struct clk_mt_fhctl_pll_data *pll_data;
243
244
245 pll_id = fh->pll_data->pll_id;
246 fh_regs = fh->fh_regs;
247 pll_data = fh->pll_data;
248 dds_mask = fh->pll_data->dds_mask;
249
250 if (pll_data->pll_type == FH_PLL_TYPE_NOT_SUPPORT) {
251 pr_info("%s not support SSC.", pll_data->pll_name);
252 return -EPERM;
253 }
254
255 pr_debug("pll_id:%d ssc:0~-%d%%", pll_id, ssc_rate);
256
257 spin_lock_irqsave(fh->lock, flags);
258
259 /* Set the relative parameter registers (dt/df/upbnd/downbnd) */
260 fh_set_field(fh_regs->reg_cfg, MASK_FRDDSX_DYS, REG_CFG_DF_VAL);
261 fh_set_field(fh_regs->reg_cfg, MASK_FRDDSX_DTS, REG_CFG_DT_VAL);
262
263 writel((readl(fh_regs->reg_con_pcw) & pll_data->dds_mask) |
264 FH_FHCTLX_PLL_TGL_ORG, fh_regs->reg_dds);
265
266 /* Calculate UPDNLMT */
267 updnlmt_val = PERCENT_TO_DDSLMT((readl(fh_regs->reg_dds) &
268 dds_mask), ssc_rate) << 16;
269
270 writel(updnlmt_val, fh_regs->reg_updnlmt);
271
272 /* Switch to FHCTL_CORE controller - Original design */
273 fh_set_field(fh_regs->reg_hp_en, (0x1U << pll_id),
274 REG_HP_EN_FHCTL_CTR);
275
276 /* Enable SSC */
277 fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 1);
278 /* Enable Hopping control */
279 fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 1);
280
281 /* Keep last ssc rate */
282 fh->pll_data->pll_default_ssc_rate = ssc_rate;
283
284 spin_unlock_irqrestore(fh->lock, flags);
285
286 return 0;
287}
288
289static int clk_mt_fh_hw_pll_hopping(struct clk_mt_fhctl *fh,
290 unsigned int new_dds,
291 int postdiv)
292{
293 int pll_id;
294 int ret;
295 unsigned long flags = 0;
296 unsigned int dds_mask;
297 unsigned int updnlmt_val;
298 unsigned int must_restore_ssc = 0;
299 struct clk_mt_fhctl_regs *fh_regs;
300 struct clk_mt_fhctl_pll_data *pll_data;
301
302
303 pll_id = fh->pll_data->pll_id;
304 fh_regs = fh->fh_regs;
305 pll_data = fh->pll_data;
306 dds_mask = pll_data->dds_mask;
307
308 if ((fh->pll_data->pll_type == FH_PLL_TYPE_NOT_SUPPORT) ||
309 (fh->pll_data->pll_type == FH_PLL_TYPE_CPU)) {
310 pr_info("%s not support hopping in AP side",
311 pll_data->pll_name);
312 return -EPERM;
313 }
314
315 pr_debug("fh_pll_id:%d", pll_id);
316
317 spin_lock_irqsave(fh->lock, flags);
318
319 /* Check SSC status. */
320 fh_get_field(fh_regs->reg_cfg, FH_FRDDSX_EN, must_restore_ssc);
321 if (must_restore_ssc) {
322 unsigned int pll_dds = 0;
323 unsigned int mon_dds = 0;
324
325 /* only when SSC is enable, turn off ARMPLL hopping */
326 /* disable SSC mode */
327 fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 0);
328 /* disable dvfs mode */
329 fh_set_field(fh_regs->reg_cfg, FH_SFSTRX_EN, 0);
330 /* disable hp ctl */
331 fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 0);
332
333 pll_dds = (readl(fh_regs->reg_dds)) & pll_data->dds_mask;
334
335 /* Wait 1000 us until DDS stable */
336 ret = readl_poll_timeout_atomic(fh_regs->reg_mon, mon_dds,
337 (mon_dds&pll_data->dds_mask) == pll_dds, 10, 1000);
338 if (ret)
339 pr_info("ERROR %s: target_dds=0x%x, mon_dds=0x%x",
340 __func__, pll_dds, mon_dds&pll_data->dds_mask);
341
342 }
343
344 ret = __mt_fh_hw_hopping(fh, pll_id, new_dds, postdiv);
345 if (ret)
346 pr_info("__mt_fh_hw_hopping error:%d", ret);
347
348
349 /* Enable SSC status, if need. */
350 if (must_restore_ssc) {
351 /* disable SSC mode */
352 fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 0);
353 /* disable dvfs mode */
354 fh_set_field(fh_regs->reg_cfg, FH_SFSTRX_EN, 0);
355 /* disable hp ctl */
356 fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 0);
357
358 fh_set_field(fh_regs->reg_cfg, MASK_FRDDSX_DYS, REG_CFG_DF_VAL);
359 fh_set_field(fh_regs->reg_cfg, MASK_FRDDSX_DTS, REG_CFG_DT_VAL);
360
361 writel((readl(fh_regs->reg_con_pcw) & pll_data->dds_mask) |
362 FH_FHCTLX_PLL_TGL_ORG, fh_regs->reg_dds);
363
364 /* Calculate UPDNLMT */
365 updnlmt_val = PERCENT_TO_DDSLMT(
366 (readl(fh_regs->reg_dds) & dds_mask),
367 pll_data->pll_default_ssc_rate) << 16;
368
369 writel(updnlmt_val, fh_regs->reg_updnlmt);
370
371 /* Switch to FHCTL_CORE controller - Original design */
372 fh_set_field(fh_regs->reg_hp_en,
373 (0x1U << pll_id), REG_HP_EN_FHCTL_CTR);
374
375 /* enable SSC mode */
376 fh_set_field(fh_regs->reg_cfg, FH_FRDDSX_EN, 1);
377 /* enable hopping ctl */
378 fh_set_field(fh_regs->reg_cfg, FH_FHCTLX_EN, 1);
379 }
380
381 spin_unlock_irqrestore(fh->lock, flags);
382
383 return ret;
384}
385
386const struct fhctl_ipi_ops ipi_ops = {
387 .ipi_init = mt_fh_ipi_init,
388};
389
390const struct clk_mt_fhctl_hal_ops mt_fhctl_hal_ops = {
391 .pll_init = clk_mt_fh_hw_pll_init,
392 .pll_unpause = clk_mt_fh_hw_pll_unpause,
393 .pll_pause = clk_mt_fh_hw_pll_pause,
394 .pll_ssc_disable = clk_mt_fh_hw_pll_ssc_disable,
395 .pll_ssc_enable = clk_mt_fh_hw_pll_ssc_enable,
396 .pll_hopping = clk_mt_fh_hw_pll_hopping,
397};
398