diff options
Diffstat (limited to 'drivers/iio/imu/bmi270/bmi270_core.c')
| -rw-r--r-- | drivers/iio/imu/bmi270/bmi270_core.c | 1664 |
1 files changed, 1664 insertions, 0 deletions
diff --git a/drivers/iio/imu/bmi270/bmi270_core.c b/drivers/iio/imu/bmi270/bmi270_core.c new file mode 100644 index 000000000000..2ad230788532 --- /dev/null +++ b/drivers/iio/imu/bmi270/bmi270_core.c @@ -0,0 +1,1664 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) + +#include <linux/bitfield.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/units.h> + +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +#include "bmi270.h" + +#define BMI270_CHIP_ID_REG 0x00 + +/* Checked to prevent sending incompatible firmware to BMI160 devices */ +#define BMI160_CHIP_ID_VAL 0xD1 + +#define BMI260_CHIP_ID_VAL 0x27 +#define BMI270_CHIP_ID_VAL 0x24 +#define BMI270_CHIP_ID_MSK GENMASK(7, 0) + +#define BMI270_ACCEL_X_REG 0x0c +#define BMI270_ANG_VEL_X_REG 0x12 + +#define BMI270_INT_STATUS_0_REG 0x1c +#define BMI270_INT_STATUS_0_STEP_CNT_MSK BIT(1) +#define BMI270_INT_STATUS_0_NOMOTION_MSK BIT(5) +#define BMI270_INT_STATUS_0_MOTION_MSK BIT(6) + +#define BMI270_INT_STATUS_1_REG 0x1d +#define BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK GENMASK(7, 6) + +#define BMI270_SC_OUT_0_REG 0x1e + +#define BMI270_INTERNAL_STATUS_REG 0x21 +#define BMI270_INTERNAL_STATUS_MSG_MSK GENMASK(3, 0) +#define BMI270_INTERNAL_STATUS_MSG_INIT_OK 0x01 +#define BMI270_INTERNAL_STATUS_AXES_REMAP_ERR_MSK BIT(5) +#define BMI270_INTERNAL_STATUS_ODR_50HZ_ERR_MSK BIT(6) + +#define BMI270_TEMPERATURE_0_REG 0x22 + +#define BMI270_FEAT_PAGE_REG 0x2f + +#define BMI270_ACC_CONF_REG 0x40 +#define BMI270_ACC_CONF_ODR_MSK GENMASK(3, 0) +#define BMI270_ACC_CONF_ODR_100HZ 0x08 +#define BMI270_ACC_CONF_BWP_MSK GENMASK(6, 4) +#define BMI270_ACC_CONF_BWP_NORMAL_MODE 0x02 +#define BMI270_ACC_CONF_FILTER_PERF_MSK BIT(7) + +#define BMI270_ACC_CONF_RANGE_REG 0x41 +#define BMI270_ACC_CONF_RANGE_MSK GENMASK(1, 0) + +#define BMI270_GYR_CONF_REG 0x42 +#define BMI270_GYR_CONF_ODR_MSK GENMASK(3, 0) +#define BMI270_GYR_CONF_ODR_200HZ 0x09 +#define BMI270_GYR_CONF_BWP_MSK GENMASK(5, 4) +#define BMI270_GYR_CONF_BWP_NORMAL_MODE 0x02 +#define BMI270_GYR_CONF_NOISE_PERF_MSK BIT(6) +#define BMI270_GYR_CONF_FILTER_PERF_MSK BIT(7) + +#define BMI270_GYR_CONF_RANGE_REG 0x43 +#define BMI270_GYR_CONF_RANGE_MSK GENMASK(2, 0) + +#define BMI270_INT1_IO_CTRL_REG 0x53 +#define BMI270_INT2_IO_CTRL_REG 0x54 +#define BMI270_INT_IO_CTRL_LVL_MSK BIT(1) +#define BMI270_INT_IO_CTRL_OD_MSK BIT(2) +#define BMI270_INT_IO_CTRL_OP_MSK BIT(3) +#define BMI270_INT_IO_LVL_OD_OP_MSK GENMASK(3, 1) + +#define BMI270_INT_LATCH_REG 0x55 +#define BMI270_INT_LATCH_REG_MSK BIT(0) + +#define BMI270_INT1_MAP_FEAT_REG 0x56 +#define BMI270_INT2_MAP_FEAT_REG 0x57 +#define BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK BIT(1) +#define BMI270_INT_MAP_FEAT_NOMOTION_MSK BIT(5) +#define BMI270_INT_MAP_FEAT_ANYMOTION_MSK BIT(6) + +#define BMI270_INT_MAP_DATA_REG 0x58 +#define BMI270_INT_MAP_DATA_DRDY_INT1_MSK BIT(2) +#define BMI270_INT_MAP_DATA_DRDY_INT2_MSK BIT(6) + +#define BMI270_INIT_CTRL_REG 0x59 +#define BMI270_INIT_CTRL_LOAD_DONE_MSK BIT(0) + +#define BMI270_INIT_DATA_REG 0x5e + +#define BMI270_PWR_CONF_REG 0x7c +#define BMI270_PWR_CONF_ADV_PWR_SAVE_MSK BIT(0) +#define BMI270_PWR_CONF_FIFO_WKUP_MSK BIT(1) +#define BMI270_PWR_CONF_FUP_EN_MSK BIT(2) + +#define BMI270_PWR_CTRL_REG 0x7d +#define BMI270_PWR_CTRL_AUX_EN_MSK BIT(0) +#define BMI270_PWR_CTRL_GYR_EN_MSK BIT(1) +#define BMI270_PWR_CTRL_ACCEL_EN_MSK BIT(2) +#define BMI270_PWR_CTRL_TEMP_EN_MSK BIT(3) + +#define BMI270_STEP_SC26_WTRMRK_MSK GENMASK(9, 0) +#define BMI270_STEP_SC26_RST_CNT_MSK BIT(10) +#define BMI270_STEP_SC26_EN_CNT_MSK BIT(12) + +#define BMI270_FEAT_MOTION_DURATION_MSK GENMASK(12, 0) +#define BMI270_FEAT_MOTION_X_EN_MSK BIT(13) +#define BMI270_FEAT_MOTION_Y_EN_MSK BIT(14) +#define BMI270_FEAT_MOTION_Z_EN_MSK BIT(15) +#define BMI270_FEAT_MOTION_XYZ_EN_MSK GENMASK(15, 13) +#define BMI270_FEAT_MOTION_THRESHOLD_MSK GENMASK(10, 0) +#define BMI270_FEAT_MOTION_OUT_CONF_MSK GENMASK(14, 11) +#define BMI270_FEAT_MOTION_ENABLE_MSK BIT(15) + +#define BMI270_MOTION_XYZ_MSK GENMASK(2, 0) + +/* See pages 92 and 93 of the datasheet */ +#define BMI270_MOTION_THRES_FULL_SCALE GENMASK(10, 0) +#define BMI270_MOTION_DURAT_SCALE 50 +#define BMI270_MOTION_DURAT_MAX 162 + +/* 9.81 * 1000000 m/s^2 */ +#define BMI270_G_MICRO_M_S_2 9810000 + +/* See datasheet section 4.6.14, Temperature Sensor */ +#define BMI270_TEMP_OFFSET 11776 +#define BMI270_TEMP_SCALE 1953125 + +/* See page 90 of datasheet. The step counter "holds implicitly a 20x factor" */ +#define BMI270_STEP_COUNTER_FACTOR 20 +#define BMI270_STEP_COUNTER_MAX 20460 + +#define BMI270_INT_MICRO_TO_RAW(val, val2, scale) \ + ((val) * (scale) + ((val2) * (scale)) / MEGA) +#define BMI270_RAW_TO_MICRO(raw, scale) \ + ((((raw) % (scale)) * MEGA) / scale) + +#define BMI260_INIT_DATA_FILE "bmi260-init-data.fw" +#define BMI270_INIT_DATA_FILE "bmi270-init-data.fw" + +enum bmi270_irq_pin { + BMI270_IRQ_DISABLED, + BMI270_IRQ_INT1, + BMI270_IRQ_INT2, +}; + +struct bmi270_data { + struct device *dev; + struct regmap *regmap; + const struct bmi270_chip_info *chip_info; + enum bmi270_irq_pin irq_pin; + struct iio_trigger *trig; + /* Protect device's private data from concurrent access */ + struct mutex mutex; + bool steps_enabled; + + /* + * Where IIO_DMA_MINALIGN may be larger than 8 bytes, align to + * that to ensure a DMA safe buffer. + */ + struct { + __le16 channels[6]; + aligned_s64 timestamp; + } buffer __aligned(IIO_DMA_MINALIGN); + /* + * Variable to access feature registers. It can be accessed concurrently + * with the 'buffer' variable + */ + __le16 regval __aligned(IIO_DMA_MINALIGN); +}; + +enum bmi270_scan { + BMI270_SCAN_ACCEL_X, + BMI270_SCAN_ACCEL_Y, + BMI270_SCAN_ACCEL_Z, + BMI270_SCAN_GYRO_X, + BMI270_SCAN_GYRO_Y, + BMI270_SCAN_GYRO_Z, + BMI270_SCAN_TIMESTAMP, +}; + +static const unsigned long bmi270_avail_scan_masks[] = { + (BIT(BMI270_SCAN_ACCEL_X) | + BIT(BMI270_SCAN_ACCEL_Y) | + BIT(BMI270_SCAN_ACCEL_Z) | + BIT(BMI270_SCAN_GYRO_X) | + BIT(BMI270_SCAN_GYRO_Y) | + BIT(BMI270_SCAN_GYRO_Z)), + 0 +}; + +const struct bmi270_chip_info bmi260_chip_info = { + .name = "bmi260", + .chip_id = BMI260_CHIP_ID_VAL, + .fw_name = BMI260_INIT_DATA_FILE, +}; +EXPORT_SYMBOL_NS_GPL(bmi260_chip_info, "IIO_BMI270"); + +const struct bmi270_chip_info bmi270_chip_info = { + .name = "bmi270", + .chip_id = BMI270_CHIP_ID_VAL, + .fw_name = BMI270_INIT_DATA_FILE, +}; +EXPORT_SYMBOL_NS_GPL(bmi270_chip_info, "IIO_BMI270"); + +enum bmi270_sensor_type { + BMI270_ACCEL = 0, + BMI270_GYRO, + BMI270_TEMP, +}; + +struct bmi270_scale { + int scale; + int uscale; +}; + +struct bmi270_odr { + int odr; + int uodr; +}; + +static const struct bmi270_scale bmi270_accel_scale[] = { + { 0, 598 }, + { 0, 1197 }, + { 0, 2394 }, + { 0, 4788 }, +}; + +static const struct bmi270_scale bmi270_gyro_scale[] = { + { 0, 1065 }, + { 0, 532 }, + { 0, 266 }, + { 0, 133 }, + { 0, 66 }, +}; + +static const struct bmi270_scale bmi270_temp_scale[] = { + { BMI270_TEMP_SCALE / MICRO, BMI270_TEMP_SCALE % MICRO }, +}; + +struct bmi270_scale_item { + const struct bmi270_scale *tbl; + int num; +}; + +static const struct bmi270_scale_item bmi270_scale_table[] = { + [BMI270_ACCEL] = { + .tbl = bmi270_accel_scale, + .num = ARRAY_SIZE(bmi270_accel_scale), + }, + [BMI270_GYRO] = { + .tbl = bmi270_gyro_scale, + .num = ARRAY_SIZE(bmi270_gyro_scale), + }, + [BMI270_TEMP] = { + .tbl = bmi270_temp_scale, + .num = ARRAY_SIZE(bmi270_temp_scale), + }, +}; + +static const struct bmi270_odr bmi270_accel_odr[] = { + { 0, 781250 }, + { 1, 562500 }, + { 3, 125000 }, + { 6, 250000 }, + { 12, 500000 }, + { 25, 0 }, + { 50, 0 }, + { 100, 0 }, + { 200, 0 }, + { 400, 0 }, + { 800, 0 }, + { 1600, 0 }, +}; + +static const u8 bmi270_accel_odr_vals[] = { + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, +}; + +static const struct bmi270_odr bmi270_gyro_odr[] = { + { 25, 0 }, + { 50, 0 }, + { 100, 0 }, + { 200, 0 }, + { 400, 0 }, + { 800, 0 }, + { 1600, 0 }, + { 3200, 0 }, +}; + +static const u8 bmi270_gyro_odr_vals[] = { + 0x06, + 0x07, + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, +}; + +struct bmi270_odr_item { + const struct bmi270_odr *tbl; + const u8 *vals; + int num; +}; + +static const struct bmi270_odr_item bmi270_odr_table[] = { + [BMI270_ACCEL] = { + .tbl = bmi270_accel_odr, + .vals = bmi270_accel_odr_vals, + .num = ARRAY_SIZE(bmi270_accel_odr), + }, + [BMI270_GYRO] = { + .tbl = bmi270_gyro_odr, + .vals = bmi270_gyro_odr_vals, + .num = ARRAY_SIZE(bmi270_gyro_odr), + }, +}; + +enum bmi270_feature_reg_id { + /* Page 1 registers */ + BMI270_ANYMO1_REG, + BMI270_ANYMO2_REG, + /* Page 2 registers */ + BMI270_NOMO1_REG, + BMI270_NOMO2_REG, + /* Page 6 registers */ + BMI270_SC_26_REG, +}; + +struct bmi270_feature_reg { + u8 page; + u8 addr; +}; + +static const struct bmi270_feature_reg bmi270_feature_regs[] = { + [BMI270_ANYMO1_REG] = { + .page = 1, + .addr = 0x3c, + }, + [BMI270_ANYMO2_REG] = { + .page = 1, + .addr = 0x3e, + }, + [BMI270_NOMO1_REG] = { + .page = 2, + .addr = 0x30, + }, + [BMI270_NOMO2_REG] = { + .page = 2, + .addr = 0x32, + }, + [BMI270_SC_26_REG] = { + .page = 6, + .addr = 0x32, + }, +}; + +static int bmi270_write_feature_reg(struct bmi270_data *data, + enum bmi270_feature_reg_id id, + u16 val) +{ + const struct bmi270_feature_reg *reg = &bmi270_feature_regs[id]; + int ret; + + ret = regmap_write(data->regmap, BMI270_FEAT_PAGE_REG, reg->page); + if (ret) + return ret; + + data->regval = cpu_to_le16(val); + return regmap_bulk_write(data->regmap, reg->addr, &data->regval, + sizeof(data->regval)); +} + +static int bmi270_read_feature_reg(struct bmi270_data *data, + enum bmi270_feature_reg_id id, + u16 *val) +{ + const struct bmi270_feature_reg *reg = &bmi270_feature_regs[id]; + int ret; + + ret = regmap_write(data->regmap, BMI270_FEAT_PAGE_REG, reg->page); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, reg->addr, &data->regval, + sizeof(data->regval)); + if (ret) + return ret; + + *val = le16_to_cpu(data->regval); + return 0; +} + +static int bmi270_update_feature_reg(struct bmi270_data *data, + enum bmi270_feature_reg_id id, + u16 mask, u16 val) +{ + u16 regval; + int ret; + + ret = bmi270_read_feature_reg(data, id, ®val); + if (ret) + return ret; + + regval = (regval & ~mask) | (val & mask); + + return bmi270_write_feature_reg(data, id, regval); +} + +static int bmi270_enable_steps(struct bmi270_data *data, int val) +{ + int ret; + + guard(mutex)(&data->mutex); + if (data->steps_enabled) + return 0; + + ret = bmi270_update_feature_reg(data, BMI270_SC_26_REG, + BMI270_STEP_SC26_EN_CNT_MSK, + FIELD_PREP(BMI270_STEP_SC26_EN_CNT_MSK, + val ? 1 : 0)); + if (ret) + return ret; + + data->steps_enabled = true; + return 0; +} + +static int bmi270_read_steps(struct bmi270_data *data, int *val) +{ + __le16 steps_count; + int ret; + + ret = regmap_bulk_read(data->regmap, BMI270_SC_OUT_0_REG, &steps_count, + sizeof(steps_count)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(steps_count), 15); + return IIO_VAL_INT; +} + +static int bmi270_int_map_reg(enum bmi270_irq_pin pin) +{ + switch (pin) { + case BMI270_IRQ_INT1: + return BMI270_INT1_MAP_FEAT_REG; + case BMI270_IRQ_INT2: + return BMI270_INT2_MAP_FEAT_REG; + default: + return -EINVAL; + } +} + +static int bmi270_step_wtrmrk_en(struct bmi270_data *data, bool state) +{ + int reg; + + guard(mutex)(&data->mutex); + if (!data->steps_enabled) + return -EINVAL; + + reg = bmi270_int_map_reg(data->irq_pin); + if (reg < 0) + return reg; + + return regmap_update_bits(data->regmap, reg, + BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK, + FIELD_PREP(BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK, + state)); +} + +static int bmi270_motion_reg(enum iio_event_type type, enum iio_event_info info) +{ + switch (info) { + case IIO_EV_INFO_PERIOD: + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + return BMI270_ANYMO1_REG; + case IIO_EV_TYPE_ROC: + return BMI270_NOMO1_REG; + default: + return -EINVAL; + } + case IIO_EV_INFO_VALUE: + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + return BMI270_ANYMO2_REG; + case IIO_EV_TYPE_ROC: + return BMI270_NOMO2_REG; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bmi270_anymotion_event_en(struct bmi270_data *data, + struct iio_chan_spec const *chan, + bool state) +{ + u16 axis_msk, axis_field_val, regval; + int ret, irq_reg; + bool axis_en; + + irq_reg = bmi270_int_map_reg(data->irq_pin); + if (irq_reg < 0) + return irq_reg; + + guard(mutex)(&data->mutex); + + ret = bmi270_read_feature_reg(data, BMI270_ANYMO1_REG, ®val); + if (ret) + return ret; + + switch (chan->channel2) { + case IIO_MOD_X: + axis_msk = BMI270_FEAT_MOTION_X_EN_MSK; + axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_X_EN_MSK, state); + axis_en = FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, regval) | + FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, regval); + break; + case IIO_MOD_Y: + axis_msk = BMI270_FEAT_MOTION_Y_EN_MSK; + axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_Y_EN_MSK, state); + axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, regval) | + FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, regval); + break; + case IIO_MOD_Z: + axis_msk = BMI270_FEAT_MOTION_Z_EN_MSK; + axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_Z_EN_MSK, state); + axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, regval) | + FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, regval); + break; + default: + return -EINVAL; + } + + ret = bmi270_update_feature_reg(data, BMI270_ANYMO1_REG, axis_msk, + axis_field_val); + if (ret) + return ret; + + ret = bmi270_update_feature_reg(data, BMI270_ANYMO2_REG, + BMI270_FEAT_MOTION_ENABLE_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_ENABLE_MSK, + state || axis_en)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, irq_reg, + BMI270_INT_MAP_FEAT_ANYMOTION_MSK, + FIELD_PREP(BMI270_INT_MAP_FEAT_ANYMOTION_MSK, + state || axis_en)); +} + +static int bmi270_nomotion_event_en(struct bmi270_data *data, bool state) +{ + int ret, irq_reg; + + irq_reg = bmi270_int_map_reg(data->irq_pin); + if (irq_reg < 0) + return irq_reg; + + guard(mutex)(&data->mutex); + + ret = bmi270_update_feature_reg(data, BMI270_NOMO1_REG, + BMI270_FEAT_MOTION_XYZ_EN_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_XYZ_EN_MSK, + state ? BMI270_MOTION_XYZ_MSK : 0)); + if (ret) + return ret; + + ret = bmi270_update_feature_reg(data, BMI270_NOMO2_REG, + BMI270_FEAT_MOTION_ENABLE_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_ENABLE_MSK, + state)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, irq_reg, + BMI270_INT_MAP_FEAT_NOMOTION_MSK, + FIELD_PREP(BMI270_INT_MAP_FEAT_NOMOTION_MSK, + state)); +} + +static int bmi270_set_scale(struct bmi270_data *data, int chan_type, int uscale) +{ + int i; + int reg, mask; + struct bmi270_scale_item bmi270_scale_item; + + switch (chan_type) { + case IIO_ACCEL: + reg = BMI270_ACC_CONF_RANGE_REG; + mask = BMI270_ACC_CONF_RANGE_MSK; + bmi270_scale_item = bmi270_scale_table[BMI270_ACCEL]; + break; + case IIO_ANGL_VEL: + reg = BMI270_GYR_CONF_RANGE_REG; + mask = BMI270_GYR_CONF_RANGE_MSK; + bmi270_scale_item = bmi270_scale_table[BMI270_GYRO]; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + for (i = 0; i < bmi270_scale_item.num; i++) { + if (bmi270_scale_item.tbl[i].uscale != uscale) + continue; + + return regmap_update_bits(data->regmap, reg, mask, i); + } + + return -EINVAL; +} + +static int bmi270_get_scale(struct bmi270_data *data, int chan_type, int *scale, + int *uscale) +{ + int ret; + unsigned int val; + struct bmi270_scale_item bmi270_scale_item; + + switch (chan_type) { + case IIO_ACCEL: + ret = regmap_read(data->regmap, BMI270_ACC_CONF_RANGE_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(BMI270_ACC_CONF_RANGE_MSK, val); + bmi270_scale_item = bmi270_scale_table[BMI270_ACCEL]; + break; + case IIO_ANGL_VEL: + ret = regmap_read(data->regmap, BMI270_GYR_CONF_RANGE_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(BMI270_GYR_CONF_RANGE_MSK, val); + bmi270_scale_item = bmi270_scale_table[BMI270_GYRO]; + break; + case IIO_TEMP: + val = 0; + bmi270_scale_item = bmi270_scale_table[BMI270_TEMP]; + break; + default: + return -EINVAL; + } + + if (val >= bmi270_scale_item.num) + return -EINVAL; + + *scale = bmi270_scale_item.tbl[val].scale; + *uscale = bmi270_scale_item.tbl[val].uscale; + return 0; +} + +static int bmi270_set_odr(struct bmi270_data *data, int chan_type, int odr, + int uodr) +{ + int i; + int reg, mask; + struct bmi270_odr_item bmi270_odr_item; + + switch (chan_type) { + case IIO_ACCEL: + reg = BMI270_ACC_CONF_REG; + mask = BMI270_ACC_CONF_ODR_MSK; + bmi270_odr_item = bmi270_odr_table[BMI270_ACCEL]; + break; + case IIO_ANGL_VEL: + reg = BMI270_GYR_CONF_REG; + mask = BMI270_GYR_CONF_ODR_MSK; + bmi270_odr_item = bmi270_odr_table[BMI270_GYRO]; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + for (i = 0; i < bmi270_odr_item.num; i++) { + if (bmi270_odr_item.tbl[i].odr != odr || + bmi270_odr_item.tbl[i].uodr != uodr) + continue; + + return regmap_update_bits(data->regmap, reg, mask, + bmi270_odr_item.vals[i]); + } + + return -EINVAL; +} + +static int bmi270_get_odr(struct bmi270_data *data, int chan_type, int *odr, + int *uodr) +{ + int i, val, ret; + struct bmi270_odr_item bmi270_odr_item; + + guard(mutex)(&data->mutex); + + switch (chan_type) { + case IIO_ACCEL: + ret = regmap_read(data->regmap, BMI270_ACC_CONF_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(BMI270_ACC_CONF_ODR_MSK, val); + bmi270_odr_item = bmi270_odr_table[BMI270_ACCEL]; + break; + case IIO_ANGL_VEL: + ret = regmap_read(data->regmap, BMI270_GYR_CONF_REG, &val); + if (ret) + return ret; + + val = FIELD_GET(BMI270_GYR_CONF_ODR_MSK, val); + bmi270_odr_item = bmi270_odr_table[BMI270_GYRO]; + break; + default: + return -EINVAL; + } + + for (i = 0; i < bmi270_odr_item.num; i++) { + if (val != bmi270_odr_item.vals[i]) + continue; + + *odr = bmi270_odr_item.tbl[i].odr; + *uodr = bmi270_odr_item.tbl[i].uodr; + return 0; + } + + return -EINVAL; +} + +static irqreturn_t bmi270_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmi270_data *data = iio_priv(indio_dev); + unsigned int status0, status1; + s64 timestamp = iio_get_time_ns(indio_dev); + int ret; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, BMI270_INT_STATUS_0_REG, + &status0); + if (ret) + return IRQ_NONE; + + ret = regmap_read(data->regmap, BMI270_INT_STATUS_1_REG, + &status1); + if (ret) + return IRQ_NONE; + } + + if (FIELD_GET(BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK, status1)) + iio_trigger_poll_nested(data->trig); + + if (FIELD_GET(BMI270_INT_STATUS_0_MOTION_MSK, status0)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_RISING), + timestamp); + + if (FIELD_GET(BMI270_INT_STATUS_0_NOMOTION_MSK, status0)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_ROC, + IIO_EV_DIR_RISING), + timestamp); + + if (FIELD_GET(BMI270_INT_STATUS_0_STEP_CNT_MSK, status0)) + iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_STEPS, 0, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + + return IRQ_HANDLED; +} + +static int bmi270_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmi270_data *data = iio_trigger_get_drvdata(trig); + unsigned int field_value = 0; + unsigned int mask; + + guard(mutex)(&data->mutex); + + switch (data->irq_pin) { + case BMI270_IRQ_INT1: + mask = BMI270_INT_MAP_DATA_DRDY_INT1_MSK; + set_mask_bits(&field_value, BMI270_INT_MAP_DATA_DRDY_INT1_MSK, + FIELD_PREP(BMI270_INT_MAP_DATA_DRDY_INT1_MSK, + state)); + break; + case BMI270_IRQ_INT2: + mask = BMI270_INT_MAP_DATA_DRDY_INT2_MSK; + set_mask_bits(&field_value, BMI270_INT_MAP_DATA_DRDY_INT2_MSK, + FIELD_PREP(BMI270_INT_MAP_DATA_DRDY_INT2_MSK, + state)); + break; + default: + return -EINVAL; + } + + return regmap_update_bits(data->regmap, BMI270_INT_MAP_DATA_REG, mask, + field_value); +} + +static const struct iio_trigger_ops bmi270_trigger_ops = { + .set_trigger_state = &bmi270_data_rdy_trigger_set_state, +}; + +static irqreturn_t bmi270_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmi270_data *data = iio_priv(indio_dev); + int ret; + + guard(mutex)(&data->mutex); + + ret = regmap_bulk_read(data->regmap, BMI270_ACCEL_X_REG, + &data->buffer.channels, + sizeof(data->buffer.channels)); + + if (ret) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, + pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int bmi270_get_data(struct bmi270_data *data, int chan_type, int axis, + int *val) +{ + __le16 sample; + int reg; + int ret; + + switch (chan_type) { + case IIO_ACCEL: + reg = BMI270_ACCEL_X_REG + (axis - IIO_MOD_X) * 2; + break; + case IIO_ANGL_VEL: + reg = BMI270_ANG_VEL_X_REG + (axis - IIO_MOD_X) * 2; + break; + case IIO_TEMP: + reg = BMI270_TEMPERATURE_0_REG; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + + ret = regmap_bulk_read(data->regmap, reg, &sample, sizeof(sample)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(sample), 15); + + return IIO_VAL_INT; +} + +static int bmi270_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct bmi270_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + return bmi270_read_steps(data, val); + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi270_get_data(data, chan->type, chan->channel2, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + ret = bmi270_get_scale(data, chan->type, val, val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = BMI270_TEMP_OFFSET; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + ret = bmi270_get_odr(data, chan->type, val, val2); + return ret ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_ENABLE: + *val = data->steps_enabled ? 1 : 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int bmi270_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmi270_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi270_set_scale(data, chan->type, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = bmi270_set_odr(data, chan->type, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_ENABLE: + return bmi270_enable_steps(data, val); + case IIO_CHAN_INFO_PROCESSED: { + if (val || !data->steps_enabled) + return -EINVAL; + + guard(mutex)(&data->mutex); + /* Clear step counter value */ + return bmi270_update_feature_reg(data, BMI270_SC_26_REG, + BMI270_STEP_SC26_RST_CNT_MSK, + FIELD_PREP(BMI270_STEP_SC26_RST_CNT_MSK, + 1)); + } + default: + return -EINVAL; + } +} + +static int bmi270_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_MICRO; + switch (chan->type) { + case IIO_ANGL_VEL: + *vals = (const int *)bmi270_gyro_scale; + *length = ARRAY_SIZE(bmi270_gyro_scale) * 2; + return IIO_AVAIL_LIST; + case IIO_ACCEL: + *vals = (const int *)bmi270_accel_scale; + *length = ARRAY_SIZE(bmi270_accel_scale) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + switch (chan->type) { + case IIO_ANGL_VEL: + *vals = (const int *)bmi270_gyro_odr; + *length = ARRAY_SIZE(bmi270_gyro_odr) * 2; + return IIO_AVAIL_LIST; + case IIO_ACCEL: + *vals = (const int *)bmi270_accel_odr; + *length = ARRAY_SIZE(bmi270_accel_odr) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static ssize_t in_accel_value_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi270_data *data = iio_priv(indio_dev); + int ret, scale, uscale; + unsigned int step, max; + + ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale); + if (ret) + return ret; + + max = BMI270_G_MICRO_M_S_2 / uscale; + step = max / BMI270_MOTION_THRES_FULL_SCALE; + + return sysfs_emit(buf, "[0 %u %u]\n", step, max); +} + +static IIO_DEVICE_ATTR_RO(in_accel_value_available, 0); + +static IIO_CONST_ATTR(in_accel_period_available, "[0.0 0.02 162.0]"); + +static struct attribute *bmi270_event_attributes[] = { + &iio_dev_attr_in_accel_value_available.dev_attr.attr, + &iio_const_attr_in_accel_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group bmi270_event_attribute_group = { + .attrs = bmi270_event_attributes, +}; + +static int bmi270_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, bool state) +{ + struct bmi270_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + return bmi270_anymotion_event_en(data, chan, state); + case IIO_EV_TYPE_ROC: + return bmi270_nomotion_event_en(data, state); + case IIO_EV_TYPE_CHANGE: + return bmi270_step_wtrmrk_en(data, state); + default: + return -EINVAL; + } +} + +static int bmi270_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct bmi270_data *data = iio_priv(indio_dev); + bool feat_en, axis_en; + int ret, reg, regval; + u16 motion_reg; + + guard(mutex)(&data->mutex); + + reg = bmi270_int_map_reg(data->irq_pin); + if (reg < 0) + return reg; + + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + + switch (chan->type) { + case IIO_STEPS: + return !!FIELD_GET(BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK, regval); + case IIO_ACCEL: + switch (type) { + case IIO_EV_TYPE_ROC: + return !!FIELD_GET(BMI270_INT_MAP_FEAT_NOMOTION_MSK, regval); + case IIO_EV_TYPE_MAG_ADAPTIVE: + ret = bmi270_read_feature_reg(data, BMI270_ANYMO1_REG, + &motion_reg); + if (ret) + return ret; + + feat_en = FIELD_GET(BMI270_INT_MAP_FEAT_ANYMOTION_MSK, + regval); + switch (chan->channel2) { + case IIO_MOD_X: + axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, + motion_reg); + break; + case IIO_MOD_Y: + axis_en = FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, + motion_reg); + break; + case IIO_MOD_Z: + axis_en = FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, + motion_reg); + break; + default: + return -EINVAL; + } + return axis_en && feat_en; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bmi270_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct bmi270_data *data = iio_priv(indio_dev); + unsigned int raw, mask, regval; + int ret, reg, scale, uscale; + u64 tmp; + + guard(mutex)(&data->mutex); + + if (type == IIO_EV_TYPE_CHANGE) { + if (!in_range(val, 0, BMI270_STEP_COUNTER_MAX + 1)) + return -EINVAL; + + raw = val / BMI270_STEP_COUNTER_FACTOR; + mask = BMI270_STEP_SC26_WTRMRK_MSK; + regval = FIELD_PREP(BMI270_STEP_SC26_WTRMRK_MSK, raw); + return bmi270_update_feature_reg(data, BMI270_SC_26_REG, mask, + regval); + } + + reg = bmi270_motion_reg(type, info); + if (reg < 0) + return reg; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale); + if (ret) + return ret; + + if (!in_range(val, 0, (BMI270_G_MICRO_M_S_2 / uscale) + 1)) + return -EINVAL; + + tmp = (u64)val * BMI270_MOTION_THRES_FULL_SCALE * uscale; + raw = DIV_ROUND_CLOSEST_ULL(tmp, BMI270_G_MICRO_M_S_2); + mask = BMI270_FEAT_MOTION_THRESHOLD_MSK; + regval = FIELD_PREP(BMI270_FEAT_MOTION_THRESHOLD_MSK, raw); + return bmi270_update_feature_reg(data, reg, mask, regval); + case IIO_EV_INFO_PERIOD: + if (!in_range(val, 0, BMI270_MOTION_DURAT_MAX + 1)) + return -EINVAL; + + raw = BMI270_INT_MICRO_TO_RAW(val, val2, + BMI270_MOTION_DURAT_SCALE); + mask = BMI270_FEAT_MOTION_DURATION_MSK; + regval = FIELD_PREP(BMI270_FEAT_MOTION_DURATION_MSK, raw); + return bmi270_update_feature_reg(data, reg, mask, regval); + default: + return -EINVAL; + } +} + +static int bmi270_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct bmi270_data *data = iio_priv(indio_dev); + int ret, reg, scale, uscale; + unsigned int raw; + u16 regval; + u64 tmp; + + guard(mutex)(&data->mutex); + + if (type == IIO_EV_TYPE_CHANGE) { + ret = bmi270_read_feature_reg(data, BMI270_SC_26_REG, ®val); + if (ret) + return ret; + + raw = FIELD_GET(BMI270_STEP_SC26_WTRMRK_MSK, regval); + *val = raw * BMI270_STEP_COUNTER_FACTOR; + return IIO_VAL_INT; + } + + reg = bmi270_motion_reg(type, info); + if (reg < 0) + return reg; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi270_read_feature_reg(data, reg, ®val); + if (ret) + return ret; + + ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale); + if (ret) + return ret; + + raw = FIELD_GET(BMI270_FEAT_MOTION_THRESHOLD_MSK, regval); + tmp = (u64)raw * BMI270_G_MICRO_M_S_2; + *val = DIV_ROUND_CLOSEST_ULL(tmp, + BMI270_MOTION_THRES_FULL_SCALE * uscale); + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + ret = bmi270_read_feature_reg(data, reg, ®val); + if (ret) + return ret; + + raw = FIELD_GET(BMI270_FEAT_MOTION_DURATION_MSK, regval); + *val = raw / BMI270_MOTION_DURAT_SCALE; + *val2 = BMI270_RAW_TO_MICRO(raw, BMI270_MOTION_DURAT_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_event_spec bmi270_step_wtrmrk_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), +}; + +static const struct iio_event_spec bmi270_anymotion_event = { + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_PERIOD), +}; + +static const struct iio_event_spec bmi270_nomotion_event = { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_PERIOD), +}; + +static const struct iio_info bmi270_info = { + .read_raw = bmi270_read_raw, + .write_raw = bmi270_write_raw, + .read_avail = bmi270_read_avail, + .write_event_config = bmi270_write_event_config, + .read_event_config = bmi270_read_event_config, + .write_event_value = bmi270_write_event_value, + .read_event_value = bmi270_read_event_value, + .event_attrs = &bmi270_event_attribute_group, +}; + +#define BMI270_ACCEL_CHANNEL(_axis) { \ + .type = IIO_ACCEL, \ + .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), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = BMI270_SCAN_ACCEL_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .event_spec = &bmi270_anymotion_event, \ + .num_event_specs = 1, \ +} + +#define BMI270_ANG_VEL_CHANNEL(_axis) { \ + .type = IIO_ANGL_VEL, \ + .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), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = BMI270_SCAN_GYRO_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec bmi270_channels[] = { + BMI270_ACCEL_CHANNEL(X), + BMI270_ACCEL_CHANNEL(Y), + BMI270_ACCEL_CHANNEL(Z), + BMI270_ANG_VEL_CHANNEL(X), + BMI270_ANG_VEL_CHANNEL(Y), + BMI270_ANG_VEL_CHANNEL(Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = -1, /* No buffer support */ + }, + { + .type = IIO_STEPS, + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ + .event_spec = &bmi270_step_wtrmrk_event, + .num_event_specs = 1, + }, + IIO_CHAN_SOFT_TIMESTAMP(BMI270_SCAN_TIMESTAMP), + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y_AND_Z, + .scan_index = -1, /* Fake channel */ + .event_spec = &bmi270_nomotion_event, + .num_event_specs = 1, + }, +}; + +static int bmi270_int_pin_config(struct bmi270_data *data, + enum bmi270_irq_pin irq_pin, + bool active_high, bool open_drain, bool latch) +{ + unsigned int reg, field_value; + int ret; + + ret = regmap_update_bits(data->regmap, BMI270_INT_LATCH_REG, + BMI270_INT_LATCH_REG_MSK, + FIELD_PREP(BMI270_INT_LATCH_REG_MSK, latch)); + if (ret) + return ret; + + switch (irq_pin) { + case BMI270_IRQ_INT1: + reg = BMI270_INT1_IO_CTRL_REG; + break; + case BMI270_IRQ_INT2: + reg = BMI270_INT2_IO_CTRL_REG; + break; + default: + return -EINVAL; + } + + field_value = FIELD_PREP(BMI270_INT_IO_CTRL_LVL_MSK, active_high) | + FIELD_PREP(BMI270_INT_IO_CTRL_OD_MSK, open_drain) | + FIELD_PREP(BMI270_INT_IO_CTRL_OP_MSK, 1); + return regmap_update_bits(data->regmap, reg, + BMI270_INT_IO_LVL_OD_OP_MSK, field_value); +} + +static int bmi270_trigger_probe(struct bmi270_data *data, + struct iio_dev *indio_dev) +{ + bool open_drain, active_high, latch; + struct fwnode_handle *fwnode; + enum bmi270_irq_pin irq_pin; + int ret, irq, irq_type; + + fwnode = dev_fwnode(data->dev); + if (!fwnode) + return -ENODEV; + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq > 0) { + irq_pin = BMI270_IRQ_INT1; + } else { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq < 0) + return 0; + + irq_pin = BMI270_IRQ_INT2; + } + + irq_type = irq_get_trigger_type(irq); + switch (irq_type) { + case IRQF_TRIGGER_RISING: + latch = false; + active_high = true; + break; + case IRQF_TRIGGER_HIGH: + latch = true; + active_high = true; + break; + case IRQF_TRIGGER_FALLING: + latch = false; + active_high = false; + break; + case IRQF_TRIGGER_LOW: + latch = true; + active_high = false; + break; + default: + return dev_err_probe(data->dev, -EINVAL, + "Invalid interrupt type 0x%x specified\n", + irq_type); + } + + open_drain = fwnode_property_read_bool(fwnode, "drive-open-drain"); + + ret = bmi270_int_pin_config(data, irq_pin, active_high, open_drain, + latch); + if (ret) + return dev_err_probe(data->dev, ret, + "Failed to configure irq line\n"); + + data->trig = devm_iio_trigger_alloc(data->dev, "%s-trig-%d", + indio_dev->name, irq_pin); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &bmi270_trigger_ops; + iio_trigger_set_drvdata(data->trig, data); + + ret = devm_request_threaded_irq(data->dev, irq, NULL, + bmi270_irq_thread_handler, + IRQF_ONESHOT, "bmi270-int", indio_dev); + if (ret) + return dev_err_probe(data->dev, ret, "Failed to request IRQ\n"); + + ret = devm_iio_trigger_register(data->dev, data->trig); + if (ret) + return dev_err_probe(data->dev, ret, + "Trigger registration failed\n"); + + /* Disable axes for motion events */ + ret = bmi270_update_feature_reg(data, BMI270_ANYMO1_REG, + BMI270_FEAT_MOTION_XYZ_EN_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_XYZ_EN_MSK, 0)); + if (ret) + return ret; + + data->irq_pin = irq_pin; + + return 0; +} + +static int bmi270_validate_chip_id(struct bmi270_data *data) +{ + int chip_id; + int ret; + struct device *dev = data->dev; + struct regmap *regmap = data->regmap; + + ret = regmap_read(regmap, BMI270_CHIP_ID_REG, &chip_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to read chip id"); + + /* + * Some manufacturers use "BMI0160" for both the BMI160 and + * BMI260. If the device is actually a BMI160, the bmi160 + * driver should handle it and this driver should not. + */ + if (chip_id == BMI160_CHIP_ID_VAL) + return -ENODEV; + + if (chip_id != data->chip_info->chip_id) + dev_info(dev, "Unexpected chip id 0x%x", chip_id); + + if (chip_id == bmi260_chip_info.chip_id) + data->chip_info = &bmi260_chip_info; + else if (chip_id == bmi270_chip_info.chip_id) + data->chip_info = &bmi270_chip_info; + + return 0; +} + +static int bmi270_write_calibration_data(struct bmi270_data *data) +{ + int ret; + int status = 0; + const struct firmware *init_data; + struct device *dev = data->dev; + struct regmap *regmap = data->regmap; + + ret = regmap_clear_bits(regmap, BMI270_PWR_CONF_REG, + BMI270_PWR_CONF_ADV_PWR_SAVE_MSK); + if (ret) + return dev_err_probe(dev, ret, + "Failed to write power configuration"); + + /* + * After disabling advanced power save, all registers are accessible + * after a 450us delay. This delay is specified in table A of the + * datasheet. + */ + usleep_range(450, 1000); + + ret = regmap_clear_bits(regmap, BMI270_INIT_CTRL_REG, + BMI270_INIT_CTRL_LOAD_DONE_MSK); + if (ret) + return dev_err_probe(dev, ret, + "Failed to prepare device to load init data"); + + ret = request_firmware(&init_data, data->chip_info->fw_name, dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to load init data file"); + + ret = regmap_bulk_write(regmap, BMI270_INIT_DATA_REG, + init_data->data, init_data->size); + release_firmware(init_data); + if (ret) + return dev_err_probe(dev, ret, "Failed to write init data"); + + ret = regmap_set_bits(regmap, BMI270_INIT_CTRL_REG, + BMI270_INIT_CTRL_LOAD_DONE_MSK); + if (ret) + return dev_err_probe(dev, ret, + "Failed to stop device initialization"); + + /* + * Wait at least 140ms for the device to complete configuration. + * This delay is specified in table C of the datasheet. + */ + usleep_range(140000, 160000); + + ret = regmap_read(regmap, BMI270_INTERNAL_STATUS_REG, &status); + if (ret) + return dev_err_probe(dev, ret, "Failed to read internal status"); + + if (status != BMI270_INTERNAL_STATUS_MSG_INIT_OK) + return dev_err_probe(dev, -ENODEV, "Device failed to initialize"); + + return 0; +} + +static int bmi270_configure_imu(struct bmi270_data *data) +{ + int ret; + struct device *dev = data->dev; + struct regmap *regmap = data->regmap; + + ret = regmap_set_bits(regmap, BMI270_PWR_CTRL_REG, + BMI270_PWR_CTRL_AUX_EN_MSK | + BMI270_PWR_CTRL_GYR_EN_MSK | + BMI270_PWR_CTRL_ACCEL_EN_MSK | + BMI270_PWR_CTRL_TEMP_EN_MSK); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable accelerometer and gyroscope"); + + ret = regmap_set_bits(regmap, BMI270_ACC_CONF_REG, + FIELD_PREP(BMI270_ACC_CONF_ODR_MSK, + BMI270_ACC_CONF_ODR_100HZ) | + FIELD_PREP(BMI270_ACC_CONF_BWP_MSK, + BMI270_ACC_CONF_BWP_NORMAL_MODE)); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure accelerometer"); + + ret = regmap_set_bits(regmap, BMI270_GYR_CONF_REG, + FIELD_PREP(BMI270_GYR_CONF_ODR_MSK, + BMI270_GYR_CONF_ODR_200HZ) | + FIELD_PREP(BMI270_GYR_CONF_BWP_MSK, + BMI270_GYR_CONF_BWP_NORMAL_MODE)); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure gyroscope"); + + /* Enable FIFO_WKUP, Disable ADV_PWR_SAVE and FUP_EN */ + ret = regmap_write(regmap, BMI270_PWR_CONF_REG, + BMI270_PWR_CONF_FIFO_WKUP_MSK); + if (ret) + return dev_err_probe(dev, ret, "Failed to set power configuration"); + + return 0; +} + +static int bmi270_chip_init(struct bmi270_data *data) +{ + int ret; + + ret = bmi270_validate_chip_id(data); + if (ret) + return ret; + + ret = bmi270_write_calibration_data(data); + if (ret) + return ret; + + return bmi270_configure_imu(data); +} + +int bmi270_core_probe(struct device *dev, struct regmap *regmap, + const struct bmi270_chip_info *chip_info) +{ + int ret; + struct bmi270_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->regmap = regmap; + data->chip_info = chip_info; + data->irq_pin = BMI270_IRQ_DISABLED; + mutex_init(&data->mutex); + + ret = bmi270_chip_init(data); + if (ret) + return ret; + + indio_dev->channels = bmi270_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi270_channels); + indio_dev->name = chip_info->name; + indio_dev->available_scan_masks = bmi270_avail_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmi270_info; + dev_set_drvdata(data->dev, indio_dev); + + ret = bmi270_trigger_probe(data, indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + bmi270_trigger_handler, NULL); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(bmi270_core_probe, "IIO_BMI270"); + +static int bmi270_core_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return iio_device_suspend_triggering(indio_dev); +} + +static int bmi270_core_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return iio_device_resume_triggering(indio_dev); +} + +const struct dev_pm_ops bmi270_core_pm_ops = { + RUNTIME_PM_OPS(bmi270_core_runtime_suspend, bmi270_core_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS_GPL(bmi270_core_pm_ops, "IIO_BMI270"); + +MODULE_AUTHOR("Alex Lanzano"); +MODULE_DESCRIPTION("BMI270 driver"); +MODULE_LICENSE("GPL"); |
