// SPDX-License-Identifier: GPL-2.0-only /* * ADXL313 3-Axis Digital Accelerometer * * Copyright (c) 2021 Lucas Stankus * * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf */ #include #include #include #include #include #include #include #include #include #include #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 "); MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer core driver"); MODULE_LICENSE("GPL v2");