root/drivers/iio/imu/bno055/bno055.c
// SPDX-License-Identifier: GPL-2.0
/*
 * IIO driver for Bosch BNO055 IMU
 *
 * Copyright (C) 2021-2022 Istituto Italiano di Tecnologia
 * Electronic Design Laboratory
 * Written by Andrea Merello <andrea.merello@iit.it>
 *
 * 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/bitfield.h>
#include <linux/bitmap.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/util_macros.h>

#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>

#include "bno055.h"

#define BNO055_FW_UID_FMT "bno055-caldata-%*phN.dat"
#define BNO055_FW_GENERIC_NAME "bno055-caldata.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_SCAN_CH_COUNT ((BNO055_GRAVITY_DATA_Z_LSB_REG - BNO055_ACC_DATA_X_LSB_REG) / 2 + 1)
#define BNO055_TEMP_REG                 0x34
#define BNO055_CALIB_STAT_REG           0x35
#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_ERR_REG              0x3A
#define BNO055_POWER_MODE_REG           0x3E
#define BNO055_POWER_MODE_NORMAL 0
#define BNO055_SYS_TRIGGER_REG          0x3F
#define BNO055_SYS_TRIGGER_RST_SYS BIT(5)
#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
/* Android orientation mode means: pitch value decreases turning clockwise */
#define BNO055_UNIT_SEL_ANDROID BIT(7)
#define BNO055_UNIT_SEL_GYR_RPS BIT(1)
#define BNO055_CALDATA_START            0x55
#define BNO055_CALDATA_END              0x6A
#define BNO055_CALDATA_LEN 22

/*
 * 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 BNO055_PG1(x) ((x) | 0x80)
#define BNO055_ACC_CONFIG_REG           BNO055_PG1(0x8)
#define BNO055_ACC_CONFIG_LPF_MASK GENMASK(4, 2)
#define BNO055_ACC_CONFIG_RANGE_MASK GENMASK(1, 0)
#define BNO055_MAG_CONFIG_REG           BNO055_PG1(0x9)
#define BNO055_MAG_CONFIG_HIGHACCURACY 0x18
#define BNO055_MAG_CONFIG_ODR_MASK GENMASK(2, 0)
#define BNO055_GYR_CONFIG_REG           BNO055_PG1(0xA)
#define BNO055_GYR_CONFIG_RANGE_MASK GENMASK(2, 0)
#define BNO055_GYR_CONFIG_LPF_MASK GENMASK(5, 3)
#define BNO055_GYR_AM_SET_REG           BNO055_PG1(0x1F)
#define BNO055_UID_LOWER_REG            BNO055_PG1(0x50)
#define BNO055_UID_HIGHER_REG           BNO055_PG1(0x5F)
#define BNO055_UID_LEN 16

struct bno055_sysfs_attr {
        const int *vals;
        int len;
        const int *fusion_vals;
        const int *hw_xlate;
        int hw_xlate_len;
        int type;
};

static const int bno055_acc_lpf_vals[] = {
        7, 810000, 15, 630000, 31, 250000, 62, 500000,
        125, 0, 250, 0, 500, 0, 1000, 0,
};

static const struct bno055_sysfs_attr bno055_acc_lpf = {
        .vals = bno055_acc_lpf_vals,
        .len = ARRAY_SIZE(bno055_acc_lpf_vals),
        .fusion_vals = (const int[]){62, 500000},
        .type = IIO_VAL_INT_PLUS_MICRO,
};

static const int bno055_acc_range_vals[] = {
  /* G:    2,    4,    8,    16 */
        1962, 3924, 7848, 15696
};

static const struct bno055_sysfs_attr bno055_acc_range = {
        .vals = bno055_acc_range_vals,
        .len = ARRAY_SIZE(bno055_acc_range_vals),
        .fusion_vals = (const int[]){3924}, /* 4G */
        .type = IIO_VAL_INT,
};

/*
 * Theoretically the IMU should return data in a given (i.e. fixed) unit
 * regardless of the range setting. This happens for the accelerometer, but not
 * for the gyroscope; the gyroscope range setting affects the scale.
 * This is probably due to this[0] bug.
 * For this reason we map the internal range setting onto the standard IIO scale
 * attribute for gyro.
 * Since the bug[0] may be fixed in future, we check for the IMU FW version and
 * eventually warn the user.
 * Currently we just don't care about "range" attributes for gyro.
 *
 * [0]  https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BNO055-Wrong-sensitivity-resolution-in-datasheet/td-p/10266
 */

/*
 * dps = hwval * (dps_range/2^15)
 * rps = hwval * (rps_range/2^15)
 *     = hwval * (dps_range/(2^15 * k))
 * where k is rad-to-deg factor
 */
static const int bno055_gyr_scale_vals[] = {
        125, 1877467, 250, 1877467, 500, 1877467,
        1000, 1877467, 2000, 1877467,
};

static const int bno055_gyr_scale_hw_xlate[] = {0, 1, 2, 3, 4};
static const struct bno055_sysfs_attr bno055_gyr_scale = {
        .vals = bno055_gyr_scale_vals,
        .len = ARRAY_SIZE(bno055_gyr_scale_vals),
        .fusion_vals = (const int[]){1, 900},
        .hw_xlate = bno055_gyr_scale_hw_xlate,
        .hw_xlate_len = ARRAY_SIZE(bno055_gyr_scale_hw_xlate),
        .type = IIO_VAL_FRACTIONAL,
};

