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 */
23 struct 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 
31 struct thermal_hwmon_attr {
32 	struct device_attribute attr;
33 	char name[16];
34 };
35 
36 /* one temperature input for each thermal zone */
37 struct 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 
44 static LIST_HEAD(thermal_hwmon_list);
45 
46 static DEFINE_MUTEX(thermal_hwmon_list_lock);
47 
48 static ssize_t
49 temp_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 
68 static ssize_t
69 temp_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 	mutex_lock(&tz->lock);
81 
82 	if (device_is_registered(&tz->device))
83 		ret = tz->ops->get_crit_temp(tz, &temperature);
84 	else
85 		ret = -ENODEV;
86 
87 	mutex_unlock(&tz->lock);
88 
89 	if (ret)
90 		return ret;
91 
92 	return sprintf(buf, "%d\n", temperature);
93 }
94 
95 
96 static struct thermal_hwmon_device *
97 thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
98 {
99 	struct thermal_hwmon_device *hwmon;
100 	char type[THERMAL_NAME_LENGTH];
101 
102 	mutex_lock(&thermal_hwmon_list_lock);
103 	list_for_each_entry(hwmon, &thermal_hwmon_list, node) {
104 		strcpy(type, tz->type);
105 		strreplace(type, '-', '_');
106 		if (!strcmp(hwmon->type, type)) {
107 			mutex_unlock(&thermal_hwmon_list_lock);
108 			return hwmon;
109 		}
110 	}
111 	mutex_unlock(&thermal_hwmon_list_lock);
112 
113 	return NULL;
114 }
115 
116 /* Find the temperature input matching a given thermal zone */
117 static struct thermal_hwmon_temp *
118 thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
119 			  const struct thermal_zone_device *tz)
120 {
121 	struct thermal_hwmon_temp *temp;
122 
123 	mutex_lock(&thermal_hwmon_list_lock);
124 	list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
125 		if (temp->tz == tz) {
126 			mutex_unlock(&thermal_hwmon_list_lock);
127 			return temp;
128 		}
129 	mutex_unlock(&thermal_hwmon_list_lock);
130 
131 	return NULL;
132 }
133 
134 static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz)
135 {
136 	int temp;
137 	return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp);
138 }
139 
140 int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
141 {
142 	struct thermal_hwmon_device *hwmon;
143 	struct thermal_hwmon_temp *temp;
144 	int new_hwmon_device = 1;
145 	int result;
146 
147 	hwmon = thermal_hwmon_lookup_by_type(tz);
148 	if (hwmon) {
149 		new_hwmon_device = 0;
150 		goto register_sys_interface;
151 	}
152 
153 	hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
154 	if (!hwmon)
155 		return -ENOMEM;
156 
157 	INIT_LIST_HEAD(&hwmon->tz_list);
158 	strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
159 	strreplace(hwmon->type, '-', '_');
160 	hwmon->device = hwmon_device_register_for_thermal(&tz->device,
161 							  hwmon->type, hwmon);
162 	if (IS_ERR(hwmon->device)) {
163 		result = PTR_ERR(hwmon->device);
164 		goto free_mem;
165 	}
166 
167  register_sys_interface:
168 	temp = kzalloc(sizeof(*temp), GFP_KERNEL);
169 	if (!temp) {
170 		result = -ENOMEM;
171 		goto unregister_name;
172 	}
173 
174 	temp->tz = tz;
175 	hwmon->count++;
176 
177 	snprintf(temp->temp_input.name, sizeof(temp->temp_input.name),
178 		 "temp%d_input", hwmon->count);
179 	temp->temp_input.attr.attr.name = temp->temp_input.name;
180 	temp->temp_input.attr.attr.mode = 0444;
181 	temp->temp_input.attr.show = temp_input_show;
182 	sysfs_attr_init(&temp->temp_input.attr.attr);
183 	result = device_create_file(hwmon->device, &temp->temp_input.attr);
184 	if (result)
185 		goto free_temp_mem;
186 
187 	if (thermal_zone_crit_temp_valid(tz)) {
188 		snprintf(temp->temp_crit.name,
189 				sizeof(temp->temp_crit.name),
190 				"temp%d_crit", hwmon->count);
191 		temp->temp_crit.attr.attr.name = temp->temp_crit.name;
192 		temp->temp_crit.attr.attr.mode = 0444;
193 		temp->temp_crit.attr.show = temp_crit_show;
194 		sysfs_attr_init(&temp->temp_crit.attr.attr);
195 		result = device_create_file(hwmon->device,
196 					    &temp->temp_crit.attr);
197 		if (result)
198 			goto unregister_input;
199 	}
200 
201 	mutex_lock(&thermal_hwmon_list_lock);
202 	if (new_hwmon_device)
203 		list_add_tail(&hwmon->node, &thermal_hwmon_list);
204 	list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
205 	mutex_unlock(&thermal_hwmon_list_lock);
206 
207 	return 0;
208 
209  unregister_input:
210 	device_remove_file(hwmon->device, &temp->temp_input.attr);
211  free_temp_mem:
212 	kfree(temp);
213  unregister_name:
214 	if (new_hwmon_device)
215 		hwmon_device_unregister(hwmon->device);
216  free_mem:
217 	kfree(hwmon);
218 
219 	return result;
220 }
221 EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs);
222 
223 void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
224 {
225 	struct thermal_hwmon_device *hwmon;
226 	struct thermal_hwmon_temp *temp;
227 
228 	hwmon = thermal_hwmon_lookup_by_type(tz);
229 	if (unlikely(!hwmon)) {
230 		/* Should never happen... */
231 		dev_dbg(&tz->device, "hwmon device lookup failed!\n");
232 		return;
233 	}
234 
235 	temp = thermal_hwmon_lookup_temp(hwmon, tz);
236 	if (unlikely(!temp)) {
237 		/* Should never happen... */
238 		dev_dbg(&tz->device, "temperature input lookup failed!\n");
239 		return;
240 	}
241 
242 	device_remove_file(hwmon->device, &temp->temp_input.attr);
243 	if (thermal_zone_crit_temp_valid(tz))
244 		device_remove_file(hwmon->device, &temp->temp_crit.attr);
245 
246 	mutex_lock(&thermal_hwmon_list_lock);
247 	list_del(&temp->hwmon_node);
248 	kfree(temp);
249 	if (!list_empty(&hwmon->tz_list)) {
250 		mutex_unlock(&thermal_hwmon_list_lock);
251 		return;
252 	}
253 	list_del(&hwmon->node);
254 	mutex_unlock(&thermal_hwmon_list_lock);
255 
256 	hwmon_device_unregister(hwmon->device);
257 	kfree(hwmon);
258 }
259 EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);
260 
261 static void devm_thermal_hwmon_release(struct device *dev, void *res)
262 {
263 	thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res);
264 }
265 
266 int devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
267 {
268 	struct thermal_zone_device **ptr;
269 	int ret;
270 
271 	ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr),
272 			   GFP_KERNEL);
273 	if (!ptr)
274 		return -ENOMEM;
275 
276 	ret = thermal_add_hwmon_sysfs(tz);
277 	if (ret) {
278 		devres_free(ptr);
279 		return ret;
280 	}
281 
282 	*ptr = tz;
283 	devres_add(&tz->device, ptr);
284 
285 	return ret;
286 }
287 EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs);
288 
289 MODULE_IMPORT_NS(HWMON_THERMAL);
290