// 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 __init 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 __init 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)
		goto out_sg_put;

	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++;
	}
	of_node_put(sg);

	return;

out_sgs_sgattrs:
	while (--i >= 0) {
		kfree(sgs[i].sgattrs);
		kfree(sgs[i].sg.attrs);
	}
	kobject_put(sg_kobj);
	of_node_put(node);
out_sgs:
	kfree(sgs);
out_sg_put:
	of_node_put(sg);
}