Contributors: 5
Author Tokens Token Proportion Commits Commit Proportion
Lothar Rubusch 2754 52.27% 8 66.67%
Lucas Stankus 2179 41.36% 1 8.33%
George Mois 315 5.98% 1 8.33%
Jonathan Cameron 12 0.23% 1 8.33%
Peter Zijlstra 9 0.17% 1 8.33%
Total 5269 12


// SPDX-License-Identifier: GPL-2.0-only
/*
 * ADXL313 3-Axis Digital Accelerometer
 *
 * Copyright (c) 2021 Lucas Stankus <lucas.p.stankus@gmail.com>
 *
 * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf
 */

#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/units.h>

#include <linux/iio/buffer.h>
#include <linux/iio/events.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)

#define ADXL313_ACT_XYZ_EN			GENMASK(6, 4)
#define ADXL313_INACT_XYZ_EN			GENMASK(2, 0)

#define ADXL313_REG_ACT_ACDC_MSK		BIT(7)
#define ADXL313_REG_INACT_ACDC_MSK		BIT(3)
#define ADXL313_COUPLING_DC			0
#define ADXL313_COUPLING_AC			1

/* activity/inactivity */
enum adxl313_activity_type {
	ADXL313_ACTIVITY,
	ADXL313_INACTIVITY,
	ADXL313_ACTIVITY_AC,
	ADXL313_INACTIVITY_AC,
};

static const unsigned int adxl313_act_int_reg[] = {
	[ADXL313_ACTIVITY] = ADXL313_INT_ACTIVITY,
	[ADXL313_INACTIVITY] = ADXL313_INT_INACTIVITY,
	[ADXL313_ACTIVITY_AC] = ADXL313_INT_ACTIVITY,
	[ADXL313_INACTIVITY_AC] = ADXL313_INT_INACTIVITY,
};

static const unsigned int adxl313_act_thresh_reg[] = {
	[ADXL313_ACTIVITY] = ADXL313_REG_THRESH_ACT,
	[ADXL313_INACTIVITY] = ADXL313_REG_THRESH_INACT,
	[ADXL313_ACTIVITY_AC] = ADXL313_REG_THRESH_ACT,
	[ADXL313_INACTIVITY_AC] = ADXL313_REG_THRESH_INACT,
};

static const unsigned int adxl313_act_acdc_msk[] = {
	[ADXL313_ACTIVITY] = ADXL313_REG_ACT_ACDC_MSK,
	[ADXL313_INACTIVITY] = ADXL313_REG_INACT_ACDC_MSK,
	[ADXL313_ACTIVITY_AC] = ADXL313_REG_ACT_ACDC_MSK,
	[ADXL313_INACTIVITY_AC] = ADXL313_REG_INACT_ACDC_MSK,
};

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)),
	regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL),
	regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_FIFO_STATUS),
};

static const struct regmap_range adxl313_readable_reg_range[] = {
	regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_XID),
	regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET),
	regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
	regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL),
	regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_FIFO_STATUS),
};

const struct regmap_access_table adxl312_readable_regs_table = {
	.yes_ranges = adxl312_readable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl312_readable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl312_readable_regs_table, "IIO_ADXL313");

const struct regmap_access_table adxl313_readable_regs_table = {
	.yes_ranges = adxl313_readable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl313_readable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl313_readable_regs_table, "IIO_ADXL313");

const struct regmap_access_table adxl314_readable_regs_table = {
	.yes_ranges = adxl312_readable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl312_readable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl314_readable_regs_table, "IIO_ADXL313");

bool adxl313_is_volatile_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case ADXL313_REG_DATA_AXIS(0):
	case ADXL313_REG_DATA_AXIS(1):
	case ADXL313_REG_DATA_AXIS(2):
	case ADXL313_REG_DATA_AXIS(3):
	case ADXL313_REG_DATA_AXIS(4):
	case ADXL313_REG_DATA_AXIS(5):
	case ADXL313_REG_FIFO_STATUS:
	case ADXL313_REG_INT_SOURCE:
		return true;
	default:
		return false;
	}
}
EXPORT_SYMBOL_NS_GPL(adxl313_is_volatile_reg, "IIO_ADXL313");

