blob: 9d8656d67aa808622605f50c302ea9a2031f8957 [file] [log] [blame]
/* Bosch BMA4xx 3-axis accelerometer driver
*
* Copyright (c) 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT bosch_bma4xx
#include <zephyr/init.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
LOG_MODULE_REGISTER(bma4xx, CONFIG_SENSOR_LOG_LEVEL);
#include "bma4xx.h"
/**
* @brief Helper for converting m/s^2 offset values into register values
*/
static int bma4xx_offset_to_reg_val(const struct sensor_value *val, uint8_t *reg_val)
{
int32_t ug = sensor_ms2_to_ug(val);
if (ug < BMA4XX_OFFSET_MICROG_MIN || ug > BMA4XX_OFFSET_MICROG_MAX) {
return -ERANGE;
}
*reg_val = ug / BMA4XX_OFFSET_MICROG_PER_BIT;
return 0;
}
/**
* @brief Set the X, Y, or Z axis offsets.
*/
static int bma4xx_attr_set_offset(const struct device *dev, enum sensor_channel chan,
const struct sensor_value *val)
{
struct bma4xx_data *bma4xx = dev->data;
uint8_t reg_addr;
uint8_t reg_val[3];
int rc;
switch (chan) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
reg_addr = BMA4XX_REG_OFFSET_0 + (chan - SENSOR_CHAN_ACCEL_X);
rc = bma4xx_offset_to_reg_val(val, &reg_val[0]);
if (rc) {
return rc;
}
return bma4xx->hw_ops->write_reg(dev, reg_addr, reg_val[0]);
case SENSOR_CHAN_ACCEL_XYZ:
/* Expect val to point to an array of three sensor_values */
reg_addr = BMA4XX_REG_OFFSET_0;
for (int i = 0; i < 3; i++) {
rc = bma4xx_offset_to_reg_val(&val[i], &reg_val[i]);
if (rc) {
return rc;
}
}
return bma4xx->hw_ops->write_data(dev, reg_addr, (uint8_t *)reg_val,
sizeof(reg_val));
default:
return -ENOTSUP;
}
}
static const uint32_t odr_to_reg_map[] = {
0, /* Invalid */
781250, /* 0.78125 Hz (25/32) => 0x1 */
1562500, /* 1.5625 Hz (25/16) => 0x2 */
3125000, /* 3.125 Hz (25/8) => 0x3 */
6250000, /* 6.25 Hz (25/4) => 0x4 */
12500000, /* 12.5 Hz (25/2) => 0x5 */
25000000, /* 25 Hz => 0x6 */
50000000, /* 50 Hz => 0x7*/
100000000, /* 100 Hz => 0x8*/
200000000, /* 200 Hz => 0x9*/
400000000, /* 400 Hz => 0xa*/
800000000, /* 800 Hz => 0xb*/
1600000000, /* 1600 Hz => 0xc*/
};
/**
* @brief Convert an ODR rate in Hz to a register value
*/
static int bma4xx_odr_to_reg(uint32_t microhertz, uint8_t *reg_val)
{
if (microhertz == 0) {
/* Illegal ODR value */
return -ERANGE;
}
for (uint8_t i = 0; i < ARRAY_SIZE(odr_to_reg_map); i++) {
if (microhertz <= odr_to_reg_map[i]) {
*reg_val = i;
return 0;
}
}
/* Requested ODR too high */
return -ERANGE;
}
/**
* Set the sensor's acceleration offset (per axis). Use bma4xx_commit_nvm() to save these
* offsets to nonvolatile memory so they are automatically set during power-on-reset.
*/
static int bma4xx_attr_set_odr(const struct device *dev, const struct sensor_value *val)
{
struct bma4xx_data *bma4xx = dev->data;
int status;
uint8_t reg_val;
/* Convert ODR Hz value to microhertz and round up to closest register setting */
status = bma4xx_odr_to_reg(val->val1 * 1000000 + val->val2, &reg_val);
if (status < 0) {
return status;
}
status = bma4xx->hw_ops->update_reg(dev, BMA4XX_REG_ACCEL_CONFIG, BMA4XX_MASK_ACC_CONF_ODR,
reg_val);
if (status < 0) {
return status;
}
bma4xx->accel_odr = reg_val;
return 0;
}
static const uint32_t fs_to_reg_map[] = {
2000000, /* +/-2G => 0x0 */
4000000, /* +/-4G => 0x1 */
8000000, /* +/-8G => 0x2 */
16000000, /* +/-16G => 0x3 */
};
static int bma4xx_fs_to_reg(int32_t range_ug, uint8_t *reg_val)
{
if (range_ug == 0) {
/* Illegal value */
return -ERANGE;
}
range_ug = abs(range_ug);
for (uint8_t i = 0; i < 4; i++) {
if (range_ug <= fs_to_reg_map[i]) {
*reg_val = i;
return 0;
}
}
/* Requested range too high */
return -ERANGE;
}
/**
* Set the sensor's full-scale range
*/
static int bma4xx_attr_set_range(const struct device *dev, const struct sensor_value *val)
{
struct bma4xx_data *bma4xx = dev->data;
int status;
uint8_t reg_val;
/* Convert m/s^2 to micro-G's and find closest register setting */
status = bma4xx_fs_to_reg(sensor_ms2_to_ug(val), &reg_val);
if (status < 0) {
return status;
}
status = bma4xx->hw_ops->update_reg(dev, BMA4XX_REG_ACCEL_RANGE, BMA4XX_MASK_ACC_RANGE,
reg_val);
if (status < 0) {
return status;
}
bma4xx->accel_fs_range = reg_val;
return 0;
}
/**
* Set the sensor's bandwidth parameter (one of BMA4XX_BWP_*)
*/
static int bma4xx_attr_set_bwp(const struct device *dev, const struct sensor_value *val)
{
/* Require that `val2` is unused, and that `val1` is in range of a valid BWP */
if (val->val2 || val->val1 < BMA4XX_BWP_OSR4_AVG1 || val->val1 > BMA4XX_BWP_RES_AVG128) {
return -EINVAL;
}
struct bma4xx_data *bma4xx = dev->data;
return bma4xx->hw_ops->update_reg(dev, BMA4XX_REG_ACCEL_CONFIG, BMA4XX_MASK_ACC_CONF_BWP,
(((uint8_t)val->val1) << BMA4XX_SHIFT_ACC_CONF_BWP));
}
/**
* @brief Implement the sensor API attribute set method.
*/
static int bma4xx_attr_set(const struct device *dev, enum sensor_channel chan,
enum sensor_attribute attr, const struct sensor_value *val)
{
switch (attr) {
case SENSOR_ATTR_SAMPLING_FREQUENCY:
return bma4xx_attr_set_odr(dev, val);
case SENSOR_ATTR_FULL_SCALE:
return bma4xx_attr_set_range(dev, val);
case SENSOR_ATTR_OFFSET:
return bma4xx_attr_set_offset(dev, chan, val);
case SENSOR_ATTR_CONFIGURATION:
/* Use for setting the bandwidth parameter (BWP) */
return bma4xx_attr_set_bwp(dev, val);
default:
return -ENOTSUP;
}
}
/**
* Internal device initialization function for both bus types.
*/
static int bma4xx_chip_init(const struct device *dev)
{
struct bma4xx_data *bma4xx = dev->data;
const struct bma4xx_config *cfg = dev->config;
int status;
/* Sensor bus-specific initialization */
status = cfg->bus_init(dev);
if (status) {
LOG_ERR("bus_init failed: %d", status);
return status;
}
/* Read Chip ID */
status = bma4xx->hw_ops->read_reg(dev, BMA4XX_REG_CHIP_ID, &bma4xx->chip_id);
if (status) {
LOG_ERR("could not read chip_id: %d", status);
return status;
}
LOG_DBG("chip_id is 0x%02x", bma4xx->chip_id);
if (bma4xx->chip_id != BMA4XX_CHIP_ID_BMA422) {
LOG_WRN("Driver tested for BMA422. Check for unintended operation.");
}
/* Issue soft reset command */
status = bma4xx->hw_ops->write_reg(dev, BMA4XX_REG_CMD, BMA4XX_CMD_SOFT_RESET);
if (status) {
LOG_ERR("Could not soft-reset chip: %d", status);
return status;
}
k_sleep(K_USEC(1000));
/* Default is: range = +/-4G, ODR = 100 Hz, BWP = "NORM_AVG4" */
bma4xx->accel_fs_range = BMA4XX_RANGE_4G;
bma4xx->accel_bwp = BMA4XX_BWP_NORM_AVG4;
bma4xx->accel_odr = BMA4XX_ODR_100;
/* Switch to performance power mode */
status = bma4xx->hw_ops->update_reg(dev, BMA4XX_REG_ACCEL_CONFIG, BMA4XX_BIT_ACC_PERF_MODE,
BMA4XX_BIT_ACC_PERF_MODE);
if (status) {
LOG_ERR("Could not enable performance power save mode: %d", status);
return status;
}
/* Enable accelerometer */
status = bma4xx->hw_ops->update_reg(dev, BMA4XX_REG_POWER_CTRL, BMA4XX_BIT_ACC_EN,
BMA4XX_BIT_ACC_EN);
if (status) {
LOG_ERR("Could not enable accel: %d", status);
return status;
}
return status;
}
/*
* Sample fetch and conversion
*/
/**
* @brief Read accelerometer data from the BMA4xx
*/
static int bma4xx_sample_fetch(const struct device *dev, int16_t *x, int16_t *y, int16_t *z)
{
struct bma4xx_data *bma4xx = dev->data;
uint8_t read_data[6];
int status;
/* Burst read regs DATA_8 through DATA_13, which holds the accel readings */
status = bma4xx->hw_ops->read_data(dev, BMA4XX_REG_DATA_8, (uint8_t *)&read_data,
BMA4XX_REG_DATA_13 - BMA4XX_REG_DATA_8 + 1);
if (status < 0) {
LOG_ERR("Cannot read accel data: %d", status);
return status;
}
LOG_DBG("Raw values [0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x]", read_data[0],
read_data[1], read_data[2], read_data[3], read_data[4], read_data[5]);
/* Values in accel_data[N] are left-aligned and will read 16x actual */
*x = ((int16_t)read_data[1] << 4) | FIELD_GET(GENMASK(7, 4), read_data[0]);
*y = ((int16_t)read_data[3] << 4) | FIELD_GET(GENMASK(7, 4), read_data[2]);
*z = ((int16_t)read_data[5] << 4) | FIELD_GET(GENMASK(7, 4), read_data[4]);
LOG_DBG("XYZ reg vals are %d, %d, %d", *x, *y, *z);
return 0;
}
#ifdef CONFIG_BMA4XX_TEMPERATURE
/**
* @brief Read temperature register on BMA4xx
*/
static int bma4xx_temp_fetch(const struct device *dev, int8_t *temp)
{
struct bma4xx_data *bma4xx = dev->data;
int status;
status = bma4xx->hw_ops->read_reg(dev, BMA4XX_REG_TEMPERATURE, temp);
if (status) {
LOG_ERR("could not read temp reg: %d", status);
return status;
}
LOG_DBG("temp reg val is %d", *temp);
return 0;
}
#endif
/*
* RTIO submit and encoding
*/
static int bma4xx_submit_one_shot(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
{
struct bma4xx_data *bma4xx = dev->data;
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
const enum sensor_channel *const channels = cfg->channels;
const size_t num_channels = cfg->count;
uint32_t min_buf_len = sizeof(struct bma4xx_encoded_data);
struct bma4xx_encoded_data *edata;
uint8_t *buf;
uint32_t buf_len;
int rc;
/* Get the buffer for the frame, it may be allocated dynamically by the rtio context */
rc = rtio_sqe_rx_buf(iodev_sqe, min_buf_len, min_buf_len, &buf, &buf_len);
if (rc != 0) {
LOG_ERR("Failed to get a read buffer of size %u bytes", min_buf_len);
rtio_iodev_sqe_err(iodev_sqe, rc);
return rc;
}
/* Prepare response */
edata = (struct bma4xx_encoded_data *)buf;
edata->header.is_fifo = false;
edata->header.accel_fs = bma4xx->accel_fs_range;
edata->header.timestamp = k_ticks_to_ns_floor64(k_uptime_ticks());
edata->has_accel = 0;
edata->has_temp = 0;
/* Determine what channels we need to fetch */
for (int i = 0; i < num_channels; i++) {
switch (channels[i]) {
case SENSOR_CHAN_ALL:
edata->has_accel = 1;
#ifdef CONFIG_BMA4XX_TEMPERATURE
edata->has_temp = 1;
#endif /* CONFIG_BMA4XX_TEMPERATURE */
break;
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
edata->has_accel = 1;
break;
#ifdef CONFIG_BMA4XX_TEMPERATURE
case SENSOR_CHAN_DIE_TEMP:
edata->has_temp = 1;
break;
#endif /* CONFIG_BMA4XX_TEMPERATURE */
default:
LOG_ERR("Requested unsupported channel ID %d", channels[i]);
return -ENOTSUP;
}
}
if (edata->has_accel) {
rc = bma4xx_sample_fetch(dev, &edata->accel_xyz[0], &edata->accel_xyz[1],
&edata->accel_xyz[2]);
if (rc != 0) {
LOG_ERR("Failed to fetch accel samples");
rtio_iodev_sqe_err(iodev_sqe, rc);
return rc;
}
}
#ifdef CONFIG_BMA4XX_TEMPERATURE
if (edata->has_temp) {
rc = bma4xx_temp_fetch(dev, &edata->temp);
if (rc != 0) {
LOG_ERR("Failed to fetch temp sample");
rtio_iodev_sqe_err(iodev_sqe, rc);
return rc;
}
}
#endif /* CONFIG_BMA4XX_TEMPERATURE */
rtio_iodev_sqe_ok(iodev_sqe, 0);
return 0;
}
static int bma4xx_submit(const struct device *dev, struct rtio_iodev_sqe *iodev_sqe)
{
const struct sensor_read_config *cfg = iodev_sqe->sqe.iodev->data;
if (!cfg->is_streaming) {
return bma4xx_submit_one_shot(dev, iodev_sqe);
}
/* TODO: Add streaming support */
return -ENOTSUP;
}
/*
* RTIO decoder
*/
static int bma4xx_decoder_get_frame_count(const uint8_t *buffer, enum sensor_channel channel,
size_t channel_idx, uint16_t *frame_count)
{
const struct bma4xx_encoded_data *edata = (const struct bma4xx_encoded_data *)buffer;
const struct bma4xx_decoder_header *header = &edata->header;
if (channel_idx != 0) {
return -ENOTSUP;
}
if (!header->is_fifo) {
switch (channel) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
*frame_count = edata->has_accel ? 1 : 0;
return 0;
case SENSOR_CHAN_DIE_TEMP:
*frame_count = edata->has_temp ? 1 : 0;
return 0;
default:
return -ENOTSUP;
}
return 0;
}
/* FIFO (streaming) mode operation is not yet supported */
return -ENOTSUP;
}
static int bma4xx_decoder_get_size_info(enum sensor_channel channel, size_t *base_size,
size_t *frame_size)
{
switch (channel) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
*base_size = sizeof(struct sensor_three_axis_data);
*frame_size = sizeof(struct sensor_three_axis_sample_data);
return 0;
case SENSOR_CHAN_DIE_TEMP:
*base_size = sizeof(struct sensor_q31_data);
*frame_size = sizeof(struct sensor_q31_sample_data);
return 0;
default:
return -ENOTSUP;
}
}
static int bma4xx_get_shift(enum sensor_channel channel, uint8_t accel_fs, int8_t *shift)
{
switch (channel) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ:
switch (accel_fs) {
case BMA4XX_RANGE_2G:
/* 2 G's = 19.62 m/s^2. Use shift of 5 (+/-32) */
*shift = 5;
return 0;
case BMA4XX_RANGE_4G:
*shift = 6;
return 0;
case BMA4XX_RANGE_8G:
*shift = 7;
return 0;
case BMA4XX_RANGE_16G:
*shift = 8;
return 0;
default:
return -EINVAL;
}
case SENSOR_CHAN_DIE_TEMP:
*shift = BMA4XX_TEMP_SHIFT;
return 0;
default:
return -EINVAL;
}
}
static void bma4xx_convert_raw_accel_to_q31(int16_t raw_val, q31_t *out)
{
/* The full calculation is (assuming floating math):
* value_ms2 = raw_value * range * 9.8065 / BIT(11)
* We can treat 'range * 9.8065' as a scale, the scale is calculated by first getting 1g
* represented as a q31 value with the same shift as our result:
* 1g = (9.8065 * BIT(31)) >> shift
* Next, we need to multiply it by our range in g, which for this driver is one of
* [2, 4, 8, 16] and maps to a left shift of [1, 2, 3, 4]:
* 1g <<= log2(range)
* Note we used a right shift by 'shift' and left shift by log2(range). 'shift' is
* [5, 6, 7, 8] for range values [2, 4, 8, 16] since it's the final shift in m/s2. It is
* calculated via:
* shift = ceil(log2(range * 9.8065))
* This means that we can shorten the above 1g alterations to:
* 1g = (1g >> ceil(log2(range * 9.8065))) << log2(range)
* For the range values [2, 4, 8, 16], the following is true:
* (x >> ceil(log2(range * 9.8065))) << log2(range)
* = x >> 4
* Since the range cancels out in the right and left shift, we've now reduced the following:
* range * 9.8065 = 9.8065 * BIT(31 - 4)
* All that's left is to divide by the bma4xx's maximum range BIT(11).
*/
const int64_t scale = (int64_t)(9.8065 * BIT64(31 - 4));
*out = CLAMP(((int64_t)raw_val * scale) >> 11, INT32_MIN, INT32_MAX);
}
#ifdef CONFIG_BMA4XX_TEMPERATURE
/**
* @brief Convert the 8-bit temp register value into a Q31 celsius value
*/
static void bma4xx_convert_raw_temp_to_q31(int8_t raw_val, q31_t *out)
{
/* Value of 0 equals 23 degrees C. Each bit count equals 1 degree C */
int64_t intermediate =
((int64_t)raw_val + 23) * ((int64_t)INT32_MAX + 1) / (1 << BMA4XX_TEMP_SHIFT);
*out = CLAMP(intermediate, INT32_MIN, INT32_MAX);
}
#endif /* CONFIG_BMA4XX_TEMPERATURE */
static int bma4xx_one_shot_decode(const uint8_t *buffer, enum sensor_channel channel,
size_t channel_idx, uint32_t *fit, uint16_t max_count,
void *data_out)
{
const struct bma4xx_encoded_data *edata = (const struct bma4xx_encoded_data *)buffer;
const struct bma4xx_decoder_header *header = &edata->header;
int rc;
if (*fit != 0) {
return 0;
}
if (max_count == 0 || channel_idx != 0) {
return -EINVAL;
}
switch (channel) {
case SENSOR_CHAN_ACCEL_X:
case SENSOR_CHAN_ACCEL_Y:
case SENSOR_CHAN_ACCEL_Z:
case SENSOR_CHAN_ACCEL_XYZ: {
if (!edata->has_accel) {
return -ENODATA;
}
struct sensor_three_axis_data *out = (struct sensor_three_axis_data *)data_out;
out->header.base_timestamp_ns = edata->header.timestamp;
out->header.reading_count = 1;
rc = bma4xx_get_shift(SENSOR_CHAN_ACCEL_XYZ, header->accel_fs, &out->shift);
if (rc != 0) {
return -EINVAL;
}
bma4xx_convert_raw_accel_to_q31(edata->accel_xyz[0], &out->readings[0].x);
bma4xx_convert_raw_accel_to_q31(edata->accel_xyz[1], &out->readings[0].y);
bma4xx_convert_raw_accel_to_q31(edata->accel_xyz[2], &out->readings[0].z);
*fit = 1;
return 1;
}
#ifdef CONFIG_BMA4XX_TEMPERATURE
case SENSOR_CHAN_DIE_TEMP: {
if (!edata->has_temp) {
return -ENODATA;
}
struct sensor_q31_data *out = (struct sensor_q31_data *)data_out;
out->header.base_timestamp_ns = edata->header.timestamp;
out->header.reading_count = 1;
rc = bma4xx_get_shift(SENSOR_CHAN_DIE_TEMP, 0, &out->shift);
if (rc != 0) {
return -EINVAL;
}
bma4xx_convert_raw_temp_to_q31(edata->temp, &out->readings[0].temperature);
*fit = 1;
return 1;
}
#endif /* CONFIG_BMA4XX_TEMPERATURE */
default:
return -EINVAL;
}
}
static int bma4xx_decoder_decode(const uint8_t *buffer, enum sensor_channel channel,
size_t channel_idx, uint32_t *fit, uint16_t max_count,
void *data_out)
{
const struct bma4xx_decoder_header *header = (const struct bma4xx_decoder_header *)buffer;
if (header->is_fifo) {
/* FIFO (streaming) mode operation is not yet supported */
return -ENOTSUP;
}
return bma4xx_one_shot_decode(buffer, channel, channel_idx, fit, max_count, data_out);
}
SENSOR_DECODER_API_DT_DEFINE() = {
.get_frame_count = bma4xx_decoder_get_frame_count,
.get_size_info = bma4xx_decoder_get_size_info,
.decode = bma4xx_decoder_decode,
};
static int bma4xx_get_decoder(const struct device *dev, const struct sensor_decoder_api **decoder)
{
ARG_UNUSED(dev);
*decoder = &SENSOR_DECODER_NAME();
return 0;
}
/*
* Sensor driver API
*/
static const struct sensor_driver_api bma4xx_driver_api = {
.attr_set = bma4xx_attr_set,
.submit = bma4xx_submit,
.get_decoder = bma4xx_get_decoder,
};
/*
* Device instantiation macros
*/
/* Initializes a struct bma4xx_config for an instance on a SPI bus.
* SPI operation is not currently supported.
*/
#define BMA4XX_CONFIG_SPI(inst) \
{ \
.bus_cfg.spi = SPI_DT_SPEC_INST_GET(inst, 0, 0), .bus_init = &bma_spi_init, \
}
/* Initializes a struct bma4xx_config for an instance on an I2C bus. */
#define BMA4XX_CONFIG_I2C(inst) \
{ \
.bus_cfg.i2c = I2C_DT_SPEC_INST_GET(inst), .bus_init = &bma4xx_i2c_init, \
}
/*
* Main instantiation macro, which selects the correct bus-specific
* instantiation macros for the instance.
*/
#define BMA4XX_DEFINE(inst) \
static struct bma4xx_data bma4xx_data_##inst; \
static const struct bma4xx_config bma4xx_config_##inst = COND_CODE_1( \
DT_INST_ON_BUS(inst, spi), (BMA4XX_CONFIG_SPI(inst)), (BMA4XX_CONFIG_I2C(inst))); \
\
SENSOR_DEVICE_DT_INST_DEFINE(inst, bma4xx_chip_init, NULL, &bma4xx_data_##inst, \
&bma4xx_config_##inst, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, &bma4xx_driver_api);
DT_INST_FOREACH_STATUS_OKAY(BMA4XX_DEFINE)