diff mbox series

[v1,2/7] thermal/core: Add thresholds support

Message ID 20240729150259.1089814-3-daniel.lezcano@linaro.org (mailing list archive)
State Superseded, archived
Headers show
Series Add thermal thresholds support | expand

Commit Message

Daniel Lezcano July 29, 2024, 3:02 p.m. UTC
The trip points are a firmware description of the temperature limits
of a specific thermal zone where we associate an action which is done
by the kernel. The time resolution is low.

The userspace has to deal with a more complex thermal management based
on heuristics from different information coming from different
places. The logic is much more complex but based on a bigger time
resolution, usually one second based.

The purpose of the userspace is to monitor the temperatures from
different places and take actions. However, it can not be constantly
reading the temperature to detect when a temperature threshold has
been reached. This is especially bad for mobile or embedded system as
that will lead to an unacceptable number of wakeup to check the
temperature with nothing to do.

On the other side, the sensors are now most of the time interrupt
driven. That means the thermal framework will use the temperature trip
points to program the sensor to trigger an interrupt when a
temperature limit is crossed.

Unfortunately, the userspace can not benefit this feature and current
solutions found here and there, iow out-of-tree, are to add fake trip
points in the firmware and enable the writable trip points.

This is bad for different reasons, the trip points are for in-kernel
actions, the semantic of their types is used by the thermal framework
and by adding trip points in the device tree is a way to overcome the
current limitation but tampering with how the thermal framework is
supposed to work. The writable trip points is a way to adjust a
temperature limit given a specific platform if the firmware is not
accurate enough and TBH it is more a debug feature from my POV.

The thresholds mechanism is a way to have the userspace to tell
thermal framework to send a notification when a temperature limit is
crossed. There is no id, no hysteresis, just the temperature and the
direction of the limit crossing. That means we can be notified when a
threshold is crossed the way up only, or the way down only or both
ways. That allows to create hysteresis values if it is needed.

A threshold can be added, deleted or flushed. The latter means all
thresholds belonging to a thermal zone will be deleted.

When a threshold is added:

 - if the same threshold (temperature and direction) exists, an error
   is returned

 - if a threshold is specified with the same temperature but a
   different direction, the specified direction is added

 - if there is no threshold with the same temperature then it is
   created

When a threshold is deleted:

 - if the same threshold (temperature and direction) exists, it is
   deleted

 - if a threshold is specified with the same temperature but a
   different direction, the specified direction is removed

 - if there is no threshold with the same temperature, then an error
   is returned

When the threshold are flushed:

 - All thresholds related to a thermal zone are deleted

When a threshold is crossed:

 - the userspace does not need to know which threshold(s) have been
   crossed, it will be notified with the current temperature and the
   previous temperature

 - if multiple thresholds have been crossed between two updates only
   one notification will be send to the userspace, it is pointless to
   send a notification per thresholds crossed as the userspace can
   handle that easily when it has the temperature delta information

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 drivers/thermal/Kconfig              |  15 ++
 drivers/thermal/Makefile             |   3 +
 drivers/thermal/thermal_core.h       |   4 +
 drivers/thermal/thermal_thresholds.c | 240 +++++++++++++++++++++++++++
 drivers/thermal/thermal_thresholds.h |  57 +++++++
 include/linux/thermal.h              |   3 +
 6 files changed, 322 insertions(+)
 create mode 100644 drivers/thermal/thermal_thresholds.c
 create mode 100644 drivers/thermal/thermal_thresholds.h

Comments

kernel test robot July 30, 2024, 7:06 a.m. UTC | #1
Hi Daniel,

kernel test robot noticed the following build warnings:

