drivers/sensor/st: add support to LSM6DSVXXX IMU family

This driver is currently only supporting the polling-mode read_and_decode
APIs (both blocking and non-blocking).

The driver implements a chip_api structure which has to be used to
provide device specific callbacks. The only lsm6dsvxxx family device
currently supported is lsm6dsv320x.

More information about LSM6DSV16X:
https://www.st.com/resource/en/datasheet/lsm6dsv320x.pdf

Signed-off-by: Armando Visconti <armando.visconti@st.com>
This commit is contained in:
Armando Visconti
2025-05-28 17:33:27 +02:00
committed by Maureen Helm
parent 5372b41979
commit d6eaae4d53
17 changed files with 1833 additions and 0 deletions

View File

@@ -31,6 +31,7 @@ add_subdirectory_ifdef(CONFIG_LSM6DSL lsm6dsl)
add_subdirectory_ifdef(CONFIG_LSM6DSO lsm6dso)
add_subdirectory_ifdef(CONFIG_LSM6DSO16IS lsm6dso16is)
add_subdirectory_ifdef(CONFIG_LSM6DSV16X lsm6dsv16x)
add_subdirectory_ifdef(CONFIG_LSM6DSVXXX lsm6dsvxxx)
add_subdirectory_ifdef(CONFIG_LSM9DS0_GYRO lsm9ds0_gyro)
add_subdirectory_ifdef(CONFIG_LSM9DS0_MFD lsm9ds0_mfd)
add_subdirectory_ifdef(CONFIG_LSM9DS1 lsm9ds1)

View File

@@ -30,6 +30,7 @@ source "drivers/sensor/st/lsm6dsl/Kconfig"
source "drivers/sensor/st/lsm6dso/Kconfig"
source "drivers/sensor/st/lsm6dso16is/Kconfig"
source "drivers/sensor/st/lsm6dsv16x/Kconfig"
source "drivers/sensor/st/lsm6dsvxxx/Kconfig"
source "drivers/sensor/st/lsm9ds0_gyro/Kconfig"
source "drivers/sensor/st/lsm9ds0_mfd/Kconfig"
source "drivers/sensor/st/lsm9ds1/Kconfig"

View File

@@ -0,0 +1,13 @@
# ST Microelectronics LSM6DSVXXX accelerometer sensor
#
# Copyright (c) 2025 STMicroelectronics
#
# SPDX-License-Identifier: Apache-2.0
#
zephyr_library()
zephyr_library_sources(lsm6dsvxxx.c)
zephyr_library_sources_ifdef(CONFIG_DT_HAS_ST_LSM6DSV320X_ENABLED lsm6dsv320x.c)
zephyr_library_sources_ifdef(CONFIG_SENSOR_ASYNC_API lsm6dsvxxx_decoder.c)
zephyr_library_include_directories(../stmemsc)

View File

@@ -0,0 +1,27 @@
# ST Microelectronics LSM6DSVXXX accelerometer sensor
# Copyright (c) 2025 STMicroelectronics
# SPDX-License-Identifier: Apache-2.0
menuconfig LSM6DSVXXX
bool "LSM6DSVXXX IMU sensor"
default y
depends on DT_HAS_ST_LSM6DSV320X_ENABLED
depends on ZEPHYR_HAL_ST_MODULE
select I2C if $(dt_compat_on_bus,$(DT_COMPAT_ST_LSM6DSV320X),i2c)
select I3C if $(dt_compat_on_bus,$(DT_COMPAT_ST_LSM6DSV320X),i3c)
select SPI if $(dt_compat_on_bus,$(DT_COMPAT_ST_LSM6DSV320X),spi)
select HAS_STMEMSC
select USE_STDC_LSM6DSV320X if DT_HAS_ST_LSM6DSV320X_ENABLED
help
Enable driver for LSM6DSVXXX family IMU sensors.
if LSM6DSVXXX
config LSM6DSVXXX_ENABLE_TEMP
bool "Temperature"
help
Enable/disable temperature sensor
endif # LSM6DSVXXX

View File