static const int bno055_gyr_lpf_vals[] = {12, 23, 32, 47, 64, 116, 230, 523};
static const int bno055_gyr_lpf_hw_xlate[] = {5, 4, 7, 3, 6, 2, 1, 0};
static const struct bno055_sysfs_attr bno055_gyr_lpf = {
        .vals = bno055_gyr_lpf_vals,
        .len = ARRAY_SIZE(bno055_gyr_lpf_vals),
        .fusion_vals = (const int[]){32},
        .hw_xlate = bno055_gyr_lpf_hw_xlate,
        .hw_xlate_len = ARRAY_SIZE(bno055_gyr_lpf_hw_xlate),
        .type = IIO_VAL_INT,
};

static const int bno055_mag_odr_vals[] = {2, 6, 8, 10, 15, 20, 25, 30};
static const struct bno055_sysfs_attr bno055_mag_odr = {
        .vals = bno055_mag_odr_vals,
        .len =  ARRAY_SIZE(bno055_mag_odr_vals),
        .fusion_vals = (const int[]){20},
        .type = IIO_VAL_INT,
};

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];
        struct gpio_desc *reset_gpio;
        bool sw_reset;
        struct {
                __le16 chans[BNO055_SCAN_CH_COUNT];
                aligned_s64 timestamp;
        } buf;
        struct dentry *debugfs;
};

static bool bno055_regmap_volatile(struct device *dev, unsigned int reg)
{
        /* data and status registers */
        if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG)
                return true;

        /* when in fusion mode, config is updated by chip */
        if (reg == BNO055_MAG_CONFIG_REG ||
            reg == BNO055_ACC_CONFIG_REG ||
            reg == BNO055_GYR_CONFIG_REG)
                return true;

        /* calibration data may be updated by the IMU */
        if (reg >= BNO055_CALDATA_START && reg <= BNO055_CALDATA_END)
                return true;

        return false;
}

static bool bno055_regmap_readable(struct device *dev, unsigned int reg)
{
        /* unnamed PG0 reserved areas */
        if ((reg < BNO055_PG1(0) && reg > BNO055_CALDATA_END) ||
            reg == 0x3C)
                return false;

        /* unnamed PG1 reserved areas */
        if (reg > BNO055_PG1(BNO055_UID_HIGHER_REG) ||
            (reg < BNO055_PG1(BNO055_UID_LOWER_REG) && reg > BNO055_PG1(BNO055_GYR_AM_SET_REG)) ||
            reg == BNO055_PG1(0xE) ||
            (reg < BNO055_PG1(BNO055_PAGESEL_REG) && reg >= BNO055_PG1(0x0)))
                return false;
        return true;
}

static bool bno055_regmap_writeable(struct device *dev, unsigned int reg)
{
        /*
         * Unreadable registers are indeed reserved; there are no WO regs
         * (except for a single bit in SYS_TRIGGER register)
         */
        if (!bno055_regmap_readable(dev, reg))
                return false;

        /* data and status registers */
        if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG)
                return false;

        /* ID areas */
        if (reg < BNO055_PAGESEL_REG ||
            (reg <= BNO055_UID_HIGHER_REG && reg >= BNO055_UID_LOWER_REG))
                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 = GENMASK(7, 0),
                .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_MAPLE,
};
EXPORT_SYMBOL_NS_GPL(bno055_regmap_config, "IIO_BNO055");

/* must be called in configuration mode */
static int bno055_calibration_load(struct bno055_priv *priv, const u8 *data, int len)
{
        if (len != BNO055_CALDATA_LEN) {
                dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)",
                        len, BNO055_CALDATA_LEN);
                return -EINVAL;
        }

        dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, data);
        return regmap_bulk_write(priv->regmap, BNO055_CALDATA_START,
                                 data, BNO055_CALDATA_LEN);
}

static int bno055_operation_mode_do_set(struct bno055_priv *priv,
                                        int operation_mode)
{
        int ret;

        ret = regmap_write(priv->regmap, BNO055_OPR_MODE_REG,
                           operation_mode);
        if (ret)
                return ret;

        /* Following datasheet specifications: sensor takes 7mS up to 19 mS to switch mode */
        msleep(20);

        return 0;
}

static int bno055_system_reset(struct bno055_priv *priv)
{
        int ret;

        if (priv->reset_gpio) {
                gpiod_set_value_cansleep(priv->reset_gpio, 0);
                usleep_range(5000, 10000);
                gpiod_set_value_cansleep(priv->reset_gpio, 1);
        } else if (priv->sw_reset) {
                ret = regmap_write(priv->regmap, BNO055_SYS_TRIGGER_REG,
                                   BNO055_SYS_TRIGGER_RST_SYS);
                if (ret)
                        return ret;
        } else {
                return 0;
        }

        regcache_drop_region(priv->regmap, 0x0, 0xff);
        usleep_range(650000, 700000);

        return 0;
}

static int bno055_init(struct bno055_priv *priv, const u8 *caldata, int len)
{
        int ret;

        ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG);
        if (ret)
                return ret;

        ret = regmap_write(priv->regmap, BNO055_POWER_MODE_REG,
                           BNO055_POWER_MODE_NORMAL);
        if (ret)
                return ret;

        ret = regmap_write(priv->regmap, BNO055_SYS_TRIGGER_REG,
                           priv->clk ? BNO055_SYS_TRIGGER_CLK_SEL : 0);
        if (ret)
                return ret;

        /* use standard SI units */
        ret = regmap_write(priv->regmap, BNO055_UNIT_SEL_REG,
                           BNO055_UNIT_SEL_ANDROID | BNO055_UNIT_SEL_GYR_RPS);
        if (ret)
                return ret;

        if (caldata) {
                ret = bno055_calibration_load(priv, caldata, len);
                if (ret)
                        dev_warn(priv->dev, "failed to load calibration data with error %d\n",
                                 ret);
        }

        return 0;
}

