Skip to content

Commit 52d0ad2

Browse files
Sokariocfriedt
authored andcommitted
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 <[email protected]>
1 parent 8a1371c commit 52d0ad2

File tree

13 files changed

+396
-22
lines changed

13 files changed

+396
-22
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: 14 additions & 2 deletions
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++) {
@@ -225,14 +235,16 @@ static int max30101_init(const struct device *dev)
225235
.fifo = (DT_INST_ENUM_IDX(n, smp_ave) << MAX30101_FIFO_CFG_SMP_AVE_SHIFT) | \
226236
(DT_INST_PROP(n, fifo_rollover_en) \
227237
<< MAX30101_FIFO_CFG_ROLLOVER_EN_SHIFT) | \
228-
(DT_INST_PROP(n, fifo_a_full) << MAX30101_FIFO_CFG_FIFO_FULL_SHIFT), \
238+
(DT_INST_PROP(n, fifo_watermark) << MAX30101_FIFO_CFG_FIFO_FULL_SHIFT), \
229239
.mode = DT_INST_ENUM_IDX(n, acq_mode), \
230240
.spo2 = (DT_INST_ENUM_IDX(n, adc_rge) << MAX30101_SPO2_ADC_RGE_SHIFT) | \
231241
(DT_INST_ENUM_IDX(n, smp_sr) << MAX30101_SPO2_SR_SHIFT) | \
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
SENSOR_DEVICE_DT_INST_DEFINE(n, max30101_init, NULL, &max30101_data_##n, \
238250
&max30101_config_##n, POST_KERNEL, \

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: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
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 separately */
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 STS1 FLAGS error");
56+
return;
57+
}
58+
59+
if ((reg & MAX30101_INT_FULL_MASK) &&
60+
(data->trigger_handler[MAX30101_FULL_CB_INDEX] != NULL)) {
61+
data->trigger_handler[MAX30101_FULL_CB_INDEX](
62+
data->dev, data->trigger[MAX30101_FULL_CB_INDEX]);
63+
}
64+
if ((reg & MAX30101_INT_PPG_MASK) &&
65+
(data->trigger_handler[MAX30101_PPG_CB_INDEX] != NULL)) {
66+
data->trigger_handler[MAX30101_PPG_CB_INDEX](data->dev,
67+
data->trigger[MAX30101_PPG_CB_INDEX]);
68+
}
69+
if ((reg & MAX30101_INT_ALC_OVF_MASK) &&
70+
(data->trigger_handler[MAX30101_ALC_CB_INDEX] != NULL)) {
71+
data->trigger_handler[MAX30101_ALC_CB_INDEX](data->dev,
72+
data->trigger[MAX30101_ALC_CB_INDEX]);
73+
}
74+
75+
#if CONFIG_MAX30101_DIE_TEMPERATURE
76+
/* Read INTERRUPT status */
77+
if (i2c_reg_read_byte_dt(&config->i2c, MAX30101_REG_INT_STS2, &reg)) {
78+
LOG_ERR("Trigger worker I2C read STS2 FLAGS error");
79+
return;
80+
}
81+
82+
if ((reg & MAX30101_INT_TEMP_MASK) &&
83+
(data->trigger_handler[MAX30101_TEMP_CB_INDEX] != NULL)) {
84+
data->trigger_handler[MAX30101_TEMP_CB_INDEX](
85+
data->dev, data->trigger[MAX30101_TEMP_CB_INDEX]);
86+
}
87+
#endif /* CONFIG_MAX30101_DIE_TEMPERATURE */
88+
}
89+
90+
int max30101_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
91+
sensor_trigger_handler_t handler)
92+
{
93+
const struct max30101_config *config = dev->config;
94+
struct max30101_data *data = dev->data;
95+
uint8_t mask, index, enable = 0x00;
96+
97+
switch (trig->type) {
98+
case SENSOR_TRIG_FIFO_WATERMARK:
99+
mask = MAX30101_INT_FULL_MASK;
100+
index = MAX30101_FULL_CB_INDEX;
101+
break;
102+
103+
case SENSOR_TRIG_OVERFLOW:
104+
if (trig->chan == SENSOR_CHAN_AMBIENT_LIGHT) {
105+
mask = MAX30101_INT_ALC_OVF_MASK;
106+
index = MAX30101_ALC_CB_INDEX;
107+
} else {
108+
LOG_ERR("Only SENSOR_CHAN_AMBIENT_LIGHT is supported for overflow trigger");
109+
return -EINVAL;
110+
}
111+
break;
112+
113+
case SENSOR_TRIG_DATA_READY:
114+
switch (trig->chan) {
115+
case SENSOR_CHAN_DIE_TEMP:
116+
mask = MAX30101_INT_TEMP_MASK;
117+
index = MAX30101_TEMP_CB_INDEX;
118+
break;
119+
120+
case SENSOR_CHAN_LIGHT:
121+
case SENSOR_CHAN_IR:
122+
case SENSOR_CHAN_RED:
123+
case SENSOR_CHAN_GREEN:
124+
mask = MAX30101_INT_PPG_MASK;
125+
index = MAX30101_PPG_CB_INDEX;
126+
break;
127+
128+
default:
129+
LOG_ERR("Only SENSOR_CHAN_DIE_TEMP and SENSOR_CHAN_LIGHT/IR/RED/GREEN are "
130+
"supported for data ready trigger");
131+
return -EINVAL;
132+
}
133+
break;
134+
135+
default:
136+
LOG_ERR("Unsupported trigger type");
137+
return -EINVAL;
138+
}
139+
140+
if (handler != NULL) {
141+
enable = 0xFF;
142+
}
143+
144+
/* Write the Interrupt enable register */
145+
LOG_DBG("Writing Interrupt enable register: [0x%02X][0x%02X]", mask, enable);
146+
if (i2c_reg_update_byte_dt(&config->i2c, MAX30101_REG_INT_EN1, mask, enable)) {
147+
LOG_ERR("Could not set interrupt enable register");
148+
return -EIO;
149+
}
150+
151+
#if CONFIG_MAX30101_DIE_TEMPERATURE
152+
if (i2c_reg_update_byte_dt(&config->i2c, MAX30101_REG_INT_EN2, mask, enable)) {
153+
LOG_ERR("Could not set interrupt enable register");
154+
return -EIO;
155+
}
156+
157+
/* Start die temperature acquisition */
158+
if (i2c_reg_write_byte_dt(&config->i2c, MAX30101_REG_TEMP_CFG, 1)) {
159+
LOG_ERR("Could not start die temperature acquisition");
160+
return -EIO;
161+
}
162+
#endif /* CONFIG_MAX30101_DIE_TEMPERATURE */
163+
164+
/* CLEAR ALL INTERRUPT STATUS */
165+
uint8_t int_status;
166+
167+
if (i2c_reg_read_byte_dt(&config->i2c, MAX30101_REG_INT_STS1, &int_status)) {
168+
LOG_ERR("Could not get interrupt STATUS register");
169+
return -EIO;
170+
}
171+
172+
if (!!enable) {
173+
data->trigger_handler[index] = handler;
174+
data->trigger[index] = trig;
175+
}
176+
LOG_DBG("TRIGGER %sset [%d][%d]", !!enable ? "" : "un", trig->type, trig->chan);
177+
return 0;
178+
}
179+
180+
int max30101_init_interrupts(const struct device *dev)
181+
{
182+
const struct max30101_config *config = dev->config;
183+
struct max30101_data *data = dev->data;
184+
185+
if (!gpio_is_ready_dt(&config->irq_gpio)) {
186+
LOG_ERR("GPIO is not ready");
187+
return -ENODEV;
188+
}
189+
190+
if (gpio_pin_configure_dt(&config->irq_gpio, GPIO_INPUT)) {
191+
LOG_ERR("Failed to configure GPIO");
192+
return -EIO;
193+
}
194+
195+
if (gpio_pin_interrupt_configure_dt(&config->irq_gpio, GPIO_INT_EDGE_TO_ACTIVE)) {
196+
LOG_ERR("Failed to configure interrupt");
197+
return -EIO;
198+
}
199+
200+
gpio_init_callback(&data->gpio_cb, max30101_gpio_callback_handler,
201+
BIT(config->irq_gpio.pin));
202+
203+
if (gpio_add_callback_dt(&config->irq_gpio, &data->gpio_cb)) {
204+
LOG_ERR("Failed to add GPIO callback");
205+
return -EIO;
206+
}
207+
LOG_DBG("GPIO callback configured");
208+
209+
data->dev = dev;
210+
memset(&(data->trigger_handler[0]), 0, sizeof(data->trigger_handler));
211+
memset(&(data->trigger[0]), 0, sizeof(data->trigger));
212+
k_work_init(&data->cb_work, max30101_work_cb);
213+
214+
return 0;
215+
}

0 commit comments

Comments
 (0)