// SPDX-License-Identifier: GPL-2.0
/*
 * IIO core driver for Bosch BMI323 6-Axis IMU.
 *
 * Copyright (C) 2023, Jagath Jog J <jagathjog1996@gmail.com>
 *
 * Datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi323-ds000.pdf
 */

#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/units.h>

#include <linux/unaligned.h>

#include <linux/iio/buffer.h>
#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/trigger.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>

#include "bmi323.h"

enum bmi323_sensor_type {
	BMI323_ACCEL,
	BMI323_GYRO,
	BMI323_SENSORS_CNT,
};

enum bmi323_opr_mode {
	ACC_GYRO_MODE_DISABLE = 0x00,
	GYRO_DRIVE_MODE_ENABLED = 0x01,
	ACC_GYRO_MODE_DUTYCYCLE = 0x03,
	ACC_GYRO_MODE_CONTINOUS = 0x04,
	ACC_GYRO_MODE_HIGH_PERF = 0x07,
};

enum bmi323_state {
	BMI323_IDLE,
	BMI323_BUFFER_DRDY_TRIGGERED,
	BMI323_BUFFER_FIFO,
};

enum bmi323_irq_pin {
	BMI323_IRQ_DISABLED,
	BMI323_IRQ_INT1,
	BMI323_IRQ_INT2,
};

enum bmi323_3db_bw {
	BMI323_BW_ODR_BY_2,
	BMI323_BW_ODR_BY_4,
};

enum bmi323_scan {
	BMI323_ACCEL_X,
	BMI323_ACCEL_Y,
	BMI323_ACCEL_Z,
	BMI323_GYRO_X,
	BMI323_GYRO_Y,
	BMI323_GYRO_Z,
	BMI323_CHAN_MAX
};

struct bmi323_hw {
	u8 data;
	u8 config;
	const int (*scale_table)[2];
	int scale_table_len;
};

/*
 * The accelerometer supports +-2G/4G/8G/16G ranges, and the resolution of
 * each sample is 16 bits, signed.
 * At +-8G the scale can calculated by
 * ((8 + 8) * 9.80665 / (2^16 - 1)) * 10^6 = 2394.23819 scale in micro
 *
 */
static const int bmi323_accel_scale[][2] = {
	{ 0, 598 },
	{ 0, 1197 },
	{ 0, 2394 },
	{ 0, 4788 },
};

static const int bmi323_gyro_scale[][2] = {
	{ 0, 66 },
	{ 0, 133 },
	{ 0, 266 },
	{ 0, 532 },
	{ 0, 1065 },
};

static const int bmi323_accel_gyro_avrg[] = {0, 2, 4, 8, 16, 32, 64};

static const struct bmi323_hw bmi323_hw[2] = {
	[BMI323_ACCEL] = {
		.data = BMI323_ACCEL_X_REG,
		.config = BMI323_ACC_CONF_REG,
		.scale_table = bmi323_accel_scale,
		.scale_table_len = ARRAY_SIZE(bmi323_accel_scale),
	},
	[BMI323_GYRO] = {
		.data = BMI323_GYRO_X_REG,
		.config = BMI323_GYRO_CONF_REG,
		.scale_table = bmi323_gyro_scale,
		.scale_table_len = ARRAY_SIZE(bmi323_gyro_scale),
	},
};

static const unsigned int bmi323_reg_savestate[] = {
	BMI323_INT_MAP1_REG,
	BMI323_INT_MAP2_REG,
	BMI323_IO_INT_CTR_REG,
	BMI323_IO_INT_CONF_REG,
	BMI323_ACC_CONF_REG,
	BMI323_GYRO_CONF_REG,
	BMI323_FEAT_IO0_REG,
	BMI323_FIFO_WTRMRK_REG,
	BMI323_FIFO_CONF_REG
};

static const unsigned int bmi323_ext_reg_savestate[] = {
	BMI323_GEN_SET1_REG,
	BMI323_TAP1_REG,
	BMI323_TAP2_REG,
	BMI323_TAP3_REG,
	BMI323_FEAT_IO0_S_TAP_MSK,
	BMI323_STEP_SC1_REG,
	BMI323_ANYMO1_REG,
	BMI323_NOMO1_REG,
	BMI323_ANYMO1_REG + BMI323_MO2_OFFSET,
	BMI323_NOMO1_REG + BMI323_MO2_OFFSET,
	BMI323_ANYMO1_REG + BMI323_MO3_OFFSET,
	BMI323_NOMO1_REG + BMI323_MO3_OFFSET
};

struct bmi323_regs_runtime_pm {
	unsigned int reg_settings[ARRAY_SIZE(bmi323_reg_savestate)];
	unsigned int ext_reg_settings[ARRAY_SIZE(bmi323_ext_reg_savestate)];
};

struct bmi323_data {
	struct device *dev;
	struct regmap *regmap;
	struct iio_mount_matrix orientation;
	enum bmi323_irq_pin irq_pin;
	struct iio_trigger *trig;
	bool drdy_trigger_enabled;
	enum bmi323_state state;
	s64 fifo_tstamp, old_fifo_tstamp;
	u32 odrns[BMI323_SENSORS_CNT];
	u32 odrhz[BMI323_SENSORS_CNT];
	unsigned int feature_events;
	struct bmi323_regs_runtime_pm runtime_pm_status;

	/*
	 * Lock to protect the members of device's private data from concurrent
	 * access and also to serialize the access of extended registers.
	 * See bmi323_write_ext_reg(..) for more info.
	 */
	struct mutex mutex;
	int watermark;
	__le16 fifo_buff[BMI323_FIFO_FULL_IN_WORDS] __aligned(IIO_DMA_MINALIGN);
	struct {
		__le16 channels[BMI323_CHAN_MAX];
		s64 ts __aligned(8);
	} buffer;
	__le16 steps_count[BMI323_STEP_LEN];
};

static const struct iio_mount_matrix *
bmi323_get_mount_matrix(const struct iio_dev *idev,
			const struct iio_chan_spec *chan)
{
	struct bmi323_data *data = iio_priv(idev);

	return &data->orientation;
}

static const struct iio_chan_spec_ext_info bmi323_ext_info[] = {
	IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, bmi323_get_mount_matrix),
	{ }
};

static const struct iio_event_spec bmi323_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 bmi323_accel_event[] = {
	{
		.type = IIO_EV_TYPE_MAG,
		.dir = IIO_EV_DIR_FALLING,
		.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
				       BIT(IIO_EV_INFO_PERIOD) |
				       BIT(IIO_EV_INFO_HYSTERESIS) |
				       BIT(IIO_EV_INFO_ENABLE),
	},
	{
		.type = IIO_EV_TYPE_MAG,
		.dir = IIO_EV_DIR_RISING,
		.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
				       BIT(IIO_EV_INFO_PERIOD) |
				       BIT(IIO_EV_INFO_HYSTERESIS) |
				       BIT(IIO_EV_INFO_ENABLE),
	},
	{
		.type = IIO_EV_TYPE_GESTURE,
		.dir = IIO_EV_DIR_SINGLETAP,
		.mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
				       BIT(IIO_EV_INFO_VALUE) |
				       BIT(IIO_EV_INFO_RESET_TIMEOUT),
	},
	{
		.type = IIO_EV_TYPE_GESTURE,
		.dir = IIO_EV_DIR_DOUBLETAP,
		.mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
				       BIT(IIO_EV_INFO_VALUE) |
				       BIT(IIO_EV_INFO_RESET_TIMEOUT) |
				       BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
	},
};

#define BMI323_ACCEL_CHANNEL(_type, _axis, _index) {			\
	.type = _type,							\
	.modified = 1,							\
	.channel2 = IIO_MOD_##_axis,					\
	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),			\
	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |	\
				    BIT(IIO_CHAN_INFO_SCALE) |		\
				    BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
	.info_mask_shared_by_type_available =				\
				    BIT(IIO_CHAN_INFO_SAMP_FREQ) |	\
				    BIT(IIO_CHAN_INFO_SCALE) |		\
				    BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
	.scan_index = _index,						\
	.scan_type = {							\
		.sign = 's',						\
		.realbits = 16,						\
		.storagebits = 16,					\
		.endianness = IIO_LE,					\
	},								\
	.ext_info = bmi323_ext_info,					\
	.event_spec = bmi323_accel_event,				\
	.num_event_specs = ARRAY_SIZE(bmi323_accel_event),		\
}

#define BMI323_GYRO_CHANNEL(_type, _axis, _index) {			\
	.type = _type,							\
	.modified = 1,							\
	.channel2 = IIO_MOD_##_axis,					\
	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),			\
	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) |	\
				    BIT(IIO_CHAN_INFO_SCALE) |		\
				    BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
	.info_mask_shared_by_type_available =				\
				    BIT(IIO_CHAN_INFO_SAMP_FREQ) |	\
				    BIT(IIO_CHAN_INFO_SCALE) |		\
				    BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
	.scan_index = _index,						\
	.scan_type = {							\
		.sign = 's',						\
		.realbits = 16,						\
		.storagebits = 16,					\
		.endianness = IIO_LE,					\
	},								\
	.ext_info = bmi323_ext_info,					\
}

