Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
// SPDX-License-Identifier: GPL-2.0+
/*
 * STMicroelectronics st_lsm6dsr FIFO buffer library driver
 *
 * Copyright 2020 STMicroelectronics Inc.
 *
 * Lorenzo Bianconi <lorenzo.bianconi@st.com>
 */
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/iio/iio.h>
#include <linux/iio/kfifo_buf.h>
#include <linux/iio/events.h>
#include <asm/unaligned.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/buffer.h>
#include "st_lsm6dsr.h"

#define ST_LSM6DSR_REG_EMB_FUNC_STATUS_MAINPAGE		0x35
#define ST_LSM6DSR_REG_INT_STEP_DET_MASK		BIT(3)
#define ST_LSM6DSR_REG_INT_TILT_MASK			BIT(4)
#define ST_LSM6DSR_REG_INT_SIGMOT_MASK			BIT(5)
#define ST_LSM6DSR_REG_INT_GLANCE_MASK			BIT(0)
#define ST_LSM6DSR_REG_INT_MOTION_MASK			BIT(1)
#define ST_LSM6DSR_REG_INT_NO_MOTION_MASK		BIT(2)
#define ST_LSM6DSR_REG_INT_WAKEUP_MASK			BIT(3)
#define ST_LSM6DSR_REG_INT_PICKUP_MASK			BIT(4)
#define ST_LSM6DSR_REG_INT_ORIENTATION_MASK		BIT(5)
#define ST_LSM6DSR_REG_INT_WRIST_MASK			BIT(6)

#define ST_LSM6DSR_SAMPLE_DISCHARD			0x7ffd

#define ST_LSM6DSR_EWMA_LEVEL				120
#define ST_LSM6DSR_EWMA_DIV				128

enum {
	ST_LSM6DSR_GYRO_TAG = 0x01,
	ST_LSM6DSR_ACC_TAG = 0x02,
	ST_LSM6DSR_TEMP_TAG = 0x03,
	ST_LSM6DSR_TS_TAG = 0x04,
	ST_LSM6DSR_EXT0_TAG = 0x0f,
	ST_LSM6DSR_EXT1_TAG = 0x10,
	ST_LSM6DSR_SC_TAG = 0x12,
};

/**
 * Get Linux timestamp (SW)
 *
 * @return  timestamp in ns
 */
static inline s64 st_lsm6dsr_get_time_ns(void)
{
	return ktime_to_ns(ktime_get_boottime());
}

/**
 * Timestamp low pass filter
 *
 * @param  old: ST IMU MEMS hw instance
 * @param  new: ST IMU MEMS hw instance
 * @param  weight: ST IMU MEMS hw instance
 * @return  estimation of the timestamp average
 */
static inline s64 st_lsm6dsr_ewma(s64 old, s64 new, int weight)
{
	s64 diff, incr;

	diff = new - old;
	incr = div_s64((ST_LSM6DSR_EWMA_DIV - weight) * diff,
			ST_LSM6DSR_EWMA_DIV);

	return old + incr;
}

/**
 * Reset HW Timestamp counter and clear timestamp data structure
 *
 * @param  hw: ST IMU MEMS hw instance
 * @return  < 0 if error, 0 otherwise
 */
inline int st_lsm6dsr_reset_hwts(struct st_lsm6dsr_hw *hw)
{
	u8 data = 0xaa;

	hw->ts = st_lsm6dsr_get_time_ns();
	hw->ts_offset = hw->ts;
	hw->val_ts_old = 0;
	hw->hw_ts_high = 0;
	hw->tsample = 0ull;

	return st_lsm6dsr_write_atomic(hw, ST_LSM6DSR_REG_TIMESTAMP2_ADDR,
				       sizeof(data), &data);
}

/**
 * Setting FIFO mode
 *
 * @param  hw: ST IMU MEMS hw instance
 * @param  fifo_mode: ST_LSM6DSR_FIFO_BYPASS or ST_LSM6DSR_FIFO_CONT
 * @return  0 FIFO configured accordingly, non zero otherwise
 */
int st_lsm6dsr_set_fifo_mode(struct st_lsm6dsr_hw *hw,
			     enum st_lsm6dsr_fifo_mode fifo_mode)
{
	int err;

	err = st_lsm6dsr_write_with_mask(hw, ST_LSM6DSR_REG_FIFO_CTRL4_ADDR,
					 ST_LSM6DSR_REG_FIFO_MODE_MASK,
					 fifo_mode);
	if (err < 0)
		return err;

	hw->fifo_mode = fifo_mode;

	return 0;
}

/**
 * Setting sensor ODR in batching mode
 *
 * @param  sensor: ST IMU sensor instance
 * @param  enable: enable or disable batching mode
 * @return  0 FIFO configured accordingly, non zero otherwise
 */
