aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk
diff options
context:
space:
mode:
authorThomas Petazzoni <thomas.petazzoni@free-electrons.com>2014-07-09 11:45:11 -0400
committerJason Cooper <jason@lakedaemon.net>2014-07-16 08:58:38 -0400
commitee2d8ea1e9bb27989f4f157520500dd6c4d45347 (patch)
tree2291ed9c826c72840424d0707b813974bf0cfbd8 /drivers/clk
parenta509ea840b8e29e512764803e30b805c7ea89038 (diff)
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>
Diffstat (limited to 'drivers/clk')
-rw-r--r--drivers/clk/mvebu/clk-cpu.c80
1 files changed, 75 insertions, 5 deletions
diff --git a/drivers/clk/mvebu/clk-cpu.c b/drivers/clk/mvebu/clk-cpu.c
index 8ebf757d29e2..3821a88077ea 100644
--- a/drivers/clk/mvebu/clk-cpu.c
+++ b/drivers/clk/mvebu/clk-cpu.c
@@ -16,10 +16,19 @@
16#include <linux/io.h> 16#include <linux/io.h>
17#include <linux/of.h> 17#include <linux/of.h>
18#include <linux/delay.h> 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 22#define SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET 0x0
21#define SYS_CTRL_CLK_DIVIDER_VALUE_OFFSET 0xC 23#define SYS_CTRL_CLK_DIVIDER_CTRL_RESET_ALL 0xff
22#define SYS_CTRL_CLK_DIVIDER_MASK 0x3F 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#define MAX_CPU 4 33#define MAX_CPU 4
25struct cpu_clk { 34struct cpu_clk {
@@ -28,6 +37,7 @@ struct cpu_clk {
28 const char *clk_name; 37 const char *clk_name;
29 const char *parent_name; 38 const char *parent_name;
30 void __iomem *reg_base; 39 void __iomem *reg_base;
40 void __iomem *pmu_dfs;
31}; 41};
32 42
33static struct clk **clks; 43static struct clk **clks;
@@ -62,8 +72,9 @@ static long clk_cpu_round_rate(struct clk_hw *hwclk, unsigned long rate,
62 return *parent_rate / div; 72 return *parent_rate / div;
63} 73}
64 74
65static int clk_cpu_set_rate(struct clk_hw *hwclk, unsigned long rate, 75static int clk_cpu_off_set_rate(struct clk_hw *hwclk, unsigned long rate,
66 unsigned long parent_rate) 76 unsigned long parent_rate)
77
67{ 78{
68 struct cpu_clk *cpuclk = to_cpu_clk(hwclk); 79 struct cpu_clk *cpuclk = to_cpu_clk(hwclk);
69 u32 reg, div; 80 u32 reg, div;
@@ -95,6 +106,58 @@ static int clk_cpu_set_rate(struct clk_hw *hwclk, unsigned long rate,
95 return 0; 106 return 0;
96} 107}
97 108
109static int clk_cpu_on_set_rate(struct clk_hw *hwclk, unsigned long rate,
110 unsigned long parent_rate)
111{
112 u32 reg;
113 unsigned long fabric_div, target_div, cur_rate;
114 struct cpu_clk *cpuclk = to_cpu_clk(hwclk);
115
116 /*
117 * PMU DFS registers are not mapped, Device Tree does not
118 * describes them. We cannot change the frequency dynamically.
119 */
120 if (!cpuclk->pmu_dfs)
121 return -ENODEV;
122
123 cur_rate = __clk_get_rate(hwclk->clk);
124
125 reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL2_OFFSET);
126 fabric_div = (reg >> SYS_CTRL_CLK_DIVIDER_CTRL2_NBCLK_RATIO_SHIFT) &
127 SYS_CTRL_CLK_DIVIDER_MASK;
128
129 /* Frequency is going up */
130 if (rate == 2 * cur_rate)
131 target_div = fabric_div / 2;
132 /* Frequency is going down */
133 else
134 target_div = fabric_div;
135
136 if (target_div == 0)
137 target_div = 1;
138
139 reg = readl(cpuclk->pmu_dfs);
140 reg &= ~(PMU_DFS_RATIO_MASK << PMU_DFS_RATIO_SHIFT);
141 reg |= (target_div << PMU_DFS_RATIO_SHIFT);
142 writel(reg, cpuclk->pmu_dfs);
143
144 reg = readl(cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET);
145 reg |= (SYS_CTRL_CLK_DIVIDER_CTRL_RESET_ALL <<
146 SYS_CTRL_CLK_DIVIDER_CTRL_RESET_SHIFT);
147 writel(reg, cpuclk->reg_base + SYS_CTRL_CLK_DIVIDER_CTRL_OFFSET);
148
149 return mvebu_pmsu_dfs_request(cpuclk->cpu);
150}
151
152static int clk_cpu_set_rate(struct clk_hw *hwclk, unsigned long rate,
153 unsigned long parent_rate)
154{
155 if (__clk_is_enabled(hwclk->clk))
156 return clk_cpu_on_set_rate(hwclk, rate, parent_rate);
157 else
158 return clk_cpu_off_set_rate(hwclk, rate, parent_rate);
159}
160
98static const struct clk_ops cpu_ops = { 161static const struct clk_ops cpu_ops = {
99 .recalc_rate = clk_cpu_recalc_rate, 162 .recalc_rate = clk_cpu_recalc_rate,
100 .round_rate = clk_cpu_round_rate, 163 .round_rate = clk_cpu_round_rate,
@@ -105,6 +168,7 @@ static void __init of_cpu_clk_setup(struct device_node *node)
105{ 168{
106 struct cpu_clk *cpuclk; 169 struct cpu_clk *cpuclk;
107 void __iomem *clock_complex_base = of_iomap(node, 0); 170 void __iomem *clock_complex_base = of_iomap(node, 0);
171 void __iomem *pmu_dfs_base = of_iomap(node, 1);
108 int ncpus = 0; 172 int ncpus = 0;
109 struct device_node *dn; 173 struct device_node *dn;
110 174
@@ -114,6 +178,10 @@ static void __init of_cpu_clk_setup(struct device_node *node)
114 return; 178 return;
115 } 179 }
116 180
181 if (pmu_dfs_base == NULL)
182 pr_warn("%s: pmu-dfs base register not set, dynamic frequency scaling not available\n",
183 __func__);
184
117 for_each_node_by_type(dn, "cpu") 185 for_each_node_by_type(dn, "cpu")
118 ncpus++; 186 ncpus++;
119 187
@@ -146,6 +214,8 @@ static void __init of_cpu_clk_setup(struct device_node *node)
146 cpuclk[cpu].clk_name = clk_name; 214 cpuclk[cpu].clk_name = clk_name;
147 cpuclk[cpu].cpu = cpu; 215 cpuclk[cpu].cpu = cpu;
148 cpuclk[cpu].reg_base = clock_complex_base; 216 cpuclk[cpu].reg_base = clock_complex_base;
217 if (pmu_dfs_base)
218 cpuclk[cpu].pmu_dfs = pmu_dfs_base + 4 * cpu;
149 cpuclk[cpu].hw.init = &init; 219 cpuclk[cpu].hw.init = &init;
150 220
151 init.name = cpuclk[cpu].clk_name; 221 init.name = cpuclk[cpu].clk_name;