29
29
* down in order to keep the output clock rate within the previous OPP limits.
30
30
*/
31
31
32
+ #include <linux/delay.h>
32
33
#include <linux/errno.h>
33
34
#include <linux/io.h>
34
35
#include <linux/slab.h>
@@ -51,6 +52,8 @@ typedef int (*exynos_rate_change_fn_t)(struct clk_notifier_data *ndata,
51
52
* @div_cpu1: offset of CPU DIV1 register (for modifying divider values)
52
53
* @div_stat_cpu0: offset of CPU DIV0_STAT register (for checking DIV status)
53
54
* @div_stat_cpu1: offset of CPU DIV1_STAT register (for checking DIV status)
55
+ * @mux: offset of MUX register for choosing CPU clock source
56
+ * @divs: offsets of DIV registers (ACLK, ATCLK, PCLKDBG and PERIPHCLK)
54
57
*/
55
58
struct exynos_cpuclk_regs {
56
59
u32 mux_sel ;
@@ -59,6 +62,9 @@ struct exynos_cpuclk_regs {
59
62
u32 div_cpu1 ;
60
63
u32 div_stat_cpu0 ;
61
64
u32 div_stat_cpu1 ;
65
+
66
+ u32 mux ;
67
+ u32 divs [4 ];
62
68
};
63
69
64
70
/**
@@ -397,6 +403,167 @@ static int exynos5433_cpuclk_post_rate_change(struct clk_notifier_data *ndata,
397
403
return 0 ;
398
404
}
399
405
406
+ /* ---- Exynos850 ----------------------------------------------------------- */
407
+
408
+ #define E850_DIV_RATIO_MASK GENMASK(3, 0)
409
+ #define E850_BUSY_MASK BIT(16)
410
+
411
+ /* Max time for divider or mux to stabilize, usec */
412
+ #define E850_DIV_MUX_STAB_TIME 100
413
+ /* OSCCLK clock rate, Hz */
414
+ #define E850_OSCCLK (26 * MHZ)
415
+
416
+ static const struct exynos_cpuclk_regs e850cl0_cpuclk_regs = {
417
+ .mux = 0x100c ,
418
+ .divs = { 0x1800 , 0x1808 , 0x180c , 0x1810 },
419
+ };
420
+
421
+ static const struct exynos_cpuclk_regs e850cl1_cpuclk_regs = {
422
+ .mux = 0x1000 ,
423
+ .divs = { 0x1800 , 0x1808 , 0x180c , 0x1810 },
424
+ };
425
+
426
+ /*
427
+ * Set alternate parent rate to "rate" value or less.
428
+ *
429
+ * rate: Desired alt_parent rate, or 0 for max alt_parent rate
430
+ *
431
+ * Exynos850 doesn't have CPU clock divider in CMU_CPUCLx block (CMUREF divider
432
+ * doesn't affect CPU speed). So CPUCLx_SWITCH divider from CMU_TOP is used
433
+ * instead to adjust alternate parent speed.
434
+ *
435
+ * It's possible to use clk_set_max_rate() instead of this function, but it
436
+ * would set overly pessimistic rate values to alternate parent.
437
+ */
438
+ static int exynos850_alt_parent_set_max_rate (const struct clk_hw * alt_parent ,
439
+ unsigned long rate )
440
+ {
441
+ struct clk_hw * clk_div , * clk_divp ;
442
+ unsigned long divp_rate , div_rate , div ;
443
+ int ret ;
444
+
445
+ /* Divider from CMU_TOP */
446
+ clk_div = clk_hw_get_parent (alt_parent );
447
+ if (!clk_div )
448
+ return - ENOENT ;
449
+ /* Divider's parent from CMU_TOP */
450
+ clk_divp = clk_hw_get_parent (clk_div );
451
+ if (!clk_divp )
452
+ return - ENOENT ;
453
+ /* Divider input rate */
454
+ divp_rate = clk_hw_get_rate (clk_divp );
455
+ if (!divp_rate )
456
+ return - EINVAL ;
457
+
458
+ /* Calculate new alt_parent rate for integer divider value */
459
+ if (rate == 0 )
460
+ div = 1 ;
461
+ else
462
+ div = DIV_ROUND_UP (divp_rate , rate );
463
+ div_rate = DIV_ROUND_UP (divp_rate , div );
464
+ WARN_ON (div >= MAX_DIV );
465
+
466
+ /* alt_parent will propagate this change up to the divider */
467
+ ret = clk_set_rate (alt_parent -> clk , div_rate );
468
+ if (ret )
469
+ return ret ;
470
+ udelay (E850_DIV_MUX_STAB_TIME );
471
+
472
+ return 0 ;
473
+ }
474
+
475
+ /* Handler for pre-rate change notification from parent clock */
476
+ static int exynos850_cpuclk_pre_rate_change (struct clk_notifier_data * ndata ,
477
+ struct exynos_cpuclk * cpuclk )
478
+ {
479
+ const unsigned int shifts [4 ] = { 16 , 12 , 8 , 4 }; /* E850_CPU_DIV0() */
480
+ const struct exynos_cpuclk_regs * const regs = cpuclk -> chip -> regs ;
481
+ const struct exynos_cpuclk_cfg_data * cfg_data = cpuclk -> cfg ;
482
+ const struct clk_hw * alt_parent = cpuclk -> alt_parent ;
483
+ void __iomem * base = cpuclk -> base ;
484
+ unsigned long alt_prate = clk_hw_get_rate (alt_parent );
485
+ unsigned long flags ;
486
+ u32 mux_reg ;
487
+ size_t i ;
488
+ int ret ;
489
+
490
+ /* No actions are needed when switching to or from OSCCLK parent */
491
+ if (ndata -> new_rate == E850_OSCCLK || ndata -> old_rate == E850_OSCCLK )
492
+ return 0 ;
493
+
494
+ /* Find out the divider values to use for clock data */
495
+ while ((cfg_data -> prate * 1000 ) != ndata -> new_rate ) {
496
+ if (cfg_data -> prate == 0 )
497
+ return - EINVAL ;
498
+ cfg_data ++ ;
499
+ }
500
+
501
+ /*
502
+ * If the old parent clock speed is less than the clock speed of
503
+ * the alternate parent, then it should be ensured that at no point
504
+ * the armclk speed is more than the old_prate until the dividers are
505
+ * set. Also workaround the issue of the dividers being set to lower
506
+ * values before the parent clock speed is set to new lower speed
507
+ * (this can result in too high speed of armclk output clocks).
508
+ */
509
+ if (alt_prate > ndata -> old_rate || ndata -> old_rate > ndata -> new_rate ) {
510
+ unsigned long tmp_rate = min (ndata -> old_rate , ndata -> new_rate );
511
+
512
+ ret = exynos850_alt_parent_set_max_rate (alt_parent , tmp_rate );
513
+ if (ret )
514
+ return ret ;
515
+ }
516
+
517
+ spin_lock_irqsave (cpuclk -> lock , flags );
518
+
519
+ /* Select the alternate parent */
520
+ mux_reg = readl (base + regs -> mux );
521
+ writel (mux_reg | 1 , base + regs -> mux );
522
+ wait_until_mux_stable (base + regs -> mux , 16 , 1 , 0 );
523
+
524
+ /* Alternate parent is active now. Set the dividers */
525
+ for (i = 0 ; i < ARRAY_SIZE (shifts ); ++ i ) {
526
+ unsigned long div = (cfg_data -> div0 >> shifts [i ]) & 0xf ;
527
+ u32 val ;
528
+
529
+ val = readl (base + regs -> divs [i ]);
530
+ val = (val & ~E850_DIV_RATIO_MASK ) | div ;
531
+ writel (val , base + regs -> divs [i ]);
532
+ wait_until_divider_stable (base + regs -> divs [i ], E850_BUSY_MASK );
533
+ }
534
+
535
+ spin_unlock_irqrestore (cpuclk -> lock , flags );
536
+
537
+ return 0 ;
538
+ }
539
+
540
+ /* Handler for post-rate change notification from parent clock */
541
+ static int exynos850_cpuclk_post_rate_change (struct clk_notifier_data * ndata ,
542
+ struct exynos_cpuclk * cpuclk )
543
+ {
544
+ const struct exynos_cpuclk_regs * const regs = cpuclk -> chip -> regs ;
545
+ const struct clk_hw * alt_parent = cpuclk -> alt_parent ;
546
+ void __iomem * base = cpuclk -> base ;
547
+ unsigned long flags ;
548
+ u32 mux_reg ;
549
+
550
+ /* No actions are needed when switching to or from OSCCLK parent */
551
+ if (ndata -> new_rate == E850_OSCCLK || ndata -> old_rate == E850_OSCCLK )
552
+ return 0 ;
553
+
554
+ spin_lock_irqsave (cpuclk -> lock , flags );
555
+
556
+ /* Select main parent (PLL) for mux */
557
+ mux_reg = readl (base + regs -> mux );
558
+ writel (mux_reg & ~1 , base + regs -> mux );
559
+ wait_until_mux_stable (base + regs -> mux , 16 , 1 , 0 );
560
+
561
+ spin_unlock_irqrestore (cpuclk -> lock , flags );
562
+
563
+ /* Set alt_parent rate back to max */
564
+ return exynos850_alt_parent_set_max_rate (alt_parent , 0 );
565
+ }
566
+
400
567
/* -------------------------------------------------------------------------- */
401
568
402
569
/* Common round rate callback usable for all types of CPU clocks */
@@ -459,6 +626,16 @@ static const struct exynos_cpuclk_chip exynos_clkcpu_chips[] = {
459
626
.pre_rate_cb = exynos5433_cpuclk_pre_rate_change ,
460
627
.post_rate_cb = exynos5433_cpuclk_post_rate_change ,
461
628
},
629
+ [CPUCLK_LAYOUT_E850_CL0 ] = {
630
+ .regs = & e850cl0_cpuclk_regs ,
631
+ .pre_rate_cb = exynos850_cpuclk_pre_rate_change ,
632
+ .post_rate_cb = exynos850_cpuclk_post_rate_change ,
633
+ },
634
+ [CPUCLK_LAYOUT_E850_CL1 ] = {
635
+ .regs = & e850cl1_cpuclk_regs ,
636
+ .pre_rate_cb = exynos850_cpuclk_pre_rate_change ,
637
+ .post_rate_cb = exynos850_cpuclk_post_rate_change ,
638
+ },
462
639
};
463
640
464
641
/* helper function to register a CPU clock */
0 commit comments