summaryrefslogtreecommitdiff
path: root/drivers/thermal/thermal_helpers.c
blob: dc374a7a1a659fd8472dce7c40b8e4d061b08873 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// SPDX-License-Identifier: GPL-2.0
/*
 *  thermal_helpers.c - helper functions to handle thermal devices
 *
 *  Copyright (C) 2016 Eduardo Valentin <edubezval@gmail.com>
 *
 *  Highly based on original thermal_core.c
 *  Copyright (C) 2008 Intel Corp
 *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
 *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/device.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/sysfs.h>

#include "thermal_core.h"
#include "thermal_trace.h"

int get_tz_trend(struct thermal_zone_device *tz, const struct thermal_trip *trip)
{
	enum thermal_trend trend;

	if (tz->emul_temperature || !tz->ops.get_trend ||
	    tz->ops.get_trend(tz, trip, &trend)) {
		if (tz->temperature > tz->last_temperature)
			trend = THERMAL_TREND_RAISING;
		else if (tz->temperature < tz->last_temperature)
			trend = THERMAL_TREND_DROPPING;
		else
			trend = THERMAL_TREND_STABLE;
	}

	return trend;
}

static bool thermal_instance_present(struct thermal_zone_device *tz,
				     struct thermal_cooling_device *cdev,
				     const struct thermal_trip *trip)
{
	struct thermal_instance *ti;

	list_for_each_entry(ti, &tz->thermal_instances, tz_node) {
		if (ti->trip == trip && ti->cdev == cdev)
			return true;
	}

	return false;
}

bool thermal_trip_is_bound_to_cdev(struct thermal_zone_device *tz,
				   const struct thermal_trip *trip,
				   struct thermal_cooling_device *cdev)
{
	bool ret;

	mutex_lock(&tz->lock);
	mutex_lock(&cdev->lock);

	ret = thermal_instance_present(tz, cdev, trip);

	mutex_unlock(&cdev->lock);
	mutex_unlock(&tz->lock);

	return ret;
}
EXPORT_SYMBOL_GPL(thermal_trip_is_bound_to_cdev);

/**
 * __thermal_zone_get_temp() - returns the temperature of a thermal zone
 * @tz: a valid pointer to a struct thermal_zone_device
 * @temp: a valid pointer to where to store the resulting temperature.
 *
 * When a valid thermal zone reference is passed, it will fetch its
 * temperature and fill @temp.
 *
 * Both tz and tz->ops must be valid pointers when calling this function,
 * and the tz->ops.get_temp callback must be provided.
 * The function must be called under tz->lock.
 *
 * Return: On success returns 0, an error code otherwise
 */
int __thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
{
	const struct thermal_trip_desc *td;
	int crit_temp = INT_MAX;
	int ret = -EINVAL;

	lockdep_assert_held(&tz->lock);

	ret = tz->ops.get_temp(tz, temp);

	if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
		for_each_trip_desc(tz, td) {
			const struct thermal_trip *trip = &td->trip;

			if (trip->type == THERMAL_TRIP_CRITICAL) {
				crit_temp = trip->temperature;
				break;
			}
		}

		/*
		 * Only allow emulating a temperature when the real temperature
		 * is below the critical temperature so that the emulation code
		 * cannot hide critical conditions.
		 */
		if (!ret && *temp < crit_temp)
			*temp = tz->emul_temperature;
	}

	if (ret)
		dev_dbg(&tz->device, "Failed to get temperature: %d\n", ret);

	return ret;
}

/**
 * thermal_zone_get_temp() - returns the temperature of a thermal zone
 * @tz: a valid pointer to a struct thermal_zone_device
 * @temp: a valid pointer to where to store the resulting temperature.
 *
 * When a valid thermal zone reference is passed, it will fetch its
 * temperature and fill @temp.
 *
 * Return: On success returns 0, an error code otherwise
 */
int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
{
	int ret;

	if (IS_ERR_OR_NULL(tz))
		return -EINVAL;

	mutex_lock(&tz->lock);

	if (!tz->ops.get_temp) {
		ret = -EINVAL;
		goto unlock;
	}

	ret = __thermal_zone_get_temp(tz, temp);
	if (!ret && *temp <= THERMAL_TEMP_INVALID)
		ret = -ENODATA;

unlock:
	mutex_unlock(&tz->lock);

	return ret;
}
EXPORT_SYMBOL_GPL(thermal_zone_get_temp);

static int thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev, int state)
{
	int ret;

	/*
	 * No check is needed for the ops->set_cur_state as the
	 * registering function checked the ops are correctly set
	 */
	ret = cdev->ops->set_cur_state(cdev, state);
	if (ret)
		return ret;

	thermal_notify_cdev_state_update(cdev, state);
	thermal_cooling_device_stats_update(cdev, state);
	thermal_debug_cdev_state_update(cdev, state);

	return 0;
}

void __thermal_cdev_update(struct thermal_cooling_device *cdev)
{
	struct thermal_instance *instance;
	unsigned long target = 0;

	/* Make sure cdev enters the deepest cooling state */
	list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
		if (instance->target == THERMAL_NO_TARGET)
			continue;
		if (instance->target > target)
			target = instance->target;
	}

	thermal_cdev_set_cur_state(cdev, target);

	trace_cdev_update(cdev, target);
	dev_dbg(&cdev->device, "set to state %lu\n", target);
}

/**
 * thermal_cdev_update - update cooling device state if needed
 * @cdev:	pointer to struct thermal_cooling_device
 *
 * Update the cooling device state if there is a need.
 */
void thermal_cdev_update(struct thermal_cooling_device *cdev)
{
	mutex_lock(&cdev->lock);
	if (!cdev->updated) {
		__thermal_cdev_update(cdev);
		cdev->updated = true;
	}
	mutex_unlock(&cdev->lock);
}

/**
 * thermal_zone_get_slope - return the slope attribute of the thermal zone
 * @tz: thermal zone device with the slope attribute
 *
 * Return: If the thermal zone device has a slope attribute, return it, else
 * return 1.
 */
int thermal_zone_get_slope(struct thermal_zone_device *tz)
{
	if (tz && tz->tzp)
		return tz->tzp->slope;
	return 1;
}
EXPORT_SYMBOL_GPL(thermal_zone_get_slope);

/**
 * thermal_zone_get_offset - return the offset attribute of the thermal zone
 * @tz: thermal zone device with the offset attribute
 *
 * Return: If the thermal zone device has a offset attribute, return it, else
 * return 0.
 */
int thermal_zone_get_offset(struct thermal_zone_device *tz)
{
	if (tz && tz->tzp)
		return tz->tzp->offset;
	return 0;
}
EXPORT_SYMBOL_GPL(thermal_zone_get_offset);