static ssize_t bno055_operation_mode_set(struct bno055_priv *priv,
                                         int operation_mode)
{
        u8 caldata[BNO055_CALDATA_LEN];
        int ret;

        mutex_lock(&priv->lock);

        ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG);
        if (ret)
                goto exit_unlock;

        if (operation_mode == BNO055_OPR_MODE_FUSION ||
            operation_mode == BNO055_OPR_MODE_FUSION_FMC_OFF) {
                /* for entering fusion mode, reset the chip to clear the algo state */
                ret = regmap_bulk_read(priv->regmap, BNO055_CALDATA_START, caldata,
                                       BNO055_CALDATA_LEN);
                if (ret)
                        goto exit_unlock;

                ret = bno055_system_reset(priv);
                if (ret)
                        goto exit_unlock;

                ret = bno055_init(priv, caldata, BNO055_CALDATA_LEN);
                if (ret)
                        goto exit_unlock;
        }

        ret = bno055_operation_mode_do_set(priv, operation_mode);
        if (ret)
                goto exit_unlock;

        priv->operation_mode = operation_mode;

exit_unlock:
        mutex_unlock(&priv->lock);
        return ret;
}

static void bno055_uninit(void *arg)
{
        struct bno055_priv *priv = arg;

        /* stop the IMU */
        bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG);
}

#define BNO055_CHANNEL(_type, _axis, _index, _address, _sep, _sh, _avail) {     \
        .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),           \
        .info_mask_shared_by_type_available = _avail,                           \
        .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 bno055_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_YAW,
        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,
        _BNO055_SCAN_MAX
};

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),
                       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),
                       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),
                       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),
                       BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
                       BIT(IIO_CHAN_INFO_SCALE)),
        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),
                       BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
                       BIT(IIO_CHAN_INFO_SCALE)),
        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),
                       BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) |
                       BIT(IIO_CHAN_INFO_SCALE)),
        /* 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), 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), 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), BIT(IIO_CHAN_INFO_SAMP_FREQ)),
        /* euler angle */
        BNO055_CHANNEL(IIO_ROT, YAW, BNO055_SCAN_YAW,
                       BNO055_EUL_DATA_X_LSB_REG, 0, 0, 0),
        BNO055_CHANNEL(IIO_ROT, ROLL, BNO055_SCAN_ROLL,
                       BNO055_EUL_DATA_Y_LSB_REG, 0, 0, 0),
        BNO055_CHANNEL(IIO_ROT, PITCH, BNO055_SCAN_PITCH,
                       BNO055_EUL_DATA_Z_LSB_REG, 0, 0, 0),
        /* quaternion */
        BNO055_CHANNEL(IIO_ROT, QUATERNION, BNO055_SCAN_QUATERNION,
                       BNO055_QUAT_DATA_W_LSB_REG, 0, 0, 0),

        /* linear acceleration */
        BNO055_CHANNEL(IIO_ACCEL, LINEAR_X, BNO055_SCAN_LIA_X,
                       BNO055_LIA_DATA_X_LSB_REG, 0, 0, 0),
        BNO055_CHANNEL(IIO_ACCEL, LINEAR_Y, BNO055_SCAN_LIA_Y,
                       BNO055_LIA_DATA_Y_LSB_REG, 0, 0, 0),
        BNO055_CHANNEL(IIO_ACCEL, LINEAR_Z, BNO055_SCAN_LIA_Z,
                       BNO055_LIA_DATA_Z_LSB_REG, 0, 0, 0),

        /* gravity vector */
        BNO055_CHANNEL(IIO_GRAVITY, X, BNO055_SCAN_GRAVITY_X,
                       BNO055_GRAVITY_DATA_X_LSB_REG, 0, 0, 0),
        BNO055_CHANNEL(IIO_GRAVITY, Y, BNO055_SCAN_GRAVITY_Y,
                       BNO055_GRAVITY_DATA_Y_LSB_REG, 0, 0, 0),
        BNO055_CHANNEL(IIO_GRAVITY, Z, BNO055_SCAN_GRAVITY_Z,
                       BNO055_GRAVITY_DATA_Z_LSB_REG, 0, 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,
                              const struct bno055_sysfs_attr *attr)
{
        const int shift = __ffs(mask);
        int hwval, idx;
        int ret;
        int i;

        ret = regmap_read(priv->regmap, reg, &hwval);
        if (ret)
                return ret;

        idx = (hwval & mask) >> shift;
        if (attr->hw_xlate)
                for (i = 0; i < attr->hw_xlate_len; i++)
                        if (attr->hw_xlate[i] == idx) {
                                idx = i;
                                break;
                        }
        if (attr->type == IIO_VAL_INT) {
                *val = attr->vals[idx];
        } else { /* IIO_VAL_INT_PLUS_MICRO or IIO_VAL_FRACTIONAL */
                *val = attr->vals[idx * 2];
                *val2 = attr->vals[idx * 2 + 1];
        }

        return attr->type;
}

