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(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, &parent.min_rate,
96					      &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(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(-EBUSY);
144
145	super = kzalloc(sizeof(*super), GFP_KERNEL);
146	if (!super)
147		return ERR_PTR(-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, &super->hw);
201	if (IS_ERR(clk))
202		kfree(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(cclk_super))
212		return -EINVAL;
213
214	if (cclk_super_get_parent(&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(&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(&cclk_super->hw, PLLX_INDEX);
233}
234