[PATCH linux dev-4.10 3/3] drivers: hwmon: OCC: Add P9 sensors, hwmon attributes, and device setup

Eddie James eajames at linux.vnet.ibm.com
Sat May 27 03:46:33 AEST 2017


From: "Edward A. James" <eajames at us.ibm.com>

Add P9 sysfs functions, sensor definitions, and hwmon attribute setup.
Add device setup after probe. Use a delay to make sure SBE driver has
finished setting up.

Signed-off-by: Edward A. James <eajames at us.ibm.com>
---
 drivers/hwmon/occ/Makefile |   2 +-
 drivers/hwmon/occ/common.h |  30 ++++
 drivers/hwmon/occ/p9.c     | 350 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/hwmon/occ/p9.h     |  17 +++
 drivers/hwmon/occ/p9_sbe.c |  53 +++++++
 5 files changed, 451 insertions(+), 1 deletion(-)
 create mode 100644 drivers/hwmon/occ/p9.c
 create mode 100644 drivers/hwmon/occ/p9.h

diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile
index 7eab78f..adc7a9c 100644
--- a/drivers/hwmon/occ/Makefile
+++ b/drivers/hwmon/occ/Makefile
@@ -1,7 +1,7 @@
 occ-hwmon-objs := common.o
 
 ifeq ($(CONFIG_SENSORS_OCC_P9_SBE), y)
-occ-hwmon-objs += p9_sbe.o
+occ-hwmon-objs += p9.o p9_sbe.o
 endif
 
 obj-$(CONFIG_SENSORS_OCC) += occ-hwmon.o
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h
index 8d07878..a3d733a 100644
--- a/drivers/hwmon/occ/common.h
+++ b/drivers/hwmon/occ/common.h
@@ -73,8 +73,14 @@ struct occ_sensors {
 	struct occ_sensor caps;
 };
 
+struct occ_attribute {
+	char name[32];
+	struct sensor_device_attribute_2 sensor;
+};
+
 struct occ {
 	struct device *bus_dev;
+	struct device *hwmon;
 
 	unsigned long last_update;
 	struct mutex lock;
@@ -82,10 +88,34 @@ struct occ {
 	struct occ_response resp;
 	struct occ_sensors sensors;
 
+	unsigned int num_attrs;
+	struct occ_attribute *attrs;
+	struct attribute_group group;
+	const struct attribute_group *groups[2];
+
 	u8 poll_cmd_data;
 	int (*send_cmd)(struct occ *occ, u8 *cmd);
 };
 
+#define ATTR_OCC(_name, _mode, _show, _store) {				\
+	.attr	= {							\
+		.name = _name,						\
+		.mode = VERIFY_OCTAL_PERMISSIONS(_mode),		\
+	},								\
+	.show	= _show,						\
+	.store	= _store,						\
+}
+
+#define SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index) {	\
+	.dev_attr	= ATTR_OCC(_name, _mode, _show, _store),	\
+	.index		= _index,					\
+	.nr		= _nr,						\
+}
+
+#define OCC_INIT_ATTR(_name, _mode, _show, _store, _nr, _index)		\
+	((struct sensor_device_attribute_2)				\
+		SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index))
+
 void occ_parse_poll_response(struct occ *occ);
 int occ_poll(struct occ *occ);
 int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap);
