18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * PowerNV OPAL Powercap interface 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2017 IBM Corp. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "opal-powercap: " fmt 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/of.h> 118c2ecf20Sopenharmony_ci#include <linux/kobject.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <asm/opal.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(powercap_mutex); 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cistatic struct kobject *powercap_kobj; 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistruct powercap_attr { 218c2ecf20Sopenharmony_ci u32 handle; 228c2ecf20Sopenharmony_ci struct kobj_attribute attr; 238c2ecf20Sopenharmony_ci}; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic struct pcap { 268c2ecf20Sopenharmony_ci struct attribute_group pg; 278c2ecf20Sopenharmony_ci struct powercap_attr *pattrs; 288c2ecf20Sopenharmony_ci} *pcaps; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistatic ssize_t powercap_show(struct kobject *kobj, struct kobj_attribute *attr, 318c2ecf20Sopenharmony_ci char *buf) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci struct powercap_attr *pcap_attr = container_of(attr, 348c2ecf20Sopenharmony_ci struct powercap_attr, attr); 358c2ecf20Sopenharmony_ci struct opal_msg msg; 368c2ecf20Sopenharmony_ci u32 pcap; 378c2ecf20Sopenharmony_ci int ret, token; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci token = opal_async_get_token_interruptible(); 408c2ecf20Sopenharmony_ci if (token < 0) { 418c2ecf20Sopenharmony_ci pr_devel("Failed to get token\n"); 428c2ecf20Sopenharmony_ci return token; 438c2ecf20Sopenharmony_ci } 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci ret = mutex_lock_interruptible(&powercap_mutex); 468c2ecf20Sopenharmony_ci if (ret) 478c2ecf20Sopenharmony_ci goto out_token; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci ret = opal_get_powercap(pcap_attr->handle, token, (u32 *)__pa(&pcap)); 508c2ecf20Sopenharmony_ci switch (ret) { 518c2ecf20Sopenharmony_ci case OPAL_ASYNC_COMPLETION: 528c2ecf20Sopenharmony_ci ret = opal_async_wait_response(token, &msg); 538c2ecf20Sopenharmony_ci if (ret) { 548c2ecf20Sopenharmony_ci pr_devel("Failed to wait for the async response\n"); 558c2ecf20Sopenharmony_ci ret = -EIO; 568c2ecf20Sopenharmony_ci goto out; 578c2ecf20Sopenharmony_ci } 588c2ecf20Sopenharmony_ci ret = opal_error_code(opal_get_async_rc(msg)); 598c2ecf20Sopenharmony_ci if (!ret) { 608c2ecf20Sopenharmony_ci ret = sprintf(buf, "%u\n", be32_to_cpu(pcap)); 618c2ecf20Sopenharmony_ci if (ret < 0) 628c2ecf20Sopenharmony_ci ret = -EIO; 638c2ecf20Sopenharmony_ci } 648c2ecf20Sopenharmony_ci break; 658c2ecf20Sopenharmony_ci case OPAL_SUCCESS: 668c2ecf20Sopenharmony_ci ret = sprintf(buf, "%u\n", be32_to_cpu(pcap)); 678c2ecf20Sopenharmony_ci if (ret < 0) 688c2ecf20Sopenharmony_ci ret = -EIO; 698c2ecf20Sopenharmony_ci break; 708c2ecf20Sopenharmony_ci default: 718c2ecf20Sopenharmony_ci ret = opal_error_code(ret); 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ciout: 758c2ecf20Sopenharmony_ci mutex_unlock(&powercap_mutex); 768c2ecf20Sopenharmony_ciout_token: 778c2ecf20Sopenharmony_ci opal_async_release_token(token); 788c2ecf20Sopenharmony_ci return ret; 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic ssize_t powercap_store(struct kobject *kobj, 828c2ecf20Sopenharmony_ci struct kobj_attribute *attr, const char *buf, 838c2ecf20Sopenharmony_ci size_t count) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci struct powercap_attr *pcap_attr = container_of(attr, 868c2ecf20Sopenharmony_ci struct powercap_attr, attr); 878c2ecf20Sopenharmony_ci struct opal_msg msg; 888c2ecf20Sopenharmony_ci u32 pcap; 898c2ecf20Sopenharmony_ci int ret, token; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci ret = kstrtoint(buf, 0, &pcap); 928c2ecf20Sopenharmony_ci if (ret) 938c2ecf20Sopenharmony_ci return ret; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci token = opal_async_get_token_interruptible(); 968c2ecf20Sopenharmony_ci if (token < 0) { 978c2ecf20Sopenharmony_ci pr_devel("Failed to get token\n"); 988c2ecf20Sopenharmony_ci return token; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci ret = mutex_lock_interruptible(&powercap_mutex); 1028c2ecf20Sopenharmony_ci if (ret) 1038c2ecf20Sopenharmony_ci goto out_token; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci ret = opal_set_powercap(pcap_attr->handle, token, pcap); 1068c2ecf20Sopenharmony_ci switch (ret) { 1078c2ecf20Sopenharmony_ci case OPAL_ASYNC_COMPLETION: 1088c2ecf20Sopenharmony_ci ret = opal_async_wait_response(token, &msg); 1098c2ecf20Sopenharmony_ci if (ret) { 1108c2ecf20Sopenharmony_ci pr_devel("Failed to wait for the async response\n"); 1118c2ecf20Sopenharmony_ci ret = -EIO; 1128c2ecf20Sopenharmony_ci goto out; 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci ret = opal_error_code(opal_get_async_rc(msg)); 1158c2ecf20Sopenharmony_ci if (!ret) 1168c2ecf20Sopenharmony_ci ret = count; 1178c2ecf20Sopenharmony_ci break; 1188c2ecf20Sopenharmony_ci case OPAL_SUCCESS: 1198c2ecf20Sopenharmony_ci ret = count; 1208c2ecf20Sopenharmony_ci break; 1218c2ecf20Sopenharmony_ci default: 1228c2ecf20Sopenharmony_ci ret = opal_error_code(ret); 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ciout: 1268c2ecf20Sopenharmony_ci mutex_unlock(&powercap_mutex); 1278c2ecf20Sopenharmony_ciout_token: 1288c2ecf20Sopenharmony_ci opal_async_release_token(token); 1298c2ecf20Sopenharmony_ci return ret; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic void powercap_add_attr(int handle, const char *name, 1338c2ecf20Sopenharmony_ci struct powercap_attr *attr) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci attr->handle = handle; 1368c2ecf20Sopenharmony_ci sysfs_attr_init(&attr->attr.attr); 1378c2ecf20Sopenharmony_ci attr->attr.attr.name = name; 1388c2ecf20Sopenharmony_ci attr->attr.attr.mode = 0444; 1398c2ecf20Sopenharmony_ci attr->attr.show = powercap_show; 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_civoid __init opal_powercap_init(void) 1438c2ecf20Sopenharmony_ci{ 1448c2ecf20Sopenharmony_ci struct device_node *powercap, *node; 1458c2ecf20Sopenharmony_ci int i = 0; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci powercap = of_find_compatible_node(NULL, NULL, "ibm,opal-powercap"); 1488c2ecf20Sopenharmony_ci if (!powercap) { 1498c2ecf20Sopenharmony_ci pr_devel("Powercap node not found\n"); 1508c2ecf20Sopenharmony_ci return; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci pcaps = kcalloc(of_get_child_count(powercap), sizeof(*pcaps), 1548c2ecf20Sopenharmony_ci GFP_KERNEL); 1558c2ecf20Sopenharmony_ci if (!pcaps) 1568c2ecf20Sopenharmony_ci return; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci powercap_kobj = kobject_create_and_add("powercap", opal_kobj); 1598c2ecf20Sopenharmony_ci if (!powercap_kobj) { 1608c2ecf20Sopenharmony_ci pr_warn("Failed to create powercap kobject\n"); 1618c2ecf20Sopenharmony_ci goto out_pcaps; 1628c2ecf20Sopenharmony_ci } 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci i = 0; 1658c2ecf20Sopenharmony_ci for_each_child_of_node(powercap, node) { 1668c2ecf20Sopenharmony_ci u32 cur, min, max; 1678c2ecf20Sopenharmony_ci int j = 0; 1688c2ecf20Sopenharmony_ci bool has_cur = false, has_min = false, has_max = false; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci if (!of_property_read_u32(node, "powercap-min", &min)) { 1718c2ecf20Sopenharmony_ci j++; 1728c2ecf20Sopenharmony_ci has_min = true; 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci if (!of_property_read_u32(node, "powercap-max", &max)) { 1768c2ecf20Sopenharmony_ci j++; 1778c2ecf20Sopenharmony_ci has_max = true; 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci if (!of_property_read_u32(node, "powercap-current", &cur)) { 1818c2ecf20Sopenharmony_ci j++; 1828c2ecf20Sopenharmony_ci has_cur = true; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci pcaps[i].pattrs = kcalloc(j, sizeof(struct powercap_attr), 1868c2ecf20Sopenharmony_ci GFP_KERNEL); 1878c2ecf20Sopenharmony_ci if (!pcaps[i].pattrs) 1888c2ecf20Sopenharmony_ci goto out_pcaps_pattrs; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci pcaps[i].pg.attrs = kcalloc(j + 1, sizeof(struct attribute *), 1918c2ecf20Sopenharmony_ci GFP_KERNEL); 1928c2ecf20Sopenharmony_ci if (!pcaps[i].pg.attrs) { 1938c2ecf20Sopenharmony_ci kfree(pcaps[i].pattrs); 1948c2ecf20Sopenharmony_ci goto out_pcaps_pattrs; 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci j = 0; 1988c2ecf20Sopenharmony_ci pcaps[i].pg.name = kasprintf(GFP_KERNEL, "%pOFn", node); 1998c2ecf20Sopenharmony_ci if (!pcaps[i].pg.name) { 2008c2ecf20Sopenharmony_ci kfree(pcaps[i].pattrs); 2018c2ecf20Sopenharmony_ci kfree(pcaps[i].pg.attrs); 2028c2ecf20Sopenharmony_ci goto out_pcaps_pattrs; 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci if (has_min) { 2068c2ecf20Sopenharmony_ci powercap_add_attr(min, "powercap-min", 2078c2ecf20Sopenharmony_ci &pcaps[i].pattrs[j]); 2088c2ecf20Sopenharmony_ci pcaps[i].pg.attrs[j] = &pcaps[i].pattrs[j].attr.attr; 2098c2ecf20Sopenharmony_ci j++; 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci if (has_max) { 2138c2ecf20Sopenharmony_ci powercap_add_attr(max, "powercap-max", 2148c2ecf20Sopenharmony_ci &pcaps[i].pattrs[j]); 2158c2ecf20Sopenharmony_ci pcaps[i].pg.attrs[j] = &pcaps[i].pattrs[j].attr.attr; 2168c2ecf20Sopenharmony_ci j++; 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci if (has_cur) { 2208c2ecf20Sopenharmony_ci powercap_add_attr(cur, "powercap-current", 2218c2ecf20Sopenharmony_ci &pcaps[i].pattrs[j]); 2228c2ecf20Sopenharmony_ci pcaps[i].pattrs[j].attr.attr.mode |= 0220; 2238c2ecf20Sopenharmony_ci pcaps[i].pattrs[j].attr.store = powercap_store; 2248c2ecf20Sopenharmony_ci pcaps[i].pg.attrs[j] = &pcaps[i].pattrs[j].attr.attr; 2258c2ecf20Sopenharmony_ci j++; 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci if (sysfs_create_group(powercap_kobj, &pcaps[i].pg)) { 2298c2ecf20Sopenharmony_ci pr_warn("Failed to create powercap attribute group %s\n", 2308c2ecf20Sopenharmony_ci pcaps[i].pg.name); 2318c2ecf20Sopenharmony_ci goto out_pcaps_pattrs; 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci i++; 2348c2ecf20Sopenharmony_ci } 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci return; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ciout_pcaps_pattrs: 2398c2ecf20Sopenharmony_ci while (--i >= 0) { 2408c2ecf20Sopenharmony_ci kfree(pcaps[i].pattrs); 2418c2ecf20Sopenharmony_ci kfree(pcaps[i].pg.attrs); 2428c2ecf20Sopenharmony_ci kfree(pcaps[i].pg.name); 2438c2ecf20Sopenharmony_ci } 2448c2ecf20Sopenharmony_ci kobject_put(powercap_kobj); 2458c2ecf20Sopenharmony_ciout_pcaps: 2468c2ecf20Sopenharmony_ci kfree(pcaps); 2478c2ecf20Sopenharmony_ci} 248