]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/commitdiff
powerpc/powernv: hwmon driver for power values, fan rpm and temperature
authorShivaprasad G Bhat <sbhat@linux.vnet.ibm.com>
Fri, 7 Mar 2014 05:33:39 +0000 (11:03 +0530)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Sun, 23 Mar 2014 22:48:22 +0000 (09:48 +1100)
This patch adds basic kernel enablement for reading power values, fan
speed rpm and temperature values on powernv platforms which will
be exported to user space through sysfs interface.

Signed-off-by: Shivaprasad G Bhat <sbhat@linux.vnet.ibm.com>
Signed-off-by: Neelesh Gupta <neelegup@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/ibmpowernv.c [new file with mode: 0644]

index 5ce43d8dfa98e635f039eaad29b5ec4627557b78..ad4cdcbc618bc527265fcad6f442af0deb985223 100644 (file)
@@ -557,6 +557,14 @@ config SENSORS_IBMPEX
          This driver can also be built as a module.  If so, the module
          will be called ibmpex.
 
+config SENSORS_IBMPOWERNV
+       tristate "IBM PowerNv Platform temperature/power/fan sensor"
+       depends on PPC_POWERNV
+       default y
+       help
+         If you say yes here you get support for the temperature/fan/power
+         sensors on your platform.
+
 config SENSORS_IIO_HWMON
        tristate "Hwmon driver that uses channels specified via iio maps"
        depends on IIO
index ec7cde06eb52bb628e1eb67d661d3d652ee91d97..807e1721ed7956bac9d48ffdd462d818762bfb57 100644 (file)
@@ -70,6 +70,7 @@ obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
 obj-$(CONFIG_SENSORS_I5K_AMB)  += i5k_amb.o
 obj-$(CONFIG_SENSORS_IBMAEM)   += ibmaem.o
 obj-$(CONFIG_SENSORS_IBMPEX)   += ibmpex.o
+obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
 obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o
 obj-$(CONFIG_SENSORS_INA209)   += ina209.o
 obj-$(CONFIG_SENSORS_INA2XX)   += ina2xx.o