static int bno055_set_regmask(struct bno055_priv *priv, int val, int val2,
                              int reg, int mask,
                              const struct bno055_sysfs_attr *attr)
{
        const int shift = __ffs(mask);
        int best_delta;
        int req_val;
        int tbl_val;
        bool first;
        int delta;
        int hwval;
        int ret;
        int len;
        int i;

        /*
         * 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;

        len = attr->len;

        /*
         * We always get a request in INT_PLUS_MICRO, but we
         * take care of the micro part only when we really have
         * non-integer tables. This prevents 32-bit overflow with
         * larger integers contained in integer tables.
         */
        req_val = val;
        if (attr->type != IIO_VAL_INT) {
                len /= 2;
                req_val = min(val, 2147) * 1000000 + val2;
        }

        first = true;
        for (i = 0; i < len; i++) {
                switch (attr->type) {
                case IIO_VAL_INT:
                        tbl_val = attr->vals[i];
                        break;
                case IIO_VAL_INT_PLUS_MICRO:
                        WARN_ON(attr->vals[i * 2] > 2147);
                        tbl_val = attr->vals[i * 2] * 1000000 +
                                attr->vals[i * 2 + 1];
                        break;
                case IIO_VAL_FRACTIONAL:
                        WARN_ON(attr->vals[i * 2] > 4294);
                        tbl_val = attr->vals[i * 2] * 1000000 /
                                attr->vals[i * 2 + 1];
                        break;
                default:
                        return -EINVAL;
                }
                delta = abs(tbl_val - req_val);
                if (first || delta < best_delta) {
                        best_delta = delta;
                        hwval = i;
                        first = false;
                }
        }

        if (attr->hw_xlate)
                hwval = attr->hw_xlate[hwval];

        ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG);
        if (ret)
                return ret;

        ret = regmap_update_bits(priv->regmap, reg, mask, hwval << shift);
        if (ret)
                return ret;

        return bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_AMG);
}

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, sizeof(raw_val));
                if (ret < 0)
                        return ret;
                *val = sign_extend32(le16_to_cpu(raw_val), 15);
                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, sizeof(raw_val));
                        if (ret < 0)
                                return ret;
                        /*
                         * IMU reports sensor offsets; IIO wants correction
                         * offsets, thus we need the 'minus' here.
                         */
                        *val = -sign_extend32(le16_to_cpu(raw_val), 15);
                }
                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
                         * .. but this is not exactly true. See comment at the
                         * beginning of this file.
                         */
                        if (priv->operation_mode != BNO055_OPR_MODE_AMG) {
                                *val = bno055_gyr_scale.fusion_vals[0];
                                *val2 = bno055_gyr_scale.fusion_vals[1];
                                return IIO_VAL_FRACTIONAL;
                        }

                        return bno055_get_regmask(priv, val, val2,
                                                  BNO055_GYR_CONFIG_REG,
                                                  BNO055_GYR_CONFIG_RANGE_MASK,
                                                  &bno055_gyr_scale);
                        break;
                case IIO_ROT:
                        /* Table 3-28: 1 degree = 16 LSB */
                        *val2 = 16;
                        break;
                default:
                        return -EINVAL;
                }
                return IIO_VAL_FRACTIONAL;

        case IIO_CHAN_INFO_SAMP_FREQ:
                if (chan->type != IIO_MAGN)
                        return -EINVAL;

                return bno055_get_regmask(priv, val, val2,
                                          BNO055_MAG_CONFIG_REG,
                                          BNO055_MAG_CONFIG_ODR_MASK,
                                          &bno055_mag_odr);

        case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
                switch (chan->type) {
                case IIO_ANGL_VEL:
                        return bno055_get_regmask(priv, val, val2,
                                                  BNO055_GYR_CONFIG_REG,
                                                  BNO055_GYR_CONFIG_LPF_MASK,
                                                  &bno055_gyr_lpf);
                case IIO_ACCEL:
                        return bno055_get_regmask(priv, val, val2,
                                                  BNO055_ACC_CONFIG_REG,
                                                  BNO055_ACC_CONFIG_LPF_MASK,
                                                  &bno055_acc_lpf);
                default:
                        return -EINVAL;
                }

        default:
                return -EINVAL;
        }
}

static int bno055_sysfs_attr_avail(struct bno055_priv *priv,
                                   const struct bno055_sysfs_attr *attr,
                                   const int **vals, int *length)
{
        if (priv->operation_mode != BNO055_OPR_MODE_AMG) {
                /* locked when fusion enabled */
                *vals = attr->fusion_vals;
                if (attr->type == IIO_VAL_INT)
                        *length = 1;
                else
                        *length = 2; /* IIO_VAL_INT_PLUS_MICRO or IIO_VAL_FRACTIONAL*/
        } else {
                *vals = attr->vals;
                *length = attr->len;
        }

        return attr->type;
}

static int bno055_read_avail(struct iio_dev *indio_dev,
                             struct iio_chan_spec const *chan,
                             const int **vals, int *type, int *length,
                             long mask)
{
        struct bno055_priv *priv = iio_priv(indio_dev);

        switch (mask) {
        case IIO_CHAN_INFO_SCALE:
                switch (chan->type) {
                case IIO_ANGL_VEL:
                        *type = bno055_sysfs_attr_avail(priv, &bno055_gyr_scale,
                                                        vals, length);
                        return IIO_AVAIL_LIST;
                default:
                        return -EINVAL;
                }
        case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
                switch (chan->type) {
                case IIO_ANGL_VEL:
                        *type = bno055_sysfs_attr_avail(priv, &bno055_gyr_lpf,
                                                        vals, length);
                        return IIO_AVAIL_LIST;
                case IIO_ACCEL:
                        *type = bno055_sysfs_attr_avail(priv, &bno055_acc_lpf,
                                                        vals, length);
                        return IIO_AVAIL_LIST;
                default:
                        return -EINVAL;
                }

                break;
        case IIO_CHAN_INFO_SAMP_FREQ:
                switch (chan->type) {
                case IIO_MAGN:
                        *type = bno055_sysfs_attr_avail(priv, &bno055_mag_odr,
                                                        vals, length);
                        return IIO_AVAIL_LIST;
                default:
                        return -EINVAL;
                }
        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] = sign_extend32(le16_to_cpu(raw_vals[i]), 15);
                *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] = 14;
                return IIO_VAL_FRACTIONAL_LOG2;
        default:
                return -EINVAL;
        }
}

