root/drivers/iio/imu/fxos8700_core.c
// SPDX-License-Identifier: GPL-2.0
/*
 * FXOS8700 - NXP IMU (accelerometer plus magnetometer)
 *
 * IIO core driver for FXOS8700, with support for I2C/SPI busses
 *
 * TODO: Buffer, trigger, and IRQ support
 */
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/bitops.h>
#include <linux/bitfield.h>

#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>

#include "fxos8700.h"

/* Register Definitions */
#define FXOS8700_STATUS             0x00
#define FXOS8700_OUT_X_MSB          0x01
#define FXOS8700_OUT_X_LSB          0x02
#define FXOS8700_OUT_Y_MSB          0x03
#define FXOS8700_OUT_Y_LSB          0x04
#define FXOS8700_OUT_Z_MSB          0x05
#define FXOS8700_OUT_Z_LSB          0x06
#define FXOS8700_F_SETUP            0x09
#define FXOS8700_TRIG_CFG           0x0a
#define FXOS8700_SYSMOD             0x0b
#define FXOS8700_INT_SOURCE         0x0c
#define FXOS8700_WHO_AM_I           0x0d
#define FXOS8700_XYZ_DATA_CFG       0x0e
#define FXOS8700_HP_FILTER_CUTOFF   0x0f
#define FXOS8700_PL_STATUS          0x10
#define FXOS8700_PL_CFG             0x11
#define FXOS8700_PL_COUNT           0x12
#define FXOS8700_PL_BF_ZCOMP        0x13
#define FXOS8700_PL_THS_REG         0x14
#define FXOS8700_A_FFMT_CFG         0x15
#define FXOS8700_A_FFMT_SRC         0x16
#define FXOS8700_A_FFMT_THS         0x17
#define FXOS8700_A_FFMT_COUNT       0x18
#define FXOS8700_TRANSIENT_CFG      0x1d
#define FXOS8700_TRANSIENT_SRC      0x1e
#define FXOS8700_TRANSIENT_THS      0x1f
#define FXOS8700_TRANSIENT_COUNT    0x20
#define FXOS8700_PULSE_CFG          0x21
#define FXOS8700_PULSE_SRC          0x22
#define FXOS8700_PULSE_THSX         0x23
#define FXOS8700_PULSE_THSY         0x24
#define FXOS8700_PULSE_THSZ         0x25
#define FXOS8700_PULSE_TMLT         0x26
#define FXOS8700_PULSE_LTCY         0x27
#define FXOS8700_PULSE_WIND         0x28
#define FXOS8700_ASLP_COUNT         0x29
#define FXOS8700_CTRL_REG1          0x2a
#define FXOS8700_CTRL_REG2          0x2b
#define FXOS8700_CTRL_REG3          0x2c
#define FXOS8700_CTRL_REG4          0x2d
#define FXOS8700_CTRL_REG5          0x2e
#define FXOS8700_OFF_X              0x2f
#define FXOS8700_OFF_Y              0x30
#define FXOS8700_OFF_Z              0x31
#define FXOS8700_M_DR_STATUS        0x32
#define FXOS8700_M_OUT_X_MSB        0x33
#define FXOS8700_M_OUT_X_LSB        0x34
#define FXOS8700_M_OUT_Y_MSB        0x35
#define FXOS8700_M_OUT_Y_LSB        0x36
#define FXOS8700_M_OUT_Z_MSB        0x37
#define FXOS8700_M_OUT_Z_LSB        0x38
#define FXOS8700_CMP_X_MSB          0x39
#define FXOS8700_CMP_X_LSB          0x3a
#define FXOS8700_CMP_Y_MSB          0x3b
#define FXOS8700_CMP_Y_LSB          0x3c
#define FXOS8700_CMP_Z_MSB          0x3d
#define FXOS8700_CMP_Z_LSB          0x3e
#define FXOS8700_M_OFF_X_MSB        0x3f
#define FXOS8700_M_OFF_X_LSB        0x40
#define FXOS8700_M_OFF_Y_MSB        0x41
#define FXOS8700_M_OFF_Y_LSB        0x42
#define FXOS8700_M_OFF_Z_MSB        0x43
#define FXOS8700_M_OFF_Z_LSB        0x44
#define FXOS8700_MAX_X_MSB          0x45
#define FXOS8700_MAX_X_LSB          0x46
#define FXOS8700_MAX_Y_MSB          0x47
#define FXOS8700_MAX_Y_LSB          0x48
#define FXOS8700_MAX_Z_MSB          0x49
#define FXOS8700_MAX_Z_LSB          0x4a
#define FXOS8700_MIN_X_MSB          0x4b
#define FXOS8700_MIN_X_LSB          0x4c
#define FXOS8700_MIN_Y_MSB          0x4d
#define FXOS8700_MIN_Y_LSB          0x4e
#define FXOS8700_MIN_Z_MSB          0x4f
#define FXOS8700_MIN_Z_LSB          0x50
#define FXOS8700_TEMP               0x51
#define FXOS8700_M_THS_CFG          0x52
#define FXOS8700_M_THS_SRC          0x53
#define FXOS8700_M_THS_X_MSB        0x54
#define FXOS8700_M_THS_X_LSB        0x55
#define FXOS8700_M_THS_Y_MSB        0x56
#define FXOS8700_M_THS_Y_LSB        0x57
#define FXOS8700_M_THS_Z_MSB        0x58
#define FXOS8700_M_THS_Z_LSB        0x59
#define FXOS8700_M_THS_COUNT        0x5a
#define FXOS8700_M_CTRL_REG1        0x5b
#define FXOS8700_M_CTRL_REG2        0x5c
#define FXOS8700_M_CTRL_REG3        0x5d
#define FXOS8700_M_INT_SRC          0x5e
#define FXOS8700_A_VECM_CFG         0x5f
#define FXOS8700_A_VECM_THS_MSB     0x60
#define FXOS8700_A_VECM_THS_LSB     0x61
#define FXOS8700_A_VECM_CNT         0x62
#define FXOS8700_A_VECM_INITX_MSB   0x63
#define FXOS8700_A_VECM_INITX_LSB   0x64
#define FXOS8700_A_VECM_INITY_MSB   0x65
#define FXOS8700_A_VECM_INITY_LSB   0x66
#define FXOS8700_A_VECM_INITZ_MSB   0x67
#define FXOS8700_A_VECM_INITZ_LSB   0x68
#define FXOS8700_M_VECM_CFG         0x69
#define FXOS8700_M_VECM_THS_MSB     0x6a
#define FXOS8700_M_VECM_THS_LSB     0x6b
#define FXOS8700_M_VECM_CNT         0x6c
#define FXOS8700_M_VECM_INITX_MSB   0x6d
#define FXOS8700_M_VECM_INITX_LSB   0x6e
#define FXOS8700_M_VECM_INITY_MSB   0x6f
#define FXOS8700_M_VECM_INITY_LSB   0x70
#define FXOS8700_M_VECM_INITZ_MSB   0x71
#define FXOS8700_M_VECM_INITZ_LSB   0x72
#define FXOS8700_A_FFMT_THS_X_MSB   0x73
#define FXOS8700_A_FFMT_THS_X_LSB   0x74
#define FXOS8700_A_FFMT_THS_Y_MSB   0x75
#define FXOS8700_A_FFMT_THS_Y_LSB   0x76
#define FXOS8700_A_FFMT_THS_Z_MSB   0x77
#define FXOS8700_A_FFMT_THS_Z_LSB   0x78
#define FXOS8700_A_TRAN_INIT_MSB    0x79
#define FXOS8700_A_TRAN_INIT_LSB_X  0x7a
#define FXOS8700_A_TRAN_INIT_LSB_Y  0x7b
#define FXOS8700_A_TRAN_INIT_LSB_Z  0x7d
#define FXOS8700_TM_NVM_LOCK        0x7e
#define FXOS8700_NVM_DATA0_35       0x80
#define FXOS8700_NVM_DATA_BNK3      0xa4
#define FXOS8700_NVM_DATA_BNK2      0xa5
#define FXOS8700_NVM_DATA_BNK1      0xa6
#define FXOS8700_NVM_DATA_BNK0      0xa7

