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