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