int __st_lsm6dsr_set_sensor_batching_odr(struct st_lsm6dsr_sensor *sensor,
					 bool enable)
{
	struct st_lsm6dsr_hw *hw = sensor->hw;
	u8 data = 0;
	int err;
	int podr, puodr;

	if (enable) {
		err = st_lsm6dsr_get_odr_val(sensor->id, sensor->odr,
					     sensor->uodr, &podr, &puodr,
					     &data);
		if (err < 0)
			return err;
	}

	err = __st_lsm6dsr_write_with_mask(hw, sensor->batch_reg.addr,
					   sensor->batch_reg.mask, data);
	return err < 0 ? err : 0;
}

/**
 * Setting timestamp ODR in batching mode
 *
 * @param  hw: ST IMU MEMS hw instance
 * @return  Timestamp ODR
 */
static int st_lsm6dsr_ts_odr(struct st_lsm6dsr_hw *hw)
{
	struct st_lsm6dsr_sensor *sensor;
	int odr = 0;
	u8 i;

	for (i = ST_LSM6DSR_ID_GYRO; i <= ST_LSM6DSR_ID_EXT1; i++) {
		if (!hw->iio_devs[i])
			continue;

		sensor = iio_priv(hw->iio_devs[i]);
		if (hw->enable_mask & BIT(sensor->id))
			odr = max_t(int, odr, sensor->odr);
	}

	return odr;
}

/**
 * Setting sensor ODR in batching mode
 *
 * @param  sensor: ST IMU sensor instance
 * @param  enable: enable or disable batching mode
 * @return  0 FIFO configured accordingly, non zero otherwise
 */
static inline int
st_lsm6dsr_set_sensor_batching_odr(struct st_lsm6dsr_sensor *sensor,
				   bool enable)
{
	struct st_lsm6dsr_hw *hw = sensor->hw;
	int err;

	mutex_lock(&hw->page_lock);
	err = __st_lsm6dsr_set_sensor_batching_odr(sensor, enable);
	mutex_unlock(&hw->page_lock);

	return err;
}

/**
 * Update watermark level in FIFO
 *
 * @param  sensor: ST IMU sensor instance
 * @param  watermark: New watermark level
 * @return  0 if FIFO configured, non zero for error
 */
int st_lsm6dsr_update_watermark(struct st_lsm6dsr_sensor *sensor,
				u16 watermark)
{
	u16 fifo_watermark = ST_LSM6DSR_MAX_FIFO_DEPTH, cur_watermark = 0;
	struct st_lsm6dsr_hw *hw = sensor->hw;
	struct st_lsm6dsr_sensor *cur_sensor;
	__le16 wdata;
	int i, err;
	u8 data;

	for (i = ST_LSM6DSR_ID_GYRO; i <= ST_LSM6DSR_ID_STEP_COUNTER; i++) {
		if (!hw->iio_devs[i])
			continue;

		cur_sensor = iio_priv(hw->iio_devs[i]);

		if (!(hw->enable_mask & BIT(cur_sensor->id)))
			continue;

		cur_watermark = (cur_sensor == sensor) ? watermark :
							 cur_sensor->watermark;

		fifo_watermark = min_t(u16, fifo_watermark, cur_watermark);
	}

	fifo_watermark = max_t(u16, fifo_watermark, 2);

	err = st_lsm6dsr_read_atomic(hw, ST_LSM6DSR_REG_FIFO_CTRL1_ADDR + 1,
				     sizeof(data), &data);
	if (err < 0)
		goto out;

	fifo_watermark = ((data << 8) & ~ST_LSM6DSR_REG_FIFO_WTM_MASK) |
			 (fifo_watermark & ST_LSM6DSR_REG_FIFO_WTM_MASK);
	wdata = cpu_to_le16(fifo_watermark);
	err = st_lsm6dsr_write_atomic(hw, ST_LSM6DSR_REG_FIFO_CTRL1_ADDR,
				      sizeof(wdata), (u8 *)&wdata);
out:
	return err < 0 ? err : 0;
}

/**
 * Timestamp correlation finction
 *
 * @param  hw: ST IMU MEMS hw instance
 * @param  ts: New timestamp
 */
static inline void st_lsm6dsr_sync_hw_ts(struct st_lsm6dsr_hw *hw, s64 ts)
{
	s64 delta = ts - hw->hw_ts;

	hw->ts_offset = st_lsm6dsr_ewma(hw->ts_offset, delta,
					ST_LSM6DSR_EWMA_LEVEL);
}

/**
 * Return the iio device structure based on FIFO TAG ID
 *
 * @param  hw: ST IMU MEMS hw instance
 * @param  tag: FIFO sample TAG ID
 * @return  0 if FIFO configured, non zero for error
 */