/* Bit definitions for FXOS8700_CTRL_REG1 */
#define FXOS8700_CTRL_ODR_MAX       0x00
#define FXOS8700_CTRL_ODR_MSK       GENMASK(5, 3)

/* Bit definitions for FXOS8700_M_CTRL_REG1 */
#define FXOS8700_HMS_MASK           GENMASK(1, 0)
#define FXOS8700_OS_MASK            GENMASK(4, 2)

/* Bit definitions for FXOS8700_M_CTRL_REG2 */
#define FXOS8700_MAXMIN_RST         BIT(2)
#define FXOS8700_MAXMIN_DIS_THS     BIT(3)
#define FXOS8700_MAXMIN_DIS         BIT(4)

#define FXOS8700_ACTIVE             0x01
#define FXOS8700_ACTIVE_MIN_USLEEP  4000 /* from table 6 in datasheet */

#define FXOS8700_DEVICE_ID          0xC7
#define FXOS8700_PRE_DEVICE_ID      0xC4
#define FXOS8700_DATA_BUF_SIZE      3

struct fxos8700_data {
        struct regmap *regmap;
        struct iio_trigger *trig;
        __be16 buf[FXOS8700_DATA_BUF_SIZE] __aligned(IIO_DMA_MINALIGN);
};

/* Regmap info */
static const struct regmap_range read_range[] = {
        {
                .range_min = FXOS8700_STATUS,
                .range_max = FXOS8700_A_FFMT_COUNT,
        }, {
                .range_min = FXOS8700_TRANSIENT_CFG,
                .range_max = FXOS8700_A_FFMT_THS_Z_LSB,
        },
};

