Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2223833/?format=api
{ "id": 2223833, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2223833/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-gpio/patch/20260416-ad4692-multichannel-sar-adc-driver-v8-3-c415bd048fa3@analog.com/", "project": { "id": 42, "url": "http://patchwork.ozlabs.org/api/1.1/projects/42/?format=api", "name": "Linux GPIO development", "link_name": "linux-gpio", "list_id": "linux-gpio.vger.kernel.org", "list_email": "linux-gpio@vger.kernel.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20260416-ad4692-multichannel-sar-adc-driver-v8-3-c415bd048fa3@analog.com>", "date": "2026-04-16T09:18:48", "name": "[v8,3/6] iio: adc: ad4691: add triggered buffer support", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "4431735563cc363e5acda9affc28323ff1cacf11", "submitter": { "id": 92791, "url": "http://patchwork.ozlabs.org/api/1.1/people/92791/?format=api", "name": "Radu Sabau via B4 Relay", "email": "devnull+radu.sabau.analog.com@kernel.org" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/linux-gpio/patch/20260416-ad4692-multichannel-sar-adc-driver-v8-3-c415bd048fa3@analog.com/mbox/", "series": [ { "id": 500122, "url": "http://patchwork.ozlabs.org/api/1.1/series/500122/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-gpio/list/?series=500122", "date": "2026-04-16T09:18:48", "name": "iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family", "version": 8, "mbox": "http://patchwork.ozlabs.org/series/500122/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2223833/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2223833/checks/", "tags": {}, "headers": { "Return-Path": "\n <linux-gpio+bounces-35187-incoming=patchwork.ozlabs.org@vger.kernel.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "linux-gpio@vger.kernel.org" ], "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256\n header.s=k20201202 header.b=cbg9+KGj;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=2600:3c09:e001:a7::12fc:5321; helo=sto.lore.kernel.org;\n envelope-from=linux-gpio+bounces-35187-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)", "smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org\n header.b=\"cbg9+KGj\"", "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=10.30.226.201" ], "Received": [ "from sto.lore.kernel.org (sto.lore.kernel.org\n [IPv6:2600:3c09:e001:a7::12fc:5321])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fxCBv6xVvz1yDF\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 16 Apr 2026 19:19:35 +1000 (AEST)", "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sto.lore.kernel.org (Postfix) with ESMTP id C05F63064EDC\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 16 Apr 2026 09:19:07 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id AB74139BFF2;\n\tThu, 16 Apr 2026 09:18:52 +0000 (UTC)", "from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org\n [10.30.226.201])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 47F0D39A05F;\n\tThu, 16 Apr 2026 09:18:52 +0000 (UTC)", "by smtp.kernel.org (Postfix) with ESMTPS id 011C0C2BCF6;\n\tThu, 16 Apr 2026 09:18:52 +0000 (UTC)", "from aws-us-west-2-korg-lkml-1.web.codeaurora.org\n (localhost.localdomain [127.0.0.1])\n\tby smtp.lore.kernel.org (Postfix) with ESMTP id E91E3F8A145;\n\tThu, 16 Apr 2026 09:18:51 +0000 (UTC)" ], "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1776331132; cv=none;\n b=UGhasKSpNLuE43IG3L9+lomdtJ+Rg0+6Yscd6e9iuyZDY3VdRkwWbkdLMJGcLm8V5fV+2fhAvdPdeQjXn/IDARbSFrclkggXhythV6rVjiaMgvIMlGTfTNKCtO1XcuNcHja6qSHM0jYF6lr/2TwSi9w7qKne/VGVOU7ku4+zyFw=", "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1776331132; c=relaxed/simple;\n\tbh=1RFSjV/KoHdG1vc0zOdMqnodO+soE2e8L2HtgCilC/E=;\n\th=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References:\n\t In-Reply-To:To:Cc;\n b=WGa+RxVCgqlMaicThm50vvjciKtuXVLYoYbh1v8ppPdlTIWxPzT8w1diRwH3tci2J5E+p+yLrbiCpqiyzL8zSOLiJigEfy9aK73d+vp6w2Vhq8ZgWHWmsiPXVxq66k0aeOt4Jesaj+6HhRcgVF725w03qJTF1RG9IF7ZDI/EghY=", "ARC-Authentication-Results": "i=1; smtp.subspace.kernel.org;\n dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org\n header.b=cbg9+KGj; arc=none smtp.client-ip=10.30.226.201", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;\n\ts=k20201202; t=1776331132;\n\tbh=1RFSjV/KoHdG1vc0zOdMqnodO+soE2e8L2HtgCilC/E=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From;\n\tb=cbg9+KGjkWAr10AdkLQYXT/jdgTMuXG7+UzUmEeDb363jEaWhnGDRjz9FG3P2GMZN\n\t ZpwgT65lxD5P/9wRp4OHVLwYjPvWFNh0eQ316a/00JI7irrnT+q3MylxEVqTXq1N8o\n\t k67hCLucW9+MqZar1GlX+4rIu/n80ZPGIG62opdRT8QDA4ZlD9xYi2nfRdhd0GgACK\n\t U5cak52/2ZHRC8cRErYUU8scW2/ix+WYVQK6Dn/4BjfaJk4NMfCPidKs6KrsTzkdrd\n\t jJ4JsR9W8aeTDMJEGfPPF/b3PcL3US2WQmel+YkP6WMLnYhXB8cG8WYLCJNKuHIozj\n\t fg+5H2SmUXPjw==", "From": "Radu Sabau via B4 Relay <devnull+radu.sabau.analog.com@kernel.org>", "Date": "Thu, 16 Apr 2026 12:18:48 +0300", "Subject": "[PATCH v8 3/6] iio: adc: ad4691: add triggered buffer support", "Precedence": "bulk", "X-Mailing-List": "linux-gpio@vger.kernel.org", "List-Id": "<linux-gpio.vger.kernel.org>", "List-Subscribe": "<mailto:linux-gpio+subscribe@vger.kernel.org>", "List-Unsubscribe": "<mailto:linux-gpio+unsubscribe@vger.kernel.org>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "8bit", "Message-Id": "\n <20260416-ad4692-multichannel-sar-adc-driver-v8-3-c415bd048fa3@analog.com>", "References": "\n <20260416-ad4692-multichannel-sar-adc-driver-v8-0-c415bd048fa3@analog.com>", "In-Reply-To": "\n <20260416-ad4692-multichannel-sar-adc-driver-v8-0-c415bd048fa3@analog.com>", "To": "Lars-Peter Clausen <lars@metafoo.de>,\n Michael Hennerich <Michael.Hennerich@analog.com>,\n Jonathan Cameron <jic23@kernel.org>, David Lechner <dlechner@baylibre.com>,\n\t=?utf-8?q?Nuno_S=C3=A1?= <nuno.sa@analog.com>,\n Andy Shevchenko <andy@kernel.org>, Rob Herring <robh@kernel.org>,\n Krzysztof Kozlowski <krzk+dt@kernel.org>,\n Conor Dooley <conor+dt@kernel.org>,\n =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= <ukleinek@kernel.org>,\n Liam Girdwood <lgirdwood@gmail.com>, Mark Brown <broonie@kernel.org>,\n Linus Walleij <linusw@kernel.org>, Bartosz Golaszewski <brgl@kernel.org>,\n Philipp Zabel <p.zabel@pengutronix.de>, Jonathan Corbet <corbet@lwn.net>,\n Shuah Khan <skhan@linuxfoundation.org>", "Cc": "linux-iio@vger.kernel.org, devicetree@vger.kernel.org,\n linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,\n linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org,\n Radu Sabau <radu.sabau@analog.com>", "X-Mailer": "b4 0.14.3", "X-Developer-Signature": "v=1; a=ed25519-sha256; t=1776331127; l=23834;\n i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id;\n bh=6WvvRyJG06yp8+r2MfuKaLHJ0vm9HtSlYrZETVC9Qk4=;\n b=OA0U+YpbISbJXcN5XwOtfToaifIE9ZVWRNxkeW3p27rqbyewT4Yb+bABDZHIKQVRPcq5smaXN\n jPZNGuL9oM0DmUrEfsJTL5v6Mzex8ZCIWZ5BZ6PempDJKRjbdsJssYN", "X-Developer-Key": "i=radu.sabau@analog.com; a=ed25519;\n pk=lDPQHgn9jTdt0vo58Na9lLxLaE2mb330if71Cn+EvFU=", "X-Endpoint-Received": "by B4 Relay for radu.sabau@analog.com/20260220 with\n auth_id=642", "X-Original-From": "Radu Sabau <radu.sabau@analog.com>", "Reply-To": "radu.sabau@analog.com" }, "content": "From: Radu Sabau <radu.sabau@analog.com>\n\nAdd buffered capture support using the IIO triggered buffer framework.\n\nCNV Burst Mode: the GP pin identified by interrupt-names in the device\ntree is configured as DATA_READY output. The IRQ handler stops\nconversions and fires the IIO trigger; the trigger handler executes a\npre-built SPI message that reads all active channels from the AVG_IN\naccumulator registers and then resets accumulator state and restarts\nconversions for the next cycle.\n\nManual Mode: CNV is tied to SPI CS so each transfer simultaneously\nreads the previous result and starts the next conversion (pipelined\nN+1 scheme). At preenable time a pre-built, optimised SPI message of\nN+1 transfers is constructed (N channel reads plus one NOOP to drain\nthe pipeline). The trigger handler executes the message in a single\nspi_sync() call and collects the results. An external trigger (e.g.\niio-trig-hrtimer) is required to drive the trigger at the desired\nsample rate.\n\nBoth modes share the same trigger handler and push a complete scan —\none u16 slot per channel at its scan_index position, followed by a\ntimestamp — to the IIO buffer via iio_push_to_buffers_with_ts().\n\nThe CNV Burst Mode sampling frequency (PWM period) is exposed as a\nbuffer-level attribute via IIO_DEVICE_ATTR.\n\nSigned-off-by: Radu Sabau <radu.sabau@analog.com>\n---\n drivers/iio/adc/Kconfig | 2 +\n drivers/iio/adc/ad4691.c | 537 +++++++++++++++++++++++++++++++++++++++++++++--\n 2 files changed, 524 insertions(+), 15 deletions(-)", "diff": "diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig\nindex 3685a03aa8dc..d498f16c0816 100644\n--- a/drivers/iio/adc/Kconfig\n+++ b/drivers/iio/adc/Kconfig\n@@ -142,6 +142,8 @@ config AD4170_4\n config AD4691\n \ttristate \"Analog Devices AD4691 Family ADC Driver\"\n \tdepends on SPI\n+\tselect IIO_BUFFER\n+\tselect IIO_TRIGGERED_BUFFER\n \tselect REGMAP\n \thelp\n \t Say yes here to build support for Analog Devices AD4691 Family MuxSAR\ndiff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c\nindex 61b5f74205af..98c079e8ded3 100644\n--- a/drivers/iio/adc/ad4691.c\n+++ b/drivers/iio/adc/ad4691.c\n@@ -5,16 +5,20 @@\n */\n #include <linux/array_size.h>\n #include <linux/bitfield.h>\n-#include <linux/bitops.h>\n+#include <linux/bitmap.h>\n #include <linux/cleanup.h>\n #include <linux/delay.h>\n #include <linux/dev_printk.h>\n #include <linux/device/devres.h>\n+#include <linux/dmaengine.h>\n #include <linux/err.h>\n+#include <linux/interrupt.h>\n #include <linux/limits.h>\n #include <linux/math.h>\n #include <linux/module.h>\n #include <linux/mod_devicetable.h>\n+#include <linux/property.h>\n+#include <linux/pwm.h>\n #include <linux/regmap.h>\n #include <linux/regulator/consumer.h>\n #include <linux/reset.h>\n@@ -22,7 +26,12 @@\n #include <linux/units.h>\n #include <linux/unaligned.h>\n \n+#include <linux/iio/buffer.h>\n #include <linux/iio/iio.h>\n+#include <linux/iio/sysfs.h>\n+#include <linux/iio/trigger.h>\n+#include <linux/iio/triggered_buffer.h>\n+#include <linux/iio/trigger_consumer.h>\n \n #define AD4691_VREF_uV_MIN\t\t\t2400000\n #define AD4691_VREF_uV_MAX\t\t\t5250000\n@@ -31,6 +40,9 @@\n #define AD4691_VREF_3P3_uV_MAX\t\t\t3750000\n #define AD4691_VREF_4P096_uV_MAX\t\t4500000\n \n+#define AD4691_CNV_DUTY_CYCLE_NS\t\t380\n+#define AD4691_CNV_HIGH_TIME_NS\t\t\t430\n+\n #define AD4691_SPI_CONFIG_A_REG\t\t\t0x000\n #define AD4691_SW_RESET\t\t\t\t(BIT(7) | BIT(0))\n \n@@ -38,6 +50,7 @@\n #define AD4691_CLAMP_STATUS1_REG\t\t0x01A\n #define AD4691_CLAMP_STATUS2_REG\t\t0x01B\n #define AD4691_DEVICE_SETUP\t\t\t0x020\n+#define AD4691_MANUAL_MODE\t\t\tBIT(2)\n #define AD4691_LDO_EN\t\t\t\tBIT(4)\n #define AD4691_REF_CTRL\t\t\t\t0x021\n #define AD4691_REF_CTRL_MASK\t\t\tGENMASK(4, 2)\n@@ -45,13 +58,18 @@\n #define AD4691_OSC_FREQ_REG\t\t\t0x023\n #define AD4691_OSC_FREQ_MASK\t\t\tGENMASK(3, 0)\n #define AD4691_STD_SEQ_CONFIG\t\t\t0x025\n+#define AD4691_SEQ_ALL_CHANNELS_OFF\t\t0x00\n #define AD4691_SPARE_CONTROL\t\t\t0x02A\n \n+#define AD4691_NOOP\t\t\t\t0x00\n+#define AD4691_ADC_CHAN(ch)\t\t\t((0x10 + (ch)) << 3)\n+\n #define AD4691_OSC_EN_REG\t\t\t0x180\n #define AD4691_STATE_RESET_REG\t\t\t0x181\n #define AD4691_STATE_RESET_ALL\t\t\t0x01\n #define AD4691_ADC_SETUP\t\t\t0x182\n #define AD4691_ADC_MODE_MASK\t\t\tGENMASK(1, 0)\n+#define AD4691_CNV_BURST_MODE\t\t\t0x01\n #define AD4691_AUTONOMOUS_MODE\t\t\t0x02\n /*\n * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a\n@@ -61,6 +79,8 @@\n #define AD4691_ACC_DEPTH_IN(n)\t\t\t(0x186 + (n))\n #define AD4691_GPIO_MODE1_REG\t\t\t0x196\n #define AD4691_GPIO_MODE2_REG\t\t\t0x197\n+#define AD4691_GP_MODE_MASK\t\t\tGENMASK(3, 0)\n+#define AD4691_GP_MODE_DATA_READY\t\t0x06\n #define AD4691_GPIO_READ\t\t\t0x1A0\n #define AD4691_ACC_STATUS_FULL1_REG\t\t0x1B0\n #define AD4691_ACC_STATUS_FULL2_REG\t\t0x1B1\n@@ -84,19 +104,23 @@ enum ad4691_ref_ctrl {\n \tAD4691_VREF_5P0 = 4,\n };\n \n-struct ad4691_chip_info {\n+struct ad4691_channel_info {\n \tconst struct iio_chan_spec *channels;\n-\tconst char *name;\n \tunsigned int num_channels;\n+};\n+\n+struct ad4691_chip_info {\n+\tconst char *name;\n \tunsigned int max_rate;\n+\tconst struct ad4691_channel_info *sw_info;\n };\n \n #define AD4691_CHANNEL(ch)\t\t\t\t\t\t\\\n \t{\t\t\t\t\t\t\t\t\\\n \t\t.type = IIO_VOLTAGE,\t\t\t\t\t\\\n \t\t.indexed = 1,\t\t\t\t\t\t\\\n-\t\t.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)\t\t\\\n-\t\t\t\t | BIT(IIO_CHAN_INFO_SAMP_FREQ),\t\\\n+\t\t.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |\t\t\\\n+\t\t\t\t BIT(IIO_CHAN_INFO_SAMP_FREQ),\t\\\n \t\t.info_mask_separate_available =\t\t\t\t\\\n \t\t\t\t BIT(IIO_CHAN_INFO_SAMP_FREQ),\t\\\n \t\t.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),\t\\\n@@ -106,6 +130,7 @@ struct ad4691_chip_info {\n \t\t\t.sign = 'u',\t\t\t\t\t\\\n \t\t\t.realbits = 16,\t\t\t\t\t\\\n \t\t\t.storagebits = 16,\t\t\t\t\\\n+\t\t\t.endianness = IIO_BE,\t\t\t\t\\\n \t\t},\t\t\t\t\t\t\t\\\n \t}\n \n@@ -126,6 +151,7 @@ static const struct iio_chan_spec ad4691_channels[] = {\n \tAD4691_CHANNEL(13),\n \tAD4691_CHANNEL(14),\n \tAD4691_CHANNEL(15),\n+\tIIO_CHAN_SOFT_TIMESTAMP(16),\n };\n \n static const struct iio_chan_spec ad4693_channels[] = {\n@@ -137,6 +163,17 @@ static const struct iio_chan_spec ad4693_channels[] = {\n \tAD4691_CHANNEL(5),\n \tAD4691_CHANNEL(6),\n \tAD4691_CHANNEL(7),\n+\tIIO_CHAN_SOFT_TIMESTAMP(8),\n+};\n+\n+static const struct ad4691_channel_info ad4691_sw_info = {\n+\t.channels = ad4691_channels,\n+\t.num_channels = ARRAY_SIZE(ad4691_channels),\n+};\n+\n+static const struct ad4691_channel_info ad4693_sw_info = {\n+\t.channels = ad4693_channels,\n+\t.num_channels = ARRAY_SIZE(ad4693_channels),\n };\n \n /*\n@@ -163,38 +200,43 @@ static const int ad4691_osc_freqs_Hz[] = {\n \t[0xF] = 1250,\n };\n \n+static const char * const ad4691_gp_names[] = { \"gp0\", \"gp1\", \"gp2\", \"gp3\" };\n+\n static const struct ad4691_chip_info ad4691_chip_info = {\n-\t.channels = ad4691_channels,\n \t.name = \"ad4691\",\n-\t.num_channels = ARRAY_SIZE(ad4691_channels),\n \t.max_rate = 500 * HZ_PER_KHZ,\n+\t.sw_info = &ad4691_sw_info,\n };\n \n static const struct ad4691_chip_info ad4692_chip_info = {\n-\t.channels = ad4691_channels,\n \t.name = \"ad4692\",\n-\t.num_channels = ARRAY_SIZE(ad4691_channels),\n \t.max_rate = 1 * HZ_PER_MHZ,\n+\t.sw_info = &ad4691_sw_info,\n };\n \n static const struct ad4691_chip_info ad4693_chip_info = {\n-\t.channels = ad4693_channels,\n \t.name = \"ad4693\",\n-\t.num_channels = ARRAY_SIZE(ad4693_channels),\n \t.max_rate = 500 * HZ_PER_KHZ,\n+\t.sw_info = &ad4693_sw_info,\n };\n \n static const struct ad4691_chip_info ad4694_chip_info = {\n-\t.channels = ad4693_channels,\n \t.name = \"ad4694\",\n-\t.num_channels = ARRAY_SIZE(ad4693_channels),\n \t.max_rate = 1 * HZ_PER_MHZ,\n+\t.sw_info = &ad4693_sw_info,\n };\n \n struct ad4691_state {\n \tconst struct ad4691_chip_info *info;\n \tstruct regmap *regmap;\n+\tstruct spi_device *spi;\n+\n+\tstruct pwm_device *conv_trigger;\n+\tint irq;\n \tint vref_uV;\n+\tu32 cnv_period_ns;\n+\n+\tbool manual_mode;\n \tbool refbuf_en;\n \tbool ldo_en;\n \t/*\n@@ -202,8 +244,45 @@ struct ad4691_state {\n \t * atomicity of consecutive SPI operations.\n \t */\n \tstruct mutex lock;\n+\t/*\n+\t * Per-buffer-enable lifetime resources:\n+\t * Manual Mode - a pre-built SPI message that clocks out N+1\n+\t *\t\t transfers in one go.\n+\t * CNV Burst Mode - a pre-built SPI message that clocks out 2*N\n+\t *\t\t transfers in one go.\n+\t */\n+\tstruct spi_message scan_msg;\n+\t/* max 16 + 1 NOOP (manual) or 2*16 + 2 (CNV burst). */\n+\tstruct spi_transfer scan_xfers[34];\n+\t/*\n+\t * CNV burst: 16 AVG_IN addresses + state-reset address + state-reset\n+\t * value = 18. Manual: 16 channel cmds + 1 NOOP = 17.\n+\t */\n+\t__be16 scan_tx[18] __aligned(IIO_DMA_MINALIGN);\n+\t/*\n+\t * Scan buffer: one BE16 slot per active channel, plus timestamp.\n+\t * DMA-aligned because scan_xfers point rx_buf directly into vals[].\n+\t */\n+\tIIO_DECLARE_DMA_BUFFER_WITH_TS(__be16, vals, 16);\n };\n \n+/*\n+ * Configure the given GP pin (0-3) as DATA_READY output.\n+ * GP0/GP1 → GPIO_MODE1_REG, GP2/GP3 → GPIO_MODE2_REG.\n+ * Even pins occupy bits [3:0], odd pins bits [7:4].\n+ */\n+static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)\n+{\n+\tunsigned int bit_off = gp_num % 2;\n+\tunsigned int reg_off = gp_num / 2;\n+\tunsigned int shift = 4 * bit_off;\n+\n+\treturn regmap_update_bits(st->regmap,\n+\t\t\t\t AD4691_GPIO_MODE1_REG + reg_off,\n+\t\t\t\t AD4691_GP_MODE_MASK << shift,\n+\t\t\t\t AD4691_GP_MODE_DATA_READY << shift);\n+}\n+\n static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)\n {\n \tstruct spi_device *spi = context;\n@@ -497,6 +576,333 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,\n \treturn regmap_write(st->regmap, reg, writeval);\n }\n \n+static int ad4691_set_pwm_freq(struct ad4691_state *st, int freq)\n+{\n+\tif (!freq)\n+\t\treturn -EINVAL;\n+\n+\tst->cnv_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, freq);\n+\treturn 0;\n+}\n+\n+static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)\n+{\n+\tstruct pwm_state conv_state = {\n+\t\t.period = st->cnv_period_ns,\n+\t\t.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS,\n+\t\t.polarity = PWM_POLARITY_NORMAL,\n+\t\t.enabled = enable,\n+\t};\n+\n+\treturn pwm_apply_might_sleep(st->conv_trigger, &conv_state);\n+}\n+\n+/*\n+ * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.\n+ *\n+ * Configures the ADC hardware registers for the mode selected at probe\n+ * (CNV_BURST or MANUAL). Called from buffer preenable before starting\n+ * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw).\n+ */\n+static int ad4691_enter_conversion_mode(struct ad4691_state *st)\n+{\n+\tint ret;\n+\n+\tif (st->manual_mode)\n+\t\treturn regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,\n+\t\t\t\t\t AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);\n+\n+\tret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,\n+\t\t\t\t AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn regmap_write(st->regmap, AD4691_STATE_RESET_REG,\n+\t\t\t AD4691_STATE_RESET_ALL);\n+}\n+\n+/*\n+ * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.\n+ *\n+ * Called from buffer postdisable to restore the chip to the\n+ * idle state used by read_raw. Clears the sequencer and resets state.\n+ */\n+static int ad4691_exit_conversion_mode(struct ad4691_state *st)\n+{\n+\tif (st->manual_mode)\n+\t\treturn regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,\n+\t\t\t\t\t AD4691_MANUAL_MODE, 0);\n+\n+\treturn regmap_update_bits(st->regmap, AD4691_ADC_SETUP,\n+\t\t\t\t AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);\n+}\n+\n+static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev)\n+{\n+\tstruct ad4691_state *st = iio_priv(indio_dev);\n+\tunsigned int prev_i, k, i;\n+\tbool first;\n+\tint ret;\n+\n+\tmemset(st->scan_xfers, 0, sizeof(st->scan_xfers));\n+\tmemset(st->scan_tx, 0, sizeof(st->scan_tx));\n+\n+\tspi_message_init(&st->scan_msg);\n+\n+\tfirst = true;\n+\tprev_i = 0;\n+\tk = 0;\n+\tiio_for_each_active_channel(indio_dev, i) {\n+\t\tst->scan_tx[k] = cpu_to_be16(AD4691_ADC_CHAN(i));\n+\t\tst->scan_xfers[k].tx_buf = &st->scan_tx[k];\n+\t\t/*\n+\t\t * The pipeline means xfer[0] receives the residual from the\n+\t\t * previous sequence, not a valid sample for channel i. Point\n+\t\t * it at vals[i] anyway; xfer[1] (or the NOOP when only one\n+\t\t * channel is active) will overwrite that slot with the real\n+\t\t * result, so no separate dummy buffer is needed.\n+\t\t */\n+\t\tif (first) {\n+\t\t\tst->scan_xfers[k].rx_buf = &st->vals[i];\n+\t\t\tfirst = false;\n+\t\t} else {\n+\t\t\tst->scan_xfers[k].rx_buf = &st->vals[prev_i];\n+\t\t}\n+\t\tst->scan_xfers[k].len = sizeof(__be16);\n+\t\tst->scan_xfers[k].cs_change = 1;\n+\t\tspi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);\n+\t\tprev_i = i;\n+\t\tk++;\n+\t}\n+\n+\t/* Final NOOP transfer retrieves the last channel's result. */\n+\tst->scan_xfers[k].tx_buf = &st->scan_tx[k]; /* scan_tx[k] == 0 == NOOP */\n+\tst->scan_xfers[k].rx_buf = &st->vals[prev_i];\n+\tst->scan_xfers[k].len = sizeof(__be16);\n+\tspi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);\n+\n+\tret = spi_optimize_message(st->spi, &st->scan_msg);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ad4691_enter_conversion_mode(st);\n+\tif (ret) {\n+\t\tspi_unoptimize_message(&st->scan_msg);\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev)\n+{\n+\tstruct ad4691_state *st = iio_priv(indio_dev);\n+\tint ret;\n+\n+\tret = ad4691_exit_conversion_mode(st);\n+\tspi_unoptimize_message(&st->scan_msg);\n+\treturn ret;\n+}\n+\n+static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = {\n+\t.preenable = &ad4691_manual_buffer_preenable,\n+\t.postdisable = &ad4691_manual_buffer_postdisable,\n+};\n+\n+static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)\n+{\n+\tstruct ad4691_state *st = iio_priv(indio_dev);\n+\tunsigned int k, i;\n+\tint ret;\n+\n+\tmemset(st->scan_xfers, 0, sizeof(st->scan_xfers));\n+\tmemset(st->scan_tx, 0, sizeof(st->scan_tx));\n+\n+\tspi_message_init(&st->scan_msg);\n+\n+\t/*\n+\t * Each AVG_IN read needs two transfers: a 2-byte address write phase\n+\t * followed by a 2-byte data read phase. CS toggles between channels\n+\t * (cs_change=1 on the read phase of all but the last channel).\n+\t */\n+\tk = 0;\n+\tiio_for_each_active_channel(indio_dev, i) {\n+\t\tst->scan_tx[k] = cpu_to_be16(0x8000 | AD4691_AVG_IN(i));\n+\t\tst->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];\n+\t\tst->scan_xfers[2 * k].len = sizeof(__be16);\n+\t\tspi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);\n+\t\tst->scan_xfers[2 * k + 1].rx_buf = &st->vals[i];\n+\t\tst->scan_xfers[2 * k + 1].len = sizeof(__be16);\n+\t\tst->scan_xfers[2 * k + 1].cs_change = 1;\n+\t\tspi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);\n+\t\tk++;\n+\t}\n+\n+\tst->scan_tx[k] = cpu_to_be16(AD4691_STATE_RESET_REG);\n+\tst->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];\n+\tst->scan_xfers[2 * k].len = sizeof(__be16);\n+\tspi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);\n+\tst->scan_tx[k + 1] = cpu_to_be16(AD4691_STATE_RESET_ALL << 8);\n+\tst->scan_xfers[2 * k + 1].tx_buf = &st->scan_tx[k + 1];\n+\tst->scan_xfers[2 * k + 1].len = sizeof(__be16);\n+\tst->scan_xfers[2 * k + 1].cs_change = 1;\n+\tspi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);\n+\n+\tret = spi_optimize_message(st->spi, &st->scan_msg);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,\n+\t\t\t bitmap_read(indio_dev->active_scan_mask, 0,\n+\t\t\t\t iio_get_masklength(indio_dev)));\n+\tif (ret)\n+\t\tgoto err_unoptimize;\n+\n+\tret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,\n+\t\t\t ~bitmap_read(indio_dev->active_scan_mask, 0,\n+\t\t\t\tiio_get_masklength(indio_dev)) & GENMASK(15, 0));\n+\tif (ret)\n+\t\tgoto err_unoptimize;\n+\n+\tret = ad4691_enter_conversion_mode(st);\n+\tif (ret)\n+\t\tgoto err_unoptimize;\n+\n+\tret = ad4691_sampling_enable(st, true);\n+\tif (ret)\n+\t\tgoto err_exit_conv;\n+\n+\tenable_irq(st->irq);\n+\treturn 0;\n+\n+err_exit_conv:\n+\tad4691_exit_conversion_mode(st);\n+err_unoptimize:\n+\tspi_unoptimize_message(&st->scan_msg);\n+\treturn ret;\n+}\n+\n+static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev)\n+{\n+\tstruct ad4691_state *st = iio_priv(indio_dev);\n+\tint ret;\n+\n+\tdisable_irq(st->irq);\n+\n+\tret = ad4691_sampling_enable(st, false);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ad4691_exit_conversion_mode(st);\n+\tspi_unoptimize_message(&st->scan_msg);\n+\treturn ret;\n+}\n+\n+static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {\n+\t.preenable = &ad4691_cnv_burst_buffer_preenable,\n+\t.postdisable = &ad4691_cnv_burst_buffer_postdisable,\n+};\n+\n+static ssize_t sampling_frequency_show(struct device *dev,\n+\t\t\t\t struct device_attribute *attr,\n+\t\t\t\t char *buf)\n+{\n+\tstruct iio_dev *indio_dev = dev_to_iio_dev(dev);\n+\tstruct ad4691_state *st = iio_priv(indio_dev);\n+\n+\treturn sysfs_emit(buf, \"%lu\\n\", NSEC_PER_SEC / st->cnv_period_ns);\n+}\n+\n+static ssize_t sampling_frequency_store(struct device *dev,\n+\t\t\t\t\tstruct device_attribute *attr,\n+\t\t\t\t\tconst char *buf, size_t len)\n+{\n+\tstruct iio_dev *indio_dev = dev_to_iio_dev(dev);\n+\tstruct ad4691_state *st = iio_priv(indio_dev);\n+\tint freq, ret;\n+\n+\tret = kstrtoint(buf, 10, &freq);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = iio_device_claim_direct(indio_dev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = ad4691_set_pwm_freq(st, freq);\n+\tiio_device_release_direct(indio_dev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn len;\n+}\n+\n+static IIO_DEVICE_ATTR_RW(sampling_frequency, 0);\n+\n+static const struct iio_dev_attr *ad4691_buffer_attrs[] = {\n+\t&iio_dev_attr_sampling_frequency,\n+\tNULL\n+};\n+\n+static irqreturn_t ad4691_irq(int irq, void *private)\n+{\n+\tstruct iio_dev *indio_dev = private;\n+\tstruct ad4691_state *st = iio_priv(indio_dev);\n+\n+\tiio_trigger_poll(indio_dev->trig);\n+\t/*\n+\t * Keep the DATA_READY IRQ disabled until the trigger handler has\n+\t * finished reading the scan, to prevent a new assertion mid-transfer.\n+\t * The PWM continues running uninterrupted; the IRQ is re-enabled in\n+\t * ad4691_trigger_handler once spi_sync completes.\n+\t *\n+\t * IRQF_ONESHOT already masks the hardware line during this threaded\n+\t * handler, so disable_irq_nosync here ensures the IRQ stays disabled\n+\t * even after IRQF_ONESHOT unmasks on return.\n+\t */\n+\tdisable_irq_nosync(st->irq);\n+\n+\treturn IRQ_HANDLED;\n+}\n+\n+static const struct iio_trigger_ops ad4691_trigger_ops = {\n+\t.validate_device = iio_trigger_validate_own_device,\n+};\n+\n+static int ad4691_read_scan(struct iio_dev *indio_dev, s64 timestamp)\n+{\n+\tstruct ad4691_state *st = iio_priv(indio_dev);\n+\tint ret;\n+\n+\tguard(mutex)(&st->lock);\n+\n+\tret = spi_sync(st->spi, &st->scan_msg);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * rx_buf pointers in scan_xfers point directly into scan.vals, so no\n+\t * copy is needed. The scan_msg already includes a STATE_RESET at the\n+\t * end (appended in preenable), so no explicit reset is needed here.\n+\t */\n+\tiio_push_to_buffers_with_ts(indio_dev, st->vals, sizeof(st->vals),\n+\t\t\t\t timestamp);\n+\treturn 0;\n+}\n+\n+static irqreturn_t ad4691_trigger_handler(int irq, void *p)\n+{\n+\tstruct iio_poll_func *pf = p;\n+\tstruct iio_dev *indio_dev = pf->indio_dev;\n+\tstruct ad4691_state *st = iio_priv(indio_dev);\n+\n+\tad4691_read_scan(indio_dev, pf->timestamp);\n+\tif (!st->manual_mode)\n+\t\tenable_irq(st->irq);\n+\tiio_trigger_notify_done(indio_dev->trig);\n+\treturn IRQ_HANDLED;\n+}\n+\n static const struct iio_info ad4691_info = {\n \t.read_raw = &ad4691_read_raw,\n \t.write_raw = &ad4691_write_raw,\n@@ -504,6 +910,18 @@ static const struct iio_info ad4691_info = {\n \t.debugfs_reg_access = &ad4691_reg_access,\n };\n \n+static int ad4691_pwm_setup(struct ad4691_state *st)\n+{\n+\tstruct device *dev = regmap_get_device(st->regmap);\n+\n+\tst->conv_trigger = devm_pwm_get(dev, \"cnv\");\n+\tif (IS_ERR(st->conv_trigger))\n+\t\treturn dev_err_probe(dev, PTR_ERR(st->conv_trigger),\n+\t\t\t\t \"Failed to get cnv pwm\\n\");\n+\n+\treturn ad4691_set_pwm_freq(st, st->info->max_rate);\n+}\n+\n static int ad4691_regulator_setup(struct ad4691_state *st)\n {\n \tstruct device *dev = regmap_get_device(st->regmap);\n@@ -583,6 +1001,22 @@ static int ad4691_config(struct ad4691_state *st)\n \tunsigned int val;\n \tint ret;\n \n+\t/*\n+\t * Determine buffer conversion mode from DT: if a PWM is provided it\n+\t * drives the CNV pin (CNV_BURST_MODE); otherwise CNV is tied to CS\n+\t * and each SPI transfer triggers a conversion (MANUAL_MODE).\n+\t * Both modes idle in AUTONOMOUS mode so that read_raw can use the\n+\t * internal oscillator without disturbing the hardware configuration.\n+\t */\n+\tif (device_property_present(dev, \"pwms\")) {\n+\t\tst->manual_mode = false;\n+\t\tret = ad4691_pwm_setup(st);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t} else {\n+\t\tst->manual_mode = true;\n+\t}\n+\n \tswitch (st->vref_uV) {\n \tcase AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:\n \t\tref_val = AD4691_VREF_2P5;\n@@ -636,6 +1070,75 @@ static int ad4691_config(struct ad4691_state *st)\n \treturn 0;\n }\n \n+static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,\n+\t\t\t\t\t struct ad4691_state *st)\n+{\n+\tstruct device *dev = regmap_get_device(st->regmap);\n+\tstruct iio_trigger *trig;\n+\tunsigned int i;\n+\tint irq, ret;\n+\n+\ttrig = devm_iio_trigger_alloc(dev, \"%s-dev%d\", indio_dev->name,\n+\t\t\t\t iio_device_id(indio_dev));\n+\tif (!trig)\n+\t\treturn -ENOMEM;\n+\n+\ttrig->ops = &ad4691_trigger_ops;\n+\tiio_trigger_set_drvdata(trig, st);\n+\n+\tret = devm_iio_trigger_register(dev, trig);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"IIO trigger register failed\\n\");\n+\n+\tindio_dev->trig = iio_trigger_get(trig);\n+\n+\tif (st->manual_mode)\n+\t\treturn devm_iio_triggered_buffer_setup(dev, indio_dev,\n+\t\t\t\t\t\t &iio_pollfunc_store_time,\n+\t\t\t\t\t\t &ad4691_trigger_handler,\n+\t\t\t\t\t\t &ad4691_manual_buffer_setup_ops);\n+\n+\t/*\n+\t * The GP pin named in interrupt-names asserts at end-of-conversion.\n+\t * The IRQ handler stops conversions and fires the IIO trigger so\n+\t * the trigger handler can read and push the sample to the buffer.\n+\t * The IRQ is kept disabled until the buffer is enabled.\n+\t */\n+\tirq = -ENXIO;\n+\tfor (i = 0; i < ARRAY_SIZE(ad4691_gp_names); i++) {\n+\t\tirq = fwnode_irq_get_byname(dev_fwnode(dev),\n+\t\t\t\t\t ad4691_gp_names[i]);\n+\t\tif (irq > 0)\n+\t\t\tbreak;\n+\t}\n+\tif (irq < 0)\n+\t\treturn dev_err_probe(dev, irq, \"failed to get GP interrupt\\n\");\n+\n+\tst->irq = irq;\n+\n+\tret = ad4691_gpio_setup(st, i);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * IRQ is kept disabled until the buffer is enabled to prevent\n+\t * spurious DATA_READY events before the SPI message is set up.\n+\t */\n+\tret = devm_request_threaded_irq(dev, irq, NULL,\n+\t\t\t\t\t&ad4691_irq,\n+\t\t\t\t\tIRQF_ONESHOT | IRQF_NO_AUTOEN,\n+\t\t\t\t\tindio_dev->name, indio_dev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn devm_iio_triggered_buffer_setup_ext(dev, indio_dev,\n+\t\t\t\t\t\t &iio_pollfunc_store_time,\n+\t\t\t\t\t\t &ad4691_trigger_handler,\n+\t\t\t\t\t\t IIO_BUFFER_DIRECTION_IN,\n+\t\t\t\t\t\t &ad4691_cnv_burst_buffer_setup_ops,\n+\t\t\t\t\t\t ad4691_buffer_attrs);\n+}\n+\n static int ad4691_probe(struct spi_device *spi)\n {\n \tstruct device *dev = &spi->dev;\n@@ -648,6 +1151,7 @@ static int ad4691_probe(struct spi_device *spi)\n \t\treturn -ENOMEM;\n \n \tst = iio_priv(indio_dev);\n+\tst->spi = spi;\n \tst->info = spi_get_device_match_data(spi);\n \n \tret = devm_mutex_init(dev, &st->lock);\n@@ -675,8 +1179,11 @@ static int ad4691_probe(struct spi_device *spi)\n \tindio_dev->info = &ad4691_info;\n \tindio_dev->modes = INDIO_DIRECT_MODE;\n \n-\tindio_dev->channels = st->info->channels;\n-\tindio_dev->num_channels = st->info->num_channels;\n+\tindio_dev->channels = st->info->sw_info->channels;\n+\tindio_dev->num_channels = st->info->sw_info->num_channels;\n+\tret = ad4691_setup_triggered_buffer(indio_dev, st);\n+\tif (ret)\n+\t\treturn ret;\n \n \treturn devm_iio_device_register(dev, indio_dev);\n }\n", "prefixes": [ "v8", "3/6" ] }