diff options
Diffstat (limited to 'drivers/clk/samsung/clk-cpu.c')
-rw-r--r-- | drivers/clk/samsung/clk-cpu.c | 131 |
1 files changed, 130 insertions, 1 deletions
diff --git a/drivers/clk/samsung/clk-cpu.c b/drivers/clk/samsung/clk-cpu.c index 813003d6ce09..8bf7e805fd34 100644 --- a/drivers/clk/samsung/clk-cpu.c +++ b/drivers/clk/samsung/clk-cpu.c | |||
@@ -45,6 +45,13 @@ | |||
45 | #define E4210_DIV_STAT_CPU0 0x400 | 45 | #define E4210_DIV_STAT_CPU0 0x400 |
46 | #define E4210_DIV_STAT_CPU1 0x404 | 46 | #define E4210_DIV_STAT_CPU1 0x404 |
47 | 47 | ||
48 | #define E5433_MUX_SEL2 0x008 | ||
49 | #define E5433_MUX_STAT2 0x208 | ||
50 | #define E5433_DIV_CPU0 0x400 | ||
51 | #define E5433_DIV_CPU1 0x404 | ||
52 | #define E5433_DIV_STAT_CPU0 0x500 | ||
53 | #define E5433_DIV_STAT_CPU1 0x504 | ||
54 | |||
48 | #define E4210_DIV0_RATIO0_MASK 0x7 | 55 | #define E4210_DIV0_RATIO0_MASK 0x7 |
49 | #define E4210_DIV1_HPM_MASK (0x7 << 4) | 56 | #define E4210_DIV1_HPM_MASK (0x7 << 4) |
50 | #define E4210_DIV1_COPY_MASK (0x7 << 0) | 57 | #define E4210_DIV1_COPY_MASK (0x7 << 0) |
@@ -253,6 +260,102 @@ static int exynos_cpuclk_post_rate_change(struct clk_notifier_data *ndata, | |||
253 | } | 260 | } |
254 | 261 | ||
255 | /* | 262 | /* |
263 | * Helper function to set the 'safe' dividers for the CPU clock. The parameters | ||
264 | * div and mask contain the divider value and the register bit mask of the | ||
265 | * dividers to be programmed. | ||
266 | */ | ||
267 | static void exynos5433_set_safe_div(void __iomem *base, unsigned long div, | ||
268 | unsigned long mask) | ||
269 | { | ||
270 | unsigned long div0; | ||
271 | |||
272 | div0 = readl(base + E5433_DIV_CPU0); | ||
273 | div0 = (div0 & ~mask) | (div & mask); | ||
274 | writel(div0, base + E5433_DIV_CPU0); | ||
275 | wait_until_divider_stable(base + E5433_DIV_STAT_CPU0, mask); | ||
276 | } | ||
277 | |||
278 | /* handler for pre-rate change notification from parent clock */ | ||
279 | static int exynos5433_cpuclk_pre_rate_change(struct clk_notifier_data *ndata, | ||
280 | struct exynos_cpuclk *cpuclk, void __iomem *base) | ||
281 | { | ||
282 | const struct exynos_cpuclk_cfg_data *cfg_data = cpuclk->cfg; | ||
283 | unsigned long alt_prate = clk_get_rate(cpuclk->alt_parent); | ||
284 | unsigned long alt_div = 0, alt_div_mask = DIV_MASK; | ||
285 | unsigned long div0, div1 = 0, mux_reg; | ||
286 | unsigned long flags; | ||
287 | |||
288 | /* find out the divider values to use for clock data */ | ||
289 | while ((cfg_data->prate * 1000) != ndata->new_rate) { | ||
290 | if (cfg_data->prate == 0) | ||
291 | return -EINVAL; | ||
292 | cfg_data++; | ||
293 | } | ||
294 | |||
295 | spin_lock_irqsave(cpuclk->lock, flags); | ||
296 | |||
297 | /* | ||
298 | * For the selected PLL clock frequency, get the pre-defined divider | ||
299 | * values. | ||
300 | */ | ||
301 | div0 = cfg_data->div0; | ||
302 | div1 = cfg_data->div1; | ||
303 | |||
304 | /* | ||
305 | * If the old parent clock speed is less than the clock speed of | ||
306 | * the alternate parent, then it should be ensured that at no point | ||
307 | * the armclk speed is more than the old_prate until the dividers are | ||
308 | * set. Also workaround the issue of the dividers being set to lower | ||
309 | * values before the parent clock speed is set to new lower speed | ||
310 | * (this can result in too high speed of armclk output clocks). | ||
311 | */ | ||
312 | if (alt_prate > ndata->old_rate || ndata->old_rate > ndata->new_rate) { | ||
313 | unsigned long tmp_rate = min(ndata->old_rate, ndata->new_rate); | ||
314 | |||
315 | alt_div = DIV_ROUND_UP(alt_prate, tmp_rate) - 1; | ||
316 | WARN_ON(alt_div >= MAX_DIV); | ||
317 | |||
318 | exynos5433_set_safe_div(base, alt_div, alt_div_mask); | ||
319 | div0 |= alt_div; | ||
320 | } | ||
321 | |||
322 | /* select the alternate parent */ | ||
323 | mux_reg = readl(base + E5433_MUX_SEL2); | ||
324 | writel(mux_reg | 1, base + E5433_MUX_SEL2); | ||
325 | wait_until_mux_stable(base + E5433_MUX_STAT2, 0, 2); | ||
326 | |||
327 | /* alternate parent is active now. set the dividers */ | ||
328 | writel(div0, base + E5433_DIV_CPU0); | ||
329 | wait_until_divider_stable(base + E5433_DIV_STAT_CPU0, DIV_MASK_ALL); | ||
330 | |||
331 | writel(div1, base + E5433_DIV_CPU1); | ||
332 | wait_until_divider_stable(base + E5433_DIV_STAT_CPU1, DIV_MASK_ALL); | ||
333 | |||
334 | spin_unlock_irqrestore(cpuclk->lock, flags); | ||
335 | return 0; | ||
336 | } | ||
337 | |||
338 | /* handler for post-rate change notification from parent clock */ | ||
339 | static int exynos5433_cpuclk_post_rate_change(struct clk_notifier_data *ndata, | ||
340 | struct exynos_cpuclk *cpuclk, void __iomem *base) | ||
341 | { | ||
342 | unsigned long div = 0, div_mask = DIV_MASK; | ||
343 | unsigned long mux_reg; | ||
344 | unsigned long flags; | ||
345 | |||
346 | spin_lock_irqsave(cpuclk->lock, flags); | ||
347 | |||
348 | /* select apll as the alternate parent */ | ||
349 | mux_reg = readl(base + E5433_MUX_SEL2); | ||
350 | writel(mux_reg & ~1, base + E5433_MUX_SEL2); | ||
351 | wait_until_mux_stable(base + E5433_MUX_STAT2, 0, 1); | ||
352 | |||
353 | exynos5433_set_safe_div(base, div, div_mask); | ||
354 | spin_unlock_irqrestore(cpuclk->lock, flags); | ||
355 | return 0; | ||
356 | } | ||
357 | |||
358 | /* | ||
256 | * This notifier function is called for the pre-rate and post-rate change | 359 | * This notifier function is called for the pre-rate and post-rate change |
257 | * notifications of the parent clock of cpuclk. | 360 | * notifications of the parent clock of cpuclk. |
258 | */ | 361 | */ |
@@ -275,6 +378,29 @@ static int exynos_cpuclk_notifier_cb(struct notifier_block *nb, | |||
275 | return notifier_from_errno(err); | 378 | return notifier_from_errno(err); |
276 | } | 379 | } |
277 | 380 | ||
381 | /* | ||
382 | * This notifier function is called for the pre-rate and post-rate change | ||
383 | * notifications of the parent clock of cpuclk. | ||
384 | */ | ||
385 | static int exynos5433_cpuclk_notifier_cb(struct notifier_block *nb, | ||
386 | unsigned long event, void *data) | ||
387 | { | ||
388 | struct clk_notifier_data *ndata = data; | ||
389 | struct exynos_cpuclk *cpuclk; | ||
390 | void __iomem *base; | ||
391 | int err = 0; | ||
392 | |||
393 | cpuclk = container_of(nb, struct exynos_cpuclk, clk_nb); | ||
394 | base = cpuclk->ctrl_base; | ||
395 | |||
396 | if (event == PRE_RATE_CHANGE) | ||
397 | err = exynos5433_cpuclk_pre_rate_change(ndata, cpuclk, base); | ||
398 | else if (event == POST_RATE_CHANGE) | ||
399 | err = exynos5433_cpuclk_post_rate_change(ndata, cpuclk, base); | ||
400 | |||
401 | return notifier_from_errno(err); | ||
402 | } | ||
403 | |||
278 | /* helper function to register a CPU clock */ | 404 | /* helper function to register a CPU clock */ |
279 | int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx, | 405 | int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx, |
280 | unsigned int lookup_id, const char *name, const char *parent, | 406 | unsigned int lookup_id, const char *name, const char *parent, |
@@ -301,7 +427,10 @@ int __init exynos_register_cpu_clock(struct samsung_clk_provider *ctx, | |||
301 | cpuclk->ctrl_base = ctx->reg_base + offset; | 427 | cpuclk->ctrl_base = ctx->reg_base + offset; |
302 | cpuclk->lock = &ctx->lock; | 428 | cpuclk->lock = &ctx->lock; |
303 | cpuclk->flags = flags; | 429 | cpuclk->flags = flags; |
304 | cpuclk->clk_nb.notifier_call = exynos_cpuclk_notifier_cb; | 430 | if (flags & CLK_CPU_HAS_E5433_REGS_LAYOUT) |
431 | cpuclk->clk_nb.notifier_call = exynos5433_cpuclk_notifier_cb; | ||
432 | else | ||
433 | cpuclk->clk_nb.notifier_call = exynos_cpuclk_notifier_cb; | ||
305 | 434 | ||
306 | cpuclk->alt_parent = __clk_lookup(alt_parent); | 435 | cpuclk->alt_parent = __clk_lookup(alt_parent); |
307 | if (!cpuclk->alt_parent) { | 436 | if (!cpuclk->alt_parent) { |