static const struct regmap_range write_range[] = {
        {
                .range_min = FXOS8700_F_SETUP,
                .range_max = FXOS8700_TRIG_CFG,
        }, {
                .range_min = FXOS8700_XYZ_DATA_CFG,
                .range_max = FXOS8700_HP_FILTER_CUTOFF,
        }, {
                .range_min = FXOS8700_PL_CFG,
                .range_max = FXOS8700_A_FFMT_CFG,
        }, {
                .range_min = FXOS8700_A_FFMT_THS,
                .range_max = FXOS8700_TRANSIENT_CFG,
        }, {
                .range_min = FXOS8700_TRANSIENT_THS,
                .range_max = FXOS8700_PULSE_CFG,
        }, {
                .range_min = FXOS8700_PULSE_THSX,
                .range_max = FXOS8700_OFF_Z,
        }, {
                .range_min = FXOS8700_M_OFF_X_MSB,
                .range_max = FXOS8700_M_OFF_Z_LSB,
        }, {
                .range_min = FXOS8700_M_THS_CFG,
                .range_max = FXOS8700_M_THS_CFG,
        }, {
                .range_min = FXOS8700_M_THS_X_MSB,
                .range_max = FXOS8700_M_CTRL_REG3,
        }, {
                .range_min = FXOS8700_A_VECM_CFG,
                .range_max = FXOS8700_A_FFMT_THS_Z_LSB,
        },
};

static const struct regmap_access_table driver_read_table = {
        .yes_ranges =   read_range,
        .n_yes_ranges = ARRAY_SIZE(read_range),
};

static const struct regmap_access_table driver_write_table = {
        .yes_ranges =   write_range,
        .n_yes_ranges = ARRAY_SIZE(write_range),
};

