Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 324 lines 8.2 kB view raw
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (C) 2016 Maxime Ripard 4 * Maxime Ripard <maxime.ripard@free-electrons.com> 5 */ 6 7#include <linux/clk.h> 8#include <linux/clk-provider.h> 9#include <linux/delay.h> 10#include <linux/io.h> 11 12#include "ccu_gate.h" 13#include "ccu_mux.h" 14 15#define CCU_MUX_KEY_VALUE 0x16aa0000 16 17static u16 ccu_mux_get_prediv(struct ccu_common *common, 18 struct ccu_mux_internal *cm, 19 int parent_index) 20{ 21 u16 prediv = 1; 22 u32 reg; 23 24 if (!((common->features & CCU_FEATURE_FIXED_PREDIV) || 25 (common->features & CCU_FEATURE_VARIABLE_PREDIV) || 26 (common->features & CCU_FEATURE_ALL_PREDIV))) 27 return 1; 28 29 if (common->features & CCU_FEATURE_ALL_PREDIV) 30 return common->prediv; 31 32 reg = readl(common->base + common->reg); 33 if (parent_index < 0) { 34 parent_index = reg >> cm->shift; 35 parent_index &= (1 << cm->width) - 1; 36 } 37 38 if (common->features & CCU_FEATURE_FIXED_PREDIV) { 39 int i; 40 41 for (i = 0; i < cm->n_predivs; i++) 42 if (parent_index == cm->fixed_predivs[i].index) 43 prediv = cm->fixed_predivs[i].div; 44 } 45 46 if (common->features & CCU_FEATURE_VARIABLE_PREDIV) { 47 int i; 48 49 for (i = 0; i < cm->n_var_predivs; i++) 50 if (parent_index == cm->var_predivs[i].index) { 51 u8 div; 52 53 div = reg >> cm->var_predivs[i].shift; 54 div &= (1 << cm->var_predivs[i].width) - 1; 55 prediv = div + 1; 56 } 57 } 58 59 return prediv; 60} 61 62unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common, 63 struct ccu_mux_internal *cm, 64 int parent_index, 65 unsigned long parent_rate) 66{ 67 return parent_rate / ccu_mux_get_prediv(common, cm, parent_index); 68} 69EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_apply_prediv, "SUNXI_CCU"); 70 71static unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common, 72 struct ccu_mux_internal *cm, 73 int parent_index, 74 unsigned long parent_rate) 75{ 76 return parent_rate * ccu_mux_get_prediv(common, cm, parent_index); 77} 78 79int ccu_mux_helper_determine_rate(struct ccu_common *common, 80 struct ccu_mux_internal *cm, 81 struct clk_rate_request *req, 82 int (*round)(struct ccu_mux_internal *, 83 struct clk_rate_request *, 84 void *), 85 void *data) 86{ 87 unsigned long best_parent_rate = 0, best_rate = 0; 88 struct clk_hw *best_parent, *hw = &common->hw; 89 unsigned int i; 90 int ret; 91 92 if (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT) { 93 struct clk_rate_request adj_req = *req; 94 95 best_parent = clk_hw_get_parent(hw); 96 best_parent_rate = clk_hw_get_rate(best_parent); 97 98 adj_req.best_parent_hw = best_parent; 99 adj_req.best_parent_rate = ccu_mux_helper_apply_prediv(common, cm, -1, 100 best_parent_rate); 101 102 ret = round(cm, &adj_req, data); 103 if (ret) 104 return ret; 105 106 best_rate = adj_req.rate; 107 108 /* 109 * best_parent_rate might have been modified by our clock. 110 * Unapply the pre-divider if there's one, and give 111 * the actual frequency the parent needs to run at. 112 */ 113 best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1, 114 adj_req.best_parent_rate); 115 116 goto out; 117 } 118 119 for (i = 0; i < clk_hw_get_num_parents(hw); i++) { 120 struct clk_rate_request tmp_req = *req; 121 unsigned long parent_rate; 122 struct clk_hw *parent; 123 124 parent = clk_hw_get_parent_by_index(hw, i); 125 if (!parent) 126 continue; 127 128 parent_rate = ccu_mux_helper_apply_prediv(common, cm, i, 129 clk_hw_get_rate(parent)); 130 131 tmp_req.best_parent_hw = parent; 132 tmp_req.best_parent_rate = parent_rate; 133 134 ret = round(cm, &tmp_req, data); 135 if (ret) 136 continue; 137 138 /* 139 * parent_rate might have been modified by our clock. 140 * Unapply the pre-divider if there's one, and give 141 * the actual frequency the parent needs to run at. 142 */ 143 parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i, 144 tmp_req.best_parent_rate); 145 146 if (tmp_req.rate == req->rate) { 147 best_parent = parent; 148 best_parent_rate = parent_rate; 149 best_rate = tmp_req.rate; 150 goto out; 151 } 152 153 if (ccu_is_better_rate(common, req->rate, tmp_req.rate, best_rate)) { 154 best_rate = tmp_req.rate; 155 best_parent_rate = parent_rate; 156 best_parent = parent; 157 } 158 } 159 160 if (best_rate == 0) 161 return -EINVAL; 162 163out: 164 req->best_parent_hw = best_parent; 165 req->best_parent_rate = best_parent_rate; 166 req->rate = best_rate; 167 return 0; 168} 169EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_determine_rate, "SUNXI_CCU"); 170 171u8 ccu_mux_helper_get_parent(struct ccu_common *common, 172 struct ccu_mux_internal *cm) 173{ 174 u32 reg; 175 u8 parent; 176 177 reg = readl(common->base + common->reg); 178 parent = reg >> cm->shift; 179 parent &= (1 << cm->width) - 1; 180 181 if (cm->table) { 182 int num_parents = clk_hw_get_num_parents(&common->hw); 183 int i; 184 185 for (i = 0; i < num_parents; i++) 186 if (cm->table[i] == parent) 187 return i; 188 } 189 190 return parent; 191} 192EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_get_parent, "SUNXI_CCU"); 193 194int ccu_mux_helper_set_parent(struct ccu_common *common, 195 struct ccu_mux_internal *cm, 196 u8 index) 197{ 198 unsigned long flags; 199 u32 reg; 200 201 if (cm->table) 202 index = cm->table[index]; 203 204 spin_lock_irqsave(common->lock, flags); 205 206 reg = readl(common->base + common->reg); 207 208 /* The key field always reads as zero. */ 209 if (common->features & CCU_FEATURE_KEY_FIELD) 210 reg |= CCU_MUX_KEY_VALUE; 211 if (common->features & CCU_FEATURE_UPDATE_BIT) 212 reg |= CCU_SUNXI_UPDATE_BIT; 213 214 reg &= ~GENMASK(cm->width + cm->shift - 1, cm->shift); 215 writel(reg | (index << cm->shift), common->base + common->reg); 216 217 spin_unlock_irqrestore(common->lock, flags); 218 219 return 0; 220} 221EXPORT_SYMBOL_NS_GPL(ccu_mux_helper_set_parent, "SUNXI_CCU"); 222 223static void ccu_mux_disable(struct clk_hw *hw) 224{ 225 struct ccu_mux *cm = hw_to_ccu_mux(hw); 226 227 return ccu_gate_helper_disable(&cm->common, cm->enable); 228} 229 230static int ccu_mux_enable(struct clk_hw *hw) 231{ 232 struct ccu_mux *cm = hw_to_ccu_mux(hw); 233 234 return ccu_gate_helper_enable(&cm->common, cm->enable); 235} 236 237static int ccu_mux_is_enabled(struct clk_hw *hw) 238{ 239 struct ccu_mux *cm = hw_to_ccu_mux(hw); 240 241 return ccu_gate_helper_is_enabled(&cm->common, cm->enable); 242} 243 244static u8 ccu_mux_get_parent(struct clk_hw *hw) 245{ 246 struct ccu_mux *cm = hw_to_ccu_mux(hw); 247 248 return ccu_mux_helper_get_parent(&cm->common, &cm->mux); 249} 250 251static int ccu_mux_set_parent(struct clk_hw *hw, u8 index) 252{ 253 struct ccu_mux *cm = hw_to_ccu_mux(hw); 254 255 return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index); 256} 257 258static int ccu_mux_determine_rate(struct clk_hw *hw, 259 struct clk_rate_request *req) 260{ 261 struct ccu_mux *cm = hw_to_ccu_mux(hw); 262 263 if (cm->common.features & CCU_FEATURE_CLOSEST_RATE) 264 return clk_mux_determine_rate_flags(hw, req, CLK_MUX_ROUND_CLOSEST); 265 266 return clk_mux_determine_rate_flags(hw, req, 0); 267} 268 269static unsigned long ccu_mux_recalc_rate(struct clk_hw *hw, 270 unsigned long parent_rate) 271{ 272 struct ccu_mux *cm = hw_to_ccu_mux(hw); 273 274 return ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1, 275 parent_rate); 276} 277 278const struct clk_ops ccu_mux_ops = { 279 .disable = ccu_mux_disable, 280 .enable = ccu_mux_enable, 281 .is_enabled = ccu_mux_is_enabled, 282 283 .get_parent = ccu_mux_get_parent, 284 .set_parent = ccu_mux_set_parent, 285 286 .determine_rate = ccu_mux_determine_rate, 287 .recalc_rate = ccu_mux_recalc_rate, 288}; 289EXPORT_SYMBOL_NS_GPL(ccu_mux_ops, "SUNXI_CCU"); 290 291/* 292 * This clock notifier is called when the frequency of the of the parent 293 * PLL clock is to be changed. The idea is to switch the parent to a 294 * stable clock, such as the main oscillator, while the PLL frequency 295 * stabilizes. 296 */ 297static int ccu_mux_notifier_cb(struct notifier_block *nb, 298 unsigned long event, void *data) 299{ 300 struct ccu_mux_nb *mux = to_ccu_mux_nb(nb); 301 int ret = 0; 302 303 if (event == PRE_RATE_CHANGE) { 304 mux->original_index = ccu_mux_helper_get_parent(mux->common, 305 mux->cm); 306 ret = ccu_mux_helper_set_parent(mux->common, mux->cm, 307 mux->bypass_index); 308 } else if (event == POST_RATE_CHANGE) { 309 ret = ccu_mux_helper_set_parent(mux->common, mux->cm, 310 mux->original_index); 311 } 312 313 udelay(mux->delay_us); 314 315 return notifier_from_errno(ret); 316} 317 318int ccu_mux_notifier_register(struct clk *clk, struct ccu_mux_nb *mux_nb) 319{ 320 mux_nb->clk_nb.notifier_call = ccu_mux_notifier_cb; 321 322 return clk_notifier_register(clk, &mux_nb->clk_nb); 323} 324EXPORT_SYMBOL_NS_GPL(ccu_mux_notifier_register, "SUNXI_CCU");