drivers: sensor: max30101: Enhanced driver to support triggers

The max30101 sensor driver doesn't support triggers.
Add `.trigger_set` API and corresponding Kconfig and
device tree parameters. Add `SENSOR_CHAN_AMBIENT_LIGHT`
and `SENSOR_TRIG_OVERFLOW`.

Signed-off-by: Logan Saint-Germain <l.saintgermain@catie.fr>
diff --git a/drivers/sensor/maxim/max30101/CMakeLists.txt b/drivers/sensor/maxim/max30101/CMakeLists.txt
index 063cffa..9ba664e 100644
--- a/drivers/sensor/maxim/max30101/CMakeLists.txt
+++ b/drivers/sensor/maxim/max30101/CMakeLists.txt
@@ -1,9 +1,11 @@
 # Makefile - MAX30101 heart rate sensor
 #
 # Copyright (c) 2017, NXP
+# Copyright (c) 2025, CATIE
 #
 # SPDX-License-Identifier: Apache-2.0
 #
 zephyr_library()
 
 zephyr_library_sources(max30101.c)
+zephyr_library_sources_ifdef(CONFIG_MAX30101_TRIGGER max30101_trigger.c)
diff --git a/drivers/sensor/maxim/max30101/Kconfig b/drivers/sensor/maxim/max30101/Kconfig
index 164ec71..ebbdcb4 100644
--- a/drivers/sensor/maxim/max30101/Kconfig
+++ b/drivers/sensor/maxim/max30101/Kconfig
@@ -5,8 +5,56 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
-config MAX30101
+menuconfig MAX30101
 	bool "MAX30101 Pulse Oximeter and Heart Rate Sensor"
 	default y
 	depends on DT_HAS_MAXIM_MAX30101_ENABLED
 	select I2C if $(dt_compat_on_bus,$(DT_COMPAT_MAXIM_MAX30101),i2c)
+
+if MAX30101
+
+choice MAX30101_TRIGGER_MODE
+	prompt "Trigger mode"
+	default MAX30101_TRIGGER_NONE
+	help
+	  Specify the type of triggering to be used by the driver.
+
+config MAX30101_TRIGGER_NONE
+	bool "No trigger"
+
+config MAX30101_TRIGGER_GLOBAL_THREAD
+	bool "Use global thread"
+	depends on GPIO
+	depends on $(dt_compat_any_has_prop,$(DT_COMPAT_MAXIM_MAX30101),irq-gpios)
+	select MAX30101_TRIGGER
+
+config MAX30101_TRIGGER_OWN_THREAD
+	bool "Use own thread"
+	depends on GPIO
+	depends on $(dt_compat_any_has_prop,$(DT_COMPAT_MAXIM_MAX30101),irq-gpios)
+	select MAX30101_TRIGGER
+
+endchoice
+
+config MAX30101_TRIGGER
+	bool
+
+if MAX30101_TRIGGER
+
+config MAX30101_THREAD_PRIORITY
+	int "Thread priority"
+	depends on MAX30101_TRIGGER_OWN_THREAD
+	default 10
+	help
+	  Priority of thread used by the driver to handle interrupts.
+
+config MAX30101_THREAD_SIZE
+	int "Thread stack size"
+	depends on MAX30101_TRIGGER_OWN_THREAD
+	default 2048
+	help
+	  Stack size of thread used by the driver to handle interrupts.
+
+endif # MAX30101_TRIGGER
+
+endif # MAX30101
diff --git a/drivers/sensor/maxim/max30101/max30101.c b/drivers/sensor/maxim/max30101/max30101.c
index b32adfd..505eb4d 100644
--- a/drivers/sensor/maxim/max30101/max30101.c
+++ b/drivers/sensor/maxim/max30101/max30101.c
@@ -91,6 +91,9 @@
 static DEVICE_API(sensor, max30101_driver_api) = {
 	.sample_fetch = max30101_sample_fetch,
 	.channel_get = max30101_channel_get,
+#if CONFIG_MAX30101_TRIGGER
+	.trigger_set = max30101_trigger_set,
+#endif
 };
 
 static int max30101_init(const struct device *dev)
@@ -183,6 +186,13 @@
 		}
 	}
 
