aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason Cooper <jason@lakedaemon.net>2014-07-22 16:46:48 -0400
committerJason Cooper <jason@lakedaemon.net>2014-07-22 16:46:48 -0400
commitba3ec5780bba27819bbc4f669e6c77418a00f14b (patch)
treeeebf5055d0fee3360e7930759ab1b3712eea2407
parentba364fc752daeded072a5ef31e43b84cb1f9e5fd (diff)
parentee2d8ea1e9bb27989f4f157520500dd6c4d45347 (diff)
Merge branch 'mvebu/soc-cpufreq' into mvebu/soc
-rw-r--r--Documentation/devicetree/bindings/clock/mvebu-cpu-clock.txt5
-rw-r--r--arch/arm/mach-mvebu/platsmp.c1
-rw-r--r--arch/arm/mach-mvebu/pmsu.c162
-rw-r--r--drivers/clk/mvebu/clk-cpu.c80
-rw-r--r--include/linux/mvebu-pmsu.h20
5 files changed, 261 insertions, 7 deletions
diff --git a/Documentation/devicetree/bindings/clock/mvebu-cpu-clock.txt b/Documentation/devicetree/bindings/clock/mvebu-cpu-clock.txt
index feb830130714..99c214660bdc 100644
--- a/Documentation/devicetree/bindings/clock/mvebu-cpu-clock.txt
+++ b/Documentation/devicetree/bindings/clock/mvebu-cpu-clock.txt
@@ -3,14 +3,15 @@ Device Tree Clock bindings for cpu clock of Marvell EBU platforms
3Required properties: 3Required properties:
4- compatible : shall be one of the following: 4- compatible : shall be one of the following:
5 "marvell,armada-xp-cpu-clock" - cpu clocks for Armada XP 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- #clock-cells : should be set to 1. 8- #clock-cells : should be set to 1.
8- clocks : shall be the input parent clock phandle for the clock. 9- clocks : shall be the input parent clock phandle for the clock.
9 10
10cpuclk: clock-complex@d0018700 { 11cpuclk: clock-complex@d0018700 {
11 #clock-cells = <1>; 12 #clock-cells = <1>;
12 compatible = "marvell,armada-xp-cpu-clock"; 13 compatible = "marvell,armada-xp-cpu-clock";
13 reg = <0xd0018700 0xA0>; 14 reg = <0xd0018700 0xA0>, <0x1c054 0x10>;
14 clocks = <&coreclk 1>; 15 clocks = <&coreclk 1>;
15} 16}
16 17
diff --git a/arch/arm/mach-mvebu/platsmp.c b/arch/arm/mach-mvebu/platsmp.c
index b6fa9f0c98b8..f278acebeaeb 100644
--- a/arch/arm/mach-mvebu/platsmp.c
+++ b/arch/arm/mach-mvebu/platsmp.c
@@ -67,6 +67,7 @@ static void __init set_secondary_cpus_clock(void)
67 if (!cpu_clk) 67 if (!cpu_clk)
68 return; 68 return;
69 clk_set_rate(cpu_clk, rate); 69 clk_set_rate(cpu_clk, rate);
70 clk_prepare_enable(cpu_clk);
70 } 71 }
71} 72}
72 73
diff --git a/arch/arm/mach-mvebu/pmsu.c b/arch/arm/mach-mvebu/pmsu.c
index 9c819d65b337..0ca285fdd569 100644
--- a/arch/arm/mach-mvebu/pmsu.c
+++ b/arch/arm/mach-mvebu/pmsu.c
@@ -18,20 +18,26 @@
18 18
19#define pr_fmt(fmt) "mvebu-pmsu: " fmt 19#define pr_fmt(fmt) "mvebu-pmsu: " fmt
20 20
21#include <linux/clk.h>
21#include <linux/cpu_pm.h> 22#include <linux/cpu_pm.h>
23#include <linux/delay.h>
22#include <linux/kernel.h> 24#include <linux/kernel.h>
23#include <linux/init.h> 25#include <linux/init.h>
24#include <linux/of_address.h> 26#include <linux/of_address.h>
27#include <linux/of_device.h>
25#include <linux/io.h> 28#include <linux/io.h>
26#include <linux/platform_device.h> 29#include <linux/platform_device.h>
30#include <linux/pm_opp.h>
27#include <linux/smp.h> 31#include <linux/smp.h>
28#include <linux/resource.h> 32#include <linux/resource.h>
33#include <linux/slab.h>
29#include <asm/cacheflush.h> 34#include <asm/cacheflush.h>
30#include <asm/cp15.h> 35#include <asm/cp15.h>
31#include <asm/smp_plat.h> 36#include <asm/smp_plat.h>
32#include <asm/suspend.h> 37#include <asm/suspend.h>
33#include <asm/tlbflush.h> 38#include <asm/tlbflush.h>
34#include "common.h" 39#include "common.h"
40#include "armada-370-xp.h"
35 41
36static void __iomem *pmsu_mp_base; 42static void __iomem *pmsu_mp_base;
37 43
@@ -57,6 +63,10 @@ static void __iomem *pmsu_mp_base;
57#define PMSU_STATUS_AND_MASK_IRQ_MASK BIT(24) 63#define PMSU_STATUS_AND_MASK_IRQ_MASK BIT(24)
58#define PMSU_STATUS_AND_MASK_FIQ_MASK BIT(25) 64#define PMSU_STATUS_AND_MASK_FIQ_MASK BIT(25)
59 65
66#define PMSU_EVENT_STATUS_AND_MASK(cpu) ((cpu * 0x100) + 0x120)
67#define PMSU_EVENT_STATUS_AND_MASK_DFS_DONE BIT(1)
68#define PMSU_EVENT_STATUS_AND_MASK_DFS_DONE_MASK BIT(17)
69
60#define PMSU_BOOT_ADDR_REDIRECT_OFFSET(cpu) ((cpu * 0x100) + 0x124) 70#define PMSU_BOOT_ADDR_REDIRECT_OFFSET(cpu) ((cpu * 0x100) + 0x124)
61 71
62/* PMSU fabric registers */ 72/* PMSU fabric registers */
@@ -291,3 +301,155 @@ static int __init armada_370_xp_cpu_pm_init(void)
291 301
292arch_initcall(armada_370_xp_cpu_pm_init); 302arch_initcall(armada_370_xp_cpu_pm_init);
293early_initcall(armada_370_xp_pmsu_init); 303early_initcall(armada_370_xp_pmsu_init);
304
305static void mvebu_pmsu_dfs_request_local(void *data)
306{
307 u32 reg;
308 u32 cpu = smp_processor_id();
309 unsigned long flags;
310
311 local_irq_save(flags);
312
313 /* Prepare to enter idle */
314 reg = readl(pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu));
315 reg |= PMSU_STATUS_AND_MASK_CPU_IDLE_WAIT |
316 PMSU_STATUS_AND_MASK_IRQ_MASK |
317 PMSU_STATUS_AND_MASK_FIQ_MASK;
318 writel(reg, pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu));
319
320 /* Request the DFS transition */
321 reg = readl(pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(cpu));
322 reg |= PMSU_CONTROL_AND_CONFIG_DFS_REQ;
323 writel(reg, pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(cpu));
324
325 /* The fact of entering idle will trigger the DFS transition */
326 wfi();
327
328 /*
329 * We're back from idle, the DFS transition has completed,
330 * clear the idle wait indication.
331 */
332 reg = readl(pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu));
333 reg &= ~PMSU_STATUS_AND_MASK_CPU_IDLE_WAIT;
334 writel(reg, pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu));
335
336 local_irq_restore(flags);
337}
338
339int mvebu_pmsu_dfs_request(int cpu)
340{
341 unsigned long timeout;
342 int hwcpu = cpu_logical_map(cpu);
343 u32 reg;
344
345 /* Clear any previous DFS DONE event */
346 reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu));
347 reg &= ~PMSU_EVENT_STATUS_AND_MASK_DFS_DONE;
348 writel(reg, pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu));
349
350 /* Mask the DFS done interrupt, since we are going to poll */
351 reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu));
352 reg |= PMSU_EVENT_STATUS_AND_MASK_DFS_DONE_MASK;
353 writel(reg, pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu));
354
355 /* Trigger the DFS on the appropriate CPU */
356 smp_call_function_single(cpu, mvebu_pmsu_dfs_request_local,
357 NULL, false);
358
359 /* Poll until the DFS done event is generated */
360 timeout = jiffies + HZ;
361 while (time_before(jiffies, timeout)) {
362 reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu));
363 if (reg & PMSU_EVENT_STATUS_AND_MASK_DFS_DONE)
364 break;
365 udelay(10);
366 }
367
368 if (time_after(jiffies, timeout))
369 return -ETIME;
370
371 /* Restore the DFS mask to its original state */
372 reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu));
373 reg &= ~PMSU_EVENT_STATUS_AND_MASK_DFS_DONE_MASK;
374 writel(reg, pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(hwcpu));
375
376 return 0;
377}
378
379static int __init armada_xp_pmsu_cpufreq_init(void)
380{
381 struct device_node *np;
382 struct resource res;
383 int ret, cpu;
384
385 if (!of_machine_is_compatible("marvell,armadaxp"))
386 return 0;
387
388 /*
389 * In order to have proper cpufreq handling, we need to ensure
390 * that the Device Tree description of the CPU clock includes
391 * the definition of the PMU DFS registers. If not, we do not
392 * register the clock notifier and the cpufreq driver. This
393 * piece of code is only for compatibility with old Device
394 * Trees.
395 */
396 np = of_find_compatible_node(NULL, NULL, "marvell,armada-xp-cpu-clock");
397 if (!np)
398 return 0;
399
400 ret = of_address_to_resource(np, 1, &res);
401 if (ret) {
402 pr_warn(FW_WARN "not enabling cpufreq, deprecated armada-xp-cpu-clock binding\n");
403 of_node_put(np);
404 return 0;
405 }
406
407 of_node_put(np);
408
409 /*
410 * For each CPU, this loop registers the operating points
411 * supported (which are the nominal CPU frequency and half of
412 * it), and registers the clock notifier that will take care
413 * of doing the PMSU part of a frequency transition.
414 */
415 for_each_possible_cpu(cpu) {
416 struct device *cpu_dev;
417 struct clk *clk;
418 int ret;
419
420 cpu_dev = get_cpu_device(cpu);
421 if (!cpu_dev) {
422 pr_err("Cannot get CPU %d\n", cpu);
423 continue;
424 }
425
426 clk = clk_get(cpu_dev, 0);
427 if (!clk) {
428 pr_err("Cannot get clock for CPU %d\n", cpu);
429 return -ENODEV;
430 }
431
432 /*
433 * In case of a failure of dev_pm_opp_add(), we don't
434 * bother with cleaning up the registered OPP (there's
435 * no function to do so), and simply cancel the
436 * registration of the cpufreq device.
437 */
438 ret = dev_pm_opp_add(cpu_dev, clk_get_rate(clk), 0);
439 if (ret) {
440 clk_put(clk);
441 return ret;
442 }
443
444 ret = dev_pm_opp_add(cpu_dev, clk_get_rate(clk) / 2, 0);
445 if (ret) {
446 clk_put(clk);
447 return ret;
448 }
449 }
450
451 platform_device_register_simple("cpufreq-generic", -1, NULL, 0);
452 return 0;
453}
454
455device_initcall(armada_xp_pmsu_cpufreq_init);
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;
diff --git a/include/linux/mvebu-pmsu.h b/include/linux/mvebu-pmsu.h
new file mode 100644
index 000000000000..b918d07efe23
--- /dev/null
+++ b/include/linux/mvebu-pmsu.h
@@ -0,0 +1,20 @@
1/*
2 * Copyright (C) 2012 Marvell
3 *
4 * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
5 *
6 * This file is licensed under the terms of the GNU General Public
7 * License version 2. This program is licensed "as is" without any
8 * warranty of any kind, whether express or implied.
9 */
10
11#ifndef __MVEBU_PMSU_H__
12#define __MVEBU_PMSU_H__
13
14#ifdef CONFIG_MACH_MVEBU_V7
15int mvebu_pmsu_dfs_request(int cpu);
16#else
17static inline int mvebu_pmsu_dfs_request(int cpu) { return -ENODEV; }
18#endif
19
20#endif /* __MVEBU_PMSU_H__ */