1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2024 SpacemiT Technology Co. Ltd
4 * Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
5 *
6 * MIX clock type is the combination of mux, factor or divider, and gate
7 */
8
9 #include <linux/clk-provider.h>
10
11 #include "ccu_mix.h"
12
13 #define MIX_FC_TIMEOUT_US 10000
14 #define MIX_FC_DELAY_US 5
15
ccu_gate_disable(struct clk_hw * hw)16 static void ccu_gate_disable(struct clk_hw *hw)
17 {
18 struct ccu_mix *mix = hw_to_ccu_mix(hw);
19
20 ccu_update(&mix->common, ctrl, mix->gate.mask, 0);
21 }
22
ccu_gate_enable(struct clk_hw * hw)23 static int ccu_gate_enable(struct clk_hw *hw)
24 {
25 struct ccu_mix *mix = hw_to_ccu_mix(hw);
26 struct ccu_gate_config *gate = &mix->gate;
27
28 ccu_update(&mix->common, ctrl, gate->mask, gate->mask);
29
30 return 0;
31 }
32
ccu_gate_is_enabled(struct clk_hw * hw)33 static int ccu_gate_is_enabled(struct clk_hw *hw)
34 {
35 struct ccu_mix *mix = hw_to_ccu_mix(hw);
36 struct ccu_gate_config *gate = &mix->gate;
37
38 return (ccu_read(&mix->common, ctrl) & gate->mask) == gate->mask;
39 }
40
ccu_factor_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)41 static unsigned long ccu_factor_recalc_rate(struct clk_hw *hw,
42 unsigned long parent_rate)
43 {
44 struct ccu_mix *mix = hw_to_ccu_mix(hw);
45
46 return parent_rate * mix->factor.mul / mix->factor.div;
47 }
48
ccu_div_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)49 static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
50 unsigned long parent_rate)
51 {
52 struct ccu_mix *mix = hw_to_ccu_mix(hw);
53 struct ccu_div_config *div = &mix->div;
54 unsigned long val;
55
56 val = ccu_read(&mix->common, ctrl) >> div->shift;
57 val &= (1 << div->width) - 1;
58
59 return divider_recalc_rate(hw, parent_rate, val, NULL, 0, div->width);
60 }
61
62 /*
63 * Some clocks require a "FC" (frequency change) bit to be set after changing
64 * their rates or reparenting. This bit will be automatically cleared by
65 * hardware in MIX_FC_TIMEOUT_US, which indicates the operation is completed.
66 */
ccu_mix_trigger_fc(struct clk_hw * hw)67 static int ccu_mix_trigger_fc(struct clk_hw *hw)
68 {
69 struct ccu_common *common = hw_to_ccu_common(hw);
70 unsigned int val;
71
72 if (common->reg_fc)
73 return 0;
74
75 ccu_update(common, fc, common->mask_fc, common->mask_fc);
76
77 return regmap_read_poll_timeout_atomic(common->regmap, common->reg_fc,
78 val, !(val & common->mask_fc),
79 MIX_FC_DELAY_US,
80 MIX_FC_TIMEOUT_US);
81 }
82
ccu_factor_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)83 static long ccu_factor_round_rate(struct clk_hw *hw, unsigned long rate,
84 unsigned long *prate)
85 {
86 return ccu_factor_recalc_rate(hw, *prate);
87 }
88
ccu_factor_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)89 static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate,
90 unsigned long parent_rate)
91 {
92 return 0;
93 }
94
95 static unsigned long
ccu_mix_calc_best_rate(struct clk_hw * hw,unsigned long rate,struct clk_hw ** best_parent,unsigned long * best_parent_rate,u32 * div_val)96 ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
97 struct clk_hw **best_parent,
98 unsigned long *best_parent_rate,
99 u32 *div_val)
100 {
101 struct ccu_mix *mix = hw_to_ccu_mix(hw);
102 unsigned int parent_num = clk_hw_get_num_parents(hw);
103 struct ccu_div_config *div = &mix->div;
104 u32 div_max = 1 << div->width;
105 unsigned long best_rate = 0;
106
107 for (int i = 0; i < parent_num; i++) {
108 struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
109 unsigned long parent_rate;
110
111 if (!parent)
112 continue;
113
114 parent_rate = clk_hw_get_rate(parent);
115
116 for (int j = 1; j <= div_max; j++) {
117 unsigned long tmp = DIV_ROUND_CLOSEST_ULL(parent_rate, j);
118
119 if (abs(tmp - rate) < abs(best_rate - rate)) {
120 best_rate = tmp;
121
122 if (div_val)
123 *div_val = j - 1;
124
125 if (best_parent) {
126 *best_parent = parent;
127 *best_parent_rate = parent_rate;
128 }
129 }
130 }
131 }
132
133 return best_rate;
134 }
135
ccu_mix_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)136 static int ccu_mix_determine_rate(struct clk_hw *hw,
137 struct clk_rate_request *req)
138 {
139 req->rate = ccu_mix_calc_best_rate(hw, req->rate,
140 &req->best_parent_hw,
141 &req->best_parent_rate,
142 NULL);
143 return 0;
144 }
145
ccu_mix_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)146 static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate,
147 unsigned long parent_rate)
148 {
149 struct ccu_mix *mix = hw_to_ccu_mix(hw);
150 struct ccu_common *common = &mix->common;
151 struct ccu_div_config *div = &mix->div;
152 u32 current_div, target_div, mask;
153
154 ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div);
155
156 current_div = ccu_read(common, ctrl) >> div->shift;
157 current_div &= (1 << div->width) - 1;
158
159 if (current_div == target_div)
160 return 0;
161
162 mask = GENMASK(div->width + div->shift - 1, div->shift);
163
164 ccu_update(common, ctrl, mask, target_div << div->shift);
165
166 return ccu_mix_trigger_fc(hw);
167 }
168
ccu_mux_get_parent(struct clk_hw * hw)169 static u8 ccu_mux_get_parent(struct clk_hw *hw)
170 {
171 struct ccu_mix *mix = hw_to_ccu_mix(hw);
172 struct ccu_mux_config *mux = &mix->mux;
173 u8 parent;
174
175 parent = ccu_read(&mix->common, ctrl) >> mux->shift;
176 parent &= (1 << mux->width) - 1;
177
178 return parent;
179 }
180
ccu_mux_set_parent(struct clk_hw * hw,u8 index)181 static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
182 {
183 struct ccu_mix *mix = hw_to_ccu_mix(hw);
184 struct ccu_mux_config *mux = &mix->mux;
185 u32 mask;
186
187 mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
188
189 ccu_update(&mix->common, ctrl, mask, index << mux->shift);
190
191 return ccu_mix_trigger_fc(hw);
192 }
193
194 const struct clk_ops spacemit_ccu_gate_ops = {
195 .disable = ccu_gate_disable,
196 .enable = ccu_gate_enable,
197 .is_enabled = ccu_gate_is_enabled,
198 };
199
200 const struct clk_ops spacemit_ccu_factor_ops = {
201 .round_rate = ccu_factor_round_rate,
202 .recalc_rate = ccu_factor_recalc_rate,
203 .set_rate = ccu_factor_set_rate,
204 };
205
206 const struct clk_ops spacemit_ccu_mux_ops = {
207 .determine_rate = ccu_mix_determine_rate,
208 .get_parent = ccu_mux_get_parent,
209 .set_parent = ccu_mux_set_parent,
210 };
211
212 const struct clk_ops spacemit_ccu_div_ops = {
213 .determine_rate = ccu_mix_determine_rate,
214 .recalc_rate = ccu_div_recalc_rate,
215 .set_rate = ccu_mix_set_rate,
216 };
217
218 const struct clk_ops spacemit_ccu_factor_gate_ops = {
219 .disable = ccu_gate_disable,
220 .enable = ccu_gate_enable,
221 .is_enabled = ccu_gate_is_enabled,
222
223 .round_rate = ccu_factor_round_rate,
224 .recalc_rate = ccu_factor_recalc_rate,
225 .set_rate = ccu_factor_set_rate,
226 };
227
228 const struct clk_ops spacemit_ccu_mux_gate_ops = {
229 .disable = ccu_gate_disable,
230 .enable = ccu_gate_enable,
231 .is_enabled = ccu_gate_is_enabled,
232
233 .determine_rate = ccu_mix_determine_rate,
234 .get_parent = ccu_mux_get_parent,
235 .set_parent = ccu_mux_set_parent,
236 };
237
238 const struct clk_ops spacemit_ccu_div_gate_ops = {
239 .disable = ccu_gate_disable,
240 .enable = ccu_gate_enable,
241 .is_enabled = ccu_gate_is_enabled,
242
243 .determine_rate = ccu_mix_determine_rate,
244 .recalc_rate = ccu_div_recalc_rate,
245 .set_rate = ccu_mix_set_rate,
246 };
247
248 const struct clk_ops spacemit_ccu_mux_div_gate_ops = {
249 .disable = ccu_gate_disable,
250 .enable = ccu_gate_enable,
251 .is_enabled = ccu_gate_is_enabled,
252
253 .get_parent = ccu_mux_get_parent,
254 .set_parent = ccu_mux_set_parent,
255
256 .determine_rate = ccu_mix_determine_rate,
257 .recalc_rate = ccu_div_recalc_rate,
258 .set_rate = ccu_mix_set_rate,
259 };
260
261 const struct clk_ops spacemit_ccu_mux_div_ops = {
262 .get_parent = ccu_mux_get_parent,
263 .set_parent = ccu_mux_set_parent,
264
265 .determine_rate = ccu_mix_determine_rate,
266 .recalc_rate = ccu_div_recalc_rate,
267 .set_rate = ccu_mix_set_rate,
268 };
269