static int adxl313_set_measure_en(struct adxl313_data *data, bool en)
{
	return regmap_assign_bits(data->regmap, ADXL313_REG_POWER_CTL,
				  ADXL313_POWER_CTL_MSK, en);
}

static int adxl312_check_id(struct device *dev,
			    struct adxl313_data *data)
{
	unsigned int regval;
	int ret;

	ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, &regval);
	if (ret)
		return ret;

	if (regval != ADXL313_DEVID0_ADXL312_314)
		dev_warn(dev, "Invalid manufacturer ID: %#02x\n", regval);

	return 0;
}

static int adxl313_check_id(struct device *dev,
			    struct adxl313_data *data)
{
	unsigned int regval;
	int ret;

	ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, &regval);
	if (ret)
		return ret;

	if (regval != ADXL313_DEVID0)
		dev_warn(dev, "Invalid manufacturer ID: 0x%02x\n", regval);

	/* Check DEVID1 and PARTID */
	if (regval == ADXL313_DEVID0) {
		ret = regmap_read(data->regmap, ADXL313_REG_DEVID1, &regval);
		if (ret)
			return ret;

		if (regval != ADXL313_DEVID1)
			dev_warn(dev, "Invalid mems ID: 0x%02x\n", regval);

		ret = regmap_read(data->regmap, ADXL313_REG_PARTID, &regval);
		if (ret)
			return ret;

		if (regval != ADXL313_PARTID)
			dev_warn(dev, "Invalid device ID: 0x%02x\n", regval);
	}

	return 0;
}

const struct adxl313_chip_info adxl31x_chip_info[] = {
	[ADXL312] = {
		.name = "adxl312",
		.type = ADXL312,
		.scale_factor = 28425072,
		.variable_range = true,
		.soft_reset = false,
		.check_id = &adxl312_check_id,
	},
	[ADXL313] = {
		.name = "adxl313",
		.type = ADXL313,
		.scale_factor = 9576806,
		.variable_range = true,
		.soft_reset = true,
		.check_id = &adxl313_check_id,
	},
	[ADXL314] = {
		.name = "adxl314",
		.type = ADXL314,
		.scale_factor = 478858719,
		.variable_range = false,
		.soft_reset = false,
		.check_id = &adxl312_check_id,
	},
};
EXPORT_SYMBOL_NS_GPL(adxl31x_chip_info, "IIO_ADXL313");

static const struct regmap_range adxl312_writable_reg_range[] = {
	regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
	regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL),
	regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_INT_MAP),
	regmap_reg_range(ADXL313_REG_DATA_FORMAT, ADXL313_REG_DATA_FORMAT),
	regmap_reg_range(ADXL313_REG_FIFO_CTL, ADXL313_REG_FIFO_CTL),
};

static const struct regmap_range adxl313_writable_reg_range[] = {
	regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET),
	regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
	regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL),
	regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_INT_MAP),
	regmap_reg_range(ADXL313_REG_DATA_FORMAT, ADXL313_REG_DATA_FORMAT),
	regmap_reg_range(ADXL313_REG_FIFO_CTL, ADXL313_REG_FIFO_CTL),
};

const struct regmap_access_table adxl312_writable_regs_table = {
	.yes_ranges = adxl312_writable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl312_writable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl312_writable_regs_table, "IIO_ADXL313");

const struct regmap_access_table adxl313_writable_regs_table = {
	.yes_ranges = adxl313_writable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl313_writable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl313_writable_regs_table, "IIO_ADXL313");

const struct regmap_access_table adxl314_writable_regs_table = {
	.yes_ranges = adxl312_writable_reg_range,
	.n_yes_ranges = ARRAY_SIZE(adxl312_writable_reg_range),
};
EXPORT_SYMBOL_NS_GPL(adxl314_writable_regs_table, "IIO_ADXL313");

static const int adxl313_odr_freqs[][2] = {
	[0] = { 6, 250000 },
	[1] = { 12, 500000 },
	[2] = { 25, 0 },
	[3] = { 50, 0 },
	[4] = { 100, 0 },
	[5] = { 200, 0 },
	[6] = { 400, 0 },
	[7] = { 800, 0 },
	[8] = { 1600, 0 },
	[9] = { 3200, 0 },
};

