Lines Matching +full:set +full:- +full:rate +full:- +full:parent
1 // SPDX-License-Identifier: GPL-2.0-only
6 * based on clk/samsung/clk-cpu.c
15 * CPU clock rate and this relation is usually specified in the hardware manual
18 * The below implementation of the CPU clock allows the rate changes of the CPU
19 * clock and the corresponding rate changes of the auxillary clocks of the CPU
21 * for each configurable rate which is then used to program the clock hardware
22 * registers to acheive a fast co-oridinated rate change for all the CPU domain
25 * On a rate change request for the CPU clock, the rate change is propagated
29 * down in order to keep the output clock rate within the previous OPP limits.
36 #include <linux/clk-provider.h>
42 * @alt_parent: alternate parent clock to use when switching the speed
43 * of the primary parent clock.
44 * @reg_base: base register for cpu-clock values.
46 * primary parent clock.
48 * @rate_table: pll-rates and their associated dividers
49 * @reg_data: cpu-specific register settings
68 struct rockchip_cpuclk *cpuclk, unsigned long rate) in rockchip_get_cpuclk_settings() argument
71 cpuclk->rate_table; in rockchip_get_cpuclk_settings()
74 for (i = 0; i < cpuclk->rate_count; i++) { in rockchip_get_cpuclk_settings()
75 if (rate == rate_table[i].prate) in rockchip_get_cpuclk_settings()
86 const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data; in rockchip_cpuclk_recalc_rate()
87 u32 clksel0 = readl_relaxed(cpuclk->reg_base + reg_data->core_reg[0]); in rockchip_cpuclk_recalc_rate()
89 clksel0 >>= reg_data->div_core_shift[0]; in rockchip_cpuclk_recalc_rate()
90 clksel0 &= reg_data->div_core_mask[0]; in rockchip_cpuclk_recalc_rate()
99 const struct rockchip_cpuclk_rate_table *rate) in rockchip_cpuclk_set_dividers() argument
103 /* alternate parent is active now. set the dividers */ in rockchip_cpuclk_set_dividers()
104 for (i = 0; i < ARRAY_SIZE(rate->divs); i++) { in rockchip_cpuclk_set_dividers()
105 const struct rockchip_cpuclk_clksel *clksel = &rate->divs[i]; in rockchip_cpuclk_set_dividers()
107 if (!clksel->reg) in rockchip_cpuclk_set_dividers()
111 __func__, clksel->reg, clksel->val); in rockchip_cpuclk_set_dividers()
112 writel(clksel->val, cpuclk->reg_base + clksel->reg); in rockchip_cpuclk_set_dividers()
117 const struct rockchip_cpuclk_rate_table *rate) in rockchip_cpuclk_set_pre_muxs() argument
121 /* alternate parent is active now. set the pre_muxs */ in rockchip_cpuclk_set_pre_muxs()
122 for (i = 0; i < ARRAY_SIZE(rate->pre_muxs); i++) { in rockchip_cpuclk_set_pre_muxs()
123 const struct rockchip_cpuclk_clksel *clksel = &rate->pre_muxs[i]; in rockchip_cpuclk_set_pre_muxs()
125 if (!clksel->reg) in rockchip_cpuclk_set_pre_muxs()
129 __func__, clksel->reg, clksel->val); in rockchip_cpuclk_set_pre_muxs()
130 writel(clksel->val, cpuclk->reg_base + clksel->reg); in rockchip_cpuclk_set_pre_muxs()
135 const struct rockchip_cpuclk_rate_table *rate) in rockchip_cpuclk_set_post_muxs() argument
139 /* alternate parent is active now. set the muxs */ in rockchip_cpuclk_set_post_muxs()
140 for (i = 0; i < ARRAY_SIZE(rate->post_muxs); i++) { in rockchip_cpuclk_set_post_muxs()
141 const struct rockchip_cpuclk_clksel *clksel = &rate->post_muxs[i]; in rockchip_cpuclk_set_post_muxs()
143 if (!clksel->reg) in rockchip_cpuclk_set_post_muxs()
147 __func__, clksel->reg, clksel->val); in rockchip_cpuclk_set_post_muxs()
148 writel(clksel->val, cpuclk->reg_base + clksel->reg); in rockchip_cpuclk_set_post_muxs()
155 const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data; in rockchip_cpuclk_pre_rate_change()
156 const struct rockchip_cpuclk_rate_table *rate; in rockchip_cpuclk_pre_rate_change() local
161 /* check validity of the new rate */ in rockchip_cpuclk_pre_rate_change()
162 rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate); in rockchip_cpuclk_pre_rate_change()
163 if (!rate) { in rockchip_cpuclk_pre_rate_change()
164 pr_err("%s: Invalid rate : %lu for cpuclk\n", in rockchip_cpuclk_pre_rate_change()
165 __func__, ndata->new_rate); in rockchip_cpuclk_pre_rate_change()
166 return -EINVAL; in rockchip_cpuclk_pre_rate_change()
169 alt_prate = clk_get_rate(cpuclk->alt_parent); in rockchip_cpuclk_pre_rate_change()
171 spin_lock_irqsave(cpuclk->lock, flags); in rockchip_cpuclk_pre_rate_change()
174 * If the old parent clock speed is less than the clock speed in rockchip_cpuclk_pre_rate_change()
175 * of the alternate parent, then it should be ensured that at no point in rockchip_cpuclk_pre_rate_change()
177 * set. in rockchip_cpuclk_pre_rate_change()
179 if (alt_prate > ndata->old_rate) { in rockchip_cpuclk_pre_rate_change()
181 alt_div = DIV_ROUND_UP(alt_prate, ndata->old_rate) - 1; in rockchip_cpuclk_pre_rate_change()
182 if (alt_div > reg_data->div_core_mask[0]) { in rockchip_cpuclk_pre_rate_change()
183 pr_warn("%s: limiting alt-divider %lu to %d\n", in rockchip_cpuclk_pre_rate_change()
184 __func__, alt_div, reg_data->div_core_mask[0]); in rockchip_cpuclk_pre_rate_change()
185 alt_div = reg_data->div_core_mask[0]; in rockchip_cpuclk_pre_rate_change()
192 * dividing the primary parent by the extra dividers that were in rockchip_cpuclk_pre_rate_change()
195 pr_debug("%s: setting div %lu as alt-rate %lu > old-rate %lu\n", in rockchip_cpuclk_pre_rate_change()
196 __func__, alt_div, alt_prate, ndata->old_rate); in rockchip_cpuclk_pre_rate_change()
198 for (i = 0; i < reg_data->num_cores; i++) { in rockchip_cpuclk_pre_rate_change()
199 writel(HIWORD_UPDATE(alt_div, reg_data->div_core_mask[i], in rockchip_cpuclk_pre_rate_change()
200 reg_data->div_core_shift[i]), in rockchip_cpuclk_pre_rate_change()
201 cpuclk->reg_base + reg_data->core_reg[i]); in rockchip_cpuclk_pre_rate_change()
205 rockchip_cpuclk_set_pre_muxs(cpuclk, rate); in rockchip_cpuclk_pre_rate_change()
207 /* select alternate parent */ in rockchip_cpuclk_pre_rate_change()
208 if (reg_data->mux_core_reg) in rockchip_cpuclk_pre_rate_change()
209 writel(HIWORD_UPDATE(reg_data->mux_core_alt, in rockchip_cpuclk_pre_rate_change()
210 reg_data->mux_core_mask, in rockchip_cpuclk_pre_rate_change()
211 reg_data->mux_core_shift), in rockchip_cpuclk_pre_rate_change()
212 cpuclk->reg_base + reg_data->mux_core_reg); in rockchip_cpuclk_pre_rate_change()
214 writel(HIWORD_UPDATE(reg_data->mux_core_alt, in rockchip_cpuclk_pre_rate_change()
215 reg_data->mux_core_mask, in rockchip_cpuclk_pre_rate_change()
216 reg_data->mux_core_shift), in rockchip_cpuclk_pre_rate_change()
217 cpuclk->reg_base + reg_data->core_reg[0]); in rockchip_cpuclk_pre_rate_change()
219 spin_unlock_irqrestore(cpuclk->lock, flags); in rockchip_cpuclk_pre_rate_change()
226 const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data; in rockchip_cpuclk_post_rate_change()
227 const struct rockchip_cpuclk_rate_table *rate; in rockchip_cpuclk_post_rate_change() local
231 rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate); in rockchip_cpuclk_post_rate_change()
232 if (!rate) { in rockchip_cpuclk_post_rate_change()
233 pr_err("%s: Invalid rate : %lu for cpuclk\n", in rockchip_cpuclk_post_rate_change()
234 __func__, ndata->new_rate); in rockchip_cpuclk_post_rate_change()
235 return -EINVAL; in rockchip_cpuclk_post_rate_change()
238 spin_lock_irqsave(cpuclk->lock, flags); in rockchip_cpuclk_post_rate_change()
240 if (ndata->old_rate < ndata->new_rate) in rockchip_cpuclk_post_rate_change()
241 rockchip_cpuclk_set_dividers(cpuclk, rate); in rockchip_cpuclk_post_rate_change()
244 * post-rate change event, re-mux to primary parent and remove dividers. in rockchip_cpuclk_post_rate_change()
247 * primary parent by the extra dividers that were needed for the alt. in rockchip_cpuclk_post_rate_change()
250 if (reg_data->mux_core_reg) in rockchip_cpuclk_post_rate_change()
251 writel(HIWORD_UPDATE(reg_data->mux_core_main, in rockchip_cpuclk_post_rate_change()
252 reg_data->mux_core_mask, in rockchip_cpuclk_post_rate_change()
253 reg_data->mux_core_shift), in rockchip_cpuclk_post_rate_change()
254 cpuclk->reg_base + reg_data->mux_core_reg); in rockchip_cpuclk_post_rate_change()
256 writel(HIWORD_UPDATE(reg_data->mux_core_main, in rockchip_cpuclk_post_rate_change()
257 reg_data->mux_core_mask, in rockchip_cpuclk_post_rate_change()
258 reg_data->mux_core_shift), in rockchip_cpuclk_post_rate_change()
259 cpuclk->reg_base + reg_data->core_reg[0]); in rockchip_cpuclk_post_rate_change()
261 rockchip_cpuclk_set_post_muxs(cpuclk, rate); in rockchip_cpuclk_post_rate_change()
264 for (i = 0; i < reg_data->num_cores; i++) { in rockchip_cpuclk_post_rate_change()
265 writel(HIWORD_UPDATE(0, reg_data->div_core_mask[i], in rockchip_cpuclk_post_rate_change()
266 reg_data->div_core_shift[i]), in rockchip_cpuclk_post_rate_change()
267 cpuclk->reg_base + reg_data->core_reg[i]); in rockchip_cpuclk_post_rate_change()
270 if (ndata->old_rate > ndata->new_rate) in rockchip_cpuclk_post_rate_change()
271 rockchip_cpuclk_set_dividers(cpuclk, rate); in rockchip_cpuclk_post_rate_change()
273 spin_unlock_irqrestore(cpuclk->lock, flags); in rockchip_cpuclk_post_rate_change()
278 * This clock notifier is called when the frequency of the parent clock
280 * the divider clocks, remux to temporary parent and handling the safe
281 * frequency levels when using temporary parent.
291 __func__, event, ndata->old_rate, ndata->new_rate); in rockchip_cpuclk_notifier_cb()
312 pr_err("%s: needs at least two parent clocks\n", __func__); in rockchip_clk_register_cpuclk()
313 return ERR_PTR(-EINVAL); in rockchip_clk_register_cpuclk()
318 return ERR_PTR(-ENOMEM); in rockchip_clk_register_cpuclk()
321 init.parent_names = &parent_names[reg_data->mux_core_main]; in rockchip_clk_register_cpuclk()
325 /* only allow rate changes when we have a rate table */ in rockchip_clk_register_cpuclk()
328 /* disallow automatic parent changes by ccf */ in rockchip_clk_register_cpuclk()
333 cpuclk->reg_base = reg_base; in rockchip_clk_register_cpuclk()
334 cpuclk->lock = lock; in rockchip_clk_register_cpuclk()
335 cpuclk->reg_data = reg_data; in rockchip_clk_register_cpuclk()
336 cpuclk->clk_nb.notifier_call = rockchip_cpuclk_notifier_cb; in rockchip_clk_register_cpuclk()
337 cpuclk->hw.init = &init; in rockchip_clk_register_cpuclk()
339 cpuclk->alt_parent = __clk_lookup(parent_names[reg_data->mux_core_alt]); in rockchip_clk_register_cpuclk()
340 if (!cpuclk->alt_parent) { in rockchip_clk_register_cpuclk()
341 pr_err("%s: could not lookup alternate parent: (%d)\n", in rockchip_clk_register_cpuclk()
342 __func__, reg_data->mux_core_alt); in rockchip_clk_register_cpuclk()
343 ret = -EINVAL; in rockchip_clk_register_cpuclk()
347 ret = clk_prepare_enable(cpuclk->alt_parent); in rockchip_clk_register_cpuclk()
349 pr_err("%s: could not enable alternate parent\n", in rockchip_clk_register_cpuclk()
354 clk = __clk_lookup(parent_names[reg_data->mux_core_main]); in rockchip_clk_register_cpuclk()
356 pr_err("%s: could not lookup parent clock: (%d) %s\n", in rockchip_clk_register_cpuclk()
357 __func__, reg_data->mux_core_main, in rockchip_clk_register_cpuclk()
358 parent_names[reg_data->mux_core_main]); in rockchip_clk_register_cpuclk()
359 ret = -EINVAL; in rockchip_clk_register_cpuclk()
363 ret = clk_notifier_register(clk, &cpuclk->clk_nb); in rockchip_clk_register_cpuclk()
371 cpuclk->rate_count = nrates; in rockchip_clk_register_cpuclk()
372 cpuclk->rate_table = kmemdup_array(rates, nrates, sizeof(*rates), in rockchip_clk_register_cpuclk()
374 if (!cpuclk->rate_table) { in rockchip_clk_register_cpuclk()
375 ret = -ENOMEM; in rockchip_clk_register_cpuclk()
380 cclk = clk_register(NULL, &cpuclk->hw); in rockchip_clk_register_cpuclk()
390 kfree(cpuclk->rate_table); in rockchip_clk_register_cpuclk()
392 clk_notifier_unregister(clk, &cpuclk->clk_nb); in rockchip_clk_register_cpuclk()
394 clk_disable_unprepare(cpuclk->alt_parent); in rockchip_clk_register_cpuclk()