1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2016 Maxime Ripard |
4 | * Maxime Ripard <maxime.ripard@free-electrons.com> |
5 | */ |
6 | |
7 | #include <linux/clk.h> |
8 | #include <linux/clk-provider.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/io.h> |
11 | |
12 | #include "ccu_gate.h" |
13 | #include "ccu_mux.h" |
14 | |
15 | #define CCU_MUX_KEY_VALUE 0x16aa0000 |
16 | |
17 | static u16 ccu_mux_get_prediv(struct ccu_common *common, |
18 | struct ccu_mux_internal *cm, |
19 | int parent_index) |
20 | { |
21 | u16 prediv = 1; |
22 | u32 reg; |
23 | |
24 | if (!((common->features & CCU_FEATURE_FIXED_PREDIV) || |
25 | (common->features & CCU_FEATURE_VARIABLE_PREDIV) || |
26 | (common->features & CCU_FEATURE_ALL_PREDIV))) |
27 | return 1; |
28 | |
29 | if (common->features & CCU_FEATURE_ALL_PREDIV) |
30 | return common->prediv; |
31 | |
32 | reg = readl(addr: common->base + common->reg); |
33 | if (parent_index < 0) { |
34 | parent_index = reg >> cm->shift; |
35 | parent_index &= (1 << cm->width) - 1; |
36 | } |
37 | |
38 | if (common->features & CCU_FEATURE_FIXED_PREDIV) { |
39 | int i; |
40 | |
41 | for (i = 0; i < cm->n_predivs; i++) |
42 | if (parent_index == cm->fixed_predivs[i].index) |
43 | prediv = cm->fixed_predivs[i].div; |
44 | } |
45 | |
46 | if (common->features & CCU_FEATURE_VARIABLE_PREDIV) { |
47 | int i; |
48 | |
49 | for (i = 0; i < cm->n_var_predivs; i++) |
50 | if (parent_index == cm->var_predivs[i].index) { |
51 | u8 div; |
52 | |
53 | div = reg >> cm->var_predivs[i].shift; |
54 | div &= (1 << cm->var_predivs[i].width) - 1; |
55 | prediv = div + 1; |
56 | } |
57 | } |
58 | |
59 | return prediv; |
60 | } |
61 | |
62 | unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common, |
63 | struct ccu_mux_internal *cm, |
64 | int parent_index, |
65 | unsigned long parent_rate) |
66 | { |
67 | return parent_rate / ccu_mux_get_prediv(common, cm, parent_index); |
68 | } |
69 | EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_apply_prediv, SUNXI_CCU); |
70 | |
71 | static unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common, |
72 | struct ccu_mux_internal *cm, |
73 | int parent_index, |
74 | unsigned long parent_rate) |
75 | { |
76 | return parent_rate * ccu_mux_get_prediv(common, cm, parent_index); |
77 | } |
78 | |
79 | int ccu_mux_helper_determine_rate(struct ccu_common *common, |
80 | struct ccu_mux_internal *cm, |
81 | struct clk_rate_request *req, |
82 | unsigned long (*round)(struct ccu_mux_internal *, |
83 | struct clk_hw *, |
84 | unsigned long *, |
85 | unsigned long, |
86 | void *), |
87 | void *data) |
88 | { |
89 | unsigned long best_parent_rate = 0, best_rate = 0; |
90 | struct clk_hw *best_parent, *hw = &common->hw; |
91 | unsigned int i; |
92 | |
93 | if (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT) { |
94 | unsigned long adj_parent_rate; |
95 | |
96 | best_parent = clk_hw_get_parent(hw); |
97 | best_parent_rate = clk_hw_get_rate(hw: best_parent); |
98 | adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, -1, |
99 | best_parent_rate); |
100 | |
101 | best_rate = round(cm, best_parent, &adj_parent_rate, |
102 | req->rate, data); |
103 | |
104 | /* |
105 | * adj_parent_rate might have been modified by our clock. |
106 | * Unapply the pre-divider if there's one, and give |
107 | * the actual frequency the parent needs to run at. |
108 | */ |
109 | best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, parent_index: -1, |
110 | parent_rate: adj_parent_rate); |
111 | |
112 | goto out; |
113 | } |
114 | |
115 | for (i = 0; i < clk_hw_get_num_parents(hw); i++) { |
116 | unsigned long tmp_rate, parent_rate; |
117 | struct clk_hw *parent; |
118 | |
119 | parent = clk_hw_get_parent_by_index(hw, index: i); |
120 | if (!parent) |
121 | continue; |
122 | |
123 | parent_rate = ccu_mux_helper_apply_prediv(common, cm, i, |
124 | clk_hw_get_rate(hw: parent)); |
125 | |
126 | tmp_rate = round(cm, parent, &parent_rate, req->rate, data); |
127 | |
128 | /* |
129 | * parent_rate might have been modified by our clock. |
130 | * Unapply the pre-divider if there's one, and give |
131 | * the actual frequency the parent needs to run at. |
132 | */ |
133 | parent_rate = ccu_mux_helper_unapply_prediv(common, cm, parent_index: i, |
134 | parent_rate); |
135 | if (tmp_rate == req->rate) { |
136 | best_parent = parent; |
137 | best_parent_rate = parent_rate; |
138 | best_rate = tmp_rate; |
139 | goto out; |
140 | } |
141 | |
142 | if (ccu_is_better_rate(common, target_rate: req->rate, current_rate: tmp_rate, best_rate)) { |
143 | best_rate = tmp_rate; |
144 | best_parent_rate = parent_rate; |
145 | best_parent = parent; |
146 | } |
147 | } |
148 | |
149 | if (best_rate == 0) |
150 | return -EINVAL; |
151 | |
152 | out: |
153 | req->best_parent_hw = best_parent; |
154 | req->best_parent_rate = best_parent_rate; |
155 | req->rate = best_rate; |
156 | return 0; |
157 | } |
158 | EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_determine_rate, SUNXI_CCU); |
159 | |
160 | u8 ccu_mux_helper_get_parent(struct ccu_common *common, |
161 | struct ccu_mux_internal *cm) |
162 | { |
163 | u32 reg; |
164 | u8 parent; |
165 | |
166 | reg = readl(addr: common->base + common->reg); |
167 | parent = reg >> cm->shift; |
168 | parent &= (1 << cm->width) - 1; |
169 | |
170 | if (cm->table) { |
171 | int num_parents = clk_hw_get_num_parents(hw: &common->hw); |
172 | int i; |
173 | |
174 | for (i = 0; i < num_parents; i++) |
175 | if (cm->table[i] == parent) |
176 | return i; |
177 | } |
178 | |
179 | return parent; |
180 | } |
181 | EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_get_parent, SUNXI_CCU); |
182 | |
183 | int ccu_mux_helper_set_parent(struct ccu_common *common, |
184 | struct ccu_mux_internal *cm, |
185 | u8 index) |
186 | { |
187 | unsigned long flags; |
188 | u32 reg; |
189 | |
190 | if (cm->table) |
191 | index = cm->table[index]; |
192 | |
193 | spin_lock_irqsave(common->lock, flags); |
194 | |
195 | reg = readl(addr: common->base + common->reg); |
196 | |
197 | /* The key field always reads as zero. */ |
198 | if (common->features & CCU_FEATURE_KEY_FIELD) |
199 | reg |= CCU_MUX_KEY_VALUE; |
200 | |
201 | reg &= ~GENMASK(cm->width + cm->shift - 1, cm->shift); |
202 | writel(val: reg | (index << cm->shift), addr: common->base + common->reg); |
203 | |
204 | spin_unlock_irqrestore(lock: common->lock, flags); |
205 | |
206 | return 0; |
207 | } |
208 | EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_set_parent, SUNXI_CCU); |
209 | |
210 | static void ccu_mux_disable(struct clk_hw *hw) |
211 | { |
212 | struct ccu_mux *cm = hw_to_ccu_mux(hw); |
213 | |
214 | return ccu_gate_helper_disable(common: &cm->common, gate: cm->enable); |
215 | } |
216 | |
217 | static int ccu_mux_enable(struct clk_hw *hw) |
218 | { |
219 | struct ccu_mux *cm = hw_to_ccu_mux(hw); |
220 | |
221 | return ccu_gate_helper_enable(common: &cm->common, gate: cm->enable); |
222 | } |
223 | |
224 | static int ccu_mux_is_enabled(struct clk_hw *hw) |
225 | { |
226 | struct ccu_mux *cm = hw_to_ccu_mux(hw); |
227 | |
228 | return ccu_gate_helper_is_enabled(common: &cm->common, gate: cm->enable); |
229 | } |
230 | |
231 | static u8 ccu_mux_get_parent(struct clk_hw *hw) |
232 | { |
233 | struct ccu_mux *cm = hw_to_ccu_mux(hw); |
234 | |
235 | return ccu_mux_helper_get_parent(&cm->common, &cm->mux); |
236 | } |
237 | |
238 | static int ccu_mux_set_parent(struct clk_hw *hw, u8 index) |
239 | { |
240 | struct ccu_mux *cm = hw_to_ccu_mux(hw); |
241 | |
242 | return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index); |
243 | } |
244 | |
245 | static int ccu_mux_determine_rate(struct clk_hw *hw, |
246 | struct clk_rate_request *req) |
247 | { |
248 | struct ccu_mux *cm = hw_to_ccu_mux(hw); |
249 | |
250 | if (cm->common.features & CCU_FEATURE_CLOSEST_RATE) |
251 | return clk_mux_determine_rate_flags(hw, req, CLK_MUX_ROUND_CLOSEST); |
252 | |
253 | return clk_mux_determine_rate_flags(hw, req, flags: 0); |
254 | } |
255 | |
256 | static unsigned long ccu_mux_recalc_rate(struct clk_hw *hw, |
257 | unsigned long parent_rate) |
258 | { |
259 | struct ccu_mux *cm = hw_to_ccu_mux(hw); |
260 | |
261 | return ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1, |
262 | parent_rate); |
263 | } |
264 | |
265 | const struct clk_ops ccu_mux_ops = { |
266 | .disable = ccu_mux_disable, |
267 | .enable = ccu_mux_enable, |
268 | .is_enabled = ccu_mux_is_enabled, |
269 | |
270 | .get_parent = ccu_mux_get_parent, |
271 | .set_parent = ccu_mux_set_parent, |
272 | |
273 | .determine_rate = ccu_mux_determine_rate, |
274 | .recalc_rate = ccu_mux_recalc_rate, |
275 | }; |
276 | EXPORT_SYMBOL_NS_GPL(ccu_mux_ops, SUNXI_CCU); |
277 | |
278 | /* |
279 | * This clock notifier is called when the frequency of the of the parent |
280 | * PLL clock is to be changed. The idea is to switch the parent to a |
281 | * stable clock, such as the main oscillator, while the PLL frequency |
282 | * stabilizes. |
283 | */ |
284 | static int ccu_mux_notifier_cb(struct notifier_block *nb, |
285 | unsigned long event, void *data) |
286 | { |
287 | struct ccu_mux_nb *mux = to_ccu_mux_nb(nb); |
288 | int ret = 0; |
289 | |
290 | if (event == PRE_RATE_CHANGE) { |
291 | mux->original_index = ccu_mux_helper_get_parent(mux->common, |
292 | mux->cm); |
293 | ret = ccu_mux_helper_set_parent(mux->common, mux->cm, |
294 | mux->bypass_index); |
295 | } else if (event == POST_RATE_CHANGE) { |
296 | ret = ccu_mux_helper_set_parent(mux->common, mux->cm, |
297 | mux->original_index); |
298 | } |
299 | |
300 | udelay(mux->delay_us); |
301 | |
302 | return notifier_from_errno(err: ret); |
303 | } |
304 | |
305 | int ccu_mux_notifier_register(struct clk *clk, struct ccu_mux_nb *mux_nb) |
306 | { |
307 | mux_nb->clk_nb.notifier_call = ccu_mux_notifier_cb; |
308 | |
309 | return clk_notifier_register(clk, nb: &mux_nb->clk_nb); |
310 | } |
311 | EXPORT_SYMBOL_NS_GPL(ccu_mux_notifier_register, SUNXI_CCU); |
312 | |