From nobody Mon Apr 6 10:45:18 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D8257396D1E; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; cv=none; b=MXxjsc8D2YpcslVdHGDl2cj7qN6AAdSfL8u2sbWMHG9xbl73bge/UYIHeWxE+uH+kX8WKdyKCHxuUPq8FjRw0H+BghDgkJMprD77QsxgATHxWebI8GoG9ZpD9XRxbGJBFZq4uPvrmqZdKlahN4NNHbFJbiURZH+Wx2HBVtRZOmA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; c=relaxed/simple; bh=d0/ZPw94aFPTAExgWJwi5bY5yasqhO+dLDXzto1Lo0I=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=UNWTHGioKJgZ2cF6aClFdRrwIHBFq1fzJgI7zs5uJj05iJlccZW8jp+Kix73zFkdUbK/98yZFOwCq9ZK4LQIUSKq3z9uCwPDDb31Kuir8xXnvmQglnOwiCNkbds16Sg71Nh3Dq9Bhvk2xTm2IuvU/+GSJ+BQ2XykxBvI4fo54a4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=uO1t5oGH; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="uO1t5oGH" Received: by smtp.kernel.org (Postfix) with ESMTPS id A686EC2BCC9; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774004639; bh=d0/ZPw94aFPTAExgWJwi5bY5yasqhO+dLDXzto1Lo0I=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=uO1t5oGHTLwEEpWQTiqm5IgBe2Wm6sxyN09lfz/SccPgTzXAM+teVqBI2Zum64m6l z/+/o8PgslLWDCVP2+ju5YgiWRFblvtnpU+UeiDNzTj5oJdpAgbAPBNdu1MjFzmh9Y kCpzRSRnHHEo4rwCFfwe8GgyTbziMDhYjIkhbgk4Ql/vr1IHm92KQ541gcYX5dlcio /Pv/qno0BlHX7FszpzY3/29FoBNokDZfMDjA7z+Ine+cF3f0Ingc6fH7DXJzhFEMug GXAF/3gK0r2fSX67LDmMN/RbO1SSbnZlWtjAIbgJkJn+K6GKWyIM2G/Fh4Rilflh3Z UBIxFUowS3DqA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9CA5C108B8F3; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Fri, 20 Mar 2026 13:03:58 +0200 Subject: [PATCH v4 4/4] iio: adc: ad4691: add SPI offload support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260320-ad4692-multichannel-sar-adc-driver-v4-4-052c1050507a@analog.com> References: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> In-Reply-To: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> To: Lars-Peter Clausen , Michael Hennerich , Jonathan Cameron , David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Liam Girdwood , Mark Brown , Linus Walleij , Bartosz Golaszewski , Philipp Zabel Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774004636; l=23384; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=CcNob1GhOWpVfV4i05s0F2XBUXGCckdQbVMCSNH+1Go=; b=SU5pnU+A0aTeL95IAt11cS9Hto61a/gS2bAyFQPWD9pTz9yx/9zTQKalqarf/6KK2pfIE3613 eqtsffDXqGaC/EHonsjqb0Y7nnTyMxvAfNW06j5iD8S9LbvtQkBh6YV X-Developer-Key: i=radu.sabau@analog.com; a=ed25519; pk=lDPQHgn9jTdt0vo58Na9lLxLaE2mb330if71Cn+EvFU= X-Endpoint-Received: by B4 Relay for radu.sabau@analog.com/20260220 with auth_id=642 X-Original-From: Radu Sabau Reply-To: radu.sabau@analog.com From: Radu Sabau Add SPI offload support to enable DMA-based, CPU-independent data acquisition using the SPI Engine offload framework. When an SPI offload is available (devm_spi_offload_get() succeeds), the driver registers a DMA engine IIO buffer and uses dedicated buffer setup operations. If no offload is available the existing software triggered buffer path is used unchanged. Both CNV Burst Mode and Manual Mode support offload, but use different trigger mechanisms: CNV Burst Mode: the SPI Engine is triggered by the ADC's DATA_READY signal on the GP pin specified by the trigger-source consumer reference in the device tree (one cell =3D GP pin number 0-3). For this mode the driver acts as both an SPI offload consumer (DMA RX stream, message optimization) and a trigger source provider: it registers the GP/DATA_READY output via devm_spi_offload_trigger_register() so the offload framework can match the '#trigger-source-cells' phandle and automatically fire the SPI Engine DMA transfer at end-of-conversion. Manual Mode: the SPI Engine is triggered by a periodic trigger at the configured sampling frequency. The pre-built SPI message uses the pipelined CNV-on-CS protocol: N+1 4-byte transfers are issued for N active channels (the first result is discarded as garbage from the pipeline flush) and the remaining N results are captured by DMA. All offload transfers use 32-bit frames (bits_per_word=3D32, len=3D4) for DMA word alignment. This patch promotes the channel scan_type from storagebits=3D16 (triggered-buffer path) to storagebits=3D32 to match the DMA word size; the triggered-buffer paths are updated to the same layout for consistency. CNV Burst Mode channel data arrives in the lower 16 bits of the 32-bit word (shift=3D0); Manual Mode data arrives in the upper 16 bits (shift=3D16), matching the 4-byte SPI transfer layout [data_hi, data_lo, 0, 0]. A separate ad4691_manual_channels[] array encodes the shift=3D16 scan type for manual mode. Kconfig gains a dependency on IIO_BUFFER_DMAENGINE. Signed-off-by: Radu Sabau --- drivers/iio/adc/Kconfig | 1 + drivers/iio/adc/ad4691.c | 470 +++++++++++++++++++++++++++++++++++++++++++= ---- 2 files changed, 435 insertions(+), 36 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index d498f16c0816..93f090e9a562 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -144,6 +144,7 @@ config AD4691 depends on SPI select IIO_BUFFER select IIO_TRIGGERED_BUFFER + select IIO_BUFFER_DMAENGINE select REGMAP help Say yes here to build support for Analog Devices AD4691 Family MuxSAR diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c index db776de32846..5e0fe993c17d 100644 --- a/drivers/iio/adc/ad4691.c +++ b/drivers/iio/adc/ad4691.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -19,10 +20,14 @@ #include #include #include +#include +#include #include #include =20 #include +#include +#include #include #include #include @@ -37,6 +42,7 @@ #define AD4691_VREF_4P096_uV_MAX 4500000 =20 #define AD4691_CNV_DUTY_CYCLE_NS 380 +#define AD4691_CNV_HIGH_TIME_NS 430 =20 #define AD4691_SPI_CONFIG_A_REG 0x000 #define AD4691_SW_RESET (BIT(7) | BIT(0)) @@ -89,6 +95,12 @@ #define AD4691_ACC_IN(n) (0x252 + (3 * (n))) #define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n))) =20 +/* SPI offload 32-bit word field masks (transmitted MSB first) */ +#define AD4691_OFFLOAD_BITS_PER_WORD 32 +#define AD4691_MSG_ADDR_HI GENMASK(31, 24) +#define AD4691_MSG_ADDR_LO GENMASK(23, 16) +#define AD4691_MSG_DATA GENMASK(15, 8) + enum ad4691_ref_ctrl { AD4691_VREF_2P5 =3D 0, AD4691_VREF_3P0 =3D 1, @@ -99,12 +111,22 @@ enum ad4691_ref_ctrl { =20 struct ad4691_chip_info { const struct iio_chan_spec *channels; + const struct iio_chan_spec *manual_channels; const char *name; unsigned int num_channels; unsigned int max_rate; }; =20 -#define AD4691_CHANNEL(ch) \ +/* + * 16-bit ADC data is stored in 32-bit slots to match the SPI offload DMA + * word size (32 bits per transfer). The shift reflects the data position + * within the 32-bit word: + * CNV_BURST: RX =3D [dummy, dummy, data_hi, data_lo] -> shift =3D 0 + * MANUAL: RX =3D [data_hi, data_lo, dummy, dummy] -> shift =3D 16 + * The triggered-buffer paths store data in the same position for consiste= ncy. + * Do not "fix" storagebits to 16. + */ +#define AD4691_CHANNEL(ch, _shift) \ { \ .type =3D IIO_VOLTAGE, \ .indexed =3D 1, \ @@ -118,40 +140,72 @@ struct ad4691_chip_info { .scan_type =3D { \ .sign =3D 'u', \ .realbits =3D 16, \ - .storagebits =3D 16, \ - .shift =3D 0, \ + .storagebits =3D 32, \ + .shift =3D _shift, \ }, \ } =20 static const struct iio_chan_spec ad4691_channels[] =3D { - AD4691_CHANNEL(0), - AD4691_CHANNEL(1), - AD4691_CHANNEL(2), - AD4691_CHANNEL(3), - AD4691_CHANNEL(4), - AD4691_CHANNEL(5), - AD4691_CHANNEL(6), - AD4691_CHANNEL(7), - AD4691_CHANNEL(8), - AD4691_CHANNEL(9), - AD4691_CHANNEL(10), - AD4691_CHANNEL(11), - AD4691_CHANNEL(12), - AD4691_CHANNEL(13), - AD4691_CHANNEL(14), - AD4691_CHANNEL(15), + AD4691_CHANNEL(0, 0), + AD4691_CHANNEL(1, 0), + AD4691_CHANNEL(2, 0), + AD4691_CHANNEL(3, 0), + AD4691_CHANNEL(4, 0), + AD4691_CHANNEL(5, 0), + AD4691_CHANNEL(6, 0), + AD4691_CHANNEL(7, 0), + AD4691_CHANNEL(8, 0), + AD4691_CHANNEL(9, 0), + AD4691_CHANNEL(10, 0), + AD4691_CHANNEL(11, 0), + AD4691_CHANNEL(12, 0), + AD4691_CHANNEL(13, 0), + AD4691_CHANNEL(14, 0), + AD4691_CHANNEL(15, 0), + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static const struct iio_chan_spec ad4691_manual_channels[] =3D { + AD4691_CHANNEL(0, 16), + AD4691_CHANNEL(1, 16), + AD4691_CHANNEL(2, 16), + AD4691_CHANNEL(3, 16), + AD4691_CHANNEL(4, 16), + AD4691_CHANNEL(5, 16), + AD4691_CHANNEL(6, 16), + AD4691_CHANNEL(7, 16), + AD4691_CHANNEL(8, 16), + AD4691_CHANNEL(9, 16), + AD4691_CHANNEL(10, 16), + AD4691_CHANNEL(11, 16), + AD4691_CHANNEL(12, 16), + AD4691_CHANNEL(13, 16), + AD4691_CHANNEL(14, 16), + AD4691_CHANNEL(15, 16), IIO_CHAN_SOFT_TIMESTAMP(16), }; =20 static const struct iio_chan_spec ad4693_channels[] =3D { - AD4691_CHANNEL(0), - AD4691_CHANNEL(1), - AD4691_CHANNEL(2), - AD4691_CHANNEL(3), - AD4691_CHANNEL(4), - AD4691_CHANNEL(5), - AD4691_CHANNEL(6), - AD4691_CHANNEL(7), + AD4691_CHANNEL(0, 0), + AD4691_CHANNEL(1, 0), + AD4691_CHANNEL(2, 0), + AD4691_CHANNEL(3, 0), + AD4691_CHANNEL(4, 0), + AD4691_CHANNEL(5, 0), + AD4691_CHANNEL(6, 0), + AD4691_CHANNEL(7, 0), + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static const struct iio_chan_spec ad4693_manual_channels[] =3D { + AD4691_CHANNEL(0, 16), + AD4691_CHANNEL(1, 16), + AD4691_CHANNEL(2, 16), + AD4691_CHANNEL(3, 16), + AD4691_CHANNEL(4, 16), + AD4691_CHANNEL(5, 16), + AD4691_CHANNEL(6, 16), + AD4691_CHANNEL(7, 16), IIO_CHAN_SOFT_TIMESTAMP(16), }; =20 @@ -181,6 +235,7 @@ static const unsigned int ad4691_osc_freqs[] =3D { =20 static const struct ad4691_chip_info ad4691_chip_info =3D { .channels =3D ad4691_channels, + .manual_channels =3D ad4691_manual_channels, .name =3D "ad4691", .num_channels =3D ARRAY_SIZE(ad4691_channels), .max_rate =3D 500 * HZ_PER_KHZ, @@ -188,6 +243,7 @@ static const struct ad4691_chip_info ad4691_chip_info = =3D { =20 static const struct ad4691_chip_info ad4692_chip_info =3D { .channels =3D ad4691_channels, + .manual_channels =3D ad4691_manual_channels, .name =3D "ad4692", .num_channels =3D ARRAY_SIZE(ad4691_channels), .max_rate =3D 1 * HZ_PER_MHZ, @@ -195,6 +251,7 @@ static const struct ad4691_chip_info ad4692_chip_info = =3D { =20 static const struct ad4691_chip_info ad4693_chip_info =3D { .channels =3D ad4693_channels, + .manual_channels =3D ad4693_manual_channels, .name =3D "ad4693", .num_channels =3D ARRAY_SIZE(ad4693_channels), .max_rate =3D 500 * HZ_PER_KHZ, @@ -202,6 +259,7 @@ static const struct ad4691_chip_info ad4693_chip_info = =3D { =20 static const struct ad4691_chip_info ad4694_chip_info =3D { .channels =3D ad4693_channels, + .manual_channels =3D ad4693_manual_channels, .name =3D "ad4694", .num_channels =3D ARRAY_SIZE(ad4693_channels), .max_rate =3D 1 * HZ_PER_MHZ, @@ -227,9 +285,9 @@ struct ad4691_state { */ struct mutex lock; /* - * Per-buffer-enabl ree lifetimesources: - * Manual Mode - a pre-built SPI message that clocks out N+1 - * transfers in one go. + * Per-buffer-enable lifetime resources (triggered-buffer paths): + * Manual Mode - a pre-built SPI message that clocks out N+1 + * transfers in one go. * CNV Burst Mode - a pre-built SPI message that clocks out 2*N * transfers in one go. */ @@ -238,9 +296,19 @@ struct ad4691_state { struct spi_transfer *scan_xfers; __be16 *scan_tx; __be16 *scan_rx; - /* Scan buffer: one slot per channel (u16) plus timestamp */ + /* SPI offload DMA path resources */ + struct spi_offload *offload; + /* SPI offload trigger - periodic (MANUAL) or DATA_READY (CNV_BURST) */ + struct spi_offload_trigger *offload_trigger; + u64 offload_trigger_hz; + struct spi_message offload_msg; + /* Max 16 channel xfers + 1 state-reset or NOOP */ + struct spi_transfer offload_xfer[17]; + u32 offload_tx_cmd[17]; + u32 offload_tx_reset; + /* Scan buffer: one slot per channel (u32) plus timestamp */ struct { - u16 vals[16]; + u32 vals[16]; s64 ts __aligned(8); } scan __aligned(IIO_DMA_MINALIGN); }; @@ -260,6 +328,46 @@ static int ad4691_gpio_setup(struct ad4691_state *st, = unsigned int gp_num) AD4691_GP_MODE_DATA_READY << shift); } =20 +static const struct spi_offload_config ad4691_offload_config =3D { + .capability_flags =3D SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static bool ad4691_offload_trigger_match(struct spi_offload_trigger *trigg= er, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + return type =3D=3D SPI_OFFLOAD_TRIGGER_DATA_READY && + nargs =3D=3D 1 && args[0] <=3D 3; +} + +static int ad4691_offload_trigger_request(struct spi_offload_trigger *trig= ger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + struct ad4691_state *st =3D spi_offload_trigger_get_priv(trigger); + + if (nargs !=3D 1) + return -EINVAL; + + return ad4691_gpio_setup(st, (unsigned int)args[0]); +} + +static int ad4691_offload_trigger_validate(struct spi_offload_trigger *tri= gger, + struct spi_offload_trigger_config *config) +{ + if (config->type !=3D SPI_OFFLOAD_TRIGGER_DATA_READY) + return -EINVAL; + + return 0; +} + +static const struct spi_offload_trigger_ops ad4691_offload_trigger_ops =3D= { + .match =3D ad4691_offload_trigger_match, + .request =3D ad4691_offload_trigger_request, + .validate =3D ad4691_offload_trigger_validate, +}; + static void ad4691_disable_pwm(void *data) { struct pwm_state state =3D { .enabled =3D false }; @@ -817,6 +925,206 @@ static const struct iio_buffer_setup_ops ad4691_cnv_b= urst_buffer_setup_ops =3D { .postdisable =3D &ad4691_cnv_burst_buffer_postdisable, }; =20 +static int ad4691_manual_offload_buffer_postenable(struct iio_dev *indio_d= ev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + struct device *dev =3D regmap_get_device(st->regmap); + struct spi_device *spi =3D to_spi_device(dev); + struct spi_offload_trigger_config config =3D { + .type =3D SPI_OFFLOAD_TRIGGER_PERIODIC, + }; + unsigned int bit, k; + int ret; + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + return ret; + + memset(st->offload_xfer, 0, sizeof(st->offload_xfer)); + + /* + * N+1 transfers for N channels. Each CS-low period triggers + * a conversion AND returns the previous result (pipelined). + * TX: [AD4691_ADC_CHAN(n), 0x00, 0x00, 0x00] + * RX: [data_hi, data_lo, 0x00, 0x00] (shift=3D16) + * Transfer 0 RX is garbage; transfers 1..N carry real data. + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, bit) { + st->offload_tx_cmd[k] =3D + cpu_to_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, + AD4691_ADC_CHAN(bit))); + st->offload_xfer[k].tx_buf =3D &st->offload_tx_cmd[k]; + st->offload_xfer[k].len =3D sizeof(u32); + st->offload_xfer[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->offload_xfer[k].cs_change =3D 1; + st->offload_xfer[k].cs_change_delay.value =3D AD4691_CNV_HIGH_TIME_NS; + st->offload_xfer[k].cs_change_delay.unit =3D SPI_DELAY_UNIT_NSECS; + /* First transfer RX is garbage =E2=80=94 skip it. */ + if (k > 0) + st->offload_xfer[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + k++; + } + + /* Final NOOP to flush pipeline and capture last channel. */ + st->offload_tx_cmd[k] =3D + cpu_to_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, AD4691_NOOP)); + st->offload_xfer[k].tx_buf =3D &st->offload_tx_cmd[k]; + st->offload_xfer[k].len =3D sizeof(u32); + st->offload_xfer[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->offload_xfer[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + k++; + + spi_message_init_with_transfers(&st->offload_msg, st->offload_xfer, k); + st->offload_msg.offload =3D st->offload; + + ret =3D spi_optimize_message(spi, &st->offload_msg); + if (ret) + goto err_exit_conversion; + + config.periodic.frequency_hz =3D st->offload_trigger_hz; + ret =3D spi_offload_trigger_enable(st->offload, st->offload_trigger, &con= fig); + if (ret) + goto err_unoptimize; + + return 0; + +err_unoptimize: + spi_unoptimize_message(&st->offload_msg); +err_exit_conversion: + ad4691_exit_conversion_mode(st); + return ret; +} + +static int ad4691_manual_offload_buffer_predisable(struct iio_dev *indio_d= ev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + spi_unoptimize_message(&st->offload_msg); + + return ad4691_exit_conversion_mode(st); +} + +static const struct iio_buffer_setup_ops ad4691_manual_offload_buffer_setu= p_ops =3D { + .postenable =3D &ad4691_manual_offload_buffer_postenable, + .predisable =3D &ad4691_manual_offload_buffer_predisable, +}; + +static int ad4691_cnv_burst_offload_buffer_postenable(struct iio_dev *indi= o_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + struct device *dev =3D regmap_get_device(st->regmap); + struct spi_device *spi =3D to_spi_device(dev); + struct spi_offload_trigger_config config =3D { + .type =3D SPI_OFFLOAD_TRIGGER_DATA_READY, + }; + unsigned int n_active =3D hweight_long(*indio_dev->active_scan_mask); + unsigned int bit, k; + int ret; + + ret =3D regmap_write(st->regmap, AD4691_ACC_MASK_REG, + (u16)~(*indio_dev->active_scan_mask)); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, + *indio_dev->active_scan_mask); + if (ret) + return ret; + + iio_for_each_active_channel(indio_dev, bit) { + ret =3D regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit), + AD4691_ACC_COUNT_VAL); + if (ret) + return ret; + } + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + return ret; + + memset(st->offload_xfer, 0, sizeof(st->offload_xfer)); + + /* + * N transfers to read N AVG_IN registers plus one state-reset + * transfer (no RX) to re-arm DATA_READY. + * TX: [reg_hi | 0x80, reg_lo, 0x00, 0x00] + * RX: [0x00, 0x00, data_hi, data_lo] (shift=3D0) + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, bit) { + unsigned int reg =3D AD4691_AVG_IN(bit); + + st->offload_tx_cmd[k] =3D + cpu_to_be32(((reg >> 8 | 0x80) << 24) | + ((reg & 0xFF) << 16)); + st->offload_xfer[k].tx_buf =3D &st->offload_tx_cmd[k]; + st->offload_xfer[k].len =3D sizeof(u32); + st->offload_xfer[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->offload_xfer[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + if (k < n_active - 1) + st->offload_xfer[k].cs_change =3D 1; + k++; + } + + /* State reset to re-arm DATA_READY for the next scan. */ + st->offload_tx_reset =3D + cpu_to_be32(((AD4691_STATE_RESET_REG >> 8) << 24) | + ((AD4691_STATE_RESET_REG & 0xFF) << 16) | + (AD4691_STATE_RESET_ALL << 8)); + st->offload_xfer[k].tx_buf =3D &st->offload_tx_reset; + st->offload_xfer[k].len =3D sizeof(u32); + st->offload_xfer[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + k++; + + spi_message_init_with_transfers(&st->offload_msg, st->offload_xfer, k); + st->offload_msg.offload =3D st->offload; + + ret =3D spi_optimize_message(spi, &st->offload_msg); + if (ret) + goto err_exit_conversion; + + ret =3D ad4691_sampling_enable(st, true); + if (ret) + goto err_unoptimize; + + ret =3D spi_offload_trigger_enable(st->offload, st->offload_trigger, &con= fig); + if (ret) + goto err_sampling_disable; + + return 0; + +err_sampling_disable: + ad4691_sampling_enable(st, false); +err_unoptimize: + spi_unoptimize_message(&st->offload_msg); +err_exit_conversion: + ad4691_exit_conversion_mode(st); + return ret; +} + +static int ad4691_cnv_burst_offload_buffer_predisable(struct iio_dev *indi= o_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + int ret; + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + + ret =3D ad4691_sampling_enable(st, false); + if (ret) + return ret; + + spi_unoptimize_message(&st->offload_msg); + + return ad4691_exit_conversion_mode(st); +} + +static const struct iio_buffer_setup_ops ad4691_cnv_burst_offload_buffer_s= etup_ops =3D { + .postenable =3D &ad4691_cnv_burst_offload_buffer_postenable, + .predisable =3D &ad4691_cnv_burst_offload_buffer_predisable, +}; + static ssize_t sampling_frequency_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -824,6 +1132,9 @@ static ssize_t sampling_frequency_show(struct device *= dev, struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); struct ad4691_state *st =3D iio_priv(indio_dev); =20 + if (st->manual_mode && st->offload) + return sysfs_emit(buf, "%llu\n", st->offload_trigger_hz); + if (st->manual_mode) return -ENODEV; =20 @@ -838,7 +1149,7 @@ static ssize_t sampling_frequency_store(struct device = *dev, struct ad4691_state *st =3D iio_priv(indio_dev); int freq, ret; =20 - if (st->manual_mode) + if (st->manual_mode && !st->offload) return -ENODEV; =20 ret =3D kstrtoint(buf, 10, &freq); @@ -847,6 +1158,20 @@ static ssize_t sampling_frequency_store(struct device= *dev, =20 guard(mutex)(&st->lock); =20 + if (st->manual_mode) { + struct spi_offload_trigger_config config =3D { + .type =3D SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic =3D { .frequency_hz =3D freq }, + }; + + ret =3D spi_offload_trigger_validate(st->offload_trigger, &config); + if (ret) + return ret; + + st->offload_trigger_hz =3D config.periodic.frequency_hz; + return len; + } + ret =3D ad4691_set_pwm_freq(st, freq); if (ret) return ret; @@ -900,7 +1225,7 @@ static irqreturn_t ad4691_trigger_handler(int irq, voi= d *p) =20 if (st->manual_mode) { iio_for_each_active_channel(indio_dev, i) { - st->scan.vals[i] =3D be16_to_cpu(st->scan_rx[k + 1]); + st->scan.vals[i] =3D (u32)be16_to_cpu(st->scan_rx[k + 1]) << 16; k++; } } else { @@ -1088,6 +1413,15 @@ static int ad4691_config(struct ad4691_state *st, u3= 2 max_speed_hz) if (st->manual_mode) return 0; =20 + /* + * In the offload CNV Burst path the GP pin is supplied by the trigger + * consumer via #trigger-source-cells; gpio_setup is called from + * ad4691_offload_trigger_request() instead. For the non-offload path + * derive the pin from the first interrupt-names entry (e.g. "gp0"). + */ + if (device_property_present(dev, "#trigger-source-cells")) + return 0; + ret =3D device_property_read_string_array(dev, "interrupt-names", &irq_name, 1); if (ret < 0) @@ -1158,6 +1492,56 @@ static int ad4691_setup_triggered_buffer(struct iio_= dev *indio_dev, &ad4691_manual_buffer_setup_ops); } =20 +static int ad4691_setup_offload(struct iio_dev *indio_dev, + struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + struct dma_chan *rx_dma; + int ret; + + if (st->manual_mode) { + st->offload_trigger =3D + 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 periodic offload trigger\n"); + + st->offload_trigger_hz =3D st->info->max_rate; + } else { + struct spi_offload_trigger_info trigger_info =3D { + .fwnode =3D dev_fwnode(dev), + .ops =3D &ad4691_offload_trigger_ops, + .priv =3D st, + }; + + ret =3D devm_spi_offload_trigger_register(dev, &trigger_info); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register offload trigger\n"); + + st->offload_trigger =3D + devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_DATA_READY); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "Failed to get DATA_READY offload trigger\n"); + } + + rx_dma =3D 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 channel\n"); + + if (st->manual_mode) + indio_dev->setup_ops =3D &ad4691_manual_offload_buffer_setup_ops; + else + indio_dev->setup_ops =3D &ad4691_cnv_burst_offload_buffer_setup_ops; + + return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma, + IIO_BUFFER_DIRECTION_IN); +} + static int ad4691_probe(struct spi_device *spi) { struct device *dev =3D &spi->dev; @@ -1193,14 +1577,27 @@ static int ad4691_probe(struct spi_device *spi) if (ret) return ret; =20 + st->offload =3D devm_spi_offload_get(dev, spi, &ad4691_offload_config); + ret =3D PTR_ERR_OR_ZERO(st->offload); + if (ret =3D=3D -ENODEV) + st->offload =3D NULL; + else if (ret) + return dev_err_probe(dev, ret, "Failed to get SPI offload\n"); + indio_dev->name =3D st->info->name; indio_dev->info =3D &ad4691_info; indio_dev->modes =3D INDIO_DIRECT_MODE; =20 - indio_dev->channels =3D st->info->channels; + if (st->manual_mode) + indio_dev->channels =3D st->info->manual_channels; + else + indio_dev->channels =3D st->info->channels; indio_dev->num_channels =3D st->info->num_channels; =20 - ret =3D ad4691_setup_triggered_buffer(indio_dev, st); + if (st->offload) + ret =3D ad4691_setup_offload(indio_dev, st); + else + ret =3D ad4691_setup_triggered_buffer(indio_dev, st); if (ret) return ret; =20 @@ -1238,3 +1635,4 @@ module_spi_driver(ad4691_driver); MODULE_AUTHOR("Radu Sabau "); MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMA_BUFFER"); --=20 2.43.0