]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
iio: imu: bmi270: add support for data ready interrupt trigger
authorGustavo Silva <gustavograzs@gmail.com>
Fri, 28 Feb 2025 23:03:50 +0000 (20:03 -0300)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Tue, 4 Mar 2025 13:17:51 +0000 (13:17 +0000)
The BMI270 sensor provides two interrupt pins that can be used for
different interrupt sources, including a data ready signal. Add support
for configuring one the pins as a trigger source.

The interrupt pin can be configured with various options: active high or
low, push-pull or open-drain, and latched or non-latched.

Acked-by: Alex Lanzano <lanzano.alex@gmail.com>
Signed-off-by: Gustavo Silva <gustavograzs@gmail.com>
Link: https://patch.msgid.link/20250228-bmi270-irq-v2-3-3f97a4e8f551@gmail.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/imu/bmi270/bmi270_core.c

index 16deda12a5c1aba6d366f7fcb134266c490d3a75..a86be5af5ccb1f010f2282ee31c47f284c1bcc86 100644 (file)
@@ -4,11 +4,13 @@
 #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/iio.h>
 #include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
 #include <linux/iio/triggered_buffer.h>
 #include <linux/iio/trigger_consumer.h>
 
@@ -26,6 +28,9 @@
 #define BMI270_ACCEL_X_REG                             0x0c
 #define BMI270_ANG_VEL_X_REG                           0x12
 
+#define BMI270_INT_STATUS_1_REG                                0x1d
+#define BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK           GENMASK(7, 6)
+
 #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_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_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 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;
 
        /*
         * Where IIO_DMA_MINALIGN may be larger than 8 bytes, align to
@@ -274,6 +303,8 @@ static int bmi270_set_scale(struct bmi270_data *data, int chan_type, int uscale)
                return -EINVAL;
        }
 
+       guard(mutex)(&data->mutex);
+
        for (i = 0; i < bmi270_scale_item.num; i++) {
                if (bmi270_scale_item.tbl[i].uscale != uscale)
                        continue;
@@ -291,6 +322,8 @@ static int bmi270_get_scale(struct bmi270_data *data, int chan_type, int *scale,
        unsigned int val;
        struct bmi270_scale_item bmi270_scale_item;
 
+       guard(mutex)(&data->mutex);
+
        switch (chan_type) {
        case IIO_ACCEL:
                ret = regmap_read(data->regmap, BMI270_ACC_CONF_RANGE_REG, &val);
@@ -346,6 +379,8 @@ static int bmi270_set_odr(struct bmi270_data *data, int chan_type, int odr,
                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)
@@ -364,6 +399,8 @@ static int bmi270_get_odr(struct bmi270_data *data, int chan_type, int *odr,
        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);
@@ -397,6 +434,60 @@ static int bmi270_get_odr(struct bmi270_data *data, int chan_type, int *odr,
        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 status;
+       int ret;
+
+       scoped_guard(mutex, &data->mutex) {
+               ret = regmap_read(data->regmap, BMI270_INT_STATUS_1_REG,
+                                 &status);
+               if (ret)
+                       return IRQ_NONE;
+       }
+
+       if (FIELD_GET(BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK, status))
+               iio_trigger_poll_nested(data->trig);
+
+       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;
@@ -404,6 +495,8 @@ static irqreturn_t bmi270_trigger_handler(int irq, void *p)
        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));
@@ -439,13 +532,15 @@ static int bmi270_get_data(struct bmi270_data *data, int chan_type, int axis,
                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 0;
+       return IIO_VAL_INT;
 }
 
 static int bmi270_read_raw(struct iio_dev *indio_dev,
@@ -457,11 +552,11 @@ static int bmi270_read_raw(struct iio_dev *indio_dev,
 
        switch (mask) {
        case IIO_CHAN_INFO_RAW:
+               if (!iio_device_claim_direct(indio_dev))
+                       return -EBUSY;
                ret = bmi270_get_data(data, chan->type, chan->channel2, val);
-               if (ret)
-                       return ret;
-
-               return IIO_VAL_INT;
+               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;
@@ -486,12 +581,21 @@ static int bmi270_write_raw(struct iio_dev *indio_dev,
                            int val, int val2, long mask)
 {
        struct bmi270_data *data = iio_priv(indio_dev);
+       int ret;
 
        switch (mask) {
        case IIO_CHAN_INFO_SCALE:
-               return bmi270_set_scale(data, chan->type, val2);
+               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:
-               return bmi270_set_odr(data, chan->type, val, val2);
+               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;
        default:
                return -EINVAL;
        }
@@ -597,6 +701,116 @@ static const struct iio_chan_spec bmi270_channels[] = {
        IIO_CHAN_SOFT_TIMESTAMP(BMI270_SCAN_TIMESTAMP),
 };
 
+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");
+
+       data->irq_pin = irq_pin;
+
+       return 0;
+}
+
 static int bmi270_validate_chip_id(struct bmi270_data *data)
 {
        int chip_id;
@@ -757,6 +971,8 @@ int bmi270_core_probe(struct device *dev, struct regmap *regmap,
        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)
@@ -769,6 +985,10 @@ int bmi270_core_probe(struct device *dev, struct regmap *regmap,
        indio_dev->modes = INDIO_DIRECT_MODE;
        indio_dev->info = &bmi270_info;
 
+       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);