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