static struct
iio_dev *st_lsm6dsr_get_iiodev_from_tag(struct st_lsm6dsr_hw *hw,
					u8 tag)
{
	struct iio_dev *iio_dev;

	switch (tag) {
	case ST_LSM6DSR_GYRO_TAG:
		iio_dev = hw->iio_devs[ST_LSM6DSR_ID_GYRO];
		break;
	case ST_LSM6DSR_ACC_TAG:
		iio_dev = hw->iio_devs[ST_LSM6DSR_ID_ACC];
		break;
	case ST_LSM6DSR_TEMP_TAG:
		iio_dev = hw->iio_devs[ST_LSM6DSR_ID_TEMP];
		break;
	case ST_LSM6DSR_EXT0_TAG:
		if (hw->enable_mask & BIT(ST_LSM6DSR_ID_EXT0))
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_EXT0];
		else
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_EXT1];
		break;
	case ST_LSM6DSR_EXT1_TAG:
		iio_dev = hw->iio_devs[ST_LSM6DSR_ID_EXT1];
		break;
	case ST_LSM6DSR_SC_TAG:
		iio_dev = hw->iio_devs[ST_LSM6DSR_ID_STEP_COUNTER];
		break;
	default:
		iio_dev = NULL;
		break;
	}

	return iio_dev;
}

/**
 * Read all FIFO data stored after WTM FIFO irq fired interrupt
 *
 * @param hw: ST IMU MEMS hw instance
 * @return Number of read bytes in FIFO or error if negative
 */
static int st_lsm6dsr_read_fifo(struct st_lsm6dsr_hw *hw)
{
	u8 iio_buf[ALIGN(ST_LSM6DSR_SAMPLE_SIZE, sizeof(s64)) + sizeof(s64)];
	/* acc + gyro + 2 ext + ts + sc */
	u8 buf[6 * ST_LSM6DSR_FIFO_SAMPLE_SIZE], tag, *ptr;
	int i, err, word_len, fifo_len, read_len;
	struct st_lsm6dsr_sensor *sensor;
	struct iio_dev *iio_dev;
	s64 ts_irq, hw_ts_old;
	__le16 fifo_status;
	u16 fifo_depth;
	s16 drdymask;
	u32 val;

	ts_irq = hw->ts - hw->delta_ts;

	err = st_lsm6dsr_read_atomic(hw, ST_LSM6DSR_REG_FIFO_STATUS1_ADDR,
				     sizeof(fifo_status), (u8 *)&fifo_status);
	if (err < 0)
		return err;

	fifo_depth = le16_to_cpu(fifo_status) & ST_LSM6DSR_REG_FIFO_STATUS_DIFF;
	if (!fifo_depth)
		return 0;

	fifo_len = fifo_depth * ST_LSM6DSR_FIFO_SAMPLE_SIZE;
	read_len = 0;

	while (read_len < fifo_len) {
		word_len = min_t(int, fifo_len - read_len, sizeof(buf));
		err = st_lsm6dsr_read_atomic(hw,
					ST_LSM6DSR_REG_FIFO_DATA_OUT_TAG_ADDR,
					word_len, buf);
		if (err < 0)
			return err;

		for (i = 0; i < word_len; i += ST_LSM6DSR_FIFO_SAMPLE_SIZE) {
			ptr = &buf[i + ST_LSM6DSR_TAG_SIZE];
			tag = buf[i] >> 3;

			if (tag == ST_LSM6DSR_TS_TAG) {
				val = get_unaligned_le32(ptr);

				if (hw->val_ts_old > val)
					hw->hw_ts_high++;

				hw_ts_old = hw->hw_ts;

				/* check hw rollover */
				hw->val_ts_old = val;
				hw->hw_ts = (val +
					     ((s64)hw->hw_ts_high << 32)) *
					     hw->ts_delta_ns;
				hw->ts_offset = st_lsm6dsr_ewma(hw->ts_offset,
						ts_irq - hw->hw_ts,
						ST_LSM6DSR_EWMA_LEVEL);

				if (!test_bit(ST_LSM6DSR_HW_FLUSH, &hw->state))
					/* sync ap timestamp and sensor one */
					st_lsm6dsr_sync_hw_ts(hw, ts_irq);

				ts_irq += hw->hw_ts;

				if (!hw->tsample)
					hw->tsample = hw->ts_offset + hw->hw_ts;
				else
					hw->tsample = hw->tsample +
						      hw->hw_ts - hw_ts_old;
			} else {
				iio_dev =
					st_lsm6dsr_get_iiodev_from_tag(hw, tag);
				if (!iio_dev)
					continue;

				sensor = iio_priv(iio_dev);

				/* skip samples if not ready */
				drdymask =
				      (s16)le16_to_cpu(get_unaligned_le16(ptr));
				if (unlikely(drdymask >=
						ST_LSM6DSR_SAMPLE_DISCHARD)) {
					continue;
				}

				/*
				 * hw ts in not queued in FIFO if only step
				 * counter enabled
				 */
				if (sensor->id == ST_LSM6DSR_ID_STEP_COUNTER) {
					val = get_unaligned_le32(ptr + 2);
					hw->tsample = (val +
						((s64)hw->hw_ts_high << 32)) *
						hw->ts_delta_ns;

					/* avoid samples in the future */
					hw->tsample = min_t(s64,
							    st_lsm6dsr_get_time_ns(),
							    hw->tsample);
				} else {
					hw->tsample = st_lsm6dsr_get_time_ns();
				}

				memcpy(iio_buf, ptr, ST_LSM6DSR_SAMPLE_SIZE);

				sensor->last_fifo_timestamp = hw->tsample;

				/* support decimation for ODR < 12.5 Hz */
				if (sensor->dec_counter > 0) {
					sensor->dec_counter--;
				} else {
					sensor->dec_counter = sensor->decimator;
					iio_push_to_buffers_with_timestamp(iio_dev,
								   iio_buf,
								   hw->tsample);
				}
			}
		}
		read_len += word_len;
	}

	return read_len;
}