static bool bno055_is_chan_readable(struct iio_dev *indio_dev,
                                    struct iio_chan_spec const *chan)
{
        struct bno055_priv *priv = iio_priv(indio_dev);

        if (priv->operation_mode != BNO055_OPR_MODE_AMG)
                return true;

        switch (chan->type) {
        case IIO_GRAVITY:
        case IIO_ROT:
                return false;
        case IIO_ACCEL:
                if (chan->channel2 == IIO_MOD_LINEAR_X ||
                    chan->channel2 == IIO_MOD_LINEAR_Y ||
                    chan->channel2 == IIO_MOD_LINEAR_Z)
                        return false;
                return true;
        default:
                return true;
        }
}

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)
{
        if (!bno055_is_chan_readable(indio_dev, chan))
                return -EBUSY;

        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)
{
        struct bno055_priv *priv = iio_priv(indio_dev);
        int ret;

        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_regmask(priv, val, val2,
                                                  BNO055_MAG_CONFIG_REG,
                                                  BNO055_MAG_CONFIG_ODR_MASK,
                                                  &bno055_mag_odr);
                default:
                        return -EINVAL;
                }
        case IIO_ACCEL:
                switch (mask) {
                case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
                        return bno055_set_regmask(priv, val, val2,
                                                  BNO055_ACC_CONFIG_REG,
                                                  BNO055_ACC_CONFIG_LPF_MASK,
                                                  &bno055_acc_lpf);

                default:
                        return -EINVAL;
                }
        case IIO_ANGL_VEL:
                switch (mask) {
                case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
                        return bno055_set_regmask(priv, val, val2,
                                                  BNO055_GYR_CONFIG_REG,
                                                  BNO055_GYR_CONFIG_LPF_MASK,
                                                  &bno055_gyr_lpf);
                case IIO_CHAN_INFO_SCALE:
                        return bno055_set_regmask(priv, val, val2,
                                                  BNO055_GYR_CONFIG_REG,
                                                  BNO055_GYR_CONFIG_RANGE_MASK,
                                                  &bno055_gyr_scale);
                default:
                        return -EINVAL;
                }
        default:
                return -EINVAL;
        }
}

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);
        int ret;

        mutex_lock(&priv->lock);
        ret = _bno055_write_raw(iio_dev, chan, val, val2, mask);
        mutex_unlock(&priv->lock);

        return ret;
}

static ssize_t in_accel_range_raw_available_show(struct device *dev,
                                                 struct device_attribute *attr,
                                                 char *buf)
{
        struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
        int len = 0;
        int i;

        if (priv->operation_mode != BNO055_OPR_MODE_AMG)
                return sysfs_emit(buf, "%d\n", bno055_acc_range.fusion_vals[0]);

        for (i = 0; i < bno055_acc_range.len; i++)
                len += sysfs_emit_at(buf, len, "%d ", bno055_acc_range.vals[i]);
        buf[len - 1] = '\n';

        return len;
}

static ssize_t fusion_enable_show(struct device *dev,
                                  struct device_attribute *attr,
                                  char *buf)
{
        struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));

        return sysfs_emit(buf, "%d\n",
                          priv->operation_mode != BNO055_OPR_MODE_AMG);
}

static ssize_t fusion_enable_store(struct device *dev,
                                   struct device_attribute *attr,
                                   const char *buf, size_t len)
{
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct bno055_priv *priv = iio_priv(indio_dev);
        bool en;
        int ret;

        if (indio_dev->active_scan_mask &&
            !bitmap_empty(indio_dev->active_scan_mask, _BNO055_SCAN_MAX))
                return -EBUSY;

        ret = kstrtobool(buf, &en);
        if (ret)
                return -EINVAL;

        if (!en)
                return bno055_operation_mode_set(priv, BNO055_OPR_MODE_AMG) ?: len;

        /*
         * Coming from AMG means the FMC was off, just switch to fusion but
         * don't change anything that doesn't belong to us (i.e let FMC stay off).
         * Coming from any other fusion mode means we don't need to do anything.
         */
        if (priv->operation_mode == BNO055_OPR_MODE_AMG)
                return  bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF) ?: len;

        return len;
}

static ssize_t in_magn_calibration_fast_enable_show(struct device *dev,
                                                    struct device_attribute *attr,
                                                    char *buf)
{
        struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));

        return sysfs_emit(buf, "%d\n",
                          priv->operation_mode == BNO055_OPR_MODE_FUSION);
}

static ssize_t in_magn_calibration_fast_enable_store(struct device *dev,
                                                     struct device_attribute *attr,
                                                     const char *buf, size_t len)
{
        struct iio_dev *indio_dev = dev_to_iio_dev(dev);
        struct bno055_priv *priv = iio_priv(indio_dev);
        int ret;

        if (indio_dev->active_scan_mask &&
            !bitmap_empty(indio_dev->active_scan_mask, _BNO055_SCAN_MAX))
                return -EBUSY;

        if (sysfs_streq(buf, "0")) {
                if (priv->operation_mode == BNO055_OPR_MODE_FUSION) {
                        ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF);
                        if (ret)
                                return ret;
                }
        } else {
                if (priv->operation_mode == BNO055_OPR_MODE_AMG)
                        return -EINVAL;

                if (priv->operation_mode != BNO055_OPR_MODE_FUSION) {
                        ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION);
                        if (ret)
                                return ret;
                }
        }

        return len;
}

