root/drivers/iio/imu/bmi270/bmi270_core.c
// 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, &regval);
        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, &regval);
        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, &regval);
        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, &regval);
                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, &regval);
                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, &regval);
                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");