2929 * down in order to keep the output clock rate within the previous OPP limits.
3030 */
3131
32+ #include <linux/delay.h>
3233#include <linux/errno.h>
3334#include <linux/io.h>
3435#include <linux/slab.h>
@@ -51,6 +52,8 @@ typedef int (*exynos_rate_change_fn_t)(struct clk_notifier_data *ndata,
5152 * @div_cpu1: offset of CPU DIV1 register (for modifying divider values)
5253 * @div_stat_cpu0: offset of CPU DIV0_STAT register (for checking DIV status)
5354 * @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)
5457 */
5558struct exynos_cpuclk_regs {
5659 u32 mux_sel ;
@@ -59,6 +62,9 @@ struct exynos_cpuclk_regs {
5962 u32 div_cpu1 ;
6063 u32 div_stat_cpu0 ;
6164 u32 div_stat_cpu1 ;
65+
66+ u32 mux ;
67+ u32 divs [4 ];
6268};
6369
6470/**
@@ -397,6 +403,167 @@ static int exynos5433_cpuclk_post_rate_change(struct clk_notifier_data *ndata,
397403 return 0 ;
398404}
399405
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+
400567/* -------------------------------------------------------------------------- */
401568
402569/* Common round rate callback usable for all types of CPU clocks */
@@ -459,6 +626,16 @@ static const struct exynos_cpuclk_chip exynos_clkcpu_chips[] = {
459626 .pre_rate_cb = exynos5433_cpuclk_pre_rate_change ,
460627 .post_rate_cb = exynos5433_cpuclk_post_rate_change ,
461628 },
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+ },
462639};
463640
464641/* helper function to register a CPU clock */
0 commit comments