/**
 * Report events after WTM FIFO irq fired interrupt
 *
 * @param hw: ST IMU MEMS hw instance
 * @return 0 if OK, non zero for error
 */
static int st_lsm6dsr_report_events(struct st_lsm6dsr_hw *hw)
{
	struct iio_dev *iio_dev;
	u8 status[3];
	s64 event;
	int err;

	if (hw->enable_mask & (BIT(ST_LSM6DSR_ID_STEP_DETECTOR) |
			       BIT(ST_LSM6DSR_ID_SIGN_MOTION) |
			       BIT(ST_LSM6DSR_ID_TILT) |
			       BIT(ST_LSM6DSR_ID_MOTION) |
			       BIT(ST_LSM6DSR_ID_NO_MOTION) |
			       BIT(ST_LSM6DSR_ID_WAKEUP) |
			       BIT(ST_LSM6DSR_ID_PICKUP) |
			       BIT(ST_LSM6DSR_ID_ORIENTATION) |
			       BIT(ST_LSM6DSR_ID_WRIST_TILT) |
			       BIT(ST_LSM6DSR_ID_GLANCE))) {

		err = hw->tf->read(hw->dev,
				   ST_LSM6DSR_REG_EMB_FUNC_STATUS_MAINPAGE,
				   sizeof(status), status);
		if (err < 0)
			return err;

		/* embedded function sensors */
		if (status[0] & ST_LSM6DSR_REG_INT_STEP_DET_MASK) {
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_STEP_DETECTOR];
			event = IIO_UNMOD_EVENT_CODE(IIO_STEP_DETECTOR, -1,
						     IIO_EV_TYPE_THRESH,
						     IIO_EV_DIR_RISING);
			iio_push_event(iio_dev, event,
				       st_lsm6dsr_get_time_ns());
		}
		if (status[0] & ST_LSM6DSR_REG_INT_SIGMOT_MASK) {
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_SIGN_MOTION];
			event = IIO_UNMOD_EVENT_CODE(IIO_SIGN_MOTION, -1,
						     IIO_EV_TYPE_THRESH,
						     IIO_EV_DIR_RISING);
			iio_push_event(iio_dev, event,
				       st_lsm6dsr_get_time_ns());
		}
		if (status[0] & ST_LSM6DSR_REG_INT_TILT_MASK) {
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_TILT];
			event = IIO_UNMOD_EVENT_CODE(IIO_TILT, -1,
						     IIO_EV_TYPE_THRESH,
						     IIO_EV_DIR_RISING);
			iio_push_event(iio_dev, event,
				       st_lsm6dsr_get_time_ns());
		}
		/*  fsm sensors */
		if (status[1] & ST_LSM6DSR_REG_INT_GLANCE_MASK) {
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_GLANCE];
			event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
						     IIO_EV_TYPE_THRESH,
						     IIO_EV_DIR_RISING);
			iio_push_event(iio_dev, event,
				       st_lsm6dsr_get_time_ns());
		}
		if (status[1] & ST_LSM6DSR_REG_INT_MOTION_MASK) {
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_MOTION];
			event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
						     IIO_EV_TYPE_THRESH,
						     IIO_EV_DIR_RISING);
			iio_push_event(iio_dev, event,
				       st_lsm6dsr_get_time_ns());
		}
		if (status[1] & ST_LSM6DSR_REG_INT_NO_MOTION_MASK) {
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_NO_MOTION];
			event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
						     IIO_EV_TYPE_THRESH,
						     IIO_EV_DIR_RISING);
			iio_push_event(iio_dev, event,
				       st_lsm6dsr_get_time_ns());
		}
		if (status[1] & ST_LSM6DSR_REG_INT_WAKEUP_MASK) {
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_WAKEUP];
			event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
						     IIO_EV_TYPE_THRESH,
						     IIO_EV_DIR_RISING);
			iio_push_event(iio_dev, event,
				       st_lsm6dsr_get_time_ns());
		}
		if (status[1] & ST_LSM6DSR_REG_INT_PICKUP_MASK) {
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_PICKUP];
			event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
						     IIO_EV_TYPE_THRESH,
						     IIO_EV_DIR_RISING);
			iio_push_event(iio_dev, event,
				       st_lsm6dsr_get_time_ns());
		}
		if (status[1] & ST_LSM6DSR_REG_INT_ORIENTATION_MASK) {
			struct st_lsm6dsr_sensor *sensor;

			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_ORIENTATION];
			sensor = iio_priv(iio_dev);
			iio_trigger_poll_chained(sensor->trig);
		}
		if (status[1] & ST_LSM6DSR_REG_INT_WRIST_MASK) {
			iio_dev = hw->iio_devs[ST_LSM6DSR_ID_WRIST_TILT];
			event = IIO_UNMOD_EVENT_CODE(IIO_GESTURE, -1,
						     IIO_EV_TYPE_THRESH,
						     IIO_EV_DIR_RISING);
			iio_push_event(iio_dev, event,
				       st_lsm6dsr_get_time_ns());
		}
	}

	return 0;
}

