1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2018, The Linux Foundation. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/kernel.h> |
7 | #include <linux/err.h> |
8 | #include <linux/platform_device.h> |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/regmap.h> |
11 | #include <linux/module.h> |
12 | #include <linux/clk.h> |
13 | #include <linux/soc/qcom/smem.h> |
14 | |
15 | #include <dt-bindings/clock/qcom,apss-ipq.h> |
16 | #include <dt-bindings/arm/qcom,ids.h> |
17 | |
18 | #include "common.h" |
19 | #include "clk-regmap.h" |
20 | #include "clk-branch.h" |
21 | #include "clk-alpha-pll.h" |
22 | #include "clk-rcg.h" |
23 | |
24 | enum { |
25 | P_XO, |
26 | P_GPLL0, |
27 | P_APSS_PLL_EARLY, |
28 | }; |
29 | |
30 | static const struct clk_parent_data parents_apcs_alias0_clk_src[] = { |
31 | { .fw_name = "xo" }, |
32 | { .fw_name = "gpll0" }, |
33 | { .fw_name = "pll" }, |
34 | }; |
35 | |
36 | static const struct parent_map parents_apcs_alias0_clk_src_map[] = { |
37 | { P_XO, 0 }, |
38 | { P_GPLL0, 4 }, |
39 | { P_APSS_PLL_EARLY, 5 }, |
40 | }; |
41 | |
42 | static struct clk_rcg2 apcs_alias0_clk_src = { |
43 | .cmd_rcgr = 0x0050, |
44 | .hid_width = 5, |
45 | .parent_map = parents_apcs_alias0_clk_src_map, |
46 | .clkr.hw.init = &(struct clk_init_data){ |
47 | .name = "apcs_alias0_clk_src" , |
48 | .parent_data = parents_apcs_alias0_clk_src, |
49 | .num_parents = ARRAY_SIZE(parents_apcs_alias0_clk_src), |
50 | .ops = &clk_rcg2_mux_closest_ops, |
51 | .flags = CLK_SET_RATE_PARENT, |
52 | }, |
53 | }; |
54 | |
55 | static struct clk_branch apcs_alias0_core_clk = { |
56 | .halt_reg = 0x0058, |
57 | .clkr = { |
58 | .enable_reg = 0x0058, |
59 | .enable_mask = BIT(0), |
60 | .hw.init = &(struct clk_init_data){ |
61 | .name = "apcs_alias0_core_clk" , |
62 | .parent_hws = (const struct clk_hw *[]){ |
63 | &apcs_alias0_clk_src.clkr.hw }, |
64 | .num_parents = 1, |
65 | .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, |
66 | .ops = &clk_branch2_ops, |
67 | }, |
68 | }, |
69 | }; |
70 | |
71 | static const struct regmap_config apss_ipq6018_regmap_config = { |
72 | .reg_bits = 32, |
73 | .reg_stride = 4, |
74 | .val_bits = 32, |
75 | .max_register = 0x1000, |
76 | .fast_io = true, |
77 | }; |
78 | |
79 | static struct clk_regmap *apss_ipq6018_clks[] = { |
80 | [APCS_ALIAS0_CLK_SRC] = &apcs_alias0_clk_src.clkr, |
81 | [APCS_ALIAS0_CORE_CLK] = &apcs_alias0_core_clk.clkr, |
82 | }; |
83 | |
84 | static const struct qcom_cc_desc apss_ipq6018_desc = { |
85 | .config = &apss_ipq6018_regmap_config, |
86 | .clks = apss_ipq6018_clks, |
87 | .num_clks = ARRAY_SIZE(apss_ipq6018_clks), |
88 | }; |
89 | |
90 | static int cpu_clk_notifier_fn(struct notifier_block *nb, unsigned long action, |
91 | void *data) |
92 | { |
93 | struct clk_hw *hw; |
94 | u8 index; |
95 | int err; |
96 | |
97 | if (action == PRE_RATE_CHANGE) |
98 | index = P_GPLL0; |
99 | else if (action == POST_RATE_CHANGE || action == ABORT_RATE_CHANGE) |
100 | index = P_APSS_PLL_EARLY; |
101 | else |
102 | return NOTIFY_OK; |
103 | |
104 | hw = &apcs_alias0_clk_src.clkr.hw; |
105 | err = clk_rcg2_mux_closest_ops.set_parent(hw, index); |
106 | |
107 | return notifier_from_errno(err); |
108 | } |
109 | |
110 | static int apss_ipq6018_probe(struct platform_device *pdev) |
111 | { |
112 | struct clk_hw *hw = &apcs_alias0_clk_src.clkr.hw; |
113 | struct notifier_block *cpu_clk_notifier; |
114 | struct regmap *regmap; |
115 | u32 soc_id; |
116 | int ret; |
117 | |
118 | ret = qcom_smem_get_soc_id(id: &soc_id); |
119 | if (ret) |
120 | return ret; |
121 | |
122 | regmap = dev_get_regmap(dev: pdev->dev.parent, NULL); |
123 | if (!regmap) |
124 | return -ENODEV; |
125 | |
126 | ret = qcom_cc_really_probe(pdev, desc: &apss_ipq6018_desc, regmap); |
127 | if (ret) |
128 | return ret; |
129 | |
130 | switch (soc_id) { |
131 | /* Only below variants of IPQ53xx support scaling */ |
132 | case QCOM_ID_IPQ5332: |
133 | case QCOM_ID_IPQ5322: |
134 | case QCOM_ID_IPQ5300: |
135 | cpu_clk_notifier = devm_kzalloc(dev: &pdev->dev, |
136 | size: sizeof(*cpu_clk_notifier), |
137 | GFP_KERNEL); |
138 | if (!cpu_clk_notifier) |
139 | return -ENOMEM; |
140 | |
141 | cpu_clk_notifier->notifier_call = cpu_clk_notifier_fn; |
142 | |
143 | ret = devm_clk_notifier_register(dev: &pdev->dev, clk: hw->clk, nb: cpu_clk_notifier); |
144 | if (ret) |
145 | return ret; |
146 | break; |
147 | default: |
148 | break; |
149 | } |
150 | |
151 | return 0; |
152 | } |
153 | |
154 | static struct platform_driver apss_ipq6018_driver = { |
155 | .probe = apss_ipq6018_probe, |
156 | .driver = { |
157 | .name = "qcom,apss-ipq6018-clk" , |
158 | }, |
159 | }; |
160 | |
161 | module_platform_driver(apss_ipq6018_driver); |
162 | |
163 | MODULE_DESCRIPTION("QCOM APSS IPQ 6018 CLK Driver" ); |
164 | MODULE_LICENSE("GPL v2" ); |
165 | |