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