|
@@ -0,0 +1,212 @@
|
|
|
+/*
|
|
|
+ * PowerNV OPAL Sensor-groups interface
|
|
|
+ *
|
|
|
+ * Copyright 2017 IBM Corp.
|
|
|
+ *
|
|
|
+ * This program is free software; you can redistribute it and/or
|
|
|
+ * modify it under the terms of the GNU General Public License
|
|
|
+ * as published by the Free Software Foundation; either version
|
|
|
+ * 2 of the License, or (at your option) any later version.
|
|
|
+ */
|
|
|
+
|
|
|
+#define pr_fmt(fmt) "opal-sensor-groups: " fmt
|
|
|
+
|
|
|
+#include <linux/of.h>
|
|
|
+#include <linux/kobject.h>
|
|
|
+#include <linux/slab.h>
|
|
|
+
|
|
|
+#include <asm/opal.h>
|
|
|
+
|
|
|
+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;
|
|
|
+
|
|
|
+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(struct sg_attr),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!sgs[i].sgattrs)
|
|
|
+ goto out_sgs_sgattrs;
|
|
|
+
|
|
|
+ sgs[i].sg.attrs = kcalloc(nr_attrs + 1,
|
|
|
+ sizeof(struct attribute *),
|
|
|
+ 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, "%s%d", node->name, chipid);
|
|
|
+ else
|
|
|
+ sprintf(sgs[i].name, "%s", node->name);
|
|
|
+
|
|
|
+ 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);
|
|
|
+}
|