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
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 <[email protected]>
Cc: Andrea Merello <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Matt Ranostay <[email protected]>
Cc: Andy Shevchenko <[email protected]>
Cc: Vlad Dogaru <[email protected]>
Cc: [email protected]
Cc: [email protected]
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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
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 <[email protected]>
Cc: Andrea Merello <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Matt Ranostay <[email protected]>
Cc: Andy Shevchenko <[email protected]>
Cc: Vlad Dogaru <[email protected]>
Cc: [email protected]
Cc: [email protected]
---
.../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 <[email protected]>
+
+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
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 <[email protected]>
Cc: Andrea Merello <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Matt Ranostay <[email protected]>
Cc: Andy Shevchenko <[email protected]>
Cc: Vlad Dogaru <[email protected]>
Cc: [email protected]
Cc: [email protected]
---
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 <[email protected]>
+ *
+ * This driver is besed on
+ * Plantower PMS7003 particulate matter sensor driver
+ * Which is
+ * Copyright (c) Tomasz Duszynski <[email protected]>
+ */
+
+#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 <[email protected]>");
+MODULE_DESCRIPTION("Bosch BNO055 serdev interface");
+MODULE_LICENSE("GPL v2");
--
2.17.1
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 <[email protected]>
Cc: Andrea Merello <[email protected]>
Cc: Rob Herring <[email protected]>
Cc: Matt Ranostay <[email protected]>
Cc: Andy Shevchenko <[email protected]>
Cc: Vlad Dogaru <[email protected]>
Cc: [email protected]
Cc: [email protected]
---
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
On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <[email protected]> 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 <[email protected]>
> Cc: Andrea Merello <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Matt Ranostay <[email protected]>
> Cc: Andy Shevchenko <[email protected]>
> Cc: Vlad Dogaru <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
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 <[email protected]>
> + *
> + * 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
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 <[email protected]>
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/[email protected]
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 <[email protected]>
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/[email protected]
On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <[email protected]> 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 <[email protected]>
> Cc: Andrea Merello <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Matt Ranostay <[email protected]>
> Cc: Andy Shevchenko <[email protected]>
> Cc: Vlad Dogaru <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> ---
> 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 <[email protected]>
> + *
> + * 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 <[email protected]>");
> +MODULE_DESCRIPTION("Bosch BNO055 driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/iio/imu/bno055/bno055.h b/drivers/iio/imu/bno055/bno055.h
> new file mode 100644
> index 000000000000..163ab8068e7c
> --- /dev/null
> +++ b/drivers/iio/imu/bno055/bno055.h
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +#ifndef __BNO055_H__
> +#define __BNO055_H__
> +
> +#include <linux/device.h>
> +#include <linux/regmap.h>
> +
> +int bno055_probe(struct device *dev, struct regmap *regmap, int irq,
> + int xfer_burst_break_thr);
> +extern const struct regmap_config bno055_regmap_config;
> +
> +#endif
> --
> 2.17.1
>
Il giorno gio 15 lug 2021 alle ore 18:50 Andy Shevchenko
<[email protected]> ha scritto:
>
> On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <[email protected]> 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 <[email protected]>
> > Cc: Andrea Merello <[email protected]>
> > Cc: Rob Herring <[email protected]>
> > Cc: Matt Ranostay <[email protected]>
> > Cc: Andy Shevchenko <[email protected]>
> > Cc: Vlad Dogaru <[email protected]>
> > Cc: [email protected]
> > Cc: [email protected]
>
> 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 <[email protected]>
> > + *
> > + * Portions of this driver are taken from the BNO055 driver patch
> > + * from Vlad Dogaru which is Copyright (c) 2016, Intel Corporation.
> > + *
> > + * This driver is also based on BMI160 driver, which is:
> > + * Copyright (c) 2016, Intel Corporation.
> > + * Copyright (c) 2019, Martin Kelly.
> > + */
>
> ...
>
> > +#include <linux/clk.h>
> > +#include <linux/firmware.h>
> > +#include <linux/gpio/consumer.h>
>
> > +#include <linux/iio/iio.h>
> > +#include <linux/iio/triggered_buffer.h>
> > +#include <linux/iio/trigger_consumer.h>
> > +#include <linux/iio/buffer.h>
> > +#include <linux/iio/sysfs.h>
>
> Can you move this group to...
>
> > +#include <linux/irq.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/regmap.h>
> > +#include <linux/util_macros.h>
>
> ...be here?
OK
> > +#include "bno055.h"
>
> ...
>
> > +#define BNO055_CALIB_STAT_MASK 3
>
> GENMASK()
OK
> ...
>
> > +#define BNO055_UNIT_SEL_ANDROID BIT(7)
>
> Android? What does this mean?
Sensors support the so-called "Android" and "Windows" modes. They
differs about pitch direction (CW vs CCW). I'd like to stick close to
the datasheet names, but I can add a comment here.
> ...
>
> > +#define BNO055_CALDATA_LEN (BNO055_CALDATA_END - BNO055_CALDATA_START + 1)
>
> Can you put just a plain number?
OK
> ...
>
> > +#define BNO055_ACC_CONFIG_LPF_MASK 0x1C
>
> GENMASK() here and everywhere where it makes sense.
OK.
> ...
>
> > +#define BNO055_UID_LEN (0xF)
>
> Useless parentheses. If the LEN is a plain number, use decimal, if
> it's limited by register width, use the form of (BIT(x) - 1). In such
> a case it's easy to see how many bits are used for it.
It's byte number, defined by how many 8-bits registers make up the
UID. I'll go for a decimal and I'll drop the parentheses.
> ...
>
> > + int i;
> > + int best_idx, best_delta, delta;
> > + int first = 1;
>
> Use reversed xmas tree order.
Looks like the kernel code is plenty of declarations in random order,
neither I can find any clue about this in coding-style.rst. Where does
this come from?
If that's mandatory then I'll do. If that's about a mere preference,
then I honestly prefer to put all declaration-plus-initialization
after all declarations-olny (but I can use reversed xmas tree order
inside each block, if you want).
>
> > + for (i = 0; i < len; i++) {
> > + delta = abs(arr[i] - val);
> > + if (first || delta < best_delta) {
> > + best_delta = delta;
> > + best_idx = i;
> > + }
> > + first = 0;
> > + }
>
> I think I saw this kind of snippet for the 100th time. Can it be
> factored out to some helper and used by everyone?
I can try to macroize this function and move it to util_macros.h
> ...
>
> > + if ((reg >= 0x8 && reg <= 0x3A) ||
>
> Use names instead of values here and in similar places elsewhere.
When I wrote this, I was actually unsure about which is best :) Do you
have a strong opinion on this?
My point:
Most of this is just about register areas, which are bounded by
addresses - register meaning is of no interest here. Using numerical
addresses here IMO is a little advantageous because it is at least
clear which is the greatest number, and it is less prone to swapping
by mistake start/end registers vrt greater-than/lesser-than comparison
operators.
It's still true that when comparing address against a specific
register address (e.g. reg == BNO055_MAG_CONFIG_REG) there is no
advantage in using numerical addresses, and it can be better to use
names (because e.g. you simply know that specific register is
volatile).
> > + /* when in fusion mode, config is updated by chip */
> > + reg == BNO055_MAG_CONFIG_REG ||
> > + reg == BNO055_ACC_CONFIG_REG ||
> > + reg == BNO055_GYR_CONFIG_REG ||
> > + (reg >= BNO055_CALDATA_START && reg <= BNO055_CALDATA_END))
>
> Please, split this to 3 or more conditionals that are easier to read
> (logically separated).
> Same comment to the rest of the similar functions.
Do you mean splitting into separate if statements? OK.
> ...
>
> > + .selector_mask = 0xff,
>
> GENMASK() ?
OK
> ...
>
> > + if (res && res != -ERESTARTSYS) {
>
> Shouldn't RESTARTSYS be handled on a regmap level?
Can you please elaborate on this?
> ...
>
> > +/* must be called in configuration mode */
> > +int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw)
> > +{
> > + int i;
> > + unsigned int tmp;
> > + u8 cal[BNO055_CALDATA_LEN];
> > + int read, tot_read = 0;
> > + int ret = 0;
> > + char *buf = kmalloc(fw->size + 1, GFP_KERNEL);
> > +
> > + if (!buf)
> > + return -ENOMEM;
> > +
> > + memcpy(buf, fw->data, fw->size);
>
> kmemdup() ?
Ah, OK
>
> > + buf[fw->size] = '\0';
>
> > + for (i = 0; i < BNO055_CALDATA_LEN; i++) {
> > + ret = sscanf(buf + tot_read, "%x%n",
> > + &tmp, &read);
> > + if (ret != 1 || tmp > 0xff) {
> > + ret = -EINVAL;
> > + goto exit;
> > + }
> > + cal[i] = tmp;
> > + tot_read += read;
> > + }
>
> Sounds like NIH hex2bin().
Indeed.. I've failed to find out this helper. Looking at the code it
seems it wouldn't work as drop-in replacement here, because of spaces
in the HEX string. But I might just decide to format the HEX string
without spaces in order to being able to use hex2bin().
>
> > + dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, cal);
> > + ret = regmap_bulk_write(priv->regmap, BNO055_CALDATA_START,
> > + cal, BNO055_CALDATA_LEN);
> > +exit:
> > + kfree(buf);
> > + return ret;
> > +}
>
> ...
>
> > + int ret = bno055_reg_read(priv, reg, &hwval);
> > +
> > + if (ret)
> > + return ret;
>
> In all cases (esp. when resource allocations are involved) better to use
>
> int ret;
>
> ret = func();
> if (foo)
> return ret;
OK (assuming you meant if(ret) actually)
> ...
>
> > + dev_dbg(priv->dev, "WR config - reg, mask, val: 0x%x, 0x%x, 0x%x",
> > + reg, mask, hwval);
>
> Why? Enable regmap trace events for this and drop these unneeded prints.
Good point. Going to drop it.
> ...
>
> > + __le16 raw_val;
>
> > + ret = regmap_bulk_read(priv->regmap, chan->address,
> > + &raw_val, 2);
>
> sizeof(raw_val)
>
> and everywhere where similar cases are.
OK
> > + if (ret < 0)
> > + return ret;
>
> ...
>
> > + case IIO_CHAN_INFO_SAMP_FREQ:
> > + if (chan->type == IIO_MAGN)
> > + return bno055_get_mag_odr(priv, val, val2);
> > + else
> > + return -EINVAL;
>
> Use usual pattern
>
> if (!cond)
> return ERRNO;
> ...
> return bar;
>
OK
>
>
> > + for (i = 0; i < 4; i++)
> > + vals[i] = (s16)le16_to_cpu(raw_vals[i]);
>
> Extract this to be a helper like there are for u32 and u64.
Could you please point me to those helpers? I don't know what you are
referring to.
> ...
>
> > + vals[1] = 1 << 14;
>
> BIT(14) But still magic.
Why magic? there is a comment a few line above explaining this - maybe
I can move it a couple of LOCs below. And BTW conceptually it is about
math (2^14), it has nothing to do with BITs.
> ...
> > + switch (mask) {
> > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
> > + return bno055_set_gyr_lpf(priv, val, val2);
>
> default?
Ah, right.
> > + }
>
> ...
>
> > + struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev));
> > +
> > + return scnprintf(buf, PAGE_SIZE, "%s\n",
> > + (priv->operation_mode != BNO055_OPR_MODE_AMG) ? "20" :
> > + "2 6 8 10 15 20 25 30");
>
> IIO core should do this, besides the fact that it must use sysfs_emit().
> Ditto for the similar.
Ok for sysfs_emit(), thanks. But what do you mean with "IIO core
should do this"? Can you please elaborate?
> ...
>
> > + if (sysfs_streq(buf, "amg"))
> > + priv->operation_mode = BNO055_OPR_MODE_AMG;
> > + else if (sysfs_streq(buf, "fusion"))
> > + priv->operation_mode = BNO055_OPR_MODE_FUSION;
> > + else if (sysfs_streq(buf, "fusion_fmc_off"))
> > + priv->operation_mode = BNO055_OPR_MODE_FUSION_FMC_OFF;
> > + else
> > + return -EINVAL;
>
> Wondering if you may use sysfs_match_string().
Ah, yes. I'll probably need a local array for the various
BNO055_OPR_MODE_* and then I can index it with sysfs_match_string()
return value.
> ...
>
> > + return res ? res : len;
>
> ret, res, ... Be consistent!
Sure; you're right
> Besides that the form
>
> return ret ?: len;
>
> is shorter and better.
>
OK
> ...
>
>
> > + static const char * const calib_status[] = {"bad", "barely enough",
> > + "fair", "good"};
>
> Please use better indentation
>
> static char ... foo[] = {
> { a, b, c, d, }
> };
>
OK, but why nested parentheses?
>
> > + for (size = 0, i = 0; i < BNO055_CALDATA_LEN; i++) {
> > + ret = scnprintf(buf + size,
> > + PAGE_SIZE - size, "%02x%c", data[i],
> > + (i + 1 < BNO055_CALDATA_LEN) ? ' ' : '\n');
> > + if (ret < 0)
> > + return ret;
> > + size += ret;
> > + }
>
> And if it's more than 4/3 kBytes (binary)?
It's few bytes long (binary)
> Isn't it better to use the request_firmware() interface or something similar?
No: I already use request_firmware() for getting the initial
calibration data (if any), but the IMU sometimes (re)calibrates. This
function is for getting current IMU calibration, so we need to read it
from registers (especially you want to get it the 1st time, in order
to create the calibration file that request_firmware() will fetch next
time you boot).
> If IIO doesn't provide the common attributes for this it probably
> should and it has to be a binary one.
I couldn't find anything for it. I wasn't sure whether exposing
calibration data using IIO attribute is something that other drivers
might need to do, hence whether it could make sense to make it generic
or not.. I wasn't even sure that an IIO attribute is the right place
to expose it, indeed :)
> ...
>
> > +static IIO_DEVICE_ATTR_RO(in_magn_sampling_frequency_available,
> > + 0);
>
> Definitely one line.
OK
> ...
>
>
> > + &iio_dev_attr_in_autocalibration_status_gyro.dev_attr.attr,
> > + &iio_dev_attr_in_autocalibration_status_magn.dev_attr.attr,
> > + &iio_dev_attr_in_calibration_data.dev_attr.attr,
> > + NULL,
>
> No comma for terminator line.
OK
> ...
>
> > +/*
> > + * Reads len samples from the HW, stores them in buf starting from buf_idx,
> > + * and applies mask to cull (skip) unneeded samples.
> > + * Updates buf_idx incrementing with the number of stored samples.
> > + * Samples from HW are xferred into buf, then in-place copy on buf is
>
> transferred
OK
> > + * performed in order to cull samples that need to be skipped.
> > + * This avoids copies of the first samples until we hit the 1st sample to skip,
> > + * and also avoids having an extra bounce buffer.
> > + * buf must be able to contain len elements inspite of how many samples we are
>
> in spite of
OK
> > + * going to cull.
> > + */
>
> ...
>
>
> > + /* All chans are made up 1 16bit sample, except for quaternion
>
> 16-bit
OK
>
> Multi-line comment style. And be consistent!
OK
> > + * that is made up 4 16-bit values.
> > + * For us the quaternion CH is just like 4 regular CHs.
> > + * If out read starts past the quaternion make sure to adjust the
> > + * starting offset; if the quaternion is contained in our scan then
> > + * make sure to adjust the read len.
>
> Your lines here like a drunk person. use the space more monotonically.
Do you mean: Assuming you are sticking to the old 80-cols-long lines
(which is still do, unless in few cases), then some lines still have
room for more words, and you could reflow the text ?
> > + */
>
> ...
>
> > + n = ((start_ch + i) == BNO055_SCAN_QUATERNION) ? 4 : 1;
>
> Too many parentheses.
OK
> ...
>
> > + for (j = 0; j < n; j++)
> > + dst[j] = src[j];
>
> NIH memcpy()
Right.
> ...
>
> > + __le16 chans[(BNO055_GRAVITY_DATA_Z_LSB_REG -
> > + BNO055_ACC_DATA_X_LSB_REG) / 2];
>
> Can you define separately what's inside square brackets?
OK
> ...
>
> > + while (!finish) {
> > + end = find_next_zero_bit(iio_dev->active_scan_mask,
> > + iio_dev->masklength, start);
> > + if (end == iio_dev->masklength) {
> > + finish = true;
>
> NIH for_each_clear_bit().
Not sure it is convenient to use for_each_clear_bit(): Here we're
searching for contiguous blocks of set-bits, and we only want indexes
of first and last set-bit in a block; alternate calls to
find_next_zero_bit() and find_next_bit() seem appropriate here to me.
Do you have in mind a better/simpler implementation using or_each_clear_bit() ?
> > + } else {
> > + next = find_next_bit(iio_dev->active_scan_mask,
> > + iio_dev->masklength, end);
> > + if (next == iio_dev->masklength) {
> > + finish = true;
>
> Ditto.
see above.
>
> > + } else {
> > + quat = ((next > BNO055_SCAN_QUATERNION) &&
> > + (end <= BNO055_SCAN_QUATERNION)) ? 3 : 0;
> > + thr_hit = (next - end + quat) >
> > + priv->xfer_burst_break_thr;
> > + }
> > + }
> > +
> > + if (thr_hit || finish) {
> > + mask = *iio_dev->active_scan_mask >> xfer_start;
> > + ret = bno055_scan_xfer(priv, xfer_start,
> > + end - xfer_start,
> > + mask, buf.chans, &buf_idx);
> > + if (ret)
> > + goto done;
> > + xfer_start = next;
> > + }
> > + start = next;
> > + }
>
> ...
>
> > + /* base name + separator + UID + ext + zero */
> > + char fw_name_buf[sizeof(BNO055_FW_NAME BNO055_FW_EXT) +
> > + BNO055_UID_LEN * 2 + 1 + 1];
>
> Perhaps devm_kasprintf()?
Wouldn't this keep the buffer allocated until the device is removed?
We just need this buffer while probing.
> ...
>
> > + memset(priv, 0, sizeof(*priv));
>
> Why?!
IIRC a leftover from a time in which nothing worked and I was doubting
about priv being really zeroed at allocation time. Will remove..
> ...
>
> > + if (IS_ERR(rst) && (PTR_ERR(rst) != -EPROBE_DEFER)) {
> > + dev_err(dev, "Failed to get reset GPIO");
> > + return PTR_ERR(rst);
> > + }
>
> if (IS_ERR(...))
> return dev_err_probe(...);
Sure; I never saw this before, but it looks good.
> ...
>
> > + if (IS_ERR(priv->clk) && (PTR_ERR(priv->clk) != -EPROBE_DEFER)) {
> > + dev_err(dev, "Failed to get CLK");
> > + return PTR_ERR(priv->clk);
> > + }
>
> Ditto.
Sure
> ...
>
> > + clk_prepare_enable(priv->clk);
>
> Missed clk_disabled_unprepare() from all below error paths.
> Use devm_add_action_or_reset() approach.
I indeed use devm_add_action_or_reset(), and clock is disabled and
unprepared in its action callback (but snooping in Alexandru's
comments in next mail I see there is still something to be addressed
about this).
> ...
>
> > + dev_dbg(dev, "Found BMO055 chip");
>
> Useless noise and LOC.
Will drop it.
> ...
>
> > + dev_info(dev, "unique ID: %*ph", BNO055_UID_LEN, priv->uid);
>
> Can it be printed somewhere together with firmware revision?
Yes, I can group those two prints.
> ...
>
> > + sprintf(fw_name_buf, BNO055_FW_NAME "-%*phN" BNO055_FW_EXT,
>
> Simply define a format string as FW_FMT somewhere above and use it here.
OK (but BNO055_FW_NAME and BNO055_FW_EXT contribute to build up two
different strings, so they will not go away, rather they will be used
in both FW_FMT and the other one definitions)
> > + BNO055_UID_LEN, priv->uid);
>
> ...
>
> > + dev_notice(dev, "Failed to load calibration data firmware file; this has nothing to do with IMU main firmware.");
> > + dev_notice(dev, "You can calibrate your IMU (look for 'in_autocalibration_status*' files in sysfs) and then copy 'in_calibration_data' to your firmware file");
>
> '\n' exists on purpose.
Ah, I initially did that, but IIRC I saw a bad-looking indentation or
something like that in my kernel log while using "\n".. Then I just
forgot about this.. I'll try again with "\n"..
> ...
>
> > +#include <linux/device.h>
>
> No user of this.
Will drop this
> > +#include <linux/regmap.h>
> > +
> > +int bno055_probe(struct device *dev, struct regmap *regmap, int irq,
> > + int xfer_burst_break_thr);
> > +extern const struct regmap_config bno055_regmap_config;
>
> --
> With Best Regards,
> Andy Shevchenko
Il giorno ven 16 lug 2021 alle ore 09:24 Alexandru Ardelean
<[email protected]> ha scritto:
>
> On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <[email protected]> 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 <[email protected]>
> > Cc: Andrea Merello <[email protected]>
> > Cc: Rob Herring <[email protected]>
> > Cc: Matt Ranostay <[email protected]>
> > Cc: Andy Shevchenko <[email protected]>
> > Cc: Vlad Dogaru <[email protected]>
> > Cc: [email protected]
> > Cc: [email protected]
> > ---
> > 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 <[email protected]>
> > + *
> > + * 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 <[email protected]>");
> > +MODULE_DESCRIPTION("Bosch BNO055 driver");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/drivers/iio/imu/bno055/bno055.h b/drivers/iio/imu/bno055/bno055.h
> > new file mode 100644
> > index 000000000000..163ab8068e7c
> > --- /dev/null
> > +++ b/drivers/iio/imu/bno055/bno055.h
> > @@ -0,0 +1,12 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +#ifndef __BNO055_H__
> > +#define __BNO055_H__
> > +
> > +#include <linux/device.h>
> > +#include <linux/regmap.h>
> > +
> > +int bno055_probe(struct device *dev, struct regmap *regmap, int irq,
> > + int xfer_burst_break_thr);
> > +extern const struct regmap_config bno055_regmap_config;
> > +
> > +#endif
> > --
> > 2.17.1
> >
On Fri, Jul 16, 2021 at 11:19:31AM +0200, Andrea Merello wrote:
> Il giorno gio 15 lug 2021 alle ore 18:50 Andy Shevchenko
> <[email protected]> ha scritto:
> > On Thu, Jul 15, 2021 at 5:21 PM Andrea Merello <[email protected]> wrote:
...
> > > Cc: [email protected]
> > > Cc: [email protected]
> >
> > 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
On Thu, 15 Jul 2021 16:17:39 +0200
Andrea Merello <[email protected]> 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 <[email protected]>
> Cc: Andrea Merello <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Matt Ranostay <[email protected]>
> Cc: Andy Shevchenko <[email protected]>
> Cc: Vlad Dogaru <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> ---
> 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_ */
> -
On Thu, 15 Jul 2021 16:17:40 +0200
Andrea Merello <[email protected]> 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 <[email protected]>
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 <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Matt Ranostay <[email protected]>
> Cc: Andy Shevchenko <[email protected]>
> Cc: Vlad Dogaru <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> ---
> 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 <[email protected]>
> + *
> + * 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 <[email protected]>");
> +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
On Thu, 15 Jul 2021 16:17:41 +0200
Andrea Merello <[email protected]> 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 <[email protected]>
> Cc: Andrea Merello <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Matt Ranostay <[email protected]>
> Cc: Andy Shevchenko <[email protected]>
> Cc: Vlad Dogaru <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> ---
> .../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 <[email protected]>
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>;
> + };
On Thu, 15 Jul 2021 16:17:42 +0200
Andrea Merello <[email protected]> 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 <[email protected]>
> Cc: Andrea Merello <[email protected]>
> Cc: Rob Herring <[email protected]>
> Cc: Matt Ranostay <[email protected]>
> Cc: Andy Shevchenko <[email protected]>
> Cc: Vlad Dogaru <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
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 <[email protected]>
> + *
> + * This driver is besed on
> + * Plantower PMS7003 particulate matter sensor driver
> + * Which is
> + * Copyright (c) Tomasz Duszynski <[email protected]>
> + */
> +
> +#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 <[email protected]>");
> +MODULE_DESCRIPTION("Bosch BNO055 serdev interface");
> +MODULE_LICENSE("GPL v2");
Just few inline comments; implicitly OK for others.
Il giorno ven 16 lug 2021 alle ore 14:39 Andy Shevchenko
<[email protected]> ha scritto:
>
>
> > > Useless parentheses. If the LEN is a plain number, use decimal, if
> > > it's limited by register width, use the form of (BIT(x) - 1). In such
> > > a case it's easy to see how many bits are used for it.
> >
> > It's byte number, defined by how many 8-bits registers make up the
> > UID. I'll go for a decimal and I'll drop the parentheses.
>
> 15 seems the right one then?
Isn't it 16? From my understanding of the datasheet registers involved
are from 0x50 to 0x5F.
>
> > > > + if (res && res != -ERESTARTSYS) {
> > >
> > > Shouldn't RESTARTSYS be handled on a regmap level?
> >
> > Can you please elaborate on this?
>
> I meant if you need to take care about this it seems to me that it has to be
> thought of on regmap level. I.o.w. what is the rationale behind this additional
> check?
The regmap_bus write() and read() implementations wait for an
interruptible completion, which is completed when a response from the
IMU is received. In practice by hitting Ctrl-C at the "right" moment I
got my kernel log polluted with dev_err() telling me the regmap
operation failed, but in this specific case there was nothing wrong:
it's just being aborted. Still, in all other error case I would like
to know. This is the rationale behind this check. The ERESTARTSYS
error have anyway to actually propagate in order to notify the caller
that the read/write just didn't complete.
If you mean move the check+dev_err() in bno055_sl.c regmap_bus read()
and write() ops, that is fine; my original point for putting it where
it is now, was because I was wondering whether this has to be common
to the (not yet here) I2C support code.
> ...
>
> > > Sounds like NIH hex2bin().
> >
> > Indeed.. I've failed to find out this helper. Looking at the code it
> > seems it wouldn't work as drop-in replacement here, because of spaces
> > in the HEX string. But I might just decide to format the HEX string
> > without spaces in order to being able to use hex2bin().
>
> I'm not even sure why it's in ASCII instead being directly binary file.
That was almost a coin-flip for me. Just, being a few bytes, I decided
to make them printable: If I load this driver for the 1st time, and
start poking around in it's sysfs, cat-ting random stuff to give a
look, I would just find a HEX line more friendly that a binary chunk
on my console.
.. But If you think it's better, I'll go for binary without any hesitation..
>
> > > IIO core should do this, besides the fact that it must use sysfs_emit().
> > > Ditto for the similar.
> >
> > Ok for sysfs_emit(), thanks. But what do you mean with "IIO core
> > should do this"? Can you please elaborate?
>
> I believe that IIO has a generic method to print tables via sysfs. AFAIR it is
> done via "_avail".
Ah, do you refer to the read_avail() operation in iio_info?
I'll try to go with it; I wasn't aware of that, thank you.
Il giorno sab 17 lug 2021 alle ore 17:30 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 15 Jul 2021 16:17:40 +0200
> Andrea Merello <[email protected]> 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 <[email protected]>
>
> 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 <[email protected]>
> > Cc: Rob Herring <[email protected]>
> > Cc: Matt Ranostay <[email protected]>
> > Cc: Andy Shevchenko <[email protected]>
> > Cc: Vlad Dogaru <[email protected]>
> > Cc: [email protected]
> > Cc: [email protected]
> > ---
> > 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 <[email protected]>
> > + *
> > + * 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 <[email protected]>");
> > +MODULE_DESCRIPTION("Bosch BNO055 driver");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/drivers/iio/imu/bno055/bno055.h b/drivers/iio/imu/bno055/bno055.h
> > new file mode 100644
> > index 000000000000..163ab8068e7c
> > --- /dev/null
> > +++ b/drivers/iio/imu/bno055/bno055.h
> > @@ -0,0 +1,12 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +#ifndef __BNO055_H__
> > +#define __BNO055_H__
> > +
> > +#include <linux/device.h>
>
> Just use
> struct device;
> and don't include device.h.
OK
> > +#include <linux/regmap.h>
> > +
> > +int bno055_probe(struct device *dev, struct regmap *regmap, int irq,
> > + int xfer_burst_break_thr);
> > +extern const struct regmap_config bno055_regmap_config;
> > +
> > +#endif
>
Il giorno sab 17 lug 2021 alle ore 17:48 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 15 Jul 2021 16:17:42 +0200
> Andrea Merello <[email protected]> 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 <[email protected]>
> > Cc: Andrea Merello <[email protected]>
> > Cc: Rob Herring <[email protected]>
> > Cc: Matt Ranostay <[email protected]>
> > Cc: Andy Shevchenko <[email protected]>
> > Cc: Vlad Dogaru <[email protected]>
> > Cc: [email protected]
> > Cc: [email protected]
>
> 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 <[email protected]>
> > + *
> > + * This driver is besed on
> > + * Plantower PMS7003 particulate matter sensor driver
> > + * Which is
> > + * Copyright (c) Tomasz Duszynski <[email protected]>
> > + */
> > +
> > +#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 <[email protected]>");
> > +MODULE_DESCRIPTION("Bosch BNO055 serdev interface");
> > +MODULE_LICENSE("GPL v2");
>
Il giorno gio 15 lug 2021 alle ore 18:50 Andy Shevchenko
<[email protected]> 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
Il giorno sab 17 lug 2021 alle ore 17:36 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 15 Jul 2021 16:17:41 +0200
> Andrea Merello <[email protected]> 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 <[email protected]>
> > Cc: Andrea Merello <[email protected]>
> > Cc: Rob Herring <[email protected]>
> > Cc: Matt Ranostay <[email protected]>
> > Cc: Andy Shevchenko <[email protected]>
> > Cc: Vlad Dogaru <[email protected]>
> > Cc: [email protected]
> > Cc: [email protected]
> > ---
> > .../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 <[email protected]>
>
> 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>;
> > + };
>
> > > > + for (i = 0; i < 4; i++)
> > > > + vals[i] = (s16)le16_to_cpu(raw_vals[i]);
> > >
> > > Extract this to be a helper like there are for u32 and u64.
> >
> > Could you please point me to those helpers? I don't know what you are
> > referring to.
>
> Read include/linux/byteorder/generic.h to the end.
I realized that implementing an helper like the other ones wouldn't
work in this specific case: I'd say a reasonable helper would take a
ptr to a s16 as its destination argument, but here we are assigning to
a int vector; so our brand new helper would be of no use here.
What I can do is to implement a macroized helper that would have no
issues wrt ptr type; I guess something like this:
#define le16_to_cpu_signed_array(dst, src, len) \
({ \
size_t __i; \
for (__i = 0; __i < len; __i++) \
dst[__i] = (s16)le16_to_cpu(src[__i]); \
})
What's your opinion here? should I go with something like this or do
you prefer to let the open-coded implementation stay in this specific
case?
On Mon, Jul 19, 2021 at 11:02:07AM +0200, Andrea Merello wrote:
> Il giorno gio 15 lug 2021 alle ore 18:50 Andy Shevchenko
> <[email protected]> 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
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
> <[email protected]> ha scritto:
> > On Thu, 15 Jul 2021 16:17:42 +0200
> > Andrea Merello <[email protected]> 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
Il giorno lun 19 lug 2021 alle ore 13:56 Andy Shevchenko
<[email protected]> 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
> > <[email protected]> ha scritto:
> > > On Thu, 15 Jul 2021 16:17:42 +0200
> > > Andrea Merello <[email protected]> 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
>
>
Il giorno lun 19 lug 2021 alle ore 13:48 Andy Shevchenko
<[email protected]> 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
> > <[email protected]> ha scritto:
> >
> > >
> > > > +/* must be called in configuration mode */
> > > > +int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw)
> > > > +{
> > > > + int i;
> > > > + unsigned int tmp;
> > > > + u8 cal[BNO055_CALDATA_LEN];
> > > > + int read, tot_read = 0;
> > > > + int ret = 0;
> > > > + char *buf = kmalloc(fw->size + 1, GFP_KERNEL);
> > > > +
> > > > + if (!buf)
> > > > + return -ENOMEM;
> > > > +
> > > > + memcpy(buf, fw->data, fw->size);
> > >
> > > kmemdup() ?
> > >
> >
> > As a second thought: no, the whole point of reallocating and copying
> > here, is that we want to allocate an extra byte; kmemdup() will
> > allocate and copy only the very same amount of memory
>
> kmemdup_nul() then.
That's one seems suitable. Thank you.
>
> --
> With Best Regards,
> Andy Shevchenko
>
>
On Mon, Jul 19, 2021 at 02:59:30PM +0200, Andrea Merello wrote:
> Il giorno lun 19 lug 2021 alle ore 13:56 Andy Shevchenko
> <[email protected]> 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
> > > <[email protected]> ha scritto:
> > > > On Thu, 15 Jul 2021 16:17:42 +0200
> > > > Andrea Merello <[email protected]> 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
Il giorno lun 19 lug 2021 alle ore 16:15 Andy Shevchenko
<[email protected]> 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
> > <[email protected]> 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
> > > > <[email protected]> ha scritto:
> > > > > On Thu, 15 Jul 2021 16:17:42 +0200
> > > > > Andrea Merello <[email protected]> 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
>
>
On Mon, 19 Jul 2021 10:30:26 +0200
Andrea Merello <[email protected]> wrote:
> Il giorno sab 17 lug 2021 alle ore 17:30 Jonathan Cameron
> <[email protected]> ha scritto:
> >
> > On Thu, 15 Jul 2021 16:17:40 +0200
> > Andrea Merello <[email protected]> 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 <[email protected]>
> >
> > 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 <[email protected]>
> > > Cc: Rob Herring <[email protected]>
> > > Cc: Matt Ranostay <[email protected]>
> > > Cc: Andy Shevchenko <[email protected]>
> > > Cc: Vlad Dogaru <[email protected]>
> > > Cc: [email protected]
> > > Cc: [email protected]
> > > ---
> > > 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 <[email protected]>
> > > + *
> > > + * 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 <[email protected]>");
> > > +MODULE_DESCRIPTION("Bosch BNO055 driver");
> > > +MODULE_LICENSE("GPL v2");
> > > diff --git a/drivers/iio/imu/bno055/bno055.h b/drivers/iio/imu/bno055/bno055.h
> > > new file mode 100644
> > > index 000000000000..163ab8068e7c
> > > --- /dev/null
> > > +++ b/drivers/iio/imu/bno055/bno055.h
> > > @@ -0,0 +1,12 @@
> > > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > > +#ifndef __BNO055_H__
> > > +#define __BNO055_H__
> > > +
> > > +#include <linux/device.h>
> >
> > Just use
> > struct device;
> > and don't include device.h.
>
> OK
>
> > > +#include <linux/regmap.h>
> > > +
> > > +int bno055_probe(struct device *dev, struct regmap *regmap, int irq,
> > > + int xfer_burst_break_thr);
> > > +extern const struct regmap_config bno055_regmap_config;
> > > +
> > > +#endif
> >
just a few of in-line comment below; OK for all the rest of your
comment, thanks!
> > > > +static int bno055_reg_write(struct bno055_priv *priv,
> > > > + unsigned int reg, unsigned int val)
> > > > +{
> > > > + int res = regmap_write(priv->regmap, reg, val);
> > > > +
> > > > + if (res && res != -ERESTARTSYS) {
> > >
> > > I think Andy asked about these, so I won't repeat...
> > > Nice to get rid of those and just be able to make the regmap calls inline though...
> >
> > Ok for inline. I've just answered in another mail to Andy's comments
> > for the rest.
Indeed, so far I couldn't understand what do you really mean. Should I
move those check+dev_err() inside the regmap core layer ?
> > > > + /*
> > > > + * Start in fusion mode (all data available), but with magnetometer auto
> > > > + * calibration switched off, in order not to overwrite magnetometer
> > > > + * calibration data in case one want to keep it untouched.
> > >
> > > Why might you? good to have a default that is what people most commonly want...
> > > If there is a usecase for this then it may be better to have a 'disable autocalibration
> > > and manually reload a fixed calibration' path.
> >
> > I'm not sure whether disabling autocalibration for magnetometer is
> > just a matter of saving some power, or whether this has the purpose of
> > carefully doing the calibration far from magnetic disturbances,
> > avoiding screwing the calibration every time you briefly pass by a
> > piece of iron. I think I found some clues for this second
> > interpretation poking on the internet, but I don't know whether they
> > were right.
>
> It's possible if the calibration routines have much faster response than
> you'd normally expect.
This HW function is called "Fast Magnetometer Calibration".. But I
don't know how fast is it..
> > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr,
> > > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr,
> > >
> > > Hmm. Range typically maps to something else (normally scale, but these smart
> > > sensors can do weird things)
> >
> > Here the scaling doesn't change, just the range. I *think* that by
> > changing range you also get better or worse precision.
>
> oh goody. Make sure the default is maximum range + when you document this
> we will have to be careful to make it clear we don't want this to be used in
> drivers where scale is an option. Perhaps we just put it in a device
> specific ABI file.
>
The default is to run the IMU with fusion mode enabled; in this mode
those parameters are locked by the HW to a given value (which is not
the maximum e.g. in case of accelerometer range).
If the user disables the fusion mode, then those parameters become
tweakable, but shouldn't they just remain at their previous values
(the one set by fusion mode), unless the user change also them?
I.o.w the only chance we have for assigning them a "default" value is
when the fusion is switched off, but this would mean that switching
off fusion mode also has a side effect on those values (which I'm
unsure if we really want to happen).
Andrea
On Mon, 26 Jul 2021 16:36:49 +0200
Andrea Merello <[email protected]> wrote:
> just a few of in-line comment below; OK for all the rest of your
> comment, thanks!
>
> > > > > +static int bno055_reg_write(struct bno055_priv *priv,
> > > > > + unsigned int reg, unsigned int val)
> > > > > +{
> > > > > + int res = regmap_write(priv->regmap, reg, val);
> > > > > +
> > > > > + if (res && res != -ERESTARTSYS) {
> > > >
> > > > I think Andy asked about these, so I won't repeat...
> > > > Nice to get rid of those and just be able to make the regmap calls inline though...
> > >
> > > Ok for inline. I've just answered in another mail to Andy's comments
> > > for the rest.
>
> Indeed, so far I couldn't understand what do you really mean. Should I
> move those check+dev_err() inside the regmap core layer ?
Better to move any necessary print to the caller of the reg_write() where any
error message can often give more information. Adding wrappers just to print
an error message normally mostly serves to make the code a little harder to
review.
>
> > > > > + /*
> > > > > + * Start in fusion mode (all data available), but with magnetometer auto
> > > > > + * calibration switched off, in order not to overwrite magnetometer
> > > > > + * calibration data in case one want to keep it untouched.
> > > >
> > > > Why might you? good to have a default that is what people most commonly want...
> > > > If there is a usecase for this then it may be better to have a 'disable autocalibration
> > > > and manually reload a fixed calibration' path.
> > >
> > > I'm not sure whether disabling autocalibration for magnetometer is
> > > just a matter of saving some power, or whether this has the purpose of
> > > carefully doing the calibration far from magnetic disturbances,
> > > avoiding screwing the calibration every time you briefly pass by a
> > > piece of iron. I think I found some clues for this second
> > > interpretation poking on the internet, but I don't know whether they
> > > were right.
> >
> > It's possible if the calibration routines have much faster response than
> > you'd normally expect.
>
> This HW function is called "Fast Magnetometer Calibration".. But I
> don't know how fast is it..
Nice - got to love informative datasheets :)
>
>
> > > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr,
> > > > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr,
> > > >
> > > > Hmm. Range typically maps to something else (normally scale, but these smart
> > > > sensors can do weird things)
> > >
> > > Here the scaling doesn't change, just the range. I *think* that by
> > > changing range you also get better or worse precision.
> >
> > oh goody. Make sure the default is maximum range + when you document this
> > we will have to be careful to make it clear we don't want this to be used in
> > drivers where scale is an option. Perhaps we just put it in a device
> > specific ABI file.
> >
>
> The default is to run the IMU with fusion mode enabled; in this mode
> those parameters are locked by the HW to a given value (which is not
> the maximum e.g. in case of accelerometer range).
>
> If the user disables the fusion mode, then those parameters become
> tweakable, but shouldn't they just remain at their previous values
> (the one set by fusion mode), unless the user change also them?
That makes sense to me.
>
> I.o.w the only chance we have for assigning them a "default" value is
> when the fusion is switched off, but this would mean that switching
> off fusion mode also has a side effect on those values (which I'm
> unsure if we really want to happen).
Thanks for the explanation. Ok. Fine to have the range here, but please
sanity check we have appropriate ABI documentation in the main ABI doc
Documentation/ABI/testing/sysfs-bus-iio.
One thing to think about is how range would generalize. These sensors are
symmetric, but not all are - a range that is higher in postive than negative
is definitely possible. Perhaps we need to name it to make it clear we are
talking magnitude here?
Ideally it should state something about range should be used only when it has no
affect on scaling. Hopefully we'll not get a device where it has an affect
but it's non linear as that doesn't make any sense. I'd imagine a range
control in hardware either is proportional to the scale, or has no affect on
it as here.
Thanks,
Jonathan
>
> Andrea
Il giorno sab 31 lug 2021 alle ore 19:58 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Mon, 26 Jul 2021 16:36:49 +0200
> Andrea Merello <[email protected]> wrote:
>
> > just a few of in-line comment below; OK for all the rest of your
> > comment, thanks!
> >
> > > > > > +static int bno055_reg_write(struct bno055_priv *priv,
> > > > > > + unsigned int reg, unsigned int val)
> > > > > > +{
> > > > > > + int res = regmap_write(priv->regmap, reg, val);
> > > > > > +
> > > > > > + if (res && res != -ERESTARTSYS) {
> > > > >
> > > > > I think Andy asked about these, so I won't repeat...
> > > > > Nice to get rid of those and just be able to make the regmap calls inline though...
> > > >
> > > > Ok for inline. I've just answered in another mail to Andy's comments
> > > > for the rest.
> >
> > Indeed, so far I couldn't understand what do you really mean. Should I
> > move those check+dev_err() inside the regmap core layer ?
>
> Better to move any necessary print to the caller of the reg_write() where any
> error message can often give more information. Adding wrappers just to print
> an error message normally mostly serves to make the code a little harder to
> review.
Isn't this like doing a cut-and-paste of check+dev_err() in more than
a dozen places in the code?
If you just want more information about the caller then we could
macroize those functions, so they can also print the caller code line
number (or they could accept an additional argument, which is the code
line number to print, and then a macro helper that adds that last
argument can be used to invoke them).. But this wouldn't address your
second point..
> >
> > > > > > + /*
> > > > > > + * Start in fusion mode (all data available), but with magnetometer auto
> > > > > > + * calibration switched off, in order not to overwrite magnetometer
> > > > > > + * calibration data in case one want to keep it untouched.
> > > > >
> > > > > Why might you? good to have a default that is what people most commonly want...
> > > > > If there is a usecase for this then it may be better to have a 'disable autocalibration
> > > > > and manually reload a fixed calibration' path.
> > > >
> > > > I'm not sure whether disabling autocalibration for magnetometer is
> > > > just a matter of saving some power, or whether this has the purpose of
> > > > carefully doing the calibration far from magnetic disturbances,
> > > > avoiding screwing the calibration every time you briefly pass by a
> > > > piece of iron. I think I found some clues for this second
> > > > interpretation poking on the internet, but I don't know whether they
> > > > were right.
> > >
> > > It's possible if the calibration routines have much faster response than
> > > you'd normally expect.
> >
> > This HW function is called "Fast Magnetometer Calibration".. But I
> > don't know how fast is it..
>
> Nice - got to love informative datasheets :)
>
> >
> >
> > > > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr,
> > > > > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr,
> > > > >
> > > > > Hmm. Range typically maps to something else (normally scale, but these smart
> > > > > sensors can do weird things)
> > > >
> > > > Here the scaling doesn't change, just the range. I *think* that by
> > > > changing range you also get better or worse precision.
> > >
> > > oh goody. Make sure the default is maximum range + when you document this
> > > we will have to be careful to make it clear we don't want this to be used in
> > > drivers where scale is an option. Perhaps we just put it in a device
> > > specific ABI file.
> > >
> >
> > The default is to run the IMU with fusion mode enabled; in this mode
> > those parameters are locked by the HW to a given value (which is not
> > the maximum e.g. in case of accelerometer range).
> >
> > If the user disables the fusion mode, then those parameters become
> > tweakable, but shouldn't they just remain at their previous values
> > (the one set by fusion mode), unless the user change also them?
>
> That makes sense to me.
>
> >
> > I.o.w the only chance we have for assigning them a "default" value is
> > when the fusion is switched off, but this would mean that switching
> > off fusion mode also has a side effect on those values (which I'm
> > unsure if we really want to happen).
>
> Thanks for the explanation. Ok. Fine to have the range here, but please
> sanity check we have appropriate ABI documentation in the main ABI doc
> Documentation/ABI/testing/sysfs-bus-iio.
So you are inclined to generalize this thing, rather than keeping it
as bno055 specific..
> One thing to think about is how range would generalize. These sensors are
> symmetric, but not all are - a range that is higher in postive than negative
> is definitely possible. Perhaps we need to name it to make it clear we are
> talking magnitude here?
I'm open to suggestions here.
The word "range" in my mind just recalls the idea of a minimum/maximum
interval in which the sensor works, without recalling me the idea of
"scaling" or changes to the unit too much; this is why I originally
chosen this name, but that might be just mine personal taste.
> Ideally it should state something about range should be used only when it has no
> affect on scaling. Hopefully we'll not get a device where it has an affect
> but it's non linear as that doesn't make any sense. I'd imagine a range
> control in hardware either is proportional to the scale, or has no affect on
> it as here.
I may have a wrong memory here, but I think I've seen something like
this on some current-sense chips, which were available for various
current ranges, but their output scale changed in non linear way wrt
the range.
Or I can imagine a device in which you can set the internal amplifier
gain, and this will affect both the scale (because of the amplifier
gain itself) and the range (because the amplifier output does saturate
at a different voltage) with a linear relationship, but there is also
another bound: the physical sensor attached to the amplifier
mechanically clamp to a given max value, that happen to be less than
the saturation point of the amp at is minimum gain. This would lead to
a non-linear net relationship between scale and range because at the
minimum gain the range doesn't extends as much as you would expect.
.. But even in non linear cases, just having the scale may be OK..
Maybe we can just say that range can be used only if there are at
least two values for which we have the same scale value; otherwise
just having the scale attribute would suffice and thus we stick on
that.. We may think about allowing a RO range attribute, alongside the
scale attribute, for non obvious cases.
> Thanks,
>
> Jonathan
>
> >
> > Andrea
>
On Wed, 4 Aug 2021 12:06:46 +0200
Andrea Merello <[email protected]> wrote:
> Il giorno sab 31 lug 2021 alle ore 19:58 Jonathan Cameron
> <[email protected]> ha scritto:
> >
> > On Mon, 26 Jul 2021 16:36:49 +0200
> > Andrea Merello <[email protected]> wrote:
> >
> > > just a few of in-line comment below; OK for all the rest of your
> > > comment, thanks!
> > >
> > > > > > > +static int bno055_reg_write(struct bno055_priv *priv,
> > > > > > > + unsigned int reg, unsigned int val)
> > > > > > > +{
> > > > > > > + int res = regmap_write(priv->regmap, reg, val);
> > > > > > > +
> > > > > > > + if (res && res != -ERESTARTSYS) {
> > > > > >
> > > > > > I think Andy asked about these, so I won't repeat...
> > > > > > Nice to get rid of those and just be able to make the regmap calls inline though...
> > > > >
> > > > > Ok for inline. I've just answered in another mail to Andy's comments
> > > > > for the rest.
> > >
> > > Indeed, so far I couldn't understand what do you really mean. Should I
> > > move those check+dev_err() inside the regmap core layer ?
> >
> > Better to move any necessary print to the caller of the reg_write() where any
> > error message can often give more information. Adding wrappers just to print
> > an error message normally mostly serves to make the code a little harder to
> > review.
>
> Isn't this like doing a cut-and-paste of check+dev_err() in more than
> a dozen places in the code?
>
> If you just want more information about the caller then we could
> macroize those functions, so they can also print the caller code line
> number (or they could accept an additional argument, which is the code
> line number to print, and then a macro helper that adds that last
> argument can be used to invoke them).. But this wouldn't address your
> second point..
It's a trade off between reviewability which these wrappers make worse
and short code.
My personal preference is don't bother with messages on simple reg read /write
failures. If it happens you either get an error reported to userspace and
can do some more debug, or the driver doesn't probe - again, more debug to
be done even if you know it was a read or write.
>
> > >
> > > > > > > + /*
> > > > > > > + * Start in fusion mode (all data available), but with magnetometer auto
> > > > > > > + * calibration switched off, in order not to overwrite magnetometer
> > > > > > > + * calibration data in case one want to keep it untouched.
> > > > > >
> > > > > > Why might you? good to have a default that is what people most commonly want...
> > > > > > If there is a usecase for this then it may be better to have a 'disable autocalibration
> > > > > > and manually reload a fixed calibration' path.
> > > > >
> > > > > I'm not sure whether disabling autocalibration for magnetometer is
> > > > > just a matter of saving some power, or whether this has the purpose of
> > > > > carefully doing the calibration far from magnetic disturbances,
> > > > > avoiding screwing the calibration every time you briefly pass by a
> > > > > piece of iron. I think I found some clues for this second
> > > > > interpretation poking on the internet, but I don't know whether they
> > > > > were right.
> > > >
> > > > It's possible if the calibration routines have much faster response than
> > > > you'd normally expect.
> > >
> > > This HW function is called "Fast Magnetometer Calibration".. But I
> > > don't know how fast is it..
> >
> > Nice - got to love informative datasheets :)
> >
> > >
> > >
> > > > > > > + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr,
> > > > > > > + &iio_dev_attr_in_anglvel_range_available.dev_attr.attr,
> > > > > >
> > > > > > Hmm. Range typically maps to something else (normally scale, but these smart
> > > > > > sensors can do weird things)
> > > > >
> > > > > Here the scaling doesn't change, just the range. I *think* that by
> > > > > changing range you also get better or worse precision.
> > > >
> > > > oh goody. Make sure the default is maximum range + when you document this
> > > > we will have to be careful to make it clear we don't want this to be used in
> > > > drivers where scale is an option. Perhaps we just put it in a device
> > > > specific ABI file.
> > > >
> > >
> > > The default is to run the IMU with fusion mode enabled; in this mode
> > > those parameters are locked by the HW to a given value (which is not
> > > the maximum e.g. in case of accelerometer range).
> > >
> > > If the user disables the fusion mode, then those parameters become
> > > tweakable, but shouldn't they just remain at their previous values
> > > (the one set by fusion mode), unless the user change also them?
> >
> > That makes sense to me.
> >
> > >
> > > I.o.w the only chance we have for assigning them a "default" value is
> > > when the fusion is switched off, but this would mean that switching
> > > off fusion mode also has a side effect on those values (which I'm
> > > unsure if we really want to happen).
> >
> > Thanks for the explanation. Ok. Fine to have the range here, but please
> > sanity check we have appropriate ABI documentation in the main ABI doc
> > Documentation/ABI/testing/sysfs-bus-iio.
>
> So you are inclined to generalize this thing, rather than keeping it
> as bno055 specific..
My mistake. I thought we had it already defined but seems we have avoided
it even though we have gotten close a few times ;)
sysfs-bus-iio-bno055 should be fine
>
> > One thing to think about is how range would generalize. These sensors are
> > symmetric, but not all are - a range that is higher in postive than negative
> > is definitely possible. Perhaps we need to name it to make it clear we are
> > talking magnitude here?
>
> I'm open to suggestions here.
It's challenging to define a clean way of specifying two linked values.
We could define rangemax, rangemin and rely on the fact that any bit of IIO
ABI can affect other bits, but that's messy given our only current user
doesn't care.
>
> The word "range" in my mind just recalls the idea of a minimum/maximum
> interval in which the sensor works, without recalling me the idea of
> "scaling" or changes to the unit too much; this is why I originally
> chosen this name, but that might be just mine personal taste.
We may regret it but let's stick with your range definition as is, with suitable
docs and keeping it device specific for now.
>
> > Ideally it should state something about range should be used only when it has no
> > affect on scaling. Hopefully we'll not get a device where it has an affect
> > but it's non linear as that doesn't make any sense. I'd imagine a range
> > control in hardware either is proportional to the scale, or has no affect on
> > it as here.
>
> I may have a wrong memory here, but I think I've seen something like
> this on some current-sense chips, which were available for various
> current ranges, but their output scale changed in non linear way wrt
> the range.
>
> Or I can imagine a device in which you can set the internal amplifier
> gain, and this will affect both the scale (because of the amplifier
> gain itself) and the range (because the amplifier output does saturate
> at a different voltage) with a linear relationship, but there is also
> another bound: the physical sensor attached to the amplifier
> mechanically clamp to a given max value, that happen to be less than
> the saturation point of the amp at is minimum gain. This would lead to
> a non-linear net relationship between scale and range because at the
> minimum gain the range doesn't extends as much as you would expect.
>
> .. But even in non linear cases, just having the scale may be OK..
> Maybe we can just say that range can be used only if there are at
> least two values for which we have the same scale value; otherwise
> just having the scale attribute would suffice and thus we stick on
> that.. We may think about allowing a RO range attribute, alongside the
> scale attribute, for non obvious cases.
For read only, you can use the read_avail approach to provide a range
for the actual channel. We do this for various in kernel consumer
users that need to know the range of values they might get.
That is only ever read only though, because of the difficulty of
allowing rights to two bounds together.
So we have devices where changing the scale will result in
inX_raw_avail changing (IIRC) and that's not necessarily linear.
Jonathan
>
>
> > Thanks,
> >
> > Jonathan
> >
> > >
> > > Andrea
> >
On Wed, Aug 4, 2021 at 7:51 PM Jonathan Cameron
<[email protected]> wrote:
> On Wed, 4 Aug 2021 12:06:46 +0200
> Andrea Merello <[email protected]> wrote:
> > Il giorno sab 31 lug 2021 alle ore 19:58 Jonathan Cameron
> > <[email protected]> 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
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
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 <[email protected]>
---
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: [email protected]
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: [email protected]
+Description:
+ Raw (unscaled) euler angles readings.
--
2.17.1
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 <[email protected]>
---
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
This path adds an I2C driver for communicating to a BNO055 IMU via I2C bus
and it enables the BNO055 core driver to work in this scenario.
Signed-off-by: Andrea Merello <[email protected]>
---
drivers/iio/imu/bno055/Kconfig | 6 ++++
drivers/iio/imu/bno055/Makefile | 1 +
drivers/iio/imu/bno055/bno055_i2c.c | 54 +++++++++++++++++++++++++++++
3 files changed, 61 insertions(+)
create mode 100644 drivers/iio/imu/bno055/bno055_i2c.c
diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig
index 941e43f0368d..87200787d548 100644
--- a/drivers/iio/imu/bno055/Kconfig
+++ b/drivers/iio/imu/bno055/Kconfig
@@ -7,3 +7,9 @@ config BOSH_BNO055_SERIAL
tristate "Bosh BNO055 attached via serial bus"
depends on SERIAL_DEV_BUS
select BOSH_BNO055_IIO
+
+config BOSH_BNO055_I2C
+ tristate "Bosh BNO055 attached via I2C bus"
+ depends on I2C
+ select REGMAP_I2C
+ select BOSH_BNO055_IIO
diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile
index 7285ade2f4b9..eaf24018cb28 100644
--- a/drivers/iio/imu/bno055/Makefile
+++ b/drivers/iio/imu/bno055/Makefile
@@ -2,3 +2,4 @@
obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o
obj-$(CONFIG_BOSH_BNO055_SERIAL) += bno055_sl.o
+obj-$(CONFIG_BOSH_BNO055_I2C) += bno055_i2c.o
diff --git a/drivers/iio/imu/bno055/bno055_i2c.c b/drivers/iio/imu/bno055/bno055_i2c.c
new file mode 100644
index 000000000000..eea0daa6a61d
--- /dev/null
+++ b/drivers/iio/imu/bno055/bno055_i2c.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * I2C interface for Bosh BNO055 IMU.
+ * This file implements I2C communication up to the register read/write
+ * level.
+ *
+ * Copyright (C) 2021 Istituto Italiano di Tecnologia
+ * Electronic Design Laboratory
+ * Written by Andrea Merello <[email protected]>
+ */
+
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+
+#include "bno055.h"
+
+#define BNO055_I2C_XFER_BURST_BREAK_THRESHOLD 3 /* FIXME */
+
+static int bno055_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct regmap *regmap =
+ devm_regmap_init_i2c(client, &bno055_regmap_config);
+
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "Unable to init register map");
+ return PTR_ERR(regmap);
+ }
+
+ return bno055_probe(&client->dev, regmap,
+ BNO055_I2C_XFER_BURST_BREAK_THRESHOLD);
+
+ return 0;
+}
+
+static const struct i2c_device_id bno055_i2c_id[] = {
+ {"bno055", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, bno055_i2c_id);
+
+static struct i2c_driver bno055_driver = {
+ .driver = {
+ .name = "bno055-i2c",
+ },
+ .probe = bno055_i2c_probe,
+ .id_table = bno055_i2c_id
+};
+module_i2c_driver(bno055_driver);
+
+MODULE_AUTHOR("Andrea Merello");
+MODULE_DESCRIPTION("Bosch BNO055 I2C interface");
+MODULE_LICENSE("GPL v2");
--
2.17.1
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 <[email protected]>
---
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
This patch adds modifiers for reporting rotations as euler angles (i.e.
yaw, pitch and roll).
Signed-off-by: Andrea Merello <[email protected]>
---
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
Introduce new documentation file for the Bosch BNO055 IMU
Signed-off-by: Andrea Merello <[email protected]>
---
.../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 <[email protected]>
+
+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
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 <[email protected]>
---
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 <[email protected]>
+ *
+ * This driver is besed on
+ * Plantower PMS7003 particulate matter sensor driver
+ * Which is
+ * Copyright (c) Tomasz Duszynski <[email protected]>
+ */
+
+#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 <[email protected]>");
+MODULE_DESCRIPTION("Bosch BNO055 serdev interface");
+MODULE_LICENSE("GPL v2");
--
2.17.1
This patch introduces ABI documentation for new iio modifiers used for
reporting "linear acceleration" measures.
Signed-off-by: Andrea Merello <[email protected]>
---
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: [email protected]
+Description:
+ Raw (unscaled) linear acceleration readings.
--
2.17.1
This patch adds ABI documentation for bno055 driver private sysfs
attributes.
Signed-off-by: Andrea Merello <[email protected]>
---
.../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: [email protected]
+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: [email protected]
+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: [email protected]
+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: [email protected]
+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: [email protected]
+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: [email protected]
+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: [email protected]
+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: [email protected]
+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: [email protected]
+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: [email protected]
+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: [email protected]
+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: [email protected]
+Description:
+ 16-bytes, 2-digits-per-byte, HEX-string representing the sensor
+ unique ID number.
--
2.17.1
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 <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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
On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <[email protected]> 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
Hi Andrea,
I'd advise not sending new versions of a series with the in-reply-to set
to an older version. It's a good way to ensure anyone using an email client
handling threads never sees them... It also gets very messy if we happen
to get replies to multiple versions overlapping.
New version, new email thread.
On Thu, 28 Oct 2021 12:18:30 +0200
Andrea Merello <[email protected]> wrote:
> 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
On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <[email protected]> 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
On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <[email protected]> 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
On Thu, Oct 28, 2021 at 1:30 PM Jonathan Cameron <[email protected]> wrote:
>
> Hi Andrea,
>
> I'd advise not sending new versions of a series with the in-reply-to set
> to an older version. It's a good way to ensure anyone using an email client
> handling threads never sees them... It also gets very messy if we happen
> to get replies to multiple versions overlapping.
>
> New version, new email thread.
+1 here, but since it will be a new version, let's leave it for a
while so reviewers may have a chance to comment on something.
--
With Best Regards,
Andy Shevchenko
On Thu, 28 Oct 2021 12:18:32 +0200
Andrea Merello <[email protected]> wrote:
> This patch introduces ABI documentation for new iio modifiers used for
> reporting "linear acceleration" measures.
>
> Signed-off-by: Andrea Merello <[email protected]>
> ---
> 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: [email protected]
> +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
On Thu, 28 Oct 2021 12:18:33 +0200
Andrea Merello <[email protected]> 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 <[email protected]>
> ---
> 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: [email protected]
> 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: [email protected]
> +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
On Thu, 28 Oct 2021 12:18:34 +0200
Andrea Merello <[email protected]> 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 <[email protected]>
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_ */
> -
?
On Thu, 28 Oct 2021 12:18:35 +0200
Andrea Merello <[email protected]> wrote:
> This patch adds modifiers for reporting rotations as euler angles (i.e.
> yaw, pitch and roll).
>
> Signed-off-by: Andrea Merello <[email protected]>
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 {
On Thu, 28 Oct 2021 12:18:36 +0200
Andrea Merello <[email protected]> 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 <[email protected]>
> ---
> .../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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +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: [email protected]
> +Description:
> + 16-bytes, 2-digits-per-byte, HEX-string representing the sensor
> + unique ID number.
On Thu, 28 Oct 2021 12:18:40 +0200
Andrea Merello <[email protected]> wrote:
> This path adds an I2C driver for communicating to a BNO055 IMU via I2C bus
> and it enables the BNO055 core driver to work in this scenario.
>
> Signed-off-by: Andrea Merello <[email protected]>
Hi Andrea,
A few minor things inline.
Jonathan
> ---
> drivers/iio/imu/bno055/Kconfig | 6 ++++
> drivers/iio/imu/bno055/Makefile | 1 +
> drivers/iio/imu/bno055/bno055_i2c.c | 54 +++++++++++++++++++++++++++++
> 3 files changed, 61 insertions(+)
> create mode 100644 drivers/iio/imu/bno055/bno055_i2c.c
>
> diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig
> index 941e43f0368d..87200787d548 100644
> --- a/drivers/iio/imu/bno055/Kconfig
> +++ b/drivers/iio/imu/bno055/Kconfig
> @@ -7,3 +7,9 @@ config BOSH_BNO055_SERIAL
> tristate "Bosh BNO055 attached via serial bus"
> depends on SERIAL_DEV_BUS
> select BOSH_BNO055_IIO
> +
> +config BOSH_BNO055_I2C
> + tristate "Bosh BNO055 attached via I2C bus"
> + depends on I2C
> + select REGMAP_I2C
> + select BOSH_BNO055_IIO
> diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile
> index 7285ade2f4b9..eaf24018cb28 100644
> --- a/drivers/iio/imu/bno055/Makefile
> +++ b/drivers/iio/imu/bno055/Makefile
> @@ -2,3 +2,4 @@
>
> obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o
> obj-$(CONFIG_BOSH_BNO055_SERIAL) += bno055_sl.o
> +obj-$(CONFIG_BOSH_BNO055_I2C) += bno055_i2c.o
> diff --git a/drivers/iio/imu/bno055/bno055_i2c.c b/drivers/iio/imu/bno055/bno055_i2c.c
> new file mode 100644
> index 000000000000..eea0daa6a61d
> --- /dev/null
> +++ b/drivers/iio/imu/bno055/bno055_i2c.c
> @@ -0,0 +1,54 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * I2C interface for Bosh BNO055 IMU.
> + * This file implements I2C communication up to the register read/write
> + * level.
Not really. It just uses regmap, so I'd drop this comment.
> + *
> + * Copyright (C) 2021 Istituto Italiano di Tecnologia
> + * Electronic Design Laboratory
> + * Written by Andrea Merello <[email protected]>
> + */
> +
> +#include <linux/i2c.h>
Why? I'm not seeing an i2c specific calls in here.
> +#include <linux/regmap.h>
> +#include <linux/module.h>
mod_devicetable.h for struct i2c_device_id
> +
> +#include "bno055.h"
> +
> +#define BNO055_I2C_XFER_BURST_BREAK_THRESHOLD 3 /* FIXME */
> +
> +static int bno055_i2c_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct regmap *regmap =
> + devm_regmap_init_i2c(client, &bno055_regmap_config);
> +
> + if (IS_ERR(regmap)) {
> + dev_err(&client->dev, "Unable to init register map");
> + return PTR_ERR(regmap);
> + }
> +
> + return bno055_probe(&client->dev, regmap,
> + BNO055_I2C_XFER_BURST_BREAK_THRESHOLD);
> +
> + return 0;
?
> +}
> +
> +static const struct i2c_device_id bno055_i2c_id[] = {
> + {"bno055", 0},
> + { },
It's at terminator, so don't put a comma as we'll never add entries after this.
> +};
> +MODULE_DEVICE_TABLE(i2c, bno055_i2c_id);
> +
> +static struct i2c_driver bno055_driver = {
> + .driver = {
> + .name = "bno055-i2c",
> + },
> + .probe = bno055_i2c_probe,
> + .id_table = bno055_i2c_id
> +};
> +module_i2c_driver(bno055_driver);
> +
> +MODULE_AUTHOR("Andrea Merello");
> +MODULE_DESCRIPTION("Bosch BNO055 I2C interface");
> +MODULE_LICENSE("GPL v2");
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 <[email protected]>
> ---
> .../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.
On Thu, 28 Oct 2021 12:18:39 +0200
Andrea Merello <[email protected]> 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 <[email protected]>
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 <[email protected]>
> + *
> + * This driver is besed on
> + * Plantower PMS7003 particulate matter sensor driver
> + * Which is
> + * Copyright (c) Tomasz Duszynski <[email protected]>
> + */
> +
> +#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 <[email protected]>");
> +MODULE_DESCRIPTION("Bosch BNO055 serdev interface");
> +MODULE_LICENSE("GPL v2");
On Thu, 28 Oct 2021 12:18:37 +0200
Andrea Merello <[email protected]> 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 <[email protected]>
> ---
> 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
On 10/28/21 3:18 AM, Andrea Merello wrote:
> This path adds an I2C driver for communicating to a BNO055 IMU via I2C bus
> and it enables the BNO055 core driver to work in this scenario.
>
> Signed-off-by: Andrea Merello <[email protected]>
> ---
> drivers/iio/imu/bno055/Kconfig | 6 ++++
> drivers/iio/imu/bno055/Makefile | 1 +
> drivers/iio/imu/bno055/bno055_i2c.c | 54 +++++++++++++++++++++++++++++
> 3 files changed, 61 insertions(+)
> create mode 100644 drivers/iio/imu/bno055/bno055_i2c.c
>
> diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig
> index 941e43f0368d..87200787d548 100644
> --- a/drivers/iio/imu/bno055/Kconfig
> +++ b/drivers/iio/imu/bno055/Kconfig
> @@ -7,3 +7,9 @@ config BOSH_BNO055_SERIAL
> tristate "Bosh BNO055 attached via serial bus"
> depends on SERIAL_DEV_BUS
> select BOSH_BNO055_IIO
> +
> +config BOSH_BNO055_I2C
> + tristate "Bosh BNO055 attached via I2C bus"
> + depends on I2C
> + select REGMAP_I2C
> + select BOSH_BNO055_IIO
Hi,
The config entries that have user prompt strings should also
have help text. scripts/checkpatch.pl should have told you
about that...
--
~Randy
Hi Andrea,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on linux/master]
[also build test ERROR on linus/master v5.15-rc7]
[cannot apply to jic23-iio/togreg next-20211028]
[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/utils_macro-introduce-find_closest_unsorted/20211028-191851
base: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 2f111a6fd5b5297b4e92f53798ca086f7c7d33a4
config: arc-allyesconfig (attached as .config)
compiler: arceb-elf-gcc (GCC) 11.2.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/185166ebe83b933e30af55d4dc7972db6f9a8fb8
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Andrea-Merello/utils_macro-introduce-find_closest_unsorted/20211028-191851
git checkout 185166ebe83b933e30af55d4dc7972db6f9a8fb8
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.2.0 make.cross ARCH=arc
If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <[email protected]>
All errors (new ones prefixed by >>):
>> drivers/iio/imu/bno055/bno055.c:234:5: error: no previous prototype for 'bno055_calibration_load' [-Werror=missing-prototypes]
234 | int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw)
| ^~~~~~~~~~~~~~~~~~~~~~~
drivers/iio/imu/bno055/bno055.c: In function 'bno055_fusion_enable_store':
>> drivers/iio/imu/bno055/bno055.c:917:13: error: variable 'ret' set but not used [-Werror=unused-but-set-variable]
917 | int ret = 0;
| ^~~
drivers/iio/imu/bno055/bno055.c: At top level:
>> drivers/iio/imu/bno055/bno055.c:1130:5: error: no previous prototype for 'bno055_debugfs_reg_access' [-Werror=missing-prototypes]
1130 | int bno055_debugfs_reg_access(struct iio_dev *iio_dev, unsigned int reg,
| ^~~~~~~~~~~~~~~~~~~~~~~~~
drivers/iio/imu/bno055/bno055.c: In function 'bno055_trigger_handler':
>> drivers/iio/imu/bno055/bno055.c:1330:9: error: implicit declaration of function 'for_each_set_bitrange'; did you mean 'for_each_set_bit'? [-Werror=implicit-function-declaration]
1330 | for_each_set_bitrange(start, end, iio_dev->active_scan_mask,
| ^~~~~~~~~~~~~~~~~~~~~
| for_each_set_bit
>> drivers/iio/imu/bno055/bno055.c:1331:51: error: expected ';' before '{' token
1331 | iio_dev->masklength) {
| ^~
| ;
>> drivers/iio/imu/bno055/bno055.c:1364:1: error: label 'done' defined but not used [-Werror=unused-label]
1364 | done:
| ^~~~
>> drivers/iio/imu/bno055/bno055.c:1327:13: error: unused variable 'ret' [-Werror=unused-variable]
1327 | int ret;
| ^~~
>> drivers/iio/imu/bno055/bno055.c:1326:13: error: unused variable 'quat' [-Werror=unused-variable]
1326 | int quat;
| ^~~~
>> drivers/iio/imu/bno055/bno055.c:1325:14: error: unused variable 'thr_hit' [-Werror=unused-variable]
1325 | bool thr_hit;
| ^~~~~~~
>> drivers/iio/imu/bno055/bno055.c:1324:13: error: unused variable 'buf_idx' [-Werror=unused-variable]
1324 | int buf_idx = 0;
| ^~~~~~~
>> drivers/iio/imu/bno055/bno055.c:1323:23: error: unused variable 'mask' [-Werror=unused-variable]
1323 | unsigned long mask;
| ^~~~
>> drivers/iio/imu/bno055/bno055.c:1322:14: error: unused variable 'first' [-Werror=unused-variable]
1322 | bool first = true;
| ^~~~~
>> drivers/iio/imu/bno055/bno055.c:1321:14: error: unused variable 'xfer_pending' [-Werror=unused-variable]
1321 | bool xfer_pending = false;
| ^~~~~~~~~~~~
>> drivers/iio/imu/bno055/bno055.c:1320:37: error: unused variable 'prev_end' [-Werror=unused-variable]
1320 | int xfer_start, start, end, prev_end;
| ^~~~~~~~
>> drivers/iio/imu/bno055/bno055.c:1320:13: error: unused variable 'xfer_start' [-Werror=unused-variable]
1320 | int xfer_start, start, end, prev_end;
| ^~~~~~~~~~
At top level:
>> drivers/iio/imu/bno055/bno055.c:1262:12: error: 'bno055_scan_xfer' defined but not used [-Werror=unused-function]
1262 | static int bno055_scan_xfer(struct bno055_priv *priv,
| ^~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
vim +/bno055_calibration_load +234 drivers/iio/imu/bno055/bno055.c
734efd9783b7759 Andrea Merello 2021-10-28 232
734efd9783b7759 Andrea Merello 2021-10-28 233 /* must be called in configuration mode */
734efd9783b7759 Andrea Merello 2021-10-28 @234 int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw)
734efd9783b7759 Andrea Merello 2021-10-28 235 {
734efd9783b7759 Andrea Merello 2021-10-28 236 if (fw->size != BNO055_CALDATA_LEN) {
734efd9783b7759 Andrea Merello 2021-10-28 237 dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)",
734efd9783b7759 Andrea Merello 2021-10-28 238 fw->size, BNO055_CALDATA_LEN);
734efd9783b7759 Andrea Merello 2021-10-28 239 return -EINVAL;
734efd9783b7759 Andrea Merello 2021-10-28 240 }
734efd9783b7759 Andrea Merello 2021-10-28 241
734efd9783b7759 Andrea Merello 2021-10-28 242 dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, fw->data);
734efd9783b7759 Andrea Merello 2021-10-28 243 return regmap_bulk_write(priv->regmap, BNO055_CALDATA_START,
734efd9783b7759 Andrea Merello 2021-10-28 244 fw->data, BNO055_CALDATA_LEN);
734efd9783b7759 Andrea Merello 2021-10-28 245 }
734efd9783b7759 Andrea Merello 2021-10-28 246
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]
Hi Andrea,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on linux/master]
[also build test WARNING on linus/master v5.15-rc7]
[cannot apply to jic23-iio/togreg next-20211029]
[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/utils_macro-introduce-find_closest_unsorted/20211028-191851
base: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 2f111a6fd5b5297b4e92f53798ca086f7c7d33a4
config: m68k-randconfig-r011-20211029 (attached as .config)
compiler: m68k-linux-gcc (GCC) 11.2.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/185166ebe83b933e30af55d4dc7972db6f9a8fb8
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Andrea-Merello/utils_macro-introduce-find_closest_unsorted/20211028-191851
git checkout 185166ebe83b933e30af55d4dc7972db6f9a8fb8
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.2.0 make.cross ARCH=m68k
If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <[email protected]>
All warnings (new ones prefixed by >>):
>> drivers/iio/imu/bno055/bno055.c:234:5: warning: no previous prototype for 'bno055_calibration_load' [-Wmissing-prototypes]
234 | int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw)
| ^~~~~~~~~~~~~~~~~~~~~~~
drivers/iio/imu/bno055/bno055.c: In function 'bno055_fusion_enable_store':
>> drivers/iio/imu/bno055/bno055.c:917:13: warning: variable 'ret' set but not used [-Wunused-but-set-variable]
917 | int ret = 0;
| ^~~
drivers/iio/imu/bno055/bno055.c: At top level:
>> drivers/iio/imu/bno055/bno055.c:1188:5: warning: no previous prototype for 'bno055_debugfs_reg_access' [-Wmissing-prototypes]
1188 | int bno055_debugfs_reg_access(struct iio_dev *iio_dev, unsigned int reg,
| ^~~~~~~~~~~~~~~~~~~~~~~~~
drivers/iio/imu/bno055/bno055.c: In function 'bno055_trigger_handler':
drivers/iio/imu/bno055/bno055.c:1330:9: error: implicit declaration of function 'for_each_set_bitrange'; did you mean 'for_each_set_bit'? [-Werror=implicit-function-declaration]
1330 | for_each_set_bitrange(start, end, iio_dev->active_scan_mask,
| ^~~~~~~~~~~~~~~~~~~~~
| for_each_set_bit
drivers/iio/imu/bno055/bno055.c:1331:51: error: expected ';' before '{' token
1331 | iio_dev->masklength) {
| ^~
| ;
drivers/iio/imu/bno055/bno055.c:1364:1: warning: label 'done' defined but not used [-Wunused-label]
1364 | done:
| ^~~~
drivers/iio/imu/bno055/bno055.c:1327:13: warning: unused variable 'ret' [-Wunused-variable]
1327 | int ret;
| ^~~
drivers/iio/imu/bno055/bno055.c:1326:13: warning: unused variable 'quat' [-Wunused-variable]
1326 | int quat;
| ^~~~
drivers/iio/imu/bno055/bno055.c:1325:14: warning: unused variable 'thr_hit' [-Wunused-variable]
1325 | bool thr_hit;
| ^~~~~~~
drivers/iio/imu/bno055/bno055.c:1324:13: warning: unused variable 'buf_idx' [-Wunused-variable]
1324 | int buf_idx = 0;
| ^~~~~~~
drivers/iio/imu/bno055/bno055.c:1323:23: warning: unused variable 'mask' [-Wunused-variable]
1323 | unsigned long mask;
| ^~~~
drivers/iio/imu/bno055/bno055.c:1322:14: warning: unused variable 'first' [-Wunused-variable]
1322 | bool first = true;
| ^~~~~
drivers/iio/imu/bno055/bno055.c:1321:14: warning: unused variable 'xfer_pending' [-Wunused-variable]
1321 | bool xfer_pending = false;
| ^~~~~~~~~~~~
drivers/iio/imu/bno055/bno055.c:1320:37: warning: unused variable 'prev_end' [-Wunused-variable]
1320 | int xfer_start, start, end, prev_end;
| ^~~~~~~~
drivers/iio/imu/bno055/bno055.c:1320:13: warning: unused variable 'xfer_start' [-Wunused-variable]
1320 | int xfer_start, start, end, prev_end;
| ^~~~~~~~~~
At top level:
drivers/iio/imu/bno055/bno055.c:1262:12: warning: 'bno055_scan_xfer' defined but not used [-Wunused-function]
1262 | static int bno055_scan_xfer(struct bno055_priv *priv,
| ^~~~~~~~~~~~~~~~
cc1: some warnings being treated as errors
vim +/bno055_calibration_load +234 drivers/iio/imu/bno055/bno055.c
734efd9783b7759 Andrea Merello 2021-10-28 232
734efd9783b7759 Andrea Merello 2021-10-28 233 /* must be called in configuration mode */
734efd9783b7759 Andrea Merello 2021-10-28 @234 int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw)
734efd9783b7759 Andrea Merello 2021-10-28 235 {
734efd9783b7759 Andrea Merello 2021-10-28 236 if (fw->size != BNO055_CALDATA_LEN) {
734efd9783b7759 Andrea Merello 2021-10-28 237 dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)",
734efd9783b7759 Andrea Merello 2021-10-28 238 fw->size, BNO055_CALDATA_LEN);
734efd9783b7759 Andrea Merello 2021-10-28 239 return -EINVAL;
734efd9783b7759 Andrea Merello 2021-10-28 240 }
734efd9783b7759 Andrea Merello 2021-10-28 241
734efd9783b7759 Andrea Merello 2021-10-28 242 dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, fw->data);
734efd9783b7759 Andrea Merello 2021-10-28 243 return regmap_bulk_write(priv->regmap, BNO055_CALDATA_START,
734efd9783b7759 Andrea Merello 2021-10-28 244 fw->data, BNO055_CALDATA_LEN);
734efd9783b7759 Andrea Merello 2021-10-28 245 }
734efd9783b7759 Andrea Merello 2021-10-28 246
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]
Hi Andrea,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on linux/master]
[also build test WARNING on linus/master v5.15-rc7]
[cannot apply to jic23-iio/togreg next-20211029]
[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/utils_macro-introduce-find_closest_unsorted/20211028-191851
base: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 2f111a6fd5b5297b4e92f53798ca086f7c7d33a4
config: alpha-randconfig-r014-20211029 (attached as .config)
compiler: alpha-linux-gcc (GCC) 11.2.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/fab64510c8234ae8acfbc853cc8c433ae831f7b3
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Andrea-Merello/utils_macro-introduce-find_closest_unsorted/20211028-191851
git checkout fab64510c8234ae8acfbc853cc8c433ae831f7b3
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-11.2.0 make.cross ARCH=alpha
If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <[email protected]>
All warnings (new ones prefixed by >>):
drivers/iio/imu/bno055/bno055.c:234:5: warning: no previous prototype for 'bno055_calibration_load' [-Wmissing-prototypes]
234 | int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw)
| ^~~~~~~~~~~~~~~~~~~~~~~
In file included from include/linux/printk.h:555,
from include/linux/kernel.h:19,
from include/linux/clk.h:13,
from drivers/iio/imu/bno055/bno055.c:18:
drivers/iio/imu/bno055/bno055.c: In function 'bno055_calibration_load':
>> drivers/iio/imu/bno055/bno055.c:237:36: warning: format '%d' expects argument of type 'int', but argument 4 has type 'size_t' {aka 'long unsigned int'} [-Wformat=]
237 | dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)",
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/dynamic_debug.h:134:29: note: in definition of macro '__dynamic_func_call'
134 | func(&id, ##__VA_ARGS__); \
| ^~~~~~~~~~~
include/linux/dynamic_debug.h:166:9: note: in expansion of macro '_dynamic_func_call'
166 | _dynamic_func_call(fmt,__dynamic_dev_dbg, \
| ^~~~~~~~~~~~~~~~~~
include/linux/dev_printk.h:155:9: note: in expansion of macro 'dynamic_dev_dbg'
155 | dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
| ^~~~~~~~~~~~~~~
include/linux/dev_printk.h:155:30: note: in expansion of macro 'dev_fmt'
155 | dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
| ^~~~~~~
drivers/iio/imu/bno055/bno055.c:237:17: note: in expansion of macro 'dev_dbg'
237 | dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)",
| ^~~~~~~
drivers/iio/imu/bno055/bno055.c:237:68: note: format string is defined here
237 | dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)",
| ~^
| |
| int
| %ld
drivers/iio/imu/bno055/bno055.c: In function 'bno055_fusion_enable_store':
drivers/iio/imu/bno055/bno055.c:917:13: warning: variable 'ret' set but not used [-Wunused-but-set-variable]
917 | int ret = 0;
| ^~~
drivers/iio/imu/bno055/bno055.c: At top level:
drivers/iio/imu/bno055/bno055.c:1130:5: warning: no previous prototype for 'bno055_debugfs_reg_access' [-Wmissing-prototypes]
1130 | int bno055_debugfs_reg_access(struct iio_dev *iio_dev, unsigned int reg,
| ^~~~~~~~~~~~~~~~~~~~~~~~~
drivers/iio/imu/bno055/bno055.c: In function 'bno055_trigger_handler':
drivers/iio/imu/bno055/bno055.c:1330:9: error: implicit declaration of function 'for_each_set_bitrange'; did you mean 'for_each_set_bit'? [-Werror=implicit-function-declaration]
1330 | for_each_set_bitrange(start, end, iio_dev->active_scan_mask,
| ^~~~~~~~~~~~~~~~~~~~~
| for_each_set_bit
drivers/iio/imu/bno055/bno055.c:1331:51: error: expected ';' before '{' token
1331 | iio_dev->masklength) {
| ^~
| ;
drivers/iio/imu/bno055/bno055.c:1364:1: warning: label 'done' defined but not used [-Wunused-label]
1364 | done:
| ^~~~
drivers/iio/imu/bno055/bno055.c:1327:13: warning: unused variable 'ret' [-Wunused-variable]
1327 | int ret;
| ^~~
drivers/iio/imu/bno055/bno055.c:1326:13: warning: unused variable 'quat' [-Wunused-variable]
1326 | int quat;
| ^~~~
drivers/iio/imu/bno055/bno055.c:1325:14: warning: unused variable 'thr_hit' [-Wunused-variable]
1325 | bool thr_hit;
| ^~~~~~~
drivers/iio/imu/bno055/bno055.c:1324:13: warning: unused variable 'buf_idx' [-Wunused-variable]
1324 | int buf_idx = 0;
| ^~~~~~~
drivers/iio/imu/bno055/bno055.c:1323:23: warning: unused variable 'mask' [-Wunused-variable]
1323 | unsigned long mask;
| ^~~~
drivers/iio/imu/bno055/bno055.c:1322:14: warning: unused variable 'first' [-Wunused-variable]
1322 | bool first = true;
| ^~~~~
drivers/iio/imu/bno055/bno055.c:1321:14: warning: unused variable 'xfer_pending' [-Wunused-variable]
1321 | bool xfer_pending = false;
| ^~~~~~~~~~~~
drivers/iio/imu/bno055/bno055.c:1320:37: warning: unused variable 'prev_end' [-Wunused-variable]
1320 | int xfer_start, start, end, prev_end;
| ^~~~~~~~
drivers/iio/imu/bno055/bno055.c:1320:13: warning: unused variable 'xfer_start' [-Wunused-variable]
1320 | int xfer_start, start, end, prev_end;
| ^~~~~~~~~~
At top level:
drivers/iio/imu/bno055/bno055.c:1262:12: warning: 'bno055_scan_xfer' defined but not used [-Wunused-function]
1262 | static int bno055_scan_xfer(struct bno055_priv *priv,
| ^~~~~~~~~~~~~~~~
cc1: some warnings being treated as errors
vim +237 drivers/iio/imu/bno055/bno055.c
734efd9783b7759 Andrea Merello 2021-10-28 232
734efd9783b7759 Andrea Merello 2021-10-28 233 /* must be called in configuration mode */
734efd9783b7759 Andrea Merello 2021-10-28 @234 int bno055_calibration_load(struct bno055_priv *priv, const struct firmware *fw)
734efd9783b7759 Andrea Merello 2021-10-28 235 {
734efd9783b7759 Andrea Merello 2021-10-28 236 if (fw->size != BNO055_CALDATA_LEN) {
734efd9783b7759 Andrea Merello 2021-10-28 @237 dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)",
734efd9783b7759 Andrea Merello 2021-10-28 238 fw->size, BNO055_CALDATA_LEN);
734efd9783b7759 Andrea Merello 2021-10-28 239 return -EINVAL;
734efd9783b7759 Andrea Merello 2021-10-28 240 }
734efd9783b7759 Andrea Merello 2021-10-28 241
734efd9783b7759 Andrea Merello 2021-10-28 242 dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, fw->data);
734efd9783b7759 Andrea Merello 2021-10-28 243 return regmap_bulk_write(priv->regmap, BNO055_CALDATA_START,
734efd9783b7759 Andrea Merello 2021-10-28 244 fw->data, BNO055_CALDATA_LEN);
734efd9783b7759 Andrea Merello 2021-10-28 245 }
734efd9783b7759 Andrea Merello 2021-10-28 246
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]
Il giorno gio 28 ott 2021 alle ore 12:26 Andy Shevchenko
<[email protected]> ha scritto:
>
> On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <[email protected]> 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
Il giorno gio 28 ott 2021 alle ore 12:32 Andy Shevchenko
<[email protected]> ha scritto:
>
> On Thu, Oct 28, 2021 at 1:18 PM Andrea Merello <[email protected]> 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
Il giorno gio 28 ott 2021 alle ore 12:35 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 28 Oct 2021 12:18:32 +0200
> Andrea Merello <[email protected]> wrote:
>
> > This patch introduces ABI documentation for new iio modifiers used for
> > reporting "linear acceleration" measures.
> >
> > Signed-off-by: Andrea Merello <[email protected]>
> > ---
> > 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: [email protected]
> > +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
>
>
Il giorno gio 28 ott 2021 alle ore 12:37 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 28 Oct 2021 12:18:33 +0200
> Andrea Merello <[email protected]> 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 <[email protected]>
> > ---
> > 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: [email protected]
> > 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: [email protected]
> > +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
>
>
Il giorno gio 28 ott 2021 alle ore 12:41 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 28 Oct 2021 12:18:34 +0200
> Andrea Merello <[email protected]> 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 <[email protected]>
>
> 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_ */
> > -
> ?
>
>
Few inline comments; ok for the rest.
Il giorno gio 28 ott 2021 alle ore 12:59 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 28 Oct 2021 12:18:36 +0200
> Andrea Merello <[email protected]> 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 <[email protected]>
> > ---
> > .../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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +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: [email protected]
> > +Description:
> > + 16-bytes, 2-digits-per-byte, HEX-string representing the sensor
> > + unique ID number.
>
Il giorno ven 29 ott 2021 alle ore 00:04 Randy Dunlap
<[email protected]> ha scritto:
>
> On 10/28/21 3:18 AM, Andrea Merello wrote:
> > This path adds an I2C driver for communicating to a BNO055 IMU via I2C bus
> > and it enables the BNO055 core driver to work in this scenario.
> >
> > Signed-off-by: Andrea Merello <[email protected]>
> > ---
> > drivers/iio/imu/bno055/Kconfig | 6 ++++
> > drivers/iio/imu/bno055/Makefile | 1 +
> > drivers/iio/imu/bno055/bno055_i2c.c | 54 +++++++++++++++++++++++++++++
> > 3 files changed, 61 insertions(+)
> > create mode 100644 drivers/iio/imu/bno055/bno055_i2c.c
> >
> > diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig
> > index 941e43f0368d..87200787d548 100644
> > --- a/drivers/iio/imu/bno055/Kconfig
> > +++ b/drivers/iio/imu/bno055/Kconfig
> > @@ -7,3 +7,9 @@ config BOSH_BNO055_SERIAL
> > tristate "Bosh BNO055 attached via serial bus"
> > depends on SERIAL_DEV_BUS
> > select BOSH_BNO055_IIO
> > +
> > +config BOSH_BNO055_I2C
> > + tristate "Bosh BNO055 attached via I2C bus"
> > + depends on I2C
> > + select REGMAP_I2C
> > + select BOSH_BNO055_IIO
>
> Hi,
>
> The config entries that have user prompt strings should also
> have help text. scripts/checkpatch.pl should have told you
> about that...
I'll add it, thanks. But FYI checkpatch doesn't complain about that here.
> --
> ~Randy
On Tue, 9 Nov 2021 09:15:09 +0100
Andrea Merello <[email protected]> wrote:
> Il giorno gio 28 ott 2021 alle ore 12:37 Jonathan Cameron
> <[email protected]> ha scritto:
> >
> > On Thu, 28 Oct 2021 12:18:33 +0200
> > Andrea Merello <[email protected]> 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 <[email protected]>
> > > ---
> > > 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: [email protected]
> > > 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: [email protected]
> > > +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
> >
> >
Few inline comments. Ok for all the rest.
Il giorno gio 28 ott 2021 alle ore 14:27 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 28 Oct 2021 12:18:39 +0200
> Andrea Merello <[email protected]> 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 <[email protected]>
>
> 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 <[email protected]>
> > + *
> > + * This driver is besed on
> > + * Plantower PMS7003 particulate matter sensor driver
> > + * Which is
> > + * Copyright (c) Tomasz Duszynski <[email protected]>
> > + */
> > +
> > +#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.
Hum. Looks like it always break here. Probably it's correct but the
loop can be reduced just up to the "continue" or something like that.
Have to rework this.
>
> > + 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?
size variable gets modified, so we cache the value to return in case of success.
> > +
> > + 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 <[email protected]>");
> > +MODULE_DESCRIPTION("Bosch BNO055 serdev interface");
> > +MODULE_LICENSE("GPL v2");
>
On 11/9/21 3:56 AM, Andrea Merello wrote:
> Il giorno ven 29 ott 2021 alle ore 00:04 Randy Dunlap
> <[email protected]> ha scritto:
>>
>> On 10/28/21 3:18 AM, Andrea Merello wrote:
>>> This path adds an I2C driver for communicating to a BNO055 IMU via I2C bus
>>> and it enables the BNO055 core driver to work in this scenario.
>>>
>>> Signed-off-by: Andrea Merello <[email protected]>
>>> ---
>>> drivers/iio/imu/bno055/Kconfig | 6 ++++
>>> drivers/iio/imu/bno055/Makefile | 1 +
>>> drivers/iio/imu/bno055/bno055_i2c.c | 54 +++++++++++++++++++++++++++++
>>> 3 files changed, 61 insertions(+)
>>> create mode 100644 drivers/iio/imu/bno055/bno055_i2c.c
>>>
>>> diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig
>>> index 941e43f0368d..87200787d548 100644
>>> --- a/drivers/iio/imu/bno055/Kconfig
>>> +++ b/drivers/iio/imu/bno055/Kconfig
>>> @@ -7,3 +7,9 @@ config BOSH_BNO055_SERIAL
>>> tristate "Bosh BNO055 attached via serial bus"
>>> depends on SERIAL_DEV_BUS
>>> select BOSH_BNO055_IIO
>>> +
>>> +config BOSH_BNO055_I2C
>>> + tristate "Bosh BNO055 attached via I2C bus"
>>> + depends on I2C
>>> + select REGMAP_I2C
>>> + select BOSH_BNO055_IIO
>>
>> Hi,
>>
>> The config entries that have user prompt strings should also
>> have help text. scripts/checkpatch.pl should have told you
>> about that...
>
> I'll add it, thanks. But FYI checkpatch doesn't complain about that here.
Hm, thanks for adding it and telling me about that.
checkpatch.pl does have some code for checking that but I confirmed
that it does not catch this simple case.
Joe, can you identify why checkpatch does not detect missing Kconfig
help text is this simple case?
Thanks.
--
~Randy
On Tue, 9 Nov 2021 09:00:09 +0100
Andrea Merello <[email protected]> wrote:
> Il giorno gio 28 ott 2021 alle ore 12:35 Jonathan Cameron
> <[email protected]> ha scritto:
> >
> > On Thu, 28 Oct 2021 12:18:32 +0200
> > Andrea Merello <[email protected]> wrote:
> >
> > > This patch introduces ABI documentation for new iio modifiers used for
> > > reporting "linear acceleration" measures.
> > >
> > > Signed-off-by: Andrea Merello <[email protected]>
> > > ---
> > > 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: [email protected]
> > > +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
> >
> >
On Tue, 9 Nov 2021 10:58:19 +0100
Andrea Merello <[email protected]> wrote:
> Il giorno gio 28 ott 2021 alle ore 12:41 Jonathan Cameron
> <[email protected]> ha scritto:
> >
> > On Thu, 28 Oct 2021 12:18:34 +0200
> > Andrea Merello <[email protected]> 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 <[email protected]>
> >
> > 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_ */
> > > -
> > ?
> >
> >
(cc'ing Andi Kleen, who wrote this code a decade ago)
On Tue, 2021-11-09 at 07:47 -0800, Randy Dunlap wrote:
> On 11/9/21 3:56 AM, Andrea Merello wrote:
> > Il giorno ven 29 ott 2021 alle ore 00:04 Randy Dunlap <[email protected]> ha scritto:
> > > On 10/28/21 3:18 AM, Andrea Merello wrote:
> > > > This path adds an I2C driver for communicating to a BNO055 IMU via I2C bus
> > > > and it enables the BNO055 core driver to work in this scenario.
> > > >
> > > > Signed-off-by: Andrea Merello <[email protected]>
> > > > ---
> > > > drivers/iio/imu/bno055/Kconfig | 6 ++++
> > > > drivers/iio/imu/bno055/Makefile | 1 +
[]
> > > > diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig
[]
> > > > @@ -7,3 +7,9 @@ config BOSH_BNO055_SERIAL
> > > > tristate "Bosh BNO055 attached via serial bus"
> > > > depends on SERIAL_DEV_BUS
> > > > select BOSH_BNO055_IIO
> > > > +
> > > > +config BOSH_BNO055_I2C
> > > > + tristate "Bosh BNO055 attached via I2C bus"
> > > > + depends on I2C
> > > > + select REGMAP_I2C
> > > > + select BOSH_BNO055_IIO
[]
> > > The config entries that have user prompt strings should also
> > > have help text. scripts/checkpatch.pl should have told you
> > > about that...
> >
> > I'll add it, thanks. But FYI checkpatch doesn't complain about that here.
>
> Hm, thanks for adding it and telling me about that.
>
> checkpatch.pl does have some code for checking that but I confirmed
> that it does not catch this simple case.
>
> Joe, can you identify why checkpatch does not detect missing Kconfig
> help text is this simple case?
Original patch here: https://lore.kernel.org/all/[email protected]/raw
checkpatch is counting the diff header lines that follow the config entry.
Maybe this is clearer (better?) code:
---
scripts/checkpatch.pl | 28 +++++++++++++++-------------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 1784921c645da..b3ce8e04d7df7 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -3483,20 +3483,22 @@ sub process {
my $cnt = $realcnt;
my $ln = $linenr + 1;
my $f;
- my $is_start = 0;
- my $is_end = 0;
+ my $needs_help = 0;
+ my $has_help = 0;
for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) {
$f = $lines[$ln - 1];
- $cnt-- if ($lines[$ln - 1] !~ /^-/);
- $is_end = $lines[$ln - 1] =~ /^\+/;
+ $cnt-- if ($f !~ /^-/);
next if ($f =~ /^-/);
- last if (!$file && $f =~ /^\@\@/);
+ last if (!$file && $f =~ /^(?:\@\@|diff )/);
- if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) {
- $is_start = 1;
- } elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) {
- $length = -1;
+ if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) {
+ $needs_help = 1;
+ next;
+ } elsif ($f =~ /^\+\s*help\s*$/) {
+ $length = 0;
+ $has_help = 1;
+ next;
}
$f =~ s/^.//;
@@ -3510,16 +3512,16 @@ sub process {
# common words in help texts
if ($f =~ /^\s*(?:config|menuconfig|choice|endchoice|
if|endif|menu|endmenu|source)\b/x) {
- $is_end = 1;
last;
}
- $length++;
+ $length++ if ($has_help);
}
- if ($is_start && $is_end && $length < $min_conf_desc_length) {
+ if ($needs_help &&
+ (!$has_help ||
+ ($has_help && $length < $min_conf_desc_length))) {
WARN("CONFIG_DESCRIPTION",
"please write a paragraph that describes the config symbol fully\n" . $herecurr);
}
- #print "is_start<$is_start> is_end<$is_end> length<$length>\n";
}
# check MAINTAINERS entries
On 11/9/21 10:21 AM, Joe Perches wrote:
> (cc'ing Andi Kleen, who wrote this code a decade ago)
>
> On Tue, 2021-11-09 at 07:47 -0800, Randy Dunlap wrote:
>> On 11/9/21 3:56 AM, Andrea Merello wrote:
>>> Il giorno ven 29 ott 2021 alle ore 00:04 Randy Dunlap <[email protected]> ha scritto:
>>>> On 10/28/21 3:18 AM, Andrea Merello wrote:
>>>>> This path adds an I2C driver for communicating to a BNO055 IMU via I2C bus
>>>>> and it enables the BNO055 core driver to work in this scenario.
>>>>>
>>>>> Signed-off-by: Andrea Merello <[email protected]>
>>>>> ---
>>>>> drivers/iio/imu/bno055/Kconfig | 6 ++++
>>>>> drivers/iio/imu/bno055/Makefile | 1 +
> []
>>>>> diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig
> []
>>>>> @@ -7,3 +7,9 @@ config BOSH_BNO055_SERIAL
>>>>> tristate "Bosh BNO055 attached via serial bus"
>>>>> depends on SERIAL_DEV_BUS
>>>>> select BOSH_BNO055_IIO
>>>>> +
>>>>> +config BOSH_BNO055_I2C
>>>>> + tristate "Bosh BNO055 attached via I2C bus"
>>>>> + depends on I2C
>>>>> + select REGMAP_I2C
>>>>> + select BOSH_BNO055_IIO
> []
>>>> The config entries that have user prompt strings should also
>>>> have help text. scripts/checkpatch.pl should have told you
>>>> about that...
>>>
>>> I'll add it, thanks. But FYI checkpatch doesn't complain about that here.
>>
>> Hm, thanks for adding it and telling me about that.
>>
>> checkpatch.pl does have some code for checking that but I confirmed
>> that it does not catch this simple case.
>>
>> Joe, can you identify why checkpatch does not detect missing Kconfig
>> help text is this simple case?
>
> Original patch here: https://lore.kernel.org/all/[email protected]/raw
>
> checkpatch is counting the diff header lines that follow the config entry.
> Maybe this is clearer (better?) code:
> ---
> scripts/checkpatch.pl | 28 +++++++++++++++-------------
> 1 file changed, 15 insertions(+), 13 deletions(-)
>
> diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
> index 1784921c645da..b3ce8e04d7df7 100755
> --- a/scripts/checkpatch.pl
> +++ b/scripts/checkpatch.pl
> @@ -3483,20 +3483,22 @@ sub process {
> my $cnt = $realcnt;
> my $ln = $linenr + 1;
> my $f;
> - my $is_start = 0;
> - my $is_end = 0;
> + my $needs_help = 0;
> + my $has_help = 0;
> for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) {
> $f = $lines[$ln - 1];
> - $cnt-- if ($lines[$ln - 1] !~ /^-/);
> - $is_end = $lines[$ln - 1] =~ /^\+/;
> + $cnt-- if ($f !~ /^-/);
>
> next if ($f =~ /^-/);
> - last if (!$file && $f =~ /^\@\@/);
> + last if (!$file && $f =~ /^(?:\@\@|diff )/);
>
> - if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) {
> - $is_start = 1;
> - } elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) {
> - $length = -1;
> + if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) {
> + $needs_help = 1;
> + next;
> + } elsif ($f =~ /^\+\s*help\s*$/) {
> + $length = 0;
> + $has_help = 1;
> + next;
> }
>
> $f =~ s/^.//;
> @@ -3510,16 +3512,16 @@ sub process {
> # common words in help texts
> if ($f =~ /^\s*(?:config|menuconfig|choice|endchoice|
> if|endif|menu|endmenu|source)\b/x) {
> - $is_end = 1;
> last;
> }
> - $length++;
> + $length++ if ($has_help);
> }
> - if ($is_start && $is_end && $length < $min_conf_desc_length) {
> + if ($needs_help &&
> + (!$has_help ||
> + ($has_help && $length < $min_conf_desc_length))) {
> WARN("CONFIG_DESCRIPTION",
> "please write a paragraph that describes the config symbol fully\n" . $herecurr);
> }
> - #print "is_start<$is_start> is_end<$is_end> length<$length>\n";
> }
>
> # check MAINTAINERS entries
>
>
Which now says:
WARNING: please write a paragraph that describes the config symbol fully
#16: FILE: drivers/iio/Kconfig:101:
+config BOSH_BNO055_I2C
(This is for a dummy entry that I made for testing, not from Andrea's
patch.)
Thanks, Joe.
Tested-by: Randy Dunlap <[email protected]>
Acked-by: Randy Dunlap <[email protected]>
--
~Randy
On Tue, 2021-11-09 at 11:11 -0800, Randy Dunlap wrote:
> On 11/9/21 10:21 AM, Joe Perches wrote:
> > (cc'ing Andi Kleen, who wrote this code a decade ago)
> > > Joe, can you identify why checkpatch does not detect missing Kconfig
> > > help text is this simple case?
> >
> > Original patch here: https://lore.kernel.org/all/[email protected]/raw
> >
> > checkpatch is counting the diff header lines that follow the config entry.
> > Maybe this is clearer (better?) code:
> > ---
> Tested-by: Randy Dunlap <[email protected]>
> Acked-by: Randy Dunlap <[email protected]>
Hey Randy/Andi.
I like this patch below a bit more.
It shows the Kconfig context block in the output message and
documents the code a bit more.
Care to test it again?
---
scripts/checkpatch.pl | 53 +++++++++++++++++++++++++++------------------------
1 file changed, 28 insertions(+), 25 deletions(-)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 1784921c645da..0b5c0363119ff 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -3480,46 +3480,49 @@ sub process {
# (\b) rather than a whitespace character (\s)
$line =~ /^\+\s*(?:config|menuconfig|choice)\b/) {
my $length = 0;
- my $cnt = $realcnt;
- my $ln = $linenr + 1;
- my $f;
- my $is_start = 0;
- my $is_end = 0;
- for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) {
- $f = $lines[$ln - 1];
- $cnt-- if ($lines[$ln - 1] !~ /^-/);
- $is_end = $lines[$ln - 1] =~ /^\+/;
+ my $ln = $linenr;
+ my $needs_help = 0;
+ my $has_help = 0;
+ my $herecfg = $herecurr;
+ while (defined $lines[$ln]) {
+ my $f = $lines[$ln++];
next if ($f =~ /^-/);
- last if (!$file && $f =~ /^\@\@/);
+ last if (!$file && $f =~ /^(?:\@\@|diff )/);
- if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) {
- $is_start = 1;
- } elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) {
- $length = -1;
+ $herecfg .= $rawlines[$ln - 1] . "\n";
+ if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) {
+ $needs_help = 1;
+ next;
+ }
+ if ($f =~ /^\+\s*help\s*$/) {
+ $has_help = 1;
+ next;
}
- $f =~ s/^.//;
- $f =~ s/#.*//;
- $f =~ s/^\s+//;
- next if ($f =~ /^$/);
+ $f =~ s/^.//; # strip patch context [+ ]
+ $f =~ s/#.*//; # strip # directives
+ $f =~ s/^\s+//; # strip leading blanks
+ next if ($f =~ /^$/); # skip blank lines
+ # At the end of this Kconfig block:
# This only checks context lines in the patch
# and so hopefully shouldn't trigger false
# positives, even though some of these are
# common words in help texts
- if ($f =~ /^\s*(?:config|menuconfig|choice|endchoice|
- if|endif|menu|endmenu|source)\b/x) {
- $is_end = 1;
+ if ($f =~ /^(?:config|menuconfig|choice|endchoice|
+ if|endif|menu|endmenu|source)\b/x) {
+ $herecfg =~ s/.*\n\Z//; # strip last line
last;
}
- $length++;
+ $length++ if ($has_help);
}
- if ($is_start && $is_end && $length < $min_conf_desc_length) {
+ if ($needs_help &&
+ (!$has_help ||
+ ($has_help && $length < $min_conf_desc_length))) {
WARN("CONFIG_DESCRIPTION",
- "please write a paragraph that describes the config symbol fully\n" . $herecurr);
+ "please write a help paragraph that fully describes the config symbol\n" . $herecfg);
}
- #print "is_start<$is_start> is_end<$is_end> length<$length>\n";
}
# check MAINTAINERS entries
On 11/9/21 12:46 PM, Joe Perches wrote:
> On Tue, 2021-11-09 at 11:11 -0800, Randy Dunlap wrote:
>> On 11/9/21 10:21 AM, Joe Perches wrote:
>>> (cc'ing Andi Kleen, who wrote this code a decade ago)
>>>> Joe, can you identify why checkpatch does not detect missing Kconfig
>>>> help text is this simple case?
>>>
>>> Original patch here: https://lore.kernel.org/all/[email protected]/raw
>>>
>>> checkpatch is counting the diff header lines that follow the config entry.
>>> Maybe this is clearer (better?) code:
>>> ---
>> Tested-by: Randy Dunlap <[email protected]>
>> Acked-by: Randy Dunlap <[email protected]>
>
> Hey Randy/Andi.
>
> I like this patch below a bit more.
>
> It shows the Kconfig context block in the output message and
> documents the code a bit more.
>
> Care to test it again?
> ---
> scripts/checkpatch.pl | 53 +++++++++++++++++++++++++++------------------------
> 1 file changed, 28 insertions(+), 25 deletions(-)
>
Same tags from me, better output. Thanks!
Tested-by: Randy Dunlap <[email protected]>
Acked-by: Randy Dunlap <[email protected]>
--
~Randy
Some inline notes. OK for all the rest.
Il giorno gio 28 ott 2021 alle ore 15:27 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 28 Oct 2021 12:18:37 +0200
> Andrea Merello <[email protected]> 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 <[email protected]>
> > ---
> > 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
Just an inline comment; OK for the rest.
Il giorno gio 28 ott 2021 alle ore 13:05 Jonathan Cameron
<[email protected]> ha scritto:
>
> On Thu, 28 Oct 2021 12:18:40 +0200
> Andrea Merello <[email protected]> wrote:
>
> > This path adds an I2C driver for communicating to a BNO055 IMU via I2C bus
> > and it enables the BNO055 core driver to work in this scenario.
> >
> > Signed-off-by: Andrea Merello <[email protected]>
> Hi Andrea,
>
> A few minor things inline.
>
> Jonathan
>
> > ---
> > drivers/iio/imu/bno055/Kconfig | 6 ++++
> > drivers/iio/imu/bno055/Makefile | 1 +
> > drivers/iio/imu/bno055/bno055_i2c.c | 54 +++++++++++++++++++++++++++++
> > 3 files changed, 61 insertions(+)
> > create mode 100644 drivers/iio/imu/bno055/bno055_i2c.c
> >
> > diff --git a/drivers/iio/imu/bno055/Kconfig b/drivers/iio/imu/bno055/Kconfig
> > index 941e43f0368d..87200787d548 100644
> > --- a/drivers/iio/imu/bno055/Kconfig
> > +++ b/drivers/iio/imu/bno055/Kconfig
> > @@ -7,3 +7,9 @@ config BOSH_BNO055_SERIAL
> > tristate "Bosh BNO055 attached via serial bus"
> > depends on SERIAL_DEV_BUS
> > select BOSH_BNO055_IIO
> > +
> > +config BOSH_BNO055_I2C
> > + tristate "Bosh BNO055 attached via I2C bus"
> > + depends on I2C
> > + select REGMAP_I2C
> > + select BOSH_BNO055_IIO
> > diff --git a/drivers/iio/imu/bno055/Makefile b/drivers/iio/imu/bno055/Makefile
> > index 7285ade2f4b9..eaf24018cb28 100644
> > --- a/drivers/iio/imu/bno055/Makefile
> > +++ b/drivers/iio/imu/bno055/Makefile
> > @@ -2,3 +2,4 @@
> >
> > obj-$(CONFIG_BOSH_BNO055_IIO) += bno055.o
> > obj-$(CONFIG_BOSH_BNO055_SERIAL) += bno055_sl.o
> > +obj-$(CONFIG_BOSH_BNO055_I2C) += bno055_i2c.o
> > diff --git a/drivers/iio/imu/bno055/bno055_i2c.c b/drivers/iio/imu/bno055/bno055_i2c.c
> > new file mode 100644
> > index 000000000000..eea0daa6a61d
> > --- /dev/null
> > +++ b/drivers/iio/imu/bno055/bno055_i2c.c
> > @@ -0,0 +1,54 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * I2C interface for Bosh BNO055 IMU.
> > + * This file implements I2C communication up to the register read/write
> > + * level.
>
> Not really. It just uses regmap, so I'd drop this comment.
>
> > + *
> > + * Copyright (C) 2021 Istituto Italiano di Tecnologia
> > + * Electronic Design Laboratory
> > + * Written by Andrea Merello <[email protected]>
> > + */
> > +
> > +#include <linux/i2c.h>
>
> Why? I'm not seeing an i2c specific calls in here.
Because of the definition of struct i2c_client, that is being accessed
in lines like this
dev_err(&client->dev, "Unable to init register map");
> > +#include <linux/regmap.h>
> > +#include <linux/module.h>
>
> mod_devicetable.h for struct i2c_device_id
>
> > +
> > +#include "bno055.h"
> > +
> > +#define BNO055_I2C_XFER_BURST_BREAK_THRESHOLD 3 /* FIXME */
> > +
> > +static int bno055_i2c_probe(struct i2c_client *client,
> > + const struct i2c_device_id *id)
> > +{
> > + struct regmap *regmap =
> > + devm_regmap_init_i2c(client, &bno055_regmap_config);
> > +
> > + if (IS_ERR(regmap)) {
> > + dev_err(&client->dev, "Unable to init register map");
> > + return PTR_ERR(regmap);
> > + }
> > +
> > + return bno055_probe(&client->dev, regmap,
> > + BNO055_I2C_XFER_BURST_BREAK_THRESHOLD);
> > +
> > + return 0;
>
> ?
>
> > +}
> > +
> > +static const struct i2c_device_id bno055_i2c_id[] = {
> > + {"bno055", 0},
> > + { },
>
> It's at terminator, so don't put a comma as we'll never add entries after this.
>
> > +};
> > +MODULE_DEVICE_TABLE(i2c, bno055_i2c_id);
> > +
> > +static struct i2c_driver bno055_driver = {
> > + .driver = {
> > + .name = "bno055-i2c",
> > + },
> > + .probe = bno055_i2c_probe,
> > + .id_table = bno055_i2c_id
> > +};
> > +module_i2c_driver(bno055_driver);
> > +
> > +MODULE_AUTHOR("Andrea Merello");
> > +MODULE_DESCRIPTION("Bosch BNO055 I2C interface");
> > +MODULE_LICENSE("GPL v2");
>
On Tue, 9 Nov 2021 11:22:27 +0100
Andrea Merello <[email protected]> wrote:
> Few inline comments; ok for the rest.
>
> Il giorno gio 28 ott 2021 alle ore 12:59 Jonathan Cameron
> <[email protected]> ha scritto:
> >
> > On Thu, 28 Oct 2021 12:18:36 +0200
> > Andrea Merello <[email protected]> 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 <[email protected]>
> > > ---
> > > .../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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +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: [email protected]
> > > +Description:
> > > + 16-bytes, 2-digits-per-byte, HEX-string representing the sensor
> > > + unique ID number.
> >
On Tue, 9 Nov 2021 12:52:14 +0100
Andrea Merello <[email protected]> wrote:
> Some inline notes. OK for all the rest.
>
> Il giorno gio 28 ott 2021 alle ore 15:27 Jonathan Cameron
> <[email protected]> ha scritto:
> >
> > On Thu, 28 Oct 2021 12:18:37 +0200
> > Andrea Merello <[email protected]> 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 <[email protected]>
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
On Tue, 9 Nov 2021 16:33:44 +0100
Andrea Merello <[email protected]> wrote:
...
> > > +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?
>
> size variable gets modified, so we cache the value to return in case of success.
Ah - missed that as unusual way around. I'd modify the local variable instead
and perhaps call it something like remaining to reflect how it is being used?
>
> > > +
> > > + 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;
> > > +}
On Thu, 11 Nov 2021 11:12:58 +0100
Andrea Merello <[email protected]> wrote:
> Just an inline comment; OK for the rest.
> > > +#include <linux/i2c.h>
> >
> > Why? I'm not seeing an i2c specific calls in here.
>
> Because of the definition of struct i2c_client, that is being accessed
> in lines like this
> dev_err(&client->dev, "Unable to init register map");
doh. I was clearly being a bit unobservant that day.
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
On Tue, 4 Jan 2022 12:42:40 +0100
Andrea Merello <[email protected]> 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
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
>
On Mon, 17 Jan 2022 10:37:33 +0100
Andrea Merello <[email protected]> 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
> >