GNU Linux-libre 6.7.9-gnu
[releases.git] / drivers / clk / sunxi-ng / ccu_mux.c
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(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(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, -1,
110                                                                  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, i);
120                 if (!parent)
121                         continue;
122
123                 parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
124                                                           clk_hw_get_rate(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, 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, req->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(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(&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(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(reg | (index << cm->shift), common->base + common->reg);
203
204         spin_unlock_irqrestore(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(&cm->common, 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(&cm->common, 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(&cm->common, 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, 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(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, &mux_nb->clk_nb);
310 }
311 EXPORT_SYMBOL_NS_GPL(ccu_mux_notifier_register, SUNXI_CCU);