Skip to content

Commit f7cd45e

Browse files
committed
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>
1 parent 9ee2243 commit f7cd45e

File tree

11 files changed

+361
-16
lines changed

11 files changed

+361
-16
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Makefile - MAX30101 heart rate sensor
22
#
33
# Copyright (c) 2017, NXP
4+
# Copyright (c) 2025, CATIE
45
#
56
# SPDX-License-Identifier: Apache-2.0
67
#
78
zephyr_library()
89

910
zephyr_library_sources(max30101.c)
11+
zephyr_library_sources_ifdef(CONFIG_MAX30101_TRIGGER max30101_trigger.c)

drivers/sensor/maxim/max30101/Kconfig

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,56 @@
55
#
66
# SPDX-License-Identifier: Apache-2.0
77

8-
config MAX30101
8+
menuconfig MAX30101
99
bool "MAX30101 Pulse Oximeter and Heart Rate Sensor"
1010
default y
1111
depends on DT_HAS_MAXIM_MAX30101_ENABLED
1212
select I2C if $(dt_compat_on_bus,$(DT_COMPAT_MAXIM_MAX30101),i2c)
13+
14+
if MAX30101
15+
16+
choice MAX30101_TRIGGER_MODE
17+
prompt "Trigger mode"
18+
default MAX30101_TRIGGER_NONE
19+
help
20+
Specify the type of triggering to be used by the driver.
21+
22+
config MAX30101_TRIGGER_NONE
23+
bool "No trigger"
24+
25+
config MAX30101_TRIGGER_GLOBAL_THREAD
26+
bool "Use global thread"
27+
depends on GPIO
28+
depends on $(dt_compat_any_has_prop,$(DT_COMPAT_MAXIM_MAX30101),irq-gpios)
29+
select MAX30101_TRIGGER
30+
31+
config MAX30101_TRIGGER_OWN_THREAD
32+
bool "Use own thread"
33+
depends on GPIO
34+
depends on $(dt_compat_any_has_prop,$(DT_COMPAT_MAXIM_MAX30101),irq-gpios)
35+
select MAX30101_TRIGGER
36+
37+
endchoice
38+
39+
config MAX30101_TRIGGER
40+
bool
41+
42+
if MAX30101_TRIGGER
43+
44+
config MAX30101_THREAD_PRIORITY
45+
int "Thread priority"
46+
depends on MAX30101_TRIGGER_OWN_THREAD
47+
default 10
48+
help
49+
Priority of thread used by the driver to handle interrupts.
50+
51+
config MAX30101_THREAD_SIZE
52+
int "Thread stack size"
53+
depends on MAX30101_TRIGGER_OWN_THREAD
54+
default 2048
55+
help
56+
Stack size of thread used by the driver to handle interrupts.
57+
58+
endif # MAX30101_TRIGGER
59+
60+
endif # MAX30101

drivers/sensor/maxim/max30101/max30101.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ static int max30101_channel_get(const struct device *dev,
9191
static DEVICE_API(sensor, max30101_driver_api) = {
9292
.sample_fetch = max30101_sample_fetch,
9393
.channel_get = max30101_channel_get,
94+
#if CONFIG_MAX30101_TRIGGER
95+
.trigger_set = max30101_trigger_set,
96+
#endif
9497
};
9598

9699
static int max30101_init(const struct device *dev)
@@ -183,6 +186,13 @@ static int max30101_init(const struct device *dev)
183186
}
184187
}
185188

189+
#if CONFIG_MAX30101_TRIGGER
190+
if (max30101_init_interrupts(dev)) {
191+
LOG_ERR("Failed to initialize interrupts");
192+
return -EIO;
193+
}
194+
#endif
195+
186196
/* Initialize the channel map and active channel count */
187197
data->num_channels = 0U;
188198
for (led_chan = 0U; led_chan < MAX30101_MAX_NUM_CHANNELS; led_chan++) {
@@ -232,7 +242,9 @@ static int max30101_init(const struct device *dev)
232242
(DT_INST_ENUM_IDX(n, led_pw) << MAX30101_SPO2_PW_SHIFT), \
233243
.led_pa = DT_INST_PROP(n, led_pa), \
234244
.slot = MAX30101_SLOT_CFG(n), \
235-
}; \
245+
IF_ENABLED(CONFIG_MAX30101_TRIGGER, \
246+
(.irq_gpio = GPIO_DT_SPEC_INST_GET_OR(n, irq_gpios, {0}),) \
247+
) }; \
236248
static struct max30101_data max30101_data_##n; \
237249
DEVICE_DT_INST_DEFINE(n, max30101_init, NULL, &max30101_data_##n, &max30101_config_##n, \
238250
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &max30101_driver_api);

