blob: 3eb23d4e65344dca15133158b686464f046932d2 [file] [log] [blame]
Maxime Ripard2a65ed42016-06-29 21:05:26 +02001/*
2 * Copyright (C) 2016 Maxime Ripard
3 * Maxime Ripard <maxime.ripard@free-electrons.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (at your option) any later version.
9 */
10
Chen-Yu Tsai8adfb082016-08-25 14:21:58 +080011#include <linux/clk.h>
Maxime Ripard2a65ed42016-06-29 21:05:26 +020012#include <linux/clk-provider.h>
Chen-Yu Tsai8adfb082016-08-25 14:21:58 +080013#include <linux/delay.h>
Maxime Ripard2a65ed42016-06-29 21:05:26 +020014
15#include "ccu_gate.h"
16#include "ccu_mux.h"
17
Maxime Ripardea8edcd2017-05-17 09:40:34 +020018static u16 ccu_mux_get_prediv(struct ccu_common *common,
19 struct ccu_mux_internal *cm,
20 int parent_index)
Maxime Ripard2a65ed42016-06-29 21:05:26 +020021{
Chen-Yu Tsai178ca532016-07-26 15:04:25 +080022 u16 prediv = 1;
Maxime Ripard2a65ed42016-06-29 21:05:26 +020023 u32 reg;
24
25 if (!((common->features & CCU_FEATURE_FIXED_PREDIV) ||
Maxime Ripard7c09b852017-01-19 22:49:26 +010026 (common->features & CCU_FEATURE_VARIABLE_PREDIV) ||
27 (common->features & CCU_FEATURE_ALL_PREDIV)))
Maxime Ripardea8edcd2017-05-17 09:40:34 +020028 return 1;
Maxime Ripard2a65ed42016-06-29 21:05:26 +020029
Maxime Ripardea8edcd2017-05-17 09:40:34 +020030 if (common->features & CCU_FEATURE_ALL_PREDIV)
31 return common->prediv;
Maxime Ripard7c09b852017-01-19 22:49:26 +010032
Maxime Ripard2a65ed42016-06-29 21:05:26 +020033 reg = readl(common->base + common->reg);
34 if (parent_index < 0) {
35 parent_index = reg >> cm->shift;
36 parent_index &= (1 << cm->width) - 1;
37 }
38
Maxime Ripardea8edcd2017-05-17 09:40:34 +020039 if (common->features & CCU_FEATURE_FIXED_PREDIV) {
40 int i;
41
Chen-Yu Tsaiff5294d2016-08-25 14:21:57 +080042 for (i = 0; i < cm->n_predivs; i++)
43 if (parent_index == cm->fixed_predivs[i].index)
44 prediv = cm->fixed_predivs[i].div;
Maxime Ripardea8edcd2017-05-17 09:40:34 +020045 }
Maxime Ripard2a65ed42016-06-29 21:05:26 +020046
47 if (common->features & CCU_FEATURE_VARIABLE_PREDIV)
48 if (parent_index == cm->variable_prediv.index) {
49 u8 div;
50
51 div = reg >> cm->variable_prediv.shift;
52 div &= (1 << cm->variable_prediv.width) - 1;
53 prediv = div + 1;
54 }
55
Maxime Ripardea8edcd2017-05-17 09:40:34 +020056 return prediv;
57}
58
59void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
60 struct ccu_mux_internal *cm,
61 int parent_index,
62 unsigned long *parent_rate)
63{
64 *parent_rate = *parent_rate / ccu_mux_get_prediv(common, cm,
65 parent_index);
Maxime Ripard2a65ed42016-06-29 21:05:26 +020066}
67
68int ccu_mux_helper_determine_rate(struct ccu_common *common,
69 struct ccu_mux_internal *cm,
70 struct clk_rate_request *req,
71 unsigned long (*round)(struct ccu_mux_internal *,
Maxime Ripard10a8d9b2017-05-17 09:40:31 +020072 struct clk_hw *,
73 unsigned long *,
Maxime Ripard2a65ed42016-06-29 21:05:26 +020074 unsigned long,
75 void *),
76 void *data)
77{
78 unsigned long best_parent_rate = 0, best_rate = 0;
79 struct clk_hw *best_parent, *hw = &common->hw;
80 unsigned int i;
81
Chen-Yu Tsai216abdc2017-01-28 20:22:31 +080082 if (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT) {
83 unsigned long adj_parent_rate;
84
85 best_parent = clk_hw_get_parent(hw);
86 best_parent_rate = clk_hw_get_rate(best_parent);
87
88 adj_parent_rate = best_parent_rate;
89 ccu_mux_helper_adjust_parent_for_prediv(common, cm, -1,
90 &adj_parent_rate);
91
Maxime Ripard10a8d9b2017-05-17 09:40:31 +020092 best_rate = round(cm, best_parent, &adj_parent_rate,
93 req->rate, data);
Chen-Yu Tsai216abdc2017-01-28 20:22:31 +080094
95 goto out;
96 }
97
Maxime Ripard2a65ed42016-06-29 21:05:26 +020098 for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
Chen-Yu Tsaied482052017-01-28 20:22:30 +080099 unsigned long tmp_rate, parent_rate, adj_parent_rate;
Maxime Ripard2a65ed42016-06-29 21:05:26 +0200100 struct clk_hw *parent;
101
102 parent = clk_hw_get_parent_by_index(hw, i);
103 if (!parent)
104 continue;
105
Maxime Ripard73e3e042017-05-17 09:40:33 +0200106 adj_parent_rate = parent_rate = clk_hw_get_rate(parent);
Maxime Ripard2a65ed42016-06-29 21:05:26 +0200107 ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
Chen-Yu Tsaied482052017-01-28 20:22:30 +0800108 &adj_parent_rate);
Maxime Ripard2a65ed42016-06-29 21:05:26 +0200109
Maxime Ripard10a8d9b2017-05-17 09:40:31 +0200110 tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
Maxime Ripard2a65ed42016-06-29 21:05:26 +0200111 if (tmp_rate == req->rate) {
112 best_parent = parent;
113 best_parent_rate = parent_rate;
114 best_rate = tmp_rate;
115 goto out;
116 }
117
118 if ((req->rate - tmp_rate) < (req->rate - best_rate)) {
119 best_rate = tmp_rate;
120 best_parent_rate = parent_rate;
121 best_parent = parent;
122 }
123 }
124
125 if (best_rate == 0)
126 return -EINVAL;
127
128out:
129 req->best_parent_hw = best_parent;
130 req->best_parent_rate = best_parent_rate;
131 req->rate = best_rate;
132 return 0;
133}
134
135u8 ccu_mux_helper_get_parent(struct ccu_common *common,
136 struct ccu_mux_internal *cm)
137{
138 u32 reg;
139 u8 parent;
140
141 reg = readl(common->base + common->reg);
142 parent = reg >> cm->shift;
143 parent &= (1 << cm->width) - 1;
144
Chen-Yu Tsai2b9c8752016-08-25 14:21:56 +0800145 if (cm->table) {
146 int num_parents = clk_hw_get_num_parents(&common->hw);
147 int i;
148
149 for (i = 0; i < num_parents; i++)
150 if (cm->table[i] == parent)
151 return i;
152 }
153
Maxime Ripard2a65ed42016-06-29 21:05:26 +0200154 return parent;
155}
156
157int ccu_mux_helper_set_parent(struct ccu_common *common,
158 struct ccu_mux_internal *cm,
159 u8 index)
160{
161 unsigned long flags;
162 u32 reg;
163
Chen-Yu Tsai2b9c8752016-08-25 14:21:56 +0800164 if (cm->table)
165 index = cm->table[index];
166
Maxime Ripard2a65ed42016-06-29 21:05:26 +0200167 spin_lock_irqsave(common->lock, flags);
168
169 reg = readl(common->base + common->reg);
170 reg &= ~GENMASK(cm->width + cm->shift - 1, cm->shift);
171 writel(reg | (index << cm->shift), common->base + common->reg);
172
173 spin_unlock_irqrestore(common->lock, flags);
174
175 return 0;
176}
177
178static void ccu_mux_disable(struct clk_hw *hw)
179{
180 struct ccu_mux *cm = hw_to_ccu_mux(hw);
181
182 return ccu_gate_helper_disable(&cm->common, cm->enable);
183}
184
185static int ccu_mux_enable(struct clk_hw *hw)
186{
187 struct ccu_mux *cm = hw_to_ccu_mux(hw);
188
189 return ccu_gate_helper_enable(&cm->common, cm->enable);
190}
191
192static int ccu_mux_is_enabled(struct clk_hw *hw)
193{
194 struct ccu_mux *cm = hw_to_ccu_mux(hw);
195
196 return ccu_gate_helper_is_enabled(&cm->common, cm->enable);
197}
198
199static u8 ccu_mux_get_parent(struct clk_hw *hw)
200{
201 struct ccu_mux *cm = hw_to_ccu_mux(hw);
202
203 return ccu_mux_helper_get_parent(&cm->common, &cm->mux);
204}
205
206static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
207{
208 struct ccu_mux *cm = hw_to_ccu_mux(hw);
209
210 return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index);
211}
212
213static unsigned long ccu_mux_recalc_rate(struct clk_hw *hw,
214 unsigned long parent_rate)
215{
216 struct ccu_mux *cm = hw_to_ccu_mux(hw);
217
218 ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
219 &parent_rate);
220
221 return parent_rate;
222}
223
224const struct clk_ops ccu_mux_ops = {
225 .disable = ccu_mux_disable,
226 .enable = ccu_mux_enable,
227 .is_enabled = ccu_mux_is_enabled,
228
229 .get_parent = ccu_mux_get_parent,
230 .set_parent = ccu_mux_set_parent,
231
232 .determine_rate = __clk_mux_determine_rate,
233 .recalc_rate = ccu_mux_recalc_rate,
234};
Chen-Yu Tsai8adfb082016-08-25 14:21:58 +0800235
236/*
237 * This clock notifier is called when the frequency of the of the parent
238 * PLL clock is to be changed. The idea is to switch the parent to a
239 * stable clock, such as the main oscillator, while the PLL frequency
240 * stabilizes.
241 */
242static int ccu_mux_notifier_cb(struct notifier_block *nb,
243 unsigned long event, void *data)
244{
245 struct ccu_mux_nb *mux = to_ccu_mux_nb(nb);
246 int ret = 0;
247
248 if (event == PRE_RATE_CHANGE) {
249 mux->original_index = ccu_mux_helper_get_parent(mux->common,
250 mux->cm);
251 ret = ccu_mux_helper_set_parent(mux->common, mux->cm,
252 mux->bypass_index);
253 } else if (event == POST_RATE_CHANGE) {
254 ret = ccu_mux_helper_set_parent(mux->common, mux->cm,
255 mux->original_index);
256 }
257
258 udelay(mux->delay_us);
259
260 return notifier_from_errno(ret);
261}
262
263int ccu_mux_notifier_register(struct clk *clk, struct ccu_mux_nb *mux_nb)
264{
265 mux_nb->clk_nb.notifier_call = ccu_mux_notifier_cb;
266
267 return clk_notifier_register(clk, &mux_nb->clk_nb);
268}