Message ID | 20210715141742.15072-3-andrea.merello@gmail.com (mailing list archive) |
---|---|
State | Changes Requested |
Delegated to: | Jonathan Cameron |
Headers | show |
Series | Add support for Bosch BNO055 IMU | expand |
On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <andrea.merello@gmail.com> 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. > > 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@iit.it> > Cc: Andrea Merello <andrea.merello@gmail.com> > Cc: Rob Herring <robh+dt@kernel.org> > Cc: Matt Ranostay <matt.ranostay@konsulko.com> > Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > Cc: Vlad Dogaru <vlad.dogaru@intel.com> > Cc: linux-kernel@vger.kernel.org > Cc: linux-iio@vger.kernel.org Instead of polluting commit messages with this, use --to and --cc parameters. You may utilize my script [1] which finds automatically to whom to send (of course it allows manually to add more). [1]: https://github.com/andy-shev/home-bin-tools/blob/master/ge2maintainer.sh ... > +# SPDX-License-Identifier: GPL-2.0-only > +# > +# driver for Bosh bmo055 Driver Point of this comment actually is ..? ... > +# Makefile for bosh bno055 Ditto. ... > +// SPDX-License-Identifier: GPL-2.0-or-later Is it the correct one, looking at the portions taken from other drivers? > +/* > + * IIO driver for Bosh BNO055 IMU > + * > + * Copyright (C) 2021 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/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> Can you move this group to... > +#include <linux/irq.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/regmap.h> > +#include <linux/util_macros.h> ...be here? > +#include "bno055.h" ... > +#define BNO055_CALIB_STAT_MASK 3 GENMASK() ... > +#define BNO055_UNIT_SEL_ANDROID BIT(7) Android? What does this mean? ... > +#define BNO055_CALDATA_LEN (BNO055_CALDATA_END - BNO055_CALDATA_START + 1) Can you put just a plain number? ... > +#define BNO055_ACC_CONFIG_LPF_MASK 0x1C GENMASK() here and everywhere where it makes sense. ... > +#define BNO055_UID_LEN (0xF) Useless parentheses. If the LEN is a plain number, use decimal, if it's limited by register width, use the form of (BIT(x) - 1). In such a case it's easy to see how many bits are used for it. ... > + int i; > + int best_idx, best_delta, delta; > + int first = 1; Use reversed xmas tree order. ... > + for (i = 0; i < len; i++) { > + delta = abs(arr[i] - val); > + if (first || delta < best_delta) { > + best_delta = delta; > + best_idx = i; > + } > + first = 0; > + } I think I saw this kind of snippet for the 100th time. Can it be factored out to some helper and used by everyone? ... > + if ((reg >= 0x8 && reg <= 0x3A) || Use names instead of values here and in similar places elsewhere. > + /* 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)) Please, split this to 3 or more conditionals that are easier to read (logically separated). Same comment to the rest of the similar functions. ... > + .selector_mask = 0xff, GENMASK() ? ... > + if (res && res != -ERESTARTSYS) { Shouldn't RESTARTSYS be handled on a regmap level? ... > +/* 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); kmemdup() ? > + 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; > + } Sounds like NIH hex2bin(). > + 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; > +} ... > + int ret = bno055_reg_read(priv, reg, &hwval); > + > + if (ret) > + return ret; In all cases (esp. when resource allocations are involved) better to use int ret; ret = func(); if (foo) return ret; ... > + dev_dbg(priv->dev, "WR config - reg, mask, val: 0x%x, 0x%x, 0x%x", > + reg, mask, hwval); Why? Enable regmap trace events for this and drop these unneeded prints. ... > + __le16 raw_val; > + ret = regmap_bulk_read(priv->regmap, chan->address, > + &raw_val, 2); sizeof(raw_val) and everywhere where similar cases are. > + if (ret < 0) > + return ret; ... > + case IIO_CHAN_INFO_SAMP_FREQ: > + if (chan->type == IIO_MAGN) > + return bno055_get_mag_odr(priv, val, val2); > + else > + return -EINVAL; Use usual pattern if (!cond) return ERRNO; ... return bar; ... > + for (i = 0; i < 4; i++) > + vals[i] = (s16)le16_to_cpu(raw_vals[i]); Extract this to be a helper like there are for u32 and u64. ... > + vals[1] = 1 << 14; BIT(14) But still magic. ... > + switch (mask) { > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: > + return bno055_set_gyr_lpf(priv, val, val2); default? > + } ... > + 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"); IIO core should do this, besides the fact that it must use sysfs_emit(). Ditto for the similar. ... > + 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; Wondering if you may use sysfs_match_string(). ... > + return res ? res : len; ret, res, ... Be consistent! Besides that the form return ret ?: len; is shorter and better. ... > + static const char * const calib_status[] = {"bad", "barely enough", > + "fair", "good"}; Please use better indentation static char ... foo[] = { { a, b, c, d, } }; > + 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; > + } And if it's more than 4/3 kBytes (binary)? Isn't it better to use the request_firmware() interface or something similar? If IIO doesn't provide the common attributes for this it probably should and it has to be a binary one. ... > +static IIO_DEVICE_ATTR_RO(in_magn_sampling_frequency_available, > + 0); Definitely one line. ... > + &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, No comma for terminator line. ... > +/* > + * 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 transferred > + * 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 in spite of > + * going to cull. > + */ ... > + /* All chans are made up 1 16bit sample, except for quaternion 16-bit Multi-line comment style. And be consistent! > + * 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. Your lines here like a drunk person. use the space more monotonically. > + */ ... > + n = ((start_ch + i) == BNO055_SCAN_QUATERNION) ? 4 : 1; Too many parentheses. ... > + for (j = 0; j < n; j++) > + dst[j] = src[j]; NIH memcpy() ... > + __le16 chans[(BNO055_GRAVITY_DATA_Z_LSB_REG - > + BNO055_ACC_DATA_X_LSB_REG) / 2]; Can you define separately what's inside square brackets? ... > + while (!finish) { > + end = find_next_zero_bit(iio_dev->active_scan_mask, > + iio_dev->masklength, start); > + if (end == iio_dev->masklength) { > + finish = true; NIH for_each_clear_bit(). > + } else { > + next = find_next_bit(iio_dev->active_scan_mask, > + iio_dev->masklength, end); > + if (next == iio_dev->masklength) { > + finish = true; Ditto. > + } 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; > + } ... > + /* base name + separator + UID + ext + zero */ > + char fw_name_buf[sizeof(BNO055_FW_NAME BNO055_FW_EXT) + > + BNO055_UID_LEN * 2 + 1 + 1]; Perhaps devm_kasprintf()? ... > + memset(priv, 0, sizeof(*priv)); Why?! ... > + if (IS_ERR(rst) && (PTR_ERR(rst) != -EPROBE_DEFER)) { > + dev_err(dev, "Failed to get reset GPIO"); > + return PTR_ERR(rst); > + } if (IS_ERR(...)) return dev_err_probe(...); ... > + if (IS_ERR(priv->clk) && (PTR_ERR(priv->clk) != -EPROBE_DEFER)) { > + dev_err(dev, "Failed to get CLK"); > + return PTR_ERR(priv->clk); > + } Ditto. ... > + clk_prepare_enable(priv->clk); Missed clk_disabled_unprepare() from all below error paths. Use devm_add_action_or_reset() approach. ... > + dev_dbg(dev, "Found BMO055 chip"); Useless noise and LOC. ... > + dev_info(dev, "unique ID: %*ph", BNO055_UID_LEN, priv->uid); Can it be printed somewhere together with firmware revision? ... > + sprintf(fw_name_buf, BNO055_FW_NAME "-%*phN" BNO055_FW_EXT, Simply define a format string as FW_FMT somewhere above and use it here. > + BNO055_UID_LEN, priv->uid); ... > + 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"); '\n' exists on purpose. ... > +#include <linux/device.h> No user of this. > +#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;
On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <andrea.merello@gmail.com> 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@iit.it> > Cc: Andrea Merello <andrea.merello@gmail.com> > Cc: Rob Herring <robh+dt@kernel.org> > Cc: Matt Ranostay <matt.ranostay@konsulko.com> > Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > Cc: Vlad Dogaru <vlad.dogaru@intel.com> > Cc: linux-kernel@vger.kernel.org > Cc: linux-iio@vger.kernel.org > --- > 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@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/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@iit.it>"); > +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 >
Il giorno gio 15 lug 2021 alle ore 18:50 Andy Shevchenko <andy.shevchenko@gmail.com> ha scritto: > > On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <andrea.merello@gmail.com> 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. > > > > 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@iit.it> > > Cc: Andrea Merello <andrea.merello@gmail.com> > > Cc: Rob Herring <robh+dt@kernel.org> > > Cc: Matt Ranostay <matt.ranostay@konsulko.com> > > Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > > Cc: Vlad Dogaru <vlad.dogaru@intel.com> > > Cc: linux-kernel@vger.kernel.org > > Cc: linux-iio@vger.kernel.org > > Instead of polluting commit messages with this, use --to and --cc > parameters. You may utilize my script [1] which finds automatically to > whom to send (of course it allows manually to add more). > > [1]: https://github.com/andy-shev/home-bin-tools/blob/master/ge2maintainer.sh I thought it was a good & widespread practice, sorry. Will drop from future series respin. > ... > > > +# SPDX-License-Identifier: GPL-2.0-only > > +# > > +# driver for Bosh bmo055 > > Driver > > Point of this comment actually is ..? IMO None :) I just saw other drivers do have a comment like this one, so I did the same.. I'll drop these also. > ... > > > +# Makefile for bosh bno055 > > Ditto. > > ... > > > +// SPDX-License-Identifier: GPL-2.0-or-later > > Is it the correct one, looking at the portions taken from other drivers? Looks like cut-n-paste mistake, sorry; I need to drop "or-later" part. Thanks for pointing this out. > > +/* > > + * IIO driver for Bosh BNO055 IMU > > + * > > + * Copyright (C) 2021 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/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> > > Can you move this group to... > > > +#include <linux/irq.h> > > +#include <linux/module.h> > > +#include <linux/mutex.h> > > +#include <linux/regmap.h> > > +#include <linux/util_macros.h> > > ...be here? OK > > +#include "bno055.h" > > ... > > > +#define BNO055_CALIB_STAT_MASK 3 > > GENMASK() OK > ... > > > +#define BNO055_UNIT_SEL_ANDROID BIT(7) > > Android? What does this mean? Sensors support the so-called "Android" and "Windows" modes. They differs about pitch direction (CW vs CCW). I'd like to stick close to the datasheet names, but I can add a comment here. > ... > > > +#define BNO055_CALDATA_LEN (BNO055_CALDATA_END - BNO055_CALDATA_START + 1) > > Can you put just a plain number? OK > ... > > > +#define BNO055_ACC_CONFIG_LPF_MASK 0x1C > > GENMASK() here and everywhere where it makes sense. OK. > ... > > > +#define BNO055_UID_LEN (0xF) > > Useless parentheses. If the LEN is a plain number, use decimal, if > it's limited by register width, use the form of (BIT(x) - 1). In such > a case it's easy to see how many bits are used for it. It's byte number, defined by how many 8-bits registers make up the UID. I'll go for a decimal and I'll drop the parentheses. > ... > > > + int i; > > + int best_idx, best_delta, delta; > > + int first = 1; > > Use reversed xmas tree order. Looks like the kernel code is plenty of declarations in random order, neither I can find any clue about this in coding-style.rst. Where does this come from? If that's mandatory then I'll do. If that's about a mere preference, then I honestly prefer to put all declaration-plus-initialization after all declarations-olny (but I can use reversed xmas tree order inside each block, if you want). > > > + for (i = 0; i < len; i++) { > > + delta = abs(arr[i] - val); > > + if (first || delta < best_delta) { > > + best_delta = delta; > > + best_idx = i; > > + } > > + first = 0; > > + } > > I think I saw this kind of snippet for the 100th time. Can it be > factored out to some helper and used by everyone? I can try to macroize this function and move it to util_macros.h > ... > > > + if ((reg >= 0x8 && reg <= 0x3A) || > > Use names instead of values here and in similar places elsewhere. When I wrote this, I was actually unsure about which is best :) Do you have a strong opinion on this? My point: Most of this is just about register areas, which are bounded by addresses - register meaning is of no interest here. Using numerical addresses here IMO is a little advantageous because it is at least clear which is the greatest number, and it is less prone to swapping by mistake start/end registers vrt greater-than/lesser-than comparison operators. It's still true that when comparing address against a specific register address (e.g. reg == BNO055_MAG_CONFIG_REG) there is no advantage in using numerical addresses, and it can be better to use names (because e.g. you simply know that specific register is volatile). > > + /* 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)) > > Please, split this to 3 or more conditionals that are easier to read > (logically separated). > Same comment to the rest of the similar functions. Do you mean splitting into separate if statements? OK. > ... > > > + .selector_mask = 0xff, > > GENMASK() ? OK > ... > > > + if (res && res != -ERESTARTSYS) { > > Shouldn't RESTARTSYS be handled on a regmap level? Can you please elaborate on this? > ... > > > +/* 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); > > kmemdup() ? Ah, OK > > > + 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; > > + } > > Sounds like NIH hex2bin(). Indeed.. I've failed to find out this helper. Looking at the code it seems it wouldn't work as drop-in replacement here, because of spaces in the HEX string. But I might just decide to format the HEX string without spaces in order to being able to use hex2bin(). > > > + 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; > > +} > > ... > > > + int ret = bno055_reg_read(priv, reg, &hwval); > > + > > + if (ret) > > + return ret; > > In all cases (esp. when resource allocations are involved) better to use > > int ret; > > ret = func(); > if (foo) > return ret; OK (assuming you meant if(ret) actually) > ... > > > + dev_dbg(priv->dev, "WR config - reg, mask, val: 0x%x, 0x%x, 0x%x", > > + reg, mask, hwval); > > Why? Enable regmap trace events for this and drop these unneeded prints. Good point. Going to drop it. > ... > > > + __le16 raw_val; > > > + ret = regmap_bulk_read(priv->regmap, chan->address, > > + &raw_val, 2); > > sizeof(raw_val) > > and everywhere where similar cases are. OK > > + if (ret < 0) > > + return ret; > > ... > > > + case IIO_CHAN_INFO_SAMP_FREQ: > > + if (chan->type == IIO_MAGN) > > + return bno055_get_mag_odr(priv, val, val2); > > + else > > + return -EINVAL; > > Use usual pattern > > if (!cond) > return ERRNO; > ... > return bar; > OK > > > > + for (i = 0; i < 4; i++) > > + vals[i] = (s16)le16_to_cpu(raw_vals[i]); > > Extract this to be a helper like there are for u32 and u64. Could you please point me to those helpers? I don't know what you are referring to. > ... > > > + vals[1] = 1 << 14; > > BIT(14) But still magic. Why magic? there is a comment a few line above explaining this - maybe I can move it a couple of LOCs below. And BTW conceptually it is about math (2^14), it has nothing to do with BITs. > ... > > + switch (mask) { > > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: > > + return bno055_set_gyr_lpf(priv, val, val2); > > default? Ah, right. > > + } > > ... > > > + 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"); > > IIO core should do this, besides the fact that it must use sysfs_emit(). > Ditto for the similar. Ok for sysfs_emit(), thanks. But what do you mean with "IIO core should do this"? Can you please elaborate? > ... > > > + 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; > > Wondering if you may use sysfs_match_string(). Ah, yes. I'll probably need a local array for the various BNO055_OPR_MODE_* and then I can index it with sysfs_match_string() return value. > ... > > > + return res ? res : len; > > ret, res, ... Be consistent! Sure; you're right > Besides that the form > > return ret ?: len; > > is shorter and better. > OK > ... > > > > + static const char * const calib_status[] = {"bad", "barely enough", > > + "fair", "good"}; > > Please use better indentation > > static char ... foo[] = { > { a, b, c, d, } > }; > OK, but why nested parentheses? > > > + 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; > > + } > > And if it's more than 4/3 kBytes (binary)? It's few bytes long (binary) > Isn't it better to use the request_firmware() interface or something similar? No: I already use request_firmware() for getting the initial calibration data (if any), but the IMU sometimes (re)calibrates. This function is for getting current IMU calibration, so we need to read it from registers (especially you want to get it the 1st time, in order to create the calibration file that request_firmware() will fetch next time you boot). > If IIO doesn't provide the common attributes for this it probably > should and it has to be a binary one. I couldn't find anything for it. I wasn't sure whether exposing calibration data using IIO attribute is something that other drivers might need to do, hence whether it could make sense to make it generic or not.. I wasn't even sure that an IIO attribute is the right place to expose it, indeed :) > ... > > > +static IIO_DEVICE_ATTR_RO(in_magn_sampling_frequency_available, > > + 0); > > Definitely one line. OK > ... > > > > + &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, > > No comma for terminator line. OK > ... > > > +/* > > + * 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 > > transferred OK > > + * 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 > > in spite of OK > > + * going to cull. > > + */ > > ... > > > > + /* All chans are made up 1 16bit sample, except for quaternion > > 16-bit OK > > Multi-line comment style. And be consistent! OK > > + * 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. > > Your lines here like a drunk person. use the space more monotonically. Do you mean: Assuming you are sticking to the old 80-cols-long lines (which is still do, unless in few cases), then some lines still have room for more words, and you could reflow the text ? > > + */ > > ... > > > + n = ((start_ch + i) == BNO055_SCAN_QUATERNION) ? 4 : 1; > > Too many parentheses. OK > ... > > > + for (j = 0; j < n; j++) > > + dst[j] = src[j]; > > NIH memcpy() Right. > ... > > > + __le16 chans[(BNO055_GRAVITY_DATA_Z_LSB_REG - > > + BNO055_ACC_DATA_X_LSB_REG) / 2]; > > Can you define separately what's inside square brackets? OK > ... > > > + while (!finish) { > > + end = find_next_zero_bit(iio_dev->active_scan_mask, > > + iio_dev->masklength, start); > > + if (end == iio_dev->masklength) { > > + finish = true; > > NIH for_each_clear_bit(). Not sure it is convenient to use for_each_clear_bit(): Here we're searching for contiguous blocks of set-bits, and we only want indexes of first and last set-bit in a block; alternate calls to find_next_zero_bit() and find_next_bit() seem appropriate here to me. Do you have in mind a better/simpler implementation using or_each_clear_bit() ? > > + } else { > > + next = find_next_bit(iio_dev->active_scan_mask, > > + iio_dev->masklength, end); > > + if (next == iio_dev->masklength) { > > + finish = true; > > Ditto. see above. > > > + } 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; > > + } > > ... > > > + /* base name + separator + UID + ext + zero */ > > + char fw_name_buf[sizeof(BNO055_FW_NAME BNO055_FW_EXT) + > > + BNO055_UID_LEN * 2 + 1 + 1]; > > Perhaps devm_kasprintf()? Wouldn't this keep the buffer allocated until the device is removed? We just need this buffer while probing. > ... > > > + memset(priv, 0, sizeof(*priv)); > > Why?! IIRC a leftover from a time in which nothing worked and I was doubting about priv being really zeroed at allocation time. Will remove.. > ... > > > + if (IS_ERR(rst) && (PTR_ERR(rst) != -EPROBE_DEFER)) { > > + dev_err(dev, "Failed to get reset GPIO"); > > + return PTR_ERR(rst); > > + } > > if (IS_ERR(...)) > return dev_err_probe(...); Sure; I never saw this before, but it looks good. > ... > > > + if (IS_ERR(priv->clk) && (PTR_ERR(priv->clk) != -EPROBE_DEFER)) { > > + dev_err(dev, "Failed to get CLK"); > > + return PTR_ERR(priv->clk); > > + } > > Ditto. Sure > ... > > > + clk_prepare_enable(priv->clk); > > Missed clk_disabled_unprepare() from all below error paths. > Use devm_add_action_or_reset() approach. I indeed use devm_add_action_or_reset(), and clock is disabled and unprepared in its action callback (but snooping in Alexandru's comments in next mail I see there is still something to be addressed about this). > ... > > > + dev_dbg(dev, "Found BMO055 chip"); > > Useless noise and LOC. Will drop it. > ... > > > + dev_info(dev, "unique ID: %*ph", BNO055_UID_LEN, priv->uid); > > Can it be printed somewhere together with firmware revision? Yes, I can group those two prints. > ... > > > + sprintf(fw_name_buf, BNO055_FW_NAME "-%*phN" BNO055_FW_EXT, > > Simply define a format string as FW_FMT somewhere above and use it here. OK (but BNO055_FW_NAME and BNO055_FW_EXT contribute to build up two different strings, so they will not go away, rather they will be used in both FW_FMT and the other one definitions) > > + BNO055_UID_LEN, priv->uid); > > ... > > > + 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"); > > '\n' exists on purpose. Ah, I initially did that, but IIRC I saw a bad-looking indentation or something like that in my kernel log while using "\n".. Then I just forgot about this.. I'll try again with "\n".. > ... > > > +#include <linux/device.h> > > No user of this. Will drop this > > +#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; > > -- > With Best Regards, > Andy Shevchenko
Il giorno ven 16 lug 2021 alle ore 09:24 Alexandru Ardelean <ardeleanalex@gmail.com> ha scritto: > > On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <andrea.merello@gmail.com> 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. Hi, Well, indeed (unless I did some mistake, which is very possible), here the lock is simply around every access to the IMU. This is because there are places (e.g. in_calibration_data_show() ) in which we switch the IMU to configuration mode, we do something, and then we switch the IMU back to normal operation mode. The lock prevent any other access to the IMU during these procedures. The very same lock also guarantees serialization for all IMU operations that comes in behalf of userspace. Note that putting the lock at a lower lever (e.g. in register access functions) wouldn't suffice because of the configuration mode swich said above. > 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@iit.it> > > Cc: Andrea Merello <andrea.merello@gmail.com> > > Cc: Rob Herring <robh+dt@kernel.org> > > Cc: Matt Ranostay <matt.ranostay@konsulko.com> > > Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > > Cc: Vlad Dogaru <vlad.dogaru@intel.com> > > Cc: linux-kernel@vger.kernel.org > > Cc: linux-iio@vger.kernel.org > > --- > > 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@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/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). > Ah, thank you for the explanation; I wasn't aware of this. I'll do this way. > > > > +} > > + > > +#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 Right. Surprisingly, my compiler doesn't complain; I also would have expected a warning to be spitted. > > +} > > + > > +#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. I'll drop it. > > + 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 Here it doesn't, but I agree that it isn't so good-looking. I'd add the default also for IIO_ANGL_VEL case. > > + default: > > + return -EINVAL; > > + } > > + > > + return 0; > > This return also looks unreachable. Indeed.. I'll drop it. > > +} > > + > > +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@iit.it>"); > > +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 > >
On Fri, Jul 16, 2021 at 11:19:31AM +0200, Andrea Merello wrote: > Il giorno gio 15 lug 2021 alle ore 18:50 Andy Shevchenko > <andy.shevchenko@gmail.com> ha scritto: > > On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <andrea.merello@gmail.com> wrote: ... > > > Cc: linux-kernel@vger.kernel.org > > > Cc: linux-iio@vger.kernel.org > > > > Instead of polluting commit messages with this, use --to and --cc > > parameters. You may utilize my script [1] which finds automatically to > > whom to send (of course it allows manually to add more). > > > > [1]: https://github.com/andy-shev/home-bin-tools/blob/master/ge2maintainer.sh > > I thought it was a good & widespread practice, sorry. Will drop from > future series respin. It's good to notify people who either maintainer or involved enough. Putting mailing lists into Cc explicitly is not good practice. ... > > > +#define BNO055_UNIT_SEL_ANDROID BIT(7) > > > > Android? What does this mean? > > Sensors support the so-called "Android" and "Windows" modes. They > differs about pitch direction (CW vs CCW). I'd like to stick close to > the datasheet names, but I can add a comment here. Keeping as in the data sheet is okay, but since it's very confusing naming, the comment in the is a must. ... > > Useless parentheses. If the LEN is a plain number, use decimal, if > > it's limited by register width, use the form of (BIT(x) - 1). In such > > a case it's easy to see how many bits are used for it. > > It's byte number, defined by how many 8-bits registers make up the > UID. I'll go for a decimal and I'll drop the parentheses. 15 seems the right one then? ... > > > + int i; > > > + int best_idx, best_delta, delta; > > > + int first = 1; > > > > Use reversed xmas tree order. > > Looks like the kernel code is plenty of declarations in random order, > neither I can find any clue about this in coding-style.rst. Where does > this come from? Easier to read. Just from practice and wishes of several maintainers. > If that's mandatory then I'll do. If that's about a mere preference, > then I honestly prefer to put all declaration-plus-initialization > after all declarations-olny (but I can use reversed xmas tree order > inside each block, if you want). I'm not a maintainer here, but for the record I do not like this style at all. ... > > > + if ((reg >= 0x8 && reg <= 0x3A) || > > > > Use names instead of values here and in similar places elsewhere. > > When I wrote this, I was actually unsure about which is best :) Do you > have a strong opinion on this? > > My point: > Most of this is just about register areas, which are bounded by > addresses - register meaning is of no interest here. Using numerical > addresses here IMO is a little advantageous because it is at least > clear which is the greatest number, and it is less prone to swapping > by mistake start/end registers vrt greater-than/lesser-than comparison > operators. > > It's still true that when comparing address against a specific > register address (e.g. reg == BNO055_MAG_CONFIG_REG) there is no > advantage in using numerical addresses, and it can be better to use > names (because e.g. you simply know that specific register is > volatile). Try your best here. Magic numbers in general are not good. ... > > > + /* 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)) > > > > Please, split this to 3 or more conditionals that are easier to read > > (logically separated). > > Same comment to the rest of the similar functions. > > Do you mean splitting into separate if statements? OK. Yes. Several if:s. ... > > > + if (res && res != -ERESTARTSYS) { > > > > Shouldn't RESTARTSYS be handled on a regmap level? > > Can you please elaborate on this? I meant if you need to take care about this it seems to me that it has to be thought of on regmap level. I.o.w. what is the rationale behind this additional check? ... > > Sounds like NIH hex2bin(). > > Indeed.. I've failed to find out this helper. Looking at the code it > seems it wouldn't work as drop-in replacement here, because of spaces > in the HEX string. But I might just decide to format the HEX string > without spaces in order to being able to use hex2bin(). I'm not even sure why it's in ASCII instead being directly binary file. ... > > > + for (i = 0; i < 4; i++) > > > + vals[i] = (s16)le16_to_cpu(raw_vals[i]); > > > > Extract this to be a helper like there are for u32 and u64. > > Could you please point me to those helpers? I don't know what you are > referring to. Read include/linux/byteorder/generic.h to the end. ... > > > + vals[1] = 1 << 14; > > > > BIT(14) But still magic. > > Why magic? there is a comment a few line above explaining this - maybe > I can move it a couple of LOCs below. And BTW conceptually it is about > math (2^14), it has nothing to do with BITs. I see. Up to you then. ... > > IIO core should do this, besides the fact that it must use sysfs_emit(). > > Ditto for the similar. > > Ok for sysfs_emit(), thanks. But what do you mean with "IIO core > should do this"? Can you please elaborate? I believe that IIO has a generic method to print tables via sysfs. AFAIR it is done via "_avail". ... > > > + static const char * const calib_status[] = {"bad", "barely enough", > > > + "fair", "good"}; > > > > Please use better indentation > > > > static char ... foo[] = { > > { a, b, c, d, } > > }; > > OK, but why nested parentheses? In this case is not needed, just typed as more often seen pattern :) ... > > Isn't it better to use the request_firmware() interface or something similar? > > No: I already use request_firmware() for getting the initial > calibration data (if any), but the IMU sometimes (re)calibrates. This > function is for getting current IMU calibration, so we need to read it > from registers (especially you want to get it the 1st time, in order > to create the calibration file that request_firmware() will fetch next > time you boot). > > > If IIO doesn't provide the common attributes for this it probably > > should and it has to be a binary one. > > I couldn't find anything for it. I wasn't sure whether exposing > calibration data using IIO attribute is something that other drivers > might need to do, hence whether it could make sense to make it generic > or not.. I wasn't even sure that an IIO attribute is the right place > to expose it, indeed :) Okay, this is for maintainers to decide. ... > > > + * 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. > > > > Your lines here like a drunk person. use the space more monotonically. > > Do you mean: Assuming you are sticking to the old 80-cols-long lines > (which is still do, unless in few cases), then some lines still have > room for more words, and you could reflow the text ? Yes. ... > > > + while (!finish) { > > > + end = find_next_zero_bit(iio_dev->active_scan_mask, > > > + iio_dev->masklength, start); > > > + if (end == iio_dev->masklength) { > > > + finish = true; > > > > NIH for_each_clear_bit(). > > Not sure it is convenient to use for_each_clear_bit(): Here we're > searching for contiguous blocks of set-bits, and we only want indexes > of first and last set-bit in a block; alternate calls to > find_next_zero_bit() and find_next_bit() seem appropriate here to me. > > Do you have in mind a better/simpler implementation using or_each_clear_bit() ? bitmap_find_next_zero_area(buf, len, pos, n, mask) Find bit free area bitmap_find_next_zero_area_off(buf, len, pos, n, mask, mask_off) bitmap_next_clear_region(map, &start, &end, nbits) Find next clear region bitmap_next_set_region(map, &start, &end, nbits) Find next set region bitmap_for_each_clear_region(map, rs, re, start, end) Iterate over all clear regions bitmap_for_each_set_region(map, rs, re, start, end) ... > > > + /* base name + separator + UID + ext + zero */ > > > + char fw_name_buf[sizeof(BNO055_FW_NAME BNO055_FW_EXT) + > > > + BNO055_UID_LEN * 2 + 1 + 1]; > > > > Perhaps devm_kasprintf()? > > Wouldn't this keep the buffer allocated until the device is removed? > We just need this buffer while probing. You free to call devm_kfree() when appropriate. ... > > > + sprintf(fw_name_buf, BNO055_FW_NAME "-%*phN" BNO055_FW_EXT, > > > > Simply define a format string as FW_FMT somewhere above and use it here. > > OK (but BNO055_FW_NAME and BNO055_FW_EXT contribute to build up two > different strings, so they will not go away, rather they will be used > in both FW_FMT and the other one definitions) #define _FW_FMT \ _FW_NAME "..." _FW_EXT ? > > > + BNO055_UID_LEN, priv->uid); ... > > > +#include <linux/device.h> > > > > No user of this. > > Will drop this I think you will need to replace it with a forward declaration. > > > +int bno055_probe(struct device *dev, struct regmap *regmap, int irq, > > > + int xfer_burst_break_thr);
On Thu, 15 Jul 2021 16:17:40 +0200 Andrea Merello <andrea.merello@gmail.com> 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. > > 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, Yuck. > quaternions, That's better :) I don't suppose we could insist that people don't do anything so silly as using euler angles by just not providing them? :) > 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@iit.it> Obviously Andy and Alex did a pretty detailed reviews of this so I might be a little lazy until a later version... But I'll take a scan read through so I know whats coming and if I notice anything will comment on it. One bit thing in here is that any non standard ABI needs documentation. It's very had to discuss whether we can accept the additions based on code. Basic rule of thumb is that nonstandard ABI will only be used by your own code. If you want this to be generally useful, then we need to figure out how to standardise things or map to existing ABI. > Cc: Andrea Merello <andrea.merello@gmail.com> > Cc: Rob Herring <robh+dt@kernel.org> > Cc: Matt Ranostay <matt.ranostay@konsulko.com> > Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > Cc: Vlad Dogaru <vlad.dogaru@intel.com> > Cc: linux-kernel@vger.kernel.org > Cc: linux-iio@vger.kernel.org > --- > 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" I'm not sure I'd bother with a directory for this one. > > 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@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/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", Don't mix and match aligning rvalue and not. Personally I prefer not trying to do pretty aligning as it normally needs to later noise when the whole lot lead realigning because we've set something else! > + .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) { I think Andy asked about these, so I won't repeat... Nice to get rid of those and just be able to make the regmap calls inline though... > + 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 */ Nice :) > + 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. Why might you? good to have a default that is what people most commonly want... If there is a usecase for this then it may be better to have a 'disable autocalibration and manually reload a fixed calibration' path. > + */ > + 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); I'm not seeing where the action this is unwinding occurs. It's uncommon to have a devm cleanup function that does two things like this which makes me suspicious about potential races. > + > + clk_disable_unprepare(priv->clk); > +} > + > +#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), Euler angles don't map to axis. If it were doing angle/axis then that would be a natural mapping, but it's not. > + 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, Ideally shouldn't need mask and shift as can normally extract the shift from the mask. This seems far more complex than I'd expect to see. It may well be both more readable and less error prone to just spend the extra lines of code to lay this out as more standard functions for each case. > + 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; if returning IIO_VAL_INT, no need to set *val2 as nothing will read it. As such, you should be able to skip the default setting above. > + > + *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; > +} > + > +#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); A comment for the negative is probably a good thing to have. > + } > + *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: Hmm. Rot is currently defined in the ABI docs only for compass rotations. If you would fix that it would be much appreciated. We also have usecases for quaternion which is well defined and for tilt angle, but not as far as I can see a euler angle use case. We need to close that gap which needs 3 more modifiers to specify which angle is which. Or we could tell people to learn how to deal with rotations in a safe and reliable way with out gimbal lock ;) > + /* > + * 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; > + 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); > + } > + default: > + return -EINVAL; > + } > + > + return 0; > +} > + > +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"); Hmm. This is going to be very hard for userspace apps to know what to do with. 99% of the time you are going to end up with the default as a result. If there is any way to map these to actual features enabled, then that will make them more likely to be used as will map to standard ABI. > + > +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, There is a bunch of ABI here that either belongs in as _avail callbacks etc or is non standard an hence needs documentation under Documentation/ABI/testing/sysfs-bus-iio* > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr, Hmm. Range typically maps to something else (normally scale, but these smart sensors can do weird things) > + &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]; Does this have potential holes? I'm guessing it probable does. As such you want to memset the whole thing to 0 in order to ensure you can't leak kernel data. One of the advantages of putting this in the priv() structure rather than on the stack is that you can rely on that being zeroed once and after that all you can leak is stale readings which are very unlikely to be a security issue! Note that you would have a problem even without holes if only some channels are enabled. > + 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)); No need. It is kzalloc'd by the IIO core. > + 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)) { Why carry on if we get DEFER? If that happens we want to return it and back off for now. dev_err_probe() will handle only printing no defer errors. > + 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"); I'd clean this sort of debug out from a final submission. It's kind of handy during driver writing, but very unlikely to be much use to anyone after the driver 'works'. > + > + 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); As below, looks like debugfs material rather than kernel log. > + > + /* > + * This has nothing to do with the IMU firmware, this is for sensor > + * calibration data. Interesting. So we have some similar cases where we use sysfs to load this sort of calibration data. That's on the basis we are getting it from there in the first place and it may want tweaking at runtime. Does this need to be in place before we initialize the device? > + */ > + 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); Some of these don't need wrapping. > + if (res) > + return res; > + > + dev_info(dev, "Firmware version %x.%x", ver, rev); May be better exposed in debugfs so it is available when needed but doesn't make the kernel log noisier than necessary. > + > + 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@iit.it>"); > +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> Just use struct device; and don't include 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
Just few inline comments; implicitly OK for others. Il giorno ven 16 lug 2021 alle ore 14:39 Andy Shevchenko <andy.shevchenko@gmail.com> ha scritto: > > > > > Useless parentheses. If the LEN is a plain number, use decimal, if > > > it's limited by register width, use the form of (BIT(x) - 1). In such > > > a case it's easy to see how many bits are used for it. > > > > It's byte number, defined by how many 8-bits registers make up the > > UID. I'll go for a decimal and I'll drop the parentheses. > > 15 seems the right one then? Isn't it 16? From my understanding of the datasheet registers involved are from 0x50 to 0x5F. > > > > > + if (res && res != -ERESTARTSYS) { > > > > > > Shouldn't RESTARTSYS be handled on a regmap level? > > > > Can you please elaborate on this? > > I meant if you need to take care about this it seems to me that it has to be > thought of on regmap level. I.o.w. what is the rationale behind this additional > check? The regmap_bus write() and read() implementations wait for an interruptible completion, which is completed when a response from the IMU is received. In practice by hitting Ctrl-C at the "right" moment I got my kernel log polluted with dev_err() telling me the regmap operation failed, but in this specific case there was nothing wrong: it's just being aborted. Still, in all other error case I would like to know. This is the rationale behind this check. The ERESTARTSYS error have anyway to actually propagate in order to notify the caller that the read/write just didn't complete. If you mean move the check+dev_err() in bno055_sl.c regmap_bus read() and write() ops, that is fine; my original point for putting it where it is now, was because I was wondering whether this has to be common to the (not yet here) I2C support code. > ... > > > > Sounds like NIH hex2bin(). > > > > Indeed.. I've failed to find out this helper. Looking at the code it > > seems it wouldn't work as drop-in replacement here, because of spaces > > in the HEX string. But I might just decide to format the HEX string > > without spaces in order to being able to use hex2bin(). > > I'm not even sure why it's in ASCII instead being directly binary file. That was almost a coin-flip for me. Just, being a few bytes, I decided to make them printable: If I load this driver for the 1st time, and start poking around in it's sysfs, cat-ting random stuff to give a look, I would just find a HEX line more friendly that a binary chunk on my console. .. But If you think it's better, I'll go for binary without any hesitation.. > > > > IIO core should do this, besides the fact that it must use sysfs_emit(). > > > Ditto for the similar. > > > > Ok for sysfs_emit(), thanks. But what do you mean with "IIO core > > should do this"? Can you please elaborate? > > I believe that IIO has a generic method to print tables via sysfs. AFAIR it is > done via "_avail". Ah, do you refer to the read_avail() operation in iio_info? I'll try to go with it; I wasn't aware of that, thank you.
Il giorno sab 17 lug 2021 alle ore 17:30 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Thu, 15 Jul 2021 16:17:40 +0200 > Andrea Merello <andrea.merello@gmail.com> 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. > > > > 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, > > Yuck. > > > quaternions, > > That's better :) I don't suppose we could insist that people don't do anything > so silly as using euler angles by just not providing them? :) They are just handy to cat in combination with the watch command when you are playing with your new bno055-equipped board, and look at them change :) > > 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@iit.it> > > Obviously Andy and Alex did a pretty detailed reviews of this so I might be a little > lazy until a later version... But I'll take a scan read through so I know whats > coming and if I notice anything will comment on it. > > One bit thing in here is that any non standard ABI needs documentation. > It's very had to discuss whether we can accept the additions based on code. > Basic rule of thumb is that nonstandard ABI will only be used by your own > code. If you want this to be generally useful, then we need to figure out > how to standardise things or map to existing ABI. I wasn't sure my new ABIs were useful for others. If you think that's the case, then we can see how to make it generic. I'll add docs in next series respin. > > Cc: Andrea Merello <andrea.merello@gmail.com> > > Cc: Rob Herring <robh+dt@kernel.org> > > Cc: Matt Ranostay <matt.ranostay@konsulko.com> > > Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > > Cc: Vlad Dogaru <vlad.dogaru@intel.com> > > Cc: linux-kernel@vger.kernel.org > > Cc: linux-iio@vger.kernel.org > > --- > > 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" > I'm not sure I'd bother with a directory for this one. > > > > 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@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/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", > > Don't mix and match aligning rvalue and not. Personally I prefer > not trying to do pretty aligning as it normally needs to later noise > when the whole lot lead realigning because we've set something else! > > > + .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) { > > I think Andy asked about these, so I won't repeat... > Nice to get rid of those and just be able to make the regmap calls inline though... Ok for inline. I've just answered in another mail to Andy's comments for the rest. > > > + 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 */ > > Nice :) > > > + 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. > > Why might you? good to have a default that is what people most commonly want... > If there is a usecase for this then it may be better to have a 'disable autocalibration > and manually reload a fixed calibration' path. I'm not sure whether disabling autocalibration for magnetometer is just a matter of saving some power, or whether this has the purpose of carefully doing the calibration far from magnetic disturbances, avoiding screwing the calibration every time you briefly pass by a piece of iron. I think I found some clues for this second interpretation poking on the internet, but I don't know whether they were right. Do you know anything about this? If we assume that disabling autocalibration is of almost no use, I may just drop support for this, otherwise if we think that sticking with initial calibration data is a possilble use case, then I would find it a bit twisted to: let the driver load initial calibration, let the IMU possibly tweak it, then disable autocalibration, then ask to realod the calibration data.. Isn't that more straightforward to let the driver load the initial calibration, and then enable autocalibration.. If you want a separate attribute for disabling/enabling calibration, then it is OK; just it would only work in fusion mode, and if support for other IMU modes will be addeed (e.g. compass) it still would be unused except for one. > > + */ > > + 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); > I'm not seeing where the action this is unwinding occurs. > > It's uncommon to have a devm cleanup function that does two things like this > which makes me suspicious about potential races. Yes, Alex told me; I'll fix that.. > > + > > + clk_disable_unprepare(priv->clk); > > +} > > + > > +#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), > > Euler angles don't map to axis. If it were doing angle/axis then that > would be a natural mapping, but it's not. So do we need new modifiers like IIO_MOD_HEADING, IIO_MOD_YAW, IIO_MODE_ROLL? But aren't euler angles really rotations around each axis (so IMHO mapping them to axis would make sense).. FWIW the datasheet also reg names are in form of e.g BNO055_EUL_DATA_X_LSB_REG. > > + 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, > > Ideally shouldn't need mask and shift as can normally extract the shift > from the mask. OK > This seems far more complex than I'd expect to see. It may well > be both more readable and less error prone to just spend the extra > lines of code to lay this out as more standard functions for each > case. That would be a cut&paste of almost identical two same functions, except for register name and shift, repeated for 5 times. The only complication I see here, is the last param "k", which serve only for one case indeed. What about letting stay the generic helper for the 4 "plain" cases, and introducing separated dedicated functions for the only case that is a bit different (accelerometer lpf), getting rid of the last parameter "k"? > > > + 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; > > if returning IIO_VAL_INT, no need to set *val2 as nothing will read it. > As such, you should be able to skip the default setting above. Ah OK! > > + > > + *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; > > +} > > + > > +#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); > > A comment for the negative is probably a good thing to have. Of course > > + } > > + *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: > > Hmm. Rot is currently defined in the ABI docs only for compass rotations. > If you would fix that it would be much appreciated. > > We also have usecases for quaternion which is well defined and for tilt > angle, but not as far as I can see a euler angle use case. > > We need to close that gap which needs 3 more modifiers to specify which > angle is which. Or we could tell people to learn how to deal with > rotations in a safe and reliable way with out gimbal lock ;) Ah, you are answering here to what I asked you above :) I think that it would be much easier for me to update the DOC, rather than teaching people how to use quaternions ;) > > + /* > > + * 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; > > + 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); > > + } > > + default: > > + return -EINVAL; > > + } > > + > > + return 0; > > +} > > + > > +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"); > > Hmm. This is going to be very hard for userspace apps to know what to do with. > 99% of the time you are going to end up with the default as a result. > If there is any way to map these to actual features enabled, then that will make > them more likely to be used as will map to standard ABI. As long as we have only those modes, then maybe yes: We can have two attributes "fusion_enable" and "mag_autocal_enable" (or something like that). But it would probably become more difficoult to support other IMU modes HW provides. Also I wonder if that wouldn't get things more difficoult to understand: changing modes have side effects (for example enabling fusion mode locks settings for accelerometer and gyroscope); even if we document them, I guess that someone might want to read the IMU datasheet (and the information scattered on the internet) to better understand what fits her/his usecase. If we stick with the idea of "modes" it would be much easier to make the link wrt documentation. > > + > > +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, > > There is a bunch of ABI here that either belongs in as _avail callbacks etc > or is non standard an hence needs documentation under > Documentation/ABI/testing/sysfs-bus-iio* Will check.. > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr, > > Hmm. Range typically maps to something else (normally scale, but these smart > sensors can do weird things) Here the scaling doesn't change, just the range. I *think* that by changing range you also get better or worse precision. > > + &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]; > > Does this have potential holes? I'm guessing it probable does. As such > you want to memset the whole thing to 0 in order to ensure you can't leak > kernel data. One of the advantages of putting this in the priv() > structure rather than on the stack is that you can rely on that being zeroed > once and after that all you can leak is stale readings which are very unlikely > to be a security issue! Note that you would have a problem even without holes > if only some channels are enabled. I'm not sure if there are holes, but I see your point. I will go for a zeroed buffer in priv. > > + 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)); > > No need. It is kzalloc'd by the IIO core. OK > > + 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)) { > > Why carry on if we get DEFER? If that happens we want to return it > and back off for now. dev_err_probe() will handle only printing no defer errors. Sure. This is obviously a bug :) I'll go with dev_err_probe(). > > + 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"); > > I'd clean this sort of debug out from a final submission. It's kind > of handy during driver writing, but very unlikely to be much use > to anyone after the driver 'works'. Altought I really cannot understand why maintainers tends to ask for killing log prints, even if they are silenced out by not enabling debug prints, (indeed people are supposed to enable debug prints when thinks breaks, and they want as much clues as possible), but OK, I'll kill it. > > + > > + 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); > > As below, looks like debugfs material rather than kernel log. No, this is needed: the calibration data has to be stored in a file in /lib/firmware; the driver looks, in sequence, for two file names; the first one has the unique id embedded in the name. So you need to know it.. If you have more than one IMU connected to your CPU, then you really need several calibration files, one for each IMU, and the unique ID distinguish them (on the other hand, if you have only one, you can fallback to the second file name, which does not contain the ID).. I think I have explained this stuff in the series cover letter. Maybe we can expose this in an attribute instead of printing in the kernel log? > > + > > + /* > > + * This has nothing to do with the IMU firmware, this is for sensor > > + * calibration data. > > Interesting. So we have some similar cases where we use sysfs to load > this sort of calibration data. That's on the basis we are getting > it from there in the first place and it may want tweaking at runtime. > Does this need to be in place before we initialize the device? Unsure if we can reload it after initialization; possbly yes. But I don't see any reason to start with an uncalibrated IMU; I really would love to get correct data as soon as I read them :) .. But IMHO: AFAIK firmware interface is here explicitly also to assist loading calibration data; why to reinvent the wheel ? BTW, if we really think someone want to tweak calibration data, then we can make the calibration data attribute R/W anyway (this doesn't need the initial load to be dropped). In this case, wouldn't it be better to stick with HEX format, rather than to switch to binary, as being discussed in other mails? > > + */ > > + 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); > > Some of these don't need wrapping. OK > > > > + if (res) > > + return res; > > + > > + dev_info(dev, "Firmware version %x.%x", ver, rev); > > May be better exposed in debugfs so it is available when needed but doesn't make the > kernel log noisier than necessary. OK > > + > > + 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@iit.it>"); > > +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> > > Just use > struct device; > and don't include device.h. OK > > +#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 >
Il giorno gio 15 lug 2021 alle ore 18:50 Andy Shevchenko <andy.shevchenko@gmail.com> ha scritto: > > > +/* 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); > > kmemdup() ? > As a second thought: no, the whole point of reallocating and copying here, is that we want to allocate an extra byte; kmemdup() will allocate and copy only the very same amount of memory
> > > > + for (i = 0; i < 4; i++) > > > > + vals[i] = (s16)le16_to_cpu(raw_vals[i]); > > > > > > Extract this to be a helper like there are for u32 and u64. > > > > Could you please point me to those helpers? I don't know what you are > > referring to. > > Read include/linux/byteorder/generic.h to the end. I realized that implementing an helper like the other ones wouldn't work in this specific case: I'd say a reasonable helper would take a ptr to a s16 as its destination argument, but here we are assigning to a int vector; so our brand new helper would be of no use here. What I can do is to implement a macroized helper that would have no issues wrt ptr type; I guess something like this: #define le16_to_cpu_signed_array(dst, src, len) \ ({ \ size_t __i; \ for (__i = 0; __i < len; __i++) \ dst[__i] = (s16)le16_to_cpu(src[__i]); \ }) What's your opinion here? should I go with something like this or do you prefer to let the open-coded implementation stay in this specific case?
On Mon, Jul 19, 2021 at 11:02:07AM +0200, Andrea Merello wrote: > Il giorno gio 15 lug 2021 alle ore 18:50 Andy Shevchenko > <andy.shevchenko@gmail.com> ha scritto: > > > > > > +/* 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); > > > > kmemdup() ? > > > > As a second thought: no, the whole point of reallocating and copying > here, is that we want to allocate an extra byte; kmemdup() will > allocate and copy only the very same amount of memory kmemdup_nul() then.
Il giorno lun 19 lug 2021 alle ore 13:48 Andy Shevchenko <andy.shevchenko@gmail.com> ha scritto: > > On Mon, Jul 19, 2021 at 11:02:07AM +0200, Andrea Merello wrote: > > Il giorno gio 15 lug 2021 alle ore 18:50 Andy Shevchenko > > <andy.shevchenko@gmail.com> ha scritto: > > > > > > > > > +/* 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); > > > > > > kmemdup() ? > > > > > > > As a second thought: no, the whole point of reallocating and copying > > here, is that we want to allocate an extra byte; kmemdup() will > > allocate and copy only the very same amount of memory > > kmemdup_nul() then. That's one seems suitable. Thank you. > > -- > With Best Regards, > Andy Shevchenko > >
On Mon, 19 Jul 2021 10:30:26 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > Il giorno sab 17 lug 2021 alle ore 17:30 Jonathan Cameron > <jic23@kernel.org> ha scritto: > > > > On Thu, 15 Jul 2021 16:17:40 +0200 > > Andrea Merello <andrea.merello@gmail.com> 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. > > > > > > 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, > > > > Yuck. > > > > > quaternions, > > > > That's better :) I don't suppose we could insist that people don't do anything > > so silly as using euler angles by just not providing them? :) > > They are just handy to cat in combination with the watch command when > you are playing with your new bno055-equipped board, and look at them > change :) Meh. Quaternions are fine once you get your head around them ;) > > > > 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@iit.it> > > > > Obviously Andy and Alex did a pretty detailed reviews of this so I might be a little > > lazy until a later version... But I'll take a scan read through so I know whats > > coming and if I notice anything will comment on it. > > > > One bit thing in here is that any non standard ABI needs documentation. > > It's very had to discuss whether we can accept the additions based on code. > > Basic rule of thumb is that nonstandard ABI will only be used by your own > > code. If you want this to be generally useful, then we need to figure out > > how to standardise things or map to existing ABI. > > I wasn't sure my new ABIs were useful for others. If you think that's > the case, then we can see how to make it generic. I'll add docs in > next series respin. > > > > Cc: Andrea Merello <andrea.merello@gmail.com> > > > Cc: Rob Herring <robh+dt@kernel.org> > > > Cc: Matt Ranostay <matt.ranostay@konsulko.com> > > > Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> > > > Cc: Vlad Dogaru <vlad.dogaru@intel.com> > > > Cc: linux-kernel@vger.kernel.org > > > Cc: linux-iio@vger.kernel.org > > > --- > > > 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" > > I'm not sure I'd bother with a directory for this one. > > > > > > 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@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/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", > > > > Don't mix and match aligning rvalue and not. Personally I prefer > > not trying to do pretty aligning as it normally needs to later noise > > when the whole lot lead realigning because we've set something else! > > > > > + .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) { > > > > I think Andy asked about these, so I won't repeat... > > Nice to get rid of those and just be able to make the regmap calls inline though... > > Ok for inline. I've just answered in another mail to Andy's comments > for the rest. > > > > > > + 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 */ > > > > Nice :) > > > > > + 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. > > > > Why might you? good to have a default that is what people most commonly want... > > If there is a usecase for this then it may be better to have a 'disable autocalibration > > and manually reload a fixed calibration' path. > > I'm not sure whether disabling autocalibration for magnetometer is > just a matter of saving some power, or whether this has the purpose of > carefully doing the calibration far from magnetic disturbances, > avoiding screwing the calibration every time you briefly pass by a > piece of iron. I think I found some clues for this second > interpretation poking on the internet, but I don't know whether they > were right. It's possible if the calibration routines have much faster response than you'd normally expect. > > Do you know anything about this? > > If we assume that disabling autocalibration is of almost no use, I may > just drop support for this, otherwise if we think that sticking with > initial calibration data is a possilble use case, then I would find it > a bit twisted to: let the driver load initial calibration, let the IMU > possibly tweak it, then disable autocalibration, then ask to realod > the calibration data.. Isn't that more straightforward to let the > driver load the initial calibration, and then enable autocalibration.. Hmm. Tricky. My gut feeling is turn it on by default but maybe it's rubbish :) > > If you want a separate attribute for disabling/enabling calibration, > then it is OK; just it would only work in fusion mode, and if support > for other IMU modes will be addeed (e.g. compass) it still would be > unused except for one. That's fine. We have lots of ABI that returns an error if it's not possible in a particular setup. > > > > + */ > > > + 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); > > I'm not seeing where the action this is unwinding occurs. > > > > It's uncommon to have a devm cleanup function that does two things like this > > which makes me suspicious about potential races. > > Yes, Alex told me; I'll fix that.. > > > > + > > > + clk_disable_unprepare(priv->clk); > > > +} > > > + > > > +#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), > > > > Euler angles don't map to axis. If it were doing angle/axis then that > > would be a natural mapping, but it's not. > > So do we need new modifiers like IIO_MOD_HEADING, IIO_MOD_YAW, > IIO_MODE_ROLL? Yes. Probably PITCH rather than HEADING though if we are using the other two terms. > But aren't euler angles really rotations around each > axis (so IMHO mapping them to axis would make sense).. It's rather messy because it's defined not in terms of rotations about fixed axis, but rather 3 separate rotations about the sensor coordinate frame done one after another. Nice animation on wikipedia on this. https://en.wikipedia.org/wiki/Euler_angles#/media/File:Euler2a.gif That isn't an obvious mapping to x y and z in the fashion we'd use them for accelerations where they are the measurements in perpendicular directions. So, Euler angle axis Y rotation has no direct relationship to either gyro or accelerometer measurements using axis Y. Ultimately you end up with ambiguities where you rotate one axis onto another. >FWIW the > datasheet also reg names are in form of e.g BNO055_EUL_DATA_X_LSB_REG. Gah! > > > > + 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, > > > > Ideally shouldn't need mask and shift as can normally extract the shift > > from the mask. > > OK > > > This seems far more complex than I'd expect to see. It may well > > be both more readable and less error prone to just spend the extra > > lines of code to lay this out as more standard functions for each > > case. > > That would be a cut&paste of almost identical two same functions, > except for register name and shift, repeated for 5 times. The only > complication I see here, is the last param "k", which serve only for > one case indeed. What about letting stay the generic helper for the 4 > "plain" cases, and introducing separated dedicated functions for the > only case that is a bit different (accelerometer lpf), getting rid of > the last parameter "k"? Sounds better. > > > > > > + 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; > > > > if returning IIO_VAL_INT, no need to set *val2 as nothing will read it. > > As such, you should be able to skip the default setting above. > > Ah OK! > > > > + > > > + *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; > > > +} > > > + > > > +#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); > > > > A comment for the negative is probably a good thing to have. > > Of course > > > > + } > > > + *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: > > > > Hmm. Rot is currently defined in the ABI docs only for compass rotations. > > If you would fix that it would be much appreciated. > > > > We also have usecases for quaternion which is well defined and for tilt > > angle, but not as far as I can see a euler angle use case. > > > > We need to close that gap which needs 3 more modifiers to specify which > > angle is which. Or we could tell people to learn how to deal with > > rotations in a safe and reliable way with out gimbal lock ;) > > Ah, you are answering here to what I asked you above :) > I think that it would be much easier for me to update the DOC, rather > than teaching people how to use quaternions ;) They should learn - don't be soft on them! If their drone crashes because of gimbal lock, then they will realise one reason why it matters :) > > > > + /* > > > + * 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; > > > + 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); > > > + } > > > + default: > > > + return -EINVAL; > > > + } > > > + > > > + return 0; > > > +} > > > + > > > +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"); > > > > Hmm. This is going to be very hard for userspace apps to know what to do with. > > 99% of the time you are going to end up with the default as a result. > > If there is any way to map these to actual features enabled, then that will make > > them more likely to be used as will map to standard ABI. > > As long as we have only those modes, then maybe yes: > We can have two attributes "fusion_enable" and "mag_autocal_enable" > (or something like that). > But it would probably become more difficoult to support other IMU > modes HW provides. > > Also I wonder if that wouldn't get things more difficoult to > understand: changing modes have side effects (for example enabling > fusion mode locks settings for accelerometer and gyroscope); even if > we document them, I guess that someone might want to read the IMU > datasheet (and the information scattered on the internet) to better > understand what fits her/his usecase. If we stick with the idea of > "modes" it would be much easier to make the link wrt documentation. No one reads datasheets :) Well other than us obviously... If you do want to do this then you need to be very very careful that the defaults correspond to what that non data sheet reading user is most likely to want. That is hard to guess, so if you can map at least some of it onto controls that are in the main ABI that is helpful in that normal tools will understand most of that and hopefully do the right thing. > > > > + > > > +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, > > > > There is a bunch of ABI here that either belongs in as _avail callbacks etc > > or is non standard an hence needs documentation under > > Documentation/ABI/testing/sysfs-bus-iio* > > Will check.. > > > > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, > > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr, > > > > Hmm. Range typically maps to something else (normally scale, but these smart > > sensors can do weird things) > > Here the scaling doesn't change, just the range. I *think* that by > changing range you also get better or worse precision. oh goody. Make sure the default is maximum range + when you document this we will have to be careful to make it clear we don't want this to be used in drivers where scale is an option. Perhaps we just put it in a device specific ABI file. > > > > + &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]; > > > > Does this have potential holes? I'm guessing it probable does. As such > > you want to memset the whole thing to 0 in order to ensure you can't leak > > kernel data. One of the advantages of putting this in the priv() > > structure rather than on the stack is that you can rely on that being zeroed > > once and after that all you can leak is stale readings which are very unlikely > > to be a security issue! Note that you would have a problem even without holes > > if only some channels are enabled. > > I'm not sure if there are holes, but I see your point. I will go for a > zeroed buffer in priv. > > > > + 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)); > > > > No need. It is kzalloc'd by the IIO core. > > OK > > > > + 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)) { > > > > Why carry on if we get DEFER? If that happens we want to return it > > and back off for now. dev_err_probe() will handle only printing no defer errors. > > Sure. This is obviously a bug :) I'll go with dev_err_probe(). > > > > + 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"); > > > > I'd clean this sort of debug out from a final submission. It's kind > > of handy during driver writing, but very unlikely to be much use > > to anyone after the driver 'works'. > > Altought I really cannot understand why maintainers tends to ask for > killing log prints, even if they are silenced out by not enabling > debug prints, (indeed people are supposed to enable debug prints when > thinks breaks, and they want as much clues as possible), but OK, I'll > kill it. The log thing is because we all watch far too many kernel logs scroll by often over rubbish serial ports :) Not having is just me preferring slightly shorter code when the debug doesn't add anything. I'm fine with this sort of thing if it also provides some info beyond 'got here'. > > > > + > > > + 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); > > > > As below, looks like debugfs material rather than kernel log. > > No, this is needed: the calibration data has to be stored in a file in > /lib/firmware; the driver looks, in sequence, for two file names; the > first one has the unique id embedded in the name. So you need to know > it.. > > If you have more than one IMU connected to your CPU, then you really > need several calibration files, one for each IMU, and the unique ID > distinguish them (on the other hand, if you have only one, you can > fallback to the second file name, which does not contain the ID).. I > think I have explained this stuff in the series cover letter. > > Maybe we can expose this in an attribute instead of printing in the kernel log? That would be better. It might be a valid use for the label attribute perhaps, or this something where custom ABI would be fine. > > > > + > > > + /* > > > + * This has nothing to do with the IMU firmware, this is for sensor > > > + * calibration data. > > > > Interesting. So we have some similar cases where we use sysfs to load > > this sort of calibration data. That's on the basis we are getting > > it from there in the first place and it may want tweaking at runtime. > > Does this need to be in place before we initialize the device? > > Unsure if we can reload it after initialization; possbly yes. But I > don't see any reason to start with an uncalibrated IMU; I really would > love to get correct data as soon as I read them :) > > .. But IMHO: AFAIK firmware interface is here explicitly also to > assist loading calibration data; why to reinvent the wheel ? > > BTW, if we really think someone want to tweak calibration data, then > we can make the calibration data attribute R/W anyway (this doesn't > need the initial load to be dropped). In this case, wouldn't it be > better to stick with HEX format, rather than to switch to binary, as > being discussed in other mails? I'm not sure it is much easier to edit hex than binary so that's not particularly important. If it makes sense to load at init then I'm fine with doing so. If we have to do an unbind and rebind to update live that might cover the few people who would want to tweak it. > > > > + */ > > > + 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); > > > > Some of these don't need wrapping. > > OK > > > > > > > > + if (res) > > > + return res; > > > + > > > + dev_info(dev, "Firmware version %x.%x", ver, rev); > > > > May be better exposed in debugfs so it is available when needed but doesn't make the > > kernel log noisier than necessary. > > OK > > > > + > > > + 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@iit.it>"); > > > +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> > > > > Just use > > struct device; > > and don't include device.h. > > OK > > > > +#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 > >
just a few of in-line comment below; OK for all the rest of your comment, thanks! > > > > +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) { > > > > > > I think Andy asked about these, so I won't repeat... > > > Nice to get rid of those and just be able to make the regmap calls inline though... > > > > Ok for inline. I've just answered in another mail to Andy's comments > > for the rest. Indeed, so far I couldn't understand what do you really mean. Should I move those check+dev_err() inside the regmap core layer ? > > > > + /* > > > > + * 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. > > > > > > Why might you? good to have a default that is what people most commonly want... > > > If there is a usecase for this then it may be better to have a 'disable autocalibration > > > and manually reload a fixed calibration' path. > > > > I'm not sure whether disabling autocalibration for magnetometer is > > just a matter of saving some power, or whether this has the purpose of > > carefully doing the calibration far from magnetic disturbances, > > avoiding screwing the calibration every time you briefly pass by a > > piece of iron. I think I found some clues for this second > > interpretation poking on the internet, but I don't know whether they > > were right. > > It's possible if the calibration routines have much faster response than > you'd normally expect. This HW function is called "Fast Magnetometer Calibration".. But I don't know how fast is it.. > > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, > > > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr, > > > > > > Hmm. Range typically maps to something else (normally scale, but these smart > > > sensors can do weird things) > > > > Here the scaling doesn't change, just the range. I *think* that by > > changing range you also get better or worse precision. > > oh goody. Make sure the default is maximum range + when you document this > we will have to be careful to make it clear we don't want this to be used in > drivers where scale is an option. Perhaps we just put it in a device > specific ABI file. > The default is to run the IMU with fusion mode enabled; in this mode those parameters are locked by the HW to a given value (which is not the maximum e.g. in case of accelerometer range). If the user disables the fusion mode, then those parameters become tweakable, but shouldn't they just remain at their previous values (the one set by fusion mode), unless the user change also them? I.o.w the only chance we have for assigning them a "default" value is when the fusion is switched off, but this would mean that switching off fusion mode also has a side effect on those values (which I'm unsure if we really want to happen). Andrea
On Mon, 26 Jul 2021 16:36:49 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > just a few of in-line comment below; OK for all the rest of your > comment, thanks! > > > > > > +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) { > > > > > > > > I think Andy asked about these, so I won't repeat... > > > > Nice to get rid of those and just be able to make the regmap calls inline though... > > > > > > Ok for inline. I've just answered in another mail to Andy's comments > > > for the rest. > > Indeed, so far I couldn't understand what do you really mean. Should I > move those check+dev_err() inside the regmap core layer ? Better to move any necessary print to the caller of the reg_write() where any error message can often give more information. Adding wrappers just to print an error message normally mostly serves to make the code a little harder to review. > > > > > > + /* > > > > > + * 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. > > > > > > > > Why might you? good to have a default that is what people most commonly want... > > > > If there is a usecase for this then it may be better to have a 'disable autocalibration > > > > and manually reload a fixed calibration' path. > > > > > > I'm not sure whether disabling autocalibration for magnetometer is > > > just a matter of saving some power, or whether this has the purpose of > > > carefully doing the calibration far from magnetic disturbances, > > > avoiding screwing the calibration every time you briefly pass by a > > > piece of iron. I think I found some clues for this second > > > interpretation poking on the internet, but I don't know whether they > > > were right. > > > > It's possible if the calibration routines have much faster response than > > you'd normally expect. > > This HW function is called "Fast Magnetometer Calibration".. But I > don't know how fast is it.. Nice - got to love informative datasheets :) > > > > > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, > > > > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr, > > > > > > > > Hmm. Range typically maps to something else (normally scale, but these smart > > > > sensors can do weird things) > > > > > > Here the scaling doesn't change, just the range. I *think* that by > > > changing range you also get better or worse precision. > > > > oh goody. Make sure the default is maximum range + when you document this > > we will have to be careful to make it clear we don't want this to be used in > > drivers where scale is an option. Perhaps we just put it in a device > > specific ABI file. > > > > The default is to run the IMU with fusion mode enabled; in this mode > those parameters are locked by the HW to a given value (which is not > the maximum e.g. in case of accelerometer range). > > If the user disables the fusion mode, then those parameters become > tweakable, but shouldn't they just remain at their previous values > (the one set by fusion mode), unless the user change also them? That makes sense to me. > > I.o.w the only chance we have for assigning them a "default" value is > when the fusion is switched off, but this would mean that switching > off fusion mode also has a side effect on those values (which I'm > unsure if we really want to happen). Thanks for the explanation. Ok. Fine to have the range here, but please sanity check we have appropriate ABI documentation in the main ABI doc Documentation/ABI/testing/sysfs-bus-iio. One thing to think about is how range would generalize. These sensors are symmetric, but not all are - a range that is higher in postive than negative is definitely possible. Perhaps we need to name it to make it clear we are talking magnitude here? Ideally it should state something about range should be used only when it has no affect on scaling. Hopefully we'll not get a device where it has an affect but it's non linear as that doesn't make any sense. I'd imagine a range control in hardware either is proportional to the scale, or has no affect on it as here. Thanks, Jonathan > > Andrea
Il giorno sab 31 lug 2021 alle ore 19:58 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Mon, 26 Jul 2021 16:36:49 +0200 > Andrea Merello <andrea.merello@gmail.com> wrote: > > > just a few of in-line comment below; OK for all the rest of your > > comment, thanks! > > > > > > > > +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) { > > > > > > > > > > I think Andy asked about these, so I won't repeat... > > > > > Nice to get rid of those and just be able to make the regmap calls inline though... > > > > > > > > Ok for inline. I've just answered in another mail to Andy's comments > > > > for the rest. > > > > Indeed, so far I couldn't understand what do you really mean. Should I > > move those check+dev_err() inside the regmap core layer ? > > Better to move any necessary print to the caller of the reg_write() where any > error message can often give more information. Adding wrappers just to print > an error message normally mostly serves to make the code a little harder to > review. Isn't this like doing a cut-and-paste of check+dev_err() in more than a dozen places in the code? If you just want more information about the caller then we could macroize those functions, so they can also print the caller code line number (or they could accept an additional argument, which is the code line number to print, and then a macro helper that adds that last argument can be used to invoke them).. But this wouldn't address your second point.. > > > > > > > > + /* > > > > > > + * 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. > > > > > > > > > > Why might you? good to have a default that is what people most commonly want... > > > > > If there is a usecase for this then it may be better to have a 'disable autocalibration > > > > > and manually reload a fixed calibration' path. > > > > > > > > I'm not sure whether disabling autocalibration for magnetometer is > > > > just a matter of saving some power, or whether this has the purpose of > > > > carefully doing the calibration far from magnetic disturbances, > > > > avoiding screwing the calibration every time you briefly pass by a > > > > piece of iron. I think I found some clues for this second > > > > interpretation poking on the internet, but I don't know whether they > > > > were right. > > > > > > It's possible if the calibration routines have much faster response than > > > you'd normally expect. > > > > This HW function is called "Fast Magnetometer Calibration".. But I > > don't know how fast is it.. > > Nice - got to love informative datasheets :) > > > > > > > > > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, > > > > > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr, > > > > > > > > > > Hmm. Range typically maps to something else (normally scale, but these smart > > > > > sensors can do weird things) > > > > > > > > Here the scaling doesn't change, just the range. I *think* that by > > > > changing range you also get better or worse precision. > > > > > > oh goody. Make sure the default is maximum range + when you document this > > > we will have to be careful to make it clear we don't want this to be used in > > > drivers where scale is an option. Perhaps we just put it in a device > > > specific ABI file. > > > > > > > The default is to run the IMU with fusion mode enabled; in this mode > > those parameters are locked by the HW to a given value (which is not > > the maximum e.g. in case of accelerometer range). > > > > If the user disables the fusion mode, then those parameters become > > tweakable, but shouldn't they just remain at their previous values > > (the one set by fusion mode), unless the user change also them? > > That makes sense to me. > > > > > I.o.w the only chance we have for assigning them a "default" value is > > when the fusion is switched off, but this would mean that switching > > off fusion mode also has a side effect on those values (which I'm > > unsure if we really want to happen). > > Thanks for the explanation. Ok. Fine to have the range here, but please > sanity check we have appropriate ABI documentation in the main ABI doc > Documentation/ABI/testing/sysfs-bus-iio. So you are inclined to generalize this thing, rather than keeping it as bno055 specific.. > One thing to think about is how range would generalize. These sensors are > symmetric, but not all are - a range that is higher in postive than negative > is definitely possible. Perhaps we need to name it to make it clear we are > talking magnitude here? I'm open to suggestions here. The word "range" in my mind just recalls the idea of a minimum/maximum interval in which the sensor works, without recalling me the idea of "scaling" or changes to the unit too much; this is why I originally chosen this name, but that might be just mine personal taste. > Ideally it should state something about range should be used only when it has no > affect on scaling. Hopefully we'll not get a device where it has an affect > but it's non linear as that doesn't make any sense. I'd imagine a range > control in hardware either is proportional to the scale, or has no affect on > it as here. I may have a wrong memory here, but I think I've seen something like this on some current-sense chips, which were available for various current ranges, but their output scale changed in non linear way wrt the range. Or I can imagine a device in which you can set the internal amplifier gain, and this will affect both the scale (because of the amplifier gain itself) and the range (because the amplifier output does saturate at a different voltage) with a linear relationship, but there is also another bound: the physical sensor attached to the amplifier mechanically clamp to a given max value, that happen to be less than the saturation point of the amp at is minimum gain. This would lead to a non-linear net relationship between scale and range because at the minimum gain the range doesn't extends as much as you would expect. .. But even in non linear cases, just having the scale may be OK.. Maybe we can just say that range can be used only if there are at least two values for which we have the same scale value; otherwise just having the scale attribute would suffice and thus we stick on that.. We may think about allowing a RO range attribute, alongside the scale attribute, for non obvious cases. > Thanks, > > Jonathan > > > > > Andrea >
On Wed, 4 Aug 2021 12:06:46 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > Il giorno sab 31 lug 2021 alle ore 19:58 Jonathan Cameron > <jic23@kernel.org> ha scritto: > > > > On Mon, 26 Jul 2021 16:36:49 +0200 > > Andrea Merello <andrea.merello@gmail.com> wrote: > > > > > just a few of in-line comment below; OK for all the rest of your > > > comment, thanks! > > > > > > > > > > +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) { > > > > > > > > > > > > I think Andy asked about these, so I won't repeat... > > > > > > Nice to get rid of those and just be able to make the regmap calls inline though... > > > > > > > > > > Ok for inline. I've just answered in another mail to Andy's comments > > > > > for the rest. > > > > > > Indeed, so far I couldn't understand what do you really mean. Should I > > > move those check+dev_err() inside the regmap core layer ? > > > > Better to move any necessary print to the caller of the reg_write() where any > > error message can often give more information. Adding wrappers just to print > > an error message normally mostly serves to make the code a little harder to > > review. > > Isn't this like doing a cut-and-paste of check+dev_err() in more than > a dozen places in the code? > > If you just want more information about the caller then we could > macroize those functions, so they can also print the caller code line > number (or they could accept an additional argument, which is the code > line number to print, and then a macro helper that adds that last > argument can be used to invoke them).. But this wouldn't address your > second point.. It's a trade off between reviewability which these wrappers make worse and short code. My personal preference is don't bother with messages on simple reg read /write failures. If it happens you either get an error reported to userspace and can do some more debug, or the driver doesn't probe - again, more debug to be done even if you know it was a read or write. > > > > > > > > > > > + /* > > > > > > > + * 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. > > > > > > > > > > > > Why might you? good to have a default that is what people most commonly want... > > > > > > If there is a usecase for this then it may be better to have a 'disable autocalibration > > > > > > and manually reload a fixed calibration' path. > > > > > > > > > > I'm not sure whether disabling autocalibration for magnetometer is > > > > > just a matter of saving some power, or whether this has the purpose of > > > > > carefully doing the calibration far from magnetic disturbances, > > > > > avoiding screwing the calibration every time you briefly pass by a > > > > > piece of iron. I think I found some clues for this second > > > > > interpretation poking on the internet, but I don't know whether they > > > > > were right. > > > > > > > > It's possible if the calibration routines have much faster response than > > > > you'd normally expect. > > > > > > This HW function is called "Fast Magnetometer Calibration".. But I > > > don't know how fast is it.. > > > > Nice - got to love informative datasheets :) > > > > > > > > > > > > > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, > > > > > > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr, > > > > > > > > > > > > Hmm. Range typically maps to something else (normally scale, but these smart > > > > > > sensors can do weird things) > > > > > > > > > > Here the scaling doesn't change, just the range. I *think* that by > > > > > changing range you also get better or worse precision. > > > > > > > > oh goody. Make sure the default is maximum range + when you document this > > > > we will have to be careful to make it clear we don't want this to be used in > > > > drivers where scale is an option. Perhaps we just put it in a device > > > > specific ABI file. > > > > > > > > > > The default is to run the IMU with fusion mode enabled; in this mode > > > those parameters are locked by the HW to a given value (which is not > > > the maximum e.g. in case of accelerometer range). > > > > > > If the user disables the fusion mode, then those parameters become > > > tweakable, but shouldn't they just remain at their previous values > > > (the one set by fusion mode), unless the user change also them? > > > > That makes sense to me. > > > > > > > > I.o.w the only chance we have for assigning them a "default" value is > > > when the fusion is switched off, but this would mean that switching > > > off fusion mode also has a side effect on those values (which I'm > > > unsure if we really want to happen). > > > > Thanks for the explanation. Ok. Fine to have the range here, but please > > sanity check we have appropriate ABI documentation in the main ABI doc > > Documentation/ABI/testing/sysfs-bus-iio. > > So you are inclined to generalize this thing, rather than keeping it > as bno055 specific.. My mistake. I thought we had it already defined but seems we have avoided it even though we have gotten close a few times ;) sysfs-bus-iio-bno055 should be fine > > > One thing to think about is how range would generalize. These sensors are > > symmetric, but not all are - a range that is higher in postive than negative > > is definitely possible. Perhaps we need to name it to make it clear we are > > talking magnitude here? > > I'm open to suggestions here. It's challenging to define a clean way of specifying two linked values. We could define rangemax, rangemin and rely on the fact that any bit of IIO ABI can affect other bits, but that's messy given our only current user doesn't care. > > The word "range" in my mind just recalls the idea of a minimum/maximum > interval in which the sensor works, without recalling me the idea of > "scaling" or changes to the unit too much; this is why I originally > chosen this name, but that might be just mine personal taste. We may regret it but let's stick with your range definition as is, with suitable docs and keeping it device specific for now. > > > Ideally it should state something about range should be used only when it has no > > affect on scaling. Hopefully we'll not get a device where it has an affect > > but it's non linear as that doesn't make any sense. I'd imagine a range > > control in hardware either is proportional to the scale, or has no affect on > > it as here. > > I may have a wrong memory here, but I think I've seen something like > this on some current-sense chips, which were available for various > current ranges, but their output scale changed in non linear way wrt > the range. > > Or I can imagine a device in which you can set the internal amplifier > gain, and this will affect both the scale (because of the amplifier > gain itself) and the range (because the amplifier output does saturate > at a different voltage) with a linear relationship, but there is also > another bound: the physical sensor attached to the amplifier > mechanically clamp to a given max value, that happen to be less than > the saturation point of the amp at is minimum gain. This would lead to > a non-linear net relationship between scale and range because at the > minimum gain the range doesn't extends as much as you would expect. > > .. But even in non linear cases, just having the scale may be OK.. > Maybe we can just say that range can be used only if there are at > least two values for which we have the same scale value; otherwise > just having the scale attribute would suffice and thus we stick on > that.. We may think about allowing a RO range attribute, alongside the > scale attribute, for non obvious cases. For read only, you can use the read_avail approach to provide a range for the actual channel. We do this for various in kernel consumer users that need to know the range of values they might get. That is only ever read only though, because of the difficulty of allowing rights to two bounds together. So we have devices where changing the scale will result in inX_raw_avail changing (IIRC) and that's not necessarily linear. Jonathan > > > > Thanks, > > > > Jonathan > > > > > > > > Andrea > >
On Wed, Aug 4, 2021 at 7:51 PM Jonathan Cameron <Jonathan.Cameron@huawei.com> wrote: > On Wed, 4 Aug 2021 12:06:46 +0200 > Andrea Merello <andrea.merello@gmail.com> wrote: > > Il giorno sab 31 lug 2021 alle ore 19:58 Jonathan Cameron > > <jic23@kernel.org> ha scritto: ... > > Isn't this like doing a cut-and-paste of check+dev_err() in more than > > a dozen places in the code? > > > > If you just want more information about the caller then we could > > macroize those functions, so they can also print the caller code line > > number (or they could accept an additional argument, which is the code > > line number to print, and then a macro helper that adds that last > > argument can be used to invoke them).. But this wouldn't address your > > second point.. > It's a trade off between reviewability which these wrappers make worse > and short code. > > My personal preference is don't bother with messages on simple reg read /write > failures. If it happens you either get an error reported to userspace and > can do some more debug, or the driver doesn't probe - again, more debug to > be done even if you know it was a read or write. The advantage of regmap is that it has already established trace events. No need to add additional stuff (at least it's easy to see, read or write or what values were there). I personally put messages on regmap reads and writes in the specific cases only, when it indeed may shed a light on some events.
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@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/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); +} + +#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; +} + +#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; + 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); + } + default: + return -EINVAL; + } + + return 0; +} + +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@iit.it>"); +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
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. 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@iit.it> Cc: Andrea Merello <andrea.merello@gmail.com> Cc: Rob Herring <robh+dt@kernel.org> Cc: Matt Ranostay <matt.ranostay@konsulko.com> Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Cc: Vlad Dogaru <vlad.dogaru@intel.com> Cc: linux-kernel@vger.kernel.org Cc: linux-iio@vger.kernel.org --- 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