1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Based on clk-super.c |
4 | * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. |
5 | * |
6 | * Based on older tegra20-cpufreq driver by Colin Cross <ccross@google.com> |
7 | * Copyright (C) 2010 Google, Inc. |
8 | * |
9 | * Author: Dmitry Osipenko <digetx@gmail.com> |
10 | * Copyright (C) 2019 GRATE-DRIVER project |
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 | |
30 | static struct tegra_clk_super_mux *cclk_super; |
31 | static bool cclk_on_pllx; |
32 | |
33 | static u8 cclk_super_get_parent(struct clk_hw *hw) |
34 | { |
35 | return tegra_clk_super_ops.get_parent(hw); |
36 | } |
37 | |
38 | static int cclk_super_set_parent(struct clk_hw *hw, u8 index) |
39 | { |
40 | return tegra_clk_super_ops.set_parent(hw, index); |
41 | } |
42 | |
43 | static 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 | |
49 | static 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 | /* check whether thermal throttling is active */ |
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 | |
68 | static 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 | * Switch parent to PLLP for all CCLK rates that are suitable for PLLP. |
82 | * PLLX will be disabled in this case, saving some power. |
83 | */ |
84 | pllp_rate = clk_hw_get_rate(hw: pllp_hw); |
85 | |
86 | if (rate <= pllp_rate) { |
87 | if (super->flags & TEGRA20_SUPER_CLK) |
88 | rate = pllp_rate; |
89 | else { |
90 | struct clk_rate_request parent = { |
91 | .rate = req->rate, |
92 | .best_parent_rate = pllp_rate, |
93 | }; |
94 | |
95 | clk_hw_get_rate_range(hw, min_rate: &parent.min_rate, |
96 | max_rate: &parent.max_rate); |
97 | tegra_clk_super_ops.determine_rate(hw, &parent); |
98 | pllp_rate = parent.best_parent_rate; |
99 | rate = parent.rate; |
100 | } |
101 | |
102 | req->best_parent_rate = pllp_rate; |
103 | req->best_parent_hw = pllp_hw; |
104 | req->rate = rate; |
105 | } else { |
106 | rate = clk_hw_round_rate(hw: pllx_hw, rate); |
107 | req->best_parent_rate = rate; |
108 | req->best_parent_hw = pllx_hw; |
109 | req->rate = rate; |
110 | } |
111 | |
112 | if (WARN_ON_ONCE(rate <= 0)) |
113 | return -EINVAL; |
114 | |
115 | return 0; |
116 | } |
117 | |
118 | static const struct clk_ops tegra_cclk_super_ops = { |
119 | .get_parent = cclk_super_get_parent, |
120 | .set_parent = cclk_super_set_parent, |
121 | .set_rate = cclk_super_set_rate, |
122 | .recalc_rate = cclk_super_recalc_rate, |
123 | .determine_rate = cclk_super_determine_rate, |
124 | }; |
125 | |
126 | static const struct clk_ops tegra_cclk_super_mux_ops = { |
127 | .get_parent = cclk_super_get_parent, |
128 | .set_parent = cclk_super_set_parent, |
129 | .determine_rate = cclk_super_determine_rate, |
130 | }; |
131 | |
132 | struct clk *tegra_clk_register_super_cclk(const char *name, |
133 | const char * const *parent_names, u8 num_parents, |
134 | unsigned long flags, void __iomem *reg, u8 clk_super_flags, |
135 | spinlock_t *lock) |
136 | { |
137 | struct tegra_clk_super_mux *super; |
138 | struct clk *clk; |
139 | struct clk_init_data init; |
140 | u32 val; |
141 | |
142 | if (WARN_ON(cclk_super)) |
143 | return ERR_PTR(error: -EBUSY); |
144 | |
145 | super = kzalloc(size: sizeof(*super), GFP_KERNEL); |
146 | if (!super) |
147 | return ERR_PTR(error: -ENOMEM); |
148 | |
149 | init.name = name; |
150 | init.flags = flags; |
151 | init.parent_names = parent_names; |
152 | init.num_parents = num_parents; |
153 | |
154 | super->reg = reg; |
155 | super->lock = lock; |
156 | super->width = 4; |
157 | super->flags = clk_super_flags; |
158 | super->hw.init = &init; |
159 | |
160 | if (super->flags & TEGRA20_SUPER_CLK) { |
161 | init.ops = &tegra_cclk_super_mux_ops; |
162 | } else { |
163 | init.ops = &tegra_cclk_super_ops; |
164 | |
165 | super->frac_div.reg = reg + 4; |
166 | super->frac_div.shift = 16; |
167 | super->frac_div.width = 8; |
168 | super->frac_div.frac_width = 1; |
169 | super->frac_div.lock = lock; |
170 | super->div_ops = &tegra_clk_frac_div_ops; |
171 | } |
172 | |
173 | /* |
174 | * Tegra30+ has the following CPUG clock topology: |
175 | * |
176 | * +---+ +-------+ +-+ +-+ +-+ |
177 | * PLLP+->+ +->+DIVIDER+->+0| +-------->+0| ------------->+0| |
178 | * | | +-------+ | | | +---+ | | | | | |
179 | * PLLC+->+MUX| | +->+ | S | | +->+ | +->+CPU |
180 | * ... | | | | | | K | | | | +-------+ | | |
181 | * PLLX+->+-->+------------>+1| +->+ I +->+1| +->+ DIV2 +->+1| |
182 | * +---+ +++ | P | +++ |SKIPPER| +++ |
183 | * ^ | P | ^ +-------+ ^ |
184 | * | | E | | | |
185 | * PLLX_SEL+--+ | R | | OVERHEAT+--+ |
186 | * +---+ | |
187 | * | |
188 | * SUPER_CDIV_ENB+--+ |
189 | * |
190 | * Tegra20 is similar, but simpler. It doesn't have the divider and |
191 | * thermal DIV2 skipper. |
192 | * |
193 | * At least for now we're not going to use clock-skipper, hence let's |
194 | * ensure that it is disabled. |
195 | */ |
196 | val = readl_relaxed(reg + 4); |
197 | val &= ~SUPER_CDIV_ENB; |
198 | writel_relaxed(val, reg + 4); |
199 | |
200 | clk = clk_register(NULL, hw: &super->hw); |
201 | if (IS_ERR(ptr: clk)) |
202 | kfree(objp: super); |
203 | else |
204 | cclk_super = super; |
205 | |
206 | return clk; |
207 | } |
208 | |
209 | int tegra_cclk_pre_pllx_rate_change(void) |
210 | { |
211 | if (IS_ERR_OR_NULL(ptr: cclk_super)) |
212 | return -EINVAL; |
213 | |
214 | if (cclk_super_get_parent(hw: &cclk_super->hw) == PLLX_INDEX) |
215 | cclk_on_pllx = true; |
216 | else |
217 | cclk_on_pllx = false; |
218 | |
219 | /* |
220 | * CPU needs to be temporarily re-parented away from PLLX if PLLX |
221 | * changes its rate. PLLP is a safe parent for CPU on all Tegra SoCs. |
222 | */ |
223 | if (cclk_on_pllx) |
224 | cclk_super_set_parent(hw: &cclk_super->hw, PLLP_INDEX); |
225 | |
226 | return 0; |
227 | } |
228 | |
229 | void tegra_cclk_post_pllx_rate_change(void) |
230 | { |
231 | if (cclk_on_pllx) |
232 | cclk_super_set_parent(hw: &cclk_super->hw, PLLX_INDEX); |
233 | } |
234 | |