static const struct iio_chan_spec bmi323_channels[] = {
	BMI323_ACCEL_CHANNEL(IIO_ACCEL, X, BMI323_ACCEL_X),
	BMI323_ACCEL_CHANNEL(IIO_ACCEL, Y, BMI323_ACCEL_Y),
	BMI323_ACCEL_CHANNEL(IIO_ACCEL, Z, BMI323_ACCEL_Z),
	BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, X, BMI323_GYRO_X),
	BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Y, BMI323_GYRO_Y),
	BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Z, BMI323_GYRO_Z),
	{
		.type = IIO_TEMP,
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
				      BIT(IIO_CHAN_INFO_OFFSET) |
				      BIT(IIO_CHAN_INFO_SCALE),
		.scan_index = -1,
	},
	{
		.type = IIO_STEPS,
		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
				      BIT(IIO_CHAN_INFO_ENABLE),
		.scan_index = -1,
		.event_spec = &bmi323_step_wtrmrk_event,
		.num_event_specs = 1,

	},
	IIO_CHAN_SOFT_TIMESTAMP(BMI323_CHAN_MAX),
};

static const int bmi323_acc_gyro_odr[][2] = {
	{ 0, 781250 },
	{ 1, 562500 },
	{ 3, 125000 },
	{ 6, 250000 },
	{ 12, 500000 },
	{ 25, 0 },
	{ 50, 0 },
	{ 100, 0 },
	{ 200, 0 },
	{ 400, 0 },
	{ 800, 0 },
};

static const int bmi323_acc_gyro_odrns[] = {
	1280 * MEGA,
	640 * MEGA,
	320 * MEGA,
	160 * MEGA,
	80 * MEGA,
	40 * MEGA,
	20 * MEGA,
	10 * MEGA,
	5 * MEGA,
	2500 * KILO,
	1250 * KILO,
};

static enum bmi323_sensor_type bmi323_iio_to_sensor(enum iio_chan_type iio_type)
{
	switch (iio_type) {
	case IIO_ACCEL:
		return BMI323_ACCEL;
	case IIO_ANGL_VEL:
		return BMI323_GYRO;
	default:
		return -EINVAL;
	}
}

static int bmi323_set_mode(struct bmi323_data *data,
			   enum bmi323_sensor_type sensor,
			   enum bmi323_opr_mode mode)
{
	guard(mutex)(&data->mutex);
	return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
				  BMI323_ACC_GYRO_CONF_MODE_MSK,
				  FIELD_PREP(BMI323_ACC_GYRO_CONF_MODE_MSK,
					     mode));
}

/*
 * When writing data to extended register there must be no communication to
 * any other register before write transaction is complete.
 * See datasheet section 6.2 Extended Register Map Description.
 */
static int bmi323_write_ext_reg(struct bmi323_data *data, unsigned int ext_addr,
				unsigned int ext_data)
{
	int ret, feature_status;

	ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS,
			  &feature_status);
	if (ret)
		return ret;

	if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status))
		return -EBUSY;

	ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr);
	if (ret)
		return ret;

	return regmap_write(data->regmap, BMI323_FEAT_DATA_TX, ext_data);
}

/*
 * When reading data from extended register there must be no communication to
 * any other register before read transaction is complete.
 * See datasheet section 6.2 Extended Register Map Description.
 */
static int bmi323_read_ext_reg(struct bmi323_data *data, unsigned int ext_addr,
			       unsigned int *ext_data)
{
	int ret, feature_status;

	ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS,
			  &feature_status);
	if (ret)
		return ret;

	if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status))
		return -EBUSY;

	ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr);
	if (ret)
		return ret;

	return regmap_read(data->regmap, BMI323_FEAT_DATA_TX, ext_data);
}

static int bmi323_update_ext_reg(struct bmi323_data *data,
				 unsigned int ext_addr,
				 unsigned int mask, unsigned int ext_data)
{
	unsigned int value;
	int ret;

	ret = bmi323_read_ext_reg(data, ext_addr, &value);
	if (ret)
		return ret;

	set_mask_bits(&value, mask, ext_data);

	return bmi323_write_ext_reg(data, ext_addr, value);
}

static int bmi323_get_error_status(struct bmi323_data *data)
{
	int error, ret;

	guard(mutex)(&data->mutex);
	ret = regmap_read(data->regmap, BMI323_ERR_REG, &error);
	if (ret)
		return ret;

	if (error)
		dev_err(data->dev, "Sensor error 0x%x\n", error);

	return error;
}

static int bmi323_feature_engine_events(struct bmi323_data *data,
					const unsigned int event_mask,
					bool state)
{
	unsigned int value;
	int ret;

	ret = regmap_read(data->regmap, BMI323_FEAT_IO0_REG, &value);
	if (ret)
		return ret;

	/* Register must be cleared before changing an active config */
	ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, 0);
	if (ret)
		return ret;

	if (state)
		value |= event_mask;
	else
		value &= ~event_mask;

	ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, value);
	if (ret)
		return ret;

	return regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG,
			    BMI323_FEAT_IO_STATUS_MSK);
}

static int bmi323_step_wtrmrk_en(struct bmi323_data *data, int state)
{
	enum bmi323_irq_pin step_irq;
	int ret;

	guard(mutex)(&data->mutex);
	if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events))
		return -EINVAL;

	if (state)
		step_irq = data->irq_pin;
	else
		step_irq = BMI323_IRQ_DISABLED;

	ret = bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG,
				    BMI323_STEP_SC1_WTRMRK_MSK,
				    FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK,
					       state ? 1 : 0));
	if (ret)
		return ret;

	return regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG,
				  BMI323_STEP_CNT_MSK,
				  FIELD_PREP(BMI323_STEP_CNT_MSK, step_irq));
}

static int bmi323_motion_config_reg(enum iio_event_direction dir)
{
	switch (dir) {
	case IIO_EV_DIR_RISING:
		return BMI323_ANYMO1_REG;
	case IIO_EV_DIR_FALLING:
		return BMI323_NOMO1_REG;
	default:
		return -EINVAL;
	}
}

static int bmi323_motion_event_en(struct bmi323_data *data,
				  enum iio_event_direction dir, int state)
{
	unsigned int state_value = state ? BMI323_FEAT_XYZ_MSK : 0;
	int config, ret, msk, raw, field_value;
	enum bmi323_irq_pin motion_irq;
	int irq_msk, irq_field_val;

	if (state)
		motion_irq = data->irq_pin;
	else
		motion_irq = BMI323_IRQ_DISABLED;

	switch (dir) {
	case IIO_EV_DIR_RISING:
		msk = BMI323_FEAT_IO0_XYZ_MOTION_MSK;
		raw = 512;
		config = BMI323_ANYMO1_REG;
		irq_msk = BMI323_MOTION_MSK;
		irq_field_val = FIELD_PREP(BMI323_MOTION_MSK, motion_irq);
		field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_MOTION_MSK,
					 state_value);
		break;
	case IIO_EV_DIR_FALLING:
		msk = BMI323_FEAT_IO0_XYZ_NOMOTION_MSK;
		raw = 0;
		config = BMI323_NOMO1_REG;
		irq_msk = BMI323_NOMOTION_MSK;
		irq_field_val = FIELD_PREP(BMI323_NOMOTION_MSK, motion_irq);
		field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK,
					 state_value);
		break;
	default:
		return -EINVAL;
	}

	guard(mutex)(&data->mutex);
	ret = bmi323_feature_engine_events(data, msk, state);
	if (ret)
		return ret;

	ret = bmi323_update_ext_reg(data, config,
				    BMI323_MO1_REF_UP_MSK,
				    FIELD_PREP(BMI323_MO1_REF_UP_MSK, 0));
	if (ret)
		return ret;

	/* Set initial value to avoid interrupts while enabling*/
	ret = bmi323_update_ext_reg(data, config,
				    BMI323_MO1_SLOPE_TH_MSK,
				    FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK, raw));
	if (ret)
		return ret;

	ret = regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG, irq_msk,
				 irq_field_val);
	if (ret)
		return ret;

	set_mask_bits(&data->feature_events, msk, field_value);

	return 0;
}

static int bmi323_tap_event_en(struct bmi323_data *data,
			       enum iio_event_direction dir, int state)
{
	enum bmi323_irq_pin tap_irq;
	int ret, tap_enabled;

	guard(mutex)(&data->mutex);

	if (data->odrhz[BMI323_ACCEL] < 200) {
		dev_err(data->dev, "Invalid accelerometer parameter\n");
		return -EINVAL;
	}

	switch (dir) {
	case IIO_EV_DIR_SINGLETAP:
		ret = bmi323_feature_engine_events(data,
						   BMI323_FEAT_IO0_S_TAP_MSK,
						   state);
		if (ret)
			return ret;

		set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_S_TAP_MSK,
			      FIELD_PREP(BMI323_FEAT_IO0_S_TAP_MSK, state));
		break;
	case IIO_EV_DIR_DOUBLETAP:
		ret = bmi323_feature_engine_events(data,
						   BMI323_FEAT_IO0_D_TAP_MSK,
						   state);
		if (ret)
			return ret;

