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 30 static struct tegra_clk_super_mux *cclk_super; 31 static bool cclk_on_pllx; 32 33 static u8 cclk_super_get_parent(struct clk_hw *hw) 34 { 35 return tegra_clk_super_ops.get_parent(hw); 36 } 37 38 static int cclk_super_set_parent(struct clk_hw *hw, u8 index) 39 { 40 return tegra_clk_super_ops.set_parent(hw, index); 41 } 42 43 static 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 49 static 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 68 static 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 109 static 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 117 static 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 123 struct 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 200 int 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 220 void 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