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
12int for_each_thermal_trip(struct thermal_zone_device *tz,
13			  int (*cb)(struct thermal_trip *, void *),
14			  void *data)
15{
16	int i, ret;
17
18	lockdep_assert_held(&tz->lock);
19
20	for (i = 0; i < tz->num_trips; i++) {
21		ret = cb(&tz->trips[i], data);
22		if (ret)
23			return ret;
24	}
25
26	return 0;
27}
28EXPORT_SYMBOL_GPL(for_each_thermal_trip);
29
30int thermal_zone_get_num_trips(struct thermal_zone_device *tz)
31{
32	return tz->num_trips;
33}
34EXPORT_SYMBOL_GPL(thermal_zone_get_num_trips);
35
36/**
37 * __thermal_zone_set_trips - Computes the next trip points for the driver
38 * @tz: a pointer to a thermal zone device structure
39 *
40 * The function computes the next temperature boundaries by browsing
41 * the trip points. The result is the closer low and high trip points
42 * to the current temperature. These values are passed to the backend
43 * driver to let it set its own notification mechanism (usually an
44 * interrupt).
45 *
46 * This function must be called with tz->lock held. Both tz and tz->ops
47 * must be valid pointers.
48 *
49 * It does not return a value
50 */
51void __thermal_zone_set_trips(struct thermal_zone_device *tz)
52{
53	struct thermal_trip trip;
54	int low = -INT_MAX, high = INT_MAX;
55	bool same_trip = false;
56	int i, ret;
57
58	lockdep_assert_held(&tz->lock);
59
60	if (!tz->ops->set_trips)
61		return;
62
63	for (i = 0; i < tz->num_trips; i++) {
64		bool low_set = false;
65		int trip_low;
66
67		ret = __thermal_zone_get_trip(tz, i , &trip);
68		if (ret)
69			return;
70
71		trip_low = trip.temperature - trip.hysteresis;
72
73		if (trip_low < tz->temperature && trip_low > low) {
74			low = trip_low;
75			low_set = true;
76			same_trip = false;
77		}
78
79		if (trip.temperature > tz->temperature &&
80		    trip.temperature < high) {
81			high = trip.temperature;
82			same_trip = low_set;
83		}
84	}
85
86	/* No need to change trip points */
87	if (tz->prev_low_trip == low && tz->prev_high_trip == high)
88		return;
89
90	/*
91	 * If "high" and "low" are the same, skip the change unless this is the
92	 * first time.
93	 */
94	if (same_trip && (tz->prev_low_trip != -INT_MAX ||
95	    tz->prev_high_trip != INT_MAX))
96		return;
97
98	tz->prev_low_trip = low;
99	tz->prev_high_trip = high;
100
101	dev_dbg(&tz->device,
102		"new temperature boundaries: %d < x < %d\n", low, high);
103
104	/*
105	 * Set a temperature window. When this window is left the driver
106	 * must inform the thermal core via thermal_zone_device_update.
107	 */
108	ret = tz->ops->set_trips(tz, low, high);
109	if (ret)
110		dev_err(&tz->device, "Failed to set trips: %d\n", ret);
111}
112
113int __thermal_zone_get_trip(struct thermal_zone_device *tz, int trip_id,
114			    struct thermal_trip *trip)
115{
116	if (!tz || !tz->trips || trip_id < 0 || trip_id >= tz->num_trips || !trip)
117		return -EINVAL;
118
119	*trip = tz->trips[trip_id];
120	return 0;
121}
122EXPORT_SYMBOL_GPL(__thermal_zone_get_trip);
123
124int thermal_zone_get_trip(struct thermal_zone_device *tz, int trip_id,
125			  struct thermal_trip *trip)
126{
127	int ret;
128
129	mutex_lock(&tz->lock);
130	ret = __thermal_zone_get_trip(tz, trip_id, trip);
131	mutex_unlock(&tz->lock);
132
133	return ret;
134}
135EXPORT_SYMBOL_GPL(thermal_zone_get_trip);
136
137int thermal_zone_set_trip(struct thermal_zone_device *tz, int trip_id,
138			  const struct thermal_trip *trip)
139{
140	struct thermal_trip t;
141	int ret;
142
143	if (!tz->ops->set_trip_temp && !tz->ops->set_trip_hyst && !tz->trips)
144		return -EINVAL;
145
146	ret = __thermal_zone_get_trip(tz, trip_id, &t);
147	if (ret)
148		return ret;
149
150	if (t.type != trip->type)
151		return -EINVAL;
152
153	if (t.temperature != trip->temperature && tz->ops->set_trip_temp) {
154		ret = tz->ops->set_trip_temp(tz, trip_id, trip->temperature);
155		if (ret)
156			return ret;
157	}
158
159	if (t.hysteresis != trip->hysteresis && tz->ops->set_trip_hyst) {
160		ret = tz->ops->set_trip_hyst(tz, trip_id, trip->hysteresis);
161		if (ret)
162			return ret;
163	}
164
165	if (tz->trips && (t.temperature != trip->temperature || t.hysteresis != trip->hysteresis))
166		tz->trips[trip_id] = *trip;
167
168	thermal_notify_tz_trip_change(tz->id, trip_id, trip->type,
169				      trip->temperature, trip->hysteresis);
170
171	__thermal_zone_device_update(tz, THERMAL_TRIP_CHANGED);
172
173	return 0;
174}
175
176int thermal_zone_trip_id(struct thermal_zone_device *tz,
177			 const struct thermal_trip *trip)
178{
179	int i;
180
181	for (i = 0; i < tz->num_trips; i++) {
182		if (&tz->trips[i] == trip)
183			return i;
184	}
185
186	return -ENODATA;
187}
188