#define ADXL313_ACCEL_CHANNEL(index, reg, axis) {			\
	.type = IIO_ACCEL,						\
	.scan_index = (index),						\
	.address = (reg),						\
	.modified = 1,							\
	.channel2 = IIO_MOD_##axis,					\
	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
			      BIT(IIO_CHAN_INFO_CALIBBIAS),		\
	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |		\
				    BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
	.info_mask_shared_by_type_available =				\
		BIT(IIO_CHAN_INFO_SAMP_FREQ),				\
	.scan_type = {							\
		.sign = 's',						\
		.realbits = 13,						\
		.storagebits = 16,					\
		.endianness = IIO_BE,					\
	},								\
}

static const struct iio_event_spec adxl313_activity_events[] = {
	{
		.type = IIO_EV_TYPE_MAG,
		.dir = IIO_EV_DIR_RISING,
		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
		.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
	},
	{
		/* activity, AC bit set */
		.type = IIO_EV_TYPE_MAG_ADAPTIVE,
		.dir = IIO_EV_DIR_RISING,
		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
		.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
	},
};

static const struct iio_event_spec adxl313_inactivity_events[] = {
	{
		/* inactivity */
		.type = IIO_EV_TYPE_MAG,
		.dir = IIO_EV_DIR_FALLING,
		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
		.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
			BIT(IIO_EV_INFO_PERIOD),
	},
	{
		/* inactivity, AC bit set */
		.type = IIO_EV_TYPE_MAG_ADAPTIVE,
		.dir = IIO_EV_DIR_FALLING,
		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
		.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
			BIT(IIO_EV_INFO_PERIOD),
	},
};

enum adxl313_chans {
	chan_x, chan_y, chan_z,
};

static const struct iio_chan_spec adxl313_channels[] = {
	ADXL313_ACCEL_CHANNEL(0, chan_x, X),
	ADXL313_ACCEL_CHANNEL(1, chan_y, Y),
	ADXL313_ACCEL_CHANNEL(2, chan_z, Z),
	{
		.type = IIO_ACCEL,
		.modified = 1,
		.channel2 = IIO_MOD_X_OR_Y_OR_Z,
		.scan_index = -1, /* Fake channel for axis OR'ing */
		.event_spec = adxl313_activity_events,
		.num_event_specs = ARRAY_SIZE(adxl313_activity_events),
	},
	{
		.type = IIO_ACCEL,
		.modified = 1,
		.channel2 = IIO_MOD_X_AND_Y_AND_Z,
		.scan_index = -1, /* Fake channel for axis AND'ing */
		.event_spec = adxl313_inactivity_events,
		.num_event_specs = ARRAY_SIZE(adxl313_inactivity_events),
	},
};

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,
			   unsigned int freq1, unsigned int freq2)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(adxl313_odr_freqs); i++) {
		if (adxl313_odr_freqs[i][0] == freq1 &&
		    adxl313_odr_freqs[i][1] == freq2)
			break;
	}

	if (i == ARRAY_SIZE(adxl313_odr_freqs))
		return -EINVAL;

	return regmap_update_bits(data->regmap, ADXL313_REG_BW_RATE,
				  ADXL313_RATE_MSK,
				  FIELD_PREP(ADXL313_RATE_MSK, ADXL313_RATE_BASE + i));
}

static int adxl313_read_axis(struct adxl313_data *data,
			     struct iio_chan_spec const *chan)
{
	int ret;

	mutex_lock(&data->lock);

	ret = regmap_bulk_read(data->regmap,
			       ADXL313_REG_DATA_AXIS(chan->address),
			       &data->transf_buf, sizeof(data->transf_buf));
	if (ret)
		goto unlock_ret;

	ret = le16_to_cpu(data->transf_buf);

unlock_ret:
	mutex_unlock(&data->lock);
	return ret;
}

static int adxl313_read_freq_avail(struct iio_dev *indio_dev,
				   struct iio_chan_spec const *chan,
				   const int **vals, int *type, int *length,
				   long mask)
{
	switch (mask) {
	case IIO_CHAN_INFO_SAMP_FREQ:
		*vals = (const int *)adxl313_odr_freqs;
		*length = ARRAY_SIZE(adxl313_odr_freqs) * 2;
		*type = IIO_VAL_INT_PLUS_MICRO;
		return IIO_AVAIL_LIST;
	default:
		return -EINVAL;
	}
}