static ssize_t in_accel_range_raw_show(struct device *dev,
                                       struct device_attribute *attr,
                                       char *buf)
{
        struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
        int val;
        int ret;

        ret = bno055_get_regmask(priv, &val, NULL,
                                 BNO055_ACC_CONFIG_REG,
                                 BNO055_ACC_CONFIG_RANGE_MASK,
                                 &bno055_acc_range);
        if (ret < 0)
                return ret;

        return sysfs_emit(buf, "%d\n", val);
}

static ssize_t in_accel_range_raw_store(struct device *dev,
                                        struct device_attribute *attr,
                                        const char *buf, size_t len)
{
        struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
        unsigned long val;
        int ret;

        ret = kstrtoul(buf, 10, &val);
        if (ret)
                return ret;

        mutex_lock(&priv->lock);
        ret = bno055_set_regmask(priv, val, 0,
                                 BNO055_ACC_CONFIG_REG,
                                 BNO055_ACC_CONFIG_RANGE_MASK,
                                 &bno055_acc_range);
        mutex_unlock(&priv->lock);

        return ret ?: len;
}

static ssize_t bno055_get_calib_status(struct device *dev, char *buf, int which)
{
        struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
        int calib;
        int ret;
        int val;

        if (priv->operation_mode == BNO055_OPR_MODE_AMG ||
            (priv->operation_mode == BNO055_OPR_MODE_FUSION_FMC_OFF &&
             which == BNO055_CALIB_STAT_MAGN_SHIFT)) {
                calib = 0;
        } else {
                mutex_lock(&priv->lock);
                ret = regmap_read(priv->regmap, BNO055_CALIB_STAT_REG, &val);
                mutex_unlock(&priv->lock);

                if (ret)
                        return -EIO;

                calib = ((val >> which) & GENMASK(1, 0)) + 1;
        }

        return sysfs_emit(buf, "%d\n", calib);
}

static ssize_t serialnumber_show(struct device *dev,
                                 struct device_attribute *attr,
                                 char *buf)
{
        struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));

        return sysfs_emit(buf, "%*ph\n", BNO055_UID_LEN, priv->uid);
}

static ssize_t calibration_data_read(struct file *filp, struct kobject *kobj,
                                     const struct bin_attribute *bin_attr, char *buf,
                                     loff_t pos, size_t count)
{
        struct bno055_priv *priv = iio_priv(dev_to_iio_dev(kobj_to_dev(kobj)));
        u8 data[BNO055_CALDATA_LEN];
        int ret;

        /*
         * Calibration data is volatile; reading it in chunks will possibly
         * results in inconsistent data. We require the user to read the whole
         * blob in a single chunk
         */
        if (count < BNO055_CALDATA_LEN || pos)
                return -EINVAL;

        mutex_lock(&priv->lock);
        ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG);
        if (ret)
                goto exit_unlock;

        ret = regmap_bulk_read(priv->regmap, BNO055_CALDATA_START, data,
                               BNO055_CALDATA_LEN);
        if (ret)
                goto exit_unlock;

        ret = bno055_operation_mode_do_set(priv, priv->operation_mode);
        if (ret)
                goto exit_unlock;

        memcpy(buf, data, BNO055_CALDATA_LEN);

        ret = BNO055_CALDATA_LEN;
exit_unlock:
        mutex_unlock(&priv->lock);
        return ret;
}

static ssize_t sys_calibration_auto_status_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_accel_calibration_auto_status_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_gyro_calibration_auto_status_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_magn_calibration_auto_status_show(struct device *dev,
                                                    struct device_attribute *a,
                                                    char *buf)
{
        return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_MAGN_SHIFT);
}

static int bno055_debugfs_reg_access(struct iio_dev *iio_dev, unsigned int reg,
                                     unsigned int writeval, unsigned int *readval)
{
        struct bno055_priv *priv = iio_priv(iio_dev);

        if (readval)
                return regmap_read(priv->regmap, reg, readval);
        else
                return regmap_write(priv->regmap, reg, writeval);
}

static ssize_t bno055_show_fw_version(struct file *file, char __user *userbuf,
                                      size_t count, loff_t *ppos)
{
        struct bno055_priv *priv = file->private_data;
        int rev, ver;
        char *buf;
        int ret;

        ret = regmap_read(priv->regmap, BNO055_SW_REV_LSB_REG, &rev);
        if (ret)
                return ret;

        ret = regmap_read(priv->regmap, BNO055_SW_REV_MSB_REG, &ver);
        if (ret)
                return ret;

        buf = kasprintf(GFP_KERNEL, "ver: 0x%x, rev: 0x%x\n", ver, rev);
        if (!buf)
                return -ENOMEM;

        ret = simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf));
        kfree(buf);

        return ret;
}

static const struct file_operations bno055_fw_version_ops = {
        .open = simple_open,
        .read = bno055_show_fw_version,
        .llseek = default_llseek,
        .owner = THIS_MODULE,
};

static void bno055_debugfs_remove(void *_priv)
{
        struct bno055_priv *priv = _priv;

        debugfs_remove(priv->debugfs);
        priv->debugfs = NULL;
}

static void bno055_debugfs_init(struct iio_dev *iio_dev)
{
        struct bno055_priv *priv = iio_priv(iio_dev);

        priv->debugfs = debugfs_create_file("firmware_version", 0400,
                                            iio_get_debugfs_dentry(iio_dev),
                                            priv, &bno055_fw_version_ops);
        if (!IS_ERR(priv->debugfs))
                devm_add_action_or_reset(priv->dev, bno055_debugfs_remove,
                                         priv);
        if (IS_ERR_OR_NULL(priv->debugfs))
                dev_warn(priv->dev, "failed to setup debugfs");
}

