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