1// SPDX-License-Identifier: GPL-2.0
2/*
3 *  thermal_helpers.c - helper functions to handle 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>
11 */
12
13#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14
15#include <linux/device.h>
16#include <linux/err.h>
17#include <linux/export.h>
18#include <linux/slab.h>
19#include <linux/string.h>
20#include <linux/sysfs.h>
21
22#include "thermal_core.h"
23#include "thermal_trace.h"
24
25int get_tz_trend(struct thermal_zone_device *tz, int trip_index)
26{
27	struct thermal_trip *trip = tz->trips ? &tz->trips[trip_index] : NULL;
28	enum thermal_trend trend;
29
30	if (tz->emul_temperature || !tz->ops->get_trend ||
31	    tz->ops->get_trend(tz, trip, &trend)) {
32		if (tz->temperature > tz->last_temperature)
33			trend = THERMAL_TREND_RAISING;
34		else if (tz->temperature < tz->last_temperature)
35			trend = THERMAL_TREND_DROPPING;
36		else
37			trend = THERMAL_TREND_STABLE;
38	}
39
40	return trend;
41}
42
43struct thermal_instance *
44get_thermal_instance(struct thermal_zone_device *tz,
45		     struct thermal_cooling_device *cdev, int trip_index)
46{
47	struct thermal_instance *pos = NULL;
48	struct thermal_instance *target_instance = NULL;
49	const struct thermal_trip *trip;
50
51	mutex_lock(&tz->lock);
52	mutex_lock(&cdev->lock);
53
54	trip = &tz->trips[trip_index];
55
56	list_for_each_entry(pos, &tz->thermal_instances, tz_node) {
57		if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
58			target_instance = pos;
59			break;
60		}
61	}
62
63	mutex_unlock(&cdev->lock);
64	mutex_unlock(&tz->lock);
65
66	return target_instance;
67}
68EXPORT_SYMBOL(get_thermal_instance);
69
70/**
71 * __thermal_zone_get_temp() - returns the temperature of a thermal zone
72 * @tz: a valid pointer to a struct thermal_zone_device
73 * @temp: a valid pointer to where to store the resulting temperature.
74 *
75 * When a valid thermal zone reference is passed, it will fetch its
76 * temperature and fill @temp.
77 *
78 * Both tz and tz->ops must be valid pointers when calling this function,
79 * and the tz->ops->get_temp callback must be provided.
80 * The function must be called under tz->lock.
81 *
82 * Return: On success returns 0, an error code otherwise
83 */
84int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
85{
86	int ret = -EINVAL;
87	int count;
88	int crit_temp = INT_MAX;
89	struct thermal_trip trip;
90
91	lockdep_assert_held(&tz->lock);
92
93	ret = tz->ops->get_temp(tz, temp);
94
95	if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
96		for (count = 0; count < tz->num_trips; count++) {
97			ret = __thermal_zone_get_trip(tz, count, &trip);
98			if (!ret && trip.type == THERMAL_TRIP_CRITICAL) {
99				crit_temp = trip.temperature;
100				break;
101			}
102		}
103
104		/*
105		 * Only allow emulating a temperature when the real temperature
106		 * is below the critical temperature so that the emulation code
107		 * cannot hide critical conditions.
108		 */
109		if (!ret && *temp < crit_temp)
110			*temp = tz->emul_temperature;
111	}
112
113	if (ret)
114		dev_dbg(&tz->device, "Failed to get temperature: %d\n", ret);
115
116	return ret;
117}
118
119/**
120 * thermal_zone_get_temp() - returns the temperature of a thermal zone
121 * @tz: a valid pointer to a struct thermal_zone_device
122 * @temp: a valid pointer to where to store the resulting temperature.
123 *
124 * When a valid thermal zone reference is passed, it will fetch its
125 * temperature and fill @temp.
126 *
127 * Return: On success returns 0, an error code otherwise
128 */
129int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
130{
131	int ret;
132
133	if (IS_ERR_OR_NULL(tz))
134		return -EINVAL;
135
136	mutex_lock(&tz->lock);
137
138	if (!tz->ops->get_temp) {
139		ret = -EINVAL;
140		goto unlock;
141	}
142
143	if (device_is_registered(&tz->device))
144		ret = __thermal_zone_get_temp(tz, temp);
145	else
146		ret = -ENODEV;
147
148unlock:
149	mutex_unlock(&tz->lock);
150
151	return ret;
152}
153EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
154
155static void thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev,
156				       int target)
157{
158	if (cdev->ops->set_cur_state(cdev, target))
159		return;
160
161	thermal_notify_cdev_state_update(cdev->id, target);
162	thermal_cooling_device_stats_update(cdev, target);
163}
164
165void __thermal_cdev_update(struct thermal_cooling_device *cdev)
166{
167	struct thermal_instance *instance;
168	unsigned long target = 0;
169
170	/* Make sure cdev enters the deepest cooling state */
171	list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
172		dev_dbg(&cdev->device, "zone%d->target=%lu\n",
173			instance->tz->id, instance->target);
174		if (instance->target == THERMAL_NO_TARGET)
175			continue;
176		if (instance->target > target)
177			target = instance->target;
178	}
179
180	thermal_cdev_set_cur_state(cdev, target);
181
182	trace_cdev_update(cdev, target);
183	dev_dbg(&cdev->device, "set to state %lu\n", target);
184}
185
186/**
187 * thermal_cdev_update - update cooling device state if needed
188 * @cdev:	pointer to struct thermal_cooling_device
189 *
190 * Update the cooling device state if there is a need.
191 */
192void thermal_cdev_update(struct thermal_cooling_device *cdev)
193{
194	mutex_lock(&cdev->lock);
195	if (!cdev->updated) {
196		__thermal_cdev_update(cdev);
197		cdev->updated = true;
198	}
199	mutex_unlock(&cdev->lock);
200}
201
202/**
203 * thermal_zone_get_slope - return the slope attribute of the thermal zone
204 * @tz: thermal zone device with the slope attribute
205 *
206 * Return: If the thermal zone device has a slope attribute, return it, else
207 * return 1.
208 */
209int thermal_zone_get_slope(struct thermal_zone_device *tz)
210{
211	if (tz && tz->tzp)
212		return tz->tzp->slope;
213	return 1;
214}
215EXPORT_SYMBOL_GPL(thermal_zone_get_slope);
216
217/**
218 * thermal_zone_get_offset - return the offset attribute of the thermal zone
219 * @tz: thermal zone device with the offset attribute
220 *
221 * Return: If the thermal zone device has a offset attribute, return it, else
222 * return 0.
223 */
224int thermal_zone_get_offset(struct thermal_zone_device *tz)
225{
226	if (tz && tz->tzp)
227		return tz->tzp->offset;
228	return 0;
229}
230EXPORT_SYMBOL_GPL(thermal_zone_get_offset);
231