15b8de18eSDaniel Lezcano // SPDX-License-Identifier: GPL-2.0
25b8de18eSDaniel Lezcano /*
35b8de18eSDaniel Lezcano  *  Copyright (C) 2008 Intel Corp
45b8de18eSDaniel Lezcano  *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
55b8de18eSDaniel Lezcano  *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
65b8de18eSDaniel Lezcano  *  Copyright 2022 Linaro Limited
75b8de18eSDaniel Lezcano  *
85b8de18eSDaniel Lezcano  * Thermal trips handling
95b8de18eSDaniel Lezcano  */
105b8de18eSDaniel Lezcano #include "thermal_core.h"
115b8de18eSDaniel Lezcano 
for_each_thermal_trip(struct thermal_zone_device * tz,int (* cb)(struct thermal_trip *,void *),void * data)1296b8b436SRafael J. Wysocki int for_each_thermal_trip(struct thermal_zone_device *tz,
135b8de18eSDaniel Lezcano 			  int (*cb)(struct thermal_trip *, void *),
145b8de18eSDaniel Lezcano 			  void *data)
155b8de18eSDaniel Lezcano {
165b8de18eSDaniel Lezcano 	int i, ret;
175b8de18eSDaniel Lezcano 
185b8de18eSDaniel Lezcano 	lockdep_assert_held(&tz->lock);
195b8de18eSDaniel Lezcano 
205b8de18eSDaniel Lezcano 	for (i = 0; i < tz->num_trips; i++) {
2196b8b436SRafael J. Wysocki 		ret = cb(&tz->trips[i], data);
225b8de18eSDaniel Lezcano 		if (ret)
235b8de18eSDaniel Lezcano 			return ret;
245b8de18eSDaniel Lezcano 	}
255b8de18eSDaniel Lezcano 
265b8de18eSDaniel Lezcano 	return 0;
275b8de18eSDaniel Lezcano }
2896b8b436SRafael J. Wysocki EXPORT_SYMBOL_GPL(for_each_thermal_trip);
295b8de18eSDaniel Lezcano 
thermal_zone_get_num_trips(struct thermal_zone_device * tz)305b8de18eSDaniel Lezcano int thermal_zone_get_num_trips(struct thermal_zone_device *tz)
315b8de18eSDaniel Lezcano {
325b8de18eSDaniel Lezcano 	return tz->num_trips;
335b8de18eSDaniel Lezcano }
345b8de18eSDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_get_num_trips);
355b8de18eSDaniel Lezcano 
365b8de18eSDaniel Lezcano /**
375b8de18eSDaniel Lezcano  * __thermal_zone_set_trips - Computes the next trip points for the driver
385b8de18eSDaniel Lezcano  * @tz: a pointer to a thermal zone device structure
395b8de18eSDaniel Lezcano  *
405b8de18eSDaniel Lezcano  * The function computes the next temperature boundaries by browsing
415b8de18eSDaniel Lezcano  * the trip points. The result is the closer low and high trip points
425b8de18eSDaniel Lezcano  * to the current temperature. These values are passed to the backend
435b8de18eSDaniel Lezcano  * driver to let it set its own notification mechanism (usually an
445b8de18eSDaniel Lezcano  * interrupt).
455b8de18eSDaniel Lezcano  *
465b8de18eSDaniel Lezcano  * This function must be called with tz->lock held. Both tz and tz->ops
475b8de18eSDaniel Lezcano  * must be valid pointers.
485b8de18eSDaniel Lezcano  *
495b8de18eSDaniel Lezcano  * It does not return a value
505b8de18eSDaniel Lezcano  */
__thermal_zone_set_trips(struct thermal_zone_device * tz)515b8de18eSDaniel Lezcano void __thermal_zone_set_trips(struct thermal_zone_device *tz)
525b8de18eSDaniel Lezcano {
535b8de18eSDaniel Lezcano 	struct thermal_trip trip;
545b8de18eSDaniel Lezcano 	int low = -INT_MAX, high = INT_MAX;
55fb64fc9fSNícolas F. R. A. Prado 	bool same_trip = false;
565b8de18eSDaniel Lezcano 	int i, ret;
575b8de18eSDaniel Lezcano 
585b8de18eSDaniel Lezcano 	lockdep_assert_held(&tz->lock);
595b8de18eSDaniel Lezcano 
605b8de18eSDaniel Lezcano 	if (!tz->ops->set_trips)
615b8de18eSDaniel Lezcano 		return;
625b8de18eSDaniel Lezcano 
635b8de18eSDaniel Lezcano 	for (i = 0; i < tz->num_trips; i++) {
64fb64fc9fSNícolas F. R. A. Prado 		bool low_set = false;
655b8de18eSDaniel Lezcano 		int trip_low;
665b8de18eSDaniel Lezcano 
675b8de18eSDaniel Lezcano 		ret = __thermal_zone_get_trip(tz, i , &trip);
685b8de18eSDaniel Lezcano 		if (ret)
695b8de18eSDaniel Lezcano 			return;
705b8de18eSDaniel Lezcano 
715b8de18eSDaniel Lezcano 		trip_low = trip.temperature - trip.hysteresis;
725b8de18eSDaniel Lezcano 
73fb64fc9fSNícolas F. R. A. Prado 		if (trip_low < tz->temperature && trip_low > low) {
745b8de18eSDaniel Lezcano 			low = trip_low;
75fb64fc9fSNícolas F. R. A. Prado 			low_set = true;
76fb64fc9fSNícolas F. R. A. Prado 			same_trip = false;
77fb64fc9fSNícolas F. R. A. Prado 		}
785b8de18eSDaniel Lezcano 
795b8de18eSDaniel Lezcano 		if (trip.temperature > tz->temperature &&
80fb64fc9fSNícolas F. R. A. Prado 		    trip.temperature < high) {
815b8de18eSDaniel Lezcano 			high = trip.temperature;
82fb64fc9fSNícolas F. R. A. Prado 			same_trip = low_set;
83fb64fc9fSNícolas F. R. A. Prado 		}
845b8de18eSDaniel Lezcano 	}
855b8de18eSDaniel Lezcano 
865b8de18eSDaniel Lezcano 	/* No need to change trip points */
875b8de18eSDaniel Lezcano 	if (tz->prev_low_trip == low && tz->prev_high_trip == high)
885b8de18eSDaniel Lezcano 		return;
895b8de18eSDaniel Lezcano 
90fb64fc9fSNícolas F. R. A. Prado 	/*
91fb64fc9fSNícolas F. R. A. Prado 	 * If "high" and "low" are the same, skip the change unless this is the
92fb64fc9fSNícolas F. R. A. Prado 	 * first time.
93fb64fc9fSNícolas F. R. A. Prado 	 */
94fb64fc9fSNícolas F. R. A. Prado 	if (same_trip && (tz->prev_low_trip != -INT_MAX ||
95fb64fc9fSNícolas F. R. A. Prado 	    tz->prev_high_trip != INT_MAX))
96fb64fc9fSNícolas F. R. A. Prado 		return;
97fb64fc9fSNícolas F. R. A. Prado 
985b8de18eSDaniel Lezcano 	tz->prev_low_trip = low;
995b8de18eSDaniel Lezcano 	tz->prev_high_trip = high;
1005b8de18eSDaniel Lezcano 
1015b8de18eSDaniel Lezcano 	dev_dbg(&tz->device,
1025b8de18eSDaniel Lezcano 		"new temperature boundaries: %d < x < %d\n", low, high);
1035b8de18eSDaniel Lezcano 
1045b8de18eSDaniel Lezcano 	/*
1055b8de18eSDaniel Lezcano 	 * Set a temperature window. When this window is left the driver
1065b8de18eSDaniel Lezcano 	 * must inform the thermal core via thermal_zone_device_update.
1075b8de18eSDaniel Lezcano 	 */
1085b8de18eSDaniel Lezcano 	ret = tz->ops->set_trips(tz, low, high);
1095b8de18eSDaniel Lezcano 	if (ret)
1105b8de18eSDaniel Lezcano 		dev_err(&tz->device, "Failed to set trips: %d\n", ret);
1115b8de18eSDaniel Lezcano }
1125b8de18eSDaniel Lezcano 
__thermal_zone_get_trip(struct thermal_zone_device * tz,int trip_id,struct thermal_trip * trip)1135b8de18eSDaniel Lezcano int __thermal_zone_get_trip(struct thermal_zone_device *tz, int trip_id,
1145b8de18eSDaniel Lezcano 			    struct thermal_trip *trip)
1155b8de18eSDaniel Lezcano {
11635d8dbbbSRafael J. Wysocki 	if (!tz || !tz->trips || trip_id < 0 || trip_id >= tz->num_trips || !trip)
1175b8de18eSDaniel Lezcano 		return -EINVAL;
1185b8de18eSDaniel Lezcano 
1195b8de18eSDaniel Lezcano 	*trip = tz->trips[trip_id];
1205b8de18eSDaniel Lezcano 	return 0;
1215b8de18eSDaniel Lezcano }
1225b8de18eSDaniel Lezcano EXPORT_SYMBOL_GPL(__thermal_zone_get_trip);
1235b8de18eSDaniel Lezcano 
thermal_zone_get_trip(struct thermal_zone_device * tz,int trip_id,struct thermal_trip * trip)1245b8de18eSDaniel Lezcano int thermal_zone_get_trip(struct thermal_zone_device *tz, int trip_id,
1255b8de18eSDaniel Lezcano 			  struct thermal_trip *trip)
1265b8de18eSDaniel Lezcano {
1275b8de18eSDaniel Lezcano 	int ret;
1285b8de18eSDaniel Lezcano 
1295b8de18eSDaniel Lezcano 	mutex_lock(&tz->lock);
1305b8de18eSDaniel Lezcano 	ret = __thermal_zone_get_trip(tz, trip_id, trip);
1315b8de18eSDaniel Lezcano 	mutex_unlock(&tz->lock);
1325b8de18eSDaniel Lezcano 
1335b8de18eSDaniel Lezcano 	return ret;
1345b8de18eSDaniel Lezcano }
1355b8de18eSDaniel Lezcano EXPORT_SYMBOL_GPL(thermal_zone_get_trip);
1365b8de18eSDaniel Lezcano 
thermal_zone_set_trip(struct thermal_zone_device * tz,int trip_id,const struct thermal_trip * trip)1375b8de18eSDaniel Lezcano int thermal_zone_set_trip(struct thermal_zone_device *tz, int trip_id,
1385b8de18eSDaniel Lezcano 			  const struct thermal_trip *trip)
1395b8de18eSDaniel Lezcano {
1405b8de18eSDaniel Lezcano 	struct thermal_trip t;
1415b8de18eSDaniel Lezcano 	int ret;
1425b8de18eSDaniel Lezcano 
1435b8de18eSDaniel Lezcano 	if (!tz->ops->set_trip_temp && !tz->ops->set_trip_hyst && !tz->trips)
1445b8de18eSDaniel Lezcano 		return -EINVAL;
1455b8de18eSDaniel Lezcano 
1465b8de18eSDaniel Lezcano 	ret = __thermal_zone_get_trip(tz, trip_id, &t);
1475b8de18eSDaniel Lezcano 	if (ret)
1485b8de18eSDaniel Lezcano 		return ret;
1495b8de18eSDaniel Lezcano 
1505b8de18eSDaniel Lezcano 	if (t.type != trip->type)
1515b8de18eSDaniel Lezcano 		return -EINVAL;
1525b8de18eSDaniel Lezcano 
1535b8de18eSDaniel Lezcano 	if (t.temperature != trip->temperature && tz->ops->set_trip_temp) {
1545b8de18eSDaniel Lezcano 		ret = tz->ops->set_trip_temp(tz, trip_id, trip->temperature);
1555b8de18eSDaniel Lezcano 		if (ret)
1565b8de18eSDaniel Lezcano 			return ret;
1575b8de18eSDaniel Lezcano 	}
1585b8de18eSDaniel Lezcano 
1595b8de18eSDaniel Lezcano 	if (t.hysteresis != trip->hysteresis && tz->ops->set_trip_hyst) {
1605b8de18eSDaniel Lezcano 		ret = tz->ops->set_trip_hyst(tz, trip_id, trip->hysteresis);
1615b8de18eSDaniel Lezcano 		if (ret)
1625b8de18eSDaniel Lezcano 			return ret;
1635b8de18eSDaniel Lezcano 	}
1645b8de18eSDaniel Lezcano 
1655b8de18eSDaniel Lezcano 	if (tz->trips && (t.temperature != trip->temperature || t.hysteresis != trip->hysteresis))
1665b8de18eSDaniel Lezcano 		tz->trips[trip_id] = *trip;
1675b8de18eSDaniel Lezcano 
1685b8de18eSDaniel Lezcano 	thermal_notify_tz_trip_change(tz->id, trip_id, trip->type,
1695b8de18eSDaniel Lezcano 				      trip->temperature, trip->hysteresis);
1705b8de18eSDaniel Lezcano 
1715b8de18eSDaniel Lezcano 	__thermal_zone_device_update(tz, THERMAL_TRIP_CHANGED);
1725b8de18eSDaniel Lezcano 
1735b8de18eSDaniel Lezcano 	return 0;
1745b8de18eSDaniel Lezcano }
175*77451ef5SRafael J. Wysocki 
thermal_zone_trip_id(struct thermal_zone_device * tz,const struct thermal_trip * trip)176*77451ef5SRafael J. Wysocki int thermal_zone_trip_id(struct thermal_zone_device *tz,
177*77451ef5SRafael J. Wysocki 			 const struct thermal_trip *trip)
178*77451ef5SRafael J. Wysocki {
179*77451ef5SRafael J. Wysocki 	int i;
180*77451ef5SRafael J. Wysocki 
181*77451ef5SRafael J. Wysocki 	for (i = 0; i < tz->num_trips; i++) {
182*77451ef5SRafael J. Wysocki 		if (&tz->trips[i] == trip)
183*77451ef5SRafael J. Wysocki 			return i;
184*77451ef5SRafael J. Wysocki 	}
185*77451ef5SRafael J. Wysocki 
186*77451ef5SRafael J. Wysocki 	return -ENODATA;
187*77451ef5SRafael J. Wysocki }
188