18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * PowerNV OPAL Sensor-groups interface
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2017 IBM Corp.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#define pr_fmt(fmt)     "opal-sensor-groups: " 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(sg_mutex);
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistatic struct kobject *sg_kobj;
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistruct sg_attr {
218c2ecf20Sopenharmony_ci	u32 handle;
228c2ecf20Sopenharmony_ci	struct kobj_attribute attr;
238c2ecf20Sopenharmony_ci};
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic struct sensor_group {
268c2ecf20Sopenharmony_ci	char name[20];
278c2ecf20Sopenharmony_ci	struct attribute_group sg;
288c2ecf20Sopenharmony_ci	struct sg_attr *sgattrs;
298c2ecf20Sopenharmony_ci} *sgs;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ciint sensor_group_enable(u32 handle, bool enable)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	struct opal_msg msg;
348c2ecf20Sopenharmony_ci	int token, ret;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	token = opal_async_get_token_interruptible();
378c2ecf20Sopenharmony_ci	if (token < 0)
388c2ecf20Sopenharmony_ci		return token;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	ret = opal_sensor_group_enable(handle, token, enable);
418c2ecf20Sopenharmony_ci	if (ret == OPAL_ASYNC_COMPLETION) {
428c2ecf20Sopenharmony_ci		ret = opal_async_wait_response(token, &msg);
438c2ecf20Sopenharmony_ci		if (ret) {
448c2ecf20Sopenharmony_ci			pr_devel("Failed to wait for the async response\n");
458c2ecf20Sopenharmony_ci			ret = -EIO;
468c2ecf20Sopenharmony_ci			goto out;
478c2ecf20Sopenharmony_ci		}
488c2ecf20Sopenharmony_ci		ret = opal_error_code(opal_get_async_rc(msg));
498c2ecf20Sopenharmony_ci	} else {
508c2ecf20Sopenharmony_ci		ret = opal_error_code(ret);
518c2ecf20Sopenharmony_ci	}
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ciout:
548c2ecf20Sopenharmony_ci	opal_async_release_token(token);
558c2ecf20Sopenharmony_ci	return ret;
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(sensor_group_enable);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic ssize_t sg_store(struct kobject *kobj, struct kobj_attribute *attr,
608c2ecf20Sopenharmony_ci			const char *buf, size_t count)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	struct sg_attr *sattr = container_of(attr, struct sg_attr, attr);
638c2ecf20Sopenharmony_ci	struct opal_msg msg;
648c2ecf20Sopenharmony_ci	u32 data;
658c2ecf20Sopenharmony_ci	int ret, token;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	ret = kstrtoint(buf, 0, &data);
688c2ecf20Sopenharmony_ci	if (ret)
698c2ecf20Sopenharmony_ci		return ret;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	if (data != 1)
728c2ecf20Sopenharmony_ci		return -EINVAL;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	token = opal_async_get_token_interruptible();
758c2ecf20Sopenharmony_ci	if (token < 0) {
768c2ecf20Sopenharmony_ci		pr_devel("Failed to get token\n");
778c2ecf20Sopenharmony_ci		return token;
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	ret = mutex_lock_interruptible(&sg_mutex);
818c2ecf20Sopenharmony_ci	if (ret)
828c2ecf20Sopenharmony_ci		goto out_token;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	ret = opal_sensor_group_clear(sattr->handle, token);
858c2ecf20Sopenharmony_ci	switch (ret) {
868c2ecf20Sopenharmony_ci	case OPAL_ASYNC_COMPLETION:
878c2ecf20Sopenharmony_ci		ret = opal_async_wait_response(token, &msg);
888c2ecf20Sopenharmony_ci		if (ret) {
898c2ecf20Sopenharmony_ci			pr_devel("Failed to wait for the async response\n");
908c2ecf20Sopenharmony_ci			ret = -EIO;
918c2ecf20Sopenharmony_ci			goto out;
928c2ecf20Sopenharmony_ci		}
938c2ecf20Sopenharmony_ci		ret = opal_error_code(opal_get_async_rc(msg));
948c2ecf20Sopenharmony_ci		if (!ret)
958c2ecf20Sopenharmony_ci			ret = count;
968c2ecf20Sopenharmony_ci		break;
978c2ecf20Sopenharmony_ci	case OPAL_SUCCESS:
988c2ecf20Sopenharmony_ci		ret = count;
998c2ecf20Sopenharmony_ci		break;
1008c2ecf20Sopenharmony_ci	default:
1018c2ecf20Sopenharmony_ci		ret = opal_error_code(ret);
1028c2ecf20Sopenharmony_ci	}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ciout:
1058c2ecf20Sopenharmony_ci	mutex_unlock(&sg_mutex);
1068c2ecf20Sopenharmony_ciout_token:
1078c2ecf20Sopenharmony_ci	opal_async_release_token(token);
1088c2ecf20Sopenharmony_ci	return ret;
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic struct sg_ops_info {
1128c2ecf20Sopenharmony_ci	int opal_no;
1138c2ecf20Sopenharmony_ci	const char *attr_name;
1148c2ecf20Sopenharmony_ci	ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
1158c2ecf20Sopenharmony_ci			const char *buf, size_t count);
1168c2ecf20Sopenharmony_ci} ops_info[] = {
1178c2ecf20Sopenharmony_ci	{ OPAL_SENSOR_GROUP_CLEAR, "clear", sg_store },
1188c2ecf20Sopenharmony_ci};
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic void add_attr(int handle, struct sg_attr *attr, int index)
1218c2ecf20Sopenharmony_ci{
1228c2ecf20Sopenharmony_ci	attr->handle = handle;
1238c2ecf20Sopenharmony_ci	sysfs_attr_init(&attr->attr.attr);
1248c2ecf20Sopenharmony_ci	attr->attr.attr.name = ops_info[index].attr_name;
1258c2ecf20Sopenharmony_ci	attr->attr.attr.mode = 0220;
1268c2ecf20Sopenharmony_ci	attr->attr.store = ops_info[index].store;
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cistatic int add_attr_group(const __be32 *ops, int len, struct sensor_group *sg,
1308c2ecf20Sopenharmony_ci			   u32 handle)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	int i, j;
1338c2ecf20Sopenharmony_ci	int count = 0;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	for (i = 0; i < len; i++)
1368c2ecf20Sopenharmony_ci		for (j = 0; j < ARRAY_SIZE(ops_info); j++)
1378c2ecf20Sopenharmony_ci			if (be32_to_cpu(ops[i]) == ops_info[j].opal_no) {
1388c2ecf20Sopenharmony_ci				add_attr(handle, &sg->sgattrs[count], j);
1398c2ecf20Sopenharmony_ci				sg->sg.attrs[count] =
1408c2ecf20Sopenharmony_ci					&sg->sgattrs[count].attr.attr;
1418c2ecf20Sopenharmony_ci				count++;
1428c2ecf20Sopenharmony_ci			}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	return sysfs_create_group(sg_kobj, &sg->sg);
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_cistatic int get_nr_attrs(const __be32 *ops, int len)
1488c2ecf20Sopenharmony_ci{
1498c2ecf20Sopenharmony_ci	int i, j;
1508c2ecf20Sopenharmony_ci	int nr_attrs = 0;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	for (i = 0; i < len; i++)
1538c2ecf20Sopenharmony_ci		for (j = 0; j < ARRAY_SIZE(ops_info); j++)
1548c2ecf20Sopenharmony_ci			if (be32_to_cpu(ops[i]) == ops_info[j].opal_no)
1558c2ecf20Sopenharmony_ci				nr_attrs++;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	return nr_attrs;
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_civoid __init opal_sensor_groups_init(void)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	struct device_node *sg, *node;
1638c2ecf20Sopenharmony_ci	int i = 0;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	sg = of_find_compatible_node(NULL, NULL, "ibm,opal-sensor-group");
1668c2ecf20Sopenharmony_ci	if (!sg) {
1678c2ecf20Sopenharmony_ci		pr_devel("Sensor groups node not found\n");
1688c2ecf20Sopenharmony_ci		return;
1698c2ecf20Sopenharmony_ci	}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	sgs = kcalloc(of_get_child_count(sg), sizeof(*sgs), GFP_KERNEL);
1728c2ecf20Sopenharmony_ci	if (!sgs)
1738c2ecf20Sopenharmony_ci		return;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	sg_kobj = kobject_create_and_add("sensor_groups", opal_kobj);
1768c2ecf20Sopenharmony_ci	if (!sg_kobj) {
1778c2ecf20Sopenharmony_ci		pr_warn("Failed to create sensor group kobject\n");
1788c2ecf20Sopenharmony_ci		goto out_sgs;
1798c2ecf20Sopenharmony_ci	}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	for_each_child_of_node(sg, node) {
1828c2ecf20Sopenharmony_ci		const __be32 *ops;
1838c2ecf20Sopenharmony_ci		u32 sgid, len, nr_attrs, chipid;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci		ops = of_get_property(node, "ops", &len);
1868c2ecf20Sopenharmony_ci		if (!ops)
1878c2ecf20Sopenharmony_ci			continue;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci		nr_attrs = get_nr_attrs(ops, len);
1908c2ecf20Sopenharmony_ci		if (!nr_attrs)
1918c2ecf20Sopenharmony_ci			continue;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci		sgs[i].sgattrs = kcalloc(nr_attrs, sizeof(*sgs[i].sgattrs),
1948c2ecf20Sopenharmony_ci					 GFP_KERNEL);
1958c2ecf20Sopenharmony_ci		if (!sgs[i].sgattrs)
1968c2ecf20Sopenharmony_ci			goto out_sgs_sgattrs;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci		sgs[i].sg.attrs = kcalloc(nr_attrs + 1,
1998c2ecf20Sopenharmony_ci					  sizeof(*sgs[i].sg.attrs),
2008c2ecf20Sopenharmony_ci					  GFP_KERNEL);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci		if (!sgs[i].sg.attrs) {
2038c2ecf20Sopenharmony_ci			kfree(sgs[i].sgattrs);
2048c2ecf20Sopenharmony_ci			goto out_sgs_sgattrs;
2058c2ecf20Sopenharmony_ci		}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci		if (of_property_read_u32(node, "sensor-group-id", &sgid)) {
2088c2ecf20Sopenharmony_ci			pr_warn("sensor-group-id property not found\n");
2098c2ecf20Sopenharmony_ci			goto out_sgs_sgattrs;
2108c2ecf20Sopenharmony_ci		}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci		if (!of_property_read_u32(node, "ibm,chip-id", &chipid))
2138c2ecf20Sopenharmony_ci			sprintf(sgs[i].name, "%pOFn%d", node, chipid);
2148c2ecf20Sopenharmony_ci		else
2158c2ecf20Sopenharmony_ci			sprintf(sgs[i].name, "%pOFn", node);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci		sgs[i].sg.name = sgs[i].name;
2188c2ecf20Sopenharmony_ci		if (add_attr_group(ops, len, &sgs[i], sgid)) {
2198c2ecf20Sopenharmony_ci			pr_warn("Failed to create sensor attribute group %s\n",
2208c2ecf20Sopenharmony_ci				sgs[i].sg.name);
2218c2ecf20Sopenharmony_ci			goto out_sgs_sgattrs;
2228c2ecf20Sopenharmony_ci		}
2238c2ecf20Sopenharmony_ci		i++;
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	return;
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ciout_sgs_sgattrs:
2298c2ecf20Sopenharmony_ci	while (--i >= 0) {
2308c2ecf20Sopenharmony_ci		kfree(sgs[i].sgattrs);
2318c2ecf20Sopenharmony_ci		kfree(sgs[i].sg.attrs);
2328c2ecf20Sopenharmony_ci	}
2338c2ecf20Sopenharmony_ci	kobject_put(sg_kobj);
2348c2ecf20Sopenharmony_ciout_sgs:
2358c2ecf20Sopenharmony_ci	kfree(sgs);
2368c2ecf20Sopenharmony_ci}
237