2 * POWER platform energy management driver
3 * Copyright (C) 2010 IBM Corporation
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.
9 * This pseries platform device driver provides access to
10 * platform energy management capabilities.
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/device.h>
19 #include <linux/cpu.h>
21 #include <asm/cputhreads.h>
23 #include <asm/hvcall.h>
24 #include <asm/firmware.h>
28 #define MODULE_VERS "1.0"
29 #define MODULE_NAME "pseries_energy"
33 static int sysfs_entries
;
37 /* Helper Routines to convert between drc_index to cpu numbers */
39 static u32
cpu_to_drc_index(int cpu
)
41 struct device_node
*dn
= NULL
;
46 dn
= of_find_node_by_path("/cpus");
50 /* Convert logical cpu number to core number */
51 thread_index
= cpu_core_index_of_thread(cpu
);
53 if (firmware_has_feature(FW_FEATURE_DRC_INFO
)) {
54 struct property
*info
= NULL
;
55 struct of_drc_info drc
;
60 info
= of_find_property(dn
, "ibm,drc-info", NULL
);
64 value
= of_prop_next_u32(info
, NULL
, &num_set_entries
);
68 for (j
= 0; j
< num_set_entries
; j
++) {
70 of_read_drc_info_cell(&info
, &value
, &drc
);
71 if (strncmp(drc
.drc_type
, "CPU", 3))
74 if (thread_index
< drc
.last_drc_index
)
78 ret
= drc
.drc_index_start
+ (thread_index
* drc
.sequential_inc
);
80 const __be32
*indexes
;
82 indexes
= of_get_property(dn
, "ibm,drc-indexes", NULL
);
87 * The first element indexes[0] is the number of drc_indexes
88 * returned in the list. Hence thread_index+1 will get the
89 * drc_index corresponding to core number thread_index.
91 ret
= indexes
[thread_index
+ 1];
100 printk(KERN_WARNING
"cpu_to_drc_index(%d) failed", cpu
);
104 static int drc_index_to_cpu(u32 drc_index
)
106 struct device_node
*dn
= NULL
;
108 int thread_index
= 0, cpu
= 0;
111 dn
= of_find_node_by_path("/cpus");
115 if (firmware_has_feature(FW_FEATURE_DRC_INFO
)) {
116 struct property
*info
= NULL
;
117 struct of_drc_info drc
;
122 info
= of_find_property(dn
, "ibm,drc-info", NULL
);
124 goto err_of_node_put
;
126 value
= of_prop_next_u32(info
, NULL
, &num_set_entries
);
128 goto err_of_node_put
;
130 for (j
= 0; j
< num_set_entries
; j
++) {
132 of_read_drc_info_cell(&info
, &value
, &drc
);
133 if (strncmp(drc
.drc_type
, "CPU", 3))
136 if (drc_index
> drc
.last_drc_index
) {
137 cpu
+= drc
.num_sequential_elems
;
140 cpu
+= ((drc_index
- drc
.drc_index_start
) /
143 thread_index
= cpu_first_thread_of_core(cpu
);
150 indexes
= of_get_property(dn
, "ibm,drc-indexes", NULL
);
152 goto err_of_node_put
;
154 * First element in the array is the number of drc_indexes
155 * returned. Search through the list to find the matching
156 * drc_index and get the core number
158 for (i
= 0; i
< indexes
[0]; i
++) {
159 if (indexes
[i
+ 1] == drc_index
)
162 /* Convert core number to logical cpu number */
163 thread_index
= cpu_first_thread_of_core(i
);
171 printk(KERN_WARNING
"drc_index_to_cpu(%d) failed", drc_index
);
176 * pseries hypervisor call H_BEST_ENERGY provides hints to OS on
177 * preferred logical cpus to activate or deactivate for optimized
178 * energy consumption.
181 #define FLAGS_MODE1 0x004E200000080E01UL
182 #define FLAGS_MODE2 0x004E200000080401UL
183 #define FLAGS_ACTIVATE 0x100
185 static ssize_t
get_best_energy_list(char *page
, int activate
)
188 unsigned long retbuf
[PLPAR_HCALL9_BUFSIZE
];
189 unsigned long flags
= 0;
193 buf_page
= (u32
*) get_zeroed_page(GFP_KERNEL
);
199 flags
|= FLAGS_ACTIVATE
;
201 rc
= plpar_hcall9(H_BEST_ENERGY
, retbuf
, flags
, 0, __pa(buf_page
),
203 if (rc
!= H_SUCCESS
) {
204 free_page((unsigned long) buf_page
);
209 for (i
= 0; i
< cnt
; i
++) {
210 cpu
= drc_index_to_cpu(buf_page
[2*i
+1]);
211 if ((cpu_online(cpu
) && !activate
) ||
212 (!cpu_online(cpu
) && activate
))
213 s
+= sprintf(s
, "%d,", cpu
);
215 if (s
> page
) { /* Something to show */
216 s
--; /* Suppress last comma */
217 s
+= sprintf(s
, "\n");
220 free_page((unsigned long) buf_page
);
224 static ssize_t
get_best_energy_data(struct device
*dev
,
225 char *page
, int activate
)
228 unsigned long retbuf
[PLPAR_HCALL9_BUFSIZE
];
229 unsigned long flags
= 0;
233 flags
|= FLAGS_ACTIVATE
;
235 rc
= plpar_hcall9(H_BEST_ENERGY
, retbuf
, flags
,
236 cpu_to_drc_index(dev
->id
),
237 0, 0, 0, 0, 0, 0, 0);
242 return sprintf(page
, "%lu\n", retbuf
[1] >> 32);
245 /* Wrapper functions */
247 static ssize_t
cpu_activate_hint_list_show(struct device
*dev
,
248 struct device_attribute
*attr
, char *page
)
250 return get_best_energy_list(page
, 1);
253 static ssize_t
cpu_deactivate_hint_list_show(struct device
*dev
,
254 struct device_attribute
*attr
, char *page
)
256 return get_best_energy_list(page
, 0);
259 static ssize_t
percpu_activate_hint_show(struct device
*dev
,
260 struct device_attribute
*attr
, char *page
)
262 return get_best_energy_data(dev
, page
, 1);
265 static ssize_t
percpu_deactivate_hint_show(struct device
*dev
,
266 struct device_attribute
*attr
, char *page
)
268 return get_best_energy_data(dev
, page
, 0);
272 * Create sysfs interface:
273 * /sys/devices/system/cpu/pseries_activate_hint_list
274 * /sys/devices/system/cpu/pseries_deactivate_hint_list
275 * Comma separated list of cpus to activate or deactivate
276 * /sys/devices/system/cpu/cpuN/pseries_activate_hint
277 * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint
278 * Per-cpu value of the hint
281 static struct device_attribute attr_cpu_activate_hint_list
=
282 __ATTR(pseries_activate_hint_list
, 0444,
283 cpu_activate_hint_list_show
, NULL
);
285 static struct device_attribute attr_cpu_deactivate_hint_list
=
286 __ATTR(pseries_deactivate_hint_list
, 0444,
287 cpu_deactivate_hint_list_show
, NULL
);
289 static struct device_attribute attr_percpu_activate_hint
=
290 __ATTR(pseries_activate_hint
, 0444,
291 percpu_activate_hint_show
, NULL
);
293 static struct device_attribute attr_percpu_deactivate_hint
=
294 __ATTR(pseries_deactivate_hint
, 0444,
295 percpu_deactivate_hint_show
, NULL
);
297 static int __init
pseries_energy_init(void)
300 struct device
*cpu_dev
;
302 if (!firmware_has_feature(FW_FEATURE_BEST_ENERGY
))
303 return 0; /* H_BEST_ENERGY hcall not supported */
305 /* Create the sysfs files */
306 err
= device_create_file(cpu_subsys
.dev_root
,
307 &attr_cpu_activate_hint_list
);
309 err
= device_create_file(cpu_subsys
.dev_root
,
310 &attr_cpu_deactivate_hint_list
);
314 for_each_possible_cpu(cpu
) {
315 cpu_dev
= get_cpu_device(cpu
);
316 err
= device_create_file(cpu_dev
,
317 &attr_percpu_activate_hint
);
320 err
= device_create_file(cpu_dev
,
321 &attr_percpu_deactivate_hint
);
329 sysfs_entries
= 1; /* Removed entries on cleanup */
334 static void __exit
pseries_energy_cleanup(void)
337 struct device
*cpu_dev
;
342 /* Remove the sysfs files */
343 device_remove_file(cpu_subsys
.dev_root
, &attr_cpu_activate_hint_list
);
344 device_remove_file(cpu_subsys
.dev_root
, &attr_cpu_deactivate_hint_list
);
346 for_each_possible_cpu(cpu
) {
347 cpu_dev
= get_cpu_device(cpu
);
348 sysfs_remove_file(&cpu_dev
->kobj
,
349 &attr_percpu_activate_hint
.attr
);
350 sysfs_remove_file(&cpu_dev
->kobj
,
351 &attr_percpu_deactivate_hint
.attr
);
355 module_init(pseries_energy_init
);
356 module_exit(pseries_energy_cleanup
);
357 MODULE_DESCRIPTION("Driver for pSeries platform energy management");
358 MODULE_AUTHOR("Vaidyanathan Srinivasan");
359 MODULE_LICENSE("GPL");