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 */
 };
 
 /**
