summaryrefslogtreecommitdiffstats
path: root/drivers/cpuidle
diff options
context:
space:
mode:
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>2014-09-25 16:18:45 -0400
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2014-09-25 16:18:45 -0400
commiteeb1aa5f78caf8d5ec1ef651c09bde34370321d8 (patch)
treecc641b7dad3bff3bf1527a99c5d88d1f23d0a563 /drivers/cpuidle
parent9133664097dd4aca0bed4882a86f0bfccbf07e53 (diff)
parentd2e5c871ed8a250f7ee1fe34dd52ed5757363fba (diff)
Merge branch 'cpuidle/3.18' of https://git.linaro.org/people/daniel.lezcano/linux into pm-cpuidle
Pull ARM cpuidle changes for v3.18 from Daniel Lezcano: "this pull request contains the following changes: * Lorenzo Pieralisi implemented a framework to initialize the ARM cpuidle drivers with the DT. As an example, it provided a couple of drivers using it: arm64 and big little. The former one is a new driver while the latter is a change. There was also a patch for Exynos allowing to use this framework but as it depends on a change in Samsung's tree, I postponed this patch until the change is visible after the merge. The set of changes depends on some other changes made in the ARM64 tree, for this reason a shared branch is used. This is why there is a merge from arm64 in my pull request. I believe we already used this procedure. * Kevin Hilman added the compatible string for the exynos 5800 in the DT" * 'cpuidle/3.18' of https://git.linaro.org/people/daniel.lezcano/linux: drivers: cpuidle: initialize big.LITTLE driver through DT drivers: cpuidle: CPU idle ARM64 driver drivers: cpuidle: implement DT based idle states infrastructure cpuidle: big.LITTLE: add Exynos5800 compatible string arm64: add PSCI CPU_SUSPEND based cpu_suspend support arm64: kernel: introduce cpu_init_idle CPU operation arm64: kernel: refactor the CPU suspend API for retention states Documentation: arm: define DT idle states bindings
Diffstat (limited to 'drivers/cpuidle')
-rw-r--r--drivers/cpuidle/Kconfig8
-rw-r--r--drivers/cpuidle/Kconfig.arm1
-rw-r--r--drivers/cpuidle/Kconfig.arm6414
-rw-r--r--drivers/cpuidle/Makefile5
-rw-r--r--drivers/cpuidle/cpuidle-arm64.c133
-rw-r--r--drivers/cpuidle/cpuidle-big_little.c20
-rw-r--r--drivers/cpuidle/dt_idle_states.c213
-rw-r--r--drivers/cpuidle/dt_idle_states.h7
8 files changed, 401 insertions, 0 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig
index 32748c36c477..c5029c1209b4 100644
--- a/drivers/cpuidle/Kconfig
+++ b/drivers/cpuidle/Kconfig
@@ -25,11 +25,19 @@ config CPU_IDLE_GOV_MENU
25 bool "Menu governor (for tickless system)" 25 bool "Menu governor (for tickless system)"
26 default y 26 default y
27 27
28config DT_IDLE_STATES
29 bool
30
28menu "ARM CPU Idle Drivers" 31menu "ARM CPU Idle Drivers"
29depends on ARM 32depends on ARM
30source "drivers/cpuidle/Kconfig.arm" 33source "drivers/cpuidle/Kconfig.arm"
31endmenu 34endmenu
32 35
36menu "ARM64 CPU Idle Drivers"
37depends on ARM64
38source "drivers/cpuidle/Kconfig.arm64"
39endmenu
40
33menu "MIPS CPU Idle Drivers" 41menu "MIPS CPU Idle Drivers"
34depends on MIPS 42depends on MIPS
35source "drivers/cpuidle/Kconfig.mips" 43source "drivers/cpuidle/Kconfig.mips"
diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm
index 38cff69ffe06..e339c7f2c2b7 100644
--- a/drivers/cpuidle/Kconfig.arm
+++ b/drivers/cpuidle/Kconfig.arm
@@ -7,6 +7,7 @@ config ARM_BIG_LITTLE_CPUIDLE
7 depends on MCPM 7 depends on MCPM
8 select ARM_CPU_SUSPEND 8 select ARM_CPU_SUSPEND
9 select CPU_IDLE_MULTIPLE_DRIVERS 9 select CPU_IDLE_MULTIPLE_DRIVERS
10 select DT_IDLE_STATES
10 help 11 help
11 Select this option to enable CPU idle driver for big.LITTLE based 12 Select this option to enable CPU idle driver for big.LITTLE based
12 ARM systems. Driver manages CPUs coordination through MCPM and 13 ARM systems. Driver manages CPUs coordination through MCPM and
diff --git a/drivers/cpuidle/Kconfig.arm64 b/drivers/cpuidle/Kconfig.arm64
new file mode 100644
index 000000000000..d0a08ed1b2ee
--- /dev/null
+++ b/drivers/cpuidle/Kconfig.arm64
@@ -0,0 +1,14 @@
1#
2# ARM64 CPU Idle drivers
3#
4
5config ARM64_CPUIDLE
6 bool "Generic ARM64 CPU idle Driver"
7 select ARM64_CPU_SUSPEND
8 select DT_IDLE_STATES
9 help
10 Select this to enable generic cpuidle driver for ARM64.
11 It provides a generic idle driver whose idle states are configured
12 at run-time through DT nodes. The CPUidle suspend backend is
13 initialized by calling the CPU operations init idle hook
14 provided by architecture code.
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index 11edb31c55e9..4d177b916f75 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -4,6 +4,7 @@
4 4
5obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ 5obj-y += cpuidle.o driver.o governor.o sysfs.o governors/
6obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o 6obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o
7obj-$(CONFIG_DT_IDLE_STATES) += dt_idle_states.o
7 8
8################################################################################## 9##################################################################################
9# ARM SoC drivers 10# ARM SoC drivers
@@ -22,6 +23,10 @@ obj-$(CONFIG_ARM_EXYNOS_CPUIDLE) += cpuidle-exynos.o
22obj-$(CONFIG_MIPS_CPS_CPUIDLE) += cpuidle-cps.o 23obj-$(CONFIG_MIPS_CPS_CPUIDLE) += cpuidle-cps.o
23 24
24############################################################################### 25###############################################################################
26# ARM64 drivers
27obj-$(CONFIG_ARM64_CPUIDLE) += cpuidle-arm64.o
28
29###############################################################################
25# POWERPC drivers 30# POWERPC drivers
26obj-$(CONFIG_PSERIES_CPUIDLE) += cpuidle-pseries.o 31obj-$(CONFIG_PSERIES_CPUIDLE) += cpuidle-pseries.o
27obj-$(CONFIG_POWERNV_CPUIDLE) += cpuidle-powernv.o 32obj-$(CONFIG_POWERNV_CPUIDLE) += cpuidle-powernv.o
diff --git a/drivers/cpuidle/cpuidle-arm64.c b/drivers/cpuidle/cpuidle-arm64.c
new file mode 100644
index 000000000000..50997ea942fc
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-arm64.c
@@ -0,0 +1,133 @@
1/*
2 * ARM64 generic CPU idle driver.
3 *
4 * Copyright (C) 2014 ARM Ltd.
5 * Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#define pr_fmt(fmt) "CPUidle arm64: " fmt
13
14#include <linux/cpuidle.h>
15#include <linux/cpumask.h>
16#include <linux/cpu_pm.h>
17#include <linux/kernel.h>
18#include <linux/module.h>
19#include <linux/of.h>
20
21#include <asm/cpuidle.h>
22#include <asm/suspend.h>
23
24#include "dt_idle_states.h"
25
26/*
27 * arm64_enter_idle_state - Programs CPU to enter the specified state
28 *
29 * dev: cpuidle device
30 * drv: cpuidle driver
31 * idx: state index
32 *
33 * Called from the CPUidle framework to program the device to the
34 * specified target state selected by the governor.
35 */
36static int arm64_enter_idle_state(struct cpuidle_device *dev,
37 struct cpuidle_driver *drv, int idx)
38{
39 int ret;
40
41 if (!idx) {
42 cpu_do_idle();
43 return idx;
44 }
45
46 ret = cpu_pm_enter();
47 if (!ret) {
48 /*
49 * Pass idle state index to cpu_suspend which in turn will
50 * call the CPU ops suspend protocol with idle index as a
51 * parameter.
52 */
53 ret = cpu_suspend(idx);
54
55 cpu_pm_exit();
56 }
57
58 return ret ? -1 : idx;
59}
60
61static struct cpuidle_driver arm64_idle_driver = {
62 .name = "arm64_idle",
63 .owner = THIS_MODULE,
64 /*
65 * State at index 0 is standby wfi and considered standard
66 * on all ARM platforms. If in some platforms simple wfi
67 * can't be used as "state 0", DT bindings must be implemented
68 * to work around this issue and allow installing a special
69 * handler for idle state index 0.
70 */
71 .states[0] = {
72 .enter = arm64_enter_idle_state,
73 .exit_latency = 1,
74 .target_residency = 1,
75 .power_usage = UINT_MAX,
76 .flags = CPUIDLE_FLAG_TIME_VALID,
77 .name = "WFI",
78 .desc = "ARM64 WFI",
79 }
80};
81
82static const struct of_device_id arm64_idle_state_match[] __initconst = {
83 { .compatible = "arm,idle-state",
84 .data = arm64_enter_idle_state },
85 { },
86};
87
88/*
89 * arm64_idle_init
90 *
91 * Registers the arm64 specific cpuidle driver with the cpuidle
92 * framework. It relies on core code to parse the idle states
93 * and initialize them using driver data structures accordingly.
94 */
95static int __init arm64_idle_init(void)
96{
97 int cpu, ret;
98 struct cpuidle_driver *drv = &arm64_idle_driver;
99
100 /*
101 * Initialize idle states data, starting at index 1.
102 * This driver is DT only, if no DT idle states are detected (ret == 0)
103 * let the driver initialization fail accordingly since there is no
104 * reason to initialize the idle driver if only wfi is supported.
105 */
106 ret = dt_init_idle_driver(drv, arm64_idle_state_match, 1);
107 if (ret <= 0) {
108 if (ret)
109 pr_err("failed to initialize idle states\n");
110 return ret ? : -ENODEV;
111 }
112
113 /*
114 * Call arch CPU operations in order to initialize
115 * idle states suspend back-end specific data
116 */
117 for_each_possible_cpu(cpu) {
118 ret = cpu_init_idle(cpu);
119 if (ret) {
120 pr_err("CPU %d failed to init idle CPU ops\n", cpu);
121 return ret;
122 }
123 }
124
125 ret = cpuidle_register(drv, NULL);
126 if (ret) {
127 pr_err("failed to register cpuidle driver\n");
128 return ret;
129 }
130
131 return 0;
132}
133device_initcall(arm64_idle_init);
diff --git a/drivers/cpuidle/cpuidle-big_little.c b/drivers/cpuidle/cpuidle-big_little.c
index ef94c3b81f18..fbc00a1d3c48 100644
--- a/drivers/cpuidle/cpuidle-big_little.c
+++ b/drivers/cpuidle/cpuidle-big_little.c
@@ -24,6 +24,8 @@
24#include <asm/smp_plat.h> 24#include <asm/smp_plat.h>
25#include <asm/suspend.h> 25#include <asm/suspend.h>
26 26
27#include "dt_idle_states.h"
28
27static int bl_enter_powerdown(struct cpuidle_device *dev, 29static int bl_enter_powerdown(struct cpuidle_device *dev,
28 struct cpuidle_driver *drv, int idx); 30 struct cpuidle_driver *drv, int idx);
29 31
@@ -73,6 +75,12 @@ static struct cpuidle_driver bl_idle_little_driver = {
73 .state_count = 2, 75 .state_count = 2,
74}; 76};
75 77
78static const struct of_device_id bl_idle_state_match[] __initconst = {
79 { .compatible = "arm,idle-state",
80 .data = bl_enter_powerdown },
81 { },
82};
83
76static struct cpuidle_driver bl_idle_big_driver = { 84static struct cpuidle_driver bl_idle_big_driver = {
77 .name = "big_idle", 85 .name = "big_idle",
78 .owner = THIS_MODULE, 86 .owner = THIS_MODULE,
@@ -159,6 +167,7 @@ static int __init bl_idle_driver_init(struct cpuidle_driver *drv, int part_id)
159static const struct of_device_id compatible_machine_match[] = { 167static const struct of_device_id compatible_machine_match[] = {
160 { .compatible = "arm,vexpress,v2p-ca15_a7" }, 168 { .compatible = "arm,vexpress,v2p-ca15_a7" },
161 { .compatible = "samsung,exynos5420" }, 169 { .compatible = "samsung,exynos5420" },
170 { .compatible = "samsung,exynos5800" },
162 {}, 171 {},
163}; 172};
164 173
@@ -190,6 +199,17 @@ static int __init bl_idle_init(void)
190 if (ret) 199 if (ret)
191 goto out_uninit_little; 200 goto out_uninit_little;
192 201
202 /* Start at index 1, index 0 standard WFI */
203 ret = dt_init_idle_driver(&bl_idle_big_driver, bl_idle_state_match, 1);
204 if (ret < 0)
205 goto out_uninit_big;
206
207 /* Start at index 1, index 0 standard WFI */
208 ret = dt_init_idle_driver(&bl_idle_little_driver,
209 bl_idle_state_match, 1);
210 if (ret < 0)
211 goto out_uninit_big;
212
193 ret = cpuidle_register(&bl_idle_little_driver, NULL); 213 ret = cpuidle_register(&bl_idle_little_driver, NULL);
194 if (ret) 214 if (ret)
195 goto out_uninit_big; 215 goto out_uninit_big;
diff --git a/drivers/cpuidle/dt_idle_states.c b/drivers/cpuidle/dt_idle_states.c
new file mode 100644
index 000000000000..52f4d11bbf3f
--- /dev/null
+++ b/drivers/cpuidle/dt_idle_states.c
@@ -0,0 +1,213 @@
1/*
2 * DT idle states parsing code.
3 *
4 * Copyright (C) 2014 ARM Ltd.
5 * Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#define pr_fmt(fmt) "DT idle-states: " fmt
13
14#include <linux/cpuidle.h>
15#include <linux/cpumask.h>
16#include <linux/errno.h>
17#include <linux/kernel.h>
18#include <linux/module.h>
19#include <linux/of.h>
20#include <linux/of_device.h>
21
22#include "dt_idle_states.h"
23
24static int init_state_node(struct cpuidle_state *idle_state,
25 const struct of_device_id *matches,
26 struct device_node *state_node)
27{
28 int err;
29 const struct of_device_id *match_id;
30
31 match_id = of_match_node(matches, state_node);
32 if (!match_id)
33 return -ENODEV;
34 /*
35 * CPUidle drivers are expected to initialize the const void *data
36 * pointer of the passed in struct of_device_id array to the idle
37 * state enter function.
38 */
39 idle_state->enter = match_id->data;
40
41 err = of_property_read_u32(state_node, "wakeup-latency-us",
42 &idle_state->exit_latency);
43 if (err) {
44 u32 entry_latency, exit_latency;
45
46 err = of_property_read_u32(state_node, "entry-latency-us",
47 &entry_latency);
48 if (err) {
49 pr_debug(" * %s missing entry-latency-us property\n",
50 state_node->full_name);
51 return -EINVAL;
52 }
53
54 err = of_property_read_u32(state_node, "exit-latency-us",
55 &exit_latency);
56 if (err) {
57 pr_debug(" * %s missing exit-latency-us property\n",
58 state_node->full_name);
59 return -EINVAL;
60 }
61 /*
62 * If wakeup-latency-us is missing, default to entry+exit
63 * latencies as defined in idle states bindings
64 */
65 idle_state->exit_latency = entry_latency + exit_latency;
66 }
67
68 err = of_property_read_u32(state_node, "min-residency-us",
69 &idle_state->target_residency);
70 if (err) {
71 pr_debug(" * %s missing min-residency-us property\n",
72 state_node->full_name);
73 return -EINVAL;
74 }
75
76 idle_state->flags = CPUIDLE_FLAG_TIME_VALID;
77 if (of_property_read_bool(state_node, "local-timer-stop"))
78 idle_state->flags |= CPUIDLE_FLAG_TIMER_STOP;
79 /*
80 * TODO:
81 * replace with kstrdup and pointer assignment when name
82 * and desc become string pointers
83 */
84 strncpy(idle_state->name, state_node->name, CPUIDLE_NAME_LEN - 1);
85 strncpy(idle_state->desc, state_node->name, CPUIDLE_DESC_LEN - 1);
86 return 0;
87}
88
89/*
90 * Check that the idle state is uniform across all CPUs in the CPUidle driver
91 * cpumask
92 */
93static bool idle_state_valid(struct device_node *state_node, unsigned int idx,
94 const cpumask_t *cpumask)
95{
96 int cpu;
97 struct device_node *cpu_node, *curr_state_node;
98 bool valid = true;
99
100 /*
101 * Compare idle state phandles for index idx on all CPUs in the
102 * CPUidle driver cpumask. Start from next logical cpu following
103 * cpumask_first(cpumask) since that's the CPU state_node was
104 * retrieved from. If a mismatch is found bail out straight
105 * away since we certainly hit a firmware misconfiguration.
106 */
107 for (cpu = cpumask_next(cpumask_first(cpumask), cpumask);
108 cpu < nr_cpu_ids; cpu = cpumask_next(cpu, cpumask)) {
109 cpu_node = of_cpu_device_node_get(cpu);
110 curr_state_node = of_parse_phandle(cpu_node, "cpu-idle-states",
111 idx);
112 if (state_node != curr_state_node)
113 valid = false;
114
115 of_node_put(curr_state_node);
116 of_node_put(cpu_node);
117 if (!valid)
118 break;
119 }
120
121 return valid;
122}
123
124/**
125 * dt_init_idle_driver() - Parse the DT idle states and initialize the
126 * idle driver states array
127 * @drv: Pointer to CPU idle driver to be initialized
128 * @matches: Array of of_device_id match structures to search in for
129 * compatible idle state nodes. The data pointer for each valid
130 * struct of_device_id entry in the matches array must point to
131 * a function with the following signature, that corresponds to
132 * the CPUidle state enter function signature:
133 *
134 * int (*)(struct cpuidle_device *dev,
135 * struct cpuidle_driver *drv,
136 * int index);
137 *
138 * @start_idx: First idle state index to be initialized
139 *
140 * If DT idle states are detected and are valid the state count and states
141 * array entries in the cpuidle driver are initialized accordingly starting
142 * from index start_idx.
143 *
144 * Return: number of valid DT idle states parsed, <0 on failure
145 */
146int dt_init_idle_driver(struct cpuidle_driver *drv,
147 const struct of_device_id *matches,
148 unsigned int start_idx)
149{
150 struct cpuidle_state *idle_state;
151 struct device_node *state_node, *cpu_node;
152 int i, err = 0;
153 const cpumask_t *cpumask;
154 unsigned int state_idx = start_idx;
155
156 if (state_idx >= CPUIDLE_STATE_MAX)
157 return -EINVAL;
158 /*
159 * We get the idle states for the first logical cpu in the
160 * driver mask (or cpu_possible_mask if the driver cpumask is not set)
161 * and we check through idle_state_valid() if they are uniform
162 * across CPUs, otherwise we hit a firmware misconfiguration.
163 */
164 cpumask = drv->cpumask ? : cpu_possible_mask;
165 cpu_node = of_cpu_device_node_get(cpumask_first(cpumask));
166
167 for (i = 0; ; i++) {
168 state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i);
169 if (!state_node)
170 break;
171
172 if (!idle_state_valid(state_node, i, cpumask)) {
173 pr_warn("%s idle state not valid, bailing out\n",
174 state_node->full_name);
175 err = -EINVAL;
176 break;
177 }
178
179 if (state_idx == CPUIDLE_STATE_MAX) {
180 pr_warn("State index reached static CPU idle driver states array size\n");
181 break;
182 }
183
184 idle_state = &drv->states[state_idx++];
185 err = init_state_node(idle_state, matches, state_node);
186 if (err) {
187 pr_err("Parsing idle state node %s failed with err %d\n",
188 state_node->full_name, err);
189 err = -EINVAL;
190 break;
191 }
192 of_node_put(state_node);
193 }
194
195 of_node_put(state_node);
196 of_node_put(cpu_node);
197 if (err)
198 return err;
199 /*
200 * Update the driver state count only if some valid DT idle states
201 * were detected
202 */
203 if (i)
204 drv->state_count = state_idx;
205
206 /*
207 * Return the number of present and valid DT idle states, which can
208 * also be 0 on platforms with missing DT idle states or legacy DT
209 * configuration predating the DT idle states bindings.
210 */
211 return i;
212}
213EXPORT_SYMBOL_GPL(dt_init_idle_driver);
diff --git a/drivers/cpuidle/dt_idle_states.h b/drivers/cpuidle/dt_idle_states.h
new file mode 100644
index 000000000000..4818134bc65b
--- /dev/null
+++ b/drivers/cpuidle/dt_idle_states.h
@@ -0,0 +1,7 @@
1#ifndef __DT_IDLE_STATES
2#define __DT_IDLE_STATES
3
4int dt_init_idle_driver(struct cpuidle_driver *drv,
5 const struct of_device_id *matches,
6 unsigned int start_idx);
7#endif