123297edbSGuenter Roeck // SPDX-License-Identifier: GPL-2.0
2ea98b29aSPunit Agrawal /*
3ea98b29aSPunit Agrawal * System Control and Power Interface(SCPI) based hwmon sensor driver
4ea98b29aSPunit Agrawal *
5ea98b29aSPunit Agrawal * Copyright (C) 2015 ARM Ltd.
6ea98b29aSPunit Agrawal * Punit Agrawal <punit.agrawal@arm.com>
7ea98b29aSPunit Agrawal */
8ea98b29aSPunit Agrawal
9ea98b29aSPunit Agrawal #include <linux/hwmon.h>
10ea98b29aSPunit Agrawal #include <linux/module.h>
11*39f03438SRob Herring #include <linux/of.h>
12ea98b29aSPunit Agrawal #include <linux/platform_device.h>
13ea98b29aSPunit Agrawal #include <linux/scpi_protocol.h>
14ea98b29aSPunit Agrawal #include <linux/slab.h>
15ea98b29aSPunit Agrawal #include <linux/sysfs.h>
1668acc77aSPunit Agrawal #include <linux/thermal.h>
17ea98b29aSPunit Agrawal
18ea98b29aSPunit Agrawal struct sensor_data {
19ccc9b826SCarlo Caione unsigned int scale;
20ea98b29aSPunit Agrawal struct scpi_sensor_info info;
21ea98b29aSPunit Agrawal struct device_attribute dev_attr_input;
22ea98b29aSPunit Agrawal struct device_attribute dev_attr_label;
23ea98b29aSPunit Agrawal char input[20];
24ea98b29aSPunit Agrawal char label[20];
25ea98b29aSPunit Agrawal };
26ea98b29aSPunit Agrawal
2768acc77aSPunit Agrawal struct scpi_thermal_zone {
2868acc77aSPunit Agrawal int sensor_id;
2968acc77aSPunit Agrawal struct scpi_sensors *scpi_sensors;
3068acc77aSPunit Agrawal };
3168acc77aSPunit Agrawal
32ea98b29aSPunit Agrawal struct scpi_sensors {
33ea98b29aSPunit Agrawal struct scpi_ops *scpi_ops;
34ea98b29aSPunit Agrawal struct sensor_data *data;
3568acc77aSPunit Agrawal struct list_head thermal_zones;
36ea98b29aSPunit Agrawal struct attribute **attrs;
37ea98b29aSPunit Agrawal struct attribute_group group;
38ea98b29aSPunit Agrawal const struct attribute_group *groups[2];
39ea98b29aSPunit Agrawal };
40ea98b29aSPunit Agrawal
41ccc9b826SCarlo Caione static const u32 gxbb_scpi_scale[] = {
42ccc9b826SCarlo Caione [TEMPERATURE] = 1, /* (celsius) */
43ccc9b826SCarlo Caione [VOLTAGE] = 1000, /* (millivolts) */
44ccc9b826SCarlo Caione [CURRENT] = 1000, /* (milliamperes) */
45ccc9b826SCarlo Caione [POWER] = 1000000, /* (microwatts) */
46ccc9b826SCarlo Caione [ENERGY] = 1000000, /* (microjoules) */
47ccc9b826SCarlo Caione };
48ccc9b826SCarlo Caione
49ccc9b826SCarlo Caione static const u32 scpi_scale[] = {
50ccc9b826SCarlo Caione [TEMPERATURE] = 1000, /* (millicelsius) */
51ccc9b826SCarlo Caione [VOLTAGE] = 1000, /* (millivolts) */
52ccc9b826SCarlo Caione [CURRENT] = 1000, /* (milliamperes) */
53ccc9b826SCarlo Caione [POWER] = 1000000, /* (microwatts) */
54ccc9b826SCarlo Caione [ENERGY] = 1000000, /* (microjoules) */
55ccc9b826SCarlo Caione };
56ccc9b826SCarlo Caione
scpi_scale_reading(u64 * value,struct sensor_data * sensor)57ccc9b826SCarlo Caione static void scpi_scale_reading(u64 *value, struct sensor_data *sensor)
58ccc9b826SCarlo Caione {
59ccc9b826SCarlo Caione if (scpi_scale[sensor->info.class] != sensor->scale) {
60ccc9b826SCarlo Caione *value *= scpi_scale[sensor->info.class];
61ccc9b826SCarlo Caione do_div(*value, sensor->scale);
62ccc9b826SCarlo Caione }
63ccc9b826SCarlo Caione }
64ccc9b826SCarlo Caione
scpi_read_temp(struct thermal_zone_device * tz,int * temp)65e5181331SDaniel Lezcano static int scpi_read_temp(struct thermal_zone_device *tz, int *temp)
6668acc77aSPunit Agrawal {
670ce637a5SDaniel Lezcano struct scpi_thermal_zone *zone = thermal_zone_device_priv(tz);
6868acc77aSPunit Agrawal struct scpi_sensors *scpi_sensors = zone->scpi_sensors;
6968acc77aSPunit Agrawal struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops;
7068acc77aSPunit Agrawal struct sensor_data *sensor = &scpi_sensors->data[zone->sensor_id];
712e874159SSudeep Holla u64 value;
7268acc77aSPunit Agrawal int ret;
7368acc77aSPunit Agrawal
7468acc77aSPunit Agrawal ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value);
7568acc77aSPunit Agrawal if (ret)
7668acc77aSPunit Agrawal return ret;
7768acc77aSPunit Agrawal
78ccc9b826SCarlo Caione scpi_scale_reading(&value, sensor);
79ccc9b826SCarlo Caione
8068acc77aSPunit Agrawal *temp = value;
8168acc77aSPunit Agrawal return 0;
8268acc77aSPunit Agrawal }
8368acc77aSPunit Agrawal
84ea98b29aSPunit Agrawal /* hwmon callback functions */
85ea98b29aSPunit Agrawal static ssize_t
scpi_show_sensor(struct device * dev,struct device_attribute * attr,char * buf)86ea98b29aSPunit Agrawal scpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
87ea98b29aSPunit Agrawal {
88ea98b29aSPunit Agrawal struct scpi_sensors *scpi_sensors = dev_get_drvdata(dev);
89ea98b29aSPunit Agrawal struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops;
90ea98b29aSPunit Agrawal struct sensor_data *sensor;
912e874159SSudeep Holla u64 value;
92ea98b29aSPunit Agrawal int ret;
93ea98b29aSPunit Agrawal
94ea98b29aSPunit Agrawal sensor = container_of(attr, struct sensor_data, dev_attr_input);
95ea98b29aSPunit Agrawal
96ea98b29aSPunit Agrawal ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value);
97ea98b29aSPunit Agrawal if (ret)
98ea98b29aSPunit Agrawal return ret;
99ea98b29aSPunit Agrawal
100ccc9b826SCarlo Caione scpi_scale_reading(&value, sensor);
101ccc9b826SCarlo Caione
10278d13552SRiwen Lu /*
10378d13552SRiwen Lu * Temperature sensor values are treated as signed values based on
10478d13552SRiwen Lu * observation even though that is not explicitly specified, and
10578d13552SRiwen Lu * because an unsigned u64 temperature does not really make practical
10678d13552SRiwen Lu * sense especially when the temperature is below zero degrees Celsius.
10778d13552SRiwen Lu */
10878d13552SRiwen Lu if (sensor->info.class == TEMPERATURE)
10978d13552SRiwen Lu return sprintf(buf, "%lld\n", (s64)value);
11078d13552SRiwen Lu
1112e874159SSudeep Holla return sprintf(buf, "%llu\n", value);
112ea98b29aSPunit Agrawal }
113ea98b29aSPunit Agrawal
114ea98b29aSPunit Agrawal static ssize_t
scpi_show_label(struct device * dev,struct device_attribute * attr,char * buf)115ea98b29aSPunit Agrawal scpi_show_label(struct device *dev, struct device_attribute *attr, char *buf)
116ea98b29aSPunit Agrawal {
117ea98b29aSPunit Agrawal struct sensor_data *sensor;
118ea98b29aSPunit Agrawal
119ea98b29aSPunit Agrawal sensor = container_of(attr, struct sensor_data, dev_attr_label);
120ea98b29aSPunit Agrawal
121ea98b29aSPunit Agrawal return sprintf(buf, "%s\n", sensor->info.name);
122ea98b29aSPunit Agrawal }
123ea98b29aSPunit Agrawal
124e5181331SDaniel Lezcano static const struct thermal_zone_device_ops scpi_sensor_ops = {
12568acc77aSPunit Agrawal .get_temp = scpi_read_temp,
12668acc77aSPunit Agrawal };
12768acc77aSPunit Agrawal
128ccc9b826SCarlo Caione static const struct of_device_id scpi_of_match[] = {
129ccc9b826SCarlo Caione {.compatible = "arm,scpi-sensors", .data = &scpi_scale},
130ccc9b826SCarlo Caione {.compatible = "amlogic,meson-gxbb-scpi-sensors", .data = &gxbb_scpi_scale},
131ccc9b826SCarlo Caione {},
132ccc9b826SCarlo Caione };
133ccc9b826SCarlo Caione MODULE_DEVICE_TABLE(of, scpi_of_match);
134ccc9b826SCarlo Caione
scpi_hwmon_probe(struct platform_device * pdev)135ea98b29aSPunit Agrawal static int scpi_hwmon_probe(struct platform_device *pdev)
136ea98b29aSPunit Agrawal {
137ea98b29aSPunit Agrawal u16 nr_sensors, i;
138ccc9b826SCarlo Caione const u32 *scale;
139ea98b29aSPunit Agrawal int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0;
140fb3b07efSSudeep Holla int num_energy = 0;
141ea98b29aSPunit Agrawal struct scpi_ops *scpi_ops;
142ea98b29aSPunit Agrawal struct device *hwdev, *dev = &pdev->dev;
143ea98b29aSPunit Agrawal struct scpi_sensors *scpi_sensors;
144d7817ff2SEduardo Valentin int idx, ret;
145ea98b29aSPunit Agrawal
146ea98b29aSPunit Agrawal scpi_ops = get_scpi_ops();
147ea98b29aSPunit Agrawal if (!scpi_ops)
148ea98b29aSPunit Agrawal return -EPROBE_DEFER;
149ea98b29aSPunit Agrawal
150ea98b29aSPunit Agrawal ret = scpi_ops->sensor_get_capability(&nr_sensors);
151ea98b29aSPunit Agrawal if (ret)
152ea98b29aSPunit Agrawal return ret;
153ea98b29aSPunit Agrawal
154ea98b29aSPunit Agrawal if (!nr_sensors)
155ea98b29aSPunit Agrawal return -ENODEV;
156ea98b29aSPunit Agrawal
157ea98b29aSPunit Agrawal scpi_sensors = devm_kzalloc(dev, sizeof(*scpi_sensors), GFP_KERNEL);
158ea98b29aSPunit Agrawal if (!scpi_sensors)
159ea98b29aSPunit Agrawal return -ENOMEM;
160ea98b29aSPunit Agrawal
161ea98b29aSPunit Agrawal scpi_sensors->data = devm_kcalloc(dev, nr_sensors,
162ea98b29aSPunit Agrawal sizeof(*scpi_sensors->data), GFP_KERNEL);
163ea98b29aSPunit Agrawal if (!scpi_sensors->data)
164ea98b29aSPunit Agrawal return -ENOMEM;
165ea98b29aSPunit Agrawal
166ea98b29aSPunit Agrawal scpi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1,
167ea98b29aSPunit Agrawal sizeof(*scpi_sensors->attrs), GFP_KERNEL);
168ea98b29aSPunit Agrawal if (!scpi_sensors->attrs)
169ea98b29aSPunit Agrawal return -ENOMEM;
170ea98b29aSPunit Agrawal
171ea98b29aSPunit Agrawal scpi_sensors->scpi_ops = scpi_ops;
172ea98b29aSPunit Agrawal
173fd6ca3f5SMinghao Chi scale = of_device_get_match_data(&pdev->dev);
174fd6ca3f5SMinghao Chi if (!scale) {
175ccc9b826SCarlo Caione dev_err(&pdev->dev, "Unable to initialize scpi-hwmon data\n");
176ccc9b826SCarlo Caione return -ENODEV;
177ccc9b826SCarlo Caione }
178ccc9b826SCarlo Caione
179cef03d7eSSudeep Holla for (i = 0, idx = 0; i < nr_sensors; i++) {
180cef03d7eSSudeep Holla struct sensor_data *sensor = &scpi_sensors->data[idx];
181ea98b29aSPunit Agrawal
182ea98b29aSPunit Agrawal ret = scpi_ops->sensor_get_info(i, &sensor->info);
183ea98b29aSPunit Agrawal if (ret)
184ea98b29aSPunit Agrawal return ret;
185ea98b29aSPunit Agrawal
186ea98b29aSPunit Agrawal switch (sensor->info.class) {
187ea98b29aSPunit Agrawal case TEMPERATURE:
188ea98b29aSPunit Agrawal snprintf(sensor->input, sizeof(sensor->input),
189ea98b29aSPunit Agrawal "temp%d_input", num_temp + 1);
190ea98b29aSPunit Agrawal snprintf(sensor->label, sizeof(sensor->input),
191ea98b29aSPunit Agrawal "temp%d_label", num_temp + 1);
192ea98b29aSPunit Agrawal num_temp++;
193ea98b29aSPunit Agrawal break;
194ea98b29aSPunit Agrawal case VOLTAGE:
195ea98b29aSPunit Agrawal snprintf(sensor->input, sizeof(sensor->input),
196ea98b29aSPunit Agrawal "in%d_input", num_volt);
197ea98b29aSPunit Agrawal snprintf(sensor->label, sizeof(sensor->input),
198ea98b29aSPunit Agrawal "in%d_label", num_volt);
199ea98b29aSPunit Agrawal num_volt++;
200ea98b29aSPunit Agrawal break;
201ea98b29aSPunit Agrawal case CURRENT:
202ea98b29aSPunit Agrawal snprintf(sensor->input, sizeof(sensor->input),
203ea98b29aSPunit Agrawal "curr%d_input", num_current + 1);
204ea98b29aSPunit Agrawal snprintf(sensor->label, sizeof(sensor->input),
205ea98b29aSPunit Agrawal "curr%d_label", num_current + 1);
206ea98b29aSPunit Agrawal num_current++;
207ea98b29aSPunit Agrawal break;
208ea98b29aSPunit Agrawal case POWER:
209ea98b29aSPunit Agrawal snprintf(sensor->input, sizeof(sensor->input),
210ea98b29aSPunit Agrawal "power%d_input", num_power + 1);
211ea98b29aSPunit Agrawal snprintf(sensor->label, sizeof(sensor->input),
212ea98b29aSPunit Agrawal "power%d_label", num_power + 1);
213ea98b29aSPunit Agrawal num_power++;
214ea98b29aSPunit Agrawal break;
215fb3b07efSSudeep Holla case ENERGY:
216fb3b07efSSudeep Holla snprintf(sensor->input, sizeof(sensor->input),
217fb3b07efSSudeep Holla "energy%d_input", num_energy + 1);
218fb3b07efSSudeep Holla snprintf(sensor->label, sizeof(sensor->input),
219fb3b07efSSudeep Holla "energy%d_label", num_energy + 1);
220fb3b07efSSudeep Holla num_energy++;
221fb3b07efSSudeep Holla break;
222ea98b29aSPunit Agrawal default:
223cef03d7eSSudeep Holla continue;
224ea98b29aSPunit Agrawal }
225ea98b29aSPunit Agrawal
226ccc9b826SCarlo Caione sensor->scale = scale[sensor->info.class];
227ccc9b826SCarlo Caione
2286a0785aaSGuenter Roeck sensor->dev_attr_input.attr.mode = 0444;
229ea98b29aSPunit Agrawal sensor->dev_attr_input.show = scpi_show_sensor;
230ea98b29aSPunit Agrawal sensor->dev_attr_input.attr.name = sensor->input;
231ea98b29aSPunit Agrawal
2326a0785aaSGuenter Roeck sensor->dev_attr_label.attr.mode = 0444;
233ea98b29aSPunit Agrawal sensor->dev_attr_label.show = scpi_show_label;
234ea98b29aSPunit Agrawal sensor->dev_attr_label.attr.name = sensor->label;
235ea98b29aSPunit Agrawal
236cef03d7eSSudeep Holla scpi_sensors->attrs[idx << 1] = &sensor->dev_attr_input.attr;
237cef03d7eSSudeep Holla scpi_sensors->attrs[(idx << 1) + 1] = &sensor->dev_attr_label.attr;
238ea98b29aSPunit Agrawal
239cef03d7eSSudeep Holla sysfs_attr_init(scpi_sensors->attrs[idx << 1]);
240cef03d7eSSudeep Holla sysfs_attr_init(scpi_sensors->attrs[(idx << 1) + 1]);
241cef03d7eSSudeep Holla idx++;
242ea98b29aSPunit Agrawal }
243ea98b29aSPunit Agrawal
244ea98b29aSPunit Agrawal scpi_sensors->group.attrs = scpi_sensors->attrs;
245ea98b29aSPunit Agrawal scpi_sensors->groups[0] = &scpi_sensors->group;
246ea98b29aSPunit Agrawal
24768acc77aSPunit Agrawal platform_set_drvdata(pdev, scpi_sensors);
24868acc77aSPunit Agrawal
249ea98b29aSPunit Agrawal hwdev = devm_hwmon_device_register_with_groups(dev,
250ea98b29aSPunit Agrawal "scpi_sensors", scpi_sensors, scpi_sensors->groups);
251ea98b29aSPunit Agrawal
25268acc77aSPunit Agrawal if (IS_ERR(hwdev))
25368acc77aSPunit Agrawal return PTR_ERR(hwdev);
25468acc77aSPunit Agrawal
25568acc77aSPunit Agrawal /*
25668acc77aSPunit Agrawal * Register the temperature sensors with the thermal framework
25768acc77aSPunit Agrawal * to allow their usage in setting up the thermal zones from
25868acc77aSPunit Agrawal * device tree.
25968acc77aSPunit Agrawal *
26068acc77aSPunit Agrawal * NOTE: Not all temperature sensors maybe used for thermal
26168acc77aSPunit Agrawal * control
26268acc77aSPunit Agrawal */
26368acc77aSPunit Agrawal INIT_LIST_HEAD(&scpi_sensors->thermal_zones);
26468acc77aSPunit Agrawal for (i = 0; i < nr_sensors; i++) {
26568acc77aSPunit Agrawal struct sensor_data *sensor = &scpi_sensors->data[i];
266d7817ff2SEduardo Valentin struct thermal_zone_device *z;
26768acc77aSPunit Agrawal struct scpi_thermal_zone *zone;
26868acc77aSPunit Agrawal
26968acc77aSPunit Agrawal if (sensor->info.class != TEMPERATURE)
27068acc77aSPunit Agrawal continue;
27168acc77aSPunit Agrawal
27268acc77aSPunit Agrawal zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL);
273d7817ff2SEduardo Valentin if (!zone)
274d7817ff2SEduardo Valentin return -ENOMEM;
27568acc77aSPunit Agrawal
27668acc77aSPunit Agrawal zone->sensor_id = i;
27768acc77aSPunit Agrawal zone->scpi_sensors = scpi_sensors;
278e5181331SDaniel Lezcano z = devm_thermal_of_zone_register(dev,
279d7817ff2SEduardo Valentin sensor->info.sensor_id,
280d7817ff2SEduardo Valentin zone,
281d7817ff2SEduardo Valentin &scpi_sensor_ops);
28268acc77aSPunit Agrawal /*
28368acc77aSPunit Agrawal * The call to thermal_zone_of_sensor_register returns
28468acc77aSPunit Agrawal * an error for sensors that are not associated with
28568acc77aSPunit Agrawal * any thermal zones or if the thermal subsystem is
28668acc77aSPunit Agrawal * not configured.
28768acc77aSPunit Agrawal */
2883045b5d6Szhong jiang if (IS_ERR(z))
28968acc77aSPunit Agrawal devm_kfree(dev, zone);
29068acc77aSPunit Agrawal }
29168acc77aSPunit Agrawal
29268acc77aSPunit Agrawal return 0;
293ea98b29aSPunit Agrawal }
294ea98b29aSPunit Agrawal
295ea98b29aSPunit Agrawal static struct platform_driver scpi_hwmon_platdrv = {
296ea98b29aSPunit Agrawal .driver = {
297ea98b29aSPunit Agrawal .name = "scpi-hwmon",
298ea98b29aSPunit Agrawal .of_match_table = scpi_of_match,
299ea98b29aSPunit Agrawal },
300ea98b29aSPunit Agrawal .probe = scpi_hwmon_probe,
301ea98b29aSPunit Agrawal };
302ea98b29aSPunit Agrawal module_platform_driver(scpi_hwmon_platdrv);
303ea98b29aSPunit Agrawal
304ea98b29aSPunit Agrawal MODULE_AUTHOR("Punit Agrawal <punit.agrawal@arm.com>");
305ea98b29aSPunit Agrawal MODULE_DESCRIPTION("ARM SCPI HWMON interface driver");
306ea98b29aSPunit Agrawal MODULE_LICENSE("GPL v2");
307