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 | ||
7619751f | 22 | static struct cpuidle_ops cpuidle_ops[NR_CPUS] __ro_after_init; |
449e056c | 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 | * | |
c3fbbf93 | 50 | * Returns the result of the suspend callback. |
9a309d6f | 51 | */ |
449e056c DL |
52 | int arm_cpuidle_suspend(int index) |
53 | { | |
449e056c DL |
54 | int cpu = smp_processor_id(); |
55 | ||
c3fbbf93 | 56 | return cpuidle_ops[cpu].suspend(index); |
449e056c DL |
57 | } |
58 | ||
9a309d6f DL |
59 | /** |
60 | * arm_cpuidle_get_ops() - find a registered cpuidle_ops by name | |
61 | * @method: the method name | |
62 | * | |
63 | * Search in the __cpuidle_method_of_table array the cpuidle ops matching the | |
64 | * method name. | |
65 | * | |
66 | * Returns a struct cpuidle_ops pointer, NULL if not found. | |
67 | */ | |
4cfd5520 | 68 | static const struct cpuidle_ops *__init arm_cpuidle_get_ops(const char *method) |
449e056c DL |
69 | { |
70 | struct of_cpuidle_method *m = __cpuidle_method_of_table; | |
71 | ||
72 | for (; m->method; m++) | |
73 | if (!strcmp(m->method, method)) | |
74 | return m->ops; | |
75 | ||
76 | return NULL; | |
77 | } | |
78 | ||
9a309d6f DL |
79 | /** |
80 | * arm_cpuidle_read_ops() - Initialize the cpuidle ops with the device tree | |
81 | * @dn: a pointer to a struct device node corresponding to a cpu node | |
82 | * @cpu: the cpu identifier | |
83 | * | |
84 | * Get the method name defined in the 'enable-method' property, retrieve the | |
85 | * associated cpuidle_ops and do a struct copy. This copy is needed because all | |
4cfd5520 | 86 | * cpuidle_ops are tagged __initconst and will be unloaded after the init |
9a309d6f DL |
87 | * process. |
88 | * | |
89 | * Return 0 on sucess, -ENOENT if no 'enable-method' is defined, -EOPNOTSUPP if | |
c3fbbf93 JZ |
90 | * no cpuidle_ops is registered for the 'enable-method', or if either init or |
91 | * suspend callback isn't defined. | |
9a309d6f | 92 | */ |
449e056c DL |
93 | static int __init arm_cpuidle_read_ops(struct device_node *dn, int cpu) |
94 | { | |
95 | const char *enable_method; | |
4cfd5520 | 96 | const struct cpuidle_ops *ops; |
449e056c DL |
97 | |
98 | enable_method = of_get_property(dn, "enable-method", NULL); | |
99 | if (!enable_method) | |
100 | return -ENOENT; | |
101 | ||
102 | ops = arm_cpuidle_get_ops(enable_method); | |
103 | if (!ops) { | |
104 | pr_warn("%s: unsupported enable-method property: %s\n", | |
105 | dn->full_name, enable_method); | |
106 | return -EOPNOTSUPP; | |
107 | } | |
108 | ||
c3fbbf93 JZ |
109 | if (!ops->init || !ops->suspend) { |
110 | pr_warn("cpuidle_ops '%s': no init or suspend callback\n", | |
f222a769 JZ |
111 | enable_method); |
112 | return -EOPNOTSUPP; | |
113 | } | |
114 | ||
449e056c DL |
115 | cpuidle_ops[cpu] = *ops; /* structure copy */ |
116 | ||
117 | pr_notice("cpuidle: enable-method property '%s'" | |
118 | " found operations\n", enable_method); | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
9a309d6f DL |
123 | /** |
124 | * arm_cpuidle_init() - Initialize cpuidle_ops for a specific cpu | |
125 | * @cpu: the cpu to be initialized | |
126 | * | |
127 | * Initialize the cpuidle ops with the device for the cpu and then call | |
128 | * the cpu's idle initialization callback. This may fail if the underlying HW | |
129 | * is not operational. | |
130 | * | |
131 | * Returns: | |
132 | * 0 on success, | |
133 | * -ENODEV if it fails to find the cpu node in the device tree, | |
f222a769 JZ |
134 | * -EOPNOTSUPP if it does not find a registered and valid cpuidle_ops for |
135 | * this cpu, | |
9a309d6f DL |
136 | * -ENOENT if it fails to find an 'enable-method' property, |
137 | * -ENXIO if the HW reports a failure or a misconfiguration, | |
138 | * -ENOMEM if the HW report an memory allocation failure | |
139 | */ | |
449e056c DL |
140 | int __init arm_cpuidle_init(int cpu) |
141 | { | |
142 | struct device_node *cpu_node = of_cpu_device_node_get(cpu); | |
143 | int ret; | |
144 | ||
145 | if (!cpu_node) | |
146 | return -ENODEV; | |
147 | ||
148 | ret = arm_cpuidle_read_ops(cpu_node, cpu); | |
f222a769 | 149 | if (!ret) |
449e056c DL |
150 | ret = cpuidle_ops[cpu].init(cpu_node, cpu); |
151 | ||
152 | of_node_put(cpu_node); | |
153 | ||
154 | return ret; | |
155 | } |