|  | // 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 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) | 
|  | return; | 
|  |  | 
|  | 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 (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++; | 
|  | } | 
|  |  | 
|  | 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); | 
|  | out_pcaps: | 
|  | kfree(pcaps); | 
|  | } |