[auto build test WARNING on rafael-pm/thermal]
[also build test WARNING on linus/master v6.11-rc1 next-20240729]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Daniel-Lezcano/thermal-core-Encapsulate-more-handle_thermal_trip/20240730-005842
base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git thermal
patch link:    https://lore.kernel.org/r/20240729150259.1089814-3-daniel.lezcano%40linaro.org
patch subject: [PATCH v1 2/7] thermal/core: Add thresholds support
config: alpha-allyesconfig (https://download.01.org/0day-ci/archive/20240730/202407301457.cOikpFah-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 13.3.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240730/202407301457.cOikpFah-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202407301457.cOikpFah-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/thermal/thermal_thresholds.c: In function 'thermal_thresholds_init':
   drivers/thermal/thermal_thresholds.c:22:22: error: implicit declaration of function 'kmalloc'; did you mean 'mm_alloc'? [-Werror=implicit-function-declaration]
      22 |         thresholds = kmalloc(sizeof(*thresholds), GFP_KERNEL);
         |                      ^~~~~~~
         |                      mm_alloc
>> drivers/thermal/thermal_thresholds.c:22:20: warning: assignment to 'struct thresholds *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
      22 |         thresholds = kmalloc(sizeof(*thresholds), GFP_KERNEL);
         |                    ^
   drivers/thermal/thermal_thresholds.c: In function 'thermal_thresholds_exit':
   drivers/thermal/thermal_thresholds.c:35:9: error: implicit declaration of function 'kfree' [-Werror=implicit-function-declaration]
      35 |         kfree(tz->thresholds);
         |         ^~~~~
   drivers/thermal/thermal_thresholds.c: In function 'thermal_thresholds_add':
>> drivers/thermal/thermal_thresholds.c:185:19: warning: assignment to 'struct threshold *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
     185 |                 t = kmalloc(sizeof(*t), GFP_KERNEL);
         |                   ^
   cc1: some warnings being treated as errors


vim +22 drivers/thermal/thermal_thresholds.c

    17	
    18	int thermal_thresholds_init(struct thermal_zone_device *tz)
    19	{
    20		struct thresholds *thresholds;
    21	
  > 22		thresholds = kmalloc(sizeof(*thresholds), GFP_KERNEL);
    23		if (!thresholds)
    24			return -ENOMEM;
    25	
    26		INIT_LIST_HEAD(&thresholds->list);
    27		tz->thresholds = thresholds;
    28	
    29		return 0;
    30	}
    31	
    32	void thermal_thresholds_exit(struct thermal_zone_device *tz)
    33	{
    34		thermal_thresholds_flush(tz);
    35		kfree(tz->thresholds);
    36		tz->thresholds = NULL;
    37	}
    38	
    39	static int __thermal_thresholds_cmp(void *data,
    40					    const struct list_head *l1,
    41					    const struct list_head *l2)
    42	{
    43		struct threshold *t1 = container_of(l1, struct threshold, list);
    44		struct threshold *t2 = container_of(l2, struct threshold, list);
    45	
    46		return t1->temperature - t2->temperature;
    47	}
    48	
    49	static struct threshold *__thermal_thresholds_find(const struct thresholds *thresholds, int temperature)
    50	{
    51		struct threshold *t;
    52	
    53		list_for_each_entry(t, &thresholds->list, list)
    54			if (t->temperature == temperature)
    55				return t;
    56	
    57		return NULL;
    58	}
    59	
    60	static bool __thermal_threshold_is_crossed(struct threshold *threshold, int temperature,
    61						   int last_temperature, int direction,
    62						   int *low, int *high)
    63	{
    64		if (temperature > threshold->temperature && threshold->temperature > *low &&
    65		    (THERMAL_THRESHOLD_WAY_DOWN & threshold->direction))
    66			*low = threshold->temperature;
    67	
    68		if (temperature < threshold->temperature && threshold->temperature < *high &&
    69		    (THERMAL_THRESHOLD_WAY_UP & threshold->direction))
    70			*high = threshold->temperature;
    71	
    72		if (temperature < threshold->temperature &&
    73		    last_temperature >= threshold->temperature &&
    74		    (threshold->direction & direction))
    75			return true;
    76	
    77		if (temperature >= threshold->temperature &&
    78		    last_temperature < threshold->temperature &&
    79		    (threshold->direction & direction))
    80			return true;
    81	
    82		return false;
    83	}
    84	
    85	static bool thermal_thresholds_handle_raising(struct thresholds *thresholds, int temperature,
    86						      int last_temperature, int *low, int *high)
    87	{
    88		struct threshold *t;
    89	
    90		list_for_each_entry(t, &thresholds->list, list) {
    91			if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
    92							   THERMAL_THRESHOLD_WAY_UP, low, high))
    93				return true;
    94		}
    95	
    96		return false;
    97	}
    98	
    99	static bool thermal_thresholds_handle_dropping(struct thresholds *thresholds, int temperature,
   100						       int last_temperature, int *low, int *high)
   101	{
   102		struct threshold *t;
   103	
   104		list_for_each_entry_reverse(t, &thresholds->list, list) {
   105			if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
   106							   THERMAL_THRESHOLD_WAY_DOWN, low, high))
   107				return true;
   108		}
   109	
   110		return false;
   111	}
   112	
   113	void thermal_thresholds_flush(struct thermal_zone_device *tz)
   114	{
   115		struct thresholds *thresholds = tz->thresholds;
   116		struct threshold *entry, *tmp;
   117	
   118		lockdep_assert_held(&tz->lock);
   119	
   120		list_for_each_entry_safe(entry, tmp, &thresholds->list, list) {
   121			list_del(&entry->list);
   122			kfree(entry);
   123		}
   124	
   125		__thermal_zone_device_update(tz, THERMAL_THRESHOLD_FLUSHED);
   126	}
   127	
   128	int thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
   129	{
   130		struct thresholds *thresholds = tz->thresholds;
   131	
   132		int temperature = tz->temperature;
   133		int last_temperature = tz->last_temperature;
   134		bool notify;
   135	
   136		lockdep_assert_held(&tz->lock);
   137	
   138		/*
   139		 * We need a second update in order to detect a threshold being crossed
   140		 */
   141		if (last_temperature == THERMAL_TEMP_INVALID)
   142			return 0;
   143	
   144		/*
   145		 * The temperature is stable, so obviously we can not have
   146		 * crossed a threshold.
   147		 */
   148		if (last_temperature == temperature)
   149			return 0;
   150	
   151		/*
   152		 * Since last update the temperature:
   153		 * - increased : thresholds are crossed the way up
   154		 * - decreased : thresholds are crossed the way down
   155		 */
   156		if (temperature > last_temperature)
   157			notify = thermal_thresholds_handle_raising(thresholds, temperature,
   158								   last_temperature, low, high);
   159		else
   160			notify = thermal_thresholds_handle_dropping(thresholds, temperature,
   161								    last_temperature, low, high);
   162	
   163		if (notify)
   164			pr_debug("A threshold has been crossed the way %s, with a temperature=%d, last_temperature=%d\n",
   165				 temperature > last_temperature ? "up" : "down", temperature, last_temperature);
   166	
   167		return 0;
   168	}
   169	
   170	int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction)
   171	{
   172		struct thresholds *thresholds = tz->thresholds;
   173		struct threshold *t;
   174	
   175		lockdep_assert_held(&tz->lock);
   176	
   177		t = __thermal_thresholds_find(thresholds, temperature);
   178		if (t) {
   179			if (t->direction == direction)
   180				return -EEXIST;
   181	
   182			t->direction |= direction;
   183		} else {
   184	
 > 185			t = kmalloc(sizeof(*t), GFP_KERNEL);
   186			if (!t)
   187				return -ENOMEM;
   188	
   189			INIT_LIST_HEAD(&t->list);
   190			t->temperature = temperature;
   191			t->direction = direction;
   192			list_add(&t->list, &thresholds->list);
   193			list_sort(NULL, &thresholds->list, __thermal_thresholds_cmp);
   194		}
   195	
   196		__thermal_zone_device_update(tz, THERMAL_THRESHOLD_ADDED);
   197	
   198		return 0;
   199	}
   200
