*/
 
 #include <linux/bitfield.h>
+#include <linux/interrupt.h>
 #include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/property.h>
 #include <linux/regmap.h>
 
+#include <linux/iio/buffer.h>
+#include <linux/iio/kfifo_buf.h>
+
 #include "adxl313.h"
 
+#define ADXL313_INT_NONE                       U8_MAX
+#define ADXL313_INT1                           1
+#define ADXL313_INT2                           2
+
+#define ADXL313_REG_XYZ_BASE                   ADXL313_REG_DATA_AXIS(0)
+
 static const struct regmap_range adxl312_readable_reg_range[] = {
        regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_DEVID0),
        regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
        [9] = { 3200, 0 },
 };
 
-#define ADXL313_ACCEL_CHANNEL(index, axis) {                           \
+#define ADXL313_ACCEL_CHANNEL(index, reg, axis) {                      \
        .type = IIO_ACCEL,                                              \
-       .address = index,                                               \
+       .scan_index = (index),                                          \
+       .address = (reg),                                               \
        .modified = 1,                                                  \
        .channel2 = IIO_MOD_##axis,                                     \
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |                  \
        .info_mask_shared_by_type_available =                           \
                BIT(IIO_CHAN_INFO_SAMP_FREQ),                           \
        .scan_type = {                                                  \
+               .sign = 's',                                            \
                .realbits = 13,                                         \
+               .storagebits = 16,                                      \
+               .endianness = IIO_BE,                                   \
        },                                                              \
 }
 
+enum adxl313_chans {
+       chan_x, chan_y, chan_z,
+};
+
 static const struct iio_chan_spec adxl313_channels[] = {
-       ADXL313_ACCEL_CHANNEL(0, X),
-       ADXL313_ACCEL_CHANNEL(1, Y),
-       ADXL313_ACCEL_CHANNEL(2, Z),
+       ADXL313_ACCEL_CHANNEL(0, chan_x, X),
+       ADXL313_ACCEL_CHANNEL(1, chan_y, Y),
+       ADXL313_ACCEL_CHANNEL(2, chan_z, Z),
+};
+
+static const unsigned long adxl313_scan_masks[] = {
+       BIT(chan_x) | BIT(chan_y) | BIT(chan_z),
+       0
 };
 
 static int adxl313_set_odr(struct adxl313_data *data,
        }
 }
 
+static int adxl313_set_watermark(struct iio_dev *indio_dev, unsigned int value)
+{
+       struct adxl313_data *data = iio_priv(indio_dev);
+       int ret;
+
+       value = min(value, ADXL313_FIFO_SIZE - 1);
+
+       ret = adxl313_set_measure_en(data, false);
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(data->regmap, ADXL313_REG_FIFO_CTL,
+                                ADXL313_REG_FIFO_CTL_MODE_MSK, value);
+       if (ret)
+               return ret;
+
+       data->watermark = value;
+
+       ret = regmap_set_bits(data->regmap, ADXL313_REG_INT_ENABLE,
+                             ADXL313_INT_WATERMARK);
+       if (ret)
+               return ret;
+
+       return adxl313_set_measure_en(data, true);
+}
+
+static int adxl313_get_samples(struct adxl313_data *data)
+{
+       unsigned int regval;
+       int ret;
+
+       ret = regmap_read(data->regmap, ADXL313_REG_FIFO_STATUS, ®val);
+       if (ret)
+               return ret;
+
+       return FIELD_GET(ADXL313_REG_FIFO_STATUS_ENTRIES_MSK, regval);
+}
+
+static int adxl313_fifo_transfer(struct adxl313_data *data, int samples)
+{
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < samples; i++) {
+               ret = regmap_bulk_read(data->regmap, ADXL313_REG_XYZ_BASE,
+                                      data->fifo_buf + (i * ADXL313_NUM_AXIS),
+                                      sizeof(data->fifo_buf[0]) * ADXL313_NUM_AXIS);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+/**
+ * adxl313_fifo_reset() - Reset the FIFO and interrupt status registers.
+ * @data: The device data.
+ *
+ * Reset the FIFO status registers. Reading out status registers clears the
+ * FIFO and interrupt configuration. Thus do not evaluate regmap return values.
+ * Ignore particular read register content. Register content is not processed
+ * any further. Therefore the function returns void.
+ */
+static void adxl313_fifo_reset(struct adxl313_data *data)
+{
+       unsigned int regval;
+       int samples;
+
+       adxl313_set_measure_en(data, false);
+
+       samples = adxl313_get_samples(data);
+       if (samples > 0)
+               adxl313_fifo_transfer(data, samples);
+
+       regmap_read(data->regmap, ADXL313_REG_INT_SOURCE, ®val);
+
+       adxl313_set_measure_en(data, true);
+}
+
+static int adxl313_buffer_postenable(struct iio_dev *indio_dev)
+{
+       struct adxl313_data *data = iio_priv(indio_dev);
+       int ret;
+
+       /* Set FIFO modes with measurement turned off, according to datasheet */
+       ret = adxl313_set_measure_en(data, false);
+       if (ret)
+               return ret;
+
+       ret = regmap_write(data->regmap, ADXL313_REG_FIFO_CTL,
+                          FIELD_PREP(ADXL313_REG_FIFO_CTL_SAMPLES_MSK, data->watermark) |
+                          FIELD_PREP(ADXL313_REG_FIFO_CTL_MODE_MSK, ADXL313_FIFO_STREAM));
+       if (ret)
+               return ret;
+
+       return adxl313_set_measure_en(data, true);
+}
+
+static int adxl313_buffer_predisable(struct iio_dev *indio_dev)
+{
+       struct adxl313_data *data = iio_priv(indio_dev);
+       int ret;
+
+       ret = adxl313_set_measure_en(data, false);
+       if (ret)
+               return ret;
+
+       ret = regmap_write(data->regmap, ADXL313_REG_FIFO_CTL,
+                          FIELD_PREP(ADXL313_REG_FIFO_CTL_MODE_MSK, ADXL313_FIFO_BYPASS));
+
+       ret = regmap_write(data->regmap, ADXL313_REG_INT_ENABLE, 0);
+       if (ret)
+               return ret;
+
+       return adxl313_set_measure_en(data, true);
+}
+
+static const struct iio_buffer_setup_ops adxl313_buffer_ops = {
+       .postenable = adxl313_buffer_postenable,
+       .predisable = adxl313_buffer_predisable,
+};
+
+static int adxl313_fifo_push(struct iio_dev *indio_dev, int samples)
+{
+       struct adxl313_data *data = iio_priv(indio_dev);
+       unsigned int i;
+       int ret;
+
+       ret = adxl313_fifo_transfer(data, samples);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < ADXL313_NUM_AXIS * samples; i += ADXL313_NUM_AXIS)
+               iio_push_to_buffers(indio_dev, &data->fifo_buf[i]);
+
+       return 0;
+}
+
+static irqreturn_t adxl313_irq_handler(int irq, void *p)
+{
+       struct iio_dev *indio_dev = p;
+       struct adxl313_data *data = iio_priv(indio_dev);
+       int samples, int_stat;
+
+       if (regmap_read(data->regmap, ADXL313_REG_INT_SOURCE, &int_stat))
+               return IRQ_NONE;
+
+       if (FIELD_GET(ADXL313_INT_WATERMARK, int_stat)) {
+               samples = adxl313_get_samples(data);
+               if (samples < 0)
+                       goto err_reset_fifo;
+
+               if (adxl313_fifo_push(indio_dev, samples))
+                       goto err_reset_fifo;
+       }
+
+       if (FIELD_GET(ADXL313_INT_OVERRUN, int_stat))
+               goto err_reset_fifo;
+
+       return IRQ_HANDLED;
+
+err_reset_fifo:
+       adxl313_fifo_reset(data);
+
+       return IRQ_HANDLED;
+}
+
 static int adxl313_reg_access(struct iio_dev *indio_dev, unsigned int reg,
                              unsigned int writeval, unsigned int *readval)
 {
        .read_raw       = adxl313_read_raw,
        .write_raw      = adxl313_write_raw,
        .read_avail     = adxl313_read_freq_avail,
+       .hwfifo_set_watermark = adxl313_set_watermark,
        .debugfs_reg_access = &adxl313_reg_access,
 };
 
        return adxl313_set_measure_en(data, true);
 }
 
