aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/cpuidle/cpuidle-big_little.c
diff options
context:
space:
mode:
authorLorenzo Pieralisi <lorenzo.pieralisi@arm.com>2013-06-12 11:40:01 -0400
committerOlof Johansson <olof@lixom.net>2013-08-28 14:28:51 -0400
commit14d2c34cfa0026ba3916f5d5b2f1ad433beeef5a (patch)
tree6239d42d4fa016f2a8a054b6dcb706d8a7147b4a /drivers/cpuidle/cpuidle-big_little.c
parent9ee2ee0f0576fc070b02e5d31a2c4d09c39f439a (diff)
cpuidle: big.LITTLE: vexpress-TC2 CPU idle driver
The big.LITTLE architecture is composed of two clusters of cpus. One cluster contains less powerful but more energy efficient processors and the other cluster groups the powerful but energy-intensive cpus. The TC2 testchip implements two clusters of CPUs (A7 and A15 clusters in a big.LITTLE configuration) connected through a CCI interconnect that manages coherency of their respective L2 caches and intercluster distributed virtual memory messages (DVM). TC2 testchip integrates a power controller that manages cores resets, wake-up IRQs and cluster low-power states. Power states are managed at cluster level, which means that voltage is removed from a cluster iff all cores in a cluster are in a wfi state. Single cores can enter a reset state which is identical to wfi in terms of power consumption but simplifies the way cluster states are entered. This patch provides a multiple driver CPU idle implementation for TC2 which paves the way for a generic big.LITTLE idle driver for all upcoming big.LITTLE based systems on chip. The driver relies on the MCPM infrastructure to coordinate and manage core power states; in particular MCPM allows to suspend specific cores and hides the CPUs coordination required to shut-down clusters of CPUs. Power down sequences for the respective clusters are implemented in the MCPM TC2 backend, with all code needed to clean caches and exit coherency. The multiple driver CPU idle infrastructure allows to define different C-states for big and little cores, determined at boot by checking the part id of the possible CPUs and initializing the respective logical masks in the big and little drivers. Current big.little systems are composed of A7 and A15 clusters, as implemented in TC2, but in the future that may change and the driver will have evolve to retrieve what is a 'big' cpu and what is a 'little' cpu in order to build the correct topology. Cc: Kevin Hilman <khilman@linaro.org> Cc: Amit Kucheria <amit.kucheria@linaro.org> Cc: Olof Johansson <olof@lixom.net> Cc: Nicolas Pitre <nicolas.pitre@linaro.org> Cc: Rafael J. Wysocki <rjw@sisk.pl> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Signed-off-by: Olof Johansson <olof@lixom.net>
Diffstat (limited to 'drivers/cpuidle/cpuidle-big_little.c')
-rw-r--r--drivers/cpuidle/cpuidle-big_little.c209
1 files changed, 209 insertions, 0 deletions
diff --git a/drivers/cpuidle/cpuidle-big_little.c b/drivers/cpuidle/cpuidle-big_little.c
new file mode 100644
index 000000000000..b45fc6249041
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-big_little.c
@@ -0,0 +1,209 @@
1/*
2 * Copyright (c) 2013 ARM/Linaro
3 *
4 * Authors: Daniel Lezcano <daniel.lezcano@linaro.org>
5 * Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
6 * Nicolas Pitre <nicolas.pitre@linaro.org>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 * Maintainer: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
13 * Maintainer: Daniel Lezcano <daniel.lezcano@linaro.org>
14 */
15#include <linux/cpuidle.h>
16#include <linux/cpu_pm.h>
17#include <linux/slab.h>
18#include <linux/of.h>
19
20#include <asm/cpu.h>
21#include <asm/cputype.h>
22#include <asm/cpuidle.h>
23#include <asm/mcpm.h>
24#include <asm/smp_plat.h>
25#include <asm/suspend.h>
26
27static int bl_enter_powerdown(struct cpuidle_device *dev,
28 struct cpuidle_driver *drv, int idx);
29
30/*
31 * NB: Owing to current menu governor behaviour big and LITTLE
32 * index 1 states have to define exit_latency and target_residency for
33 * cluster state since, when all CPUs in a cluster hit it, the cluster
34 * can be shutdown. This means that when a single CPU enters this state
35 * the exit_latency and target_residency values are somewhat overkill.
36 * There is no notion of cluster states in the menu governor, so CPUs
37 * have to define CPU states where possibly the cluster will be shutdown
38 * depending on the state of other CPUs. idle states entry and exit happen
39 * at random times; however the cluster state provides target_residency
40 * values as if all CPUs in a cluster enter the state at once; this is
41 * somewhat optimistic and behaviour should be fixed either in the governor
42 * or in the MCPM back-ends.
43 * To make this driver 100% generic the number of states and the exit_latency
44 * target_residency values must be obtained from device tree bindings.
45 *
46 * exit_latency: refers to the TC2 vexpress test chip and depends on the
47 * current cluster operating point. It is the time it takes to get the CPU
48 * up and running when the CPU is powered up on cluster wake-up from shutdown.
49 * Current values for big and LITTLE clusters are provided for clusters
50 * running at default operating points.
51 *
52 * target_residency: it is the minimum amount of time the cluster has
53 * to be down to break even in terms of power consumption. cluster
54 * shutdown has inherent dynamic power costs (L2 writebacks to DRAM
55 * being the main factor) that depend on the current operating points.
56 * The current values for both clusters are provided for a CPU whose half
57 * of L2 lines are dirty and require cleaning to DRAM, and takes into
58 * account leakage static power values related to the vexpress TC2 testchip.
59 */
60static struct cpuidle_driver bl_idle_little_driver = {
61 .name = "little_idle",
62 .owner = THIS_MODULE,
63 .states[0] = ARM_CPUIDLE_WFI_STATE,
64 .states[1] = {
65 .enter = bl_enter_powerdown,
66 .exit_latency = 700,
67 .target_residency = 2500,
68 .flags = CPUIDLE_FLAG_TIME_VALID |
69 CPUIDLE_FLAG_TIMER_STOP,
70 .name = "C1",
71 .desc = "ARM little-cluster power down",
72 },
73 .state_count = 2,
74};
75
76static struct cpuidle_driver bl_idle_big_driver = {
77 .name = "big_idle",
78 .owner = THIS_MODULE,
79 .states[0] = ARM_CPUIDLE_WFI_STATE,
80 .states[1] = {
81 .enter = bl_enter_powerdown,
82 .exit_latency = 500,
83 .target_residency = 2000,
84 .flags = CPUIDLE_FLAG_TIME_VALID |
85 CPUIDLE_FLAG_TIMER_STOP,
86 .name = "C1",
87 .desc = "ARM big-cluster power down",
88 },
89 .state_count = 2,
90};
91
92/*
93 * notrace prevents trace shims from getting inserted where they
94 * should not. Global jumps and ldrex/strex must not be inserted
95 * in power down sequences where caches and MMU may be turned off.
96 */
97static int notrace bl_powerdown_finisher(unsigned long arg)
98{
99 /* MCPM works with HW CPU identifiers */
100 unsigned int mpidr = read_cpuid_mpidr();
101 unsigned int cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
102 unsigned int cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0);
103
104 mcpm_set_entry_vector(cpu, cluster, cpu_resume);
105
106 /*
107 * Residency value passed to mcpm_cpu_suspend back-end
108 * has to be given clear semantics. Set to 0 as a
109 * temporary value.
110 */
111 mcpm_cpu_suspend(0);
112
113 /* return value != 0 means failure */
114 return 1;
115}
116
117/**
118 * bl_enter_powerdown - Programs CPU to enter the specified state
119 * @dev: cpuidle device
120 * @drv: The target state to be programmed
121 * @idx: state index
122 *
123 * Called from the CPUidle framework to program the device to the
124 * specified target state selected by the governor.
125 */
126static int bl_enter_powerdown(struct cpuidle_device *dev,
127 struct cpuidle_driver *drv, int idx)
128{
129 cpu_pm_enter();
130
131 cpu_suspend(0, bl_powerdown_finisher);
132
133 /* signals the MCPM core that CPU is out of low power state */
134 mcpm_cpu_powered_up();
135
136 cpu_pm_exit();
137
138 return idx;
139}
140
141static int __init bl_idle_driver_init(struct cpuidle_driver *drv, int cpu_id)
142{
143 struct cpuinfo_arm *cpu_info;
144 struct cpumask *cpumask;
145 unsigned long cpuid;
146 int cpu;
147
148 cpumask = kzalloc(cpumask_size(), GFP_KERNEL);
149 if (!cpumask)
150 return -ENOMEM;
151
152 for_each_possible_cpu(cpu) {
153 cpu_info = &per_cpu(cpu_data, cpu);
154 cpuid = is_smp() ? cpu_info->cpuid : read_cpuid_id();
155
156 /* read cpu id part number */
157 if ((cpuid & 0xFFF0) == cpu_id)
158 cpumask_set_cpu(cpu, cpumask);
159 }
160
161 drv->cpumask = cpumask;
162
163 return 0;
164}
165
166static int __init bl_idle_init(void)
167{
168 int ret;
169
170 /*
171 * Initialize the driver just for a compliant set of machines
172 */
173 if (!of_machine_is_compatible("arm,vexpress,v2p-ca15_a7"))
174 return -ENODEV;
175 /*
176 * For now the differentiation between little and big cores
177 * is based on the part number. A7 cores are considered little
178 * cores, A15 are considered big cores. This distinction may
179 * evolve in the future with a more generic matching approach.
180 */
181 ret = bl_idle_driver_init(&bl_idle_little_driver,
182 ARM_CPU_PART_CORTEX_A7);
183 if (ret)
184 return ret;
185
186 ret = bl_idle_driver_init(&bl_idle_big_driver, ARM_CPU_PART_CORTEX_A15);
187 if (ret)
188 goto out_uninit_little;
189
190 ret = cpuidle_register(&bl_idle_little_driver, NULL);
191 if (ret)
192 goto out_uninit_big;
193
194 ret = cpuidle_register(&bl_idle_big_driver, NULL);
195 if (ret)
196 goto out_unregister_little;
197
198 return 0;
199
200out_unregister_little:
201 cpuidle_unregister(&bl_idle_little_driver);
202out_uninit_big:
203 kfree(bl_idle_big_driver.cpumask);
204out_uninit_little:
205 kfree(bl_idle_little_driver.cpumask);
206
207 return ret;
208}
209device_initcall(bl_idle_init);