/**
 * Return the max FIFO watermark level accepted
 *
 * @param  dev: Linux Device
 * @param  attr: Device Attribute
 * @param  buf: User Buffer
 * @return  Number of chars printed into the buffer
 */
ssize_t st_lsm6dsr_get_max_watermark(struct device *dev,
				     struct device_attribute *attr,
				     char *buf)
{
	struct iio_dev *iio_dev = dev_get_drvdata(dev);
	struct st_lsm6dsr_sensor *sensor = iio_priv(iio_dev);

	return sprintf(buf, "%d\n", sensor->max_watermark);
}

/**
 * Return the FIFO watermark level
 *
 * @param  dev: Linux Device
 * @param  attr: Device Attribute
 * @param  buf: User Buffer
 * @return  Number of chars printed into the buffer
 */
ssize_t st_lsm6dsr_get_watermark(struct device *dev,
				 struct device_attribute *attr,
				 char *buf)
{
	struct iio_dev *iio_dev = dev_get_drvdata(dev);
	struct st_lsm6dsr_sensor *sensor = iio_priv(iio_dev);

	return sprintf(buf, "%d\n", sensor->watermark);
}

/**
 * Set the FIFO watermark level
 *
 * @param  dev: Linux Device
 * @param  attr: Device Attribute
 * @param  buf: User Buffer
 * @param  size: New FIFO watermark level
 * @return  Watermark level if >= 0, error otherwise
 */
ssize_t st_lsm6dsr_set_watermark(struct device *dev,
				 struct device_attribute *attr,
				 const char *buf, size_t size)
{
	struct iio_dev *iio_dev = dev_get_drvdata(dev);
	struct st_lsm6dsr_sensor *sensor = iio_priv(iio_dev);
	int err, val;

	mutex_lock(&iio_dev->mlock);

	err = kstrtoint(buf, 10, &val);
	if (err < 0)
		goto out;

	err = st_lsm6dsr_update_watermark(sensor, val);
	if (err < 0)
		goto out;

	sensor->watermark = val;

out:
	mutex_unlock(&iio_dev->mlock);

	return err < 0 ? err : size;
}

/**
 * Flush internal HW FIFO
 *
 * @param  dev: Linux Device
 * @param  attr: Device Attribute
 * @param  buf: User Buffer
 * @param  size: unused
 * @return  Watermark level if >= 0, error otherwise
 */
ssize_t st_lsm6dsr_flush_fifo(struct device *dev,
			      struct device_attribute *attr,
			      const char *buf, size_t size)
{
	struct iio_dev *iio_dev = dev_get_drvdata(dev);
	struct st_lsm6dsr_sensor *sensor = iio_priv(iio_dev);
	struct st_lsm6dsr_hw *hw = sensor->hw;
	int count;
	s64 ts;
#ifdef CONFIG_NO_GKI
	s64 type;
	s64 event;
	s64 fts;
#endif

	mutex_lock(&hw->fifo_lock);
	ts = st_lsm6dsr_get_time_ns();
	hw->delta_ts = ts - hw->ts;
	hw->ts = ts;
	set_bit(ST_LSM6DSR_HW_FLUSH, &hw->state);
	count = st_lsm6dsr_read_fifo(hw);
	sensor->dec_counter = 0;
	mutex_unlock(&hw->fifo_lock);

#ifdef CONFIG_NO_GKI
	if (count > 0)
		fts = sensor->last_fifo_timestamp;
	else
		fts = ts;

	type = count > 0 ? IIO_EV_DIR_FIFO_DATA : IIO_EV_DIR_FIFO_EMPTY;
	event = IIO_UNMOD_EVENT_CODE(iio_dev->channels[0].type, -1,
				     IIO_EV_TYPE_FIFO_FLUSH, type);
	iio_push_event(iio_dev, event, fts);
#endif

	return size;
}