@@ -0,0 +1,648 @@
/* ST Microelectronics LSM6DSVXXX family IMU sensor
*
* Copyright (c) 2025 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*
* Datasheet:
* https://www.st.com/resource/en/datasheet/lsm6dsv320x.pdf
*/
#include "lsm6dsv320x.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(LSM6DSVXXX, CONFIG_SENSOR_LOG_LEVEL);
/*
* XL configuration
*/
static uint16_t lsm6dsv320x_accel_gain_ug(uint8_t fs)
{
if (fs == 8) {
/* range is 320g */
return 10417;
}
return (61 * (1 << fs));
}
/* The first nibble of fs tells if it is High-G or not */
static int lsm6dsv320x_accel_range_to_fs_val(const struct device *dev,
int32_t range,
uint8_t *fs)
{
switch (range) {
case LSM6DSV320X_DT_FS_2G:
*fs = 0;
break;
case LSM6DSV320X_DT_FS_4G:
*fs = 1;
break;
case LSM6DSV320X_DT_FS_8G:
*fs = 2;
break;
case LSM6DSV320X_DT_FS_16G:
*fs = 3;
break;
case LSM6DSV320X_DT_FS_32G:
*fs = 4;
break;
case LSM6DSV320X_DT_FS_64G:
*fs = 5;
break;
case LSM6DSV320X_DT_FS_128G:
*fs = 6;
break;
case LSM6DSV320X_DT_FS_256G:
*fs = 7;
break;
case LSM6DSV320X_DT_FS_320G:
*fs = 8;
break;
default:
return -EINVAL;
}
return 0;
}
static int lsm6dsv320x_accel_set_fs_raw(const struct device *dev, uint8_t fs)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
struct lsm6dsvxxx_data *data = dev->data;
if (fs < 4) { /* 2g/4g/8g/16g */
lsm6dsv320x_xl_full_scale_t val = fs;
if (lsm6dsv320x_xl_full_scale_set(ctx, val) < 0) {
return -EIO;
}
data->out_xl = LSM6DSV320X_OUTX_L_A;
} else if (fs <= 8) { /* 32g/64g/128g/256g/320g */
lsm6dsv320x_hg_xl_full_scale_t val = (fs - 4);
if (lsm6dsv320x_hg_xl_full_scale_set(ctx, val) < 0) {
return -EIO;
}
data->out_xl = LSM6DSV320X_UI_OUTX_L_A_OIS_HG;
} else {
return -EINVAL;
}
data->accel_fs = fs;
data->acc_gain = lsm6dsv320x_accel_gain_ug(fs);
return 0;
}
static int lsm6dsv320x_accel_set_fs(const struct device *dev, int32_t range)
{
uint8_t fs;
int ret;
ret = lsm6dsv320x_accel_range_to_fs_val(dev, range, &fs);
if (ret < 0) {
return ret;
}
ret = lsm6dsv320x_accel_set_fs_raw(dev, fs);
if (ret < 0) {
return ret;
}
return 0;
}
static int lsm6dsv320x_accel_set_odr_raw(const struct device *dev, uint8_t odr)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
struct lsm6dsvxxx_data *data = dev->data;
if (cfg->accel_hg_odr != LSM6DSV320X_HG_XL_ODR_OFF) {
if (lsm6dsv320x_hg_xl_data_rate_set(ctx, cfg->accel_hg_odr, 1) < 0) {
return -EIO;
}
} else {
if (lsm6dsv320x_xl_data_rate_set(ctx, odr) < 0) {
return -EIO;
}
}
data->accel_freq = odr;
return 0;
}
/*
* values taken from lsm6dsv320x_data_rate_t in hal/st module. The mode/accuracy
* should be selected through accel-odr property in DT
*/
static const float lsm6dsv320x_odr_map[3][13] = {
/* High Accuracy off */
{0.0f, 1.875f, 7.5f, 15.0f, 30.0f, 60.0f,
120.0f, 240.0f, 480.0f, 960.0f, 1920.0f,
3840.0f, 7680.0f},
/* High Accuracy 1 */
{0.0f, 1.875f, 7.5f, 15.625f, 31.25f, 62.5f,
125.0f, 250.0f, 500.0f, 1000.0f, 2000.0f,
4000.0f, 8000.0f},
/* High Accuracy 2 */
{0.0f, 1.875f, 7.5f, 12.5f, 25.0f, 50.0f,
100.0f, 200.0f, 400.0f, 800.0f, 1600.0f,
3200.0f, 6400.0f},
};
static uint8_t lsm6dsv320x_freq_to_odr_val(const struct device *dev, int32_t freq)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
lsm6dsv320x_data_rate_t odr;
int8_t mode;
size_t i;
if (lsm6dsv320x_xl_data_rate_get(ctx, &odr) < 0) {
return -EINVAL;
}
mode = (odr >> 4) & 0xf;
for (i = 0; i < ARRAY_SIZE(lsm6dsv320x_odr_map[mode]); i++) {
if (freq <= lsm6dsv320x_odr_map[mode][i]) {
LOG_DBG("mode: %d - odr: %d", mode, i);
return i | (mode << 4);
}
}
return 0xFF;
}
static int lsm6dsv320x_accel_set_odr(const struct device *dev, int32_t freq)
{
uint8_t odr;
odr = lsm6dsv320x_freq_to_odr_val(dev, freq);
if (odr == 0xFF) {
return -EINVAL;
}
if (lsm6dsv320x_accel_set_odr_raw(dev, odr) < 0) {
LOG_DBG("failed to set accelerometer sampling rate");
return -EIO;
}
return 0;
}
static int32_t lsm6dsv320x_accel_set_mode(const struct device *dev, int32_t mode)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
switch (mode) {
case 0: /* High Performance */
mode = LSM6DSV320X_XL_HIGH_PERFORMANCE_MD;
break;
case 1: /* High Accuracy */
mode = LSM6DSV320X_XL_HIGH_ACCURACY_ODR_MD;
break;
case 3: /* ODR triggered */
mode = LSM6DSV320X_XL_ODR_TRIGGERED_MD;
break;
case 4: /* Low Power 2 */
mode = LSM6DSV320X_XL_LOW_POWER_2_AVG_MD;
break;
case 5: /* Low Power 4 */
mode = LSM6DSV320X_XL_LOW_POWER_4_AVG_MD;
break;
case 6: /* Low Power 8 */
mode = LSM6DSV320X_XL_LOW_POWER_8_AVG_MD;
break;
case 7: /* Normal */
mode = LSM6DSV320X_XL_NORMAL_MD;
break;
default:
return -EIO;
}
return lsm6dsv320x_xl_mode_set(ctx, mode);
}
static int32_t lsm6dsv320x_accel_get_fs(const struct device *dev, int32_t *range)
{
return -ENOTSUP;
}
static int32_t lsm6dsv320x_accel_get_odr(const struct device *dev, int32_t *freq)
{
return -ENOTSUP;
}
static int32_t lsm6dsv320x_accel_get_mode(const struct device *dev, int32_t *mode)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
lsm6dsv320x_xl_mode_t md;
lsm6dsv320x_xl_mode_get(ctx, &md);
switch (md) {
case LSM6DSV320X_XL_HIGH_PERFORMANCE_MD:
*mode = 0;
break;
case LSM6DSV320X_XL_HIGH_ACCURACY_ODR_MD:
*mode = 1;
break;
case LSM6DSV320X_XL_ODR_TRIGGERED_MD:
*mode = 3;
break;
case LSM6DSV320X_XL_LOW_POWER_2_AVG_MD:
*mode = 4;
break;
case LSM6DSV320X_XL_LOW_POWER_4_AVG_MD:
*mode = 5;
break;
case LSM6DSV320X_XL_LOW_POWER_8_AVG_MD:
*mode = 6;
break;
case LSM6DSV320X_XL_NORMAL_MD:
*mode = 7;
break;
default:
return -EIO;
}
return 0;
}
/*
* GY configuration
*/
static int lsm6dsv320x_gyro_range_to_fs_val(const struct device *dev,
int32_t range,
uint8_t *fs)
{
switch (range) {
case 0:
*fs = 0;
break;
case 250:
*fs = LSM6DSV320X_DT_FS_250DPS;
break;
case 500:
*fs = LSM6DSV320X_DT_FS_500DPS;
break;
case 1000:
*fs = LSM6DSV320X_DT_FS_1000DPS;
break;
case 2000:
*fs = LSM6DSV320X_DT_FS_2000DPS;
break;
case 4000:
*fs = LSM6DSV320X_DT_FS_4000DPS;
break;
default:
return -EINVAL;
}
return 0;
}
static uint32_t lsm6dsv320x_gyro_gain_udps(uint8_t fs)
{
return (4375 * (1 << fs));
}
static int lsm6dsv320x_gyro_set_fs_raw(const struct device *dev, uint8_t fs)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
struct lsm6dsvxxx_data *data = dev->data;
if (fs == 0) {
/* skip power-up value */
return 0;
}
if (lsm6dsv320x_gy_full_scale_set(ctx, fs) < 0) {
return -EIO;
}
data->gyro_fs = fs;
data->gyro_gain = lsm6dsv320x_gyro_gain_udps(fs);
return 0;
}
static int lsm6dsv320x_gyro_set_fs(const struct device *dev, int32_t range)
{
uint8_t fs;
int ret;
ret = lsm6dsv320x_gyro_range_to_fs_val(dev, range, &fs);
if (ret < 0) {
return ret;
}
if (lsm6dsv320x_gyro_set_fs_raw(dev, fs) < 0) {
return -EIO;
}
return 0;
}
static int lsm6dsv320x_gyro_set_odr_raw(const struct device *dev, uint8_t odr)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
struct lsm6dsvxxx_data *data = dev->data;
if (lsm6dsv320x_gy_data_rate_set(ctx, odr) < 0) {
return -EIO;
}
data->gyro_freq = odr;
return 0;
}
static int lsm6dsv320x_gyro_set_odr(const struct device *dev, int32_t freq)
{
uint8_t odr;
odr = lsm6dsv320x_freq_to_odr_val(dev, freq);
if (odr == 0xFF) {
return -EINVAL;
}
if (lsm6dsv320x_gyro_set_odr_raw(dev, odr) < 0) {
LOG_DBG("failed to set gyroscope sampling rate");
return -EIO;
}
return 0;
}
static int32_t lsm6dsv320x_gyro_set_mode(const struct device *dev, int32_t mode)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
switch (mode) {
case 0: /* High Performance */
mode = LSM6DSV320X_GY_HIGH_PERFORMANCE_MD;
break;
case 1: /* High Accuracy */
mode = LSM6DSV320X_GY_HIGH_ACCURACY_ODR_MD;
break;
case 4: /* Sleep */
mode = LSM6DSV320X_GY_SLEEP_MD;
break;
case 5: /* Low Power */
mode = LSM6DSV320X_GY_LOW_POWER_MD;
break;
default:
return -EIO;
}
return lsm6dsv320x_gy_mode_set(ctx, mode);
}
static int32_t lsm6dsv320x_gyro_get_fs(const struct device *dev, int32_t *range)
{
return -ENOTSUP;
}
static int32_t lsm6dsv320x_gyro_get_odr(const struct device *dev, int32_t *freq)
{
return -ENOTSUP;
}
static int32_t lsm6dsv320x_gyro_get_mode(const struct device *dev, int32_t *mode)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
lsm6dsv320x_gy_mode_t md;
lsm6dsv320x_gy_mode_get(ctx, &md);
switch (md) {
case LSM6DSV320X_GY_HIGH_PERFORMANCE_MD:
*mode = 0;
break;
case LSM6DSV320X_GY_HIGH_ACCURACY_ODR_MD:
*mode = 1;
break;
case LSM6DSV320X_GY_SLEEP_MD:
*mode = 4;
break;
case LSM6DSV320X_GY_LOW_POWER_MD:
*mode = 5;
break;
default:
return -EIO;
}
return 0;
}
static int lsm6dsv320x_init_chip(const struct device *dev)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
struct lsm6dsvxxx_data *data = dev->data;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
uint8_t chip_id;
uint8_t odr, fs;
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
if (cfg->i3c.bus != NULL) {
/*
* Need to grab the pointer to the I3C device descriptor
* before we can talk to the sensor.
*/
lsm6dsvxxx->i3c_dev = i3c_device_find(cfg->i3c.bus, &cfg->i3c.dev_id);
if (lsm6dsvxxx->i3c_dev == NULL) {
LOG_ERR("Cannot find I3C device descriptor");
return -ENODEV;
}
}
#endif
/* All registers except 0x01 are different between banks, including the WHO_AM_I
* register and the register used for a SW reset. If the lsm6dsvxxx wasn't on the user
* bank when it reset, then both the chip id check and the sw reset will fail unless we
* set the bank now.
*/
if (lsm6dsv320x_mem_bank_set(ctx, LSM6DSV320X_MAIN_MEM_BANK) < 0) {
LOG_DBG("Failed to set user bank");
return -EIO;
}
if (lsm6dsv320x_device_id_get(ctx, &chip_id) < 0) {
LOG_DBG("Failed reading chip id");
return -EIO;
}
LOG_INF("chip id 0x%x", chip_id);
if (chip_id != LSM6DSV320X_ID) {
LOG_DBG("Invalid chip id 0x%x", chip_id);
return -EIO;
}
/* Resetting the whole device while using I3C will also reset the DA, therefore perform
* only a software reset if the bus is I3C. It should be assumed that the device was
* already fully reset by the I3C CCC RSTACT (whole chip) done as apart of the I3C Bus
* initialization.
*/
if (ON_I3C_BUS(cfg)) {
/* Restore default configuration */
lsm6dsv320x_reboot(ctx);
/* wait 150us as reported in AN5763 */
k_sleep(K_USEC(150));
} else {
/* reset device (sw_por) */
if (lsm6dsv320x_sw_por(ctx) < 0) {
return -EIO;
}
/* wait 30ms as reported in AN5763 */
k_sleep(K_MSEC(30));
}
data->out_xl = LSM6DSV320X_OUTX_L_A;
data->out_tp = LSM6DSV320X_OUT_TEMP_L;
fs = cfg->accel_range;
LOG_DBG("accel range is %d", fs);
if (lsm6dsv320x_accel_set_fs_raw(dev, fs) < 0) {
LOG_ERR("failed to set accelerometer range %d", fs);
return -EIO;
}
odr = cfg->accel_odr;
LOG_DBG("accel odr is %d", odr);
if (lsm6dsv320x_accel_set_odr_raw(dev, odr) < 0) {
LOG_ERR("failed to set accelerometer odr %d", odr);
return -EIO;
}
fs = cfg->gyro_range;
LOG_DBG("gyro range is %d", fs);
if (lsm6dsv320x_gyro_set_fs_raw(dev, fs) < 0) {
LOG_ERR("failed to set gyroscope range %d", fs);
return -EIO;
}
odr = cfg->gyro_odr;
LOG_DBG("gyro odr is %d", odr);
if (lsm6dsv320x_gyro_set_odr_raw(dev, odr) < 0) {
LOG_ERR("failed to set gyroscope odr %d", odr);
return -EIO;
}
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
if (IS_ENABLED(CONFIG_LSM6DSVXXX_STREAM) && (ON_I3C_BUS(cfg))) {
/*
* Set MRL to the Max Size of the FIFO so the entire FIFO can be read
* out at once
*/
struct i3c_ccc_mrl setmrl = {
.len = 0x0700,
.ibi_len = lsm6dsvxxx->i3c_dev->data_length.max_ibi,
};
if (i3c_ccc_do_setmrl(lsm6dsvxxx->i3c_dev, &setmrl) < 0) {
LOG_ERR("failed to set mrl");
return -EIO;
}
}
#endif
if (lsm6dsv320x_block_data_update_set(ctx, 1) < 0) {
LOG_DBG("failed to set BDU mode");
return -EIO;
}
return 0;
}
#if defined(CONFIG_PM_DEVICE)
static int lsm6dsv320x_pm_action(const struct device *dev, enum pm_device_action action)
{
struct lsm6dsvxxx_data *data = dev->data;
const struct lsm6dsvxxx_config *cfg = dev->config;
stmdev_ctx_t *ctx = (stmdev_ctx_t *)&cfg->ctx;
int ret = 0;
LOG_DBG("PM action: %d", (int)action);
switch (action) {
case PM_DEVICE_ACTION_RESUME:
if (lsm6dsv320x_xl_data_rate_set(ctx, data->accel_freq) < 0) {
LOG_ERR("failed to set accelerometer odr %d", (int)data->accel_freq);
ret = -EIO;
}
if (lsm6dsv320x_gy_data_rate_set(ctx, data->gyro_freq) < 0) {
LOG_ERR("failed to set gyroscope odr %d", (int)data->gyro_freq);
ret = -EIO;
}
break;
case PM_DEVICE_ACTION_SUSPEND:
if (lsm6dsv320x_xl_data_rate_set(ctx, LSM6DSVXXX_DT_ODR_OFF) < 0) {
LOG_ERR("failed to disable accelerometer");
ret = -EIO;
}
if (lsm6dsv320x_gy_data_rate_set(ctx, LSM6DSVXXX_DT_ODR_OFF) < 0) {
LOG_ERR("failed to disable gyroscope");
ret = -EIO;
}
break;
default:
ret = -ENOTSUP;
break;
}
return ret;
}
#endif /* CONFIG_PM_DEVICE */
const struct lsm6dsvxxx_chip_api st_lsm6dsv320x_chip_api = {
.init_chip = lsm6dsv320x_init_chip,
#if defined(CONFIG_PM_DEVICE)
.pm_action = lsm6dsv320x_pm_action,
#endif /* CONFIG_PM_DEVICE */
.accel_fs_set = lsm6dsv320x_accel_set_fs,
.accel_odr_set = lsm6dsv320x_accel_set_odr,
.accel_mode_set = lsm6dsv320x_accel_set_mode,
.accel_fs_get = lsm6dsv320x_accel_get_fs,
.accel_odr_get = lsm6dsv320x_accel_get_odr,
.accel_mode_get = lsm6dsv320x_accel_get_mode,
.gyro_fs_set = lsm6dsv320x_gyro_set_fs,
.gyro_odr_set = lsm6dsv320x_gyro_set_odr,
.gyro_mode_set = lsm6dsv320x_gyro_set_mode,
.gyro_fs_get = lsm6dsv320x_gyro_get_fs,
.gyro_odr_get = lsm6dsv320x_gyro_get_odr,
.gyro_mode_get = lsm6dsv320x_gyro_get_mode,
};

