| // 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, ®val); |
| 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, ®val); |
| 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, ®val); |
| 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, ®val); |
| 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, ®val); |
| 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, ®val); |
| 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), ®val); |
| 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, ®val); |
| 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, ®val); |
| 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, ®val); |
| |
| 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, ®val); |
| 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, ®val); |
| 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"); |