/**
 * Empty FIFO and set HW FIFO in Bypass mode
 *
 * @param  hw: ST IMU MEMS hw instance
 * @return  Watermark level if >= 0, error otherwise
 */
int st_lsm6dsr_suspend_fifo(struct st_lsm6dsr_hw *hw)
{
	int err;

	mutex_lock(&hw->fifo_lock);
	st_lsm6dsr_read_fifo(hw);
	err = st_lsm6dsr_set_fifo_mode(hw, ST_LSM6DSR_FIFO_BYPASS);
	mutex_unlock(&hw->fifo_lock);

	return err;
}

/**
 * Update ODR batching in FIFO and Timestamp
 *
 * @param  iio_dev: Linux IIO device
 * @param  enable: enable/disable batcing in FIFO
 * @return  < 0 if error, 0 otherwise
 */
int st_lsm6dsr_update_batching(struct iio_dev *iio_dev, bool enable)
{
	struct st_lsm6dsr_sensor *sensor = iio_priv(iio_dev);
	struct st_lsm6dsr_hw *hw = sensor->hw;
	int err;

	disable_irq(hw->irq);

	err = st_lsm6dsr_set_sensor_batching_odr(sensor, enable);
	if (err < 0)
		goto out;

	/* Calc TS ODR */
	hw->odr = st_lsm6dsr_ts_odr(hw);

out:
	enable_irq(hw->irq);

	return err;
}

/**
 * Update FIFO watermark value based to the enabled sensors
 *
 * @param  iio_dev: Linux IIO device
 * @param  enable: enable/disable batcing in FIFO
 * @return  < 0 if error, 0 otherwise
 */
static int st_lsm6dsr_update_fifo(struct iio_dev *iio_dev, bool enable)
{
	struct st_lsm6dsr_sensor *sensor = iio_priv(iio_dev);
	struct st_lsm6dsr_hw *hw = sensor->hw;
	int err;
	int podr, puodr;

	disable_irq(hw->irq);

	if (sensor->id == ST_LSM6DSR_ID_EXT0 ||
	    sensor->id == ST_LSM6DSR_ID_EXT1) {
		err = st_lsm6dsr_shub_set_enable(sensor, enable);
		if (err < 0)
			goto out;
	} else {
		if (sensor->id == ST_LSM6DSR_ID_STEP_COUNTER) {
			err = st_lsm6dsr_step_counter_set_enable(sensor,
								 enable);
			if (err < 0)
				goto out;
		} else {
			err = st_lsm6dsr_sensor_set_enable(sensor, enable);
			if (err < 0)
				goto out;

			err = st_lsm6dsr_set_sensor_batching_odr(sensor,
								 enable);
			if (err < 0)
				goto out;
		}
	}

	/*
	 * this is an auxiliary sensor, it need to get batched
	 * together at least with a primary sensor (Acc/Gyro)
	 */
	if (sensor->id == ST_LSM6DSR_ID_TEMP) {
		if (!(hw->enable_mask & (BIT(ST_LSM6DSR_ID_ACC) |
					 BIT(ST_LSM6DSR_ID_GYRO)))) {
			struct st_lsm6dsr_sensor *acc_sensor;
			u8 data = 0;

			acc_sensor = iio_priv(hw->iio_devs[ST_LSM6DSR_ID_ACC]);
			if (enable) {
				err = st_lsm6dsr_get_odr_val(ST_LSM6DSR_ID_ACC,
						sensor->odr, sensor->uodr,
						&podr, &puodr, &data);
				if (err < 0)
					goto out;
			}

			err = st_lsm6dsr_write_with_mask(hw,
					acc_sensor->batch_reg.addr,
					acc_sensor->batch_reg.mask,
					data);
			if (err < 0)
				goto out;
		}
	}

	err = st_lsm6dsr_update_watermark(sensor, sensor->watermark);
	if (err < 0)
		goto out;

	/* Calc TS ODR */
	hw->odr = st_lsm6dsr_ts_odr(hw);

	if (enable && hw->fifo_mode == ST_LSM6DSR_FIFO_BYPASS) {
		st_lsm6dsr_reset_hwts(hw);
		err = st_lsm6dsr_set_fifo_mode(hw, ST_LSM6DSR_FIFO_CONT);
	} else if (!hw->enable_mask) {
		err = st_lsm6dsr_set_fifo_mode(hw, ST_LSM6DSR_FIFO_BYPASS);
	}

out:
	enable_irq(hw->irq);

	return err;
}