drivers/sensor/maxim/max30101/max30101.h

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@
3232
#define MAX30101_REG_REV_ID 0xfe
3333
#define MAX30101_REG_PART_ID 0xff
3434

35-
#define MAX30101_INT_PPG_MASK (1 << 6)
36-
3735
#define MAX30101_FIFO_CFG_SMP_AVE_SHIFT 5
3836
#define MAX30101_FIFO_CFG_ROLLOVER_EN_SHIFT 4
3937
#define MAX30101_FIFO_CFG_FIFO_FULL_SHIFT 0
@@ -58,6 +56,27 @@
5856
#define MAX30101_FIFO_DATA_BITS 18
5957
#define MAX30101_FIFO_DATA_MASK ((1 << MAX30101_FIFO_DATA_BITS) - 1)
6058

59+
#if CONFIG_MAX30101_TRIGGER
60+
#define MAX30101_SUPPORTED_INTERRUPTS 4 /* FIFO_FULL | PPG | ALC | TEMP */
61+
62+
enum max30101_callback_idx {
63+
MAX30101_FULL_CB_INDEX = 0,
64+
MAX30101_PPG_CB_INDEX = 1,
65+
MAX30101_ALC_CB_INDEX = 2,
66+
MAX30101_TEMP_CB_INDEX = 3,
67+
};
68+
69+
#define MAX30101_INT_FULL_MASK BIT(7) /* FIFO full */
70+
#define MAX30101_INT_PPG_MASK BIT(6) /* PPG data ready */
71+
#define MAX30101_INT_ALC_OVF_MASK BIT(5) /* Ambient Light Cancellation overflow */
72+
#define MAX30101_INT_TEMP_MASK BIT(1) /* DIE Temperature data ready */
73+
#define MAX30101_STAT_POR_MASK BIT(0) /* Power on Reset status */
74+
75+
/* SPO2 channels RED/IR/GREEN */
76+
#define MAX30101_SENSOR_PPG_CHANNEL_MIN SENSOR_CHAN_IR
77+
#define MAX30101_SENSOR_PPG_CHANNEL_MAX SENSOR_CHAN_GREEN
78+
#endif
79+
6180
enum max30101_mode {
6281
MAX30101_MODE_HEART_RATE = 2,
6382
MAX30101_MODE_SPO2 = 3,
@@ -102,10 +121,27 @@ struct max30101_config {
102121
uint8_t led_pa[MAX30101_MAX_NUM_CHANNELS];
103122
uint8_t mode;
104123
uint8_t slot[4];
124+
#if CONFIG_MAX30101_TRIGGER
125+
const struct gpio_dt_spec irq_gpio;
126+
#endif
105127
};
106128

107129
struct max30101_data {
108130
uint32_t raw[MAX30101_MAX_NUM_CHANNELS];
109131
uint8_t map[MAX30101_MAX_NUM_CHANNELS];
110132
uint8_t num_channels;
133+
#if CONFIG_MAX30101_TRIGGER
134+
const struct device *dev;
135+
struct gpio_callback gpio_cb;
136+
sensor_trigger_handler_t trigger_handler[MAX30101_SUPPORTED_INTERRUPTS];
137+
const struct sensor_trigger *trigger[MAX30101_SUPPORTED_INTERRUPTS];
138+
struct k_work cb_work;
139+
#endif
111140
};
141+
142+
#ifdef CONFIG_MAX30101_TRIGGER
143+
int max30101_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
144+
sensor_trigger_handler_t handler);
145+
146+
int max30101_init_interrupts(const struct device *dev);
147+
#endif
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Copyright (c) 2025, CATIE
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/logging/log.h>
8+
9+
#include "max30101.h"
10+
11+
LOG_MODULE_DECLARE(MAX30101, CONFIG_SENSOR_LOG_LEVEL);
12+
13+
#if CONFIG_MAX30101_TRIGGER_OWN_THREAD
14+
K_THREAD_STACK_DEFINE(max30101_workqueue_stack, CONFIG_MAX30101_THREAD_SIZE);
15+
static struct k_work_q max30101_workqueue;
16+
17+
static int max30101_workqueue_init(void)
18+
{
19+
k_work_queue_init(&max30101_workqueue);
20+
k_work_queue_start(&max30101_workqueue, max30101_workqueue_stack,
21+
K_THREAD_STACK_SIZEOF(max30101_workqueue_stack),
22+
CONFIG_MAX30101_THREAD_PRIORITY, NULL);
23+
24+
return 0;
25+
}
26+
27+
/* The work-queue is shared across all instances, hence it is initialized separatedly */
28+
SYS_INIT(max30101_workqueue_init, POST_KERNEL, CONFIG_I2C_INIT_PRIORITY);
29+
#endif /* CONFIG_MAX30101_TRIGGER_OWN_THREAD */
30+
31+
static void max30101_gpio_callback_handler(const struct device *p_port, struct gpio_callback *p_cb,
32+
uint32_t pins)
33+
{
34+
ARG_UNUSED(p_port);
35+
ARG_UNUSED(pins);
36+
37+
struct max30101_data *data = CONTAINER_OF(p_cb, struct max30101_data, gpio_cb);
38+
39+
/* Using work queue to exit isr context */
40+
#if CONFIG_MAX30101_TRIGGER_OWN_THREAD
41+
k_work_submit_to_queue(&max30101_workqueue, &data->cb_work);
42+
#else
43+
k_work_submit(&data->cb_work);
44+
#endif /* CONFIG_MAX30101_TRIGGER_OWN_THREAD */
45+
}
46+
47+
static void max30101_work_cb(struct k_work *p_work)
48+
{
49+
struct max30101_data *data = CONTAINER_OF(p_work, struct max30101_data, cb_work);
50+
const struct max30101_config *config = data->dev->config;
51+
uint8_t reg;
52+
53+
/* Read INTERRUPT status */
54+
if (i2c_reg_read_byte_dt(&config->i2c, MAX30101_REG_INT_STS1, &reg)) {
55+
LOG_ERR("Trigger worker I2C read FLAGS error");
56+
return;
57+
}
58+
59+
if ((reg & MAX30101_INT_FULL_MASK) && (data->trigger_handler[MAX30101_FULL_CB_INDEX] != NULL)) {
60+
data->trigger_handler[MAX30101_FULL_CB_INDEX](
61+
data->dev, data->trigger[MAX30101_FULL_CB_INDEX]);
62+
}
63+
if ((reg & MAX30101_INT_PPG_MASK) && (data->trigger_handler[MAX30101_PPG_CB_INDEX] != NULL)) {
64+
data->trigger_handler[MAX30101_PPG_CB_INDEX](
65+
data->dev, data->trigger[MAX30101_PPG_CB_INDEX]);
66+
}
67+
if ((reg & MAX30101_INT_ALC_OVF_MASK) && (data->trigger_handler[MAX30101_ALC_CB_INDEX] != NULL)) {
68+
data->trigger_handler[MAX30101_ALC_CB_INDEX](
69+
data->dev, data->trigger[MAX30101_ALC_CB_INDEX]);
70+
}
71+
if ((reg & MAX30101_INT_TEMP_MASK) && (data->trigger_handler[MAX30101_TEMP_CB_INDEX] != NULL)) {
72+
data->trigger_handler[MAX30101_TEMP_CB_INDEX](
73+
data->dev, data->trigger[MAX30101_TEMP_CB_INDEX]);
74+
}
75+
}
76+
77+
int max30101_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
78+
sensor_trigger_handler_t handler)
79+
{
80+
const struct max30101_config *config = dev->config;
81+
struct max30101_data *data = dev->data;
82+
uint8_t mask, index, enable = 0x00;
83+
84+
switch (trig->type) {
85+
case SENSOR_TRIG_FIFO_FULL:
86+
mask = MAX30101_INT_FULL_MASK;
87+
index = MAX30101_FULL_CB_INDEX;
88+
break;
89+
90+
case SENSOR_TRIG_OVERFLOW:
91+
if (trig->chan == SENSOR_CHAN_AMBIENT_LIGHT) {
92+
mask = MAX30101_INT_ALC_OVF_MASK;
93+
index = MAX30101_ALC_CB_INDEX;
94+
} else {
95+
LOG_ERR("Only SENSOR_CHAN_LIGHT are supported for overflow trigger");
96+
return -EINVAL;
97+
}
98+
break;
99+
100+
case SENSOR_TRIG_DATA_READY:
101+
switch (trig->chan) {
102+
case SENSOR_CHAN_DIE_TEMP:
103+
mask = MAX30101_INT_TEMP_MASK;
104+
index = MAX30101_TEMP_CB_INDEX;
105+
break;
106+
107+
case SENSOR_CHAN_LIGHT:
108+
case SENSOR_CHAN_IR:
109+
case SENSOR_CHAN_RED:
110+
case SENSOR_CHAN_GREEN:
111+
mask = MAX30101_INT_PPG_MASK;
112+
index = MAX30101_PPG_CB_INDEX;
113+
break;
114+
115+
default:
116+
LOG_ERR("Only SENSOR_CHAN_DIE_TEMP and SENSOR_CHAN_LIGHT/IR/RED/GREEN are "
117+
"supported for data ready trigger");
118+
return -EINVAL;
119+
}
120+
break;
121+
122+
default:
123+
LOG_ERR("Unsupported trigger type");
124+
return -EINVAL;
125+
}
126+
127+
if (handler != NULL) {
128+
enable = 0xFF;
129+
}
130+
131+
/* Write the Interrupt enable register */
132+
LOG_DBG("Writing Interrupt enable register: [0x%02X][0x%02X]", mask, enable);
133+
if (i2c_reg_update_byte_dt(&config->i2c, MAX30101_REG_INT_EN1, mask, enable)) {
134+
LOG_ERR("Could not set interrupt enable register");
135+
return -EIO;
136+
}
137+
138+
/* CLEAR ALL INTERRUPT STATUS */
139+
uint8_t int_status;
140+
141+
if (i2c_reg_read_byte_dt(&config->i2c, MAX30101_REG_INT_STS1, &int_status)) {
142+
LOG_ERR("Could not get interrupt STATUS register");
143+
return -EIO;
144+
}
145+
146+
if (!!enable) {
147+
data->trigger_handler[index] = handler;
148+
data->trigger[index] = trig;
149+
}
150+
LOG_DBG("TRIGGER %sset [%d][%d]", !!enable ? "" : "un", trig->type, trig->chan);
151+
return 0;
152+
}
153+
154+
int max30101_init_interrupts(const struct device *dev)
155+
{
156+
const struct max30101_config *config = dev->config;
157+
struct max30101_data *data = dev->data;
158+
159+
if (!gpio_is_ready_dt(&config->irq_gpio)) {
160+
LOG_ERR("GPIO is not ready");
161+
return -ENODEV;
162+
}
163+
164+
if (gpio_pin_configure_dt(&config->irq_gpio, GPIO_INPUT)) {
165+
LOG_ERR("Failed to configure GPIO");
166+
return -EIO;
167+
}
168+
169+
if (gpio_pin_interrupt_configure_dt(&config->irq_gpio, GPIO_INT_EDGE_TO_ACTIVE)) {
170+
LOG_ERR("Failed to configure interrupt");
171+
return -EIO;
172+
}
173+
174+
gpio_init_callback(&data->gpio_cb, max30101_gpio_callback_handler,
175+
BIT(config->irq_gpio.pin));
176+
177+
if (gpio_add_callback_dt(&config->irq_gpio, &data->gpio_cb)) {
178+
LOG_ERR("Failed to add GPIO callback");
179+
return -EIO;
180+
}
181+
LOG_DBG("GPIO callback configured");
182+
183+
data->dev = dev;
184+
memset(&(data->trigger_handler[0]), 0, sizeof(data->trigger_handler));
185+
memset(&(data->trigger[0]), 0, sizeof(data->trigger));
186+
k_work_init(&data->cb_work, max30101_work_cb);
187+
188+
return 0;
189+
}