		set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_D_TAP_MSK,
			      FIELD_PREP(BMI323_FEAT_IO0_D_TAP_MSK, state));
		break;
	default:
		return -EINVAL;
	}

	tap_enabled = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK |
				BMI323_FEAT_IO0_D_TAP_MSK,
				data->feature_events);

	if (tap_enabled)
		tap_irq = data->irq_pin;
	else
		tap_irq = BMI323_IRQ_DISABLED;

	ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
				 BMI323_TAP_MSK,
				 FIELD_PREP(BMI323_TAP_MSK, tap_irq));
	if (ret)
		return ret;

	if (!state)
		return 0;

	ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG,
				    BMI323_TAP1_MAX_PEAKS_MSK,
				    FIELD_PREP(BMI323_TAP1_MAX_PEAKS_MSK,
					       0x04));
	if (ret)
		return ret;

	ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG,
				    BMI323_TAP1_AXIS_SEL_MSK,
				    FIELD_PREP(BMI323_TAP1_AXIS_SEL_MSK,
					       BMI323_AXIS_XYZ_MSK));
	if (ret)
		return ret;

	return bmi323_update_ext_reg(data, BMI323_TAP1_REG,
				     BMI323_TAP1_TIMOUT_MSK,
				     FIELD_PREP(BMI323_TAP1_TIMOUT_MSK,
						0));
}

static ssize_t in_accel_gesture_tap_wait_dur_show(struct device *dev,
						  struct device_attribute *attr,
						  char *buf)
{
	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
	struct bmi323_data *data = iio_priv(indio_dev);
	unsigned int reg_value, raw;
	int ret, val[2];

	scoped_guard(mutex, &data->mutex) {
		ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG, &reg_value);
		if (ret)
			return ret;
	}

	raw = FIELD_GET(BMI323_TAP2_MAX_DUR_MSK, reg_value);
	val[0] = raw / BMI323_MAX_GES_DUR_SCALE;
	val[1] = BMI323_RAW_TO_MICRO(raw, BMI323_MAX_GES_DUR_SCALE);

	return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(val),
				val);
}

static ssize_t in_accel_gesture_tap_wait_dur_store(struct device *dev,
						   struct device_attribute *attr,
						   const char *buf, size_t len)
{
	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
	struct bmi323_data *data = iio_priv(indio_dev);
	int ret, val_int, val_fract, raw;

	ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract);
	if (ret)
		return ret;

	raw = BMI323_INT_MICRO_TO_RAW(val_int, val_fract,
				      BMI323_MAX_GES_DUR_SCALE);
	if (!in_range(raw, 0, 64))
		return -EINVAL;

	guard(mutex)(&data->mutex);
	ret = bmi323_update_ext_reg(data, BMI323_TAP2_REG,
				    BMI323_TAP2_MAX_DUR_MSK,
				    FIELD_PREP(BMI323_TAP2_MAX_DUR_MSK, raw));
	if (ret)
		return ret;

	return len;
}

/*
 * Maximum duration from first tap within the second tap is expected to happen.
 * This timeout is applicable only if gesture_tap_wait_timeout is enabled.
 */
static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_dur, 0);

static ssize_t in_accel_gesture_tap_wait_timeout_show(struct device *dev,
						      struct device_attribute *attr,
						      char *buf)
{
	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
	struct bmi323_data *data = iio_priv(indio_dev);
	unsigned int reg_value, raw;
	int ret;

	scoped_guard(mutex, &data->mutex) {
		ret = bmi323_read_ext_reg(data, BMI323_TAP1_REG, &reg_value);
		if (ret)
			return ret;
	}

	raw = FIELD_GET(BMI323_TAP1_TIMOUT_MSK, reg_value);

	return iio_format_value(buf, IIO_VAL_INT, 1, &raw);
}

static ssize_t in_accel_gesture_tap_wait_timeout_store(struct device *dev,
						       struct device_attribute *attr,
						       const char *buf,
						       size_t len)
{
	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
	struct bmi323_data *data = iio_priv(indio_dev);
	bool val;
	int ret;

	ret = kstrtobool(buf, &val);
	if (ret)
		return ret;

	guard(mutex)(&data->mutex);
	ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG,
				    BMI323_TAP1_TIMOUT_MSK,
				    FIELD_PREP(BMI323_TAP1_TIMOUT_MSK, val));
	if (ret)
		return ret;

	return len;
}

/* Enable/disable gesture confirmation with wait time */
static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_timeout, 0);

static IIO_CONST_ATTR(in_accel_gesture_tap_wait_dur_available,
		      "[0.0 0.04 2.52]");

static IIO_CONST_ATTR(in_accel_gesture_doubletap_tap2_min_delay_available,
		      "[0.005 0.005 0.075]");

static IIO_CONST_ATTR(in_accel_gesture_tap_reset_timeout_available,
		      "[0.04 0.04 0.6]");

static IIO_CONST_ATTR(in_accel_gesture_tap_value_available, "[0.0 0.002 1.99]");

static IIO_CONST_ATTR(in_accel_mag_value_available, "[0.0 0.002 7.99]");

static IIO_CONST_ATTR(in_accel_mag_period_available, "[0.0 0.02 162.0]");

static IIO_CONST_ATTR(in_accel_mag_hysteresis_available, "[0.0 0.002 1.99]");

static struct attribute *bmi323_event_attributes[] = {
	&iio_const_attr_in_accel_gesture_tap_value_available.dev_attr.attr,
	&iio_const_attr_in_accel_gesture_tap_reset_timeout_available.dev_attr.attr,
	&iio_const_attr_in_accel_gesture_doubletap_tap2_min_delay_available.dev_attr.attr,
	&iio_const_attr_in_accel_gesture_tap_wait_dur_available.dev_attr.attr,
	&iio_dev_attr_in_accel_gesture_tap_wait_timeout.dev_attr.attr,
	&iio_dev_attr_in_accel_gesture_tap_wait_dur.dev_attr.attr,
	&iio_const_attr_in_accel_mag_value_available.dev_attr.attr,
	&iio_const_attr_in_accel_mag_period_available.dev_attr.attr,
	&iio_const_attr_in_accel_mag_hysteresis_available.dev_attr.attr,
	NULL
};

static const struct attribute_group bmi323_event_attribute_group = {
	.attrs = bmi323_event_attributes,
};

static int bmi323_write_event_config(struct iio_dev *indio_dev,
				     const struct iio_chan_spec *chan,
				     enum iio_event_type type,
				     enum iio_event_direction dir, int state)
{
	struct bmi323_data *data = iio_priv(indio_dev);

	switch (type) {
	case IIO_EV_TYPE_MAG:
		return bmi323_motion_event_en(data, dir, state);
	case IIO_EV_TYPE_GESTURE:
		return bmi323_tap_event_en(data, dir, state);
	case IIO_EV_TYPE_CHANGE:
		return bmi323_step_wtrmrk_en(data, state);
	default:
		return -EINVAL;
	}
}

static int bmi323_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 bmi323_data *data = iio_priv(indio_dev);
	int ret, value, reg_val;

	guard(mutex)(&data->mutex);

	switch (chan->type) {
	case IIO_ACCEL:
		switch (dir) {
		case IIO_EV_DIR_SINGLETAP:
			ret = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK,
					data->feature_events);
			break;
		case IIO_EV_DIR_DOUBLETAP:
			ret = FIELD_GET(BMI323_FEAT_IO0_D_TAP_MSK,
					data->feature_events);
			break;
		case IIO_EV_DIR_RISING:
			value = FIELD_GET(BMI323_FEAT_IO0_XYZ_MOTION_MSK,
					  data->feature_events);
			ret = value ? 1 : 0;
			break;
		case IIO_EV_DIR_FALLING:
			value = FIELD_GET(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK,
					  data->feature_events);
			ret = value ? 1 : 0;
			break;
		default:
			ret = -EINVAL;
			break;
		}
		return ret;
	case IIO_STEPS:
		ret = regmap_read(data->regmap, BMI323_INT_MAP1_REG, &reg_val);
		if (ret)
			return ret;

		return FIELD_GET(BMI323_STEP_CNT_MSK, reg_val) ? 1 : 0;
	default:
		return -EINVAL;
	}
}

