blob: e9463d170796c9da8ac179ed51d2824588d04df0 [file] [log] [blame]
/* 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, &reg, 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 = &reg, .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)