Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
16static 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
25static 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
35static 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
45static 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
53static 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 */
71static 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
87static 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
95static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate,
96 unsigned long parent_rate)
97{
98 return 0;
99}
100
101static unsigned long
102ccu_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
142static 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
152static 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
175static 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
187static 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
200const 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};
205EXPORT_SYMBOL_NS_GPL(spacemit_ccu_gate_ops, "CLK_SPACEMIT");
206
207const 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};
212EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_ops, "CLK_SPACEMIT");
213
214const 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};
219EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_ops, "CLK_SPACEMIT");
220
221const 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};
226EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_ops, "CLK_SPACEMIT");
227
228const 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};
237EXPORT_SYMBOL_NS_GPL(spacemit_ccu_factor_gate_ops, "CLK_SPACEMIT");
238
239const 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};
248EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_gate_ops, "CLK_SPACEMIT");
249
250const 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};
259EXPORT_SYMBOL_NS_GPL(spacemit_ccu_div_gate_ops, "CLK_SPACEMIT");
260
261const 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};
273EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_gate_ops, "CLK_SPACEMIT");
274
275const 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};
283EXPORT_SYMBOL_NS_GPL(spacemit_ccu_mux_div_ops, "CLK_SPACEMIT");