// 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.
*/
/* * 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
/* * 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
*/
staticbool bno055_regmap_volatile(struct device *dev, unsignedint reg)
{ /* data and status registers */ if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG) returntrue;
/* 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) returntrue;
/* calibration data may be updated by the IMU */ if (reg >= BNO055_CALDATA_START && reg <= BNO055_CALDATA_END) returntrue;
staticbool bno055_regmap_writeable(struct device *dev, unsignedint 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)) returnfalse;
/* data and status registers */ if (reg >= BNO055_ACC_DATA_X_LSB_REG && reg <= BNO055_SYS_ERR_REG) returnfalse;
/* ID areas */ if (reg < BNO055_PAGESEL_REG ||
(reg <= BNO055_UID_HIGHER_REG && reg >= BNO055_UID_LOWER_REG)) returnfalse;
staticint 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;
staticint bno055_get_regmask(struct bno055_priv *priv, int *val, int *val2, int reg, int mask, conststruct bno055_sysfs_attr *attr)
{ constint 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->hw_xlate_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;
}
staticint bno055_set_regmask(struct bno055_priv *priv, int val, int val2, int reg, int mask, conststruct bno055_sysfs_attr *attr)
{ constint 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;
staticint bno055_read_temp_chan(struct iio_dev *indio_dev, int *val)
{ struct bno055_priv *priv = iio_priv(indio_dev); unsignedint 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;
}
staticint 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;
}
}
if (priv->operation_mode != BNO055_OPR_MODE_AMG) returntrue;
switch (chan->type) { case IIO_GRAVITY: case IIO_ROT: returnfalse; case IIO_ACCEL: if (chan->channel2 == IIO_MOD_LINEAR_X ||
chan->channel2 == IIO_MOD_LINEAR_Y ||
chan->channel2 == IIO_MOD_LINEAR_Z) returnfalse; returntrue; default: returntrue;
}
}
staticint _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;
}
}
staticint 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;
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;
/* * 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;
}
/* * 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.
*/ staticint bno055_scan_xfer(struct bno055_priv *priv, int start_ch, int len, unsignedlong mask,
__le16 *buf, int *buf_idx)
{ constint 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;
} elseif ((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;
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; unsignedlong 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_get_masklength(iio_dev)) { /* * 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);
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);
} elseif (!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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.