static int bmi323_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 bmi323_data *data = iio_priv(indio_dev);
	unsigned int raw;
	int reg;

	guard(mutex)(&data->mutex);

	switch (type) {
	case IIO_EV_TYPE_GESTURE:
		switch (info) {
		case IIO_EV_INFO_VALUE:
			if (!in_range(val, 0, 2))
				return -EINVAL;

			raw = BMI323_INT_MICRO_TO_RAW(val, val2,
						      BMI323_TAP_THRES_SCALE);

			return bmi323_update_ext_reg(data, BMI323_TAP2_REG,
						     BMI323_TAP2_THRES_MSK,
						     FIELD_PREP(BMI323_TAP2_THRES_MSK,
								raw));
		case IIO_EV_INFO_RESET_TIMEOUT:
			if (val || !in_range(val2, 40000, 560001))
				return -EINVAL;

			raw = BMI323_INT_MICRO_TO_RAW(val, val2,
						      BMI323_QUITE_TIM_GES_SCALE);

			return bmi323_update_ext_reg(data, BMI323_TAP3_REG,
						     BMI323_TAP3_QT_AFT_GES_MSK,
						     FIELD_PREP(BMI323_TAP3_QT_AFT_GES_MSK,
								raw));
		case IIO_EV_INFO_TAP2_MIN_DELAY:
			if (val || !in_range(val2, 5000, 70001))
				return -EINVAL;

			raw = BMI323_INT_MICRO_TO_RAW(val, val2,
						      BMI323_DUR_BW_TAP_SCALE);

			return bmi323_update_ext_reg(data, BMI323_TAP3_REG,
						     BMI323_TAP3_QT_BW_TAP_MSK,
						     FIELD_PREP(BMI323_TAP3_QT_BW_TAP_MSK,
								raw));
		default:
			return -EINVAL;
		}
	case IIO_EV_TYPE_MAG:
		reg = bmi323_motion_config_reg(dir);
		if (reg < 0)
			return -EINVAL;

		switch (info) {
		case IIO_EV_INFO_VALUE:
			if (!in_range(val, 0, 8))
				return -EINVAL;

			raw = BMI323_INT_MICRO_TO_RAW(val, val2,
						      BMI323_MOTION_THRES_SCALE);

			return bmi323_update_ext_reg(data, reg,
						     BMI323_MO1_SLOPE_TH_MSK,
						     FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK,
								raw));
		case IIO_EV_INFO_PERIOD:
			if (!in_range(val, 0, 163))
				return -EINVAL;

			raw = BMI323_INT_MICRO_TO_RAW(val, val2,
						      BMI323_MOTION_DURAT_SCALE);

			return bmi323_update_ext_reg(data,
						     reg + BMI323_MO3_OFFSET,
						     BMI323_MO3_DURA_MSK,
						     FIELD_PREP(BMI323_MO3_DURA_MSK,
								raw));
		case IIO_EV_INFO_HYSTERESIS:
			if (!in_range(val, 0, 2))
				return -EINVAL;

			raw = BMI323_INT_MICRO_TO_RAW(val, val2,
						      BMI323_MOTION_HYSTR_SCALE);

			return bmi323_update_ext_reg(data,
						     reg + BMI323_MO2_OFFSET,
						     BMI323_MO2_HYSTR_MSK,
						     FIELD_PREP(BMI323_MO2_HYSTR_MSK,
								raw));
		default:
			return -EINVAL;
		}
	case IIO_EV_TYPE_CHANGE:
		if (!in_range(val, 0, 20461))
			return -EINVAL;

		raw = val / 20;
		return bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG,
					     BMI323_STEP_SC1_WTRMRK_MSK,
					     FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK,
							raw));
	default:
		return -EINVAL;
	}
}

static int bmi323_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 bmi323_data *data = iio_priv(indio_dev);
	unsigned int raw, reg_value;
	int ret, reg;

	guard(mutex)(&data->mutex);

	switch (type) {
	case IIO_EV_TYPE_GESTURE:
		switch (info) {
		case IIO_EV_INFO_VALUE:
			ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG,
						  &reg_value);
			if (ret)
				return ret;

			raw = FIELD_GET(BMI323_TAP2_THRES_MSK, reg_value);
			*val = raw / BMI323_TAP_THRES_SCALE;
			*val2 = BMI323_RAW_TO_MICRO(raw, BMI323_TAP_THRES_SCALE);
			return IIO_VAL_INT_PLUS_MICRO;
		case IIO_EV_INFO_RESET_TIMEOUT:
			ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG,
						  &reg_value);
			if (ret)
				return ret;

			raw = FIELD_GET(BMI323_TAP3_QT_AFT_GES_MSK, reg_value);
			*val = 0;
			*val2 = BMI323_RAW_TO_MICRO(raw,
						    BMI323_QUITE_TIM_GES_SCALE);
			return IIO_VAL_INT_PLUS_MICRO;
		case IIO_EV_INFO_TAP2_MIN_DELAY:
			ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG,
						  &reg_value);
			if (ret)
				return ret;

			raw = FIELD_GET(BMI323_TAP3_QT_BW_TAP_MSK, reg_value);
			*val = 0;
			*val2 = BMI323_RAW_TO_MICRO(raw,
						    BMI323_DUR_BW_TAP_SCALE);
			return IIO_VAL_INT_PLUS_MICRO;
		default:
			return -EINVAL;
		}
	case IIO_EV_TYPE_MAG:
		reg = bmi323_motion_config_reg(dir);
		if (reg < 0)
			return -EINVAL;

		switch (info) {
		case IIO_EV_INFO_VALUE:
			ret = bmi323_read_ext_reg(data, reg, &reg_value);
			if (ret)
				return ret;

			raw = FIELD_GET(BMI323_MO1_SLOPE_TH_MSK, reg_value);
			*val = raw / BMI323_MOTION_THRES_SCALE;
			*val2 = BMI323_RAW_TO_MICRO(raw,
						    BMI323_MOTION_THRES_SCALE);
			return IIO_VAL_INT_PLUS_MICRO;
		case IIO_EV_INFO_PERIOD:
			ret = bmi323_read_ext_reg(data,
						  reg + BMI323_MO3_OFFSET,
						  &reg_value);
			if (ret)
				return ret;

			raw = FIELD_GET(BMI323_MO3_DURA_MSK, reg_value);
			*val = raw / BMI323_MOTION_DURAT_SCALE;
			*val2 = BMI323_RAW_TO_MICRO(raw,
						    BMI323_MOTION_DURAT_SCALE);
			return IIO_VAL_INT_PLUS_MICRO;
		case IIO_EV_INFO_HYSTERESIS:
			ret = bmi323_read_ext_reg(data,
						  reg + BMI323_MO2_OFFSET,
						  &reg_value);
			if (ret)
				return ret;

			raw = FIELD_GET(BMI323_MO2_HYSTR_MSK, reg_value);
			*val = raw / BMI323_MOTION_HYSTR_SCALE;
			*val2 = BMI323_RAW_TO_MICRO(raw,
						    BMI323_MOTION_HYSTR_SCALE);
			return IIO_VAL_INT_PLUS_MICRO;
		default:
			return -EINVAL;
		}
	case IIO_EV_TYPE_CHANGE:
		ret = bmi323_read_ext_reg(data, BMI323_STEP_SC1_REG,
					  &reg_value);
		if (ret)
			return ret;

		raw = FIELD_GET(BMI323_STEP_SC1_WTRMRK_MSK, reg_value);
		*val = raw * 20;
		return IIO_VAL_INT;
	default:
		return -EINVAL;
	}
}

static int __bmi323_fifo_flush(struct iio_dev *indio_dev)
{
	struct bmi323_data *data = iio_priv(indio_dev);
	int i, ret, fifo_lvl, frame_count, bit, index;
	__le16 *frame, *pchannels;
	u64 sample_period;
	s64 tstamp;

	guard(mutex)(&data->mutex);
	ret = regmap_read(data->regmap, BMI323_FIFO_FILL_LEVEL_REG, &fifo_lvl);
	if (ret)
		return ret;

	fifo_lvl = min(fifo_lvl, BMI323_FIFO_FULL_IN_WORDS);

	frame_count = fifo_lvl / BMI323_FIFO_FRAME_LENGTH;
	if (!frame_count)
		return -EINVAL;

	if (fifo_lvl % BMI323_FIFO_FRAME_LENGTH)
		dev_warn(data->dev, "Bad FIFO alignment\n");

	/*
	 * Approximate timestamps for each of the sample based on the sampling
	 * frequency, timestamp for last sample and number of samples.
	 */
	if (data->old_fifo_tstamp) {
		sample_period = data->fifo_tstamp - data->old_fifo_tstamp;
		do_div(sample_period, frame_count);
	} else {
		sample_period = data->odrns[BMI323_ACCEL];
	}

	tstamp = data->fifo_tstamp - (frame_count - 1) * sample_period;

	ret = regmap_noinc_read(data->regmap, BMI323_FIFO_DATA_REG,
				&data->fifo_buff[0],
				fifo_lvl * BMI323_BYTES_PER_SAMPLE);
	if (ret)
		return ret;

	for (i = 0; i < frame_count; i++) {
		frame = &data->fifo_buff[i * BMI323_FIFO_FRAME_LENGTH];
		pchannels = &data->buffer.channels[0];

		index = 0;
		for_each_set_bit(bit, indio_dev->active_scan_mask,
				 BMI323_CHAN_MAX)
			pchannels[index++] = frame[bit];

		iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer,
						   tstamp);

		tstamp += sample_period;
	}

	return frame_count;
}

static int bmi323_set_watermark(struct iio_dev *indio_dev, unsigned int val)
{
	struct bmi323_data *data = iio_priv(indio_dev);

	val = min(val, (u32)BMI323_FIFO_FULL_IN_FRAMES);

	guard(mutex)(&data->mutex);
	data->watermark = val;

	return 0;
}