static int adxl313_set_inact_time_s(struct adxl313_data *data,
				    unsigned int val_s)
{
	unsigned int max_boundary = U8_MAX; /* by register size */
	unsigned int val = min(val_s, max_boundary);

	return regmap_write(data->regmap, ADXL313_REG_TIME_INACT, val);
}

/**
 * adxl313_is_act_inact_ac() - Check if AC coupling is enabled.
 * @data: The device data.
 * @type: The activity or inactivity type.
 *
 * Provide a type of activity or inactivity, combined with either AC coupling
 * set, or default to DC coupling. This function verifies if the combination is
 * currently enabled or not.
 *
 * Return: if the provided activity type has AC coupling enabled or a negative
 * error value.
 */
static int adxl313_is_act_inact_ac(struct adxl313_data *data,
				   enum adxl313_activity_type type)
{
	unsigned int regval;
	bool coupling;
	int ret;

	ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval);
	if (ret)
		return ret;

	coupling = adxl313_act_acdc_msk[type] & regval;

	switch (type) {
	case ADXL313_ACTIVITY:
	case ADXL313_INACTIVITY:
		return coupling == ADXL313_COUPLING_DC;
	case ADXL313_ACTIVITY_AC:
	case ADXL313_INACTIVITY_AC:
		return coupling == ADXL313_COUPLING_AC;
	default:
		return -EINVAL;
	}
}

static int adxl313_set_act_inact_ac(struct adxl313_data *data,
				    enum adxl313_activity_type type,
				    bool cmd_en)
{
	unsigned int act_inact_ac;

	switch (type) {
	case ADXL313_ACTIVITY_AC:
	case ADXL313_INACTIVITY_AC:
		act_inact_ac = ADXL313_COUPLING_AC && cmd_en;
		break;
	case ADXL313_ACTIVITY:
	case ADXL313_INACTIVITY:
		act_inact_ac = ADXL313_COUPLING_DC && cmd_en;
		break;
	default:
		return -EINVAL;
	}

	return regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL,
				  adxl313_act_acdc_msk[type], act_inact_ac);
}

static int adxl313_is_act_inact_en(struct adxl313_data *data,
				   enum adxl313_activity_type type)
{
	unsigned int axis_ctrl;
	unsigned int regval;
	bool int_en;
	int ret;

	ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &axis_ctrl);
	if (ret)
		return ret;

	/* Check if axis for activity are enabled */
	switch (type) {
	case ADXL313_ACTIVITY:
	case ADXL313_ACTIVITY_AC:
		if (!FIELD_GET(ADXL313_ACT_XYZ_EN, axis_ctrl))
			return false;
		break;
	case ADXL313_INACTIVITY:
	case ADXL313_INACTIVITY_AC:
		if (!FIELD_GET(ADXL313_INACT_XYZ_EN, axis_ctrl))
			return false;
		break;
	default:
		return -EINVAL;
	}

	/* Check if specific interrupt is enabled */
	ret = regmap_read(data->regmap, ADXL313_REG_INT_ENABLE, &regval);
	if (ret)
		return ret;

	int_en = adxl313_act_int_reg[type] & regval;
	if (!int_en)
		return false;

	/* Check if configured coupling matches provided type */
	return adxl313_is_act_inact_ac(data, type);
}

static int adxl313_set_act_inact_linkbit(struct adxl313_data *data, bool en)
{
	int act_ac_en, inact_ac_en;
	int act_en, inact_en;

	act_en = adxl313_is_act_inact_en(data, ADXL313_ACTIVITY);
	if (act_en < 0)
		return act_en;

	act_ac_en = adxl313_is_act_inact_en(data, ADXL313_ACTIVITY_AC);
	if (act_ac_en < 0)
		return act_ac_en;

	inact_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY);
	if (inact_en < 0)
		return inact_en;

	inact_ac_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY_AC);
	if (inact_ac_en < 0)
		return inact_ac_en;

	act_en = act_en || act_ac_en;

	inact_en = inact_en || inact_ac_en;

	return regmap_assign_bits(data->regmap, ADXL313_REG_POWER_CTL,
				  ADXL313_POWER_CTL_AUTO_SLEEP | ADXL313_POWER_CTL_LINK,
				  en && act_en && inact_en);
}