+#if CONFIG_MAX30101_TRIGGER
+	if (max30101_init_interrupts(dev)) {
+		LOG_ERR("Failed to initialize interrupts");
+		return -EIO;
+	}
+#endif
+
 	/* Initialize the channel map and active channel count */
 	data->num_channels = 0U;
 	for (led_chan = 0U; led_chan < MAX30101_MAX_NUM_CHANNELS; led_chan++) {
@@ -225,14 +235,16 @@
 		.fifo = (DT_INST_ENUM_IDX(n, smp_ave) << MAX30101_FIFO_CFG_SMP_AVE_SHIFT) |        \
 			(DT_INST_PROP(n, fifo_rollover_en)                                         \
 			 << MAX30101_FIFO_CFG_ROLLOVER_EN_SHIFT) |                                 \
-			(DT_INST_PROP(n, fifo_a_full) << MAX30101_FIFO_CFG_FIFO_FULL_SHIFT),       \
+			(DT_INST_PROP(n, fifo_watermark) << MAX30101_FIFO_CFG_FIFO_FULL_SHIFT),    \
 		.mode = DT_INST_ENUM_IDX(n, acq_mode),                                             \
 		.spo2 = (DT_INST_ENUM_IDX(n, adc_rge) << MAX30101_SPO2_ADC_RGE_SHIFT) |            \
 			(DT_INST_ENUM_IDX(n, smp_sr) << MAX30101_SPO2_SR_SHIFT) |                  \
 			(DT_INST_ENUM_IDX(n, led_pw) << MAX30101_SPO2_PW_SHIFT),                   \
 		.led_pa = DT_INST_PROP(n, led_pa),                                                 \
 		.slot = MAX30101_SLOT_CFG(n),                                                      \
-	};                                                                                         \
+		IF_ENABLED(CONFIG_MAX30101_TRIGGER, \
+			(.irq_gpio = GPIO_DT_SPEC_INST_GET_OR(n, irq_gpios, {0}),) \
+		) };              \
 	static struct max30101_data max30101_data_##n;                                             \
 	SENSOR_DEVICE_DT_INST_DEFINE(n, max30101_init, NULL, &max30101_data_##n,                   \
 				     &max30101_config_##n, POST_KERNEL,                            \
diff --git a/drivers/sensor/maxim/max30101/max30101.h b/drivers/sensor/maxim/max30101/max30101.h
index 00aacdc..286193f 100644
--- a/drivers/sensor/maxim/max30101/max30101.h
+++ b/drivers/sensor/maxim/max30101/max30101.h
@@ -32,8 +32,6 @@
 #define MAX30101_REG_REV_ID		0xfe
 #define MAX30101_REG_PART_ID		0xff
 
-#define MAX30101_INT_PPG_MASK		(1 << 6)
-
 #define MAX30101_FIFO_CFG_SMP_AVE_SHIFT		5
 #define MAX30101_FIFO_CFG_ROLLOVER_EN_SHIFT     4
 #define MAX30101_FIFO_CFG_FIFO_FULL_SHIFT	0
@@ -58,6 +56,27 @@
 #define MAX30101_FIFO_DATA_BITS		18
 #define MAX30101_FIFO_DATA_MASK		((1 << MAX30101_FIFO_DATA_BITS) - 1)
 
+#if CONFIG_MAX30101_TRIGGER
+#define MAX30101_SUPPORTED_INTERRUPTS 4 /* FIFO_FULL | PPG | ALC | TEMP */
+
+enum max30101_callback_idx {
+	MAX30101_FULL_CB_INDEX = 0,
+	MAX30101_PPG_CB_INDEX = 1,
+	MAX30101_ALC_CB_INDEX = 2,
+	MAX30101_TEMP_CB_INDEX = 3,
+};
+
+#define MAX30101_INT_FULL_MASK    BIT(7) /* FIFO full */
+#define MAX30101_INT_PPG_MASK     BIT(6) /* PPG data ready */
+#define MAX30101_INT_ALC_OVF_MASK BIT(5) /* Ambient Light Cancellation overflow */
+#define MAX30101_INT_TEMP_MASK    BIT(1) /* DIE Temperature data ready */
+#define MAX30101_STAT_POR_MASK    BIT(0) /* Power on Reset status */
+
+/* SPO2 channels RED/IR/GREEN */
+#define MAX30101_SENSOR_PPG_CHANNEL_MIN SENSOR_CHAN_IR
+#define MAX30101_SENSOR_PPG_CHANNEL_MAX SENSOR_CHAN_GREEN
+#endif
+
 enum max30101_mode {
 	MAX30101_MODE_HEART_RATE = 2,
 	MAX30101_MODE_SPO2 = 3,
@@ -102,10 +121,27 @@
 	uint8_t led_pa[MAX30101_MAX_NUM_CHANNELS];
 	uint8_t mode;
 	uint8_t slot[4];
+#if CONFIG_MAX30101_TRIGGER
+	const struct gpio_dt_spec irq_gpio;
+#endif
 };
 
 struct max30101_data {
 	uint32_t raw[MAX30101_MAX_NUM_CHANNELS];
 	uint8_t map[MAX30101_MAX_NUM_CHANNELS];
 	uint8_t num_channels;
+#if CONFIG_MAX30101_TRIGGER
+	const struct device *dev;
+	struct gpio_callback gpio_cb;
+	sensor_trigger_handler_t trigger_handler[MAX30101_SUPPORTED_INTERRUPTS];
+	const struct sensor_trigger *trigger[MAX30101_SUPPORTED_INTERRUPTS];
+	struct k_work cb_work;
+#endif
 };
+
+#ifdef CONFIG_MAX30101_TRIGGER
+int max30101_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
+			 sensor_trigger_handler_t handler);
+
+int max30101_init_interrupts(const struct device *dev);
+#endif
diff --git a/drivers/sensor/maxim/max30101/max30101_trigger.c b/drivers/sensor/maxim/max30101/max30101_trigger.c
new file mode 100644
index 0000000..2a9e2e5
--- /dev/null
+++ b/drivers/sensor/maxim/max30101/max30101_trigger.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2025, CATIE
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr/logging/log.h>
+
+#include "max30101.h"
+
+LOG_MODULE_DECLARE(MAX30101, CONFIG_SENSOR_LOG_LEVEL);
+
+#if CONFIG_MAX30101_TRIGGER_OWN_THREAD
+K_THREAD_STACK_DEFINE(max30101_workqueue_stack, CONFIG_MAX30101_THREAD_SIZE);
+static struct k_work_q max30101_workqueue;
+
+static int max30101_workqueue_init(void)
+{
+	k_work_queue_init(&max30101_workqueue);
+	k_work_queue_start(&max30101_workqueue, max30101_workqueue_stack,
+			   K_THREAD_STACK_SIZEOF(max30101_workqueue_stack),
+			   CONFIG_MAX30101_THREAD_PRIORITY, NULL);
+
+	return 0;
+}
+
+/* The work-queue is shared across all instances, hence it is initialized separately */
+SYS_INIT(max30101_workqueue_init, POST_KERNEL, CONFIG_I2C_INIT_PRIORITY);
+#endif /* CONFIG_MAX30101_TRIGGER_OWN_THREAD */
+
+static void max30101_gpio_callback_handler(const struct device *p_port, struct gpio_callback *p_cb,
+					   uint32_t pins)
+{
+	ARG_UNUSED(p_port);
+	ARG_UNUSED(pins);
+
+	struct max30101_data *data = CONTAINER_OF(p_cb, struct max30101_data, gpio_cb);
+
+	/* Using work queue to exit isr context */
+#if CONFIG_MAX30101_TRIGGER_OWN_THREAD
+	k_work_submit_to_queue(&max30101_workqueue, &data->cb_work);
+#else
+	k_work_submit(&data->cb_work);
+#endif /* CONFIG_MAX30101_TRIGGER_OWN_THREAD */
+}
+
+static void max30101_work_cb(struct k_work *p_work)
+{
+	struct max30101_data *data = CONTAINER_OF(p_work, struct max30101_data, cb_work);
+	const struct max30101_config *config = data->dev->config;
+	uint8_t reg;
+
+	/* Read INTERRUPT status */
+	if (i2c_reg_read_byte_dt(&config->i2c, MAX30101_REG_INT_STS1, &reg)) {
+		LOG_ERR("Trigger worker I2C read STS1 FLAGS error");
+		return;
+	}
+
+	if ((reg & MAX30101_INT_FULL_MASK) &&
+	    (data->trigger_handler[MAX30101_FULL_CB_INDEX] != NULL)) {
+		data->trigger_handler[MAX30101_FULL_CB_INDEX](
+			data->dev, data->trigger[MAX30101_FULL_CB_INDEX]);
+	}
+	if ((reg & MAX30101_INT_PPG_MASK) &&
+	    (data->trigger_handler[MAX30101_PPG_CB_INDEX] != NULL)) {
+		data->trigger_handler[MAX30101_PPG_CB_INDEX](data->dev,
+							     data->trigger[MAX30101_PPG_CB_INDEX]);
+	}
+	if ((reg & MAX30101_INT_ALC_OVF_MASK) &&
+	    (data->trigger_handler[MAX30101_ALC_CB_INDEX] != NULL)) {
+		data->trigger_handler[MAX30101_ALC_CB_INDEX](data->dev,
+							     data->trigger[MAX30101_ALC_CB_INDEX]);
+	}
+
+#if CONFIG_MAX30101_DIE_TEMPERATURE
+	/* Read INTERRUPT status */
+	if (i2c_reg_read_byte_dt(&config->i2c, MAX30101_REG_INT_STS2, &reg)) {
+		LOG_ERR("Trigger worker I2C read STS2 FLAGS error");
+		return;
+	}
+
+	if ((reg & MAX30101_INT_TEMP_MASK) &&
+	    (data->trigger_handler[MAX30101_TEMP_CB_INDEX] != NULL)) {
+		data->trigger_handler[MAX30101_TEMP_CB_INDEX](
+			data->dev, data->trigger[MAX30101_TEMP_CB_INDEX]);
+	}
+#endif /* CONFIG_MAX30101_DIE_TEMPERATURE */
+}
+
+int max30101_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
+			 sensor_trigger_handler_t handler)
+{
+	const struct max30101_config *config = dev->config;
+	struct max30101_data *data = dev->data;
+	uint8_t mask, index, enable = 0x00;
+
+	switch (trig->type) {
+	case SENSOR_TRIG_FIFO_WATERMARK:
+		mask = MAX30101_INT_FULL_MASK;
+		index = MAX30101_FULL_CB_INDEX;
+		break;
+
+	case SENSOR_TRIG_OVERFLOW:
+		if (trig->chan == SENSOR_CHAN_AMBIENT_LIGHT) {
+			mask = MAX30101_INT_ALC_OVF_MASK;
+			index = MAX30101_ALC_CB_INDEX;
+		} else {
+			LOG_ERR("Only SENSOR_CHAN_AMBIENT_LIGHT is supported for overflow trigger");
+			return -EINVAL;
+		}
+		break;
+
+	case SENSOR_TRIG_DATA_READY:
+		switch (trig->chan) {
+		case SENSOR_CHAN_DIE_TEMP:
+			mask = MAX30101_INT_TEMP_MASK;
+			index = MAX30101_TEMP_CB_INDEX;
+			break;
+
+		case SENSOR_CHAN_LIGHT:
+		case SENSOR_CHAN_IR:
+		case SENSOR_CHAN_RED:
+		case SENSOR_CHAN_GREEN:
+			mask = MAX30101_INT_PPG_MASK;
+			index = MAX30101_PPG_CB_INDEX;
+			break;
+
+		default:
+			LOG_ERR("Only SENSOR_CHAN_DIE_TEMP and SENSOR_CHAN_LIGHT/IR/RED/GREEN are "
+				"supported for data ready trigger");
+			return -EINVAL;
+		}
+		break;
+
+	default:
+		LOG_ERR("Unsupported trigger type");
+		return -EINVAL;
+	}
+
+	if (handler != NULL) {
+		enable = 0xFF;
+	}
+
+	/* Write the Interrupt enable register */
+	LOG_DBG("Writing Interrupt enable register: [0x%02X][0x%02X]", mask, enable);
+	if (i2c_reg_update_byte_dt(&config->i2c, MAX30101_REG_INT_EN1, mask, enable)) {
+		LOG_ERR("Could not set interrupt enable register");
+		return -EIO;
+	}
+
+#if CONFIG_MAX30101_DIE_TEMPERATURE
+	if (i2c_reg_update_byte_dt(&config->i2c, MAX30101_REG_INT_EN2, mask, enable)) {
+		LOG_ERR("Could not set interrupt enable register");
+		return -EIO;
+	}
+
+	/* Start die temperature acquisition */
+	if (i2c_reg_write_byte_dt(&config->i2c, MAX30101_REG_TEMP_CFG, 1)) {
+		LOG_ERR("Could not start die temperature acquisition");
+		return -EIO;
+	}
+#endif /* CONFIG_MAX30101_DIE_TEMPERATURE */
+
+	/* CLEAR ALL INTERRUPT STATUS */
+	uint8_t int_status;
+
+	if (i2c_reg_read_byte_dt(&config->i2c, MAX30101_REG_INT_STS1, &int_status)) {
+		LOG_ERR("Could not get interrupt STATUS register");
+		return -EIO;
+	}
+
+	if (!!enable) {
+		data->trigger_handler[index] = handler;
+		data->trigger[index] = trig;
+	}
+	LOG_DBG("TRIGGER %sset [%d][%d]", !!enable ? "" : "un", trig->type, trig->chan);
+	return 0;
+}
+
+int max30101_init_interrupts(const struct device *dev)
+{
+	const struct max30101_config *config = dev->config;
+	struct max30101_data *data = dev->data;
+
+	if (!gpio_is_ready_dt(&config->irq_gpio)) {
+		LOG_ERR("GPIO is not ready");
+		return -ENODEV;
+	}
+
+	if (gpio_pin_configure_dt(&config->irq_gpio, GPIO_INPUT)) {
+		LOG_ERR("Failed to configure GPIO");
+		return -EIO;
+	}
+
+	if (gpio_pin_interrupt_configure_dt(&config->irq_gpio, GPIO_INT_EDGE_TO_ACTIVE)) {
+		LOG_ERR("Failed to configure interrupt");
+		return -EIO;
+	}
+
+	gpio_init_callback(&data->gpio_cb, max30101_gpio_callback_handler,
+			   BIT(config->irq_gpio.pin));
+
+	if (gpio_add_callback_dt(&config->irq_gpio, &data->gpio_cb)) {
+		LOG_ERR("Failed to add GPIO callback");
+		return -EIO;
+	}
+	LOG_DBG("GPIO callback configured");
+
+	data->dev = dev;
+	memset(&(data->trigger_handler[0]), 0, sizeof(data->trigger_handler));
+	memset(&(data->trigger[0]), 0, sizeof(data->trigger));
+	k_work_init(&data->cb_work, max30101_work_cb);
+
+	return 0;
+}
diff --git a/drivers/sensor/sensor_shell.c b/drivers/sensor/sensor_shell.c
index 767b0da..369c2c9 100644
--- a/drivers/sensor/sensor_shell.c
+++ b/drivers/sensor/sensor_shell.c
@@ -67,6 +67,7 @@
 	[SENSOR_CHAN_PRESS] = "press",
 	[SENSOR_CHAN_PROX] = "prox",
 	[SENSOR_CHAN_HUMIDITY] = "humidity",