static int bmi323_fifo_disable(struct bmi323_data *data)
{
	int ret;

	guard(mutex)(&data->mutex);
	ret = regmap_write(data->regmap, BMI323_FIFO_CONF_REG, 0);
	if (ret)
		return ret;

	ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
				 BMI323_FIFO_WTRMRK_MSK,
				 FIELD_PREP(BMI323_FIFO_WTRMRK_MSK, 0));
	if (ret)
		return ret;

	data->fifo_tstamp = 0;
	data->state = BMI323_IDLE;

	return 0;
}

static int bmi323_buffer_predisable(struct iio_dev *indio_dev)
{
	struct bmi323_data *data = iio_priv(indio_dev);

	if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED)
		return 0;

	return bmi323_fifo_disable(data);
}

static int bmi323_update_watermark(struct bmi323_data *data)
{
	int wtrmrk;

	wtrmrk = data->watermark * BMI323_FIFO_FRAME_LENGTH;

	return regmap_write(data->regmap, BMI323_FIFO_WTRMRK_REG, wtrmrk);
}

static int bmi323_fifo_enable(struct bmi323_data *data)
{
	int ret;

	guard(mutex)(&data->mutex);
	ret = regmap_update_bits(data->regmap, BMI323_FIFO_CONF_REG,
				 BMI323_FIFO_CONF_ACC_GYR_EN_MSK,
				 FIELD_PREP(BMI323_FIFO_CONF_ACC_GYR_EN_MSK,
					    BMI323_FIFO_ACC_GYR_MSK));
	if (ret)
		return ret;

	ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
				 BMI323_FIFO_WTRMRK_MSK,
				 FIELD_PREP(BMI323_FIFO_WTRMRK_MSK,
					    data->irq_pin));
	if (ret)
		return ret;

	ret = bmi323_update_watermark(data);
	if (ret)
		return ret;

	ret = regmap_write(data->regmap, BMI323_FIFO_CTRL_REG,
			   BMI323_FIFO_FLUSH_MSK);
	if (ret)
		return ret;

	data->state = BMI323_BUFFER_FIFO;

	return 0;
}

static int bmi323_buffer_preenable(struct iio_dev *indio_dev)
{
	struct bmi323_data *data = iio_priv(indio_dev);

	guard(mutex)(&data->mutex);
	/*
	 * When the ODR of the accelerometer and gyroscope do not match, the
	 * maximum ODR value between the accelerometer and gyroscope is used
	 * for FIFO and the signal with lower ODR will insert dummy frame.
	 * So allow buffer read only when ODR's of accelero and gyro are equal.
	 * See datasheet section 5.7 "FIFO Data Buffering".
	 */
	if (data->odrns[BMI323_ACCEL] != data->odrns[BMI323_GYRO]) {
		dev_err(data->dev, "Accelero and Gyro ODR doesn't match\n");
		return -EINVAL;
	}

	return 0;
}

static int bmi323_buffer_postenable(struct iio_dev *indio_dev)
{
	struct bmi323_data *data = iio_priv(indio_dev);

	if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED)
		return 0;

	return bmi323_fifo_enable(data);
}

static ssize_t hwfifo_watermark_show(struct device *dev,
				     struct device_attribute *attr, char *buf)
{
	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
	struct bmi323_data *data = iio_priv(indio_dev);
	int wm;

	scoped_guard(mutex, &data->mutex)
		wm = data->watermark;

	return sysfs_emit(buf, "%d\n", wm);
}
static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0);

static ssize_t hwfifo_enabled_show(struct device *dev,
				   struct device_attribute *attr,
				   char *buf)
{
	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
	struct bmi323_data *data = iio_priv(indio_dev);
	bool state;

	scoped_guard(mutex, &data->mutex)
		state = data->state == BMI323_BUFFER_FIFO;

	return sysfs_emit(buf, "%d\n", state);
}
static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0);

static const struct iio_dev_attr *bmi323_fifo_attributes[] = {
	&iio_dev_attr_hwfifo_watermark,
	&iio_dev_attr_hwfifo_enabled,
	NULL
};

static const struct iio_buffer_setup_ops bmi323_buffer_ops = {
	.preenable = bmi323_buffer_preenable,
	.postenable = bmi323_buffer_postenable,
	.predisable = bmi323_buffer_predisable,
};

static irqreturn_t bmi323_irq_thread_handler(int irq, void *private)
{
	struct iio_dev *indio_dev = private;
	struct bmi323_data *data = iio_priv(indio_dev);
	unsigned int status_addr, status, feature_event;
	s64 timestamp = iio_get_time_ns(indio_dev);
	int ret;

	if (data->irq_pin == BMI323_IRQ_INT1)
		status_addr = BMI323_STATUS_INT1_REG;
	else
		status_addr = BMI323_STATUS_INT2_REG;

	scoped_guard(mutex, &data->mutex) {
		ret = regmap_read(data->regmap, status_addr, &status);
		if (ret)
			return IRQ_NONE;
	}

	if (!status || FIELD_GET(BMI323_STATUS_ERROR_MSK, status))
		return IRQ_NONE;

	if (FIELD_GET(BMI323_STATUS_FIFO_WTRMRK_MSK, status)) {
		data->old_fifo_tstamp = data->fifo_tstamp;
		data->fifo_tstamp = iio_get_time_ns(indio_dev);
		ret = __bmi323_fifo_flush(indio_dev);
		if (ret < 0)
			return IRQ_NONE;
	}

	if (FIELD_GET(BMI323_STATUS_ACC_GYR_DRDY_MSK, status))
		iio_trigger_poll_nested(data->trig);

	if (FIELD_GET(BMI323_STATUS_MOTION_MSK, status))
		iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
							     IIO_MOD_X_OR_Y_OR_Z,
							     IIO_EV_TYPE_MAG,
							     IIO_EV_DIR_RISING),
			       timestamp);

	if (FIELD_GET(BMI323_STATUS_NOMOTION_MSK, status))
		iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
							     IIO_MOD_X_OR_Y_OR_Z,
							     IIO_EV_TYPE_MAG,
							     IIO_EV_DIR_FALLING),
			       timestamp);

	if (FIELD_GET(BMI323_STATUS_STP_WTR_MSK, status))
		iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_STEPS, 0,
							     IIO_NO_MOD,
							     IIO_EV_TYPE_CHANGE,
							     IIO_EV_DIR_NONE),
			       timestamp);

	if (FIELD_GET(BMI323_STATUS_TAP_MSK, status)) {
		scoped_guard(mutex, &data->mutex) {
			ret = regmap_read(data->regmap,
					  BMI323_FEAT_EVNT_EXT_REG,
					  &feature_event);
			if (ret)
				return IRQ_NONE;
		}

		if (FIELD_GET(BMI323_FEAT_EVNT_EXT_S_MSK, feature_event)) {
			iio_push_event(indio_dev,
				       IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
							  IIO_MOD_X_OR_Y_OR_Z,
							  IIO_EV_TYPE_GESTURE,
							  IIO_EV_DIR_SINGLETAP),
				       timestamp);
		}

		if (FIELD_GET(BMI323_FEAT_EVNT_EXT_D_MSK, feature_event))
			iio_push_event(indio_dev,
				       IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
							  IIO_MOD_X_OR_Y_OR_Z,
							  IIO_EV_TYPE_GESTURE,
							  IIO_EV_DIR_DOUBLETAP),
				       timestamp);
	}

	return IRQ_HANDLED;
}

static int bmi323_set_drdy_irq(struct bmi323_data *data,
			       enum bmi323_irq_pin irq_pin)
{
	int ret;

	ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
				 BMI323_GYR_DRDY_MSK,
				 FIELD_PREP(BMI323_GYR_DRDY_MSK, irq_pin));
	if (ret)
		return ret;

	return regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
				  BMI323_ACC_DRDY_MSK,
				  FIELD_PREP(BMI323_ACC_DRDY_MSK, irq_pin));
}

static int bmi323_data_rdy_trigger_set_state(struct iio_trigger *trig,
					     bool state)
{
	struct bmi323_data *data = iio_trigger_get_drvdata(trig);
	enum bmi323_irq_pin irq_pin;

	guard(mutex)(&data->mutex);

	if (data->state == BMI323_BUFFER_FIFO) {
		dev_warn(data->dev, "Can't set trigger when FIFO enabled\n");
		return -EBUSY;
	}

	if (state) {
		data->state = BMI323_BUFFER_DRDY_TRIGGERED;
		irq_pin = data->irq_pin;
	} else {
		data->state = BMI323_IDLE;
		irq_pin = BMI323_IRQ_DISABLED;
	}

	return bmi323_set_drdy_irq(data, irq_pin);
}

static const struct iio_trigger_ops bmi323_trigger_ops = {
	.set_trigger_state = &bmi323_data_rdy_trigger_set_state,
};