static int adxl313_set_act_inact_en(struct adxl313_data *data,
				    enum adxl313_activity_type type,
				    bool cmd_en)
{
	unsigned int axis_ctrl;
	unsigned int threshold;
	unsigned int inact_time_s;
	int ret;

	if (cmd_en) {
		/* When turning on, check if threshold is valid */
		ret = regmap_read(data->regmap, adxl313_act_thresh_reg[type],
				  &threshold);
		if (ret)
			return ret;

		if (!threshold) /* Just ignore the command if threshold is 0 */
			return 0;

		/* When turning on inactivity, check if inact time is valid */
		if (type == ADXL313_INACTIVITY || type == ADXL313_INACTIVITY_AC) {
			ret = regmap_read(data->regmap,
					  ADXL313_REG_TIME_INACT,
					  &inact_time_s);
			if (ret)
				return ret;

			if (!inact_time_s)
				return 0;
		}
	} else {
		/*
		 * When turning off an activity, ensure that the correct
		 * coupling event is specified. This step helps prevent misuse -
		 * for example, if an AC-coupled activity is active and the
		 * current call attempts to turn off a DC-coupled activity, this
		 * inconsistency should be detected here.
		 */
		if (adxl313_is_act_inact_ac(data, type) <= 0)
			return 0;
	}

	/* Start modifying configuration registers */
	ret = adxl313_set_measure_en(data, false);
	if (ret)
		return ret;

	/* Enable axis according to the command */
	switch (type) {
	case ADXL313_ACTIVITY:
	case ADXL313_ACTIVITY_AC:
		axis_ctrl = ADXL313_ACT_XYZ_EN;
		break;
	case ADXL313_INACTIVITY:
	case ADXL313_INACTIVITY_AC:
		axis_ctrl = ADXL313_INACT_XYZ_EN;
		break;
	default:
		return -EINVAL;
	}
	ret = regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL,
				 axis_ctrl, cmd_en);
	if (ret)
		return ret;

	/* Update AC/DC-coupling according to the command */
	ret = adxl313_set_act_inact_ac(data, type, cmd_en);
	if (ret)
		return ret;

	/* Enable the interrupt line, according to the command */
	ret = regmap_assign_bits(data->regmap, ADXL313_REG_INT_ENABLE,
				 adxl313_act_int_reg[type], cmd_en);
	if (ret)
		return ret;

	/* Set link-bit and auto-sleep only when ACT and INACT are enabled */
	ret = adxl313_set_act_inact_linkbit(data, cmd_en);
	if (ret)
		return ret;

	return adxl313_set_measure_en(data, true);
}

static int adxl313_read_raw(struct iio_dev *indio_dev,
			    struct iio_chan_spec const *chan,
			    int *val, int *val2, long mask)
{
	struct adxl313_data *data = iio_priv(indio_dev);
	unsigned int regval;
	int ret;

	switch (mask) {
	case IIO_CHAN_INFO_RAW:
		ret = adxl313_read_axis(data, chan);
		if (ret < 0)
			return ret;

		*val = sign_extend32(ret, chan->scan_type.realbits - 1);
		return IIO_VAL_INT;
	case IIO_CHAN_INFO_SCALE:
		*val = 0;

		*val2 = data->chip_info->scale_factor;

		return IIO_VAL_INT_PLUS_NANO;
	case IIO_CHAN_INFO_CALIBBIAS:
		ret = regmap_read(data->regmap,
				  ADXL313_REG_OFS_AXIS(chan->address), &regval);
		if (ret)
			return ret;

		/*
		 * 8-bit resolution at minimum range, that is 4x accel data scale
		 * factor at full resolution
		 */
		*val = sign_extend32(regval, 7) * 4;
		return IIO_VAL_INT;
	case IIO_CHAN_INFO_SAMP_FREQ:
		ret = regmap_read(data->regmap, ADXL313_REG_BW_RATE, &regval);
		if (ret)
			return ret;

		ret = FIELD_GET(ADXL313_RATE_MSK, regval) - ADXL313_RATE_BASE;
		*val = adxl313_odr_freqs[ret][0];
		*val2 = adxl313_odr_freqs[ret][1];
		return IIO_VAL_INT_PLUS_MICRO;
	default:
		return -EINVAL;
	}
}

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

	switch (mask) {
	case IIO_CHAN_INFO_CALIBBIAS:
		/*
		 * 8-bit resolution at minimum range, that is 4x accel data scale
		 * factor at full resolution
		 */
		if (clamp_val(val, -128 * 4, 127 * 4) != val)
			return -EINVAL;

		return regmap_write(data->regmap,
				    ADXL313_REG_OFS_AXIS(chan->address),
				    val / 4);
	case IIO_CHAN_INFO_SAMP_FREQ:
		return adxl313_set_odr(data, val, val2);
	default:
		return -EINVAL;
	}
}