+	[SENSOR_CHAN_AMBIENT_LIGHT] = "ambient_light",
 	[SENSOR_CHAN_LIGHT] = "light",
 	[SENSOR_CHAN_IR] = "ir",
 	[SENSOR_CHAN_RED] = "red",
@@ -217,6 +218,8 @@
 	TRIGGER_DATA_ENTRY(SENSOR_TRIG_STATIONARY, stationary, NULL),
 	TRIGGER_DATA_ENTRY(SENSOR_TRIG_FIFO_WATERMARK, fifo_wm, NULL),
 	TRIGGER_DATA_ENTRY(SENSOR_TRIG_FIFO_FULL, fifo_full, NULL),
+	TRIGGER_DATA_ENTRY(SENSOR_TRIG_TILT, tilt, NULL),
+	TRIGGER_DATA_ENTRY(SENSOR_TRIG_OVERFLOW, overflow, NULL),
 };
 
 /**
diff --git a/dts/bindings/sensor/maxim,max30101.yaml b/dts/bindings/sensor/maxim,max30101.yaml
index 2cf02e2..d5611b5 100644
--- a/dts/bindings/sensor/maxim,max30101.yaml
+++ b/dts/bindings/sensor/maxim,max30101.yaml
@@ -12,6 +12,11 @@
 include: [sensor-device.yaml, i2c-device.yaml]
 
 properties:
+  irq-gpios:
+    type: phandle-array
+    description: |
+      Active low interrupt signal. It is an open drain signal, so it
+      require either hardware or software pull-up.
   fifo-rollover-en:
     type: boolean
     description: |
@@ -20,7 +25,7 @@
       FIFO continues to fill with new data. If not set, then the FIFO is
       not updated until FIFO_DATA is read or the WRITE/READ pointer
       positions are changed.
-  fifo-a-full:
+  fifo-watermark:
     type: int
     default: 0
     enum:
@@ -41,7 +46,7 @@
       - 14 # Each 18 samples @ 14 empty space
       - 15 # Each 17 samples @ 15 empty space
     description: |
-      Configure the trigger for the FIFO_A_FULL interrupt (e.g. if set to 2,
+      Configure the trigger for the FIFO_WATERMARK interrupt (e.g. if set to 2,
       then the flag is set when the 30th word is written to the FIFO).
       Default set to 0, same as after Power Reset. Range: 0 - 15.
   acq-mode:
diff --git a/include/zephyr/drivers/sensor.h b/include/zephyr/drivers/sensor.h
index f6ef9b4..2e92f10 100644
--- a/include/zephyr/drivers/sensor.h
+++ b/include/zephyr/drivers/sensor.h
@@ -100,6 +100,8 @@
 	SENSOR_CHAN_PROX,
 	/** Humidity, in percent. */
 	SENSOR_CHAN_HUMIDITY,