View File

@@ -0,0 +1,23 @@
/* ST Microelectronics LSM6DSVXXX family IMU sensor
*
* Copyright (c) 2025 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*
* Datasheet:
* https://www.st.com/resource/en/datasheet/lsm6dsv320x.pdf
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_LSM6DSV320X_H_
#define ZEPHYR_DRIVERS_SENSOR_LSM6DSV320X_H_
#include <stdint.h>
#include <stmemsc.h>
#include "lsm6dsvxxx.h"
#include "lsm6dsv320x_reg.h"
#include <zephyr/drivers/sensor.h>
extern const struct lsm6dsvxxx_chip_api st_lsm6dsv320x_chip_api;
#endif /* ZEPHYR_DRIVERS_SENSOR_LSM6DSV320X_H_ */

View File

@@ -0,0 +1,502 @@
/* ST Microelectronics LSM6DSVXXX family IMU sensor
*
* Copyright (c) 2025 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*
* Datasheet:
* https://www.st.com/resource/en/datasheet/lsm6dsv320x.pdf
*/
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <string.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/logging/log.h>
#include "lsm6dsvxxx.h"
#include "lsm6dsvxxx_rtio.h"
#if DT_HAS_COMPAT_STATUS_OKAY(st_lsm6dsv320x)
#include "lsm6dsv320x.h"
#endif
LOG_MODULE_REGISTER(LSM6DSVXXX, CONFIG_SENSOR_LOG_LEVEL);
static int lsm6dsvxxx_accel_config(const struct device *dev,
enum sensor_channel chan,
enum sensor_attribute attr,
const struct sensor_value *val)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
switch (attr) {
case SENSOR_ATTR_FULL_SCALE:
return cfg->chip_api->accel_fs_set(dev, sensor_ms2_to_g(val));
case SENSOR_ATTR_SAMPLING_FREQUENCY:
return cfg->chip_api->accel_odr_set(dev, val->val1);
case SENSOR_ATTR_CONFIGURATION:
return cfg->chip_api->accel_mode_set(dev, val->val1);
default:
LOG_DBG("Accel attribute not supported.");
return -ENOTSUP;
}
return 0;
}
static int lsm6dsvxxx_gyro_config(const struct device *dev,
enum sensor_channel chan,
enum sensor_attribute attr,
const struct sensor_value *val)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
switch (attr) {
case SENSOR_ATTR_FULL_SCALE:
return cfg->chip_api->gyro_fs_set(dev, sensor_rad_to_degrees(val));
case SENSOR_ATTR_SAMPLING_FREQUENCY:
return cfg->chip_api->gyro_odr_set(dev, val->val1);
case SENSOR_ATTR_CONFIGURATION:
return cfg->chip_api->gyro_mode_set(dev, val->val1);
default:
LOG_DBG("Gyro attribute not supported.");
return -ENOTSUP;
}
return 0;
}
static int lsm6dsvxxx_attr_set(const struct device *dev,
enum sensor_channel chan,
enum sensor_attribute attr,
const struct sensor_value *val)
{
switch (chan) {
case SENSOR_CHAN_ACCEL_XYZ:
return lsm6dsvxxx_accel_config(dev, chan, attr, val);
case SENSOR_CHAN_GYRO_XYZ:
return lsm6dsvxxx_gyro_config(dev, chan, attr, val);
#ifdef CONFIG_LSM6DSVXXX_STREAM
case SENSOR_CHAN_GBIAS_XYZ:
return lsm6dsvxxx_gbias_config(dev, chan, attr, val);
#endif /* CONFIG_LSM6DSVXXX_STREAM */
default:
LOG_WRN("attr_set() not supported on this channel.");
return -ENOTSUP;
}
return 0;
}
static int lsm6dsvxxx_accel_get_config(const struct device *dev,
enum sensor_channel chan,
enum sensor_attribute attr,
struct sensor_value *val)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
switch (attr) {
case SENSOR_ATTR_CONFIGURATION:
return cfg->chip_api->accel_mode_get(dev, &val->val1);
default:
LOG_DBG("Accel attribute not supported.");
return -ENOTSUP;
}
return 0;
}
static int lsm6dsvxxx_gyro_get_config(const struct device *dev,
enum sensor_channel chan,
enum sensor_attribute attr,
struct sensor_value *val)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
switch (attr) {
case SENSOR_ATTR_CONFIGURATION:
return cfg->chip_api->gyro_mode_get(dev, &val->val1);
default:
LOG_DBG("Gyro attribute not supported.");
return -ENOTSUP;
}
return 0;
}
static int lsm6dsvxxx_attr_get(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, struct sensor_value *val)
{
switch (chan) {
case SENSOR_CHAN_ACCEL_XYZ:
return lsm6dsvxxx_accel_get_config(dev, chan, attr, val);
case SENSOR_CHAN_GYRO_XYZ:
return lsm6dsvxxx_gyro_get_config(dev, chan, attr, val);
#ifdef CONFIG_LSM6DSVXXX_STREAM
case SENSOR_CHAN_GBIAS_XYZ:
return lsm6dsvxxx_gbias_get_config(dev, chan, attr, val);
#endif /* CONFIG_LSM6DSVXXX_STREAM */
default:
LOG_WRN("attr_get() not supported on this channel.");
return -ENOTSUP;
}
return 0;
}
static void lsm6dsvxxx_one_shot_complete_cb(struct rtio *ctx, const struct rtio_sqe *sqe,
int result, void *arg)
{
ARG_UNUSED(result);
struct rtio_iodev_sqe *iodev_sqe = (struct rtio_iodev_sqe *)sqe->userdata;
int err = 0;
err = rtio_flush_completion_queue(ctx);
if (err) {
rtio_iodev_sqe_err(iodev_sqe, err);
} else {
rtio_iodev_sqe_ok(iodev_sqe, 0);
}
}
static void lsm6dsvxxx_submit_one_shot(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
{
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
const struct sensor_chan_spec *const channels = cfg->channels;
const size_t num_channels = cfg->count;
uint32_t min_buf_len = sizeof(struct lsm6dsvxxx_rtio_data);
uint64_t cycles;
int rc = 0;
uint8_t *buf;
uint32_t buf_len;
struct lsm6dsvxxx_rtio_data *edata;
struct lsm6dsvxxx_data *data = dev->data;
/* Get the buffer for the frame, it may be allocated dynamically by the rtio context */
rc = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len);
if (rc != 0) {
LOG_ERR("Failed to get a read buffer of size %u bytes", min_buf_len);
return;
}
edata = (struct lsm6dsvxxx_rtio_data *)buf;
edata->has_accel = 0;
edata->has_temp = 0;
rc = sensor_clock_get_cycles(&cycles);
if (rc != 0) {
LOG_ERR("Failed to get sensor clock cycles");
rtio_iodev_sqe_err(iodev_sqe, rc);
return;
}
edata->header.is_fifo = false;
edata->header.range = data->accel_fs;
edata->header.timestamp = sensor_clock_cycles_to_ns(cycles);
for (int i = 0; i < num_channels; i++) {
switch (channels[i].chan_type) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
edata->has_accel = 1;
uint8_t xl_addr = lsm6dsvxxx_bus_reg(data->bus_type, data->out_xl);
struct rtio_regs outx_regs;
struct rtio_regs_list xl_regs_list[] = {
{
xl_addr,
(uint8_t *)edata->accel,
6,
},
};
outx_regs.rtio_regs_list = xl_regs_list;
outx_regs.rtio_regs_num = ARRAY_SIZE(xl_regs_list);
/*
* Prepare rtio enabled bus to read LSM6DSVXXX_OUTX_L_A register
* where accelerometer data is available.
* Then lsm6dsvxxx_one_shot_complete_cb callback will be invoked.
*
* STMEMSC API equivalent code:
*
* uint8_t accel_raw[6];
*
* lsm6dsvxxx_acceleration_raw_get(&dev_ctx, accel_raw);
*/
rtio_read_regs_async(data->rtio_ctx, data->iodev, data->bus_type,
&outx_regs, iodev_sqe, dev,
lsm6dsvxxx_one_shot_complete_cb);
break;
#if defined(CONFIG_LSM6DSVXXX_ENABLE_TEMP)
case SENSOR_CHAN_DIE_TEMP:
edata->has_temp = 1;
uint8_t t_addr = lsm6dsvxxx_bus_reg(data->bus_type, data->out_tp);
struct rtio_regs outt_regs;
struct rtio_regs_list t_regs_list[] = {
{
t_addr,
(uint8_t *)&edata->temp,
2,
},
};
outt_regs.rtio_regs_list = t_regs_list;
outt_regs.rtio_regs_num = ARRAY_SIZE(t_regs_list);
/*
* Prepare rtio enabled bus to read LSM6DSVXX0X_OUT_TEMP_L register
* where temperature data is available.
* Then lsm6dsvxxx_one_shot_complete_cb callback will be invoked.
*
* STMEMSC API equivalent code:
*
* int16_t val;
*
* lsm6dsvxxx_temperature_raw_get(&dev_ctx, &val);
*/
rtio_read_regs_async(data->rtio_ctx, data->iodev, data->bus_type,
&outt_regs, iodev_sqe, dev,
lsm6dsvxxx_one_shot_complete_cb);
break;
#endif
default:
continue;
}
}
if (edata->has_accel == 0) {
rtio_iodev_sqe_err(iodev_sqe, -EIO);
}
}
void lsm6dsvxxx_submit(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
{
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
if (!cfg->is_streaming) {
lsm6dsvxxx_submit_one_shot(dev, iodev_sqe);
} else {
rtio_iodev_sqe_err(iodev_sqe, -ENOTSUP);
}
}
static DEVICE_API(sensor, lsm6dsvxxx_driver_api) = {
.attr_set = lsm6dsvxxx_attr_set,
.attr_get = lsm6dsvxxx_attr_get,
#ifdef CONFIG_SENSOR_ASYNC_API
.get_decoder = lsm6dsvxxx_get_decoder,
.submit = lsm6dsvxxx_submit,
#endif
};
static int lsm6dsvxxx_init(const struct device *dev)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
struct lsm6dsvxxx_data *data = dev->data;
LOG_INF("Initialize device %s", dev->name);
data->dev = dev;
if (cfg->chip_api->init_chip(dev) < 0) {
LOG_DBG("failed to initialize chip");
return -EIO;
}
#ifdef CONFIG_LSM6DSVXXX_TRIGGER
if (cfg->trig_enabled && (lsm6dsvxxx_init_interrupt(dev) < 0)) {
LOG_ERR("Failed to initialize interrupt.");
return -EIO;
}
#endif
return 0;
}
#if defined(CONFIG_PM_DEVICE)
static int lsm6dsvxxx_pm_action(const struct device *dev, enum pm_device_action action)
{
const struct lsm6dsvxxx_config *cfg = dev->config;
return cfg->chip_api->pm_action(dev, action);
}
#endif /* CONFIG_PM_DEVICE */
/*
* Device creation macro, shared by LSM6DSVXXX_DEFINE_SPI() and
* LSM6DSVXXX_DEFINE_I2C().
*/
/* clang-format off */
#define LSM6DSVXXX_DEVICE_INIT(inst, prefix) \
PM_DEVICE_DT_INST_DEFINE(inst, lsm6dsvxxx_pm_action); \
SENSOR_DEVICE_DT_INST_DEFINE(inst, lsm6dsvxxx_init, PM_DEVICE_DT_INST_GET(inst), \
&prefix##_data_##inst, &prefix##_config_##inst, \
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \
&lsm6dsvxxx_driver_api);
#ifdef CONFIG_LSM6DSVXXX_TRIGGER
#define LSM6DSVXXX_CFG_IRQ(inst) \
.trig_enabled = true, \
.int1_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, int1_gpios, { 0 }), \
.int2_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, int2_gpios, { 0 }), \
.drdy_pulsed = DT_INST_PROP(inst, drdy_pulsed), \
.drdy_pin = DT_INST_PROP(inst, drdy_pin)
#else
#define LSM6DSVXXX_CFG_IRQ(inst)
#endif /* CONFIG_LSM6DSVXXX_TRIGGER */
#define LSM6DSVXXX_CONFIG_COMMON(inst, prefix) \
.chip_api = &prefix##_chip_api, \
.accel_odr = DT_INST_PROP(inst, accel_odr), \
IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, accel_hg_odr), \
(.accel_hg_odr = DT_INST_PROP(inst, accel_hg_odr),)) \
.accel_range = DT_INST_ENUM_IDX(inst, accel_range), \
.gyro_odr = DT_INST_PROP(inst, gyro_odr), \
.gyro_range = DT_INST_PROP(inst, gyro_range), \
IF_ENABLED(CONFIG_LSM6DSVXXX_STREAM, \
(.fifo_wtm = DT_INST_PROP(inst, fifo_watermark), \
.accel_batch = DT_INST_PROP(inst, accel_fifo_batch_rate), \
.gyro_batch = DT_INST_PROP(inst, gyro_fifo_batch_rate), \
.sflp_odr = DT_INST_PROP(inst, sflp_odr), \
.sflp_fifo_en = DT_INST_PROP(inst, sflp_fifo_enable), \
.temp_batch = DT_INST_PROP(inst, temp_fifo_batch_rate),)) \
IF_ENABLED(UTIL_OR(DT_INST_NODE_HAS_PROP(inst, int1_gpios), \
DT_INST_NODE_HAS_PROP(inst, int2_gpios)), \
(LSM6DSVXXX_CFG_IRQ(inst)))
/*
* Instantiation macros used when a device is on a SPI bus.
*/
#define LSM6DSVXXX_SPI_OP (SPI_WORD_SET(8) | \
SPI_OP_MODE_MASTER | \
SPI_MODE_CPOL | \
SPI_MODE_CPHA) \
#define LSM6DSVXXX_SPI_RTIO_DEFINE(inst, prefix) \
SPI_DT_IODEV_DEFINE(prefix##_iodev_##inst, \
DT_DRV_INST(inst), LSM6DSVXXX_SPI_OP, 0U); \
RTIO_DEFINE(prefix##_rtio_ctx_##inst, 8, 8);
#define LSM6DSVXXX_CONFIG_SPI(inst, prefix) \
{ \
STMEMSC_CTX_SPI(&prefix##_config_##inst.stmemsc_cfg), \
.stmemsc_cfg = { \
.spi = SPI_DT_SPEC_INST_GET(inst, \
LSM6DSVXXX_SPI_OP, \
0), \
}, \
LSM6DSVXXX_CONFIG_COMMON(inst, prefix) \
}
#define LSM6DSVXXX_DEFINE_SPI(inst, prefix) \
IF_ENABLED(CONFIG_SPI_RTIO, \
(LSM6DSVXXX_SPI_RTIO_DEFINE(inst, prefix))); \
static struct lsm6dsvxxx_data prefix##_data_##inst = { \
IF_ENABLED(CONFIG_SPI_RTIO, \
(.rtio_ctx = &prefix##_rtio_ctx_##inst, \
.iodev = &prefix##_iodev_##inst, \
.bus_type = RTIO_BUS_SPI,)) \
}; \
static const struct lsm6dsvxxx_config prefix##_config_##inst = \
LSM6DSVXXX_CONFIG_SPI(inst, prefix);
/*
* Instantiation macros used when a device is on an I2C bus.
*/
#define LSM6DSVXXX_I2C_RTIO_DEFINE(inst, prefix) \
I2C_DT_IODEV_DEFINE(prefix##_iodev_##inst, DT_DRV_INST(inst)); \
RTIO_DEFINE(prefix##_rtio_ctx_##inst, 8, 8);
#define LSM6DSVXXX_CONFIG_I2C(inst, prefix) \
{ \
STMEMSC_CTX_I2C(&prefix##_config_##inst.stmemsc_cfg), \
.stmemsc_cfg = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
}, \
LSM6DSVXXX_CONFIG_COMMON(inst, prefix) \
}
#define LSM6DSVXXX_DEFINE_I2C(inst, prefix) \
IF_ENABLED(CONFIG_I2C_RTIO, \
(LSM6DSVXXX_I2C_RTIO_DEFINE(inst, prefix))); \
static struct lsm6dsvxxx_data prefix##_data_##inst = { \
IF_ENABLED(CONFIG_I2C_RTIO, \
(.rtio_ctx = &prefix##_rtio_ctx_##inst, \
.iodev = &prefix##_iodev_##inst, \
.bus_type = RTIO_BUS_I2C,)) \
}; \
static const struct lsm6dsvxxx_config prefix##_config_##inst = \
LSM6DSVXXX_CONFIG_I2C(inst, prefix);
/*
* Instantiation macros used when a device is on an I3C bus.
*/
#define LSM6DSVXXX_I3C_RTIO_DEFINE(inst, prefix) \
I3C_DT_IODEV_DEFINE(prefix##_i3c_iodev_##inst, DT_DRV_INST(inst)); \
RTIO_DEFINE(prefix##_rtio_ctx_##inst, 8, 8);
#define LSM6DSVXXX_CONFIG_I3C(inst, prefix) \
{ \
STMEMSC_CTX_I3C(&prefix##_config_##inst.stmemsc_cfg), \
.stmemsc_cfg = { \
.i3c = &prefix##_data_##inst.i3c_dev, \
}, \
.i3c.bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \
.i3c.dev_id = I3C_DEVICE_ID_DT_INST(inst), \
IF_ENABLED(CONFIG_LSM6DSVXXX_TRIGGER, \
(.int_en_i3c = DT_INST_PROP(inst, int_en_i3c), \
.bus_act_sel = DT_INST_ENUM_IDX(inst, bus_act_sel_us),)) \
LSM6DSVXXX_CONFIG_COMMON(inst, prefix) \
}
#define LSM6DSVXXX_DEFINE_I3C(inst, prefix) \
IF_ENABLED(CONFIG_I3C_RTIO, \
(LSM6DSVXXX_I3C_RTIO_DEFINE(inst, prefix))); \
static struct lsm6dsvxxx_data prefix##_data_##inst = { \
IF_ENABLED(CONFIG_I3C_RTIO, \
(.rtio_ctx = &prefix##_rtio_ctx_##inst, \
.iodev = &prefix##_i3c_iodev_##inst, \
.bus_type = RTIO_BUS_I3C,)) \
}; \
static const struct lsm6dsvxxx_config prefix##_config_##inst = \
LSM6DSVXXX_CONFIG_I3C(inst, prefix);
#define LSM6DSVXXX_DEFINE_I3C_OR_I2C(inst, prefix) \
COND_CODE_0(DT_INST_PROP_BY_IDX(inst, reg, 1), \
(LSM6DSVXXX_DEFINE_I2C(inst, prefix)), \
(LSM6DSVXXX_DEFINE_I3C(inst, prefix)))
/*
* Main instantiation macro. Use of COND_CODE_1() selects the right
* bus-specific macro at preprocessor time.
*/
#define LSM6DSVXXX_DEFINE(inst, prefix) \
COND_CODE_1(DT_INST_ON_BUS(inst, spi), \
(LSM6DSVXXX_DEFINE_SPI(inst, prefix)), \
(COND_CODE_1(DT_INST_ON_BUS(inst, i3c), \
(LSM6DSVXXX_DEFINE_I3C_OR_I2C(inst, prefix)), \
(LSM6DSVXXX_DEFINE_I2C(inst, prefix))))); \
LSM6DSVXXX_DEVICE_INIT(inst, prefix)
/* clang-format on */
#define DT_DRV_COMPAT st_lsm6dsv320x
DT_INST_FOREACH_STATUS_OKAY_VARGS(LSM6DSVXXX_DEFINE, DT_DRV_COMPAT)
#undef DT_DRV_COMPAT

View File

@@ -0,0 +1,209 @@
/* ST Microelectronics LSM6DSVXXX family IMU sensor
*
* Copyright (c) 2025 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*
* Datasheet:
* https://www.st.com/resource/en/datasheet/lsm6dsv320x.pdf
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_LSM6DSVXXX_LSM6DSVXXX_H_
#define ZEPHYR_DRIVERS_SENSOR_LSM6DSVXXX_LSM6DSVXXX_H_
#include <stdint.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/sys/util.h>
#include <stmemsc.h>
#include <zephyr/rtio/regmap.h>
#include <zephyr/drivers/sensor_clock.h>
#include <zephyr/pm/device.h>
#define LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(bus) \
(DT_HAS_COMPAT_ON_BUS_STATUS_OKAY(st_lsm6dsv320x, bus))
#if DT_HAS_COMPAT_STATUS_OKAY(st_lsm6dsv320x)
#include "lsm6dsv320x_reg.h"
#include <zephyr/dt-bindings/sensor/lsm6dsv320x.h>
#endif
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(spi)
#include <zephyr/drivers/spi.h>
#endif /* LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(spi) */
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
#include <zephyr/drivers/i2c.h>
#endif /* LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
#include <zephyr/drivers/i3c.h>
#endif /* LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i3c) */
#if (LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i3c))
#define ON_I3C_BUS(cfg) (cfg->i3c.bus != NULL)
#else
#define ON_I3C_BUS(cfg) (false)
#endif
typedef int32_t (*api_lsm6dsvxxx_init_chip)(const struct device *dev);
#if defined(CONFIG_PM_DEVICE)
typedef int32_t (*api_lsm6dsvxxx_pm_action)(const struct device *dev, enum pm_device_action action);
#endif /* CONFIG_PM_DEVICE */
typedef int32_t (*api_lsm6dsvxxx_accel_set_fs)(const struct device *dev, int32_t range);
typedef int32_t (*api_lsm6dsvxxx_accel_set_odr)(const struct device *dev, int32_t freq);
typedef int32_t (*api_lsm6dsvxxx_accel_set_mode)(const struct device *dev, int32_t mode);
typedef int32_t (*api_lsm6dsvxxx_accel_get_fs)(const struct device *dev, int32_t *range);
typedef int32_t (*api_lsm6dsvxxx_accel_get_odr)(const struct device *dev, int32_t *freq);
typedef int32_t (*api_lsm6dsvxxx_accel_get_mode)(const struct device *dev, int32_t *mode);
typedef int32_t (*api_lsm6dsvxxx_gyro_set_fs)(const struct device *dev, int32_t range);
typedef int32_t (*api_lsm6dsvxxx_gyro_set_odr)(const struct device *dev, int32_t freq);
typedef int32_t (*api_lsm6dsvxxx_gyro_set_mode)(const struct device *dev, int32_t mode);
typedef int32_t (*api_lsm6dsvxxx_gyro_get_fs)(const struct device *dev, int32_t *range);
typedef int32_t (*api_lsm6dsvxxx_gyro_get_odr)(const struct device *dev, int32_t *freq);
typedef int32_t (*api_lsm6dsvxxx_gyro_get_mode)(const struct device *dev, int32_t *mode);
struct lsm6dsvxxx_chip_api {
api_lsm6dsvxxx_init_chip init_chip;
#if defined(CONFIG_PM_DEVICE)
api_lsm6dsvxxx_pm_action pm_action;
#endif /* CONFIG_PM_DEVICE */
api_lsm6dsvxxx_accel_set_fs accel_fs_set;
api_lsm6dsvxxx_accel_set_odr accel_odr_set;
api_lsm6dsvxxx_accel_set_mode accel_mode_set;
api_lsm6dsvxxx_accel_get_fs accel_fs_get;
api_lsm6dsvxxx_accel_get_odr accel_odr_get;
api_lsm6dsvxxx_accel_get_mode accel_mode_get;
api_lsm6dsvxxx_gyro_set_fs gyro_fs_set;
api_lsm6dsvxxx_gyro_set_odr gyro_odr_set;
api_lsm6dsvxxx_gyro_set_mode gyro_mode_set;
api_lsm6dsvxxx_gyro_get_fs gyro_fs_get;
api_lsm6dsvxxx_gyro_get_odr gyro_odr_get;
api_lsm6dsvxxx_gyro_get_mode gyro_mode_get;
};
struct lsm6dsvxxx_config {
stmdev_ctx_t ctx;
union {
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i2c)
const struct i2c_dt_spec i2c;
#endif
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(spi)
const struct spi_dt_spec spi;
#endif
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
struct i3c_device_desc **i3c;
#endif
} stmemsc_cfg;
uint8_t accel_pm;
uint8_t accel_odr;
uint8_t accel_hg_odr;
uint8_t accel_range;
uint8_t gyro_pm;
uint8_t gyro_odr;
uint8_t gyro_range;
uint8_t drdy_pulsed;
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
struct {
const struct device *bus;
const struct i3c_device_id dev_id;
} i3c;
#endif
const struct lsm6dsvxxx_chip_api *chip_api;
};
struct lsm6dsvxxx_ibi_payload {
uint8_t mdb;
uint8_t fifo_status1;
uint8_t fifo_status2;
uint8_t all_int_src;
uint8_t status_reg;
uint8_t status_reg_ois;
uint8_t status_master_main;
uint8_t emb_func_status;
uint8_t fsm_status;
uint8_t mlc_status;
} __packed;
struct lsm6dsvxxx_data {
const struct device *dev;
int16_t acc[3];
uint32_t acc_gain;
int16_t gyro[3];
uint32_t gyro_gain;
#if defined(CONFIG_LSM6DSV16X_ENABLE_TEMP)
int16_t temp_sample;
#endif
uint8_t accel_freq;
uint8_t accel_fs;
uint8_t gyro_freq;
uint8_t gyro_fs;
uint8_t out_xl;
uint8_t out_tp;
struct rtio_iodev_sqe *streaming_sqe;
struct rtio *rtio_ctx;
struct rtio_iodev *iodev;
rtio_bus_type bus_type; /* I2C is 0, SPI is 1, I3C is 2 */
#if LSM6DSVXXX_ANY_INST_ON_BUS_STATUS_OKAY(i3c)
struct i3c_device_desc *i3c_dev;
struct lsm6dsvxxx_ibi_payload ibi_payload;
#endif
};
static inline uint8_t lsm6dsvxxx_bus_reg(rtio_bus_type bus, uint8_t addr)
{
return (rtio_is_spi(bus)) ? addr | 0x80 : addr;
}
int lsm6dsvxxx_spi_init(const struct device *dev);
/* decoder */
struct lsm6dsvxxx_decoder_header {
uint64_t timestamp;
uint8_t is_fifo: 1;
uint8_t range: 4;
uint8_t reserved: 3;
uint8_t int_status;
} __attribute__((__packed__));
struct lsm6dsvxxx_fifo_data {
struct lsm6dsvxxx_decoder_header header;
uint32_t accel_odr: 4;
uint32_t fifo_mode_sel: 2;
uint32_t fifo_count: 7;
uint32_t reserved_1: 5;
uint32_t accel_batch_odr: 3;
uint32_t ts_batch_odr: 2;
uint32_t reserved: 9;
} __attribute__((__packed__));
struct lsm6dsvxxx_rtio_data {
struct lsm6dsvxxx_decoder_header header;
struct {
uint8_t has_accel: 1; /* set if accel channel has data */
uint8_t has_temp: 1; /* set if temp channel has data */
uint8_t reserved: 6;
} __attribute__((__packed__));
int16_t accel[3];
int16_t temp;
};
int lsm6dsvxxx_encode(const struct device *dev, const struct sensor_chan_spec *const channels,
const size_t num_channels, uint8_t *buf);
int lsm6dsvxxx_get_decoder(const struct device *dev, const struct sensor_decoder_api **decoder);
void lsm6dsvxxx_rtio_rd_transaction(const struct device *dev,
uint8_t *regs, uint8_t regs_num,
struct spi_buf *buf,
struct rtio_iodev_sqe *iodev_sqe,
rtio_callback_t complete_op_cb);
#endif /* ZEPHYR_DRIVERS_SENSOR_LSM6DSVXXX_LSM6DSVXXX_H_ */

View File

@@ -0,0 +1,215 @@
/* ST Microelectronics LSM6DSVXXX family IMU sensor
*
* Copyright (c) 2025 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*
* Datasheet:
* https://www.st.com/resource/en/datasheet/lsm6dsv320x.pdf
*/
#include "lsm6dsvxxx.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LSM6DSVXXX_DECODER, CONFIG_SENSOR_LOG_LEVEL);
/*
* Expand val to q31_t according to its range; this is achieved multiplying by 2^31/2^range.
*/
#define Q31_SHIFT_VAL(val, range) \
(q31_t) (roundf((val) * ((int64_t)1 << (31 - (range)))))
/*
* Expand micro_val (a generic micro unit) to q31_t according to its range; this is achieved
* multiplying by 2^31/2^range. Then transform it to val.
*/
#define Q31_SHIFT_MICROVAL(micro_val, range) \
(q31_t) ((int64_t)(micro_val) * ((int64_t)1 << (31 - (range))) / 1000000LL)
/* bit range for Accelerometer for a given range value */
static const int8_t accel_range[] = {
5, /* FS_2G */
6, /* FS_4G */
7, /* FS_8G */
8, /* FS_16G */
9, /* FS_32G */
10, /* FS_64G */
11, /* FS_128G */
12, /* FS_256G */
13, /* FS_320G */
};
#if defined(CONFIG_LSM6DSVXXX_ENABLE_TEMP)
/* bit range for Temperature sensor */
static const int8_t temp_range = 9;
/* transform temperature LSB into micro-Celsius */
#define SENSOR_TEMP_UCELSIUS(t_lsb) \
(int64_t) (25000000LL + (((int64_t)(t_lsb) * 1000000LL) / 355LL))
#endif
/* Calculate scaling factor to transform micro-g/LSB unit into micro-ms2/LSB */
#define SENSOR_SCALE_UG_TO_UMS2(ug_lsb) \
(int32_t)((ug_lsb) * SENSOR_G / 1000000LL)
/*
* Accelerometer scaling factors table for a given range value
* GAIN_UNIT_XL is expressed in ug/LSB.
*/
static const int32_t accel_scaler[] = {
SENSOR_SCALE_UG_TO_UMS2(61), /* FS_2G */
SENSOR_SCALE_UG_TO_UMS2(122), /* FS_4G */
SENSOR_SCALE_UG_TO_UMS2(244), /* FS_8G */
SENSOR_SCALE_UG_TO_UMS2(488), /* FS_16G */
SENSOR_SCALE_UG_TO_UMS2(976), /* FS_32G */
SENSOR_SCALE_UG_TO_UMS2(1952), /* FS_64G */
SENSOR_SCALE_UG_TO_UMS2(2904), /* FS_128G */
SENSOR_SCALE_UG_TO_UMS2(7808), /* FS_256G */
SENSOR_SCALE_UG_TO_UMS2(10417), /* FS_320G */
};
/* Calculate scaling factor to transform micro-dps/LSB unit into micro-rads/LSB */
#define SENSOR_SCALE_UDPS_TO_URADS(udps_lsb) \
(int32_t)(((udps_lsb) * SENSOR_PI / 180LL) / 1000000LL)
static int lsm6dsvxxx_decoder_get_frame_count(const uint8_t *buffer,
struct sensor_chan_spec chan_spec,
uint16_t *frame_count)
{
const struct lsm6dsvxxx_fifo_data *data = (const struct lsm6dsvxxx_fifo_data *)buffer;
const struct lsm6dsvxxx_rtio_data *rdata = (const struct lsm6dsvxxx_rtio_data *)buffer;
const struct lsm6dsvxxx_decoder_header *header = &data->header;
if (chan_spec.chan_idx != 0) {
return -ENOTSUP;
}
if (!header->is_fifo) {
switch (chan_spec.chan_type) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
*frame_count = rdata->has_accel ? 1 : 0;
return 0;
case SENSOR_CHAN_DIE_TEMP:
*frame_count = rdata->has_temp ? 1 : 0;
return 0;
default:
*frame_count = 0;
return -ENOTSUP;
}
return 0;
}
return 0;
}
static int lsm6dsvxxx_decode_sample(const uint8_t *buffer, struct sensor_chan_spec chan_spec,
uint32_t *fit, uint16_t max_count, void *data_out)
{
const struct lsm6dsvxxx_rtio_data *edata = (const struct lsm6dsvxxx_rtio_data *)buffer;
const struct lsm6dsvxxx_decoder_header *header = &edata->header;
if (*fit != 0) {
return 0;
}
if (max_count == 0 || chan_spec.chan_idx != 0) {
return -EINVAL;
}
switch (chan_spec.chan_type) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ: {
const int32_t scale = accel_scaler[header->range];
if (edata->has_accel == 0) {
return -ENODATA;
}
struct sensor_three_axis_data *out = data_out;
out->header.base_timestamp_ns = edata->header.timestamp;
out->header.reading_count = 1;
out->shift = accel_range[header->range];
out->readings[0].x = Q31_SHIFT_MICROVAL(scale * edata->accel[0], out->shift);
out->readings[0].y = Q31_SHIFT_MICROVAL(scale * edata->accel[1], out->shift);
out->readings[0].z = Q31_SHIFT_MICROVAL(scale * edata->accel[2], out->shift);
*fit = 1;
return 1;
}
#if defined(CONFIG_LSM6DSVXXX_ENABLE_TEMP)
case SENSOR_CHAN_DIE_TEMP: {
int64_t t_uC;
if (edata->has_temp == 0) {
return -ENODATA;
}
struct sensor_q31_data *out = data_out;
out->header.base_timestamp_ns = edata->header.timestamp;
out->header.reading_count = 1;
out->shift = temp_range;
/* transform temperature LSB into micro-Celsius */
t_uC = SENSOR_TEMP_UCELSIUS(edata->temp);
out->readings[0].temperature = Q31_SHIFT_MICROVAL(t_uC, out->shift);
*fit = 1;
return 1;
}
#endif
default:
return -EINVAL;
}
}
static int lsm6dsvxxx_decoder_decode(const uint8_t *buffer, struct sensor_chan_spec chan_spec,
uint32_t *fit, uint16_t max_count, void *data_out)
{
return lsm6dsvxxx_decode_sample(buffer, chan_spec, fit, max_count, data_out);
}
static int lsm6dsvxxx_decoder_get_size_info(struct sensor_chan_spec chan_spec, size_t *base_size,
size_t *frame_size)
{
switch (chan_spec.chan_type) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
*base_size = sizeof(struct sensor_three_axis_data);
*frame_size = sizeof(struct sensor_three_axis_sample_data);
return 0;
case SENSOR_CHAN_DIE_TEMP:
*base_size = sizeof(struct sensor_q31_data);
*frame_size = sizeof(struct sensor_q31_sample_data);
return 0;
default:
return -ENOTSUP;
}
}
SENSOR_DECODER_API_DT_DEFINE() = {
.get_frame_count = lsm6dsvxxx_decoder_get_frame_count,
.get_size_info = lsm6dsvxxx_decoder_get_size_info,
.decode = lsm6dsvxxx_decoder_decode,
};
int lsm6dsvxxx_get_decoder(const struct device *dev, const struct sensor_decoder_api **decoder)
{
ARG_UNUSED(dev);
*decoder = &SENSOR_DECODER_NAME();
return 0;
}

