Commit | Line | Data |
---|---|---|
7e3c0381 | 1 | // SPDX-License-Identifier: GPL-2.0 |
a369ee88 EV |
2 | /* |
3 | * thermal.c - sysfs interface of thermal devices | |
4 | * | |
5 | * Copyright (C) 2016 Eduardo Valentin <edubezval@gmail.com> | |
6 | * | |
7 | * Highly based on original thermal_core.c | |
8 | * Copyright (C) 2008 Intel Corp | |
9 | * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> | |
10 | * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> | |
a369ee88 EV |
11 | */ |
12 | ||
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
14 | ||
15 | #include <linux/sysfs.h> | |
16 | #include <linux/device.h> | |
17 | #include <linux/err.h> | |
18 | #include <linux/slab.h> | |
19 | #include <linux/string.h> | |
8ea22951 | 20 | #include <linux/jiffies.h> |
a369ee88 EV |
21 | |
22 | #include "thermal_core.h" | |
23 | ||
24 | /* sys I/F for thermal zone */ | |
25 | ||
26 | static ssize_t | |
27 | type_show(struct device *dev, struct device_attribute *attr, char *buf) | |
28 | { | |
29 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
30 | ||
31 | return sprintf(buf, "%s\n", tz->type); | |
32 | } | |
33 | ||
34 | static ssize_t | |
35 | temp_show(struct device *dev, struct device_attribute *attr, char *buf) | |
36 | { | |
37 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
38 | int temperature, ret; | |
39 | ||
40 | ret = thermal_zone_get_temp(tz, &temperature); | |
41 | ||
42 | if (ret) | |
43 | return ret; | |
44 | ||
45 | return sprintf(buf, "%d\n", temperature); | |
46 | } | |
47 | ||
48 | static ssize_t | |
49 | mode_show(struct device *dev, struct device_attribute *attr, char *buf) | |
50 | { | |
51 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
a930da9b DL |
52 | int enabled; |
53 | ||
54 | mutex_lock(&tz->lock); | |
55 | enabled = thermal_zone_device_is_enabled(tz); | |
56 | mutex_unlock(&tz->lock); | |
a369ee88 | 57 | |
7f4957be | 58 | return sprintf(buf, "%s\n", enabled ? "enabled" : "disabled"); |
a369ee88 EV |
59 | } |
60 | ||
61 | static ssize_t | |
62 | mode_store(struct device *dev, struct device_attribute *attr, | |
63 | const char *buf, size_t count) | |
64 | { | |
65 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
66 | int result; | |
67 | ||
a369ee88 | 68 | if (!strncmp(buf, "enabled", sizeof("enabled") - 1)) |
7f4957be | 69 | result = thermal_zone_device_enable(tz); |
a369ee88 | 70 | else if (!strncmp(buf, "disabled", sizeof("disabled") - 1)) |
7f4957be | 71 | result = thermal_zone_device_disable(tz); |
a369ee88 EV |
72 | else |
73 | result = -EINVAL; | |
74 | ||
75 | if (result) | |
76 | return result; | |
77 | ||
78 | return count; | |
79 | } | |
80 | ||
81 | static ssize_t | |
82 | trip_point_type_show(struct device *dev, struct device_attribute *attr, | |
83 | char *buf) | |
84 | { | |
85 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
7c3d5c20 DL |
86 | struct thermal_trip trip; |
87 | int trip_id, result; | |
a369ee88 | 88 | |
7c3d5c20 | 89 | if (sscanf(attr->attr.name, "trip_point_%d_type", &trip_id) != 1) |
a369ee88 EV |
90 | return -EINVAL; |
91 | ||
05eeee2b GR |
92 | mutex_lock(&tz->lock); |
93 | ||
94 | if (device_is_registered(dev)) | |
7c3d5c20 | 95 | result = __thermal_zone_get_trip(tz, trip_id, &trip); |
05eeee2b GR |
96 | else |
97 | result = -ENODEV; | |
98 | ||
99 | mutex_unlock(&tz->lock); | |
7c3d5c20 | 100 | |
a369ee88 EV |
101 | if (result) |
102 | return result; | |
103 | ||
7c3d5c20 | 104 | switch (trip.type) { |
a369ee88 EV |
105 | case THERMAL_TRIP_CRITICAL: |
106 | return sprintf(buf, "critical\n"); | |
107 | case THERMAL_TRIP_HOT: | |
108 | return sprintf(buf, "hot\n"); | |
109 | case THERMAL_TRIP_PASSIVE: | |
110 | return sprintf(buf, "passive\n"); | |
111 | case THERMAL_TRIP_ACTIVE: | |
112 | return sprintf(buf, "active\n"); | |
113 | default: | |
114 | return sprintf(buf, "unknown\n"); | |
115 | } | |
116 | } | |
117 | ||
118 | static ssize_t | |
119 | trip_point_temp_store(struct device *dev, struct device_attribute *attr, | |
120 | const char *buf, size_t count) | |
121 | { | |
122 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
7c3d5c20 DL |
123 | struct thermal_trip trip; |
124 | int trip_id, ret; | |
a369ee88 | 125 | |
7c3d5c20 | 126 | if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip_id) != 1) |
a369ee88 EV |
127 | return -EINVAL; |
128 | ||
05eeee2b GR |
129 | mutex_lock(&tz->lock); |
130 | ||
131 | if (!device_is_registered(dev)) { | |
132 | ret = -ENODEV; | |
133 | goto unlock; | |
134 | } | |
135 | ||
7c3d5c20 | 136 | ret = __thermal_zone_get_trip(tz, trip_id, &trip); |
55cdf0a2 | 137 | if (ret) |
05eeee2b | 138 | goto unlock; |
55cdf0a2 | 139 | |
2e38a2a9 DL |
140 | ret = kstrtoint(buf, 10, &trip.temperature); |
141 | if (ret) | |
142 | goto unlock; | |
05eeee2b | 143 | |
2e38a2a9 | 144 | ret = thermal_zone_set_trip(tz, trip_id, &trip); |
05eeee2b GR |
145 | unlock: |
146 | mutex_unlock(&tz->lock); | |
2e38a2a9 DL |
147 | |
148 | return ret ? ret : count; | |
a369ee88 EV |
149 | } |
150 | ||
151 | static ssize_t | |
152 | trip_point_temp_show(struct device *dev, struct device_attribute *attr, | |
153 | char *buf) | |
154 | { | |
155 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
7c3d5c20 DL |
156 | struct thermal_trip trip; |
157 | int trip_id, ret; | |
a369ee88 | 158 | |
7c3d5c20 | 159 | if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip_id) != 1) |
a369ee88 EV |
160 | return -EINVAL; |
161 | ||
05eeee2b GR |
162 | mutex_lock(&tz->lock); |
163 | ||
164 | if (device_is_registered(dev)) | |
7c3d5c20 | 165 | ret = __thermal_zone_get_trip(tz, trip_id, &trip); |
05eeee2b GR |
166 | else |
167 | ret = -ENODEV; | |
168 | ||
169 | mutex_unlock(&tz->lock); | |
a369ee88 EV |
170 | |
171 | if (ret) | |
172 | return ret; | |
173 | ||
7c3d5c20 | 174 | return sprintf(buf, "%d\n", trip.temperature); |
a369ee88 EV |
175 | } |
176 | ||
177 | static ssize_t | |
178 | trip_point_hyst_store(struct device *dev, struct device_attribute *attr, | |
179 | const char *buf, size_t count) | |
180 | { | |
181 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
2e38a2a9 DL |
182 | struct thermal_trip trip; |
183 | int trip_id, ret; | |
a369ee88 | 184 | |
2e38a2a9 | 185 | if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip_id) != 1) |
a369ee88 EV |
186 | return -EINVAL; |
187 | ||
2e38a2a9 | 188 | if (kstrtoint(buf, 10, &trip.hysteresis)) |
a369ee88 EV |
189 | return -EINVAL; |
190 | ||
05eeee2b GR |
191 | mutex_lock(&tz->lock); |
192 | ||
193 | if (!device_is_registered(dev)) { | |
194 | ret = -ENODEV; | |
195 | goto unlock; | |
196 | } | |
197 | ||
2e38a2a9 DL |
198 | ret = __thermal_zone_get_trip(tz, trip_id, &trip); |
199 | if (ret) | |
200 | goto unlock; | |
201 | ||
202 | ret = thermal_zone_set_trip(tz, trip_id, &trip); | |
05eeee2b GR |
203 | unlock: |
204 | mutex_unlock(&tz->lock); | |
a369ee88 EV |
205 | |
206 | return ret ? ret : count; | |
207 | } | |
208 | ||
209 | static ssize_t | |
210 | trip_point_hyst_show(struct device *dev, struct device_attribute *attr, | |
211 | char *buf) | |
212 | { | |
213 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
7c3d5c20 DL |
214 | struct thermal_trip trip; |
215 | int trip_id, ret; | |
a369ee88 | 216 | |
7c3d5c20 | 217 | if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip_id) != 1) |
a369ee88 EV |
218 | return -EINVAL; |
219 | ||
05eeee2b GR |
220 | mutex_lock(&tz->lock); |
221 | ||
222 | if (device_is_registered(dev)) | |
7c3d5c20 | 223 | ret = __thermal_zone_get_trip(tz, trip_id, &trip); |
05eeee2b GR |
224 | else |
225 | ret = -ENODEV; | |
226 | ||
227 | mutex_unlock(&tz->lock); | |
a369ee88 | 228 | |
7c3d5c20 | 229 | return ret ? ret : sprintf(buf, "%d\n", trip.hysteresis); |
a369ee88 EV |
230 | } |
231 | ||
a369ee88 EV |
232 | static ssize_t |
233 | policy_store(struct device *dev, struct device_attribute *attr, | |
234 | const char *buf, size_t count) | |
235 | { | |
236 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
237 | char name[THERMAL_NAME_LENGTH]; | |
238 | int ret; | |
239 | ||
240 | snprintf(name, sizeof(name), "%s", buf); | |
241 | ||
242 | ret = thermal_zone_device_set_policy(tz, name); | |
243 | if (!ret) | |
244 | ret = count; | |
245 | ||
246 | return ret; | |
247 | } | |
248 | ||
249 | static ssize_t | |
250 | policy_show(struct device *dev, struct device_attribute *devattr, char *buf) | |
251 | { | |
252 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
253 | ||
254 | return sprintf(buf, "%s\n", tz->governor->name); | |
255 | } | |
256 | ||
257 | static ssize_t | |
258 | available_policies_show(struct device *dev, struct device_attribute *devattr, | |
259 | char *buf) | |
260 | { | |
261 | return thermal_build_list_of_policies(buf); | |
262 | } | |
263 | ||
698db4fd | 264 | #if (IS_ENABLED(CONFIG_THERMAL_EMULATION)) |
a369ee88 EV |
265 | static ssize_t |
266 | emul_temp_store(struct device *dev, struct device_attribute *attr, | |
267 | const char *buf, size_t count) | |
268 | { | |
269 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
270 | int ret = 0; | |
271 | int temperature; | |
272 | ||
273 | if (kstrtoint(buf, 10, &temperature)) | |
274 | return -EINVAL; | |
275 | ||
05eeee2b GR |
276 | mutex_lock(&tz->lock); |
277 | ||
278 | if (!device_is_registered(dev)) { | |
279 | ret = -ENODEV; | |
280 | goto unlock; | |
281 | } | |
282 | ||
283 | if (!tz->ops->set_emul_temp) | |
a369ee88 | 284 | tz->emul_temperature = temperature; |
05eeee2b | 285 | else |
a369ee88 | 286 | ret = tz->ops->set_emul_temp(tz, temperature); |
a369ee88 EV |
287 | |
288 | if (!ret) | |
05eeee2b GR |
289 | __thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); |
290 | ||
291 | unlock: | |
292 | mutex_unlock(&tz->lock); | |
a369ee88 EV |
293 | |
294 | return ret ? ret : count; | |
295 | } | |
6cbaefb4 | 296 | static DEVICE_ATTR_WO(emul_temp); |
698db4fd | 297 | #endif |
a369ee88 EV |
298 | |
299 | static ssize_t | |
300 | sustainable_power_show(struct device *dev, struct device_attribute *devattr, | |
301 | char *buf) | |
302 | { | |
303 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
304 | ||
305 | if (tz->tzp) | |
306 | return sprintf(buf, "%u\n", tz->tzp->sustainable_power); | |
307 | else | |
308 | return -EIO; | |
309 | } | |
310 | ||
311 | static ssize_t | |
312 | sustainable_power_store(struct device *dev, struct device_attribute *devattr, | |
313 | const char *buf, size_t count) | |
314 | { | |
315 | struct thermal_zone_device *tz = to_thermal_zone(dev); | |
316 | u32 sustainable_power; | |
317 | ||
318 | if (!tz->tzp) | |
319 | return -EIO; | |
320 | ||
321 | if (kstrtou32(buf, 10, &sustainable_power)) | |
322 | return -EINVAL; | |
323 | ||
324 | tz->tzp->sustainable_power = sustainable_power; | |
325 | ||
326 | return count; | |
327 | } | |
328 | ||
329 | #define create_s32_tzp_attr(name) \ | |
330 | static ssize_t \ | |
331 | name##_show(struct device *dev, struct device_attribute *devattr, \ | |
332 | char *buf) \ | |
333 | { \ | |
334 | struct thermal_zone_device *tz = to_thermal_zone(dev); \ | |
335 | \ | |
336 | if (tz->tzp) \ | |
337 | return sprintf(buf, "%d\n", tz->tzp->name); \ | |
338 | else \ | |
339 | return -EIO; \ | |
340 | } \ | |
341 | \ | |
342 | static ssize_t \ | |
343 | name##_store(struct device *dev, struct device_attribute *devattr, \ | |
344 | const char *buf, size_t count) \ | |
345 | { \ | |
346 | struct thermal_zone_device *tz = to_thermal_zone(dev); \ | |
347 | s32 value; \ | |
348 | \ | |
349 | if (!tz->tzp) \ | |
350 | return -EIO; \ | |
351 | \ | |
352 | if (kstrtos32(buf, 10, &value)) \ | |
353 | return -EINVAL; \ | |
354 | \ | |
355 | tz->tzp->name = value; \ | |
356 | \ | |
357 | return count; \ | |
358 | } \ | |
e76a4386 | 359 | static DEVICE_ATTR_RW(name) |
a369ee88 EV |
360 | |
361 | create_s32_tzp_attr(k_po); | |
362 | create_s32_tzp_attr(k_pu); | |
363 | create_s32_tzp_attr(k_i); | |
364 | create_s32_tzp_attr(k_d); | |
365 | create_s32_tzp_attr(integral_cutoff); | |
366 | create_s32_tzp_attr(slope); | |
367 | create_s32_tzp_attr(offset); | |
368 | #undef create_s32_tzp_attr | |
369 | ||
370 | /* | |
371 | * These are thermal zone device attributes that will always be present. | |
372 | * All the attributes created for tzp (create_s32_tzp_attr) also are always | |
373 | * present on the sysfs interface. | |
374 | */ | |
c828a892 JP |
375 | static DEVICE_ATTR_RO(type); |
376 | static DEVICE_ATTR_RO(temp); | |
b6b996b6 | 377 | static DEVICE_ATTR_RW(policy); |
c828a892 | 378 | static DEVICE_ATTR_RO(available_policies); |
b6b996b6 | 379 | static DEVICE_ATTR_RW(sustainable_power); |
a369ee88 EV |
380 | |
381 | /* These thermal zone device attributes are created based on conditions */ | |
b6b996b6 | 382 | static DEVICE_ATTR_RW(mode); |
a369ee88 EV |
383 | |
384 | /* These attributes are unconditionally added to a thermal zone */ | |
385 | static struct attribute *thermal_zone_dev_attrs[] = { | |
386 | &dev_attr_type.attr, | |
387 | &dev_attr_temp.attr, | |
388 | #if (IS_ENABLED(CONFIG_THERMAL_EMULATION)) | |
389 | &dev_attr_emul_temp.attr, | |
390 | #endif | |
391 | &dev_attr_policy.attr, | |
392 | &dev_attr_available_policies.attr, | |
393 | &dev_attr_sustainable_power.attr, | |
394 | &dev_attr_k_po.attr, | |
395 | &dev_attr_k_pu.attr, | |
396 | &dev_attr_k_i.attr, | |
397 | &dev_attr_k_d.attr, | |
398 | &dev_attr_integral_cutoff.attr, | |
399 | &dev_attr_slope.attr, | |
400 | &dev_attr_offset.attr, | |
401 | NULL, | |
402 | }; | |
403 | ||
f74bed6a | 404 | static const struct attribute_group thermal_zone_attribute_group = { |
a369ee88 EV |
405 | .attrs = thermal_zone_dev_attrs, |
406 | }; | |
407 | ||
a369ee88 EV |
408 | static struct attribute *thermal_zone_mode_attrs[] = { |
409 | &dev_attr_mode.attr, | |
410 | NULL, | |
411 | }; | |
412 | ||
f74bed6a | 413 | static const struct attribute_group thermal_zone_mode_attribute_group = { |
a369ee88 | 414 | .attrs = thermal_zone_mode_attrs, |
a369ee88 EV |
415 | }; |
416 | ||
a369ee88 EV |
417 | static const struct attribute_group *thermal_zone_attribute_groups[] = { |
418 | &thermal_zone_attribute_group, | |
419 | &thermal_zone_mode_attribute_group, | |
a369ee88 EV |
420 | /* This is not NULL terminated as we create the group dynamically */ |
421 | }; | |
422 | ||
423 | /** | |
424 | * create_trip_attrs() - create attributes for trip points | |
425 | * @tz: the thermal zone device | |
426 | * @mask: Writeable trip point bitmap. | |
427 | * | |
428 | * helper function to instantiate sysfs entries for every trip | |
429 | * point and its properties of a struct thermal_zone_device. | |
430 | * | |
431 | * Return: 0 on success, the proper error value otherwise. | |
432 | */ | |
433 | static int create_trip_attrs(struct thermal_zone_device *tz, int mask) | |
434 | { | |
a369ee88 EV |
435 | struct attribute **attrs; |
436 | int indx; | |
437 | ||
438 | /* This function works only for zones with at least one trip */ | |
e5bfcd30 | 439 | if (tz->num_trips <= 0) |
a369ee88 EV |
440 | return -EINVAL; |
441 | ||
e5bfcd30 | 442 | tz->trip_type_attrs = kcalloc(tz->num_trips, sizeof(*tz->trip_type_attrs), |
b819dc9e | 443 | GFP_KERNEL); |
a369ee88 EV |
444 | if (!tz->trip_type_attrs) |
445 | return -ENOMEM; | |
446 | ||
e5bfcd30 | 447 | tz->trip_temp_attrs = kcalloc(tz->num_trips, sizeof(*tz->trip_temp_attrs), |
b819dc9e | 448 | GFP_KERNEL); |
a369ee88 EV |
449 | if (!tz->trip_temp_attrs) { |
450 | kfree(tz->trip_type_attrs); | |
451 | return -ENOMEM; | |
452 | } | |
453 | ||
0614755d DL |
454 | tz->trip_hyst_attrs = kcalloc(tz->num_trips, |
455 | sizeof(*tz->trip_hyst_attrs), | |
456 | GFP_KERNEL); | |
457 | if (!tz->trip_hyst_attrs) { | |
458 | kfree(tz->trip_type_attrs); | |
459 | kfree(tz->trip_temp_attrs); | |
460 | return -ENOMEM; | |
a369ee88 EV |
461 | } |
462 | ||
e5bfcd30 | 463 | attrs = kcalloc(tz->num_trips * 3 + 1, sizeof(*attrs), GFP_KERNEL); |
a369ee88 EV |
464 | if (!attrs) { |
465 | kfree(tz->trip_type_attrs); | |
466 | kfree(tz->trip_temp_attrs); | |
0614755d | 467 | kfree(tz->trip_hyst_attrs); |
a369ee88 EV |
468 | return -ENOMEM; |
469 | } | |
470 | ||
e5bfcd30 | 471 | for (indx = 0; indx < tz->num_trips; indx++) { |
a369ee88 EV |
472 | /* create trip type attribute */ |
473 | snprintf(tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH, | |
474 | "trip_point_%d_type", indx); | |
475 | ||
476 | sysfs_attr_init(&tz->trip_type_attrs[indx].attr.attr); | |
477 | tz->trip_type_attrs[indx].attr.attr.name = | |
478 | tz->trip_type_attrs[indx].name; | |
479 | tz->trip_type_attrs[indx].attr.attr.mode = S_IRUGO; | |
480 | tz->trip_type_attrs[indx].attr.show = trip_point_type_show; | |
481 | attrs[indx] = &tz->trip_type_attrs[indx].attr.attr; | |
482 | ||
483 | /* create trip temp attribute */ | |
484 | snprintf(tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH, | |
485 | "trip_point_%d_temp", indx); | |
486 | ||
487 | sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr); | |
488 | tz->trip_temp_attrs[indx].attr.attr.name = | |
489 | tz->trip_temp_attrs[indx].name; | |
490 | tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO; | |
491 | tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show; | |
492 | if (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) && | |
493 | mask & (1 << indx)) { | |
494 | tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR; | |
495 | tz->trip_temp_attrs[indx].attr.store = | |
496 | trip_point_temp_store; | |
497 | } | |
e5bfcd30 | 498 | attrs[indx + tz->num_trips] = &tz->trip_temp_attrs[indx].attr.attr; |
a369ee88 | 499 | |
a369ee88 EV |
500 | snprintf(tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH, |
501 | "trip_point_%d_hyst", indx); | |
502 | ||
503 | sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr); | |
504 | tz->trip_hyst_attrs[indx].attr.attr.name = | |
505 | tz->trip_hyst_attrs[indx].name; | |
506 | tz->trip_hyst_attrs[indx].attr.attr.mode = S_IRUGO; | |
507 | tz->trip_hyst_attrs[indx].attr.show = trip_point_hyst_show; | |
508 | if (tz->ops->set_trip_hyst) { | |
509 | tz->trip_hyst_attrs[indx].attr.attr.mode |= S_IWUSR; | |
510 | tz->trip_hyst_attrs[indx].attr.store = | |
511 | trip_point_hyst_store; | |
512 | } | |
e5bfcd30 | 513 | attrs[indx + tz->num_trips * 2] = |
a369ee88 EV |
514 | &tz->trip_hyst_attrs[indx].attr.attr; |
515 | } | |
e5bfcd30 | 516 | attrs[tz->num_trips * 3] = NULL; |
a369ee88 EV |
517 | |
518 | tz->trips_attribute_group.attrs = attrs; | |
519 | ||
520 | return 0; | |
521 | } | |
522 | ||
32fa5ba3 CJ |
523 | /** |
524 | * destroy_trip_attrs() - destroy attributes for trip points | |
525 | * @tz: the thermal zone device | |
526 | * | |
527 | * helper function to free resources allocated by create_trip_attrs() | |
528 | */ | |
529 | static void destroy_trip_attrs(struct thermal_zone_device *tz) | |
530 | { | |
531 | if (!tz) | |
532 | return; | |
533 | ||
534 | kfree(tz->trip_type_attrs); | |
535 | kfree(tz->trip_temp_attrs); | |
0614755d | 536 | kfree(tz->trip_hyst_attrs); |
32fa5ba3 CJ |
537 | kfree(tz->trips_attribute_group.attrs); |
538 | } | |
539 | ||
a369ee88 EV |
540 | int thermal_zone_create_device_groups(struct thermal_zone_device *tz, |
541 | int mask) | |
542 | { | |
543 | const struct attribute_group **groups; | |
544 | int i, size, result; | |
545 | ||
546 | /* we need one extra for trips and the NULL to terminate the array */ | |
547 | size = ARRAY_SIZE(thermal_zone_attribute_groups) + 2; | |
548 | /* This also takes care of API requirement to be NULL terminated */ | |
549 | groups = kcalloc(size, sizeof(*groups), GFP_KERNEL); | |
550 | if (!groups) | |
551 | return -ENOMEM; | |
552 | ||
553 | for (i = 0; i < size - 2; i++) | |
554 | groups[i] = thermal_zone_attribute_groups[i]; | |
555 | ||
e5bfcd30 | 556 | if (tz->num_trips) { |
a369ee88 EV |
557 | result = create_trip_attrs(tz, mask); |
558 | if (result) { | |
559 | kfree(groups); | |
560 | ||
561 | return result; | |
562 | } | |
563 | ||
564 | groups[size - 2] = &tz->trips_attribute_group; | |
565 | } | |
566 | ||
567 | tz->device.groups = groups; | |
568 | ||
569 | return 0; | |
570 | } | |
45cf2ec9 | 571 | |
32fa5ba3 CJ |
572 | void thermal_zone_destroy_device_groups(struct thermal_zone_device *tz) |
573 | { | |
574 | if (!tz) | |
575 | return; | |
576 | ||
e5bfcd30 | 577 | if (tz->num_trips) |
32fa5ba3 CJ |
578 | destroy_trip_attrs(tz); |
579 | ||
580 | kfree(tz->device.groups); | |
581 | } | |
582 | ||
45cf2ec9 EV |
583 | /* sys I/F for cooling device */ |
584 | static ssize_t | |
33e678d4 | 585 | cdev_type_show(struct device *dev, struct device_attribute *attr, char *buf) |
45cf2ec9 EV |
586 | { |
587 | struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
588 | ||
589 | return sprintf(buf, "%s\n", cdev->type); | |
590 | } | |
591 | ||
33e678d4 VK |
592 | static ssize_t max_state_show(struct device *dev, struct device_attribute *attr, |
593 | char *buf) | |
45cf2ec9 EV |
594 | { |
595 | struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
45cf2ec9 | 596 | |
c408b3d1 | 597 | return sprintf(buf, "%ld\n", cdev->max_state); |
45cf2ec9 EV |
598 | } |
599 | ||
33e678d4 VK |
600 | static ssize_t cur_state_show(struct device *dev, struct device_attribute *attr, |
601 | char *buf) | |
45cf2ec9 EV |
602 | { |
603 | struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
604 | unsigned long state; | |
605 | int ret; | |
606 | ||
607 | ret = cdev->ops->get_cur_state(cdev, &state); | |
608 | if (ret) | |
609 | return ret; | |
610 | return sprintf(buf, "%ld\n", state); | |
611 | } | |
612 | ||
613 | static ssize_t | |
33e678d4 VK |
614 | cur_state_store(struct device *dev, struct device_attribute *attr, |
615 | const char *buf, size_t count) | |
45cf2ec9 EV |
616 | { |
617 | struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
618 | unsigned long state; | |
619 | int result; | |
620 | ||
621 | if (sscanf(buf, "%ld\n", &state) != 1) | |
622 | return -EINVAL; | |
623 | ||
624 | if ((long)state < 0) | |
625 | return -EINVAL; | |
626 | ||
c408b3d1 VK |
627 | /* Requested state should be less than max_state + 1 */ |
628 | if (state > cdev->max_state) | |
629 | return -EINVAL; | |
630 | ||
68000a0d TG |
631 | mutex_lock(&cdev->lock); |
632 | ||
45cf2ec9 | 633 | result = cdev->ops->set_cur_state(cdev, state); |
68000a0d TG |
634 | if (!result) |
635 | thermal_cooling_device_stats_update(cdev, state); | |
636 | ||
637 | mutex_unlock(&cdev->lock); | |
638 | return result ? result : count; | |
45cf2ec9 EV |
639 | } |
640 | ||
33e678d4 VK |
641 | static struct device_attribute |
642 | dev_attr_cdev_type = __ATTR(type, 0444, cdev_type_show, NULL); | |
e76a4386 VK |
643 | static DEVICE_ATTR_RO(max_state); |
644 | static DEVICE_ATTR_RW(cur_state); | |
45cf2ec9 EV |
645 | |
646 | static struct attribute *cooling_device_attrs[] = { | |
647 | &dev_attr_cdev_type.attr, | |
648 | &dev_attr_max_state.attr, | |
649 | &dev_attr_cur_state.attr, | |
650 | NULL, | |
651 | }; | |
652 | ||
653 | static const struct attribute_group cooling_device_attr_group = { | |
654 | .attrs = cooling_device_attrs, | |
655 | }; | |
656 | ||
657 | static const struct attribute_group *cooling_device_attr_groups[] = { | |
658 | &cooling_device_attr_group, | |
8ea22951 | 659 | NULL, /* Space allocated for cooling_device_stats_attr_group */ |
45cf2ec9 EV |
660 | NULL, |
661 | }; | |
662 | ||
8ea22951 VK |
663 | #ifdef CONFIG_THERMAL_STATISTICS |
664 | struct cooling_dev_stats { | |
665 | spinlock_t lock; | |
666 | unsigned int total_trans; | |
667 | unsigned long state; | |
8ea22951 VK |
668 | ktime_t last_time; |
669 | ktime_t *time_in_state; | |
670 | unsigned int *trans_table; | |
671 | }; | |
672 | ||
673 | static void update_time_in_state(struct cooling_dev_stats *stats) | |
674 | { | |
675 | ktime_t now = ktime_get(), delta; | |
676 | ||
677 | delta = ktime_sub(now, stats->last_time); | |
678 | stats->time_in_state[stats->state] = | |
679 | ktime_add(stats->time_in_state[stats->state], delta); | |
680 | stats->last_time = now; | |
681 | } | |
682 | ||
683 | void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev, | |
684 | unsigned long new_state) | |
685 | { | |
686 | struct cooling_dev_stats *stats = cdev->stats; | |
687 | ||
790930f4 RW |
688 | lockdep_assert_held(&cdev->lock); |
689 | ||
2046a24a MMP |
690 | if (!stats) |
691 | return; | |
692 | ||
8ea22951 VK |
693 | spin_lock(&stats->lock); |
694 | ||
695 | if (stats->state == new_state) | |
696 | goto unlock; | |
697 | ||
698 | update_time_in_state(stats); | |
a365105c | 699 | stats->trans_table[stats->state * (cdev->max_state + 1) + new_state]++; |
8ea22951 VK |
700 | stats->state = new_state; |
701 | stats->total_trans++; | |
702 | ||
703 | unlock: | |
704 | spin_unlock(&stats->lock); | |
705 | } | |
706 | ||
33e678d4 VK |
707 | static ssize_t total_trans_show(struct device *dev, |
708 | struct device_attribute *attr, char *buf) | |
8ea22951 VK |
709 | { |
710 | struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
790930f4 RW |
711 | struct cooling_dev_stats *stats; |
712 | int ret = 0; | |
713 | ||
714 | mutex_lock(&cdev->lock); | |
715 | ||
716 | stats = cdev->stats; | |
717 | if (!stats) | |
718 | goto unlock; | |
8ea22951 VK |
719 | |
720 | spin_lock(&stats->lock); | |
721 | ret = sprintf(buf, "%u\n", stats->total_trans); | |
722 | spin_unlock(&stats->lock); | |
723 | ||
790930f4 RW |
724 | unlock: |
725 | mutex_unlock(&cdev->lock); | |
726 | ||
8ea22951 VK |
727 | return ret; |
728 | } | |
729 | ||
730 | static ssize_t | |
33e678d4 VK |
731 | time_in_state_ms_show(struct device *dev, struct device_attribute *attr, |
732 | char *buf) | |
8ea22951 VK |
733 | { |
734 | struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
790930f4 | 735 | struct cooling_dev_stats *stats; |
8ea22951 VK |
736 | ssize_t len = 0; |
737 | int i; | |
738 | ||
790930f4 RW |
739 | mutex_lock(&cdev->lock); |
740 | ||
741 | stats = cdev->stats; | |
742 | if (!stats) | |
743 | goto unlock; | |
744 | ||
8ea22951 | 745 | spin_lock(&stats->lock); |
790930f4 | 746 | |
8ea22951 VK |
747 | update_time_in_state(stats); |
748 | ||
a365105c | 749 | for (i = 0; i <= cdev->max_state; i++) { |
8ea22951 VK |
750 | len += sprintf(buf + len, "state%u\t%llu\n", i, |
751 | ktime_to_ms(stats->time_in_state[i])); | |
752 | } | |
753 | spin_unlock(&stats->lock); | |
754 | ||
790930f4 RW |
755 | unlock: |
756 | mutex_unlock(&cdev->lock); | |
757 | ||
8ea22951 VK |
758 | return len; |
759 | } | |
760 | ||
761 | static ssize_t | |
33e678d4 VK |
762 | reset_store(struct device *dev, struct device_attribute *attr, const char *buf, |
763 | size_t count) | |
8ea22951 VK |
764 | { |
765 | struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
790930f4 RW |
766 | struct cooling_dev_stats *stats; |
767 | int i, states; | |
768 | ||
769 | mutex_lock(&cdev->lock); | |
770 | ||
771 | stats = cdev->stats; | |
772 | if (!stats) | |
773 | goto unlock; | |
774 | ||
775 | states = cdev->max_state + 1; | |
8ea22951 VK |
776 | |
777 | spin_lock(&stats->lock); | |
778 | ||
779 | stats->total_trans = 0; | |
780 | stats->last_time = ktime_get(); | |
781 | memset(stats->trans_table, 0, | |
782 | states * states * sizeof(*stats->trans_table)); | |
783 | ||
a365105c | 784 | for (i = 0; i < states; i++) |
8ea22951 VK |
785 | stats->time_in_state[i] = ktime_set(0, 0); |
786 | ||
787 | spin_unlock(&stats->lock); | |
788 | ||
790930f4 RW |
789 | unlock: |
790 | mutex_unlock(&cdev->lock); | |
791 | ||
8ea22951 VK |
792 | return count; |
793 | } | |
794 | ||
33e678d4 VK |
795 | static ssize_t trans_table_show(struct device *dev, |
796 | struct device_attribute *attr, char *buf) | |
8ea22951 VK |
797 | { |
798 | struct thermal_cooling_device *cdev = to_cooling_device(dev); | |
790930f4 | 799 | struct cooling_dev_stats *stats; |
8ea22951 VK |
800 | ssize_t len = 0; |
801 | int i, j; | |
802 | ||
790930f4 RW |
803 | mutex_lock(&cdev->lock); |
804 | ||
805 | stats = cdev->stats; | |
806 | if (!stats) { | |
807 | len = -ENODATA; | |
808 | goto unlock; | |
809 | } | |
810 | ||
8ea22951 VK |
811 | len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n"); |
812 | len += snprintf(buf + len, PAGE_SIZE - len, " : "); | |
a365105c | 813 | for (i = 0; i <= cdev->max_state; i++) { |
8ea22951 VK |
814 | if (len >= PAGE_SIZE) |
815 | break; | |
816 | len += snprintf(buf + len, PAGE_SIZE - len, "state%2u ", i); | |
817 | } | |
790930f4 RW |
818 | if (len >= PAGE_SIZE) { |
819 | len = PAGE_SIZE; | |
820 | goto unlock; | |
821 | } | |
8ea22951 VK |
822 | |
823 | len += snprintf(buf + len, PAGE_SIZE - len, "\n"); | |
824 | ||
a365105c | 825 | for (i = 0; i <= cdev->max_state; i++) { |
8ea22951 VK |
826 | if (len >= PAGE_SIZE) |
827 | break; | |
828 | ||
829 | len += snprintf(buf + len, PAGE_SIZE - len, "state%2u:", i); | |
830 | ||
a365105c | 831 | for (j = 0; j <= cdev->max_state; j++) { |
8ea22951 VK |
832 | if (len >= PAGE_SIZE) |
833 | break; | |
834 | len += snprintf(buf + len, PAGE_SIZE - len, "%8u ", | |
a365105c | 835 | stats->trans_table[i * (cdev->max_state + 1) + j]); |
8ea22951 VK |
836 | } |
837 | if (len >= PAGE_SIZE) | |
838 | break; | |
839 | len += snprintf(buf + len, PAGE_SIZE - len, "\n"); | |
840 | } | |
841 | ||
842 | if (len >= PAGE_SIZE) { | |
843 | pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n"); | |
790930f4 | 844 | len = -EFBIG; |
8ea22951 | 845 | } |
790930f4 RW |
846 | |
847 | unlock: | |
848 | mutex_unlock(&cdev->lock); | |
849 | ||
8ea22951 VK |
850 | return len; |
851 | } | |
852 | ||
e76a4386 VK |
853 | static DEVICE_ATTR_RO(total_trans); |
854 | static DEVICE_ATTR_RO(time_in_state_ms); | |
855 | static DEVICE_ATTR_WO(reset); | |
856 | static DEVICE_ATTR_RO(trans_table); | |
8ea22951 VK |
857 | |
858 | static struct attribute *cooling_device_stats_attrs[] = { | |
859 | &dev_attr_total_trans.attr, | |
860 | &dev_attr_time_in_state_ms.attr, | |
861 | &dev_attr_reset.attr, | |
862 | &dev_attr_trans_table.attr, | |
863 | NULL | |
864 | }; | |
865 | ||
866 | static const struct attribute_group cooling_device_stats_attr_group = { | |
867 | .attrs = cooling_device_stats_attrs, | |
868 | .name = "stats" | |
869 | }; | |
870 | ||
871 | static void cooling_device_stats_setup(struct thermal_cooling_device *cdev) | |
872 | { | |
d5a8aa5d | 873 | const struct attribute_group *stats_attr_group = NULL; |
8ea22951 | 874 | struct cooling_dev_stats *stats; |
a365105c VK |
875 | /* Total number of states is highest state + 1 */ |
876 | unsigned long states = cdev->max_state + 1; | |
8ea22951 VK |
877 | int var; |
878 | ||
8ea22951 VK |
879 | var = sizeof(*stats); |
880 | var += sizeof(*stats->time_in_state) * states; | |
881 | var += sizeof(*stats->trans_table) * states * states; | |
882 | ||
883 | stats = kzalloc(var, GFP_KERNEL); | |
884 | if (!stats) | |
d5a8aa5d | 885 | goto out; |
8ea22951 VK |
886 | |
887 | stats->time_in_state = (ktime_t *)(stats + 1); | |
888 | stats->trans_table = (unsigned int *)(stats->time_in_state + states); | |
889 | cdev->stats = stats; | |
890 | stats->last_time = ktime_get(); | |
8ea22951 VK |
891 | |
892 | spin_lock_init(&stats->lock); | |
893 | ||
d5a8aa5d RW |
894 | stats_attr_group = &cooling_device_stats_attr_group; |
895 | ||
896 | out: | |
8ea22951 VK |
897 | /* Fill the empty slot left in cooling_device_attr_groups */ |
898 | var = ARRAY_SIZE(cooling_device_attr_groups) - 2; | |
d5a8aa5d | 899 | cooling_device_attr_groups[var] = stats_attr_group; |
8ea22951 VK |
900 | } |
901 | ||
902 | static void cooling_device_stats_destroy(struct thermal_cooling_device *cdev) | |
903 | { | |
904 | kfree(cdev->stats); | |
905 | cdev->stats = NULL; | |
906 | } | |
907 | ||
908 | #else | |
909 | ||
910 | static inline void | |
911 | cooling_device_stats_setup(struct thermal_cooling_device *cdev) {} | |
912 | static inline void | |
913 | cooling_device_stats_destroy(struct thermal_cooling_device *cdev) {} | |
914 | ||
915 | #endif /* CONFIG_THERMAL_STATISTICS */ | |
916 | ||
45cf2ec9 EV |
917 | void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *cdev) |
918 | { | |
8ea22951 | 919 | cooling_device_stats_setup(cdev); |
45cf2ec9 EV |
920 | cdev->device.groups = cooling_device_attr_groups; |
921 | } | |
922 | ||
8ea22951 VK |
923 | void thermal_cooling_device_destroy_sysfs(struct thermal_cooling_device *cdev) |
924 | { | |
925 | cooling_device_stats_destroy(cdev); | |
926 | } | |
927 | ||
790930f4 RW |
928 | void thermal_cooling_device_stats_reinit(struct thermal_cooling_device *cdev) |
929 | { | |
b57841fb RW |
930 | lockdep_assert_held(&cdev->lock); |
931 | ||
790930f4 RW |
932 | cooling_device_stats_destroy(cdev); |
933 | cooling_device_stats_setup(cdev); | |
934 | } | |
935 | ||
45cf2ec9 EV |
936 | /* these helper will be used only at the time of bindig */ |
937 | ssize_t | |
33e678d4 | 938 | trip_point_show(struct device *dev, struct device_attribute *attr, char *buf) |
45cf2ec9 EV |
939 | { |
940 | struct thermal_instance *instance; | |
941 | ||
942 | instance = | |
943 | container_of(attr, struct thermal_instance, attr); | |
944 | ||
53f04ca8 | 945 | return sprintf(buf, "%d\n", instance->trip); |
45cf2ec9 EV |
946 | } |
947 | ||
948 | ssize_t | |
33e678d4 | 949 | weight_show(struct device *dev, struct device_attribute *attr, char *buf) |
45cf2ec9 EV |
950 | { |
951 | struct thermal_instance *instance; | |
952 | ||
953 | instance = container_of(attr, struct thermal_instance, weight_attr); | |
954 | ||
955 | return sprintf(buf, "%d\n", instance->weight); | |
956 | } | |
957 | ||
33e678d4 VK |
958 | ssize_t weight_store(struct device *dev, struct device_attribute *attr, |
959 | const char *buf, size_t count) | |
45cf2ec9 EV |
960 | { |
961 | struct thermal_instance *instance; | |
962 | int ret, weight; | |
963 | ||
964 | ret = kstrtoint(buf, 0, &weight); | |
965 | if (ret) | |
966 | return ret; | |
967 | ||
968 | instance = container_of(attr, struct thermal_instance, weight_attr); | |
969 | instance->weight = weight; | |
970 | ||
971 | return count; | |
972 | } |