Commit | Line | Data |
---|---|---|
e1689795 RL |
1 | /* |
2 | * Copyright 2012 Linaro Ltd. | |
3 | * | |
4 | * The code contained herein is licensed under the GNU General Public | |
5 | * License. You may obtain a copy of the GNU General Public License | |
6 | * Version 2 or later at the following locations: | |
7 | * | |
8 | * http://www.opensource.org/licenses/gpl-license.html | |
9 | * http://www.gnu.org/copyleft/gpl.html | |
10 | */ | |
11 | ||
12 | #include <linux/cpuidle.h> | |
449e056c DL |
13 | #include <linux/of.h> |
14 | #include <linux/of_device.h> | |
eeebc3bb | 15 | #include <asm/cpuidle.h> |
e1689795 | 16 | |
449e056c DL |
17 | extern struct of_cpuidle_method __cpuidle_method_of_table[]; |
18 | ||
19 | static const struct of_cpuidle_method __cpuidle_method_of_table_sentinel | |
20 | __used __section(__cpuidle_method_of_table_end); | |
21 | ||
22 | static struct cpuidle_ops cpuidle_ops[NR_CPUS]; | |
23 | ||
9a309d6f DL |
24 | /** |
25 | * arm_cpuidle_simple_enter() - a wrapper to cpu_do_idle() | |
26 | * @dev: not used | |
27 | * @drv: not used | |
28 | * @index: not used | |
29 | * | |
30 | * A trivial wrapper to allow the cpu_do_idle function to be assigned as a | |
31 | * cpuidle callback by matching the function signature. | |
32 | * | |
33 | * Returns the index passed as parameter | |
34 | */ | |
e1689795 RL |
35 | int arm_cpuidle_simple_enter(struct cpuidle_device *dev, |
36 | struct cpuidle_driver *drv, int index) | |
37 | { | |
38 | cpu_do_idle(); | |
39 | ||
40 | return index; | |
41 | } | |
449e056c | 42 | |
9a309d6f DL |
43 | /** |
44 | * arm_cpuidle_suspend() - function to enter low power idle states | |
45 | * @index: an integer used as an identifier for the low level PM callbacks | |
46 | * | |
47 | * This function calls the underlying arch specific low level PM code as | |
48 | * registered at the init time. | |
49 | * | |
50 | * Returns -EOPNOTSUPP if no suspend callback is defined, the result of the | |
51 | * callback otherwise. | |
52 | */ | |
449e056c DL |
53 | int arm_cpuidle_suspend(int index) |
54 | { | |
55 | int ret = -EOPNOTSUPP; | |
56 | int cpu = smp_processor_id(); | |
57 | ||
58 | if (cpuidle_ops[cpu].suspend) | |
f6419f24 | 59 | ret = cpuidle_ops[cpu].suspend(index); |
449e056c DL |
60 | |
61 | return ret; | |
62 | } | |
63 | ||
9a309d6f DL |
64 | /** |
65 | * arm_cpuidle_get_ops() - find a registered cpuidle_ops by name | |
66 | * @method: the method name | |
67 | * | |
68 | * Search in the __cpuidle_method_of_table array the cpuidle ops matching the | |
69 | * method name. | |
70 | * | |
71 | * Returns a struct cpuidle_ops pointer, NULL if not found. | |
72 | */ | |
4cfd5520 | 73 | static const struct cpuidle_ops *__init arm_cpuidle_get_ops(const char *method) |
449e056c DL |
74 | { |
75 | struct of_cpuidle_method *m = __cpuidle_method_of_table; | |
76 | ||
77 | for (; m->method; m++) | |
78 | if (!strcmp(m->method, method)) | |
79 | return m->ops; | |
80 | ||
81 | return NULL; | |
82 | } | |
83 | ||
9a309d6f DL |
84 | /** |
85 | * arm_cpuidle_read_ops() - Initialize the cpuidle ops with the device tree | |
86 | * @dn: a pointer to a struct device node corresponding to a cpu node | |
87 | * @cpu: the cpu identifier | |
88 | * | |
89 | * Get the method name defined in the 'enable-method' property, retrieve the | |
90 | * associated cpuidle_ops and do a struct copy. This copy is needed because all | |
4cfd5520 | 91 | * cpuidle_ops are tagged __initconst and will be unloaded after the init |
9a309d6f DL |
92 | * process. |
93 | * | |
94 | * Return 0 on sucess, -ENOENT if no 'enable-method' is defined, -EOPNOTSUPP if | |
95 | * no cpuidle_ops is registered for the 'enable-method'. | |
96 | */ | |
449e056c DL |
97 | static int __init arm_cpuidle_read_ops(struct device_node *dn, int cpu) |
98 | { | |
99 | const char *enable_method; | |
4cfd5520 | 100 | const struct cpuidle_ops *ops; |
449e056c DL |
101 | |
102 | enable_method = of_get_property(dn, "enable-method", NULL); | |
103 | if (!enable_method) | |
104 | return -ENOENT; | |
105 | ||
106 | ops = arm_cpuidle_get_ops(enable_method); | |
107 | if (!ops) { | |
108 | pr_warn("%s: unsupported enable-method property: %s\n", | |
109 | dn->full_name, enable_method); | |
110 | return -EOPNOTSUPP; | |
111 | } | |
112 | ||
113 | cpuidle_ops[cpu] = *ops; /* structure copy */ | |
114 | ||
115 | pr_notice("cpuidle: enable-method property '%s'" | |
116 | " found operations\n", enable_method); | |
117 | ||
118 | return 0; | |
119 | } | |
120 | ||
9a309d6f DL |
121 | /** |
122 | * arm_cpuidle_init() - Initialize cpuidle_ops for a specific cpu | |
123 | * @cpu: the cpu to be initialized | |
124 | * | |
125 | * Initialize the cpuidle ops with the device for the cpu and then call | |
126 | * the cpu's idle initialization callback. This may fail if the underlying HW | |
127 | * is not operational. | |
128 | * | |
129 | * Returns: | |
130 | * 0 on success, | |
131 | * -ENODEV if it fails to find the cpu node in the device tree, | |
132 | * -EOPNOTSUPP if it does not find a registered cpuidle_ops for this cpu, | |
133 | * -ENOENT if it fails to find an 'enable-method' property, | |
134 | * -ENXIO if the HW reports a failure or a misconfiguration, | |
135 | * -ENOMEM if the HW report an memory allocation failure | |
136 | */ | |
449e056c DL |
137 | int __init arm_cpuidle_init(int cpu) |
138 | { | |
139 | struct device_node *cpu_node = of_cpu_device_node_get(cpu); | |
140 | int ret; | |
141 | ||
142 | if (!cpu_node) | |
143 | return -ENODEV; | |
144 | ||
145 | ret = arm_cpuidle_read_ops(cpu_node, cpu); | |
146 | if (!ret && cpuidle_ops[cpu].init) | |
147 | ret = cpuidle_ops[cpu].init(cpu_node, cpu); | |
148 | ||
149 | of_node_put(cpu_node); | |
150 | ||
151 | return ret; | |
152 | } |