1// SPDX-License-Identifier: GPL-2.0
2/*
3 *  thermal_hwmon.c - Generic Thermal Management hwmon support.
4 *
5 *  Code based on Intel thermal_core.c. Copyrights of the original code:
6 *  Copyright (C) 2008 Intel Corp
7 *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
8 *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
9 *
10 *  Copyright (C) 2013 Texas Instruments
11 *  Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
12 */
13#include <linux/err.h>
14#include <linux/export.h>
15#include <linux/hwmon.h>
16#include <linux/slab.h>
17#include <linux/thermal.h>
18
19#include "thermal_hwmon.h"
20
21/* hwmon sys I/F */
22/* thermal zone devices with the same type share one hwmon device */
23struct thermal_hwmon_device {
24	char type[THERMAL_NAME_LENGTH];
25	struct device *device;
26	int count;
27	struct list_head tz_list;
28	struct list_head node;
29};
30
31struct thermal_hwmon_attr {
32	struct device_attribute attr;
33	char name[16];
34};
35
36/* one temperature input for each thermal zone */
37struct thermal_hwmon_temp {
38	struct list_head hwmon_node;
39	struct thermal_zone_device *tz;
40	struct thermal_hwmon_attr temp_input;	/* hwmon sys attr */
41	struct thermal_hwmon_attr temp_crit;	/* hwmon sys attr */
42};
43
44static LIST_HEAD(thermal_hwmon_list);
45
46static DEFINE_MUTEX(thermal_hwmon_list_lock);
47
48static ssize_t
49temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
50{
51	int temperature;
52	int ret;
53	struct thermal_hwmon_attr *hwmon_attr
54			= container_of(attr, struct thermal_hwmon_attr, attr);
55	struct thermal_hwmon_temp *temp
56			= container_of(hwmon_attr, struct thermal_hwmon_temp,
57				       temp_input);
58	struct thermal_zone_device *tz = temp->tz;
59
60	ret = thermal_zone_get_temp(tz, &temperature);
61
62	if (ret)
63		return ret;
64
65	return sprintf(buf, "%d\n", temperature);
66}
67
68static ssize_t
69temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
70{
71	struct thermal_hwmon_attr *hwmon_attr
72			= container_of(attr, struct thermal_hwmon_attr, attr);
73	struct thermal_hwmon_temp *temp
74			= container_of(hwmon_attr, struct thermal_hwmon_temp,
75				       temp_crit);
76	struct thermal_zone_device *tz = temp->tz;
77	int temperature;
78	int ret;
79
80	ret = tz->ops->get_crit_temp(tz, &temperature);
81	if (ret)
82		return ret;
83
84	return sprintf(buf, "%d\n", temperature);
85}
86
87
88static struct thermal_hwmon_device *
89thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
90{
91	struct thermal_hwmon_device *hwmon;
92	char type[THERMAL_NAME_LENGTH];
93
94	mutex_lock(&thermal_hwmon_list_lock);
95	list_for_each_entry(hwmon, &thermal_hwmon_list, node) {
96		strcpy(type, tz->type);
97		strreplace(type, '-', '_');
98		if (!strcmp(hwmon->type, type)) {
99			mutex_unlock(&thermal_hwmon_list_lock);
100			return hwmon;
101		}
102	}
103	mutex_unlock(&thermal_hwmon_list_lock);
104
105	return NULL;
106}
107
108/* Find the temperature input matching a given thermal zone */
109static struct thermal_hwmon_temp *
110thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
111			  const struct thermal_zone_device *tz)
112{
113	struct thermal_hwmon_temp *temp;
114
115	mutex_lock(&thermal_hwmon_list_lock);
116	list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
117		if (temp->tz == tz) {
118			mutex_unlock(&thermal_hwmon_list_lock);
119			return temp;
120		}
121	mutex_unlock(&thermal_hwmon_list_lock);
122
123	return NULL;
124}
125
126static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz)
127{
128	int temp;
129	return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp);
130}
131
132int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
133{
134	struct thermal_hwmon_device *hwmon;
135	struct thermal_hwmon_temp *temp;
136	int new_hwmon_device = 1;
137	int result;
138
139	hwmon = thermal_hwmon_lookup_by_type(tz);
140	if (hwmon) {
141		new_hwmon_device = 0;
142		goto register_sys_interface;
143	}
144
145	hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
146	if (!hwmon)
147		return -ENOMEM;
148
149	INIT_LIST_HEAD(&hwmon->tz_list);
150	strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
151	strreplace(hwmon->type, '-', '_');
152	hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type,
153							hwmon, NULL, NULL);
154	if (IS_ERR(hwmon->device)) {
155		result = PTR_ERR(hwmon->device);
156		goto free_mem;
157	}
158
159 register_sys_interface:
160	temp = kzalloc(sizeof(*temp), GFP_KERNEL);
161	if (!temp) {
162		result = -ENOMEM;
163		goto unregister_name;
164	}
165
166	temp->tz = tz;
167	hwmon->count++;
168
169	snprintf(temp->temp_input.name, sizeof(temp->temp_input.name),
170		 "temp%d_input", hwmon->count);
171	temp->temp_input.attr.attr.name = temp->temp_input.name;
172	temp->temp_input.attr.attr.mode = 0444;
173	temp->temp_input.attr.show = temp_input_show;
174	sysfs_attr_init(&temp->temp_input.attr.attr);
175	result = device_create_file(hwmon->device, &temp->temp_input.attr);
176	if (result)
177		goto free_temp_mem;
178
179	if (thermal_zone_crit_temp_valid(tz)) {
180		snprintf(temp->temp_crit.name,
181				sizeof(temp->temp_crit.name),
182				"temp%d_crit", hwmon->count);
183		temp->temp_crit.attr.attr.name = temp->temp_crit.name;
184		temp->temp_crit.attr.attr.mode = 0444;
185		temp->temp_crit.attr.show = temp_crit_show;
186		sysfs_attr_init(&temp->temp_crit.attr.attr);
187		result = device_create_file(hwmon->device,
188					    &temp->temp_crit.attr);
189		if (result)
190			goto unregister_input;
191	}
192
193	mutex_lock(&thermal_hwmon_list_lock);
194	if (new_hwmon_device)
195		list_add_tail(&hwmon->node, &thermal_hwmon_list);
196	list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
197	mutex_unlock(&thermal_hwmon_list_lock);
198
199	return 0;
200
201 unregister_input:
202	device_remove_file(hwmon->device, &temp->temp_input.attr);
203 free_temp_mem:
204	kfree(temp);
205 unregister_name:
206	if (new_hwmon_device)
207		hwmon_device_unregister(hwmon->device);
208 free_mem:
209	if (new_hwmon_device)
210		kfree(hwmon);
211
212	return result;
213}
214EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs);
215
216void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
217{
218	struct thermal_hwmon_device *hwmon;
219	struct thermal_hwmon_temp *temp;
220
221	hwmon = thermal_hwmon_lookup_by_type(tz);
222	if (unlikely(!hwmon)) {
223		/* Should never happen... */
224		dev_dbg(&tz->device, "hwmon device lookup failed!\n");
225		return;
226	}
227
228	temp = thermal_hwmon_lookup_temp(hwmon, tz);
229	if (unlikely(!temp)) {
230		/* Should never happen... */
231		dev_dbg(&tz->device, "temperature input lookup failed!\n");
232		return;
233	}
234
235	device_remove_file(hwmon->device, &temp->temp_input.attr);
236	if (thermal_zone_crit_temp_valid(tz))
237		device_remove_file(hwmon->device, &temp->temp_crit.attr);
238
239	mutex_lock(&thermal_hwmon_list_lock);
240	list_del(&temp->hwmon_node);
241	kfree(temp);
242	if (!list_empty(&hwmon->tz_list)) {
243		mutex_unlock(&thermal_hwmon_list_lock);
244		return;
245	}
246	list_del(&hwmon->node);
247	mutex_unlock(&thermal_hwmon_list_lock);
248
249	hwmon_device_unregister(hwmon->device);
250	kfree(hwmon);
251}
252EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);
253
254static void devm_thermal_hwmon_release(struct device *dev, void *res)
255{
256	thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res);
257}
258
259int devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
260{
261	struct thermal_zone_device **ptr;
262	int ret;
263
264	ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr),
265			   GFP_KERNEL);
266	if (!ptr)
267		return -ENOMEM;
268
269	ret = thermal_add_hwmon_sysfs(tz);
270	if (ret) {
271		devres_free(ptr);
272		return ret;
273	}
274
275	*ptr = tz;
276	devres_add(&tz->device, ptr);
277
278	return ret;
279}
280EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs);
281