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