static irqreturn_t bmi323_trigger_handler(int irq, void *p)
{
	struct iio_poll_func *pf = p;
	struct iio_dev *indio_dev = pf->indio_dev;
	struct bmi323_data *data = iio_priv(indio_dev);
	int ret, bit, index = 0;

	/* Lock to protect the data->buffer */
	guard(mutex)(&data->mutex);

	if (*indio_dev->active_scan_mask == BMI323_ALL_CHAN_MSK) {
		ret = regmap_bulk_read(data->regmap, BMI323_ACCEL_X_REG,
				       &data->buffer.channels,
				       ARRAY_SIZE(data->buffer.channels));
		if (ret)
			goto out;
	} else {
		for_each_set_bit(bit, indio_dev->active_scan_mask,
				 BMI323_CHAN_MAX) {
			ret = regmap_raw_read(data->regmap,
					      BMI323_ACCEL_X_REG + bit,
					      &data->buffer.channels[index++],
					      BMI323_BYTES_PER_SAMPLE);
			if (ret)
				goto out;
		}
	}

	iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer,
					   iio_get_time_ns(indio_dev));

out:
	iio_trigger_notify_done(indio_dev->trig);

	return IRQ_HANDLED;
}

static int bmi323_set_average(struct bmi323_data *data,
			      enum bmi323_sensor_type sensor, int avg)
{
	int raw = ARRAY_SIZE(bmi323_accel_gyro_avrg);

	while (raw--)
		if (avg == bmi323_accel_gyro_avrg[raw])
			break;
	if (raw < 0)
		return -EINVAL;

	guard(mutex)(&data->mutex);
	return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
				 BMI323_ACC_GYRO_CONF_AVG_MSK,
				 FIELD_PREP(BMI323_ACC_GYRO_CONF_AVG_MSK,
					    raw));
}

static int bmi323_get_average(struct bmi323_data *data,
			      enum bmi323_sensor_type sensor, int *avg)
{
	int ret, value, raw;

	scoped_guard(mutex, &data->mutex) {
		ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value);
		if (ret)
			return ret;
	}

	raw = FIELD_GET(BMI323_ACC_GYRO_CONF_AVG_MSK, value);
	*avg = bmi323_accel_gyro_avrg[raw];

	return IIO_VAL_INT;
}

static int bmi323_enable_steps(struct bmi323_data *data, int val)
{
	int ret;

	guard(mutex)(&data->mutex);
	if (data->odrhz[BMI323_ACCEL] < 200) {
		dev_err(data->dev, "Invalid accelerometer parameter\n");
		return -EINVAL;
	}

	ret = bmi323_feature_engine_events(data, BMI323_FEAT_IO0_STP_CNT_MSK,
					   val ? 1 : 0);
	if (ret)
		return ret;

	set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_STP_CNT_MSK,
		      FIELD_PREP(BMI323_FEAT_IO0_STP_CNT_MSK, val ? 1 : 0));

	return 0;
}

static int bmi323_read_steps(struct bmi323_data *data, int *val)
{
	int ret;

	guard(mutex)(&data->mutex);
	if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events))
		return -EINVAL;

	ret = regmap_bulk_read(data->regmap, BMI323_FEAT_IO2_REG,
			       data->steps_count,
			       ARRAY_SIZE(data->steps_count));
	if (ret)
		return ret;

	*val = get_unaligned_le32(data->steps_count);

	return IIO_VAL_INT;
}

static int bmi323_read_axis(struct bmi323_data *data,
			    struct iio_chan_spec const *chan, int *val)
{
	enum bmi323_sensor_type sensor;
	unsigned int value;
	u8 addr;
	int ret;

	ret = bmi323_get_error_status(data);
	if (ret)
		return -EINVAL;

	sensor = bmi323_iio_to_sensor(chan->type);
	addr = bmi323_hw[sensor].data + (chan->channel2 - IIO_MOD_X);

	scoped_guard(mutex, &data->mutex) {
		ret = regmap_read(data->regmap, addr, &value);
		if (ret)
			return ret;
	}

	*val = sign_extend32(value, chan->scan_type.realbits - 1);

	return IIO_VAL_INT;
}

static int bmi323_get_temp_data(struct bmi323_data *data, int *val)
{
	unsigned int value;
	int ret;

	ret = bmi323_get_error_status(data);
	if (ret)
		return -EINVAL;

	scoped_guard(mutex, &data->mutex) {
		ret = regmap_read(data->regmap, BMI323_TEMP_REG, &value);
		if (ret)
			return ret;
	}

	*val = sign_extend32(value, 15);

	return IIO_VAL_INT;
}

static int bmi323_get_odr(struct bmi323_data *data,
			  enum bmi323_sensor_type sensor, int *odr, int *uodr)
{
	int ret, value, odr_raw;

	scoped_guard(mutex, &data->mutex) {
		ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value);
		if (ret)
			return ret;
	}

	odr_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_ODR_MSK, value);
	*odr = bmi323_acc_gyro_odr[odr_raw - 1][0];
	*uodr = bmi323_acc_gyro_odr[odr_raw - 1][1];

	return IIO_VAL_INT_PLUS_MICRO;
}

static int bmi323_configure_power_mode(struct bmi323_data *data,
				       enum bmi323_sensor_type sensor,
				       int odr_index)
{
	enum bmi323_opr_mode mode;

	if (bmi323_acc_gyro_odr[odr_index][0] > 25)
		mode = ACC_GYRO_MODE_CONTINOUS;
	else
		mode = ACC_GYRO_MODE_DUTYCYCLE;

	return bmi323_set_mode(data, sensor, mode);
}

static int bmi323_set_odr(struct bmi323_data *data,
			  enum bmi323_sensor_type sensor, int odr, int uodr)
{
	int odr_raw, ret;

	odr_raw = ARRAY_SIZE(bmi323_acc_gyro_odr);

	while (odr_raw--)
		if (odr == bmi323_acc_gyro_odr[odr_raw][0] &&
		    uodr == bmi323_acc_gyro_odr[odr_raw][1])
			break;
	if (odr_raw < 0)
		return -EINVAL;

	ret = bmi323_configure_power_mode(data, sensor, odr_raw);
	if (ret)
		return -EINVAL;

	guard(mutex)(&data->mutex);
	data->odrhz[sensor] = bmi323_acc_gyro_odr[odr_raw][0];
	data->odrns[sensor] = bmi323_acc_gyro_odrns[odr_raw];

	odr_raw++;

	return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
				  BMI323_ACC_GYRO_CONF_ODR_MSK,
				  FIELD_PREP(BMI323_ACC_GYRO_CONF_ODR_MSK,
					     odr_raw));
}

static int bmi323_get_scale(struct bmi323_data *data,
			    enum bmi323_sensor_type sensor, int *val2)
{
	int ret, value, scale_raw;

	scoped_guard(mutex, &data->mutex) {
		ret = regmap_read(data->regmap, bmi323_hw[sensor].config,
				  &value);
		if (ret)
			return ret;
	}

	scale_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_SCL_MSK, value);
	*val2 = bmi323_hw[sensor].scale_table[scale_raw][1];

	return IIO_VAL_INT_PLUS_MICRO;
}

static int bmi323_set_scale(struct bmi323_data *data,
			    enum bmi323_sensor_type sensor, int val, int val2)
{
	int scale_raw;

	scale_raw = bmi323_hw[sensor].scale_table_len;

	while (scale_raw--)
		if (val == bmi323_hw[sensor].scale_table[scale_raw][0] &&
		    val2 == bmi323_hw[sensor].scale_table[scale_raw][1])
			break;
	if (scale_raw < 0)
		return -EINVAL;

	guard(mutex)(&data->mutex);
	return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
				  BMI323_ACC_GYRO_CONF_SCL_MSK,
				  FIELD_PREP(BMI323_ACC_GYRO_CONF_SCL_MSK,
					     scale_raw));
}

static int bmi323_read_avail(struct iio_dev *indio_dev,
			     struct iio_chan_spec const *chan,
			     const int **vals, int *type, int *length,
			     long mask)
{
	enum bmi323_sensor_type sensor;

	switch (mask) {
	case IIO_CHAN_INFO_SAMP_FREQ:
		*type = IIO_VAL_INT_PLUS_MICRO;
		*vals = (const int *)bmi323_acc_gyro_odr;
		*length = ARRAY_SIZE(bmi323_acc_gyro_odr) * 2;
		return IIO_AVAIL_LIST;
	case IIO_CHAN_INFO_SCALE:
		sensor = bmi323_iio_to_sensor(chan->type);
		*type = IIO_VAL_INT_PLUS_MICRO;
		*vals = (const int *)bmi323_hw[sensor].scale_table;
		*length = bmi323_hw[sensor].scale_table_len * 2;
		return IIO_AVAIL_LIST;
	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
		*type = IIO_VAL_INT;
		*vals = (const int *)bmi323_accel_gyro_avrg;
		*length = ARRAY_SIZE(bmi323_accel_gyro_avrg);
		return IIO_AVAIL_LIST;
	default:
		return -EINVAL;
	}
}

