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
28static struct tegra_clk_super_mux *cclk_super;
29static bool cclk_on_pllx;
30
31static u8 cclk_super_get_parent(struct clk_hw *hw)
32{
33	return tegra_clk_super_ops.get_parent(hw);
34}
35
36static int cclk_super_set_parent(struct clk_hw *hw, u8 index)
37{
38	return tegra_clk_super_ops.set_parent(hw, index);
39}
40
41static int cclk_super_set_rate(struct clk_hw *hw, unsigned long rate,
42			       unsigned long parent_rate)
43{
44	return tegra_clk_super_ops.set_rate(hw, rate, parent_rate);
45}
46
47static unsigned long cclk_super_recalc_rate(struct clk_hw *hw,
48					    unsigned long parent_rate)
49{
50	if (cclk_super_get_parent(hw) == PLLX_INDEX)
51		return parent_rate;
52
53	return tegra_clk_super_ops.recalc_rate(hw, parent_rate);
54}
55
56static int cclk_super_determine_rate(struct clk_hw *hw,
57				     struct clk_rate_request *req)
58{
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;
64
65	if (WARN_ON_ONCE(!pllp_hw || !pllx_hw))
66		return -EINVAL;
67
68	/*
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.
71	 */
72	pllp_rate = clk_hw_get_rate(pllp_hw);
73
74	if (rate <= pllp_rate) {
75		if (super->flags & TEGRA20_SUPER_CLK)
76			rate = pllp_rate;
77		else
78			rate = tegra_clk_super_ops.round_rate(hw, rate,
79							      &pllp_rate);
80
81		req->best_parent_rate = pllp_rate;
82		req->best_parent_hw = pllp_hw;
83		req->rate = rate;
84	} else {
85		rate = clk_hw_round_rate(pllx_hw, rate);
86		req->best_parent_rate = rate;
87		req->best_parent_hw = pllx_hw;
88		req->rate = rate;
89	}
90
91	if (WARN_ON_ONCE(rate <= 0))
92		return -EINVAL;
93
94	return 0;
95}
96
97static 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,
103};
104
105static 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,
109};
110
111struct 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,
114		spinlock_t *lock)
115{
116	struct tegra_clk_super_mux *super;
117	struct clk *clk;
118	struct clk_init_data init;
119	u32 val;
120
121	if (WARN_ON(cclk_super))
122		return ERR_PTR(-EBUSY);
123
124	super = kzalloc(sizeof(*super), GFP_KERNEL);
125	if (!super)
126		return ERR_PTR(-ENOMEM);
127
128	init.name = name;
129	init.flags = flags;
130	init.parent_names = parent_names;
131	init.num_parents = num_parents;
132
133	super->reg = reg;
134	super->lock = lock;
135	super->width = 4;
136	super->flags = clk_super_flags;
137	super->hw.init = &init;
138
139	if (super->flags & TEGRA20_SUPER_CLK) {
140		init.ops = &tegra_cclk_super_mux_ops;
141	} else {
142		init.ops = &tegra_cclk_super_ops;
143
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;
150	}
151
152	/*
153	 * Tegra30+ has the following CPUG clock topology:
154	 *
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 |   ^      +-------+   ^
163	 *                           |      | E |   |                  |
164	 *                PLLX_SEL+--+      | R |   |       OVERHEAT+--+
165	 *                                  +---+   |
166	 *                                          |
167	 *                         SUPER_CDIV_ENB+--+
168	 *
169	 * Tegra20 is similar, but simpler. It doesn't have the divider and
170	 * thermal DIV2 skipper.
171	 *
172	 * At least for now we're not going to use clock-skipper, hence let's
173	 * ensure that it is disabled.
174	 */
175	val = readl_relaxed(reg + 4);
176	val &= ~SUPER_CDIV_ENB;
177	writel_relaxed(val, reg + 4);
178
179	clk = clk_register(NULL, &super->hw);
180	if (IS_ERR(clk))
181		kfree(super);
182	else
183		cclk_super = super;
184
185	return clk;
186}
187
188int tegra_cclk_pre_pllx_rate_change(void)
189{
190	if (IS_ERR_OR_NULL(cclk_super))
191		return -EINVAL;
192
193	if (cclk_super_get_parent(&cclk_super->hw) == PLLX_INDEX)
194		cclk_on_pllx = true;
195	else
196		cclk_on_pllx = false;
197
198	/*
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.
201	 */
202	if (cclk_on_pllx)
203		cclk_super_set_parent(&cclk_super->hw, PLLP_INDEX);
204
205	return 0;
206}
207
208void tegra_cclk_post_pllx_rate_change(void)
209{
210	if (cclk_on_pllx)
211		cclk_super_set_parent(&cclk_super->hw, PLLX_INDEX);
212}
213