From nobody Thu Apr 9 23:26:09 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 66D073E0C73; Thu, 9 Apr 2026 15:28:29 +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=1775748509; cv=none; b=Ht47Vn4ZXtIjKRf7RhOlZELhGalbuoDoIA6hKemMIWLDw+9VFn+HAaIXsDvMPJ6T5HzS/X22S9Ph9NGt7q2xGEMGkrapda8Zk8L4hyygXtA3rHKfkZQG0wFB9DDCwgaN/zv4fottF1aRaCOI2YHwfcUNS4jKABxs9ElUrWzGn4k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775748509; c=relaxed/simple; bh=a3sivRVcRVgdEWalCeAfQYpuQpE1QVustkNCWadtQiw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=thGChUBVXluviBKiUUzjYCBhLFRSwsSDjW0iutlGlu+G17dKdkx4s9neRh7kTloVQGaxBtKYxlEmwetU8bpMvvyXC3QZKeNDWkQL/0D6L0C0BUXKchqBZFxSLzm18t2Sdj8hrqx+xOtIf0V0TzZ0IhL+uoaR6E5glI5Scx5n2qA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=AsZT1IYd; 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="AsZT1IYd" Received: by smtp.kernel.org (Postfix) with ESMTPS id 39113C2BCC6; Thu, 9 Apr 2026 15:28:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1775748509; bh=a3sivRVcRVgdEWalCeAfQYpuQpE1QVustkNCWadtQiw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=AsZT1IYdqHl0fWG9Wtm24tBJLAU1GA3U6VpMhapuuOR1zvX3WYIHx9yYF4R4lC28t Idp4Ue3/HC/F89oADBJcGZg/g4kjhFGwRTykza5IWJOlvdvURC0JydLejrw12BEnFB CG9N16aI0xWjOqCHye49YNzOtewiSUhhjNp90EoPdREQxEid5nMBuv6MX7BY1K5FhG cwhVx/mN3M2fVzCiS7PVuCylzNPbmnCh7ID2S3Fv33eq8uacy6AclcMnn0KEc5QKd6 eiYLICQuHQBt+nXLWz+R3/rfUUndIRVk00Om5xhokia0Io01zW2Ml+T3FxDRhoDREZ E8sPwYzjmj1Fg== 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 30B2DF31E29; Thu, 9 Apr 2026 15:28:29 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Thu, 09 Apr 2026 18:28:25 +0300 Subject: [PATCH v7 4/6] 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: <20260409-ad4692-multichannel-sar-adc-driver-v7-4-be375d4df2c5@analog.com> References: <20260409-ad4692-multichannel-sar-adc-driver-v7-0-be375d4df2c5@analog.com> In-Reply-To: <20260409-ad4692-multichannel-sar-adc-driver-v7-0-be375d4df2c5@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 , Jonathan Corbet , Shuah Khan 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, linux-doc@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1775748505; l=20066; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=Ousdf/UUlNDEkFs02oWb2zGh3S08PDZ2UYODcd0dY2M=; b=YazVMq8egOV5J2t0sFtya0WAQ6bOi+1C+akpV85XA7eNZwq+4bdg+Vb718mslBmS6x8nIgDXm DXOgDb4vDIIB2O36DwtR8TILGD0MaKqH4KkZEXpiig5dApKtmDVM/dR 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 16-bit 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 16-bit frames (bits_per_word=3D16, len=3D2). The channel scan_type (storagebits=3D16, shift=3D0, IIO_BE) is shared between the software triggered-buffer and offload paths; no separate scan_type or channel array is needed for the offload case. The ad4691_manual_channels[] array introduced in the triggered-buffer commit is reused here: it hides the IIO_CHAN_INFO_OVERSAMPLING_RATIO attribute, which is not applicable in Manual Mode. Kconfig gains a dependency on IIO_BUFFER_DMAENGINE. Signed-off-by: Radu Sabau --- drivers/iio/adc/Kconfig | 2 + drivers/iio/adc/ad4691.c | 398 +++++++++++++++++++++++++++++++++++++++++++= +++- 2 files changed, 395 insertions(+), 5 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index d498f16c0816..fdc6565933c5 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -143,8 +143,10 @@ config AD4691 tristate "Analog Devices AD4691 Family ADC Driver" depends on SPI select IIO_BUFFER + select IIO_BUFFER_DMAENGINE select IIO_TRIGGERED_BUFFER select REGMAP + select SPI_OFFLOAD help Say yes here to build support for Analog Devices AD4691 Family MuxSAR SPI analog to digital converters (ADC). diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c index 3e5caa0972eb..839ea7f44c78 100644 --- a/drivers/iio/adc/ad4691.c +++ b/drivers/iio/adc/ad4691.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include =20 @@ -43,6 +45,11 @@ =20 #define AD4691_CNV_DUTY_CYCLE_NS 380 #define AD4691_CNV_HIGH_TIME_NS 430 +/* + * Conservative default for the manual offload periodic trigger. Low enough + * to work safely out of the box across all OSR and channel count combinat= ions. + */ +#define AD4691_OFFLOAD_INITIAL_TRIGGER_HZ (100 * HZ_PER_KHZ) =20 #define AD4691_SPI_CONFIG_A_REG 0x000 #define AD4691_SW_RESET (BIT(7) | BIT(0)) @@ -95,6 +102,8 @@ #define AD4691_ACC_IN(n) (0x252 + (3 * (n))) #define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n))) =20 +#define AD4691_OFFLOAD_BITS_PER_WORD 16 + static const char * const ad4691_supplies[] =3D { "avdd", "vio" }; =20 enum ad4691_ref_ctrl { @@ -114,6 +123,7 @@ struct ad4691_chip_info { const char *name; unsigned int max_rate; const struct ad4691_channel_info *sw_info; + const struct ad4691_channel_info *offload_info; }; =20 #define AD4691_CHANNEL(ch) \ @@ -177,6 +187,18 @@ static const struct ad4691_channel_info ad4693_sw_info= =3D { .num_channels =3D ARRAY_SIZE(ad4693_channels), }; =20 +static const struct ad4691_channel_info ad4691_offload_info =3D { + .channels =3D ad4691_channels, + /* No soft timestamp; num_channels caps access to 16. */ + .num_channels =3D 16, +}; + +static const struct ad4691_channel_info ad4693_offload_info =3D { + .channels =3D ad4693_channels, + /* No soft timestamp; num_channels caps access to 8. */ + .num_channels =3D 8, +}; + /* * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] val= ue. * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support @@ -207,24 +229,36 @@ static const struct ad4691_chip_info ad4691_chip_info= =3D { .name =3D "ad4691", .max_rate =3D 500 * HZ_PER_KHZ, .sw_info =3D &ad4691_sw_info, + .offload_info =3D &ad4691_offload_info, }; =20 static const struct ad4691_chip_info ad4692_chip_info =3D { .name =3D "ad4692", .max_rate =3D 1 * HZ_PER_MHZ, .sw_info =3D &ad4691_sw_info, + .offload_info =3D &ad4691_offload_info, }; =20 static const struct ad4691_chip_info ad4693_chip_info =3D { .name =3D "ad4693", .max_rate =3D 500 * HZ_PER_KHZ, .sw_info =3D &ad4693_sw_info, + .offload_info =3D &ad4693_offload_info, }; =20 static const struct ad4691_chip_info ad4694_chip_info =3D { .name =3D "ad4694", .max_rate =3D 1 * HZ_PER_MHZ, .sw_info =3D &ad4693_sw_info, + .offload_info =3D &ad4693_offload_info, +}; + +struct ad4691_offload_state { + struct spi_offload *spi; + struct spi_offload_trigger *trigger; + u64 trigger_hz; + u8 tx_cmd[17][2]; + u8 tx_reset[4]; }; =20 struct ad4691_state { @@ -253,7 +287,10 @@ struct ad4691_state { * transfers in one go. */ struct spi_message scan_msg; - /* max 16 + 1 NOOP (manual) or 2*16 + 2 (CNV burst). */ + /* + * Non-offload: max 16 + 1 NOOP (manual) or 2*16 + 2 (CNV burst). + * Offload reuses this array =E2=80=94 both paths are mutually exclusive. + */ struct spi_transfer scan_xfers[34]; /* * CNV burst: 16 AVG_IN addresses + state-reset address + state-reset @@ -265,6 +302,8 @@ struct ad4691_state { __be16 vals[16]; aligned_s64 ts; } scan; + /* NULL when no SPI offload hardware is present */ + struct ad4691_offload_state *offload; }; =20 /* @@ -284,6 +323,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, 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 int ad4691_reg_read(void *context, unsigned int reg, unsigned int *= val) { struct spi_device *spi =3D context; @@ -807,6 +886,214 @@ 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 ad4691_offload_state *offload =3D st->offload; + 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->scan_xfers, 0, sizeof(st->scan_xfers)); + + /* + * 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] + * RX: [data_hi, data_lo] (storagebits=3D16, shift=3D0) + * Transfer 0 RX is garbage; transfers 1..N carry real data. + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, bit) { + offload->tx_cmd[k][0] =3D AD4691_ADC_CHAN(bit); + st->scan_xfers[k].tx_buf =3D offload->tx_cmd[k]; + st->scan_xfers[k].len =3D sizeof(offload->tx_cmd[k]); + st->scan_xfers[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->scan_xfers[k].cs_change =3D 1; + st->scan_xfers[k].cs_change_delay.value =3D AD4691_CNV_HIGH_TIME_NS; + st->scan_xfers[k].cs_change_delay.unit =3D SPI_DELAY_UNIT_NSECS; + /* First transfer RX is garbage =E2=80=94 skip it. */ + if (k > 0) + st->scan_xfers[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + k++; + } + + /* Final NOOP to flush pipeline and capture last channel. */ + offload->tx_cmd[k][0] =3D AD4691_NOOP; + st->scan_xfers[k].tx_buf =3D offload->tx_cmd[k]; + st->scan_xfers[k].len =3D sizeof(offload->tx_cmd[k]); + st->scan_xfers[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->scan_xfers[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + k++; + + spi_message_init_with_transfers(&st->scan_msg, st->scan_xfers, k); + st->scan_msg.offload =3D offload->spi; + + ret =3D spi_optimize_message(spi, &st->scan_msg); + if (ret) + goto err_exit_conversion; + + config.periodic.frequency_hz =3D offload->trigger_hz; + ret =3D spi_offload_trigger_enable(offload->spi, offload->trigger, &confi= g); + if (ret) + goto err_unoptimize; + + return 0; + +err_unoptimize: + spi_unoptimize_message(&st->scan_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); + struct ad4691_offload_state *offload =3D st->offload; + + spi_offload_trigger_disable(offload->spi, offload->trigger); + spi_unoptimize_message(&st->scan_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 ad4691_offload_state *offload =3D st->offload; + 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; + unsigned int bit, k; + int ret; + + n_active =3D bitmap_weight(indio_dev->active_scan_mask, iio_get_masklengt= h(indio_dev)); + + ret =3D regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, + bitmap_read(indio_dev->active_scan_mask, 0, + iio_get_masklength(indio_dev))); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD4691_ACC_MASK_REG, + ~bitmap_read(indio_dev->active_scan_mask, 0, + iio_get_masklength(indio_dev)) & GENMASK(15, 0)); + if (ret) + return ret; + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + return ret; + + memset(st->scan_xfers, 0, sizeof(st->scan_xfers)); + + /* + * Each AVG_IN register read uses two 16-bit transfers: + * TX: [reg_hi | 0x80, reg_lo] (address, CS stays asserted) + * RX: [data_hi, data_lo] (data, storagebits=3D16, shift=3D0) + * The state reset is also split into two 16-bit transfers + * (address then value) to keep bits_per_word uniform throughout. + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, bit) { + put_unaligned_be16(0x8000 | AD4691_AVG_IN(bit), offload->tx_cmd[k]); + + /* TX: address phase, CS stays asserted into data phase */ + st->scan_xfers[2 * k].tx_buf =3D offload->tx_cmd[k]; + st->scan_xfers[2 * k].len =3D sizeof(offload->tx_cmd[k]); + st->scan_xfers[2 * k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + + /* RX: data phase, CS toggles after to delimit the next register op */ + st->scan_xfers[2 * k + 1].len =3D sizeof(offload->tx_cmd[k]); + st->scan_xfers[2 * k + 1].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->scan_xfers[2 * k + 1].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + st->scan_xfers[2 * k + 1].cs_change =3D 1; + k++; + } + + /* State reset to re-arm DATA_READY for the next scan. */ + put_unaligned_be16(AD4691_STATE_RESET_REG, offload->tx_reset); + offload->tx_reset[2] =3D AD4691_STATE_RESET_ALL; + + st->scan_xfers[2 * k].tx_buf =3D offload->tx_reset; + st->scan_xfers[2 * k].len =3D sizeof(offload->tx_cmd[k]); + st->scan_xfers[2 * k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + + st->scan_xfers[2 * k + 1].tx_buf =3D &offload->tx_reset[2]; + st->scan_xfers[2 * k + 1].len =3D sizeof(offload->tx_cmd[k]); + st->scan_xfers[2 * k + 1].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->scan_xfers[2 * k + 1].cs_change =3D 1; + + spi_message_init_with_transfers(&st->scan_msg, st->scan_xfers, 2 * k + 2); + st->scan_msg.offload =3D offload->spi; + + ret =3D spi_optimize_message(spi, &st->scan_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(offload->spi, offload->trigger, &confi= g); + if (ret) + goto err_sampling_disable; + + return 0; + +err_sampling_disable: + ad4691_sampling_enable(st, false); +err_unoptimize: + spi_unoptimize_message(&st->scan_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); + struct ad4691_offload_state *offload =3D st->offload; + int ret; + + spi_offload_trigger_disable(offload->spi, offload->trigger); + + ret =3D ad4691_sampling_enable(st, false); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, + AD4691_SEQ_ALL_CHANNELS_OFF); + if (ret) + return ret; + + spi_unoptimize_message(&st->scan_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) @@ -814,7 +1101,10 @@ 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 - return sysfs_emit(buf, "%u\n", NSEC_PER_SEC / st->cnv_period_ns); + if (st->manual_mode && st->offload) + return sysfs_emit(buf, "%llu\n", st->offload->trigger_hz); + + return sysfs_emit(buf, "%lu\n", NSEC_PER_SEC / st->cnv_period_ns); } =20 static ssize_t sampling_frequency_store(struct device *dev, @@ -833,6 +1123,23 @@ static ssize_t sampling_frequency_store(struct device= *dev, if (ret) return ret; =20 + if (st->manual_mode && st->offload) { + 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) { + iio_device_release_direct(indio_dev); + return ret; + } + + st->offload->trigger_hz =3D config.periodic.frequency_hz; + iio_device_release_direct(indio_dev); + return len; + } + ret =3D ad4691_set_pwm_freq(st, freq); iio_device_release_direct(indio_dev); if (ret) @@ -1132,9 +1439,75 @@ static int ad4691_setup_triggered_buffer(struct iio_= dev *indio_dev, ad4691_buffer_attrs); } =20 +static int ad4691_setup_offload(struct iio_dev *indio_dev, + struct ad4691_state *st, + struct spi_offload *spi_offload) +{ + struct device *dev =3D regmap_get_device(st->regmap); + struct ad4691_offload_state *offload; + struct dma_chan *rx_dma; + int ret; + + offload =3D devm_kzalloc(dev, sizeof(*offload), GFP_KERNEL); + if (!offload) + return -ENOMEM; + + offload->spi =3D spi_offload; + st->offload =3D offload; + + if (st->manual_mode) { + offload->trigger =3D + devm_spi_offload_trigger_get(dev, offload->spi, + SPI_OFFLOAD_TRIGGER_PERIODIC); + if (IS_ERR(offload->trigger)) + return dev_err_probe(dev, PTR_ERR(offload->trigger), + "Failed to get periodic offload trigger\n"); + + offload->trigger_hz =3D AD4691_OFFLOAD_INITIAL_TRIGGER_HZ; + } 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"); + + offload->trigger =3D + devm_spi_offload_trigger_get(dev, offload->spi, + SPI_OFFLOAD_TRIGGER_DATA_READY); + if (IS_ERR(offload->trigger)) + return dev_err_probe(dev, PTR_ERR(offload->trigger), + "Failed to get DATA_READY offload trigger\n"); + } + + rx_dma =3D devm_spi_offload_rx_stream_request_dma_chan(dev, offload->spi); + 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; + + ret =3D devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dm= a, + IIO_BUFFER_DIRECTION_IN); + if (ret) + return ret; + + indio_dev->buffer->attrs =3D ad4691_buffer_attrs; + + return 0; +} + static int ad4691_probe(struct spi_device *spi) { struct device *dev =3D &spi->dev; + struct spi_offload *spi_offload; struct iio_dev *indio_dev; struct ad4691_state *st; int ret; @@ -1168,13 +1541,26 @@ static int ad4691_probe(struct spi_device *spi) if (ret) return ret; =20 + spi_offload =3D devm_spi_offload_get(dev, spi, &ad4691_offload_config); + ret =3D PTR_ERR_OR_ZERO(spi_offload); + if (ret =3D=3D -ENODEV) + spi_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->sw_info->channels; - indio_dev->num_channels =3D st->info->sw_info->num_channels; - ret =3D ad4691_setup_triggered_buffer(indio_dev, st); + if (spi_offload) { + indio_dev->channels =3D st->info->offload_info->channels; + indio_dev->num_channels =3D st->info->offload_info->num_channels; + ret =3D ad4691_setup_offload(indio_dev, st, spi_offload); + } else { + indio_dev->channels =3D st->info->sw_info->channels; + indio_dev->num_channels =3D st->info->sw_info->num_channels; + ret =3D ad4691_setup_triggered_buffer(indio_dev, st); + } if (ret) return ret; =20 @@ -1212,3 +1598,5 @@ 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"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); --=20 2.43.0