1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2020, The Linux Foundation. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/clk-provider.h> |
7 | #include <linux/err.h> |
8 | #include <linux/module.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/pm_clock.h> |
11 | #include <linux/pm_runtime.h> |
12 | #include <linux/of.h> |
13 | #include <linux/regmap.h> |
14 | |
15 | #include <dt-bindings/clock/qcom,lpasscorecc-sc7180.h> |
16 | |
17 | #include "clk-alpha-pll.h" |
18 | #include "clk-branch.h" |
19 | #include "clk-rcg.h" |
20 | #include "clk-regmap.h" |
21 | #include "common.h" |
22 | #include "gdsc.h" |
23 | |
24 | enum { |
25 | P_BI_TCXO, |
26 | P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, |
27 | P_SLEEP_CLK, |
28 | }; |
29 | |
30 | static struct pll_vco fabia_vco[] = { |
31 | { 249600000, 2000000000, 0 }, |
32 | }; |
33 | |
34 | static const struct alpha_pll_config lpass_lpaaudio_dig_pll_config = { |
35 | .l = 0x20, |
36 | .alpha = 0x0, |
37 | .config_ctl_val = 0x20485699, |
38 | .config_ctl_hi_val = 0x00002067, |
39 | .test_ctl_val = 0x40000000, |
40 | .test_ctl_hi_val = 0x00000000, |
41 | .user_ctl_val = 0x00005105, |
42 | .user_ctl_hi_val = 0x00004805, |
43 | }; |
44 | |
45 | static const u8 clk_alpha_pll_regs_offset[][PLL_OFF_MAX_REGS] = { |
46 | [CLK_ALPHA_PLL_TYPE_FABIA] = { |
47 | [PLL_OFF_L_VAL] = 0x04, |
48 | [PLL_OFF_CAL_L_VAL] = 0x8, |
49 | [PLL_OFF_USER_CTL] = 0x0c, |
50 | [PLL_OFF_USER_CTL_U] = 0x10, |
51 | [PLL_OFF_USER_CTL_U1] = 0x14, |
52 | [PLL_OFF_CONFIG_CTL] = 0x18, |
53 | [PLL_OFF_CONFIG_CTL_U] = 0x1C, |
54 | [PLL_OFF_CONFIG_CTL_U1] = 0x20, |
55 | [PLL_OFF_TEST_CTL] = 0x24, |
56 | [PLL_OFF_TEST_CTL_U] = 0x28, |
57 | [PLL_OFF_STATUS] = 0x30, |
58 | [PLL_OFF_OPMODE] = 0x38, |
59 | [PLL_OFF_FRAC] = 0x40, |
60 | }, |
61 | }; |
62 | |
63 | static struct clk_alpha_pll lpass_lpaaudio_dig_pll = { |
64 | .offset = 0x1000, |
65 | .vco_table = fabia_vco, |
66 | .num_vco = ARRAY_SIZE(fabia_vco), |
67 | .regs = clk_alpha_pll_regs_offset[CLK_ALPHA_PLL_TYPE_FABIA], |
68 | .clkr = { |
69 | .hw.init = &(struct clk_init_data){ |
70 | .name = "lpass_lpaaudio_dig_pll" , |
71 | .parent_data = &(const struct clk_parent_data){ |
72 | .fw_name = "bi_tcxo" , |
73 | }, |
74 | .num_parents = 1, |
75 | .ops = &clk_alpha_pll_fabia_ops, |
76 | }, |
77 | }, |
78 | }; |
79 | |
80 | static const struct clk_div_table |
81 | post_div_table_lpass_lpaaudio_dig_pll_out_odd[] = { |
82 | { 0x5, 5 }, |
83 | { } |
84 | }; |
85 | |
86 | static struct clk_alpha_pll_postdiv lpass_lpaaudio_dig_pll_out_odd = { |
87 | .offset = 0x1000, |
88 | .post_div_shift = 12, |
89 | .post_div_table = post_div_table_lpass_lpaaudio_dig_pll_out_odd, |
90 | .num_post_div = |
91 | ARRAY_SIZE(post_div_table_lpass_lpaaudio_dig_pll_out_odd), |
92 | .width = 4, |
93 | .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_FABIA], |
94 | .clkr.hw.init = &(struct clk_init_data){ |
95 | .name = "lpass_lpaaudio_dig_pll_out_odd" , |
96 | .parent_hws = (const struct clk_hw*[]) { |
97 | &lpass_lpaaudio_dig_pll.clkr.hw, |
98 | }, |
99 | .num_parents = 1, |
100 | .flags = CLK_SET_RATE_PARENT, |
101 | .ops = &clk_alpha_pll_postdiv_fabia_ops, |
102 | }, |
103 | }; |
104 | |
105 | static const struct parent_map lpass_core_cc_parent_map_0[] = { |
106 | { P_BI_TCXO, 0 }, |
107 | { P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 5 }, |
108 | }; |
109 | |
110 | static const struct clk_parent_data lpass_core_cc_parent_data_0[] = { |
111 | { .fw_name = "bi_tcxo" }, |
112 | { .hw = &lpass_lpaaudio_dig_pll_out_odd.clkr.hw }, |
113 | }; |
114 | |
115 | static const struct parent_map lpass_core_cc_parent_map_2[] = { |
116 | { P_BI_TCXO, 0 }, |
117 | }; |
118 | |
119 | static struct clk_rcg2 core_clk_src = { |
120 | .cmd_rcgr = 0x1d000, |
121 | .mnd_width = 8, |
122 | .hid_width = 5, |
123 | .parent_map = lpass_core_cc_parent_map_2, |
124 | .clkr.hw.init = &(struct clk_init_data){ |
125 | .name = "core_clk_src" , |
126 | .parent_data = &(const struct clk_parent_data){ |
127 | .fw_name = "bi_tcxo" , |
128 | }, |
129 | .num_parents = 1, |
130 | .ops = &clk_rcg2_ops, |
131 | }, |
132 | }; |
133 | |
134 | static const struct freq_tbl ftbl_ext_mclk0_clk_src[] = { |
135 | F(9600000, P_BI_TCXO, 2, 0, 0), |
136 | F(19200000, P_BI_TCXO, 1, 0, 0), |
137 | { } |
138 | }; |
139 | |
140 | static const struct freq_tbl ftbl_ext_lpaif_clk_src[] = { |
141 | F(256000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 32), |
142 | F(512000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 16), |
143 | F(768000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 1, 16), |
144 | F(1024000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 8), |
145 | F(1536000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 1, 8), |
146 | F(2048000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 4), |
147 | F(3072000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 1, 4), |
148 | F(4096000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 2), |
149 | F(6144000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 1, 2), |
150 | F(8192000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 0, 0), |
151 | F(9600000, P_BI_TCXO, 2, 0, 0), |
152 | F(12288000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 0, 0), |
153 | F(19200000, P_BI_TCXO, 1, 0, 0), |
154 | F(24576000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 5, 0, 0), |
155 | { } |
156 | }; |
157 | |
158 | static struct clk_rcg2 ext_mclk0_clk_src = { |
159 | .cmd_rcgr = 0x20000, |
160 | .mnd_width = 8, |
161 | .hid_width = 5, |
162 | .parent_map = lpass_core_cc_parent_map_0, |
163 | .freq_tbl = ftbl_ext_mclk0_clk_src, |
164 | .clkr.hw.init = &(struct clk_init_data){ |
165 | .name = "ext_mclk0_clk_src" , |
166 | .parent_data = lpass_core_cc_parent_data_0, |
167 | .num_parents = 2, |
168 | .flags = CLK_SET_RATE_PARENT, |
169 | .ops = &clk_rcg2_ops, |
170 | }, |
171 | }; |
172 | |
173 | static struct clk_rcg2 lpaif_pri_clk_src = { |
174 | .cmd_rcgr = 0x10000, |
175 | .mnd_width = 16, |
176 | .hid_width = 5, |
177 | .parent_map = lpass_core_cc_parent_map_0, |
178 | .freq_tbl = ftbl_ext_lpaif_clk_src, |
179 | .clkr.hw.init = &(struct clk_init_data){ |
180 | .name = "lpaif_pri_clk_src" , |
181 | .parent_data = lpass_core_cc_parent_data_0, |
182 | .num_parents = 2, |
183 | .flags = CLK_SET_RATE_PARENT, |
184 | .ops = &clk_rcg2_ops, |
185 | }, |
186 | }; |
187 | |
188 | static struct clk_rcg2 lpaif_sec_clk_src = { |
189 | .cmd_rcgr = 0x11000, |
190 | .mnd_width = 16, |
191 | .hid_width = 5, |
192 | .parent_map = lpass_core_cc_parent_map_0, |
193 | .freq_tbl = ftbl_ext_lpaif_clk_src, |
194 | .clkr.hw.init = &(struct clk_init_data){ |
195 | .name = "lpaif_sec_clk_src" , |
196 | .parent_data = lpass_core_cc_parent_data_0, |
197 | .num_parents = 2, |
198 | .flags = CLK_SET_RATE_PARENT, |
199 | .ops = &clk_rcg2_ops, |
200 | }, |
201 | }; |
202 | |
203 | static struct clk_branch lpass_audio_core_ext_mclk0_clk = { |
204 | .halt_reg = 0x20014, |
205 | .halt_check = BRANCH_HALT, |
206 | .hwcg_reg = 0x20014, |
207 | .hwcg_bit = 1, |
208 | .clkr = { |
209 | .enable_reg = 0x20014, |
210 | .enable_mask = BIT(0), |
211 | .hw.init = &(struct clk_init_data){ |
212 | .name = "lpass_audio_core_ext_mclk0_clk" , |
213 | .parent_hws = (const struct clk_hw*[]) { |
214 | &ext_mclk0_clk_src.clkr.hw, |
215 | }, |
216 | .num_parents = 1, |
217 | .flags = CLK_SET_RATE_PARENT, |
218 | .ops = &clk_branch2_ops, |
219 | }, |
220 | }, |
221 | }; |
222 | |
223 | static struct clk_branch lpass_audio_core_lpaif_pri_ibit_clk = { |
224 | .halt_reg = 0x10018, |
225 | .halt_check = BRANCH_HALT, |
226 | .hwcg_reg = 0x10018, |
227 | .hwcg_bit = 1, |
228 | .clkr = { |
229 | .enable_reg = 0x10018, |
230 | .enable_mask = BIT(0), |
231 | .hw.init = &(struct clk_init_data){ |
232 | .name = "lpass_audio_core_lpaif_pri_ibit_clk" , |
233 | .parent_hws = (const struct clk_hw*[]) { |
234 | &lpaif_pri_clk_src.clkr.hw, |
235 | }, |
236 | .num_parents = 1, |
237 | .flags = CLK_SET_RATE_PARENT, |
238 | .ops = &clk_branch2_ops, |
239 | }, |
240 | }, |
241 | }; |
242 | |
243 | static struct clk_branch lpass_audio_core_lpaif_sec_ibit_clk = { |
244 | .halt_reg = 0x11018, |
245 | .halt_check = BRANCH_HALT, |
246 | .hwcg_reg = 0x11018, |
247 | .hwcg_bit = 1, |
248 | .clkr = { |
249 | .enable_reg = 0x11018, |
250 | .enable_mask = BIT(0), |
251 | .hw.init = &(struct clk_init_data){ |
252 | .name = "lpass_audio_core_lpaif_sec_ibit_clk" , |
253 | .parent_hws = (const struct clk_hw*[]) { |
254 | &lpaif_sec_clk_src.clkr.hw, |
255 | }, |
256 | .num_parents = 1, |
257 | .flags = CLK_SET_RATE_PARENT, |
258 | .ops = &clk_branch2_ops, |
259 | }, |
260 | }, |
261 | }; |
262 | |
263 | static struct clk_branch lpass_audio_core_sysnoc_mport_core_clk = { |
264 | .halt_reg = 0x23000, |
265 | .halt_check = BRANCH_HALT, |
266 | .hwcg_reg = 0x23000, |
267 | .hwcg_bit = 1, |
268 | .clkr = { |
269 | .enable_reg = 0x23000, |
270 | .enable_mask = BIT(0), |
271 | .hw.init = &(struct clk_init_data){ |
272 | .name = "lpass_audio_core_sysnoc_mport_core_clk" , |
273 | .parent_hws = (const struct clk_hw*[]) { |
274 | &core_clk_src.clkr.hw, |
275 | }, |
276 | .num_parents = 1, |
277 | .flags = CLK_SET_RATE_PARENT, |
278 | .ops = &clk_branch2_ops, |
279 | }, |
280 | }, |
281 | }; |
282 | |
283 | static struct clk_regmap *lpass_core_cc_sc7180_clocks[] = { |
284 | [EXT_MCLK0_CLK_SRC] = &ext_mclk0_clk_src.clkr, |
285 | [LPAIF_PRI_CLK_SRC] = &lpaif_pri_clk_src.clkr, |
286 | [LPAIF_SEC_CLK_SRC] = &lpaif_sec_clk_src.clkr, |
287 | [CORE_CLK_SRC] = &core_clk_src.clkr, |
288 | [LPASS_AUDIO_CORE_EXT_MCLK0_CLK] = &lpass_audio_core_ext_mclk0_clk.clkr, |
289 | [LPASS_AUDIO_CORE_LPAIF_PRI_IBIT_CLK] = |
290 | &lpass_audio_core_lpaif_pri_ibit_clk.clkr, |
291 | [LPASS_AUDIO_CORE_LPAIF_SEC_IBIT_CLK] = |
292 | &lpass_audio_core_lpaif_sec_ibit_clk.clkr, |
293 | [LPASS_AUDIO_CORE_SYSNOC_MPORT_CORE_CLK] = |
294 | &lpass_audio_core_sysnoc_mport_core_clk.clkr, |
295 | [LPASS_LPAAUDIO_DIG_PLL] = &lpass_lpaaudio_dig_pll.clkr, |
296 | [LPASS_LPAAUDIO_DIG_PLL_OUT_ODD] = &lpass_lpaaudio_dig_pll_out_odd.clkr, |
297 | }; |
298 | |
299 | static struct gdsc lpass_pdc_hm_gdsc = { |
300 | .gdscr = 0x3090, |
301 | .pd = { |
302 | .name = "lpass_pdc_hm_gdsc" , |
303 | }, |
304 | .pwrsts = PWRSTS_OFF_ON, |
305 | .flags = VOTABLE, |
306 | }; |
307 | |
308 | static struct gdsc lpass_audio_hm_gdsc = { |
309 | .gdscr = 0x9090, |
310 | .pd = { |
311 | .name = "lpass_audio_hm_gdsc" , |
312 | }, |
313 | .pwrsts = PWRSTS_OFF_ON, |
314 | }; |
315 | |
316 | static struct gdsc lpass_core_hm_gdsc = { |
317 | .gdscr = 0x0, |
318 | .pd = { |
319 | .name = "lpass_core_hm_gdsc" , |
320 | }, |
321 | .pwrsts = PWRSTS_OFF_ON, |
322 | .flags = RETAIN_FF_ENABLE, |
323 | }; |
324 | |
325 | static struct gdsc *lpass_core_hm_sc7180_gdscs[] = { |
326 | [LPASS_CORE_HM_GDSCR] = &lpass_core_hm_gdsc, |
327 | }; |
328 | |
329 | static struct gdsc *lpass_audio_hm_sc7180_gdscs[] = { |
330 | [LPASS_PDC_HM_GDSCR] = &lpass_pdc_hm_gdsc, |
331 | [LPASS_AUDIO_HM_GDSCR] = &lpass_audio_hm_gdsc, |
332 | }; |
333 | |
334 | static struct regmap_config lpass_core_cc_sc7180_regmap_config = { |
335 | .reg_bits = 32, |
336 | .reg_stride = 4, |
337 | .val_bits = 32, |
338 | .fast_io = true, |
339 | }; |
340 | |
341 | static const struct qcom_cc_desc lpass_core_hm_sc7180_desc = { |
342 | .config = &lpass_core_cc_sc7180_regmap_config, |
343 | .gdscs = lpass_core_hm_sc7180_gdscs, |
344 | .num_gdscs = ARRAY_SIZE(lpass_core_hm_sc7180_gdscs), |
345 | }; |
346 | |
347 | static const struct qcom_cc_desc lpass_core_cc_sc7180_desc = { |
348 | .config = &lpass_core_cc_sc7180_regmap_config, |
349 | .clks = lpass_core_cc_sc7180_clocks, |
350 | .num_clks = ARRAY_SIZE(lpass_core_cc_sc7180_clocks), |
351 | }; |
352 | |
353 | static const struct qcom_cc_desc lpass_audio_hm_sc7180_desc = { |
354 | .config = &lpass_core_cc_sc7180_regmap_config, |
355 | .gdscs = lpass_audio_hm_sc7180_gdscs, |
356 | .num_gdscs = ARRAY_SIZE(lpass_audio_hm_sc7180_gdscs), |
357 | }; |
358 | |
359 | static int lpass_setup_runtime_pm(struct platform_device *pdev) |
360 | { |
361 | int ret; |
362 | |
363 | pm_runtime_use_autosuspend(dev: &pdev->dev); |
364 | pm_runtime_set_autosuspend_delay(dev: &pdev->dev, delay: 500); |
365 | |
366 | ret = devm_pm_runtime_enable(dev: &pdev->dev); |
367 | if (ret) |
368 | return ret; |
369 | |
370 | ret = devm_pm_clk_create(dev: &pdev->dev); |
371 | if (ret) |
372 | return ret; |
373 | |
374 | ret = pm_clk_add(dev: &pdev->dev, con_id: "iface" ); |
375 | if (ret < 0) |
376 | dev_err(&pdev->dev, "failed to acquire iface clock\n" ); |
377 | |
378 | return pm_runtime_resume_and_get(dev: &pdev->dev); |
379 | } |
380 | |
381 | static int lpass_core_cc_sc7180_probe(struct platform_device *pdev) |
382 | { |
383 | const struct qcom_cc_desc *desc; |
384 | struct regmap *regmap; |
385 | int ret; |
386 | |
387 | ret = lpass_setup_runtime_pm(pdev); |
388 | if (ret) |
389 | return ret; |
390 | |
391 | lpass_core_cc_sc7180_regmap_config.name = "lpass_audio_cc" ; |
392 | desc = &lpass_audio_hm_sc7180_desc; |
393 | ret = qcom_cc_probe_by_index(pdev, index: 1, desc); |
394 | if (ret) |
395 | goto exit; |
396 | |
397 | lpass_core_cc_sc7180_regmap_config.name = "lpass_core_cc" ; |
398 | regmap = qcom_cc_map(pdev, desc: &lpass_core_cc_sc7180_desc); |
399 | if (IS_ERR(ptr: regmap)) { |
400 | ret = PTR_ERR(ptr: regmap); |
401 | goto exit; |
402 | } |
403 | |
404 | /* Keep some clocks always-on */ |
405 | qcom_branch_set_clk_en(regmap, cbcr: 0x24000); /* LPASS_AUDIO_CORE_SYSNOC_SWAY_CORE_CLK */ |
406 | |
407 | /* PLL settings */ |
408 | regmap_write(map: regmap, reg: 0x1008, val: 0x20); |
409 | regmap_update_bits(map: regmap, reg: 0x1014, BIT(0), BIT(0)); |
410 | |
411 | clk_fabia_pll_configure(pll: &lpass_lpaaudio_dig_pll, regmap, |
412 | config: &lpass_lpaaudio_dig_pll_config); |
413 | |
414 | ret = qcom_cc_really_probe(pdev, desc: &lpass_core_cc_sc7180_desc, regmap); |
415 | |
416 | pm_runtime_mark_last_busy(dev: &pdev->dev); |
417 | exit: |
418 | pm_runtime_put_autosuspend(dev: &pdev->dev); |
419 | |
420 | return ret; |
421 | } |
422 | |
423 | static int lpass_hm_core_probe(struct platform_device *pdev) |
424 | { |
425 | const struct qcom_cc_desc *desc; |
426 | int ret; |
427 | |
428 | ret = lpass_setup_runtime_pm(pdev); |
429 | if (ret) |
430 | return ret; |
431 | |
432 | lpass_core_cc_sc7180_regmap_config.name = "lpass_hm_core" ; |
433 | desc = &lpass_core_hm_sc7180_desc; |
434 | |
435 | ret = qcom_cc_probe_by_index(pdev, index: 0, desc); |
436 | |
437 | pm_runtime_mark_last_busy(dev: &pdev->dev); |
438 | pm_runtime_put_autosuspend(dev: &pdev->dev); |
439 | |
440 | return ret; |
441 | } |
442 | |
443 | static const struct of_device_id lpass_hm_sc7180_match_table[] = { |
444 | { |
445 | .compatible = "qcom,sc7180-lpasshm" , |
446 | }, |
447 | { } |
448 | }; |
449 | MODULE_DEVICE_TABLE(of, lpass_hm_sc7180_match_table); |
450 | |
451 | static const struct of_device_id lpass_core_cc_sc7180_match_table[] = { |
452 | { |
453 | .compatible = "qcom,sc7180-lpasscorecc" , |
454 | }, |
455 | { } |
456 | }; |
457 | MODULE_DEVICE_TABLE(of, lpass_core_cc_sc7180_match_table); |
458 | |
459 | static const struct dev_pm_ops lpass_pm_ops = { |
460 | SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL) |
461 | }; |
462 | |
463 | static struct platform_driver lpass_core_cc_sc7180_driver = { |
464 | .probe = lpass_core_cc_sc7180_probe, |
465 | .driver = { |
466 | .name = "lpass_core_cc-sc7180" , |
467 | .of_match_table = lpass_core_cc_sc7180_match_table, |
468 | .pm = &lpass_pm_ops, |
469 | }, |
470 | }; |
471 | |
472 | static struct platform_driver lpass_hm_sc7180_driver = { |
473 | .probe = lpass_hm_core_probe, |
474 | .driver = { |
475 | .name = "lpass_hm-sc7180" , |
476 | .of_match_table = lpass_hm_sc7180_match_table, |
477 | .pm = &lpass_pm_ops, |
478 | }, |
479 | }; |
480 | |
481 | static int __init lpass_sc7180_init(void) |
482 | { |
483 | int ret; |
484 | |
485 | ret = platform_driver_register(&lpass_core_cc_sc7180_driver); |
486 | if (ret) |
487 | return ret; |
488 | |
489 | ret = platform_driver_register(&lpass_hm_sc7180_driver); |
490 | if (ret) { |
491 | platform_driver_unregister(&lpass_core_cc_sc7180_driver); |
492 | return ret; |
493 | } |
494 | |
495 | return 0; |
496 | } |
497 | subsys_initcall(lpass_sc7180_init); |
498 | |
499 | static void __exit lpass_sc7180_exit(void) |
500 | { |
501 | platform_driver_unregister(&lpass_hm_sc7180_driver); |
502 | platform_driver_unregister(&lpass_core_cc_sc7180_driver); |
503 | } |
504 | module_exit(lpass_sc7180_exit); |
505 | |
506 | MODULE_DESCRIPTION("QTI LPASS_CORE_CC SC7180 Driver" ); |
507 | MODULE_LICENSE("GPL v2" ); |
508 | |