diff mbox series

Patch

diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index ed16897584b4..84f9643678d6 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -40,6 +40,21 @@  config THERMAL_DEBUGFS
 	  Say Y to allow the thermal subsystem to collect diagnostic
 	  information that can be accessed via debugfs.
 
+config THERMAL_THRESHOLDS
+	bool "Thermal thresholds notification mechanism"
+	depends on THERMAL_NETLINK
+	help
+	  The userspace implements thermal engines which needs to get
+	  notified when temperature thresholds are crossed the way up
+	  and down. These notification allow them to analyze the
+	  thermal situation of the platform and take decision to
+	  fulfill specific thermal profile like 'balanced',
+	  'performance' or 'power saving'. In addition, the
+	  temperature of the skin sensor is very important in this
+	  case and must be monitored as well.
+
+	  If in doubt, say Y
+
 config THERMAL_EMERGENCY_POWEROFF_DELAY_MS
 	int "Emergency poweroff delay in milli-seconds"
 	default 0
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index ce7a4752ef52..3b991b1a7db4 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -7,6 +7,9 @@  obj-$(CONFIG_THERMAL)		+= thermal_sys.o
 thermal_sys-y			+= thermal_core.o thermal_sysfs.o
 thermal_sys-y			+= thermal_trip.o thermal_helpers.o
 