const struct regmap_config fxos8700_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,
        .max_register = FXOS8700_NVM_DATA_BNK0,
        .rd_table = &driver_read_table,
        .wr_table = &driver_write_table,
};
EXPORT_SYMBOL(fxos8700_regmap_config);

#define FXOS8700_CHANNEL(_type, _axis) {                        \
        .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_SCALE) |  \
                BIT(IIO_CHAN_INFO_SAMP_FREQ),                   \
}

enum fxos8700_accel_scale_bits {
        MODE_2G = 0,
        MODE_4G,
        MODE_8G,
};

/* scan indexes follow DATA register order */
enum fxos8700_scan_axis {
        FXOS8700_SCAN_ACCEL_X = 0,
        FXOS8700_SCAN_ACCEL_Y,
        FXOS8700_SCAN_ACCEL_Z,
        FXOS8700_SCAN_MAGN_X,
        FXOS8700_SCAN_MAGN_Y,
        FXOS8700_SCAN_MAGN_Z,
        FXOS8700_SCAN_RHALL,
        FXOS8700_SCAN_TIMESTAMP,
};

enum fxos8700_sensor {
        FXOS8700_ACCEL  = 0,
        FXOS8700_MAGN,
        FXOS8700_NUM_SENSORS /* must be last */
};

enum fxos8700_int_pin {
        FXOS8700_PIN_INT1,
        FXOS8700_PIN_INT2
};

struct fxos8700_scale {
        u8 bits;
        int uscale;
};

struct fxos8700_odr {
        u8 bits;
        int odr;
        int uodr;
};

static const struct fxos8700_scale fxos8700_accel_scale[] = {
        { MODE_2G, 244},
        { MODE_4G, 488},
        { MODE_8G, 976},
};

/*
 * Accellerometer and magnetometer have the same ODR options, set in the
 * CTRL_REG1 register. ODR is halved when using both sensors at once in
 * hybrid mode.
 */
static const struct fxos8700_odr fxos8700_odr[] = {
        {0x00, 800, 0},
        {0x01, 400, 0},
        {0x02, 200, 0},
        {0x03, 100, 0},
        {0x04, 50, 0},
        {0x05, 12, 500000},
        {0x06, 6, 250000},
        {0x07, 1, 562500},
};

static const struct iio_chan_spec fxos8700_channels[] = {
        FXOS8700_CHANNEL(IIO_ACCEL, X),
        FXOS8700_CHANNEL(IIO_ACCEL, Y),
        FXOS8700_CHANNEL(IIO_ACCEL, Z),
        FXOS8700_CHANNEL(IIO_MAGN, X),
        FXOS8700_CHANNEL(IIO_MAGN, Y),
        FXOS8700_CHANNEL(IIO_MAGN, Z),
        IIO_CHAN_SOFT_TIMESTAMP(FXOS8700_SCAN_TIMESTAMP),
};

static enum fxos8700_sensor fxos8700_to_sensor(enum iio_chan_type iio_type)
{
        switch (iio_type) {
        case IIO_ACCEL:
                return FXOS8700_ACCEL;
        case IIO_MAGN:
                return FXOS8700_MAGN;
        default:
                return -EINVAL;
        }
}

static int fxos8700_set_active_mode(struct fxos8700_data *data,
                                    enum fxos8700_sensor t, bool mode)
{
        int ret;

        ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, mode);
        if (ret)
                return ret;

        usleep_range(FXOS8700_ACTIVE_MIN_USLEEP,
                     FXOS8700_ACTIVE_MIN_USLEEP + 1000);

        return 0;
}

static int fxos8700_set_scale(struct fxos8700_data *data,
                              enum fxos8700_sensor t, int uscale)
{
        int i, ret, val;
        bool active_mode;
        static const int scale_num = ARRAY_SIZE(fxos8700_accel_scale);
        struct device *dev = regmap_get_device(data->regmap);

