xref: /linux/drivers/clk/spacemit/ccu_mix.c (revision 9f32a03e3e0d372c520d829dd4da6022fe88832a)
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