| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Analog Devices LTC2378 ADC series driver | |
| * | |
| * Copyright (C) 2026 Analog Devices Inc. | |
| * Author: Marcelo Schmitt <marcelo.schmitt@analog.com> | |
| */ | |
| #include <linux/array_size.h> | |
| #include <linux/bitops.h> | |
| #include <linux/bits.h> | |
| #include <linux/byteorder/generic.h> | |
| #include <linux/cleanup.h> | |
| #include <linux/device.h> | |
| #include <linux/delay.h> | |
| #include <linux/device-id/spi.h> | |
| #include <linux/device-id/of.h> | |
| #include <linux/err.h> | |
| #include <linux/gpio/consumer.h> | |
| #include <linux/math64.h> | |
| #include <linux/minmax.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/regulator/consumer.h> | |
| #include <linux/pwm.h> | |
| #include <linux/spi/spi.h> | |
| #include <linux/spi/offload/consumer.h> | |
| #include <linux/spi/offload/types.h> | |
| #include <linux/time64.h> | |
| #include <linux/types.h> | |
| #include <linux/units.h> | |
| #include <linux/iio/buffer.h> | |
| #include <linux/iio/buffer-dmaengine.h> | |
| #include <linux/iio/iio.h> | |
| #include <linux/iio/triggered_buffer.h> | |
| #include <linux/iio/trigger_consumer.h> | |
| #include <linux/iio/types.h> | |
| #define LTC2378_TDSDOBUSYL_NS 5 | |
| #define LTC2378_TBUSYLH_NS 13 | |
| #define LTC2378_TCNV_HIGH_NS 20 | |
| #define LTC2378_MAX_DATA_WAIT_US 4 /* max(TBUSYLH + TCONV + TDSDOBUSYL) */ | |
| #define LTC2378_CHANNEL(_sign, _real_bits, _storage_bits) \ | |
| { \ | |
| .type = IIO_VOLTAGE, \ | |
| .indexed = 1, \ | |
| .differential = _sign, \ | |
| .channel = 0, \ | |
| .channel2 = _sign ? 1 : 0, \ | |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ | |
| BIT(IIO_CHAN_INFO_SCALE), \ | |
| .scan_index = 0, \ | |
| .scan_type = { \ | |
| .format = _sign ? IIO_SCAN_FORMAT_SIGNED_INT : \ | |
| IIO_SCAN_FORMAT_UNSIGNED_INT, \ | |
| .realbits = _real_bits, \ | |
| .storagebits = _storage_bits, \ | |
| .shift = _storage_bits - _real_bits, \ | |
| .endianness = IIO_BE, \ | |
| }, \ | |
| } | |
| #define LTC2378_DIFF_CHANNEL(_real_bits) \ | |
| LTC2378_CHANNEL(1, _real_bits, (((_real_bits) > 16) ? 32 : 16)) | |
| #define LTC2378_PSEUDO_DIFF_CHANNEL(_real_bits) \ | |
| LTC2378_CHANNEL(0, _real_bits, (((_real_bits) > 16) ? 32 : 16)) | |
| #define LTC2378_OFFLOAD_CHANNEL(_sign, _real_bits, _storage_bits) \ | |
| { \ | |
| .type = IIO_VOLTAGE, \ | |
| .indexed = 1, \ | |
| .differential = _sign, \ | |
| .channel = 0, \ | |
| .channel2 = _sign ? 1 : 0, \ | |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ | |
| BIT(IIO_CHAN_INFO_SCALE) | \ | |
| BIT(IIO_CHAN_INFO_SAMP_FREQ), \ | |
| .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ | |
| .scan_index = 0, \ | |
| .scan_type = { \ | |
| .format = _sign ? IIO_SCAN_FORMAT_SIGNED_INT : \ | |
| IIO_SCAN_FORMAT_UNSIGNED_INT, \ | |
| .realbits = _real_bits, \ | |
| .storagebits = _storage_bits, \ | |
| .shift = 0, \ | |
| .endianness = IIO_CPU, \ | |
| }, \ | |
| } | |
| /* | |
| * Currently, the available offload hardware + DMA configuration only supports | |
| * pushing 32-bit data elements to DMA IIO buffers in CPU endianness. For 16-bit | |
| * precision parts, those 32-bit elements (in CPU endianness) contain 2 bytes | |
| * with data and 2 bytes always zeroed out. Nevertheless, for the offload use | |
| * case, the IIO buffer is configured for 32 storage bits in CPU endianness so | |
| * data is correctly aligned in user space despite 2 out of the 4 bytes being | |
| * zeros. | |
| */ | |
| #define LTC2378_OFFLOAD_DIFF_CHANNEL(_real_bits) \ | |
| LTC2378_OFFLOAD_CHANNEL(1, (_real_bits), 32) | |
| #define LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(_real_bits) \ | |
| LTC2378_OFFLOAD_CHANNEL(0, (_real_bits), 32) | |
| struct ltc2378_chip_info { | |
| const char *name; | |
| unsigned int internal_ref_uV; | |
| struct u32_fract internal_div; | |
| struct iio_chan_spec chan[2]; /* 1 physical chan + 1 timestamp chan */ | |
| struct iio_chan_spec offload_chan; | |
| unsigned int max_sample_rate_Hz; | |
| unsigned int tconv_ns; | |
| }; | |
| struct ltc2378_state { | |
| const struct ltc2378_chip_info *info; | |
| struct gpio_desc *cnv_gpio; | |
| struct spi_device *spi; | |
| struct mutex lock; /* Protect data acquisition cycle */ | |
| int ref_uV; | |
| struct spi_transfer xfer; | |
| struct spi_transfer offload_xfer; | |
| struct spi_offload *offload; | |
| struct spi_offload_trigger *offload_trigger; | |
| struct pwm_waveform cnv_wf; | |
| struct spi_message offload_msg; | |
| struct spi_offload_trigger_config offload_trigger_config; | |
| struct pwm_device *cnv_trigger; | |
| unsigned int cnv_Hz; | |
| unsigned int sample_freq_range[3]; | |
| /* | |
| * DMA (thus cache coherency maintenance) requires the transfer buffers | |
| * to live in their own cache lines. | |
| */ | |
| struct { | |
| union { | |
| __be16 sample_buf16_be; | |
| __be32 sample_buf32_be; | |
| u16 sample_buf16; | |
| u32 sample_buf32; | |
| } data; | |
| aligned_s64 timestamp; | |
| } scan __aligned(IIO_DMA_MINALIGN); | |
| }; | |
| static const struct ltc2378_chip_info ltc2338_18_chip_info = { | |
| .name = "ltc2338-18", | |
| .internal_ref_uV = 2048000, | |
| .internal_div = { .numerator = 5, .denominator = 2 }, | |
| .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), | |
| .max_sample_rate_Hz = 1 * HZ_PER_MHZ, | |
| .tconv_ns = 527, | |
| }; | |
| static const struct ltc2378_chip_info ltc2364_16_chip_info = { | |
| .name = "ltc2364-16", | |
| .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(16), | |
| .max_sample_rate_Hz = 250 * HZ_PER_KHZ, | |
| .tconv_ns = 3000, | |
| }; | |
| static const struct ltc2378_chip_info ltc2364_18_chip_info = { | |
| .name = "ltc2364-18", | |
| .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(18), | |
| .max_sample_rate_Hz = 250 * HZ_PER_KHZ, | |
| .tconv_ns = 3000, | |
| }; | |
| static const struct ltc2378_chip_info ltc2367_16_chip_info = { | |
| .name = "ltc2367-16", | |
| .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(16), | |
| .max_sample_rate_Hz = 500 * HZ_PER_KHZ, | |
| .tconv_ns = 1500, | |
| }; | |
| static const struct ltc2378_chip_info ltc2367_18_chip_info = { | |
| .name = "ltc2367-18", | |
| .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(18), | |
| .max_sample_rate_Hz = 500 * HZ_PER_KHZ, | |
| .tconv_ns = 1500, | |
| }; | |
| static const struct ltc2378_chip_info ltc2368_16_chip_info = { | |
| .name = "ltc2368-16", | |
| .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(16), | |
| .max_sample_rate_Hz = 1 * HZ_PER_MHZ, | |
| .tconv_ns = 527, | |
| }; | |
| static const struct ltc2378_chip_info ltc2368_18_chip_info = { | |
| .name = "ltc2368-18", | |
| .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(18), | |
| .max_sample_rate_Hz = 1 * HZ_PER_MHZ, | |
| .tconv_ns = 527, | |
| }; | |
| static const struct ltc2378_chip_info ltc2369_18_chip_info = { | |
| .name = "ltc2369-18", | |
| .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(18), | |
| .max_sample_rate_Hz = 1600 * HZ_PER_KHZ, | |
| .tconv_ns = 412, | |
| }; | |
| static const struct ltc2378_chip_info ltc2370_16_chip_info = { | |
| .name = "ltc2370-16", | |
| .chan = { LTC2378_PSEUDO_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_PSEUDO_DIFF_CHANNEL(16), | |
| .max_sample_rate_Hz = 2 * HZ_PER_MHZ, | |
| .tconv_ns = 322, | |
| }; | |
| static const struct ltc2378_chip_info ltc2376_16_chip_info = { | |
| .name = "ltc2376-16", | |
| .chan = { LTC2378_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(16), | |
| .max_sample_rate_Hz = 250 * HZ_PER_KHZ, | |
| .tconv_ns = 3000, | |
| }; | |
| static const struct ltc2378_chip_info ltc2376_18_chip_info = { | |
| .name = "ltc2376-18", | |
| .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), | |
| .max_sample_rate_Hz = 250 * HZ_PER_KHZ, | |
| .tconv_ns = 3000, | |
| }; | |
| static const struct ltc2378_chip_info ltc2376_20_chip_info = { | |
| .name = "ltc2376-20", | |
| .chan = { LTC2378_DIFF_CHANNEL(20), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(20), | |
| .max_sample_rate_Hz = 250 * HZ_PER_KHZ, | |
| .tconv_ns = 3000, | |
| }; | |
| static const struct ltc2378_chip_info ltc2377_16_chip_info = { | |
| .name = "ltc2377-16", | |
| .chan = { LTC2378_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(16), | |
| .max_sample_rate_Hz = 500 * HZ_PER_KHZ, | |
| .tconv_ns = 1500, | |
| }; | |
| static const struct ltc2378_chip_info ltc2377_18_chip_info = { | |
| .name = "ltc2377-18", | |
| .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), | |
| .max_sample_rate_Hz = 500 * HZ_PER_KHZ, | |
| .tconv_ns = 1500, | |
| }; | |
| static const struct ltc2378_chip_info ltc2377_20_chip_info = { | |
| .name = "ltc2377-20", | |
| .chan = { LTC2378_DIFF_CHANNEL(20), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(20), | |
| .max_sample_rate_Hz = 500 * HZ_PER_KHZ, | |
| .tconv_ns = 1500, | |
| }; | |
| static const struct ltc2378_chip_info ltc2378_16_chip_info = { | |
| .name = "ltc2378-16", | |
| .chan = { LTC2378_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(16), | |
| .max_sample_rate_Hz = 1 * HZ_PER_MHZ, | |
| .tconv_ns = 527, | |
| }; | |
| static const struct ltc2378_chip_info ltc2378_18_chip_info = { | |
| .name = "ltc2378-18", | |
| .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), | |
| .max_sample_rate_Hz = 1 * HZ_PER_MHZ, | |
| .tconv_ns = 527, | |
| }; | |
| static const struct ltc2378_chip_info ltc2378_20_chip_info = { | |
| .name = "ltc2378-20", | |
| .chan = { LTC2378_DIFF_CHANNEL(20), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(20), | |
| .max_sample_rate_Hz = 1 * HZ_PER_MHZ, | |
| .tconv_ns = 675, | |
| }; | |
| static const struct ltc2378_chip_info ltc2379_18_chip_info = { | |
| .name = "ltc2379-18", | |
| .chan = { LTC2378_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(18), | |
| .max_sample_rate_Hz = 1600 * HZ_PER_KHZ, | |
| .tconv_ns = 412, | |
| }; | |
| static const struct ltc2378_chip_info ltc2380_16_chip_info = { | |
| .name = "ltc2380-16", | |
| .chan = { LTC2378_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) }, | |
| .offload_chan = LTC2378_OFFLOAD_DIFF_CHANNEL(16), | |
| .max_sample_rate_Hz = 2 * HZ_PER_MHZ, | |
| .tconv_ns = 322, | |
| }; | |
| static int ltc2378_convert_and_acquire(struct ltc2378_state *st) | |
| { | |
| int ret; | |
| /* Cause a rising edge of CNV to initiate a new ADC conversion */ | |
| gpiod_set_value_cansleep(st->cnv_gpio, 1); | |
| fsleep(LTC2378_MAX_DATA_WAIT_US); | |
| ret = spi_sync_transfer(st->spi, &st->xfer, 1); | |
| gpiod_set_value_cansleep(st->cnv_gpio, 0); | |
| return ret; | |
| } | |
| static irqreturn_t ltc2378_trigger_handler(int irq, void *p) | |
| { | |
| struct iio_poll_func *pf = p; | |
| struct iio_dev *indio_dev = pf->indio_dev; | |
| struct ltc2378_state *st = iio_priv(indio_dev); | |
| int ret; | |
| ret = ltc2378_convert_and_acquire(st); | |
| if (ret < 0) | |
| goto err_out; | |
| iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan), | |
| pf->timestamp); | |
| err_out: | |
| iio_trigger_notify_done(indio_dev->trig); | |
| return IRQ_HANDLED; | |
| } | |
| static int ltc2378_channel_single_read(const struct iio_chan_spec *chan, | |
| struct ltc2378_state *st, int *val) | |
| { | |
| const struct iio_scan_type *scan_type = &chan->scan_type; | |
| u32 sample; | |
| int ret; | |
| guard(mutex)(&st->lock); | |
| ret = ltc2378_convert_and_acquire(st); | |
| if (ret) | |
| return ret; | |
| if (chan->scan_type.endianness == IIO_BE) { | |
| if (chan->scan_type.realbits > 16) | |
| sample = be32_to_cpu(st->scan.data.sample_buf32_be); | |
| else | |
| sample = be16_to_cpu(st->scan.data.sample_buf16_be); | |
| } else { /* IIO_CPU */ | |
| if (chan->scan_type.realbits > 16) | |
| sample = st->scan.data.sample_buf32; | |
| else | |
| sample = st->scan.data.sample_buf16; | |
| } | |
| sample >>= chan->scan_type.shift; | |
| if (scan_type->format == IIO_SCAN_FORMAT_SIGNED_INT) | |
| *val = sign_extend32(sample, scan_type->realbits - 1); | |
| else | |
| *val = sample; | |
| return 0; | |
| } | |
| static int ltc2378_read_raw(struct iio_dev *indio_dev, | |
| const struct iio_chan_spec *chan, | |
| int *val, int *val2, long mask) | |
| { | |
| struct ltc2378_state *st = iio_priv(indio_dev); | |
| int ret; | |
| switch (mask) { | |
| case IIO_CHAN_INFO_RAW: { | |
| IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); | |
| if (IIO_DEV_ACQUIRE_FAILED(claim)) | |
| return -EBUSY; | |
| ret = ltc2378_channel_single_read(chan, st, val); | |
| if (ret) | |
| return ret; | |
| return IIO_VAL_INT; | |
| } | |
| case IIO_CHAN_INFO_SCALE: { | |
| struct u32_fract fract = st->info->internal_div; | |
| *val = st->ref_uV / MILLI; | |
| if (fract.numerator && fract.denominator) | |
| *val = mult_frac(*val, fract.numerator, fract.denominator); | |
| /* | |
| * For all LTC2378-like devices, the amount of bits that express | |
| * voltage magnitude depend on the polarity / output code format: | |
| * - straight binary: All precision/resolution bits are used. | |
| * - 2's complement: One of the precision bits is used for sign. | |
| */ | |
| if (chan->scan_type.format == IIO_SCAN_FORMAT_SIGNED_INT) | |
| *val2 = chan->scan_type.realbits - 1; | |
| else | |
| *val2 = chan->scan_type.realbits; | |
| return IIO_VAL_FRACTIONAL_LOG2; | |
| } | |
| case IIO_CHAN_INFO_SAMP_FREQ: | |
| *val = st->cnv_Hz; | |
| return IIO_VAL_INT; | |
| default: | |
| return -EINVAL; | |
| } | |
| } | |
| static int ltc2378_read_avail(struct iio_dev *indio_dev, | |
| struct iio_chan_spec const *chan, | |
| const int **vals, int *type, int *length, long mask) | |
| { | |
| struct ltc2378_state *st = iio_priv(indio_dev); | |
| switch (mask) { | |
| case IIO_CHAN_INFO_SAMP_FREQ: | |
| *vals = st->sample_freq_range; | |
| *type = IIO_VAL_INT; | |
| return IIO_AVAIL_RANGE; | |
| default: | |
| return -EINVAL; | |
| } | |
| } | |
| /* | |
| * SPI offload wiring schema | |
| * | |
| * +-------------+ +-------------+ | |
| * | CNV |<-----+--| GPIO | | |
| * | | +--| PWM0 | | |
| * | | | | | |
| * | | +--| PWM1 | | |
| * | | | +-------------+ | |
| * | | +->| TRIGGER | | |
| * | | | | | |
| * | ADC | | SPI | | |
| * | | | controller | | |
| * | | | | | |
| * | SDI |<--------| SDO | | |
| * | SDO |-------->| SDI | | |
| * | SCLK |<--------| SCLK | | |
| * +-------------+ +-------------+ | |
| * | |
| */ | |
| static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz) | |
| { | |
| struct spi_offload_trigger_config config = st->offload_trigger_config; | |
| unsigned int min_read_offset, offload_period_ns; | |
| struct pwm_waveform cnv_wf = { }; | |
| u64 target = LTC2378_TCNV_HIGH_NS; | |
| unsigned int count; | |
| u64 offload_offset_ns; | |
| int ret; | |
| if (freq_Hz == 0) | |
| return -EINVAL; | |
| if (!in_range(freq_Hz, 1, st->info->max_sample_rate_Hz)) | |
| return -ERANGE; | |
| /* Configure CNV PWM waveform */ | |
| cnv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq_Hz); | |
| /* | |
| * Ensure CNV high time meets minimum requirement (20ns). The PWM | |
| * hardware may round the duty cycle, so iterate until we get at least | |
| * the minimum required high time (or reach a try count limit). | |
| */ | |
| count = 100; | |
| do { | |
| cnv_wf.duty_length_ns = target; | |
| ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf); | |
| if (ret) | |
| return ret; | |
| target += 10; /* Increment by PWM duty cycle period */ | |
| } while (count-- && cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS); | |
| /* Check the minimum CNV high time is met */ | |
| if (cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS) | |
| return -EDOM; | |
| /* | |
| * Configure SPI offload PWM trigger. | |
| * The trigger should fire after tBUSYLH + tCONV + tDSDOBUSYL. | |
| * Minimum time needed: TBUSYLH (13ns) + TCONV (part-specific) + TDSDOBUSYL (5ns) | |
| * | |
| * Use the same period as CNV PWM to avoid timing issues. | |
| * Convert back from period to frequency for the SPI offload API. | |
| */ | |
| offload_period_ns = cnv_wf.period_length_ns; | |
| config.periodic.frequency_hz = div_u64(HZ_PER_GHZ, offload_period_ns); | |
| min_read_offset = LTC2378_TBUSYLH_NS + st->info->tconv_ns + LTC2378_TDSDOBUSYL_NS; | |
| offload_offset_ns = min_read_offset; | |
| count = 100; | |
| do { | |
| config.periodic.offset_ns = offload_offset_ns; | |
| ret = spi_offload_trigger_validate(st->offload_trigger, &config); | |
| if (ret) | |
| return ret; | |
| offload_offset_ns += 10; | |
| } while (count-- && config.periodic.offset_ns < min_read_offset); | |
| /* Check the minimum CNV to SCLK delay is met */ | |
| if (config.periodic.offset_ns < min_read_offset) | |
| return -EDOM; | |
| /* Check the PWM periods remain the same */ | |
| offload_period_ns = div64_u64(HZ_PER_GHZ, config.periodic.frequency_hz); | |
| if (cnv_wf.period_length_ns != offload_period_ns) | |
| return -EDOM; | |
| st->offload_trigger_config = config; | |
| st->cnv_wf = cnv_wf; | |
| st->cnv_Hz = DIV_ROUND_CLOSEST_ULL(HZ_PER_GHZ, cnv_wf.period_length_ns); | |
| return 0; | |
| } | |
| static int ltc2378_write_raw(struct iio_dev *indio_dev, | |
| struct iio_chan_spec const *chan, | |
| int val, int val2, long mask) | |
| { | |
| struct ltc2378_state *st = iio_priv(indio_dev); | |
| IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); | |
| if (IIO_DEV_ACQUIRE_FAILED(claim)) | |
| return -EBUSY; | |
| switch (mask) { | |
| case IIO_CHAN_INFO_SAMP_FREQ: | |
| return ltc2378_update_conversion_rate(st, val); | |
| default: | |
| return -EINVAL; | |
| } | |
| } | |
| static const struct iio_info ltc2378_iio_info = { | |
| .read_raw = <c2378_read_raw, | |
| }; | |
| static const struct iio_info ltc2378_offload_iio_info = { | |
| .read_raw = <c2378_read_raw, | |
| .read_avail = <c2378_read_avail, | |
| .write_raw = <c2378_write_raw, | |
| }; | |
| static int ltc2378_offload_buffer_postenable(struct iio_dev *indio_dev) | |
| { | |
| struct ltc2378_state *st = iio_priv(indio_dev); | |
| int ret; | |
| ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_wf, true); | |
| if (ret) | |
| return ret; | |
| ret = spi_offload_trigger_enable(st->offload, st->offload_trigger, | |
| &st->offload_trigger_config); | |
| if (ret) | |
| goto out_pwm_disable; | |
| return 0; | |
| out_pwm_disable: | |
| pwm_disable(st->cnv_trigger); | |
| return ret; | |
| } | |
| static int ltc2378_offload_buffer_predisable(struct iio_dev *indio_dev) | |
| { | |
| struct ltc2378_state *st = iio_priv(indio_dev); | |
| spi_offload_trigger_disable(st->offload, st->offload_trigger); | |
| pwm_disable(st->cnv_trigger); | |
| return 0; | |
| } | |
| static const struct iio_buffer_setup_ops ltc2378_offload_buffer_ops = { | |
| .postenable = <c2378_offload_buffer_postenable, | |
| .predisable = <c2378_offload_buffer_predisable, | |
| }; | |
| static int ltc2378_prepare_offload_message(struct device *dev, | |
| struct ltc2378_state *st) | |
| { | |
| unsigned int resolution = st->info->offload_chan.scan_type.realbits; | |
| st->offload_xfer.bits_per_word = resolution; | |
| st->offload_xfer.len = spi_bpw_to_bytes(resolution); | |
| st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM; | |
| /* Initialize message with offload */ | |
| spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1); | |
| st->offload_msg.offload = st->offload; | |
| return devm_spi_optimize_message(dev, st->spi, &st->offload_msg); | |
| } | |
| static int ltc2378_spi_offload_setup(struct iio_dev *indio_dev, | |
| struct ltc2378_state *st) | |
| { | |
| struct device *dev = &st->spi->dev; | |
| struct dma_chan *rx_dma; | |
| indio_dev->setup_ops = <c2378_offload_buffer_ops; | |
| st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload, | |
| SPI_OFFLOAD_TRIGGER_PERIODIC); | |
| if (IS_ERR(st->offload_trigger)) | |
| return dev_err_probe(dev, PTR_ERR(st->offload_trigger), | |
| "failed to get offload trigger\n"); | |
| st->offload_trigger_config.type = SPI_OFFLOAD_TRIGGER_PERIODIC; | |
| rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload); | |
| if (IS_ERR(rx_dma)) | |
| return dev_err_probe(dev, PTR_ERR(rx_dma), "failed to get offload RX DMA\n"); | |
| return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma, | |
| IIO_BUFFER_DIRECTION_IN); | |
| } | |
| static int ltc2378_pwm_get(struct ltc2378_state *st) | |
| { | |
| struct device *dev = &st->spi->dev; | |
| st->cnv_trigger = devm_pwm_get(dev, NULL); | |
| if (IS_ERR(st->cnv_trigger)) | |
| return dev_err_probe(dev, PTR_ERR(st->cnv_trigger), | |
| "failed to get cnv pwm\n"); | |
| /* | |
| * Disable the PWM connected to CNV in case it was left running by | |
| * something else. | |
| */ | |
| pwm_disable(st->cnv_trigger); | |
| return 0; | |
| } | |
| static const struct spi_offload_config ltc2378_offload_config = { | |
| .capability_flags = SPI_OFFLOAD_CAP_TRIGGER | | |
| SPI_OFFLOAD_CAP_RX_STREAM_DMA, | |
| }; | |
| static int ltc2378_refin_setup(struct device *dev, struct ltc2378_state *st) | |
| { | |
| int ret; | |
| /* | |
| * The internal reference buffer amplifies both the internal reference | |
| * and REFIN by a factor of 2. | |
| */ | |
| ret = devm_regulator_get_enable_read_voltage(dev, "refin"); | |
| if (ret == -ENODEV) { /* refin is optional */ | |
| st->ref_uV = st->info->internal_ref_uV * 2; | |
| return 0; | |
| } | |
| if (ret < 0) | |
| return dev_err_probe(dev, ret, "failed to read refin regulator\n"); | |
| st->ref_uV = ret * 2; | |
| return 0; | |
| } | |
| static int ltc2378_ref_setup(struct device *dev, struct ltc2378_state *st) | |
| { | |
| int ret; | |
| ret = devm_regulator_get_enable_read_voltage(dev, "ref"); | |
| if (ret < 0) | |
| return dev_err_probe(dev, ret, "failed to read ref regulator\n"); | |
| st->ref_uV = ret; | |
| return 0; | |
| } | |
| static int ltc2378_probe(struct spi_device *spi) | |
| { | |
| struct device *dev = &spi->dev; | |
| struct iio_dev *indio_dev; | |
| struct ltc2378_state *st; | |
| int ret; | |
| indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); | |
| if (!indio_dev) | |
| return -ENOMEM; | |
| st = iio_priv(indio_dev); | |
| st->spi = spi; | |
| ret = devm_mutex_init(dev, &st->lock); | |
| if (ret) | |
| return ret; | |
| st->info = spi_get_device_match_data(spi); | |
| if (!st->info) | |
| return -EINVAL; | |
| if (st->info->internal_ref_uV) | |
| ret = ltc2378_refin_setup(dev, st); | |
| else | |
| ret = ltc2378_ref_setup(dev, st); | |
| if (ret) | |
| return ret; | |
| indio_dev->name = st->info->name; | |
| indio_dev->modes = INDIO_DIRECT_MODE; | |
| st->cnv_gpio = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW); | |
| if (IS_ERR(st->cnv_gpio)) | |
| return dev_err_probe(dev, PTR_ERR(st->cnv_gpio), | |
| "failed to get CNV GPIO"); | |
| st->offload = devm_spi_offload_get(dev, spi, <c2378_offload_config); | |
| ret = PTR_ERR_OR_ZERO(st->offload); | |
| /* Fall back to low speed usage when no SPI offload is available. */ | |
| if (ret == -ENODEV) { | |
| indio_dev->info = <c2378_iio_info; | |
| indio_dev->channels = st->info->chan; | |
| indio_dev->num_channels = ARRAY_SIZE(st->info->chan); | |
| ret = devm_iio_triggered_buffer_setup(dev, indio_dev, | |
| iio_pollfunc_store_time, | |
| ltc2378_trigger_handler, | |
| NULL); | |
| if (ret) | |
| return ret; | |
| } else if (ret) { | |
| return dev_err_probe(dev, ret, "failed to get offload\n"); | |
| } else { | |
| indio_dev->info = <c2378_offload_iio_info; | |
| indio_dev->channels = &st->info->offload_chan; | |
| indio_dev->num_channels = 1; | |
| ret = ltc2378_spi_offload_setup(indio_dev, st); | |
| if (ret) | |
| return dev_err_probe(dev, ret, | |
| "failed to setup SPI offload\n"); | |
| ret = ltc2378_pwm_get(st); | |
| if (ret) | |
| return dev_err_probe(dev, ret, "failed to get PWM\n"); | |
| st->sample_freq_range[0] = 1; /* min */ | |
| st->sample_freq_range[1] = 1; /* step */ | |
| st->sample_freq_range[2] = st->info->max_sample_rate_Hz; /* max */ | |
| /* | |
| * Start with a slower sampling rate so there is some room for | |
| * adjusting the sample averaging and the sampling frequency | |
| * without hitting the maximum conversion rate. | |
| */ | |
| ret = ltc2378_update_conversion_rate(st, st->info->max_sample_rate_Hz >> 4); | |
| if (ret) | |
| return dev_err_probe(dev, ret, | |
| "failed to set offload samp freq\n"); | |
| ret = ltc2378_prepare_offload_message(&spi->dev, st); | |
| if (ret) | |
| return dev_err_probe(dev, ret, "failed to optimize SPI message\n"); | |
| /* | |
| * Set single-read transfer bits_per_word so the SPI subsystem | |
| * rearranges data to CPU endianness, enabling us to reuse | |
| * offload_chan specifications for single-shot reads. | |
| */ | |
| st->xfer.bits_per_word = st->info->offload_chan.scan_type.realbits; | |
| } | |
| st->xfer.rx_buf = &st->scan.data; | |
| st->xfer.len = spi_bpw_to_bytes(indio_dev->channels[0].scan_type.realbits); | |
| return devm_iio_device_register(&spi->dev, indio_dev); | |
| } | |
| static const struct of_device_id ltc2378_of_match[] = { | |
| { .compatible = "adi,ltc2338-18", .data = <c2338_18_chip_info }, | |
| { .compatible = "adi,ltc2364-16", .data = <c2364_16_chip_info }, | |
| { .compatible = "adi,ltc2364-18", .data = <c2364_18_chip_info }, | |
| { .compatible = "adi,ltc2367-16", .data = <c2367_16_chip_info }, | |
| { .compatible = "adi,ltc2367-18", .data = <c2367_18_chip_info }, | |
| { .compatible = "adi,ltc2368-16", .data = <c2368_16_chip_info }, | |
| { .compatible = "adi,ltc2368-18", .data = <c2368_18_chip_info }, | |
| { .compatible = "adi,ltc2369-18", .data = <c2369_18_chip_info }, | |
| { .compatible = "adi,ltc2370-16", .data = <c2370_16_chip_info }, | |
| { .compatible = "adi,ltc2376-16", .data = <c2376_16_chip_info }, | |
| { .compatible = "adi,ltc2376-18", .data = <c2376_18_chip_info }, | |
| { .compatible = "adi,ltc2376-20", .data = <c2376_20_chip_info }, | |
| { .compatible = "adi,ltc2377-16", .data = <c2377_16_chip_info }, | |
| { .compatible = "adi,ltc2377-18", .data = <c2377_18_chip_info }, | |
| { .compatible = "adi,ltc2377-20", .data = <c2377_20_chip_info }, | |
| { .compatible = "adi,ltc2378-16", .data = <c2378_16_chip_info }, | |
| { .compatible = "adi,ltc2378-18", .data = <c2378_18_chip_info }, | |
| { .compatible = "adi,ltc2378-20", .data = <c2378_20_chip_info }, | |
| { .compatible = "adi,ltc2379-18", .data = <c2379_18_chip_info }, | |
| { .compatible = "adi,ltc2380-16", .data = <c2380_16_chip_info }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(of, ltc2378_of_match); | |
| static const struct spi_device_id ltc2378_spi_id[] = { | |
| { .name = "ltc2338-18", .driver_data = (kernel_ulong_t)<c2338_18_chip_info }, | |
| { .name = "ltc2364-16", .driver_data = (kernel_ulong_t)<c2364_16_chip_info }, | |
| { .name = "ltc2364-18", .driver_data = (kernel_ulong_t)<c2364_18_chip_info }, | |
| { .name = "ltc2367-16", .driver_data = (kernel_ulong_t)<c2367_16_chip_info }, | |
| { .name = "ltc2367-18", .driver_data = (kernel_ulong_t)<c2367_18_chip_info }, | |
| { .name = "ltc2368-16", .driver_data = (kernel_ulong_t)<c2368_16_chip_info }, | |
| { .name = "ltc2368-18", .driver_data = (kernel_ulong_t)<c2368_18_chip_info }, | |
| { .name = "ltc2369-18", .driver_data = (kernel_ulong_t)<c2369_18_chip_info }, | |
| { .name = "ltc2370-16", .driver_data = (kernel_ulong_t)<c2370_16_chip_info }, | |
| { .name = "ltc2376-16", .driver_data = (kernel_ulong_t)<c2376_16_chip_info }, | |
| { .name = "ltc2376-18", .driver_data = (kernel_ulong_t)<c2376_18_chip_info }, | |
| { .name = "ltc2376-20", .driver_data = (kernel_ulong_t)<c2376_20_chip_info }, | |
| { .name = "ltc2377-16", .driver_data = (kernel_ulong_t)<c2377_16_chip_info }, | |
| { .name = "ltc2377-18", .driver_data = (kernel_ulong_t)<c2377_18_chip_info }, | |
| { .name = "ltc2377-20", .driver_data = (kernel_ulong_t)<c2377_20_chip_info }, | |
| { .name = "ltc2378-16", .driver_data = (kernel_ulong_t)<c2378_16_chip_info }, | |
| { .name = "ltc2378-18", .driver_data = (kernel_ulong_t)<c2378_18_chip_info }, | |
| { .name = "ltc2378-20", .driver_data = (kernel_ulong_t)<c2378_20_chip_info }, | |
| { .name = "ltc2379-18", .driver_data = (kernel_ulong_t)<c2379_18_chip_info }, | |
| { .name = "ltc2380-16", .driver_data = (kernel_ulong_t)<c2380_16_chip_info }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(spi, ltc2378_spi_id); | |
| static struct spi_driver ltc2378_driver = { | |
| .driver = { | |
| .name = "ltc2378", | |
| .of_match_table = ltc2378_of_match | |
| }, | |
| .probe = ltc2378_probe, | |
| .id_table = ltc2378_spi_id, | |
| }; | |
| module_spi_driver(ltc2378_driver); | |
| MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>"); | |
| MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver"); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); | |
| MODULE_IMPORT_NS("SPI_OFFLOAD"); |