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