Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Andrea Merello | 7123 | 99.97% | 1 | 50.00% |
Dan Carpenter | 2 | 0.03% | 1 | 50.00% |
Total | 7125 | 2 |
// SPDX-License-Identifier: GPL-2.0 /* * IIO driver for Bosch BNO055 IMU * * Copyright (C) 2021-2022 Istituto Italiano di Tecnologia * Electronic Design Laboratory * Written by Andrea Merello <andrea.merello@iit.it> * * Portions of this driver are taken from the BNO055 driver patch * from Vlad Dogaru which is Copyright (c) 2016, Intel Corporation. * * This driver is also based on BMI160 driver, which is: * Copyright (c) 2016, Intel Corporation. * Copyright (c) 2019, Martin Kelly. */ #include <linux/bitfield.h> #include <linux/bitmap.h> #include <linux/clk.h> #include <linux/debugfs.h> #include <linux/device.h> #include <linux/firmware.h> #include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/regmap.h> #include <linux/util_macros.h> #include <linux/iio/buffer.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> #include "bno055.h" #define BNO055_FW_UID_FMT "bno055-caldata-%*phN.dat" #define BNO055_FW_GENERIC_NAME "bno055-caldata.dat" /* common registers */ #define BNO055_PAGESEL_REG 0x7 /* page 0 registers */ #define BNO055_CHIP_ID_REG 0x0 #define BNO055_CHIP_ID_MAGIC 0xA0 #define BNO055_SW_REV_LSB_REG 0x4 #define BNO055_SW_REV_MSB_REG 0x5 #define BNO055_ACC_DATA_X_LSB_REG 0x8 #define BNO055_ACC_DATA_Y_LSB_REG 0xA #define BNO055_ACC_DATA_Z_LSB_REG 0xC #define BNO055_MAG_DATA_X_LSB_REG 0xE #define BNO055_MAG_DATA_Y_LSB_REG 0x10 #define BNO055_MAG_DATA_Z_LSB_REG 0x12 #define BNO055_GYR_DATA_X_LSB_REG 0x14 #define BNO055_GYR_DATA_Y_LSB_REG 0x16 #define BNO055_GYR_DATA_Z_LSB_REG 0x18 #define BNO055_EUL_DATA_X_LSB_REG 0x1A #define BNO055_EUL_DATA_Y_LSB_REG 0x1C #define BNO055_EUL_DATA_Z_LSB_REG 0x1E #define BNO055_QUAT_DATA_W_LSB_REG 0x20 #define BNO055_LIA_DATA_X_LSB_REG 0x28 #define BNO055_LIA_DATA_Y_LSB_REG 0x2A #define BNO055_LIA_DATA_Z_LSB_REG 0x2C #define BNO055_GRAVITY_DATA_X_LSB_REG 0x2E #define BNO055_GRAVITY_DATA_Y_LSB_REG 0x30 #define BNO055_GRAVITY_DATA_Z_LSB_REG 0x32 #define BNO055_SCAN_CH_COUNT ((BNO055_GRAVITY_DATA_Z_LSB_REG - BNO055_ACC_DATA_X_LSB_REG) / 2) #define BNO055_TEMP_REG 0x34 #define BNO055_CALIB_STAT_REG 0x35 #define BNO055_CALIB_STAT_MAGN_SHIFT 0 #define BNO055_CALIB_STAT_ACCEL_SHIFT 2 #define BNO055_CALIB_STAT_GYRO_SHIFT 4 #define BNO055_CALIB_STAT_SYS_SHIFT 6 #define BNO055_SYS_ERR_REG 0x3A #define BNO055_POWER_MODE_REG 0x3E #define BNO055_POWER_MODE_NORMAL 0 #define BNO055_SYS_TRIGGER_REG 0x3F #define BNO055_SYS_TRIGGER_RST_SYS BIT(5) #define BNO055_SYS_TRIGGER_CLK_SEL BIT(7) #define BNO055_OPR_MODE_REG 0x3D #define BNO055_OPR_MODE_CONFIG 0x0 #define BNO055_OPR_MODE_AMG 0x7 #define BNO055_OPR_MODE_FUSION_FMC_OFF 0xB #define BNO055_OPR_MODE_FUSION 0xC #define BNO055_UNIT_SEL_REG 0x3B /* Android orientation mode means: pitch value decreases turning clockwise */ #define BNO055_UNIT_SEL_ANDROID BIT(7) #define BNO055_UNIT_SEL_GYR_RPS BIT(1) #define BNO055_CALDATA_START 0x55 #define BNO055_CALDATA_END 0x6A #define BNO055_CALDATA_LEN 22 /* * The difference in address between the register that contains the * value and the register that contains the offset. This applies for * accel, gyro and magn channels. */ #define BNO055_REG_OFFSET_ADDR 0x4D /* page 1 registers */ #define BNO055_PG1(x) ((x) | 0x80) #define BNO055_ACC_CONFIG_REG BNO055_PG1(0x8) #define BNO055_ACC_CONFIG_LPF_MASK GENMASK(4, 2) #define BNO055_ACC_CONFIG_RANGE_MASK GENMASK(1, 0) #define BNO055_MAG_CONFIG_REG BNO055_PG1(0x9) #define BNO055_MAG_CONFIG_HIGHACCURACY 0x18 #define BNO055_MAG_CONFIG_ODR_MASK GENMASK(2, 0) #define BNO055_GYR_CONFIG_REG BNO055_PG1(0xA) #define BNO055_GYR_CONFIG_RANGE_MASK GENMASK(2, 0) #define BNO055_GYR_CONFIG_LPF_MASK GENMASK(5, 3) #define BNO055_GYR_AM_SET_REG BNO055_PG1(0x1F) #define BNO055_UID_LOWER_REG BNO055_PG1(0x50) #define BNO055_UID_HIGHER_REG BNO055_PG1(0x5F) #define BNO055_UID_LEN 16 struct bno055_sysfs_attr { int *vals; int len; int *fusion_vals; int *hw_xlate; int type; }; static int bno055_acc_lpf_vals[] = { 7, 810000, 15, 630000, 31, 250000, 62, 500000, 125, 0, 250, 0, 500, 0, 1000, 0, }; static struct bno055_sysfs_attr bno055_acc_lpf = { .vals = bno055_acc_lpf_vals, .len = ARRAY_SIZE(bno055_acc_lpf_vals), .fusion_vals = (int[]){62, 500000}, .type = IIO_VAL_INT_PLUS_MICRO, }; static int bno055_acc_range_vals[] = { /* G: 2, 4, 8, 16 */ 1962, 3924, 7848, 15696 }; static struct bno055_sysfs_attr bno055_acc_range = { .vals = bno055_acc_range_vals, .len = ARRAY_SIZE(bno055_acc_range_vals), .fusion_vals = (int[]){3924}, /* 4G */ .type = IIO_VAL_INT, }; /* * Theoretically the IMU should return data in a given (i.e. fixed) unit * regardless of the range setting. This happens for the accelerometer, but not * for the gyroscope; the gyroscope range setting affects the scale. * This is probably due to this[0] bug. * For this reason we map the internal range setting onto the standard IIO scale * attribute for gyro. * Since the bug[0] may be fixed in future, we check for the IMU FW version and * eventually warn the user. * Currently we just don't care about "range" attributes for gyro. * * [0] https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BNO055-Wrong-sensitivity-resolution-in-datasheet/td-p/10266 */ /* * dps = hwval * (dps_range/2^15) * rps = hwval * (rps_range/2^15) * = hwval * (dps_range/(2^15 * k)) * where k is rad-to-deg factor */ static int bno055_gyr_scale_vals[] = { 125, 1877467, 250, 1877467, 500, 1877467, 1000, 1877467, 2000, 1877467, }; static struct bno055_sysfs_attr bno055_gyr_scale = { .vals = bno055_gyr_scale_vals, .len = ARRAY_SIZE(bno055_gyr_scale_vals), .fusion_vals = (int[]){1, 900}, .hw_xlate = (int[]){4, 3, 2, 1, 0}, .type = IIO_VAL_FRACTIONAL, }; static int bno055_gyr_lpf_vals[] = {12, 23, 32, 47, 64, 116, 230, 523}; static struct bno055_sysfs_attr bno055_gyr_lpf = { .vals = bno055_gyr_lpf_vals, .len = ARRAY_SIZE(bno055_gyr_lpf_vals), .fusion_vals = (int[]){32}, .hw_xlate = (int[]){5, 4, 7, 3, 6, 2, 1, 0}, .type = IIO_VAL_INT, }; static int bno055_mag_odr_vals[] = {2, 6, 8, 10, 15, 20, 25, 30}; static struct bno055_sysfs_attr bno055_mag_odr = { .vals = bno055_mag_odr_vals, .len = ARRAY_SIZE(bno055_mag_odr_vals), .fusion_vals = (int[]){20}, .type = IIO_VAL_INT, }; struct bno055_priv { struct regmap *regmap; struct device *dev; struct clk *clk; int operation_mode; int xfer_burst_break_thr; struct mutex lock; u8 uid[BNO055_UID_LEN]; struct gpio_desc *reset_gpio; bool sw_reset; struct { __le16 chans[BNO055_SCAN_CH_COUNT]; s64 timestamp __aligned(8); } buf; struct dentry *debugfs; }; static bool bno055_regmap_volatile(struct device *dev, unsigned int reg) { /* data and status registers */ if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG) return true; /* when in fusion mode, config is updated by chip */ if (reg == BNO055_MAG_CONFIG_REG || reg == BNO055_ACC_CONFIG_REG || reg == BNO055_GYR_CONFIG_REG) return true; /* calibration data may be updated by the IMU */ if (reg >= BNO055_CALDATA_START && reg <= BNO055_CALDATA_END) return true; return false; } static bool bno055_regmap_readable(struct device *dev, unsigned int reg) { /* unnamed PG0 reserved areas */ if ((reg < BNO055_PG1(0) && reg > BNO055_CALDATA_END) || reg == 0x3C) return false; /* unnamed PG1 reserved areas */ if (reg > BNO055_PG1(BNO055_UID_HIGHER_REG) || (reg < BNO055_PG1(BNO055_UID_LOWER_REG) && reg > BNO055_PG1(BNO055_GYR_AM_SET_REG)) || reg == BNO055_PG1(0xE) || (reg < BNO055_PG1(BNO055_PAGESEL_REG) && reg >= BNO055_PG1(0x0))) return false; return true; } static bool bno055_regmap_writeable(struct device *dev, unsigned int reg) { /* * Unreadable registers are indeed reserved; there are no WO regs * (except for a single bit in SYS_TRIGGER register) */ if (!bno055_regmap_readable(dev, reg)) return false; /* data and status registers */ if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG) return false; /* ID areas */ if (reg < BNO055_PAGESEL_REG || (reg <= BNO055_UID_HIGHER_REG && reg >= BNO055_UID_LOWER_REG)) return false; return true; } static const struct regmap_range_cfg bno055_regmap_ranges[] = { { .range_min = 0, .range_max = 0x7f * 2, .selector_reg = BNO055_PAGESEL_REG, .selector_mask = GENMASK(7, 0), .selector_shift = 0, .window_start = 0, .window_len = 0x80, }, }; const struct regmap_config bno055_regmap_config = { .name = "bno055", .reg_bits = 8, .val_bits = 8, .ranges = bno055_regmap_ranges, .num_ranges = 1, .volatile_reg = bno055_regmap_volatile, .max_register = 0x80 * 2, .writeable_reg = bno055_regmap_writeable, .readable_reg = bno055_regmap_readable, .cache_type = REGCACHE_RBTREE, }; EXPORT_SYMBOL_NS_GPL(bno055_regmap_config, IIO_BNO055); /* must be called in configuration mode */ static int bno055_calibration_load(struct bno055_priv *priv, const u8 *data, int len) { if (len != BNO055_CALDATA_LEN) { dev_dbg(priv->dev, "Invalid calibration file size %d (expected %d)", len, BNO055_CALDATA_LEN); return -EINVAL; } dev_dbg(priv->dev, "loading cal data: %*ph", BNO055_CALDATA_LEN, data); return regmap_bulk_write(priv->regmap, BNO055_CALDATA_START, data, BNO055_CALDATA_LEN); } static int bno055_operation_mode_do_set(struct bno055_priv *priv, int operation_mode) { int ret; ret = regmap_write(priv->regmap, BNO055_OPR_MODE_REG, operation_mode); if (ret) return ret; /* Following datasheet specifications: sensor takes 7mS up to 19 mS to switch mode */ msleep(20); return 0; } static int bno055_system_reset(struct bno055_priv *priv) { int ret; if (priv->reset_gpio) { gpiod_set_value_cansleep(priv->reset_gpio, 0); usleep_range(5000, 10000); gpiod_set_value_cansleep(priv->reset_gpio, 1); } else if (priv->sw_reset) { ret = regmap_write(priv->regmap, BNO055_SYS_TRIGGER_REG, BNO055_SYS_TRIGGER_RST_SYS); if (ret) return ret; } else { return 0; } regcache_drop_region(priv->regmap, 0x0, 0xff); usleep_range(650000, 700000); return 0; } static int bno055_init(struct bno055_priv *priv, const u8 *caldata, int len) { int ret; ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); if (ret) return ret; ret = regmap_write(priv->regmap, BNO055_POWER_MODE_REG, BNO055_POWER_MODE_NORMAL); if (ret) return ret; ret = regmap_write(priv->regmap, BNO055_SYS_TRIGGER_REG, priv->clk ? BNO055_SYS_TRIGGER_CLK_SEL : 0); if (ret) return ret; /* use standard SI units */ ret = regmap_write(priv->regmap, BNO055_UNIT_SEL_REG, BNO055_UNIT_SEL_ANDROID | BNO055_UNIT_SEL_GYR_RPS); if (ret) return ret; if (caldata) { ret = bno055_calibration_load(priv, caldata, len); if (ret) dev_warn(priv->dev, "failed to load calibration data with error %d\n", ret); } return 0; } static ssize_t bno055_operation_mode_set(struct bno055_priv *priv, int operation_mode) { u8 caldata[BNO055_CALDATA_LEN]; int ret; mutex_lock(&priv->lock); ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); if (ret) goto exit_unlock; if (operation_mode == BNO055_OPR_MODE_FUSION || operation_mode == BNO055_OPR_MODE_FUSION_FMC_OFF) { /* for entering fusion mode, reset the chip to clear the algo state */ ret = regmap_bulk_read(priv->regmap, BNO055_CALDATA_START, caldata, BNO055_CALDATA_LEN); if (ret) goto exit_unlock; ret = bno055_system_reset(priv); if (ret) goto exit_unlock; ret = bno055_init(priv, caldata, BNO055_CALDATA_LEN); if (ret) goto exit_unlock; } ret = bno055_operation_mode_do_set(priv, operation_mode); if (ret) goto exit_unlock; priv->operation_mode = operation_mode; exit_unlock: mutex_unlock(&priv->lock); return ret; } static void bno055_uninit(void *arg) { struct bno055_priv *priv = arg; /* stop the IMU */ bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); } #define BNO055_CHANNEL(_type, _axis, _index, _address, _sep, _sh, _avail) { \ .address = _address, \ .type = _type, \ .modified = 1, \ .channel2 = IIO_MOD_##_axis, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | (_sep), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | (_sh), \ .info_mask_shared_by_type_available = _avail, \ .scan_index = _index, \ .scan_type = { \ .sign = 's', \ .realbits = 16, \ .storagebits = 16, \ .endianness = IIO_LE, \ .repeat = IIO_MOD_##_axis == IIO_MOD_QUATERNION ? 4 : 0, \ }, \ } /* scan indexes follow DATA register order */ enum bno055_scan_axis { BNO055_SCAN_ACCEL_X, BNO055_SCAN_ACCEL_Y, BNO055_SCAN_ACCEL_Z, BNO055_SCAN_MAGN_X, BNO055_SCAN_MAGN_Y, BNO055_SCAN_MAGN_Z, BNO055_SCAN_GYRO_X, BNO055_SCAN_GYRO_Y, BNO055_SCAN_GYRO_Z, BNO055_SCAN_YAW, BNO055_SCAN_ROLL, BNO055_SCAN_PITCH, BNO055_SCAN_QUATERNION, BNO055_SCAN_LIA_X, BNO055_SCAN_LIA_Y, BNO055_SCAN_LIA_Z, BNO055_SCAN_GRAVITY_X, BNO055_SCAN_GRAVITY_Y, BNO055_SCAN_GRAVITY_Z, BNO055_SCAN_TIMESTAMP, _BNO055_SCAN_MAX }; static const struct iio_chan_spec bno055_channels[] = { /* accelerometer */ BNO055_CHANNEL(IIO_ACCEL, X, BNO055_SCAN_ACCEL_X, BNO055_ACC_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), BNO055_CHANNEL(IIO_ACCEL, Y, BNO055_SCAN_ACCEL_Y, BNO055_ACC_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), BNO055_CHANNEL(IIO_ACCEL, Z, BNO055_SCAN_ACCEL_Z, BNO055_ACC_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY)), /* gyroscope */ BNO055_CHANNEL(IIO_ANGL_VEL, X, BNO055_SCAN_GYRO_X, BNO055_GYR_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | BIT(IIO_CHAN_INFO_SCALE)), BNO055_CHANNEL(IIO_ANGL_VEL, Y, BNO055_SCAN_GYRO_Y, BNO055_GYR_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | BIT(IIO_CHAN_INFO_SCALE)), BNO055_CHANNEL(IIO_ANGL_VEL, Z, BNO055_SCAN_GYRO_Z, BNO055_GYR_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | BIT(IIO_CHAN_INFO_SCALE)), /* magnetometer */ BNO055_CHANNEL(IIO_MAGN, X, BNO055_SCAN_MAGN_X, BNO055_MAG_DATA_X_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), BIT(IIO_CHAN_INFO_SAMP_FREQ), BIT(IIO_CHAN_INFO_SAMP_FREQ)), BNO055_CHANNEL(IIO_MAGN, Y, BNO055_SCAN_MAGN_Y, BNO055_MAG_DATA_Y_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), BIT(IIO_CHAN_INFO_SAMP_FREQ), BIT(IIO_CHAN_INFO_SAMP_FREQ)), BNO055_CHANNEL(IIO_MAGN, Z, BNO055_SCAN_MAGN_Z, BNO055_MAG_DATA_Z_LSB_REG, BIT(IIO_CHAN_INFO_OFFSET), BIT(IIO_CHAN_INFO_SAMP_FREQ), BIT(IIO_CHAN_INFO_SAMP_FREQ)), /* euler angle */ BNO055_CHANNEL(IIO_ROT, YAW, BNO055_SCAN_YAW, BNO055_EUL_DATA_X_LSB_REG, 0, 0, 0), BNO055_CHANNEL(IIO_ROT, ROLL, BNO055_SCAN_ROLL, BNO055_EUL_DATA_Y_LSB_REG, 0, 0, 0), BNO055_CHANNEL(IIO_ROT, PITCH, BNO055_SCAN_PITCH, BNO055_EUL_DATA_Z_LSB_REG, 0, 0, 0), /* quaternion */ BNO055_CHANNEL(IIO_ROT, QUATERNION, BNO055_SCAN_QUATERNION, BNO055_QUAT_DATA_W_LSB_REG, 0, 0, 0), /* linear acceleration */ BNO055_CHANNEL(IIO_ACCEL, LINEAR_X, BNO055_SCAN_LIA_X, BNO055_LIA_DATA_X_LSB_REG, 0, 0, 0), BNO055_CHANNEL(IIO_ACCEL, LINEAR_Y, BNO055_SCAN_LIA_Y, BNO055_LIA_DATA_Y_LSB_REG, 0, 0, 0), BNO055_CHANNEL(IIO_ACCEL, LINEAR_Z, BNO055_SCAN_LIA_Z, BNO055_LIA_DATA_Z_LSB_REG, 0, 0, 0), /* gravity vector */ BNO055_CHANNEL(IIO_GRAVITY, X, BNO055_SCAN_GRAVITY_X, BNO055_GRAVITY_DATA_X_LSB_REG, 0, 0, 0), BNO055_CHANNEL(IIO_GRAVITY, Y, BNO055_SCAN_GRAVITY_Y, BNO055_GRAVITY_DATA_Y_LSB_REG, 0, 0, 0), BNO055_CHANNEL(IIO_GRAVITY, Z, BNO055_SCAN_GRAVITY_Z, BNO055_GRAVITY_DATA_Z_LSB_REG, 0, 0, 0), { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), .scan_index = -1, }, IIO_CHAN_SOFT_TIMESTAMP(BNO055_SCAN_TIMESTAMP), }; static int bno055_get_regmask(struct bno055_priv *priv, int *val, int *val2, int reg, int mask, struct bno055_sysfs_attr *attr) { const int shift = __ffs(mask); int hwval, idx; int ret; int i; ret = regmap_read(priv->regmap, reg, &hwval); if (ret) return ret; idx = (hwval & mask) >> shift; if (attr->hw_xlate) for (i = 0; i < attr->len; i++) if (attr->hw_xlate[i] == idx) { idx = i; break; } if (attr->type == IIO_VAL_INT) { *val = attr->vals[idx]; } else { /* IIO_VAL_INT_PLUS_MICRO or IIO_VAL_FRACTIONAL */ *val = attr->vals[idx * 2]; *val2 = attr->vals[idx * 2 + 1]; } return attr->type; } static int bno055_set_regmask(struct bno055_priv *priv, int val, int val2, int reg, int mask, struct bno055_sysfs_attr *attr) { const int shift = __ffs(mask); int best_delta; int req_val; int tbl_val; bool first; int delta; int hwval; int ret; int len; int i; /* * The closest value the HW supports is only one in fusion mode, * and it is autoselected, so don't do anything, just return OK, * as the closest possible value has been (virtually) selected */ if (priv->operation_mode != BNO055_OPR_MODE_AMG) return 0; len = attr->len; /* * We always get a request in INT_PLUS_MICRO, but we * take care of the micro part only when we really have * non-integer tables. This prevents 32-bit overflow with * larger integers contained in integer tables. */ req_val = val; if (attr->type != IIO_VAL_INT) { len /= 2; req_val = min(val, 2147) * 1000000 + val2; } first = true; for (i = 0; i < len; i++) { switch (attr->type) { case IIO_VAL_INT: tbl_val = attr->vals[i]; break; case IIO_VAL_INT_PLUS_MICRO: WARN_ON(attr->vals[i * 2] > 2147); tbl_val = attr->vals[i * 2] * 1000000 + attr->vals[i * 2 + 1]; break; case IIO_VAL_FRACTIONAL: WARN_ON(attr->vals[i * 2] > 4294); tbl_val = attr->vals[i * 2] * 1000000 / attr->vals[i * 2 + 1]; break; default: return -EINVAL; } delta = abs(tbl_val - req_val); if (first || delta < best_delta) { best_delta = delta; hwval = i; first = false; } } if (attr->hw_xlate) hwval = attr->hw_xlate[hwval]; ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); if (ret) return ret; ret = regmap_update_bits(priv->regmap, reg, mask, hwval << shift); if (ret) return ret; return bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_AMG); } static int bno055_read_simple_chan(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { struct bno055_priv *priv = iio_priv(indio_dev); __le16 raw_val; int ret; switch (mask) { case IIO_CHAN_INFO_RAW: ret = regmap_bulk_read(priv->regmap, chan->address, &raw_val, sizeof(raw_val)); if (ret < 0) return ret; *val = sign_extend32(le16_to_cpu(raw_val), 15); return IIO_VAL_INT; case IIO_CHAN_INFO_OFFSET: if (priv->operation_mode != BNO055_OPR_MODE_AMG) { *val = 0; } else { ret = regmap_bulk_read(priv->regmap, chan->address + BNO055_REG_OFFSET_ADDR, &raw_val, sizeof(raw_val)); if (ret < 0) return ret; /* * IMU reports sensor offsets; IIO wants correction * offsets, thus we need the 'minus' here. */ *val = -sign_extend32(le16_to_cpu(raw_val), 15); } return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = 1; switch (chan->type) { case IIO_GRAVITY: /* Table 3-35: 1 m/s^2 = 100 LSB */ case IIO_ACCEL: /* Table 3-17: 1 m/s^2 = 100 LSB */ *val2 = 100; break; case IIO_MAGN: /* * Table 3-19: 1 uT = 16 LSB. But we need * Gauss: 1G = 0.1 uT. */ *val2 = 160; break; case IIO_ANGL_VEL: /* * Table 3-22: 1 Rps = 900 LSB * .. but this is not exactly true. See comment at the * beginning of this file. */ if (priv->operation_mode != BNO055_OPR_MODE_AMG) { *val = bno055_gyr_scale.fusion_vals[0]; *val2 = bno055_gyr_scale.fusion_vals[1]; return IIO_VAL_FRACTIONAL; } return bno055_get_regmask(priv, val, val2, BNO055_GYR_CONFIG_REG, BNO055_GYR_CONFIG_RANGE_MASK, &bno055_gyr_scale); break; case IIO_ROT: /* Table 3-28: 1 degree = 16 LSB */ *val2 = 16; break; default: return -EINVAL; } return IIO_VAL_FRACTIONAL; case IIO_CHAN_INFO_SAMP_FREQ: if (chan->type != IIO_MAGN) return -EINVAL; return bno055_get_regmask(priv, val, val2, BNO055_MAG_CONFIG_REG, BNO055_MAG_CONFIG_ODR_MASK, &bno055_mag_odr); case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: switch (chan->type) { case IIO_ANGL_VEL: return bno055_get_regmask(priv, val, val2, BNO055_GYR_CONFIG_REG, BNO055_GYR_CONFIG_LPF_MASK, &bno055_gyr_lpf); case IIO_ACCEL: return bno055_get_regmask(priv, val, val2, BNO055_ACC_CONFIG_REG, BNO055_ACC_CONFIG_LPF_MASK, &bno055_acc_lpf); default: return -EINVAL; } default: return -EINVAL; } } static int bno055_sysfs_attr_avail(struct bno055_priv *priv, struct bno055_sysfs_attr *attr, const int **vals, int *length) { if (priv->operation_mode != BNO055_OPR_MODE_AMG) { /* locked when fusion enabled */ *vals = attr->fusion_vals; if (attr->type == IIO_VAL_INT) *length = 1; else *length = 2; /* IIO_VAL_INT_PLUS_MICRO or IIO_VAL_FRACTIONAL*/ } else { *vals = attr->vals; *length = attr->len; } return attr->type; } static int bno055_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, const int **vals, int *type, int *length, long mask) { struct bno055_priv *priv = iio_priv(indio_dev); switch (mask) { case IIO_CHAN_INFO_SCALE: switch (chan->type) { case IIO_ANGL_VEL: *type = bno055_sysfs_attr_avail(priv, &bno055_gyr_scale, vals, length); return IIO_AVAIL_LIST; default: return -EINVAL; } case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: switch (chan->type) { case IIO_ANGL_VEL: *type = bno055_sysfs_attr_avail(priv, &bno055_gyr_lpf, vals, length); return IIO_AVAIL_LIST; case IIO_ACCEL: *type = bno055_sysfs_attr_avail(priv, &bno055_acc_lpf, vals, length); return IIO_AVAIL_LIST; default: return -EINVAL; } break; case IIO_CHAN_INFO_SAMP_FREQ: switch (chan->type) { case IIO_MAGN: *type = bno055_sysfs_attr_avail(priv, &bno055_mag_odr, vals, length); return IIO_AVAIL_LIST; default: return -EINVAL; } default: return -EINVAL; } } static int bno055_read_temp_chan(struct iio_dev *indio_dev, int *val) { struct bno055_priv *priv = iio_priv(indio_dev); unsigned int raw_val; int ret; ret = regmap_read(priv->regmap, BNO055_TEMP_REG, &raw_val); if (ret < 0) return ret; /* * Tables 3-36 and 3-37: one byte of priv, signed, 1 LSB = 1C. * ABI wants milliC. */ *val = raw_val * 1000; return IIO_VAL_INT; } static int bno055_read_quaternion(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int size, int *vals, int *val_len, long mask) { struct bno055_priv *priv = iio_priv(indio_dev); __le16 raw_vals[4]; int i, ret; switch (mask) { case IIO_CHAN_INFO_RAW: if (size < 4) return -EINVAL; ret = regmap_bulk_read(priv->regmap, BNO055_QUAT_DATA_W_LSB_REG, raw_vals, sizeof(raw_vals)); if (ret < 0) return ret; for (i = 0; i < 4; i++) vals[i] = sign_extend32(le16_to_cpu(raw_vals[i]), 15); *val_len = 4; return IIO_VAL_INT_MULTIPLE; case IIO_CHAN_INFO_SCALE: /* Table 3-31: 1 quaternion = 2^14 LSB */ if (size < 2) return -EINVAL; vals[0] = 1; vals[1] = 14; return IIO_VAL_FRACTIONAL_LOG2; default: return -EINVAL; } } static bool bno055_is_chan_readable(struct iio_dev *indio_dev, struct iio_chan_spec const *chan) { struct bno055_priv *priv = iio_priv(indio_dev); if (priv->operation_mode != BNO055_OPR_MODE_AMG) return true; switch (chan->type) { case IIO_GRAVITY: case IIO_ROT: return false; case IIO_ACCEL: if (chan->channel2 == IIO_MOD_LINEAR_X || chan->channel2 == IIO_MOD_LINEAR_Y || chan->channel2 == IIO_MOD_LINEAR_Z) return false; return true; default: return true; } } static int _bno055_read_raw_multi(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int size, int *vals, int *val_len, long mask) { if (!bno055_is_chan_readable(indio_dev, chan)) return -EBUSY; switch (chan->type) { case IIO_MAGN: case IIO_ACCEL: case IIO_ANGL_VEL: case IIO_GRAVITY: if (size < 2) return -EINVAL; *val_len = 2; return bno055_read_simple_chan(indio_dev, chan, &vals[0], &vals[1], mask); case IIO_TEMP: *val_len = 1; return bno055_read_temp_chan(indio_dev, &vals[0]); case IIO_ROT: /* * Rotation is exposed as either a quaternion or three * Euler angles. */ if (chan->channel2 == IIO_MOD_QUATERNION) return bno055_read_quaternion(indio_dev, chan, size, vals, val_len, mask); if (size < 2) return -EINVAL; *val_len = 2; return bno055_read_simple_chan(indio_dev, chan, &vals[0], &vals[1], mask); default: return -EINVAL; } } static int bno055_read_raw_multi(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int size, int *vals, int *val_len, long mask) { struct bno055_priv *priv = iio_priv(indio_dev); int ret; mutex_lock(&priv->lock); ret = _bno055_read_raw_multi(indio_dev, chan, size, vals, val_len, mask); mutex_unlock(&priv->lock); return ret; } static int _bno055_write_raw(struct iio_dev *iio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { struct bno055_priv *priv = iio_priv(iio_dev); switch (chan->type) { case IIO_MAGN: switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: return bno055_set_regmask(priv, val, val2, BNO055_MAG_CONFIG_REG, BNO055_MAG_CONFIG_ODR_MASK, &bno055_mag_odr); default: return -EINVAL; } case IIO_ACCEL: switch (mask) { case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: return bno055_set_regmask(priv, val, val2, BNO055_ACC_CONFIG_REG, BNO055_ACC_CONFIG_LPF_MASK, &bno055_acc_lpf); default: return -EINVAL; } case IIO_ANGL_VEL: switch (mask) { case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: return bno055_set_regmask(priv, val, val2, BNO055_GYR_CONFIG_REG, BNO055_GYR_CONFIG_LPF_MASK, &bno055_gyr_lpf); case IIO_CHAN_INFO_SCALE: return bno055_set_regmask(priv, val, val2, BNO055_GYR_CONFIG_REG, BNO055_GYR_CONFIG_RANGE_MASK, &bno055_gyr_scale); default: return -EINVAL; } default: return -EINVAL; } } static int bno055_write_raw(struct iio_dev *iio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { struct bno055_priv *priv = iio_priv(iio_dev); int ret; mutex_lock(&priv->lock); ret = _bno055_write_raw(iio_dev, chan, val, val2, mask); mutex_unlock(&priv->lock); return ret; } static ssize_t in_accel_range_raw_available_show(struct device *dev, struct device_attribute *attr, char *buf) { struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); int len = 0; int i; if (priv->operation_mode != BNO055_OPR_MODE_AMG) return sysfs_emit(buf, "%d\n", bno055_acc_range.fusion_vals[0]); for (i = 0; i < bno055_acc_range.len; i++) len += sysfs_emit_at(buf, len, "%d ", bno055_acc_range.vals[i]); buf[len - 1] = '\n'; return len; } static ssize_t fusion_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); return sysfs_emit(buf, "%d\n", priv->operation_mode != BNO055_OPR_MODE_AMG); } static ssize_t fusion_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct bno055_priv *priv = iio_priv(indio_dev); bool en; int ret; if (indio_dev->active_scan_mask && !bitmap_empty(indio_dev->active_scan_mask, _BNO055_SCAN_MAX)) return -EBUSY; ret = kstrtobool(buf, &en); if (ret) return -EINVAL; if (!en) return bno055_operation_mode_set(priv, BNO055_OPR_MODE_AMG) ?: len; /* * Coming from AMG means the FMC was off, just switch to fusion but * don't change anything that doesn't belong to us (i.e let FMC stay off). * Coming from any other fusion mode means we don't need to do anything. */ if (priv->operation_mode == BNO055_OPR_MODE_AMG) return bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF) ?: len; return len; } static ssize_t in_magn_calibration_fast_enable_show(struct device *dev, struct device_attribute *attr, char *buf) { struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); return sysfs_emit(buf, "%d\n", priv->operation_mode == BNO055_OPR_MODE_FUSION); } static ssize_t in_magn_calibration_fast_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct iio_dev *indio_dev = dev_to_iio_dev(dev); struct bno055_priv *priv = iio_priv(indio_dev); int ret; if (indio_dev->active_scan_mask && !bitmap_empty(indio_dev->active_scan_mask, _BNO055_SCAN_MAX)) return -EBUSY; if (sysfs_streq(buf, "0")) { if (priv->operation_mode == BNO055_OPR_MODE_FUSION) { ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION_FMC_OFF); if (ret) return ret; } } else { if (priv->operation_mode == BNO055_OPR_MODE_AMG) return -EINVAL; if (priv->operation_mode != BNO055_OPR_MODE_FUSION) { ret = bno055_operation_mode_set(priv, BNO055_OPR_MODE_FUSION); if (ret) return ret; } } return len; } static ssize_t in_accel_range_raw_show(struct device *dev, struct device_attribute *attr, char *buf) { struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); int val; int ret; ret = bno055_get_regmask(priv, &val, NULL, BNO055_ACC_CONFIG_REG, BNO055_ACC_CONFIG_RANGE_MASK, &bno055_acc_range); if (ret < 0) return ret; return sysfs_emit(buf, "%d\n", val); } static ssize_t in_accel_range_raw_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); unsigned long val; int ret; ret = kstrtoul(buf, 10, &val); if (ret) return ret; mutex_lock(&priv->lock); ret = bno055_set_regmask(priv, val, 0, BNO055_ACC_CONFIG_REG, BNO055_ACC_CONFIG_RANGE_MASK, &bno055_acc_range); mutex_unlock(&priv->lock); return ret ?: len; } static ssize_t bno055_get_calib_status(struct device *dev, char *buf, int which) { struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); int calib; int ret; int val; if (priv->operation_mode == BNO055_OPR_MODE_AMG || (priv->operation_mode == BNO055_OPR_MODE_FUSION_FMC_OFF && which == BNO055_CALIB_STAT_MAGN_SHIFT)) { calib = 0; } else { mutex_lock(&priv->lock); ret = regmap_read(priv->regmap, BNO055_CALIB_STAT_REG, &val); mutex_unlock(&priv->lock); if (ret) return -EIO; calib = ((val >> which) & GENMASK(1, 0)) + 1; } return sysfs_emit(buf, "%d\n", calib); } static ssize_t serialnumber_show(struct device *dev, struct device_attribute *attr, char *buf) { struct bno055_priv *priv = iio_priv(dev_to_iio_dev(dev)); return sysfs_emit(buf, "%*ph\n", BNO055_UID_LEN, priv->uid); } static ssize_t calibration_data_read(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { struct bno055_priv *priv = iio_priv(dev_to_iio_dev(kobj_to_dev(kobj))); u8 data[BNO055_CALDATA_LEN]; int ret; /* * Calibration data is volatile; reading it in chunks will possibly * results in inconsistent data. We require the user to read the whole * blob in a single chunk */ if (count < BNO055_CALDATA_LEN || pos) return -EINVAL; mutex_lock(&priv->lock); ret = bno055_operation_mode_do_set(priv, BNO055_OPR_MODE_CONFIG); if (ret) goto exit_unlock; ret = regmap_bulk_read(priv->regmap, BNO055_CALDATA_START, data, BNO055_CALDATA_LEN); if (ret) goto exit_unlock; ret = bno055_operation_mode_do_set(priv, priv->operation_mode); if (ret) goto exit_unlock; memcpy(buf, data, BNO055_CALDATA_LEN); ret = BNO055_CALDATA_LEN; exit_unlock: mutex_unlock(&priv->lock); return ret; } static ssize_t sys_calibration_auto_status_show(struct device *dev, struct device_attribute *a, char *buf) { return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_SYS_SHIFT); } static ssize_t in_accel_calibration_auto_status_show(struct device *dev, struct device_attribute *a, char *buf) { return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_ACCEL_SHIFT); } static ssize_t in_gyro_calibration_auto_status_show(struct device *dev, struct device_attribute *a, char *buf) { return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_GYRO_SHIFT); } static ssize_t in_magn_calibration_auto_status_show(struct device *dev, struct device_attribute *a, char *buf) { return bno055_get_calib_status(dev, buf, BNO055_CALIB_STAT_MAGN_SHIFT); } static int bno055_debugfs_reg_access(struct iio_dev *iio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { struct bno055_priv *priv = iio_priv(iio_dev); if (readval) return regmap_read(priv->regmap, reg, readval); else return regmap_write(priv->regmap, reg, writeval); } static ssize_t bno055_show_fw_version(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { struct bno055_priv *priv = file->private_data; int rev, ver; char *buf; int ret; ret = regmap_read(priv->regmap, BNO055_SW_REV_LSB_REG, &rev); if (ret) return ret; ret = regmap_read(priv->regmap, BNO055_SW_REV_MSB_REG, &ver); if (ret) return ret; buf = kasprintf(GFP_KERNEL, "ver: 0x%x, rev: 0x%x\n", ver, rev); if (!buf) return -ENOMEM; ret = simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf)); kfree(buf); return ret; } static const struct file_operations bno055_fw_version_ops = { .open = simple_open, .read = bno055_show_fw_version, .llseek = default_llseek, .owner = THIS_MODULE, }; static void bno055_debugfs_remove(void *_priv) { struct bno055_priv *priv = _priv; debugfs_remove(priv->debugfs); priv->debugfs = NULL; } static void bno055_debugfs_init(struct iio_dev *iio_dev) { struct bno055_priv *priv = iio_priv(iio_dev); priv->debugfs = debugfs_create_file("firmware_version", 0400, iio_get_debugfs_dentry(iio_dev), priv, &bno055_fw_version_ops); if (!IS_ERR(priv->debugfs)) devm_add_action_or_reset(priv->dev, bno055_debugfs_remove, priv); if (IS_ERR_OR_NULL(priv->debugfs)) dev_warn(priv->dev, "failed to setup debugfs"); } static IIO_DEVICE_ATTR_RW(fusion_enable, 0); static IIO_DEVICE_ATTR_RW(in_magn_calibration_fast_enable, 0); static IIO_DEVICE_ATTR_RW(in_accel_range_raw, 0); static IIO_DEVICE_ATTR_RO(in_accel_range_raw_available, 0); static IIO_DEVICE_ATTR_RO(sys_calibration_auto_status, 0); static IIO_DEVICE_ATTR_RO(in_accel_calibration_auto_status, 0); static IIO_DEVICE_ATTR_RO(in_gyro_calibration_auto_status, 0); static IIO_DEVICE_ATTR_RO(in_magn_calibration_auto_status, 0); static IIO_DEVICE_ATTR_RO(serialnumber, 0); static struct attribute *bno055_attrs[] = { &iio_dev_attr_in_accel_range_raw_available.dev_attr.attr, &iio_dev_attr_in_accel_range_raw.dev_attr.attr, &iio_dev_attr_fusion_enable.dev_attr.attr, &iio_dev_attr_in_magn_calibration_fast_enable.dev_attr.attr, &iio_dev_attr_sys_calibration_auto_status.dev_attr.attr, &iio_dev_attr_in_accel_calibration_auto_status.dev_attr.attr, &iio_dev_attr_in_gyro_calibration_auto_status.dev_attr.attr, &iio_dev_attr_in_magn_calibration_auto_status.dev_attr.attr, &iio_dev_attr_serialnumber.dev_attr.attr, NULL }; static BIN_ATTR_RO(calibration_data, BNO055_CALDATA_LEN); static struct bin_attribute *bno055_bin_attrs[] = { &bin_attr_calibration_data, NULL }; static const struct attribute_group bno055_attrs_group = { .attrs = bno055_attrs, .bin_attrs = bno055_bin_attrs, }; static const struct iio_info bno055_info = { .read_raw_multi = bno055_read_raw_multi, .read_avail = bno055_read_avail, .write_raw = bno055_write_raw, .attrs = &bno055_attrs_group, .debugfs_reg_access = bno055_debugfs_reg_access, }; /* * Reads len samples from the HW, stores them in buf starting from buf_idx, * and applies mask to cull (skip) unneeded samples. * Updates buf_idx incrementing with the number of stored samples. * Samples from HW are transferred into buf, then in-place copy on buf is * performed in order to cull samples that need to be skipped. * This avoids copies of the first samples until we hit the 1st sample to skip, * and also avoids having an extra bounce buffer. * buf must be able to contain len elements in spite of how many samples we are * going to cull. */ static int bno055_scan_xfer(struct bno055_priv *priv, int start_ch, int len, unsigned long mask, __le16 *buf, int *buf_idx) { const int base = BNO055_ACC_DATA_X_LSB_REG; bool quat_in_read = false; int buf_base = *buf_idx; __le16 *dst, *src; int offs_fixup = 0; int xfer_len = len; int ret; int i, n; if (!mask) return 0; /* * All channels are made up 1 16-bit sample, except for quaternion that * is made up 4 16-bit values. * For us the quaternion CH is just like 4 regular CHs. * If our read starts past the quaternion make sure to adjust the * starting offset; if the quaternion is contained in our scan then make * sure to adjust the read len. */ if (start_ch > BNO055_SCAN_QUATERNION) { start_ch += 3; } else if ((start_ch <= BNO055_SCAN_QUATERNION) && ((start_ch + len) > BNO055_SCAN_QUATERNION)) { quat_in_read = true; xfer_len += 3; } ret = regmap_bulk_read(priv->regmap, base + start_ch * sizeof(__le16), buf + buf_base, xfer_len * sizeof(__le16)); if (ret) return ret; for_each_set_bit(i, &mask, len) { if (quat_in_read && ((start_ch + i) > BNO055_SCAN_QUATERNION)) offs_fixup = 3; dst = buf + *buf_idx; src = buf + buf_base + offs_fixup + i; n = (start_ch + i == BNO055_SCAN_QUATERNION) ? 4 : 1; if (dst != src) memcpy(dst, src, n * sizeof(__le16)); *buf_idx += n; } return 0; } static irqreturn_t bno055_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; struct iio_dev *iio_dev = pf->indio_dev; struct bno055_priv *priv = iio_priv(iio_dev); int xfer_start, start, end, prev_end; unsigned long mask; int quat_extra_len; bool first = true; int buf_idx = 0; bool thr_hit; int ret; mutex_lock(&priv->lock); /* * Walk the bitmap and eventually perform several transfers. * Bitmap ones-fields that are separated by gaps <= xfer_burst_break_thr * will be included in same transfer. * Every time the bitmap contains a gap wider than xfer_burst_break_thr * then we split the transfer, skipping the gap. */ for_each_set_bitrange(start, end, iio_dev->active_scan_mask, iio_dev->masklength) { /* * First transfer will start from the beginning of the first * ones-field in the bitmap */ if (first) { xfer_start = start; } else { /* * We found the next ones-field; check whether to * include it in * the current transfer or not (i.e. * let's perform the current * transfer and prepare for * another one). */ /* * In case the zeros-gap contains the quaternion bit, * then its length is actually 4 words instead of 1 * (i.e. +3 wrt other channels). */ quat_extra_len = ((start > BNO055_SCAN_QUATERNION) && (prev_end <= BNO055_SCAN_QUATERNION)) ? 3 : 0; /* If the gap is wider than xfer_burst_break_thr then.. */ thr_hit = (start - prev_end + quat_extra_len) > priv->xfer_burst_break_thr; /* * .. transfer all the data up to the gap. Then set the * next transfer start index at right after the gap * (i.e. at the start of this ones-field). */ if (thr_hit) { mask = *iio_dev->active_scan_mask >> xfer_start; ret = bno055_scan_xfer(priv, xfer_start, prev_end - xfer_start, mask, priv->buf.chans, &buf_idx); if (ret) goto done; xfer_start = start; } } first = false; prev_end = end; } /* * We finished walking the bitmap; no more gaps to check for. Just * perform the current transfer. */ mask = *iio_dev->active_scan_mask >> xfer_start; ret = bno055_scan_xfer(priv, xfer_start, prev_end - xfer_start, mask, priv->buf.chans, &buf_idx); if (!ret) iio_push_to_buffers_with_timestamp(iio_dev, &priv->buf, pf->timestamp); done: mutex_unlock(&priv->lock); iio_trigger_notify_done(iio_dev->trig); return IRQ_HANDLED; } static int bno055_buffer_preenable(struct iio_dev *indio_dev) { struct bno055_priv *priv = iio_priv(indio_dev); const unsigned long fusion_mask = BIT(BNO055_SCAN_YAW) | BIT(BNO055_SCAN_ROLL) | BIT(BNO055_SCAN_PITCH) | BIT(BNO055_SCAN_QUATERNION) | BIT(BNO055_SCAN_LIA_X) | BIT(BNO055_SCAN_LIA_Y) | BIT(BNO055_SCAN_LIA_Z) | BIT(BNO055_SCAN_GRAVITY_X) | BIT(BNO055_SCAN_GRAVITY_Y) | BIT(BNO055_SCAN_GRAVITY_Z); if (priv->operation_mode == BNO055_OPR_MODE_AMG && bitmap_intersects(indio_dev->active_scan_mask, &fusion_mask, _BNO055_SCAN_MAX)) return -EBUSY; return 0; } static const struct iio_buffer_setup_ops bno055_buffer_setup_ops = { .preenable = bno055_buffer_preenable, }; int bno055_probe(struct device *dev, struct regmap *regmap, int xfer_burst_break_thr, bool sw_reset) { const struct firmware *caldata = NULL; struct bno055_priv *priv; struct iio_dev *iio_dev; char *fw_name_buf; unsigned int val; int rev, ver; int ret; iio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); if (!iio_dev) return -ENOMEM; iio_dev->name = "bno055"; priv = iio_priv(iio_dev); mutex_init(&priv->lock); priv->regmap = regmap; priv->dev = dev; priv->xfer_burst_break_thr = xfer_burst_break_thr; priv->sw_reset = sw_reset; priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(priv->reset_gpio)) return dev_err_probe(dev, PTR_ERR(priv->reset_gpio), "Failed to get reset GPIO\n"); priv->clk = devm_clk_get_optional_enabled(dev, "clk"); if (IS_ERR(priv->clk)) return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get CLK\n"); if (priv->reset_gpio) { usleep_range(5000, 10000); gpiod_set_value_cansleep(priv->reset_gpio, 1); usleep_range(650000, 750000); } else if (!sw_reset) { dev_warn(dev, "No usable reset method; IMU may be unreliable\n"); } ret = regmap_read(priv->regmap, BNO055_CHIP_ID_REG, &val); if (ret) return ret; if (val != BNO055_CHIP_ID_MAGIC) dev_warn(dev, "Unrecognized chip ID 0x%x\n", val); /* * In case we haven't a HW reset pin, we can still reset the chip via * register write. This is probably nonsense in case we can't even * communicate with the chip or the chip isn't the one we expect (i.e. * we don't write to unknown chips), so we perform SW reset only after * chip magic ID check */ if (!priv->reset_gpio) { ret = bno055_system_reset(priv); if (ret) return ret; } ret = regmap_read(priv->regmap, BNO055_SW_REV_LSB_REG, &rev); if (ret) return ret; ret = regmap_read(priv->regmap, BNO055_SW_REV_MSB_REG, &ver); if (ret) return ret; /* * The stock FW version contains a bug (see comment at the beginning of * this file) that causes the anglvel scale to be changed depending on * the chip range setting. We workaround this, but we don't know what * other FW versions might do. */ if (ver != 0x3 || rev != 0x11) dev_warn(dev, "Untested firmware version. Anglvel scale may not work as expected\n"); ret = regmap_bulk_read(priv->regmap, BNO055_UID_LOWER_REG, priv->uid, BNO055_UID_LEN); if (ret) return ret; /* Sensor calibration data */ fw_name_buf = kasprintf(GFP_KERNEL, BNO055_FW_UID_FMT, BNO055_UID_LEN, priv->uid); if (!fw_name_buf) return -ENOMEM; ret = request_firmware(&caldata, fw_name_buf, dev); kfree(fw_name_buf); if (ret) ret = request_firmware(&caldata, BNO055_FW_GENERIC_NAME, dev); if (ret) { dev_notice(dev, "Calibration file load failed. See instruction in kernel Documentation/iio/bno055.rst\n"); ret = bno055_init(priv, NULL, 0); } else { ret = bno055_init(priv, caldata->data, caldata->size); release_firmware(caldata); } if (ret) return ret; priv->operation_mode = BNO055_OPR_MODE_FUSION; ret = bno055_operation_mode_do_set(priv, priv->operation_mode); if (ret) return ret; ret = devm_add_action_or_reset(dev, bno055_uninit, priv); if (ret) return ret; iio_dev->channels = bno055_channels; iio_dev->num_channels = ARRAY_SIZE(bno055_channels); iio_dev->info = &bno055_info; iio_dev->modes = INDIO_DIRECT_MODE; ret = devm_iio_triggered_buffer_setup(dev, iio_dev, iio_pollfunc_store_time, bno055_trigger_handler, &bno055_buffer_setup_ops); if (ret) return ret; ret = devm_iio_device_register(dev, iio_dev); if (ret) return ret; bno055_debugfs_init(iio_dev); return 0; } EXPORT_SYMBOL_NS_GPL(bno055_probe, IIO_BNO055); MODULE_AUTHOR("Andrea Merello <andrea.merello@iit.it>"); MODULE_DESCRIPTION("Bosch BNO055 driver"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1