Merge branch 'x86-cache-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux-2.6-block.git] / drivers / hwmon / scpi-hwmon.c
1 /*
2  * System Control and Power Interface(SCPI) based hwmon sensor driver
3  *
4  * Copyright (C) 2015 ARM Ltd.
5  * Punit Agrawal <punit.agrawal@arm.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 2 as
9  * published by the Free Software Foundation.
10  *
11  * This program is distributed "as is" WITHOUT ANY WARRANTY of any
12  * kind, whether express or implied; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  */
16
17 #include <linux/hwmon.h>
18 #include <linux/module.h>
19 #include <linux/platform_device.h>
20 #include <linux/scpi_protocol.h>
21 #include <linux/slab.h>
22 #include <linux/sysfs.h>
23 #include <linux/thermal.h>
24
25 struct sensor_data {
26         struct scpi_sensor_info info;
27         struct device_attribute dev_attr_input;
28         struct device_attribute dev_attr_label;
29         char input[20];
30         char label[20];
31 };
32
33 struct scpi_thermal_zone {
34         int sensor_id;
35         struct scpi_sensors *scpi_sensors;
36 };
37
38 struct scpi_sensors {
39         struct scpi_ops *scpi_ops;
40         struct sensor_data *data;
41         struct list_head thermal_zones;
42         struct attribute **attrs;
43         struct attribute_group group;
44         const struct attribute_group *groups[2];
45 };
46
47 static int scpi_read_temp(void *dev, int *temp)
48 {
49         struct scpi_thermal_zone *zone = dev;
50         struct scpi_sensors *scpi_sensors = zone->scpi_sensors;
51         struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops;
52         struct sensor_data *sensor = &scpi_sensors->data[zone->sensor_id];
53         u64 value;
54         int ret;
55
56         ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value);
57         if (ret)
58                 return ret;
59
60         *temp = value;
61         return 0;
62 }
63
64 /* hwmon callback functions */
65 static ssize_t
66 scpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
67 {
68         struct scpi_sensors *scpi_sensors = dev_get_drvdata(dev);
69         struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops;
70         struct sensor_data *sensor;
71         u64 value;
72         int ret;
73
74         sensor = container_of(attr, struct sensor_data, dev_attr_input);
75
76         ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value);
77         if (ret)
78                 return ret;
79
80         return sprintf(buf, "%llu\n", value);
81 }
82
83 static ssize_t
84 scpi_show_label(struct device *dev, struct device_attribute *attr, char *buf)
85 {
86         struct sensor_data *sensor;
87
88         sensor = container_of(attr, struct sensor_data, dev_attr_label);
89
90         return sprintf(buf, "%s\n", sensor->info.name);
91 }
92
93 static struct thermal_zone_of_device_ops scpi_sensor_ops = {
94         .get_temp = scpi_read_temp,
95 };
96
97 static int scpi_hwmon_probe(struct platform_device *pdev)
98 {
99         u16 nr_sensors, i;
100         int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0;
101         int num_energy = 0;
102         struct scpi_ops *scpi_ops;
103         struct device *hwdev, *dev = &pdev->dev;
104         struct scpi_sensors *scpi_sensors;
105         int idx, ret;
106
107         scpi_ops = get_scpi_ops();
108         if (!scpi_ops)
109                 return -EPROBE_DEFER;
110
111         ret = scpi_ops->sensor_get_capability(&nr_sensors);
112         if (ret)
113                 return ret;
114
115         if (!nr_sensors)
116                 return -ENODEV;
117
118         scpi_sensors = devm_kzalloc(dev, sizeof(*scpi_sensors), GFP_KERNEL);
119         if (!scpi_sensors)
120                 return -ENOMEM;
121
122         scpi_sensors->data = devm_kcalloc(dev, nr_sensors,
123                                    sizeof(*scpi_sensors->data), GFP_KERNEL);
124         if (!scpi_sensors->data)
125                 return -ENOMEM;
126
127         scpi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1,
128                                    sizeof(*scpi_sensors->attrs), GFP_KERNEL);
129         if (!scpi_sensors->attrs)
130                 return -ENOMEM;
131
132         scpi_sensors->scpi_ops = scpi_ops;
133
134         for (i = 0, idx = 0; i < nr_sensors; i++) {
135                 struct sensor_data *sensor = &scpi_sensors->data[idx];
136
137                 ret = scpi_ops->sensor_get_info(i, &sensor->info);
138                 if (ret)
139                         return ret;
140
141                 switch (sensor->info.class) {
142                 case TEMPERATURE:
143                         snprintf(sensor->input, sizeof(sensor->input),
144                                  "temp%d_input", num_temp + 1);
145                         snprintf(sensor->label, sizeof(sensor->input),
146                                  "temp%d_label", num_temp + 1);
147                         num_temp++;
148                         break;
149                 case VOLTAGE:
150                         snprintf(sensor->input, sizeof(sensor->input),
151                                  "in%d_input", num_volt);
152                         snprintf(sensor->label, sizeof(sensor->input),
153                                  "in%d_label", num_volt);
154                         num_volt++;
155                         break;
156                 case CURRENT:
157                         snprintf(sensor->input, sizeof(sensor->input),
158                                  "curr%d_input", num_current + 1);
159                         snprintf(sensor->label, sizeof(sensor->input),
160                                  "curr%d_label", num_current + 1);
161                         num_current++;
162                         break;
163                 case POWER:
164                         snprintf(sensor->input, sizeof(sensor->input),
165                                  "power%d_input", num_power + 1);
166                         snprintf(sensor->label, sizeof(sensor->input),
167                                  "power%d_label", num_power + 1);
168                         num_power++;
169                         break;
170                 case ENERGY:
171                         snprintf(sensor->input, sizeof(sensor->input),
172                                  "energy%d_input", num_energy + 1);
173                         snprintf(sensor->label, sizeof(sensor->input),
174                                  "energy%d_label", num_energy + 1);
175                         num_energy++;
176                         break;
177                 default:
178                         continue;
179                 }
180
181                 sensor->dev_attr_input.attr.mode = S_IRUGO;
182                 sensor->dev_attr_input.show = scpi_show_sensor;
183                 sensor->dev_attr_input.attr.name = sensor->input;
184
185                 sensor->dev_attr_label.attr.mode = S_IRUGO;
186                 sensor->dev_attr_label.show = scpi_show_label;
187                 sensor->dev_attr_label.attr.name = sensor->label;
188
189                 scpi_sensors->attrs[idx << 1] = &sensor->dev_attr_input.attr;
190                 scpi_sensors->attrs[(idx << 1) + 1] = &sensor->dev_attr_label.attr;
191
192                 sysfs_attr_init(scpi_sensors->attrs[idx << 1]);
193                 sysfs_attr_init(scpi_sensors->attrs[(idx << 1) + 1]);
194                 idx++;
195         }
196
197         scpi_sensors->group.attrs = scpi_sensors->attrs;
198         scpi_sensors->groups[0] = &scpi_sensors->group;
199
200         platform_set_drvdata(pdev, scpi_sensors);
201
202         hwdev = devm_hwmon_device_register_with_groups(dev,
203                         "scpi_sensors", scpi_sensors, scpi_sensors->groups);
204
205         if (IS_ERR(hwdev))
206                 return PTR_ERR(hwdev);
207
208         /*
209          * Register the temperature sensors with the thermal framework
210          * to allow their usage in setting up the thermal zones from
211          * device tree.
212          *
213          * NOTE: Not all temperature sensors maybe used for thermal
214          * control
215          */
216         INIT_LIST_HEAD(&scpi_sensors->thermal_zones);
217         for (i = 0; i < nr_sensors; i++) {
218                 struct sensor_data *sensor = &scpi_sensors->data[i];
219                 struct thermal_zone_device *z;
220                 struct scpi_thermal_zone *zone;
221
222                 if (sensor->info.class != TEMPERATURE)
223                         continue;
224
225                 zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL);
226                 if (!zone)
227                         return -ENOMEM;
228
229                 zone->sensor_id = i;
230                 zone->scpi_sensors = scpi_sensors;
231                 z = devm_thermal_zone_of_sensor_register(dev,
232                                                          sensor->info.sensor_id,
233                                                          zone,
234                                                          &scpi_sensor_ops);
235                 /*
236                  * The call to thermal_zone_of_sensor_register returns
237                  * an error for sensors that are not associated with
238                  * any thermal zones or if the thermal subsystem is
239                  * not configured.
240                  */
241                 if (IS_ERR(z)) {
242                         devm_kfree(dev, zone);
243                         continue;
244                 }
245         }
246
247         return 0;
248 }
249
250 static const struct of_device_id scpi_of_match[] = {
251         {.compatible = "arm,scpi-sensors"},
252         {},
253 };
254 MODULE_DEVICE_TABLE(of, scpi_of_match);
255
256 static struct platform_driver scpi_hwmon_platdrv = {
257         .driver = {
258                 .name   = "scpi-hwmon",
259                 .of_match_table = scpi_of_match,
260         },
261         .probe          = scpi_hwmon_probe,
262 };
263 module_platform_driver(scpi_hwmon_platdrv);
264
265 MODULE_AUTHOR("Punit Agrawal <punit.agrawal@arm.com>");
266 MODULE_DESCRIPTION("ARM SCPI HWMON interface driver");
267 MODULE_LICENSE("GPL v2");