diff --git a/drivers/hwmon/occ/p9.c b/drivers/hwmon/occ/p9.c
new file mode 100644
index 0000000..832d6ff
--- /dev/null
+++ b/drivers/hwmon/occ/p9.c
@@ -0,0 +1,350 @@
+/*
+ * 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.
+ */
+
+#include <asm/unaligned.h>
+#include "common.h"
+
+struct p9_temp_sensor {
+	u32 sensor_id;
+	u8 fru_type;
+	u8 value;
+} __packed;
+
+struct p9_freq_sensor {
+	u32 sensor_id;
+	u16 value;
+} __packed;
+
+struct p9_power_sensor {
+	u32 sensor_id;
+	u8 function_id;
+	u8 apss_channel;
+	u16 reserved;
+	u32 update_tag;
+	u64 accumulator;
+	u16 value;
+} __packed;
+
+struct p9_caps_sensor {
+	u16 curr_powercap;
+	u16 curr_powerreading;
+	u16 norm_powercap;
+	u16 max_powercap;
+	u16 min_powercap;
+	u16 user_powerlimit;
+	u8 user_powerlimit_source;
+} __packed;
+
+static ssize_t p9_occ_show_temp(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	int rc;
+	u32 val = 0;
+	struct p9_temp_sensor *temp;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	temp = ((struct p9_temp_sensor *)sensors->temp.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = be32_to_cpu(get_unaligned(&temp->sensor_id));
+		break;
+	case 1:
+		val = temp->fru_type;
+		break;
+	case 2:
+		/* millidegree */
+		val = temp->value * 1000;
+		break;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+static ssize_t p9_occ_show_freq(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	int rc;
+	u32 val = 0;
+	struct p9_freq_sensor *freq;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	freq = ((struct p9_freq_sensor *)sensors->freq.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = be32_to_cpu(get_unaligned(&freq->sensor_id));
+		break;
+	case 1:
+		val = be16_to_cpu(get_unaligned(&freq->value));
+		break;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+static ssize_t p9_occ_show_power(struct device *dev,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	int rc;
+	u64 val = 0;
+	struct p9_power_sensor *power;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	power = ((struct p9_power_sensor *)sensors->power.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = be32_to_cpu(get_unaligned(&power->sensor_id));
+		break;
+	case 1:
+		val = power->function_id;
+		break;
+	case 2:
+		val = power->apss_channel;
+		break;
+	case 3:
+		val = be32_to_cpu(get_unaligned(&power->update_tag));
+		break;
+	case 4:
+		val = be64_to_cpu(get_unaligned(&power->accumulator));
+		break;
+	case 5:
+		val = be16_to_cpu(get_unaligned(&power->value));
+		break;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val);
+}
+
+static ssize_t p9_occ_show_caps(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	int rc;
+	u16 val = 0;
+	struct p9_caps_sensor *caps;
+	struct occ *occ = dev_get_drvdata(dev);
+	struct occ_sensors *sensors = &occ->sensors;
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+
+	rc = occ_update_response(occ);
+	if (rc)
+		return rc;
+
+	caps = ((struct p9_caps_sensor *)sensors->caps.data) + sattr->index;
+
+	switch (sattr->nr) {
+	case 0:
+		val = be16_to_cpu(get_unaligned(&caps->curr_powercap));
+		break;
+	case 1:
+		val = be16_to_cpu(get_unaligned(&caps->curr_powerreading));
+		break;
+	case 2:
+		val = be16_to_cpu(get_unaligned(&caps->norm_powercap));
+		break;
+	case 3:
+		val = be16_to_cpu(get_unaligned(&caps->max_powercap));
+		break;
+	case 4:
+		val = be16_to_cpu(get_unaligned(&caps->min_powercap));
+		break;
+	case 5:
+		val = be16_to_cpu(get_unaligned(&caps->user_powerlimit));
+		break;
+	case 6:
+		val = caps->user_powerlimit_source;
+		break;
+	}
+
+	return snprintf(buf, PAGE_SIZE - 1, "%u\n", val);
+}
+
+static ssize_t p9_occ_store_caps_user(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	int rc;
+	u16 user_power_cap;
+	struct occ *occ = dev_get_drvdata(dev);
+
+	rc = kstrtou16(buf, 0, &user_power_cap);
+	if (rc)
+		return rc;
+
+	rc = occ_set_user_power_cap(occ, user_power_cap);
+	if (rc)
+		return rc;
+
+	return count;
+}
+
+int p9_occ_setup_sensor_attrs(struct occ *occ)
+{
+	unsigned int i, s;
+	struct device *dev = occ->bus_dev;
+	struct occ_sensors *sensors = &occ->sensors;
+	struct occ_attribute *attr;
+
+	occ->num_attrs = (sensors->temp.num_sensors * 3);
+	occ->num_attrs += (sensors->freq.num_sensors * 2);
+	occ->num_attrs += (sensors->power.num_sensors * 6);
+	occ->num_attrs += (sensors->caps.num_sensors * 7);
+
+	occ->attrs = devm_kzalloc(dev, sizeof(*occ->attrs) * occ->num_attrs,
+				  GFP_KERNEL);
+	if (!occ->attrs)
+		return -ENOMEM;
+
+	occ->group.attrs = devm_kzalloc(dev, sizeof(*occ->group.attrs) *
+					occ->num_attrs + 1, GFP_KERNEL);
+	if (!occ->group.attrs)
+		return -ENOMEM;
+
+	attr = occ->attrs;
+	for (i = 0; i < sensors->temp.num_sensors; ++i) {
+		s = i + 1;
+
+		snprintf(attr->name, sizeof(attr->name), "temp%d_label", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_temp, NULL, 0, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "temp%d_fru_type", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_temp, NULL, 1, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "temp%d_input", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_temp, NULL, 2, i);
+		attr++;
+	}
+
+	for (i = 0; i < sensors->freq.num_sensors; ++i) {
+		s = i + 1;
+
+		snprintf(attr->name, sizeof(attr->name), "freq%d_label", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_freq, NULL, 0, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "freq%d_input", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_freq, NULL, 1, i);
+		attr++;
+	}
+
+	for (i = 0; i < sensors->power.num_sensors; ++i) {
+		s = i + 1;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_label", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_power, NULL, 0, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_function_id",
+			 s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_power, NULL, 1, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name),
+			 "power%d_apss_channel", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_power, NULL, 2, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_update_tag",
+			 s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_power, NULL, 3, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_accumulator",
+			 s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_power, NULL, 4, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "power%d_input", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_power, NULL, 5, i);
+		attr++;
+	}
+
+	for (i = 0; i < sensors->caps.num_sensors; ++i) {
+		s = i + 1;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_curr", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_caps, NULL, 0, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_reading", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_caps, NULL, 1, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_norm", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_caps, NULL, 2, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_max", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_caps, NULL, 3, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_min", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_caps, NULL, 4, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_user", s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0644,
+					     p9_occ_show_caps,
+					     p9_occ_store_caps_user, 5, i);
+		attr++;
+
+		snprintf(attr->name, sizeof(attr->name), "caps%d_user_source",
+			 s);
+		attr->sensor = OCC_INIT_ATTR(attr->name, 0444,
+					     p9_occ_show_caps, NULL, 6, i);
+		attr++;
+	}
+
+	/* put the sensors in the group */
+	for (i = 0; i < occ->num_attrs; ++i)
+		occ->group.attrs[i] = &occ->attrs[i].sensor.dev_attr.attr;
+
+	return 0;
+}
diff --git a/drivers/hwmon/occ/p9.h b/drivers/hwmon/occ/p9.h
new file mode 100644
index 0000000..12d1bc5
--- /dev/null
+++ b/drivers/hwmon/occ/p9.h
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+#ifndef __OCC_P9_H__
+#define __OCC_P9_H__
+
+struct occ;
+
+int p9_occ_setup_sensor_attrs(struct occ *occ);
+
+#endif /* __OCC_P9_H__ */
diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c
index 889594c..88f71b1 100644
--- a/drivers/hwmon/occ/p9_sbe.c
+++ b/drivers/hwmon/occ/p9_sbe.c
@@ -15,6 +15,9 @@
 #include <linux/occ.h>
 #include <linux/sched.h>
 #include <linux/workqueue.h>
+#include "p9.h"
+
+#define P9_SBE_OCC_SETUP_DELAY		2500
 
 #define OCC_TIMEOUT_MS			5000
 #define OCC_CMD_IN_PRG_MS		100
@@ -30,6 +33,7 @@
 
 struct p9_sbe_occ {
 	struct occ occ;
+	struct delayed_work setup;
 	struct device *sbe;
 };
 
@@ -101,6 +105,40 @@ static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd)
 	return rc;
 }
 
