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 u32 nr_drc_indexes
, thread_drc_index
;
83 * The first element of ibm,drc-indexes array is the
84 * number of drc_indexes returned in the list. Hence
85 * thread_index+1 will get the drc_index corresponding
86 * to core number thread_index.
88 rc
= of_property_read_u32_index(dn
, "ibm,drc-indexes",
93 WARN_ON_ONCE(thread_index
> nr_drc_indexes
);
94 rc
= of_property_read_u32_index(dn
, "ibm,drc-indexes",
100 ret
= thread_drc_index
;
109 printk(KERN_WARNING
"cpu_to_drc_index(%d) failed", cpu
);
113 static int drc_index_to_cpu(u32 drc_index
)
115 struct device_node
*dn
= NULL
;
117 int thread_index
= 0, cpu
= 0;
120 dn
= of_find_node_by_path("/cpus");
124 if (firmware_has_feature(FW_FEATURE_DRC_INFO
)) {
125 struct property
*info
= NULL
;
126 struct of_drc_info drc
;
131 info
= of_find_property(dn
, "ibm,drc-info", NULL
);
133 goto err_of_node_put
;
135 value
= of_prop_next_u32(info
, NULL
, &num_set_entries
);
137 goto err_of_node_put
;
139 for (j
= 0; j
< num_set_entries
; j
++) {
141 of_read_drc_info_cell(&info
, &value
, &drc
);
142 if (strncmp(drc
.drc_type
, "CPU", 3))
145 if (drc_index
> drc
.last_drc_index
) {
146 cpu
+= drc
.num_sequential_elems
;
149 cpu
+= ((drc_index
- drc
.drc_index_start
) /
152 thread_index
= cpu_first_thread_of_core(cpu
);
159 indexes
= of_get_property(dn
, "ibm,drc-indexes", NULL
);
161 goto err_of_node_put
;
163 * First element in the array is the number of drc_indexes
164 * returned. Search through the list to find the matching
165 * drc_index and get the core number
167 for (i
= 0; i
< indexes
[0]; i
++) {
168 if (indexes
[i
+ 1] == drc_index
)
171 /* Convert core number to logical cpu number */
172 thread_index
= cpu_first_thread_of_core(i
);
180 printk(KERN_WARNING
"drc_index_to_cpu(%d) failed", drc_index
);
185 * pseries hypervisor call H_BEST_ENERGY provides hints to OS on
186 * preferred logical cpus to activate or deactivate for optimized
187 * energy consumption.
190 #define FLAGS_MODE1 0x004E200000080E01UL
191 #define FLAGS_MODE2 0x004E200000080401UL
192 #define FLAGS_ACTIVATE 0x100
194 static ssize_t
get_best_energy_list(char *page
, int activate
)
197 unsigned long retbuf
[PLPAR_HCALL9_BUFSIZE
];
198 unsigned long flags
= 0;
202 buf_page
= (u32
*) get_zeroed_page(GFP_KERNEL
);
208 flags
|= FLAGS_ACTIVATE
;
210 rc
= plpar_hcall9(H_BEST_ENERGY
, retbuf
, flags
, 0, __pa(buf_page
),
212 if (rc
!= H_SUCCESS
) {
213 free_page((unsigned long) buf_page
);
218 for (i
= 0; i
< cnt
; i
++) {
219 cpu
= drc_index_to_cpu(buf_page
[2*i
+1]);
220 if ((cpu_online(cpu
) && !activate
) ||
221 (!cpu_online(cpu
) && activate
))
222 s
+= sprintf(s
, "%d,", cpu
);
224 if (s
> page
) { /* Something to show */
225 s
--; /* Suppress last comma */
226 s
+= sprintf(s
, "\n");
229 free_page((unsigned long) buf_page
);
233 static ssize_t
get_best_energy_data(struct device
*dev
,
234 char *page
, int activate
)
237 unsigned long retbuf
[PLPAR_HCALL9_BUFSIZE
];
238 unsigned long flags
= 0;
242 flags
|= FLAGS_ACTIVATE
;
244 rc
= plpar_hcall9(H_BEST_ENERGY
, retbuf
, flags
,
245 cpu_to_drc_index(dev
->id
),
246 0, 0, 0, 0, 0, 0, 0);
251 return sprintf(page
, "%lu\n", retbuf
[1] >> 32);
254 /* Wrapper functions */
256 static ssize_t
cpu_activate_hint_list_show(struct device
*dev
,
257 struct device_attribute
*attr
, char *page
)
259 return get_best_energy_list(page
, 1);
262 static ssize_t
cpu_deactivate_hint_list_show(struct device
*dev
,
263 struct device_attribute
*attr
, char *page
)
265 return get_best_energy_list(page
, 0);
268 static ssize_t
percpu_activate_hint_show(struct device
*dev
,
269 struct device_attribute
*attr
, char *page
)
271 return get_best_energy_data(dev
, page
, 1);
274 static ssize_t
percpu_deactivate_hint_show(struct device
*dev
,
275 struct device_attribute
*attr
, char *page
)
277 return get_best_energy_data(dev
, page
, 0);
281 * Create sysfs interface:
282 * /sys/devices/system/cpu/pseries_activate_hint_list
283 * /sys/devices/system/cpu/pseries_deactivate_hint_list
284 * Comma separated list of cpus to activate or deactivate
285 * /sys/devices/system/cpu/cpuN/pseries_activate_hint
286 * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint
287 * Per-cpu value of the hint
290 static struct device_attribute attr_cpu_activate_hint_list
=
291 __ATTR(pseries_activate_hint_list
, 0444,
292 cpu_activate_hint_list_show
, NULL
);
294 static struct device_attribute attr_cpu_deactivate_hint_list
=
295 __ATTR(pseries_deactivate_hint_list
, 0444,
296 cpu_deactivate_hint_list_show
, NULL
);
298 static struct device_attribute attr_percpu_activate_hint
=
299 __ATTR(pseries_activate_hint
, 0444,
300 percpu_activate_hint_show
, NULL
);
302 static struct device_attribute attr_percpu_deactivate_hint
=
303 __ATTR(pseries_deactivate_hint
, 0444,
304 percpu_deactivate_hint_show
, NULL
);
306 static int __init
pseries_energy_init(void)
309 struct device
*cpu_dev
;
311 if (!firmware_has_feature(FW_FEATURE_BEST_ENERGY
))
312 return 0; /* H_BEST_ENERGY hcall not supported */
314 /* Create the sysfs files */
315 err
= device_create_file(cpu_subsys
.dev_root
,
316 &attr_cpu_activate_hint_list
);
318 err
= device_create_file(cpu_subsys
.dev_root
,
319 &attr_cpu_deactivate_hint_list
);
323 for_each_possible_cpu(cpu
) {
324 cpu_dev
= get_cpu_device(cpu
);
325 err
= device_create_file(cpu_dev
,
326 &attr_percpu_activate_hint
);
329 err
= device_create_file(cpu_dev
,
330 &attr_percpu_deactivate_hint
);
338 sysfs_entries
= 1; /* Removed entries on cleanup */
343 static void __exit
pseries_energy_cleanup(void)
346 struct device
*cpu_dev
;
351 /* Remove the sysfs files */
352 device_remove_file(cpu_subsys
.dev_root
, &attr_cpu_activate_hint_list
);
353 device_remove_file(cpu_subsys
.dev_root
, &attr_cpu_deactivate_hint_list
);
355 for_each_possible_cpu(cpu
) {
356 cpu_dev
= get_cpu_device(cpu
);
357 sysfs_remove_file(&cpu_dev
->kobj
,
358 &attr_percpu_activate_hint
.attr
);
359 sysfs_remove_file(&cpu_dev
->kobj
,
360 &attr_percpu_deactivate_hint
.attr
);
364 module_init(pseries_energy_init
);
365 module_exit(pseries_energy_cleanup
);
366 MODULE_DESCRIPTION("Driver for pSeries platform energy management");
367 MODULE_AUTHOR("Vaidyanathan Srinivasan");
368 MODULE_LICENSE("GPL");