        if (t == FXOS8700_MAGN) {
                dev_err(dev, "Magnetometer scale is locked at 0.001Gs\n");
                return -EINVAL;
        }

        /*
         * When device is in active mode, it failed to set an ACCEL
         * full-scale range(2g/4g/8g) in FXOS8700_XYZ_DATA_CFG.
         * This is not align with the datasheet, but it is a fxos8700
         * chip behavier. Set the device in standby mode before setting
         * an ACCEL full-scale range.
         */
        ret = regmap_read(data->regmap, FXOS8700_CTRL_REG1, &val);
        if (ret)
                return ret;

        active_mode = val & FXOS8700_ACTIVE;
        if (active_mode) {
                ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1,
                                   val & ~FXOS8700_ACTIVE);
                if (ret)
                        return ret;
        }

        for (i = 0; i < scale_num; i++)
                if (fxos8700_accel_scale[i].uscale == uscale)
                        break;

        if (i == scale_num)
                return -EINVAL;

        ret = regmap_write(data->regmap, FXOS8700_XYZ_DATA_CFG,
                            fxos8700_accel_scale[i].bits);
        if (ret)
                return ret;
        return regmap_write(data->regmap, FXOS8700_CTRL_REG1,
                                  active_mode);
}

static int fxos8700_get_scale(struct fxos8700_data *data,
                              enum fxos8700_sensor t, int *uscale)
{
        int i, ret, val;
        static const int scale_num = ARRAY_SIZE(fxos8700_accel_scale);

        if (t == FXOS8700_MAGN) {
                *uscale = 1000; /* Magnetometer is locked at 0.001Gs */
                return 0;
        }

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

        for (i = 0; i < scale_num; i++) {
                if (fxos8700_accel_scale[i].bits == (val & 0x3)) {
                        *uscale = fxos8700_accel_scale[i].uscale;
                        return 0;
                }
        }

        return -EINVAL;
}

static int fxos8700_get_data(struct fxos8700_data *data, int chan_type,
                             int axis, int *val)
{
        u8 base, reg;
        s16 tmp;
        int ret;

        /*
         * Different register base addresses varies with channel types.
         * This bug hasn't been noticed before because using an enum is
         * really hard to read. Use an a switch statement to take over that.
         */
        switch (chan_type) {
        case IIO_ACCEL:
                base = FXOS8700_OUT_X_MSB;
                break;
        case IIO_MAGN:
                base = FXOS8700_M_OUT_X_MSB;
                break;
        default:
                return -EINVAL;
        }

        /* Block read 6 bytes of device output registers to avoid data loss */
        ret = regmap_bulk_read(data->regmap, base, data->buf,
                               sizeof(data->buf));
        if (ret)
                return ret;

        /* Convert axis to buffer index */
        reg = axis - IIO_MOD_X;

        /*
         * Convert to native endianness. The accel data and magn data
         * are signed, so a forced type conversion is needed.
         */
        tmp = be16_to_cpu(data->buf[reg]);

        /*
         * ACCEL output data registers contain the X-axis, Y-axis, and Z-axis
         * 14-bit left-justified sample data and MAGN output data registers
         * contain the X-axis, Y-axis, and Z-axis 16-bit sample data. Apply
         * a signed 2 bits right shift to the readback raw data from ACCEL
         * output data register and keep that from MAGN sensor as the origin.
         * Value should be extended to 32 bit.
         */
        switch (chan_type) {
        case IIO_ACCEL:
                tmp = tmp >> 2;
                break;
        case IIO_MAGN:
                /* Nothing to do */
                break;
        default:
                return -EINVAL;
        }

        /* Convert to native endianness */
        *val = sign_extend32(tmp, 15);

        return 0;
}

