summaryrefslogtreecommitdiffstats
path: root/drivers/iio/imu/bmi160
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/imu/bmi160')
-rw-r--r--drivers/iio/imu/bmi160/Kconfig33
-rw-r--r--drivers/iio/imu/bmi160/Makefile7
-rw-r--r--drivers/iio/imu/bmi160/bmi160.h31
-rw-r--r--drivers/iio/imu/bmi160/bmi160_core.c891
-rw-r--r--drivers/iio/imu/bmi160/bmi160_i2c.c71
-rw-r--r--drivers/iio/imu/bmi160/bmi160_spi.c68
6 files changed, 1101 insertions, 0 deletions
diff --git a/drivers/iio/imu/bmi160/Kconfig b/drivers/iio/imu/bmi160/Kconfig
new file mode 100644
index 000000000..9d14d85cc
--- /dev/null
+++ b/drivers/iio/imu/bmi160/Kconfig
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# BMI160 IMU driver
+#
+
+config BMI160
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config BMI160_I2C
+ tristate "Bosch BMI160 I2C driver"
+ depends on I2C
+ select BMI160
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for BMI160 IMU on I2C with
+ accelerometer, gyroscope and external BMG160 magnetometer.
+
+ This driver can also be built as a module. If so, the module will be
+ called bmi160_i2c.
+
+config BMI160_SPI
+ tristate "Bosch BMI160 SPI driver"
+ depends on SPI
+ select BMI160
+ select REGMAP_SPI
+ help
+ If you say yes here you get support for BMI160 IMU on SPI with
+ accelerometer, gyroscope and external BMG160 magnetometer.
+
+ This driver can also be built as a module. If so, the module will be
+ called bmi160_spi.
diff --git a/drivers/iio/imu/bmi160/Makefile b/drivers/iio/imu/bmi160/Makefile
new file mode 100644
index 000000000..fdcfeddf4
--- /dev/null
+++ b/drivers/iio/imu/bmi160/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for Bosch BMI160 IMU
+#
+obj-$(CONFIG_BMI160) += bmi160_core.o
+obj-$(CONFIG_BMI160_I2C) += bmi160_i2c.o
+obj-$(CONFIG_BMI160_SPI) += bmi160_spi.o
diff --git a/drivers/iio/imu/bmi160/bmi160.h b/drivers/iio/imu/bmi160/bmi160.h
new file mode 100644
index 000000000..32c2ea2d7
--- /dev/null
+++ b/drivers/iio/imu/bmi160/bmi160.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef BMI160_H_
+#define BMI160_H_
+
+#include <linux/iio/iio.h>
+#include <linux/regulator/consumer.h>
+
+struct bmi160_data {
+ struct regmap *regmap;
+ struct iio_trigger *trig;
+ struct regulator_bulk_data supplies[2];
+ struct iio_mount_matrix orientation;
+ /*
+ * Ensure natural alignment for timestamp if present.
+ * Max length needed: 2 * 3 channels + 4 bytes padding + 8 byte ts.
+ * If fewer channels are enabled, less space may be needed, as
+ * long as the timestamp is still aligned to 8 bytes.
+ */
+ __le16 buf[12] __aligned(8);
+};
+
+extern const struct regmap_config bmi160_regmap_config;
+
+int bmi160_core_probe(struct device *dev, struct regmap *regmap,
+ const char *name, bool use_spi);
+
+int bmi160_enable_irq(struct regmap *regmap, bool enable);
+
+int bmi160_probe_trigger(struct iio_dev *indio_dev, int irq, u32 irq_type);
+
+#endif /* BMI160_H_ */
diff --git a/drivers/iio/imu/bmi160/bmi160_core.c b/drivers/iio/imu/bmi160/bmi160_core.c
new file mode 100644
index 000000000..a77f1a834
--- /dev/null
+++ b/drivers/iio/imu/bmi160/bmi160_core.c
@@ -0,0 +1,891 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BMI160 - Bosch IMU (accel, gyro plus external magnetometer)
+ *
+ * Copyright (c) 2016, Intel Corporation.
+ * Copyright (c) 2019, Martin Kelly.
+ *
+ * IIO core driver for BMI160, with support for I2C/SPI busses
+ *
+ * TODO: magnetometer, hardware FIFO
+ */
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/property.h>
+#include <linux/regulator/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/iio/trigger.h>
+
+#include "bmi160.h"
+
+#define BMI160_REG_CHIP_ID 0x00
+#define BMI160_CHIP_ID_VAL 0xD1
+
+#define BMI160_REG_PMU_STATUS 0x03
+
+/* X axis data low byte address, the rest can be obtained using axis offset */
+#define BMI160_REG_DATA_MAGN_XOUT_L 0x04
+#define BMI160_REG_DATA_GYRO_XOUT_L 0x0C
+#define BMI160_REG_DATA_ACCEL_XOUT_L 0x12
+
+#define BMI160_REG_ACCEL_CONFIG 0x40
+#define BMI160_ACCEL_CONFIG_ODR_MASK GENMASK(3, 0)
+#define BMI160_ACCEL_CONFIG_BWP_MASK GENMASK(6, 4)
+
+#define BMI160_REG_ACCEL_RANGE 0x41
+#define BMI160_ACCEL_RANGE_2G 0x03
+#define BMI160_ACCEL_RANGE_4G 0x05
+#define BMI160_ACCEL_RANGE_8G 0x08
+#define BMI160_ACCEL_RANGE_16G 0x0C
+
+#define BMI160_REG_GYRO_CONFIG 0x42
+#define BMI160_GYRO_CONFIG_ODR_MASK GENMASK(3, 0)
+#define BMI160_GYRO_CONFIG_BWP_MASK GENMASK(5, 4)
+
+#define BMI160_REG_GYRO_RANGE 0x43
+#define BMI160_GYRO_RANGE_2000DPS 0x00
+#define BMI160_GYRO_RANGE_1000DPS 0x01
+#define BMI160_GYRO_RANGE_500DPS 0x02
+#define BMI160_GYRO_RANGE_250DPS 0x03
+#define BMI160_GYRO_RANGE_125DPS 0x04
+
+#define BMI160_REG_CMD 0x7E
+#define BMI160_CMD_ACCEL_PM_SUSPEND 0x10
+#define BMI160_CMD_ACCEL_PM_NORMAL 0x11
+#define BMI160_CMD_ACCEL_PM_LOW_POWER 0x12
+#define BMI160_CMD_GYRO_PM_SUSPEND 0x14
+#define BMI160_CMD_GYRO_PM_NORMAL 0x15
+#define BMI160_CMD_GYRO_PM_FAST_STARTUP 0x17
+#define BMI160_CMD_SOFTRESET 0xB6
+
+#define BMI160_REG_INT_EN 0x51
+#define BMI160_DRDY_INT_EN BIT(4)
+
+#define BMI160_REG_INT_OUT_CTRL 0x53
+#define BMI160_INT_OUT_CTRL_MASK 0x0f
+#define BMI160_INT1_OUT_CTRL_SHIFT 0
+#define BMI160_INT2_OUT_CTRL_SHIFT 4
+#define BMI160_EDGE_TRIGGERED BIT(0)
+#define BMI160_ACTIVE_HIGH BIT(1)
+#define BMI160_OPEN_DRAIN BIT(2)
+#define BMI160_OUTPUT_EN BIT(3)
+
+#define BMI160_REG_INT_LATCH 0x54
+#define BMI160_INT1_LATCH_MASK BIT(4)
+#define BMI160_INT2_LATCH_MASK BIT(5)
+
+/* INT1 and INT2 are in the opposite order as in INT_OUT_CTRL! */
+#define BMI160_REG_INT_MAP 0x56
+#define BMI160_INT1_MAP_DRDY_EN 0x80
+#define BMI160_INT2_MAP_DRDY_EN 0x08
+
+#define BMI160_REG_DUMMY 0x7F
+
+#define BMI160_NORMAL_WRITE_USLEEP 2
+#define BMI160_SUSPENDED_WRITE_USLEEP 450
+
+#define BMI160_ACCEL_PMU_MIN_USLEEP 3800
+#define BMI160_GYRO_PMU_MIN_USLEEP 80000
+#define BMI160_SOFTRESET_USLEEP 1000
+
+#define BMI160_CHANNEL(_type, _axis, _index) { \
+ .type = _type, \
+ .modified = 1, \
+ .channel2 = IIO_MOD_##_axis, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .scan_index = _index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_LE, \
+ }, \
+ .ext_info = bmi160_ext_info, \
+}
+
+/* scan indexes follow DATA register order */
+enum bmi160_scan_axis {
+ BMI160_SCAN_EXT_MAGN_X = 0,
+ BMI160_SCAN_EXT_MAGN_Y,
+ BMI160_SCAN_EXT_MAGN_Z,
+ BMI160_SCAN_RHALL,
+ BMI160_SCAN_GYRO_X,
+ BMI160_SCAN_GYRO_Y,
+ BMI160_SCAN_GYRO_Z,
+ BMI160_SCAN_ACCEL_X,
+ BMI160_SCAN_ACCEL_Y,
+ BMI160_SCAN_ACCEL_Z,
+ BMI160_SCAN_TIMESTAMP,
+};
+
+enum bmi160_sensor_type {
+ BMI160_ACCEL = 0,
+ BMI160_GYRO,
+ BMI160_EXT_MAGN,
+ BMI160_NUM_SENSORS /* must be last */
+};
+
+enum bmi160_int_pin {
+ BMI160_PIN_INT1,
+ BMI160_PIN_INT2
+};
+
+const struct regmap_config bmi160_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+EXPORT_SYMBOL_NS(bmi160_regmap_config, IIO_BMI160);
+
+struct bmi160_regs {
+ u8 data; /* LSB byte register for X-axis */
+ u8 config;
+ u8 config_odr_mask;
+ u8 config_bwp_mask;
+ u8 range;
+ u8 pmu_cmd_normal;
+ u8 pmu_cmd_suspend;
+};
+
+static struct bmi160_regs bmi160_regs[] = {
+ [BMI160_ACCEL] = {
+ .data = BMI160_REG_DATA_ACCEL_XOUT_L,
+ .config = BMI160_REG_ACCEL_CONFIG,
+ .config_odr_mask = BMI160_ACCEL_CONFIG_ODR_MASK,
+ .config_bwp_mask = BMI160_ACCEL_CONFIG_BWP_MASK,
+ .range = BMI160_REG_ACCEL_RANGE,
+ .pmu_cmd_normal = BMI160_CMD_ACCEL_PM_NORMAL,
+ .pmu_cmd_suspend = BMI160_CMD_ACCEL_PM_SUSPEND,
+ },
+ [BMI160_GYRO] = {
+ .data = BMI160_REG_DATA_GYRO_XOUT_L,
+ .config = BMI160_REG_GYRO_CONFIG,
+ .config_odr_mask = BMI160_GYRO_CONFIG_ODR_MASK,
+ .config_bwp_mask = BMI160_GYRO_CONFIG_BWP_MASK,
+ .range = BMI160_REG_GYRO_RANGE,
+ .pmu_cmd_normal = BMI160_CMD_GYRO_PM_NORMAL,
+ .pmu_cmd_suspend = BMI160_CMD_GYRO_PM_SUSPEND,
+ },
+};
+
+static unsigned long bmi160_pmu_time[] = {
+ [BMI160_ACCEL] = BMI160_ACCEL_PMU_MIN_USLEEP,
+ [BMI160_GYRO] = BMI160_GYRO_PMU_MIN_USLEEP,
+};
+
+struct bmi160_scale {
+ u8 bits;
+ int uscale;
+};
+
+struct bmi160_odr {
+ u8 bits;
+ int odr;
+ int uodr;
+};
+
+static const struct bmi160_scale bmi160_accel_scale[] = {
+ { BMI160_ACCEL_RANGE_2G, 598},
+ { BMI160_ACCEL_RANGE_4G, 1197},
+ { BMI160_ACCEL_RANGE_8G, 2394},
+ { BMI160_ACCEL_RANGE_16G, 4788},
+};
+
+static const struct bmi160_scale bmi160_gyro_scale[] = {
+ { BMI160_GYRO_RANGE_2000DPS, 1065},
+ { BMI160_GYRO_RANGE_1000DPS, 532},
+ { BMI160_GYRO_RANGE_500DPS, 266},
+ { BMI160_GYRO_RANGE_250DPS, 133},
+ { BMI160_GYRO_RANGE_125DPS, 66},
+};
+
+struct bmi160_scale_item {
+ const struct bmi160_scale *tbl;
+ int num;
+};
+
+static const struct bmi160_scale_item bmi160_scale_table[] = {
+ [BMI160_ACCEL] = {
+ .tbl = bmi160_accel_scale,
+ .num = ARRAY_SIZE(bmi160_accel_scale),
+ },
+ [BMI160_GYRO] = {
+ .tbl = bmi160_gyro_scale,
+ .num = ARRAY_SIZE(bmi160_gyro_scale),
+ },
+};
+
+static const struct bmi160_odr bmi160_accel_odr[] = {
+ {0x01, 0, 781250},
+ {0x02, 1, 562500},
+ {0x03, 3, 125000},
+ {0x04, 6, 250000},
+ {0x05, 12, 500000},
+ {0x06, 25, 0},
+ {0x07, 50, 0},
+ {0x08, 100, 0},
+ {0x09, 200, 0},
+ {0x0A, 400, 0},
+ {0x0B, 800, 0},
+ {0x0C, 1600, 0},
+};
+
+static const struct bmi160_odr bmi160_gyro_odr[] = {
+ {0x06, 25, 0},
+ {0x07, 50, 0},
+ {0x08, 100, 0},
+ {0x09, 200, 0},
+ {0x0A, 400, 0},
+ {0x0B, 800, 0},
+ {0x0C, 1600, 0},
+ {0x0D, 3200, 0},
+};
+
+struct bmi160_odr_item {
+ const struct bmi160_odr *tbl;
+ int num;
+};
+
+static const struct bmi160_odr_item bmi160_odr_table[] = {
+ [BMI160_ACCEL] = {
+ .tbl = bmi160_accel_odr,
+ .num = ARRAY_SIZE(bmi160_accel_odr),
+ },
+ [BMI160_GYRO] = {
+ .tbl = bmi160_gyro_odr,
+ .num = ARRAY_SIZE(bmi160_gyro_odr),
+ },
+};
+
+static const struct iio_mount_matrix *
+bmi160_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct bmi160_data *data = iio_priv(indio_dev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info bmi160_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, bmi160_get_mount_matrix),
+ { }
+};
+
+static const struct iio_chan_spec bmi160_channels[] = {
+ BMI160_CHANNEL(IIO_ACCEL, X, BMI160_SCAN_ACCEL_X),
+ BMI160_CHANNEL(IIO_ACCEL, Y, BMI160_SCAN_ACCEL_Y),
+ BMI160_CHANNEL(IIO_ACCEL, Z, BMI160_SCAN_ACCEL_Z),
+ BMI160_CHANNEL(IIO_ANGL_VEL, X, BMI160_SCAN_GYRO_X),
+ BMI160_CHANNEL(IIO_ANGL_VEL, Y, BMI160_SCAN_GYRO_Y),
+ BMI160_CHANNEL(IIO_ANGL_VEL, Z, BMI160_SCAN_GYRO_Z),
+ IIO_CHAN_SOFT_TIMESTAMP(BMI160_SCAN_TIMESTAMP),
+};
+
+static enum bmi160_sensor_type bmi160_to_sensor(enum iio_chan_type iio_type)
+{
+ switch (iio_type) {
+ case IIO_ACCEL:
+ return BMI160_ACCEL;
+ case IIO_ANGL_VEL:
+ return BMI160_GYRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static
+int bmi160_set_mode(struct bmi160_data *data, enum bmi160_sensor_type t,
+ bool mode)
+{
+ int ret;
+ u8 cmd;
+
+ if (mode)
+ cmd = bmi160_regs[t].pmu_cmd_normal;
+ else
+ cmd = bmi160_regs[t].pmu_cmd_suspend;
+
+ ret = regmap_write(data->regmap, BMI160_REG_CMD, cmd);
+ if (ret)
+ return ret;
+
+ usleep_range(bmi160_pmu_time[t], bmi160_pmu_time[t] + 1000);
+
+ return 0;
+}
+
+static
+int bmi160_set_scale(struct bmi160_data *data, enum bmi160_sensor_type t,
+ int uscale)
+{
+ int i;
+
+ for (i = 0; i < bmi160_scale_table[t].num; i++)
+ if (bmi160_scale_table[t].tbl[i].uscale == uscale)
+ break;
+
+ if (i == bmi160_scale_table[t].num)
+ return -EINVAL;
+
+ return regmap_write(data->regmap, bmi160_regs[t].range,
+ bmi160_scale_table[t].tbl[i].bits);
+}
+
+static
+int bmi160_get_scale(struct bmi160_data *data, enum bmi160_sensor_type t,
+ int *uscale)
+{
+ int i, ret, val;
+
+ ret = regmap_read(data->regmap, bmi160_regs[t].range, &val);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < bmi160_scale_table[t].num; i++)
+ if (bmi160_scale_table[t].tbl[i].bits == val) {
+ *uscale = bmi160_scale_table[t].tbl[i].uscale;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int bmi160_get_data(struct bmi160_data *data, int chan_type,
+ int axis, int *val)
+{
+ u8 reg;
+ int ret;
+ __le16 sample;
+ enum bmi160_sensor_type t = bmi160_to_sensor(chan_type);
+
+ reg = bmi160_regs[t].data + (axis - IIO_MOD_X) * sizeof(sample);
+
+ ret = regmap_bulk_read(data->regmap, reg, &sample, sizeof(sample));
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(le16_to_cpu(sample), 15);
+
+ return 0;
+}
+
+static
+int bmi160_set_odr(struct bmi160_data *data, enum bmi160_sensor_type t,
+ int odr, int uodr)
+{
+ int i;
+
+ for (i = 0; i < bmi160_odr_table[t].num; i++)
+ if (bmi160_odr_table[t].tbl[i].odr == odr &&
+ bmi160_odr_table[t].tbl[i].uodr == uodr)
+ break;
+
+ if (i >= bmi160_odr_table[t].num)
+ return -EINVAL;
+
+ return regmap_update_bits(data->regmap,
+ bmi160_regs[t].config,
+ bmi160_regs[t].config_odr_mask,
+ bmi160_odr_table[t].tbl[i].bits);
+}
+
+static int bmi160_get_odr(struct bmi160_data *data, enum bmi160_sensor_type t,
+ int *odr, int *uodr)
+{
+ int i, val, ret;
+
+ ret = regmap_read(data->regmap, bmi160_regs[t].config, &val);
+ if (ret)
+ return ret;
+
+ val &= bmi160_regs[t].config_odr_mask;
+
+ for (i = 0; i < bmi160_odr_table[t].num; i++)
+ if (val == bmi160_odr_table[t].tbl[i].bits)
+ break;
+
+ if (i >= bmi160_odr_table[t].num)
+ return -EINVAL;
+
+ *odr = bmi160_odr_table[t].tbl[i].odr;
+ *uodr = bmi160_odr_table[t].tbl[i].uodr;
+
+ return 0;
+}
+
+static irqreturn_t bmi160_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct bmi160_data *data = iio_priv(indio_dev);
+ int i, ret, j = 0, base = BMI160_REG_DATA_MAGN_XOUT_L;
+ __le16 sample;
+
+ for_each_set_bit(i, indio_dev->active_scan_mask,
+ indio_dev->masklength) {
+ ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample),
+ &sample, sizeof(sample));
+ if (ret)
+ goto done;
+ data->buf[j++] = sample;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, data->buf, pf->timestamp);
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
+static int bmi160_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int ret;
+ struct bmi160_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = bmi160_get_data(data, chan->type, chan->channel2, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 0;
+ ret = bmi160_get_scale(data,
+ bmi160_to_sensor(chan->type), val2);
+ return ret ? ret : IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = bmi160_get_odr(data, bmi160_to_sensor(chan->type),
+ val, val2);
+ return ret ? ret : IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bmi160_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct bmi160_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return bmi160_set_scale(data,
+ bmi160_to_sensor(chan->type), val2);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return bmi160_set_odr(data, bmi160_to_sensor(chan->type),
+ val, val2);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static
+IIO_CONST_ATTR(in_accel_sampling_frequency_available,
+ "0.78125 1.5625 3.125 6.25 12.5 25 50 100 200 400 800 1600");
+static
+IIO_CONST_ATTR(in_anglvel_sampling_frequency_available,
+ "25 50 100 200 400 800 1600 3200");
+static
+IIO_CONST_ATTR(in_accel_scale_available,
+ "0.000598 0.001197 0.002394 0.004788");
+static
+IIO_CONST_ATTR(in_anglvel_scale_available,
+ "0.001065 0.000532 0.000266 0.000133 0.000066");
+
+static struct attribute *bmi160_attrs[] = {
+ &iio_const_attr_in_accel_sampling_frequency_available.dev_attr.attr,
+ &iio_const_attr_in_anglvel_sampling_frequency_available.dev_attr.attr,
+ &iio_const_attr_in_accel_scale_available.dev_attr.attr,
+ &iio_const_attr_in_anglvel_scale_available.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group bmi160_attrs_group = {
+ .attrs = bmi160_attrs,
+};
+
+static const struct iio_info bmi160_info = {
+ .read_raw = bmi160_read_raw,
+ .write_raw = bmi160_write_raw,
+ .attrs = &bmi160_attrs_group,
+};
+
+static int bmi160_write_conf_reg(struct regmap *regmap, unsigned int reg,
+ unsigned int mask, unsigned int bits,
+ unsigned int write_usleep)
+{
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(regmap, reg, &val);
+ if (ret)
+ return ret;
+
+ val = (val & ~mask) | bits;
+
+ ret = regmap_write(regmap, reg, val);
+ if (ret)
+ return ret;
+
+ /*
+ * We need to wait after writing before we can write again. See the
+ * datasheet, page 93.
+ */
+ usleep_range(write_usleep, write_usleep + 1000);
+
+ return 0;
+}
+
+static int bmi160_config_pin(struct regmap *regmap, enum bmi160_int_pin pin,
+ bool open_drain, u8 irq_mask,
+ unsigned long write_usleep)
+{
+ int ret;
+ struct device *dev = regmap_get_device(regmap);
+ u8 int_out_ctrl_shift;
+ u8 int_latch_mask;
+ u8 int_map_mask;
+ u8 int_out_ctrl_mask;
+ u8 int_out_ctrl_bits;
+ const char *pin_name;
+
+ switch (pin) {
+ case BMI160_PIN_INT1:
+ int_out_ctrl_shift = BMI160_INT1_OUT_CTRL_SHIFT;
+ int_latch_mask = BMI160_INT1_LATCH_MASK;
+ int_map_mask = BMI160_INT1_MAP_DRDY_EN;
+ break;
+ case BMI160_PIN_INT2:
+ int_out_ctrl_shift = BMI160_INT2_OUT_CTRL_SHIFT;
+ int_latch_mask = BMI160_INT2_LATCH_MASK;
+ int_map_mask = BMI160_INT2_MAP_DRDY_EN;
+ break;
+ }
+ int_out_ctrl_mask = BMI160_INT_OUT_CTRL_MASK << int_out_ctrl_shift;
+
+ /*
+ * Enable the requested pin with the right settings:
+ * - Push-pull/open-drain
+ * - Active low/high
+ * - Edge/level triggered
+ */
+ int_out_ctrl_bits = BMI160_OUTPUT_EN;
+ if (open_drain)
+ /* Default is push-pull. */
+ int_out_ctrl_bits |= BMI160_OPEN_DRAIN;
+ int_out_ctrl_bits |= irq_mask;
+ int_out_ctrl_bits <<= int_out_ctrl_shift;
+
+ ret = bmi160_write_conf_reg(regmap, BMI160_REG_INT_OUT_CTRL,
+ int_out_ctrl_mask, int_out_ctrl_bits,
+ write_usleep);
+ if (ret)
+ return ret;
+
+ /* Set the pin to input mode with no latching. */
+ ret = bmi160_write_conf_reg(regmap, BMI160_REG_INT_LATCH,
+ int_latch_mask, int_latch_mask,
+ write_usleep);
+ if (ret)
+ return ret;
+
+ /* Map interrupts to the requested pin. */
+ ret = bmi160_write_conf_reg(regmap, BMI160_REG_INT_MAP,
+ int_map_mask, int_map_mask,
+ write_usleep);
+ if (ret) {
+ switch (pin) {
+ case BMI160_PIN_INT1:
+ pin_name = "INT1";
+ break;
+ case BMI160_PIN_INT2:
+ pin_name = "INT2";
+ break;
+ }
+ dev_err(dev, "Failed to configure %s IRQ pin", pin_name);
+ }
+
+ return ret;
+}
+
+int bmi160_enable_irq(struct regmap *regmap, bool enable)
+{
+ unsigned int enable_bit = 0;
+
+ if (enable)
+ enable_bit = BMI160_DRDY_INT_EN;
+
+ return bmi160_write_conf_reg(regmap, BMI160_REG_INT_EN,
+ BMI160_DRDY_INT_EN, enable_bit,
+ BMI160_NORMAL_WRITE_USLEEP);
+}
+EXPORT_SYMBOL_NS(bmi160_enable_irq, IIO_BMI160);
+
+static int bmi160_get_irq(struct fwnode_handle *fwnode, enum bmi160_int_pin *pin)
+{
+ int irq;
+
+ /* Use INT1 if possible, otherwise fall back to INT2. */
+ irq = fwnode_irq_get_byname(fwnode, "INT1");
+ if (irq > 0) {
+ *pin = BMI160_PIN_INT1;
+ return irq;
+ }
+
+ irq = fwnode_irq_get_byname(fwnode, "INT2");
+ if (irq > 0)
+ *pin = BMI160_PIN_INT2;
+
+ return irq;
+}
+
+static int bmi160_config_device_irq(struct iio_dev *indio_dev, int irq_type,
+ enum bmi160_int_pin pin)
+{
+ bool open_drain;
+ u8 irq_mask;
+ struct bmi160_data *data = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(data->regmap);
+
+ /* Level-triggered, active-low is the default if we set all zeroes. */
+ if (irq_type == IRQF_TRIGGER_RISING)
+ irq_mask = BMI160_ACTIVE_HIGH | BMI160_EDGE_TRIGGERED;
+ else if (irq_type == IRQF_TRIGGER_FALLING)
+ irq_mask = BMI160_EDGE_TRIGGERED;
+ else if (irq_type == IRQF_TRIGGER_HIGH)
+ irq_mask = BMI160_ACTIVE_HIGH;
+ else if (irq_type == IRQF_TRIGGER_LOW)
+ irq_mask = 0;
+ else {
+ dev_err(&indio_dev->dev,
+ "Invalid interrupt type 0x%x specified\n", irq_type);
+ return -EINVAL;
+ }
+
+ open_drain = device_property_read_bool(dev, "drive-open-drain");
+
+ return bmi160_config_pin(data->regmap, pin, open_drain, irq_mask,
+ BMI160_NORMAL_WRITE_USLEEP);
+}
+
+static int bmi160_setup_irq(struct iio_dev *indio_dev, int irq,
+ enum bmi160_int_pin pin)
+{
+ struct irq_data *desc;
+ u32 irq_type;
+ int ret;
+
+ desc = irq_get_irq_data(irq);
+ if (!desc) {
+ dev_err(&indio_dev->dev, "Could not find IRQ %d\n", irq);
+ return -EINVAL;
+ }
+
+ irq_type = irqd_get_trigger_type(desc);
+
+ ret = bmi160_config_device_irq(indio_dev, irq_type, pin);
+ if (ret)
+ return ret;
+
+ return bmi160_probe_trigger(indio_dev, irq, irq_type);
+}
+
+static int bmi160_chip_init(struct bmi160_data *data, bool use_spi)
+{
+ int ret;
+ unsigned int val;
+ struct device *dev = regmap_get_device(data->regmap);
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), data->supplies);
+ if (ret) {
+ dev_err(dev, "Failed to enable regulators: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_write(data->regmap, BMI160_REG_CMD, BMI160_CMD_SOFTRESET);
+ if (ret)
+ goto disable_regulator;
+
+ usleep_range(BMI160_SOFTRESET_USLEEP, BMI160_SOFTRESET_USLEEP + 1);
+
+ /*
+ * CS rising edge is needed before starting SPI, so do a dummy read
+ * See Section 3.2.1, page 86 of the datasheet
+ */
+ if (use_spi) {
+ ret = regmap_read(data->regmap, BMI160_REG_DUMMY, &val);
+ if (ret)
+ goto disable_regulator;
+ }
+
+ ret = regmap_read(data->regmap, BMI160_REG_CHIP_ID, &val);
+ if (ret) {
+ dev_err(dev, "Error reading chip id\n");
+ goto disable_regulator;
+ }
+ if (val != BMI160_CHIP_ID_VAL) {
+ dev_err(dev, "Wrong chip id, got %x expected %x\n",
+ val, BMI160_CHIP_ID_VAL);
+ ret = -ENODEV;
+ goto disable_regulator;
+ }
+
+ ret = bmi160_set_mode(data, BMI160_ACCEL, true);
+ if (ret)
+ goto disable_regulator;
+
+ ret = bmi160_set_mode(data, BMI160_GYRO, true);
+ if (ret)
+ goto disable_accel;
+
+ return 0;
+
+disable_accel:
+ bmi160_set_mode(data, BMI160_ACCEL, false);
+
+disable_regulator:
+ regulator_bulk_disable(ARRAY_SIZE(data->supplies), data->supplies);
+ return ret;
+}
+
+static int bmi160_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool enable)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct bmi160_data *data = iio_priv(indio_dev);
+
+ return bmi160_enable_irq(data->regmap, enable);
+}
+
+static const struct iio_trigger_ops bmi160_trigger_ops = {
+ .set_trigger_state = &bmi160_data_rdy_trigger_set_state,
+};
+
+int bmi160_probe_trigger(struct iio_dev *indio_dev, int irq, u32 irq_type)
+{
+ struct bmi160_data *data = iio_priv(indio_dev);
+ int ret;
+
+ data->trig = devm_iio_trigger_alloc(&indio_dev->dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+
+ if (data->trig == NULL)
+ return -ENOMEM;
+
+ ret = devm_request_irq(&indio_dev->dev, irq,
+ &iio_trigger_generic_data_rdy_poll,
+ irq_type, "bmi160", data->trig);
+ if (ret)
+ return ret;
+
+ data->trig->dev.parent = regmap_get_device(data->regmap);
+ data->trig->ops = &bmi160_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, indio_dev);
+
+ ret = devm_iio_trigger_register(&indio_dev->dev, data->trig);
+ if (ret)
+ return ret;
+
+ indio_dev->trig = iio_trigger_get(data->trig);
+
+ return 0;
+}
+
+static void bmi160_chip_uninit(void *data)
+{
+ struct bmi160_data *bmi_data = data;
+ struct device *dev = regmap_get_device(bmi_data->regmap);
+ int ret;
+
+ bmi160_set_mode(bmi_data, BMI160_GYRO, false);
+ bmi160_set_mode(bmi_data, BMI160_ACCEL, false);
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(bmi_data->supplies),
+ bmi_data->supplies);
+ if (ret)
+ dev_err(dev, "Failed to disable regulators: %d\n", ret);
+}
+
+int bmi160_core_probe(struct device *dev, struct regmap *regmap,
+ const char *name, bool use_spi)
+{
+ struct iio_dev *indio_dev;
+ struct bmi160_data *data;
+ int irq;
+ enum bmi160_int_pin int_pin;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ dev_set_drvdata(dev, indio_dev);
+ data->regmap = regmap;
+
+ data->supplies[0].supply = "vdd";
+ data->supplies[1].supply = "vddio";
+ ret = devm_regulator_bulk_get(dev,
+ ARRAY_SIZE(data->supplies),
+ data->supplies);
+ if (ret) {
+ dev_err(dev, "Failed to get regulators: %d\n", ret);
+ return ret;
+ }
+
+ ret = iio_read_mount_matrix(dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ ret = bmi160_chip_init(data, use_spi);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, bmi160_chip_uninit, data);
+ if (ret)
+ return ret;
+
+ indio_dev->channels = bmi160_channels;
+ indio_dev->num_channels = ARRAY_SIZE(bmi160_channels);
+ indio_dev->name = name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &bmi160_info;
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ bmi160_trigger_handler, NULL);
+ if (ret)
+ return ret;
+
+ irq = bmi160_get_irq(dev_fwnode(dev), &int_pin);
+ if (irq > 0) {
+ ret = bmi160_setup_irq(indio_dev, irq, int_pin);
+ if (ret)
+ dev_err(&indio_dev->dev, "Failed to setup IRQ %d\n",
+ irq);
+ } else {
+ dev_info(&indio_dev->dev, "Not setting up IRQ trigger\n");
+ }
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(bmi160_core_probe, IIO_BMI160);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
+MODULE_DESCRIPTION("Bosch BMI160 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/imu/bmi160/bmi160_i2c.c b/drivers/iio/imu/bmi160/bmi160_i2c.c
new file mode 100644
index 000000000..d93f4fa2a
--- /dev/null
+++ b/drivers/iio/imu/bmi160/bmi160_i2c.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BMI160 - Bosch IMU, I2C bits
+ *
+ * Copyright (c) 2016, Intel Corporation.
+ *
+ * 7-bit I2C slave address is:
+ * - 0x68 if SDO is pulled to GND
+ * - 0x69 if SDO is pulled to VDDIO
+ */
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "bmi160.h"
+
+static int bmi160_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct regmap *regmap;
+ const char *name;
+
+ regmap = devm_regmap_init_i2c(client, &bmi160_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&client->dev, "Failed to register i2c regmap: %pe\n",
+ regmap);
+ return PTR_ERR(regmap);
+ }
+
+ if (id)
+ name = id->name;
+ else
+ name = dev_name(&client->dev);
+
+ return bmi160_core_probe(&client->dev, regmap, name, false);
+}
+
+static const struct i2c_device_id bmi160_i2c_id[] = {
+ {"bmi160", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, bmi160_i2c_id);
+
+static const struct acpi_device_id bmi160_acpi_match[] = {
+ {"BMI0160", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, bmi160_acpi_match);
+
+static const struct of_device_id bmi160_of_match[] = {
+ { .compatible = "bosch,bmi160" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bmi160_of_match);
+
+static struct i2c_driver bmi160_i2c_driver = {
+ .driver = {
+ .name = "bmi160_i2c",
+ .acpi_match_table = bmi160_acpi_match,
+ .of_match_table = bmi160_of_match,
+ },
+ .probe = bmi160_i2c_probe,
+ .id_table = bmi160_i2c_id,
+};
+module_i2c_driver(bmi160_i2c_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
+MODULE_DESCRIPTION("BMI160 I2C driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_BMI160);
diff --git a/drivers/iio/imu/bmi160/bmi160_spi.c b/drivers/iio/imu/bmi160/bmi160_spi.c
new file mode 100644
index 000000000..8b573ea99
--- /dev/null
+++ b/drivers/iio/imu/bmi160/bmi160_spi.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BMI160 - Bosch IMU, SPI bits
+ *
+ * Copyright (c) 2016, Intel Corporation.
+ *
+ */
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "bmi160.h"
+
+static int bmi160_spi_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+ const struct spi_device_id *id = spi_get_device_id(spi);
+ const char *name;
+
+ regmap = devm_regmap_init_spi(spi, &bmi160_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&spi->dev, "Failed to register spi regmap: %pe\n",
+ regmap);
+ return PTR_ERR(regmap);
+ }
+
+ if (id)
+ name = id->name;
+ else
+ name = dev_name(&spi->dev);
+
+ return bmi160_core_probe(&spi->dev, regmap, name, true);
+}
+
+static const struct spi_device_id bmi160_spi_id[] = {
+ {"bmi160", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, bmi160_spi_id);
+
+static const struct acpi_device_id bmi160_acpi_match[] = {
+ {"BMI0160", 0},
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, bmi160_acpi_match);
+
+static const struct of_device_id bmi160_of_match[] = {
+ { .compatible = "bosch,bmi160" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bmi160_of_match);
+
+static struct spi_driver bmi160_spi_driver = {
+ .probe = bmi160_spi_probe,
+ .id_table = bmi160_spi_id,
+ .driver = {
+ .acpi_match_table = bmi160_acpi_match,
+ .of_match_table = bmi160_of_match,
+ .name = "bmi160_spi",
+ },
+};
+module_spi_driver(bmi160_spi_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com");
+MODULE_DESCRIPTION("Bosch BMI160 SPI driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_BMI160);