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_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)83 static int ccu_factor_determine_rate(struct clk_hw *hw,
84 struct clk_rate_request *req)
85 {
86 req->rate = ccu_factor_recalc_rate(hw, req->best_parent_rate);
87
88 return 0;
89 }
90
ccu_factor_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)91 static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate,
92 unsigned long parent_rate)
93 {
94 return 0;
95 }
96
97 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)98 ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
99 struct clk_hw **best_parent,
100 unsigned long *best_parent_rate,
101 u32 *div_val)
102 {
103 struct ccu_mix *mix = hw_to_ccu_mix(hw);
104 unsigned int parent_num = clk_hw_get_num_parents(hw);
105 struct ccu_div_config *div = &mix->div;
106 u32 div_max = 1 << div->width;
107 unsigned long best_rate = 0;
108
109 for (int i = 0; i < parent_num; i++) {
110 struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
111 unsigned long parent_rate;
112
113 if (!parent)
114 continue;
115
116 parent_rate = clk_hw_get_rate(parent);
117
118 for (int j = 1; j <= div_max; j++) {
119 unsigned long tmp = DIV_ROUND_CLOSEST_ULL(parent_rate, j);
120
121 if (abs(tmp - rate) < abs(best_rate - rate)) {
122 best_rate = tmp;
123
124 if (div_val)
125 *div_val = j - 1;
126
127 if (best_parent) {
128 *best_parent = parent;
129 *best_parent_rate = parent_rate;
130 }
131 }
132 }
133 }
134
135 return best_rate;
136 }
137
ccu_mix_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)138 static int ccu_mix_determine_rate(struct clk_hw *hw,
139 struct clk_rate_request *req)
140 {
141 req->rate = ccu_mix_calc_best_rate(hw, req->rate,
142 &req->best_parent_hw,
143 &req->best_parent_rate,
144 NULL);
145 return 0;
146 }
147
ccu_mix_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)148 static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate,
149 unsigned long parent_rate)
150 {
151 struct ccu_mix *mix = hw_to_ccu_mix(hw);
152 struct ccu_common *common = &mix->common;
153 struct ccu_div_config *div = &mix->div;
154 u32 current_div, target_div, mask;
155
156 ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div);
157
158 current_div = ccu_read(common, ctrl) >> div->shift;
159 current_div &= (1 << div->width) - 1;
160
161 if (current_div == target_div)
162 return 0;
163
164 mask = GENMASK(div->width + div->shift - 1, div->shift);
165
166 ccu_update(common, ctrl, mask, target_div << div->shift);
167
168 return ccu_mix_trigger_fc(hw);
169 }
170
ccu_mux_get_parent(struct clk_hw * hw)171 static u8 ccu_mux_get_parent(struct clk_hw *hw)
172 {
173 struct ccu_mix *mix = hw_to_ccu_mix(hw);
174 struct ccu_mux_config *mux = &mix->mux;
175 u8 parent;
176
177 parent = ccu_read(&mix->common, ctrl) >> mux->shift;
178 parent &= (1 << mux->width) - 1;
179
180 return parent;
181 }
182
ccu_mux_set_parent(struct clk_hw * hw,u8 index)183 static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
184 {
185 struct ccu_mix *mix = hw_to_ccu_mix(hw);
186 struct ccu_mux_config *mux = &mix->mux;
187 u32 mask;
188
189 mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
190
191 ccu_update(&mix->common, ctrl, mask, index << mux->shift);
192
193 return ccu_mix_trigger_fc(hw);
194 }
195
196 const struct clk_ops spacemit_ccu_gate_ops = {
197 .disable = ccu_gate_disable,
198 .enable = ccu_gate_enable,
199 .is_enabled = ccu_gate_is_enabled,
200 };
201
202 const struct clk_ops spacemit_ccu_factor_ops = {
203 .determine_rate = ccu_factor_determine_rate,
204 .recalc_rate = ccu_factor_recalc_rate,
205 .set_rate = ccu_factor_set_rate,
206 };
207
208 const struct clk_ops spacemit_ccu_mux_ops = {
209 .determine_rate = ccu_mix_determine_rate,
210 .get_parent = ccu_mux_get_parent,
211 .set_parent = ccu_mux_set_parent,
212 };
213
214 const struct clk_ops spacemit_ccu_div_ops = {
215 .determine_rate = ccu_mix_determine_rate,
216 .recalc_rate = ccu_div_recalc_rate,
217 .set_rate = ccu_mix_set_rate,
218 };
219
220 const struct clk_ops spacemit_ccu_factor_gate_ops = {
221 .disable = ccu_gate_disable,
222 .enable = ccu_gate_enable,
223 .is_enabled = ccu_gate_is_enabled,
224
225 .determine_rate = ccu_factor_determine_rate,
226 .recalc_rate = ccu_factor_recalc_rate,
227 .set_rate = ccu_factor_set_rate,
228 };
229
230 const struct clk_ops spacemit_ccu_mux_gate_ops = {
231 .disable = ccu_gate_disable,
232 .enable = ccu_gate_enable,
233 .is_enabled = ccu_gate_is_enabled,
234
235 .determine_rate = ccu_mix_determine_rate,
236 .get_parent = ccu_mux_get_parent,
237 .set_parent = ccu_mux_set_parent,
238 };
239
240 const struct clk_ops spacemit_ccu_div_gate_ops = {
241 .disable = ccu_gate_disable,
242 .enable = ccu_gate_enable,
243 .is_enabled = ccu_gate_is_enabled,
244
245 .determine_rate = ccu_mix_determine_rate,
246 .recalc_rate = ccu_div_recalc_rate,
247 .set_rate = ccu_mix_set_rate,
248 };
249
250 const struct clk_ops spacemit_ccu_mux_div_gate_ops = {
251 .disable = ccu_gate_disable,
252 .enable = ccu_gate_enable,
253 .is_enabled = ccu_gate_is_enabled,
254
255 .get_parent = ccu_mux_get_parent,
256 .set_parent = ccu_mux_set_parent,
257
258 .determine_rate = ccu_mix_determine_rate,
259 .recalc_rate = ccu_div_recalc_rate,
260 .set_rate = ccu_mix_set_rate,
261 };
262
263 const struct clk_ops spacemit_ccu_mux_div_ops = {
264 .get_parent = ccu_mux_get_parent,
265 .set_parent = ccu_mux_set_parent,
266
267 .determine_rate = ccu_mix_determine_rate,
268 .recalc_rate = ccu_div_recalc_rate,
269 .set_rate = ccu_mix_set_rate,
270 };
271