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 >