| // 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); |
| } |