static int adxl313_read_mag_config(struct adxl313_data *data,
				   enum iio_event_direction dir,
				   enum adxl313_activity_type type_act,
				   enum adxl313_activity_type type_inact)
{
	switch (dir) {
	case IIO_EV_DIR_RISING:
		return !!adxl313_is_act_inact_en(data, type_act);
	case IIO_EV_DIR_FALLING:
		return !!adxl313_is_act_inact_en(data, type_inact);
	default:
		return -EINVAL;
	}
}

static int adxl313_write_mag_config(struct adxl313_data *data,
				    enum iio_event_direction dir,
				    enum adxl313_activity_type type_act,
				    enum adxl313_activity_type type_inact,
				    bool state)
{
	switch (dir) {
	case IIO_EV_DIR_RISING:
		return adxl313_set_act_inact_en(data, type_act, state);
	case IIO_EV_DIR_FALLING:
		return adxl313_set_act_inact_en(data, type_inact, state);
	default:
		return -EINVAL;
	}
}

static int adxl313_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 adxl313_data *data = iio_priv(indio_dev);

	switch (type) {
	case IIO_EV_TYPE_MAG:
		return adxl313_read_mag_config(data, dir,
					       ADXL313_ACTIVITY,
					       ADXL313_INACTIVITY);
	case IIO_EV_TYPE_MAG_ADAPTIVE:
		return adxl313_read_mag_config(data, dir,
					       ADXL313_ACTIVITY_AC,
					       ADXL313_INACTIVITY_AC);
	default:
		return -EINVAL;
	}
}

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

	switch (type) {
	case IIO_EV_TYPE_MAG:
		return adxl313_write_mag_config(data, dir,
						ADXL313_ACTIVITY,
						ADXL313_INACTIVITY,
						state);
	case IIO_EV_TYPE_MAG_ADAPTIVE:
		return adxl313_write_mag_config(data, dir,
						ADXL313_ACTIVITY_AC,
						ADXL313_INACTIVITY_AC,
						state);
	default:
		return -EINVAL;
	}
}

static int adxl313_read_mag_value(struct adxl313_data *data,
				  enum iio_event_direction dir,
				  enum iio_event_info info,
				  enum adxl313_activity_type type_act,
				  enum adxl313_activity_type type_inact,
				  int *val, int *val2)
{
	unsigned int threshold;
	unsigned int period;
	int ret;

	switch (info) {
	case IIO_EV_INFO_VALUE:
		switch (dir) {
		case IIO_EV_DIR_RISING:
			ret = regmap_read(data->regmap,
					  adxl313_act_thresh_reg[type_act],
					  &threshold);
			if (ret)
				return ret;
			*val = threshold * 15625;
			*val2 = MICRO;
			return IIO_VAL_FRACTIONAL;
		case IIO_EV_DIR_FALLING:
			ret = regmap_read(data->regmap,
					  adxl313_act_thresh_reg[type_inact],
					  &threshold);
			if (ret)
				return ret;
			*val = threshold * 15625;
			*val2 = MICRO;
			return IIO_VAL_FRACTIONAL;
		default:
			return -EINVAL;
		}
	case IIO_EV_INFO_PERIOD:
		ret = regmap_read(data->regmap, ADXL313_REG_TIME_INACT,
				  &period);
		if (ret)
			return ret;
		*val = period;
		return IIO_VAL_INT;
	default:
		return -EINVAL;
	}
}

