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

clk: mvebu: extend clk-cpu for dynamic frequency scaling

This commit extends the existing clk-cpu driver used on Marvell Armada
XP platforms to support the dynamic frequency scaling of the CPU
clock. Non-dynamic frequency change was already supported (and used
before secondary CPUs are started), but the dynamic frequency change
requires a completely different procedure.

In order to achieve this, the clk_cpu_set_rate() function is reworked
to handle two separate cases:

- The case where the clock is enabled, which is the new dynamic
frequency change code, implemented in clk_cpu_on_set_rate(). This
part will be used for cpufreq activities.

- The case where the clock is disabled, which is the existing
frequency change code, moved in clk_cpu_off_set_rate(). This part
is already used to set the clock frequency of the secondary CPUs
before starting them.

In order to implement the dynamic frequency change function, we need
to access the PMU DFS registers, which are outside the currently
mapped "Clock Complex" registers, so a new area of registers is now
mapped. This affects the Device Tree binding, but we are careful to do
it in a backward-compatible way (by allowing the second pair of
registers to be non-existent, and in this case, ensuring
clk_cpu_on_set_rate() returns an error).

Note that technically speaking, the clk_cpu_on_set_rate() does not do
the entire procedure needed to change the frequency dynamically, as it
involves touching a number of PMSU registers. This is done through a
clock notifier registered by the PMSU driver in followup commits.

Cc: <devicetree@vger.kernel.org>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
Link: https://lkml.kernel.org/r/1404920715-19834-4-git-send-email-thomas.petazzoni@free-electrons.com
Signed-off-by: Jason Cooper <jason@lakedaemon.net>

authored by

Thomas Petazzoni and committed by
Jason Cooper
ee2d8ea1 a509ea84

