aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLorenzo Pieralisi <lorenzo.pieralisi@arm.com>2014-02-14 09:28:39 -0500
committerDaniel Lezcano <daniel.lezcano@linaro.org>2014-09-25 04:52:20 -0400
commit9f14da345599c14b329cf5ac9499ad322056dd32 (patch)
treebe20b582f94e247ead39d5b385662d9f78ef3a78
parent6baf6ee534c56e754ca854176be0073bc2c9e6a4 (diff)
drivers: cpuidle: implement DT based idle states infrastructure
On most common ARM systems, the low-power states a CPU can be put into are not discoverable in HW and require device tree bindings to describe power down suspend operations and idle states parameters. In order to enable DT based idle states and configure idle drivers, this patch implements the bulk infrastructure required to parse the device tree idle states bindings and initialize the corresponding CPUidle driver states data. The parsing API accepts a start index that defines the first idle state that should be initialized by the parsing code in order to give new and legacy driver flexibility over which states should be parsed using the new DT mechanism. The idle states node(s) is obtained from the phandle list of the first cpu in the driver cpumask; the kernel checks that the idle state node phandle is the same for all CPUs in the driver cpumask before declaring the idle state as valid and start parsing its content. The idle state enter function pointer is initialized through DT match structures passed in by the CPUidle driver, so that ARM legacy code can cope with platform specific idle entry method based on compatible string matching and the code used to initialize the enter function pointer can be moved to the DT generic layer. Acked-by: Catalin Marinas <catalin.marinas@arm.com> Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
-rw-r--r--drivers/cpuidle/Kconfig3
-rw-r--r--drivers/cpuidle/Makefile1
-rw-r--r--drivers/cpuidle/dt_idle_states.c213
-rw-r--r--drivers/cpuidle/dt_idle_states.h7
4 files changed, 224 insertions, 0 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig
index 32748c36c477..8deb934c6356 100644
--- a/drivers/cpuidle/Kconfig
+++ b/drivers/cpuidle/Kconfig
@@ -25,6 +25,9 @@ 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"
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index 11edb31c55e9..002b65336ba9 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
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