static int fxos8700_set_odr(struct fxos8700_data *data, enum fxos8700_sensor t,
                            int odr, int uodr)
{
        int i, ret, val;
        bool active_mode;
        static const int odr_num = ARRAY_SIZE(fxos8700_odr);

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

        active_mode = val & FXOS8700_ACTIVE;

        if (active_mode) {
                /*
                 * The device must be in standby mode to change any of the
                 * other fields within CTRL_REG1
                 */
                ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1,
                                   val & ~FXOS8700_ACTIVE);
                if (ret)
                        return ret;
        }

        for (i = 0; i < odr_num; i++)
                if (fxos8700_odr[i].odr == odr && fxos8700_odr[i].uodr == uodr)
                        break;

        if (i >= odr_num)
                return -EINVAL;

        val &= ~FXOS8700_CTRL_ODR_MSK;
        val |= FIELD_PREP(FXOS8700_CTRL_ODR_MSK, fxos8700_odr[i].bits) | FXOS8700_ACTIVE;
        return regmap_write(data->regmap, FXOS8700_CTRL_REG1, val);
}

static int fxos8700_get_odr(struct fxos8700_data *data, enum fxos8700_sensor t,
                            int *odr, int *uodr)
{
        int i, val, ret;
        static const int odr_num = ARRAY_SIZE(fxos8700_odr);

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

        val = FIELD_GET(FXOS8700_CTRL_ODR_MSK, val);

        for (i = 0; i < odr_num; i++)
                if (val == fxos8700_odr[i].bits)
                        break;

        if (i >= odr_num)
                return -EINVAL;

        *odr = fxos8700_odr[i].odr;
        *uodr = fxos8700_odr[i].uodr;

        return 0;
}

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

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                ret = fxos8700_get_data(data, chan->type, chan->channel2, val);
                if (ret)
                        return ret;
                return IIO_VAL_INT;
        case IIO_CHAN_INFO_SCALE:
                *val = 0;
                ret = fxos8700_get_scale(data, fxos8700_to_sensor(chan->type),
                                         val2);
                return ret ? ret : IIO_VAL_INT_PLUS_MICRO;
        case IIO_CHAN_INFO_SAMP_FREQ:
                ret = fxos8700_get_odr(data, fxos8700_to_sensor(chan->type),
                                       val, val2);
                return ret ? ret : IIO_VAL_INT_PLUS_MICRO;
        default:
                return -EINVAL;
        }
}

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

        switch (mask) {
        case IIO_CHAN_INFO_SCALE:
                return fxos8700_set_scale(data, fxos8700_to_sensor(chan->type),
                                          val2);
        case IIO_CHAN_INFO_SAMP_FREQ:
                return fxos8700_set_odr(data, fxos8700_to_sensor(chan->type),
                                        val, val2);
        default:
                return -EINVAL;
        }
}

static IIO_CONST_ATTR(in_accel_sampling_frequency_available,
                      "1.5625 6.25 12.5 50 100 200 400 800");
static IIO_CONST_ATTR(in_magn_sampling_frequency_available,
                      "1.5625 6.25 12.5 50 100 200 400 800");
static IIO_CONST_ATTR(in_accel_scale_available, "0.000244 0.000488 0.000976");
static IIO_CONST_ATTR(in_magn_scale_available, "0.001000");

static struct attribute *fxos8700_attrs[] = {
        &iio_const_attr_in_accel_sampling_frequency_available.dev_attr.attr,
        &iio_const_attr_in_magn_sampling_frequency_available.dev_attr.attr,
        &iio_const_attr_in_accel_scale_available.dev_attr.attr,
        &iio_const_attr_in_magn_scale_available.dev_attr.attr,
        NULL,
};

static const struct attribute_group fxos8700_attrs_group = {
        .attrs = fxos8700_attrs,
};

static const struct iio_info fxos8700_info = {
        .read_raw = fxos8700_read_raw,
        .write_raw = fxos8700_write_raw,
        .attrs = &fxos8700_attrs_group,
};