+78 -7
+3 -2
Documentation/devicetree/bindings/clock/mvebu-cpu-clock.txt
··· 3 3 Required properties: 4 4 - compatible : shall be one of the following: 5 5 "marvell,armada-xp-cpu-clock" - cpu clocks for Armada XP 6 - - reg : Address and length of the clock complex register set 6 + - reg : Address and length of the clock complex register set, followed 7 + by address and length of the PMU DFS registers 7 8 - #clock-cells : should be set to 1. 8 9 - clocks : shall be the input parent clock phandle for the clock. 9 10 10 11 cpuclk: clock-complex@d0018700 { 11 12 #clock-cells = <1>; 12 13 compatible = "marvell,armada-xp-cpu-clock"; 13 - reg = <0xd0018700 0xA0>; 14 + reg = <0xd0018700 0xA0>, <0x1c054 0x10>; 14 15 clocks = <&coreclk 1>; 15 16 } 16 17
+75 -5
drivers/clk/mvebu/clk-cpu.c
··· 16 16 #include <linux/io.h> 17 17 #include <linux/of.h> 18 18 #include <linux/delay.h> 19 + #include <linux/mvebu-pmsu.h> 20 + #include <asm/smp_plat.h> 19 21 20 - #define SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET 0x0 21 - #define SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET 0xC 22 - #define SYS_CTRL_CLK_DIVIDER_MASK 0x3F 22 + #define SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET 0x0 23 + #define SYS_CTRL_CLK_DIVIDER_CTRL_RESET_ALL 0xff 24 + #define SYS_CTRL_CLK_DIVIDER_CTRL_RESET_SHIFT 8 25 + #define SYS_CTRL_CLK_DIVIDER_CTRL2_OFFSET 0x8 26 + #define SYS_CTRL_CLK_DIVIDER_CTRL2_NBCLK_RATIO_SHIFT 16 27 + #define SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET 0xC 28 + #define SYS_CTRL_CLK_DIVIDER_MASK 0x3F 29 + 30 + #define PMU_DFS_RATIO_SHIFT 16 31 + #define PMU_DFS_RATIO_MASK 0x3F 23 32 24 33 #define MAX_CPU 4 25 34 struct cpu_clk { ··· 37 28 const char *clk_name; 38 29 const char *parent_name; 39 30 void __iomem *reg_base; 31 + void __iomem *pmu_dfs; 40 32 }; 41 33 42 34 static struct clk **clks; ··· 72 62 return *parent_rate / div; 73 63 } 74 64 75 - static int clk_cpu_set_rate(struct clk_hw *hwclk, unsigned long rate, 76 - unsigned long parent_rate) 65 + static int clk_cpu_off_set_rate(struct clk_hw *hwclk, unsigned long rate, 66 + unsigned long parent_rate) 67 + 77 68 { 78 69 struct cpu_clk *cpuclk = to_cpu_clk(hwclk); 79 70 u32 reg, div; ··· 106 95 return 0; 107 96 } 108 97 98 + static int clk_cpu_on_set_rate(struct clk_hw *hwclk, unsigned long rate, 99 + unsigned long parent_rate) 100 + { 101 + u32 reg; 102 + unsigned long fabric_div, target_div, cur_rate; 103 + struct cpu_clk *cpuclk = to_cpu_clk(hwclk); 104 + 105 + /* 106 + * PMU DFS registers are not mapped, Device Tree does not 107 + * describes them. We cannot change the frequency dynamically. 108 + */ 109 + if (!cpuclk->pmu_dfs) 110 + return -ENODEV; 111 + 112 + cur_rate = __clk_get_rate(hwclk->clk); 113 + 114 + reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL2_OFFSET); 115 + fabric_div = (reg >> SYS_CTRL_CLK_DIVIDER_CTRL2_NBCLK_RATIO_SHIFT) & 116 + SYS_CTRL_CLK_DIVIDER_MASK; 117 + 118 + /* Frequency is going up */ 119 + if (rate == 2 * cur_rate) 120 + target_div = fabric_div / 2; 121 + /* Frequency is going down */ 122 + else 123 + target_div = fabric_div; 124 + 125 + if (target_div == 0) 126 + target_div = 1; 127 + 128 + reg = readl(cpuclk->pmu_dfs); 129 + reg &= ~(PMU_DFS_RATIO_MASK << PMU_DFS_RATIO_SHIFT); 130 + reg |= (target_div << PMU_DFS_RATIO_SHIFT); 131 + writel(reg, cpuclk->pmu_dfs); 132 + 133 + reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET); 134 + reg |= (SYS_CTRL_CLK_DIVIDER_CTRL_RESET_ALL << 135 + SYS_CTRL_CLK_DIVIDER_CTRL_RESET_SHIFT); 136 + writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET); 137 + 138 + return mvebu_pmsu_dfs_request(cpuclk->cpu); 139 + } 140 + 141 + static int clk_cpu_set_rate(struct clk_hw *hwclk, unsigned long rate, 142 + unsigned long parent_rate) 143 + { 144 + if (__clk_is_enabled(hwclk->clk)) 145 + return clk_cpu_on_set_rate(hwclk, rate, parent_rate); 146 + else 147 + return clk_cpu_off_set_rate(hwclk, rate, parent_rate); 148 + } 149 + 109 150 static const struct clk_ops cpu_ops = { 110 151 .recalc_rate = clk_cpu_recalc_rate, 111 152 .round_rate = clk_cpu_round_rate, ··· 168 105 { 169 106 struct cpu_clk *cpuclk; 170 107 void __iomem *clock_complex_base = of_iomap(node, 0); 108 + void __iomem *pmu_dfs_base = of_iomap(node, 1); 171 109 int ncpus = 0; 172 110 struct device_node *dn; 173 111 ··· 177 113 __func__); 178 114 return; 179 115 } 116 + 117 + if (pmu_dfs_base == NULL) 118 + pr_warn("%s: pmu-dfs base register not set, dynamic frequency scaling not available\n", 119 + __func__); 180 120 181 121 for_each_node_by_type(dn, "cpu") 182 122 ncpus++; ··· 214 146 cpuclk[cpu].clk_name = clk_name; 215 147 cpuclk[cpu].cpu = cpu; 216 148 cpuclk[cpu].reg_base = clock_complex_base; 149 + if (pmu_dfs_base) 150 + cpuclk[cpu].pmu_dfs = pmu_dfs_base + 4 * cpu; 217 151 cpuclk[cpu].hw.init = &init; 218 152 219 153 init.name = cpuclk[cpu].clk_name;