root/arch/powerpc/platforms/powernv/opal-sensor-groups.c
// 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 = kzalloc_objs(*sgs, of_get_child_count(sg));
        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 = kzalloc_objs(*sgs[i].sgattrs, nr_attrs);
                if (!sgs[i].sgattrs)
                        goto out_sgs_sgattrs;

                sgs[i].sg.attrs = kzalloc_objs(*sgs[i].sg.attrs, nr_attrs + 1);

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