aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hwmon
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon')
-rw-r--r--drivers/hwmon/Kconfig11
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/ibmpowernv.c364
3 files changed, 376 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 37908ff8f7ff..c0aec8db462f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -554,6 +554,17 @@ config SENSORS_IBMPEX
554 This driver can also be built as a module. If so, the module 554 This driver can also be built as a module. If so, the module
555 will be called ibmpex. 555 will be called ibmpex.
556 556
557config SENSORS_IBMPOWERNV
558 tristate "IBM POWERNV platform sensors"
559 depends on PPC_POWERNV
560 default y
561 help
562 If you say yes here you get support for the temperature/fan/power
563 sensors on your PowerNV platform.
564
565 This driver can also be built as a module. If so, the module
566 will be called ibmpowernv.
567
557config SENSORS_IIO_HWMON 568config SENSORS_IIO_HWMON
558 tristate "Hwmon driver that uses channels specified via iio maps" 569 tristate "Hwmon driver that uses channels specified via iio maps"
559 depends on IIO 570 depends on IIO
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 1362382a3187..f4435ea6b169 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
71obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o 71obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
72obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o 72obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
73obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o 73obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
74obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
74obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o 75obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o
75obj-$(CONFIG_SENSORS_INA209) += ina209.o 76obj-$(CONFIG_SENSORS_INA209) += ina209.o
76obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o 77obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
diff --git a/drivers/hwmon/ibmpowernv.c b/drivers/hwmon/ibmpowernv.c
new file mode 100644
index 000000000000..e6b652a35815
--- /dev/null
+++ b/drivers/hwmon/ibmpowernv.c
@@ -0,0 +1,364 @@
1/*
2 * IBM PowerNV platform sensors for temperature/fan/voltage/power
3 * Copyright (C) 2014 IBM
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.
17 */
18
19#define DRVNAME "ibmpowernv"
20#define pr_fmt(fmt) DRVNAME ": " fmt
21
22#include <linux/init.h>
23#include <linux/module.h>
24#include <linux/kernel.h>
25#include <linux/hwmon.h>
26#include <linux/hwmon-sysfs.h>
27#include <linux/of.h>
28#include <linux/slab.h>
29
30#include <linux/platform_device.h>
31#include <asm/opal.h>
32#include <linux/err.h>
33
34#define MAX_ATTR_LEN 32
35
36/* Sensor suffix name from DT */
37#define DT_FAULT_ATTR_SUFFIX "faulted"
38#define DT_DATA_ATTR_SUFFIX "data"
39#define DT_THRESHOLD_ATTR_SUFFIX "thrs"
40
41/*
42 * Enumerates all the types of sensors in the POWERNV platform and does index
43 * into 'struct sensor_group'
44 */
45enum sensors {
46 FAN,
47 AMBIENT_TEMP,
48 POWER_SUPPLY,
49 POWER_INPUT,
50 MAX_SENSOR_TYPE,
51};
52
53static struct sensor_group {
54 const char *name;
55 const char *compatible;
56 struct attribute_group group;
57 u32 attr_count;
58} sensor_groups[] = {
59 {"fan", "ibm,opal-sensor-cooling-fan"},
60 {"temp", "ibm,opal-sensor-amb-temp"},
61 {"in", "ibm,opal-sensor-power-supply"},
62 {"power", "ibm,opal-sensor-power"}
63};
64
65struct sensor_data {
66 u32 id; /* An opaque id of the firmware for each sensor */
67 enum sensors type;
68 char name[MAX_ATTR_LEN];
69 struct device_attribute dev_attr;
70};
71
72struct platform_data {
73 const struct attribute_group *attr_groups[MAX_SENSOR_TYPE + 1];
74 u32 sensors_count; /* Total count of sensors from each group */
75};
76
77/* Platform device representing all the ibmpowernv sensors */
78static struct platform_device *pdevice;
79
80static ssize_t show_sensor(struct device *dev, struct device_attribute *devattr,
81 char *buf)
82{
83 struct sensor_data *sdata = container_of(devattr, struct sensor_data,
84 dev_attr);
85 ssize_t ret;
86 u32 x;
87
88 ret = opal_get_sensor_data(sdata->id, &x);
89 if (ret)
90 return ret;
91
92 /* Convert temperature to milli-degrees */
93 if (sdata->type == AMBIENT_TEMP)
94 x *= 1000;
95 /* Convert power to micro-watts */
96 else if (sdata->type == POWER_INPUT)
97 x *= 1000000;
98
99 return sprintf(buf, "%u\n", x);
100}
101
102static int __init get_sensor_index_attr(const char *name, u32 *index,
103 char *attr)
104{
105 char *hash_pos = strchr(name, '#');
106 char buf[8] = { 0 };
107 char *dash_pos;
108 u32 copy_len;
109 int err;
110
111 if (!hash_pos)
112 return -EINVAL;
113
114 dash_pos = strchr(hash_pos, '-');
115 if (!dash_pos)
116 return -EINVAL;
117
118 copy_len = dash_pos - hash_pos - 1;
119 if (copy_len >= sizeof(buf))
120 return -EINVAL;
121
122 strncpy(buf, hash_pos + 1, copy_len);
123
124 err = kstrtou32(buf, 10, index);
125 if (err)
126 return err;
127
128 strncpy(attr, dash_pos + 1, MAX_ATTR_LEN);
129
130 return 0;
131}
132
133/*
134 * This function translates the DT node name into the 'hwmon' attribute name.
135 * IBMPOWERNV device node appear like cooling-fan#2-data, amb-temp#1-thrs etc.
136 * which need to be mapped as fan2_input, temp1_max respectively before
137 * populating them inside hwmon device class.
138 */
139static int __init create_hwmon_attr_name(struct device *dev, enum sensors type,
140 const char *node_name,
141 char *hwmon_attr_name)
142{
143 char attr_suffix[MAX_ATTR_LEN];
144 char *attr_name;
145 u32 index;
146 int err;
147
148 err = get_sensor_index_attr(node_name, &index, attr_suffix);
149 if (err) {
150 dev_err(dev, "Sensor device node name '%s' is invalid\n",
151 node_name);
152 return err;
153 }
154
155 if (!strcmp(attr_suffix, DT_FAULT_ATTR_SUFFIX)) {
156 attr_name = "fault";
157 } else if (!strcmp(attr_suffix, DT_DATA_ATTR_SUFFIX)) {
158 attr_name = "input";
159 } else if (!strcmp(attr_suffix, DT_THRESHOLD_ATTR_SUFFIX)) {
160 if (type == AMBIENT_TEMP)
161 attr_name = "max";
162 else if (type == FAN)
163 attr_name = "min";
164 else
165 return -ENOENT;
166 } else {
167 return -ENOENT;
168 }
169
170 snprintf(hwmon_attr_name, MAX_ATTR_LEN, "%s%d_%s",
171 sensor_groups[type].name, index, attr_name);
172 return 0;
173}
174
175static int __init populate_attr_groups(struct platform_device *pdev)
176{
177 struct platform_data *pdata = platform_get_drvdata(pdev);
178 const struct attribute_group **pgroups = pdata->attr_groups;
179 struct device_node *opal, *np;
180 enum sensors type;
181
182 opal = of_find_node_by_path("/ibm,opal/sensors");
183 if (!opal) {
184 dev_err(&pdev->dev, "Opal node 'sensors' not found\n");
185 return -ENODEV;
186 }
187
188 for_each_child_of_node(opal, np) {
189 if (np->name == NULL)
190 continue;
191
192 for (type = 0; type < MAX_SENSOR_TYPE; type++)
193 if (of_device_is_compatible(np,
194 sensor_groups[type].compatible)) {
195 sensor_groups[type].attr_count++;
196 break;
197 }
198 }
199
200 of_node_put(opal);
201
202 for (type = 0; type < MAX_SENSOR_TYPE; type++) {
203 sensor_groups[type].group.attrs = devm_kzalloc(&pdev->dev,
204 sizeof(struct attribute *) *
205 (sensor_groups[type].attr_count + 1),
206 GFP_KERNEL);
207 if (!sensor_groups[type].group.attrs)
208 return -ENOMEM;
209
210 pgroups[type] = &sensor_groups[type].group;
211 pdata->sensors_count += sensor_groups[type].attr_count;
212 sensor_groups[type].attr_count = 0;
213 }
214
215 return 0;
216}
217
218/*
219 * Iterate through the device tree for each child of 'sensors' node, create
220 * a sysfs attribute file, the file is named by translating the DT node name
221 * to the name required by the higher 'hwmon' driver like fan1_input, temp1_max
222 * etc..
223 */
224static int __init create_device_attrs(struct platform_device *pdev)
225{
226 struct platform_data *pdata = platform_get_drvdata(pdev);
227 const struct attribute_group **pgroups = pdata->attr_groups;
228 struct device_node *opal, *np;
229 struct sensor_data *sdata;
230 const __be32 *sensor_id;
231 enum sensors type;
232 u32 count = 0;
233 int err = 0;
234
235 opal = of_find_node_by_path("/ibm,opal/sensors");
236 sdata = devm_kzalloc(&pdev->dev, pdata->sensors_count * sizeof(*sdata),
237 GFP_KERNEL);
238 if (!sdata) {
239 err = -ENOMEM;
240 goto exit_put_node;
241 }
242
243 for_each_child_of_node(opal, np) {
244 if (np->name == NULL)
245 continue;
246
247 for (type = 0; type < MAX_SENSOR_TYPE; type++)
248 if (of_device_is_compatible(np,
249 sensor_groups[type].compatible))
250 break;
251
252 if (type == MAX_SENSOR_TYPE)
253 continue;
254
255 sensor_id = of_get_property(np, "sensor-id", NULL);
256 if (!sensor_id) {
257 dev_info(&pdev->dev,
258 "'sensor-id' missing in the node '%s'\n",
259 np->name);
260 continue;
261 }
262
263 sdata[count].id = be32_to_cpup(sensor_id);
264 sdata[count].type = type;
265 err = create_hwmon_attr_name(&pdev->dev, type, np->name,
266 sdata[count].name);
267 if (err)
268 goto exit_put_node;
269
270 sysfs_attr_init(&sdata[count].dev_attr.attr);
271 sdata[count].dev_attr.attr.name = sdata[count].name;
272 sdata[count].dev_attr.attr.mode = S_IRUGO;
273 sdata[count].dev_attr.show = show_sensor;
274
275 pgroups[type]->attrs[sensor_groups[type].attr_count++] =
276 &sdata[count++].dev_attr.attr;
277 }
278
279exit_put_node:
280 of_node_put(opal);
281 return err;
282}
283
284static int __init ibmpowernv_probe(struct platform_device *pdev)
285{
286 struct platform_data *pdata;
287 struct device *hwmon_dev;
288 int err;
289
290 pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
291 if (!pdata)
292 return -ENOMEM;
293
294 platform_set_drvdata(pdev, pdata);
295 pdata->sensors_count = 0;
296 err = populate_attr_groups(pdev);
297 if (err)
298 return err;
299
300 /* Create sysfs attribute data for each sensor found in the DT */
301 err = create_device_attrs(pdev);
302 if (err)
303 return err;
304
305 /* Finally, register with hwmon */
306 hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, DRVNAME,
307 pdata,
308 pdata->attr_groups);
309
310 return PTR_ERR_OR_ZERO(hwmon_dev);
311}
312
313static struct platform_driver ibmpowernv_driver = {
314 .driver = {
315 .owner = THIS_MODULE,
316 .name = DRVNAME,
317 },
318};
319
320static int __init ibmpowernv_init(void)
321{
322 int err;
323
324 pdevice = platform_device_alloc(DRVNAME, 0);
325 if (!pdevice) {
326 pr_err("Device allocation failed\n");
327 err = -ENOMEM;
328 goto exit;
329 }
330
331 err = platform_device_add(pdevice);
332 if (err) {
333 pr_err("Device addition failed (%d)\n", err);
334 goto exit_device_put;
335 }
336
337 err = platform_driver_probe(&ibmpowernv_driver, ibmpowernv_probe);
338 if (err) {
339 pr_err("Platfrom driver probe failed\n");
340 goto exit_device_del;
341 }
342
343 return 0;
344
345exit_device_del:
346 platform_device_del(pdevice);
347exit_device_put:
348 platform_device_put(pdevice);
349exit:
350 return err;
351}
352
353static void __exit ibmpowernv_exit(void)
354{
355 platform_driver_unregister(&ibmpowernv_driver);
356 platform_device_unregister(pdevice);
357}
358
359MODULE_AUTHOR("Neelesh Gupta <neelegup@linux.vnet.ibm.com>");
360MODULE_DESCRIPTION("IBM POWERNV platform sensors");
361MODULE_LICENSE("GPL");
362
363module_init(ibmpowernv_init);
364module_exit(ibmpowernv_exit);