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