static int fxos8700_chip_init(struct fxos8700_data *data, bool use_spi)
{
        int ret;
        unsigned int val;
        struct device *dev = regmap_get_device(data->regmap);

        ret = regmap_read(data->regmap, FXOS8700_WHO_AM_I, &val);
        if (ret) {
                dev_err(dev, "Error reading chip id\n");
                return ret;
        }
        if (val != FXOS8700_DEVICE_ID && val != FXOS8700_PRE_DEVICE_ID) {
                dev_err(dev, "Wrong chip id, got %x expected %x or %x\n",
                        val, FXOS8700_DEVICE_ID, FXOS8700_PRE_DEVICE_ID);
                return -ENODEV;
        }

        ret = fxos8700_set_active_mode(data, FXOS8700_ACCEL, true);
        if (ret)
                return ret;

        ret = fxos8700_set_active_mode(data, FXOS8700_MAGN, true);
        if (ret)
                return ret;

        /*
         * The device must be in standby mode to change any of the other fields
         * within CTRL_REG1
         */
        ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, 0x00);
        if (ret)
                return ret;

        /* Set max oversample ratio (OSR) and both devices active */
        ret = regmap_write(data->regmap, FXOS8700_M_CTRL_REG1,
                           FXOS8700_HMS_MASK | FXOS8700_OS_MASK);
        if (ret)
                return ret;

        /* Disable and rst min/max measurements & threshold */
        ret = regmap_write(data->regmap, FXOS8700_M_CTRL_REG2,
                           FXOS8700_MAXMIN_RST | FXOS8700_MAXMIN_DIS_THS |
                           FXOS8700_MAXMIN_DIS);
        if (ret)
                return ret;

        /*
         * Set max full-scale range (+/-8G) for ACCEL sensor in chip
         * initialization then activate the device.
         */
        ret = regmap_write(data->regmap, FXOS8700_XYZ_DATA_CFG, MODE_8G);
        if (ret)
                return ret;

        /* Max ODR (800Hz individual or 400Hz hybrid), active mode */
        return regmap_update_bits(data->regmap, FXOS8700_CTRL_REG1,
                                FXOS8700_CTRL_ODR_MSK | FXOS8700_ACTIVE,
                                FIELD_PREP(FXOS8700_CTRL_ODR_MSK, FXOS8700_CTRL_ODR_MAX) |
                                FXOS8700_ACTIVE);
}

static void fxos8700_chip_uninit(void *data)
{
        struct fxos8700_data *fxos8700_data = data;

        fxos8700_set_active_mode(fxos8700_data, FXOS8700_ACCEL, false);
        fxos8700_set_active_mode(fxos8700_data, FXOS8700_MAGN, false);
}

int fxos8700_core_probe(struct device *dev, struct regmap *regmap,
                        const char *name, bool use_spi)
{
        struct iio_dev *indio_dev;
        struct fxos8700_data *data;
        int ret;

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

        data = iio_priv(indio_dev);
        dev_set_drvdata(dev, indio_dev);
        data->regmap = regmap;

        ret = fxos8700_chip_init(data, use_spi);
        if (ret)
                return ret;

        ret = devm_add_action_or_reset(dev, fxos8700_chip_uninit, data);
        if (ret)
                return ret;

        indio_dev->channels = fxos8700_channels;
        indio_dev->num_channels = ARRAY_SIZE(fxos8700_channels);
        indio_dev->name = name ? name : "fxos8700";
        indio_dev->modes = INDIO_DIRECT_MODE;
        indio_dev->info = &fxos8700_info;

        return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_GPL(fxos8700_core_probe);

MODULE_AUTHOR("Robert Jones <rjones@gateworks.com>");
MODULE_DESCRIPTION("FXOS8700 6-Axis Acc and Mag Combo Sensor driver");
MODULE_LICENSE("GPL v2");