+	/** Ambient illuminance in visible spectrum, in lux. */
+	SENSOR_CHAN_AMBIENT_LIGHT,
 	/** Illuminance in visible spectrum, in lux. */
 	SENSOR_CHAN_LIGHT,
 	/** Illuminance in infra-red spectrum, in lux. */
@@ -282,6 +284,9 @@
 	/** Trigger fires when a tilt is detected. */
 	SENSOR_TRIG_TILT,
 
+	/** Trigger fires when data overflows. */
+	SENSOR_TRIG_OVERFLOW,
+
 	/**
 	 * Number of all common sensor triggers.
 	 */
diff --git a/samples/sensor/heart_rate/README.rst b/samples/sensor/heart_rate/README.rst
index ba1dde5..6b12f81 100644
--- a/samples/sensor/heart_rate/README.rst
+++ b/samples/sensor/heart_rate/README.rst
@@ -2,13 +2,13 @@
    :name: Heart Rate Sensor
    :relevant-api: sensor_interface
 
-   Get heart rate data from a sensor (polling mode).
+   Get heart rate data from a sensor (polling/interrupt mode).
 
 Overview
 ********
 
-A sensor application that demonstrates how to poll data from a heart rate
-sensor.
+A sensor application that demonstrates how to get data from a heart rate
+sensor either by polling or interrupt.
 
 Requirements
 ************
