forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
4.9 KiB
236 lines
4.9 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* PowerNV OPAL Sensor-groups interface |
|
* |
|
* Copyright 2017 IBM Corp. |
|
*/ |
|
|
|
#define pr_fmt(fmt) "opal-sensor-groups: " fmt |
|
|
|
#include <linux/of.h> |
|
#include <linux/kobject.h> |
|
#include <linux/slab.h> |
|
|
|
#include <asm/opal.h> |
|
|
|
static DEFINE_MUTEX(sg_mutex); |
|
|
|
static struct kobject *sg_kobj; |
|
|
|
struct sg_attr { |
|
u32 handle; |
|
struct kobj_attribute attr; |
|
}; |
|
|
|
static struct sensor_group { |
|
char name[20]; |
|
struct attribute_group sg; |
|
struct sg_attr *sgattrs; |
|
} *sgs; |
|
|
|
int sensor_group_enable(u32 handle, bool enable) |
|
{ |
|
struct opal_msg msg; |
|
int token, ret; |
|
|
|
token = opal_async_get_token_interruptible(); |
|
if (token < 0) |
|
return token; |
|
|
|
ret = opal_sensor_group_enable(handle, token, enable); |
|
if (ret == 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)); |
|
} else { |
|
ret = opal_error_code(ret); |
|
} |
|
|
|
out: |
|
opal_async_release_token(token); |
|
return ret; |
|
} |
|
EXPORT_SYMBOL_GPL(sensor_group_enable); |
|
|
|
static ssize_t sg_store(struct kobject *kobj, struct kobj_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct sg_attr *sattr = container_of(attr, struct sg_attr, attr); |
|
struct opal_msg msg; |
|
u32 data; |
|
int ret, token; |
|
|
|
ret = kstrtoint(buf, 0, &data); |
|
if (ret) |
|
return ret; |
|
|
|
if (data != 1) |
|
return -EINVAL; |
|
|
|
token = opal_async_get_token_interruptible(); |
|
if (token < 0) { |
|
pr_devel("Failed to get token\n"); |
|
return token; |
|
} |
|
|
|
ret = mutex_lock_interruptible(&sg_mutex); |
|
if (ret) |
|
goto out_token; |
|
|
|
ret = opal_sensor_group_clear(sattr->handle, token); |
|
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(&sg_mutex); |
|
out_token: |
|
opal_async_release_token(token); |
|
return ret; |
|
} |
|
|
|
static struct sg_ops_info { |
|
int opal_no; |
|
const char *attr_name; |
|
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, |
|
const char *buf, size_t count); |
|
} ops_info[] = { |
|
{ OPAL_SENSOR_GROUP_CLEAR, "clear", sg_store }, |
|
}; |
|
|
|
static void add_attr(int handle, struct sg_attr *attr, int index) |
|
{ |
|
attr->handle = handle; |
|
sysfs_attr_init(&attr->attr.attr); |
|
attr->attr.attr.name = ops_info[index].attr_name; |
|
attr->attr.attr.mode = 0220; |
|
attr->attr.store = ops_info[index].store; |
|
} |
|
|
|
static int add_attr_group(const __be32 *ops, int len, struct sensor_group *sg, |
|
u32 handle) |
|
{ |
|
int i, j; |
|
int count = 0; |
|
|
|
for (i = 0; i < len; i++) |
|
for (j = 0; j < ARRAY_SIZE(ops_info); j++) |
|
if (be32_to_cpu(ops[i]) == ops_info[j].opal_no) { |
|
add_attr(handle, &sg->sgattrs[count], j); |
|
sg->sg.attrs[count] = |
|
&sg->sgattrs[count].attr.attr; |
|
count++; |
|
} |
|
|
|
return sysfs_create_group(sg_kobj, &sg->sg); |
|
} |
|
|
|
static int get_nr_attrs(const __be32 *ops, int len) |
|
{ |
|
int i, j; |
|
int nr_attrs = 0; |
|
|
|
for (i = 0; i < len; i++) |
|
for (j = 0; j < ARRAY_SIZE(ops_info); j++) |
|
if (be32_to_cpu(ops[i]) == ops_info[j].opal_no) |
|
nr_attrs++; |
|
|
|
return nr_attrs; |
|
} |
|
|
|
void __init opal_sensor_groups_init(void) |
|
{ |
|
struct device_node *sg, *node; |
|
int i = 0; |
|
|
|
sg = of_find_compatible_node(NULL, NULL, "ibm,opal-sensor-group"); |
|
if (!sg) { |
|
pr_devel("Sensor groups node not found\n"); |
|
return; |
|
} |
|
|
|
sgs = kcalloc(of_get_child_count(sg), sizeof(*sgs), GFP_KERNEL); |
|
if (!sgs) |
|
return; |
|
|
|
sg_kobj = kobject_create_and_add("sensor_groups", opal_kobj); |
|
if (!sg_kobj) { |
|
pr_warn("Failed to create sensor group kobject\n"); |
|
goto out_sgs; |
|
} |
|
|
|
for_each_child_of_node(sg, node) { |
|
const __be32 *ops; |
|
u32 sgid, len, nr_attrs, chipid; |
|
|
|
ops = of_get_property(node, "ops", &len); |
|
if (!ops) |
|
continue; |
|
|
|
nr_attrs = get_nr_attrs(ops, len); |
|
if (!nr_attrs) |
|
continue; |
|
|
|
sgs[i].sgattrs = kcalloc(nr_attrs, sizeof(*sgs[i].sgattrs), |
|
GFP_KERNEL); |
|
if (!sgs[i].sgattrs) |
|
goto out_sgs_sgattrs; |
|
|
|
sgs[i].sg.attrs = kcalloc(nr_attrs + 1, |
|
sizeof(*sgs[i].sg.attrs), |
|
GFP_KERNEL); |
|
|
|
if (!sgs[i].sg.attrs) { |
|
kfree(sgs[i].sgattrs); |
|
goto out_sgs_sgattrs; |
|
} |
|
|
|
if (of_property_read_u32(node, "sensor-group-id", &sgid)) { |
|
pr_warn("sensor-group-id property not found\n"); |
|
goto out_sgs_sgattrs; |
|
} |
|
|
|
if (!of_property_read_u32(node, "ibm,chip-id", &chipid)) |
|
sprintf(sgs[i].name, "%pOFn%d", node, chipid); |
|
else |
|
sprintf(sgs[i].name, "%pOFn", node); |
|
|
|
sgs[i].sg.name = sgs[i].name; |
|
if (add_attr_group(ops, len, &sgs[i], sgid)) { |
|
pr_warn("Failed to create sensor attribute group %s\n", |
|
sgs[i].sg.name); |
|
goto out_sgs_sgattrs; |
|
} |
|
i++; |
|
} |
|
|
|
return; |
|
|
|
out_sgs_sgattrs: |
|
while (--i >= 0) { |
|
kfree(sgs[i].sgattrs); |
|
kfree(sgs[i].sg.attrs); |
|
} |
|
kobject_put(sg_kobj); |
|
out_sgs: |
|
kfree(sgs); |
|
}
|
|
|