[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