/**
 * Bottom handler for FSM Orientation sensor event generation
 *
 * @param  irq: IIO trigger irq number
 * @param  p: iio poll function environment
 * @return  IRQ_HANDLED or < 0 for error
 */
static irqreturn_t st_lsm6dsr_buffer_handler_thread(int irq, void *p)
{
	struct iio_poll_func *pf = p;
	struct iio_dev *iio_dev = pf->indio_dev;
	struct st_lsm6dsr_sensor *sensor = iio_priv(iio_dev);
	u8 buffer[sizeof(u8) + sizeof(s64)];
	int err;

	err = st_lsm6dsr_fsm_get_orientation(sensor->hw, buffer);
	if (err < 0)
		goto out;

	iio_push_to_buffers_with_timestamp(iio_dev, buffer,
					   st_lsm6dsr_get_time_ns());
out:
	iio_trigger_notify_done(sensor->trig);

	return IRQ_HANDLED;
}

/**
 * Top handler for sensor event generation + FIFO management
 *
 * @param  irq: IIO trigger irq number
 * @param  private: iio poll function environment
 * @return  IRQ_HANDLED or < 0 for error
 */
static irqreturn_t st_lsm6dsr_handler_irq(int irq, void *private)
{
	struct st_lsm6dsr_hw *hw = (struct st_lsm6dsr_hw *)private;
	s64 ts = st_lsm6dsr_get_time_ns();

	hw->delta_ts = ts - hw->ts;
	hw->ts = ts;

	return IRQ_WAKE_THREAD;
}

/**
 * Bottom handler for sensor event generation + FIFO management
 *
 * @param  irq: irq line number
 * @param  private: device private environment pointer
 * @return  IRQ_HANDLED or < 0 for error
 */
static irqreturn_t st_lsm6dsr_handler_thread(int irq, void *private)
{
	struct st_lsm6dsr_hw *hw = (struct st_lsm6dsr_hw *)private;

	mutex_lock(&hw->fifo_lock);
	st_lsm6dsr_read_fifo(hw);
	clear_bit(ST_LSM6DSR_HW_FLUSH, &hw->state);
	mutex_unlock(&hw->fifo_lock);

	st_lsm6dsr_report_events(hw);

	return IRQ_HANDLED;
}

/**
 * IIO fifo pre enabled callback function
 *
 * @param  iio_dev: IIO device
 * @return  < 0 if error, 0 otherwise
 */
static int st_lsm6dsr_fifo_preenable(struct iio_dev *iio_dev)
{
	return st_lsm6dsr_update_fifo(iio_dev, true);
}

/**
 * IIO fifo post disable callback function
 *
 * @param  iio_dev: IIO device
 * @return  < 0 if error, 0 otherwise
 */
static int st_lsm6dsr_fifo_postdisable(struct iio_dev *iio_dev)
{
	return st_lsm6dsr_update_fifo(iio_dev, false);
}

/**
 * IIO fifo callback registruction structure
 */
static const struct iio_buffer_setup_ops st_lsm6dsr_fifo_ops = {
	.preenable = st_lsm6dsr_fifo_preenable,
	.postdisable = st_lsm6dsr_fifo_postdisable,
};

/**
 * Enable HW FIFO
 *
 * @param  hw: ST IMU MEMS hw instance
 * @return  < 0 if error, 0 otherwise
 */
static int st_lsm6dsr_fifo_init(struct st_lsm6dsr_hw *hw)
{
	return st_lsm6dsr_write_with_mask(hw,
					  ST_LSM6DSR_REG_FIFO_CTRL4_ADDR,
					  ST_LSM6DSR_REG_DEC_TS_MASK, 1);
}

static const struct iio_trigger_ops st_lsm6dsr_trigger_ops;

static int st_lsm6dsr_buffer_preenable(struct iio_dev *iio_dev)
{
	return st_lsm6dsr_embfunc_sensor_set_enable(iio_priv(iio_dev), true);
}

static int st_lsm6dsr_buffer_postdisable(struct iio_dev *iio_dev)
{
	return st_lsm6dsr_embfunc_sensor_set_enable(iio_priv(iio_dev), false);
}

static const struct iio_buffer_setup_ops st_lsm6dsr_buffer_ops = {
	.preenable = st_lsm6dsr_buffer_preenable,
	.postenable = NULL,
	.predisable = NULL,
	.postdisable = st_lsm6dsr_buffer_postdisable,
};


/**
 * Init IRQ
 *
 * @param  hw: ST IMU MEMS hw instance
 * @return  < 0 if error, 0 otherwise
 */
