xref: /linux/drivers/clk/tegra/clk-tegra-super-cclk.c (revision cdd5b5a9761fd66d17586e4f4ba6588c70e640ea)
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 
cclk_super_get_parent(struct clk_hw * hw)33 static u8 cclk_super_get_parent(struct clk_hw *hw)
34 {
35 	return tegra_clk_super_ops.get_parent(hw);
36 }
37 
cclk_super_set_parent(struct clk_hw * hw,u8 index)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 
cclk_super_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)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 
cclk_super_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)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 
cclk_super_determine_rate(struct clk_hw * hw,struct clk_rate_request * req)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(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 
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 
tegra_clk_register_super_cclk(const char * name,const char * const * parent_names,u8 num_parents,unsigned long flags,void __iomem * reg,u8 clk_super_flags,spinlock_t * lock)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(-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 
tegra_cclk_pre_pllx_rate_change(void)209 int 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 
tegra_cclk_post_pllx_rate_change(void)229 void 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