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