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 static struct tegra_clk_super_mux *cclk_super;
29 static bool cclk_on_pllx;
31 static u8 cclk_super_get_parent(struct clk_hw *hw)
33 return tegra_clk_super_ops.get_parent(hw);
36 static int cclk_super_set_parent(struct clk_hw *hw, u8 index)
38 return tegra_clk_super_ops.set_parent(hw, index);
41 static int cclk_super_set_rate(struct clk_hw *hw, unsigned long rate,
42 unsigned long parent_rate)
44 return tegra_clk_super_ops.set_rate(hw, rate, parent_rate);
47 static unsigned long cclk_super_recalc_rate(struct clk_hw *hw,
48 unsigned long parent_rate)
50 if (cclk_super_get_parent(hw) == PLLX_INDEX)
53 return tegra_clk_super_ops.recalc_rate(hw, parent_rate);
56 static int cclk_super_determine_rate(struct clk_hw *hw,
57 struct clk_rate_request *req)
59 struct clk_hw *pllp_hw = clk_hw_get_parent_by_index(hw, PLLP_INDEX);
60 struct clk_hw *pllx_hw = clk_hw_get_parent_by_index(hw, PLLX_INDEX);
61 struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
62 unsigned long pllp_rate;
63 long rate = req->rate;
65 if (WARN_ON_ONCE(!pllp_hw || !pllx_hw))
69 * Switch parent to PLLP for all CCLK rates that are suitable for PLLP.
70 * PLLX will be disabled in this case, saving some power.
72 pllp_rate = clk_hw_get_rate(pllp_hw);
74 if (rate <= pllp_rate) {
75 if (super->flags & TEGRA20_SUPER_CLK)
78 rate = tegra_clk_super_ops.round_rate(hw, rate,
81 req->best_parent_rate = pllp_rate;
82 req->best_parent_hw = pllp_hw;
85 rate = clk_hw_round_rate(pllx_hw, rate);
86 req->best_parent_rate = rate;
87 req->best_parent_hw = pllx_hw;
91 if (WARN_ON_ONCE(rate <= 0))
97 static const struct clk_ops tegra_cclk_super_ops = {
98 .get_parent = cclk_super_get_parent,
99 .set_parent = cclk_super_set_parent,
100 .set_rate = cclk_super_set_rate,
101 .recalc_rate = cclk_super_recalc_rate,
102 .determine_rate = cclk_super_determine_rate,
105 static const struct clk_ops tegra_cclk_super_mux_ops = {
106 .get_parent = cclk_super_get_parent,
107 .set_parent = cclk_super_set_parent,
108 .determine_rate = cclk_super_determine_rate,
111 struct clk *tegra_clk_register_super_cclk(const char *name,
112 const char * const *parent_names, u8 num_parents,
113 unsigned long flags, void __iomem *reg, u8 clk_super_flags,
116 struct tegra_clk_super_mux *super;
118 struct clk_init_data init;
121 if (WARN_ON(cclk_super))
122 return ERR_PTR(-EBUSY);
124 super = kzalloc(sizeof(*super), GFP_KERNEL);
126 return ERR_PTR(-ENOMEM);
130 init.parent_names = parent_names;
131 init.num_parents = num_parents;
136 super->flags = clk_super_flags;
137 super->hw.init = &init;
139 if (super->flags & TEGRA20_SUPER_CLK) {
140 init.ops = &tegra_cclk_super_mux_ops;
142 init.ops = &tegra_cclk_super_ops;
144 super->frac_div.reg = reg + 4;
145 super->frac_div.shift = 16;
146 super->frac_div.width = 8;
147 super->frac_div.frac_width = 1;
148 super->frac_div.lock = lock;
149 super->div_ops = &tegra_clk_frac_div_ops;
153 * Tegra30+ has the following CPUG clock topology:
155 * +---+ +-------+ +-+ +-+ +-+
156 * PLLP+->+ +->+DIVIDER+->+0| +-------->+0| ------------->+0|
157 * | | +-------+ | | | +---+ | | | | |
158 * PLLC+->+MUX| | +->+ | S | | +->+ | +->+CPU
159 * ... | | | | | | K | | | | +-------+ | |
160 * PLLX+->+-->+------------>+1| +->+ I +->+1| +->+ DIV2 +->+1|
161 * +---+ +++ | P | +++ |SKIPPER| +++
162 * ^ | P | ^ +-------+ ^
164 * PLLX_SEL+--+ | R | | OVERHEAT+--+
169 * Tegra20 is similar, but simpler. It doesn't have the divider and
170 * thermal DIV2 skipper.
172 * At least for now we're not going to use clock-skipper, hence let's
173 * ensure that it is disabled.
175 val = readl_relaxed(reg + 4);
176 val &= ~SUPER_CDIV_ENB;
177 writel_relaxed(val, reg + 4);
179 clk = clk_register(NULL, &super->hw);
188 int tegra_cclk_pre_pllx_rate_change(void)
190 if (IS_ERR_OR_NULL(cclk_super))
193 if (cclk_super_get_parent(&cclk_super->hw) == PLLX_INDEX)
196 cclk_on_pllx = false;
199 * CPU needs to be temporarily re-parented away from PLLX if PLLX
200 * changes its rate. PLLP is a safe parent for CPU on all Tegra SoCs.
203 cclk_super_set_parent(&cclk_super->hw, PLLP_INDEX);
208 void tegra_cclk_post_pllx_rate_change(void)
211 cclk_super_set_parent(&cclk_super->hw, PLLX_INDEX);