Linux Audio

Check our new training course

Embedded Linux training

Mar 10-20, 2025, special US time zones
Register
Loading...
Note: File does not exist in v3.5.6.
  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			rate = tegra_clk_super_ops.round_rate(hw, rate,
 91							      &pllp_rate);
 92
 93		req->best_parent_rate = pllp_rate;
 94		req->best_parent_hw = pllp_hw;
 95		req->rate = rate;
 96	} else {
 97		rate = clk_hw_round_rate(pllx_hw, rate);
 98		req->best_parent_rate = rate;
 99		req->best_parent_hw = pllx_hw;
100		req->rate = rate;
101	}
102
103	if (WARN_ON_ONCE(rate <= 0))
104		return -EINVAL;
105
106	return 0;
107}
108
109static const struct clk_ops tegra_cclk_super_ops = {
110	.get_parent = cclk_super_get_parent,
111	.set_parent = cclk_super_set_parent,
112	.set_rate = cclk_super_set_rate,
113	.recalc_rate = cclk_super_recalc_rate,
114	.determine_rate = cclk_super_determine_rate,
115};
116
117static const struct clk_ops tegra_cclk_super_mux_ops = {
118	.get_parent = cclk_super_get_parent,
119	.set_parent = cclk_super_set_parent,
120	.determine_rate = cclk_super_determine_rate,
121};
122
123struct clk *tegra_clk_register_super_cclk(const char *name,
124		const char * const *parent_names, u8 num_parents,
125		unsigned long flags, void __iomem *reg, u8 clk_super_flags,
126		spinlock_t *lock)
127{
128	struct tegra_clk_super_mux *super;
129	struct clk *clk;
130	struct clk_init_data init;
131	u32 val;
132
133	if (WARN_ON(cclk_super))
134		return ERR_PTR(-EBUSY);
135
136	super = kzalloc(sizeof(*super), GFP_KERNEL);
137	if (!super)
138		return ERR_PTR(-ENOMEM);
139
140	init.name = name;
141	init.flags = flags;
142	init.parent_names = parent_names;
143	init.num_parents = num_parents;
144
145	super->reg = reg;
146	super->lock = lock;
147	super->width = 4;
148	super->flags = clk_super_flags;
149	super->hw.init = &init;
150
151	if (super->flags & TEGRA20_SUPER_CLK) {
152		init.ops = &tegra_cclk_super_mux_ops;
153	} else {
154		init.ops = &tegra_cclk_super_ops;
155
156		super->frac_div.reg = reg + 4;
157		super->frac_div.shift = 16;
158		super->frac_div.width = 8;
159		super->frac_div.frac_width = 1;
160		super->frac_div.lock = lock;
161		super->div_ops = &tegra_clk_frac_div_ops;
162	}
163
164	/*
165	 * Tegra30+ has the following CPUG clock topology:
166	 *
167	 *        +---+  +-------+  +-+            +-+                +-+
168	 * PLLP+->+   +->+DIVIDER+->+0|  +-------->+0|  ------------->+0|
169	 *        |   |  +-------+  | |  |  +---+  | |  |             | |
170	 * PLLC+->+MUX|             | +->+  | S |  | +->+             | +->+CPU
171	 *  ...   |   |             | |  |  | K |  | |  |  +-------+  | |
172	 * PLLX+->+-->+------------>+1|  +->+ I +->+1|  +->+ DIV2  +->+1|
173	 *        +---+             +++     | P |  +++     |SKIPPER|  +++
174	 *                           ^      | P |   ^      +-------+   ^
175	 *                           |      | E |   |                  |
176	 *                PLLX_SEL+--+      | R |   |       OVERHEAT+--+
177	 *                                  +---+   |
178	 *                                          |
179	 *                         SUPER_CDIV_ENB+--+
180	 *
181	 * Tegra20 is similar, but simpler. It doesn't have the divider and
182	 * thermal DIV2 skipper.
183	 *
184	 * At least for now we're not going to use clock-skipper, hence let's
185	 * ensure that it is disabled.
186	 */
187	val = readl_relaxed(reg + 4);
188	val &= ~SUPER_CDIV_ENB;
189	writel_relaxed(val, reg + 4);
190
191	clk = clk_register(NULL, &super->hw);
192	if (IS_ERR(clk))
193		kfree(super);
194	else
195		cclk_super = super;
196
197	return clk;
198}
199
200int tegra_cclk_pre_pllx_rate_change(void)
201{
202	if (IS_ERR_OR_NULL(cclk_super))
203		return -EINVAL;
204
205	if (cclk_super_get_parent(&cclk_super->hw) == PLLX_INDEX)
206		cclk_on_pllx = true;
207	else
208		cclk_on_pllx = false;
209
210	/*
211	 * CPU needs to be temporarily re-parented away from PLLX if PLLX
212	 * changes its rate. PLLP is a safe parent for CPU on all Tegra SoCs.
213	 */
214	if (cclk_on_pllx)
215		cclk_super_set_parent(&cclk_super->hw, PLLP_INDEX);
216
217	return 0;
218}
219
220void tegra_cclk_post_pllx_rate_change(void)
221{
222	if (cclk_on_pllx)
223		cclk_super_set_parent(&cclk_super->hw, PLLX_INDEX);
224}