| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Driver for NXP FXAS21002C Gyroscope - Core |
| * |
| * Copyright (C) 2019 Linaro Ltd. |
| */ |
| |
| #include <linux/interrupt.h> |
| #include <linux/module.h> |
| #include <linux/of_irq.h> |
| #include <linux/pm.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.h> |
| |
| #include <linux/iio/events.h> |
| #include <linux/iio/iio.h> |
| #include <linux/iio/buffer.h> |
| #include <linux/iio/sysfs.h> |
| #include <linux/iio/trigger.h> |
| #include <linux/iio/trigger_consumer.h> |
| #include <linux/iio/triggered_buffer.h> |
| |
| #include "fxas21002c.h" |
| |
| #define FXAS21002C_CHIP_ID_1 0xD6 |
| #define FXAS21002C_CHIP_ID_2 0xD7 |
| |
| enum fxas21002c_mode_state { |
| FXAS21002C_MODE_STANDBY, |
| FXAS21002C_MODE_READY, |
| FXAS21002C_MODE_ACTIVE, |
| }; |
| |
| #define FXAS21002C_STANDBY_ACTIVE_TIME_MS 62 |
| #define FXAS21002C_READY_ACTIVE_TIME_MS 7 |
| |
| #define FXAS21002C_ODR_LIST_MAX 10 |
| |
| #define FXAS21002C_SCALE_FRACTIONAL 32 |
| #define FXAS21002C_RANGE_LIMIT_DOUBLE 2000 |
| |
| #define FXAS21002C_AXIS_TO_REG(axis) (FXAS21002C_REG_OUT_X_MSB + ((axis) * 2)) |
| |
| static const int fxas21002c_odr_values[] = { |
| 800, 400, 200, 100, 50, 25, 12, 12 |
| }; |
| |
| /* |
| * These values are taken from the low-pass filter cutoff frequency calculated |
| * ODR * 0.lpf_values. So, for ODR = 800Hz with a lpf value = 0.32 |
| * => LPF cutoff frequency = 800 * 0.32 = 256 Hz |
| */ |
| static const int fxas21002c_lpf_values[] = { |
| 32, 16, 8 |
| }; |
| |
| /* |
| * These values are taken from the high-pass filter cutoff frequency calculated |
| * ODR * 0.0hpf_values. So, for ODR = 800Hz with a hpf value = 0.018750 |
| * => HPF cutoff frequency = 800 * 0.018750 = 15 Hz |
| */ |
| static const int fxas21002c_hpf_values[] = { |
| 18750, 9625, 4875, 2475 |
| }; |
| |
| static const int fxas21002c_range_values[] = { |
| 4000, 2000, 1000, 500, 250 |
| }; |
| |
| struct fxas21002c_data { |
| u8 chip_id; |
| enum fxas21002c_mode_state mode; |
| enum fxas21002c_mode_state prev_mode; |
| |
| struct mutex lock; /* serialize data access */ |
| struct regmap *regmap; |
| struct regmap_field *regmap_fields[F_MAX_FIELDS]; |
| struct iio_trigger *dready_trig; |
| s64 timestamp; |
| int irq; |
| |
| struct regulator *vdd; |
| struct regulator *vddio; |
| |
| /* |
| * DMA (thus cache coherency maintenance) requires the |
| * transfer buffers to live in their own cache lines. |
| */ |
| s16 buffer[8] ____cacheline_aligned; |
| }; |
| |
| enum fxas21002c_channel_index { |
| CHANNEL_SCAN_INDEX_X, |
| CHANNEL_SCAN_INDEX_Y, |
| CHANNEL_SCAN_INDEX_Z, |
| CHANNEL_SCAN_MAX, |
| }; |
| |
| static int fxas21002c_odr_hz_from_value(struct fxas21002c_data *data, u8 value) |
| { |
| int odr_value_max = ARRAY_SIZE(fxas21002c_odr_values) - 1; |
| |
| value = min_t(u8, value, odr_value_max); |
| |
| return fxas21002c_odr_values[value]; |
| } |
| |
| static int fxas21002c_odr_value_from_hz(struct fxas21002c_data *data, |
| unsigned int hz) |
| { |
| int odr_table_size = ARRAY_SIZE(fxas21002c_odr_values); |
| int i; |
| |
| for (i = 0; i < odr_table_size; i++) |
| if (fxas21002c_odr_values[i] == hz) |
| return i; |
| |
| return -EINVAL; |
| } |
| |
| static int fxas21002c_lpf_bw_from_value(struct fxas21002c_data *data, u8 value) |
| { |
| int lpf_value_max = ARRAY_SIZE(fxas21002c_lpf_values) - 1; |
| |
| value = min_t(u8, value, lpf_value_max); |
| |
| return fxas21002c_lpf_values[value]; |
| } |
| |
| static int fxas21002c_lpf_value_from_bw(struct fxas21002c_data *data, |
| unsigned int hz) |
| { |
| int lpf_table_size = ARRAY_SIZE(fxas21002c_lpf_values); |
| int i; |
| |
| for (i = 0; i < lpf_table_size; i++) |
| if (fxas21002c_lpf_values[i] == hz) |
| return i; |
| |
| return -EINVAL; |
| } |
| |
| static int fxas21002c_hpf_sel_from_value(struct fxas21002c_data *data, u8 value) |
| { |
| int hpf_value_max = ARRAY_SIZE(fxas21002c_hpf_values) - 1; |
| |
| value = min_t(u8, value, hpf_value_max); |
| |
| return fxas21002c_hpf_values[value]; |
| } |
| |
| static int fxas21002c_hpf_value_from_sel(struct fxas21002c_data *data, |
| unsigned int hz) |
| { |
| int hpf_table_size = ARRAY_SIZE(fxas21002c_hpf_values); |
| int i; |
| |
| for (i = 0; i < hpf_table_size; i++) |
| if (fxas21002c_hpf_values[i] == hz) |
| return i; |
| |
| return -EINVAL; |
| } |
| |
| static int fxas21002c_range_fs_from_value(struct fxas21002c_data *data, |
| u8 value) |
| { |
| int range_value_max = ARRAY_SIZE(fxas21002c_range_values) - 1; |
| unsigned int fs_double; |
| int ret; |
| |
| /* We need to check if FS_DOUBLE is enabled to offset the value */ |
| ret = regmap_field_read(data->regmap_fields[F_FS_DOUBLE], &fs_double); |
| if (ret < 0) |
| return ret; |
| |
| if (!fs_double) |
| value += 1; |
| |
| value = min_t(u8, value, range_value_max); |
| |
| return fxas21002c_range_values[value]; |
| } |
| |
| static int fxas21002c_range_value_from_fs(struct fxas21002c_data *data, |
| unsigned int range) |
| { |
| int range_table_size = ARRAY_SIZE(fxas21002c_range_values); |
| bool found = false; |
| int fs_double = 0; |
| int ret; |
| int i; |
| |
| for (i = 0; i < range_table_size; i++) |
| if (fxas21002c_range_values[i] == range) { |
| found = true; |
| break; |
| } |
| |
| if (!found) |
| return -EINVAL; |
| |
| if (range > FXAS21002C_RANGE_LIMIT_DOUBLE) |
| fs_double = 1; |
| |
| ret = regmap_field_write(data->regmap_fields[F_FS_DOUBLE], fs_double); |
| if (ret < 0) |
| return ret; |
| |
| return i; |
| } |
| |
| static int fxas21002c_mode_get(struct fxas21002c_data *data) |
| { |
| unsigned int active; |
| unsigned int ready; |
| int ret; |
| |
| ret = regmap_field_read(data->regmap_fields[F_ACTIVE], &active); |
| if (ret < 0) |
| return ret; |
| if (active) |
| return FXAS21002C_MODE_ACTIVE; |
| |
| ret = regmap_field_read(data->regmap_fields[F_READY], &ready); |
| if (ret < 0) |
| return ret; |
| if (ready) |
| return FXAS21002C_MODE_READY; |
| |
| return FXAS21002C_MODE_STANDBY; |
| } |
| |
| static int fxas21002c_mode_set(struct fxas21002c_data *data, |
| enum fxas21002c_mode_state mode) |
| { |
| int ret; |
| |
| if (mode == data->mode) |
| return 0; |
| |
| if (mode == FXAS21002C_MODE_READY) |
| ret = regmap_field_write(data->regmap_fields[F_READY], 1); |
| else |
| ret = regmap_field_write(data->regmap_fields[F_READY], 0); |
| if (ret < 0) |
| return ret; |
| |
| if (mode == FXAS21002C_MODE_ACTIVE) |
| ret = regmap_field_write(data->regmap_fields[F_ACTIVE], 1); |
| else |
| ret = regmap_field_write(data->regmap_fields[F_ACTIVE], 0); |
| if (ret < 0) |
| return ret; |
| |
| /* if going to active wait the setup times */ |
| if (mode == FXAS21002C_MODE_ACTIVE && |
| data->mode == FXAS21002C_MODE_STANDBY) |
| msleep_interruptible(FXAS21002C_STANDBY_ACTIVE_TIME_MS); |
| |
| if (data->mode == FXAS21002C_MODE_READY) |
| msleep_interruptible(FXAS21002C_READY_ACTIVE_TIME_MS); |
| |
| data->prev_mode = data->mode; |
| data->mode = mode; |
| |
| return ret; |
| } |
| |
| static int fxas21002c_write(struct fxas21002c_data *data, |
| enum fxas21002c_fields field, int bits) |
| { |
| int actual_mode; |
| int ret; |
| |
| mutex_lock(&data->lock); |
| |
| actual_mode = fxas21002c_mode_get(data); |
| if (actual_mode < 0) { |
| ret = actual_mode; |
| goto out_unlock; |
| } |
| |
| ret = fxas21002c_mode_set(data, FXAS21002C_MODE_READY); |
| if (ret < 0) |
| goto out_unlock; |
| |
| ret = regmap_field_write(data->regmap_fields[field], bits); |
| if (ret < 0) |
| goto out_unlock; |
| |
| ret = fxas21002c_mode_set(data, data->prev_mode); |
| |
| out_unlock: |
| mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int fxas21002c_pm_get(struct fxas21002c_data *data) |
| { |
| return pm_runtime_resume_and_get(regmap_get_device(data->regmap)); |
| } |
| |
| static int fxas21002c_pm_put(struct fxas21002c_data *data) |
| { |
| struct device *dev = regmap_get_device(data->regmap); |
| |
| pm_runtime_mark_last_busy(dev); |
| |
| return pm_runtime_put_autosuspend(dev); |
| } |
| |
| static int fxas21002c_temp_get(struct fxas21002c_data *data, int *val) |
| { |
| struct device *dev = regmap_get_device(data->regmap); |
| unsigned int temp; |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = fxas21002c_pm_get(data); |
| if (ret < 0) |
| goto data_unlock; |
| |
| ret = regmap_field_read(data->regmap_fields[F_TEMP], &temp); |
| if (ret < 0) { |
| dev_err(dev, "failed to read temp: %d\n", ret); |
| fxas21002c_pm_put(data); |
| goto data_unlock; |
| } |
| |
| *val = sign_extend32(temp, 7); |
| |
| ret = fxas21002c_pm_put(data); |
| if (ret < 0) |
| goto data_unlock; |
| |
| ret = IIO_VAL_INT; |
| |
| data_unlock: |
| mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int fxas21002c_axis_get(struct fxas21002c_data *data, |
| int index, int *val) |
| { |
| struct device *dev = regmap_get_device(data->regmap); |
| __be16 axis_be; |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = fxas21002c_pm_get(data); |
| if (ret < 0) |
| goto data_unlock; |
| |
| ret = regmap_bulk_read(data->regmap, FXAS21002C_AXIS_TO_REG(index), |
| &axis_be, sizeof(axis_be)); |
| if (ret < 0) { |
| dev_err(dev, "failed to read axis: %d: %d\n", index, ret); |
| fxas21002c_pm_put(data); |
| goto data_unlock; |
| } |
| |
| *val = sign_extend32(be16_to_cpu(axis_be), 15); |
| |
| ret = fxas21002c_pm_put(data); |
| if (ret < 0) |
| goto data_unlock; |
| |
| ret = IIO_VAL_INT; |
| |
| data_unlock: |
| mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int fxas21002c_odr_get(struct fxas21002c_data *data, int *odr) |
| { |
| unsigned int odr_bits; |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = regmap_field_read(data->regmap_fields[F_DR], &odr_bits); |
| if (ret < 0) |
| goto data_unlock; |
| |
| *odr = fxas21002c_odr_hz_from_value(data, odr_bits); |
| |
| ret = IIO_VAL_INT; |
| |
| data_unlock: |
| mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int fxas21002c_odr_set(struct fxas21002c_data *data, int odr) |
| { |
| int odr_bits; |
| |
| odr_bits = fxas21002c_odr_value_from_hz(data, odr); |
| if (odr_bits < 0) |
| return odr_bits; |
| |
| return fxas21002c_write(data, F_DR, odr_bits); |
| } |
| |
| static int fxas21002c_lpf_get(struct fxas21002c_data *data, int *val2) |
| { |
| unsigned int bw_bits; |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = regmap_field_read(data->regmap_fields[F_BW], &bw_bits); |
| if (ret < 0) |
| goto data_unlock; |
| |
| *val2 = fxas21002c_lpf_bw_from_value(data, bw_bits) * 10000; |
| |
| ret = IIO_VAL_INT_PLUS_MICRO; |
| |
| data_unlock: |
| mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int fxas21002c_lpf_set(struct fxas21002c_data *data, int bw) |
| { |
| int bw_bits; |
| int odr; |
| int ret; |
| |
| bw_bits = fxas21002c_lpf_value_from_bw(data, bw); |
| if (bw_bits < 0) |
| return bw_bits; |
| |
| /* |
| * From table 33 of the device spec, for ODR = 25Hz and 12.5 value 0.08 |
| * is not allowed and for ODR = 12.5 value 0.16 is also not allowed |
| */ |
| ret = fxas21002c_odr_get(data, &odr); |
| if (ret < 0) |
| return -EINVAL; |
| |
| if ((odr == 25 && bw_bits > 0x01) || (odr == 12 && bw_bits > 0)) |
| return -EINVAL; |
| |
| return fxas21002c_write(data, F_BW, bw_bits); |
| } |
| |
| static int fxas21002c_hpf_get(struct fxas21002c_data *data, int *val2) |
| { |
| unsigned int sel_bits; |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = regmap_field_read(data->regmap_fields[F_SEL], &sel_bits); |
| if (ret < 0) |
| goto data_unlock; |
| |
| *val2 = fxas21002c_hpf_sel_from_value(data, sel_bits); |
| |
| ret = IIO_VAL_INT_PLUS_MICRO; |
| |
| data_unlock: |
| mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int fxas21002c_hpf_set(struct fxas21002c_data *data, int sel) |
| { |
| int sel_bits; |
| |
| sel_bits = fxas21002c_hpf_value_from_sel(data, sel); |
| if (sel_bits < 0) |
| return sel_bits; |
| |
| return fxas21002c_write(data, F_SEL, sel_bits); |
| } |
| |
| static int fxas21002c_scale_get(struct fxas21002c_data *data, int *val) |
| { |
| int fs_bits; |
| int scale; |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = regmap_field_read(data->regmap_fields[F_FS], &fs_bits); |
| if (ret < 0) |
| goto data_unlock; |
| |
| scale = fxas21002c_range_fs_from_value(data, fs_bits); |
| if (scale < 0) { |
| ret = scale; |
| goto data_unlock; |
| } |
| |
| *val = scale; |
| |
| data_unlock: |
| mutex_unlock(&data->lock); |
| |
| return ret; |
| } |
| |
| static int fxas21002c_scale_set(struct fxas21002c_data *data, int range) |
| { |
| int fs_bits; |
| |
| fs_bits = fxas21002c_range_value_from_fs(data, range); |
| if (fs_bits < 0) |
| return fs_bits; |
| |
| return fxas21002c_write(data, F_FS, fs_bits); |
| } |
| |
| static int fxas21002c_read_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, int *val, |
| int *val2, long mask) |
| { |
| struct fxas21002c_data *data = iio_priv(indio_dev); |
| int ret; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_RAW: |
| switch (chan->type) { |
| case IIO_TEMP: |
| return fxas21002c_temp_get(data, val); |
| case IIO_ANGL_VEL: |
| return fxas21002c_axis_get(data, chan->scan_index, val); |
| default: |
| return -EINVAL; |
| } |
| case IIO_CHAN_INFO_SCALE: |
| switch (chan->type) { |
| case IIO_ANGL_VEL: |
| *val2 = FXAS21002C_SCALE_FRACTIONAL; |
| ret = fxas21002c_scale_get(data, val); |
| if (ret < 0) |
| return ret; |
| |
| return IIO_VAL_FRACTIONAL; |
| default: |
| return -EINVAL; |
| } |
| case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: |
| *val = 0; |
| return fxas21002c_lpf_get(data, val2); |
| case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: |
| *val = 0; |
| return fxas21002c_hpf_get(data, val2); |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| *val2 = 0; |
| return fxas21002c_odr_get(data, val); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int fxas21002c_write_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, int val, |
| int val2, long mask) |
| { |
| struct fxas21002c_data *data = iio_priv(indio_dev); |
| int range; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| if (val2) |
| return -EINVAL; |
| |
| return fxas21002c_odr_set(data, val); |
| case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: |
| if (val) |
| return -EINVAL; |
| |
| val2 = val2 / 10000; |
| return fxas21002c_lpf_set(data, val2); |
| case IIO_CHAN_INFO_SCALE: |
| switch (chan->type) { |
| case IIO_ANGL_VEL: |
| range = (((val * 1000 + val2 / 1000) * |
| FXAS21002C_SCALE_FRACTIONAL) / 1000); |
| return fxas21002c_scale_set(data, range); |
| default: |
| return -EINVAL; |
| } |
| case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: |
| return fxas21002c_hpf_set(data, val2); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("12.5 25 50 100 200 400 800"); |
| |
| static IIO_CONST_ATTR(in_anglvel_filter_low_pass_3db_frequency_available, |
| "0.32 0.16 0.08"); |
| |
| static IIO_CONST_ATTR(in_anglvel_filter_high_pass_3db_frequency_available, |
| "0.018750 0.009625 0.004875 0.002475"); |
| |
| static IIO_CONST_ATTR(in_anglvel_scale_available, |
| "125.0 62.5 31.25 15.625 7.8125"); |
| |
| static struct attribute *fxas21002c_attributes[] = { |
| &iio_const_attr_sampling_frequency_available.dev_attr.attr, |
| &iio_const_attr_in_anglvel_filter_low_pass_3db_frequency_available.dev_attr.attr, |
| &iio_const_attr_in_anglvel_filter_high_pass_3db_frequency_available.dev_attr.attr, |
| &iio_const_attr_in_anglvel_scale_available.dev_attr.attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group fxas21002c_attrs_group = { |
| .attrs = fxas21002c_attributes, |
| }; |
| |
| #define FXAS21002C_CHANNEL(_axis) { \ |
| .type = IIO_ANGL_VEL, \ |
| .modified = 1, \ |
| .channel2 = IIO_MOD_##_axis, \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
| .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ |
| BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ |
| BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) | \ |
| BIT(IIO_CHAN_INFO_SAMP_FREQ), \ |
| .scan_index = CHANNEL_SCAN_INDEX_##_axis, \ |
| .scan_type = { \ |
| .sign = 's', \ |
| .realbits = 16, \ |
| .storagebits = 16, \ |
| .endianness = IIO_BE, \ |
| }, \ |
| } |
| |
| static const struct iio_chan_spec fxas21002c_channels[] = { |
| { |
| .type = IIO_TEMP, |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
| .scan_index = -1, |
| }, |
| FXAS21002C_CHANNEL(X), |
| FXAS21002C_CHANNEL(Y), |
| FXAS21002C_CHANNEL(Z), |
| }; |
| |
| static const struct iio_info fxas21002c_info = { |
| .attrs = &fxas21002c_attrs_group, |
| .read_raw = &fxas21002c_read_raw, |
| .write_raw = &fxas21002c_write_raw, |
| }; |
| |
| static irqreturn_t fxas21002c_trigger_handler(int irq, void *p) |
| { |
| struct iio_poll_func *pf = p; |
| struct iio_dev *indio_dev = pf->indio_dev; |
| struct fxas21002c_data *data = iio_priv(indio_dev); |
| int ret; |
| |
| mutex_lock(&data->lock); |
| ret = regmap_bulk_read(data->regmap, FXAS21002C_REG_OUT_X_MSB, |
| data->buffer, CHANNEL_SCAN_MAX * sizeof(s16)); |
| if (ret < 0) |
| goto out_unlock; |
| |
| iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, |
| data->timestamp); |
| |
| out_unlock: |
| mutex_unlock(&data->lock); |
| |
| iio_trigger_notify_done(indio_dev->trig); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int fxas21002c_chip_init(struct fxas21002c_data *data) |
| { |
| struct device *dev = regmap_get_device(data->regmap); |
| unsigned int chip_id; |
| int ret; |
| |
| ret = regmap_field_read(data->regmap_fields[F_WHO_AM_I], &chip_id); |
| if (ret < 0) |
| return ret; |
| |
| if (chip_id != FXAS21002C_CHIP_ID_1 && |
| chip_id != FXAS21002C_CHIP_ID_2) { |
| dev_err(dev, "chip id 0x%02x is not supported\n", chip_id); |
| return -EINVAL; |
| } |
| |
| data->chip_id = chip_id; |
| |
| ret = fxas21002c_mode_set(data, FXAS21002C_MODE_STANDBY); |
| if (ret < 0) |
| return ret; |
| |
| /* Set ODR to 200HZ as default */ |
| ret = fxas21002c_odr_set(data, 200); |
| if (ret < 0) |
| dev_err(dev, "failed to set ODR: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static int fxas21002c_data_rdy_trigger_set_state(struct iio_trigger *trig, |
| bool state) |
| { |
| struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); |
| struct fxas21002c_data *data = iio_priv(indio_dev); |
| |
| return regmap_field_write(data->regmap_fields[F_INT_EN_DRDY], state); |
| } |
| |
| static const struct iio_trigger_ops fxas21002c_trigger_ops = { |
| .set_trigger_state = &fxas21002c_data_rdy_trigger_set_state, |
| }; |
| |
| static irqreturn_t fxas21002c_data_rdy_handler(int irq, void *private) |
| { |
| struct iio_dev *indio_dev = private; |
| struct fxas21002c_data *data = iio_priv(indio_dev); |
| |
| data->timestamp = iio_get_time_ns(indio_dev); |
| |
| return IRQ_WAKE_THREAD; |
| } |
| |
| static irqreturn_t fxas21002c_data_rdy_thread(int irq, void *private) |
| { |
| struct iio_dev *indio_dev = private; |
| struct fxas21002c_data *data = iio_priv(indio_dev); |
| unsigned int data_ready; |
| int ret; |
| |
| ret = regmap_field_read(data->regmap_fields[F_SRC_DRDY], &data_ready); |
| if (ret < 0) |
| return IRQ_NONE; |
| |
| if (!data_ready) |
| return IRQ_NONE; |
| |
| iio_trigger_poll_chained(data->dready_trig); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int fxas21002c_trigger_probe(struct fxas21002c_data *data) |
| { |
| struct device *dev = regmap_get_device(data->regmap); |
| struct iio_dev *indio_dev = dev_get_drvdata(dev); |
| struct device_node *np = indio_dev->dev.of_node; |
| unsigned long irq_trig; |
| bool irq_open_drain; |
| int irq1; |
| int ret; |
| |
| if (!data->irq) |
| return 0; |
| |
| irq1 = of_irq_get_byname(np, "INT1"); |
| |
| if (irq1 == data->irq) { |
| dev_info(dev, "using interrupt line INT1\n"); |
| ret = regmap_field_write(data->regmap_fields[F_INT_CFG_DRDY], |
| 1); |
| if (ret < 0) |
| return ret; |
| } |
| |
| dev_info(dev, "using interrupt line INT2\n"); |
| |
| irq_open_drain = of_property_read_bool(np, "drive-open-drain"); |
| |
| data->dready_trig = devm_iio_trigger_alloc(dev, "%s-dev%d", |
| indio_dev->name, |
| indio_dev->id); |
| if (!data->dready_trig) |
| return -ENOMEM; |
| |
| irq_trig = irqd_get_trigger_type(irq_get_irq_data(data->irq)); |
| |
| if (irq_trig == IRQF_TRIGGER_RISING) { |
| ret = regmap_field_write(data->regmap_fields[F_IPOL], 1); |
| if (ret < 0) |
| return ret; |
| } |
| |
| if (irq_open_drain) |
| irq_trig |= IRQF_SHARED; |
| |
| ret = devm_request_threaded_irq(dev, data->irq, |
| fxas21002c_data_rdy_handler, |
| fxas21002c_data_rdy_thread, |
| irq_trig, "fxas21002c_data_ready", |
| indio_dev); |
| if (ret < 0) |
| return ret; |
| |
| data->dready_trig->dev.parent = dev; |
| data->dready_trig->ops = &fxas21002c_trigger_ops; |
| iio_trigger_set_drvdata(data->dready_trig, indio_dev); |
| |
| return devm_iio_trigger_register(dev, data->dready_trig); |
| } |
| |
| static int fxas21002c_power_enable(struct fxas21002c_data *data) |
| { |
| int ret; |
| |
| ret = regulator_enable(data->vdd); |
| if (ret < 0) |
| return ret; |
| |
| ret = regulator_enable(data->vddio); |
| if (ret < 0) { |
| regulator_disable(data->vdd); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static void fxas21002c_power_disable(struct fxas21002c_data *data) |
| { |
| regulator_disable(data->vdd); |
| regulator_disable(data->vddio); |
| } |
| |
| static void fxas21002c_power_disable_action(void *_data) |
| { |
| struct fxas21002c_data *data = _data; |
| |
| fxas21002c_power_disable(data); |
| } |
| |
| static int fxas21002c_regulators_get(struct fxas21002c_data *data) |
| { |
| struct device *dev = regmap_get_device(data->regmap); |
| |
| data->vdd = devm_regulator_get(dev->parent, "vdd"); |
| if (IS_ERR(data->vdd)) |
| return PTR_ERR(data->vdd); |
| |
| data->vddio = devm_regulator_get(dev->parent, "vddio"); |
| |
| return PTR_ERR_OR_ZERO(data->vddio); |
| } |
| |
| int fxas21002c_core_probe(struct device *dev, struct regmap *regmap, int irq, |
| const char *name) |
| { |
| struct fxas21002c_data *data; |
| struct iio_dev *indio_dev; |
| struct regmap_field *f; |
| int i; |
| int ret; |
| |
| indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); |
| if (!indio_dev) |
| return -ENOMEM; |
| |
| data = iio_priv(indio_dev); |
| dev_set_drvdata(dev, indio_dev); |
| data->irq = irq; |
| data->regmap = regmap; |
| |
| for (i = 0; i < F_MAX_FIELDS; i++) { |
| f = devm_regmap_field_alloc(dev, data->regmap, |
| fxas21002c_reg_fields[i]); |
| if (IS_ERR(f)) |
| return PTR_ERR(f); |
| |
| data->regmap_fields[i] = f; |
| } |
| |
| mutex_init(&data->lock); |
| |
| ret = fxas21002c_regulators_get(data); |
| if (ret < 0) |
| return ret; |
| |
| ret = fxas21002c_power_enable(data); |
| if (ret < 0) |
| return ret; |
| |
| ret = devm_add_action_or_reset(dev, fxas21002c_power_disable_action, |
| data); |
| if (ret < 0) |
| return ret; |
| |
| ret = fxas21002c_chip_init(data); |
| if (ret < 0) |
| return ret; |
| |
| indio_dev->dev.parent = dev; |
| indio_dev->channels = fxas21002c_channels; |
| indio_dev->num_channels = ARRAY_SIZE(fxas21002c_channels); |
| indio_dev->name = name; |
| indio_dev->modes = INDIO_DIRECT_MODE; |
| indio_dev->info = &fxas21002c_info; |
| |
| ret = fxas21002c_trigger_probe(data); |
| if (ret < 0) |
| return ret; |
| |
| ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, |
| fxas21002c_trigger_handler, NULL); |
| if (ret < 0) |
| return ret; |
| |
| ret = pm_runtime_set_active(dev); |
| if (ret) |
| return ret; |
| |
| pm_runtime_enable(dev); |
| pm_runtime_set_autosuspend_delay(dev, 2000); |
| pm_runtime_use_autosuspend(dev); |
| |
| ret = iio_device_register(indio_dev); |
| if (ret < 0) |
| goto pm_disable; |
| |
| return 0; |
| |
| pm_disable: |
| pm_runtime_disable(dev); |
| pm_runtime_set_suspended(dev); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(fxas21002c_core_probe); |
| |
| void fxas21002c_core_remove(struct device *dev) |
| { |
| struct iio_dev *indio_dev = dev_get_drvdata(dev); |
| |
| iio_device_unregister(indio_dev); |
| |
| pm_runtime_disable(dev); |
| pm_runtime_set_suspended(dev); |
| } |
| EXPORT_SYMBOL_GPL(fxas21002c_core_remove); |
| |
| static int __maybe_unused fxas21002c_suspend(struct device *dev) |
| { |
| struct fxas21002c_data *data = iio_priv(dev_get_drvdata(dev)); |
| |
| fxas21002c_mode_set(data, FXAS21002C_MODE_STANDBY); |
| fxas21002c_power_disable(data); |
| |
| return 0; |
| } |
| |
| static int __maybe_unused fxas21002c_resume(struct device *dev) |
| { |
| struct fxas21002c_data *data = iio_priv(dev_get_drvdata(dev)); |
| int ret; |
| |
| ret = fxas21002c_power_enable(data); |
| if (ret < 0) |
| return ret; |
| |
| return fxas21002c_mode_set(data, data->prev_mode); |
| } |
| |
| static int __maybe_unused fxas21002c_runtime_suspend(struct device *dev) |
| { |
| struct fxas21002c_data *data = iio_priv(dev_get_drvdata(dev)); |
| |
| return fxas21002c_mode_set(data, FXAS21002C_MODE_READY); |
| } |
| |
| static int __maybe_unused fxas21002c_runtime_resume(struct device *dev) |
| { |
| struct fxas21002c_data *data = iio_priv(dev_get_drvdata(dev)); |
| |
| return fxas21002c_mode_set(data, FXAS21002C_MODE_ACTIVE); |
| } |
| |
| const struct dev_pm_ops fxas21002c_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(fxas21002c_suspend, fxas21002c_resume) |
| SET_RUNTIME_PM_OPS(fxas21002c_runtime_suspend, |
| fxas21002c_runtime_resume, NULL) |
| }; |
| EXPORT_SYMBOL_GPL(fxas21002c_pm_ops); |
| |
| MODULE_AUTHOR("Rui Miguel Silva <rui.silva@linaro.org>"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("FXAS21002C Gyro driver"); |