int st_lsm6dsr_irq_setup(struct st_lsm6dsr_hw *hw)
{
	unsigned long irq_type;
	bool irq_active_low;
	int err;

	irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq));
	if (irq_type == IRQF_TRIGGER_NONE)
		irq_type = IRQF_TRIGGER_HIGH;

	switch (irq_type) {
	case IRQF_TRIGGER_HIGH:
	case IRQF_TRIGGER_RISING:
		irq_active_low = false;
		break;
	case IRQF_TRIGGER_LOW:
	case IRQF_TRIGGER_FALLING:
		irq_active_low = true;
		break;
	default:
		dev_info(hw->dev, "mode %lx unsupported\n", irq_type);
		return -EINVAL;
	}

	err = st_lsm6dsr_write_with_mask(hw, ST_LSM6DSR_REG_CTRL3_C_ADDR,
					 ST_LSM6DSR_REG_H_LACTIVE_MASK,
					 irq_active_low);
	if (err < 0)
		return err;

	err = st_lsm6dsr_write_with_mask(hw, ST_LSM6DSR_REG_INT1_CTRL_ADDR,
					 ST_LSM6DSR_REG_INT_FIFO_TH_MASK, 1);
	if (err < 0)
		return err;

	return 0;
}

/**
 * Init IIO buffers and triggers
 *
 * @param  hw: ST IMU MEMS hw instance
 * @return  < 0 if error, 0 otherwise
 */
int st_lsm6dsr_buffers_setup(struct st_lsm6dsr_hw *hw)
{
	struct device_node *np = hw->dev->of_node;
	struct st_lsm6dsr_sensor *sensor;
	struct iio_buffer *buffer;
	struct iio_dev *iio_dev;
	unsigned long irq_type;
	int i, err;

	err = st_lsm6dsr_irq_setup(hw);
	if (err < 0)
		return err;

	irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq));
	if (irq_type == IRQF_TRIGGER_NONE)
		irq_type = IRQF_TRIGGER_HIGH;

	if (np && of_property_read_bool(np, "drive-open-drain")) {
		err = st_lsm6dsr_write_with_mask(hw,
						 ST_LSM6DSR_REG_CTRL3_C_ADDR,
						 ST_LSM6DSR_REG_PP_OD_MASK, 1);
		if (err < 0)
			return err;

		irq_type |= IRQF_SHARED;
	}

	err = devm_request_threaded_irq(hw->dev, hw->irq,
					st_lsm6dsr_handler_irq,
					st_lsm6dsr_handler_thread,
					irq_type | IRQF_ONESHOT,
					"lsm6dsr", hw);
	if (err) {
		dev_err(hw->dev, "failed to request trigger irq %d\n",
			hw->irq);
		return err;
	}

	for (i = ST_LSM6DSR_ID_GYRO; i <= ST_LSM6DSR_ID_SIGN_MOTION; i++) {
		if (!hw->iio_devs[i])
			continue;

		buffer = devm_iio_kfifo_allocate(hw->dev);
		if (!buffer)
			return -ENOMEM;

		iio_device_attach_buffer(hw->iio_devs[i], buffer);
		hw->iio_devs[i]->modes |= INDIO_BUFFER_SOFTWARE;
		hw->iio_devs[i]->setup_ops = &st_lsm6dsr_fifo_ops;
	}

	err = st_lsm6dsr_fifo_init(hw);
	if (err < 0)
		return err;

	iio_dev = hw->iio_devs[ST_LSM6DSR_ID_ORIENTATION];
	sensor = iio_priv(iio_dev);

	err = iio_triggered_buffer_setup(iio_dev,
				NULL, st_lsm6dsr_buffer_handler_thread,
				&st_lsm6dsr_buffer_ops);
	if (err < 0)
		return err;

	sensor->trig = devm_iio_trigger_alloc(hw->dev, "%s-trigger",
					      iio_dev->name);
	if (!sensor->trig)
		return -ENOMEM;

	iio_trigger_set_drvdata(sensor->trig, iio_dev);
	sensor->trig->ops = &st_lsm6dsr_trigger_ops;
	sensor->trig->dev.parent = hw->dev;
	sensor->trig->owner = THIS_MODULE;
	iio_dev->trig = iio_trigger_get(sensor->trig);

	err = iio_trigger_register(sensor->trig);
	if (err < 0)
		iio_triggered_buffer_cleanup(iio_dev);

	return err;
}

int st_lsm6dsr_deallocate_buffers(struct st_lsm6dsr_hw *hw)
{
	struct iio_dev *iio_dev = hw->iio_devs[ST_LSM6DSR_ID_ORIENTATION];
	struct st_lsm6dsr_sensor *sensor = iio_priv(iio_dev);

	iio_trigger_unregister(sensor->trig);
	iio_triggered_buffer_cleanup(iio_dev);

	return 0;
}