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
  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