View File

@@ -0,0 +1,22 @@
/* ST Microelectronics LSM6DSVXXX family IMU sensor
*
* Copyright (c) 2025 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*
* Datasheet:
* https://www.st.com/resource/en/datasheet/lsm6dsv320x.pdf
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_LSM6DSVXXX_RTIO_H_
#define ZEPHYR_DRIVERS_SENSOR_LSM6DSVXXX_RTIO_H_
#include <zephyr/device.h>
#include <zephyr/rtio/rtio.h>
#include <zephyr/rtio/work.h>
void lsm6dsvxxx_submit(const struct device *sensor, struct rtio_iodev_sqe *iodev_sqe);
void lsm6dsvxxx_submit_stream(const struct device *sensor, struct rtio_iodev_sqe *iodev_sqe);
void lsm6dsvxxx_stream_irq_handler(const struct device *dev);
#endif /* ZEPHYR_DRIVERS_SENSOR_LSM6DSVXXX_RTIO_H_ */

View File

@@ -0,0 +1,76 @@
# Copyright (c) 2025 STMicroelectronics
# SPDX-License-Identifier: Apache-2.0
description: |
When setting the accel-range, accel-odr, gyro-range, gyro-odr properties in
a .dts or .dtsi file you may include lsm6dsv320x.h and use the macros
defined there.
Example:
#include <zephyr/dt-bindings/sensor/lsm6dsv320x.h>
lsm6dsv320x: lsm6dsv320x@0 {
...
accel-range = <LSM6DSV320X_DT_FS_8G>;
accel-odr = <LSM6DSVXXX_DT_ODR_AT_60Hz>;
gyro-range = <LSM6DSV320X_DT_FS_4000DPS>;
gyro-odr = <LSM6DSVXXX_DT_ODR_AT_60Hz>;
};
include: st,lsm6dsvxxx-common.yaml
properties:
accel-hg-odr:
type: int
default: 0x0
description: |
Specify the default accelerometer High-g output data rate expressed in samples per
second (Hz).
The values are taken in accordance to lsm6dsv320x_hg_xl_data_rate_t enumerative in hal/st
module. Please note that this values will not change the operating mode, which will remain
High Performance (device default)
Default is power-up configuration.
- 0x0 # LSM6DSV320X_HG_XL_ODR_OFF
- 0x3 # LSM6DSV320X_HG_XL_ODR_AT_480Hz
- 0x4 # LSM6DSV320X_HG_XL_ODR_AT_960Hz
- 0x5 # LSM6DSV320X_HG_XL_ODR_AT_1920Hz
- 0x6 # LSM6DSV320X_HG_XL_ODR_AT_3840Hz
- 0x7 # LSM6DSV320X_HG_XL_ODR_AT_7680Hz
enum: [0x0, 0x3, 0x4, 0x5, 0x6, 0x7]
accel-range:
type: int
default: 2
description: |
Range in g. Default is power-up configuration.
Ranges from 32g and above requires the High-g sensor to be turned on (see accel-hg-odr).
- 2 # LSM6DSV320X_DT_FS_2G (0.061 mg/LSB)
- 4 # LSM6DSV320X_DT_FS_4G (0.122 mg/LSB)
- 8 # LSM6DSV320X_DT_FS_8G (0.244 mg/LSB)
- 16 # LSM6DSV320X_DT_FS_16G (0.488 mg/LSB)
- 32 # LSM6DSV320X_DT_FS_32G (0.976 mg/LSB)
- 64 # LSM6DSV320X_DT_FS_64G (1.952 mg/LSB)
- 128 # LSM6DSV320X_DT_FS_128G (3.904 mg/LSB)
- 256 # LSM6DSV320X_DT_FS_256G (7.808 mg/LSB)
- 320 # LSM6DSV320X_DT_FS_320G (10.417 mg/LSB)
enum: [2, 4, 8, 16, 32, 64, 128, 256, 320]
gyro-range:
type: int
default: 0
description: |
Range in dps. Default is power-up configuration.
- 0x0 # power-up configuration
- 0x1 # LSM6DSV320X_DT_FS_250DPS (8.75 mdps/LSB)
- 0x2 # LSM6DSV320X_DT_FS_500DPS (17.50 mdps/LSB)
- 0x3 # LSM6DSV320X_DT_FS_1000DPS (35 mdps/LSB)
- 0x4 # LSM6DSV320X_DT_FS_2000DPS (70 mdps/LSB)
- 0x5 # LSM6DSV320X_DT_FS_4000DPS (140 mdps/LSB)
enum: [0x0, 0x1, 0x2, 0x3, 0x4, 0x5]

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2025 STMicroelectronics
# SPDX-License-Identifier: Apache-2.0
description: |
STMicroelectronics LSM6DSV320X 6-axis IMU (Inertial Measurement Unit) sensor
accessed through I2C bus
compatible: "st,lsm6dsv320x"
include: ["i2c-device.yaml", "st,lsm6dsv320x-common.yaml"]

