diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 08d3f5ca89e40..980cc9e74864d 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -44,6 +44,22 @@ config AD400X To compile this driver as a module, choose M here: the module will be called ad400x. +config AD4052 + tristate "Analog Devices AD4052 Driver" + depends on SPI + depends on PWM + depends on GPIOLIB + select IIO_BUFFER + select IIO_BUFFER_DMA + select IIO_BUFFER_DMAENGINE + select REGMAP_SPI + help + Say yes here to build support for Analog Devices AD4052 SPI analog + to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4052. + config AD4130 tristate "Analog Device AD4130 ADC Driver" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index c694cf255a9cf..220fe58d874a6 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o obj-$(CONFIG_AD_PULSAR) += ad_pulsar.o obj-$(CONFIG_AD400X) += ad400x.o +obj-$(CONFIG_AD4052) += ad4052.o obj-$(CONFIG_AD4130) += ad4130.o obj-$(CONFIG_AD4134) += ad4134.o obj-$(CONFIG_AD4630) += ad4630.o diff --git a/drivers/iio/adc/ad4052.c b/drivers/iio/adc/ad4052.c new file mode 100644 index 0000000000000..f4174ed10e303 --- /dev/null +++ b/drivers/iio/adc/ad4052.c @@ -0,0 +1,1114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4052 SPI ADC driver + * + * Copyright 2024 Analog Devices Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD4052_REG_INTERFACE_CONFIG_A 0x00 +#define AD4052_REG_DEVICE_CONFIG 0x02 +#define AD4052_REG_PROD_ID_1 0x05 +#define AD4052_REG_DEVICE_GRADE 0x06 +#define AD4052_REG_SCRATCH_PAD 0x0A +#define AD4052_REG_VENDOR_H 0x0D +#define AD4052_REG_STREAM_MODE 0x0E +#define AD4052_REG_INTERFACE_STATUS 0x11 +#define AD4052_REG_MODE_SET 0x20 +#define AD4052_REG_ADC_MODES 0x21 +#define AD4052_REG_AVG_CONFIG 0x23 +#define AD4052_REG_GP_CONFIG 0x24 +#define AD4052_REG_INTR_CONFIG 0x25 +#define AD4052_REG_TIMER_CONFIG 0x27 +#define AD4052_REG_MAX_LIMIT 0x29 +#define AD4052_REG_MIN_LIMIT 0x2B +#define AD4052_REG_MAX_HYST 0x2C +#define AD4052_REG_MIN_HYST 0x2D +#define AD4052_REG_MON_VAL 0x2F +#define AD4052_REG_FUSE_CRC 0x40 +#define AD4052_REG_DEVICE_STATUS 0x41 +#define AD4052_REG_MIN_SAMPLE 0x45 +#define AD4052_MAX_REG 0x45 +/* GP_CONFIG */ +#define AD4052_GP_MODE_MSK(x) (GENMASK(2, 0) << (x) * 4) +#define AD4052_GP_MODE(x, y) FIELD_PREP(AD4052_GP_MODE_MSK(x), (y)) +/* INTR_CONFIG */ +#define AD4052_INTR_EN_MSK(x) (GENMASK(1, 0) << (x) * 4) +#define AD4052_INTR_EN(x, y) FIELD_PREP(AD4052_INTR_EN_MSK(x), (y)) +/* ADC_MODES */ +#define AD4052_DATA_FORMAT BIT(7) +/* DEVICE_CONFIG */ +#define AD4052_POWER_MODE_MSK GENMASK(1, 0) +#define AD4052_LOW_POWER_MODE 3 +/* DEVICE_STATUS */ +#define AD4052_DEVICE_RESET BIT(6) +#define AD4052_THRESH_OVERRUN BIT(4) +#define AD4052_MAX_FLAG BIT(3) +#define AD4052_MIN_FLAG BIT(2) +#define AD4052_EVENT_CLEAR (AD4052_THRESH_OVERRUN | AD4052_MAX_FLAG | AD4052_MIN_FLAG) +/* TIMER_CONFIG */ +#define AD4052_FS_BURST_AUTO_MSK GENMASK(7, 4) + +#define AD4052_SPI_VENDOR 0x0456 +#define AD4052_SPI_MAX_SCLK 25000000 + +#define AD4050_MAX_AVG 0x7 +#define AD4052_MAX_AVG 0xB +#define AD4056_CHECK_SPEED(x, y) ((x) == AD4052_500KSPS && (y) < 2) + +enum ad4052_device_type { + ID_AD4050, + ID_AD4056, + ID_AD4052, + ID_AD4058, +}; + +enum ad4052_grade { + AD4052_2MSPS, + AD4052_500KSPS, +}; + +enum ad4052_operation_mode { + AD4052_SAMPLE_MODE = 0, + AD4052_BURST_AVERAGING_MODE = 1, + AD4052_AVERAGING_MODE = 2, + AD4052_MONITOR_MODE = 3, + AD4052_CONFIG_MODE = 8, +}; + +enum ad4052_gp_mode { + AD4052_GP_DISABLED, + AD4052_GP_INTR, + AD4052_GP_DRDY, +}; + +enum ad4052_interrupt_en { + AD4052_INTR_EN_NEITHER, + AD4052_INTR_EN_MIN, + AD4052_INTR_EN_MAX, + AD4052_INTR_EN_EITHER, +}; + +struct ad4052_chip_info { + const struct iio_chan_spec channels[2]; + const char *name; + u16 prod_id; + u8 max_avg; + u8 grade; +}; + +struct ad4052_state { + const struct ad4052_bus_ops *ops; + const struct ad4052_chip_info *chip; + struct spi_device *spi; + struct spi_transfer offload_xfer; + struct spi_message offload_msg; + struct pwm_device *cnv_pwm; + struct gpio_desc *cnv_gp; + struct regmap *regmap; + enum ad4052_operation_mode mode; + u8 data_format; + __be32 d32[1] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct regmap_range ad4052_regmap_rd_ranges[] = { + regmap_reg_range(AD4052_REG_INTERFACE_CONFIG_A, AD4052_REG_DEVICE_GRADE), + regmap_reg_range(AD4052_REG_SCRATCH_PAD, AD4052_REG_INTERFACE_STATUS), + regmap_reg_range(AD4052_REG_MODE_SET, AD4052_REG_MON_VAL), + regmap_reg_range(AD4052_REG_FUSE_CRC, AD4052_REG_MIN_SAMPLE), +}; + +static const struct regmap_access_table ad4052_regmap_rd_table = { + .yes_ranges = ad4052_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4052_regmap_rd_ranges), +}; + +static const struct regmap_range ad4052_regmap_wr_ranges[] = { + regmap_reg_range(AD4052_REG_INTERFACE_CONFIG_A, AD4052_REG_DEVICE_CONFIG), + regmap_reg_range(AD4052_REG_SCRATCH_PAD, AD4052_REG_SCRATCH_PAD), + regmap_reg_range(AD4052_REG_STREAM_MODE, AD4052_REG_INTERFACE_STATUS), + regmap_reg_range(AD4052_REG_MODE_SET, AD4052_REG_MON_VAL), + regmap_reg_range(AD4052_REG_FUSE_CRC, AD4052_REG_DEVICE_STATUS), +}; + +static const struct regmap_access_table ad4052_regmap_wr_table = { + .yes_ranges = ad4052_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4052_regmap_wr_ranges), +}; + +static const struct iio_event_spec ad4052_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE) + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS) + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS) + } +}; + +static int ad4052_set_avg_filter(struct iio_dev *indio_dev, + unsigned int val) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret = 0; + + if (val < 1 || val > BIT(st->chip->max_avg + 1)) + return -EINVAL; + + val = ilog2(val); + + if (val == 0) { + st->mode = AD4052_SAMPLE_MODE; + } else { + st->mode = AD4052_BURST_AVERAGING_MODE; + ret = regmap_write(st->regmap, AD4052_REG_AVG_CONFIG, val - 1); + } + + return ret; +} + +static int ad4052_get_avg_filter(struct iio_dev *indio_dev, + unsigned int *val) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int buf; + int ret; + + if (st->mode == AD4052_SAMPLE_MODE) { + *val = 1; + return 0; + } + + ret = regmap_read(st->regmap, AD4052_REG_AVG_CONFIG, &buf); + if (ret) + return ret; + + *val = BIT(buf + 1); + + return 0; +} + +static int ad4052_set_fs_burst_auto(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + if (AD4056_CHECK_SPEED(st->chip->grade, val)) + return -EINVAL; + + if (st->mode == AD4052_MONITOR_MODE) + return -EBUSY; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + val = FIELD_PREP(AD4052_FS_BURST_AUTO_MSK, val); + ret = regmap_write(st->regmap, AD4052_REG_TIMER_CONFIG, val); + + iio_device_release_direct_mode(indio_dev); + + return ret; +} + +static int ad4052_get_fs_burst_auto(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int buf; + int ret; + + if (st->mode == AD4052_MONITOR_MODE) + return -EBUSY; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = regmap_read(st->regmap, AD4052_REG_TIMER_CONFIG, &buf); + iio_device_release_direct_mode(indio_dev); + if (ret) + return ret; + + return FIELD_GET(AD4052_FS_BURST_AUTO_MSK, buf); +} + +static int ad4052_assert(struct ad4052_state *st) +{ + int ret; + u16 val; + + ret = regmap_bulk_read(st->regmap, AD4052_REG_PROD_ID_1, st->d32, 2); + val = be16_to_cpu(*st->d32); + if (ret) + return ret; + + if (val != st->chip->prod_id) + return -ENODEV; + + ret = regmap_bulk_read(st->regmap, AD4052_REG_VENDOR_H, st->d32, 2); + val = be16_to_cpu(*st->d32); + if (ret) + return ret; + + if (val != AD4052_SPI_VENDOR) + return -ENODEV; + + return 0; +} + +static const char *const ad4052_fs_burst_auto[] = { + "2000000", "1000000", "300000", "100000", "33300", "10000", "3000", + "500", "333", "250", "200", "166", "140", "125", "111" +}; + +static const struct iio_enum ad4052_fs_burst_auto_enum = { + .items = ad4052_fs_burst_auto, + .num_items = ARRAY_SIZE(ad4052_fs_burst_auto), + .set = ad4052_set_fs_burst_auto, + .get = ad4052_get_fs_burst_auto, +}; + +static const struct iio_chan_spec_ext_info ad4052_ext_info[] = { + IIO_ENUM("fs_burst_auto", IIO_SHARED_BY_TYPE, + &ad4052_fs_burst_auto_enum), + IIO_ENUM_AVAILABLE("fs_burst_auto", IIO_SHARED_BY_TYPE, + &ad4052_fs_burst_auto_enum), + {} +}; + +#define AD4052_CHAN { \ + .type = IIO_VOLTAGE, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = 0, \ + .event_spec = ad4052_events, \ + .num_event_specs = ARRAY_SIZE(ad4052_events), \ + .ext_info = ad4052_ext_info, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 32 \ + }, \ +} + +const struct ad4052_chip_info ad4052_chip_info[] = { + [ID_AD4050] = { + .name = "ad4050", + .channels = { + AD4052_CHAN + }, + .prod_id = 0x70, + .max_avg = AD4050_MAX_AVG, + .grade = AD4052_2MSPS, + }, + [ID_AD4052] = { + .name = "ad4052", + .channels = { + AD4052_CHAN + }, + .prod_id = 0x72, + .max_avg = AD4052_MAX_AVG, + .grade = AD4052_2MSPS, + }, + [ID_AD4056] = { + .name = "ad4056", + .channels = { + AD4052_CHAN + }, + .prod_id = 0x70, + .max_avg = AD4050_MAX_AVG, + .grade = AD4052_500KSPS, + }, + [ID_AD4058] = { + .name = "ad4058", + .channels = { + AD4052_CHAN + }, + .prod_id = 0x72, + .max_avg = AD4052_MAX_AVG, + .grade = AD4052_500KSPS, + }, +}; + +static int ad4052_exit_command(struct ad4052_state *st) +{ + struct spi_device *spi = st->spi; + const u8 buf = 0xA8; + + return spi_write(spi, &buf, 1); +} + +static int ad4052_set_operation_mode(struct ad4052_state *st, enum ad4052_operation_mode mode) +{ + u8 buf = st->data_format | mode; + int ret; + + ret = regmap_write(st->regmap, AD4052_REG_ADC_MODES, buf); + if (ret) + return ret; + + buf = BIT(0); + return regmap_write(st->regmap, AD4052_REG_MODE_SET, buf); +}; + +static int ad4052_config(struct ad4052_state *st) +{ + struct device *dev = &st->spi->dev; + struct pwm_state conv_state; + int ret; + + /* + * Receive buffer needs to be non-zero for the SPI engine controller + * to mark the transfer as a read. + */ + st->offload_xfer.speed_hz = AD4052_SPI_MAX_SCLK; + st->offload_xfer.rx_buf = (void *)-1; + + spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1); + + /* Prepare PWM CNV */ + st->cnv_pwm = devm_pwm_get(dev, "cnv"); + if (IS_ERR(st->cnv_pwm)) { + return dev_err_probe(dev, PTR_ERR(st->cnv_pwm), + "Failed to get cnv pwm\n"); + } + pwm_init_state(st->cnv_pwm, &conv_state); + conv_state.duty_cycle = 20; + conv_state.period = 1000; + conv_state.enabled = false; + ret = pwm_apply_state(st->cnv_pwm, &conv_state); + if (ret) + return ret; + + /* Prepare GPIO CNV */ + st->cnv_gp = devm_gpiod_get_optional(dev, "cnv", + GPIOD_OUT_LOW); + if (IS_ERR(st->cnv_gp)) { + return dev_err_probe(dev, PTR_ERR(st->cnv_gp), + "Failed to get cnv gpio\n"); + } + + return 0; +} + +static int ad4052_soft_reset(struct spi_device *spi) +{ + int ret; + u8 buf[18] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE + }; + + ret = spi_write(spi, buf, sizeof(buf)); + if (ret) + return ret; + + /* Wait at least AD4052 Tswreset time - 500us */ + fsleep(750); + + return 0; +}; + +static int ad4052_set_non_defaults(struct ad4052_state *st) +{ + struct iio_chan_spec chan = st->chip->channels[0]; + u8 val = AD4052_GP_MODE(1, AD4052_GP_DRDY) | AD4052_GP_MODE(0, AD4052_GP_INTR); + int ret; + + ret = regmap_update_bits(st->regmap, AD4052_REG_GP_CONFIG, + AD4052_GP_MODE_MSK(1) | AD4052_GP_MODE_MSK(0), + val); + if (ret) + return ret; + + val = AD4052_INTR_EN(0, AD4052_INTR_EN_EITHER) | + AD4052_INTR_EN(1, AD4052_INTR_EN_NEITHER); + + ret = regmap_update_bits(st->regmap, AD4052_REG_INTR_CONFIG, + AD4052_INTR_EN_MSK(0) | AD4052_INTR_EN_MSK(1), + val); + if (ret) + return ret; + + val = 0; + if (chan.scan_type.sign == 's') + val |= AD4052_DATA_FORMAT; + + st->data_format = val; + + return regmap_write(st->regmap, AD4052_REG_ADC_MODES, val); +} + +irqreturn_t ad4052_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static int ad4052_request_irq(struct iio_dev *indio_dev) +{ + struct ad4052_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + int irq = st->spi->irq; + int ret; + + /* Optional */ + if (irq) { + ret = devm_request_threaded_irq(dev, irq, + NULL, ad4052_interrupt_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) { + dev_err(dev, "irq request for gp0 error %d.\n", ret); + return ret; + } + } + + return 0; +} + +static int ad4052_set_sampling_freq(const struct ad4052_state *st, unsigned int freq) +{ + struct spi_device *spi = st->spi; + struct pwm_state conv_state; + + if (freq > spi->max_speed_hz) + return -EINVAL; + + pwm_get_state(st->cnv_pwm, &conv_state); + conv_state.period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq); + return pwm_apply_state(st->cnv_pwm, &conv_state); +} + +static void ad4052_get_sampling_freq(const struct ad4052_state *st, int *val) +{ + struct pwm_state conversion_state; + + pwm_get_state(st->cnv_pwm, &conversion_state); + *val = DIV_ROUND_CLOSEST_ULL(NANO, conversion_state.period); +} + +static int __ad4052_read_chan_raw(struct ad4052_state *st, int *val) +{ + struct spi_device *spi = st->spi; + short size; + int ret; + + if (st->mode == AD4052_BURST_AVERAGING_MODE && + st->chip->prod_id == 0x72) + size = 3; + else + size = 2; + + struct spi_transfer t[] = { + { + .rx_buf = st->d32, + .len = size, + }, + }; + + gpiod_set_value(st->cnv_gp, 1); + /* Takes > 3us */ + ret = spi_sync_transfer(spi, t, ARRAY_SIZE(t)); + gpiod_set_value(st->cnv_gp, 0); + if (ret) + return ret; + + if (size == 2) { + *val = be16_to_cpu(*st->d32); + if (st->data_format & AD4052_DATA_FORMAT) + *val = sign_extend32(*val, 15); + } else { + *val = be32_to_cpu(*st->d32); + *val >>= 8; + if (st->data_format & AD4052_DATA_FORMAT) + *val = sign_extend32(*val, 23); + } + + return 0; +}; + +static int ad4052_read_chan_raw(struct iio_dev *indio_dev, int *val) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + ret = pm_runtime_resume_and_get(&st->spi->dev); + if (ret) + return ret; + + ret = ad4052_set_operation_mode(st, st->mode); + if (ret) + goto out_error; + + ret = __ad4052_read_chan_raw(st, val); + if (ret) + goto out_error; + + ret = ad4052_exit_command(st); + +out_error: + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + return ret; +} + +static int ad4052_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + if (st->mode == AD4052_MONITOR_MODE) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + ret = ad4052_read_chan_raw(indio_dev, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + } + unreachable(); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + ret = ad4052_get_avg_filter(indio_dev, val); + if (ret) + return ret; + + return IIO_VAL_INT; + } + unreachable(); + case IIO_CHAN_INFO_SAMP_FREQ: + ad4052_get_sampling_freq(st, val); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad4052_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + const struct ad4052_state *st = iio_priv(indio_dev); + + if (st->mode == AD4052_MONITOR_MODE) + return -EBUSY; + + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + return ad4052_set_avg_filter(indio_dev, val); + } + unreachable(); + case IIO_CHAN_INFO_SAMP_FREQ: + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + return ad4052_set_sampling_freq(st, val); + } + unreachable(); + default: + return -EINVAL; + } +} + +static int ad4052_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 ad4052_state *st = iio_priv(indio_dev); + int state; + int ret; + + if (st->mode == AD4052_MONITOR_MODE) + return -EBUSY; + + if (type != IIO_EV_TYPE_THRESH || dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + ret = regmap_read(st->regmap, AD4052_REG_GP_CONFIG, &state); + if (!ret) + ret = state & AD4052_GP_MODE_MSK(0); + return ret; + } + unreachable(); +} + +static int ad4052_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct ad4052_state *st = iio_priv(indio_dev); + int ret; + + if (type != IIO_EV_TYPE_THRESH || dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + state = state ? AD4052_MONITOR_MODE : AD4052_SAMPLE_MODE; + + if (st->mode == state) + return 0; + + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + if (state) { + ret = pm_runtime_resume_and_get(&st->spi->dev); + if (ret) + goto out_error; + + ret = ad4052_set_operation_mode(st, AD4052_MONITOR_MODE); + if (ret) + goto out_err_suspend; + } else { + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + + ret = ad4052_exit_command(st); + } + st->mode = state; + iio_device_release_direct_mode(indio_dev); + return ret; + +out_err_suspend: + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + +out_error: + iio_device_release_direct_mode(indio_dev); + return ret; +} + +static int ad4052_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 ad4052_state *st = iio_priv(indio_dev); + u8 size = 1; + int ret; + u8 reg; + + if (st->mode == AD4052_MONITOR_MODE) + return -EBUSY; + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (dir) { + case IIO_EV_DIR_RISING: + case IIO_EV_DIR_FALLING: + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_RISING) + reg = AD4052_REG_MAX_LIMIT; + else + reg = AD4052_REG_MIN_LIMIT; + size++; + break; + case IIO_EV_INFO_HYSTERESIS: + if (dir == IIO_EV_DIR_RISING) + reg = AD4052_REG_MAX_HYST; + else + reg = AD4052_REG_MIN_HYST; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + *st->d32 = 0; + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + ret = regmap_bulk_read(st->regmap, reg, st->d32, size); + if (ret) + return ret; + + if (reg == AD4052_REG_MAX_LIMIT || reg == AD4052_REG_MIN_LIMIT) { + *val = be16_to_cpu(*st->d32); + if (st->data_format & AD4052_DATA_FORMAT) + *val = sign_extend32(*val, 11); + } else { + *val = *st->d32; + } + return IIO_VAL_INT; + } + unreachable(); +} + +static int ad4052_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 ad4052_state *st = iio_priv(indio_dev); + u8 size = 1; + int ret; + u8 reg; + + if (st->mode == AD4052_MONITOR_MODE) + return -EBUSY; + + *st->d32 = cpu_to_be16(val); + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (dir) { + case IIO_EV_DIR_RISING: + case IIO_EV_DIR_FALLING: + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (info) { + case IIO_EV_INFO_VALUE: + if (st->data_format & AD4052_DATA_FORMAT) { + if (val > 2047 || val < -2048) + return -EINVAL; + } else if (val > 4095 || val < 0) { + return -EINVAL; + } + if (dir == IIO_EV_DIR_RISING) + reg = AD4052_REG_MAX_LIMIT; + else + reg = AD4052_REG_MIN_LIMIT; + size++; + break; + case IIO_EV_INFO_HYSTERESIS: + if (val & BIT(7)) + return -EINVAL; + if (dir == IIO_EV_DIR_RISING) + reg = AD4052_REG_MAX_HYST; + else + reg = AD4052_REG_MIN_HYST; + *st->d32 >>= 8; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + ret = regmap_bulk_write(st->regmap, reg, st->d32, size); + return ret; + } + unreachable(); +} + +static int ad4052_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad4052_state *st = iio_priv(indio_dev); + struct spi_device *spi = st->spi; + struct pwm_state conv_state; + u8 realbits = 16; + int ret; + + if (st->mode == AD4052_MONITOR_MODE) + return -EBUSY; + + if (st->mode == AD4052_BURST_AVERAGING_MODE && + st->chip->prod_id == 0x72) + realbits = 24; + + st->offload_xfer.bits_per_word = realbits; + st->offload_xfer.len = roundup_pow_of_two(BITS_TO_BYTES(realbits)); + ret = spi_optimize_message(st->spi, &st->offload_msg); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(&st->spi->dev); + if (ret) + return ret; + + pwm_get_state(st->cnv_pwm, &conv_state); + conv_state.enabled = true; + ret = pwm_apply_state(st->cnv_pwm, &conv_state); + if (ret) + goto out_error; + + ret = ad4052_set_operation_mode(st, st->mode); + if (ret) + goto out_error; + + spi_bus_lock(spi->controller); + spi_engine_ex_offload_load_msg(spi, &st->offload_msg); + spi_engine_ex_offload_enable(spi, true); + + return 0; +out_error: + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + return ret; +} + +static int ad4052_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4052_state *st = iio_priv(indio_dev); + struct spi_device *spi = st->spi; + struct pwm_state conv_state; + int ret; + + spi_unoptimize_message(&st->offload_msg); + + spi_engine_ex_offload_enable(spi, false); + spi_bus_unlock(spi->master); + + pwm_get_state(st->cnv_pwm, &conv_state); + conv_state.enabled = false; + ret = pwm_apply_state(st->cnv_pwm, &conv_state); + if (ret) + goto out_error; + + ret = ad4052_exit_command(st); +out_error: + pm_runtime_mark_last_busy(&st->spi->dev); + pm_runtime_put_autosuspend(&st->spi->dev); + return ret; +} + +static int ad4052_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4052_state *st = iio_priv(indio_dev); + + if (st->mode == AD4052_MONITOR_MODE) + return -EBUSY; + + iio_device_claim_direct_scoped(return -EBUSY, indio_dev) { + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); + } + unreachable(); +} + +static const struct iio_buffer_setup_ops ad4052_buffer_setup_ops = { + .preenable = &ad4052_buffer_preenable, + .postdisable = &ad4052_buffer_postdisable, +}; + +static const struct iio_info ad4052_info = { + .read_raw = ad4052_read_raw, + .write_raw = ad4052_write_raw, + .read_event_config = &ad4052_read_event_config, + .write_event_config = &ad4052_write_event_config, + .read_event_value = &ad4052_read_event_value, + .write_event_value = &ad4052_write_event_value, + .debugfs_reg_access = &ad4052_debugfs_reg_access, +}; + +static const struct regmap_config ad4052_regmap_config = { + .name = "ad4062", + .reg_bits = 8, + .val_bits = 8, + .max_register = AD4052_MAX_REG, + .read_flag_mask = BIT(7), +}; + +static int ad4052_probe(struct spi_device *spi) +{ + const struct ad4052_chip_info *chip; + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ad4052_state *st; + int ret; + u8 buf; + + chip = spi_get_device_match_data(spi); + if (!chip) + return dev_err_probe(dev, -ENODEV, + "Could not find chip info data\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + spi_set_drvdata(spi, st); + + st->regmap = devm_regmap_init_spi(spi, &ad4052_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + st->mode = AD4052_SAMPLE_MODE; + st->chip = chip; + + ret = ad4052_config(st); + if (ret) + return dev_err_probe(dev, ret, + "Resources configuration failed\n"); + + indio_dev->modes = INDIO_BUFFER_HARDWARE | INDIO_DIRECT_MODE; + indio_dev->channels = chip->channels; + indio_dev->setup_ops = &ad4052_buffer_setup_ops; + indio_dev->num_channels = 1; + indio_dev->info = &ad4052_info; + indio_dev->name = chip->name; + + ret = devm_iio_dmaengine_buffer_setup(dev, indio_dev, "rx", + IIO_BUFFER_DIRECTION_IN); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get DMA buffer\n"); + + ret = ad4052_soft_reset(spi); + if (ret) { + return dev_err_probe(dev, ret, + "AD4052 failed to soft reset\n"); + } + + ret = ad4052_assert(st); + if (ret) { + return dev_err_probe(dev, ret, + "AD4052 fields assertions failed\n"); + } + + ret = ad4052_set_non_defaults(st); + if (ret) + return ret; + + buf = AD4052_DEVICE_RESET; + regmap_write(st->regmap, AD4052_REG_DEVICE_STATUS, buf); + + ret = ad4052_request_irq(indio_dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable pm_runtime\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad4052_runtime_suspend(struct device *dev) +{ + u8 val = FIELD_PREP(AD4052_POWER_MODE_MSK, AD4052_LOW_POWER_MODE); + struct ad4052_state *st = dev_get_drvdata(dev); + + return regmap_write(st->regmap, AD4052_REG_DEVICE_CONFIG, val); +} + +static int ad4052_runtime_resume(struct device *dev) +{ + struct ad4052_state *st = dev_get_drvdata(dev); + u8 val = FIELD_PREP(AD4052_POWER_MODE_MSK, 0); + + return regmap_write(st->regmap, AD4052_REG_DEVICE_CONFIG, val); +} + +static const struct dev_pm_ops ad4052_pm_ops = { + SET_RUNTIME_PM_OPS(ad4052_runtime_suspend, ad4052_runtime_resume, NULL) +}; + +static const struct spi_device_id ad4052_id_table[] = { + {"ad4050", (kernel_ulong_t)&ad4052_chip_info[ID_AD4050] }, + {"ad4056", (kernel_ulong_t)&ad4052_chip_info[ID_AD4056] }, + {"ad4052", (kernel_ulong_t)&ad4052_chip_info[ID_AD4052] }, + {"ad4058", (kernel_ulong_t)&ad4052_chip_info[ID_AD4058] }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad4052_id_table); + +static const struct of_device_id ad4052_of_match[] = { + { .compatible = "adi,ad4050", .data = &ad4052_chip_info[ID_AD4050] }, + { .compatible = "adi,ad4052", .data = &ad4052_chip_info[ID_AD4052] }, + { .compatible = "adi,ad4056", .data = &ad4052_chip_info[ID_AD4056] }, + { .compatible = "adi,ad4058", .data = &ad4052_chip_info[ID_AD4058] }, + {} +}; +MODULE_DEVICE_TABLE(of, ad4052_of_match); + +static struct spi_driver ad4052_driver = { + .driver = { + .name = "ad4052", + .of_match_table = ad4052_of_match, + .pm = pm_ptr(&ad4052_pm_ops), + }, + .probe = ad4052_probe, + .id_table = ad4052_id_table, +}; +module_spi_driver(ad4052_driver); + +MODULE_AUTHOR("Jorge Marques "); +MODULE_DESCRIPTION("Analog Devices AD4052"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_AD4052); +MODULE_IMPORT_NS(IIO_DMAENGINE_BUFFER);