root/drivers/iio/accel/adxl313_core.c
// 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/cleanup.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;

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

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

        return le16_to_cpu(data->transf_buf);
}

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));
        if (ret)
                return ret;

        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;

        ret = devm_mutex_init(dev, &data->lock);
        if (ret)
                return ret;

        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)
                return dev_err_probe(dev, ret, "ADXL313 setup failed\n");

        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");