Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Based on clk-super.c
0004  * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
0005  *
0006  * Based on older tegra20-cpufreq driver by Colin Cross <ccross@google.com>
0007  * Copyright (C) 2010 Google, Inc.
0008  *
0009  * Author: Dmitry Osipenko <digetx@gmail.com>
0010  * Copyright (C) 2019 GRATE-DRIVER project
0011  */
0012 
0013 #include <linux/bits.h>
0014 #include <linux/clk-provider.h>
0015 #include <linux/err.h>
0016 #include <linux/io.h>
0017 #include <linux/kernel.h>
0018 #include <linux/slab.h>
0019 #include <linux/types.h>
0020 
0021 #include "clk.h"
0022 
0023 #define PLLP_INDEX      4
0024 #define PLLX_INDEX      8
0025 
0026 #define SUPER_CDIV_ENB      BIT(31)
0027 
0028 #define TSENSOR_SLOWDOWN    BIT(23)
0029 
0030 static struct tegra_clk_super_mux *cclk_super;
0031 static bool cclk_on_pllx;
0032 
0033 static u8 cclk_super_get_parent(struct clk_hw *hw)
0034 {
0035     return tegra_clk_super_ops.get_parent(hw);
0036 }
0037 
0038 static int cclk_super_set_parent(struct clk_hw *hw, u8 index)
0039 {
0040     return tegra_clk_super_ops.set_parent(hw, index);
0041 }
0042 
0043 static int cclk_super_set_rate(struct clk_hw *hw, unsigned long rate,
0044                    unsigned long parent_rate)
0045 {
0046     return tegra_clk_super_ops.set_rate(hw, rate, parent_rate);
0047 }
0048 
0049 static unsigned long cclk_super_recalc_rate(struct clk_hw *hw,
0050                         unsigned long parent_rate)
0051 {
0052     struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
0053     u32 val = readl_relaxed(super->reg);
0054     unsigned int div2;
0055 
0056     /* check whether thermal throttling is active */
0057     if (val & TSENSOR_SLOWDOWN)
0058         div2 = 1;
0059     else
0060         div2 = 0;
0061 
0062     if (cclk_super_get_parent(hw) == PLLX_INDEX)
0063         return parent_rate >> div2;
0064 
0065     return tegra_clk_super_ops.recalc_rate(hw, parent_rate) >> div2;
0066 }
0067 
0068 static int cclk_super_determine_rate(struct clk_hw *hw,
0069                      struct clk_rate_request *req)
0070 {
0071     struct clk_hw *pllp_hw = clk_hw_get_parent_by_index(hw, PLLP_INDEX);
0072     struct clk_hw *pllx_hw = clk_hw_get_parent_by_index(hw, PLLX_INDEX);
0073     struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
0074     unsigned long pllp_rate;
0075     long rate = req->rate;
0076 
0077     if (WARN_ON_ONCE(!pllp_hw || !pllx_hw))
0078         return -EINVAL;
0079 
0080     /*
0081      * Switch parent to PLLP for all CCLK rates that are suitable for PLLP.
0082      * PLLX will be disabled in this case, saving some power.
0083      */
0084     pllp_rate = clk_hw_get_rate(pllp_hw);
0085 
0086     if (rate <= pllp_rate) {
0087         if (super->flags & TEGRA20_SUPER_CLK)
0088             rate = pllp_rate;
0089         else
0090             rate = tegra_clk_super_ops.round_rate(hw, rate,
0091                                   &pllp_rate);
0092 
0093         req->best_parent_rate = pllp_rate;
0094         req->best_parent_hw = pllp_hw;
0095         req->rate = rate;
0096     } else {
0097         rate = clk_hw_round_rate(pllx_hw, rate);
0098         req->best_parent_rate = rate;
0099         req->best_parent_hw = pllx_hw;
0100         req->rate = rate;
0101     }
0102 
0103     if (WARN_ON_ONCE(rate <= 0))
0104         return -EINVAL;
0105 
0106     return 0;
0107 }
0108 
0109 static const struct clk_ops tegra_cclk_super_ops = {
0110     .get_parent = cclk_super_get_parent,
0111     .set_parent = cclk_super_set_parent,
0112     .set_rate = cclk_super_set_rate,
0113     .recalc_rate = cclk_super_recalc_rate,
0114     .determine_rate = cclk_super_determine_rate,
0115 };
0116 
0117 static const struct clk_ops tegra_cclk_super_mux_ops = {
0118     .get_parent = cclk_super_get_parent,
0119     .set_parent = cclk_super_set_parent,
0120     .determine_rate = cclk_super_determine_rate,
0121 };
0122 
0123 struct clk *tegra_clk_register_super_cclk(const char *name,
0124         const char * const *parent_names, u8 num_parents,
0125         unsigned long flags, void __iomem *reg, u8 clk_super_flags,
0126         spinlock_t *lock)
0127 {
0128     struct tegra_clk_super_mux *super;
0129     struct clk *clk;
0130     struct clk_init_data init;
0131     u32 val;
0132 
0133     if (WARN_ON(cclk_super))
0134         return ERR_PTR(-EBUSY);
0135 
0136     super = kzalloc(sizeof(*super), GFP_KERNEL);
0137     if (!super)
0138         return ERR_PTR(-ENOMEM);
0139 
0140     init.name = name;
0141     init.flags = flags;
0142     init.parent_names = parent_names;
0143     init.num_parents = num_parents;
0144 
0145     super->reg = reg;
0146     super->lock = lock;
0147     super->width = 4;
0148     super->flags = clk_super_flags;
0149     super->hw.init = &init;
0150 
0151     if (super->flags & TEGRA20_SUPER_CLK) {
0152         init.ops = &tegra_cclk_super_mux_ops;
0153     } else {
0154         init.ops = &tegra_cclk_super_ops;
0155 
0156         super->frac_div.reg = reg + 4;
0157         super->frac_div.shift = 16;
0158         super->frac_div.width = 8;
0159         super->frac_div.frac_width = 1;
0160         super->frac_div.lock = lock;
0161         super->div_ops = &tegra_clk_frac_div_ops;
0162     }
0163 
0164     /*
0165      * Tegra30+ has the following CPUG clock topology:
0166      *
0167      *        +---+  +-------+  +-+            +-+                +-+
0168      * PLLP+->+   +->+DIVIDER+->+0|  +-------->+0|  ------------->+0|
0169      *        |   |  +-------+  | |  |  +---+  | |  |             | |
0170      * PLLC+->+MUX|             | +->+  | S |  | +->+             | +->+CPU
0171      *  ...   |   |             | |  |  | K |  | |  |  +-------+  | |
0172      * PLLX+->+-->+------------>+1|  +->+ I +->+1|  +->+ DIV2  +->+1|
0173      *        +---+             +++     | P |  +++     |SKIPPER|  +++
0174      *                           ^      | P |   ^      +-------+   ^
0175      *                           |      | E |   |                  |
0176      *                PLLX_SEL+--+      | R |   |       OVERHEAT+--+
0177      *                                  +---+   |
0178      *                                          |
0179      *                         SUPER_CDIV_ENB+--+
0180      *
0181      * Tegra20 is similar, but simpler. It doesn't have the divider and
0182      * thermal DIV2 skipper.
0183      *
0184      * At least for now we're not going to use clock-skipper, hence let's
0185      * ensure that it is disabled.
0186      */
0187     val = readl_relaxed(reg + 4);
0188     val &= ~SUPER_CDIV_ENB;
0189     writel_relaxed(val, reg + 4);
0190 
0191     clk = clk_register(NULL, &super->hw);
0192     if (IS_ERR(clk))
0193         kfree(super);
0194     else
0195         cclk_super = super;
0196 
0197     return clk;
0198 }
0199 
0200 int tegra_cclk_pre_pllx_rate_change(void)
0201 {
0202     if (IS_ERR_OR_NULL(cclk_super))
0203         return -EINVAL;
0204 
0205     if (cclk_super_get_parent(&cclk_super->hw) == PLLX_INDEX)
0206         cclk_on_pllx = true;
0207     else
0208         cclk_on_pllx = false;
0209 
0210     /*
0211      * CPU needs to be temporarily re-parented away from PLLX if PLLX
0212      * changes its rate. PLLP is a safe parent for CPU on all Tegra SoCs.
0213      */
0214     if (cclk_on_pllx)
0215         cclk_super_set_parent(&cclk_super->hw, PLLP_INDEX);
0216 
0217     return 0;
0218 }
0219 
0220 void tegra_cclk_post_pllx_rate_change(void)
0221 {
0222     if (cclk_on_pllx)
0223         cclk_super_set_parent(&cclk_super->hw, PLLX_INDEX);
0224 }