static IIO_DEVICE_ATTR_RW(fusion_enable, 0);
static IIO_DEVICE_ATTR_RW(in_magn_calibration_fast_enable, 0);
static IIO_DEVICE_ATTR_RW(in_accel_range_raw, 0);

static IIO_DEVICE_ATTR_RO(in_accel_range_raw_available, 0);
static IIO_DEVICE_ATTR_RO(sys_calibration_auto_status, 0);
static IIO_DEVICE_ATTR_RO(in_accel_calibration_auto_status, 0);
static IIO_DEVICE_ATTR_RO(in_gyro_calibration_auto_status, 0);
static IIO_DEVICE_ATTR_RO(in_magn_calibration_auto_status, 0);
static IIO_DEVICE_ATTR_RO(serialnumber, 0);

static struct attribute *bno055_attrs[] = {
        &iio_dev_attr_in_accel_range_raw_available.dev_attr.attr,
        &iio_dev_attr_in_accel_range_raw.dev_attr.attr,
        &iio_dev_attr_fusion_enable.dev_attr.attr,
        &iio_dev_attr_in_magn_calibration_fast_enable.dev_attr.attr,
        &iio_dev_attr_sys_calibration_auto_status.dev_attr.attr,
        &iio_dev_attr_in_accel_calibration_auto_status.dev_attr.attr,
        &iio_dev_attr_in_gyro_calibration_auto_status.dev_attr.attr,
        &iio_dev_attr_in_magn_calibration_auto_status.dev_attr.attr,
        &iio_dev_attr_serialnumber.dev_attr.attr,
        NULL
};

static const BIN_ATTR_RO(calibration_data, BNO055_CALDATA_LEN);

static const struct bin_attribute *const bno055_bin_attrs[] = {
        &bin_attr_calibration_data,
        NULL
};

static const struct attribute_group bno055_attrs_group = {
        .attrs = bno055_attrs,
        .bin_attrs = bno055_bin_attrs,
};

static const struct iio_info bno055_info = {
        .read_raw_multi = bno055_read_raw_multi,
        .read_avail = bno055_read_avail,
        .write_raw = bno055_write_raw,
        .attrs = &bno055_attrs_group,
        .debugfs_reg_access = bno055_debugfs_reg_access,
};

/*
 * 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 transferred 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 in spite 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)
{
        const int base = BNO055_ACC_DATA_X_LSB_REG;
        bool quat_in_read = false;
        int buf_base = *buf_idx;
        __le16 *dst, *src;
        int offs_fixup = 0;
        int xfer_len = len;
        int ret;
        int i, n;

        if (!mask)
                return 0;

        /*
         * All channels are made up 1 16-bit sample, except for quaternion that
         * is made up 4 16-bit values.
         * For us the quaternion CH is just like 4 regular CHs.
         * If our 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)
                        memcpy(dst, src, n * sizeof(__le16));

                *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);
        int xfer_start, start, end, prev_end;
        unsigned long mask;
        int quat_extra_len;
        bool first = true;
        int buf_idx = 0;
        bool thr_hit;
        int ret;

        mutex_lock(&priv->lock);

        /*
         * Walk the bitmap and eventually perform several transfers.
         * Bitmap ones-fields that are separated by gaps <= xfer_burst_break_thr
         * will be included in same transfer.
         * Every time the bitmap contains a gap wider than xfer_burst_break_thr
         * then we split the transfer, skipping the gap.
         */
        for_each_set_bitrange(start, end, iio_dev->active_scan_mask,
                              iio_get_masklength(iio_dev)) {
                /*
                 * First transfer will start from the beginning of the first
                 * ones-field in the bitmap
                 */
                if (first) {
                        xfer_start = start;
                } else {
                        /*
                         * We found the next ones-field; check whether to
                         * include it in * the current transfer or not (i.e.
                         * let's perform the current * transfer and prepare for
                         * another one).
                         */

                        /*
                         * In case the zeros-gap contains the quaternion bit,
                         * then its length is actually 4 words instead of 1
                         * (i.e. +3 wrt other channels).
                         */
                        quat_extra_len = ((start > BNO055_SCAN_QUATERNION) &&
                                          (prev_end <= BNO055_SCAN_QUATERNION)) ? 3 : 0;

                        /* If the gap is wider than xfer_burst_break_thr then.. */
                        thr_hit = (start - prev_end + quat_extra_len) >
                                priv->xfer_burst_break_thr;

                        /*
                         * .. transfer all the data up to the gap. Then set the
                         * next transfer start index at right after the gap
                         * (i.e. at the start of this ones-field).
                         */
                        if (thr_hit) {
                                mask = *iio_dev->active_scan_mask >> xfer_start;
                                ret = bno055_scan_xfer(priv, xfer_start,
                                                       prev_end - xfer_start,
                                                       mask, priv->buf.chans, &buf_idx);
                                if (ret)
                                        goto done;
                                xfer_start = start;
                        }
                }
                first = false;
                prev_end = end;
        }

        /*
         * We finished walking the bitmap; no more gaps to check for. Just
         * perform the current transfer.
         */
        mask = *iio_dev->active_scan_mask >> xfer_start;
        ret = bno055_scan_xfer(priv, xfer_start,
                               prev_end - xfer_start,
                               mask, priv->buf.chans, &buf_idx);

        if (!ret)
                iio_push_to_buffers_with_timestamp(iio_dev,
                                                   &priv->buf, pf->timestamp);
done:
        mutex_unlock(&priv->lock);
        iio_trigger_notify_done(iio_dev->trig);
        return IRQ_HANDLED;
}