+static void p9_sbe_occ_setup(struct work_struct *work)
+{
+	int rc;
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct p9_sbe_occ *p9_sbe_occ = container_of(dwork, struct p9_sbe_occ,
+						     setup);
+	struct occ *occ = &p9_sbe_occ->occ;
+
+	/* no need to lock */
+	rc = occ_poll(occ);
+	if (rc < 0) {
+		dev_err(occ->bus_dev, "failed to get OCC poll response: %d\n",
+			rc);
+		return;
+	}
+
+	occ_parse_poll_response(occ);
+
+	rc = p9_occ_setup_sensor_attrs(occ);
+	if (rc) {
+		dev_err(occ->bus_dev, "failed to setup p9 attrs: %d\n", rc);
+		return;
+	}
+
+	occ->hwmon = devm_hwmon_device_register_with_groups(occ->bus_dev,
+							    "p9_occ", occ,
+							    occ->groups);
+	if (IS_ERR(occ->hwmon)) {
+		dev_err(occ->bus_dev, "failed to register hwmon device: %ld\n",
+			PTR_ERR(occ->hwmon));
+		return;
+	}
+}
+
 static int p9_sbe_occ_probe(struct platform_device *pdev)
 {
 	struct occ *occ;
@@ -114,11 +152,25 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
 
 	occ = &p9_sbe_occ->occ;
 	occ->bus_dev = &pdev->dev;
+	occ->groups[0] = &occ->group;
 	occ->poll_cmd_data = 0x20;
 	occ->send_cmd = p9_sbe_occ_send_cmd;
 	mutex_init(&occ->lock);
+	INIT_DELAYED_WORK(&p9_sbe_occ->setup, p9_sbe_occ_setup);
 	platform_set_drvdata(pdev, p9_sbe_occ);
 
+	schedule_delayed_work(&p9_sbe_occ->setup,
+			      msecs_to_jiffies(P9_SBE_OCC_SETUP_DELAY));
+
+	return 0;
+}
+
+static int p9_sbe_occ_remove(struct platform_device *pdev)
+{
+	struct p9_sbe_occ *p9_sbe_occ = platform_get_drvdata(pdev);
+
+	cancel_delayed_work_sync(&p9_sbe_occ->setup);
+
 	return 0;
 }
 
@@ -133,6 +185,7 @@ static int p9_sbe_occ_probe(struct platform_device *pdev)
 		.of_match_table	= p9_sbe_occ_of_match,
 	},
 	.probe	= p9_sbe_occ_probe,
+	.remove = p9_sbe_occ_remove,
 };
 
 module_platform_driver(p9_sbe_occ_driver);
-- 
1.8.3.1



More information about the openbmc mailing list