diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index ddbdba27c6d..4e9909ef97c 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -28,6 +28,7 @@ add_subdirectory_ifdef(CONFIG_HTS221 hts221) add_subdirectory_ifdef(CONFIG_IIS2DLPC iis2dlpc) add_subdirectory_ifdef(CONFIG_IIS3DHHC iis3dhhc) add_subdirectory_ifdef(CONFIG_ISL29035 isl29035) +add_subdirectory_ifdef(CONFIG_ISM330DHCX ism330dhcx) add_subdirectory_ifdef(CONFIG_LIS2DH lis2dh) add_subdirectory_ifdef(CONFIG_LIS2DS12 lis2ds12) add_subdirectory_ifdef(CONFIG_LIS2DW12 lis2dw12) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index ae1b17e683e..8a98d3aad06 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -85,6 +85,8 @@ source "drivers/sensor/iis3dhhc/Kconfig" source "drivers/sensor/isl29035/Kconfig" +source "drivers/sensor/ism330dhcx/Kconfig" + source "drivers/sensor/lis2dh/Kconfig" source "drivers/sensor/lis2ds12/Kconfig" diff --git a/drivers/sensor/ism330dhcx/CMakeLists.txt b/drivers/sensor/ism330dhcx/CMakeLists.txt new file mode 100644 index 00000000000..511b91e450b --- /dev/null +++ b/drivers/sensor/ism330dhcx/CMakeLists.txt @@ -0,0 +1,13 @@ +# ST Microelectronics ISM330DHCX 6-axis IMU sensor driver +# +# Copyright (c) 2020 STMicroelectronics +# +# SPDX-License-Identifier: Apache-2.0 +# +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_ISM330DHCX ism330dhcx.c) +zephyr_library_sources_ifdef(CONFIG_ISM330DHCX ism330dhcx_i2c.c) +zephyr_library_sources_ifdef(CONFIG_ISM330DHCX ism330dhcx_spi.c) +zephyr_library_sources_ifdef(CONFIG_ISM330DHCX_SENSORHUB ism330dhcx_shub.c) +zephyr_library_sources_ifdef(CONFIG_ISM330DHCX_TRIGGER ism330dhcx_trigger.c) diff --git a/drivers/sensor/ism330dhcx/Kconfig b/drivers/sensor/ism330dhcx/Kconfig new file mode 100644 index 00000000000..109d1d084cb --- /dev/null +++ b/drivers/sensor/ism330dhcx/Kconfig @@ -0,0 +1,171 @@ +# ST Microelectronics ISM330DHCX 6-axis IMU sensor driver + +# Copyright (c) 2020 STMicroelectronics +# SPDX-License-Identifier: Apache-2.0 + +menuconfig ISM330DHCX + bool "ISM330DHCX I2C/SPI accelerometer and gyroscope Chip" + depends on (I2C && HAS_DTS_I2C) || SPI + select HAS_STMEMSC + select USE_STDC_ISM330DHCX + help + Enable driver for ISM330DHCX accelerometer and gyroscope + sensor. + +if ISM330DHCX + +choice ISM330DHCX_TRIGGER_MODE + prompt "Trigger mode" + help + Specify the type of triggering to be used by the driver. + +config ISM330DHCX_TRIGGER_NONE + bool "No trigger" + +config ISM330DHCX_TRIGGER_GLOBAL_THREAD + bool "Use global thread" + depends on GPIO + select ISM330DHCX_TRIGGER + +config ISM330DHCX_TRIGGER_OWN_THREAD + bool "Use own thread" + depends on GPIO + select ISM330DHCX_TRIGGER + +endchoice + +config ISM330DHCX_TRIGGER + bool + +if ISM330DHCX_TRIGGER + +config ISM330DHCX_THREAD_PRIORITY + int "Thread priority" + depends on ISM330DHCX_TRIGGER_OWN_THREAD + default 10 + help + Priority of thread used by the driver to handle interrupts. + +config ISM330DHCX_THREAD_STACK_SIZE + int "Thread stack size" + depends on ISM330DHCX_TRIGGER_OWN_THREAD + default 1024 + help + Stack size of thread used by the driver to handle interrupts. + +choice ISM330DHCX_INT_PIN + prompt "Sensor INT pin number" + default ISM330DHCX_INT_PIN_1 + help + The number of ISM330DHCX int pin used to generate interrupt to cpu. + Supported values are int1 or int2 + +config ISM330DHCX_INT_PIN_1 + bool "int1" + +config ISM330DHCX_INT_PIN_2 + bool "int2" +endchoice + +endif # ISM330DHCX_TRIGGER + +config ISM330DHCX_ENABLE_TEMP + bool "Enable temperature" + help + Enable/disable temperature + +config ISM330DHCX_SENSORHUB + bool "Enable I2C sensorhub feature" + help + Enable/disable internal sensorhub. You can enable + a maximum of two external sensors (if more than two are enabled + the system would enumerate only the first two found) + +if ISM330DHCX_SENSORHUB + +config ISM330DHCX_EXT_LIS2MDL + bool "Enable LIS2MDL as external sensor" + +config ISM330DHCX_EXT_IIS2MDC + bool "Enable IIS2MDC as external sensor" + +config ISM330DHCX_EXT_LPS22HH + bool "Enable LPS22HH as external sensor" + +config ISM330DHCX_EXT_HTS221 + bool "Enable HTS221 as external sensor" + +config ISM330DHCX_EXT_LPS22HB + bool "Enable LPS22HB as external sensor" + +endif # ISM330DHCX_SENSORHUB + +menu "Attributes" + +config ISM330DHCX_GYRO_FS + int "Gyroscope full-scale range" + default 0 + help + Specify the default gyroscope full-scale range. + An X value for the config represents a range of +/- X degree per + second. Valid values are: + 0: Full Scale selected at runtime + 125: +/- 125dps + 250: +/- 250dps + 500: +/- 500dps + 1000: +/- 1000dps + 2000: +/- 2000dps + +config ISM330DHCX_GYRO_ODR + int "Gyroscope Output data rate frequency" + range 0 10 + default 0 + help + Specify the default accelerometer output data rate expressed in + samples per second (Hz). + 0: ODR selected at runtime + 1: 12.5Hz + 2: 26Hz + 3: 52Hz + 4: 104Hz + 5: 208Hz + 6: 416Hz + 7: 833Hz + 8: 1660Hz + 9: 3330Hz + 10: 6660Hz + +config ISM330DHCX_ACCEL_FS + int "Accelerometer full-scale range" + default 0 + help + Specify the default accelerometer full-scale range. + An X value for the config represents a range of +/- X G. Valid values + are: + 0: Full Scale selected at runtime + 2: +/- 2g + 4: +/- 4g + 8: +/- 8g + 16: +/- 16g + +config ISM330DHCX_ACCEL_ODR + int "Accelerometer Output data rate frequency" + range 0 10 + default 0 + help + Specify the default accelerometer output data rate expressed in + samples per second (Hz). + 0: ODR selected at runtime + 1: 12.5Hz + 2: 26Hz + 3: 52Hz + 4: 104Hz + 5: 208Hz + 6: 416Hz + 7: 833Hz + 8: 1660Hz + 9: 3330Hz + 10: 6660Hz +endmenu + +endif # ISM330DHCX diff --git a/drivers/sensor/ism330dhcx/ism330dhcx.c b/drivers/sensor/ism330dhcx/ism330dhcx.c new file mode 100644 index 00000000000..169b6e72a48 --- /dev/null +++ b/drivers/sensor/ism330dhcx/ism330dhcx.c @@ -0,0 +1,844 @@ +/* ST Microelectronics ISM330DHCX 6-axis IMU sensor driver + * + * Copyright (c) 2020 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + * + * Datasheet: + * https://www.st.com/resource/en/datasheet/ism330dhcx.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ism330dhcx.h" + +LOG_MODULE_REGISTER(ISM330DHCX, CONFIG_SENSOR_LOG_LEVEL); + +static const u16_t ism330dhcx_odr_map[] = {0, 12, 26, 52, 104, 208, 416, 833, + 1660, 3330, 6660}; + +#if defined(ISM330DHCX_ACCEL_ODR_RUNTIME) || defined(ISM330DHCX_GYRO_ODR_RUNTIME) +static int ism330dhcx_freq_to_odr_val(u16_t freq) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(ism330dhcx_odr_map); i++) { + if (freq == ism330dhcx_odr_map[i]) { + return i; + } + } + + return -EINVAL; +} +#endif + +static int ism330dhcx_odr_to_freq_val(u16_t odr) +{ + /* for valid index, return value from map */ + if (odr < ARRAY_SIZE(ism330dhcx_odr_map)) { + return ism330dhcx_odr_map[odr]; + } + + /* invalid index, return last entry */ + return ism330dhcx_odr_map[ARRAY_SIZE(ism330dhcx_odr_map) - 1]; +} + +#ifdef ISM330DHCX_ACCEL_FS_RUNTIME +static const u16_t ism330dhcx_accel_fs_map[] = {2, 16, 4, 8}; +static const u16_t ism330dhcx_accel_fs_sens[] = {1, 8, 2, 4}; + +static int ism330dhcx_accel_range_to_fs_val(s32_t range) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(ism330dhcx_accel_fs_map); i++) { + if (range == ism330dhcx_accel_fs_map[i]) { + return i; + } + } + + return -EINVAL; +} +#endif + +#ifdef ISM330DHCX_GYRO_FS_RUNTIME +static const u16_t ism330dhcx_gyro_fs_map[] = {250, 500, 1000, 2000, 125}; +static const u16_t ism330dhcx_gyro_fs_sens[] = {2, 4, 8, 16, 1}; + +static int ism330dhcx_gyro_range_to_fs_val(s32_t range) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(ism330dhcx_gyro_fs_map); i++) { + if (range == ism330dhcx_gyro_fs_map[i]) { + return i; + } + } + + return -EINVAL; +} +#endif + +static inline int ism330dhcx_reboot(struct device *dev) +{ + struct ism330dhcx_data *data = dev->driver_data; + + if (ism330dhcx_boot_set(data->ctx, 1) < 0) { + return -EIO; + } + + /* Wait sensor turn-on time as per datasheet */ + k_busy_wait(35 * USEC_PER_MSEC); + + return 0; +} + +static int ism330dhcx_accel_set_fs_raw(struct device *dev, u8_t fs) +{ + struct ism330dhcx_data *data = dev->driver_data; + + if (ism330dhcx_xl_full_scale_set(data->ctx, fs) < 0) { + return -EIO; + } + + data->accel_fs = fs; + + return 0; +} + +static int ism330dhcx_accel_set_odr_raw(struct device *dev, u8_t odr) +{ + struct ism330dhcx_data *data = dev->driver_data; + + if (ism330dhcx_xl_data_rate_set(data->ctx, odr) < 0) { + return -EIO; + } + + data->accel_freq = ism330dhcx_odr_to_freq_val(odr); + + return 0; +} + +static int ism330dhcx_gyro_set_fs_raw(struct device *dev, u8_t fs) +{ + struct ism330dhcx_data *data = dev->driver_data; + + if (ism330dhcx_gy_full_scale_set(data->ctx, fs) < 0) { + return -EIO; + } + + return 0; +} + +static int ism330dhcx_gyro_set_odr_raw(struct device *dev, u8_t odr) +{ + struct ism330dhcx_data *data = dev->driver_data; + + if (ism330dhcx_gy_data_rate_set(data->ctx, odr) < 0) { + return -EIO; + } + + return 0; +} + +#ifdef ISM330DHCX_ACCEL_ODR_RUNTIME +static int ism330dhcx_accel_odr_set(struct device *dev, u16_t freq) +{ + int odr; + + odr = ism330dhcx_freq_to_odr_val(freq); + if (odr < 0) { + return odr; + } + + if (ism330dhcx_accel_set_odr_raw(dev, odr) < 0) { + LOG_DBG("failed to set accelerometer sampling rate"); + return -EIO; + } + + return 0; +} +#endif + +#ifdef ISM330DHCX_ACCEL_FS_RUNTIME +static int ism330dhcx_accel_range_set(struct device *dev, s32_t range) +{ + int fs; + struct ism330dhcx_data *data = dev->driver_data; + + fs = ism330dhcx_accel_range_to_fs_val(range); + if (fs < 0) { + return fs; + } + + if (ism330dhcx_accel_set_fs_raw(dev, fs) < 0) { + LOG_DBG("failed to set accelerometer full-scale"); + return -EIO; + } + + data->acc_gain = (ism330dhcx_accel_fs_sens[fs] * GAIN_UNIT_XL); + return 0; +} +#endif + +static int ism330dhcx_accel_config(struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + switch (attr) { +#ifdef ISM330DHCX_ACCEL_FS_RUNTIME + case SENSOR_ATTR_FULL_SCALE: + return ism330dhcx_accel_range_set(dev, sensor_ms2_to_g(val)); +#endif +#ifdef ISM330DHCX_ACCEL_ODR_RUNTIME + case SENSOR_ATTR_SAMPLING_FREQUENCY: + return ism330dhcx_accel_odr_set(dev, val->val1); +#endif + default: + LOG_DBG("Accel attribute not supported."); + return -ENOTSUP; + } + + return 0; +} + +#ifdef ISM330DHCX_GYRO_ODR_RUNTIME +static int ism330dhcx_gyro_odr_set(struct device *dev, u16_t freq) +{ + int odr; + + odr = ism330dhcx_freq_to_odr_val(freq); + if (odr < 0) { + return odr; + } + + if (ism330dhcx_gyro_set_odr_raw(dev, odr) < 0) { + LOG_DBG("failed to set gyroscope sampling rate"); + return -EIO; + } + + return 0; +} +#endif + +#ifdef ISM330DHCX_GYRO_FS_RUNTIME +static int ism330dhcx_gyro_range_set(struct device *dev, s32_t range) +{ + int fs; + struct ism330dhcx_data *data = dev->driver_data; + + fs = ism330dhcx_gyro_range_to_fs_val(range); + if (fs < 0) { + return fs; + } + + if (ism330dhcx_gyro_set_fs_raw(dev, fs) < 0) { + LOG_DBG("failed to set gyroscope full-scale"); + return -EIO; + } + + data->gyro_gain = (ism330dhcx_gyro_fs_sens[fs] * GAIN_UNIT_G); + return 0; +} +#endif + +static int ism330dhcx_gyro_config(struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + switch (attr) { +#ifdef ISM330DHCX_GYRO_FS_RUNTIME + case SENSOR_ATTR_FULL_SCALE: + return ism330dhcx_gyro_range_set(dev, sensor_rad_to_degrees(val)); +#endif +#ifdef ISM330DHCX_GYRO_ODR_RUNTIME + case SENSOR_ATTR_SAMPLING_FREQUENCY: + return ism330dhcx_gyro_odr_set(dev, val->val1); +#endif + default: + LOG_DBG("Gyro attribute not supported."); + return -ENOTSUP; + } + + return 0; +} + +static int ism330dhcx_attr_set(struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + switch (chan) { + case SENSOR_CHAN_ACCEL_XYZ: + return ism330dhcx_accel_config(dev, chan, attr, val); + case SENSOR_CHAN_GYRO_XYZ: + return ism330dhcx_gyro_config(dev, chan, attr, val); +#if defined(CONFIG_ISM330DHCX_SENSORHUB) + case SENSOR_CHAN_MAGN_XYZ: + case SENSOR_CHAN_PRESS: + case SENSOR_CHAN_HUMIDITY: + return ism330dhcx_shub_config(dev, chan, attr, val); +#endif /* CONFIG_ISM330DHCX_SENSORHUB */ + default: + LOG_WRN("attr_set() not supported on this channel."); + return -ENOTSUP; + } + + return 0; +} + +static int ism330dhcx_sample_fetch_accel(struct device *dev) +{ + struct ism330dhcx_data *data = dev->driver_data; + union axis3bit16_t buf; + + if (ism330dhcx_acceleration_raw_get(data->ctx, buf.u8bit) < 0) { + LOG_DBG("Failed to read sample"); + return -EIO; + } + + data->acc[0] = sys_le16_to_cpu(buf.i16bit[0]); + data->acc[1] = sys_le16_to_cpu(buf.i16bit[1]); + data->acc[2] = sys_le16_to_cpu(buf.i16bit[2]); + + return 0; +} + +static int ism330dhcx_sample_fetch_gyro(struct device *dev) +{ + struct ism330dhcx_data *data = dev->driver_data; + union axis3bit16_t buf; + + if (ism330dhcx_angular_rate_raw_get(data->ctx, buf.u8bit) < 0) { + LOG_DBG("Failed to read sample"); + return -EIO; + } + + data->gyro[0] = sys_le16_to_cpu(buf.i16bit[0]); + data->gyro[1] = sys_le16_to_cpu(buf.i16bit[1]); + data->gyro[2] = sys_le16_to_cpu(buf.i16bit[2]); + + return 0; +} + +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) +static int ism330dhcx_sample_fetch_temp(struct device *dev) +{ + struct ism330dhcx_data *data = dev->driver_data; + union axis1bit16_t buf; + + if (ism330dhcx_temperature_raw_get(data->ctx, buf.u8bit) < 0) { + LOG_DBG("Failed to read sample"); + return -EIO; + } + + data->temp_sample = sys_le16_to_cpu(buf.i16bit); + + return 0; +} +#endif + +#if defined(CONFIG_ISM330DHCX_SENSORHUB) +static int ism330dhcx_sample_fetch_shub(struct device *dev) +{ + if (ism330dhcx_shub_fetch_external_devs(dev) < 0) { + LOG_DBG("failed to read ext shub devices"); + return -EIO; + } + + return 0; +} +#endif /* CONFIG_ISM330DHCX_SENSORHUB */ + +static int ism330dhcx_sample_fetch(struct device *dev, enum sensor_channel chan) +{ + switch (chan) { + case SENSOR_CHAN_ACCEL_XYZ: + ism330dhcx_sample_fetch_accel(dev); +#if defined(CONFIG_ISM330DHCX_SENSORHUB) + ism330dhcx_sample_fetch_shub(dev); +#endif + break; + case SENSOR_CHAN_GYRO_XYZ: + ism330dhcx_sample_fetch_gyro(dev); + break; +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) + case SENSOR_CHAN_DIE_TEMP: + ism330dhcx_sample_fetch_temp(dev); + break; +#endif + case SENSOR_CHAN_ALL: + ism330dhcx_sample_fetch_accel(dev); + ism330dhcx_sample_fetch_gyro(dev); +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) + ism330dhcx_sample_fetch_temp(dev); +#endif +#if defined(CONFIG_ISM330DHCX_SENSORHUB) + ism330dhcx_sample_fetch_shub(dev); +#endif + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static inline void ism330dhcx_accel_convert(struct sensor_value *val, int raw_val, + u32_t sensitivity) +{ + s64_t dval; + + /* Sensitivity is exposed in ug/LSB */ + /* Convert to m/s^2 */ + dval = (s64_t)(raw_val) * sensitivity * SENSOR_G_DOUBLE; + val->val1 = (s32_t)(dval / 1000000); + val->val2 = (s32_t)(dval % 1000000); + +} + +static inline int ism330dhcx_accel_get_channel(enum sensor_channel chan, + struct sensor_value *val, + struct ism330dhcx_data *data, + u32_t sensitivity) +{ + u8_t i; + + switch (chan) { + case SENSOR_CHAN_ACCEL_X: + ism330dhcx_accel_convert(val, data->acc[0], sensitivity); + break; + case SENSOR_CHAN_ACCEL_Y: + ism330dhcx_accel_convert(val, data->acc[1], sensitivity); + break; + case SENSOR_CHAN_ACCEL_Z: + ism330dhcx_accel_convert(val, data->acc[2], sensitivity); + break; + case SENSOR_CHAN_ACCEL_XYZ: + for (i = 0; i < 3; i++) { + ism330dhcx_accel_convert(val++, data->acc[i], sensitivity); + } + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static int ism330dhcx_accel_channel_get(enum sensor_channel chan, + struct sensor_value *val, + struct ism330dhcx_data *data) +{ + return ism330dhcx_accel_get_channel(chan, val, data, data->acc_gain); +} + +static inline void ism330dhcx_gyro_convert(struct sensor_value *val, int raw_val, + u32_t sensitivity) +{ + s64_t dval; + + /* Sensitivity is exposed in udps/LSB */ + /* Convert to rad/s */ + dval = (s64_t)(raw_val) * sensitivity * SENSOR_DEG2RAD_DOUBLE; + val->val1 = (s32_t)(dval / 1000000); + val->val2 = (s32_t)(dval % 1000000); +} + +static inline int ism330dhcx_gyro_get_channel(enum sensor_channel chan, + struct sensor_value *val, + struct ism330dhcx_data *data, + u32_t sensitivity) +{ + u8_t i; + + switch (chan) { + case SENSOR_CHAN_GYRO_X: + ism330dhcx_gyro_convert(val, data->gyro[0], sensitivity); + break; + case SENSOR_CHAN_GYRO_Y: + ism330dhcx_gyro_convert(val, data->gyro[1], sensitivity); + break; + case SENSOR_CHAN_GYRO_Z: + ism330dhcx_gyro_convert(val, data->gyro[2], sensitivity); + break; + case SENSOR_CHAN_GYRO_XYZ: + for (i = 0; i < 3; i++) { + ism330dhcx_gyro_convert(val++, data->gyro[i], sensitivity); + } + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static int ism330dhcx_gyro_channel_get(enum sensor_channel chan, + struct sensor_value *val, + struct ism330dhcx_data *data) +{ + return ism330dhcx_gyro_get_channel(chan, val, data, + ISM330DHCX_DEFAULT_GYRO_SENSITIVITY); +} + +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) +static void ism330dhcx_gyro_channel_get_temp(struct sensor_value *val, + struct ism330dhcx_data *data) +{ + /* val = temp_sample / 256 + 25 */ + val->val1 = data->temp_sample / 256 + 25; + val->val2 = (data->temp_sample % 256) * (1000000 / 256); +} +#endif + +#if defined(CONFIG_ISM330DHCX_SENSORHUB) +static inline void ism330dhcx_magn_convert(struct sensor_value *val, int raw_val, + u16_t sensitivity) +{ + double dval; + + /* Sensitivity is exposed in mgauss/LSB */ + dval = (double)(raw_val * sensitivity); + val->val1 = (s32_t)dval / 1000000; + val->val2 = (s32_t)dval % 1000000; +} + +static inline int ism330dhcx_magn_get_channel(enum sensor_channel chan, + struct sensor_value *val, + struct ism330dhcx_data *data) +{ + s16_t sample[3]; + int idx; + + idx = ism330dhcx_shub_get_idx(SENSOR_CHAN_MAGN_XYZ); + if (idx < 0) { + LOG_DBG("external magn not supported"); + return -ENOTSUP; + } + + + sample[0] = (s16_t)(data->ext_data[idx][0] | + (data->ext_data[idx][1] << 8)); + sample[1] = (s16_t)(data->ext_data[idx][2] | + (data->ext_data[idx][3] << 8)); + sample[2] = (s16_t)(data->ext_data[idx][4] | + (data->ext_data[idx][5] << 8)); + + switch (chan) { + case SENSOR_CHAN_MAGN_X: + ism330dhcx_magn_convert(val, sample[0], data->magn_gain); + break; + case SENSOR_CHAN_MAGN_Y: + ism330dhcx_magn_convert(val, sample[1], data->magn_gain); + break; + case SENSOR_CHAN_MAGN_Z: + ism330dhcx_magn_convert(val, sample[2], data->magn_gain); + break; + case SENSOR_CHAN_MAGN_XYZ: + ism330dhcx_magn_convert(val, sample[0], data->magn_gain); + ism330dhcx_magn_convert(val + 1, sample[1], data->magn_gain); + ism330dhcx_magn_convert(val + 2, sample[2], data->magn_gain); + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static inline void ism330dhcx_hum_convert(struct sensor_value *val, + struct ism330dhcx_data *data) +{ + float rh; + s16_t raw_val; + struct hts221_data *ht = &data->hts221; + int idx; + + idx = ism330dhcx_shub_get_idx(SENSOR_CHAN_HUMIDITY); + if (idx < 0) { + LOG_DBG("external press/temp not supported"); + return; + } + + raw_val = ((s16_t)(data->ext_data[idx][0] | + (data->ext_data[idx][1] << 8))); + + /* find relative humidty by linear interpolation */ + rh = (ht->y1 - ht->y0) * raw_val + ht->x1 * ht->y0 - ht->x0 * ht->y1; + rh /= (ht->x1 - ht->x0); + + /* convert humidity to integer and fractional part */ + val->val1 = rh; + val->val2 = rh * 1000000; +} + +static inline void ism330dhcx_press_convert(struct sensor_value *val, + struct ism330dhcx_data *data) +{ + s32_t raw_val; + int idx; + + idx = ism330dhcx_shub_get_idx(SENSOR_CHAN_PRESS); + if (idx < 0) { + LOG_DBG("external press/temp not supported"); + return; + } + + raw_val = (s32_t)(data->ext_data[idx][0] | + (data->ext_data[idx][1] << 8) | + (data->ext_data[idx][2] << 16)); + + /* Pressure sensitivity is 4096 LSB/hPa */ + /* Convert raw_val to val in kPa */ + val->val1 = (raw_val >> 12) / 10; + val->val2 = (raw_val >> 12) % 10 * 100000 + + (((s32_t)((raw_val) & 0x0FFF) * 100000L) >> 12); +} + +static inline void ism330dhcx_temp_convert(struct sensor_value *val, + struct ism330dhcx_data *data) +{ + s16_t raw_val; + int idx; + + idx = ism330dhcx_shub_get_idx(SENSOR_CHAN_PRESS); + if (idx < 0) { + LOG_DBG("external press/temp not supported"); + return; + } + + raw_val = (s16_t)(data->ext_data[idx][3] | + (data->ext_data[idx][4] << 8)); + + /* Temperature sensitivity is 100 LSB/deg C */ + val->val1 = raw_val / 100; + val->val2 = (s32_t)raw_val % 100 * (10000); +} +#endif + +static int ism330dhcx_channel_get(struct device *dev, + enum sensor_channel chan, + struct sensor_value *val) +{ + struct ism330dhcx_data *data = dev->driver_data; + + switch (chan) { + case SENSOR_CHAN_ACCEL_X: + case SENSOR_CHAN_ACCEL_Y: + case SENSOR_CHAN_ACCEL_Z: + case SENSOR_CHAN_ACCEL_XYZ: + ism330dhcx_accel_channel_get(chan, val, data); + break; + case SENSOR_CHAN_GYRO_X: + case SENSOR_CHAN_GYRO_Y: + case SENSOR_CHAN_GYRO_Z: + case SENSOR_CHAN_GYRO_XYZ: + ism330dhcx_gyro_channel_get(chan, val, data); + break; +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) + case SENSOR_CHAN_DIE_TEMP: + ism330dhcx_gyro_channel_get_temp(val, data); + break; +#endif +#if defined(CONFIG_ISM330DHCX_SENSORHUB) + case SENSOR_CHAN_MAGN_X: + case SENSOR_CHAN_MAGN_Y: + case SENSOR_CHAN_MAGN_Z: + case SENSOR_CHAN_MAGN_XYZ: + ism330dhcx_magn_get_channel(chan, val, data); + break; + + case SENSOR_CHAN_HUMIDITY: + ism330dhcx_hum_convert(val, data); + break; + + case SENSOR_CHAN_PRESS: + ism330dhcx_press_convert(val, data); + break; + + case SENSOR_CHAN_AMBIENT_TEMP: + ism330dhcx_temp_convert(val, data); + break; +#endif + default: + return -ENOTSUP; + } + + return 0; +} + +static const struct sensor_driver_api ism330dhcx_api_funcs = { + .attr_set = ism330dhcx_attr_set, +#if CONFIG_ISM330DHCX_TRIGGER + .trigger_set = ism330dhcx_trigger_set, +#endif + .sample_fetch = ism330dhcx_sample_fetch, + .channel_get = ism330dhcx_channel_get, +}; + +static int ism330dhcx_init_chip(struct device *dev) +{ + struct ism330dhcx_data *ism330dhcx = dev->driver_data; + u8_t chip_id; + + if (ism330dhcx_device_id_get(ism330dhcx->ctx, &chip_id) < 0) { + LOG_DBG("Failed reading chip id"); + return -EIO; + } + + LOG_INF("chip id 0x%x", chip_id); + + if (chip_id != ISM330DHCX_ID) { + LOG_DBG("Invalid chip id 0x%x", chip_id); + return -EIO; + } + + /* reset device */ + if (ism330dhcx_reset_set(ism330dhcx->ctx, 1) < 0) { + return -EIO; + } + + k_busy_wait(100); + + if (ism330dhcx_accel_set_fs_raw(dev, + ISM330DHCX_DEFAULT_ACCEL_FULLSCALE) < 0) { + LOG_DBG("failed to set accelerometer full-scale"); + return -EIO; + } + ism330dhcx->acc_gain = ISM330DHCX_DEFAULT_ACCEL_SENSITIVITY; + + ism330dhcx->accel_freq = ism330dhcx_odr_to_freq_val(CONFIG_ISM330DHCX_ACCEL_ODR); + if (ism330dhcx_accel_set_odr_raw(dev, CONFIG_ISM330DHCX_ACCEL_ODR) < 0) { + LOG_DBG("failed to set accelerometer sampling rate"); + return -EIO; + } + + if (ism330dhcx_gyro_set_fs_raw(dev, ISM330DHCX_DEFAULT_GYRO_FULLSCALE) < 0) { + LOG_DBG("failed to set gyroscope full-scale"); + return -EIO; + } + ism330dhcx->gyro_gain = ISM330DHCX_DEFAULT_GYRO_SENSITIVITY; + + ism330dhcx->gyro_freq = ism330dhcx_odr_to_freq_val(CONFIG_ISM330DHCX_GYRO_ODR); + if (ism330dhcx_gyro_set_odr_raw(dev, CONFIG_ISM330DHCX_GYRO_ODR) < 0) { + LOG_DBG("failed to set gyroscope sampling rate"); + return -EIO; + } + + /* Set FIFO bypass mode */ + if (ism330dhcx_fifo_mode_set(ism330dhcx->ctx, ISM330DHCX_BYPASS_MODE) < 0) { + LOG_DBG("failed to set FIFO mode"); + return -EIO; + } + + if (ism330dhcx_block_data_update_set(ism330dhcx->ctx, 1) < 0) { + LOG_DBG("failed to set BDU mode"); + return -EIO; + } + + return 0; +} + +static struct ism330dhcx_data ism330dhcx_data; + +static const struct ism330dhcx_config ism330dhcx_config = { + .bus_name = DT_INST_0_ST_ISM330DHCX_BUS_NAME, +#if defined(DT_ST_ISM330DHCX_BUS_SPI) + .bus_init = ism330dhcx_spi_init, + .spi_conf.frequency = DT_INST_0_ST_ISM330DHCX_SPI_MAX_FREQUENCY, + .spi_conf.operation = (SPI_OP_MODE_MASTER | SPI_MODE_CPOL | + SPI_MODE_CPHA | SPI_WORD_SET(8) | + SPI_LINES_SINGLE), + .spi_conf.slave = DT_INST_0_ST_ISM330DHCX_BASE_ADDRESS, +#if defined(DT_INST_0_ST_ISM330DHCX_CS_GPIOS_CONTROLLER) + .gpio_cs_port = DT_INST_0_ST_ISM330DHCX_CS_GPIOS_CONTROLLER, + .cs_gpio = DT_INST_0_ST_ISM330DHCX_CS_GPIOS_PIN, + + .spi_conf.cs = &ism330dhcx_data.cs_ctrl, +#else + .spi_conf.cs = NULL, +#endif +#elif defined(DT_ST_ISM330DHCX_BUS_I2C) + .bus_init = ism330dhcx_i2c_init, + .i2c_slv_addr = DT_INST_0_ST_ISM330DHCX_BASE_ADDRESS, +#else +#error "BUS MACRO NOT DEFINED IN DTS" +#endif +#ifdef CONFIG_ISM330DHCX_TRIGGER +#if defined(DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_CONTROLLER_1) + /* Two gpio pins declared in DTS */ +#if defined(CONFIG_ISM330DHCX_INT_PIN_1) + .int_gpio_port = DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_CONTROLLER_0, + .int_gpio_pin = DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_PIN_0, + .int_gpio_flags = DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_FLAGS_0, + .int_pin = 1, +#elif defined(CONFIG_ISM330DHCX_INT_PIN_2) + .int_gpio_port = DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_CONTROLLER_1, + .int_gpio_pin = DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_PIN_1, + .int_gpio_flags = DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_FLAGS_1, + .int_pin = 2, +#endif /* CONFIG_ISM330DHCX_INT_PIN_* */ +#else + /* One gpio pin declared in DTS */ + .int_gpio_port = DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_CONTROLLER, + .int_gpio_pin = DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_PIN, + .int_gpio_flags = DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_FLAGS, +#if defined(CONFIG_ISM330DHCX_INT_PIN_1) + .int_pin = 1, +#elif defined(CONFIG_ISM330DHCX_INT_PIN_2) + .int_pin = 2, +#endif /* CONFIG_ISM330DHCX_INT_PIN_* */ +#endif /* DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_CONTROLLER_1 */ + +#endif /* CONFIG_ISM330DHCX_TRIGGER */ +}; + +static int ism330dhcx_init(struct device *dev) +{ + const struct ism330dhcx_config * const config = dev->config->config_info; + struct ism330dhcx_data *data = dev->driver_data; + + data->bus = device_get_binding(config->bus_name); + if (!data->bus) { + LOG_DBG("master not found: %s", + config->bus_name); + return -EINVAL; + } + + config->bus_init(dev); + +#ifdef CONFIG_ISM330DHCX_TRIGGER + if (ism330dhcx_init_interrupt(dev) < 0) { + LOG_ERR("Failed to initialize interrupt."); + return -EIO; + } +#endif + + if (ism330dhcx_init_chip(dev) < 0) { + LOG_DBG("failed to initialize chip"); + return -EIO; + } + +#ifdef CONFIG_ISM330DHCX_SENSORHUB + if (ism330dhcx_shub_init(dev) < 0) { + LOG_DBG("failed to initialize external chip"); + return -EIO; + } +#endif + + return 0; +} + + +static struct ism330dhcx_data ism330dhcx_data; + +DEVICE_AND_API_INIT(ism330dhcx, DT_INST_0_ST_ISM330DHCX_LABEL, ism330dhcx_init, + &ism330dhcx_data, &ism330dhcx_config, POST_KERNEL, + CONFIG_SENSOR_INIT_PRIORITY, &ism330dhcx_api_funcs); diff --git a/drivers/sensor/ism330dhcx/ism330dhcx.h b/drivers/sensor/ism330dhcx/ism330dhcx.h new file mode 100644 index 00000000000..cf26875e7cf --- /dev/null +++ b/drivers/sensor/ism330dhcx/ism330dhcx.h @@ -0,0 +1,214 @@ +/* ST Microelectronics ISM330DHCX 6-axis IMU sensor driver + * + * Copyright (c) 2020 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + * + * Datasheet: + * https://www.st.com/resource/en/datasheet/ism330dhcx.pdf + */ + +#ifndef ZEPHYR_DRIVERS_SENSOR_ISM330DHCX_ISM330DHCX_H_ +#define ZEPHYR_DRIVERS_SENSOR_ISM330DHCX_ISM330DHCX_H_ + +#include +#include +#include +#include +#include +#include "ism330dhcx_reg.h" + +union axis3bit16_t { + s16_t i16bit[3]; + u8_t u8bit[6]; +}; + +union axis1bit16_t { + s16_t i16bit; + u8_t u8bit[2]; +}; + +#define ISM330DHCX_EN_BIT 0x01 +#define ISM330DHCX_DIS_BIT 0x00 + +/* Accel sensor sensitivity grain is 61 ug/LSB */ +#define GAIN_UNIT_XL (61LL) + +/* Gyro sensor sensitivity grain is 4.375 udps/LSB */ +#define GAIN_UNIT_G (4375LL) + +#define SENSOR_PI_DOUBLE (SENSOR_PI / 1000000.0) +#define SENSOR_DEG2RAD_DOUBLE (SENSOR_PI_DOUBLE / 180) +#define SENSOR_G_DOUBLE (SENSOR_G / 1000000.0) + +#if CONFIG_ISM330DHCX_ACCEL_FS == 0 + #define ISM330DHCX_ACCEL_FS_RUNTIME 1 + #define ISM330DHCX_DEFAULT_ACCEL_FULLSCALE 0 + #define ISM330DHCX_DEFAULT_ACCEL_SENSITIVITY GAIN_UNIT_XL +#elif CONFIG_ISM330DHCX_ACCEL_FS == 2 + #define ISM330DHCX_DEFAULT_ACCEL_FULLSCALE 0 + #define ISM330DHCX_DEFAULT_ACCEL_SENSITIVITY GAIN_UNIT_XL +#elif CONFIG_ISM330DHCX_ACCEL_FS == 4 + #define ISM330DHCX_DEFAULT_ACCEL_FULLSCALE 2 + #define ISM330DHCX_DEFAULT_ACCEL_SENSITIVITY (2.0 * GAIN_UNIT_XL) +#elif CONFIG_ISM330DHCX_ACCEL_FS == 8 + #define ISM330DHCX_DEFAULT_ACCEL_FULLSCALE 3 + #define ISM330DHCX_DEFAULT_ACCEL_SENSITIVITY (4.0 * GAIN_UNIT_XL) +#elif CONFIG_ISM330DHCX_ACCEL_FS == 16 + #define ISM330DHCX_DEFAULT_ACCEL_FULLSCALE 1 + #define ISM330DHCX_DEFAULT_ACCEL_SENSITIVITY (8.0 * GAIN_UNIT_XL) +#endif + +#if (CONFIG_ISM330DHCX_ACCEL_ODR == 0) +#define ISM330DHCX_ACCEL_ODR_RUNTIME 1 +#endif + +#define GYRO_FULLSCALE_125 4 + +#if CONFIG_ISM330DHCX_GYRO_FS == 0 + #define ISM330DHCX_GYRO_FS_RUNTIME 1 + #define ISM330DHCX_DEFAULT_GYRO_FULLSCALE 4 + #define ISM330DHCX_DEFAULT_GYRO_SENSITIVITY GAIN_UNIT_G +#elif CONFIG_ISM330DHCX_GYRO_FS == 125 + #define ISM330DHCX_DEFAULT_GYRO_FULLSCALE 4 + #define ISM330DHCX_DEFAULT_GYRO_SENSITIVITY GAIN_UNIT_G +#elif CONFIG_ISM330DHCX_GYRO_FS == 250 + #define ISM330DHCX_DEFAULT_GYRO_FULLSCALE 0 + #define ISM330DHCX_DEFAULT_GYRO_SENSITIVITY (2.0 * GAIN_UNIT_G) +#elif CONFIG_ISM330DHCX_GYRO_FS == 500 + #define ISM330DHCX_DEFAULT_GYRO_FULLSCALE 1 + #define ISM330DHCX_DEFAULT_GYRO_SENSITIVITY (4.0 * GAIN_UNIT_G) +#elif CONFIG_ISM330DHCX_GYRO_FS == 1000 + #define ISM330DHCX_DEFAULT_GYRO_FULLSCALE 2 + #define ISM330DHCX_DEFAULT_GYRO_SENSITIVITY (8.0 * GAIN_UNIT_G) +#elif CONFIG_ISM330DHCX_GYRO_FS == 2000 + #define ISM330DHCX_DEFAULT_GYRO_FULLSCALE 3 + #define ISM330DHCX_DEFAULT_GYRO_SENSITIVITY (16.0 * GAIN_UNIT_G) +#endif + + +#if (CONFIG_ISM330DHCX_GYRO_ODR == 0) +#define ISM330DHCX_GYRO_ODR_RUNTIME 1 +#endif + +struct ism330dhcx_config { + char *bus_name; + int (*bus_init)(struct device *dev); +#ifdef CONFIG_ISM330DHCX_TRIGGER + const char *int_gpio_port; + u8_t int_gpio_pin; + u8_t int_gpio_flags; + u8_t int_pin; +#endif /* CONFIG_ISM330DHCX_TRIGGER */ +#ifdef DT_ST_ISM330DHCX_BUS_I2C + u16_t i2c_slv_addr; +#elif DT_ST_ISM330DHCX_BUS_SPI + struct spi_config spi_conf; +#if defined(DT_INST_0_ST_ISM330DHCX_CS_GPIOS_CONTROLLER) + const char *gpio_cs_port; + u8_t cs_gpio; +#endif /* DT_INST_0_ST_ISM330DHCX_CS_GPIOS_CONTROLLER */ +#endif /* DT_ST_ISM330DHCX_BUS_I2C */ +}; + +union samples { + u8_t raw[6]; + struct { + s16_t axis[3]; + }; +} __aligned(2); + +/* sensor data forward declaration (member definition is below) */ +struct ism330dhcx_data; + +struct ism330dhcx_tf { + int (*read_data)(struct ism330dhcx_data *data, u8_t reg_addr, + u8_t *value, u8_t len); + int (*write_data)(struct ism330dhcx_data *data, u8_t reg_addr, + u8_t *value, u8_t len); + int (*read_reg)(struct ism330dhcx_data *data, u8_t reg_addr, + u8_t *value); + int (*write_reg)(struct ism330dhcx_data *data, u8_t reg_addr, + u8_t value); + int (*update_reg)(struct ism330dhcx_data *data, u8_t reg_addr, + u8_t mask, u8_t value); +}; + +#define ISM330DHCX_SHUB_MAX_NUM_SLVS 2 + +struct ism330dhcx_data { + struct device *bus; + s16_t acc[3]; + u32_t acc_gain; + s16_t gyro[3]; + u32_t gyro_gain; +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) + int temp_sample; +#endif +#if defined(CONFIG_ISM330DHCX_SENSORHUB) + u8_t ext_data[2][6]; + u16_t magn_gain; + + struct hts221_data { + s16_t x0; + s16_t x1; + s16_t y0; + s16_t y1; + } hts221; +#endif /* CONFIG_ISM330DHCX_SENSORHUB */ + + stmdev_ctx_t *ctx; + + #ifdef DT_ST_ISM330DHCX_BUS_I2C + stmdev_ctx_t ctx_i2c; + #elif DT_ST_ISM330DHCX_BUS_SPI + stmdev_ctx_t ctx_spi; + #endif + + u16_t accel_freq; + u8_t accel_fs; + u16_t gyro_freq; + u8_t gyro_fs; + +#ifdef CONFIG_ISM330DHCX_TRIGGER + struct device *gpio; + struct gpio_callback gpio_cb; + sensor_trigger_handler_t handler_drdy_acc; + sensor_trigger_handler_t handler_drdy_gyr; + sensor_trigger_handler_t handler_drdy_temp; + +#if defined(CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD) + K_THREAD_STACK_MEMBER(thread_stack, CONFIG_ISM330DHCX_THREAD_STACK_SIZE); + struct k_thread thread; + struct k_sem gpio_sem; +#elif defined(CONFIG_ISM330DHCX_TRIGGER_GLOBAL_THREAD) + struct k_work work; + struct device *dev; +#endif +#endif /* CONFIG_ISM330DHCX_TRIGGER */ + +#if defined(DT_INST_0_ST_ISM330DHCX_CS_GPIOS_CONTROLLER) + struct spi_cs_control cs_ctrl; +#endif +}; + +int ism330dhcx_spi_init(struct device *dev); +int ism330dhcx_i2c_init(struct device *dev); +#if defined(CONFIG_ISM330DHCX_SENSORHUB) +int ism330dhcx_shub_init(struct device *dev); +int ism330dhcx_shub_fetch_external_devs(struct device *dev); +int ism330dhcx_shub_get_idx(enum sensor_channel type); +int ism330dhcx_shub_config(struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val); +#endif /* CONFIG_ISM330DHCX_SENSORHUB */ + +#ifdef CONFIG_ISM330DHCX_TRIGGER +int ism330dhcx_trigger_set(struct device *dev, + const struct sensor_trigger *trig, + sensor_trigger_handler_t handler); + +int ism330dhcx_init_interrupt(struct device *dev); +#endif + +#endif /* ZEPHYR_DRIVERS_SENSOR_ISM330DHCX_ISM330DHCX_H_ */ diff --git a/drivers/sensor/ism330dhcx/ism330dhcx_i2c.c b/drivers/sensor/ism330dhcx/ism330dhcx_i2c.c new file mode 100644 index 00000000000..c77acd457bc --- /dev/null +++ b/drivers/sensor/ism330dhcx/ism330dhcx_i2c.c @@ -0,0 +1,53 @@ +/* ST Microelectronics ISM330DHCX 6-axis IMU sensor driver + * + * Copyright (c) 2020 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + * + * Datasheet: + * https://www.st.com/resource/en/datasheet/ism330dhcx.pdf + */ + +#include +#include +#include + +#include "ism330dhcx.h" + +#ifdef DT_ST_ISM330DHCX_BUS_I2C + +LOG_MODULE_DECLARE(ISM330DHCX, CONFIG_SENSOR_LOG_LEVEL); + +static int ism330dhcx_i2c_read(struct device *dev, u8_t reg_addr, + u8_t *value, u8_t len) +{ + struct ism330dhcx_data *data = dev->driver_data; + const struct ism330dhcx_config *cfg = dev->config->config_info; + + return i2c_burst_read(data->bus, cfg->i2c_slv_addr, + reg_addr, value, len); +} + +static int ism330dhcx_i2c_write(struct device *dev, u8_t reg_addr, + u8_t *value, u8_t len) +{ + struct ism330dhcx_data *data = dev->driver_data; + const struct ism330dhcx_config *cfg = dev->config->config_info; + + return i2c_burst_write(data->bus, cfg->i2c_slv_addr, + reg_addr, value, len); +} + +int ism330dhcx_i2c_init(struct device *dev) +{ + struct ism330dhcx_data *data = dev->driver_data; + + data->ctx_i2c.read_reg = (stmdev_read_ptr) ism330dhcx_i2c_read, + data->ctx_i2c.write_reg = (stmdev_write_ptr) ism330dhcx_i2c_write, + + data->ctx = &data->ctx_i2c; + data->ctx->handle = dev; + + return 0; +} +#endif /* DT_ST_ISM330DHCX_BUS_I2C */ diff --git a/drivers/sensor/ism330dhcx/ism330dhcx_shub.c b/drivers/sensor/ism330dhcx/ism330dhcx_shub.c new file mode 100644 index 00000000000..047afb617ba --- /dev/null +++ b/drivers/sensor/ism330dhcx/ism330dhcx_shub.c @@ -0,0 +1,789 @@ +/* ST Microelectronics ISM330DHCX 6-axis IMU sensor driver + * + * Copyright (c) 2020 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + * + * Datasheet: + * https://www.st.com/resource/en/datasheet/ism330dhcx.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ism330dhcx.h" + +LOG_MODULE_DECLARE(ISM330DHCX, CONFIG_SENSOR_LOG_LEVEL); + +#define ISM330DHCX_SHUB_DATA_OUT 0x02 + +#define ISM330DHCX_SHUB_SLV0_ADDR 0x15 +#define ISM330DHCX_SHUB_SLV0_SUBADDR 0x16 +#define ISM330DHCX_SHUB_SLV0_CONFIG 0x17 +#define ISM330DHCX_SHUB_SLV1_ADDR 0x18 +#define ISM330DHCX_SHUB_SLV1_SUBADDR 0x19 +#define ISM330DHCX_SHUB_SLV1_CONFIG 0x1A +#define ISM330DHCX_SHUB_SLV2_ADDR 0x1B +#define ISM330DHCX_SHUB_SLV2_SUBADDR 0x1C +#define ISM330DHCX_SHUB_SLV2_CONFIG 0x1D +#define ISM330DHCX_SHUB_SLV3_ADDR 0x1E +#define ISM330DHCX_SHUB_SLV3_SUBADDR 0x1F +#define ISM330DHCX_SHUB_SLV3_CONFIG 0x20 +#define ISM330DHCX_SHUB_SLV0_DATAWRITE 0x21 + +#define ISM330DHCX_SHUB_STATUS_MASTER 0x22 +#define ISM330DHCX_SHUB_STATUS_SLV0_NACK BIT(3) +#define ISM330DHCX_SHUB_STATUS_ENDOP BIT(0) + +#define ISM330DHCX_SHUB_SLVX_WRITE 0x0 +#define ISM330DHCX_SHUB_SLVX_READ 0x1 + +static u8_t num_ext_dev; +static u8_t shub_ext[ISM330DHCX_SHUB_MAX_NUM_SLVS]; + +static int ism330dhcx_shub_write_slave_reg(struct ism330dhcx_data *data, + u8_t slv_addr, u8_t slv_reg, + u8_t *value, u16_t len); +static int ism330dhcx_shub_read_slave_reg(struct ism330dhcx_data *data, + u8_t slv_addr, u8_t slv_reg, + u8_t *value, u16_t len); +static void ism330dhcx_shub_enable(struct ism330dhcx_data *data, u8_t enable); + +/* + * LIS2MDL magn device specific part + */ +#if defined(CONFIG_ISM330DHCX_EXT_LIS2MDL) || defined(CONFIG_ISM330DHCX_EXT_IIS2MDC) + +#define LIS2MDL_CFG_REG_A 0x60 +#define LIS2MDL_CFG_REG_B 0x61 +#define LIS2MDL_CFG_REG_C 0x62 +#define LIS2MDL_STATUS_REG 0x67 + +#define LIS2MDL_SW_RESET 0x20 +#define LIS2MDL_ODR_10HZ 0x00 +#define LIS2MDL_ODR_100HZ 0x0C +#define LIS2MDL_OFF_CANC 0x02 +#define LIS2MDL_SENSITIVITY 1500 + +static int ism330dhcx_lis2mdl_init(struct ism330dhcx_data *data, u8_t i2c_addr) +{ + u8_t mag_cfg[2]; + + data->magn_gain = LIS2MDL_SENSITIVITY; + + /* sw reset device */ + mag_cfg[0] = LIS2MDL_SW_RESET; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + LIS2MDL_CFG_REG_A, mag_cfg, 1); + + k_sleep(K_MSEC(10)); /* turn-on time in ms */ + + /* configure mag */ + mag_cfg[0] = LIS2MDL_ODR_10HZ; + mag_cfg[1] = LIS2MDL_OFF_CANC; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + LIS2MDL_CFG_REG_A, mag_cfg, 2); + + return 0; +} + +static const u16_t lis2mdl_map[] = {10, 20, 50, 100}; + +static int ism330dhcx_lis2mdl_odr_set(struct ism330dhcx_data *data, + u8_t i2c_addr, u16_t freq) +{ + u8_t odr, cfg; + + for (odr = 0; odr < ARRAY_SIZE(lis2mdl_map); odr++) { + if (freq == lis2mdl_map[odr]) { + break; + } + } + + if (odr == ARRAY_SIZE(lis2mdl_map)) { + LOG_DBG("shub: LIS2MDL freq val %d not supported.", freq); + return -ENOTSUP; + } + + cfg = (odr << 2); + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + LIS2MDL_CFG_REG_A, &cfg, 1); + + ism330dhcx_shub_enable(data, 1); + return 0; +} + +static int ism330dhcx_lis2mdl_conf(struct ism330dhcx_data *data, u8_t i2c_addr, + enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + switch (attr) { + case SENSOR_ATTR_SAMPLING_FREQUENCY: + return ism330dhcx_lis2mdl_odr_set(data, i2c_addr, val->val1); + default: + LOG_DBG("shub: LIS2MDL attribute not supported."); + return -ENOTSUP; + } + + return 0; +} +#endif /* CONFIG_ISM330DHCX_EXT_LIS2MDL || CONFIG_ISM330DHCX_EXT_IIS2MDC */ + +/* + * HTS221 humidity device specific part + */ +#ifdef CONFIG_ISM330DHCX_EXT_HTS221 + +#define HTS221_AUTOINCREMENT BIT(7) + +#define HTS221_REG_CTRL1 0x20 +#define HTS221_ODR_1HZ 0x01 +#define HTS221_BDU 0x04 +#define HTS221_PD 0x80 + +#define HTS221_REG_CONV_START 0x30 + +static int lsmdso_hts221_read_conv_data(struct ism330dhcx_data *data, + u8_t i2c_addr) +{ + u8_t buf[16], i; + struct hts221_data *ht = &data->hts221; + + for (i = 0; i < sizeof(buf); i += 7) { + unsigned char len = MIN(7, sizeof(buf) - i); + + if (ism330dhcx_shub_read_slave_reg(data, i2c_addr, + (HTS221_REG_CONV_START + i) | + HTS221_AUTOINCREMENT, + &buf[i], len) < 0) { + LOG_DBG("shub: failed to read hts221 conv data"); + return -EIO; + } + } + + ht->y0 = buf[0] / 2; + ht->y1 = buf[1] / 2; + ht->x0 = sys_le16_to_cpu(buf[6] | (buf[7] << 8)); + ht->x1 = sys_le16_to_cpu(buf[10] | (buf[11] << 8)); + + return 0; +} + +static int ism330dhcx_hts221_init(struct ism330dhcx_data *data, u8_t i2c_addr) +{ + u8_t hum_cfg; + + /* configure ODR and BDU */ + hum_cfg = HTS221_ODR_1HZ | HTS221_BDU | HTS221_PD; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + HTS221_REG_CTRL1, &hum_cfg, 1); + + return lsmdso_hts221_read_conv_data(data, i2c_addr); +} + +static const u16_t hts221_map[] = {0, 1, 7, 12}; + +static int ism330dhcx_hts221_odr_set(struct ism330dhcx_data *data, + u8_t i2c_addr, u16_t freq) +{ + u8_t odr, cfg; + + for (odr = 0; odr < ARRAY_SIZE(hts221_map); odr++) { + if (freq == hts221_map[odr]) { + break; + } + } + + if (odr == ARRAY_SIZE(hts221_map)) { + LOG_DBG("shub: HTS221 freq val %d not supported.", freq); + return -ENOTSUP; + } + + cfg = odr | HTS221_BDU | HTS221_PD; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + HTS221_REG_CTRL1, &cfg, 1); + + ism330dhcx_shub_enable(data, 1); + return 0; +} + +static int ism330dhcx_hts221_conf(struct ism330dhcx_data *data, u8_t i2c_addr, + enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + switch (attr) { + case SENSOR_ATTR_SAMPLING_FREQUENCY: + return ism330dhcx_hts221_odr_set(data, i2c_addr, val->val1); + default: + LOG_DBG("shub: HTS221 attribute not supported."); + return -ENOTSUP; + } + + return 0; +} +#endif /* CONFIG_ISM330DHCX_EXT_HTS221 */ + +/* + * LPS22HB baro/temp device specific part + */ +#ifdef CONFIG_ISM330DHCX_EXT_LPS22HB + +#define LPS22HB_CTRL_REG1 0x10 +#define LPS22HB_CTRL_REG2 0x11 + +#define LPS22HB_SW_RESET 0x04 +#define LPS22HB_ODR_10HZ 0x20 +#define LPS22HB_LPF_EN 0x08 +#define LPS22HB_BDU_EN 0x02 + +static int ism330dhcx_lps22hb_init(struct ism330dhcx_data *data, u8_t i2c_addr) +{ + u8_t baro_cfg[2]; + + /* sw reset device */ + baro_cfg[0] = LPS22HB_SW_RESET; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + LPS22HB_CTRL_REG2, baro_cfg, 1); + + k_sleep(K_MSEC(1)); /* turn-on time in ms */ + + /* configure device */ + baro_cfg[0] = LPS22HB_ODR_10HZ | LPS22HB_LPF_EN | LPS22HB_BDU_EN; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + LPS22HB_CTRL_REG1, baro_cfg, 1); + + return 0; +} +#endif /* CONFIG_ISM330DHCX_EXT_LPS22HB */ + +/* + * LPS22HH baro/temp device specific part + */ +#ifdef CONFIG_ISM330DHCX_EXT_LPS22HH + +#define LPS22HH_CTRL_REG1 0x10 +#define LPS22HH_CTRL_REG2 0x11 + +#define LPS22HH_SW_RESET 0x04 +#define LPS22HH_IF_ADD_INC 0x10 +#define LPS22HH_ODR_10HZ 0x20 +#define LPS22HH_LPF_EN 0x08 +#define LPS22HH_BDU_EN 0x02 + +static int ism330dhcx_lps22hh_init(struct ism330dhcx_data *data, u8_t i2c_addr) +{ + u8_t baro_cfg[2]; + + /* sw reset device */ + baro_cfg[0] = LPS22HH_SW_RESET; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + LPS22HH_CTRL_REG2, baro_cfg, 1); + + k_sleep(K_MSEC(100)); /* turn-on time in ms */ + + /* configure device */ + baro_cfg[0] = LPS22HH_IF_ADD_INC; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + LPS22HH_CTRL_REG2, baro_cfg, 1); + + baro_cfg[0] = LPS22HH_ODR_10HZ | LPS22HH_LPF_EN | LPS22HH_BDU_EN; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + LPS22HH_CTRL_REG1, baro_cfg, 1); + + return 0; +} + +static const u16_t lps22hh_map[] = {0, 1, 10, 25, 50, 75, 100, 200}; + +static int ism330dhcx_lps22hh_odr_set(struct ism330dhcx_data *data, + u8_t i2c_addr, u16_t freq) +{ + u8_t odr, cfg; + + for (odr = 0; odr < ARRAY_SIZE(lps22hh_map); odr++) { + if (freq == lps22hh_map[odr]) { + break; + } + } + + if (odr == ARRAY_SIZE(lps22hh_map)) { + LOG_DBG("shub: LPS22HH freq val %d not supported.", freq); + return -ENOTSUP; + } + + cfg = (odr << 4) | LPS22HH_LPF_EN | LPS22HH_BDU_EN; + ism330dhcx_shub_write_slave_reg(data, i2c_addr, + LPS22HH_CTRL_REG1, &cfg, 1); + + ism330dhcx_shub_enable(data, 1); + return 0; +} + +static int ism330dhcx_lps22hh_conf(struct ism330dhcx_data *data, u8_t i2c_addr, + enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + switch (attr) { + case SENSOR_ATTR_SAMPLING_FREQUENCY: + return ism330dhcx_lps22hh_odr_set(data, i2c_addr, val->val1); + default: + LOG_DBG("shub: LPS22HH attribute not supported."); + return -ENOTSUP; + } + + return 0; +} +#endif /* CONFIG_ISM330DHCX_EXT_LPS22HH */ + +/* List of supported external sensors */ +static struct ism330dhcx_shub_slist { + enum sensor_channel type; + u8_t i2c_addr[2]; + u8_t ext_i2c_addr; + u8_t wai_addr; + u8_t wai_val; + u8_t out_data_addr; + u8_t out_data_len; + u8_t sh_out_reg; + int (*dev_init)(struct ism330dhcx_data *data, u8_t i2c_addr); + int (*dev_conf)(struct ism330dhcx_data *data, u8_t i2c_addr, + enum sensor_channel chan, enum sensor_attribute attr, + const struct sensor_value *val); +} ism330dhcx_shub_slist[] = { +#if defined(CONFIG_ISM330DHCX_EXT_LIS2MDL) || defined(CONFIG_ISM330DHCX_EXT_IIS2MDC) + { + /* LIS2MDL */ + .type = SENSOR_CHAN_MAGN_XYZ, + .i2c_addr = { 0x1E }, + .wai_addr = 0x4F, + .wai_val = 0x40, + .out_data_addr = 0x68, + .out_data_len = 0x06, + .dev_init = (ism330dhcx_lis2mdl_init), + .dev_conf = (ism330dhcx_lis2mdl_conf), + }, +#endif /* CONFIG_ISM330DHCX_EXT_LIS2MDL || CONFIG_ISM330DHCX_EXT_IIS2MDC */ + +#ifdef CONFIG_ISM330DHCX_EXT_HTS221 + { + /* HTS221 */ + .type = SENSOR_CHAN_HUMIDITY, + .i2c_addr = { 0x5F }, + .wai_addr = 0x0F, + .wai_val = 0xBC, + .out_data_addr = 0x28 | HTS221_AUTOINCREMENT, + .out_data_len = 0x02, + .dev_init = (ism330dhcx_hts221_init), + .dev_conf = (ism330dhcx_hts221_conf), + }, +#endif /* CONFIG_ISM330DHCX_EXT_HTS221 */ + +#ifdef CONFIG_ISM330DHCX_EXT_LPS22HB + { + /* LPS22HB */ + .type = SENSOR_CHAN_PRESS, + .i2c_addr = { 0x5C, 0x5D }, + .wai_addr = 0x0F, + .wai_val = 0xB1, + .out_data_addr = 0x28, + .out_data_len = 0x05, + .dev_init = (ism330dhcx_lps22hb_init), + }, +#endif /* CONFIG_ISM330DHCX_EXT_LPS22HB */ + +#ifdef CONFIG_ISM330DHCX_EXT_LPS22HH + { + /* LPS22HH */ + .type = SENSOR_CHAN_PRESS, + .i2c_addr = { 0x5C, 0x5D }, + .wai_addr = 0x0F, + .wai_val = 0xB3, + .out_data_addr = 0x28, + .out_data_len = 0x05, + .dev_init = (ism330dhcx_lps22hh_init), + .dev_conf = (ism330dhcx_lps22hh_conf), + }, +#endif /* CONFIG_ISM330DHCX_EXT_LPS22HH */ +}; + +static inline void ism330dhcx_shub_wait_completed(struct ism330dhcx_data *data) +{ + u16_t freq; + + freq = (data->accel_freq == 0) ? 26 : data->accel_freq; + k_sleep((2000U / freq) + 1); +} + +static inline void ism330dhcx_shub_embedded_en(struct ism330dhcx_data *data, bool on) +{ + if (on) { + (void) ism330dhcx_mem_bank_set(data->ctx, ISM330DHCX_SENSOR_HUB_BANK); + } else { + (void) ism330dhcx_mem_bank_set(data->ctx, ISM330DHCX_USER_BANK); + } + + k_busy_wait(150); +} + +static int ism330dhcx_shub_read_embedded_regs(struct ism330dhcx_data *data, + u8_t reg_addr, + u8_t *value, int len) +{ + ism330dhcx_shub_embedded_en(data, true); + + if (ism330dhcx_read_reg(data->ctx, reg_addr, value, len) < 0) { + LOG_DBG("shub: failed to read external reg: %02x", reg_addr); + ism330dhcx_shub_embedded_en(data, false); + return -EIO; + } + + ism330dhcx_shub_embedded_en(data, false); + + return 0; +} + +static int ism330dhcx_shub_write_embedded_regs(struct ism330dhcx_data *data, + u8_t reg_addr, + u8_t *value, u8_t len) +{ + ism330dhcx_shub_embedded_en(data, true); + + if (ism330dhcx_write_reg(data->ctx, reg_addr, value, len) < 0) { + LOG_DBG("shub: failed to write external reg: %02x", reg_addr); + ism330dhcx_shub_embedded_en(data, false); + return -EIO; + } + + ism330dhcx_shub_embedded_en(data, false); + + return 0; +} + +static void ism330dhcx_shub_enable(struct ism330dhcx_data *data, u8_t enable) +{ + /* Enable Accel @26hz */ + if (!data->accel_freq) { + u8_t odr = (enable) ? 2 : 0; + + if (ism330dhcx_xl_data_rate_set(data->ctx, odr) < 0) { + LOG_DBG("shub: failed to set XL sampling rate"); + return; + } + } + + ism330dhcx_shub_embedded_en(data, true); + + if (ism330dhcx_sh_master_set(data->ctx, enable) < 0) { + LOG_DBG("shub: failed to set master on"); + ism330dhcx_shub_embedded_en(data, false); + return; + } + + ism330dhcx_shub_embedded_en(data, false); +} + +/* must be called with master on */ +static int ism330dhcx_shub_check_slv0_nack(struct ism330dhcx_data *data) +{ + u8_t status; + + if (ism330dhcx_shub_read_embedded_regs(data, ISM330DHCX_SHUB_STATUS_MASTER, + &status, 1) < 0) { + LOG_DBG("shub: error reading embedded reg"); + return -EIO; + } + + if (status & (ISM330DHCX_SHUB_STATUS_SLV0_NACK)) { + LOG_DBG("shub: SLV0 nacked"); + return -EIO; + } + + return 0; +} + +/* + * use SLV0 for generic read to slave device + */ +static int ism330dhcx_shub_read_slave_reg(struct ism330dhcx_data *data, + u8_t slv_addr, u8_t slv_reg, + u8_t *value, u16_t len) +{ + u8_t slave[3]; + + slave[0] = (slv_addr << 1) | ISM330DHCX_SHUB_SLVX_READ; + slave[1] = slv_reg; + slave[2] = (len & 0x7); + + if (ism330dhcx_shub_write_embedded_regs(data, ISM330DHCX_SHUB_SLV0_ADDR, + slave, 3) < 0) { + LOG_DBG("shub: error writing embedded reg"); + return -EIO; + } + + /* turn SH on */ + ism330dhcx_shub_enable(data, 1); + ism330dhcx_shub_wait_completed(data); + + if (ism330dhcx_shub_check_slv0_nack(data) < 0) { + ism330dhcx_shub_enable(data, 0); + return -EIO; + } + + /* read data from external slave */ + ism330dhcx_shub_embedded_en(data, true); + if (ism330dhcx_read_reg(data->ctx, ISM330DHCX_SHUB_DATA_OUT, + value, len) < 0) { + LOG_DBG("shub: error reading sensor data"); + ism330dhcx_shub_embedded_en(data, false); + return -EIO; + } + ism330dhcx_shub_embedded_en(data, false); + + ism330dhcx_shub_enable(data, 0); + return 0; +} + +/* + * use SLV0 to configure slave device + */ +static int ism330dhcx_shub_write_slave_reg(struct ism330dhcx_data *data, + u8_t slv_addr, u8_t slv_reg, + u8_t *value, u16_t len) +{ + u8_t slv_cfg[3]; + u8_t cnt = 0U; + + while (cnt < len) { + slv_cfg[0] = (slv_addr << 1) & ~ISM330DHCX_SHUB_SLVX_READ; + slv_cfg[1] = slv_reg + cnt; + + if (ism330dhcx_shub_write_embedded_regs(data, + ISM330DHCX_SHUB_SLV0_ADDR, + slv_cfg, 2) < 0) { + LOG_DBG("shub: error writing embedded reg"); + return -EIO; + } + + slv_cfg[0] = value[cnt]; + if (ism330dhcx_shub_write_embedded_regs(data, + ISM330DHCX_SHUB_SLV0_DATAWRITE, + slv_cfg, 1) < 0) { + LOG_DBG("shub: error writing embedded reg"); + return -EIO; + } + + /* turn SH on */ + ism330dhcx_shub_enable(data, 1); + ism330dhcx_shub_wait_completed(data); + + if (ism330dhcx_shub_check_slv0_nack(data) < 0) { + ism330dhcx_shub_enable(data, 0); + return -EIO; + } + + ism330dhcx_shub_enable(data, 0); + + cnt++; + } + + /* Put SLV0 in IDLE mode */ + slv_cfg[0] = 0x7; + slv_cfg[1] = 0x0; + slv_cfg[2] = 0x0; + if (ism330dhcx_shub_write_embedded_regs(data, ISM330DHCX_SHUB_SLV0_ADDR, + slv_cfg, 3) < 0) { + LOG_DBG("shub: error writing embedded reg"); + return -EIO; + } + + return 0; +} + +/* + * SLAVEs configurations: + * + * - SLAVE 0: used for configuring all slave devices + * - SLAVE 1: used as data read channel for external slave device #1 + * - SLAVE 2: used as data read channel for external slave device #2 + * - SLAVE 3: used for generic reads while data channel is enabled + */ +static int ism330dhcx_shub_set_data_channel(struct ism330dhcx_data *data) +{ + u8_t n, i, slv_cfg[6]; + struct ism330dhcx_shub_slist *sp; + + /* Set data channel for slave devices */ + for (n = 0; n < num_ext_dev; n++) { + sp = &ism330dhcx_shub_slist[shub_ext[n]]; + + i = n * 3; + slv_cfg[i] = (sp->ext_i2c_addr << 1) | ISM330DHCX_SHUB_SLVX_READ; + slv_cfg[i + 1] = sp->out_data_addr; + slv_cfg[i + 2] = sp->out_data_len; + } + + if (ism330dhcx_shub_write_embedded_regs(data, + ISM330DHCX_SHUB_SLV1_ADDR, + slv_cfg, n*3) < 0) { + LOG_DBG("shub: error writing embedded reg"); + return -EIO; + } + + /* Configure the master */ + ism330dhcx_aux_sens_on_t aux = ISM330DHCX_SLV_0_1_2; + + if (ism330dhcx_sh_slave_connected_set(data->ctx, aux) < 0) { + LOG_DBG("shub: error setting aux sensors"); + return -EIO; + } + + ism330dhcx_write_once_t wo = ISM330DHCX_ONLY_FIRST_CYCLE; + + if (ism330dhcx_sh_write_mode_set(data->ctx, wo) < 0) { + LOG_DBG("shub: error setting write once"); + return -EIO; + } + + + /* turn SH on */ + ism330dhcx_shub_enable(data, 1); + ism330dhcx_shub_wait_completed(data); + + return 0; +} + +int ism330dhcx_shub_get_idx(enum sensor_channel type) +{ + u8_t n; + struct ism330dhcx_shub_slist *sp; + + for (n = 0; n < num_ext_dev; n++) { + sp = &ism330dhcx_shub_slist[shub_ext[n]]; + + if (sp->type == type) + return n; + } + + return -ENOTSUP; +} + +int ism330dhcx_shub_fetch_external_devs(struct device *dev) +{ + u8_t n; + struct ism330dhcx_data *data = dev->driver_data; + struct ism330dhcx_shub_slist *sp; + + /* read data from external slave */ + ism330dhcx_shub_embedded_en(data, true); + + for (n = 0; n < num_ext_dev; n++) { + sp = &ism330dhcx_shub_slist[shub_ext[n]]; + + if (ism330dhcx_read_reg(data->ctx, sp->sh_out_reg, + data->ext_data[n], sp->out_data_len) < 0) { + LOG_DBG("shub: failed to read sample"); + ism330dhcx_shub_embedded_en(data, false); + return -EIO; + } + } + + ism330dhcx_shub_embedded_en(data, false); + + return 0; +} + +int ism330dhcx_shub_config(struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + struct ism330dhcx_data *data = dev->driver_data; + struct ism330dhcx_shub_slist *sp = NULL; + u8_t n; + + for (n = 0; n < num_ext_dev; n++) { + sp = &ism330dhcx_shub_slist[shub_ext[n]]; + + if (sp->type == chan) + break; + } + + if (n == num_ext_dev) { + LOG_DBG("shub: chan not supported"); + return -ENOTSUP; + } + + if (sp == NULL || sp->dev_conf == NULL) { + LOG_DBG("shub: chan not configurable"); + return -ENOTSUP; + } + + return sp->dev_conf(data, sp->ext_i2c_addr, chan, attr, val); +} + +int ism330dhcx_shub_init(struct device *dev) +{ + struct ism330dhcx_data *data = dev->driver_data; + u8_t i, n = 0, regn; + u8_t chip_id; + struct ism330dhcx_shub_slist *sp; + + for (n = 0; n < ARRAY_SIZE(ism330dhcx_shub_slist); n++) { + if (num_ext_dev >= ISM330DHCX_SHUB_MAX_NUM_SLVS) + break; + + chip_id = 0; + sp = &ism330dhcx_shub_slist[n]; + + /* + * The external sensor may have different I2C address. + * So, try them one by one until we read the correct + * chip ID. + */ + for (i = 0U; i < ARRAY_SIZE(sp->i2c_addr); i++) { + if (ism330dhcx_shub_read_slave_reg(data, + sp->i2c_addr[i], + sp->wai_addr, + &chip_id, 1) < 0) { + continue; + } + if (chip_id == sp->wai_val) { + break; + } + } + + if (i >= ARRAY_SIZE(sp->i2c_addr)) { + LOG_DBG("shub: invalid chip id 0x%x", chip_id); + continue; + } + LOG_INF("shub: Ext Device Chip Id: 0x%02x", chip_id); + sp->ext_i2c_addr = sp->i2c_addr[i]; + + shub_ext[num_ext_dev++] = n; + } + + if (num_ext_dev == 0) { + LOG_ERR("shub: no slave devices found"); + return -EINVAL; + } + + /* init external devices */ + for (n = 0, regn = 0; n < num_ext_dev; n++) { + sp = &ism330dhcx_shub_slist[shub_ext[n]]; + sp->sh_out_reg = ISM330DHCX_SHUB_DATA_OUT + regn; + regn += sp->out_data_len; + sp->dev_init(data, sp->ext_i2c_addr); + } + + ism330dhcx_shub_set_data_channel(data); + + return 0; +} diff --git a/drivers/sensor/ism330dhcx/ism330dhcx_spi.c b/drivers/sensor/ism330dhcx/ism330dhcx_spi.c new file mode 100644 index 00000000000..41835084ca1 --- /dev/null +++ b/drivers/sensor/ism330dhcx/ism330dhcx_spi.c @@ -0,0 +1,126 @@ +/* ST Microelectronics ISM330DHCX 6-axis IMU sensor driver + * + * Copyright (c) 2020 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + * + * Datasheet: + * https://www.st.com/resource/en/datasheet/ism330dhcx.pdf + */ + +#include +#include "ism330dhcx.h" +#include + +#ifdef DT_ST_ISM330DHCX_BUS_SPI + +#define ISM330DHCX_SPI_READ (1 << 7) + +LOG_MODULE_DECLARE(ISM330DHCX, CONFIG_SENSOR_LOG_LEVEL); + +static int ism330dhcx_spi_read(struct device *dev, u8_t reg_addr, + u8_t *value, u8_t len) +{ + struct ism330dhcx_data *data = dev->driver_data; + const struct ism330dhcx_config *cfg = dev->config->config_info; + const struct spi_config *spi_cfg = &cfg->spi_conf; + u8_t buffer_tx[2] = { reg_addr | ISM330DHCX_SPI_READ, 0 }; + const struct spi_buf tx_buf = { + .buf = buffer_tx, + .len = 2, + }; + const struct spi_buf_set tx = { + .buffers = &tx_buf, + .count = 1 + }; + const struct spi_buf rx_buf[2] = { + { + .buf = NULL, + .len = 1, + }, + { + .buf = value, + .len = len, + } + }; + const struct spi_buf_set rx = { + .buffers = rx_buf, + .count = 2 + }; + + + if (len > 64) { + return -EIO; + } + + if (spi_transceive(data->bus, spi_cfg, &tx, &rx)) { + return -EIO; + } + + return 0; +} + +static int ism330dhcx_spi_write(struct device *dev, u8_t reg_addr, + u8_t *value, u8_t len) +{ + struct ism330dhcx_data *data = dev->driver_data; + const struct ism330dhcx_config *cfg = dev->config->config_info; + const struct spi_config *spi_cfg = &cfg->spi_conf; + u8_t buffer_tx[1] = { reg_addr & ~ISM330DHCX_SPI_READ }; + const struct spi_buf tx_buf[2] = { + { + .buf = buffer_tx, + .len = 1, + }, + { + .buf = value, + .len = len, + } + }; + const struct spi_buf_set tx = { + .buffers = tx_buf, + .count = 2 + }; + + + if (len > 64) { + return -EIO; + } + + if (spi_write(data->bus, spi_cfg, &tx)) { + return -EIO; + } + + return 0; +} + +int ism330dhcx_spi_init(struct device *dev) +{ + struct ism330dhcx_data *data = dev->driver_data; + + data->ctx_spi.read_reg = (stmdev_read_ptr) ism330dhcx_spi_read, + data->ctx_spi.write_reg = (stmdev_write_ptr) ism330dhcx_spi_write, + + data->ctx = &data->ctx_spi; + data->ctx->handle = dev; + +#if defined(DT_INST_0_ST_ISM330DHCX_CS_GPIOS_CONTROLLER) + const struct ism330dhcx_config *cfg = dev->config->config_info; + + /* handle SPI CS thru GPIO if it is the case */ + data->cs_ctrl.gpio_dev = device_get_binding(cfg->gpio_cs_port); + if (!data->cs_ctrl.gpio_dev) { + LOG_ERR("Unable to get GPIO SPI CS device"); + return -ENODEV; + } + + data->cs_ctrl.gpio_pin = cfg->cs_gpio; + data->cs_ctrl.delay = 0; + + LOG_DBG("SPI GPIO CS configured on %s:%u", + cfg->gpio_cs_port, cfg->cs_gpio); +#endif + + return 0; +} +#endif /* DT_ST_ISM330DHCX_BUS_SPI */ diff --git a/drivers/sensor/ism330dhcx/ism330dhcx_trigger.c b/drivers/sensor/ism330dhcx/ism330dhcx_trigger.c new file mode 100644 index 00000000000..7a99d9d8160 --- /dev/null +++ b/drivers/sensor/ism330dhcx/ism330dhcx_trigger.c @@ -0,0 +1,300 @@ +/* ST Microelectronics ISM330DHCX 6-axis IMU sensor driver + * + * Copyright (c) 2020 STMicroelectronics + * + * SPDX-License-Identifier: Apache-2.0 + * + * Datasheet: + * https://www.st.com/resource/en/datasheet/ism330dhcx.pdf + */ + +#include +#include +#include +#include + +#include "ism330dhcx.h" + +LOG_MODULE_DECLARE(ISM330DHCX, CONFIG_SENSOR_LOG_LEVEL); + +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) +/** + * ism330dhcx_enable_t_int - TEMP enable selected int pin to generate interrupt + */ +static int ism330dhcx_enable_t_int(struct device *dev, int enable) +{ + const struct ism330dhcx_config *cfg = dev->config->config_info; + struct ism330dhcx_data *ism330dhcx = dev->driver_data; + ism330dhcx_pin_int2_route_t int2_route; + + if (enable) { + union axis1bit16_t buf; + + /* dummy read: re-trigger interrupt */ + ism330dhcx_temperature_raw_get(ism330dhcx->ctx, buf.u8bit); + } + + /* set interrupt (TEMP DRDY interrupt is only on INT2) */ + if (cfg->int_pin == 1) + return -EIO; + + ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL, + (uint8_t *)&int2_route.int2_ctrl, 1); + int2_route.int2_ctrl.int2_drdy_temp = enable; + return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL, + (uint8_t *)&int2_route.int2_ctrl, 1); +} +#endif + +/** + * ism330dhcx_enable_xl_int - XL enable selected int pin to generate interrupt + */ +static int ism330dhcx_enable_xl_int(struct device *dev, int enable) +{ + const struct ism330dhcx_config *cfg = dev->config->config_info; + struct ism330dhcx_data *ism330dhcx = dev->driver_data; + + if (enable) { + union axis3bit16_t buf; + + /* dummy read: re-trigger interrupt */ + ism330dhcx_acceleration_raw_get(ism330dhcx->ctx, buf.u8bit); + } + + /* set interrupt */ + if (cfg->int_pin == 1) { + ism330dhcx_pin_int1_route_t int1_route; + + ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT1_CTRL, + (uint8_t *)&int1_route.int1_ctrl, 1); + + int1_route.int1_ctrl.int1_drdy_xl = enable; + return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT1_CTRL, + (uint8_t *)&int1_route.int1_ctrl, 1); + } else { + ism330dhcx_pin_int2_route_t int2_route; + + ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL, + (uint8_t *)&int2_route.int2_ctrl, 1); + int2_route.int2_ctrl.int2_drdy_xl = enable; + return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL, + (uint8_t *)&int2_route.int2_ctrl, 1); + } +} + +/** + * ism330dhcx_enable_g_int - Gyro enable selected int pin to generate interrupt + */ +static int ism330dhcx_enable_g_int(struct device *dev, int enable) +{ + const struct ism330dhcx_config *cfg = dev->config->config_info; + struct ism330dhcx_data *ism330dhcx = dev->driver_data; + + if (enable) { + union axis3bit16_t buf; + + /* dummy read: re-trigger interrupt */ + ism330dhcx_angular_rate_raw_get(ism330dhcx->ctx, buf.u8bit); + } + + /* set interrupt */ + if (cfg->int_pin == 1) { + ism330dhcx_pin_int1_route_t int1_route; + + ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT1_CTRL, + (uint8_t *)&int1_route.int1_ctrl, 1); + int1_route.int1_ctrl.int1_drdy_g = enable; + return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT1_CTRL, + (uint8_t *)&int1_route.int1_ctrl, 1); + } else { + ism330dhcx_pin_int2_route_t int2_route; + + ism330dhcx_read_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL, + (uint8_t *)&int2_route.int2_ctrl, 1); + int2_route.int2_ctrl.int2_drdy_g = enable; + return ism330dhcx_write_reg(ism330dhcx->ctx, ISM330DHCX_INT2_CTRL, + (uint8_t *)&int2_route.int2_ctrl, 1); + } +} + +/** + * ism330dhcx_trigger_set - link external trigger to event data ready + */ +int ism330dhcx_trigger_set(struct device *dev, + const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) +{ + struct ism330dhcx_data *ism330dhcx = dev->driver_data; + + if (trig->chan == SENSOR_CHAN_ACCEL_XYZ) { + ism330dhcx->handler_drdy_acc = handler; + if (handler) { + return ism330dhcx_enable_xl_int(dev, ISM330DHCX_EN_BIT); + } else { + return ism330dhcx_enable_xl_int(dev, ISM330DHCX_DIS_BIT); + } + } else if (trig->chan == SENSOR_CHAN_GYRO_XYZ) { + ism330dhcx->handler_drdy_gyr = handler; + if (handler) { + return ism330dhcx_enable_g_int(dev, ISM330DHCX_EN_BIT); + } else { + return ism330dhcx_enable_g_int(dev, ISM330DHCX_DIS_BIT); + } + } +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) + else if (trig->chan == SENSOR_CHAN_DIE_TEMP) { + ism330dhcx->handler_drdy_temp = handler; + if (handler) { + return ism330dhcx_enable_t_int(dev, ISM330DHCX_EN_BIT); + } else { + return ism330dhcx_enable_t_int(dev, ISM330DHCX_DIS_BIT); + } + } +#endif + + return -ENOTSUP; +} + +/** + * ism330dhcx_handle_interrupt - handle the drdy event + * read data and call handler if registered any + */ +static void ism330dhcx_handle_interrupt(void *arg) +{ + struct device *dev = arg; + struct ism330dhcx_data *ism330dhcx = dev->driver_data; + struct sensor_trigger drdy_trigger = { + .type = SENSOR_TRIG_DATA_READY, + }; + const struct ism330dhcx_config *cfg = dev->config->config_info; + ism330dhcx_status_reg_t status; + + while (1) { + if (ism330dhcx_status_reg_get(ism330dhcx->ctx, &status) < 0) { + LOG_DBG("failed reading status reg"); + return; + } + + if ((status.xlda == 0) && (status.gda == 0) +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) + && (status.tda == 0) +#endif + ) { + break; + } + + if ((status.xlda) && (ism330dhcx->handler_drdy_acc != NULL)) { + ism330dhcx->handler_drdy_acc(dev, &drdy_trigger); + } + + if ((status.gda) && (ism330dhcx->handler_drdy_gyr != NULL)) { + ism330dhcx->handler_drdy_gyr(dev, &drdy_trigger); + } + +#if defined(CONFIG_ISM330DHCX_ENABLE_TEMP) + if ((status.tda) && (ism330dhcx->handler_drdy_temp != NULL)) { + ism330dhcx->handler_drdy_temp(dev, &drdy_trigger); + } +#endif + } + + gpio_pin_interrupt_configure(ism330dhcx->gpio, cfg->int_gpio_pin, + GPIO_INT_EDGE_TO_ACTIVE); +} + +static void ism330dhcx_gpio_callback(struct device *dev, + struct gpio_callback *cb, u32_t pins) +{ + struct ism330dhcx_data *ism330dhcx = + CONTAINER_OF(cb, struct ism330dhcx_data, gpio_cb); + const struct ism330dhcx_config *cfg = dev->config->config_info; + + ARG_UNUSED(pins); + + gpio_pin_interrupt_configure(ism330dhcx->gpio, cfg->int_gpio_pin, + GPIO_INT_DISABLE); + +#if defined(CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD) + k_sem_give(&ism330dhcx->gpio_sem); +#elif defined(CONFIG_ISM330DHCX_TRIGGER_GLOBAL_THREAD) + k_work_submit(&ism330dhcx->work); +#endif /* CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD */ +} + +#ifdef CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD +static void ism330dhcx_thread(int dev_ptr, int unused) +{ + struct device *dev = INT_TO_POINTER(dev_ptr); + struct ism330dhcx_data *ism330dhcx = dev->driver_data; + + ARG_UNUSED(unused); + + while (1) { + k_sem_take(&ism330dhcx->gpio_sem, K_FOREVER); + ism330dhcx_handle_interrupt(dev); + } +} +#endif /* CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD */ + +#ifdef CONFIG_ISM330DHCX_TRIGGER_GLOBAL_THREAD +static void ism330dhcx_work_cb(struct k_work *work) +{ + struct ism330dhcx_data *ism330dhcx = + CONTAINER_OF(work, struct ism330dhcx_data, work); + + ism330dhcx_handle_interrupt(ism330dhcx->dev); +} +#endif /* CONFIG_ISM330DHCX_TRIGGER_GLOBAL_THREAD */ + +int ism330dhcx_init_interrupt(struct device *dev) +{ + struct ism330dhcx_data *ism330dhcx = dev->driver_data; + const struct ism330dhcx_config *cfg = dev->config->config_info; + int ret; + + /* setup data ready gpio interrupt (INT1 or INT2) */ + ism330dhcx->gpio = device_get_binding(cfg->int_gpio_port); + if (ism330dhcx->gpio == NULL) { + LOG_ERR("Cannot get pointer to %s device", cfg->int_gpio_port); + return -EINVAL; + } + +#if defined(CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD) + k_sem_init(&ism330dhcx->gpio_sem, 0, UINT_MAX); + + k_thread_create(&ism330dhcx->thread, ism330dhcx->thread_stack, + CONFIG_ISM330DHCX_THREAD_STACK_SIZE, + (k_thread_entry_t)ism330dhcx_thread, dev, + 0, NULL, K_PRIO_COOP(CONFIG_ISM330DHCX_THREAD_PRIORITY), + 0, K_NO_WAIT); +#elif defined(CONFIG_ISM330DHCX_TRIGGER_GLOBAL_THREAD) + ism330dhcx->work.handler = ism330dhcx_work_cb; + ism330dhcx->dev = dev; +#endif /* CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD */ + + ret = gpio_pin_configure(ism330dhcx->gpio, cfg->int_gpio_pin, + GPIO_INPUT | cfg->int_gpio_flags); + if (ret < 0) { + LOG_ERR("Could not configure gpio"); + return ret; + } + + gpio_init_callback(&ism330dhcx->gpio_cb, + ism330dhcx_gpio_callback, + BIT(cfg->int_gpio_pin)); + + if (gpio_add_callback(ism330dhcx->gpio, &ism330dhcx->gpio_cb) < 0) { + LOG_ERR("Could not set gpio callback"); + return -EIO; + } + + /* enable interrupt on int1/int2 in pulse mode */ + if (ism330dhcx_int_notification_set(ism330dhcx->ctx, + ISM330DHCX_ALL_INT_PULSED) < 0) { + LOG_ERR("Could not set pulse mode"); + return -EIO; + } + + return gpio_pin_interrupt_configure(ism330dhcx->gpio, cfg->int_gpio_pin, + GPIO_INT_EDGE_TO_ACTIVE); +} diff --git a/dts/bindings/sensor/st,ism330dhcx-i2c.yaml b/dts/bindings/sensor/st,ism330dhcx-i2c.yaml new file mode 100644 index 00000000000..e3d9f17e5f2 --- /dev/null +++ b/dts/bindings/sensor/st,ism330dhcx-i2c.yaml @@ -0,0 +1,20 @@ +# Copyright (c) 2020 STMicroelectronics +# SPDX-License-Identifier: Apache-2.0 + +description: | + STMicroelectronics ISM330DHCX 6-axis IMU (Inertial Measurement Unit) sensor + accessed through I2C bus + +compatible: "st,ism330dhcx" + +include: i2c-device.yaml + +properties: + drdy-gpios: + type: phandle-array + required: false + description: DRDY pin + + This pin defaults to active high when produced by the sensor. + The property value should ensure the flags properly describe + the signal that is presented to the driver. diff --git a/dts/bindings/sensor/st,ism330dhcx-spi.yaml b/dts/bindings/sensor/st,ism330dhcx-spi.yaml new file mode 100644 index 00000000000..aa6805b65ce --- /dev/null +++ b/dts/bindings/sensor/st,ism330dhcx-spi.yaml @@ -0,0 +1,20 @@ +# Copyright (c) 2020 STMicroelectronics +# SPDX-License-Identifier: Apache-2.0 + +description: | + STMicroelectronics ISM330DHCX 6-axis IMU (Inertial Measurement Unit) sensor + accessed through SPI bus + +compatible: "st,ism330dhcx" + +include: spi-device.yaml + +properties: + drdy-gpios: + type: phandle-array + required: false + description: DRDY pin + + This pin defaults to active high when produced by the sensor. + The property value should ensure the flags properly describe + the signal that is presented to the driver. diff --git a/tests/drivers/build_all/dts_fixup.h b/tests/drivers/build_all/dts_fixup.h index 8963295d11e..13197700bb9 100644 --- a/tests/drivers/build_all/dts_fixup.h +++ b/tests/drivers/build_all/dts_fixup.h @@ -110,6 +110,16 @@ #define DT_INST_0_PANASONIC_AMG88XX_INT_GPIOS_PIN 0 #endif +#ifndef DT_INST_0_ST_ISM330DHCX_LABEL +#define DT_INST_0_ST_ISM330DHCX_LABEL "" +#define DT_INST_0_ST_ISM330DHCX_BUS_NAME "" +#define DT_INST_0_ST_ISM330DHCX_BASE_ADDRESS 0 +#define DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_FLAGS 0 +#define DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_PIN 0 +#define DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_CONTROLLER "" +#define DT_ST_ISM330DHCX_BUS_I2C 1 +#endif + #ifndef DT_INST_0_ST_HTS221_LABEL #define DT_INST_0_ST_HTS221_LABEL "" #define DT_INST_0_ST_HTS221_BUS_NAME "" @@ -506,6 +516,17 @@ #define DT_ST_IIS2DLPC_BUS_SPI 1 #endif +#ifndef DT_INST_0_ST_ISM330DHCX_LABEL +#define DT_INST_0_ST_ISM330DHCX_LABEL "" +#define DT_INST_0_ST_ISM330DHCX_BUS_NAME "" +#define DT_INST_0_ST_ISM330DHCX_SPI_MAX_FREQUENCY 100000 +#define DT_INST_0_ST_ISM330DHCX_BASE_ADDRESS 1 +#define DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_CONTROLLER "" +#define DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_FLAGS 0 +#define DT_INST_0_ST_ISM330DHCX_DRDY_GPIOS_PIN 0 +#define DT_ST_ISM330DHCX_BUS_SPI 1 +#endif + #ifndef DT_INST_0_ST_IIS3DHHC_LABEL #define DT_INST_0_ST_IIS3DHHC_LABEL "" #define DT_INST_0_ST_IIS3DHHC_BASE_ADDRESS 0 diff --git a/tests/drivers/build_all/sensors_stmemsc.conf b/tests/drivers/build_all/sensors_stmemsc.conf index cea8b6cb008..e901801b118 100644 --- a/tests/drivers/build_all/sensors_stmemsc.conf +++ b/tests/drivers/build_all/sensors_stmemsc.conf @@ -8,3 +8,4 @@ CONFIG_SENSOR=y CONFIG_IIS2DLPC=y CONFIG_LIS2DW12=y CONFIG_STTS751=y +CONFIG_ISM330DHCX=y diff --git a/tests/drivers/build_all/sensors_stmemsc_trigger.conf b/tests/drivers/build_all/sensors_stmemsc_trigger.conf index 99b257a3cb2..ccbb8f0f943 100644 --- a/tests/drivers/build_all/sensors_stmemsc_trigger.conf +++ b/tests/drivers/build_all/sensors_stmemsc_trigger.conf @@ -11,3 +11,5 @@ CONFIG_LIS2DW12=y CONFIG_LIS2DW12_TRIGGER_OWN_THREAD=y CONFIG_STTS751=y CONFIG_STTS751_TRIGGER_OWN_THREAD=y +CONFIG_ISM330DHCX=y +CONFIG_ISM330DHCX_TRIGGER_OWN_THREAD=y