+static unsigned int adxl313_get_int_type(struct device *dev, int *irq)
+{
+       *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT1");
+       if (*irq > 0)
+               return ADXL313_INT1;
+
+       *irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
+       if (*irq > 0)
+               return ADXL313_INT2;
+
+       return ADXL313_INT_NONE;
+}
+
 /**
  * adxl313_core_probe() - probe and setup for adxl313 accelerometer
  * @dev:       Driver model representation of the device
 {
        struct adxl313_data *data;
        struct iio_dev *indio_dev;
-       int ret;
+       u8 int_line;
+       u8 int_map_msk;
+       int irq, ret;
 
        indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
        if (!indio_dev)
        indio_dev->modes = INDIO_DIRECT_MODE;
        indio_dev->channels = adxl313_channels;
        indio_dev->num_channels = ARRAY_SIZE(adxl313_channels);
+       indio_dev->available_scan_masks = adxl313_scan_masks;
 
        ret = adxl313_setup(dev, data, setup);
        if (ret) {
                return ret;
        }
 
+       int_line = adxl313_get_int_type(dev, &irq);
+       if (int_line == ADXL313_INT_NONE) {
+               /*
+                * FIFO_BYPASSED mode
+                *
+                * When no interrupt lines are specified, the driver falls back
+                * to use the sensor in FIFO_BYPASS mode. This means turning off
+                * internal FIFO and interrupt generation (since there is no
+                * line specified). Unmaskable interrupts such as overrun or
+                * data ready won't interfere. Even that a FIFO_STREAM mode w/o
+                * connected interrupt line might allow for obtaining raw
+                * measurements, a fallback to disable interrupts when no
+                * interrupt lines are connected seems to be the cleaner
+                * solution.
+                */
+               ret = regmap_write(data->regmap, ADXL313_REG_FIFO_CTL,
+                                  FIELD_PREP(ADXL313_REG_FIFO_CTL_MODE_MSK,
+                                             ADXL313_FIFO_BYPASS));
+               if (ret)
+                       return ret;
+       } else {
+               /* FIFO_STREAM mode */
+               int_map_msk = ADXL313_INT_DREADY | ADXL313_INT_ACTIVITY |
+                       ADXL313_INT_INACTIVITY | ADXL313_INT_WATERMARK |
+                       ADXL313_INT_OVERRUN;
+               ret = regmap_assign_bits(data->regmap, ADXL313_REG_INT_MAP,
+                                        int_map_msk, int_line == ADXL313_INT2);
+               if (ret)
+                       return ret;
+
+               ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
+                                                 &adxl313_buffer_ops);
+               if (ret)
+                       return ret;
+
+               ret = devm_request_threaded_irq(dev, irq, NULL,
+                                               &adxl313_irq_handler,
+                                               IRQF_SHARED | IRQF_ONESHOT,
+                                               indio_dev->name, indio_dev);
+               if (ret)
+                       return ret;
+       }
+
        return devm_iio_device_register(dev, indio_dev);
 }
 EXPORT_SYMBOL_NS_GPL(adxl313_core_probe, "IIO_ADXL313");