diff options
Diffstat (limited to 'drivers/iio/accel/adxl313_core.c')
-rw-r--r-- | drivers/iio/accel/adxl313_core.c | 923 |
1 files changed, 914 insertions, 9 deletions
diff --git a/drivers/iio/accel/adxl313_core.c b/drivers/iio/accel/adxl313_core.c index 46cca10e776f..9f5d4d2cb325 100644 --- a/drivers/iio/accel/adxl313_core.c +++ b/drivers/iio/accel/adxl313_core.c @@ -8,11 +8,62 @@ */ #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)), @@ -46,6 +97,30 @@ const struct regmap_access_table adxl314_readable_regs_table = { }; 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) { @@ -171,9 +246,10 @@ static const int adxl313_odr_freqs[][2] = { [9] = { 3200, 0 }, }; -#define ADXL313_ACCEL_CHANNEL(index, axis) { \ +#define ADXL313_ACCEL_CHANNEL(index, reg, axis) { \ .type = IIO_ACCEL, \ - .address = index, \ + .scan_index = (index), \ + .address = (reg), \ .modified = 1, \ .channel2 = IIO_MOD_##axis, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ @@ -183,14 +259,77 @@ static const int adxl313_odr_freqs[][2] = { .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, X), - ADXL313_ACCEL_CHANNEL(1, Y), - ADXL313_ACCEL_CHANNEL(2, Z), + 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, @@ -248,6 +387,230 @@ static int adxl313_read_freq_avail(struct iio_dev *indio_dev, } } +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) @@ -321,10 +684,474 @@ static int adxl313_write_raw(struct iio_dev *indio_dev, } } +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, @@ -369,9 +1196,20 @@ static int adxl313_setup(struct device *dev, struct adxl313_data *data, } /* Enables measurement mode */ - return regmap_update_bits(data->regmap, ADXL313_REG_POWER_CTL, - ADXL313_POWER_CTL_MSK, - ADXL313_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; } /** @@ -391,7 +1229,9 @@ int adxl313_core_probe(struct device *dev, { struct adxl313_data *data; struct iio_dev *indio_dev; - int ret; + u8 int_line; + u8 int_map_msk; + int irq, ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) @@ -408,6 +1248,7 @@ int adxl313_core_probe(struct device *dev, 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) { @@ -415,6 +1256,70 @@ int adxl313_core_probe(struct device *dev, 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"); |