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 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 118 static 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 126 static 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 132 struct 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 | | +->+ | +->+CPU 180 * ... | | | | | | 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 and 191 * thermal DIV2 skipper. 192 * 193 * At least for now we're not going to use clock-skipper, hence let's 194 * 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 else 204 cclk_super = super; 205 206 return clk; 207 } 208 209 int 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 else 217 cclk_on_pllx = false; 218 219 /* 220 * CPU needs to be temporarily re-parented away from PLLX if PLLX 221 * 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 229 void 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