Commit | Line | Data |
---|---|---|
e0f8a24e JC |
1 | /* Hwmon client for industrial I/O devices |
2 | * | |
3 | * Copyright (c) 2011 Jonathan Cameron | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License version 2 as published by | |
7 | * the Free Software Foundation. | |
8 | */ | |
9 | ||
10 | #include <linux/kernel.h> | |
11 | #include <linux/slab.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/err.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/hwmon.h> | |
16 | #include <linux/hwmon-sysfs.h> | |
06458e27 JC |
17 | #include <linux/iio/consumer.h> |
18 | #include <linux/iio/types.h> | |
e0f8a24e JC |
19 | |
20 | /** | |
21 | * struct iio_hwmon_state - device instance state | |
22 | * @channels: filled with array of channels from iio | |
23 | * @num_channels: number of channels in channels (saves counting twice) | |
24 | * @hwmon_dev: associated hwmon device | |
25 | * @attr_group: the group of attributes | |
26 | * @attrs: null terminated array of attribute pointers. | |
27 | */ | |
28 | struct iio_hwmon_state { | |
29 | struct iio_channel *channels; | |
30 | int num_channels; | |
31 | struct device *hwmon_dev; | |
32 | struct attribute_group attr_group; | |
33 | struct attribute **attrs; | |
34 | }; | |
35 | ||
36 | /* | |
37 | * Assumes that IIO and hwmon operate in the same base units. | |
38 | * This is supposed to be true, but needs verification for | |
39 | * new channel types. | |
40 | */ | |
41 | static ssize_t iio_hwmon_read_val(struct device *dev, | |
42 | struct device_attribute *attr, | |
43 | char *buf) | |
44 | { | |
45 | long result; | |
46 | int val, ret, scaleint, scalepart; | |
47 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | |
48 | struct iio_hwmon_state *state = dev_get_drvdata(dev); | |
49 | ||
50 | /* | |
51 | * No locking between this pair, so theoretically possible | |
52 | * the scale has changed. | |
53 | */ | |
314be14b | 54 | ret = iio_read_channel_raw(&state->channels[sattr->index], |
e0f8a24e JC |
55 | &val); |
56 | if (ret < 0) | |
57 | return ret; | |
58 | ||
314be14b | 59 | ret = iio_read_channel_scale(&state->channels[sattr->index], |
e0f8a24e JC |
60 | &scaleint, &scalepart); |
61 | if (ret < 0) | |
62 | return ret; | |
63 | switch (ret) { | |
64 | case IIO_VAL_INT: | |
65 | result = val * scaleint; | |
66 | break; | |
67 | case IIO_VAL_INT_PLUS_MICRO: | |
68 | result = (s64)val * (s64)scaleint + | |
69 | div_s64((s64)val * (s64)scalepart, 1000000LL); | |
70 | break; | |
71 | case IIO_VAL_INT_PLUS_NANO: | |
72 | result = (s64)val * (s64)scaleint + | |
73 | div_s64((s64)val * (s64)scalepart, 1000000000LL); | |
74 | break; | |
75 | default: | |
76 | return -EINVAL; | |
77 | } | |
78 | return sprintf(buf, "%ld\n", result); | |
79 | } | |
80 | ||
81 | static void iio_hwmon_free_attrs(struct iio_hwmon_state *st) | |
82 | { | |
83 | int i; | |
84 | struct sensor_device_attribute *a; | |
85 | for (i = 0; i < st->num_channels; i++) | |
86 | if (st->attrs[i]) { | |
87 | a = to_sensor_dev_attr( | |
88 | container_of(st->attrs[i], | |
89 | struct device_attribute, | |
90 | attr)); | |
91 | kfree(a); | |
92 | } | |
93 | } | |
94 | ||
95 | static int __devinit iio_hwmon_probe(struct platform_device *pdev) | |
96 | { | |
97 | struct iio_hwmon_state *st; | |
98 | struct sensor_device_attribute *a; | |
99 | int ret, i; | |
100 | int in_i = 1, temp_i = 1, curr_i = 1; | |
101 | enum iio_chan_type type; | |
102 | ||
103 | st = kzalloc(sizeof(*st), GFP_KERNEL); | |
104 | if (st == NULL) { | |
105 | ret = -ENOMEM; | |
106 | goto error_ret; | |
107 | } | |
108 | ||
314be14b | 109 | st->channels = iio_channel_get_all(dev_name(&pdev->dev)); |
e0f8a24e JC |
110 | if (IS_ERR(st->channels)) { |
111 | ret = PTR_ERR(st->channels); | |
112 | goto error_free_state; | |
113 | } | |
114 | ||
115 | /* count how many attributes we have */ | |
116 | while (st->channels[st->num_channels].indio_dev) | |
117 | st->num_channels++; | |
118 | ||
119 | st->attrs = kzalloc(sizeof(st->attrs) * (st->num_channels + 1), | |
120 | GFP_KERNEL); | |
121 | if (st->attrs == NULL) { | |
122 | ret = -ENOMEM; | |
123 | goto error_release_channels; | |
124 | } | |
125 | for (i = 0; i < st->num_channels; i++) { | |
126 | a = kzalloc(sizeof(*a), GFP_KERNEL); | |
127 | if (a == NULL) { | |
128 | ret = -ENOMEM; | |
129 | goto error_free_attrs; | |
130 | } | |
131 | ||
132 | sysfs_attr_init(&a->dev_attr.attr); | |
314be14b | 133 | ret = iio_get_channel_type(&st->channels[i], &type); |
e0f8a24e JC |
134 | if (ret < 0) { |
135 | kfree(a); | |
136 | goto error_free_attrs; | |
137 | } | |
138 | switch (type) { | |
139 | case IIO_VOLTAGE: | |
140 | a->dev_attr.attr.name = kasprintf(GFP_KERNEL, | |
141 | "in%d_input", | |
142 | in_i++); | |
143 | break; | |
144 | case IIO_TEMP: | |
145 | a->dev_attr.attr.name = kasprintf(GFP_KERNEL, | |
146 | "temp%d_input", | |
147 | temp_i++); | |
148 | break; | |
149 | case IIO_CURRENT: | |
150 | a->dev_attr.attr.name = kasprintf(GFP_KERNEL, | |
151 | "curr%d_input", | |
152 | curr_i++); | |
153 | break; | |
154 | default: | |
155 | ret = -EINVAL; | |
156 | kfree(a); | |
157 | goto error_free_attrs; | |
158 | } | |
159 | if (a->dev_attr.attr.name == NULL) { | |
160 | kfree(a); | |
161 | ret = -ENOMEM; | |
162 | goto error_free_attrs; | |
163 | } | |
164 | a->dev_attr.show = iio_hwmon_read_val; | |
165 | a->dev_attr.attr.mode = S_IRUGO; | |
166 | a->index = i; | |
167 | st->attrs[i] = &a->dev_attr.attr; | |
168 | } | |
169 | ||
170 | st->attr_group.attrs = st->attrs; | |
171 | platform_set_drvdata(pdev, st); | |
172 | ret = sysfs_create_group(&pdev->dev.kobj, &st->attr_group); | |
173 | if (ret < 0) | |
174 | goto error_free_attrs; | |
175 | ||
176 | st->hwmon_dev = hwmon_device_register(&pdev->dev); | |
177 | if (IS_ERR(st->hwmon_dev)) { | |
178 | ret = PTR_ERR(st->hwmon_dev); | |
179 | goto error_remove_group; | |
180 | } | |
181 | return 0; | |
182 | ||
183 | error_remove_group: | |
184 | sysfs_remove_group(&pdev->dev.kobj, &st->attr_group); | |
185 | error_free_attrs: | |
186 | iio_hwmon_free_attrs(st); | |
187 | kfree(st->attrs); | |
188 | error_release_channels: | |
314be14b | 189 | iio_channel_release_all(st->channels); |
e0f8a24e JC |
190 | error_free_state: |
191 | kfree(st); | |
192 | error_ret: | |
193 | return ret; | |
194 | } | |
195 | ||
196 | static int __devexit iio_hwmon_remove(struct platform_device *pdev) | |
197 | { | |
198 | struct iio_hwmon_state *st = platform_get_drvdata(pdev); | |
199 | ||
200 | hwmon_device_unregister(st->hwmon_dev); | |
201 | sysfs_remove_group(&pdev->dev.kobj, &st->attr_group); | |
202 | iio_hwmon_free_attrs(st); | |
203 | kfree(st->attrs); | |
314be14b | 204 | iio_channel_release_all(st->channels); |
e0f8a24e JC |
205 | |
206 | return 0; | |
207 | } | |
208 | ||
209 | static struct platform_driver __refdata iio_hwmon_driver = { | |
210 | .driver = { | |
211 | .name = "iio_hwmon", | |
212 | .owner = THIS_MODULE, | |
213 | }, | |
214 | .probe = iio_hwmon_probe, | |
215 | .remove = __devexit_p(iio_hwmon_remove), | |
216 | }; | |
217 | ||
d16f6dbd | 218 | module_platform_driver(iio_hwmon_driver); |
e0f8a24e JC |
219 | |
220 | MODULE_AUTHOR("Jonathan Cameron <jic23@cam.ac.uk>"); | |
221 | MODULE_DESCRIPTION("IIO to hwmon driver"); | |
222 | MODULE_LICENSE("GPL v2"); |