dts/bindings/sensor/maxim,max30101.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ compatible: "maxim,max30101"
1212
include: [sensor-device.yaml, i2c-device.yaml]
1313

1414
properties:
15+
irq-gpios:
16+
type: phandle-array
17+
description: |
18+
Active low interrupt signal. It is an open drain signal, so it
19+
require either hardware or software pull-up.
1520
fifo-rollover-en:
1621
type: boolean
1722
description: |

include/zephyr/drivers/sensor.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ enum sensor_channel {
100100
SENSOR_CHAN_PROX,
101101
/** Humidity, in percent. */
102102
SENSOR_CHAN_HUMIDITY,
103+
/** Ambient illuminance in visible spectrum, in lux. */
104+
SENSOR_CHAN_AMBIENT_LIGHT,
103105
/** Illuminance in visible spectrum, in lux. */
104106
SENSOR_CHAN_LIGHT,
105107
/** Illuminance in infra-red spectrum, in lux. */
@@ -280,6 +282,9 @@ enum sensor_trigger_type {
280282
/** Trigger fires when a tilt is detected. */
281283
SENSOR_TRIG_TILT,
282284

285+
/** Trigger fires when data overflows. */
286+
SENSOR_TRIG_OVERFLOW,
287+
283288
/**
284289
* Number of all common sensor triggers.
285290
*/

0 commit comments

Comments
 (0)