static int adxl313_write_mag_value(struct adxl313_data *data,
				   enum iio_event_direction dir,
				   enum iio_event_info info,
				   enum adxl313_activity_type type_act,
				   enum adxl313_activity_type type_inact,
				   int val, int val2)
{
	unsigned int regval;

	switch (info) {
	case IIO_EV_INFO_VALUE:
		/* Scale factor 15.625 mg/LSB */
		regval = DIV_ROUND_CLOSEST(MICRO * val + val2, 15625);
		switch (dir) {
		case IIO_EV_DIR_RISING:
			return regmap_write(data->regmap,
					    adxl313_act_thresh_reg[type_act],
					    regval);
		case IIO_EV_DIR_FALLING:
			return regmap_write(data->regmap,
					    adxl313_act_thresh_reg[type_inact],
					    regval);
		default:
			return -EINVAL;
		}
	case IIO_EV_INFO_PERIOD:
		return adxl313_set_inact_time_s(data, val);
	default:
		return -EINVAL;
	}
}

static int adxl313_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 adxl313_data *data = iio_priv(indio_dev);

	switch (type) {
	case IIO_EV_TYPE_MAG:
		return adxl313_read_mag_value(data, dir, info,
					      ADXL313_ACTIVITY,
					      ADXL313_INACTIVITY,
					      val, val2);
	case IIO_EV_TYPE_MAG_ADAPTIVE:
		return adxl313_read_mag_value(data, dir, info,
					      ADXL313_ACTIVITY_AC,
					      ADXL313_INACTIVITY_AC,
					      val, val2);
	default:
		return -EINVAL;
	}
}

static int adxl313_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 adxl313_data *data = iio_priv(indio_dev);

	switch (type) {
	case IIO_EV_TYPE_MAG:
		return adxl313_write_mag_value(data, dir, info,
					       ADXL313_ACTIVITY,
					       ADXL313_INACTIVITY,
					       val, val2);
	case IIO_EV_TYPE_MAG_ADAPTIVE:
		return adxl313_write_mag_value(data, dir, info,
					       ADXL313_ACTIVITY_AC,
					       ADXL313_INACTIVITY_AC,
					       val, val2);
	default:
		return -EINVAL;
	}
}

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, &regval);
	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, &regval);

	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 int adxl313_push_events(struct iio_dev *indio_dev, int int_stat)
{
	s64 ts = iio_get_time_ns(indio_dev);
	struct adxl313_data *data = iio_priv(indio_dev);
	unsigned int regval;
	int ret = -ENOENT;

	if (FIELD_GET(ADXL313_INT_ACTIVITY, int_stat)) {
		ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval);
		if (ret)
			return ret;

		if (FIELD_GET(ADXL313_REG_ACT_ACDC_MSK, regval)) {
			/* AC coupled */
			ret = iio_push_event(indio_dev,
					     IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
								IIO_MOD_X_OR_Y_OR_Z,
								IIO_EV_TYPE_MAG_ADAPTIVE,
								IIO_EV_DIR_RISING),
					     ts);
			if (ret)
				return ret;
		} else {
			/* DC coupled, relying on THRESH */
			ret = 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),
					     ts);
			if (ret)
				return ret;
		}
	}

	if (FIELD_GET(ADXL313_INT_INACTIVITY, int_stat)) {
		ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval);
		if (ret)
			return ret;

		if (FIELD_GET(ADXL313_REG_INACT_ACDC_MSK, regval)) {
			/* AC coupled */
			ret = iio_push_event(indio_dev,
					     IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
								IIO_MOD_X_AND_Y_AND_Z,
								IIO_EV_TYPE_MAG_ADAPTIVE,
								IIO_EV_DIR_FALLING),
					     ts);
			if (ret)
				return ret;
		} else {
			/* DC coupled, relying on THRESH */
			ret = iio_push_event(indio_dev,
					     IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
								IIO_MOD_X_AND_Y_AND_Z,
								IIO_EV_TYPE_MAG,
								IIO_EV_DIR_FALLING),
					     ts);
			if (ret)
				return ret;
		}
	}

	return ret;
}

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;

	/*
	 * In cases of sensor events not handled (still not implemented) by
	 * this driver, the FIFO needs to be drained to become operational
	 * again. In general the sensor configuration only should issue events
	 * which were configured by this driver. Anyway a miss-configuration
	 * easily might end up in a hanging sensor FIFO.
	 */
	if (adxl313_push_events(indio_dev, int_stat))
		goto err_reset_fifo;

	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)
{
	struct adxl313_data *data = iio_priv(indio_dev);

	if (readval)
		return regmap_read(data->regmap, reg, readval);
	return regmap_write(data->regmap, reg, writeval);
}