diff --git a/samples/sensor/heart_rate/boards/nrf52840dk_nrf52840.overlay b/samples/sensor/heart_rate/boards/nrf52840dk_nrf52840.overlay
index 8fc90f4..f79ce36 100644
--- a/samples/sensor/heart_rate/boards/nrf52840dk_nrf52840.overlay
+++ b/samples/sensor/heart_rate/boards/nrf52840dk_nrf52840.overlay
@@ -6,7 +6,7 @@
 
 / {
 	aliases {
-		heart-rate-sensor = &bh1790;
+		heart-rate-sensor = &max30101;
 	};
 };
 
@@ -21,4 +21,13 @@
 		compatible = "rohm,bh1790";
 		reg = <0x5b>;
 	};
+
+	/* Example configuration of a MAX30101 device on an I2C bus. */
+	max30101: max30101@57 {
+		compatible = "maxim,max30101";
+		reg = <0x57>;
+		acq-mode = "multi-led";
+		led-slot = <3 0 0 0>;
+		irq-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
+	};
 };
diff --git a/samples/sensor/heart_rate/sample.yaml b/samples/sensor/heart_rate/sample.yaml
index 3d6c889..2b4fae8 100644
--- a/samples/sensor/heart_rate/sample.yaml
+++ b/samples/sensor/heart_rate/sample.yaml
@@ -1,10 +1,23 @@
 sample:
   name: Heart Rate Sensor Sample
