1 // SPDX-License-Identifier: GPL-2.0-only
4 * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
6 * Based on older tegra20-cpufreq driver by Colin Cross <ccross@google.com>
7 * Copyright (C) 2010 Google, Inc.
9 * Author: Dmitry Osipenko <digetx@gmail.com>
10 * Copyright (C) 2019 GRATE-DRIVER project
13 #include <linux/bits.h>
14 #include <linux/clk-provider.h>
15 #include <linux/err.h>
17 #include <linux/kernel.h>
18 #include <linux/slab.h>
19 #include <linux/types.h>
26 #define SUPER_CDIV_ENB BIT(31)
28 #define TSENSOR_SLOWDOWN BIT(23)
30 static struct tegra_clk_super_mux
*cclk_super
;
31 static bool cclk_on_pllx
;
33 static u8
cclk_super_get_parent(struct clk_hw
*hw
)
35 return tegra_clk_super_ops
.get_parent(hw
);
38 static int cclk_super_set_parent(struct clk_hw
*hw
, u8 index
)
40 return tegra_clk_super_ops
.set_parent(hw
, index
);
43 static int cclk_super_set_rate(struct clk_hw
*hw
, unsigned long rate
,
44 unsigned long parent_rate
)
46 return tegra_clk_super_ops
.set_rate(hw
, rate
, parent_rate
);
49 static unsigned long cclk_super_recalc_rate(struct clk_hw
*hw
,
50 unsigned long parent_rate
)
52 struct tegra_clk_super_mux
*super
= to_clk_super_mux(hw
);
53 u32 val
= readl_relaxed(super
->reg
);
56 /* check whether thermal throttling is active */
57 if (val
& TSENSOR_SLOWDOWN
)
62 if (cclk_super_get_parent(hw
) == PLLX_INDEX
)
63 return parent_rate
>> div2
;
65 return tegra_clk_super_ops
.recalc_rate(hw
, parent_rate
) >> div2
;
68 static int cclk_super_determine_rate(struct clk_hw
*hw
,
69 struct clk_rate_request
*req
)
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
;
77 if (WARN_ON_ONCE(!pllp_hw
|| !pllx_hw
))
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.
84 pllp_rate
= clk_hw_get_rate(pllp_hw
);
86 if (rate
<= pllp_rate
) {
87 if (super
->flags
& TEGRA20_SUPER_CLK
)
90 struct clk_rate_request parent
= {
92 .best_parent_rate
= pllp_rate
,
95 clk_hw_get_rate_range(hw
, &parent
.min_rate
,
97 tegra_clk_super_ops
.determine_rate(hw
, &parent
);
98 pllp_rate
= parent
.best_parent_rate
;
102 req
->best_parent_rate
= pllp_rate
;
103 req
->best_parent_hw
= pllp_hw
;
106 rate
= clk_hw_round_rate(pllx_hw
, rate
);
107 req
->best_parent_rate
= rate
;
108 req
->best_parent_hw
= pllx_hw
;
112 if (WARN_ON_ONCE(rate
<= 0))
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
,
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
,
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
,
137 struct tegra_clk_super_mux
*super
;
139 struct clk_init_data init
;
142 if (WARN_ON(cclk_super
))
143 return ERR_PTR(-EBUSY
);
145 super
= kzalloc(sizeof(*super
), GFP_KERNEL
);
147 return ERR_PTR(-ENOMEM
);
151 init
.parent_names
= parent_names
;
152 init
.num_parents
= num_parents
;
157 super
->flags
= clk_super_flags
;
158 super
->hw
.init
= &init
;
160 if (super
->flags
& TEGRA20_SUPER_CLK
) {
161 init
.ops
= &tegra_cclk_super_mux_ops
;
163 init
.ops
= &tegra_cclk_super_ops
;
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
;
174 * Tegra30+ has the following CPUG clock topology:
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 | ^ +-------+ ^
185 * PLLX_SEL+--+ | R | | OVERHEAT+--+
190 * Tegra20 is similar, but simpler. It doesn't have the divider and
191 * thermal DIV2 skipper.
193 * At least for now we're not going to use clock-skipper, hence let's
194 * ensure that it is disabled.
196 val
= readl_relaxed(reg
+ 4);
197 val
&= ~SUPER_CDIV_ENB
;
198 writel_relaxed(val
, reg
+ 4);
200 clk
= clk_register(NULL
, &super
->hw
);
209 int tegra_cclk_pre_pllx_rate_change(void)
211 if (IS_ERR_OR_NULL(cclk_super
))
214 if (cclk_super_get_parent(&cclk_super
->hw
) == PLLX_INDEX
)
217 cclk_on_pllx
= false;
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.
224 cclk_super_set_parent(&cclk_super
->hw
, PLLP_INDEX
);
229 void tegra_cclk_post_pllx_rate_change(void)
232 cclk_super_set_parent(&cclk_super
->hw
, PLLX_INDEX
);