diff --git a/drivers/hwmon/ibmpowernv.c b/drivers/hwmon/ibmpowernv.c
new file mode 100644 (file)
index 0000000..b7b1297
--- /dev/null
@@ -0,0 +1,529 @@
+/*
+ * hwmon driver for temperature/power/fan on IBM PowerNV platform
+ * Copyright (C) 2013 IBM
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <asm/opal.h>
+#include <linux/err.h>
+
+MODULE_DESCRIPTION("IBM PowerNV Platform power/temp/fan sensor hwmon module");
+MODULE_LICENSE("GPL");
+
+#define MAX_ATTR_LENGTH                32
+
+/* Device tree sensor name prefixes. The device tree has the names in the
+ * format "cooling-fan#2-faulted" where the "cooling-fan" is the sensor type,
+ * 2 is the sensor count, and "faulted" is the sensor data attribute type.
+ */
+#define DT_FAULT_ATTR_SUFFIX           "faulted"
+#define DT_DATA_ATTR_SUFFIX            "data"
+#define DT_THRESHOLD_ATTR_SUFFIX       "thrs"
+
+enum sensors {
+       FAN,
+       TEMPERATURE,
+       POWERSUPPLY,
+       POWER,
+       MAX_SENSOR_TYPE,
+};
+
+enum attributes {
+       INPUT,
+       MINIMUM,
+       MAXIMUM,
+       FAULT,
+       MAX_ATTR_TYPES
+};
+
+static struct sensor_name {
+       char *name;
+       char *compaible;
+} sensor_names[] = {
+               {"fan-sensor", "ibm,opal-sensor-cooling-fan"},
+               {"amb-temp-sensor", "ibm,opal-sensor-amb-temp"},
+               {"power-sensor", "ibm,opal-sensor-power-supply"},
+               {"power", "ibm,opal-sensor-power"}
+};
+
+static const char * const attribute_type_table[] = {
+       "input",
+       "min",
+       "max",
+       "fault",
+       NULL
+};
+
+struct pdev_entry {
+       struct list_head list;
+       struct platform_device *pdev;
+       enum sensors type;
+};
+
+static LIST_HEAD(pdev_list);
+
+/* The sensors are categorised on type.
+ *
+ * The sensors of same type are categorised under a common platform device.
+ * So, The pdev is shared by all sensors of same type.
+ * Ex : temp1_input, temp1_max, temp2_input,temp2_max all share same platform
+ * device.
+ *
+ * "sensor_data" is the Platform device specific data.
+ * There is one hwmon_device instance for all the sensors of same type.
+ * This also holds the list of all sensors with same type but different
+ * attribute and index.
+ */
+struct sensor_specific_data {
+       u32 sensor_id; /* The hex value as in the device tree */
+       u32 sensor_index; /* The sensor instance index */
+       struct sensor_device_attribute sd_attr;
+       enum attributes attr_type;
+       char attr_name[64];
+};
+
+struct sensor_data {
+       struct device *hwmon_dev;
+       struct list_head sensor_list;
+       struct device_attribute name_attr;
+};
+
+struct  sensor_entry {
+       struct list_head list;
+       struct sensor_specific_data *sensor_data;
+};
+
+static struct platform_device *powernv_sensor_get_pdev(enum sensors type)
+{
+       struct pdev_entry *p;
+       list_for_each_entry(p, &pdev_list, list)
+               if (p->type == type)
+                       return p->pdev;
+
+       return NULL;
+}
+
+static struct sensor_specific_data *powernv_sensor_get_sensor_data(
+                                       struct sensor_data *pdata,
+                                       int index, enum attributes attr_type)
+{
+       struct sensor_entry *p;
+       list_for_each_entry(p, &pdata->sensor_list, list)
+               if ((p->sensor_data->sensor_index == index) &&
+                   (attr_type == p->sensor_data->attr_type))
+                       return p->sensor_data;
+
+       return NULL;
+}
+
+static ssize_t show_name(struct device *dev,
+                               struct device_attribute *devattr, char *buf)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+
+       return sprintf(buf, "%s\n", pdev->name);
+}
+
+/* Note: Data from the sensors for each sensor type needs to be converted to
+ * the dimension appropriate.
+ */
+static ssize_t show_sensor(struct device *dev,
+                               struct device_attribute *devattr, char *buf)
+{
+       struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(devattr);
+       struct platform_device *pdev = to_platform_device(dev);
+       struct sensor_data *pdata = platform_get_drvdata(pdev);
+       struct sensor_specific_data *tdata = NULL;
+       enum sensors sensor_type = pdev->id;
+       u32 x = -1;
+       int ret;
+
+       if (sd_attr && sd_attr->dev_attr.attr.name) {
+               char *pos = strchr(sd_attr->dev_attr.attr.name, '_');
+               int i;
+
+               for (i = 0; i < MAX_ATTR_TYPES; i++) {
+                       if (strcmp(pos+1, attribute_type_table[i]) == 0) {
+                               tdata = powernv_sensor_get_sensor_data(pdata,
+                                               sd_attr->index, i);
+                               break;
+                       }
+               }
+       }
+
+       if (tdata) {
+               ret = opal_get_sensor_data(tdata->sensor_id, &x);
+               if (ret)
+                       x = -1;
+       }
+
+       if (sensor_type == TEMPERATURE && x > 0) {
+               /* Temperature comes in Degrees and convert it to
+                * milli-degrees.
+                */
+               x = x*1000;
+       } else if (sensor_type == POWER && x > 0) {
+               /* Power value comes in watts, convert to micro-watts */
+               x = x * 1000000;
+       }
+
+       return sprintf(buf, "%d\n", x);
+}
+
+static u32 get_sensor_index_from_name(const char *name)
+{
+       char *hash_position = strchr(name, '#');
+       u32 index = 0, copy_length;
+       char newbuf[8];
+
+       if (hash_position) {
+               copy_length = strchr(hash_position, '-') - hash_position - 1;
+               if (copy_length < sizeof(newbuf)) {
+                       strncpy(newbuf, hash_position + 1, copy_length);
+                       sscanf(newbuf, "%d", &index);
+               }
+       }
+
+       return index;
+}
+
+static inline void get_sensor_suffix_from_name(const char *name, char *suffix)
+{
+       char *dash_position = strrchr(name, '-');
+       if (dash_position)
+               strncpy(suffix, dash_position+1, MAX_ATTR_LENGTH);
+       else
+               strcpy(suffix,"");
+}
+
+static int get_sensor_attr_properties(const char *sensor_name,
+               enum sensors sensor_type, enum attributes *attr_type,
+               u32 *sensor_index)
+{
+       char suffix[MAX_ATTR_LENGTH];
+
+       *attr_type = MAX_ATTR_TYPES;
+       *sensor_index = get_sensor_index_from_name(sensor_name);
+       if (*sensor_index == 0)
+               return -EINVAL;
+
+       get_sensor_suffix_from_name(sensor_name, suffix);
+       if (strcmp(suffix, "") == 0)
+               return -EINVAL;
+
+       if (strcmp(suffix, DT_FAULT_ATTR_SUFFIX) == 0)
+               *attr_type = FAULT;
+       else if (strcmp(suffix, DT_DATA_ATTR_SUFFIX) == 0)
+               *attr_type = INPUT;
+       else if ((sensor_type == TEMPERATURE) &&
+                       (strcmp(suffix, DT_THRESHOLD_ATTR_SUFFIX) == 0))
+               *attr_type = MAXIMUM;
+       else if ((sensor_type == FAN) &&
+                       (strcmp(suffix, DT_THRESHOLD_ATTR_SUFFIX) == 0))
+               *attr_type = MINIMUM;
+       else
+               return -ENOENT;
+
+       if (((sensor_type == FAN) && ((*attr_type == INPUT) ||
+                                   (*attr_type == MINIMUM)))
+           || ((sensor_type == TEMPERATURE) && ((*attr_type == INPUT) ||
+                                                (*attr_type == MAXIMUM)))
+           || ((sensor_type == POWER) && ((*attr_type == INPUT))))
+               return 0;
+
+       return -ENOENT;
+}
+
+static int create_sensor_attr(struct sensor_specific_data *tdata,
+               struct device *dev, enum sensors sensor_type,
+               enum attributes attr_type)
+{
+       int err = 0;
+       char temp_file_prefix[50];
+       static const char *const file_name_format = "%s%d_%s";
+
+       tdata->attr_type = attr_type;
+
+       if (sensor_type == FAN)
+               strcpy(temp_file_prefix, "fan");
+       else if (sensor_type == TEMPERATURE)
+               strcpy(temp_file_prefix, "temp");
+       else if (sensor_type == POWERSUPPLY)
+               strcpy(temp_file_prefix, "powersupply");
+       else if (sensor_type == POWER)
+               strcpy(temp_file_prefix, "power");
+
+       snprintf(tdata->attr_name, sizeof(tdata->attr_name), file_name_format,
+                temp_file_prefix, tdata->sensor_index,
+                attribute_type_table[tdata->attr_type]);
+
+       sysfs_attr_init(&tdata->sd_attr.dev_attr.attr);
+       tdata->sd_attr.dev_attr.attr.name = tdata->attr_name;
+       tdata->sd_attr.dev_attr.attr.mode = S_IRUGO;
+       tdata->sd_attr.dev_attr.show = show_sensor;
+
+       tdata->sd_attr.index = tdata->sensor_index;
+       err = device_create_file(dev, &tdata->sd_attr.dev_attr);
+
+       return err;
+}
+
+static int create_name_attr(struct sensor_data *pdata,
+                               struct device *dev)
+{
+       sysfs_attr_init(&pdata->name_attr.attr);
+       pdata->name_attr.attr.name = "name";
+       pdata->name_attr.attr.mode = S_IRUGO;
+       pdata->name_attr.show = show_name;
+       return device_create_file(dev, &pdata->name_attr);
+}
+
+static int create_platform_device(enum sensors sensor_type,
+                                       struct platform_device **pdev)
+{
+       struct pdev_entry *pdev_entry = NULL;
+       int err;
+
+       *pdev = platform_device_alloc(sensor_names[sensor_type].name,
+                       sensor_type);
+       if (!*pdev) {
+               pr_err("Device allocation failed\n");
+               err = -ENOMEM;
+               goto exit;
+       }
+
+       pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL);
+       if (!pdev_entry) {
+               pr_err("Device allocation failed\n");
+               err = -ENOMEM;
+               goto exit_device_put;
+       }
+
+       err = platform_device_add(*pdev);
+       if (err) {
+               pr_err("Device addition failed (%d)\n", err);
+               goto exit_device_free;
+       }
+
+       pdev_entry->pdev = *pdev;
+       pdev_entry->type = (*pdev)->id;
+
+       list_add_tail(&pdev_entry->list, &pdev_list);
+
+       return 0;
+exit_device_free:
+       kfree(pdev_entry);
+exit_device_put:
+       platform_device_put(*pdev);
+exit:
+       return err;
+}
+
+static int create_sensor_data(struct platform_device *pdev)
+{
+       struct sensor_data *pdata = NULL;
+       int err = 0;
+
+       pdata = kzalloc(sizeof(struct sensor_data), GFP_KERNEL);
+       if (!pdata) {
+               err = -ENOMEM;
+               goto exit;
+       }
+
+       err = create_name_attr(pdata, &pdev->dev);
+       if (err)
+               goto exit_free;
+
+       pdata->hwmon_dev = hwmon_device_register(&pdev->dev);
+       if (IS_ERR(pdata->hwmon_dev)) {
+               err = PTR_ERR(pdata->hwmon_dev);
+               dev_err(&pdev->dev, "Class registration failed (%d)\n",
+                       err);
+               goto exit_name;
+       }
+
+       INIT_LIST_HEAD(&pdata->sensor_list);
+       platform_set_drvdata(pdev, pdata);
+
+       return 0;
+
+exit_name:
+       device_remove_file(&pdev->dev, &pdata->name_attr);
+exit_free:
+       kfree(pdata);
+exit:
+       return err;
+}
+
+static void delete_sensor_attr(struct sensor_data *pdata)
+{
+       struct sensor_entry *s, *l;
+
+       list_for_each_entry_safe(s, l, &pdata->sensor_list, list) {
+               struct sensor_specific_data *tdata = s->sensor_data;
+                       kfree(tdata);
+                       list_del(&s->list);
+                       kfree(s);
+               }
+}
+
+static int powernv_sensor_init(u32 sensor_id, const struct device_node *np,
+               enum sensors sensor_type, enum attributes attr_type,
+               u32 sensor_index)
+{
+       struct platform_device *pdev = powernv_sensor_get_pdev(sensor_type);
+       struct sensor_specific_data *tdata;
+       struct sensor_entry *sensor_entry;
+       struct sensor_data *pdata;
+       int err = 0;
+
+       if (!pdev) {
+               err = create_platform_device(sensor_type, &pdev);
+               if (err)
+                       goto exit;
+
+               err = create_sensor_data(pdev);
+               if (err)
+                       goto exit;
+       }
+
+       pdata = platform_get_drvdata(pdev);
+       if (!pdata) {
+               err = -ENOMEM;
+               goto exit;
+       }
+
+       tdata = kzalloc(sizeof(struct sensor_specific_data), GFP_KERNEL);
+       if (!tdata) {
+               err = -ENOMEM;
+               goto exit;
+       }
+
+       tdata->sensor_id = sensor_id;
+       tdata->sensor_index = sensor_index;
+
+       err = create_sensor_attr(tdata, &pdev->dev, sensor_type, attr_type);
+       if (err)
+               goto exit_free;
+
+       sensor_entry = kzalloc(sizeof(struct sensor_entry), GFP_KERNEL);
+       if (!sensor_entry) {
+               err = -ENOMEM;
+               goto exit_attr;
+       }
+
+       sensor_entry->sensor_data = tdata;
+
+       list_add_tail(&sensor_entry->list, &pdata->sensor_list);
+
+       return 0;
+exit_attr:
+       device_remove_file(&pdev->dev, &tdata->sd_attr.dev_attr);
+exit_free:
+       kfree(tdata);
+exit:
+       return err;
+}
+
+static void delete_unregister_sensors(void)
+{
+       struct pdev_entry *p, *n;
+
+       list_for_each_entry_safe(p, n, &pdev_list, list) {
+               struct sensor_data *pdata = platform_get_drvdata(p->pdev);
+                       if (pdata) {
+                               delete_sensor_attr(pdata);
+
+                               hwmon_device_unregister(pdata->hwmon_dev);
+                               kfree(pdata);
+                       }
+               platform_device_unregister(p->pdev);
+               list_del(&p->list);
+               kfree(p);
+       }
+}
+
+static int __init powernv_hwmon_init(void)
+{
+       struct device_node *opal, *np = NULL;
+       enum attributes attr_type;
+       enum sensors type;
+       const u32 *sensor_id;
+       u32 sensor_index;
+       int err;
+
+       opal = of_find_node_by_path("/ibm,opal/sensors");
+       if (!opal) {
+               pr_err("%s: Opal 'sensors' node not found\n", __func__);
+               return -ENXIO;
+       }
+
+       for_each_child_of_node(opal, np) {
+               if (np->name == NULL)
+                       continue;
+
+               for (type = 0; type < MAX_SENSOR_TYPE; type++)
+                       if (of_device_is_compatible(np,
+                                       sensor_names[type].compaible))
+                               break;
+
+               if (type == MAX_SENSOR_TYPE)
+                       continue;
+
+               if (get_sensor_attr_properties(np->name, type, &attr_type,
+                               &sensor_index))
+                       continue;
+
+               sensor_id = of_get_property(np, "sensor-id", NULL);
+               if (!sensor_id) {
+                       pr_info("%s: %s doesn't have sensor-id\n", __func__,
+                                       np->name);
+                       continue;
+               }
+
+               err = powernv_sensor_init(*sensor_id, np, type, attr_type,
+                               sensor_index);
+               if (err) {
+                       of_node_put(opal);
+                       goto exit;
+               }
+       }
+       of_node_put(opal);
+
+       return 0;
+exit:
+       delete_unregister_sensors();
+       return err;
+
+}
+
+static void powernv_hwmon_exit(void)
+{
+       delete_unregister_sensors();
+}
+
+module_init(powernv_hwmon_init);
+module_exit(powernv_hwmon_exit);