Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver

From: Alexandru Ardelean
Date: Fri Jul 16 2021 - 03:24:19 EST


On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <andrea.merello@xxxxxxxxx> wrote:
>
> This patch adds a core driver for the BNO055 IMU from Bosch. This IMU
> can be connected via both serial and I2C busses; separate patches will
> add support for them.
>

Hey,

I tried to comment on the ones that Andy did not touch.
I had a little trouble following all the locking.
I'm not sure if I missed anything.
My notes are inline.


> The driver supports "AMG" (Accelerometer, Magnetometer, Gyroscope) mode,
> that provides raw data from the said internal sensors, and a couple of
> "fusion" modes (i.e. the IMU also do calculations in order to provide
> euler angles, quaternions, linear acceleration and gravity measurements).
>
> In fusion modes the AMG data is still available (with some calibration
> refinements done by the IMU), but certain settings such as low pass
> filters cut-off frequency and sensors ranges are fixed, while in AMG mode
> they can be customized; this is why AMG mode can still be interesting.
>
> Signed-off-by: Andrea Merello <andrea.merello@xxxxxx>
> Cc: Andrea Merello <andrea.merello@xxxxxxxxx>
> Cc: Rob Herring <robh+dt@xxxxxxxxxx>
> Cc: Matt Ranostay <matt.ranostay@xxxxxxxxxxxx>
> Cc: Andy Shevchenko <andriy.shevchenko@xxxxxxxxxxxxxxx>
> Cc: Vlad Dogaru <vlad.dogaru@xxxxxxxxx>
> Cc: linux-kernel@xxxxxxxxxxxxxxx
> Cc: linux-iio@xxxxxxxxxxxxxxx
> ---
> drivers/iio/imu/Kconfig | 1 +
> drivers/iio/imu/Makefile | 1 +
> drivers/iio/imu/bno055/Kconfig | 7 +
> drivers/iio/imu/bno055/Makefile | 6 +
> drivers/iio/imu/bno055/bno055.c | 1361 +++++++++++++++++++++++++++++++
> drivers/iio/imu/bno055/bno055.h | 12 +
> 6 files changed, 1388 insertions(+)
> create mode 100644 drivers/iio/imu/bno055/Kconfig
> create mode 100644 drivers/iio/imu/bno055/Makefile
> create mode 100644 drivers/iio/imu/bno055/bno055.c
> create mode 100644 drivers/iio/imu/bno055/bno055.h
>
> diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig
> index 001ca2c3ff95..f1d7d4b5e222 100644
> --- a/drivers/iio/imu/Kconfig
> +++ b/drivers/iio/imu/Kconfig
> @@ -52,6 +52,7 @@ config ADIS16480
> ADIS16485, ADIS16488 inertial sensors.
>
> source "drivers/iio/imu/bmi160/Kconfig"
> +source "drivers/iio/imu/bno055/Kconfig"
>
> config FXOS8700
> tristate
> diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile
> index c82748096c77..6eb612034722 100644
> --- a/drivers/iio/imu/Makefile
> +++ b/drivers/iio/imu/Makefile
> @@ -15,6 +15,7 @@ adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o
> obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o
>
> obj-y += bmi160/
> +obj-y += bno055/
>
> obj-$(CONFIG_FXOS8700) += fxos8700_core.o
> obj-$(CONFIG_FXOS8700_I2C) += fxos8700_i2c.o
> diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig
> new file mode 100644
> index 000000000000..2bfed8df4554
> --- /dev/null
> +++ b/drivers/iio/imu/bno055/Kconfig
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# driver for Bosh bmo055
> +#
> +
> +config BOSH_BNO055_IIO
> + tristate
> diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile
> new file mode 100644
> index 000000000000..15c5ddf8d648
> --- /dev/null
> +++ b/drivers/iio/imu/bno055/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for bosh bno055
> +#
> +
> +obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o
> diff --git a/drivers/iio/imu/bno055/bno055.c b/drivers/iio/imu/bno055/bno055.c
> new file mode 100644
> index 000000000000..888a88bb13d5
> --- /dev/null
> +++ b/drivers/iio/imu/bno055/bno055.c
> @@ -0,0 +1,1361 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * IIO driver for Bosh BNO055 IMU
> + *
> + * Copyright (C) 2021 Istituto Italiano di Tecnologia
> + * Electronic Design Laboratory
> + * Written by Andrea Merello <andrea.merello@xxxxxx>
> + *
> + * Portions of this driver are taken from the BNO055 driver patch
> + * from Vlad Dogaru which is Copyright (c) 2016, Intel Corporation.
> + *
> + * This driver is also based on BMI160 driver, which is:
> + * Copyright (c) 2016, Intel Corporation.
> + * Copyright (c) 2019, Martin Kelly.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/firmware.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/regmap.h>
> +#include <linux/util_macros.h>
> +
> +#include "bno055.h"
> +
> +#define BNO055_FW_NAME "bno055-caldata"
> +#define BNO055_FW_EXT ".dat"
> +
> +/* common registers */
> +#define BNO055_PAGESEL_REG 0x7
> +
> +/* page 0 registers */
> +#define BNO055_CHIP_ID_REG 0x0
> +#define BNO055_CHIP_ID_MAGIC 0xA0
> +#define BNO055_SW_REV_LSB_REG 0x4
> +#define BNO055_SW_REV_MSB_REG 0x5
> +#define BNO055_ACC_DATA_X_LSB_REG 0x8
> +#define BNO055_ACC_DATA_Y_LSB_REG 0xA
> +#define BNO055_ACC_DATA_Z_LSB_REG 0xC
> +#define BNO055_MAG_DATA_X_LSB_REG 0xE
> +#define BNO055_MAG_DATA_Y_LSB_REG 0x10
> +#define BNO055_MAG_DATA_Z_LSB_REG 0x12
> +#define BNO055_GYR_DATA_X_LSB_REG 0x14
> +#define BNO055_GYR_DATA_Y_LSB_REG 0x16
> +#define BNO055_GYR_DATA_Z_LSB_REG 0x18
> +#define BNO055_EUL_DATA_X_LSB_REG 0x1A
> +#define BNO055_EUL_DATA_Y_LSB_REG 0x1C
> +#define BNO055_EUL_DATA_Z_LSB_REG 0x1E
> +#define BNO055_QUAT_DATA_W_LSB_REG 0x20
> +#define BNO055_LIA_DATA_X_LSB_REG 0x28
> +#define BNO055_LIA_DATA_Y_LSB_REG 0x2A
> +#define BNO055_LIA_DATA_Z_LSB_REG 0x2C
> +#define BNO055_GRAVITY_DATA_X_LSB_REG 0x2E
> +#define BNO055_GRAVITY_DATA_Y_LSB_REG 0x30
> +#define BNO055_GRAVITY_DATA_Z_LSB_REG 0x32
> +#define BNO055_TEMP_REG 0x34
> +#define BNO055_CALIB_STAT_REG 0x35
> +#define BNO055_CALIB_STAT_MASK 3
> +#define BNO055_CALIB_STAT_MAGN_SHIFT 0
> +#define BNO055_CALIB_STAT_ACCEL_SHIFT 2
> +#define BNO055_CALIB_STAT_GYRO_SHIFT 4
> +#define BNO055_CALIB_STAT_SYS_SHIFT 6
> +#define BNO055_SYS_TRIGGER_REG 0x3F
> +#define BNO055_SYS_TRIGGER_RST_INT BIT(6)
> +#define BNO055_SYS_TRIGGER_CLK_SEL BIT(7)
> +#define BNO055_OPR_MODE_REG 0x3D
> +#define BNO055_OPR_MODE_CONFIG 0x0
> +#define BNO055_OPR_MODE_AMG 0x7
> +#define BNO055_OPR_MODE_FUSION_FMC_OFF 0xB
> +#define BNO055_OPR_MODE_FUSION 0xC
> +#define BNO055_UNIT_SEL_REG 0x3B
> +#define BNO055_UNIT_SEL_ANDROID BIT(7)
> +#define BNO055_CALDATA_START 0x55
> +#define BNO055_CALDATA_END 0x6A
> +#define BNO055_CALDATA_LEN (BNO055_CALDATA_END - BNO055_CALDATA_START + 1)
> +
> +/*
> + * The difference in address between the register that contains the
> + * value and the register that contains the offset. This applies for
> + * accel, gyro and magn channels.
> + */
> +#define BNO055_REG_OFFSET_ADDR 0x4D
> +
> +/* page 1 registers */
> +#define PG1(x) ((x) | 0x80)
> +#define BNO055_ACC_CONFIG_REG PG1(0x8)
> +#define BNO055_ACC_CONFIG_LPF_MASK 0x1C
> +#define BNO055_ACC_CONFIG_LPF_SHIFT 0x2
> +#define BNO055_ACC_CONFIG_RANGE_MASK 0x3
> +#define BNO055_ACC_CONFIG_RANGE_SHIFT 0x0
> +#define BNO055_MAG_CONFIG_REG PG1(0x9)
> +#define BNO055_MAG_CONFIG_HIGHACCURACY 0x18
> +#define BNO055_MAG_CONFIG_ODR_MASK 0x7
> +#define BNO055_MAG_CONFIG_ODR_SHIFT 0
> +#define BNO055_GYR_CONFIG_REG PG1(0xA)
> +#define BNO055_GYR_CONFIG_RANGE_MASK 0x7
> +#define BNO055_GYR_CONFIG_RANGE_SHIFT 0
> +#define BNO055_GYR_CONFIG_LPF_MASK 0x38
> +#define BNO055_GYR_CONFIG_LPF_SHIFT 3
> +#define BNO055_INT_MSK PG1(0xF)
> +#define BNO055_INT_EN PG1(0x10)
> +#define BNO055_INT_ACC_BSX_DRDY BIT(0)
> +#define BNO055_INT_MAG_DRDY BIT(1)
> +#define BNO055_INT_GYR_DRDY BIT(4)
> +#define BNO055_UID_REG PG1(0x50)
> +#define BNO055_UID_LEN (0xF)
> +
> +static const int bno055_mag_odr_vals[] = {2, 6, 8, 10, 15, 20, 25, 30};
> +static const int bno055_acc_lpf_vals[] = {781, 1563, 3125, 6250,
> + 12500, 25000, 50000, 100000};
> +static const int bno055_acc_ranges[] = {2, 4, 8, 16};
> +static const int bno055_gyr_lpf_vals[] = {523, 230, 116, 47, 23, 12, 64, 32};
> +static const int bno055_gyr_ranges[] = {2000, 1000, 500, 250, 125};
> +
> +struct bno055_priv {
> + struct regmap *regmap;
> + struct device *dev;
> + struct clk *clk;
> + int operation_mode;
> + int xfer_burst_break_thr;
> + struct mutex lock;
> + u8 uid[BNO055_UID_LEN];
> +};
> +
> +static int find_closest_unsorted(int val, const int arr[], int len)
> +{
> + int i;
> + int best_idx, best_delta, delta;
> + int first = 1;
> +
> + for (i = 0; i < len; i++) {
> + delta = abs(arr[i] - val);
> + if (first || delta < best_delta) {
> + best_delta = delta;
> + best_idx = i;
> + }
> + first = 0;
> + }
> +
> + return best_idx;
> +}
> +
> +static bool bno055_regmap_volatile(struct device *dev, unsigned int reg)
> +{
> + if ((reg >= 0x8 && reg <= 0x3A) ||
> + /* when in fusion mode, config is updated by chip */
> + reg == BNO055_MAG_CONFIG_REG ||
> + reg == BNO055_ACC_CONFIG_REG ||
> + reg == BNO055_GYR_CONFIG_REG ||
> + (reg >= BNO055_CALDATA_START && reg <= BNO055_CALDATA_END))
> + return true;
> + return false;
> +}
> +
> +static bool bno055_regmap_readable(struct device *dev, unsigned int reg)
> +{
> + if ((reg <= 0x7F && reg >= 0x6B) ||
> + reg == 0x3C ||
> + (reg <= PG1(0x7F) && reg >= PG1(0x60)) ||
> + (reg <= PG1(0x4F) && reg >= PG1(0x20)) ||
> + reg == PG1(0xE) ||
> + (reg <= PG1(0x6) && reg >= PG1(0x0)))
> + return false;
> + return true;
> +}
> +
> +static bool bno055_regmap_writeable(struct device *dev, unsigned int reg)
> +{
> + if ((!bno055_regmap_readable(dev, reg)) ||
> + (reg <= 0x3A && reg >= 0x8) ||
> + reg <= 0x6 ||
> + (reg <= PG1(0x5F) && reg >= PG1(0x50)))
> + return false;
> + return true;
> +}
> +
> +static const struct regmap_range_cfg bno055_regmap_ranges[] = {
> + {
> + .range_min = 0,
> + .range_max = 0x7f * 2,
> + .selector_reg = BNO055_PAGESEL_REG,
> + .selector_mask = 0xff,
> + .selector_shift = 0,
> + .window_start = 0,
> + .window_len = 0x80
> + },
> +};
> +
> +const struct regmap_config bno055_regmap_config = {
> + .name = "bno055",
> + .reg_bits = 8,
> + .val_bits = 8,
> + .ranges = bno055_regmap_ranges,
> + .num_ranges = 1,
> + .volatile_reg = bno055_regmap_volatile,
> + .max_register = 0x80 * 2,
> + .writeable_reg = bno055_regmap_writeable,
> + .readable_reg = bno055_regmap_readable,
> + .cache_type = REGCACHE_RBTREE,
> +};
> +EXPORT_SYMBOL_GPL(bno055_regmap_config);
> +
> +static int bno055_reg_read(struct bno055_priv *priv,
> + unsigned int reg, unsigned int *val)
> +{
> + int res = regmap_read(priv->regmap, reg, val);
> +
> + if (res && res != -ERESTARTSYS) {
> + dev_err(priv->dev, "Regmap read error. adr: 0x%x, res: %d",
> + reg, res);
> + }
> +
> + return res;
> +}
> +
> +static int bno055_reg_write(struct bno055_priv *priv,
> + unsigned int reg, unsigned int val)
> +{
> + int res = regmap_write(priv->regmap, reg, val);
> +
> + if (res && res != -ERESTARTSYS) {
> + dev_err(priv->dev, "Regmap write error. adr: 0x%x, res: %d",
> + reg, res);
> + }
> +
> + return res;
> +}
> +
> +static int bno055_reg_update_bits(struct bno055_priv *priv, unsigned int reg,
> + unsigned int mask, unsigned int val)
> +{
> + int res = regmap_update_bits(priv->regmap, reg, mask, val);
> +
> + if (res && res != -ERESTARTSYS) {
> + dev_err(priv->dev, "Regmap update_bits error. adr: 0x%x, res: %d",
> + reg, res);
> + }
> +
> + return res;
> +}
> +
> +/* must be called in configuration mode */
> +int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw)
> +{
> + int i;
> + unsigned int tmp;
> + u8 cal[BNO055_CALDATA_LEN];
> + int read, tot_read = 0;
> + int ret = 0;
> + char *buf = kmalloc(fw->size + 1, GFP_KERNEL);
> +
> + if (!buf)
> + return -ENOMEM;
> +
> + memcpy(buf, fw->data, fw->size);
> + buf[fw->size] = '\0';
> + for (i = 0; i < BNO055_CALDATA_LEN; i++) {
> + ret = sscanf(buf + tot_read, "%x%n",
> + &tmp, &read);
> + if (ret != 1 || tmp > 0xff) {
> + ret = -EINVAL;
> + goto exit;
> + }
> + cal[i] = tmp;
> + tot_read += read;
> + }
> + dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, cal);
> + ret = regmap_bulk_write(priv->regmap, BNO055_CALDATA_START,
> + cal, BNO055_CALDATA_LEN);
> +exit:
> + kfree(buf);
> + return ret;
> +}
> +
> +static int bno055_init(struct bno055_priv *priv, const struct firmware *caldata)
> +{
> + int res;
> +
> + res = bno055_reg_write(priv, BNO055_SYS_TRIGGER_REG,
> + (priv->clk ? BNO055_SYS_TRIGGER_CLK_SEL : 0) |
> + BNO055_SYS_TRIGGER_RST_INT);
> + if (res)
> + return res;
> +
> + msleep(100);
> + res = bno055_reg_write(priv, BNO055_OPR_MODE_REG,
> + BNO055_OPR_MODE_CONFIG);
> + if (res)
> + return res;
> +
> + /* use standard SI units */
> + res = bno055_reg_write(priv, BNO055_UNIT_SEL_REG,
> + BNO055_UNIT_SEL_ANDROID);
> + if (res)
> + return res;
> +
> + if (caldata) {
> + res = bno055_calibration_load(priv, caldata);
> + if (res)
> + dev_warn(priv->dev, "failed to load calibration data with error %d",
> + res);
> + }
> +
> + /*
> + * Start in fusion mode (all data available), but with magnetometer auto
> + * calibration switched off, in order not to overwrite magnetometer
> + * calibration data in case one want to keep it untouched.
> + */
> + priv->operation_mode = BNO055_OPR_MODE_FUSION_FMC_OFF;
> + return bno055_reg_write(priv, BNO055_OPR_MODE_REG,
> + priv->operation_mode);
> +}
> +
> +static void bno055_uninit(void *arg)
> +{
> + struct bno055_priv *priv = arg;
> +
> + bno055_reg_write(priv, BNO055_INT_EN, 0);
> +
> + clk_disable_unprepare(priv->clk);

devm_add_action_or_reset() callbacks should be used as one-per-each uninit;
it's one of the rules for their usage; it also took me a while to get this;

so, you would do:

..................
static void bno055_clk_disable(void *clk)
{
clk_disable_unprepare(clk)
}

static void bno055_uninit(void *priv)
{
bno055_reg_write(priv, BNO055_INT_EN, 0);
}

.........................

ret = clk_prepare_enable(priv->clk);
if (ret)
return ret;
// also make sure to check return code for clk_prepare_enable

ret = devm_add_action_or_reset(dev, bno055_clk_disable, priv->clk)
if (ret)
return ret;

............

res = bno055_init(priv, caldata);
if (res)
return res;

ret = devm_add_action_or_reset(dev, bno055_uninit, priv)
if (ret)
return ret;

Right now, as bno055_uninit() does both, which is not recommended,
as devm_ uninit actions should mirror the init actions (but in
reverse).




> +}
> +
> +#define BNO055_CHANNEL(_type, _axis, _index, _address, _sep, _sh) { \
> + .address = _address, \
> + .type = _type, \
> + .modified = 1, \
> + .channel2 = IIO_MOD_##_axis, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | (_sep), \
> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_sh), \
> + .scan_index = _index, \
> + .scan_type = { \
> + .sign = 's', \
> + .realbits = 16, \
> + .storagebits = 16, \
> + .endianness = IIO_LE, \
> + .repeat = IIO_MOD_##_axis == IIO_MOD_QUATERNION ? 4 : 0 \
> + }, \
> +}
> +
> +/* scan indexes follow DATA register order */
> +enum bmi160_scan_axis {
> + BNO055_SCAN_ACCEL_X,
> + BNO055_SCAN_ACCEL_Y,
> + BNO055_SCAN_ACCEL_Z,
> + BNO055_SCAN_MAGN_X,
> + BNO055_SCAN_MAGN_Y,
> + BNO055_SCAN_MAGN_Z,
> + BNO055_SCAN_GYRO_X,
> + BNO055_SCAN_GYRO_Y,
> + BNO055_SCAN_GYRO_Z,
> + BNO055_SCAN_HEADING,
> + BNO055_SCAN_ROLL,
> + BNO055_SCAN_PITCH,
> + BNO055_SCAN_QUATERNION,
> + BNO055_SCAN_LIA_X,
> + BNO055_SCAN_LIA_Y,
> + BNO055_SCAN_LIA_Z,
> + BNO055_SCAN_GRAVITY_X,
> + BNO055_SCAN_GRAVITY_Y,
> + BNO055_SCAN_GRAVITY_Z,
> + BNO055_SCAN_TIMESTAMP,
> +};
> +
> +static const struct iio_chan_spec bno055_channels[] = {
> + /* accelerometer */
> + BNO055_CHANNEL(IIO_ACCEL, X, BNO055_SCAN_ACCEL_X,
> + BNO055_ACC_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)),
> + BNO055_CHANNEL(IIO_ACCEL, Y, BNO055_SCAN_ACCEL_Y,
> + BNO055_ACC_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)),
> + BNO055_CHANNEL(IIO_ACCEL, Z, BNO055_SCAN_ACCEL_Z,
> + BNO055_ACC_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)),
> + /* gyroscope */
> + BNO055_CHANNEL(IIO_ANGL_VEL, X, BNO055_SCAN_GYRO_X,
> + BNO055_GYR_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)),
> + BNO055_CHANNEL(IIO_ANGL_VEL, Y, BNO055_SCAN_GYRO_Y,
> + BNO055_GYR_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)),
> + BNO055_CHANNEL(IIO_ANGL_VEL, Z, BNO055_SCAN_GYRO_Z,
> + BNO055_GYR_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)),
> + /* magnetometer */
> + BNO055_CHANNEL(IIO_MAGN, X, BNO055_SCAN_MAGN_X,
> + BNO055_MAG_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_SAMP_FREQ)),
> + BNO055_CHANNEL(IIO_MAGN, Y, BNO055_SCAN_MAGN_Y,
> + BNO055_MAG_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_SAMP_FREQ)),
> + BNO055_CHANNEL(IIO_MAGN, Z, BNO055_SCAN_MAGN_Z,
> + BNO055_MAG_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET),
> + BIT(IIO_CHAN_INFO_SAMP_FREQ)),
> + /* euler angle */
> + BNO055_CHANNEL(IIO_ROT, X, BNO055_SCAN_HEADING,
> + BNO055_EUL_DATA_X_LSB_REG, 0, 0),
> + BNO055_CHANNEL(IIO_ROT, Y, BNO055_SCAN_ROLL,
> + BNO055_EUL_DATA_Y_LSB_REG, 0, 0),
> + BNO055_CHANNEL(IIO_ROT, Z, BNO055_SCAN_PITCH,
> + BNO055_EUL_DATA_Z_LSB_REG, 0, 0),
> + /* quaternion */
> + BNO055_CHANNEL(IIO_ROT, QUATERNION, BNO055_SCAN_QUATERNION,
> + BNO055_QUAT_DATA_W_LSB_REG, 0, 0),
> +
> + /* linear acceleration */
> + BNO055_CHANNEL(IIO_ACCEL, ACCEL_LINEAR_X, BNO055_SCAN_LIA_X,
> + BNO055_LIA_DATA_X_LSB_REG, 0, 0),
> + BNO055_CHANNEL(IIO_ACCEL, ACCEL_LINEAR_Y, BNO055_SCAN_LIA_Y,
> + BNO055_LIA_DATA_Y_LSB_REG, 0, 0),
> + BNO055_CHANNEL(IIO_ACCEL, ACCEL_LINEAR_Z, BNO055_SCAN_LIA_Z,
> + BNO055_LIA_DATA_Z_LSB_REG, 0, 0),
> +
> + /* gravity vector */
> + BNO055_CHANNEL(IIO_GRAVITY, X, BNO055_SCAN_GRAVITY_X,
> + BNO055_GRAVITY_DATA_X_LSB_REG, 0, 0),
> + BNO055_CHANNEL(IIO_GRAVITY, Y, BNO055_SCAN_GRAVITY_Y,
> + BNO055_GRAVITY_DATA_Y_LSB_REG, 0, 0),
> + BNO055_CHANNEL(IIO_GRAVITY, Z, BNO055_SCAN_GRAVITY_Z,
> + BNO055_GRAVITY_DATA_Z_LSB_REG, 0, 0),
> +
> + {
> + .type = IIO_TEMP,
> + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
> + .scan_index = -1
> + },
> + IIO_CHAN_SOFT_TIMESTAMP(BNO055_SCAN_TIMESTAMP),
> +};
> +
> +static int bno055_get_regmask(struct bno055_priv *priv, int *val, int *val2,
> + int reg, int mask, int shift,
> + const int tbl[], int k)
> +{
> + int hwval, idx;
> + int ret = bno055_reg_read(priv, reg, &hwval);
> +
> + if (ret)
> + return ret;
> + if (val2)
> + *val2 = 0;
> + idx = (hwval & mask) >> shift;
> + *val = tbl[idx] / k;
> +
> + if (k == 1)
> + return IIO_VAL_INT;
> +
> + *val2 = (tbl[idx] % k) * 10000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +}
> +
> +static int bno055_set_regmask(struct bno055_priv *priv, int val, int val2,
> + int reg, int mask, int shift,
> + const int table[], int table_len, int k)
> +
> +{
> + int ret;
> + int hwval = find_closest_unsorted(val * k + val2 / 10000,
> + table, table_len);
> + /*
> + * The closest value the HW supports is only one in fusion mode,
> + * and it is autoselected, so don't do anything, just return OK,
> + * as the closest possible value has been (virtually) selected
> + */
> + if (priv->operation_mode != BNO055_OPR_MODE_AMG)
> + return 0;
> +
> + dev_dbg(priv->dev, "WR config - reg, mask, val: 0x%x, 0x%x, 0x%x",
> + reg, mask, hwval);
> +
> + ret = bno055_reg_write(priv, BNO055_OPR_MODE_REG,
> + BNO055_OPR_MODE_CONFIG);
> + if (ret)
> + return ret;
> +
> + ret = bno055_reg_update_bits(priv, reg, mask, hwval << shift);
> +
> + if (ret)
> + return ret;
> +
> + return bno055_reg_write(priv, BNO055_OPR_MODE_REG,
> + BNO055_OPR_MODE_AMG);
> + return 0;

this return 0 statement looks unreachable;
i wonder if the compiler would have caught this

> +}
> +
> +#define bno055_get_mag_odr(p, v, v2) \
> + bno055_get_regmask(p, v, v2, \
> + BNO055_MAG_CONFIG_REG, BNO055_MAG_CONFIG_ODR_MASK, \
> + BNO055_MAG_CONFIG_ODR_SHIFT, bno055_mag_odr_vals, 1)
> +
> +#define bno055_set_mag_odr(p, v, v2) \
> + bno055_set_regmask(p, v, v2, \
> + BNO055_MAG_CONFIG_REG, BNO055_MAG_CONFIG_ODR_MASK, \
> + BNO055_MAG_CONFIG_ODR_SHIFT, \
> + bno055_mag_odr_vals, \
> + ARRAY_SIZE(bno055_mag_odr_vals), 1)
> +
> +#define bno055_get_acc_lpf(p, v, v2) \
> + bno055_get_regmask(p, v, v2, \
> + BNO055_ACC_CONFIG_REG, BNO055_ACC_CONFIG_LPF_MASK, \
> + BNO055_ACC_CONFIG_LPF_SHIFT, \
> + bno055_acc_lpf_vals, 100)
> +
> +#define bno055_set_acc_lpf(p, v, v2) \
> + bno055_set_regmask(p, v, v2, \
> + BNO055_ACC_CONFIG_REG, BNO055_ACC_CONFIG_LPF_MASK, \
> + BNO055_ACC_CONFIG_LPF_SHIFT, \
> + bno055_acc_lpf_vals, \
> + ARRAY_SIZE(bno055_acc_lpf_vals), 100)
> +
> +#define bno055_get_acc_range(p, v, v2) \
> + bno055_get_regmask(priv, v, v2, \
> + BNO055_ACC_CONFIG_REG, \
> + BNO055_ACC_CONFIG_RANGE_MASK, \
> + BNO055_ACC_CONFIG_RANGE_SHIFT, bno055_acc_ranges, 1)
> +
> +#define bno055_set_acc_range(p, v, v2) \
> + bno055_set_regmask(p, v, v2, \
> + BNO055_ACC_CONFIG_REG, \
> + BNO055_ACC_CONFIG_RANGE_MASK, \
> + BNO055_ACC_CONFIG_RANGE_SHIFT, \
> + bno055_acc_ranges, ARRAY_SIZE(bno055_acc_ranges), 1)
> +
> +#define bno055_get_gyr_lpf(p, v, v2) \
> + bno055_get_regmask(p, v, v2, \
> + BNO055_GYR_CONFIG_REG, BNO055_GYR_CONFIG_LPF_MASK, \
> + BNO055_GYR_CONFIG_LPF_SHIFT, bno055_gyr_lpf_vals, 1)
> +
> +#define bno055_set_gyr_lpf(p, v, v2) \
> + bno055_set_regmask(p, v, v2, \
> + BNO055_GYR_CONFIG_REG, BNO055_GYR_CONFIG_LPF_MASK, \
> + BNO055_GYR_CONFIG_LPF_SHIFT, \
> + bno055_gyr_lpf_vals, \
> + ARRAY_SIZE(bno055_gyr_lpf_vals), 1)
> +
> +#define bno055_get_gyr_range(p, v, v2) \
> + bno055_get_regmask(p, v, v2, \
> + BNO055_GYR_CONFIG_REG, \
> + BNO055_GYR_CONFIG_RANGE_MASK, \
> + BNO055_GYR_CONFIG_RANGE_SHIFT, \
> + bno055_gyr_ranges, 1)
> +
> +#define bno055_set_gyr_range(p, v, v2) \
> + bno055_set_regmask(p, v, v2, \
> + BNO055_GYR_CONFIG_REG, \
> + BNO055_GYR_CONFIG_RANGE_MASK, \
> + BNO055_GYR_CONFIG_RANGE_SHIFT, \
> + bno055_gyr_ranges, ARRAY_SIZE(bno055_gyr_ranges), 1)
> +
> +static int bno055_read_simple_chan(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + struct bno055_priv *priv = iio_priv(indio_dev);
> + __le16 raw_val;
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + ret = regmap_bulk_read(priv->regmap, chan->address,
> + &raw_val, 2);
> + if (ret < 0)
> + return ret;
> + *val = (s16)le16_to_cpu(raw_val);
> + *val2 = 0;
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_OFFSET:
> + if (priv->operation_mode != BNO055_OPR_MODE_AMG) {
> + *val = 0;
> + } else {
> + ret = regmap_bulk_read(priv->regmap,
> + chan->address +
> + BNO055_REG_OFFSET_ADDR,
> + &raw_val, 2);
> + if (ret < 0)
> + return ret;
> + *val = -(s16)le16_to_cpu(raw_val);
> + }
> + *val2 = 0;
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_SCALE:
> + *val = 1;
> + switch (chan->type) {
> + case IIO_GRAVITY:
> + /* Table 3-35: 1 m/s^2 = 100 LSB */
> + case IIO_ACCEL:
> + /* Table 3-17: 1 m/s^2 = 100 LSB */
> + *val2 = 100;
> + break;
> + case IIO_MAGN:
> + /*
> + * Table 3-19: 1 uT = 16 LSB. But we need
> + * Gauss: 1G = 0.1 uT.
> + */
> + *val2 = 160;
> + break;
> + case IIO_ANGL_VEL:
> + /* Table 3-22: 1 Rps = 900 LSB */
> + *val2 = 900;
> + break;
> + case IIO_ROT:
> + /* Table 3-28: 1 degree = 16 LSB */
> + *val2 = 16;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return IIO_VAL_FRACTIONAL;
> + default:
> + return -EINVAL;
> +
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + if (chan->type == IIO_MAGN)
> + return bno055_get_mag_odr(priv, val, val2);
> + else
> + return -EINVAL;
> +
> + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> + switch (chan->type) {
> + case IIO_ANGL_VEL:
> + return bno055_get_gyr_lpf(priv, val, val2);
> + case IIO_ACCEL:
> + return bno055_get_acc_lpf(priv, val, val2);
> + default:
> + return -EINVAL;
> + }
> + }
> +}
> +
> +static int bno055_read_temp_chan(struct iio_dev *indio_dev, int *val)
> +{
> + struct bno055_priv *priv = iio_priv(indio_dev);
> + unsigned int raw_val;
> + int ret;
> +
> + ret = regmap_read(priv->regmap, BNO055_TEMP_REG, &raw_val);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * Tables 3-36 and 3-37: one byte of priv, signed, 1 LSB = 1C.
> + * ABI wants milliC.
> + */
> + *val = raw_val * 1000;
> +
> + return IIO_VAL_INT;
> +}
> +
> +static int bno055_read_quaternion(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int size, int *vals, int *val_len,
> + long mask)
> +{
> + struct bno055_priv *priv = iio_priv(indio_dev);
> + __le16 raw_vals[4];
> + int i, ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + if (size < 4)
> + return -EINVAL;
> + ret = regmap_bulk_read(priv->regmap,
> + BNO055_QUAT_DATA_W_LSB_REG,
> + raw_vals, sizeof(raw_vals));
> + if (ret < 0)
> + return ret;
> + for (i = 0; i < 4; i++)
> + vals[i] = (s16)le16_to_cpu(raw_vals[i]);
> + *val_len = 4;
> + return IIO_VAL_INT_MULTIPLE;
> + case IIO_CHAN_INFO_SCALE:
> + /* Table 3-31: 1 quaternion = 2^14 LSB */
> + if (size < 2)
> + return -EINVAL;
> + vals[0] = 1;
> + vals[1] = 1 << 14;
> + return IIO_VAL_FRACTIONAL;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int _bno055_read_raw_multi(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int size, int *vals, int *val_len,
> + long mask)
> +{
> + switch (chan->type) {
> + case IIO_MAGN:
> + case IIO_ACCEL:
> + case IIO_ANGL_VEL:
> + case IIO_GRAVITY:
> + if (size < 2)
> + return -EINVAL;
> + *val_len = 2;
> + return bno055_read_simple_chan(indio_dev, chan,
> + &vals[0], &vals[1],
> + mask);
> +
> + case IIO_TEMP:
> + *val_len = 1;
> + return bno055_read_temp_chan(indio_dev, &vals[0]);
> +
> + case IIO_ROT:
> + /*
> + * Rotation is exposed as either a quaternion or three
> + * Euler angles.
> + */
> + if (chan->channel2 == IIO_MOD_QUATERNION)
> + return bno055_read_quaternion(indio_dev, chan,
> + size, vals,
> + val_len, mask);
> + if (size < 2)
> + return -EINVAL;
> + *val_len = 2;
> + return bno055_read_simple_chan(indio_dev, chan,
> + &vals[0], &vals[1],
> + mask);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int bno055_read_raw_multi(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int size, int *vals, int *val_len,
> + long mask)
> +{
> + int ret;
> + struct bno055_priv *priv = iio_priv(indio_dev);
> +
> + mutex_lock(&priv->lock);
> + ret = _bno055_read_raw_multi(indio_dev, chan, size,
> + vals, val_len, mask);
> + mutex_unlock(&priv->lock);
> + return ret;
> +}
> +
> +static int _bno055_write_raw(struct iio_dev *iio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + struct bno055_priv *priv = iio_priv(iio_dev);
> +
> + switch (chan->type) {
> + case IIO_MAGN:
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + return bno055_set_mag_odr(priv, val, val2);
> +
> + default:
> + return -EINVAL;
> + }
> + break;

This break looks unreachable.

> + case IIO_ACCEL:
> + switch (mask) {
> + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> + return bno055_set_acc_lpf(priv, val, val2);
> +
> + default:
> + return -EINVAL;
> + }
> + case IIO_ANGL_VEL:
> + switch (mask) {
> + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> + return bno055_set_gyr_lpf(priv, val, val2);
> + }

this looks like an implicit switch-case fall-through;
sometimes the compiler complains about these

> + default:
> + return -EINVAL;
> + }
> +
> + return 0;

This return also looks unreachable.

> +}
> +
> +static int bno055_write_raw(struct iio_dev *iio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + int ret;
> + struct bno055_priv *priv = iio_priv(iio_dev);
> +
> + mutex_lock(&priv->lock);
> + ret = _bno055_write_raw(iio_dev, chan, val, val2, mask);
> + mutex_unlock(&priv->lock);
> +
> + return ret;
> +}
> +
> +static ssize_t in_magn_sampling_frequency_available_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n",
> + (priv->operation_mode != BNO055_OPR_MODE_AMG) ? "20" :
> + "2 6 8 10 15 20 25 30");
> +}
> +
> +static ssize_t in_accel_range_available_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n",
> + (priv->operation_mode != BNO055_OPR_MODE_AMG) ? "4" :
> + "2 4 8 16");
> +}
> +
> +static ssize_t
> +in_accel_filter_low_pass_3db_frequency_available_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n",
> + (priv->operation_mode != BNO055_OPR_MODE_AMG) ? "62.5" :
> + "7.81 15.63 31.25 62.5 125 250 500 1000");
> +}
> +
> +static ssize_t in_anglvel_range_available_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n",
> + (priv->operation_mode != BNO055_OPR_MODE_AMG) ? "2000" :
> + "125 250 500 1000 2000");
> +}
> +
> +static ssize_t
> +in_anglvel_filter_low_pass_3db_frequency_available_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n",
> + (priv->operation_mode != BNO055_OPR_MODE_AMG) ? "32" :
> + "12 23 47 32 64 116 230 523");
> +}
> +
> +static ssize_t bno055_operation_mode_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n",
> + (priv->operation_mode == BNO055_OPR_MODE_AMG) ? "amg" :
> + (priv->operation_mode == BNO055_OPR_MODE_FUSION) ?
> + "fusion" : "fusion_fmc_off");
> +}
> +
> +static ssize_t bno055_operation_mode_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + int res;
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + if (sysfs_streq(buf, "amg"))
> + priv->operation_mode = BNO055_OPR_MODE_AMG;
> + else if (sysfs_streq(buf, "fusion"))
> + priv->operation_mode = BNO055_OPR_MODE_FUSION;
> + else if (sysfs_streq(buf, "fusion_fmc_off"))
> + priv->operation_mode = BNO055_OPR_MODE_FUSION_FMC_OFF;
> + else
> + return -EINVAL;
> +
> + mutex_lock(&priv->lock);
> + res = bno055_reg_write(priv, BNO055_OPR_MODE_REG,
> + BNO055_OPR_MODE_CONFIG);
> + if (res) {
> + mutex_unlock(&priv->lock);
> + return res;
> + }
> +
> + res = bno055_reg_write(priv, BNO055_OPR_MODE_REG, priv->operation_mode);
> + mutex_unlock(&priv->lock);
> +
> + return res ? res : len;
> +}
> +
> +static ssize_t bno055_in_accel_range_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int val;
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + int res = bno055_get_acc_range(priv, &val, NULL);
> +
> + if (res < 0)
> + return res;
> +
> + return scnprintf(buf, PAGE_SIZE, "%d\n", val);
> +}
> +
> +static ssize_t bno055_in_accel_range_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + int ret;
> + unsigned long val;
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + ret = kstrtoul(buf, 10, &val);
> + if (ret)
> + return ret;
> +
> + mutex_lock(&priv->lock);
> + ret = bno055_set_acc_range(priv, val, 0);
> + mutex_unlock(&priv->lock);
> +
> + return ret ? ret : len;
> +}
> +
> +static ssize_t bno055_in_gyr_range_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int val;
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> + int res = bno055_get_gyr_range(priv, &val, NULL);
> +
> + if (res < 0)
> + return res;
> +
> + return scnprintf(buf, PAGE_SIZE, "%d\n", val);
> +}
> +
> +static ssize_t bno055_in_gyr_range_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + int ret;
> + unsigned long val;
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + ret = kstrtoul(buf, 10, &val);
> + if (ret)
> + return ret;
> +
> + mutex_lock(&priv->lock);
> + ret = bno055_set_gyr_range(priv, val, 0);
> + mutex_unlock(&priv->lock);
> +
> + return ret ? ret : len;
> +}
> +
> +static ssize_t bno055_get_calib_status(struct device *dev, char *buf, int which)
> +{
> + int val;
> + int ret;
> + const char *calib_str;
> + static const char * const calib_status[] = {"bad", "barely enough",
> + "fair", "good"};
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + if (priv->operation_mode == BNO055_OPR_MODE_AMG ||
> + (priv->operation_mode == BNO055_OPR_MODE_FUSION_FMC_OFF &&
> + which == BNO055_CALIB_STAT_MAGN_SHIFT)) {
> + calib_str = "idle";
> + } else {
> + mutex_lock(&priv->lock);
> + ret = bno055_reg_read(priv, BNO055_CALIB_STAT_REG, &val);
> + mutex_unlock(&priv->lock);
> +
> + if (ret)
> + return -EIO;
> +
> + val = (val >> which) & BNO055_CALIB_STAT_MASK;
> + calib_str = calib_status[val];
> + }
> +
> + return scnprintf(buf, PAGE_SIZE, "%s\n", calib_str);
> +}
> +
> +static ssize_t in_calibration_data_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + int size;
> + int i;
> + u8 data[BNO055_CALDATA_LEN];
> + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> +
> + mutex_lock(&priv->lock);
> + ret = bno055_reg_write(priv, BNO055_OPR_MODE_REG,
> + BNO055_OPR_MODE_CONFIG);
> + if (ret)
> + goto unlock;
> +
> + ret = regmap_bulk_read(priv->regmap, BNO055_CALDATA_START, data,
> + BNO055_CALDATA_LEN);
> + if (ret)
> + goto unlock;
> +
> + ret = bno055_reg_write(priv, BNO055_OPR_MODE_REG, priv->operation_mode);
> + mutex_unlock(&priv->lock);
> + if (ret)
> + return ret;
> +
> + for (size = 0, i = 0; i < BNO055_CALDATA_LEN; i++) {
> + ret = scnprintf(buf + size,
> + PAGE_SIZE - size, "%02x%c", data[i],
> + (i + 1 < BNO055_CALDATA_LEN) ? ' ' : '\n');
> + if (ret < 0)
> + return ret;
> + size += ret;
> + }
> +
> + return size;
> +unlock:
> + mutex_unlock(&priv->lock);
> + return ret;
> +}
> +
> +static ssize_t in_autocalibration_status_sys_show(struct device *dev,
> + struct device_attribute *a,
> + char *buf)
> +{
> + return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_SYS_SHIFT);
> +}
> +
> +static ssize_t in_autocalibration_status_accel_show(struct device *dev,
> + struct device_attribute *a,
> + char *buf)
> +{
> + return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_ACCEL_SHIFT);
> +}
> +
> +static ssize_t in_autocalibration_status_gyro_show(struct device *dev,
> + struct device_attribute *a,
> + char *buf)
> +{
> + return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_GYRO_SHIFT);
> +}
> +
> +static ssize_t in_autocalibration_status_magn_show(struct device *dev,
> + struct device_attribute *a,
> + char *buf)
> +{
> + return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_MAGN_SHIFT);
> +}
> +
> +static IIO_DEVICE_ATTR_RO(in_magn_sampling_frequency_available,
> + 0);
> +
> +static IIO_DEVICE_ATTR(operation_mode, 0644,
> + bno055_operation_mode_show,
> + bno055_operation_mode_store, 0);
> +
> +static IIO_CONST_ATTR(operation_mode_available,
> + "amg fusion fusion_fmc_off");
> +
> +static IIO_DEVICE_ATTR(in_accel_range, 0644,
> + bno055_in_accel_range_show,
> + bno055_in_accel_range_store, 0);
> +
> +static IIO_DEVICE_ATTR_RO(in_accel_range_available, 0);
> +static IIO_DEVICE_ATTR_RO(in_accel_filter_low_pass_3db_frequency_available, 0);
> +
> +static IIO_DEVICE_ATTR(in_anglvel_range, 0644,
> + bno055_in_gyr_range_show,
> + bno055_in_gyr_range_store, 0);
> +
> +static IIO_DEVICE_ATTR_RO(in_anglvel_range_available, 0);
> +static IIO_DEVICE_ATTR_RO(in_anglvel_filter_low_pass_3db_frequency_available, 0);
> +
> +static IIO_DEVICE_ATTR_RO(in_autocalibration_status_sys, 0);
> +static IIO_DEVICE_ATTR_RO(in_autocalibration_status_accel, 0);
> +static IIO_DEVICE_ATTR_RO(in_autocalibration_status_gyro, 0);
> +static IIO_DEVICE_ATTR_RO(in_autocalibration_status_magn, 0);
> +static IIO_DEVICE_ATTR_RO(in_calibration_data, 0);
> +
> +static struct attribute *bno055_attrs[] = {
> + &iio_dev_attr_in_magn_sampling_frequency_available.dev_attr.attr,
> + &iio_dev_attr_in_accel_range_available.dev_attr.attr,
> + &iio_dev_attr_in_accel_range.dev_attr.attr,
> + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr,
> + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr,
> + &iio_dev_attr_in_anglvel_range.dev_attr.attr,
> + &iio_dev_attr_in_anglvel_filter_low_pass_3db_frequency_available.dev_attr.attr,
> + &iio_const_attr_operation_mode_available.dev_attr.attr,
> + &iio_dev_attr_operation_mode.dev_attr.attr,
> + &iio_dev_attr_in_autocalibration_status_sys.dev_attr.attr,
> + &iio_dev_attr_in_autocalibration_status_accel.dev_attr.attr,
> + &iio_dev_attr_in_autocalibration_status_gyro.dev_attr.attr,
> + &iio_dev_attr_in_autocalibration_status_magn.dev_attr.attr,
> + &iio_dev_attr_in_calibration_data.dev_attr.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group bno055_attrs_group = {
> + .attrs = bno055_attrs,
> +};
> +
> +static const struct iio_info bno055_info = {
> + .read_raw_multi = bno055_read_raw_multi,
> + .write_raw = bno055_write_raw,
> + .attrs = &bno055_attrs_group,
> +};
> +
> +/*
> + * Reads len samples from the HW, stores them in buf starting from buf_idx,
> + * and applies mask to cull (skip) unneeded samples.
> + * Updates buf_idx incrementing with the number of stored samples.
> + * Samples from HW are xferred into buf, then in-place copy on buf is
> + * performed in order to cull samples that need to be skipped.
> + * This avoids copies of the first samples until we hit the 1st sample to skip,
> + * and also avoids having an extra bounce buffer.
> + * buf must be able to contain len elements inspite of how many samples we are
> + * going to cull.
> + */
> +static int bno055_scan_xfer(struct bno055_priv *priv,
> + int start_ch, int len, unsigned long mask,
> + __le16 *buf, int *buf_idx)
> +{
> + int buf_base = *buf_idx;
> + const int base = BNO055_ACC_DATA_X_LSB_REG;
> + int ret;
> + int i, j, n;
> + __le16 *dst, *src;
> + bool quat_in_read = false;
> + int offs_fixup = 0;
> + int xfer_len = len;
> +
> + /* All chans are made up 1 16bit sample, except for quaternion
> + * that is made up 4 16-bit values.
> + * For us the quaternion CH is just like 4 regular CHs.
> + * If out read starts past the quaternion make sure to adjust the
> + * starting offset; if the quaternion is contained in our scan then
> + * make sure to adjust the read len.
> + */
> + if (start_ch > BNO055_SCAN_QUATERNION) {
> + start_ch += 3;
> + } else if ((start_ch <= BNO055_SCAN_QUATERNION) &&
> + ((start_ch + len) > BNO055_SCAN_QUATERNION)) {
> + quat_in_read = true;
> + xfer_len += 3;
> + }
> +
> + ret = regmap_bulk_read(priv->regmap,
> + base + start_ch * sizeof(__le16),
> + buf + buf_base,
> + xfer_len * sizeof(__le16));
> + if (ret)
> + return ret;
> +
> + for_each_set_bit(i, &mask, len) {
> + if (quat_in_read && ((start_ch + i) > BNO055_SCAN_QUATERNION))
> + offs_fixup = 3;
> +
> + dst = buf + *buf_idx;
> + src = buf + buf_base + offs_fixup + i;
> +
> + n = ((start_ch + i) == BNO055_SCAN_QUATERNION) ? 4 : 1;
> +
> + if (dst != src) {
> + for (j = 0; j < n; j++)
> + dst[j] = src[j];
> + }
> +
> + *buf_idx += n;
> + }
> + return 0;
> +}
> +
> +static irqreturn_t bno055_trigger_handler(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *iio_dev = pf->indio_dev;
> + struct bno055_priv *priv = iio_priv(iio_dev);
> + struct {
> + __le16 chans[(BNO055_GRAVITY_DATA_Z_LSB_REG -
> + BNO055_ACC_DATA_X_LSB_REG) / 2];
> + s64 timestamp __aligned(8);
> + } buf;
> + bool thr_hit;
> + int quat;
> + int ret;
> + int start, end, xfer_start, next = 0;
> + int buf_idx = 0;
> + bool finish = false;
> + unsigned long mask;
> +
> + /* we have less than 32 chs, all masks fit in an ulong */
> + start = find_first_bit(iio_dev->active_scan_mask, iio_dev->masklength);
> + xfer_start = start;
> + if (start == iio_dev->masklength)
> + goto done;
> +
> + mutex_lock(&priv->lock);
> + while (!finish) {
> + end = find_next_zero_bit(iio_dev->active_scan_mask,
> + iio_dev->masklength, start);
> + if (end == iio_dev->masklength) {
> + finish = true;
> + } else {
> + next = find_next_bit(iio_dev->active_scan_mask,
> + iio_dev->masklength, end);
> + if (next == iio_dev->masklength) {
> + finish = true;
> + } else {
> + quat = ((next > BNO055_SCAN_QUATERNION) &&
> + (end <= BNO055_SCAN_QUATERNION)) ? 3 : 0;
> + thr_hit = (next - end + quat) >
> + priv->xfer_burst_break_thr;
> + }
> + }
> +
> + if (thr_hit || finish) {
> + mask = *iio_dev->active_scan_mask >> xfer_start;
> + ret = bno055_scan_xfer(priv, xfer_start,
> + end - xfer_start,
> + mask, buf.chans, &buf_idx);
> + if (ret)
> + goto done;
> + xfer_start = next;
> + }
> + start = next;
> + }
> + iio_push_to_buffers_with_timestamp(iio_dev, &buf, pf->timestamp);
> +done:
> + mutex_unlock(&priv->lock);
> + iio_trigger_notify_done(iio_dev->trig);
> + return IRQ_HANDLED;
> +}
> +
> +int bno055_probe(struct device *dev, struct regmap *regmap, int irq,
> + int xfer_burst_break_thr)
> +{
> + int ver, rev;
> + int res;
> + unsigned int val;
> + struct gpio_desc *rst;
> + struct iio_dev *iio_dev;
> + struct bno055_priv *priv;
> + /* base name + separator + UID + ext + zero */
> + char fw_name_buf[sizeof(BNO055_FW_NAME BNO055_FW_EXT) +
> + BNO055_UID_LEN * 2 + 1 + 1];
> + const struct firmware *caldata;
> +
> + iio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
> + if (!iio_dev)
> + return -ENOMEM;
> +
> + iio_dev->name = "bno055";
> + priv = iio_priv(iio_dev);
> + memset(priv, 0, sizeof(*priv));
> + mutex_init(&priv->lock);
> + priv->regmap = regmap;
> + priv->dev = dev;
> + priv->xfer_burst_break_thr = xfer_burst_break_thr;
> + rst = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(rst) && (PTR_ERR(rst) != -EPROBE_DEFER)) {
> + dev_err(dev, "Failed to get reset GPIO");
> + return PTR_ERR(rst);
> + }
> +
> + priv->clk = devm_clk_get_optional(dev, "clk");
> + if (IS_ERR(priv->clk) && (PTR_ERR(priv->clk) != -EPROBE_DEFER)) {
> + dev_err(dev, "Failed to get CLK");
> + return PTR_ERR(priv->clk);
> + }
> +
> + clk_prepare_enable(priv->clk);
> +
> + if (rst) {
> + usleep_range(5000, 10000);
> + gpiod_set_value_cansleep(rst, 0);
> + usleep_range(650000, 750000);
> + }
> +
> + res = devm_add_action_or_reset(dev, bno055_uninit, priv);
> + if (res)
> + return res;
> +
> + res = bno055_reg_read(priv, BNO055_CHIP_ID_REG, &val);
> + if (res)
> + return res;
> +
> + if (val != BNO055_CHIP_ID_MAGIC) {
> + dev_err(dev, "Unrecognized chip ID 0x%x", val);
> + return -ENODEV;
> + }
> + dev_dbg(dev, "Found BMO055 chip");
> +
> + res = regmap_bulk_read(priv->regmap, BNO055_UID_REG,
> + priv->uid, BNO055_UID_LEN);
> + if (res)
> + return res;
> +
> + dev_info(dev, "unique ID: %*ph", BNO055_UID_LEN, priv->uid);
> +
> + /*
> + * This has nothing to do with the IMU firmware, this is for sensor
> + * calibration data.
> + */
> + sprintf(fw_name_buf, BNO055_FW_NAME "-%*phN" BNO055_FW_EXT,
> + BNO055_UID_LEN, priv->uid);
> + res = request_firmware(&caldata, fw_name_buf, dev);
> + if (res)
> + res = request_firmware(&caldata,
> + BNO055_FW_NAME BNO055_FW_EXT, dev);
> +
> + if (res) {
> + dev_notice(dev, "Failed to load calibration data firmware file; this has nothing to do with IMU main firmware.");
> + dev_notice(dev, "You can calibrate your IMU (look for 'in_autocalibration_status*' files in sysfs) and then copy 'in_calibration_data' to your firmware file");
> + caldata = NULL;
> + }
> +
> + res = bno055_init(priv, caldata);
> + if (res)
> + return res;
> +
> + if (caldata)
> + release_firmware(caldata);
> +
> + res = regmap_read(priv->regmap,
> + BNO055_SW_REV_LSB_REG, &rev);
> + if (res)
> + return res;
> +
> + res = regmap_read(priv->regmap,
> + BNO055_SW_REV_MSB_REG, &ver);
> + if (res)
> + return res;
> +
> + dev_info(dev, "Firmware version %x.%x", ver, rev);
> +
> + iio_dev->channels = bno055_channels;
> + iio_dev->num_channels = ARRAY_SIZE(bno055_channels);
> + iio_dev->info = &bno055_info;
> + iio_dev->modes = INDIO_DIRECT_MODE;
> +
> + res = devm_iio_triggered_buffer_setup(dev, iio_dev,
> + iio_pollfunc_store_time,
> + bno055_trigger_handler, NULL);
> + if (res)
> + return res;
> +
> + return devm_iio_device_register(dev, iio_dev);
> +}
> +EXPORT_SYMBOL_GPL(bno055_probe);
> +
> +MODULE_AUTHOR("Andrea Merello <andrea.merello@xxxxxx>");
> +MODULE_DESCRIPTION("Bosch BNO055 driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/iio/imu/bno055/bno055.h b/drivers/iio/imu/bno055/bno055.h
> new file mode 100644
> index 000000000000..163ab8068e7c
> --- /dev/null
> +++ b/drivers/iio/imu/bno055/bno055.h
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +#ifndef __BNO055_H__
> +#define __BNO055_H__
> +
> +#include <linux/device.h>
> +#include <linux/regmap.h>
> +
> +int bno055_probe(struct device *dev, struct regmap *regmap, int irq,
> + int xfer_burst_break_thr);
> +extern const struct regmap_config bno055_regmap_config;
> +
> +#endif
> --
> 2.17.1
>