diff options
author | Rob Herring <rob.herring@calxeda.com> | 2012-10-12 13:45:34 -0400 |
---|---|---|
committer | Rob Herring <rob.herring@calxeda.com> | 2012-11-07 18:15:36 -0500 |
commit | be6a98d3f00c292d347465d96acbec9d8c2783cf (patch) | |
tree | 0904f11c26daaf11be227060c3afcebeeeea9b79 /drivers/cpuidle | |
parent | 8f0d8163b50e01f398b14bcd4dc039ac5ab18d64 (diff) |
cpuidle: add Calxeda SOC idle support
Add support for core powergating on Calxeda platforms. Initially, this
supports ECX-1000 (highbank), but support will be added for ECX-2000
later.
Signed-off-by: Rob Herring <rob.herring@calxeda.com>
Cc: Len Brown <len.brown@intel.com>
Cc: "Rafael J. Wysocki" <rjw@sisk.pl>
Diffstat (limited to 'drivers/cpuidle')
-rw-r--r-- | drivers/cpuidle/Kconfig | 10 | ||||
-rw-r--r-- | drivers/cpuidle/Makefile | 2 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle-calxeda.c | 161 |
3 files changed, 173 insertions, 0 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index a76b689e553b..d2d007b0651a 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig | |||
@@ -21,3 +21,13 @@ config CPU_IDLE_GOV_MENU | |||
21 | 21 | ||
22 | config ARCH_NEEDS_CPU_IDLE_COUPLED | 22 | config ARCH_NEEDS_CPU_IDLE_COUPLED |
23 | def_bool n | 23 | def_bool n |
24 | |||
25 | if CPU_IDLE | ||
26 | |||
27 | config CPU_IDLE_CALXEDA | ||
28 | bool "CPU Idle Driver for Calxeda processors" | ||
29 | depends on ARCH_HIGHBANK | ||
30 | help | ||
31 | Select this to enable cpuidle on Calxeda processors. | ||
32 | |||
33 | endif | ||
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 38c8f69f30cf..03ee87482c71 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile | |||
@@ -4,3 +4,5 @@ | |||
4 | 4 | ||
5 | obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ | 5 | obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ |
6 | obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o | 6 | obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o |
7 | |||
8 | obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o | ||
diff --git a/drivers/cpuidle/cpuidle-calxeda.c b/drivers/cpuidle/cpuidle-calxeda.c new file mode 100644 index 000000000000..e1aab38c5a8d --- /dev/null +++ b/drivers/cpuidle/cpuidle-calxeda.c | |||
@@ -0,0 +1,161 @@ | |||
1 | /* | ||
2 | * Copyright 2012 Calxeda, Inc. | ||
3 | * | ||
4 | * Based on arch/arm/plat-mxc/cpuidle.c: | ||
5 | * Copyright 2012 Freescale Semiconductor, Inc. | ||
6 | * Copyright 2012 Linaro Ltd. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms and conditions of the GNU General Public License, | ||
10 | * version 2, as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
15 | * more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License along with | ||
18 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | ||
20 | |||
21 | #include <linux/cpuidle.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/io.h> | ||
24 | #include <linux/of.h> | ||
25 | #include <linux/time.h> | ||
26 | #include <linux/delay.h> | ||
27 | #include <linux/suspend.h> | ||
28 | #include <asm/cpuidle.h> | ||
29 | #include <asm/proc-fns.h> | ||
30 | #include <asm/smp_scu.h> | ||
31 | #include <asm/suspend.h> | ||
32 | #include <asm/cacheflush.h> | ||
33 | #include <asm/cp15.h> | ||
34 | |||
35 | extern void highbank_set_cpu_jump(int cpu, void *jump_addr); | ||
36 | extern void *scu_base_addr; | ||
37 | |||
38 | static struct cpuidle_device __percpu *calxeda_idle_cpuidle_devices; | ||
39 | |||
40 | static inline unsigned int get_auxcr(void) | ||
41 | { | ||
42 | unsigned int val; | ||
43 | asm("mrc p15, 0, %0, c1, c0, 1 @ get AUXCR" : "=r" (val) : : "cc"); | ||
44 | return val; | ||
45 | } | ||
46 | |||
47 | static inline void set_auxcr(unsigned int val) | ||
48 | { | ||
49 | asm volatile("mcr p15, 0, %0, c1, c0, 1 @ set AUXCR" | ||
50 | : : "r" (val) : "cc"); | ||
51 | isb(); | ||
52 | } | ||
53 | |||
54 | static noinline void calxeda_idle_restore(void) | ||
55 | { | ||
56 | set_cr(get_cr() | CR_C); | ||
57 | set_auxcr(get_auxcr() | 0x40); | ||
58 | scu_power_mode(scu_base_addr, SCU_PM_NORMAL); | ||
59 | } | ||
60 | |||
61 | static int calxeda_idle_finish(unsigned long val) | ||
62 | { | ||
63 | /* Already flushed cache, but do it again as the outer cache functions | ||
64 | * dirty the cache with spinlocks */ | ||
65 | flush_cache_all(); | ||
66 | |||
67 | set_auxcr(get_auxcr() & ~0x40); | ||
68 | set_cr(get_cr() & ~CR_C); | ||
69 | |||
70 | scu_power_mode(scu_base_addr, SCU_PM_DORMANT); | ||
71 | |||
72 | cpu_do_idle(); | ||
73 | |||
74 | /* Restore things if we didn't enter power-gating */ | ||
75 | calxeda_idle_restore(); | ||
76 | return 1; | ||
77 | } | ||
78 | |||
79 | static int calxeda_pwrdown_idle(struct cpuidle_device *dev, | ||
80 | struct cpuidle_driver *drv, | ||
81 | int index) | ||
82 | { | ||
83 | highbank_set_cpu_jump(smp_processor_id(), cpu_resume); | ||
84 | cpu_suspend(0, calxeda_idle_finish); | ||
85 | return index; | ||
86 | } | ||
87 | |||
88 | static void calxeda_idle_cpuidle_devices_uninit(void) | ||
89 | { | ||
90 | int i; | ||
91 | struct cpuidle_device *dev; | ||
92 | |||
93 | for_each_possible_cpu(i) { | ||
94 | dev = per_cpu_ptr(calxeda_idle_cpuidle_devices, i); | ||
95 | cpuidle_unregister_device(dev); | ||
96 | } | ||
97 | |||
98 | free_percpu(calxeda_idle_cpuidle_devices); | ||
99 | } | ||
100 | |||
101 | static struct cpuidle_driver calxeda_idle_driver = { | ||
102 | .name = "calxeda_idle", | ||
103 | .en_core_tk_irqen = 1, | ||
104 | .states = { | ||
105 | ARM_CPUIDLE_WFI_STATE, | ||
106 | { | ||
107 | .name = "PG", | ||
108 | .desc = "Power Gate", | ||
109 | .flags = CPUIDLE_FLAG_TIME_VALID, | ||
110 | .exit_latency = 30, | ||
111 | .power_usage = 50, | ||
112 | .target_residency = 200, | ||
113 | .enter = calxeda_pwrdown_idle, | ||
114 | }, | ||
115 | }, | ||
116 | .state_count = 2, | ||
117 | }; | ||
118 | |||
119 | static int __init calxeda_cpuidle_init(void) | ||
120 | { | ||
121 | int cpu_id; | ||
122 | int ret; | ||
123 | struct cpuidle_device *dev; | ||
124 | struct cpuidle_driver *drv = &calxeda_idle_driver; | ||
125 | |||
126 | if (!of_machine_is_compatible("calxeda,highbank")) | ||
127 | return -ENODEV; | ||
128 | |||
129 | ret = cpuidle_register_driver(drv); | ||
130 | if (ret) | ||
131 | return ret; | ||
132 | |||
133 | calxeda_idle_cpuidle_devices = alloc_percpu(struct cpuidle_device); | ||
134 | if (calxeda_idle_cpuidle_devices == NULL) { | ||
135 | ret = -ENOMEM; | ||
136 | goto unregister_drv; | ||
137 | } | ||
138 | |||
139 | /* initialize state data for each cpuidle_device */ | ||
140 | for_each_possible_cpu(cpu_id) { | ||
141 | dev = per_cpu_ptr(calxeda_idle_cpuidle_devices, cpu_id); | ||
142 | dev->cpu = cpu_id; | ||
143 | dev->state_count = drv->state_count; | ||
144 | |||
145 | ret = cpuidle_register_device(dev); | ||
146 | if (ret) { | ||
147 | pr_err("Failed to register cpu %u, error: %d\n", | ||
148 | cpu_id, ret); | ||
149 | goto uninit; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | return 0; | ||
154 | |||
155 | uninit: | ||
156 | calxeda_idle_cpuidle_devices_uninit(); | ||
157 | unregister_drv: | ||
158 | cpuidle_unregister_driver(drv); | ||
159 | return ret; | ||
160 | } | ||
161 | module_init(calxeda_cpuidle_init); | ||