LKML Archive on lore.kernel.org help / color / mirror / Atom feed
* [PATCH 0/4] Add support for Bosch BNO055 IMU @ 2021-07-15 14:17 Andrea Merello 2021-07-15 14:17 ` [PATCH 1/4] iio: add modifiers for linear acceleration Andrea Merello ` (4 more replies) 0 siblings, 5 replies; 89+ messages in thread From: Andrea Merello @ 2021-07-15 14:17 UTC (permalink / raw) To: jic23, lars Cc: robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello This series (tries to) add support for Bosch BNO055 IMU to Linux IIO subsystem. It is made up four patches: 1/4: adds some IIO modifiers to the IIO core layer, in order to being able to expose the linear acceleration among standard attributes. 2/4: adds the core IIO BNO055 driver 3/4: adds DT bindings for the serdev BNO055 driver - Note: I've put as "maintainer" the one that ./scripts/get_maintainer.pl suggested to me, but I wasn't sure about this. 4/4: adds serdev BNO055 driver to actually use the IMU via serial line Previously at least another driver for the very same chip has been posted to the Linux ML [0], but it has been never merged, and it seems no one cared of it since quite a long time. This driver differs from the above driver on the following aspects: - This driver supports serial access, instead of I2C access (however it is designed in a modular way that should make it easy to eventually add I2C support later on). - The above driver tried to support all IMU HW modes by allowing to choose one in the DT, and adapting IIO attributes accordingly. This driver does not rely on DT for this, instead an attribute "operation_mode" is exposed. All IIO attributes used in all modes are exposed; more on this later on. This driver BTW supports only a subset of the HW-supported modes. - This driver has some support for managing the IMU calibration This driver supports three operation modes: - AMG (accelerometer, magnetometer and gyroscope) mode, which provides raw measurements from the said sensors, and allows for setting some parameters about them (e.g. filter cut-off frequency, max sensor ranges, etc). - Two flavors of fusion mode, which still provide AMG measures, while they also provide other data calculated by the IMU (e.g. rotation angles, linear acceleration, etc). In this mode user has no freedom to set any sensor parameter, since the HW locks them. IIO attributes exposing sensors parameters are always present, but in fusion modes the available values are constrained to just the one used by the HW. This is reflected in the '*_available' IIO attributes. Trying to set a not-supported value always falls back to the closest supported one, which in this case is just the one in use by the HW. IIO attributes for unavailable measurements (e.g. Euler angles in AMG mode) just read zero (which is consistent WRT what you get when reading from a buffer with those attributes enabled). Another option could be make them return -EINVAL or to make those attributes reading as "N/A" or something like that, but in those cases I wouldn't know what to do with buffers.. About the IMU calibration: The IMU supports for two sets of calibration parameters: - SIC matrix, which has to be provided by the user, and that this driver doesn't currently care about of (yet) - offset and radius parameters, which the IMU can automatically find out, and for which this driver has support The driver provides access to autocalibration flags (i.e. you can known if the IMU has successfully autocalibrated) and to calibration data blob. The user can save this blob in a "firmware" file (i.e. in /lib/firmware) that the driver looks for at probe time. If found, then the IMU is initialized with this calibration data. This saves the user from performing the calibration procedure every time (which consist of moving the IMU in various way). The driver looks for calibration data file using two different names: first a file whose name is suffixed with the IMU unique ID is searched for; this is useful when there is more than one IMU instance. If this file is not found, then a "generic" calibration file is searched for (which can be used when only one IMU is present, without struggling with fancy names, that change on each device). In fusion modes the HW applies calibration data to all measurements, while in AMG mode the calibration data is not used by the HW. The IIO 'offset' attributes provide access to the offsets from calibration data, so that the user can apply them to the accel, angvel and magn IIO attributes. There is an hack here: since the HW does already apply the offsets when in fusion modes, then we report 'zero' for all offsets when we are in fusion modes. In this way offsets can be always applied by the user and this leads to correct results in all operation modes. Alternatively we could try to un-correct the measures when in fusion mode, and let the offset attributes to report actual calibration offset, but this seems awkward to me, and I can see some problems about races WRT HW autocalibration (which always run in background anyway). The last alternative could be to expose two sets of IIO attributes for accel, anglvel and magn measures, which would refer to the same registers indeed; one attribute set has associated offsets, while the other hasn't. Then we make only one set "readable", depending by the current operation mode. Unfortunately they would all still be "raw" anyway, since they still need to get the 'scale' attribute applied. Furthermore this may complicate things with buffer and bursts handling. I've implemented the 1st solution because IMHO it's simpler and still reasonably coherent. About access protocol and serdev module: The serial protocol is quite simple, but there are tricks to make it really works. Those tricks and workarounds are documented in the driver source file. The core BNO055 driver tries to group readings in burst when appropriate, in order to optimize triggered buffer operation. The threshold for splitting a burst (i.e. max number of unused bytes in the middle of a burst that will be throw away) is provided to the core driver by the lowlevel access driver (which is the bno055_sl serdev driver indeed) at probe time. If an I2C access driver will be written, then it can provide its own threshold value (which depends by the protocol overhead, etc..) to the core bno055 driver. Finally, I wrote and tested this driver on a Zynq-based custom board that runs a kernel v5.4 and I've rebased on latest mainline tree on which I've just compile-tested. [0] https://www.spinics.net/lists/linux-iio/msg25508.html Andrea Merello (4): iio: add modifiers for linear acceleration iio: imu: add Bosch Sensortec BNO055 core driver dt-bindings: iio: imu: add bosch BNO055 serdev driver bindings iio: imu: add BNO055 serdev driver .../bindings/iio/imu/bosch,bno055-serial.yaml | 40 + drivers/iio/imu/Kconfig | 1 + drivers/iio/imu/Makefile | 1 + drivers/iio/imu/bno055/Kconfig | 12 + drivers/iio/imu/bno055/Makefile | 7 + drivers/iio/imu/bno055/bno055.c | 1361 +++++++++++++++++ drivers/iio/imu/bno055/bno055.h | 12 + drivers/iio/imu/bno055/bno055_sl.c | 576 +++++++ drivers/iio/industrialio-core.c | 3 + include/uapi/linux/iio/types.h | 4 +- 10 files changed, 2016 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml 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 create mode 100644 drivers/iio/imu/bno055/bno055_sl.c -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH 1/4] iio: add modifiers for linear acceleration 2021-07-15 14:17 [PATCH 0/4] Add support for Bosch BNO055 IMU Andrea Merello @ 2021-07-15 14:17 ` Andrea Merello 2021-07-17 14:32 ` Jonathan Cameron 2021-07-15 14:17 ` [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver Andrea Merello ` (3 subsequent siblings) 4 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-07-15 14:17 UTC (permalink / raw) To: jic23, lars Cc: robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello, Andrea Merello This patch is preparatory for adding the Bosh BNO055 IMU driver. The said IMU can report raw accelerations (among x, y and z axis) as well as the so called "linear accelerations" (again, among x, y and z axis) which is basically the acceleration after subtracting gravity. This patch adds IIO_MOD_ACCEL_LINEAR_X, IIO_MOD_ACCEL_LINEAR_Y and IIO_MOD_ACCEL_LINEAR_Z modifiers to te IIO core. 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/industrialio-core.c | 3 +++ include/uapi/linux/iio/types.h | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 6d2175eb7af2..e378f48240ad 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -134,6 +134,9 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_ETHANOL] = "ethanol", [IIO_MOD_H2] = "h2", [IIO_MOD_O2] = "o2", + [IIO_MOD_ACCEL_LINEAR_X] = "linear_x", + [IIO_MOD_ACCEL_LINEAR_Y] = "linear_y", + [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z" }; /* relies on pairs of these shared then separate */ diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index 48c13147c0a8..db00f7c45f48 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -95,6 +95,9 @@ enum iio_modifier { IIO_MOD_ETHANOL, IIO_MOD_H2, IIO_MOD_O2, + IIO_MOD_ACCEL_LINEAR_X, + IIO_MOD_ACCEL_LINEAR_Y, + IIO_MOD_ACCEL_LINEAR_Z, }; enum iio_event_type { @@ -114,4 +117,3 @@ enum iio_event_direction { }; #endif /* _UAPI_IIO_TYPES_H_ */ - -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 1/4] iio: add modifiers for linear acceleration 2021-07-15 14:17 ` [PATCH 1/4] iio: add modifiers for linear acceleration Andrea Merello @ 2021-07-17 14:32 ` Jonathan Cameron 0 siblings, 0 replies; 89+ messages in thread From: Jonathan Cameron @ 2021-07-17 14:32 UTC (permalink / raw) To: Andrea Merello Cc: lars, robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello On Thu, 15 Jul 2021 16:17:39 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > This patch is preparatory for adding the Bosh BNO055 IMU driver. > The said IMU can report raw accelerations (among x, y and z axis) > as well as the so called "linear accelerations" (again, among x, > y and z axis) which is basically the acceleration after subtracting > gravity. > > This patch adds IIO_MOD_ACCEL_LINEAR_X, IIO_MOD_ACCEL_LINEAR_Y and > IIO_MOD_ACCEL_LINEAR_Z modifiers to te IIO core. > > 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/industrialio-core.c | 3 +++ > include/uapi/linux/iio/types.h | 4 +++- > 2 files changed, 6 insertions(+), 1 deletion(-) > > diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c > index 6d2175eb7af2..e378f48240ad 100644 > --- a/drivers/iio/industrialio-core.c > +++ b/drivers/iio/industrialio-core.c > @@ -134,6 +134,9 @@ static const char * const iio_modifier_names[] = { > [IIO_MOD_ETHANOL] = "ethanol", > [IIO_MOD_H2] = "h2", > [IIO_MOD_O2] = "o2", > + [IIO_MOD_ACCEL_LINEAR_X] = "linear_x", > + [IIO_MOD_ACCEL_LINEAR_Y] = "linear_y", > + [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z" So this is an interesting question. Should we treat this as 'modified' or is it better treated as a different channel type? I guess it's similar in some ways to the magn varients for magnetic north and true north, so we have precedence for modifier. It's also a little bit similar to calculating illuminance from intensity channels which we did as two different channel types. Anyhow, I'm fine with this, but open to hearing opinions from others! Either way we definitely need something new. Thanks, Jonathan > }; > > /* relies on pairs of these shared then separate */ > diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h > index 48c13147c0a8..db00f7c45f48 100644 > --- a/include/uapi/linux/iio/types.h > +++ b/include/uapi/linux/iio/types.h > @@ -95,6 +95,9 @@ enum iio_modifier { > IIO_MOD_ETHANOL, > IIO_MOD_H2, > IIO_MOD_O2, > + IIO_MOD_ACCEL_LINEAR_X, > + IIO_MOD_ACCEL_LINEAR_Y, > + IIO_MOD_ACCEL_LINEAR_Z, > }; > > enum iio_event_type { > @@ -114,4 +117,3 @@ enum iio_event_direction { > }; > > #endif /* _UAPI_IIO_TYPES_H_ */ > - ^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-15 14:17 [PATCH 0/4] Add support for Bosch BNO055 IMU Andrea Merello 2021-07-15 14:17 ` [PATCH 1/4] iio: add modifiers for linear acceleration Andrea Merello @ 2021-07-15 14:17 ` Andrea Merello 2021-07-15 16:49 ` Andy Shevchenko ` (2 more replies) 2021-07-15 14:17 ` [PATCH 3/4] dt-bindings: iio: imu: add bosch BNO055 serdev driver bindings Andrea Merello ` (2 subsequent siblings) 4 siblings, 3 replies; 89+ messages in thread From: Andrea Merello @ 2021-07-15 14:17 UTC (permalink / raw) To: jic23, lars Cc: robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello, Andrea Merello 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 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 -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-15 14:17 ` [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver Andrea Merello @ 2021-07-15 16:49 ` Andy Shevchenko 2021-07-16 9:19 ` Andrea Merello 2021-07-19 9:02 ` Andrea Merello 2021-07-16 7:24 ` Alexandru Ardelean 2021-07-17 15:32 ` Jonathan Cameron 2 siblings, 2 replies; 89+ messages in thread From: Andy Shevchenko @ 2021-07-15 16:49 UTC (permalink / raw) To: Andrea Merello Cc: jic23, lars, robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello 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; -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-15 16:49 ` Andy Shevchenko @ 2021-07-16 9:19 ` Andrea Merello 2021-07-16 12:39 ` Andy Shevchenko 2021-07-19 9:02 ` Andrea Merello 1 sibling, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-07-16 9:19 UTC (permalink / raw) To: Andy Shevchenko Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, andriy.shevchenko, linux-kernel, linux-iio, Andrea Merello 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 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-16 9:19 ` Andrea Merello @ 2021-07-16 12:39 ` Andy Shevchenko 2021-07-19 7:12 ` Andrea Merello 2021-07-19 10:33 ` Andrea Merello 0 siblings, 2 replies; 89+ messages in thread From: Andy Shevchenko @ 2021-07-16 12:39 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, linux-kernel, linux-iio, Andrea Merello 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); -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-16 12:39 ` Andy Shevchenko @ 2021-07-19 7:12 ` Andrea Merello 2021-07-19 10:33 ` Andrea Merello 1 sibling, 0 replies; 89+ messages in thread From: Andrea Merello @ 2021-07-19 7:12 UTC (permalink / raw) To: Andy Shevchenko Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, linux-kernel, linux-iio, Andrea Merello 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. ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-16 12:39 ` Andy Shevchenko 2021-07-19 7:12 ` Andrea Merello @ 2021-07-19 10:33 ` Andrea Merello 1 sibling, 0 replies; 89+ messages in thread From: Andrea Merello @ 2021-07-19 10:33 UTC (permalink / raw) To: Andy Shevchenko Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, linux-kernel, linux-iio, Andrea Merello > > > > + 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? ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-15 16:49 ` Andy Shevchenko 2021-07-16 9:19 ` Andrea Merello @ 2021-07-19 9:02 ` Andrea Merello 2021-07-19 11:48 ` Andy Shevchenko 1 sibling, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-07-19 9:02 UTC (permalink / raw) To: Andy Shevchenko Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello 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 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-19 9:02 ` Andrea Merello @ 2021-07-19 11:48 ` Andy Shevchenko 2021-07-19 13:13 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Andy Shevchenko @ 2021-07-19 11:48 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello 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. -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-19 11:48 ` Andy Shevchenko @ 2021-07-19 13:13 ` Andrea Merello 0 siblings, 0 replies; 89+ messages in thread From: Andrea Merello @ 2021-07-19 13:13 UTC (permalink / raw) To: Andy Shevchenko Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, linux-kernel, linux-iio, Andrea Merello 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 > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-15 14:17 ` [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver Andrea Merello 2021-07-15 16:49 ` Andy Shevchenko @ 2021-07-16 7:24 ` Alexandru Ardelean 2021-07-16 9:49 ` Andrea Merello 2021-07-17 15:32 ` Jonathan Cameron 2 siblings, 1 reply; 89+ messages in thread From: Alexandru Ardelean @ 2021-07-16 7:24 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, matt.ranostay, Andy Shevchenko, vlad.dogaru, LKML, linux-iio, Andrea Merello 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 > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-16 7:24 ` Alexandru Ardelean @ 2021-07-16 9:49 ` Andrea Merello 0 siblings, 0 replies; 89+ messages in thread From: Andrea Merello @ 2021-07-16 9:49 UTC (permalink / raw) To: Alexandru Ardelean Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, vlad.dogaru, LKML, linux-iio, Andrea Merello 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 > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-15 14:17 ` [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver Andrea Merello 2021-07-15 16:49 ` Andy Shevchenko 2021-07-16 7:24 ` Alexandru Ardelean @ 2021-07-17 15:32 ` Jonathan Cameron 2021-07-19 8:30 ` Andrea Merello 2 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-07-17 15:32 UTC (permalink / raw) To: Andrea Merello Cc: lars, robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello 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 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-17 15:32 ` Jonathan Cameron @ 2021-07-19 8:30 ` Andrea Merello 2021-07-24 17:08 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-07-19 8:30 UTC (permalink / raw) To: Jonathan Cameron Cc: Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, linux-kernel, linux-iio, Andrea Merello 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 > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-19 8:30 ` Andrea Merello @ 2021-07-24 17:08 ` Jonathan Cameron 2021-07-26 14:36 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-07-24 17:08 UTC (permalink / raw) To: Andrea Merello Cc: Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, linux-kernel, linux-iio, Andrea Merello 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 > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-24 17:08 ` Jonathan Cameron @ 2021-07-26 14:36 ` Andrea Merello 2021-07-31 18:01 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-07-26 14:36 UTC (permalink / raw) To: Jonathan Cameron Cc: Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, linux-kernel, linux-iio, Andrea Merello 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 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-26 14:36 ` Andrea Merello @ 2021-07-31 18:01 ` Jonathan Cameron 2021-08-04 10:06 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-07-31 18:01 UTC (permalink / raw) To: Andrea Merello Cc: Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, linux-kernel, linux-iio, Andrea Merello 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 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-07-31 18:01 ` Jonathan Cameron @ 2021-08-04 10:06 ` Andrea Merello 2021-08-04 16:50 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-08-04 10:06 UTC (permalink / raw) To: Jonathan Cameron Cc: Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, linux-kernel, linux-iio, Andrea Merello 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 > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-08-04 10:06 ` Andrea Merello @ 2021-08-04 16:50 ` Jonathan Cameron 2021-08-04 19:27 ` Andy Shevchenko 0 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-08-04 16:50 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, linux-kernel, linux-iio, Andrea Merello 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 > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver 2021-08-04 16:50 ` Jonathan Cameron @ 2021-08-04 19:27 ` Andy Shevchenko 0 siblings, 0 replies; 89+ messages in thread From: Andy Shevchenko @ 2021-08-04 19:27 UTC (permalink / raw) To: Jonathan Cameron Cc: Andrea Merello, Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, linux-kernel, linux-iio, Andrea Merello 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. -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH 3/4] dt-bindings: iio: imu: add bosch BNO055 serdev driver bindings 2021-07-15 14:17 [PATCH 0/4] Add support for Bosch BNO055 IMU Andrea Merello 2021-07-15 14:17 ` [PATCH 1/4] iio: add modifiers for linear acceleration Andrea Merello 2021-07-15 14:17 ` [PATCH 2/4] iio: imu: add Bosch Sensortec BNO055 core driver Andrea Merello @ 2021-07-15 14:17 ` Andrea Merello 2021-07-17 15:39 ` Jonathan Cameron 2021-07-15 14:17 ` [PATCH 4/4] iio: imu: add BNO055 serdev driver Andrea Merello 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello 4 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-07-15 14:17 UTC (permalink / raw) To: jic23, lars Cc: robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello, Andrea Merello Introduce new documentation file for the BNO055 serdev driver that will be included in next patches of this same series 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 --- .../bindings/iio/imu/bosch,bno055-serial.yaml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml diff --git a/Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml b/Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml new file mode 100644 index 000000000000..743c784ebc94 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/imu/bosch,bno055-serial.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Serial-attached Bosch BNO055 + +maintainers: + - Jonathan Cameron <jic23@kernel.org> + +description: | + Inertial Measurement Unit with Accelerometer, Gyroscope, Magnetometer and + internal MCU for sensor fusion + https://www.bosch-sensortec.com/products/smart-sensors/bno055/ + +properties: + compatible: + enum: + - bosch,bno055-serial + + reset-gpios: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + bno055 { + compatible = "bosch,bno055-serial"; + reset-gpios = <&gpio0 54 GPIO_ACTIVE_LOW>; + clocks = <&imu_clk>; + }; -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 3/4] dt-bindings: iio: imu: add bosch BNO055 serdev driver bindings 2021-07-15 14:17 ` [PATCH 3/4] dt-bindings: iio: imu: add bosch BNO055 serdev driver bindings Andrea Merello @ 2021-07-17 15:39 ` Jonathan Cameron 2021-07-19 8:44 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-07-17 15:39 UTC (permalink / raw) To: Andrea Merello Cc: lars, robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello On Thu, 15 Jul 2021 16:17:41 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > Introduce new documentation file for the BNO055 serdev driver that will dt bindings are for the device not the driver (so don't mention driver in the binding or the patch description). > be included in next patches of this same series > > 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 > --- > .../bindings/iio/imu/bosch,bno055-serial.yaml | 40 +++++++++++++++++++ > 1 file changed, 40 insertions(+) > create mode 100644 Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml > > diff --git a/Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml b/Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml > new file mode 100644 > index 000000000000..743c784ebc94 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml Better to have just one doc covering this interface and i2c if that gets added. > @@ -0,0 +1,40 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/iio/imu/bosch,bno055-serial.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Serial-attached Bosch BNO055 > + > +maintainers: > + - Jonathan Cameron <jic23@kernel.org> That's just mean! I have plenty of these to look after already! Joking aside, you'd be a better maintainer for this than me as more likely to pay attention. > + > +description: | > + Inertial Measurement Unit with Accelerometer, Gyroscope, Magnetometer and > + internal MCU for sensor fusion > + https://www.bosch-sensortec.com/products/smart-sensors/bno055/ > + > +properties: > + compatible: > + enum: > + - bosch,bno055-serial > + > + reset-gpios: > + maxItems: 1 > + > + clocks: > + maxItems: 1 > + > +required: > + - compatible > + > +additionalProperties: false > + > +examples: > + - | > + #include <dt-bindings/gpio/gpio.h> > + bno055 { name needs to be the one for the device type found in the device tree spec or if it's not there, something in same 'spirit'. Probably imu here > + compatible = "bosch,bno055-serial"; Don't need the -serial. It will bind based on the bus this is under. Speaking of which, it's normal to provide that bus info as part of the example. See for example chemical/sensiron,scd30.yaml > + reset-gpios = <&gpio0 54 GPIO_ACTIVE_LOW>; > + clocks = <&imu_clk>; > + }; ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 3/4] dt-bindings: iio: imu: add bosch BNO055 serdev driver bindings 2021-07-17 15:39 ` Jonathan Cameron @ 2021-07-19 8:44 ` Andrea Merello 0 siblings, 0 replies; 89+ messages in thread From: Andrea Merello @ 2021-07-19 8:44 UTC (permalink / raw) To: Jonathan Cameron Cc: Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello Il giorno sab 17 lug 2021 alle ore 17:36 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Thu, 15 Jul 2021 16:17:41 +0200 > Andrea Merello <andrea.merello@gmail.com> wrote: > > > Introduce new documentation file for the BNO055 serdev driver that will > dt bindings are for the device not the driver (so don't mention driver > in the binding or the patch description). Ah, right > > be included in next patches of this same series > > > > 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 > > --- > > .../bindings/iio/imu/bosch,bno055-serial.yaml | 40 +++++++++++++++++++ > > 1 file changed, 40 insertions(+) > > create mode 100644 Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml > > > > diff --git a/Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml b/Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml > > new file mode 100644 > > index 000000000000..743c784ebc94 > > --- /dev/null > > +++ b/Documentation/devicetree/bindings/iio/imu/bosch,bno055-serial.yaml > > Better to have just one doc covering this interface and i2c if that gets added. OK > > @@ -0,0 +1,40 @@ > > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > > +%YAML 1.2 > > +--- > > +$id: http://devicetree.org/schemas/iio/imu/bosch,bno055-serial.yaml# > > +$schema: http://devicetree.org/meta-schemas/core.yaml# > > + > > +title: Serial-attached Bosch BNO055 > > + > > +maintainers: > > + - Jonathan Cameron <jic23@kernel.org> > > That's just mean! I have plenty of these to look after already! Joking > aside, you'd be a better maintainer for this than me as more likely > to pay attention. Ok. I was really embarrassed about this: didn't want to proclaim me as a maintainer, neither I wanted me to decide something about you :) > > + > > +description: | > > + Inertial Measurement Unit with Accelerometer, Gyroscope, Magnetometer and > > + internal MCU for sensor fusion > > + https://www.bosch-sensortec.com/products/smart-sensors/bno055/ > > + > > +properties: > > + compatible: > > + enum: > > + - bosch,bno055-serial > > + > > + reset-gpios: > > + maxItems: 1 > > + > > + clocks: > > + maxItems: 1 > > + > > +required: > > + - compatible > > + > > +additionalProperties: false > > + > > +examples: > > + - | > > + #include <dt-bindings/gpio/gpio.h> > > + bno055 { > > name needs to be the one for the device type found in the device tree spec or > if it's not there, something in same 'spirit'. Probably imu here OK > > + compatible = "bosch,bno055-serial"; > Don't need the -serial. It will bind based on the bus this is under. > Speaking of which, it's normal to provide that bus info as part of the example. > See for example chemical/sensiron,scd30.yaml OK > > + reset-gpios = <&gpio0 54 GPIO_ACTIVE_LOW>; > > + clocks = <&imu_clk>; > > + }; > ^ permalink raw reply [flat|nested] 89+ messages in thread
* [PATCH 4/4] iio: imu: add BNO055 serdev driver 2021-07-15 14:17 [PATCH 0/4] Add support for Bosch BNO055 IMU Andrea Merello ` (2 preceding siblings ...) 2021-07-15 14:17 ` [PATCH 3/4] dt-bindings: iio: imu: add bosch BNO055 serdev driver bindings Andrea Merello @ 2021-07-15 14:17 ` Andrea Merello 2021-07-15 17:08 ` kernel test robot ` (2 more replies) 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello 4 siblings, 3 replies; 89+ messages in thread From: Andrea Merello @ 2021-07-15 14:17 UTC (permalink / raw) To: jic23, lars Cc: robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello, Andrea Merello This path adds a serdev driver for communicating to a BNO055 IMU via serial bus, and enables the BNO055 core driver to work in this scenario. 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/bno055/Kconfig | 5 + drivers/iio/imu/bno055/Makefile | 1 + drivers/iio/imu/bno055/bno055_sl.c | 576 +++++++++++++++++++++++++++++ 3 files changed, 582 insertions(+) create mode 100644 drivers/iio/imu/bno055/bno055_sl.c diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig index 2bfed8df4554..6d2e8c9f85b7 100644 --- a/drivers/iio/imu/bno055/Kconfig +++ b/drivers/iio/imu/bno055/Kconfig @@ -5,3 +5,8 @@ config BOSH_BNO055_IIO tristate + +config BOSH_BNO055_SERIAL + tristate "Bosh BNO055 attached via serial bus" + depends on SERIAL_DEV_BUS + select BOSH_BNO055_IIO diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile index 15c5ddf8d648..b704b10b6bd1 100644 --- a/drivers/iio/imu/bno055/Makefile +++ b/drivers/iio/imu/bno055/Makefile @@ -4,3 +4,4 @@ # obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o +obj-$(CONFIG_BOSH_BNO055_SERIAL) += bno055_sl.o diff --git a/drivers/iio/imu/bno055/bno055_sl.c b/drivers/iio/imu/bno055/bno055_sl.c new file mode 100644 index 000000000000..9604d73d126c --- /dev/null +++ b/drivers/iio/imu/bno055/bno055_sl.c @@ -0,0 +1,576 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Serial line interface for Bosh BNO055 IMU (via serdev). + * This file implements serial communication up to the register read/write + * level. + * + * Copyright (C) 2021 Istituto Italiano di Tecnologia + * Electronic Design Laboratory + * Written by Andrea Merello <andrea.merello@iit.it> + * + * This driver is besed on + * Plantower PMS7003 particulate matter sensor driver + * Which is + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + */ + +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> +#include <linux/serdev.h> + +#include "bno055.h" + +#define BNO055_SL_DRIVER_NAME "bno055-sl" + +/* + * Register writes cmd have the following format + * +------+------+-----+-----+----- ... ----+ + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | + * +------+------+-----+-----+----- ... ----+ + * + * Register write responses have the following format + * +------+----------+ + * | 0xEE | ERROCODE | + * +------+----------+ + * + * Register read have the following format + * +------+------+-----+-----+ + * | 0xAA | 0xO1 | REG | LEN | + * +------+------+-----+-----+ + * + * Successful register read response have the following format + * +------+-----+----- ... ----+ + * | 0xBB | LEN | payload[LEN] | + * +------+-----+----- ... ----+ + * + * Failed register read response have the following format + * +------+--------+ + * | 0xEE | ERRCODE| (ERRCODE always > 1) + * +------+--------+ + * + * Error codes are + * 01: OK + * 02: read/write FAIL + * 04: invalid address + * 05: write on RO + * 06: wrong start byte + * 07: bus overrun + * 08: len too high + * 09: len too low + * 10: bus RX byte timeout (timeout is 30mS) + * + * + * **WORKAROUND ALERT** + * + * Serial communication seems very fragile: the BNO055 buffer seems to overflow + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in + * between two bytes then the transaction fails (IMU internal RX FSM resets). + * + * BMU055 has been seen also failing to process commands in case we send them + * too close each other (or if it is somehow busy?) + * + * One idea would be to split data in chunks, and then wait 1-2mS between + * chunks (we hope not to exceed 30mS delay for any reason - which should + * be pretty a lot of time for us), and eventually retry in case the BNO055 + * gets upset for any reason. This seems to work in avoiding the overflow + * errors, but indeed it seems slower than just perform a retry when an overflow + * error occur. + * In particular I saw these scenarios: + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could + * overflow, but it seem to sink all 4 bytes, then it returns error. + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending + * error after 4 bytes are sent; we have troubles in synchronizing again, + * because we are still sending data, and the IMU interprets it as the 1st + * byte of a new command. + * + * So, we workaround all this in the following way: + * In case of read we don't split the header but we rely on retries; This seems + * convenient for data read (where we TX only the hdr). + * For TX we split the transmission in 2-bytes chunks so that, we should not + * only avoid case 2 (which is still manageable), but we also hopefully avoid + * case 3, that would be by far worse. + */ + +/* Read operation overhead: + * 4 bytes req + 2byte resp hdr + * 6 bytes = 60 bit (considering 1start + 1stop bits). + * 60/115200 = ~520uS + * In 520uS we could read back about 34 bytes that means 3 samples, this means + * that in case of scattered read in which the gap is 3 samples or less it is + * still convenient to go for a burst. + * We have to take into account also IMU response time - IMU seems to be often + * reasonably quick to respond, but sometimes it seems to be in some "critical + * section" in which it delays handling of serial protocol. + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap + */ + +#define BNO055_SL_XFER_BURST_BREAK_THRESHOLD 6 + +struct bno055_sl_priv { + struct serdev_device *serdev; + struct completion cmd_complete; + enum { + CMD_NONE, + CMD_READ, + CMD_WRITE, + } expect_response; + int expected_data_len; + u8 *response_buf; + enum { + STATUS_OK = 0, /* command OK */ + STATUS_FAIL = 1,/* IMU communicated an error */ + STATUS_CRIT = -1/* serial communication with IMU failed */ + } cmd_status; + struct mutex lock; + + /* Only accessed in behalf of RX callback context. No lock needed. */ + struct { + enum { + RX_IDLE, + RX_START, + RX_DATA + } state; + int databuf_count; + int expected_len; + int type; + } rx; + + /* Never accessed in behalf of RX callback context. No lock needed */ + bool cmd_stale; +}; + +static int bno055_sl_send_chunk(struct bno055_sl_priv *priv, u8 *data, int len) +{ + int ret; + + dev_dbg(&priv->serdev->dev, "send (len: %d): %*ph", len, len, data); + ret = serdev_device_write(priv->serdev, data, len, + msecs_to_jiffies(25)); + if (ret < len) + return ret < 0 ? ret : -EIO; + return 0; +} + +/* + * Sends a read or write command. + * 'data' can be NULL (used in read case). 'len' parameter is always valid; in + * case 'data' is non-NULL then it must match 'data' size. + */ +static int bno055_sl_do_send_cmd(struct bno055_sl_priv *priv, + int read, int addr, int len, u8 *data) +{ + int ret; + int chunk_len; + u8 hdr[] = {0xAA, !!read, addr, len}; + + if (read) { + ret = bno055_sl_send_chunk(priv, hdr, 4); + } else { + ret = bno055_sl_send_chunk(priv, hdr, 2); + if (ret) + goto fail; + + usleep_range(2000, 3000); + ret = bno055_sl_send_chunk(priv, hdr + 2, 2); + } + if (ret) + goto fail; + + if (data) { + while (len) { + chunk_len = min(len, 2); + usleep_range(2000, 3000); + ret = bno055_sl_send_chunk(priv, data, chunk_len); + if (ret) + goto fail; + data += chunk_len; + len -= chunk_len; + } + } + + return 0; +fail: + /* waiting more than 30mS should clear the BNO055 internal state */ + usleep_range(40000, 50000); + return ret; +} + +static int bno_sl_send_cmd(struct bno055_sl_priv *priv, + int read, int addr, int len, u8 *data) +{ + const int retry_max = 5; + int retry = retry_max; + int ret = 0; + + /* + * In case previous command was interrupted we still neet to wait it to + * complete before we can issue new commands + */ + if (priv->cmd_stale) { + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, + msecs_to_jiffies(100)); + if (ret == -ERESTARTSYS) + return -ERESTARTSYS; + + priv->cmd_stale = false; + /* if serial protocol broke, bail out */ + if (priv->cmd_status == STATUS_CRIT) + goto exit; + } + + /* + * Try to convince the IMU to cooperate.. as explained in the comments + * at the top of this file, the IMU could also refuse the command (i.e. + * it is not ready yet); retry in this case. + */ + while (retry--) { + mutex_lock(&priv->lock); + priv->expect_response = read ? CMD_READ : CMD_WRITE; + reinit_completion(&priv->cmd_complete); + mutex_unlock(&priv->lock); + + if (retry != (retry_max - 1)) + dev_dbg(&priv->serdev->dev, "cmd retry: %d", + retry_max - retry); + ret = bno055_sl_do_send_cmd(priv, read, addr, len, data); + if (ret) + continue; + + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, + msecs_to_jiffies(100)); + if (ret == -ERESTARTSYS) { + priv->cmd_stale = true; + return -ERESTARTSYS; + } else if (!ret) { + ret = -ETIMEDOUT; + break; + } + ret = 0; + + /* + * Poll if the IMU returns error (i.e busy), break if the IMU + * returns OK or if the serial communication broke + */ + if (priv->cmd_status <= 0) + break; + } + +exit: + if (ret) + return ret; + if (priv->cmd_status == STATUS_CRIT) + return -EIO; + if (priv->cmd_status == STATUS_FAIL) + return -EINVAL; + return 0; +} + +static int bno055_sl_write_reg(void *context, const void *data, size_t count) +{ + int ret; + int reg; + u8 *write_data = (u8 *)data + 1; + struct bno055_sl_priv *priv = context; + + if (count < 2) { + dev_err(&priv->serdev->dev, "Invalid write count %d", count); + return -EINVAL; + } + + reg = ((u8 *)data)[0]; + dev_dbg(&priv->serdev->dev, "wr reg 0x%x = 0x%x", reg, ((u8 *)data)[1]); + ret = bno_sl_send_cmd(priv, 0, reg, count - 1, write_data); + + return ret; +} + +static int bno055_sl_read_reg(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + int ret; + int reg_addr; + struct bno055_sl_priv *priv = context; + + if (reg_size != 1) { + dev_err(&priv->serdev->dev, "Invalid read regsize %d", + reg_size); + return -EINVAL; + } + + if (val_size > 128) { + dev_err(&priv->serdev->dev, "Invalid read valsize %d", + val_size); + return -EINVAL; + } + + reg_addr = ((u8 *)reg)[0]; + dev_dbg(&priv->serdev->dev, "rd reg 0x%x (len %d)", reg_addr, val_size); + mutex_lock(&priv->lock); + priv->expected_data_len = val_size; + priv->response_buf = val; + mutex_unlock(&priv->lock); + + ret = bno_sl_send_cmd(priv, 1, reg_addr, val_size, NULL); + + mutex_lock(&priv->lock); + priv->response_buf = NULL; + mutex_unlock(&priv->lock); + + return ret; +} + +/* + * Handler for received data; this is called from the reicever callback whenever + * it got some packet from the serial bus. The status tell us whether the + * packet is valid (i.e. header ok && received payload len consistent wrt the + * header). It's now our responsability to check whether this is what we + * expected, of whether we got some unexpected, yet valid, packet. + */ +static void bno055_sl_handle_rx(struct bno055_sl_priv *priv, int status) +{ + mutex_lock(&priv->lock); + switch (priv->expect_response) { + case CMD_NONE: + dev_warn(&priv->serdev->dev, "received unexpected, yet valid, data from sensor"); + mutex_unlock(&priv->lock); + return; + + case CMD_READ: + priv->cmd_status = status; + if (status == STATUS_OK && + priv->rx.databuf_count != priv->expected_data_len) { + /* + * If we got here, then the lower layer serial protocol + * seems consistent with itself; if we got an unexpected + * amount of data then signal it as a non critical error + */ + priv->cmd_status = STATUS_FAIL; + dev_warn(&priv->serdev->dev, "received an unexpected amount of, yet valid, data from sensor"); + } + break; + + case CMD_WRITE: + priv->cmd_status = status; + break; + } + + priv->expect_response = CMD_NONE; + complete(&priv->cmd_complete); + mutex_unlock(&priv->lock); +} + +/* + * Serdev receiver FSM. This tracks the serial communication and parse the + * header. It pushes packets to bno055_sl_handle_rx(), eventually communicating + * failures (i.e. malformed packets). + * Idellay it doesn't know anything about upper layer (i.e. if this is the + * packet we were really expecting), but since we copies the payload into the + * receiver buffer (that is not valid when i.e. we don't expect data), we + * snoop a bit in the upper layer.. + * Also, we assume to RX one pkt per time (i.e. the HW doesn't send anything + * unless we require to AND we don't queue more than one request per time). + */ +static int bno055_sl_receive_buf(struct serdev_device *serdev, + const unsigned char *buf, size_t size) +{ + int status; + struct bno055_sl_priv *priv = serdev_device_get_drvdata(serdev); + int _size = size; + + if (size == 0) + return 0; + + dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); + switch (priv->rx.state) { + case RX_IDLE: + /* + * New packet. + * Check for its 1st byte, that identifies the pkt type. + */ + if (buf[0] != 0xEE && buf[0] != 0xBB) { + dev_err(&priv->serdev->dev, + "Invalid packet start %x", buf[0]); + bno055_sl_handle_rx(priv, STATUS_CRIT); + break; + } + priv->rx.type = buf[0]; + priv->rx.state = RX_START; + size--; + buf++; + priv->rx.databuf_count = 0; + fallthrough; + + case RX_START: + /* + * Packet RX in progress, we expect either 1-byte len or 1-byte + * status depending by the packet type. + */ + if (size == 0) + break; + + if (priv->rx.type == 0xEE) { + if (size > 1) { + dev_err(&priv->serdev->dev, "EE pkt. Extra data received"); + status = STATUS_CRIT; + + } else { + status = (buf[0] == 1) ? STATUS_OK : STATUS_FAIL; + } + bno055_sl_handle_rx(priv, status); + priv->rx.state = RX_IDLE; + break; + + } else { + /*priv->rx.type == 0xBB */ + priv->rx.state = RX_DATA; + priv->rx.expected_len = buf[0]; + size--; + buf++; + } + fallthrough; + + case RX_DATA: + /* Header parsed; now receiving packet data payload */ + if (size == 0) + break; + + if (priv->rx.databuf_count + size > priv->rx.expected_len) { + /* + * This is a inconsistency in serial protocol, we lost + * sync and we don't know how to handle further data + */ + dev_err(&priv->serdev->dev, "BB pkt. Extra data received"); + bno055_sl_handle_rx(priv, STATUS_CRIT); + priv->rx.state = RX_IDLE; + break; + } + + mutex_lock(&priv->lock); + /* + * NULL e.g. when read cmd is stale or when no read cmd is + * actually pending. + */ + if (priv->response_buf && + /* + * Snoop on the upper layer protocol stuff to make sure not + * to write to an invalid memory. Apart for this, let's the + * upper layer manage any inconsistency wrt expected data + * len (as long as the serial protocol is consistent wrt + * itself (i.e. response header is consistent with received + * response len. + */ + (priv->rx.databuf_count + size <= priv->expected_data_len)) + memcpy(priv->response_buf + priv->rx.databuf_count, + buf, size); + mutex_unlock(&priv->lock); + + priv->rx.databuf_count += size; + + /* + * Reached expected len advertised by the IMU for the current + * packet. Pass it to the upper layer (for us it is just valid). + */ + if (priv->rx.databuf_count == priv->rx.expected_len) { + bno055_sl_handle_rx(priv, STATUS_OK); + priv->rx.state = RX_IDLE; + } + break; + } + + return _size; +} + +static const struct serdev_device_ops bno055_sl_serdev_ops = { + .receive_buf = bno055_sl_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static struct regmap_bus bno055_sl_regmap_bus = { + .write = bno055_sl_write_reg, + .read = bno055_sl_read_reg, +}; + +static int bno055_sl_probe(struct serdev_device *serdev) +{ + struct bno055_sl_priv *priv; + struct regmap *regmap; + int ret; + int irq = 0; + + priv = devm_kzalloc(&serdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + serdev_device_set_drvdata(serdev, priv); + priv->serdev = serdev; + mutex_init(&priv->lock); + init_completion(&priv->cmd_complete); + + serdev_device_set_client_ops(serdev, &bno055_sl_serdev_ops); + ret = devm_serdev_device_open(&serdev->dev, serdev); + if (ret) + return ret; + + if (serdev_device_set_baudrate(serdev, 115200) != 115200) { + dev_err(&serdev->dev, "Cannot set required baud rate"); + return -EIO; + } + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) { + dev_err(&serdev->dev, "Cannot set required parity setting"); + return ret; + } + serdev_device_set_flow_control(serdev, false); + + regmap = devm_regmap_init(&serdev->dev, &bno055_sl_regmap_bus, + priv, &bno055_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&serdev->dev, "Unable to init register map"); + return PTR_ERR(regmap); + } + + if (serdev->dev.of_node) { + irq = of_irq_get(serdev->dev.of_node, 0); + if (irq == -EPROBE_DEFER) + return irq; + if (irq <= 0) { + dev_info(&serdev->dev, + "Can't get IRQ resource (err %d)", irq); + irq = 0; + } + } + + return bno055_probe(&serdev->dev, regmap, irq, + BNO055_SL_XFER_BURST_BREAK_THRESHOLD); +} + +static const struct of_device_id bno055_sl_of_match[] = { + { .compatible = "bosch,bno055-serial" }, + { } +}; +MODULE_DEVICE_TABLE(of, bno055_sl_of_match); + +static struct serdev_device_driver bno055_sl_driver = { + .driver = { + .name = BNO055_SL_DRIVER_NAME, + .of_match_table = bno055_sl_of_match, + }, + .probe = bno055_sl_probe, +}; +module_serdev_device_driver(bno055_sl_driver); + +MODULE_AUTHOR("Andrea Merello <andrea.merello@iit.it>"); +MODULE_DESCRIPTION("Bosch BNO055 serdev interface"); +MODULE_LICENSE("GPL v2"); -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 4/4] iio: imu: add BNO055 serdev driver 2021-07-15 14:17 ` [PATCH 4/4] iio: imu: add BNO055 serdev driver Andrea Merello @ 2021-07-15 17:08 ` kernel test robot 2021-07-15 18:14 ` kernel test robot 2021-07-17 15:50 ` Jonathan Cameron 2 siblings, 0 replies; 89+ messages in thread From: kernel test robot @ 2021-07-15 17:08 UTC (permalink / raw) To: Andrea Merello, jic23, lars Cc: kbuild-all, robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello [-- Attachment #1: Type: text/plain, Size: 4567 bytes --] Hi Andrea, Thank you for the patch! Perhaps something to improve: [auto build test WARNING on iio/togreg] [also build test WARNING on robh/for-next linus/master v5.14-rc1 next-20210715] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch] url: https://github.com/0day-ci/linux/commits/Andrea-Merello/Add-support-for-Bosch-BNO055-IMU/20210715-222018 base: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg config: arc-allyesconfig (attached as .config) compiler: arceb-elf-gcc (GCC) 10.3.0 reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/0day-ci/linux/commit/616d1b9a99ec2045cdf6cc827751660a48ccc5d2 git remote add linux-review https://github.com/0day-ci/linux git fetch --no-tags linux-review Andrea-Merello/Add-support-for-Bosch-BNO055-IMU/20210715-222018 git checkout 616d1b9a99ec2045cdf6cc827751660a48ccc5d2 # save the attached .config to linux build tree COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-10.3.0 make.cross ARCH=arc If you fix the issue, kindly add following tag as appropriate Reported-by: kernel test robot <lkp@intel.com> All warnings (new ones prefixed by >>): >> drivers/iio/imu/bno055/bno055.c:250:5: warning: no previous prototype for 'bno055_calibration_load' [-Wmissing-prototypes] 250 | int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw) | ^~~~~~~~~~~~~~~~~~~~~~~ drivers/iio/imu/bno055/bno055.c: In function '_bno055_write_raw': >> drivers/iio/imu/bno055/bno055.c:770:3: warning: this statement may fall through [-Wimplicit-fallthrough=] 770 | switch (mask) { | ^~~~~~ drivers/iio/imu/bno055/bno055.c:774:2: note: here 774 | default: | ^~~~~~~ vim +/bno055_calibration_load +250 drivers/iio/imu/bno055/bno055.c d13cfdff130569 Andrea Merello 2021-07-15 248 d13cfdff130569 Andrea Merello 2021-07-15 249 /* must be called in configuration mode */ d13cfdff130569 Andrea Merello 2021-07-15 @250 int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw) d13cfdff130569 Andrea Merello 2021-07-15 251 { d13cfdff130569 Andrea Merello 2021-07-15 252 int i; d13cfdff130569 Andrea Merello 2021-07-15 253 unsigned int tmp; d13cfdff130569 Andrea Merello 2021-07-15 254 u8 cal[BNO055_CALDATA_LEN]; d13cfdff130569 Andrea Merello 2021-07-15 255 int read, tot_read = 0; d13cfdff130569 Andrea Merello 2021-07-15 256 int ret = 0; d13cfdff130569 Andrea Merello 2021-07-15 257 char *buf = kmalloc(fw->size + 1, GFP_KERNEL); d13cfdff130569 Andrea Merello 2021-07-15 258 d13cfdff130569 Andrea Merello 2021-07-15 259 if (!buf) d13cfdff130569 Andrea Merello 2021-07-15 260 return -ENOMEM; d13cfdff130569 Andrea Merello 2021-07-15 261 d13cfdff130569 Andrea Merello 2021-07-15 262 memcpy(buf, fw->data, fw->size); d13cfdff130569 Andrea Merello 2021-07-15 263 buf[fw->size] = '\0'; d13cfdff130569 Andrea Merello 2021-07-15 264 for (i = 0; i < BNO055_CALDATA_LEN; i++) { d13cfdff130569 Andrea Merello 2021-07-15 265 ret = sscanf(buf + tot_read, "%x%n", d13cfdff130569 Andrea Merello 2021-07-15 266 &tmp, &read); d13cfdff130569 Andrea Merello 2021-07-15 267 if (ret != 1 || tmp > 0xff) { d13cfdff130569 Andrea Merello 2021-07-15 268 ret = -EINVAL; d13cfdff130569 Andrea Merello 2021-07-15 269 goto exit; d13cfdff130569 Andrea Merello 2021-07-15 270 } d13cfdff130569 Andrea Merello 2021-07-15 271 cal[i] = tmp; d13cfdff130569 Andrea Merello 2021-07-15 272 tot_read += read; d13cfdff130569 Andrea Merello 2021-07-15 273 } d13cfdff130569 Andrea Merello 2021-07-15 274 dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, cal); d13cfdff130569 Andrea Merello 2021-07-15 275 ret = regmap_bulk_write(priv->regmap, BNO055_CALDATA_START, d13cfdff130569 Andrea Merello 2021-07-15 276 cal, BNO055_CALDATA_LEN); d13cfdff130569 Andrea Merello 2021-07-15 277 exit: d13cfdff130569 Andrea Merello 2021-07-15 278 kfree(buf); d13cfdff130569 Andrea Merello 2021-07-15 279 return ret; d13cfdff130569 Andrea Merello 2021-07-15 280 } d13cfdff130569 Andrea Merello 2021-07-15 281 --- 0-DAY CI Kernel Test Service, Intel Corporation https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org [-- Attachment #2: .config.gz --] [-- Type: application/gzip, Size: 68533 bytes --] ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 4/4] iio: imu: add BNO055 serdev driver 2021-07-15 14:17 ` [PATCH 4/4] iio: imu: add BNO055 serdev driver Andrea Merello 2021-07-15 17:08 ` kernel test robot @ 2021-07-15 18:14 ` kernel test robot 2021-07-17 15:50 ` Jonathan Cameron 2 siblings, 0 replies; 89+ messages in thread From: kernel test robot @ 2021-07-15 18:14 UTC (permalink / raw) To: Andrea Merello, jic23, lars Cc: kbuild-all, robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello [-- Attachment #1: Type: text/plain, Size: 18531 bytes --] Hi Andrea, Thank you for the patch! Perhaps something to improve: [auto build test WARNING on iio/togreg] [also build test WARNING on robh/for-next linus/master v5.14-rc1 next-20210715] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch] url: https://github.com/0day-ci/linux/commits/Andrea-Merello/Add-support-for-Bosch-BNO055-IMU/20210715-222018 base: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg config: riscv-allyesconfig (attached as .config) compiler: riscv64-linux-gcc (GCC) 10.3.0 reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/0day-ci/linux/commit/616d1b9a99ec2045cdf6cc827751660a48ccc5d2 git remote add linux-review https://github.com/0day-ci/linux git fetch --no-tags linux-review Andrea-Merello/Add-support-for-Bosch-BNO055-IMU/20210715-222018 git checkout 616d1b9a99ec2045cdf6cc827751660a48ccc5d2 # save the attached .config to linux build tree COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-10.3.0 make.cross ARCH=riscv If you fix the issue, kindly add following tag as appropriate Reported-by: kernel test robot <lkp@intel.com> All warnings (new ones prefixed by >>): In file included from include/linux/device.h:15, from drivers/iio/imu/bno055/bno055_sl.c:18: drivers/iio/imu/bno055/bno055_sl.c: In function 'bno055_sl_write_reg': >> drivers/iio/imu/bno055/bno055_sl.c:286:31: warning: format '%d' expects argument of type 'int', but argument 3 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 286 | dev_err(&priv->serdev->dev, "Invalid write count %d", count); | ^~~~~~~~~~~~~~~~~~~~~~~~ include/linux/dev_printk.h:19:22: note: in definition of macro 'dev_fmt' 19 | #define dev_fmt(fmt) fmt | ^~~ drivers/iio/imu/bno055/bno055_sl.c:286:3: note: in expansion of macro 'dev_err' 286 | dev_err(&priv->serdev->dev, "Invalid write count %d", count); | ^~~~~~~ drivers/iio/imu/bno055/bno055_sl.c:286:53: note: format string is defined here 286 | dev_err(&priv->serdev->dev, "Invalid write count %d", count); | ~^ | | | int | %ld In file included from include/linux/device.h:15, from drivers/iio/imu/bno055/bno055_sl.c:18: drivers/iio/imu/bno055/bno055_sl.c: In function 'bno055_sl_read_reg': drivers/iio/imu/bno055/bno055_sl.c:306:31: warning: format '%d' expects argument of type 'int', but argument 3 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 306 | dev_err(&priv->serdev->dev, "Invalid read regsize %d", | ^~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/dev_printk.h:19:22: note: in definition of macro 'dev_fmt' 19 | #define dev_fmt(fmt) fmt | ^~~ drivers/iio/imu/bno055/bno055_sl.c:306:3: note: in expansion of macro 'dev_err' 306 | dev_err(&priv->serdev->dev, "Invalid read regsize %d", | ^~~~~~~ drivers/iio/imu/bno055/bno055_sl.c:306:54: note: format string is defined here 306 | dev_err(&priv->serdev->dev, "Invalid read regsize %d", | ~^ | | | int | %ld In file included from include/linux/device.h:15, from drivers/iio/imu/bno055/bno055_sl.c:18: drivers/iio/imu/bno055/bno055_sl.c:312:31: warning: format '%d' expects argument of type 'int', but argument 3 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 312 | dev_err(&priv->serdev->dev, "Invalid read valsize %d", | ^~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/dev_printk.h:19:22: note: in definition of macro 'dev_fmt' 19 | #define dev_fmt(fmt) fmt | ^~~ drivers/iio/imu/bno055/bno055_sl.c:312:3: note: in expansion of macro 'dev_err' 312 | dev_err(&priv->serdev->dev, "Invalid read valsize %d", | ^~~~~~~ drivers/iio/imu/bno055/bno055_sl.c:312:54: note: format string is defined here 312 | dev_err(&priv->serdev->dev, "Invalid read valsize %d", | ~^ | | | int | %ld In file included from include/linux/printk.h:456, from include/linux/kernel.h:19, from include/linux/list.h:9, from include/linux/swait.h:5, from include/linux/completion.h:12, from drivers/iio/imu/bno055/bno055_sl.c:17: drivers/iio/imu/bno055/bno055_sl.c:318:30: warning: format '%d' expects argument of type 'int', but argument 5 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 318 | dev_dbg(&priv->serdev->dev, "rd reg 0x%x (len %d)", reg_addr, val_size); | ^~~~~~~~~~~~~~~~~~~~~~ include/linux/dynamic_debug.h:134:15: note: in definition of macro '__dynamic_func_call' 134 | func(&id, ##__VA_ARGS__); \ | ^~~~~~~~~~~ include/linux/dynamic_debug.h:166:2: note: in expansion of macro '_dynamic_func_call' 166 | _dynamic_func_call(fmt,__dynamic_dev_dbg, \ | ^~~~~~~~~~~~~~~~~~ include/linux/dev_printk.h:123:2: note: in expansion of macro 'dynamic_dev_dbg' 123 | dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__) | ^~~~~~~~~~~~~~~ include/linux/dev_printk.h:123:23: note: in expansion of macro 'dev_fmt' 123 | dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__) | ^~~~~~~ drivers/iio/imu/bno055/bno055_sl.c:318:2: note: in expansion of macro 'dev_dbg' 318 | dev_dbg(&priv->serdev->dev, "rd reg 0x%x (len %d)", reg_addr, val_size); | ^~~~~~~ drivers/iio/imu/bno055/bno055_sl.c:318:49: note: format string is defined here 318 | dev_dbg(&priv->serdev->dev, "rd reg 0x%x (len %d)", reg_addr, val_size); | ~^ | | | int | %ld In file included from include/linux/printk.h:456, from include/linux/kernel.h:19, from include/linux/list.h:9, from include/linux/swait.h:5, from include/linux/completion.h:12, from drivers/iio/imu/bno055/bno055_sl.c:17: drivers/iio/imu/bno055/bno055_sl.c: In function 'bno055_sl_receive_buf': drivers/iio/imu/bno055/bno055_sl.c:394:30: warning: format '%d' expects argument of type 'int', but argument 4 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 394 | dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); | ^~~~~~~~~~~~~~~~~~~~~~ include/linux/dynamic_debug.h:134:15: note: in definition of macro '__dynamic_func_call' 134 | func(&id, ##__VA_ARGS__); \ | ^~~~~~~~~~~ include/linux/dynamic_debug.h:166:2: note: in expansion of macro '_dynamic_func_call' 166 | _dynamic_func_call(fmt,__dynamic_dev_dbg, \ | ^~~~~~~~~~~~~~~~~~ include/linux/dev_printk.h:123:2: note: in expansion of macro 'dynamic_dev_dbg' 123 | dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__) | ^~~~~~~~~~~~~~~ include/linux/dev_printk.h:123:23: note: in expansion of macro 'dev_fmt' 123 | dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__) | ^~~~~~~ drivers/iio/imu/bno055/bno055_sl.c:394:2: note: in expansion of macro 'dev_dbg' 394 | dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); | ^~~~~~~ drivers/iio/imu/bno055/bno055_sl.c:394:42: note: format string is defined here 394 | dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); | ~^ | | | int | %ld In file included from include/linux/printk.h:456, from include/linux/kernel.h:19, from include/linux/list.h:9, from include/linux/swait.h:5, from include/linux/completion.h:12, from drivers/iio/imu/bno055/bno055_sl.c:17: >> drivers/iio/imu/bno055/bno055_sl.c:394:30: warning: field width specifier '*' expects argument of type 'int', but argument 5 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 394 | dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); | ^~~~~~~~~~~~~~~~~~~~~~ include/linux/dynamic_debug.h:134:15: note: in definition of macro '__dynamic_func_call' 134 | func(&id, ##__VA_ARGS__); \ | ^~~~~~~~~~~ include/linux/dynamic_debug.h:166:2: note: in expansion of macro '_dynamic_func_call' 166 | _dynamic_func_call(fmt,__dynamic_dev_dbg, \ | ^~~~~~~~~~~~~~~~~~ include/linux/dev_printk.h:123:2: note: in expansion of macro 'dynamic_dev_dbg' 123 | dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__) | ^~~~~~~~~~~~~~~ include/linux/dev_printk.h:123:23: note: in expansion of macro 'dev_fmt' 123 | dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__) | ^~~~~~~ drivers/iio/imu/bno055/bno055_sl.c:394:2: note: in expansion of macro 'dev_dbg' 394 | dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); | ^~~~~~~ drivers/iio/imu/bno055/bno055_sl.c:394:47: note: format string is defined here 394 | dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); | ~^~ | | | int vim +286 drivers/iio/imu/bno055/bno055_sl.c 277 278 static int bno055_sl_write_reg(void *context, const void *data, size_t count) 279 { 280 int ret; 281 int reg; 282 u8 *write_data = (u8 *)data + 1; 283 struct bno055_sl_priv *priv = context; 284 285 if (count < 2) { > 286 dev_err(&priv->serdev->dev, "Invalid write count %d", count); 287 return -EINVAL; 288 } 289 290 reg = ((u8 *)data)[0]; 291 dev_dbg(&priv->serdev->dev, "wr reg 0x%x = 0x%x", reg, ((u8 *)data)[1]); 292 ret = bno_sl_send_cmd(priv, 0, reg, count - 1, write_data); 293 294 return ret; 295 } 296 297 static int bno055_sl_read_reg(void *context, 298 const void *reg, size_t reg_size, 299 void *val, size_t val_size) 300 { 301 int ret; 302 int reg_addr; 303 struct bno055_sl_priv *priv = context; 304 305 if (reg_size != 1) { 306 dev_err(&priv->serdev->dev, "Invalid read regsize %d", 307 reg_size); 308 return -EINVAL; 309 } 310 311 if (val_size > 128) { 312 dev_err(&priv->serdev->dev, "Invalid read valsize %d", 313 val_size); 314 return -EINVAL; 315 } 316 317 reg_addr = ((u8 *)reg)[0]; 318 dev_dbg(&priv->serdev->dev, "rd reg 0x%x (len %d)", reg_addr, val_size); 319 mutex_lock(&priv->lock); 320 priv->expected_data_len = val_size; 321 priv->response_buf = val; 322 mutex_unlock(&priv->lock); 323 324 ret = bno_sl_send_cmd(priv, 1, reg_addr, val_size, NULL); 325 326 mutex_lock(&priv->lock); 327 priv->response_buf = NULL; 328 mutex_unlock(&priv->lock); 329 330 return ret; 331 } 332 333 /* 334 * Handler for received data; this is called from the reicever callback whenever 335 * it got some packet from the serial bus. The status tell us whether the 336 * packet is valid (i.e. header ok && received payload len consistent wrt the 337 * header). It's now our responsability to check whether this is what we 338 * expected, of whether we got some unexpected, yet valid, packet. 339 */ 340 static void bno055_sl_handle_rx(struct bno055_sl_priv *priv, int status) 341 { 342 mutex_lock(&priv->lock); 343 switch (priv->expect_response) { 344 case CMD_NONE: 345 dev_warn(&priv->serdev->dev, "received unexpected, yet valid, data from sensor"); 346 mutex_unlock(&priv->lock); 347 return; 348 349 case CMD_READ: 350 priv->cmd_status = status; 351 if (status == STATUS_OK && 352 priv->rx.databuf_count != priv->expected_data_len) { 353 /* 354 * If we got here, then the lower layer serial protocol 355 * seems consistent with itself; if we got an unexpected 356 * amount of data then signal it as a non critical error 357 */ 358 priv->cmd_status = STATUS_FAIL; 359 dev_warn(&priv->serdev->dev, "received an unexpected amount of, yet valid, data from sensor"); 360 } 361 break; 362 363 case CMD_WRITE: 364 priv->cmd_status = status; 365 break; 366 } 367 368 priv->expect_response = CMD_NONE; 369 complete(&priv->cmd_complete); 370 mutex_unlock(&priv->lock); 371 } 372 373 /* 374 * Serdev receiver FSM. This tracks the serial communication and parse the 375 * header. It pushes packets to bno055_sl_handle_rx(), eventually communicating 376 * failures (i.e. malformed packets). 377 * Idellay it doesn't know anything about upper layer (i.e. if this is the 378 * packet we were really expecting), but since we copies the payload into the 379 * receiver buffer (that is not valid when i.e. we don't expect data), we 380 * snoop a bit in the upper layer.. 381 * Also, we assume to RX one pkt per time (i.e. the HW doesn't send anything 382 * unless we require to AND we don't queue more than one request per time). 383 */ 384 static int bno055_sl_receive_buf(struct serdev_device *serdev, 385 const unsigned char *buf, size_t size) 386 { 387 int status; 388 struct bno055_sl_priv *priv = serdev_device_get_drvdata(serdev); 389 int _size = size; 390 391 if (size == 0) 392 return 0; 393 > 394 dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); 395 switch (priv->rx.state) { 396 case RX_IDLE: 397 /* 398 * New packet. 399 * Check for its 1st byte, that identifies the pkt type. 400 */ 401 if (buf[0] != 0xEE && buf[0] != 0xBB) { 402 dev_err(&priv->serdev->dev, 403 "Invalid packet start %x", buf[0]); 404 bno055_sl_handle_rx(priv, STATUS_CRIT); 405 break; 406 } 407 priv->rx.type = buf[0]; 408 priv->rx.state = RX_START; 409 size--; 410 buf++; 411 priv->rx.databuf_count = 0; 412 fallthrough; 413 414 case RX_START: 415 /* 416 * Packet RX in progress, we expect either 1-byte len or 1-byte 417 * status depending by the packet type. 418 */ 419 if (size == 0) 420 break; 421 422 if (priv->rx.type == 0xEE) { 423 if (size > 1) { 424 dev_err(&priv->serdev->dev, "EE pkt. Extra data received"); 425 status = STATUS_CRIT; 426 427 } else { 428 status = (buf[0] == 1) ? STATUS_OK : STATUS_FAIL; 429 } 430 bno055_sl_handle_rx(priv, status); 431 priv->rx.state = RX_IDLE; 432 break; 433 434 } else { 435 /*priv->rx.type == 0xBB */ 436 priv->rx.state = RX_DATA; 437 priv->rx.expected_len = buf[0]; 438 size--; 439 buf++; 440 } 441 fallthrough; 442 443 case RX_DATA: 444 /* Header parsed; now receiving packet data payload */ 445 if (size == 0) 446 break; 447 448 if (priv->rx.databuf_count + size > priv->rx.expected_len) { 449 /* 450 * This is a inconsistency in serial protocol, we lost 451 * sync and we don't know how to handle further data 452 */ 453 dev_err(&priv->serdev->dev, "BB pkt. Extra data received"); 454 bno055_sl_handle_rx(priv, STATUS_CRIT); 455 priv->rx.state = RX_IDLE; 456 break; 457 } 458 459 mutex_lock(&priv->lock); 460 /* 461 * NULL e.g. when read cmd is stale or when no read cmd is 462 * actually pending. 463 */ 464 if (priv->response_buf && 465 /* 466 * Snoop on the upper layer protocol stuff to make sure not 467 * to write to an invalid memory. Apart for this, let's the 468 * upper layer manage any inconsistency wrt expected data 469 * len (as long as the serial protocol is consistent wrt 470 * itself (i.e. response header is consistent with received 471 * response len. 472 */ 473 (priv->rx.databuf_count + size <= priv->expected_data_len)) 474 memcpy(priv->response_buf + priv->rx.databuf_count, 475 buf, size); 476 mutex_unlock(&priv->lock); 477 478 priv->rx.databuf_count += size; 479 480 /* 481 * Reached expected len advertised by the IMU for the current 482 * packet. Pass it to the upper layer (for us it is just valid). 483 */ 484 if (priv->rx.databuf_count == priv->rx.expected_len) { 485 bno055_sl_handle_rx(priv, STATUS_OK); 486 priv->rx.state = RX_IDLE; 487 } 488 break; 489 } 490 491 return _size; 492 } 493 --- 0-DAY CI Kernel Test Service, Intel Corporation https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org [-- Attachment #2: .config.gz --] [-- Type: application/gzip, Size: 70294 bytes --] ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 4/4] iio: imu: add BNO055 serdev driver 2021-07-15 14:17 ` [PATCH 4/4] iio: imu: add BNO055 serdev driver Andrea Merello 2021-07-15 17:08 ` kernel test robot 2021-07-15 18:14 ` kernel test robot @ 2021-07-17 15:50 ` Jonathan Cameron 2021-07-19 8:49 ` Andrea Merello 2 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-07-17 15:50 UTC (permalink / raw) To: Andrea Merello Cc: lars, robh+dt, matt.ranostay, andriy.shevchenko, vlad.dogaru, linux-kernel, linux-iio, Andrea Merello On Thu, 15 Jul 2021 16:17:42 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > This path adds a serdev driver for communicating to a BNO055 IMU > via serial bus, and enables the BNO055 core driver to work in this > scenario. > > 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 Hi Andrea, A few comments inline. Jonathan > --- > drivers/iio/imu/bno055/Kconfig | 5 + > drivers/iio/imu/bno055/Makefile | 1 + > drivers/iio/imu/bno055/bno055_sl.c | 576 +++++++++++++++++++++++++++++ > 3 files changed, 582 insertions(+) > create mode 100644 drivers/iio/imu/bno055/bno055_sl.c > > diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig > index 2bfed8df4554..6d2e8c9f85b7 100644 > --- a/drivers/iio/imu/bno055/Kconfig > +++ b/drivers/iio/imu/bno055/Kconfig > @@ -5,3 +5,8 @@ > > config BOSH_BNO055_IIO > tristate > + > +config BOSH_BNO055_SERIAL > + tristate "Bosh BNO055 attached via serial bus" > + depends on SERIAL_DEV_BUS > + select BOSH_BNO055_IIO > diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile > index 15c5ddf8d648..b704b10b6bd1 100644 > --- a/drivers/iio/imu/bno055/Makefile > +++ b/drivers/iio/imu/bno055/Makefile > @@ -4,3 +4,4 @@ > # > > obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o > +obj-$(CONFIG_BOSH_BNO055_SERIAL) += bno055_sl.o > diff --git a/drivers/iio/imu/bno055/bno055_sl.c b/drivers/iio/imu/bno055/bno055_sl.c > new file mode 100644 > index 000000000000..9604d73d126c > --- /dev/null > +++ b/drivers/iio/imu/bno055/bno055_sl.c > @@ -0,0 +1,576 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Serial line interface for Bosh BNO055 IMU (via serdev). > + * This file implements serial communication up to the register read/write > + * level. > + * > + * Copyright (C) 2021 Istituto Italiano di Tecnologia > + * Electronic Design Laboratory > + * Written by Andrea Merello <andrea.merello@iit.it> > + * > + * This driver is besed on > + * Plantower PMS7003 particulate matter sensor driver > + * Which is > + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> > + */ > + > +#include <linux/completion.h> > +#include <linux/device.h> > +#include <linux/errno.h> > +#include <linux/jiffies.h> > +#include <linux/kernel.h> > +#include <linux/mod_devicetable.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/of_irq.h> > +#include <linux/regmap.h> > +#include <linux/serdev.h> > + > +#include "bno055.h" > + > +#define BNO055_SL_DRIVER_NAME "bno055-sl" > + > +/* > + * Register writes cmd have the following format > + * +------+------+-----+-----+----- ... ----+ > + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | > + * +------+------+-----+-----+----- ... ----+ > + * > + * Register write responses have the following format > + * +------+----------+ > + * | 0xEE | ERROCODE | > + * +------+----------+ > + * > + * Register read have the following format > + * +------+------+-----+-----+ > + * | 0xAA | 0xO1 | REG | LEN | > + * +------+------+-----+-----+ > + * > + * Successful register read response have the following format > + * +------+-----+----- ... ----+ > + * | 0xBB | LEN | payload[LEN] | > + * +------+-----+----- ... ----+ > + * > + * Failed register read response have the following format > + * +------+--------+ > + * | 0xEE | ERRCODE| (ERRCODE always > 1) > + * +------+--------+ > + * > + * Error codes are > + * 01: OK > + * 02: read/write FAIL > + * 04: invalid address > + * 05: write on RO > + * 06: wrong start byte > + * 07: bus overrun > + * 08: len too high > + * 09: len too low > + * 10: bus RX byte timeout (timeout is 30mS) > + * > + * > + * **WORKAROUND ALERT** > + * > + * Serial communication seems very fragile: the BNO055 buffer seems to overflow > + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. > + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in > + * between two bytes then the transaction fails (IMU internal RX FSM resets). > + * > + * BMU055 has been seen also failing to process commands in case we send them > + * too close each other (or if it is somehow busy?) > + * > + * One idea would be to split data in chunks, and then wait 1-2mS between > + * chunks (we hope not to exceed 30mS delay for any reason - which should > + * be pretty a lot of time for us), and eventually retry in case the BNO055 > + * gets upset for any reason. This seems to work in avoiding the overflow > + * errors, but indeed it seems slower than just perform a retry when an overflow > + * error occur. > + * In particular I saw these scenarios: > + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. > + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could > + * overflow, but it seem to sink all 4 bytes, then it returns error. > + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending > + * error after 4 bytes are sent; we have troubles in synchronizing again, > + * because we are still sending data, and the IMU interprets it as the 1st > + * byte of a new command. > + * > + * So, we workaround all this in the following way: > + * In case of read we don't split the header but we rely on retries; This seems > + * convenient for data read (where we TX only the hdr). > + * For TX we split the transmission in 2-bytes chunks so that, we should not > + * only avoid case 2 (which is still manageable), but we also hopefully avoid > + * case 3, that would be by far worse. Nice docs and this sounds terrible! > + */ > + > +/* Read operation overhead: > + * 4 bytes req + 2byte resp hdr > + * 6 bytes = 60 bit (considering 1start + 1stop bits). > + * 60/115200 = ~520uS > + * In 520uS we could read back about 34 bytes that means 3 samples, this means > + * that in case of scattered read in which the gap is 3 samples or less it is > + * still convenient to go for a burst. > + * We have to take into account also IMU response time - IMU seems to be often > + * reasonably quick to respond, but sometimes it seems to be in some "critical > + * section" in which it delays handling of serial protocol. > + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap > + */ > + > +#define BNO055_SL_XFER_BURST_BREAK_THRESHOLD 6 > + > +struct bno055_sl_priv { > + struct serdev_device *serdev; > + struct completion cmd_complete; > + enum { > + CMD_NONE, > + CMD_READ, > + CMD_WRITE, > + } expect_response; > + int expected_data_len; > + u8 *response_buf; > + enum { > + STATUS_OK = 0, /* command OK */ > + STATUS_FAIL = 1,/* IMU communicated an error */ > + STATUS_CRIT = -1/* serial communication with IMU failed */ > + } cmd_status; > + struct mutex lock; > + > + /* Only accessed in behalf of RX callback context. No lock needed. */ > + struct { > + enum { > + RX_IDLE, > + RX_START, > + RX_DATA > + } state; > + int databuf_count; > + int expected_len; > + int type; > + } rx; > + > + /* Never accessed in behalf of RX callback context. No lock needed */ > + bool cmd_stale; > +}; > + > +static int bno055_sl_send_chunk(struct bno055_sl_priv *priv, u8 *data, int len) > +{ > + int ret; > + > + dev_dbg(&priv->serdev->dev, "send (len: %d): %*ph", len, len, data); > + ret = serdev_device_write(priv->serdev, data, len, > + msecs_to_jiffies(25)); > + if (ret < len) > + return ret < 0 ? ret : -EIO; Break this up perhaps as will be easier to read. if (ret < 0) return ret; if (ret < len) return -EIO; return 0; > + return 0; > +} > + > +/* > + * Sends a read or write command. > + * 'data' can be NULL (used in read case). 'len' parameter is always valid; in > + * case 'data' is non-NULL then it must match 'data' size. > + */ > +static int bno055_sl_do_send_cmd(struct bno055_sl_priv *priv, > + int read, int addr, int len, u8 *data) > +{ > + int ret; > + int chunk_len; > + u8 hdr[] = {0xAA, !!read, addr, len}; > + > + if (read) { > + ret = bno055_sl_send_chunk(priv, hdr, 4); > + } else { > + ret = bno055_sl_send_chunk(priv, hdr, 2); > + if (ret) > + goto fail; > + > + usleep_range(2000, 3000); > + ret = bno055_sl_send_chunk(priv, hdr + 2, 2); > + } > + if (ret) > + goto fail; > + > + if (data) { > + while (len) { > + chunk_len = min(len, 2); > + usleep_range(2000, 3000); > + ret = bno055_sl_send_chunk(priv, data, chunk_len); > + if (ret) > + goto fail; > + data += chunk_len; > + len -= chunk_len; > + } > + } > + > + return 0; > +fail: > + /* waiting more than 30mS should clear the BNO055 internal state */ > + usleep_range(40000, 50000); > + return ret; > +} > + > +static int bno_sl_send_cmd(struct bno055_sl_priv *priv, > + int read, int addr, int len, u8 *data) > +{ > + const int retry_max = 5; > + int retry = retry_max; > + int ret = 0; > + > + /* > + * In case previous command was interrupted we still neet to wait it to > + * complete before we can issue new commands > + */ > + if (priv->cmd_stale) { > + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, > + msecs_to_jiffies(100)); > + if (ret == -ERESTARTSYS) > + return -ERESTARTSYS; > + > + priv->cmd_stale = false; > + /* if serial protocol broke, bail out */ > + if (priv->cmd_status == STATUS_CRIT) > + goto exit; > + } > + > + /* > + * Try to convince the IMU to cooperate.. as explained in the comments > + * at the top of this file, the IMU could also refuse the command (i.e. > + * it is not ready yet); retry in this case. > + */ > + while (retry--) { > + mutex_lock(&priv->lock); > + priv->expect_response = read ? CMD_READ : CMD_WRITE; > + reinit_completion(&priv->cmd_complete); > + mutex_unlock(&priv->lock); > + > + if (retry != (retry_max - 1)) > + dev_dbg(&priv->serdev->dev, "cmd retry: %d", > + retry_max - retry); > + ret = bno055_sl_do_send_cmd(priv, read, addr, len, data); > + if (ret) > + continue; > + > + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, > + msecs_to_jiffies(100)); > + if (ret == -ERESTARTSYS) { > + priv->cmd_stale = true; > + return -ERESTARTSYS; > + } else if (!ret) { > + ret = -ETIMEDOUT; > + break; > + } > + ret = 0; > + > + /* > + * Poll if the IMU returns error (i.e busy), break if the IMU > + * returns OK or if the serial communication broke > + */ > + if (priv->cmd_status <= 0) > + break; > + } > + > +exit: > + if (ret) > + return ret; > + if (priv->cmd_status == STATUS_CRIT) > + return -EIO; > + if (priv->cmd_status == STATUS_FAIL) > + return -EINVAL; > + return 0; > +} > + > +static int bno055_sl_write_reg(void *context, const void *data, size_t count) > +{ > + int ret; > + int reg; > + u8 *write_data = (u8 *)data + 1; > + struct bno055_sl_priv *priv = context; > + > + if (count < 2) { > + dev_err(&priv->serdev->dev, "Invalid write count %d", count); > + return -EINVAL; > + } > + > + reg = ((u8 *)data)[0]; > + dev_dbg(&priv->serdev->dev, "wr reg 0x%x = 0x%x", reg, ((u8 *)data)[1]); > + ret = bno_sl_send_cmd(priv, 0, reg, count - 1, write_data); > + > + return ret; > +} > + > +static int bno055_sl_read_reg(void *context, > + const void *reg, size_t reg_size, > + void *val, size_t val_size) > +{ > + int ret; > + int reg_addr; > + struct bno055_sl_priv *priv = context; > + > + if (reg_size != 1) { Can we plausibly hit this? I would have though the regmap controls it and is set appropriately. Hence safe to drop this check. > + dev_err(&priv->serdev->dev, "Invalid read regsize %d", > + reg_size); > + return -EINVAL; > + } > + > + if (val_size > 128) { > + dev_err(&priv->serdev->dev, "Invalid read valsize %d", > + val_size); > + return -EINVAL; > + } > + > + reg_addr = ((u8 *)reg)[0]; > + dev_dbg(&priv->serdev->dev, "rd reg 0x%x (len %d)", reg_addr, val_size); > + mutex_lock(&priv->lock); > + priv->expected_data_len = val_size; > + priv->response_buf = val; > + mutex_unlock(&priv->lock); > + > + ret = bno_sl_send_cmd(priv, 1, reg_addr, val_size, NULL); > + > + mutex_lock(&priv->lock); > + priv->response_buf = NULL; > + mutex_unlock(&priv->lock); > + > + return ret; > +} > + > +/* > + * Handler for received data; this is called from the reicever callback whenever > + * it got some packet from the serial bus. The status tell us whether the > + * packet is valid (i.e. header ok && received payload len consistent wrt the > + * header). It's now our responsability to check whether this is what we > + * expected, of whether we got some unexpected, yet valid, packet. > + */ > +static void bno055_sl_handle_rx(struct bno055_sl_priv *priv, int status) > +{ > + mutex_lock(&priv->lock); > + switch (priv->expect_response) { > + case CMD_NONE: > + dev_warn(&priv->serdev->dev, "received unexpected, yet valid, data from sensor"); > + mutex_unlock(&priv->lock); > + return; > + > + case CMD_READ: > + priv->cmd_status = status; > + if (status == STATUS_OK && > + priv->rx.databuf_count != priv->expected_data_len) { > + /* > + * If we got here, then the lower layer serial protocol > + * seems consistent with itself; if we got an unexpected > + * amount of data then signal it as a non critical error > + */ > + priv->cmd_status = STATUS_FAIL; > + dev_warn(&priv->serdev->dev, "received an unexpected amount of, yet valid, data from sensor"); > + } > + break; > + > + case CMD_WRITE: > + priv->cmd_status = status; > + break; > + } > + > + priv->expect_response = CMD_NONE; > + complete(&priv->cmd_complete); > + mutex_unlock(&priv->lock); > +} > + > +/* > + * Serdev receiver FSM. This tracks the serial communication and parse the > + * header. It pushes packets to bno055_sl_handle_rx(), eventually communicating > + * failures (i.e. malformed packets). > + * Idellay it doesn't know anything about upper layer (i.e. if this is the Ideally > + * packet we were really expecting), but since we copies the payload into the > + * receiver buffer (that is not valid when i.e. we don't expect data), we > + * snoop a bit in the upper layer.. > + * Also, we assume to RX one pkt per time (i.e. the HW doesn't send anything > + * unless we require to AND we don't queue more than one request per time). > + */ > +static int bno055_sl_receive_buf(struct serdev_device *serdev, > + const unsigned char *buf, size_t size) > +{ > + int status; > + struct bno055_sl_priv *priv = serdev_device_get_drvdata(serdev); > + int _size = size; > + > + if (size == 0) > + return 0; > + > + dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); > + switch (priv->rx.state) { > + case RX_IDLE: > + /* > + * New packet. > + * Check for its 1st byte, that identifies the pkt type. > + */ > + if (buf[0] != 0xEE && buf[0] != 0xBB) { > + dev_err(&priv->serdev->dev, > + "Invalid packet start %x", buf[0]); > + bno055_sl_handle_rx(priv, STATUS_CRIT); > + break; > + } > + priv->rx.type = buf[0]; > + priv->rx.state = RX_START; > + size--; > + buf++; > + priv->rx.databuf_count = 0; > + fallthrough; > + > + case RX_START: > + /* > + * Packet RX in progress, we expect either 1-byte len or 1-byte > + * status depending by the packet type. > + */ > + if (size == 0) > + break; > + > + if (priv->rx.type == 0xEE) { > + if (size > 1) { > + dev_err(&priv->serdev->dev, "EE pkt. Extra data received"); > + status = STATUS_CRIT; > + > + } else { > + status = (buf[0] == 1) ? STATUS_OK : STATUS_FAIL; > + } > + bno055_sl_handle_rx(priv, status); > + priv->rx.state = RX_IDLE; > + break; > + > + } else { > + /*priv->rx.type == 0xBB */ > + priv->rx.state = RX_DATA; > + priv->rx.expected_len = buf[0]; > + size--; > + buf++; > + } > + fallthrough; > + > + case RX_DATA: > + /* Header parsed; now receiving packet data payload */ > + if (size == 0) > + break; > + > + if (priv->rx.databuf_count + size > priv->rx.expected_len) { > + /* > + * This is a inconsistency in serial protocol, we lost > + * sync and we don't know how to handle further data > + */ > + dev_err(&priv->serdev->dev, "BB pkt. Extra data received"); > + bno055_sl_handle_rx(priv, STATUS_CRIT); > + priv->rx.state = RX_IDLE; > + break; > + } > + > + mutex_lock(&priv->lock); > + /* > + * NULL e.g. when read cmd is stale or when no read cmd is > + * actually pending. > + */ > + if (priv->response_buf && > + /* > + * Snoop on the upper layer protocol stuff to make sure not > + * to write to an invalid memory. Apart for this, let's the > + * upper layer manage any inconsistency wrt expected data > + * len (as long as the serial protocol is consistent wrt > + * itself (i.e. response header is consistent with received > + * response len. > + */ > + (priv->rx.databuf_count + size <= priv->expected_data_len)) > + memcpy(priv->response_buf + priv->rx.databuf_count, > + buf, size); > + mutex_unlock(&priv->lock); > + > + priv->rx.databuf_count += size; > + > + /* > + * Reached expected len advertised by the IMU for the current > + * packet. Pass it to the upper layer (for us it is just valid). > + */ > + if (priv->rx.databuf_count == priv->rx.expected_len) { > + bno055_sl_handle_rx(priv, STATUS_OK); > + priv->rx.state = RX_IDLE; > + } > + break; > + } > + > + return _size; > +} > + > +static const struct serdev_device_ops bno055_sl_serdev_ops = { > + .receive_buf = bno055_sl_receive_buf, > + .write_wakeup = serdev_device_write_wakeup, > +}; > + > +static struct regmap_bus bno055_sl_regmap_bus = { > + .write = bno055_sl_write_reg, > + .read = bno055_sl_read_reg, > +}; > + > +static int bno055_sl_probe(struct serdev_device *serdev) > +{ > + struct bno055_sl_priv *priv; > + struct regmap *regmap; > + int ret; > + int irq = 0; > + > + priv = devm_kzalloc(&serdev->dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + serdev_device_set_drvdata(serdev, priv); > + priv->serdev = serdev; > + mutex_init(&priv->lock); > + init_completion(&priv->cmd_complete); > + > + serdev_device_set_client_ops(serdev, &bno055_sl_serdev_ops); > + ret = devm_serdev_device_open(&serdev->dev, serdev); > + if (ret) > + return ret; > + > + if (serdev_device_set_baudrate(serdev, 115200) != 115200) { > + dev_err(&serdev->dev, "Cannot set required baud rate"); > + return -EIO; > + } > + > + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); > + if (ret) { > + dev_err(&serdev->dev, "Cannot set required parity setting"); > + return ret; > + } > + serdev_device_set_flow_control(serdev, false); > + > + regmap = devm_regmap_init(&serdev->dev, &bno055_sl_regmap_bus, > + priv, &bno055_regmap_config); > + if (IS_ERR(regmap)) { > + dev_err(&serdev->dev, "Unable to init register map"); > + return PTR_ERR(regmap); > + } > + > + if (serdev->dev.of_node) { If possible, use generic fw node functions from linux/property.h rather than of specific ones. It 'might' be possible to instantiate this from ACPI using the magic of PRP0001 (which uses the dt bindings from an entry in the DSDT table in ACPI firmware). > + irq = of_irq_get(serdev->dev.of_node, 0); > + if (irq == -EPROBE_DEFER) > + return irq; > + if (irq <= 0) { > + dev_info(&serdev->dev, > + "Can't get IRQ resource (err %d)", irq); Isn't there an explicit errno for when it fails to get it because it isn't specified? We want to catch that and error out on anything else. Afterall if someone specified an IRQ that doesn't work, then they don't want us to hid that fact. > + irq = 0; > + } > + } > + > + return bno055_probe(&serdev->dev, regmap, irq, > + BNO055_SL_XFER_BURST_BREAK_THRESHOLD); > +} > + > +static const struct of_device_id bno055_sl_of_match[] = { > + { .compatible = "bosch,bno055-serial" }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, bno055_sl_of_match); > + > +static struct serdev_device_driver bno055_sl_driver = { > + .driver = { > + .name = BNO055_SL_DRIVER_NAME, > + .of_match_table = bno055_sl_of_match, > + }, > + .probe = bno055_sl_probe, > +}; > +module_serdev_device_driver(bno055_sl_driver); > + > +MODULE_AUTHOR("Andrea Merello <andrea.merello@iit.it>"); > +MODULE_DESCRIPTION("Bosch BNO055 serdev interface"); > +MODULE_LICENSE("GPL v2"); ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 4/4] iio: imu: add BNO055 serdev driver 2021-07-17 15:50 ` Jonathan Cameron @ 2021-07-19 8:49 ` Andrea Merello 2021-07-19 11:55 ` Andy Shevchenko 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-07-19 8:49 UTC (permalink / raw) To: Jonathan Cameron Cc: Lars-Peter Clausen, Rob Herring, Matt Ranostay, Andy Shevchenko, linux-kernel, linux-iio, Andrea Merello Il giorno sab 17 lug 2021 alle ore 17:48 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Thu, 15 Jul 2021 16:17:42 +0200 > Andrea Merello <andrea.merello@gmail.com> wrote: > > > This path adds a serdev driver for communicating to a BNO055 IMU > > via serial bus, and enables the BNO055 core driver to work in this > > scenario. > > > > 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 > > Hi Andrea, > > A few comments inline. > > Jonathan > > > --- > > drivers/iio/imu/bno055/Kconfig | 5 + > > drivers/iio/imu/bno055/Makefile | 1 + > > drivers/iio/imu/bno055/bno055_sl.c | 576 +++++++++++++++++++++++++++++ > > 3 files changed, 582 insertions(+) > > create mode 100644 drivers/iio/imu/bno055/bno055_sl.c > > > > diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig > > index 2bfed8df4554..6d2e8c9f85b7 100644 > > --- a/drivers/iio/imu/bno055/Kconfig > > +++ b/drivers/iio/imu/bno055/Kconfig > > @@ -5,3 +5,8 @@ > > > > config BOSH_BNO055_IIO > > tristate > > + > > +config BOSH_BNO055_SERIAL > > + tristate "Bosh BNO055 attached via serial bus" > > + depends on SERIAL_DEV_BUS > > + select BOSH_BNO055_IIO > > diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile > > index 15c5ddf8d648..b704b10b6bd1 100644 > > --- a/drivers/iio/imu/bno055/Makefile > > +++ b/drivers/iio/imu/bno055/Makefile > > @@ -4,3 +4,4 @@ > > # > > > > obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o > > +obj-$(CONFIG_BOSH_BNO055_SERIAL) += bno055_sl.o > > diff --git a/drivers/iio/imu/bno055/bno055_sl.c b/drivers/iio/imu/bno055/bno055_sl.c > > new file mode 100644 > > index 000000000000..9604d73d126c > > --- /dev/null > > +++ b/drivers/iio/imu/bno055/bno055_sl.c > > @@ -0,0 +1,576 @@ > > +// SPDX-License-Identifier: GPL-2.0-or-later > > +/* > > + * Serial line interface for Bosh BNO055 IMU (via serdev). > > + * This file implements serial communication up to the register read/write > > + * level. > > + * > > + * Copyright (C) 2021 Istituto Italiano di Tecnologia > > + * Electronic Design Laboratory > > + * Written by Andrea Merello <andrea.merello@iit.it> > > + * > > + * This driver is besed on > > + * Plantower PMS7003 particulate matter sensor driver > > + * Which is > > + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> > > + */ > > + > > +#include <linux/completion.h> > > +#include <linux/device.h> > > +#include <linux/errno.h> > > +#include <linux/jiffies.h> > > +#include <linux/kernel.h> > > +#include <linux/mod_devicetable.h> > > +#include <linux/module.h> > > +#include <linux/mutex.h> > > +#include <linux/of_irq.h> > > +#include <linux/regmap.h> > > +#include <linux/serdev.h> > > + > > +#include "bno055.h" > > + > > +#define BNO055_SL_DRIVER_NAME "bno055-sl" > > + > > +/* > > + * Register writes cmd have the following format > > + * +------+------+-----+-----+----- ... ----+ > > + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | > > + * +------+------+-----+-----+----- ... ----+ > > + * > > + * Register write responses have the following format > > + * +------+----------+ > > + * | 0xEE | ERROCODE | > > + * +------+----------+ > > + * > > + * Register read have the following format > > + * +------+------+-----+-----+ > > + * | 0xAA | 0xO1 | REG | LEN | > > + * +------+------+-----+-----+ > > + * > > + * Successful register read response have the following format > > + * +------+-----+----- ... ----+ > > + * | 0xBB | LEN | payload[LEN] | > > + * +------+-----+----- ... ----+ > > + * > > + * Failed register read response have the following format > > + * +------+--------+ > > + * | 0xEE | ERRCODE| (ERRCODE always > 1) > > + * +------+--------+ > > + * > > + * Error codes are > > + * 01: OK > > + * 02: read/write FAIL > > + * 04: invalid address > > + * 05: write on RO > > + * 06: wrong start byte > > + * 07: bus overrun > > + * 08: len too high > > + * 09: len too low > > + * 10: bus RX byte timeout (timeout is 30mS) > > + * > > + * > > + * **WORKAROUND ALERT** > > + * > > + * Serial communication seems very fragile: the BNO055 buffer seems to overflow > > + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. > > + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in > > + * between two bytes then the transaction fails (IMU internal RX FSM resets). > > + * > > + * BMU055 has been seen also failing to process commands in case we send them > > + * too close each other (or if it is somehow busy?) > > + * > > + * One idea would be to split data in chunks, and then wait 1-2mS between > > + * chunks (we hope not to exceed 30mS delay for any reason - which should > > + * be pretty a lot of time for us), and eventually retry in case the BNO055 > > + * gets upset for any reason. This seems to work in avoiding the overflow > > + * errors, but indeed it seems slower than just perform a retry when an overflow > > + * error occur. > > + * In particular I saw these scenarios: > > + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. > > + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could > > + * overflow, but it seem to sink all 4 bytes, then it returns error. > > + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending > > + * error after 4 bytes are sent; we have troubles in synchronizing again, > > + * because we are still sending data, and the IMU interprets it as the 1st > > + * byte of a new command. > > + * > > + * So, we workaround all this in the following way: > > + * In case of read we don't split the header but we rely on retries; This seems > > + * convenient for data read (where we TX only the hdr). > > + * For TX we split the transmission in 2-bytes chunks so that, we should not > > + * only avoid case 2 (which is still manageable), but we also hopefully avoid > > + * case 3, that would be by far worse. > > Nice docs and this sounds terrible! Indeed.. If anyone has nicer ideas, or is aware about better workaround, I would really love to know... > > + */ > > + > > +/* Read operation overhead: > > + * 4 bytes req + 2byte resp hdr > > + * 6 bytes = 60 bit (considering 1start + 1stop bits). > > + * 60/115200 = ~520uS > > + * In 520uS we could read back about 34 bytes that means 3 samples, this means > > + * that in case of scattered read in which the gap is 3 samples or less it is > > + * still convenient to go for a burst. > > + * We have to take into account also IMU response time - IMU seems to be often > > + * reasonably quick to respond, but sometimes it seems to be in some "critical > > + * section" in which it delays handling of serial protocol. > > + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap > > + */ > > + > > +#define BNO055_SL_XFER_BURST_BREAK_THRESHOLD 6 > > + > > +struct bno055_sl_priv { > > + struct serdev_device *serdev; > > + struct completion cmd_complete; > > + enum { > > + CMD_NONE, > > + CMD_READ, > > + CMD_WRITE, > > + } expect_response; > > + int expected_data_len; > > + u8 *response_buf; > > + enum { > > + STATUS_OK = 0, /* command OK */ > > + STATUS_FAIL = 1,/* IMU communicated an error */ > > + STATUS_CRIT = -1/* serial communication with IMU failed */ > > + } cmd_status; > > + struct mutex lock; > > + > > + /* Only accessed in behalf of RX callback context. No lock needed. */ > > + struct { > > + enum { > > + RX_IDLE, > > + RX_START, > > + RX_DATA > > + } state; > > + int databuf_count; > > + int expected_len; > > + int type; > > + } rx; > > + > > + /* Never accessed in behalf of RX callback context. No lock needed */ > > + bool cmd_stale; > > +}; > > + > > +static int bno055_sl_send_chunk(struct bno055_sl_priv *priv, u8 *data, int len) > > +{ > > + int ret; > > + > > + dev_dbg(&priv->serdev->dev, "send (len: %d): %*ph", len, len, data); > > + ret = serdev_device_write(priv->serdev, data, len, > > + msecs_to_jiffies(25)); > > + if (ret < len) > > + return ret < 0 ? ret : -EIO; > > Break this up perhaps as will be easier to read. > > if (ret < 0) > return ret; > > if (ret < len) > return -EIO; > > return 0; OK > > + return 0; > > +} > > + > > +/* > > + * Sends a read or write command. > > + * 'data' can be NULL (used in read case). 'len' parameter is always valid; in > > + * case 'data' is non-NULL then it must match 'data' size. > > + */ > > +static int bno055_sl_do_send_cmd(struct bno055_sl_priv *priv, > > + int read, int addr, int len, u8 *data) > > +{ > > + int ret; > > + int chunk_len; > > + u8 hdr[] = {0xAA, !!read, addr, len}; > > + > > + if (read) { > > + ret = bno055_sl_send_chunk(priv, hdr, 4); > > + } else { > > + ret = bno055_sl_send_chunk(priv, hdr, 2); > > + if (ret) > > + goto fail; > > + > > + usleep_range(2000, 3000); > > + ret = bno055_sl_send_chunk(priv, hdr + 2, 2); > > + } > > + if (ret) > > + goto fail; > > + > > + if (data) { > > + while (len) { > > + chunk_len = min(len, 2); > > + usleep_range(2000, 3000); > > + ret = bno055_sl_send_chunk(priv, data, chunk_len); > > + if (ret) > > + goto fail; > > + data += chunk_len; > > + len -= chunk_len; > > + } > > + } > > + > > + return 0; > > +fail: > > + /* waiting more than 30mS should clear the BNO055 internal state */ > > + usleep_range(40000, 50000); > > + return ret; > > +} > > + > > +static int bno_sl_send_cmd(struct bno055_sl_priv *priv, > > + int read, int addr, int len, u8 *data) > > +{ > > + const int retry_max = 5; > > + int retry = retry_max; > > + int ret = 0; > > + > > + /* > > + * In case previous command was interrupted we still neet to wait it to > > + * complete before we can issue new commands > > + */ > > + if (priv->cmd_stale) { > > + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, > > + msecs_to_jiffies(100)); > > + if (ret == -ERESTARTSYS) > > + return -ERESTARTSYS; > > + > > + priv->cmd_stale = false; > > + /* if serial protocol broke, bail out */ > > + if (priv->cmd_status == STATUS_CRIT) > > + goto exit; > > + } > > + > > + /* > > + * Try to convince the IMU to cooperate.. as explained in the comments > > + * at the top of this file, the IMU could also refuse the command (i.e. > > + * it is not ready yet); retry in this case. > > + */ > > + while (retry--) { > > + mutex_lock(&priv->lock); > > + priv->expect_response = read ? CMD_READ : CMD_WRITE; > > + reinit_completion(&priv->cmd_complete); > > + mutex_unlock(&priv->lock); > > + > > + if (retry != (retry_max - 1)) > > + dev_dbg(&priv->serdev->dev, "cmd retry: %d", > > + retry_max - retry); > > + ret = bno055_sl_do_send_cmd(priv, read, addr, len, data); > > + if (ret) > > + continue; > > + > > + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, > > + msecs_to_jiffies(100)); > > + if (ret == -ERESTARTSYS) { > > + priv->cmd_stale = true; > > + return -ERESTARTSYS; > > + } else if (!ret) { > > + ret = -ETIMEDOUT; > > + break; > > + } > > + ret = 0; > > + > > + /* > > + * Poll if the IMU returns error (i.e busy), break if the IMU > > + * returns OK or if the serial communication broke > > + */ > > + if (priv->cmd_status <= 0) > > + break; > > + } > > + > > +exit: > > + if (ret) > > + return ret; > > + if (priv->cmd_status == STATUS_CRIT) > > + return -EIO; > > + if (priv->cmd_status == STATUS_FAIL) > > + return -EINVAL; > > + return 0; > > +} > > + > > +static int bno055_sl_write_reg(void *context, const void *data, size_t count) > > +{ > > + int ret; > > + int reg; > > + u8 *write_data = (u8 *)data + 1; > > + struct bno055_sl_priv *priv = context; > > + > > + if (count < 2) { > > + dev_err(&priv->serdev->dev, "Invalid write count %d", count); > > + return -EINVAL; > > + } > > + > > + reg = ((u8 *)data)[0]; > > + dev_dbg(&priv->serdev->dev, "wr reg 0x%x = 0x%x", reg, ((u8 *)data)[1]); > > + ret = bno_sl_send_cmd(priv, 0, reg, count - 1, write_data); > > + > > + return ret; > > +} > > + > > +static int bno055_sl_read_reg(void *context, > > + const void *reg, size_t reg_size, > > + void *val, size_t val_size) > > +{ > > + int ret; > > + int reg_addr; > > + struct bno055_sl_priv *priv = context; > > + > > + if (reg_size != 1) { > > Can we plausibly hit this? I would have though the regmap controls it > and is set appropriately. Hence safe to drop this check. OK > > + dev_err(&priv->serdev->dev, "Invalid read regsize %d", > > + reg_size); > > + return -EINVAL; > > + } > > + > > + if (val_size > 128) { > > + dev_err(&priv->serdev->dev, "Invalid read valsize %d", > > + val_size); > > + return -EINVAL; > > + } > > + > > + reg_addr = ((u8 *)reg)[0]; > > + dev_dbg(&priv->serdev->dev, "rd reg 0x%x (len %d)", reg_addr, val_size); > > + mutex_lock(&priv->lock); > > + priv->expected_data_len = val_size; > > + priv->response_buf = val; > > + mutex_unlock(&priv->lock); > > + > > + ret = bno_sl_send_cmd(priv, 1, reg_addr, val_size, NULL); > > + > > + mutex_lock(&priv->lock); > > + priv->response_buf = NULL; > > + mutex_unlock(&priv->lock); > > + > > + return ret; > > +} > > + > > +/* > > + * Handler for received data; this is called from the reicever callback whenever > > + * it got some packet from the serial bus. The status tell us whether the > > + * packet is valid (i.e. header ok && received payload len consistent wrt the > > + * header). It's now our responsability to check whether this is what we > > + * expected, of whether we got some unexpected, yet valid, packet. > > + */ > > +static void bno055_sl_handle_rx(struct bno055_sl_priv *priv, int status) > > +{ > > + mutex_lock(&priv->lock); > > + switch (priv->expect_response) { > > + case CMD_NONE: > > + dev_warn(&priv->serdev->dev, "received unexpected, yet valid, data from sensor"); > > + mutex_unlock(&priv->lock); > > + return; > > + > > + case CMD_READ: > > + priv->cmd_status = status; > > + if (status == STATUS_OK && > > + priv->rx.databuf_count != priv->expected_data_len) { > > + /* > > + * If we got here, then the lower layer serial protocol > > + * seems consistent with itself; if we got an unexpected > > + * amount of data then signal it as a non critical error > > + */ > > + priv->cmd_status = STATUS_FAIL; > > + dev_warn(&priv->serdev->dev, "received an unexpected amount of, yet valid, data from sensor"); > > + } > > + break; > > + > > + case CMD_WRITE: > > + priv->cmd_status = status; > > + break; > > + } > > + > > + priv->expect_response = CMD_NONE; > > + complete(&priv->cmd_complete); > > + mutex_unlock(&priv->lock); > > +} > > + > > +/* > > + * Serdev receiver FSM. This tracks the serial communication and parse the > > + * header. It pushes packets to bno055_sl_handle_rx(), eventually communicating > > + * failures (i.e. malformed packets). > > + * Idellay it doesn't know anything about upper layer (i.e. if this is the > > Ideally Sure > > > + * packet we were really expecting), but since we copies the payload into the > > + * receiver buffer (that is not valid when i.e. we don't expect data), we > > + * snoop a bit in the upper layer.. > > + * Also, we assume to RX one pkt per time (i.e. the HW doesn't send anything > > + * unless we require to AND we don't queue more than one request per time). > > + */ > > +static int bno055_sl_receive_buf(struct serdev_device *serdev, > > + const unsigned char *buf, size_t size) > > +{ > > + int status; > > + struct bno055_sl_priv *priv = serdev_device_get_drvdata(serdev); > > + int _size = size; > > + > > + if (size == 0) > > + return 0; > > + > > + dev_dbg(&priv->serdev->dev, "recv (len %d): %*ph ", size, size, buf); > > + switch (priv->rx.state) { > > + case RX_IDLE: > > + /* > > + * New packet. > > + * Check for its 1st byte, that identifies the pkt type. > > + */ > > + if (buf[0] != 0xEE && buf[0] != 0xBB) { > > + dev_err(&priv->serdev->dev, > > + "Invalid packet start %x", buf[0]); > > + bno055_sl_handle_rx(priv, STATUS_CRIT); > > + break; > > + } > > + priv->rx.type = buf[0]; > > + priv->rx.state = RX_START; > > + size--; > > + buf++; > > + priv->rx.databuf_count = 0; > > + fallthrough; > > + > > + case RX_START: > > + /* > > + * Packet RX in progress, we expect either 1-byte len or 1-byte > > + * status depending by the packet type. > > + */ > > + if (size == 0) > > + break; > > + > > + if (priv->rx.type == 0xEE) { > > + if (size > 1) { > > + dev_err(&priv->serdev->dev, "EE pkt. Extra data received"); > > + status = STATUS_CRIT; > > + > > + } else { > > + status = (buf[0] == 1) ? STATUS_OK : STATUS_FAIL; > > + } > > + bno055_sl_handle_rx(priv, status); > > + priv->rx.state = RX_IDLE; > > + break; > > + > > + } else { > > + /*priv->rx.type == 0xBB */ > > + priv->rx.state = RX_DATA; > > + priv->rx.expected_len = buf[0]; > > + size--; > > + buf++; > > + } > > + fallthrough; > > + > > + case RX_DATA: > > + /* Header parsed; now receiving packet data payload */ > > + if (size == 0) > > + break; > > + > > + if (priv->rx.databuf_count + size > priv->rx.expected_len) { > > + /* > > + * This is a inconsistency in serial protocol, we lost > > + * sync and we don't know how to handle further data > > + */ > > + dev_err(&priv->serdev->dev, "BB pkt. Extra data received"); > > + bno055_sl_handle_rx(priv, STATUS_CRIT); > > + priv->rx.state = RX_IDLE; > > + break; > > + } > > + > > + mutex_lock(&priv->lock); > > + /* > > + * NULL e.g. when read cmd is stale or when no read cmd is > > + * actually pending. > > + */ > > + if (priv->response_buf && > > + /* > > + * Snoop on the upper layer protocol stuff to make sure not > > + * to write to an invalid memory. Apart for this, let's the > > + * upper layer manage any inconsistency wrt expected data > > + * len (as long as the serial protocol is consistent wrt > > + * itself (i.e. response header is consistent with received > > + * response len. > > + */ > > + (priv->rx.databuf_count + size <= priv->expected_data_len)) > > + memcpy(priv->response_buf + priv->rx.databuf_count, > > + buf, size); > > + mutex_unlock(&priv->lock); > > + > > + priv->rx.databuf_count += size; > > + > > + /* > > + * Reached expected len advertised by the IMU for the current > > + * packet. Pass it to the upper layer (for us it is just valid). > > + */ > > + if (priv->rx.databuf_count == priv->rx.expected_len) { > > + bno055_sl_handle_rx(priv, STATUS_OK); > > + priv->rx.state = RX_IDLE; > > + } > > + break; > > + } > > + > > + return _size; > > +} > > + > > +static const struct serdev_device_ops bno055_sl_serdev_ops = { > > + .receive_buf = bno055_sl_receive_buf, > > + .write_wakeup = serdev_device_write_wakeup, > > +}; > > + > > +static struct regmap_bus bno055_sl_regmap_bus = { > > + .write = bno055_sl_write_reg, > > + .read = bno055_sl_read_reg, > > +}; > > + > > +static int bno055_sl_probe(struct serdev_device *serdev) > > +{ > > + struct bno055_sl_priv *priv; > > + struct regmap *regmap; > > + int ret; > > + int irq = 0; > > + > > + priv = devm_kzalloc(&serdev->dev, sizeof(*priv), GFP_KERNEL); > > + if (!priv) > > + return -ENOMEM; > > + > > + serdev_device_set_drvdata(serdev, priv); > > + priv->serdev = serdev; > > + mutex_init(&priv->lock); > > + init_completion(&priv->cmd_complete); > > + > > + serdev_device_set_client_ops(serdev, &bno055_sl_serdev_ops); > > + ret = devm_serdev_device_open(&serdev->dev, serdev); > > + if (ret) > > + return ret; > > + > > + if (serdev_device_set_baudrate(serdev, 115200) != 115200) { > > + dev_err(&serdev->dev, "Cannot set required baud rate"); > > + return -EIO; > > + } > > + > > + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); > > + if (ret) { > > + dev_err(&serdev->dev, "Cannot set required parity setting"); > > + return ret; > > + } > > + serdev_device_set_flow_control(serdev, false); > > + > > + regmap = devm_regmap_init(&serdev->dev, &bno055_sl_regmap_bus, > > + priv, &bno055_regmap_config); > > + if (IS_ERR(regmap)) { > > + dev_err(&serdev->dev, "Unable to init register map"); > > + return PTR_ERR(regmap); > > + } > > + > > + if (serdev->dev.of_node) { > If possible, use generic fw node functions from > linux/property.h rather than of specific ones. It 'might' be possible > to instantiate this from ACPI using the magic of PRP0001 > (which uses the dt bindings from an entry in the DSDT table in ACPI > firmware). OK > > + irq = of_irq_get(serdev->dev.of_node, 0); > > + if (irq == -EPROBE_DEFER) > > + return irq; > > + if (irq <= 0) { > > + dev_info(&serdev->dev, > > + "Can't get IRQ resource (err %d)", irq); > Isn't there an explicit errno for when it fails to get it because it > isn't specified? We want to catch that and error out on anything else. > > Afterall if someone specified an IRQ that doesn't work, then they don't > want us to hid that fact. Here is my mistake, sorry: I wrote code for handling data ready interrupt, and I tried to hook it to a trigger in bno055.c. Unfortunately I cannot get it working because the IMU firmware is too old (and I really couldn't find a way to update it). So, since I couldn't even test my code, I dropped interrupt handling/trigger code for this series.. But I missed this chunk, that is actually a leftover.. We don't use the IRQ at all (it could be used for event like high G acceleration, but I have no support for them). So I would just drop this > > > + irq = 0; > > + } > > + } > > + > > + return bno055_probe(&serdev->dev, regmap, irq, > > + BNO055_SL_XFER_BURST_BREAK_THRESHOLD); > > +} > > + > > +static const struct of_device_id bno055_sl_of_match[] = { > > + { .compatible = "bosch,bno055-serial" }, > > + { } > > +}; > > +MODULE_DEVICE_TABLE(of, bno055_sl_of_match); > > + > > +static struct serdev_device_driver bno055_sl_driver = { > > + .driver = { > > + .name = BNO055_SL_DRIVER_NAME, > > + .of_match_table = bno055_sl_of_match, > > + }, > > + .probe = bno055_sl_probe, > > +}; > > +module_serdev_device_driver(bno055_sl_driver); > > + > > +MODULE_AUTHOR("Andrea Merello <andrea.merello@iit.it>"); > > +MODULE_DESCRIPTION("Bosch BNO055 serdev interface"); > > +MODULE_LICENSE("GPL v2"); > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 4/4] iio: imu: add BNO055 serdev driver 2021-07-19 8:49 ` Andrea Merello @ 2021-07-19 11:55 ` Andy Shevchenko 2021-07-19 12:59 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Andy Shevchenko @ 2021-07-19 11:55 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, linux-kernel, linux-iio, Andrea Merello On Mon, Jul 19, 2021 at 10:49:54AM +0200, Andrea Merello wrote: > Il giorno sab 17 lug 2021 alle ore 17:48 Jonathan Cameron > <jic23@kernel.org> ha scritto: > > On Thu, 15 Jul 2021 16:17:42 +0200 > > Andrea Merello <andrea.merello@gmail.com> wrote: ... > > > +/* > > > + * Register writes cmd have the following format > > > + * +------+------+-----+-----+----- ... ----+ > > > + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | > > > + * +------+------+-----+-----+----- ... ----+ > > > + * > > > + * Register write responses have the following format > > > + * +------+----------+ > > > + * | 0xEE | ERROCODE | > > > + * +------+----------+ > > > + * > > > + * Register read have the following format > > > + * +------+------+-----+-----+ > > > + * | 0xAA | 0xO1 | REG | LEN | > > > + * +------+------+-----+-----+ > > > + * > > > + * Successful register read response have the following format > > > + * +------+-----+----- ... ----+ > > > + * | 0xBB | LEN | payload[LEN] | > > > + * +------+-----+----- ... ----+ > > > + * > > > + * Failed register read response have the following format > > > + * +------+--------+ > > > + * | 0xEE | ERRCODE| (ERRCODE always > 1) > > > + * +------+--------+ > > > + * > > > + * Error codes are > > > + * 01: OK > > > + * 02: read/write FAIL > > > + * 04: invalid address > > > + * 05: write on RO > > > + * 06: wrong start byte > > > + * 07: bus overrun > > > + * 08: len too high > > > + * 09: len too low > > > + * 10: bus RX byte timeout (timeout is 30mS) > > > + * > > > + * > > > + * **WORKAROUND ALERT** > > > + * > > > + * Serial communication seems very fragile: the BNO055 buffer seems to overflow > > > + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. > > > + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in > > > + * between two bytes then the transaction fails (IMU internal RX FSM resets). > > > + * > > > + * BMU055 has been seen also failing to process commands in case we send them > > > + * too close each other (or if it is somehow busy?) > > > + * > > > + * One idea would be to split data in chunks, and then wait 1-2mS between > > > + * chunks (we hope not to exceed 30mS delay for any reason - which should > > > + * be pretty a lot of time for us), and eventually retry in case the BNO055 > > > + * gets upset for any reason. This seems to work in avoiding the overflow > > > + * errors, but indeed it seems slower than just perform a retry when an overflow > > > + * error occur. > > > + * In particular I saw these scenarios: > > > + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. > > > + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could > > > + * overflow, but it seem to sink all 4 bytes, then it returns error. > > > + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending > > > + * error after 4 bytes are sent; we have troubles in synchronizing again, > > > + * because we are still sending data, and the IMU interprets it as the 1st > > > + * byte of a new command. > > > + * > > > + * So, we workaround all this in the following way: > > > + * In case of read we don't split the header but we rely on retries; This seems > > > + * convenient for data read (where we TX only the hdr). > > > + * For TX we split the transmission in 2-bytes chunks so that, we should not > > > + * only avoid case 2 (which is still manageable), but we also hopefully avoid > > > + * case 3, that would be by far worse. > > > > Nice docs and this sounds terrible! > > Indeed.. If anyone has nicer ideas, or is aware about better > workaround, I would really love to know... This needs somebody to go thru data sheet and check for possibilities, what you described above is not gonna fly. Okay, "in a robust way". I can't believe there is nothing in the communication protocol that may increase a robustness. > > > + */ ... > > > +/* Read operation overhead: > > > + * 4 bytes req + 2byte resp hdr > > > + * 6 bytes = 60 bit (considering 1start + 1stop bits). > > > + * 60/115200 = ~520uS > > > + * In 520uS we could read back about 34 bytes that means 3 samples, this means > > > + * that in case of scattered read in which the gap is 3 samples or less it is > > > + * still convenient to go for a burst. > > > + * We have to take into account also IMU response time - IMU seems to be often > > > + * reasonably quick to respond, but sometimes it seems to be in some "critical > > > + * section" in which it delays handling of serial protocol. > > > + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap Missed perial and entire comment needs proper style and space occupation ratio. > > > + */ ... > > > + enum { > > > + STATUS_OK = 0, /* command OK */ > > > + STATUS_FAIL = 1,/* IMU communicated an error */ > > > + STATUS_CRIT = -1/* serial communication with IMU failed */ enum may be kernel doc described. > > > + } cmd_status; ... > > > +static struct serdev_device_driver bno055_sl_driver = { > > > + .driver = { > > > + .name = BNO055_SL_DRIVER_NAME, This is (semi-)ABI and preferably should be hard coded explicitly. > > > + .of_match_table = bno055_sl_of_match, > > > + }, > > > + .probe = bno055_sl_probe, > > > +}; -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 4/4] iio: imu: add BNO055 serdev driver 2021-07-19 11:55 ` Andy Shevchenko @ 2021-07-19 12:59 ` Andrea Merello 2021-07-19 14:15 ` Andy Shevchenko 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-07-19 12:59 UTC (permalink / raw) To: Andy Shevchenko Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, linux-kernel, linux-iio, Andrea Merello Il giorno lun 19 lug 2021 alle ore 13:56 Andy Shevchenko <andriy.shevchenko@linux.intel.com> ha scritto: > > On Mon, Jul 19, 2021 at 10:49:54AM +0200, Andrea Merello wrote: > > Il giorno sab 17 lug 2021 alle ore 17:48 Jonathan Cameron > > <jic23@kernel.org> ha scritto: > > > On Thu, 15 Jul 2021 16:17:42 +0200 > > > Andrea Merello <andrea.merello@gmail.com> wrote: > > ... > > > > > +/* > > > > + * Register writes cmd have the following format > > > > + * +------+------+-----+-----+----- ... ----+ > > > > + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | > > > > + * +------+------+-----+-----+----- ... ----+ > > > > + * > > > > + * Register write responses have the following format > > > > + * +------+----------+ > > > > + * | 0xEE | ERROCODE | > > > > + * +------+----------+ > > > > + * > > > > + * Register read have the following format > > > > + * +------+------+-----+-----+ > > > > + * | 0xAA | 0xO1 | REG | LEN | > > > > + * +------+------+-----+-----+ > > > > + * > > > > + * Successful register read response have the following format > > > > + * +------+-----+----- ... ----+ > > > > + * | 0xBB | LEN | payload[LEN] | > > > > + * +------+-----+----- ... ----+ > > > > + * > > > > + * Failed register read response have the following format > > > > + * +------+--------+ > > > > + * | 0xEE | ERRCODE| (ERRCODE always > 1) > > > > + * +------+--------+ > > > > + * > > > > + * Error codes are > > > > + * 01: OK > > > > + * 02: read/write FAIL > > > > + * 04: invalid address > > > > + * 05: write on RO > > > > + * 06: wrong start byte > > > > + * 07: bus overrun > > > > + * 08: len too high > > > > + * 09: len too low > > > > + * 10: bus RX byte timeout (timeout is 30mS) > > > > + * > > > > + * > > > > + * **WORKAROUND ALERT** > > > > + * > > > > + * Serial communication seems very fragile: the BNO055 buffer seems to overflow > > > > + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. > > > > + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in > > > > + * between two bytes then the transaction fails (IMU internal RX FSM resets). > > > > + * > > > > + * BMU055 has been seen also failing to process commands in case we send them > > > > + * too close each other (or if it is somehow busy?) > > > > + * > > > > + * One idea would be to split data in chunks, and then wait 1-2mS between > > > > + * chunks (we hope not to exceed 30mS delay for any reason - which should > > > > + * be pretty a lot of time for us), and eventually retry in case the BNO055 > > > > + * gets upset for any reason. This seems to work in avoiding the overflow > > > > + * errors, but indeed it seems slower than just perform a retry when an overflow > > > > + * error occur. > > > > + * In particular I saw these scenarios: > > > > + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. > > > > + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could > > > > + * overflow, but it seem to sink all 4 bytes, then it returns error. > > > > + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending > > > > + * error after 4 bytes are sent; we have troubles in synchronizing again, > > > > + * because we are still sending data, and the IMU interprets it as the 1st > > > > + * byte of a new command. > > > > + * > > > > + * So, we workaround all this in the following way: > > > > + * In case of read we don't split the header but we rely on retries; This seems > > > > + * convenient for data read (where we TX only the hdr). > > > > + * For TX we split the transmission in 2-bytes chunks so that, we should not > > > > + * only avoid case 2 (which is still manageable), but we also hopefully avoid > > > > + * case 3, that would be by far worse. > > > > > > Nice docs and this sounds terrible! > > > > Indeed.. If anyone has nicer ideas, or is aware about better > > workaround, I would really love to know... > > This needs somebody to go thru data sheet and check for possibilities, what you > described above is not gonna fly. Okay, "in a robust way". > > I can't believe there is nothing in the communication protocol that may > increase a robustness. The serial protocol is both described in the datasheet and in an application note "BNO055UART interface". Both of them mention the fact that the IMU could just fail in processing the commands and responds with a status message with the "overflow" error code. The application note says this can happen because of an internal IMU buffer clearing stuff not happening in time, and that you have to retry the command in such case (which works for read commands, because after the header the IMU will always respond with something). They say nothing about the fact that the IMU could decide to respond with an "overflow" status message when a write command is still being TXed, even if it is not finished yet, but this actually happens (seen at least after the 4-bytes header). I think there is not much other information about this in datasheet and application note. Besides, the message formats are also described the comments in bno055_sl.c Given that the IMU behaves like this, I could only see three possible workarounds for managing write commands: 1 - be quick enough on RX side in catching the IMU overflow status response before sending the next char, which seems unfeasible. 2 - be slow enough to let the IMU do its own stuff, which seems doable. 3 - let the mess happen and try to recover: when we get the IMU overflow error then we might still being TXing; in this case we stop and we wait for the IMU to complain for a malformed command, but I'm unsure how the IMU could handle this: it will refuse the wrong starting byte (unless our payload is 0xAA by chance), but then how many bytes does it throw away? How may (malformed) commands would it try to extract from a the garbage we TXed (how may complaints response would we receive and need to wait) ? And what if there is something in payload that resembles a valid command and got accepted? This seems worse than workaround #2 What I meant: given this IMU behaviour, if someone has a better idea about how to deal with it, I would listen :) > > > > + */ > > ... > > > > > +/* Read operation overhead: > > > > + * 4 bytes req + 2byte resp hdr > > > > + * 6 bytes = 60 bit (considering 1start + 1stop bits). > > > > + * 60/115200 = ~520uS > > > > + * In 520uS we could read back about 34 bytes that means 3 samples, this means > > > > + * that in case of scattered read in which the gap is 3 samples or less it is > > > > + * still convenient to go for a burst. > > > > + * We have to take into account also IMU response time - IMU seems to be often > > > > + * reasonably quick to respond, but sometimes it seems to be in some "critical > > > > + * section" in which it delays handling of serial protocol. > > > > + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap > > Missed perial and entire comment needs proper style and space occupation ratio. Perial? But OK: text reflow and I see the 1st line for multilne commend is not correct. > > > > + */ > > ... > > > > > + enum { > > > > + STATUS_OK = 0, /* command OK */ > > > > + STATUS_FAIL = 1,/* IMU communicated an error */ > > > > + STATUS_CRIT = -1/* serial communication with IMU failed */ > > enum may be kernel doc described. OK > > > > + } cmd_status; > > ... > > > > > +static struct serdev_device_driver bno055_sl_driver = { > > > > + .driver = { > > > > > + .name = BNO055_SL_DRIVER_NAME, > > This is (semi-)ABI and preferably should be hard coded explicitly. OK > > > > + .of_match_table = bno055_sl_of_match, > > > > + }, > > > > + .probe = bno055_sl_probe, > > > > +}; > > -- > With Best Regards, > Andy Shevchenko > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 4/4] iio: imu: add BNO055 serdev driver 2021-07-19 12:59 ` Andrea Merello @ 2021-07-19 14:15 ` Andy Shevchenko 2021-07-19 15:07 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Andy Shevchenko @ 2021-07-19 14:15 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, linux-kernel, linux-iio, Andrea Merello On Mon, Jul 19, 2021 at 02:59:30PM +0200, Andrea Merello wrote: > Il giorno lun 19 lug 2021 alle ore 13:56 Andy Shevchenko > <andriy.shevchenko@linux.intel.com> ha scritto: > > > > On Mon, Jul 19, 2021 at 10:49:54AM +0200, Andrea Merello wrote: > > > Il giorno sab 17 lug 2021 alle ore 17:48 Jonathan Cameron > > > <jic23@kernel.org> ha scritto: > > > > On Thu, 15 Jul 2021 16:17:42 +0200 > > > > Andrea Merello <andrea.merello@gmail.com> wrote: ... > > > > > +/* > > > > > + * Register writes cmd have the following format > > > > > + * +------+------+-----+-----+----- ... ----+ > > > > > + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | > > > > > + * +------+------+-----+-----+----- ... ----+ > > > > > + * > > > > > + * Register write responses have the following format > > > > > + * +------+----------+ > > > > > + * | 0xEE | ERROCODE | > > > > > + * +------+----------+ > > > > > + * > > > > > + * Register read have the following format > > > > > + * +------+------+-----+-----+ > > > > > + * | 0xAA | 0xO1 | REG | LEN | > > > > > + * +------+------+-----+-----+ > > > > > + * > > > > > + * Successful register read response have the following format > > > > > + * +------+-----+----- ... ----+ > > > > > + * | 0xBB | LEN | payload[LEN] | > > > > > + * +------+-----+----- ... ----+ > > > > > + * > > > > > + * Failed register read response have the following format > > > > > + * +------+--------+ > > > > > + * | 0xEE | ERRCODE| (ERRCODE always > 1) > > > > > + * +------+--------+ > > > > > + * > > > > > + * Error codes are > > > > > + * 01: OK > > > > > + * 02: read/write FAIL > > > > > + * 04: invalid address > > > > > + * 05: write on RO > > > > > + * 06: wrong start byte > > > > > + * 07: bus overrun > > > > > + * 08: len too high > > > > > + * 09: len too low > > > > > + * 10: bus RX byte timeout (timeout is 30mS) > > > > > + * > > > > > + * > > > > > + * **WORKAROUND ALERT** > > > > > + * > > > > > + * Serial communication seems very fragile: the BNO055 buffer seems to overflow > > > > > + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. > > > > > + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in > > > > > + * between two bytes then the transaction fails (IMU internal RX FSM resets). > > > > > + * > > > > > + * BMU055 has been seen also failing to process commands in case we send them > > > > > + * too close each other (or if it is somehow busy?) > > > > > + * > > > > > + * One idea would be to split data in chunks, and then wait 1-2mS between > > > > > + * chunks (we hope not to exceed 30mS delay for any reason - which should > > > > > + * be pretty a lot of time for us), and eventually retry in case the BNO055 > > > > > + * gets upset for any reason. This seems to work in avoiding the overflow > > > > > + * errors, but indeed it seems slower than just perform a retry when an overflow > > > > > + * error occur. > > > > > + * In particular I saw these scenarios: > > > > > + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. > > > > > + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could > > > > > + * overflow, but it seem to sink all 4 bytes, then it returns error. > > > > > + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending > > > > > + * error after 4 bytes are sent; we have troubles in synchronizing again, > > > > > + * because we are still sending data, and the IMU interprets it as the 1st > > > > > + * byte of a new command. > > > > > + * > > > > > + * So, we workaround all this in the following way: > > > > > + * In case of read we don't split the header but we rely on retries; This seems > > > > > + * convenient for data read (where we TX only the hdr). > > > > > + * For TX we split the transmission in 2-bytes chunks so that, we should not > > > > > + * only avoid case 2 (which is still manageable), but we also hopefully avoid > > > > > + * case 3, that would be by far worse. > > > > > > > > Nice docs and this sounds terrible! > > > > > > Indeed.. If anyone has nicer ideas, or is aware about better > > > workaround, I would really love to know... > > > > This needs somebody to go thru data sheet and check for possibilities, what you > > described above is not gonna fly. Okay, "in a robust way". > > > > I can't believe there is nothing in the communication protocol that may > > increase a robustness. > > The serial protocol is both described in the datasheet and in an > application note "BNO055UART interface". Both of them mention the fact > that the IMU could just fail in processing the commands and responds > with a status message with the "overflow" error code. The application > note says this can happen because of an internal IMU buffer clearing > stuff not happening in time, and that you have to retry the command in > such case (which works for read commands, because after the header the > IMU will always respond with something). > > They say nothing about the fact that the IMU could decide to respond > with an "overflow" status message when a write command is still being > TXed, even if it is not finished yet, but this actually happens (seen > at least after the 4-bytes header). (1) > > I think there is not much other information about this in datasheet > and application note. Besides, the message formats are also described > the comments in bno055_sl.c > > Given that the IMU behaves like this, I could only see three possible > workarounds for managing write commands: > 1 - be quick enough on RX side in catching the IMU overflow status > response before sending the next char, which seems unfeasible. > 2 - be slow enough to let the IMU do its own stuff, which seems doable. > 3 - let the mess happen and try to recover: when we get the IMU > overflow error then we might still being TXing; in this case we stop > and we wait for the IMU to complain for a malformed command, but I'm > unsure how the IMU could handle this: it will refuse the wrong > starting byte (unless our payload is 0xAA by chance), but then how > many bytes does it throw away? How may (malformed) commands would it > try to extract from a the garbage we TXed (how may complaints response > would we receive and need to wait) ? And what if there is something in > payload that resembles a valid command and got accepted? This seems > worse than workaround #2 I believe the #3 is the right thing to do actually. The payload is up to 128 bytes and speed is fixed. I believe that firmware has internally the state of the input processing. OTOH the UART is duplex and what you need in the driver is to have a callback that will read whatever the answer from the sensor will be at the time it appears. Also note that Linux is not an RTOS and you may end up, maybe rarely, in the case which resembles the #3 while using workaround #2. On top of that you demolish the idea of using DMA with UART. (Btw, AN012 [1] says 100ms is the write timeout for the next byte, and not 30ms.) As AN012 rightfully pointed out the UART is _async_ interface, so I believe (1) is covered by this, meaning that error respond may appear _at any time_ on the (host-side) Rx line. My personal take away is never ever use UART for this kind (*) of communications and this sensor specifically. *) time-based IPCs are doomed by definition in non-RTOS environments with UART hardware interface. [1]: BST-BNO055-AN012-00 | Revision 1.0 | June 2015 > What I meant: given this IMU behaviour, if someone has a better idea > about how to deal with it, I would listen :) > > > > > > + */ ... > > > > > +/* Read operation overhead: > > > > > + * 4 bytes req + 2byte resp hdr > > > > > + * 6 bytes = 60 bit (considering 1start + 1stop bits). > > > > > + * 60/115200 = ~520uS > > > > > + * In 520uS we could read back about 34 bytes that means 3 samples, this means > > > > > + * that in case of scattered read in which the gap is 3 samples or less it is > > > > > + * still convenient to go for a burst. > > > > > + * We have to take into account also IMU response time - IMU seems to be often > > > > > + * reasonably quick to respond, but sometimes it seems to be in some "critical > > > > > + * section" in which it delays handling of serial protocol. > > > > > + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap > > > > Missed perial and entire comment needs proper style and space occupation ratio. > > Perial? But OK: text reflow and I see the 1st line for multilne Period. > commend is not correct. > > > > > > + */ -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [PATCH 4/4] iio: imu: add BNO055 serdev driver 2021-07-19 14:15 ` Andy Shevchenko @ 2021-07-19 15:07 ` Andrea Merello 0 siblings, 0 replies; 89+ messages in thread From: Andrea Merello @ 2021-07-19 15:07 UTC (permalink / raw) To: Andy Shevchenko Cc: Jonathan Cameron, Lars-Peter Clausen, Rob Herring, Matt Ranostay, linux-kernel, linux-iio, Andrea Merello Il giorno lun 19 lug 2021 alle ore 16:15 Andy Shevchenko <andriy.shevchenko@linux.intel.com> ha scritto: > > On Mon, Jul 19, 2021 at 02:59:30PM +0200, Andrea Merello wrote: > > Il giorno lun 19 lug 2021 alle ore 13:56 Andy Shevchenko > > <andriy.shevchenko@linux.intel.com> ha scritto: > > > > > > On Mon, Jul 19, 2021 at 10:49:54AM +0200, Andrea Merello wrote: > > > > Il giorno sab 17 lug 2021 alle ore 17:48 Jonathan Cameron > > > > <jic23@kernel.org> ha scritto: > > > > > On Thu, 15 Jul 2021 16:17:42 +0200 > > > > > Andrea Merello <andrea.merello@gmail.com> wrote: > > ... > > > > > > > +/* > > > > > > + * Register writes cmd have the following format > > > > > > + * +------+------+-----+-----+----- ... ----+ > > > > > > + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | > > > > > > + * +------+------+-----+-----+----- ... ----+ > > > > > > + * > > > > > > + * Register write responses have the following format > > > > > > + * +------+----------+ > > > > > > + * | 0xEE | ERROCODE | > > > > > > + * +------+----------+ > > > > > > + * > > > > > > + * Register read have the following format > > > > > > + * +------+------+-----+-----+ > > > > > > + * | 0xAA | 0xO1 | REG | LEN | > > > > > > + * +------+------+-----+-----+ > > > > > > + * > > > > > > + * Successful register read response have the following format > > > > > > + * +------+-----+----- ... ----+ > > > > > > + * | 0xBB | LEN | payload[LEN] | > > > > > > + * +------+-----+----- ... ----+ > > > > > > + * > > > > > > + * Failed register read response have the following format > > > > > > + * +------+--------+ > > > > > > + * | 0xEE | ERRCODE| (ERRCODE always > 1) > > > > > > + * +------+--------+ > > > > > > + * > > > > > > + * Error codes are > > > > > > + * 01: OK > > > > > > + * 02: read/write FAIL > > > > > > + * 04: invalid address > > > > > > + * 05: write on RO > > > > > > + * 06: wrong start byte > > > > > > + * 07: bus overrun > > > > > > + * 08: len too high > > > > > > + * 09: len too low > > > > > > + * 10: bus RX byte timeout (timeout is 30mS) > > > > > > + * > > > > > > + * > > > > > > + * **WORKAROUND ALERT** > > > > > > + * > > > > > > + * Serial communication seems very fragile: the BNO055 buffer seems to overflow > > > > > > + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. > > > > > > + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in > > > > > > + * between two bytes then the transaction fails (IMU internal RX FSM resets). > > > > > > + * > > > > > > + * BMU055 has been seen also failing to process commands in case we send them > > > > > > + * too close each other (or if it is somehow busy?) > > > > > > + * > > > > > > + * One idea would be to split data in chunks, and then wait 1-2mS between > > > > > > + * chunks (we hope not to exceed 30mS delay for any reason - which should > > > > > > + * be pretty a lot of time for us), and eventually retry in case the BNO055 > > > > > > + * gets upset for any reason. This seems to work in avoiding the overflow > > > > > > + * errors, but indeed it seems slower than just perform a retry when an overflow > > > > > > + * error occur. > > > > > > + * In particular I saw these scenarios: > > > > > > + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. > > > > > > + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could > > > > > > + * overflow, but it seem to sink all 4 bytes, then it returns error. > > > > > > + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending > > > > > > + * error after 4 bytes are sent; we have troubles in synchronizing again, > > > > > > + * because we are still sending data, and the IMU interprets it as the 1st > > > > > > + * byte of a new command. > > > > > > + * > > > > > > + * So, we workaround all this in the following way: > > > > > > + * In case of read we don't split the header but we rely on retries; This seems > > > > > > + * convenient for data read (where we TX only the hdr). > > > > > > + * For TX we split the transmission in 2-bytes chunks so that, we should not > > > > > > + * only avoid case 2 (which is still manageable), but we also hopefully avoid > > > > > > + * case 3, that would be by far worse. > > > > > > > > > > Nice docs and this sounds terrible! > > > > > > > > Indeed.. If anyone has nicer ideas, or is aware about better > > > > workaround, I would really love to know... > > > > > > This needs somebody to go thru data sheet and check for possibilities, what you > > > described above is not gonna fly. Okay, "in a robust way". > > > > > > I can't believe there is nothing in the communication protocol that may > > > increase a robustness. > > > > The serial protocol is both described in the datasheet and in an > > application note "BNO055UART interface". Both of them mention the fact > > that the IMU could just fail in processing the commands and responds > > with a status message with the "overflow" error code. The application > > note says this can happen because of an internal IMU buffer clearing > > stuff not happening in time, and that you have to retry the command in > > such case (which works for read commands, because after the header the > > IMU will always respond with something). > > > > They say nothing about the fact that the IMU could decide to respond > > with an "overflow" status message when a write command is still being > > TXed, even if it is not finished yet, but this actually happens (seen > > at least after the 4-bytes header). > > (1) > > > > > I think there is not much other information about this in datasheet > > and application note. Besides, the message formats are also described > > the comments in bno055_sl.c > > > > Given that the IMU behaves like this, I could only see three possible > > workarounds for managing write commands: > > 1 - be quick enough on RX side in catching the IMU overflow status > > response before sending the next char, which seems unfeasible. > > 2 - be slow enough to let the IMU do its own stuff, which seems doable. > > 3 - let the mess happen and try to recover: when we get the IMU > > overflow error then we might still being TXing; in this case we stop > > and we wait for the IMU to complain for a malformed command, but I'm > > unsure how the IMU could handle this: it will refuse the wrong > > starting byte (unless our payload is 0xAA by chance), but then how > > many bytes does it throw away? How may (malformed) commands would it > > try to extract from a the garbage we TXed (how may complaints response > > would we receive and need to wait) ? And what if there is something in > > payload that resembles a valid command and got accepted? This seems > > worse than workaround #2 > > I believe the #3 is the right thing to do actually. > > The payload is up to 128 bytes and speed is fixed. I believe that firmware has > internally the state of the input processing. OTOH the UART is duplex and what > you need in the driver is to have a callback that will read whatever the answer > from the sensor will be at the time it appears. I can add a reader thread that always process IMU data, but I doubt we can make assumptions on the fact it will be triggered in time to avoid to TX extra characters after IMU signals an overflow; this seems really a RT stuff, and it would even depends by the USART HW (think about USB-UART adaptors). So, how could we implement #3 dealing correctly with the issue about the IMU possiblly misinterpreting the bytes we might have already sent out? What's if they are a valid command by chance? The problem here is that this protocol has no any CRC or something like that to make sure garbage is really thrown away. > Also note that Linux is not an RTOS and you may end up, maybe rarely, in the > case which resembles the #3 while using workaround #2. Yes, in case we sleep more than 30mS we fail. I would say it's very unlikely, but that's true. Maybe if I add a reader thread to the workaround #2, considering we sleep every two bytes, then we can know about IMU errors - maybe still not before TXing any further data - but before we TX the bare minimum data amount in order to produce an unintentionally valid write command (should be 5 bytes). Anyway it seems to me that there is no perfectly robust way to deal with this IMU.. But, in real word scenarios, workaround #2 seem to work well enough. > On top of that you demolish the idea of using DMA with UART. True, but we really have no rush for write commands, so that it shouldn't be an issue indeed - and apart for the few bytes of the calibration data, that we might discuss about, all other writes are so short that DMA has probably no real advantage (OTOH read command doesn't have any extra sleep, so we are fine with reading the IMU measures). > (Btw, AN012 [1] says 100ms is the write timeout for the next byte, > and not 30ms.) Yes: there is an inconsistency here: the datasheet says 30mS, the AN says 100mS. We should then consider not to exceed 30mS. > As AN012 rightfully pointed out the UART is _async_ interface, so I believe (1) > is covered by this, meaning that error respond may appear _at any time_ on the > (host-side) Rx line. Ah, that is an interesting interpretation :) > My personal take away is never ever use UART for this kind (*) of > communications and this sensor specifically. Consider that this kind of devices are position dependant, e.g. you want to fix them on your arms, or on your robot feet, etc. I2C is electrically unsuitable for long off-board wires, while serial lines should be by far better in this case. > *) time-based IPCs are doomed by definition in non-RTOS environments with UART > hardware interface. We have to live with this :/ But indeed having sensors that measure real-world physical phenomena probably are a bit RT-ish anyway.. > [1]: BST-BNO055-AN012-00 | Revision 1.0 | June 2015 > > > What I meant: given this IMU behaviour, if someone has a better idea > > about how to deal with it, I would listen :) > > > > > > > > + */ > > ... > > > > > > > +/* Read operation overhead: > > > > > > + * 4 bytes req + 2byte resp hdr > > > > > > + * 6 bytes = 60 bit (considering 1start + 1stop bits). > > > > > > + * 60/115200 = ~520uS > > > > > > + * In 520uS we could read back about 34 bytes that means 3 samples, this means > > > > > > + * that in case of scattered read in which the gap is 3 samples or less it is > > > > > > + * still convenient to go for a burst. > > > > > > + * We have to take into account also IMU response time - IMU seems to be often > > > > > > + * reasonably quick to respond, but sometimes it seems to be in some "critical > > > > > > + * section" in which it delays handling of serial protocol. > > > > > > + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap > > > > > > Missed perial and entire comment needs proper style and space occupation ratio. > > > > Perial? But OK: text reflow and I see the 1st line for multilne > > Period. Ah, OK :) > > commend is not correct. > > > > > > > > + */ > > -- > With Best Regards, > Andy Shevchenko > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 00/10] Add support for Bosch BNO055 IMU 2021-07-15 14:17 [PATCH 0/4] Add support for Bosch BNO055 IMU Andrea Merello ` (3 preceding siblings ...) 2021-07-15 14:17 ` [PATCH 4/4] iio: imu: add BNO055 serdev driver Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 10:18 ` [v2 01/10] utils_macro: introduce find_closest_unsorted() Andrea Merello ` (10 more replies) 4 siblings, 11 replies; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo This series (tries to) add support for Bosch BNO055 IMU to Linux IIO subsystem. It is made up several patches: 1/10: introduces the generic helper find_closest_unsorted() 2/10 to 5/10: add some IIO modifiers, and their documentation, to the IIO core layer, in order to being able to expose the linear acceleration and Euler angles among standard attributes. 6/10 to 8/10: add the core IIO BNO055 driver and its documentation (including documentation for DT bindings) 9/10: adds serdev BNO055 driver to actually use the IMU via serial line 10/10: adds I2C BNO055 driver to actually use the IMU via I2C wiring Differences wrt v1: - Fixed GPL license version, which was wrong due to bad copy-pastes - Make less noise in log and get rid of some dev_dbg()s - Fix deferred probe handing and fix devm_add_action_or_reset() usage - Get rid of unneeded zeroing for driver data and some IIO "val2"s - Get rid of some leftovers of my attempt to support interrupts (which don't fully work unless the IMU firmware gets updated) - Move IIO buffer off stack and make sure its first zeroed not to leak kernel data - Hopefully addressed all maintainers and reviewers stylistic advices; fixed some typos - Take advantage of more kernel helpers. Note: this series depends on Yury Norov bitmap series i.e. "[PATCH 14/16] bitmap: unify find_bit operations" - Make find_closest_unsorted() become an external generic helper - Reworked sysfs ABI as per maintainers advices - Added ABI documentation where needed - Added I2C support - Reworked DT documentation as per maintainers advices. Added I2C example The serial protocol handling have been criticized because it is not very robust, however I couldn't really find any way to improve it; no changes here wrt v1. I think the protocol itself is inherently weak and there is nothing we can do about this (BTW here it is working fine). Differences wrt other BNO055 drivers: Previously at least another driver for the very same chip has been posted to the Linux ML [0], but it has been never merged, and it seems no one cared of it since quite a long time. This driver differs from the above driver on the following aspects: - This driver supports also serial access - The above driver tried to support all IMU HW modes by allowing to choose one in the DT, and adapting IIO attributes accordingly. This driver does not rely on DT for this, instead settings are done via sysfs attributes. All IIO attributes are always exposed; more on this later on. This driver however supports only a subset of the HW-supported modes. - This driver has some support for managing the IMU calibration Supported operation modes: - AMG (accelerometer, magnetometer and gyroscope) mode, which provides raw (uncalibrated) measurements from the said sensors, and allows for setting some parameters about them (e.g. filter cut-off frequency, max sensor ranges, etc). - Fusion mode, which still provides AMG measures, while it also provides other data calculated by the IMU (e.g. rotation angles, linear acceleration, etc). In this mode user has no freedom to set any sensor parameter, since the HW locks them. Autocalibration and correction is performed by the IMU. IIO attributes exposing sensors parameters are always present, but in fusion modes the available values are constrained to just the one used by the HW. This is reflected in the '*_available' IIO attributes. Trying to set a not-supported value always falls back to the closest supported one, which in this case is just the one in use by the HW. IIO attributes for unavailable measurements (e.g. Euler angles in AMG mode) just read zero (which is consistent WRT what you get when reading from a buffer with those attributes enabled). IMU calibration: The IMU supports for two sets of calibration parameters: - SIC matrix. user-provided; this driver doesn't currently support it - Offset and radius parameters. The IMU automatically finds out them when it is running in fusion mode; supported by this driver. The driver provides access to autocalibration flags (i.e. you can known if the IMU has successfully autocalibrated) and to calibration data blob. The user can save this blob in a "firmware" file (i.e. in /lib/firmware) that the driver looks for at probe time. If found, then the IMU is initialized with this calibration data. This saves the user from performing the calibration procedure every time (which consist of moving the IMU in various way). The driver looks for calibration data file using two different names: first a file whose name is suffixed with the IMU unique ID is searched for; this is useful when there is more than one IMU instance. If this file is not found, then a "generic" calibration file is searched for (which can be used when only one IMU is present, without struggling with fancy names, that changes on each device). In AMG mode the IIO 'offset' attributes provide access to the offsets from calibration data (if any), so that the user can apply them to the accel, angvel and magn IIO attributes. In fusion mode they are not needed and read as zero. Access protocols and serdev module: The serial protocol is quite simple, but there are tricks to make it really works. Those tricks and workarounds are documented in the driver source file. The core BNO055 driver tries to group readings in burst when appropriate, in order to optimize triggered buffer operation. The threshold for splitting a burst (i.e. max number of unused bytes in the middle of a burst that will be throw away) is provided to the core driver by the lowlevel access driver (either serdev or I2C) at probe time. [0] https://www.spinics.net/lists/linux-iio/msg25508.html Andrea Merello (10): utils_macro: introduce find_closest_unsorted() iio: document linear acceleration modifiers iio: document euler angles modifiers iio: add modifiers for linear acceleration iio: add modifers for pitch, yaw, roll iio: document bno055 private sysfs attributes iio: imu: add Bosch Sensortec BNO055 core driver dt-bindings: iio: imu: add documentation for Bosch BNO055 bindings iio: imu: add BNO055 serdev driver iio: imu: add BNO055 I2C driver Documentation/ABI/testing/sysfs-bus-iio | 16 + .../ABI/testing/sysfs-bus-iio-bno055 | 84 + .../bindings/iio/imu/bosch,bno055.yaml | 59 + drivers/iio/imu/Kconfig | 1 + drivers/iio/imu/Makefile | 1 + drivers/iio/imu/bno055/Kconfig | 15 + drivers/iio/imu/bno055/Makefile | 5 + drivers/iio/imu/bno055/bno055.c | 1485 +++++++++++++++++ drivers/iio/imu/bno055/bno055.h | 12 + drivers/iio/imu/bno055/bno055_i2c.c | 54 + drivers/iio/imu/bno055/bno055_sl.c | 568 +++++++ drivers/iio/industrialio-core.c | 6 + include/linux/util_macros.h | 26 + include/uapi/linux/iio/types.h | 7 +- 14 files changed, 2338 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-bno055 create mode 100644 Documentation/devicetree/bindings/iio/imu/bosch,bno055.yaml 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 create mode 100644 drivers/iio/imu/bno055/bno055_i2c.c create mode 100644 drivers/iio/imu/bno055/bno055_sl.c -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 01/10] utils_macro: introduce find_closest_unsorted() 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 10:25 ` Andy Shevchenko 2021-10-28 10:18 ` [v2 02/10] iio: document linear acceleration modifiers Andrea Merello ` (9 subsequent siblings) 10 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello, Andrea Merello This is similar to find_closest() and find_closest_descending(), but, it doesn't make any assumption about the array being ordered. Signed-off-by: Andrea Merello <andrea.merello@iit.it> --- include/linux/util_macros.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/linux/util_macros.h b/include/linux/util_macros.h index 72299f261b25..b48f80ceb380 100644 --- a/include/linux/util_macros.h +++ b/include/linux/util_macros.h @@ -2,6 +2,8 @@ #ifndef _LINUX_HELPER_MACROS_H_ #define _LINUX_HELPER_MACROS_H_ +#include <linux/math.h> + #define __find_closest(x, a, as, op) \ ({ \ typeof(as) __fc_i, __fc_as = (as) - 1; \ @@ -38,4 +40,28 @@ */ #define find_closest_descending(x, a, as) __find_closest(x, a, as, >=) +/** + * find_closest_unsorted - locate the closest element in a unsorted array + * @x: The reference value. + * @a: The array in which to look for the closest element. + * @as: Size of 'a'. + * + * Similar to find_closest() but 'a' has no requirement to being sorted + */ +#define find_closest_unsorted(x, a, as) \ +({ \ + typeof(x) __fc_best_delta, __fc_delta; \ + typeof(as) __fc_i, __fc_best_idx; \ + bool __fc_first = true; \ + for (__fc_i = 0; __fc_i < (as); __fc_i++) { \ + __fc_delta = abs(a[__fc_i] - (x)); \ + if (__fc_first || __fc_delta < __fc_best_delta) { \ + __fc_best_delta = __fc_delta; \ + __fc_best_idx = __fc_i; \ + } \ + __fc_first = false; \ + } \ + (__fc_best_idx); \ +}) + #endif -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 01/10] utils_macro: introduce find_closest_unsorted() 2021-10-28 10:18 ` [v2 01/10] utils_macro: introduce find_closest_unsorted() Andrea Merello @ 2021-10-28 10:25 ` Andy Shevchenko 2021-11-08 11:05 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Andy Shevchenko @ 2021-10-28 10:25 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Mauro Carvalho Chehab, linux-iio, Linux Kernel Mailing List, devicetree, Lars-Peter Clausen, Rob Herring, Matt Ranostay, Alexandru Ardelean, jmondi, Andrea Merello On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <andrea.merello@gmail.com> wrote: > > This is similar to find_closest() and find_closest_descending(), but, it > doesn't make any assumption about the array being ordered. Macros in general are not so welcoming. Why do you do it as a macro? ... > +#include <linux/math.h> Wondering if the current header misses other inclusions it's a direct user of. ... > +/** > + * find_closest_unsorted - locate the closest element in a unsorted array an > + * @x: The reference value. > + * @a: The array in which to look for the closest element. > + * @as: Size of 'a'. > + * > + * Similar to find_closest() but 'a' has no requirement to being sorted > + */ -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 01/10] utils_macro: introduce find_closest_unsorted() 2021-10-28 10:25 ` Andy Shevchenko @ 2021-11-08 11:05 ` Andrea Merello 0 siblings, 0 replies; 89+ messages in thread From: Andrea Merello @ 2021-11-08 11:05 UTC (permalink / raw) To: Andy Shevchenko Cc: Jonathan Cameron, Mauro Carvalho Chehab, linux-iio, Linux Kernel Mailing List, devicetree, Lars-Peter Clausen, Rob Herring, Matt Ranostay, Alexandru Ardelean, jmondi, Andrea Merello Il giorno gio 28 ott 2021 alle ore 12:26 Andy Shevchenko <andy.shevchenko@gmail.com> ha scritto: > > On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <andrea.merello@gmail.com> wrote: > > > > This is similar to find_closest() and find_closest_descending(), but, it > > doesn't make any assumption about the array being ordered. > > Macros in general are not so welcoming. > Why do you do it as a macro? Honestly, I did that just because find_closest() and find_closest_descending() are macros (i.e. to be consistent wrt them). I see no drawbacks in making this a regular function indeed; just, do you have any advice about where should it live? > ... > > > +#include <linux/math.h> > > Wondering if the current header misses other inclusions it's a direct user of. Looking at it, it seems that also __find_closest() actually needs math.h because it (apparently incorrectly[*]) uses DIV_ROUND_CLOSEST().. [*]Indeed it seems there is another issue here about find_closest(): for example it picks the 1st element while searching for "2" in an array like this: {1,2,..} ..This needs to be reported/fixed.. > ... > > > +/** > > + * find_closest_unsorted - locate the closest element in a unsorted array > > an > > > + * @x: The reference value. > > + * @a: The array in which to look for the closest element. > > + * @as: Size of 'a'. > > + * > > + * Similar to find_closest() but 'a' has no requirement to being sorted > > + */ > > -- > With Best Regards, > Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 02/10] iio: document linear acceleration modifiers 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello 2021-10-28 10:18 ` [v2 01/10] utils_macro: introduce find_closest_unsorted() Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 10:31 ` Andy Shevchenko 2021-10-28 10:40 ` Jonathan Cameron 2021-10-28 10:18 ` [v2 03/10] iio: document euler angles modifiers Andrea Merello ` (8 subsequent siblings) 10 siblings, 2 replies; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello, Andrea Merello This patch introduces ABI documentation for new iio modifiers used for reporting "linear acceleration" measures. Signed-off-by: Andrea Merello <andrea.merello@iit.it> --- Documentation/ABI/testing/sysfs-bus-iio | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 6ad47a67521c..5147a00bf24a 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1957,3 +1957,11 @@ Description: Specify the percent for light sensor relative to the channel absolute value that a data field should change before an event is generated. Units are a percentage of the prior reading. + +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_x_raw +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_y_raw +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_z_raw +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled) linear acceleration readings. -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 02/10] iio: document linear acceleration modifiers 2021-10-28 10:18 ` [v2 02/10] iio: document linear acceleration modifiers Andrea Merello @ 2021-10-28 10:31 ` Andy Shevchenko 2021-11-09 7:48 ` Andrea Merello 2021-10-28 10:40 ` Jonathan Cameron 1 sibling, 1 reply; 89+ messages in thread From: Andy Shevchenko @ 2021-10-28 10:31 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Mauro Carvalho Chehab, linux-iio, Linux Kernel Mailing List, devicetree, Lars-Peter Clausen, Rob Herring, Matt Ranostay, Alexandru Ardelean, jmondi, Andrea Merello On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <andrea.merello@gmail.com> wrote: > > This patch introduces ABI documentation for new iio modifiers used for > reporting "linear acceleration" measures. Because of ordering and absence of Fixes tag I haven't clearly got if this is an existing set of attributes or that that will be added by the series. If the former, use a Fixes tag and place it first in the series. If the latter, move it after the actual addition of the attributes in the code. -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 02/10] iio: document linear acceleration modifiers 2021-10-28 10:31 ` Andy Shevchenko @ 2021-11-09 7:48 ` Andrea Merello 0 siblings, 0 replies; 89+ messages in thread From: Andrea Merello @ 2021-11-09 7:48 UTC (permalink / raw) To: Andy Shevchenko Cc: Jonathan Cameron, Mauro Carvalho Chehab, linux-iio, Linux Kernel Mailing List, devicetree, Lars-Peter Clausen, Rob Herring, Matt Ranostay, Alexandru Ardelean, jmondi, Andrea Merello Il giorno gio 28 ott 2021 alle ore 12:32 Andy Shevchenko <andy.shevchenko@gmail.com> ha scritto: > > On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <andrea.merello@gmail.com> wrote: > > > > This patch introduces ABI documentation for new iio modifiers used for > > reporting "linear acceleration" measures. > > Because of ordering and absence of Fixes tag I haven't clearly got if > this is an existing set of attributes or that that will be added by > the series. If the former, use a Fixes tag and place it first in the > series. If the latter, move it after the actual addition of the > attributes in the code. The latter. Will move in V3. Thanks > -- > With Best Regards, > Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 02/10] iio: document linear acceleration modifiers 2021-10-28 10:18 ` [v2 02/10] iio: document linear acceleration modifiers Andrea Merello 2021-10-28 10:31 ` Andy Shevchenko @ 2021-10-28 10:40 ` Jonathan Cameron 2021-11-09 8:00 ` Andrea Merello 1 sibling, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-10-28 10:40 UTC (permalink / raw) To: Andrea Merello Cc: mchehab+huawei, linux-iio, linux-kernel, devicetree, lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello On Thu, 28 Oct 2021 12:18:32 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > This patch introduces ABI documentation for new iio modifiers used for > reporting "linear acceleration" measures. > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > --- > Documentation/ABI/testing/sysfs-bus-iio | 8 ++++++++ > 1 file changed, 8 insertions(+) > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio > index 6ad47a67521c..5147a00bf24a 100644 > --- a/Documentation/ABI/testing/sysfs-bus-iio > +++ b/Documentation/ABI/testing/sysfs-bus-iio > @@ -1957,3 +1957,11 @@ Description: > Specify the percent for light sensor relative to the channel > absolute value that a data field should change before an event > is generated. Units are a percentage of the prior reading. > + > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_x_raw > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_y_raw > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_z_raw > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Raw (unscaled) linear acceleration readings. Probably need more information that this. What element is being 'removed' from a normal acceleration measurement? What are units after application of offset and scale? Can cross refer to the in_accel_x_raw for that info if you like. Also, but them immediately after the block with the in_accel_x_raw etc The organization fo that file needs a rethink but let us try to avoid making it worse in the meeantime! Jonathan ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 02/10] iio: document linear acceleration modifiers 2021-10-28 10:40 ` Jonathan Cameron @ 2021-11-09 8:00 ` Andrea Merello 2021-11-09 17:00 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-11-09 8:00 UTC (permalink / raw) To: Jonathan Cameron Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello Il giorno gio 28 ott 2021 alle ore 12:35 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Thu, 28 Oct 2021 12:18:32 +0200 > Andrea Merello <andrea.merello@gmail.com> wrote: > > > This patch introduces ABI documentation for new iio modifiers used for > > reporting "linear acceleration" measures. > > > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > > --- > > Documentation/ABI/testing/sysfs-bus-iio | 8 ++++++++ > > 1 file changed, 8 insertions(+) > > > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio > > index 6ad47a67521c..5147a00bf24a 100644 > > --- a/Documentation/ABI/testing/sysfs-bus-iio > > +++ b/Documentation/ABI/testing/sysfs-bus-iio > > @@ -1957,3 +1957,11 @@ Description: > > Specify the percent for light sensor relative to the channel > > absolute value that a data field should change before an event > > is generated. Units are a percentage of the prior reading. > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_x_raw > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_y_raw > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_z_raw > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Raw (unscaled) linear acceleration readings. > > Probably need more information that this. What element is being 'removed' from > a normal acceleration measurement? What are units after application of offset and > scale? Can cross refer to the in_accel_x_raw for that info if you like. OK. So, may I just state something like "As per in_accel_X_raw attributes, but minus the gravity acceleration" ? > Also, but them immediately after the block with the in_accel_x_raw etc OK > The organization fo that file needs a rethink but let us try to avoid making > it worse in the meeantime! > > Jonathan > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 02/10] iio: document linear acceleration modifiers 2021-11-09 8:00 ` Andrea Merello @ 2021-11-09 17:00 ` Jonathan Cameron 0 siblings, 0 replies; 89+ messages in thread From: Jonathan Cameron @ 2021-11-09 17:00 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello On Tue, 9 Nov 2021 09:00:09 +0100 Andrea Merello <andrea.merello@gmail.com> wrote: > Il giorno gio 28 ott 2021 alle ore 12:35 Jonathan Cameron > <jic23@kernel.org> ha scritto: > > > > On Thu, 28 Oct 2021 12:18:32 +0200 > > Andrea Merello <andrea.merello@gmail.com> wrote: > > > > > This patch introduces ABI documentation for new iio modifiers used for > > > reporting "linear acceleration" measures. > > > > > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > > > --- > > > Documentation/ABI/testing/sysfs-bus-iio | 8 ++++++++ > > > 1 file changed, 8 insertions(+) > > > > > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio > > > index 6ad47a67521c..5147a00bf24a 100644 > > > --- a/Documentation/ABI/testing/sysfs-bus-iio > > > +++ b/Documentation/ABI/testing/sysfs-bus-iio > > > @@ -1957,3 +1957,11 @@ Description: > > > Specify the percent for light sensor relative to the channel > > > absolute value that a data field should change before an event > > > is generated. Units are a percentage of the prior reading. > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_x_raw > > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_y_raw > > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_linear_z_raw > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Raw (unscaled) linear acceleration readings. > > > > Probably need more information that this. What element is being 'removed' from > > a normal acceleration measurement? What are units after application of offset and > > scale? Can cross refer to the in_accel_x_raw for that info if you like. > > OK. So, may I just state something like "As per in_accel_X_raw > attributes, but minus the gravity acceleration" ? Yup, something along those lines. Wow, I had a lot of typos in my email. :) Jonathan > > > Also, but them immediately after the block with the in_accel_x_raw etc > > OK > > > The organization fo that file needs a rethink but let us try to avoid making > > it worse in the meeantime! > > > > Jonathan > > > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 03/10] iio: document euler angles modifiers 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello 2021-10-28 10:18 ` [v2 01/10] utils_macro: introduce find_closest_unsorted() Andrea Merello 2021-10-28 10:18 ` [v2 02/10] iio: document linear acceleration modifiers Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 10:33 ` Andy Shevchenko 2021-10-28 10:41 ` Jonathan Cameron 2021-10-28 10:18 ` [v2 04/10] iio: add modifiers for linear acceleration Andrea Merello ` (7 subsequent siblings) 10 siblings, 2 replies; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello, Andrea Merello This patch introduces ABI documentation for new modifiers used for reporting rotations expressed as euler angles (i.e. yaw, pitch, roll). Signed-off-by: Andrea Merello <andrea.merello@iit.it> --- Documentation/ABI/testing/sysfs-bus-iio | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 5147a00bf24a..f0adc2c817bd 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1965,3 +1965,11 @@ KernelVersion: 5.15 Contact: linux-iio@vger.kernel.org Description: Raw (unscaled) linear acceleration readings. + +What: /sys/bus/iio/devices/iio:deviceX/in_rot_yaw_raw +What: /sys/bus/iio/devices/iio:deviceX/in_rot_pitch_raw +What: /sys/bus/iio/devices/iio:deviceX/in_rot_roll_raw +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled) euler angles readings. -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 03/10] iio: document euler angles modifiers 2021-10-28 10:18 ` [v2 03/10] iio: document euler angles modifiers Andrea Merello @ 2021-10-28 10:33 ` Andy Shevchenko 2021-10-28 10:41 ` Jonathan Cameron 1 sibling, 0 replies; 89+ messages in thread From: Andy Shevchenko @ 2021-10-28 10:33 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Mauro Carvalho Chehab, linux-iio, Linux Kernel Mailing List, devicetree, Lars-Peter Clausen, Rob Herring, Matt Ranostay, Alexandru Ardelean, jmondi, Andrea Merello On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <andrea.merello@gmail.com> wrote: > > This patch introduces ABI documentation for new modifiers used for > reporting rotations expressed as euler angles (i.e. yaw, pitch, roll). As per previous patch. -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 03/10] iio: document euler angles modifiers 2021-10-28 10:18 ` [v2 03/10] iio: document euler angles modifiers Andrea Merello 2021-10-28 10:33 ` Andy Shevchenko @ 2021-10-28 10:41 ` Jonathan Cameron 2021-11-09 8:15 ` Andrea Merello 1 sibling, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-10-28 10:41 UTC (permalink / raw) To: Andrea Merello Cc: mchehab+huawei, linux-iio, linux-kernel, devicetree, lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello On Thu, 28 Oct 2021 12:18:33 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > This patch introduces ABI documentation for new modifiers used for > reporting rotations expressed as euler angles (i.e. yaw, pitch, roll). > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > --- > Documentation/ABI/testing/sysfs-bus-iio | 8 ++++++++ > 1 file changed, 8 insertions(+) > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio > index 5147a00bf24a..f0adc2c817bd 100644 > --- a/Documentation/ABI/testing/sysfs-bus-iio > +++ b/Documentation/ABI/testing/sysfs-bus-iio > @@ -1965,3 +1965,11 @@ KernelVersion: 5.15 > Contact: linux-iio@vger.kernel.org > Description: > Raw (unscaled) linear acceleration readings. > + > +What: /sys/bus/iio/devices/iio:deviceX/in_rot_yaw_raw > +What: /sys/bus/iio/devices/iio:deviceX/in_rot_pitch_raw > +What: /sys/bus/iio/devices/iio:deviceX/in_rot_roll_raw > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Raw (unscaled) euler angles readings. Any _raw entry should also include what the units are after application of offset and scale. Or you could just add this as more info to the in_rot_raw block as an extra sentence explaining that they are euler angles. That will lose the 'KernelVersion' information but honestly I'm not sure we care that much about that. Jonathan ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 03/10] iio: document euler angles modifiers 2021-10-28 10:41 ` Jonathan Cameron @ 2021-11-09 8:15 ` Andrea Merello 2021-11-09 17:03 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-11-09 8:15 UTC (permalink / raw) To: Jonathan Cameron Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello Il giorno gio 28 ott 2021 alle ore 12:37 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Thu, 28 Oct 2021 12:18:33 +0200 > Andrea Merello <andrea.merello@gmail.com> wrote: > > > This patch introduces ABI documentation for new modifiers used for > > reporting rotations expressed as euler angles (i.e. yaw, pitch, roll). > > > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > > --- > > Documentation/ABI/testing/sysfs-bus-iio | 8 ++++++++ > > 1 file changed, 8 insertions(+) > > > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio > > index 5147a00bf24a..f0adc2c817bd 100644 > > --- a/Documentation/ABI/testing/sysfs-bus-iio > > +++ b/Documentation/ABI/testing/sysfs-bus-iio > > @@ -1965,3 +1965,11 @@ KernelVersion: 5.15 > > Contact: linux-iio@vger.kernel.org > > Description: > > Raw (unscaled) linear acceleration readings. > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_rot_yaw_raw > > +What: /sys/bus/iio/devices/iio:deviceX/in_rot_pitch_raw > > +What: /sys/bus/iio/devices/iio:deviceX/in_rot_roll_raw > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Raw (unscaled) euler angles readings. > Any _raw entry should also include what the units are after application of > offset and scale. Or you could just add this as more info to the in_rot_raw > block as an extra sentence explaining that they are euler angles. > That will lose the 'KernelVersion' information but honestly I'm not sure we > care that much about that. I'm unsure which block you are talking about: I see there are two blocks that refer to rot things: in_rot_quaternion_raw and in_rot_from_north_xxx_raw. Looking at the 1st one description, it looks very specific to quaternions to me; the 2nd seems very specific to its own thing, whatever it is.. So I would just add the missing information (unit) in the new block just being introduced, if this is ok for you. Or am I missing some other block in which I could coalesce this new euler thing? > Jonathan > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 03/10] iio: document euler angles modifiers 2021-11-09 8:15 ` Andrea Merello @ 2021-11-09 17:03 ` Jonathan Cameron 0 siblings, 0 replies; 89+ messages in thread From: Jonathan Cameron @ 2021-11-09 17:03 UTC (permalink / raw) To: Andrea Merello Cc: Jonathan Cameron, Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello On Tue, 9 Nov 2021 09:15:09 +0100 Andrea Merello <andrea.merello@gmail.com> wrote: > Il giorno gio 28 ott 2021 alle ore 12:37 Jonathan Cameron > <jic23@kernel.org> ha scritto: > > > > On Thu, 28 Oct 2021 12:18:33 +0200 > > Andrea Merello <andrea.merello@gmail.com> wrote: > > > > > This patch introduces ABI documentation for new modifiers used for > > > reporting rotations expressed as euler angles (i.e. yaw, pitch, roll). > > > > > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > > > --- > > > Documentation/ABI/testing/sysfs-bus-iio | 8 ++++++++ > > > 1 file changed, 8 insertions(+) > > > > > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio > > > index 5147a00bf24a..f0adc2c817bd 100644 > > > --- a/Documentation/ABI/testing/sysfs-bus-iio > > > +++ b/Documentation/ABI/testing/sysfs-bus-iio > > > @@ -1965,3 +1965,11 @@ KernelVersion: 5.15 > > > Contact: linux-iio@vger.kernel.org > > > Description: > > > Raw (unscaled) linear acceleration readings. > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_rot_yaw_raw > > > +What: /sys/bus/iio/devices/iio:deviceX/in_rot_pitch_raw > > > +What: /sys/bus/iio/devices/iio:deviceX/in_rot_roll_raw > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Raw (unscaled) euler angles readings. > > Any _raw entry should also include what the units are after application of > > offset and scale. Or you could just add this as more info to the in_rot_raw > > block as an extra sentence explaining that they are euler angles. > > That will lose the 'KernelVersion' information but honestly I'm not sure we > > care that much about that. > > I'm unsure which block you are talking about: I see there are two > blocks that refer to rot things: in_rot_quaternion_raw and > in_rot_from_north_xxx_raw. > > Looking at the 1st one description, it looks very specific to > quaternions to me; the 2nd seems very specific to its own thing, > whatever it is.. So I would just add the missing information (unit) in > the new block just being introduced, if this is ok for you. Or am I > missing some other block in which I could coalesce this new euler > thing? Good point, not sure what I was thinking. There isn't a sensible block to add this to. So just add the info about units. Jonathan > > > > Jonathan > > > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 04/10] iio: add modifiers for linear acceleration 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello ` (2 preceding siblings ...) 2021-10-28 10:18 ` [v2 03/10] iio: document euler angles modifiers Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 10:45 ` Jonathan Cameron 2021-10-28 10:18 ` [v2 05/10] iio: add modifers for pitch, yaw, roll Andrea Merello ` (6 subsequent siblings) 10 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello, Andrea Merello This patch is preparatory for adding the Bosh BNO055 IMU driver. The said IMU can report raw accelerations (among x, y and z axis) as well as the so called "linear accelerations" (again, among x, y and z axis) which is basically the acceleration after subtracting gravity. This patch adds IIO_MOD_ACCEL_LINEAR_X, IIO_MOD_ACCEL_LINEAR_Y and IIO_MOD_ACCEL_LINEAR_Z modifiers to te IIO core. Signed-off-by: Andrea Merello <andrea.merello@iit.it> --- drivers/iio/industrialio-core.c | 3 +++ include/uapi/linux/iio/types.h | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 2dbb37e09b8c..a79cb32207e4 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -134,6 +134,9 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_ETHANOL] = "ethanol", [IIO_MOD_H2] = "h2", [IIO_MOD_O2] = "o2", + [IIO_MOD_ACCEL_LINEAR_X] = "linear_x", + [IIO_MOD_ACCEL_LINEAR_Y] = "linear_y", + [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z" }; /* relies on pairs of these shared then separate */ diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index 48c13147c0a8..db00f7c45f48 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -95,6 +95,9 @@ enum iio_modifier { IIO_MOD_ETHANOL, IIO_MOD_H2, IIO_MOD_O2, + IIO_MOD_ACCEL_LINEAR_X, + IIO_MOD_ACCEL_LINEAR_Y, + IIO_MOD_ACCEL_LINEAR_Z, }; enum iio_event_type { @@ -114,4 +117,3 @@ enum iio_event_direction { }; #endif /* _UAPI_IIO_TYPES_H_ */ - -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 04/10] iio: add modifiers for linear acceleration 2021-10-28 10:18 ` [v2 04/10] iio: add modifiers for linear acceleration Andrea Merello @ 2021-10-28 10:45 ` Jonathan Cameron 2021-11-09 9:58 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-10-28 10:45 UTC (permalink / raw) To: Andrea Merello Cc: mchehab+huawei, linux-iio, linux-kernel, devicetree, lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello On Thu, 28 Oct 2021 12:18:34 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > This patch is preparatory for adding the Bosh BNO055 IMU driver. > The said IMU can report raw accelerations (among x, y and z axis) > as well as the so called "linear accelerations" (again, among x, > y and z axis) which is basically the acceleration after subtracting > gravity. > > This patch adds IIO_MOD_ACCEL_LINEAR_X, IIO_MOD_ACCEL_LINEAR_Y and > IIO_MOD_ACCEL_LINEAR_Z modifiers to te IIO core. > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> They sometimes get forgotten but we should also update tools/iio/iio_event_montitor.c to handle these new modifiers. That can be a separate patch, but also fine to do it in this one. > --- > drivers/iio/industrialio-core.c | 3 +++ > include/uapi/linux/iio/types.h | 4 +++- > 2 files changed, 6 insertions(+), 1 deletion(-) > > diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c > index 2dbb37e09b8c..a79cb32207e4 100644 > --- a/drivers/iio/industrialio-core.c > +++ b/drivers/iio/industrialio-core.c > @@ -134,6 +134,9 @@ static const char * const iio_modifier_names[] = { > [IIO_MOD_ETHANOL] = "ethanol", > [IIO_MOD_H2] = "h2", > [IIO_MOD_O2] = "o2", > + [IIO_MOD_ACCEL_LINEAR_X] = "linear_x", > + [IIO_MOD_ACCEL_LINEAR_Y] = "linear_y", > + [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z" > }; > > /* relies on pairs of these shared then separate */ > diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h > index 48c13147c0a8..db00f7c45f48 100644 > --- a/include/uapi/linux/iio/types.h > +++ b/include/uapi/linux/iio/types.h > @@ -95,6 +95,9 @@ enum iio_modifier { > IIO_MOD_ETHANOL, > IIO_MOD_H2, > IIO_MOD_O2, > + IIO_MOD_ACCEL_LINEAR_X, > + IIO_MOD_ACCEL_LINEAR_Y, > + IIO_MOD_ACCEL_LINEAR_Z, It might be useful for other channel types, so probably drop the ACCEL part of the name. I'll admit I can't immediately think of what, but you never know.. :) > }; > > enum iio_event_type { > @@ -114,4 +117,3 @@ enum iio_event_direction { > }; > > #endif /* _UAPI_IIO_TYPES_H_ */ > - ? ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 04/10] iio: add modifiers for linear acceleration 2021-10-28 10:45 ` Jonathan Cameron @ 2021-11-09 9:58 ` Andrea Merello 2021-11-09 17:05 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-11-09 9:58 UTC (permalink / raw) To: Jonathan Cameron Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello Il giorno gio 28 ott 2021 alle ore 12:41 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Thu, 28 Oct 2021 12:18:34 +0200 > Andrea Merello <andrea.merello@gmail.com> wrote: > > > This patch is preparatory for adding the Bosh BNO055 IMU driver. > > The said IMU can report raw accelerations (among x, y and z axis) > > as well as the so called "linear accelerations" (again, among x, > > y and z axis) which is basically the acceleration after subtracting > > gravity. > > > > This patch adds IIO_MOD_ACCEL_LINEAR_X, IIO_MOD_ACCEL_LINEAR_Y and > > IIO_MOD_ACCEL_LINEAR_Z modifiers to te IIO core. > > > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > > They sometimes get forgotten but we should also update > tools/iio/iio_event_montitor.c to handle these new modifiers. I'm not so familiar with this tool, but it seems like it has to do with IIO events, which the bno055 driver doesn't use. On the other hand the modifiers I would add are not used by any other driver right now. So I would say that it would end up in adding things that I couldn't test.. Or is there any test infrastructure for this? It seems trivial, just a matter of a few defines, so it shouldn't be an issue indeed.. > That can be a separate patch, but also fine to do it in this one. > > > --- > > drivers/iio/industrialio-core.c | 3 +++ > > include/uapi/linux/iio/types.h | 4 +++- > > 2 files changed, 6 insertions(+), 1 deletion(-) > > > > diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c > > index 2dbb37e09b8c..a79cb32207e4 100644 > > --- a/drivers/iio/industrialio-core.c > > +++ b/drivers/iio/industrialio-core.c > > @@ -134,6 +134,9 @@ static const char * const iio_modifier_names[] = { > > [IIO_MOD_ETHANOL] = "ethanol", > > [IIO_MOD_H2] = "h2", > > [IIO_MOD_O2] = "o2", > > + [IIO_MOD_ACCEL_LINEAR_X] = "linear_x", > > + [IIO_MOD_ACCEL_LINEAR_Y] = "linear_y", > > + [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z" > > }; > > > > /* relies on pairs of these shared then separate */ > > diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h > > index 48c13147c0a8..db00f7c45f48 100644 > > --- a/include/uapi/linux/iio/types.h > > +++ b/include/uapi/linux/iio/types.h > > @@ -95,6 +95,9 @@ enum iio_modifier { > > IIO_MOD_ETHANOL, > > IIO_MOD_H2, > > IIO_MOD_O2, > > + IIO_MOD_ACCEL_LINEAR_X, > > + IIO_MOD_ACCEL_LINEAR_Y, > > + IIO_MOD_ACCEL_LINEAR_Z, > > It might be useful for other channel types, so probably drop the ACCEL > part of the name. > > I'll admit I can't immediately think of what, but you never know.. :) But in this case what should I write in the ABI documentation? If I state that this is something that makes the gravity not being included then isn't it intrinsically tied to be an acceleration? Or, I do that, and if someone eventually finds another use, then she/he will change the ABI doc? > > }; > > > > enum iio_event_type { > > @@ -114,4 +117,3 @@ enum iio_event_direction { > > }; > > > > #endif /* _UAPI_IIO_TYPES_H_ */ > > - > ? > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 04/10] iio: add modifiers for linear acceleration 2021-11-09 9:58 ` Andrea Merello @ 2021-11-09 17:05 ` Jonathan Cameron 0 siblings, 0 replies; 89+ messages in thread From: Jonathan Cameron @ 2021-11-09 17:05 UTC (permalink / raw) To: Andrea Merello, linux-kernel, devicetree Cc: Jonathan Cameron, Mauro Carvalho Chehab, linux-iio, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello On Tue, 9 Nov 2021 10:58:19 +0100 Andrea Merello <andrea.merello@gmail.com> wrote: > Il giorno gio 28 ott 2021 alle ore 12:41 Jonathan Cameron > <jic23@kernel.org> ha scritto: > > > > On Thu, 28 Oct 2021 12:18:34 +0200 > > Andrea Merello <andrea.merello@gmail.com> wrote: > > > > > This patch is preparatory for adding the Bosh BNO055 IMU driver. > > > The said IMU can report raw accelerations (among x, y and z axis) > > > as well as the so called "linear accelerations" (again, among x, > > > y and z axis) which is basically the acceleration after subtracting > > > gravity. > > > > > > This patch adds IIO_MOD_ACCEL_LINEAR_X, IIO_MOD_ACCEL_LINEAR_Y and > > > IIO_MOD_ACCEL_LINEAR_Z modifiers to te IIO core. > > > > > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > > > > They sometimes get forgotten but we should also update > > tools/iio/iio_event_montitor.c to handle these new modifiers. > > I'm not so familiar with this tool, but it seems like it has to do > with IIO events, which the bno055 driver doesn't use. On the other > hand the modifiers I would add are not used by any other driver right > now. > > So I would say that it would end up in adding things that I couldn't > test.. Or is there any test infrastructure for this? It seems trivial, > just a matter of a few defines, so it shouldn't be an issue indeed.. > > > That can be a separate patch, but also fine to do it in this one. > > > > > --- > > > drivers/iio/industrialio-core.c | 3 +++ > > > include/uapi/linux/iio/types.h | 4 +++- > > > 2 files changed, 6 insertions(+), 1 deletion(-) > > > > > > diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c > > > index 2dbb37e09b8c..a79cb32207e4 100644 > > > --- a/drivers/iio/industrialio-core.c > > > +++ b/drivers/iio/industrialio-core.c > > > @@ -134,6 +134,9 @@ static const char * const iio_modifier_names[] = { > > > [IIO_MOD_ETHANOL] = "ethanol", > > > [IIO_MOD_H2] = "h2", > > > [IIO_MOD_O2] = "o2", > > > + [IIO_MOD_ACCEL_LINEAR_X] = "linear_x", > > > + [IIO_MOD_ACCEL_LINEAR_Y] = "linear_y", > > > + [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z" > > > }; > > > > > > /* relies on pairs of these shared then separate */ > > > diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h > > > index 48c13147c0a8..db00f7c45f48 100644 > > > --- a/include/uapi/linux/iio/types.h > > > +++ b/include/uapi/linux/iio/types.h > > > @@ -95,6 +95,9 @@ enum iio_modifier { > > > IIO_MOD_ETHANOL, > > > IIO_MOD_H2, > > > IIO_MOD_O2, > > > + IIO_MOD_ACCEL_LINEAR_X, > > > + IIO_MOD_ACCEL_LINEAR_Y, > > > + IIO_MOD_ACCEL_LINEAR_Z, > > > > It might be useful for other channel types, so probably drop the ACCEL > > part of the name. > > > > I'll admit I can't immediately think of what, but you never know.. :) > > But in this case what should I write in the ABI documentation? If I > state that this is something that makes the gravity not being included > then isn't it intrinsically tied to be an acceleration? Or, I do > that, and if someone eventually finds another use, then she/he will > change the ABI doc? The ABI docs are only documenting the complete ABI, not separately the modifier so you will be documenting the same thing whatever we call the modifier inside the code. I'm just suggesting you call the enum entries the more generic IIO_MOD_LINEAR_X, etc, not a change to the resulting string. Jonathan > > > > }; > > > > > > enum iio_event_type { > > > @@ -114,4 +117,3 @@ enum iio_event_direction { > > > }; > > > > > > #endif /* _UAPI_IIO_TYPES_H_ */ > > > - > > ? > > > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 05/10] iio: add modifers for pitch, yaw, roll 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello ` (3 preceding siblings ...) 2021-10-28 10:18 ` [v2 04/10] iio: add modifiers for linear acceleration Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 10:47 ` Jonathan Cameron 2021-10-28 10:18 ` [v2 06/10] iio: document bno055 private sysfs attributes Andrea Merello ` (5 subsequent siblings) 10 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello, Andrea Merello This patch adds modifiers for reporting rotations as euler angles (i.e. yaw, pitch and roll). Signed-off-by: Andrea Merello <andrea.merello@iit.it> --- drivers/iio/industrialio-core.c | 5 ++++- include/uapi/linux/iio/types.h | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index a79cb32207e4..d2ebbfa8b9fc 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -136,7 +136,10 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_O2] = "o2", [IIO_MOD_ACCEL_LINEAR_X] = "linear_x", [IIO_MOD_ACCEL_LINEAR_Y] = "linear_y", - [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z" + [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z", + [IIO_MOD_PITCH] = "pitch", + [IIO_MOD_YAW] = "yaw", + [IIO_MOD_ROLL] = "roll" }; /* relies on pairs of these shared then separate */ diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index db00f7c45f48..fc9909ca4f95 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -98,6 +98,9 @@ enum iio_modifier { IIO_MOD_ACCEL_LINEAR_X, IIO_MOD_ACCEL_LINEAR_Y, IIO_MOD_ACCEL_LINEAR_Z, + IIO_MOD_PITCH, + IIO_MOD_YAW, + IIO_MOD_ROLL }; enum iio_event_type { -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 05/10] iio: add modifers for pitch, yaw, roll 2021-10-28 10:18 ` [v2 05/10] iio: add modifers for pitch, yaw, roll Andrea Merello @ 2021-10-28 10:47 ` Jonathan Cameron 0 siblings, 0 replies; 89+ messages in thread From: Jonathan Cameron @ 2021-10-28 10:47 UTC (permalink / raw) To: Andrea Merello Cc: mchehab+huawei, linux-iio, linux-kernel, devicetree, lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello On Thu, 28 Oct 2021 12:18:35 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > This patch adds modifiers for reporting rotations as euler angles (i.e. > yaw, pitch and roll). > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> Same comment on tools update, and a few editorial things inline. Jonathan > --- > drivers/iio/industrialio-core.c | 5 ++++- > include/uapi/linux/iio/types.h | 3 +++ > 2 files changed, 7 insertions(+), 1 deletion(-) > > diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c > index a79cb32207e4..d2ebbfa8b9fc 100644 > --- a/drivers/iio/industrialio-core.c > +++ b/drivers/iio/industrialio-core.c > @@ -136,7 +136,10 @@ static const char * const iio_modifier_names[] = { > [IIO_MOD_O2] = "o2", > [IIO_MOD_ACCEL_LINEAR_X] = "linear_x", > [IIO_MOD_ACCEL_LINEAR_Y] = "linear_y", > - [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z" > + [IIO_MOD_ACCEL_LINEAR_Z] = "linear_z", Move the comman to the previous patch. > + [IIO_MOD_PITCH] = "pitch", > + [IIO_MOD_YAW] = "yaw", > + [IIO_MOD_ROLL] = "roll" > }; > > /* relies on pairs of these shared then separate */ > diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h > index db00f7c45f48..fc9909ca4f95 100644 > --- a/include/uapi/linux/iio/types.h > +++ b/include/uapi/linux/iio/types.h > @@ -98,6 +98,9 @@ enum iio_modifier { > IIO_MOD_ACCEL_LINEAR_X, > IIO_MOD_ACCEL_LINEAR_Y, > IIO_MOD_ACCEL_LINEAR_Z, > + IIO_MOD_PITCH, > + IIO_MOD_YAW, > + IIO_MOD_ROLL And add a comma here to make extending this in future easy. > }; > > enum iio_event_type { ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 06/10] iio: document bno055 private sysfs attributes 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello ` (4 preceding siblings ...) 2021-10-28 10:18 ` [v2 05/10] iio: add modifers for pitch, yaw, roll Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 11:04 ` Jonathan Cameron 2021-10-28 10:18 ` [v2 07/10] iio: imu: add Bosch Sensortec BNO055 core driver Andrea Merello ` (4 subsequent siblings) 10 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello, Andrea Merello This patch adds ABI documentation for bno055 driver private sysfs attributes. Signed-off-by: Andrea Merello <andrea.merello@iit.it> --- .../ABI/testing/sysfs-bus-iio-bno055 | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-bno055 diff --git a/Documentation/ABI/testing/sysfs-bus-iio-bno055 b/Documentation/ABI/testing/sysfs-bus-iio-bno055 new file mode 100644 index 000000000000..930a70c5a858 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-bno055 @@ -0,0 +1,84 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_accel_range +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Range for acceleration readings in G. Note that this does not + affects the scale (which should be used when changing the + maximum and minimum readable value affects also the reading + scaling factor). + +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_range +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Range for angular velocity readings in dps. Note that this does + not affects the scale (which should be used when changing the + maximum and minimum readable value affects also the reading + scaling factor). + +What: /sys/bus/iio/devices/iio:deviceX/in_accel_range_available +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + List of allowed values for in_accel_range attribute + +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_range_available +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + List of allowed values for in_anglvel_range attribute + +What: /sys/bus/iio/devices/iio:deviceX/fast_magnetometer_calibration_enable +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Can be 1 or 0. Enables/disables the "Fast Magnetometer + Calibration" HW function. + +What: /sys/bus/iio/devices/iio:deviceX/fusion_enable +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Can be 1 or 0. Enables/disables the "sensor fusion" (a.k.a. + NDOF) HW function. + +What: /sys/bus/iio/devices/iio:deviceX/in_calibration_data +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Reports the binary calibration data blob for the IMU sensors. + +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_accel +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". + Report the autocalibration status for the accelerometer sensor. + +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_gyro +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". + Reports the autocalibration status for the gyroscope sensor. + +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_magn +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". + Reports the autocalibration status for the magnetometer sensor. + +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_sys +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". + Reports the status for the IMU overall autocalibration. + +What: /sys/bus/iio/devices/iio:deviceX/unique_id +KernelVersion: 5.15 +Contact: linux-iio@vger.kernel.org +Description: + 16-bytes, 2-digits-per-byte, HEX-string representing the sensor + unique ID number. -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 06/10] iio: document bno055 private sysfs attributes 2021-10-28 10:18 ` [v2 06/10] iio: document bno055 private sysfs attributes Andrea Merello @ 2021-10-28 11:04 ` Jonathan Cameron 2021-11-09 10:22 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-10-28 11:04 UTC (permalink / raw) To: Andrea Merello Cc: mchehab+huawei, linux-iio, linux-kernel, devicetree, lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello On Thu, 28 Oct 2021 12:18:36 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > This patch adds ABI documentation for bno055 driver private sysfs > attributes. Hohum. As normal I dislike custom attributes but reality is these don't map to anything 'standard' and I don't want them getting adopted in places where the 'standard' approach works. So thinking a bit more on this, I wonder if we can fit it into standard ABI. We can't use the normal range specification method of _scale because it's internal to the device and the output reading is unaffected. The range specification via _raw_available would let us know the range, but it is not writeable so.. A control that changes the internal scaling of the sensor in a fashion that is not visible to the outside world maps to calibscale. Whilst that was intended for little tweaks to the input signal (often front end amplifier gain tweak) it works here. It doesn't map through to anything userspace is expected to apply. That combined with _raw_available to let us know what the result is should work? What do you think of that approach? It's obviously a little more complex to handle in the driver, but it does map to existing ABI and avoids custom attributes etc. > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > --- > .../ABI/testing/sysfs-bus-iio-bno055 | 84 +++++++++++++++++++ > 1 file changed, 84 insertions(+) > create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-bno055 > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-bno055 b/Documentation/ABI/testing/sysfs-bus-iio-bno055 > new file mode 100644 > index 000000000000..930a70c5a858 > --- /dev/null > +++ b/Documentation/ABI/testing/sysfs-bus-iio-bno055 > @@ -0,0 +1,84 @@ > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_range > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Range for acceleration readings in G. Note that this does not > + affects the scale (which should be used when changing the > + maximum and minimum readable value affects also the reading > + scaling factor). Having this in G but the sensor output in m/s^2 seems inconsistent. > + > +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_range > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Range for angular velocity readings in dps. Note that this does > + not affects the scale (which should be used when changing the > + maximum and minimum readable value affects also the reading > + scaling factor). Again, units need to match or this is going to be really confusing. > + > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_range_available > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + List of allowed values for in_accel_range attribute > + > +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_range_available > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + List of allowed values for in_anglvel_range attribute > + > +What: /sys/bus/iio/devices/iio:deviceX/fast_magnetometer_calibration_enable > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Can be 1 or 0. Enables/disables the "Fast Magnetometer > + Calibration" HW function. Naming needs to be consistent with the ABI. This is a channel type specific function and to match existing calibration related ABI naming it would be. in_magn_calibration_fast_enable Some of the others need renaming in a similar way. > + > +What: /sys/bus/iio/devices/iio:deviceX/fusion_enable > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Can be 1 or 0. Enables/disables the "sensor fusion" (a.k.a. > + NDOF) HW function. > + > +What: /sys/bus/iio/devices/iio:deviceX/in_calibration_data > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Reports the binary calibration data blob for the IMU sensors. Why in_ ? What channels does this apply to? > + > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_accel > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > + Report the autocalibration status for the accelerometer sensor. For interfaces that really don't have any chance of generalising this one is terrible. Any hope at all of mapping this to something numeric? in_accel_calibration_auto_status > + > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_gyro > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > + Reports the autocalibration status for the gyroscope sensor. in_angvel_calibration_auto_status etc. > + > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_magn > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > + Reports the autocalibration status for the magnetometer sensor. > + > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_sys > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > + Reports the status for the IMU overall autocalibration. > + > +What: /sys/bus/iio/devices/iio:deviceX/unique_id Hmm. So normally we just dump these in the kernel log. I guess you need it here to associate a calibration blob with a particular sensor? We could put it in label, but that would stop us using that for things like positioning of the sensor. So perhaps this is something that we should add to the main ABI doc. Probably as serial_number rather than unique ID though. > +KernelVersion: 5.15 > +Contact: linux-iio@vger.kernel.org > +Description: > + 16-bytes, 2-digits-per-byte, HEX-string representing the sensor > + unique ID number. ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 06/10] iio: document bno055 private sysfs attributes 2021-10-28 11:04 ` Jonathan Cameron @ 2021-11-09 10:22 ` Andrea Merello 2021-11-14 16:20 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-11-09 10:22 UTC (permalink / raw) To: Jonathan Cameron Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello Few inline comments; ok for the rest. Il giorno gio 28 ott 2021 alle ore 12:59 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Thu, 28 Oct 2021 12:18:36 +0200 > Andrea Merello <andrea.merello@gmail.com> wrote: > > > This patch adds ABI documentation for bno055 driver private sysfs > > attributes. > > Hohum. As normal I dislike custom attributes but reality is these > don't map to anything 'standard' and I don't want them getting adopted > in places where the 'standard' approach works. > > So thinking a bit more on this, I wonder if we can fit it into standard > ABI. > > We can't use the normal range specification method of > _scale because it's internal to the device and the output reading is > unaffected. The range specification via _raw_available would let us know > the range, but it is not writeable so.. > > A control that changes the internal scaling of the sensor in a fashion > that is not visible to the outside world maps to calibscale. Whilst > that was intended for little tweaks to the input signal (often front > end amplifier gain tweak) it works here. It doesn't map through to > anything userspace is expected to apply. That combined with > _raw_available to let us know what the result is should work? > > What do you think of that approach? It's obviously a little more complex > to handle in the driver, but it does map to existing ABI and avoids > custom attributes etc. If I read the ABI documentation, then I would say that calibscale has nothing to do with this, but I think you have obviously a better feeling than me about what calibscale is really for. To be honest I've probably not a clear idea about what calibscale is indeed... In general, I would say that is better to stick to standard attributes when possible, and of course to avoid having the same thing mapped on random custom attributes in each driver, but IMO only up to the extent which doesn't force something that is really something different to map on a standard thing just because of the sake of having as much standard things as possible... But all this is probably quite obvious, and it all depends on the above (i.e. is it calibscale fitting well in your opinion?) .. Up to you on this one.. BTW I'm missing why this should complicate the driver.. I guess I'll find out if I'll implement it :) > > > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > > --- > > .../ABI/testing/sysfs-bus-iio-bno055 | 84 +++++++++++++++++++ > > 1 file changed, 84 insertions(+) > > create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-bno055 > > > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-bno055 b/Documentation/ABI/testing/sysfs-bus-iio-bno055 > > new file mode 100644 > > index 000000000000..930a70c5a858 > > --- /dev/null > > +++ b/Documentation/ABI/testing/sysfs-bus-iio-bno055 > > @@ -0,0 +1,84 @@ > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_range > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Range for acceleration readings in G. Note that this does not > > + affects the scale (which should be used when changing the > > + maximum and minimum readable value affects also the reading > > + scaling factor). > > Having this in G but the sensor output in m/s^2 seems inconsistent. > > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_range > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Range for angular velocity readings in dps. Note that this does > > + not affects the scale (which should be used when changing the > > + maximum and minimum readable value affects also the reading > > + scaling factor). > > Again, units need to match or this is going to be really confusing. > > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_range_available > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + List of allowed values for in_accel_range attribute > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_range_available > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + List of allowed values for in_anglvel_range attribute > > + > > +What: /sys/bus/iio/devices/iio:deviceX/fast_magnetometer_calibration_enable > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Can be 1 or 0. Enables/disables the "Fast Magnetometer > > + Calibration" HW function. > > Naming needs to be consistent with the ABI. This is a channel type specific function > and to match existing calibration related ABI naming it would be. > > in_magn_calibration_fast_enable > > Some of the others need renaming in a similar way. > > > + > > +What: /sys/bus/iio/devices/iio:deviceX/fusion_enable > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Can be 1 or 0. Enables/disables the "sensor fusion" (a.k.a. > > + NDOF) HW function. > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_calibration_data > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Reports the binary calibration data blob for the IMU sensors. > > Why in_ ? What channels does this apply to? > > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_accel > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > > + Report the autocalibration status for the accelerometer sensor. > > For interfaces that really don't have any chance of generalising this one is terrible. > Any hope at all of mapping this to something numeric? > > in_accel_calibration_auto_status > > > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_gyro > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > > + Reports the autocalibration status for the gyroscope sensor. > > in_angvel_calibration_auto_status > etc. > > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_magn > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > > + Reports the autocalibration status for the magnetometer sensor. > > + > > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_sys > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > > + Reports the status for the IMU overall autocalibration. > > + > > +What: /sys/bus/iio/devices/iio:deviceX/unique_id > > Hmm. So normally we just dump these in the kernel log. I guess you need it > here to associate a calibration blob with a particular sensor? Well, it was originally in kernel log, but putting in an attribute was one of the changes that has been requested for V2. It is needed by the user who copies the calibration data to the calibration file, in order for her/him to be able to properly name it (in case of more than 1 sensor on the same setup). > We could put it in label, but that would stop us using that for things like > positioning of the sensor. So perhaps this is something that we should add > to the main ABI doc. Probably as serial_number rather than unique ID though. OK, for renaming to "serial_number". I'm not sure they are conceptually the same thing, but I think it works anyway. Of course I can move its doc to the main file. Do you want a separate patch for this? > > +KernelVersion: 5.15 > > +Contact: linux-iio@vger.kernel.org > > +Description: > > + 16-bytes, 2-digits-per-byte, HEX-string representing the sensor > > + unique ID number. > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 06/10] iio: document bno055 private sysfs attributes 2021-11-09 10:22 ` Andrea Merello @ 2021-11-14 16:20 ` Jonathan Cameron 2022-01-04 11:42 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-11-14 16:20 UTC (permalink / raw) To: Andrea Merello Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello On Tue, 9 Nov 2021 11:22:27 +0100 Andrea Merello <andrea.merello@gmail.com> wrote: > Few inline comments; ok for the rest. > > Il giorno gio 28 ott 2021 alle ore 12:59 Jonathan Cameron > <jic23@kernel.org> ha scritto: > > > > On Thu, 28 Oct 2021 12:18:36 +0200 > > Andrea Merello <andrea.merello@gmail.com> wrote: > > > > > This patch adds ABI documentation for bno055 driver private sysfs > > > attributes. > > > > Hohum. As normal I dislike custom attributes but reality is these > > don't map to anything 'standard' and I don't want them getting adopted > > in places where the 'standard' approach works. > > > > So thinking a bit more on this, I wonder if we can fit it into standard > > ABI. > > > > We can't use the normal range specification method of > > _scale because it's internal to the device and the output reading is > > unaffected. The range specification via _raw_available would let us know > > the range, but it is not writeable so.. > > > > A control that changes the internal scaling of the sensor in a fashion > > that is not visible to the outside world maps to calibscale. Whilst > > that was intended for little tweaks to the input signal (often front > > end amplifier gain tweak) it works here. It doesn't map through to > > anything userspace is expected to apply. That combined with > > _raw_available to let us know what the result is should work? > > > > What do you think of that approach? It's obviously a little more complex > > to handle in the driver, but it does map to existing ABI and avoids > > custom attributes etc. > > If I read the ABI documentation, then I would say that calibscale has > nothing to do with this, but I think you have obviously a better > feeling than me about what calibscale is really for. To be honest I've > probably not a clear idea about what calibscale is indeed... Original intent was that it was a tweak for input amplifiers on some sensor types that you'd set as part of a calibration process. These days, for many sensors that have this it's handled at factory anyway and these tweak values are rarely exposed to software. > > In general, I would say that is better to stick to standard attributes > when possible, and of course to avoid having the same thing mapped on > random custom attributes in each driver, but IMO only up to the extent > which doesn't force something that is really something different to > map on a standard thing just because of the sake of having as much > standard things as possible... But all this is probably quite obvious, > and it all depends on the above (i.e. is it calibscale fitting well in > your opinion?) .. Up to you on this one.. > > BTW I'm missing why this should complicate the driver.. I guess I'll > find out if I'll implement it :) Inverse of the range values which is always a mess without floating point. Ok, I'm persuaded that we have to go with range here even if it is a bit painful and might cause confusion if we start getting it in lots of drivers. *fingers crossed we don't* There is still a units question though. Should we express the ranges in _processed or _raw units? Or do we make it explicit and call it rangeprocessed for example? For some devices the range will naturally be expressed as the range of ADC raw values, so there is definite room for confusion if we don't make it clear in the name. I'm open to other suggestions of how we name this to avoid falling into any heffalump traps. > > > > > > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > > > --- > > > .../ABI/testing/sysfs-bus-iio-bno055 | 84 +++++++++++++++++++ > > > 1 file changed, 84 insertions(+) > > > create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-bno055 > > > > > > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-bno055 b/Documentation/ABI/testing/sysfs-bus-iio-bno055 > > > new file mode 100644 > > > index 000000000000..930a70c5a858 > > > --- /dev/null > > > +++ b/Documentation/ABI/testing/sysfs-bus-iio-bno055 > > > @@ -0,0 +1,84 @@ > > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_range > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Range for acceleration readings in G. Note that this does not > > > + affects the scale (which should be used when changing the > > > + maximum and minimum readable value affects also the reading > > > + scaling factor). > > > > Having this in G but the sensor output in m/s^2 seems inconsistent. > > > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_range > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Range for angular velocity readings in dps. Note that this does > > > + not affects the scale (which should be used when changing the > > > + maximum and minimum readable value affects also the reading > > > + scaling factor). > > > > Again, units need to match or this is going to be really confusing. > > > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_accel_range_available > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + List of allowed values for in_accel_range attribute > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_range_available > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + List of allowed values for in_anglvel_range attribute > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/fast_magnetometer_calibration_enable > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Can be 1 or 0. Enables/disables the "Fast Magnetometer > > > + Calibration" HW function. > > > > Naming needs to be consistent with the ABI. This is a channel type specific function > > and to match existing calibration related ABI naming it would be. > > > > in_magn_calibration_fast_enable > > > > Some of the others need renaming in a similar way. > > > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/fusion_enable > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Can be 1 or 0. Enables/disables the "sensor fusion" (a.k.a. > > > + NDOF) HW function. > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_calibration_data > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Reports the binary calibration data blob for the IMU sensors. > > > > Why in_ ? What channels does this apply to? > > > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_accel > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > > > + Report the autocalibration status for the accelerometer sensor. > > > > For interfaces that really don't have any chance of generalising this one is terrible. > > Any hope at all of mapping this to something numeric? > > > > in_accel_calibration_auto_status > > > > > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_gyro > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > > > + Reports the autocalibration status for the gyroscope sensor. > > > > in_angvel_calibration_auto_status > > etc. > > > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_magn > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > > > + Reports the autocalibration status for the magnetometer sensor. > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/in_autocalibration_status_sys > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + Can be "Idle", "Bad", "Barely enough", "Fair", or "Good". > > > + Reports the status for the IMU overall autocalibration. > > > + > > > +What: /sys/bus/iio/devices/iio:deviceX/unique_id > > > > Hmm. So normally we just dump these in the kernel log. I guess you need it > > here to associate a calibration blob with a particular sensor? > > Well, it was originally in kernel log, but putting in an attribute was > one of the changes that has been requested for V2. Oops. :) Inconsistency is my middle name... > It is needed by the user who copies the calibration data to the > calibration file, in order for her/him to be able to properly name it > (in case of more than 1 sensor on the same setup). Fair enough that makes complete sense. > > > We could put it in label, but that would stop us using that for things like > > positioning of the sensor. So perhaps this is something that we should add > > to the main ABI doc. Probably as serial_number rather than unique ID though. > > OK, for renaming to "serial_number". I'm not sure they are > conceptually the same thing, but I think it works anyway. > Of course I can move its doc to the main file. Do you want a separate > patch for this? Separate patch would be great. Thanks, Jonathan > > > > +KernelVersion: 5.15 > > > +Contact: linux-iio@vger.kernel.org > > > +Description: > > > + 16-bytes, 2-digits-per-byte, HEX-string representing the sensor > > > + unique ID number. > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 06/10] iio: document bno055 private sysfs attributes 2021-11-14 16:20 ` Jonathan Cameron @ 2022-01-04 11:42 ` Andrea Merello 2022-01-15 15:27 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2022-01-04 11:42 UTC (permalink / raw) To: Jonathan Cameron Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello Sorry for the huge delay... > There is still a units question though. Should we express the ranges > in _processed or _raw units? Or do we make it explicit and call it > rangeprocessed for example? For some devices the range will naturally > be expressed as the range of ADC raw values, so there is definite room > for confusion if we don't make it clear in the name. > > I'm open to other suggestions of how we name this to avoid falling into > any heffalump traps. You are right: this might lead to confusion.. Making it explicit in the name seems a good idea. I've looked at other iio sysfs attributes in the DOC. It seems that "thesh" and "roc" attributes allows for both preprocessed and raw data: I found e.g. "<type>[Y][_name]_<raw|input>_thresh_value", but the related "what" entries written above all seem to omit both "_raw" and "_input"; I don't understand why. In any case, maybe we can stick to that already-existent naming schema? Assuming the pattern is correct, then wouldn't it be "in_accel_raw_range" (or "in_accel_x_raw_range", in case it could have different values for each axis) or "in_accel_input_range" in case range applies to preprocessed vals, etc ? Andrea ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 06/10] iio: document bno055 private sysfs attributes 2022-01-04 11:42 ` Andrea Merello @ 2022-01-15 15:27 ` Jonathan Cameron 2022-01-17 9:37 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2022-01-15 15:27 UTC (permalink / raw) To: Andrea Merello Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello On Tue, 4 Jan 2022 12:42:40 +0100 Andrea Merello <andrea.merello@gmail.com> wrote: > Sorry for the huge delay... No problem though I may have forgotten some of the discussion! > > > There is still a units question though. Should we express the ranges > > in _processed or _raw units? Or do we make it explicit and call it > > rangeprocessed for example? For some devices the range will naturally > > be expressed as the range of ADC raw values, so there is definite room > > for confusion if we don't make it clear in the name. > > > > I'm open to other suggestions of how we name this to avoid falling into > > any heffalump traps. > > You are right: this might lead to confusion.. Making it explicit in > the name seems a good idea. > > I've looked at other iio sysfs attributes in the DOC. It seems that > "thesh" and "roc" attributes allows for both preprocessed and raw > data: I found e.g. "<type>[Y][_name]_<raw|input>_thresh_value", but > the related "what" entries written above all seem to omit both "_raw" > and "_input"; I don't understand why. Excellent point. That documentation is garbage. Events are meant to pick it up implicitly from the related channel _raw or _input. I don't remember them ever having raw or input in their naming but it's possible they did right at the beginning before the ABI was anywhere near stable. Gah. I dread to think how long that that has been wrong. > > In any case, maybe we can stick to that already-existent naming schema? It doesn't exist really the docs are wrong. > > Assuming the pattern is correct, then wouldn't it be > "in_accel_raw_range" (or "in_accel_x_raw_range", in case it could > have different values for each axis) or "in_accel_input_range" in case > range applies to preprocessed vals, etc ? Tricky corner but I'd go with no, because the pattern is direction_type_infotype and in this case the infotype is rangeraw. We've not been totally consistent on whether we allow spaces in infotype or not. Intially we always did but then some of the userspace folks asked us to stop doing so because it requires all userspace software to have an explicit list rather than just adding controls to some GUI based on generic parsing. Hohum. Historical decisions that lead to messy interfaces... *sigh* Nearest to what you have here though are peak_raw and mean_raw though those are odd in of themselves in that they are basically special forms of _raw rather than something else that is in _raw units... So I think range_raw postfix is the best bet. Jonathan > > > Andrea ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 06/10] iio: document bno055 private sysfs attributes 2022-01-15 15:27 ` Jonathan Cameron @ 2022-01-17 9:37 ` Andrea Merello 2022-01-22 18:08 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2022-01-17 9:37 UTC (permalink / raw) To: Jonathan Cameron Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello Trivial inline comments below. Beside that, I've found another pleasing issue with this "range" thing on this device.. One one hand, things seem to always work as we discussed for the accelerometer (i.e. range doesn't affect the scale; the HW always provides readings in the same scale, but with different range and precision) on the other hand, the gyroscope behavior depends by the internal IMU firmware version.. great.. Stock firmware has a bug[0], so that the "range" gyroscope registers do change the scale indeed. AFAICT stock firmware is the one you find in most (all?) breakout boards, which are usually available (and which I'm using right now for this driver mainlining attempt). Upgrading firmware looks like a rather obscure process that AFAICT can be done only in some specific USB-stick demo-board ("shuttle board") or with maybe with FAE assistance on custom developed boards [1] (i.e. maybe can be done by some professional user; I would say not for most people). So, I'm now wondering how to handle this... I really want to support the stock FW, which seems the most widespread, and the one I have right now; I'd say this means: the accelerometer thing will still work as we discussed (i.e. the range attribute thing), while the gyro will have writeable scale, and a (ro) scale_available attrib. But what about the gyro range thing? Should I drop it, or keep it as informative read-only? Then I could also support the new firmware (which I cannot test right now with my actual breakout board, but I might see whether I could get a board with an updated IMU), keeping also the current driver behavior (i.e. range stuff). But the question is: in either cases (new vs old fw) should the non-necessary attributes disappear or they may just be RO or locked (i.e. scale_available for new FW and range stuff for the old one)? Any thoughts and advice on this whole thing would be very welcome :) my current inclination anyway now tends to be: go on supporting only the stock FW (i.e. the board I have here now) and eventually add support for the new fw later on, after merge. [0] https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BNO055-Wrong-sensitivity-resolution-in-datasheet/td-p/10266 [1] https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BNO055-Software-Version/td-p/14001 > > I've looked at other iio sysfs attributes in the DOC. It seems that > > "thesh" and "roc" attributes allows for both preprocessed and raw > > data: I found e.g. "<type>[Y][_name]_<raw|input>_thresh_value", but > > the related "what" entries written above all seem to omit both "_raw" > > and "_input"; I don't understand why. > > Excellent point. That documentation is garbage. Events are meant > to pick it up implicitly from the related channel _raw or _input. > I don't remember them ever having raw or input in their naming but > it's possible they did right at the beginning before the ABI was anywhere > near stable. Gah. I dread to think how long that that has been wrong. Ok, great :) > So I think range_raw postfix is the best bet. Will go with this, thanks. > Jonathan > > > > > > > > > > > > Andrea > ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 06/10] iio: document bno055 private sysfs attributes 2022-01-17 9:37 ` Andrea Merello @ 2022-01-22 18:08 ` Jonathan Cameron 0 siblings, 0 replies; 89+ messages in thread From: Jonathan Cameron @ 2022-01-22 18:08 UTC (permalink / raw) To: Andrea Merello Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello On Mon, 17 Jan 2022 10:37:33 +0100 Andrea Merello <andrea.merello@gmail.com> wrote: > Trivial inline comments below. Beside that, I've found another > pleasing issue with this "range" thing on this device.. > > One one hand, things seem to always work as we discussed for the > accelerometer (i.e. range doesn't affect the scale; the HW always > provides readings in the same scale, but with different range and > precision) on the other hand, the gyroscope behavior depends by the > internal IMU firmware version.. great.. *sigh* :) > > Stock firmware has a bug[0], so that the "range" gyroscope registers > do change the scale indeed. AFAICT stock firmware is the one you find > in most (all?) breakout boards, which are usually available (and which > I'm using right now for this driver mainlining attempt). Upgrading > firmware looks like a rather obscure process that AFAICT can be done > only in some specific USB-stick demo-board ("shuttle board") or with > maybe with FAE assistance on custom developed boards [1] (i.e. maybe > can be done by some professional user; I would say not for most > people). > > So, I'm now wondering how to handle this... I really want to support > the stock FW, which seems the most widespread, and the one I have > right now; I'd say this means: the accelerometer thing will still work > as we discussed (i.e. the range attribute thing), while the gyro will > have writeable scale, and a (ro) scale_available attrib. But what > about the gyro range thing? Should I drop it, or keep it as > informative read-only? I'd be cynical and for initial version at least, just hide it as 'too complex' with a comment in the driver code on why. > > Then I could also support the new firmware (which I cannot test right > now with my actual breakout board, but I might see whether I could get > a board with an updated IMU), keeping also the current driver behavior > (i.e. range stuff). > > But the question is: in either cases (new vs old fw) should the > non-necessary attributes disappear or they may just be RO or locked > (i.e. scale_available for new FW and range stuff for the old one)? If they don't have meaning then they should disappear, but it would also be valid to have the 'broken' one be read only if there is an appropriate value. > > Any thoughts and advice on this whole thing would be very welcome :) > my current inclination anyway now tends to be: go on supporting only > the stock FW (i.e. the board I have here now) and eventually add > support for the new fw later on, after merge. Sounds sensible - but.... Make sure you check the firmware version number (I hope it has one) and print a warning at least if you get one that you have strong reason to believe will handle this differently from whatever the driver is supporting. This is definitely going to be a case for detailed comments in the driver code so that we can 'recall' what on earth was going on here in N years time! > > [0] https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BNO055-Wrong-sensitivity-resolution-in-datasheet/td-p/10266 > [1] https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BNO055-Software-Version/td-p/14001 > > > > I've looked at other iio sysfs attributes in the DOC. It seems that > > > "thesh" and "roc" attributes allows for both preprocessed and raw > > > data: I found e.g. "<type>[Y][_name]_<raw|input>_thresh_value", but > > > the related "what" entries written above all seem to omit both "_raw" > > > and "_input"; I don't understand why. > > > > Excellent point. That documentation is garbage. Events are meant > > to pick it up implicitly from the related channel _raw or _input. > > I don't remember them ever having raw or input in their naming but > > it's possible they did right at the beginning before the ABI was anywhere > > near stable. Gah. I dread to think how long that that has been wrong. > > Ok, great :) > > > So I think range_raw postfix is the best bet. > > Will go with this, thanks. > > > Jonathan > > > > > > > > > > > > > > > > > > > > > Andrea > > ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 07/10] iio: imu: add Bosch Sensortec BNO055 core driver 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello ` (5 preceding siblings ...) 2021-10-28 10:18 ` [v2 06/10] iio: document bno055 private sysfs attributes Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 13:31 ` Jonathan Cameron 2021-10-28 10:18 ` [v2 08/10] dt-bindings: iio: imu: add documentation for Bosch BNO055 bindings Andrea Merello ` (3 subsequent siblings) 10 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello, Andrea Merello 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> --- drivers/iio/imu/Kconfig | 1 + drivers/iio/imu/Makefile | 1 + drivers/iio/imu/bno055/Kconfig | 4 + drivers/iio/imu/bno055/Makefile | 3 + drivers/iio/imu/bno055/bno055.c | 1480 +++++++++++++++++++++++++++++++ drivers/iio/imu/bno055/bno055.h | 12 + 6 files changed, 1501 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..d197310661af --- /dev/null +++ b/drivers/iio/imu/bno055/Kconfig @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +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..c55741d0e96f --- /dev/null +++ b/drivers/iio/imu/bno055/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +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..c85cb985f0f1 --- /dev/null +++ b/drivers/iio/imu/bno055/bno055.c @@ -0,0 +1,1480 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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/bitmap.h> +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/util_macros.h> + +#include <linux/iio/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 "bno055.h" + +#define BNO055_FW_NAME "bno055-caldata" +#define BNO055_FW_EXT ".dat" +#define BNO055_FW_UID_NAME BNO055_FW_NAME "-%*phN" BNO055_FW_EXT +#define BNO055_FW_GENERIC_NAME (BNO055_FW_NAME BNO055_FW_EXT) + +/* common registers */ +#define BNO055_PAGESEL_REG 0x7 + +/* page 0 registers */ +#define BNO055_CHIP_ID_REG 0x0 +#define BNO055_CHIP_ID_MAGIC 0xA0 +#define BNO055_SW_REV_LSB_REG 0x4 +#define BNO055_SW_REV_MSB_REG 0x5 +#define BNO055_ACC_DATA_X_LSB_REG 0x8 +#define BNO055_ACC_DATA_Y_LSB_REG 0xA +#define BNO055_ACC_DATA_Z_LSB_REG 0xC +#define BNO055_MAG_DATA_X_LSB_REG 0xE +#define BNO055_MAG_DATA_Y_LSB_REG 0x10 +#define BNO055_MAG_DATA_Z_LSB_REG 0x12 +#define BNO055_GYR_DATA_X_LSB_REG 0x14 +#define BNO055_GYR_DATA_Y_LSB_REG 0x16 +#define BNO055_GYR_DATA_Z_LSB_REG 0x18 +#define BNO055_EUL_DATA_X_LSB_REG 0x1A +#define BNO055_EUL_DATA_Y_LSB_REG 0x1C +#define BNO055_EUL_DATA_Z_LSB_REG 0x1E +#define BNO055_QUAT_DATA_W_LSB_REG 0x20 +#define BNO055_LIA_DATA_X_LSB_REG 0x28 +#define BNO055_LIA_DATA_Y_LSB_REG 0x2A +#define BNO055_LIA_DATA_Z_LSB_REG 0x2C +#define BNO055_GRAVITY_DATA_X_LSB_REG 0x2E +#define BNO055_GRAVITY_DATA_Y_LSB_REG 0x30 +#define BNO055_GRAVITY_DATA_Z_LSB_REG 0x32 +#define BNO055_SCAN_CH_COUNT ((BNO055_GRAVITY_DATA_Z_LSB_REG - BNO055_ACC_DATA_X_LSB_REG) / 2) +#define BNO055_TEMP_REG 0x34 +#define BNO055_CALIB_STAT_REG 0x35 +#define BNO055_CALIB_STAT_MASK GENMASK(1, 0) +#define BNO055_CALIB_STAT_MAGN_SHIFT 0 +#define BNO055_CALIB_STAT_ACCEL_SHIFT 2 +#define BNO055_CALIB_STAT_GYRO_SHIFT 4 +#define BNO055_CALIB_STAT_SYS_SHIFT 6 +#define BNO055_SYS_ERR_REG 0x3A +#define BNO055_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 +/* Android orientation mode means: pitch value decreases turning clockwise */ +#define BNO055_UNIT_SEL_ANDROID BIT(7) +#define BNO055_CALDATA_START 0x55 +#define BNO055_CALDATA_END 0x6A +#define BNO055_CALDATA_LEN 22 + +/* + * The difference in address between the register that contains the + * value and the register that contains the offset. This applies for + * accel, gyro and magn channels. + */ +#define BNO055_REG_OFFSET_ADDR 0x4D + +/* page 1 registers */ +#define PG1(x) ((x) | 0x80) +#define BNO055_ACC_CONFIG_REG PG1(0x8) +#define BNO055_ACC_CONFIG_LPF_MASK GENMASK(4, 2) +#define BNO055_ACC_CONFIG_RANGE_MASK GENMASK(1, 0) +#define BNO055_MAG_CONFIG_REG PG1(0x9) +#define BNO055_MAG_CONFIG_HIGHACCURACY 0x18 +#define BNO055_MAG_CONFIG_ODR_MASK GENMASK(2, 0) +#define BNO055_GYR_CONFIG_REG PG1(0xA) +#define BNO055_GYR_CONFIG_RANGE_MASK GENMASK(2, 0) +#define BNO055_GYR_CONFIG_LPF_MASK GENMASK(5, 3) +#define BNO055_GYR_AM_SET_REG PG1(0x1F) +#define BNO055_UID_LOWER_REG PG1(0x50) +#define BNO055_UID_HIGHER_REG PG1(0x5F) +#define BNO055_UID_LEN 16 + +static const int bno055_mag_odr_vals[] = {2, 6, 8, 10, 15, 20, 25, 30}; +/* the following one is INT_PLUS_MICRO */ +static const int bno055_acc_lpf_vals[] = {7, 810000, 15, 630000, + 31, 250000, 62, 500000, 125, 0, + 250, 0, 500, 0, 1000, 0}; +static const 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]; + struct { + __le16 chans[BNO055_SCAN_CH_COUNT]; + s64 timestamp __aligned(8); + } buf; +}; + +static bool bno055_regmap_volatile(struct device *dev, unsigned int reg) +{ + /* data and status registers */ + if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG) + return true; + + /* when in fusion mode, config is updated by chip */ + if (reg == BNO055_MAG_CONFIG_REG || + reg == BNO055_ACC_CONFIG_REG || + reg == BNO055_GYR_CONFIG_REG) + return true; + + /* calibration data may be updated by the IMU */ + if (reg >= BNO055_CALDATA_START && reg <= BNO055_CALDATA_END) + return true; + return false; +} + +static bool bno055_regmap_readable(struct device *dev, unsigned int reg) +{ + /* unnamed PG0 reserved areas */ + if ((reg < PG1(0) && reg > BNO055_CALDATA_END) || + reg == 0x3C) + return false; + + /* unnamed PG1 reserved areas */ + if (reg > PG1(BNO055_UID_HIGHER_REG) || + (reg < PG1(BNO055_UID_LOWER_REG) && reg > PG1(BNO055_GYR_AM_SET_REG)) || + reg == PG1(0xE) || + (reg < PG1(BNO055_PAGESEL_REG) && reg >= PG1(0x0))) + return false; + return true; +} + +static bool bno055_regmap_writeable(struct device *dev, unsigned int reg) +{ + /* + * Unreadable registers are indeed reserved; there are no WO regs + * (except for a single bit in SYS_TRIGGER register) + */ + if (!bno055_regmap_readable(dev, reg)) + return false; + + /* data and status registers */ + if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG) + return false; + + /* IDs areas */ + if (reg < BNO055_PAGESEL_REG || + (reg <= BNO055_UID_HIGHER_REG && reg >= BNO055_UID_LOWER_REG)) + return false; + + return true; +} + +static const struct regmap_range_cfg bno055_regmap_ranges[] = { + { + .range_min = 0, + .range_max = 0x7f * 2, + .selector_reg = BNO055_PAGESEL_REG, + .selector_mask = GENMASK(7, 0), + .selector_shift = 0, + .window_start = 0, + .window_len = 0x80 + }, +}; + +const struct regmap_config bno055_regmap_config = { + .name = "bno055", + .reg_bits = 8, + .val_bits = 8, + .ranges = bno055_regmap_ranges, + .num_ranges = 1, + .volatile_reg = bno055_regmap_volatile, + .max_register = 0x80 * 2, + .writeable_reg = bno055_regmap_writeable, + .readable_reg = bno055_regmap_readable, + .cache_type = REGCACHE_RBTREE, +}; +EXPORT_SYMBOL_GPL(bno055_regmap_config); + +static int bno055_reg_update_bits(struct bno055_priv *priv, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int ret; + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret && ret != -ERESTARTSYS) { + dev_err(priv->dev, "Regmap update_bits error. adr: 0x%x, ret: %d", + reg, ret); + } + + return ret; +} + +/* must be called in configuration mode */ +int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw) +{ + if (fw->size != BNO055_CALDATA_LEN) { + dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)", + fw->size, BNO055_CALDATA_LEN); + return -EINVAL; + } + + dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, fw->data); + return regmap_bulk_write(priv->regmap, BNO055_CALDATA_START, + fw->data, BNO055_CALDATA_LEN); +} + +static int bno055_init(struct bno055_priv *priv, const struct firmware *caldata) +{ + int ret; + + ret = regmap_write(priv->regmap, BNO055_SYS_TRIGGER_REG, + (priv->clk ? BNO055_SYS_TRIGGER_CLK_SEL : 0) | + BNO055_SYS_TRIGGER_RST_INT); + if (ret) + return ret; + + msleep(100); + ret = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, + BNO055_OPR_MODE_CONFIG); + if (ret) + return ret; + + /* use standard SI units */ + ret = regmap_write(priv->regmap, BNO055_UNIT_SEL_REG, + BNO055_UNIT_SEL_ANDROID); + if (ret) + return ret; + + if (caldata) { + ret = bno055_calibration_load(priv, caldata); + if (ret) + dev_warn(priv->dev, "failed to load calibration data with error %d", + ret); + } + + priv->operation_mode = BNO055_OPR_MODE_FUSION; + return regmap_write(priv->regmap, BNO055_OPR_MODE_REG, + priv->operation_mode); +} + +static void bno055_uninit(void *arg) +{ + struct bno055_priv *priv = arg; + + /* stop the IMU */ + regmap_write(priv->regmap, BNO055_OPR_MODE_REG, BNO055_OPR_MODE_CONFIG); +} + +static void bno055_clk_disable(void *arg) +{ + struct bno055_priv *priv = arg; + + clk_disable_unprepare(priv->clk); +} + +#define BNO055_CHANNEL(_type, _axis, _index, _address, _sep, _sh, _avail) { \ + .address = _address, \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | (_sep), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_sh), \ + .info_mask_shared_by_type_available = _avail, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + .repeat = IIO_MOD_##_axis == IIO_MOD_QUATERNION ? 4 : 0 \ + }, \ +} + +/* scan indexes follow DATA register order */ +enum 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_YAW, + BNO055_SCAN_ROLL, + BNO055_SCAN_PITCH, + BNO055_SCAN_QUATERNION, + BNO055_SCAN_LIA_X, + BNO055_SCAN_LIA_Y, + BNO055_SCAN_LIA_Z, + BNO055_SCAN_GRAVITY_X, + BNO055_SCAN_GRAVITY_Y, + BNO055_SCAN_GRAVITY_Z, + BNO055_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec bno055_channels[] = { + /* accelerometer */ + BNO055_CHANNEL(IIO_ACCEL, X, BNO055_SCAN_ACCEL_X, + BNO055_ACC_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), + BNO055_CHANNEL(IIO_ACCEL, Y, BNO055_SCAN_ACCEL_Y, + BNO055_ACC_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), + BNO055_CHANNEL(IIO_ACCEL, Z, BNO055_SCAN_ACCEL_Z, + BNO055_ACC_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), + /* gyroscope */ + BNO055_CHANNEL(IIO_ANGL_VEL, X, BNO055_SCAN_GYRO_X, + BNO055_GYR_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), + BNO055_CHANNEL(IIO_ANGL_VEL, Y, BNO055_SCAN_GYRO_Y, + BNO055_GYR_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), + BNO055_CHANNEL(IIO_ANGL_VEL, Z, BNO055_SCAN_GYRO_Z, + BNO055_GYR_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), + /* magnetometer */ + BNO055_CHANNEL(IIO_MAGN, X, BNO055_SCAN_MAGN_X, + BNO055_MAG_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_SAMP_FREQ), BIT(IIO_CHAN_INFO_SAMP_FREQ)), + BNO055_CHANNEL(IIO_MAGN, Y, BNO055_SCAN_MAGN_Y, + BNO055_MAG_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_SAMP_FREQ), BIT(IIO_CHAN_INFO_SAMP_FREQ)), + BNO055_CHANNEL(IIO_MAGN, Z, BNO055_SCAN_MAGN_Z, + BNO055_MAG_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), + BIT(IIO_CHAN_INFO_SAMP_FREQ), BIT(IIO_CHAN_INFO_SAMP_FREQ)), + /* euler angle */ + BNO055_CHANNEL(IIO_ROT, YAW, BNO055_SCAN_YAW, + BNO055_EUL_DATA_X_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_ROT, ROLL, BNO055_SCAN_ROLL, + BNO055_EUL_DATA_Y_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_ROT, PITCH, BNO055_SCAN_PITCH, + BNO055_EUL_DATA_Z_LSB_REG, 0, 0, 0), + /* quaternion */ + BNO055_CHANNEL(IIO_ROT, QUATERNION, BNO055_SCAN_QUATERNION, + BNO055_QUAT_DATA_W_LSB_REG, 0, 0, 0), + + /* linear acceleration */ + BNO055_CHANNEL(IIO_ACCEL, ACCEL_LINEAR_X, BNO055_SCAN_LIA_X, + BNO055_LIA_DATA_X_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_ACCEL, ACCEL_LINEAR_Y, BNO055_SCAN_LIA_Y, + BNO055_LIA_DATA_Y_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_ACCEL, ACCEL_LINEAR_Z, BNO055_SCAN_LIA_Z, + BNO055_LIA_DATA_Z_LSB_REG, 0, 0, 0), + + /* gravity vector */ + BNO055_CHANNEL(IIO_GRAVITY, X, BNO055_SCAN_GRAVITY_X, + BNO055_GRAVITY_DATA_X_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_GRAVITY, Y, BNO055_SCAN_GRAVITY_Y, + BNO055_GRAVITY_DATA_Y_LSB_REG, 0, 0, 0), + BNO055_CHANNEL(IIO_GRAVITY, Z, BNO055_SCAN_GRAVITY_Z, + BNO055_GRAVITY_DATA_Z_LSB_REG, 0, 0, 0), + + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1 + }, + IIO_CHAN_SOFT_TIMESTAMP(BNO055_SCAN_TIMESTAMP), +}; + +static int bno055_get_acc_lpf(struct bno055_priv *priv, int *val, int *val2) +{ + const int shift = __ffs(BNO055_ACC_CONFIG_LPF_MASK); + int hwval, idx; + int ret; + + ret = regmap_read(priv->regmap, BNO055_ACC_CONFIG_REG, &hwval); + if (ret) + return ret; + + idx = (hwval & BNO055_ACC_CONFIG_LPF_MASK) >> shift; + *val = bno055_acc_lpf_vals[idx * 2]; + *val2 = bno055_acc_lpf_vals[idx * 2 + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int bno055_set_acc_lpf(struct bno055_priv *priv, int val, int val2) +{ + const int shift = __ffs(BNO055_ACC_CONFIG_LPF_MASK); + int req_val = val * 1000 + val2 / 1000; + bool first = true; + int best_delta; + int best_idx; + int tbl_val; + int delta; + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(bno055_acc_lpf_vals) / 2; i++) { + tbl_val = bno055_acc_lpf_vals[i * 2] * 1000 + + bno055_acc_lpf_vals[i * 2 + 1] / 1000; + delta = abs(tbl_val - req_val); + if (first || delta < best_delta) { + best_delta = delta; + best_idx = i; + first = false; + } + } + + /* + * 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; + + ret = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, + BNO055_OPR_MODE_CONFIG); + if (ret) + return ret; + + ret = bno055_reg_update_bits(priv, BNO055_ACC_CONFIG_REG, + BNO055_ACC_CONFIG_LPF_MASK, + best_idx << shift); + + if (ret) + return ret; + + return regmap_write(priv->regmap, BNO055_OPR_MODE_REG, + BNO055_OPR_MODE_AMG); +} + +static int bno055_get_regmask(struct bno055_priv *priv, int *val, int reg, + int mask, const int tbl[]) +{ + const int shift = __ffs(mask); + int hwval, idx; + int ret; + + ret = regmap_read(priv->regmap, reg, &hwval); + if (ret) + return ret; + + idx = (hwval & mask) >> shift; + *val = tbl[idx]; + + return IIO_VAL_INT; +} + +static int bno055_set_regmask(struct bno055_priv *priv, int val, int reg, + int mask, const int table[], int table_len) + +{ + int hwval = find_closest_unsorted(val, table, table_len); + const int shift = __ffs(mask); + int ret; + /* + * 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; + + ret = regmap_write(priv->regmap, 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 regmap_write(priv->regmap, BNO055_OPR_MODE_REG, + BNO055_OPR_MODE_AMG); +} + +#define bno055_get_mag_odr(p, v) \ + bno055_get_regmask(p, v, \ + BNO055_MAG_CONFIG_REG, BNO055_MAG_CONFIG_ODR_MASK, \ + bno055_mag_odr_vals) + +#define bno055_set_mag_odr(p, v) \ + bno055_set_regmask(p, v, \ + BNO055_MAG_CONFIG_REG, BNO055_MAG_CONFIG_ODR_MASK, \ + bno055_mag_odr_vals, \ + ARRAY_SIZE(bno055_mag_odr_vals)) + +#define bno055_get_acc_range(p, v) \ + bno055_get_regmask(priv, v, \ + BNO055_ACC_CONFIG_REG, BNO055_ACC_CONFIG_RANGE_MASK, \ + bno055_acc_ranges) + +#define bno055_set_acc_range(p, v) \ + bno055_set_regmask(p, v, \ + BNO055_ACC_CONFIG_REG, \ + BNO055_ACC_CONFIG_RANGE_MASK, \ + bno055_acc_ranges, ARRAY_SIZE(bno055_acc_ranges)) + +#define bno055_get_gyr_lpf(p, v) \ + bno055_get_regmask(p, v, \ + BNO055_GYR_CONFIG_REG, BNO055_GYR_CONFIG_LPF_MASK, \ + bno055_gyr_lpf_vals) + +#define bno055_set_gyr_lpf(p, v) \ + bno055_set_regmask(p, v, \ + BNO055_GYR_CONFIG_REG, BNO055_GYR_CONFIG_LPF_MASK, \ + bno055_gyr_lpf_vals, \ + ARRAY_SIZE(bno055_gyr_lpf_vals)) + +#define bno055_get_gyr_range(p, v) \ + bno055_get_regmask(p, v, \ + BNO055_GYR_CONFIG_REG, BNO055_GYR_CONFIG_RANGE_MASK, \ + bno055_gyr_ranges) + +#define bno055_set_gyr_range(p, v) \ + bno055_set_regmask(p, v, \ + BNO055_GYR_CONFIG_REG, \ + BNO055_GYR_CONFIG_RANGE_MASK, \ + bno055_gyr_ranges, ARRAY_SIZE(bno055_gyr_ranges)) + +static int bno055_read_simple_chan(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + __le16 raw_val; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_bulk_read(priv->regmap, chan->address, + &raw_val, sizeof(raw_val)); + if (ret < 0) + return ret; + *val = (s16)le16_to_cpu(raw_val); + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + if (priv->operation_mode != BNO055_OPR_MODE_AMG) { + *val = 0; + } else { + ret = regmap_bulk_read(priv->regmap, + chan->address + + BNO055_REG_OFFSET_ADDR, + &raw_val, sizeof(raw_val)); + if (ret < 0) + return ret; + /* + * IMU reports sensor offests; IIO wants correction + * offset, thus we need the 'minus' here. + */ + *val = -(s16)le16_to_cpu(raw_val); + } + 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 -EINVAL; + else + return bno055_get_mag_odr(priv, val); + + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + case IIO_ANGL_VEL: + return bno055_get_gyr_lpf(priv, val); + case IIO_ACCEL: + return bno055_get_acc_lpf(priv, val, val2); + default: + return -EINVAL; + } + } +} + +static int bno055_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + switch (chan->type) { + case IIO_ANGL_VEL: + if (priv->operation_mode != BNO055_OPR_MODE_AMG) { + /* locked on 32 */ + *vals = bno055_gyr_lpf_vals + 7; + *length = 1; + } else { + *vals = bno055_gyr_lpf_vals; + *length = ARRAY_SIZE(bno055_gyr_lpf_vals); + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + case IIO_ACCEL: + if (priv->operation_mode != BNO055_OPR_MODE_AMG) { + /* locked on 62.5Hz */ + *vals = bno055_acc_lpf_vals + 6; + *length = 2; + } else { + *vals = bno055_acc_lpf_vals; + *length = ARRAY_SIZE(bno055_acc_lpf_vals); + } + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + } + break; + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->type) { + case IIO_MAGN: + if (priv->operation_mode != BNO055_OPR_MODE_AMG) { + /* locked on 20Hz */ + *vals = bno055_mag_odr_vals + 5; + *length = 1; + } else { + *vals = bno055_mag_odr_vals; + *length = ARRAY_SIZE(bno055_mag_odr_vals); + } + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bno055_read_temp_chan(struct iio_dev *indio_dev, int *val) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + unsigned int raw_val; + int ret; + + ret = regmap_read(priv->regmap, BNO055_TEMP_REG, &raw_val); + if (ret < 0) + return ret; + + /* + * Tables 3-36 and 3-37: one byte of priv, signed, 1 LSB = 1C. + * ABI wants milliC. + */ + *val = raw_val * 1000; + + return IIO_VAL_INT; +} + +static int bno055_read_quaternion(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int size, int *vals, int *val_len, + long mask) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + __le16 raw_vals[4]; + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (size < 4) + return -EINVAL; + ret = regmap_bulk_read(priv->regmap, + BNO055_QUAT_DATA_W_LSB_REG, + raw_vals, sizeof(raw_vals)); + if (ret < 0) + return ret; + for (i = 0; i < 4; i++) + vals[i] = (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) +{ + struct bno055_priv *priv = iio_priv(indio_dev); + int ret; + + mutex_lock(&priv->lock); + ret = _bno055_read_raw_multi(indio_dev, chan, size, + vals, val_len, mask); + mutex_unlock(&priv->lock); + return ret; +} + +static int _bno055_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bno055_priv *priv = iio_priv(iio_dev); + + switch (chan->type) { + case IIO_MAGN: + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return bno055_set_mag_odr(priv, val); + + default: + return -EINVAL; + } + 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); + + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bno055_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bno055_priv *priv = iio_priv(iio_dev); + int ret; + + mutex_lock(&priv->lock); + ret = _bno055_write_raw(iio_dev, chan, val, val2, mask); + mutex_unlock(&priv->lock); + + return ret; +} + +static ssize_t in_accel_range_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%s\n", + priv->operation_mode != BNO055_OPR_MODE_AMG ? "4" : + "2 4 8 16"); +} + +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 sysfs_emit(buf, "%s\n", + (priv->operation_mode != BNO055_OPR_MODE_AMG) ? "2000" : + "125 250 500 1000 2000"); +} + +static ssize_t bno055_operation_mode_set(struct bno055_priv *priv, + int operation_mode) +{ + int ret; + + mutex_lock(&priv->lock); + priv->operation_mode = operation_mode; + ret = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, + BNO055_OPR_MODE_CONFIG); + if (ret) { + mutex_unlock(&priv->lock); + return ret; + } + + ret = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, priv->operation_mode); + mutex_unlock(&priv->lock); + + return ret; +} + +static ssize_t bno055_fusion_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d\n", + priv->operation_mode != BNO055_OPR_MODE_AMG); +} + +static ssize_t bno055_fusion_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + int ret = 0; + + if (sysfs_streq(buf, "0")) { + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_AMG); + } else { + /* + * Coming from AMG means the FMC was off, just switch to fusion + * but don't change anything that doesn't belong to us (i.e let. + * FMC stay off. + * Coming from any other fusion mode means we don't need to do + * anything. + */ + if (priv->operation_mode == BNO055_OPR_MODE_AMG) + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF); + } + + return len ?: len; +} + +static ssize_t bno055_fmc_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d\n", + priv->operation_mode == BNO055_OPR_MODE_FUSION); +} + +static ssize_t bno055_fmc_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + int ret = 0; + + if (sysfs_streq(buf, "0")) { + if (priv->operation_mode == BNO055_OPR_MODE_FUSION) + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF); + } else { + if (priv->operation_mode == BNO055_OPR_MODE_AMG) + return -EINVAL; + } + + return len ?: ret; +} + +static ssize_t bno055_in_accel_range_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + int val; + int ret; + + ret = bno055_get_acc_range(priv, &val); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t bno055_in_accel_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&priv->lock); + ret = bno055_set_acc_range(priv, val); + mutex_unlock(&priv->lock); + + return ret ?: len; +} + +static ssize_t bno055_in_gyr_range_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int val; + int ret; + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + + ret = bno055_get_gyr_range(priv, &val); + if (ret < 0) + return ret; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t bno055_in_gyr_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&priv->lock); + ret = bno055_set_gyr_range(priv, val); + mutex_unlock(&priv->lock); + + return ret ?: len; +} + +static ssize_t bno055_get_calib_status(struct device *dev, char *buf, int which) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + static const char * const calib_status[] = { + "bad", "barely enough", "fair", "good" }; + const char *calib_str; + int ret; + int val; + + if (priv->operation_mode == BNO055_OPR_MODE_AMG || + (priv->operation_mode == BNO055_OPR_MODE_FUSION_FMC_OFF && + which == BNO055_CALIB_STAT_MAGN_SHIFT)) { + calib_str = "idle"; + } else { + mutex_lock(&priv->lock); + ret = regmap_read(priv->regmap, 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 sysfs_emit(buf, "%s\n", calib_str); +} + +static ssize_t unique_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%*ph\n", BNO055_UID_LEN, priv->uid); +} + +static ssize_t in_calibration_data_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); + u8 data[BNO055_CALDATA_LEN]; + int ret; + + mutex_lock(&priv->lock); + ret = regmap_write(priv->regmap, 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 = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, priv->operation_mode); + mutex_unlock(&priv->lock); + if (ret) + return ret; + + memcpy(buf, data, BNO055_CALDATA_LEN); + + return BNO055_CALDATA_LEN; +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); +} + +#ifdef CONFIG_DEBUG_FS +int bno055_debugfs_reg_access(struct iio_dev *iio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct bno055_priv *priv = iio_priv(iio_dev); + + if (readval) + return regmap_read(priv->regmap, reg, readval); + else + return regmap_write(priv->regmap, reg, writeval); +} + +static ssize_t bno055_show_fw_version(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct bno055_priv *priv = file->private_data; + int rev, ver; + char *buf; + int ret; + + ret = regmap_read(priv->regmap, BNO055_SW_REV_LSB_REG, &rev); + if (ret) + return ret; + + ret = regmap_read(priv->regmap, BNO055_SW_REV_MSB_REG, &ver); + if (ret) + return ret; + + buf = devm_kasprintf(priv->dev, GFP_KERNEL, "ver: 0x%x, rev: 0x%x\n", + ver, rev); + if (!buf) + return -ENOMEM; + + ret = simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf)); + devm_kfree(priv->dev, buf); + + return ret; +} + +static const struct file_operations bno055_fw_version_ops = { + .open = simple_open, + .read = bno055_show_fw_version, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static void bno055_debugfs_init(struct iio_dev *iio_dev) +{ + struct bno055_priv *priv = iio_priv(iio_dev); + + debugfs_create_file("firmware_version", 0400, + iio_get_debugfs_dentry(iio_dev), priv, + &bno055_fw_version_ops); +} +#else +static void bno055_debugfs_init(struct iio_dev *iio_dev) +{ +} + +int bno055_debugfs_reg_access(struct iio_dev *iio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + return 0; +} +#endif + +static IIO_DEVICE_ATTR(fusion_enable, 0644, + bno055_fusion_enable_show, + bno055_fusion_enable_store, 0); + +static IIO_DEVICE_ATTR(fast_magnetometer_calibration_enable, 0644, + bno055_fmc_enable_show, + bno055_fmc_enable_store, 0); + +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(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_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 IIO_DEVICE_ATTR_RO(unique_id, 0); + +static struct attribute *bno055_attrs[] = { + &iio_dev_attr_in_accel_range_available.dev_attr.attr, + &iio_dev_attr_in_accel_range.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_fusion_enable.dev_attr.attr, + &iio_dev_attr_fast_magnetometer_calibration_enable.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, + &iio_dev_attr_unique_id.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, + .read_avail = bno055_read_avail, + .write_raw = bno055_write_raw, + .attrs = &bno055_attrs_group, + .debugfs_reg_access = bno055_debugfs_reg_access, +}; + +/* + * Reads len samples from the HW, stores them in buf starting from buf_idx, + * and applies mask to cull (skip) unneeded samples. + * Updates buf_idx incrementing with the number of stored samples. + * Samples from HW are transferred into buf, then in-place copy on buf is + * performed in order to cull samples that need to be skipped. + * This avoids copies of the first samples until we hit the 1st sample to skip, + * and also avoids having an extra bounce buffer. + * buf must be able to contain len elements in spite of how many samples we are + * going to cull. + */ +static int bno055_scan_xfer(struct bno055_priv *priv, + int start_ch, int len, unsigned long mask, + __le16 *buf, int *buf_idx) +{ + const int base = BNO055_ACC_DATA_X_LSB_REG; + bool quat_in_read = false; + int buf_base = *buf_idx; + __le16 *dst, *src; + int offs_fixup = 0; + int xfer_len = len; + int ret; + int i, n; + + /* + * All chans are made up 1 16-bit sample, except for quaternion that is + * made up 4 16-bit values. + * For us the quaternion CH is just like 4 regular CHs. + * If our read starts past the quaternion make sure to adjust the + * starting offset; if the quaternion is contained in our scan then make + * sure to adjust the read len. + */ + if (start_ch > BNO055_SCAN_QUATERNION) { + start_ch += 3; + } else if ((start_ch <= BNO055_SCAN_QUATERNION) && + ((start_ch + len) > BNO055_SCAN_QUATERNION)) { + quat_in_read = true; + xfer_len += 3; + } + + ret = regmap_bulk_read(priv->regmap, + base + start_ch * sizeof(__le16), + buf + buf_base, + xfer_len * sizeof(__le16)); + if (ret) + return ret; + + for_each_set_bit(i, &mask, len) { + if (quat_in_read && ((start_ch + i) > BNO055_SCAN_QUATERNION)) + offs_fixup = 3; + + dst = buf + *buf_idx; + src = buf + buf_base + offs_fixup + i; + + n = (start_ch + i == BNO055_SCAN_QUATERNION) ? 4 : 1; + + if (dst != src) + memcpy(dst, src, n * sizeof(__le16)); + + *buf_idx += n; + } + return 0; +} + +static irqreturn_t bno055_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio_dev = pf->indio_dev; + struct bno055_priv *priv = iio_priv(iio_dev); + int xfer_start, start, end, prev_end; + bool xfer_pending = false; + bool first = true; + unsigned long mask; + int buf_idx = 0; + bool thr_hit; + int quat; + int ret; + + mutex_lock(&priv->lock); + for_each_set_bitrange(start, end, iio_dev->active_scan_mask, + iio_dev->masklength) { + if (!xfer_pending) + xfer_start = start; + xfer_pending = true; + + if (!first) { + quat = ((start > BNO055_SCAN_QUATERNION) && + (prev_end <= BNO055_SCAN_QUATERNION)) ? 3 : 0; + thr_hit = (start - prev_end + quat) > + priv->xfer_burst_break_thr; + + if (thr_hit) { + mask = *iio_dev->active_scan_mask >> xfer_start; + ret = bno055_scan_xfer(priv, xfer_start, + prev_end - xfer_start + 1, + mask, priv->buf.chans, &buf_idx); + if (ret) + goto done; + xfer_pending = false; + } + first = false; + } + prev_end = end; + } + + if (xfer_pending) { + mask = *iio_dev->active_scan_mask >> xfer_start; + ret = bno055_scan_xfer(priv, xfer_start, + end - xfer_start + 1, + mask, priv->buf.chans, &buf_idx); + } + + iio_push_to_buffers_with_timestamp(iio_dev, &priv->buf, pf->timestamp); +done: + mutex_unlock(&priv->lock); + iio_trigger_notify_done(iio_dev->trig); + return IRQ_HANDLED; +} + +int bno055_probe(struct device *dev, struct regmap *regmap, + int xfer_burst_break_thr) +{ + const struct firmware *caldata; + struct bno055_priv *priv; + struct iio_dev *iio_dev; + struct gpio_desc *rst; + char *fw_name_buf; + unsigned int val; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!iio_dev) + return -ENOMEM; + + iio_dev->name = "bno055"; + priv = iio_priv(iio_dev); + mutex_init(&priv->lock); + priv->regmap = regmap; + priv->dev = dev; + priv->xfer_burst_break_thr = xfer_burst_break_thr; + rst = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset GPIO"); + + priv->clk = devm_clk_get_optional(dev, "clk"); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get CLK"); + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, bno055_clk_disable, priv); + if (ret) + return ret; + + if (rst) { + usleep_range(5000, 10000); + gpiod_set_value_cansleep(rst, 0); + usleep_range(650000, 750000); + } + + ret = regmap_read(priv->regmap, BNO055_CHIP_ID_REG, &val); + if (ret) + return ret; + + if (val != BNO055_CHIP_ID_MAGIC) { + dev_err(dev, "Unrecognized chip ID 0x%x", val); + return -ENODEV; + } + + ret = regmap_bulk_read(priv->regmap, BNO055_UID_LOWER_REG, + priv->uid, BNO055_UID_LEN); + if (ret) + return ret; + + /* + * This has nothing to do with the IMU firmware, this is for sensor + * calibration data. + */ + fw_name_buf = devm_kasprintf(dev, GFP_KERNEL, + BNO055_FW_UID_NAME, + BNO055_UID_LEN, priv->uid); + if (!fw_name_buf) + return -ENOMEM; + + ret = request_firmware(&caldata, fw_name_buf, dev); + devm_kfree(dev, fw_name_buf); + if (ret) + ret = request_firmware(&caldata, BNO055_FW_GENERIC_NAME, dev); + + if (ret) { + dev_notice(dev, "Failed to load calibration data firmware file; this has nothing to do with IMU main firmware.\nYou can calibrate your IMU (look for 'in_autocalibration_status*' files in sysfs) and then copy 'in_calibration_data' to your firmware file"); + caldata = NULL; + } + + ret = bno055_init(priv, caldata); + if (caldata) + release_firmware(caldata); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, bno055_uninit, priv); + if (ret) + return ret; + + iio_dev->channels = bno055_channels; + iio_dev->num_channels = ARRAY_SIZE(bno055_channels); + iio_dev->info = &bno055_info; + iio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_triggered_buffer_setup(dev, iio_dev, + iio_pollfunc_store_time, + bno055_trigger_handler, NULL); + if (ret) + return ret; + + ret = devm_iio_device_register(dev, iio_dev); + if (ret) + return ret; + + bno055_debugfs_init(iio_dev); + + return 0; +} +EXPORT_SYMBOL_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..7ad8da1ffbf0 --- /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/regmap.h> + +struct device; +int bno055_probe(struct device *dev, struct regmap *regmap, + int xfer_burst_break_thr); +extern const struct regmap_config bno055_regmap_config; + +#endif -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 07/10] iio: imu: add Bosch Sensortec BNO055 core driver 2021-10-28 10:18 ` [v2 07/10] iio: imu: add Bosch Sensortec BNO055 core driver Andrea Merello @ 2021-10-28 13:31 ` Jonathan Cameron 2021-11-09 11:52 ` Andrea Merello 0 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-10-28 13:31 UTC (permalink / raw) To: Andrea Merello Cc: mchehab+huawei, linux-iio, linux-kernel, devicetree, lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello On Thu, 28 Oct 2021 12:18:37 +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, 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> > --- > drivers/iio/imu/Kconfig | 1 + > drivers/iio/imu/Makefile | 1 + > drivers/iio/imu/bno055/Kconfig | 4 + > drivers/iio/imu/bno055/Makefile | 3 + > drivers/iio/imu/bno055/bno055.c | 1480 +++++++++++++++++++++++++++++++ > drivers/iio/imu/bno055/bno055.h | 12 + > 6 files changed, 1501 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/bno055/bno055.c b/drivers/iio/imu/bno055/bno055.c > new file mode 100644 > index 000000000000..c85cb985f0f1 > --- /dev/null > +++ b/drivers/iio/imu/bno055/bno055.c > @@ -0,0 +1,1480 @@ ... > + > +static int bno055_reg_update_bits(struct bno055_priv *priv, unsigned int reg, > + unsigned int mask, unsigned int val) > +{ > + int ret; > + > + ret = regmap_update_bits(priv->regmap, reg, mask, val); > + if (ret && ret != -ERESTARTSYS) { > + dev_err(priv->dev, "Regmap update_bits error. adr: 0x%x, ret: %d", > + reg, ret); This feels like a wrapper that made sense when developing but probably doesn't want to still be here now things are 'working'. > + } > + > + return ret; > +} > + ... > + > +static void bno055_clk_disable(void *arg) Easy to make arg == priv->clk and turn this into a one line function. I'd expect these cleanup functions to be just above where probe() is defined rather than all the way up here. > +{ > + struct bno055_priv *priv = arg; > + > + clk_disable_unprepare(priv->clk); > +} > + ... > + > +static int bno055_get_acc_lpf(struct bno055_priv *priv, int *val, int *val2) > +{ > + const int shift = __ffs(BNO055_ACC_CONFIG_LPF_MASK); > + int hwval, idx; > + int ret; > + > + ret = regmap_read(priv->regmap, BNO055_ACC_CONFIG_REG, &hwval); > + if (ret) > + return ret; > + > + idx = (hwval & BNO055_ACC_CONFIG_LPF_MASK) >> shift; Use FIELD_GET() and FIELD_PREP where possible rather than reinventing them. > + *val = bno055_acc_lpf_vals[idx * 2]; > + *val2 = bno055_acc_lpf_vals[idx * 2 + 1]; > + > + return IIO_VAL_INT_PLUS_MICRO; > +} > + > +static int bno055_set_acc_lpf(struct bno055_priv *priv, int val, int val2) > +{ > + const int shift = __ffs(BNO055_ACC_CONFIG_LPF_MASK); > + int req_val = val * 1000 + val2 / 1000; > + bool first = true; > + int best_delta; > + int best_idx; > + int tbl_val; > + int delta; > + int ret; > + int i; > + > + for (i = 0; i < ARRAY_SIZE(bno055_acc_lpf_vals) / 2; i++) { > + tbl_val = bno055_acc_lpf_vals[i * 2] * 1000 + > + bno055_acc_lpf_vals[i * 2 + 1] / 1000; > + delta = abs(tbl_val - req_val); > + if (first || delta < best_delta) { > + best_delta = delta; > + best_idx = i; > + first = false; > + } > + } > + > + /* > + * 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; Can we do this before the big loop above? > + > + ret = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, > + BNO055_OPR_MODE_CONFIG); > + if (ret) > + return ret; > + > + ret = bno055_reg_update_bits(priv, BNO055_ACC_CONFIG_REG, > + BNO055_ACC_CONFIG_LPF_MASK, > + best_idx << shift); > + > + if (ret) > + return ret; > + > + return regmap_write(priv->regmap, BNO055_OPR_MODE_REG, > + BNO055_OPR_MODE_AMG); > +} > + ... > + > +#define bno055_get_mag_odr(p, v) \ > + bno055_get_regmask(p, v, \ > + BNO055_MAG_CONFIG_REG, BNO055_MAG_CONFIG_ODR_MASK, \ > + bno055_mag_odr_vals) I'm not really convinced this is a worthwhile abstraction as these are typically only used once. > + ... > +static int bno055_read_simple_chan(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int *val, int *val2, long mask) > +{ > + struct bno055_priv *priv = iio_priv(indio_dev); > + __le16 raw_val; > + int ret; > + > + switch (mask) { > + case IIO_CHAN_INFO_RAW: > + ret = regmap_bulk_read(priv->regmap, chan->address, > + &raw_val, sizeof(raw_val)); > + if (ret < 0) > + return ret; > + *val = (s16)le16_to_cpu(raw_val); > + return IIO_VAL_INT; > + case IIO_CHAN_INFO_OFFSET: > + if (priv->operation_mode != BNO055_OPR_MODE_AMG) { > + *val = 0; > + } else { > + ret = regmap_bulk_read(priv->regmap, > + chan->address + > + BNO055_REG_OFFSET_ADDR, > + &raw_val, sizeof(raw_val)); > + if (ret < 0) > + return ret; > + /* > + * IMU reports sensor offests; IIO wants correction > + * offset, thus we need the 'minus' here. > + */ > + *val = -(s16)le16_to_cpu(raw_val); > + } > + 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; default in the middle is a bit unusual. move it to the end. > + > + case IIO_CHAN_INFO_SAMP_FREQ: > + if (chan->type != IIO_MAGN) > + return -EINVAL; > + else > + return bno055_get_mag_odr(priv, val); > + > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: > + switch (chan->type) { > + case IIO_ANGL_VEL: > + return bno055_get_gyr_lpf(priv, val); > + case IIO_ACCEL: > + return bno055_get_acc_lpf(priv, val, val2); > + default: > + return -EINVAL; > + } > + } > +} > + > + > +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; IIO_VAL_FRACTIONAL_LOG2? > + return IIO_VAL_FRACTIONAL; > + default: > + return -EINVAL; > + } > +} > + ... > + > +static ssize_t bno055_fusion_enable_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); > + int ret = 0; > + > + if (sysfs_streq(buf, "0")) { > + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_AMG); > + } else { > + /* > + * Coming from AMG means the FMC was off, just switch to fusion > + * but don't change anything that doesn't belong to us (i.e let. > + * FMC stay off. > + * Coming from any other fusion mode means we don't need to do > + * anything. > + */ > + if (priv->operation_mode == BNO055_OPR_MODE_AMG) > + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF); > + } > + > + return len ?: len; return ret?: len; might make sense, though my inclination would be to use an explicit if (ret) at the various possible error locations. > +} ... > +static ssize_t bno055_fmc_enable_store(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); > + int ret = 0; > + > + if (sysfs_streq(buf, "0")) { > + if (priv->operation_mode == BNO055_OPR_MODE_FUSION) > + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF); > + } else { > + if (priv->operation_mode == BNO055_OPR_MODE_AMG) > + return -EINVAL; > + } > + > + return len ?: ret; Don't think that will return ret which is what we want if it's set. > +} > + ... > +static ssize_t in_calibration_data_show(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); > + u8 data[BNO055_CALDATA_LEN]; > + int ret; > + > + mutex_lock(&priv->lock); > + ret = regmap_write(priv->regmap, 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 = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, priv->operation_mode); > + mutex_unlock(&priv->lock); > + if (ret) > + return ret; This is a case where I'd move the mutex_unlock after the check so that we have a nice shared error path via the unlock lable. > + > + memcpy(buf, data, BNO055_CALDATA_LEN); > + > + return BNO055_CALDATA_LEN; > +unlock: > + mutex_unlock(&priv->lock); > + return ret; > +} > + ... > +static ssize_t bno055_show_fw_version(struct file *file, char __user *userbuf, > + size_t count, loff_t *ppos) > +{ > + struct bno055_priv *priv = file->private_data; > + int rev, ver; > + char *buf; > + int ret; > + > + ret = regmap_read(priv->regmap, BNO055_SW_REV_LSB_REG, &rev); > + if (ret) > + return ret; > + > + ret = regmap_read(priv->regmap, BNO055_SW_REV_MSB_REG, &ver); > + if (ret) > + return ret; > + > + buf = devm_kasprintf(priv->dev, GFP_KERNEL, "ver: 0x%x, rev: 0x%x\n", > + ver, rev); > + if (!buf) > + return -ENOMEM; > + > + ret = simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf)); > + devm_kfree(priv->dev, buf); Why use devm managed allocations if you are just going to free it immediately? > + > + return ret; > +} > + ... > +/* > + * Reads len samples from the HW, stores them in buf starting from buf_idx, > + * and applies mask to cull (skip) unneeded samples. > + * Updates buf_idx incrementing with the number of stored samples. > + * Samples from HW are transferred into buf, then in-place copy on buf is > + * performed in order to cull samples that need to be skipped. > + * This avoids copies of the first samples until we hit the 1st sample to skip, > + * and also avoids having an extra bounce buffer. > + * buf must be able to contain len elements in spite of how many samples we are > + * going to cull. This is rather complex - I take we can't just fall back to letting the IIO core demux do all the hard work for us? > + */ > +static int bno055_scan_xfer(struct bno055_priv *priv, > + int start_ch, int len, unsigned long mask, > + __le16 *buf, int *buf_idx) > +{ > + const int base = BNO055_ACC_DATA_X_LSB_REG; > + bool quat_in_read = false; > + int buf_base = *buf_idx; > + __le16 *dst, *src; > + int offs_fixup = 0; > + int xfer_len = len; > + int ret; > + int i, n; > + > + /* > + * All chans are made up 1 16-bit sample, except for quaternion that is > + * made up 4 16-bit values. > + * For us the quaternion CH is just like 4 regular CHs. > + * If our read starts past the quaternion make sure to adjust the > + * starting offset; if the quaternion is contained in our scan then make > + * sure to adjust the read len. > + */ > + if (start_ch > BNO055_SCAN_QUATERNION) { > + start_ch += 3; > + } else if ((start_ch <= BNO055_SCAN_QUATERNION) && > + ((start_ch + len) > BNO055_SCAN_QUATERNION)) { > + quat_in_read = true; > + xfer_len += 3; > + } > + > + ret = regmap_bulk_read(priv->regmap, > + base + start_ch * sizeof(__le16), > + buf + buf_base, > + xfer_len * sizeof(__le16)); > + if (ret) > + return ret; > + > + for_each_set_bit(i, &mask, len) { > + if (quat_in_read && ((start_ch + i) > BNO055_SCAN_QUATERNION)) > + offs_fixup = 3; > + > + dst = buf + *buf_idx; > + src = buf + buf_base + offs_fixup + i; > + > + n = (start_ch + i == BNO055_SCAN_QUATERNION) ? 4 : 1; > + > + if (dst != src) > + memcpy(dst, src, n * sizeof(__le16)); > + > + *buf_idx += n; > + } > + return 0; > +} > + > +static irqreturn_t bno055_trigger_handler(int irq, void *p) > +{ > + struct iio_poll_func *pf = p; > + struct iio_dev *iio_dev = pf->indio_dev; > + struct bno055_priv *priv = iio_priv(iio_dev); > + int xfer_start, start, end, prev_end; > + bool xfer_pending = false; > + bool first = true; > + unsigned long mask; > + int buf_idx = 0; > + bool thr_hit; > + int quat; > + int ret; > + > + mutex_lock(&priv->lock); > + for_each_set_bitrange(start, end, iio_dev->active_scan_mask, > + iio_dev->masklength) { I'm not seeing this function in mainline... I guess this series has a dependency I missed? > + if (!xfer_pending) > + xfer_start = start; > + xfer_pending = true; > + > + if (!first) { first == true and we never get in here to set it to false. Perhaps we would benefit from a state machine diagram for this function? In general this function is complex enough to need documentation of what each major part is doing. > + quat = ((start > BNO055_SCAN_QUATERNION) && > + (prev_end <= BNO055_SCAN_QUATERNION)) ? 3 : 0; Having quat == 3 for a variable named quat doesn't seem intuitive. > + thr_hit = (start - prev_end + quat) > > + priv->xfer_burst_break_thr; > + > + if (thr_hit) { > + mask = *iio_dev->active_scan_mask >> xfer_start; > + ret = bno055_scan_xfer(priv, xfer_start, > + prev_end - xfer_start + 1, > + mask, priv->buf.chans, &buf_idx); > + if (ret) > + goto done; > + xfer_pending = false; > + } > + first = false; > + } > + prev_end = end; > + } > + > + if (xfer_pending) { > + mask = *iio_dev->active_scan_mask >> xfer_start; > + ret = bno055_scan_xfer(priv, xfer_start, > + end - xfer_start + 1, > + mask, priv->buf.chans, &buf_idx); > + } > + > + iio_push_to_buffers_with_timestamp(iio_dev, &priv->buf, pf->timestamp); > +done: > + mutex_unlock(&priv->lock); > + iio_trigger_notify_done(iio_dev->trig); > + return IRQ_HANDLED; > +} > + > +int bno055_probe(struct device *dev, struct regmap *regmap, > + int xfer_burst_break_thr) > +{ > + const struct firmware *caldata; > + struct bno055_priv *priv; > + struct iio_dev *iio_dev; > + struct gpio_desc *rst; > + char *fw_name_buf; > + unsigned int val; > + int ret; > + > + iio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); > + if (!iio_dev) > + return -ENOMEM; > + > + iio_dev->name = "bno055"; > + priv = iio_priv(iio_dev); > + mutex_init(&priv->lock); > + priv->regmap = regmap; > + priv->dev = dev; > + priv->xfer_burst_break_thr = xfer_burst_break_thr; blank line here would hep readability a little I think. > + rst = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); > + if (IS_ERR(rst)) > + return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset GPIO"); > + > + priv->clk = devm_clk_get_optional(dev, "clk"); > + if (IS_ERR(priv->clk)) > + return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get CLK"); > + > + ret = clk_prepare_enable(priv->clk); > + if (ret) > + return ret; > + > + ret = devm_add_action_or_reset(dev, bno055_clk_disable, priv); As mentioned above, pass priv->clk into this as we don't need to see anything else in the callback. > + if (ret) > + return ret; > + > + if (rst) { > + usleep_range(5000, 10000); > + gpiod_set_value_cansleep(rst, 0); > + usleep_range(650000, 750000); > + } > + > + ret = regmap_read(priv->regmap, BNO055_CHIP_ID_REG, &val); > + if (ret) > + return ret; > + > + if (val != BNO055_CHIP_ID_MAGIC) { > + dev_err(dev, "Unrecognized chip ID 0x%x", val); > + return -ENODEV; > + } > + > + ret = regmap_bulk_read(priv->regmap, BNO055_UID_LOWER_REG, > + priv->uid, BNO055_UID_LEN); > + if (ret) > + return ret; > + > + /* > + * This has nothing to do with the IMU firmware, this is for sensor > + * calibration data. > + */ > + fw_name_buf = devm_kasprintf(dev, GFP_KERNEL, > + BNO055_FW_UID_NAME, > + BNO055_UID_LEN, priv->uid); > + if (!fw_name_buf) > + return -ENOMEM; > + > + ret = request_firmware(&caldata, fw_name_buf, dev); > + devm_kfree(dev, fw_name_buf); > + if (ret) > + ret = request_firmware(&caldata, BNO055_FW_GENERIC_NAME, dev); > + > + if (ret) { > + dev_notice(dev, "Failed to load calibration data firmware file; this has nothing to do with IMU main firmware.\nYou can calibrate your IMU (look for 'in_autocalibration_status*' files in sysfs) and then copy 'in_calibration_data' to your firmware file"); As the notice has multiple lines, you can break at the \n points without any loss of greppability. It would be good to make this shorter though if we can - I wouldn't way what it isn't for example. Calibration file load failed. Follow instructions in Documentation/ * + write some docs on the calibration procedure. I don't think it is a good plan to give a guide in a kernel log. > + caldata = NULL; I'd hope that is already the case and it definitely looks like it is from a quick look at request_firmware(). I'd consider request_firmware buggy if it did anything else as that would imply it had side effects that weren't cleaned up on error. > + } > + > + ret = bno055_init(priv, caldata); > + if (caldata) > + release_firmware(caldata); > + if (ret) > + return ret; > + > + ret = devm_add_action_or_reset(dev, bno055_uninit, priv); > + if (ret) > + return ret; > + > + iio_dev->channels = bno055_channels; > + iio_dev->num_channels = ARRAY_SIZE(bno055_channels); > + iio_dev->info = &bno055_info; > + iio_dev->modes = INDIO_DIRECT_MODE; > + > + ret = devm_iio_triggered_buffer_setup(dev, iio_dev, > + iio_pollfunc_store_time, > + bno055_trigger_handler, NULL); > + if (ret) > + return ret; > + > + ret = devm_iio_device_register(dev, iio_dev); > + if (ret) > + return ret; > + > + bno055_debugfs_init(iio_dev); > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(bno055_probe); > + ... Thanks, Jonathan ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 07/10] iio: imu: add Bosch Sensortec BNO055 core driver 2021-10-28 13:31 ` Jonathan Cameron @ 2021-11-09 11:52 ` Andrea Merello 2021-11-14 16:33 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-11-09 11:52 UTC (permalink / raw) To: Jonathan Cameron Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello Some inline notes. OK for all the rest. Il giorno gio 28 ott 2021 alle ore 15:27 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Thu, 28 Oct 2021 12:18:37 +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, 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> > > --- > > drivers/iio/imu/Kconfig | 1 + > > drivers/iio/imu/Makefile | 1 + > > drivers/iio/imu/bno055/Kconfig | 4 + > > drivers/iio/imu/bno055/Makefile | 3 + > > drivers/iio/imu/bno055/bno055.c | 1480 +++++++++++++++++++++++++++++++ > > drivers/iio/imu/bno055/bno055.h | 12 + > > 6 files changed, 1501 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/bno055/bno055.c b/drivers/iio/imu/bno055/bno055.c > > new file mode 100644 > > index 000000000000..c85cb985f0f1 > > --- /dev/null > > +++ b/drivers/iio/imu/bno055/bno055.c > > @@ -0,0 +1,1480 @@ > > ... > > > + > > +static int bno055_reg_update_bits(struct bno055_priv *priv, unsigned int reg, > > + unsigned int mask, unsigned int val) > > +{ > > + int ret; > > + > > + ret = regmap_update_bits(priv->regmap, reg, mask, val); > > + if (ret && ret != -ERESTARTSYS) { > > + dev_err(priv->dev, "Regmap update_bits error. adr: 0x%x, ret: %d", > > + reg, ret); > > This feels like a wrapper that made sense when developing but probably doesn't > want to still be here now things are 'working'. > > > + } > > + > > + return ret; > > +} > > + > > ... > > > + > > +static void bno055_clk_disable(void *arg) > > Easy to make arg == priv->clk and turn this into a one line function. > I'd expect these cleanup functions to be just above where probe() is defined rather > than all the way up here. > > > +{ > > + struct bno055_priv *priv = arg; > > + > > + clk_disable_unprepare(priv->clk); > > +} > > + > > ... > > > + > > +static int bno055_get_acc_lpf(struct bno055_priv *priv, int *val, int *val2) > > +{ > > + const int shift = __ffs(BNO055_ACC_CONFIG_LPF_MASK); > > + int hwval, idx; > > + int ret; > > + > > + ret = regmap_read(priv->regmap, BNO055_ACC_CONFIG_REG, &hwval); > > + if (ret) > > + return ret; > > + > > + idx = (hwval & BNO055_ACC_CONFIG_LPF_MASK) >> shift; > > Use FIELD_GET() and FIELD_PREP where possible rather than reinventing them. > > > + *val = bno055_acc_lpf_vals[idx * 2]; > > + *val2 = bno055_acc_lpf_vals[idx * 2 + 1]; > > + > > + return IIO_VAL_INT_PLUS_MICRO; > > +} > > + > > +static int bno055_set_acc_lpf(struct bno055_priv *priv, int val, int val2) > > +{ > > + const int shift = __ffs(BNO055_ACC_CONFIG_LPF_MASK); > > + int req_val = val * 1000 + val2 / 1000; > > + bool first = true; > > + int best_delta; > > + int best_idx; > > + int tbl_val; > > + int delta; > > + int ret; > > + int i; > > + > > + for (i = 0; i < ARRAY_SIZE(bno055_acc_lpf_vals) / 2; i++) { > > + tbl_val = bno055_acc_lpf_vals[i * 2] * 1000 + > > + bno055_acc_lpf_vals[i * 2 + 1] / 1000; > > + delta = abs(tbl_val - req_val); > > + if (first || delta < best_delta) { > > + best_delta = delta; > > + best_idx = i; > > + first = false; > > + } > > + } > > + > > + /* > > + * 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; > > Can we do this before the big loop above? > > > > + > > + ret = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, > > + BNO055_OPR_MODE_CONFIG); > > + if (ret) > > + return ret; > > + > > + ret = bno055_reg_update_bits(priv, BNO055_ACC_CONFIG_REG, > > + BNO055_ACC_CONFIG_LPF_MASK, > > + best_idx << shift); > > + > > + if (ret) > > + return ret; > > + > > + return regmap_write(priv->regmap, BNO055_OPR_MODE_REG, > > + BNO055_OPR_MODE_AMG); > > +} > > + > > ... > > > + > > +#define bno055_get_mag_odr(p, v) \ > > + bno055_get_regmask(p, v, \ > > + BNO055_MAG_CONFIG_REG, BNO055_MAG_CONFIG_ODR_MASK, \ > > + bno055_mag_odr_vals) > > I'm not really convinced this is a worthwhile abstraction as these are typically > only used once. > > > + > ... > > > +static int bno055_read_simple_chan(struct iio_dev *indio_dev, > > + struct iio_chan_spec const *chan, > > + int *val, int *val2, long mask) > > +{ > > + struct bno055_priv *priv = iio_priv(indio_dev); > > + __le16 raw_val; > > + int ret; > > + > > + switch (mask) { > > + case IIO_CHAN_INFO_RAW: > > + ret = regmap_bulk_read(priv->regmap, chan->address, > > + &raw_val, sizeof(raw_val)); > > + if (ret < 0) > > + return ret; > > + *val = (s16)le16_to_cpu(raw_val); > > + return IIO_VAL_INT; > > + case IIO_CHAN_INFO_OFFSET: > > + if (priv->operation_mode != BNO055_OPR_MODE_AMG) { > > + *val = 0; > > + } else { > > + ret = regmap_bulk_read(priv->regmap, > > + chan->address + > > + BNO055_REG_OFFSET_ADDR, > > + &raw_val, sizeof(raw_val)); > > + if (ret < 0) > > + return ret; > > + /* > > + * IMU reports sensor offests; IIO wants correction > > + * offset, thus we need the 'minus' here. > > + */ > > + *val = -(s16)le16_to_cpu(raw_val); > > + } > > + 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; > > default in the middle is a bit unusual. move it to the end. > > > + > > + case IIO_CHAN_INFO_SAMP_FREQ: > > + if (chan->type != IIO_MAGN) > > + return -EINVAL; > > + else > > + return bno055_get_mag_odr(priv, val); > > + > > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: > > + switch (chan->type) { > > + case IIO_ANGL_VEL: > > + return bno055_get_gyr_lpf(priv, val); > > + case IIO_ACCEL: > > + return bno055_get_acc_lpf(priv, val, val2); > > + default: > > + return -EINVAL; > > + } > > + } > > +} > > + > > > > + > > +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; > > IIO_VAL_FRACTIONAL_LOG2? > > > + return IIO_VAL_FRACTIONAL; > > + default: > > + return -EINVAL; > > + } > > +} > > + > > ... > > > + > > +static ssize_t bno055_fusion_enable_store(struct device *dev, > > + struct device_attribute *attr, > > + const char *buf, size_t len) > > +{ > > + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); > > + int ret = 0; > > + > > + if (sysfs_streq(buf, "0")) { > > + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_AMG); > > + } else { > > + /* > > + * Coming from AMG means the FMC was off, just switch to fusion > > + * but don't change anything that doesn't belong to us (i.e let. > > + * FMC stay off. > > + * Coming from any other fusion mode means we don't need to do > > + * anything. > > + */ > > + if (priv->operation_mode == BNO055_OPR_MODE_AMG) > > + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF); > > + } > > + > > + return len ?: len; > > return ret?: len; might make sense, though my inclination would be to use an explicit > if (ret) at the various possible error locations. > > > +} > > ... > > > +static ssize_t bno055_fmc_enable_store(struct device *dev, > > + struct device_attribute *attr, > > + const char *buf, size_t len) > > +{ > > + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); > > + int ret = 0; > > + > > + if (sysfs_streq(buf, "0")) { > > + if (priv->operation_mode == BNO055_OPR_MODE_FUSION) > > + ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF); > > + } else { > > + if (priv->operation_mode == BNO055_OPR_MODE_AMG) > > + return -EINVAL; > > + } > > + > > + return len ?: ret; > > Don't think that will return ret which is what we want if it's set. > > > +} > > + > > ... > > > +static ssize_t in_calibration_data_show(struct device *dev, > > + struct device_attribute *attr, > > + char *buf) > > +{ > > + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); > > + u8 data[BNO055_CALDATA_LEN]; > > + int ret; > > + > > + mutex_lock(&priv->lock); > > + ret = regmap_write(priv->regmap, 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 = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, priv->operation_mode); > > + mutex_unlock(&priv->lock); > > + if (ret) > > + return ret; > > This is a case where I'd move the mutex_unlock after the check so that we have > a nice shared error path via the unlock lable. > > > + > > + memcpy(buf, data, BNO055_CALDATA_LEN); > > + > > + return BNO055_CALDATA_LEN; > > +unlock: > > + mutex_unlock(&priv->lock); > > + return ret; > > +} > > + > ... > > > +static ssize_t bno055_show_fw_version(struct file *file, char __user *userbuf, > > + size_t count, loff_t *ppos) > > +{ > > + struct bno055_priv *priv = file->private_data; > > + int rev, ver; > > + char *buf; > > + int ret; > > + > > + ret = regmap_read(priv->regmap, BNO055_SW_REV_LSB_REG, &rev); > > + if (ret) > > + return ret; > > + > > + ret = regmap_read(priv->regmap, BNO055_SW_REV_MSB_REG, &ver); > > + if (ret) > > + return ret; > > + > > + buf = devm_kasprintf(priv->dev, GFP_KERNEL, "ver: 0x%x, rev: 0x%x\n", > > + ver, rev); > > + if (!buf) > > + return -ENOMEM; > > + > > + ret = simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf)); > > + devm_kfree(priv->dev, buf); > > Why use devm managed allocations if you are just going to free it immediately? > > > + > > + return ret; > > +} > > + > > ... > > > +/* > > + * Reads len samples from the HW, stores them in buf starting from buf_idx, > > + * and applies mask to cull (skip) unneeded samples. > > + * Updates buf_idx incrementing with the number of stored samples. > > + * Samples from HW are transferred into buf, then in-place copy on buf is > > + * performed in order to cull samples that need to be skipped. > > + * This avoids copies of the first samples until we hit the 1st sample to skip, > > + * and also avoids having an extra bounce buffer. > > + * buf must be able to contain len elements in spite of how many samples we are > > + * going to cull. > > This is rather complex - I take we can't just fall back to letting the IIO core > demux do all the hard work for us? Hum. I'm not sure.. I admit that I'm not familiar with the demux thing, but as far as I can see it needs to be initialized once with a list containing all allowed scan masks; IIO core will pick one of them and eventually cull extra samples it contains. Is this right? I would say we may precalculate this list at probe time (depending on the burst break threshold) and populate it with all the possible scan masks in which there are no gaps < than the bust break threshold. But this could be a quite high number of combinations.. This way the IIO layer will only request xfers in which gaps are always > than burst break threshold, which the driver in turn will always split in several xfers. Does this make sense to you? > > + */ > > +static int bno055_scan_xfer(struct bno055_priv *priv, > > + int start_ch, int len, unsigned long mask, > > + __le16 *buf, int *buf_idx) > > +{ > > + const int base = BNO055_ACC_DATA_X_LSB_REG; > > + bool quat_in_read = false; > > + int buf_base = *buf_idx; > > + __le16 *dst, *src; > > + int offs_fixup = 0; > > + int xfer_len = len; > > + int ret; > > + int i, n; > > + > > + /* > > + * All chans are made up 1 16-bit sample, except for quaternion that is > > + * made up 4 16-bit values. > > + * For us the quaternion CH is just like 4 regular CHs. > > + * If our read starts past the quaternion make sure to adjust the > > + * starting offset; if the quaternion is contained in our scan then make > > + * sure to adjust the read len. > > + */ > > + if (start_ch > BNO055_SCAN_QUATERNION) { > > + start_ch += 3; > > + } else if ((start_ch <= BNO055_SCAN_QUATERNION) && > > + ((start_ch + len) > BNO055_SCAN_QUATERNION)) { > > + quat_in_read = true; > > + xfer_len += 3; > > + } > > + > > + ret = regmap_bulk_read(priv->regmap, > > + base + start_ch * sizeof(__le16), > > + buf + buf_base, > > + xfer_len * sizeof(__le16)); > > + if (ret) > > + return ret; > > + > > + for_each_set_bit(i, &mask, len) { > > + if (quat_in_read && ((start_ch + i) > BNO055_SCAN_QUATERNION)) > > + offs_fixup = 3; > > + > > + dst = buf + *buf_idx; > > + src = buf + buf_base + offs_fixup + i; > > + > > + n = (start_ch + i == BNO055_SCAN_QUATERNION) ? 4 : 1; > > + > > + if (dst != src) > > + memcpy(dst, src, n * sizeof(__le16)); > > + > > + *buf_idx += n; > > + } > > + return 0; > > +} > > + > > +static irqreturn_t bno055_trigger_handler(int irq, void *p) > > +{ > > + struct iio_poll_func *pf = p; > > + struct iio_dev *iio_dev = pf->indio_dev; > > + struct bno055_priv *priv = iio_priv(iio_dev); > > + int xfer_start, start, end, prev_end; > > + bool xfer_pending = false; > > + bool first = true; > > + unsigned long mask; > > + int buf_idx = 0; > > + bool thr_hit; > > + int quat; > > + int ret; > > + > > + mutex_lock(&priv->lock); > > + for_each_set_bitrange(start, end, iio_dev->active_scan_mask, > > + iio_dev->masklength) { > > I'm not seeing this function in mainline... I guess this series has a dependency > I missed? I've been pointed to Yuri Norov bitmap series (I mentioned this in the cover letter). Assuming it is close to be merged, I've updated my drv for its API changes, but if you prefer I can revert to the current mainline API. It's a trivial change. > > + if (!xfer_pending) > > + xfer_start = start; > > + xfer_pending = true; > > + > > + if (!first) { > > first == true and we never get in here to set it to false. That's awful. Possibly I've broken this while making changes for V2, and my test program didn't catch it. Maybe it just impacts performances, which, now I realize, I probably didn't rechek :( > Perhaps we would benefit from a state machine diagram for this function? > In general this function is complex enough to need documentation of what > each major part is doing. > > > + quat = ((start > BNO055_SCAN_QUATERNION) && > > + (prev_end <= BNO055_SCAN_QUATERNION)) ? 3 : 0; > > Having quat == 3 for a variable named quat doesn't seem intuitive. > > > + thr_hit = (start - prev_end + quat) > > > + priv->xfer_burst_break_thr; > > + > > + if (thr_hit) { > > + mask = *iio_dev->active_scan_mask >> xfer_start; > > + ret = bno055_scan_xfer(priv, xfer_start, > > + prev_end - xfer_start + 1, > > + mask, priv->buf.chans, &buf_idx); > > + if (ret) > > + goto done; > > + xfer_pending = false; > > + } > > + first = false; > > + } > > + prev_end = end; > > + } > > + > > + if (xfer_pending) { > > + mask = *iio_dev->active_scan_mask >> xfer_start; > > + ret = bno055_scan_xfer(priv, xfer_start, > > + end - xfer_start + 1, > > + mask, priv->buf.chans, &buf_idx); > > + } > > + > > + iio_push_to_buffers_with_timestamp(iio_dev, &priv->buf, pf->timestamp); > > +done: > > + mutex_unlock(&priv->lock); > > + iio_trigger_notify_done(iio_dev->trig); > > + return IRQ_HANDLED; > > +} > > + > > +int bno055_probe(struct device *dev, struct regmap *regmap, > > + int xfer_burst_break_thr) > > +{ > > + const struct firmware *caldata; > > + struct bno055_priv *priv; > > + struct iio_dev *iio_dev; > > + struct gpio_desc *rst; > > + char *fw_name_buf; > > + unsigned int val; > > + int ret; > > + > > + iio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); > > + if (!iio_dev) > > + return -ENOMEM; > > + > > + iio_dev->name = "bno055"; > > + priv = iio_priv(iio_dev); > > + mutex_init(&priv->lock); > > + priv->regmap = regmap; > > + priv->dev = dev; > > + priv->xfer_burst_break_thr = xfer_burst_break_thr; > > blank line here would hep readability a little I think. > > > + rst = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); > > + if (IS_ERR(rst)) > > + return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset GPIO"); > > + > > + priv->clk = devm_clk_get_optional(dev, "clk"); > > + if (IS_ERR(priv->clk)) > > + return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get CLK"); > > + > > + ret = clk_prepare_enable(priv->clk); > > + if (ret) > > + return ret; > > + > > + ret = devm_add_action_or_reset(dev, bno055_clk_disable, priv); > > As mentioned above, pass priv->clk into this as we don't need to see anything > else in the callback. > > > + if (ret) > > + return ret; > > + > > + if (rst) { > > + usleep_range(5000, 10000); > > + gpiod_set_value_cansleep(rst, 0); > > + usleep_range(650000, 750000); > > + } > > + > > + ret = regmap_read(priv->regmap, BNO055_CHIP_ID_REG, &val); > > + if (ret) > > + return ret; > > + > > + if (val != BNO055_CHIP_ID_MAGIC) { > > + dev_err(dev, "Unrecognized chip ID 0x%x", val); > > + return -ENODEV; > > + } > > + > > + ret = regmap_bulk_read(priv->regmap, BNO055_UID_LOWER_REG, > > + priv->uid, BNO055_UID_LEN); > > + if (ret) > > + return ret; > > + > > + /* > > + * This has nothing to do with the IMU firmware, this is for sensor > > + * calibration data. > > + */ > > + fw_name_buf = devm_kasprintf(dev, GFP_KERNEL, > > + BNO055_FW_UID_NAME, > > + BNO055_UID_LEN, priv->uid); > > + if (!fw_name_buf) > > + return -ENOMEM; > > + > > + ret = request_firmware(&caldata, fw_name_buf, dev); > > + devm_kfree(dev, fw_name_buf); > > + if (ret) > > + ret = request_firmware(&caldata, BNO055_FW_GENERIC_NAME, dev); > > + > > + if (ret) { > > + dev_notice(dev, "Failed to load calibration data firmware file; this has nothing to do with IMU main firmware.\nYou can calibrate your IMU (look for 'in_autocalibration_status*' files in sysfs) and then copy 'in_calibration_data' to your firmware file"); > > As the notice has multiple lines, you can break at the \n points without any loss of greppability. > It would be good to make this shorter though if we can - I wouldn't way what it isn't for example. > > Calibration file load failed. > Follow instructions in Documentation/ * > > + write some docs on the calibration procedure. I don't think it is a > good plan to give a guide in a kernel log. > > > + caldata = NULL; > > I'd hope that is already the case and it definitely looks like it is from a quick look > at request_firmware(). I'd consider request_firmware buggy if it did anything else > as that would imply it had side effects that weren't cleaned up on error. > > > + } > > + > > + ret = bno055_init(priv, caldata); > > + if (caldata) > > + release_firmware(caldata); > > + if (ret) > > + return ret; > > + > > + ret = devm_add_action_or_reset(dev, bno055_uninit, priv); > > + if (ret) > > + return ret; > > + > > + iio_dev->channels = bno055_channels; > > + iio_dev->num_channels = ARRAY_SIZE(bno055_channels); > > + iio_dev->info = &bno055_info; > > + iio_dev->modes = INDIO_DIRECT_MODE; > > + > > + ret = devm_iio_triggered_buffer_setup(dev, iio_dev, > > + iio_pollfunc_store_time, > > + bno055_trigger_handler, NULL); > > + if (ret) > > + return ret; > > + > > + ret = devm_iio_device_register(dev, iio_dev); > > + if (ret) > > + return ret; > > + > > + bno055_debugfs_init(iio_dev); > > + > > + return 0; > > +} > > +EXPORT_SYMBOL_GPL(bno055_probe); > > + > ... > > Thanks, > > Jonathan ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 07/10] iio: imu: add Bosch Sensortec BNO055 core driver 2021-11-09 11:52 ` Andrea Merello @ 2021-11-14 16:33 ` Jonathan Cameron 0 siblings, 0 replies; 89+ messages in thread From: Jonathan Cameron @ 2021-11-14 16:33 UTC (permalink / raw) To: Andrea Merello Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello On Tue, 9 Nov 2021 12:52:14 +0100 Andrea Merello <andrea.merello@gmail.com> wrote: > Some inline notes. OK for all the rest. > > Il giorno gio 28 ott 2021 alle ore 15:27 Jonathan Cameron > <jic23@kernel.org> ha scritto: > > > > On Thu, 28 Oct 2021 12:18:37 +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, 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> Side not. Please crop out bits of the discussion we aren't continuing. Makes it easier to find the relevant parts of the email! Note this is a do as I say rather than do as I do as I don't always remember to do this either. ... > > > > > +/* > > > + * Reads len samples from the HW, stores them in buf starting from buf_idx, > > > + * and applies mask to cull (skip) unneeded samples. > > > + * Updates buf_idx incrementing with the number of stored samples. > > > + * Samples from HW are transferred into buf, then in-place copy on buf is > > > + * performed in order to cull samples that need to be skipped. > > > + * This avoids copies of the first samples until we hit the 1st sample to skip, > > > + * and also avoids having an extra bounce buffer. > > > + * buf must be able to contain len elements in spite of how many samples we are > > > + * going to cull. > > > > This is rather complex - I take we can't just fall back to letting the IIO core > > demux do all the hard work for us? > > Hum. I'm not sure.. I admit that I'm not familiar with the demux > thing, but as far as I can see it needs to be initialized once with a > list containing all allowed scan masks; IIO core will pick one of them > and eventually cull extra samples it contains. Is this right? yup - that's pretty much it. > > I would say we may precalculate this list at probe time (depending on > the burst break threshold) and populate it with all the possible scan > masks in which there are no gaps < than the bust break threshold. But > this could be a quite high number of combinations.. > > This way the IIO layer will only request xfers in which gaps are > always > than burst break threshold, which the driver in turn will > always split in several xfers. > > Does this make sense to you? If it works and ends up simpler than this I'm all for it, but I'll confess you've lost me in the explanation. Whether the approach you are using here ends up more efficient than the one in the demux (which IIRC works by doing an expensive copy map building just once and can then use that to do things like merging copies of neighbouring elements) will be dependent on exact combinations of enabled channels. There is also a usecase question for how much effort it is worth putting in to optimise these paths. In a lot of cases people have put an IMU in because their application needs one so will be grabbing almost all channels all the time. It is unlikely they want a random set of scattered channels. > > > > + */ > > > +static int bno055_scan_xfer(struct bno055_priv *priv, > > > + int start_ch, int len, unsigned long mask, > > > + __le16 *buf, int *buf_idx) > > > +{ > > > + const int base = BNO055_ACC_DATA_X_LSB_REG; > > > + bool quat_in_read = false; > > > + int buf_base = *buf_idx; > > > + __le16 *dst, *src; > > > + int offs_fixup = 0; > > > + int xfer_len = len; > > > + int ret; > > > + int i, n; > > > + > > > + /* > > > + * All chans are made up 1 16-bit sample, except for quaternion that is > > > + * made up 4 16-bit values. > > > + * For us the quaternion CH is just like 4 regular CHs. > > > + * If our read starts past the quaternion make sure to adjust the > > > + * starting offset; if the quaternion is contained in our scan then make > > > + * sure to adjust the read len. > > > + */ > > > + if (start_ch > BNO055_SCAN_QUATERNION) { > > > + start_ch += 3; > > > + } else if ((start_ch <= BNO055_SCAN_QUATERNION) && > > > + ((start_ch + len) > BNO055_SCAN_QUATERNION)) { > > > + quat_in_read = true; > > > + xfer_len += 3; > > > + } > > > + > > > + ret = regmap_bulk_read(priv->regmap, > > > + base + start_ch * sizeof(__le16), > > > + buf + buf_base, > > > + xfer_len * sizeof(__le16)); > > > + if (ret) > > > + return ret; > > > + > > > + for_each_set_bit(i, &mask, len) { > > > + if (quat_in_read && ((start_ch + i) > BNO055_SCAN_QUATERNION)) > > > + offs_fixup = 3; > > > + > > > + dst = buf + *buf_idx; > > > + src = buf + buf_base + offs_fixup + i; > > > + > > > + n = (start_ch + i == BNO055_SCAN_QUATERNION) ? 4 : 1; > > > + > > > + if (dst != src) > > > + memcpy(dst, src, n * sizeof(__le16)); > > > + > > > + *buf_idx += n; > > > + } > > > + return 0; > > > +} > > > + > > > +static irqreturn_t bno055_trigger_handler(int irq, void *p) > > > +{ > > > + struct iio_poll_func *pf = p; > > > + struct iio_dev *iio_dev = pf->indio_dev; > > > + struct bno055_priv *priv = iio_priv(iio_dev); > > > + int xfer_start, start, end, prev_end; > > > + bool xfer_pending = false; > > > + bool first = true; > > > + unsigned long mask; > > > + int buf_idx = 0; > > > + bool thr_hit; > > > + int quat; > > > + int ret; > > > + > > > + mutex_lock(&priv->lock); > > > + for_each_set_bitrange(start, end, iio_dev->active_scan_mask, > > > + iio_dev->masklength) { > > > > I'm not seeing this function in mainline... I guess this series has a dependency > > I missed? > > I've been pointed to Yuri Norov bitmap series (I mentioned this in the > cover letter). Assuming it is close to be merged, I've updated my drv > for its API changes, but if you prefer I can revert to the current > mainline API. It's a trivial change. Ah. Thanks for pointing that out. I missed the note in the cover letter. Jonathan ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 08/10] dt-bindings: iio: imu: add documentation for Bosch BNO055 bindings 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello ` (6 preceding siblings ...) 2021-10-28 10:18 ` [v2 07/10] iio: imu: add Bosch Sensortec BNO055 core driver Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 12:25 ` Rob Herring 2021-10-28 10:18 ` [v2 09/10] iio: imu: add BNO055 serdev driver Andrea Merello ` (2 subsequent siblings) 10 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello, Andrea Merello Introduce new documentation file for the Bosch BNO055 IMU Signed-off-by: Andrea Merello <andrea.merello@iit.it> --- .../bindings/iio/imu/bosch,bno055.yaml | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/imu/bosch,bno055.yaml diff --git a/Documentation/devicetree/bindings/iio/imu/bosch,bno055.yaml b/Documentation/devicetree/bindings/iio/imu/bosch,bno055.yaml new file mode 100644 index 000000000000..0c0141162d63 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/imu/bosch,bno055.yaml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/imu/bosch,bno055-serial.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Bosch BNO055 + +maintainers: + - Andrea Merello <andrea.merello@iit.it> + +description: | + Inertial Measurement Unit with Accelerometer, Gyroscope, Magnetometer and + internal MCU for sensor fusion + https://www.bosch-sensortec.com/products/smart-sensors/bno055/ + +properties: + compatible: + enum: + - bosch,bno055 + + reg: + maxItems: 1 + + reset-gpios: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + serial { + imu { + compatible = "bosch,bno055"; + reset-gpios = <&gpio0 54 GPIO_ACTIVE_LOW>; + clocks = <&imu_clk>; + }; + }; + + - | + #include <dt-bindings/gpio/gpio.h> + i2c { + #address-cells = <1>; + #size-cells = <0>; + + imu@28 { + compatible = "bosch,bno055"; + reg = <0x28> + reset-gpios = <&gpio0 54 GPIO_ACTIVE_LOW>; + clocks = <&imu_clk>; + }; + }; -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 08/10] dt-bindings: iio: imu: add documentation for Bosch BNO055 bindings 2021-10-28 10:18 ` [v2 08/10] dt-bindings: iio: imu: add documentation for Bosch BNO055 bindings Andrea Merello @ 2021-10-28 12:25 ` Rob Herring 0 siblings, 0 replies; 89+ messages in thread From: Rob Herring @ 2021-10-28 12:25 UTC (permalink / raw) To: Andrea Merello Cc: andy.shevchenko, robh+dt, lars, matt.ranostay, Andrea Merello, devicetree, jacopo, mchehab+huawei, linux-kernel, linux-iio, jic23, ardeleanalex On Thu, 28 Oct 2021 12:18:38 +0200, Andrea Merello wrote: > Introduce new documentation file for the Bosch BNO055 IMU > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > --- > .../bindings/iio/imu/bosch,bno055.yaml | 59 +++++++++++++++++++ > 1 file changed, 59 insertions(+) > create mode 100644 Documentation/devicetree/bindings/iio/imu/bosch,bno055.yaml > My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check' on your patch (DT_CHECKER_FLAGS is new in v5.13): yamllint warnings/errors: ./Documentation/devicetree/bindings/iio/imu/bosch,bno055.yaml:20:6: [warning] wrong indentation: expected 6 but found 5 (indentation) ./Documentation/devicetree/bindings/iio/imu/bosch,bno055.yaml:37:2: [warning] wrong indentation: expected 2 but found 1 (indentation) dtschema/dtc warnings/errors: ./Documentation/devicetree/bindings/iio/imu/bosch,bno055.yaml: $id: relative path/filename doesn't match actual path or filename expected: http://devicetree.org/schemas/iio/imu/bosch,bno055.yaml# Error: Documentation/devicetree/bindings/iio/imu/bosch,bno055.example.dts:53.13-14 syntax error FATAL ERROR: Unable to parse input tree make[1]: *** [scripts/Makefile.lib:385: Documentation/devicetree/bindings/iio/imu/bosch,bno055.example.dt.yaml] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:1441: dt_binding_check] Error 2 doc reference errors (make refcheckdocs): See https://patchwork.ozlabs.org/patch/1547408 This check can fail if there are any dependencies. The base for a patch series is generally the most recent rc1. If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date: pip3 install dtschema --upgrade Please check and re-submit. ^ permalink raw reply [flat|nested] 89+ messages in thread
* [v2 09/10] iio: imu: add BNO055 serdev driver 2021-10-28 10:18 ` [v2 00/10] Add support for Bosch BNO055 IMU Andrea Merello ` (7 preceding siblings ...) 2021-10-28 10:18 ` [v2 08/10] dt-bindings: iio: imu: add documentation for Bosch BNO055 bindings Andrea Merello @ 2021-10-28 10:18 ` Andrea Merello 2021-10-28 12:31 ` Jonathan Cameron ` (2 more replies) 2021-10-28 10:18 ` [v2 10/10] iio: imu: add BNO055 I2C driver Andrea Merello 2021-10-28 10:35 ` [v2 00/10] Add support for Bosch BNO055 IMU Jonathan Cameron 10 siblings, 3 replies; 89+ messages in thread From: Andrea Merello @ 2021-10-28 10:18 UTC (permalink / raw) To: jic23, mchehab+huawei, linux-iio, linux-kernel, devicetree Cc: lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello, Andrea Merello This path adds a serdev driver for communicating to a BNO055 IMU via serial bus, and it enables the BNO055 core driver to work in this scenario. Signed-off-by: Andrea Merello <andrea.merello@iit.it> --- drivers/iio/imu/bno055/Kconfig | 5 + drivers/iio/imu/bno055/Makefile | 1 + drivers/iio/imu/bno055/bno055_sl.c | 568 +++++++++++++++++++++++++++++ 3 files changed, 574 insertions(+) create mode 100644 drivers/iio/imu/bno055/bno055_sl.c diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig index d197310661af..941e43f0368d 100644 --- a/drivers/iio/imu/bno055/Kconfig +++ b/drivers/iio/imu/bno055/Kconfig @@ -2,3 +2,8 @@ config BOSH_BNO055_IIO tristate + +config BOSH_BNO055_SERIAL + tristate "Bosh BNO055 attached via serial bus" + depends on SERIAL_DEV_BUS + select BOSH_BNO055_IIO diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile index c55741d0e96f..7285ade2f4b9 100644 --- a/drivers/iio/imu/bno055/Makefile +++ b/drivers/iio/imu/bno055/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o +obj-$(CONFIG_BOSH_BNO055_SERIAL) += bno055_sl.o diff --git a/drivers/iio/imu/bno055/bno055_sl.c b/drivers/iio/imu/bno055/bno055_sl.c new file mode 100644 index 000000000000..1d1410fdaa7c --- /dev/null +++ b/drivers/iio/imu/bno055/bno055_sl.c @@ -0,0 +1,568 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Serial line interface for Bosh BNO055 IMU (via serdev). + * This file implements serial communication up to the register read/write + * level. + * + * Copyright (C) 2021 Istituto Italiano di Tecnologia + * Electronic Design Laboratory + * Written by Andrea Merello <andrea.merello@iit.it> + * + * This driver is besed on + * Plantower PMS7003 particulate matter sensor driver + * Which is + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + */ + +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/serdev.h> + +#include "bno055.h" + +/* + * Register writes cmd have the following format + * +------+------+-----+-----+----- ... ----+ + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | + * +------+------+-----+-----+----- ... ----+ + * + * Register write responses have the following format + * +------+----------+ + * | 0xEE | ERROCODE | + * +------+----------+ + * + * Register read have the following format + * +------+------+-----+-----+ + * | 0xAA | 0xO1 | REG | LEN | + * +------+------+-----+-----+ + * + * Successful register read response have the following format + * +------+-----+----- ... ----+ + * | 0xBB | LEN | payload[LEN] | + * +------+-----+----- ... ----+ + * + * Failed register read response have the following format + * +------+--------+ + * | 0xEE | ERRCODE| (ERRCODE always > 1) + * +------+--------+ + * + * Error codes are + * 01: OK + * 02: read/write FAIL + * 04: invalid address + * 05: write on RO + * 06: wrong start byte + * 07: bus overrun + * 08: len too high + * 09: len too low + * 10: bus RX byte timeout (timeout is 30mS) + * + * + * **WORKAROUND ALERT** + * + * Serial communication seems very fragile: the BNO055 buffer seems to overflow + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in + * between two bytes then the transaction fails (IMU internal RX FSM resets). + * + * BMU055 has been seen also failing to process commands in case we send them + * too close each other (or if it is somehow busy?) + * + * One idea would be to split data in chunks, and then wait 1-2mS between + * chunks (we hope not to exceed 30mS delay for any reason - which should + * be pretty a lot of time for us), and eventually retry in case the BNO055 + * gets upset for any reason. This seems to work in avoiding the overflow + * errors, but indeed it seems slower than just perform a retry when an overflow + * error occur. + * In particular I saw these scenarios: + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could + * overflow, but it seem to sink all 4 bytes, then it returns error. + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending + * error after 4 bytes are sent; we have troubles in synchronizing again, + * because we are still sending data, and the IMU interprets it as the 1st + * byte of a new command. + * + * So, we workaround all this in the following way: + * In case of read we don't split the header but we rely on retries; This seems + * convenient for data read (where we TX only the hdr). + * For TX we split the transmission in 2-bytes chunks so that, we should not + * only avoid case 2 (which is still manageable), but we also hopefully avoid + * case 3, that would be by far worse. + */ + +/* + * Read operation overhead: + * 4 bytes req + 2byte resp hdr. + * 6 bytes = 60 bit (considering 1start + 1stop bits). + * 60/115200 = ~520uS. + * + * In 520uS we could read back about 34 bytes that means 3 samples, this means + * that in case of scattered read in which the gap is 3 samples or less it is + * still convenient to go for a burst. + * We have to take into account also IMU response time - IMU seems to be often + * reasonably quick to respond, but sometimes it seems to be in some "critical + * section" in which it delays handling of serial protocol. + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap. + */ + +#define BNO055_SL_XFER_BURST_BREAK_THRESHOLD 6 + +struct bno055_sl_priv { + struct serdev_device *serdev; + struct completion cmd_complete; + enum { + CMD_NONE, + CMD_READ, + CMD_WRITE, + } expect_response; + int expected_data_len; + u8 *response_buf; + + /** + * enum cmd_status - represent the status of a command sent to the HW. + * @STATUS_OK: The command executed successfully. + * @STATUS_FAIL: The command failed: HW responded with an error. + * @STATUS_CRIT: The command failed: the serial communication failed. + */ + enum { + STATUS_OK = 0, + STATUS_FAIL = 1, + STATUS_CRIT = -1 + } cmd_status; + struct mutex lock; + + /* Only accessed in behalf of RX callback context. No lock needed. */ + struct { + enum { + RX_IDLE, + RX_START, + RX_DATA + } state; + int databuf_count; + int expected_len; + int type; + } rx; + + /* Never accessed in behalf of RX callback context. No lock needed */ + bool cmd_stale; +}; + +static int bno055_sl_send_chunk(struct bno055_sl_priv *priv, u8 *data, int len) +{ + int ret; + + dev_dbg(&priv->serdev->dev, "send (len: %d): %*ph", len, len, data); + ret = serdev_device_write(priv->serdev, data, len, + msecs_to_jiffies(25)); + if (ret < 0) + return ret; + + if (ret < len) + return -EIO; + + return 0; +} + +/* + * Sends a read or write command. + * 'data' can be NULL (used in read case). 'len' parameter is always valid; in + * case 'data' is non-NULL then it must match 'data' size. + */ +static int bno055_sl_do_send_cmd(struct bno055_sl_priv *priv, + int read, int addr, int len, u8 *data) +{ + int ret; + int chunk_len; + u8 hdr[] = {0xAA, !!read, addr, len}; + + if (read) { + ret = bno055_sl_send_chunk(priv, hdr, 4); + } else { + ret = bno055_sl_send_chunk(priv, hdr, 2); + if (ret) + goto fail; + + usleep_range(2000, 3000); + ret = bno055_sl_send_chunk(priv, hdr + 2, 2); + } + if (ret) + goto fail; + + if (data) { + while (len) { + chunk_len = min(len, 2); + usleep_range(2000, 3000); + ret = bno055_sl_send_chunk(priv, data, chunk_len); + if (ret) + goto fail; + data += chunk_len; + len -= chunk_len; + } + } + + return 0; +fail: + /* waiting more than 30mS should clear the BNO055 internal state */ + usleep_range(40000, 50000); + return ret; +} + +static int bno_sl_send_cmd(struct bno055_sl_priv *priv, + int read, int addr, int len, u8 *data) +{ + const int retry_max = 5; + int retry = retry_max; + int ret = 0; + + /* + * In case previous command was interrupted we still neet to wait it to + * complete before we can issue new commands + */ + if (priv->cmd_stale) { + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, + msecs_to_jiffies(100)); + if (ret == -ERESTARTSYS) + return -ERESTARTSYS; + + priv->cmd_stale = false; + /* if serial protocol broke, bail out */ + if (priv->cmd_status == STATUS_CRIT) + goto exit; + } + + /* + * Try to convince the IMU to cooperate.. as explained in the comments + * at the top of this file, the IMU could also refuse the command (i.e. + * it is not ready yet); retry in this case. + */ + while (retry--) { + mutex_lock(&priv->lock); + priv->expect_response = read ? CMD_READ : CMD_WRITE; + reinit_completion(&priv->cmd_complete); + mutex_unlock(&priv->lock); + + if (retry != (retry_max - 1)) + dev_dbg(&priv->serdev->dev, "cmd retry: %d", + retry_max - retry); + ret = bno055_sl_do_send_cmd(priv, read, addr, len, data); + if (ret) + continue; + + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, + msecs_to_jiffies(100)); + if (ret == -ERESTARTSYS) { + priv->cmd_stale = true; + return -ERESTARTSYS; + } else if (!ret) { + ret = -ETIMEDOUT; + break; + } + ret = 0; + + /* + * Poll if the IMU returns error (i.e busy), break if the IMU + * returns OK or if the serial communication broke + */ + if (priv->cmd_status <= 0) + break; + } + +exit: + if (ret) + return ret; + if (priv->cmd_status == STATUS_CRIT) + return -EIO; + if (priv->cmd_status == STATUS_FAIL) + return -EINVAL; + return 0; +} + +static int bno055_sl_write_reg(void *context, const void *data, size_t count) +{ + int ret; + int reg; + u8 *write_data = (u8 *)data + 1; + struct bno055_sl_priv *priv = context; + + if (count < 2) { + dev_err(&priv->serdev->dev, "Invalid write count %zu", count); + return -EINVAL; + } + + reg = ((u8 *)data)[0]; + dev_dbg(&priv->serdev->dev, "wr reg 0x%x = 0x%x", reg, ((u8 *)data)[1]); + ret = bno_sl_send_cmd(priv, 0, reg, count - 1, write_data); + + return ret; +} + +static int bno055_sl_read_reg(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + int ret; + int reg_addr; + struct bno055_sl_priv *priv = context; + + if (val_size > 128) { + dev_err(&priv->serdev->dev, "Invalid read valsize %d", + val_size); + return -EINVAL; + } + + reg_addr = ((u8 *)reg)[0]; + dev_dbg(&priv->serdev->dev, "rd reg 0x%x (len %d)", reg_addr, val_size); + mutex_lock(&priv->lock); + priv->expected_data_len = val_size; + priv->response_buf = val; + mutex_unlock(&priv->lock); + + ret = bno_sl_send_cmd(priv, 1, reg_addr, val_size, NULL); + + mutex_lock(&priv->lock); + priv->response_buf = NULL; + mutex_unlock(&priv->lock); + + return ret; +} + +/* + * Handler for received data; this is called from the reicever callback whenever + * it got some packet from the serial bus. The status tell us whether the + * packet is valid (i.e. header ok && received payload len consistent wrt the + * header). It's now our responsability to check whether this is what we + * expected, of whether we got some unexpected, yet valid, packet. + */ +static void bno055_sl_handle_rx(struct bno055_sl_priv *priv, int status) +{ + mutex_lock(&priv->lock); + switch (priv->expect_response) { + case CMD_NONE: + dev_warn(&priv->serdev->dev, "received unexpected, yet valid, data from sensor"); + mutex_unlock(&priv->lock); + return; + + case CMD_READ: + priv->cmd_status = status; + if (status == STATUS_OK && + priv->rx.databuf_count != priv->expected_data_len) { + /* + * If we got here, then the lower layer serial protocol + * seems consistent with itself; if we got an unexpected + * amount of data then signal it as a non critical error + */ + priv->cmd_status = STATUS_FAIL; + dev_warn(&priv->serdev->dev, "received an unexpected amount of, yet valid, data from sensor"); + } + break; + + case CMD_WRITE: + priv->cmd_status = status; + break; + } + + priv->expect_response = CMD_NONE; + complete(&priv->cmd_complete); + mutex_unlock(&priv->lock); +} + +/* + * Serdev receiver FSM. This tracks the serial communication and parse the + * header. It pushes packets to bno055_sl_handle_rx(), eventually communicating + * failures (i.e. malformed packets). + * Ideally it doesn't know anything about upper layer (i.e. if this is the + * packet we were really expecting), but since we copies the payload into the + * receiver buffer (that is not valid when i.e. we don't expect data), we + * snoop a bit in the upper layer.. + * Also, we assume to RX one pkt per time (i.e. the HW doesn't send anything + * unless we require to AND we don't queue more than one request per time). + */ +static int bno055_sl_receive_buf(struct serdev_device *serdev, + const unsigned char *buf, size_t size) +{ + int status; + struct bno055_sl_priv *priv = serdev_device_get_drvdata(serdev); + int _size = size; + + if (size == 0) + return 0; + + dev_dbg(&priv->serdev->dev, "recv (len %zu): %*ph ", size, size, buf); + switch (priv->rx.state) { + case RX_IDLE: + /* + * New packet. + * Check for its 1st byte, that identifies the pkt type. + */ + if (buf[0] != 0xEE && buf[0] != 0xBB) { + dev_err(&priv->serdev->dev, + "Invalid packet start %x", buf[0]); + bno055_sl_handle_rx(priv, STATUS_CRIT); + break; + } + priv->rx.type = buf[0]; + priv->rx.state = RX_START; + size--; + buf++; + priv->rx.databuf_count = 0; + fallthrough; + + case RX_START: + /* + * Packet RX in progress, we expect either 1-byte len or 1-byte + * status depending by the packet type. + */ + if (size == 0) + break; + + if (priv->rx.type == 0xEE) { + if (size > 1) { + dev_err(&priv->serdev->dev, "EE pkt. Extra data received"); + status = STATUS_CRIT; + + } else { + status = (buf[0] == 1) ? STATUS_OK : STATUS_FAIL; + } + bno055_sl_handle_rx(priv, status); + priv->rx.state = RX_IDLE; + break; + + } else { + /*priv->rx.type == 0xBB */ + priv->rx.state = RX_DATA; + priv->rx.expected_len = buf[0]; + size--; + buf++; + } + fallthrough; + + case RX_DATA: + /* Header parsed; now receiving packet data payload */ + if (size == 0) + break; + + if (priv->rx.databuf_count + size > priv->rx.expected_len) { + /* + * This is a inconsistency in serial protocol, we lost + * sync and we don't know how to handle further data + */ + dev_err(&priv->serdev->dev, "BB pkt. Extra data received"); + bno055_sl_handle_rx(priv, STATUS_CRIT); + priv->rx.state = RX_IDLE; + break; + } + + mutex_lock(&priv->lock); + /* + * NULL e.g. when read cmd is stale or when no read cmd is + * actually pending. + */ + if (priv->response_buf && + /* + * Snoop on the upper layer protocol stuff to make sure not + * to write to an invalid memory. Apart for this, let's the + * upper layer manage any inconsistency wrt expected data + * len (as long as the serial protocol is consistent wrt + * itself (i.e. response header is consistent with received + * response len. + */ + (priv->rx.databuf_count + size <= priv->expected_data_len)) + memcpy(priv->response_buf + priv->rx.databuf_count, + buf, size); + mutex_unlock(&priv->lock); + + priv->rx.databuf_count += size; + + /* + * Reached expected len advertised by the IMU for the current + * packet. Pass it to the upper layer (for us it is just valid). + */ + if (priv->rx.databuf_count == priv->rx.expected_len) { + bno055_sl_handle_rx(priv, STATUS_OK); + priv->rx.state = RX_IDLE; + } + break; + } + + return _size; +} + +static const struct serdev_device_ops bno055_sl_serdev_ops = { + .receive_buf = bno055_sl_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static struct regmap_bus bno055_sl_regmap_bus = { + .write = bno055_sl_write_reg, + .read = bno055_sl_read_reg, +}; + +static int bno055_sl_probe(struct serdev_device *serdev) +{ + struct bno055_sl_priv *priv; + struct regmap *regmap; + int ret; + + priv = devm_kzalloc(&serdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + serdev_device_set_drvdata(serdev, priv); + priv->serdev = serdev; + mutex_init(&priv->lock); + init_completion(&priv->cmd_complete); + + serdev_device_set_client_ops(serdev, &bno055_sl_serdev_ops); + ret = devm_serdev_device_open(&serdev->dev, serdev); + if (ret) + return ret; + + if (serdev_device_set_baudrate(serdev, 115200) != 115200) { + dev_err(&serdev->dev, "Cannot set required baud rate"); + return -EIO; + } + + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + if (ret) { + dev_err(&serdev->dev, "Cannot set required parity setting"); + return ret; + } + serdev_device_set_flow_control(serdev, false); + + regmap = devm_regmap_init(&serdev->dev, &bno055_sl_regmap_bus, + priv, &bno055_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&serdev->dev, "Unable to init register map"); + return PTR_ERR(regmap); + } + + return bno055_probe(&serdev->dev, regmap, + BNO055_SL_XFER_BURST_BREAK_THRESHOLD); +} + +static const struct of_device_id bno055_sl_of_match[] = { + { .compatible = "bosch,bno055" }, + { } +}; +MODULE_DEVICE_TABLE(of, bno055_sl_of_match); + +static struct serdev_device_driver bno055_sl_driver = { + .driver = { + .name = "bno055-sl", + .of_match_table = bno055_sl_of_match, + }, + .probe = bno055_sl_probe, +}; +module_serdev_device_driver(bno055_sl_driver); + +MODULE_AUTHOR("Andrea Merello <andrea.merello@iit.it>"); +MODULE_DESCRIPTION("Bosch BNO055 serdev interface"); +MODULE_LICENSE("GPL v2"); -- 2.17.1 ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 09/10] iio: imu: add BNO055 serdev driver 2021-10-28 10:18 ` [v2 09/10] iio: imu: add BNO055 serdev driver Andrea Merello @ 2021-10-28 12:31 ` Jonathan Cameron 2021-11-09 15:33 ` Andrea Merello 2021-10-29 7:09 ` kernel test robot 2021-10-29 12:59 ` kernel test robot 2 siblings, 1 reply; 89+ messages in thread From: Jonathan Cameron @ 2021-10-28 12:31 UTC (permalink / raw) To: Andrea Merello Cc: mchehab+huawei, linux-iio, linux-kernel, devicetree, lars, robh+dt, andy.shevchenko, matt.ranostay, ardeleanalex, jacopo, Andrea Merello On Thu, 28 Oct 2021 12:18:39 +0200 Andrea Merello <andrea.merello@gmail.com> wrote: > This path adds a serdev driver for communicating to a BNO055 IMU via > serial bus, and it enables the BNO055 core driver to work in this > scenario. > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> Hi Andrea, Some comments inline. Note I'm not that familiar with the serial_dev interface so would definitely appreciate input from others on that part. Jonathan > --- > drivers/iio/imu/bno055/Kconfig | 5 + > drivers/iio/imu/bno055/Makefile | 1 + > drivers/iio/imu/bno055/bno055_sl.c | 568 +++++++++++++++++++++++++++++ > 3 files changed, 574 insertions(+) > create mode 100644 drivers/iio/imu/bno055/bno055_sl.c > > diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig > index d197310661af..941e43f0368d 100644 > --- a/drivers/iio/imu/bno055/Kconfig > +++ b/drivers/iio/imu/bno055/Kconfig > @@ -2,3 +2,8 @@ > > config BOSH_BNO055_IIO > tristate > + > +config BOSH_BNO055_SERIAL > + tristate "Bosh BNO055 attached via serial bus" > + depends on SERIAL_DEV_BUS > + select BOSH_BNO055_IIO > diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile > index c55741d0e96f..7285ade2f4b9 100644 > --- a/drivers/iio/imu/bno055/Makefile > +++ b/drivers/iio/imu/bno055/Makefile > @@ -1,3 +1,4 @@ > # SPDX-License-Identifier: GPL-2.0 > > obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o > +obj-$(CONFIG_BOSH_BNO055_SERIAL) += bno055_sl.o > diff --git a/drivers/iio/imu/bno055/bno055_sl.c b/drivers/iio/imu/bno055/bno055_sl.c > new file mode 100644 > index 000000000000..1d1410fdaa7c > --- /dev/null > +++ b/drivers/iio/imu/bno055/bno055_sl.c > @@ -0,0 +1,568 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Serial line interface for Bosh BNO055 IMU (via serdev). > + * This file implements serial communication up to the register read/write > + * level. > + * > + * Copyright (C) 2021 Istituto Italiano di Tecnologia > + * Electronic Design Laboratory > + * Written by Andrea Merello <andrea.merello@iit.it> > + * > + * This driver is besed on > + * Plantower PMS7003 particulate matter sensor driver > + * Which is > + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> > + */ > + > +#include <linux/completion.h> > +#include <linux/device.h> > +#include <linux/errno.h> > +#include <linux/jiffies.h> > +#include <linux/kernel.h> > +#include <linux/mod_devicetable.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/regmap.h> > +#include <linux/serdev.h> > + > +#include "bno055.h" > + > +/* > + * Register writes cmd have the following format > + * +------+------+-----+-----+----- ... ----+ > + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | > + * +------+------+-----+-----+----- ... ----+ > + * > + * Register write responses have the following format > + * +------+----------+ > + * | 0xEE | ERROCODE | > + * +------+----------+ > + * > + * Register read have the following format > + * +------+------+-----+-----+ > + * | 0xAA | 0xO1 | REG | LEN | > + * +------+------+-----+-----+ > + * > + * Successful register read response have the following format > + * +------+-----+----- ... ----+ > + * | 0xBB | LEN | payload[LEN] | > + * +------+-----+----- ... ----+ > + * > + * Failed register read response have the following format > + * +------+--------+ > + * | 0xEE | ERRCODE| (ERRCODE always > 1) > + * +------+--------+ > + * > + * Error codes are > + * 01: OK > + * 02: read/write FAIL > + * 04: invalid address > + * 05: write on RO > + * 06: wrong start byte > + * 07: bus overrun > + * 08: len too high > + * 09: len too low > + * 10: bus RX byte timeout (timeout is 30mS) > + * > + * > + * **WORKAROUND ALERT** > + * > + * Serial communication seems very fragile: the BNO055 buffer seems to overflow > + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. > + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in > + * between two bytes then the transaction fails (IMU internal RX FSM resets). > + * > + * BMU055 has been seen also failing to process commands in case we send them > + * too close each other (or if it is somehow busy?) > + * > + * One idea would be to split data in chunks, and then wait 1-2mS between > + * chunks (we hope not to exceed 30mS delay for any reason - which should > + * be pretty a lot of time for us), and eventually retry in case the BNO055 > + * gets upset for any reason. This seems to work in avoiding the overflow > + * errors, but indeed it seems slower than just perform a retry when an overflow > + * error occur. > + * In particular I saw these scenarios: > + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. > + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could > + * overflow, but it seem to sink all 4 bytes, then it returns error. > + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending > + * error after 4 bytes are sent; we have troubles in synchronizing again, > + * because we are still sending data, and the IMU interprets it as the 1st > + * byte of a new command. > + * > + * So, we workaround all this in the following way: > + * In case of read we don't split the header but we rely on retries; This seems > + * convenient for data read (where we TX only the hdr). > + * For TX we split the transmission in 2-bytes chunks so that, we should not > + * only avoid case 2 (which is still manageable), but we also hopefully avoid > + * case 3, that would be by far worse. > + */ > + > +/* > + * Read operation overhead: > + * 4 bytes req + 2byte resp hdr. > + * 6 bytes = 60 bit (considering 1start + 1stop bits). > + * 60/115200 = ~520uS. > + * > + * In 520uS we could read back about 34 bytes that means 3 samples, this means > + * that in case of scattered read in which the gap is 3 samples or less it is > + * still convenient to go for a burst. > + * We have to take into account also IMU response time - IMU seems to be often > + * reasonably quick to respond, but sometimes it seems to be in some "critical > + * section" in which it delays handling of serial protocol. > + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap. > + */ > + > +#define BNO055_SL_XFER_BURST_BREAK_THRESHOLD 6 > + > +struct bno055_sl_priv { > + struct serdev_device *serdev; > + struct completion cmd_complete; > + enum { > + CMD_NONE, > + CMD_READ, > + CMD_WRITE, > + } expect_response; > + int expected_data_len; > + u8 *response_buf; > + > + /** > + * enum cmd_status - represent the status of a command sent to the HW. > + * @STATUS_OK: The command executed successfully. > + * @STATUS_FAIL: The command failed: HW responded with an error. > + * @STATUS_CRIT: The command failed: the serial communication failed. > + */ > + enum { > + STATUS_OK = 0, > + STATUS_FAIL = 1, > + STATUS_CRIT = -1 > + } cmd_status; > + struct mutex lock; > + > + /* Only accessed in behalf of RX callback context. No lock needed. */ would "Only accessed in RX callback context. No lock needed." convey the same meaning? > + struct { > + enum { > + RX_IDLE, > + RX_START, > + RX_DATA > + } state; > + int databuf_count; > + int expected_len; > + int type; > + } rx; > + > + /* Never accessed in behalf of RX callback context. No lock needed */ > + bool cmd_stale; > +}; > + > +static int bno055_sl_send_chunk(struct bno055_sl_priv *priv, u8 *data, int len) > +{ > + int ret; > + > + dev_dbg(&priv->serdev->dev, "send (len: %d): %*ph", len, len, data); > + ret = serdev_device_write(priv->serdev, data, len, > + msecs_to_jiffies(25)); > + if (ret < 0) > + return ret; > + > + if (ret < len) > + return -EIO; > + > + return 0; > +} > + > +/* > + * Sends a read or write command. > + * 'data' can be NULL (used in read case). 'len' parameter is always valid; in > + * case 'data' is non-NULL then it must match 'data' size. > + */ > +static int bno055_sl_do_send_cmd(struct bno055_sl_priv *priv, > + int read, int addr, int len, u8 *data) Read is a bool, so give it that type. > +{ > + int ret; > + int chunk_len; > + u8 hdr[] = {0xAA, !!read, addr, len}; > + > + if (read) { > + ret = bno055_sl_send_chunk(priv, hdr, 4); > + } else { > + ret = bno055_sl_send_chunk(priv, hdr, 2); > + if (ret) > + goto fail; > + > + usleep_range(2000, 3000); > + ret = bno055_sl_send_chunk(priv, hdr + 2, 2); > + } > + if (ret) > + goto fail; > + > + if (data) { I would flip this condition to reduce indent and make it easy to see we are done in the no 'data' case. Also, does this correspond in all cases to read? If so I would use that as the variable to check. if (!data) return 0; while (len) { ... > + while (len) { > + chunk_len = min(len, 2); > + usleep_range(2000, 3000); > + ret = bno055_sl_send_chunk(priv, data, chunk_len); > + if (ret) > + goto fail; > + data += chunk_len; > + len -= chunk_len; > + } > + } > + > + return 0; > +fail: > + /* waiting more than 30mS should clear the BNO055 internal state */ > + usleep_range(40000, 50000); > + return ret; > +} > + > +static int bno_sl_send_cmd(struct bno055_sl_priv *priv, > + int read, int addr, int len, u8 *data) Read looks to be a bool to me not an integer. > +{ > + const int retry_max = 5; > + int retry = retry_max; > + int ret = 0; > + > + /* > + * In case previous command was interrupted we still neet to wait it to > + * complete before we can issue new commands > + */ > + if (priv->cmd_stale) { > + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, > + msecs_to_jiffies(100)); > + if (ret == -ERESTARTSYS) > + return -ERESTARTSYS; > + > + priv->cmd_stale = false; > + /* if serial protocol broke, bail out */ > + if (priv->cmd_status == STATUS_CRIT) > + goto exit; return -EIO; > + } > + > + /* > + * Try to convince the IMU to cooperate.. as explained in the comments > + * at the top of this file, the IMU could also refuse the command (i.e. > + * it is not ready yet); retry in this case. > + */ > + while (retry--) { > + mutex_lock(&priv->lock); > + priv->expect_response = read ? CMD_READ : CMD_WRITE; > + reinit_completion(&priv->cmd_complete); > + mutex_unlock(&priv->lock); > + > + if (retry != (retry_max - 1)) > + dev_dbg(&priv->serdev->dev, "cmd retry: %d", > + retry_max - retry); > + ret = bno055_sl_do_send_cmd(priv, read, addr, len, data); > + if (ret) > + continue; > + > + ret = wait_for_completion_interruptible_timeout(&priv->cmd_complete, > + msecs_to_jiffies(100)); > + if (ret == -ERESTARTSYS) { > + priv->cmd_stale = true; > + return -ERESTARTSYS; > + } else if (!ret) { > + ret = -ETIMEDOUT; return -ETIMEDOUT; > + break; > + } > + ret = 0; > + > + /* > + * Poll if the IMU returns error (i.e busy), break if the IMU > + * returns OK or if the serial communication broke > + */ > + if (priv->cmd_status <= 0) I 'think' this is only place we can break out with status set to anything (with the suggested modifications above) so move the if statements from the error path here and drop the ret = 0 above. > + break; > + } > + > +exit: > + if (ret) > + return ret; > + if (priv->cmd_status == STATUS_CRIT) > + return -EIO; > + if (priv->cmd_status == STATUS_FAIL) > + return -EINVAL; > + return 0; > +} > + > +static int bno055_sl_write_reg(void *context, const void *data, size_t count) > +{ > + int ret; > + int reg; > + u8 *write_data = (u8 *)data + 1; Given you dereference data as a u8 * in several places, perhaps a local variable to allow you to do it once. > + struct bno055_sl_priv *priv = context; > + > + if (count < 2) { > + dev_err(&priv->serdev->dev, "Invalid write count %zu", count); > + return -EINVAL; > + } > + > + reg = ((u8 *)data)[0]; > + dev_dbg(&priv->serdev->dev, "wr reg 0x%x = 0x%x", reg, ((u8 *)data)[1]); > + ret = bno_sl_send_cmd(priv, 0, reg, count - 1, write_data); > + > + return ret; return bno_sl_send_cmd(...) > +} > + > +static int bno055_sl_read_reg(void *context, > + const void *reg, size_t reg_size, > + void *val, size_t val_size) > +{ > + int ret; > + int reg_addr; > + struct bno055_sl_priv *priv = context; > + > + if (val_size > 128) { > + dev_err(&priv->serdev->dev, "Invalid read valsize %d", > + val_size); > + return -EINVAL; > + } > + > + reg_addr = ((u8 *)reg)[0]; > + dev_dbg(&priv->serdev->dev, "rd reg 0x%x (len %d)", reg_addr, val_size); > + mutex_lock(&priv->lock); > + priv->expected_data_len = val_size; > + priv->response_buf = val; > + mutex_unlock(&priv->lock); > + > + ret = bno_sl_send_cmd(priv, 1, reg_addr, val_size, NULL); > + > + mutex_lock(&priv->lock); > + priv->response_buf = NULL; > + mutex_unlock(&priv->lock); > + > + return ret; > +} > + > +/* > + * Handler for received data; this is called from the reicever callback whenever > + * it got some packet from the serial bus. The status tell us whether the > + * packet is valid (i.e. header ok && received payload len consistent wrt the > + * header). It's now our responsability to check whether this is what we > + * expected, of whether we got some unexpected, yet valid, packet. > + */ > +static void bno055_sl_handle_rx(struct bno055_sl_priv *priv, int status) > +{ > + mutex_lock(&priv->lock); > + switch (priv->expect_response) { > + case CMD_NONE: > + dev_warn(&priv->serdev->dev, "received unexpected, yet valid, data from sensor"); > + mutex_unlock(&priv->lock); > + return; > + > + case CMD_READ: > + priv->cmd_status = status; > + if (status == STATUS_OK && > + priv->rx.databuf_count != priv->expected_data_len) { > + /* > + * If we got here, then the lower layer serial protocol > + * seems consistent with itself; if we got an unexpected > + * amount of data then signal it as a non critical error > + */ > + priv->cmd_status = STATUS_FAIL; > + dev_warn(&priv->serdev->dev, "received an unexpected amount of, yet valid, data from sensor"); > + } > + break; > + > + case CMD_WRITE: > + priv->cmd_status = status; > + break; > + } > + > + priv->expect_response = CMD_NONE; > + complete(&priv->cmd_complete); > + mutex_unlock(&priv->lock); > +} > + > +/* > + * Serdev receiver FSM. This tracks the serial communication and parse the > + * header. It pushes packets to bno055_sl_handle_rx(), eventually communicating > + * failures (i.e. malformed packets). > + * Ideally it doesn't know anything about upper layer (i.e. if this is the > + * packet we were really expecting), but since we copies the payload into the > + * receiver buffer (that is not valid when i.e. we don't expect data), we > + * snoop a bit in the upper layer.. > + * Also, we assume to RX one pkt per time (i.e. the HW doesn't send anything > + * unless we require to AND we don't queue more than one request per time). > + */ > +static int bno055_sl_receive_buf(struct serdev_device *serdev, > + const unsigned char *buf, size_t size) > +{ > + int status; > + struct bno055_sl_priv *priv = serdev_device_get_drvdata(serdev); > + int _size = size; Why the local variable? > + > + if (size == 0) > + return 0; > + > + dev_dbg(&priv->serdev->dev, "recv (len %zu): %*ph ", size, size, buf); > + switch (priv->rx.state) { > + case RX_IDLE: > + /* > + * New packet. > + * Check for its 1st byte, that identifies the pkt type. > + */ > + if (buf[0] != 0xEE && buf[0] != 0xBB) { > + dev_err(&priv->serdev->dev, > + "Invalid packet start %x", buf[0]); > + bno055_sl_handle_rx(priv, STATUS_CRIT); > + break; > + } > + priv->rx.type = buf[0]; > + priv->rx.state = RX_START; > + size--; > + buf++; > + priv->rx.databuf_count = 0; > + fallthrough; > + > + case RX_START: > + /* > + * Packet RX in progress, we expect either 1-byte len or 1-byte > + * status depending by the packet type. > + */ > + if (size == 0) > + break; > + > + if (priv->rx.type == 0xEE) { > + if (size > 1) { > + dev_err(&priv->serdev->dev, "EE pkt. Extra data received"); > + status = STATUS_CRIT; > + > + } else { > + status = (buf[0] == 1) ? STATUS_OK : STATUS_FAIL; > + } > + bno055_sl_handle_rx(priv, status); > + priv->rx.state = RX_IDLE; > + break; > + > + } else { > + /*priv->rx.type == 0xBB */ > + priv->rx.state = RX_DATA; > + priv->rx.expected_len = buf[0]; > + size--; > + buf++; > + } > + fallthrough; > + > + case RX_DATA: > + /* Header parsed; now receiving packet data payload */ > + if (size == 0) > + break; > + > + if (priv->rx.databuf_count + size > priv->rx.expected_len) { > + /* > + * This is a inconsistency in serial protocol, we lost > + * sync and we don't know how to handle further data > + */ > + dev_err(&priv->serdev->dev, "BB pkt. Extra data received"); > + bno055_sl_handle_rx(priv, STATUS_CRIT); > + priv->rx.state = RX_IDLE; > + break; > + } > + > + mutex_lock(&priv->lock); > + /* > + * NULL e.g. when read cmd is stale or when no read cmd is > + * actually pending. > + */ > + if (priv->response_buf && > + /* > + * Snoop on the upper layer protocol stuff to make sure not > + * to write to an invalid memory. Apart for this, let's the > + * upper layer manage any inconsistency wrt expected data > + * len (as long as the serial protocol is consistent wrt > + * itself (i.e. response header is consistent with received > + * response len. > + */ > + (priv->rx.databuf_count + size <= priv->expected_data_len)) > + memcpy(priv->response_buf + priv->rx.databuf_count, > + buf, size); > + mutex_unlock(&priv->lock); > + > + priv->rx.databuf_count += size; > + > + /* > + * Reached expected len advertised by the IMU for the current > + * packet. Pass it to the upper layer (for us it is just valid). > + */ > + if (priv->rx.databuf_count == priv->rx.expected_len) { > + bno055_sl_handle_rx(priv, STATUS_OK); > + priv->rx.state = RX_IDLE; > + } > + break; > + } > + > + return _size; > +} > + > +static const struct serdev_device_ops bno055_sl_serdev_ops = { > + .receive_buf = bno055_sl_receive_buf, > + .write_wakeup = serdev_device_write_wakeup, > +}; > + > +static struct regmap_bus bno055_sl_regmap_bus = { > + .write = bno055_sl_write_reg, > + .read = bno055_sl_read_reg, > +}; > + > +static int bno055_sl_probe(struct serdev_device *serdev) > +{ > + struct bno055_sl_priv *priv; > + struct regmap *regmap; > + int ret; > + > + priv = devm_kzalloc(&serdev->dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + serdev_device_set_drvdata(serdev, priv); > + priv->serdev = serdev; > + mutex_init(&priv->lock); > + init_completion(&priv->cmd_complete); > + > + serdev_device_set_client_ops(serdev, &bno055_sl_serdev_ops); > + ret = devm_serdev_device_open(&serdev->dev, serdev); > + if (ret) > + return ret; > + > + if (serdev_device_set_baudrate(serdev, 115200) != 115200) { > + dev_err(&serdev->dev, "Cannot set required baud rate"); > + return -EIO; > + } > + > + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); > + if (ret) { > + dev_err(&serdev->dev, "Cannot set required parity setting"); > + return ret; > + } > + serdev_device_set_flow_control(serdev, false); > + > + regmap = devm_regmap_init(&serdev->dev, &bno055_sl_regmap_bus, > + priv, &bno055_regmap_config); > + if (IS_ERR(regmap)) { > + dev_err(&serdev->dev, "Unable to init register map"); > + return PTR_ERR(regmap); > + } > + > + return bno055_probe(&serdev->dev, regmap, > + BNO055_SL_XFER_BURST_BREAK_THRESHOLD); > +} > + > +static const struct of_device_id bno055_sl_of_match[] = { > + { .compatible = "bosch,bno055" }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, bno055_sl_of_match); > + > +static struct serdev_device_driver bno055_sl_driver = { > + .driver = { > + .name = "bno055-sl", > + .of_match_table = bno055_sl_of_match, > + }, > + .probe = bno055_sl_probe, > +}; > +module_serdev_device_driver(bno055_sl_driver); > + > +MODULE_AUTHOR("Andrea Merello <andrea.merello@iit.it>"); > +MODULE_DESCRIPTION("Bosch BNO055 serdev interface"); > +MODULE_LICENSE("GPL v2"); ^ permalink raw reply [flat|nested] 89+ messages in thread
* Re: [v2 09/10] iio: imu: add BNO055 serdev driver 2021-10-28 12:31 ` Jonathan Cameron @ 2021-11-09 15:33 ` Andrea Merello 2021-11-14 16:37 ` Jonathan Cameron 0 siblings, 1 reply; 89+ messages in thread From: Andrea Merello @ 2021-11-09 15:33 UTC (permalink / raw) To: Jonathan Cameron Cc: Mauro Carvalho Chehab, linux-iio, linux-kernel, devicetree, Lars-Peter Clausen, Rob Herring, Andy Shevchenko, Matt Ranostay, Alexandru Ardelean, Jacopo Mondi, Andrea Merello Few inline comments. Ok for all the rest. Il giorno gio 28 ott 2021 alle ore 14:27 Jonathan Cameron <jic23@kernel.org> ha scritto: > > On Thu, 28 Oct 2021 12:18:39 +0200 > Andrea Merello <andrea.merello@gmail.com> wrote: > > > This path adds a serdev driver for communicating to a BNO055 IMU via > > serial bus, and it enables the BNO055 core driver to work in this > > scenario. > > > > Signed-off-by: Andrea Merello <andrea.merello@iit.it> > > Hi Andrea, > > Some comments inline. Note I'm not that familiar with the serial_dev interface > so would definitely appreciate input from others on that part. > > Jonathan > > --- > > drivers/iio/imu/bno055/Kconfig | 5 + > > drivers/iio/imu/bno055/Makefile | 1 + > > drivers/iio/imu/bno055/bno055_sl.c | 568 +++++++++++++++++++++++++++++ > > 3 files changed, 574 insertions(+) > > create mode 100644 drivers/iio/imu/bno055/bno055_sl.c > > > > diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig > > index d197310661af..941e43f0368d 100644 > > --- a/drivers/iio/imu/bno055/Kconfig > > +++ b/drivers/iio/imu/bno055/Kconfig > > @@ -2,3 +2,8 @@ > > > > config BOSH_BNO055_IIO > > tristate > > + > > +config BOSH_BNO055_SERIAL > > + tristate "Bosh BNO055 attached via serial bus" > > + depends on SERIAL_DEV_BUS > > + select BOSH_BNO055_IIO > > diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile > > index c55741d0e96f..7285ade2f4b9 100644 > > --- a/drivers/iio/imu/bno055/Makefile > > +++ b/drivers/iio/imu/bno055/Makefile > > @@ -1,3 +1,4 @@ > > # SPDX-License-Identifier: GPL-2.0 > > > > obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o > > +obj-$(CONFIG_BOSH_BNO055_SERIAL) += bno055_sl.o > > diff --git a/drivers/iio/imu/bno055/bno055_sl.c b/drivers/iio/imu/bno055/bno055_sl.c > > new file mode 100644 > > index 000000000000..1d1410fdaa7c > > --- /dev/null > > +++ b/drivers/iio/imu/bno055/bno055_sl.c > > @@ -0,0 +1,568 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Serial line interface for Bosh BNO055 IMU (via serdev). > > + * This file implements serial communication up to the register read/write > > + * level. > > + * > > + * Copyright (C) 2021 Istituto Italiano di Tecnologia > > + * Electronic Design Laboratory > > + * Written by Andrea Merello <andrea.merello@iit.it> > > + * > > + * This driver is besed on > > + * Plantower PMS7003 particulate matter sensor driver > > + * Which is > > + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> > > + */ > > + > > +#include <linux/completion.h> > > +#include <linux/device.h> > > +#include <linux/errno.h> > > +#include <linux/jiffies.h> > > +#include <linux/kernel.h> > > +#include <linux/mod_devicetable.h> > > +#include <linux/module.h> > > +#include <linux/mutex.h> > > +#include <linux/regmap.h> > > +#include <linux/serdev.h> > > + > > +#include "bno055.h" > > + > > +/* > > + * Register writes cmd have the following format > > + * +------+------+-----+-----+----- ... ----+ > > + * | 0xAA | 0xOO | REG | LEN | payload[LEN] | > > + * +------+------+-----+-----+----- ... ----+ > > + * > > + * Register write responses have the following format > > + * +------+----------+ > > + * | 0xEE | ERROCODE | > > + * +------+----------+ > > + * > > + * Register read have the following format > > + * +------+------+-----+-----+ > > + * | 0xAA | 0xO1 | REG | LEN | > > + * +------+------+-----+-----+ > > + * > > + * Successful register read response have the following format > > + * +------+-----+----- ... ----+ > > + * | 0xBB | LEN | payload[LEN] | > > + * +------+-----+----- ... ----+ > > + * > > + * Failed register read response have the following format > > + * +------+--------+ > > + * | 0xEE | ERRCODE| (ERRCODE always > 1) > > + * +------+--------+ > > + * > > + * Error codes are > > + * 01: OK > > + * 02: read/write FAIL > > + * 04: invalid address > > + * 05: write on RO > > + * 06: wrong start byte > > + * 07: bus overrun > > + * 08: len too high > > + * 09: len too low > > + * 10: bus RX byte timeout (timeout is 30mS) > > + * > > + * > > + * **WORKAROUND ALERT** > > + * > > + * Serial communication seems very fragile: the BNO055 buffer seems to overflow > > + * very easy; BNO055 seems able to sink few bytes, then it needs a brief pause. > > + * On the other hand, it is also picky on timeout: if there is a pause > 30mS in > > + * between two bytes then the transaction fails (IMU internal RX FSM resets). > > + * > > + * BMU055 has been seen also failing to process commands in case we send them > > + * too close each other (or if it is somehow busy?) > > + * > > + * One idea would be to split data in chunks, and then wait 1-2mS between > > + * chunks (we hope not to exceed 30mS delay for any reason - which should > > + * be pretty a lot of time for us), and eventually retry in case the BNO055 > > + * gets upset for any reason. This seems to work in avoiding the overflow > > + * errors, but indeed it seems slower than just perform a retry when an overflow > > + * error occur. > > + * In particular I saw these scenarios: > > + * 1) If we send 2 bytes per time, then the IMU never(?) overflows. > > + * 2) If we send 4 bytes per time (i.e. the full header), then the IMU could > > + * overflow, but it seem to sink all 4 bytes, then it returns error. > > + * 3) If we send more than 4 bytes, the IMU could overflow, and I saw it sending > > + * error after 4 bytes are sent; we have troubles in synchronizing again, > > + * because we are still sending data, and the IMU interprets it as the 1st > > + * byte of a new command. > > + * > > + * So, we workaround all this in the following way: > > + * In case of read we don't split the header but we rely on retries; This seems > > + * convenient for data read (where we TX only the hdr). > > + * For TX we split the transmission in 2-bytes chunks so that, we should not > > + * only avoid case 2 (which is still manageable), but we also hopefully avoid > > + * case 3, that would be by far worse. > > + */ > > + > > +/* > > + * Read operation overhead: > > + * 4 bytes req + 2byte resp hdr. > > + * 6 bytes = 60 bit (considering 1start + 1stop bits). > > + * 60/115200 = ~520uS. > > + * > > + * In 520uS we could read back about 34 bytes that means 3 samples, this means > > + * that in case of scattered read in which the gap is 3 samples or less it is > > + * still convenient to go for a burst. > > + * We have to take into account also IMU response time - IMU seems to be often > > + * reasonably quick to respond, but sometimes it seems to be in some "critical > > + * section" in which it delays handling of serial protocol. > > + * By experiment, it seems convenient to burst up to about 5/6-samples-long gap. > > + */ > > + > > +#define BNO055_SL_XFER_BURST_BREAK_THRESHOLD 6 > > + > > +struct bno055_sl_priv { > > + struct serdev_device *serdev; > > + struct completion cmd_complete; > > + enum { > > + CMD_NONE, > > + CMD_READ, > > + CMD_WRITE, > > + } expect_response; > > + int expected_data_len; > > + u8 *response_buf; > > + > > + /** > > + * enum cmd_status - represent the status of a command sent to the HW. > > + * @STATUS_OK: The command executed successfully. > > + * @STATUS_FAIL: The command failed: HW responded with an error. > > + * @STATUS_CRIT: The command failed: the serial communication failed. > > + */ > > + enum { > > + STATUS_OK = 0, > > + STATUS_FAIL = 1, > > + STATUS_CRIT = -1 > > + } cmd_status; > > + struct mutex loc