Merge branch 'master' of master.kernel.org:/pub/scm/linux/kernel/git/davem/net-2.6
[pandora-kernel.git] / arch / powerpc / platforms / pseries / pseries_energy.c
1 /*
2  * POWER platform energy management driver
3  * Copyright (C) 2010 IBM Corporation
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * version 2 as published by the Free Software Foundation.
8  *
9  * This pseries platform device driver provides access to
10  * platform energy management capabilities.
11  */
12
13 #include <linux/module.h>
14 #include <linux/types.h>
15 #include <linux/errno.h>
16 #include <linux/init.h>
17 #include <linux/seq_file.h>
18 #include <linux/sysdev.h>
19 #include <linux/cpu.h>
20 #include <linux/of.h>
21 #include <asm/cputhreads.h>
22 #include <asm/page.h>
23 #include <asm/hvcall.h>
24
25
26 #define MODULE_VERS "1.0"
27 #define MODULE_NAME "pseries_energy"
28
29 /* Driver flags */
30
31 static int sysfs_entries;
32
33 /* Helper routines */
34
35 /*
36  * Routine to detect firmware support for hcall
37  * return 1 if H_BEST_ENERGY is supported
38  * else return 0
39  */
40
41 static int check_for_h_best_energy(void)
42 {
43         struct device_node *rtas = NULL;
44         const char *hypertas, *s;
45         int length;
46         int rc = 0;
47
48         rtas = of_find_node_by_path("/rtas");
49         if (!rtas)
50                 return 0;
51
52         hypertas = of_get_property(rtas, "ibm,hypertas-functions", &length);
53         if (!hypertas) {
54                 of_node_put(rtas);
55                 return 0;
56         }
57
58         /* hypertas will have list of strings with hcall names */
59         for (s = hypertas; s < hypertas + length; s += strlen(s) + 1) {
60                 if (!strncmp("hcall-best-energy-1", s, 19)) {
61                         rc = 1; /* Found the string */
62                         break;
63                 }
64         }
65         of_node_put(rtas);
66         return rc;
67 }
68
69 /* Helper Routines to convert between drc_index to cpu numbers */
70
71 static u32 cpu_to_drc_index(int cpu)
72 {
73         struct device_node *dn = NULL;
74         const int *indexes;
75         int i;
76         int rc = 1;
77         u32 ret = 0;
78
79         dn = of_find_node_by_path("/cpus");
80         if (dn == NULL)
81                 goto err;
82         indexes = of_get_property(dn, "ibm,drc-indexes", NULL);
83         if (indexes == NULL)
84                 goto err_of_node_put;
85         /* Convert logical cpu number to core number */
86         i = cpu_core_index_of_thread(cpu);
87         /*
88          * The first element indexes[0] is the number of drc_indexes
89          * returned in the list.  Hence i+1 will get the drc_index
90          * corresponding to core number i.
91          */
92         WARN_ON(i > indexes[0]);
93         ret = indexes[i + 1];
94         rc = 0;
95
96 err_of_node_put:
97         of_node_put(dn);
98 err:
99         if (rc)
100                 printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu);
101         return ret;
102 }
103
104 static int drc_index_to_cpu(u32 drc_index)
105 {
106         struct device_node *dn = NULL;
107         const int *indexes;
108         int i, cpu = 0;
109         int rc = 1;
110
111         dn = of_find_node_by_path("/cpus");
112         if (dn == NULL)
113                 goto err;
114         indexes = of_get_property(dn, "ibm,drc-indexes", NULL);
115         if (indexes == NULL)
116                 goto err_of_node_put;
117         /*
118          * First element in the array is the number of drc_indexes
119          * returned.  Search through the list to find the matching
120          * drc_index and get the core number
121          */
122         for (i = 0; i < indexes[0]; i++) {
123                 if (indexes[i + 1] == drc_index)
124                         break;
125         }
126         /* Convert core number to logical cpu number */
127         cpu = cpu_first_thread_of_core(i);
128         rc = 0;
129
130 err_of_node_put:
131         of_node_put(dn);
132 err:
133         if (rc)
134                 printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index);
135         return cpu;
136 }
137
138 /*
139  * pseries hypervisor call H_BEST_ENERGY provides hints to OS on
140  * preferred logical cpus to activate or deactivate for optimized
141  * energy consumption.
142  */
143
144 #define FLAGS_MODE1     0x004E200000080E01
145 #define FLAGS_MODE2     0x004E200000080401
146 #define FLAGS_ACTIVATE  0x100
147
148 static ssize_t get_best_energy_list(char *page, int activate)
149 {
150         int rc, cnt, i, cpu;
151         unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];
152         unsigned long flags = 0;
153         u32 *buf_page;
154         char *s = page;
155
156         buf_page = (u32 *) get_zeroed_page(GFP_KERNEL);
157         if (!buf_page)
158                 return -ENOMEM;
159
160         flags = FLAGS_MODE1;
161         if (activate)
162                 flags |= FLAGS_ACTIVATE;
163
164         rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page),
165                                 0, 0, 0, 0, 0, 0);
166         if (rc != H_SUCCESS) {
167                 free_page((unsigned long) buf_page);
168                 return -EINVAL;
169         }
170
171         cnt = retbuf[0];
172         for (i = 0; i < cnt; i++) {
173                 cpu = drc_index_to_cpu(buf_page[2*i+1]);
174                 if ((cpu_online(cpu) && !activate) ||
175                     (!cpu_online(cpu) && activate))
176                         s += sprintf(s, "%d,", cpu);
177         }
178         if (s > page) { /* Something to show */
179                 s--; /* Suppress last comma */
180                 s += sprintf(s, "\n");
181         }
182
183         free_page((unsigned long) buf_page);
184         return s-page;
185 }
186
187 static ssize_t get_best_energy_data(struct sys_device *dev,
188                                         char *page, int activate)
189 {
190         int rc;
191         unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];
192         unsigned long flags = 0;
193
194         flags = FLAGS_MODE2;
195         if (activate)
196                 flags |= FLAGS_ACTIVATE;
197
198         rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags,
199                                 cpu_to_drc_index(dev->id),
200                                 0, 0, 0, 0, 0, 0, 0);
201
202         if (rc != H_SUCCESS)
203                 return -EINVAL;
204
205         return sprintf(page, "%lu\n", retbuf[1] >> 32);
206 }
207
208 /* Wrapper functions */
209
210 static ssize_t cpu_activate_hint_list_show(struct sysdev_class *class,
211                         struct sysdev_class_attribute *attr, char *page)
212 {
213         return get_best_energy_list(page, 1);
214 }
215
216 static ssize_t cpu_deactivate_hint_list_show(struct sysdev_class *class,
217                         struct sysdev_class_attribute *attr, char *page)
218 {
219         return get_best_energy_list(page, 0);
220 }
221
222 static ssize_t percpu_activate_hint_show(struct sys_device *dev,
223                         struct sysdev_attribute *attr, char *page)
224 {
225         return get_best_energy_data(dev, page, 1);
226 }
227
228 static ssize_t percpu_deactivate_hint_show(struct sys_device *dev,
229                         struct sysdev_attribute *attr, char *page)
230 {
231         return get_best_energy_data(dev, page, 0);
232 }
233
234 /*
235  * Create sysfs interface:
236  * /sys/devices/system/cpu/pseries_activate_hint_list
237  * /sys/devices/system/cpu/pseries_deactivate_hint_list
238  *      Comma separated list of cpus to activate or deactivate
239  * /sys/devices/system/cpu/cpuN/pseries_activate_hint
240  * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint
241  *      Per-cpu value of the hint
242  */
243
244 struct sysdev_class_attribute attr_cpu_activate_hint_list =
245                 _SYSDEV_CLASS_ATTR(pseries_activate_hint_list, 0444,
246                 cpu_activate_hint_list_show, NULL);
247
248 struct sysdev_class_attribute attr_cpu_deactivate_hint_list =
249                 _SYSDEV_CLASS_ATTR(pseries_deactivate_hint_list, 0444,
250                 cpu_deactivate_hint_list_show, NULL);
251
252 struct sysdev_attribute attr_percpu_activate_hint =
253                 _SYSDEV_ATTR(pseries_activate_hint, 0444,
254                 percpu_activate_hint_show, NULL);
255
256 struct sysdev_attribute attr_percpu_deactivate_hint =
257                 _SYSDEV_ATTR(pseries_deactivate_hint, 0444,
258                 percpu_deactivate_hint_show, NULL);
259
260 static int __init pseries_energy_init(void)
261 {
262         int cpu, err;
263         struct sys_device *cpu_sys_dev;
264
265         if (!check_for_h_best_energy()) {
266                 printk(KERN_INFO "Hypercall H_BEST_ENERGY not supported\n");
267                 return 0;
268         }
269         /* Create the sysfs files */
270         err = sysfs_create_file(&cpu_sysdev_class.kset.kobj,
271                                 &attr_cpu_activate_hint_list.attr);
272         if (!err)
273                 err = sysfs_create_file(&cpu_sysdev_class.kset.kobj,
274                                 &attr_cpu_deactivate_hint_list.attr);
275
276         if (err)
277                 return err;
278         for_each_possible_cpu(cpu) {
279                 cpu_sys_dev = get_cpu_sysdev(cpu);
280                 err = sysfs_create_file(&cpu_sys_dev->kobj,
281                                 &attr_percpu_activate_hint.attr);
282                 if (err)
283                         break;
284                 err = sysfs_create_file(&cpu_sys_dev->kobj,
285                                 &attr_percpu_deactivate_hint.attr);
286                 if (err)
287                         break;
288         }
289
290         if (err)
291                 return err;
292
293         sysfs_entries = 1; /* Removed entries on cleanup */
294         return 0;
295
296 }
297
298 static void __exit pseries_energy_cleanup(void)
299 {
300         int cpu;
301         struct sys_device *cpu_sys_dev;
302
303         if (!sysfs_entries)
304                 return;
305
306         /* Remove the sysfs files */
307         sysfs_remove_file(&cpu_sysdev_class.kset.kobj,
308                                 &attr_cpu_activate_hint_list.attr);
309
310         sysfs_remove_file(&cpu_sysdev_class.kset.kobj,
311                                 &attr_cpu_deactivate_hint_list.attr);
312
313         for_each_possible_cpu(cpu) {
314                 cpu_sys_dev = get_cpu_sysdev(cpu);
315                 sysfs_remove_file(&cpu_sys_dev->kobj,
316                                 &attr_percpu_activate_hint.attr);
317                 sysfs_remove_file(&cpu_sys_dev->kobj,
318                                 &attr_percpu_deactivate_hint.attr);
319         }
320 }
321
322 module_init(pseries_energy_init);
323 module_exit(pseries_energy_cleanup);
324 MODULE_DESCRIPTION("Driver for pSeries platform energy management");
325 MODULE_AUTHOR("Vaidyanathan Srinivasan");
326 MODULE_LICENSE("GPL");