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
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 /* 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
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 * 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
118static 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
126static 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
132struct 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
209int 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
229void 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

source code of linux/drivers/clk/tegra/clk-tegra-super-cclk.c