1
2
3
4
5
6
7
8
9
10
11
12
13#include <linux/bits.h>
14#include <linux/clk-provider.h>
15#include <linux/err.h>
16#include <linux/io.h>
17#include <linux/kernel.h>
18#include <linux/slab.h>
19#include <linux/types.h>
20
21#include "clk.h"
22
23#define PLLP_INDEX 4
24#define PLLX_INDEX 8
25
26#define SUPER_CDIV_ENB BIT(31)
27
28#define TSENSOR_SLOWDOWN BIT(23)
29
30static struct tegra_clk_super_mux *cclk_super;
31static bool cclk_on_pllx;
32
33static u8 cclk_super_get_parent(struct clk_hw *hw)
34{
35 return tegra_clk_super_ops.get_parent(hw);
36}
37
38static int cclk_super_set_parent(struct clk_hw *hw, u8 index)
39{
40 return tegra_clk_super_ops.set_parent(hw, index);
41}
42
43static int cclk_super_set_rate(struct clk_hw *hw, unsigned long rate,
44 unsigned long parent_rate)
45{
46 return tegra_clk_super_ops.set_rate(hw, rate, parent_rate);
47}
48
49static unsigned long cclk_super_recalc_rate(struct clk_hw *hw,
50 unsigned long parent_rate)
51{
52 struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
53 u32 val = readl_relaxed(super->reg);
54 unsigned int div2;
55
56
57 if (val & TSENSOR_SLOWDOWN)
58 div2 = 1;
59 else
60 div2 = 0;
61
62 if (cclk_super_get_parent(hw) == PLLX_INDEX)
63 return parent_rate >> div2;
64
65 return tegra_clk_super_ops.recalc_rate(hw, parent_rate) >> div2;
66}
67
68static int cclk_super_determine_rate(struct clk_hw *hw,
69 struct clk_rate_request *req)
70{
71 struct clk_hw *pllp_hw = clk_hw_get_parent_by_index(hw, PLLP_INDEX);
72 struct clk_hw *pllx_hw = clk_hw_get_parent_by_index(hw, PLLX_INDEX);
73 struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
74 unsigned long pllp_rate;
75 long rate = req->rate;
76
77 if (WARN_ON_ONCE(!pllp_hw || !pllx_hw))
78 return -EINVAL;
79
80
81
82
83
84 pllp_rate = clk_hw_get_rate(pllp_hw);
85
86 if (rate <= pllp_rate) {
87 if (super->flags & TEGRA20_SUPER_CLK)
88 rate = pllp_rate;
89 else
90 rate = tegra_clk_super_ops.round_rate(hw, rate,
91 &pllp_rate);
92
93 req->best_parent_rate = pllp_rate;
94 req->best_parent_hw = pllp_hw;
95 req->rate = rate;
96 } else {
97 rate = clk_hw_round_rate(pllx_hw, rate);
98 req->best_parent_rate = rate;
99 req->best_parent_hw = pllx_hw;
100 req->rate = rate;
101 }
102
103 if (WARN_ON_ONCE(rate <= 0))
104 return -EINVAL;
105
106 return 0;
107}
108
109static const struct clk_ops tegra_cclk_super_ops = {
110 .get_parent = cclk_super_get_parent,
111 .set_parent = cclk_super_set_parent,
112 .set_rate = cclk_super_set_rate,
113 .recalc_rate = cclk_super_recalc_rate,
114 .determine_rate = cclk_super_determine_rate,
115};
116
117static const struct clk_ops tegra_cclk_super_mux_ops = {
118 .get_parent = cclk_super_get_parent,
119 .set_parent = cclk_super_set_parent,
120 .determine_rate = cclk_super_determine_rate,
121};
122
123struct clk *tegra_clk_register_super_cclk(const char *name,
124 const char * const *parent_names, u8 num_parents,
125 unsigned long flags, void __iomem *reg, u8 clk_super_flags,
126 spinlock_t *lock)
127{
128 struct tegra_clk_super_mux *super;
129 struct clk *clk;
130 struct clk_init_data init;
131 u32 val;
132
133 if (WARN_ON(cclk_super))
134 return ERR_PTR(-EBUSY);
135
136 super = kzalloc(sizeof(*super), GFP_KERNEL);
137 if (!super)
138 return ERR_PTR(-ENOMEM);
139
140 init.name = name;
141 init.flags = flags;
142 init.parent_names = parent_names;
143 init.num_parents = num_parents;
144
145 super->reg = reg;
146 super->lock = lock;
147 super->width = 4;
148 super->flags = clk_super_flags;
149 super->hw.init = &init;
150
151 if (super->flags & TEGRA20_SUPER_CLK) {
152 init.ops = &tegra_cclk_super_mux_ops;
153 } else {
154 init.ops = &tegra_cclk_super_ops;
155
156 super->frac_div.reg = reg + 4;
157 super->frac_div.shift = 16;
158 super->frac_div.width = 8;
159 super->frac_div.frac_width = 1;
160 super->frac_div.lock = lock;
161 super->div_ops = &tegra_clk_frac_div_ops;
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187 val = readl_relaxed(reg + 4);
188 val &= ~SUPER_CDIV_ENB;
189 writel_relaxed(val, reg + 4);
190
191 clk = clk_register(NULL, &super->hw);
192 if (IS_ERR(clk))
193 kfree(super);
194 else
195 cclk_super = super;
196
197 return clk;
198}
199
200int tegra_cclk_pre_pllx_rate_change(void)
201{
202 if (IS_ERR_OR_NULL(cclk_super))
203 return -EINVAL;
204
205 if (cclk_super_get_parent(&cclk_super->hw) == PLLX_INDEX)
206 cclk_on_pllx = true;
207 else
208 cclk_on_pllx = false;
209
210
211
212
213
214 if (cclk_on_pllx)
215 cclk_super_set_parent(&cclk_super->hw, PLLP_INDEX);
216
217 return 0;
218}
219
220void tegra_cclk_post_pllx_rate_change(void)
221{
222 if (cclk_on_pllx)
223 cclk_super_set_parent(&cclk_super->hw, PLLX_INDEX);
224}
225