| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* | 
 |  * PowerNV OPAL Powercap interface | 
 |  * | 
 |  * Copyright 2017 IBM Corp. | 
 |  */ | 
 |  | 
 | #define pr_fmt(fmt)     "opal-powercap: " fmt | 
 |  | 
 | #include <linux/of.h> | 
 | #include <linux/kobject.h> | 
 | #include <linux/slab.h> | 
 |  | 
 | #include <asm/opal.h> | 
 |  | 
 | static DEFINE_MUTEX(powercap_mutex); | 
 |  | 
 | static struct kobject *powercap_kobj; | 
 |  | 
 | struct powercap_attr { | 
 | 	u32 handle; | 
 | 	struct kobj_attribute attr; | 
 | }; | 
 |  | 
 | static struct pcap { | 
 | 	struct attribute_group pg; | 
 | 	struct powercap_attr *pattrs; | 
 | } *pcaps; | 
 |  | 
 | static ssize_t powercap_show(struct kobject *kobj, struct kobj_attribute *attr, | 
 | 			     char *buf) | 
 | { | 
 | 	struct powercap_attr *pcap_attr = container_of(attr, | 
 | 						struct powercap_attr, attr); | 
 | 	struct opal_msg msg; | 
 | 	u32 pcap; | 
 | 	int ret, token; | 
 |  | 
 | 	token = opal_async_get_token_interruptible(); | 
 | 	if (token < 0) { | 
 | 		pr_devel("Failed to get token\n"); | 
 | 		return token; | 
 | 	} | 
 |  | 
 | 	ret = mutex_lock_interruptible(&powercap_mutex); | 
 | 	if (ret) | 
 | 		goto out_token; | 
 |  | 
 | 	ret = opal_get_powercap(pcap_attr->handle, token, (u32 *)__pa(&pcap)); | 
 | 	switch (ret) { | 
 | 	case OPAL_ASYNC_COMPLETION: | 
 | 		ret = opal_async_wait_response(token, &msg); | 
 | 		if (ret) { | 
 | 			pr_devel("Failed to wait for the async response\n"); | 
 | 			ret = -EIO; | 
 | 			goto out; | 
 | 		} | 
 | 		ret = opal_error_code(opal_get_async_rc(msg)); | 
 | 		if (!ret) { | 
 | 			ret = sprintf(buf, "%u\n", be32_to_cpu(pcap)); | 
 | 			if (ret < 0) | 
 | 				ret = -EIO; | 
 | 		} | 
 | 		break; | 
 | 	case OPAL_SUCCESS: | 
 | 		ret = sprintf(buf, "%u\n", be32_to_cpu(pcap)); | 
 | 		if (ret < 0) | 
 | 			ret = -EIO; | 
 | 		break; | 
 | 	default: | 
 | 		ret = opal_error_code(ret); | 
 | 	} | 
 |  | 
 | out: | 
 | 	mutex_unlock(&powercap_mutex); | 
 | out_token: | 
 | 	opal_async_release_token(token); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static ssize_t powercap_store(struct kobject *kobj, | 
 | 			      struct kobj_attribute *attr, const char *buf, | 
 | 			      size_t count) | 
 | { | 
 | 	struct powercap_attr *pcap_attr = container_of(attr, | 
 | 						struct powercap_attr, attr); | 
 | 	struct opal_msg msg; | 
 | 	u32 pcap; | 
 | 	int ret, token; | 
 |  | 
 | 	ret = kstrtoint(buf, 0, &pcap); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	token = opal_async_get_token_interruptible(); | 
 | 	if (token < 0) { | 
 | 		pr_devel("Failed to get token\n"); | 
 | 		return token; | 
 | 	} | 
 |  | 
 | 	ret = mutex_lock_interruptible(&powercap_mutex); | 
 | 	if (ret) | 
 | 		goto out_token; | 
 |  | 
 | 	ret = opal_set_powercap(pcap_attr->handle, token, pcap); | 
 | 	switch (ret) { | 
 | 	case OPAL_ASYNC_COMPLETION: | 
 | 		ret = opal_async_wait_response(token, &msg); | 
 | 		if (ret) { | 
 | 			pr_devel("Failed to wait for the async response\n"); | 
 | 			ret = -EIO; | 
 | 			goto out; | 
 | 		} | 
 | 		ret = opal_error_code(opal_get_async_rc(msg)); | 
 | 		if (!ret) | 
 | 			ret = count; | 
 | 		break; | 
 | 	case OPAL_SUCCESS: | 
 | 		ret = count; | 
 | 		break; | 
 | 	default: | 
 | 		ret = opal_error_code(ret); | 
 | 	} | 
 |  | 
 | out: | 
 | 	mutex_unlock(&powercap_mutex); | 
 | out_token: | 
 | 	opal_async_release_token(token); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void __init powercap_add_attr(int handle, const char *name, | 
 | 			      struct powercap_attr *attr) | 
 | { | 
 | 	attr->handle = handle; | 
 | 	sysfs_attr_init(&attr->attr.attr); | 
 | 	attr->attr.attr.name = name; | 
 | 	attr->attr.attr.mode = 0444; | 
 | 	attr->attr.show = powercap_show; | 
 | } | 
 |  | 
 | void __init opal_powercap_init(void) | 
 | { | 
 | 	struct device_node *powercap, *node; | 
 | 	int i = 0; | 
 |  | 
 | 	powercap = of_find_compatible_node(NULL, NULL, "ibm,opal-powercap"); | 
 | 	if (!powercap) { | 
 | 		pr_devel("Powercap node not found\n"); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	pcaps = kcalloc(of_get_child_count(powercap), sizeof(*pcaps), | 
 | 			GFP_KERNEL); | 
 | 	if (!pcaps) | 
 | 		goto out_put_powercap; | 
 |  | 
 | 	powercap_kobj = kobject_create_and_add("powercap", opal_kobj); | 
 | 	if (!powercap_kobj) { | 
 | 		pr_warn("Failed to create powercap kobject\n"); | 
 | 		goto out_pcaps; | 
 | 	} | 
 |  | 
 | 	i = 0; | 
 | 	for_each_child_of_node(powercap, node) { | 
 | 		u32 cur, min, max; | 
 | 		int j = 0; | 
 | 		bool has_cur = false, has_min = false, has_max = false; | 
 |  | 
 | 		if (!of_property_read_u32(node, "powercap-min", &min)) { | 
 | 			j++; | 
 | 			has_min = true; | 
 | 		} | 
 |  | 
 | 		if (!of_property_read_u32(node, "powercap-max", &max)) { | 
 | 			j++; | 
 | 			has_max = true; | 
 | 		} | 
 |  | 
 | 		if (!of_property_read_u32(node, "powercap-current", &cur)) { | 
 | 			j++; | 
 | 			has_cur = true; | 
 | 		} | 
 |  | 
 | 		pcaps[i].pattrs = kcalloc(j, sizeof(struct powercap_attr), | 
 | 					  GFP_KERNEL); | 
 | 		if (!pcaps[i].pattrs) | 
 | 			goto out_pcaps_pattrs; | 
 |  | 
 | 		pcaps[i].pg.attrs = kcalloc(j + 1, sizeof(struct attribute *), | 
 | 					    GFP_KERNEL); | 
 | 		if (!pcaps[i].pg.attrs) { | 
 | 			kfree(pcaps[i].pattrs); | 
 | 			goto out_pcaps_pattrs; | 
 | 		} | 
 |  | 
 | 		j = 0; | 
 | 		pcaps[i].pg.name = kasprintf(GFP_KERNEL, "%pOFn", node); | 
 | 		if (!pcaps[i].pg.name) { | 
 | 			kfree(pcaps[i].pattrs); | 
 | 			kfree(pcaps[i].pg.attrs); | 
 | 			goto out_pcaps_pattrs; | 
 | 		} | 
 |  | 
 | 		if (has_min) { | 
 | 			powercap_add_attr(min, "powercap-min", | 
 | 					  &pcaps[i].pattrs[j]); | 
 | 			pcaps[i].pg.attrs[j] = &pcaps[i].pattrs[j].attr.attr; | 
 | 			j++; | 
 | 		} | 
 |  | 
 | 		if (has_max) { | 
 | 			powercap_add_attr(max, "powercap-max", | 
 | 					  &pcaps[i].pattrs[j]); | 
 | 			pcaps[i].pg.attrs[j] = &pcaps[i].pattrs[j].attr.attr; | 
 | 			j++; | 
 | 		} | 
 |  | 
 | 		if (has_cur) { | 
 | 			powercap_add_attr(cur, "powercap-current", | 
 | 					  &pcaps[i].pattrs[j]); | 
 | 			pcaps[i].pattrs[j].attr.attr.mode |= 0220; | 
 | 			pcaps[i].pattrs[j].attr.store = powercap_store; | 
 | 			pcaps[i].pg.attrs[j] = &pcaps[i].pattrs[j].attr.attr; | 
 | 			j++; | 
 | 		} | 
 |  | 
 | 		if (sysfs_create_group(powercap_kobj, &pcaps[i].pg)) { | 
 | 			pr_warn("Failed to create powercap attribute group %s\n", | 
 | 				pcaps[i].pg.name); | 
 | 			goto out_pcaps_pattrs; | 
 | 		} | 
 | 		i++; | 
 | 	} | 
 | 	of_node_put(powercap); | 
 |  | 
 | 	return; | 
 |  | 
 | out_pcaps_pattrs: | 
 | 	while (--i >= 0) { | 
 | 		kfree(pcaps[i].pattrs); | 
 | 		kfree(pcaps[i].pg.attrs); | 
 | 		kfree(pcaps[i].pg.name); | 
 | 	} | 
 | 	kobject_put(powercap_kobj); | 
 | 	of_node_put(node); | 
 | out_pcaps: | 
 | 	kfree(pcaps); | 
 | out_put_powercap: | 
 | 	of_node_put(powercap); | 
 | } |