linux/drivers/clk/tegra/clk-tegra-super-cclk.c
<<
>>
Prefs
   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}
 225