From c94d50c1bcf3d09bcbcec8a8696a544a1ec433bb Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 8 Mar 2024 13:09:58 -0600 Subject: [PATCH] iio: adc: ad7944: add spi offload support This adds support for SPI Engine offload to the ad7944-ex driver. This allows reaching the max sample rate of 2/2.5 MSPS when the chip is wired in 3-wire mode. Signed-off-by: David Lechner --- drivers/iio/adc/ad7944.c | 245 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 240 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c index b706e19dcb9a96..c75171951620bc 100644 --- a/drivers/iio/adc/ad7944.c +++ b/drivers/iio/adc/ad7944.c @@ -14,15 +14,25 @@ #include #include #include +#include #include +#include #include #include #include #include +#include #include #include +/* + * Older SPI Engine HDL versions have a bug where the offload is level triggered + * instead of edge triggered, so we use a small duty cycle to allow sampling + * at lower rates without spurious triggers. + */ +#define AD7944_PWM_TRIGGER_DUTY_CYCLE_NS 20 +#define AD7944_DEFAULT_SAMPLE_FREQ_HZ 10000 /* arbitrary */ #define AD7944_INTERNAL_REF_MV 4096 struct ad7944_timing_spec { @@ -30,6 +40,10 @@ struct ad7944_timing_spec { unsigned int conv_ns; /* TURBO mode max conversion time (t_{CONV}). */ unsigned int turbo_conv_ns; + /* Normal mode data read during conversion time (t_{DATA}). */ + unsigned int data_ns; + /* TURBO mode data read during conversion time (t_{DATA}). */ + unsigned int turbo_data_ns; }; enum ad7944_spi_mode { @@ -53,6 +67,8 @@ struct ad7944_adc { enum ad7944_spi_mode spi_mode; struct spi_transfer xfers[3]; struct spi_message msg; + struct spi_transfer turbo_xfers[3]; + struct spi_message turbo_msg; /* Chip-specific timing specifications. */ const struct ad7944_timing_spec *timing_spec; /* GPIO connected to CNV pin. */ @@ -63,6 +79,8 @@ struct ad7944_adc { bool always_turbo; /* Reference voltage (millivolts). */ unsigned int ref_mv; + /* SPI offload trigger. */ + struct pwm_device *pwm; /* * DMA (thus cache coherency maintenance) requires the @@ -77,17 +95,25 @@ struct ad7944_adc { } sample __aligned(IIO_DMA_MINALIGN); }; +/* minimum CNV high time */ +#define T_CNVH_NS 10 +/* CS low to data valid time */ +#define T_EN_NS 5 /* quite time before CNV rising edge */ #define T_QUIET_NS 20 static const struct ad7944_timing_spec ad7944_timing_spec = { .conv_ns = 420, .turbo_conv_ns = 320, + .data_ns = 290, + .turbo_data_ns = 190, }; static const struct ad7944_timing_spec ad7986_timing_spec = { .conv_ns = 500, .turbo_conv_ns = 400, + .data_ns = 300, + .turbo_data_ns = 200, }; struct ad7944_chip_info { @@ -180,6 +206,57 @@ static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct ad7944_adc * return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg); } +static int ad7944_3wire_cs_mode_init_turbo_msg(struct device *dev, + struct ad7944_adc *adc, + const struct iio_chan_spec *chan) +{ + struct spi_transfer *xfers = adc->turbo_xfers; + int ret; + + /* + * This sequence of xfers performs a read during conversion in 3-wire CS + * mode with timings for when TURBO mode is enabled. Using this msg will + * only work with SPI offloads since the timing needs to be precise to + * actually be able to read during the conversion time. + */ + + /* change CNV to high to trigger conversion */ + xfers[0].cs_off = 1; + xfers[0].bits_per_word = chan->scan_type.realbits; + xfers[0].delay.value = T_CNVH_NS; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + /* read sample data from previous conversion */ + xfers[1].rx_buf = &adc->sample.raw; + xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits); + xfers[1].bits_per_word = chan->scan_type.realbits; + /* + * CNV has to be high at end of conversion to avoid triggering the busy + * signal on the SDO line. + */ + xfers[1].cs_change = 1; + /* t[CONV] - t[CNVH] - t[EN] - t[DATA] */ + xfers[1].cs_change_delay.value = adc->timing_spec->turbo_conv_ns - + T_CNVH_NS - T_EN_NS - + adc->timing_spec->turbo_data_ns; + xfers[1].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS; + + /* + * Since this is the last xfer, this changes CNV to low and keeps it + * there until the next xfer. + */ + xfers[2].cs_change = 1; + xfers[2].bits_per_word = chan->scan_type.realbits; + + spi_message_init_with_transfers(&adc->turbo_msg, xfers, 3); + + ret = spi_optimize_message(adc->spi, &adc->turbo_msg); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->turbo_msg); +} + static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc, const struct iio_chan_spec *chan) { @@ -327,8 +404,132 @@ static int ad7944_read_raw(struct iio_dev *indio_dev, } } +static ssize_t ad7944_sampling_frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7944_adc *adc = iio_priv(indio_dev); + u64 period_ns; + + if (!adc->pwm) + return -ENODEV; + + period_ns = pwm_get_period(adc->pwm); + + return sysfs_emit(buf, "%llu\n", div64_u64(NSEC_PER_SEC, period_ns)); +} + +static ssize_t ad7944_sampling_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7944_adc *adc = iio_priv(indio_dev); + u64 period_ns; + u32 val; + int ret; + + if (!adc->pwm) + return -ENODEV; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + if (val == 0) + return -EINVAL; + + period_ns = div_u64(NSEC_PER_SEC, val); + + ret = pwm_config(adc->pwm, AD7944_PWM_TRIGGER_DUTY_CYCLE_NS, period_ns); + if (ret) + return ret; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(0644, ad7944_sampling_frequency_show, + ad7944_sampling_frequency_store); + +static struct attribute *ad7944_attrs[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL +}; + +static umode_t ad7944_attrs_is_visible(struct kobject *kobj, + struct attribute *attr, int unused) +{ + struct device *dev = kobj_to_dev(kobj); + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7944_adc *adc = iio_priv(indio_dev); + + /* hide sampling_frequency attribute when there is no pwm */ + if (attr == &iio_dev_attr_sampling_frequency.dev_attr.attr && !adc->pwm) + return 0; + + /* show all other attributes */ + return attr->mode; +} + +static const struct attribute_group ad7944_group = { + .attrs = ad7944_attrs, + .is_visible = ad7944_attrs_is_visible, +}; + static const struct iio_info ad7944_iio_info = { .read_raw = &ad7944_read_raw, + .attrs = &ad7944_group, +}; + +static int ad7944_offload_ex_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + switch (adc->spi_mode) { + case AD7944_SPI_MODE_DEFAULT: + return spi_engine_ex_offload_load_msg(adc->spi, &adc->msg); + case AD7944_SPI_MODE_SINGLE: + /* REVISIT: could do non-turbo for lower sample rates */ + gpiod_set_value_cansleep(adc->turbo, 1); + return spi_engine_ex_offload_load_msg(adc->spi, &adc->turbo_msg); + default: + return -EOPNOTSUPP; + } +} + +static int ad7944_offload_ex_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + spi_engine_ex_offload_enable(adc->spi, true); + + return 0; +} + +static int ad7944_offload_ex_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + spi_engine_ex_offload_enable(adc->spi, false); + + return 0; +} + +static int ad7944_offload_ex_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad7944_adc *adc = iio_priv(indio_dev); + + gpiod_set_value_cansleep(adc->turbo, 0); + + return 0; +} + +static const struct iio_buffer_setup_ops ad7944_offload_ex_buffer_setup_ops = { + .preenable = &ad7944_offload_ex_buffer_preenable, + .postenable = &ad7944_offload_ex_buffer_postenable, + .predisable = &ad7944_offload_ex_buffer_predisable, + .postdisable = &ad7944_offload_ex_buffer_postdisable, }; static irqreturn_t ad7944_trigger_handler(int irq, void *p) @@ -526,6 +727,10 @@ static int ad7944_probe(struct spi_device *spi) if (ret) return ret; + ret = ad7944_3wire_cs_mode_init_turbo_msg(dev, adc, &chip_info->channels[0]); + if (ret) + return ret; + break; case AD7944_SPI_MODE_CHAIN: return dev_err_probe(dev, -EINVAL, "chain mode is not implemented\n"); @@ -537,11 +742,41 @@ static int ad7944_probe(struct spi_device *spi) indio_dev->channels = chip_info->channels; indio_dev->num_channels = ARRAY_SIZE(chip_info->channels); - ret = devm_iio_triggered_buffer_setup(dev, indio_dev, - iio_pollfunc_store_time, - ad7944_trigger_handler, NULL); - if (ret) - return ret; + if (spi_engine_ex_offload_supported(spi)) { + struct pwm_state state = { + .period = NSEC_PER_SEC / AD7944_DEFAULT_SAMPLE_FREQ_HZ, + .duty_cycle = AD7944_PWM_TRIGGER_DUTY_CYCLE_NS, + .enabled = true, + .time_unit = PWM_UNIT_NSEC, + }; + + adc->pwm = devm_pwm_get(dev, NULL); + if (IS_ERR(adc->pwm)) + return dev_err_probe(dev, PTR_ERR(adc->pwm), + "failed to get PWM\n"); + + ret = pwm_apply_state(adc->pwm, &state); + if (ret) + return dev_err_probe(dev, ret, + "failed to apply PWM state\n"); + + ret = devm_iio_dmaengine_buffer_setup(dev, indio_dev, "rx", + IIO_BUFFER_DIRECTION_IN); + if (ret) + return ret; + + indio_dev->setup_ops = &ad7944_offload_ex_buffer_setup_ops; + + /* can't have soft timestamp with SPI offload */ + indio_dev->num_channels--; + } else { + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad7944_trigger_handler, + NULL); + if (ret) + return ret; + } return devm_iio_device_register(dev, indio_dev); }