static int bno055_buffer_preenable(struct iio_dev *indio_dev)
{
        struct bno055_priv *priv = iio_priv(indio_dev);
        const unsigned long fusion_mask =
                BIT(BNO055_SCAN_YAW) |
                BIT(BNO055_SCAN_ROLL) |
                BIT(BNO055_SCAN_PITCH) |
                BIT(BNO055_SCAN_QUATERNION) |
                BIT(BNO055_SCAN_LIA_X) |
                BIT(BNO055_SCAN_LIA_Y) |
                BIT(BNO055_SCAN_LIA_Z) |
                BIT(BNO055_SCAN_GRAVITY_X) |
                BIT(BNO055_SCAN_GRAVITY_Y) |
                BIT(BNO055_SCAN_GRAVITY_Z);

        if (priv->operation_mode == BNO055_OPR_MODE_AMG &&
            bitmap_intersects(indio_dev->active_scan_mask, &fusion_mask,
                              _BNO055_SCAN_MAX))
                return -EBUSY;
        return 0;
}

static const struct iio_buffer_setup_ops bno055_buffer_setup_ops = {
        .preenable = bno055_buffer_preenable,
};

int bno055_probe(struct device *dev, struct regmap *regmap,
                 int xfer_burst_break_thr, bool sw_reset)
{
        const struct firmware *caldata = NULL;
        struct bno055_priv *priv;
        struct iio_dev *iio_dev;
        char *fw_name_buf;
        unsigned int val;
        int rev, ver;
        int ret;

        iio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
        if (!iio_dev)
                return -ENOMEM;

        iio_dev->name = "bno055";
        priv = iio_priv(iio_dev);
        mutex_init(&priv->lock);
        priv->regmap = regmap;
        priv->dev = dev;
        priv->xfer_burst_break_thr = xfer_burst_break_thr;
        priv->sw_reset = sw_reset;

        priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
        if (IS_ERR(priv->reset_gpio))
                return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), "Failed to get reset GPIO\n");

        priv->clk = devm_clk_get_optional_enabled(dev, "clk");
        if (IS_ERR(priv->clk))
                return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get CLK\n");

        if (priv->reset_gpio) {
                usleep_range(5000, 10000);
                gpiod_set_value_cansleep(priv->reset_gpio, 1);
                usleep_range(650000, 750000);
        } else if (!sw_reset) {
                dev_warn(dev, "No usable reset method; IMU may be unreliable\n");
        }

        ret = regmap_read(priv->regmap, BNO055_CHIP_ID_REG, &val);
        if (ret)
                return ret;

        if (val != BNO055_CHIP_ID_MAGIC)
                dev_warn(dev, "Unrecognized chip ID 0x%x\n", val);

        /*
         * In case we haven't a HW reset pin, we can still reset the chip via
         * register write. This is probably nonsense in case we can't even
         * communicate with the chip or the chip isn't the one we expect (i.e.
         * we don't write to unknown chips), so we perform SW reset only after
         * chip magic ID check
         */
        if (!priv->reset_gpio) {
                ret = bno055_system_reset(priv);
                if (ret)
                        return ret;
        }

        ret = regmap_read(priv->regmap, BNO055_SW_REV_LSB_REG, &rev);
        if (ret)
                return ret;

        ret = regmap_read(priv->regmap, BNO055_SW_REV_MSB_REG, &ver);
        if (ret)
                return ret;

        /*
         * The stock FW version contains a bug (see comment at the beginning of
         * this file) that causes the anglvel scale to be changed depending on
         * the chip range setting. We workaround this, but we don't know what
         * other FW versions might do.
         */
        if (ver != 0x3 || rev != 0x11)
                dev_warn(dev, "Untested firmware version. Anglvel scale may not work as expected\n");

        ret = regmap_bulk_read(priv->regmap, BNO055_UID_LOWER_REG,
                               priv->uid, BNO055_UID_LEN);
        if (ret)
                return ret;

        /* Sensor calibration data */
        fw_name_buf = kasprintf(GFP_KERNEL, BNO055_FW_UID_FMT,
                                BNO055_UID_LEN, priv->uid);
        if (!fw_name_buf)
                return -ENOMEM;

        ret = request_firmware(&caldata, fw_name_buf, dev);
        kfree(fw_name_buf);
        if (ret)
                ret = request_firmware(&caldata, BNO055_FW_GENERIC_NAME, dev);
        if (ret) {
                dev_notice(dev, "Calibration file load failed. See instruction in kernel Documentation/iio/bno055.rst\n");
                ret = bno055_init(priv, NULL, 0);
        } else {
                ret = bno055_init(priv, caldata->data, caldata->size);
                release_firmware(caldata);
        }
        if (ret)
                return ret;

        priv->operation_mode = BNO055_OPR_MODE_FUSION;
        ret = bno055_operation_mode_do_set(priv, priv->operation_mode);
        if (ret)
                return ret;

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

        iio_dev->channels = bno055_channels;
        iio_dev->num_channels = ARRAY_SIZE(bno055_channels);
        iio_dev->info = &bno055_info;
        iio_dev->modes = INDIO_DIRECT_MODE;

        ret = devm_iio_triggered_buffer_setup(dev, iio_dev,
                                              iio_pollfunc_store_time,
                                              bno055_trigger_handler,
                                              &bno055_buffer_setup_ops);
        if (ret)
                return ret;

        ret = devm_iio_device_register(dev, iio_dev);
        if (ret)
                return ret;

        bno055_debugfs_init(iio_dev);

        return 0;
}
EXPORT_SYMBOL_NS_GPL(bno055_probe, "IIO_BNO055");

MODULE_AUTHOR("Andrea Merello <andrea.merello@iit.it>");
MODULE_DESCRIPTION("Bosch BNO055 driver");
MODULE_LICENSE("GPL");