1 // SPDX-License-Identifier: GPL-2.0-only
3 * POWER platform energy management driver
4 * Copyright (C) 2010 IBM Corporation
6 * This pseries platform device driver provides access to
7 * platform energy management capabilities.
10 #include <linux/module.h>
11 #include <linux/types.h>
12 #include <linux/errno.h>
13 #include <linux/init.h>
14 #include <linux/seq_file.h>
15 #include <linux/device.h>
16 #include <linux/cpu.h>
18 #include <asm/cputhreads.h>
20 #include <asm/hvcall.h>
21 #include <asm/firmware.h>
25 #define MODULE_VERS "1.0"
26 #define MODULE_NAME "pseries_energy"
30 static int sysfs_entries
;
34 /* Helper Routines to convert between drc_index to cpu numbers */
36 static u32
cpu_to_drc_index(int cpu
)
38 struct device_node
*dn
= NULL
;
39 struct property
*info
;
44 dn
= of_find_node_by_path("/cpus");
48 /* Convert logical cpu number to core number */
49 thread_index
= cpu_core_index_of_thread(cpu
);
51 info
= of_find_property(dn
, "ibm,drc-info", NULL
);
53 struct of_drc_info drc
;
58 value
= of_prop_next_u32(info
, NULL
, &num_set_entries
);
64 for (j
= 0; j
< num_set_entries
; j
++) {
66 of_read_drc_info_cell(&info
, &value
, &drc
);
67 if (strncmp(drc
.drc_type
, "CPU", 3))
70 if (thread_index
< drc
.last_drc_index
)
74 ret
= drc
.drc_index_start
+ (thread_index
* drc
.sequential_inc
);
76 u32 nr_drc_indexes
, thread_drc_index
;
79 * The first element of ibm,drc-indexes array is the
80 * number of drc_indexes returned in the list. Hence
81 * thread_index+1 will get the drc_index corresponding
82 * to core number thread_index.
84 rc
= of_property_read_u32_index(dn
, "ibm,drc-indexes",
89 WARN_ON_ONCE(thread_index
> nr_drc_indexes
);
90 rc
= of_property_read_u32_index(dn
, "ibm,drc-indexes",
96 ret
= thread_drc_index
;
105 printk(KERN_WARNING
"cpu_to_drc_index(%d) failed", cpu
);
109 static int drc_index_to_cpu(u32 drc_index
)
111 struct device_node
*dn
= NULL
;
112 struct property
*info
;
114 int thread_index
= 0, cpu
= 0;
117 dn
= of_find_node_by_path("/cpus");
120 info
= of_find_property(dn
, "ibm,drc-info", NULL
);
122 struct of_drc_info drc
;
127 value
= of_prop_next_u32(info
, NULL
, &num_set_entries
);
129 goto err_of_node_put
;
133 for (j
= 0; j
< num_set_entries
; j
++) {
135 of_read_drc_info_cell(&info
, &value
, &drc
);
136 if (strncmp(drc
.drc_type
, "CPU", 3))
139 if (drc_index
> drc
.last_drc_index
) {
140 cpu
+= drc
.num_sequential_elems
;
143 cpu
+= ((drc_index
- drc
.drc_index_start
) /
146 thread_index
= cpu_first_thread_of_core(cpu
);
153 indexes
= of_get_property(dn
, "ibm,drc-indexes", NULL
);
155 goto err_of_node_put
;
157 * First element in the array is the number of drc_indexes
158 * returned. Search through the list to find the matching
159 * drc_index and get the core number
161 for (i
= 0; i
< indexes
[0]; i
++) {
162 if (indexes
[i
+ 1] == drc_index
)
165 /* Convert core number to logical cpu number */
166 thread_index
= cpu_first_thread_of_core(i
);
174 printk(KERN_WARNING
"drc_index_to_cpu(%d) failed", drc_index
);
179 * pseries hypervisor call H_BEST_ENERGY provides hints to OS on
180 * preferred logical cpus to activate or deactivate for optimized
181 * energy consumption.
184 #define FLAGS_MODE1 0x004E200000080E01UL
185 #define FLAGS_MODE2 0x004E200000080401UL
186 #define FLAGS_ACTIVATE 0x100
188 static ssize_t
get_best_energy_list(char *page
, int activate
)
191 unsigned long retbuf
[PLPAR_HCALL9_BUFSIZE
];
192 unsigned long flags
= 0;
196 buf_page
= (u32
*) get_zeroed_page(GFP_KERNEL
);
202 flags
|= FLAGS_ACTIVATE
;
204 rc
= plpar_hcall9(H_BEST_ENERGY
, retbuf
, flags
, 0, __pa(buf_page
),
206 if (rc
!= H_SUCCESS
) {
207 free_page((unsigned long) buf_page
);
212 for (i
= 0; i
< cnt
; i
++) {
213 cpu
= drc_index_to_cpu(buf_page
[2*i
+1]);
214 if ((cpu_online(cpu
) && !activate
) ||
215 (!cpu_online(cpu
) && activate
))
216 s
+= sprintf(s
, "%d,", cpu
);
218 if (s
> page
) { /* Something to show */
219 s
--; /* Suppress last comma */
220 s
+= sprintf(s
, "\n");
223 free_page((unsigned long) buf_page
);
227 static ssize_t
get_best_energy_data(struct device
*dev
,
228 char *page
, int activate
)
231 unsigned long retbuf
[PLPAR_HCALL9_BUFSIZE
];
232 unsigned long flags
= 0;
236 flags
|= FLAGS_ACTIVATE
;
238 rc
= plpar_hcall9(H_BEST_ENERGY
, retbuf
, flags
,
239 cpu_to_drc_index(dev
->id
),
240 0, 0, 0, 0, 0, 0, 0);
245 return sprintf(page
, "%lu\n", retbuf
[1] >> 32);
248 /* Wrapper functions */
250 static ssize_t
cpu_activate_hint_list_show(struct device
*dev
,
251 struct device_attribute
*attr
, char *page
)
253 return get_best_energy_list(page
, 1);
256 static ssize_t
cpu_deactivate_hint_list_show(struct device
*dev
,
257 struct device_attribute
*attr
, char *page
)
259 return get_best_energy_list(page
, 0);
262 static ssize_t
percpu_activate_hint_show(struct device
*dev
,
263 struct device_attribute
*attr
, char *page
)
265 return get_best_energy_data(dev
, page
, 1);
268 static ssize_t
percpu_deactivate_hint_show(struct device
*dev
,
269 struct device_attribute
*attr
, char *page
)
271 return get_best_energy_data(dev
, page
, 0);
275 * Create sysfs interface:
276 * /sys/devices/system/cpu/pseries_activate_hint_list
277 * /sys/devices/system/cpu/pseries_deactivate_hint_list
278 * Comma separated list of cpus to activate or deactivate
279 * /sys/devices/system/cpu/cpuN/pseries_activate_hint
280 * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint
281 * Per-cpu value of the hint
284 static struct device_attribute attr_cpu_activate_hint_list
=
285 __ATTR(pseries_activate_hint_list
, 0444,
286 cpu_activate_hint_list_show
, NULL
);
288 static struct device_attribute attr_cpu_deactivate_hint_list
=
289 __ATTR(pseries_deactivate_hint_list
, 0444,
290 cpu_deactivate_hint_list_show
, NULL
);
292 static struct device_attribute attr_percpu_activate_hint
=
293 __ATTR(pseries_activate_hint
, 0444,
294 percpu_activate_hint_show
, NULL
);
296 static struct device_attribute attr_percpu_deactivate_hint
=
297 __ATTR(pseries_deactivate_hint
, 0444,
298 percpu_deactivate_hint_show
, NULL
);
300 static int __init
pseries_energy_init(void)
303 struct device
*cpu_dev
;
305 if (!firmware_has_feature(FW_FEATURE_BEST_ENERGY
))
306 return 0; /* H_BEST_ENERGY hcall not supported */
308 /* Create the sysfs files */
309 err
= device_create_file(cpu_subsys
.dev_root
,
310 &attr_cpu_activate_hint_list
);
312 err
= device_create_file(cpu_subsys
.dev_root
,
313 &attr_cpu_deactivate_hint_list
);
317 for_each_possible_cpu(cpu
) {
318 cpu_dev
= get_cpu_device(cpu
);
319 err
= device_create_file(cpu_dev
,
320 &attr_percpu_activate_hint
);
323 err
= device_create_file(cpu_dev
,
324 &attr_percpu_deactivate_hint
);
332 sysfs_entries
= 1; /* Removed entries on cleanup */
337 static void __exit
pseries_energy_cleanup(void)
340 struct device
*cpu_dev
;
345 /* Remove the sysfs files */
346 device_remove_file(cpu_subsys
.dev_root
, &attr_cpu_activate_hint_list
);
347 device_remove_file(cpu_subsys
.dev_root
, &attr_cpu_deactivate_hint_list
);
349 for_each_possible_cpu(cpu
) {
350 cpu_dev
= get_cpu_device(cpu
);
351 sysfs_remove_file(&cpu_dev
->kobj
,
352 &attr_percpu_activate_hint
.attr
);
353 sysfs_remove_file(&cpu_dev
->kobj
,
354 &attr_percpu_deactivate_hint
.attr
);
358 module_init(pseries_energy_init
);
359 module_exit(pseries_energy_cleanup
);
360 MODULE_DESCRIPTION("Driver for pSeries platform energy management");
361 MODULE_AUTHOR("Vaidyanathan Srinivasan");
362 MODULE_LICENSE("GPL");