View File

@@ -0,0 +1,24 @@
# Copyright (c) 2025 STMicroelectronics
# SPDX-License-Identifier: Apache-2.0
description: |
STMicroelectronics LSM6DSV320X 6-axis IMU (Inertial Measurement Unit) sensor
accessed through I3C bus
compatible: "st,lsm6dsv320x"
include: ["i3c-device.yaml", "st,lsm6dsv320x-common.yaml"]
properties:
int-en-i3c:
type: boolean
description: |
Enables INT pin when I3C is enabled
bus-act-sel-us:
type: int
default: 50
description: |
Bus available time for I3C IBI in microseconds
enum: [50, 2, 1000, 25000]

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2025 STMicroelectronics
# SPDX-License-Identifier: Apache-2.0
description: |
STMicroelectronics LSM6DSV320X 6-axis IMU (Inertial Measurement Unit) sensor
accessed through SPI bus
compatible: "st,lsm6dsv320x"
include: ["spi-device.yaml", "st,lsm6dsv320x-common.yaml"]

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025 STMicroelectronics
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_ST_LSM6DSV320X_H_
#define ZEPHYR_INCLUDE_DT_BINDINGS_ST_LSM6DSV320X_H_
#include "lsm6dsvxxx.h"
/* Accel High-g odr */
#define LSM6DSV320X_HG_XL_ODR_OFF 0x0
#define LSM6DSV320X_HG_XL_ODR_AT_480Hz 0x3
#define LSM6DSV320X_HG_XL_ODR_AT_960Hz 0x4
#define LSM6DSV320X_HG_XL_ODR_AT_1920Hz 0x5
#define LSM6DSV320X_HG_XL_ODR_AT_3840Hz 0x6
#define LSM6DSV320X_HG_XL_ODR_AT_7680Hz 0x7
/* Accel range */
#define LSM6DSV320X_DT_FS_2G 2
#define LSM6DSV320X_DT_FS_4G 4
#define LSM6DSV320X_DT_FS_8G 8
#define LSM6DSV320X_DT_FS_16G 16
#define LSM6DSV320X_DT_FS_32G 32
#define LSM6DSV320X_DT_FS_64G 64
#define LSM6DSV320X_DT_FS_128G 128
#define LSM6DSV320X_DT_FS_256G 256
#define LSM6DSV320X_DT_FS_320G 320
/* Gyro range */
#define LSM6DSV320X_DT_FS_250DPS 0x1
#define LSM6DSV320X_DT_FS_500DPS 0x2
#define LSM6DSV320X_DT_FS_1000DPS 0x3
#define LSM6DSV320X_DT_FS_2000DPS 0x4
#define LSM6DSV320X_DT_FS_4000DPS 0x5
#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_ST_LSM6DSV320X_H_ */

