xref: /linux/drivers/thermal/thermal_trip.c (revision da5b2ad1c2f18834cb1ce429e2e5a5cf5cbdf21b)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *  Copyright (C) 2008 Intel Corp
4  *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
5  *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
6  *  Copyright 2022 Linaro Limited
7  *
8  * Thermal trips handling
9  */
10 #include "thermal_core.h"
11 
12 static const char *trip_type_names[] = {
13 	[THERMAL_TRIP_ACTIVE] = "active",
14 	[THERMAL_TRIP_PASSIVE] = "passive",
15 	[THERMAL_TRIP_HOT] = "hot",
16 	[THERMAL_TRIP_CRITICAL] = "critical",
17 };
18 
19 const char *thermal_trip_type_name(enum thermal_trip_type trip_type)
20 {
21 	if (trip_type < THERMAL_TRIP_ACTIVE || trip_type > THERMAL_TRIP_CRITICAL)
22 		return "unknown";
23 
24 	return trip_type_names[trip_type];
25 }
26 
27 int for_each_thermal_trip(struct thermal_zone_device *tz,
28 			  int (*cb)(struct thermal_trip *, void *),
29 			  void *data)
30 {
31 	struct thermal_trip_desc *td;
32 	int ret;
33 
34 	for_each_trip_desc(tz, td) {
35 		ret = cb(&td->trip, data);
36 		if (ret)
37 			return ret;
38 	}
39 
40 	return 0;
41 }
42 EXPORT_SYMBOL_GPL(for_each_thermal_trip);
43 
44 int thermal_zone_for_each_trip(struct thermal_zone_device *tz,
45 			       int (*cb)(struct thermal_trip *, void *),
46 			       void *data)
47 {
48 	int ret;
49 
50 	mutex_lock(&tz->lock);
51 	ret = for_each_thermal_trip(tz, cb, data);
52 	mutex_unlock(&tz->lock);
53 
54 	return ret;
55 }
56 EXPORT_SYMBOL_GPL(thermal_zone_for_each_trip);
57 
58 int thermal_zone_get_num_trips(struct thermal_zone_device *tz)
59 {
60 	return tz->num_trips;
61 }
62 EXPORT_SYMBOL_GPL(thermal_zone_get_num_trips);
63 
64 /**
65  * thermal_zone_set_trips - Computes the next trip points for the driver
66  * @tz: a pointer to a thermal zone device structure
67  *
68  * The function computes the next temperature boundaries by browsing
69  * the trip points. The result is the closer low and high trip points
70  * to the current temperature. These values are passed to the backend
71  * driver to let it set its own notification mechanism (usually an
72  * interrupt).
73  *
74  * This function must be called with tz->lock held. Both tz and tz->ops
75  * must be valid pointers.
76  *
77  * It does not return a value
78  */
79 void thermal_zone_set_trips(struct thermal_zone_device *tz)
80 {
81 	const struct thermal_trip_desc *td;
82 	int low = -INT_MAX, high = INT_MAX;
83 	int ret;
84 
85 	lockdep_assert_held(&tz->lock);
86 
87 	if (!tz->ops.set_trips)
88 		return;
89 
90 	for_each_trip_desc(tz, td) {
91 		if (td->threshold <= tz->temperature && td->threshold > low)
92 			low = td->threshold;
93 
94 		if (td->threshold >= tz->temperature && td->threshold < high)
95 			high = td->threshold;
96 	}
97 
98 	/* No need to change trip points */
99 	if (tz->prev_low_trip == low && tz->prev_high_trip == high)
100 		return;
101 
102 	tz->prev_low_trip = low;
103 	tz->prev_high_trip = high;
104 
105 	dev_dbg(&tz->device,
106 		"new temperature boundaries: %d < x < %d\n", low, high);
107 
108 	/*
109 	 * Set a temperature window. When this window is left the driver
110 	 * must inform the thermal core via thermal_zone_device_update.
111 	 */
112 	ret = tz->ops.set_trips(tz, low, high);
113 	if (ret)
114 		dev_err(&tz->device, "Failed to set trips: %d\n", ret);
115 }
116 
117 int thermal_zone_get_trip(struct thermal_zone_device *tz, int trip_id,
118 			  struct thermal_trip *trip)
119 {
120 	if (!tz || !trip || trip_id < 0 || trip_id >= tz->num_trips)
121 		return -EINVAL;
122 
123 	mutex_lock(&tz->lock);
124 	*trip = tz->trips[trip_id].trip;
125 	mutex_unlock(&tz->lock);
126 
127 	return 0;
128 }
129 EXPORT_SYMBOL_GPL(thermal_zone_get_trip);
130 
131 int thermal_zone_trip_id(const struct thermal_zone_device *tz,
132 			 const struct thermal_trip *trip)
133 {
134 	/*
135 	 * Assume the trip to be located within the bounds of the thermal
136 	 * zone's trips[] table.
137 	 */
138 	return trip_to_trip_desc(trip) - tz->trips;
139 }
140 
141 void thermal_zone_trip_updated(struct thermal_zone_device *tz,
142 			       const struct thermal_trip *trip)
143 {
144 	thermal_notify_tz_trip_change(tz, trip);
145 	__thermal_zone_device_update(tz, THERMAL_TRIP_CHANGED);
146 }
147 
148 void thermal_zone_set_trip_temp(struct thermal_zone_device *tz,
149 				struct thermal_trip *trip, int temp)
150 {
151 	if (trip->temperature == temp)
152 		return;
153 
154 	WRITE_ONCE(trip->temperature, temp);
155 	thermal_notify_tz_trip_change(tz, trip);
156 
157 	if (temp == THERMAL_TEMP_INVALID) {
158 		struct thermal_trip_desc *td = trip_to_trip_desc(trip);
159 
160 		if (tz->temperature >= td->threshold) {
161 			/*
162 			 * The trip has been crossed on the way up, so some
163 			 * adjustments are needed to compensate for the lack
164 			 * of it going forward.
165 			 */
166 			if (trip->type == THERMAL_TRIP_PASSIVE) {
167 				tz->passive--;
168 				WARN_ON_ONCE(tz->passive < 0);
169 			}
170 			thermal_zone_trip_down(tz, trip);
171 		}
172 		/*
173 		 * Invalidate the threshold to avoid triggering a spurious
174 		 * trip crossing notification when the trip becomes valid.
175 		 */
176 		td->threshold = INT_MAX;
177 	}
178 }
179 EXPORT_SYMBOL_GPL(thermal_zone_set_trip_temp);
180