Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.2/patches/2221509/?format=api
{ "id": 2221509, "url": "http://patchwork.ozlabs.org/api/1.2/patches/2221509/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-gpio/patch/20260409-ad4692-multichannel-sar-adc-driver-v7-3-be375d4df2c5@analog.com/", "project": { "id": 42, "url": "http://patchwork.ozlabs.org/api/1.2/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": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20260409-ad4692-multichannel-sar-adc-driver-v7-3-be375d4df2c5@analog.com>", "list_archive_url": null, "date": "2026-04-09T15:28:24", "name": "[v7,3/6] iio: adc: ad4691: add triggered buffer support", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "69871be913bc19caa9cf0b3feb06a7dc0956993a", "submitter": { "id": 92791, "url": "http://patchwork.ozlabs.org/api/1.2/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/20260409-ad4692-multichannel-sar-adc-driver-v7-3-be375d4df2c5@analog.com/mbox/", "series": [ { "id": 499317, "url": "http://patchwork.ozlabs.org/api/1.2/series/499317/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-gpio/list/?series=499317", "date": "2026-04-09T15:28:22", "name": "iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family", "version": 7, "mbox": "http://patchwork.ozlabs.org/series/499317/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2221509/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2221509/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "\n <linux-gpio+bounces-34961-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=fO4+C9cz;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=172.232.135.74; helo=sto.lore.kernel.org;\n envelope-from=linux-gpio+bounces-34961-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=\"fO4+C9cz\"", "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=10.30.226.201" ], "Received": [ "from sto.lore.kernel.org (sto.lore.kernel.org [172.232.135.74])\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 4fs3qY2Tz5z1yCv\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 10 Apr 2026 01:33:29 +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 46A1A305DA2C\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 9 Apr 2026 15:29:10 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 909933E1211;\n\tThu, 9 Apr 2026 15:28:29 +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 33A473DFC74;\n\tThu, 9 Apr 2026 15:28:29 +0000 (UTC)", "by smtp.kernel.org (Postfix) with ESMTPS id 0A467C2BCB2;\n\tThu, 9 Apr 2026 15:28:29 +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 F3C48F31E2D;\n\tThu, 9 Apr 2026 15:28:28 +0000 (UTC)" ], "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1775748509; cv=none;\n b=fihNkGE5ub1Pks2zU5agV1FH2+ZnILfonFY6ZuYoR8vtue++M/E1H5ZXZ/GWfAFlfnBkt4Zlzyng3AKBn/IHlL5ROeR58krTlpbwYYEp+wA3SvHDbbt44e/246QZO2R82wgDr+ezbDjTwRuGbIIecxRVDseMxM65Yy19xrBMn/s=", "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1775748509; c=relaxed/simple;\n\tbh=QfH9u6O8rzKFyCCXKwsytcBG/RDh9kxkD/2YsQb2msI=;\n\th=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References:\n\t In-Reply-To:To:Cc;\n b=T2epa1zhrSoalleK5klhzCAaBga7lvZZs57KUOX8rWTiNG0DXYnkkndDRJIzqcIxtw0Y5WeWjo3ZBdHIf9xL6HZfToe2231QLXZK4r5lNFnp2vn3jzcSnWM6bLcL88MDI7qgzYJPsiBpWvC/3YL+yO0G1iUpVfbWWsE4U6lfwnQ=", "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=fO4+C9cz; 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=1775748509;\n\tbh=QfH9u6O8rzKFyCCXKwsytcBG/RDh9kxkD/2YsQb2msI=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From;\n\tb=fO4+C9czOvMK2JC1Nhv8TLHU6A7re6NAGaATjDr28qjUDAdKufoxWp01EdIDhQ9AT\n\t jGiTWTet2fuxeN7/bBpXM3SpGLz8EY8tx0lPB7hmo62htdZ9f0X12RYifMJ6WTtC14\n\t letuxKom2NnEHR8aYM4r/HupJQvP/4+sdivetseCv9X1fblTmCU+0UtqhgluVJmuR+\n\t T68IIbjoCGQgXqaJuE14isbvWulr9A3nybYRD3hoymm0q6X2x14MMw4fQXBteuOKTh\n\t gnJWzdGbpUUvRSWIW9h2K1PXyG4zAXa/9FjeR8eBUxzqmiDbAiWr1zUzp0BczI8iHe\n\t l3KWwGxm8OqMQ==", "From": "Radu Sabau via B4 Relay <devnull+radu.sabau.analog.com@kernel.org>", "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-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 <20260409-ad4692-multichannel-sar-adc-driver-v7-3-be375d4df2c5@analog.com>", "References": "\n <20260409-ad4692-multichannel-sar-adc-driver-v7-0-be375d4df2c5@analog.com>", "In-Reply-To": "\n <20260409-ad4692-multichannel-sar-adc-driver-v7-0-be375d4df2c5@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=1775748505; l=24317;\n i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id;\n bh=F0QB2cO663nAuEzGsci2CDTXdKrVe4mcBWEFDCaotLE=;\n b=hjT+EWQDRvx/lIHzm99pn53d3cbYXUXLxgPcrpA7t/36ZuWLZzEctBvA0RcyOu729EbnF4iDT\n HzymbZUq+JBDh84w2eqpBybvTO78Z3XsnoJwLMYO7nSSU/5D9exjSVW", "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 | 553 +++++++++++++++++++++++++++++++++++++++++++++--\n 2 files changed, 540 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 43bd408c3d11..3e5caa0972eb 100644\n--- a/drivers/iio/adc/ad4691.c\n+++ b/drivers/iio/adc/ad4691.c\n@@ -5,15 +5,19 @@\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/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@@ -21,7 +25,14 @@\n #include <linux/units.h>\n #include <linux/unaligned.h>\n \n+#include <linux/iio/buffer.h>\n+#include <linux/iio/buffer-dma.h>\n+#include <linux/iio/buffer-dmaengine.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@@ -30,6 +41,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@@ -37,6 +51,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@@ -44,13 +59,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@@ -60,6 +80,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@@ -83,19 +105,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@@ -105,6 +131,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@@ -125,6 +152,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@@ -136,6 +164,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@@ -162,38 +201,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@@ -201,8 +245,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];\n+\t/* Scan buffer: one BE16 slot per channel (rx'd directly), plus timestamp */\n+\tstruct {\n+\t\t__be16 vals[16];\n+\t\taligned_s64 ts;\n+\t} scan;\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@@ -486,6 +567,346 @@ 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 n_active;\n+\tunsigned int n_xfers;\n+\tunsigned int prev_i, k, i;\n+\tbool first;\n+\tint ret;\n+\n+\tn_active = bitmap_weight(indio_dev->active_scan_mask, iio_get_masklength(indio_dev));\n+\tn_xfers = n_active + 1;\n+\n+\tmemset(st->scan_xfers, 0, n_xfers * sizeof(st->scan_xfers[0]));\n+\tmemset(st->scan_tx, 0, n_xfers * sizeof(st->scan_tx[0]));\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->scan.vals[i];\n+\t\t\tfirst = false;\n+\t\t} else {\n+\t\t\tst->scan_xfers[k].rx_buf = &st->scan.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->scan.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 n_active;\n+\tunsigned int k, i;\n+\tint ret;\n+\n+\tn_active = bitmap_weight(indio_dev->active_scan_mask, iio_get_masklength(indio_dev));\n+\n+\tmemset(st->scan_xfers, 0, (2 * n_active + 2) * sizeof(st->scan_xfers[0]));\n+\tmemset(st->scan_tx, 0, (n_active + 2) * sizeof(st->scan_tx[0]));\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->scan.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\tgoto err_unoptimize;\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+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,\n+\t\t\t AD4691_SEQ_ALL_CHANNELS_OFF);\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, \"%u\\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->scan, sizeof(st->scan),\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@@ -493,6 +914,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@@ -558,6 +991,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@@ -613,6 +1062,76 @@ 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\",\n+\t\t\t\t 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@@ -625,6 +1144,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@@ -652,8 +1172,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": [ "v7", "3/6" ] }