View File

@@ -192,6 +192,9 @@ config USE_STDC_LSM6DSRX
config USE_STDC_LSM6DSV16X
bool
config USE_STDC_LSM6DSV320X
bool
config USE_STDC_LSM9DS1
bool

View File

@@ -8,6 +8,7 @@
#include <zephyr/dt-bindings/sensor/lsm6dsv16x.h>
#include <zephyr/dt-bindings/sensor/lsm6dsv32x.h>
#include <zephyr/dt-bindings/sensor/lsm6dsv320x.h>
#include <zephyr/dt-bindings/sensor/lsm6dso.h>
#include <zephyr/dt-bindings/sensor/lsm6dso16is.h>
#include <zephyr/dt-bindings/sensor/lps22hh.h>
@@ -1489,3 +1490,14 @@ test_i2c_max30210: max30210@c5 {
reg = <0xc5>;
interrupt-gpios = <&test_gpio 0 0>;
};
test_i2c_lsm6dsv320x: lsm6dsv320x@c6 {
compatible = "st,lsm6dsv320x";
reg = <0xc6>;
int1-gpios = <&test_gpio 0 0>;
int2-gpios = <&test_gpio 0 0>;
accel-range = <LSM6DSV320X_DT_FS_8G>;
accel-odr = <LSM6DSVXXX_DT_ODR_AT_60Hz>;
gyro-range = <LSM6DSV320X_DT_FS_2000DPS>;
gyro-odr = <LSM6DSVXXX_DT_ODR_AT_60Hz>;
};