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-provider.h> |
8 | #include <linux/io.h> |
9 | |
10 | #include "ccu_gate.h" |
11 | #include "ccu_mp.h" |
12 | |
13 | static unsigned long ccu_mp_find_best(unsigned long parent, unsigned long rate, |
14 | unsigned int max_m, unsigned int max_p, |
15 | unsigned int *m, unsigned int *p) |
16 | { |
17 | unsigned long best_rate = 0; |
18 | unsigned int best_m = 0, best_p = 0; |
19 | unsigned int _m, _p; |
20 | |
21 | for (_p = 1; _p <= max_p; _p <<= 1) { |
22 | for (_m = 1; _m <= max_m; _m++) { |
23 | unsigned long tmp_rate = parent / _p / _m; |
24 | |
25 | if (tmp_rate > rate) |
26 | continue; |
27 | |
28 | if ((rate - tmp_rate) < (rate - best_rate)) { |
29 | best_rate = tmp_rate; |
30 | best_m = _m; |
31 | best_p = _p; |
32 | } |
33 | } |
34 | } |
35 | |
36 | *m = best_m; |
37 | *p = best_p; |
38 | |
39 | return best_rate; |
40 | } |
41 | |
42 | static unsigned long ccu_mp_find_best_with_parent_adj(struct clk_hw *hw, |
43 | unsigned long *parent, |
44 | unsigned long rate, |
45 | unsigned int max_m, |
46 | unsigned int max_p) |
47 | { |
48 | unsigned long parent_rate_saved; |
49 | unsigned long parent_rate, now; |
50 | unsigned long best_rate = 0; |
51 | unsigned int _m, _p, div; |
52 | unsigned long maxdiv; |
53 | |
54 | parent_rate_saved = *parent; |
55 | |
56 | /* |
57 | * The maximum divider we can use without overflowing |
58 | * unsigned long in rate * m * p below |
59 | */ |
60 | maxdiv = max_m * max_p; |
61 | maxdiv = min(ULONG_MAX / rate, maxdiv); |
62 | |
63 | for (_p = 1; _p <= max_p; _p <<= 1) { |
64 | for (_m = 1; _m <= max_m; _m++) { |
65 | div = _m * _p; |
66 | |
67 | if (div > maxdiv) |
68 | break; |
69 | |
70 | if (rate * div == parent_rate_saved) { |
71 | /* |
72 | * It's the most ideal case if the requested |
73 | * rate can be divided from parent clock without |
74 | * needing to change parent rate, so return the |
75 | * divider immediately. |
76 | */ |
77 | *parent = parent_rate_saved; |
78 | return rate; |
79 | } |
80 | |
81 | parent_rate = clk_hw_round_rate(hw, rate: rate * div); |
82 | now = parent_rate / div; |
83 | |
84 | if (now <= rate && now > best_rate) { |
85 | best_rate = now; |
86 | *parent = parent_rate; |
87 | |
88 | if (now == rate) |
89 | return rate; |
90 | } |
91 | } |
92 | } |
93 | |
94 | return best_rate; |
95 | } |
96 | |
97 | static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux, |
98 | struct clk_hw *hw, |
99 | unsigned long *parent_rate, |
100 | unsigned long rate, |
101 | void *data) |
102 | { |
103 | struct ccu_mp *cmp = data; |
104 | unsigned int max_m, max_p; |
105 | unsigned int m, p; |
106 | |
107 | if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV) |
108 | rate *= cmp->fixed_post_div; |
109 | |
110 | max_m = cmp->m.max ?: 1 << cmp->m.width; |
111 | max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1); |
112 | |
113 | if (!clk_hw_can_set_rate_parent(&cmp->common.hw)) { |
114 | rate = ccu_mp_find_best(parent: *parent_rate, rate, max_m, max_p, m: &m, p: &p); |
115 | } else { |
116 | rate = ccu_mp_find_best_with_parent_adj(hw, parent: parent_rate, rate, |
117 | max_m, max_p); |
118 | } |
119 | |
120 | if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV) |
121 | rate /= cmp->fixed_post_div; |
122 | |
123 | return rate; |
124 | } |
125 | |
126 | static void ccu_mp_disable(struct clk_hw *hw) |
127 | { |
128 | struct ccu_mp *cmp = hw_to_ccu_mp(hw); |
129 | |
130 | return ccu_gate_helper_disable(common: &cmp->common, gate: cmp->enable); |
131 | } |
132 | |
133 | static int ccu_mp_enable(struct clk_hw *hw) |
134 | { |
135 | struct ccu_mp *cmp = hw_to_ccu_mp(hw); |
136 | |
137 | return ccu_gate_helper_enable(common: &cmp->common, gate: cmp->enable); |
138 | } |
139 | |
140 | static int ccu_mp_is_enabled(struct clk_hw *hw) |
141 | { |
142 | struct ccu_mp *cmp = hw_to_ccu_mp(hw); |
143 | |
144 | return ccu_gate_helper_is_enabled(common: &cmp->common, gate: cmp->enable); |
145 | } |
146 | |
147 | static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw, |
148 | unsigned long parent_rate) |
149 | { |
150 | struct ccu_mp *cmp = hw_to_ccu_mp(hw); |
151 | unsigned long rate; |
152 | unsigned int m, p; |
153 | u32 reg; |
154 | |
155 | /* Adjust parent_rate according to pre-dividers */ |
156 | parent_rate = ccu_mux_helper_apply_prediv(common: &cmp->common, cm: &cmp->mux, parent_index: -1, |
157 | parent_rate); |
158 | |
159 | reg = readl(addr: cmp->common.base + cmp->common.reg); |
160 | |
161 | m = reg >> cmp->m.shift; |
162 | m &= (1 << cmp->m.width) - 1; |
163 | m += cmp->m.offset; |
164 | if (!m) |
165 | m++; |
166 | |
167 | p = reg >> cmp->p.shift; |
168 | p &= (1 << cmp->p.width) - 1; |
169 | |
170 | rate = (parent_rate >> p) / m; |
171 | if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV) |
172 | rate /= cmp->fixed_post_div; |
173 | |
174 | return rate; |
175 | } |
176 | |
177 | static int ccu_mp_determine_rate(struct clk_hw *hw, |
178 | struct clk_rate_request *req) |
179 | { |
180 | struct ccu_mp *cmp = hw_to_ccu_mp(hw); |
181 | |
182 | return ccu_mux_helper_determine_rate(common: &cmp->common, cm: &cmp->mux, |
183 | req, round: ccu_mp_round_rate, data: cmp); |
184 | } |
185 | |
186 | static int ccu_mp_set_rate(struct clk_hw *hw, unsigned long rate, |
187 | unsigned long parent_rate) |
188 | { |
189 | struct ccu_mp *cmp = hw_to_ccu_mp(hw); |
190 | unsigned long flags; |
191 | unsigned int max_m, max_p; |
192 | unsigned int m, p; |
193 | u32 reg; |
194 | |
195 | /* Adjust parent_rate according to pre-dividers */ |
196 | parent_rate = ccu_mux_helper_apply_prediv(common: &cmp->common, cm: &cmp->mux, parent_index: -1, |
197 | parent_rate); |
198 | |
199 | max_m = cmp->m.max ?: 1 << cmp->m.width; |
200 | max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1); |
201 | |
202 | /* Adjust target rate according to post-dividers */ |
203 | if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV) |
204 | rate = rate * cmp->fixed_post_div; |
205 | |
206 | ccu_mp_find_best(parent: parent_rate, rate, max_m, max_p, m: &m, p: &p); |
207 | |
208 | spin_lock_irqsave(cmp->common.lock, flags); |
209 | |
210 | reg = readl(addr: cmp->common.base + cmp->common.reg); |
211 | reg &= ~GENMASK(cmp->m.width + cmp->m.shift - 1, cmp->m.shift); |
212 | reg &= ~GENMASK(cmp->p.width + cmp->p.shift - 1, cmp->p.shift); |
213 | reg |= (m - cmp->m.offset) << cmp->m.shift; |
214 | reg |= ilog2(p) << cmp->p.shift; |
215 | |
216 | writel(val: reg, addr: cmp->common.base + cmp->common.reg); |
217 | |
218 | spin_unlock_irqrestore(lock: cmp->common.lock, flags); |
219 | |
220 | return 0; |
221 | } |
222 | |
223 | static u8 ccu_mp_get_parent(struct clk_hw *hw) |
224 | { |
225 | struct ccu_mp *cmp = hw_to_ccu_mp(hw); |
226 | |
227 | return ccu_mux_helper_get_parent(common: &cmp->common, cm: &cmp->mux); |
228 | } |
229 | |
230 | static int ccu_mp_set_parent(struct clk_hw *hw, u8 index) |
231 | { |
232 | struct ccu_mp *cmp = hw_to_ccu_mp(hw); |
233 | |
234 | return ccu_mux_helper_set_parent(common: &cmp->common, cm: &cmp->mux, index); |
235 | } |
236 | |
237 | const struct clk_ops ccu_mp_ops = { |
238 | .disable = ccu_mp_disable, |
239 | .enable = ccu_mp_enable, |
240 | .is_enabled = ccu_mp_is_enabled, |
241 | |
242 | .get_parent = ccu_mp_get_parent, |
243 | .set_parent = ccu_mp_set_parent, |
244 | |
245 | .determine_rate = ccu_mp_determine_rate, |
246 | .recalc_rate = ccu_mp_recalc_rate, |
247 | .set_rate = ccu_mp_set_rate, |
248 | }; |
249 | EXPORT_SYMBOL_NS_GPL(ccu_mp_ops, SUNXI_CCU); |
250 | |
251 | /* |
252 | * Support for MMC timing mode switching |
253 | * |
254 | * The MMC clocks on some SoCs support switching between old and |
255 | * new timing modes. A platform specific API is provided to query |
256 | * and set the timing mode on supported SoCs. |
257 | * |
258 | * In addition, a special class of ccu_mp_ops is provided, which |
259 | * takes in to account the timing mode switch. When the new timing |
260 | * mode is active, the clock output rate is halved. This new class |
261 | * is a wrapper around the generic ccu_mp_ops. When clock rates |
262 | * are passed through to ccu_mp_ops callbacks, they are doubled |
263 | * if the new timing mode bit is set, to account for the post |
264 | * divider. Conversely, when clock rates are passed back, they |
265 | * are halved if the mode bit is set. |
266 | */ |
267 | |
268 | static unsigned long ccu_mp_mmc_recalc_rate(struct clk_hw *hw, |
269 | unsigned long parent_rate) |
270 | { |
271 | unsigned long rate = ccu_mp_recalc_rate(hw, parent_rate); |
272 | struct ccu_common *cm = hw_to_ccu_common(hw); |
273 | u32 val = readl(addr: cm->base + cm->reg); |
274 | |
275 | if (val & CCU_MMC_NEW_TIMING_MODE) |
276 | return rate / 2; |
277 | return rate; |
278 | } |
279 | |
280 | static int ccu_mp_mmc_determine_rate(struct clk_hw *hw, |
281 | struct clk_rate_request *req) |
282 | { |
283 | struct ccu_common *cm = hw_to_ccu_common(hw); |
284 | u32 val = readl(addr: cm->base + cm->reg); |
285 | int ret; |
286 | |
287 | /* adjust the requested clock rate */ |
288 | if (val & CCU_MMC_NEW_TIMING_MODE) { |
289 | req->rate *= 2; |
290 | req->min_rate *= 2; |
291 | req->max_rate *= 2; |
292 | } |
293 | |
294 | ret = ccu_mp_determine_rate(hw, req); |
295 | |
296 | /* re-adjust the requested clock rate back */ |
297 | if (val & CCU_MMC_NEW_TIMING_MODE) { |
298 | req->rate /= 2; |
299 | req->min_rate /= 2; |
300 | req->max_rate /= 2; |
301 | } |
302 | |
303 | return ret; |
304 | } |
305 | |
306 | static int ccu_mp_mmc_set_rate(struct clk_hw *hw, unsigned long rate, |
307 | unsigned long parent_rate) |
308 | { |
309 | struct ccu_common *cm = hw_to_ccu_common(hw); |
310 | u32 val = readl(addr: cm->base + cm->reg); |
311 | |
312 | if (val & CCU_MMC_NEW_TIMING_MODE) |
313 | rate *= 2; |
314 | |
315 | return ccu_mp_set_rate(hw, rate, parent_rate); |
316 | } |
317 | |
318 | const struct clk_ops ccu_mp_mmc_ops = { |
319 | .disable = ccu_mp_disable, |
320 | .enable = ccu_mp_enable, |
321 | .is_enabled = ccu_mp_is_enabled, |
322 | |
323 | .get_parent = ccu_mp_get_parent, |
324 | .set_parent = ccu_mp_set_parent, |
325 | |
326 | .determine_rate = ccu_mp_mmc_determine_rate, |
327 | .recalc_rate = ccu_mp_mmc_recalc_rate, |
328 | .set_rate = ccu_mp_mmc_set_rate, |
329 | }; |
330 | EXPORT_SYMBOL_NS_GPL(ccu_mp_mmc_ops, SUNXI_CCU); |
331 | |