1 /* ST Microelectronics LSM6DSV16X 6-axis IMU sensor driver
2 *
3 * Copyright (c) 2023 STMicroelectronics
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Datasheet:
8 * https://www.st.com/resource/en/datasheet/lsm6dsv16x.pdf
9 */
10
11 #define DT_DRV_COMPAT st_lsm6dsv16x
12
13 #include <zephyr/kernel.h>
14 #include <zephyr/drivers/sensor.h>
15 #include <zephyr/drivers/gpio.h>
16 #include <zephyr/logging/log.h>
17
18 #include "lsm6dsv16x.h"
19 #include "lsm6dsv16x_rtio.h"
20
21 LOG_MODULE_DECLARE(LSM6DSV16X, CONFIG_SENSOR_LOG_LEVEL);
22
23 /**
24 * lsm6dsv16x_enable_xl_int - XL enable selected int pin to generate interrupt
25 */
lsm6dsv16x_enable_xl_int(const struct device * dev,int enable)26 static int lsm6dsv16x_enable_xl_int(const struct device *dev, int enable)
27 {
28 const struct lsm6dsv16x_config *cfg = dev->config;
29 stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
30 int ret;
31
32 if (enable) {
33 int16_t buf[3];
34
35 /* dummy read: re-trigger interrupt */
36 lsm6dsv16x_acceleration_raw_get(ctx, buf);
37 }
38
39 /* set interrupt */
40 if ((cfg->drdy_pin == 1) || (ON_I3C_BUS(cfg) && (!I3C_INT_PIN(cfg)))) {
41 lsm6dsv16x_pin_int_route_t val = {};
42
43 ret = lsm6dsv16x_pin_int1_route_get(ctx, &val);
44 if (ret < 0) {
45 LOG_ERR("pint_int1_route_get error");
46 return ret;
47 }
48
49 val.drdy_xl = 1;
50
51 ret = lsm6dsv16x_pin_int1_route_set(ctx, &val);
52 } else {
53 lsm6dsv16x_pin_int_route_t val = {};
54
55 ret = lsm6dsv16x_pin_int2_route_get(ctx, &val);
56 if (ret < 0) {
57 LOG_ERR("pint_int2_route_get error");
58 return ret;
59 }
60
61 val.drdy_xl = 1;
62
63 ret = lsm6dsv16x_pin_int2_route_set(ctx, &val);
64 }
65
66 return ret;
67 }
68
69 /**
70 * lsm6dsv16x_enable_g_int - Gyro enable selected int pin to generate interrupt
71 */
lsm6dsv16x_enable_g_int(const struct device * dev,int enable)72 static int lsm6dsv16x_enable_g_int(const struct device *dev, int enable)
73 {
74 const struct lsm6dsv16x_config *cfg = dev->config;
75 stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
76 int ret;
77
78 if (enable) {
79 int16_t buf[3];
80
81 /* dummy read: re-trigger interrupt */
82 lsm6dsv16x_angular_rate_raw_get(ctx, buf);
83 }
84
85 /* set interrupt */
86 if ((cfg->drdy_pin == 1) || (ON_I3C_BUS(cfg) && (!I3C_INT_PIN(cfg)))) {
87 lsm6dsv16x_pin_int_route_t val = {};
88
89 ret = lsm6dsv16x_pin_int1_route_get(ctx, &val);
90 if (ret < 0) {
91 LOG_ERR("pint_int1_route_get error");
92 return ret;
93 }
94
95 val.drdy_g = 1;
96
97 ret = lsm6dsv16x_pin_int1_route_set(ctx, &val);
98 } else {
99 lsm6dsv16x_pin_int_route_t val = {};
100
101 ret = lsm6dsv16x_pin_int2_route_get(ctx, &val);
102 if (ret < 0) {
103 LOG_ERR("pint_int2_route_get error");
104 return ret;
105 }
106
107 val.drdy_g = 1;
108
109 ret = lsm6dsv16x_pin_int2_route_set(ctx, &val);
110 }
111
112 return ret;
113 }
114
115 /**
116 * lsm6dsv16x_enable_wake_int - Enable selected int pin to generate wakeup interrupt
117 */
lsm6dsv16x_enable_wake_int(const struct device * dev,int enable)118 static int lsm6dsv16x_enable_wake_int(const struct device *dev, int enable)
119 {
120 const struct lsm6dsv16x_config *cfg = dev->config;
121 stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
122 int ret;
123 lsm6dsv16x_interrupt_mode_t int_mode;
124
125 int_mode.enable = enable ? 1 : 0;
126 int_mode.lir = !cfg->drdy_pulsed;
127 ret = lsm6dsv16x_interrupt_enable_set(ctx, int_mode);
128 if (ret < 0) {
129 LOG_ERR("interrupt_enable_set error");
130 return ret;
131 }
132
133 if ((cfg->drdy_pin == 1) || ON_I3C_BUS(cfg)) {
134 lsm6dsv16x_pin_int_route_t val;
135
136 ret = lsm6dsv16x_pin_int1_route_get(ctx, &val);
137 if (ret < 0) {
138 LOG_ERR("pint_int1_route_get error");
139 return ret;
140 }
141
142 val.wakeup = enable ? 1 : 0;
143
144 ret = lsm6dsv16x_pin_int1_route_set(ctx, &val);
145 } else {
146 lsm6dsv16x_pin_int_route_t val;
147
148 ret = lsm6dsv16x_pin_int2_route_get(ctx, &val);
149 if (ret < 0) {
150 LOG_ERR("pint_int2_route_get error");
151 return ret;
152 }
153
154 val.wakeup = enable ? 1 : 0;
155
156 ret = lsm6dsv16x_pin_int2_route_set(ctx, &val);
157 }
158
159 return ret;
160 }
161
162 /**
163 * lsm6dsv16x_trigger_set - link external trigger to event data ready
164 */
lsm6dsv16x_trigger_set(const struct device * dev,const struct sensor_trigger * trig,sensor_trigger_handler_t handler)165 int lsm6dsv16x_trigger_set(const struct device *dev,
166 const struct sensor_trigger *trig,
167 sensor_trigger_handler_t handler)
168 {
169 const struct lsm6dsv16x_config *cfg = dev->config;
170 struct lsm6dsv16x_data *lsm6dsv16x = dev->data;
171 int ret = 0;
172
173 if (!cfg->trig_enabled) {
174 LOG_ERR("trigger_set op not supported");
175 return -ENOTSUP;
176 }
177
178 if (trig == NULL) {
179 LOG_ERR("no trigger");
180 return -EINVAL;
181 }
182
183 if (!lsm6dsv16x_is_active(dev)) {
184 return -EBUSY;
185 }
186
187 switch (trig->type) {
188 case SENSOR_TRIG_DATA_READY:
189 if (trig->chan == SENSOR_CHAN_ACCEL_XYZ) {
190 lsm6dsv16x->handler_drdy_acc = handler;
191 lsm6dsv16x->trig_drdy_acc = trig;
192 if (handler) {
193 lsm6dsv16x_enable_xl_int(dev, LSM6DSV16X_EN_BIT);
194 } else {
195 lsm6dsv16x_enable_xl_int(dev, LSM6DSV16X_DIS_BIT);
196 }
197 } else if (trig->chan == SENSOR_CHAN_GYRO_XYZ) {
198 lsm6dsv16x->handler_drdy_gyr = handler;
199 lsm6dsv16x->trig_drdy_gyr = trig;
200 if (handler) {
201 lsm6dsv16x_enable_g_int(dev, LSM6DSV16X_EN_BIT);
202 } else {
203 lsm6dsv16x_enable_g_int(dev, LSM6DSV16X_DIS_BIT);
204 }
205 }
206 break;
207 case SENSOR_TRIG_DELTA:
208 if (trig->chan != SENSOR_CHAN_ACCEL_XYZ) {
209 return -ENOTSUP;
210 }
211 lsm6dsv16x->handler_wakeup = handler;
212 lsm6dsv16x->trig_wakeup = trig;
213 if (handler) {
214 lsm6dsv16x_enable_wake_int(dev, LSM6DSV16X_EN_BIT);
215 } else {
216 lsm6dsv16x_enable_wake_int(dev, LSM6DSV16X_DIS_BIT);
217 }
218 break;
219 default:
220 ret = -ENOTSUP;
221 break;
222 }
223
224 return ret;
225 }
226
227 #if defined(CONFIG_LSM6DSV16X_TRIGGER_OWN_THREAD) || \
228 defined(CONFIG_LSM6DSV16X_TRIGGER_GLOBAL_THREAD)
229 /**
230 * lsm6dsv16x_handle_interrupt - handle the drdy event
231 * read data and call handler if registered any
232 */
lsm6dsv16x_handle_interrupt(const struct device * dev)233 static void lsm6dsv16x_handle_interrupt(const struct device *dev)
234 {
235 struct lsm6dsv16x_data *lsm6dsv16x = dev->data;
236 const struct lsm6dsv16x_config *cfg = dev->config;
237 stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
238 lsm6dsv16x_data_ready_t status;
239 lsm6dsv16x_all_int_src_t all_int_src;
240 int ret;
241
242 while (1) {
243 /* When using I3C IBI interrupt the status register is already automatically
244 * read (clearing the interrupt condition), so we can skip the extra bus
245 * transaction for FIFO stream case.
246 */
247 if (ON_I3C_BUS(cfg) && !I3C_INT_PIN(cfg) && IS_ENABLED(CONFIG_LSM6DSV16X_STREAM)) {
248 break;
249 }
250
251 if (lsm6dsv16x_flag_data_ready_get(ctx, &status) < 0) {
252 LOG_DBG("failed reading status reg");
253 return;
254 }
255
256 ret = lsm6dsv16x_read_reg(ctx, LSM6DSV16X_ALL_INT_SRC, (uint8_t *)&all_int_src, 1);
257 if (ret < 0) {
258 LOG_DBG("failed reading all_int_src reg");
259 return;
260 }
261
262 if (((status.drdy_xl == 0) && (status.drdy_gy == 0) && (all_int_src.wu_ia == 0)) ||
263 IS_ENABLED(CONFIG_LSM6DSV16X_STREAM)) {
264 break;
265 }
266
267 if ((status.drdy_xl) && (lsm6dsv16x->handler_drdy_acc != NULL)) {
268 lsm6dsv16x->handler_drdy_acc(dev, lsm6dsv16x->trig_drdy_acc);
269 }
270
271 if ((status.drdy_gy) && (lsm6dsv16x->handler_drdy_gyr != NULL)) {
272 lsm6dsv16x->handler_drdy_gyr(dev, lsm6dsv16x->trig_drdy_gyr);
273 }
274
275 if ((all_int_src.wu_ia) && lsm6dsv16x->handler_wakeup != NULL) {
276 lsm6dsv16x->handler_wakeup(dev, lsm6dsv16x->trig_wakeup);
277 }
278 }
279
280 if (!ON_I3C_BUS(cfg) || (I3C_INT_PIN(cfg))) {
281 ret = gpio_pin_interrupt_configure_dt(lsm6dsv16x->drdy_gpio,
282 GPIO_INT_EDGE_TO_ACTIVE);
283 if (ret < 0) {
284 LOG_ERR("%s: Not able to configure pin_int", dev->name);
285 }
286 }
287 }
288 #endif /* CONFIG_LSM6DSV16X_TRIGGER_OWN_THREAD || CONFIG_LSM6DSV16X_TRIGGER_GLOBAL_THREAD */
289
lsm6dsv16x_intr_callback(struct lsm6dsv16x_data * lsm6dsv16x)290 static void lsm6dsv16x_intr_callback(struct lsm6dsv16x_data *lsm6dsv16x)
291 {
292 #if defined(CONFIG_LSM6DSV16X_TRIGGER_OWN_THREAD)
293 k_sem_give(&lsm6dsv16x->intr_sem);
294 #elif defined(CONFIG_LSM6DSV16X_TRIGGER_GLOBAL_THREAD)
295 k_work_submit(&lsm6dsv16x->work);
296 #endif /* CONFIG_LSM6DSV16X_TRIGGER_OWN_THREAD */
297 if (IS_ENABLED(CONFIG_LSM6DSV16X_STREAM)) {
298 lsm6dsv16x_stream_irq_handler(lsm6dsv16x->dev);
299 }
300 }
301
lsm6dsv16x_gpio_callback(const struct device * dev,struct gpio_callback * cb,uint32_t pins)302 static void lsm6dsv16x_gpio_callback(const struct device *dev,
303 struct gpio_callback *cb, uint32_t pins)
304 {
305 struct lsm6dsv16x_data *lsm6dsv16x =
306 CONTAINER_OF(cb, struct lsm6dsv16x_data, gpio_cb);
307 int ret;
308
309 ARG_UNUSED(pins);
310
311 ret = gpio_pin_interrupt_configure_dt(lsm6dsv16x->drdy_gpio, GPIO_INT_DISABLE);
312 if (ret < 0) {
313 LOG_ERR("%s: Not able to configure pin_int", dev->name);
314 }
315
316 lsm6dsv16x_intr_callback(lsm6dsv16x);
317 }
318
319 #ifdef CONFIG_LSM6DSV16X_TRIGGER_OWN_THREAD
lsm6dsv16x_thread(void * p1,void * p2,void * p3)320 static void lsm6dsv16x_thread(void *p1, void *p2, void *p3)
321 {
322 ARG_UNUSED(p2);
323 ARG_UNUSED(p3);
324
325 struct lsm6dsv16x_data *lsm6dsv16x = p1;
326
327 while (1) {
328 k_sem_take(&lsm6dsv16x->intr_sem, K_FOREVER);
329 lsm6dsv16x_handle_interrupt(lsm6dsv16x->dev);
330 }
331 }
332 #endif /* CONFIG_LSM6DSV16X_TRIGGER_OWN_THREAD */
333
334 #ifdef CONFIG_LSM6DSV16X_TRIGGER_GLOBAL_THREAD
lsm6dsv16x_work_cb(struct k_work * work)335 static void lsm6dsv16x_work_cb(struct k_work *work)
336 {
337 struct lsm6dsv16x_data *lsm6dsv16x =
338 CONTAINER_OF(work, struct lsm6dsv16x_data, work);
339
340 lsm6dsv16x_handle_interrupt(lsm6dsv16x->dev);
341 }
342 #endif /* CONFIG_LSM6DSV16X_TRIGGER_GLOBAL_THREAD */
343
344 #if DT_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
lsm6dsv16x_ibi_cb(struct i3c_device_desc * target,struct i3c_ibi_payload * payload)345 static int lsm6dsv16x_ibi_cb(struct i3c_device_desc *target,
346 struct i3c_ibi_payload *payload)
347 {
348 const struct device *dev = target->dev;
349 struct lsm6dsv16x_data *lsm6dsv16x = dev->data;
350
351 /*
352 * The IBI Payload consist of the following 10 bytes :
353 * 1st byte: MDB
354 * - MDB[0]: FIFO interrupts (FIFO_WTM_IA, FIFO_OVR_IA, FIFO_FULL_IA, CONTER_BDR_IA)
355 * - MDB[1]: Physical interrupts (XLDS, GDA, TDA, XLDA_OIS, GDA_OIS)
356 * - MDB[2]: Basic interrupts (SLEEP_CHANGE_IA, D6D_IA, DOUBLE_TAP, SINGLE_TAP, WU_IA,
357 * FF_IA)
358 * - MDB[3]: SHUB DRDY (SENS_HUB_ENDOP)
359 * - MDB[4]: Advanced Function interrupt group
360 * - MDB[7:5]: 3'b000: Vendor Definied
361 * 3'b100: Timing Information
362 * 2nd byte: FIFO_STATUS1
363 * 3rd byte: FIFO_STATUS2
364 * 4th byte: ALL_INT_SRC
365 * 5th byte: STATUS_REG
366 * 6th byte: STATUS_REG_OIS
367 * 7th byte: STATUS_MASTER_MAIN
368 * 8th byte: EMB_FUNC_STATUS
369 * 9th byte: FSM_STATUS
370 * 10th byte: MLC_STATUS
371 */
372 if (payload->payload_len != sizeof(lsm6dsv16x->ibi_payload)) {
373 LOG_ERR("Invalid IBI payload length");
374 return -EINVAL;
375 }
376
377 memcpy(&lsm6dsv16x->ibi_payload, payload->payload, payload->payload_len);
378
379 lsm6dsv16x_intr_callback(lsm6dsv16x);
380
381 return 0;
382 }
383 #endif
384
lsm6dsv16x_init_interrupt(const struct device * dev)385 int lsm6dsv16x_init_interrupt(const struct device *dev)
386 {
387 struct lsm6dsv16x_data *lsm6dsv16x = dev->data;
388 const struct lsm6dsv16x_config *cfg = dev->config;
389 stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
390 int ret;
391
392 lsm6dsv16x->drdy_gpio = (cfg->drdy_pin == 1) ?
393 (struct gpio_dt_spec *)&cfg->int1_gpio :
394 (struct gpio_dt_spec *)&cfg->int2_gpio;
395
396 /* setup data ready gpio interrupt (INT1 or INT2) */
397 if ((!ON_I3C_BUS(cfg) || (I3C_INT_PIN(cfg))) && !gpio_is_ready_dt(lsm6dsv16x->drdy_gpio)) {
398 LOG_ERR("Cannot get pointer to drdy_gpio device");
399 return -EINVAL;
400 }
401
402 if (IS_ENABLED(CONFIG_LSM6DSV16X_STREAM)) {
403 lsm6dsv16x_stream_irq_handler(lsm6dsv16x->dev);
404 }
405
406 #if defined(CONFIG_LSM6DSV16X_TRIGGER_OWN_THREAD)
407 k_sem_init(&lsm6dsv16x->intr_sem, 0, K_SEM_MAX_LIMIT);
408
409 k_thread_create(&lsm6dsv16x->thread, lsm6dsv16x->thread_stack,
410 CONFIG_LSM6DSV16X_THREAD_STACK_SIZE,
411 lsm6dsv16x_thread, lsm6dsv16x,
412 NULL, NULL, K_PRIO_COOP(CONFIG_LSM6DSV16X_THREAD_PRIORITY),
413 0, K_NO_WAIT);
414 k_thread_name_set(&lsm6dsv16x->thread, "lsm6dsv16x");
415 #elif defined(CONFIG_LSM6DSV16X_TRIGGER_GLOBAL_THREAD)
416 lsm6dsv16x->work.handler = lsm6dsv16x_work_cb;
417 #endif /* CONFIG_LSM6DSV16X_TRIGGER_OWN_THREAD */
418
419 if (!ON_I3C_BUS(cfg) || (I3C_INT_PIN(cfg))) {
420 ret = gpio_pin_configure_dt(lsm6dsv16x->drdy_gpio, GPIO_INPUT);
421 if (ret < 0) {
422 LOG_DBG("Could not configure gpio");
423 return ret;
424 }
425
426 gpio_init_callback(&lsm6dsv16x->gpio_cb,
427 lsm6dsv16x_gpio_callback,
428 BIT(lsm6dsv16x->drdy_gpio->pin));
429
430 if (gpio_add_callback(lsm6dsv16x->drdy_gpio->port, &lsm6dsv16x->gpio_cb) < 0) {
431 LOG_DBG("Could not set gpio callback");
432 return -EIO;
433 }
434
435 }
436
437 /* set data ready mode on int1/int2/tir */
438 LOG_DBG("drdy_pulsed is %d", (int)cfg->drdy_pulsed);
439 lsm6dsv16x_data_ready_mode_t mode = cfg->drdy_pulsed ? LSM6DSV16X_DRDY_PULSED :
440 LSM6DSV16X_DRDY_LATCHED;
441
442
443 ret = lsm6dsv16x_data_ready_mode_set(ctx, mode);
444 if (ret < 0) {
445 LOG_ERR("drdy_pulsed config error %d", (int)cfg->drdy_pulsed);
446 return ret;
447 }
448
449 #if DT_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
450 if (ON_I3C_BUS(cfg)) {
451 if (I3C_INT_PIN(cfg)) {
452 /* Enable INT Pins when using I3C */
453 ret = lsm6dsv16x_i3c_int_en_set(ctx, I3C_INT_PIN(cfg));
454 if (ret < 0) {
455 LOG_ERR("failed to enable int pin for I3C %d", ret);
456 return ret;
457 }
458
459 ret = gpio_pin_interrupt_configure_dt(lsm6dsv16x->drdy_gpio,
460 GPIO_INT_EDGE_TO_ACTIVE);
461 if (ret < 0) {
462 LOG_ERR("Could not configure gpio interrupt");
463 return ret;
464 }
465 } else {
466 /* I3C IBI does not utilize GPIO interrupt. */
467 lsm6dsv16x->i3c_dev->ibi_cb = lsm6dsv16x_ibi_cb;
468
469 /*
470 * Set IBI availability time, this is the time that the sensor
471 * will wait for inactivity before it is okay to generate an IBI TIR.
472 *
473 * NOTE: There is a bug in the API and the Documentation where
474 * the defines for the values are incorrect. The correct values are:
475 * 0 = 50us
476 * 1 = 2us
477 * 2 = 1ms
478 * 3 = 25ms
479 */
480 ret = lsm6dsv16x_i3c_ibi_time_set(ctx, cfg->bus_act_sel);
481 if (ret < 0) {
482 LOG_ERR("failed to set ibi available time %d", ret);
483 return -EIO;
484 }
485
486 if (i3c_ibi_enable(lsm6dsv16x->i3c_dev) != 0) {
487 LOG_ERR("Could not enable I3C IBI");
488 return -EIO;
489 }
490 }
491
492 return 0;
493 }
494 #endif
495
496 return gpio_pin_interrupt_configure_dt(lsm6dsv16x->drdy_gpio,
497 GPIO_INT_EDGE_TO_ACTIVE);
498 }
499