brintos

brintos / linux-shallow public Read only

0
0
Text · 6.0 KiB · 3b22a4d Raw
234 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Based on clk-super.c4 * 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 project11 */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		424#define PLLX_INDEX		825 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	else60		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 |  | +->+             | +->+CPU180	 *  ...   |   |             | |  |  | 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 and191	 * thermal DIV2 skipper.192	 *193	 * At least for now we're not going to use clock-skipper, hence let's194	 * 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	else204		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	else217		cclk_on_pllx = false;218 219	/*220	 * CPU needs to be temporarily re-parented away from PLLX if PLLX221	 * 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