From nobody Thu Apr 9 23:26:08 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 33A473DFC74; 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=fihNkGE5ub1Pks2zU5agV1FH2+ZnILfonFY6ZuYoR8vtue++M/E1H5ZXZ/GWfAFlfnBkt4Zlzyng3AKBn/IHlL5ROeR58krTlpbwYYEp+wA3SvHDbbt44e/246QZO2R82wgDr+ezbDjTwRuGbIIecxRVDseMxM65Yy19xrBMn/s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775748509; c=relaxed/simple; bh=QfH9u6O8rzKFyCCXKwsytcBG/RDh9kxkD/2YsQb2msI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=T2epa1zhrSoalleK5klhzCAaBga7lvZZs57KUOX8rWTiNG0DXYnkkndDRJIzqcIxtw0Y5WeWjo3ZBdHIf9xL6HZfToe2231QLXZK4r5lNFnp2vn3jzcSnWM6bLcL88MDI7qgzYJPsiBpWvC/3YL+yO0G1iUpVfbWWsE4U6lfwnQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=fO4+C9cz; 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="fO4+C9cz" Received: by smtp.kernel.org (Postfix) with ESMTPS id 0A467C2BCB2; 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=QfH9u6O8rzKFyCCXKwsytcBG/RDh9kxkD/2YsQb2msI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=fO4+C9czOvMK2JC1Nhv8TLHU6A7re6NAGaATjDr28qjUDAdKufoxWp01EdIDhQ9AT jGiTWTet2fuxeN7/bBpXM3SpGLz8EY8tx0lPB7hmo62htdZ9f0X12RYifMJ6WTtC14 letuxKom2NnEHR8aYM4r/HupJQvP/4+sdivetseCv9X1fblTmCU+0UtqhgluVJmuR+ T68IIbjoCGQgXqaJuE14isbvWulr9A3nybYRD3hoymm0q6X2x14MMw4fQXBteuOKTh gnJWzdGbpUUvRSWIW9h2K1PXyG4zAXa/9FjeR8eBUxzqmiDbAiWr1zUzp0BczI8iHe l3KWwGxm8OqMQ== 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 F3C48F31E2D; Thu, 9 Apr 2026 15:28:28 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Thu, 09 Apr 2026 18:28:24 +0300 Subject: [PATCH v7 3/6] iio: adc: ad4691: add triggered buffer 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-3-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=24317; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=F0QB2cO663nAuEzGsci2CDTXdKrVe4mcBWEFDCaotLE=; b=hjT+EWQDRvx/lIHzm99pn53d3cbYXUXLxgPcrpA7t/36ZuWLZzEctBvA0RcyOu729EbnF4iDT HzymbZUq+JBDh84w2eqpBybvTO78Z3XsnoJwLMYO7nSSU/5D9exjSVW 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 buffered capture support using the IIO triggered buffer framework. CNV Burst Mode: the GP pin identified by interrupt-names in the device tree is configured as DATA_READY output. The IRQ handler stops conversions and fires the IIO trigger; the trigger handler executes a pre-built SPI message that reads all active channels from the AVG_IN accumulator registers and then resets accumulator state and restarts conversions for the next cycle. Manual Mode: CNV is tied to SPI CS so each transfer simultaneously reads the previous result and starts the next conversion (pipelined N+1 scheme). At preenable time a pre-built, optimised SPI message of N+1 transfers is constructed (N channel reads plus one NOOP to drain the pipeline). The trigger handler executes the message in a single spi_sync() call and collects the results. An external trigger (e.g. iio-trig-hrtimer) is required to drive the trigger at the desired sample rate. Both modes share the same trigger handler and push a complete scan =E2=80= =94 one u16 slot per channel at its scan_index position, followed by a timestamp =E2=80=94 to the IIO buffer via iio_push_to_buffers_with_ts(). The CNV Burst Mode sampling frequency (PWM period) is exposed as a buffer-level attribute via IIO_DEVICE_ATTR. Signed-off-by: Radu Sabau --- drivers/iio/adc/Kconfig | 2 + drivers/iio/adc/ad4691.c | 553 +++++++++++++++++++++++++++++++++++++++++++= ++-- 2 files changed, 540 insertions(+), 15 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 3685a03aa8dc..d498f16c0816 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -142,6 +142,8 @@ config AD4170_4 config AD4691 tristate "Analog Devices AD4691 Family ADC Driver" depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER 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 43bd408c3d11..3e5caa0972eb 100644 --- a/drivers/iio/adc/ad4691.c +++ b/drivers/iio/adc/ad4691.c @@ -5,15 +5,19 @@ */ #include #include -#include +#include #include #include #include #include +#include #include +#include #include #include #include +#include +#include #include #include #include @@ -21,7 +25,14 @@ #include #include =20 +#include +#include +#include #include +#include +#include +#include +#include =20 #define AD4691_VREF_uV_MIN 2400000 #define AD4691_VREF_uV_MAX 5250000 @@ -30,6 +41,9 @@ #define AD4691_VREF_3P3_uV_MAX 3750000 #define AD4691_VREF_4P096_uV_MAX 4500000 =20 +#define AD4691_CNV_DUTY_CYCLE_NS 380 +#define AD4691_CNV_HIGH_TIME_NS 430 + #define AD4691_SPI_CONFIG_A_REG 0x000 #define AD4691_SW_RESET (BIT(7) | BIT(0)) =20 @@ -37,6 +51,7 @@ #define AD4691_CLAMP_STATUS1_REG 0x01A #define AD4691_CLAMP_STATUS2_REG 0x01B #define AD4691_DEVICE_SETUP 0x020 +#define AD4691_MANUAL_MODE BIT(2) #define AD4691_LDO_EN BIT(4) #define AD4691_REF_CTRL 0x021 #define AD4691_REF_CTRL_MASK GENMASK(4, 2) @@ -44,13 +59,18 @@ #define AD4691_OSC_FREQ_REG 0x023 #define AD4691_OSC_FREQ_MASK GENMASK(3, 0) #define AD4691_STD_SEQ_CONFIG 0x025 +#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00 #define AD4691_SPARE_CONTROL 0x02A =20 +#define AD4691_NOOP 0x00 +#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3) + #define AD4691_OSC_EN_REG 0x180 #define AD4691_STATE_RESET_REG 0x181 #define AD4691_STATE_RESET_ALL 0x01 #define AD4691_ADC_SETUP 0x182 #define AD4691_ADC_MODE_MASK GENMASK(1, 0) +#define AD4691_CNV_BURST_MODE 0x01 #define AD4691_AUTONOMOUS_MODE 0x02 /* * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a @@ -60,6 +80,8 @@ #define AD4691_ACC_DEPTH_IN(n) (0x186 + (n)) #define AD4691_GPIO_MODE1_REG 0x196 #define AD4691_GPIO_MODE2_REG 0x197 +#define AD4691_GP_MODE_MASK GENMASK(3, 0) +#define AD4691_GP_MODE_DATA_READY 0x06 #define AD4691_GPIO_READ 0x1A0 #define AD4691_ACC_STATUS_FULL1_REG 0x1B0 #define AD4691_ACC_STATUS_FULL2_REG 0x1B1 @@ -83,19 +105,23 @@ enum ad4691_ref_ctrl { AD4691_VREF_5P0 =3D 4, }; =20 -struct ad4691_chip_info { +struct ad4691_channel_info { const struct iio_chan_spec *channels; - const char *name; unsigned int num_channels; +}; + +struct ad4691_chip_info { + const char *name; unsigned int max_rate; + const struct ad4691_channel_info *sw_info; }; =20 #define AD4691_CHANNEL(ch) \ { \ .type =3D IIO_VOLTAGE, \ .indexed =3D 1, \ - .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) \ - | BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ .info_mask_separate_available =3D \ BIT(IIO_CHAN_INFO_SAMP_FREQ), \ .info_mask_shared_by_all =3D BIT(IIO_CHAN_INFO_SCALE), \ @@ -105,6 +131,7 @@ struct ad4691_chip_info { .sign =3D 'u', \ .realbits =3D 16, \ .storagebits =3D 16, \ + .endianness =3D IIO_BE, \ }, \ } =20 @@ -125,6 +152,7 @@ static const struct iio_chan_spec ad4691_channels[] =3D= { AD4691_CHANNEL(13), AD4691_CHANNEL(14), AD4691_CHANNEL(15), + IIO_CHAN_SOFT_TIMESTAMP(16), }; =20 static const struct iio_chan_spec ad4693_channels[] =3D { @@ -136,6 +164,17 @@ static const struct iio_chan_spec ad4693_channels[] = =3D { AD4691_CHANNEL(5), AD4691_CHANNEL(6), AD4691_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct ad4691_channel_info ad4691_sw_info =3D { + .channels =3D ad4691_channels, + .num_channels =3D ARRAY_SIZE(ad4691_channels), +}; + +static const struct ad4691_channel_info ad4693_sw_info =3D { + .channels =3D ad4693_channels, + .num_channels =3D ARRAY_SIZE(ad4693_channels), }; =20 /* @@ -162,38 +201,43 @@ static const int ad4691_osc_freqs_Hz[] =3D { [0xF] =3D 1250, }; =20 +static const char * const ad4691_gp_names[] =3D { "gp0", "gp1", "gp2", "gp= 3" }; + static const struct ad4691_chip_info ad4691_chip_info =3D { - .channels =3D ad4691_channels, .name =3D "ad4691", - .num_channels =3D ARRAY_SIZE(ad4691_channels), .max_rate =3D 500 * HZ_PER_KHZ, + .sw_info =3D &ad4691_sw_info, }; =20 static const struct ad4691_chip_info ad4692_chip_info =3D { - .channels =3D ad4691_channels, .name =3D "ad4692", - .num_channels =3D ARRAY_SIZE(ad4691_channels), .max_rate =3D 1 * HZ_PER_MHZ, + .sw_info =3D &ad4691_sw_info, }; =20 static const struct ad4691_chip_info ad4693_chip_info =3D { - .channels =3D ad4693_channels, .name =3D "ad4693", - .num_channels =3D ARRAY_SIZE(ad4693_channels), .max_rate =3D 500 * HZ_PER_KHZ, + .sw_info =3D &ad4693_sw_info, }; =20 static const struct ad4691_chip_info ad4694_chip_info =3D { - .channels =3D ad4693_channels, .name =3D "ad4694", - .num_channels =3D ARRAY_SIZE(ad4693_channels), .max_rate =3D 1 * HZ_PER_MHZ, + .sw_info =3D &ad4693_sw_info, }; =20 struct ad4691_state { const struct ad4691_chip_info *info; struct regmap *regmap; + struct spi_device *spi; + + struct pwm_device *conv_trigger; + int irq; int vref_uV; + u32 cnv_period_ns; + + bool manual_mode; bool refbuf_en; bool ldo_en; /* @@ -201,8 +245,45 @@ struct ad4691_state { * atomicity of consecutive SPI operations. */ struct mutex lock; + /* + * Per-buffer-enable lifetime resources: + * 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. + */ + struct spi_message scan_msg; + /* max 16 + 1 NOOP (manual) or 2*16 + 2 (CNV burst). */ + struct spi_transfer scan_xfers[34]; + /* + * CNV burst: 16 AVG_IN addresses + state-reset address + state-reset + * value =3D 18. Manual: 16 channel cmds + 1 NOOP =3D 17. + */ + __be16 scan_tx[18]; + /* Scan buffer: one BE16 slot per channel (rx'd directly), plus timestamp= */ + struct { + __be16 vals[16]; + aligned_s64 ts; + } scan; }; =20 +/* + * Configure the given GP pin (0-3) as DATA_READY output. + * GP0/GP1 =E2=86=92 GPIO_MODE1_REG, GP2/GP3 =E2=86=92 GPIO_MODE2_REG. + * Even pins occupy bits [3:0], odd pins bits [7:4]. + */ +static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num) +{ + unsigned int bit_off =3D gp_num % 2; + unsigned int reg_off =3D gp_num / 2; + unsigned int shift =3D 4 * bit_off; + + return regmap_update_bits(st->regmap, + AD4691_GPIO_MODE1_REG + reg_off, + AD4691_GP_MODE_MASK << shift, + AD4691_GP_MODE_DATA_READY << shift); +} + static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *= val) { struct spi_device *spi =3D context; @@ -486,6 +567,346 @@ static int ad4691_reg_access(struct iio_dev *indio_de= v, unsigned int reg, return regmap_write(st->regmap, reg, writeval); } =20 +static int ad4691_set_pwm_freq(struct ad4691_state *st, int freq) +{ + if (!freq) + return -EINVAL; + + st->cnv_period_ns =3D DIV_ROUND_UP(NSEC_PER_SEC, freq); + return 0; +} + +static int ad4691_sampling_enable(struct ad4691_state *st, bool enable) +{ + struct pwm_state conv_state =3D { + .period =3D st->cnv_period_ns, + .duty_cycle =3D AD4691_CNV_DUTY_CYCLE_NS, + .polarity =3D PWM_POLARITY_NORMAL, + .enabled =3D enable, + }; + + return pwm_apply_might_sleep(st->conv_trigger, &conv_state); +} + +/* + * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion= mode. + * + * Configures the ADC hardware registers for the mode selected at probe + * (CNV_BURST or MANUAL). Called from buffer preenable before starting + * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw). + */ +static int ad4691_enter_conversion_mode(struct ad4691_state *st) +{ + int ret; + + if (st->manual_mode) + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP, + AD4691_MANUAL_MODE, AD4691_MANUAL_MODE); + + ret =3D regmap_update_bits(st->regmap, AD4691_ADC_SETUP, + AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE); + if (ret) + return ret; + + return regmap_write(st->regmap, AD4691_STATE_RESET_REG, + AD4691_STATE_RESET_ALL); +} + +/* + * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode. + * + * Called from buffer postdisable to restore the chip to the + * idle state used by read_raw. Clears the sequencer and resets state. + */ +static int ad4691_exit_conversion_mode(struct ad4691_state *st) +{ + if (st->manual_mode) + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP, + AD4691_MANUAL_MODE, 0); + + return regmap_update_bits(st->regmap, AD4691_ADC_SETUP, + AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE); +} + +static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + unsigned int n_active; + unsigned int n_xfers; + unsigned int prev_i, k, i; + bool first; + int ret; + + n_active =3D bitmap_weight(indio_dev->active_scan_mask, iio_get_masklengt= h(indio_dev)); + n_xfers =3D n_active + 1; + + memset(st->scan_xfers, 0, n_xfers * sizeof(st->scan_xfers[0])); + memset(st->scan_tx, 0, n_xfers * sizeof(st->scan_tx[0])); + + spi_message_init(&st->scan_msg); + + first =3D true; + prev_i =3D 0; + k =3D 0; + iio_for_each_active_channel(indio_dev, i) { + st->scan_tx[k] =3D cpu_to_be16(AD4691_ADC_CHAN(i)); + st->scan_xfers[k].tx_buf =3D &st->scan_tx[k]; + /* + * The pipeline means xfer[0] receives the residual from the + * previous sequence, not a valid sample for channel i. Point + * it at vals[i] anyway; xfer[1] (or the NOOP when only one + * channel is active) will overwrite that slot with the real + * result, so no separate dummy buffer is needed. + */ + if (first) { + st->scan_xfers[k].rx_buf =3D &st->scan.vals[i]; + first =3D false; + } else { + st->scan_xfers[k].rx_buf =3D &st->scan.vals[prev_i]; + } + st->scan_xfers[k].len =3D sizeof(__be16); + st->scan_xfers[k].cs_change =3D 1; + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg); + prev_i =3D i; + k++; + } + + /* Final NOOP transfer retrieves the last channel's result. */ + st->scan_xfers[k].tx_buf =3D &st->scan_tx[k]; /* scan_tx[k] =3D=3D 0 =3D= =3D NOOP */ + st->scan_xfers[k].rx_buf =3D &st->scan.vals[prev_i]; + st->scan_xfers[k].len =3D sizeof(__be16); + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg); + + ret =3D spi_optimize_message(st->spi, &st->scan_msg); + if (ret) + return ret; + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) { + spi_unoptimize_message(&st->scan_msg); + return ret; + } + + return 0; +} + +static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + int ret; + + ret =3D ad4691_exit_conversion_mode(st); + spi_unoptimize_message(&st->scan_msg); + return ret; +} + +static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = =3D { + .preenable =3D &ad4691_manual_buffer_preenable, + .postdisable =3D &ad4691_manual_buffer_postdisable, +}; + +static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + unsigned int n_active; + unsigned int k, i; + int ret; + + n_active =3D bitmap_weight(indio_dev->active_scan_mask, iio_get_masklengt= h(indio_dev)); + + memset(st->scan_xfers, 0, (2 * n_active + 2) * sizeof(st->scan_xfers[0])); + memset(st->scan_tx, 0, (n_active + 2) * sizeof(st->scan_tx[0])); + + spi_message_init(&st->scan_msg); + + /* + * Each AVG_IN read needs two transfers: a 2-byte address write phase + * followed by a 2-byte data read phase. CS toggles between channels + * (cs_change=3D1 on the read phase of all but the last channel). + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, i) { + st->scan_tx[k] =3D cpu_to_be16(0x8000 | AD4691_AVG_IN(i)); + st->scan_xfers[2 * k].tx_buf =3D &st->scan_tx[k]; + st->scan_xfers[2 * k].len =3D sizeof(__be16); + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg); + st->scan_xfers[2 * k + 1].rx_buf =3D &st->scan.vals[i]; + st->scan_xfers[2 * k + 1].len =3D sizeof(__be16); + st->scan_xfers[2 * k + 1].cs_change =3D 1; + spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg); + k++; + } + + st->scan_tx[k] =3D cpu_to_be16(AD4691_STATE_RESET_REG); + st->scan_xfers[2 * k].tx_buf =3D &st->scan_tx[k]; + st->scan_xfers[2 * k].len =3D sizeof(__be16); + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg); + st->scan_tx[k + 1] =3D cpu_to_be16(AD4691_STATE_RESET_ALL << 8); + st->scan_xfers[2 * k + 1].tx_buf =3D &st->scan_tx[k + 1]; + st->scan_xfers[2 * k + 1].len =3D sizeof(__be16); + st->scan_xfers[2 * k + 1].cs_change =3D 1; + spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg); + + ret =3D spi_optimize_message(st->spi, &st->scan_msg); + if (ret) + goto err_unoptimize; + + 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) + goto err_unoptimize; + + 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) + goto err_unoptimize; + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + goto err_unoptimize; + + ret =3D ad4691_sampling_enable(st, true); + if (ret) + goto err_exit_conv; + + enable_irq(st->irq); + return 0; + +err_exit_conv: + ad4691_exit_conversion_mode(st); +err_unoptimize: + spi_unoptimize_message(&st->scan_msg); + return ret; +} + +static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + int ret; + + disable_irq(st->irq); + + ret =3D ad4691_sampling_enable(st, false); + if (ret) + return ret; + + ret =3D ad4691_exit_conversion_mode(st); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, + AD4691_SEQ_ALL_CHANNELS_OFF); + spi_unoptimize_message(&st->scan_msg); + return ret; +} + +static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops= =3D { + .preenable =3D &ad4691_cnv_burst_buffer_preenable, + .postdisable =3D &ad4691_cnv_burst_buffer_postdisable, +}; + +static ssize_t sampling_frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct ad4691_state *st =3D iio_priv(indio_dev); + + return sysfs_emit(buf, "%u\n", NSEC_PER_SEC / st->cnv_period_ns); +} + +static ssize_t sampling_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct ad4691_state *st =3D iio_priv(indio_dev); + int freq, ret; + + ret =3D kstrtoint(buf, 10, &freq); + if (ret) + return ret; + + ret =3D iio_device_claim_direct(indio_dev); + if (ret) + return ret; + + ret =3D ad4691_set_pwm_freq(st, freq); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_RW(sampling_frequency, 0); + +static const struct iio_dev_attr *ad4691_buffer_attrs[] =3D { + &iio_dev_attr_sampling_frequency, + NULL +}; + +static irqreturn_t ad4691_irq(int irq, void *private) +{ + struct iio_dev *indio_dev =3D private; + struct ad4691_state *st =3D iio_priv(indio_dev); + + iio_trigger_poll(indio_dev->trig); + /* + * Keep the DATA_READY IRQ disabled until the trigger handler has + * finished reading the scan, to prevent a new assertion mid-transfer. + * The PWM continues running uninterrupted; the IRQ is re-enabled in + * ad4691_trigger_handler once spi_sync completes. + * + * IRQF_ONESHOT already masks the hardware line during this threaded + * handler, so disable_irq_nosync here ensures the IRQ stays disabled + * even after IRQF_ONESHOT unmasks on return. + */ + disable_irq_nosync(st->irq); + + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops ad4691_trigger_ops =3D { + .validate_device =3D iio_trigger_validate_own_device, +}; + +static int ad4691_read_scan(struct iio_dev *indio_dev, s64 timestamp) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + ret =3D spi_sync(st->spi, &st->scan_msg); + if (ret) + return ret; + + /* + * rx_buf pointers in scan_xfers point directly into scan.vals, so no + * copy is needed. The scan_msg already includes a STATE_RESET at the + * end (appended in preenable), so no explicit reset is needed here. + */ + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan), + timestamp); + return 0; +} + +static irqreturn_t ad4691_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf =3D p; + struct iio_dev *indio_dev =3D pf->indio_dev; + struct ad4691_state *st =3D iio_priv(indio_dev); + + ad4691_read_scan(indio_dev, pf->timestamp); + if (!st->manual_mode) + enable_irq(st->irq); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + static const struct iio_info ad4691_info =3D { .read_raw =3D &ad4691_read_raw, .write_raw =3D &ad4691_write_raw, @@ -493,6 +914,18 @@ static const struct iio_info ad4691_info =3D { .debugfs_reg_access =3D &ad4691_reg_access, }; =20 +static int ad4691_pwm_setup(struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + + st->conv_trigger =3D devm_pwm_get(dev, "cnv"); + if (IS_ERR(st->conv_trigger)) + return dev_err_probe(dev, PTR_ERR(st->conv_trigger), + "Failed to get cnv pwm\n"); + + return ad4691_set_pwm_freq(st, st->info->max_rate); +} + static int ad4691_regulator_setup(struct ad4691_state *st) { struct device *dev =3D regmap_get_device(st->regmap); @@ -558,6 +991,22 @@ static int ad4691_config(struct ad4691_state *st) unsigned int val; int ret; =20 + /* + * Determine buffer conversion mode from DT: if a PWM is provided it + * drives the CNV pin (CNV_BURST_MODE); otherwise CNV is tied to CS + * and each SPI transfer triggers a conversion (MANUAL_MODE). + * Both modes idle in AUTONOMOUS mode so that read_raw can use the + * internal oscillator without disturbing the hardware configuration. + */ + if (device_property_present(dev, "pwms")) { + st->manual_mode =3D false; + ret =3D ad4691_pwm_setup(st); + if (ret) + return ret; + } else { + st->manual_mode =3D true; + } + switch (st->vref_uV) { case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX: ref_val =3D AD4691_VREF_2P5; @@ -613,6 +1062,76 @@ static int ad4691_config(struct ad4691_state *st) return 0; } =20 +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev, + struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + struct iio_trigger *trig; + unsigned int i; + int irq, ret; + + trig =3D devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!trig) + return -ENOMEM; + + trig->ops =3D &ad4691_trigger_ops; + iio_trigger_set_drvdata(trig, st); + + ret =3D devm_iio_trigger_register(dev, trig); + if (ret) + return dev_err_probe(dev, ret, "IIO trigger register failed\n"); + + indio_dev->trig =3D iio_trigger_get(trig); + + if (st->manual_mode) + return devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &ad4691_trigger_handler, + &ad4691_manual_buffer_setup_ops); + + /* + * The GP pin named in interrupt-names asserts at end-of-conversion. + * The IRQ handler stops conversions and fires the IIO trigger so + * the trigger handler can read and push the sample to the buffer. + * The IRQ is kept disabled until the buffer is enabled. + */ + irq =3D -ENXIO; + for (i =3D 0; i < ARRAY_SIZE(ad4691_gp_names); i++) { + irq =3D fwnode_irq_get_byname(dev_fwnode(dev), + ad4691_gp_names[i]); + if (irq > 0) + break; + } + if (irq < 0) + return dev_err_probe(dev, irq, "failed to get GP interrupt\n"); + + st->irq =3D irq; + + ret =3D ad4691_gpio_setup(st, i); + if (ret) + return ret; + + /* + * IRQ is kept disabled until the buffer is enabled to prevent + * spurious DATA_READY events before the SPI message is set up. + */ + ret =3D devm_request_threaded_irq(dev, irq, NULL, + &ad4691_irq, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + indio_dev->name, indio_dev); + if (ret) + return ret; + + return devm_iio_triggered_buffer_setup_ext(dev, indio_dev, + &iio_pollfunc_store_time, + &ad4691_trigger_handler, + IIO_BUFFER_DIRECTION_IN, + &ad4691_cnv_burst_buffer_setup_ops, + ad4691_buffer_attrs); +} + static int ad4691_probe(struct spi_device *spi) { struct device *dev =3D &spi->dev; @@ -625,6 +1144,7 @@ static int ad4691_probe(struct spi_device *spi) return -ENOMEM; =20 st =3D iio_priv(indio_dev); + st->spi =3D spi; st->info =3D spi_get_device_match_data(spi); =20 ret =3D devm_mutex_init(dev, &st->lock); @@ -652,8 +1172,11 @@ static int ad4691_probe(struct spi_device *spi) indio_dev->info =3D &ad4691_info; indio_dev->modes =3D INDIO_DIRECT_MODE; =20 - indio_dev->channels =3D st->info->channels; - indio_dev->num_channels =3D st->info->num_channels; + 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 return devm_iio_device_register(dev, indio_dev); } --=20 2.43.0