+# thermal thresholds
+thermal_sys-$(CONFIG_THERMAL_THRESHOLDS)	+= thermal_thresholds.o
+
 # netlink interface to manage the thermal framework
 thermal_sys-$(CONFIG_THERMAL_NETLINK)		+= thermal_netlink.o
 
diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h
index 67a09f90eb95..0742c0f03d46 100644
--- a/drivers/thermal/thermal_core.h
+++ b/drivers/thermal/thermal_core.h
@@ -13,6 +13,7 @@ 
 #include <linux/thermal.h>
 
 #include "thermal_netlink.h"
+#include "thermal_thresholds.h"
 #include "thermal_debugfs.h"
 
 struct thermal_trip_desc {
@@ -132,6 +133,9 @@  struct thermal_zone_device {
 	bool resuming;
 #ifdef CONFIG_THERMAL_DEBUGFS
 	struct thermal_debugfs *debugfs;
+#endif
+#ifdef CONFIG_THERMAL_THRESHOLDS
+	struct thresholds *thresholds;
 #endif
 	struct thermal_trip_desc trips[] __counted_by(num_trips);
 };
diff --git a/drivers/thermal/thermal_thresholds.c b/drivers/thermal/thermal_thresholds.c
new file mode 100644
index 000000000000..7506684cca58
--- /dev/null
+++ b/drivers/thermal/thermal_thresholds.c
@@ -0,0 +1,240 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2024 Linaro Limited
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ * Thermal thresholds
+ */
+#include <linux/list.h>
+#include <linux/list_sort.h>
+
+#include "thermal_core.h"
+
+struct thresholds {
+	struct list_head list;
+};
+
+int thermal_thresholds_init(struct thermal_zone_device *tz)
+{
+	struct thresholds *thresholds;
+
+	thresholds = kmalloc(sizeof(*thresholds), GFP_KERNEL);
+	if (!thresholds)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&thresholds->list);
+	tz->thresholds = thresholds;
+
+	return 0;
+}
+
+void thermal_thresholds_exit(struct thermal_zone_device *tz)
+{
+	thermal_thresholds_flush(tz);
+	kfree(tz->thresholds);
+	tz->thresholds = NULL;
+}
+
+static int __thermal_thresholds_cmp(void *data,
+				    const struct list_head *l1,
+				    const struct list_head *l2)
+{
+	struct threshold *t1 = container_of(l1, struct threshold, list);
+	struct threshold *t2 = container_of(l2, struct threshold, list);
+
+	return t1->temperature - t2->temperature;
+}
+
+static struct threshold *__thermal_thresholds_find(const struct thresholds *thresholds, int temperature)
+{
+	struct threshold *t;
+
+	list_for_each_entry(t, &thresholds->list, list)
+		if (t->temperature == temperature)
+			return t;
+
+	return NULL;
+}
+
+static bool __thermal_threshold_is_crossed(struct threshold *threshold, int temperature,
+					   int last_temperature, int direction,
+					   int *low, int *high)
+{
+	if (temperature > threshold->temperature && threshold->temperature > *low &&
+	    (THERMAL_THRESHOLD_WAY_DOWN & threshold->direction))
+		*low = threshold->temperature;
+
+	if (temperature < threshold->temperature && threshold->temperature < *high &&
+	    (THERMAL_THRESHOLD_WAY_UP & threshold->direction))
+		*high = threshold->temperature;
+
+	if (temperature < threshold->temperature &&
+	    last_temperature >= threshold->temperature &&
+	    (threshold->direction & direction))
+		return true;
+
+	if (temperature >= threshold->temperature &&
+	    last_temperature < threshold->temperature &&
+	    (threshold->direction & direction))
+		return true;
+
+	return false;
+}
+
+static bool thermal_thresholds_handle_raising(struct thresholds *thresholds, int temperature,
+					      int last_temperature, int *low, int *high)
+{
+	struct threshold *t;
+
+	list_for_each_entry(t, &thresholds->list, list) {
+		if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
+						   THERMAL_THRESHOLD_WAY_UP, low, high))
+			return true;
+	}
+
+	return false;
+}
+
+static bool thermal_thresholds_handle_dropping(struct thresholds *thresholds, int temperature,
+					       int last_temperature, int *low, int *high)
+{
+	struct threshold *t;
+
+	list_for_each_entry_reverse(t, &thresholds->list, list) {
+		if (__thermal_threshold_is_crossed(t, temperature, last_temperature,
+						   THERMAL_THRESHOLD_WAY_DOWN, low, high))
+			return true;
+	}
+
+	return false;
+}
+
+void thermal_thresholds_flush(struct thermal_zone_device *tz)
+{
+	struct thresholds *thresholds = tz->thresholds;
+	struct threshold *entry, *tmp;
+
+	lockdep_assert_held(&tz->lock);
+
+	list_for_each_entry_safe(entry, tmp, &thresholds->list, list) {
+		list_del(&entry->list);
+		kfree(entry);
+	}
+
+	__thermal_zone_device_update(tz, THERMAL_THRESHOLD_FLUSHED);
+}
+
+int thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
+{
+	struct thresholds *thresholds = tz->thresholds;
+
+	int temperature = tz->temperature;
+	int last_temperature = tz->last_temperature;
+	bool notify;
+
+	lockdep_assert_held(&tz->lock);
+
+	/*
+	 * We need a second update in order to detect a threshold being crossed
+	 */
+	if (last_temperature == THERMAL_TEMP_INVALID)
+		return 0;
+
+	/*
+	 * The temperature is stable, so obviously we can not have
+	 * crossed a threshold.
+	 */
+	if (last_temperature == temperature)
+		return 0;
+
+	/*
+	 * Since last update the temperature:
+	 * - increased : thresholds are crossed the way up
+	 * - decreased : thresholds are crossed the way down
+	 */
+	if (temperature > last_temperature)
+		notify = thermal_thresholds_handle_raising(thresholds, temperature,
+							   last_temperature, low, high);
+	else
+		notify = thermal_thresholds_handle_dropping(thresholds, temperature,
+							    last_temperature, low, high);
+
+	if (notify)
+		pr_debug("A threshold has been crossed the way %s, with a temperature=%d, last_temperature=%d\n",
+			 temperature > last_temperature ? "up" : "down", temperature, last_temperature);
+
+	return 0;
+}
+
+int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction)
+{
+	struct thresholds *thresholds = tz->thresholds;
+	struct threshold *t;
+
+	lockdep_assert_held(&tz->lock);
+
+	t = __thermal_thresholds_find(thresholds, temperature);
+	if (t) {
+		if (t->direction == direction)
+			return -EEXIST;
+
+		t->direction |= direction;
+	} else {
+
+		t = kmalloc(sizeof(*t), GFP_KERNEL);
+		if (!t)
+			return -ENOMEM;
+
+		INIT_LIST_HEAD(&t->list);
+		t->temperature = temperature;
+		t->direction = direction;
+		list_add(&t->list, &thresholds->list);
+		list_sort(NULL, &thresholds->list, __thermal_thresholds_cmp);
+	}
+
+	__thermal_zone_device_update(tz, THERMAL_THRESHOLD_ADDED);
+
+	return 0;
+}
+
+int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction)
+{
+	struct thresholds *thresholds = tz->thresholds;
+	struct threshold *t;
+
+	lockdep_assert_held(&tz->lock);
+
+	t = __thermal_thresholds_find(thresholds, temperature);
+	if (!t)
+		return -ENOENT;
+
+	if (t->direction == direction) {
+		list_del(&t->list);
+		kfree(t);
+	} else {
+		t->direction &= ~direction;
+	}
+
+	__thermal_zone_device_update(tz, THERMAL_THRESHOLD_DELETED);
+
+	return 0;
+}
+
+int thermal_thresholds_for_each(struct thermal_zone_device *tz,
+				int (*cb)(struct threshold *, void *arg), void *arg)
+{
+	struct thresholds *thresholds = tz->thresholds;
+	struct threshold *entry;
+	int ret;
+
+	lockdep_assert_held(&tz->lock);
+
+	list_for_each_entry(entry, &thresholds->list, list) {
+		ret = cb(entry, arg);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
diff --git a/drivers/thermal/thermal_thresholds.h b/drivers/thermal/thermal_thresholds.h
new file mode 100644
index 000000000000..7c8ce150d6d0
--- /dev/null
+++ b/drivers/thermal/thermal_thresholds.h
@@ -0,0 +1,57 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define THERMAL_THRESHOLD_WAY_UP   BIT(0)
+#define THERMAL_THRESHOLD_WAY_DOWN BIT(1)
+
+struct threshold {
+	int temperature;
+	int direction;
+	struct list_head list;
+};
+
+#ifdef CONFIG_THERMAL_THRESHOLDS
+int thermal_thresholds_init(struct thermal_zone_device *tz);
+void thermal_thresholds_exit(struct thermal_zone_device *tz);
+void thermal_thresholds_flush(struct thermal_zone_device *tz);
+int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction);
+int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction);
+int thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high);
+int thermal_thresholds_for_each(struct thermal_zone_device *tz,
+				int (*cb)(struct threshold *, void *arg), void *arg);
+#else
+static inline int thermal_thresholds_init(struct thermal_zone_device *tz)
+{
+	return 0;
+}
+
+static inline void thermal_thresholds_exit(struct thermal_zone_device *tz)
+{
+	;
+}
+
+static inline void thermal_thresholds_flush(struct thermal_zone_device *tz)
+{
+	;
+}
+
+static inline int thermal_thresholds_add(struct thermal_zone_device *tz, int temperature, int direction)
+{
+	return 0;
+}
+
+static inline int thermal_thresholds_delete(struct thermal_zone_device *tz, int temperature, int direction)
+{
+	return 0;
+}
+
+static inline int thermal_thresholds_handle(struct thermal_zone_device *tz, int *low, int *high)
+{
+	return 0;
+}
+
+static inline int thermal_thresholds_for_each(struct thermal_zone_device *tz,
+					      int (*cb)(struct threshold *, void *arg), void *arg)
+{
+	return 0;
+}
+#endif
diff --git a/include/linux/thermal.h b/include/linux/thermal.h
index 25fbf960b474..bf0b4a8218f6 100644
--- a/include/linux/thermal.h
+++ b/include/linux/thermal.h
@@ -55,6 +55,9 @@  enum thermal_notify_event {
 	THERMAL_TZ_BIND_CDEV, /* Cooling dev is bind to the thermal zone */
 	THERMAL_TZ_UNBIND_CDEV, /* Cooling dev is unbind from the thermal zone */
 	THERMAL_INSTANCE_WEIGHT_CHANGED, /* Thermal instance weight changed */
+	THERMAL_THRESHOLD_ADDED, /* Threshold added */
+	THERMAL_THRESHOLD_DELETED, /* Threshold deleted */
+	THERMAL_THRESHOLD_FLUSHED, /* All thresholds deleted */
 };
 
 /**