| /* Bosch BMI08X inertial measurement unit driver |
| * |
| * Copyright (c) 2022 Meta Platforms, Inc. and its affiliates |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/init.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/sys/byteorder.h> |
| |
| #define DT_DRV_COMPAT bosch_bmi08x_gyro |
| #include "bmi08x.h" |
| |
| LOG_MODULE_REGISTER(BMI08X_GYRO, CONFIG_SENSOR_LOG_LEVEL); |
| |
| #if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) |
| |
| static int bmi08x_gyro_transceive_i2c(const struct device *dev, uint8_t reg, bool write, void *data, |
| size_t length) |
| { |
| const struct bmi08x_gyro_config *bmi08x = dev->config; |
| |
| if (!write) { |
| return i2c_write_read_dt(&bmi08x->bus.i2c, ®, 1, data, length); |
| } |
| if (length > CONFIG_BMI08X_I2C_WRITE_BURST_SIZE) { |
| return -EINVAL; |
| } |
| uint8_t buf[1 + CONFIG_BMI08X_I2C_WRITE_BURST_SIZE]; |
| |
| buf[0] = reg; |
| memcpy(&buf[1], data, length); |
| return i2c_write_dt(&bmi08x->bus.i2c, buf, 1 + length); |
| } |
| |
| static int bmi08x_bus_check_i2c(const union bmi08x_bus *bus) |
| { |
| return i2c_is_ready_dt(&bus->i2c) ? 0 : -ENODEV; |
| } |
| |
| static const struct bmi08x_gyro_bus_io bmi08x_i2c_api = { |
| .check = bmi08x_bus_check_i2c, |
| .transceive = bmi08x_gyro_transceive_i2c, |
| }; |
| |
| #endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */ |
| |
| #if DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) |
| |
| static int bmi08x_gyro_transceive_spi(const struct device *dev, uint8_t reg, bool write, void *data, |
| size_t length) |
| { |
| const struct bmi08x_gyro_config *bmi08x = dev->config; |
| const struct spi_buf tx_buf[2] = {{.buf = ®, .len = 1}, {.buf = data, .len = length}}; |
| const struct spi_buf_set tx = {.buffers = tx_buf, .count = write ? 2 : 1}; |
| |
| if (!write) { |
| uint16_t dummy; |
| const struct spi_buf rx_buf[2] = {{.buf = &dummy, .len = 1}, |
| {.buf = data, .len = length}}; |
| const struct spi_buf_set rx = {.buffers = rx_buf, .count = 2}; |
| |
| return spi_transceive_dt(&bmi08x->bus.spi, &tx, &rx); |
| } |
| |
| return spi_write_dt(&bmi08x->bus.spi, &tx); |
| } |
| |
| static int bmi08x_bus_check_spi(const union bmi08x_bus *bus) |
| { |
| return spi_is_ready_dt(&bus->spi) ? 0 : -ENODEV; |
| } |
| |
| static const struct bmi08x_gyro_bus_io bmi08x_spi_api = { |
| .check = bmi08x_bus_check_spi, |
| .transceive = bmi08x_gyro_transceive_spi, |
| }; |
| |
| #endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(spi) */ |
| |
| static inline int bmi08x_bus_check(const struct device *dev) |
| { |
| const struct bmi08x_gyro_config *config = dev->config; |
| |
| return config->api->check(&config->bus); |
| } |
| |
| static int bmi08x_gyro_transceive(const struct device *dev, uint8_t reg, bool write, void *data, |
| size_t length) |
| { |
| const struct bmi08x_gyro_config *cfg = dev->config; |
| |
| return cfg->api->transceive(dev, reg, write, data, length); |
| } |
| |
| int bmi08x_gyro_read(const struct device *dev, uint8_t reg_addr, uint8_t *data, uint8_t len) |
| { |
| return bmi08x_gyro_transceive(dev, reg_addr | BIT(7), false, data, len); |
| } |
| |
| int bmi08x_gyro_byte_read(const struct device *dev, uint8_t reg_addr, uint8_t *byte) |
| { |
| return bmi08x_gyro_transceive(dev, reg_addr | BIT(7), false, byte, 1); |
| } |
| |
| int bmi08x_gyro_byte_write(const struct device *dev, uint8_t reg_addr, uint8_t byte) |
| { |
| return bmi08x_gyro_transceive(dev, reg_addr & 0x7F, true, &byte, 1); |
| } |
| |
| int bmi08x_gyro_word_write(const struct device *dev, uint8_t reg_addr, uint16_t word) |
| { |
| uint8_t tx_word[2] = {(uint8_t)(word & 0xff), (uint8_t)(word >> 8)}; |
| |
| return bmi08x_gyro_transceive(dev, reg_addr & 0x7F, true, tx_word, 2); |
| } |
| |
| int bmi08x_gyro_reg_field_update(const struct device *dev, uint8_t reg_addr, uint8_t pos, |
| uint8_t mask, uint8_t val) |
| { |
| uint8_t old_val; |
| int ret; |
| |
| ret = bmi08x_gyro_byte_read(dev, reg_addr, &old_val); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| return bmi08x_gyro_byte_write(dev, reg_addr, (old_val & ~mask) | ((val << pos) & mask)); |
| } |
| |
| static const struct bmi08x_range bmi08x_gyr_range_map[] = { |
| {125, BMI08X_GYR_RANGE_125DPS}, {250, BMI08X_GYR_RANGE_250DPS}, |
| {500, BMI08X_GYR_RANGE_500DPS}, {1000, BMI08X_GYR_RANGE_1000DPS}, |
| {2000, BMI08X_GYR_RANGE_2000DPS}, |
| }; |
| #define BMI08X_GYR_RANGE_MAP_SIZE ARRAY_SIZE(bmi08x_gyr_range_map) |
| |
| int32_t bmi08x_gyr_reg_val_to_range(uint8_t reg_val) |
| { |
| return bmi08x_reg_val_to_range(reg_val, bmi08x_gyr_range_map, BMI08X_GYR_RANGE_MAP_SIZE); |
| } |
| |
| static int bmi08x_gyr_odr_set(const struct device *dev, uint16_t freq_int, uint16_t freq_milli) |
| { |
| int odr = bmi08x_freq_to_odr_val(freq_int, freq_milli); |
| |
| if (odr < 0) { |
| return odr; |
| } |
| |
| if (odr < BMI08X_GYRO_BW_532_ODR_2000_HZ || odr > BMI08X_GYRO_BW_32_ODR_100_HZ) { |
| return -ENOTSUP; |
| } |
| |
| return bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_BANDWIDTH, (uint8_t)odr); |
| } |
| |
| static int bmi08x_gyr_range_set(const struct device *dev, uint16_t range) |
| { |
| struct bmi08x_gyro_data *bmi08x = dev->data; |
| int32_t reg_val = |
| bmi08x_range_to_reg_val(range, bmi08x_gyr_range_map, BMI08X_GYR_RANGE_MAP_SIZE); |
| int ret; |
| |
| if (reg_val < 0) { |
| return reg_val; |
| } |
| |
| ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_RANGE, reg_val); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| bmi08x->scale = BMI08X_GYR_SCALE(range); |
| |
| return ret; |
| } |
| |
| static int bmi08x_gyr_config(const struct device *dev, enum sensor_channel chan, |
| enum sensor_attribute attr, const struct sensor_value *val) |
| { |
| switch (attr) { |
| case SENSOR_ATTR_FULL_SCALE: |
| return bmi08x_gyr_range_set(dev, sensor_rad_to_degrees(val)); |
| case SENSOR_ATTR_SAMPLING_FREQUENCY: |
| return bmi08x_gyr_odr_set(dev, val->val1, val->val2 / 1000); |
| default: |
| LOG_DBG("Gyro attribute not supported."); |
| return -ENOTSUP; |
| } |
| } |
| |
| static int bmi08x_attr_set(const struct device *dev, enum sensor_channel chan, |
| enum sensor_attribute attr, const struct sensor_value *val) |
| { |
| #ifdef CONFIG_PM_DEVICE |
| enum pm_device_state state; |
| |
| (void)pm_device_state_get(dev, &state); |
| if (state != PM_DEVICE_STATE_ACTIVE) { |
| return -EBUSY; |
| } |
| #endif |
| |
| switch (chan) { |
| case SENSOR_CHAN_GYRO_X: |
| case SENSOR_CHAN_GYRO_Y: |
| case SENSOR_CHAN_GYRO_Z: |
| case SENSOR_CHAN_GYRO_XYZ: |
| return bmi08x_gyr_config(dev, chan, attr, val); |
| default: |
| LOG_DBG("attr_set() not supported on this channel."); |
| return -ENOTSUP; |
| } |
| } |
| |
| static int bmi08x_sample_fetch(const struct device *dev, enum sensor_channel chan) |
| { |
| struct bmi08x_gyro_data *bmi08x = dev->data; |
| size_t i; |
| int ret; |
| |
| if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_GYRO_XYZ) { |
| LOG_DBG("Unsupported sensor channel"); |
| return -ENOTSUP; |
| } |
| |
| ret = bmi08x_gyro_read(dev, BMI08X_REG_GYRO_X_LSB, (uint8_t *)bmi08x->gyr_sample, |
| sizeof(bmi08x->gyr_sample)); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* convert samples to cpu endianness */ |
| for (i = 0; i < ARRAY_SIZE(bmi08x->gyr_sample); i++) { |
| bmi08x->gyr_sample[i] = sys_le16_to_cpu(bmi08x->gyr_sample[i]); |
| } |
| |
| return ret; |
| } |
| |
| static void bmi08x_to_fixed_point(int16_t raw_val, uint16_t scale, struct sensor_value *val) |
| { |
| int32_t converted_val; |
| |
| /* |
| * maximum converted value we can get is: max(raw_val) * max(scale) |
| * max(raw_val) = +/- 2^15 |
| * max(scale) = 4785 |
| * max(converted_val) = 156794880 which is less than 2^31 |
| */ |
| converted_val = raw_val * scale; |
| val->val1 = converted_val / 1000000; |
| val->val2 = converted_val % 1000000; |
| } |
| |
| static void bmi08x_channel_convert(enum sensor_channel chan, uint16_t scale, uint16_t *raw_xyz, |
| struct sensor_value *val) |
| { |
| int i; |
| uint8_t ofs_start, ofs_stop; |
| |
| switch (chan) { |
| case SENSOR_CHAN_GYRO_X: |
| ofs_start = ofs_stop = 0U; |
| break; |
| case SENSOR_CHAN_GYRO_Y: |
| ofs_start = ofs_stop = 1U; |
| break; |
| case SENSOR_CHAN_GYRO_Z: |
| ofs_start = ofs_stop = 2U; |
| break; |
| default: |
| ofs_start = 0U; |
| ofs_stop = 2U; |
| break; |
| } |
| |
| for (i = ofs_start; i <= ofs_stop; i++, val++) { |
| bmi08x_to_fixed_point(raw_xyz[i], scale, val); |
| } |
| } |
| |
| static inline void bmi08x_gyr_channel_get(const struct device *dev, enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| struct bmi08x_gyro_data *bmi08x = dev->data; |
| |
| bmi08x_channel_convert(chan, bmi08x->scale, bmi08x->gyr_sample, val); |
| } |
| |
| static int bmi08x_channel_get(const struct device *dev, enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| #ifdef CONFIG_PM_DEVICE |
| enum pm_device_state state; |
| |
| (void)pm_device_state_get(dev, &state); |
| if (state != PM_DEVICE_STATE_ACTIVE) { |
| return -EBUSY; |
| } |
| #endif |
| |
| switch ((int16_t)chan) { |
| case SENSOR_CHAN_GYRO_X: |
| case SENSOR_CHAN_GYRO_Y: |
| case SENSOR_CHAN_GYRO_Z: |
| case SENSOR_CHAN_GYRO_XYZ: |
| bmi08x_gyr_channel_get(dev, chan, val); |
| return 0; |
| default: |
| LOG_DBG("Channel not supported."); |
| return -ENOTSUP; |
| } |
| } |
| |
| #ifdef CONFIG_PM_DEVICE |
| static int bmi08x_gyro_pm_action(const struct device *dev, enum pm_device_action action) |
| { |
| uint8_t reg_val; |
| int ret; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| reg_val = BMI08X_GYRO_PM_NORMAL; |
| break; |
| case PM_DEVICE_ACTION_SUSPEND: |
| reg_val = BMI08X_GYRO_PM_SUSPEND; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_LPM1, reg_val); |
| if (ret < 0) { |
| LOG_ERR("Failed to set power mode"); |
| return ret; |
| } |
| k_msleep(BMI08X_GYRO_POWER_MODE_CONFIG_DELAY); |
| |
| return ret; |
| } |
| #endif /* CONFIG_PM_DEVICE */ |
| |
| static const struct sensor_driver_api bmi08x_api = { |
| .attr_set = bmi08x_attr_set, |
| #ifdef CONFIG_BMI08X_GYRO_TRIGGER |
| .trigger_set = bmi08x_trigger_set_gyr, |
| #endif |
| .sample_fetch = bmi08x_sample_fetch, |
| .channel_get = bmi08x_channel_get, |
| }; |
| |
| int bmi08x_gyro_init(const struct device *dev) |
| { |
| const struct bmi08x_gyro_config *config = dev->config; |
| uint8_t val = 0U; |
| int ret; |
| |
| ret = bmi08x_bus_check(dev); |
| if (ret < 0) { |
| LOG_ERR("Bus not ready for '%s'", dev->name); |
| return ret; |
| } |
| |
| /* reboot the chip */ |
| ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_SOFTRESET, BMI08X_SOFT_RESET_CMD); |
| if (ret < 0) { |
| LOG_ERR("Cannot reboot chip."); |
| return ret; |
| } |
| |
| k_msleep(BMI08X_GYRO_SOFTRESET_DELAY); |
| |
| ret = bmi08x_gyro_byte_read(dev, BMI08X_REG_GYRO_CHIP_ID, &val); |
| if (ret < 0) { |
| LOG_ERR("Failed to read chip id."); |
| return ret; |
| } |
| |
| if (val != BMI08X_GYRO_CHIP_ID) { |
| LOG_ERR("Unsupported chip detected (0x%02x)!", val); |
| return -ENODEV; |
| } |
| |
| /* set gyro default range */ |
| ret = bmi08x_gyr_range_set(dev, config->gyro_fs); |
| if (ret < 0) { |
| LOG_ERR("Cannot set default range for gyroscope."); |
| return ret; |
| } |
| |
| /* set gyro default bandwidth */ |
| ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_BANDWIDTH, config->gyro_hz); |
| if (ret < 0) { |
| LOG_ERR("Failed to set gyro's default ODR."); |
| return ret; |
| } |
| |
| #ifdef CONFIG_BMI08X_GYRO_TRIGGER |
| ret = bmi08x_gyr_trigger_mode_init(dev); |
| if (ret < 0) { |
| LOG_ERR("Cannot set up trigger mode."); |
| return ret; |
| } |
| #endif |
| /* with BMI08X_DATA_SYNC set, it is expected that the INT3 or INT4 is wired to either INT1 |
| * or INT2 |
| */ |
| #if defined(CONFIG_BMI08X_GYRO_TRIGGER) || BMI08X_GYRO_ANY_INST_HAS_DATA_SYNC |
| /* set gyro ints */ |
| /* set ints */ |
| ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_INT_CTRL, 0x80); |
| if (ret < 0) { |
| LOG_ERR("Failed to map interrupts."); |
| return ret; |
| } |
| ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_INT3_INT4_IO_CONF, |
| config->int3_4_conf_io); |
| if (ret < 0) { |
| LOG_ERR("Failed to map interrupts."); |
| return ret; |
| } |
| ret = bmi08x_gyro_byte_write(dev, BMI08X_REG_GYRO_INT3_INT4_IO_MAP, config->int3_4_map); |
| if (ret < 0) { |
| LOG_ERR("Failed to map interrupts."); |
| return ret; |
| } |
| #endif |
| |
| return ret; |
| } |
| |
| #define BMI08X_CONFIG_SPI(inst) \ |
| .bus.spi = SPI_DT_SPEC_INST_GET( \ |
| inst, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 2), |
| |
| #define BMI08X_CONFIG_I2C(inst) .bus.i2c = I2C_DT_SPEC_INST_GET(inst), |
| |
| #define BMI08X_GYRO_TRIG(inst) \ |
| .int3_4_map = DT_INST_PROP(inst, int3_4_map_io), \ |
| .int3_4_conf_io = DT_INST_PROP(inst, int3_4_conf_io), |
| |
| #if BMI08X_GYRO_ANY_INST_HAS_DATA_SYNC |
| /* the bmi08x-gyro should not have trigger mode with data-sync enabled */ |
| BUILD_ASSERT(CONFIG_BMI08X_GYRO_TRIGGER_NONE, |
| "Only none trigger type allowed for bmi08x-gyro with data-sync enabled"); |
| /* with data-sync, one of the int pins should be wired directory to the accel's int pins, their |
| * config should be defined |
| */ |
| #define BMI08X_GYRO_TRIGGER_PINS(inst) BMI08X_GYRO_TRIG(inst) |
| #else |
| #define BMI08X_GYRO_TRIGGER_PINS(inst) \ |
| IF_ENABLED(CONFIG_BMI08X_GYRO_TRIGGER, (BMI08X_GYRO_TRIG(inst))) |
| #endif |
| |
| #define BMI08X_CREATE_INST(inst) \ |
| \ |
| static struct bmi08x_gyro_data bmi08x_drv_##inst; \ |
| \ |
| static const struct bmi08x_gyro_config bmi08x_config_##inst = { \ |
| COND_CODE_1(DT_INST_ON_BUS(inst, spi), (BMI08X_CONFIG_SPI(inst)), \ |
| (BMI08X_CONFIG_I2C(inst))) \ |
| .api = COND_CODE_1(DT_INST_ON_BUS(inst, spi), (&bmi08x_spi_api), \ |
| (&bmi08x_i2c_api)), \ |
| IF_ENABLED(CONFIG_BMI08X_GYRO_TRIGGER, \ |
| (.int_gpio = GPIO_DT_SPEC_INST_GET(inst, int_gpios),)) \ |
| .gyro_hz = DT_INST_ENUM_IDX(inst, gyro_hz), \ |
| BMI08X_GYRO_TRIGGER_PINS(inst).gyro_fs = DT_INST_PROP(inst, gyro_fs), \ |
| }; \ |
| \ |
| PM_DEVICE_DT_INST_DEFINE(inst, bmi08x_gyro_pm_action); \ |
| SENSOR_DEVICE_DT_INST_DEFINE(inst, bmi08x_gyro_init, PM_DEVICE_DT_INST_GET(inst), \ |
| &bmi08x_drv_##inst, &bmi08x_config_##inst, POST_KERNEL, \ |
| CONFIG_SENSOR_INIT_PRIORITY, &bmi08x_api); |
| |
| /* Create the struct device for every status "okay" node in the devicetree. */ |
| DT_INST_FOREACH_STATUS_OKAY(BMI08X_CREATE_INST) |