static int bmi323_write_raw(struct iio_dev *indio_dev,
			    struct iio_chan_spec const *chan, int val,
			    int val2, long mask)
{
	struct bmi323_data *data = iio_priv(indio_dev);

	switch (mask) {
	case IIO_CHAN_INFO_SAMP_FREQ:
		iio_device_claim_direct_scoped(return -EBUSY, indio_dev)
			return bmi323_set_odr(data,
					      bmi323_iio_to_sensor(chan->type),
					      val, val2);
		unreachable();
	case IIO_CHAN_INFO_SCALE:
		iio_device_claim_direct_scoped(return -EBUSY, indio_dev)
			return bmi323_set_scale(data,
						bmi323_iio_to_sensor(chan->type),
						val, val2);
		unreachable();
	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
		iio_device_claim_direct_scoped(return -EBUSY, indio_dev)
			return bmi323_set_average(data,
						  bmi323_iio_to_sensor(chan->type),
						  val);
		unreachable();
	case IIO_CHAN_INFO_ENABLE:
		return bmi323_enable_steps(data, val);
	case IIO_CHAN_INFO_PROCESSED: {
		guard(mutex)(&data->mutex);

		if (val || !FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK,
				      data->feature_events))
			return -EINVAL;

		/* Clear step counter value */
		return bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG,
					     BMI323_STEP_SC1_RST_CNT_MSK,
					     FIELD_PREP(BMI323_STEP_SC1_RST_CNT_MSK,
							1));
	}
	default:
		return -EINVAL;
	}
}

static int bmi323_read_raw(struct iio_dev *indio_dev,
			   struct iio_chan_spec const *chan, int *val,
			   int *val2, long mask)
{
	struct bmi323_data *data = iio_priv(indio_dev);

	switch (mask) {
	case IIO_CHAN_INFO_PROCESSED:
		return bmi323_read_steps(data, val);
	case IIO_CHAN_INFO_RAW:
		switch (chan->type) {
		case IIO_ACCEL:
		case IIO_ANGL_VEL:
			iio_device_claim_direct_scoped(return -EBUSY,
						       indio_dev)
				return bmi323_read_axis(data, chan, val);
			unreachable();
		case IIO_TEMP:
			return bmi323_get_temp_data(data, val);
		default:
			return -EINVAL;
		}
	case IIO_CHAN_INFO_SAMP_FREQ:
		return bmi323_get_odr(data, bmi323_iio_to_sensor(chan->type),
				      val, val2);
	case IIO_CHAN_INFO_SCALE:
		switch (chan->type) {
		case IIO_ACCEL:
		case IIO_ANGL_VEL:
			*val = 0;
			return bmi323_get_scale(data,
						bmi323_iio_to_sensor(chan->type),
						val2);
		case IIO_TEMP:
			*val = BMI323_TEMP_SCALE / MEGA;
			*val2 = BMI323_TEMP_SCALE % MEGA;
			return IIO_VAL_INT_PLUS_MICRO;
		default:
			return -EINVAL;
		}
	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
		return bmi323_get_average(data,
					  bmi323_iio_to_sensor(chan->type),
					  val);
	case IIO_CHAN_INFO_OFFSET:
		switch (chan->type) {
		case IIO_TEMP:
			*val = BMI323_TEMP_OFFSET;
			return IIO_VAL_INT;
		default:
			return -EINVAL;
		}
	case IIO_CHAN_INFO_ENABLE:
		scoped_guard(mutex, &data->mutex)
			*val = FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK,
					 data->feature_events);
		return IIO_VAL_INT;
	default:
		return -EINVAL;
	}
}

static const struct iio_info bmi323_info = {
	.read_raw = bmi323_read_raw,
	.write_raw = bmi323_write_raw,
	.read_avail = bmi323_read_avail,
	.hwfifo_set_watermark = bmi323_set_watermark,
	.write_event_config = bmi323_write_event_config,
	.read_event_config = bmi323_read_event_config,
	.write_event_value = bmi323_write_event_value,
	.read_event_value = bmi323_read_event_value,
	.event_attrs = &bmi323_event_attribute_group,
};

#define BMI323_SCAN_MASK_ACCEL_3AXIS		\
	(BIT(BMI323_ACCEL_X) | BIT(BMI323_ACCEL_Y) | BIT(BMI323_ACCEL_Z))

#define BMI323_SCAN_MASK_GYRO_3AXIS		\
	(BIT(BMI323_GYRO_X) | BIT(BMI323_GYRO_Y) | BIT(BMI323_GYRO_Z))

static const unsigned long bmi323_avail_scan_masks[] = {
	/* 3-axis accel */
	BMI323_SCAN_MASK_ACCEL_3AXIS,
	/* 3-axis gyro */
	BMI323_SCAN_MASK_GYRO_3AXIS,
	/* 3-axis accel + 3-axis gyro */
	BMI323_SCAN_MASK_ACCEL_3AXIS | BMI323_SCAN_MASK_GYRO_3AXIS,
	0
};

static int bmi323_int_pin_config(struct bmi323_data *data,
				 enum bmi323_irq_pin irq_pin,
				 bool active_high, bool open_drain, bool latch)
{
	unsigned int mask, field_value;
	int ret;

	ret = regmap_update_bits(data->regmap, BMI323_IO_INT_CONF_REG,
				 BMI323_IO_INT_LTCH_MSK,
				 FIELD_PREP(BMI323_IO_INT_LTCH_MSK, latch));
	if (ret)
		return ret;

	ret = bmi323_update_ext_reg(data, BMI323_GEN_SET1_REG,
				    BMI323_GEN_HOLD_DUR_MSK,
				    FIELD_PREP(BMI323_GEN_HOLD_DUR_MSK, 0));
	if (ret)
		return ret;

	switch (irq_pin) {
	case BMI323_IRQ_INT1:
		mask = BMI323_IO_INT1_LVL_OD_OP_MSK;

		field_value = FIELD_PREP(BMI323_IO_INT1_LVL_MSK, active_high) |
			      FIELD_PREP(BMI323_IO_INT1_OD_MSK, open_drain) |
			      FIELD_PREP(BMI323_IO_INT1_OP_EN_MSK, 1);
		break;
	case BMI323_IRQ_INT2:
		mask = BMI323_IO_INT2_LVL_OD_OP_MSK;

		field_value = FIELD_PREP(BMI323_IO_INT2_LVL_MSK, active_high) |
			      FIELD_PREP(BMI323_IO_INT2_OD_MSK, open_drain) |
			      FIELD_PREP(BMI323_IO_INT2_OP_EN_MSK, 1);
		break;
	default:
		return -EINVAL;
	}

	return regmap_update_bits(data->regmap, BMI323_IO_INT_CTR_REG, mask,
				  field_value);
}

static int bmi323_trigger_probe(struct bmi323_data *data,
				struct iio_dev *indio_dev)
{
	bool open_drain, active_high, latch;
	struct fwnode_handle *fwnode;
	enum bmi323_irq_pin irq_pin;
	int ret, irq, irq_type;
	struct irq_data *desc;

	fwnode = dev_fwnode(data->dev);
	if (!fwnode)
		return -ENODEV;

	irq = fwnode_irq_get_byname(fwnode, "INT1");
	if (irq > 0) {
		irq_pin = BMI323_IRQ_INT1;
	} else {
		irq = fwnode_irq_get_byname(fwnode, "INT2");
		if (irq < 0)
			return 0;

		irq_pin = BMI323_IRQ_INT2;
	}

	desc = irq_get_irq_data(irq);
	if (!desc)
		return dev_err_probe(data->dev, -EINVAL,
				     "Could not find IRQ %d\n", irq);

	irq_type = irqd_get_trigger_type(desc);
	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 = bmi323_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 = &bmi323_trigger_ops;
	iio_trigger_set_drvdata(data->trig, data);

	ret = devm_request_threaded_irq(data->dev, irq, NULL,
					bmi323_irq_thread_handler,
					IRQF_ONESHOT, "bmi323-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 bmi323_feature_engine_enable(struct bmi323_data *data, bool en)
{
	unsigned int feature_status;
	int ret;

	if (!en)
		return regmap_write(data->regmap, BMI323_FEAT_CTRL_REG, 0);

	ret = regmap_write(data->regmap, BMI323_FEAT_IO2_REG, 0x012c);
	if (ret)
		return ret;

	ret = regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG,
			   BMI323_FEAT_IO_STATUS_MSK);
	if (ret)
		return ret;

	ret = regmap_write(data->regmap, BMI323_FEAT_CTRL_REG,
			   BMI323_FEAT_ENG_EN_MSK);
	if (ret)
		return ret;

	/*
	 * It takes around 4 msec to enable the Feature engine, so check
	 * the status of the feature engine every 2 msec for a maximum
	 * of 5 trials.
	 */
	ret = regmap_read_poll_timeout(data->regmap, BMI323_FEAT_IO1_REG,
				       feature_status,
				       FIELD_GET(BMI323_FEAT_IO1_ERR_MSK,
						 feature_status) == 1,
				       BMI323_FEAT_ENG_POLL,
				       BMI323_FEAT_ENG_TIMEOUT);
	if (ret)
		return dev_err_probe(data->dev, -EINVAL,
				"Failed to enable feature engine\n");

	return 0;
}

static void bmi323_disable(void *data_ptr)
{
	struct bmi323_data *data = data_ptr;

	bmi323_set_mode(data, BMI323_ACCEL, ACC_GYRO_MODE_DISABLE);
	bmi323_set_mode(data, BMI323_GYRO, ACC_GYRO_MODE_DISABLE);

	/*
	 * Place the peripheral in its lowest power consuming state.
	 */
	regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL);
}

