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