xref: /linux/drivers/hwmon/scpi-hwmon.c (revision a1c613ae4c322ddd58d5a8539dbfba2a0380a8c0)
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