static int bmi323_set_bw(struct bmi323_data *data,
			 enum bmi323_sensor_type sensor, enum bmi323_3db_bw bw)
{
	return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
				  BMI323_ACC_GYRO_CONF_BW_MSK,
				  FIELD_PREP(BMI323_ACC_GYRO_CONF_BW_MSK, bw));
}

static int bmi323_init(struct bmi323_data *data)
{
	int ret, val;

	/*
	 * Perform soft reset to make sure the device is in a known state after
	 * start up. A delay of 1.5 ms is required after reset.
	 * See datasheet section 5.17 "Soft Reset".
	 */
	ret = regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL);
	if (ret)
		return ret;

	usleep_range(1500, 2000);

	/*
	 * Dummy read is required to enable SPI interface after reset.
	 * See datasheet section 7.2.1 "Protocol Selection".
	 */
	regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val);

	ret = regmap_read(data->regmap, BMI323_STATUS_REG, &val);
	if (ret)
		return ret;

	if (!FIELD_GET(BMI323_STATUS_POR_MSK, val))
		return dev_err_probe(data->dev, -EINVAL,
				     "Sensor initialization error\n");

	ret = regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val);
	if (ret)
		return ret;

	if (FIELD_GET(BMI323_CHIP_ID_MSK, val) != BMI323_CHIP_ID_VAL)
		return dev_err_probe(data->dev, -EINVAL, "Chip ID mismatch\n");

	ret = bmi323_feature_engine_enable(data, true);
	if (ret)
		return ret;

	ret = regmap_read(data->regmap, BMI323_ERR_REG, &val);
	if (ret)
		return ret;

	if (val)
		return dev_err_probe(data->dev, -EINVAL,
				     "Sensor power error = 0x%x\n", val);

	return 0;
}

static int bmi323_init_reset(struct bmi323_data *data)
{
	int ret;

	/*
	 * Set the Bandwidth coefficient which defines the 3 dB cutoff
	 * frequency in relation to the ODR.
	 */
	ret = bmi323_set_bw(data, BMI323_ACCEL, BMI323_BW_ODR_BY_2);
	if (ret)
		return ret;

	ret = bmi323_set_bw(data, BMI323_GYRO, BMI323_BW_ODR_BY_2);
	if (ret)
		return ret;

	ret = bmi323_set_odr(data, BMI323_ACCEL, 25, 0);
	if (ret)
		return ret;

	ret = bmi323_set_odr(data, BMI323_GYRO, 25, 0);
	if (ret)
		return ret;

	return devm_add_action_or_reset(data->dev, bmi323_disable, data);
}

int bmi323_core_probe(struct device *dev)
{
	static const char * const regulator_names[] = { "vdd", "vddio" };
	struct iio_dev *indio_dev;
	struct bmi323_data *data;
	struct regmap *regmap;
	int ret;

	regmap = dev_get_regmap(dev, NULL);
	if (!regmap)
		return dev_err_probe(dev, -ENODEV, "Failed to get regmap\n");

	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
	if (!indio_dev)
		return dev_err_probe(dev, -ENOMEM,
				     "Failed to allocate device\n");

	ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
					     regulator_names);
	if (ret)
		return dev_err_probe(dev, ret, "Failed to enable regulators\n");

	data = iio_priv(indio_dev);
	data->dev = dev;
	data->regmap = regmap;
	data->irq_pin = BMI323_IRQ_DISABLED;
	data->state = BMI323_IDLE;
	mutex_init(&data->mutex);

	ret = bmi323_init(data);
	if (ret)
		return -EINVAL;

	ret = bmi323_init_reset(data);
	if (ret)
		return -EINVAL;

	if (!iio_read_acpi_mount_matrix(dev, &data->orientation, "ROTM")) {
		ret = iio_read_mount_matrix(dev, &data->orientation);
		if (ret)
			return ret;
	}

	indio_dev->name = "bmi323-imu";
	indio_dev->info = &bmi323_info;
	indio_dev->channels = bmi323_channels;
	indio_dev->num_channels = ARRAY_SIZE(bmi323_channels);
	indio_dev->available_scan_masks = bmi323_avail_scan_masks;
	indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
	dev_set_drvdata(data->dev, indio_dev);

	ret = bmi323_trigger_probe(data, indio_dev);
	if (ret)
		return -EINVAL;

	ret = devm_iio_triggered_buffer_setup_ext(data->dev, indio_dev,
						  &iio_pollfunc_store_time,
						  bmi323_trigger_handler,
						  IIO_BUFFER_DIRECTION_IN,
						  &bmi323_buffer_ops,
						  bmi323_fifo_attributes);
	if (ret)
		return dev_err_probe(data->dev, ret,
				     "Failed to setup trigger buffer\n");

	ret = devm_iio_device_register(data->dev, indio_dev);
	if (ret)
		return dev_err_probe(data->dev, ret,
				     "Unable to register iio device\n");

	return bmi323_fifo_disable(data);
}
EXPORT_SYMBOL_NS_GPL(bmi323_core_probe, IIO_BMI323);

static int bmi323_core_runtime_suspend(struct device *dev)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct bmi323_data *data = iio_priv(indio_dev);
	struct bmi323_regs_runtime_pm *savestate = &data->runtime_pm_status;
	int ret;

	guard(mutex)(&data->mutex);

	ret = iio_device_suspend_triggering(indio_dev);
	if (ret)
		return ret;

	/* Save registers meant to be restored by resume pm callback. */
	for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_reg_savestate); i++) {
		ret = regmap_read(data->regmap, bmi323_reg_savestate[i],
				  &savestate->reg_settings[i]);
		if (ret) {
			dev_err(data->dev,
				"Error reading bmi323 reg 0x%x: %d\n",
				bmi323_reg_savestate[i], ret);
			return ret;
		}
	}

	for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_ext_reg_savestate); i++) {
		ret = bmi323_read_ext_reg(data, bmi323_ext_reg_savestate[i],
					  &savestate->ext_reg_settings[i]);
		if (ret) {
			dev_err(data->dev,
				"Error reading bmi323 external reg 0x%x: %d\n",
				bmi323_ext_reg_savestate[i], ret);
			return ret;
		}
	}

	/* Perform soft reset to place the device in its lowest power state. */
	ret = regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL);
	if (ret)
		return ret;

	return 0;
}

static int bmi323_core_runtime_resume(struct device *dev)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct bmi323_data *data = iio_priv(indio_dev);
	struct bmi323_regs_runtime_pm *savestate = &data->runtime_pm_status;
	unsigned int val;
	int ret;

	guard(mutex)(&data->mutex);

	/*
	 * Perform the device power-on and initial setup once again
	 * after being reset in the lower power state by runtime-pm.
	 */
	ret = bmi323_init(data);
	if (ret) {
		dev_err(data->dev, "Device power-on and init failed: %d", ret);
		return ret;
	}

	/* Register must be cleared before changing an active config */
	ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, 0);
	if (ret) {
		dev_err(data->dev, "Error stopping feature engine\n");
		return ret;
	}

	for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_ext_reg_savestate); i++) {
		ret = bmi323_write_ext_reg(data, bmi323_ext_reg_savestate[i],
					   savestate->ext_reg_settings[i]);
		if (ret) {
			dev_err(data->dev,
				"Error writing bmi323 external reg 0x%x: %d\n",
				bmi323_ext_reg_savestate[i], ret);
			return ret;
		}
	}

	for (unsigned int i = 0; i < ARRAY_SIZE(bmi323_reg_savestate); i++) {
		ret = regmap_write(data->regmap, bmi323_reg_savestate[i],
				   savestate->reg_settings[i]);
		if (ret) {
			dev_err(data->dev,
				"Error writing bmi323 reg 0x%x: %d\n",
				bmi323_reg_savestate[i], ret);
			return ret;
		}
	}

	/*
	 * Clear old FIFO samples that might be generated before suspend
	 * or generated from a peripheral state not equal to the saved one.
	 */
	if (data->state == BMI323_BUFFER_FIFO) {
		ret = regmap_write(data->regmap, BMI323_FIFO_CTRL_REG,
				   BMI323_FIFO_FLUSH_MSK);
		if (ret) {
			dev_err(data->dev, "Error flushing FIFO buffer: %d\n", ret);
			return ret;
		}
	}

	ret = regmap_read(data->regmap, BMI323_ERR_REG, &val);
	if (ret) {
		dev_err(data->dev,
			"Error reading bmi323 error register: %d\n", ret);
		return ret;
	}

	if (val) {
		dev_err(data->dev,
			"Sensor power error in PM = 0x%x\n", val);
		return -EINVAL;
	}

	return iio_device_resume_triggering(indio_dev);
}

const struct dev_pm_ops bmi323_core_pm_ops = {
	RUNTIME_PM_OPS(bmi323_core_runtime_suspend,
		       bmi323_core_runtime_resume, NULL)
};
EXPORT_SYMBOL_NS_GPL(bmi323_core_pm_ops, IIO_BMI323);

MODULE_DESCRIPTION("Bosch BMI323 IMU driver");
MODULE_AUTHOR("Jagath Jog J <jagathjog1996@gmail.com>");
MODULE_LICENSE("GPL");