+common:
+  filter: dt_alias_exists("heart-rate-sensor")
+  harness: sensor
 tests:
   sample.sensor.heart_rate:
-    harness: sensor
     tags: sensors
     platform_allow: hexiwear/mk64f12
-    depends_on: i2c
+    depends_on:
+      - i2c
     integration_platforms:
       - hexiwear/mk64f12
+  sample.sensor.heart_rate_interrupt:
+    tags: sensors
+    platform_allow: nrf52840dk/nrf52840
+    depends_on:
+      - i2c
+    integration_platforms:
+      - nrf52840dk/nrf52840
+    extra_configs:
+      - CONFIG_GPIO=y
+      - CONFIG_MAX30101_TRIGGER_GLOBAL_THREAD=y
diff --git a/samples/sensor/heart_rate/src/main.c b/samples/sensor/heart_rate/src/main.c
index fe1e92f..55dcadb 100644
--- a/samples/sensor/heart_rate/src/main.c
+++ b/samples/sensor/heart_rate/src/main.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2017, NXP
+ * Copyright (c) 2025, CATIE
  *
  * SPDX-License-Identifier: Apache-2.0
  */
@@ -8,9 +9,30 @@
 #include <zephyr/drivers/sensor.h>
 #include <stdio.h>
 
+#define MAX30101_SENSOR_CHANNEL SENSOR_CHAN_GREEN
+
+static void print_sample_fetch(const struct device *dev)
+{
+	static struct sensor_value green;
+
+	sensor_sample_fetch(dev);
+	sensor_channel_get(dev, MAX30101_SENSOR_CHANNEL, &green);
+
+	/* Print LED data*/
+	printf("GREEN = %d\n", green.val1);
+}
+
+#if CONFIG_MAX30101_TRIGGER
+static struct sensor_trigger trig_drdy;
+
+void sensor_data_ready(const struct device *dev, const struct sensor_trigger *trigger)
+{
+	print_sample_fetch(dev);
+}
+#endif /* CONFIG_MAX30101_TRIGGER */
+
 int main(void)
 {
-	struct sensor_value green;
 	const struct device *const dev = DEVICE_DT_GET(DT_ALIAS(heart_rate_sensor));
 
 	if (dev == NULL) {
@@ -22,12 +44,16 @@
 		return 0;
 	}
 
-	while (1) {
-		sensor_sample_fetch(dev);
-		sensor_channel_get(dev, SENSOR_CHAN_GREEN, &green);
+#if CONFIG_MAX30101_TRIGGER
+	trig_drdy.type = SENSOR_TRIG_DATA_READY;
+	trig_drdy.chan = MAX30101_SENSOR_CHANNEL;
+	sensor_trigger_set(dev, &trig_drdy, sensor_data_ready);
+#endif /* CONFIG_MAX30101_TRIGGER */
 
-		/* Print green LED data*/
-		printf("GREEN=%d\n", green.val1);
+	while (1) {
+#if !CONFIG_MAX30101_TRIGGER
+		print_sample_fetch(dev);
+#endif /* !CONFIG_MAX30101_TRIGGER */
 
 		k_sleep(K_MSEC(20));
 	}
diff --git a/samples/sensor/sensor_shell/pytest/test_sensor_shell.py b/samples/sensor/sensor_shell/pytest/test_sensor_shell.py
index 366c3a9..8256524 100644
--- a/samples/sensor/sensor_shell/pytest/test_sensor_shell.py
+++ b/samples/sensor/sensor_shell/pytest/test_sensor_shell.py
@@ -24,7 +24,7 @@
 
     # Channel should be the last one before 'all' (because 'all' doesn't print anything) so that the
     # for-loop in `parse_named_int()` will go through everything
-    for channel in range(59):
+    for channel in range(65):
         logger.info(f'channel {channel}')
         shell.wait_for_prompt()
         lines = shell.exec_command(f'sensor get sensor@0 {channel}')
@@ -41,7 +41,7 @@
     assert any(['sensor@0(channel=co2, attr=sampling_frequency)' in line for line in lines]), 'expected response not found'
 
     shell.wait_for_prompt()
-    lines = shell.exec_command('sensor attr_get sensor@1 55 3')
+    lines = shell.exec_command('sensor attr_get sensor@1 gauge_state_of_health 3')
     assert any(['sensor@1(channel=gauge_state_of_health, attr=slope_th)' in line for line in lines]), 'expected response not found'
 
     logger.info('response is valid')
@@ -56,7 +56,7 @@
     assert any([expected_line in line for line in lines]), 'expected response not found'
 
     shell.wait_for_prompt()
-    lines = shell.exec_command('sensor attr_set sensor@1 55 3 1')
+    lines = shell.exec_command('sensor attr_set sensor@1 gauge_state_of_health 3 1')
     expected_line = 'sensor@1 channel=gauge_state_of_health, attr=slope_th set to value=1'
     assert any([expected_line in line for line in lines]), 'expected response not found'