cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

clk-tegra-super-cclk.c (5927B)


      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}