static const struct iio_info adxl313_info = {
	.read_raw	= adxl313_read_raw,
	.write_raw	= adxl313_write_raw,
	.read_event_config = adxl313_read_event_config,
	.write_event_config = adxl313_write_event_config,
	.read_event_value = adxl313_read_event_value,
	.write_event_value = adxl313_write_event_value,
	.read_avail	= adxl313_read_freq_avail,
	.hwfifo_set_watermark = adxl313_set_watermark,
	.debugfs_reg_access = &adxl313_reg_access,
};

static int adxl313_setup(struct device *dev, struct adxl313_data *data,
			 int (*setup)(struct device *, struct regmap *))
{
	int ret;

	/*
	 * If sw reset available, ensures the device is in a consistent
	 * state after start up
	 */
	if (data->chip_info->soft_reset) {
		ret = regmap_write(data->regmap, ADXL313_REG_SOFT_RESET,
				   ADXL313_SOFT_RESET);
		if (ret)
			return ret;
	}

	if (setup) {
		ret = setup(dev, data->regmap);
		if (ret)
			return ret;
	}

	ret = data->chip_info->check_id(dev, data);
	if (ret)
		return ret;

	/* Sets the range to maximum, full resolution, if applicable */
	if (data->chip_info->variable_range) {
		ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT,
					 ADXL313_RANGE_MSK,
					 FIELD_PREP(ADXL313_RANGE_MSK, ADXL313_RANGE_MAX));
		if (ret)
			return ret;

		/* Enables full resolution */
		ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT,
					 ADXL313_FULL_RES, ADXL313_FULL_RES);
		if (ret)
			return ret;
	}

	/* Enables measurement mode */
	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
 * @regmap:	Register map of the device
 * @chip_info:	Structure containing device specific data
 * @setup:	Setup routine to be executed right before the standard device
 *		setup, can also be set to NULL if not required
 *
 * Return: 0 on success, negative errno on error cases
 */
int adxl313_core_probe(struct device *dev,
		       struct regmap *regmap,
		       const struct adxl313_chip_info *chip_info,
		       int (*setup)(struct device *, struct regmap *))
{
	struct adxl313_data *data;
	struct iio_dev *indio_dev;
	u8 int_line;
	u8 int_map_msk;
	int irq, ret;

	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
	if (!indio_dev)
		return -ENOMEM;

	data = iio_priv(indio_dev);
	data->regmap = regmap;
	data->chip_info = chip_info;

	mutex_init(&data->lock);

	indio_dev->name = chip_info->name;
	indio_dev->info = &adxl313_info;
	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) {
		dev_err(dev, "ADXL313 setup failed\n");
		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;

		/*
		 * Reset or configure the registers with reasonable default
		 * values. As having 0 in most cases may result in undesirable
		 * behavior if the interrupts are enabled.
		 */
		ret = regmap_write(data->regmap, ADXL313_REG_ACT_INACT_CTL, 0x00);
		if (ret)
			return ret;

		ret = regmap_write(data->regmap, ADXL313_REG_TIME_INACT, 5);
		if (ret)
			return ret;

		ret = regmap_write(data->regmap, ADXL313_REG_THRESH_INACT, 0x4f);
		if (ret)
			return ret;

		ret = regmap_write(data->regmap, ADXL313_REG_THRESH_ACT, 0x52);
		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");

MODULE_AUTHOR("Lucas Stankus <lucas.p.stankus@gmail.com>");
MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer core driver");
MODULE_LICENSE("GPL v2");